./PaxHeaders/bluez-5.870000644000000000000000000000005015221755522011741 xustar0020 atime=1783094113 20 ctime=1783094098 bluez-5.87/0000755000000000000000000000000015221755522011342 5ustar00rootrootbluez-5.87/PaxHeaders/Makefile.plugins0000644000000000000000000000005015200607060014767 xustar0020 atime=1783092682 20 ctime=1783094098 bluez-5.87/Makefile.plugins0000644000000000000000000000707015200607060014454 0ustar00rootroot# SPDX-License-Identifier: GPL-2.0 builtin_modules += hostname builtin_sources += plugins/hostname.c builtin_modules += autopair builtin_sources += plugins/autopair.c builtin_modules += policy builtin_sources += plugins/policy.c builtin_sources += profiles/audio/media.h profiles/audio/media.c \ profiles/audio/transport.h profiles/audio/transport.c \ profiles/audio/player.h profiles/audio/player.c if ADMIN builtin_modules += admin builtin_sources += plugins/admin.c endif if NFC builtin_modules += neard builtin_sources += plugins/neard.c endif if A2DP builtin_modules += a2dp builtin_sources += profiles/audio/source.h profiles/audio/source.c \ profiles/audio/sink.h profiles/audio/sink.c \ profiles/audio/a2dp.h profiles/audio/a2dp.c \ profiles/audio/avdtp.h profiles/audio/avdtp.c \ profiles/audio/a2dp-codecs.h endif if AVRCP builtin_modules += avrcp builtin_sources += profiles/audio/control.h profiles/audio/control.c \ profiles/audio/avctp.h profiles/audio/avctp.c \ profiles/audio/avrcp.h profiles/audio/avrcp.c \ profiles/audio/avrcp-player.c endif if NETWORK builtin_modules += network builtin_sources += profiles/network/manager.c \ profiles/network/bnep.h profiles/network/bnep.c \ profiles/network/server.h profiles/network/server.c \ profiles/network/connection.h \ profiles/network/connection.c endif if HID builtin_modules += input builtin_sources += profiles/input/manager.c \ profiles/input/server.h profiles/input/server.c \ profiles/input/device.h profiles/input/device.c \ profiles/input/hidp_defs.h profiles/input/sixaxis.h endif if HOG builtin_modules += hog builtin_sources += profiles/input/hog.c \ profiles/input/hog-lib.c profiles/input/hog-lib.h \ profiles/deviceinfo/dis.c profiles/deviceinfo/dis.h \ profiles/battery/bas.c profiles/battery/bas.h \ profiles/scanparam/scpp.c profiles/scanparam/scpp.h \ profiles/input/suspend.h profiles/input/suspend-none.c endif builtin_modules += gap builtin_sources += profiles/gap/gas.c builtin_modules += scanparam builtin_sources += profiles/scanparam/scan.c builtin_modules += deviceinfo builtin_sources += profiles/deviceinfo/deviceinfo.c if MIDI builtin_modules += midi builtin_sources += profiles/midi/midi.c \ profiles/midi/libmidi.h \ profiles/midi/libmidi.c builtin_cppflags += $(ALSA_CFLAGS) builtin_ldadd += $(ALSA_LIBS) endif builtin_modules += battery builtin_sources += profiles/battery/battery.c builtin_modules += rap builtin_sources += profiles/ranging/rap.c \ profiles/ranging/rap_hci.c if SIXAXIS builtin_modules += sixaxis builtin_sources += plugins/sixaxis.c builtin_ldadd += $(UDEV_LIBS) endif if BAP builtin_modules += bap builtin_sources += profiles/audio/bap.c endif if BASS builtin_modules += bass builtin_sources += profiles/audio/bass.c endif if MCP builtin_modules += mcp builtin_sources += profiles/audio/mcp.c endif if VCP builtin_modules += vcp builtin_sources += profiles/audio/vcp.h profiles/audio/vcp.c endif if MICP builtin_modules += micp builtin_sources += profiles/audio/micp.c endif if CCP builtin_modules += ccp builtin_sources += profiles/audio/ccp.c endif if CSIP builtin_modules += csip builtin_sources += profiles/audio/csip.c endif if TMAP builtin_modules += tmap builtin_sources += profiles/audio/tmap.c endif if GMAP builtin_modules += gmap builtin_sources += profiles/audio/gmap.c endif if ASHA builtin_modules += asha builtin_sources += profiles/audio/asha.h profiles/audio/asha.c endif if HFP builtin_modules += hfp builtin_sources += profiles/audio/telephony.h profiles/audio/telephony.c profiles/audio/hfp-hf.c endif bluez-5.87/PaxHeaders/emulator0000644000000000000000000000005015221755532013435 xustar0020 atime=1783094113 20 ctime=1783094106 bluez-5.87/emulator/0000755000000000000000000000000015221755532013173 5ustar00rootrootbluez-5.87/emulator/PaxHeaders/bthost.h0000644000000000000000000000005015142177620015164 xustar0020 atime=1778585135 20 ctime=1783094103 bluez-5.87/emulator/bthost.h0000644000000000000000000002056215142177620014652 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2012 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright 2023-2024 NXP * * */ #include #include #include "bluetooth/bluetooth.h" typedef void (*bthost_send_func) (const struct iovec *iov, int iovlen, void *user_data); struct bthost; struct bthost *bthost_create(void); void bthost_destroy(struct bthost *bthost); typedef void (*bthost_ready_cb) (void); void bthost_notify_ready(struct bthost *bthost, bthost_ready_cb cb); typedef void (*bthost_debug_func_t)(const char *str, void *user_data); typedef void (*bthost_destroy_func_t)(void *user_data); bool bthost_set_debug(struct bthost *bthost, bthost_debug_func_t callback, void *user_data, bthost_destroy_func_t destroy); void bthost_debug(struct bthost *bthost, const char *format, ...) __attribute__((format(printf, 2, 3))); void bthost_set_send_handler(struct bthost *bthost, bthost_send_func handler, void *user_data); void bthost_set_acl_mtu(struct bthost *bthost, uint16_t mtu); void bthost_set_iso_mtu(struct bthost *bthost, uint16_t mtu); void bthost_receive_h4(struct bthost *bthost, const void *data, uint16_t len); typedef void (*bthost_cmd_complete_cb) (uint16_t opcode, uint8_t status, const void *param, uint8_t len, void *user_data); void bthost_set_cmd_complete_cb(struct bthost *bthost, bthost_cmd_complete_cb cb, void *user_data); typedef uint8_t (*bthost_accept_conn_cb) (uint16_t handle, void *user_data); typedef void (*bthost_new_conn_cb) (uint16_t handle, void *user_data); void bthost_set_connect_cb(struct bthost *bthost, bthost_new_conn_cb cb, void *user_data); void bthost_set_sco_cb(struct bthost *bthost, bthost_new_conn_cb cb, void *user_data); void bthost_set_iso_cb(struct bthost *bthost, bthost_accept_conn_cb accept, bthost_new_conn_cb cb, void *user_data); void bthost_hci_connect(struct bthost *bthost, const uint8_t *bdaddr, uint8_t addr_type); void bthost_hci_ext_connect(struct bthost *bthost, const uint8_t *bdaddr, uint8_t addr_type); void bthost_hci_disconnect(struct bthost *bthost, uint16_t handle, uint8_t reason); int bthost_setup_sco(struct bthost *bthost, uint16_t acl_handle, uint16_t setting); typedef void (*bthost_cid_hook_func_t)(const void *data, uint16_t len, void *user_data); void bthost_add_cid_hook(struct bthost *bthost, uint16_t handle, uint16_t cid, bthost_cid_hook_func_t func, void *user_data); typedef void (*bthost_sco_hook_func_t)(const void *data, uint16_t len, uint8_t status, void *user_data); void bthost_add_sco_hook(struct bthost *bthost, uint16_t handle, bthost_sco_hook_func_t func, void *user_data, bthost_destroy_func_t destroy); typedef void (*bthost_iso_hook_func_t)(const void *data, uint16_t len, void *user_data); void bthost_add_iso_hook(struct bthost *bthost, uint16_t handle, bthost_iso_hook_func_t func, void *user_data, bthost_destroy_func_t destroy); void bthost_send_cid(struct bthost *bthost, uint16_t handle, uint16_t cid, const void *data, uint16_t len); void bthost_send_cid_v(struct bthost *bthost, uint16_t handle, uint16_t cid, const struct iovec *iov, int iovcnt); void bthost_send_sco(struct bthost *bthost, uint16_t handle, uint8_t pkt_status, const struct iovec *iov, int iovcnt); void bthost_send_iso(struct bthost *bthost, uint16_t handle, bool ts, uint16_t sn, uint32_t timestamp, uint8_t pkt_status, const struct iovec *iov, int iovcnt); void bthost_disconnect_cid(struct bthost *bthost, uint16_t handle, uint16_t cid); typedef void (*bthost_l2cap_rsp_cb) (uint8_t code, const void *data, uint16_t len, void *user_data); bool bthost_l2cap_req(struct bthost *bthost, uint16_t handle, uint8_t req, const void *data, uint16_t len, bthost_l2cap_rsp_cb cb, void *user_data); void bthost_write_scan_enable(struct bthost *bthost, uint8_t scan); void bthost_set_adv_data(struct bthost *bthost, const uint8_t *data, uint8_t len); void bthost_set_adv_enable(struct bthost *bthost, uint8_t enable); void bthost_set_ext_adv_data(struct bthost *bthost, const uint8_t *data, uint8_t len); void bthost_set_ext_adv_params(struct bthost *bthost, uint8_t sid); void bthost_set_ext_adv_enable(struct bthost *bthost, uint8_t enable); void bthost_set_pa_params(struct bthost *bthost); void bthost_set_pa_data(struct bthost *bthost, const uint8_t *data, uint8_t len); void bthost_set_past_mode(struct bthost *bthost, uint16_t handle, uint8_t mode); void bthost_set_base(struct bthost *bthost, const uint8_t *data, uint8_t len); void bthost_set_pa_enable(struct bthost *bthost, uint8_t enable); void bthost_past_set_info(struct bthost *bthost, uint16_t handle); void bthost_create_big(struct bthost *bthost, uint8_t num_bis, uint8_t enc, const uint8_t *bcode); void bthost_terminate_big(struct bthost *bthost, uint8_t reason); bool bthost_search_ext_adv_addr(struct bthost *bthost, const uint8_t *addr); void bthost_set_cig_params(struct bthost *bthost, uint8_t cig_id, uint8_t cis_id, const struct bt_iso_qos *qos); void bthost_create_cis(struct bthost *bthost, uint16_t cis_handle, uint16_t acl_handle); void bthost_set_scan_params(struct bthost *bthost, uint8_t scan_type, uint8_t addr_type, uint8_t filter_policy); void bthost_set_scan_enable(struct bthost *bthost, uint8_t enable); void bthost_write_ssp_mode(struct bthost *bthost, uint8_t mode); void bthost_write_le_host_supported(struct bthost *bthost, uint8_t mode); void bthost_request_auth(struct bthost *bthost, uint16_t handle); void bthost_le_start_encrypt(struct bthost *bthost, uint16_t handle, const uint8_t ltk[16]); typedef void (*bthost_l2cap_connect_cb) (uint16_t handle, uint16_t cid, void *user_data); typedef void (*bthost_l2cap_disconnect_cb) (uint16_t handle, uint16_t cid, void *user_data); void bthost_add_l2cap_server(struct bthost *bthost, uint16_t psm, bthost_l2cap_connect_cb func, bthost_l2cap_disconnect_cb disconn_func, void *user_data); void bthost_add_l2cap_server_custom(struct bthost *bthost, uint16_t psm, uint16_t mtu, uint16_t mps, uint16_t credits, bthost_l2cap_connect_cb func, bthost_l2cap_disconnect_cb disconn_func, void *user_data); void bthost_set_sc_support(struct bthost *bthost, bool enable); void bthost_set_pin_code(struct bthost *bthost, const uint8_t *pin, uint8_t pin_len); void bthost_set_io_capability(struct bthost *bthost, uint8_t io_capability); uint8_t bthost_get_io_capability(struct bthost *bthost); void bthost_set_auth_req(struct bthost *bthost, uint8_t auth_req); uint8_t bthost_get_auth_req(struct bthost *bthost); void bthost_set_reject_user_confirm(struct bthost *bthost, bool reject); bool bthost_get_reject_user_confirm(struct bthost *bthost); bool bthost_bredr_capable(struct bthost *bthost); uint64_t bthost_conn_get_fixed_chan(struct bthost *bthost, uint16_t handle); typedef void (*bthost_rfcomm_connect_cb) (uint16_t handle, uint16_t cid, void *user_data, bool status); void bthost_add_rfcomm_server(struct bthost *bthost, uint8_t channel, bthost_rfcomm_connect_cb func, void *user_data); bool bthost_connect_rfcomm(struct bthost *bthost, uint16_t handle, uint8_t channel, bthost_rfcomm_connect_cb func, void *user_data); typedef void (*bthost_rfcomm_chan_hook_func_t) (const void *data, uint16_t len, void *user_data); void bthost_add_rfcomm_chan_hook(struct bthost *bthost, uint16_t handle, uint8_t channel, bthost_rfcomm_chan_hook_func_t func, void *user_data); void bthost_send_rfcomm_data(struct bthost *bthost, uint16_t handle, uint8_t channel, const void *data, uint16_t len); void bthost_start(struct bthost *bthost); /* LE SMP support */ void *smp_start(struct bthost *bthost); void smp_stop(void *smp_data); void *smp_conn_add(void *smp_data, uint16_t handle, const uint8_t *ia, uint8_t ia_type, const uint8_t *ra, uint8_t ra_type, bool conn_init); void smp_conn_del(void *conn_data); void smp_conn_encrypted(void *conn_data, uint8_t encrypt); void smp_data(void *conn_data, const void *data, uint16_t len); void smp_bredr_data(void *conn_data, const void *data, uint16_t len); int smp_get_ltk(void *smp_data, uint64_t rand, uint16_t ediv, uint8_t *ltk); void smp_pair(void *conn_data, uint8_t io_cap, uint8_t auth_req); bluez-5.87/emulator/PaxHeaders/phy.c0000644000000000000000000000005014214376354014460 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/emulator/phy.c0000644000000000000000000001264014214376354014144 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2012 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "src/shared/util.h" #include "src/shared/mainloop.h" #include "phy.h" #define BT_PHY_PORT 45023 struct bt_phy { volatile int ref_count; int rx_fd; int tx_fd; uint64_t id; bt_phy_callback_func_t callback; void *user_data; }; struct bt_phy_hdr { uint64_t id; uint32_t flags; uint16_t type; uint16_t len; } __attribute__ ((packed)); static bool get_random_bytes(void *buf, size_t num_bytes) { ssize_t len; int fd; fd = open("/dev/urandom", O_RDONLY); if (fd < 0) return false; len = read(fd, buf, num_bytes); close(fd); if (len < 0) return false; return true; } static void phy_rx_callback(int fd, uint32_t events, void *user_data) { struct bt_phy *phy = user_data; struct msghdr msg; struct iovec iov[2]; struct bt_phy_hdr hdr; unsigned char buf[4096]; ssize_t len; if (events & (EPOLLERR | EPOLLHUP)) { mainloop_remove_fd(fd); return; } iov[0].iov_base = &hdr; iov[0].iov_len = sizeof(hdr); iov[1].iov_base = buf; iov[1].iov_len = sizeof(buf); memset(&msg, 0, sizeof(msg)); msg.msg_iov = iov; msg.msg_iovlen = 2; len = recvmsg(phy->rx_fd, &msg, MSG_DONTWAIT); if (len < 0) return; if ((size_t) len < sizeof(hdr)) return; if (le64_to_cpu(hdr.id) == phy->id) return; if (len - sizeof(hdr) != le16_to_cpu(hdr.len)) return; if (phy->callback) phy->callback(le16_to_cpu(hdr.type), buf, len - sizeof(hdr), phy->user_data); } static int create_rx_socket(void) { struct sockaddr_in addr; int fd, opt = 1; fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (fd < 0) return -1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { close(fd); return -1; } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(BT_PHY_PORT); addr.sin_addr.s_addr = INADDR_BROADCAST; if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { close(fd); return -1; } return fd; } static int create_tx_socket(void) { int fd, opt = 1; fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (fd < 0) return -1; if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt)) < 0) { close(fd); return -1; } return fd; } struct bt_phy *bt_phy_new(void) { struct bt_phy *phy; phy = calloc(1, sizeof(*phy)); if (!phy) return NULL; phy->rx_fd = create_rx_socket(); if (phy->rx_fd < 0) { free(phy); return NULL; } phy->tx_fd = create_tx_socket(); if (phy->tx_fd < 0) { close(phy->rx_fd); free(phy); return NULL; } mainloop_add_fd(phy->rx_fd, EPOLLIN, phy_rx_callback, phy, NULL); if (!get_random_bytes(&phy->id, sizeof(phy->id))) { if (util_getrandom(&phy->id, sizeof(phy->id), 0) < 0) { mainloop_remove_fd(phy->rx_fd); close(phy->tx_fd); close(phy->rx_fd); free(phy); return NULL; } } bt_phy_send(phy, BT_PHY_PKT_NULL, NULL, 0); return bt_phy_ref(phy); } struct bt_phy *bt_phy_ref(struct bt_phy *phy) { if (!phy) return NULL; __sync_fetch_and_add(&phy->ref_count, 1); return phy; } void bt_phy_unref(struct bt_phy *phy) { if (!phy) return; if (__sync_sub_and_fetch(&phy->ref_count, 1)) return; mainloop_remove_fd(phy->rx_fd); close(phy->tx_fd); close(phy->rx_fd); free(phy); } bool bt_phy_send(struct bt_phy *phy, uint16_t type, const void *data, size_t size) { return bt_phy_send_vector(phy, type, data, size, NULL, 0, NULL, 0); } bool bt_phy_send_vector(struct bt_phy *phy, uint16_t type, const void *data1, size_t size1, const void *data2, size_t size2, const void *data3, size_t size3) { struct bt_phy_hdr hdr; struct sockaddr_in addr; struct msghdr msg; struct iovec iov[4]; ssize_t len; if (!phy) return false; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(BT_PHY_PORT); addr.sin_addr.s_addr = INADDR_BROADCAST; memset(&msg, 0, sizeof(msg)); msg.msg_name = &addr; msg.msg_namelen = sizeof(addr); msg.msg_iov = iov; msg.msg_iovlen = 0; memset(&hdr, 0, sizeof(hdr)); hdr.id = cpu_to_le64(phy->id); hdr.flags = cpu_to_le32(0); hdr.type = cpu_to_le16(type); hdr.len = cpu_to_le16(size1 + size2 + size3); iov[msg.msg_iovlen].iov_base = &hdr; iov[msg.msg_iovlen].iov_len = sizeof(hdr); msg.msg_iovlen++; if (data1 && size1 > 0) { iov[msg.msg_iovlen].iov_base = (void *) data1; iov[msg.msg_iovlen].iov_len = size1; msg.msg_iovlen++; } if (data2 && size2 > 0) { iov[msg.msg_iovlen].iov_base = (void *) data2; iov[msg.msg_iovlen].iov_len = size2; msg.msg_iovlen++; } if (data3 && size3 > 0) { iov[msg.msg_iovlen].iov_base = (void *) data3; iov[msg.msg_iovlen].iov_len = size3; msg.msg_iovlen++; } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(BT_PHY_PORT); addr.sin_addr.s_addr = INADDR_BROADCAST; len = sendmsg(phy->tx_fd, &msg, MSG_DONTWAIT); if (len < 0) return false; return true; } bool bt_phy_register(struct bt_phy *phy, bt_phy_callback_func_t callback, void *user_data) { if (!phy) return false; phy->callback = callback; phy->user_data = user_data; return true; } bluez-5.87/emulator/PaxHeaders/hfp.c0000644000000000000000000000005014015011623014415 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/emulator/hfp.c0000644000000000000000000000352114015011623014077 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2012 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "src/shared/mainloop.h" #include "src/shared/hfp.h" static void hfp_debug(const char *str, void *user_data) { const char *prefix = user_data; printf("%s%s\n", prefix, str); } static void command_handler(const char *command, void *user_data) { struct hfp_gw *hfp = user_data; printf("Command: %s\n", command); hfp_gw_send_result(hfp, HFP_RESULT_ERROR); } static bool open_connection(void) { static const char SOCKET_PATH[] = "\0hfp-headset"; struct hfp_gw *hfp; struct sockaddr_un addr; int fd; fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); if (fd < 0) { perror("Failed to create Unix socket"); return false; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; memcpy(addr.sun_path, SOCKET_PATH, sizeof(SOCKET_PATH)); if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { perror("Failed to connect Unix socket"); close(fd); return false; } hfp = hfp_gw_new(fd); if (!hfp) { close(fd); return false; } hfp_gw_set_close_on_unref(hfp, true); hfp_gw_set_debug(hfp, hfp_debug, "HFP: ", NULL); hfp_gw_set_command_handler(hfp, command_handler, hfp, NULL); return true; } static void signal_callback(int signum, void *user_data) { switch (signum) { case SIGINT: case SIGTERM: mainloop_quit(); break; } } int main(int argc, char *argv[]) { mainloop_init(); if (!open_connection()) return EXIT_FAILURE; return mainloop_run_with_signal(signal_callback, NULL); } bluez-5.87/emulator/PaxHeaders/hciemu.h0000644000000000000000000000005015200607060015122 xustar0020 atime=1783092683 20 ctime=1783094106 bluez-5.87/emulator/hciemu.h0000644000000000000000000000623715200607060014613 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #include #include struct hciemu; struct hciemu_client; enum hciemu_type { HCIEMU_TYPE_BREDRLE, HCIEMU_TYPE_BREDR, HCIEMU_TYPE_LE, HCIEMU_TYPE_LEGACY, HCIEMU_TYPE_BREDRLE50, HCIEMU_TYPE_BREDRLE52, HCIEMU_TYPE_BREDRLE60, }; enum hciemu_hook_type { HCIEMU_HOOK_PRE_CMD, HCIEMU_HOOK_POST_CMD, HCIEMU_HOOK_PRE_EVT, HCIEMU_HOOK_POST_EVT, }; typedef void (*hciemu_debug_func_t)(const char *str, void *user_data); typedef void (*hciemu_destroy_func_t)(void *user_data); struct hciemu *hciemu_new(enum hciemu_type type); struct hciemu *hciemu_new_num(enum hciemu_type type, uint8_t num); struct hciemu *hciemu_new_debug(enum hciemu_type type, hciemu_debug_func_t callback, void *user_data, hciemu_destroy_func_t destroy); struct hciemu *hciemu_new_num_debug(enum hciemu_type type, uint8_t num, hciemu_debug_func_t callback, void *user_data, hciemu_destroy_func_t destroy); struct hciemu *hciemu_ref(struct hciemu *hciemu); void hciemu_unref(struct hciemu *hciemu); struct hciemu_client *hciemu_get_client(struct hciemu *hciemu, int num); struct bthost *hciemu_client_host(struct hciemu_client *client); const uint8_t *hciemu_client_bdaddr(struct hciemu_client *client); bool hciemu_set_client_bdaddr(struct hciemu_client *client, const uint8_t *bdaddr); bool hciemu_set_debug(struct hciemu *hciemu, hciemu_debug_func_t callback, void *user_data, hciemu_destroy_func_t destroy); struct vhci *hciemu_get_vhci(struct hciemu *hciemu); struct bthost *hciemu_client_get_host(struct hciemu *hciemu); /* Process pending client events before new VHCI events */ void hciemu_flush_client_events(struct hciemu *hciemu); const char *hciemu_get_address(struct hciemu *hciemu); uint8_t *hciemu_get_features(struct hciemu *hciemu); uint8_t *hciemu_get_commands(struct hciemu *hciemu); const uint8_t *hciemu_get_central_bdaddr(struct hciemu *hciemu); const uint8_t *hciemu_get_client_bdaddr(struct hciemu *hciemu); uint8_t hciemu_get_central_scan_enable(struct hciemu *hciemu); uint8_t hciemu_get_central_le_scan_enable(struct hciemu *hciemu); void hciemu_set_central_le_states(struct hciemu *hciemu, const uint8_t *le_states); void hciemu_set_central_le_al_len(struct hciemu *hciemu, uint8_t len); void hciemu_set_central_le_rl_len(struct hciemu *hciemu, uint8_t len); const uint8_t *hciemu_get_central_adv_addr(struct hciemu *hciemu, uint8_t handle); typedef void (*hciemu_command_func_t)(uint16_t opcode, const void *data, uint8_t len, void *user_data); typedef bool (*hciemu_hook_func_t)(const void *data, uint16_t len, void *user_data); bool hciemu_add_central_post_command_hook(struct hciemu *hciemu, hciemu_command_func_t function, void *user_data); bool hciemu_clear_central_post_command_hooks(struct hciemu *hciemu); int hciemu_add_hook(struct hciemu *hciemu, enum hciemu_hook_type type, uint16_t opcode, hciemu_hook_func_t function, void *user_data); bool hciemu_del_hook(struct hciemu *hciemu, enum hciemu_hook_type type, uint16_t opcode); bluez-5.87/emulator/PaxHeaders/smp.c0000644000000000000000000000005015063045254014452 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/emulator/smp.c0000644000000000000000000005056315063045254014144 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2013-2014 Intel Corporation * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "src/shared/util.h" #include "src/shared/crypto.h" #include "src/shared/ecc.h" #include "monitor/bt.h" #include "bthost.h" #define SMP_CID 0x0006 #define SMP_BREDR_CID 0x0007 #define L2CAP_FC_SMP_BREDR 0x80 #define SMP_PASSKEY_ENTRY_FAILED 0x01 #define SMP_OOB_NOT_AVAIL 0x02 #define SMP_AUTH_REQUIREMENTS 0x03 #define SMP_CONFIRM_FAILED 0x04 #define SMP_PAIRING_NOTSUPP 0x05 #define SMP_ENC_KEY_SIZE 0x06 #define SMP_CMD_NOTSUPP 0x07 #define SMP_UNSPECIFIED 0x08 #define SMP_REPEATED_ATTEMPTS 0x09 #define SMP_INVALID_PARAMS 0x0a #define SMP_DHKEY_CHECK_FAILED 0x0b #define SMP_NUMERIC_COMP_FAILED 0x0c #define SMP_BREDR_PAIRING_IN_PROGRESS 0x0d #define DIST_ENC_KEY 0x01 #define DIST_ID_KEY 0x02 #define DIST_SIGN 0x04 #define DIST_LINK_KEY 0x08 #define SC_NO_DIST (DIST_ENC_KEY | DIST_LINK_KEY) #define MAX_IO_CAP 0x04 #define SMP_AUTH_NONE 0x00 #define SMP_AUTH_BONDING 0x01 #define SMP_AUTH_MITM 0x04 #define SMP_AUTH_SC 0x08 #define SMP_AUTH_KEYPRESS 0x10 struct smp { struct bthost *bthost; struct smp_conn *conn; struct bt_crypto *crypto; }; struct smp_conn { struct smp *smp; uint16_t handle; uint8_t addr_type; bool out; bool sc; bool initiator; uint8_t method; uint8_t local_key_dist; uint8_t remote_key_dist; uint8_t ia[6]; uint8_t ia_type; uint8_t ra[6]; uint8_t ra_type; uint8_t tk[16]; uint8_t prnd[16]; uint8_t rrnd[16]; uint8_t pcnf[16]; uint8_t preq[7]; uint8_t prsp[7]; uint8_t ltk[16]; uint8_t local_sk[32]; uint8_t local_pk[64]; uint8_t remote_pk[64]; uint8_t dhkey[32]; uint8_t mackey[16]; uint8_t passkey_notify; uint8_t passkey_round; }; enum { JUST_WORKS, JUST_CFM, REQ_PASSKEY, CFM_PASSKEY, REQ_OOB, DSP_PASSKEY, OVERLAP, }; static const uint8_t gen_method[5][5] = { { JUST_WORKS, JUST_CFM, REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY }, { JUST_WORKS, JUST_CFM, REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY }, { CFM_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY }, { JUST_WORKS, JUST_CFM, JUST_WORKS, JUST_WORKS, JUST_CFM }, { CFM_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, OVERLAP }, }; static const uint8_t sc_method[5][5] = { { JUST_WORKS, JUST_CFM, REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY }, { JUST_WORKS, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY }, { DSP_PASSKEY, DSP_PASSKEY, REQ_PASSKEY, JUST_WORKS, DSP_PASSKEY }, { JUST_WORKS, JUST_CFM, JUST_WORKS, JUST_WORKS, JUST_CFM }, { DSP_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY }, }; static uint8_t get_auth_method(struct smp_conn *conn, uint8_t local_io, uint8_t remote_io) { /* If either side has unknown io_caps, use JUST_CFM (which gets * converted later to JUST_WORKS if we're initiators. */ if (local_io > MAX_IO_CAP || remote_io > MAX_IO_CAP) return JUST_CFM; if (conn->sc) return sc_method[remote_io][local_io]; return gen_method[remote_io][local_io]; } static uint8_t sc_select_method(struct smp_conn *conn) { struct bt_l2cap_smp_pairing_request *local, *remote; uint8_t local_mitm, remote_mitm, local_io, remote_io, method; if (conn->out) { local = (void *) &conn->preq[1]; remote = (void *) &conn->prsp[1]; } else { local = (void *) &conn->prsp[1]; remote = (void *) &conn->preq[1]; } local_io = local->io_capa; remote_io = remote->io_capa; local_mitm = (local->auth_req & SMP_AUTH_MITM); remote_mitm = (remote->auth_req & SMP_AUTH_MITM); /* If either side wants MITM, look up the method from the table, * otherwise use JUST WORKS. */ if (local_mitm || remote_mitm) method = get_auth_method(conn, local_io, remote_io); else method = JUST_WORKS; /* Don't confirm locally initiated pairing attempts */ if (method == JUST_CFM && conn->initiator) method = JUST_WORKS; return method; } static uint8_t key_dist(struct bthost *host) { if (!bthost_bredr_capable(host)) return (DIST_ENC_KEY | DIST_ID_KEY | DIST_SIGN); return (DIST_ENC_KEY | DIST_ID_KEY | DIST_SIGN | DIST_LINK_KEY); } static void smp_send(struct smp_conn *conn, uint8_t smp_cmd, const void *data, uint8_t len) { struct iovec iov[2]; uint16_t cid; iov[0].iov_base = &smp_cmd; iov[0].iov_len = 1; iov[1].iov_base = (void *) data; iov[1].iov_len = len; if (conn->addr_type == BDADDR_BREDR) cid = SMP_BREDR_CID; else cid = SMP_CID; bthost_send_cid_v(conn->smp->bthost, conn->handle, cid, iov, 2); } static bool send_public_key(struct smp_conn *conn) { if (!ecc_make_key(conn->local_pk, conn->local_sk)) return false; smp_send(conn, BT_L2CAP_SMP_PUBLIC_KEY, conn->local_pk, 64); return true; } static void sc_dhkey_check(struct smp_conn *conn) { uint8_t io_cap[3], r[16], a[7], b[7], *local_addr, *remote_addr; struct bt_l2cap_smp_dhkey_check check; memcpy(a, conn->ia, 6); memcpy(b, conn->ra, 6); a[6] = conn->ia_type; b[6] = conn->ra_type; if (conn->out) { local_addr = a; remote_addr = b; memcpy(io_cap, &conn->preq[1], 3); } else { local_addr = b; remote_addr = a; memcpy(io_cap, &conn->prsp[1], 3); } memset(r, 0, sizeof(r)); bt_crypto_f6(conn->smp->crypto, conn->mackey, conn->prnd, conn->rrnd, r, io_cap, local_addr, remote_addr, check.e); smp_send(conn, BT_L2CAP_SMP_DHKEY_CHECK, &check, sizeof(check)); } static void sc_mackey_and_ltk(struct smp_conn *conn) { uint8_t *na, *nb, a[7], b[7]; if (conn->out) { na = conn->prnd; nb = conn->rrnd; } else { na = conn->rrnd; nb = conn->prnd; } memcpy(a, conn->ia, 6); memcpy(b, conn->ra, 6); a[6] = conn->ia_type; b[6] = conn->ra_type; bt_crypto_f5(conn->smp->crypto, conn->dhkey, na, nb, a, b, conn->mackey, conn->ltk); } static uint8_t sc_passkey_send_confirm(struct smp_conn *conn) { struct bt_l2cap_smp_pairing_confirm cfm; uint8_t r; r = ((conn->passkey_notify >> conn->passkey_round) & 0x01); r |= 0x80; if (!bt_crypto_f4(conn->smp->crypto, conn->local_pk, conn->remote_pk, conn->prnd, r, cfm.value)) return SMP_UNSPECIFIED; smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, &cfm, sizeof(cfm)); return 0; } static uint8_t sc_passkey_round(struct smp_conn *conn, uint8_t smp_op) { uint8_t cfm[16], r; /* Ignore the PDU if we've already done 20 rounds (0 - 19) */ if (conn->passkey_round >= 20) return 0; switch (smp_op) { case BT_L2CAP_SMP_PAIRING_RANDOM: r = ((conn->passkey_notify >> conn->passkey_round) & 0x01); r |= 0x80; if (!bt_crypto_f4(conn->smp->crypto, conn->remote_pk, conn->local_pk, conn->rrnd, r, cfm)) return SMP_UNSPECIFIED; if (memcmp(conn->pcnf, cfm, 16)) return SMP_CONFIRM_FAILED; conn->passkey_round++; if (conn->passkey_round == 20) { /* Generate MacKey and LTK */ sc_mackey_and_ltk(conn); } /* The round is only complete when the initiator * receives pairing random. */ if (!conn->out) { smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd, sizeof(conn->prnd)); return 0; } /* Start the next round */ if (conn->passkey_round != 20) return sc_passkey_round(conn, 0); /* Passkey rounds are complete - start DHKey Check */ sc_dhkey_check(conn); break; case BT_L2CAP_SMP_PAIRING_CONFIRM: if (conn->out) { smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd, sizeof(conn->prnd)); return 0; } return sc_passkey_send_confirm(conn); case BT_L2CAP_SMP_PUBLIC_KEY: default: /* Initiating device starts the round */ if (!conn->out) return 0; return sc_passkey_send_confirm(conn); } return 0; } static bool verify_random(struct smp_conn *conn, const uint8_t rnd[16]) { uint8_t confirm[16]; if (!bt_crypto_c1(conn->smp->crypto, conn->tk, conn->rrnd, conn->prsp, conn->preq, conn->ia_type, conn->ia, conn->ra_type, conn->ra, confirm)) return false; if (memcmp(conn->pcnf, confirm, sizeof(conn->pcnf)) != 0) { bthost_debug(conn->smp->bthost, "Confirmation values don't match"); return false; } if (conn->out) { bt_crypto_s1(conn->smp->crypto, conn->tk, conn->rrnd, conn->prnd, conn->ltk); bthost_le_start_encrypt(conn->smp->bthost, conn->handle, conn->ltk); } else { bt_crypto_s1(conn->smp->crypto, conn->tk, conn->prnd, conn->rrnd, conn->ltk); } return true; } static void distribute_keys(struct smp_conn *conn) { uint8_t buf[16]; if (conn->local_key_dist & DIST_ENC_KEY) { memset(buf, 0, sizeof(buf)); smp_send(conn, BT_L2CAP_SMP_ENCRYPT_INFO, buf, sizeof(buf)); smp_send(conn, BT_L2CAP_SMP_CENTRAL_IDENT, buf, 10); } if (conn->local_key_dist & DIST_ID_KEY) { memset(buf, 0, sizeof(buf)); smp_send(conn, BT_L2CAP_SMP_IDENT_INFO, buf, sizeof(buf)); memset(buf, 0, sizeof(buf)); if (conn->out) { buf[0] = conn->ia_type; memcpy(&buf[1], conn->ia, 6); } else { buf[0] = conn->ra_type; memcpy(&buf[1], conn->ra, 6); } smp_send(conn, BT_L2CAP_SMP_IDENT_ADDR_INFO, buf, 7); } if (conn->local_key_dist & DIST_SIGN) { memset(buf, 0, sizeof(buf)); smp_send(conn, BT_L2CAP_SMP_SIGNING_INFO, buf, sizeof(buf)); } } static void pairing_req(struct smp_conn *conn, const void *data, uint16_t len) { struct bthost *bthost = conn->smp->bthost; struct bt_l2cap_smp_pairing_response rsp; memcpy(conn->preq, data, sizeof(conn->preq)); if (conn->addr_type == BDADDR_BREDR) { rsp.io_capa = 0x00; rsp.oob_data = 0x00; rsp.auth_req = 0x00; } else { rsp.io_capa = bthost_get_io_capability(bthost); rsp.oob_data = 0x00; rsp.auth_req = bthost_get_auth_req(bthost); } rsp.max_key_size = 0x10; rsp.init_key_dist = conn->preq[5] & key_dist(bthost); rsp.resp_key_dist = conn->preq[6] & key_dist(bthost); conn->prsp[0] = BT_L2CAP_SMP_PAIRING_RESPONSE; memcpy(&conn->prsp[1], &rsp, sizeof(rsp)); conn->local_key_dist = rsp.resp_key_dist; conn->remote_key_dist = rsp.init_key_dist; if (((conn->prsp[3] & 0x08) && (conn->preq[3] & 0x08)) || conn->addr_type == BDADDR_BREDR) { conn->sc = true; conn->local_key_dist &= ~SC_NO_DIST; conn->remote_key_dist &= ~SC_NO_DIST; } smp_send(conn, BT_L2CAP_SMP_PAIRING_RESPONSE, &rsp, sizeof(rsp)); if (conn->addr_type == BDADDR_BREDR) distribute_keys(conn); } static void pairing_rsp(struct smp_conn *conn, const void *data, uint16_t len) { struct smp *smp = conn->smp; uint8_t cfm[16]; memcpy(conn->prsp, data, sizeof(conn->prsp)); conn->local_key_dist = conn->prsp[5]; conn->remote_key_dist = conn->prsp[6]; if (conn->addr_type == BDADDR_BREDR) { conn->local_key_dist &= ~SC_NO_DIST; conn->remote_key_dist &= ~SC_NO_DIST; distribute_keys(conn); return; } if (((conn->prsp[3] & 0x08) && (conn->preq[3] & 0x08)) || conn->addr_type == BDADDR_BREDR) { conn->sc = true; conn->local_key_dist &= ~SC_NO_DIST; conn->remote_key_dist &= ~SC_NO_DIST; if (conn->addr_type == BDADDR_BREDR) distribute_keys(conn); else send_public_key(conn); return; } bt_crypto_c1(smp->crypto, conn->tk, conn->prnd, conn->prsp, conn->preq, conn->ia_type, conn->ia, conn->ra_type, conn->ra, cfm); smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, cfm, sizeof(cfm)); } static void sc_check_confirm(struct smp_conn *conn) { if (conn->method == REQ_PASSKEY || conn->method == DSP_PASSKEY) { sc_passkey_round(conn, BT_L2CAP_SMP_PAIRING_CONFIRM); return; } if (conn->out) smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd, sizeof(conn->prnd)); } static void pairing_cfm(struct smp_conn *conn, const void *data, uint16_t len) { uint8_t rsp[16]; memcpy(conn->pcnf, data + 1, 16); if (conn->sc) { sc_check_confirm(conn); return; } if (conn->out) { memset(rsp, 0, sizeof(rsp)); smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd, sizeof(conn->prnd)); } else { bt_crypto_c1(conn->smp->crypto, conn->tk, conn->prnd, conn->prsp, conn->preq, conn->ia_type, conn->ia, conn->ra_type, conn->ra, rsp); smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, rsp, sizeof(rsp)); } } static uint8_t sc_random(struct smp_conn *conn) { /* Passkey entry has special treatment */ if (conn->method == REQ_PASSKEY || conn->method == DSP_PASSKEY) return sc_passkey_round(conn, BT_L2CAP_SMP_PAIRING_RANDOM); if (conn->out) { uint8_t cfm[16]; bt_crypto_f4(conn->smp->crypto, conn->remote_pk, conn->local_pk, conn->rrnd, 0, cfm); if (memcmp(conn->pcnf, cfm, 16)) return 0x04; /* Confirm Value Failed */ } else { smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd, 16); } sc_mackey_and_ltk(conn); if (conn->out) sc_dhkey_check(conn); return 0; } static void pairing_rnd(struct smp_conn *conn, const void *data, uint16_t len) { memcpy(conn->rrnd, data + 1, 16); if (conn->sc) { uint8_t reason = sc_random(conn); if (reason) smp_send(conn, BT_L2CAP_SMP_PAIRING_FAILED, &reason, sizeof(reason)); return; } if (!verify_random(conn, data + 1)) return; if (conn->out) return; smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd, sizeof(conn->prnd)); } static void encrypt_info(struct smp_conn *conn, const void *data, uint16_t len) { } static void central_ident(struct smp_conn *conn, const void *data, uint16_t len) { conn->remote_key_dist &= ~DIST_ENC_KEY; if (conn->out && !conn->remote_key_dist) distribute_keys(conn); } static void ident_addr_info(struct smp_conn *conn, const void *data, uint16_t len) { } static void ident_info(struct smp_conn *conn, const void *data, uint16_t len) { conn->remote_key_dist &= ~DIST_ID_KEY; if (conn->out && !conn->remote_key_dist) distribute_keys(conn); } static void signing_info(struct smp_conn *conn, const void *data, uint16_t len) { conn->remote_key_dist &= ~DIST_SIGN; if (conn->out && !conn->remote_key_dist) distribute_keys(conn); } static void public_key(struct smp_conn *conn, const void *data, uint16_t len) { struct smp *smp = conn->smp; uint8_t buf[16]; memcpy(conn->remote_pk, data + 1, 64); if (!conn->out) { if (!send_public_key(conn)) return; } if (!ecdh_shared_secret(conn->remote_pk, conn->local_sk, conn->dhkey)) return; conn->method = sc_select_method(conn); if (conn->method == DSP_PASSKEY || conn->method == REQ_PASSKEY) { sc_passkey_round(conn, BT_L2CAP_SMP_PUBLIC_KEY); return; } if (conn->out) return; if (!bt_crypto_f4(smp->crypto, conn->local_pk, conn->remote_pk, conn->prnd, 0, buf)) return; smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, buf, sizeof(buf)); } static void dhkey_check(struct smp_conn *conn, const void *data, uint16_t len) { const struct bt_l2cap_smp_dhkey_check *cmd = data + 1; uint8_t a[7], b[7], *local_addr, *remote_addr; uint8_t io_cap[3], r[16], e[16]; memcpy(a, &conn->ia, 6); memcpy(b, &conn->ra, 6); a[6] = conn->ia_type; b[6] = conn->ra_type; if (conn->out) { local_addr = a; remote_addr = b; memcpy(io_cap, &conn->prsp[1], 3); } else { local_addr = b; remote_addr = a; memcpy(io_cap, &conn->preq[1], 3); } memset(r, 0, sizeof(r)); if (conn->method == REQ_PASSKEY || conn->method == DSP_PASSKEY) put_le32(conn->passkey_notify, r); if (!bt_crypto_f6(conn->smp->crypto, conn->mackey, conn->rrnd, conn->prnd, r, io_cap, remote_addr, local_addr, e)) return; if (memcmp(cmd->e, e, 16)) { uint8_t reason = 0x0b; /* DHKey Check Failed */ smp_send(conn, BT_L2CAP_SMP_PAIRING_FAILED, &reason, sizeof(reason)); } if (conn->out) bthost_le_start_encrypt(conn->smp->bthost, conn->handle, conn->ltk); else sc_dhkey_check(conn); } void smp_pair(void *conn_data, uint8_t io_cap, uint8_t auth_req) { struct smp_conn *conn = conn_data; struct bt_l2cap_smp_pairing_request req; req.io_capa = io_cap; req.oob_data = 0x00; req.auth_req = auth_req; req.max_key_size = 0x10; req.init_key_dist = key_dist(conn->smp->bthost); req.resp_key_dist = key_dist(conn->smp->bthost); conn->preq[0] = BT_L2CAP_SMP_PAIRING_REQUEST; memcpy(&conn->preq[1], &req, sizeof(req)); smp_send(conn, BT_L2CAP_SMP_PAIRING_REQUEST, &req, sizeof(req)); } void smp_data(void *conn_data, const void *data, uint16_t len) { struct smp_conn *conn = conn_data; uint8_t opcode; if (len < 1) { bthost_debug(conn->smp->bthost, "Received too small SMP PDU"); return; } if (conn->addr_type == BDADDR_BREDR) { bthost_debug(conn->smp->bthost, "Received BR/EDR SMP data on LE link"); return; } opcode = *((const uint8_t *) data); switch (opcode) { case BT_L2CAP_SMP_PAIRING_REQUEST: pairing_req(conn, data, len); break; case BT_L2CAP_SMP_PAIRING_RESPONSE: pairing_rsp(conn, data, len); break; case BT_L2CAP_SMP_PAIRING_CONFIRM: pairing_cfm(conn, data, len); break; case BT_L2CAP_SMP_PAIRING_RANDOM: pairing_rnd(conn, data, len); break; case BT_L2CAP_SMP_ENCRYPT_INFO: encrypt_info(conn, data, len); break; case BT_L2CAP_SMP_CENTRAL_IDENT: central_ident(conn, data, len); break; case BT_L2CAP_SMP_IDENT_ADDR_INFO: ident_addr_info(conn, data, len); break; case BT_L2CAP_SMP_IDENT_INFO: ident_info(conn, data, len); break; case BT_L2CAP_SMP_SIGNING_INFO: signing_info(conn, data, len); break; case BT_L2CAP_SMP_PUBLIC_KEY: public_key(conn, data, len); break; case BT_L2CAP_SMP_DHKEY_CHECK: dhkey_check(conn, data, len); break; default: break; } } void smp_bredr_data(void *conn_data, const void *data, uint16_t len) { struct smp_conn *conn = conn_data; uint8_t opcode; if (len < 1) { bthost_debug(conn->smp->bthost, "Received too small SMP PDU"); return; } if (conn->addr_type != BDADDR_BREDR) { bthost_debug(conn->smp->bthost, "Received LE SMP data on BR/EDR link"); return; } opcode = *((const uint8_t *) data); switch (opcode) { case BT_L2CAP_SMP_PAIRING_REQUEST: pairing_req(conn, data, len); break; case BT_L2CAP_SMP_PAIRING_RESPONSE: pairing_rsp(conn, data, len); break; default: break; } } int smp_get_ltk(void *smp_data, uint64_t rand, uint16_t ediv, uint8_t *ltk) { struct smp_conn *conn = smp_data; static const uint8_t no_ltk[16] = { 0 }; if (!memcmp(conn->ltk, no_ltk, 16)) return -ENOENT; memcpy(ltk, conn->ltk, 16); return 0; } static void smp_conn_bredr(struct smp_conn *conn, uint8_t encrypt) { struct smp *smp = conn->smp; struct bt_l2cap_smp_pairing_request req; uint64_t fixed_chan; if (encrypt != 0x02) return; conn->sc = true; if (!conn->out) return; fixed_chan = bthost_conn_get_fixed_chan(smp->bthost, conn->handle); if (!(fixed_chan & L2CAP_FC_SMP_BREDR)) return; memset(&req, 0, sizeof(req)); req.max_key_size = 0x10; req.init_key_dist = key_dist(smp->bthost); req.resp_key_dist = key_dist(smp->bthost); smp_send(conn, BT_L2CAP_SMP_PAIRING_REQUEST, &req, sizeof(req)); } void smp_conn_encrypted(void *conn_data, uint8_t encrypt) { struct smp_conn *conn = conn_data; if (!encrypt) return; if (conn->addr_type == BDADDR_BREDR) { smp_conn_bredr(conn, encrypt); return; } if (conn->out && conn->remote_key_dist) return; distribute_keys(conn); } static uint8_t type2hci(uint8_t addr_type) { switch (addr_type) { case BDADDR_BREDR: case BDADDR_LE_PUBLIC: return LE_PUBLIC_ADDRESS; case BDADDR_LE_RANDOM: return LE_RANDOM_ADDRESS; } return 0x00; } void *smp_conn_add(void *smp_data, uint16_t handle, const uint8_t *ia, uint8_t ia_type, const uint8_t *ra, uint8_t ra_type, bool conn_init) { struct smp *smp = smp_data; struct smp_conn *conn; char ia_str[18], ra_str[18]; conn = malloc(sizeof(struct smp_conn)); if (!conn) return NULL; memset(conn, 0, sizeof(*conn)); conn->smp = smp; conn->handle = handle; conn->out = conn_init; conn->addr_type = conn_init ? ia_type : ra_type; conn->ia_type = type2hci(ia_type); conn->ra_type = type2hci(ra_type); memcpy(conn->ia, ia, 6); memcpy(conn->ra, ra, 6); ba2str((bdaddr_t *) ia, ia_str); ba2str((bdaddr_t *) ra, ra_str); bthost_debug(smp->bthost, "ia %s type 0x%02x ra %s type 0x%02x", ia_str, ia_type, ra_str, ra_type); bt_crypto_random_bytes(smp->crypto, conn->prnd, sizeof(conn->prnd)); return conn; } void smp_conn_del(void *conn_data) { struct smp_conn *conn = conn_data; free(conn); } void *smp_start(struct bthost *bthost) { struct smp *smp; smp = malloc(sizeof(struct smp)); if (!smp) return NULL; memset(smp, 0, sizeof(*smp)); smp->crypto = bt_crypto_new(); if (!smp->crypto) { free(smp); return NULL; } smp->bthost = bthost; return smp; } void smp_stop(void *smp_data) { struct smp *smp = smp_data; bt_crypto_unref(smp->crypto); free(smp); } bluez-5.87/emulator/PaxHeaders/le.h0000644000000000000000000000005014015011623014245 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/emulator/le.h0000644000000000000000000000057214015011623013732 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2012 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #include struct bt_le; struct bt_le *bt_le_new(void); struct bt_le *bt_le_ref(struct bt_le *le); void bt_le_unref(struct bt_le *le); bluez-5.87/emulator/PaxHeaders/server.c0000644000000000000000000000005015221752714015163 xustar0020 atime=1783092684 20 ctime=1783094102 bluez-5.87/emulator/server.c0000644000000000000000000002002315221752714014641 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "src/shared/mainloop.h" #include "btdev.h" #include "server.h" #define uninitialized_var(x) x = x struct server { enum server_type type; uint16_t id; int fd; }; struct client { int fd; struct btdev *btdev; uint8_t *pkt_data; uint8_t pkt_type; uint16_t pkt_expect; uint16_t pkt_len; uint16_t pkt_offset; }; static void server_destroy(void *user_data) { struct server *server = user_data; close(server->fd); free(server); } static void client_destroy(void *user_data) { struct client *client = user_data; btdev_destroy(client->btdev); close(client->fd); free(client); } static void client_write_callback(const struct iovec *iov, int iovlen, void *user_data) { struct client *client = user_data; struct msghdr msg; ssize_t written; memset(&msg, 0, sizeof(msg)); msg.msg_iov = (struct iovec *) iov; msg.msg_iovlen = iovlen; written = sendmsg(client->fd, &msg, MSG_DONTWAIT); if (written < 0) return; } static void client_read_callback(int fd, uint32_t events, void *user_data) { struct client *client = user_data; static uint8_t buf[4096]; uint8_t *ptr = buf; ssize_t len; uint16_t count; if (events & (EPOLLERR | EPOLLHUP)) { mainloop_remove_fd(client->fd); return; } again: len = recv(fd, buf + client->pkt_offset, sizeof(buf) - client->pkt_offset, MSG_DONTWAIT); if (len < 0) { if (errno == EAGAIN) goto again; return; } if (!client->btdev) return; count = client->pkt_offset + len; while (count > 0) { hci_command_hdr *cmd_hdr; hci_acl_hdr *acl_hdr; hci_iso_hdr *iso_hdr; hci_sco_hdr *sco_hdr; if (!client->pkt_data) { client->pkt_type = ptr[0]; switch (client->pkt_type) { case HCI_COMMAND_PKT: if (count < HCI_COMMAND_HDR_SIZE + 1) { client->pkt_offset += len; return; } cmd_hdr = (hci_command_hdr *) (ptr + 1); client->pkt_expect = HCI_COMMAND_HDR_SIZE + cmd_hdr->plen + 1; client->pkt_data = malloc(client->pkt_expect); client->pkt_len = 0; break; case HCI_ACLDATA_PKT: if (count < HCI_ACL_HDR_SIZE + 1) { client->pkt_offset += len; return; } acl_hdr = (hci_acl_hdr*)(ptr + 1); client->pkt_expect = HCI_ACL_HDR_SIZE + acl_hdr->dlen + 1; client->pkt_data = malloc(client->pkt_expect); client->pkt_len = 0; break; case HCI_ISODATA_PKT: if (count < HCI_ISO_HDR_SIZE + 1) { client->pkt_offset += len; return; } iso_hdr = (hci_iso_hdr *)(ptr + 1); client->pkt_expect = HCI_ISO_HDR_SIZE + iso_hdr->dlen + 1; client->pkt_data = malloc(client->pkt_expect); client->pkt_len = 0; break; case HCI_SCODATA_PKT: if (count < HCI_SCO_HDR_SIZE + 1) { client->pkt_offset += len; return; } sco_hdr = (hci_sco_hdr *)(ptr + 1); client->pkt_expect = HCI_SCO_HDR_SIZE + sco_hdr->dlen + 1; client->pkt_data = malloc(client->pkt_expect); client->pkt_len = 0; break; default: printf("packet error, unknown type: %d\n", client->pkt_type); client->pkt_offset = 0; return; } client->pkt_offset = 0; } if (count >= client->pkt_expect) { memcpy(client->pkt_data + client->pkt_len, ptr, client->pkt_expect); ptr += client->pkt_expect; count -= client->pkt_expect; btdev_receive_h4(client->btdev, client->pkt_data, client->pkt_len + client->pkt_expect); free(client->pkt_data); client->pkt_data = NULL; } else { memcpy(client->pkt_data + client->pkt_len, ptr, count); client->pkt_len += count; client->pkt_expect -= count; count = 0; } } } static int accept_client(int fd) { struct sockaddr_un addr; socklen_t len; int nfd; memset(&addr, 0, sizeof(addr)); len = sizeof(addr); if (getsockname(fd, (struct sockaddr *) &addr, &len) < 0) { perror("Failed to get socket name"); return -1; } printf("Request for %s\n", addr.sun_path); nfd = accept(fd, (struct sockaddr *) &addr, &len); if (nfd < 0) { perror("Failed to accept client socket"); return -1; } return nfd; } static void server_accept_callback(int fd, uint32_t events, void *user_data) { struct server *server = user_data; struct client *client; enum btdev_type uninitialized_var(type); if (events & (EPOLLERR | EPOLLHUP)) { mainloop_remove_fd(server->fd); return; } client = malloc(sizeof(*client)); if (!client) return; memset(client, 0, sizeof(*client)); client->fd = accept_client(server->fd); if (client->fd < 0) { free(client); return; } switch (server->type) { case SERVER_TYPE_BREDRLE: type = BTDEV_TYPE_BREDRLE52; break; case SERVER_TYPE_BREDR: type = BTDEV_TYPE_BREDR; break; case SERVER_TYPE_LE: type = BTDEV_TYPE_LE; break; case SERVER_TYPE_AMP: type = BTDEV_TYPE_AMP; break; case SERVER_TYPE_MONITOR: goto done; } client->btdev = btdev_create(type, server->id); if (!client->btdev) { close(client->fd); free(client); return; } btdev_set_send_handler(client->btdev, client_write_callback, client); done: if (mainloop_add_fd(client->fd, EPOLLIN, client_read_callback, client, client_destroy) < 0) { btdev_destroy(client->btdev); close(client->fd); free(client); } } static int open_unix(const char *path) { struct sockaddr_un addr; int fd; unlink(path); fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { perror("Failed to open server socket"); return -1; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strcpy(addr.sun_path, path); if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { perror("Failed to bind server socket"); close(fd); return -1; } if (listen(fd, 5) < 0) { perror("Failed to listen server socket"); close(fd); return -1; } return fd; } struct server *server_open_unix(enum server_type type, const char *path) { struct server *server; server = malloc(sizeof(*server)); if (!server) return NULL; memset(server, 0, sizeof(*server)); server->type = type; server->id = 0x42; server->fd = open_unix(path); if (server->fd < 0) { free(server); return NULL; } if (mainloop_add_fd(server->fd, EPOLLIN, server_accept_callback, server, server_destroy) < 0) { close(server->fd); free(server); return NULL; } return server; } static int open_tcp(uint16_t port) { struct sockaddr_in addr; int fd, opt = 1; fd = socket(PF_INET, SOCK_STREAM, 0); if (fd < 0) { perror("Failed to open server socket"); return -1; } if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { perror("Failed to set socket option"); close(fd); return -1; } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_addr.s_addr = inet_addr("127.0.0.1"); addr.sin_port = htons(port); if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { perror("Failed to bind server socket"); close(fd); return -1; } if (listen(fd, 5) < 0) { perror("Failed to listen server socket"); close(fd); return -1; } return fd; } struct server *server_open_tcp(enum server_type type, uint16_t port) { struct server *server; server = malloc(sizeof(*server)); if (!server) return server; memset(server, 0, sizeof(*server)); server->type = type; server->id = 0x43; server->fd = open_tcp(port); if (server->fd < 0) { free(server); return NULL; } if (mainloop_add_fd(server->fd, EPOLLIN, server_accept_callback, server, server_destroy) < 0) { close(server->fd); free(server); return NULL; } return server; } void server_close(struct server *server) { if (!server) return; mainloop_remove_fd(server->fd); } bluez-5.87/emulator/PaxHeaders/bthost.c0000644000000000000000000000005015200607060015146 xustar0020 atime=1783092683 20 ctime=1783094103 bluez-5.87/emulator/bthost.c0000644000000000000000000027671015200607060014644 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2012 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright 2023-2024 NXP * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "src/shared/util.h" #include "src/shared/tester.h" #include "src/shared/queue.h" #include "src/shared/ad.h" #include "monitor/bt.h" #include "monitor/rfcomm.h" #include "bthost.h" #define lmp_bredr_capable(bthost) (!((bthost)->features[4] & 0x20)) /* ACL handle and flags pack/unpack */ #define acl_handle_pack(h, f) (uint16_t)((h & 0x0fff)|(f << 12)) #define acl_handle(h) (h & 0x0fff) #define acl_flags(h) (h >> 12) #define sco_flags_status(f) (f & 0x03) #define sco_flags_pack(status) (status & 0x03) #define iso_flags_pb(f) (f & 0x0003) #define iso_flags_ts(f) ((f >> 2) & 0x0001) #define iso_flags_pack(pb, ts) (((pb) & 0x03) | (((ts) & 0x01) << 2)) #define iso_data_len_pack(h, f) ((uint16_t) ((h) | ((f) << 14))) #define L2CAP_FEAT_FIXED_CHAN 0x00000080 #define L2CAP_FC_SIG_BREDR 0x02 #define L2CAP_FC_SMP_BREDR 0x80 #define L2CAP_IT_FEAT_MASK 0x0002 #define L2CAP_IT_FIXED_CHAN 0x0003 /* RFCOMM setters */ #define RFCOMM_ADDR(cr, dlci) (((dlci & 0x3f) << 2) | (cr << 1) | 0x01) #define RFCOMM_CTRL(type, pf) (((type & 0xef) | (pf << 4))) #define RFCOMM_LEN8(len) (((len) << 1) | 1) #define RFCOMM_LEN16(len) ((len) << 1) #define RFCOMM_MCC_TYPE(cr, type) (((type << 2) | (cr << 1) | 0x01)) /* RFCOMM FCS calculation */ #define CRC(data) (rfcomm_crc_table[rfcomm_crc_table[0xff ^ data[0]] ^ data[1]]) static const unsigned char rfcomm_crc_table[256] = { 0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75, 0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b, 0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69, 0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67, 0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d, 0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43, 0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51, 0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f, 0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05, 0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b, 0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19, 0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17, 0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d, 0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33, 0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21, 0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f, 0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95, 0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b, 0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89, 0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87, 0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad, 0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3, 0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1, 0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf, 0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5, 0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb, 0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9, 0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7, 0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd, 0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3, 0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1, 0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf }; static uint8_t rfcomm_fcs2(uint8_t *data) { return 0xff - rfcomm_crc_table[CRC(data) ^ data[2]]; } static uint8_t rfcomm_fcs(uint8_t *data) { return 0xff - CRC(data); } struct cmd { struct cmd *next; struct cmd *prev; uint8_t data[256 + sizeof(struct bt_hci_cmd_hdr)]; uint16_t len; }; struct cmd_queue { struct cmd *head; struct cmd *tail; }; struct cid_hook { uint16_t cid; bthost_cid_hook_func_t func; void *user_data; struct cid_hook *next; }; struct rfcomm_chan_hook { uint8_t channel; bthost_rfcomm_chan_hook_func_t func; void *user_data; struct rfcomm_chan_hook *next; }; struct sco_hook { bthost_sco_hook_func_t func; void *user_data; bthost_destroy_func_t destroy; }; struct iso_hook { bthost_iso_hook_func_t func; void *user_data; bthost_destroy_func_t destroy; }; struct btconn { uint16_t handle; uint8_t bdaddr[6]; uint8_t addr_type; uint8_t encr_mode; uint16_t next_cid; uint64_t fixed_chan; struct l2conn *l2conns; struct rcconn *rcconns; struct cid_hook *cid_hooks; struct rfcomm_chan_hook *rfcomm_chan_hooks; struct sco_hook *sco_hook; struct iso_hook *iso_hook; struct btconn *next; void *smp_data; uint16_t recv_len; uint16_t data_len; void *recv_data; }; enum l2cap_mode { L2CAP_MODE_OTHER, L2CAP_MODE_LE_CRED, L2CAP_MODE_LE_ENH_CRED, }; struct l2conn { struct l2conn *next; uint16_t scid; uint16_t dcid; uint16_t psm; uint16_t rx_mps; uint16_t tx_mps; uint16_t rx_credits; uint16_t tx_credits; enum l2cap_mode mode; uint16_t data_len; uint16_t recv_len; void *recv_data; }; struct rcconn { uint8_t channel; uint16_t scid; struct rcconn *next; }; struct l2cap_pending_req { uint8_t ident; bthost_l2cap_rsp_cb cb; void *user_data; struct l2cap_pending_req *next; }; struct l2cap_conn_cb_data { uint16_t psm; uint16_t mtu; uint16_t mps; uint16_t credits; bthost_l2cap_connect_cb func; bthost_l2cap_disconnect_cb disconn_func; void *user_data; struct l2cap_conn_cb_data *next; }; struct rfcomm_conn_cb_data { uint8_t channel; bthost_rfcomm_connect_cb func; void *user_data; struct rfcomm_conn_cb_data *next; }; struct rfcomm_connection_data { uint8_t channel; struct btconn *conn; bthost_rfcomm_connect_cb cb; void *user_data; }; struct le_ext_adv { struct bthost *bthost; uint16_t event_type; uint8_t addr_type; uint8_t addr[6]; uint8_t direct_addr_type; uint8_t direct_addr[6]; }; struct bthost { bool ready; bthost_ready_cb ready_cb; uint8_t bdaddr[6]; uint8_t features[8]; bthost_send_func send_handler; void *send_data; struct cmd_queue cmd_q; uint8_t ncmd; struct btconn *conns; bthost_cmd_complete_cb cmd_complete_cb; void *cmd_complete_data; bthost_new_conn_cb new_conn_cb; void *new_conn_data; bthost_new_conn_cb new_sco_cb; void *new_sco_data; bthost_accept_conn_cb accept_iso_cb; bthost_new_conn_cb new_iso_cb; void *new_iso_data; uint16_t acl_mtu; uint16_t iso_mtu; struct rfcomm_connection_data *rfcomm_conn_data; struct l2cap_conn_cb_data *new_l2cap_conn_data; struct rfcomm_conn_cb_data *new_rfcomm_conn_data; struct l2cap_pending_req *l2reqs; uint8_t pin[16]; uint8_t pin_len; uint8_t io_capability; uint8_t auth_req; bool reject_user_confirm; void *smp_data; bool conn_init; bool le; bool sc; struct queue *le_ext_adv; bthost_debug_func_t debug_callback; bthost_destroy_func_t debug_destroy; void *debug_data; }; struct bthost *bthost_create(void) { struct bthost *bthost; bthost = new0(struct bthost, 1); if (!bthost) return NULL; bthost->smp_data = smp_start(bthost); if (!bthost->smp_data) { free(bthost); return NULL; } bthost->le_ext_adv = queue_new(); /* Set defaults */ bthost->io_capability = 0x03; bthost->acl_mtu = UINT16_MAX; bthost->iso_mtu = UINT16_MAX; return bthost; } static void l2conn_free(struct l2conn *conn) { free(conn->recv_data); free(conn); } static void btconn_free(struct btconn *conn) { if (conn->smp_data) smp_conn_del(conn->smp_data); while (conn->l2conns) { struct l2conn *l2conn = conn->l2conns; conn->l2conns = l2conn->next; l2conn_free(l2conn); } while (conn->cid_hooks) { struct cid_hook *hook = conn->cid_hooks; conn->cid_hooks = hook->next; free(hook); } while (conn->rcconns) { struct rcconn *rcconn = conn->rcconns; conn->rcconns = rcconn->next; free(rcconn); } while (conn->rfcomm_chan_hooks) { struct rfcomm_chan_hook *hook = conn->rfcomm_chan_hooks; conn->rfcomm_chan_hooks = hook->next; free(hook); } if (conn->sco_hook && conn->sco_hook->destroy) conn->sco_hook->destroy(conn->sco_hook->user_data); if (conn->iso_hook && conn->iso_hook->destroy) conn->iso_hook->destroy(conn->iso_hook->user_data); free(conn->sco_hook); free(conn->iso_hook); free(conn->recv_data); free(conn); } static struct btconn *bthost_find_conn(struct bthost *bthost, uint16_t handle) { struct btconn *conn; for (conn = bthost->conns; conn != NULL; conn = conn->next) { if (conn->handle == handle) return conn; } return NULL; } static struct btconn *bthost_find_conn_by_bdaddr(struct bthost *bthost, const uint8_t *bdaddr) { struct btconn *conn; for (conn = bthost->conns; conn != NULL; conn = conn->next) { if (!memcmp(conn->bdaddr, bdaddr, 6)) return conn; } return NULL; } static struct l2conn *bthost_add_l2cap_conn(struct bthost *bthost, struct btconn *conn, uint16_t scid, uint16_t dcid, uint16_t psm) { struct l2conn *l2conn; l2conn = malloc(sizeof(*l2conn)); if (!l2conn) return NULL; memset(l2conn, 0, sizeof(*l2conn)); l2conn->psm = psm; l2conn->scid = scid; l2conn->dcid = dcid; l2conn->mode = L2CAP_MODE_OTHER; l2conn->next = conn->l2conns; conn->l2conns = l2conn; return l2conn; } static void btconn_detach_l2cap_conn(struct btconn *conn, struct l2conn *l2conn) { struct l2conn *c; if (conn->l2conns == l2conn) { conn->l2conns = l2conn->next; l2conn->next = NULL; return; } for (c = conn->l2conns; c != NULL; c = c->next) { if (c->next == l2conn) { c->next = l2conn->next; l2conn->next = NULL; return; } } } static struct rcconn *bthost_add_rfcomm_conn(struct bthost *bthost, struct btconn *conn, struct l2conn *l2conn, uint8_t channel) { struct rcconn *rcconn; rcconn = malloc(sizeof(*rcconn)); if (!rcconn) return NULL; memset(rcconn, 0, sizeof(*rcconn)); rcconn->channel = channel; rcconn->scid = l2conn->scid; rcconn->next = conn->rcconns; conn->rcconns = rcconn; return rcconn; } static struct rcconn *btconn_find_rfcomm_conn_by_channel(struct btconn *conn, uint8_t chan) { struct rcconn *rcconn; for (rcconn = conn->rcconns; rcconn != NULL; rcconn = rcconn->next) { if (rcconn->channel == chan) return rcconn; } return NULL; } static struct l2conn *btconn_find_l2cap_conn_by_scid(struct btconn *conn, uint16_t scid) { struct l2conn *l2conn; for (l2conn = conn->l2conns; l2conn != NULL; l2conn = l2conn->next) { if (l2conn->scid == scid) return l2conn; } return NULL; } static struct l2conn *btconn_find_l2cap_conn_by_dcid(struct btconn *conn, uint16_t dcid) { struct l2conn *l2conn; for (l2conn = conn->l2conns; l2conn != NULL; l2conn = l2conn->next) { if (l2conn->dcid == dcid) return l2conn; } return NULL; } static struct l2cap_conn_cb_data *bthost_find_l2cap_cb_by_psm( struct bthost *bthost, uint16_t psm) { struct l2cap_conn_cb_data *cb; for (cb = bthost->new_l2cap_conn_data; cb != NULL; cb = cb->next) { if (cb->psm == psm) return cb; } return NULL; } static struct rfcomm_conn_cb_data *bthost_find_rfcomm_cb_by_channel( struct bthost *bthost, uint8_t channel) { struct rfcomm_conn_cb_data *cb; for (cb = bthost->new_rfcomm_conn_data; cb != NULL; cb = cb->next) { if (cb->channel == channel) return cb; } return NULL; } static struct le_ext_adv *le_ext_adv_new(struct bthost *bthost) { struct le_ext_adv *ext_adv; ext_adv = new0(struct le_ext_adv, 1); ext_adv->bthost = bthost; /* Add to queue */ if (!queue_push_tail(bthost->le_ext_adv, ext_adv)) { free(ext_adv); return NULL; } return ext_adv; } static void le_ext_adv_free(void *data) { struct le_ext_adv *ext_adv = data; /* Remove from queue */ queue_remove(ext_adv->bthost->le_ext_adv, ext_adv); free(ext_adv); } void bthost_destroy(struct bthost *bthost) { if (!bthost) return; while (bthost->cmd_q.tail) { struct cmd *cmd = bthost->cmd_q.tail; bthost->cmd_q.tail = cmd->prev; free(cmd); } while (bthost->conns) { struct btconn *conn = bthost->conns; bthost->conns = conn->next; btconn_free(conn); } while (bthost->l2reqs) { struct l2cap_pending_req *req = bthost->l2reqs; bthost->l2reqs = req->next; req->cb(0, NULL, 0, req->user_data); free(req); } while (bthost->new_l2cap_conn_data) { struct l2cap_conn_cb_data *cb = bthost->new_l2cap_conn_data; bthost->new_l2cap_conn_data = cb->next; free(cb); } while (bthost->new_rfcomm_conn_data) { struct rfcomm_conn_cb_data *cb = bthost->new_rfcomm_conn_data; bthost->new_rfcomm_conn_data = cb->next; free(cb); } if (bthost->rfcomm_conn_data) free(bthost->rfcomm_conn_data); smp_stop(bthost->smp_data); queue_destroy(bthost->le_ext_adv, le_ext_adv_free); free(bthost); } void bthost_set_send_handler(struct bthost *bthost, bthost_send_func handler, void *user_data) { if (!bthost) return; bthost->send_handler = handler; bthost->send_data = user_data; } void bthost_set_acl_mtu(struct bthost *bthost, uint16_t mtu) { if (!bthost) return; bthost->acl_mtu = mtu; } void bthost_set_iso_mtu(struct bthost *bthost, uint16_t mtu) { if (!bthost) return; bthost->iso_mtu = mtu; } static void queue_command(struct bthost *bthost, const struct iovec *iov, int iovlen) { struct cmd_queue *cmd_q = &bthost->cmd_q; struct cmd *cmd; int i; cmd = malloc(sizeof(*cmd)); if (!cmd) return; memset(cmd, 0, sizeof(*cmd)); for (i = 0; i < iovlen; i++) { memcpy(cmd->data + cmd->len, iov[i].iov_base, iov[i].iov_len); cmd->len += iov[i].iov_len; } if (cmd_q->tail) cmd_q->tail->next = cmd; else cmd_q->head = cmd; cmd->prev = cmd_q->tail; cmd_q->tail = cmd; } static void send_packet(struct bthost *bthost, const struct iovec *iov, int iovlen) { int i; if (!bthost->send_handler) return; for (i = 0; i < iovlen; i++) { if (!i) util_hexdump('<', iov[i].iov_base, iov[i].iov_len, bthost->debug_callback, bthost->debug_data); else util_hexdump(' ', iov[i].iov_base, iov[i].iov_len, bthost->debug_callback, bthost->debug_data); } bthost->send_handler(iov, iovlen, bthost->send_data); } static void iov_pull_n(struct iovec *src, unsigned int *src_cnt, struct iovec *dst, unsigned int *dst_cnt, unsigned int max_dst, size_t len) { unsigned int i; size_t count; *dst_cnt = 0; while (len && *dst_cnt < max_dst && *src_cnt) { count = len; if (count > src[0].iov_len) count = src[0].iov_len; dst[*dst_cnt].iov_base = src[0].iov_base; dst[*dst_cnt].iov_len = count; *dst_cnt += 1; util_iov_pull(&src[0], count); len -= count; if (!src[0].iov_len) { for (i = 1; i < *src_cnt; ++i) src[i - 1] = src[i]; *src_cnt -= 1; } } } static void send_iov(struct bthost *bthost, uint16_t handle, uint16_t cid, const struct iovec *iov, unsigned int iovcnt) { struct bt_hci_acl_hdr acl_hdr; struct bt_l2cap_hdr l2_hdr; uint8_t pkt = BT_H4_ACL_PKT; struct iovec pdu[3 + iovcnt]; struct iovec payload[1 + iovcnt]; size_t payload_mtu, len; int flag; unsigned int i; len = 0; for (i = 0; i < iovcnt; i++) { payload[1 + i].iov_base = iov[i].iov_base; payload[1 + i].iov_len = iov[i].iov_len; len += iov[i].iov_len; } l2_hdr.cid = cpu_to_le16(cid); l2_hdr.len = cpu_to_le16(len); payload[0].iov_base = &l2_hdr; payload[0].iov_len = sizeof(l2_hdr); len += sizeof(l2_hdr); iovcnt++; /* Fragment to ACL MTU */ payload_mtu = bthost->acl_mtu - sizeof(pkt) - sizeof(acl_hdr); flag = 0x00; do { size_t count = (len > payload_mtu) ? payload_mtu : len; unsigned int pdu_iovcnt; pdu[0].iov_base = &pkt; pdu[0].iov_len = sizeof(pkt); acl_hdr.dlen = cpu_to_le16(count); acl_hdr.handle = acl_handle_pack(handle, flag); pdu[1].iov_base = &acl_hdr; pdu[1].iov_len = sizeof(acl_hdr); iov_pull_n(payload, &iovcnt, &pdu[2], &pdu_iovcnt, ARRAY_SIZE(pdu) - 2, count); pdu_iovcnt += 2; send_packet(bthost, pdu, pdu_iovcnt); len -= count; flag = 0x01; } while (len); } static void send_acl(struct bthost *bthost, uint16_t handle, uint16_t cid, uint16_t sdu_len, const void *data, uint16_t len) { struct iovec iov[2]; uint16_t sdu; int num = 0; if (sdu_len) { sdu = cpu_to_le16(sdu_len); iov[num].iov_base = &sdu; iov[num].iov_len = sizeof(sdu); num++; } iov[num].iov_base = (void *) data; iov[num].iov_len = len; num++; send_iov(bthost, handle, cid, iov, num); } static uint8_t l2cap_sig_send(struct bthost *bthost, struct btconn *conn, uint8_t code, uint8_t ident, const void *data, uint16_t len) { static uint8_t next_ident = 1; struct bt_l2cap_hdr_sig hdr; uint16_t cid; struct iovec iov[2]; if (!ident) { ident = next_ident++; if (!ident) ident = next_ident++; } hdr.code = code; hdr.ident = ident; hdr.len = cpu_to_le16(len); iov[0].iov_base = &hdr; iov[0].iov_len = sizeof(hdr); if (conn->addr_type == BDADDR_BREDR) cid = 0x0001; else cid = 0x0005; if (len == 0) { send_iov(bthost, conn->handle, cid, iov, 1); return ident; } iov[1].iov_base = (void *) data; iov[1].iov_len = len; send_iov(bthost, conn->handle, cid, iov, 2); return ident; } void bthost_add_cid_hook(struct bthost *bthost, uint16_t handle, uint16_t cid, bthost_cid_hook_func_t func, void *user_data) { struct cid_hook *hook; struct btconn *conn; conn = bthost_find_conn(bthost, handle); if (!conn) return; hook = malloc(sizeof(*hook)); if (!hook) return; memset(hook, 0, sizeof(*hook)); hook->cid = cid; hook->func = func; hook->user_data = user_data; hook->next = conn->cid_hooks; conn->cid_hooks = hook; } void bthost_add_sco_hook(struct bthost *bthost, uint16_t handle, bthost_sco_hook_func_t func, void *user_data, bthost_destroy_func_t destroy) { struct sco_hook *hook; struct btconn *conn; conn = bthost_find_conn(bthost, handle); if (!conn || conn->sco_hook) return; hook = malloc(sizeof(*hook)); if (!hook) return; memset(hook, 0, sizeof(*hook)); hook->func = func; hook->user_data = user_data; hook->destroy = destroy; conn->sco_hook = hook; } void bthost_add_iso_hook(struct bthost *bthost, uint16_t handle, bthost_iso_hook_func_t func, void *user_data, bthost_destroy_func_t destroy) { struct iso_hook *hook; struct btconn *conn; conn = bthost_find_conn(bthost, handle); if (!conn || conn->iso_hook) return; hook = malloc(sizeof(*hook)); if (!hook) return; memset(hook, 0, sizeof(*hook)); hook->func = func; hook->user_data = user_data; hook->destroy = destroy; conn->iso_hook = hook; } void bthost_send_cid(struct bthost *bthost, uint16_t handle, uint16_t cid, const void *data, uint16_t len) { struct btconn *conn; struct l2conn *l2conn; struct iovec iov = { .iov_base = (void *) data, .iov_len = len, }; conn = bthost_find_conn(bthost, handle); if (!conn) return; l2conn = btconn_find_l2cap_conn_by_dcid(conn, cid); /* Segment SDU in case of LE (Enhanced) Credit Based Flow Control */ if (l2conn && (l2conn->mode == L2CAP_MODE_LE_CRED || l2conn->mode == L2CAP_MODE_LE_ENH_CRED)) { uint16_t sdu_len = len; uint16_t slen; while (iov.iov_len) { if (sdu_len) slen = MIN(iov.iov_len, l2conn->tx_mps - sizeof(sdu_len)); else slen = MIN(iov.iov_len, l2conn->tx_mps); send_acl(bthost, handle, cid, sdu_len, util_iov_pull_mem(&iov, slen), slen); if (sdu_len) sdu_len = 0; } return; } send_acl(bthost, handle, cid, 0, data, len); } void bthost_send_cid_v(struct bthost *bthost, uint16_t handle, uint16_t cid, const struct iovec *iov, int iovcnt) { struct btconn *conn; conn = bthost_find_conn(bthost, handle); if (!conn) return; send_iov(bthost, handle, cid, iov, iovcnt); } void bthost_send_sco(struct bthost *bthost, uint16_t handle, uint8_t pkt_status, const struct iovec *iov, int iovcnt) { uint8_t pkt = BT_H4_SCO_PKT; struct iovec pdu[2 + iovcnt]; struct bt_hci_sco_hdr sco_hdr; struct btconn *conn; int i, len = 0; conn = bthost_find_conn(bthost, handle); if (!conn) return; for (i = 0; i < iovcnt; i++) { pdu[2 + i].iov_base = iov[i].iov_base; pdu[2 + i].iov_len = iov[i].iov_len; len += iov[i].iov_len; } pdu[0].iov_base = &pkt; pdu[0].iov_len = sizeof(pkt); sco_hdr.handle = acl_handle_pack(handle, sco_flags_pack(pkt_status)); sco_hdr.dlen = len; pdu[1].iov_base = &sco_hdr; pdu[1].iov_len = sizeof(sco_hdr); send_packet(bthost, pdu, 2 + iovcnt); } static void send_iso(struct bthost *bthost, uint16_t handle, bool ts, uint16_t sn, uint32_t timestamp, uint8_t pkt_status, const struct iovec *iov, unsigned int iovcnt) { struct bt_hci_iso_hdr iso_hdr; struct bt_hci_iso_data_start data_hdr; uint8_t pkt = BT_H4_ISO_PKT; struct iovec pdu[4 + iovcnt]; struct iovec payload[2 + iovcnt]; uint16_t payload_mtu = bthost->iso_mtu - sizeof(iso_hdr); int len = 0, packet = 0; unsigned int i; for (i = 0; i < iovcnt; i++) { payload[2 + i].iov_base = iov[i].iov_base; payload[2 + i].iov_len = iov[i].iov_len; len += iov[i].iov_len; } data_hdr.sn = cpu_to_le16(sn); data_hdr.slen = cpu_to_le16(iso_data_len_pack(len, pkt_status)); if (ts) { timestamp = cpu_to_le32(timestamp); payload[0].iov_base = ×tamp; payload[0].iov_len = sizeof(timestamp); len += sizeof(timestamp); } else { payload[0].iov_base = NULL; payload[0].iov_len = 0; } iovcnt++; payload[1].iov_base = &data_hdr; payload[1].iov_len = sizeof(data_hdr); len += sizeof(data_hdr); iovcnt++; /* ISO fragmentation */ do { unsigned int pdu_iovcnt; uint16_t iso_len, pb, flags; if (packet == 0 && len <= payload_mtu) pb = 0x02; else if (packet == 0) pb = 0x00; else if (len <= payload_mtu) pb = 0x03; else pb = 0x01; flags = iso_flags_pack(pb, ts); iso_len = len <= payload_mtu ? len : payload_mtu; iso_hdr.handle = acl_handle_pack(handle, flags); iso_hdr.dlen = cpu_to_le16(iso_len); pdu[0].iov_base = &pkt; pdu[0].iov_len = sizeof(pkt); pdu[1].iov_base = &iso_hdr; pdu[1].iov_len = sizeof(iso_hdr); iov_pull_n(payload, &iovcnt, &pdu[2], &pdu_iovcnt, ARRAY_SIZE(pdu) - 2, iso_len); pdu_iovcnt += 2; send_packet(bthost, pdu, pdu_iovcnt); packet++; ts = false; len -= iso_len; } while (len); } void bthost_send_iso(struct bthost *bthost, uint16_t handle, bool ts, uint16_t sn, uint32_t timestamp, uint8_t pkt_status, const struct iovec *iov, int iovcnt) { struct btconn *conn; conn = bthost_find_conn(bthost, handle); if (!conn) return; send_iso(bthost, handle, ts, sn, timestamp, pkt_status, iov, iovcnt); } bool bthost_l2cap_req(struct bthost *bthost, uint16_t handle, uint8_t code, const void *data, uint16_t len, bthost_l2cap_rsp_cb cb, void *user_data) { struct l2cap_pending_req *req; struct btconn *conn; uint8_t ident; conn = bthost_find_conn(bthost, handle); if (!conn) return false; if (code == BT_L2CAP_PDU_CONN_REQ && len == sizeof(struct bt_l2cap_pdu_conn_req)) { const struct bt_l2cap_pdu_conn_req *req = data; bthost_add_l2cap_conn(bthost, conn, le16_to_cpu(req->scid), le16_to_cpu(req->scid), le16_to_cpu(req->psm)); } ident = l2cap_sig_send(bthost, conn, code, 0, data, len); if (!ident) return false; if (!cb) return true; req = malloc(sizeof(*req)); if (!req) return false; memset(req, 0, sizeof(*req)); req->ident = ident; req->cb = cb; req->user_data = user_data; req->next = bthost->l2reqs; bthost->l2reqs = req; return true; } static void send_command(struct bthost *bthost, uint16_t opcode, const void *data, uint8_t len) { struct bt_hci_cmd_hdr hdr; uint8_t pkt = BT_H4_CMD_PKT; struct iovec iov[3]; bthost_debug(bthost, "command 0x%02x", opcode); iov[0].iov_base = &pkt; iov[0].iov_len = sizeof(pkt); hdr.opcode = cpu_to_le16(opcode); hdr.plen = len; iov[1].iov_base = &hdr; iov[1].iov_len = sizeof(hdr); if (len > 0) { iov[2].iov_base = (void *) data; iov[2].iov_len = len; } if (bthost->ncmd) { send_packet(bthost, iov, len > 0 ? 3 : 2); bthost->ncmd--; } else { queue_command(bthost, iov, len > 0 ? 3 : 2); } } static void next_cmd(struct bthost *bthost) { struct cmd_queue *cmd_q = &bthost->cmd_q; struct cmd *cmd = cmd_q->head; struct cmd *next; struct iovec iov; if (!cmd) return; next = cmd->next; if (!bthost->ncmd) return; iov.iov_base = cmd->data; iov.iov_len = cmd->len; send_packet(bthost, &iov, 1); bthost->ncmd--; if (next) next->prev = NULL; else cmd_q->tail = NULL; cmd_q->head = next; free(cmd); } static void read_bd_addr_complete(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_rsp_read_bd_addr *ev = data; if (len < sizeof(*ev)) return; if (ev->status) return; memcpy(bthost->bdaddr, ev->bdaddr, 6); bthost->ready = true; if (bthost->ready_cb) { bthost->ready_cb(); bthost->ready_cb = NULL; } } void bthost_notify_ready(struct bthost *bthost, bthost_ready_cb cb) { if (bthost->ready) { cb(); return; } bthost->ready_cb = cb; } bool bthost_set_debug(struct bthost *bthost, bthost_debug_func_t callback, void *user_data, bthost_destroy_func_t destroy) { if (!bthost) return false; if (bthost->debug_destroy) bthost->debug_destroy(bthost->debug_data); bthost->debug_callback = callback; bthost->debug_destroy = destroy; bthost->debug_data = user_data; return true; } void bthost_debug(struct bthost *host, const char *format, ...) { va_list ap; if (!host || !format || !host->debug_callback) return; va_start(ap, format); util_debug_va(host->debug_callback, host->debug_data, format, ap); va_end(ap); } static void read_local_features_complete(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_rsp_read_local_features *ev = data; if (len < sizeof(*ev)) return; if (ev->status) return; memcpy(bthost->features, ev->features, 8); } static void evt_cmd_complete(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_cmd_complete *ev = data; const void *param; uint16_t opcode; if (len < sizeof(*ev)) return; param = data + sizeof(*ev); bthost->ncmd = ev->ncmd; opcode = le16toh(ev->opcode); switch (opcode) { case BT_HCI_CMD_RESET: break; case BT_HCI_CMD_READ_LOCAL_FEATURES: read_local_features_complete(bthost, param, len - sizeof(*ev)); break; case BT_HCI_CMD_READ_BD_ADDR: read_bd_addr_complete(bthost, param, len - sizeof(*ev)); break; case BT_HCI_CMD_WRITE_SCAN_ENABLE: break; case BT_HCI_CMD_LE_SET_ADV_ENABLE: break; case BT_HCI_CMD_LE_SET_ADV_PARAMETERS: break; case BT_HCI_CMD_PIN_CODE_REQUEST_REPLY: break; case BT_HCI_CMD_PIN_CODE_REQUEST_NEG_REPLY: break; case BT_HCI_CMD_LINK_KEY_REQUEST_NEG_REPLY: break; case BT_HCI_CMD_WRITE_SIMPLE_PAIRING_MODE: break; case BT_HCI_CMD_WRITE_LE_HOST_SUPPORTED: break; case BT_HCI_CMD_WRITE_SECURE_CONN_SUPPORT: break; case BT_HCI_CMD_IO_CAPABILITY_REQUEST_REPLY: break; case BT_HCI_CMD_USER_CONFIRM_REQUEST_REPLY: break; case BT_HCI_CMD_USER_CONFIRM_REQUEST_NEG_REPLY: break; case BT_HCI_CMD_LE_LTK_REQ_REPLY: break; case BT_HCI_CMD_LE_LTK_REQ_NEG_REPLY: break; case BT_HCI_CMD_LE_SET_ADV_DATA: break; case BT_HCI_CMD_LE_SET_EXT_ADV_PARAMS: break; case BT_HCI_CMD_LE_SET_EXT_ADV_DATA: break; case BT_HCI_CMD_LE_SET_EXT_ADV_ENABLE: break; case BT_HCI_CMD_LE_SET_PA_PARAMS: break; case BT_HCI_CMD_LE_SET_PA_ENABLE: break; default: bthost_debug(bthost, "Unhandled cmd_complete opcode 0x%04x", opcode); break; } if (bthost->cmd_complete_cb) bthost->cmd_complete_cb(opcode, 0, param, len - sizeof(*ev), bthost->cmd_complete_data); next_cmd(bthost); } static void evt_cmd_status(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_cmd_status *ev = data; uint16_t opcode; if (len < sizeof(*ev)) return; bthost->ncmd = ev->ncmd; opcode = le16toh(ev->opcode); if (ev->status && bthost->cmd_complete_cb) bthost->cmd_complete_cb(opcode, ev->status, NULL, 0, bthost->cmd_complete_data); next_cmd(bthost); } static void evt_conn_request(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_conn_request *ev = data; struct bt_hci_cmd_accept_conn_request cmd; if (len < sizeof(*ev)) return; if (ev->link_type == 0x01 || ev->link_type == 0x00) { memset(&cmd, 0, sizeof(cmd)); memcpy(cmd.bdaddr, ev->bdaddr, sizeof(ev->bdaddr)); send_command(bthost, BT_HCI_CMD_ACCEPT_CONN_REQUEST, &cmd, sizeof(cmd)); } else if (ev->link_type == 0x02) { struct bt_hci_cmd_accept_sync_conn_request cmd; memset(&cmd, 0, sizeof(cmd)); memcpy(cmd.bdaddr, ev->bdaddr, sizeof(ev->bdaddr)); /* TODO: emulate these properly? */ cmd.tx_bandwidth = cpu_to_le32(0x00001f40); cmd.rx_bandwidth = cpu_to_le32(0x00001f40); cmd.max_latency = cpu_to_le16(0xffff); cmd.voice_setting = 0x00; cmd.retrans_effort = 0x02; cmd.pkt_type = 0xff; send_command(bthost, BT_HCI_CMD_ACCEPT_SYNC_CONN_REQUEST, &cmd, sizeof(cmd)); } } static void init_conn(struct bthost *bthost, uint16_t handle, const uint8_t *bdaddr, uint8_t addr_type) { struct btconn *conn; const uint8_t *ia, *ra; uint8_t ia_type, ra_type; conn = malloc(sizeof(*conn)); if (!conn) return; memset(conn, 0, sizeof(*conn)); conn->handle = handle; memcpy(conn->bdaddr, bdaddr, 6); conn->addr_type = addr_type; conn->next_cid = 0x0040; conn->next = bthost->conns; bthost->conns = conn; if (bthost->conn_init) { ia = bthost->bdaddr; if (addr_type == BDADDR_BREDR) ia_type = addr_type; else ia_type = BDADDR_LE_PUBLIC; ra = bdaddr; ra_type = addr_type; } else { ia = bdaddr; ia_type = addr_type; ra = bthost->bdaddr; if (addr_type == BDADDR_BREDR) ra_type = addr_type; else ra_type = BDADDR_LE_PUBLIC; } conn->smp_data = smp_conn_add(bthost->smp_data, handle, ia, ia_type, ra, ra_type, bthost->conn_init); if (bthost->new_conn_cb) bthost->new_conn_cb(conn->handle, bthost->new_conn_data); if (addr_type == BDADDR_BREDR) { struct bt_l2cap_pdu_info_req req; req.type = L2CAP_IT_FIXED_CHAN; l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_INFO_REQ, 1, &req, sizeof(req)); } } static void init_sco(struct bthost *bthost, uint16_t handle, const uint8_t *bdaddr, uint8_t addr_type) { struct btconn *conn; bthost_debug(bthost, "SCO handle 0x%4.4x", handle); conn = malloc(sizeof(*conn)); if (!conn) return; memset(conn, 0, sizeof(*conn)); conn->handle = handle; memcpy(conn->bdaddr, bdaddr, 6); conn->addr_type = addr_type; conn->next = bthost->conns; bthost->conns = conn; if (bthost->new_sco_cb) bthost->new_sco_cb(handle, bthost->new_sco_data); } static void evt_conn_complete(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_conn_complete *ev = data; if (len < sizeof(*ev)) return; if (ev->status) return; if (ev->link_type == 0x00) { init_sco(bthost, le16_to_cpu(ev->handle), ev->bdaddr, BDADDR_BREDR); } else if (ev->link_type == 0x01) { init_conn(bthost, le16_to_cpu(ev->handle), ev->bdaddr, BDADDR_BREDR); } } static void evt_disconn_complete(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_disconnect_complete *ev = data; struct btconn **curr; uint16_t handle; if (len < sizeof(*ev)) return; if (ev->status) return; handle = le16_to_cpu(ev->handle); for (curr = &bthost->conns; *curr;) { struct btconn *conn = *curr; if (conn->handle == handle) { *curr = conn->next; btconn_free(conn); } else { curr = &conn->next; } } } static void evt_num_completed_packets(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_num_completed_packets *ev = data; if (len < sizeof(*ev)) return; } static void evt_auth_complete(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_auth_complete *ev = data; struct bt_hci_cmd_set_conn_encrypt cp; if (len < sizeof(*ev)) return; if (ev->status) return; cp.handle = ev->handle; cp.encr_mode = 0x01; send_command(bthost, BT_HCI_CMD_SET_CONN_ENCRYPT, &cp, sizeof(cp)); } static void evt_pin_code_request(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_pin_code_request *ev = data; if (len < sizeof(*ev)) return; if (bthost->pin_len > 0) { struct bt_hci_cmd_pin_code_request_reply cp; memset(&cp, 0, sizeof(cp)); memcpy(cp.bdaddr, ev->bdaddr, 6); cp.pin_len = bthost->pin_len; memcpy(cp.pin_code, bthost->pin, bthost->pin_len); send_command(bthost, BT_HCI_CMD_PIN_CODE_REQUEST_REPLY, &cp, sizeof(cp)); } else { struct bt_hci_cmd_pin_code_request_neg_reply cp; memcpy(cp.bdaddr, ev->bdaddr, 6); send_command(bthost, BT_HCI_CMD_PIN_CODE_REQUEST_NEG_REPLY, &cp, sizeof(cp)); } } static void evt_link_key_request(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_link_key_request *ev = data; struct bt_hci_cmd_link_key_request_neg_reply cp; if (len < sizeof(*ev)) return; memset(&cp, 0, sizeof(cp)); memcpy(cp.bdaddr, ev->bdaddr, 6); send_command(bthost, BT_HCI_CMD_LINK_KEY_REQUEST_NEG_REPLY, &cp, sizeof(cp)); } static void evt_link_key_notify(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_link_key_notify *ev = data; if (len < sizeof(*ev)) return; } static void evt_encrypt_change(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_encrypt_change *ev = data; struct btconn *conn; uint16_t handle; if (len < sizeof(*ev)) return; handle = acl_handle(ev->handle); conn = bthost_find_conn(bthost, handle); if (!conn) return; if (ev->status) return; conn->encr_mode = ev->encr_mode; if (conn->smp_data) smp_conn_encrypted(conn->smp_data, conn->encr_mode); } static void evt_io_cap_response(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_io_capability_response *ev = data; struct btconn *conn; if (len < sizeof(*ev)) return; conn = bthost_find_conn_by_bdaddr(bthost, ev->bdaddr); if (!conn) return; } static void evt_io_cap_request(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_io_capability_request *ev = data; struct bt_hci_cmd_io_capability_request_reply cp; struct btconn *conn; if (len < sizeof(*ev)) return; conn = bthost_find_conn_by_bdaddr(bthost, ev->bdaddr); if (!conn) return; memcpy(cp.bdaddr, ev->bdaddr, 6); cp.capability = bthost->io_capability; cp.oob_data = 0x00; cp.authentication = bthost->auth_req; send_command(bthost, BT_HCI_CMD_IO_CAPABILITY_REQUEST_REPLY, &cp, sizeof(cp)); } static void evt_user_confirm_request(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_user_confirm_request *ev = data; struct btconn *conn; if (len < sizeof(*ev)) return; conn = bthost_find_conn_by_bdaddr(bthost, ev->bdaddr); if (!conn) return; if (bthost->reject_user_confirm) { send_command(bthost, BT_HCI_CMD_USER_CONFIRM_REQUEST_NEG_REPLY, ev->bdaddr, 6); return; } send_command(bthost, BT_HCI_CMD_USER_CONFIRM_REQUEST_REPLY, ev->bdaddr, 6); } static void evt_simple_pairing_complete(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_simple_pairing_complete *ev = data; if (len < sizeof(*ev)) return; } static void evt_sync_conn_complete(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_sync_conn_complete *ev = data; if (len < sizeof(*ev)) return; if (ev->status) return; init_sco(bthost, le16_to_cpu(ev->handle), ev->bdaddr, BDADDR_BREDR); } static void evt_le_conn_complete(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_le_conn_complete *ev = data; uint8_t addr_type; if (len < sizeof(*ev)) return; if (ev->status) return; if (ev->peer_addr_type == 0x00) addr_type = BDADDR_LE_PUBLIC; else addr_type = BDADDR_LE_RANDOM; init_conn(bthost, le16_to_cpu(ev->handle), ev->peer_addr, addr_type); } static void evt_le_ext_conn_complete(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_le_enhanced_conn_complete *ev = data; uint8_t addr_type; if (len < sizeof(*ev)) return; if (ev->status) return; if (ev->peer_addr_type == 0x00) addr_type = BDADDR_LE_PUBLIC; else addr_type = BDADDR_LE_RANDOM; init_conn(bthost, le16_to_cpu(ev->handle), ev->peer_addr, addr_type); } static void evt_le_conn_update_complete(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_le_conn_update_complete *ev = data; if (len < sizeof(*ev)) return; if (ev->status) return; } static void evt_le_remote_features_complete(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_le_remote_features_complete *ev = data; if (len < sizeof(*ev)) return; if (ev->status) return; } static void evt_le_ltk_request(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_le_long_term_key_request *ev = data; struct bt_hci_cmd_le_ltk_req_reply cp; struct bt_hci_cmd_le_ltk_req_neg_reply *neg_cp = (void *) &cp; uint16_t handle, ediv; uint64_t rand; struct btconn *conn; int err; if (len < sizeof(*ev)) return; handle = acl_handle(ev->handle); conn = bthost_find_conn(bthost, handle); if (!conn) return; rand = le64_to_cpu(ev->rand); ediv = le16_to_cpu(ev->ediv); cp.handle = ev->handle; err = smp_get_ltk(conn->smp_data, rand, ediv, cp.ltk); if (err < 0) send_command(bthost, BT_HCI_CMD_LE_LTK_REQ_NEG_REPLY, neg_cp, sizeof(*neg_cp)); else send_command(bthost, BT_HCI_CMD_LE_LTK_REQ_REPLY, &cp, sizeof(cp)); } static void init_iso(struct bthost *bthost, uint16_t handle, const uint8_t *bdaddr, uint8_t addr_type) { struct btconn *conn; bthost_debug(bthost, "ISO handle 0x%4.4x", handle); conn = malloc(sizeof(*conn)); if (!conn) return; memset(conn, 0, sizeof(*conn)); conn->handle = handle; memcpy(conn->bdaddr, bdaddr, 6); conn->addr_type = addr_type; conn->next = bthost->conns; bthost->conns = conn; if (bthost->new_iso_cb) bthost->new_iso_cb(handle, bthost->new_iso_data); } static void evt_le_cis_established(struct bthost *bthost, const void *data, uint8_t size) { const struct bt_hci_evt_le_cis_established *ev = data; if (ev->status) return; init_iso(bthost, ev->conn_handle, BDADDR_ANY->b, BDADDR_LE_PUBLIC); } static void evt_le_cis_req(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_le_cis_req *ev = data; struct bt_hci_cmd_le_accept_cis cmd; struct bt_hci_cmd_le_reject_cis rej; if (len < sizeof(*ev)) return; if (bthost->accept_iso_cb) { memset(&rej, 0, sizeof(rej)); rej.reason = bthost->accept_iso_cb(le16_to_cpu(ev->cis_handle), bthost->new_iso_data); if (rej.reason) { rej.handle = ev->cis_handle; send_command(bthost, BT_HCI_CMD_LE_REJECT_CIS, &rej, sizeof(rej)); return; } } memset(&cmd, 0, sizeof(cmd)); cmd.handle = ev->cis_handle; send_command(bthost, BT_HCI_CMD_LE_ACCEPT_CIS, &cmd, sizeof(cmd)); } static void evt_le_ext_adv_report(struct bthost *bthost, const void *data, uint8_t len) { const struct bt_hci_evt_le_ext_adv_report *ev = data; const struct bt_hci_le_ext_adv_report *report; struct le_ext_adv *le_ext_adv; int i; data += sizeof(ev->num_reports); for (i = 0; i < ev->num_reports; i++) { char addr_str[18]; report = data; ba2str((bdaddr_t *) report->addr, addr_str); bthost_debug(bthost, "le ext adv report: %s (0x%02x)", addr_str, report->addr_type); /* Add ext event to the queue */ le_ext_adv = le_ext_adv_new(bthost); if (le_ext_adv) { le_ext_adv->addr_type = report->addr_type; memcpy(le_ext_adv->addr, report->addr, 6); le_ext_adv->direct_addr_type = report->direct_addr_type; memcpy(le_ext_adv->direct_addr, report->direct_addr, 6); } data += (sizeof(*report) + report->data_len); } } static void evt_le_big_complete(struct bthost *bthost, const void *data, uint8_t size) { const struct bt_hci_evt_le_big_complete *ev = data; int i; if (ev->status) return; for (i = 0; i < ev->num_bis; i++) { uint16_t handle = le16_to_cpu(ev->bis_handle[i]); init_iso(bthost, handle, BDADDR_ANY->b, BDADDR_LE_PUBLIC); } } static void evt_le_big_sync_established(struct bthost *bthost, const void *data, uint8_t size) { const struct bt_hci_evt_le_big_sync_estabilished *ev = data; int i; if (ev->status) return; for (i = 0; i < ev->num_bis; i++) { uint16_t handle = le16_to_cpu(ev->bis[i]); init_iso(bthost, handle, BDADDR_ANY->b, BDADDR_LE_PUBLIC); } } static void evt_le_big_info_adv_report(struct bthost *bthost, const void *data, uint8_t size) { const struct bt_hci_evt_le_big_info_adv_report *ev = data; struct { struct bt_hci_cmd_le_big_create_sync cp; struct bt_hci_bis_sync index[1]; } cmd; memset(&cmd, 0, sizeof(cmd)); cmd.cp.handle = 0x01; cmd.cp.sync_handle = ev->sync_handle; cmd.cp.encryption = ev->encryption; cmd.cp.num_bis = 1; send_command(bthost, BT_HCI_CMD_LE_BIG_CREATE_SYNC, &cmd, sizeof(cmd)); } static void evt_le_meta_event(struct bthost *bthost, const void *data, uint8_t len) { const uint8_t *event = data; const void *evt_data = data + 1; if (len < 1) return; bthost_debug(bthost, "meta event 0x%02x", *event); switch (*event) { case BT_HCI_EVT_LE_CONN_COMPLETE: evt_le_conn_complete(bthost, evt_data, len - 1); break; case BT_HCI_EVT_LE_CONN_UPDATE_COMPLETE: evt_le_conn_update_complete(bthost, evt_data, len - 1); break; case BT_HCI_EVT_LE_REMOTE_FEATURES_COMPLETE: evt_le_remote_features_complete(bthost, evt_data, len - 1); break; case BT_HCI_EVT_LE_LONG_TERM_KEY_REQUEST: evt_le_ltk_request(bthost, evt_data, len - 1); break; case BT_HCI_EVT_LE_ENHANCED_CONN_COMPLETE: evt_le_ext_conn_complete(bthost, evt_data, len - 1); break; case BT_HCI_EVT_LE_EXT_ADV_REPORT: evt_le_ext_adv_report(bthost, evt_data, len - 1); break; case BT_HCI_EVT_LE_CIS_ESTABLISHED: evt_le_cis_established(bthost, evt_data, len - 1); break; case BT_HCI_EVT_LE_CIS_REQ: evt_le_cis_req(bthost, evt_data, len - 1); break; case BT_HCI_EVT_LE_BIG_COMPLETE: evt_le_big_complete(bthost, evt_data, len - 1); break; case BT_HCI_EVT_LE_BIG_SYNC_ESTABILISHED: evt_le_big_sync_established(bthost, evt_data, len - 1); break; case BT_HCI_EVT_LE_BIG_INFO_ADV_REPORT: evt_le_big_info_adv_report(bthost, evt_data, len - 1); break; default: bthost_debug(bthost, "Unsupported LE Meta event 0x%2.2x", *event); break; } } static void process_evt(struct bthost *bthost, const void *data, uint16_t len) { const struct bt_hci_evt_hdr *hdr = data; const void *param; if (len < sizeof(*hdr)) return; if (sizeof(*hdr) + hdr->plen != len) return; param = data + sizeof(*hdr); bthost_debug(bthost, "event 0x%02x", hdr->evt); switch (hdr->evt) { case BT_HCI_EVT_CMD_COMPLETE: evt_cmd_complete(bthost, param, hdr->plen); break; case BT_HCI_EVT_CMD_STATUS: evt_cmd_status(bthost, param, hdr->plen); break; case BT_HCI_EVT_CONN_REQUEST: evt_conn_request(bthost, param, hdr->plen); break; case BT_HCI_EVT_CONN_COMPLETE: evt_conn_complete(bthost, param, hdr->plen); break; case BT_HCI_EVT_DISCONNECT_COMPLETE: evt_disconn_complete(bthost, param, hdr->plen); break; case BT_HCI_EVT_SYNC_CONN_COMPLETE: evt_sync_conn_complete(bthost, param, hdr->plen); break; case BT_HCI_EVT_NUM_COMPLETED_PACKETS: evt_num_completed_packets(bthost, param, hdr->plen); break; case BT_HCI_EVT_AUTH_COMPLETE: evt_auth_complete(bthost, param, hdr->plen); break; case BT_HCI_EVT_PIN_CODE_REQUEST: evt_pin_code_request(bthost, param, hdr->plen); break; case BT_HCI_EVT_LINK_KEY_REQUEST: evt_link_key_request(bthost, param, hdr->plen); break; case BT_HCI_EVT_LINK_KEY_NOTIFY: evt_link_key_notify(bthost, param, hdr->plen); break; case BT_HCI_EVT_ENCRYPT_CHANGE: evt_encrypt_change(bthost, param, hdr->plen); break; case BT_HCI_EVT_IO_CAPABILITY_RESPONSE: evt_io_cap_response(bthost, param, hdr->plen); break; case BT_HCI_EVT_IO_CAPABILITY_REQUEST: evt_io_cap_request(bthost, param, hdr->plen); break; case BT_HCI_EVT_USER_CONFIRM_REQUEST: evt_user_confirm_request(bthost, param, hdr->plen); break; case BT_HCI_EVT_SIMPLE_PAIRING_COMPLETE: evt_simple_pairing_complete(bthost, param, hdr->plen); break; case BT_HCI_EVT_LE_META_EVENT: evt_le_meta_event(bthost, param, hdr->plen); break; default: bthost_debug(bthost, "Unsupported event 0x%2.2x", hdr->evt); break; } } static bool l2cap_cmd_rej(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct bt_l2cap_pdu_cmd_reject *rsp = data; if (len < sizeof(*rsp)) return false; return true; } static bool l2cap_conn_req(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct bt_l2cap_pdu_conn_req *req = data; struct l2cap_conn_cb_data *cb_data; struct bt_l2cap_pdu_conn_rsp rsp; uint16_t psm; if (len < sizeof(*req)) return false; psm = le16_to_cpu(req->psm); memset(&rsp, 0, sizeof(rsp)); rsp.scid = req->scid; cb_data = bthost_find_l2cap_cb_by_psm(bthost, psm); if (cb_data) rsp.dcid = rsp.scid; else rsp.result = cpu_to_le16(0x0002); /* PSM Not Supported */ l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_CONN_RSP, ident, &rsp, sizeof(rsp)); if (!rsp.result) { struct bt_l2cap_pdu_config_req conf_req; struct l2conn *l2conn; l2conn = bthost_add_l2cap_conn(bthost, conn, le16_to_cpu(rsp.dcid), le16_to_cpu(rsp.scid), le16_to_cpu(psm)); memset(&conf_req, 0, sizeof(conf_req)); conf_req.dcid = rsp.scid; l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_CONFIG_REQ, 0, &conf_req, sizeof(conf_req)); if (cb_data && l2conn->psm == cb_data->psm && cb_data->func) cb_data->func(conn->handle, l2conn->dcid, cb_data->user_data); } return true; } static void rfcomm_sabm_send(struct bthost *bthost, struct btconn *conn, struct l2conn *l2conn, uint8_t cr, uint8_t dlci) { struct rfcomm_cmd cmd; memset(&cmd, 0, sizeof(cmd)); cmd.address = RFCOMM_ADDR(cr, dlci); cmd.control = RFCOMM_CTRL(RFCOMM_SABM, 1); cmd.length = RFCOMM_LEN8(0); cmd.fcs = rfcomm_fcs2((uint8_t *)&cmd); send_acl(bthost, conn->handle, l2conn->dcid, 0, &cmd, sizeof(cmd)); } static bool l2cap_conn_rsp(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct bt_l2cap_pdu_conn_rsp *rsp = data; struct bt_l2cap_pdu_config_req req; struct l2conn *l2conn; if (len < sizeof(*rsp)) return false; l2conn = btconn_find_l2cap_conn_by_scid(conn, le16_to_cpu(rsp->scid)); if (l2conn) l2conn->dcid = le16_to_cpu(rsp->dcid); else return false; if (rsp->result) return true; memset(&req, 0, sizeof(req)); req.dcid = rsp->dcid; l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_CONFIG_REQ, 0, &req, sizeof(req)); return true; } static bool l2cap_config_req(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct bt_l2cap_pdu_config_req *req = data; struct bt_l2cap_pdu_config_rsp rsp; struct l2conn *l2conn; uint16_t dcid; if (len < sizeof(*req)) return false; dcid = le16_to_cpu(req->dcid); l2conn = btconn_find_l2cap_conn_by_scid(conn, dcid); if (!l2conn) return false; memset(&rsp, 0, sizeof(rsp)); rsp.scid = cpu_to_le16(l2conn->dcid); rsp.flags = req->flags; l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_CONFIG_RSP, ident, &rsp, sizeof(rsp)); return true; } static bool l2cap_config_rsp(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct bt_l2cap_pdu_config_rsp *rsp = data; struct l2conn *l2conn; if (len < sizeof(*rsp)) return false; l2conn = btconn_find_l2cap_conn_by_scid(conn, rsp->scid); if (!l2conn) return false; if (l2conn->psm == 0x0003 && !rsp->result && bthost->rfcomm_conn_data) rfcomm_sabm_send(bthost, conn, l2conn, 1, 0); return true; } static bool l2cap_disconn_req(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct bt_l2cap_pdu_disconn_req *req = data; struct l2cap_conn_cb_data *cb_data; struct bt_l2cap_pdu_disconn_rsp rsp; struct l2conn *l2conn; if (len < sizeof(*req)) return false; memset(&rsp, 0, sizeof(rsp)); rsp.dcid = req->dcid; rsp.scid = req->scid; l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_DISCONN_RSP, ident, &rsp, sizeof(rsp)); l2conn = btconn_find_l2cap_conn_by_scid(conn, rsp.scid); if (!l2conn) return true; btconn_detach_l2cap_conn(conn, l2conn); cb_data = bthost_find_l2cap_cb_by_psm(bthost, l2conn->psm); if (cb_data && cb_data->disconn_func) cb_data->disconn_func(conn->handle, l2conn->dcid, cb_data->user_data); l2conn_free(l2conn); return true; } static bool l2cap_disconn_rsp(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct bt_l2cap_pdu_disconn_rsp *rsp = data; struct l2cap_conn_cb_data *cb_data; struct l2conn *l2conn; if (len < sizeof(*rsp)) return false; l2conn = btconn_find_l2cap_conn_by_scid(conn, rsp->dcid); if (!l2conn) return true; btconn_detach_l2cap_conn(conn, l2conn); cb_data = bthost_find_l2cap_cb_by_psm(bthost, l2conn->psm); if (cb_data && cb_data->disconn_func) cb_data->disconn_func(conn->handle, l2conn->dcid, cb_data->user_data); l2conn_free(l2conn); return true; } static bool l2cap_info_req(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct bt_l2cap_pdu_info_req *req = data; uint64_t fixed_chan; uint16_t type; uint8_t buf[12]; struct bt_l2cap_pdu_info_rsp *rsp = (void *) buf; if (len < sizeof(*req)) return false; memset(buf, 0, sizeof(buf)); rsp->type = req->type; type = le16_to_cpu(req->type); switch (type) { case L2CAP_IT_FEAT_MASK: rsp->result = 0x0000; put_le32(L2CAP_FEAT_FIXED_CHAN, rsp->data); l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_INFO_RSP, ident, rsp, sizeof(*rsp) + 4); break; case L2CAP_IT_FIXED_CHAN: rsp->result = 0x0000; fixed_chan = L2CAP_FC_SIG_BREDR; if (bthost->sc && bthost->le) fixed_chan |= L2CAP_FC_SMP_BREDR; put_le64(fixed_chan, rsp->data); l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_INFO_RSP, ident, rsp, sizeof(*rsp) + sizeof(fixed_chan)); break; default: rsp->result = cpu_to_le16(0x0001); /* Not Supported */ l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_INFO_RSP, ident, rsp, sizeof(*rsp)); break; } return true; } static bool l2cap_info_rsp(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct bt_l2cap_pdu_info_rsp *rsp = data; uint16_t type; if (len < sizeof(*rsp)) return false; if (rsp->result) return true; type = le16_to_cpu(rsp->type); switch (type) { case L2CAP_IT_FIXED_CHAN: if (len < sizeof(*rsp) + 8) return false; conn->fixed_chan = get_le64(rsp->data); if (conn->smp_data && conn->encr_mode) smp_conn_encrypted(conn->smp_data, conn->encr_mode); break; default: break; } return true; } static void handle_pending_l2reqs(struct bthost *bthost, struct btconn *conn, uint8_t ident, uint8_t code, const void *data, uint16_t len) { struct l2cap_pending_req **curr; for (curr = &bthost->l2reqs; *curr != NULL;) { struct l2cap_pending_req *req = *curr; if (req->ident != ident) { curr = &req->next; continue; } *curr = req->next; req->cb(code, data, len, req->user_data); free(req); } } static bool l2cap_rsp(uint8_t code) { switch (code) { case BT_L2CAP_PDU_CMD_REJECT: case BT_L2CAP_PDU_CONN_RSP: case BT_L2CAP_PDU_CONFIG_RSP: case BT_L2CAP_PDU_INFO_RSP: return true; } return false; } static void l2cap_sig(struct bthost *bthost, struct btconn *conn, const void *data, uint16_t len) { const struct bt_l2cap_hdr_sig *hdr = data; struct bt_l2cap_pdu_cmd_reject rej; uint16_t hdr_len; bool ret; if (len < sizeof(*hdr)) goto reject; hdr_len = le16_to_cpu(hdr->len); if (sizeof(*hdr) + hdr_len != len) goto reject; switch (hdr->code) { case BT_L2CAP_PDU_CMD_REJECT: ret = l2cap_cmd_rej(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_CONN_REQ: ret = l2cap_conn_req(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_CONN_RSP: ret = l2cap_conn_rsp(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_CONFIG_REQ: ret = l2cap_config_req(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_CONFIG_RSP: ret = l2cap_config_rsp(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_DISCONN_REQ: ret = l2cap_disconn_req(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_DISCONN_RSP: ret = l2cap_disconn_rsp(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_INFO_REQ: ret = l2cap_info_req(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_INFO_RSP: ret = l2cap_info_rsp(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; default: bthost_debug(bthost, "Unknown L2CAP code 0x%02x", hdr->code); ret = false; } if (l2cap_rsp(hdr->code)) handle_pending_l2reqs(bthost, conn, hdr->ident, hdr->code, data + sizeof(*hdr), hdr_len); if (ret) return; reject: memset(&rej, 0, sizeof(rej)); l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_CMD_REJECT, 0, &rej, sizeof(rej)); } static bool l2cap_conn_param_req(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct bt_l2cap_pdu_conn_param_req *req = data; struct bt_l2cap_pdu_conn_param_rsp rsp; struct bt_hci_cmd_le_conn_update hci_cmd; if (len < sizeof(*req)) return false; memset(&hci_cmd, 0, sizeof(hci_cmd)); hci_cmd.handle = cpu_to_le16(conn->handle); hci_cmd.min_interval = req->min_interval; hci_cmd.max_interval = req->max_interval; hci_cmd.latency = req->latency; hci_cmd.supv_timeout = req->timeout; hci_cmd.min_length = cpu_to_le16(0x0001); hci_cmd.max_length = cpu_to_le16(0x0001); send_command(bthost, BT_HCI_CMD_LE_CONN_UPDATE, &hci_cmd, sizeof(hci_cmd)); memset(&rsp, 0, sizeof(rsp)); l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_CONN_PARAM_RSP, ident, &rsp, sizeof(rsp)); return true; } static bool l2cap_conn_param_rsp(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct bt_l2cap_pdu_conn_param_req *rsp = data; if (len < sizeof(*rsp)) return false; return true; } static bool l2cap_le_conn_req(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct bt_l2cap_pdu_le_conn_req *req = data; struct l2cap_conn_cb_data *cb_data; struct bt_l2cap_pdu_le_conn_rsp rsp; uint16_t psm; if (len < sizeof(*req)) return false; psm = le16_to_cpu(req->psm); memset(&rsp, 0, sizeof(rsp)); cb_data = bthost_find_l2cap_cb_by_psm(bthost, psm); if (cb_data) { rsp.dcid = cpu_to_le16(conn->next_cid++); rsp.mtu = cpu_to_le16(cb_data->mtu) ? : cpu_to_le16(23); rsp.mps = cpu_to_le16(cb_data->mps) ? : cpu_to_le16(23); rsp.credits = cpu_to_le16(cb_data->credits) ? : cpu_to_le16(1); } else rsp.result = cpu_to_le16(0x0002); /* PSM Not Supported */ l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_LE_CONN_RSP, ident, &rsp, sizeof(rsp)); if (!rsp.result) { struct l2conn *l2conn; l2conn = bthost_add_l2cap_conn(bthost, conn, le16_to_cpu(rsp.dcid), le16_to_cpu(req->scid), le16_to_cpu(psm)); l2conn->mode = L2CAP_MODE_LE_CRED; l2conn->rx_mps = le16_to_cpu(rsp.mps); l2conn->tx_mps = le16_to_cpu(req->mps); l2conn->rx_credits = le16_to_cpu(rsp.credits); l2conn->tx_credits = le16_to_cpu(req->credits); if (cb_data && l2conn->psm == cb_data->psm && cb_data->func) cb_data->func(conn->handle, l2conn->dcid, cb_data->user_data); } return true; } static bool l2cap_le_conn_rsp(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct bt_l2cap_pdu_le_conn_rsp *rsp = data; struct l2conn *l2conn; if (len < sizeof(*rsp)) return false; /* TODO add L2CAP connection before with proper PSM */ l2conn = bthost_add_l2cap_conn(bthost, conn, 0, le16_to_cpu(rsp->dcid), 0); l2conn->mode = L2CAP_MODE_LE_CRED; l2conn->rx_credits = 1; l2conn->tx_credits = le16_to_cpu(rsp->credits); return true; } static bool l2cap_ecred_conn_req(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct bt_l2cap_pdu_ecred_conn_req *req = data; struct { struct bt_l2cap_pdu_ecred_conn_rsp pdu; uint16_t dcid[5]; } __attribute__ ((packed)) rsp; uint16_t psm; int num_scid, i = 0; if (len < sizeof(*req)) return false; psm = le16_to_cpu(req->psm); memset(&rsp, 0, sizeof(rsp)); rsp.pdu.mtu = 64; rsp.pdu.mps = 64; rsp.pdu.credits = 1; if (!bthost_find_l2cap_cb_by_psm(bthost, psm)) { rsp.pdu.result = cpu_to_le16(0x0002); /* PSM Not Supported */ goto respond; } len -= sizeof(rsp.pdu); num_scid = len / sizeof(*req->scid); for (; i < num_scid; i++) rsp.dcid[i] = cpu_to_le16(conn->next_cid++); respond: l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_ECRED_CONN_RSP, ident, &rsp, sizeof(rsp.pdu) + i * sizeof(*rsp.dcid)); return true; } static bool l2cap_ecred_conn_rsp(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct { const struct bt_l2cap_pdu_ecred_conn_rsp pdu; uint16_t scid[5]; } __attribute__ ((packed)) *rsp = data; int num_scid, i; struct l2conn *l2conn; if (len < sizeof(*rsp)) return false; num_scid = len / sizeof(*rsp->scid); for (i = 0; i < num_scid; i++) { /* TODO add L2CAP connection before with proper PSM */ l2conn = bthost_add_l2cap_conn(bthost, conn, 0, le16_to_cpu(rsp->scid[i]), 0); l2conn->mode = L2CAP_MODE_LE_ENH_CRED; l2conn->tx_credits = rsp->pdu.credits; l2conn->rx_credits = 1; } return true; } static bool l2cap_le_flowctl_creds(struct bthost *bthost, struct btconn *conn, uint8_t ident, const void *data, uint16_t len) { const struct bt_l2cap_pdu_le_flowctl_creds *ind = data; struct l2conn *l2conn; if (len < sizeof(*ind)) return false; l2conn = btconn_find_l2cap_conn_by_dcid(conn, ind->cid); if (!l2conn) return true; l2conn->tx_credits += ind->credits; return true; } static bool l2cap_le_rsp(uint8_t code) { switch (code) { case BT_L2CAP_PDU_CMD_REJECT: case BT_L2CAP_PDU_CONN_PARAM_RSP: case BT_L2CAP_PDU_LE_CONN_RSP: case BT_L2CAP_PDU_ECRED_CONN_RSP: return true; } return false; } static void l2cap_le_sig(struct bthost *bthost, struct btconn *conn, const void *data, uint16_t len) { const struct bt_l2cap_hdr_sig *hdr = data; struct bt_l2cap_pdu_cmd_reject rej; uint16_t hdr_len; bool ret; if (len < sizeof(*hdr)) goto reject; hdr_len = le16_to_cpu(hdr->len); if (sizeof(*hdr) + hdr_len != len) goto reject; switch (hdr->code) { case BT_L2CAP_PDU_CMD_REJECT: ret = l2cap_cmd_rej(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_DISCONN_REQ: ret = l2cap_disconn_req(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_DISCONN_RSP: ret = l2cap_disconn_rsp(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_CONN_PARAM_REQ: ret = l2cap_conn_param_req(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_CONN_PARAM_RSP: ret = l2cap_conn_param_rsp(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_LE_CONN_REQ: ret = l2cap_le_conn_req(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_LE_CONN_RSP: ret = l2cap_le_conn_rsp(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_ECRED_CONN_REQ: ret = l2cap_ecred_conn_req(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_ECRED_CONN_RSP: ret = l2cap_ecred_conn_rsp(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; case BT_L2CAP_PDU_LE_FLOWCTL_CREDS: ret = l2cap_le_flowctl_creds(bthost, conn, hdr->ident, data + sizeof(*hdr), hdr_len); break; default: bthost_debug(bthost, "Unknown L2CAP code 0x%02x", hdr->code); ret = false; } if (l2cap_le_rsp(hdr->code)) handle_pending_l2reqs(bthost, conn, hdr->ident, hdr->code, data + sizeof(*hdr), hdr_len); if (ret) return; reject: memset(&rej, 0, sizeof(rej)); l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_CMD_REJECT, 0, &rej, sizeof(rej)); } static struct cid_hook *find_cid_hook(struct btconn *conn, uint16_t cid) { struct cid_hook *hook; for (hook = conn->cid_hooks; hook != NULL; hook = hook->next) { if (hook->cid == cid) return hook; } return NULL; } static struct rfcomm_chan_hook *find_rfcomm_chan_hook(struct btconn *conn, uint8_t channel) { struct rfcomm_chan_hook *hook; for (hook = conn->rfcomm_chan_hooks; hook != NULL; hook = hook->next) if (hook->channel == channel) return hook; return NULL; } static void rfcomm_ua_send(struct bthost *bthost, struct btconn *conn, struct l2conn *l2conn, uint8_t cr, uint8_t dlci) { struct rfcomm_cmd cmd; memset(&cmd, 0, sizeof(cmd)); cmd.address = RFCOMM_ADDR(cr, dlci); cmd.control = RFCOMM_CTRL(RFCOMM_UA, 1); cmd.length = RFCOMM_LEN8(0); cmd.fcs = rfcomm_fcs2((uint8_t *)&cmd); send_acl(bthost, conn->handle, l2conn->dcid, 0, &cmd, sizeof(cmd)); } static void rfcomm_dm_send(struct bthost *bthost, struct btconn *conn, struct l2conn *l2conn, uint8_t cr, uint8_t dlci) { struct rfcomm_cmd cmd; memset(&cmd, 0, sizeof(cmd)); cmd.address = RFCOMM_ADDR(cr, dlci); cmd.control = RFCOMM_CTRL(RFCOMM_DM, 1); cmd.length = RFCOMM_LEN8(0); cmd.fcs = rfcomm_fcs2((uint8_t *)&cmd); send_acl(bthost, conn->handle, l2conn->dcid, 0, &cmd, sizeof(cmd)); } static void rfcomm_sabm_recv(struct bthost *bthost, struct btconn *conn, struct l2conn *l2conn, const void *data, uint16_t len) { const struct rfcomm_cmd *hdr = data; uint8_t dlci; struct rfcomm_conn_cb_data *cb; uint8_t chan; if (len < sizeof(*hdr)) return; chan = RFCOMM_GET_CHANNEL(hdr->address); dlci = RFCOMM_GET_DLCI(hdr->address); cb = bthost_find_rfcomm_cb_by_channel(bthost, chan); if (!dlci || cb) { bthost_add_rfcomm_conn(bthost, conn, l2conn, chan); rfcomm_ua_send(bthost, conn, l2conn, 1, dlci); if (cb && cb->func) cb->func(conn->handle, l2conn->scid, cb->user_data, true); } else { rfcomm_dm_send(bthost, conn, l2conn, 1, dlci); } } static void rfcomm_disc_recv(struct bthost *bthost, struct btconn *conn, struct l2conn *l2conn, const void *data, uint16_t len) { const struct rfcomm_cmd *hdr = data; uint8_t dlci; if (len < sizeof(*hdr)) return; dlci = RFCOMM_GET_DLCI(hdr->address); rfcomm_ua_send(bthost, conn, l2conn, 0, dlci); } static void rfcomm_uih_send(struct bthost *bthost, struct btconn *conn, struct l2conn *l2conn, uint8_t address, uint8_t type, const void *data, uint16_t len) { struct rfcomm_hdr hdr; struct rfcomm_mcc mcc; uint8_t fcs; struct iovec iov[4]; hdr.address = address; hdr.control = RFCOMM_CTRL(RFCOMM_UIH, 0); hdr.length = RFCOMM_LEN8(sizeof(mcc) + len); iov[0].iov_base = &hdr; iov[0].iov_len = sizeof(hdr); mcc.type = type; mcc.length = RFCOMM_LEN8(len); iov[1].iov_base = &mcc; iov[1].iov_len = sizeof(mcc); iov[2].iov_base = (void *) data; iov[2].iov_len = len; fcs = rfcomm_fcs((uint8_t *) &hdr); iov[3].iov_base = &fcs; iov[3].iov_len = sizeof(fcs); send_iov(bthost, conn->handle, l2conn->dcid, iov, 4); } static void rfcomm_ua_recv(struct bthost *bthost, struct btconn *conn, struct l2conn *l2conn, const void *data, uint16_t len) { const struct rfcomm_cmd *ua_hdr = data; uint8_t channel; struct rfcomm_connection_data *conn_data = bthost->rfcomm_conn_data; uint8_t type; struct rfcomm_pn pn_cmd; if (len < sizeof(*ua_hdr)) return; channel = RFCOMM_GET_CHANNEL(ua_hdr->address); type = RFCOMM_GET_TYPE(ua_hdr->control); if (channel && conn_data && conn_data->channel == channel) { bthost_add_rfcomm_conn(bthost, conn, l2conn, channel); if (conn_data->cb) conn_data->cb(conn->handle, l2conn->scid, conn_data->user_data, true); free(bthost->rfcomm_conn_data); bthost->rfcomm_conn_data = NULL; return; } if (!conn_data || !RFCOMM_TEST_CR(type)) return; bthost_add_rfcomm_conn(bthost, conn, l2conn, channel); pn_cmd.dlci = conn_data->channel * 2; pn_cmd.priority = 7; pn_cmd.ack_timer = 0; pn_cmd.max_retrans = 0; pn_cmd.mtu = 667; pn_cmd.credits = 7; rfcomm_uih_send(bthost, conn, l2conn, RFCOMM_ADDR(1, 0), RFCOMM_MCC_TYPE(1, RFCOMM_PN), &pn_cmd, sizeof(pn_cmd)); } static void rfcomm_dm_recv(struct bthost *bthost, struct btconn *conn, struct l2conn *l2conn, const void *data, uint16_t len) { const struct rfcomm_cmd *hdr = data; uint8_t channel; struct rfcomm_connection_data *conn_data = bthost->rfcomm_conn_data; if (len < sizeof(*hdr)) return; channel = RFCOMM_GET_CHANNEL(hdr->address); if (conn_data && conn_data->channel == channel) { if (conn_data->cb) conn_data->cb(conn->handle, l2conn->scid, conn_data->user_data, false); free(bthost->rfcomm_conn_data); bthost->rfcomm_conn_data = NULL; } } static void rfcomm_msc_recv(struct bthost *bthost, struct btconn *conn, struct l2conn *l2conn, uint8_t cr, const struct rfcomm_msc *msc) { struct rfcomm_msc msc_cmd; msc_cmd.dlci = msc->dlci; msc_cmd.v24_sig = msc->v24_sig; rfcomm_uih_send(bthost, conn, l2conn, RFCOMM_ADDR(0, 0), RFCOMM_MCC_TYPE(cr, RFCOMM_MSC), &msc_cmd, sizeof(msc_cmd)); } static void rfcomm_pn_recv(struct bthost *bthost, struct btconn *conn, struct l2conn *l2conn, uint8_t cr, const struct rfcomm_pn *pn) { struct rfcomm_pn pn_cmd; if (!cr) { rfcomm_sabm_send(bthost, conn, l2conn, 1, bthost->rfcomm_conn_data->channel * 2); return; } pn_cmd.dlci = pn->dlci; pn_cmd.flow_ctrl = pn->flow_ctrl; pn_cmd.priority = pn->priority; pn_cmd.ack_timer = pn->ack_timer; pn_cmd.max_retrans = pn->max_retrans; pn_cmd.mtu = pn->mtu; pn_cmd.credits = 255; rfcomm_uih_send(bthost, conn, l2conn, RFCOMM_ADDR(1, 0), RFCOMM_MCC_TYPE(0, RFCOMM_PN), &pn_cmd, sizeof(pn_cmd)); } static void rfcomm_mcc_recv(struct bthost *bthost, struct btconn *conn, struct l2conn *l2conn, const void *data, uint16_t len) { const struct rfcomm_mcc *mcc = data; const struct rfcomm_msc *msc; const struct rfcomm_pn *pn; if (len < sizeof(*mcc)) return; switch (RFCOMM_GET_MCC_TYPE(mcc->type)) { case RFCOMM_MSC: if (len - sizeof(*mcc) < sizeof(*msc)) break; msc = data + sizeof(*mcc); rfcomm_msc_recv(bthost, conn, l2conn, RFCOMM_TEST_CR(mcc->type) / 2, msc); break; case RFCOMM_PN: if (len - sizeof(*mcc) < sizeof(*pn)) break; pn = data + sizeof(*mcc); rfcomm_pn_recv(bthost, conn, l2conn, RFCOMM_TEST_CR(mcc->type) / 2, pn); break; default: break; } } #define GET_LEN8(length) ((length & 0xfe) >> 1) #define GET_LEN16(length) ((length & 0xfffe) >> 1) static void rfcomm_uih_recv(struct bthost *bthost, struct btconn *conn, struct l2conn *l2conn, const void *data, uint16_t len) { const struct rfcomm_hdr *hdr = data; uint16_t hdr_len, data_len; const void *p; if (len < sizeof(*hdr)) { bthost_debug(bthost, "RFCOMM UIH: too short"); return; } if (RFCOMM_TEST_EA(hdr->length)) { data_len = (uint16_t) GET_LEN8(hdr->length); hdr_len = sizeof(*hdr); } else { uint8_t ex_len = *((uint8_t *)(data + sizeof(*hdr))); data_len = GET_LEN16((((uint16_t) ex_len << 8) | hdr->length)); hdr_len = sizeof(*hdr) + sizeof(uint8_t); } if (len < hdr_len + data_len) { bthost_debug(bthost, "RFCOMM UIH: %u != %u", len, hdr_len + data_len); return; } p = data + hdr_len; if (RFCOMM_GET_DLCI(hdr->address)) { struct rfcomm_chan_hook *hook; hook = find_rfcomm_chan_hook(conn, RFCOMM_GET_CHANNEL(hdr->address)); if (hook && data_len) hook->func(p, data_len, hook->user_data); } else { rfcomm_mcc_recv(bthost, conn, l2conn, p, data_len); } } static void process_rfcomm(struct bthost *bthost, struct btconn *conn, struct l2conn *l2conn, const void *data, uint16_t len) { const struct rfcomm_hdr *hdr = data; bthost_debug(bthost, "RFCOMM data: %u bytes", len); switch (RFCOMM_GET_TYPE(hdr->control)) { case RFCOMM_SABM: rfcomm_sabm_recv(bthost, conn, l2conn, data, len); break; case RFCOMM_DISC: rfcomm_disc_recv(bthost, conn, l2conn, data, len); break; case RFCOMM_UA: rfcomm_ua_recv(bthost, conn, l2conn, data, len); break; case RFCOMM_DM: rfcomm_dm_recv(bthost, conn, l2conn, data, len); break; case RFCOMM_UIH: rfcomm_uih_recv(bthost, conn, l2conn, data, len); break; default: bthost_debug(bthost, "Unknown frame type"); break; } } static void append_l2conn_data(struct bthost *bthost, struct l2conn *conn, const void *data, uint16_t len) { if (!conn->recv_data) { bthost_debug(bthost, "Unexpected L2CAP SDU data: sCID 0x%4.4x ", conn->scid); return; } if (conn->recv_len + len > conn->data_len) { bthost_debug(bthost, "Unexpected L2CAP SDU data: sCID 0x%4.4x ", conn->scid); return; } memcpy(conn->recv_data + conn->recv_len, data, len); conn->recv_len += len; bthost_debug(bthost, "L2CAP SDU data: %u/%u bytes", conn->recv_len, conn->data_len); } static void free_l2conn_data(struct l2conn *conn) { free(conn->recv_data); conn->recv_data = NULL; conn->recv_len = 0; conn->data_len = 0; } static void new_l2conn_data(struct bthost *bthost, struct l2conn *conn, uint16_t len) { free(conn->recv_data); conn->recv_data = malloc(len); conn->recv_len = 0; conn->data_len = len; } static bool process_l2cap_conn(struct bthost *bthost, struct btconn *conn, struct l2conn *l2conn, struct iovec *data) { struct bt_l2cap_pdu_le_flowctl_creds creds; uint16_t sdu; if (!l2conn) return true; switch (l2conn->mode) { case L2CAP_MODE_LE_CRED: case L2CAP_MODE_LE_ENH_CRED: break; case L2CAP_MODE_OTHER: return true; } /* Credit-based flow control */ creds.cid = cpu_to_le16(l2conn->scid); creds.credits = cpu_to_le16(1); l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_LE_FLOWCTL_CREDS, 0, &creds, sizeof(creds)); if (!l2conn->data_len) { if (!util_iov_pull_le16(data, &sdu)) { free_l2conn_data(l2conn); bthost_debug(bthost, "L2CAP invalid SDU"); return false; } new_l2conn_data(bthost, l2conn, sdu); } append_l2conn_data(bthost, l2conn, data->iov_base, data->iov_len); if (l2conn->recv_len < l2conn->data_len) return false; /* SDU incomplete */ l2conn->data_len = 0; data->iov_base = l2conn->recv_data; data->iov_len = l2conn->recv_len; return true; } static void process_l2cap(struct bthost *bthost, struct btconn *conn, const void *buf, uint16_t len) { const struct bt_l2cap_hdr *l2_hdr = buf; struct cid_hook *hook; struct l2conn *l2conn; struct iovec data; uint16_t cid, l2_len; l2_len = le16_to_cpu(l2_hdr->len); if (len != sizeof(*l2_hdr) + l2_len) { bthost_debug(bthost, "L2CAP invalid length: %u != %zu", len, sizeof(*l2_hdr) + l2_len); return; } bthost_debug(bthost, "L2CAP data: %u bytes", l2_len); cid = le16_to_cpu(l2_hdr->cid); l2conn = btconn_find_l2cap_conn_by_scid(conn, cid); data.iov_base = (void *)l2_hdr->data; data.iov_len = l2_len; if (!process_l2cap_conn(bthost, conn, l2conn, &data)) return; hook = find_cid_hook(conn, cid); if (hook) { hook->func(data.iov_base, data.iov_len, hook->user_data); return; } switch (cid) { case 0x0001: l2cap_sig(bthost, conn, data.iov_base, data.iov_len); break; case 0x0005: l2cap_le_sig(bthost, conn, data.iov_base, data.iov_len); break; case 0x0006: smp_data(conn->smp_data, data.iov_base, data.iov_len); break; case 0x0007: smp_bredr_data(conn->smp_data, data.iov_base, data.iov_len); break; default: if (l2conn && l2conn->psm == 0x0003) process_rfcomm(bthost, conn, l2conn, data.iov_base, data.iov_len); else bthost_debug(bthost, "Packet for unknown CID 0x%04x (%u)", cid, cid); break; } } static void append_recv_data(struct bthost *bthost, struct btconn *conn, const char *type, uint8_t flags, const void *data, uint16_t len) { if (!conn->recv_data) { bthost_debug(bthost, "Unexpected %s frame: handle 0x%4.4x " "flags 0x%2.2x", type, conn->handle, flags); return; } if (conn->recv_len + len > conn->data_len) { bthost_debug(bthost, "Unexpected %s frame: handle 0x%4.4x " "flags 0x%2.2x", type, conn->handle, flags); return; } memcpy(conn->recv_data + conn->recv_len, data, len); conn->recv_len += len; bthost_debug(bthost, "%s data: %u/%u bytes", type, conn->recv_len, conn->data_len); } static void free_recv_data(struct btconn *conn) { free(conn->recv_data); conn->recv_data = NULL; conn->recv_len = 0; conn->data_len = 0; } static void append_acl_data(struct bthost *bthost, struct btconn *conn, uint8_t flags, const void *data, uint16_t len) { append_recv_data(bthost, conn, "ACL", flags, data, len); if (conn->recv_len < conn->data_len) return; process_l2cap(bthost, conn, conn->recv_data, conn->recv_len); free_recv_data(conn); } static void new_recv_data(struct btconn *conn, uint16_t len) { conn->recv_data = malloc(len); conn->recv_len = 0; conn->data_len = len; } static void process_acl(struct bthost *bthost, const void *data, uint16_t len) { const struct bt_hci_acl_hdr *acl_hdr = data; const struct bt_l2cap_hdr *l2_hdr = (void *) acl_hdr->data; uint16_t handle, acl_len, l2_len; uint8_t flags; struct btconn *conn; acl_len = le16_to_cpu(acl_hdr->dlen); if (len != sizeof(*acl_hdr) + acl_len) return; handle = acl_handle(acl_hdr->handle); flags = acl_flags(acl_hdr->handle); conn = bthost_find_conn(bthost, handle); if (!conn) { bthost_debug(bthost, "Unknown handle: 0x%4.4x", handle); return; } switch (flags) { case 0x00: /* start of a non-automatically-flushable PDU */ case 0x02: /* start of an automatically-flushable PDU */ if (conn->recv_data) { bthost_debug(bthost, "Unexpected ACL start frame"); free_recv_data(conn); } l2_len = le16_to_cpu(l2_hdr->len) + sizeof(*l2_hdr); bthost_debug(bthost, "acl_len %u l2_len %u", acl_len, l2_len); if (acl_len == l2_len) { process_l2cap(bthost, conn, acl_hdr->data, acl_len); break; } new_recv_data(conn, l2_len); /* fall through */ case 0x01: /* continuing fragment */ append_acl_data(bthost, conn, flags, acl_hdr->data, acl_len); break; case 0x03: /* complete automatically-flushable PDU */ process_l2cap(bthost, conn, acl_hdr->data, acl_len); break; default: bthost_debug(bthost, "Invalid ACL frame flags 0x%2.2x", flags); } } static void process_sco(struct bthost *bthost, const void *data, uint16_t len) { const struct bt_hci_sco_hdr *sco_hdr = data; uint16_t handle, sco_len; uint8_t status; struct btconn *conn; struct sco_hook *hook; sco_len = le16_to_cpu(sco_hdr->dlen); if (len != sizeof(*sco_hdr) + sco_len) return; handle = acl_handle(sco_hdr->handle); status = sco_flags_status(acl_flags(sco_hdr->handle)); conn = bthost_find_conn(bthost, handle); if (!conn) { bthost_debug(bthost, "Unknown handle: 0x%4.4x", handle); return; } bthost_debug(bthost, "SCO data: %u bytes", sco_len); hook = conn->sco_hook; if (!hook) return; hook->func(sco_hdr->data, sco_len, status, hook->user_data); } static void process_iso_data(struct bthost *bthost, struct btconn *conn, const void *data, uint16_t len) { const struct bt_hci_iso_data_start *data_hdr = data; uint16_t data_len; struct iso_hook *hook; data_len = le16_to_cpu(data_hdr->slen); if (len != sizeof(*data_hdr) + data_len) { bthost_debug(bthost, "ISO invalid length: %u != %zu", len, sizeof(*data_hdr) + data_len); return; } bthost_debug(bthost, "ISO data: %u bytes (%u)", data_len, data_hdr->sn); hook = conn->iso_hook; if (!hook) return; hook->func(data_hdr->data, data_len, hook->user_data); } static void append_iso_data(struct bthost *bthost, struct btconn *conn, uint8_t flags, const void *data, uint16_t len) { append_recv_data(bthost, conn, "ISO", flags, data, len); if (conn->recv_len < conn->data_len) { if (flags == 0x03) { bthost_debug(bthost, "Unexpected ISO end frame"); free_recv_data(conn); } return; } process_iso_data(bthost, conn, conn->recv_data, conn->recv_len); free_recv_data(conn); } static void process_iso(struct bthost *bthost, const void *data, uint16_t len) { const struct bt_hci_iso_hdr *iso_hdr = data; const struct bt_hci_iso_data_start *data_hdr; uint16_t handle, iso_len, data_len; uint8_t flags; struct btconn *conn; iso_len = le16_to_cpu(iso_hdr->dlen); if (len != sizeof(*iso_hdr) + iso_len) return; handle = acl_handle(iso_hdr->handle); flags = iso_flags_pb(acl_flags(iso_hdr->handle)); conn = bthost_find_conn(bthost, handle); if (!conn) { bthost_debug(bthost, "Unknown handle: 0x%4.4x", handle); return; } data_hdr = (void *) data + sizeof(*iso_hdr); switch (flags) { case 0x00: case 0x02: if (conn->recv_data) { bthost_debug(bthost, "Unexpected ISO start frame"); free_recv_data(conn); } data_len = le16_to_cpu(data_hdr->slen) + sizeof(*data_hdr); bthost_debug(bthost, "iso_len %u data_len %u", iso_len, data_len); if (iso_len == data_len) { process_iso_data(bthost, conn, iso_hdr->data, iso_len); break; } new_recv_data(conn, data_len); /* fall through */ case 0x01: case 0x03: append_iso_data(bthost, conn, flags, iso_hdr->data, iso_len); break; default: bthost_debug(bthost, "Invalid ISO frame flags 0x%2.2x", flags); } } void bthost_receive_h4(struct bthost *bthost, const void *data, uint16_t len) { uint8_t pkt_type; if (!bthost) return; if (len < 1) return; util_hexdump('>', data, len, bthost->debug_callback, bthost->debug_data); pkt_type = ((const uint8_t *) data)[0]; switch (pkt_type) { case BT_H4_EVT_PKT: process_evt(bthost, data + 1, len - 1); break; case BT_H4_ACL_PKT: process_acl(bthost, data + 1, len - 1); break; case BT_H4_SCO_PKT: process_sco(bthost, data + 1, len - 1); break; case BT_H4_ISO_PKT: process_iso(bthost, data + 1, len - 1); break; default: bthost_debug(bthost, "Unsupported packet 0x%2.2x", pkt_type); break; } } void bthost_set_cmd_complete_cb(struct bthost *bthost, bthost_cmd_complete_cb cb, void *user_data) { bthost->cmd_complete_cb = cb; bthost->cmd_complete_data = user_data; } void bthost_set_connect_cb(struct bthost *bthost, bthost_new_conn_cb cb, void *user_data) { bthost->new_conn_cb = cb; bthost->new_conn_data = user_data; } void bthost_set_sco_cb(struct bthost *bthost, bthost_new_conn_cb cb, void *user_data) { bthost->new_sco_cb = cb; bthost->new_sco_data = user_data; } void bthost_set_iso_cb(struct bthost *bthost, bthost_accept_conn_cb accept, bthost_new_conn_cb cb, void *user_data) { bthost->accept_iso_cb = accept; bthost->new_iso_cb = cb; bthost->new_iso_data = user_data; } void bthost_hci_connect(struct bthost *bthost, const uint8_t *bdaddr, uint8_t addr_type) { bthost->conn_init = true; if (addr_type == BDADDR_BREDR) { struct bt_hci_cmd_create_conn cc; memset(&cc, 0, sizeof(cc)); memcpy(cc.bdaddr, bdaddr, sizeof(cc.bdaddr)); send_command(bthost, BT_HCI_CMD_CREATE_CONN, &cc, sizeof(cc)); } else { struct bt_hci_cmd_le_create_conn cc; memset(&cc, 0, sizeof(cc)); memcpy(cc.peer_addr, bdaddr, sizeof(cc.peer_addr)); if (addr_type == BDADDR_LE_RANDOM) cc.peer_addr_type = 0x01; cc.scan_interval = cpu_to_le16(0x0060); cc.scan_window = cpu_to_le16(0x0030); cc.min_interval = cpu_to_le16(0x0028); cc.max_interval = cpu_to_le16(0x0038); cc.supv_timeout = cpu_to_le16(0x002a); send_command(bthost, BT_HCI_CMD_LE_CREATE_CONN, &cc, sizeof(cc)); } } void bthost_hci_ext_connect(struct bthost *bthost, const uint8_t *bdaddr, uint8_t addr_type) { struct bt_hci_cmd_le_ext_create_conn *cc; struct bt_hci_le_ext_create_conn *cp; uint8_t buf[sizeof(*cc) + sizeof(*cp)]; cc = (void *)buf; cp = (void *)cc->data; bthost->conn_init = true; memset(cc, 0, sizeof(*cc)); memset(cp, 0, sizeof(*cp)); memcpy(cc->peer_addr, bdaddr, sizeof(cc->peer_addr)); if (addr_type == BDADDR_LE_RANDOM) cc->peer_addr_type = 0x01; cc->phys = 0x01; cp->scan_interval = cpu_to_le16(0x0060); cp->scan_window = cpu_to_le16(0x0030); cp->min_interval = cpu_to_le16(0x0028); cp->max_interval = cpu_to_le16(0x0038); cp->supv_timeout = cpu_to_le16(0x002a); send_command(bthost, BT_HCI_CMD_LE_EXT_CREATE_CONN, buf, sizeof(buf)); } void bthost_hci_disconnect(struct bthost *bthost, uint16_t handle, uint8_t reason) { struct bt_hci_cmd_disconnect disc; disc.handle = cpu_to_le16(handle); disc.reason = reason; send_command(bthost, BT_HCI_CMD_DISCONNECT, &disc, sizeof(disc)); } int bthost_setup_sco(struct bthost *bthost, uint16_t acl_handle, uint16_t setting) { const struct bt_hci_cmd_setup_sync_conn settings[] = { { .tx_bandwidth = cpu_to_le32(0x00001f40), .rx_bandwidth = cpu_to_le32(0x00001f40), .max_latency = cpu_to_le16(0x000a), .retrans_effort = 0x01, .voice_setting = cpu_to_le16(BT_VOICE_CVSD_16BIT), }, { .tx_bandwidth = cpu_to_le32(0x00001f40), .rx_bandwidth = cpu_to_le32(0x00001f40), .max_latency = cpu_to_le16(0x000d), .retrans_effort = 0x02, .voice_setting = cpu_to_le16(BT_VOICE_TRANSPARENT), } }; struct bt_hci_cmd_setup_sync_conn cmd; unsigned int i; memset(&cmd, 0, sizeof(cmd)); for (i = 0; i < ARRAY_SIZE(settings); ++i) { if (settings[i].voice_setting == cpu_to_le16(setting)) { memcpy(&cmd, &settings[i], sizeof(cmd)); break; } } if (!cmd.voice_setting) return -EINVAL; cmd.handle = cpu_to_le16(acl_handle); send_command(bthost, BT_HCI_CMD_SETUP_SYNC_CONN, &cmd, sizeof(cmd)); return 0; } void bthost_write_scan_enable(struct bthost *bthost, uint8_t scan) { send_command(bthost, BT_HCI_CMD_WRITE_SCAN_ENABLE, &scan, 1); } void bthost_set_adv_data(struct bthost *bthost, const uint8_t *data, uint8_t len) { struct bt_hci_cmd_le_set_adv_data adv_cp; memset(adv_cp.data, 0, 31); if (len) { adv_cp.len = len; memcpy(adv_cp.data, data, len); } send_command(bthost, BT_HCI_CMD_LE_SET_ADV_DATA, &adv_cp, sizeof(adv_cp)); } void bthost_set_ext_adv_data(struct bthost *bthost, const uint8_t *data, uint8_t len) { struct bt_hci_cmd_le_set_ext_adv_data *adv_cp; uint8_t buf[sizeof(*adv_cp) + 31]; adv_cp = (void *)buf; memset(adv_cp, 0, sizeof(*adv_cp)); memset(adv_cp->data, 0, 31); adv_cp->handle = 1; adv_cp->operation = 0x03; adv_cp->fragment_preference = 0x01; if (len) { adv_cp->data_len = len; memcpy(adv_cp->data, data, len); } send_command(bthost, BT_HCI_CMD_LE_SET_EXT_ADV_DATA, buf, sizeof(buf)); } void bthost_set_adv_enable(struct bthost *bthost, uint8_t enable) { struct bt_hci_cmd_le_set_adv_parameters cp; memset(&cp, 0, sizeof(cp)); send_command(bthost, BT_HCI_CMD_LE_SET_ADV_PARAMETERS, &cp, sizeof(cp)); send_command(bthost, BT_HCI_CMD_LE_SET_ADV_ENABLE, &enable, 1); } void bthost_set_scan_params(struct bthost *bthost, uint8_t scan_type, uint8_t addr_type, uint8_t filter_policy) { struct bt_hci_cmd_le_set_scan_parameters cp; memset(&cp, 0, sizeof(cp)); cp.type = scan_type; cp.own_addr_type = addr_type; cp.filter_policy = filter_policy; send_command(bthost, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS, &cp, sizeof(cp)); } void bthost_set_scan_enable(struct bthost *bthost, uint8_t enable) { struct bt_hci_cmd_le_set_scan_enable cp; memset(&cp, 0, sizeof(cp)); cp.enable = enable; send_command(bthost, BT_HCI_CMD_LE_SET_SCAN_ENABLE, &cp, sizeof(cp)); } void bthost_set_ext_adv_params(struct bthost *bthost, uint8_t sid) { const uint8_t interval_20ms[] = { 0x20, 0x00, 0x00 }; struct bt_hci_cmd_le_set_ext_adv_params cp; memset(&cp, 0, sizeof(cp)); cp.handle = 0x01; cp.evt_properties = cpu_to_le16(0x0013); memcpy(cp.min_interval, interval_20ms, sizeof(cp.min_interval)); memcpy(cp.max_interval, interval_20ms, sizeof(cp.max_interval)); cp.sid = sid; send_command(bthost, BT_HCI_CMD_LE_SET_EXT_ADV_PARAMS, &cp, sizeof(cp)); } void bthost_set_ext_adv_enable(struct bthost *bthost, uint8_t enable) { struct bt_hci_cmd_le_set_ext_adv_enable *cp_enable; struct bt_hci_cmd_ext_adv_set *cp_set; uint8_t cp[6]; memset(cp, 0, 6); cp_enable = (struct bt_hci_cmd_le_set_ext_adv_enable *)cp; cp_set = (struct bt_hci_cmd_ext_adv_set *)(cp + sizeof(*cp_enable)); cp_enable->enable = enable; cp_enable->num_of_sets = 1; cp_set->handle = 1; send_command(bthost, BT_HCI_CMD_LE_SET_EXT_ADV_ENABLE, cp, 6); } void bthost_set_pa_params(struct bthost *bthost) { struct bt_hci_cmd_le_set_pa_params cp; memset(&cp, 0, sizeof(cp)); cp.handle = 0x01; send_command(bthost, BT_HCI_CMD_LE_SET_PA_PARAMS, &cp, sizeof(cp)); } static void set_pa_data(struct bthost *bthost, const uint8_t *data, uint8_t len, uint8_t offset) { struct bt_hci_cmd_le_set_pa_data *cp; uint8_t buf[sizeof(*cp) + BT_PA_MAX_DATA_LEN]; size_t data_len; cp = (void *)buf; memset(cp, 0, sizeof(*cp)); memset(cp->data, 0, BT_PA_MAX_DATA_LEN); cp->handle = 1; if (len - offset > BT_PA_MAX_DATA_LEN) { data_len = BT_PA_MAX_DATA_LEN; if (!offset) cp->operation = 0x01; else cp->operation = 0x00; } else { data_len = len - offset; if (!offset) cp->operation = 0x03; else cp->operation = 0x02; } memcpy(cp->data, data + offset, data_len); cp->data_len = data_len; send_command(bthost, BT_HCI_CMD_LE_SET_PA_DATA, buf, sizeof(*cp) + cp->data_len); if (cp->operation == 0x01 || cp->operation == 0x00) { offset += cp->data_len; set_pa_data(bthost, data, len, offset); } } void bthost_set_pa_data(struct bthost *bthost, const uint8_t *data, uint8_t len) { set_pa_data(bthost, data, len, 0); } void bthost_set_base(struct bthost *bthost, const uint8_t *data, uint8_t len) { struct bt_ad *ad; bt_uuid_t uuid; uint8_t *pa_data; size_t pa_len; bt_uuid16_create(&uuid, BAA_SERVICE); ad = bt_ad_new(); bt_ad_set_max_len(ad, BT_PA_MAX_DATA_LEN); bt_ad_add_service_data(ad, &uuid, (void *)data, len); pa_data = bt_ad_generate(ad, &pa_len); bthost_set_pa_data(bthost, pa_data, pa_len); bt_ad_unref(ad); } void bthost_set_pa_enable(struct bthost *bthost, uint8_t enable) { struct bt_hci_cmd_le_set_pa_enable cp; memset(&cp, 0, sizeof(cp)); cp.enable = enable; cp.handle = 0x01; send_command(bthost, BT_HCI_CMD_LE_SET_PA_ENABLE, &cp, sizeof(cp)); } void bthost_set_past_mode(struct bthost *bthost, uint16_t handle, uint8_t mode) { struct bt_hci_cmd_le_past_params cp; memset(&cp, 0, sizeof(cp)); cp.handle = cpu_to_le16(handle); cp.mode = mode; send_command(bthost, BT_HCI_CMD_LE_PAST_PARAMS, &cp, sizeof(cp)); } void bthost_past_set_info(struct bthost *bthost, uint16_t handle) { struct bt_hci_cmd_le_past_set_info cp; if (!bthost) return; memset(&cp, 0, sizeof(cp)); cp.handle = cpu_to_le16(handle); cp.adv_handle = 0x01; send_command(bthost, BT_HCI_CMD_LE_PAST_SET_INFO, &cp, sizeof(cp)); } void bthost_create_big(struct bthost *bthost, uint8_t num_bis, uint8_t enc, const uint8_t *bcode) { struct bt_hci_cmd_le_create_big cp; memset(&cp, 0, sizeof(cp)); cp.handle = 0x01; cp.adv_handle = 0x01; cp.num_bis = num_bis; put_le24(10000, cp.bis.sdu_interval); cp.bis.sdu = 40; cp.bis.latency = cpu_to_le16(10); cp.bis.rtn = 0x02; cp.bis.phys = BIT(2); cp.bis.encryption = enc; memcpy(cp.bis.bcode, bcode, sizeof(cp.bis.bcode)); send_command(bthost, BT_HCI_CMD_LE_CREATE_BIG, &cp, sizeof(cp)); } void bthost_terminate_big(struct bthost *bthost, uint8_t reason) { struct bt_hci_cmd_le_term_big cp; memset(&cp, 0, sizeof(cp)); cp.handle = 0x01; cp.reason = reason; send_command(bthost, BT_HCI_CMD_LE_TERM_BIG, &cp, sizeof(cp)); } bool bthost_search_ext_adv_addr(struct bthost *bthost, const uint8_t *addr) { const struct queue_entry *entry; if (queue_isempty(bthost->le_ext_adv)) return false; for (entry = queue_get_entries(bthost->le_ext_adv); entry; entry = entry->next) { struct le_ext_adv *le_ext_adv = entry->data; if (!memcmp(le_ext_adv->addr, addr, 6)) return true; } return false; } void bthost_set_cig_params(struct bthost *bthost, uint8_t cig_id, uint8_t cis_id, const struct bt_iso_qos *qos) { struct bt_hci_cmd_le_set_cig_params *cp; cp = malloc(sizeof(*cp) + sizeof(*cp->cis)); memset(cp, 0, sizeof(*cp) + sizeof(*cp->cis)); cp->cig_id = cig_id; put_le24(qos->ucast.in.interval ? qos->ucast.in.interval : qos->ucast.out.interval, cp->c_interval); put_le24(qos->ucast.out.interval ? qos->ucast.out.interval : qos->ucast.in.interval, cp->p_interval); cp->c_latency = cpu_to_le16(qos->ucast.in.latency ? qos->ucast.in.latency : qos->ucast.out.latency); cp->p_latency = cpu_to_le16(qos->ucast.out.latency ? qos->ucast.out.latency : qos->ucast.in.latency); cp->num_cis = 0x01; cp->cis[0].cis_id = cis_id; cp->cis[0].c_sdu = qos->ucast.in.sdu; cp->cis[0].p_sdu = qos->ucast.out.sdu; cp->cis[0].c_phys = qos->ucast.in.phys ? qos->ucast.in.phys : qos->ucast.out.phys; cp->cis[0].p_phys = qos->ucast.out.phys ? qos->ucast.out.phys : qos->ucast.in.phys; cp->cis[0].c_rtn = qos->ucast.in.rtn; cp->cis[0].p_rtn = qos->ucast.out.rtn; send_command(bthost, BT_HCI_CMD_LE_SET_CIG_PARAMS, cp, sizeof(*cp) + sizeof(*cp->cis)); free(cp); } void bthost_create_cis(struct bthost *bthost, uint16_t cis_handle, uint16_t acl_handle) { struct bt_hci_cmd_le_create_cis *cp; cp = malloc(sizeof(*cp) + sizeof(*cp->cis)); memset(cp, 0, sizeof(*cp) + sizeof(*cp->cis)); cp->num_cis = 0x01; cp->cis[0].cis_handle = cpu_to_le16(cis_handle); cp->cis[0].acl_handle = cpu_to_le16(acl_handle); send_command(bthost, BT_HCI_CMD_LE_CREATE_CIS, cp, sizeof(*cp) + sizeof(*cp->cis)); free(cp); } void bthost_write_ssp_mode(struct bthost *bthost, uint8_t mode) { send_command(bthost, BT_HCI_CMD_WRITE_SIMPLE_PAIRING_MODE, &mode, 1); } void bthost_write_le_host_supported(struct bthost *bthost, uint8_t mode) { struct bt_hci_cmd_write_le_host_supported cmd; bthost->le = mode; memset(&cmd, 0, sizeof(cmd)); cmd.supported = mode; send_command(bthost, BT_HCI_CMD_WRITE_LE_HOST_SUPPORTED, &cmd, sizeof(cmd)); } bool bthost_bredr_capable(struct bthost *bthost) { return lmp_bredr_capable(bthost); } void bthost_request_auth(struct bthost *bthost, uint16_t handle) { struct btconn *conn; conn = bthost_find_conn(bthost, handle); if (!conn) return; if (conn->addr_type == BDADDR_BREDR) { struct bt_hci_cmd_auth_requested cp; cp.handle = cpu_to_le16(handle); send_command(bthost, BT_HCI_CMD_AUTH_REQUESTED, &cp, sizeof(cp)); } else { uint8_t auth_req = bthost->auth_req; if (bthost->sc) auth_req |= 0x08; smp_pair(conn->smp_data, bthost->io_capability, auth_req); } } void bthost_le_start_encrypt(struct bthost *bthost, uint16_t handle, const uint8_t ltk[16]) { struct bt_hci_cmd_le_start_encrypt cmd; memset(&cmd, 0, sizeof(cmd)); cmd.handle = htobs(handle); memcpy(cmd.ltk, ltk, 16); send_command(bthost, BT_HCI_CMD_LE_START_ENCRYPT, &cmd, sizeof(cmd)); } uint64_t bthost_conn_get_fixed_chan(struct bthost *bthost, uint16_t handle) { struct btconn *conn; conn = bthost_find_conn(bthost, handle); if (!conn) return 0; return conn->fixed_chan; } void bthost_add_l2cap_server_custom(struct bthost *bthost, uint16_t psm, uint16_t mtu, uint16_t mps, uint16_t credits, bthost_l2cap_connect_cb func, bthost_l2cap_disconnect_cb disconn_func, void *user_data) { struct l2cap_conn_cb_data *data; data = malloc(sizeof(struct l2cap_conn_cb_data)); if (!data) return; data->psm = psm; data->mtu = mtu; data->mps = mps; data->credits = credits; data->user_data = user_data; data->func = func; data->disconn_func = disconn_func; data->next = bthost->new_l2cap_conn_data; bthost->new_l2cap_conn_data = data; } void bthost_add_l2cap_server(struct bthost *bthost, uint16_t psm, bthost_l2cap_connect_cb func, bthost_l2cap_disconnect_cb disconn_func, void *user_data) { bthost_add_l2cap_server_custom(bthost, psm, 0, 0, 0, func, disconn_func, user_data); } void bthost_set_sc_support(struct bthost *bthost, bool enable) { struct bt_hci_cmd_write_secure_conn_support cmd; bthost->sc = enable; if (!lmp_bredr_capable(bthost)) return; cmd.support = enable; send_command(bthost, BT_HCI_CMD_WRITE_SECURE_CONN_SUPPORT, &cmd, sizeof(cmd)); } void bthost_set_pin_code(struct bthost *bthost, const uint8_t *pin, uint8_t pin_len) { memcpy(bthost->pin, pin, pin_len); bthost->pin_len = pin_len; } void bthost_set_io_capability(struct bthost *bthost, uint8_t io_capability) { bthost->io_capability = io_capability; } uint8_t bthost_get_io_capability(struct bthost *bthost) { return bthost->io_capability; } void bthost_set_auth_req(struct bthost *bthost, uint8_t auth_req) { bthost->auth_req = auth_req; } uint8_t bthost_get_auth_req(struct bthost *bthost) { uint8_t auth_req = bthost->auth_req; if (bthost->sc) auth_req |= 0x08; return auth_req; } void bthost_set_reject_user_confirm(struct bthost *bthost, bool reject) { bthost->reject_user_confirm = reject; } bool bthost_get_reject_user_confirm(struct bthost *bthost) { return bthost->reject_user_confirm; } void bthost_add_rfcomm_server(struct bthost *bthost, uint8_t channel, bthost_rfcomm_connect_cb func, void *user_data) { struct rfcomm_conn_cb_data *data; data = malloc(sizeof(struct rfcomm_conn_cb_data)); if (!data) return; data->channel = channel; data->user_data = user_data; data->func = func; data->next = bthost->new_rfcomm_conn_data; bthost->new_rfcomm_conn_data = data; } void bthost_start(struct bthost *bthost) { if (!bthost) return; bthost->ncmd = 1; send_command(bthost, BT_HCI_CMD_RESET, NULL, 0); send_command(bthost, BT_HCI_CMD_READ_LOCAL_FEATURES, NULL, 0); send_command(bthost, BT_HCI_CMD_READ_BD_ADDR, NULL, 0); } bool bthost_connect_rfcomm(struct bthost *bthost, uint16_t handle, uint8_t channel, bthost_rfcomm_connect_cb func, void *user_data) { struct rfcomm_connection_data *data; struct bt_l2cap_pdu_conn_req req; struct btconn *conn; if (bthost->rfcomm_conn_data) return false; conn = bthost_find_conn(bthost, handle); if (!conn) return false; data = malloc(sizeof(struct rfcomm_connection_data)); if (!data) return false; data->channel = channel; data->conn = conn; data->cb = func; data->user_data = user_data; bthost->rfcomm_conn_data = data; req.psm = cpu_to_le16(0x0003); req.scid = cpu_to_le16(conn->next_cid++); return bthost_l2cap_req(bthost, handle, BT_L2CAP_PDU_CONN_REQ, &req, sizeof(req), NULL, NULL); } void bthost_add_rfcomm_chan_hook(struct bthost *bthost, uint16_t handle, uint8_t channel, bthost_rfcomm_chan_hook_func_t func, void *user_data) { struct rfcomm_chan_hook *hook; struct btconn *conn; conn = bthost_find_conn(bthost, handle); if (!conn) return; hook = malloc(sizeof(*hook)); if (!hook) return; memset(hook, 0, sizeof(*hook)); hook->channel = channel; hook->func = func; hook->user_data = user_data; hook->next = conn->rfcomm_chan_hooks; conn->rfcomm_chan_hooks = hook; } void bthost_send_rfcomm_data(struct bthost *bthost, uint16_t handle, uint8_t channel, const void *data, uint16_t len) { struct btconn *conn; struct rcconn *rcconn; struct rfcomm_hdr *hdr; uint8_t *uih_frame; uint16_t uih_len; conn = bthost_find_conn(bthost, handle); if (!conn) return; rcconn = btconn_find_rfcomm_conn_by_channel(conn, channel); if (!rcconn) return; if (len > 127) uih_len = len + sizeof(struct rfcomm_cmd) + sizeof(uint8_t); else uih_len = len + sizeof(struct rfcomm_cmd); uih_frame = malloc(uih_len); if (!uih_frame) return; hdr = (struct rfcomm_hdr *) uih_frame; hdr->address = RFCOMM_ADDR(1, channel * 2); hdr->control = RFCOMM_CTRL(RFCOMM_UIH, 0); if (len > 127) { hdr->length = RFCOMM_LEN16(cpu_to_le16(sizeof(*hdr) + len)); memcpy(uih_frame + sizeof(*hdr) + 1, data, len); } else { hdr->length = RFCOMM_LEN8(sizeof(*hdr) + len); memcpy(uih_frame + sizeof(*hdr), data, len); } uih_frame[uih_len - 1] = rfcomm_fcs((void *)hdr); send_acl(bthost, handle, rcconn->scid, 0, uih_frame, uih_len); free(uih_frame); } bluez-5.87/emulator/PaxHeaders/b1ee.c0000644000000000000000000000005015063045254014467 xustar0020 atime=1770587287 20 ctime=1783094102 bluez-5.87/emulator/b1ee.c0000644000000000000000000001511715063045254014155 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2012 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "src/shared/mainloop.h" #define DEFAULT_HOST_PORT "45550" /* 0xb1ee */ #define DEFAULT_SNIFFER_PORT "45551" /* 0xb1ef */ static int sniffer_fd; static int server_fd; static int vhci_fd; static void usage(void) { printf("b1ee - Bluetooth device testing tool over internet\n" "Usage:\n"); printf("\tb1ee [options] \n"); printf("options:\n" "\t-p, --port Specify the server port\n" "\t-s, --sniffer-port Specify the sniffer port\n" "\t-v, --version Show version information\n" "\t-h, --help Show help options\n"); } static const struct option main_options[] = { { "port", required_argument, NULL, 'p' }, { "sniffer-port", required_argument, NULL, 's' }, { "version", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, { } }; static char *set_port(char *str) { char *c; if (str == NULL || str[0] == '\0') return NULL; for (c = str; *c != '\0'; c++) if (isdigit(*c) == 0) return NULL; if (atol(str) > 65535) return NULL; return strdup(str); } static void sniffer_read_callback(int fd, uint32_t events, void *user_data) { static uint8_t buf[4096]; ssize_t len; if (events & (EPOLLERR | EPOLLHUP)) return; again: len = recv(fd, buf, sizeof(buf), MSG_DONTWAIT); if (len < 0) { if (errno == EAGAIN) goto again; return; } printf("Sniffer received: %zi bytes\n", len); } static uint8_t *server_pkt_data; static uint8_t server_pkt_type; static uint16_t server_pkt_expect; static uint16_t server_pkt_len; static uint16_t server_pkt_offset; static void server_read_callback(int fd, uint32_t events, void *user_data) { static uint8_t buf[4096]; uint8_t *ptr = buf; ssize_t len; uint16_t count; if (events & (EPOLLERR | EPOLLHUP)) return; again: len = recv(fd, buf + server_pkt_offset, sizeof(buf) - server_pkt_offset, MSG_DONTWAIT); if (len < 0) { if (errno == EAGAIN) goto again; return; } count = server_pkt_offset + len; while (count > 0) { hci_event_hdr *evt_hdr; if (!server_pkt_data) { server_pkt_type = ptr[0]; switch (server_pkt_type) { case HCI_EVENT_PKT: if (count < HCI_EVENT_HDR_SIZE + 1) { server_pkt_offset += len; return; } evt_hdr = (hci_event_hdr *) (ptr + 1); server_pkt_expect = HCI_EVENT_HDR_SIZE + evt_hdr->plen + 1; server_pkt_data = malloc(server_pkt_expect); server_pkt_len = 0; break; default: fprintf(stderr, "Unknown packet from server\n"); return; } server_pkt_offset = 0; } if (count >= server_pkt_expect) { ssize_t written; memcpy(server_pkt_data + server_pkt_len, ptr, server_pkt_expect); ptr += server_pkt_expect; count -= server_pkt_expect; written = write(vhci_fd, server_pkt_data, server_pkt_len + server_pkt_expect); if (written != server_pkt_len + server_pkt_expect) fprintf(stderr, "Write to /dev/vhci failed\n"); free(server_pkt_data); server_pkt_data = NULL; } else { memcpy(server_pkt_data + server_pkt_len, ptr, count); server_pkt_len += count; server_pkt_expect -= count; count = 0; } } } static void vhci_read_callback(int fd, uint32_t events, void *user_data) { unsigned char buf[4096]; ssize_t len, written; if (events & (EPOLLERR | EPOLLHUP)) return; len = read(fd, buf, sizeof(buf)); if (len < 0) return; written = write(server_fd, buf, len); if (written != len) fprintf(stderr, "Write to server failed\n"); } static void signal_callback(int signum, void *user_data) { switch (signum) { case SIGINT: case SIGTERM: mainloop_quit(); break; } } static int do_connect(const char *node, const char *service) { struct addrinfo hints; struct addrinfo *info, *res; int err, fd = -1; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; err = getaddrinfo(node, service, &hints, &res); if (err) { perror(gai_strerror(err)); exit(1); } for (info = res; info; info = info->ai_next) { char str[INET6_ADDRSTRLEN]; inet_ntop(info->ai_family, info->ai_addr->sa_data, str, sizeof(str)); fd = socket(info->ai_family, info->ai_socktype, info->ai_protocol); if (fd < 0) continue; printf("Trying to connect to %s on port %s\n", str, service); if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) { perror("Failed to connect"); close(fd); continue; } printf("Successfully connected to %s on port %s\n", str, service); break; } freeaddrinfo(res); if (res == NULL) exit(1); return fd; } int main(int argc, char *argv[]) { const char sniff_cmd[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; char *server_port = NULL, *sniffer_port = NULL; int ret = EXIT_FAILURE; ssize_t written; for (;;) { int opt; opt = getopt_long(argc, argv, "s:p:vh", main_options, NULL); if (opt < 0) break; switch (opt) { case 'p': server_port = set_port(optarg); if (server_port == NULL) goto usage; break; case 's': sniffer_port = set_port(optarg); if (sniffer_port == NULL) goto usage; break; case 'v': printf("%s\n", VERSION); ret = EXIT_SUCCESS; goto done; case 'h': ret = EXIT_SUCCESS; goto usage; default: goto usage; } } argc = argc - optind; argv = argv + optind; optind = 0; if (argv[0] == NULL || argv[0][0] == '\0') goto usage; server_fd = do_connect(argv[0], server_port ? : DEFAULT_HOST_PORT); sniffer_fd = do_connect(argv[0], sniffer_port ? : DEFAULT_SNIFFER_PORT); written = write(sniffer_fd, sniff_cmd, sizeof(sniff_cmd)); if (written < 0) perror("Failed to enable sniffer"); vhci_fd = open("/dev/vhci", O_RDWR | O_NONBLOCK); if (vhci_fd < 0) { perror("Failed to /dev/vhci"); close(server_fd); exit(1); } mainloop_init(); mainloop_add_fd(sniffer_fd, EPOLLIN, sniffer_read_callback, NULL, NULL); mainloop_add_fd(server_fd, EPOLLIN, server_read_callback, NULL, NULL); mainloop_add_fd(vhci_fd, EPOLLIN, vhci_read_callback, NULL, NULL); ret = mainloop_run_with_signal(signal_callback, NULL); goto done; usage: usage(); done: free(server_port); free(sniffer_port); return ret; } bluez-5.87/emulator/PaxHeaders/serial.h0000644000000000000000000000005014015011623015124 xustar0020 atime=1770587287 20 ctime=1783094102 bluez-5.87/emulator/serial.h0000644000000000000000000000071414015011623014607 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #include enum serial_type { SERIAL_TYPE_BREDRLE, SERIAL_TYPE_BREDR, SERIAL_TYPE_LE, SERIAL_TYPE_AMP, }; struct serial; struct serial *serial_open(enum serial_type type); void serial_close(struct serial *serial); bluez-5.87/emulator/PaxHeaders/vhci.h0000644000000000000000000000005014471706457014624 xustar0020 atime=1770587287 20 ctime=1783094102 bluez-5.87/emulator/vhci.h0000644000000000000000000000224514471706457014310 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #include struct vhci; typedef void (*vhci_debug_func_t)(const char *str, void *user_data); typedef void (*vhci_destroy_func_t)(void *user_data); bool vhci_set_debug(struct vhci *vhci, vhci_debug_func_t callback, void *user_data, vhci_destroy_func_t destroy); struct vhci *vhci_open(uint8_t type); void vhci_close(struct vhci *vhci); struct btdev *vhci_get_btdev(struct vhci *vhci); int vhci_set_force_suspend(struct vhci *vhci, bool enable); int vhci_set_force_wakeup(struct vhci *vhci, bool enable); int vhci_set_msft_opcode(struct vhci *vhci, uint16_t opcode); int vhci_set_aosp_capable(struct vhci *vhci, bool enable); int vhci_set_emu_opcode(struct vhci *vhci, uint16_t opcode); int vhci_set_force_static_address(struct vhci *vhci, bool enable); int vhci_force_devcd(struct vhci *vhci, const void *data, size_t len); int vhci_read_devcd(struct vhci *vhci, void *buf, size_t size); bool vhci_pause_input(struct vhci *vhci, bool paused); bluez-5.87/emulator/PaxHeaders/phy.h0000644000000000000000000000005014015011623014445 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/emulator/phy.h0000644000000000000000000000263314015011623014132 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2012 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #include #include struct bt_phy; struct bt_phy *bt_phy_new(void); struct bt_phy *bt_phy_ref(struct bt_phy *phy); void bt_phy_unref(struct bt_phy *phy); bool bt_phy_send(struct bt_phy *phy, uint16_t type, const void *data, size_t size); bool bt_phy_send_vector(struct bt_phy *phy, uint16_t type, const void *data1, size_t size1, const void *data2, size_t size2, const void *data3, size_t size3); typedef void (*bt_phy_callback_func_t)(uint16_t type, const void *data, size_t size, void *user_data); bool bt_phy_register(struct bt_phy *phy, bt_phy_callback_func_t callback, void *user_data); #define BT_PHY_PKT_NULL 0x0000 #define BT_PHY_PKT_ADV 0x0001 struct bt_phy_pkt_adv { uint8_t chan_idx; uint8_t pdu_type; uint8_t tx_addr_type; uint8_t tx_addr[6]; uint8_t rx_addr_type; uint8_t rx_addr[6]; uint8_t adv_data_len; uint8_t scan_rsp_len; } __attribute__ ((packed)); #define BT_PHY_PKT_CONN 0x0002 struct bt_phy_pkt_conn { uint8_t chan_idx; uint8_t link_type; uint8_t tx_addr_type; uint8_t tx_addr[6]; uint8_t rx_addr_type; uint8_t rx_addr[6]; uint8_t features[8]; uint8_t id; } __attribute__ ((packed)); bluez-5.87/emulator/PaxHeaders/btdev.c0000644000000000000000000000005015221752714014761 xustar0020 atime=1783092684 20 ctime=1783094102 bluez-5.87/emulator/btdev.c0000644000000000000000000071237715221752714014463 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2012 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright 2023-2024 NXP * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "src/shared/util.h" #include "src/shared/timeout.h" #include "src/shared/crypto.h" #include "src/shared/ecc.h" #include "src/shared/queue.h" #include "monitor/bt.h" #include "monitor/msft.h" #include "monitor/emulator.h" #include "btdev.h" #define AL_SIZE 16 #define RL_SIZE 16 #define CIS_SIZE 3 #define BIS_SIZE 3 #define CIG_SIZE 3 #define MAX_PA_DATA_LEN 252 #define MAX_EXT_ADV_LEN 252 #define has_bredr(btdev) (!((btdev)->features[4] & 0x20)) #define has_le(btdev) (!!((btdev)->features[4] & 0x40)) #define struct_group(NAME, MEMBERS...) \ union { \ struct { MEMBERS }; \ struct { MEMBERS } NAME; \ } #define ACL_HANDLE BIT(0) #define SCO_HANDLE BIT(8) #define CIS_HANDLE SCO_HANDLE #define BIS_HANDLE BIT(9) #define SYNC_HANDLE 1 #define INV_HANDLE 0xffff #define SCO_AIRMODE_MASK 0x0003 #define SCO_AIRMODE_CVSD 0x0000 #define SCO_AIRMODE_TRANSP 0x0003 struct hook { btdev_hook_func handler; void *user_data; enum btdev_hook_type type; uint16_t opcode; }; #define MAX_HOOK_ENTRIES 16 #define MAX_EXT_ADV_SETS 3 #define MAX_PENDING_CONN 16 struct btdev_conn { uint16_t handle; uint8_t type; uint8_t past_mode; struct btdev *dev; struct btdev_conn *link; struct queue *links; void *data; }; struct btdev_al { uint8_t type; bdaddr_t addr; }; struct btdev_rl { uint8_t type; bdaddr_t addr; uint8_t mode; uint8_t peer_irk[16]; uint8_t local_irk[16]; }; struct le_ext_adv { struct btdev *dev; uint8_t handle; uint8_t enable; uint32_t interval; uint8_t type; /* evt_properties */ uint8_t own_addr_type; /* own_addr_type */ uint8_t direct_addr_type; /* peer_addr_type */ uint8_t direct_addr[6]; /* peer_addr */ uint8_t filter_policy; /* filter_policy */ uint8_t random_addr[6]; bool rpa; uint8_t adv_data[MAX_EXT_ADV_LEN]; uint8_t adv_data_len; uint8_t scan_data[MAX_EXT_ADV_LEN]; uint8_t scan_data_len; unsigned int broadcast_id; unsigned int timeout_id; uint8_t sid; }; struct le_per_adv { struct btdev *dev; struct btdev *remote; uint8_t addr_type; uint8_t addr[6]; uint8_t sid; uint16_t sync_handle; struct queue *syncs; }; struct le_big { struct btdev *dev; uint8_t handle; struct queue *bis; }; struct le_cig { struct bt_hci_cmd_le_set_cig_params params; struct bt_hci_cis_params cis[CIS_SIZE]; bool activated; } __attribute__ ((packed)); struct pending_conn { struct btdev *dev; uint8_t link_type; }; struct btdev { enum btdev_type type; uint16_t id; struct queue *conns; btdev_command_func command_handler; void *command_data; btdev_send_func send_handler; void *send_data; unsigned int inquiry_id; unsigned int inquiry_timeout_id; struct hook *hook_list[MAX_HOOK_ENTRIES]; struct bt_crypto *crypto; uint16_t manufacturer; uint8_t version; uint16_t revision; uint8_t commands[64]; uint8_t max_page; uint8_t features[8]; uint8_t feat_page_2[8]; uint16_t acl_mtu; uint16_t acl_max_pkt; uint16_t sco_mtu; uint16_t sco_max_pkt; uint16_t iso_mtu; uint16_t iso_max_pkt; uint8_t country_code; uint8_t bdaddr[6]; uint8_t random_addr[6]; uint8_t le_features[248]; uint8_t le_states[8]; const struct btdev_cmd *cmds; uint16_t msft_opcode; const struct btdev_cmd *msft_cmds; uint16_t emu_opcode; const struct btdev_cmd *emu_cmds; bool aosp_capable; /* State zeroed on reset */ struct_group(reset_group, bool auth_init; uint8_t link_key[16]; uint16_t pin[16]; uint8_t pin_len; uint8_t io_cap; uint8_t auth_req; bool ssp_auth_complete; uint8_t ssp_status; uint16_t default_link_policy; uint8_t event_mask[8]; uint8_t event_mask_page2[8]; uint8_t event_filter; uint8_t name[248]; uint8_t dev_class[3]; uint16_t voice_setting; uint16_t conn_accept_timeout; uint16_t page_timeout; uint8_t scan_enable; uint16_t page_scan_interval; uint16_t page_scan_window; uint16_t page_scan_type; uint8_t auth_enable; uint16_t inquiry_scan_interval; uint16_t inquiry_scan_window; uint8_t inquiry_mode; uint8_t afh_assessment_mode; uint8_t ext_inquiry_fec; uint8_t ext_inquiry_rsp[240]; uint8_t simple_pairing_mode; uint8_t ssp_debug_mode; uint8_t secure_conn_support; uint8_t host_flow_control; uint8_t sco_flowctl; uint8_t le_supported; uint8_t le_simultaneous; uint8_t le_event_mask[8]; uint8_t le_adv_data[31]; uint8_t le_adv_data_len; uint8_t le_adv_type; uint8_t le_adv_own_addr; uint8_t le_adv_direct_addr_type; uint8_t le_adv_direct_addr[6]; uint8_t le_adv_filter_policy; uint8_t le_scan_data[31]; uint8_t le_scan_data_len; uint8_t le_scan_enable; uint8_t le_scan_type; uint8_t le_scan_own_addr_type; uint8_t le_scan_filter_policy; uint8_t le_filter_dup; uint8_t le_adv_enable; uint8_t le_pa_enable; uint16_t le_pa_properties; uint16_t le_pa_min_interval; uint16_t le_pa_max_interval; uint8_t le_pa_data_len; uint8_t le_pa_data[MAX_PA_DATA_LEN]; uint8_t le_ltk[16]; struct le_cig le_cig[CIG_SIZE]; uint8_t le_iso_path[2]; uint8_t le_rl_enable; struct pending_conn pending_conn[MAX_PENDING_CONN]; uint16_t sync_train_interval; uint32_t sync_train_timeout; uint8_t sync_train_service_data; uint16_t le_ext_adv_type; ); /* reset_group */ /* Real time length of AL array */ uint8_t le_al_len; /* Real time length of RL array */ uint8_t le_rl_len; struct btdev_al le_al[AL_SIZE]; struct btdev_rl le_rl[RL_SIZE]; uint16_t le_rl_timeout; struct queue *le_ext_adv; struct queue *le_per_adv; struct queue *le_big; btdev_debug_func_t debug_callback; btdev_destroy_func_t debug_destroy; void *debug_data; }; struct inquiry_data { struct btdev *btdev; int num_resp; int sent_count; int iter; }; #define DEFAULT_INQUIRY_INTERVAL 100 /* 100 milliseconds */ #define MAX_BTDEV_ENTRIES 16 static const uint8_t LINK_KEY_NONE[16] = { 0 }; static const uint8_t LINK_KEY_DUMMY[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5 }; static struct btdev *btdev_list[MAX_BTDEV_ENTRIES] = { }; static int get_hook_index(struct btdev *btdev, enum btdev_hook_type type, uint16_t opcode) { int i; for (i = 0; i < MAX_HOOK_ENTRIES; i++) { if (btdev->hook_list[i] == NULL) continue; if (btdev->hook_list[i]->type == type && btdev->hook_list[i]->opcode == opcode) return i; } return -1; } static bool run_hooks(struct btdev *btdev, enum btdev_hook_type type, uint16_t opcode, const void *data, uint16_t len) { int index = get_hook_index(btdev, type, opcode); if (index < 0) return true; return btdev->hook_list[index]->handler(data, len, btdev->hook_list[index]->user_data); } static inline int add_btdev(struct btdev *btdev) { int i, index = -1; for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { if (btdev_list[i] == NULL) { index = i; btdev_list[index] = btdev; break; } } return index; } static inline int del_btdev(struct btdev *btdev) { int i, index = -1; for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { if (btdev_list[i] == btdev) { index = i; btdev_list[index] = NULL; break; } } return index; } static inline bool valid_btdev(struct btdev *btdev) { int i; for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { if (btdev_list[i] == btdev) return true; } return false; } static inline struct btdev *find_btdev_by_bdaddr(const uint8_t *bdaddr) { int i; for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { if (btdev_list[i] && !memcmp(btdev_list[i]->bdaddr, bdaddr, 6)) return btdev_list[i]; } return NULL; } static bool match_adv_addr(const void *data, const void *match_data) { const struct le_ext_adv *adv = data; const uint8_t *bdaddr = match_data; return !memcmp(adv->random_addr, bdaddr, 6); } static inline struct btdev *find_btdev_by_bdaddr_type(const uint8_t *bdaddr, uint8_t bdaddr_type) { int i; for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { struct btdev *dev = btdev_list[i]; int cmp; struct le_ext_adv *adv; if (!dev) continue; if (bdaddr_type == 0x01 || bdaddr_type == 0x03) cmp = memcmp(dev->random_addr, bdaddr, 6); else cmp = memcmp(dev->bdaddr, bdaddr, 6); if (!cmp) return dev; /* Check for instance own Random addresses */ if (bdaddr_type == 0x01 || bdaddr_type == 0x03) { adv = queue_find(dev->le_ext_adv, match_adv_addr, bdaddr); if (adv) return dev; } } return NULL; } static void get_bdaddr(uint16_t id, uint8_t index, uint8_t *bdaddr) { bdaddr[0] = id & 0xff; bdaddr[1] = id >> 8; bdaddr[2] = index; bdaddr[3] = 0x01; bdaddr[4] = 0xaa; bdaddr[5] = 0x00; } struct btdev_cmd { uint16_t opcode; int (*func)(struct btdev *dev, const void *data, uint8_t len); int (*complete)(struct btdev *dev, const void *data, uint8_t len); }; #define CMD(_opcode, _func, _complete) \ { \ .opcode = _opcode, \ .func = _func, \ .complete = _complete, \ } static void send_packet(struct btdev *btdev, const struct iovec *iov, int iovlen) { int i; if (!btdev->send_handler) return; for (i = 0; i < iovlen; i++) { if (!i) util_hexdump('<', iov[i].iov_base, iov[i].iov_len, btdev->debug_callback, btdev->debug_data); else util_hexdump(' ', iov[i].iov_base, iov[i].iov_len, btdev->debug_callback, btdev->debug_data); } btdev->send_handler(iov, iovlen, btdev->send_data); } static void send_cmd(struct btdev *btdev, uint8_t evt, uint16_t opcode, const struct iovec *iov, int iovlen) { struct bt_hci_evt_hdr hdr; struct iovec iov2[2 + iovlen]; uint8_t pkt = BT_H4_EVT_PKT; int i; util_debug(btdev->debug_callback, btdev->debug_data, "event 0x%02x opcode 0x%04x", evt, opcode); iov2[0].iov_base = &pkt; iov2[0].iov_len = sizeof(pkt); hdr.evt = evt; hdr.plen = 0; iov2[1].iov_base = &hdr; iov2[1].iov_len = sizeof(hdr); for (i = 0; i < iovlen; i++) { hdr.plen += iov[i].iov_len; iov2[2 + i].iov_base = iov[i].iov_base; iov2[2 + i].iov_len = iov[i].iov_len; } if (run_hooks(btdev, BTDEV_HOOK_POST_CMD, opcode, iov[i -1].iov_base, iov[i -1].iov_len)) send_packet(btdev, iov2, 2 + iovlen); } static void cmd_complete(struct btdev *btdev, uint16_t opcode, const void *data, uint8_t len) { struct bt_hci_evt_cmd_complete cc; struct iovec iov[2]; cc.ncmd = 0x01; cc.opcode = cpu_to_le16(opcode); iov[0].iov_base = &cc; iov[0].iov_len = sizeof(cc); iov[1].iov_base = (void *) data; iov[1].iov_len = len; send_cmd(btdev, BT_HCI_EVT_CMD_COMPLETE, opcode, iov, 2); } static int cmd_set_event_mask(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_set_event_mask *cmd = data; uint8_t status; memcpy(dev->event_mask, cmd->mask, 8); status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_SET_EVENT_MASK, &status, sizeof(status)); return 0; } static void al_reset(struct btdev_al *al) { al->type = 0xff; bacpy(&al->addr, BDADDR_ANY); } static void al_clear(struct btdev *dev) { int i; for (i = 0; i < AL_SIZE; i++) al_reset(&dev->le_al[i]); } static void rl_reset(struct btdev_rl *rl) { rl->type = 0xff; bacpy(&rl->addr, BDADDR_ANY); memset(rl->peer_irk, 0, 16); memset(rl->local_irk, 0, 16); } static void rl_clear(struct btdev *dev) { int i; for (i = 0; i < RL_SIZE; i++) rl_reset(&dev->le_rl[i]); } /* Set the real time length of AL array */ void btdev_set_al_len(struct btdev *btdev, uint8_t len) { btdev->le_al_len = len; } /* Set the real time length of RL array */ void btdev_set_rl_len(struct btdev *btdev, uint8_t len) { btdev->le_rl_len = len; } static void conn_unlink(struct btdev_conn *conn1, struct btdev_conn *conn2) { conn1->link = NULL; conn2->link = NULL; } static void conn_remove(void *data) { struct btdev_conn *conn = data; if (conn->link) { struct btdev_conn *link = conn->link; if (link->link == conn) { conn_unlink(conn, conn->link); conn_remove(link); } else queue_remove(link->links, conn); } queue_destroy(conn->links, conn_remove); queue_remove(conn->dev->conns, conn); free(conn->data); free(conn); } static void le_ext_adv_free(void *data) { struct le_ext_adv *ext_adv = data; /* Remove to queue */ queue_remove(ext_adv->dev->le_ext_adv, ext_adv); if (ext_adv->broadcast_id) timeout_remove(ext_adv->broadcast_id); if (ext_adv->timeout_id) timeout_remove(ext_adv->timeout_id); free(ext_adv); } static void le_big_free(void *data) { struct le_big *big = data; queue_destroy(big->bis, NULL); free(big); } static void btdev_init_param(struct btdev *btdev) { unsigned int i; btdev->page_scan_interval = 0x0800; btdev->page_scan_window = 0x0012; btdev->page_scan_type = 0x00; btdev->sync_train_interval = 0x0080; btdev->sync_train_timeout = 0x0002ee00; btdev->sync_train_service_data = 0x00; btdev->acl_mtu = 192; btdev->acl_max_pkt = 1; btdev->sco_mtu = 72; btdev->sco_max_pkt = 1; btdev->iso_mtu = 251; btdev->iso_max_pkt = 1; for (i = 0; i < ARRAY_SIZE(btdev->le_cig); ++i) btdev->le_cig[i].params.cig_id = 0xff; btdev->country_code = 0x00; } static void btdev_reset(struct btdev *btdev) { /* FIXME: include here clearing of all states that should be * cleared upon HCI_Reset */ if (btdev->inquiry_id > 0) { timeout_remove(btdev->inquiry_id); btdev->inquiry_id = 0; } queue_remove_all(btdev->conns, NULL, NULL, conn_remove); queue_remove_all(btdev->le_ext_adv, NULL, NULL, le_ext_adv_free); queue_remove_all(btdev->le_per_adv, NULL, NULL, free); queue_remove_all(btdev->le_big, NULL, NULL, le_big_free); memset(&btdev->reset_group, 0, sizeof(btdev->reset_group)); btdev_init_param(btdev); al_clear(btdev); rl_clear(btdev); btdev->le_al_len = AL_SIZE; btdev->le_rl_len = RL_SIZE; btdev->le_rl_timeout = 0x0384; } static int cmd_reset(struct btdev *dev, const void *data, uint8_t len) { uint8_t status; btdev_reset(dev); status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_RESET, &status, sizeof(status)); return 0; } static int cmd_read_local_version(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_local_version rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.hci_ver = dev->version; rsp.hci_rev = cpu_to_le16(dev->revision); rsp.lmp_ver = dev->version; rsp.manufacturer = cpu_to_le16(dev->manufacturer); rsp.lmp_subver = cpu_to_le16(dev->revision); cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_VERSION, &rsp, sizeof(rsp)); return 0; } static int cmd_read_local_commands(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_local_commands rsp; rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.commands, dev->commands, 64); cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_COMMANDS, &rsp, sizeof(rsp)); return 0; } static int cmd_read_local_features(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_local_features rsp; rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.features, dev->features, 8); cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_FEATURES, &rsp, sizeof(rsp)); return 0; } static int cmd_read_buffer_size(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_buffer_size rsp; rsp.status = BT_HCI_ERR_SUCCESS; rsp.acl_mtu = cpu_to_le16(dev->acl_mtu); rsp.sco_mtu = cpu_to_le16(dev->sco_mtu); rsp.acl_max_pkt = cpu_to_le16(dev->acl_max_pkt); rsp.sco_max_pkt = cpu_to_le16(dev->sco_max_pkt); cmd_complete(dev, BT_HCI_CMD_READ_BUFFER_SIZE, &rsp, sizeof(rsp)); return 0; } #define CMD_COMMON_ALL \ CMD(BT_HCI_CMD_SET_EVENT_MASK, cmd_set_event_mask, NULL), \ CMD(BT_HCI_CMD_RESET, cmd_reset, NULL), \ CMD(BT_HCI_CMD_READ_LOCAL_VERSION, cmd_read_local_version, NULL), \ CMD(BT_HCI_CMD_READ_LOCAL_COMMANDS, cmd_read_local_commands, NULL), \ CMD(BT_HCI_CMD_READ_LOCAL_FEATURES, cmd_read_local_features, NULL), \ CMD(BT_HCI_CMD_READ_BUFFER_SIZE, cmd_read_buffer_size, NULL) static void set_common_commands_all(struct btdev *btdev) { btdev->commands[5] |= 0x40; /* Set Event Mask */ btdev->commands[5] |= 0x80; /* Reset */ btdev->commands[14] |= 0x08; /* Read Local Version */ btdev->commands[14] |= 0x10; /* Read Local Supported Commands */ btdev->commands[14] |= 0x20; /* Read Local Supported Features */ btdev->commands[14] |= 0x80; /* Read Buffer Size */ } static void cmd_status(struct btdev *btdev, uint8_t status, uint16_t opcode) { struct bt_hci_evt_cmd_status cs; struct iovec iov; cs.status = status; cs.ncmd = 0x01; cs.opcode = cpu_to_le16(opcode); iov.iov_base = &cs; iov.iov_len = sizeof(cs); send_cmd(btdev, BT_HCI_EVT_CMD_STATUS, opcode, &iov, 1); } static int cmd_disconnect(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_DISCONNECT); return 0; } static void send_event(struct btdev *btdev, uint8_t event, const void *data, uint8_t len) { struct bt_hci_evt_hdr hdr; struct iovec iov[3]; uint8_t pkt = BT_H4_EVT_PKT; util_debug(btdev->debug_callback, btdev->debug_data, "event 0x%02x", event); iov[0].iov_base = &pkt; iov[0].iov_len = sizeof(pkt); hdr.evt = event; hdr.plen = len; iov[1].iov_base = &hdr; iov[1].iov_len = sizeof(hdr); if (len > 0) { iov[2].iov_base = (void *) data; iov[2].iov_len = len; } if (run_hooks(btdev, BTDEV_HOOK_POST_EVT, event, data, len)) send_packet(btdev, iov, len > 0 ? 3 : 2); } static bool match_handle(const void *data, const void *match_data) { const struct btdev_conn *conn = data; uint16_t handle = PTR_TO_UINT(match_data); return conn->handle == handle; } static void disconnect_complete(struct btdev *dev, uint16_t handle, uint8_t status, uint8_t reason) { struct bt_hci_evt_disconnect_complete rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = status; rsp.handle = cpu_to_le16(handle); rsp.reason = reason; send_event(dev, BT_HCI_EVT_DISCONNECT_COMPLETE, &rsp, sizeof(rsp)); } static int cmd_disconnect_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_disconnect *cmd = data; struct bt_hci_evt_disconnect_complete rsp; struct btdev_conn *conn; memset(&rsp, 0, sizeof(rsp)); conn = queue_remove_if(dev->conns, match_handle, UINT_TO_PTR(cpu_to_le16(cmd->handle))); if (!conn) { disconnect_complete(dev, 0x0000, BT_HCI_ERR_UNKNOWN_CONN_ID, 0x00); return 0; } /* Local host has different reason (Core v5.3 Vol 4 Part E Sec 7.1.6) */ disconnect_complete(dev, conn->handle, BT_HCI_ERR_SUCCESS, BT_HCI_ERR_LOCAL_HOST_TERM); if (conn->link) disconnect_complete(conn->link->dev, conn->link->handle, BT_HCI_ERR_SUCCESS, cmd->reason); conn_remove(conn); return 0; } static int cmd_remote_version(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_READ_REMOTE_VERSION); return 0; } static int cmd_remote_version_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_read_remote_version *cmd = data; struct bt_hci_evt_remote_version_complete ev; struct btdev_conn *conn; memset(&ev, 0, sizeof(ev)); conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(cpu_to_le16(cmd->handle))); if (conn) { ev.status = BT_HCI_ERR_SUCCESS; ev.handle = cpu_to_le16(cmd->handle); ev.lmp_ver = conn->link->dev->version; ev.manufacturer = cpu_to_le16(conn->link->dev->manufacturer); ev.lmp_subver = cpu_to_le16(conn->link->dev->revision); } else { ev.status = BT_HCI_ERR_UNKNOWN_CONN_ID; ev.handle = cpu_to_le16(cmd->handle); ev.lmp_ver = 0x00; ev.manufacturer = cpu_to_le16(0); ev.lmp_subver = cpu_to_le16(0); } send_event(dev, BT_HCI_EVT_REMOTE_VERSION_COMPLETE, &ev, sizeof(ev)); return 0; } static int cmd_set_host_flowctl(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_set_host_flow_control *cmd = data; uint8_t status; if (cmd->enable > 0x03) { status = BT_HCI_ERR_INVALID_PARAMETERS; } else { dev->host_flow_control = cmd->enable; status = BT_HCI_ERR_SUCCESS; } cmd_complete(dev, BT_HCI_CMD_SET_HOST_FLOW_CONTROL, &status, sizeof(status)); return 0; } static int cmd_host_buffer_size(struct btdev *dev, const void *data, uint8_t len) { uint8_t status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_HOST_BUFFER_SIZE, &status, sizeof(status)); return 0; } static int cmd_host_num_completed_pkts(struct btdev *dev, const void *data, uint8_t len) { /* This command is special in the sense that no event is * normally generated after the command has completed. */ return 0; } static int cmd_read_bdaddr(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_bd_addr rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.bdaddr, dev->bdaddr, 6); cmd_complete(dev, BT_HCI_CMD_READ_BD_ADDR, &rsp, sizeof(rsp)); return 0; } #define CMD_COMMON_BREDR_LE \ CMD(BT_HCI_CMD_DISCONNECT, cmd_disconnect, cmd_disconnect_complete), \ CMD(BT_HCI_CMD_READ_REMOTE_VERSION, cmd_remote_version, \ cmd_remote_version_complete), \ CMD(BT_HCI_CMD_SET_HOST_FLOW_CONTROL, cmd_set_host_flowctl, NULL), \ CMD(BT_HCI_CMD_HOST_BUFFER_SIZE, cmd_host_buffer_size, NULL), \ CMD(BT_HCI_CMD_HOST_NUM_COMPLETED_PACKETS, \ cmd_host_num_completed_pkts, NULL), \ CMD(BT_HCI_CMD_READ_BD_ADDR, cmd_read_bdaddr, NULL) static void set_common_commands_bredrle(struct btdev *btdev) { btdev->commands[0] |= 0x20; /* Disconnect */ btdev->commands[2] |= 0x80; /* Read Remote Version Information */ btdev->commands[10] |= 0x20; /* Set Host Flow Control */ btdev->commands[10] |= 0x40; /* Host Buffer Size */ btdev->commands[15] |= 0x02; /* Read BD ADDR */ } static int cmd_inquiry(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_INQUIRY); return 0; } static bool inquiry_callback(void *user_data) { struct inquiry_data *data = user_data; struct btdev *btdev = data->btdev; struct bt_hci_evt_inquiry_complete ic; int sent = data->sent_count; int i; /*Report devices only once and wait for inquiry timeout*/ if (data->iter == MAX_BTDEV_ENTRIES) return true; for (i = data->iter; i < MAX_BTDEV_ENTRIES; i++) { /*Lets sent 10 inquiry results at once */ if (sent + 10 == data->sent_count) break; if (!btdev_list[i] || btdev_list[i] == btdev) continue; if (!(btdev_list[i]->scan_enable & 0x02)) continue; if (btdev->inquiry_mode == 0x02 && btdev_list[i]->ext_inquiry_rsp[0]) { struct bt_hci_evt_ext_inquiry_result ir; ir.num_resp = 0x01; memcpy(ir.bdaddr, btdev_list[i]->bdaddr, 6); ir.pscan_rep_mode = 0x00; ir.pscan_period_mode = 0x00; memcpy(ir.dev_class, btdev_list[i]->dev_class, 3); ir.clock_offset = 0x0000; ir.rssi = -60; memcpy(ir.data, btdev_list[i]->ext_inquiry_rsp, 240); send_event(btdev, BT_HCI_EVT_EXT_INQUIRY_RESULT, &ir, sizeof(ir)); data->sent_count++; continue; } if (btdev->inquiry_mode > 0x00) { struct bt_hci_evt_inquiry_result_with_rssi ir; ir.num_resp = 0x01; memcpy(ir.bdaddr, btdev_list[i]->bdaddr, 6); ir.pscan_rep_mode = 0x00; ir.pscan_period_mode = 0x00; memcpy(ir.dev_class, btdev_list[i]->dev_class, 3); ir.clock_offset = 0x0000; ir.rssi = -60; send_event(btdev, BT_HCI_EVT_INQUIRY_RESULT_WITH_RSSI, &ir, sizeof(ir)); data->sent_count++; } else { struct bt_hci_evt_inquiry_result ir; ir.num_resp = 0x01; memcpy(ir.bdaddr, btdev_list[i]->bdaddr, 6); ir.pscan_rep_mode = 0x00; ir.pscan_period_mode = 0x00; ir.pscan_mode = 0x00; memcpy(ir.dev_class, btdev_list[i]->dev_class, 3); ir.clock_offset = 0x0000; send_event(btdev, BT_HCI_EVT_INQUIRY_RESULT, &ir, sizeof(ir)); data->sent_count++; } } data->iter = i; /* Check if we sent already required amount of responses*/ if (data->num_resp && data->sent_count == data->num_resp) goto finish; return true; finish: /* Note that destroy will be called */ ic.status = BT_HCI_ERR_SUCCESS; send_event(btdev, BT_HCI_EVT_INQUIRY_COMPLETE, &ic, sizeof(ic)); return false; } static void inquiry_destroy(void *user_data) { struct inquiry_data *data = user_data; struct btdev *btdev = data->btdev; if (!btdev) goto finish; btdev->inquiry_id = 0; if (btdev->inquiry_timeout_id > 0) { timeout_remove(btdev->inquiry_timeout_id); btdev->inquiry_timeout_id = 0; } finish: free(data); } static bool inquiry_timeout(void *user_data) { struct inquiry_data *data = user_data; struct btdev *btdev = data->btdev; struct bt_hci_evt_inquiry_complete ic; timeout_remove(btdev->inquiry_id); btdev->inquiry_timeout_id = 0; /* Inquiry is stopped, send Inquiry complete event. */ ic.status = BT_HCI_ERR_SUCCESS; send_event(btdev, BT_HCI_EVT_INQUIRY_COMPLETE, &ic, sizeof(ic)); return false; } static int cmd_inquiry_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_inquiry *cmd = data; struct inquiry_data *idata; struct bt_hci_evt_inquiry_complete ic; int status = BT_HCI_ERR_HARDWARE_FAILURE; unsigned int inquiry_len_ms; if (dev->inquiry_id > 0) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto failed; } idata = malloc0(sizeof(*idata)); if (!idata) goto failed; idata->btdev = dev; idata->num_resp = cmd->num_resp; /* Add timeout to cancel inquiry */ inquiry_len_ms = 1280 * cmd->length; if (inquiry_len_ms) dev->inquiry_timeout_id = timeout_add(inquiry_len_ms, inquiry_timeout, idata, NULL); dev->inquiry_id = timeout_add(DEFAULT_INQUIRY_INTERVAL, inquiry_callback, idata, inquiry_destroy); /* Return if success */ if (dev->inquiry_id > 0) return 0; failed: ic.status = status; send_event(dev, BT_HCI_EVT_INQUIRY_COMPLETE, &ic, sizeof(ic)); return 0; } static int cmd_inquiry_cancel(struct btdev *dev, const void *data, uint8_t len) { uint8_t status; if (!dev->inquiry_id) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } timeout_remove(dev->inquiry_timeout_id); dev->inquiry_timeout_id = 0; timeout_remove(dev->inquiry_id); dev->inquiry_id = 0; status = BT_HCI_ERR_SUCCESS; done: cmd_complete(dev, BT_HCI_CMD_INQUIRY_CANCEL, &status, sizeof(status)); return 0; } static int cmd_create_conn(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_CREATE_CONN); return 0; } static struct btdev_conn *conn_new(struct btdev *dev, uint16_t handle, uint8_t type) { struct btdev_conn *conn; while (queue_find(dev->conns, match_handle, UINT_TO_PTR(handle))) handle++; conn = new0(struct btdev_conn, 1); conn->handle = handle; conn->type = type; conn->dev = dev; if (!queue_push_tail(dev->conns, conn)) { free(conn); return NULL; } return conn; } static struct btdev_conn *conn_link(struct btdev *dev, struct btdev *remote, uint16_t handle, uint8_t type) { struct btdev_conn *conn1, *conn2; conn1 = conn_new(dev, handle, type); if (!conn1) return NULL; conn2 = conn_new(remote, handle, type); if (!conn2) { free(conn1); return NULL; } conn1->link = conn2; conn2->link = conn1; util_debug(dev->debug_callback, dev->debug_data, "conn1 %p handle 0x%04x", conn1, conn1->handle); util_debug(dev->debug_callback, dev->debug_data, "conn2 %p handle 0x%04x", conn2, conn2->handle); return conn1; } static struct btdev_conn *conn_add(struct btdev *dev, const uint8_t *bdaddr, uint8_t bdaddr_type, uint16_t handle, uint8_t type) { struct btdev *remote; remote = find_btdev_by_bdaddr_type(bdaddr, bdaddr_type); if (!remote) return NULL; return conn_link(dev, remote, handle, type); } static struct btdev_conn *conn_add_acl(struct btdev *dev, const uint8_t *bdaddr, uint8_t bdaddr_type) { return conn_add(dev, bdaddr, bdaddr_type, ACL_HANDLE, HCI_ACLDATA_PKT); } static struct btdev_conn *conn_add_sco(struct btdev_conn *acl) { return conn_link(acl->dev, acl->link->dev, SCO_HANDLE, HCI_SCODATA_PKT); } static struct btdev_conn *conn_add_cis(struct btdev_conn *acl, uint16_t handle) { return conn_link(acl->dev, acl->link->dev, handle, HCI_ISODATA_PKT); } static struct btdev_conn *conn_add_bis(struct btdev *dev, uint16_t handle, const struct bt_hci_bis *bis) { struct btdev_conn *conn; conn = conn_new(dev, handle, HCI_ISODATA_PKT); if (!conn) return conn; conn->data = util_memdup(bis, sizeof(*bis)); return conn; } static struct btdev_conn *find_bis_index(const struct btdev *remote, uint8_t index) { struct btdev_conn *conn; const struct queue_entry *entry; for (entry = queue_get_entries(remote->conns); entry; entry = entry->next) { conn = entry->data; /* Skip if not a broadcast */ if (conn->type != HCI_ISODATA_PKT && conn->handle < BIS_HANDLE) continue; if (!index) return conn; index--; } return NULL; } static struct btdev_conn *conn_link_bis(struct btdev *dev, struct btdev *remote, uint8_t index) { struct btdev_conn *conn; struct btdev_conn *bis; bis = find_bis_index(remote, index); if (!bis) return NULL; conn = conn_add_bis(dev, BIS_HANDLE, bis->data); if (!conn) return NULL; if (!bis->link) { bis->link = conn; } else { if (!bis->links) bis->links = queue_new(); if (!queue_push_tail(bis->links, conn)) { conn_remove(conn); return NULL; } } conn->link = bis; util_debug(dev->debug_callback, dev->debug_data, "bis %p handle 0x%04x", bis, bis->handle); util_debug(dev->debug_callback, dev->debug_data, "conn %p handle 0x%04x", conn, conn->handle); return conn; } static void pending_conn_add(struct btdev *btdev, struct btdev *remote, uint8_t link_type) { unsigned int i; for (i = 0; i < ARRAY_SIZE(btdev->pending_conn); ++i) { if (!btdev->pending_conn[i].dev) { btdev->pending_conn[i].dev = remote; btdev->pending_conn[i].link_type = link_type; return; } } } static int pending_conn_del(struct btdev *btdev, struct btdev *remote) { unsigned int i; for (i = 0; i < ARRAY_SIZE(btdev->pending_conn); ++i) { if (btdev->pending_conn[i].dev == remote) { btdev->pending_conn[i].dev = NULL; return btdev->pending_conn[i].link_type; } } return -ENOENT; } static void sync_conn_init_complete(struct btdev_conn *conn, uint8_t link_type, struct bt_hci_evt_sync_conn_complete *cc) { struct bt_hci_cmd_enhanced_setup_sync_conn *cmd = conn->data; memset(cc, 0, sizeof(*cc)); cc->status = 0x00; memcpy(cc->bdaddr, conn->link->dev->bdaddr, 6); cc->link_type = link_type; cc->tx_interval = 0x0c; cc->retrans_window = 0x06; cc->rx_pkt_len = cpu_to_le16(60); cc->tx_pkt_len = cpu_to_le16(60); cc->air_mode = cmd->tx_coding_format[0]; cc->handle = cpu_to_le16(conn->handle); } static void conn_complete(struct btdev *btdev, const uint8_t *bdaddr, uint8_t status) { struct bt_hci_evt_conn_complete cc; struct btdev *remote = find_btdev_by_bdaddr(bdaddr); int link_type; if (!remote) return; link_type = pending_conn_del(btdev, remote); switch (link_type) { case 0x00: case 0x01: break; default: if (!status) status = BT_HCI_ERR_UNKNOWN_CONN_ID; link_type = 0x01; break; } cc.link_type = link_type; if (!status) { struct bt_hci_evt_sync_conn_complete scc; struct btdev_conn *conn; if (link_type == 0x01) { conn = conn_add_acl(btdev, bdaddr, BDADDR_BREDR); if (!conn) return; } else { conn = queue_find(btdev->conns, match_handle, UINT_TO_PTR(SCO_HANDLE)); if (!conn) return; } pending_conn_del(conn->link->dev, btdev); cc.status = status; memcpy(cc.bdaddr, btdev->bdaddr, 6); cc.encr_mode = 0x00; cc.handle = cpu_to_le16(conn->link->handle); send_event(conn->link->dev, BT_HCI_EVT_CONN_COMPLETE, &cc, sizeof(cc)); if (link_type != 0x01) { /* Initiating controller gets Sync Conn Complete */ sync_conn_init_complete(conn, link_type, &scc); send_event(btdev, BT_HCI_EVT_SYNC_CONN_COMPLETE, &scc, sizeof(scc)); return; } cc.handle = cpu_to_le16(conn->handle); } else { cc.handle = cpu_to_le16(0x0000); } cc.status = status; memcpy(cc.bdaddr, bdaddr, 6); cc.encr_mode = 0x00; send_event(btdev, BT_HCI_EVT_CONN_COMPLETE, &cc, sizeof(cc)); } static void sync_conn_complete(struct btdev *btdev, const uint8_t *bdaddr, uint8_t status) { struct bt_hci_evt_sync_conn_complete cc; struct btdev *remote = find_btdev_by_bdaddr(bdaddr); struct btdev_conn *conn; int link_type; if (!remote) return; link_type = pending_conn_del(btdev, remote); switch (link_type) { case 0x00: /* SCO */ case 0x02: /* eSCO */ break; default: status = BT_HCI_ERR_UNKNOWN_CONN_ID; goto done; } conn = queue_find(btdev->conns, match_handle, UINT_TO_PTR(SCO_HANDLE)); if (!conn) { status = BT_HCI_ERR_UNKNOWN_CONN_ID; goto done; } if (!status) { pending_conn_del(conn->link->dev, btdev); sync_conn_init_complete(conn->link, link_type, &cc); send_event(conn->link->dev, BT_HCI_EVT_SYNC_CONN_COMPLETE, &cc, sizeof(cc)); sync_conn_init_complete(conn, link_type, &cc); } else { conn_remove(conn); memset(&cc, 0, sizeof(cc)); cc.handle = cpu_to_le16(0x0000); cc.link_type = 0x02; } done: cc.status = status; memcpy(cc.bdaddr, bdaddr, 6); send_event(btdev, BT_HCI_EVT_SYNC_CONN_COMPLETE, &cc, sizeof(cc)); } struct page_timeout_data { struct btdev *btdev; uint8_t bdaddr[6]; unsigned int timeout_id; }; static bool page_timeout(void *user_data) { struct page_timeout_data *pt_data = user_data; struct btdev *btdev = pt_data->btdev; const uint8_t *bdaddr = pt_data->bdaddr; timeout_remove(pt_data->timeout_id); pt_data->timeout_id = 0; if (valid_btdev(btdev)) conn_complete(btdev, bdaddr, BT_HCI_ERR_PAGE_TIMEOUT); free(pt_data); return false; } static int cmd_create_conn_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_create_conn *cmd = data; struct btdev *remote = find_btdev_by_bdaddr(cmd->bdaddr); if (remote && remote->scan_enable & 0x02) { struct bt_hci_evt_conn_request cr; memcpy(cr.bdaddr, dev->bdaddr, 6); memcpy(cr.dev_class, dev->dev_class, 3); cr.link_type = 0x01; pending_conn_add(dev, remote, cr.link_type); send_event(remote, BT_HCI_EVT_CONN_REQUEST, &cr, sizeof(cr)); } else { struct page_timeout_data *pt_data = new0(struct page_timeout_data, 1); pt_data->btdev = dev; memcpy(pt_data->bdaddr, cmd->bdaddr, 6); /* Send page timeout after 5.12 seconds to emulate real * paging. */ pt_data->timeout_id = timeout_add(5120, page_timeout, pt_data, NULL); } return 0; } static int cmd_add_sco_conn(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_add_sco_conn *cmd = data; struct bt_hci_evt_conn_complete cc; struct btdev_conn *conn; struct bt_hci_cmd_enhanced_setup_sync_conn enh = { 0 }; memset(&cc, 0, sizeof(cc)); conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(cpu_to_le16(cmd->handle))); if (!conn) { cc.status = BT_HCI_ERR_UNKNOWN_CONN_ID; goto done; } conn = conn_add_sco(conn); if (!conn) { cc.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; goto done; } conn->data = util_memdup(&enh, sizeof(enh)); cc.status = BT_HCI_ERR_SUCCESS; memcpy(cc.bdaddr, conn->link->dev->bdaddr, 6); cc.handle = cpu_to_le16(conn->handle); cc.link_type = 0x00; cc.encr_mode = 0x00; done: send_event(dev, BT_HCI_EVT_CONN_COMPLETE, &cc, sizeof(cc)); if (conn) send_event(conn->link->dev, BT_HCI_EVT_CONN_COMPLETE, &cc, sizeof(cc)); return 0; } static bool match_bdaddr(const void *data, const void *match_data) { const struct btdev_conn *conn = data; const uint8_t *bdaddr = match_data; return !memcmp(conn->link->dev->bdaddr, bdaddr, 6); } static int cmd_create_conn_cancel(struct btdev *dev, const void *data, uint8_t len) { return 0; } static int cmd_create_conn_cancel_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_create_conn_cancel *cmd = data; struct bt_hci_rsp_create_conn_cancel rp; struct btdev *remote = find_btdev_by_bdaddr(cmd->bdaddr); struct btdev_conn *conn; /* BLUETOOTH CORE SPECIFICATION Version 5.4 | Vol 4, Part E page 1848 * * If the connection is already established, and the * HCI_Connection_Complete event has been sent, then the Controller * shall return an HCI_Command_Complete event with the error code * Connection Already Exists (0x0B). If the HCI_Create_Connection_Cancel * command is sent to the Controller without a preceding * HCI_Create_Connection command to the same device, the BR/EDR * Controller shall return an HCI_Command_Complete event with the error * code Unknown Connection Identifier (0x02). */ if (pending_conn_del(dev, remote) >= 0) { rp.status = BT_HCI_ERR_SUCCESS; } else { conn = queue_find(dev->conns, match_bdaddr, cmd->bdaddr); if (conn) rp.status = BT_HCI_ERR_CONN_ALREADY_EXISTS; else rp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; } memcpy(rp.bdaddr, cmd->bdaddr, sizeof(rp.bdaddr)); cmd_complete(dev, BT_HCI_CMD_CREATE_CONN_CANCEL, &rp, sizeof(rp)); if (!rp.status) conn_complete(dev, cmd->bdaddr, BT_HCI_ERR_UNKNOWN_CONN_ID); return 0; } static int cmd_accept_conn(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_ACCEPT_CONN_REQUEST); return 0; } static int cmd_accept_conn_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_accept_conn_request *cmd = data; struct btdev *remote = find_btdev_by_bdaddr(cmd->bdaddr); if (!remote) return 0; if (dev->auth_enable || remote->auth_enable) send_event(remote, BT_HCI_EVT_LINK_KEY_REQUEST, dev->bdaddr, 6); else conn_complete(remote, dev->bdaddr, BT_HCI_ERR_SUCCESS); return 0; } static int cmd_reject_conn(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_REJECT_CONN_REQUEST); return 0; } static int cmd_reject_conn_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_reject_conn_request *cmd = data; struct btdev *remote = find_btdev_by_bdaddr(cmd->bdaddr); if (!remote) return 0; conn_complete(remote, dev->bdaddr, cmd->reason); return 0; } static int cmd_link_key_reply(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_link_key_request_reply rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.bdaddr, data, 6); cmd_complete(dev, BT_HCI_CMD_LINK_KEY_REQUEST_REPLY, &rsp, sizeof(rsp)); return 0; } static void auth_complete(struct btdev_conn *conn, uint8_t status) { struct bt_hci_evt_auth_complete ev; if (!conn) return; memset(&ev, 0, sizeof(ev)); ev.handle = cpu_to_le16(conn->handle); ev.status = status; send_event(conn->dev, BT_HCI_EVT_AUTH_COMPLETE, &ev, sizeof(ev)); conn->dev->ssp_status = 0; conn->dev->ssp_auth_complete = false; conn->link->dev->ssp_status = 0; conn->link->dev->ssp_auth_complete = false; } static int cmd_link_key_reply_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_link_key_request_reply *cmd = data; struct btdev_conn *conn; uint8_t status; conn = queue_find(dev->conns, match_bdaddr, cmd->bdaddr); if (!conn) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } memcpy(dev->link_key, cmd->link_key, 16); if (!memcmp(conn->link->dev->link_key, LINK_KEY_NONE, 16)) { send_event(conn->link->dev, BT_HCI_EVT_LINK_KEY_REQUEST, dev->bdaddr, 6); return 0; } if (!memcmp(dev->link_key, conn->link->dev->link_key, 16)) status = BT_HCI_ERR_SUCCESS; else status = BT_HCI_ERR_AUTH_FAILURE; done: auth_complete(conn, status); if (conn) auth_complete(conn->link, status); return 0; } static int cmd_link_key_neg_reply(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_link_key_request_neg_reply rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.bdaddr, data, 6); cmd_complete(dev, BT_HCI_CMD_LINK_KEY_REQUEST_NEG_REPLY, &rsp, sizeof(rsp)); return 0; } static bool use_ssp(struct btdev *btdev1, struct btdev *btdev2) { if (btdev1->auth_enable || btdev2->auth_enable) return false; return (btdev1->simple_pairing_mode && btdev2->simple_pairing_mode); } static int cmd_link_key_neg_reply_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_link_key_request_neg_reply *cmd = data; struct btdev *remote; remote = find_btdev_by_bdaddr(cmd->bdaddr); if (!remote) return 0; if (use_ssp(dev, remote)) { struct bt_hci_evt_io_capability_request io_req; memcpy(io_req.bdaddr, cmd->bdaddr, 6); send_event(dev, BT_HCI_EVT_IO_CAPABILITY_REQUEST, &io_req, sizeof(io_req)); } else { struct bt_hci_evt_pin_code_request pin_req; memcpy(pin_req.bdaddr, cmd->bdaddr, 6); send_event(dev, BT_HCI_EVT_PIN_CODE_REQUEST, &pin_req, sizeof(pin_req)); } return 0; } static int cmd_pin_code_reply(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_pin_code_request_neg_reply rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.bdaddr, data, 6); cmd_complete(dev, BT_HCI_CMD_PIN_CODE_REQUEST_REPLY, &rsp, sizeof(rsp)); return 0; } static uint8_t get_link_key_type(struct btdev *btdev, const uint8_t *bdaddr) { struct btdev_conn *conn; uint8_t auth, unauth; conn = queue_find(btdev->conns, match_bdaddr, bdaddr); if (!conn) return 0x00; if (!btdev->simple_pairing_mode) return 0x00; if (btdev->ssp_debug_mode || conn->link->dev->ssp_debug_mode) return 0x03; if (btdev->secure_conn_support && conn->link->dev->secure_conn_support) { unauth = 0x07; auth = 0x08; } else { unauth = 0x04; auth = 0x05; } if (btdev->io_cap == 0x03 || conn->link->dev->io_cap == 0x03) return unauth; if (!(btdev->auth_req & 0x01) && !(conn->link->dev->auth_req & 0x01)) return unauth; /* DisplayOnly only produces authenticated with KeyboardOnly */ if (btdev->io_cap == 0x00 && conn->link->dev->io_cap != 0x02) return unauth; /* DisplayOnly only produces authenticated with KeyboardOnly */ if (conn->link->dev->io_cap == 0x00 && btdev->io_cap != 0x02) return unauth; return auth; } static void link_key_notify(struct btdev *btdev, const uint8_t *bdaddr, const uint8_t *key) { struct bt_hci_evt_link_key_notify ev; memcpy(btdev->link_key, key, 16); memcpy(ev.bdaddr, bdaddr, 6); memcpy(ev.link_key, key, 16); ev.key_type = get_link_key_type(btdev, bdaddr); send_event(btdev, BT_HCI_EVT_LINK_KEY_NOTIFY, &ev, sizeof(ev)); } static int cmd_pin_code_reply_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_pin_code_request_reply *cmd = data; struct btdev *remote; struct btdev_conn *conn; uint8_t status; conn = queue_find(dev->conns, match_bdaddr, cmd->bdaddr); if (!conn) { remote = find_btdev_by_bdaddr(cmd->bdaddr); if (!remote) return 0; } else remote = conn->link->dev; memcpy(dev->pin, cmd->pin_code, cmd->pin_len); dev->pin_len = cmd->pin_len; if (!remote->pin_len) { struct bt_hci_evt_pin_code_request pin_req; memcpy(pin_req.bdaddr, dev->bdaddr, 6); send_event(remote, BT_HCI_EVT_PIN_CODE_REQUEST, &pin_req, sizeof(pin_req)); return 0; } if (dev->pin_len == remote->pin_len && !memcmp(dev->pin, remote->pin, dev->pin_len)) { link_key_notify(dev, remote->bdaddr, LINK_KEY_DUMMY); link_key_notify(remote, dev->bdaddr, LINK_KEY_DUMMY); status = BT_HCI_ERR_SUCCESS; } else { status = BT_HCI_ERR_AUTH_FAILURE; } if (conn) auth_complete(conn->link, status); else conn_complete(remote, dev->bdaddr, status); dev->pin_len = 0; remote->pin_len = 0; return 0; } static int cmd_pin_code_neg_reply(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_pin_code_request_neg_reply rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.bdaddr, data, 6); cmd_complete(dev, BT_HCI_CMD_PIN_CODE_REQUEST_NEG_REPLY, &rsp, sizeof(rsp)); return 0; } static int cmd_pin_code_neg_reply_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_pin_code_request_neg_reply *cmd = data; struct btdev *remote; struct btdev_conn *conn; uint8_t status; remote = find_btdev_by_bdaddr(cmd->bdaddr); if (!remote) return 0; status = BT_HCI_ERR_PIN_OR_KEY_MISSING; conn = queue_find(dev->conns, match_bdaddr, cmd->bdaddr); if (conn) auth_complete(conn, status); else conn_complete(dev, cmd->bdaddr, BT_HCI_ERR_PIN_OR_KEY_MISSING); if (conn) { if (remote->pin_len) auth_complete(conn->link, status); } else { conn_complete(remote, dev->bdaddr, BT_HCI_ERR_PIN_OR_KEY_MISSING); } return 0; } static int cmd_change_conn_pkt_type(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_change_conn_pkt_type *cmd = data; struct btdev_conn *conn; uint8_t status = BT_HCI_ERR_SUCCESS; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(cpu_to_le16(cmd->handle))); if (!conn) status = BT_HCI_ERR_UNKNOWN_CONN_ID; cmd_status(dev, status, BT_HCI_CMD_CHANGE_CONN_PKT_TYPE); return 0; } static int cmd_change_conn_pkt_type_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_change_conn_pkt_type *cmd = data; struct bt_hci_evt_conn_pkt_type_changed ev; struct btdev_conn *conn; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(cpu_to_le16(cmd->handle))); if (!conn) return 0; memset(&ev, 0, sizeof(ev)); ev.status = BT_HCI_ERR_SUCCESS; ev.handle = cmd->handle; ev.pkt_type = cmd->pkt_type; send_event(dev, BT_HCI_EVT_CONN_PKT_TYPE_CHANGED, &ev, sizeof(ev)); return 0; } static int cmd_auth_requested(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_AUTH_REQUESTED); return 0; } static int cmd_auth_requested_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_auth_requested *cmd = data; struct btdev_conn *conn; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (!conn) { struct bt_hci_evt_auth_complete ev; ev.status = BT_HCI_ERR_UNKNOWN_CONN_ID; ev.handle = cpu_to_le16(cmd->handle); send_event(dev, BT_HCI_EVT_AUTH_COMPLETE, &ev, sizeof(ev)); return 0; } dev->auth_init = true; send_event(dev, BT_HCI_EVT_LINK_KEY_REQUEST, conn->link->dev->bdaddr, sizeof(conn->link->dev->bdaddr)); return 0; } static int cmd_set_conn_encrypt(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_SET_CONN_ENCRYPT); return 0; } static void encrypt_change(struct btdev_conn *conn, uint8_t mode, uint8_t status) { struct bt_hci_evt_encrypt_change ev; if (!conn) return; memset(&ev, 0, sizeof(ev)); ev.status = status; ev.handle = cpu_to_le16(conn->handle); ev.encr_mode = mode; send_event(conn->dev, BT_HCI_EVT_ENCRYPT_CHANGE, &ev, sizeof(ev)); } static int cmd_set_conn_encrypt_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_set_conn_encrypt *cmd = data; struct btdev_conn *conn; uint8_t mode; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (!conn) return 0; if (!cmd->encr_mode) mode = 0x00; else if (dev->secure_conn_support && conn->link->dev->secure_conn_support) mode = 0x02; else mode = 0x01; encrypt_change(conn, mode, BT_HCI_ERR_SUCCESS); encrypt_change(conn->link, mode, BT_HCI_ERR_SUCCESS); return 0; } static int cmd_remote_name(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_REMOTE_NAME_REQUEST); return 0; } static void name_request_complete(struct btdev *btdev, const uint8_t *bdaddr, uint8_t status) { struct bt_hci_evt_remote_name_request_complete nc; nc.status = status; memcpy(nc.bdaddr, bdaddr, 6); memset(nc.name, 0, 248); if (!status) { struct btdev *remote = find_btdev_by_bdaddr(bdaddr); if (remote) memcpy(nc.name, remote->name, 248); else nc.status = BT_HCI_ERR_UNKNOWN_CONN_ID; } send_event(btdev, BT_HCI_EVT_REMOTE_NAME_REQUEST_COMPLETE, &nc, sizeof(nc)); } static int cmd_remote_name_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_remote_name_request *cmd = data; name_request_complete(dev, cmd->bdaddr, BT_HCI_ERR_SUCCESS); return 0; } static int cmd_remote_name_cancel(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_remote_name_request_cancel *cmd = data; struct bt_hci_rsp_remote_name_request_cancel rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.bdaddr, cmd->bdaddr, 6); cmd_complete(dev, BT_HCI_CMD_REMOTE_NAME_REQUEST_CANCEL, &rsp, sizeof(rsp)); return 0; } static int cmd_remote_name_cancel_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_remote_name_request_cancel *cmd = data; name_request_complete(dev, cmd->bdaddr, BT_HCI_ERR_UNKNOWN_CONN_ID); return 0; } static int cmd_read_remote_features(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_READ_REMOTE_FEATURES); return 0; } static int cmd_read_remote_features_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_read_remote_features *cmd = data; struct bt_hci_evt_remote_features_complete rfc; struct btdev_conn *conn; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (conn) { rfc.status = BT_HCI_ERR_SUCCESS; rfc.handle = cpu_to_le16(cmd->handle); memcpy(rfc.features, conn->link->dev->features, 8); } else { rfc.status = BT_HCI_ERR_UNKNOWN_CONN_ID; rfc.handle = cpu_to_le16(cmd->handle); memset(rfc.features, 0, 8); } send_event(dev, BT_HCI_EVT_REMOTE_FEATURES_COMPLETE, &rfc, sizeof(rfc)); return 0; } static int cmd_read_remote_ext_features(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_READ_REMOTE_EXT_FEATURES); return 0; } static void btdev_get_host_features(struct btdev *btdev, uint8_t features[8]) { memset(features, 0, 8); if (btdev->simple_pairing_mode) features[0] |= 0x01; if (btdev->le_supported) features[0] |= 0x02; if (btdev->le_simultaneous) features[0] |= 0x04; if (btdev->secure_conn_support) features[0] |= 0x08; } static int cmd_read_remote_ext_features_compl(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_read_remote_ext_features *cmd = data; struct bt_hci_evt_remote_ext_features_complete ev; struct btdev_conn *conn; memset(&ev, 0, sizeof(ev)); conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (conn && cmd->page < 0x02) { ev.handle = cpu_to_le16(cmd->handle); ev.page = cmd->page; ev.max_page = 0x01; switch (cmd->page) { case 0x00: ev.status = BT_HCI_ERR_SUCCESS; memcpy(ev.features, conn->link->dev->features, 8); break; case 0x01: ev.status = BT_HCI_ERR_SUCCESS; btdev_get_host_features(conn->link->dev, ev.features); break; default: ev.status = BT_HCI_ERR_INVALID_PARAMETERS; memset(ev.features, 0, 8); break; } } else { ev.status = BT_HCI_ERR_UNKNOWN_CONN_ID; ev.handle = cpu_to_le16(cmd->handle); ev.page = cmd->page; ev.max_page = 0x01; memset(ev.features, 0, 8); } send_event(dev, BT_HCI_EVT_REMOTE_EXT_FEATURES_COMPLETE, &ev, sizeof(ev)); return 0; } static int cmd_read_clock_offset(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_READ_CLOCK_OFFSET); return 0; } static int cmd_read_clock_offset_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_read_clock_offset *cmd = data; struct bt_hci_evt_clock_offset_complete ev; struct btdev_conn *conn; memset(&ev, 0, sizeof(ev)); conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (conn) { ev.status = BT_HCI_ERR_SUCCESS; ev.handle = cpu_to_le16(cmd->handle); ev.clock_offset = 0; } else { ev.status = BT_HCI_ERR_UNKNOWN_CONN_ID; ev.handle = cpu_to_le16(cmd->handle); ev.clock_offset = 0; } send_event(dev, BT_HCI_EVT_CLOCK_OFFSET_COMPLETE, &ev, sizeof(ev)); return 0; } static int cmd_read_link_policy(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_default_link_policy rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.policy = cpu_to_le16(dev->default_link_policy); cmd_complete(dev, BT_HCI_CMD_READ_DEFAULT_LINK_POLICY, &rsp, sizeof(rsp)); return 0; } static int cmd_write_link_policy(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_default_link_policy *cmd = data; uint8_t status; dev->default_link_policy = le16_to_cpu(cmd->policy); status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_WRITE_DEFAULT_LINK_POLICY, &status, sizeof(status)); return 0; } static int cmd_set_event_filter(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_set_event_filter *cmd = data; uint8_t status; dev->event_filter = cmd->type; status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_SET_EVENT_FILTER, &status, sizeof(status)); return 0; } static int cmd_read_link_key(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_stored_link_key rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.max_num_keys = cpu_to_le16(0); rsp.num_keys = cpu_to_le16(0); cmd_complete(dev, BT_HCI_CMD_READ_STORED_LINK_KEY, &rsp, sizeof(rsp)); return 0; } static int cmd_write_link_key(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_write_stored_link_key rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.num_keys = 0; cmd_complete(dev, BT_HCI_CMD_WRITE_STORED_LINK_KEY, &rsp, sizeof(rsp)); return 0; } static int cmd_delete_link_key(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_delete_stored_link_key rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.num_keys = cpu_to_le16(0); cmd_complete(dev, BT_HCI_CMD_DELETE_STORED_LINK_KEY, &rsp, sizeof(rsp)); return 0; } static int cmd_read_local_name(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_local_name rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.name, dev->name, 248); cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_NAME, &rsp, sizeof(rsp)); return 0; } static int cmd_write_local_name(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_local_name *cmd = data; uint8_t status; memcpy(dev->name, cmd->name, 248); status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_WRITE_LOCAL_NAME, &status, sizeof(status)); return 0; } static int cmd_read_accept_timeout(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_conn_accept_timeout rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.timeout = cpu_to_le16(dev->conn_accept_timeout); cmd_complete(dev, BT_HCI_CMD_READ_CONN_ACCEPT_TIMEOUT, &rsp, sizeof(rsp)); return 0; } static int cmd_write_accept_timeout(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_conn_accept_timeout *cmd = data; uint8_t status; dev->conn_accept_timeout = le16_to_cpu(cmd->timeout); status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_WRITE_CONN_ACCEPT_TIMEOUT, &status, sizeof(status)); return 0; } static int cmd_read_page_timeout(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_page_timeout rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.timeout = cpu_to_le16(dev->page_timeout); cmd_complete(dev, BT_HCI_CMD_READ_PAGE_TIMEOUT, &rsp, sizeof(rsp)); return 0; } static int cmd_write_page_timeout(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_page_timeout *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; dev->page_timeout = le16_to_cpu(cmd->timeout); cmd_complete(dev, BT_HCI_CMD_WRITE_PAGE_TIMEOUT, &status, sizeof(status)); return 0; } static int cmd_read_scan_enable(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_scan_enable rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.enable = dev->scan_enable; cmd_complete(dev, BT_HCI_CMD_READ_SCAN_ENABLE, &rsp, sizeof(rsp)); return 0; } static int cmd_write_scan_enable(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_scan_enable *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; dev->scan_enable = cmd->enable; cmd_complete(dev, BT_HCI_CMD_WRITE_SCAN_ENABLE, &status, sizeof(status)); return 0; } static int cmd_read_page_scan(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_page_scan_activity rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.interval = cpu_to_le16(dev->page_scan_interval); rsp.window = cpu_to_le16(dev->page_scan_window); cmd_complete(dev, BT_HCI_CMD_READ_PAGE_SCAN_ACTIVITY, &rsp, sizeof(rsp)); return 0; } static int cmd_write_page_scan(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_page_scan_activity *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; dev->page_scan_interval = le16_to_cpu(cmd->interval); dev->page_scan_window = le16_to_cpu(cmd->window); cmd_complete(dev, BT_HCI_CMD_WRITE_PAGE_SCAN_ACTIVITY, &status, sizeof(status)); return 0; } static int cmd_read_inquiry_scan(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_inquiry_scan_activity rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.interval = cpu_to_le16(dev->inquiry_scan_interval); rsp.window = cpu_to_le16(dev->inquiry_scan_window); cmd_complete(dev, BT_HCI_CMD_READ_INQUIRY_SCAN_ACTIVITY, &rsp, sizeof(rsp)); return 0; } static int cmd_write_inquiry_scan(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_inquiry_scan_activity *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; dev->inquiry_scan_interval = le16_to_cpu(cmd->interval); dev->inquiry_scan_window = le16_to_cpu(cmd->window); cmd_complete(dev, BT_HCI_CMD_WRITE_INQUIRY_SCAN_ACTIVITY, &status, sizeof(status)); return 0; } static int cmd_read_auth_enable(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_auth_enable rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.enable = dev->auth_enable; cmd_complete(dev, BT_HCI_CMD_READ_AUTH_ENABLE, &rsp, sizeof(rsp)); return 0; } static int cmd_write_auth_enable(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_auth_enable *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; dev->auth_enable = cmd->enable; cmd_complete(dev, BT_HCI_CMD_WRITE_AUTH_ENABLE, &status, sizeof(status)); return 0; } static int cmd_read_class(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_class_of_dev rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.dev_class, dev->dev_class, 3); cmd_complete(dev, BT_HCI_CMD_READ_CLASS_OF_DEV, &rsp, sizeof(rsp)); return 0; } static int cmd_write_class(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_class_of_dev *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; memcpy(dev->dev_class, cmd->dev_class, 3); cmd_complete(dev, BT_HCI_CMD_WRITE_CLASS_OF_DEV, &status, sizeof(status)); return 0; } static int cmd_read_voice(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_voice_setting rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.setting = cpu_to_le16(dev->voice_setting); cmd_complete(dev, BT_HCI_CMD_READ_VOICE_SETTING, &rsp, sizeof(rsp)); return 0; } static int cmd_write_voice(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_voice_setting *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; dev->voice_setting = le16_to_cpu(cmd->setting); cmd_complete(dev, BT_HCI_CMD_WRITE_VOICE_SETTING, &status, sizeof(status)); return 0; } static int cmd_read_tx_power_level(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_read_tx_power *cmd = data; struct bt_hci_rsp_read_tx_power rsp; memset(&rsp, 0, sizeof(rsp)); rsp.handle = le16_to_cpu(cmd->handle); rsp.status = BT_HCI_ERR_SUCCESS; if (cmd->type) rsp.level = 4; else rsp.level = -1; cmd_complete(dev, BT_HCI_CMD_READ_TX_POWER, &rsp, sizeof(rsp)); return 0; } static int cmd_write_sync_flowctl(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_sync_flow_control *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; dev->sco_flowctl = cmd->enable; cmd_complete(dev, BT_HCI_CMD_WRITE_SYNC_FLOW_CONTROL, &status, sizeof(status)); return 0; } static int cmd_read_num_iac(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_num_supported_iac rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.num_iac = 0x01; cmd_complete(dev, BT_HCI_CMD_READ_NUM_SUPPORTED_IAC, &rsp, sizeof(rsp)); return 0; } static int cmd_read_current_iac_lap(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_current_iac_lap *rsp; rsp = alloca(sizeof(*rsp) + 3); rsp->status = BT_HCI_ERR_SUCCESS; rsp->num_iac = 0x01; rsp->iac_lap[0] = 0x33; rsp->iac_lap[1] = 0x8b; rsp->iac_lap[2] = 0x9e; cmd_complete(dev, BT_HCI_CMD_READ_CURRENT_IAC_LAP, rsp, sizeof(*rsp) + 3); return 0; } static int cmd_write_current_iac_lap(struct btdev *dev, const void *data, uint8_t len) { uint8_t status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_WRITE_CURRENT_IAC_LAP, &status, sizeof(status)); return 0; } static int cmd_read_inquiry_mode(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_inquiry_mode rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.mode = dev->inquiry_mode; cmd_complete(dev, BT_HCI_CMD_READ_INQUIRY_MODE, &rsp, sizeof(rsp)); return 0; } static int cmd_write_inquiry_mode(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_inquiry_mode *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; dev->inquiry_mode = cmd->mode; cmd_complete(dev, BT_HCI_CMD_WRITE_INQUIRY_MODE, &status, sizeof(status)); return 0; } static int cmd_read_page_scan_type(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_page_scan_type rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.type = dev->page_scan_type; cmd_complete(dev, BT_HCI_CMD_READ_PAGE_SCAN_TYPE, &rsp, sizeof(rsp)); return 0; } static int cmd_write_page_scan_type(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_page_scan_type *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; dev->page_scan_type = cmd->type; cmd_complete(dev, BT_HCI_CMD_WRITE_PAGE_SCAN_TYPE, &status, sizeof(status)); return 0; } static int cmd_read_afh_mode(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_afh_assessment_mode rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.mode = dev->afh_assessment_mode; cmd_complete(dev, BT_HCI_CMD_READ_AFH_ASSESSMENT_MODE, &rsp, sizeof(rsp)); return 0; } static int cmd_write_afh_mode(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_afh_assessment_mode *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; dev->afh_assessment_mode = cmd->mode; cmd_complete(dev, BT_HCI_CMD_WRITE_AFH_ASSESSMENT_MODE, &status, sizeof(status)); return 0; } static int cmd_read_local_ext_features(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_local_ext_features rsp; uint8_t page = ((const uint8_t *) data)[0]; memset(&rsp, 0, sizeof(rsp)); rsp.page = page; rsp.max_page = dev->max_page; if (page > dev->max_page) { rsp.status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } rsp.status = BT_HCI_ERR_SUCCESS; switch (page) { case 0x00: memcpy(rsp.features, dev->features, 8); break; case 0x01: btdev_get_host_features(dev, rsp.features); break; case 0x02: memcpy(rsp.features, dev->feat_page_2, 8); break; default: rsp.status = BT_HCI_ERR_INVALID_PARAMETERS; break; } done: cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_EXT_FEATURES, &rsp, sizeof(rsp)); return 0; } static int cmd_read_country_code(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_country_code rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.code = dev->country_code; cmd_complete(dev, BT_HCI_CMD_READ_COUNTRY_CODE, &rsp, sizeof(rsp)); return 0; } static int cmd_read_rssi(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_read_rssi *cmd = data; struct bt_hci_rsp_read_rssi rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.handle = le16_to_cpu(cmd->handle); rsp.rssi = -1; cmd_complete(dev, BT_HCI_CMD_READ_RSSI, &rsp, sizeof(rsp)); return 0; } static int cmd_read_clock(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_read_clock *cmd = data; struct bt_hci_rsp_read_clock rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.handle = le16_to_cpu(cmd->handle); rsp.clock = 0x11223344; rsp.accuracy = 0x5566; cmd_complete(dev, BT_HCI_CMD_READ_CLOCK, &rsp, sizeof(rsp)); return 0; } static int cmd_enable_dut_mode(struct btdev *dev, const void *data, uint8_t len) { uint8_t status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_ENABLE_DUT_MODE, &status, sizeof(status)); return 0; } #define CMD_COMMON_BREDR_20 \ CMD(BT_HCI_CMD_INQUIRY, cmd_inquiry, cmd_inquiry_complete), \ CMD(BT_HCI_CMD_INQUIRY_CANCEL, cmd_inquiry_cancel, NULL), \ CMD(BT_HCI_CMD_CREATE_CONN, cmd_create_conn, \ cmd_create_conn_complete), \ CMD(BT_HCI_CMD_ADD_SCO_CONN, cmd_add_sco_conn, NULL), \ CMD(BT_HCI_CMD_CREATE_CONN_CANCEL, cmd_create_conn_cancel, \ cmd_create_conn_cancel_complete), \ CMD(BT_HCI_CMD_ACCEPT_CONN_REQUEST, cmd_accept_conn, \ cmd_accept_conn_complete), \ CMD(BT_HCI_CMD_REJECT_CONN_REQUEST, cmd_reject_conn, \ cmd_reject_conn_complete), \ CMD(BT_HCI_CMD_LINK_KEY_REQUEST_REPLY, cmd_link_key_reply, \ cmd_link_key_reply_complete), \ CMD(BT_HCI_CMD_LINK_KEY_REQUEST_NEG_REPLY, \ cmd_link_key_neg_reply, \ cmd_link_key_neg_reply_complete), \ CMD(BT_HCI_CMD_PIN_CODE_REQUEST_REPLY, cmd_pin_code_reply, \ cmd_pin_code_reply_complete), \ CMD(BT_HCI_CMD_PIN_CODE_REQUEST_NEG_REPLY, \ cmd_pin_code_neg_reply, \ cmd_pin_code_neg_reply_complete), \ CMD(BT_HCI_CMD_CHANGE_CONN_PKT_TYPE, \ cmd_change_conn_pkt_type, \ cmd_change_conn_pkt_type_complete), \ CMD(BT_HCI_CMD_AUTH_REQUESTED, cmd_auth_requested, \ cmd_auth_requested_complete), \ CMD(BT_HCI_CMD_SET_CONN_ENCRYPT, cmd_set_conn_encrypt, \ cmd_set_conn_encrypt_complete), \ CMD(BT_HCI_CMD_REMOTE_NAME_REQUEST, cmd_remote_name, \ cmd_remote_name_complete), \ CMD(BT_HCI_CMD_REMOTE_NAME_REQUEST_CANCEL, cmd_remote_name_cancel, \ cmd_remote_name_cancel_complete), \ CMD(BT_HCI_CMD_READ_REMOTE_FEATURES, cmd_read_remote_features, \ cmd_read_remote_features_complete), \ CMD(BT_HCI_CMD_READ_REMOTE_EXT_FEATURES, \ cmd_read_remote_ext_features, \ cmd_read_remote_ext_features_compl), \ CMD(BT_HCI_CMD_READ_CLOCK_OFFSET, cmd_read_clock_offset, \ cmd_read_clock_offset_complete), \ CMD(BT_HCI_CMD_READ_DEFAULT_LINK_POLICY, cmd_read_link_policy, NULL), \ CMD(BT_HCI_CMD_WRITE_DEFAULT_LINK_POLICY, cmd_write_link_policy, \ NULL), \ CMD(BT_HCI_CMD_SET_EVENT_FILTER, cmd_set_event_filter, NULL), \ CMD(BT_HCI_CMD_READ_STORED_LINK_KEY, cmd_read_link_key, NULL), \ CMD(BT_HCI_CMD_WRITE_STORED_LINK_KEY, cmd_write_link_key, NULL), \ CMD(BT_HCI_CMD_DELETE_STORED_LINK_KEY, cmd_delete_link_key, NULL), \ CMD(BT_HCI_CMD_READ_LOCAL_NAME, cmd_read_local_name, NULL), \ CMD(BT_HCI_CMD_WRITE_LOCAL_NAME, cmd_write_local_name, NULL), \ CMD(BT_HCI_CMD_READ_CONN_ACCEPT_TIMEOUT, cmd_read_accept_timeout, \ NULL), \ CMD(BT_HCI_CMD_WRITE_CONN_ACCEPT_TIMEOUT, cmd_write_accept_timeout, \ NULL), \ CMD(BT_HCI_CMD_READ_PAGE_TIMEOUT, cmd_read_page_timeout, NULL), \ CMD(BT_HCI_CMD_WRITE_PAGE_TIMEOUT, cmd_write_page_timeout, NULL), \ CMD(BT_HCI_CMD_READ_SCAN_ENABLE, cmd_read_scan_enable, NULL), \ CMD(BT_HCI_CMD_WRITE_SCAN_ENABLE, cmd_write_scan_enable, NULL), \ CMD(BT_HCI_CMD_READ_PAGE_SCAN_ACTIVITY, cmd_read_page_scan, NULL), \ CMD(BT_HCI_CMD_WRITE_PAGE_SCAN_ACTIVITY, cmd_write_page_scan, NULL), \ CMD(BT_HCI_CMD_READ_INQUIRY_SCAN_ACTIVITY, cmd_read_inquiry_scan, \ NULL), \ CMD(BT_HCI_CMD_WRITE_INQUIRY_SCAN_ACTIVITY, cmd_write_inquiry_scan, \ NULL), \ CMD(BT_HCI_CMD_READ_AUTH_ENABLE, cmd_read_auth_enable, NULL), \ CMD(BT_HCI_CMD_WRITE_AUTH_ENABLE, cmd_write_auth_enable, NULL), \ CMD(BT_HCI_CMD_READ_CLASS_OF_DEV, cmd_read_class, NULL), \ CMD(BT_HCI_CMD_WRITE_CLASS_OF_DEV, cmd_write_class, NULL), \ CMD(BT_HCI_CMD_READ_VOICE_SETTING, cmd_read_voice, NULL), \ CMD(BT_HCI_CMD_WRITE_VOICE_SETTING, cmd_write_voice, NULL), \ CMD(BT_HCI_CMD_READ_TX_POWER, cmd_read_tx_power_level, NULL), \ CMD(BT_HCI_CMD_WRITE_SYNC_FLOW_CONTROL, cmd_write_sync_flowctl, NULL), \ CMD(BT_HCI_CMD_READ_NUM_SUPPORTED_IAC, cmd_read_num_iac, NULL), \ CMD(BT_HCI_CMD_READ_CURRENT_IAC_LAP, cmd_read_current_iac_lap, \ NULL), \ CMD(BT_HCI_CMD_WRITE_CURRENT_IAC_LAP, cmd_write_current_iac_lap, \ NULL), \ CMD(BT_HCI_CMD_READ_INQUIRY_MODE, cmd_read_inquiry_mode, NULL), \ CMD(BT_HCI_CMD_WRITE_INQUIRY_MODE, cmd_write_inquiry_mode, NULL), \ CMD(BT_HCI_CMD_READ_PAGE_SCAN_TYPE, cmd_read_page_scan_type, NULL), \ CMD(BT_HCI_CMD_WRITE_PAGE_SCAN_TYPE, cmd_write_page_scan_type, NULL), \ CMD(BT_HCI_CMD_READ_AFH_ASSESSMENT_MODE, cmd_read_afh_mode, NULL), \ CMD(BT_HCI_CMD_WRITE_AFH_ASSESSMENT_MODE, cmd_write_afh_mode, NULL), \ CMD(BT_HCI_CMD_READ_LOCAL_EXT_FEATURES, cmd_read_local_ext_features, \ NULL), \ CMD(BT_HCI_CMD_READ_COUNTRY_CODE, cmd_read_country_code, NULL), \ CMD(BT_HCI_CMD_READ_RSSI, cmd_read_rssi, NULL), \ CMD(BT_HCI_CMD_READ_CLOCK, cmd_read_clock, NULL), \ CMD(BT_HCI_CMD_ENABLE_DUT_MODE, cmd_enable_dut_mode, NULL) static void set_common_commands_bredr20(struct btdev *btdev) { btdev->commands[0] |= 0x01; /* Inquiry */ btdev->commands[0] |= 0x02; /* Inquiry Cancel */ btdev->commands[0] |= 0x10; /* Create Connection */ btdev->commands[0] |= 0x40; /* Add SCO Connection */ btdev->commands[0] |= 0x80; /* Cancel Create Connection */ btdev->commands[1] |= 0x01; /* Accept Connection Request */ btdev->commands[1] |= 0x02; /* Reject Connection Request */ btdev->commands[1] |= 0x04; /* Link Key Request Reply */ btdev->commands[1] |= 0x08; /* Link Key Request Negative Reply */ btdev->commands[1] |= 0x10; /* PIN Code Request Reply */ btdev->commands[1] |= 0x20; /* PIN Code Request Negative Reply */ btdev->commands[1] |= 0x80; /* Authentication Requested */ btdev->commands[2] |= 0x01; /* Set Connection Encryption */ btdev->commands[2] |= 0x08; /* Remote Name Request */ btdev->commands[2] |= 0x10; /* Cancel Remote Name Request */ btdev->commands[2] |= 0x20; /* Read Remote Supported Features */ btdev->commands[2] |= 0x40; /* Read Remote Extended Features */ btdev->commands[3] |= 0x01; /* Read Clock Offset */ btdev->commands[5] |= 0x08; /* Read Default Link Policy */ btdev->commands[5] |= 0x10; /* Write Default Link Policy */ btdev->commands[6] |= 0x01; /* Set Event Filter */ btdev->commands[6] |= 0x20; /* Read Stored Link Key */ btdev->commands[6] |= 0x40; /* Write Stored Link Key */ btdev->commands[6] |= 0x80; /* Delete Stored Link Key */ btdev->commands[7] |= 0x01; /* Write Local Name */ btdev->commands[7] |= 0x02; /* Read Local Name */ btdev->commands[7] |= 0x04; /* Read Connection Accept Timeout */ btdev->commands[7] |= 0x08; /* Write Connection Accept Timeout */ btdev->commands[7] |= 0x10; /* Read Page Timeout */ btdev->commands[7] |= 0x20; /* Write Page Timeout */ btdev->commands[7] |= 0x40; /* Read Scan Enable */ btdev->commands[7] |= 0x80; /* Write Scan Enable */ btdev->commands[8] |= 0x01; /* Read Page Scan Activity */ btdev->commands[8] |= 0x02; /* Write Page Scan Activity */ btdev->commands[8] |= 0x04; /* Read Inquiry Scan Activity */ btdev->commands[8] |= 0x08; /* Write Inquiry Scan Activity */ btdev->commands[8] |= 0x10; /* Read Authentication Enable */ btdev->commands[8] |= 0x20; /* Write Authentication Enable */ btdev->commands[9] |= 0x01; /* Read Class Of Device */ btdev->commands[9] |= 0x02; /* Write Class Of Device */ btdev->commands[9] |= 0x04; /* Read Voice Setting */ btdev->commands[9] |= 0x08; /* Write Voice Setting */ btdev->commands[10] |= 0x04; /* Read TX Power Level */ btdev->commands[10] |= BIT(4); /* Write Sync Flow Control */ btdev->commands[11] |= 0x04; /* Read Number of Supported IAC */ btdev->commands[11] |= 0x08; /* Read Current IAC LAP */ btdev->commands[11] |= 0x10; /* Write Current IAC LAP */ btdev->commands[12] |= 0x40; /* Read Inquiry Mode */ btdev->commands[12] |= 0x80; /* Write Inquiry Mode */ btdev->commands[13] |= 0x01; /* Read Page Scan Type */ btdev->commands[13] |= 0x02; /* Write Page Scan Type */ btdev->commands[13] |= 0x04; /* Read AFH Assess Mode */ btdev->commands[13] |= 0x08; /* Write AFH Assess Mode */ btdev->commands[14] |= 0x40; /* Read Local Extended Features */ btdev->commands[15] |= 0x01; /* Read Country Code */ btdev->commands[15] |= 0x20; /* Read RSSI */ btdev->commands[15] |= 0x80; /* Read Clock */ btdev->commands[16] |= 0x04; /* Enable Device Under Test Mode */ } static int cmd_enhanced_setup_sync_conn(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_enhanced_setup_sync_conn *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; if (cmd->tx_coding_format[0] > 5) status = BT_HCI_ERR_INVALID_PARAMETERS; cmd_status(dev, status, BT_HCI_CMD_ENHANCED_SETUP_SYNC_CONN); return 0; } static int cmd_enhanced_setup_sync_conn_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_enhanced_setup_sync_conn *cmd = data; struct bt_hci_evt_sync_conn_complete cc; struct bt_hci_evt_conn_request cr; struct btdev_conn *conn; memset(&cc, 0, sizeof(cc)); conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (!conn || conn->type != HCI_ACLDATA_PKT) { cc.status = BT_HCI_ERR_UNKNOWN_CONN_ID; goto fail; } conn = conn_add_sco(conn); if (!conn) { cc.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; goto fail; } conn->data = util_memdup(cmd, sizeof(*cmd)); conn->link->data = util_memdup(cmd, sizeof(*cmd)); memcpy(cr.bdaddr, dev->bdaddr, 6); memcpy(cr.dev_class, dev->dev_class, 3); cr.link_type = 0x02; pending_conn_add(dev, conn->link->dev, cr.link_type); send_event(conn->link->dev, BT_HCI_EVT_CONN_REQUEST, &cr, sizeof(cr)); return 0; fail: send_event(dev, BT_HCI_EVT_SYNC_CONN_COMPLETE, &cc, sizeof(cc)); return 0; } static int cmd_setup_sync_conn(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_SETUP_SYNC_CONN); return 0; } static int cmd_setup_sync_conn_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_setup_sync_conn *cmd = data; struct bt_hci_cmd_enhanced_setup_sync_conn enh; struct btdev_conn *conn; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (conn && conn->type == HCI_SCODATA_PKT) { struct bt_hci_evt_sync_conn_changed cc; /* TODO: actually modify connection parameters */ memset(&cc, 0, sizeof(cc)); cc.status = BT_HCI_ERR_UNSUPPORTED_FEATURE; send_event(dev, BT_HCI_EVT_SYNC_CONN_CHANGED, &cc, sizeof(cc)); return 0; } memset(&enh, 0, sizeof(enh)); enh.handle = cmd->handle; /* TODO: emulate also this properly? */ enh.tx_bandwidth = cmd->tx_bandwidth; enh.rx_bandwidth = cmd->rx_bandwidth; enh.max_latency = cmd->max_latency; enh.retrans_effort = cmd->retrans_effort; enh.pkt_type = cmd->pkt_type; switch (le16_to_cpu(cmd->voice_setting) & SCO_AIRMODE_MASK) { case SCO_AIRMODE_TRANSP: enh.tx_coding_format[0] = 0x03; break; case SCO_AIRMODE_CVSD: default: enh.tx_coding_format[0] = 0x02; break; } return cmd_enhanced_setup_sync_conn_complete(dev, &enh, sizeof(enh)); } static int cmd_accept_sync_conn_request(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_accept_sync_conn_request *cmd = data; if (!find_btdev_by_bdaddr(cmd->bdaddr)) return -EPERM; cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_ACCEPT_SYNC_CONN_REQUEST); return 0; } static int cmd_accept_sync_conn_request_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_accept_sync_conn_request *cmd = data; struct btdev *remote = find_btdev_by_bdaddr(cmd->bdaddr); if (!remote) return 0; sync_conn_complete(remote, dev->bdaddr, BT_HCI_ERR_SUCCESS); return 0; } static int cmd_reject_sync_conn_request(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_accept_sync_conn_request *cmd = data; if (!find_btdev_by_bdaddr(cmd->bdaddr)) return -EPERM; cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_REJECT_SYNC_CONN_REQUEST); return 0; } static int cmd_reject_sync_conn_request_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_reject_sync_conn_request *cmd = data; struct btdev *remote = find_btdev_by_bdaddr(cmd->bdaddr); if (!remote) return 0; sync_conn_complete(remote, dev->bdaddr, cmd->reason); return 0; } static int cmd_read_ext_inquiry(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_ext_inquiry_response rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.fec = dev->ext_inquiry_fec; memcpy(rsp.data, dev->ext_inquiry_rsp, 240); cmd_complete(dev, BT_HCI_CMD_READ_EXT_INQUIRY_RESPONSE, &rsp, sizeof(rsp)); return 0; } static int cmd_write_ext_inquiry(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_ext_inquiry_response *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; dev->ext_inquiry_fec = cmd->fec; memcpy(dev->ext_inquiry_rsp, cmd->data, 240); cmd_complete(dev, BT_HCI_CMD_WRITE_EXT_INQUIRY_RESPONSE, &status, sizeof(status)); return 0; } static int cmd_read_ssp_mode(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_simple_pairing_mode rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.mode = dev->simple_pairing_mode; cmd_complete(dev, BT_HCI_CMD_READ_SIMPLE_PAIRING_MODE, &rsp, sizeof(rsp)); return 0; } static int cmd_write_ssp_mode(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_simple_pairing_mode *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; dev->simple_pairing_mode = cmd->mode; cmd_complete(dev, BT_HCI_CMD_WRITE_SIMPLE_PAIRING_MODE, &status, sizeof(status)); return 0; } static int cmd_read_oob_data(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_local_oob_data rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_OOB_DATA, &rsp, sizeof(rsp)); return 0; } static int cmd_read_inquiry_tx_power(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_inquiry_resp_tx_power rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.level = 0; cmd_complete(dev, BT_HCI_CMD_READ_INQUIRY_RESP_TX_POWER, &rsp, sizeof(rsp)); return 0; } static int cmd_write_inquiry_tx_power(struct btdev *dev, const void *data, uint8_t len) { return -ENOTSUP; } static int cmd_io_cap_reply(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_io_capability_request_reply *cmd = data; struct bt_hci_evt_io_capability_response ev; struct bt_hci_rsp_io_capability_request_reply rsp; struct btdev_conn *conn; uint8_t status; conn = queue_find(dev->conns, match_bdaddr, cmd->bdaddr); if (!conn) { status = BT_HCI_ERR_UNKNOWN_CONN_ID; goto done; } status = BT_HCI_ERR_SUCCESS; dev->io_cap = cmd->capability; dev->auth_req = cmd->authentication; memcpy(ev.bdaddr, dev->bdaddr, 6); ev.capability = cmd->capability; ev.oob_data = cmd->oob_data; ev.authentication = cmd->authentication; send_event(conn->link->dev, BT_HCI_EVT_IO_CAPABILITY_RESPONSE, &ev, sizeof(ev)); if (conn->link->dev->io_cap) { struct bt_hci_evt_user_confirm_request cfm; memcpy(cfm.bdaddr, dev->bdaddr, 6); cfm.passkey = 0; send_event(conn->link->dev, BT_HCI_EVT_USER_CONFIRM_REQUEST, &cfm, sizeof(cfm)); memcpy(cfm.bdaddr, cmd->bdaddr, 6); send_event(dev, BT_HCI_EVT_USER_CONFIRM_REQUEST, &cfm, sizeof(cfm)); } else { send_event(conn->link->dev, BT_HCI_EVT_IO_CAPABILITY_REQUEST, dev->bdaddr, 6); } done: rsp.status = status; memcpy(rsp.bdaddr, cmd->bdaddr, 6); cmd_complete(dev, BT_HCI_CMD_IO_CAPABILITY_REQUEST_REPLY, &rsp, sizeof(rsp)); return 0; } static void ssp_complete(struct btdev *btdev, const uint8_t *bdaddr, uint8_t status, bool wait) { struct bt_hci_evt_simple_pairing_complete iev, aev; struct btdev_conn *conn; struct btdev_conn *init, *accp; conn = queue_find(btdev->conns, match_bdaddr, bdaddr); if (!conn) return; btdev->ssp_status = status; btdev->ssp_auth_complete = true; if (!conn->link->dev->ssp_auth_complete && wait) return; if (status == BT_HCI_ERR_SUCCESS && conn->link->dev->ssp_status != BT_HCI_ERR_SUCCESS) status = conn->link->dev->ssp_status; iev.status = status; aev.status = status; if (btdev->auth_init) { init = conn; accp = conn->link; memcpy(iev.bdaddr, bdaddr, 6); memcpy(aev.bdaddr, btdev->bdaddr, 6); } else { init = conn->link; accp = conn; memcpy(iev.bdaddr, btdev->bdaddr, 6); memcpy(aev.bdaddr, bdaddr, 6); } send_event(init->dev, BT_HCI_EVT_SIMPLE_PAIRING_COMPLETE, &iev, sizeof(iev)); send_event(accp->dev, BT_HCI_EVT_SIMPLE_PAIRING_COMPLETE, &aev, sizeof(aev)); if (status == BT_HCI_ERR_SUCCESS) { link_key_notify(init->dev, iev.bdaddr, LINK_KEY_DUMMY); link_key_notify(accp->dev, aev.bdaddr, LINK_KEY_DUMMY); } auth_complete(init, status); } static int cmd_user_confirm_reply(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_user_confirm_request_reply rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.bdaddr, data, 6); cmd_complete(dev, BT_HCI_CMD_USER_CONFIRM_REQUEST_REPLY, &rsp, sizeof(rsp)); ssp_complete(dev, data, BT_HCI_ERR_SUCCESS, true); return 0; } static int cmd_user_confirm_negative_reply(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_user_confirm_request_neg_reply rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.bdaddr, data, 6); cmd_complete(dev, BT_HCI_CMD_USER_CONFIRM_REQUEST_NEG_REPLY, &rsp, sizeof(rsp)); ssp_complete(dev, data, BT_HCI_ERR_AUTH_FAILURE, true); return 0; } static int cmd_user_passkey_reply(struct btdev *dev, const void *data, uint8_t len) { return -ENOTSUP; } static int cmd_user_passkey_negative_reply(struct btdev *dev, const void *data, uint8_t len) { return -ENOTSUP; } static int cmd_io_cap_negative_reply(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_io_capability_request_neg_reply *cmd = data; struct bt_hci_rsp_io_capability_request_neg_reply rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.bdaddr, cmd->bdaddr, 6); cmd_complete(dev, BT_HCI_CMD_IO_CAPABILITY_REQUEST_NEG_REPLY, &rsp, sizeof(rsp)); ssp_complete(dev, cmd->bdaddr, BT_HCI_ERR_AUTH_FAILURE, false); return 0; } static int cmd_read_encrypt_key_size(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_read_encrypt_key_size *cmd = data; struct bt_hci_rsp_read_encrypt_key_size rsp; struct btdev_conn *conn; memset(&rsp, 0, sizeof(rsp)); rsp.handle = cmd->handle; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (conn) { rsp.status = BT_HCI_ERR_SUCCESS; rsp.key_size = 16; } else { rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; rsp.key_size = 0; } cmd_complete(dev, BT_HCI_CMD_READ_ENCRYPT_KEY_SIZE, &rsp, sizeof(rsp)); return 0; } static int cmd_read_data_block_size(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_data_block_size rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.max_acl_len = cpu_to_le16(dev->acl_mtu); rsp.block_len = cpu_to_le16(dev->acl_mtu); rsp.num_blocks = cpu_to_le16(dev->acl_max_pkt); cmd_complete(dev, BT_HCI_CMD_READ_DATA_BLOCK_SIZE, &rsp, sizeof(rsp)); return 0; } static int cmd_read_local_codecs(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_local_codecs *rsp; rsp = alloca(sizeof(*rsp) + 7); rsp->status = BT_HCI_ERR_SUCCESS; rsp->num_codecs = 0x06; rsp->codec[0] = 0x00; rsp->codec[1] = 0x01; rsp->codec[2] = 0x02; rsp->codec[3] = 0x03; rsp->codec[4] = 0x04; rsp->codec[5] = 0x05; rsp->codec[6] = 0x00; cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_CODECS, rsp, sizeof(*rsp) + 7); return 0; } static int cmd_get_mws_transport_config(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_get_mws_transport_config *rsp; rsp = alloca(sizeof(*rsp)); rsp->status = BT_HCI_ERR_SUCCESS; rsp->num_transports = 0x00; cmd_complete(dev, BT_HCI_CMD_GET_MWS_TRANSPORT_CONFIG, rsp, sizeof(*rsp)); return 0; } #define CMD_BREDR \ CMD(BT_HCI_CMD_SETUP_SYNC_CONN, cmd_setup_sync_conn, \ cmd_setup_sync_conn_complete), \ CMD(BT_HCI_CMD_ACCEPT_SYNC_CONN_REQUEST, cmd_accept_sync_conn_request, \ cmd_accept_sync_conn_request_complete), \ CMD(BT_HCI_CMD_REJECT_SYNC_CONN_REQUEST, cmd_reject_sync_conn_request, \ cmd_reject_sync_conn_request_complete), \ CMD(BT_HCI_CMD_READ_EXT_INQUIRY_RESPONSE, cmd_read_ext_inquiry, NULL), \ CMD(BT_HCI_CMD_WRITE_EXT_INQUIRY_RESPONSE, cmd_write_ext_inquiry, \ NULL), \ CMD(BT_HCI_CMD_READ_SIMPLE_PAIRING_MODE, cmd_read_ssp_mode, NULL), \ CMD(BT_HCI_CMD_WRITE_SIMPLE_PAIRING_MODE, cmd_write_ssp_mode, NULL), \ CMD(BT_HCI_CMD_READ_LOCAL_OOB_DATA, cmd_read_oob_data, NULL), \ CMD(BT_HCI_CMD_READ_INQUIRY_RESP_TX_POWER, cmd_read_inquiry_tx_power, \ NULL), \ CMD(BT_HCI_CMD_WRITE_INQUIRY_TX_POWER, cmd_write_inquiry_tx_power, \ NULL), \ CMD(BT_HCI_CMD_IO_CAPABILITY_REQUEST_REPLY, cmd_io_cap_reply, NULL), \ CMD(BT_HCI_CMD_USER_CONFIRM_REQUEST_REPLY, cmd_user_confirm_reply, \ NULL), \ CMD(BT_HCI_CMD_USER_CONFIRM_REQUEST_NEG_REPLY, \ cmd_user_confirm_negative_reply, \ NULL), \ CMD(BT_HCI_CMD_USER_PASSKEY_REQUEST_NEG_REPLY, cmd_user_passkey_reply, \ NULL), \ CMD(BT_HCI_CMD_USER_PASSKEY_REQUEST_NEG_REPLY, \ cmd_user_passkey_negative_reply, \ NULL), \ CMD(BT_HCI_CMD_IO_CAPABILITY_REQUEST_NEG_REPLY, \ cmd_io_cap_negative_reply, NULL), \ CMD(BT_HCI_CMD_READ_ENCRYPT_KEY_SIZE, cmd_read_encrypt_key_size, \ NULL), \ CMD(BT_HCI_CMD_READ_DATA_BLOCK_SIZE, cmd_read_data_block_size, NULL), \ CMD(BT_HCI_CMD_READ_LOCAL_CODECS, cmd_read_local_codecs, NULL), \ CMD(BT_HCI_CMD_GET_MWS_TRANSPORT_CONFIG, cmd_get_mws_transport_config, \ NULL), \ CMD(BT_HCI_CMD_ENHANCED_SETUP_SYNC_CONN, cmd_enhanced_setup_sync_conn, \ cmd_enhanced_setup_sync_conn_complete) static int cmd_set_event_mask_2(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_set_event_mask_page2 *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; memcpy(dev->event_mask_page2, cmd->mask, 8); cmd_complete(dev, BT_HCI_CMD_SET_EVENT_MASK_PAGE2, &status, sizeof(status)); return 0; } static int cmd_read_sync_train_params(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_sync_train_params rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.interval = cpu_to_le16(dev->sync_train_interval); rsp.timeout = cpu_to_le32(dev->sync_train_timeout); rsp.service_data = dev->sync_train_service_data; cmd_complete(dev, BT_HCI_CMD_READ_SYNC_TRAIN_PARAMS, &rsp, sizeof(rsp)); return 0; } static int cmd_read_sc_support(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_secure_conn_support rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.support = dev->secure_conn_support; cmd_complete(dev, BT_HCI_CMD_READ_SECURE_CONN_SUPPORT, &rsp, sizeof(rsp)); return 0; } static int cmd_write_sc_support(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_secure_conn_support *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; dev->secure_conn_support = cmd->support; cmd_complete(dev, BT_HCI_CMD_WRITE_SECURE_CONN_SUPPORT, &status, sizeof(status)); return 0; } static int cmd_read_auth_payload_timeout(struct btdev *dev, const void *data, uint8_t len) { /* TODO */ return -ENOTSUP; } static int cmd_write_auth_payload_timeout(struct btdev *dev, const void *data, uint8_t len) { /* TODO */ return -ENOTSUP; } static int cmd_read_local_oob_ext_data(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_local_oob_ext_data rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_OOB_EXT_DATA, &rsp, sizeof(rsp)); return 0; } #define CMD_COMMON_BREDR_LE_40 \ CMD(BT_HCI_CMD_SET_EVENT_MASK_PAGE2, cmd_set_event_mask_2, NULL), \ CMD(BT_HCI_CMD_READ_SYNC_TRAIN_PARAMS, cmd_read_sync_train_params, \ NULL), \ CMD(BT_HCI_CMD_READ_SECURE_CONN_SUPPORT, cmd_read_sc_support, NULL), \ CMD(BT_HCI_CMD_WRITE_SECURE_CONN_SUPPORT, cmd_write_sc_support, NULL), \ CMD(BT_HCI_CMD_READ_AUTH_PAYLOAD_TIMEOUT, \ cmd_read_auth_payload_timeout, NULL), \ CMD(BT_HCI_CMD_WRITE_AUTH_PAYLOAD_TIMEOUT, \ cmd_write_auth_payload_timeout, NULL), \ CMD(BT_HCI_CMD_READ_LOCAL_OOB_EXT_DATA, \ cmd_read_local_oob_ext_data, NULL) static const struct btdev_cmd cmd_bredr[] = { CMD_COMMON_ALL, CMD_COMMON_BREDR_LE, CMD_COMMON_BREDR_20, CMD_BREDR, {} }; static void set_bredr_commands(struct btdev *btdev) { set_common_commands_all(btdev); set_common_commands_bredrle(btdev); set_common_commands_bredr20(btdev); btdev->commands[16] |= 0x08; /* Setup Synchronous Connection */ btdev->commands[16] |= 0x10; /* Accept Synchronous Connection Req */ btdev->commands[16] |= 0x20; /* Reject Synchronous Connection Req */ btdev->commands[17] |= 0x01; /* Read Extended Inquiry Response */ btdev->commands[17] |= 0x02; /* Write Extended Inquiry Response */ btdev->commands[17] |= 0x20; /* Read Simple Pairing Mode */ btdev->commands[17] |= 0x40; /* Write Simple Pairing Mode */ btdev->commands[17] |= 0x80; /* Read Local OOB Data */ btdev->commands[18] |= 0x01; /* Read Inquiry Response TX Power */ btdev->commands[18] |= 0x02; /* Write Inquiry Response TX Power */ btdev->commands[18] |= 0x80; /* IO Capability Request Reply */ btdev->commands[19] |= 0x01; /* User Confirmation Request Reply */ btdev->commands[19] |= 0x02; /* User Confirmation Request N Reply */ btdev->commands[19] |= 0x04; /* User Passkey Request Reply */ btdev->commands[19] |= 0x08; /* User Passkey Request N Reply */ btdev->commands[20] |= 0x08; /* IO Capability Request N Reply */ btdev->commands[20] |= 0x10; /* Read Encryption Key Size */ btdev->commands[23] |= 0x04; /* Read Data Block Size */ btdev->commands[29] |= 0x20; /* Read Local Supported Codecs */ btdev->commands[29] |= 0x08; /* Enhanced Setup Synchronous Conn */ btdev->commands[30] |= 0x08; /* Get MWS Transport Layer Config */ btdev->cmds = cmd_bredr; } static const struct btdev_cmd cmd_bredr_20[] = { CMD_COMMON_ALL, CMD_COMMON_BREDR_LE, CMD_COMMON_BREDR_20, {} }; static void set_bredr20_commands(struct btdev *btdev) { set_common_commands_all(btdev); set_common_commands_bredrle(btdev); set_common_commands_bredr20(btdev); btdev->cmds = cmd_bredr_20; } static int cmd_read_le_host_supported(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_read_le_host_supported rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.supported = dev->le_supported; rsp.simultaneous = dev->le_simultaneous; cmd_complete(dev, BT_HCI_CMD_READ_LE_HOST_SUPPORTED, &rsp, sizeof(rsp)); return 0; } static int cmd_write_le_host_supported(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_write_le_host_supported *cmd = data; uint8_t status; dev->le_supported = cmd->supported; dev->le_simultaneous = cmd->simultaneous; status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_WRITE_LE_HOST_SUPPORTED, &status, sizeof(status)); return 0; } static int cmd_le_set_event_mask(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_event_mask *cmd = data; uint8_t status; memcpy(dev->le_event_mask, cmd->mask, 8); status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_LE_SET_EVENT_MASK, &status, sizeof(status)); return 0; } static int cmd_le_read_buffer_size(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_le_read_buffer_size rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.le_mtu = cpu_to_le16(dev->acl_mtu); rsp.le_max_pkt = dev->acl_max_pkt; cmd_complete(dev, BT_HCI_CMD_LE_READ_BUFFER_SIZE, &rsp, sizeof(rsp)); return 0; } static int cmd_le_read_local_features(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_le_read_local_features rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.features, dev->le_features, 8); cmd_complete(dev, BT_HCI_CMD_LE_READ_LOCAL_FEATURES, &rsp, sizeof(rsp)); return 0; } static int cmd_set_random_address(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_random_address *cmd = data; uint8_t status; /* If the Host issues this command when any of advertising * (created using legacy advertising commands), scanning, or initiating * are enabled, the Controller shall return the error code * Command Disallowed (0x0C). */ if (dev->le_scan_enable || (dev->le_adv_enable && queue_isempty(dev->le_ext_adv))) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } memcpy(dev->random_addr, cmd->addr, 6); status = BT_HCI_ERR_SUCCESS; done: cmd_complete(dev, BT_HCI_CMD_LE_SET_RANDOM_ADDRESS, &status, sizeof(status)); return 0; } static uint16_t ext_legacy_adv_type(uint8_t type) { switch (type) { case 0x00: /* Connectable undirected - ADV_IND" */ return 0x0013; case 0x01: /* Connectable directed - ADV_DIRECT_IND */ return 0x0015; case 0x02: /* Scannable undirected - ADV_SCAN_IND */ return 0x0012; case 0x03: /* Non connectable undirected - ADV_NONCONN_IND */ return 0x0010; case 0x04: /* Scan response - SCAN_RSP */ return 0x0012; } return 0x0000; } static int cmd_set_adv_params(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_adv_parameters *cmd = data; uint8_t status; if (dev->le_adv_enable) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } dev->le_adv_type = cmd->type; /* Use Legacy PDU if the remote is using EXT Scan */ dev->le_ext_adv_type = ext_legacy_adv_type(cmd->type); dev->le_adv_own_addr = cmd->own_addr_type; dev->le_adv_direct_addr_type = cmd->direct_addr_type; memcpy(dev->le_adv_direct_addr, cmd->direct_addr, 6); dev->le_adv_filter_policy = cmd->filter_policy; status = BT_HCI_ERR_SUCCESS; done: cmd_complete(dev, BT_HCI_CMD_LE_SET_ADV_PARAMETERS, &status, sizeof(status)); return 0; } static int cmd_read_adv_tx_power(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_le_read_adv_tx_power rsp; rsp.status = BT_HCI_ERR_SUCCESS; rsp.level = 0; cmd_complete(dev, BT_HCI_CMD_LE_READ_ADV_TX_POWER, &rsp, sizeof(rsp)); return 0; } static int cmd_set_adv_data(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_adv_data *cmd = data; uint8_t status; dev->le_adv_data_len = cmd->len; memcpy(dev->le_adv_data, cmd->data, 31); status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_LE_SET_ADV_DATA, &status, sizeof(status)); return 0; } static int cmd_set_scan_rsp_data(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_scan_rsp_data *cmd = data; uint8_t status; dev->le_scan_data_len = cmd->len; memcpy(dev->le_scan_data, cmd->data, 31); status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_LE_SET_SCAN_RSP_DATA, &status, sizeof(status)); return 0; } static uint8_t get_ext_adv_type(uint8_t ext_adv_type) { /* * If legacy bit is not set then just reset high duty cycle directed * bit. */ if (!(ext_adv_type & 0x10)) return (ext_adv_type & 0xf7); /* * Connectable low duty cycle directed advertising creates a * connectable directed advertising report type. */ if (ext_adv_type == 0x001d) return 0x0015; return ext_adv_type; } static const uint8_t *scan_addr(const struct btdev *btdev) { if (btdev->le_scan_own_addr_type == 0x01) return btdev->random_addr; return btdev->bdaddr; } static const uint8_t *adv_addr(const struct btdev *btdev) { if (btdev->le_adv_own_addr == 0x01) return btdev->random_addr; return btdev->bdaddr; } static void le_send_adv_report(struct btdev *btdev, const struct btdev *remote, uint8_t type) { struct __packed { uint8_t subevent; union { struct bt_hci_evt_le_adv_report lar; uint8_t raw[10 + 31 + 1]; }; } meta_event; meta_event.subevent = BT_HCI_EVT_LE_ADV_REPORT; memset(&meta_event.lar, 0, sizeof(meta_event.lar)); meta_event.lar.num_reports = 1; meta_event.lar.event_type = type; meta_event.lar.addr_type = remote->le_adv_own_addr; memcpy(meta_event.lar.addr, adv_addr(remote), 6); /* Scan or advertising response */ if (type == 0x04) { meta_event.lar.data_len = remote->le_scan_data_len; memcpy(meta_event.lar.data, remote->le_scan_data, meta_event.lar.data_len); } else { meta_event.lar.data_len = remote->le_adv_data_len; memcpy(meta_event.lar.data, remote->le_adv_data, meta_event.lar.data_len); } /* Not available */ meta_event.raw[10 + meta_event.lar.data_len] = 127; send_event(btdev, BT_HCI_EVT_LE_META_EVENT, &meta_event, 1 + 10 + meta_event.lar.data_len + 1); } static uint8_t get_adv_report_type(uint8_t adv_type) { /* * Connectable low duty cycle directed advertising creates a * connectable directed advertising report type. */ if (adv_type == 0x04) return 0x01; return adv_type; } static bool adv_match(struct btdev *scan, struct btdev *adv) { /* Match everything if this is not directed advertising */ if (adv->le_adv_type != 0x01 && adv->le_adv_type != 0x04) return true; if (scan->le_scan_own_addr_type != adv->le_adv_direct_addr_type) return false; return !memcmp(scan_addr(scan), adv->le_adv_direct_addr, 6); } static void le_set_adv_enable_complete(struct btdev *btdev) { uint8_t report_type; int i; report_type = get_adv_report_type(btdev->le_adv_type); for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { if (!btdev_list[i] || btdev_list[i] == btdev) continue; if (!btdev_list[i]->le_scan_enable) continue; if (!adv_match(btdev_list[i], btdev)) continue; le_send_adv_report(btdev_list[i], btdev, report_type); if (btdev_list[i]->le_scan_type != 0x01) continue; /* ADV_IND & ADV_SCAN_IND generate a scan response */ if (btdev->le_adv_type == 0x00 || btdev->le_adv_type == 0x02) le_send_adv_report(btdev_list[i], btdev, 0x04); } } #define RL_ADDR_EQUAL(_rl, _type, _addr) \ (_rl->type == _type && !bacmp(&_rl->addr, (bdaddr_t *)_addr)) static const struct btdev_rl *rl_find(const struct btdev *dev, uint8_t type, const uint8_t *addr) { unsigned int i; for (i = 0; i < ARRAY_SIZE(dev->le_rl); i++) { const struct btdev_rl *rl = &dev->le_rl[i]; if (RL_ADDR_EQUAL(rl, type, addr)) return rl; } return NULL; } static int cmd_set_adv_enable(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_ext_adv_enable *cmd = data; uint8_t status; bool random_addr; if (dev->le_adv_enable == cmd->enable) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } dev->le_adv_enable = cmd->enable; status = BT_HCI_ERR_SUCCESS; if (!cmd->enable) goto done; random_addr = bacmp((bdaddr_t *)dev->random_addr, BDADDR_ANY); /* If Advertising_Enable is set to 0x01, the advertising parameters' * Own_Address_Type parameter is set to 0x01, and the random address for * the device has not been initialized, the Controller shall return the * error code Invalid HCI Command Parameters (0x12). */ if (dev->le_adv_own_addr == 0x01 && !random_addr) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } /* If Advertising_Enable is set to 0x01, the advertising parameters' * Own_Address_Type parameter is set to 0x03, the controller's resolving * list did not contain a matching entry, and the random address for the * device has not been initialized, the Controller shall return the * error code Invalid HCI Command Parameters (0x12). */ if (dev->le_adv_own_addr == 0x03 && !random_addr) { if (!dev->le_rl_enable || !rl_find(dev, dev->le_adv_direct_addr_type, dev->le_adv_direct_addr)) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } } done: cmd_complete(dev, BT_HCI_CMD_LE_SET_ADV_ENABLE, &status, sizeof(status)); if (!status && dev->le_adv_enable) le_set_adv_enable_complete(dev); return 0; } static int cmd_set_scan_params(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_scan_parameters *cmd = data; uint8_t status; if (dev->le_scan_enable) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } status = BT_HCI_ERR_SUCCESS; dev->le_scan_type = cmd->type; dev->le_scan_own_addr_type = cmd->own_addr_type; dev->le_scan_filter_policy = cmd->filter_policy; done: cmd_complete(dev, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS, &status, sizeof(status)); return 0; } static int cmd_set_scan_enable(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_scan_enable *cmd = data; uint8_t status; if (dev->le_scan_enable == cmd->enable) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } /* If LE_Scan_Enable is set to 0x01, the scanning parameters' * Own_Address_Type parameter is set to 0x01 or 0x03, and the random * address for the device has not been initialized, the Controller shall * return the error code Invalid HCI Command Parameters (0x12). */ if ((dev->le_scan_own_addr_type == 0x01 || dev->le_scan_own_addr_type == 0x03) && !bacmp((bdaddr_t *)dev->random_addr, BDADDR_ANY)) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } dev->le_scan_enable = cmd->enable; dev->le_filter_dup = cmd->filter_dup; status = BT_HCI_ERR_SUCCESS; done: cmd_complete(dev, BT_HCI_CMD_LE_SET_SCAN_ENABLE, &status, sizeof(status)); return 0; } static int cmd_set_scan_enable_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_scan_enable *cmd = data; int i; if (!dev->le_scan_enable || !cmd->enable) return 0; for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { uint8_t report_type; if (!btdev_list[i] || btdev_list[i] == dev) continue; if (!btdev_list[i]->le_adv_enable) continue; if (!adv_match(dev, btdev_list[i])) continue; report_type = get_adv_report_type(btdev_list[i]->le_adv_type); le_send_adv_report(dev, btdev_list[i], report_type); if (dev->le_scan_type != 0x01) continue; /* ADV_IND & ADV_SCAN_IND generate a scan response */ if (btdev_list[i]->le_adv_type == 0x00 || btdev_list[i]->le_adv_type == 0x02) le_send_adv_report(dev, btdev_list[i], 0x04); } return 0; } static bool adv_connectable(struct btdev *btdev) { if (!btdev->le_adv_enable) return false; return btdev->le_adv_type != 0x03; } static void le_meta_event(struct btdev *btdev, uint8_t event, void *data, uint8_t len) { void *pkt_data; util_debug(btdev->debug_callback, btdev->debug_data, "meta event 0x%02x", event); pkt_data = alloca(1 + len); if (!pkt_data) return; ((uint8_t *) pkt_data)[0] = event; if (len > 0) memcpy(pkt_data + 1, data, len); send_event(btdev, BT_HCI_EVT_LE_META_EVENT, pkt_data, 1 + len); } static void le_conn_complete(struct btdev *btdev, const struct bt_hci_cmd_le_create_conn *lecc, uint8_t status) { struct bt_hci_evt_le_conn_complete cc; memset(&cc, 0, sizeof(cc)); if (!status) { struct btdev_conn *conn; conn = conn_add_acl(btdev, lecc->peer_addr, lecc->peer_addr_type); if (!conn) return; btdev->le_adv_enable = 0; conn->link->dev->le_adv_enable = 0; cc.status = status; cc.peer_addr_type = btdev->le_scan_own_addr_type; if (cc.peer_addr_type == 0x01) memcpy(cc.peer_addr, btdev->random_addr, 6); else memcpy(cc.peer_addr, btdev->bdaddr, 6); cc.role = 0x01; cc.handle = cpu_to_le16(conn->handle); cc.interval = lecc->max_interval; cc.latency = lecc->latency; cc.supv_timeout = lecc->supv_timeout; le_meta_event(conn->link->dev, BT_HCI_EVT_LE_CONN_COMPLETE, &cc, sizeof(cc)); } cc.status = status; cc.peer_addr_type = lecc->peer_addr_type; memcpy(cc.peer_addr, lecc->peer_addr, 6); cc.role = 0x00; le_meta_event(btdev, BT_HCI_EVT_LE_CONN_COMPLETE, &cc, sizeof(cc)); } static int cmd_le_create_conn(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_CREATE_CONN); return 0; } static int cmd_le_create_conn_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_create_conn *cmd = data; struct btdev *remote; dev->le_scan_own_addr_type = cmd->own_addr_type; remote = find_btdev_by_bdaddr_type(cmd->peer_addr, cmd->peer_addr_type); if (remote && adv_connectable(remote) && adv_match(dev, remote) && remote->le_adv_own_addr == cmd->peer_addr_type) le_conn_complete(dev, cmd, 0); else le_conn_complete(dev, cmd, BT_HCI_ERR_CONN_FAILED_TO_ESTABLISH); return 0; } static int cmd_le_create_conn_cancel(struct btdev *dev, const void *data, uint8_t len) { uint8_t status = BT_HCI_ERR_COMMAND_DISALLOWED; cmd_complete(dev, BT_HCI_CMD_LE_CREATE_CONN_CANCEL, &status, sizeof(status)); return 0; } static int cmd_read_al_size(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_le_read_accept_list_size rsp; rsp.status = BT_HCI_ERR_SUCCESS; rsp.size = dev->le_al_len; cmd_complete(dev, BT_HCI_CMD_LE_READ_ACCEPT_LIST_SIZE, &rsp, sizeof(rsp)); return 0; } static bool al_can_change(struct btdev *dev) { /* filter policy uses the Accept List and advertising is enable. */ if (dev->le_adv_enable && dev->le_adv_filter_policy) return false; /* scan filter policy uses the Accept List and scanning is enabled */ if (dev->le_scan_enable) { switch (dev->le_scan_filter_policy) { case 0x00: return true; case 0x01: return false; case 0x02: return true; case 0x03: return false; } } return true; } static int cmd_al_clear(struct btdev *dev, const void *data, uint8_t len) { uint8_t status; /* This command shall not be used when: * • any advertising filter policy uses the Accept List and * advertising is enabled, * • the scanning filter policy uses the Accept List and scanning is * enabled, or * • the initiator filter policy uses the Accept List and an * HCI_LE_Create_Connection or HCI_LE_Extended_Create_Connection * command is outstanding. */ if (!al_can_change(dev)) return -EPERM; al_clear(dev); status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_LE_CLEAR_ACCEPT_LIST, &status, sizeof(status)); return 0; } #define AL_ADDR_EQUAL(_al, _type, _addr) \ (_al->type == _type && !bacmp(&_al->addr, (bdaddr_t *)_addr)) static void al_add(struct btdev_al *al, uint8_t type, bdaddr_t *addr) { al->type = type; bacpy(&al->addr, addr); } static int cmd_add_al(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_add_to_accept_list *cmd = data; uint8_t status; bool exists = false; int i, pos = -1; /* This command shall not be used when: * • any advertising filter policy uses the Accept List and * advertising is enabled, * • the scanning filter policy uses the Accept List and scanning is * enabled, or * • the initiator filter policy uses the Accept List and an * HCI_LE_Create_Connection or HCI_LE_Extended_Create_Connection * command is outstanding. */ if (!al_can_change(dev)) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } /* Valid range for address type is 0x00 to 0x01 */ if (cmd->addr_type > 0x01) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } for (i = 0; i < dev->le_al_len; i++) { struct btdev_al *al = &dev->le_al[i]; if (AL_ADDR_EQUAL(al, cmd->addr_type, &cmd->addr)) { exists = true; break; } else if (pos < 0 && al->type == 0xff) pos = i; } /* If the device is already in the Filter Accept List, the Controller * should not add the device to the Filter Accept List again and should * return success. */ if (exists) { status = BT_HCI_ERR_SUCCESS; goto done; } if (pos < 0) { status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; goto done; } al_add(&dev->le_al[pos], cmd->addr_type, (bdaddr_t *)&cmd->addr); status = BT_HCI_ERR_SUCCESS; done: cmd_complete(dev, BT_HCI_CMD_LE_ADD_TO_ACCEPT_LIST, &status, sizeof(status)); return 0; } static int cmd_remove_al(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_remove_from_accept_list *cmd = data; uint8_t status; int i; char addr[18]; /* This command shall not be used when: * • any advertising filter policy uses the Accept List and * advertising is enabled, * • the scanning filter policy uses the Accept List and scanning is * enabled, or * • the initiator filter policy uses the Accept List and an * HCI_LE_Create_Connection or HCI_LE_Extended_Create_Connection * command is outstanding. */ if (!al_can_change(dev)) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } /* Valid range for address type is 0x00 to 0x01 */ if (cmd->addr_type > 0x01) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } for (i = 0; i < dev->le_al_len; i++) { struct btdev_al *al = &dev->le_al[i]; ba2str(&al->addr, addr); util_debug(dev->debug_callback, dev->debug_data, "type 0x%02x addr %s", dev->le_al[i].type, addr); if (AL_ADDR_EQUAL(al, cmd->addr_type, &cmd->addr)) { al_reset(al); break; } } if (i == dev->le_al_len) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } status = BT_HCI_ERR_SUCCESS; done: cmd_complete(dev, BT_HCI_CMD_LE_REMOVE_FROM_ACCEPT_LIST, &status, sizeof(status)); return 0; } static int cmd_add_rl(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_add_to_resolv_list *cmd = data; uint8_t status; bool exists = false; int i, pos = -1; /* This command shall not be used when address resolution is enabled in * the Controller and: * • Advertising (other than periodic advertising) is enabled, * • Scanning is enabled, or * • an HCI_LE_Create_Connection, HCI_LE_Extended_Create_Connection, * or HCI_LE_Periodic_Advertising_Create_Sync command is outstanding. */ if (dev->le_adv_enable || dev->le_scan_enable) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } /* Valid range for address type is 0x00 to 0x01 */ if (cmd->addr_type > 0x01) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } for (i = 0; i < dev->le_rl_len; i++) { struct btdev_rl *rl = &dev->le_rl[i]; if (RL_ADDR_EQUAL(rl, cmd->addr_type, &cmd->addr)) { exists = true; break; } else if (pos < 0 && rl->type == 0xff) pos = i; } /* If an entry already exists in the resolving list with the same four * parameter values, the Controller shall either reject the command or * not add the device to the resolving list again and return success. */ if (exists) { status = BT_HCI_ERR_SUCCESS; goto done; } if (pos < 0) { status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; goto done; } dev->le_rl[pos].type = cmd->addr_type; bacpy(&dev->le_rl[pos].addr, (bdaddr_t *)&cmd->addr); memcpy(dev->le_rl[pos].peer_irk, cmd->peer_irk, 16); memcpy(dev->le_rl[pos].local_irk, cmd->local_irk, 16); status = BT_HCI_ERR_SUCCESS; done: cmd_complete(dev, BT_HCI_CMD_LE_ADD_TO_RESOLV_LIST, &status, sizeof(status)); return 0; } static int cmd_remove_rl(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_remove_from_resolv_list *cmd = data; uint8_t status; int i; /* This command shall not be used when address resolution is enabled in * the Controller and: * • Advertising (other than periodic advertising) is enabled, * • Scanning is enabled, or * • an HCI_LE_Create_Connection, HCI_LE_Extended_Create_Connection, * or HCI_LE_Periodic_Advertising_Create_Sync command is outstanding. */ if (dev->le_adv_enable || dev->le_scan_enable) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } /* Valid range for address type is 0x00 to 0x01 */ if (cmd->addr_type > 0x01) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } for (i = 0; i < dev->le_rl_len; i++) { struct btdev_rl *rl = &dev->le_rl[i]; if (RL_ADDR_EQUAL(rl, cmd->addr_type, &cmd->addr)) { rl_reset(rl); break; } } if (i == dev->le_rl_len) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } status = BT_HCI_ERR_SUCCESS; done: cmd_complete(dev, BT_HCI_CMD_LE_REMOVE_FROM_RESOLV_LIST, &status, sizeof(status)); return 0; } static int cmd_clear_rl(struct btdev *dev, const void *data, uint8_t len) { uint8_t status; /* This command shall not be used when address resolution is enabled in * the Controller and: * • Advertising (other than periodic advertising) is enabled, * • Scanning is enabled, or * • an HCI_LE_Create_Connection, HCI_LE_Extended_Create_Connection, * or HCI_LE_Periodic_Advertising_Create_Sync command is outstanding. */ if (dev->le_adv_enable || dev->le_scan_enable) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } rl_clear(dev); status = BT_HCI_ERR_SUCCESS; done: cmd_complete(dev, BT_HCI_CMD_LE_CLEAR_RESOLV_LIST, &status, sizeof(status)); return 0; } static int cmd_read_rl_size(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_le_read_resolv_list_size rsp; rsp.status = BT_HCI_ERR_SUCCESS; rsp.size = dev->le_rl_len; cmd_complete(dev, BT_HCI_CMD_LE_READ_RESOLV_LIST_SIZE, &rsp, sizeof(rsp)); return 0; } static int cmd_read_peer_rl_addr(struct btdev *dev, const void *data, uint8_t size) { const struct bt_hci_cmd_le_read_peer_resolv_addr *cmd = data; struct bt_hci_rsp_le_read_peer_resolv_addr rsp; /* Valid range for address type is 0x00 to 0x01 */ if (cmd->addr_type > 0x01) { rsp.status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; memset(rsp.addr, 0, 6); done: cmd_complete(dev, BT_HCI_CMD_LE_READ_PEER_RESOLV_ADDR, &rsp, sizeof(rsp)); return 0; } static int cmd_read_local_rl_addr(struct btdev *dev, const void *data, uint8_t size) { const struct bt_hci_cmd_le_read_local_resolv_addr *cmd = data; struct bt_hci_rsp_le_read_local_resolv_addr rsp; /* Valid range for address type is 0x00 to 0x01 */ if (cmd->addr_type > 0x01) { rsp.status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; memset(rsp.addr, 0, 6); done: cmd_complete(dev, BT_HCI_CMD_LE_READ_LOCAL_RESOLV_ADDR, &rsp, sizeof(rsp)); return 0; } static int cmd_set_rl_enable(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_resolv_enable *cmd = data; uint8_t status; /* This command shall not be used when address resolution is enabled in * the Controller and: * • Advertising (other than periodic advertising) is enabled, * • Scanning is enabled, or * • an HCI_LE_Create_Connection, HCI_LE_Extended_Create_Connection, * or HCI_LE_Periodic_Advertising_Create_Sync command is outstanding. */ if ((dev->le_adv_enable && !dev->le_pa_enable) || dev->le_scan_enable) return -EPERM; /* Valid range for address resolution enable is 0x00 to 0x01 */ if (cmd->enable > 0x01) return -EINVAL; dev->le_rl_enable = cmd->enable; status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_LE_SET_RESOLV_ENABLE, &status, sizeof(status)); return 0; } static int cmd_set_rl_timeout(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_resolv_timeout *cmd = data; uint16_t timeout; uint8_t status; timeout = le16_to_cpu(cmd->timeout); /* Valid range for RPA timeout is 0x0001 to 0xa1b8 */ if (timeout < 0x0001 || timeout > 0xa1b8) return -EINVAL; dev->le_rl_timeout = timeout; status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_LE_SET_RESOLV_TIMEOUT, &status, sizeof(status)); return 0; } static int cmd_conn_update(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_CONN_UPDATE); return 0; } static void le_conn_update(struct btdev *btdev, uint16_t handle, uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t supv_timeout, uint16_t min_length, uint16_t max_length) { struct bt_hci_evt_le_conn_update_complete ev; struct btdev_conn *conn; memset(&ev, 0, sizeof(ev)); ev.handle = cpu_to_le16(handle); ev.interval = cpu_to_le16(min_interval); ev.latency = cpu_to_le16(latency); ev.supv_timeout = cpu_to_le16(supv_timeout); conn = queue_find(btdev->conns, match_handle, UINT_TO_PTR(handle)); if (conn) ev.status = BT_HCI_ERR_SUCCESS; else ev.status = BT_HCI_ERR_UNKNOWN_CONN_ID; le_meta_event(btdev, BT_HCI_EVT_LE_CONN_UPDATE_COMPLETE, &ev, sizeof(ev)); if (conn) le_meta_event(conn->link->dev, BT_HCI_EVT_LE_CONN_UPDATE_COMPLETE, &ev, sizeof(ev)); } static void le_conn_param_req(struct btdev *btdev, uint16_t handle, uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t supv_timeout, uint16_t min_length, uint16_t max_length) { struct bt_hci_evt_le_conn_param_request ev; struct btdev_conn *conn; conn = queue_find(btdev->conns, match_handle, UINT_TO_PTR(handle)); if (!conn) return; memset(&ev, 0, sizeof(ev)); ev.handle = cpu_to_le16(handle); ev.min_interval = cpu_to_le16(min_interval); ev.max_interval = cpu_to_le16(max_interval); ev.latency = cpu_to_le16(latency); ev.supv_timeout = cpu_to_le16(supv_timeout); le_meta_event(conn->link->dev, BT_HCI_EVT_LE_CONN_PARAM_REQUEST, &ev, sizeof(ev)); } static int cmd_conn_update_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_conn_update *cmd = data; if (dev->le_features[0] & 0x02) le_conn_param_req(dev, le16_to_cpu(cmd->handle), le16_to_cpu(cmd->min_interval), le16_to_cpu(cmd->max_interval), le16_to_cpu(cmd->latency), le16_to_cpu(cmd->supv_timeout), le16_to_cpu(cmd->min_length), le16_to_cpu(cmd->max_length)); else le_conn_update(dev, le16_to_cpu(cmd->handle), le16_to_cpu(cmd->min_interval), le16_to_cpu(cmd->max_interval), le16_to_cpu(cmd->latency), le16_to_cpu(cmd->supv_timeout), le16_to_cpu(cmd->min_length), le16_to_cpu(cmd->max_length)); return 0; } static int cmd_le_read_remote_features(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_read_remote_features *cmd = data; struct bt_hci_evt_le_remote_features_complete ev; struct btdev_conn *conn; uint8_t status = BT_HCI_ERR_SUCCESS; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (!conn) status = BT_HCI_ERR_UNKNOWN_CONN_ID; cmd_status(dev, status, BT_HCI_CMD_LE_READ_REMOTE_FEATURES); if (status) return 0; memset(&ev, 0, sizeof(ev)); ev.status = BT_HCI_ERR_SUCCESS; ev.handle = cpu_to_le16(conn->handle); memcpy(ev.features, conn->link->dev->le_features, 8); le_meta_event(dev, BT_HCI_EVT_LE_REMOTE_FEATURES_COMPLETE, &ev, sizeof(ev)); return 0; } static int cmd_encrypt(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_encrypt *cmd = data; struct bt_hci_rsp_le_encrypt rsp; if (!bt_crypto_e(dev->crypto, cmd->key, cmd->plaintext, rsp.data)) { cmd_status(dev, BT_HCI_ERR_COMMAND_DISALLOWED, BT_HCI_CMD_LE_ENCRYPT); return 0; } rsp.status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_LE_ENCRYPT, &rsp, sizeof(rsp)); return 0; } static int cmd_rand(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_le_rand rsp; if (!bt_crypto_random_bytes(dev->crypto, (uint8_t *)&rsp.number, 8)) { cmd_status(dev, BT_HCI_ERR_COMMAND_DISALLOWED, BT_HCI_CMD_LE_RAND); return 0; } rsp.status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_LE_RAND, &rsp, sizeof(rsp)); return 0; } static int cmd_start_encrypt(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_start_encrypt *cmd = data; struct bt_hci_evt_le_long_term_key_request ev; struct btdev_conn *conn; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (!conn) { cmd_status(dev, BT_HCI_ERR_UNKNOWN_CONN_ID, BT_HCI_CMD_LE_START_ENCRYPT); return 0; } cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_START_ENCRYPT); memcpy(dev->le_ltk, cmd->ltk, 16); ev.handle = cpu_to_le16(conn->handle); ev.ediv = cmd->ediv; ev.rand = cmd->rand; le_meta_event(conn->link->dev, BT_HCI_EVT_LE_LONG_TERM_KEY_REQUEST, &ev, sizeof(ev)); return 0; } static int cmd_ltk_reply(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_ltk_req_reply *cmd = data; struct bt_hci_rsp_le_ltk_req_reply rp; struct btdev_conn *conn; uint8_t mode, status; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (!conn) { rp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; cmd_complete(dev, BT_HCI_CMD_LE_LTK_REQ_REPLY, &rp, sizeof(rp)); return 0; } memcpy(dev->le_ltk, cmd->ltk, 16); memset(&rp, 0, sizeof(rp)); rp.handle = cpu_to_le16(conn->handle); rp.status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_LE_LTK_REQ_REPLY, &rp, sizeof(rp)); if (memcmp(dev->le_ltk, conn->link->dev->le_ltk, 16)) { status = BT_HCI_ERR_AUTH_FAILURE; mode = 0x00; } else { status = BT_HCI_ERR_SUCCESS; mode = 0x01; } encrypt_change(conn, mode, status); encrypt_change(conn->link, mode, status); return 0; } static int cmd_ltk_neg_reply(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_ltk_req_neg_reply *cmd = data; struct bt_hci_rsp_le_ltk_req_neg_reply rp; struct btdev_conn *conn; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (!conn) { rp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; cmd_complete(dev, BT_HCI_CMD_LE_LTK_REQ_NEG_REPLY, &rp, sizeof(rp)); return 0; } memset(&rp, 0, sizeof(rp)); rp.handle = cpu_to_le16(conn->handle); rp.status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_LE_LTK_REQ_NEG_REPLY, &rp, sizeof(rp)); encrypt_change(conn->link, 0x00, BT_HCI_ERR_PIN_OR_KEY_MISSING); return 0; } static int cmd_le_read_supported_states(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_le_read_supported_states rsp; memset(&rsp, 0, sizeof(0)); rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.states, dev->le_states, 8); cmd_complete(dev, BT_HCI_CMD_LE_READ_SUPPORTED_STATES, &rsp, sizeof(rsp)); return 0; } static int cmd_rx_test(struct btdev *dev, const void *data, uint8_t len) { return -ENOTSUP; } static int cmd_tx_test(struct btdev *dev, const void *data, uint8_t len) { return -ENOTSUP; } static int cmd_test_end(struct btdev *dev, const void *data, uint8_t len) { return -ENOTSUP; } static int cmd_conn_param_reply(struct btdev *dev, const void *data, uint8_t len) { uint8_t status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_LE_CONN_PARAM_REQ_REPLY, &status, sizeof(status)); return 0; } static int cmd_conn_param_reply_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_conn_param_req_reply *cmd = data; le_conn_update(dev, le16_to_cpu(cmd->handle), le16_to_cpu(cmd->min_interval), le16_to_cpu(cmd->max_interval), le16_to_cpu(cmd->latency), le16_to_cpu(cmd->supv_timeout), le16_to_cpu(cmd->min_length), le16_to_cpu(cmd->max_length)); return 0; } static int cmd_conn_param_neg_reply(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_conn_param_req_neg_reply *cmd = data; struct bt_hci_rsp_le_conn_param_req_neg_reply rsp; memset(&rsp, 0, sizeof(rsp)); rsp.handle = cmd->handle; rsp.status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_LE_CONN_PARAM_REQ_NEG_REPLY, &rsp, sizeof(rsp)); return 0; } static int cmd_conn_param_neg_reply_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_conn_param_req_neg_reply *cmd = data; struct btdev_conn *conn; struct bt_hci_evt_le_conn_update_complete ev; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (!conn) return 0; memset(&ev, 0, sizeof(ev)); ev.handle = cpu_to_le16(cmd->handle); ev.status = cpu_to_le16(cmd->reason); le_meta_event(conn->link->dev, BT_HCI_EVT_LE_CONN_UPDATE_COMPLETE, &ev, sizeof(ev)); return 0; } static int cmd_read_local_pk256(struct btdev *dev, const void *data, uint8_t len) { return -ENOTSUP; } static int cmd_gen_dhkey(struct btdev *dev, const void *data, uint8_t len) { return -ENOTSUP; } #define CMD_LE \ CMD(BT_HCI_CMD_READ_LE_HOST_SUPPORTED, cmd_read_le_host_supported, \ NULL), \ CMD(BT_HCI_CMD_WRITE_LE_HOST_SUPPORTED, cmd_write_le_host_supported, \ NULL), \ CMD(BT_HCI_CMD_LE_SET_EVENT_MASK, cmd_le_set_event_mask, NULL), \ CMD(BT_HCI_CMD_LE_READ_BUFFER_SIZE, cmd_le_read_buffer_size, NULL), \ CMD(BT_HCI_CMD_LE_READ_LOCAL_FEATURES, cmd_le_read_local_features, \ NULL), \ CMD(BT_HCI_CMD_LE_SET_RANDOM_ADDRESS, cmd_set_random_address, NULL), \ CMD(BT_HCI_CMD_LE_SET_ADV_PARAMETERS, cmd_set_adv_params, NULL), \ CMD(BT_HCI_CMD_LE_READ_ADV_TX_POWER, cmd_read_adv_tx_power, NULL), \ CMD(BT_HCI_CMD_LE_SET_ADV_DATA, cmd_set_adv_data, NULL), \ CMD(BT_HCI_CMD_LE_SET_SCAN_RSP_DATA, cmd_set_scan_rsp_data, NULL), \ CMD(BT_HCI_CMD_LE_SET_ADV_ENABLE, cmd_set_adv_enable, NULL), \ CMD(BT_HCI_CMD_LE_SET_SCAN_PARAMETERS, cmd_set_scan_params, NULL), \ CMD(BT_HCI_CMD_LE_SET_SCAN_ENABLE, cmd_set_scan_enable, \ cmd_set_scan_enable_complete), \ CMD(BT_HCI_CMD_LE_CREATE_CONN, cmd_le_create_conn, \ cmd_le_create_conn_complete), \ CMD(BT_HCI_CMD_LE_CREATE_CONN_CANCEL, cmd_le_create_conn_cancel, \ NULL), \ CMD(BT_HCI_CMD_LE_READ_ACCEPT_LIST_SIZE, cmd_read_al_size, NULL), \ CMD(BT_HCI_CMD_LE_CLEAR_ACCEPT_LIST, cmd_al_clear, NULL), \ CMD(BT_HCI_CMD_LE_ADD_TO_ACCEPT_LIST, cmd_add_al, NULL), \ CMD(BT_HCI_CMD_LE_REMOVE_FROM_ACCEPT_LIST, cmd_remove_al, NULL), \ CMD(BT_HCI_CMD_LE_CONN_UPDATE, cmd_conn_update, \ cmd_conn_update_complete), \ CMD(BT_HCI_CMD_LE_READ_REMOTE_FEATURES, cmd_le_read_remote_features, \ NULL), \ CMD(BT_HCI_CMD_LE_ENCRYPT, cmd_encrypt, NULL), \ CMD(BT_HCI_CMD_LE_RAND, cmd_rand, NULL), \ CMD(BT_HCI_CMD_LE_START_ENCRYPT, cmd_start_encrypt, NULL), \ CMD(BT_HCI_CMD_LE_LTK_REQ_REPLY, cmd_ltk_reply, NULL), \ CMD(BT_HCI_CMD_LE_LTK_REQ_NEG_REPLY, cmd_ltk_neg_reply, NULL), \ CMD(BT_HCI_CMD_LE_READ_SUPPORTED_STATES, cmd_le_read_supported_states, \ NULL), \ CMD(BT_HCI_CMD_LE_RECEIVER_TEST, cmd_rx_test, NULL), \ CMD(BT_HCI_CMD_LE_TRANSMITTER_TEST, cmd_tx_test, NULL), \ CMD(BT_HCI_CMD_LE_ISO_TEST_END, cmd_test_end, NULL), \ CMD(BT_HCI_CMD_LE_CONN_PARAM_REQ_REPLY, cmd_conn_param_reply, \ cmd_conn_param_reply_complete), \ CMD(BT_HCI_CMD_LE_CONN_PARAM_REQ_NEG_REPLY, cmd_conn_param_neg_reply, \ cmd_conn_param_neg_reply_complete), \ CMD(BT_HCI_CMD_LE_READ_LOCAL_PK256, cmd_read_local_pk256, NULL), \ CMD(BT_HCI_CMD_LE_GENERATE_DHKEY, cmd_gen_dhkey, NULL), \ CMD(BT_HCI_CMD_LE_ADD_TO_RESOLV_LIST, cmd_add_rl, NULL), \ CMD(BT_HCI_CMD_LE_REMOVE_FROM_RESOLV_LIST, cmd_remove_rl, NULL), \ CMD(BT_HCI_CMD_LE_CLEAR_RESOLV_LIST, cmd_clear_rl, NULL), \ CMD(BT_HCI_CMD_LE_READ_RESOLV_LIST_SIZE, cmd_read_rl_size, NULL), \ CMD(BT_HCI_CMD_LE_READ_PEER_RESOLV_ADDR, cmd_read_peer_rl_addr, NULL), \ CMD(BT_HCI_CMD_LE_READ_LOCAL_RESOLV_ADDR, cmd_read_local_rl_addr, \ NULL), \ CMD(BT_HCI_CMD_LE_SET_RESOLV_ENABLE, cmd_set_rl_enable, NULL), \ CMD(BT_HCI_CMD_LE_SET_RESOLV_TIMEOUT, cmd_set_rl_timeout, NULL) static const struct btdev_cmd cmd_bredr_le[] = { CMD_COMMON_ALL, CMD_COMMON_BREDR_LE, CMD_COMMON_BREDR_20, CMD_COMMON_BREDR_LE_40, CMD_BREDR, CMD_LE, {} }; static void set_bredrle_40_commands(struct btdev *btdev) { /* Extra BR/EDR commands we want to only support for >= 4.0 * adapters. */ btdev->commands[22] |= 0x04; /* Set Event Mask Page 2 */ btdev->commands[31] |= 0x80; /* Read Sync Train Parameters */ btdev->commands[32] |= 0x04; /* Read Secure Connections Support */ btdev->commands[32] |= 0x08; /* Write Secure Connections Support */ btdev->commands[32] |= 0x10; /* Read Auth Payload Timeout */ btdev->commands[32] |= 0x20; /* Write Auth Payload Timeout */ btdev->commands[32] |= 0x40; /* Read Local OOB Extended Data */ btdev->cmds = cmd_bredr_le; } static int cmd_set_default_phy(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_default_phy *cmd = data; uint8_t status; if (cmd->all_phys > 0x03 || (!(cmd->all_phys & 0x01) && (!cmd->tx_phys || cmd->tx_phys > 0x07)) || (!(cmd->all_phys & 0x02) && (!cmd->rx_phys || cmd->rx_phys > 0x07))) status = BT_HCI_ERR_INVALID_PARAMETERS; else status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_LE_SET_DEFAULT_PHY, &status, sizeof(status)); return 0; } static int cmd_le_set_phy(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_phy *cmd = data; struct btdev_conn *conn; uint8_t status; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(cpu_to_le16(cmd->handle))); if (!conn) { status = BT_HCI_ERR_UNKNOWN_CONN_ID; goto done; } if (cmd->all_phys > 0x03 || (!(cmd->all_phys & 0x01) && (!cmd->tx_phys || cmd->tx_phys > 0x07)) || (!(cmd->all_phys & 0x02) && (!cmd->rx_phys || cmd->rx_phys > 0x07))) status = BT_HCI_ERR_INVALID_PARAMETERS; else status = BT_HCI_ERR_SUCCESS; done: cmd_status(dev, status, BT_HCI_CMD_LE_SET_PHY); return 0; } static int cmd_le_set_phy_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_phy *cmd = data; struct bt_hci_evt_le_phy_update_complete ev; struct btdev_conn *conn; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(cpu_to_le16(cmd->handle))); if (!conn) return 0; if (cmd->all_phys > 0x03 || (!(cmd->all_phys & 0x01) && (!cmd->tx_phys || cmd->tx_phys > 0x07)) || (!(cmd->all_phys & 0x02) && (!cmd->rx_phys || cmd->rx_phys > 0x07))) return 0; memset(&ev, 0, sizeof(ev)); ev.handle = cmd->handle; /* Use the highest PHY possible */ if (cmd->tx_phys & BIT(0)) ev.tx_phy = 0x01; /* LE 1M PHY */ if (cmd->rx_phys & BIT(0)) ev.rx_phy = 0x01; /* LE 1M PHY */ if (cmd->tx_phys & BIT(1)) ev.tx_phy |= 0x02; /* LE 2M PHY */ if (cmd->rx_phys & BIT(1)) ev.rx_phy |= 0x02; /* LE 2M PHY */ if (cmd->tx_phys & BIT(2)) ev.tx_phy |= 0x03; /* LE CODED PHY */ if (cmd->rx_phys & BIT(2)) ev.rx_phy |= 0x03; /* LE CODED PHY */ le_meta_event(dev, BT_HCI_EVT_LE_PHY_UPDATE_COMPLETE, &ev, sizeof(ev)); return 0; } static const uint8_t *ext_adv_gen_rpa(const struct btdev *dev, struct le_ext_adv *adv) { const struct btdev_rl *rl; if (adv->rpa) return adv->random_addr; /* Lookup for Local IRK in the resolving list */ rl = rl_find(dev, adv->direct_addr_type, adv->direct_addr); if (rl) { uint8_t rpa[6]; bt_crypto_random_bytes(dev->crypto, rpa + 3, 3); rpa[5] &= 0x3f; /* Clear two most significant bits */ rpa[5] |= 0x40; /* Set second most significant bit */ bt_crypto_ah(dev->crypto, rl->peer_irk, rpa + 3, rpa); memcpy(adv->random_addr, rpa, sizeof(rpa)); adv->rpa = true; } return adv->random_addr; } static const uint8_t *ext_adv_addr(const struct btdev *btdev, struct le_ext_adv *ext_adv) { if (ext_adv->own_addr_type == 0x01) return ext_adv->random_addr; if (ext_adv->own_addr_type == 0x03) return ext_adv_gen_rpa(btdev, ext_adv); return btdev->bdaddr; } static bool ext_adv_match_addr(const struct btdev *btdev, struct le_ext_adv *ext_adv) { /* Match everything if this is not directed advertising */ if (!(ext_adv->type & 0x04)) return true; if (btdev->le_scan_own_addr_type != ext_adv->direct_addr_type) return false; return !memcmp(scan_addr(btdev), ext_adv->direct_addr, 6); } static bool match_ext_adv_handle(const void *data, const void *match_data) { const struct le_ext_adv *ext_adv = data; uint8_t handle = PTR_TO_UINT(match_data); return ext_adv->handle == handle; } static bool match_ext_adv_enable(const void *data, const void *match_data) { const struct le_ext_adv *ext_adv = data; return ext_adv->enable; } static void ext_adv_disable(void *data, void *user_data) { struct le_ext_adv *ext_adv = data; struct btdev *btdev = ext_adv->dev; uint8_t handle = PTR_TO_UINT(user_data); if (handle && ext_adv->handle != handle) return; if (ext_adv->broadcast_id) { timeout_remove(ext_adv->broadcast_id); ext_adv->broadcast_id = 0; } if (ext_adv->timeout_id) { timeout_remove(ext_adv->timeout_id); ext_adv->timeout_id = 0; } ext_adv->enable = 0x00; /* Consider le_adv_enable disabled if all advertising sets are * disabled. */ ext_adv = queue_find(btdev->le_ext_adv, match_ext_adv_enable, NULL); if (!ext_adv) btdev->le_adv_enable = 0x00; } static bool ext_adv_is_connectable(struct le_ext_adv *ext_adv) { if (!ext_adv->enable) return false; return ext_adv->type & 0x01; } static struct le_ext_adv *le_ext_adv_new(struct btdev *btdev, uint8_t handle) { struct le_ext_adv *ext_adv; ext_adv = new0(struct le_ext_adv, 1); ext_adv->dev = btdev; ext_adv->handle = handle; /* Default value for min/max advertising interval shall be 1.28s. */ ext_adv->interval = 1280; /* Add to queue */ if (!queue_push_tail(btdev->le_ext_adv, ext_adv)) { free(ext_adv); return NULL; } return ext_adv; } static int cmd_set_adv_rand_addr(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_adv_set_rand_addr *cmd = data; struct le_ext_adv *ext_adv; uint8_t status = BT_HCI_ERR_SUCCESS; /* Check if Ext Adv is already existed */ ext_adv = queue_find(dev->le_ext_adv, match_ext_adv_handle, UINT_TO_PTR(cmd->handle)); if (!ext_adv) { status = BT_HCI_ERR_UNKNOWN_ADVERTISING_ID; cmd_complete(dev, BT_HCI_CMD_LE_SET_ADV_SET_RAND_ADDR, &status, sizeof(status)); return 0; } if (ext_adv_is_connectable(ext_adv)) { status = BT_HCI_ERR_COMMAND_DISALLOWED; cmd_complete(dev, BT_HCI_CMD_LE_SET_ADV_SET_RAND_ADDR, &status, sizeof(status)); return 0; } memcpy(ext_adv->random_addr, cmd->bdaddr, 6); cmd_complete(dev, BT_HCI_CMD_LE_SET_ADV_SET_RAND_ADDR, &status, sizeof(status)); return 0; } static int cmd_set_ext_adv_params(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_ext_adv_params *cmd = data; struct bt_hci_rsp_le_set_ext_adv_params rsp; struct le_ext_adv *ext_adv; memset(&rsp, 0, sizeof(rsp)); /* Check if Ext Adv is already existed */ ext_adv = queue_find(dev->le_ext_adv, match_ext_adv_handle, UINT_TO_PTR(cmd->handle)); if (!ext_adv) { /* No more than maximum number */ if (queue_length(dev->le_ext_adv) >= MAX_EXT_ADV_SETS) { rsp.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; cmd_complete(dev, BT_HCI_CMD_LE_SET_EXT_ADV_PARAMS, &rsp, sizeof(rsp)); return 0; } /* Create new set */ ext_adv = le_ext_adv_new(dev, cmd->handle); if (!ext_adv) { rsp.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; cmd_complete(dev, BT_HCI_CMD_LE_SET_EXT_ADV_PARAMS, &rsp, sizeof(rsp)); return 0; } } if (ext_adv->enable) { rsp.status = BT_HCI_ERR_COMMAND_DISALLOWED; cmd_complete(dev, BT_HCI_CMD_LE_SET_EXT_ADV_PARAMS, &rsp, sizeof(rsp)); return 0; } ext_adv->type = le16_to_cpu(cmd->evt_properties); /* In case of high duty cycle directed connectable advertising * intervals shall be ignored and high duty cycle shall be used * (advertising interval <= 3.75 ms). */ if ((ext_adv->type & 0x0D) == 0x0D /* 0b1101 */) ext_adv->interval = 3; else { unsigned int min_interval = get_le24(cmd->min_interval); if (min_interval < 0x0020 || min_interval > 0xFFFFFF) { rsp.status = BT_HCI_ERR_UNSUPPORTED_FEATURE; cmd_complete(dev, BT_HCI_CMD_LE_SET_EXT_ADV_PARAMS, &rsp, sizeof(rsp)); return 0; } ext_adv->interval = min_interval * 0.625; } ext_adv->own_addr_type = cmd->own_addr_type; ext_adv->direct_addr_type = cmd->peer_addr_type; memcpy(ext_adv->direct_addr, cmd->peer_addr, 6); ext_adv->filter_policy = cmd->filter_policy; ext_adv->sid = cmd->sid; rsp.status = BT_HCI_ERR_SUCCESS; rsp.tx_power = 0; cmd_complete(dev, BT_HCI_CMD_LE_SET_EXT_ADV_PARAMS, &rsp, sizeof(rsp)); return 0; } static int cmd_set_ext_adv_data(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_ext_adv_data *cmd = data; struct le_ext_adv *ext_adv; uint8_t status = BT_HCI_ERR_SUCCESS; ext_adv = queue_find(dev->le_ext_adv, match_ext_adv_handle, UINT_TO_PTR(cmd->handle)); if (!ext_adv) { status = BT_HCI_ERR_UNKNOWN_ADVERTISING_ID; cmd_complete(dev, BT_HCI_CMD_LE_SET_EXT_ADV_DATA, &status, sizeof(status)); return 0; } ext_adv->adv_data_len = MIN(cmd->data_len, MAX_EXT_ADV_LEN); memcpy(ext_adv->adv_data, cmd->data, cmd->data_len); cmd_complete(dev, BT_HCI_CMD_LE_SET_EXT_ADV_DATA, &status, sizeof(status)); return 0; } static int cmd_set_ext_scan_rsp_data(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_ext_scan_rsp_data *cmd = data; struct le_ext_adv *ext_adv; uint8_t status = BT_HCI_ERR_SUCCESS; ext_adv = queue_find(dev->le_ext_adv, match_ext_adv_handle, UINT_TO_PTR(cmd->handle)); if (!ext_adv) { status = BT_HCI_ERR_UNKNOWN_ADVERTISING_ID; cmd_complete(dev, BT_HCI_CMD_LE_SET_EXT_ADV_DATA, &status, sizeof(status)); return 0; } ext_adv->scan_data_len = MIN(cmd->data_len, MAX_EXT_ADV_LEN); memcpy(ext_adv->scan_data, cmd->data, cmd->data_len); cmd_complete(dev, BT_HCI_CMD_LE_SET_EXT_SCAN_RSP_DATA, &status, sizeof(status)); return 0; } static uint8_t ext_adv_addr_type(struct le_ext_adv *adv) { /* Converts the address type on advertising params to advertising * report. */ switch (adv->own_addr_type) { /* LL RPAs shall be advertised as random type or they need to be * resolved depending on the filter policy. */ case 0x02: case 0x03: return 0x01; } return adv->own_addr_type; } static void send_ext_adv(struct btdev *btdev, const struct btdev *remote, struct le_ext_adv *ext_adv, uint16_t type, bool is_scan_rsp) { struct __packed { uint8_t num_reports; union { struct bt_hci_le_ext_adv_report lear; uint8_t raw[24 + MAX_EXT_ADV_LEN]; }; } meta_event; memset(&meta_event.lear, 0, sizeof(meta_event.lear)); meta_event.num_reports = 1; meta_event.lear.event_type = cpu_to_le16(type); meta_event.lear.addr_type = ext_adv_addr_type(ext_adv); memcpy(meta_event.lear.addr, ext_adv_addr(remote, ext_adv), 6); meta_event.lear.rssi = 127; meta_event.lear.tx_power = 127; /* Right now we dont care about phy in adv report */ meta_event.lear.primary_phy = 0x01; meta_event.lear.secondary_phy = 0x01; meta_event.lear.sid = ext_adv->sid; /* Scan or advertising response */ if (is_scan_rsp) { meta_event.lear.data_len = ext_adv->scan_data_len; memcpy(meta_event.lear.data, ext_adv->scan_data, meta_event.lear.data_len); } else { meta_event.lear.data_len = ext_adv->adv_data_len; memcpy(meta_event.lear.data, ext_adv->adv_data, meta_event.lear.data_len); } le_meta_event(btdev, BT_HCI_EVT_LE_EXT_ADV_REPORT, &meta_event, 1 + 24 + meta_event.lear.data_len); } static bool ext_adv_broadcast(void *user_data) { struct le_ext_adv *ext_adv = user_data; struct btdev *btdev = ext_adv->dev; uint16_t report_type; int i; report_type = get_ext_adv_type(ext_adv->type); for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { if (!btdev_list[i] || btdev_list[i] == btdev) continue; if (!btdev_list[i]->le_scan_enable) continue; if (!ext_adv_match_addr(btdev_list[i], ext_adv)) continue; send_ext_adv(btdev_list[i], btdev, ext_adv, report_type, false); if (btdev_list[i]->le_scan_type != 0x01) continue; /* if scannable bit is set the send scan response */ if (ext_adv->type & 0x02) { if (ext_adv->type == 0x13) report_type = 0x1b; else if (ext_adv->type == 0x12) report_type = 0x1a; else if (!(ext_adv->type & 0x10)) report_type &= 0x08; else continue; send_ext_adv(btdev_list[i], btdev, ext_adv, report_type, true); } } return true; } static void adv_set_terminate(struct btdev *dev, uint8_t status, uint8_t handle, uint16_t conn_handle, uint8_t num_evts) { struct bt_hci_evt_le_adv_set_term ev; memset(&ev, 0, sizeof(ev)); ev.status = status; ev.handle = handle; ev.conn_handle = cpu_to_le16(conn_handle); ev.num_evts = num_evts; le_meta_event(dev, BT_HCI_EVT_LE_ADV_SET_TERM, &ev, sizeof(ev)); } static bool ext_adv_timeout(void *user_data) { struct le_ext_adv *adv = user_data; adv->timeout_id = 0; adv_set_terminate(adv->dev, BT_HCI_ERR_ADV_TIMEOUT, adv->handle, 0x0000, 0x00); le_ext_adv_free(adv); return false; } static int cmd_set_ext_adv_enable(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_ext_adv_enable *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; int i; /* Num of set is zero */ if (!cmd->num_of_sets) { if (cmd->enable) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto exit_complete; } /* Disable all advertising sets */ queue_foreach(dev->le_ext_adv, ext_adv_disable, NULL); dev->le_adv_enable = 0x00; goto exit_complete; } /* Process each sets */ for (i = 0; i < cmd->num_of_sets; i++) { const struct bt_hci_cmd_ext_adv_set *eas; struct le_ext_adv *ext_adv; bool random_addr; eas = data + sizeof(*cmd) + (sizeof(*eas) * i); ext_adv = queue_find(dev->le_ext_adv, match_ext_adv_handle, UINT_TO_PTR(eas->handle)); if (!ext_adv) { status = BT_HCI_ERR_UNKNOWN_ADVERTISING_ID; goto exit_complete; } if (ext_adv->enable == cmd->enable) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto exit_complete; } random_addr = bacmp((bdaddr_t *)ext_adv->random_addr, BDADDR_ANY); /* If the advertising set's Own_Address_Type parameter * is set to 0x01 and the random address for * the advertising set has not been initialized, the * Controller shall return the error code Invalid HCI * Command Parameters (0x12). */ if (ext_adv->own_addr_type == 0x01 && !random_addr) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto exit_complete; } /* If the advertising set's Own_Address_Type parameter is set * to 0x03, the controller's resolving list did not contain a * matching entry, and the random address for the advertising * set has not been initialized, the Controller shall return the * error code Invalid HCI Command Parameters (0x12). */ if (ext_adv->own_addr_type == 0x03 && !random_addr) { if (!dev->le_rl_enable || !rl_find(dev, ext_adv->direct_addr_type, ext_adv->direct_addr)) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto exit_complete; } } ext_adv->enable = cmd->enable; dev->le_adv_enable = 0x01; if (!cmd->enable) ext_adv_disable(ext_adv, NULL); else { /* Send the first advertising report right away and * start the timer for continuous advertising. */ ext_adv_broadcast(ext_adv); ext_adv->broadcast_id = timeout_add(ext_adv->interval, ext_adv_broadcast, ext_adv, NULL); if (eas->duration) { unsigned int duration_ms = eas->duration * 10; ext_adv->timeout_id = timeout_add(duration_ms, ext_adv_timeout, ext_adv, NULL); } } } exit_complete: cmd_complete(dev, BT_HCI_CMD_LE_SET_EXT_ADV_ENABLE, &status, sizeof(status)); return 0; } static int cmd_read_max_adv_data_len(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_le_read_max_adv_data_len rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.max_len = MAX_EXT_ADV_LEN; cmd_complete(dev, BT_HCI_CMD_LE_READ_MAX_ADV_DATA_LEN, &rsp, sizeof(rsp)); return 0; } static int cmd_read_num_adv_sets(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_le_read_num_supported_adv_sets rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.num_of_sets = MAX_EXT_ADV_SETS; cmd_complete(dev, BT_HCI_CMD_LE_READ_NUM_SUPPORTED_ADV_SETS, &rsp, sizeof(rsp)); return 0; } static int cmd_remove_adv_set(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_remove_adv_set *cmd = data; struct le_ext_adv *ext_adv; uint8_t status = BT_HCI_ERR_SUCCESS; ext_adv = queue_find(dev->le_ext_adv, match_ext_adv_handle, UINT_TO_PTR(cmd->handle)); if (!ext_adv) { status = BT_HCI_ERR_UNKNOWN_ADVERTISING_ID; cmd_complete(dev, BT_HCI_CMD_LE_REMOVE_ADV_SET, &status, sizeof(status)); return 0; } if (ext_adv->enable) { status = BT_HCI_ERR_COMMAND_DISALLOWED; cmd_complete(dev, BT_HCI_CMD_LE_REMOVE_ADV_SET, &status, sizeof(status)); return 0; } queue_remove(dev->le_ext_adv, ext_adv); free(ext_adv); cmd_complete(dev, BT_HCI_CMD_LE_REMOVE_ADV_SET, &status, sizeof(status)); return 0; } static int cmd_clear_adv_sets(struct btdev *dev, const void *data, uint8_t len) { const struct queue_entry *entry; uint8_t status = BT_HCI_ERR_SUCCESS; for (entry = queue_get_entries(dev->le_ext_adv); entry; entry = entry->next) { struct le_ext_adv *ext_adv = entry->data; if (ext_adv->enable) { status = BT_HCI_ERR_COMMAND_DISALLOWED; cmd_complete(dev, BT_HCI_CMD_LE_CLEAR_ADV_SETS, &status, sizeof(status)); return 0; } } queue_remove_all(dev->le_ext_adv, NULL, NULL, le_ext_adv_free); cmd_complete(dev, BT_HCI_CMD_LE_CLEAR_ADV_SETS, &status, sizeof(status)); return 0; } static int cmd_set_pa_params(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_pa_params *cmd = data; uint8_t status; if (dev->le_pa_enable) { status = BT_HCI_ERR_COMMAND_DISALLOWED; } else { status = BT_HCI_ERR_SUCCESS; dev->le_pa_properties = le16_to_cpu(cmd->properties); dev->le_pa_min_interval = cmd->min_interval; dev->le_pa_max_interval = cmd->max_interval; } cmd_complete(dev, BT_HCI_CMD_LE_SET_PA_PARAMS, &status, sizeof(status)); return 0; } static int cmd_set_pa_data(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_pa_data *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; uint8_t data_len = cmd->data_len; if (data_len > MAX_PA_DATA_LEN) data_len = MAX_PA_DATA_LEN; dev->le_pa_data_len = data_len; memcpy(dev->le_pa_data, cmd->data, data_len); cmd_complete(dev, BT_HCI_CMD_LE_SET_PA_DATA, &status, sizeof(status)); return 0; } static uint8_t bits_to_phy(uint8_t bits) { uint8_t phy = 0x00; /* Convert PHY bits to PHY values on a ascending order. */ if (bits & BIT(0)) phy = 0x01; /* LE 1M */ if (bits & BIT(1)) phy = 0x02; /* LE 2M */ if (bits & BIT(2)) phy = 0x03; /* LE Coded */ return phy; } static void send_biginfo(struct btdev *dev, const struct btdev *remote, uint16_t sync_handle) { struct bt_hci_evt_le_big_info_adv_report ev; const struct btdev_conn *conn; struct bt_hci_bis *bis; conn = find_bis_index(remote, 0); if (!conn) return; bis = conn->data; memset(&ev, 0, sizeof(ev)); ev.sync_handle = cpu_to_le16(sync_handle); ev.num_bis = 1; while (find_bis_index(remote, ev.num_bis)) ev.num_bis++; ev.nse = 0x01; ev.iso_interval = bis->latency / 1.25; ev.bn = 0x01; ev.pto = 0x00; ev.irc = 0x01; ev.max_pdu = bis->sdu; memcpy(ev.sdu_interval, bis->sdu_interval, sizeof(ev.sdu_interval)); ev.max_sdu = bis->sdu; ev.phy = bits_to_phy(bis->phys); ev.framing = bis->framing; ev.encryption = bis->encryption; le_meta_event(dev, BT_HCI_EVT_LE_BIG_INFO_ADV_REPORT, &ev, sizeof(ev)); } static void send_pa(struct btdev *dev, const struct btdev *remote, uint8_t offset, uint16_t sync_handle) { struct __packed { struct bt_hci_le_pa_report ev; uint8_t data[247]; } pdu; memset(&pdu.ev, 0, sizeof(pdu.ev)); pdu.ev.handle = cpu_to_le16(sync_handle); pdu.ev.tx_power = 127; pdu.ev.rssi = 127; pdu.ev.cte_type = 0x0ff; if ((size_t) remote->le_pa_data_len - offset > sizeof(pdu.data)) { pdu.ev.data_status = 0x01; pdu.ev.data_len = sizeof(pdu.data); } else { pdu.ev.data_status = 0x00; pdu.ev.data_len = remote->le_pa_data_len - offset; } memcpy(pdu.data, remote->le_pa_data + offset, pdu.ev.data_len); le_meta_event(dev, BT_HCI_EVT_LE_PA_REPORT, &pdu, sizeof(pdu.ev) + pdu.ev.data_len); if (pdu.ev.data_status == 0x01) { offset += pdu.ev.data_len; send_pa(dev, remote, offset, sync_handle); return; } send_biginfo(dev, remote, sync_handle); } static bool match_sync_handle(const void *data, const void *match_data) { const struct le_per_adv *per_adv = data; uint16_t sync_handle = PTR_TO_UINT(match_data); return per_adv->sync_handle == sync_handle; } static bool match_big_handle(const void *data, const void *match_data) { const struct le_big *big = data; uint8_t handle = PTR_TO_UINT(match_data); return big->handle == handle; } static bool match_dev(const void *data, const void *match_data) { const struct le_per_adv *per_adv = data; const struct btdev *dev = match_data; return dev == find_btdev_by_bdaddr_type(per_adv->addr, per_adv->addr_type); } static bool match_sid(const void *data, const void *match_data) { const struct le_ext_adv *ext_adv = data; uint8_t sid = PTR_TO_UINT(match_data); return ext_adv->sid == sid; } static void le_pa_sync_estabilished(struct btdev *dev, struct btdev *remote, uint8_t status) { struct bt_hci_evt_le_per_sync_established ev; struct le_per_adv *per_adv; struct le_ext_adv *ext_adv; uint16_t sync_handle = SYNC_HANDLE; if (!remote) return; per_adv = queue_find(dev->le_per_adv, match_dev, remote); if (!per_adv) return; /* Match SID */ ext_adv = queue_find(remote->le_ext_adv, match_sid, UINT_TO_PTR(per_adv->sid)); if (!ext_adv) return; memset(&ev, 0, sizeof(ev)); ev.status = status; if (status) { queue_remove(dev->le_per_adv, per_adv); free(per_adv); le_meta_event(dev, BT_HCI_EVT_LE_PA_SYNC_ESTABLISHED, &ev, sizeof(ev)); return; } while (queue_find(dev->le_per_adv, match_sync_handle, UINT_TO_PTR(sync_handle))) sync_handle++; per_adv->sync_handle = sync_handle; per_adv->remote = remote; ev.handle = cpu_to_le16(per_adv->sync_handle); ev.sid = per_adv->sid; ev.addr_type = per_adv->addr_type; memcpy(ev.addr, per_adv->addr, sizeof(ev.addr)); ev.phy = 0x01; ev.interval = remote->le_pa_min_interval; ev.clock_accuracy = 0x07; le_meta_event(dev, BT_HCI_EVT_LE_PA_SYNC_ESTABLISHED, &ev, sizeof(ev)); send_pa(dev, remote, 0, per_adv->sync_handle); } static void le_pa_sync_lost(struct le_per_adv *pa) { struct bt_hci_evt_le_per_sync_lost ev; memset(&ev, 0, sizeof(ev)); ev.handle = cpu_to_le16(pa->sync_handle); le_meta_event(pa->dev, BT_HCI_EVT_LE_PA_SYNC_LOST, &ev, sizeof(ev)); free(pa); } static bool match_remote(const void *data, const void *match_data) { const struct le_per_adv *pa = data; const struct btdev *remote = match_data; return pa->remote == remote; } static int cmd_set_pa_enable(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_pa_enable *cmd = data; uint8_t status; int i; if (dev->le_pa_enable == cmd->enable) { status = BT_HCI_ERR_COMMAND_DISALLOWED; } else { dev->le_pa_enable = cmd->enable; status = BT_HCI_ERR_SUCCESS; } cmd_complete(dev, BT_HCI_CMD_LE_SET_PA_ENABLE, &status, sizeof(status)); for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { struct btdev *remote = btdev_list[i]; if (!remote || remote == dev) continue; if (dev->le_scan_enable && queue_find(remote->le_per_adv, match_sync_handle, UINT_TO_PTR(INV_HANDLE))) le_pa_sync_estabilished(remote, dev, BT_HCI_ERR_SUCCESS); else if (!dev->le_pa_enable) { struct le_per_adv *pa; pa = queue_remove_if(remote->le_per_adv, match_remote, dev); if (pa) le_pa_sync_lost(pa); } } return 0; } static int cmd_set_ext_scan_params(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_ext_scan_params *cmd = data; const struct bt_hci_le_scan_phy *scan = (void *)cmd->data; uint8_t status; if (dev->le_scan_enable) status = BT_HCI_ERR_COMMAND_DISALLOWED; else if (cmd->num_phys == 0) status = BT_HCI_ERR_INVALID_PARAMETERS; else { status = BT_HCI_ERR_SUCCESS; /* Currently we dont support multiple types in single * command So just take the first one will do. */ dev->le_scan_type = scan->type; dev->le_scan_own_addr_type = cmd->own_addr_type; dev->le_scan_filter_policy = cmd->filter_policy; } cmd_complete(dev, BT_HCI_CMD_LE_SET_EXT_SCAN_PARAMS, &status, sizeof(status)); return 0; } static int cmd_set_ext_scan_enable(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_ext_scan_enable *cmd = data; uint8_t status; if (dev->le_scan_enable == cmd->enable) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } /* If Enable is set to 0x01, the scanning parameters' Own_Address_Type * parameter is set to 0x01 or 0x03, and the random address for the * device has not been initialized, the Controller shall return the * error code Invalid HCI Command Parameters (0x12). */ if ((dev->le_scan_own_addr_type == 0x01 || dev->le_scan_own_addr_type == 0x03) && !bacmp((bdaddr_t *)dev->random_addr, BDADDR_ANY)) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } dev->le_scan_enable = cmd->enable; dev->le_filter_dup = cmd->filter_dup; status = BT_HCI_ERR_SUCCESS; done: cmd_complete(dev, BT_HCI_CMD_LE_SET_EXT_SCAN_ENABLE, &status, sizeof(status)); return 0; } static void scan_pa(struct btdev *dev, struct btdev *remote) { struct le_per_adv *per_adv = queue_find(dev->le_per_adv, match_sync_handle, UINT_TO_PTR(INV_HANDLE)); if (!per_adv || !remote->le_pa_enable) return; if (remote != find_btdev_by_bdaddr_type(per_adv->addr, per_adv->addr_type)) return; le_pa_sync_estabilished(dev, remote, BT_HCI_ERR_SUCCESS); } static int cmd_set_ext_scan_enable_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_ext_scan_enable *cmd = data; int i; if (!dev->le_scan_enable || !cmd->enable) return 0; for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { if (!btdev_list[i] || btdev_list[i] == dev) continue; scan_pa(dev, btdev_list[i]); } return 0; } static int cmd_ext_create_conn(struct btdev *dev, const void *data, uint8_t len) { cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_EXT_CREATE_CONN); return 0; } static void ext_adv_term(void *data, void *user_data) { struct le_ext_adv *adv = data; struct btdev_conn *conn = user_data; /* if connectable bit is set the send adv terminate */ if (conn && adv->type & 0x01) { adv_set_terminate(adv->dev, 0x00, adv->handle, conn->handle, 0x00); ext_adv_disable(adv, NULL); } } static void le_ext_conn_complete(struct btdev *btdev, const struct bt_hci_cmd_le_ext_create_conn *cmd, struct le_ext_adv *ext_adv, uint8_t status) { struct btdev_conn *conn = NULL; struct bt_hci_evt_le_enhanced_conn_complete ev; struct bt_hci_le_ext_create_conn *lecc = (void *)cmd->data; memset(&ev, 0, sizeof(ev)); if (!status) { conn = conn_add_acl(btdev, cmd->peer_addr, cmd->peer_addr_type); if (!conn) return; ev.status = status; ev.peer_addr_type = btdev->le_scan_own_addr_type; if (ev.peer_addr_type == 0x01 || ev.peer_addr_type == 0x03) { ev.peer_addr_type = 0x01; memcpy(ev.peer_addr, btdev->random_addr, 6); } else memcpy(ev.peer_addr, btdev->bdaddr, 6); ev.role = 0x01; ev.handle = cpu_to_le16(conn->handle); ev.interval = lecc->max_interval; ev.latency = lecc->latency; ev.supv_timeout = lecc->supv_timeout; /* Set Local RPA if an RPA was generated for the advertising */ if (ext_adv->rpa) memcpy(ev.local_rpa, ext_adv->random_addr, sizeof(ev.local_rpa)); le_meta_event(conn->link->dev, BT_HCI_EVT_LE_ENHANCED_CONN_COMPLETE, &ev, sizeof(ev)); /* Disable EXT ADV */ queue_foreach(conn->link->dev->le_ext_adv, ext_adv_term, conn); } ev.status = status; ev.peer_addr_type = cmd->peer_addr_type; memcpy(ev.peer_addr, cmd->peer_addr, 6); ev.role = 0x00; /* Use random address as Local RPA if Create Connection own_addr_type * is 0x03 since that expects the controller to generate the RPA. */ if (btdev->le_scan_own_addr_type == 0x03) memcpy(ev.local_rpa, btdev->random_addr, 6); else memset(ev.local_rpa, 0, sizeof(ev.local_rpa)); le_meta_event(btdev, BT_HCI_EVT_LE_ENHANCED_CONN_COMPLETE, &ev, sizeof(ev)); /* Disable EXT ADV */ if (conn) queue_foreach(btdev->le_ext_adv, ext_adv_term, conn); } static int cmd_ext_create_conn_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_ext_create_conn *cmd = data; const struct queue_entry *entry; struct btdev *remote; dev->le_scan_own_addr_type = cmd->own_addr_type; remote = find_btdev_by_bdaddr_type(cmd->peer_addr, cmd->peer_addr_type); if (!remote) { le_ext_conn_complete(dev, cmd, NULL, BT_HCI_ERR_CONN_FAILED_TO_ESTABLISH); return 0; } for (entry = queue_get_entries(remote->le_ext_adv); entry; entry = entry->next) { struct le_ext_adv *ext_adv = entry->data; if (ext_adv_is_connectable(ext_adv) && ext_adv_match_addr(dev, ext_adv) && ext_adv_addr_type(ext_adv) == cmd->peer_addr_type) { le_ext_conn_complete(dev, cmd, ext_adv, 0); return 0; } } return 0; } static struct le_per_adv *le_per_adv_new(struct btdev *btdev, uint8_t addr_type, const uint8_t *addr, uint8_t sid) { struct le_per_adv *per_adv; per_adv = new0(struct le_per_adv, 1); per_adv->dev = btdev; per_adv->addr_type = addr_type; memcpy(per_adv->addr, addr, 6); per_adv->sid = sid; per_adv->sync_handle = INV_HANDLE; /* Add to queue */ if (!queue_push_tail(btdev->le_per_adv, per_adv)) { free(per_adv); return NULL; } return per_adv; } static int cmd_pa_create_sync(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_pa_create_sync *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; struct le_per_adv *per_adv; if (cmd->sid > 0x0f) { cmd_status(dev, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_PA_CREATE_SYNC); return 0; } /* Create new train */ per_adv = le_per_adv_new(dev, cmd->addr_type, cmd->addr, cmd->sid); if (!per_adv) status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; cmd_status(dev, status, BT_HCI_CMD_LE_PA_CREATE_SYNC); return 0; } static int cmd_pa_create_sync_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_pa_create_sync *cmd = data; struct btdev *remote; /* This command may be issued whether or not scanning is enabled and * scanning may be enabled and disabled (see the LE Set Extended Scan * Enable command) while this command is pending. However, * synchronization can only occur when scanning is enabled. While * scanning is disabled, no attempt to synchronize will take place. */ if (!dev->le_scan_enable) return 0; remote = find_btdev_by_bdaddr_type(cmd->addr, cmd->addr_type); if (!remote || !remote->le_pa_enable) return 0; le_pa_sync_estabilished(dev, remote, BT_HCI_ERR_SUCCESS); return 0; } static int cmd_pa_create_sync_cancel(struct btdev *dev, const void *data, uint8_t len) { uint8_t status = BT_HCI_ERR_SUCCESS; /* If the Host issues this command while no * HCI_LE_Periodic_Advertising_Create_Sync command is pending, the * Controller shall return the error code Command Disallowed (0x0C). */ if (!queue_find(dev->le_per_adv, match_sync_handle, UINT_TO_PTR(INV_HANDLE))) status = BT_HCI_ERR_COMMAND_DISALLOWED; cmd_complete(dev, BT_HCI_CMD_LE_PA_CREATE_SYNC_CANCEL, &status, sizeof(status)); /* After the HCI_Command_Complete is sent and if the cancellation was * successful, the Controller sends an * HCI_LE_Periodic_Advertising_Sync_Established event to the Host with * the error code Operation Cancelled by Host (0x44). */ if (!status) le_pa_sync_estabilished(dev, NULL, BT_HCI_ERR_CANCELLED); return 0; } static int cmd_pa_term_sync(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_pa_term_sync *cmd = data; struct le_per_adv *per_adv; uint16_t sync_handle = le16_to_cpu(cmd->sync_handle); uint8_t status = BT_HCI_ERR_SUCCESS; per_adv = queue_find(dev->le_per_adv, match_sync_handle, UINT_TO_PTR(sync_handle)); /* If the periodic advertising train corresponding to the Sync_Handle * parameter does not exist, then the Controller shall return the error * code Unknown Advertising Identifier (0x42). */ if (!per_adv) { status = BT_HCI_ERR_UNKNOWN_ADVERTISING_ID; } else { queue_remove(dev->le_per_adv, per_adv); free(per_adv); } cmd_complete(dev, BT_HCI_CMD_LE_PA_TERM_SYNC, &status, sizeof(status)); return 0; } static int cmd_pa_add(struct btdev *dev, const void *data, uint8_t len) { /* TODO */ return -ENOTSUP; } static int cmd_pa_remove(struct btdev *dev, const void *data, uint8_t len) { /* TODO */ return -ENOTSUP; } static int cmd_pa_clear(struct btdev *dev, const void *data, uint8_t len) { /* TODO */ return -ENOTSUP; } static int cmd_read_pa_list_size(struct btdev *dev, const void *data, uint8_t len) { /* TODO */ return -ENOTSUP; } static int cmd_read_tx_power(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_le_read_tx_power rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; /* a random default value */ rsp.max_tx_power = 0x07; rsp.min_tx_power = 0xDE; cmd_complete(dev, BT_HCI_CMD_LE_READ_TX_POWER, &rsp, sizeof(rsp)); return 0; } static int cmd_set_privacy_mode(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_priv_mode *cmd = data; const struct btdev_rl *rl; uint8_t status; /* This command shall not be used when address resolution is enabled in * the Controller and: * • Advertising (other than periodic advertising) is enabled, * • Scanning is enabled, or * • an HCI_LE_Create_Connection, HCI_LE_Extended_Create_Connection, * or HCI_LE_Periodic_Advertising_Create_Sync command is pending. */ if (dev->le_rl_enable || dev->le_adv_enable || dev->le_scan_enable) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } /* If the device is not on the resolving list, the Controller shall * return the error code Unknown Connection Identifier (0x02). */ rl = rl_find(dev, cmd->peer_id_addr_type, cmd->peer_id_addr); if (!rl) { status = BT_HCI_ERR_UNKNOWN_CONN_ID; goto done; } if (cmd->priv_mode > 0x01) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } ((struct btdev_rl *)rl)->mode = cmd->priv_mode; status = BT_HCI_ERR_SUCCESS; done: cmd_complete(dev, BT_HCI_CMD_LE_SET_PRIV_MODE, &status, sizeof(status)); return 0; } #define CMD_LE_50 \ CMD(BT_HCI_CMD_LE_SET_DEFAULT_PHY, cmd_set_default_phy, NULL), \ CMD(BT_HCI_CMD_LE_SET_PHY, cmd_le_set_phy, cmd_le_set_phy_complete), \ CMD(BT_HCI_CMD_LE_SET_ADV_SET_RAND_ADDR, cmd_set_adv_rand_addr, NULL), \ CMD(BT_HCI_CMD_LE_SET_EXT_ADV_PARAMS, cmd_set_ext_adv_params, NULL), \ CMD(BT_HCI_CMD_LE_SET_EXT_ADV_DATA, cmd_set_ext_adv_data, NULL), \ CMD(BT_HCI_CMD_LE_SET_EXT_SCAN_RSP_DATA, cmd_set_ext_scan_rsp_data, \ NULL), \ CMD(BT_HCI_CMD_LE_SET_EXT_ADV_ENABLE, cmd_set_ext_adv_enable, NULL), \ CMD(BT_HCI_CMD_LE_READ_MAX_ADV_DATA_LEN, cmd_read_max_adv_data_len, \ NULL), \ CMD(BT_HCI_CMD_LE_READ_NUM_SUPPORTED_ADV_SETS, cmd_read_num_adv_sets, \ NULL), \ CMD(BT_HCI_CMD_LE_REMOVE_ADV_SET, cmd_remove_adv_set, NULL), \ CMD(BT_HCI_CMD_LE_CLEAR_ADV_SETS, cmd_clear_adv_sets, NULL), \ CMD(BT_HCI_CMD_LE_SET_PA_PARAMS, cmd_set_pa_params, \ NULL), \ CMD(BT_HCI_CMD_LE_SET_PA_DATA, cmd_set_pa_data, NULL), \ CMD(BT_HCI_CMD_LE_SET_PA_ENABLE, cmd_set_pa_enable, NULL), \ CMD(BT_HCI_CMD_LE_SET_EXT_SCAN_PARAMS, cmd_set_ext_scan_params, NULL), \ CMD(BT_HCI_CMD_LE_SET_EXT_SCAN_ENABLE, cmd_set_ext_scan_enable, \ cmd_set_ext_scan_enable_complete), \ CMD(BT_HCI_CMD_LE_EXT_CREATE_CONN, cmd_ext_create_conn, \ cmd_ext_create_conn_complete), \ CMD(BT_HCI_CMD_LE_PA_CREATE_SYNC, cmd_pa_create_sync, \ cmd_pa_create_sync_complete), \ CMD(BT_HCI_CMD_LE_PA_CREATE_SYNC_CANCEL, cmd_pa_create_sync_cancel, \ NULL), \ CMD(BT_HCI_CMD_LE_PA_TERM_SYNC, cmd_pa_term_sync, NULL), \ CMD(BT_HCI_CMD_LE_ADD_DEV_PA_LIST, cmd_pa_add, NULL), \ CMD(BT_HCI_CMD_LE_REMOVE_DEV_PA_LIST, cmd_pa_remove, NULL), \ CMD(BT_HCI_CMD_LE_CLEAR_PA_LIST, cmd_pa_clear, NULL), \ CMD(BT_HCI_CMD_LE_READ_PA_LIST_SIZE, cmd_read_pa_list_size, NULL), \ CMD(BT_HCI_CMD_LE_READ_TX_POWER, cmd_read_tx_power, NULL), \ CMD(BT_HCI_CMD_LE_SET_PRIV_MODE, cmd_set_privacy_mode, NULL) static const struct btdev_cmd cmd_le_5_0[] = { CMD_COMMON_ALL, CMD_COMMON_BREDR_LE, CMD_COMMON_BREDR_20, CMD_COMMON_BREDR_LE_40, CMD_BREDR, CMD_LE, CMD_LE_50, {} }; static void set_le_50_commands(struct btdev *btdev) { btdev->commands[35] |= 0x20; /* LE Set Default PHY */ btdev->commands[36] |= 0x02; /* LE Set Adv Set Random Address */ btdev->commands[36] |= 0x04; /* LE Set Ext Adv Parameters */ btdev->commands[36] |= 0x08; /* LE Set Ext Adv Data */ btdev->commands[36] |= 0x10; /* LE Set Ext Scan Response Data */ btdev->commands[36] |= 0x20; /* LE Set Ext Adv Enable */ btdev->commands[36] |= 0x40; /* LE Read Maximum Adv Data Length */ btdev->commands[36] |= 0x80; /* LE Read Num of Supported Adv Sets */ btdev->commands[37] |= 0x01; /* LE Remove Adv Set */ btdev->commands[37] |= 0x02; /* LE Clear Adv Sets */ btdev->commands[37] |= 0x04; /* LE Set Periodic Adv Parameters */ btdev->commands[37] |= 0x08; /* LE Set Periodic Adv Data */ btdev->commands[37] |= 0x10; /* LE Set Periodic Adv Enable */ btdev->commands[37] |= 0x20; /* LE Set Ext Scan Parameters */ btdev->commands[37] |= 0x40; /* LE Set Ext Scan Enable */ btdev->commands[37] |= 0x80; /* LE Ext Create Connection */ btdev->commands[38] |= 0x01; /* LE Periodic Adv Create Sync */ btdev->commands[38] |= 0x02; /* LE Periodic Adv Create Sync Cancel */ btdev->commands[38] |= 0x04; /* LE Periodic Adv Terminate Sync */ btdev->commands[38] |= 0x08; /* LE Add Device To Periodic Adv List */ btdev->commands[38] |= 0x10; /* LE Remove Periodic Adv List */ btdev->commands[38] |= 0x20; /* LE Clear Periodic Adv List */ btdev->commands[38] |= 0x40; /* LE Read Periodic Adv List Size */ btdev->commands[38] |= 0x80; /* LE Read Transmit Power */ btdev->commands[39] |= 0x04; /* LE Set Privacy Mode */ btdev->cmds = cmd_le_5_0; } static void le_past_received_event(struct btdev_conn *acl, struct bt_hci_evt_le_past_recv *ev, struct le_per_adv *pa) { struct btdev *remote; le_meta_event(acl->dev, BT_HCI_EVT_LE_PAST_RECEIVED, ev, sizeof(*ev)); if (ev->status != BT_HCI_ERR_SUCCESS) return; remote = find_btdev_by_bdaddr_type(pa->addr, pa->addr_type); if (!remote) return; switch (acl->past_mode) { case 0x01: /* HCI_LE_Periodic_Advertising_Report events will be * disabled. */ break; case 0x02: /* HCI_LE_Periodic_Advertising_Report events will be enabled * with duplicate filtering disabled. */ case 0x03: /* HCI_LE_Periodic_Advertising_Report events will be enabled * with duplicate filtering enabled. */ send_pa(acl->dev, remote, 0, pa->sync_handle); break; } } static void le_past_received(struct btdev_conn *acl, struct le_per_adv *pa) { struct btdev *dev = acl->dev; struct bt_hci_evt_le_past_recv ev; struct le_per_adv *cp; uint16_t sync_handle = SYNC_HANDLE; /* 0x00: * No attempt is made to synchronize to the periodic advertising and no * HCI_LE_Periodic_Advertising_Sync_Transfer_Received event is sent to * the Host. */ if (!acl->past_mode) return; memset(&ev, 0, sizeof(ev)); ev.status = BT_HCI_ERR_SUCCESS; /* Create a copy of the PA sync */ cp = le_per_adv_new(dev, pa->addr_type, pa->addr, pa->sid); if (!cp) { ev.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; goto done; } /* Find the next available sync handle */ while (queue_find(dev->le_per_adv, match_sync_handle, UINT_TO_PTR(sync_handle))) sync_handle++; cp->sync_handle = sync_handle; ev.handle = cpu_to_le16(acl->handle); ev.sync_handle = cpu_to_le16(cp->sync_handle); ev.sid = cpu_to_le16(cp->sid); ev.addr_type = cp->addr_type; memcpy(ev.addr, cp->addr, sizeof(ev.addr)); ev.phy = 0x01; ev.interval = acl->dev->le_pa_min_interval; ev.clock_accuracy = 0x07; done: le_past_received_event(acl, &ev, pa); } static int cmd_past(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_past *cmd = data; struct bt_hci_rsp_le_past rsp; struct le_per_adv *pa; struct btdev_conn *acl = NULL; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.handle = cmd->handle; pa = queue_find(dev->le_per_adv, match_sync_handle, UINT_TO_PTR(le16_to_cpu(cmd->sync_handle))); if (!pa) { /* If the periodic advertising train corresponding to the * Sync_Handle parameter does not exist, the Controller shall * return the error code Unknown Advertising Identifier (0x42). */ rsp.status = BT_HCI_ERR_UNKNOWN_ADVERTISING_ID; goto done; } acl = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (!acl) { /* If the Connection_Handle parameter does not identify a * current connection, the Controller shall return the error * code Unknown Connection Identifier (0x02). */ rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; goto done; } done: cmd_complete(dev, BT_HCI_CMD_LE_PAST, &rsp, sizeof(rsp)); if (rsp.status == BT_HCI_ERR_SUCCESS) le_past_received(acl->link, pa); return 0; } static void le_past_info_received(struct btdev_conn *acl, struct le_ext_adv *ea) { struct btdev *dev = acl->dev; struct bt_hci_evt_le_past_recv ev; struct le_per_adv *pa; uint16_t sync_handle = SYNC_HANDLE; /* 0x00: * No attempt is made to synchronize to the periodic advertising and no * HCI_LE_Periodic_Advertising_Sync_Transfer_Received event is sent to * the Host. */ if (!acl->past_mode) return; memset(&ev, 0, sizeof(ev)); ev.status = BT_HCI_ERR_SUCCESS; /* Create a copy using EA parameters */ pa = le_per_adv_new(dev, ea->own_addr_type, ea->own_addr_type ? ea->random_addr : acl->link->dev->bdaddr, ea->sid); if (!pa) { ev.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; goto done; } /* Find the next available sync handle */ while (queue_find(dev->le_per_adv, match_sync_handle, UINT_TO_PTR(sync_handle))) sync_handle++; pa->sync_handle = sync_handle; ev.handle = cpu_to_le16(acl->handle); ev.sync_handle = cpu_to_le16(pa->sync_handle); ev.sid = cpu_to_le16(pa->sid); ev.addr_type = pa->addr_type; memcpy(ev.addr, pa->addr, sizeof(ev.addr)); ev.phy = 0x01; ev.interval = acl->dev->le_pa_min_interval; ev.clock_accuracy = 0x07; done: le_past_received_event(acl, &ev, pa); } static int cmd_past_set_info(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_past_set_info *cmd = data; struct bt_hci_rsp_le_past_set_info rsp; struct le_ext_adv *ea; struct btdev_conn *acl = NULL; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.handle = cmd->handle; ea = queue_find(dev->le_ext_adv, match_ext_adv_handle, UINT_TO_PTR(cmd->adv_handle)); if (!ea) { /* If the advertising set corresponding to the * Advertising_Handle parameter does not exist, the Controller * shall return the error code Unknown Advertising Identifier * (0x42). */ rsp.status = BT_HCI_ERR_UNKNOWN_ADVERTISING_ID; goto done; } if (!dev->le_pa_enable) { /* If periodic advertising is not currently in progress for the * advertising set, the Controller shall return the error code * Command Disallowed (0x0C). */ rsp.status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } acl = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (!acl) { /* If the Connection_Handle parameter does not identify a * current connection, the Controller shall return the error * code Unknown Connection Identifier (0x02). */ rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; goto done; } done: cmd_complete(dev, BT_HCI_CMD_LE_PAST_SET_INFO, &rsp, sizeof(rsp)); if (rsp.status == BT_HCI_ERR_SUCCESS) le_past_info_received(acl->link, ea); return 0; } static int cmd_past_params(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_past_params *cmd = data; struct bt_hci_rsp_le_past_set_info rsp; struct btdev_conn *acl = NULL; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.handle = cmd->handle; acl = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (!acl) { /* If the Connection_Handle parameter does not identify a * current connection, the Controller shall return the error * code Unknown Connection Identifier (0x02). */ rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; goto done; } acl->past_mode = cmd->mode; done: cmd_complete(dev, BT_HCI_CMD_LE_PAST_PARAMS, &rsp, sizeof(rsp)); return 0; } static int cmd_read_size_v2(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_le_read_buffer_size_v2 rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.acl_mtu = cpu_to_le16(dev->acl_mtu); rsp.acl_max_pkt = dev->acl_max_pkt; rsp.iso_mtu = cpu_to_le16(dev->iso_mtu); rsp.iso_max_pkt = dev->iso_max_pkt; cmd_complete(dev, BT_HCI_CMD_LE_READ_BUFFER_SIZE_V2, &rsp, sizeof(rsp)); return 0; } static int cmd_read_iso_tx_sync(struct btdev *dev, const void *data, uint8_t len) { /* TODO */ return -ENOTSUP; } static int find_cig(struct btdev *dev, uint8_t cig_id) { unsigned int i; for (i = 0; i < ARRAY_SIZE(dev->le_cig); ++i) if (dev->le_cig[i].params.cig_id == cig_id) return i; return -1; } static uint16_t make_cis_handle(uint8_t cig_idx, uint8_t cis_idx) { /* Put CIG+CIS idxs to handle so don't need to track separately */ return CIS_HANDLE + cig_idx*CIS_SIZE + cis_idx; } static int parse_cis_handle(uint16_t handle, int *cis_idx) { int cig_idx; if (handle < CIS_HANDLE || handle >= CIS_HANDLE + CIS_SIZE*CIG_SIZE) return -1; cig_idx = (handle - CIS_HANDLE) / CIS_SIZE; if (cis_idx) *cis_idx = (handle - CIS_HANDLE) % CIS_SIZE; return cig_idx; } static int cmd_set_cig_params(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_cig_params *cmd = data; struct lescp { struct bt_hci_rsp_le_set_cig_params params; uint16_t handle[CIS_SIZE]; } __attribute__ ((packed)) rsp; int i = 0; int cig_idx; uint32_t interval; uint16_t latency; memset(&rsp, 0, sizeof(rsp)); rsp.params.cig_id = cmd->cig_id; if (cmd->num_cis > ARRAY_SIZE(dev->le_cig[0].cis)) { rsp.params.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; goto done; } if (cmd->cig_id > 0xef) { rsp.params.status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } interval = get_le24(cmd->c_interval); if (interval < 0x0000ff || interval > 0x0fffff) { rsp.params.status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } interval = get_le24(cmd->p_interval); if (interval < 0x0000ff || interval > 0x0fffff) { rsp.params.status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } if (cmd->sca > 0x07) { rsp.params.status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } if (cmd->packing > 0x01) { rsp.params.status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } if (cmd->framing > 0x01) { rsp.params.status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } latency = cpu_to_le16(cmd->c_latency); if (latency < 0x0005 || latency > 0x0fa0) { rsp.params.status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } latency = cpu_to_le16(cmd->p_latency); if (latency < 0x0005 || latency > 0x0fa0) { rsp.params.status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } cig_idx = find_cig(dev, cmd->cig_id); if (cig_idx < 0) cig_idx = find_cig(dev, 0xff); if (cig_idx < 0) { rsp.params.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; goto done; } /* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 4, Part E * page 2553 * * If the Host issues this command when the CIG is not in the * configurable state, the Controller shall return the error * code Command Disallowed (0x0C). */ if (dev->le_cig[cig_idx].activated) { rsp.params.status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } rsp.params.status = BT_HCI_ERR_SUCCESS; for (i = 0; i < cmd->num_cis; i++) { rsp.params.num_handles++; rsp.handle[i] = cpu_to_le16(make_cis_handle(cig_idx, i)); } memcpy(&dev->le_cig[cig_idx], data, len); done: cmd_complete(dev, BT_HCI_CMD_LE_SET_CIG_PARAMS, &rsp, sizeof(rsp.params) + (i * sizeof(uint16_t))); return 0; } static int cmd_set_cig_params_test(struct btdev *dev, const void *data, uint8_t len) { /* TODO */ return -ENOTSUP; } static int cmd_create_cis(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_create_cis *cmd = data; int i, j; for (i = 0; i < cmd->num_cis; i++) { const struct bt_hci_cis *cis = &cmd->cis[i]; struct btdev_conn *acl; struct btdev_conn *iso; int cig_idx, cis_idx; /* Check for errors (Core v5.3 Vol 4 Part E Sec. 7.8.99) */ for (j = 0; j < i; j++) if (cis->cis_handle == cmd->cis[j].cis_handle) return -EINVAL; cig_idx = parse_cis_handle(le16_to_cpu(cis->cis_handle), &cis_idx); if (cig_idx < 0) return -ENOENT; if (cis_idx >= dev->le_cig[cig_idx].params.num_cis) return -ENOENT; acl = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cis->acl_handle))); if (!acl) return -ENOENT; iso = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cis->cis_handle))); if (iso) return -EEXIST; } cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_CREATE_CIS); return 0; } static uint8_t le_cis_interval(uint8_t sdu_interval[3]) { /* ISO Interval (slots of 1.25 ms) = SDU_Interval (us) */ return get_le24(sdu_interval) / 1250; } static uint32_t le_cis_latecy(uint8_t ft, uint8_t iso_interval, uint8_t sdu_interval[3]) { uint32_t latency; uint32_t interval = get_le24(sdu_interval); /* Transport_Latency = FT × ISO_Interval - SDU_Interval */ latency = ft * (iso_interval * 1250) - interval; return latency >= interval ? latency : interval; } static void le_cis_estabilished(struct btdev *dev, struct btdev_conn *conn, uint8_t status) { struct bt_hci_evt_le_cis_established evt; int cig_idx, cis_idx = -1; memset(&evt, 0, sizeof(evt)); evt.status = status; evt.conn_handle = conn ? cpu_to_le16(conn->handle) : 0x0000; cig_idx = conn ? parse_cis_handle(conn->link->handle, &cis_idx) : -1; if (cig_idx < 0 && !evt.status) evt.status = BT_HCI_ERR_UNSPECIFIED_ERROR; if (!evt.status) { struct btdev *remote = conn->link->dev; struct le_cig *le_cig = &remote->le_cig[cig_idx]; memset(evt.cig_sync_delay, 0, sizeof(evt.cig_sync_delay)); memset(evt.cis_sync_delay, 0, sizeof(evt.cis_sync_delay)); evt.c_phy = bits_to_phy(le_cig->cis[cis_idx].c_phys); evt.p_phy = bits_to_phy(le_cig->cis[cis_idx].p_phys); evt.nse = 0x01; evt.c_bn = 0x01; evt.p_bn = 0x01; evt.c_ft = 0x02; evt.p_ft = 0x02; evt.c_mtu = le_cig->cis[cis_idx].c_sdu; evt.p_mtu = le_cig->cis[cis_idx].p_sdu; evt.interval = le_cis_interval(le_cig->params.c_interval); /* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 6, Part G * page 3050: * * Transport_Latency_C_To_P = CIG_Sync_Delay + FT_C_To_P × * ISO_Interval - SDU_Interval_C_To_P * Transport_Latency_P_To_C = CIG_Sync_Delay + FT_P_To_C × * ISO_Interval - SDU_Interval_P_To_C */ put_le24(le_cis_latecy(evt.c_ft, evt.interval, le_cig->params.c_interval), evt.c_latency); put_le24(le_cis_latecy(evt.p_ft, evt.interval, le_cig->params.p_interval), evt.p_latency); } le_meta_event(dev, BT_HCI_EVT_LE_CIS_ESTABLISHED, &evt, sizeof(evt)); if (conn) le_meta_event(conn->link->dev, BT_HCI_EVT_LE_CIS_ESTABLISHED, &evt, sizeof(evt)); } static int cmd_create_cis_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_create_cis *cmd = data; int i; for (i = 0; i < cmd->num_cis; i++) { const struct bt_hci_cis *cis = &cmd->cis[i]; struct btdev_conn *acl; struct btdev_conn *iso; struct bt_hci_evt_le_cis_req evt; struct le_cig *le_cig; int cig_idx, cis_idx; cig_idx = parse_cis_handle(le16_to_cpu(cis->cis_handle), &cis_idx); if (cig_idx < 0) { le_cis_estabilished(dev, NULL, BT_HCI_ERR_UNKNOWN_CONN_ID); break; } le_cig = &dev->le_cig[cig_idx]; acl = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cis->acl_handle))); if (!acl) { le_cis_estabilished(dev, NULL, BT_HCI_ERR_UNKNOWN_CONN_ID); break; } iso = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cis->cis_handle))); if (!iso) { iso = conn_add_cis(acl, le16_to_cpu(cis->cis_handle)); if (!iso) { le_cis_estabilished(dev, NULL, BT_HCI_ERR_UNKNOWN_CONN_ID); break; } } evt.acl_handle = cpu_to_le16(acl->handle); evt.cis_handle = cpu_to_le16(iso->handle); evt.cig_id = le_cig->params.cig_id; evt.cis_id = le_cig->cis[cis_idx].cis_id; le_cig->activated = true; le_meta_event(iso->link->dev, BT_HCI_EVT_LE_CIS_REQ, &evt, sizeof(evt)); } return 0; } static bool match_handle_cig_idx(const void *data, const void *match_data) { const struct btdev_conn *conn = data; int cig_idx = PTR_TO_INT(match_data); return cig_idx >= 0 && parse_cis_handle(conn->handle, NULL) == cig_idx; } static int cmd_remove_cig(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_remove_cig *cmd = data; struct bt_hci_rsp_le_remove_cig rsp; struct btdev_conn *conn = NULL; int idx; memset(&rsp, 0, sizeof(rsp)); rsp.cig_id = cmd->cig_id; idx = find_cig(dev, cmd->cig_id); conn = queue_find(dev->conns, match_handle_cig_idx, INT_TO_PTR(idx)); if (idx >= 0 && !conn) { memset(&dev->le_cig[idx], 0, sizeof(struct le_cig)); dev->le_cig[idx].params.cig_id = 0xff; rsp.status = BT_HCI_ERR_SUCCESS; } else if (conn) { rsp.status = BT_HCI_ERR_COMMAND_DISALLOWED; } else { rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; } cmd_complete(dev, BT_HCI_CMD_LE_REMOVE_CIG, &rsp, sizeof(rsp)); return 0; } static int cmd_accept_cis(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_accept_cis *cmd = data; struct btdev_conn *conn; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(cpu_to_le16(cmd->handle))); if (!conn) { cmd_status(dev, BT_HCI_ERR_UNKNOWN_CONN_ID, BT_HCI_CMD_LE_ACCEPT_CIS); return 0; } cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_ACCEPT_CIS); le_cis_estabilished(dev, conn, BT_HCI_ERR_SUCCESS); return 0; } static int cmd_reject_cis(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_reject_cis *cmd = data; struct btdev_conn *conn; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(cpu_to_le16(cmd->handle))); if (!conn) { cmd_status(dev, BT_HCI_ERR_UNKNOWN_CONN_ID, BT_HCI_CMD_LE_REJECT_CIS); return 0; } cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_REJECT_CIS); le_cis_estabilished(dev, conn, cmd->reason); return 0; } static int cmd_create_big(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_big_create_sync *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; /* If the Advertising_Handle does not identify a periodic advertising * train,, the Controller shall return the error code Unknown * Advertising Identifier (0x42). */ if (!dev->le_pa_enable) { status = BT_HCI_ERR_UNKNOWN_ADVERTISING_ID; goto done; } /* If the Host sends this command with a BIG_Handle that is already * allocated, the Controller shall return the error code Command * Disallowed (0x0C). */ if (queue_find(dev->le_big, match_big_handle, UINT_TO_PTR(cmd->handle))) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } done: cmd_status(dev, status, BT_HCI_CMD_LE_CREATE_BIG); return 0; } static struct le_big *le_big_new(struct btdev *btdev, uint8_t handle) { struct le_big *big; big = new0(struct le_big, 1); big->dev = btdev; big->handle = handle; big->bis = queue_new(); /* Add to queue */ if (!queue_push_tail(btdev->le_big, big)) { le_big_free(big); return NULL; } return big; } static int cmd_create_big_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_create_big *cmd = data; const struct bt_hci_bis *bis = &cmd->bis; struct le_big *big; int i; struct bt_hci_evt_le_big_complete evt; uint16_t *bis_handle; uint8_t *pdu; uint8_t pdu_len; pdu_len = sizeof(evt) + cmd->num_bis * sizeof(*bis_handle); pdu = malloc(pdu_len); if (!pdu) return -ENOMEM; /* If the Controller cannot create all BISes of the BIG or if Num_BIS * exceeds the maximum value supported by the Controller, then it shall * return the error code Rejected due to Limited Resources (0x0D). */ big = le_big_new(dev, cmd->handle); if (!big) { evt.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; goto done; } bis_handle = (uint16_t *)(pdu + sizeof(evt)); memset(&evt, 0, sizeof(evt)); for (i = 0; i < cmd->num_bis; i++) { struct btdev_conn *conn; conn = conn_add_bis(dev, BIS_HANDLE + i, bis); if (!conn) { queue_remove(dev->le_big, big); le_big_free(big); evt.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; goto done; } queue_push_tail(big->bis, conn); *bis_handle = cpu_to_le16(conn->handle); bis_handle++; } evt.handle = cmd->handle; evt.phy = bits_to_phy(bis->phys); evt.max_pdu = bis->sdu; memcpy(evt.sync_delay, bis->sdu_interval, 3); memcpy(evt.latency, bis->sdu_interval, 3); evt.interval = bis->latency / 1.25; evt.num_bis = cmd->num_bis; done: memcpy(pdu, &evt, sizeof(evt)); le_meta_event(dev, BT_HCI_EVT_LE_BIG_COMPLETE, pdu, pdu_len); free(pdu); return 0; } static int cmd_create_big_test(struct btdev *dev, const void *data, uint8_t len) { /* TODO */ return -ENOTSUP; } static int cmd_term_big(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_term_big *cmd = data; struct le_big *big; uint8_t status = BT_HCI_ERR_SUCCESS; /* If the BIG_Handle does not identify a BIG, the Controller shall * return the error code Unknown Advertising Identifier (0x42). */ big = queue_find(dev->le_big, match_big_handle, UINT_TO_PTR(cmd->handle)); if (!big) { status = BT_HCI_ERR_UNKNOWN_ADVERTISING_ID; goto done; } done: cmd_status(dev, status, BT_HCI_CMD_LE_TERM_BIG); if (status) return -EALREADY; return 0; } static bool match_bis(const void *data, const void *match_data) { const struct le_big *big = data; const struct btdev_conn *conn = match_data; return queue_find(big->bis, NULL, conn); } static int cmd_term_big_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_term_big *cmd = data; struct bt_hci_evt_le_big_terminate rsp; struct le_big *big, *rbig; struct btdev_conn *conn; struct btdev *remote = NULL; memset(&rsp, 0, sizeof(rsp)); rsp.reason = cmd->reason; rsp.handle = cmd->handle; le_meta_event(dev, BT_HCI_EVT_LE_BIG_TERMINATE, &rsp, sizeof(rsp)); big = queue_find(dev->le_big, match_big_handle, UINT_TO_PTR(cmd->handle)); if (!big) return 0; /* Cleanup existing connections */ while ((conn = queue_pop_head(big->bis))) { if (!conn->link) { conn_remove(conn); continue; } /* Send BIG Sync Lost event once per remote device */ if (conn->link->dev != remote) { struct bt_hci_evt_le_big_sync_lost evt; remote = conn->link->dev; rbig = queue_find(remote->le_big, match_bis, conn->link); if (rbig) { memset(&evt, 0, sizeof(evt)); evt.big_handle = rbig->handle; evt.reason = cmd->reason; le_meta_event(remote, BT_HCI_EVT_LE_BIG_SYNC_LOST, &evt, sizeof(evt)); queue_remove(remote->le_big, rbig); le_big_free(rbig); } } /* Unlink conn from remote BIS */ conn_unlink(conn, conn->link); conn_remove(conn); } queue_remove(dev->le_big, big); le_big_free(big); return 0; } static int cmd_big_create_sync(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_big_create_sync *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; uint16_t sync_handle = le16_to_cpu(cmd->sync_handle); /* If the Sync_Handle does not exist, the Controller shall return the * error code Unknown Advertising Identifier (0x42). */ if (!queue_find(dev->le_per_adv, match_sync_handle, UINT_TO_PTR(sync_handle))) { status = BT_HCI_ERR_UNKNOWN_ADVERTISING_ID; goto done; } /* If the Host sends this command with a BIG_Handle that is already * allocated, the Controller shall return the error code Command * Disallowed (0x0C). */ if (queue_find(dev->le_big, match_big_handle, UINT_TO_PTR(cmd->handle))) { status = BT_HCI_ERR_COMMAND_DISALLOWED; goto done; } /* If the Num_BIS parameter is greater than the total number of BISes * in the BIG, the Controller shall return the error code Unsupported * Feature or Parameter Value (0x11). */ if (cmd->num_bis != len - sizeof(*cmd)) status = BT_HCI_ERR_UNSUPPORTED_FEATURE; done: cmd_status(dev, status, BT_HCI_CMD_LE_BIG_CREATE_SYNC); return 0; } static int cmd_big_create_sync_complete(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_big_create_sync *cmd = data; struct __packed { struct bt_hci_evt_le_big_sync_estabilished ev; uint16_t bis[BIS_SIZE]; } pdu; struct btdev *remote; struct btdev_conn *conn = NULL; struct bt_hci_bis *bis; int i; uint16_t sync_handle = le16_to_cpu(cmd->sync_handle); struct le_per_adv *per_adv; struct le_big *big; memset(&pdu.ev, 0, sizeof(pdu.ev)); per_adv = queue_find(dev->le_per_adv, match_sync_handle, UINT_TO_PTR(sync_handle)); if (!per_adv) { pdu.ev.status = BT_HCI_ERR_UNKNOWN_ADVERTISING_ID; le_meta_event(dev, BT_HCI_EVT_LE_BIG_SYNC_ESTABILISHED, &pdu, sizeof(pdu.ev)); return 0; } remote = find_btdev_by_bdaddr_type(per_adv->addr, per_adv->addr_type); if (!remote) { pdu.ev.status = BT_HCI_ERR_UNKNOWN_ADVERTISING_ID; le_meta_event(dev, BT_HCI_EVT_LE_BIG_SYNC_ESTABILISHED, &pdu, sizeof(pdu.ev)); return 0; } big = le_big_new(dev, cmd->handle); if (!big) { pdu.ev.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; le_meta_event(dev, BT_HCI_EVT_LE_BIG_SYNC_ESTABILISHED, &pdu, sizeof(pdu.ev)); return 0; } for (i = 0; i < cmd->num_bis; i++) { conn = conn_link_bis(dev, remote, i); if (!conn) break; pdu.bis[i] = cpu_to_le16(conn->handle); queue_push_tail(big->bis, conn); } if (i != cmd->num_bis || !conn) { pdu.ev.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; le_meta_event(dev, BT_HCI_EVT_LE_BIG_SYNC_ESTABILISHED, &pdu, sizeof(pdu.ev)); return 0; } bis = conn->data; if (bis->encryption != cmd->encryption) { pdu.ev.status = BT_HCI_ERR_ENC_MODE_NOT_ACCEPTABLE; le_meta_event(dev, BT_HCI_EVT_LE_BIG_SYNC_ESTABILISHED, &pdu, sizeof(pdu.ev)); return 0; } pdu.ev.handle = cmd->handle; memcpy(pdu.ev.latency, bis->sdu_interval, sizeof(pdu.ev.interval)); pdu.ev.nse = 0x01; pdu.ev.bn = 0x01; pdu.ev.pto = 0x00; pdu.ev.irc = 0x01; pdu.ev.max_pdu = bis->sdu; pdu.ev.interval = bis->latency / 1.25; pdu.ev.num_bis = cmd->num_bis; le_meta_event(dev, BT_HCI_EVT_LE_BIG_SYNC_ESTABILISHED, &pdu, sizeof(pdu.ev) + (cmd->num_bis * sizeof(uint16_t))); return 0; } static int cmd_big_term_sync(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_big_term_sync *cmd = data; struct bt_hci_rsp_le_big_term_sync rsp; struct btdev_conn *conn; struct le_big *big = queue_find(dev->le_big, match_big_handle, UINT_TO_PTR(cmd->handle)); memset(&rsp, 0, sizeof(rsp)); /* If the Host issues this command with a BIG_Handle that does not * exist, the Controller shall return the error code Unknown * Advertising Identifier (0x42). */ if (!big) { rsp.status = BT_HCI_ERR_UNKNOWN_ADVERTISING_ID; goto done; } rsp.status = BT_HCI_ERR_COMMAND_DISALLOWED; rsp.handle = cmd->handle; /* Cleanup existing connections */ while ((conn = queue_pop_head(big->bis))) { rsp.status = BT_HCI_ERR_SUCCESS; /* Unlink conn from remote BIS */ conn_unlink(conn, conn->link); conn_remove(conn); } done: if (rsp.status == BT_HCI_ERR_SUCCESS) { queue_remove(dev->le_big, big); le_big_free(big); } cmd_complete(dev, BT_HCI_CMD_LE_BIG_TERM_SYNC, &rsp, sizeof(rsp)); return 0; } static int cmd_req_peer_sca(struct btdev *dev, const void *data, uint8_t len) { /* TODO */ return -ENOTSUP; } static int cmd_setup_iso_path(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_setup_iso_path *cmd = data; struct bt_hci_rsp_le_setup_iso_path rsp; struct btdev_conn *conn; memset(&rsp, 0, sizeof(rsp)); conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(cpu_to_le16(cmd->handle))); if (!conn) { rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; goto done; } /* Only support HCI or disabled paths */ if (cmd->path && cmd->path != 0xff) { rsp.status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } switch (cmd->direction) { case 0x00: dev->le_iso_path[0] = cmd->path; rsp.handle = cpu_to_le16(conn->handle); break; case 0x01: dev->le_iso_path[1] = cmd->path; rsp.handle = cpu_to_le16(conn->handle); break; default: rsp.status = BT_HCI_ERR_INVALID_PARAMETERS; } done: cmd_complete(dev, BT_HCI_CMD_LE_SETUP_ISO_PATH, &rsp, sizeof(rsp)); return 0; } static int cmd_remove_iso_path(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_remove_iso_path *cmd = data; struct bt_hci_rsp_le_remove_iso_path rsp; struct btdev_conn *conn; memset(&rsp, 0, sizeof(rsp)); rsp.handle = cmd->handle; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(cpu_to_le16(cmd->handle))); if (!conn) { rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; goto done; } if (cmd->direction > 0x3) { rsp.status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } if (cmd->direction & 0x1) dev->le_iso_path[0] = 0x00; if (cmd->direction & 0x2) dev->le_iso_path[1] = 0x00; done: cmd_complete(dev, BT_HCI_CMD_LE_REMOVE_ISO_PATH, &rsp, sizeof(rsp)); return 0; } static int cmd_iso_tx_test(struct btdev *dev, const void *data, uint8_t len) { /* TODO */ return -ENOTSUP; } static int cmd_iso_rx_test(struct btdev *dev, const void *data, uint8_t len) { /* TODO */ return -ENOTSUP; } static int cmd_iso_read_test_counter(struct btdev *dev, const void *data, uint8_t len) { /* TODO */ return -ENOTSUP; } static int cmd_iso_test_end(struct btdev *dev, const void *data, uint8_t len) { /* TODO */ return -ENOTSUP; } static int cmd_set_host_feature(struct btdev *dev, const void *data, uint8_t len) { uint8_t status = BT_HCI_ERR_SUCCESS; cmd_complete(dev, BT_HCI_CMD_LE_SET_HOST_FEATURE, &status, sizeof(status)); return 0; } static int cmd_read_local_codecs_v2(struct btdev *dev, const void *data, uint8_t len) { struct { struct bt_hci_rsp_read_local_codecs rsp; struct bt_hci_codec codec[6]; uint8_t num_vnd_codecs; } pdu; memset(&pdu, 0, sizeof(pdu)); pdu.rsp.status = BT_HCI_ERR_SUCCESS; pdu.rsp.num_codecs = 0x06; pdu.codec[0].id = 0x00; pdu.codec[0].transport = BT_HCI_LOCAL_CODEC_BREDR_SCO; pdu.codec[1].id = 0x01; pdu.codec[1].transport = BT_HCI_LOCAL_CODEC_BREDR_SCO; pdu.codec[2].id = 0x02; pdu.codec[2].transport = BT_HCI_LOCAL_CODEC_BREDR_SCO; pdu.codec[3].id = 0x03; pdu.codec[3].transport = BT_HCI_LOCAL_CODEC_BREDR_SCO; pdu.codec[4].id = 0x04; pdu.codec[4].transport = BT_HCI_LOCAL_CODEC_BREDR_SCO; pdu.codec[5].id = 0x05; pdu.codec[5].transport = BT_HCI_LOCAL_CODEC_BREDR_SCO; cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_CODECS_V2, &pdu, sizeof(pdu)); return 0; } static int cmd_read_local_codec_caps(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_read_local_codec_caps *cmd = data; struct bt_hci_rsp_read_local_codec_caps rsp; memset(&rsp, 0, sizeof(rsp)); if (cmd->codec.id > 0x05) rsp.status = BT_HCI_ERR_INVALID_PARAMETERS; cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_CODEC_CAPS, &rsp, sizeof(rsp)); return 0; } static int cmd_read_local_ctrl_delay(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_read_local_ctrl_delay *cmd = data; struct bt_hci_rsp_read_local_ctrl_delay rsp; memset(&rsp, 0, sizeof(rsp)); if (cmd->codec.id > 0x05) rsp.status = BT_HCI_ERR_INVALID_PARAMETERS; cmd_complete(dev, BT_HCI_CMD_READ_LOCAL_CTRL_DELAY, &rsp, sizeof(rsp)); return 0; } static int cmd_config_data_path(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_config_data_path *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; if (cmd->id > 0x05) status = BT_HCI_ERR_INVALID_PARAMETERS; cmd_complete(dev, BT_HCI_CMD_CONFIG_DATA_PATH, &status, sizeof(status)); return 0; } #define CMD_LE_52 \ CMD(BT_HCI_CMD_LE_PAST, cmd_past, NULL), \ CMD(BT_HCI_CMD_LE_PAST_SET_INFO, cmd_past_set_info, NULL), \ CMD(BT_HCI_CMD_LE_PAST_PARAMS, cmd_past_params, NULL), \ CMD(BT_HCI_CMD_LE_READ_BUFFER_SIZE_V2, cmd_read_size_v2, NULL), \ CMD(BT_HCI_CMD_LE_READ_ISO_TX_SYNC, cmd_read_iso_tx_sync, NULL), \ CMD(BT_HCI_CMD_LE_SET_EVENT_MASK, cmd_le_set_event_mask, NULL), \ CMD(BT_HCI_CMD_LE_SET_CIG_PARAMS, cmd_set_cig_params, NULL), \ CMD(BT_HCI_CMD_LE_SET_CIG_PARAMS_TEST, cmd_set_cig_params_test, NULL), \ CMD(BT_HCI_CMD_LE_CREATE_CIS, cmd_create_cis, \ cmd_create_cis_complete), \ CMD(BT_HCI_CMD_LE_REMOVE_CIG, cmd_remove_cig, NULL), \ CMD(BT_HCI_CMD_LE_ACCEPT_CIS, cmd_accept_cis, NULL), \ CMD(BT_HCI_CMD_LE_REJECT_CIS, cmd_reject_cis, NULL), \ CMD(BT_HCI_CMD_LE_CREATE_BIG, cmd_create_big, \ cmd_create_big_complete), \ CMD(BT_HCI_CMD_LE_CREATE_BIG_TEST, cmd_create_big_test, NULL), \ CMD(BT_HCI_CMD_LE_TERM_BIG, cmd_term_big, cmd_term_big_complete), \ CMD(BT_HCI_CMD_LE_BIG_CREATE_SYNC, cmd_big_create_sync, \ cmd_big_create_sync_complete), \ CMD(BT_HCI_CMD_LE_BIG_TERM_SYNC, cmd_big_term_sync, NULL), \ CMD(BT_HCI_CMD_LE_REQ_PEER_SCA, cmd_req_peer_sca, NULL), \ CMD(BT_HCI_CMD_LE_SETUP_ISO_PATH, cmd_setup_iso_path, NULL), \ CMD(BT_HCI_CMD_LE_REMOVE_ISO_PATH, cmd_remove_iso_path, NULL), \ CMD(BT_HCI_CMD_LE_ISO_TX_TEST, cmd_iso_tx_test, NULL), \ CMD(BT_HCI_CMD_LE_ISO_RX_TEST, cmd_iso_rx_test, NULL), \ CMD(BT_HCI_CMD_LE_ISO_READ_TEST_COUNTER, cmd_iso_read_test_counter, \ NULL), \ CMD(BT_HCI_CMD_LE_ISO_TEST_END, cmd_iso_test_end, NULL), \ CMD(BT_HCI_CMD_LE_SET_HOST_FEATURE, cmd_set_host_feature, NULL), \ CMD(BT_HCI_CMD_READ_LOCAL_CODECS_V2, cmd_read_local_codecs_v2, NULL), \ CMD(BT_HCI_CMD_READ_LOCAL_CODEC_CAPS, cmd_read_local_codec_caps, \ NULL), \ CMD(BT_HCI_CMD_READ_LOCAL_CTRL_DELAY, cmd_read_local_ctrl_delay, \ NULL), \ CMD(BT_HCI_CMD_CONFIG_DATA_PATH, cmd_config_data_path, NULL) static const struct btdev_cmd cmd_le_5_2[] = { CMD_COMMON_ALL, CMD_COMMON_BREDR_LE, CMD_COMMON_BREDR_20, CMD_COMMON_BREDR_LE_40, CMD_BREDR, CMD_LE, CMD_LE_50, CMD_LE_52, {} }; static void set_le_52_commands(struct btdev *btdev) { btdev->commands[40] |= BIT(6); /* LE PAST */ btdev->commands[40] |= BIT(7); /* LE PA Set Info Transfer */ btdev->commands[41] |= BIT(1); /* LE PAST Parameters */ btdev->commands[41] |= 0x20; /* LE Read Buffer Size v2 */ btdev->commands[41] |= 0x40; /* LE Read ISO TX Sync */ btdev->commands[41] |= 0x80; /* LE Set CIG Parameters */ btdev->commands[42] |= 0x01; /* LE Set CIG Parameters Test */ btdev->commands[42] |= 0x02; /* LE Create CIS */ btdev->commands[42] |= 0x04; /* LE Remove CIG */ btdev->commands[42] |= 0x08; /* LE Accept CIS */ btdev->commands[42] |= 0x10; /* LE Reject CIS */ btdev->commands[42] |= 0x20; /* LE Create BIG */ btdev->commands[42] |= 0x40; /* LE Create BIG Test */ btdev->commands[42] |= 0x80; /* LE Terminate BIG */ btdev->commands[43] |= 0x01; /* LE BIG Create Sync */ btdev->commands[43] |= 0x02; /* LE BIG Terminate Sync */ btdev->commands[43] |= 0x04; /* LE Request Peer SCA */ btdev->commands[43] |= 0x08; /* LE Setup ISO Path */ btdev->commands[43] |= 0x10; /* LE Remove ISO Path */ btdev->commands[43] |= 0x20; /* LE ISO TX Test */ btdev->commands[43] |= 0x40; /* LE ISO RX Test */ btdev->commands[43] |= 0x80; /* LE ISO Read Test Counter */ btdev->commands[44] |= 0x01; /* LE ISO Test End */ btdev->commands[44] |= 0x02; /* LE ISO Set Host Feature */ btdev->commands[45] |= 0x04; /* Read Local Supported Codecs v2 */ btdev->commands[45] |= 0x08; /* Read Local Supported Codecs Caps */ btdev->commands[45] |= 0x10; /* Read Local Supported Ctrl Delay */ btdev->commands[45] |= 0x20; /* Config Data Path */ btdev->cmds = cmd_le_5_2; } static const struct btdev_cmd cmd_le[] = { CMD_COMMON_ALL, CMD_COMMON_BREDR_LE, CMD_LE, {} }; static int cmd_le_read_all_local_features(struct btdev *dev, const void *data, uint8_t len) { struct bt_hci_rsp_le_read_all_local_features rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.page = 10; memcpy(rsp.features, dev->le_features, 248); cmd_complete(dev, BT_HCI_CMD_LE_READ_ALL_LOCAL_FEATURES, &rsp, sizeof(rsp)); return 0; } static int cmd_le_read_all_remote_features(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_read_all_remote_features *cmd = data; struct bt_hci_evt_le_read_all_remote_features_complete ev; struct btdev_conn *conn; uint8_t status = BT_HCI_ERR_SUCCESS; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (!conn) status = BT_HCI_ERR_UNKNOWN_CONN_ID; cmd_status(dev, status, BT_HCI_CMD_LE_READ_ALL_REMOTE_FEATURES); if (status) return 0; memset(&ev, 0, sizeof(ev)); ev.status = BT_HCI_ERR_SUCCESS; ev.handle = cpu_to_le16(conn->handle); ev.max_pages = 1; ev.valid_pages = 1; memcpy(ev.features, conn->link->dev->le_features, 248); le_meta_event(dev, BT_HCI_EVT_LE_READ_ALL_REMOTE_FEATURES_COMPLETE, &ev, sizeof(ev)); return 0; } static int cmd_set_host_feature_v2(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_set_host_feature_v2 *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; uint16_t bit = le16_to_cpu(cmd->bit_number); uint8_t page = bit / 8; uint8_t mask = BIT(bit % 8); /* Only allow setting host controlled features */ if (page >= sizeof(dev->le_features)) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } if (cmd->bit_value) dev->le_features[page] |= mask; else dev->le_features[page] &= ~mask; done: cmd_complete(dev, BT_HCI_CMD_LE_SET_HOST_FEATURE_V2, &status, sizeof(status)); return 0; } #define CMD_LE_60 \ CMD(BT_HCI_CMD_LE_READ_ALL_LOCAL_FEATURES, \ cmd_le_read_all_local_features, NULL), \ CMD(BT_HCI_CMD_LE_READ_ALL_REMOTE_FEATURES, \ cmd_le_read_all_remote_features, NULL), \ CMD(BT_HCI_CMD_LE_SET_HOST_FEATURE_V2, \ cmd_set_host_feature_v2, NULL) static const struct btdev_cmd cmd_le_6_0[] = { CMD_COMMON_ALL, CMD_COMMON_BREDR_LE, CMD_COMMON_BREDR_20, CMD_COMMON_BREDR_LE_40, CMD_BREDR, CMD_LE, CMD_LE_50, CMD_LE_52, CMD_LE_60, {} }; static void set_le_60_commands(struct btdev *btdev) { btdev->commands[47] |= BIT(2); /* LE Read All Local Features */ btdev->commands[47] |= BIT(3); /* LE Read All Remote Features */ btdev->commands[47] |= BIT(4); /* LE Set Host Feature V2 */ btdev->cmds = cmd_le_6_0; } static void set_le_commands(struct btdev *btdev) { set_common_commands_all(btdev); set_common_commands_bredrle(btdev); btdev->commands[24] |= 0x20; /* Read LE Host Supported */ btdev->commands[24] |= 0x20; /* Write LE Host Supported */ btdev->commands[25] |= 0x01; /* LE Set Event Mask */ btdev->commands[25] |= 0x02; /* LE Read Buffer Size */ btdev->commands[25] |= 0x04; /* LE Read Local Features */ btdev->commands[25] |= 0x10; /* LE Set Random Address */ btdev->commands[25] |= 0x20; /* LE Set Adv Parameters */ btdev->commands[25] |= 0x40; /* LE Read Adv TX Power */ btdev->commands[25] |= 0x80; /* LE Set Adv Data */ btdev->commands[26] |= 0x01; /* LE Set Scan Response Data */ btdev->commands[26] |= 0x02; /* LE Set Adv Enable */ btdev->commands[26] |= 0x04; /* LE Set Scan Parameters */ btdev->commands[26] |= 0x08; /* LE Set Scan Enable */ btdev->commands[26] |= 0x10; /* LE Create Connection */ btdev->commands[26] |= 0x20; /* LE Create Connection Cancel */ btdev->commands[26] |= 0x40; /* LE Read Accept List Size */ btdev->commands[26] |= 0x80; /* LE Clear Accept List */ btdev->commands[27] |= 0x01; /* LE Add Device to Accept List */ btdev->commands[27] |= 0x02; /* LE Remove Device from Accept List */ btdev->commands[27] |= 0x04; /* LE Connection Update */ btdev->commands[27] |= 0x20; /* LE Read Remote Used Features */ btdev->commands[27] |= 0x40; /* LE Encrypt */ btdev->commands[27] |= 0x80; /* LE Rand */ btdev->commands[28] |= 0x01; /* LE Start Encryption */ btdev->commands[28] |= 0x02; /* LE Long Term Key Request Reply */ btdev->commands[28] |= 0x04; /* LE Long Term Key Request Neg Reply */ btdev->commands[28] |= 0x08; /* LE Read Supported States */ btdev->commands[28] |= 0x10; /* LE Receiver Test */ btdev->commands[28] |= 0x20; /* LE Transmitter Test */ btdev->commands[28] |= 0x40; /* LE Test End */ /* Extra LE commands for >= 4.1 adapters */ btdev->commands[33] |= 0x10; /* LE Remote Conn Param Req Reply */ btdev->commands[33] |= 0x20; /* LE Remote Conn Param Req Neg Reply */ /* Extra LE commands for >= 4.2 adapters */ btdev->commands[34] |= 0x02; /* LE Read Local P-256 Public Key */ btdev->commands[34] |= 0x04; /* LE Generate DHKey */ btdev->commands[34] |= 0x08; /* LE Add Device To Resolving List */ btdev->commands[34] |= 0x10; /* LE Remove Dev From Resolving List */ btdev->commands[34] |= 0x20; /* LE Clear Resolving List */ btdev->commands[34] |= 0x40; /* LE Read Resolving List Size */ btdev->commands[34] |= 0x80; /* LE Read Peer Resolvable Address */ btdev->commands[35] |= 0x01; /* LE Read Local Resolvable Address */ btdev->commands[35] |= 0x02; /* LE Set Address Resolution Enable */ btdev->commands[35] |= 0x04; /* LE Set RPA Timeout */ btdev->cmds = cmd_le; if (btdev->type >= BTDEV_TYPE_BREDRLE) set_bredrle_40_commands(btdev); /* Extra LE commands for >= 5.0 adapters */ if (btdev->type >= BTDEV_TYPE_BREDRLE50) { set_le_50_commands(btdev); btdev->cmds = cmd_le_5_0; } /* Extra LE commands for >= 5.2 adapters */ if (btdev->type >= BTDEV_TYPE_BREDRLE52) { set_le_52_commands(btdev); btdev->cmds = cmd_le_5_2; } /* Extra LE commands for >= 6.0 adapters */ if (btdev->type >= BTDEV_TYPE_BREDRLE60) { set_le_60_commands(btdev); btdev->cmds = cmd_le_6_0; } } static void set_bredrle_commands(struct btdev *btdev) { set_bredr_commands(btdev); set_le_commands(btdev); } static void set_amp_commands(struct btdev *btdev) { set_common_commands_all(btdev); btdev->commands[22] |= 0x20; /* Read Local AMP Info */ } static void set_bredrle_features(struct btdev *btdev) { btdev->features[0] |= 0x04; /* Encryption */ btdev->features[0] |= 0x20; /* Role switch */ btdev->features[0] |= 0x80; /* Sniff mode */ btdev->features[1] |= 0x08; /* SCO link */ btdev->features[2] |= 0x08; /* Transparent SCO */ btdev->features[3] |= 0x40; /* RSSI with inquiry results */ btdev->features[3] |= 0x80; /* Extended SCO link */ btdev->features[4] |= 0x08; /* AFH capable peripheral */ btdev->features[4] |= 0x10; /* AFH classification peripheral */ btdev->features[4] |= 0x40; /* LE Supported */ btdev->features[5] |= 0x02; /* Sniff subrating */ btdev->features[5] |= 0x04; /* Pause encryption */ btdev->features[5] |= 0x08; /* AFH capable central */ btdev->features[5] |= 0x10; /* AFH classification central */ btdev->features[6] |= 0x01; /* Extended Inquiry Response */ btdev->features[6] |= 0x02; /* Simultaneous LE and BR/EDR */ btdev->features[6] |= 0x08; /* Secure Simple Pairing */ btdev->features[6] |= 0x10; /* Encapsulated PDU */ btdev->features[6] |= 0x20; /* Erroneous Data Reporting */ btdev->features[6] |= 0x40; /* Non-flushable Packet Boundary Flag */ btdev->features[7] |= 0x01; /* Link Supervision Timeout Event */ btdev->features[7] |= 0x02; /* Inquiry TX Power Level */ btdev->features[7] |= 0x80; /* Extended features */ if (btdev->type >= BTDEV_TYPE_BREDRLE50) { /* These BREDR features are added to test new configuration * command. If this is added above it will break existing tests */ btdev->features[0] |= 0x01; /* 3 slot Packets */ btdev->features[0] |= 0x02; /* 5 slot Packets */ btdev->features[3] |= 0x02; /* EDR ACL 2M mode */ btdev->features[3] |= 0x04; /* EDR ACL 3M mode */ btdev->features[4] |= 0x80; /* 3 slot EDR ACL packets */ btdev->features[5] |= 0x01; /* 5 slot EDR ACL packets */ btdev->le_features[0] |= 0x40; /* LE PRIVACY */ btdev->le_features[1] |= 0x01; /* LE 2M PHY */ btdev->le_features[1] |= 0x08; /* LE Coded PHY */ btdev->le_features[1] |= 0x10; /* LE EXT ADV */ } if (btdev->type >= BTDEV_TYPE_BREDRLE52) { btdev->version = 0x0b; btdev->le_features[1] |= 0x20; /* LE PER ADV */ btdev->le_features[3] |= BIT(0); /* LE PAST Sender */ btdev->le_features[3] |= BIT(1); /* LE PAST Receiver */ btdev->le_features[3] |= 0x10; /* LE CIS Central */ btdev->le_features[3] |= 0x20; /* LE CIS Peripheral */ btdev->le_features[3] |= 0x40; /* LE ISO Broadcaster */ btdev->le_features[3] |= 0x80; /* LE Synchronized Receiver */ btdev->le_features[4] |= 0x01; /* LE ISO channels */ } if (btdev->type >= BTDEV_TYPE_BREDRLE60) { btdev->version = 0x0e; btdev->le_features[7] |= BIT(7); /* LL Extended Features */ } btdev->feat_page_2[0] |= 0x01; /* CPB - Central Operation */ btdev->feat_page_2[0] |= 0x02; /* CPB - Peripheral Operation */ btdev->feat_page_2[0] |= 0x04; /* Synchronization Train */ btdev->feat_page_2[0] |= 0x08; /* Synchronization Scan */ btdev->feat_page_2[0] |= 0x10; /* Inquiry Response Notification */ btdev->feat_page_2[1] |= 0x01; /* Secure Connections */ btdev->feat_page_2[1] |= 0x02; /* Ping */ btdev->max_page = 2; } static void set_bredr_features(struct btdev *btdev) { btdev->features[0] |= 0x04; /* Encryption */ btdev->features[0] |= 0x20; /* Role switch */ btdev->features[0] |= 0x80; /* Sniff mode */ btdev->features[1] |= 0x08; /* SCO link */ btdev->features[3] |= 0x40; /* RSSI with inquiry results */ btdev->features[3] |= 0x80; /* Extended SCO link */ btdev->features[4] |= 0x08; /* AFH capable peripheral */ btdev->features[4] |= 0x10; /* AFH classification peripheral */ btdev->features[5] |= 0x02; /* Sniff subrating */ btdev->features[5] |= 0x04; /* Pause encryption */ btdev->features[5] |= 0x08; /* AFH capable central */ btdev->features[5] |= 0x10; /* AFH classification central */ btdev->features[6] |= 0x01; /* Extended Inquiry Response */ btdev->features[6] |= 0x08; /* Secure Simple Pairing */ btdev->features[6] |= 0x10; /* Encapsulated PDU */ btdev->features[6] |= 0x20; /* Erroneous Data Reporting */ btdev->features[6] |= 0x40; /* Non-flushable Packet Boundary Flag */ btdev->features[7] |= 0x01; /* Link Supervision Timeout Event */ btdev->features[7] |= 0x02; /* Inquiry TX Power Level */ btdev->features[7] |= 0x80; /* Extended features */ btdev->max_page = 1; } static void set_bredr20_features(struct btdev *btdev) { btdev->features[0] |= 0x04; /* Encryption */ btdev->features[0] |= 0x20; /* Role switch */ btdev->features[0] |= 0x80; /* Sniff mode */ btdev->features[1] |= 0x08; /* SCO link */ btdev->features[3] |= 0x40; /* RSSI with inquiry results */ btdev->features[3] |= 0x80; /* Extended SCO link */ btdev->features[4] |= 0x08; /* AFH capable peripheral */ btdev->features[4] |= 0x10; /* AFH classification peripheral */ btdev->features[5] |= 0x02; /* Sniff subrating */ btdev->features[5] |= 0x04; /* Pause encryption */ btdev->features[5] |= 0x08; /* AFH capable central */ btdev->features[5] |= 0x10; /* AFH classification central */ btdev->features[7] |= 0x80; /* Extended features */ btdev->max_page = 1; } static void set_le_features(struct btdev *btdev) { btdev->features[4] |= 0x20; /* BR/EDR Not Supported */ btdev->features[4] |= 0x40; /* LE Supported */ btdev->max_page = 1; btdev->le_features[0] |= 0x01; /* LE Encryption */ btdev->le_features[0] |= 0x02; /* Connection Parameters Request */ btdev->le_features[0] |= 0x08; /* Peripheral-initd Features Exchange */ } static void set_le_states(struct btdev *btdev) { /* Set all 41 bits as per Bluetooth 5.0 specification */ btdev->le_states[0] = 0xff; btdev->le_states[1] = 0xff; btdev->le_states[2] = 0xff; btdev->le_states[3] = 0xff; btdev->le_states[4] = 0xff; btdev->le_states[5] = 0x03; al_clear(btdev); rl_clear(btdev); btdev->le_rl_enable = 0x00; btdev->le_rl_timeout = 0x0384; /* 900 secs or 15 minutes */ } static void set_amp_features(struct btdev *btdev) { } struct btdev *btdev_create(enum btdev_type type, uint16_t id) { struct btdev *btdev; int index; btdev = malloc(sizeof(*btdev)); if (!btdev) return NULL; memset(btdev, 0, sizeof(*btdev)); switch (type) { case BTDEV_TYPE_BREDRLE: case BTDEV_TYPE_LE: case BTDEV_TYPE_BREDRLE50: case BTDEV_TYPE_BREDRLE52: case BTDEV_TYPE_BREDRLE60: btdev->crypto = bt_crypto_new(); if (!btdev->crypto) { free(btdev); return NULL; } break; case BTDEV_TYPE_BREDR: case BTDEV_TYPE_BREDR20: case BTDEV_TYPE_AMP: default: break; } btdev->type = type; btdev->id = id; btdev->manufacturer = 1521; btdev->revision = 0x0000; switch (btdev->type) { case BTDEV_TYPE_BREDRLE: case BTDEV_TYPE_BREDRLE50: case BTDEV_TYPE_BREDRLE52: case BTDEV_TYPE_BREDRLE60: btdev->version = 0x09; set_bredrle_features(btdev); set_bredrle_commands(btdev); set_le_states(btdev); break; case BTDEV_TYPE_BREDR: btdev->version = 0x05; set_bredr_features(btdev); set_bredr_commands(btdev); break; case BTDEV_TYPE_LE: btdev->version = 0x09; set_le_features(btdev); set_le_commands(btdev); set_le_states(btdev); break; case BTDEV_TYPE_AMP: btdev->version = 0x01; set_amp_features(btdev); set_amp_commands(btdev); break; case BTDEV_TYPE_BREDR20: btdev->version = 0x03; set_bredr20_features(btdev); set_bredr20_commands(btdev); break; } btdev_init_param(btdev); index = add_btdev(btdev); if (index < 0) { bt_crypto_unref(btdev->crypto); free(btdev); return NULL; } get_bdaddr(id, index, btdev->bdaddr); btdev->conns = queue_new(); btdev->le_ext_adv = queue_new(); btdev->le_per_adv = queue_new(); btdev->le_big = queue_new(); btdev->le_al_len = AL_SIZE; btdev->le_rl_len = RL_SIZE; return btdev; } void btdev_destroy(struct btdev *btdev) { if (!btdev) return; if (btdev->inquiry_id > 0) timeout_remove(btdev->inquiry_id); bt_crypto_unref(btdev->crypto); del_btdev(btdev); queue_destroy(btdev->conns, conn_remove); queue_destroy(btdev->le_ext_adv, le_ext_adv_free); queue_destroy(btdev->le_per_adv, free); queue_destroy(btdev->le_big, le_big_free); free(btdev); } bool btdev_set_debug(struct btdev *btdev, btdev_debug_func_t callback, void *user_data, btdev_destroy_func_t destroy) { if (!btdev) return false; if (btdev->debug_destroy) btdev->debug_destroy(btdev->debug_data); btdev->debug_callback = callback; btdev->debug_destroy = destroy; btdev->debug_data = user_data; return true; } const uint8_t *btdev_get_bdaddr(struct btdev *btdev) { return btdev->bdaddr; } bool btdev_set_bdaddr(struct btdev *btdev, const uint8_t *bdaddr) { if (!btdev || !bdaddr) return false; memcpy(btdev->bdaddr, bdaddr, sizeof(btdev->bdaddr)); return true; } uint8_t *btdev_get_features(struct btdev *btdev) { return btdev->features; } uint8_t *btdev_get_commands(struct btdev *btdev) { return btdev->commands; } uint8_t btdev_get_scan_enable(struct btdev *btdev) { return btdev->scan_enable; } uint8_t btdev_get_le_scan_enable(struct btdev *btdev) { return btdev->le_scan_enable; } const uint8_t *btdev_get_adv_addr(struct btdev *btdev, uint8_t handle) { struct le_ext_adv *ext_adv; /* Check if Ext Adv is already existed */ ext_adv = queue_find(btdev->le_ext_adv, match_ext_adv_handle, UINT_TO_PTR(handle)); if (!ext_adv) return NULL; return ext_adv_addr(btdev, ext_adv); } void btdev_get_mtu(struct btdev *btdev, uint16_t *acl, uint16_t *sco, uint16_t *iso) { if (acl) *acl = btdev->acl_mtu; if (sco) *sco = btdev->sco_mtu; if (iso) *iso = btdev->iso_mtu; } void btdev_set_le_states(struct btdev *btdev, const uint8_t *le_states) { memcpy(btdev->le_states, le_states, sizeof(btdev->le_states)); } void btdev_set_command_handler(struct btdev *btdev, btdev_command_func handler, void *user_data) { if (!btdev) return; btdev->command_handler = handler; btdev->command_data = user_data; } void btdev_set_send_handler(struct btdev *btdev, btdev_send_func handler, void *user_data) { if (!btdev) return; btdev->send_handler = handler; btdev->send_data = user_data; } static void num_completed_packets(struct btdev *btdev, struct btdev_conn *conn) { struct bt_hci_evt_num_completed_packets ncp; ncp.num_handles = 1; ncp.handle = cpu_to_le16(conn->handle); ncp.count = cpu_to_le16(1); send_event(btdev, BT_HCI_EVT_NUM_COMPLETED_PACKETS, &ncp, sizeof(ncp)); } static const struct btdev_cmd *run_cmd(struct btdev *btdev, const struct btdev_cmd *cmd, const void *data, uint8_t len) { uint8_t status = BT_HCI_ERR_UNKNOWN_COMMAND; int err; err = cmd->func(btdev, data, len); switch (err) { case 0: return cmd; case -ENOTSUP: status = BT_HCI_ERR_UNKNOWN_COMMAND; break; case -EINVAL: status = BT_HCI_ERR_INVALID_PARAMETERS; break; case -EPERM: status = BT_HCI_ERR_COMMAND_DISALLOWED; break; case -EEXIST: status = BT_HCI_ERR_CONN_ALREADY_EXISTS; break; case -ENOENT: status = BT_HCI_ERR_UNKNOWN_CONN_ID; break; case -EALREADY: return NULL; default: status = BT_HCI_ERR_UNSPECIFIED_ERROR; break; } cmd_status(btdev, status, cmd->opcode); return NULL; } static const struct btdev_cmd *vnd_cmd(struct btdev *btdev, uint8_t op, const struct btdev_cmd *cmd, const void *data, uint8_t len) { uint8_t opcode = ((const uint8_t *)data)[0]; for (; cmd && cmd->func; cmd++) { if (cmd->opcode != opcode) continue; return run_cmd(btdev, cmd, data, len); } util_debug(btdev->debug_callback, btdev->debug_data, "Unsupported Vendor subcommand 0x%2.2x", opcode); cmd_status(btdev, BT_HCI_ERR_UNKNOWN_COMMAND, op); return NULL; } static const struct btdev_cmd *default_cmd(struct btdev *btdev, uint16_t opcode, const void *data, uint8_t len) { const struct btdev_cmd *cmd; if (btdev->emu_opcode == opcode) return vnd_cmd(btdev, opcode, btdev->emu_cmds, data, len); if (btdev->msft_opcode == opcode) return vnd_cmd(btdev, opcode, btdev->msft_cmds, data, len); for (cmd = btdev->cmds; cmd->func; cmd++) { if (cmd->opcode != opcode) continue; return run_cmd(btdev, cmd, data, len); } util_debug(btdev->debug_callback, btdev->debug_data, "Unsupported command 0x%4.4x", opcode); cmd_status(btdev, BT_HCI_ERR_UNKNOWN_COMMAND, opcode); return NULL; } struct btdev_callback { void (*function)(btdev_callback callback, uint8_t response, uint8_t status, const void *data, uint8_t len); void *user_data; uint16_t opcode; const void *data; uint8_t len; }; void btdev_command_response(btdev_callback callback, uint8_t response, uint8_t status, const void *data, uint8_t len) { callback->function(callback, response, status, data, len); } static void handler_callback(btdev_callback callback, uint8_t response, uint8_t status, const void *data, uint8_t len) { struct btdev *btdev = callback->user_data; const struct btdev_cmd *cmd; switch (response) { case BTDEV_RESPONSE_DEFAULT: if (!run_hooks(btdev, BTDEV_HOOK_PRE_CMD, callback->opcode, callback->data, callback->len)) return; cmd = default_cmd(btdev, callback->opcode, callback->data, callback->len); if (!cmd) return; if (!run_hooks(btdev, BTDEV_HOOK_PRE_EVT, callback->opcode, callback->data, callback->len)) return; if (cmd->complete) cmd->complete(btdev, callback->data, callback->len); break; case BTDEV_RESPONSE_COMMAND_STATUS: cmd_status(btdev, status, callback->opcode); break; case BTDEV_RESPONSE_COMMAND_COMPLETE: cmd_complete(btdev, callback->opcode, data, len); break; default: cmd_status(btdev, BT_HCI_ERR_UNKNOWN_COMMAND, callback->opcode); break; } } static void process_cmd(struct btdev *btdev, const void *data, uint16_t len) { struct btdev_callback callback; const struct bt_hci_cmd_hdr *hdr = data; const struct btdev_cmd *cmd; if (len < sizeof(*hdr)) return; callback.function = handler_callback; callback.user_data = btdev; callback.opcode = le16_to_cpu(hdr->opcode); callback.data = data + sizeof(*hdr); callback.len = hdr->plen; util_debug(btdev->debug_callback, btdev->debug_data, "command 0x%04x", callback.opcode); if (btdev->command_handler) btdev->command_handler(callback.opcode, callback.data, callback.len, &callback, btdev->command_data); else { if (!run_hooks(btdev, BTDEV_HOOK_PRE_CMD, callback.opcode, callback.data, callback.len)) return; cmd = default_cmd(btdev, callback.opcode, callback.data, callback.len); if (!cmd) return; if (!run_hooks(btdev, BTDEV_HOOK_PRE_EVT, callback.opcode, callback.data, callback.len)) return; if (cmd->complete) cmd->complete(btdev, callback.data, callback.len); } } static void send_acl(struct btdev *dev, const void *data, uint16_t len) { struct bt_hci_acl_hdr hdr; struct iovec iov[3]; struct btdev_conn *conn; uint8_t pkt_type = BT_H4_ACL_PKT; /* Packet type */ iov[0].iov_base = &pkt_type; iov[0].iov_len = sizeof(pkt_type); memcpy(&hdr, data, sizeof(hdr)); conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(acl_handle(hdr.handle))); if (!conn) return; num_completed_packets(dev, conn); /* ACL_START_NO_FLUSH is only allowed from host to controller. * From controller to host this should be converted to ACL_START. */ if (acl_flags(hdr.handle) == ACL_START_NO_FLUSH) hdr.handle = acl_handle_pack(conn->handle, ACL_START); iov[1].iov_base = &hdr; iov[1].iov_len = sizeof(hdr); iov[2].iov_base = (void *) (data + sizeof(hdr)); iov[2].iov_len = len - sizeof(hdr); send_packet(conn->link->dev, iov, 3); } static void send_sco(struct btdev *dev, const void *data, uint16_t len) { struct bt_hci_sco_hdr *hdr; struct iovec iov[2]; struct btdev_conn *conn; uint8_t pkt_type = BT_H4_SCO_PKT; /* Packet type */ iov[0].iov_base = &pkt_type; iov[0].iov_len = sizeof(pkt_type); iov[1].iov_base = hdr = (void *) (data); iov[1].iov_len = len; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(acl_handle(hdr->handle))); if (!conn) return; if (dev->sco_flowctl) num_completed_packets(dev, conn); if (conn->link) send_packet(conn->link->dev, iov, 2); } static void send_iso(struct btdev *dev, const void *data, uint16_t len) { struct bt_hci_acl_hdr *hdr; struct iovec iov[2]; struct btdev_conn *conn; uint8_t pkt_type = BT_H4_ISO_PKT; /* Packet type */ iov[0].iov_base = &pkt_type; iov[0].iov_len = sizeof(pkt_type); iov[1].iov_base = hdr = (void *) (data); iov[1].iov_len = len; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(acl_handle(hdr->handle))); if (!conn) return; num_completed_packets(dev, conn); if (conn->link) send_packet(conn->link->dev, iov, 2); } void btdev_receive_h4(struct btdev *btdev, const void *data, uint16_t len) { uint8_t pkt_type; if (!btdev) return; if (len < 1) return; util_hexdump('>', data, len, btdev->debug_callback, btdev->debug_data); pkt_type = ((const uint8_t *) data)[0]; switch (pkt_type) { case BT_H4_CMD_PKT: process_cmd(btdev, data + 1, len - 1); break; case BT_H4_ACL_PKT: send_acl(btdev, data + 1, len - 1); break; case BT_H4_SCO_PKT: send_sco(btdev, data + 1, len - 1); break; case BT_H4_ISO_PKT: send_iso(btdev, data + 1, len - 1); break; default: util_debug(btdev->debug_callback, btdev->debug_data, "Unsupported packet 0x%2.2x", pkt_type); break; } } int btdev_add_hook(struct btdev *btdev, enum btdev_hook_type type, uint16_t opcode, btdev_hook_func handler, void *user_data) { int i; if (!btdev) return -1; if (get_hook_index(btdev, type, opcode) > 0) return -1; for (i = 0; i < MAX_HOOK_ENTRIES; i++) { if (btdev->hook_list[i] == NULL) { btdev->hook_list[i] = malloc(sizeof(struct hook)); if (btdev->hook_list[i] == NULL) return -1; btdev->hook_list[i]->handler = handler; btdev->hook_list[i]->user_data = user_data; btdev->hook_list[i]->opcode = opcode; btdev->hook_list[i]->type = type; return i; } } return -1; } bool btdev_del_hook(struct btdev *btdev, enum btdev_hook_type type, uint16_t opcode) { int i; if (!btdev) return false; for (i = 0; i < MAX_HOOK_ENTRIES; i++) { if (btdev->hook_list[i] == NULL) continue; if (btdev->hook_list[i]->type != type || btdev->hook_list[i]->opcode != opcode) continue; free(btdev->hook_list[i]); btdev->hook_list[i] = NULL; return true; } return false; } static int cmd_msft_read_features(struct btdev *dev, const void *data, uint8_t len) { struct msft_rsp_read_supported_features rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.subcmd = MSFT_SUBCMD_READ_SUPPORTED_FEATURES; rsp.features[0] = MSFT_MONITOR_BREDR_RSSI | MSFT_MONITOR_LE_RSSI | MSFT_MONITOR_LE_LEGACY_RSSI | MSFT_MONITOR_LE_ADV | MSFT_MONITOR_SSP_VALIDATION | MSFT_MONITOR_LE_ADV_CONTINUOS; cmd_complete(dev, dev->msft_opcode, &rsp, sizeof(rsp)); return 0; } static int cmd_msft_monitor_rssi(struct btdev *dev, const void *data, uint8_t len) { const struct msft_cmd_monitor_rssi *cmd = data; struct msft_rsp_monitor_rssi rsp; struct btdev_conn *conn; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.subcmd = MSFT_SUBCMD_MONITOR_RSSI; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (!conn) rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; cmd_complete(dev, dev->msft_opcode, &rsp, sizeof(rsp)); return 0; } static int cmd_msft_cancel_monitor_rssi(struct btdev *dev, const void *data, uint8_t len) { const struct msft_cmd_cancel_monitor_rssi *cmd = data; struct msft_rsp_cancel_monitor_rssi rsp; struct btdev_conn *conn; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.subcmd = MSFT_SUBCMD_CANCEL_MONITOR_RSSI; conn = queue_find(dev->conns, match_handle, UINT_TO_PTR(le16_to_cpu(cmd->handle))); if (!conn) rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; cmd_complete(dev, dev->msft_opcode, &rsp, sizeof(rsp)); return 0; } static int cmd_msft_le_monitor_adv(struct btdev *dev, const void *data, uint8_t len) { const struct msft_cmd_le_monitor_adv *cmd = data; struct msft_rsp_le_monitor_adv rsp; static uint8_t handle; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.subcmd = MSFT_SUBCMD_LE_MONITOR_ADV; switch (cmd->type) { case MSFT_LE_MONITOR_ADV_PATTERN: case MSFT_LE_MONITOR_ADV_UUID: case MSFT_LE_MONITOR_ADV_IRK: case MSFT_LE_MONITOR_ADV_ADDR: rsp.handle = handle++; break; default: rsp.status = BT_HCI_ERR_INVALID_PARAMETERS; break; } cmd_complete(dev, dev->msft_opcode, &rsp, sizeof(rsp)); return 0; } static int cmd_msft_le_cancel_monitor_adv(struct btdev *dev, const void *data, uint8_t len) { struct msft_rsp_le_cancel_monitor_adv rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.subcmd = MSFT_SUBCMD_LE_CANCEL_MONITOR_ADV; cmd_complete(dev, dev->msft_opcode, &rsp, sizeof(rsp)); return 0; } static int cmd_msft_le_monitor_adv_enable(struct btdev *dev, const void *data, uint8_t len) { struct msft_rsp_le_cancel_monitor_adv rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.subcmd = MSFT_SUBCMD_LE_MONITOR_ADV_ENABLE; cmd_complete(dev, dev->msft_opcode, &rsp, sizeof(rsp)); return 0; } static int cmd_msft_read_abs_rssi(struct btdev *dev, const void *data, uint8_t len) { struct msft_rsp_read_abs_rssi rsp; memset(&rsp, 0, sizeof(rsp)); rsp.status = BT_HCI_ERR_SUCCESS; rsp.subcmd = MSFT_SUBCMD_READ_ABS_RSSI; cmd_complete(dev, dev->msft_opcode, &rsp, sizeof(rsp)); return 0; } #define CMD_MSFT \ CMD(MSFT_SUBCMD_READ_SUPPORTED_FEATURES, cmd_msft_read_features, \ NULL), \ CMD(MSFT_SUBCMD_MONITOR_RSSI, cmd_msft_monitor_rssi, NULL), \ CMD(MSFT_SUBCMD_CANCEL_MONITOR_RSSI, cmd_msft_cancel_monitor_rssi, \ NULL), \ CMD(MSFT_SUBCMD_LE_MONITOR_ADV, cmd_msft_le_monitor_adv, NULL), \ CMD(MSFT_SUBCMD_LE_CANCEL_MONITOR_ADV, cmd_msft_le_cancel_monitor_adv, \ NULL), \ CMD(MSFT_SUBCMD_LE_MONITOR_ADV_ENABLE, cmd_msft_le_monitor_adv_enable, \ NULL), \ CMD(MSFT_SUBCMD_READ_ABS_RSSI, cmd_msft_read_abs_rssi, NULL) static const struct btdev_cmd cmd_msft[] = { CMD_MSFT, {} }; int btdev_set_msft_opcode(struct btdev *btdev, uint16_t opcode) { if (!btdev) return -EINVAL; switch (btdev->type) { case BTDEV_TYPE_BREDRLE: case BTDEV_TYPE_BREDRLE50: case BTDEV_TYPE_BREDRLE52: case BTDEV_TYPE_BREDRLE60: btdev->msft_opcode = opcode; btdev->msft_cmds = cmd_msft; return 0; case BTDEV_TYPE_BREDR: case BTDEV_TYPE_LE: case BTDEV_TYPE_AMP: case BTDEV_TYPE_BREDR20: default: return -ENOTSUP; } } int btdev_set_aosp_capable(struct btdev *btdev, bool enable) { if (!btdev) return -EINVAL; btdev->aosp_capable = enable; return 0; } static int cmd_emu_test_event(struct btdev *dev, const void *data, uint8_t len) { const struct emu_cmd_test_event *cmd = data; uint8_t status = BT_HCI_ERR_SUCCESS; if (len < sizeof(*cmd)) { status = BT_HCI_ERR_INVALID_PARAMETERS; goto done; } send_event(dev, cmd->evt, cmd->data, len - sizeof(*cmd)); done: cmd_complete(dev, dev->emu_opcode, &status, sizeof(status)); return 0; } #define CMD_EMU \ CMD(EMU_SUBCMD_TEST_EVENT, cmd_emu_test_event, NULL) static const struct btdev_cmd cmd_emu[] = { CMD_EMU, {} }; int btdev_set_emu_opcode(struct btdev *btdev, uint16_t opcode) { if (!btdev) return -EINVAL; switch (btdev->type) { case BTDEV_TYPE_BREDRLE: case BTDEV_TYPE_BREDRLE50: case BTDEV_TYPE_BREDRLE52: case BTDEV_TYPE_BREDRLE60: btdev->emu_opcode = opcode; btdev->emu_cmds = cmd_emu; return 0; case BTDEV_TYPE_BREDR: case BTDEV_TYPE_LE: case BTDEV_TYPE_AMP: case BTDEV_TYPE_BREDR20: default: return -ENOTSUP; } } bluez-5.87/emulator/PaxHeaders/btdev.h0000644000000000000000000000005015110075565014765 xustar0020 atime=1770585999 20 ctime=1783094102 bluez-5.87/emulator/btdev.h0000644000000000000000000000643315110075565014454 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2012 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #include #include #define BTDEV_RESPONSE_DEFAULT 0 #define BTDEV_RESPONSE_COMMAND_STATUS 1 #define BTDEV_RESPONSE_COMMAND_COMPLETE 2 typedef struct btdev_callback * btdev_callback; void btdev_command_response(btdev_callback callback, uint8_t response, uint8_t status, const void *data, uint8_t len); #define btdev_command_default(callback) \ btdev_command_response(callback, \ BTDEV_RESPONSE_DEFAULT, 0x00, NULL, 0); #define btdev_command_status(callback, status) \ btdev_command_response(callback, \ BTDEV_RESPONSE_COMMAND_STATUS, status, NULL, 0); #define btdev_command_complete(callback, data, len) \ btdev_command_response(callback, \ BTDEV_RESPONSE_COMMAND_COMPLETE, 0x00, data, len); typedef void (*btdev_command_func) (uint16_t opcode, const void *data, uint8_t len, btdev_callback callback, void *user_data); typedef void (*btdev_send_func) (const struct iovec *iov, int iovlen, void *user_data); typedef bool (*btdev_hook_func) (const void *data, uint16_t len, void *user_data); enum btdev_type { BTDEV_TYPE_BREDRLE, BTDEV_TYPE_BREDR, BTDEV_TYPE_LE, BTDEV_TYPE_AMP, BTDEV_TYPE_BREDR20, BTDEV_TYPE_BREDRLE50, BTDEV_TYPE_BREDRLE52, BTDEV_TYPE_BREDRLE60, }; enum btdev_hook_type { BTDEV_HOOK_PRE_CMD, BTDEV_HOOK_POST_CMD, BTDEV_HOOK_PRE_EVT, BTDEV_HOOK_POST_EVT, }; struct btdev; struct btdev *btdev_create(enum btdev_type type, uint16_t id); void btdev_destroy(struct btdev *btdev); typedef void (*btdev_debug_func_t)(const char *str, void *user_data); typedef void (*btdev_destroy_func_t)(void *user_data); bool btdev_set_debug(struct btdev *btdev, btdev_debug_func_t callback, void *user_data, btdev_destroy_func_t destroy); const uint8_t *btdev_get_bdaddr(struct btdev *btdev); bool btdev_set_bdaddr(struct btdev *btdev, const uint8_t *bdaddr); uint8_t *btdev_get_features(struct btdev *btdev); uint8_t *btdev_get_commands(struct btdev *btdev); uint8_t btdev_get_scan_enable(struct btdev *btdev); uint8_t btdev_get_le_scan_enable(struct btdev *btdev); const uint8_t *btdev_get_adv_addr(struct btdev *btdev, uint8_t handle); void btdev_get_mtu(struct btdev *btdev, uint16_t *acl, uint16_t *sco, uint16_t *iso); void btdev_set_le_states(struct btdev *btdev, const uint8_t *le_states); void btdev_set_al_len(struct btdev *btdev, uint8_t len); void btdev_set_rl_len(struct btdev *btdev, uint8_t len); void btdev_set_command_handler(struct btdev *btdev, btdev_command_func handler, void *user_data); void btdev_set_send_handler(struct btdev *btdev, btdev_send_func handler, void *user_data); void btdev_receive_h4(struct btdev *btdev, const void *data, uint16_t len); int btdev_add_hook(struct btdev *btdev, enum btdev_hook_type type, uint16_t opcode, btdev_hook_func handler, void *user_data); bool btdev_del_hook(struct btdev *btdev, enum btdev_hook_type type, uint16_t opcode); int btdev_set_msft_opcode(struct btdev *btdev, uint16_t opcode); int btdev_set_aosp_capable(struct btdev *btdev, bool enable); int btdev_set_emu_opcode(struct btdev *btdev, uint16_t opcode); bluez-5.87/emulator/PaxHeaders/main.c0000644000000000000000000000005015221752714014601 xustar0020 atime=1783092684 20 ctime=1783094102 bluez-5.87/emulator/main.c0000644000000000000000000001320515221752714014263 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "src/shared/mainloop.h" #include "src/shared/util.h" #include "serial.h" #include "server.h" #include "btdev.h" #include "vhci.h" #include "le.h" static void signal_callback(int signum, void *user_data) { switch (signum) { case SIGINT: case SIGTERM: mainloop_quit(); break; } } static void usage(void) { printf("btvirt - Bluetooth emulator\n" "Usage:\n"); printf("\tbtvirt [options]\n"); printf("options:\n" "\t-d Enable debug\n" "\t-S Create local serial port\n" "\t-s[path=/tmp] Create local server sockets\n" "\t-t[port=45550] Create a TCP server\n" "\t-l[num] Number of local controllers\n" "\t-L Create LE only controller\n" "\t-U[num] Number of test LE controllers\n" "\t-B Create BR/EDR only controller\n" "\t-A Create AMP controller\n" "\t-T[num] Number of test AMP controllers\n" "\t-h, --help Show help options\n"); } static const struct option main_options[] = { { "debug", no_argument, NULL, 'd' }, { "serial", no_argument, NULL, 'S' }, { "server", optional_argument, NULL, 's' }, { "tcp", optional_argument, NULL, 't' }, { "local", optional_argument, NULL, 'l' }, { "le", no_argument, NULL, 'L' }, { "bredr", no_argument, NULL, 'B' }, { "amp", no_argument, NULL, 'A' }, { "letest", optional_argument, NULL, 'U' }, { "version", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, { } }; static void vhci_debug(const char *str, void *user_data) { int i = PTR_TO_UINT(user_data); printf("vhci%u: %s\n", i, str); } int main(int argc, char *argv[]) { struct server *server1; struct server *server2; struct server *server3; struct server *server4; struct server *server5; bool debug_enabled = false; bool server_enabled = false; const char *server_path = "/tmp"; uint16_t tcp_port = 0; bool serial_enabled = false; int letest_count = 0; int vhci_count = 0; enum btdev_type type = BTDEV_TYPE_BREDRLE60; int i; mainloop_init(); for (;;) { int opt; opt = getopt_long(argc, argv, "dSs::t::l::LBAU::T::vh", main_options, NULL); if (opt < 0) break; switch (opt) { case 'd': debug_enabled = true; break; case 'S': serial_enabled = true; break; case 's': server_enabled = true; if (optarg) server_path = optarg; break; case 't': if (optarg) tcp_port = atoi(optarg); else tcp_port = 45550; break; case 'l': if (optarg) vhci_count = atoi(optarg); else vhci_count = 1; break; case 'L': type = BTDEV_TYPE_LE; break; case 'B': type = BTDEV_TYPE_BREDR; break; case 'A': type = BTDEV_TYPE_AMP; break; case 'U': if (optarg) letest_count = atoi(optarg); else letest_count = 1; break; case 'v': printf("%s\n", VERSION); return EXIT_SUCCESS; case 'h': usage(); return EXIT_SUCCESS; default: return EXIT_FAILURE; } } if (letest_count < 1 && vhci_count < 1 && !server_enabled && !tcp_port && !serial_enabled) { fprintf(stderr, "No emulator specified\n"); return EXIT_FAILURE; } printf("Bluetooth emulator ver %s\n", VERSION); for (i = 0; i < letest_count; i++) { struct bt_le *le; le = bt_le_new(); if (!le) { fprintf(stderr, "Failed to create LE controller\n"); return EXIT_FAILURE; } } for (i = 0; i < vhci_count; i++) { struct vhci *vhci; vhci = vhci_open(type); if (!vhci) { fprintf(stderr, "Failed to open Virtual HCI device\n"); return EXIT_FAILURE; } if (debug_enabled) vhci_set_debug(vhci, vhci_debug, UINT_TO_PTR(i), NULL); vhci_set_emu_opcode(vhci, 0xfc10); vhci_set_msft_opcode(vhci, 0xfc1e); } if (serial_enabled) { struct serial *serial; serial = serial_open(SERIAL_TYPE_BREDRLE); if (!serial) fprintf(stderr, "Failed to open serial emulation\n"); } if (server_enabled) { char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", server_path, "bt-server-bredrle"); server1 = server_open_unix(SERVER_TYPE_BREDRLE, path); if (!server1) fprintf(stderr, "Failed to open BR/EDR/LE server\n"); snprintf(path, sizeof(path), "%s/%s", server_path, "bt-server-bredr"); server2 = server_open_unix(SERVER_TYPE_BREDR, path); if (!server2) fprintf(stderr, "Failed to open BR/EDR server\n"); snprintf(path, sizeof(path), "%s/%s", server_path, "bt-server-amp"); server3 = server_open_unix(SERVER_TYPE_AMP, path); if (!server3) fprintf(stderr, "Failed to open AMP server\n"); snprintf(path, sizeof(path), "%s/%s", server_path, "bt-server-le"); server4 = server_open_unix(SERVER_TYPE_LE, path); if (!server4) fprintf(stderr, "Failed to open LE server\n"); snprintf(path, sizeof(path), "%s/%s", server_path, "bt-server-mon"); server5 = server_open_unix(SERVER_TYPE_MONITOR, path); if (!server5) fprintf(stderr, "Failed to open monitor server\n"); } if (tcp_port) { struct server *tcp_server; tcp_server = server_open_tcp(SERVER_TYPE_BREDRLE, tcp_port); if (!tcp_server) fprintf(stderr, "Failed to open TCP port\n"); fprintf(stderr, "Listening TCP on 127.0.0.1:%d\n", tcp_port); } return mainloop_run_with_signal(signal_callback, NULL); } bluez-5.87/emulator/PaxHeaders/le.c0000644000000000000000000000005015063045254014253 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/emulator/le.c0000644000000000000000000016240715063045254013746 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2012 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "src/shared/util.h" #include "src/shared/crypto.h" #include "src/shared/ecc.h" #include "src/shared/mainloop.h" #include "monitor/bt.h" #include "phy.h" #include "le.h" #define ACCEPT_LIST_SIZE 16 #define RESOLV_LIST_SIZE 16 #define SCAN_CACHE_SIZE 64 #define DEFAULT_TX_LEN 0x001b #define DEFAULT_TX_TIME 0x0148 #define MAX_TX_LEN 0x00fb #define MAX_TX_TIME 0x0848 #define MAX_RX_LEN 0x00fb #define MAX_RX_TIME 0x0848 #define DEFAULT_ALL_PHYS 0x03 #define DEFAULT_TX_PHYS 0x00 #define DEFAULT_RX_PHYS 0x00 struct bt_peer { uint8_t addr_type; uint8_t addr[6]; }; struct bt_le { volatile int ref_count; int vhci_fd; struct bt_phy *phy; struct bt_crypto *crypto; int adv_timeout_id; int scan_timeout_id; bool scan_window_active; uint8_t scan_chan_idx; uint8_t event_mask[16]; uint16_t manufacturer; uint8_t commands[64]; uint8_t features[8]; uint8_t bdaddr[6]; uint8_t le_event_mask[8]; uint16_t le_mtu; uint8_t le_max_pkt; uint8_t le_features[8]; uint8_t le_random_addr[6]; uint16_t le_adv_min_interval; uint16_t le_adv_max_interval; uint8_t le_adv_type; uint8_t le_adv_own_addr_type; uint8_t le_adv_direct_addr_type; uint8_t le_adv_direct_addr[6]; uint8_t le_adv_channel_map; uint8_t le_adv_filter_policy; int8_t le_adv_tx_power; uint8_t le_adv_data_len; uint8_t le_adv_data[31]; uint8_t le_scan_rsp_data_len; uint8_t le_scan_rsp_data[31]; uint8_t le_adv_enable; uint8_t le_scan_type; uint16_t le_scan_interval; uint16_t le_scan_window; uint8_t le_scan_own_addr_type; uint8_t le_scan_filter_policy; uint8_t le_scan_enable; uint8_t le_scan_filter_dup; uint8_t le_conn_peer_addr_type; uint8_t le_conn_peer_addr[6]; uint8_t le_conn_own_addr_type; uint8_t le_conn_enable; uint8_t le_accept_list_size; uint8_t le_accept_list[ACCEPT_LIST_SIZE][7]; uint8_t le_states[8]; uint16_t le_default_tx_len; uint16_t le_default_tx_time; uint8_t le_local_sk256[32]; uint8_t le_resolv_list[RESOLV_LIST_SIZE][39]; uint8_t le_resolv_list_size; uint8_t le_resolv_enable; uint16_t le_resolv_timeout; uint8_t le_default_all_phys; uint8_t le_default_tx_phys; uint8_t le_default_rx_phys; struct bt_peer scan_cache[SCAN_CACHE_SIZE]; uint8_t scan_cache_count; }; static bool is_in_accept_list(struct bt_le *hci, uint8_t addr_type, const uint8_t addr[6]) { int i; for (i = 0; i < hci->le_accept_list_size; i++) { if (hci->le_accept_list[i][0] == addr_type && !memcmp(&hci->le_accept_list[i][1], addr, 6)) return true; } return false; } static void clear_accept_list(struct bt_le *hci) { int i; for (i = 0; i < hci->le_accept_list_size; i++) { hci->le_accept_list[i][0] = 0xff; memset(&hci->le_accept_list[i][1], 0, 6); } } static void resolve_peer_addr(struct bt_le *hci, uint8_t peer_addr_type, const uint8_t peer_addr[6], uint8_t *addr_type, uint8_t addr[6]) { int i; if (!hci->le_resolv_enable) goto done; if (peer_addr_type != 0x01) goto done; if ((peer_addr[5] & 0xc0) != 0x40) goto done; for (i = 0; i < hci->le_resolv_list_size; i++) { uint8_t local_hash[3]; if (hci->le_resolv_list[i][0] == 0xff) continue; bt_crypto_ah(hci->crypto, &hci->le_resolv_list[i][7], peer_addr + 3, local_hash); if (!memcmp(peer_addr, local_hash, 3)) { switch (hci->le_resolv_list[i][0]) { case 0x00: *addr_type = 0x02; break; case 0x01: *addr_type = 0x03; break; default: continue; } memcpy(addr, &hci->le_resolv_list[i][1], 6); return; } } done: *addr_type = peer_addr_type; memcpy(addr, peer_addr, 6); } static void clear_resolv_list(struct bt_le *hci) { int i; for (i = 0; i < hci->le_resolv_list_size; i++) { hci->le_resolv_list[i][0] = 0xff; memset(&hci->le_resolv_list[i][1], 0, 38); } } static void reset_defaults(struct bt_le *hci) { memset(hci->event_mask, 0, sizeof(hci->event_mask)); hci->event_mask[0] |= 0x10; /* Disconnection Complete */ hci->event_mask[0] |= 0x80; /* Encryption Change */ hci->event_mask[1] |= 0x08; /* Read Remote Version Information Complete */ hci->event_mask[1] |= 0x20; /* Command Complete */ hci->event_mask[1] |= 0x40; /* Command Status */ hci->event_mask[1] |= 0x80; /* Hardware Error */ hci->event_mask[2] |= 0x04; /* Number of Completed Packets */ hci->event_mask[3] |= 0x02; /* Data Buffer Overflow */ hci->event_mask[5] |= 0x80; /* Encryption Key Refresh Complete */ //hci->event_mask[7] |= 0x20; /* LE Meta Event */ hci->manufacturer = 0x003f; /* Bluetooth SIG (63) */ memset(hci->commands, 0, sizeof(hci->commands)); hci->commands[0] |= 0x20; /* Disconnect */ //hci->commands[2] |= 0x80; /* Read Remote Version Information */ hci->commands[5] |= 0x40; /* Set Event Mask */ hci->commands[5] |= 0x80; /* Reset */ //hci->commands[10] |= 0x04; /* Read Transmit Power Level */ hci->commands[14] |= 0x08; /* Read Local Version Information */ hci->commands[14] |= 0x10; /* Read Local Supported Commands */ hci->commands[14] |= 0x20; /* Read Local Supported Features */ hci->commands[14] |= 0x80; /* Read Buffer Size */ hci->commands[15] |= 0x02; /* Read BD ADDR */ //hci->commands[15] |= 0x20; /* Read RSSI */ hci->commands[22] |= 0x04; /* Set Event Mask Page 2 */ hci->commands[25] |= 0x01; /* LE Set Event Mask */ hci->commands[25] |= 0x02; /* LE Read Buffer Size */ hci->commands[25] |= 0x04; /* LE Read Local Supported Features */ hci->commands[25] |= 0x10; /* LE Set Random Address */ hci->commands[25] |= 0x20; /* LE Set Advertising Parameters */ hci->commands[25] |= 0x40; /* LE Read Advertising Channel TX Power */ hci->commands[25] |= 0x80; /* LE Set Advertising Data */ hci->commands[26] |= 0x01; /* LE Set Scan Response Data */ hci->commands[26] |= 0x02; /* LE Set Advertise Enable */ hci->commands[26] |= 0x04; /* LE Set Scan Parameters */ hci->commands[26] |= 0x08; /* LE Set Scan Enable */ hci->commands[26] |= 0x10; /* LE Create Connection */ hci->commands[26] |= 0x20; /* LE Create Connection Cancel */ hci->commands[26] |= 0x40; /* LE Read Accept List Size */ hci->commands[26] |= 0x80; /* LE Clear Accept List */ hci->commands[27] |= 0x01; /* LE Add Device To Accept List */ hci->commands[27] |= 0x02; /* LE Remove Device From Accept List */ //hci->commands[27] |= 0x04; /* LE Connection Update */ //hci->commands[27] |= 0x08; /* LE Set Host Channel Classification */ //hci->commands[27] |= 0x10; /* LE Read Channel Map */ //hci->commands[27] |= 0x20; /* LE Read Remote Used Features */ hci->commands[27] |= 0x40; /* LE Encrypt */ hci->commands[27] |= 0x80; /* LE Rand */ //hci->commands[28] |= 0x01; /* LE Start Encryption */ //hci->commands[28] |= 0x02; /* LE Long Term Key Request Reply */ //hci->commands[28] |= 0x04; /* LE Long Term Key Request Negative Reply */ hci->commands[28] |= 0x08; /* LE Read Supported States */ //hci->commands[28] |= 0x10; /* LE Receiver Test */ //hci->commands[28] |= 0x20; /* LE Transmitter Test */ //hci->commands[28] |= 0x40; /* LE Test End */ //hci->commands[33] |= 0x10; /* LE Remote Connection Parameter Request Reply */ //hci->commands[33] |= 0x20; /* LE Remote Connection Parameter Request Negative Reply */ hci->commands[33] |= 0x40; /* LE Set Data Length */ hci->commands[33] |= 0x80; /* LE Read Suggested Default Data Length */ hci->commands[34] |= 0x01; /* LE Write Suggested Default Data Length */ hci->commands[34] |= 0x02; /* LE Read Local P-256 Public Key */ hci->commands[34] |= 0x04; /* LE Generate DHKey */ hci->commands[34] |= 0x08; /* LE Add Device To Resolving List */ hci->commands[34] |= 0x10; /* LE Remove Device From Resolving List */ hci->commands[34] |= 0x20; /* LE Clear Resolving List */ hci->commands[34] |= 0x40; /* LE Read Resolving List Size */ hci->commands[34] |= 0x80; /* LE Read Peer Resolvable Address */ hci->commands[35] |= 0x01; /* LE Read Local Resolvable Address */ hci->commands[35] |= 0x02; /* LE Set Address Resolution Enable */ hci->commands[35] |= 0x04; /* LE Set Resolvable Private Address Timeout */ hci->commands[35] |= 0x08; /* LE Read Maximum Data Length */ hci->commands[35] |= 0x10; /* LE Read PHY */ hci->commands[35] |= 0x20; /* LE Set Default PHY */ hci->commands[35] |= 0x40; /* LE Set PHY */ //hci->commands[35] |= 0x80; /* LE Enhanced Receiver Test */ //hci->commands[36] |= 0x01; /* LE Enhanced Transmitter Test */ //hci->commands[36] |= 0x02; /* LE Set Advertising Set Random Address */ //hci->commands[36] |= 0x04; /* LE Set Extended Advertising Parameters */ //hci->commands[36] |= 0x08; /* LE Set Extended Advertising Data */ //hci->commands[36] |= 0x10; /* LE Set Extended Scan Response Data */ //hci->commands[36] |= 0x20; /* LE Set Extended Advertising Enable */ //hci->commands[36] |= 0x40; /* LE Read Maximum Advertising Data Length */ //hci->commands[36] |= 0x80; /* LE Read Number of Supported Advertising Sets */ //hci->commands[37] |= 0x01; /* LE Remove Advertising Set */ //hci->commands[37] |= 0x02; /* LE Clear Advertising Sets */ //hci->commands[37] |= 0x04; /* LE Set Periodic Advertising Parameters */ //hci->commands[37] |= 0x08; /* LE Set Periodic Advertising Data */ //hci->commands[37] |= 0x10; /* LE Set Periodic Advertising Enable */ //hci->commands[37] |= 0x20; /* LE Set Extended Scan Parameters */ //hci->commands[37] |= 0x40; /* LE Set Extended Scan Enable */ //hci->commands[37] |= 0x80; /* LE Extended Create Connection */ //hci->commands[38] |= 0x01; /* LE Periodic Advertising Create Sync */ //hci->commands[38] |= 0x02; /* LE Periodic Advertising Create Sync Cancel */ //hci->commands[38] |= 0x04; /* LE Periodic Advertising Terminate Sync */ //hci->commands[38] |= 0x08; /* LE Add Device To Periodic Advertiser List */ //hci->commands[38] |= 0x10; /* LE Remove Device From Periodic Advertiser List */ //hci->commands[38] |= 0x20; /* LE Clear Periodic Advertiser List */ //hci->commands[38] |= 0x40; /* LE Read Periodic Advertiser List Size */ //hci->commands[38] |= 0x80; /* LE Read Transmit Power */ //hci->commands[39] |= 0x01; /* LE Read RF Path Compensation */ //hci->commands[39] |= 0x02; /* LE Write RF Path Compensation */ //hci->commands[39] |= 0x04; /* LE Set Privacy Mode */ memset(hci->features, 0, sizeof(hci->features)); hci->features[4] |= 0x20; /* BR/EDR Not Supported */ hci->features[4] |= 0x40; /* LE Supported */ memset(hci->bdaddr, 0, sizeof(hci->bdaddr)); memset(hci->le_event_mask, 0, sizeof(hci->le_event_mask)); hci->le_event_mask[0] |= 0x01; /* LE Connection Complete */ hci->le_event_mask[0] |= 0x02; /* LE Advertising Report */ hci->le_event_mask[0] |= 0x04; /* LE Connection Update Complete */ hci->le_event_mask[0] |= 0x08; /* LE Read Remote Used Features Complete */ hci->le_event_mask[0] |= 0x10; /* LE Long Term Key Request */ //hci->le_event_mask[0] |= 0x20; /* LE Remote Connection Parameter Request */ //hci->le_event_mask[0] |= 0x40; /* LE Data Length Change */ //hci->le_event_mask[0] |= 0x80; /* LE Read Local P-256 Public Key Complete */ //hci->le_event_mask[1] |= 0x01; /* LE Generate DHKey Complete */ //hci->le_event_mask[1] |= 0x02; /* LE Enhanced Connection Complete */ //hci->le_event_mask[1] |= 0x04; /* LE Direct Advertising Report */ //hci->le_event_mask[1] |= 0x08; /* LE PHY Update Complete */ //hci->le_event_mask[1] |= 0x10; /* LE Extended Advertising Report */ //hci->le_event_mask[1] |= 0x20; /* LE Periodic Advertising Sync Established */ //hci->le_event_mask[1] |= 0x40; /* LE Periodic Advertising Report */ //hci->le_event_mask[1] |= 0x80; /* LE Periodic Advertising Sync Lost */ //hci->le_event_mask[2] |= 0x01; /* LE Extended Scan Timeout */ //hci->le_event_mask[2] |= 0x02; /* LE Extended Advertising Set Terminated */ //hci->le_event_mask[2] |= 0x04; /* LE Scan Request Received */ //hci->le_event_mask[2] |= 0x08; /* LE Channel Selection Algorithm */ hci->le_mtu = 64; hci->le_max_pkt = 1; memset(hci->le_features, 0, sizeof(hci->le_features)); hci->le_features[0] |= 0x01; /* LE Encryption */ //hci->le_features[0] |= 0x02; /* Connection Parameter Request Procedure */ //hci->le_features[0] |= 0x04; /* Extended Reject Indication */ //hci->le_features[0] |= 0x08; /* Peripheral-initd Features Exchange */ hci->le_features[0] |= 0x10; /* LE Ping */ hci->le_features[0] |= 0x20; /* LE Data Packet Length Extension */ hci->le_features[0] |= 0x40; /* LL Privacy */ hci->le_features[0] |= 0x80; /* Extended Scanner Filter Policies */ hci->le_features[1] |= 0x01; /* LE 2M PHY */ hci->le_features[1] |= 0x02; /* Stable Modulation Index - Transmitter */ hci->le_features[1] |= 0x04; /* Stable Modulation Index - Receiver */ hci->le_features[1] |= 0x08; /* LE Coded PHY */ //hci->le_features[1] |= 0x10; /* LE Extended Advertising */ //hci->le_features[1] |= 0x20; /* LE Periodic Advertising */ hci->le_features[1] |= 0x40; /* Channel Selection Algorithm #2 */ hci->le_features[1] |= 0x80; /* LE Power Class 1 */ hci->le_features[2] |= 0x01; /* Minimum Number of Used Channels Procedure */ memset(hci->le_random_addr, 0, sizeof(hci->le_random_addr)); hci->le_adv_min_interval = 0x0800; hci->le_adv_max_interval = 0x0800; hci->le_adv_type = 0x00; hci->le_adv_own_addr_type = 0x00; hci->le_adv_direct_addr_type = 0x00; memset(hci->le_adv_direct_addr, 0, 6); hci->le_adv_channel_map = 0x07; hci->le_adv_filter_policy = 0x00; hci->le_adv_tx_power = 0; memset(hci->le_adv_data, 0, sizeof(hci->le_adv_data)); hci->le_adv_data_len = 0; memset(hci->le_scan_rsp_data, 0, sizeof(hci->le_scan_rsp_data)); hci->le_scan_rsp_data_len = 0; hci->le_adv_enable = 0x00; hci->le_scan_type = 0x00; /* Passive Scanning */ hci->le_scan_interval = 0x0010; /* 10 ms */ hci->le_scan_window = 0x0010; /* 10 ms */ hci->le_scan_own_addr_type = 0x00; /* Public Device Address */ hci->le_scan_filter_policy = 0x00; hci->le_scan_enable = 0x00; hci->le_scan_filter_dup = 0x00; hci->le_conn_enable = 0x00; hci->le_accept_list_size = ACCEPT_LIST_SIZE; clear_accept_list(hci); memset(hci->le_states, 0, sizeof(hci->le_states)); hci->le_states[0] |= 0x01; /* Non-connectable Advertising */ hci->le_states[0] |= 0x02; /* Scannable Advertising */ hci->le_states[0] |= 0x04; /* Connectable Advertising */ hci->le_states[0] |= 0x08; /* High Duty Cycle Directed Advertising */ hci->le_states[0] |= 0x10; /* Passive Scanning */ hci->le_states[0] |= 0x20; /* Active Scanning */ hci->le_states[0] |= 0x40; /* Initiating + Conn (Central Role) */ hci->le_states[0] |= 0x80; /* Connection (Peripheral Role) */ hci->le_states[1] |= 0x01; /* Passive Scanning + * Non-connectable Advertising */ hci->le_default_tx_len = DEFAULT_TX_LEN; hci->le_default_tx_time = DEFAULT_TX_TIME; memset(hci->le_local_sk256, 0, sizeof(hci->le_local_sk256)); hci->le_resolv_list_size = RESOLV_LIST_SIZE; clear_resolv_list(hci); hci->le_resolv_enable = 0x00; hci->le_resolv_timeout = 0x0384; /* 900 secs or 15 minutes */ hci->le_default_all_phys = DEFAULT_ALL_PHYS; hci->le_default_tx_phys = DEFAULT_TX_PHYS; hci->le_default_rx_phys = DEFAULT_RX_PHYS; } static void clear_scan_cache(struct bt_le *hci) { memset(hci->scan_cache, 0, sizeof(hci->scan_cache)); hci->scan_cache_count = 0; } static bool add_to_scan_cache(struct bt_le *hci, uint8_t addr_type, const uint8_t addr[6]) { int i; for (i = 0; i < hci->scan_cache_count; i++) { if (hci->scan_cache[i].addr_type == addr_type && !memcmp(hci->scan_cache[i].addr, addr, 6)) return false; } if (hci->scan_cache_count >= SCAN_CACHE_SIZE) return true; hci->scan_cache[hci->scan_cache_count].addr_type = addr_type; memcpy(hci->scan_cache[hci->scan_cache_count].addr, addr, 6); hci->scan_cache_count++; return true; } static void send_event(struct bt_le *hci, uint8_t event, void *data, uint8_t size) { uint8_t type = BT_H4_EVT_PKT; struct bt_hci_evt_hdr hdr; struct iovec iov[3]; int iovcnt; hdr.evt = event; hdr.plen = size; iov[0].iov_base = &type; iov[0].iov_len = 1; iov[1].iov_base = &hdr; iov[1].iov_len = sizeof(hdr); if (size > 0) { iov[2].iov_base = data; iov[2].iov_len = size; iovcnt = 3; } else iovcnt = 2; if (writev(hci->vhci_fd, iov, iovcnt) < 0) fprintf(stderr, "Write to /dev/vhci failed (%m)\n"); } static void send_adv_pkt(struct bt_le *hci, uint8_t channel) { struct bt_phy_pkt_adv pkt; memset(&pkt, 0, sizeof(pkt)); pkt.chan_idx = channel; pkt.pdu_type = hci->le_adv_type; pkt.tx_addr_type = hci->le_adv_own_addr_type; switch (hci->le_adv_own_addr_type) { case 0x00: case 0x02: memcpy(pkt.tx_addr, hci->bdaddr, 6); break; case 0x01: case 0x03: memcpy(pkt.tx_addr, hci->le_random_addr, 6); break; } pkt.rx_addr_type = hci->le_adv_direct_addr_type; memcpy(pkt.rx_addr, hci->le_adv_direct_addr, 6); pkt.adv_data_len = hci->le_adv_data_len; pkt.scan_rsp_len = hci->le_scan_rsp_data_len; bt_phy_send_vector(hci->phy, BT_PHY_PKT_ADV, &pkt, sizeof(pkt), hci->le_adv_data, pkt.adv_data_len, hci->le_scan_rsp_data, pkt.scan_rsp_len); } static unsigned int get_adv_delay(void) { unsigned int val; /* The advertising delay is a pseudo-random value with a range * of 0 ms to 10 ms generated for each advertising event. */ if (util_getrandom(&val, sizeof(val), 0) < 0) { /* If it fails to get the random number, use a static value */ val = 5; } return (val % 11); } static void adv_timeout_callback(int id, void *user_data) { struct bt_le *hci = user_data; unsigned int msec, min_msec, max_msec; if (hci->le_adv_channel_map & 0x01) send_adv_pkt(hci, 37); if (hci->le_adv_channel_map & 0x02) send_adv_pkt(hci, 38); if (hci->le_adv_channel_map & 0x04) send_adv_pkt(hci, 39); min_msec = (hci->le_adv_min_interval * 625) / 1000; max_msec = (hci->le_adv_max_interval * 625) / 1000; msec = ((min_msec + max_msec) / 2) + get_adv_delay(); if (mainloop_modify_timeout(id, msec) < 0) { fprintf(stderr, "Setting advertising timeout failed\n"); hci->le_adv_enable = 0x00; } } static bool start_adv(struct bt_le *hci) { unsigned int msec; if (hci->adv_timeout_id >= 0) return false; msec = ((hci->le_adv_min_interval * 625) / 1000) + get_adv_delay(); hci->adv_timeout_id = mainloop_add_timeout(msec, adv_timeout_callback, hci, NULL); if (hci->adv_timeout_id < 0) return false; return true; } static bool stop_adv(struct bt_le *hci) { if (hci->adv_timeout_id < 0) return false; mainloop_remove_timeout(hci->adv_timeout_id); hci->adv_timeout_id = -1; return true; } static void scan_timeout_callback(int id, void *user_data) { struct bt_le *hci = user_data; unsigned int msec; if (hci->le_scan_window == hci->le_scan_interval || !hci->scan_window_active) { msec = (hci->le_scan_window * 625) / 1000; hci->scan_window_active = true; hci->scan_chan_idx++; if (hci->scan_chan_idx > 39) hci->scan_chan_idx = 37; } else { msec = ((hci->le_scan_interval - hci->le_scan_window) * 625) / 1000; hci->scan_window_active = false; } if (mainloop_modify_timeout(id, msec) < 0) { fprintf(stderr, "Setting scanning timeout failed\n"); hci->le_scan_enable = 0x00; hci->scan_window_active = false; } } static bool start_scan(struct bt_le *hci) { unsigned int msec; if (hci->scan_timeout_id >= 0) return false; msec = (hci->le_scan_window * 625) / 1000; hci->scan_timeout_id = mainloop_add_timeout(msec, scan_timeout_callback, hci, NULL); if (hci->scan_timeout_id < 0) return false; hci->scan_window_active = true; hci->scan_chan_idx = 37; return true; } static bool stop_scan(struct bt_le *hci) { if (hci->scan_timeout_id < 0) return false; mainloop_remove_timeout(hci->scan_timeout_id); hci->scan_timeout_id = -1; hci->scan_window_active = false; return true; } static void cmd_complete(struct bt_le *hci, uint16_t opcode, const void *data, uint8_t len) { struct bt_hci_evt_cmd_complete *cc; void *pkt_data; pkt_data = alloca(sizeof(*cc) + len); if (!pkt_data) return; cc = pkt_data; cc->ncmd = 0x01; cc->opcode = cpu_to_le16(opcode); if (len > 0) memcpy(pkt_data + sizeof(*cc), data, len); send_event(hci, BT_HCI_EVT_CMD_COMPLETE, pkt_data, sizeof(*cc) + len); } static void cmd_status(struct bt_le *hci, uint8_t status, uint16_t opcode) { struct bt_hci_evt_cmd_status cs; cs.status = status; cs.ncmd = 0x01; cs.opcode = cpu_to_le16(opcode); send_event(hci, BT_HCI_EVT_CMD_STATUS, &cs, sizeof(cs)); } static void le_meta_event(struct bt_le *hci, uint8_t event, void *data, uint8_t len) { void *pkt_data; if (!(hci->event_mask[7] & 0x20)) return; pkt_data = alloca(1 + len); if (!pkt_data) return; ((uint8_t *) pkt_data)[0] = event; if (len > 0) memcpy(pkt_data + 1, data, len); send_event(hci, BT_HCI_EVT_LE_META_EVENT, pkt_data, 1 + len); } static void cmd_disconnect(struct bt_le *hci, const void *data, uint8_t size) { cmd_status(hci, BT_HCI_ERR_UNKNOWN_CONN_ID, BT_HCI_CMD_DISCONNECT); } static void cmd_set_event_mask(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_set_event_mask *cmd = data; uint8_t status; memcpy(hci->event_mask, cmd->mask, 8); status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_SET_EVENT_MASK, &status, sizeof(status)); } static void cmd_reset(struct bt_le *hci, const void *data, uint8_t size) { uint8_t status; stop_adv(hci); stop_scan(hci); reset_defaults(hci); status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_RESET, &status, sizeof(status)); } static void cmd_set_event_mask_page2(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_set_event_mask_page2 *cmd = data; uint8_t status; memcpy(hci->event_mask + 8, cmd->mask, 8); status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_SET_EVENT_MASK_PAGE2, &status, sizeof(status)); } static void cmd_read_local_version(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_rsp_read_local_version rsp; rsp.status = BT_HCI_ERR_SUCCESS; rsp.hci_ver = 0x09; rsp.hci_rev = cpu_to_le16(0x0000); rsp.lmp_ver = 0x09; rsp.manufacturer = cpu_to_le16(hci->manufacturer); rsp.lmp_subver = cpu_to_le16(0x0000); cmd_complete(hci, BT_HCI_CMD_READ_LOCAL_VERSION, &rsp, sizeof(rsp)); } static void cmd_read_local_commands(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_rsp_read_local_commands rsp; rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.commands, hci->commands, 64); cmd_complete(hci, BT_HCI_CMD_READ_LOCAL_COMMANDS, &rsp, sizeof(rsp)); } static void cmd_read_local_features(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_rsp_read_local_features rsp; rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.features, hci->features, 8); cmd_complete(hci, BT_HCI_CMD_READ_LOCAL_FEATURES, &rsp, sizeof(rsp)); } static void cmd_read_buffer_size(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_rsp_read_buffer_size rsp; rsp.status = BT_HCI_ERR_SUCCESS; rsp.acl_mtu = cpu_to_le16(0x0000); rsp.sco_mtu = 0x00; rsp.acl_max_pkt = cpu_to_le16(0x0000); rsp.sco_max_pkt = cpu_to_le16(0x0000); cmd_complete(hci, BT_HCI_CMD_READ_BUFFER_SIZE, &rsp, sizeof(rsp)); } static void cmd_read_bd_addr(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_rsp_read_bd_addr rsp; rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.bdaddr, hci->bdaddr, 6); cmd_complete(hci, BT_HCI_CMD_READ_BD_ADDR, &rsp, sizeof(rsp)); } static void cmd_le_set_event_mask(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_set_event_mask *cmd = data; uint8_t status; memcpy(hci->le_event_mask, cmd->mask, 8); status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_SET_EVENT_MASK, &status, sizeof(status)); } static void cmd_le_read_buffer_size(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_rsp_le_read_buffer_size rsp; rsp.status = BT_HCI_ERR_SUCCESS; rsp.le_mtu = cpu_to_le16(hci->le_mtu); rsp.le_max_pkt = hci->le_max_pkt; cmd_complete(hci, BT_HCI_CMD_LE_READ_BUFFER_SIZE, &rsp, sizeof(rsp)); } static void cmd_le_read_local_features(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_rsp_le_read_local_features rsp; rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.features, hci->le_features, 8); cmd_complete(hci, BT_HCI_CMD_LE_READ_LOCAL_FEATURES, &rsp, sizeof(rsp)); } static void cmd_le_set_random_address(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_set_random_address *cmd = data; uint8_t status; memcpy(hci->le_random_addr, cmd->addr, 6); status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_SET_RANDOM_ADDRESS, &status, sizeof(status)); } static void cmd_le_set_adv_parameters(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_set_adv_parameters *cmd = data; uint16_t min_interval, max_interval; uint8_t status; if (hci->le_adv_enable == 0x01) { cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, BT_HCI_CMD_LE_SET_ADV_PARAMETERS); return; } min_interval = le16_to_cpu(cmd->min_interval); max_interval = le16_to_cpu(cmd->max_interval); /* Valid range for advertising type is 0x00 to 0x03 */ switch (cmd->type) { case 0x00: /* ADV_IND */ /* Range for advertising interval min is 0x0020 to 0x4000 */ if (min_interval < 0x0020 || min_interval > 0x4000) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_ADV_PARAMETERS); return; } /* Range for advertising interval max is 0x0020 to 0x4000 */ if (max_interval < 0x0020 || max_interval > 0x4000) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_ADV_PARAMETERS); return; } /* Advertising interval max shall be less or equal */ if (min_interval > max_interval) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_ADV_PARAMETERS); return; } break; case 0x01: /* ADV_DIRECT_IND */ /* Range for direct address type is 0x00 to 0x01 */ if (cmd->direct_addr_type > 0x01) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_ADV_PARAMETERS); return; } break; case 0x02: /* ADV_SCAN_IND */ case 0x03: /* ADV_NONCONN_IND */ /* Range for advertising interval min is 0x00a0 to 0x4000 */ if (min_interval < 0x00a0 || min_interval > 0x4000) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_ADV_PARAMETERS); return; } /* Range for advertising interval max is 0x00a0 to 0x4000 */ if (max_interval < 0x00a0 || max_interval > 0x4000) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_ADV_PARAMETERS); return; } /* Advertising interval min shall be less or equal */ if (min_interval > max_interval) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_ADV_PARAMETERS); return; } break; default: cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_ADV_PARAMETERS); return; } /* Valid range for own address type is 0x00 to 0x03 */ if (cmd->own_addr_type > 0x03) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_ADV_PARAMETERS); return; } /* Valid range for advertising channel map is 0x01 to 0x07 */ if (cmd->channel_map < 0x01 || cmd->channel_map > 0x07) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_ADV_PARAMETERS); return; } /* Valid range for advertising filter policy is 0x00 to 0x03 */ if (cmd->filter_policy > 0x03) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_ADV_PARAMETERS); return; } hci->le_adv_min_interval = min_interval; hci->le_adv_max_interval = max_interval; hci->le_adv_type = cmd->type; hci->le_adv_own_addr_type = cmd->own_addr_type; hci->le_adv_direct_addr_type = cmd->direct_addr_type; memcpy(hci->le_adv_direct_addr, cmd->direct_addr, 6); hci->le_adv_channel_map = cmd->channel_map; hci->le_adv_filter_policy = cmd->filter_policy; status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_SET_ADV_PARAMETERS, &status, sizeof(status)); } static void cmd_le_read_adv_tx_power(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_rsp_le_read_adv_tx_power rsp; rsp.status = BT_HCI_ERR_SUCCESS; rsp.level = hci->le_adv_tx_power; cmd_complete(hci, BT_HCI_CMD_LE_READ_ADV_TX_POWER, &rsp, sizeof(rsp)); } static void cmd_le_set_adv_data(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_set_adv_data *cmd = data; uint8_t status; /* Valid range for advertising data length is 0x00 to 0x1f */ if (cmd->len > 0x1f) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_ADV_DATA); return; } hci->le_adv_data_len = cmd->len; memcpy(hci->le_adv_data, cmd->data, 31); status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_SET_ADV_DATA, &status, sizeof(status)); } static void cmd_le_set_scan_rsp_data(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_set_scan_rsp_data *cmd = data; uint8_t status; /* Valid range for scan response data length is 0x00 to 0x1f */ if (cmd->len > 0x1f) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_SCAN_RSP_DATA); return; } hci->le_scan_rsp_data_len = cmd->len; memcpy(hci->le_scan_rsp_data, cmd->data, 31); status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_SET_SCAN_RSP_DATA, &status, sizeof(status)); } static void cmd_le_set_adv_enable(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_set_adv_enable *cmd = data; uint8_t status; bool result; /* Valid range for advertising enable is 0x00 to 0x01 */ if (cmd->enable > 0x01) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_ADV_ENABLE); return; } if (cmd->enable == hci->le_adv_enable) { cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, BT_HCI_CMD_LE_SET_ADV_ENABLE); return; } if (cmd->enable == 0x01) result = start_adv(hci); else result = stop_adv(hci); if (!result) { cmd_status(hci, BT_HCI_ERR_UNSPECIFIED_ERROR, BT_HCI_CMD_LE_SET_ADV_ENABLE); return; } hci->le_adv_enable = cmd->enable; status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_SET_ADV_ENABLE, &status, sizeof(status)); } static void cmd_le_set_scan_parameters(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_set_scan_parameters *cmd = data; uint16_t interval, window; uint8_t status; if (hci->le_scan_enable == 0x01) { cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS); return; } interval = le16_to_cpu(cmd->interval); window = le16_to_cpu(cmd->window); /* Valid range for scan type is 0x00 to 0x01 */ if (cmd->type > 0x01) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS); return; } /* Valid range for scan interval is 0x0004 to 0x4000 */ if (interval < 0x0004 || interval > 0x4000) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS); return; } /* Valid range for scan window is 0x0004 to 0x4000 */ if (window < 0x0004 || window > 0x4000) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS); return; } /* Scan window shall be less or equal than scan interval */ if (window > interval) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS); return; } /* Valid range for own address type is 0x00 to 0x03 */ if (cmd->own_addr_type > 0x03) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS); return; } /* Valid range for scanning filter policy is 0x00 to 0x03 */ if (cmd->filter_policy > 0x03) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS); return; } hci->le_scan_type = cmd->type; hci->le_scan_interval = interval; hci->le_scan_window = window; hci->le_scan_own_addr_type = cmd->own_addr_type; hci->le_scan_filter_policy = cmd->filter_policy; status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS, &status, sizeof(status)); } static void cmd_le_set_scan_enable(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_set_scan_enable *cmd = data; uint8_t status; bool result; /* Valid range for scan enable is 0x00 to 0x01 */ if (cmd->enable > 0x01) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_SCAN_ENABLE); return; } /* Valid range for filter duplicates is 0x00 to 0x01 */ if (cmd->filter_dup > 0x01) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_SCAN_ENABLE); return; } if (cmd->enable == hci->le_scan_enable) { cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, BT_HCI_CMD_LE_SET_SCAN_ENABLE); return; } clear_scan_cache(hci); if (cmd->enable == 0x01) result = start_scan(hci); else result = stop_scan(hci); if (!result) { cmd_status(hci, BT_HCI_ERR_UNSPECIFIED_ERROR, BT_HCI_CMD_LE_SET_SCAN_ENABLE); return; } hci->le_scan_enable = cmd->enable; hci->le_scan_filter_dup = cmd->filter_dup; status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_SET_SCAN_ENABLE, &status, sizeof(status)); } static void cmd_le_create_conn(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_create_conn *cmd = data; if (hci->le_conn_enable == 0x01) { cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, BT_HCI_CMD_LE_CREATE_CONN); return; } /* Valid range for peer address type is 0x00 to 0x03 */ if (cmd->peer_addr_type > 0x03) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_CREATE_CONN); return; } /* Valid range for own address type is 0x00 to 0x03 */ if (cmd->own_addr_type > 0x03) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_CREATE_CONN); return; } hci->le_conn_peer_addr_type = cmd->peer_addr_type; memcpy(hci->le_conn_peer_addr, cmd->peer_addr, 6); hci->le_conn_own_addr_type = cmd->own_addr_type; hci->le_conn_enable = 0x01; cmd_status(hci, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_CREATE_CONN); } static void cmd_le_create_conn_cancel(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_evt_le_conn_complete evt; uint8_t status; if (hci->le_conn_enable == 0x00) { cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, BT_HCI_CMD_LE_CREATE_CONN_CANCEL); return; } hci->le_conn_enable = 0x00; status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_CREATE_CONN_CANCEL, &status, sizeof(status)); evt.status = BT_HCI_ERR_UNKNOWN_CONN_ID; evt.handle = cpu_to_le16(0x0000); evt.role = 0x00; evt.peer_addr_type = 0x00; memset(evt.peer_addr, 0, 6); evt.interval = cpu_to_le16(0x0000); evt.latency = cpu_to_le16(0x0000); evt.supv_timeout = cpu_to_le16(0x0000); evt.clock_accuracy = 0x00; if (hci->le_event_mask[0] & 0x01) le_meta_event(hci, BT_HCI_EVT_LE_CONN_COMPLETE, &evt, sizeof(evt)); } static void cmd_le_read_accept_list_size(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_rsp_le_read_accept_list_size rsp; rsp.status = BT_HCI_ERR_SUCCESS; rsp.size = hci->le_accept_list_size; cmd_complete(hci, BT_HCI_CMD_LE_READ_ACCEPT_LIST_SIZE, &rsp, sizeof(rsp)); } static void cmd_le_clear_accept_list(struct bt_le *hci, const void *data, uint8_t size) { uint8_t status; clear_accept_list(hci); status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_CLEAR_ACCEPT_LIST, &status, sizeof(status)); } static void cmd_le_add_to_accept_list(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_add_to_accept_list *cmd = data; uint8_t status; bool exists = false; int i, pos = -1; /* Valid range for address type is 0x00 to 0x01 */ if (cmd->addr_type > 0x01) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_ADD_TO_ACCEPT_LIST); return; } for (i = 0; i < hci->le_accept_list_size; i++) { if (hci->le_accept_list[i][0] == cmd->addr_type && !memcmp(&hci->le_accept_list[i][1], cmd->addr, 6)) { exists = true; break; } else if (pos < 0 && hci->le_accept_list[i][0] == 0xff) pos = i; } if (exists) { cmd_status(hci, BT_HCI_ERR_UNSPECIFIED_ERROR, BT_HCI_CMD_LE_ADD_TO_ACCEPT_LIST); return; } if (pos < 0) { cmd_status(hci, BT_HCI_ERR_MEM_CAPACITY_EXCEEDED, BT_HCI_CMD_LE_ADD_TO_ACCEPT_LIST); return; } hci->le_accept_list[pos][0] = cmd->addr_type; memcpy(&hci->le_accept_list[pos][1], cmd->addr, 6); status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_ADD_TO_ACCEPT_LIST, &status, sizeof(status)); } static void cmd_le_remove_from_accept_list(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_remove_from_accept_list *cmd = data; uint8_t status; int i, pos = -1; /* Valid range for address type is 0x00 to 0x01 */ if (cmd->addr_type > 0x01) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_REMOVE_FROM_ACCEPT_LIST); return; } for (i = 0; i < hci->le_accept_list_size; i++) { if (hci->le_accept_list[i][0] == cmd->addr_type && !memcmp(&hci->le_accept_list[i][1], cmd->addr, 6)) { pos = i; break; } } if (pos < 0) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_REMOVE_FROM_ACCEPT_LIST); return; } hci->le_accept_list[pos][0] = 0xff; memset(&hci->le_accept_list[pos][1], 0, 6); status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_REMOVE_FROM_ACCEPT_LIST, &status, sizeof(status)); } static void cmd_le_encrypt(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_encrypt *cmd = data; struct bt_hci_rsp_le_encrypt rsp; if (!bt_crypto_e(hci->crypto, cmd->key, cmd->plaintext, rsp.data)) { cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, BT_HCI_CMD_LE_ENCRYPT); return; } rsp.status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_ENCRYPT, &rsp, sizeof(rsp)); } static void cmd_le_rand(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_rsp_le_rand rsp; uint8_t value[8]; if (!bt_crypto_random_bytes(hci->crypto, value, 8)) { cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, BT_HCI_CMD_LE_RAND); return; } rsp.status = BT_HCI_ERR_SUCCESS; memcpy(&rsp.number, value, 8); cmd_complete(hci, BT_HCI_CMD_LE_RAND, &rsp, sizeof(rsp)); } static void cmd_le_read_supported_states(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_rsp_le_read_supported_states rsp; rsp.status = BT_HCI_ERR_SUCCESS; memcpy(rsp.states, hci->le_states, 8); cmd_complete(hci, BT_HCI_CMD_LE_READ_SUPPORTED_STATES, &rsp, sizeof(rsp)); } static void cmd_le_set_data_length(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_set_data_length *cmd = data; struct bt_hci_rsp_le_set_data_length rsp; uint16_t handle, tx_len, tx_time; handle = le16_to_cpu(cmd->handle); tx_len = le16_to_cpu(cmd->tx_len); tx_time = le16_to_cpu(cmd->tx_time); /* Valid range for connection handle is 0x0000 to 0x0eff */ if (handle > 0x0eff) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_DATA_LENGTH); return; } /* Valid range for suggested max TX octets is 0x001b to 0x00fb */ if (tx_len < 0x001b || tx_len > 0x00fb) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_DATA_LENGTH); return; } /* Valid range for suggested max TX time is 0x0148 to 0x0848 */ if (tx_time < 0x0148 || tx_time > 0x0848) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_DATA_LENGTH); return; } /* Max TX len and time shall be less or equal supported */ if (tx_len > MAX_TX_LEN || tx_time > MAX_TX_TIME) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_DATA_LENGTH); return; } rsp.status = BT_HCI_ERR_SUCCESS; rsp.handle = cpu_to_le16(handle); cmd_complete(hci, BT_HCI_CMD_LE_SET_DATA_LENGTH, &rsp, sizeof(rsp)); } static void cmd_le_read_default_data_length(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_rsp_le_read_default_data_length rsp; rsp.status = BT_HCI_ERR_SUCCESS; rsp.tx_len = cpu_to_le16(hci->le_default_tx_len); rsp.tx_time = cpu_to_le16(hci->le_default_tx_time); cmd_complete(hci, BT_HCI_CMD_LE_READ_DEFAULT_DATA_LENGTH, &rsp, sizeof(rsp)); } static void cmd_le_write_default_data_length(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_write_default_data_length *cmd = data; uint16_t tx_len, tx_time; uint8_t status; tx_len = le16_to_cpu(cmd->tx_len); tx_time = le16_to_cpu(cmd->tx_time); /* Valid range for suggested max TX octets is 0x001b to 0x00fb */ if (tx_len < 0x001b || tx_len > 0x00fb) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_WRITE_DEFAULT_DATA_LENGTH); return; } /* Valid range for suggested max TX time is 0x0148 to 0x0848 */ if (tx_time < 0x0148 || tx_time > 0x0848) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_WRITE_DEFAULT_DATA_LENGTH); return; } /* Suggested max TX len and time shall be less or equal supported */ if (tx_len > MAX_TX_LEN || tx_time > MAX_TX_TIME) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_WRITE_DEFAULT_DATA_LENGTH); return; } hci->le_default_tx_len = tx_len; hci->le_default_tx_time = tx_time; status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_WRITE_DEFAULT_DATA_LENGTH, &status, sizeof(status)); } static void cmd_le_read_local_pk256(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_evt_le_read_local_pk256_complete evt; cmd_status(hci, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_READ_LOCAL_PK256); evt.status = BT_HCI_ERR_SUCCESS; ecc_make_key(evt.local_pk256, hci->le_local_sk256); if (hci->le_event_mask[0] & 0x80) le_meta_event(hci, BT_HCI_EVT_LE_READ_LOCAL_PK256_COMPLETE, &evt, sizeof(evt)); } static void cmd_le_generate_dhkey(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_generate_dhkey *cmd = data; struct bt_hci_evt_le_generate_dhkey_complete evt; cmd_status(hci, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_GENERATE_DHKEY); evt.status = BT_HCI_ERR_SUCCESS; ecdh_shared_secret(cmd->remote_pk256, hci->le_local_sk256, evt.dhkey); if (hci->le_event_mask[1] & 0x01) le_meta_event(hci, BT_HCI_EVT_LE_GENERATE_DHKEY_COMPLETE, &evt, sizeof(evt)); } static void cmd_le_add_to_resolv_list(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_add_to_resolv_list *cmd = data; uint8_t status; bool exists = false; int i, pos = -1; /* Valid range for address type is 0x00 to 0x01 */ if (cmd->addr_type > 0x01) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_ADD_TO_RESOLV_LIST); return; } for (i = 0; i < hci->le_resolv_list_size; i++) { if (hci->le_resolv_list[i][0] == cmd->addr_type && !memcmp(&hci->le_resolv_list[i][1], cmd->addr, 6)) { exists = true; break; } else if (pos < 0 && hci->le_resolv_list[i][0] == 0xff) pos = i; } if (exists) { cmd_status(hci, BT_HCI_ERR_UNSPECIFIED_ERROR, BT_HCI_CMD_LE_ADD_TO_RESOLV_LIST); return; } if (pos < 0) { cmd_status(hci, BT_HCI_ERR_MEM_CAPACITY_EXCEEDED, BT_HCI_CMD_LE_ADD_TO_RESOLV_LIST); return; } hci->le_resolv_list[pos][0] = cmd->addr_type; memcpy(&hci->le_resolv_list[pos][1], cmd->addr, 6); memcpy(&hci->le_resolv_list[pos][7], cmd->peer_irk, 16); memcpy(&hci->le_resolv_list[pos][23], cmd->local_irk, 16); status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_ADD_TO_RESOLV_LIST, &status, sizeof(status)); } static void cmd_le_remove_from_resolv_list(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_remove_from_resolv_list *cmd = data; uint8_t status; int i, pos = -1; /* Valid range for address type is 0x00 to 0x01 */ if (cmd->addr_type > 0x01) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_REMOVE_FROM_RESOLV_LIST); return; } for (i = 0; i < hci->le_resolv_list_size; i++) { if (hci->le_resolv_list[i][0] == cmd->addr_type && !memcmp(&hci->le_resolv_list[i][1], cmd->addr, 6)) { pos = i; break; } } if (pos < 0) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_REMOVE_FROM_RESOLV_LIST); return; } hci->le_resolv_list[pos][0] = 0xff; memset(&hci->le_resolv_list[pos][1], 0, 38); status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_REMOVE_FROM_RESOLV_LIST, &status, sizeof(status)); } static void cmd_le_clear_resolv_list(struct bt_le *hci, const void *data, uint8_t size) { uint8_t status; clear_resolv_list(hci); status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_CLEAR_RESOLV_LIST, &status, sizeof(status)); } static void cmd_le_read_resolv_list_size(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_rsp_le_read_resolv_list_size rsp; rsp.status = BT_HCI_ERR_SUCCESS; rsp.size = hci->le_resolv_list_size; cmd_complete(hci, BT_HCI_CMD_LE_READ_RESOLV_LIST_SIZE, &rsp, sizeof(rsp)); } static void cmd_le_read_peer_resolv_addr(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_read_peer_resolv_addr *cmd = data; struct bt_hci_rsp_le_read_peer_resolv_addr rsp; /* Valid range for address type is 0x00 to 0x01 */ if (cmd->addr_type > 0x01) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_READ_PEER_RESOLV_ADDR); return; } rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; memset(rsp.addr, 0, 6); cmd_complete(hci, BT_HCI_CMD_LE_READ_PEER_RESOLV_ADDR, &rsp, sizeof(rsp)); } static void cmd_le_read_local_resolv_addr(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_read_local_resolv_addr *cmd = data; struct bt_hci_rsp_le_read_local_resolv_addr rsp; /* Valid range for address type is 0x00 to 0x01 */ if (cmd->addr_type > 0x01) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_READ_LOCAL_RESOLV_ADDR); return; } rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; memset(rsp.addr, 0, 6); cmd_complete(hci, BT_HCI_CMD_LE_READ_LOCAL_RESOLV_ADDR, &rsp, sizeof(rsp)); } static void cmd_le_set_resolv_enable(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_set_resolv_enable *cmd = data; uint8_t status; /* Valid range for address resolution enable is 0x00 to 0x01 */ if (cmd->enable > 0x01) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_RESOLV_ENABLE); return; } hci->le_resolv_enable = cmd->enable; status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_SET_RESOLV_ENABLE, &status, sizeof(status)); } static void cmd_le_set_resolv_timeout(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_set_resolv_timeout *cmd = data; uint16_t timeout; uint8_t status; timeout = le16_to_cpu(cmd->timeout); /* Valid range for RPA timeout is 0x0001 to 0xa1b8 */ if (timeout < 0x0001 || timeout > 0xa1b8) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_RESOLV_TIMEOUT); return; } hci->le_resolv_timeout = timeout; status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_SET_RESOLV_TIMEOUT, &status, sizeof(status)); } static void cmd_le_read_max_data_length(struct bt_le *hci, const void *data, uint8_t size) { struct bt_hci_rsp_le_read_max_data_length rsp; rsp.status = BT_HCI_ERR_SUCCESS; rsp.max_tx_len = cpu_to_le16(MAX_TX_LEN); rsp.max_tx_time = cpu_to_le16(MAX_TX_TIME); rsp.max_rx_len = cpu_to_le16(MAX_RX_LEN); rsp.max_rx_time = cpu_to_le16(MAX_RX_TIME); cmd_complete(hci, BT_HCI_CMD_LE_READ_MAX_DATA_LENGTH, &rsp, sizeof(rsp)); } static void cmd_le_read_phy(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_read_phy *cmd = data; struct bt_hci_rsp_le_read_phy rsp; rsp.status = BT_HCI_ERR_SUCCESS; rsp.handle = cmd->handle; rsp.tx_phy = 0x01; /* LE 1M */ rsp.rx_phy = 0x01; /* LE 1M */ cmd_complete(hci, BT_HCI_CMD_LE_READ_PHY, &rsp, sizeof(rsp)); } static void cmd_le_set_default_phy(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_set_default_phy *cmd = data; uint8_t status, tx_phys, rx_phys; uint8_t phys_mask; phys_mask = (true << 0) | ((!!(hci->le_features[1] & 0x01)) << 1) | ((!!(hci->le_features[1] & 0x08)) << 2); if (cmd->all_phys > 0x03) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_DEFAULT_PHY); return; } /* Transmitter PHYs preferences */ if (!(cmd->all_phys & 0x01)) { /* At least one preference bit shall be set to 1 */ if (!cmd->tx_phys) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_DEFAULT_PHY); return; } if (cmd->tx_phys & ~phys_mask) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_DEFAULT_PHY); return; } tx_phys = cmd->tx_phys; } else tx_phys = 0x00; /* Transmitter PHYs preferences */ if (!(cmd->all_phys & 0x02)) { /* At least one preference bit shall be set to 1 */ if (!cmd->rx_phys) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_DEFAULT_PHY); return; } if (cmd->rx_phys & ~phys_mask) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, BT_HCI_CMD_LE_SET_DEFAULT_PHY); return; } rx_phys = cmd->rx_phys; } else rx_phys = 0x00; hci->le_default_all_phys = cmd->all_phys; hci->le_default_tx_phys = tx_phys; hci->le_default_rx_phys = rx_phys; status = BT_HCI_ERR_SUCCESS; cmd_complete(hci, BT_HCI_CMD_LE_SET_DEFAULT_PHY, &status, sizeof(status)); } static void cmd_le_set_phy(struct bt_le *hci, const void *data, uint8_t size) { const struct bt_hci_cmd_le_set_phy *cmd = data; struct bt_hci_evt_le_phy_update_complete evt; cmd_status(hci, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_SET_PHY); evt.status = BT_HCI_ERR_SUCCESS; evt.handle = cmd->handle; evt.tx_phy = 0x01; /* LE 1M */ evt.rx_phy = 0x01; /* LE 1M */ if (hci->le_event_mask[1] & 0x08) le_meta_event(hci, BT_HCI_EVT_LE_PHY_UPDATE_COMPLETE, &evt, sizeof(evt)); } static const struct { uint16_t opcode; void (*func) (struct bt_le *hci, const void *data, uint8_t size); uint8_t size; bool fixed; } cmd_table[] = { { BT_HCI_CMD_DISCONNECT, cmd_disconnect, 3, true }, { BT_HCI_CMD_SET_EVENT_MASK, cmd_set_event_mask, 8, true }, { BT_HCI_CMD_RESET, cmd_reset, 0, true }, { BT_HCI_CMD_SET_EVENT_MASK_PAGE2, cmd_set_event_mask_page2, 8, true }, { BT_HCI_CMD_READ_LOCAL_VERSION, cmd_read_local_version, 0, true }, { BT_HCI_CMD_READ_LOCAL_COMMANDS, cmd_read_local_commands, 0, true }, { BT_HCI_CMD_READ_LOCAL_FEATURES, cmd_read_local_features, 0, true }, { BT_HCI_CMD_READ_BUFFER_SIZE, cmd_read_buffer_size, 0, true }, { BT_HCI_CMD_READ_BD_ADDR, cmd_read_bd_addr, 0, true }, { BT_HCI_CMD_LE_SET_EVENT_MASK, cmd_le_set_event_mask, 8, true }, { BT_HCI_CMD_LE_READ_BUFFER_SIZE, cmd_le_read_buffer_size, 0, true }, { BT_HCI_CMD_LE_READ_LOCAL_FEATURES, cmd_le_read_local_features, 0, true }, { BT_HCI_CMD_LE_SET_RANDOM_ADDRESS, cmd_le_set_random_address, 6, true }, { BT_HCI_CMD_LE_SET_ADV_PARAMETERS, cmd_le_set_adv_parameters, 15, true }, { BT_HCI_CMD_LE_READ_ADV_TX_POWER, cmd_le_read_adv_tx_power, 0, true }, { BT_HCI_CMD_LE_SET_ADV_DATA, cmd_le_set_adv_data, 32, true }, { BT_HCI_CMD_LE_SET_SCAN_RSP_DATA, cmd_le_set_scan_rsp_data, 32, true }, { BT_HCI_CMD_LE_SET_ADV_ENABLE, cmd_le_set_adv_enable, 1, true }, { BT_HCI_CMD_LE_SET_SCAN_PARAMETERS, cmd_le_set_scan_parameters, 7, true }, { BT_HCI_CMD_LE_SET_SCAN_ENABLE, cmd_le_set_scan_enable, 2, true }, { BT_HCI_CMD_LE_CREATE_CONN, cmd_le_create_conn, 25, true }, { BT_HCI_CMD_LE_CREATE_CONN_CANCEL, cmd_le_create_conn_cancel, 0, true }, { BT_HCI_CMD_LE_READ_ACCEPT_LIST_SIZE, cmd_le_read_accept_list_size, 0, true }, { BT_HCI_CMD_LE_CLEAR_ACCEPT_LIST, cmd_le_clear_accept_list, 0, true }, { BT_HCI_CMD_LE_ADD_TO_ACCEPT_LIST, cmd_le_add_to_accept_list, 7, true }, { BT_HCI_CMD_LE_REMOVE_FROM_ACCEPT_LIST, cmd_le_remove_from_accept_list, 7, true }, { BT_HCI_CMD_LE_ENCRYPT, cmd_le_encrypt, 32, true }, { BT_HCI_CMD_LE_RAND, cmd_le_rand, 0, true }, { BT_HCI_CMD_LE_READ_SUPPORTED_STATES, cmd_le_read_supported_states, 0, true }, { BT_HCI_CMD_LE_SET_DATA_LENGTH, cmd_le_set_data_length, 6, true }, { BT_HCI_CMD_LE_READ_DEFAULT_DATA_LENGTH, cmd_le_read_default_data_length, 0, true }, { BT_HCI_CMD_LE_WRITE_DEFAULT_DATA_LENGTH, cmd_le_write_default_data_length, 4, true }, { BT_HCI_CMD_LE_READ_LOCAL_PK256, cmd_le_read_local_pk256, 0, true }, { BT_HCI_CMD_LE_GENERATE_DHKEY, cmd_le_generate_dhkey, 64, true }, { BT_HCI_CMD_LE_ADD_TO_RESOLV_LIST, cmd_le_add_to_resolv_list, 39, true }, { BT_HCI_CMD_LE_REMOVE_FROM_RESOLV_LIST, cmd_le_remove_from_resolv_list, 7, true }, { BT_HCI_CMD_LE_CLEAR_RESOLV_LIST, cmd_le_clear_resolv_list, 0, true }, { BT_HCI_CMD_LE_READ_RESOLV_LIST_SIZE, cmd_le_read_resolv_list_size, 0, true }, { BT_HCI_CMD_LE_READ_PEER_RESOLV_ADDR, cmd_le_read_peer_resolv_addr, 7, true }, { BT_HCI_CMD_LE_READ_LOCAL_RESOLV_ADDR, cmd_le_read_local_resolv_addr, 7, true }, { BT_HCI_CMD_LE_SET_RESOLV_ENABLE, cmd_le_set_resolv_enable, 1, true }, { BT_HCI_CMD_LE_SET_RESOLV_TIMEOUT, cmd_le_set_resolv_timeout, 2, true }, { BT_HCI_CMD_LE_READ_MAX_DATA_LENGTH, cmd_le_read_max_data_length, 0, true }, { BT_HCI_CMD_LE_READ_PHY, cmd_le_read_phy, 2, true }, { BT_HCI_CMD_LE_SET_DEFAULT_PHY, cmd_le_set_default_phy, 3, true }, { BT_HCI_CMD_LE_SET_PHY, cmd_le_set_phy, 7, true }, { } }; static void process_command(struct bt_le *hci, const void *data, size_t size) { const struct bt_hci_cmd_hdr *hdr = data; uint16_t opcode; unsigned int i; if (size < sizeof(*hdr)) return; data += sizeof(*hdr); size -= sizeof(*hdr); opcode = le16_to_cpu(hdr->opcode); if (hdr->plen != size) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, opcode); return; } for (i = 0; cmd_table[i].func; i++) { if (cmd_table[i].opcode != opcode) continue; if ((cmd_table[i].fixed && size != cmd_table[i].size) || size < cmd_table[i].size) { cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, opcode); return; } cmd_table[i].func(hci, data, size); return; } cmd_status(hci, BT_HCI_ERR_UNKNOWN_COMMAND, opcode); } static void vhci_read_callback(int fd, uint32_t events, void *user_data) { struct bt_le *hci = user_data; unsigned char buf[4096]; ssize_t len; if (events & (EPOLLERR | EPOLLHUP)) return; len = read(hci->vhci_fd, buf, sizeof(buf)); if (len < 1) return; switch (buf[0]) { case BT_H4_CMD_PKT: process_command(hci, buf + 1, len - 1); break; } } static void phy_recv_callback(uint16_t type, const void *data, size_t size, void *user_data) { struct bt_le *hci = user_data; switch (type) { case BT_PHY_PKT_ADV: if (!(hci->le_event_mask[0] & 0x02)) return; if (hci->scan_window_active) { const struct bt_phy_pkt_adv *pkt = data; uint8_t buf[100]; struct bt_hci_evt_le_adv_report *evt = (void *) buf; uint8_t tx_addr_type, tx_addr[6]; if (hci->scan_chan_idx != pkt->chan_idx) break; resolve_peer_addr(hci, pkt->tx_addr_type, pkt->tx_addr, &tx_addr_type, tx_addr); if (hci->le_scan_filter_policy == 0x01 || hci->le_scan_filter_policy == 0x03) { if (!is_in_accept_list(hci, tx_addr_type, tx_addr)) break; } if (hci->le_scan_filter_dup) { if (!add_to_scan_cache(hci, tx_addr_type, tx_addr)) break; } memset(buf, 0, sizeof(buf)); evt->num_reports = 0x01; evt->event_type = pkt->pdu_type; evt->addr_type = tx_addr_type; memcpy(evt->addr, tx_addr, 6); evt->data_len = pkt->adv_data_len; memcpy(buf + sizeof(*evt), data + sizeof(*pkt), pkt->adv_data_len); le_meta_event(hci, BT_HCI_EVT_LE_ADV_REPORT, buf, sizeof(*evt) + pkt->adv_data_len + 1); if (hci->le_scan_type == 0x00) break; memset(buf, 0, sizeof(buf)); evt->num_reports = 0x01; evt->event_type = 0x04; evt->addr_type = tx_addr_type; memcpy(evt->addr, tx_addr, 6); evt->data_len = pkt->scan_rsp_len; memcpy(buf + sizeof(*evt), data + sizeof(*pkt) + pkt->adv_data_len, pkt->scan_rsp_len); le_meta_event(hci, BT_HCI_EVT_LE_ADV_REPORT, buf, sizeof(*evt) + pkt->scan_rsp_len + 1); } break; } } struct bt_le *bt_le_new(void) { unsigned char setup_cmd[2]; struct bt_le *hci; hci = calloc(1, sizeof(*hci)); if (!hci) return NULL; hci->adv_timeout_id = -1; hci->scan_timeout_id = -1; hci->scan_window_active = false; reset_defaults(hci); hci->vhci_fd = open("/dev/vhci", O_RDWR); if (hci->vhci_fd < 0) { free(hci); return NULL; } setup_cmd[0] = HCI_VENDOR_PKT; setup_cmd[1] = HCI_PRIMARY; if (write(hci->vhci_fd, setup_cmd, sizeof(setup_cmd)) < 0) { close(hci->vhci_fd); free(hci); return NULL; } mainloop_add_fd(hci->vhci_fd, EPOLLIN, vhci_read_callback, hci, NULL); hci->phy = bt_phy_new(); hci->crypto = bt_crypto_new(); bt_phy_register(hci->phy, phy_recv_callback, hci); return bt_le_ref(hci); } struct bt_le *bt_le_ref(struct bt_le *hci) { if (!hci) return NULL; __sync_fetch_and_add(&hci->ref_count, 1); return hci; } void bt_le_unref(struct bt_le *hci) { if (!hci) return; if (__sync_sub_and_fetch(&hci->ref_count, 1)) return; stop_adv(hci); bt_crypto_unref(hci->crypto); bt_phy_unref(hci->phy); mainloop_remove_fd(hci->vhci_fd); close(hci->vhci_fd); free(hci); } bluez-5.87/emulator/PaxHeaders/serial.c0000644000000000000000000000005015063045254015132 xustar0020 atime=1770587287 20 ctime=1783094102 bluez-5.87/emulator/serial.c0000644000000000000000000001074615063045254014623 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "src/shared/mainloop.h" #include "btdev.h" #include "serial.h" #define uninitialized_var(x) x = x struct serial { enum serial_type type; uint16_t id; int fd; char path[PATH_MAX]; struct btdev *btdev; uint8_t *pkt_data; uint8_t pkt_type; uint16_t pkt_expect; uint16_t pkt_len; uint16_t pkt_offset; }; static void open_pty(struct serial *serial); static void serial_destroy(void *user_data) { struct serial *serial = user_data; btdev_destroy(serial->btdev); serial->btdev = NULL; close(serial->fd); serial->fd = -1; } static void serial_write_callback(const struct iovec *iov, int iovlen, void *user_data) { struct serial *serial = user_data; ssize_t written; written = writev(serial->fd, iov, iovlen); if (written < 0) return; } static void serial_read_callback(int fd, uint32_t events, void *user_data) { struct serial *serial = user_data; uint8_t buf[4096]; uint8_t *ptr = buf; ssize_t len; uint16_t count; if (events & (EPOLLERR | EPOLLHUP)) { mainloop_remove_fd(serial->fd); open_pty(serial); return; } again: len = read(serial->fd, buf, sizeof(buf)); if (len < 0) { if (errno == EAGAIN) goto again; return; } if (!serial->btdev) return; count = len; while (count > 0) { hci_command_hdr *cmd_hdr; if (!serial->pkt_data) { serial->pkt_type = ptr[0]; switch (serial->pkt_type) { case HCI_COMMAND_PKT: if (count < HCI_COMMAND_HDR_SIZE + 1) { serial->pkt_offset += len; return; } cmd_hdr = (hci_command_hdr *) (ptr + 1); serial->pkt_expect = HCI_COMMAND_HDR_SIZE + cmd_hdr->plen + 1; serial->pkt_data = malloc(serial->pkt_expect); serial->pkt_len = 0; break; default: printf("packet error\n"); return; } serial->pkt_offset = 0; } if (count >= serial->pkt_expect) { memcpy(serial->pkt_data + serial->pkt_len, ptr, serial->pkt_expect); ptr += serial->pkt_expect; count -= serial->pkt_expect; btdev_receive_h4(serial->btdev, serial->pkt_data, serial->pkt_len + serial->pkt_expect); free(serial->pkt_data); serial->pkt_data = NULL; } else { memcpy(serial->pkt_data + serial->pkt_len, ptr, count); serial->pkt_len += count; serial->pkt_expect -= count; count = 0; } } } static void open_pty(struct serial *serial) { enum btdev_type uninitialized_var(type); serial->fd = posix_openpt(O_RDWR | O_NOCTTY); if (serial->fd < 0) { perror("Failed to get central pseudo terminal"); return; } if (grantpt(serial->fd) < 0) { perror("Failed to grant peripheral pseudo terminal"); close(serial->fd); serial->fd = -1; return; } if (unlockpt(serial->fd) < 0) { perror("Failed to unlock peripheral pseudo terminal"); close(serial->fd); serial->fd = -1; return; } ptsname_r(serial->fd, serial->path, sizeof(serial->path)); printf("Pseudo terminal at %s\n", serial->path); switch (serial->type) { case SERIAL_TYPE_BREDRLE: type = BTDEV_TYPE_BREDRLE; break; case SERIAL_TYPE_BREDR: type = BTDEV_TYPE_BREDR; break; case SERIAL_TYPE_LE: type = BTDEV_TYPE_LE; break; case SERIAL_TYPE_AMP: type = BTDEV_TYPE_AMP; break; } serial->btdev = btdev_create(type, serial->id); if (!serial->btdev) { close(serial->fd); serial->fd = -1; return; } btdev_set_send_handler(serial->btdev, serial_write_callback, serial); if (mainloop_add_fd(serial->fd, EPOLLIN, serial_read_callback, serial, serial_destroy) < 0) { btdev_destroy(serial->btdev); serial->btdev = NULL; close(serial->fd); serial->fd = -1; return; } } struct serial *serial_open(enum serial_type type) { struct serial *serial; enum btdev_type uninitialized_var(dev_type); serial = malloc(sizeof(*serial)); if (!serial) return NULL; memset(serial, 0, sizeof(*serial)); serial->type = type; serial->id = 0x42; open_pty(serial); return serial; } void serial_close(struct serial *serial) { if (!serial) return; mainloop_remove_fd(serial->fd); free(serial); } bluez-5.87/emulator/PaxHeaders/vhci.c0000644000000000000000000000005015200607060014574 xustar0020 atime=1783092683 20 ctime=1783094102 bluez-5.87/emulator/vhci.c0000644000000000000000000001530015200607060014254 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "src/shared/io.h" #include "src/shared/util.h" #include "monitor/bt.h" #include "btdev.h" #include "vhci.h" #define DEBUGFS_PATH "/sys/kernel/debug/bluetooth" #define DEVCORE_PATH "/sys/class/devcoredump" struct vhci { enum btdev_type type; uint16_t index; struct io *io; struct btdev *btdev; vhci_debug_func_t debug_callback; vhci_destroy_func_t debug_destroy; void *debug_data; }; static void vhci_debug(struct vhci *vhci, const char *format, ...) { va_list ap; if (!vhci || !format || !vhci->debug_callback) return; va_start(ap, format); util_debug_va(vhci->debug_callback, vhci->debug_data, format, ap); va_end(ap); } static void vhci_destroy(void *user_data) { struct vhci *vhci = user_data; btdev_destroy(vhci->btdev); io_destroy(vhci->io); free(vhci); } static void vhci_write_callback(const struct iovec *iov, int iovlen, void *user_data) { struct vhci *vhci = user_data; ssize_t written; written = io_send(vhci->io, iov, iovlen); if (written < 0) vhci_debug(vhci, "Failed to write: %s", strerror(-written)); } static bool vhci_read_callback(struct io *io, void *user_data) { struct vhci *vhci = user_data; int fd = io_get_fd(vhci->io); unsigned char buf[4096]; ssize_t len; len = read(fd, buf, sizeof(buf)); if (len < 1) { vhci_debug(vhci, "Failed to read: %s", strerror(errno)); return false; } btdev_receive_h4(vhci->btdev, buf, len); return true; } bool vhci_set_debug(struct vhci *vhci, vhci_debug_func_t callback, void *user_data, vhci_destroy_func_t destroy) { if (!vhci) return false; if (vhci->debug_destroy) vhci->debug_destroy(vhci->debug_data); vhci->debug_callback = callback; vhci->debug_destroy = destroy; vhci->debug_data = user_data; return btdev_set_debug(vhci->btdev, callback, user_data, destroy); } struct vhci_create_req { uint8_t pkt_type; uint8_t opcode; } __attribute__((packed)); struct vhci_create_rsp { uint8_t pkt_type; uint8_t opcode; uint16_t index; } __attribute__((packed)); struct vhci *vhci_open(uint8_t type) { struct vhci *vhci; struct vhci_create_req req; struct vhci_create_rsp rsp; int fd; fd = open("/dev/vhci", O_RDWR | O_NONBLOCK); if (fd < 0) return NULL; memset(&req, 0, sizeof(req)); req.pkt_type = HCI_VENDOR_PKT; switch (type) { case BTDEV_TYPE_AMP: req.opcode = HCI_AMP; break; default: req.opcode = HCI_PRIMARY; break; } if (write(fd, &req, sizeof(req)) != sizeof(req)) { close(fd); return NULL; } memset(&rsp, 0, sizeof(rsp)); if (read(fd, &rsp, sizeof(rsp)) != sizeof(rsp) || rsp.pkt_type != HCI_VENDOR_PKT || rsp.opcode != req.opcode) { close(fd); return NULL; } vhci = malloc(sizeof(*vhci)); if (!vhci) { close(fd); return NULL; } memset(vhci, 0, sizeof(*vhci)); vhci->type = type; vhci->index = rsp.index; vhci->io = io_new(fd); io_set_close_on_destroy(vhci->io, true); vhci->btdev = btdev_create(type, rsp.index); if (!vhci->btdev) { vhci_destroy(vhci); return NULL; } btdev_set_send_handler(vhci->btdev, vhci_write_callback, vhci); if (!io_set_read_handler(vhci->io, vhci_read_callback, vhci, NULL)) { vhci_destroy(vhci); return NULL; } return vhci; } void vhci_close(struct vhci *vhci) { if (!vhci) return; vhci_destroy(vhci); } bool vhci_pause_input(struct vhci *vhci, bool paused) { if (paused) return io_set_read_handler(vhci->io, NULL, NULL, NULL); else return io_set_read_handler(vhci->io, vhci_read_callback, vhci, NULL); } struct btdev *vhci_get_btdev(struct vhci *vhci) { if (!vhci) return NULL; return vhci->btdev; } static int vhci_debugfs_write(struct vhci *vhci, char *option, const void *data, size_t len) { char path[64]; int fd, err; size_t n; if (!vhci) return -EINVAL; memset(path, 0, sizeof(path)); sprintf(path, DEBUGFS_PATH "/hci%d/%s", vhci->index, option); fd = open(path, O_RDWR); if (fd < 0) { vhci_debug(vhci, "Failed to open %s: %s", path, strerror(errno)); return -errno; } n = write(fd, data, len); if (n == len) err = 0; else { err = -errno; vhci_debug(vhci, "Failed to write %s: %s", option, strerror(-err)); } close(fd); return err; } int vhci_set_force_suspend(struct vhci *vhci, bool enable) { char val; val = (enable) ? 'Y' : 'N'; return vhci_debugfs_write(vhci, "force_suspend", &val, sizeof(val)); } int vhci_set_force_wakeup(struct vhci *vhci, bool enable) { char val; val = (enable) ? 'Y' : 'N'; return vhci_debugfs_write(vhci, "force_wakeup", &val, sizeof(val)); } int vhci_set_msft_opcode(struct vhci *vhci, uint16_t opcode) { int err; char val[7]; snprintf(val, sizeof(val), "0x%4x", opcode); err = vhci_debugfs_write(vhci, "msft_opcode", &val, sizeof(val)); if (err) return err; return btdev_set_msft_opcode(vhci->btdev, opcode); } int vhci_set_aosp_capable(struct vhci *vhci, bool enable) { char val; val = (enable) ? 'Y' : 'N'; return vhci_debugfs_write(vhci, "aosp_capable", &val, sizeof(val)); } int vhci_set_emu_opcode(struct vhci *vhci, uint16_t opcode) { return btdev_set_emu_opcode(vhci->btdev, opcode); } int vhci_set_force_static_address(struct vhci *vhci, bool enable) { char val; val = (enable) ? 'Y' : 'N'; return vhci_debugfs_write(vhci, "force_static_address", &val, sizeof(val)); } int vhci_force_devcd(struct vhci *vhci, const void *data, size_t len) { return vhci_debugfs_write(vhci, "force_devcoredump", data, len); } int vhci_read_devcd(struct vhci *vhci, void *buf, size_t size) { DIR *dir; struct dirent *entry; char filename[PATH_MAX]; int fd; int ret; dir = opendir(DEVCORE_PATH); if (dir == NULL) return -errno; while ((entry = readdir(dir)) != NULL) { if (strstr(entry->d_name, "devcd")) break; } if (entry == NULL) { ret = -ENOENT; goto close_dir; } sprintf(filename, DEVCORE_PATH "/%s/data", entry->d_name); fd = open(filename, O_RDWR); if (fd < 0) { ret = -errno; goto close_dir; } ret = read(fd, buf, size); if (ret < 0) { ret = -errno; goto close_file; } /* Once the devcoredump is read, write anything to it to mark it for * cleanup. */ if (write(fd, "0", 1) < 0) { ret = -errno; goto close_file; } close_file: close(fd); close_dir: closedir(dir); return ret; } bluez-5.87/emulator/PaxHeaders/server.h0000644000000000000000000000005015063045254015166 xustar0020 atime=1770587287 20 ctime=1783094102 bluez-5.87/emulator/server.h0000644000000000000000000000107715063045254014654 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #include enum server_type { SERVER_TYPE_BREDRLE, SERVER_TYPE_BREDR, SERVER_TYPE_LE, SERVER_TYPE_AMP, SERVER_TYPE_MONITOR, }; struct server; struct server *server_open_unix(enum server_type type, const char *path); struct server *server_open_tcp(enum server_type type, uint16_t port); void server_close(struct server *server); bluez-5.87/emulator/PaxHeaders/hciemu.c0000644000000000000000000000005015200607060015115 xustar0020 atime=1783092683 20 ctime=1783094106 bluez-5.87/emulator/hciemu.c0000644000000000000000000004323215200607060014602 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "monitor/bt.h" #include "emulator/vhci.h" #include "emulator/btdev.h" #include "emulator/bthost.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "emulator/hciemu.h" struct hciemu_client { struct bthost *host; struct btdev *dev; guint start_source; guint host_source; guint source; int sock[2]; }; struct hciemu { int ref_count; enum btdev_type btdev_type; struct vhci *vhci; struct queue *clients; struct queue *post_command_hooks; char bdaddr_str[18]; hciemu_debug_func_t debug_callback; hciemu_destroy_func_t debug_destroy; void *debug_data; unsigned int flush_id; }; static void hciemu_debug(struct hciemu *hciemu, const char *format, ...) { va_list ap; if (!hciemu || !format || !hciemu->debug_callback) return; va_start(ap, format); util_debug_va(hciemu->debug_callback, hciemu->debug_data, format, ap); va_end(ap); } struct hciemu_command_hook { hciemu_command_func_t function; void *user_data; }; static void destroy_command_hook(void *data) { struct hciemu_command_hook *hook = data; free(hook); } struct run_data { uint16_t opcode; const void *data; uint8_t len; }; static void run_command_hook(void *data, void *user_data) { struct hciemu_command_hook *hook = data; struct run_data *run_data = user_data; if (hook->function) hook->function(run_data->opcode, run_data->data, run_data->len, hook->user_data); } static void central_command_callback(uint16_t opcode, const void *data, uint8_t len, btdev_callback callback, void *user_data) { struct hciemu *hciemu = user_data; struct run_data run_data = { .opcode = opcode, .data = data, .len = len }; btdev_command_default(callback); queue_foreach(hciemu->post_command_hooks, run_command_hook, &run_data); } static void client_command_callback(uint16_t opcode, const void *data, uint8_t len, btdev_callback callback, void *user_data) { btdev_command_default(callback); } static void writev_callback(const struct iovec *iov, int iovlen, void *user_data) { GIOChannel *channel = user_data; ssize_t written; int fd; fd = g_io_channel_unix_get_fd(channel); written = writev(fd, iov, iovlen); if (written < 0) { ssize_t ret; int size, data_len; socklen_t len = sizeof(size); int i; if (errno != EAGAIN) return; data_len = 0; for (i = 0; i < iovlen; i++) data_len += iov[i].iov_len; /* Automatically bump the send buffer size if the data to be * sent is larger than the current buffer size. This is needed * for btdev which doesn't flush the socket buffer until all * data has been sent. */ ret = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, &len); if (!ret) { size += data_len; setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, len); } written = writev(fd, iov, iovlen); if (written < 0) return; } } static gboolean receive_bthost(GIOChannel *channel, GIOCondition condition, gpointer user_data) { struct bthost *bthost = user_data; unsigned char buf[4096]; ssize_t len; int fd; if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) return FALSE; fd = g_io_channel_unix_get_fd(channel); len = read(fd, buf, sizeof(buf)); if (len < 0) return FALSE; bthost_receive_h4(bthost, buf, len); return TRUE; } static guint create_source_bthost(int fd, struct bthost *bthost) { GIOChannel *channel; guint source; channel = g_io_channel_unix_new(fd); g_io_channel_set_close_on_unref(channel, TRUE); g_io_channel_set_encoding(channel, NULL, NULL); g_io_channel_set_buffered(channel, FALSE); bthost_set_send_handler(bthost, writev_callback, channel); source = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, receive_bthost, bthost, NULL); g_io_channel_unref(channel); return source; } static gboolean receive_btdev(GIOChannel *channel, GIOCondition condition, gpointer user_data) { struct btdev *btdev = user_data; unsigned char buf[4096]; ssize_t len; int fd; if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) return FALSE; fd = g_io_channel_unix_get_fd(channel); len = read(fd, buf, sizeof(buf)); if (len < 0) { if (errno == EAGAIN || errno == EINTR) return TRUE; return FALSE; } if (len < 1) return FALSE; btdev_receive_h4(btdev, buf, len); return TRUE; } static guint create_source_btdev(int fd, struct btdev *btdev) { GIOChannel *channel; guint source; channel = g_io_channel_unix_new(fd); g_io_channel_set_close_on_unref(channel, TRUE); g_io_channel_set_encoding(channel, NULL, NULL); g_io_channel_set_buffered(channel, FALSE); btdev_set_send_handler(btdev, writev_callback, channel); source = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, receive_btdev, btdev, NULL); g_io_channel_unref(channel); return source; } static bool create_vhci(struct hciemu *hciemu) { struct vhci *vhci; vhci = vhci_open(hciemu->btdev_type); if (!vhci) { hciemu_debug(hciemu, "Failed to open vhci"); return false; } btdev_set_command_handler(vhci_get_btdev(vhci), central_command_callback, hciemu); hciemu->vhci = vhci; return true; } struct vhci *hciemu_get_vhci(struct hciemu *hciemu) { if (!hciemu) return NULL; return hciemu->vhci; } struct hciemu_client *hciemu_get_client(struct hciemu *hciemu, int num) { const struct queue_entry *entry; if (!hciemu) return NULL; for (entry = queue_get_entries(hciemu->clients); entry; entry = entry->next, num--) { if (!num) return entry->data; } return NULL; } struct bthost *hciemu_client_host(struct hciemu_client *client) { if (!client) return NULL; return client->host; } struct bthost *hciemu_client_get_host(struct hciemu *hciemu) { struct hciemu_client *client; if (!hciemu) return NULL; client = hciemu_get_client(hciemu, 0); return hciemu_client_host(client); } static gboolean start_host(gpointer user_data) { struct hciemu_client *client = user_data; client->start_source = 0; bthost_start(client->host); return FALSE; } static void hciemu_client_destroy(void *data) { struct hciemu_client *client = data; if (client->start_source) g_source_remove(client->start_source); g_source_remove(client->host_source); g_source_remove(client->source); bthost_destroy(client->host); btdev_destroy(client->dev); free(client); } static struct hciemu_client *hciemu_client_new(struct hciemu *hciemu, uint8_t id) { struct hciemu_client *client; int sv[2]; uint16_t acl_mtu, iso_mtu; client = new0(struct hciemu_client, 1); if (!client) return NULL; client->dev = btdev_create(hciemu->btdev_type, id++); if (!client->dev) { hciemu_debug(hciemu, "Failed to create btdev"); free(client); return NULL; } client->host = bthost_create(); if (!client->host) { hciemu_debug(hciemu, "Failed to create bthost"); btdev_destroy(client->dev); free(client); return NULL; } btdev_set_command_handler(client->dev, client_command_callback, client); if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, sv) < 0) { hciemu_debug(hciemu, "Failed to create socketpair: %s", strerror(errno)); bthost_destroy(client->host); btdev_destroy(client->dev); return NULL; } client->sock[0] = sv[0]; client->sock[1] = sv[1]; client->source = create_source_btdev(sv[0], client->dev); client->host_source = create_source_bthost(sv[1], client->host); client->start_source = g_idle_add(start_host, client); btdev_get_mtu(client->dev, &acl_mtu, NULL, &iso_mtu); bthost_set_acl_mtu(client->host, acl_mtu); bthost_set_iso_mtu(client->host, iso_mtu); return client; } struct hciemu *hciemu_new_num_debug(enum hciemu_type type, uint8_t num, hciemu_debug_func_t callback, void *user_data, hciemu_destroy_func_t destroy) { struct hciemu *hciemu; int i; if (!num) return NULL; hciemu = new0(struct hciemu, 1); if (!hciemu) return NULL; hciemu->debug_callback = callback; hciemu->debug_destroy = destroy; hciemu->debug_data = user_data; switch (type) { case HCIEMU_TYPE_BREDRLE: hciemu->btdev_type = BTDEV_TYPE_BREDRLE; break; case HCIEMU_TYPE_BREDR: hciemu->btdev_type = BTDEV_TYPE_BREDR; break; case HCIEMU_TYPE_LE: hciemu->btdev_type = BTDEV_TYPE_LE; break; case HCIEMU_TYPE_LEGACY: hciemu->btdev_type = BTDEV_TYPE_BREDR20; break; case HCIEMU_TYPE_BREDRLE50: hciemu->btdev_type = BTDEV_TYPE_BREDRLE50; break; case HCIEMU_TYPE_BREDRLE52: hciemu->btdev_type = BTDEV_TYPE_BREDRLE52; break; case HCIEMU_TYPE_BREDRLE60: hciemu->btdev_type = BTDEV_TYPE_BREDRLE60; break; default: return NULL; } hciemu->post_command_hooks = queue_new(); if (!hciemu->post_command_hooks) { free(hciemu); return NULL; } if (!create_vhci(hciemu)) { queue_destroy(hciemu->post_command_hooks, NULL); free(hciemu); return NULL; } hciemu->clients = queue_new(); for (i = 0; i < num; i++) { struct hciemu_client *client = hciemu_client_new(hciemu, i); if (!client) { queue_destroy(hciemu->clients, hciemu_client_destroy); queue_destroy(hciemu->post_command_hooks, NULL); free(hciemu); return NULL; } queue_push_tail(hciemu->clients, client); } return hciemu_ref(hciemu); } struct hciemu *hciemu_new_num(enum hciemu_type type, uint8_t num) { return hciemu_new_num_debug(type, num, NULL, NULL, NULL); } struct hciemu *hciemu_new(enum hciemu_type type) { return hciemu_new_num(type, 1); } struct hciemu *hciemu_new_debug(enum hciemu_type type, hciemu_debug_func_t callback, void *user_data, hciemu_destroy_func_t destroy) { return hciemu_new_num_debug(type, 1, callback, user_data, destroy); } struct hciemu *hciemu_ref(struct hciemu *hciemu) { if (!hciemu) return NULL; __sync_fetch_and_add(&hciemu->ref_count, 1); return hciemu; } void hciemu_unref(struct hciemu *hciemu) { if (!hciemu) return; if (__sync_sub_and_fetch(&hciemu->ref_count, 1)) return; queue_destroy(hciemu->post_command_hooks, destroy_command_hook); queue_destroy(hciemu->clients, hciemu_client_destroy); if (hciemu->flush_id) g_source_remove(hciemu->flush_id); vhci_close(hciemu->vhci); free(hciemu); } static void bthost_print(const char *str, void *user_data) { struct hciemu *hciemu = user_data; util_debug(hciemu->debug_callback, hciemu->debug_data, "bthost: %s", str); } static void vhci_debug(const char *str, void *user_data) { struct hciemu *hciemu = user_data; util_debug(hciemu->debug_callback, hciemu->debug_data, "vhci: %s", str); } static void btdev_client_debug(const char *str, void *user_data) { struct hciemu *hciemu = user_data; util_debug(hciemu->debug_callback, hciemu->debug_data, "btdev: %s", str); } static void hciemu_client_set_debug(void *data, void *user_data) { struct hciemu_client *client = data; struct hciemu *hciemu = user_data; btdev_set_debug(client->dev, btdev_client_debug, hciemu, NULL); bthost_set_debug(client->host, bthost_print, hciemu, NULL); } bool hciemu_set_debug(struct hciemu *hciemu, hciemu_debug_func_t callback, void *user_data, hciemu_destroy_func_t destroy) { if (!hciemu) return false; if (hciemu->debug_destroy) hciemu->debug_destroy(hciemu->debug_data); hciemu->debug_callback = callback; hciemu->debug_destroy = destroy; hciemu->debug_data = user_data; vhci_set_debug(hciemu->vhci, vhci_debug, hciemu, NULL); queue_foreach(hciemu->clients, hciemu_client_set_debug, hciemu); return true; } const char *hciemu_get_address(struct hciemu *hciemu) { const uint8_t *addr; struct btdev *dev; if (!hciemu || !hciemu->vhci) return NULL; dev = vhci_get_btdev(hciemu->vhci); if (!dev) return NULL; addr = btdev_get_bdaddr(dev); sprintf(hciemu->bdaddr_str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]); return hciemu->bdaddr_str; } uint8_t *hciemu_get_features(struct hciemu *hciemu) { struct btdev *dev; if (!hciemu || !hciemu->vhci) return NULL; dev = vhci_get_btdev(hciemu->vhci); if (!dev) return NULL; return btdev_get_features(dev); } uint8_t *hciemu_get_commands(struct hciemu *hciemu) { struct btdev *dev; if (!hciemu || !hciemu->vhci) return NULL; dev = vhci_get_btdev(hciemu->vhci); if (!dev) return NULL; return btdev_get_commands(dev); } const uint8_t *hciemu_get_central_bdaddr(struct hciemu *hciemu) { struct btdev *dev; if (!hciemu || !hciemu->vhci) return NULL; dev = vhci_get_btdev(hciemu->vhci); if (!dev) return NULL; return btdev_get_bdaddr(dev); } const uint8_t *hciemu_client_bdaddr(struct hciemu_client *client) { if (!client) return NULL; return btdev_get_bdaddr(client->dev); } bool hciemu_set_client_bdaddr(struct hciemu_client *client, const uint8_t *bdaddr) { if (!client) return NULL; return btdev_set_bdaddr(client->dev, bdaddr); } const uint8_t *hciemu_get_client_bdaddr(struct hciemu *hciemu) { struct hciemu_client *client; if (!hciemu) return NULL; client = hciemu_get_client(hciemu, 0); return hciemu_client_bdaddr(client); } uint8_t hciemu_get_central_scan_enable(struct hciemu *hciemu) { struct btdev *dev; if (!hciemu || !hciemu->vhci) return 0; dev = vhci_get_btdev(hciemu->vhci); if (!dev) return 0; return btdev_get_scan_enable(dev); } uint8_t hciemu_get_central_le_scan_enable(struct hciemu *hciemu) { struct btdev *dev; if (!hciemu || !hciemu->vhci) return 0; dev = vhci_get_btdev(hciemu->vhci); if (!dev) return 0; return btdev_get_le_scan_enable(dev); } void hciemu_set_central_le_states(struct hciemu *hciemu, const uint8_t *le_states) { struct btdev *dev; if (!hciemu || !hciemu->vhci) return; dev = vhci_get_btdev(hciemu->vhci); if (!dev) return; btdev_set_le_states(dev, le_states); } void hciemu_set_central_le_al_len(struct hciemu *hciemu, uint8_t len) { struct btdev *dev; if (!hciemu || !hciemu->vhci) return; dev = vhci_get_btdev(hciemu->vhci); if (!dev) return; btdev_set_al_len(dev, len); } void hciemu_set_central_le_rl_len(struct hciemu *hciemu, uint8_t len) { struct btdev *dev; if (!hciemu || !hciemu->vhci) return; dev = vhci_get_btdev(hciemu->vhci); if (!dev) return; btdev_set_rl_len(dev, len); } const uint8_t *hciemu_get_central_adv_addr(struct hciemu *hciemu, uint8_t handle) { struct btdev *dev; if (!hciemu || !hciemu->vhci) return NULL; dev = vhci_get_btdev(hciemu->vhci); if (!dev) return NULL; return btdev_get_adv_addr(dev, handle); } bool hciemu_add_central_post_command_hook(struct hciemu *hciemu, hciemu_command_func_t function, void *user_data) { struct hciemu_command_hook *hook; if (!hciemu) return false; hook = new0(struct hciemu_command_hook, 1); if (!hook) return false; hook->function = function; hook->user_data = user_data; if (!queue_push_tail(hciemu->post_command_hooks, hook)) { free(hook); return false; } return true; } bool hciemu_clear_central_post_command_hooks(struct hciemu *hciemu) { if (!hciemu) return false; queue_remove_all(hciemu->post_command_hooks, NULL, NULL, destroy_command_hook); return true; } int hciemu_add_hook(struct hciemu *hciemu, enum hciemu_hook_type type, uint16_t opcode, hciemu_hook_func_t function, void *user_data) { enum btdev_hook_type hook_type; struct btdev *dev; if (!hciemu || !hciemu->vhci) return -1; dev = vhci_get_btdev(hciemu->vhci); if (!dev) return 0; switch (type) { case HCIEMU_HOOK_PRE_CMD: hook_type = BTDEV_HOOK_PRE_CMD; break; case HCIEMU_HOOK_POST_CMD: hook_type = BTDEV_HOOK_POST_CMD; break; case HCIEMU_HOOK_PRE_EVT: hook_type = BTDEV_HOOK_PRE_EVT; break; case HCIEMU_HOOK_POST_EVT: hook_type = BTDEV_HOOK_POST_EVT; break; default: return -1; } return btdev_add_hook(dev, hook_type, opcode, function, user_data); } bool hciemu_del_hook(struct hciemu *hciemu, enum hciemu_hook_type type, uint16_t opcode) { enum btdev_hook_type hook_type; struct btdev *dev; if (!hciemu || !hciemu->vhci) return false; dev = vhci_get_btdev(hciemu->vhci); if (!dev) return false; switch (type) { case HCIEMU_HOOK_PRE_CMD: hook_type = BTDEV_HOOK_PRE_CMD; break; case HCIEMU_HOOK_POST_CMD: hook_type = BTDEV_HOOK_POST_CMD; break; case HCIEMU_HOOK_PRE_EVT: hook_type = BTDEV_HOOK_PRE_EVT; break; case HCIEMU_HOOK_POST_EVT: hook_type = BTDEV_HOOK_POST_EVT; break; default: return false; } return btdev_del_hook(dev, hook_type, opcode); } static bool client_is_pending(const void *data, const void *match_data) { struct hciemu_client *client = (struct hciemu_client *)data; int used, i; if (!client->source || !client->host_source) return false; for (i = 0; i < 2; ++i) { if (!ioctl(client->sock[i], TIOCOUTQ, &used) && used > 0) return true; if (!ioctl(client->sock[i], TIOCINQ, &used) && used > 0) return true; } return false; } static gboolean flush_client_events(gpointer user_data) { struct hciemu *hciemu = user_data; if (queue_find(hciemu->clients, client_is_pending, NULL)) return TRUE; hciemu->flush_id = 0; util_debug(hciemu->debug_callback, hciemu->debug_data, "vhci: resume"); if (hciemu->vhci) vhci_pause_input(hciemu->vhci, false); return FALSE; } void hciemu_flush_client_events(struct hciemu *hciemu) { if (hciemu->flush_id || !hciemu->vhci) return; util_debug(hciemu->debug_callback, hciemu->debug_data, "vhci: pause"); vhci_pause_input(hciemu->vhci, true); hciemu->flush_id = g_idle_add(flush_client_events, hciemu); } bluez-5.87/PaxHeaders/aclocal.m40000644000000000000000000000005015221752773013526 xustar0020 atime=1783092732 20 ctime=1783094098 bluez-5.87/aclocal.m40000644000000000000000000142003715221752773013216 0ustar00rootroot# generated automatically by aclocal 1.18.1 -*- Autoconf -*- # Copyright (C) 1996-2025 Free Software Foundation, Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.73],, [m4_warning([this file was generated for autoconf 2.73. You have another version of autoconf. It may work, but is not guaranteed to. If you have problems, you may need to regenerate the build system entirely. To do so, use the procedure documented by the package, typically 'autoreconf'.])]) # libtool.m4 - Configure libtool for the host system. -*-Autoconf-*- # # Copyright (C) 1996-2001, 2003-2019, 2021-2024 Free Software # Foundation, Inc. # Written by Gordon Matzigkeit, 1996 # # This file is free software; the Free Software Foundation gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. m4_define([_LT_COPYING], [dnl # Copyright (C) 2024 Free Software Foundation, Inc. # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # GNU Libtool 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. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program or library that is built # using GNU Libtool, you may include this file under the same # distribution terms that you use for the rest of that program. # # GNU Libtool 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, see . ]) # serial 63 LT_INIT # LT_PREREQ(VERSION) # ------------------ # Complain and exit if this libtool version is less that VERSION. m4_defun([LT_PREREQ], [m4_if(m4_version_compare(m4_defn([LT_PACKAGE_VERSION]), [$1]), -1, [m4_default([$3], [m4_fatal([Libtool version $1 or higher is required], 63)])], [$2])]) # _LT_CHECK_BUILDDIR # ------------------ # Complain if the absolute build directory name contains unusual characters m4_defun([_LT_CHECK_BUILDDIR], [case `pwd` in *\ * | *\ *) AC_MSG_WARN([Libtool does not cope well with whitespace in `pwd`]) ;; esac ]) # LT_INIT([OPTIONS]) # ------------------ AC_DEFUN([LT_INIT], [AC_PREREQ([2.64])dnl We use AC_PATH_PROGS_FEATURE_CHECK AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl AC_BEFORE([$0], [LT_LANG])dnl AC_BEFORE([$0], [LT_OUTPUT])dnl AC_BEFORE([$0], [LTDL_INIT])dnl m4_require([_LT_CHECK_BUILDDIR])dnl dnl Autoconf doesn't catch unexpanded LT_ macros by default: m4_pattern_forbid([^_?LT_[A-Z_]+$])dnl m4_pattern_allow([^(_LT_EOF|LT_DLGLOBAL|LT_DLLAZY_OR_NOW|LT_MULTI_MODULE)$])dnl dnl aclocal doesn't pull ltoptions.m4, ltsugar.m4, or ltversion.m4 dnl unless we require an AC_DEFUNed macro: AC_REQUIRE([LTOPTIONS_VERSION])dnl AC_REQUIRE([LTSUGAR_VERSION])dnl AC_REQUIRE([LTVERSION_VERSION])dnl AC_REQUIRE([LTOBSOLETE_VERSION])dnl m4_require([_LT_PROG_LTMAIN])dnl _LT_SHELL_INIT([SHELL=${CONFIG_SHELL-/bin/sh}]) dnl Parse OPTIONS _LT_SET_OPTIONS([$0], [$1]) # This can be used to rebuild libtool when needed LIBTOOL_DEPS=$ltmain # Always use our own libtool. LIBTOOL='$(SHELL) $(top_builddir)/libtool' AC_SUBST(LIBTOOL)dnl _LT_SETUP # Only expand once: m4_define([LT_INIT]) ])# LT_INIT # Old names: AU_ALIAS([AC_PROG_LIBTOOL], [LT_INIT]) AU_ALIAS([AM_PROG_LIBTOOL], [LT_INIT]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_PROG_LIBTOOL], []) dnl AC_DEFUN([AM_PROG_LIBTOOL], []) # _LT_PREPARE_CC_BASENAME # ----------------------- m4_defun([_LT_PREPARE_CC_BASENAME], [ # Calculate cc_basename. Skip known compiler wrappers and cross-prefix. func_cc_basename () { for cc_temp in @S|@*""; do case $cc_temp in compile | *[[\\/]]compile | ccache | *[[\\/]]ccache ) ;; distcc | *[[\\/]]distcc | purify | *[[\\/]]purify ) ;; \-*) ;; *) break;; esac done func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` } ])# _LT_PREPARE_CC_BASENAME # _LT_CC_BASENAME(CC) # ------------------- # It would be clearer to call AC_REQUIREs from _LT_PREPARE_CC_BASENAME, # but that macro is also expanded into generated libtool script, which # arranges for $SED and $ECHO to be set by different means. m4_defun([_LT_CC_BASENAME], [m4_require([_LT_PREPARE_CC_BASENAME])dnl AC_REQUIRE([_LT_DECL_SED])dnl AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl func_cc_basename $1 cc_basename=$func_cc_basename_result ]) # _LT_FILEUTILS_DEFAULTS # ---------------------- # It is okay to use these file commands and assume they have been set # sensibly after 'm4_require([_LT_FILEUTILS_DEFAULTS])'. m4_defun([_LT_FILEUTILS_DEFAULTS], [: ${CP="cp -f"} : ${MV="mv -f"} : ${RM="rm -f"} ])# _LT_FILEUTILS_DEFAULTS # _LT_SETUP # --------- m4_defun([_LT_SETUP], [AC_REQUIRE([AC_CANONICAL_HOST])dnl AC_REQUIRE([AC_CANONICAL_BUILD])dnl AC_REQUIRE([_LT_PREPARE_SED_QUOTE_VARS])dnl AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl _LT_DECL([], [PATH_SEPARATOR], [1], [The PATH separator for the build system])dnl dnl _LT_DECL([], [host_alias], [0], [The host system])dnl _LT_DECL([], [host], [0])dnl _LT_DECL([], [host_os], [0])dnl dnl _LT_DECL([], [build_alias], [0], [The build system])dnl _LT_DECL([], [build], [0])dnl _LT_DECL([], [build_os], [0])dnl dnl AC_REQUIRE([AC_PROG_CC])dnl AC_REQUIRE([LT_PATH_LD])dnl AC_REQUIRE([LT_PATH_NM])dnl dnl AC_REQUIRE([AC_PROG_LN_S])dnl test -z "$LN_S" && LN_S="ln -s" _LT_DECL([], [LN_S], [1], [Whether we need soft or hard links])dnl dnl AC_REQUIRE([LT_CMD_MAX_LEN])dnl _LT_DECL([objext], [ac_objext], [0], [Object file suffix (normally "o")])dnl _LT_DECL([], [exeext], [0], [Executable file suffix (normally "")])dnl dnl m4_require([_LT_FILEUTILS_DEFAULTS])dnl m4_require([_LT_CHECK_SHELL_FEATURES])dnl m4_require([_LT_PATH_CONVERSION_FUNCTIONS])dnl m4_require([_LT_CMD_RELOAD])dnl m4_require([_LT_DECL_FILECMD])dnl m4_require([_LT_CHECK_MAGIC_METHOD])dnl m4_require([_LT_CHECK_SHAREDLIB_FROM_LINKLIB])dnl m4_require([_LT_CMD_OLD_ARCHIVE])dnl m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl m4_require([_LT_WITH_SYSROOT])dnl m4_require([_LT_CMD_TRUNCATE])dnl _LT_CONFIG_LIBTOOL_INIT([ # See if we are running on zsh, and set the options that allow our # commands through without removal of \ escapes INIT. if test -n "\${ZSH_VERSION+set}"; then setopt NO_GLOB_SUBST fi ]) if test -n "${ZSH_VERSION+set}"; then setopt NO_GLOB_SUBST fi _LT_CHECK_OBJDIR m4_require([_LT_TAG_COMPILER])dnl case $host_os in aix3*) # AIX sometimes has problems with the GCC collect2 program. For some # reason, if we set the COLLECT_NAMES environment variable, the problems # vanish in a puff of smoke. if test set != "${COLLECT_NAMES+set}"; then COLLECT_NAMES= export COLLECT_NAMES fi ;; esac # Global variables: ofile=libtool can_build_shared=yes # All known linkers require a '.a' archive for static linking (except MSVC and # ICC, which need '.lib'). libext=a with_gnu_ld=$lt_cv_prog_gnu_ld old_CC=$CC old_CFLAGS=$CFLAGS # Set sane defaults for various variables test -z "$CC" && CC=cc test -z "$LTCC" && LTCC=$CC test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS test -z "$LD" && LD=ld test -z "$ac_objext" && ac_objext=o _LT_CC_BASENAME([$compiler]) # Only perform the check for file, if the check method requires it test -z "$MAGIC_CMD" && MAGIC_CMD=file case $deplibs_check_method in file_magic*) if test "$file_magic_cmd" = '$MAGIC_CMD'; then _LT_PATH_MAGIC fi ;; esac # Use C for the default configuration in the libtool script LT_SUPPORTED_TAG([CC]) _LT_LANG_C_CONFIG _LT_LANG_DEFAULT_CONFIG _LT_CONFIG_COMMANDS ])# _LT_SETUP # _LT_PREPARE_SED_QUOTE_VARS # -------------------------- # Define a few sed substitution that help us do robust quoting. m4_defun([_LT_PREPARE_SED_QUOTE_VARS], [# Backslashify metacharacters that are still active within # double-quoted strings. sed_quote_subst='s/\([["`$\\]]\)/\\\1/g' # Same as above, but do not quote variable references. double_quote_subst='s/\([["`\\]]\)/\\\1/g' # Sed substitution to delay expansion of an escaped shell variable in a # double_quote_subst'ed string. delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' # Sed substitution to delay expansion of an escaped single quote. delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' # Sed substitution to avoid accidental globbing in evaled expressions no_glob_subst='s/\*/\\\*/g' ]) # _LT_PROG_LTMAIN # --------------- # Note that this code is called both from 'configure', and 'config.status' # now that we use AC_CONFIG_COMMANDS to generate libtool. Notably, # 'config.status' has no value for ac_aux_dir unless we are using Automake, # so we pass a copy along to make sure it has a sensible value anyway. m4_defun([_LT_PROG_LTMAIN], [m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([ltmain.sh])])dnl _LT_CONFIG_LIBTOOL_INIT([ac_aux_dir='$ac_aux_dir']) ltmain=$ac_aux_dir/ltmain.sh ])# _LT_PROG_LTMAIN # So that we can recreate a full libtool script including additional # tags, we accumulate the chunks of code to send to AC_CONFIG_COMMANDS # in macros and then make a single call at the end using the 'libtool' # label. # _LT_CONFIG_LIBTOOL_INIT([INIT-COMMANDS]) # ---------------------------------------- # Register INIT-COMMANDS to be passed to AC_CONFIG_COMMANDS later. m4_define([_LT_CONFIG_LIBTOOL_INIT], [m4_ifval([$1], [m4_append([_LT_OUTPUT_LIBTOOL_INIT], [$1 ])])]) # Initialize. m4_define([_LT_OUTPUT_LIBTOOL_INIT]) # _LT_CONFIG_LIBTOOL([COMMANDS]) # ------------------------------ # Register COMMANDS to be passed to AC_CONFIG_COMMANDS later. m4_define([_LT_CONFIG_LIBTOOL], [m4_ifval([$1], [m4_append([_LT_OUTPUT_LIBTOOL_COMMANDS], [$1 ])])]) # Initialize. m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS]) # _LT_CONFIG_SAVE_COMMANDS([COMMANDS], [INIT_COMMANDS]) # ----------------------------------------------------- m4_defun([_LT_CONFIG_SAVE_COMMANDS], [_LT_CONFIG_LIBTOOL([$1]) _LT_CONFIG_LIBTOOL_INIT([$2]) ]) # _LT_FORMAT_COMMENT([COMMENT]) # ----------------------------- # Add leading comment marks to the start of each line, and a trailing # full-stop to the whole comment if one is not present already. m4_define([_LT_FORMAT_COMMENT], [m4_ifval([$1], [ m4_bpatsubst([m4_bpatsubst([$1], [^ *], [# ])], [['`$\]], [\\\&])]m4_bmatch([$1], [[!?.]$], [], [.]) )]) # _LT_DECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION], [IS-TAGGED?]) # ------------------------------------------------------------------- # CONFIGNAME is the name given to the value in the libtool script. # VARNAME is the (base) name used in the configure script. # VALUE may be 0, 1 or 2 for a computed quote escaped value based on # VARNAME. Any other value will be used directly. m4_define([_LT_DECL], [lt_if_append_uniq([lt_decl_varnames], [$2], [, ], [lt_dict_add_subkey([lt_decl_dict], [$2], [libtool_name], [m4_ifval([$1], [$1], [$2])]) lt_dict_add_subkey([lt_decl_dict], [$2], [value], [$3]) m4_ifval([$4], [lt_dict_add_subkey([lt_decl_dict], [$2], [description], [$4])]) lt_dict_add_subkey([lt_decl_dict], [$2], [tagged?], [m4_ifval([$5], [yes], [no])])]) ]) # _LT_TAGDECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION]) # -------------------------------------------------------- m4_define([_LT_TAGDECL], [_LT_DECL([$1], [$2], [$3], [$4], [yes])]) # lt_decl_tag_varnames([SEPARATOR], [VARNAME1...]) # ------------------------------------------------ m4_define([lt_decl_tag_varnames], [_lt_decl_filter([tagged?], [yes], $@)]) # _lt_decl_filter(SUBKEY, VALUE, [SEPARATOR], [VARNAME1..]) # --------------------------------------------------------- m4_define([_lt_decl_filter], [m4_case([$#], [0], [m4_fatal([$0: too few arguments: $#])], [1], [m4_fatal([$0: too few arguments: $#: $1])], [2], [lt_dict_filter([lt_decl_dict], [$1], [$2], [], lt_decl_varnames)], [3], [lt_dict_filter([lt_decl_dict], [$1], [$2], [$3], lt_decl_varnames)], [lt_dict_filter([lt_decl_dict], $@)])[]dnl ]) # lt_decl_quote_varnames([SEPARATOR], [VARNAME1...]) # -------------------------------------------------- m4_define([lt_decl_quote_varnames], [_lt_decl_filter([value], [1], $@)]) # lt_decl_dquote_varnames([SEPARATOR], [VARNAME1...]) # --------------------------------------------------- m4_define([lt_decl_dquote_varnames], [_lt_decl_filter([value], [2], $@)]) # lt_decl_varnames_tagged([SEPARATOR], [VARNAME1...]) # --------------------------------------------------- m4_define([lt_decl_varnames_tagged], [m4_assert([$# <= 2])dnl _$0(m4_quote(m4_default([$1], [[, ]])), m4_ifval([$2], [[$2]], [m4_dquote(lt_decl_tag_varnames)]), m4_split(m4_normalize(m4_quote(_LT_TAGS)), [ ]))]) m4_define([_lt_decl_varnames_tagged], [m4_ifval([$3], [lt_combine([$1], [$2], [_], $3)])]) # lt_decl_all_varnames([SEPARATOR], [VARNAME1...]) # ------------------------------------------------ m4_define([lt_decl_all_varnames], [_$0(m4_quote(m4_default([$1], [[, ]])), m4_if([$2], [], m4_quote(lt_decl_varnames), m4_quote(m4_shift($@))))[]dnl ]) m4_define([_lt_decl_all_varnames], [lt_join($@, lt_decl_varnames_tagged([$1], lt_decl_tag_varnames([[, ]], m4_shift($@))))dnl ]) # _LT_CONFIG_STATUS_DECLARE([VARNAME]) # ------------------------------------ # Quote a variable value, and forward it to 'config.status' so that its # declaration there will have the same value as in 'configure'. VARNAME # must have a single quote delimited value for this to work. m4_define([_LT_CONFIG_STATUS_DECLARE], [$1='`$ECHO "$][$1" | $SED "$delay_single_quote_subst"`']) # _LT_CONFIG_STATUS_DECLARATIONS # ------------------------------ # We delimit libtool config variables with single quotes, so when # we write them to config.status, we have to be sure to quote all # embedded single quotes properly. In configure, this macro expands # each variable declared with _LT_DECL (and _LT_TAGDECL) into: # # ='`$ECHO "$" | $SED "$delay_single_quote_subst"`' m4_defun([_LT_CONFIG_STATUS_DECLARATIONS], [m4_foreach([_lt_var], m4_quote(lt_decl_all_varnames), [m4_n([_LT_CONFIG_STATUS_DECLARE(_lt_var)])])]) # _LT_LIBTOOL_TAGS # ---------------- # Output comment and list of tags supported by the script m4_defun([_LT_LIBTOOL_TAGS], [_LT_FORMAT_COMMENT([The names of the tagged configurations supported by this script])dnl available_tags='_LT_TAGS'dnl ]) # _LT_LIBTOOL_DECLARE(VARNAME, [TAG]) # ----------------------------------- # Extract the dictionary values for VARNAME (optionally with TAG) and # expand to a commented shell variable setting: # # # Some comment about what VAR is for. # visible_name=$lt_internal_name m4_define([_LT_LIBTOOL_DECLARE], [_LT_FORMAT_COMMENT(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [description])))[]dnl m4_pushdef([_libtool_name], m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [libtool_name])))[]dnl m4_case(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [value])), [0], [_libtool_name=[$]$1], [1], [_libtool_name=$lt_[]$1], [2], [_libtool_name=$lt_[]$1], [_libtool_name=lt_dict_fetch([lt_decl_dict], [$1], [value])])[]dnl m4_ifval([$2], [_$2])[]m4_popdef([_libtool_name])[]dnl ]) # _LT_LIBTOOL_CONFIG_VARS # ----------------------- # Produce commented declarations of non-tagged libtool config variables # suitable for insertion in the LIBTOOL CONFIG section of the 'libtool' # script. Tagged libtool config variables (even for the LIBTOOL CONFIG # section) are produced by _LT_LIBTOOL_TAG_VARS. m4_defun([_LT_LIBTOOL_CONFIG_VARS], [m4_foreach([_lt_var], m4_quote(_lt_decl_filter([tagged?], [no], [], lt_decl_varnames)), [m4_n([_LT_LIBTOOL_DECLARE(_lt_var)])])]) # _LT_LIBTOOL_TAG_VARS(TAG) # ------------------------- m4_define([_LT_LIBTOOL_TAG_VARS], [m4_foreach([_lt_var], m4_quote(lt_decl_tag_varnames), [m4_n([_LT_LIBTOOL_DECLARE(_lt_var, [$1])])])]) # _LT_TAGVAR(VARNAME, [TAGNAME]) # ------------------------------ m4_define([_LT_TAGVAR], [m4_ifval([$2], [$1_$2], [$1])]) # _LT_CONFIG_COMMANDS # ------------------- # Send accumulated output to $CONFIG_STATUS. Thanks to the lists of # variables for single and double quote escaping we saved from calls # to _LT_DECL, we can put quote escaped variables declarations # into 'config.status', and then the shell code to quote escape them in # for loops in 'config.status'. Finally, any additional code accumulated # from calls to _LT_CONFIG_LIBTOOL_INIT is expanded. m4_defun([_LT_CONFIG_COMMANDS], [AC_PROVIDE_IFELSE([LT_OUTPUT], dnl If the libtool generation code has been placed in $CONFIG_LT, dnl instead of duplicating it all over again into config.status, dnl then we will have config.status run $CONFIG_LT later, so it dnl needs to know what name is stored there: [AC_CONFIG_COMMANDS([libtool], [$SHELL $CONFIG_LT || AS_EXIT(1)], [CONFIG_LT='$CONFIG_LT'])], dnl If the libtool generation code is destined for config.status, dnl expand the accumulated commands and init code now: [AC_CONFIG_COMMANDS([libtool], [_LT_OUTPUT_LIBTOOL_COMMANDS], [_LT_OUTPUT_LIBTOOL_COMMANDS_INIT])]) ])#_LT_CONFIG_COMMANDS # Initialize. m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS_INIT], [ # The HP-UX ksh and POSIX shell print the target directory to stdout # if CDPATH is set. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH sed_quote_subst='$sed_quote_subst' double_quote_subst='$double_quote_subst' delay_variable_subst='$delay_variable_subst' _LT_CONFIG_STATUS_DECLARATIONS LTCC='$LTCC' LTCFLAGS='$LTCFLAGS' compiler='$compiler_DEFAULT' # A function that is used when there is no print builtin or printf. func_fallback_echo () { eval 'cat <<_LTECHO_EOF \$[]1 _LTECHO_EOF' } # Quote evaled strings. for var in lt_decl_all_varnames([[ \ ]], lt_decl_quote_varnames); do case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in *[[\\\\\\\`\\"\\\$]]*) eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes ;; *) eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" ;; esac done # Double-quote double-evaled strings. for var in lt_decl_all_varnames([[ \ ]], lt_decl_dquote_varnames); do case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in *[[\\\\\\\`\\"\\\$]]*) eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes ;; *) eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" ;; esac done _LT_OUTPUT_LIBTOOL_INIT ]) # _LT_GENERATED_FILE_INIT(FILE, [COMMENT]) # ------------------------------------ # Generate a child script FILE with all initialization necessary to # reuse the environment learned by the parent script, and make the # file executable. If COMMENT is supplied, it is inserted after the # '#!' sequence but before initialization text begins. After this # macro, additional text can be appended to FILE to form the body of # the child script. The macro ends with non-zero status if the # file could not be fully written (such as if the disk is full). m4_ifdef([AS_INIT_GENERATED], [m4_defun([_LT_GENERATED_FILE_INIT],[AS_INIT_GENERATED($@)])], [m4_defun([_LT_GENERATED_FILE_INIT], [m4_require([AS_PREPARE])]dnl [m4_pushdef([AS_MESSAGE_LOG_FD])]dnl [lt_write_fail=0 cat >$1 <<_ASEOF || lt_write_fail=1 #! $SHELL # Generated by $as_me. $2 SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$1 <<\_ASEOF || lt_write_fail=1 AS_SHELL_SANITIZE _AS_PREPARE exec AS_MESSAGE_FD>&1 _ASEOF test 0 = "$lt_write_fail" && chmod +x $1[]dnl m4_popdef([AS_MESSAGE_LOG_FD])])])# _LT_GENERATED_FILE_INIT # LT_OUTPUT # --------- # This macro allows early generation of the libtool script (before # AC_OUTPUT is called), in case it is used in configure for compilation # tests. AC_DEFUN([LT_OUTPUT], [: ${CONFIG_LT=./config.lt} AC_MSG_NOTICE([creating $CONFIG_LT]) _LT_GENERATED_FILE_INIT(["$CONFIG_LT"], [# Run this file to recreate a libtool stub with the current configuration.]) cat >>"$CONFIG_LT" <<\_LTEOF lt_cl_silent=false exec AS_MESSAGE_LOG_FD>>config.log { echo AS_BOX([Running $as_me.]) } >&AS_MESSAGE_LOG_FD lt_cl_help="\ '$as_me' creates a local libtool stub from the current configuration, for use in further configure time tests before the real libtool is generated. Usage: $[0] [[OPTIONS]] -h, --help print this help, then exit -V, --version print version number, then exit -q, --quiet do not print progress messages -d, --debug don't remove temporary files Report bugs to ." lt_cl_version="\ m4_ifset([AC_PACKAGE_NAME], [AC_PACKAGE_NAME ])config.lt[]dnl m4_ifset([AC_PACKAGE_VERSION], [ AC_PACKAGE_VERSION]) configured by $[0], generated by m4_PACKAGE_STRING. Copyright (C) 2024 Free Software Foundation, Inc. This config.lt script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." while test 0 != $[#] do case $[1] in --version | --v* | -V ) echo "$lt_cl_version"; exit 0 ;; --help | --h* | -h ) echo "$lt_cl_help"; exit 0 ;; --debug | --d* | -d ) debug=: ;; --quiet | --q* | --silent | --s* | -q ) lt_cl_silent=: ;; -*) AC_MSG_ERROR([unrecognized option: $[1] Try '$[0] --help' for more information.]) ;; *) AC_MSG_ERROR([unrecognized argument: $[1] Try '$[0] --help' for more information.]) ;; esac shift done if $lt_cl_silent; then exec AS_MESSAGE_FD>/dev/null fi _LTEOF cat >>"$CONFIG_LT" <<_LTEOF _LT_OUTPUT_LIBTOOL_COMMANDS_INIT _LTEOF cat >>"$CONFIG_LT" <<\_LTEOF AC_MSG_NOTICE([creating $ofile]) _LT_OUTPUT_LIBTOOL_COMMANDS AS_EXIT(0) _LTEOF chmod +x "$CONFIG_LT" # configure is writing to config.log, but config.lt does its own redirection, # appending to config.log, which fails on DOS, as config.log is still kept # open by configure. Here we exec the FD to /dev/null, effectively closing # config.log, so it can be properly (re)opened and appended to by config.lt. lt_cl_success=: test yes = "$silent" && lt_config_lt_args="$lt_config_lt_args --quiet" exec AS_MESSAGE_LOG_FD>/dev/null $SHELL "$CONFIG_LT" $lt_config_lt_args || lt_cl_success=false exec AS_MESSAGE_LOG_FD>>config.log $lt_cl_success || AS_EXIT(1) ])# LT_OUTPUT # _LT_CONFIG(TAG) # --------------- # If TAG is the built-in tag, create an initial libtool script with a # default configuration from the untagged config vars. Otherwise add code # to config.status for appending the configuration named by TAG from the # matching tagged config vars. m4_defun([_LT_CONFIG], [m4_require([_LT_FILEUTILS_DEFAULTS])dnl _LT_CONFIG_SAVE_COMMANDS([ m4_define([_LT_TAG], m4_if([$1], [], [C], [$1]))dnl m4_if(_LT_TAG, [C], [ # See if we are running on zsh, and set the options that allow our # commands through without removal of \ escapes. if test -n "${ZSH_VERSION+set}"; then setopt NO_GLOB_SUBST fi cfgfile=${ofile}T trap "$RM \"$cfgfile\"; exit 1" 1 2 15 $RM "$cfgfile" cat <<_LT_EOF >> "$cfgfile" #! $SHELL # Generated automatically by $as_me ($PACKAGE) $VERSION # NOTE: Changes made to this file will be lost: look at ltmain.sh. # Provide generalized library-building support services. # Written by Gordon Matzigkeit, 1996 _LT_COPYING _LT_LIBTOOL_TAGS # Configured defaults for sys_lib_dlsearch_path munging. : \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"} # ### BEGIN LIBTOOL CONFIG _LT_LIBTOOL_CONFIG_VARS _LT_LIBTOOL_TAG_VARS # ### END LIBTOOL CONFIG _LT_EOF cat <<'_LT_EOF' >> "$cfgfile" # ### BEGIN FUNCTIONS SHARED WITH CONFIGURE _LT_PREPARE_MUNGE_PATH_LIST _LT_PREPARE_CC_BASENAME # ### END FUNCTIONS SHARED WITH CONFIGURE _LT_EOF case $host_os in aix3*) cat <<\_LT_EOF >> "$cfgfile" # AIX sometimes has problems with the GCC collect2 program. For some # reason, if we set the COLLECT_NAMES environment variable, the problems # vanish in a puff of smoke. if test set != "${COLLECT_NAMES+set}"; then COLLECT_NAMES= export COLLECT_NAMES fi _LT_EOF ;; esac _LT_PROG_LTMAIN # We use sed instead of cat because bash on DJGPP gets confused if # if finds mixed CR/LF and LF-only lines. Since sed operates in # text mode, it properly converts lines to CR/LF. This bash problem # is reportedly fixed, but why not run on old versions too? $SED '$q' "$ltmain" >> "$cfgfile" \ || (rm -f "$cfgfile"; exit 1) mv -f "$cfgfile" "$ofile" || (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") chmod +x "$ofile" ], [cat <<_LT_EOF >> "$ofile" dnl Unfortunately we have to use $1 here, since _LT_TAG is not expanded dnl in a comment (ie after a #). # ### BEGIN LIBTOOL TAG CONFIG: $1 _LT_LIBTOOL_TAG_VARS(_LT_TAG) # ### END LIBTOOL TAG CONFIG: $1 _LT_EOF ])dnl /m4_if ], [m4_if([$1], [], [ PACKAGE='$PACKAGE' VERSION='$VERSION' RM='$RM' ofile='$ofile'], []) ])dnl /_LT_CONFIG_SAVE_COMMANDS ])# _LT_CONFIG # LT_SUPPORTED_TAG(TAG) # --------------------- # Trace this macro to discover what tags are supported by the libtool # --tag option, using: # autoconf --trace 'LT_SUPPORTED_TAG:$1' AC_DEFUN([LT_SUPPORTED_TAG], []) # C support is built-in for now m4_define([_LT_LANG_C_enabled], []) m4_define([_LT_TAGS], []) # LT_LANG(LANG) # ------------- # Enable libtool support for the given language if not already enabled. AC_DEFUN([LT_LANG], [AC_BEFORE([$0], [LT_OUTPUT])dnl m4_case([$1], [C], [_LT_LANG(C)], [C++], [_LT_LANG(CXX)], [Go], [_LT_LANG(GO)], [Java], [_LT_LANG(GCJ)], [Fortran 77], [_LT_LANG(F77)], [Fortran], [_LT_LANG(FC)], [Windows Resource], [_LT_LANG(RC)], [m4_ifdef([_LT_LANG_]$1[_CONFIG], [_LT_LANG($1)], [m4_fatal([$0: unsupported language: "$1"])])])dnl ])# LT_LANG # _LT_LANG(LANGNAME) # ------------------ m4_defun([_LT_LANG], [m4_ifdef([_LT_LANG_]$1[_enabled], [], [LT_SUPPORTED_TAG([$1])dnl m4_append([_LT_TAGS], [$1 ])dnl m4_define([_LT_LANG_]$1[_enabled], [])dnl _LT_LANG_$1_CONFIG($1)])dnl ])# _LT_LANG m4_ifndef([AC_PROG_GO], [ # NOTE: This macro has been submitted for inclusion into # # GNU Autoconf as AC_PROG_GO. When it is available in # # a released version of Autoconf we should remove this # # macro and use it instead. # m4_defun([AC_PROG_GO], [AC_LANG_PUSH(Go)dnl AC_ARG_VAR([GOC], [Go compiler command])dnl AC_ARG_VAR([GOFLAGS], [Go compiler flags])dnl _AC_ARG_VAR_LDFLAGS()dnl AC_CHECK_TOOL(GOC, gccgo) if test -z "$GOC"; then if test -n "$ac_tool_prefix"; then AC_CHECK_PROG(GOC, [${ac_tool_prefix}gccgo], [${ac_tool_prefix}gccgo]) fi fi if test -z "$GOC"; then AC_CHECK_PROG(GOC, gccgo, gccgo, false) fi ])#m4_defun ])#m4_ifndef # _LT_LANG_DEFAULT_CONFIG # ----------------------- m4_defun([_LT_LANG_DEFAULT_CONFIG], [AC_PROVIDE_IFELSE([AC_PROG_CXX], [LT_LANG(CXX)], [m4_define([AC_PROG_CXX], defn([AC_PROG_CXX])[LT_LANG(CXX)])]) AC_PROVIDE_IFELSE([AC_PROG_F77], [LT_LANG(F77)], [m4_define([AC_PROG_F77], defn([AC_PROG_F77])[LT_LANG(F77)])]) AC_PROVIDE_IFELSE([AC_PROG_FC], [LT_LANG(FC)], [m4_define([AC_PROG_FC], defn([AC_PROG_FC])[LT_LANG(FC)])]) dnl The call to [A][M_PROG_GCJ] is quoted like that to stop aclocal dnl pulling things in needlessly. AC_PROVIDE_IFELSE([AC_PROG_GCJ], [LT_LANG(GCJ)], [AC_PROVIDE_IFELSE([A][M_PROG_GCJ], [LT_LANG(GCJ)], [AC_PROVIDE_IFELSE([LT_PROG_GCJ], [LT_LANG(GCJ)], [m4_ifdef([AC_PROG_GCJ], [m4_define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[LT_LANG(GCJ)])]) m4_ifdef([A][M_PROG_GCJ], [m4_define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[LT_LANG(GCJ)])]) m4_ifdef([LT_PROG_GCJ], [m4_define([LT_PROG_GCJ], defn([LT_PROG_GCJ])[LT_LANG(GCJ)])])])])]) AC_PROVIDE_IFELSE([AC_PROG_GO], [LT_LANG(GO)], [m4_define([AC_PROG_GO], defn([AC_PROG_GO])[LT_LANG(GO)])]) AC_PROVIDE_IFELSE([LT_PROG_RC], [LT_LANG(RC)], [m4_define([LT_PROG_RC], defn([LT_PROG_RC])[LT_LANG(RC)])]) ])# _LT_LANG_DEFAULT_CONFIG # Obsolete macros: AU_DEFUN([AC_LIBTOOL_CXX], [LT_LANG(C++)]) AU_DEFUN([AC_LIBTOOL_F77], [LT_LANG(Fortran 77)]) AU_DEFUN([AC_LIBTOOL_FC], [LT_LANG(Fortran)]) AU_DEFUN([AC_LIBTOOL_GCJ], [LT_LANG(Java)]) AU_DEFUN([AC_LIBTOOL_RC], [LT_LANG(Windows Resource)]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_CXX], []) dnl AC_DEFUN([AC_LIBTOOL_F77], []) dnl AC_DEFUN([AC_LIBTOOL_FC], []) dnl AC_DEFUN([AC_LIBTOOL_GCJ], []) dnl AC_DEFUN([AC_LIBTOOL_RC], []) # _LT_TAG_COMPILER # ---------------- m4_defun([_LT_TAG_COMPILER], [AC_REQUIRE([AC_PROG_CC])dnl _LT_DECL([LTCC], [CC], [1], [A C compiler])dnl _LT_DECL([LTCFLAGS], [CFLAGS], [1], [LTCC compiler flags])dnl _LT_TAGDECL([CC], [compiler], [1], [A language specific compiler])dnl _LT_TAGDECL([with_gcc], [GCC], [0], [Is the compiler the GNU compiler?])dnl # If no C compiler was specified, use CC. LTCC=${LTCC-"$CC"} # If no C compiler flags were specified, use CFLAGS. LTCFLAGS=${LTCFLAGS-"$CFLAGS"} # Allow CC to be a program name with arguments. compiler=$CC ])# _LT_TAG_COMPILER # _LT_COMPILER_BOILERPLATE # ------------------------ # Check for compiler boilerplate output or warnings with # the simple compiler test code. m4_defun([_LT_COMPILER_BOILERPLATE], [m4_require([_LT_DECL_SED])dnl ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" >conftest.$ac_ext eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err _lt_compiler_boilerplate=`cat conftest.err` $RM conftest* ])# _LT_COMPILER_BOILERPLATE # _LT_LINKER_BOILERPLATE # ---------------------- # Check for linker boilerplate output or warnings with # the simple link test code. m4_defun([_LT_LINKER_BOILERPLATE], [m4_require([_LT_DECL_SED])dnl ac_outfile=conftest.$ac_objext echo "$lt_simple_link_test_code" >conftest.$ac_ext eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err _lt_linker_boilerplate=`cat conftest.err` $RM -r conftest* ])# _LT_LINKER_BOILERPLATE # _LT_REQUIRED_DARWIN_CHECKS # ------------------------- m4_defun_once([_LT_REQUIRED_DARWIN_CHECKS],[ case $host_os in rhapsody* | darwin*) AC_CHECK_TOOL([DSYMUTIL], [dsymutil], [:]) AC_CHECK_TOOL([NMEDIT], [nmedit], [:]) AC_CHECK_TOOL([LIPO], [lipo], [:]) AC_CHECK_TOOL([OTOOL], [otool], [:]) AC_CHECK_TOOL([OTOOL64], [otool64], [:]) _LT_DECL([], [DSYMUTIL], [1], [Tool to manipulate archived DWARF debug symbol files on Mac OS X]) _LT_DECL([], [NMEDIT], [1], [Tool to change global to local symbols on Mac OS X]) _LT_DECL([], [LIPO], [1], [Tool to manipulate fat objects and archives on Mac OS X]) _LT_DECL([], [OTOOL], [1], [ldd/readelf like tool for Mach-O binaries on Mac OS X]) _LT_DECL([], [OTOOL64], [1], [ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4]) AC_CACHE_CHECK([for -single_module linker flag],[lt_cv_apple_cc_single_mod], [lt_cv_apple_cc_single_mod=no if test -z "$LT_MULTI_MODULE"; then # By default we will add the -single_module flag. You can override # by either setting the environment variable LT_MULTI_MODULE # non-empty at configure time, or by adding -multi_module to the # link flags. rm -rf libconftest.dylib* echo "int foo(void){return 1;}" > conftest.c echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ -dynamiclib -Wl,-single_module conftest.c" >&AS_MESSAGE_LOG_FD $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ -dynamiclib -Wl,-single_module conftest.c 2>conftest.err _lt_result=$? # If there is a non-empty error log, and "single_module" # appears in it, assume the flag caused a linker warning if test -s conftest.err && $GREP single_module conftest.err; then cat conftest.err >&AS_MESSAGE_LOG_FD # Otherwise, if the output was created with a 0 exit code from # the compiler, it worked. elif test -f libconftest.dylib && test 0 = "$_lt_result"; then lt_cv_apple_cc_single_mod=yes else cat conftest.err >&AS_MESSAGE_LOG_FD fi rm -rf libconftest.dylib* rm -f conftest.* fi]) # Feature test to disable chained fixups since it is not # compatible with '-undefined dynamic_lookup' AC_CACHE_CHECK([for -no_fixup_chains linker flag], [lt_cv_support_no_fixup_chains], [ save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -Wl,-no_fixup_chains" AC_LINK_IFELSE( [AC_LANG_PROGRAM([],[])], lt_cv_support_no_fixup_chains=yes, lt_cv_support_no_fixup_chains=no ) LDFLAGS=$save_LDFLAGS ] ) AC_CACHE_CHECK([for -exported_symbols_list linker flag], [lt_cv_ld_exported_symbols_list], [lt_cv_ld_exported_symbols_list=no save_LDFLAGS=$LDFLAGS echo "_main" > conftest.sym LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], [lt_cv_ld_exported_symbols_list=yes], [lt_cv_ld_exported_symbols_list=no]) LDFLAGS=$save_LDFLAGS ]) AC_CACHE_CHECK([for -force_load linker flag],[lt_cv_ld_force_load], [lt_cv_ld_force_load=no cat > conftest.c << _LT_EOF int forced_loaded() { return 2;} _LT_EOF echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&AS_MESSAGE_LOG_FD $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&AS_MESSAGE_LOG_FD echo "$AR $AR_FLAGS libconftest.a conftest.o" >&AS_MESSAGE_LOG_FD $AR $AR_FLAGS libconftest.a conftest.o 2>&AS_MESSAGE_LOG_FD echo "$RANLIB libconftest.a" >&AS_MESSAGE_LOG_FD $RANLIB libconftest.a 2>&AS_MESSAGE_LOG_FD cat > conftest.c << _LT_EOF int main(void) { return 0;} _LT_EOF echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&AS_MESSAGE_LOG_FD $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err _lt_result=$? if test -s conftest.err && $GREP force_load conftest.err; then cat conftest.err >&AS_MESSAGE_LOG_FD elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then lt_cv_ld_force_load=yes else cat conftest.err >&AS_MESSAGE_LOG_FD fi rm -f conftest.err libconftest.a conftest conftest.c rm -rf conftest.dSYM ]) case $host_os in rhapsody* | darwin1.[[012]]) _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; darwin1.*) _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; darwin*) case $MACOSX_DEPLOYMENT_TARGET,$host in 10.[[012]],*|,*powerpc*-darwin[[5-8]]*) _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; *) _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' if test yes = "$lt_cv_support_no_fixup_chains"; then AS_VAR_APPEND([_lt_dar_allow_undefined], [' $wl-no_fixup_chains']) fi ;; esac ;; esac if test yes = "$lt_cv_apple_cc_single_mod"; then _lt_dar_single_mod='$single_module' fi _lt_dar_needs_single_mod=no case $host_os in rhapsody* | darwin1.*) _lt_dar_needs_single_mod=yes ;; darwin*) # When targeting Mac OS X 10.4 (darwin 8) or later, # -single_module is the default and -multi_module is unsupported. # The toolchain on macOS 10.14 (darwin 18) and later cannot # target any OS version that needs -single_module. case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in 10.0,*-darwin[[567]].*|10.[[0-3]],*-darwin[[5-9]].*|10.[[0-3]],*-darwin1[[0-7]].*) _lt_dar_needs_single_mod=yes ;; esac ;; esac if test yes = "$lt_cv_ld_exported_symbols_list"; then _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym' else _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib' fi if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then _lt_dsymutil='~$DSYMUTIL $lib || :' else _lt_dsymutil= fi ;; esac ]) # _LT_DARWIN_LINKER_FEATURES([TAG]) # --------------------------------- # Checks for linker and compiler features on darwin m4_defun([_LT_DARWIN_LINKER_FEATURES], [ m4_require([_LT_REQUIRED_DARWIN_CHECKS]) _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_automatic, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported if test yes = "$lt_cv_ld_force_load"; then _LT_TAGVAR(whole_archive_flag_spec, $1)='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' m4_case([$1], [F77], [_LT_TAGVAR(compiler_needs_object, $1)=yes], [FC], [_LT_TAGVAR(compiler_needs_object, $1)=yes]) else _LT_TAGVAR(whole_archive_flag_spec, $1)='' fi _LT_TAGVAR(link_all_deplibs, $1)=yes _LT_TAGVAR(allow_undefined_flag, $1)=$_lt_dar_allow_undefined case $cc_basename in ifort*|nagfor*) _lt_dar_can_shared=yes ;; *) _lt_dar_can_shared=$GCC ;; esac if test yes = "$_lt_dar_can_shared"; then output_verbose_link_cmd=func_echo_all _LT_TAGVAR(archive_cmds, $1)="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" _LT_TAGVAR(module_cmds, $1)="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" _LT_TAGVAR(archive_expsym_cmds, $1)="$SED 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" _LT_TAGVAR(module_expsym_cmds, $1)="$SED -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" m4_if([$1], [CXX], [ if test yes = "$_lt_dar_needs_single_mod" -a yes != "$lt_cv_apple_cc_single_mod"; then _LT_TAGVAR(archive_cmds, $1)="\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dsymutil" _LT_TAGVAR(archive_expsym_cmds, $1)="$SED 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dar_export_syms$_lt_dsymutil" fi ],[]) else _LT_TAGVAR(ld_shlibs, $1)=no fi ]) # _LT_SYS_MODULE_PATH_AIX([TAGNAME]) # ---------------------------------- # Links a minimal program and checks the executable # for the system default hardcoded library path. In most cases, # this is /usr/lib:/lib, but when the MPI compilers are used # the location of the communication and MPI libs are included too. # If we don't find anything, use the default library path according # to the aix ld manual. # Store the results from the different compilers for each TAGNAME. # Allow to override them for all tags through lt_cv_aix_libpath. m4_defun([_LT_SYS_MODULE_PATH_AIX], [m4_require([_LT_DECL_SED])dnl if test set = "${lt_cv_aix_libpath+set}"; then aix_libpath=$lt_cv_aix_libpath else AC_CACHE_VAL([_LT_TAGVAR([lt_cv_aix_libpath_], [$1])], [AC_LINK_IFELSE([AC_LANG_PROGRAM],[ lt_aix_libpath_sed='[ /Import File Strings/,/^$/ { /^0/ { s/^0 *\([^ ]*\) *$/\1/ p } }]' _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` # Check for a 64-bit object if we didn't find anything. if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` fi],[]) if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=/usr/lib:/lib fi ]) aix_libpath=$_LT_TAGVAR([lt_cv_aix_libpath_], [$1]) fi ])# _LT_SYS_MODULE_PATH_AIX # _LT_SHELL_INIT(ARG) # ------------------- m4_define([_LT_SHELL_INIT], [m4_divert_text([M4SH-INIT], [$1 ])])# _LT_SHELL_INIT # _LT_PROG_ECHO_BACKSLASH # ----------------------- # Find how we can fake an echo command that does not interpret backslash. # In particular, with Autoconf 2.60 or later we add some code to the start # of the generated configure script that will find a shell with a builtin # printf (that we can use as an echo command). m4_defun([_LT_PROG_ECHO_BACKSLASH], [ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO AC_MSG_CHECKING([how to print strings]) # Test print first, because it will be a builtin if present. if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then ECHO='print -r --' elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then ECHO='printf %s\n' else # Use this function as a fallback that always works. func_fallback_echo () { eval 'cat <<_LTECHO_EOF $[]1 _LTECHO_EOF' } ECHO='func_fallback_echo' fi # func_echo_all arg... # Invoke $ECHO with all args, space-separated. func_echo_all () { $ECHO "$*" } case $ECHO in printf*) AC_MSG_RESULT([printf]) ;; print*) AC_MSG_RESULT([print -r]) ;; *) AC_MSG_RESULT([cat]) ;; esac m4_ifdef([_AS_DETECT_SUGGESTED], [_AS_DETECT_SUGGESTED([ test -n "${ZSH_VERSION+set}${BASH_VERSION+set}" || ( ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO PATH=/empty FPATH=/empty; export PATH FPATH test "X`printf %s $ECHO`" = "X$ECHO" \ || test "X`print -r -- $ECHO`" = "X$ECHO" )])]) _LT_DECL([], [SHELL], [1], [Shell to use when invoking shell scripts]) _LT_DECL([], [ECHO], [1], [An echo program that protects backslashes]) ])# _LT_PROG_ECHO_BACKSLASH # _LT_WITH_SYSROOT # ---------------- AC_DEFUN([_LT_WITH_SYSROOT], [m4_require([_LT_DECL_SED])dnl AC_MSG_CHECKING([for sysroot]) AC_ARG_WITH([sysroot], [AS_HELP_STRING([--with-sysroot@<:@=DIR@:>@], [Search for dependent libraries within DIR (or the compiler's sysroot if not specified).])], [], [with_sysroot=no]) dnl lt_sysroot will always be passed unquoted. We quote it here dnl in case the user passed a directory name. lt_sysroot= case $with_sysroot in #( yes) if test yes = "$GCC"; then # Trim trailing / since we'll always append absolute paths and we want # to avoid //, if only for less confusing output for the user. lt_sysroot=`$CC --print-sysroot 2>/dev/null | $SED 's:/\+$::'` fi ;; #( /*) lt_sysroot=`echo "$with_sysroot" | $SED -e "$sed_quote_subst"` ;; #( no|'') ;; #( *) AC_MSG_RESULT([$with_sysroot]) AC_MSG_ERROR([The sysroot must be an absolute path.]) ;; esac AC_MSG_RESULT([${lt_sysroot:-no}]) _LT_DECL([], [lt_sysroot], [0], [The root where to search for ]dnl [dependent libraries, and where our libraries should be installed.])]) # _LT_ENABLE_LOCK # --------------- m4_defun([_LT_ENABLE_LOCK], [AC_ARG_ENABLE([libtool-lock], [AS_HELP_STRING([--disable-libtool-lock], [avoid locking (might break parallel builds)])]) test no = "$enable_libtool_lock" || enable_libtool_lock=yes # Some flags need to be propagated to the compiler or linker for good # libtool support. case $host in ia64-*-hpux*) # Find out what ABI is being produced by ac_compile, and set mode # options accordingly. echo 'int i;' > conftest.$ac_ext if AC_TRY_EVAL(ac_compile); then case `$FILECMD conftest.$ac_objext` in *ELF-32*) HPUX_IA64_MODE=32 ;; *ELF-64*) HPUX_IA64_MODE=64 ;; esac fi rm -rf conftest* ;; *-*-irix6*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext if AC_TRY_EVAL(ac_compile); then if test yes = "$lt_cv_prog_gnu_ld"; then case `$FILECMD conftest.$ac_objext` in *32-bit*) LD="${LD-ld} -melf32bsmip" ;; *N32*) LD="${LD-ld} -melf32bmipn32" ;; *64-bit*) LD="${LD-ld} -melf64bmip" ;; esac else case `$FILECMD conftest.$ac_objext` in *32-bit*) LD="${LD-ld} -32" ;; *N32*) LD="${LD-ld} -n32" ;; *64-bit*) LD="${LD-ld} -64" ;; esac fi fi rm -rf conftest* ;; mips64*-*linux*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext if AC_TRY_EVAL(ac_compile); then emul=elf case `$FILECMD conftest.$ac_objext` in *32-bit*) emul="${emul}32" ;; *64-bit*) emul="${emul}64" ;; esac case `$FILECMD conftest.$ac_objext` in *MSB*) emul="${emul}btsmip" ;; *LSB*) emul="${emul}ltsmip" ;; esac case `$FILECMD conftest.$ac_objext` in *N32*) emul="${emul}n32" ;; esac LD="${LD-ld} -m $emul" fi rm -rf conftest* ;; x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \ s390*-*linux*|s390*-*tpf*|sparc*-*linux*|x86_64-gnu*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. Note that the listed cases only cover the # situations where additional linker options are needed (such as when # doing 32-bit compilation for a host where ld defaults to 64-bit, or # vice versa); the common cases where no linker options are needed do # not appear in the list. echo 'int i;' > conftest.$ac_ext if AC_TRY_EVAL(ac_compile); then case `$FILECMD conftest.o` in *32-bit*) case $host in x86_64-*kfreebsd*-gnu) LD="${LD-ld} -m elf_i386_fbsd" ;; x86_64-*linux*|x86_64-gnu*) case `$FILECMD conftest.o` in *x86-64*) LD="${LD-ld} -m elf32_x86_64" ;; *) LD="${LD-ld} -m elf_i386" ;; esac ;; powerpc64le-*linux*) LD="${LD-ld} -m elf32lppclinux" ;; powerpc64-*linux*) LD="${LD-ld} -m elf32ppclinux" ;; s390x-*linux*) LD="${LD-ld} -m elf_s390" ;; sparc64-*linux*) LD="${LD-ld} -m elf32_sparc" ;; esac ;; *64-bit*) case $host in x86_64-*kfreebsd*-gnu) LD="${LD-ld} -m elf_x86_64_fbsd" ;; x86_64-*linux*|x86_64-gnu*) LD="${LD-ld} -m elf_x86_64" ;; powerpcle-*linux*) LD="${LD-ld} -m elf64lppc" ;; powerpc-*linux*) LD="${LD-ld} -m elf64ppc" ;; s390*-*linux*|s390*-*tpf*) LD="${LD-ld} -m elf64_s390" ;; sparc*-*linux*) LD="${LD-ld} -m elf64_sparc" ;; esac ;; esac fi rm -rf conftest* ;; *-*-sco3.2v5*) # On SCO OpenServer 5, we need -belf to get full-featured binaries. SAVE_CFLAGS=$CFLAGS CFLAGS="$CFLAGS -belf" AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf, [AC_LANG_PUSH(C) AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[]])],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no]) AC_LANG_POP]) if test yes != "$lt_cv_cc_needs_belf"; then # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf CFLAGS=$SAVE_CFLAGS fi ;; *-*solaris*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. echo 'int i;' > conftest.$ac_ext if AC_TRY_EVAL(ac_compile); then case `$FILECMD conftest.o` in *64-bit*) case $lt_cv_prog_gnu_ld in yes*) case $host in i?86-*-solaris*|x86_64-*-solaris*) LD="${LD-ld} -m elf_x86_64" ;; sparc*-*-solaris*) LD="${LD-ld} -m elf64_sparc" ;; esac # GNU ld 2.21 introduced _sol2 emulations. Use them if available. if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then LD=${LD-ld}_sol2 fi ;; *) if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then LD="${LD-ld} -64" fi ;; esac ;; esac fi rm -rf conftest* ;; esac need_locks=$enable_libtool_lock ])# _LT_ENABLE_LOCK # _LT_PROG_AR # ----------- m4_defun([_LT_PROG_AR], [AC_CHECK_TOOLS(AR, [ar], false) : ${AR=ar} _LT_DECL([], [AR], [1], [The archiver]) # Use ARFLAGS variable as AR's operation code to sync the variable naming with # Automake. If both AR_FLAGS and ARFLAGS are specified, AR_FLAGS should have # higher priority because that's what people were doing historically (setting # ARFLAGS for automake and AR_FLAGS for libtool). FIXME: Make the AR_FLAGS # variable obsoleted/removed. test ${AR_FLAGS+y} || AR_FLAGS=${ARFLAGS-cr} lt_ar_flags=$AR_FLAGS _LT_DECL([], [lt_ar_flags], [0], [Flags to create an archive (by configure)]) # Make AR_FLAGS overridable by 'make ARFLAGS='. Don't try to run-time override # by AR_FLAGS because that was never working and AR_FLAGS is about to die. _LT_DECL([], [AR_FLAGS], [\@S|@{ARFLAGS-"\@S|@lt_ar_flags"}], [Flags to create an archive]) AC_CACHE_CHECK([for archiver @FILE support], [lt_cv_ar_at_file], [lt_cv_ar_at_file=no AC_COMPILE_IFELSE([AC_LANG_PROGRAM], [echo conftest.$ac_objext > conftest.lst lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&AS_MESSAGE_LOG_FD' AC_TRY_EVAL([lt_ar_try]) if test 0 -eq "$ac_status"; then # Ensure the archiver fails upon bogus file names. rm -f conftest.$ac_objext libconftest.a AC_TRY_EVAL([lt_ar_try]) if test 0 -ne "$ac_status"; then lt_cv_ar_at_file=@ fi fi rm -f conftest.* libconftest.a ]) ]) if test no = "$lt_cv_ar_at_file"; then archiver_list_spec= else archiver_list_spec=$lt_cv_ar_at_file fi _LT_DECL([], [archiver_list_spec], [1], [How to feed a file listing to the archiver]) ])# _LT_PROG_AR # _LT_CMD_OLD_ARCHIVE # ------------------- m4_defun([_LT_CMD_OLD_ARCHIVE], [_LT_PROG_AR AC_CHECK_TOOL(STRIP, strip, :) test -z "$STRIP" && STRIP=: _LT_DECL([], [STRIP], [1], [A symbol stripping program]) AC_REQUIRE([AC_PROG_RANLIB]) test -z "$RANLIB" && RANLIB=: _LT_DECL([], [RANLIB], [1], [Commands used to install an old-style archive]) # Determine commands to create old-style static archives. old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' old_postinstall_cmds='chmod 644 $oldlib' old_postuninstall_cmds= if test -n "$RANLIB"; then old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" fi case $host_os in darwin*) lock_old_archive_extraction=yes ;; *) lock_old_archive_extraction=no ;; esac _LT_DECL([], [old_postinstall_cmds], [2]) _LT_DECL([], [old_postuninstall_cmds], [2]) _LT_TAGDECL([], [old_archive_cmds], [2], [Commands used to build an old-style archive]) _LT_DECL([], [lock_old_archive_extraction], [0], [Whether to use a lock for old archive extraction]) ])# _LT_CMD_OLD_ARCHIVE # _LT_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, # [OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE]) # ---------------------------------------------------------------- # Check whether the given compiler option works AC_DEFUN([_LT_COMPILER_OPTION], [m4_require([_LT_FILEUTILS_DEFAULTS])dnl m4_require([_LT_DECL_SED])dnl AC_CACHE_CHECK([$1], [$2], [$2=no m4_if([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4]) echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="$3" ## exclude from sc_useless_quotes_in_assignment # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. # The option is referenced via a variable to avoid confusing sed. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&AS_MESSAGE_LOG_FD echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then $2=yes fi fi $RM conftest* ]) if test yes = "[$]$2"; then m4_if([$5], , :, [$5]) else m4_if([$6], , :, [$6]) fi ])# _LT_COMPILER_OPTION # Old name: AU_ALIAS([AC_LIBTOOL_COMPILER_OPTION], [_LT_COMPILER_OPTION]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], []) # _LT_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, # [ACTION-SUCCESS], [ACTION-FAILURE]) # ---------------------------------------------------- # Check whether the given linker option works AC_DEFUN([_LT_LINKER_OPTION], [m4_require([_LT_FILEUTILS_DEFAULTS])dnl m4_require([_LT_DECL_SED])dnl AC_CACHE_CHECK([$1], [$2], [$2=no save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS $3" echo "$lt_simple_link_test_code" > conftest.$ac_ext if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then # The linker can only warn and ignore the option if not recognized # So say no if there are warnings if test -s conftest.err; then # Append any errors to the config.log. cat conftest.err 1>&AS_MESSAGE_LOG_FD $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if diff conftest.exp conftest.er2 >/dev/null; then $2=yes fi else $2=yes fi fi $RM -r conftest* LDFLAGS=$save_LDFLAGS ]) if test yes = "[$]$2"; then m4_if([$4], , :, [$4]) else m4_if([$5], , :, [$5]) fi ])# _LT_LINKER_OPTION # Old name: AU_ALIAS([AC_LIBTOOL_LINKER_OPTION], [_LT_LINKER_OPTION]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], []) # LT_CMD_MAX_LEN #--------------- AC_DEFUN([LT_CMD_MAX_LEN], [AC_REQUIRE([AC_CANONICAL_HOST])dnl # find the maximum length of command line arguments AC_MSG_CHECKING([the maximum length of command line arguments]) AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl i=0 teststring=ABCD case $build_os in msdosdjgpp*) # On DJGPP, this test can blow up pretty badly due to problems in libc # (any single argument exceeding 2000 bytes causes a buffer overrun # during glob expansion). Even if it were fixed, the result of this # check would be larger than it should be. lt_cv_sys_max_cmd_len=12288; # 12K is about right ;; gnu* | ironclad*) # Under GNU Hurd and Ironclad, this test is not required because there # is no limit to the length of command line arguments. # Libtool will interpret -1 as no limit whatsoever lt_cv_sys_max_cmd_len=-1; ;; cygwin* | mingw* | windows* | cegcc*) # On Win9x/ME, this test blows up -- it succeeds, but takes # about 5 minutes as the teststring grows exponentially. # Worse, since 9x/ME are not pre-emptively multitasking, # you end up with a "frozen" computer, even though with patience # the test eventually succeeds (with a max line length of 256k). # Instead, let's just punt: use the minimum linelength reported by # all of the supported platforms: 8192 (on NT/2K/XP). lt_cv_sys_max_cmd_len=8192; ;; mint*) # On MiNT this can take a long time and run out of memory. lt_cv_sys_max_cmd_len=8192; ;; amigaos*) # On AmigaOS with pdksh, this test takes hours, literally. # So we just punt and use a minimum line length of 8192. lt_cv_sys_max_cmd_len=8192; ;; darwin* | dragonfly* | freebsd* | midnightbsd* | netbsd* | openbsd*) # This has been around since 386BSD, at least. Likely further. if test -x /sbin/sysctl; then lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` elif test -x /usr/sbin/sysctl; then lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` else lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs fi # And add a safety zone lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` ;; interix*) # We know the value 262144 and hardcode it with a safety zone (like BSD) lt_cv_sys_max_cmd_len=196608 ;; os2*) # The test takes a long time on OS/2. lt_cv_sys_max_cmd_len=8192 ;; osf*) # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not # nice to cause kernel panics so lets avoid the loop below. # First set a reasonable default. lt_cv_sys_max_cmd_len=16384 # if test -x /sbin/sysconfig; then case `/sbin/sysconfig -q proc exec_disable_arg_limit` in *1*) lt_cv_sys_max_cmd_len=-1 ;; esac fi ;; sco3.2v5*) lt_cv_sys_max_cmd_len=102400 ;; sysv5* | sco5v6* | sysv4.2uw2*) kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` if test -n "$kargmax"; then lt_cv_sys_max_cmd_len=`echo $kargmax | $SED 's/.*[[ ]]//'` else lt_cv_sys_max_cmd_len=32768 fi ;; *) lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` if test -n "$lt_cv_sys_max_cmd_len" && \ test undefined != "$lt_cv_sys_max_cmd_len"; then lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` else # Make teststring a little bigger before we do anything with it. # a 1K string should be a reasonable start. for i in 1 2 3 4 5 6 7 8; do teststring=$teststring$teststring done SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} # If test is not a shell built-in, we'll probably end up computing a # maximum length that is only half of the actual maximum length, but # we can't tell. while { test X`env echo "$teststring$teststring" 2>/dev/null` \ = "X$teststring$teststring"; } >/dev/null 2>&1 && test 17 != "$i" # 1/2 MB should be enough do i=`expr $i + 1` teststring=$teststring$teststring done # Only check the string length outside the loop. lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` teststring= # Add a significant safety factor because C++ compilers can tack on # massive amounts of additional arguments before passing them to the # linker. It appears as though 1/2 is a usable value. lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` fi ;; esac ]) if test -n "$lt_cv_sys_max_cmd_len"; then AC_MSG_RESULT($lt_cv_sys_max_cmd_len) else AC_MSG_RESULT(none) fi max_cmd_len=$lt_cv_sys_max_cmd_len _LT_DECL([], [max_cmd_len], [0], [What is the maximum length of a command?]) ])# LT_CMD_MAX_LEN # Old name: AU_ALIAS([AC_LIBTOOL_SYS_MAX_CMD_LEN], [LT_CMD_MAX_LEN]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], []) # _LT_HEADER_DLFCN # ---------------- m4_defun([_LT_HEADER_DLFCN], [AC_CHECK_HEADERS([dlfcn.h], [], [], [AC_INCLUDES_DEFAULT])dnl ])# _LT_HEADER_DLFCN # _LT_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE, # ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING) # ---------------------------------------------------------------- m4_defun([_LT_TRY_DLOPEN_SELF], [m4_require([_LT_HEADER_DLFCN])dnl if test yes = "$cross_compiling"; then : [$4] else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF [#line $LINENO "configure" #include "confdefs.h" #if HAVE_DLFCN_H #include #endif #include #ifdef RTLD_GLOBAL # define LT_DLGLOBAL RTLD_GLOBAL #else # ifdef DL_GLOBAL # define LT_DLGLOBAL DL_GLOBAL # else # define LT_DLGLOBAL 0 # endif #endif /* We may have to define LT_DLLAZY_OR_NOW in the command line if we find out it does not work in some platform. */ #ifndef LT_DLLAZY_OR_NOW # ifdef RTLD_LAZY # define LT_DLLAZY_OR_NOW RTLD_LAZY # else # ifdef DL_LAZY # define LT_DLLAZY_OR_NOW DL_LAZY # else # ifdef RTLD_NOW # define LT_DLLAZY_OR_NOW RTLD_NOW # else # ifdef DL_NOW # define LT_DLLAZY_OR_NOW DL_NOW # else # define LT_DLLAZY_OR_NOW 0 # endif # endif # endif # endif #endif /* When -fvisibility=hidden is used, assume the code has been annotated correspondingly for the symbols needed. */ #if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) int fnord (void) __attribute__((visibility("default"))); #endif int fnord (void) { return 42; } int main (void) { void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); int status = $lt_dlunknown; if (self) { if (dlsym (self,"fnord")) status = $lt_dlno_uscore; else { if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; else puts (dlerror ()); } /* dlclose (self); */ } else puts (dlerror ()); return status; }] _LT_EOF if AC_TRY_EVAL(ac_link) && test -s "conftest$ac_exeext" 2>/dev/null; then (./conftest; exit; ) >&AS_MESSAGE_LOG_FD 2>/dev/null lt_status=$? case x$lt_status in x$lt_dlno_uscore) $1 ;; x$lt_dlneed_uscore) $2 ;; x$lt_dlunknown|x*) $3 ;; esac else : # compilation failed $3 fi fi rm -fr conftest* ])# _LT_TRY_DLOPEN_SELF # LT_SYS_DLOPEN_SELF # ------------------ AC_DEFUN([LT_SYS_DLOPEN_SELF], [m4_require([_LT_HEADER_DLFCN])dnl if test yes != "$enable_dlopen"; then enable_dlopen=unknown enable_dlopen_self=unknown enable_dlopen_self_static=unknown else lt_cv_dlopen=no lt_cv_dlopen_libs= case $host_os in beos*) lt_cv_dlopen=load_add_on lt_cv_dlopen_libs= lt_cv_dlopen_self=yes ;; mingw* | windows* | pw32* | cegcc*) lt_cv_dlopen=LoadLibrary lt_cv_dlopen_libs= ;; cygwin*) lt_cv_dlopen=dlopen lt_cv_dlopen_libs= ;; darwin*) # if libdl is installed we need to link against it AC_CHECK_LIB([dl], [dlopen], [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl],[ lt_cv_dlopen=dyld lt_cv_dlopen_libs= lt_cv_dlopen_self=yes ]) ;; tpf*) # Don't try to run any link tests for TPF. We know it's impossible # because TPF is a cross-compiler, and we know how we open DSOs. lt_cv_dlopen=dlopen lt_cv_dlopen_libs= lt_cv_dlopen_self=no ;; *) AC_CHECK_FUNC([shl_load], [lt_cv_dlopen=shl_load], [AC_CHECK_LIB([dld], [shl_load], [lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld], [AC_CHECK_FUNC([dlopen], [lt_cv_dlopen=dlopen], [AC_CHECK_LIB([dl], [dlopen], [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl], [AC_CHECK_LIB([svld], [dlopen], [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld], [AC_CHECK_LIB([dld], [dld_link], [lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld]) ]) ]) ]) ]) ]) ;; esac if test no = "$lt_cv_dlopen"; then enable_dlopen=no else enable_dlopen=yes fi case $lt_cv_dlopen in dlopen) save_CPPFLAGS=$CPPFLAGS test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" save_LDFLAGS=$LDFLAGS wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" save_LIBS=$LIBS LIBS="$lt_cv_dlopen_libs $LIBS" AC_CACHE_CHECK([whether a program can dlopen itself], lt_cv_dlopen_self, [dnl _LT_TRY_DLOPEN_SELF( lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes, lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross) ]) if test yes = "$lt_cv_dlopen_self"; then wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" AC_CACHE_CHECK([whether a statically linked program can dlopen itself], lt_cv_dlopen_self_static, [dnl _LT_TRY_DLOPEN_SELF( lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=no, lt_cv_dlopen_self_static=cross) ]) fi CPPFLAGS=$save_CPPFLAGS LDFLAGS=$save_LDFLAGS LIBS=$save_LIBS ;; esac case $lt_cv_dlopen_self in yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; *) enable_dlopen_self=unknown ;; esac case $lt_cv_dlopen_self_static in yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; *) enable_dlopen_self_static=unknown ;; esac fi _LT_DECL([dlopen_support], [enable_dlopen], [0], [Whether dlopen is supported]) _LT_DECL([dlopen_self], [enable_dlopen_self], [0], [Whether dlopen of programs is supported]) _LT_DECL([dlopen_self_static], [enable_dlopen_self_static], [0], [Whether dlopen of statically linked programs is supported]) ])# LT_SYS_DLOPEN_SELF # Old name: AU_ALIAS([AC_LIBTOOL_DLOPEN_SELF], [LT_SYS_DLOPEN_SELF]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], []) # _LT_COMPILER_C_O([TAGNAME]) # --------------------------- # Check to see if options -c and -o are simultaneously supported by compiler. # This macro does not hard code the compiler like AC_PROG_CC_C_O. m4_defun([_LT_COMPILER_C_O], [m4_require([_LT_DECL_SED])dnl m4_require([_LT_FILEUTILS_DEFAULTS])dnl m4_require([_LT_TAG_COMPILER])dnl AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext], [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)], [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no $RM -r conftest 2>/dev/null mkdir conftest cd conftest mkdir out echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="-o out/conftest2.$ac_objext" # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&AS_MESSAGE_LOG_FD echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then _LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes fi fi chmod u+w . 2>&AS_MESSAGE_LOG_FD $RM conftest* # SGI C++ compiler will create directory out/ii_files/ for # template instantiation test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files $RM out/* && rmdir out cd .. $RM -r conftest $RM conftest* ]) _LT_TAGDECL([compiler_c_o], [lt_cv_prog_compiler_c_o], [1], [Does compiler simultaneously support -c and -o options?]) ])# _LT_COMPILER_C_O # _LT_COMPILER_FILE_LOCKS([TAGNAME]) # ---------------------------------- # Check to see if we can do hard links to lock some files if needed m4_defun([_LT_COMPILER_FILE_LOCKS], [m4_require([_LT_ENABLE_LOCK])dnl m4_require([_LT_FILEUTILS_DEFAULTS])dnl _LT_COMPILER_C_O([$1]) hard_links=nottested if test no = "$_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)" && test no != "$need_locks"; then # do not overwrite the value of need_locks provided by the user AC_MSG_CHECKING([if we can lock with hard links]) hard_links=yes $RM conftest* ln conftest.a conftest.b 2>/dev/null && hard_links=no touch conftest.a ln conftest.a conftest.b 2>&5 || hard_links=no ln conftest.a conftest.b 2>/dev/null && hard_links=no AC_MSG_RESULT([$hard_links]) if test no = "$hard_links"; then AC_MSG_WARN(['$CC' does not support '-c -o', so 'make -j' may be unsafe]) need_locks=warn fi else need_locks=no fi _LT_DECL([], [need_locks], [1], [Must we lock files when doing compilation?]) ])# _LT_COMPILER_FILE_LOCKS # _LT_CHECK_OBJDIR # ---------------- m4_defun([_LT_CHECK_OBJDIR], [AC_CACHE_CHECK([for objdir], [lt_cv_objdir], [rm -f .libs 2>/dev/null mkdir .libs 2>/dev/null if test -d .libs; then lt_cv_objdir=.libs else # MS-DOS does not allow filenames that begin with a dot. lt_cv_objdir=_libs fi rmdir .libs 2>/dev/null]) objdir=$lt_cv_objdir _LT_DECL([], [objdir], [0], [The name of the directory that contains temporary libtool files])dnl m4_pattern_allow([LT_OBJDIR])dnl AC_DEFINE_UNQUOTED([LT_OBJDIR], "$lt_cv_objdir/", [Define to the sub-directory where libtool stores uninstalled libraries.]) ])# _LT_CHECK_OBJDIR # _LT_LINKER_HARDCODE_LIBPATH([TAGNAME]) # -------------------------------------- # Check hardcoding attributes. m4_defun([_LT_LINKER_HARDCODE_LIBPATH], [AC_MSG_CHECKING([how to hardcode library paths into programs]) _LT_TAGVAR(hardcode_action, $1)= if test -n "$_LT_TAGVAR(hardcode_libdir_flag_spec, $1)" || test -n "$_LT_TAGVAR(runpath_var, $1)" || test yes = "$_LT_TAGVAR(hardcode_automatic, $1)"; then # We can hardcode non-existent directories. if test no != "$_LT_TAGVAR(hardcode_direct, $1)" && # If the only mechanism to avoid hardcoding is shlibpath_var, we # have to relink, otherwise we might link with an installed library # when we should be linking with a yet-to-be-installed one ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, $1)" && test no != "$_LT_TAGVAR(hardcode_minus_L, $1)"; then # Linking always hardcodes the temporary library directory. _LT_TAGVAR(hardcode_action, $1)=relink else # We can link without hardcoding, and we can hardcode nonexisting dirs. _LT_TAGVAR(hardcode_action, $1)=immediate fi else # We cannot hardcode anything, or else we can only hardcode existing # directories. _LT_TAGVAR(hardcode_action, $1)=unsupported fi AC_MSG_RESULT([$_LT_TAGVAR(hardcode_action, $1)]) if test relink = "$_LT_TAGVAR(hardcode_action, $1)" || test yes = "$_LT_TAGVAR(inherit_rpath, $1)"; then # Fast installation is not supported enable_fast_install=no elif test yes = "$shlibpath_overrides_runpath" || test no = "$enable_shared"; then # Fast installation is not necessary enable_fast_install=needless fi _LT_TAGDECL([], [hardcode_action], [0], [How to hardcode a shared library path into an executable]) ])# _LT_LINKER_HARDCODE_LIBPATH # _LT_CMD_STRIPLIB # ---------------- m4_defun([_LT_CMD_STRIPLIB], [m4_require([_LT_DECL_EGREP]) striplib= old_striplib= AC_MSG_CHECKING([whether stripping libraries is possible]) if test -z "$STRIP"; then AC_MSG_RESULT([no]) else if $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then old_striplib="$STRIP --strip-debug" striplib="$STRIP --strip-unneeded" AC_MSG_RESULT([yes]) else case $host_os in darwin*) # FIXME - insert some real tests, host_os isn't really good enough striplib="$STRIP -x" old_striplib="$STRIP -S" AC_MSG_RESULT([yes]) ;; freebsd*) if $STRIP -V 2>&1 | $GREP "elftoolchain" >/dev/null; then old_striplib="$STRIP --strip-debug" striplib="$STRIP --strip-unneeded" AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) fi ;; *) AC_MSG_RESULT([no]) ;; esac fi fi _LT_DECL([], [old_striplib], [1], [Commands to strip libraries]) _LT_DECL([], [striplib], [1]) ])# _LT_CMD_STRIPLIB # _LT_PREPARE_MUNGE_PATH_LIST # --------------------------- # Make sure func_munge_path_list() is defined correctly. m4_defun([_LT_PREPARE_MUNGE_PATH_LIST], [[# func_munge_path_list VARIABLE PATH # ----------------------------------- # VARIABLE is name of variable containing _space_ separated list of # directories to be munged by the contents of PATH, which is string # having a format: # "DIR[:DIR]:" # string "DIR[ DIR]" will be prepended to VARIABLE # ":DIR[:DIR]" # string "DIR[ DIR]" will be appended to VARIABLE # "DIRP[:DIRP]::[DIRA:]DIRA" # string "DIRP[ DIRP]" will be prepended to VARIABLE and string # "DIRA[ DIRA]" will be appended to VARIABLE # "DIR[:DIR]" # VARIABLE will be replaced by "DIR[ DIR]" func_munge_path_list () { case x@S|@2 in x) ;; *:) eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'` \@S|@@S|@1\" ;; x:*) eval @S|@1=\"\@S|@@S|@1 `$ECHO @S|@2 | $SED 's/:/ /g'`\" ;; *::*) eval @S|@1=\"\@S|@@S|@1\ `$ECHO @S|@2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" eval @S|@1=\"`$ECHO @S|@2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \@S|@@S|@1\" ;; *) eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'`\" ;; esac } ]])# _LT_PREPARE_PATH_LIST # _LT_SYS_DYNAMIC_LINKER([TAG]) # ----------------------------- # PORTME Fill in your ld.so characteristics m4_defun([_LT_SYS_DYNAMIC_LINKER], [AC_REQUIRE([AC_CANONICAL_HOST])dnl m4_require([_LT_DECL_EGREP])dnl m4_require([_LT_FILEUTILS_DEFAULTS])dnl m4_require([_LT_DECL_OBJDUMP])dnl m4_require([_LT_DECL_SED])dnl m4_require([_LT_CHECK_SHELL_FEATURES])dnl m4_require([_LT_PREPARE_MUNGE_PATH_LIST])dnl AC_MSG_CHECKING([dynamic linker characteristics]) m4_if([$1], [], [ if test yes = "$GCC"; then case $host_os in darwin*) lt_awk_arg='/^libraries:/,/LR/' ;; *) lt_awk_arg='/^libraries:/' ;; esac case $host_os in mingw* | windows* | cegcc*) lt_sed_strip_eq='s|=\([[A-Za-z]]:\)|\1|g' ;; *) lt_sed_strip_eq='s|=/|/|g' ;; esac lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` case $lt_search_path_spec in *\;*) # if the path contains ";" then we assume it to be the separator # otherwise default to the standard path separator (i.e. ":") - it is # assumed that no part of a normal pathname contains ";" but that should # okay in the real world where ";" in dirpaths is itself problematic. lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` ;; *) lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` ;; esac # Ok, now we have the path, separated by spaces, we can step through it # and add multilib dir if necessary... lt_tmp_lt_search_path_spec= lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` # ...but if some path component already ends with the multilib dir we assume # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer). case "$lt_multi_os_dir; $lt_search_path_spec " in "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*) lt_multi_os_dir= ;; esac for lt_sys_path in $lt_search_path_spec; do if test -d "$lt_sys_path$lt_multi_os_dir"; then lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir" elif test -n "$lt_multi_os_dir"; then test -d "$lt_sys_path" && \ lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" fi done lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' BEGIN {RS = " "; FS = "/|\n";} { lt_foo = ""; lt_count = 0; for (lt_i = NF; lt_i > 0; lt_i--) { if ($lt_i != "" && $lt_i != ".") { if ($lt_i == "..") { lt_count++; } else { if (lt_count == 0) { lt_foo = "/" $lt_i lt_foo; } else { lt_count--; } } } } if (lt_foo != "") { lt_freq[[lt_foo]]++; } if (lt_freq[[lt_foo]] == 1) { print lt_foo; } }'` # AWK program above erroneously prepends '/' to C:/dos/paths # for these hosts. case $host_os in mingw* | windows* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ $SED 's|/\([[A-Za-z]]:\)|\1|g'` ;; esac sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` else sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" fi]) library_names_spec= libname_spec='lib$name' soname_spec= shrext_cmds=.so postinstall_cmds= postuninstall_cmds= finish_cmds= finish_eval= shlibpath_var= shlibpath_overrides_runpath=unknown version_type=none dynamic_linker="$host_os ld.so" sys_lib_dlsearch_path_spec="/lib /usr/lib" need_lib_prefix=unknown hardcode_into_libs=no # when you set need_version to no, make sure it does not cause -set_version # flags to be left without arguments need_version=unknown AC_ARG_VAR([LT_SYS_LIBRARY_PATH], [User-defined run-time library search path.]) case $host_os in aix3*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname.a' shlibpath_var=LIBPATH # AIX 3 has no versioning support, so we append a major version to the name. soname_spec='$libname$release$shared_ext$major' ;; aix[[4-9]]*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no hardcode_into_libs=yes if test ia64 = "$host_cpu"; then # AIX 5 supports IA64 library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH else # With GCC up to 2.95.x, collect2 would create an import file # for dependence libraries. The import file would start with # the line '#! .'. This would cause the generated library to # depend on '.', always an invalid library. This was fixed in # development snapshots of GCC prior to 3.0. case $host_os in aix4 | aix4.[[01]] | aix4.[[01]].*) if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' echo ' yes ' echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then : else can_build_shared=no fi ;; esac # Using Import Files as archive members, it is possible to support # filename-based versioning of shared library archives on AIX. While # this would work for both with and without runtime linking, it will # prevent static linking of such archives. So we do filename-based # shared library versioning with .so extension only, which is used # when both runtime linking and shared linking is enabled. # Unfortunately, runtime linking may impact performance, so we do # not want this to be the default eventually. Also, we use the # versioned .so libs for executables only if there is the -brtl # linker flag in LDFLAGS as well, or --enable-aix-soname=svr4 only. # To allow for filename-based versioning support, we need to create # libNAME.so.V as an archive file, containing: # *) an Import File, referring to the versioned filename of the # archive as well as the shared archive member, telling the # bitwidth (32 or 64) of that shared object, and providing the # list of exported symbols of that shared object, eventually # decorated with the 'weak' keyword # *) the shared object with the F_LOADONLY flag set, to really avoid # it being seen by the linker. # At run time we better use the real file rather than another symlink, # but for link time we create the symlink libNAME.so -> libNAME.so.V case $with_aix_soname,$aix_use_runtimelinking in # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct # soname into executable. Probably we can add versioning support to # collect2, so additional links can be useful in future. aix,yes) # traditional libtool dynamic_linker='AIX unversionable lib.so' # If using run time linking (on AIX 4.2 or later) use lib.so # instead of lib.a to let people know that these are not # typical AIX shared libraries. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' ;; aix,no) # traditional AIX only dynamic_linker='AIX lib.a[(]lib.so.V[)]' # We preserve .a as extension for shared libraries through AIX4.2 # and later when we are not doing run time linking. library_names_spec='$libname$release.a $libname.a' soname_spec='$libname$release$shared_ext$major' ;; svr4,*) # full svr4 only dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)]" library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' # We do not specify a path in Import Files, so LIBPATH fires. shlibpath_overrides_runpath=yes ;; *,yes) # both, prefer svr4 dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)], lib.a[(]lib.so.V[)]" library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' # unpreferred sharedlib libNAME.a needs extra handling postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' # We do not specify a path in Import Files, so LIBPATH fires. shlibpath_overrides_runpath=yes ;; *,no) # both, prefer aix dynamic_linker="AIX lib.a[(]lib.so.V[)], lib.so.V[(]$shared_archive_member_spec.o[)]" library_names_spec='$libname$release.a $libname.a' soname_spec='$libname$release$shared_ext$major' # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' ;; esac shlibpath_var=LIBPATH fi ;; amigaos*) case $host_cpu in powerpc) # Since July 2007 AmigaOS4 officially supports .so libraries. # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' ;; m68k) library_names_spec='$libname.ixlibrary $libname.a' # Create ${libname}_ixlibrary.a entries in /sys/libs. finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' ;; esac ;; beos*) library_names_spec='$libname$shared_ext' dynamic_linker="$host_os ld.so" shlibpath_var=LIBRARY_PATH ;; bsdi[[45]]*) version_type=linux # correct to gnu/linux during the next big refactor need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' shlibpath_var=LD_LIBRARY_PATH sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" # the default ld.so.conf also contains /usr/contrib/lib and # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow # libtool to hard-code these into programs ;; cygwin* | mingw* | windows* | pw32* | cegcc*) version_type=windows shrext_cmds=.dll need_version=no need_lib_prefix=no case $GCC,$cc_basename in yes,*) # gcc library_names_spec='$libname.dll.a' # DLL is installed to $(libdir)/../bin by postinstall_cmds # If user builds GCC with multilib enabled, # it should just install on $(libdir) # not on $(libdir)/../bin or 32 bits dlls would override 64 bit ones. if test xyes = x"$multilib"; then postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ $install_prog $dir/$dlname $destdir/$dlname~ chmod a+x $destdir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib $destdir/$dlname'\'' || exit \$?; fi' else postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname~ chmod a+x \$dldir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; fi' fi postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' shlibpath_overrides_runpath=yes case $host_os in cygwin*) # Cygwin DLLs use 'cyg' prefix rather than 'lib' soname_spec='`echo $libname | $SED -e 's/^lib/cyg/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' m4_if([$1], [],[ sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"]) ;; mingw* | windows* | cegcc*) # MinGW DLLs use traditional 'lib' prefix soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' ;; pw32*) # pw32 DLLs use 'pw' prefix rather than 'lib' library_names_spec='`echo $libname | $SED -e 's/^lib/pw/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' ;; esac dynamic_linker='Win32 ld.exe' ;; *,cl* | *,icl*) # Native MSVC or ICC libname_spec='$name' soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' library_names_spec='$libname.dll.lib' case $build_os in mingw* | windows*) sys_lib_search_path_spec= lt_save_ifs=$IFS IFS=';' for lt_path in $LIB do IFS=$lt_save_ifs # Let DOS variable expansion print the short 8.3 style file name. lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" done IFS=$lt_save_ifs # Convert to MSYS style. sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's|\\\\|/|g' -e 's| \\([[a-zA-Z]]\\):| /\\1|g' -e 's|^ ||'` ;; cygwin*) # Convert to unix form, then to dos form, then back to unix form # but this time dos style (no spaces!) so that the unix form looks # like /cygdrive/c/PROGRA~1:/cygdr... sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` ;; *) sys_lib_search_path_spec=$LIB if $ECHO "$sys_lib_search_path_spec" | [$GREP ';[c-zC-Z]:/' >/dev/null]; then # It is most probably a Windows format PATH. sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` else sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` fi # FIXME: find the short name or the path components, as spaces are # common. (e.g. "Program Files" -> "PROGRA~1") ;; esac # DLL is installed to $(libdir)/../bin by postinstall_cmds postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' shlibpath_overrides_runpath=yes dynamic_linker='Win32 link.exe' ;; *) # Assume MSVC and ICC wrapper library_names_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext $libname.lib' dynamic_linker='Win32 ld.exe' ;; esac # FIXME: first we should search . and the directory the executable is in shlibpath_var=PATH ;; darwin* | rhapsody*) dynamic_linker="$host_os dyld" version_type=darwin need_lib_prefix=no need_version=no library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' soname_spec='$libname$release$major$shared_ext' shlibpath_overrides_runpath=yes shlibpath_var=DYLD_LIBRARY_PATH shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' m4_if([$1], [],[ sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"]) sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' ;; dgux*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH ;; freebsd* | dragonfly* | midnightbsd*) # DragonFly does not have aout. When/if they implement a new # versioning mechanism, adjust this. if test -x /usr/bin/objformat; then objformat=`/usr/bin/objformat` else case $host_os in freebsd[[23]].*) objformat=aout ;; *) objformat=elf ;; esac fi version_type=freebsd-$objformat case $version_type in freebsd-elf*) library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' need_version=no need_lib_prefix=no ;; freebsd-*) library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' need_version=yes ;; esac case $host_cpu in powerpc64) # On FreeBSD bi-arch platforms, a different variable is used for 32-bit # binaries. See . AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[int test_pointer_size[sizeof (void *) - 5]; ]])], [shlibpath_var=LD_LIBRARY_PATH], [shlibpath_var=LD_32_LIBRARY_PATH]) ;; *) shlibpath_var=LD_LIBRARY_PATH ;; esac case $host_os in freebsd2.*) shlibpath_overrides_runpath=yes ;; freebsd3.[[01]]* | freebsdelf3.[[01]]*) shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; freebsd3.[[2-9]]* | freebsdelf3.[[2-9]]* | \ freebsd4.[[0-5]] | freebsdelf4.[[0-5]] | freebsd4.1.1 | freebsdelf4.1.1) shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; *) # from 4.6 on, and DragonFly shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; esac ;; haiku*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no dynamic_linker="$host_os runtime_loader" library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LIBRARY_PATH shlibpath_overrides_runpath=no sys_lib_search_path_spec='/boot/system/non-packaged/develop/lib /boot/system/develop/lib' sys_lib_dlsearch_path_spec='/boot/home/config/non-packaged/lib /boot/home/config/lib /boot/system/non-packaged/lib /boot/system/lib' hardcode_into_libs=no ;; hpux9* | hpux10* | hpux11*) # Give a soname corresponding to the major version so that dld.sl refuses to # link against other versions. version_type=sunos need_lib_prefix=no need_version=no case $host_cpu in ia64*) shrext_cmds='.so' hardcode_into_libs=yes dynamic_linker="$host_os dld.so" shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' if test 32 = "$HPUX_IA64_MODE"; then sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" sys_lib_dlsearch_path_spec=/usr/lib/hpux32 else sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" sys_lib_dlsearch_path_spec=/usr/lib/hpux64 fi ;; hppa*64*) shrext_cmds='.sl' hardcode_into_libs=yes dynamic_linker="$host_os dld.sl" shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec ;; *) shrext_cmds='.sl' dynamic_linker="$host_os dld.sl" shlibpath_var=SHLIB_PATH shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' ;; esac # HP-UX runs *really* slowly unless shared libraries are mode 555, ... postinstall_cmds='chmod 555 $lib' # or fails outright, so override atomically: install_override_mode=555 ;; interix[[3-9]]*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; irix5* | irix6* | nonstopux*) case $host_os in nonstopux*) version_type=nonstopux ;; *) if test yes = "$lt_cv_prog_gnu_ld"; then version_type=linux # correct to gnu/linux during the next big refactor else version_type=irix fi ;; esac need_lib_prefix=no need_version=no soname_spec='$libname$release$shared_ext$major' library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' case $host_os in irix5* | nonstopux*) libsuff= shlibsuff= ;; *) case $LD in # libtool.m4 will add one of these switches to LD *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= libmagic=32-bit;; *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 libmagic=N32;; *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 libmagic=64-bit;; *) libsuff= shlibsuff= libmagic=never-match;; esac ;; esac shlibpath_var=LD_LIBRARY${shlibsuff}_PATH shlibpath_overrides_runpath=no sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" hardcode_into_libs=yes ;; # No shared lib support for Linux oldld, aout, or coff. linux*oldld* | linux*aout* | linux*coff*) dynamic_linker=no ;; linux*android*) version_type=none # Android doesn't support versioned libraries. need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext $libname$shared_ext' soname_spec='$libname$release$shared_ext' finish_cmds= shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes # This implies no fast_install, which is unacceptable. # Some rework will be needed to allow for fast_install # before this can be enabled. hardcode_into_libs=yes dynamic_linker='Android linker' # -rpath works at least for libraries that are not overridden by # libraries installed in system locations. _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' ;; # This must be glibc/ELF. linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no # Some binutils ld are patched to set DT_RUNPATH AC_CACHE_VAL([lt_cv_shlibpath_overrides_runpath], [lt_cv_shlibpath_overrides_runpath=no save_LDFLAGS=$LDFLAGS save_libdir=$libdir eval "libdir=/foo; wl=\"$_LT_TAGVAR(lt_prog_compiler_wl, $1)\"; \ LDFLAGS=\"\$LDFLAGS $_LT_TAGVAR(hardcode_libdir_flag_spec, $1)\"" AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], [AS_IF([ ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null], [lt_cv_shlibpath_overrides_runpath=yes])]) LDFLAGS=$save_LDFLAGS libdir=$save_libdir ]) shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath # This implies no fast_install, which is unacceptable. # Some rework will be needed to allow for fast_install # before this can be enabled. hardcode_into_libs=yes # Ideally, we could use ldconfig to report *all* directories which are # searched for libraries, however this is still not possible. Aside from not # being certain /sbin/ldconfig is available, command # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, # even though it is searched at run-time. Try to do the best guess by # appending ld.so.conf contents (and includes) to the search path. if test -f /etc/ld.so.conf; then lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \[$]2)); skip = 1; } { if (!skip) print \[$]0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" fi # We used to test for /lib/ld.so.1 and disable shared libraries on # powerpc, because MkLinux only supported shared libraries with the # GNU dynamic linker. Since this was broken with cross compilers, # most powerpc-linux boxes support dynamic linking these days and # people can always --disable-shared, the test was removed, and we # assume the GNU/Linux dynamic linker is in use. dynamic_linker='GNU/Linux ld.so' ;; netbsdelf*-gnu) version_type=linux need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='NetBSD ld.elf_so' ;; netbsdelf*-gnu) version_type=linux need_lib_prefix=no need_version=no library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' soname_spec='${libname}${release}${shared_ext}$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='NetBSD ld.elf_so' ;; netbsd*) version_type=sunos need_lib_prefix=no need_version=no if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' dynamic_linker='NetBSD (a.out) ld.so' else library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='NetBSD ld.elf_so' fi shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; *-mlibc) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='mlibc ld.so' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; newsos6) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes ;; *nto* | *qnx*) version_type=qnx need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='ldqnx.so' ;; openbsd*) version_type=sunos sys_lib_dlsearch_path_spec=/usr/lib need_lib_prefix=no if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then need_version=no else need_version=yes fi library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes ;; os2*) libname_spec='$name' version_type=windows shrext_cmds=.dll need_version=no need_lib_prefix=no # OS/2 can only load a DLL with a base name of 8 characters or less. soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; v=$($ECHO $release$versuffix | tr -d .-); n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); $ECHO $n$v`$shared_ext' library_names_spec='${libname}_dll.$libext' dynamic_linker='OS/2 ld.exe' shlibpath_var=BEGINLIBPATH sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname~ chmod a+x \$dldir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; fi' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' ;; osf3* | osf4* | osf5*) version_type=osf need_lib_prefix=no need_version=no soname_spec='$libname$release$shared_ext$major' library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec ;; rdos*) dynamic_linker=no ;; serenity*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no dynamic_linker='SerenityOS LibELF' ;; solaris*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes # ldd complains unless libraries are executable postinstall_cmds='chmod +x $lib' ;; sunos4*) version_type=sunos library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes if test yes = "$with_gnu_ld"; then need_lib_prefix=no fi need_version=yes ;; sysv4 | sysv4.3*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH case $host_vendor in sni) shlibpath_overrides_runpath=no need_lib_prefix=no runpath_var=LD_RUN_PATH ;; siemens) need_lib_prefix=no ;; motorola) need_lib_prefix=no need_version=no shlibpath_overrides_runpath=no sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' ;; esac ;; sysv4*MP*) if test -d /usr/nec; then version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' soname_spec='$libname$shared_ext.$major' shlibpath_var=LD_LIBRARY_PATH fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) version_type=sco need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes if test yes = "$with_gnu_ld"; then sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' else sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' case $host_os in sco3.2v5*) sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" ;; esac fi sys_lib_dlsearch_path_spec='/usr/lib' ;; tpf*) # TPF is a cross-target only. Preferred cross-host = GNU/Linux. version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; uts4*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH ;; emscripten*) version_type=none need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext' soname_spec='$libname$release$shared_ext' finish_cmds= dynamic_linker="Emscripten linker" _LT_COMPILER_PIC($1)='-fPIC' _LT_TAGVAR(archive_cmds, $1)='$CC -sSIDE_MODULE=2 -shared $libobjs $deplibs $compiler_flags -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$SED "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -sSIDE_MODULE=2 -shared $libobjs $deplibs $compiler_flags -o $lib -s EXPORTED_FUNCTIONS=@$output_objdir/$soname.expsym' _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(no_undefined_flag, $1)= ;; *) dynamic_linker=no ;; esac AC_MSG_RESULT([$dynamic_linker]) test no = "$dynamic_linker" && can_build_shared=no variables_saved_for_relink="PATH $shlibpath_var $runpath_var" if test yes = "$GCC"; then variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" fi if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec fi if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec fi # remember unaugmented sys_lib_dlsearch_path content for libtool script decls... configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec # ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" # to be used as default LT_SYS_LIBRARY_PATH value in generated libtool configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH _LT_DECL([], [variables_saved_for_relink], [1], [Variables whose values should be saved in libtool wrapper scripts and restored at link time]) _LT_DECL([], [need_lib_prefix], [0], [Do we need the "lib" prefix for modules?]) _LT_DECL([], [need_version], [0], [Do we need a version for libraries?]) _LT_DECL([], [version_type], [0], [Library versioning type]) _LT_DECL([], [runpath_var], [0], [Shared library runtime path variable]) _LT_DECL([], [shlibpath_var], [0],[Shared library path variable]) _LT_DECL([], [shlibpath_overrides_runpath], [0], [Is shlibpath searched before the hard-coded library search path?]) _LT_DECL([], [libname_spec], [1], [Format of library name prefix]) _LT_DECL([], [library_names_spec], [1], [[List of archive names. First name is the real one, the rest are links. The last name is the one that the linker finds with -lNAME]]) _LT_DECL([], [soname_spec], [1], [[The coded name of the library, if different from the real name]]) _LT_DECL([], [install_override_mode], [1], [Permission mode override for installation of shared libraries]) _LT_DECL([], [postinstall_cmds], [2], [Command to use after installation of a shared archive]) _LT_DECL([], [postuninstall_cmds], [2], [Command to use after uninstallation of a shared archive]) _LT_DECL([], [finish_cmds], [2], [Commands used to finish a libtool library installation in a directory]) _LT_DECL([], [finish_eval], [1], [[As "finish_cmds", except a single script fragment to be evaled but not shown]]) _LT_DECL([], [hardcode_into_libs], [0], [Whether we should hardcode library paths into libraries]) _LT_DECL([], [sys_lib_search_path_spec], [2], [Compile-time system search path for libraries]) _LT_DECL([sys_lib_dlsearch_path_spec], [configure_time_dlsearch_path], [2], [Detected run-time system search path for libraries]) _LT_DECL([], [configure_time_lt_sys_library_path], [2], [Explicit LT_SYS_LIBRARY_PATH set during ./configure time]) ])# _LT_SYS_DYNAMIC_LINKER # _LT_PATH_TOOL_PREFIX(TOOL) # -------------------------- # find a file program that can recognize shared library AC_DEFUN([_LT_PATH_TOOL_PREFIX], [m4_require([_LT_DECL_EGREP])dnl AC_MSG_CHECKING([for $1]) AC_CACHE_VAL(lt_cv_path_MAGIC_CMD, [case $MAGIC_CMD in [[\\/*] | ?:[\\/]*]) lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. ;; *) lt_save_MAGIC_CMD=$MAGIC_CMD lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR dnl $ac_dummy forces splitting on constant user-supplied paths. dnl POSIX.2 word splitting is done only on the output of word expansions, dnl not every word. This closes a longstanding sh security hole. ac_dummy="m4_if([$2], , $PATH, [$2])" for ac_dir in $ac_dummy; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/$1"; then lt_cv_path_MAGIC_CMD=$ac_dir/"$1" if test -n "$file_magic_test_file"; then case $deplibs_check_method in "file_magic "*) file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` MAGIC_CMD=$lt_cv_path_MAGIC_CMD if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | $EGREP "$file_magic_regex" > /dev/null; then : else cat <<_LT_EOF 1>&2 *** Warning: the command libtool uses to detect shared libraries, *** $file_magic_cmd, produces output that libtool cannot recognize. *** The result is that libtool may fail to recognize shared libraries *** as such. This will affect the creation of libtool libraries that *** depend on shared libraries, but programs linked with such libtool *** libraries will work regardless of this problem. Nevertheless, you *** may want to report the problem to your system manager and/or to *** bug-libtool@gnu.org _LT_EOF fi ;; esac fi break fi done IFS=$lt_save_ifs MAGIC_CMD=$lt_save_MAGIC_CMD ;; esac]) MAGIC_CMD=$lt_cv_path_MAGIC_CMD if test -n "$MAGIC_CMD"; then AC_MSG_RESULT($MAGIC_CMD) else AC_MSG_RESULT(no) fi _LT_DECL([], [MAGIC_CMD], [0], [Used to examine libraries when file_magic_cmd begins with "file"])dnl ])# _LT_PATH_TOOL_PREFIX # Old name: AU_ALIAS([AC_PATH_TOOL_PREFIX], [_LT_PATH_TOOL_PREFIX]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_PATH_TOOL_PREFIX], []) # _LT_PATH_MAGIC # -------------- # find a file program that can recognize a shared library m4_defun([_LT_PATH_MAGIC], [_LT_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH) if test -z "$lt_cv_path_MAGIC_CMD"; then if test -n "$ac_tool_prefix"; then _LT_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH) else MAGIC_CMD=: fi fi ])# _LT_PATH_MAGIC # LT_PATH_LD # ---------- # find the pathname to the GNU or non-GNU linker AC_DEFUN([LT_PATH_LD], [AC_REQUIRE([AC_PROG_CC])dnl AC_REQUIRE([AC_CANONICAL_HOST])dnl AC_REQUIRE([AC_CANONICAL_BUILD])dnl m4_require([_LT_DECL_SED])dnl m4_require([_LT_DECL_EGREP])dnl m4_require([_LT_PROG_ECHO_BACKSLASH])dnl AC_ARG_WITH([gnu-ld], [AS_HELP_STRING([--with-gnu-ld], [assume the C compiler uses GNU ld @<:@default=no@:>@])], [test no = "$withval" || with_gnu_ld=yes], [with_gnu_ld=no])dnl ac_prog=ld if test yes = "$GCC"; then # Check if gcc -print-prog-name=ld gives a path. AC_MSG_CHECKING([for ld used by $CC]) case $host in *-*-mingw* | *-*-windows*) # gcc leaves a trailing carriage return, which upsets mingw ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; *) ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; esac case $ac_prog in # Accept absolute paths. [[\\/]]* | ?:[[\\/]]*) re_direlt='/[[^/]][[^/]]*/\.\./' # Canonicalize the pathname of ld ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` done test -z "$LD" && LD=$ac_prog ;; "") # If it fails, then pretend we aren't using GCC. ac_prog=ld ;; *) # If it is relative, then search for the first ld in PATH. with_gnu_ld=unknown ;; esac elif test yes = "$with_gnu_ld"; then AC_MSG_CHECKING([for GNU ld]) else AC_MSG_CHECKING([for non-GNU ld]) fi AC_CACHE_VAL(lt_cv_path_LD, [if test -z "$LD"; then lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR for ac_dir in $PATH; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then lt_cv_path_LD=$ac_dir/$ac_prog # Check to see if the program is GNU ld. I'd rather use --version, # but apparently some variants of GNU ld only accept -v. # Break only if it was the GNU/non-GNU ld that we prefer. case `"$lt_cv_path_LD" -v 2>&1 &1 conftest.i cat conftest.i conftest.i >conftest2.i : ${lt_DD:=$DD} AC_PATH_PROGS_FEATURE_CHECK([lt_DD], [dd], [if "$ac_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then cmp -s conftest.i conftest.out \ && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=: fi]) rm -f conftest.i conftest2.i conftest.out]) ])# _LT_PATH_DD # _LT_CMD_TRUNCATE # ---------------- # find command to truncate a binary pipe m4_defun([_LT_CMD_TRUNCATE], [m4_require([_LT_PATH_DD]) AC_CACHE_CHECK([how to truncate binary pipes], [lt_cv_truncate_bin], [printf 0123456789abcdef0123456789abcdef >conftest.i cat conftest.i conftest.i >conftest2.i lt_cv_truncate_bin= if "$ac_cv_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then cmp -s conftest.i conftest.out \ && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1" fi rm -f conftest.i conftest2.i conftest.out test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q"]) _LT_DECL([lt_truncate_bin], [lt_cv_truncate_bin], [1], [Command to truncate a binary pipe]) ])# _LT_CMD_TRUNCATE # _LT_CHECK_MAGIC_METHOD # ---------------------- # how to check for library dependencies # -- PORTME fill in with the dynamic library characteristics m4_defun([_LT_CHECK_MAGIC_METHOD], [m4_require([_LT_DECL_EGREP]) m4_require([_LT_DECL_OBJDUMP]) AC_CACHE_CHECK([how to recognize dependent libraries], lt_cv_deplibs_check_method, [lt_cv_file_magic_cmd='$MAGIC_CMD' lt_cv_file_magic_test_file= lt_cv_deplibs_check_method='unknown' # Need to set the preceding variable on all platforms that support # interlibrary dependencies. # 'none' -- dependencies not supported. # 'unknown' -- same as none, but documents that we really don't know. # 'pass_all' -- all dependencies passed with no checks. # 'file_magic [[regex]]' -- check by looking for files in library path # that responds to the $file_magic_cmd with a given extended regex. # If you have 'file' or equivalent on your system and you're not sure # whether 'pass_all' will *always* work, you probably want this one. case $host_os in aix[[4-9]]*) lt_cv_deplibs_check_method=pass_all ;; beos*) lt_cv_deplibs_check_method=pass_all ;; bsdi[[45]]*) lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib)' lt_cv_file_magic_cmd='$FILECMD -L' lt_cv_file_magic_test_file=/shlib/libc.so ;; cygwin*) # func_win32_libid is a shell function defined in ltmain.sh lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' lt_cv_file_magic_cmd='func_win32_libid' ;; mingw* | windows* | pw32*) # Base MSYS/MinGW do not provide the 'file' command needed by # func_win32_libid shell function, so use a weaker test based on 'objdump', # unless we find 'file', for example because we are cross-compiling. if ( file / ) >/dev/null 2>&1; then lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' lt_cv_file_magic_cmd='func_win32_libid' else # Keep this pattern in sync with the one in func_win32_libid. lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64|pe-aarch64)' lt_cv_file_magic_cmd='$OBJDUMP -f' fi ;; cegcc*) # use the weaker test based on 'objdump'. See mingw*. lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' lt_cv_file_magic_cmd='$OBJDUMP -f' ;; darwin* | rhapsody*) lt_cv_deplibs_check_method=pass_all ;; freebsd* | dragonfly* | midnightbsd*) if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then case $host_cpu in i*86 ) # Not sure whether the presence of OpenBSD here was a mistake. # Let's accept both of them until this is cleared up. lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[[3-9]]86 (compact )?demand paged shared library' lt_cv_file_magic_cmd=$FILECMD lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` ;; esac else lt_cv_deplibs_check_method=pass_all fi ;; haiku*) lt_cv_deplibs_check_method=pass_all ;; hpux10.20* | hpux11*) lt_cv_file_magic_cmd=$FILECMD case $host_cpu in ia64*) lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64' lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so ;; hppa*64*) [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]'] lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl ;; *) lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]]\.[[0-9]]) shared library' lt_cv_file_magic_test_file=/usr/lib/libc.sl ;; esac ;; interix[[3-9]]*) # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|\.a)$' ;; irix5* | irix6* | nonstopux*) case $LD in *-32|*"-32 ") libmagic=32-bit;; *-n32|*"-n32 ") libmagic=N32;; *-64|*"-64 ") libmagic=64-bit;; *) libmagic=never-match;; esac lt_cv_deplibs_check_method=pass_all ;; # This must be glibc/ELF. linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) lt_cv_deplibs_check_method=pass_all ;; *-mlibc) lt_cv_deplibs_check_method=pass_all ;; netbsd* | netbsdelf*-gnu) if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' else lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$' fi ;; newos6*) lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)' lt_cv_file_magic_cmd=$FILECMD lt_cv_file_magic_test_file=/usr/lib/libnls.so ;; *nto* | *qnx*) lt_cv_deplibs_check_method=pass_all ;; openbsd*) if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|\.so|_pic\.a)$' else lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' fi ;; osf3* | osf4* | osf5*) lt_cv_deplibs_check_method=pass_all ;; rdos*) lt_cv_deplibs_check_method=pass_all ;; serenity*) lt_cv_deplibs_check_method=pass_all ;; solaris*) lt_cv_deplibs_check_method=pass_all ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) lt_cv_deplibs_check_method=pass_all ;; sysv4 | sysv4.3*) case $host_vendor in motorola) lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]' lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` ;; ncr) lt_cv_deplibs_check_method=pass_all ;; sequent) lt_cv_file_magic_cmd='/bin/file' lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )' ;; sni) lt_cv_file_magic_cmd='/bin/file' lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib" lt_cv_file_magic_test_file=/lib/libc.so ;; siemens) lt_cv_deplibs_check_method=pass_all ;; pc) lt_cv_deplibs_check_method=pass_all ;; esac ;; tpf*) lt_cv_deplibs_check_method=pass_all ;; os2*) lt_cv_deplibs_check_method=pass_all ;; esac ]) file_magic_glob= want_nocaseglob=no if test "$build" = "$host"; then case $host_os in mingw* | windows* | pw32*) if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then want_nocaseglob=yes else file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[[\1]]\/[[\1]]\/g;/g"` fi ;; esac fi file_magic_cmd=$lt_cv_file_magic_cmd deplibs_check_method=$lt_cv_deplibs_check_method test -z "$deplibs_check_method" && deplibs_check_method=unknown _LT_DECL([], [deplibs_check_method], [1], [Method to check whether dependent libraries are shared objects]) _LT_DECL([], [file_magic_cmd], [1], [Command to use when deplibs_check_method = "file_magic"]) _LT_DECL([], [file_magic_glob], [1], [How to find potential files when deplibs_check_method = "file_magic"]) _LT_DECL([], [want_nocaseglob], [1], [Find potential files using nocaseglob when deplibs_check_method = "file_magic"]) ])# _LT_CHECK_MAGIC_METHOD # LT_PATH_NM # ---------- # find the pathname to a BSD- or MS-compatible name lister AC_DEFUN([LT_PATH_NM], [AC_REQUIRE([AC_PROG_CC])dnl AC_CACHE_CHECK([for BSD- or MS-compatible name lister (nm)], lt_cv_path_NM, [if test -n "$NM"; then # Let the user override the test. lt_cv_path_NM=$NM else lt_nm_to_check=${ac_tool_prefix}nm if test -n "$ac_tool_prefix" && test "$build" = "$host"; then lt_nm_to_check="$lt_nm_to_check nm" fi for lt_tmp_nm in $lt_nm_to_check; do lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. tmp_nm=$ac_dir/$lt_tmp_nm if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then # Check to see if the nm accepts a BSD-compat flag. # Adding the 'sed 1q' prevents false positives on HP-UX, which says: # nm: unknown option "B" ignored # Tru64's nm complains that /dev/null is an invalid object file # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty case $build_os in mingw* | windows*) lt_bad_file=conftest.nm/nofile ;; *) lt_bad_file=/dev/null ;; esac case `"$tmp_nm" -B $lt_bad_file 2>&1 | $SED '1q'` in *$lt_bad_file* | *'Invalid file or object type'*) lt_cv_path_NM="$tmp_nm -B" break 2 ;; *) case `"$tmp_nm" -p /dev/null 2>&1 | $SED '1q'` in */dev/null*) lt_cv_path_NM="$tmp_nm -p" break 2 ;; *) lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but continue # so that we can try to find one that supports BSD flags ;; esac ;; esac fi done IFS=$lt_save_ifs done : ${lt_cv_path_NM=no} fi]) if test no != "$lt_cv_path_NM"; then NM=$lt_cv_path_NM else # Didn't find any BSD compatible name lister, look for dumpbin. if test -n "$DUMPBIN"; then : # Let the user override the test. else AC_CHECK_TOOLS(DUMPBIN, [dumpbin "link -dump"], :) case `$DUMPBIN -symbols -headers /dev/null 2>&1 | $SED '1q'` in *COFF*) DUMPBIN="$DUMPBIN -symbols -headers" ;; *) DUMPBIN=: ;; esac fi AC_SUBST([DUMPBIN]) if test : != "$DUMPBIN"; then NM=$DUMPBIN fi fi test -z "$NM" && NM=nm AC_SUBST([NM]) _LT_DECL([], [NM], [1], [A BSD- or MS-compatible name lister])dnl AC_CACHE_CHECK([the name lister ($NM) interface], [lt_cv_nm_interface], [lt_cv_nm_interface="BSD nm" echo "int some_variable = 0;" > conftest.$ac_ext (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&AS_MESSAGE_LOG_FD) (eval "$ac_compile" 2>conftest.err) cat conftest.err >&AS_MESSAGE_LOG_FD (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&AS_MESSAGE_LOG_FD) (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) cat conftest.err >&AS_MESSAGE_LOG_FD (eval echo "\"\$as_me:$LINENO: output\"" >&AS_MESSAGE_LOG_FD) cat conftest.out >&AS_MESSAGE_LOG_FD if $GREP 'External.*some_variable' conftest.out > /dev/null; then lt_cv_nm_interface="MS dumpbin" fi rm -f conftest*]) ])# LT_PATH_NM # Old names: AU_ALIAS([AM_PROG_NM], [LT_PATH_NM]) AU_ALIAS([AC_PROG_NM], [LT_PATH_NM]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AM_PROG_NM], []) dnl AC_DEFUN([AC_PROG_NM], []) # _LT_CHECK_SHAREDLIB_FROM_LINKLIB # -------------------------------- # how to determine the name of the shared library # associated with a specific link library. # -- PORTME fill in with the dynamic library characteristics m4_defun([_LT_CHECK_SHAREDLIB_FROM_LINKLIB], [m4_require([_LT_DECL_EGREP]) m4_require([_LT_DECL_OBJDUMP]) m4_require([_LT_DECL_DLLTOOL]) AC_CACHE_CHECK([how to associate runtime and link libraries], lt_cv_sharedlib_from_linklib_cmd, [lt_cv_sharedlib_from_linklib_cmd='unknown' case $host_os in cygwin* | mingw* | windows* | pw32* | cegcc*) # two different shell functions defined in ltmain.sh; # decide which one to use based on capabilities of $DLLTOOL case `$DLLTOOL --help 2>&1` in *--identify-strict*) lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib ;; *) lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback ;; esac ;; *) # fallback: assume linklib IS sharedlib lt_cv_sharedlib_from_linklib_cmd=$ECHO ;; esac ]) sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO _LT_DECL([], [sharedlib_from_linklib_cmd], [1], [Command to associate shared and link libraries]) ])# _LT_CHECK_SHAREDLIB_FROM_LINKLIB # _LT_PATH_MANIFEST_TOOL # ---------------------- # locate the manifest tool m4_defun([_LT_PATH_MANIFEST_TOOL], [AC_CHECK_TOOL(MANIFEST_TOOL, mt, :) test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt AC_CACHE_CHECK([if $MANIFEST_TOOL is a manifest tool], [lt_cv_path_manifest_tool], [lt_cv_path_manifest_tool=no echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&AS_MESSAGE_LOG_FD $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out cat conftest.err >&AS_MESSAGE_LOG_FD if $GREP 'Manifest Tool' conftest.out > /dev/null; then lt_cv_path_manifest_tool=yes fi rm -f conftest*]) if test yes != "$lt_cv_path_manifest_tool"; then MANIFEST_TOOL=: fi _LT_DECL([], [MANIFEST_TOOL], [1], [Manifest tool])dnl ])# _LT_PATH_MANIFEST_TOOL # _LT_DLL_DEF_P([FILE]) # --------------------- # True iff FILE is a Windows DLL '.def' file. # Keep in sync with func_dll_def_p in the libtool script AC_DEFUN([_LT_DLL_DEF_P], [dnl test DEF = "`$SED -n dnl -e '\''s/^[[ ]]*//'\'' dnl Strip leading whitespace -e '\''/^\(;.*\)*$/d'\'' dnl Delete empty lines and comments -e '\''s/^\(EXPORTS\|LIBRARY\)\([[ ]].*\)*$/DEF/p'\'' dnl -e q dnl Only consider the first "real" line $1`" dnl ])# _LT_DLL_DEF_P # LT_LIB_M # -------- # check for math library AC_DEFUN([LT_LIB_M], [AC_REQUIRE([AC_CANONICAL_HOST])dnl LIBM= case $host in *-*-beos* | *-*-cegcc* | *-*-cygwin* | *-*-haiku* | *-*-mingw* | *-*-pw32* | *-*-darwin*) # These system don't have libm, or don't need it ;; *-ncr-sysv4.3*) AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM=-lmw) AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm") ;; *) AC_CHECK_LIB(m, cos, LIBM=-lm) ;; esac AC_SUBST([LIBM]) ])# LT_LIB_M # Old name: AU_ALIAS([AC_CHECK_LIBM], [LT_LIB_M]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_CHECK_LIBM], []) # _LT_COMPILER_NO_RTTI([TAGNAME]) # ------------------------------- m4_defun([_LT_COMPILER_NO_RTTI], [m4_require([_LT_TAG_COMPILER])dnl _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= if test yes = "$GCC"; then case $cc_basename in nvcc*) _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -Xcompiler -fno-builtin' ;; *) _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' ;; esac _LT_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions], lt_cv_prog_compiler_rtti_exceptions, [-fno-rtti -fno-exceptions], [], [_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"]) fi _LT_TAGDECL([no_builtin_flag], [lt_prog_compiler_no_builtin_flag], [1], [Compiler flag to turn off builtin functions]) ])# _LT_COMPILER_NO_RTTI # _LT_CMD_GLOBAL_SYMBOLS # ---------------------- m4_defun([_LT_CMD_GLOBAL_SYMBOLS], [AC_REQUIRE([AC_CANONICAL_HOST])dnl AC_REQUIRE([AC_PROG_CC])dnl AC_REQUIRE([AC_PROG_AWK])dnl AC_REQUIRE([LT_PATH_NM])dnl AC_REQUIRE([LT_PATH_LD])dnl m4_require([_LT_DECL_SED])dnl m4_require([_LT_DECL_EGREP])dnl m4_require([_LT_TAG_COMPILER])dnl # Check for command to grab the raw symbol name followed by C symbol from nm. AC_MSG_CHECKING([command to parse $NM output from $compiler object]) AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe], [ # These are sane defaults that work on at least a few old systems. # [They come from Ultrix. What could be older than Ultrix?!! ;)] # Character class describing NM global symbol codes. symcode='[[BCDEGRST]]' # Regexp to match symbols that can be accessed directly from C. sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)' # Define system-specific variables. case $host_os in aix*) symcode='[[BCDT]]' ;; cygwin* | mingw* | windows* | pw32* | cegcc*) symcode='[[ABCDGISTW]]' ;; hpux*) if test ia64 = "$host_cpu"; then symcode='[[ABCDEGRST]]' fi ;; irix* | nonstopux*) symcode='[[BCDEGRST]]' ;; osf*) symcode='[[BCDEGQRST]]' ;; solaris*) symcode='[[BCDRT]]' ;; sco3.2v5*) symcode='[[DT]]' ;; sysv4.2uw2*) symcode='[[DT]]' ;; sysv5* | sco5v6* | unixware* | OpenUNIX*) symcode='[[ABDT]]' ;; sysv4) symcode='[[DFNSTU]]' ;; esac # If we're using GNU nm, then use its standard symbol codes. case `$NM -V 2>&1` in *GNU* | *'with BFD'*) symcode='[[ABCDGIRSTW]]' ;; esac if test "$lt_cv_nm_interface" = "MS dumpbin"; then # Gets list of data symbols to import. lt_cv_sys_global_symbol_to_import="$SED -n -e 's/^I .* \(.*\)$/\1/p'" # Adjust the below global symbol transforms to fixup imported variables. lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'" lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'" lt_c_name_lib_hook="\ -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\ -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'" else # Disable hooks by default. lt_cv_sys_global_symbol_to_import= lt_cdecl_hook= lt_c_name_hook= lt_c_name_lib_hook= fi # Transform an extracted symbol line into a proper C declaration. # Some systems (esp. on ia64) link data and code symbols differently, # so use this general approach. lt_cv_sys_global_symbol_to_cdecl="$SED -n"\ $lt_cdecl_hook\ " -e 's/^T .* \(.*\)$/extern int \1();/p'"\ " -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'" # Transform an extracted symbol line into symbol name and symbol address lt_cv_sys_global_symbol_to_c_name_address="$SED -n"\ $lt_c_name_hook\ " -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ " -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'" # Transform an extracted symbol line into symbol name with lib prefix and # symbol address. lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="$SED -n"\ $lt_c_name_lib_hook\ " -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ " -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\ " -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'" # Handle CRLF in mingw tool chain opt_cr= case $build_os in mingw* | windows*) opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp ;; esac # Try without a prefix underscore, then with it. for ac_symprfx in "" "_"; do # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. symxfrm="\\1 $ac_symprfx\\2 \\2" # Write the raw and C identifiers. if test "$lt_cv_nm_interface" = "MS dumpbin"; then # Fake it for dumpbin and say T for any non-static function, # D for any global variable and I for any imported variable. # Also find C++ and __fastcall symbols from MSVC++ or ICC, # which start with @ or ?. lt_cv_sys_global_symbol_pipe="$AWK ['"\ " {last_section=section; section=\$ 3};"\ " /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ " /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ " /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\ " /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\ " /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\ " \$ 0!~/External *\|/{next};"\ " / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ " {if(hide[section]) next};"\ " {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\ " {split(\$ 0,a,/\||\r/); split(a[2],s)};"\ " s[1]~/^[@?]/{print f,s[1],s[1]; next};"\ " s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\ " ' prfx=^$ac_symprfx]" else lt_cv_sys_global_symbol_pipe="$SED -n -e 's/^.*[[ ]]\($symcode$symcode*\)[[ ]][[ ]]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" fi lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | $SED '/ __gnu_lto/d'" # Check to see that the pipe works correctly. pipe_works=no rm -f conftest* cat > conftest.$ac_ext <<_LT_EOF #ifdef __cplusplus extern "C" { #endif char nm_test_var; void nm_test_func(void); void nm_test_func(void){} #ifdef __cplusplus } #endif int main(void){nm_test_var='a';nm_test_func();return(0);} _LT_EOF if AC_TRY_EVAL(ac_compile); then # Now try to grab the symbols. nlist=conftest.nm $ECHO "$as_me:$LINENO: $NM conftest.$ac_objext | $lt_cv_sys_global_symbol_pipe > $nlist" >&AS_MESSAGE_LOG_FD if eval "$NM" conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist 2>&AS_MESSAGE_LOG_FD && test -s "$nlist"; then # Try sorting and uniquifying the output. if sort "$nlist" | uniq > "$nlist"T; then mv -f "$nlist"T "$nlist" else rm -f "$nlist"T fi # Make sure that we snagged all the symbols we need. if $GREP ' nm_test_var$' "$nlist" >/dev/null; then if $GREP ' nm_test_func$' "$nlist" >/dev/null; then cat <<_LT_EOF > conftest.$ac_ext /* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ #if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE /* DATA imports from DLLs on WIN32 can't be const, because runtime relocations are performed -- see ld's documentation on pseudo-relocs. */ # define LT@&t@_DLSYM_CONST #elif defined __osf__ /* This system does not cope well with relocations in const data. */ # define LT@&t@_DLSYM_CONST #else # define LT@&t@_DLSYM_CONST const #endif #ifdef __cplusplus extern "C" { #endif _LT_EOF # Now generate the symbol file. eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' cat <<_LT_EOF >> conftest.$ac_ext /* The mapping between symbol names and symbols. */ LT@&t@_DLSYM_CONST struct { const char *name; void *address; } lt__PROGRAM__LTX_preloaded_symbols[[]] = { { "@PROGRAM@", (void *) 0 }, _LT_EOF $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext cat <<\_LT_EOF >> conftest.$ac_ext {0, (void *) 0} }; /* This works around a problem in FreeBSD linker */ #ifdef FREEBSD_WORKAROUND static const void *lt_preloaded_setup() { return lt__PROGRAM__LTX_preloaded_symbols; } #endif #ifdef __cplusplus } #endif _LT_EOF # Now try linking the two files. mv conftest.$ac_objext conftstm.$ac_objext lt_globsym_save_LIBS=$LIBS lt_globsym_save_CFLAGS=$CFLAGS LIBS=conftstm.$ac_objext CFLAGS="$CFLAGS$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)" if AC_TRY_EVAL(ac_link) && test -s conftest$ac_exeext; then pipe_works=yes fi LIBS=$lt_globsym_save_LIBS CFLAGS=$lt_globsym_save_CFLAGS else echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD fi else echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD fi else echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD fi else echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD cat conftest.$ac_ext >&5 fi rm -rf conftest* conftst* # Do not use the global_symbol_pipe unless it works. if test yes = "$pipe_works"; then break else lt_cv_sys_global_symbol_pipe= fi done ]) if test -z "$lt_cv_sys_global_symbol_pipe"; then lt_cv_sys_global_symbol_to_cdecl= fi if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then AC_MSG_RESULT(failed) else AC_MSG_RESULT(ok) fi # Response file support. if test "$lt_cv_nm_interface" = "MS dumpbin"; then nm_file_list_spec='@' elif $NM --help 2>/dev/null | grep '[[@]]FILE' >/dev/null; then nm_file_list_spec='@' fi _LT_DECL([global_symbol_pipe], [lt_cv_sys_global_symbol_pipe], [1], [Take the output of nm and produce a listing of raw symbols and C names]) _LT_DECL([global_symbol_to_cdecl], [lt_cv_sys_global_symbol_to_cdecl], [1], [Transform the output of nm in a proper C declaration]) _LT_DECL([global_symbol_to_import], [lt_cv_sys_global_symbol_to_import], [1], [Transform the output of nm into a list of symbols to manually relocate]) _LT_DECL([global_symbol_to_c_name_address], [lt_cv_sys_global_symbol_to_c_name_address], [1], [Transform the output of nm in a C name address pair]) _LT_DECL([global_symbol_to_c_name_address_lib_prefix], [lt_cv_sys_global_symbol_to_c_name_address_lib_prefix], [1], [Transform the output of nm in a C name address pair when lib prefix is needed]) _LT_DECL([nm_interface], [lt_cv_nm_interface], [1], [The name lister interface]) _LT_DECL([], [nm_file_list_spec], [1], [Specify filename containing input files for $NM]) ]) # _LT_CMD_GLOBAL_SYMBOLS # _LT_COMPILER_PIC([TAGNAME]) # --------------------------- m4_defun([_LT_COMPILER_PIC], [m4_require([_LT_TAG_COMPILER])dnl _LT_TAGVAR(lt_prog_compiler_wl, $1)= _LT_TAGVAR(lt_prog_compiler_pic, $1)= _LT_TAGVAR(lt_prog_compiler_static, $1)= m4_if([$1], [CXX], [ # C++ specific cases for pic, static, wl, etc. if test yes = "$GXX"; then _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' case $host_os in aix*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' fi _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; m68k) # FIXME: we need at least 68020 code to build shared libraries, but # adding the '-m68020' flag to GCC prevents building anything better, # like '-m68040'. _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' ;; esac ;; beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) # PIC is the default for these OSes. ;; mingw* | windows* | cygwin* | os2* | pw32* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). # Although the cygwin gcc ignores -fPIC, still need this for old-style # (--disable-auto-import) libraries m4_if([$1], [GCJ], [], [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) case $host_os in os2*) _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' ;; esac ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' ;; *djgpp*) # DJGPP does not support shared libraries at all _LT_TAGVAR(lt_prog_compiler_pic, $1)= ;; haiku*) # PIC is the default for Haiku. # The "-static" flag exists, but is broken. _LT_TAGVAR(lt_prog_compiler_static, $1)= ;; interix[[3-9]]*) # Interix 3.x gcc -fpic/-fPIC options generate broken code. # Instead, we relocate shared libraries at runtime. ;; sysv4*MP*) if test -d /usr/nec; then _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic fi ;; hpux*) # PIC is the default for 64-bit PA HP-UX, but not for 32-bit # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag # sets the default TLS model and affects inlining. case $host_cpu in hppa*64*) ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; esac ;; *qnx* | *nto*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; esac else case $host_os in aix[[4-9]]*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' else _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' fi ;; chorus*) case $cc_basename in cxch68*) # Green Hills C++ Compiler # _LT_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" ;; esac ;; mingw* | windows* | cygwin* | os2* | pw32* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). m4_if([$1], [GCJ], [], [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) ;; dgux*) case $cc_basename in ec++*) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' ;; ghcx*) # Green Hills C++ Compiler _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' ;; *) ;; esac ;; freebsd* | dragonfly* | midnightbsd*) # FreeBSD uses GNU C++ ;; hpux9* | hpux10* | hpux11*) case $cc_basename in CC*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' if test ia64 != "$host_cpu"; then _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' fi ;; aCC*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' case $host_cpu in hppa*64*|ia64*) # +Z the default ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' ;; esac ;; *) ;; esac ;; interix*) # This is c89, which is MS Visual C++ (no shared libs) # Anyone wants to do a port? ;; irix5* | irix6* | nonstopux*) case $cc_basename in CC*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' # CC pic flag -KPIC is the default. ;; *) ;; esac ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in KCC*) # KAI C++ Compiler _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; ecpc* ) # old Intel C++ for x86_64, which still supported -KPIC. _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; icpc* ) # Intel C++, used to be incompatible with GCC. # ICC 10 doesn't accept -KPIC any more. _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; pgCC* | pgcpp*) # Portland Group C++ compiler _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; cxx*) # Compaq C++ # Make sure the PIC flag is empty. It appears that all Alpha # Linux and Compaq Tru64 Unix objects are PIC. _LT_TAGVAR(lt_prog_compiler_pic, $1)= _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' ;; xlc* | xlC* | bgxl[[cC]]* | mpixl[[cC]]*) # IBM XL 8.0, 9.0 on PPC and BlueGene _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' ;; *) case `$CC -V 2>&1 | $SED 5q` in *Sun\ C*) # Sun C++ 5.9 _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' ;; esac ;; esac ;; lynxos*) ;; m88k*) ;; mvs*) case $cc_basename in cxx*) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall' ;; *) ;; esac ;; netbsd* | netbsdelf*-gnu) ;; *-mlibc) ;; *qnx* | *nto*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' ;; osf3* | osf4* | osf5*) case $cc_basename in KCC*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' ;; RCC*) # Rational C++ 2.4.1 _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' ;; cxx*) # Digital/Compaq C++ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' # Make sure the PIC flag is empty. It appears that all Alpha # Linux and Compaq Tru64 Unix objects are PIC. _LT_TAGVAR(lt_prog_compiler_pic, $1)= _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' ;; *) ;; esac ;; psos*) ;; serenity*) ;; solaris*) case $cc_basename in CC* | sunCC*) # Sun C++ 4.2, 5.x and Centerline C++ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' ;; gcx*) # Green Hills C++ Compiler _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' ;; *) ;; esac ;; sunos4*) case $cc_basename in CC*) # Sun C++ 4.x _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; lcc*) # Lucid _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' ;; *) ;; esac ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) case $cc_basename in CC*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; esac ;; tandem*) case $cc_basename in NCC*) # NonStop-UX NCC 3.20 _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' ;; *) ;; esac ;; vxworks*) ;; *) _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no ;; esac fi ], [ if test yes = "$GCC"; then _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' case $host_os in aix*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' fi _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; m68k) # FIXME: we need at least 68020 code to build shared libraries, but # adding the '-m68020' flag to GCC prevents building anything better, # like '-m68040'. _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' ;; esac ;; beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) # PIC is the default for these OSes. ;; mingw* | windows* | cygwin* | pw32* | os2* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). # Although the cygwin gcc ignores -fPIC, still need this for old-style # (--disable-auto-import) libraries m4_if([$1], [GCJ], [], [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) case $host_os in os2*) _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' ;; esac ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' ;; haiku*) # PIC is the default for Haiku. # The "-static" flag exists, but is broken. _LT_TAGVAR(lt_prog_compiler_static, $1)= ;; hpux*) # PIC is the default for 64-bit PA HP-UX, but not for 32-bit # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag # sets the default TLS model and affects inlining. case $host_cpu in hppa*64*) # +Z the default ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; esac ;; interix[[3-9]]*) # Interix 3.x gcc -fpic/-fPIC options generate broken code. # Instead, we relocate shared libraries at runtime. ;; msdosdjgpp*) # Just because we use GCC doesn't mean we suddenly get shared libraries # on systems that don't support them. _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no enable_shared=no ;; *nto* | *qnx*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' ;; sysv4*MP*) if test -d /usr/nec; then _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic fi ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' ;; esac case $cc_basename in nvcc*) # Cuda Compiler Driver 2.2 _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Xlinker ' if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then _LT_TAGVAR(lt_prog_compiler_pic, $1)="-Xcompiler $_LT_TAGVAR(lt_prog_compiler_pic, $1)" fi ;; esac else # PORTME Check for flag to pass linker flags through the system compiler. case $host_os in aix*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' else _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' fi ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' case $cc_basename in nagfor*) # NAG Fortran compiler _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; esac ;; mingw* | windows* | cygwin* | pw32* | os2* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). m4_if([$1], [GCJ], [], [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) case $host_os in os2*) _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' ;; esac ;; hpux9* | hpux10* | hpux11*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but # not for PA HP-UX. case $host_cpu in hppa*64*|ia64*) # +Z the default ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' ;; esac # Is there a better lt_prog_compiler_static that works with the bundled CC? _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' ;; irix5* | irix6* | nonstopux*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' # PIC (with -KPIC) is the default. _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in # old Intel for x86_64, which still supported -KPIC. ecc*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; *flang* | ftn | f18* | f95*) # Flang compiler. _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; # flang / f18. f95 an alias for gfortran or flang on Debian flang* | f18* | f95*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; # icc used to be incompatible with GCC. # ICC 10 doesn't accept -KPIC any more. icc* | ifort*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; # Lahey Fortran 8.1. lf95*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='--shared' _LT_TAGVAR(lt_prog_compiler_static, $1)='--static' ;; nagfor*) # NAG Fortran compiler _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; tcc*) # Fabrice Bellard et al's Tiny C Compiler _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) # Portland Group compilers (*not* the Pentium gcc compiler, # which looks to be a dead project) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; ccc*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' # All Alpha code is PIC. _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' ;; xl* | bgxl* | bgf* | mpixl*) # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' ;; *) case `$CC -V 2>&1 | $SED 5q` in *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [[1-7]].* | *Sun*Fortran*\ 8.[[0-3]]*) # Sun Fortran 8.3 passes all unrecognized flags to the linker _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' _LT_TAGVAR(lt_prog_compiler_wl, $1)='' ;; *Sun\ F* | *Sun*Fortran*) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' ;; *Sun\ C*) # Sun C 5.9 _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' ;; *Intel*\ [[CF]]*Compiler*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; *Portland\ Group*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; esac ;; esac ;; newsos6) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; *-mlibc) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; *nto* | *qnx*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' ;; osf3* | osf4* | osf5*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' # All OSF/1 code is PIC. _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' ;; rdos*) _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' ;; serenity*) ;; solaris*) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' case $cc_basename in f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ';; *) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,';; esac ;; sunos4*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; sysv4 | sysv4.2uw2* | sysv4.3*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; sysv4*MP*) if test -d /usr/nec; then _LT_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' fi ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; unicos*) _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no ;; uts4*) _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' ;; *) _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no ;; esac fi ]) case $host_os in # For platforms that do not support PIC, -DPIC is meaningless: *djgpp*) _LT_TAGVAR(lt_prog_compiler_pic, $1)= ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])" ;; esac AC_CACHE_CHECK([for $compiler option to produce PIC], [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)], [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_prog_compiler_pic, $1)]) _LT_TAGVAR(lt_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_cv_prog_compiler_pic, $1) # # Check to make sure the PIC flag actually works. # if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then _LT_COMPILER_OPTION([if $compiler PIC flag $_LT_TAGVAR(lt_prog_compiler_pic, $1) works], [_LT_TAGVAR(lt_cv_prog_compiler_pic_works, $1)], [$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])], [], [case $_LT_TAGVAR(lt_prog_compiler_pic, $1) in "" | " "*) ;; *) _LT_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_TAGVAR(lt_prog_compiler_pic, $1)" ;; esac], [_LT_TAGVAR(lt_prog_compiler_pic, $1)= _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no]) fi _LT_TAGDECL([pic_flag], [lt_prog_compiler_pic], [1], [Additional compiler flags for building library objects]) _LT_TAGDECL([wl], [lt_prog_compiler_wl], [1], [How to pass a linker flag through the compiler]) # # Check to make sure the static flag actually works. # wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) eval lt_tmp_static_flag=\"$_LT_TAGVAR(lt_prog_compiler_static, $1)\" _LT_LINKER_OPTION([if $compiler static flag $lt_tmp_static_flag works], _LT_TAGVAR(lt_cv_prog_compiler_static_works, $1), $lt_tmp_static_flag, [], [_LT_TAGVAR(lt_prog_compiler_static, $1)=]) _LT_TAGDECL([link_static_flag], [lt_prog_compiler_static], [1], [Compiler flag to prevent dynamic linking]) ])# _LT_COMPILER_PIC # _LT_LINKER_SHLIBS([TAGNAME]) # ---------------------------- # See if the linker supports building shared libraries. m4_defun([_LT_LINKER_SHLIBS], [AC_REQUIRE([LT_PATH_LD])dnl AC_REQUIRE([LT_PATH_NM])dnl m4_require([_LT_PATH_MANIFEST_TOOL])dnl m4_require([_LT_FILEUTILS_DEFAULTS])dnl m4_require([_LT_DECL_EGREP])dnl m4_require([_LT_DECL_SED])dnl m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl m4_require([_LT_TAG_COMPILER])dnl AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) m4_if([$1], [CXX], [ _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] case $host_os in aix[[4-9]]*) # If we're using GNU nm, then we don't want the "-C" option. # -C means demangle to GNU nm, but means don't demangle to AIX nm. # Without the "-l" option, or with the "-B" option, AIX nm treats # weak defined symbols like other global defined symbols, whereas # GNU nm marks them as "W". # While the 'weak' keyword is ignored in the Export File, we need # it in the Import File for the 'aix-soname' feature, so we have # to replace the "-B" option with "-P" for AIX nm. if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' else _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "L") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' fi ;; pw32*) _LT_TAGVAR(export_symbols_cmds, $1)=$ltdll_cmds ;; cygwin* | mingw* | windows* | cegcc*) case $cc_basename in cl* | icl*) _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' ;; *) _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] ;; esac ;; linux* | k*bsd*-gnu | gnu*) _LT_TAGVAR(link_all_deplibs, $1)=no ;; *) _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' ;; esac ], [ runpath_var= _LT_TAGVAR(allow_undefined_flag, $1)= _LT_TAGVAR(always_export_symbols, $1)=no _LT_TAGVAR(archive_cmds, $1)= _LT_TAGVAR(archive_expsym_cmds, $1)= _LT_TAGVAR(compiler_needs_object, $1)=no _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no _LT_TAGVAR(export_dynamic_flag_spec, $1)= _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' _LT_TAGVAR(hardcode_automatic, $1)=no _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= _LT_TAGVAR(hardcode_libdir_separator, $1)= _LT_TAGVAR(hardcode_minus_L, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported _LT_TAGVAR(inherit_rpath, $1)=no _LT_TAGVAR(link_all_deplibs, $1)=unknown _LT_TAGVAR(module_cmds, $1)= _LT_TAGVAR(module_expsym_cmds, $1)= _LT_TAGVAR(old_archive_from_new_cmds, $1)= _LT_TAGVAR(old_archive_from_expsyms_cmds, $1)= _LT_TAGVAR(thread_safe_flag_spec, $1)= _LT_TAGVAR(whole_archive_flag_spec, $1)= # include_expsyms should be a list of space-separated symbols to be *always* # included in the symbol list _LT_TAGVAR(include_expsyms, $1)= # exclude_expsyms can be an extended regexp of symbols to exclude # it will be wrapped by ' (' and ')$', so one must not match beginning or # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc', # as well as any symbol that contains 'd'. _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out # platforms (ab)use it in PIC code, but their linkers get confused if # the symbol is explicitly referenced. Since portable code cannot # rely on this symbol name, it's probably fine to never include it in # preloaded symbol tables. # Exclude shared library initialization/finalization symbols. dnl Note also adjust exclude_expsyms for C++ above. extract_expsyms_cmds= case $host_os in cygwin* | mingw* | windows* | pw32* | cegcc*) # FIXME: the MSVC++ and ICC port hasn't been tested in a loooong time # When not using gcc, we currently assume that we are using # Microsoft Visual C++ or Intel C++ Compiler. if test yes != "$GCC"; then with_gnu_ld=no fi ;; interix*) # we just hope/assume this is gcc and not c89 (= MSVC++ or ICC) with_gnu_ld=yes ;; linux* | k*bsd*-gnu | gnu*) _LT_TAGVAR(link_all_deplibs, $1)=no ;; esac _LT_TAGVAR(ld_shlibs, $1)=yes # On some targets, GNU ld is compatible enough with the native linker # that we're better off using the native interface for both. lt_use_gnu_ld_interface=no if test yes = "$with_gnu_ld"; then case $host_os in aix*) # The AIX port of GNU ld has always aspired to compatibility # with the native linker. However, as the warning in the GNU ld # block says, versions before 2.19.5* couldn't really create working # shared libraries, regardless of the interface used. case `$LD -v 2>&1` in *\ \(GNU\ Binutils\)\ 2.19.5*) ;; *\ \(GNU\ Binutils\)\ 2.[[2-9]]*) ;; *\ \(GNU\ Binutils\)\ [[3-9]]*) ;; *) lt_use_gnu_ld_interface=yes ;; esac ;; *) lt_use_gnu_ld_interface=yes ;; esac fi if test yes = "$lt_use_gnu_ld_interface"; then # If archive_cmds runs LD, not CC, wlarc should be empty wlarc='$wl' # Set some defaults for GNU ld with shared library support. These # are reset later if shared libraries are not supported. Putting them # here allows them to be overridden if necessary. runpath_var=LD_RUN_PATH _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' # ancient GNU ld didn't support --whole-archive et. al. if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' else _LT_TAGVAR(whole_archive_flag_spec, $1)= fi supports_anon_versioning=no case `$LD -v | $SED -e 's/([[^)]]\+)\s\+//' 2>&1` in *GNU\ gold*) supports_anon_versioning=yes ;; *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.10.*) ;; # catch versions < 2.11 *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... *\ 2.11.*) ;; # other 2.11 versions *) supports_anon_versioning=yes ;; esac # See if GNU ld supports shared libraries. case $host_os in aix[[3-9]]*) # On AIX/PPC, the GNU linker is very broken if test ia64 != "$host_cpu"; then _LT_TAGVAR(ld_shlibs, $1)=no cat <<_LT_EOF 1>&2 *** Warning: the GNU linker, at least up to release 2.19, is reported *** to be unable to reliably create shared libraries on AIX. *** Therefore, libtool is disabling shared libraries support. If you *** really care for shared libraries, you may want to install binutils *** 2.20 or above, or modify your PATH so that a non-GNU linker is found. *** You will then need to restart the configuration process. _LT_EOF fi ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='' ;; m68k) _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_minus_L, $1)=yes ;; esac ;; beos*) if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then _LT_TAGVAR(allow_undefined_flag, $1)=unsupported # Joseph Beckenbach says some releases of gcc # support --undefined. This deserves some investigation. FIXME _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; cygwin* | mingw* | windows* | pw32* | cegcc*) # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, # as there is no search path for DLLs. _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' _LT_TAGVAR(allow_undefined_flag, $1)=unsupported _LT_TAGVAR(always_export_symbols, $1)=no _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] _LT_TAGVAR(file_list_spec, $1)='@' if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' # If the export-symbols file already is a .def file, use it as # is; otherwise, prepend EXPORTS... _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then cp $export_symbols $output_objdir/$soname.def; else echo EXPORTS > $output_objdir/$soname.def; cat $export_symbols >> $output_objdir/$soname.def; fi~ $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; haiku*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(link_all_deplibs, $1)=no ;; os2*) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(allow_undefined_flag, $1)=unsupported shrext_cmds=.dll _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ prefix_cmds="$SED"~ if test EXPORTS = "`$SED 1q $export_symbols`"; then prefix_cmds="$prefix_cmds -e 1d"; fi~ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' _LT_TAGVAR(old_archive_from_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes _LT_TAGVAR(file_list_spec, $1)='@' ;; interix[[3-9]]*) _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. # Instead, shared libraries are loaded at an image base (0x10000000 by # default) and relocated if they conflict, which is a slow very memory # consuming and fragmenting process. To avoid this, we pick a random, # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link # time. Moving up from 0x10000000 also allows more sbrk(2) space. _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$SED "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' ;; gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) tmp_diet=no if test linux-dietlibc = "$host_os"; then case $cc_basename in diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) esac fi if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ && test no = "$tmp_diet" then tmp_addflag=' $pic_flag' tmp_sharedflag='-shared' case $cc_basename,$host_cpu in pgcc*) # Portland Group C compiler _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' tmp_addflag=' $pic_flag' ;; pgf77* | pgf90* | pgf95* | pgfortran*) # Portland Group f77 and f90 compilers _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' tmp_addflag=' $pic_flag -Mnomain' ;; ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 tmp_addflag=' -i_dynamic' ;; efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 tmp_addflag=' -i_dynamic -nofor_main' ;; ifc* | ifort*) # Intel Fortran compiler tmp_addflag=' -nofor_main' ;; lf95*) # Lahey Fortran 8.1 _LT_TAGVAR(whole_archive_flag_spec, $1)= tmp_sharedflag='--shared' ;; nagfor*) # NAGFOR 5.3 tmp_sharedflag='-Wl,-shared' ;; xl[[cC]]* | bgxl[[cC]]* | mpixl[[cC]]*) # IBM XL C 8.0 on PPC (deal with xlf below) tmp_sharedflag='-qmkshrobj' tmp_addflag= ;; nvcc*) # Cuda Compiler Driver 2.2 _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' _LT_TAGVAR(compiler_needs_object, $1)=yes ;; esac case `$CC -V 2>&1 | $SED 5q` in *Sun\ C*) # Sun C 5.9 _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' _LT_TAGVAR(compiler_needs_object, $1)=yes tmp_sharedflag='-G' ;; *Sun\ F*) # Sun Fortran 8.3 tmp_sharedflag='-G' ;; esac _LT_TAGVAR(archive_cmds, $1)='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' if test yes = "$supports_anon_versioning"; then _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ echo "local: *; };" >> $output_objdir/$libname.ver~ $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' fi case $cc_basename in tcc*) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='-rdynamic' ;; xlf* | bgf* | bgxlf* | mpixlf*) # IBM XL Fortran 10.1 on PPC cannot create shared libs itself _LT_TAGVAR(whole_archive_flag_spec, $1)='--whole-archive$convenience --no-whole-archive' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' if test yes = "$supports_anon_versioning"; then _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ echo "local: *; };" >> $output_objdir/$libname.ver~ $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' fi ;; esac else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; *-mlibc) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; netbsd* | netbsdelf*-gnu) if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' wlarc= else _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' fi ;; solaris*) if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then _LT_TAGVAR(ld_shlibs, $1)=no cat <<_LT_EOF 1>&2 *** Warning: The releases 2.8.* of the GNU linker cannot reliably *** create shared libraries on Solaris systems. Therefore, libtool *** is disabling shared libraries support. We urge you to upgrade GNU *** binutils to release 2.9.1 or newer. Another option is to modify *** your PATH or compiler configuration so that the native linker is *** used, and then restart. _LT_EOF elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) case `$LD -v 2>&1` in *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.1[[0-5]].*) _LT_TAGVAR(ld_shlibs, $1)=no cat <<_LT_EOF 1>&2 *** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot *** reliably create shared libraries on SCO systems. Therefore, libtool *** is disabling shared libraries support. We urge you to upgrade GNU *** binutils to release 2.16.91.0.3 or newer. Another option is to modify *** your PATH or compiler configuration so that the native linker is *** used, and then restart. _LT_EOF ;; *) # For security reasons, it is highly recommended that you always # use absolute paths for naming shared libraries, and exclude the # DT_RUNPATH tag from executables and libraries. But doing so # requires that you compile everything twice, which is a pain. if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; esac ;; sunos4*) _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' wlarc= _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; *) if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; esac if test no = "$_LT_TAGVAR(ld_shlibs, $1)"; then runpath_var= _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= _LT_TAGVAR(export_dynamic_flag_spec, $1)= _LT_TAGVAR(whole_archive_flag_spec, $1)= fi else # PORTME fill in a description of your system's linker (not GNU ld) case $host_os in aix3*) _LT_TAGVAR(allow_undefined_flag, $1)=unsupported _LT_TAGVAR(always_export_symbols, $1)=yes _LT_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' # Note: this linker hardcodes the directories in LIBPATH if there # are no directories specified by -L. _LT_TAGVAR(hardcode_minus_L, $1)=yes if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then # Neither direct hardcoding nor static linking is supported with a # broken collect2. _LT_TAGVAR(hardcode_direct, $1)=unsupported fi ;; aix[[4-9]]*) if test ia64 = "$host_cpu"; then # On IA64, the linker does run time linking by default, so we don't # have to do anything special. aix_use_runtimelinking=no exp_sym_flag='-Bexport' no_entry_flag= else # If we're using GNU nm, then we don't want the "-C" option. # -C means demangle to GNU nm, but means don't demangle to AIX nm. # Without the "-l" option, or with the "-B" option, AIX nm treats # weak defined symbols like other global defined symbols, whereas # GNU nm marks them as "W". # While the 'weak' keyword is ignored in the Export File, we need # it in the Import File for the 'aix-soname' feature, so we have # to replace the "-B" option with "-P" for AIX nm. if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' else _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "L") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' fi aix_use_runtimelinking=no # Test if we are trying to use run time linking or normal # AIX style linking. If -brtl is somewhere in LDFLAGS, we # have runtime linking enabled, and use it for executables. # For shared libraries, we enable/disable runtime linking # depending on the kind of the shared library created - # when "with_aix_soname,aix_use_runtimelinking" is: # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables # "aix,yes" lib.so shared, rtl:yes, for executables # lib.a static archive # "both,no" lib.so.V(shr.o) shared, rtl:yes # lib.a(lib.so.V) shared, rtl:no, for executables # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a(lib.so.V) shared, rtl:no # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a static archive case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) for ld_flag in $LDFLAGS; do if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then aix_use_runtimelinking=yes break fi done if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then # With aix-soname=svr4, we create the lib.so.V shared archives only, # so we don't have lib.a shared libs to link our executables. # We have to force runtime linking in this case. aix_use_runtimelinking=yes LDFLAGS="$LDFLAGS -Wl,-brtl" fi ;; esac exp_sym_flag='-bexport' no_entry_flag='-bnoentry' fi # When large executables or shared objects are built, AIX ld can # have problems creating the table of contents. If linking a library # or program results in "error TOC overflow" add -mminimal-toc to # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. _LT_TAGVAR(archive_cmds, $1)='' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_direct_absolute, $1)=yes _LT_TAGVAR(hardcode_libdir_separator, $1)=':' _LT_TAGVAR(link_all_deplibs, $1)=yes _LT_TAGVAR(file_list_spec, $1)='$wl-f,' case $with_aix_soname,$aix_use_runtimelinking in aix,*) ;; # traditional, no import file svr4,* | *,yes) # use import file # The Import File defines what to hardcode. _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=no ;; esac if test yes = "$GCC"; then case $host_os in aix4.[[012]]|aix4.[[012]].*) # We only want to do this on AIX 4.2 and lower, the check # below for broken collect2 doesn't work under 4.3+ collect2name=`$CC -print-prog-name=collect2` if test -f "$collect2name" && strings "$collect2name" | $GREP resolve_lib_name >/dev/null then # We have reworked collect2 : else # We have old collect2 _LT_TAGVAR(hardcode_direct, $1)=unsupported # It fails to find uninstalled libraries when the uninstalled # path is not listed in the libpath. Setting hardcode_minus_L # to unsupported forces relinking _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)= fi ;; esac shared_flag='-shared' if test yes = "$aix_use_runtimelinking"; then shared_flag="$shared_flag "'$wl-G' fi # Need to ensure runtime linking is disabled for the traditional # shared library, or the linker may eventually find shared libraries # /with/ Import File - we do not want to mix them. shared_flag_aix='-shared' shared_flag_svr4='-shared $wl-G' else # not using gcc if test ia64 = "$host_cpu"; then # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release # chokes on -Wl,-G. The following line is correct: shared_flag='-G' else if test yes = "$aix_use_runtimelinking"; then shared_flag='$wl-G' else shared_flag='$wl-bM:SRE' fi shared_flag_aix='$wl-bM:SRE' shared_flag_svr4='$wl-G' fi fi _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' # It seems that -bexpall does not export symbols beginning with # underscore (_), so it is better to generate a list of symbols to export. _LT_TAGVAR(always_export_symbols, $1)=yes if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then # Warning - without using the other runtime loading flags (-brtl), # -berok will link without error, but may produce a broken library. _LT_TAGVAR(allow_undefined_flag, $1)='-berok' # Determine the default libpath from the value encoded in an # empty executable. _LT_SYS_MODULE_PATH_AIX([$1]) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag else if test ia64 = "$host_cpu"; then _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" else # Determine the default libpath from the value encoded in an # empty executable. _LT_SYS_MODULE_PATH_AIX([$1]) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" # Warning - without using the other run time loading flags, # -berok will link without error, but may produce a broken library. _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' if test yes = "$with_gnu_ld"; then # We only use this code for GNU lds that support --whole-archive. _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' else # Exported symbols can be pulled into shared objects from archives _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' fi _LT_TAGVAR(archive_cmds_need_lc, $1)=yes _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' # -brtl affects multiple linker settings, -berok does not and is overridden later compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' if test svr4 != "$with_aix_soname"; then # This is similar to how AIX traditionally builds its shared libraries. _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' fi if test aix != "$with_aix_soname"; then _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' else # used by -dlpreopen to get the symbols _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' fi _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' fi fi ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='' ;; m68k) _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_minus_L, $1)=yes ;; esac ;; bsdi[[45]]*) _LT_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic ;; cygwin* | mingw* | windows* | pw32* | cegcc*) # When not using gcc, we currently assume that we are using # Microsoft Visual C++ or Intel C++ Compiler. # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. case $cc_basename in cl* | icl*) # Native MSVC or ICC _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' _LT_TAGVAR(allow_undefined_flag, $1)=unsupported _LT_TAGVAR(always_export_symbols, $1)=yes _LT_TAGVAR(file_list_spec, $1)='@' # Tell ltmain to make .lib files, not .a files. libext=lib # Tell ltmain to make .dll files, not .so files. shrext_cmds=.dll # FIXME: Setting linknames here is a bad hack. _LT_TAGVAR(archive_cmds, $1)='$CC -Fe$output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then cp "$export_symbols" "$output_objdir/$soname.def"; echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; else $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; fi~ $CC -Fe$tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ linknames=' # The linker will not automatically build a static lib if we build a DLL. # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1,DATA/'\'' | $SED -e '\''/^[[AITW]][[ ]]/s/.*[[ ]]//'\'' | sort | uniq > $export_symbols' # Don't use ranlib _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ lt_tool_outputfile="@TOOL_OUTPUT@"~ case $lt_outputfile in *.exe|*.EXE) ;; *) lt_outputfile=$lt_outputfile.exe lt_tool_outputfile=$lt_tool_outputfile.exe ;; esac~ if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; $RM "$lt_outputfile.manifest"; fi' ;; *) # Assume MSVC and ICC wrapper _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' _LT_TAGVAR(allow_undefined_flag, $1)=unsupported # Tell ltmain to make .lib files, not .a files. libext=lib # Tell ltmain to make .dll files, not .so files. shrext_cmds=.dll # FIXME: Setting linknames here is a bad hack. _LT_TAGVAR(archive_cmds, $1)='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' # The linker will automatically build a .lib file if we build a DLL. _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' # FIXME: Should let the user specify the lib program. _LT_TAGVAR(old_archive_cmds, $1)='lib -OUT:$oldlib$oldobjs$old_deplibs' _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes ;; esac ;; darwin* | rhapsody*) _LT_DARWIN_LINKER_FEATURES($1) ;; dgux*) _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor # support. Future versions do this automatically, but an explicit c++rt0.o # does not break anything, and helps significantly (at the cost of a little # extra space). freebsd2.2*) _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; # Unfortunately, older versions of FreeBSD 2 do not have this feature. freebsd2.*) _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; # FreeBSD 3 and greater uses gcc -shared to do shared libraries. freebsd* | dragonfly* | midnightbsd*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; hpux9*) if test yes = "$GCC"; then _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' else _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' fi _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: _LT_TAGVAR(hardcode_direct, $1)=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' ;; hpux10*) if test yes,no = "$GCC,$with_gnu_ld"; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' else _LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' fi if test no = "$with_gnu_ld"; then _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_direct_absolute, $1)=yes _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. _LT_TAGVAR(hardcode_minus_L, $1)=yes fi ;; hpux11*) if test yes,no = "$GCC,$with_gnu_ld"; then case $host_cpu in hppa*64*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' ;; ia64*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' ;; *) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' ;; esac else case $host_cpu in hppa*64*) _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' ;; ia64*) _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' ;; *) m4_if($1, [], [ # Older versions of the 11.00 compiler do not understand -b yet # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) _LT_LINKER_OPTION([if $CC understands -b], _LT_TAGVAR(lt_cv_prog_compiler__b, $1), [-b], [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags'], [_LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'])], [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags']) ;; esac fi if test no = "$with_gnu_ld"; then _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: case $host_cpu in hppa*64*|ia64*) _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; *) _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_direct_absolute, $1)=yes _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. _LT_TAGVAR(hardcode_minus_L, $1)=yes ;; esac fi ;; irix5* | irix6* | nonstopux*) if test yes = "$GCC"; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' # Try to use the -exported_symbol ld option, if it does not # work, assume that -exports_file does not work either and # implicitly export all symbols. # This should be the same for all languages, so no per-tag cache variable. AC_CACHE_CHECK([whether the $host_os linker accepts -exported_symbol], [lt_cv_irix_exported_symbol], [save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null" AC_LINK_IFELSE( [AC_LANG_SOURCE( [AC_LANG_CASE([C], [[int foo (void) { return 0; }]], [C++], [[int foo (void) { return 0; }]], [Fortran 77], [[ subroutine foo end]], [Fortran], [[ subroutine foo end]])])], [lt_cv_irix_exported_symbol=yes], [lt_cv_irix_exported_symbol=no]) LDFLAGS=$save_LDFLAGS]) if test yes = "$lt_cv_irix_exported_symbol"; then _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib' fi _LT_TAGVAR(link_all_deplibs, $1)=no else _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib' fi _LT_TAGVAR(archive_cmds_need_lc, $1)='no' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: _LT_TAGVAR(inherit_rpath, $1)=yes _LT_TAGVAR(link_all_deplibs, $1)=yes ;; linux*) case $cc_basename in tcc*) # Fabrice Bellard et al's Tiny C Compiler _LT_TAGVAR(ld_shlibs, $1)=yes _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' ;; esac ;; *-mlibc) ;; netbsd* | netbsdelf*-gnu) if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out else _LT_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF fi _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; newsos6) _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; *nto* | *qnx*) ;; openbsd*) if test -f /usr/libexec/ld.so; then _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=yes if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' else _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' fi else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; os2*) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(allow_undefined_flag, $1)=unsupported shrext_cmds=.dll _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ prefix_cmds="$SED"~ if test EXPORTS = "`$SED 1q $export_symbols`"; then prefix_cmds="$prefix_cmds -e 1d"; fi~ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' _LT_TAGVAR(old_archive_from_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes _LT_TAGVAR(file_list_spec, $1)='@' ;; osf3*) if test yes = "$GCC"; then _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' else _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' fi _LT_TAGVAR(archive_cmds_need_lc, $1)='no' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: ;; osf4* | osf5*) # as osf3* with the addition of -msym flag if test yes = "$GCC"; then _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' else _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp' # Both c and cxx compiler support -rpath directly _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' fi _LT_TAGVAR(archive_cmds_need_lc, $1)='no' _LT_TAGVAR(hardcode_libdir_separator, $1)=: ;; serenity*) ;; solaris*) _LT_TAGVAR(no_undefined_flag, $1)=' -z defs' if test yes = "$GCC"; then wlarc='$wl' _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' else case `$CC -V 2>&1` in *"Compilers 5.0"*) wlarc='' _LT_TAGVAR(archive_cmds, $1)='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' ;; *) wlarc='$wl' _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' ;; esac fi _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' _LT_TAGVAR(hardcode_shlibpath_var, $1)=no case $host_os in solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; *) # The compiler driver will combine and reorder linker options, # but understands '-z linker_flag'. GCC discards it without '$wl', # but is careful enough not to reorder. # Supported since Solaris 2.6 (maybe 2.5.1?) if test yes = "$GCC"; then _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' else _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' fi ;; esac _LT_TAGVAR(link_all_deplibs, $1)=yes ;; sunos4*) if test sequent = "$host_vendor"; then # Use $CC to link under sequent, because it throws in some extra .o # files that make .init and .fini sections work. _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags' else _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' fi _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; sysv4) case $host_vendor in sni) _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_direct, $1)=yes # is this really true??? ;; siemens) ## LD is ld it makes a PLAMLIB ## CC just makes a GrossModule. _LT_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs' _LT_TAGVAR(hardcode_direct, $1)=no ;; motorola) _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie ;; esac runpath_var='LD_RUN_PATH' _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; sysv4.3*) _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_shlibpath_var, $1)=no _LT_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport' ;; sysv4*MP*) if test -d /usr/nec; then _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_shlibpath_var, $1)=no runpath_var=LD_RUN_PATH hardcode_runpath_var=yes _LT_TAGVAR(ld_shlibs, $1)=yes fi ;; sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no runpath_var='LD_RUN_PATH' if test yes = "$GCC"; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' else _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' fi ;; sysv5* | sco3.2v5* | sco5v6*) # Note: We CANNOT use -z defs as we might desire, because we do not # link with -lc, and that would cause any symbols used from libc to # always be unresolved, which means just about no library would # ever link correctly. If we're not using GNU ld we use -z text # though, which does catch some bad symbols but isn't as heavy-handed # as -z defs. _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=':' _LT_TAGVAR(link_all_deplibs, $1)=yes _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' runpath_var='LD_RUN_PATH' if test yes = "$GCC"; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' else _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' fi ;; uts4*) _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; *) _LT_TAGVAR(ld_shlibs, $1)=no ;; esac if test sni = "$host_vendor"; then case $host in sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Blargedynsym' ;; esac fi fi ]) AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no _LT_TAGVAR(with_gnu_ld, $1)=$with_gnu_ld _LT_DECL([], [libext], [0], [Old archive suffix (normally "a")])dnl _LT_DECL([], [shrext_cmds], [1], [Shared library suffix (normally ".so")])dnl _LT_DECL([], [extract_expsyms_cmds], [2], [The commands to extract the exported symbol list from a shared archive]) # # Do we need to explicitly link libc? # case "x$_LT_TAGVAR(archive_cmds_need_lc, $1)" in x|xyes) # Assume -lc should be added _LT_TAGVAR(archive_cmds_need_lc, $1)=yes if test yes,yes = "$GCC,$enable_shared"; then case $_LT_TAGVAR(archive_cmds, $1) in *'~'*) # FIXME: we may have to deal with multi-command sequences. ;; '$CC '*) # Test whether the compiler implicitly links with -lc since on some # systems, -lgcc has to come before -lc. If gcc already passes -lc # to ld, don't add -lc before -lgcc. AC_CACHE_CHECK([whether -lc should be explicitly linked in], [lt_cv_]_LT_TAGVAR(archive_cmds_need_lc, $1), [$RM conftest* echo "$lt_simple_compile_test_code" > conftest.$ac_ext if AC_TRY_EVAL(ac_compile) 2>conftest.err; then soname=conftest lib=conftest libobjs=conftest.$ac_objext deplibs= wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) pic_flag=$_LT_TAGVAR(lt_prog_compiler_pic, $1) compiler_flags=-v linker_flags=-v verstring= output_objdir=. libname=conftest lt_save_allow_undefined_flag=$_LT_TAGVAR(allow_undefined_flag, $1) _LT_TAGVAR(allow_undefined_flag, $1)= if AC_TRY_EVAL(_LT_TAGVAR(archive_cmds, $1) 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) then lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=no else lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=yes fi _LT_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag else cat conftest.err 1>&5 fi $RM conftest* ]) _LT_TAGVAR(archive_cmds_need_lc, $1)=$lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1) ;; esac fi ;; esac _LT_TAGDECL([build_libtool_need_lc], [archive_cmds_need_lc], [0], [Whether or not to add -lc for building shared libraries]) _LT_TAGDECL([allow_libtool_libs_with_static_runtimes], [enable_shared_with_static_runtimes], [0], [Whether or not to disallow shared libs when runtime libs are static]) _LT_TAGDECL([], [export_dynamic_flag_spec], [1], [Compiler flag to allow reflexive dlopens]) _LT_TAGDECL([], [whole_archive_flag_spec], [1], [Compiler flag to generate shared objects directly from archives]) _LT_TAGDECL([], [compiler_needs_object], [1], [Whether the compiler copes with passing no objects directly]) _LT_TAGDECL([], [old_archive_from_new_cmds], [2], [Create an old-style archive from a shared archive]) _LT_TAGDECL([], [old_archive_from_expsyms_cmds], [2], [Create a temporary old-style archive to link instead of a shared archive]) _LT_TAGDECL([], [archive_cmds], [2], [Commands used to build a shared archive]) _LT_TAGDECL([], [archive_expsym_cmds], [2]) _LT_TAGDECL([], [module_cmds], [2], [Commands used to build a loadable module if different from building a shared archive.]) _LT_TAGDECL([], [module_expsym_cmds], [2]) _LT_TAGDECL([], [with_gnu_ld], [1], [Whether we are building with GNU ld or not]) _LT_TAGDECL([], [allow_undefined_flag], [1], [Flag that allows shared libraries with undefined symbols to be built]) _LT_TAGDECL([], [no_undefined_flag], [1], [Flag that enforces no undefined symbols]) _LT_TAGDECL([], [hardcode_libdir_flag_spec], [1], [Flag to hardcode $libdir into a binary during linking. This must work even if $libdir does not exist]) _LT_TAGDECL([], [hardcode_libdir_separator], [1], [Whether we need a single "-rpath" flag with a separated argument]) _LT_TAGDECL([], [hardcode_direct], [0], [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes DIR into the resulting binary]) _LT_TAGDECL([], [hardcode_direct_absolute], [0], [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes DIR into the resulting binary and the resulting library dependency is "absolute", i.e. impossible to change by setting $shlibpath_var if the library is relocated]) _LT_TAGDECL([], [hardcode_minus_L], [0], [Set to "yes" if using the -LDIR flag during linking hardcodes DIR into the resulting binary]) _LT_TAGDECL([], [hardcode_shlibpath_var], [0], [Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR into the resulting binary]) _LT_TAGDECL([], [hardcode_automatic], [0], [Set to "yes" if building a shared library automatically hardcodes DIR into the library and all subsequent libraries and executables linked against it]) _LT_TAGDECL([], [inherit_rpath], [0], [Set to yes if linker adds runtime paths of dependent libraries to runtime path list]) _LT_TAGDECL([], [link_all_deplibs], [0], [Whether libtool must link a program against all its dependency libraries]) _LT_TAGDECL([], [always_export_symbols], [0], [Set to "yes" if exported symbols are required]) _LT_TAGDECL([], [export_symbols_cmds], [2], [The commands to list exported symbols]) _LT_TAGDECL([], [exclude_expsyms], [1], [Symbols that should not be listed in the preloaded symbols]) _LT_TAGDECL([], [include_expsyms], [1], [Symbols that must always be exported]) _LT_TAGDECL([], [prelink_cmds], [2], [Commands necessary for linking programs (against libraries) with templates]) _LT_TAGDECL([], [postlink_cmds], [2], [Commands necessary for finishing linking programs]) _LT_TAGDECL([], [file_list_spec], [1], [Specify filename containing input files]) dnl FIXME: Not yet implemented dnl _LT_TAGDECL([], [thread_safe_flag_spec], [1], dnl [Compiler flag to generate thread safe objects]) ])# _LT_LINKER_SHLIBS # _LT_LANG_C_CONFIG([TAG]) # ------------------------ # Ensure that the configuration variables for a C compiler are suitably # defined. These variables are subsequently used by _LT_CONFIG to write # the compiler configuration to 'libtool'. m4_defun([_LT_LANG_C_CONFIG], [m4_require([_LT_DECL_EGREP])dnl lt_save_CC=$CC AC_LANG_PUSH(C) # Source file extension for C test sources. ac_ext=c # Object file extension for compiled C test sources. objext=o _LT_TAGVAR(objext, $1)=$objext # Code to be used in simple compile tests lt_simple_compile_test_code="int some_variable = 0;" # Code to be used in simple link tests lt_simple_link_test_code='int main(void){return(0);}' _LT_TAG_COMPILER # Save the default compiler, since it gets overwritten when the other # tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. compiler_DEFAULT=$CC # save warnings/boilerplate of simple test code _LT_COMPILER_BOILERPLATE _LT_LINKER_BOILERPLATE if test -n "$compiler"; then _LT_COMPILER_NO_RTTI($1) _LT_COMPILER_PIC($1) _LT_COMPILER_C_O($1) _LT_COMPILER_FILE_LOCKS($1) _LT_LINKER_SHLIBS($1) _LT_SYS_DYNAMIC_LINKER($1) _LT_LINKER_HARDCODE_LIBPATH($1) LT_SYS_DLOPEN_SELF _LT_CMD_STRIPLIB # Report what library types will actually be built AC_MSG_CHECKING([if libtool supports shared libraries]) AC_MSG_RESULT([$can_build_shared]) AC_MSG_CHECKING([whether to build shared libraries]) test no = "$can_build_shared" && enable_shared=no # On AIX, shared libraries and static libraries use the same namespace, and # are all built from PIC. case $host_os in aix3*) test yes = "$enable_shared" && enable_static=no if test -n "$RANLIB"; then archive_cmds="$archive_cmds~\$RANLIB \$lib" postinstall_cmds='$RANLIB $lib' fi ;; aix[[4-9]]*) if test ia64 != "$host_cpu"; then case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in yes,aix,yes) ;; # shared object as lib.so file only yes,svr4,*) ;; # shared object as lib.so archive member only yes,*) enable_static=no ;; # shared object in lib.a archive as well esac fi ;; esac AC_MSG_RESULT([$enable_shared]) AC_MSG_CHECKING([whether to build static libraries]) # Make sure either enable_shared or enable_static is yes. test yes = "$enable_shared" || enable_static=yes AC_MSG_RESULT([$enable_static]) _LT_CONFIG($1) fi AC_LANG_POP CC=$lt_save_CC ])# _LT_LANG_C_CONFIG # _LT_LANG_CXX_CONFIG([TAG]) # -------------------------- # Ensure that the configuration variables for a C++ compiler are suitably # defined. These variables are subsequently used by _LT_CONFIG to write # the compiler configuration to 'libtool'. m4_defun([_LT_LANG_CXX_CONFIG], [m4_require([_LT_FILEUTILS_DEFAULTS])dnl m4_require([_LT_DECL_EGREP])dnl m4_require([_LT_PATH_MANIFEST_TOOL])dnl if test -n "$CXX" && ( test no != "$CXX" && ( (test g++ = "$CXX" && `g++ -v >/dev/null 2>&1` ) || (test g++ != "$CXX"))); then AC_PROG_CXXCPP else _lt_caught_CXX_error=yes fi AC_LANG_PUSH(C++) _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(allow_undefined_flag, $1)= _LT_TAGVAR(always_export_symbols, $1)=no _LT_TAGVAR(archive_expsym_cmds, $1)= _LT_TAGVAR(compiler_needs_object, $1)=no _LT_TAGVAR(export_dynamic_flag_spec, $1)= _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= _LT_TAGVAR(hardcode_libdir_separator, $1)= _LT_TAGVAR(hardcode_minus_L, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported _LT_TAGVAR(hardcode_automatic, $1)=no _LT_TAGVAR(inherit_rpath, $1)=no _LT_TAGVAR(module_cmds, $1)= _LT_TAGVAR(module_expsym_cmds, $1)= _LT_TAGVAR(link_all_deplibs, $1)=unknown _LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds _LT_TAGVAR(reload_flag, $1)=$reload_flag _LT_TAGVAR(reload_cmds, $1)=$reload_cmds _LT_TAGVAR(no_undefined_flag, $1)= _LT_TAGVAR(whole_archive_flag_spec, $1)= _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no # Source file extension for C++ test sources. ac_ext=cpp # Object file extension for compiled C++ test sources. objext=o _LT_TAGVAR(objext, $1)=$objext # No sense in running all these tests if we already determined that # the CXX compiler isn't working. Some variables (like enable_shared) # are currently assumed to apply to all compilers on this platform, # and will be corrupted by setting them based on a non-working compiler. if test yes != "$_lt_caught_CXX_error"; then # Code to be used in simple compile tests lt_simple_compile_test_code="int some_variable = 0;" # Code to be used in simple link tests lt_simple_link_test_code='int main(int, char *[[]]) { return(0); }' # ltmain only uses $CC for tagged configurations so make sure $CC is set. _LT_TAG_COMPILER # save warnings/boilerplate of simple test code _LT_COMPILER_BOILERPLATE _LT_LINKER_BOILERPLATE # Allow CC to be a program name with arguments. lt_save_CC=$CC lt_save_CFLAGS=$CFLAGS lt_save_LD=$LD lt_save_GCC=$GCC GCC=$GXX lt_save_with_gnu_ld=$with_gnu_ld lt_save_path_LD=$lt_cv_path_LD if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx else $as_unset lt_cv_prog_gnu_ld fi if test -n "${lt_cv_path_LDCXX+set}"; then lt_cv_path_LD=$lt_cv_path_LDCXX else $as_unset lt_cv_path_LD fi test -z "${LDCXX+set}" || LD=$LDCXX CC=${CXX-"c++"} CFLAGS=$CXXFLAGS compiler=$CC _LT_TAGVAR(compiler, $1)=$CC _LT_CC_BASENAME([$compiler]) if test -n "$compiler"; then # We don't want -fno-exception when compiling C++ code, so set the # no_builtin_flag separately if test yes = "$GXX"; then _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' else _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= fi if test yes = "$GXX"; then # Set up default GNU C++ configuration LT_PATH_LD # Check if GNU C++ uses GNU ld as the underlying linker, since the # archiving commands below assume that GNU ld is being used. if test yes = "$with_gnu_ld"; then _LT_TAGVAR(archive_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' # If archive_cmds runs LD, not CC, wlarc should be empty # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to # investigate it a little bit more. (MM) wlarc='$wl' # ancient GNU ld didn't support --whole-archive et. al. if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' else _LT_TAGVAR(whole_archive_flag_spec, $1)= fi else with_gnu_ld=no wlarc= # A generic and very simple default shared library creation # command for GNU C++ for the case where it uses the native # linker, instead of GNU ld. If possible, this setting should # overridden to take advantage of the native linker features on # the platform it is being used on. _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' fi # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " [[-]]L"' else GXX=no with_gnu_ld=no wlarc= fi # PORTME: fill in a description of your system's C++ link characteristics AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) _LT_TAGVAR(ld_shlibs, $1)=yes case $host_os in aix3*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; aix[[4-9]]*) if test ia64 = "$host_cpu"; then # On IA64, the linker does run time linking by default, so we don't # have to do anything special. aix_use_runtimelinking=no exp_sym_flag='-Bexport' no_entry_flag= else aix_use_runtimelinking=no # Test if we are trying to use run time linking or normal # AIX style linking. If -brtl is somewhere in LDFLAGS, we # have runtime linking enabled, and use it for executables. # For shared libraries, we enable/disable runtime linking # depending on the kind of the shared library created - # when "with_aix_soname,aix_use_runtimelinking" is: # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables # "aix,yes" lib.so shared, rtl:yes, for executables # lib.a static archive # "both,no" lib.so.V(shr.o) shared, rtl:yes # lib.a(lib.so.V) shared, rtl:no, for executables # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a(lib.so.V) shared, rtl:no # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a static archive case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) for ld_flag in $LDFLAGS; do case $ld_flag in *-brtl*) aix_use_runtimelinking=yes break ;; esac done if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then # With aix-soname=svr4, we create the lib.so.V shared archives only, # so we don't have lib.a shared libs to link our executables. # We have to force runtime linking in this case. aix_use_runtimelinking=yes LDFLAGS="$LDFLAGS -Wl,-brtl" fi ;; esac exp_sym_flag='-bexport' no_entry_flag='-bnoentry' fi # When large executables or shared objects are built, AIX ld can # have problems creating the table of contents. If linking a library # or program results in "error TOC overflow" add -mminimal-toc to # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. _LT_TAGVAR(archive_cmds, $1)='' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_direct_absolute, $1)=yes _LT_TAGVAR(hardcode_libdir_separator, $1)=':' _LT_TAGVAR(link_all_deplibs, $1)=yes _LT_TAGVAR(file_list_spec, $1)='$wl-f,' case $with_aix_soname,$aix_use_runtimelinking in aix,*) ;; # no import file svr4,* | *,yes) # use import file # The Import File defines what to hardcode. _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=no ;; esac if test yes = "$GXX"; then case $host_os in aix4.[[012]]|aix4.[[012]].*) # We only want to do this on AIX 4.2 and lower, the check # below for broken collect2 doesn't work under 4.3+ collect2name=`$CC -print-prog-name=collect2` if test -f "$collect2name" && strings "$collect2name" | $GREP resolve_lib_name >/dev/null then # We have reworked collect2 : else # We have old collect2 _LT_TAGVAR(hardcode_direct, $1)=unsupported # It fails to find uninstalled libraries when the uninstalled # path is not listed in the libpath. Setting hardcode_minus_L # to unsupported forces relinking _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)= fi esac shared_flag='-shared' if test yes = "$aix_use_runtimelinking"; then shared_flag=$shared_flag' $wl-G' fi # Need to ensure runtime linking is disabled for the traditional # shared library, or the linker may eventually find shared libraries # /with/ Import File - we do not want to mix them. shared_flag_aix='-shared' shared_flag_svr4='-shared $wl-G' else # not using gcc if test ia64 = "$host_cpu"; then # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release # chokes on -Wl,-G. The following line is correct: shared_flag='-G' else if test yes = "$aix_use_runtimelinking"; then shared_flag='$wl-G' else shared_flag='$wl-bM:SRE' fi shared_flag_aix='$wl-bM:SRE' shared_flag_svr4='$wl-G' fi fi _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' # It seems that -bexpall does not export symbols beginning with # underscore (_), so it is better to generate a list of symbols to # export. _LT_TAGVAR(always_export_symbols, $1)=yes if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then # Warning - without using the other runtime loading flags (-brtl), # -berok will link without error, but may produce a broken library. # The "-G" linker flag allows undefined symbols. _LT_TAGVAR(no_undefined_flag, $1)='-bernotok' # Determine the default libpath from the value encoded in an empty # executable. _LT_SYS_MODULE_PATH_AIX([$1]) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag else if test ia64 = "$host_cpu"; then _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" else # Determine the default libpath from the value encoded in an # empty executable. _LT_SYS_MODULE_PATH_AIX([$1]) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" # Warning - without using the other run time loading flags, # -berok will link without error, but may produce a broken library. _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' if test yes = "$with_gnu_ld"; then # We only use this code for GNU lds that support --whole-archive. _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' else # Exported symbols can be pulled into shared objects from archives _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' fi _LT_TAGVAR(archive_cmds_need_lc, $1)=yes _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' # -brtl affects multiple linker settings, -berok does not and is overridden later compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' if test svr4 != "$with_aix_soname"; then # This is similar to how AIX traditionally builds its shared # libraries. Need -bnortl late, we may have -brtl in LDFLAGS. _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' fi if test aix != "$with_aix_soname"; then _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' else # used by -dlpreopen to get the symbols _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' fi _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' fi fi ;; beos*) if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then _LT_TAGVAR(allow_undefined_flag, $1)=unsupported # Joseph Beckenbach says some releases of gcc # support --undefined. This deserves some investigation. FIXME _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; chorus*) case $cc_basename in *) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; esac ;; cygwin* | mingw* | windows* | pw32* | cegcc*) case $GXX,$cc_basename in ,cl* | no,cl* | ,icl* | no,icl*) # Native MSVC or ICC # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' _LT_TAGVAR(allow_undefined_flag, $1)=unsupported _LT_TAGVAR(always_export_symbols, $1)=yes _LT_TAGVAR(file_list_spec, $1)='@' # Tell ltmain to make .lib files, not .a files. libext=lib # Tell ltmain to make .dll files, not .so files. shrext_cmds=.dll # FIXME: Setting linknames here is a bad hack. _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then cp "$export_symbols" "$output_objdir/$soname.def"; echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; else $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; fi~ $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ linknames=' # The linker will not automatically build a static lib if we build a DLL. # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes # Don't use ranlib _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ lt_tool_outputfile="@TOOL_OUTPUT@"~ case $lt_outputfile in *.exe|*.EXE) ;; *) lt_outputfile=$lt_outputfile.exe lt_tool_outputfile=$lt_tool_outputfile.exe ;; esac~ func_to_tool_file "$lt_outputfile"~ if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; $RM "$lt_outputfile.manifest"; fi' ;; *) # g++ # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, # as there is no search path for DLLs. _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' _LT_TAGVAR(allow_undefined_flag, $1)=unsupported _LT_TAGVAR(always_export_symbols, $1)=no _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes _LT_TAGVAR(file_list_spec, $1)='@' if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' # If the export-symbols file already is a .def file, use it as # is; otherwise, prepend EXPORTS... _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then cp $export_symbols $output_objdir/$soname.def; else echo EXPORTS > $output_objdir/$soname.def; cat $export_symbols >> $output_objdir/$soname.def; fi~ $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; esac ;; darwin* | rhapsody*) _LT_DARWIN_LINKER_FEATURES($1) ;; os2*) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' _LT_TAGVAR(hardcode_minus_L, $1)=yes _LT_TAGVAR(allow_undefined_flag, $1)=unsupported shrext_cmds=.dll _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ prefix_cmds="$SED"~ if test EXPORTS = "`$SED 1q $export_symbols`"; then prefix_cmds="$prefix_cmds -e 1d"; fi~ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' _LT_TAGVAR(old_archive_from_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes _LT_TAGVAR(file_list_spec, $1)='@' ;; dgux*) case $cc_basename in ec++*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; ghcx*) # Green Hills C++ Compiler # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; *) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; esac ;; freebsd2.*) # C++ shared libraries reported to be fairly broken before # switch to ELF _LT_TAGVAR(ld_shlibs, $1)=no ;; freebsd-elf*) _LT_TAGVAR(archive_cmds_need_lc, $1)=no ;; freebsd* | dragonfly* | midnightbsd*) # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF # conventions _LT_TAGVAR(ld_shlibs, $1)=yes ;; haiku*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(link_all_deplibs, $1)=no ;; hpux9*) _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, # but as the default # location of the library. case $cc_basename in CC*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; aCC*) _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -b $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP "[[-]]L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' ;; *) if test yes = "$GXX"; then _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' else # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no fi ;; esac ;; hpux10*|hpux11*) if test no = "$with_gnu_ld"; then _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: case $host_cpu in hppa*64*|ia64*) ;; *) _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' ;; esac fi case $host_cpu in hppa*64*|ia64*) _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no ;; *) _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_direct_absolute, $1)=yes _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, # but as the default # location of the library. ;; esac case $cc_basename in CC*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; aCC*) case $host_cpu in hppa*64*) _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; ia64*) _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; *) _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; esac # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP " [[-]]L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' ;; *) if test yes = "$GXX"; then if test no = "$with_gnu_ld"; then case $host_cpu in hppa*64*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; ia64*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; *) _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; esac fi else # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no fi ;; esac ;; interix[[3-9]]*) _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. # Instead, shared libraries are loaded at an image base (0x10000000 by # default) and relocated if they conflict, which is a slow very memory # consuming and fragmenting process. To avoid this, we pick a random, # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link # time. Moving up from 0x10000000 also allows more sbrk(2) space. _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$SED "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' ;; irix5* | irix6*) case $cc_basename in CC*) # SGI C++ _LT_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' # Archives containing C++ object files must be created using # "CC -ar", where "CC" is the IRIX C++ compiler. This is # necessary to make sure instantiated templates are included # in the archive. _LT_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs' ;; *) if test yes = "$GXX"; then if test no = "$with_gnu_ld"; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' else _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` -o $lib' fi fi _LT_TAGVAR(link_all_deplibs, $1)=yes ;; esac _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: _LT_TAGVAR(inherit_rpath, $1)=yes ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in KCC*) # Kuck and Associates, Inc. (KAI) C++ Compiler # KCC will only create a shared library if the output file # ends with ".so" (or ".sl" for HP-UX), so rename the library # to its proper name (with version) after linking. _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib $wl-retain-symbols-file,$export_symbols; mv \$templib $lib' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' # Archives containing C++ object files must be created using # "CC -Bstatic", where "CC" is the KAI C++ compiler. _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' ;; icpc* | ecpc* ) # Intel C++ with_gnu_ld=yes # version 8.0 and above of icpc choke on multiply defined symbols # if we add $predep_objects and $postdep_objects, however 7.1 and # earlier do not add the objects themselves. case `$CC -V 2>&1` in *"Version 7."*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; *) # Version 8.0 or newer tmp_idyn= case $host_cpu in ia64*) tmp_idyn=' -i_dynamic';; esac _LT_TAGVAR(archive_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; esac _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' ;; pgCC* | pgcpp*) # Portland Group C++ compiler case `$CC -V` in *pgCC\ [[1-5]].* | *pgcpp\ [[1-5]].*) _LT_TAGVAR(prelink_cmds, $1)='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~ compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"' _LT_TAGVAR(old_archive_cmds, $1)='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~ $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~ $RANLIB $oldlib' _LT_TAGVAR(archive_cmds, $1)='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; *) # Version 6 and above use weak symbols _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; esac _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl--rpath $wl$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' ;; cxx*) # Compaq C++ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib $wl-retain-symbols-file $wl$export_symbols' runpath_var=LD_RUN_PATH _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed' ;; xl* | mpixl* | bgxl*) # IBM XL 8.0 on PPC, with GNU ld _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' _LT_TAGVAR(archive_cmds, $1)='$CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' if test yes = "$supports_anon_versioning"; then _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ echo "local: *; };" >> $output_objdir/$libname.ver~ $CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' fi ;; *) case `$CC -V 2>&1 | $SED 5q` in *Sun\ C*) # Sun C++ 5.9 _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file $wl$export_symbols' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' _LT_TAGVAR(compiler_needs_object, $1)=yes # Not sure whether something based on # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 # would be better. output_verbose_link_cmd='func_echo_all' # Archives containing C++ object files must be created using # "CC -xar", where "CC" is the Sun C++ compiler. This is # necessary to make sure instantiated templates are included # in the archive. _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' ;; esac ;; esac ;; lynxos*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; m88k*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; mvs*) case $cc_basename in cxx*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; *) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; esac ;; *-mlibc) _LT_TAGVAR(ld_shlibs, $1)=yes ;; netbsd*) if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' wlarc= _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no fi # Workaround some broken pre-1.5 toolchains output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' ;; *nto* | *qnx*) _LT_TAGVAR(ld_shlibs, $1)=yes ;; openbsd*) if test -f /usr/libexec/ld.so; then _LT_TAGVAR(hardcode_direct, $1)=yes _LT_TAGVAR(hardcode_shlibpath_var, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=yes _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`"; then _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file,$export_symbols -o $lib' _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' fi output_verbose_link_cmd=func_echo_all else _LT_TAGVAR(ld_shlibs, $1)=no fi ;; osf3* | osf4* | osf5*) case $cc_basename in KCC*) # Kuck and Associates, Inc. (KAI) C++ Compiler # KCC will only create a shared library if the output file # ends with ".so" (or ".sl" for HP-UX), so rename the library # to its proper name (with version) after linking. _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: # Archives containing C++ object files must be created using # the KAI C++ compiler. case $host in osf3*) _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' ;; *) _LT_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' ;; esac ;; RCC*) # Rational C++ 2.4.1 # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; cxx*) case $host in osf3*) _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $soname `test -n "$verstring" && func_echo_all "$wl-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' ;; *) _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ echo "-hidden">> $lib.exp~ $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname $wl-input $wl$lib.exp `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~ $RM $lib.exp' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' ;; esac _LT_TAGVAR(hardcode_libdir_separator, $1)=: # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' ;; *) if test yes,no = "$GXX,$with_gnu_ld"; then _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' case $host in osf3*) _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' ;; *) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' ;; esac _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=: # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " [[-]]L"' else # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no fi ;; esac ;; psos*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; serenity*) ;; sunos4*) case $cc_basename in CC*) # Sun C++ 4.x # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; lcc*) # Lucid # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; *) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; esac ;; solaris*) case $cc_basename in CC* | sunCC*) # Sun C++ 4.2, 5.x and Centerline C++ _LT_TAGVAR(archive_cmds_need_lc,$1)=yes _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -G$allow_undefined_flag $wl-M $wl$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' _LT_TAGVAR(hardcode_shlibpath_var, $1)=no case $host_os in solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; *) # The compiler driver will combine and reorder linker options, # but understands '-z linker_flag'. # Supported since Solaris 2.6 (maybe 2.5.1?) _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' ;; esac _LT_TAGVAR(link_all_deplibs, $1)=yes output_verbose_link_cmd='func_echo_all' # Archives containing C++ object files must be created using # "CC -xar", where "CC" is the Sun C++ compiler. This is # necessary to make sure instantiated templates are included # in the archive. _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' ;; gcx*) # Green Hills C++ Compiler _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' # The C++ compiler must be used to create the archive. _LT_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs' ;; *) # GNU C++ compiler with Solaris linker if test yes,no = "$GXX,$with_gnu_ld"; then _LT_TAGVAR(no_undefined_flag, $1)=' $wl-z ${wl}defs' if $CC --version | $GREP -v '^2\.7' > /dev/null; then _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -shared $pic_flag -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " [[-]]L"' else # g++ 2.7 appears to require '-G' NOT '-shared' on this # platform. _LT_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -G -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " [[-]]L"' fi _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $wl$libdir' case $host_os in solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; *) _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' ;; esac fi ;; esac ;; sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no runpath_var='LD_RUN_PATH' case $cc_basename in CC*) _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' ;; *) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' ;; esac ;; sysv5* | sco3.2v5* | sco5v6*) # Note: We CANNOT use -z defs as we might desire, because we do not # link with -lc, and that would cause any symbols used from libc to # always be unresolved, which means just about no library would # ever link correctly. If we're not using GNU ld we use -z text # though, which does catch some bad symbols but isn't as heavy-handed # as -z defs. _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(hardcode_shlibpath_var, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' _LT_TAGVAR(hardcode_libdir_separator, $1)=':' _LT_TAGVAR(link_all_deplibs, $1)=yes _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' runpath_var='LD_RUN_PATH' case $cc_basename in CC*) _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(old_archive_cmds, $1)='$CC -Tprelink_objects $oldobjs~ '"$_LT_TAGVAR(old_archive_cmds, $1)" _LT_TAGVAR(reload_cmds, $1)='$CC -Tprelink_objects $reload_objs~ '"$_LT_TAGVAR(reload_cmds, $1)" ;; *) _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' ;; esac ;; tandem*) case $cc_basename in NCC*) # NonStop-UX NCC 3.20 # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; *) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; esac ;; vxworks*) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; *) # FIXME: insert proper C++ library support _LT_TAGVAR(ld_shlibs, $1)=no ;; esac AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no _LT_TAGVAR(GCC, $1)=$GXX _LT_TAGVAR(LD, $1)=$LD ## CAVEAT EMPTOR: ## There is no encapsulation within the following macros, do not change ## the running order or otherwise move them around unless you know exactly ## what you are doing... _LT_SYS_HIDDEN_LIBDEPS($1) _LT_COMPILER_PIC($1) _LT_COMPILER_C_O($1) _LT_COMPILER_FILE_LOCKS($1) _LT_LINKER_SHLIBS($1) _LT_SYS_DYNAMIC_LINKER($1) _LT_LINKER_HARDCODE_LIBPATH($1) _LT_CONFIG($1) fi # test -n "$compiler" CC=$lt_save_CC CFLAGS=$lt_save_CFLAGS LDCXX=$LD LD=$lt_save_LD GCC=$lt_save_GCC with_gnu_ld=$lt_save_with_gnu_ld lt_cv_path_LDCXX=$lt_cv_path_LD lt_cv_path_LD=$lt_save_path_LD lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld fi # test yes != "$_lt_caught_CXX_error" AC_LANG_POP ])# _LT_LANG_CXX_CONFIG # _LT_FUNC_STRIPNAME_CNF # ---------------------- # func_stripname_cnf prefix suffix name # strip PREFIX and SUFFIX off of NAME. # PREFIX and SUFFIX must not contain globbing or regex special # characters, hashes, percent signs, but SUFFIX may contain a leading # dot (in which case that matches only a dot). # # This function is identical to the (non-XSI) version of func_stripname, # except this one can be used by m4 code that may be executed by configure, # rather than the libtool script. m4_defun([_LT_FUNC_STRIPNAME_CNF],[dnl AC_REQUIRE([_LT_DECL_SED]) AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH]) func_stripname_cnf () { case @S|@2 in .*) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%\\\\@S|@2\$%%"`;; *) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%@S|@2\$%%"`;; esac } # func_stripname_cnf ])# _LT_FUNC_STRIPNAME_CNF # _LT_SYS_HIDDEN_LIBDEPS([TAGNAME]) # --------------------------------- # Figure out "hidden" library dependencies from verbose # compiler output when linking a shared library. # Parse the compiler output and extract the necessary # objects, libraries and library flags. m4_defun([_LT_SYS_HIDDEN_LIBDEPS], [m4_require([_LT_FILEUTILS_DEFAULTS])dnl AC_REQUIRE([_LT_FUNC_STRIPNAME_CNF])dnl # Dependencies to place before and after the object being linked: _LT_TAGVAR(predep_objects, $1)= _LT_TAGVAR(postdep_objects, $1)= _LT_TAGVAR(predeps, $1)= _LT_TAGVAR(postdeps, $1)= _LT_TAGVAR(compiler_lib_search_path, $1)= dnl we can't use the lt_simple_compile_test_code here, dnl because it contains code intended for an executable, dnl not a library. It's possible we should let each dnl tag define a new lt_????_link_test_code variable, dnl but it's only used here... m4_if([$1], [], [cat > conftest.$ac_ext <<_LT_EOF int a; void foo (void) { a = 0; } _LT_EOF ], [$1], [CXX], [cat > conftest.$ac_ext <<_LT_EOF class Foo { public: Foo (void) { a = 0; } private: int a; }; _LT_EOF ], [$1], [F77], [cat > conftest.$ac_ext <<_LT_EOF subroutine foo implicit none integer*4 a a=0 return end _LT_EOF ], [$1], [FC], [cat > conftest.$ac_ext <<_LT_EOF subroutine foo implicit none integer a a=0 return end _LT_EOF ], [$1], [GCJ], [cat > conftest.$ac_ext <<_LT_EOF public class foo { private int a; public void bar (void) { a = 0; } }; _LT_EOF ], [$1], [GO], [cat > conftest.$ac_ext <<_LT_EOF package foo func foo() { } _LT_EOF ]) _lt_libdeps_save_CFLAGS=$CFLAGS case "$CC $CFLAGS " in #( *\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;; *\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;; *\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;; esac dnl Parse the compiler output and extract the necessary dnl objects, libraries and library flags. if AC_TRY_EVAL(ac_compile); then # Parse the compiler output and extract the necessary # objects, libraries and library flags. # Sentinel used to keep track of whether or not we are before # the conftest object file. pre_test_object_deps_done=no for p in `eval "$output_verbose_link_cmd"`; do case $prev$p in -L* | -R* | -l*) # Some compilers place space between "-{L,R,l}" and the path. # Remove the space. if test x-L = x"$p" || test x-R = x"$p" || test x-l = x"$p"; then prev=$p continue fi # Expand the sysroot to ease extracting the directories later. if test -z "$prev"; then case $p in -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;; -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;; -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;; esac fi case $p in =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;; esac if test no = "$pre_test_object_deps_done"; then case $prev in -L | -R) # Internal compiler library paths should come after those # provided the user. The postdeps already come after the # user supplied libs so there is no need to process them. if test -z "$_LT_TAGVAR(compiler_lib_search_path, $1)"; then _LT_TAGVAR(compiler_lib_search_path, $1)=$prev$p else _LT_TAGVAR(compiler_lib_search_path, $1)="${_LT_TAGVAR(compiler_lib_search_path, $1)} $prev$p" fi ;; # The "-l" case would never come before the object being # linked, so don't bother handling this case. esac else if test -z "$_LT_TAGVAR(postdeps, $1)"; then _LT_TAGVAR(postdeps, $1)=$prev$p else _LT_TAGVAR(postdeps, $1)="${_LT_TAGVAR(postdeps, $1)} $prev$p" fi fi prev= ;; *.lto.$objext) ;; # Ignore GCC LTO objects *.$objext) # This assumes that the test object file only shows up # once in the compiler output. if test "$p" = "conftest.$objext"; then pre_test_object_deps_done=yes continue fi if test no = "$pre_test_object_deps_done"; then if test -z "$_LT_TAGVAR(predep_objects, $1)"; then _LT_TAGVAR(predep_objects, $1)=$p else _LT_TAGVAR(predep_objects, $1)="$_LT_TAGVAR(predep_objects, $1) $p" fi else if test -z "$_LT_TAGVAR(postdep_objects, $1)"; then _LT_TAGVAR(postdep_objects, $1)=$p else _LT_TAGVAR(postdep_objects, $1)="$_LT_TAGVAR(postdep_objects, $1) $p" fi fi ;; *) ;; # Ignore the rest. esac done # Clean up. rm -f a.out a.exe else echo "libtool.m4: error: problem compiling $1 test program" fi $RM -f confest.$objext CFLAGS=$_lt_libdeps_save_CFLAGS # PORTME: override above test on systems where it is broken m4_if([$1], [CXX], [case $host_os in interix[[3-9]]*) # Interix 3.5 installs completely hosed .la files for C++, so rather than # hack all around it, let's just trust "g++" to DTRT. _LT_TAGVAR(predep_objects,$1)= _LT_TAGVAR(postdep_objects,$1)= _LT_TAGVAR(postdeps,$1)= ;; esac ]) case " $_LT_TAGVAR(postdeps, $1) " in *" -lc "*) _LT_TAGVAR(archive_cmds_need_lc, $1)=no ;; esac _LT_TAGVAR(compiler_lib_search_dirs, $1)= if test -n "${_LT_TAGVAR(compiler_lib_search_path, $1)}"; then _LT_TAGVAR(compiler_lib_search_dirs, $1)=`echo " ${_LT_TAGVAR(compiler_lib_search_path, $1)}" | $SED -e 's! -L! !g' -e 's!^ !!'` fi _LT_TAGDECL([], [compiler_lib_search_dirs], [1], [The directories searched by this compiler when creating a shared library]) _LT_TAGDECL([], [predep_objects], [1], [Dependencies to place before and after the objects being linked to create a shared library]) _LT_TAGDECL([], [postdep_objects], [1]) _LT_TAGDECL([], [predeps], [1]) _LT_TAGDECL([], [postdeps], [1]) _LT_TAGDECL([], [compiler_lib_search_path], [1], [The library search path used internally by the compiler when linking a shared library]) ])# _LT_SYS_HIDDEN_LIBDEPS # _LT_LANG_F77_CONFIG([TAG]) # -------------------------- # Ensure that the configuration variables for a Fortran 77 compiler are # suitably defined. These variables are subsequently used by _LT_CONFIG # to write the compiler configuration to 'libtool'. m4_defun([_LT_LANG_F77_CONFIG], [AC_LANG_PUSH(Fortran 77) if test -z "$F77" || test no = "$F77"; then _lt_disable_F77=yes fi _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(allow_undefined_flag, $1)= _LT_TAGVAR(always_export_symbols, $1)=no _LT_TAGVAR(archive_expsym_cmds, $1)= _LT_TAGVAR(export_dynamic_flag_spec, $1)= _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= _LT_TAGVAR(hardcode_libdir_separator, $1)= _LT_TAGVAR(hardcode_minus_L, $1)=no _LT_TAGVAR(hardcode_automatic, $1)=no _LT_TAGVAR(inherit_rpath, $1)=no _LT_TAGVAR(module_cmds, $1)= _LT_TAGVAR(module_expsym_cmds, $1)= _LT_TAGVAR(link_all_deplibs, $1)=unknown _LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds _LT_TAGVAR(reload_flag, $1)=$reload_flag _LT_TAGVAR(reload_cmds, $1)=$reload_cmds _LT_TAGVAR(no_undefined_flag, $1)= _LT_TAGVAR(whole_archive_flag_spec, $1)= _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no # Source file extension for f77 test sources. ac_ext=f # Object file extension for compiled f77 test sources. objext=o _LT_TAGVAR(objext, $1)=$objext # No sense in running all these tests if we already determined that # the F77 compiler isn't working. Some variables (like enable_shared) # are currently assumed to apply to all compilers on this platform, # and will be corrupted by setting them based on a non-working compiler. if test yes != "$_lt_disable_F77"; then # Code to be used in simple compile tests lt_simple_compile_test_code="\ subroutine t return end " # Code to be used in simple link tests lt_simple_link_test_code="\ program t end " # ltmain only uses $CC for tagged configurations so make sure $CC is set. _LT_TAG_COMPILER # save warnings/boilerplate of simple test code _LT_COMPILER_BOILERPLATE _LT_LINKER_BOILERPLATE # Allow CC to be a program name with arguments. lt_save_CC=$CC lt_save_GCC=$GCC lt_save_CFLAGS=$CFLAGS CC=${F77-"f77"} CFLAGS=$FFLAGS compiler=$CC _LT_TAGVAR(compiler, $1)=$CC _LT_CC_BASENAME([$compiler]) GCC=$G77 if test -n "$compiler"; then AC_MSG_CHECKING([if libtool supports shared libraries]) AC_MSG_RESULT([$can_build_shared]) AC_MSG_CHECKING([whether to build shared libraries]) test no = "$can_build_shared" && enable_shared=no # On AIX, shared libraries and static libraries use the same namespace, and # are all built from PIC. case $host_os in aix3*) test yes = "$enable_shared" && enable_static=no if test -n "$RANLIB"; then archive_cmds="$archive_cmds~\$RANLIB \$lib" postinstall_cmds='$RANLIB $lib' fi ;; aix[[4-9]]*) if test ia64 != "$host_cpu"; then case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in yes,aix,yes) ;; # shared object as lib.so file only yes,svr4,*) ;; # shared object as lib.so archive member only yes,*) enable_static=no ;; # shared object in lib.a archive as well esac fi ;; esac AC_MSG_RESULT([$enable_shared]) AC_MSG_CHECKING([whether to build static libraries]) # Make sure either enable_shared or enable_static is yes. test yes = "$enable_shared" || enable_static=yes AC_MSG_RESULT([$enable_static]) _LT_TAGVAR(GCC, $1)=$G77 _LT_TAGVAR(LD, $1)=$LD ## CAVEAT EMPTOR: ## There is no encapsulation within the following macros, do not change ## the running order or otherwise move them around unless you know exactly ## what you are doing... _LT_COMPILER_PIC($1) _LT_COMPILER_C_O($1) _LT_COMPILER_FILE_LOCKS($1) _LT_LINKER_SHLIBS($1) _LT_SYS_DYNAMIC_LINKER($1) _LT_LINKER_HARDCODE_LIBPATH($1) _LT_CONFIG($1) fi # test -n "$compiler" GCC=$lt_save_GCC CC=$lt_save_CC CFLAGS=$lt_save_CFLAGS fi # test yes != "$_lt_disable_F77" AC_LANG_POP ])# _LT_LANG_F77_CONFIG # _LT_LANG_FC_CONFIG([TAG]) # ------------------------- # Ensure that the configuration variables for a Fortran compiler are # suitably defined. These variables are subsequently used by _LT_CONFIG # to write the compiler configuration to 'libtool'. m4_defun([_LT_LANG_FC_CONFIG], [AC_LANG_PUSH(Fortran) if test -z "$FC" || test no = "$FC"; then _lt_disable_FC=yes fi _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(allow_undefined_flag, $1)= _LT_TAGVAR(always_export_symbols, $1)=no _LT_TAGVAR(archive_expsym_cmds, $1)= _LT_TAGVAR(export_dynamic_flag_spec, $1)= _LT_TAGVAR(hardcode_direct, $1)=no _LT_TAGVAR(hardcode_direct_absolute, $1)=no _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= _LT_TAGVAR(hardcode_libdir_separator, $1)= _LT_TAGVAR(hardcode_minus_L, $1)=no _LT_TAGVAR(hardcode_automatic, $1)=no _LT_TAGVAR(inherit_rpath, $1)=no _LT_TAGVAR(module_cmds, $1)= _LT_TAGVAR(module_expsym_cmds, $1)= _LT_TAGVAR(link_all_deplibs, $1)=unknown _LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds _LT_TAGVAR(reload_flag, $1)=$reload_flag _LT_TAGVAR(reload_cmds, $1)=$reload_cmds _LT_TAGVAR(no_undefined_flag, $1)= _LT_TAGVAR(whole_archive_flag_spec, $1)= _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no # Source file extension for fc test sources. ac_ext=${ac_fc_srcext-f} # Object file extension for compiled fc test sources. objext=o _LT_TAGVAR(objext, $1)=$objext # No sense in running all these tests if we already determined that # the FC compiler isn't working. Some variables (like enable_shared) # are currently assumed to apply to all compilers on this platform, # and will be corrupted by setting them based on a non-working compiler. if test yes != "$_lt_disable_FC"; then # Code to be used in simple compile tests lt_simple_compile_test_code="\ subroutine t return end " # Code to be used in simple link tests lt_simple_link_test_code="\ program t end " # ltmain only uses $CC for tagged configurations so make sure $CC is set. _LT_TAG_COMPILER # save warnings/boilerplate of simple test code _LT_COMPILER_BOILERPLATE _LT_LINKER_BOILERPLATE # Allow CC to be a program name with arguments. lt_save_CC=$CC lt_save_GCC=$GCC lt_save_CFLAGS=$CFLAGS CC=${FC-"f95"} CFLAGS=$FCFLAGS compiler=$CC GCC=$ac_cv_fc_compiler_gnu _LT_TAGVAR(compiler, $1)=$CC _LT_CC_BASENAME([$compiler]) if test -n "$compiler"; then AC_MSG_CHECKING([if libtool supports shared libraries]) AC_MSG_RESULT([$can_build_shared]) AC_MSG_CHECKING([whether to build shared libraries]) test no = "$can_build_shared" && enable_shared=no # On AIX, shared libraries and static libraries use the same namespace, and # are all built from PIC. case $host_os in aix3*) test yes = "$enable_shared" && enable_static=no if test -n "$RANLIB"; then archive_cmds="$archive_cmds~\$RANLIB \$lib" postinstall_cmds='$RANLIB $lib' fi ;; aix[[4-9]]*) if test ia64 != "$host_cpu"; then case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in yes,aix,yes) ;; # shared object as lib.so file only yes,svr4,*) ;; # shared object as lib.so archive member only yes,*) enable_static=no ;; # shared object in lib.a archive as well esac fi ;; esac AC_MSG_RESULT([$enable_shared]) AC_MSG_CHECKING([whether to build static libraries]) # Make sure either enable_shared or enable_static is yes. test yes = "$enable_shared" || enable_static=yes AC_MSG_RESULT([$enable_static]) _LT_TAGVAR(GCC, $1)=$ac_cv_fc_compiler_gnu _LT_TAGVAR(LD, $1)=$LD ## CAVEAT EMPTOR: ## There is no encapsulation within the following macros, do not change ## the running order or otherwise move them around unless you know exactly ## what you are doing... _LT_SYS_HIDDEN_LIBDEPS($1) _LT_COMPILER_PIC($1) _LT_COMPILER_C_O($1) _LT_COMPILER_FILE_LOCKS($1) _LT_LINKER_SHLIBS($1) _LT_SYS_DYNAMIC_LINKER($1) _LT_LINKER_HARDCODE_LIBPATH($1) _LT_CONFIG($1) fi # test -n "$compiler" GCC=$lt_save_GCC CC=$lt_save_CC CFLAGS=$lt_save_CFLAGS fi # test yes != "$_lt_disable_FC" AC_LANG_POP ])# _LT_LANG_FC_CONFIG # _LT_LANG_GCJ_CONFIG([TAG]) # -------------------------- # Ensure that the configuration variables for the GNU Java Compiler compiler # are suitably defined. These variables are subsequently used by _LT_CONFIG # to write the compiler configuration to 'libtool'. m4_defun([_LT_LANG_GCJ_CONFIG], [AC_REQUIRE([LT_PROG_GCJ])dnl AC_LANG_SAVE # Source file extension for Java test sources. ac_ext=java # Object file extension for compiled Java test sources. objext=o _LT_TAGVAR(objext, $1)=$objext # Code to be used in simple compile tests lt_simple_compile_test_code="class foo {}" # Code to be used in simple link tests lt_simple_link_test_code='public class conftest { public static void main(String[[]] argv) {}; }' # ltmain only uses $CC for tagged configurations so make sure $CC is set. _LT_TAG_COMPILER # save warnings/boilerplate of simple test code _LT_COMPILER_BOILERPLATE _LT_LINKER_BOILERPLATE # Allow CC to be a program name with arguments. lt_save_CC=$CC lt_save_CFLAGS=$CFLAGS lt_save_GCC=$GCC GCC=yes CC=${GCJ-"gcj"} CFLAGS=$GCJFLAGS compiler=$CC _LT_TAGVAR(compiler, $1)=$CC _LT_TAGVAR(LD, $1)=$LD _LT_CC_BASENAME([$compiler]) # GCJ did not exist at the time GCC didn't implicitly link libc in. _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds _LT_TAGVAR(reload_flag, $1)=$reload_flag _LT_TAGVAR(reload_cmds, $1)=$reload_cmds if test -n "$compiler"; then _LT_COMPILER_NO_RTTI($1) _LT_COMPILER_PIC($1) _LT_COMPILER_C_O($1) _LT_COMPILER_FILE_LOCKS($1) _LT_LINKER_SHLIBS($1) _LT_LINKER_HARDCODE_LIBPATH($1) _LT_CONFIG($1) fi AC_LANG_RESTORE GCC=$lt_save_GCC CC=$lt_save_CC CFLAGS=$lt_save_CFLAGS ])# _LT_LANG_GCJ_CONFIG # _LT_LANG_GO_CONFIG([TAG]) # -------------------------- # Ensure that the configuration variables for the GNU Go compiler # are suitably defined. These variables are subsequently used by _LT_CONFIG # to write the compiler configuration to 'libtool'. m4_defun([_LT_LANG_GO_CONFIG], [AC_REQUIRE([LT_PROG_GO])dnl AC_LANG_SAVE # Source file extension for Go test sources. ac_ext=go # Object file extension for compiled Go test sources. objext=o _LT_TAGVAR(objext, $1)=$objext # Code to be used in simple compile tests lt_simple_compile_test_code="package main; func main() { }" # Code to be used in simple link tests lt_simple_link_test_code='package main; func main() { }' # ltmain only uses $CC for tagged configurations so make sure $CC is set. _LT_TAG_COMPILER # save warnings/boilerplate of simple test code _LT_COMPILER_BOILERPLATE _LT_LINKER_BOILERPLATE # Allow CC to be a program name with arguments. lt_save_CC=$CC lt_save_CFLAGS=$CFLAGS lt_save_GCC=$GCC GCC=yes CC=${GOC-"gccgo"} CFLAGS=$GOFLAGS compiler=$CC _LT_TAGVAR(compiler, $1)=$CC _LT_TAGVAR(LD, $1)=$LD _LT_CC_BASENAME([$compiler]) # Go did not exist at the time GCC didn't implicitly link libc in. _LT_TAGVAR(archive_cmds_need_lc, $1)=no _LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds _LT_TAGVAR(reload_flag, $1)=$reload_flag _LT_TAGVAR(reload_cmds, $1)=$reload_cmds if test -n "$compiler"; then _LT_COMPILER_NO_RTTI($1) _LT_COMPILER_PIC($1) _LT_COMPILER_C_O($1) _LT_COMPILER_FILE_LOCKS($1) _LT_LINKER_SHLIBS($1) _LT_LINKER_HARDCODE_LIBPATH($1) _LT_CONFIG($1) fi AC_LANG_RESTORE GCC=$lt_save_GCC CC=$lt_save_CC CFLAGS=$lt_save_CFLAGS ])# _LT_LANG_GO_CONFIG # _LT_LANG_RC_CONFIG([TAG]) # ------------------------- # Ensure that the configuration variables for the Windows resource compiler # are suitably defined. These variables are subsequently used by _LT_CONFIG # to write the compiler configuration to 'libtool'. m4_defun([_LT_LANG_RC_CONFIG], [AC_REQUIRE([LT_PROG_RC])dnl AC_LANG_SAVE # Source file extension for RC test sources. ac_ext=rc # Object file extension for compiled RC test sources. objext=o _LT_TAGVAR(objext, $1)=$objext # Code to be used in simple compile tests lt_simple_compile_test_code='sample MENU { MENUITEM "&Soup", 100, CHECKED }' # Code to be used in simple link tests lt_simple_link_test_code=$lt_simple_compile_test_code # ltmain only uses $CC for tagged configurations so make sure $CC is set. _LT_TAG_COMPILER # save warnings/boilerplate of simple test code _LT_COMPILER_BOILERPLATE _LT_LINKER_BOILERPLATE # Allow CC to be a program name with arguments. lt_save_CC=$CC lt_save_CFLAGS=$CFLAGS lt_save_GCC=$GCC GCC= CC=${RC-"windres"} CFLAGS= compiler=$CC _LT_TAGVAR(compiler, $1)=$CC _LT_CC_BASENAME([$compiler]) _LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes if test -n "$compiler"; then : _LT_CONFIG($1) fi GCC=$lt_save_GCC AC_LANG_RESTORE CC=$lt_save_CC CFLAGS=$lt_save_CFLAGS ])# _LT_LANG_RC_CONFIG # LT_PROG_GCJ # ----------- AC_DEFUN([LT_PROG_GCJ], [m4_ifdef([AC_PROG_GCJ], [AC_PROG_GCJ], [m4_ifdef([A][M_PROG_GCJ], [A][M_PROG_GCJ], [AC_CHECK_TOOL(GCJ, gcj,) test set = "${GCJFLAGS+set}" || GCJFLAGS="-g -O2" AC_SUBST(GCJFLAGS)])])[]dnl ]) # Old name: AU_ALIAS([LT_AC_PROG_GCJ], [LT_PROG_GCJ]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([LT_AC_PROG_GCJ], []) # LT_PROG_GO # ---------- AC_DEFUN([LT_PROG_GO], [AC_CHECK_TOOL(GOC, gccgo,) ]) # LT_PROG_RC # ---------- AC_DEFUN([LT_PROG_RC], [AC_CHECK_TOOL(RC, windres,) ]) # Old name: AU_ALIAS([LT_AC_PROG_RC], [LT_PROG_RC]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([LT_AC_PROG_RC], []) # _LT_DECL_EGREP # -------------- # If we don't have a new enough Autoconf to choose the best grep # available, choose the one first in the user's PATH. m4_defun([_LT_DECL_EGREP], [AC_REQUIRE([AC_PROG_EGREP])dnl AC_REQUIRE([AC_PROG_FGREP])dnl test -z "$GREP" && GREP=grep _LT_DECL([], [GREP], [1], [A grep program that handles long lines]) _LT_DECL([], [EGREP], [1], [An ERE matcher]) _LT_DECL([], [FGREP], [1], [A literal string matcher]) dnl Non-bleeding-edge autoconf doesn't subst GREP, so do it here too AC_SUBST([GREP]) ]) # _LT_DECL_OBJDUMP # -------------- # If we don't have a new enough Autoconf to choose the best objdump # available, choose the one first in the user's PATH. m4_defun([_LT_DECL_OBJDUMP], [AC_CHECK_TOOL(OBJDUMP, objdump, false) test -z "$OBJDUMP" && OBJDUMP=objdump _LT_DECL([], [OBJDUMP], [1], [An object symbol dumper]) AC_SUBST([OBJDUMP]) ]) # _LT_DECL_DLLTOOL # ---------------- # Ensure DLLTOOL variable is set. m4_defun([_LT_DECL_DLLTOOL], [AC_CHECK_TOOL(DLLTOOL, dlltool, false) test -z "$DLLTOOL" && DLLTOOL=dlltool _LT_DECL([], [DLLTOOL], [1], [DLL creation program]) AC_SUBST([DLLTOOL]) ]) # _LT_DECL_FILECMD # ---------------- # Check for a file(cmd) program that can be used to detect file type and magic m4_defun([_LT_DECL_FILECMD], [AC_CHECK_PROG([FILECMD], [file], [file], [:]) _LT_DECL([], [FILECMD], [1], [A file(cmd) program that detects file types]) ])# _LD_DECL_FILECMD # _LT_DECL_SED # ------------ # Check for a fully-functional sed program, that truncates # as few characters as possible. Prefer GNU sed if found. m4_defun([_LT_DECL_SED], [AC_PROG_SED test -z "$SED" && SED=sed Xsed="$SED -e 1s/^X//" _LT_DECL([], [SED], [1], [A sed program that does not truncate output]) _LT_DECL([], [Xsed], ["\$SED -e 1s/^X//"], [Sed that helps us avoid accidentally triggering echo(1) options like -n]) ])# _LT_DECL_SED dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([LT_AC_PROG_SED], []) # _LT_CHECK_SHELL_FEATURES # ------------------------ # Find out whether the shell is Bourne or XSI compatible, # or has some other useful features. m4_defun([_LT_CHECK_SHELL_FEATURES], [if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then lt_unset=unset else lt_unset=false fi _LT_DECL([], [lt_unset], [0], [whether the shell understands "unset"])dnl # test EBCDIC or ASCII case `echo X|tr X '\101'` in A) # ASCII based system # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr lt_SP2NL='tr \040 \012' lt_NL2SP='tr \015\012 \040\040' ;; *) # EBCDIC based system lt_SP2NL='tr \100 \n' lt_NL2SP='tr \r\n \100\100' ;; esac _LT_DECL([SP2NL], [lt_SP2NL], [1], [turn spaces into newlines])dnl _LT_DECL([NL2SP], [lt_NL2SP], [1], [turn newlines into spaces])dnl ])# _LT_CHECK_SHELL_FEATURES # _LT_PATH_CONVERSION_FUNCTIONS # ----------------------------- # Determine what file name conversion functions should be used by # func_to_host_file (and, implicitly, by func_to_host_path). These are needed # for certain cross-compile configurations and native mingw. m4_defun([_LT_PATH_CONVERSION_FUNCTIONS], [AC_REQUIRE([AC_CANONICAL_HOST])dnl AC_REQUIRE([AC_CANONICAL_BUILD])dnl AC_MSG_CHECKING([how to convert $build file names to $host format]) AC_CACHE_VAL(lt_cv_to_host_file_cmd, [case $host in *-*-mingw* ) case $build in *-*-mingw* | *-*-windows* ) # actually msys lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 ;; *-*-cygwin* ) lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 ;; * ) # otherwise, assume *nix lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 ;; esac ;; *-*-cygwin* ) case $build in *-*-mingw* | *-*-windows* ) # actually msys lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin ;; *-*-cygwin* ) lt_cv_to_host_file_cmd=func_convert_file_noop ;; * ) # otherwise, assume *nix lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin ;; esac ;; * ) # unhandled hosts (and "normal" native builds) lt_cv_to_host_file_cmd=func_convert_file_noop ;; esac ]) to_host_file_cmd=$lt_cv_to_host_file_cmd AC_MSG_RESULT([$lt_cv_to_host_file_cmd]) _LT_DECL([to_host_file_cmd], [lt_cv_to_host_file_cmd], [0], [convert $build file names to $host format])dnl AC_MSG_CHECKING([how to convert $build file names to toolchain format]) AC_CACHE_VAL(lt_cv_to_tool_file_cmd, [#assume ordinary cross tools, or native build. lt_cv_to_tool_file_cmd=func_convert_file_noop case $host in *-*-mingw* | *-*-windows* ) case $build in *-*-mingw* | *-*-windows* ) # actually msys lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 ;; esac ;; esac ]) to_tool_file_cmd=$lt_cv_to_tool_file_cmd AC_MSG_RESULT([$lt_cv_to_tool_file_cmd]) _LT_DECL([to_tool_file_cmd], [lt_cv_to_tool_file_cmd], [0], [convert $build files to toolchain format])dnl ])# _LT_PATH_CONVERSION_FUNCTIONS # Helper functions for option handling. -*- Autoconf -*- # # Copyright (C) 2004-2005, 2007-2009, 2011-2019, 2021-2024 Free # Software Foundation, Inc. # Written by Gary V. Vaughan, 2004 # # This file is free software; the Free Software Foundation gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # serial 10 ltoptions.m4 # This is to help aclocal find these macros, as it can't see m4_define. AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])]) # _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME) # ------------------------------------------ m4_define([_LT_MANGLE_OPTION], [[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])]) # _LT_SET_OPTION(MACRO-NAME, OPTION-NAME) # --------------------------------------- # Set option OPTION-NAME for macro MACRO-NAME, and if there is a # matching handler defined, dispatch to it. Other OPTION-NAMEs are # saved as a flag. m4_define([_LT_SET_OPTION], [m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]), _LT_MANGLE_DEFUN([$1], [$2]), [m4_warning([Unknown $1 option '$2'])])[]dnl ]) # _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET]) # ------------------------------------------------------------ # Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. m4_define([_LT_IF_OPTION], [m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])]) # _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET) # ------------------------------------------------------- # Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME # are set. m4_define([_LT_UNLESS_OPTIONS], [m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), [m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option), [m4_define([$0_found])])])[]dnl m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3 ])[]dnl ]) # _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST) # ---------------------------------------- # OPTION-LIST is a space-separated list of Libtool options associated # with MACRO-NAME. If any OPTION has a matching handler declared with # LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about # the unknown option and exit. m4_defun([_LT_SET_OPTIONS], [# Set options m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), [_LT_SET_OPTION([$1], _LT_Option)]) m4_if([$1],[LT_INIT],[ dnl dnl Simply set some default values (i.e off) if boolean options were not dnl specified: _LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no ]) _LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no ]) dnl dnl If no reference was made to various pairs of opposing options, then dnl we run the default mode handler for the pair. For example, if neither dnl 'shared' nor 'disable-shared' was passed, we enable building of shared dnl archives by default: _LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED]) _LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC]) _LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC]) _LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install], [_LT_ENABLE_FAST_INSTALL]) _LT_UNLESS_OPTIONS([LT_INIT], [aix-soname=aix aix-soname=both aix-soname=svr4], [_LT_WITH_AIX_SONAME([aix])]) ]) ])# _LT_SET_OPTIONS # _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME) # ----------------------------------------- m4_define([_LT_MANGLE_DEFUN], [[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])]) # LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE) # ----------------------------------------------- m4_define([LT_OPTION_DEFINE], [m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl ])# LT_OPTION_DEFINE # dlopen # ------ LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes ]) AU_DEFUN([AC_LIBTOOL_DLOPEN], [_LT_SET_OPTION([LT_INIT], [dlopen]) AC_DIAGNOSE([obsolete], [$0: Remove this warning and the call to _LT_SET_OPTION when you put the 'dlopen' option into LT_INIT's first parameter.]) ]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], []) # win32-dll # --------- # Declare package support for building win32 dll's. LT_OPTION_DEFINE([LT_INIT], [win32-dll], [enable_win32_dll=yes case $host in *-*-cygwin* | *-*-mingw* | *-*-windows* | *-*-pw32* | *-*-cegcc*) AC_CHECK_TOOL(AS, as, false) AC_CHECK_TOOL(DLLTOOL, dlltool, false) AC_CHECK_TOOL(OBJDUMP, objdump, false) ;; esac test -z "$AS" && AS=as _LT_DECL([], [AS], [1], [Assembler program])dnl test -z "$DLLTOOL" && DLLTOOL=dlltool _LT_DECL([], [DLLTOOL], [1], [DLL creation program])dnl test -z "$OBJDUMP" && OBJDUMP=objdump _LT_DECL([], [OBJDUMP], [1], [Object dumper program])dnl ])# win32-dll AU_DEFUN([AC_LIBTOOL_WIN32_DLL], [AC_REQUIRE([AC_CANONICAL_HOST])dnl _LT_SET_OPTION([LT_INIT], [win32-dll]) AC_DIAGNOSE([obsolete], [$0: Remove this warning and the call to _LT_SET_OPTION when you put the 'win32-dll' option into LT_INIT's first parameter.]) ]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], []) # _LT_ENABLE_SHARED([DEFAULT]) # ---------------------------- # implement the --enable-shared flag, and supports the 'shared' and # 'disable-shared' LT_INIT options. # DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. m4_define([_LT_ENABLE_SHARED], [m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl AC_ARG_ENABLE([shared], [AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@], [build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])], [p=${PACKAGE-default} case $enableval in yes) enable_shared=yes ;; no) enable_shared=no ;; *) enable_shared=no # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for pkg in $enableval; do IFS=$lt_save_ifs if test "X$pkg" = "X$p"; then enable_shared=yes fi done IFS=$lt_save_ifs ;; esac], [enable_shared=]_LT_ENABLE_SHARED_DEFAULT) _LT_DECL([build_libtool_libs], [enable_shared], [0], [Whether or not to build shared libraries]) ])# _LT_ENABLE_SHARED LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])]) LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])]) # Old names: AC_DEFUN([AC_ENABLE_SHARED], [_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared]) ]) AC_DEFUN([AC_DISABLE_SHARED], [_LT_SET_OPTION([LT_INIT], [disable-shared]) ]) AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)]) AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AM_ENABLE_SHARED], []) dnl AC_DEFUN([AM_DISABLE_SHARED], []) # _LT_ENABLE_STATIC([DEFAULT]) # ---------------------------- # implement the --enable-static flag, and support the 'static' and # 'disable-static' LT_INIT options. # DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. m4_define([_LT_ENABLE_STATIC], [m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl AC_ARG_ENABLE([static], [AS_HELP_STRING([--enable-static@<:@=PKGS@:>@], [build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])], [p=${PACKAGE-default} case $enableval in yes) enable_static=yes ;; no) enable_static=no ;; *) enable_static=no # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for pkg in $enableval; do IFS=$lt_save_ifs if test "X$pkg" = "X$p"; then enable_static=yes fi done IFS=$lt_save_ifs ;; esac], [enable_static=]_LT_ENABLE_STATIC_DEFAULT) _LT_DECL([build_old_libs], [enable_static], [0], [Whether or not to build static libraries]) ])# _LT_ENABLE_STATIC LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])]) LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])]) # Old names: AC_DEFUN([AC_ENABLE_STATIC], [_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static]) ]) AC_DEFUN([AC_DISABLE_STATIC], [_LT_SET_OPTION([LT_INIT], [disable-static]) ]) AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)]) AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AM_ENABLE_STATIC], []) dnl AC_DEFUN([AM_DISABLE_STATIC], []) # _LT_ENABLE_FAST_INSTALL([DEFAULT]) # ---------------------------------- # implement the --enable-fast-install flag, and support the 'fast-install' # and 'disable-fast-install' LT_INIT options. # DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. m4_define([_LT_ENABLE_FAST_INSTALL], [m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl AC_ARG_ENABLE([fast-install], [AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@], [optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])], [p=${PACKAGE-default} case $enableval in yes) enable_fast_install=yes ;; no) enable_fast_install=no ;; *) enable_fast_install=no # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for pkg in $enableval; do IFS=$lt_save_ifs if test "X$pkg" = "X$p"; then enable_fast_install=yes fi done IFS=$lt_save_ifs ;; esac], [enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT) _LT_DECL([fast_install], [enable_fast_install], [0], [Whether or not to optimize for fast installation])dnl ])# _LT_ENABLE_FAST_INSTALL LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])]) LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])]) # Old names: AU_DEFUN([AC_ENABLE_FAST_INSTALL], [_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install]) AC_DIAGNOSE([obsolete], [$0: Remove this warning and the call to _LT_SET_OPTION when you put the 'fast-install' option into LT_INIT's first parameter.]) ]) AU_DEFUN([AC_DISABLE_FAST_INSTALL], [_LT_SET_OPTION([LT_INIT], [disable-fast-install]) AC_DIAGNOSE([obsolete], [$0: Remove this warning and the call to _LT_SET_OPTION when you put the 'disable-fast-install' option into LT_INIT's first parameter.]) ]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], []) dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], []) # _LT_WITH_AIX_SONAME([DEFAULT]) # ---------------------------------- # implement the --enable-aix-soname configure option, and support the # `aix-soname=aix' and `aix-soname=both' and `aix-soname=svr4' LT_INIT options. # DEFAULT is either `aix', `both', or `svr4'. If omitted, it defaults to `aix'. m4_define([_LT_WITH_AIX_SONAME], [m4_define([_LT_WITH_AIX_SONAME_DEFAULT], [m4_if($1, svr4, svr4, m4_if($1, both, both, aix))])dnl shared_archive_member_spec= case $host,$enable_shared in power*-*-aix[[5-9]]*,yes) AC_MSG_CHECKING([which variant of shared library versioning to provide]) AC_ARG_ENABLE([aix-soname], [AS_HELP_STRING([--enable-aix-soname=aix|svr4|both], [shared library versioning (aka "SONAME") variant to provide on AIX, @<:@default=]_LT_WITH_AIX_SONAME_DEFAULT[@:>@.])], [case $enableval in aix|svr4|both) ;; *) AC_MSG_ERROR([Unknown argument to --enable-aix-soname]) ;; esac lt_cv_with_aix_soname=$enable_aix_soname], [_AC_ENABLE_IF([with], [aix-soname], [case $withval in aix|svr4|both) ;; *) AC_MSG_ERROR([Unknown argument to --with-aix-soname]) ;; esac lt_cv_with_aix_soname=$with_aix_soname], [AC_CACHE_VAL([lt_cv_with_aix_soname], [lt_cv_with_aix_soname=]_LT_WITH_AIX_SONAME_DEFAULT)]) enable_aix_soname=$lt_cv_with_aix_soname]) with_aix_soname=$enable_aix_soname AC_MSG_RESULT([$with_aix_soname]) if test aix != "$with_aix_soname"; then # For the AIX way of multilib, we name the shared archive member # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o', # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File. # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag, # the AIX toolchain works better with OBJECT_MODE set (default 32). if test 64 = "${OBJECT_MODE-32}"; then shared_archive_member_spec=shr_64 else shared_archive_member_spec=shr fi fi ;; *) with_aix_soname=aix ;; esac _LT_DECL([], [shared_archive_member_spec], [0], [Shared archive member basename, for filename based shared library versioning on AIX])dnl ])# _LT_WITH_AIX_SONAME LT_OPTION_DEFINE([LT_INIT], [aix-soname=aix], [_LT_WITH_AIX_SONAME([aix])]) LT_OPTION_DEFINE([LT_INIT], [aix-soname=both], [_LT_WITH_AIX_SONAME([both])]) LT_OPTION_DEFINE([LT_INIT], [aix-soname=svr4], [_LT_WITH_AIX_SONAME([svr4])]) # _LT_WITH_PIC([MODE]) # -------------------- # implement the --enable-pic flag, and support the 'pic-only' and 'no-pic' # LT_INIT options. # MODE is either 'yes' or 'no'. If omitted, it defaults to 'both'. m4_define([_LT_WITH_PIC], [AC_ARG_ENABLE([pic], [AS_HELP_STRING([--enable-pic@<:@=PKGS@:>@], [try to use only PIC/non-PIC objects @<:@default=use both@:>@])], [lt_p=${PACKAGE-default} case $enableval in yes|no) pic_mode=$enableval ;; *) pic_mode=default # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for lt_pkg in $enableval; do IFS=$lt_save_ifs if test "X$lt_pkg" = "X$lt_p"; then pic_mode=yes fi done IFS=$lt_save_ifs ;; esac], [dnl Continue to support --with-pic and --without-pic, for backward dnl compatibility. _AC_ENABLE_IF([with], [pic], [lt_p=${PACKAGE-default} case $withval in yes|no) pic_mode=$withval ;; *) pic_mode=default # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for lt_pkg in $withval; do IFS=$lt_save_ifs if test "X$lt_pkg" = "X$lt_p"; then pic_mode=yes fi done IFS=$lt_save_ifs ;; esac], [pic_mode=m4_default([$1], [default])])] ) _LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl ])# _LT_WITH_PIC LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])]) LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])]) # Old name: AU_DEFUN([AC_LIBTOOL_PICMODE], [_LT_SET_OPTION([LT_INIT], [pic-only]) AC_DIAGNOSE([obsolete], [$0: Remove this warning and the call to _LT_SET_OPTION when you put the 'pic-only' option into LT_INIT's first parameter.]) ]) dnl aclocal-1.4 backwards compatibility: dnl AC_DEFUN([AC_LIBTOOL_PICMODE], []) m4_define([_LTDL_MODE], []) LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive], [m4_define([_LTDL_MODE], [nonrecursive])]) LT_OPTION_DEFINE([LTDL_INIT], [recursive], [m4_define([_LTDL_MODE], [recursive])]) LT_OPTION_DEFINE([LTDL_INIT], [subproject], [m4_define([_LTDL_MODE], [subproject])]) m4_define([_LTDL_TYPE], []) LT_OPTION_DEFINE([LTDL_INIT], [installable], [m4_define([_LTDL_TYPE], [installable])]) LT_OPTION_DEFINE([LTDL_INIT], [convenience], [m4_define([_LTDL_TYPE], [convenience])]) # ltsugar.m4 -- libtool m4 base layer. -*-Autoconf-*- # # Copyright (C) 2004-2005, 2007-2008, 2011-2019, 2021-2024 Free Software # Foundation, Inc. # Written by Gary V. Vaughan, 2004 # # This file is free software; the Free Software Foundation gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # serial 6 ltsugar.m4 # This is to help aclocal find these macros, as it can't see m4_define. AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])]) # lt_join(SEP, ARG1, [ARG2...]) # ----------------------------- # Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their # associated separator. # Needed until we can rely on m4_join from Autoconf 2.62, since all earlier # versions in m4sugar had bugs. m4_define([lt_join], [m4_if([$#], [1], [], [$#], [2], [[$2]], [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])]) m4_define([_lt_join], [m4_if([$#$2], [2], [], [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])]) # lt_car(LIST) # lt_cdr(LIST) # ------------ # Manipulate m4 lists. # These macros are necessary as long as will still need to support # Autoconf-2.59, which quotes differently. m4_define([lt_car], [[$1]]) m4_define([lt_cdr], [m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])], [$#], 1, [], [m4_dquote(m4_shift($@))])]) m4_define([lt_unquote], $1) # lt_append(MACRO-NAME, STRING, [SEPARATOR]) # ------------------------------------------ # Redefine MACRO-NAME to hold its former content plus 'SEPARATOR''STRING'. # Note that neither SEPARATOR nor STRING are expanded; they are appended # to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked). # No SEPARATOR is output if MACRO-NAME was previously undefined (different # than defined and empty). # # This macro is needed until we can rely on Autoconf 2.62, since earlier # versions of m4sugar mistakenly expanded SEPARATOR but not STRING. m4_define([lt_append], [m4_define([$1], m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])]) # lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...]) # ---------------------------------------------------------- # Produce a SEP delimited list of all paired combinations of elements of # PREFIX-LIST with SUFFIX1 through SUFFIXn. Each element of the list # has the form PREFIXmINFIXSUFFIXn. # Needed until we can rely on m4_combine added in Autoconf 2.62. m4_define([lt_combine], [m4_if(m4_eval([$# > 3]), [1], [m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl [[m4_foreach([_Lt_prefix], [$2], [m4_foreach([_Lt_suffix], ]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[, [_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])]) # lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ]) # ----------------------------------------------------------------------- # Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited # by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ. m4_define([lt_if_append_uniq], [m4_ifdef([$1], [m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1], [lt_append([$1], [$2], [$3])$4], [$5])], [lt_append([$1], [$2], [$3])$4])]) # lt_dict_add(DICT, KEY, VALUE) # ----------------------------- m4_define([lt_dict_add], [m4_define([$1($2)], [$3])]) # lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE) # -------------------------------------------- m4_define([lt_dict_add_subkey], [m4_define([$1($2:$3)], [$4])]) # lt_dict_fetch(DICT, KEY, [SUBKEY]) # ---------------------------------- m4_define([lt_dict_fetch], [m4_ifval([$3], m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]), m4_ifdef([$1($2)], [m4_defn([$1($2)])]))]) # lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE]) # ----------------------------------------------------------------- m4_define([lt_if_dict_fetch], [m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4], [$5], [$6])]) # lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...]) # -------------------------------------------------------------- m4_define([lt_dict_filter], [m4_if([$5], [], [], [lt_join(m4_quote(m4_default([$4], [[, ]])), lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]), [lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl ]) # ltversion.m4 -- version numbers -*- Autoconf -*- # # Copyright (C) 2004, 2011-2019, 2021-2024 Free Software Foundation, # Inc. # Written by Scott James Remnant, 2004 # # This file is free software; the Free Software Foundation gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # @configure_input@ # serial 4441 ltversion.m4 # This file is part of GNU Libtool m4_define([LT_PACKAGE_VERSION], [2.5.4]) m4_define([LT_PACKAGE_REVISION], [2.5.4]) AC_DEFUN([LTVERSION_VERSION], [macro_version='2.5.4' macro_revision='2.5.4' _LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?]) _LT_DECL(, macro_revision, 0) ]) # lt~obsolete.m4 -- aclocal satisfying obsolete definitions. -*-Autoconf-*- # # Copyright (C) 2004-2005, 2007, 2009, 2011-2019, 2021-2024 Free # Software Foundation, Inc. # Written by Scott James Remnant, 2004. # # This file is free software; the Free Software Foundation gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # serial 5 lt~obsolete.m4 # These exist entirely to fool aclocal when bootstrapping libtool. # # In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN), # which have later been changed to m4_define as they aren't part of the # exported API, or moved to Autoconf or Automake where they belong. # # The trouble is, aclocal is a bit thick. It'll see the old AC_DEFUN # in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us # using a macro with the same name in our local m4/libtool.m4 it'll # pull the old libtool.m4 in (it doesn't see our shiny new m4_define # and doesn't know about Autoconf macros at all.) # # So we provide this file, which has a silly filename so it's always # included after everything else. This provides aclocal with the # AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything # because those macros already exist, or will be overwritten later. # We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6. # # Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here. # Yes, that means every name once taken will need to remain here until # we give up compatibility with versions before 1.7, at which point # we need to keep only those names which we still refer to. # This is to help aclocal find these macros, as it can't see m4_define. AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])]) m4_ifndef([AC_LIBTOOL_LINKER_OPTION], [AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])]) m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP])]) m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])]) m4_ifndef([_LT_AC_SHELL_INIT], [AC_DEFUN([_LT_AC_SHELL_INIT])]) m4_ifndef([_LT_AC_SYS_LIBPATH_AIX], [AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])]) m4_ifndef([_LT_PROG_LTMAIN], [AC_DEFUN([_LT_PROG_LTMAIN])]) m4_ifndef([_LT_AC_TAGVAR], [AC_DEFUN([_LT_AC_TAGVAR])]) m4_ifndef([AC_LTDL_ENABLE_INSTALL], [AC_DEFUN([AC_LTDL_ENABLE_INSTALL])]) m4_ifndef([AC_LTDL_PREOPEN], [AC_DEFUN([AC_LTDL_PREOPEN])]) m4_ifndef([_LT_AC_SYS_COMPILER], [AC_DEFUN([_LT_AC_SYS_COMPILER])]) m4_ifndef([_LT_AC_LOCK], [AC_DEFUN([_LT_AC_LOCK])]) m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE], [AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])]) m4_ifndef([_LT_AC_TRY_DLOPEN_SELF], [AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])]) m4_ifndef([AC_LIBTOOL_PROG_CC_C_O], [AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])]) m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])]) m4_ifndef([AC_LIBTOOL_OBJDIR], [AC_DEFUN([AC_LIBTOOL_OBJDIR])]) m4_ifndef([AC_LTDL_OBJDIR], [AC_DEFUN([AC_LTDL_OBJDIR])]) m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])]) m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP], [AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])]) m4_ifndef([AC_PATH_MAGIC], [AC_DEFUN([AC_PATH_MAGIC])]) m4_ifndef([AC_PROG_LD_GNU], [AC_DEFUN([AC_PROG_LD_GNU])]) m4_ifndef([AC_PROG_LD_RELOAD_FLAG], [AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])]) m4_ifndef([AC_DEPLIBS_CHECK_METHOD], [AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])]) m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])]) m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])]) m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])]) m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS], [AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])]) m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP], [AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])]) m4_ifndef([LT_AC_PROG_EGREP], [AC_DEFUN([LT_AC_PROG_EGREP])]) m4_ifndef([LT_AC_PROG_SED], [AC_DEFUN([LT_AC_PROG_SED])]) m4_ifndef([_LT_CC_BASENAME], [AC_DEFUN([_LT_CC_BASENAME])]) m4_ifndef([_LT_COMPILER_BOILERPLATE], [AC_DEFUN([_LT_COMPILER_BOILERPLATE])]) m4_ifndef([_LT_LINKER_BOILERPLATE], [AC_DEFUN([_LT_LINKER_BOILERPLATE])]) m4_ifndef([_AC_PROG_LIBTOOL], [AC_DEFUN([_AC_PROG_LIBTOOL])]) m4_ifndef([AC_LIBTOOL_SETUP], [AC_DEFUN([AC_LIBTOOL_SETUP])]) m4_ifndef([_LT_AC_CHECK_DLFCN], [AC_DEFUN([_LT_AC_CHECK_DLFCN])]) m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER], [AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])]) m4_ifndef([_LT_AC_TAGCONFIG], [AC_DEFUN([_LT_AC_TAGCONFIG])]) m4_ifndef([AC_DISABLE_FAST_INSTALL], [AC_DEFUN([AC_DISABLE_FAST_INSTALL])]) m4_ifndef([_LT_AC_LANG_CXX], [AC_DEFUN([_LT_AC_LANG_CXX])]) m4_ifndef([_LT_AC_LANG_F77], [AC_DEFUN([_LT_AC_LANG_F77])]) m4_ifndef([_LT_AC_LANG_GCJ], [AC_DEFUN([_LT_AC_LANG_GCJ])]) m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])]) m4_ifndef([_LT_AC_LANG_C_CONFIG], [AC_DEFUN([_LT_AC_LANG_C_CONFIG])]) m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])]) m4_ifndef([_LT_AC_LANG_CXX_CONFIG], [AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])]) m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])]) m4_ifndef([_LT_AC_LANG_F77_CONFIG], [AC_DEFUN([_LT_AC_LANG_F77_CONFIG])]) m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])]) m4_ifndef([_LT_AC_LANG_GCJ_CONFIG], [AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])]) m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])]) m4_ifndef([_LT_AC_LANG_RC_CONFIG], [AC_DEFUN([_LT_AC_LANG_RC_CONFIG])]) m4_ifndef([AC_LIBTOOL_CONFIG], [AC_DEFUN([AC_LIBTOOL_CONFIG])]) m4_ifndef([_LT_AC_FILE_LTDLL_C], [AC_DEFUN([_LT_AC_FILE_LTDLL_C])]) m4_ifndef([_LT_REQUIRED_DARWIN_CHECKS], [AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS])]) m4_ifndef([_LT_AC_PROG_CXXCPP], [AC_DEFUN([_LT_AC_PROG_CXXCPP])]) m4_ifndef([_LT_PREPARE_SED_QUOTE_VARS], [AC_DEFUN([_LT_PREPARE_SED_QUOTE_VARS])]) m4_ifndef([_LT_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_PROG_ECHO_BACKSLASH])]) m4_ifndef([_LT_PROG_F77], [AC_DEFUN([_LT_PROG_F77])]) m4_ifndef([_LT_PROG_FC], [AC_DEFUN([_LT_PROG_FC])]) m4_ifndef([_LT_PROG_CXX], [AC_DEFUN([_LT_PROG_CXX])]) # pkg.m4 - Macros to locate and use pkg-config. -*- Autoconf -*- # serial 13 (pkgconf) dnl Copyright © 2004 Scott James Remnant . dnl Copyright © 2012-2015 Dan Nicholson dnl dnl This program is free software; you can redistribute it and/or modify dnl it under the terms of the GNU General Public License as published by dnl the Free Software Foundation; either version 2 of the License, or dnl (at your option) any later version. dnl dnl This program is distributed in the hope that it will be useful, but dnl WITHOUT ANY WARRANTY; without even the implied warranty of dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU dnl General Public License for more details. dnl dnl You should have received a copy of the GNU General Public License dnl along with this program; if not, see . dnl dnl As a special exception to the GNU General Public License, if you dnl distribute this file as part of a program that contains a dnl configuration script generated by Autoconf, you may include it under dnl the same distribution terms that you use for the rest of that dnl program. dnl PKG_PREREQ(MIN-VERSION) dnl ----------------------- dnl Since: 0.29 dnl dnl Verify that the version of the pkg-config macros are at least dnl MIN-VERSION. Unlike PKG_PROG_PKG_CONFIG, which checks the user's dnl installed version of pkg-config, this checks the developer's version dnl of pkg.m4 when generating configure. dnl dnl To ensure that this macro is defined, also add: dnl m4_ifndef([PKG_PREREQ], dnl [m4_fatal([must install pkg-config 0.29 or later before running autoconf/autogen])]) dnl dnl See the "Since" comment for each macro you use to see what version dnl of the macros you require. m4_defun([PKG_PREREQ], [m4_define([PKG_MACROS_VERSION], [0.29.2]) m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1, [m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])]) ])dnl PKG_PREREQ dnl PKG_PROG_PKG_CONFIG([MIN-VERSION], [ACTION-IF-NOT-FOUND]) dnl --------------------------------------------------------- dnl Since: 0.16 dnl dnl Search for the pkg-config tool and set the PKG_CONFIG variable to dnl first found in the path. Checks that the version of pkg-config found dnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is dnl used since that's the first version where most current features of dnl pkg-config existed. dnl dnl If pkg-config is not found or older than specified, it will result dnl in an empty PKG_CONFIG variable. To avoid widespread issues with dnl scripts not checking it, ACTION-IF-NOT-FOUND defaults to aborting. dnl You can specify [PKG_CONFIG=false] as an action instead, which would dnl result in pkg-config tests failing, but no bogus error messages. AC_DEFUN([PKG_PROG_PKG_CONFIG], [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) fi if test -n "$PKG_CONFIG"; then _pkg_min_version=m4_default([$1], [0.9.0]) AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) PKG_CONFIG="" fi fi if test -z "$PKG_CONFIG"; then m4_default([$2], [AC_MSG_ERROR([pkg-config not found])]) fi[]dnl ])dnl PKG_PROG_PKG_CONFIG dnl PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl ------------------------------------------------------------------- dnl Since: 0.18 dnl dnl Check to see whether a particular set of modules exists. Similar to dnl PKG_CHECK_MODULES(), but does not set variables or print errors. dnl dnl Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) dnl only at the first occurrence in configure.ac, so if the first place dnl it's called might be skipped (such as if it is within an "if", you dnl have to call PKG_CHECK_EXISTS manually AC_DEFUN([PKG_CHECK_EXISTS], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl if test -n "$PKG_CONFIG" && \ AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then m4_default([$2], [:]) m4_ifvaln([$3], [else $3])dnl fi]) dnl _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) dnl --------------------------------------------- dnl Internal wrapper calling pkg-config via PKG_CONFIG and setting dnl pkg_failed based on the result. m4_define([_PKG_CONFIG], [if test -n "$$1"; then pkg_cv_[]$1="$$1" elif test -n "$PKG_CONFIG"; then PKG_CHECK_EXISTS([$3], [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes ], [pkg_failed=yes]) else pkg_failed=untried fi[]dnl ])dnl _PKG_CONFIG dnl _PKG_SHORT_ERRORS_SUPPORTED dnl --------------------------- dnl Internal check to see if pkg-config supports short errors. AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], [AC_REQUIRE([PKG_PROG_PKG_CONFIG]) if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi[]dnl ])dnl _PKG_SHORT_ERRORS_SUPPORTED dnl PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], dnl [ACTION-IF-NOT-FOUND]) dnl -------------------------------------------------------------- dnl Since: 0.4.0 dnl dnl Note that if there is a possibility the first call to dnl PKG_CHECK_MODULES might not happen, you should be sure to include an dnl explicit call to PKG_PROG_PKG_CONFIG in your configure.ac AC_DEFUN([PKG_CHECK_MODULES], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl pkg_failed=no AC_MSG_CHECKING([for $2]) _PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) _PKG_CONFIG([$1][_LIBS], [libs], [$2]) m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS and $1[]_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details.]) if test $pkg_failed = yes; then AC_MSG_RESULT([no]) _PKG_SHORT_ERRORS_SUPPORTED if test $_pkg_short_errors_supported = yes; then $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` else $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD m4_default([$4], [AC_MSG_ERROR( [Package requirements ($2) were not met: $$1_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. _PKG_TEXT])[]dnl ]) elif test $pkg_failed = untried; then AC_MSG_RESULT([no]) m4_default([$4], [AC_MSG_FAILURE( [The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. _PKG_TEXT To get pkg-config, see .])[]dnl ]) else $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS $1[]_LIBS=$pkg_cv_[]$1[]_LIBS AC_MSG_RESULT([yes]) $3 fi[]dnl ])dnl PKG_CHECK_MODULES dnl PKG_CHECK_MODULES_STATIC(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], dnl [ACTION-IF-NOT-FOUND]) dnl --------------------------------------------------------------------- dnl Since: 0.29 dnl dnl Checks for existence of MODULES and gathers its build flags with dnl static libraries enabled. Sets VARIABLE-PREFIX_CFLAGS from --cflags dnl and VARIABLE-PREFIX_LIBS from --libs. dnl dnl Note that if there is a possibility the first call to dnl PKG_CHECK_MODULES_STATIC might not happen, you should be sure to dnl include an explicit call to PKG_PROG_PKG_CONFIG in your dnl configure.ac. AC_DEFUN([PKG_CHECK_MODULES_STATIC], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl _save_PKG_CONFIG=$PKG_CONFIG PKG_CONFIG="$PKG_CONFIG --static" PKG_CHECK_MODULES($@) PKG_CONFIG=$_save_PKG_CONFIG[]dnl ])dnl PKG_CHECK_MODULES_STATIC dnl PKG_INSTALLDIR([DIRECTORY]) dnl ------------------------- dnl Since: 0.27 dnl dnl Substitutes the variable pkgconfigdir as the location where a module dnl should install pkg-config .pc files. By default the directory is dnl $libdir/pkgconfig, but the default can be changed by passing dnl DIRECTORY. The user can override through the --with-pkgconfigdir dnl parameter. AC_DEFUN([PKG_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([pkgconfigdir], [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, [with_pkgconfigdir=]pkg_default) AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ])dnl PKG_INSTALLDIR dnl PKG_NOARCH_INSTALLDIR([DIRECTORY]) dnl -------------------------------- dnl Since: 0.27 dnl dnl Substitutes the variable noarch_pkgconfigdir as the location where a dnl module should install arch-independent pkg-config .pc files. By dnl default the directory is $datadir/pkgconfig, but the default can be dnl changed by passing DIRECTORY. The user can override through the dnl --with-noarch-pkgconfigdir parameter. AC_DEFUN([PKG_NOARCH_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([noarch-pkgconfigdir], [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, [with_noarch_pkgconfigdir=]pkg_default) AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ])dnl PKG_NOARCH_INSTALLDIR dnl PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl ------------------------------------------- dnl Since: 0.28 dnl dnl Retrieves the value of the pkg-config variable for the given module. AC_DEFUN([PKG_CHECK_VAR], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl _PKG_CONFIG([$1], [variable="][$3]["], [$2]) AS_VAR_COPY([$1], [pkg_cv_][$1]) AS_VAR_IF([$1], [""], [$5], [$4])dnl ])dnl PKG_CHECK_VAR dnl PKG_WITH_MODULES(VARIABLE-PREFIX, MODULES, dnl [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND], dnl [DESCRIPTION], [DEFAULT]) dnl ------------------------------------------ dnl dnl Prepare a "--with-" configure option using the lowercase dnl [VARIABLE-PREFIX] name, merging the behaviour of AC_ARG_WITH and dnl PKG_CHECK_MODULES in a single macro. AC_DEFUN([PKG_WITH_MODULES], [ m4_pushdef([with_arg], m4_tolower([$1])) m4_pushdef([description], [m4_default([$5], [build with ]with_arg[ support])]) m4_pushdef([def_arg], [m4_default([$6], [auto])]) m4_pushdef([def_action_if_found], [AS_TR_SH([with_]with_arg)=yes]) m4_pushdef([def_action_if_not_found], [AS_TR_SH([with_]with_arg)=no]) m4_case(def_arg, [yes],[m4_pushdef([with_without], [--without-]with_arg)], [m4_pushdef([with_without],[--with-]with_arg)]) AC_ARG_WITH(with_arg, AS_HELP_STRING(with_without, description[ @<:@default=]def_arg[@:>@]),, [AS_TR_SH([with_]with_arg)=def_arg]) AS_CASE([$AS_TR_SH([with_]with_arg)], [yes],[PKG_CHECK_MODULES([$1],[$2],$3,$4)], [auto],[PKG_CHECK_MODULES([$1],[$2], [m4_n([def_action_if_found]) $3], [m4_n([def_action_if_not_found]) $4])]) m4_popdef([with_arg]) m4_popdef([description]) m4_popdef([def_arg]) ])dnl PKG_WITH_MODULES dnl PKG_HAVE_WITH_MODULES(VARIABLE-PREFIX, MODULES, dnl [DESCRIPTION], [DEFAULT]) dnl ----------------------------------------------- dnl dnl Convenience macro to trigger AM_CONDITIONAL after PKG_WITH_MODULES dnl check._[VARIABLE-PREFIX] is exported as make variable. AC_DEFUN([PKG_HAVE_WITH_MODULES], [ PKG_WITH_MODULES([$1],[$2],,,[$3],[$4]) AM_CONDITIONAL([HAVE_][$1], [test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"]) ])dnl PKG_HAVE_WITH_MODULES dnl PKG_HAVE_DEFINE_WITH_MODULES(VARIABLE-PREFIX, MODULES, dnl [DESCRIPTION], [DEFAULT]) dnl ------------------------------------------------------ dnl dnl Convenience macro to run AM_CONDITIONAL and AC_DEFINE after dnl PKG_WITH_MODULES check. HAVE_[VARIABLE-PREFIX] is exported as make dnl and preprocessor variable. AC_DEFUN([PKG_HAVE_DEFINE_WITH_MODULES], [ PKG_HAVE_WITH_MODULES([$1],[$2],[$3],[$4]) AS_IF([test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"], [AC_DEFINE([HAVE_][$1], 1, [Enable ]m4_tolower([$1])[ support])]) ])dnl PKG_HAVE_DEFINE_WITH_MODULES # Copyright (C) 2002-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_AUTOMAKE_VERSION(VERSION) # ---------------------------- # Automake X.Y traces this macro to ensure aclocal.m4 has been # generated from the m4 files accompanying Automake X.Y. # (This private macro should not be called outside this file.) AC_DEFUN([AM_AUTOMAKE_VERSION], [am__api_version='1.18' dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to dnl require some minimum version. Point them to the right macro. m4_if([$1], [1.18.1], [], [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl ]) # _AM_AUTOCONF_VERSION(VERSION) # ----------------------------- # aclocal traces this macro to find the Autoconf version. # This is a private macro too. Using m4_define simplifies # the logic in aclocal, which can simply ignore this definition. m4_define([_AM_AUTOCONF_VERSION], []) # AM_SET_CURRENT_AUTOMAKE_VERSION # ------------------------------- # Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. # This function is AC_REQUIREd by AM_INIT_AUTOMAKE. AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], [AM_AUTOMAKE_VERSION([1.18.1])dnl m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) # AM_AUX_DIR_EXPAND -*- Autoconf -*- # Copyright (C) 2001-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets # $ac_aux_dir to '$srcdir/foo'. In other projects, it is set to # '$srcdir', '$srcdir/..', or '$srcdir/../..'. # # Of course, Automake must honor this variable whenever it calls a # tool from the auxiliary directory. The problem is that $srcdir (and # therefore $ac_aux_dir as well) can be either absolute or relative, # depending on how configure is run. This is pretty annoying, since # it makes $ac_aux_dir quite unusable in subdirectories: in the top # source directory, any form will work fine, but in subdirectories a # relative path needs to be adjusted first. # # $ac_aux_dir/missing # fails when called from a subdirectory if $ac_aux_dir is relative # $top_srcdir/$ac_aux_dir/missing # fails if $ac_aux_dir is absolute, # fails when called from a subdirectory in a VPATH build with # a relative $ac_aux_dir # # The reason of the latter failure is that $top_srcdir and $ac_aux_dir # are both prefixed by $srcdir. In an in-source build this is usually # harmless because $srcdir is '.', but things will broke when you # start a VPATH build or use an absolute $srcdir. # # So we could use something similar to $top_srcdir/$ac_aux_dir/missing, # iff we strip the leading $srcdir from $ac_aux_dir. That would be: # am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` # and then we would define $MISSING as # MISSING="\${SHELL} $am_aux_dir/missing" # This will work as long as MISSING is not called from configure, because # unfortunately $(top_srcdir) has no meaning in configure. # However there are other variables, like CC, which are often used in # configure, and could therefore not use this "fixed" $ac_aux_dir. # # Another solution, used here, is to always expand $ac_aux_dir to an # absolute PATH. The drawback is that using absolute paths prevent a # configured tree to be moved without reconfiguration. AC_DEFUN([AM_AUX_DIR_EXPAND], [AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl # Expand $ac_aux_dir to an absolute path. am_aux_dir=`cd "$ac_aux_dir" && pwd` ]) # AM_CONDITIONAL -*- Autoconf -*- # Copyright (C) 1997-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_CONDITIONAL(NAME, SHELL-CONDITION) # ------------------------------------- # Define a conditional. AC_DEFUN([AM_CONDITIONAL], [AC_PREREQ([2.52])dnl m4_if([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl AC_SUBST([$1_TRUE])dnl AC_SUBST([$1_FALSE])dnl _AM_SUBST_NOTMAKE([$1_TRUE])dnl _AM_SUBST_NOTMAKE([$1_FALSE])dnl m4_define([_AM_COND_VALUE_$1], [$2])dnl if $2; then $1_TRUE= $1_FALSE='#' else $1_TRUE='#' $1_FALSE= fi AC_CONFIG_COMMANDS_PRE( [if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then AC_MSG_ERROR([[conditional "$1" was never defined. Usually this means the macro was only invoked conditionally.]]) fi])]) # Copyright (C) 1999-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # There are a few dirty hacks below to avoid letting 'AC_PROG_CC' be # written in clear, in which case automake, when reading aclocal.m4, # will think it sees a *use*, and therefore will trigger all it's # C support machinery. Also note that it means that autoscan, seeing # CC etc. in the Makefile, will ask for an AC_PROG_CC use... # _AM_DEPENDENCIES(NAME) # ---------------------- # See how the compiler implements dependency checking. # NAME is "CC", "CXX", "OBJC", "OBJCXX", "UPC", or "GJC". # We try a few techniques and use that to set a single cache variable. # # We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was # modified to invoke _AM_DEPENDENCIES(CC); we would have a circular # dependency, and given that the user is not expected to run this macro, # just rely on AC_PROG_CC. AC_DEFUN([_AM_DEPENDENCIES], [AC_REQUIRE([AM_SET_DEPDIR])dnl AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl AC_REQUIRE([AM_MAKE_INCLUDE])dnl AC_REQUIRE([AM_DEP_TRACK])dnl m4_if([$1], [CC], [depcc="$CC" am_compiler_list=], [$1], [CXX], [depcc="$CXX" am_compiler_list=], [$1], [OBJC], [depcc="$OBJC" am_compiler_list='gcc3 gcc'], [$1], [OBJCXX], [depcc="$OBJCXX" am_compiler_list='gcc3 gcc'], [$1], [UPC], [depcc="$UPC" am_compiler_list=], [$1], [GCJ], [depcc="$GCJ" am_compiler_list='gcc3 gcc'], [depcc="$$1" am_compiler_list=]) AC_CACHE_CHECK([dependency style of $depcc], [am_cv_$1_dependencies_compiler_type], [if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named 'D' -- because '-MD' means "put the output # in D". rm -rf conftest.dir mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_$1_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` fi am__universal=false m4_case([$1], [CC], [case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac], [CXX], [case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac]) for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with # Solaris 10 /bin/sh. echo '/* dummy */' > sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf # We check with '-c' and '-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle '-M -o', and we need to detect this. Also, some Intel # versions had trouble with output in subdirs. am__obj=sub/conftest.${OBJEXT-o} am__minus_obj="-o $am__obj" case $depmode in gcc) # This depmode causes a compiler race in universal mode. test "$am__universal" = false || continue ;; nosideeffect) # After this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested. if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; msvc7 | msvc7msys | msvisualcpp | msvcmsys) # This compiler won't grok '-c -o', but also, the minuso test has # not run yet. These depmodes are late enough in the game, and # so weak that their functioning should not be impacted. am__obj=conftest.${OBJEXT-o} am__minus_obj= ;; none) break ;; esac if depmode=$depmode \ source=sub/conftest.c object=$am__obj \ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ >/dev/null 2>conftest.err && grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && grep $am__obj sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thus: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_$1_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_$1_dependencies_compiler_type=none fi ]) AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) AM_CONDITIONAL([am__fastdep$1], [ test "x$enable_dependency_tracking" != xno \ && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) ]) # AM_SET_DEPDIR # ------------- # Choose a directory name for dependency files. # This macro is AC_REQUIREd in _AM_DEPENDENCIES. AC_DEFUN([AM_SET_DEPDIR], [AC_REQUIRE([AM_SET_LEADING_DOT])dnl AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl ]) # AM_DEP_TRACK # ------------ AC_DEFUN([AM_DEP_TRACK], [AC_ARG_ENABLE([dependency-tracking], [dnl AS_HELP_STRING( [--enable-dependency-tracking], [do not reject slow dependency extractors]) AS_HELP_STRING( [--disable-dependency-tracking], [speeds up one-time build])]) if test "x$enable_dependency_tracking" != xno; then am_depcomp="$ac_aux_dir/depcomp" AMDEPBACKSLASH='\' am__nodep='_no' fi AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) AC_SUBST([AMDEPBACKSLASH])dnl _AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl AC_SUBST([am__nodep])dnl _AM_SUBST_NOTMAKE([am__nodep])dnl ]) # Generate code to set up dependency tracking. -*- Autoconf -*- # Copyright (C) 1999-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_OUTPUT_DEPENDENCY_COMMANDS # ------------------------------ AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], [{ # Older Autoconf quotes --file arguments for eval, but not when files # are listed without --file. Let's play safe and only enable the eval # if we detect the quoting. # TODO: see whether this extra hack can be removed once we start # requiring Autoconf 2.70 or later. AS_CASE([$CONFIG_FILES], [*\'*], [eval set x "$CONFIG_FILES"], [*], [set x $CONFIG_FILES]) shift # Used to flag and report bootstrapping failures. am_rc=0 for am_mf do # Strip MF so we end up with the name of the file. am_mf=`AS_ECHO(["$am_mf"]) | sed -e 's/:.*$//'` # Check whether this is an Automake generated Makefile which includes # dependency-tracking related rules and includes. # Grep'ing the whole file directly is not great: AIX grep has a line # limit of 2048, but all sed's we know have understand at least 4000. sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \ || continue am_dirpart=`AS_DIRNAME(["$am_mf"])` am_filepart=`AS_BASENAME(["$am_mf"])` AM_RUN_LOG([cd "$am_dirpart" \ && sed -e '/# am--include-marker/d' "$am_filepart" \ | $MAKE -f - am--depfiles]) || am_rc=$? done if test $am_rc -ne 0; then AC_MSG_FAILURE([Something went wrong bootstrapping makefile fragments for automatic dependency tracking. If GNU make was not used, consider re-running the configure script with MAKE="gmake" (or whatever is necessary). You can also try re-running configure with the '--disable-dependency-tracking' option to at least be able to build the package (albeit without support for automatic dependency tracking).]) fi AS_UNSET([am_dirpart]) AS_UNSET([am_filepart]) AS_UNSET([am_mf]) AS_UNSET([am_rc]) rm -f conftest-deps.mk } ])# _AM_OUTPUT_DEPENDENCY_COMMANDS # AM_OUTPUT_DEPENDENCY_COMMANDS # ----------------------------- # This macro should only be invoked once -- use via AC_REQUIRE. # # This code is only required when automatic dependency tracking is enabled. # This creates each '.Po' and '.Plo' makefile fragment that we'll need in # order to bootstrap the dependency handling code. AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], [AC_CONFIG_COMMANDS([depfiles], [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], [AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}"])]) # Do all the work for Automake. -*- Autoconf -*- # Copyright (C) 1996-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This macro actually does too much. Some checks are only needed if # your package does certain things. But this isn't really a big deal. dnl Redefine AC_PROG_CC to automatically invoke _AM_PROG_CC_C_O. m4_define([AC_PROG_CC], m4_defn([AC_PROG_CC]) [_AM_PROG_CC_C_O ]) # AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) # AM_INIT_AUTOMAKE([OPTIONS]) # ----------------------------------------------- # The call with PACKAGE and VERSION arguments is the old style # call (pre autoconf-2.50), which is being phased out. PACKAGE # and VERSION should now be passed to AC_INIT and removed from # the call to AM_INIT_AUTOMAKE. # We support both call styles for the transition. After # the next Automake release, Autoconf can make the AC_INIT # arguments mandatory, and then we can depend on a new Autoconf # release and drop the old call support. AC_DEFUN([AM_INIT_AUTOMAKE], [AC_PREREQ([2.65])dnl m4_ifdef([_$0_ALREADY_INIT], [m4_fatal([$0 expanded multiple times ]m4_defn([_$0_ALREADY_INIT]))], [m4_define([_$0_ALREADY_INIT], m4_expansion_stack)])dnl dnl Autoconf wants to disallow AM_ names. We explicitly allow dnl the ones we care about. m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl AC_REQUIRE([AC_PROG_INSTALL])dnl if test "`cd $srcdir && pwd`" != "`pwd`"; then # Use -I$(srcdir) only when $(srcdir) != ., so that make's output # is not polluted with repeated "-I." AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl # test to see if srcdir already configured if test -f $srcdir/config.status; then AC_MSG_ERROR([source directory already configured; run "make distclean" there first]) fi fi # test whether we have cygpath if test -z "$CYGPATH_W"; then if (cygpath --version) >/dev/null 2>/dev/null; then CYGPATH_W='cygpath -w' else CYGPATH_W=echo fi fi AC_SUBST([CYGPATH_W]) # Define the identity of the package. dnl Distinguish between old-style and new-style calls. m4_ifval([$2], [AC_DIAGNOSE([obsolete], [$0: two- and three-arguments forms are deprecated.]) m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl AC_SUBST([PACKAGE], [$1])dnl AC_SUBST([VERSION], [$2])], [_AM_SET_OPTIONS([$1])dnl dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT. m4_if( m4_ifset([AC_PACKAGE_NAME], [ok]):m4_ifset([AC_PACKAGE_VERSION], [ok]), [ok:ok],, [m4_fatal([AC_INIT should be called with package and version arguments])])dnl AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl _AM_IF_OPTION([no-define],, [AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package]) AC_DEFINE_UNQUOTED([VERSION], ["$VERSION"], [Version number of package])])dnl # Some tools Automake needs. AC_REQUIRE([AM_SANITY_CHECK])dnl AC_REQUIRE([AC_ARG_PROGRAM])dnl AM_MISSING_PROG([ACLOCAL], [aclocal-${am__api_version}]) AM_MISSING_PROG([AUTOCONF], [autoconf]) AM_MISSING_PROG([AUTOMAKE], [automake-${am__api_version}]) AM_MISSING_PROG([AUTOHEADER], [autoheader]) AM_MISSING_PROG([MAKEINFO], [makeinfo]) AC_REQUIRE([AM_PROG_INSTALL_SH])dnl AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl AC_REQUIRE([AC_PROG_MKDIR_P])dnl # For better backward compatibility. To be removed once Automake 1.9.x # dies out for good. For more background, see: # # AC_SUBST([mkdir_p], ['$(MKDIR_P)']) # We need awk for the "check" target (and possibly the TAP driver). The # system "awk" is bad on some platforms. AC_REQUIRE([AC_PROG_AWK])dnl AC_REQUIRE([AC_PROG_MAKE_SET])dnl AC_REQUIRE([AM_SET_LEADING_DOT])dnl _AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], [_AM_IF_OPTION([tar-v7], [_AM_PROG_TAR([v7])], [_AM_PROG_TAR([ustar])])])]) _AM_IF_OPTION([no-dependencies],, [AC_PROVIDE_IFELSE([AC_PROG_CC], [_AM_DEPENDENCIES([CC])], [m4_define([AC_PROG_CC], m4_defn([AC_PROG_CC])[_AM_DEPENDENCIES([CC])])])dnl AC_PROVIDE_IFELSE([AC_PROG_CXX], [_AM_DEPENDENCIES([CXX])], [m4_define([AC_PROG_CXX], m4_defn([AC_PROG_CXX])[_AM_DEPENDENCIES([CXX])])])dnl AC_PROVIDE_IFELSE([AC_PROG_OBJC], [_AM_DEPENDENCIES([OBJC])], [m4_define([AC_PROG_OBJC], m4_defn([AC_PROG_OBJC])[_AM_DEPENDENCIES([OBJC])])])dnl AC_PROVIDE_IFELSE([AC_PROG_OBJCXX], [_AM_DEPENDENCIES([OBJCXX])], [m4_define([AC_PROG_OBJCXX], m4_defn([AC_PROG_OBJCXX])[_AM_DEPENDENCIES([OBJCXX])])])dnl ]) # Variables for tags utilities; see am/tags.am if test -z "$CTAGS"; then CTAGS=ctags fi AC_SUBST([CTAGS]) if test -z "$ETAGS"; then ETAGS=etags fi AC_SUBST([ETAGS]) if test -z "$CSCOPE"; then CSCOPE=cscope fi AC_SUBST([CSCOPE]) AC_REQUIRE([_AM_SILENT_RULES])dnl dnl The testsuite driver may need to know about EXEEXT, so add the dnl 'am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen. This dnl macro is hooked onto _AC_COMPILER_EXEEXT early, see below. AC_CONFIG_COMMANDS_PRE(dnl [m4_provide_if([_AM_COMPILER_EXEEXT], [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl AC_REQUIRE([_AM_PROG_RM_F]) AC_REQUIRE([_AM_PROG_XARGS_N]) dnl The trailing newline in this macro's definition is deliberate, for dnl backward compatibility and to allow trailing 'dnl'-style comments dnl after the AM_INIT_AUTOMAKE invocation. See automake bug#16841. ]) dnl Hook into '_AC_COMPILER_EXEEXT' early to learn its expansion. Do not dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further dnl mangled by Autoconf and run in a shell conditional statement. m4_define([_AC_COMPILER_EXEEXT], m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])]) # When config.status generates a header, we must update the stamp-h file. # This file resides in the same directory as the config header # that is generated. The stamp files are numbered to have different names. # Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the # loop where config.status creates the headers, so we can generate # our stamp files there. AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], [# Compute $1's index in $config_headers. _am_arg=$1 _am_stamp_count=1 for _am_header in $config_headers :; do case $_am_header in $_am_arg | $_am_arg:* ) break ;; * ) _am_stamp_count=`expr $_am_stamp_count + 1` ;; esac done echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) # Copyright (C) 2001-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_PROG_INSTALL_SH # ------------------ # Define $install_sh. AC_DEFUN([AM_PROG_INSTALL_SH], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl if test x"${install_sh+set}" != xset; then case $am_aux_dir in *\ * | *\ *) install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; *) install_sh="\${SHELL} $am_aux_dir/install-sh" esac fi AC_SUBST([install_sh])]) # Copyright (C) 2003-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # Check whether the underlying file-system supports filenames # with a leading dot. For instance MS-DOS doesn't. AC_DEFUN([AM_SET_LEADING_DOT], [rm -rf .tst 2>/dev/null mkdir .tst 2>/dev/null if test -d .tst; then am__leading_dot=. else am__leading_dot=_ fi rmdir .tst 2>/dev/null AC_SUBST([am__leading_dot])]) # Add --enable-maintainer-mode option to configure. -*- Autoconf -*- # From Jim Meyering # Copyright (C) 1996-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_MAINTAINER_MODE([DEFAULT-MODE]) # ---------------------------------- # Control maintainer-specific portions of Makefiles. # Default is to disable them, unless 'enable' is passed literally. # For symmetry, 'disable' may be passed as well. Anyway, the user # can override the default with the --enable/--disable switch. AC_DEFUN([AM_MAINTAINER_MODE], [m4_case(m4_default([$1], [disable]), [enable], [m4_define([am_maintainer_other], [disable])], [disable], [m4_define([am_maintainer_other], [enable])], [m4_define([am_maintainer_other], [enable]) m4_warn([syntax], [unexpected argument to AM@&t@_MAINTAINER_MODE: $1])]) AC_MSG_CHECKING([whether to enable maintainer-specific portions of Makefiles]) dnl maintainer-mode's default is 'disable' unless 'enable' is passed AC_ARG_ENABLE([maintainer-mode], [AS_HELP_STRING([--]am_maintainer_other[-maintainer-mode], am_maintainer_other[ make rules and dependencies not useful (and sometimes confusing) to the casual installer])], [USE_MAINTAINER_MODE=$enableval], [USE_MAINTAINER_MODE=]m4_if(am_maintainer_other, [enable], [no], [yes])) AC_MSG_RESULT([$USE_MAINTAINER_MODE]) AM_CONDITIONAL([MAINTAINER_MODE], [test $USE_MAINTAINER_MODE = yes]) MAINT=$MAINTAINER_MODE_TRUE AC_SUBST([MAINT])dnl ] ) # Check to see how 'make' treats includes. -*- Autoconf -*- # Copyright (C) 2001-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_MAKE_INCLUDE() # ----------------- # Check whether make has an 'include' directive that can support all # the idioms we need for our automatic dependency tracking code. AC_DEFUN([AM_MAKE_INCLUDE], [AC_MSG_CHECKING([whether ${MAKE-make} supports the include directive]) cat > confinc.mk << 'END' am__doit: @echo this is the am__doit target >confinc.out .PHONY: am__doit END am__include="#" am__quote= # BSD make does it like this. echo '.include "confinc.mk" # ignored' > confmf.BSD # Other make implementations (GNU, Solaris 10, AIX) do it like this. echo 'include confinc.mk # ignored' > confmf.GNU _am_result=no for s in GNU BSD; do AM_RUN_LOG([${MAKE-make} -f confmf.$s && cat confinc.out]) AS_CASE([$?:`cat confinc.out 2>/dev/null`], ['0:this is the am__doit target'], [AS_CASE([$s], [BSD], [am__include='.include' am__quote='"'], [am__include='include' am__quote=''])]) if test "$am__include" != "#"; then _am_result="yes ($s style)" break fi done rm -f confinc.* confmf.* AC_MSG_RESULT([${_am_result}]) AC_SUBST([am__include])]) AC_SUBST([am__quote])]) # Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- # Copyright (C) 1997-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_MISSING_PROG(NAME, PROGRAM) # ------------------------------ AC_DEFUN([AM_MISSING_PROG], [AC_REQUIRE([AM_MISSING_HAS_RUN]) $1=${$1-"${am_missing_run}$2"} AC_SUBST($1)]) # AM_MISSING_HAS_RUN # ------------------ # Define MISSING if not defined so far and test if it is modern enough. # If it is, set am_missing_run to use it, otherwise, to nothing. AC_DEFUN([AM_MISSING_HAS_RUN], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl AC_REQUIRE_AUX_FILE([missing])dnl if test x"${MISSING+set}" != xset; then MISSING="\${SHELL} '$am_aux_dir/missing'" fi # Use eval to expand $SHELL if eval "$MISSING --is-lightweight"; then am_missing_run="$MISSING " else am_missing_run= AC_MSG_WARN(['missing' script is too old or missing]) fi ]) # Helper functions for option handling. -*- Autoconf -*- # Copyright (C) 2001-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_MANGLE_OPTION(NAME) # ----------------------- AC_DEFUN([_AM_MANGLE_OPTION], [[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) # _AM_SET_OPTION(NAME) # -------------------- # Set option NAME. Presently that only means defining a flag for this option. AC_DEFUN([_AM_SET_OPTION], [m4_define(_AM_MANGLE_OPTION([$1]), [1])]) # _AM_SET_OPTIONS(OPTIONS) # ------------------------ # OPTIONS is a space-separated list of Automake options. AC_DEFUN([_AM_SET_OPTIONS], [m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) # _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) # ------------------------------------------- # Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. AC_DEFUN([_AM_IF_OPTION], [m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) # Copyright (C) 1999-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_PROG_CC_C_O # --------------- # Like AC_PROG_CC_C_O, but changed for automake. We rewrite AC_PROG_CC # to automatically call this. AC_DEFUN([_AM_PROG_CC_C_O], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl AC_REQUIRE_AUX_FILE([compile])dnl AC_LANG_PUSH([C])dnl AC_CACHE_CHECK( [whether $CC understands -c and -o together], [am_cv_prog_cc_c_o], [AC_LANG_CONFTEST([AC_LANG_PROGRAM([])]) # Make sure it works both with $CC and with simple cc. # Following AC_PROG_CC_C_O, we do the test twice because some # compilers refuse to overwrite an existing .o file with -o, # though they will create one. am_cv_prog_cc_c_o=yes for am_i in 1 2; do if AM_RUN_LOG([$CC -c conftest.$ac_ext -o conftest2.$ac_objext]) \ && test -f conftest2.$ac_objext; then : OK else am_cv_prog_cc_c_o=no break fi done # aligned with autoconf, so not including core; see bug#72225. rm -f -r a.out a.exe b.out conftest.$ac_ext conftest.$ac_objext \ conftest.dSYM conftest1.$ac_ext conftest1.$ac_objext conftest1.dSYM \ conftest2.$ac_ext conftest2.$ac_objext conftest2.dSYM unset am_i]) if test "$am_cv_prog_cc_c_o" != yes; then # Losing compiler, so override with the script. # FIXME: It is wrong to rewrite CC. # But if we don't then we get into trouble of one sort or another. # A longer-term fix would be to have automake use am__CC in this case, # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" CC="$am_aux_dir/compile $CC" fi AC_LANG_POP([C])]) # For backward compatibility. AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])]) # Copyright (C) 2022-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_PROG_RM_F # --------------- # Check whether 'rm -f' without any arguments works. # https://bugs.gnu.org/10828 AC_DEFUN([_AM_PROG_RM_F], [am__rm_f_notfound= AS_IF([(rm -f && rm -fr && rm -rf) 2>/dev/null], [], [am__rm_f_notfound='""']) AC_SUBST(am__rm_f_notfound) ]) # Copyright (C) 2001-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_RUN_LOG(COMMAND) # ------------------- # Run COMMAND, save the exit status in ac_status, and log it. # (This has been adapted from Autoconf's _AC_RUN_LOG macro.) AC_DEFUN([AM_RUN_LOG], [{ echo "$as_me:$LINENO: $1" >&AS_MESSAGE_LOG_FD ($1) >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD (exit $ac_status); }]) # Check to make sure that the build environment is sane. -*- Autoconf -*- # Copyright (C) 1996-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_SLEEP_FRACTIONAL_SECONDS # ---------------------------- AC_DEFUN([_AM_SLEEP_FRACTIONAL_SECONDS], [dnl AC_CACHE_CHECK([whether sleep supports fractional seconds], am_cv_sleep_fractional_seconds, [dnl AS_IF([sleep 0.001 2>/dev/null], [am_cv_sleep_fractional_seconds=yes], [am_cv_sleep_fractional_seconds=no]) ])]) # _AM_FILESYSTEM_TIMESTAMP_RESOLUTION # ----------------------------------- # Determine the filesystem's resolution for file modification # timestamps. The coarsest we know of is FAT, with a resolution # of only two seconds, even with the most recent "exFAT" extensions. # The finest (e.g. ext4 with large inodes, XFS, ZFS) is one # nanosecond, matching clock_gettime. However, it is probably not # possible to delay execution of a shell script for less than one # millisecond, due to process creation overhead and scheduling # granularity, so we don't check for anything finer than that. (See below.) AC_DEFUN([_AM_FILESYSTEM_TIMESTAMP_RESOLUTION], [dnl AC_REQUIRE([_AM_SLEEP_FRACTIONAL_SECONDS]) AC_CACHE_CHECK([filesystem timestamp resolution], am_cv_filesystem_timestamp_resolution, [dnl # Default to the worst case. am_cv_filesystem_timestamp_resolution=2 # Only try to go finer than 1 sec if sleep can do it. # Don't try 1 sec, because if 0.01 sec and 0.1 sec don't work, # - 1 sec is not much of a win compared to 2 sec, and # - it takes 2 seconds to perform the test whether 1 sec works. # # Instead, just use the default 2s on platforms that have 1s resolution, # accept the extra 1s delay when using $sleep in the Automake tests, in # exchange for not incurring the 2s delay for running the test for all # packages. # am_try_resolutions= if test "$am_cv_sleep_fractional_seconds" = yes; then # Even a millisecond often causes a bunch of false positives, # so just try a hundredth of a second. The time saved between .001 and # .01 is not terribly consequential. am_try_resolutions="0.01 0.1 $am_try_resolutions" fi # In order to catch current-generation FAT out, we must *modify* files # that already exist; the *creation* timestamp is finer. Use names # that make ls -t sort them differently when they have equal # timestamps than when they have distinct timestamps, keeping # in mind that ls -t prints the *newest* file first. rm -f conftest.ts? : > conftest.ts1 : > conftest.ts2 : > conftest.ts3 # Make sure ls -t actually works. Do 'set' in a subshell so we don't # clobber the current shell's arguments. (Outer-level square brackets # are removed by m4; they're present so that m4 does not expand # ; be careful, easy to get confused.) if ( set X `[ls -t conftest.ts[12]]` && { test "$[]*" != "X conftest.ts1 conftest.ts2" || test "$[]*" != "X conftest.ts2 conftest.ts1"; } ); then :; else # If neither matched, then we have a broken ls. This can happen # if, for instance, CONFIG_SHELL is bash and it inherits a # broken ls alias from the environment. This has actually # happened. Such a system could not be considered "sane". _AS_ECHO_UNQUOTED( ["Bad output from ls -t: \"`[ls -t conftest.ts[12]]`\""], [AS_MESSAGE_LOG_FD]) AC_MSG_FAILURE([ls -t produces unexpected output. Make sure there is not a broken ls alias in your environment.]) fi for am_try_res in $am_try_resolutions; do # Any one fine-grained sleep might happen to cross the boundary # between two values of a coarser actual resolution, but if we do # two fine-grained sleeps in a row, at least one of them will fall # entirely within a coarse interval. echo alpha > conftest.ts1 sleep $am_try_res echo beta > conftest.ts2 sleep $am_try_res echo gamma > conftest.ts3 # We assume that 'ls -t' will make use of high-resolution # timestamps if the operating system supports them at all. if (set X `ls -t conftest.ts?` && test "$[]2" = conftest.ts3 && test "$[]3" = conftest.ts2 && test "$[]4" = conftest.ts1); then # # Ok, ls -t worked. If we're at a resolution of 1 second, we're done, # because we don't need to test make. make_ok=true if test $am_try_res != 1; then # But if we've succeeded so far with a subsecond resolution, we # have one more thing to check: make. It can happen that # everything else supports the subsecond mtimes, but make doesn't; # notably on macOS, which ships make 3.81 from 2006 (the last one # released under GPLv2). https://bugs.gnu.org/68808 # # We test $MAKE if it is defined in the environment, else "make". # It might get overridden later, but our hope is that in practice # it does not matter: it is the system "make" which is (by far) # the most likely to be broken, whereas if the user overrides it, # probably they did so with a better, or at least not worse, make. # https://lists.gnu.org/archive/html/automake/2024-06/msg00051.html # # Create a Makefile (real tab character here): rm -f conftest.mk echo 'conftest.ts1: conftest.ts2' >conftest.mk echo ' touch conftest.ts2' >>conftest.mk # # Now, running # touch conftest.ts1; touch conftest.ts2; make # should touch ts1 because ts2 is newer. This could happen by luck, # but most often, it will fail if make's support is insufficient. So # test for several consecutive successes. # # (We reuse conftest.ts[12] because we still want to modify existing # files, not create new ones, per above.) n=0 make=${MAKE-make} until test $n -eq 3; do echo one > conftest.ts1 sleep $am_try_res echo two > conftest.ts2 # ts2 should now be newer than ts1 if $make -f conftest.mk | grep 'up to date' >/dev/null; then make_ok=false break # out of $n loop fi n=`expr $n + 1` done fi # if $make_ok; then # Everything we know to check worked out, so call this resolution good. am_cv_filesystem_timestamp_resolution=$am_try_res break # out of $am_try_res loop fi # Otherwise, we'll go on to check the next resolution. fi done rm -f conftest.ts? # (end _am_filesystem_timestamp_resolution) ])]) # AM_SANITY_CHECK # --------------- AC_DEFUN([AM_SANITY_CHECK], [AC_REQUIRE([_AM_FILESYSTEM_TIMESTAMP_RESOLUTION]) # This check should not be cached, as it may vary across builds of # different projects. AC_MSG_CHECKING([whether build environment is sane]) # Reject unsafe characters in $srcdir or the absolute working directory # name. Accept space and tab only in the latter. am_lf=' ' case `pwd` in *[[\\\"\#\$\&\'\`$am_lf]]*) AC_MSG_RESULT([no]) AC_MSG_ERROR([unsafe absolute working directory name]);; esac case $srcdir in *[[\\\"\#\$\&\'\`$am_lf\ \ ]]*) AC_MSG_RESULT([no]) AC_MSG_ERROR([unsafe srcdir value: '$srcdir']);; esac # Do 'set' in a subshell so we don't clobber the current shell's # arguments. Must try -L first in case configure is actually a # symlink; some systems play weird games with the mod time of symlinks # (eg FreeBSD returns the mod time of the symlink's containing # directory). am_build_env_is_sane=no am_has_slept=no rm -f conftest.file for am_try in 1 2; do echo "timestamp, slept: $am_has_slept" > conftest.file if ( set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` if test "$[]*" = "X"; then # -L didn't work. set X `ls -t "$srcdir/configure" conftest.file` fi test "$[]2" = conftest.file ); then am_build_env_is_sane=yes break fi # Just in case. sleep "$am_cv_filesystem_timestamp_resolution" am_has_slept=yes done AC_MSG_RESULT([$am_build_env_is_sane]) if test "$am_build_env_is_sane" = no; then AC_MSG_ERROR([newly created file is older than distributed files! Check your system clock]) fi # If we didn't sleep, we still need to ensure time stamps of config.status and # generated files are strictly newer. am_sleep_pid= AS_IF([test -e conftest.file || grep 'slept: no' conftest.file >/dev/null 2>&1],, [dnl ( sleep "$am_cv_filesystem_timestamp_resolution" ) & am_sleep_pid=$! ]) AC_CONFIG_COMMANDS_PRE( [AC_MSG_CHECKING([that generated files are newer than configure]) if test -n "$am_sleep_pid"; then # Hide warnings about reused PIDs. wait $am_sleep_pid 2>/dev/null fi AC_MSG_RESULT([done])]) rm -f conftest.file ]) # Copyright (C) 2009-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_SILENT_RULES # ---------------- # Enable less verbose build rules support. AC_DEFUN([_AM_SILENT_RULES], [AM_DEFAULT_VERBOSITY=1 AC_ARG_ENABLE([silent-rules], [dnl AS_HELP_STRING( [--enable-silent-rules], [less verbose build output (undo: "make V=1")]) AS_HELP_STRING( [--disable-silent-rules], [verbose build output (undo: "make V=0")])dnl ]) dnl dnl A few 'make' implementations (e.g., NonStop OS and NextStep) dnl do not support nested variable expansions. dnl See automake bug#9928 and bug#10237. am_make=${MAKE-make} AC_CACHE_CHECK([whether $am_make supports nested variables], [am_cv_make_support_nested_variables], [if AS_ECHO([['TRUE=$(BAR$(V)) BAR0=false BAR1=true V=1 am__doit: @$(TRUE) .PHONY: am__doit']]) | $am_make -f - >/dev/null 2>&1; then am_cv_make_support_nested_variables=yes else am_cv_make_support_nested_variables=no fi]) AC_SUBST([AM_V])dnl AM_SUBST_NOTMAKE([AM_V])dnl AC_SUBST([AM_DEFAULT_V])dnl AM_SUBST_NOTMAKE([AM_DEFAULT_V])dnl AC_SUBST([AM_DEFAULT_VERBOSITY])dnl AM_BACKSLASH='\' AC_SUBST([AM_BACKSLASH])dnl _AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl dnl Delay evaluation of AM_DEFAULT_VERBOSITY to the end to allow multiple calls dnl to AM_SILENT_RULES to change the default value. AC_CONFIG_COMMANDS_PRE([dnl case $enable_silent_rules in @%:@ ((( yes) AM_DEFAULT_VERBOSITY=0;; no) AM_DEFAULT_VERBOSITY=1;; esac if test $am_cv_make_support_nested_variables = yes; then dnl Using '$V' instead of '$(V)' breaks IRIX make. AM_V='$(V)' AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' else AM_V=$AM_DEFAULT_VERBOSITY AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY fi ])dnl ]) # AM_SILENT_RULES([DEFAULT]) # -------------------------- # Set the default verbosity level to DEFAULT ("yes" being less verbose, "no" or # empty being verbose). AC_DEFUN([AM_SILENT_RULES], [AC_REQUIRE([_AM_SILENT_RULES]) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1])m4_newline dnl We intentionally force a newline after the assignment, since a) nothing dnl good can come of more text following, and b) that was the behavior dnl before 1.17. See https://bugs.gnu.org/72267. ]) # Copyright (C) 2001-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_PROG_INSTALL_STRIP # --------------------- # One issue with vendor 'install' (even GNU) is that you can't # specify the program used to strip binaries. This is especially # annoying in cross-compiling environments, where the build's strip # is unlikely to handle the host's binaries. # Fortunately install-sh will honor a STRIPPROG variable, so we # always use install-sh in "make install-strip", and initialize # STRIPPROG with the value of the STRIP variable (set by the user). AC_DEFUN([AM_PROG_INSTALL_STRIP], [AC_REQUIRE([AM_PROG_INSTALL_SH])dnl # Installed binaries are usually stripped using 'strip' when the user # run "make install-strip". However 'strip' might not be the right # tool to use in cross-compilation environments, therefore Automake # will honor the 'STRIP' environment variable to overrule this program. dnl Don't test for $cross_compiling = yes, because it might be 'maybe'. if test "$cross_compiling" != no; then AC_CHECK_TOOL([STRIP], [strip], :) fi INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" AC_SUBST([INSTALL_STRIP_PROGRAM])]) # Copyright (C) 2006-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_SUBST_NOTMAKE(VARIABLE) # --------------------------- # Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in. # This macro is traced by Automake. AC_DEFUN([_AM_SUBST_NOTMAKE]) # AM_SUBST_NOTMAKE(VARIABLE) # -------------------------- # Public sister of _AM_SUBST_NOTMAKE. AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) # Check how to create a tarball. -*- Autoconf -*- # Copyright (C) 2004-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_PROG_TAR(FORMAT) # -------------------- # Check how to create a tarball in format FORMAT. # FORMAT should be one of 'v7', 'ustar', or 'pax'. # # Substitute a variable $(am__tar) that is a command # writing to stdout a FORMAT-tarball containing the directory # $tardir. # tardir=directory && $(am__tar) > result.tar # # Substitute a variable $(am__untar) that extract such # a tarball read from stdin. # $(am__untar) < result.tar # AC_DEFUN([_AM_PROG_TAR], [# Always define AMTAR for backward compatibility. Yes, it's still used # in the wild :-( We should find a proper way to deprecate it ... AC_SUBST([AMTAR], ['$${TAR-tar}']) # We'll loop over all known methods to create a tar archive until one works. _am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' m4_if([$1], [v7], [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'], [m4_case([$1], [ustar], [# The POSIX 1988 'ustar' format is defined with fixed-size fields. # There is notably a 21 bits limit for the UID and the GID. In fact, # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343 # and bug#13588). am_max_uid=2097151 # 2^21 - 1 am_max_gid=$am_max_uid # The $UID and $GID variables are not portable, so we need to resort # to the POSIX-mandated id(1) utility. Errors in the 'id' calls # below are definitely unexpected, so allow the users to see them # (that is, avoid stderr redirection). am_uid=`id -u || echo unknown` am_gid=`id -g || echo unknown` AC_MSG_CHECKING([whether UID '$am_uid' is supported by ustar format]) if test x$am_uid = xunknown; then AC_MSG_WARN([ancient id detected; assuming current UID is ok, but dist-ustar might not work]) elif test $am_uid -le $am_max_uid; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) _am_tools=none fi AC_MSG_CHECKING([whether GID '$am_gid' is supported by ustar format]) if test x$gm_gid = xunknown; then AC_MSG_WARN([ancient id detected; assuming current GID is ok, but dist-ustar might not work]) elif test $am_gid -le $am_max_gid; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) _am_tools=none fi], [pax], [], [m4_fatal([Unknown tar format])]) AC_MSG_CHECKING([how to create a $1 tar archive]) # Go ahead even if we have the value already cached. We do so because we # need to set the values for the 'am__tar' and 'am__untar' variables. _am_tools=${am_cv_prog_tar_$1-$_am_tools} for _am_tool in $_am_tools; do case $_am_tool in gnutar) for _am_tar in tar gnutar gtar; do AM_RUN_LOG([$_am_tar --version]) && break done am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' am__untar="$_am_tar -xf -" ;; plaintar) # Must skip GNU tar: if it does not support --format= it doesn't create # ustar tarball either. (tar --version) >/dev/null 2>&1 && continue am__tar='tar chf - "$$tardir"' am__tar_='tar chf - "$tardir"' am__untar='tar xf -' ;; pax) am__tar='pax -L -x $1 -w "$$tardir"' am__tar_='pax -L -x $1 -w "$tardir"' am__untar='pax -r' ;; cpio) am__tar='find "$$tardir" -print | cpio -o -H $1 -L' am__tar_='find "$tardir" -print | cpio -o -H $1 -L' am__untar='cpio -i -H $1 -d' ;; none) am__tar=false am__tar_=false am__untar=false ;; esac # If the value was cached, stop now. We just wanted to have am__tar # and am__untar set. test -n "${am_cv_prog_tar_$1}" && break # tar/untar a dummy directory, and stop if the command works. rm -rf conftest.dir mkdir conftest.dir echo GrepMe > conftest.dir/file AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) rm -rf conftest.dir if test -s conftest.tar; then AM_RUN_LOG([$am__untar /dev/null 2>&1 && break fi done rm -rf conftest.dir AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) AC_MSG_RESULT([$am_cv_prog_tar_$1])]) AC_SUBST([am__tar]) AC_SUBST([am__untar]) ]) # _AM_PROG_TAR # Copyright (C) 2022-2025 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_PROG_XARGS_N # ---------------- # Check whether 'xargs -n' works. It should work everywhere, so the fallback # is not optimized at all as we never expect to use it. AC_DEFUN([_AM_PROG_XARGS_N], [AC_CACHE_CHECK([xargs -n works], am_cv_xargs_n_works, [dnl AS_IF([test "`echo 1 2 3 | xargs -n2 echo`" = "1 2 3"], [am_cv_xargs_n_works=yes], [am_cv_xargs_n_works=no])]) AS_IF([test "$am_cv_xargs_n_works" = yes], [am__xargs_n='xargs -n'], [dnl am__xargs_n='am__xargs_n () { shift; sed "s/ /\\n/g" | while read am__xargs_n_arg; do "$@" "$am__xargs_n_arg"; done; }' ])dnl AC_SUBST(am__xargs_n) ]) m4_include([acinclude.m4]) bluez-5.87/PaxHeaders/depcomp0000644000000000000000000000005015221753002013222 xustar0020 atime=1783092748 20 ctime=1783094098 bluez-5.87/depcomp0000755000000000000000000005622415221753002012717 0ustar00rootroot#! /bin/sh # depcomp - compile a program generating dependencies as side-effects scriptversion=2025-06-18.21; # UTC # Copyright (C) 1999-2025 Free Software Foundation, Inc. # 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, 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, see . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Originally written by Alexandre Oliva . case $1 in '') echo "$0: No command. Try '$0 --help' for more information." 1>&2 exit 1; ;; -h | --h*) cat <<\EOF Usage: depcomp [--help] [--version] PROGRAM [ARGS] Run PROGRAMS ARGS to compile a file, generating dependencies as side-effects. Environment variables: depmode Dependency tracking mode. source Source file read by 'PROGRAMS ARGS'. object Object file output by 'PROGRAMS ARGS'. DEPDIR directory where to store dependencies. depfile Dependency file to output. tmpdepfile Temporary file to use when outputting dependencies. libtool Whether libtool is used (yes/no). Report bugs to . GNU Automake home page: . General help using GNU software: . EOF exit $? ;; -v | --v*) echo "depcomp (GNU Automake) $scriptversion" exit $? ;; esac # Get the directory component of the given path, and save it in the # global variables '$dir'. Note that this directory component will # be either empty or ending with a '/' character. This is deliberate. set_dir_from () { case $1 in */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;; *) dir=;; esac } # Get the suffix-stripped basename of the given path, and save it the # global variable '$base'. set_base_from () { base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'` } # If no dependency file was actually created by the compiler invocation, # we still have to create a dummy depfile, to avoid errors with the # Makefile "include basename.Plo" scheme. make_dummy_depfile () { echo "#dummy" > "$depfile" } # Factor out some common post-processing of the generated depfile. # Requires the auxiliary global variable '$tmpdepfile' to be set. aix_post_process_depfile () { # If the compiler actually managed to produce a dependency file, # post-process it. if test -f "$tmpdepfile"; then # Each line is of the form 'foo.o: dependency.h'. # Do two passes, one to just change these to # $object: dependency.h # and one to simply output # dependency.h: # which is needed to avoid the deleted-header problem. { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile" sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile" } > "$depfile" rm -f "$tmpdepfile" else make_dummy_depfile fi } # A tabulation character. tab=' ' # A newline character. nl=' ' # Character ranges might be problematic outside the C locale. # These definitions help. upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ lower=abcdefghijklmnopqrstuvwxyz alpha=${upper}${lower} if test -z "$depmode" || test -z "$source" || test -z "$object"; then echo "depcomp: Variables source, object and depmode must be set" 1>&2 exit 1 fi # Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po. depfile=${depfile-`echo "$object" | sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`} tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} rm -f "$tmpdepfile" # Avoid interference from the environment. gccflag= dashmflag= # Some modes work just like other modes, but use different flags. We # parameterize here, but still list the modes in the big case below, # to make depend.m4 easier to write. Note that we *cannot* use a case # here, because this file can only contain one case statement. if test "$depmode" = hp; then # HP compiler uses -M and no extra arg. gccflag=-M depmode=gcc fi if test "$depmode" = dashXmstdout; then # This is just like dashmstdout with a different argument. dashmflag=-xM depmode=dashmstdout fi cygpath_u="cygpath -u -f -" if test "$depmode" = msvcmsys; then # This is just like msvisualcpp but w/o cygpath translation. # Just convert the backslash-escaped backslashes to single forward # slashes to satisfy depend.m4 cygpath_u='sed s,\\\\,/,g' depmode=msvisualcpp fi if test "$depmode" = msvc7msys; then # This is just like msvc7 but w/o cygpath translation. # Just convert the backslash-escaped backslashes to single forward # slashes to satisfy depend.m4 cygpath_u='sed s,\\\\,/,g' depmode=msvc7 fi if test "$depmode" = xlc; then # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information. gccflag=-qmakedep=gcc,-MF depmode=gcc fi case "$depmode" in gcc3) ## gcc 3 implements dependency tracking that does exactly what ## we want. Yay! Note: for some reason libtool 1.4 doesn't like ## it if -MD -MP comes after the -MF stuff. Hmm. ## Unfortunately, FreeBSD c89 acceptance of flags depends upon ## the command line argument order; so add the flags where they ## appear in depend2.am. Note that the slowdown incurred here ## affects only configure: in makefiles, %FASTDEP% shortcuts this. for arg do case $arg in -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;; *) set fnord "$@" "$arg" ;; esac shift # fnord shift # $arg done "$@" stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi mv "$tmpdepfile" "$depfile" ;; gcc) ## Note that this doesn't just cater to obsolete pre-3.x GCC compilers. ## but also to in-use compilers like IBM xlc/xlC and the HP C compiler. ## (see the conditional assignment to $gccflag above). ## There are various ways to get dependency output from gcc. Here's ## why we pick this rather obscure method: ## - Don't want to use -MD because we'd like the dependencies to end ## up in a subdir. Having to rename by hand is ugly. ## (We might end up doing this anyway to support other compilers.) ## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like ## -MM, not -M (despite what the docs say). Also, it might not be ## supported by the other compilers which use the 'gcc' depmode. ## - Using -M directly means running the compiler twice (even worse ## than renaming). if test -z "$gccflag"; then gccflag=-MD, fi "$@" -Wp,"$gccflag$tmpdepfile" stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" echo "$object : \\" > "$depfile" # The second -e expression handles DOS-style file names with drive # letters. sed -e 's/^[^:]*: / /' \ -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" ## This next piece of magic avoids the "deleted header file" problem. ## The problem is that when a header file which appears in a .P file ## is deleted, the dependency causes make to die (because there is ## typically no way to rebuild the header). We avoid this by adding ## dummy dependencies for each header file. Too bad gcc doesn't do ## this for us directly. ## Some versions of gcc put a space before the ':'. On the theory ## that the space means something, we add a space to the output as ## well. hp depmode also adds that space, but also prefixes the VPATH ## to the object. Take care to not repeat it in the output. ## Some versions of the HPUX 10.20 sed can't process this invocation ## correctly. Breaking it into two sed invocations is a workaround. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; hp) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; sgi) if test "$libtool" = yes; then "$@" "-Wp,-MDupdate,$tmpdepfile" else "$@" -MDupdate "$tmpdepfile" fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files echo "$object : \\" > "$depfile" # Clip off the initial element (the dependent). Don't try to be # clever and replace this with sed code, as IRIX sed won't handle # lines with more than a fixed number of characters (4096 in # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; # the IRIX cc adds comments like '#:fec' to the end of the # dependency line. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \ | tr "$nl" ' ' >> "$depfile" echo >> "$depfile" # The second pass generates a dummy entry for each header file. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ >> "$depfile" else make_dummy_depfile fi rm -f "$tmpdepfile" ;; xlc) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; aix) # The C for AIX Compiler uses -M and outputs the dependencies # in a .u file. In older versions, this file always lives in the # current directory. Also, the AIX compiler puts '$object:' at the # start of each line; $object doesn't have directory information. # Version 6 uses the directory in both cases. set_dir_from "$object" set_base_from "$object" if test "$libtool" = yes; then tmpdepfile1=$dir$base.u tmpdepfile2=$base.u tmpdepfile3=$dir.libs/$base.u "$@" -Wc,-M else tmpdepfile1=$dir$base.u tmpdepfile2=$dir$base.u tmpdepfile3=$dir$base.u "$@" -M fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" exit $stat fi for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" do test -f "$tmpdepfile" && break done aix_post_process_depfile ;; tcc) # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26 # FIXME: That version still under development at the moment of writing. # Make that this statement remains true also for stable, released # versions. # It will wrap lines (doesn't matter whether long or short) with a # trailing '\', as in: # # foo.o : \ # foo.c \ # foo.h \ # # It will put a trailing '\' even on the last line, and will use leading # spaces rather than leading tabs (at least since its commit 0394caf7 # "Emit spaces for -MD"). "$@" -MD -MF "$tmpdepfile" stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'. # We have to change lines of the first kind to '$object: \'. sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile" # And for each line of the second kind, we have to emit a 'dep.h:' # dummy dependency, to avoid the deleted-header problem. sed -n -e 's|^ *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile" rm -f "$tmpdepfile" ;; ## The order of this option in the case statement is important, since the ## shell code in configure will try each of these formats in the order ## listed in this file. A plain '-MD' option would be understood by many ## compilers, so we must ensure this comes after the gcc and icc options. pgcc) # Portland's C compiler understands '-MD'. # Will always output deps to 'file.d' where file is the root name of the # source file under compilation, even if file resides in a subdirectory. # The object file name does not affect the name of the '.d' file. # pgcc 10.2 will output # foo.o: sub/foo.c sub/foo.h # and will wrap long lines using '\' : # foo.o: sub/foo.c ... \ # sub/foo.h ... \ # ... set_dir_from "$object" # Use the source, not the object, to determine the base name, since # that's sadly what pgcc will do too. set_base_from "$source" tmpdepfile=$base.d # For projects that build the same source file twice into different object # files, the pgcc approach of using the *source* file root name can cause # problems in parallel builds. Use a locking strategy to avoid stomping on # the same $tmpdepfile. lockdir=$base.d-lock trap " echo '$0: caught signal, cleaning up...' >&2 rmdir '$lockdir' exit 1 " 1 2 13 15 numtries=100 i=$numtries while test $i -gt 0; do # mkdir is a portable test-and-set. if mkdir "$lockdir" 2>/dev/null; then # This process acquired the lock. "$@" -MD stat=$? # Release the lock. rmdir "$lockdir" break else # If the lock is being held by a different process, wait # until the winning process is done or we timeout. while test -d "$lockdir" && test $i -gt 0; do sleep 1 i=`expr $i - 1` done fi i=`expr $i - 1` done trap - 1 2 13 15 if test $i -le 0; then echo "$0: failed to acquire lock after $numtries attempts" >&2 echo "$0: check lockdir '$lockdir'" >&2 exit 1 fi if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" # Each line is of the form `foo.o: dependent.h', # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. # Do two passes, one to just change these to # `$object: dependent.h' and one to simply `dependent.h:'. sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" # Some versions of the HPUX 10.20 sed can't process this invocation # correctly. Breaking it into two sed invocations is a workaround. sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; hp2) # The "hp" stanza above does not work with aCC (C++) and HP's ia64 # compilers, which have integrated preprocessors. The correct option # to use with these is +Maked; it writes dependencies to a file named # 'foo.d', which lands next to the object file, wherever that # happens to be. # Much of this is similar to the tru64 case; see comments there. set_dir_from "$object" set_base_from "$object" if test "$libtool" = yes; then tmpdepfile1=$dir$base.d tmpdepfile2=$dir.libs/$base.d "$@" -Wc,+Maked else tmpdepfile1=$dir$base.d tmpdepfile2=$dir$base.d "$@" +Maked fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile1" "$tmpdepfile2" exit $stat fi for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" do test -f "$tmpdepfile" && break done if test -f "$tmpdepfile"; then sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile" # Add 'dependent.h:' lines. sed -ne '2,${ s/^ *// s/ \\*$// s/$/:/ p }' "$tmpdepfile" >> "$depfile" else make_dummy_depfile fi rm -f "$tmpdepfile" "$tmpdepfile2" ;; tru64) # The Tru64 compiler uses -MD to generate dependencies as a side # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'. # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put # dependencies in 'foo.d' instead, so we check for that too. # Subdirectories are respected. set_dir_from "$object" set_base_from "$object" if test "$libtool" = yes; then # Libtool generates 2 separate objects for the 2 libraries. These # two compilations output dependencies in $dir.libs/$base.o.d and # in $dir$base.o.d. We have to check for both files, because # one of the two compilations can be disabled. We should prefer # $dir$base.o.d over $dir.libs/$base.o.d because the latter is # automatically cleaned when .libs/ is deleted, while ignoring # the former would cause a distcleancheck panic. tmpdepfile1=$dir$base.o.d # libtool 1.5 tmpdepfile2=$dir.libs/$base.o.d # Likewise. tmpdepfile3=$dir.libs/$base.d # Compaq CCC V6.2-504 "$@" -Wc,-MD else tmpdepfile1=$dir$base.d tmpdepfile2=$dir$base.d tmpdepfile3=$dir$base.d "$@" -MD fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" exit $stat fi for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" do test -f "$tmpdepfile" && break done # Same post-processing that is required for AIX mode. aix_post_process_depfile ;; msvc7) if test "$libtool" = yes; then showIncludes=-Wc,-showIncludes else showIncludes=-showIncludes fi "$@" $showIncludes > "$tmpdepfile" stat=$? grep -v '^Note: including file: ' "$tmpdepfile" if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" echo "$object : \\" > "$depfile" # The first sed program below extracts the file names and escapes # backslashes for cygpath. The second sed program outputs the file # name when reading, but also accumulates all include files in the # hold buffer in order to output them again at the end. This only # works with sed implementations that can handle large buffers. sed < "$tmpdepfile" -n ' /^Note: including file: *\(.*\)/ { s//\1/ s/\\/\\\\/g p }' | $cygpath_u | sort -u | sed -n ' s/ /\\ /g s/\(.*\)/'"$tab"'\1 \\/p s/.\(.*\) \\/\1:/ H $ { s/.*/'"$tab"'/ G p }' >> "$depfile" echo >> "$depfile" # make sure the fragment doesn't end with a backslash rm -f "$tmpdepfile" ;; msvc7msys) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; #nosideeffect) # This comment above is used by automake to tell side-effect # dependency tracking mechanisms from slower ones. dashmstdout) # Important note: in order to support this mode, a compiler *must* # always write the preprocessed file to stdout, regardless of -o. "$@" || exit $? # Remove the call to Libtool. if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi # Remove '-o $object'. IFS=" " for arg do case $arg in -o) shift ;; $object) shift ;; *) set fnord "$@" "$arg" shift # fnord shift # $arg ;; esac done test -z "$dashmflag" && dashmflag=-M # Require at least two characters before searching for ':' # in the target name. This is to cope with DOS-style filenames: # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise. "$@" $dashmflag | sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile" rm -f "$depfile" cat < "$tmpdepfile" > "$depfile" # Some versions of the HPUX 10.20 sed can't process this sed invocation # correctly. Breaking it into two sed invocations is a workaround. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; dashXmstdout) # This case only exists to satisfy depend.m4. It is never actually # run, as this mode is specially recognized in the preamble. exit 1 ;; makedepend) "$@" || exit $? # Remove any Libtool call if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi # X makedepend shift cleared=no eat=no for arg do case $cleared in no) set ""; shift cleared=yes ;; esac if test $eat = yes; then eat=no continue fi case "$arg" in -D*|-I*) set fnord "$@" "$arg"; shift ;; # Strip any option that makedepend may not understand. Remove # the object too, otherwise makedepend will parse it as a source file. -arch) eat=yes ;; -*|$object) ;; *) set fnord "$@" "$arg"; shift ;; esac done obj_suffix=`echo "$object" | sed 's/^.*\././'` touch "$tmpdepfile" ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" rm -f "$depfile" # makedepend may prepend the VPATH from the source file name to the object. # No need to regex-escape $object, excess matching of '.' is harmless. sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile" # Some versions of the HPUX 10.20 sed can't process the last invocation # correctly. Breaking it into two sed invocations is a workaround. sed '1,2d' "$tmpdepfile" \ | tr ' ' "$nl" \ | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" "$tmpdepfile".bak ;; cpp) # Important note: in order to support this mode, a compiler *must* # always write the preprocessed file to stdout. "$@" || exit $? # Remove the call to Libtool. if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi # Remove '-o $object'. IFS=" " for arg do case $arg in -o) shift ;; $object) shift ;; *) set fnord "$@" "$arg" shift # fnord shift # $arg ;; esac done "$@" -E \ | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ | sed '$ s: \\$::' > "$tmpdepfile" rm -f "$depfile" echo "$object : \\" > "$depfile" cat < "$tmpdepfile" >> "$depfile" sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; msvisualcpp) # Important note: in order to support this mode, a compiler *must* # always write the preprocessed file to stdout. "$@" || exit $? # Remove the call to Libtool. if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi IFS=" " for arg do case "$arg" in -o) shift ;; $object) shift ;; "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI") set fnord "$@" shift shift ;; *) set fnord "$@" "$arg" shift shift ;; esac done "$@" -E 2>/dev/null | sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile" rm -f "$depfile" echo "$object : \\" > "$depfile" sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile" echo "$tab" >> "$depfile" sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile" rm -f "$tmpdepfile" ;; msvcmsys) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; none) exec "$@" ;; *) echo "Unknown depmode $depmode" 1>&2 exit 1 ;; esac exit 0 # Local Variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'before-save-hook 'time-stamp nil t) # time-stamp-start: "scriptversion=" # time-stamp-format: "%Y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: bluez-5.87/PaxHeaders/INSTALL0000644000000000000000000000005011071665350012710 xustar0020 atime=1770587285 20 ctime=1783094098 bluez-5.87/INSTALL0000644000000000000000000002240611071665350012375 0ustar00rootrootInstallation Instructions ************************* Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005 Free Software Foundation, Inc. This file is free documentation; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. Basic Installation ================== These are generic installation instructions. The `configure' shell script attempts to guess correct values for various system-dependent variables used during compilation. It uses those values to create a `Makefile' in each directory of the package. It may also create one or more `.h' files containing system-dependent definitions. Finally, it creates a shell script `config.status' that you can run in the future to recreate the current configuration, and a file `config.log' containing compiler output (useful mainly for debugging `configure'). It can also use an optional file (typically called `config.cache' and enabled with `--cache-file=config.cache' or simply `-C') that saves the results of its tests to speed up reconfiguring. (Caching is disabled by default to prevent problems with accidental use of stale cache files.) If you need to do unusual things to compile the package, please try to figure out how `configure' could check whether to do them, and mail diffs or instructions to the address given in the `README' so they can be considered for the next release. If you are using the cache, and at some point `config.cache' contains results you don't want to keep, you may remove or edit it. The file `configure.ac' (or `configure.in') is used to create `configure' by a program called `autoconf'. You only need `configure.ac' if you want to change it or regenerate `configure' using a newer version of `autoconf'. The simplest way to compile this package is: 1. `cd' to the directory containing the package's source code and type `./configure' to configure the package for your system. If you're using `csh' on an old version of System V, you might need to type `sh ./configure' instead to prevent `csh' from trying to execute `configure' itself. Running `configure' takes awhile. While running, it prints some messages telling which features it is checking for. 2. Type `make' to compile the package. 3. Optionally, type `make check' to run any self-tests that come with the package. 4. Type `make install' to install the programs and any data files and documentation. 5. You can remove the program binaries and object files from the source code directory by typing `make clean'. To also remove the files that `configure' created (so you can compile the package for a different kind of computer), type `make distclean'. There is also a `make maintainer-clean' target, but that is intended mainly for the package's developers. If you use it, you may have to get all sorts of other programs in order to regenerate files that came with the distribution. Compilers and Options ===================== Some systems require unusual options for compilation or linking that the `configure' script does not know about. Run `./configure --help' for details on some of the pertinent environment variables. You can give `configure' initial values for configuration parameters by setting variables in the command line or in the environment. Here is an example: ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix *Note Defining Variables::, for more details. Compiling For Multiple Architectures ==================================== You can compile the package for more than one kind of computer at the same time, by placing the object files for each architecture in their own directory. To do this, you must use a version of `make' that supports the `VPATH' variable, such as GNU `make'. `cd' to the directory where you want the object files and executables to go and run the `configure' script. `configure' automatically checks for the source code in the directory that `configure' is in and in `..'. If you have to use a `make' that does not support the `VPATH' variable, you have to compile the package for one architecture at a time in the source code directory. After you have installed the package for one architecture, use `make distclean' before reconfiguring for another architecture. Installation Names ================== By default, `make install' will install the package's files in `/usr/local/bin', `/usr/local/man', etc. You can specify an installation prefix other than `/usr/local' by giving `configure' the option `--prefix=PREFIX'. You can specify separate installation prefixes for architecture-specific files and architecture-independent files. If you give `configure' the option `--exec-prefix=PREFIX', the package will use PREFIX as the prefix for installing programs and libraries. Documentation and other data files will still use the regular prefix. In addition, if you use an unusual directory layout you can give options like `--bindir=DIR' to specify different values for particular kinds of files. Run `configure --help' for a list of the directories you can set and what kinds of files go in them. If the package supports it, you can cause programs to be installed with an extra prefix or suffix on their names by giving `configure' the option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. Optional Features ================= Some packages pay attention to `--enable-FEATURE' options to `configure', where FEATURE indicates an optional part of the package. They may also pay attention to `--with-PACKAGE' options, where PACKAGE is something like `gnu-as' or `x' (for the X Window System). The `README' should mention any `--enable-' and `--with-' options that the package recognizes. For packages that use the X Window System, `configure' can usually find the X include and library files automatically, but if it doesn't, you can use the `configure' options `--x-includes=DIR' and `--x-libraries=DIR' to specify their locations. Specifying the System Type ========================== There may be some features `configure' cannot figure out automatically, but needs to determine by the type of machine the package will run on. Usually, assuming the package is built to be run on the _same_ architectures, `configure' can figure that out, but if it prints a message saying it cannot guess the machine type, give it the `--build=TYPE' option. TYPE can either be a short name for the system type, such as `sun4', or a canonical name which has the form: CPU-COMPANY-SYSTEM where SYSTEM can have one of these forms: OS KERNEL-OS See the file `config.sub' for the possible values of each field. If `config.sub' isn't included in this package, then this package doesn't need to know the machine type. If you are _building_ compiler tools for cross-compiling, you should use the `--target=TYPE' option to select the type of system they will produce code for. If you want to _use_ a cross compiler, that generates code for a platform different from the build platform, you should specify the "host" platform (i.e., that on which the generated programs will eventually be run) with `--host=TYPE'. Sharing Defaults ================ If you want to set default values for `configure' scripts to share, you can create a site shell script called `config.site' that gives default values for variables like `CC', `cache_file', and `prefix'. `configure' looks for `PREFIX/share/config.site' if it exists, then `PREFIX/etc/config.site' if it exists. Or, you can set the `CONFIG_SITE' environment variable to the location of the site script. A warning: not all `configure' scripts look for a site script. Defining Variables ================== Variables not defined in a site shell script can be set in the environment passed to `configure'. However, some packages may run configure again during the build, and the customized values of these variables may be lost. In order to avoid this problem, you should set them in the `configure' command line, using `VAR=value'. For example: ./configure CC=/usr/local2/bin/gcc causes the specified `gcc' to be used as the C compiler (unless it is overridden in the site shell script). Here is a another example: /bin/bash ./configure CONFIG_SHELL=/bin/bash Here the `CONFIG_SHELL=/bin/bash' operand causes subsequent configuration-related scripts to be executed by `/bin/bash'. `configure' Invocation ====================== `configure' recognizes the following options to control how it operates. `--help' `-h' Print a summary of the options to `configure', and exit. `--version' `-V' Print the version of Autoconf used to generate the `configure' script, and exit. `--cache-file=FILE' Enable the cache: use and save the results of the tests in FILE, traditionally `config.cache'. FILE defaults to `/dev/null' to disable caching. `--config-cache' `-C' Alias for `--cache-file=config.cache'. `--quiet' `--silent' `-q' Do not print messages saying which checks are being made. To suppress all normal output, redirect it to `/dev/null' (any error messages will still be shown). `--srcdir=DIR' Look for the package's source code in directory DIR. Usually `configure' can determine that directory automatically. `configure' also accepts some other, not widely useful, options. Run `configure --help' for more details. bluez-5.87/PaxHeaders/peripheral0000644000000000000000000000005015221755531013737 xustar0020 atime=1783094113 20 ctime=1783094105 bluez-5.87/peripheral/0000755000000000000000000000000015221755531013475 5ustar00rootrootbluez-5.87/peripheral/PaxHeaders/gatt.h0000644000000000000000000000005014015011623015107 xustar0020 atime=1770587291 20 ctime=1783094105 bluez-5.87/peripheral/gatt.h0000644000000000000000000000055614015011623014576 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Intel Corporation. All rights reserved. * * */ #include void gatt_set_static_address(uint8_t addr[6]); void gatt_set_device_name(uint8_t name[20], uint8_t len); void gatt_server_start(void); void gatt_server_stop(void); bluez-5.87/peripheral/PaxHeaders/efivars.c0000644000000000000000000000005014711225434015614 xustar0020 atime=1770587291 20 ctime=1783094105 bluez-5.87/peripheral/efivars.c0000644000000000000000000000446614711225434015307 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include "peripheral/efivars.h" #define SYSFS_EFIVARS "/sys/firmware/efi/efivars" typedef struct { uint32_t data1; uint16_t data2; uint16_t data3; uint8_t data4[8]; } efi_guid_t; #define VENDOR_GUID \ (efi_guid_t) { 0xd5f9d775, 0x1a09, 0x4e89, \ { 0x96, 0xcf, 0x1d, 0x19, 0x55, 0x4d, 0xa6, 0x67 } } static void efivars_pathname(const char *name, char *pathname, size_t size) { static efi_guid_t guid = VENDOR_GUID; snprintf(pathname, size - 1, "%s/%s-%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", SYSFS_EFIVARS, name, guid.data1, guid.data2, guid.data3, guid.data4[0], guid.data4[1], guid.data4[2], guid.data4[3], guid.data4[4], guid.data4[5], guid.data4[6], guid.data4[7]); } int efivars_read(const char *name, uint32_t *attributes, void *data, size_t size) { char pathname[PATH_MAX]; struct iovec iov[2]; uint32_t attr; ssize_t len; int fd; efivars_pathname(name, pathname, PATH_MAX); fd = open(pathname, O_RDONLY | O_CLOEXEC); if (fd < 0) return -EIO; iov[0].iov_base = &attr; iov[0].iov_len = sizeof(attr); iov[1].iov_base = data; iov[1].iov_len = size; len = readv(fd, iov, 2); close(fd); if (len < 0) return -EIO; if (attributes) *attributes = attr; return 0; } int efivars_write(const char *name, uint32_t attributes, const void *data, size_t size) { char pathname[PATH_MAX]; void *buf; ssize_t written; int fd; efivars_pathname(name, pathname, PATH_MAX); buf = malloc(size + sizeof(attributes)); if (!buf) return -ENOMEM; fd = open(pathname, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0644); if (fd < 0) { free(buf); return -EIO; } memcpy(buf, &attributes, sizeof(attributes)); memcpy(buf + sizeof(attributes), data, size); written = write(fd, buf, size + sizeof(attributes)); close(fd); free(buf); if (written < 0) return -EIO; return 0; } bluez-5.87/peripheral/PaxHeaders/log.h0000644000000000000000000000005014015011623014731 xustar0020 atime=1770587291 20 ctime=1783094105 bluez-5.87/peripheral/log.h0000644000000000000000000000033714015011623014415 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Intel Corporation. All rights reserved. * * */ void log_open(void); void log_close(void); bluez-5.87/peripheral/PaxHeaders/attach.c0000644000000000000000000000005015063045254015422 xustar0020 atime=1770587291 20 ctime=1783094105 bluez-5.87/peripheral/attach.c0000644000000000000000000000451015063045254015103 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/hci_lib.h" #include "tools/hciattach.h" #include "peripheral/attach.h" static const char *serial_dev = "/dev/ttyS1"; static int serial_fd = -1; static int open_serial(const char *path) { struct termios ti; int fd, saved_ldisc, ldisc = N_HCI; fd = open(path, O_RDWR | O_NOCTTY); if (fd < 0) { perror("Failed to open serial port"); return -1; } if (tcflush(fd, TCIOFLUSH) < 0) { perror("Failed to flush serial port"); close(fd); return -1; } if (ioctl(fd, TIOCGETD, &saved_ldisc) < 0) { perror("Failed get serial line discipline"); close(fd); return -1; } /* Switch TTY to raw mode */ memset(&ti, 0, sizeof(ti)); cfmakeraw(&ti); ti.c_cflag |= (B115200 | CLOCAL | CREAD); /* Set flow control */ ti.c_cflag |= CRTSCTS; if (tcsetattr(fd, TCSANOW, &ti) < 0) { perror("Failed to set serial port settings"); close(fd); return -1; } if (ioctl(fd, TIOCSETD, &ldisc) < 0) { perror("Failed set serial line discipline"); close(fd); return -1; } printf("Switched line discipline from %d to %d\n", saved_ldisc, ldisc); return fd; } static int attach_proto(const char *path, unsigned int proto, unsigned int flags) { int fd, dev_id; fd = open_serial(path); if (fd < 0) return -1; if (ioctl(fd, HCIUARTSETFLAGS, flags) < 0) { perror("Failed to set flags"); close(fd); return -1; } if (ioctl(fd, HCIUARTSETPROTO, proto) < 0) { perror("Failed to set protocol"); close(fd); return -1; } dev_id = ioctl(fd, HCIUARTGETDEVICE); if (dev_id < 0) { perror("Failed to get device id"); close(fd); return -1; } printf("Device index %d attached\n", dev_id); return fd; } void attach_start(void) { unsigned long flags; if (serial_fd >= 0) return; printf("Attaching BR/EDR controller to %s\n", serial_dev); flags = (1 << HCI_UART_RESET_ON_INIT); serial_fd = attach_proto(serial_dev, HCI_UART_H4, flags); } void attach_stop(void) { if (serial_fd < 0) return; close(serial_fd); serial_fd = -1; } bluez-5.87/peripheral/PaxHeaders/gap.h0000644000000000000000000000005014015011623014717 xustar0020 atime=1770587291 20 ctime=1783094105 bluez-5.87/peripheral/gap.h0000644000000000000000000000044314015011623014401 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Intel Corporation. All rights reserved. * * */ #include void gap_set_static_address(uint8_t addr[6]); void gap_start(void); void gap_stop(void); bluez-5.87/peripheral/PaxHeaders/attach.h0000644000000000000000000000005014015011623015414 xustar0020 atime=1770587291 20 ctime=1783094105 bluez-5.87/peripheral/attach.h0000644000000000000000000000034514015011623015077 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Intel Corporation. All rights reserved. * * */ void attach_start(void); void attach_stop(void); bluez-5.87/peripheral/PaxHeaders/gap.c0000644000000000000000000000005015063045254014725 xustar0020 atime=1770587291 20 ctime=1783094105 bluez-5.87/peripheral/gap.c0000644000000000000000000003231715063045254014414 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/mgmt.h" #include "src/shared/util.h" #include "src/shared/mgmt.h" #include "peripheral/gatt.h" #include "peripheral/gap.h" static struct mgmt *mgmt = NULL; static uint16_t mgmt_index = MGMT_INDEX_NONE; static bool adv_features = false; static bool adv_instances = false; static bool require_connectable = true; static uint8_t static_addr[6] = { 0x90, 0x78, 0x56, 0x34, 0x12, 0xc0 }; static uint8_t dev_name[260] = { 0x00, }; static uint8_t dev_name_len = 0; void gap_set_static_address(uint8_t addr[6]) { memcpy(static_addr, addr, sizeof(static_addr)); printf("Using static address %02x:%02x:%02x:%02x:%02x:%02x\n", static_addr[5], static_addr[4], static_addr[3], static_addr[2], static_addr[1], static_addr[0]); } static void clear_long_term_keys(uint16_t index) { struct mgmt_cp_load_long_term_keys cp; memset(&cp, 0, sizeof(cp)); cp.key_count = cpu_to_le16(0); mgmt_send(mgmt, MGMT_OP_LOAD_LONG_TERM_KEYS, index, sizeof(cp), &cp, NULL, NULL, NULL); } static void clear_identity_resolving_keys(uint16_t index) { struct mgmt_cp_load_irks cp; memset(&cp, 0, sizeof(cp)); cp.irk_count = cpu_to_le16(0); mgmt_send(mgmt, MGMT_OP_LOAD_IRKS, index, sizeof(cp), &cp, NULL, NULL, NULL); } static void add_advertising(uint16_t index) { const char ad[] = { 0x11, 0x15, 0xd0, 0x00, 0x2d, 0x12, 0x1e, 0x4b, 0x0f, 0xa4, 0x99, 0x4e, 0xce, 0xb5, 0x31, 0xf4, 0x05, 0x79 }; struct mgmt_cp_add_advertising *cp; void *buf; buf = malloc(sizeof(*cp) + sizeof(ad)); if (!buf) return; memset(buf, 0, sizeof(*cp) + sizeof(ad)); cp = buf; cp->instance = 0x01; cp->flags = cpu_to_le32((1 << 0) | (1 << 1) | (1 << 4)); cp->duration = cpu_to_le16(0); cp->timeout = cpu_to_le16(0); cp->adv_data_len = sizeof(ad); cp->scan_rsp_len = 0; memcpy(cp->data, ad, sizeof(ad)); mgmt_send(mgmt, MGMT_OP_ADD_ADVERTISING, index, sizeof(*cp) + sizeof(ad), buf, NULL, NULL, NULL); free(buf); } static void enable_advertising(uint16_t index) { uint8_t val; val = require_connectable ? 0x01 : 0x00; mgmt_send(mgmt, MGMT_OP_SET_CONNECTABLE, index, 1, &val, NULL, NULL, NULL); val = 0x01; mgmt_send(mgmt, MGMT_OP_SET_POWERED, index, 1, &val, NULL, NULL, NULL); if (adv_instances) { add_advertising(index); return; } val = require_connectable ? 0x01 : 0x02; mgmt_send(mgmt, MGMT_OP_SET_ADVERTISING, index, 1, &val, NULL, NULL, NULL); } static void new_settings_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("New settings\n"); } static void local_name_changed_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("Local name changed\n"); } static void new_long_term_key_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("New long term key\n"); } static void device_connected_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("Device connected\n"); } static void device_disconnected_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("Device disconnected\n"); } static void user_confirm_request_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("User confirm request\n"); } static void user_passkey_request_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("User passkey request\n"); } static void auth_failed_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("Authentication failed\n"); } static void device_unpaired_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("Device unpaired\n"); } static void passkey_notify_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("Passkey notification\n"); } static void new_irk_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("New identify resolving key\n"); } static void new_csrk_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("New connection signature resolving key\n"); } static void new_conn_param_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("New connection parameter\n"); } static void advertising_added_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("Advertising added\n"); } static void advertising_removed_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("Advertising removed\n"); } static void read_adv_features_complete(uint8_t status, uint16_t len, const void *param, void *user_data) { const struct mgmt_rp_read_adv_features *rp = param; uint16_t index = PTR_TO_UINT(user_data); uint32_t flags; flags = le32_to_cpu(rp->supported_flags); if (rp->max_instances > 0) { adv_instances = true; if (flags & (1 << 0)) require_connectable = false; } else require_connectable = false; enable_advertising(index); } static void read_info_complete(uint8_t status, uint16_t len, const void *param, void *user_data) { const struct mgmt_rp_read_info *rp = param; uint16_t index = PTR_TO_UINT(user_data); uint32_t required_settings = MGMT_SETTING_LE | MGMT_SETTING_STATIC_ADDRESS; uint32_t supported_settings, current_settings; uint8_t val; required_settings = MGMT_SETTING_LE; if (status) { fprintf(stderr, "Reading info for index %u failed: %s\n", index, mgmt_errstr(status)); return; } if (mgmt_index != MGMT_INDEX_NONE) return; supported_settings = le32_to_cpu(rp->supported_settings); current_settings = le32_to_cpu(rp->current_settings); if ((supported_settings & required_settings) != required_settings) return; printf("Selecting index %u\n", index); mgmt_index = index; mgmt_register(mgmt, MGMT_EV_NEW_SETTINGS, index, new_settings_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_LOCAL_NAME_CHANGED, index, local_name_changed_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_NEW_LONG_TERM_KEY, index, new_long_term_key_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_DEVICE_CONNECTED, index, device_connected_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_DEVICE_DISCONNECTED, index, device_disconnected_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_USER_CONFIRM_REQUEST, index, user_confirm_request_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_USER_PASSKEY_REQUEST, index, user_passkey_request_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_AUTH_FAILED, index, auth_failed_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_DEVICE_UNPAIRED, index, device_unpaired_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_PASSKEY_NOTIFY, index, passkey_notify_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_NEW_IRK, index, new_irk_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_NEW_CSRK, index, new_csrk_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_NEW_CONN_PARAM, index, new_conn_param_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_ADVERTISING_ADDED, index, advertising_added_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_ADVERTISING_REMOVED, index, advertising_removed_event, NULL, NULL); dev_name_len = snprintf((char *) dev_name, 26, "BlueZ Peripheral"); if (current_settings & MGMT_SETTING_POWERED) { val = 0x00; mgmt_send(mgmt, MGMT_OP_SET_POWERED, index, 1, &val, NULL, NULL, NULL); } if (!(current_settings & MGMT_SETTING_LE)) { val = 0x01; mgmt_send(mgmt, MGMT_OP_SET_LE, index, 1, &val, NULL, NULL, NULL); } if (current_settings & MGMT_SETTING_BREDR) { val = 0x00; mgmt_send(mgmt, MGMT_OP_SET_BREDR, index, 1, &val, NULL, NULL, NULL); } if ((supported_settings & MGMT_SETTING_SECURE_CONN) && !(current_settings & MGMT_SETTING_SECURE_CONN)) { val = 0x01; mgmt_send(mgmt, MGMT_OP_SET_SECURE_CONN, index, 1, &val, NULL, NULL, NULL); } if (current_settings & MGMT_SETTING_DEBUG_KEYS) { val = 0x00; mgmt_send(mgmt, MGMT_OP_SET_DEBUG_KEYS, index, 1, &val, NULL, NULL, NULL); } if (!(current_settings & MGMT_SETTING_BONDABLE)) { val = 0x01; mgmt_send(mgmt, MGMT_OP_SET_BONDABLE, index, 1, &val, NULL, NULL, NULL); } clear_long_term_keys(mgmt_index); clear_identity_resolving_keys(mgmt_index); mgmt_send(mgmt, MGMT_OP_SET_STATIC_ADDRESS, index, 6, static_addr, NULL, NULL, NULL); mgmt_send(mgmt, MGMT_OP_SET_LOCAL_NAME, index, 260, dev_name, NULL, NULL, NULL); gatt_set_static_address(static_addr); gatt_set_device_name(dev_name, dev_name_len); gatt_server_start(); if (adv_features) mgmt_send(mgmt, MGMT_OP_READ_ADV_FEATURES, index, 0, NULL, read_adv_features_complete, UINT_TO_PTR(index), NULL); else enable_advertising(index); } static void read_index_list_complete(uint8_t status, uint16_t len, const void *param, void *user_data) { const struct mgmt_rp_read_index_list *rp = param; uint16_t count; int i; if (status) { fprintf(stderr, "Reading index list failed: %s\n", mgmt_errstr(status)); return; } count = le16_to_cpu(rp->num_controllers); printf("Index list: %u\n", count); for (i = 0; i < count; i++) { uint16_t index = cpu_to_le16(rp->index[i]); mgmt_send(mgmt, MGMT_OP_READ_INFO, index, 0, NULL, read_info_complete, UINT_TO_PTR(index), NULL); } } static void index_added_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("Index added\n"); if (mgmt_index != MGMT_INDEX_NONE) return; mgmt_send(mgmt, MGMT_OP_READ_INFO, index, 0, NULL, read_info_complete, UINT_TO_PTR(index), NULL); } static void index_removed_event(uint16_t index, uint16_t length, const void *param, void *user_data) { printf("Index removed\n"); if (mgmt_index != index) return; mgmt_index = MGMT_INDEX_NONE; } static void read_ext_index_list_complete(uint8_t status, uint16_t len, const void *param, void *user_data) { const struct mgmt_rp_read_ext_index_list *rp = param; uint16_t count; int i; if (status) { fprintf(stderr, "Reading extended index list failed: %s\n", mgmt_errstr(status)); return; } count = le16_to_cpu(rp->num_controllers); printf("Extended index list: %u\n", count); for (i = 0; i < count; i++) { uint16_t index = cpu_to_le16(rp->entry[i].index); if (rp->entry[i].type != 0x00) continue; mgmt_send(mgmt, MGMT_OP_READ_INFO, index, 0, NULL, read_info_complete, UINT_TO_PTR(index), NULL); } } static void ext_index_added_event(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_ext_index_added *ev = param; printf("Extended index added: %u\n", ev->type); if (mgmt_index != MGMT_INDEX_NONE) return; if (ev->type != 0x00) return; mgmt_send(mgmt, MGMT_OP_READ_INFO, index, 0, NULL, read_info_complete, UINT_TO_PTR(index), NULL); } static void ext_index_removed_event(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_ext_index_added *ev = param; printf("Extended index removed: %u\n", ev->type); if (mgmt_index != index) return; if (ev->type != 0x00) return; mgmt_index = MGMT_INDEX_NONE; } static void read_commands_complete(uint8_t status, uint16_t len, const void *param, void *user_data) { const struct mgmt_rp_read_commands *rp = param; uint16_t num_commands; bool ext_index_list = false; int i; if (status) { fprintf(stderr, "Reading index list failed: %s\n", mgmt_errstr(status)); return; } num_commands = le16_to_cpu(rp->num_commands); for (i = 0; i < num_commands; i++) { uint16_t op = get_le16(rp->opcodes + 1); if (op == MGMT_OP_READ_EXT_INDEX_LIST) ext_index_list = true; else if (op == MGMT_OP_READ_ADV_FEATURES) adv_features = true; } if (ext_index_list) { mgmt_register(mgmt, MGMT_EV_EXT_INDEX_ADDED, MGMT_INDEX_NONE, ext_index_added_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_EXT_INDEX_REMOVED, MGMT_INDEX_NONE, ext_index_removed_event, NULL, NULL); if (!mgmt_send(mgmt, MGMT_OP_READ_EXT_INDEX_LIST, MGMT_INDEX_NONE, 0, NULL, read_ext_index_list_complete, NULL, NULL)) { fprintf(stderr, "Failed to read extended index list\n"); return; } } else { mgmt_register(mgmt, MGMT_EV_INDEX_ADDED, MGMT_INDEX_NONE, index_added_event, NULL, NULL); mgmt_register(mgmt, MGMT_EV_INDEX_REMOVED, MGMT_INDEX_NONE, index_removed_event, NULL, NULL); if (!mgmt_send(mgmt, MGMT_OP_READ_INDEX_LIST, MGMT_INDEX_NONE, 0, NULL, read_index_list_complete, NULL, NULL)) { fprintf(stderr, "Failed to read index list\n"); return; } } } void gap_start(void) { mgmt = mgmt_new_default(); if (!mgmt) { fprintf(stderr, "Failed to open management socket\n"); return; } if (!mgmt_send(mgmt, MGMT_OP_READ_COMMANDS, MGMT_INDEX_NONE, 0, NULL, read_commands_complete, NULL, NULL)) { fprintf(stderr, "Failed to read supported commands\n"); return; } } void gap_stop(void) { if (!mgmt) return; gatt_server_stop(); mgmt_unref(mgmt); mgmt = NULL; mgmt_index = MGMT_INDEX_NONE; } bluez-5.87/peripheral/PaxHeaders/efivars.h0000644000000000000000000000005014015011623015607 xustar0020 atime=1770587291 20 ctime=1783094105 bluez-5.87/peripheral/efivars.h0000644000000000000000000000113014015011623015263 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Intel Corporation. All rights reserved. * * */ #define EFIVARS_NON_VOLATILE 0x00000001 #define EFIVARS_BOOTSERVICE_ACCESS 0x00000002 #define EFIVARS_RUNTIME_ACCESS 0x00000004 #define EFIVARS_HARDWARE_ERROR_RECORD 0x00000008 #define EFIVARS_AUTHENTICATED_WRITE_ACCESS 0x00000010 int efivars_read(const char *name, uint32_t *attributes, void *data, size_t size); int efivars_write(const char *name, uint32_t attributes, const void *data, size_t size); bluez-5.87/peripheral/PaxHeaders/gatt.c0000644000000000000000000000005015200607060015105 xustar0020 atime=1783092683 20 ctime=1783094105 bluez-5.87/peripheral/gatt.c0000644000000000000000000001460315200607060014572 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/l2cap.h" #include "bluetooth/uuid.h" #include "src/shared/mainloop.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-server.h" #include "src/shared/gatt-client.h" #include "peripheral/gatt.h" #define UUID_GAP 0x1800 struct gatt_conn { struct bt_att *att; struct bt_gatt_server *gatt; struct bt_gatt_client *client; }; static int att_fd = -1; static struct queue *conn_list = NULL; static struct gatt_db *gatt_db = NULL; static struct gatt_db *gatt_cache = NULL; static uint8_t static_addr[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static uint8_t dev_name[20]; static uint8_t dev_name_len = 0; void gatt_set_static_address(uint8_t addr[6]) { memcpy(static_addr, addr, sizeof(static_addr)); } void gatt_set_device_name(uint8_t name[20], uint8_t len) { memcpy(dev_name, name, sizeof(dev_name)); dev_name_len = len; } static void gatt_conn_destroy(void *data) { struct gatt_conn *conn = data; bt_gatt_client_unref(conn->client); bt_gatt_server_unref(conn->gatt); bt_att_unref(conn->att); free(conn); } static void gatt_conn_disconnect(int err, void *user_data) { struct gatt_conn *conn = user_data; printf("Device disconnected: %s\n", strerror(err)); queue_remove(conn_list, conn); gatt_conn_destroy(conn); } static void client_ready_callback(bool success, uint8_t att_ecode, void *user_data) { printf("GATT client discovery complete\n"); } static void client_service_changed_callback(uint16_t start_handle, uint16_t end_handle, void *user_data) { printf("GATT client service changed notification\n"); } static struct gatt_conn *gatt_conn_new(int fd) { struct gatt_conn *conn; uint16_t mtu = 0; conn = new0(struct gatt_conn, 1); if (!conn) return NULL; conn->att = bt_att_new(fd, false); if (!conn->att) { fprintf(stderr, "Failed to initialize ATT transport layer\n"); free(conn); return NULL; } bt_att_set_close_on_unref(conn->att, true); bt_att_register_disconnect(conn->att, gatt_conn_disconnect, conn, NULL); bt_att_set_security(conn->att, BT_SECURITY_MEDIUM); conn->gatt = bt_gatt_server_new(gatt_db, conn->att, mtu, 0); if (!conn->gatt) { fprintf(stderr, "Failed to create GATT server\n"); bt_att_unref(conn->att); free(conn); return NULL; } conn->client = bt_gatt_client_new(gatt_cache, conn->att, mtu, 0); if (!conn->gatt) { fprintf(stderr, "Failed to create GATT client\n"); bt_gatt_server_unref(conn->gatt); bt_att_unref(conn->att); free(conn); return NULL; } bt_gatt_client_ready_register(conn->client, client_ready_callback, conn, NULL); bt_gatt_client_set_service_changed(conn->client, client_service_changed_callback, conn, NULL); return conn; } static void att_conn_callback(int fd, uint32_t events, void *user_data) { struct gatt_conn *conn; struct sockaddr_l2 addr; socklen_t addrlen; int new_fd; if (events & (EPOLLERR | EPOLLHUP)) { mainloop_remove_fd(fd); return; } memset(&addr, 0, sizeof(addr)); addrlen = sizeof(addr); new_fd = accept(att_fd, (struct sockaddr *) &addr, &addrlen); if (new_fd < 0) { fprintf(stderr, "Failed to accept new ATT connection: %m\n"); return; } conn = gatt_conn_new(new_fd); if (!conn) { fprintf(stderr, "Failed to create GATT connection\n"); close(new_fd); return; } if (!queue_push_tail(conn_list, conn)) { fprintf(stderr, "Failed to add GATT connection\n"); gatt_conn_destroy(conn); close(new_fd); } printf("New device connected\n"); } static void gap_device_name_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { uint8_t error; const uint8_t *value; size_t len; if (offset > dev_name_len) { error = BT_ATT_ERROR_INVALID_OFFSET; value = NULL; len = dev_name_len; } else { error = 0; len = dev_name_len - offset; value = len ? &dev_name[offset] : NULL; } gatt_db_attribute_read_result(attrib, id, error, value, len); } static void populate_gap_service(struct gatt_db *db) { struct gatt_db_attribute *service; bt_uuid_t uuid; bt_uuid16_create(&uuid, UUID_GAP); service = gatt_db_add_service(db, &uuid, true, 6); bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME); gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, gap_device_name_read, NULL, NULL); gatt_db_service_set_active(service, true); } static void populate_devinfo_service(struct gatt_db *db) { struct gatt_db_attribute *service; bt_uuid_t uuid; bt_uuid16_create(&uuid, 0x180a); service = gatt_db_add_service(db, &uuid, true, 17); gatt_db_service_set_active(service, true); } void gatt_server_start(void) { struct sockaddr_l2 addr; if (att_fd >= 0) return; att_fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET | SOCK_CLOEXEC, BTPROTO_L2CAP); if (att_fd < 0) { fprintf(stderr, "Failed to create ATT server socket: %m\n"); return; } memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; addr.l2_cid = htobs(BT_ATT_CID); memcpy(&addr.l2_bdaddr, static_addr, 6); addr.l2_bdaddr_type = BDADDR_LE_RANDOM; if (bind(att_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { fprintf(stderr, "Failed to bind ATT server socket: %m\n"); close(att_fd); att_fd = -1; return; } if (listen(att_fd, 1) < 0) { fprintf(stderr, "Failed to listen on ATT server socket: %m\n"); close(att_fd); att_fd = -1; return; } gatt_db = gatt_db_new(); if (!gatt_db) { close(att_fd); att_fd = -1; return; } populate_gap_service(gatt_db); populate_devinfo_service(gatt_db); gatt_cache = gatt_db_new(); conn_list = queue_new(); if (!conn_list) { gatt_db_unref(gatt_db); gatt_db = NULL; close(att_fd); att_fd = -1; return; } mainloop_add_fd(att_fd, EPOLLIN, att_conn_callback, NULL, NULL); } void gatt_server_stop(void) { if (att_fd < 0) return; mainloop_remove_fd(att_fd); queue_destroy(conn_list, gatt_conn_destroy); gatt_db_unref(gatt_cache); gatt_cache = NULL; gatt_db_unref(gatt_db); gatt_db = NULL; close(att_fd); att_fd = -1; } bluez-5.87/peripheral/PaxHeaders/main.c0000644000000000000000000000005014214376354015107 xustar0020 atime=1770587290 20 ctime=1783094105 bluez-5.87/peripheral/main.c0000644000000000000000000001052214214376354014570 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #ifndef WAIT_ANY #define WAIT_ANY (-1) #endif #include "src/shared/mainloop.h" #include "src/shared/util.h" #include "peripheral/efivars.h" #include "peripheral/attach.h" #include "peripheral/gap.h" #include "peripheral/log.h" static bool is_init = false; static pid_t shell_pid = -1; static const struct { const char *target; const char *linkpath; } dev_table[] = { { "/proc/self/fd", "/dev/fd" }, { "/proc/self/fd/0", "/dev/stdin" }, { "/proc/self/fd/1", "/dev/stdout" }, { "/proc/self/fd/2", "/dev/stderr" }, { } }; static const struct { const char *fstype; const char *target; const char *options; unsigned long flags; } mount_table[] = { { "sysfs", "/sys", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV }, { "proc", "/proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV }, { "devtmpfs", "/dev", NULL, MS_NOSUID|MS_STRICTATIME }, { "efivarfs", "/sys/firmware/efi/efivars", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV }, { "pstore", "/sys/fs/pstore", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV }, { } }; static void prepare_filesystem(void) { int i; if (!is_init) return; for (i = 0; mount_table[i].fstype && mount_table[i].target; i++) { struct stat st; if (lstat(mount_table[i].target, &st) < 0) { printf("Creating %s\n", mount_table[i].target); if (mkdir(mount_table[i].target, 0755) < 0) perror("Failed to create dir"); } printf("Mounting %s to %s\n", mount_table[i].fstype, mount_table[i].target); if (mount(mount_table[i].fstype, mount_table[i].target, mount_table[i].fstype, mount_table[i].flags, NULL) < 0) perror("Failed to mount filesystem"); } for (i = 0; dev_table[i].target; i++) { printf("Linking %s to %s\n", dev_table[i].linkpath, dev_table[i].target); if (symlink(dev_table[i].target, dev_table[i].linkpath) < 0) perror("Failed to create device symlink"); } } static void run_shell(void) { pid_t pid; printf("Starting shell\n"); pid = fork(); if (pid < 0) { perror("Failed to fork new process"); return; } if (pid == 0) { char *prg_argv[] = { "/bin/sh", NULL }; char *prg_envp[] = { NULL }; execve(prg_argv[0], prg_argv, prg_envp); exit(0); } printf("PID %d created\n", pid); shell_pid = pid; } static void exit_shell(void) { shell_pid = -1; if (!is_init) { mainloop_quit(); return; } run_shell(); } static void signal_callback(int signum, void *user_data) { switch (signum) { case SIGINT: case SIGTERM: mainloop_quit(); break; case SIGCHLD: while (1) { pid_t pid; int status; pid = waitpid(WAIT_ANY, &status, WNOHANG); if (pid < 0 || pid == 0) break; if (WIFEXITED(status)) { printf("PID %d exited (status=%d)\n", pid, WEXITSTATUS(status)); if (pid == shell_pid) exit_shell(); } else if (WIFSIGNALED(status)) { printf("PID %d terminated (signal=%d)\n", pid, WTERMSIG(status)); if (pid == shell_pid) exit_shell(); } } break; } } int main(int argc, char *argv[]) { int exit_status; if (getpid() == 1 && getppid() == 0) is_init = true; mainloop_init(); printf("Bluetooth periperhal ver %s\n", VERSION); prepare_filesystem(); if (is_init) { uint8_t addr[6]; if (efivars_read("BluetoothStaticAddress", NULL, addr, 6) < 0) { printf("Generating new persistent static address\n"); if (util_getrandom(addr, sizeof(addr), 0) < 0) { perror("Failed to get random static address"); return EXIT_FAILURE; } /* Overwrite the MSB to make it a static address */ addr[5] = 0xc0; efivars_write("BluetoothStaticAddress", EFIVARS_NON_VOLATILE | EFIVARS_BOOTSERVICE_ACCESS | EFIVARS_RUNTIME_ACCESS, addr, 6); } gap_set_static_address(addr); run_shell(); } log_open(); gap_start(); if (is_init) attach_start(); exit_status = mainloop_run_with_signal(signal_callback, NULL); if (is_init) attach_stop(); gap_stop(); log_close(); return exit_status; } bluez-5.87/peripheral/PaxHeaders/log.c0000644000000000000000000000005014015011623014724 xustar0020 atime=1770587291 20 ctime=1783094105 bluez-5.87/peripheral/log.c0000644000000000000000000000124214015011623014404 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include "peripheral/log.h" static int kmsg_fd = -1; void log_open(void) { if (kmsg_fd >= 0) return; kmsg_fd = open("/dev/kmsg", O_WRONLY | O_NOCTTY | O_CLOEXEC); if (kmsg_fd < 0) { fprintf(stderr, "Failed to open kernel logging: %m\n"); return; } } void log_close(void) { if (kmsg_fd < 0) return; close(kmsg_fd); kmsg_fd = -1; } bluez-5.87/PaxHeaders/Makefile.obexd0000644000000000000000000000005015110075565014420 xustar0020 atime=1783092736 20 ctime=1783094098 bluez-5.87/Makefile.obexd0000644000000000000000000001025315110075565014102 0ustar00rootroot# SPDX-License-Identifier: GPL-2.0 if OBEX if SYSTEMD systemduserunit_DATA += obexd/src/obex.service obexd-add-service-symlink: $(LN_S) -f obex.service $(DESTDIR)$(SYSTEMD_USERUNITDIR)/dbus-org.bluez.obex.service obexd-remove-service-symlink: rm -f $(DESTDIR)$(SYSTEMD_USERUNITDIR)/dbus-org.bluez.obex.service else obexd-add-service-symlink: obexd-remove-service-symlink: endif dbussessionbusdir = $(DBUS_SESSIONBUSDIR) dbussessionbus_DATA = obexd/src/org.bluez.obex.service obex_plugindir = $(libdir)/obex/plugins obexd_builtin_modules = obexd_builtin_sources = obexd_builtin_nodist = obexd_builtin_modules += filesystem obexd_builtin_sources += obexd/plugins/filesystem.c obexd/plugins/filesystem.h obexd_builtin_modules += bluetooth obexd_builtin_sources += obexd/plugins/bluetooth.c if EXPERIMENTAL obexd_builtin_modules += pcsuite obexd_builtin_sources += obexd/plugins/pcsuite.c endif obexd_builtin_modules += opp obexd_builtin_sources += obexd/plugins/opp.c obexd_builtin_modules += ftp obexd_builtin_sources += obexd/plugins/ftp.c obexd/plugins/ftp.h obexd_builtin_modules += irmc obexd_builtin_sources += obexd/plugins/irmc.c obexd_builtin_modules += pbap obexd_builtin_sources += obexd/plugins/pbap.c \ obexd/plugins/vcard.h obexd/plugins/vcard.c \ obexd/plugins/phonebook.h \ obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c EXTRA_DIST += obexd/plugins/phonebook-dummy.c obexd/plugins/phonebook-ebook.c \ obexd/plugins/phonebook-tracker.c obexd_builtin_modules += mas obexd_builtin_sources += obexd/plugins/mas.c obexd/src/map_ap.h \ obexd/plugins/messages.h \ obexd/plugins/messages-dummy.c obexd_builtin_modules += mns obexd_builtin_sources += obexd/client/mns.c obexd/src/map_ap.h \ obexd/client/map-event.h pkglibexec_PROGRAMS += obexd/src/obexd obexd_src_obexd_SOURCES = $(btio_sources) $(gobex_sources) \ $(obexd_builtin_sources) \ obexd/src/main.c obexd/src/obexd.h \ obexd/src/plugin.h obexd/src/plugin.c \ obexd/src/log.h obexd/src/log.c \ obexd/src/logind.h obexd/src/logind.c \ obexd/src/manager.h obexd/src/manager.c \ obexd/src/obex.h obexd/src/obex.c obexd/src/obex-priv.h \ obexd/src/mimetype.h obexd/src/mimetype.c \ obexd/src/service.h obexd/src/service.c \ obexd/src/transport.h obexd/src/transport.c \ obexd/src/server.h obexd/src/server.c \ obexd/client/manager.h obexd/client/manager.c \ obexd/client/session.h obexd/client/session.c \ obexd/client/bluetooth.h obexd/client/bluetooth.c \ obexd/client/sync.h obexd/client/sync.c \ obexd/client/pbap.h obexd/client/pbap.c \ obexd/client/ftp.h obexd/client/ftp.c \ obexd/client/opp.h obexd/client/opp.c \ obexd/client/map.h obexd/client/map.c \ obexd/client/bip.h obexd/client/bip.c \ obexd/client/bip-common.h obexd/client/bip-common.c \ obexd/client/map-event.h obexd/client/map-event.c \ obexd/client/transfer.h obexd/client/transfer.c \ obexd/client/transport.h obexd/client/transport.c \ obexd/client/driver.h obexd/client/driver.c \ obexd/src/map_ap.h obexd_src_obexd_LDADD = lib/libbluetooth-internal.la \ gdbus/libgdbus-internal.la \ src/libshared-glib.la \ $(ICAL_LIBS) $(DBUS_LIBS) $(LIBEBOOK_LIBS) \ $(LIBEDATASERVER_LIBS) $(GLIB_LIBS) -ldl if EXTERNAL_PLUGINS obexd_src_obexd_LDFLAGS = $(AM_LDFLAGS) -Wl,--export-dynamic else obexd_src_obexd_LDFLAGS = endif obexd_src_obexd_CPPFLAGS = $(AM_CPPFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS) \ $(ICAL_CFLAGS) -DOBEX_PLUGIN_BUILTIN \ -DPLUGINDIR=\""$(obex_plugindir)"\" \ -D_FILE_OFFSET_BITS=64 \ -I$(builddir)/obexd/src else obexd-add-service-symlink: obexd-remove-service-symlink: endif if OBEX if SYSTEMD obexd_src_obexd_CPPFLAGS += -DSYSTEMD obexd_src_obexd_LDFLAGS += -lsystemd endif endif obexd_src_obexd_SHORTNAME = obexd obexd_builtin_files = obexd/src/builtin.h $(obexd_builtin_nodist) nodist_obexd_src_obexd_SOURCES = $(obexd_builtin_files) BUILT_SOURCES += obexd/src/builtin.h obexd/src/plugin.$(OBJEXT): obexd/src/builtin.h obexd/src/builtin.h: obexd/src/genbuiltin $(obexd_builtin_sources) $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_GEN)$(srcdir)/obexd/src/genbuiltin $(obexd_builtin_modules) > $@ CLEANFILES += obexd/src/builtin.h EXTRA_DIST += obexd/src/genbuiltin bluez-5.87/PaxHeaders/mesh0000644000000000000000000000005015221755537012546 xustar0020 atime=1783094113 20 ctime=1783094111 bluez-5.87/mesh/0000755000000000000000000000000015221755537012304 5ustar00rootrootbluez-5.87/mesh/PaxHeaders/dbus.c0000644000000000000000000000005014772767672013741 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/dbus.c0000644000000000000000000001033514772767672013424 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "mesh/mesh-defs.h" #include "mesh/node.h" #include "mesh/manager.h" #include "mesh/mesh.h" #include "mesh/error.h" #include "mesh/dbus.h" static struct l_dbus *dbus; struct error_entry { const char *dbus_err; const char *default_desc; }; struct send_info { struct l_dbus *dbus; struct l_timeout *timeout; l_dbus_message_func_t cb; l_dbus_destroy_func_t destroy; void *user_data; uint32_t serial; }; /* * Important: The entries in this table follow the order of * enumerated values in mesh_error (file error.h) */ static struct error_entry const error_table[] = { { NULL, NULL }, { ERROR_INTERFACE ".Failed", "Operation failed" }, { ERROR_INTERFACE ".NotAuthorized", "Permission denied"}, { ERROR_INTERFACE ".NotFound", "Object not found"}, { ERROR_INTERFACE ".InvalidArgs", "Invalid arguments"}, { ERROR_INTERFACE ".InProgress", "Operation already in progress"}, { ERROR_INTERFACE ".Busy", "Busy"}, { ERROR_INTERFACE ".AlreadyExists", "Already exists"}, { ERROR_INTERFACE ".DoesNotExist", "Does not exist"}, { ERROR_INTERFACE ".Canceled", "Operation canceled"}, { ERROR_INTERFACE ".NotImplemented", "Not implemented"}, }; struct l_dbus_message *dbus_error(struct l_dbus_message *msg, int err, const char *description) { int array_len = L_ARRAY_SIZE(error_table); /* Default to ".Failed" */ if (!err || err >= array_len) err = MESH_ERROR_FAILED; if (description) return l_dbus_message_new_error(msg, error_table[err].dbus_err, "%s", description); else return l_dbus_message_new_error(msg, error_table[err].dbus_err, "%s", error_table[err].default_desc); } struct l_dbus *dbus_get_bus(void) { return dbus; } bool dbus_init(struct l_dbus *bus) { /* Network interface */ if (!mesh_dbus_init(bus)) return false; /* Node interface */ if (!node_dbus_init(bus)) return false; /* Management interface */ if (!manager_dbus_init(bus)) return false; dbus = bus; return true; } bool dbus_match_interface(struct l_dbus_message_iter *interfaces, const char *match) { const char *interface; struct l_dbus_message_iter properties; while (l_dbus_message_iter_next_entry(interfaces, &interface, &properties)) { if (!strcmp(match, interface)) return true; } return false; } void dbus_append_byte_array(struct l_dbus_message_builder *builder, const uint8_t *data, int len) { int i; if (!builder) return; l_dbus_message_builder_enter_array(builder, "y"); for (i = 0; i < len; i++) l_dbus_message_builder_append_basic(builder, 'y', data + i); l_dbus_message_builder_leave_array(builder); } void dbus_append_dict_entry_basic(struct l_dbus_message_builder *builder, const char *key, const char *signature, const void *data) { if (!builder) return; l_dbus_message_builder_enter_dict(builder, "sv"); l_dbus_message_builder_append_basic(builder, 's', key); l_dbus_message_builder_enter_variant(builder, signature); l_dbus_message_builder_append_basic(builder, signature[0], data); l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_leave_dict(builder); } static void send_reply(struct l_dbus_message *message, void *user_data) { struct send_info *info = user_data; l_timeout_remove(info->timeout); info->cb(message, info->user_data); if (info->destroy) info->destroy(info->user_data); l_free(info); } static void send_timeout(struct l_timeout *timeout, void *user_data) { struct send_info *info = user_data; l_dbus_cancel(info->dbus, info->serial); send_reply(NULL, info); } void dbus_send_with_timeout(struct l_dbus *dbus, struct l_dbus_message *msg, l_dbus_message_func_t cb, void *user_data, l_dbus_destroy_func_t destroy, unsigned int seconds) { struct send_info *info = l_new(struct send_info, 1); info->dbus = dbus; info->cb = cb; info->user_data = user_data; info->destroy = destroy; info->serial = l_dbus_send_with_reply(dbus, msg, send_reply, info, NULL); info->timeout = l_timeout_create(seconds, send_timeout, info, NULL); } bluez-5.87/mesh/PaxHeaders/dbus.h0000644000000000000000000000005014015011623013706 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/dbus.h0000644000000000000000000000176614015011623013401 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ #define BLUEZ_MESH_PATH "/org/bluez/mesh" #define BLUEZ_MESH_SERVICE "org.bluez.mesh" #define DEFAULT_DBUS_TIMEOUT 30 bool dbus_init(struct l_dbus *dbus); struct l_dbus *dbus_get_bus(void); void dbus_append_byte_array(struct l_dbus_message_builder *builder, const uint8_t *data, int len); void dbus_append_dict_entry_basic(struct l_dbus_message_builder *builder, const char *key, const char *signature, const void *data); bool dbus_match_interface(struct l_dbus_message_iter *interfaces, const char *match); struct l_dbus_message *dbus_error(struct l_dbus_message *msg, int err, const char *description); void dbus_send_with_timeout(struct l_dbus *dbus, struct l_dbus_message *msg, l_dbus_message_func_t cb, void *user_data, l_dbus_destroy_func_t destroy, unsigned int seconds); bluez-5.87/mesh/PaxHeaders/pb-adv.h0000644000000000000000000000005014447506754014151 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/pb-adv.h0000644000000000000000000000064214447506754013634 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ bool pb_adv_reg(bool initiator, mesh_prov_open_func_t open_cb, mesh_prov_close_func_t close_cb, mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb, const uint8_t *uuid, void *user_data); void pb_adv_unreg(void *user_data); bluez-5.87/mesh/PaxHeaders/appkey.h0000644000000000000000000000005014214376354014262 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/appkey.h0000644000000000000000000000252214214376354013744 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ /* TODO: get this number from configuration */ #define MAX_APP_KEYS 32 struct mesh_app_key; bool appkey_key_init(struct mesh_net *net, uint16_t net_idx, uint16_t app_idx, uint8_t *key_value, uint8_t *new_key_value); void appkey_key_free(void *data); void appkey_finalize(struct mesh_net *net, uint16_t net_idx); const uint8_t *appkey_get_key(struct mesh_net *net, uint16_t app_idx, uint8_t *key_aid); int appkey_get_key_idx(struct mesh_app_key *app_key, const uint8_t **key, uint8_t *key_aid, const uint8_t **new_key, uint8_t *new_key_aid); bool appkey_have_key(struct mesh_net *net, uint16_t app_idx); uint16_t appkey_net_idx(struct mesh_net *net, uint16_t app_idx); int appkey_key_add(struct mesh_net *net, uint16_t net_idx, uint16_t app_idx, const uint8_t *new_key); int appkey_key_update(struct mesh_net *net, uint16_t net_idx, uint16_t app_idx, const uint8_t *new_key); int appkey_key_delete(struct mesh_net *net, uint16_t net_idx, uint16_t app_idx); void appkey_delete_bound_keys(struct mesh_net *net, uint16_t net_idx); uint8_t appkey_list(struct mesh_net *net, uint16_t net_idx, uint8_t *buf, uint16_t buf_size, uint16_t *size); bluez-5.87/mesh/PaxHeaders/mesh-mgmt.c0000644000000000000000000000005015063045254014655 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/mesh-mgmt.c0000644000000000000000000001473715063045254014352 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2019 SILVAIR sp. z o.o. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/mgmt.h" #include "src/shared/mgmt.h" #include "mesh/mesh-mgmt.h" struct mesh_controler { int index; bool mesh_support; bool powered; }; static mesh_mgmt_read_info_func_t ctl_info; static struct mgmt *mgmt_mesh; static struct l_queue *ctl_list; static void *list_user_data; static bool mesh_detected; static const uint8_t set_exp_feat_param_mesh[] = { 0x76, 0x6e, 0xf3, 0xe8, 0x24, 0x5f, 0x05, 0xbf, /* UUID - Mesh */ 0x8d, 0x4d, 0x03, 0x7a, 0xd7, 0x63, 0xe4, 0x2c, 0x01, /* Action - enable */ }; static bool by_index(const void *a, const void *b) { const struct mesh_controler *ctl = a; int index = L_PTR_TO_UINT(b); return ctl->index == index; } static void index_removed(uint16_t index, uint16_t length, const void *param, void *user_data); static void features_cb(uint8_t status, uint16_t length, const void *param, void *user_data) { int index = L_PTR_TO_UINT(user_data); struct mesh_controler *ctl; ctl = l_queue_find(ctl_list, by_index, L_UINT_TO_PTR(index)); if (!ctl) return; l_debug("Status: %d, Length: %d", status, length); if (status != MGMT_STATUS_NOT_SUPPORTED && status != MGMT_STATUS_UNKNOWN_COMMAND) { ctl->mesh_support = true; if (!mesh_detected) { mgmt_register(mgmt_mesh, MGMT_EV_INDEX_REMOVED, MGMT_INDEX_NONE, index_removed, NULL, NULL); } mesh_detected = true; } else l_debug("Kernel mesh not supported for hci%u", index); if (ctl_info) ctl_info(index, true, ctl->powered, ctl->mesh_support, list_user_data); } static void set_exp_mesh_cb(uint8_t status, uint16_t length, const void *param, void *user_data) { int index = L_PTR_TO_UINT(user_data); mesh_mgmt_send(MGMT_OP_MESH_READ_FEATURES, index, 0, NULL, features_cb, L_UINT_TO_PTR(index), NULL); } static void read_info_cb(uint8_t status, uint16_t length, const void *param, void *user_data) { int index = L_PTR_TO_UINT(user_data); const struct mgmt_rp_read_info *rp = param; uint32_t current_settings, supported_settings; struct mesh_controler *ctl; l_debug("hci %u status 0x%02x", index, status); ctl = l_queue_find(ctl_list, by_index, L_UINT_TO_PTR(index)); if (!ctl) return; if (status != MGMT_STATUS_SUCCESS) { ctl = l_queue_remove_if(ctl_list, by_index, L_UINT_TO_PTR(index)); l_error("Failed to read info for hci index %u: %s (0x%02x)", index, mgmt_errstr(status), status); l_warn("Hci dev %d removal detected", index); if (ctl && ctl_info) ctl_info(index, false, false, false, list_user_data); l_free(ctl); return; } if (length < sizeof(*rp)) { l_error("Read info response too short"); return; } current_settings = btohl(rp->current_settings); supported_settings = btohl(rp->supported_settings); l_debug("settings: supp %8.8x curr %8.8x", supported_settings, current_settings); if (!(supported_settings & MGMT_SETTING_LE)) { l_info("Controller hci %u does not support LE", index); l_queue_remove(ctl_list, ctl); l_free(ctl); return; } if (current_settings & MGMT_SETTING_POWERED) ctl->powered = true; mesh_mgmt_send(MGMT_OP_SET_EXP_FEATURE, index, sizeof(set_exp_feat_param_mesh), set_exp_feat_param_mesh, set_exp_mesh_cb, L_UINT_TO_PTR(index), NULL); } static void index_added(uint16_t index, uint16_t length, const void *param, void *user_data) { struct mesh_controler *ctl = l_queue_find(ctl_list, by_index, L_UINT_TO_PTR(index)); if (!ctl) { ctl = l_new(struct mesh_controler, 1); ctl->index = index; l_queue_push_head(ctl_list, ctl); } else { ctl->mesh_support = ctl->powered = false; } mgmt_send(mgmt_mesh, MGMT_OP_READ_INFO, index, 0, NULL, read_info_cb, L_UINT_TO_PTR(index), NULL); } static void index_removed(uint16_t index, uint16_t length, const void *param, void *user_data) { mgmt_send(mgmt_mesh, MGMT_OP_READ_INFO, index, 0, NULL, read_info_cb, L_UINT_TO_PTR(index), NULL); } static void read_index_list_cb(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_read_index_list *rp = param; uint16_t num; int i; if (status != MGMT_STATUS_SUCCESS) { l_error("Failed to read index list: %s (0x%02x)", mgmt_errstr(status), status); return; } if (length < sizeof(*rp)) { l_error("Read index list response sixe too short"); return; } num = btohs(rp->num_controllers); l_debug("Number of controllers: %u", num); if (num * sizeof(uint16_t) + sizeof(*rp) != length) { l_error("Incorrect packet size for index list response"); return; } for (i = 0; i < num; i++) { uint16_t index; index = btohs(rp->index[i]); index_added(index, 0, NULL, user_data); } } static bool mesh_mgmt_init(void) { if (!ctl_list) ctl_list = l_queue_new(); if (!mgmt_mesh) { mgmt_mesh = mgmt_new_default(); if (!mgmt_mesh) { l_error("Failed to initialize mesh management"); return false; } mgmt_register(mgmt_mesh, MGMT_EV_INDEX_ADDED, MGMT_INDEX_NONE, index_added, NULL, NULL); } return true; } bool mesh_mgmt_list(mesh_mgmt_read_info_func_t cb, void *user_data) { if (!mesh_mgmt_init()) return false; ctl_info = cb; list_user_data = user_data; /* Use MGMT to find a candidate controller */ l_debug("send read index_list"); if (mgmt_send(mgmt_mesh, MGMT_OP_READ_INDEX_LIST, MGMT_INDEX_NONE, 0, NULL, read_index_list_cb, NULL, NULL) <= 0) return false; return true; } void mesh_mgmt_destroy(void) { mgmt_unref(mgmt_mesh); mgmt_mesh = NULL; ctl_info = NULL; list_user_data = NULL; l_queue_destroy(ctl_list, l_free); ctl_list = NULL; } unsigned int mesh_mgmt_send(uint16_t opcode, uint16_t index, uint16_t length, const void *param, mgmt_request_func_t callback, void *user_data, mgmt_destroy_func_t destroy) { return mgmt_send_timeout(mgmt_mesh, opcode, index, length, param, callback, user_data, destroy, 0); } unsigned int mesh_mgmt_register(uint16_t event, uint16_t index, mgmt_notify_func_t callback, void *user_data, mgmt_destroy_func_t destroy) { return mgmt_register(mgmt_mesh, event, index, callback, user_data, destroy); } bool mesh_mgmt_unregister(unsigned int id) { return mgmt_unregister(mgmt_mesh, id); } void mesh_mgmt_clear(void) { l_queue_clear(ctl_list, l_free); } bluez-5.87/mesh/PaxHeaders/cfgmod.h0000644000000000000000000000005014015011623014210 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/cfgmod.h0000644000000000000000000000626314015011623013700 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ #define CONFIG_SRV_MODEL SET_ID(SIG_VENDOR, 0x0000) #define CONFIG_CLI_MODEL SET_ID(SIG_VENDOR, 0x0001) /* New List */ #define OP_APPKEY_ADD 0x00 #define OP_APPKEY_DELETE 0x8000 #define OP_APPKEY_GET 0x8001 #define OP_APPKEY_LIST 0x8002 #define OP_APPKEY_STATUS 0x8003 #define OP_APPKEY_UPDATE 0x01 #define OP_DEV_COMP_GET 0x8008 #define OP_DEV_COMP_STATUS 0x02 #define OP_CONFIG_BEACON_GET 0x8009 #define OP_CONFIG_BEACON_SET 0x800A #define OP_CONFIG_BEACON_STATUS 0x800B #define OP_CONFIG_DEFAULT_TTL_GET 0x800C #define OP_CONFIG_DEFAULT_TTL_SET 0x800D #define OP_CONFIG_DEFAULT_TTL_STATUS 0x800E #define OP_CONFIG_FRIEND_GET 0x800F #define OP_CONFIG_FRIEND_SET 0x8010 #define OP_CONFIG_FRIEND_STATUS 0x8011 #define OP_CONFIG_PROXY_GET 0x8012 #define OP_CONFIG_PROXY_SET 0x8013 #define OP_CONFIG_PROXY_STATUS 0x8014 #define OP_CONFIG_KEY_REFRESH_PHASE_GET 0x8015 #define OP_CONFIG_KEY_REFRESH_PHASE_SET 0x8016 #define OP_CONFIG_KEY_REFRESH_PHASE_STATUS 0x8017 #define OP_CONFIG_MODEL_PUB_GET 0x8018 #define OP_CONFIG_MODEL_PUB_SET 0x03 #define OP_CONFIG_MODEL_PUB_STATUS 0x8019 #define OP_CONFIG_MODEL_PUB_VIRT_SET 0x801A #define OP_CONFIG_MODEL_SUB_ADD 0x801B #define OP_CONFIG_MODEL_SUB_DELETE 0x801C #define OP_CONFIG_MODEL_SUB_DELETE_ALL 0x801D #define OP_CONFIG_MODEL_SUB_OVERWRITE 0x801E #define OP_CONFIG_MODEL_SUB_STATUS 0x801F #define OP_CONFIG_MODEL_SUB_VIRT_ADD 0x8020 #define OP_CONFIG_MODEL_SUB_VIRT_DELETE 0x8021 #define OP_CONFIG_MODEL_SUB_VIRT_OVERWRITE 0x8022 #define OP_CONFIG_NETWORK_TRANSMIT_GET 0x8023 #define OP_CONFIG_NETWORK_TRANSMIT_SET 0x8024 #define OP_CONFIG_NETWORK_TRANSMIT_STATUS 0x8025 #define OP_CONFIG_RELAY_GET 0x8026 #define OP_CONFIG_RELAY_SET 0x8027 #define OP_CONFIG_RELAY_STATUS 0x8028 #define OP_CONFIG_MODEL_SUB_GET 0x8029 #define OP_CONFIG_MODEL_SUB_LIST 0x802A #define OP_CONFIG_VEND_MODEL_SUB_GET 0x802B #define OP_CONFIG_VEND_MODEL_SUB_LIST 0x802C #define OP_CONFIG_POLL_TIMEOUT_GET 0x802D #define OP_CONFIG_POLL_TIMEOUT_STATUS 0x802E /* Health opcodes in health-mod.h */ #define OP_CONFIG_HEARTBEAT_PUB_GET 0x8038 #define OP_CONFIG_HEARTBEAT_PUB_SET 0x8039 #define OP_CONFIG_HEARTBEAT_PUB_STATUS 0x06 #define OP_CONFIG_HEARTBEAT_SUB_GET 0x803A #define OP_CONFIG_HEARTBEAT_SUB_SET 0x803B #define OP_CONFIG_HEARTBEAT_SUB_STATUS 0x803C #define OP_MODEL_APP_BIND 0x803D #define OP_MODEL_APP_STATUS 0x803E #define OP_MODEL_APP_UNBIND 0x803F #define OP_NETKEY_ADD 0x8040 #define OP_NETKEY_DELETE 0x8041 #define OP_NETKEY_GET 0x8042 #define OP_NETKEY_LIST 0x8043 #define OP_NETKEY_STATUS 0x8044 #define OP_NETKEY_UPDATE 0x8045 #define OP_NODE_IDENTITY_GET 0x8046 #define OP_NODE_IDENTITY_SET 0x8047 #define OP_NODE_IDENTITY_STATUS 0x8048 #define OP_NODE_RESET 0x8049 #define OP_NODE_RESET_STATUS 0x804A #define OP_MODEL_APP_GET 0x804B #define OP_MODEL_APP_LIST 0x804C #define OP_VEND_MODEL_APP_GET 0x804D #define OP_VEND_MODEL_APP_LIST 0x804E void cfgmod_server_init(struct mesh_node *node, uint8_t ele_idx); bluez-5.87/mesh/PaxHeaders/remprv-server.c0000644000000000000000000000005015063045254015576 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/remprv-server.c0000644000000000000000000005157015063045254015267 0ustar00rootroot/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2023 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. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "src/shared/ad.h" #include "mesh/mesh-defs.h" #include "mesh/mesh-io.h" #include "mesh/util.h" #include "mesh/node.h" #include "mesh/net.h" #include "mesh/appkey.h" #include "mesh/model.h" #include "mesh/prov.h" #include "mesh/provision.h" #include "mesh/pb-adv.h" #include "mesh/remprv.h" #define EXT_LIST_SIZE 60 #define RPR_DEV_KEY 0x00 #define RPR_ADDR 0x01 #define RPR_COMP 0x02 #define RPR_ADV 0xFF /* Internal use only*/ struct rem_scan_data { struct mesh_node *node; struct l_timeout *timeout; uint8_t *list; uint16_t client; uint16_t oob_info; uint16_t net_idx; uint8_t state; uint8_t scanned_limit; uint8_t addr[6]; uint8_t uuid[16]; uint8_t to_secs; uint8_t rxed_ads; uint8_t ext_cnt; bool fltr; uint8_t ext[0]; }; static struct rem_scan_data *rpb_scan; struct rem_prov_data { struct mesh_node *node; struct l_timeout *timeout; void *trans_data; uint16_t client; uint16_t net_idx; uint8_t svr_pdu_num; uint8_t cli_pdu_num; uint8_t state; uint8_t nppi_proc; union { struct { mesh_prov_open_func_t open_cb; mesh_prov_close_func_t close_cb; mesh_prov_receive_func_t rx_cb; mesh_prov_ack_func_t ack_cb; struct mesh_prov_node_info info; } nppi; struct { uint8_t uuid[17]; prov_trans_tx_t tx; } adv; } u; }; static struct rem_prov_data *rpb_prov; static const uint8_t prvb[2] = {BT_AD_MESH_BEACON, 0x00}; static const uint8_t pkt_filter = BT_AD_MESH_PROV; static const char *name = "Test Name"; static const uint8_t zero[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; static void srv_open(void *user_data, prov_trans_tx_t adv_tx, void *trans_data, uint8_t nppi_proc) { struct rem_prov_data *prov = user_data; uint8_t msg[5]; int n; if (prov != rpb_prov || prov->state != PB_REMOTE_STATE_LINK_OPENING) return; l_debug("Remote Link open confirmed"); prov->u.adv.tx = adv_tx; prov->trans_data = trans_data; prov->state = PB_REMOTE_STATE_LINK_ACTIVE; n = mesh_model_opcode_set(OP_REM_PROV_LINK_REPORT, msg); msg[n++] = PB_REM_ERR_SUCCESS; msg[n++] = prov->state; mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL, prov->net_idx, DEFAULT_TTL, true, n, msg); } static void srv_rx(void *user_data, const void *dptr, uint16_t len) { struct rem_prov_data *prov = user_data; const uint8_t *data = dptr; uint8_t msg[69]; int n; if (prov != rpb_prov || prov->state < PB_REMOTE_STATE_LINK_ACTIVE || len > 65) return; l_debug("Remote PB IB-PDU"); prov->svr_pdu_num++; n = mesh_model_opcode_set(OP_REM_PROV_PDU_REPORT, msg); msg[n++] = prov->svr_pdu_num; memcpy(msg + n, data, len); n += len; mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL, prov->net_idx, DEFAULT_TTL, true, n, msg); } static void srv_ack(void *user_data, uint8_t msg_num) { struct rem_prov_data *prov = user_data; uint8_t msg[4]; int n; if (prov != rpb_prov || prov->state != PB_REMOTE_STATE_OB_PKT_TX) return; l_debug("Remote PB ACK"); prov->state = PB_REMOTE_STATE_LINK_ACTIVE; n = mesh_model_opcode_set(OP_REM_PROV_PDU_OB_REPORT, msg); msg[n++] = prov->cli_pdu_num; mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL, prov->net_idx, DEFAULT_TTL, true, n, msg); } static void srv_close(void *user_data, uint8_t reason) { struct rem_prov_data *prov = user_data; uint8_t msg[4]; int n; if (prov != rpb_prov || prov->state < PB_REMOTE_STATE_LINK_ACTIVE) return; l_debug("Remote PB Close"); prov->state = PB_REMOTE_STATE_LINK_CLOSING; n = mesh_model_opcode_set(OP_REM_PROV_LINK_REPORT, msg); msg[n++] = prov->state; msg[n++] = reason; mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL, prov->net_idx, DEFAULT_TTL, true, n, msg); } static void send_prov_status(struct rem_prov_data *prov, uint8_t status) { uint16_t n; uint8_t msg[5]; bool segmented = prov->state == PB_REMOTE_STATE_LINK_CLOSING ? true : false; n = mesh_model_opcode_set(OP_REM_PROV_LINK_STATUS, msg); msg[n++] = status; msg[n++] = prov->state; l_info("RPB-Link Status(%d): dst %4.4x", prov->state, prov->client); mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL, prov->net_idx, DEFAULT_TTL, segmented, n, msg); } static void remprv_prov_cancel(struct l_timeout *timeout, void *user_data) { struct rem_prov_data *prov = user_data; if (prov != rpb_prov) return; l_timeout_remove(prov->timeout); l_free(prov); rpb_prov = NULL; } static void deregister_ext_ad_type(uint8_t ad_type) { uint8_t short_ad; switch (ad_type) { case BT_AD_MESH_BEACON: case BT_AD_MESH_DATA: case BT_AD_MESH_PROV: case BT_AD_UUID16_SOME: case BT_AD_UUID32_SOME: case BT_AD_UUID128_SOME: case BT_AD_NAME_SHORT: return; case BT_AD_UUID16_ALL: case BT_AD_UUID32_ALL: case BT_AD_UUID128_ALL: case BT_AD_NAME_COMPLETE: /* Automatically get short versions */ short_ad = ad_type - 1; mesh_io_deregister_recv_cb(NULL, &short_ad, 1); /* fall through */ default: mesh_io_deregister_recv_cb(NULL, &ad_type, 1); break; } } static void remprv_scan_cancel(struct l_timeout *timeout, void *user_data) { struct rem_scan_data *scan = user_data; uint8_t msg[22 + EXT_LIST_SIZE]; uint16_t i, n; if (!scan || scan != rpb_scan) return; for (n = 0; n < scan->ext_cnt; n++) deregister_ext_ad_type(scan->ext[n]); if (scan->timeout == timeout) { /* Return Extended Results */ if (scan->ext_cnt) { /* Return Extended Result */ n = mesh_model_opcode_set( OP_REM_PROV_EXT_SCAN_REPORT, msg); msg[n++] = PB_REM_ERR_SUCCESS; memcpy(msg + n, scan->uuid, 16); n += 16; if (scan->oob_info) { l_put_le16(0, msg + n); n += 2; } i = 0; while (scan->list[i]) { msg[n++] = scan->list[i]; memcpy(msg + n, &scan->list[i + 1], scan->list[i]); n += scan->list[i]; i += scan->list[i] + 1; } } } l_timeout_remove(scan->timeout); l_free(scan->list); l_free(scan); rpb_scan = NULL; } static void scan_pkt(void *user_data, struct mesh_io_recv_info *info, const uint8_t *data, uint16_t len) { struct rem_scan_data *scan = user_data; uint8_t msg[22 + EXT_LIST_SIZE]; uint8_t addr[6]; uint16_t i, n; int8_t rssi; uint8_t filled = 0; bool report = false; if (scan != rpb_scan) return; if (info) { rssi = info->rssi; memcpy(addr, info->addr, 6); } else { rssi = 0; memset(addr, 0, 6); } if (scan->ext_cnt) goto extended_scan; /* RX Unprovisioned Beacon */ if (data[0] != BT_AD_MESH_BEACON || data[1] || (len != 18 && len != 20 && len != 24)) return; data += 2; len -= 2; for (n = 0; !report && n < scan->scanned_limit; n++) { if (!memcmp(&scan->list[n * 17 + 1], data, 16)) { /* Repeat UUID, check RSSI */ if ((int8_t) scan->list[n * 17] < rssi) { report = true; scan->list[n * 17] = (uint8_t) rssi; } } else if (!memcmp(&scan->list[n * 17 + 1], zero, 16)) { /* Found Empty slot */ report = true; scan->list[n * 17] = (uint8_t) rssi; memcpy(&scan->list[n * 17 + 1], data, 16); } filled++; } if (!report) return; n = mesh_model_opcode_set(OP_REM_PROV_SCAN_REPORT, msg); msg[n++] = (uint8_t) rssi; memcpy(msg + n, data, len); n += len; /* Always return oob_info, even if it wasn't in beacon */ if (len == 16) { l_put_le16(0, msg + n); n += 2; } goto send_report; extended_scan: if (data[0] == BT_AD_MESH_BEACON && !data[1]) { if (len != 18 && len != 20 && len != 24) return; /* Check UUID */ if (memcmp(data + 2, scan->uuid, 16)) return; /* Zero AD list if prior data RXed from different bd_addr */ if (memcmp(scan->addr, addr, 6)) { scan->list[0] = 0; scan->rxed_ads = 0; } memcpy(scan->addr, addr, 6); scan->fltr = true; if (len >= 20) scan->oob_info = l_get_le16(data + 18); if (scan->rxed_ads != scan->ext_cnt) return; } else if (data[0] != BT_AD_MESH_BEACON) { if (!scan->fltr || !memcmp(scan->addr, addr, 6)) { i = 0; while (scan->list[i]) { /* check if seen */ if (scan->list[i + 1] == data[0]) return; i += scan->list[i] + 1; } /* Overflow Protection */ if (i + len + 1 > EXT_LIST_SIZE) return; scan->list[i] = len; scan->list[i + len + 1] = 0; memcpy(scan->list + i + 1, data, len); scan->rxed_ads++; } if (scan->rxed_ads != scan->ext_cnt) return; } else return; n = mesh_model_opcode_set(OP_REM_PROV_EXT_SCAN_REPORT, msg); msg[n++] = PB_REM_ERR_SUCCESS; memcpy(msg + n, scan->uuid, 16); n += 16; l_put_le16(scan->oob_info, msg + n); n += 2; i = 0; while (scan->list[i]) { msg[n++] = scan->list[i]; memcpy(msg + n, &scan->list[i + 1], scan->list[i]); n += scan->list[i]; i += scan->list[i]; } send_report: print_packet("App Tx", msg, n); mesh_model_send(scan->node, 0, scan->client, APP_IDX_DEV_LOCAL, scan->net_idx, DEFAULT_TTL, true, n, msg); /* Clean-up if we are done reporting*/ if (filled == scan->scanned_limit || scan->ext_cnt) remprv_scan_cancel(NULL, scan); } static bool register_ext_ad_type(uint8_t ad_type, struct rem_scan_data *scan) { uint8_t short_ad; switch (ad_type) { case BT_AD_MESH_PROV: case BT_AD_UUID16_SOME: case BT_AD_UUID32_SOME: case BT_AD_UUID128_SOME: case BT_AD_NAME_SHORT: /* Illegal Requests */ return false; case BT_AD_UUID16_ALL: case BT_AD_UUID32_ALL: case BT_AD_UUID128_ALL: case BT_AD_NAME_COMPLETE: /* Automatically get short versions */ short_ad = ad_type - 1; mesh_io_register_recv_cb(NULL, &short_ad, 1, scan_pkt, scan); /* fall through */ default: mesh_io_register_recv_cb(NULL, &ad_type, 1, scan_pkt, scan); /* fall through */ case BT_AD_MESH_BEACON: /* Ignored/auto request */ break; } return true; } static void link_active(void *user_data) { struct rem_prov_data *prov = user_data; uint8_t msg[5]; int n; if (prov != rpb_prov || prov->state != PB_REMOTE_STATE_LINK_OPENING) return; l_debug("Remote Link open confirmed"); prov->state = PB_REMOTE_STATE_LINK_ACTIVE; n = mesh_model_opcode_set(OP_REM_PROV_LINK_REPORT, msg); msg[n++] = PB_REM_ERR_SUCCESS; msg[n++] = PB_REMOTE_STATE_LINK_ACTIVE; mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL, prov->net_idx, DEFAULT_TTL, true, n, msg); } bool register_nppi_acceptor(mesh_prov_open_func_t open_cb, mesh_prov_close_func_t close_cb, mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb, void *user_data) { struct rem_prov_data *prov = rpb_prov; if (!prov || prov->nppi_proc == RPR_ADV) return false; prov->u.nppi.open_cb = open_cb; prov->u.nppi.close_cb = close_cb; prov->u.nppi.rx_cb = rx_cb; prov->u.nppi.ack_cb = ack_cb; prov->trans_data = user_data; open_cb(user_data, srv_rx, prov, prov->nppi_proc); l_idle_oneshot(link_active, prov, NULL); return true; } static bool nppi_cmplt(void *user_data, uint8_t status, const struct mesh_prov_node_info *info) { struct rem_prov_data *prov = user_data; if (prov != rpb_prov) return false; /* Save new info to apply on Link Close */ prov->u.nppi.info = *info; return true; } static bool start_dev_key_refresh(struct mesh_node *node, uint8_t nppi_proc, struct rem_prov_data *prov) { uint8_t num_ele = node_get_num_elements(node); prov->nppi_proc = nppi_proc; return acceptor_start(num_ele, NULL, 0x0001, 60, NULL, nppi_cmplt, prov); } static bool remprv_srv_pkt(uint16_t src, uint16_t unicast, uint16_t app_idx, uint16_t net_idx, const uint8_t *data, uint16_t size, const void *user_data) { struct rem_prov_data *prov = rpb_prov; struct rem_scan_data *scan = rpb_scan; struct mesh_node *node = (struct mesh_node *) user_data; const uint8_t *pkt = data; bool segmented = false; uint32_t opcode; uint8_t msg[69]; uint8_t old_state, status; uint16_t n; if (app_idx != APP_IDX_DEV_LOCAL) return false; if (mesh_model_opcode_get(pkt, size, &opcode, &n)) { size -= n; pkt += n; } else return false; n = 0; switch (opcode) { default: return false; case OP_REM_PROV_SCAN_CAP_GET: if (size != 0) return true; /* Compose Scan Info Status */ n = mesh_model_opcode_set(OP_REM_PROV_SCAN_CAP_STATUS, msg); msg[n++] = PB_REMOTE_MAX_SCAN_QUEUE_SIZE; msg[n++] = 1; /* Active Scanning Supported */ break; case OP_REM_PROV_EXT_SCAN_START: if (!size || !pkt[0]) return true; /* Size check the message */ if (pkt[0] + 18 == size) { /* Range check the Timeout */ if (!pkt[size - 1] || pkt[size - 1] > 5) return true; } else if (pkt[0] + 1 != size) return true; /* Get local device extended info */ if (pkt[0] + 18 != size) { n = mesh_model_opcode_set( OP_REM_PROV_EXT_SCAN_REPORT, msg); msg[n++] = PB_REM_ERR_SUCCESS; memcpy(msg + n, node_uuid_get(node), 16); n += 16; l_put_le16(0, msg + n); n += 2; size--; pkt++; while (size--) { if (*pkt++ == BT_AD_NAME_COMPLETE) { msg[n] = strlen(name) + 1; if (msg[n] > sizeof(msg) - n - 1) msg[n] = sizeof(msg) - n - 1; n++; msg[n++] = BT_AD_NAME_COMPLETE; memcpy(&msg[n], name, msg[n - 2] - 1); n += msg[n - 2] - 1; goto send_pkt; } } /* Send internal report */ l_debug("Send internal extended info %d", n); goto send_pkt; } status = PB_REM_ERR_SUCCESS; if (scan) { if (scan->client != src || scan->node != node || scan->ext_cnt != pkt[0]) status = PB_REM_ERR_SCANNING_CANNOT_START; else if (memcmp(scan->ext, pkt + 1, pkt[0])) status = PB_REM_ERR_SCANNING_CANNOT_START; else if (memcmp(scan->uuid, pkt + 2, 16)) status = PB_REM_ERR_SCANNING_CANNOT_START; } if (status != PB_REM_ERR_SUCCESS) { n = mesh_model_opcode_set(OP_REM_PROV_EXT_SCAN_REPORT, msg); msg[n++] = status; memset(msg + n, 0, 16); n += 16; segmented = true; break; } /* Ignore extended requests while already scanning */ if (scan) return true; scan = (void *) l_new(uint8_t, sizeof(struct rem_scan_data) + pkt[0]); /* Validate and register Extended AD types */ for (n = 0; n < pkt[0]; n++) { if (!register_ext_ad_type(pkt[1 + n], scan)) { /* Invalid AD type detected -- Undo */ while (n--) deregister_ext_ad_type(pkt[1 + n]); l_free(scan); return true; } } rpb_scan = scan; scan->client = src; scan->net_idx = net_idx; memcpy(scan->uuid, pkt + size - 17, 16); scan->ext_cnt = pkt[0]; memcpy(scan->ext, pkt + 1, pkt[0]); scan->list = l_malloc(EXT_LIST_SIZE); scan->list[0] = 0; mesh_io_register_recv_cb(NULL, prvb, sizeof(prvb), scan_pkt, scan); scan->timeout = l_timeout_create(pkt[size-1], remprv_scan_cancel, scan, NULL); return true; case OP_REM_PROV_SCAN_START: if (size != 2 && size != 18) return true; /* Reject Timeout of Zero */ if (!pkt[1]) return true; status = PB_REM_ERR_SUCCESS; if (scan) { if (scan->ext_cnt || scan->client != src || scan->node != node) status = PB_REM_ERR_SCANNING_CANNOT_START; else if (!!(scan->fltr) != !!(size != 18)) status = PB_REM_ERR_SCANNING_CANNOT_START; else if (scan->fltr && memcmp(scan->uuid, pkt + 2, 16)) status = PB_REM_ERR_SCANNING_CANNOT_START; } if (status != PB_REM_ERR_SUCCESS) { n = mesh_model_opcode_set(OP_REM_PROV_SCAN_STATUS, msg); msg[n++] = status; msg[n++] = scan ? scan->state : 0; msg[n++] = scan ? scan->scanned_limit : PB_REMOTE_MAX_SCAN_QUEUE_SIZE; msg[n++] = scan ? scan->to_secs : 0; break; } if (!scan) scan = l_new(struct rem_scan_data, 1); rpb_scan = scan; if (size == 18) { memcpy(scan->uuid, pkt + 2, 16); scan->fltr = true; scan->state = 0x02; /* Limited */ } else { memset(scan->uuid, 0, 16); scan->fltr = false; scan->state = 0x01; /* Unlimited */ } scan->client = src; scan->net_idx = net_idx; scan->node = node; if (!scan->list) scan->list = l_new(uint8_t, 23 * PB_REMOTE_MAX_SCAN_QUEUE_SIZE); mesh_io_register_recv_cb(NULL, prvb, 2, scan_pkt, scan); scan->to_secs = pkt[1]; if (pkt[0]) scan->scanned_limit = pkt[0]; else scan->scanned_limit = PB_REMOTE_MAX_SCAN_QUEUE_SIZE; scan->timeout = l_timeout_create(pkt[1], remprv_scan_cancel, scan, NULL); /* fall through */ case OP_REM_PROV_SCAN_GET: /* Compose Scan Status */ n = mesh_model_opcode_set(OP_REM_PROV_SCAN_STATUS, msg); msg[n++] = PB_REM_ERR_SUCCESS; msg[n++] = scan ? scan->state : 0; msg[n++] = scan ? scan->scanned_limit : PB_REMOTE_MAX_SCAN_QUEUE_SIZE; msg[n++] = scan ? scan->to_secs : 0; break; case OP_REM_PROV_SCAN_STOP: if (size != 0 || !scan) return true; remprv_scan_cancel(NULL, scan); return true; case OP_REM_PROV_LINK_GET: if (size != 0 || !prov) return true; send_prov_status(prov, PB_REM_ERR_SUCCESS); return true; case OP_REM_PROV_LINK_OPEN: /* Sanity check args */ if (size != 16 && size != 17 && size != 1) return true; if (size == 17 && (pkt[16] == 0 || pkt[16] > 0x3c)) return true; if (size == 1 && pkt[0] > 0x02) return true; if (prov) { if (prov->client != src || prov->node != node || (size == 1 && prov->nppi_proc != pkt[0]) || (size >= 16 && (prov->nppi_proc != RPR_ADV || memcmp(prov->u.adv.uuid, pkt, 16)))) { /* Send Reject (in progress) */ send_prov_status(prov, PB_REM_ERR_CANNOT_OPEN); n = mesh_model_opcode_set( OP_REM_PROV_LINK_STATUS, msg); msg[n++] = PB_REM_ERR_CANNOT_OPEN; msg[n++] = PB_REMOTE_STATE_LINK_ACTIVE; break; } /* Send redundant Success */ send_prov_status(prov, PB_REM_ERR_SUCCESS); return true; } if (scan && scan->client != src && scan->node != node) { n = mesh_model_opcode_set(OP_REM_PROV_LINK_STATUS, msg); msg[n++] = PB_REM_ERR_CANNOT_OPEN; msg[n++] = PB_REMOTE_STATE_LINK_ACTIVE; break; } print_packet("Remote Prov Link Open", pkt, size); remprv_scan_cancel(NULL, scan); rpb_prov = prov = l_new(struct rem_prov_data, 1); prov->client = src; prov->net_idx = net_idx; prov->node = node; prov->state = PB_REMOTE_STATE_LINK_OPENING; if (size == 1) { status = start_dev_key_refresh(node, pkt[0], prov); } else { if (size == 17) prov->timeout = l_timeout_create(pkt[16], remprv_prov_cancel, prov, NULL); prov->nppi_proc = RPR_ADV; memcpy(prov->u.adv.uuid, pkt, 16); status = pb_adv_reg(true, srv_open, srv_close, srv_rx, srv_ack, pkt, prov); } if (status) send_prov_status(prov, PB_REM_ERR_SUCCESS); else { n = mesh_model_opcode_set(OP_REM_PROV_LINK_STATUS, msg); msg[n++] = PB_REM_ERR_CANNOT_OPEN; msg[n++] = PB_REMOTE_STATE_IDLE; remprv_prov_cancel(NULL, prov); } return true; case OP_REM_PROV_LINK_CLOSE: if (size != 1) return true; if (!prov || prov->node != node || prov->client != src) return true; old_state = prov->state; prov->state = PB_REMOTE_STATE_LINK_CLOSING; mesh_io_send_cancel(NULL, &pkt_filter, sizeof(pkt_filter)); send_prov_status(prov, PB_REM_ERR_SUCCESS); if (pkt[0] == 0x02 && old_state >= PB_REMOTE_STATE_LINK_ACTIVE) { msg[0] = PROV_FAILED; msg[1] = PROV_ERR_CANT_ASSIGN_ADDR; if (prov->nppi_proc == RPR_ADV) prov->u.adv.tx(prov->trans_data, msg, 2); else prov->u.nppi.rx_cb(prov->trans_data, msg, 2); } if (prov->nppi_proc == RPR_ADV) pb_adv_unreg(prov); else if (prov->nppi_proc <= RPR_COMP) { /* Hard or Soft refresh of local node, based on NPPI */ node_refresh(prov->node, (prov->nppi_proc == RPR_ADDR), &prov->u.nppi.info); } remprv_prov_cancel(NULL, prov); return true; case OP_REM_PROV_PDU_SEND: if (!prov || prov->node != node || prov->client != src) return true; if (size < 2) return true; prov->cli_pdu_num = *pkt++; size--; prov->state = PB_REMOTE_STATE_OB_PKT_TX; if (prov->nppi_proc == RPR_ADV) prov->u.adv.tx(prov->trans_data, pkt, size); else { srv_ack(prov, prov->cli_pdu_num); prov->u.nppi.rx_cb(prov->trans_data, pkt, size); } return true; } send_pkt: l_info("PB-SVR: src %4.4x dst %4.4x", unicast, src); print_packet("App Tx", msg, n); mesh_model_send(node, 0, src, APP_IDX_DEV_LOCAL, net_idx, DEFAULT_TTL, segmented, n, msg); return true; } static void remprv_srv_unregister(void *user_data) { } static const struct mesh_model_ops ops = { .unregister = remprv_srv_unregister, .recv = remprv_srv_pkt, .bind = NULL, .sub = NULL, .pub = NULL }; void remote_prov_server_init(struct mesh_node *node, uint8_t ele_idx) { mesh_model_register(node, ele_idx, REM_PROV_SRV_MODEL, &ops, node); } bluez-5.87/mesh/PaxHeaders/bluetooth-mesh.conf0000644000000000000000000000005013540747027016426 xustar0020 atime=1770587295 20 ctime=1783094111 bluez-5.87/mesh/bluetooth-mesh.conf0000644000000000000000000000144213540747027016110 0ustar00rootroot bluez-5.87/mesh/PaxHeaders/prov.h0000644000000000000000000000005015063045254013752 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/prov.h0000644000000000000000000000344415063045254013440 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ #ifndef __packed #define __packed __attribute__((packed)) #endif typedef void (*prov_trans_tx_t)(void *tx_data, const void *data, uint16_t len); typedef void (*mesh_prov_open_func_t)(void *user_data, prov_trans_tx_t trans_tx, void *trans_data, uint8_t trans_type); typedef void (*mesh_prov_close_func_t)(void *user_data, uint8_t reason); typedef void (*mesh_prov_ack_func_t)(void *user_data, uint8_t msg_num); typedef void (*mesh_prov_receive_func_t)(void *user_data, const void *data, uint16_t size); struct prov_invite { uint8_t attention; } __packed; struct prov_invite_msg { uint8_t opcode; struct prov_invite invite; } __packed; struct prov_start { uint8_t algorithm; uint8_t pub_key; uint8_t auth_method; uint8_t auth_action; uint8_t auth_size; } __packed; struct prov_caps_msg { uint8_t opcode; struct mesh_net_prov_caps caps; } __packed; struct prov_start_msg { uint8_t opcode; struct prov_start start; } __packed; struct prov_pub_key_msg { uint8_t opcode; uint8_t pub_key[64]; } __packed; struct prov_conf_msg { uint8_t opcode; uint8_t conf[16]; } __packed; struct prov_rand_msg { uint8_t opcode; uint8_t rand[16]; } __packed; struct prov_data { uint8_t net_key[16]; uint16_t net_idx; uint8_t flags; uint32_t iv_index; uint16_t primary; } __packed; struct prov_data_msg { uint8_t opcode; struct prov_data data; uint64_t mic; } __packed; struct prov_fail_msg { uint8_t opcode; uint8_t reason; } __packed; struct conf_input { struct prov_invite invite; struct mesh_net_prov_caps caps; struct prov_start start; uint8_t prv_pub_key[64]; uint8_t dev_pub_key[64]; } __packed; bluez-5.87/mesh/PaxHeaders/mesh-io.h0000644000000000000000000000005015063045254014325 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/mesh-io.h0000644000000000000000000000363315063045254014013 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ #define MESH_IO_TX_COUNT_UNLIMITED 0 enum mesh_io_type { MESH_IO_TYPE_NONE = 0, MESH_IO_TYPE_UNIT_TEST, MESH_IO_TYPE_AUTO, /* If MGMT required, add after here */ MESH_IO_TYPE_MGMT, MESH_IO_TYPE_GENERIC, }; enum mesh_io_timing_type { MESH_IO_TIMING_TYPE_GENERAL = 1, MESH_IO_TIMING_TYPE_POLL, MESH_IO_TIMING_TYPE_POLL_RSP }; struct mesh_io_recv_info { const uint8_t *addr; uint32_t instant; uint8_t chan; int8_t rssi; }; struct mesh_io_send_info { enum mesh_io_timing_type type; union { struct { uint16_t interval; uint8_t cnt; uint8_t min_delay; uint8_t max_delay; } gen; struct { uint16_t scan_duration; uint8_t scan_delay; uint8_t filter_ids[2]; uint8_t min_delay; uint8_t max_delay; } poll; struct { uint32_t instant; uint8_t delay; } poll_rsp; } u; }; struct mesh_io_caps { uint8_t max_num_filters; uint8_t window_accuracy; }; typedef void (*mesh_io_recv_func_t)(void *user_data, struct mesh_io_recv_info *info, const uint8_t *data, uint16_t len); typedef void (*mesh_io_ready_func_t)(void *user_data, bool result); struct mesh_io *mesh_io_new(enum mesh_io_type type, void *opts, mesh_io_ready_func_t cb, void *user_data); void mesh_io_destroy(struct mesh_io *io); bool mesh_io_get_caps(struct mesh_io *io, struct mesh_io_caps *caps); bool mesh_io_register_recv_cb(struct mesh_io *io, const uint8_t *filter, uint8_t len, mesh_io_recv_func_t cb, void *user_data); bool mesh_io_deregister_recv_cb(struct mesh_io *io, const uint8_t *filter, uint8_t len); bool mesh_io_send(struct mesh_io *io, struct mesh_io_send_info *info, const uint8_t *data, uint16_t len); bool mesh_io_send_cancel(struct mesh_io *io, const uint8_t *pattern, uint8_t len); bluez-5.87/mesh/PaxHeaders/rpl.h0000644000000000000000000000005014015011623013546 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/rpl.h0000644000000000000000000000110014015011623013217 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2020 Intel Corporation. All rights reserved. * * */ struct mesh_rpl { uint32_t iv_index; uint32_t seq; uint16_t src; }; bool rpl_put_entry(struct mesh_node *node, uint16_t src, uint32_t iv_index, uint32_t seq); void rpl_del_entry(struct mesh_node *node, uint16_t src); bool rpl_get_list(struct mesh_node *node, struct l_queue *rpl_list); void rpl_update(struct mesh_node *node, uint32_t iv_index); bool rpl_init(const char *node_path); bluez-5.87/mesh/PaxHeaders/net.h0000644000000000000000000000005015063045254013552 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/net.h0000644000000000000000000002355715063045254013247 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ #ifndef __packed #define __packed __attribute__((packed)) #endif struct mesh_io; struct mesh_node; #define DEV_ID 0 #define UNUSED_KEY_IDX 0xffff #define APP_AID_DEV 0x00 #define CTL 0x80 #define KEY_CACHE_SIZE 64 #define FRND_CACHE_MAX 32 #define MAX_UNSEG_LEN 15 /* msg_len == 11 + sizeof(MIC) */ #define MAX_SEG_LEN 12 /* UnSeg length - 3 octets overhead */ #define SEG_MAX(seg, len) ((!seg && len <= MAX_UNSEG_LEN) ? 0 : \ (((len) - 1) / MAX_SEG_LEN)) #define SEG_OFF(seg) ((seg) * MAX_SEG_LEN) #define MAX_SEG_TO_LEN(seg) ((seg) ? SEG_OFF((seg) + 1) : MAX_UNSEG_LEN) #define SEGMENTED 0x80 #define UNSEGMENTED 0x00 #define SEG_HDR_SHIFT 31 #define IS_SEGMENTED(hdr) (!!((hdr) & ((uint32_t) 0x1 << SEG_HDR_SHIFT))) #define KEY_ID_MASK 0x7f #define KEY_AID_MASK 0x3f #define KEY_ID_AKF 0x40 #define KEY_AID_SHIFT 0 #define AKF_HDR_SHIFT 30 #define KEY_HDR_SHIFT 24 #define HAS_APP_KEY(hdr) (!!((hdr) & ((uint32_t) 0x1 << AKF_HDR_SHIFT))) #define OPCODE_MASK 0x7f #define OPCODE_HDR_SHIFT 24 #define RELAY 0x80 #define RELAY_HDR_SHIFT 23 #define SZMIC 0x80 #define SZMIC_HDR_SHIFT 23 #define SEQ_ZERO_MASK 0x1fff #define SEQ_ZERO_HDR_SHIFT 10 #define IS_RELAYED(hdr) (!!((hdr) & ((uint32_t) 0x1 << RELAY_HDR_SHIFT))) #define HAS_MIC64(hdr) (!!((hdr) & ((uint32_t) 0x1 << SZMIC_HDR_SHIFT))) #define SEG_MASK 0x1f #define SEGO_HDR_SHIFT 5 #define SEGN_HDR_SHIFT 0 #define SEG_TOTAL(hdr) (((hdr) >> SEGN_HDR_SHIFT) & SEG_MASK) /* Mask of Hdr bits which must be constant over entire incoming SAR message */ /* (SEG || AKF || AID || SZMIC || SeqZero || SegN) */ #define HDR_KEY_MASK ((0x1 << SEG_HDR_SHIFT) | \ (KEY_ID_MASK << KEY_HDR_SHIFT) | \ (0x1 << SZMIC_HDR_SHIFT) | \ (SEQ_ZERO_MASK << SEQ_ZERO_HDR_SHIFT) | \ (SEG_MASK << SEGN_HDR_SHIFT)) #define HDR_ACK_MASK ((OPCODE_MASK << OPCODE_HDR_SHIFT) | \ (SEQ_ZERO_MASK << SEQ_ZERO_HDR_SHIFT)) #define MSG_CACHE_SIZE 70 #define REPLAY_CACHE_SIZE 10 /* Proxy Configuration Opcodes */ #define PROXY_OP_SET_FILTER_TYPE 0x00 #define PROXY_OP_FILTER_ADD 0x01 #define PROXY_OP_FILTER_DEL 0x02 #define PROXY_OP_FILTER_STATUS 0x03 /* Proxy Filter Defines */ #define PROXY_FILTER_ACCEPT_LIST 0x00 #define PROXY_FILTER_REJECT_LIST 0x01 /* Network Transport Opcodes */ #define NET_OP_SEG_ACKNOWLEDGE 0x00 #define NET_OP_FRND_POLL 0x01 #define NET_OP_FRND_UPDATE 0x02 #define NET_OP_FRND_REQUEST 0x03 #define NET_OP_FRND_OFFER 0x04 #define NET_OP_FRND_CLEAR 0x05 #define NET_OP_FRND_CLEAR_CONFIRM 0x06 #define NET_OP_PROXY_SUB_ADD 0x07 #define NET_OP_PROXY_SUB_REMOVE 0x08 #define NET_OP_PROXY_SUB_CONFIRM 0x09 #define NET_OP_HEARTBEAT 0x0a #define FRND_OPCODE(x) \ ((x) >= NET_OP_FRND_POLL && (x) <= NET_OP_FRND_CLEAR_CONFIRM) #define DEFAULT_MIN_DELAY 0 #define DEFAULT_MAX_DELAY 25 struct mesh_net_prov_caps { uint8_t num_ele; uint16_t algorithms; uint8_t pub_type; uint8_t static_type; uint8_t output_size; uint16_t output_action; uint8_t input_size; uint16_t input_action; } __packed; struct mesh_net_heartbeat_sub { struct l_timeout *timer; uint32_t start; uint32_t period; uint16_t features; uint16_t src; uint16_t dst; uint16_t count; bool enabled; uint8_t min_hops; uint8_t max_hops; }; struct mesh_net_heartbeat_pub { struct l_timeout *timer; uint32_t period; uint16_t dst; uint16_t count; uint16_t features; uint16_t net_idx; uint8_t ttl; }; struct friend_neg { int8_t rssi; bool clearing; }; struct friend_act { uint16_t *grp_list; uint32_t last_hdr; int16_t grp_cnt; bool seq; bool last; }; struct mesh_friend { struct mesh_net *net; struct l_timeout *timeout; struct l_queue *pkt_cache; void *pkt; uint32_t poll_timeout; uint32_t net_key_cur; uint32_t net_key_upd; uint16_t old_friend; uint16_t net_idx; uint16_t lp_addr;/* dst; * Primary Element unicast addr */ uint16_t fn_cnt; uint16_t lp_cnt; uint8_t receive_delay; uint8_t ele_cnt; uint8_t frd; uint8_t frw; union { struct friend_neg negotiate; struct friend_act active; } u; }; struct mesh_friend_seg_one { uint32_t hdr; uint32_t seq; bool sent; bool md; uint8_t data[15]; }; struct mesh_friend_seg_12 { uint32_t hdr; uint32_t seq; bool sent; bool md; uint8_t data[12]; }; struct mesh_friend_msg { uint32_t iv_index; uint32_t flags; uint16_t src; uint16_t dst; uint8_t ttl; uint8_t cnt_in; uint8_t cnt_out; uint8_t last_len; bool done; bool ctl; union { struct mesh_friend_seg_one one[1]; /* Single segment */ struct mesh_friend_seg_12 s12[0]; /* Array of segments */ } u; }; struct mesh_net *mesh_net_new(struct mesh_node *node); void mesh_net_free(void *net); void mesh_net_cleanup(void); void mesh_net_set_iv_index(struct mesh_net *net, uint32_t index, bool update); bool mesh_net_iv_index_update(struct mesh_net *net); bool mesh_net_set_seq_num(struct mesh_net *net, uint32_t number); uint32_t mesh_net_get_seq_num(struct mesh_net *net); uint32_t mesh_net_next_seq_num(struct mesh_net *net); bool mesh_net_set_default_ttl(struct mesh_net *net, uint8_t ttl); uint8_t mesh_net_get_default_ttl(struct mesh_net *net); bool mesh_net_get_frnd_seq(struct mesh_net *net); void mesh_net_set_frnd_seq(struct mesh_net *net, bool seq); uint16_t mesh_net_get_address(struct mesh_net *net); bool mesh_net_register_unicast(struct mesh_net *net, uint16_t unicast, uint8_t num_ele); void net_local_beacon(uint32_t key_id, uint32_t ivi, bool ivu, bool kr); bool mesh_net_set_snb_mode(struct mesh_net *net, bool enable); bool mesh_net_set_mpb_mode(struct mesh_net *net, bool enabla, uint8_t period, bool init); bool mesh_net_set_proxy_mode(struct mesh_net *net, bool enable); bool mesh_net_set_relay_mode(struct mesh_net *net, bool enable, uint8_t cnt, uint8_t interval); bool mesh_net_set_friend_mode(struct mesh_net *net, bool enable); int mesh_net_del_key(struct mesh_net *net, uint16_t net_idx); int mesh_net_add_key(struct mesh_net *net, uint16_t net_idx, const uint8_t *key); int mesh_net_update_key(struct mesh_net *net, uint16_t net_idx, const uint8_t *key); bool mesh_net_set_key(struct mesh_net *net, uint16_t idx, const uint8_t *key, const uint8_t *new_key, uint8_t phase); uint32_t mesh_net_get_iv_index(struct mesh_net *net); void mesh_net_get_snb_state(struct mesh_net *net, uint8_t *flags, uint32_t *iv_index); bool mesh_net_get_key(struct mesh_net *net, bool new_key, uint16_t idx, uint32_t *net_key_id); bool mesh_net_attach(struct mesh_net *net, struct mesh_io *io); struct mesh_io *mesh_net_detach(struct mesh_net *net); struct l_queue *mesh_net_get_app_keys(struct mesh_net *net); void mesh_net_transport_send(struct mesh_net *net, uint32_t net_key_id, uint16_t net_idx, uint32_t iv_index, uint8_t ttl, uint32_t seq, uint16_t src, uint16_t dst, const uint8_t *msg, uint16_t msg_len); bool mesh_net_app_send(struct mesh_net *net, bool frnd_cred, uint16_t src, uint16_t dst, uint8_t key_aid, uint16_t net_idx, uint8_t ttl, uint8_t cnt, uint16_t interval, uint32_t seq, uint32_t iv_index, bool segmented, bool szmic, const void *msg, uint16_t msg_len); void mesh_net_ack_send(struct mesh_net *net, uint32_t net_key_id, uint32_t iv_index, uint8_t ttl, uint32_t seq, uint16_t src, uint16_t dst, bool rly, uint16_t seqZero, uint32_t ack_flags); int mesh_net_get_identity_mode(struct mesh_net *net, uint16_t idx, uint8_t *mode); bool mesh_net_dst_reg(struct mesh_net *net, uint16_t dst); bool mesh_net_dst_unreg(struct mesh_net *net, uint16_t dst); struct mesh_friend *mesh_friend_new(struct mesh_net *net, uint16_t dst, uint8_t ele_cnt, uint8_t frd, uint8_t frw, uint32_t fpt, uint16_t fn_cnt, uint16_t lp_cnt); void mesh_friend_free(void *frnd); bool mesh_friend_clear(struct mesh_net *net, struct mesh_friend *frnd); void mesh_friend_sub_add(struct mesh_net *net, uint16_t lpn, uint8_t ele_cnt, uint8_t grp_cnt, const uint8_t *list); void mesh_friend_sub_del(struct mesh_net *net, uint16_t lpn, uint8_t cnt, const uint8_t *del_list); int mesh_net_key_refresh_phase_set(struct mesh_net *net, uint16_t net_idx, uint8_t transition); int mesh_net_key_refresh_phase_get(struct mesh_net *net, uint16_t net_idx, uint8_t *phase); void mesh_net_send_seg(struct mesh_net *net, uint32_t net_key_id, uint32_t iv_index, uint8_t ttl, uint32_t seq, uint16_t src, uint16_t dst, uint32_t hdr, const void *seg, uint16_t seg_len); struct mesh_net_heartbeat_sub *mesh_net_get_heartbeat_sub(struct mesh_net *net); int mesh_net_set_heartbeat_sub(struct mesh_net *net, uint16_t src, uint16_t dst, uint8_t period_log); struct mesh_net_heartbeat_pub *mesh_net_get_heartbeat_pub(struct mesh_net *net); int mesh_net_set_heartbeat_pub(struct mesh_net *net, uint16_t dst, uint16_t features, uint16_t idx, uint8_t ttl, uint8_t count_log, uint8_t period_log); bool mesh_net_key_list_get(struct mesh_net *net, uint8_t *buf, uint16_t *count); uint16_t mesh_net_get_primary_idx(struct mesh_net *net); uint32_t mesh_net_friend_timeout(struct mesh_net *net, uint16_t addr); struct mesh_io *mesh_net_get_io(struct mesh_net *net); struct mesh_node *mesh_net_node_get(struct mesh_net *net); bool mesh_net_have_key(struct mesh_net *net, uint16_t net_idx); bool mesh_net_is_local_address(struct mesh_net *net, uint16_t src, uint16_t count); void mesh_net_transmit_params_set(struct mesh_net *net, uint8_t count, uint16_t interval); void mesh_net_transmit_params_get(struct mesh_net *net, uint8_t *count, uint16_t *interval); struct mesh_prov *mesh_net_get_prov(struct mesh_net *net); void mesh_net_set_prov(struct mesh_net *net, struct mesh_prov *prov); uint32_t mesh_net_get_instant(struct mesh_net *net); struct l_queue *mesh_net_get_friends(struct mesh_net *net); struct l_queue *mesh_net_get_negotiations(struct mesh_net *net); bool mesh_net_load_rpl(struct mesh_net *net); bluez-5.87/mesh/PaxHeaders/bluetooth-mesh.service.in0000644000000000000000000000005014572354773017556 xustar0020 atime=1783092767 20 ctime=1783094098 bluez-5.87/mesh/bluetooth-mesh.service.in0000644000000000000000000000053114572354773017236 0ustar00rootroot[Unit] Description=Bluetooth mesh service ConditionPathIsDirectory=/sys/class/bluetooth [Service] Type=dbus BusName=org.bluez.mesh ExecStart=@PKGLIBEXECDIR@/bluetooth-meshd NotifyAccess=main LimitNPROC=1 ProtectHome=true ProtectSystem=full Restart=on-failure RestartSec=5s [Install] WantedBy=bluetooth.target Alias=dbus-org.bluez.mesh.service bluez-5.87/mesh/PaxHeaders/util.h0000644000000000000000000000005015063045254013741 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/util.h0000644000000000000000000000122315063045254013420 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ uint32_t get_timestamp_secs(void); bool str2hex(const char *str, uint16_t in_len, uint8_t *out, uint16_t out_len); size_t hex2str(const uint8_t *in, size_t in_len, char *out, size_t out_len); void print_packet(const char *label, const void *data, uint16_t size); int create_dir(const char *dir_name); void del_path(const char *path); void enable_debug(void); #if !HAVE_DECL_BASENAME const char *mesh_basename(const char *path); #else #define mesh_basename basename #endif bluez-5.87/mesh/PaxHeaders/mesh-io-unit.c0000644000000000000000000000005015063045254015275 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/mesh-io-unit.c0000644000000000000000000002441715063045254014766 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2021 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "src/shared/ad.h" #include "mesh/mesh-defs.h" #include "mesh/dbus.h" #include "mesh/mesh-io.h" #include "mesh/mesh-io-api.h" #include "mesh/mesh-io-unit.h" struct mesh_io_private { struct mesh_io *io; struct l_io *sio; void *user_data; char *unique_name; struct l_timeout *tx_timeout; struct l_queue *rx_regs; struct l_queue *tx_pkts; struct sockaddr_un addr; int fd; uint16_t interval; }; struct pvt_rx_reg { mesh_io_recv_func_t cb; void *user_data; uint8_t len; uint8_t filter[0]; }; struct process_data { struct mesh_io_private *pvt; const uint8_t *data; uint8_t len; struct mesh_io_recv_info info; }; struct tx_pkt { struct mesh_io_send_info info; bool delete; uint8_t len; uint8_t pkt[MESH_AD_MAX_LEN]; }; struct tx_pattern { const uint8_t *data; uint8_t len; }; static uint32_t get_instant(void) { struct timeval tm; uint32_t instant; gettimeofday(&tm, NULL); instant = tm.tv_sec * 1000; instant += tm.tv_usec / 1000; return instant; } static uint32_t instant_remaining_ms(uint32_t instant) { instant -= get_instant(); return instant; } static void process_rx_callbacks(void *v_reg, void *v_rx) { struct pvt_rx_reg *rx_reg = v_reg; struct process_data *rx = v_rx; if (!memcmp(rx->data, rx_reg->filter, rx_reg->len)) rx_reg->cb(rx_reg->user_data, &rx->info, rx->data, rx->len); } static void process_rx(struct mesh_io_private *pvt, int8_t rssi, uint32_t instant, const uint8_t *addr, const uint8_t *data, uint8_t len) { struct process_data rx = { .pvt = pvt, .data = data, .len = len, .info.instant = instant, .info.addr = addr, .info.chan = 7, .info.rssi = rssi, }; l_queue_foreach(pvt->rx_regs, process_rx_callbacks, &rx); } static bool incoming(struct l_io *sio, void *user_data) { struct mesh_io_private *pvt = user_data; uint32_t instant; uint8_t buf[31]; size_t size; instant = get_instant(); size = recv(pvt->fd, buf, sizeof(buf), MSG_DONTWAIT); if (size > 9 && buf[0]) { process_rx(pvt, -20, instant, NULL, buf + 1, (uint8_t)size); } else if (size == 1 && !buf[0] && pvt->unique_name) { /* Return DBUS unique name */ size = strlen(pvt->unique_name); if (size > sizeof(buf) - 2) return true; buf[0] = 0; memcpy(buf + 1, pvt->unique_name, size + 1); if (send(pvt->fd, buf, size + 2, MSG_DONTWAIT) < 0) l_error("Failed to send(%d)", errno); } return true; } static bool find_by_ad_type(const void *a, const void *b) { const struct tx_pkt *tx = a; uint8_t ad_type = L_PTR_TO_UINT(b); return !ad_type || ad_type == tx->pkt[0]; } static bool find_by_pattern(const void *a, const void *b) { const struct tx_pkt *tx = a; const struct tx_pattern *pattern = b; if (tx->len < pattern->len) return false; return (!memcmp(tx->pkt, pattern->data, pattern->len)); } static void free_socket(struct mesh_io_private *pvt) { l_io_destroy(pvt->sio); close(pvt->fd); unlink(pvt->addr.sun_path); } static void hello_callback(struct l_dbus_message *msg, void *user_data) { struct mesh_io_private *pvt = user_data; pvt->unique_name = l_strdup(l_dbus_message_get_destination(msg)); l_debug("User-Daemon unique name: %s", pvt->unique_name); } static void get_name(struct l_timeout *timeout, void *user_data) { struct mesh_io_private *pvt = user_data; struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg; l_timeout_remove(timeout); if (!dbus) { l_timeout_create_ms(20, get_name, pvt, NULL); return; } /* Retrieve unique name */ msg = l_dbus_message_new_method_call(dbus, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "GetId"); l_dbus_message_set_arguments(msg, ""); l_dbus_send_with_reply(dbus, msg, hello_callback, pvt, NULL); } static void unit_up(void *user_data) { struct mesh_io_private *pvt = user_data; l_debug("Started io-unit"); if (pvt->io && pvt->io->ready) pvt->io->ready(pvt->user_data, true); l_timeout_create_ms(1, get_name, pvt, NULL); } static bool unit_init(struct mesh_io *io, void *opt, void *user_data) { struct mesh_io_private *pvt; char *sk_path; size_t size; l_debug("Starting Unit test IO"); if (!io || io->pvt) return false; sk_path = (char *) opt; pvt = l_new(struct mesh_io_private, 1); pvt->addr.sun_family = AF_LOCAL; snprintf(pvt->addr.sun_path, sizeof(pvt->addr.sun_path), "%s", sk_path); pvt->fd = socket(PF_LOCAL, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (pvt->fd < 0) goto fail; unlink(pvt->addr.sun_path); size = offsetof(struct sockaddr_un, sun_path) + strlen(pvt->addr.sun_path); if (bind(pvt->fd, (struct sockaddr *) &pvt->addr, size) < 0) goto fail; /* Setup socket handlers */ pvt->sio = l_io_new(pvt->fd); if (!l_io_set_read_handler(pvt->sio, incoming, pvt, NULL)) goto fail; pvt->rx_regs = l_queue_new(); pvt->tx_pkts = l_queue_new(); pvt->io = io; pvt->user_data = user_data; io->pvt = pvt; l_idle_oneshot(unit_up, pvt, NULL); return true; fail: l_error("Failed to bind Unit Test socket"); free_socket(pvt); l_free(pvt); return false; } static bool unit_destroy(struct mesh_io *io) { struct mesh_io_private *pvt = io->pvt; if (!pvt) return true; l_free(pvt->unique_name); l_timeout_remove(pvt->tx_timeout); l_queue_destroy(pvt->rx_regs, l_free); l_queue_destroy(pvt->tx_pkts, l_free); free_socket(pvt); l_free(pvt); io->pvt = NULL; return true; } static bool unit_caps(struct mesh_io *io, struct mesh_io_caps *caps) { struct mesh_io_private *pvt = io->pvt; if (!pvt || !caps) return false; caps->max_num_filters = 255; caps->window_accuracy = 50; return true; } static bool simple_match(const void *a, const void *b) { return a == b; } static void send_pkt(struct mesh_io_private *pvt, struct tx_pkt *tx, uint16_t interval) { if (send(pvt->fd, tx->pkt, tx->len, MSG_DONTWAIT) < 0) l_error("Failed to send(%d)", errno); if (tx->delete) { l_queue_remove_if(pvt->tx_pkts, simple_match, tx); l_free(tx); } } static void tx_to(struct l_timeout *timeout, void *user_data) { struct mesh_io_private *pvt = user_data; struct tx_pkt *tx; uint16_t ms; uint8_t count; if (!pvt) return; tx = l_queue_pop_head(pvt->tx_pkts); if (!tx) { l_timeout_remove(timeout); pvt->tx_timeout = NULL; return; } if (tx->info.type == MESH_IO_TIMING_TYPE_GENERAL) { ms = tx->info.u.gen.interval; count = tx->info.u.gen.cnt; if (count != MESH_IO_TX_COUNT_UNLIMITED) tx->info.u.gen.cnt--; } else { ms = 25; count = 1; } tx->delete = (count == 1); send_pkt(pvt, tx, ms); if (count == 1) { /* Recalculate wakeup if we are responding to POLL */ tx = l_queue_peek_head(pvt->tx_pkts); if (tx && tx->info.type == MESH_IO_TIMING_TYPE_POLL_RSP) { ms = instant_remaining_ms(tx->info.u.poll_rsp.instant + tx->info.u.poll_rsp.delay); } } else l_queue_push_tail(pvt->tx_pkts, tx); if (timeout) { pvt->tx_timeout = timeout; l_timeout_modify_ms(timeout, ms); } else pvt->tx_timeout = l_timeout_create_ms(ms, tx_to, pvt, NULL); } static void tx_worker(void *user_data) { struct mesh_io_private *pvt = user_data; struct tx_pkt *tx; uint32_t delay; tx = l_queue_peek_head(pvt->tx_pkts); if (!tx) return; switch (tx->info.type) { case MESH_IO_TIMING_TYPE_GENERAL: if (tx->info.u.gen.min_delay == tx->info.u.gen.max_delay) delay = tx->info.u.gen.min_delay; else { l_getrandom(&delay, sizeof(delay)); delay %= tx->info.u.gen.max_delay - tx->info.u.gen.min_delay; delay += tx->info.u.gen.min_delay; } break; case MESH_IO_TIMING_TYPE_POLL: if (tx->info.u.poll.min_delay == tx->info.u.poll.max_delay) delay = tx->info.u.poll.min_delay; else { l_getrandom(&delay, sizeof(delay)); delay %= tx->info.u.poll.max_delay - tx->info.u.poll.min_delay; delay += tx->info.u.poll.min_delay; } break; case MESH_IO_TIMING_TYPE_POLL_RSP: /* Delay until Instant + Delay */ delay = instant_remaining_ms(tx->info.u.poll_rsp.instant + tx->info.u.poll_rsp.delay); if (delay > 255) delay = 0; break; default: return; } if (!delay) tx_to(pvt->tx_timeout, pvt); else if (pvt->tx_timeout) l_timeout_modify_ms(pvt->tx_timeout, delay); else pvt->tx_timeout = l_timeout_create_ms(delay, tx_to, pvt, NULL); } static bool send_tx(struct mesh_io *io, struct mesh_io_send_info *info, const uint8_t *data, uint16_t len) { struct mesh_io_private *pvt = io->pvt; struct tx_pkt *tx; bool sending = false; if (!info || !data || !len || len > sizeof(tx->pkt)) return false; tx = l_new(struct tx_pkt, 1); memcpy(&tx->info, info, sizeof(tx->info)); memcpy(&tx->pkt, data, len); tx->len = len; if (info->type == MESH_IO_TIMING_TYPE_POLL_RSP) l_queue_push_head(pvt->tx_pkts, tx); else { sending = !l_queue_isempty(pvt->tx_pkts); l_queue_push_tail(pvt->tx_pkts, tx); } if (!sending) { l_timeout_remove(pvt->tx_timeout); pvt->tx_timeout = NULL; l_idle_oneshot(tx_worker, pvt, NULL); } return true; } static bool tx_cancel(struct mesh_io *io, const uint8_t *data, uint8_t len) { struct mesh_io_private *pvt = io->pvt; struct tx_pkt *tx; if (!data) return false; if (len == 1) { do { tx = l_queue_remove_if(pvt->tx_pkts, find_by_ad_type, L_UINT_TO_PTR(data[0])); l_free(tx); } while (tx); } else { struct tx_pattern pattern = { .data = data, .len = len }; do { tx = l_queue_remove_if(pvt->tx_pkts, find_by_pattern, &pattern); l_free(tx); } while (tx); } if (l_queue_isempty(pvt->tx_pkts)) { l_timeout_remove(pvt->tx_timeout); pvt->tx_timeout = NULL; } return true; } static bool recv_register(struct mesh_io *io, const uint8_t *filter, uint8_t len, mesh_io_recv_func_t cb, void *user_data) { return true; } static bool recv_deregister(struct mesh_io *io, const uint8_t *filter, uint8_t len) { return true; } const struct mesh_io_api mesh_io_unit = { .init = unit_init, .destroy = unit_destroy, .caps = unit_caps, .send = send_tx, .reg = recv_register, .dereg = recv_deregister, .cancel = tx_cancel, }; bluez-5.87/mesh/PaxHeaders/prvbeac-server.c0000644000000000000000000000005014772767672015732 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/prvbeac-server.c0000644000000000000000000000557314772767672015425 0ustar00rootroot/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2023 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. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "mesh/mesh-defs.h" #include "mesh/node.h" #include "mesh/net.h" #include "mesh/appkey.h" #include "mesh/model.h" #include "mesh/mesh-config.h" #include "mesh/prv-beacon.h" #define NOT_SUPPORTED 0x02 static bool prvbec_srv_pkt(uint16_t src, uint16_t dst, uint16_t app_idx, uint16_t net_idx, const uint8_t *data, uint16_t size, const void *user_data) { struct mesh_node *node = (struct mesh_node *) user_data; const uint8_t *pkt = data; uint32_t opcode; uint8_t msg[5]; uint16_t n; uint8_t period; if (app_idx != APP_IDX_DEV_LOCAL) return false; if (mesh_model_opcode_get(pkt, size, &opcode, &n)) { size -= n; pkt += n; } else return false; l_debug("PRV-BEAC-SRV-opcode 0x%x size %u idx %3.3x", opcode, size, net_idx); n = 0; switch (opcode) { default: return false; case OP_PRIVATE_BEACON_SET: if (size == 1) node_mpb_mode_get(node, &period); else if (size == 2) period = pkt[1]; else return true; if (pkt[0] > 1) return true; node_mpb_mode_set(node, !!pkt[0], period); /* fall through */ case OP_PRIVATE_BEACON_GET: n = mesh_model_opcode_set(OP_PRIVATE_BEACON_STATUS, msg); msg[n++] = node_mpb_mode_get(node, &period); msg[n++] = period; l_debug("Get/Set Private Beacon (%d)", msg[n-2]); break; case OP_PRIVATE_GATT_PROXY_SET: /* fall through */ case OP_PRIVATE_GATT_PROXY_GET: n = mesh_model_opcode_set(OP_PRIVATE_GATT_PROXY_STATUS, msg); msg[n++] = NOT_SUPPORTED; break; case OP_PRIVATE_NODE_ID_SET: /* fall through */ case OP_PRIVATE_NODE_ID_GET: n = mesh_model_opcode_set(OP_PRIVATE_NODE_ID_STATUS, msg); msg[n++] = NOT_SUPPORTED; break; } if (n) mesh_model_send(node, dst, src, APP_IDX_DEV_LOCAL, net_idx, DEFAULT_TTL, false, n, msg); return true; } static void prvbec_srv_unregister(void *user_data) { } static const struct mesh_model_ops ops = { .unregister = prvbec_srv_unregister, .recv = prvbec_srv_pkt, .bind = NULL, .sub = NULL, .pub = NULL }; void prv_beacon_server_init(struct mesh_node *node, uint8_t ele_idx) { l_debug("%2.2x", ele_idx); mesh_model_register(node, ele_idx, PRV_BEACON_SRV_MODEL, &ops, node); } bluez-5.87/mesh/PaxHeaders/friend.c0000644000000000000000000000005015063045254014226 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/friend.c0000644000000000000000000003776515063045254013731 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "mesh/mesh-defs.h" #include "mesh/net-keys.h" #include "mesh/net.h" #include "mesh/model.h" #include "mesh/util.h" #include "mesh/friend.h" #define MAX_FRND_GROUPS 20 #define FRND_RELAY_WINDOW 250 /* 250 ms */ #define FRND_CACHE_SIZE FRND_CACHE_MAX #define FRND_SUB_LIST_SIZE 8 #define RESPONSE_DELAY (100 - 12) /* 100 ms - 12ms hw delay */ #define MIN_RESP_DELAY 10 /* 10 ms */ #define MAX_RESP_DELAY 255 /* 255 ms */ /* Absolute maximum time to wait for LPN to choose us. */ #define RESPONSE_POLL_DELAY 1300 /* 1.300 s */ static uint8_t frnd_relay_window = FRND_RELAY_WINDOW; static uint8_t frnd_cache_size = FRND_CACHE_SIZE; static uint8_t frnd_sublist_size = FRND_SUB_LIST_SIZE; static uint16_t counter; static struct l_queue *retired_lpns; static void response_timeout(struct l_timeout *timeout, void *user_data) { struct mesh_friend *neg = user_data; struct l_queue *negotiations = mesh_net_get_negotiations(neg->net); /* LPN did not choose us */ l_debug("Did not win negotiation for %4.4x", neg->lp_addr); net_key_unref(neg->net_key_cur); net_key_unref(neg->net_key_upd); l_queue_remove(negotiations, neg); l_timeout_remove(timeout); l_free(neg); } static void response_delay(struct l_timeout *timeout, void *user_data) { struct mesh_friend *neg = user_data; uint16_t net_idx = neg->net_idx; uint32_t net_key_id, seq; uint8_t msg[8]; uint16_t n = 0; bool res; l_timeout_remove(timeout); /* Create key Set for this offer */ res = mesh_net_get_key(neg->net, false, net_idx, &net_key_id); if (!res) goto cleanup; neg->net_key_cur = net_key_frnd_add(net_key_id, neg->lp_addr, mesh_net_get_address(neg->net), neg->lp_cnt, counter); if (!neg->net_key_cur) goto cleanup; neg->fn_cnt = counter++; msg[n++] = NET_OP_FRND_OFFER; msg[n++] = frnd_relay_window; msg[n++] = frnd_cache_size; msg[n++] = frnd_sublist_size; msg[n++] = neg->u.negotiate.rssi; l_put_be16(neg->fn_cnt, msg + n); n += 2; seq = mesh_net_next_seq_num(neg->net); print_packet("Tx-NET_OP_FRND_OFFER", msg, n); mesh_net_transport_send(neg->net, net_key_id, 0, mesh_net_get_iv_index(neg->net), 0, seq, 0, neg->lp_addr, msg, n); /* Offer expires in 1.3 seconds, which is the max time for LPN to * receive all offers, 1 second to make decision, and a little extra */ neg->timeout = l_timeout_create_ms(1000 + MAX_RESP_DELAY, response_timeout, neg, NULL); return; cleanup: net_key_unref(neg->net_key_cur); net_key_unref(neg->net_key_upd); l_queue_remove(mesh_net_get_negotiations(neg->net), neg); l_free(neg); } static uint8_t cache_size(uint8_t power) { return 1 << power; } static bool match_by_lpn(const void *a, const void *b) { const struct mesh_friend *neg = a; uint16_t lpn = L_PTR_TO_UINT(b); return neg->lp_addr == lpn; } /* Scaling factors in 1/10 ms */ static const int32_t scaling[] = { 10, 15, 20, 15, }; void friend_request(struct mesh_net *net, uint16_t net_idx, uint16_t src, uint8_t minReq, uint8_t delay, uint32_t timeout, uint16_t prev, uint8_t num_ele, uint16_t cntr, int8_t rssi) { struct l_queue *negotiations = mesh_net_get_negotiations(net); struct mesh_friend *neg; uint8_t rssiScale = (minReq >> 5) & 3; uint8_t winScale = (minReq >> 3) & 3; uint8_t minCache = (minReq >> 0) & 7; int32_t rsp_delay; l_debug("RSSI of Request: %d dbm", rssi); l_debug("Delay: %d ms", delay); l_debug("Poll Timeout of Request: %d ms", timeout * 100); l_debug("Previous Friend: %4.4x", prev); l_debug("Num Elem: %2.2x", num_ele); l_debug("Cache Requested: %d", cache_size(minCache)); l_debug("Cache to offer: %d", frnd_cache_size); /* Determine our own suitability before * deciding to participate in negotiation */ if (minCache == 0 || num_ele == 0) return; if (delay < 0x0A) return; if (timeout < 0x00000A || timeout > 0x34BBFF) return; if (cache_size(minCache) > frnd_cache_size) return; /* TODO: Check RSSI, and then start Negotiation if appropriate */ /* We are participating in this Negotiation */ neg = l_new(struct mesh_friend, 1); l_queue_push_head(negotiations, neg); neg->net = net; neg->lp_addr = src; neg->lp_cnt = cntr; neg->u.negotiate.rssi = rssi; neg->receive_delay = delay; neg->poll_timeout = timeout; neg->old_friend = prev; neg->ele_cnt = num_ele; neg->net_idx = net_idx; /* RSSI (Negative Factor, larger values == less time) * Scaling factor 0-3 == multiplier of 1.0 - 2.5 * Minimum factor of 1. Bit 1 adds additional factor * of 1, bit zero and additional 0.5 */ rsp_delay = -(rssi * scaling[rssiScale]); l_debug("RSSI Factor: %d ms", rsp_delay / 10); /* Relay Window (Positive Factor, larger values == more time) * Scaling factor 0-3 == multiplier of 1.0 - 2.5 * Minimum factor of 1. Bit 1 adds additional factor * of 1, bit zero and additional 0.5 */ rsp_delay += frnd_relay_window * scaling[winScale]; l_debug("Win Size Factor: %d ms", (frnd_relay_window * scaling[winScale]) / 10); /* Normalize to ms */ rsp_delay /= 10; /* Range limits are 10-255 ms */ if (rsp_delay < MIN_RESP_DELAY) rsp_delay = MIN_RESP_DELAY; else if (rsp_delay > MAX_RESP_DELAY) rsp_delay = MAX_RESP_DELAY; l_debug("Total Response Delay: %d ms", rsp_delay); /* Add in 100ms delay before start of "Offer Period" */ rsp_delay += RESPONSE_DELAY; neg->timeout = l_timeout_create_ms(rsp_delay, response_delay, neg, NULL); } void friend_clear_confirm(struct mesh_net *net, uint16_t src, uint16_t lpn, uint16_t lpnCounter) { struct l_queue *negotiations = mesh_net_get_negotiations(net); struct mesh_friend *neg = l_queue_remove_if(negotiations, match_by_lpn, L_UINT_TO_PTR(lpn)); l_debug("Friend Clear confirmed %4.4x (cnt %4.4x)", lpn, lpnCounter); if (!neg) return; l_timeout_remove(neg->timeout); l_queue_remove(negotiations, neg); l_free(neg); } static void friend_poll_timeout(struct l_timeout *timeout, void *user_data) { struct mesh_friend *frnd = user_data; if (mesh_friend_clear(frnd->net, frnd)) l_debug("Friend Poll Timeout %4.4x", frnd->lp_addr); l_timeout_remove(frnd->timeout); frnd->timeout = NULL; /* Friend may be in either Network or Retired list, so try both */ l_queue_remove(retired_lpns, frnd); mesh_friend_free(frnd); } void friend_clear(struct mesh_net *net, uint16_t src, uint16_t lpn, uint16_t lpnCounter, struct mesh_friend *frnd) { struct l_queue *negotiations = mesh_net_get_negotiations(net); uint8_t msg[5] = { NET_OP_FRND_CLEAR_CONFIRM }; bool removed = false; uint16_t lpnDelta; if (frnd) { lpnDelta = lpnCounter - frnd->lp_cnt; /* Ignore old Friend Clear commands */ if (lpnDelta > 0x100) return; /* Move friend from Network list to Retired list */ removed = mesh_friend_clear(net, frnd); if (removed) { struct mesh_friend *neg, *old; neg = l_queue_remove_if(negotiations, match_by_lpn, L_UINT_TO_PTR(lpn)); /* Cancel any negotiations or clears */ if (neg) { l_timeout_remove(neg->timeout); l_free(neg); } /* Find any duplicates */ old = l_queue_find(retired_lpns, match_by_lpn, L_UINT_TO_PTR(lpn)); /* Force time-out of old friendship */ if (old) friend_poll_timeout(old->timeout, old); if (!retired_lpns) retired_lpns = l_queue_new(); /* Retire this LPN (keeps timeout running) */ l_queue_push_tail(retired_lpns, frnd); } } else { frnd = l_queue_find(retired_lpns, match_by_lpn, L_UINT_TO_PTR(lpn)); if (!frnd) return; lpnDelta = lpnCounter - frnd->lp_cnt; /* Ignore old Friend Clear commands */ if (!lpnDelta || (lpnDelta > 0x100)) return; } l_debug("Friend Cleared %4.4x (%4.4x)", lpn, lpnCounter); l_put_be16(lpn, msg + 1); l_put_be16(lpnCounter, msg + 3); mesh_net_transport_send(net, 0, 0, mesh_net_get_iv_index(net), DEFAULT_TTL, 0, 0, src, msg, sizeof(msg)); } static void clear_retry(struct l_timeout *timeout, void *user_data) { struct mesh_friend *neg = user_data; struct l_queue *negotiations = mesh_net_get_negotiations(neg->net); uint8_t msg[5] = { NET_OP_FRND_CLEAR }; uint32_t secs = 1 << neg->receive_delay; l_put_be16(neg->lp_addr, msg + 1); l_put_be16(neg->lp_cnt, msg + 3); mesh_net_transport_send(neg->net, 0, 0, mesh_net_get_iv_index(neg->net), DEFAULT_TTL, 0, 0, neg->old_friend, msg, sizeof(msg)); if (secs && ((secs << 1) < neg->poll_timeout/10)) { neg->receive_delay++; l_debug("Try FRND_CLR again in %d seconds (total timeout %d)", secs, neg->poll_timeout/10); l_timeout_modify(neg->timeout, secs); } else { l_debug("FRND_CLR timed out %d", secs); l_timeout_remove(timeout); l_queue_remove(negotiations, neg); l_free(neg); } } static void friend_delay_rsp(struct l_timeout *timeout, void *user_data) { struct mesh_friend *frnd = user_data; struct mesh_friend_msg *pkt = frnd->pkt; struct mesh_net *net = frnd->net; uint32_t net_seq, iv_index; uint8_t upd[7] = { NET_OP_FRND_UPDATE }; l_timeout_remove(timeout); if (pkt == NULL) goto update; if (pkt->ctl) { /* Make sure we don't change the bit-sense of MD, * once it has been set because that would cause * a "Dirty Nonce" security violation */ if (((pkt->u.one[0].hdr >> OPCODE_HDR_SHIFT) & OPCODE_MASK) == NET_OP_SEG_ACKNOWLEDGE) { bool rly = !!((pkt->u.one[0].hdr >> RELAY_HDR_SHIFT) & true); uint16_t seqZero = pkt->u.one[0].hdr >> SEQ_ZERO_HDR_SHIFT; seqZero &= SEQ_ZERO_MASK; l_debug("Fwd ACK pkt %6.6x-%8.8x", pkt->u.one[0].seq, pkt->iv_index); pkt->u.one[0].sent = true; mesh_net_ack_send(net, frnd->net_key_cur, pkt->iv_index, pkt->ttl, pkt->u.one[0].seq, pkt->src, pkt->dst, rly, seqZero, l_get_be32(pkt->u.one[0].data)); } else { l_debug("Fwd CTL pkt %6.6x-%8.8x", pkt->u.one[0].seq, pkt->iv_index); print_packet("Frnd-CTL", pkt->u.one[0].data, pkt->last_len); pkt->u.one[0].sent = true; mesh_net_transport_send(net, frnd->net_key_cur, 0, pkt->iv_index, pkt->ttl, pkt->u.one[0].seq, pkt->src, pkt->dst, pkt->u.one[0].data, pkt->last_len); } } else { /* If segments after this one, then More Data must be TRUE */ uint8_t len; if (pkt->cnt_out < pkt->cnt_in) len = sizeof(pkt->u.s12[0].data); else len = pkt->last_len; l_debug("Fwd FRND pkt %6.6x", pkt->u.s12[pkt->cnt_out].seq); print_packet("Frnd-Msg", pkt->u.s12[pkt->cnt_out].data, len); pkt->u.s12[pkt->cnt_out].sent = true; mesh_net_send_seg(net, frnd->net_key_cur, pkt->iv_index, pkt->ttl, pkt->u.s12[pkt->cnt_out].seq, pkt->src, pkt->dst, pkt->u.s12[pkt->cnt_out].hdr, pkt->u.s12[pkt->cnt_out].data, len); } return; update: /* No More Data -- send Update message with md = false */ net_seq = mesh_net_get_seq_num(net); l_debug("Fwd FRND UPDATE %6.6x with MD == 0", net_seq); frnd->u.active.last = frnd->u.active.seq; mesh_net_get_snb_state(net, upd + 1, &iv_index); l_put_be32(iv_index, upd + 2); upd[6] = false; /* Queue is Empty */ print_packet("Update", upd, sizeof(upd)); mesh_net_transport_send(net, frnd->net_key_cur, 0, mesh_net_get_iv_index(net), 0, net_seq, 0, frnd->lp_addr, upd, sizeof(upd)); mesh_net_next_seq_num(net); } void friend_poll(struct mesh_net *net, uint16_t src, bool seq, struct mesh_friend *frnd) { struct l_queue *negotiations = mesh_net_get_negotiations(net); struct mesh_friend *neg; struct mesh_friend_msg *pkt; bool md; l_debug("POLL-RXED"); neg = l_queue_find(negotiations, match_by_lpn, L_UINT_TO_PTR(src)); if (neg && !neg->u.negotiate.clearing) { uint8_t msg[5] = { NET_OP_FRND_CLEAR }; l_debug("Won negotiation for %4.4x", neg->lp_addr); /* This call will clean-up and replace if already friends */ frnd = mesh_friend_new(net, src, neg->ele_cnt, neg->receive_delay, neg->frw, neg->poll_timeout, neg->fn_cnt, neg->lp_cnt); frnd->timeout = l_timeout_create_ms( frnd->poll_timeout * 100, friend_poll_timeout, frnd, NULL); l_timeout_remove(neg->timeout); net_key_unref(neg->net_key_cur); net_key_unref(neg->net_key_upd); neg->net_key_upd = neg->net_key_cur = 0; if (neg->old_friend == 0 || neg->old_friend == mesh_net_get_address(net)) { l_queue_remove(negotiations, neg); l_free(neg); } else { neg->u.negotiate.clearing = true; l_put_be16(neg->lp_addr, msg + 1); l_put_be16(neg->lp_cnt, msg + 3); mesh_net_transport_send(net, 0, 0, mesh_net_get_iv_index(net), DEFAULT_TTL, 0, 0, neg->old_friend, msg, sizeof(msg)); /* Reuse receive_delay as a shift counter to * time-out FRIEND_CLEAR */ neg->receive_delay = 1; neg->timeout = l_timeout_create(1, clear_retry, neg, NULL); } } if (!frnd) return; /* Reset Poll Timeout */ l_timeout_modify_ms(frnd->timeout, frnd->poll_timeout * 100); if (!l_queue_length(frnd->pkt_cache)) goto update; if (frnd->u.active.seq != frnd->u.active.last && frnd->u.active.seq != seq) { pkt = l_queue_peek_head(frnd->pkt_cache); if (pkt->cnt_out < pkt->cnt_in) { pkt->cnt_out++; } else { pkt = l_queue_pop_head(frnd->pkt_cache); l_free(pkt); } } pkt = l_queue_peek_head(frnd->pkt_cache); if (!pkt) goto update; frnd->u.active.seq = seq; frnd->u.active.last = !seq; md = (l_queue_length(frnd->pkt_cache) > 1); if (pkt->ctl) { /* Make sure we don't change the bit-sense of MD, * once it has been set because that would cause * a "Dirty Nonce" security violation */ if (!(pkt->u.one[0].sent)) pkt->u.one[0].md = md; } else { /* If segments after this one, then More Data must be TRUE */ if (pkt->cnt_out < pkt->cnt_in) md = true; /* Make sure we don't change the bit-sense of MD, once * it has been set because that would cause a * "Dirty Nonce" security violation */ if (!(pkt->u.s12[pkt->cnt_out].sent)) pkt->u.s12[pkt->cnt_out].md = md; } frnd->pkt = pkt; l_timeout_create_ms(frnd->frd, friend_delay_rsp, frnd, NULL); return; update: frnd->pkt = NULL; l_timeout_create_ms(frnd->frd, friend_delay_rsp, frnd, NULL); } void friend_sub_add(struct mesh_net *net, struct mesh_friend *frnd, const uint8_t *pkt, uint8_t len) { uint16_t *new_list; uint32_t net_seq; uint8_t plen = len; uint8_t msg[] = { NET_OP_PROXY_SUB_CONFIRM, 0 }; if (!frnd || MAX_FRND_GROUPS < frnd->u.active.grp_cnt + (len/2)) return; msg[1] = *pkt++; plen--; /* Sanity Check Values, abort if any illegal */ while (plen >= 2) { plen -= 2; if (l_get_be16(pkt + plen) < 0x8000) return; } new_list = l_malloc(frnd->u.active.grp_cnt * sizeof(uint16_t) + len); if (frnd->u.active.grp_list) memcpy(new_list, frnd->u.active.grp_list, frnd->u.active.grp_cnt * sizeof(uint16_t)); while (len >= 2) { new_list[frnd->u.active.grp_cnt++] = l_get_be16(pkt); pkt += 2; len -= 2; } l_free(frnd->u.active.grp_list); frnd->u.active.grp_list = new_list; print_packet("Tx-NET_OP_PROXY_SUB_CONFIRM", msg, sizeof(msg)); net_seq = mesh_net_get_seq_num(net); mesh_net_transport_send(net, frnd->net_key_cur, 0, mesh_net_get_iv_index(net), 0, net_seq, 0, frnd->lp_addr, msg, sizeof(msg)); mesh_net_next_seq_num(net); } void friend_sub_del(struct mesh_net *net, struct mesh_friend *frnd, const uint8_t *pkt, uint8_t len) { uint32_t net_seq; uint8_t msg[] = { NET_OP_PROXY_SUB_CONFIRM, 0 }; int i; if (!frnd) return; msg[1] = *pkt++; len--; while (len >= 2) { uint16_t grp = l_get_be16(pkt); for (i = frnd->u.active.grp_cnt - 1; i >= 0; i--) { if (frnd->u.active.grp_list[i] == grp) { frnd->u.active.grp_cnt--; memcpy(&frnd->u.active.grp_list[i], &frnd->u.active.grp_list[i + 1], (frnd->u.active.grp_cnt - i) * 2); break; } } len -= 2; pkt += 2; } print_packet("Tx-NET_OP_PROXY_SUB_CONFIRM", msg, sizeof(msg)); net_seq = mesh_net_get_seq_num(net); mesh_net_transport_send(net, frnd->net_key_cur, 0, mesh_net_get_iv_index(net), 0, net_seq, 0, frnd->lp_addr, msg, sizeof(msg)); mesh_net_next_seq_num(net); } bluez-5.87/mesh/PaxHeaders/rpl.c0000644000000000000000000000005014772767672013601 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/rpl.c0000644000000000000000000001416314772767672013267 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2020 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "mesh/mesh-defs.h" #include "mesh/node.h" #include "mesh/net.h" #include "mesh/util.h" #include "mesh/rpl.h" static const char *rpl_dir = "/rpl"; bool rpl_put_entry(struct mesh_node *node, uint16_t src, uint32_t iv_index, uint32_t seq) { const char *node_path; char src_file[PATH_MAX]; char seq_txt[7]; bool result = false; DIR *dir; int fd; if (!IS_UNICAST(src)) return false; node_path = node_get_storage_dir(node); if (strlen(node_path) + strlen(rpl_dir) + 15 >= PATH_MAX) return false; snprintf(src_file, PATH_MAX, "%s%s/%8.8x", node_path, rpl_dir, iv_index); dir = opendir(src_file); if (!dir) { if (mkdir(src_file, 0755) != 0) l_error("Failed to create dir: %s", src_file); } else closedir(dir); snprintf(src_file, PATH_MAX, "%s%s/%8.8x/%4.4x", node_path, rpl_dir, iv_index, src); fd = open(src_file, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd >= 0) { snprintf(seq_txt, 7, "%6.6x", seq); if (write(fd, seq_txt, 6) == 6) result = true; close(fd); } if (!result) return false; /* Delete RPL entry from old iv_index (if it exists) */ iv_index--; snprintf(src_file, PATH_MAX, "%s%s/%8.8x/%4.4x", node_path, rpl_dir, iv_index, src); if (remove(src_file) < 0 && errno != ENOENT) l_error("Failed to remove(%d): %s", errno, src_file); return result; } void rpl_del_entry(struct mesh_node *node, uint16_t src) { const char *node_path; char rpl_path[PATH_MAX]; struct dirent *entry; DIR *dir; if (!IS_UNICAST(src)) return; node_path = node_get_storage_dir(node); if (strlen(node_path) + strlen(rpl_dir) + 15 >= PATH_MAX) return; snprintf(rpl_path, PATH_MAX, "%s%s", node_path, rpl_dir); dir = opendir(rpl_path); if (!dir) return; /* Remove all instances of src address */ while ((entry = readdir(dir)) != NULL) { if (entry->d_type == DT_DIR && entry->d_name[0] != '.') { snprintf(rpl_path, PATH_MAX, "%s%s/%s/%4.4x", node_path, rpl_dir, entry->d_name, src); if (remove(rpl_path) < 0) l_error("Failed to remove(%d): %s", errno, rpl_path); } } closedir(dir); } static bool match_src(const void *a, const void *b) { const struct mesh_rpl *rpl = a; uint16_t src = L_PTR_TO_UINT(b); return rpl->src == src; } static void get_entries(const char *iv_path, struct l_queue *rpl_list) { struct mesh_rpl *rpl; struct dirent *entry; DIR *dir; int fd; const char *iv_txt; char src_path[PATH_MAX]; char seq_txt[7]; uint32_t iv_index, seq; uint16_t src; dir = opendir(iv_path); if (!dir) return; iv_txt = mesh_basename(iv_path); if (sscanf(iv_txt, "%08x", &iv_index) != 1) { closedir(dir); return; } memset(seq_txt, 0, sizeof(seq_txt)); while ((entry = readdir(dir)) != NULL) { /* RPL sequences are stored in src files under iv_index */ if (entry->d_type == DT_REG) { if (sscanf(entry->d_name, "%04hx", &src) != 1) continue; snprintf(src_path, PATH_MAX, "%s/%4.4x", iv_path, src); fd = open(src_path, O_RDONLY); if (fd < 0) continue; if (read(fd, seq_txt, 6) == 6 && sscanf(seq_txt, "%06x", &seq) == 1) { rpl = l_queue_find(rpl_list, match_src, L_UINT_TO_PTR(src)); if (rpl) { /* Replace older entries */ if (rpl->iv_index < iv_index) { rpl->iv_index = iv_index; rpl->seq = seq; } } else if (seq <= SEQ_MASK && IS_UNICAST(src)) { rpl = l_new(struct mesh_rpl, 1); rpl->src = src; rpl->iv_index = iv_index; rpl->seq = seq; l_queue_push_head(rpl_list, rpl); } } close(fd); } } closedir(dir); } bool rpl_get_list(struct mesh_node *node, struct l_queue *rpl_list) { const char *node_path; struct dirent *entry; char *rpl_path; size_t len; DIR *dir; if (!rpl_list) return false; node_path = node_get_storage_dir(node); len = strlen(node_path) + strlen(rpl_dir) + 15; if (len > PATH_MAX) return false; rpl_path = l_malloc(len); snprintf(rpl_path, len, "%s%s", node_path, rpl_dir); dir = opendir(rpl_path); if (!dir) { l_error("Failed to read RPL dir: %s", rpl_path); l_free(rpl_path); return false; } while ((entry = readdir(dir)) != NULL) { /* RPL sequences are stored in files under iv_indexs */ if (entry->d_type == DT_DIR && entry->d_name[0] != '.') { snprintf(rpl_path, len, "%s%s/%s", node_path, rpl_dir, entry->d_name); get_entries(rpl_path, rpl_list); } } l_free(rpl_path); closedir(dir); return true; } void rpl_update(struct mesh_node *node, uint32_t cur) { uint32_t old = cur - 1; const char *node_path; struct dirent *entry; char path[PATH_MAX]; DIR *dir; node_path = node_get_storage_dir(node); if (!node_path) return; if (strlen(node_path) + strlen(rpl_dir) + 15 >= PATH_MAX) return; /* Make sure path exists */ snprintf(path, PATH_MAX, "%s%s", node_path, rpl_dir); if (mkdir(path, 0755) != 0 && errno != EEXIST) l_error("Failed to create dir(%d): %s", errno, path); dir = opendir(path); if (!dir) return; /* Cleanup any stale or malformed trees */ while ((entry = readdir(dir)) != NULL) { if (entry->d_type == DT_DIR && entry->d_name[0] != '.') { uint32_t val; bool del = false; if (strlen(entry->d_name) != 8) del = true; else if (sscanf(entry->d_name, "%08x", &val) != 1) del = true; /* Delete all invalid iv_index trees */ if (del || (val != cur && val != old)) { snprintf(path, PATH_MAX, "%s%s/%s", node_path, rpl_dir, entry->d_name); del_path(path); } } } closedir(dir); } bool rpl_init(const char *node_path) { char path[PATH_MAX]; if (strlen(node_path) + strlen(rpl_dir) + 15 >= PATH_MAX) return false; snprintf(path, PATH_MAX, "%s%s", node_path, rpl_dir); if (mkdir(path, 0755) != 0 && errno != EEXIST) l_error("Failed to create dir(%d): %s", errno, path); return true; } bluez-5.87/mesh/PaxHeaders/mesh-io-generic.c0000644000000000000000000000005015063045254015732 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/mesh-io-generic.c0000644000000000000000000004560515063045254015425 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "monitor/bt.h" #include "src/shared/ad.h" #include "src/shared/hci.h" #include "src/shared/mgmt.h" #include "bluetooth/bluetooth.h" #include "bluetooth/mgmt.h" #include "mesh/mesh-defs.h" #include "mesh/mesh-mgmt.h" #include "mesh/mesh-io.h" #include "mesh/mesh-io-api.h" #include "mesh/mesh-io-generic.h" struct mesh_io_private { struct mesh_io *io; struct bt_hci *hci; struct l_timeout *tx_timeout; struct l_queue *tx_pkts; struct tx_pkt *tx; uint16_t interval; bool sending; bool active; }; struct process_data { struct mesh_io_private *pvt; const uint8_t *data; uint8_t len; struct mesh_io_recv_info info; }; struct tx_pkt { struct mesh_io_send_info info; bool delete; uint8_t len; uint8_t pkt[MESH_AD_MAX_LEN]; }; struct tx_pattern { const uint8_t *data; uint8_t len; }; static uint32_t get_instant(void) { struct timeval tm; uint32_t instant; gettimeofday(&tm, NULL); instant = tm.tv_sec * 1000; instant += tm.tv_usec / 1000; return instant; } static uint32_t instant_remaining_ms(uint32_t instant) { instant -= get_instant(); return instant; } static void process_rx_callbacks(void *v_reg, void *v_rx) { struct mesh_io_reg *rx_reg = v_reg; struct process_data *rx = v_rx; if (!memcmp(rx->data, rx_reg->filter, rx_reg->len)) rx_reg->cb(rx_reg->user_data, &rx->info, rx->data, rx->len); } static void process_rx(struct mesh_io_private *pvt, int8_t rssi, uint32_t instant, const uint8_t *addr, const uint8_t *data, uint8_t len) { struct process_data rx = { .pvt = pvt, .data = data, .len = len, .info.instant = instant, .info.addr = addr, .info.chan = 7, .info.rssi = rssi, }; l_queue_foreach(pvt->io->rx_regs, process_rx_callbacks, &rx); } static void event_adv_report(struct mesh_io *io, const void *buf, uint8_t size) { const struct bt_hci_evt_le_adv_report *evt = buf; const uint8_t *adv; const uint8_t *addr; uint32_t instant; uint8_t adv_len; uint16_t len = 0; int8_t rssi; if (evt->event_type != 0x03) return; instant = get_instant(); adv = evt->data; adv_len = evt->data_len; addr = evt->addr; /* rssi is just beyond last byte of data */ rssi = (int8_t) adv[adv_len]; while (len < adv_len - 1) { uint8_t field_len = adv[0]; /* Check for the end of advertising data */ if (field_len == 0) break; len += field_len + 1; /* Do not continue data parsing if got incorrect length */ if (len > adv_len) break; /* TODO: Create an Instant to use */ process_rx(io->pvt, rssi, instant, addr, adv + 1, adv[0]); adv += field_len + 1; } } static void event_callback(const void *buf, uint8_t size, void *user_data) { uint8_t event = l_get_u8(buf); struct mesh_io *io = user_data; switch (event) { case BT_HCI_EVT_LE_ADV_REPORT: event_adv_report(io, buf + 1, size - 1); break; default: l_debug("Other Meta Evt - %d", event); } } static void local_commands_callback(const void *data, uint8_t size, void *user_data) { const struct bt_hci_rsp_read_local_commands *rsp = data; if (rsp->status) l_error("Failed to read local commands"); } static void local_features_callback(const void *data, uint8_t size, void *user_data) { const struct bt_hci_rsp_read_local_features *rsp = data; if (rsp->status) l_error("Failed to read local features"); } static void hci_generic_callback(const void *data, uint8_t size, void *user_data) { uint8_t status = l_get_u8(data); if (status) l_error("Failed to initialize HCI"); } static void configure_hci(struct mesh_io_private *io) { struct bt_hci_cmd_le_set_scan_parameters cmd; struct bt_hci_cmd_set_event_mask cmd_sem; struct bt_hci_cmd_le_set_event_mask cmd_slem; struct bt_hci_cmd_le_set_random_address cmd_raddr; /* Set scan parameters */ cmd.type = 0x00; /* Passive Scanning. No scanning PDUs shall be sent */ cmd.interval = 0x0030; /* Scan Interval = N * 0.625ms */ cmd.window = 0x0030; /* Scan Window = N * 0.625ms */ cmd.own_addr_type = 0x00; /* Public Device Address */ /* Accept all advertising packets except directed advertising packets * not addressed to this device (default). */ cmd.filter_policy = 0x00; /* Set event mask * * Mask: 0x2000800002008890 * Disconnection Complete * Encryption Change * Read Remote Version Information Complete * Hardware Error * Data Buffer Overflow * Encryption Key Refresh Complete * LE Meta */ cmd_sem.mask[0] = 0x90; cmd_sem.mask[1] = 0x88; cmd_sem.mask[2] = 0x00; cmd_sem.mask[3] = 0x02; cmd_sem.mask[4] = 0x00; cmd_sem.mask[5] = 0x80; cmd_sem.mask[6] = 0x00; cmd_sem.mask[7] = 0x20; /* Set LE event mask * * Mask: 0x000000000000087f * LE Connection Complete * LE Advertising Report * LE Connection Update Complete * LE Read Remote Used Features Complete * LE Long Term Key Request * LE Remote Connection Parameter Request * LE Data Length Change * LE PHY Update Complete */ cmd_slem.mask[0] = 0x7f; cmd_slem.mask[1] = 0x08; cmd_slem.mask[2] = 0x00; cmd_slem.mask[3] = 0x00; cmd_slem.mask[4] = 0x00; cmd_slem.mask[5] = 0x00; cmd_slem.mask[6] = 0x00; cmd_slem.mask[7] = 0x00; /* Set LE random address */ l_getrandom(cmd_raddr.addr, 6); cmd_raddr.addr[5] |= 0xc0; /* TODO: Move to suitable place. Set suitable masks */ /* Reset Command */ bt_hci_send(io->hci, BT_HCI_CMD_RESET, NULL, 0, hci_generic_callback, NULL, NULL); /* Read local supported commands */ bt_hci_send(io->hci, BT_HCI_CMD_READ_LOCAL_COMMANDS, NULL, 0, local_commands_callback, NULL, NULL); /* Read local supported features */ bt_hci_send(io->hci, BT_HCI_CMD_READ_LOCAL_FEATURES, NULL, 0, local_features_callback, NULL, NULL); /* Set event mask */ bt_hci_send(io->hci, BT_HCI_CMD_SET_EVENT_MASK, &cmd_sem, sizeof(cmd_sem), hci_generic_callback, NULL, NULL); /* Set LE event mask */ bt_hci_send(io->hci, BT_HCI_CMD_LE_SET_EVENT_MASK, &cmd_slem, sizeof(cmd_slem), hci_generic_callback, NULL, NULL); /* Set LE random address */ bt_hci_send(io->hci, BT_HCI_CMD_LE_SET_RANDOM_ADDRESS, &cmd_raddr, sizeof(cmd_raddr), hci_generic_callback, NULL, NULL); /* Scan Params */ bt_hci_send(io->hci, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS, &cmd, sizeof(cmd), hci_generic_callback, NULL, NULL); } static void scan_enable_rsp(const void *buf, uint8_t size, void *user_data) { uint8_t status = *((uint8_t *) buf); if (status) l_error("LE Scan enable failed (0x%02x)", status); } static void set_recv_scan_enable(const void *buf, uint8_t size, void *user_data) { struct mesh_io_private *pvt = user_data; struct bt_hci_cmd_le_set_scan_enable cmd; cmd.enable = 0x01; /* Enable scanning */ cmd.filter_dup = 0x00; /* Report duplicates */ bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_SCAN_ENABLE, &cmd, sizeof(cmd), scan_enable_rsp, pvt, NULL); } static void scan_disable_rsp(const void *buf, uint8_t size, void *user_data) { struct bt_hci_cmd_le_set_scan_parameters cmd; struct mesh_io_private *pvt = user_data; uint8_t status = *((uint8_t *) buf); if (status) l_error("LE Scan disable failed (0x%02x)", status); cmd.type = pvt->active ? 0x01 : 0x00; /* Passive/Active scanning */ cmd.interval = L_CPU_TO_LE16(0x0010); /* 10 ms */ cmd.window = L_CPU_TO_LE16(0x0010); /* 10 ms */ cmd.own_addr_type = 0x01; /* ADDR_TYPE_RANDOM */ cmd.filter_policy = 0x00; /* Accept all */ bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS, &cmd, sizeof(cmd), set_recv_scan_enable, pvt, NULL); } static bool simple_match(const void *a, const void *b) { return a == b; } static bool find_by_ad_type(const void *a, const void *b) { const struct tx_pkt *tx = a; uint8_t ad_type = L_PTR_TO_UINT(b); return !ad_type || ad_type == tx->pkt[0]; } static bool find_by_pattern(const void *a, const void *b) { const struct tx_pkt *tx = a; const struct tx_pattern *pattern = b; if (tx->len < pattern->len) return false; return (!memcmp(tx->pkt, pattern->data, pattern->len)); } static bool find_active(const void *a, const void *b) { const struct mesh_io_reg *rx_reg = a; /* Mesh specific AD types do *not* require active scanning, * so do not turn on Active Scanning on their account. */ if (rx_reg->filter[0] < BT_AD_MESH_PROV || rx_reg->filter[0] > BT_AD_MESH_BEACON) return true; return false; } static void restart_scan(struct mesh_io_private *pvt) { struct bt_hci_cmd_le_set_scan_enable cmd; if (l_queue_isempty(pvt->io->rx_regs)) return; pvt->active = l_queue_find(pvt->io->rx_regs, find_active, NULL); cmd.enable = 0x00; /* Disable scanning */ cmd.filter_dup = 0x00; /* Report duplicates */ bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_SCAN_ENABLE, &cmd, sizeof(cmd), scan_disable_rsp, pvt, NULL); } static void hci_init(void *user_data) { struct mesh_io *io = user_data; bool result = true; if (io->pvt->hci) bt_hci_unref(io->pvt->hci); /* Clear controller HCI list to suppress mgmt interface warnings */ mesh_mgmt_clear(); io->pvt->hci = bt_hci_new_user_channel(io->index); if (!io->pvt->hci) { l_error("Failed to start mesh io (hci %u): %s", io->index, strerror(errno)); result = false; } if (result) { configure_hci(io->pvt); bt_hci_register(io->pvt->hci, BT_HCI_EVT_LE_META_EVENT, event_callback, io, NULL); l_debug("Started mesh on hci %u", io->index); restart_scan(io->pvt); } if (io->ready) io->ready(io->user_data, result); } static bool dev_init(struct mesh_io *io, void *opts, void *user_data) { if (!io || io->pvt) return false; io->pvt = l_new(struct mesh_io_private, 1); io->pvt->tx_pkts = l_queue_new(); io->pvt->io = io; l_idle_oneshot(hci_init, io, NULL); return true; } static bool dev_destroy(struct mesh_io *io) { struct mesh_io_private *pvt = io->pvt; if (!pvt) return true; bt_hci_unref(pvt->hci); l_timeout_remove(pvt->tx_timeout); l_queue_remove_if(pvt->tx_pkts, simple_match, pvt->tx); l_queue_destroy(pvt->tx_pkts, l_free); l_free(pvt->tx); l_free(pvt); io->pvt = NULL; return true; } static bool dev_caps(struct mesh_io *io, struct mesh_io_caps *caps) { struct mesh_io_private *pvt = io->pvt; if (!pvt || !caps) return false; caps->max_num_filters = 255; caps->window_accuracy = 50; return true; } static void send_cancel_done(const void *buf, uint8_t size, void *user_data) { struct mesh_io_private *pvt = user_data; struct bt_hci_cmd_le_set_random_address cmd; if (!pvt) return; pvt->sending = false; /* At end of any burst of ADVs, change random address */ l_getrandom(cmd.addr, 6); cmd.addr[5] |= 0xc0; bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_RANDOM_ADDRESS, &cmd, sizeof(cmd), NULL, NULL, NULL); } static void send_cancel(struct mesh_io_private *pvt) { struct bt_hci_cmd_le_set_adv_enable cmd; if (!pvt) return; if (!pvt->sending) { send_cancel_done(NULL, 0, pvt); return; } cmd.enable = 0x00; /* Disable advertising */ bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_ADV_ENABLE, &cmd, sizeof(cmd), send_cancel_done, pvt, NULL); } static void set_send_adv_enable(const void *buf, uint8_t size, void *user_data) { struct mesh_io_private *pvt = user_data; struct bt_hci_cmd_le_set_adv_enable cmd; if (!pvt) return; pvt->sending = true; cmd.enable = 0x01; /* Enable advertising */ bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_ADV_ENABLE, &cmd, sizeof(cmd), NULL, NULL, NULL); } static void set_send_adv_data(const void *buf, uint8_t size, void *user_data) { struct mesh_io_private *pvt = user_data; struct tx_pkt *tx; struct bt_hci_cmd_le_set_adv_data cmd; if (!pvt || !pvt->tx) return; tx = pvt->tx; if (tx->len >= sizeof(cmd.data)) goto done; memset(&cmd, 0, sizeof(cmd)); cmd.len = tx->len + 1; cmd.data[0] = tx->len; memcpy(cmd.data + 1, tx->pkt, tx->len); bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_ADV_DATA, &cmd, sizeof(cmd), set_send_adv_enable, pvt, NULL); done: if (tx->delete) { l_queue_remove_if(pvt->tx_pkts, simple_match, tx); l_free(tx); } pvt->tx = NULL; } static void set_send_adv_params(const void *buf, uint8_t size, void *user_data) { struct mesh_io_private *pvt = user_data; struct bt_hci_cmd_le_set_adv_parameters cmd; uint16_t hci_interval; if (!pvt) return; hci_interval = (pvt->interval * 16) / 10; cmd.min_interval = L_CPU_TO_LE16(hci_interval); cmd.max_interval = L_CPU_TO_LE16(hci_interval); cmd.type = 0x03; /* ADV_NONCONN_IND */ cmd.own_addr_type = 0x01; /* ADDR_TYPE_RANDOM */ cmd.direct_addr_type = 0x00; memset(cmd.direct_addr, 0, 6); cmd.channel_map = 0x07; cmd.filter_policy = 0x03; bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_ADV_PARAMETERS, &cmd, sizeof(cmd), set_send_adv_data, pvt, NULL); } static void send_pkt(struct mesh_io_private *pvt, struct tx_pkt *tx, uint16_t interval) { struct bt_hci_cmd_le_set_adv_enable cmd; /* Delete superseded packet in favor of new packet */ if (pvt->tx && pvt->tx != tx && pvt->tx->delete) { l_queue_remove_if(pvt->tx_pkts, simple_match, pvt->tx); l_free(pvt->tx); } pvt->tx = tx; pvt->interval = interval; if (!pvt->sending) { set_send_adv_params(NULL, 0, pvt); return; } cmd.enable = 0x00; /* Disable advertising */ bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_ADV_ENABLE, &cmd, sizeof(cmd), set_send_adv_params, pvt, NULL); } static void tx_to(struct l_timeout *timeout, void *user_data) { struct mesh_io_private *pvt = user_data; struct tx_pkt *tx; uint16_t ms; uint8_t count; if (!pvt) return; tx = l_queue_pop_head(pvt->tx_pkts); if (!tx) { l_timeout_remove(timeout); pvt->tx_timeout = NULL; send_cancel(pvt); return; } if (tx->info.type == MESH_IO_TIMING_TYPE_GENERAL) { ms = tx->info.u.gen.interval; count = tx->info.u.gen.cnt; if (count != MESH_IO_TX_COUNT_UNLIMITED) tx->info.u.gen.cnt--; } else { ms = 25; count = 1; } tx->delete = !!(count == 1); send_pkt(pvt, tx, ms); if (count == 1) { /* Recalculate wakeup if we are responding to POLL */ tx = l_queue_peek_head(pvt->tx_pkts); if (tx && tx->info.type == MESH_IO_TIMING_TYPE_POLL_RSP) { ms = instant_remaining_ms(tx->info.u.poll_rsp.instant + tx->info.u.poll_rsp.delay); } } else l_queue_push_tail(pvt->tx_pkts, tx); if (timeout) { pvt->tx_timeout = timeout; l_timeout_modify_ms(timeout, ms); } else pvt->tx_timeout = l_timeout_create_ms(ms, tx_to, pvt, NULL); } static void tx_worker(void *user_data) { struct mesh_io_private *pvt = user_data; struct tx_pkt *tx; uint32_t delay; tx = l_queue_peek_head(pvt->tx_pkts); if (!tx) return; switch (tx->info.type) { case MESH_IO_TIMING_TYPE_GENERAL: if (tx->info.u.gen.min_delay == tx->info.u.gen.max_delay) delay = tx->info.u.gen.min_delay; else { l_getrandom(&delay, sizeof(delay)); delay %= tx->info.u.gen.max_delay - tx->info.u.gen.min_delay; delay += tx->info.u.gen.min_delay; } break; case MESH_IO_TIMING_TYPE_POLL: if (tx->info.u.poll.min_delay == tx->info.u.poll.max_delay) delay = tx->info.u.poll.min_delay; else { l_getrandom(&delay, sizeof(delay)); delay %= tx->info.u.poll.max_delay - tx->info.u.poll.min_delay; delay += tx->info.u.poll.min_delay; } break; case MESH_IO_TIMING_TYPE_POLL_RSP: /* Delay until Instant + Delay */ delay = instant_remaining_ms(tx->info.u.poll_rsp.instant + tx->info.u.poll_rsp.delay); if (delay > 255) delay = 0; break; default: return; } if (!delay) tx_to(pvt->tx_timeout, pvt); else if (pvt->tx_timeout) l_timeout_modify_ms(pvt->tx_timeout, delay); else pvt->tx_timeout = l_timeout_create_ms(delay, tx_to, pvt, NULL); } static bool send_tx(struct mesh_io *io, struct mesh_io_send_info *info, const uint8_t *data, uint16_t len) { struct mesh_io_private *pvt = io->pvt; struct tx_pkt *tx; if (!info || !data || !len || len > sizeof(tx->pkt)) return false; tx = l_new(struct tx_pkt, 1); memcpy(&tx->info, info, sizeof(tx->info)); memcpy(&tx->pkt, data, len); tx->len = len; if (info->type == MESH_IO_TIMING_TYPE_POLL_RSP) l_queue_push_head(pvt->tx_pkts, tx); else { /* * If transmitter is idle, send packets at least twice to * guard against in-line cancellation of HCI command chain. */ if (info->type == MESH_IO_TIMING_TYPE_GENERAL && !pvt->tx && l_queue_isempty(pvt->tx_pkts) && tx->info.u.gen.cnt == 1) tx->info.u.gen.cnt++; l_queue_push_tail(pvt->tx_pkts, tx); } /* If not already sending, schedule the tx worker */ if (!pvt->tx) { l_timeout_remove(pvt->tx_timeout); pvt->tx_timeout = NULL; l_idle_oneshot(tx_worker, pvt, NULL); } return true; } static bool tx_cancel(struct mesh_io *io, const uint8_t *data, uint8_t len) { struct mesh_io_private *pvt = io->pvt; struct tx_pkt *tx; if (!data) return false; if (len == 1) { do { tx = l_queue_remove_if(pvt->tx_pkts, find_by_ad_type, L_UINT_TO_PTR(data[0])); l_free(tx); if (tx == pvt->tx) pvt->tx = NULL; } while (tx); } else { struct tx_pattern pattern = { .data = data, .len = len }; do { tx = l_queue_remove_if(pvt->tx_pkts, find_by_pattern, &pattern); l_free(tx); if (tx == pvt->tx) pvt->tx = NULL; } while (tx); } if (l_queue_isempty(pvt->tx_pkts)) { send_cancel(pvt); l_timeout_remove(pvt->tx_timeout); pvt->tx_timeout = NULL; } return true; } static bool recv_register(struct mesh_io *io, const uint8_t *filter, uint8_t len, mesh_io_recv_func_t cb, void *user_data) { struct bt_hci_cmd_le_set_scan_enable cmd; struct mesh_io_private *pvt = io->pvt; bool already_scanning; bool active = false; already_scanning = l_queue_length(io->rx_regs) > 1; /* Look for any AD types requiring Active Scanning */ if (l_queue_find(io->rx_regs, find_active, NULL)) active = true; if (!already_scanning || pvt->active != active) { pvt->active = active; cmd.enable = 0x00; /* Disable scanning */ cmd.filter_dup = 0x00; /* Report duplicates */ bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_SCAN_ENABLE, &cmd, sizeof(cmd), scan_disable_rsp, pvt, NULL); } return true; } static bool recv_deregister(struct mesh_io *io, const uint8_t *filter, uint8_t len) { struct bt_hci_cmd_le_set_scan_enable cmd = {0, 0}; struct mesh_io_private *pvt = io->pvt; bool active = false; /* Look for any AD types requiring Active Scanning */ if (l_queue_find(io->rx_regs, find_active, NULL)) active = true; if (l_queue_isempty(io->rx_regs)) { bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_SCAN_ENABLE, &cmd, sizeof(cmd), NULL, NULL, NULL); } else if (active != pvt->active) { pvt->active = active; bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_SCAN_ENABLE, &cmd, sizeof(cmd), scan_disable_rsp, pvt, NULL); } return true; } const struct mesh_io_api mesh_io_generic = { .init = dev_init, .destroy = dev_destroy, .caps = dev_caps, .send = send_tx, .reg = recv_register, .dereg = recv_deregister, .cancel = tx_cancel, }; bluez-5.87/mesh/PaxHeaders/agent.c0000644000000000000000000000005015017252326014054 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/agent.c0000644000000000000000000004124115017252326013537 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "mesh/crypto.h" #include "mesh/mesh.h" #include "mesh/provision.h" #include "mesh/error.h" #include "mesh/dbus.h" #include "mesh/agent.h" typedef enum { MESH_AGENT_REQUEST_BLINK, MESH_AGENT_REQUEST_BEEP, MESH_AGENT_REQUEST_VIBRATE, MESH_AGENT_REQUEST_OUT_NUMERIC, MESH_AGENT_REQUEST_OUT_ALPHA, MESH_AGENT_REQUEST_PUSH, MESH_AGENT_REQUEST_TWIST, MESH_AGENT_REQUEST_IN_NUMERIC, MESH_AGENT_REQUEST_IN_ALPHA, MESH_AGENT_REQUEST_STATIC_OOB, MESH_AGENT_REQUEST_PRIVATE_KEY, MESH_AGENT_REQUEST_PUBLIC_KEY, MESH_AGENT_REQUEST_CAPABILITIES, } agent_request_type_t; struct agent_request { agent_request_type_t type; struct l_dbus_message *msg; void *cb; void *user_data; }; struct mesh_agent { char *path; char *owner; struct mesh_agent_prov_caps caps; struct agent_request *req; }; struct prov_action { const char *action; uint16_t output; uint16_t input; uint8_t size; }; struct oob_info { const char *oob; uint16_t mask; }; static const struct prov_action cap_table[] = { {"blink", 0x0001, 0x0000, 1}, {"beep", 0x0002, 0x0000, 1}, {"vibrate", 0x0004, 0x0000, 1}, {"out-numeric", 0x0008, 0x0000, 8}, {"out-alpha", 0x0010, 0x0000, 8}, {"push", 0x0000, 0x0001, 1}, {"twist", 0x0000, 0x0002, 1}, {"in-numeric", 0x0000, 0x0004, 8}, {"in-alpha", 0x0000, 0x0008, 8} }; static const struct oob_info oob_table[] = { {"other", 0x0001}, {"uri", 0x0002}, {"machine-code-2d", 0x0004}, {"barcode", 0x0008}, {"nfc", 0x0010}, {"number", 0x0020}, {"string", 0x0040}, {"on-box", 0x0800}, {"in-box", 0x1000}, {"on-paper", 0x2000}, {"in-manual", 0x4000}, {"on-device", 0x8000} }; static struct l_queue *agents; static bool simple_match(const void *a, const void *b) { return a == b; } static bool parse_prov_caps(struct mesh_agent_prov_caps *caps, struct l_dbus_message_iter *property) { struct l_dbus_message_iter iter_caps; const char *str; uint32_t i; if (!l_dbus_message_iter_get_variant(property, "as", &iter_caps)) return false; while (l_dbus_message_iter_next_entry(&iter_caps, &str)) { for (i = 0; i < L_ARRAY_SIZE(cap_table); i++) { if (strcmp(str, cap_table[i].action)) continue; caps->output_action |= cap_table[i].output; if (cap_table[i].output && caps->output_size < cap_table[i].size) caps->output_size = cap_table[i].size; caps->input_action |= cap_table[i].input; if (cap_table[i].input && caps->input_size < cap_table[i].size) caps->input_size = cap_table[i].size; break; } if (!strcmp(str, "public-oob")) caps->pub_type = 1; else if (!strcmp(str, "static-oob")) caps->static_type = 1; } return true; } static bool parse_oob_info(struct mesh_agent_prov_caps *caps, struct l_dbus_message_iter *property) { struct l_dbus_message_iter iter_oob; uint32_t i; const char *str; if (!l_dbus_message_iter_get_variant(property, "as", &iter_oob)) return false; while (l_dbus_message_iter_next_entry(&iter_oob, &str)) { for (i = 0; i < L_ARRAY_SIZE(oob_table); i++) { if (strcmp(str, oob_table[i].oob)) continue; caps->oob_info |= oob_table[i].mask; } } return true; } static bool parse_properties(struct mesh_agent *agent, struct l_dbus_message_iter *properties) { const char *key, *uri_string; struct l_dbus_message_iter variant; memset(&agent->caps, 0, sizeof(agent->caps)); while (l_dbus_message_iter_next_entry(properties, &key, &variant)) { if (!strcmp(key, "Capabilities")) { if (!parse_prov_caps(&agent->caps, &variant)) return false; } else if (!strcmp(key, "URI")) { uint8_t salt[16]; if (!l_dbus_message_iter_get_variant(&variant, "s", &uri_string)) return false; mesh_crypto_s1(uri_string, strlen(uri_string), salt); agent->caps.uri_hash = salt[0] << 24 | salt[1] << 16 | salt[2] << 8 | salt[3] << 0; agent->caps.oob_info |= OOB_INFO_URI_HASH; } else if (!strcmp(key, "OutOfBandInfo")) { if (!parse_oob_info(&agent->caps, &variant)) return false; } } return true; } static void agent_free(void *agent_data) { struct mesh_agent *agent = agent_data; int err; mesh_agent_cb_t simple_cb; mesh_agent_key_cb_t key_cb; mesh_agent_number_cb_t number_cb; err = MESH_ERROR_DOES_NOT_EXIST; if (agent->req && agent->req->cb) { struct agent_request *req = agent->req; switch (req->type) { case MESH_AGENT_REQUEST_PUSH: case MESH_AGENT_REQUEST_TWIST: case MESH_AGENT_REQUEST_IN_NUMERIC: number_cb = req->cb; number_cb(req->user_data, err, 0); break; case MESH_AGENT_REQUEST_IN_ALPHA: case MESH_AGENT_REQUEST_STATIC_OOB: case MESH_AGENT_REQUEST_PRIVATE_KEY: case MESH_AGENT_REQUEST_PUBLIC_KEY: key_cb = req->cb; key_cb(req->user_data, err, NULL, 0); break; case MESH_AGENT_REQUEST_BLINK: case MESH_AGENT_REQUEST_BEEP: case MESH_AGENT_REQUEST_VIBRATE: case MESH_AGENT_REQUEST_OUT_NUMERIC: case MESH_AGENT_REQUEST_OUT_ALPHA: case MESH_AGENT_REQUEST_CAPABILITIES: simple_cb = agent->req->cb; simple_cb(req->user_data, err); default: break; } l_dbus_message_unref(req->msg); l_free(req); } l_free(agent->path); l_free(agent->owner); l_free(agent); } void mesh_agent_remove(struct mesh_agent *agent) { if (!agent) return; if (l_queue_remove(agents, agent)) agent_free(agent); } void mesh_agent_cleanup(void) { if (!agents) return; l_queue_destroy(agents, agent_free); agents = NULL; } void mesh_agent_init(void) { if (!agents) agents = l_queue_new(); } struct mesh_agent *mesh_agent_create(const char *path, const char *owner, struct l_dbus_message_iter *properties) { struct mesh_agent *agent; agent = l_new(struct mesh_agent, 1); agent->owner = l_strdup(owner); agent->path = l_strdup(path); if (!parse_properties(agent, properties)) { l_free(agent); return NULL; } l_queue_push_tail(agents, agent); return agent; } struct mesh_agent_prov_caps *mesh_agent_get_caps(struct mesh_agent *agent) { if (!agent || !l_queue_find(agents, simple_match, agent)) return NULL; return &agent->caps; } static struct agent_request *create_request(agent_request_type_t type, void *cb, void *data) { struct agent_request *req; req = l_new(struct agent_request, 1); req->type = type; req->cb = cb; req->user_data = data; return req; } static int get_reply_error(struct l_dbus_message *reply) { const char *name, *desc; if (l_dbus_message_is_error(reply)) { l_dbus_message_get_error(reply, &name, &desc); l_error("Agent failed (%s), %s", name, desc); return MESH_ERROR_FAILED; } return MESH_ERROR_NONE; } static void properties_reply(struct l_dbus_message *reply, void *user_data) { struct mesh_agent *agent = user_data; struct agent_request *req; mesh_agent_cb_t cb; struct l_dbus_message_iter properties; int err; if (!l_queue_find(agents, simple_match, agent) || !agent->req) return; req = agent->req; err = get_reply_error(reply); if (err != MESH_ERROR_NONE) goto done; if (!l_dbus_message_get_arguments(reply, "a{sv}", &properties)) { err = MESH_ERROR_FAILED; goto done; } if (!parse_properties(agent, &properties)) err = MESH_ERROR_FAILED; done: if (req->cb) { cb = req->cb; cb(req->user_data, err); } l_dbus_message_unref(req->msg); l_free(req); agent->req = NULL; } void mesh_agent_refresh(struct mesh_agent *agent, mesh_agent_cb_t cb, void *user_data) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg; struct l_dbus_message_builder *builder; agent->req = create_request(MESH_AGENT_REQUEST_CAPABILITIES, (void *)cb, user_data); msg = l_dbus_message_new_method_call(dbus, agent->owner, agent->path, L_DBUS_INTERFACE_PROPERTIES, "GetAll"); builder = l_dbus_message_builder_new(msg); l_dbus_message_builder_append_basic(builder, 's', MESH_PROVISION_AGENT_INTERFACE); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); l_dbus_send_with_reply(dbus_get_bus(), msg, properties_reply, agent, NULL); agent->req->msg = l_dbus_message_ref(msg); } static void simple_reply(struct l_dbus_message *reply, void *user_data) { struct mesh_agent *agent = user_data; struct agent_request *req; mesh_agent_cb_t cb; int err; if (!l_queue_find(agents, simple_match, agent) || !agent->req) return; req = agent->req; err = get_reply_error(reply); l_dbus_message_unref(req->msg); if (req->cb) { cb = req->cb; cb(req->user_data, err); } l_free(req); agent->req = NULL; } static void numeric_reply(struct l_dbus_message *reply, void *user_data) { struct mesh_agent *agent = user_data; struct agent_request *req; mesh_agent_number_cb_t cb; uint32_t count; int err; if (!l_queue_find(agents, simple_match, agent) || !agent->req) return; req = agent->req; err = get_reply_error(reply); count = 0; if (err == MESH_ERROR_NONE) { if (!l_dbus_message_get_arguments(reply, "u", &count)) { l_error("Failed to retrieve numeric input"); err = MESH_ERROR_FAILED; } } l_dbus_message_unref(req->msg); if (req->cb) { cb = req->cb; cb(req->user_data, err, count); } l_free(req); agent->req = NULL; } static void key_reply(struct l_dbus_message *reply, void *user_data) { struct mesh_agent *agent = user_data; struct agent_request *req; mesh_agent_key_cb_t cb; struct l_dbus_message_iter iter_array; uint32_t n = 0, expected_len = 0; uint8_t *buf = NULL; int err; if (!l_queue_find(agents, simple_match, agent) || !agent->req) return; req = agent->req; err = get_reply_error(reply); if (err != MESH_ERROR_NONE) goto done; if (!l_dbus_message_get_arguments(reply, "ay", &iter_array)) { l_error("Failed to retrieve key input"); err = MESH_ERROR_FAILED; goto done; } if (!l_dbus_message_iter_get_fixed_array(&iter_array, &buf, &n)) { l_error("Failed to retrieve key input"); err = MESH_ERROR_FAILED; goto done; } if (req->type == MESH_AGENT_REQUEST_PRIVATE_KEY) expected_len = 32; else if (req->type == MESH_AGENT_REQUEST_PUBLIC_KEY) expected_len = 64; else expected_len = 16; if (n != expected_len) { l_error("Bad response length: %u (need %u)", n, expected_len); err = MESH_ERROR_FAILED; n = 0; } done: if (req->cb) { cb = req->cb; cb(req->user_data, err, buf, n); } l_dbus_message_unref(req->msg); l_free(req); agent->req = NULL; } static int output_request(struct mesh_agent *agent, const char *action, agent_request_type_t type, uint32_t cnt, void *cb, void *user_data) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg; struct l_dbus_message_builder *builder; if (!l_queue_find(agents, simple_match, agent)) return MESH_ERROR_DOES_NOT_EXIST; if (agent->req) return MESH_ERROR_BUSY; agent->req = create_request(type, cb, user_data); msg = l_dbus_message_new_method_call(dbus, agent->owner, agent->path, MESH_PROVISION_AGENT_INTERFACE, "DisplayNumeric"); builder = l_dbus_message_builder_new(msg); l_dbus_message_builder_append_basic(builder, 's', action); l_dbus_message_builder_append_basic(builder, 'u', &cnt); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); l_debug("Send DisplayNumeric request to %s %s", agent->owner, agent->path); l_dbus_send_with_reply(dbus_get_bus(), msg, simple_reply, agent, NULL); agent->req->msg = l_dbus_message_ref(msg); return MESH_ERROR_NONE; } static int prompt_input(struct mesh_agent *agent, const char *action, agent_request_type_t type, bool numeric, void *cb, void *user_data) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg; struct l_dbus_message_builder *builder; const char *method_name; l_dbus_message_func_t reply_cb; if (!l_queue_find(agents, simple_match, agent)) return MESH_ERROR_DOES_NOT_EXIST; if (agent->req) return MESH_ERROR_BUSY; agent->req = create_request(type, cb, user_data); method_name = numeric ? "PromptNumeric" : "PromptStatic"; msg = l_dbus_message_new_method_call(dbus, agent->owner, agent->path, MESH_PROVISION_AGENT_INTERFACE, method_name); builder = l_dbus_message_builder_new(msg); l_dbus_message_builder_append_basic(builder, 's', action); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); l_debug("Send \"%s\" input request to %s %s", action, agent->owner, agent->path); reply_cb = numeric ? numeric_reply : key_reply; l_dbus_send_with_reply(dbus_get_bus(), msg, reply_cb, agent, NULL); agent->req->msg = l_dbus_message_ref(msg); return MESH_ERROR_NONE; } static int request_key(struct mesh_agent *agent, agent_request_type_t type, void *cb, void *user_data) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg; const char *method_name; if (!l_queue_find(agents, simple_match, agent)) return MESH_ERROR_DOES_NOT_EXIST; if (agent->req) return MESH_ERROR_BUSY; agent->req = create_request(type, cb, user_data); method_name = (type == MESH_AGENT_REQUEST_PRIVATE_KEY) ? "PrivateKey" : "PublicKey"; msg = l_dbus_message_new_method_call(dbus, agent->owner, agent->path, MESH_PROVISION_AGENT_INTERFACE, method_name); l_dbus_message_set_arguments(msg, ""); l_debug("Send key request to %s %s", agent->owner, agent->path); l_dbus_send_with_reply(dbus_get_bus(), msg, key_reply, agent, NULL); agent->req->msg = l_dbus_message_ref(msg); return MESH_ERROR_NONE; } int mesh_agent_display_string(struct mesh_agent *agent, const char *str, mesh_agent_cb_t cb, void *user_data) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg; struct l_dbus_message_builder *builder; if (!l_queue_find(agents, simple_match, agent)) return MESH_ERROR_DOES_NOT_EXIST; if (agent->req) return MESH_ERROR_BUSY; agent->req = create_request(MESH_AGENT_REQUEST_OUT_ALPHA, cb, user_data); msg = l_dbus_message_new_method_call(dbus, agent->owner, agent->path, MESH_PROVISION_AGENT_INTERFACE, "DisplayString"); builder = l_dbus_message_builder_new(msg); l_dbus_message_builder_append_basic(builder, 's', str); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); l_debug("Send DisplayString request to %s %s", agent->owner, agent->path); l_dbus_send_with_reply(dbus_get_bus(), msg, simple_reply, agent, NULL); agent->req->msg = l_dbus_message_ref(msg); return MESH_ERROR_NONE; } int mesh_agent_display_number(struct mesh_agent *agent, bool initiator, uint8_t action, uint32_t count, mesh_agent_cb_t cb, void *user_data) { const char *str_type; agent_request_type_t type; type = action; if (initiator) type = action + MESH_AGENT_REQUEST_PUSH; if (type >= L_ARRAY_SIZE(cap_table)) return MESH_ERROR_INVALID_ARGS; str_type = cap_table[type].action; return output_request(agent, str_type, type, count, cb, user_data); } int mesh_agent_prompt_number(struct mesh_agent *agent, bool initiator, uint8_t action, mesh_agent_number_cb_t cb, void *user_data) { const char *str_type; agent_request_type_t type; type = action; if (!initiator) type = action + MESH_AGENT_REQUEST_PUSH; if (type >= L_ARRAY_SIZE(cap_table)) return MESH_ERROR_INVALID_ARGS; str_type = cap_table[type].action; return prompt_input(agent, str_type, type, true, cb, user_data); } int mesh_agent_prompt_alpha(struct mesh_agent *agent, bool initiator, mesh_agent_key_cb_t cb, void *user_data) { if (initiator) return prompt_input(agent, cap_table[MESH_AGENT_REQUEST_OUT_ALPHA].action, MESH_AGENT_REQUEST_OUT_ALPHA, false, cb, user_data); else return prompt_input(agent, cap_table[MESH_AGENT_REQUEST_IN_ALPHA].action, MESH_AGENT_REQUEST_IN_ALPHA, false, cb, user_data); } int mesh_agent_request_static(struct mesh_agent *agent, mesh_agent_key_cb_t cb, void *user_data) { return prompt_input(agent, "static-oob", MESH_AGENT_REQUEST_STATIC_OOB, false, cb, user_data); } int mesh_agent_request_private_key(struct mesh_agent *agent, mesh_agent_key_cb_t cb, void *user_data) { return request_key(agent, MESH_AGENT_REQUEST_PRIVATE_KEY, cb, user_data); } int mesh_agent_request_public_key(struct mesh_agent *agent, mesh_agent_key_cb_t cb, void *user_data) { return request_key(agent, MESH_AGENT_REQUEST_PUBLIC_KEY, cb, user_data); } void mesh_agent_cancel(struct mesh_agent *agent) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg; if (!l_queue_find(agents, simple_match, agent)) return; msg = l_dbus_message_new_method_call(dbus, agent->owner, agent->path, MESH_PROVISION_AGENT_INTERFACE, "Cancel"); l_dbus_message_set_arguments(msg, ""); l_dbus_send(dbus, msg); } bluez-5.87/mesh/PaxHeaders/crypto.h0000644000000000000000000000005015063045254014304 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/crypto.h0000644000000000000000000001034415063045254013767 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ #include #include #include bool mesh_crypto_aes_ccm_encrypt(const uint8_t nonce[13], const uint8_t key[16], const uint8_t *aad, uint16_t aad_len, const void *msg, uint16_t msg_len, void *out_msg, size_t mic_size); bool mesh_crypto_aes_ccm_decrypt(const uint8_t nonce[13], const uint8_t key[16], const uint8_t *aad, uint16_t aad_len, const void *enc_msg, uint16_t enc_msg_len, void *out_msg, void *out_mic, size_t mic_size); bool mesh_crypto_nkik(const uint8_t network_key[16], uint8_t identity_key[16]); bool mesh_crypto_nkbk(const uint8_t network_key[16], uint8_t beacon_key[16]); bool mesh_crypto_nkpk(const uint8_t network_key[16], uint8_t private_key[16]); bool mesh_crypto_identity(const uint8_t net_key[16], uint16_t addr, uint8_t id[16]); bool mesh_crypto_beacon_cmac(const uint8_t encryption_key[16], const uint8_t network_id[8], uint32_t iv_index, bool kr, bool iu, uint64_t *cmac); bool mesh_crypto_device_key(const uint8_t secret[32], const uint8_t salt[16], uint8_t device_key[16]); bool mesh_crypto_virtual_addr(const uint8_t virtual_label[16], uint16_t *v_addr); bool mesh_crypto_nonce(const uint8_t secret[32], const uint8_t salt[16], uint8_t nonce[13]); bool mesh_crypto_k1(const uint8_t ikm[16], const uint8_t salt[16], const void *info, size_t info_len, uint8_t okm[16]); bool mesh_crypto_k2(const uint8_t n[16], const uint8_t *p, size_t p_len, uint8_t net_id[1], uint8_t enc_key[16], uint8_t priv_key[16]); bool mesh_crypto_k3(const uint8_t n[16], uint8_t out64[8]); bool mesh_crypto_k4(const uint8_t a[16], uint8_t out5[1]); bool mesh_crypto_s1(const void *info, size_t len, uint8_t salt[16]); bool mesh_crypto_prov_prov_salt(const uint8_t conf_salt[16], const uint8_t prov_rand[16], const uint8_t dev_rand[16], uint8_t prov_salt[16]); bool mesh_crypto_prov_conf_key(const uint8_t secret[32], const uint8_t salt[16], uint8_t conf_key[16]); bool mesh_crypto_session_key(const uint8_t secret[32], const uint8_t salt[16], uint8_t session_key[16]); bool mesh_crypto_packet_build(bool ctl, uint8_t ttl, uint32_t seq, uint16_t src, uint16_t dst, uint8_t opcode, bool segmented, uint8_t key_aid, bool szmic, uint16_t seqZero, uint8_t segO, uint8_t segN, const uint8_t *payload, uint8_t payload_len, uint8_t *packet, uint8_t *packet_len); bool mesh_crypto_packet_parse(const uint8_t *packet, uint8_t packet_len, bool *ctl, uint8_t *ttl, uint32_t *seq, uint16_t *src, uint16_t *dst, uint32_t *cookie, uint8_t *opcode, bool *segmented, uint8_t *key_aid, bool *szmic, bool *relay, uint16_t *seqZero, uint8_t *segO, uint8_t *segN, const uint8_t **payload, uint8_t *payload_len); bool mesh_crypto_payload_encrypt(uint8_t *aad, const uint8_t *payload, uint8_t *out, uint16_t payload_len, uint16_t src, uint16_t dst, uint8_t key_aid, uint32_t seq_num, uint32_t iv_index, bool aszmic, const uint8_t application_key[16]); bool mesh_crypto_payload_decrypt(uint8_t *aad, uint16_t aad_len, const uint8_t *payload, uint16_t payload_len, bool szmict, uint16_t src, uint16_t dst, uint8_t key_aid, uint32_t seq_num, uint32_t iv_index, uint8_t *out, const uint8_t application_key[16]); bool mesh_crypto_packet_encode(uint8_t *packet, uint8_t packet_len, uint32_t iv_index, const uint8_t network_key[16], const uint8_t privacy_key[16]); bool mesh_crypto_packet_decode(const uint8_t *packet, uint8_t packet_len, bool proxy, uint8_t *out, uint32_t iv_index, const uint8_t network_key[16], const uint8_t privacy_key[16]); bool mesh_crypto_packet_label(uint8_t *packet, uint8_t packet_len, uint16_t iv_index, uint8_t network_id); uint8_t mesh_crypto_compute_fcs(const uint8_t *packet, uint8_t packet_len); bool mesh_crypto_check_fcs(const uint8_t *packet, uint8_t packet_len, uint8_t received_fcs); bool mesh_crypto_aes_cmac(const uint8_t key[16], const uint8_t *msg, size_t msg_len, uint8_t res[16]); bool mesh_crypto_check_avail(void); bluez-5.87/mesh/PaxHeaders/net-keys.c0000644000000000000000000000005015063045254014516 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/net-keys.c0000644000000000000000000004136215063045254014205 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "src/shared/ad.h" #include "mesh/mesh-defs.h" #include "mesh/util.h" #include "mesh/crypto.h" #include "mesh/mesh-io.h" #include "mesh/net.h" #include "mesh/net-keys.h" #define BEACON_INTERVAL_MIN 10 #define BEACON_INTERVAL_MAX 600 /* This allows daemon to skip decryption on recently seen beacons */ #define BEACON_CACHE_MAX 10 struct beacon_rx { uint8_t data[BEACON_LEN_MAX]; uint32_t id; uint32_t ivi; bool kr; bool ivu; }; struct beacon_observe { struct l_timeout *timeout; uint32_t ts; uint16_t period; uint16_t seen; uint16_t expected; bool half_period; }; struct net_key { uint32_t id; struct l_timeout *mpb_to; uint8_t *mpb; uint8_t *snb; struct beacon_observe observe; uint32_t ivi; uint16_t ref_cnt; uint16_t mpb_enables; uint16_t snb_enables; uint8_t mpb_refresh; uint8_t friend_key; uint8_t nid; uint8_t flooding[16]; uint8_t enc_key[16]; uint8_t prv_key[16]; uint8_t snb_key[16]; uint8_t pvt_key[16]; uint8_t net_id[8]; bool kr; bool ivu; }; static struct l_queue *beacons; static struct l_queue *keys; static uint32_t last_flooding_id; /* To avoid re-decrypting same packet for multiple nodes, cache and check */ static uint8_t cache_pkt[MESH_NET_MAX_PDU_LEN]; static uint8_t cache_plain[MESH_NET_MAX_PDU_LEN]; static size_t cache_len; static size_t cache_plainlen; static uint32_t cache_id; static uint32_t cache_iv_index; static bool match_flooding(const void *a, const void *b) { const struct net_key *key = a; return (memcmp(key->flooding, b, sizeof(key->flooding)) == 0); } static bool match_id(const void *a, const void *b) { const struct net_key *key = a; uint32_t id = L_PTR_TO_UINT(b); return id == key->id; } static bool match_network(const void *a, const void *b) { const struct net_key *key = a; const uint8_t *net_id = b; return memcmp(key->net_id, net_id, sizeof(key->net_id)) == 0; } /* Key added from Provisioning, NetKey Add or NetKey update */ uint32_t net_key_add(const uint8_t flooding[16]) { struct net_key *key = l_queue_find(keys, match_flooding, flooding); uint8_t p[] = {0}; bool result; if (key) { key->ref_cnt++; return key->id; } if (!keys) keys = l_queue_new(); if (!beacons) beacons = l_queue_new(); key = l_new(struct net_key, 1); memcpy(key->flooding, flooding, 16); key->ref_cnt++; key->mpb_refresh = NET_MPB_REFRESH_DEFAULT; result = mesh_crypto_k2(flooding, p, sizeof(p), &key->nid, key->enc_key, key->prv_key); if (!result) goto fail; result = mesh_crypto_k3(flooding, key->net_id); if (!result) goto fail; result = mesh_crypto_nkbk(flooding, key->snb_key); if (!result) goto fail; result = mesh_crypto_nkpk(flooding, key->pvt_key); if (!result) goto fail; key->id = ++last_flooding_id; l_queue_push_tail(keys, key); return key->id; fail: l_free(key); return 0; } uint32_t net_key_frnd_add(uint32_t flooding_id, uint16_t lpn, uint16_t frnd, uint16_t lp_cnt, uint16_t fn_cnt) { const struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(flooding_id)); struct net_key *frnd_key; uint8_t p[9] = {0x01}; bool result; if (!key || key->friend_key) return 0; frnd_key = l_new(struct net_key, 1); l_put_be16(lpn, p + 1); l_put_be16(frnd, p + 3); l_put_be16(lp_cnt, p + 5); l_put_be16(fn_cnt, p + 7); result = mesh_crypto_k2(key->flooding, p, sizeof(p), &frnd_key->nid, frnd_key->enc_key, frnd_key->prv_key); if (!result) { l_free(frnd_key); return 0; } frnd_key->friend_key = true; frnd_key->ref_cnt++; frnd_key->id = ++last_flooding_id; l_queue_push_head(keys, frnd_key); return frnd_key->id; } void net_key_unref(uint32_t id) { struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); if (key && key->ref_cnt) { if (--key->ref_cnt == 0) { l_timeout_remove(key->observe.timeout); l_queue_remove(keys, key); l_free(key); } } } bool net_key_confirm(uint32_t id, const uint8_t flooding[16]) { struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); if (key) return !memcmp(key->flooding, flooding, sizeof(key->flooding)); return false; } bool net_key_retrieve(uint32_t id, uint8_t *flooding) { struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); if (key) { memcpy(flooding, key->flooding, sizeof(key->flooding)); return true; } return false; } static void decrypt_net_pkt(void *a, void *b) { const struct net_key *key = a; bool result; if (cache_id || !key->ref_cnt || (cache_pkt[0] & 0x7f) != key->nid) return; result = mesh_crypto_packet_decode(cache_pkt, cache_len, false, cache_plain, cache_iv_index, key->enc_key, key->prv_key); if (result) { cache_id = key->id; cache_plainlen = cache_len; } } uint32_t net_key_decrypt(uint32_t iv_index, const uint8_t *pkt, size_t len, uint8_t **plain, size_t *plain_len) { /* If we already successfully decrypted this packet, use cached data */ if (cache_id && cache_len == len && !memcmp(pkt, cache_pkt, len)) { /* IV Index must match what was used to decrypt */ if (cache_iv_index != iv_index) return 0; goto done; } cache_id = 0; memcpy(cache_pkt, pkt, len); cache_len = len; cache_iv_index = iv_index; /* Try all network keys known to us */ l_queue_foreach(keys, decrypt_net_pkt, NULL); done: if (cache_id) { *plain = cache_plain; *plain_len = cache_plainlen; } return cache_id; } bool net_key_encrypt(uint32_t id, uint32_t iv_index, uint8_t *pkt, size_t len) { struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); bool result; if (!key) return false; result = mesh_crypto_packet_encode(pkt, len, iv_index, key->enc_key, key->prv_key); if (!result) return false; result = mesh_crypto_packet_label(pkt, len, iv_index, key->nid); return result; } uint32_t net_key_network_id(const uint8_t net_id[8]) { struct net_key *key = l_queue_find(keys, match_network, net_id); if (!key) return 0; return key->id; } struct auth_check { const uint8_t *data; uint32_t id; uint32_t ivi; bool ivu; bool kr; }; static void check_auth(void *a, void *b) { struct net_key *key = a; struct auth_check *auth = b; uint8_t out[5]; /* Stop checking if already found */ if (auth->id) return; if (mesh_crypto_aes_ccm_decrypt(auth->data + 1, key->pvt_key, NULL, 0, auth->data + 14, 13, out, NULL, 8)) { auth->id = key->id; auth->ivi = l_get_be32(out + 1); auth->ivu = !!(out[0] & 0x02); auth->kr = !!(out[0] & 0x01); } } static uint32_t private_beacon_check(const void *beacon, uint32_t *ivi, bool *ivu, bool *kr) { struct auth_check auth = { .data = beacon, .id = 0, }; auth.id = 0; l_queue_foreach(keys, check_auth, &auth); if (auth.id) { *ivi = auth.ivi; *ivu = auth.ivu; *kr = auth.kr; } return auth.id; } bool net_key_snb_check(uint32_t id, uint32_t iv_index, bool kr, bool ivu, uint64_t cmac) { struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); uint64_t cmac_check; if (!key) return false; /* Any behavioral changes must pass CMAC test */ if (!mesh_crypto_beacon_cmac(key->snb_key, key->net_id, iv_index, kr, ivu, &cmac_check)) { l_error("mesh_crypto_beacon_cmac failed"); return false; } if (cmac != cmac_check) { l_error("cmac compare failed 0x%16" PRIx64 " != 0x%16" PRIx64, cmac, cmac_check); return false; } return true; } static bool mpb_compose(struct net_key *key, uint32_t ivi, bool kr, bool ivu) { uint8_t b_data[5 + 8]; uint8_t random[13]; if (!key) return false; b_data[0] = 0; l_put_be32(ivi, b_data + 1); if (kr) b_data[0] |= KEY_REFRESH; if (ivu) b_data[0] |= IV_INDEX_UPDATE; l_getrandom(random, sizeof(random)); if (!mesh_crypto_aes_ccm_encrypt(random, key->pvt_key, NULL, 0, b_data, 5, b_data, 8)) return false; key->mpb[0] = BT_AD_MESH_BEACON; key->mpb[1] = BEACON_TYPE_MPB; memcpy(key->mpb + 2, random, 13); memcpy(key->mpb + 15, b_data, 13); return true; } static bool snb_compose(struct net_key *key, uint32_t ivi, bool kr, bool ivu) { uint64_t cmac; if (!key) return false; /* Any behavioral changes must pass CMAC test */ if (!mesh_crypto_beacon_cmac(key->snb_key, key->net_id, ivi, kr, ivu, &cmac)) { l_error("mesh_crypto_beacon_cmac failed"); return false; } key->snb[0] = BT_AD_MESH_BEACON; key->snb[1] = BEACON_TYPE_SNB; key->snb[2] = 0; if (kr) key->snb[2] |= KEY_REFRESH; if (ivu) key->snb[2] |= IV_INDEX_UPDATE; memcpy(key->snb + 3, key->net_id, 8); l_put_be32(ivi, key->snb + 11); l_put_be64(cmac, key->snb + 15); return true; } static bool match_beacon(const void *a, const void *b) { const struct beacon_rx *cached = a; const uint8_t *incoming = b; if (incoming[0] == BEACON_TYPE_MPB) return !memcmp(cached->data, incoming, BEACON_LEN_MPB - 1); if (incoming[0] == BEACON_TYPE_SNB) return !memcmp(cached->data, incoming, BEACON_LEN_SNB - 1); return false; } uint32_t net_key_beacon(const uint8_t *data, uint16_t len, uint32_t *ivi, bool *ivu, bool *kr) { struct net_key *key; struct beacon_rx *beacon; uint32_t b_id, b_ivi; bool b_ivu, b_kr; if (data[1] == BEACON_TYPE_SNB && len != BEACON_LEN_SNB) return 0; if (data[1] == BEACON_TYPE_MPB && len != BEACON_LEN_MPB) return 0; beacon = l_queue_remove_if(beacons, match_beacon, data + 1); if (beacon) goto accept; /* Validate beacon data */ if (data[1] == BEACON_TYPE_SNB) { key = l_queue_find(keys, match_network, data + 3); if (!key) return 0; b_id = key->id; b_ivu = !!(data[2] & 0x02); b_kr = !!(data[2] & 0x01); b_ivi = l_get_be32(data + 11); if (!net_key_snb_check(b_id, b_ivi, b_kr, b_ivu, l_get_be64(data + 15))) return 0; } else if (data[1] == BEACON_TYPE_MPB) { b_id = private_beacon_check(data + 1, &b_ivi, &b_ivu, &b_kr); if (!b_id) return 0; } else return 0; beacon = l_new(struct beacon_rx, 1); memcpy(beacon->data, data + 1, len - 1); beacon->id = b_id; beacon->ivi = b_ivi; beacon->ivu = b_ivu; beacon->kr = b_kr; accept: *ivi = beacon->ivi; *ivu = beacon->ivu; *kr = beacon->kr; l_queue_push_head(beacons, beacon); return beacon->id; } static void send_network_beacon(struct net_key *key) { struct mesh_io_send_info info = { .type = MESH_IO_TIMING_TYPE_GENERAL, .u.gen.interval = 100, .u.gen.cnt = 1, .u.gen.min_delay = DEFAULT_MIN_DELAY, .u.gen.max_delay = DEFAULT_MAX_DELAY }; if (key->mpb_enables) { /* If Interval steps == 0, refresh key every time */ if (!key->mpb_refresh || !key->mpb || !key->mpb[0]) net_key_beacon_refresh(key->id, key->ivi, key->kr, key->ivu, true); mesh_io_send(NULL, &info, key->mpb, BEACON_LEN_MPB); } if (key->snb_enables) { if (!key->snb || !key->snb[0]) { net_key_beacon_refresh(key->id, key->ivi, key->kr, key->ivu, true); } mesh_io_send(NULL, &info, key->snb, BEACON_LEN_SNB); } } static void beacon_timeout(struct l_timeout *timeout, void *user_data) { struct net_key *key = user_data; uint32_t interval, scale_factor; /* Always send at least one beacon */ send_network_beacon(key); /* Count our own beacons towards the vicinity total */ key->observe.seen++; if (!key->observe.half_period) { l_debug("beacon %d for %d nodes, period %d, obs %d, exp %d", key->id, key->snb_enables + key->mpb_enables, key->observe.period, key->observe.seen, key->observe.expected); interval = (key->observe.period * key->observe.seen) / key->observe.expected; /* Limit Increases and Decreases by 10 seconds Up and * 20 seconds down each step, to avoid going nearly silent * in highly populated environments. */ if (interval - 10 > key->observe.period) interval = key->observe.period + 10; else if (interval + 20 < key->observe.period) interval = key->observe.period - 20; /* Beaconing must be no *slower* than once every 10 minutes, * and no *faster* than once every 10 seconds, per spec. * Observation period is twice beaconing period. */ if (interval < BEACON_INTERVAL_MIN * 2) interval = BEACON_INTERVAL_MIN * 2; else if (interval > BEACON_INTERVAL_MAX * 2) interval = BEACON_INTERVAL_MAX * 2; key->observe.period = interval; key->observe.seen = 0; /* To prevent "over slowing" of the beaconing frequency, * require more significant "over observing" the slower * our own beaconing frequency. */ key->observe.expected = interval / 10; scale_factor = interval / 60; key->observe.expected += scale_factor * 3; } interval = key->observe.period / 2; key->observe.half_period = !key->observe.half_period; if (key->mpb_enables || key->snb_enables) l_timeout_modify(timeout, interval); else { l_timeout_remove(timeout); key->observe.timeout = NULL; } } void net_key_beacon_seen(uint32_t id) { struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); if (key) { key->observe.seen++; key->observe.ts = get_timestamp_secs(); } } uint32_t net_key_beacon_last_seen(uint32_t id) { struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); if (key) return key->observe.ts; return 0; } bool net_key_beacon_refresh(uint32_t id, uint32_t ivi, bool kr, bool ivu, bool force) { struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); bool refresh = force; uint32_t rand_ms; if (!key) return false; if (key->snb_enables && !key->snb) { key->snb = l_new(uint8_t, BEACON_LEN_SNB); refresh = true; } if (key->mpb_enables && !key->mpb) { key->mpb = l_new(uint8_t, BEACON_LEN_MPB); refresh = true; } if (key->ivi != ivi || key->ivu != ivu || key->kr != kr) refresh = true; if (!refresh) return true; if (key->mpb) { if (!mpb_compose(key, ivi, kr, ivu)) return false; print_packet("Set MPB to", key->mpb, BEACON_LEN_MPB); } if (key->snb) { if (!snb_compose(key, ivi, kr, ivu)) return false; print_packet("Set SNB to", key->snb, BEACON_LEN_SNB); } l_debug("Set Beacon: IVI: %8.8x, IVU: %d, KR: %d", ivi, ivu, kr); key->ivi = ivi; key->ivu = ivu; key->kr = kr; /* Propagate changes to all local nodes */ net_local_beacon(id, ivi, ivu, kr); /* Send one new SNB soon, after all nodes have seen it */ l_getrandom(&rand_ms, sizeof(rand_ms)); rand_ms %= 1000; key->observe.expected++; if (key->observe.timeout) l_timeout_modify_ms(key->observe.timeout, 500 + rand_ms); else key->observe.timeout = l_timeout_create_ms(500 + rand_ms, beacon_timeout, key, NULL); return true; } static void mpb_timeout(struct l_timeout *timeout, void *user_data) { struct net_key *key = user_data; if (key->mpb_refresh) { l_debug("Refresh in %d seconds", key->mpb_refresh * 10); l_timeout_modify(timeout, key->mpb_refresh * 10); } net_key_beacon_refresh(key->id, key->ivi, key->kr, key->ivu, true); } void net_key_beacon_enable(uint32_t id, bool mpb, uint8_t refresh_count) { struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); bool enabled; uint32_t rand_ms; if (!key) return; enabled = !!key->snb_enables || !!key->mpb_enables; if (mpb) { key->mpb_enables++; key->mpb_refresh = refresh_count; l_timeout_remove(key->mpb_to); if (refresh_count) key->mpb_to = l_timeout_create(refresh_count * 10, mpb_timeout, key, NULL); else key->mpb_to = NULL; } else key->snb_enables++; /* If already Enabled, do nothing */ if (enabled) return; /* Randomize first timeout to avoid bursts of beacons */ l_getrandom(&rand_ms, sizeof(rand_ms)); rand_ms %= (BEACON_INTERVAL_MIN * 1000); rand_ms++; /* Enable Periodic Beaconing on this key */ key->observe.period = BEACON_INTERVAL_MIN * 2; key->observe.expected = 2; key->observe.seen = 0; key->observe.half_period = true; l_timeout_remove(key->observe.timeout); key->observe.timeout = l_timeout_create_ms(rand_ms, beacon_timeout, key, NULL); } void net_key_beacon_disable(uint32_t id, bool mpb) { struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); if (!key) return; if (mpb) { if (!key->mpb_enables) return; key->mpb_enables--; if (!key->mpb_enables) { l_free(key->mpb); key->mpb = NULL; l_timeout_remove(key->mpb_to); key->mpb_to = NULL; } } else { if (!key->snb_enables) return; key->snb_enables--; if (!key->snb_enables) { l_free(key->snb); key->snb = NULL; } } if (key->snb_enables || key->mpb_enables) return; /* Disable periodic Beaconing on this key */ l_timeout_remove(key->observe.timeout); key->observe.timeout = NULL; } static void free_key(void *data) { struct net_key *key = data; l_timeout_remove(key->mpb_to); l_free(key->snb); l_free(key->mpb); l_free(key); } void net_key_cleanup(void) { l_queue_destroy(keys, free_key); keys = NULL; l_queue_destroy(beacons, l_free); beacons = NULL; } bluez-5.87/mesh/PaxHeaders/keyring.h0000644000000000000000000000005015063045254014434 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/keyring.h0000644000000000000000000000301515063045254014114 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2019 Intel Corporation. All rights reserved. * * */ struct keyring_net_key { uint16_t net_idx; uint8_t phase; uint8_t old_key[16]; uint8_t new_key[16]; }; struct keyring_app_key { uint16_t app_idx; uint16_t net_idx; uint8_t old_key[16]; uint8_t new_key[16]; }; bool keyring_put_net_key(struct mesh_node *node, uint16_t net_idx, const struct keyring_net_key *key); bool keyring_get_net_key(struct mesh_node *node, uint16_t net_idx, struct keyring_net_key *key); bool keyring_del_net_key(struct mesh_node *node, uint16_t net_idx); bool keyring_put_app_key(struct mesh_node *node, uint16_t app_idx, uint16_t net_idx, struct keyring_app_key *key); bool keyring_finalize_app_keys(struct mesh_node *node, uint16_t net_id); bool keyring_get_app_key(struct mesh_node *node, uint16_t app_idx, struct keyring_app_key *key); bool keyring_del_app_key(struct mesh_node *node, uint16_t app_idx); bool keyring_get_remote_dev_key(struct mesh_node *node, uint16_t unicast, uint8_t dev_key[16]); bool keyring_put_remote_dev_key(struct mesh_node *node, uint16_t unicast, uint8_t count, const uint8_t dev_key[16]); bool keyring_del_remote_dev_key(struct mesh_node *node, uint16_t unicast, uint8_t count); bool keyring_del_remote_dev_key_all(struct mesh_node *node, uint16_t unicast); bool keyring_build_export_keys_reply(struct mesh_node *node, struct l_dbus_message_builder *builder); bluez-5.87/mesh/PaxHeaders/model.c0000644000000000000000000000005014772767672014104 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/model.c0000644000000000000000000013450014772767672013570 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2020 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "mesh/mesh-defs.h" #include "mesh/mesh.h" #include "mesh/crypto.h" #include "mesh/node.h" #include "mesh/mesh-config.h" #include "mesh/net.h" #include "mesh/appkey.h" #include "mesh/cfgmod.h" #include "mesh/prov.h" #include "mesh/remprv.h" #include "mesh/prv-beacon.h" #include "mesh/error.h" #include "mesh/dbus.h" #include "mesh/util.h" #include "mesh/model.h" #include "mesh/keyring.h" /* Divide and round to ceiling (up) to calculate segment count */ #define CEILDIV(val, div) (((val) + (div) - 1) / (div)) #define VIRTUAL_BASE 0x10000 struct mesh_model { const struct mesh_model_ops *cbs; void *user_data; struct l_queue *bindings; struct l_queue *subs; struct l_queue *virtuals; struct mesh_model_pub *pub; bool sub_enabled; bool pub_enabled; uint32_t id; }; struct mesh_virtual { uint16_t ref_cnt; uint16_t addr; /* 16-bit virtual address, used in messages */ uint8_t label[16]; /* 128 bit label UUID */ }; /* These struct is used to pass lots of params to l_queue_foreach */ struct mod_forward { struct mesh_virtual *virt; const uint8_t *data; uint16_t src; uint16_t dst; uint16_t unicast; uint16_t app_idx; uint16_t net_idx; uint16_t size; int8_t rssi; bool szmict; bool has_dst; bool done; }; static struct l_queue *mesh_virtuals; static bool is_internal(uint32_t id) { if (id == CONFIG_SRV_MODEL || id == CONFIG_CLI_MODEL) return true; if (id == REM_PROV_SRV_MODEL || id == REM_PROV_CLI_MODEL) return true; if (id == PRV_BEACON_SRV_MODEL || id == PRV_BEACON_CLI_MODEL) return true; return false; } static void unref_virt(void *data) { struct mesh_virtual *virt = data; if (virt->ref_cnt > 0) virt->ref_cnt--; if (virt->ref_cnt) return; l_queue_remove(mesh_virtuals, virt); l_free(virt); } static bool simple_match(const void *a, const void *b) { return a == b; } static bool has_binding(struct l_queue *bindings, uint16_t idx) { const struct l_queue_entry *entry; for (entry = l_queue_get_entries(bindings); entry; entry = entry->next) if (L_PTR_TO_INT(entry->data) == idx) return true; return false; } static bool find_virt_by_label(const void *a, const void *b) { const struct mesh_virtual *virt = a; const uint8_t *label = b; return memcmp(virt->label, label, 16) == 0; } static bool match_model_id(const void *a, const void *b) { const struct mesh_model *mod = a; uint32_t id = L_PTR_TO_UINT(b); return (mod->id == id); } static int compare_model_id(const void *a, const void *b, void *user_data) { const struct mesh_model *mod_a = a; const struct mesh_model *mod_b = b; if (mod_a->id < mod_b->id) return -1; if (mod_a->id > mod_b->id) return 1; return 0; } static struct mesh_model *get_model(struct mesh_node *node, uint8_t ele_idx, uint32_t id) { struct l_queue *mods; struct mesh_model *mod; mods = node_get_element_models(node, ele_idx); if (!mods) return NULL; mod = l_queue_find(mods, match_model_id, L_UINT_TO_PTR(id)); if (!mod) l_debug("Model not found"); return mod; } static uint32_t pub_period_to_ms(uint8_t pub_period) { int step_res, num_steps; step_res = pub_period >> 6; num_steps = pub_period & 0x3f; switch (step_res) { default: return num_steps * 100; case 2: num_steps *= 10; /* Fall Through */ case 1: return num_steps * 1000; case 3: return num_steps * 10 * 60 * 1000; } } static struct l_dbus_message *create_config_update_msg(struct mesh_node *node, uint8_t ele_idx, uint32_t id, struct l_dbus_message_builder **builder) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg; const char *owner; const char *path; uint16_t model_id; owner = node_get_owner(node); path = node_get_element_path(node, ele_idx); if (!path || !owner) return NULL; l_debug("Send \"UpdateModelConfiguration\""); msg = l_dbus_message_new_method_call(dbus, owner, path, MESH_ELEMENT_INTERFACE, "UpdateModelConfiguration"); *builder = l_dbus_message_builder_new(msg); model_id = (uint16_t) MODEL_ID(id); l_dbus_message_builder_append_basic(*builder, 'q', &model_id); l_dbus_message_builder_enter_array(*builder, "{sv}"); if (IS_VENDOR(id)) { uint16_t vendor_id = (uint16_t) VENDOR_ID(id); dbus_append_dict_entry_basic(*builder, "Vendor", "q", &vendor_id); } return msg; } static void config_update_model_pub_period(struct mesh_node *node, uint8_t ele_idx, uint32_t model_id, uint32_t period) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg; struct l_dbus_message_builder *builder; msg = create_config_update_msg(node, ele_idx, model_id, &builder); if (!msg) return; dbus_append_dict_entry_basic(builder, "PublicationPeriod", "u", &period); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); l_dbus_send(dbus, msg); } static void append_dict_uint16_array(struct l_dbus_message_builder *builder, struct l_queue *q, const char *key) { const struct l_queue_entry *entry; l_dbus_message_builder_enter_dict(builder, "sv"); l_dbus_message_builder_append_basic(builder, 's', key); l_dbus_message_builder_enter_variant(builder, "aq"); l_dbus_message_builder_enter_array(builder, "q"); for (entry = l_queue_get_entries(q); entry; entry = entry->next) { uint16_t value = (uint16_t) L_PTR_TO_UINT(entry->data); l_dbus_message_builder_append_basic(builder,'q', &value); } l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_leave_dict(builder); } static void cfg_update_mod_bindings(struct mesh_node *node, uint16_t ele_idx, struct mesh_model *mod) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg; struct l_dbus_message_builder *builder; msg = create_config_update_msg(node, ele_idx, mod->id, &builder); if (!msg) return; append_dict_uint16_array(builder, mod->bindings, "Bindings"); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); l_dbus_send(dbus, msg); } static void append_dict_subs_array(struct l_dbus_message_builder *builder, struct l_queue *subs, struct l_queue *virts, const char *key) { const struct l_queue_entry *entry; l_dbus_message_builder_enter_dict(builder, "sv"); l_dbus_message_builder_append_basic(builder, 's', key); l_dbus_message_builder_enter_variant(builder, "av"); l_dbus_message_builder_enter_array(builder, "v"); if (l_queue_isempty(subs)) goto virts; for (entry = l_queue_get_entries(subs); entry; entry = entry->next) { uint16_t grp = L_PTR_TO_UINT(entry->data); l_dbus_message_builder_enter_variant(builder, "q"); l_dbus_message_builder_append_basic(builder, 'q', &grp); l_dbus_message_builder_leave_variant(builder); } virts: if (l_queue_isempty(virts)) goto done; for (entry = l_queue_get_entries(virts); entry; entry = entry->next) { const struct mesh_virtual *virt = entry->data; l_dbus_message_builder_enter_variant(builder, "ay"); dbus_append_byte_array(builder, virt->label, sizeof(virt->label)); l_dbus_message_builder_leave_variant(builder); } done: l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_leave_dict(builder); } static void cfg_update_model_subs(struct mesh_node *node, uint16_t ele_idx, struct mesh_model *mod) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg; struct l_dbus_message_builder *builder; msg = create_config_update_msg(node, ele_idx, mod->id, &builder); if (!msg) return; append_dict_subs_array(builder, mod->subs, mod->virtuals, "Subscriptions"); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); l_dbus_send(dbus, msg); } static void forward_model(void *a, void *b) { struct mesh_model *mod = a; struct mod_forward *fwd = b; struct mesh_virtual *virt; uint16_t dst; bool result; if (fwd->app_idx != APP_IDX_DEV_LOCAL && fwd->app_idx != APP_IDX_DEV_REMOTE && !has_binding(mod->bindings, fwd->app_idx)) return; dst = fwd->dst; if (dst == fwd->unicast || IS_FIXED_GROUP_ADDRESS(dst)) { fwd->has_dst = true; } else if (fwd->virt) { virt = l_queue_find(mod->virtuals, simple_match, fwd->virt); if (virt) { fwd->has_dst = true; dst = virt->addr; } } else { if (l_queue_find(mod->subs, simple_match, L_UINT_TO_PTR(dst))) fwd->has_dst = true; } if (!fwd->has_dst) return; /* Return, if this is not a internal model */ if (!mod->cbs) return; result = false; if (mod->cbs->recv) result = mod->cbs->recv(fwd->src, dst, fwd->app_idx, fwd->net_idx, fwd->data, fwd->size, mod->user_data); if (dst == fwd->unicast && result) fwd->done = true; } static int app_packet_decrypt(struct mesh_net *net, const uint8_t *data, uint16_t size, bool szmict, uint16_t src, uint16_t dst, uint8_t *virt, uint16_t virt_size, uint8_t key_aid, uint32_t seq, uint32_t iv_idx, uint8_t *out) { struct l_queue *app_keys = mesh_net_get_app_keys(net); const struct l_queue_entry *entry; if (!app_keys) return -1; for (entry = l_queue_get_entries(app_keys); entry; entry = entry->next) { const uint8_t *old_key = NULL, *new_key = NULL; uint8_t old_key_aid, new_key_aid; int app_idx; bool decrypted; app_idx = appkey_get_key_idx(entry->data, &old_key, &old_key_aid, &new_key, &new_key_aid); if (app_idx < 0) continue; if (old_key && old_key_aid == key_aid) { decrypted = mesh_crypto_payload_decrypt(virt, virt_size, data, size, szmict, src, dst, key_aid, seq, iv_idx, out, old_key); if (decrypted) { print_packet("Used App Key", old_key, 16); return app_idx; } print_packet("Failed App Key", old_key, 16); } if (new_key && new_key_aid == key_aid) { decrypted = mesh_crypto_payload_decrypt(virt, virt_size, data, size, szmict, src, dst, key_aid, seq, iv_idx, out, new_key); if (decrypted) { print_packet("Used App Key", new_key, 16); return app_idx; } print_packet("Failed App Key", new_key, 16); } } return -1; } static int dev_packet_decrypt(struct mesh_node *node, const uint8_t *data, uint16_t size, bool szmict, uint16_t src, uint16_t dst, uint8_t key_aid, uint32_t seq, uint32_t iv_idx, uint8_t *out) { uint8_t dev_key[16]; const uint8_t *key; key = node_get_device_key(node); if (!key) return -1; if (mesh_crypto_payload_decrypt(NULL, 0, data, size, szmict, src, dst, key_aid, seq, iv_idx, out, key)) return APP_IDX_DEV_LOCAL; key = dev_key; if (keyring_get_remote_dev_key(node, src, dev_key)) { if (mesh_crypto_payload_decrypt(NULL, 0, data, size, szmict, src, dst, key_aid, seq, iv_idx, out, key)) return APP_IDX_DEV_REMOTE; } /* See if there is a local Device Key Candidate as last resort */ if (!node_get_device_key_candidate(node, dev_key)) return -1; if (mesh_crypto_payload_decrypt(NULL, 0, data, size, szmict, src, dst, key_aid, seq, iv_idx, out, key)) { /* If candidate dev_key worked, it is considered finalized */ node_finalize_candidate(node); return APP_IDX_DEV_LOCAL; } return -1; } static int virt_packet_decrypt(struct mesh_net *net, const uint8_t *data, uint16_t size, bool szmict, uint16_t src, uint16_t dst, uint8_t key_aid, uint32_t seq, uint32_t iv_idx, uint8_t *out, struct mesh_virtual **decrypt_virt) { const struct l_queue_entry *v; for (v = l_queue_get_entries(mesh_virtuals); v; v = v->next) { struct mesh_virtual *virt = v->data; int decrypt_idx; if (virt->addr != dst) continue; decrypt_idx = app_packet_decrypt(net, data, size, szmict, src, dst, virt->label, 16, key_aid, seq, iv_idx, out); if (decrypt_idx >= 0) { *decrypt_virt = virt; return decrypt_idx; } } return -1; } static bool msg_send(struct mesh_node *node, bool cred, uint16_t src, uint16_t dst, uint16_t app_idx, uint16_t net_idx, uint8_t *label, uint8_t ttl, uint8_t cnt, uint16_t interval, bool segmented, const void *msg, uint16_t msg_len) { uint8_t dev_key[16]; uint32_t iv_index, seq_num; const uint8_t *key; uint8_t *out; uint8_t key_aid = APP_AID_DEV; bool szmic = false; bool ret = false; uint16_t out_len = msg_len + sizeof(uint32_t); struct mesh_net *net = node_get_net(node); /* Use large MIC if it doesn't affect segmentation */ if (msg_len > 11 && msg_len <= 376) { if (CEILDIV(out_len, 12) == CEILDIV(out_len + 4, 12)) { szmic = true; out_len = msg_len + sizeof(uint64_t); } } if (app_idx == APP_IDX_DEV_LOCAL) { key = node_get_device_key(node); if (!key) return false; } else if (app_idx == APP_IDX_DEV_REMOTE) { if (!keyring_get_remote_dev_key(node, dst, dev_key)) return false; key = dev_key; } else { key = appkey_get_key(node_get_net(node), app_idx, &key_aid); if (!key) { l_debug("no app key for (%x)", app_idx); return false; } } out = l_malloc(out_len); iv_index = mesh_net_get_iv_index(net); seq_num = mesh_net_next_seq_num(net); if (!mesh_crypto_payload_encrypt(label, msg, out, msg_len, src, dst, key_aid, seq_num, iv_index, szmic, key)) { l_error("Failed to Encrypt Payload"); goto done; } ret = mesh_net_app_send(net, cred, src, dst, key_aid, net_idx, ttl, cnt, interval, seq_num, iv_index, segmented, szmic, out, out_len); done: l_free(out); return ret; } static void remove_pub(struct mesh_node *node, uint16_t ele_idx, struct mesh_model *mod) { if (mod->pub) { if (mod->pub->virt) unref_virt(mod->pub->virt); l_free(mod->pub); mod->pub = NULL; } if (!mod->cbs) /* External models */ config_update_model_pub_period(node, ele_idx, mod->id, 0); else if (mod->cbs && mod->cbs->pub) /* Internal models */ mod->cbs->pub(NULL); } static void model_unbind_idx(struct mesh_node *node, uint16_t ele_idx, struct mesh_model *mod, uint16_t idx) { l_queue_remove(mod->bindings, L_UINT_TO_PTR(idx)); if (!mod->cbs) /* External model */ cfg_update_mod_bindings(node, ele_idx, mod); else if (mod->cbs->bind) /* Internal model */ mod->cbs->bind(idx, ACTION_DELETE); /* Remove model publication if the publication key is unbound */ if (mod->pub && idx == mod->pub->idx) remove_pub(node, ele_idx, mod); } static void model_bind_idx(struct mesh_node *node, uint16_t ele_idx, struct mesh_model *mod, uint16_t idx) { if (!mod->bindings) mod->bindings = l_queue_new(); l_queue_push_tail(mod->bindings, L_UINT_TO_PTR(idx)); l_debug("Bind key %4.4x to model %8.8x", idx, mod->id); if (!mod->cbs) /* External model */ cfg_update_mod_bindings(node, ele_idx, mod); else if (mod->cbs->bind) /* Internal model */ mod->cbs->bind(idx, ACTION_ADD); } static int update_binding(struct mesh_node *node, uint16_t addr, uint32_t id, uint16_t app_idx, bool unbind) { struct mesh_model *mod; bool vendor; int ele_idx = node_get_element_idx(node, addr); if (ele_idx < 0) return MESH_STATUS_INVALID_ADDRESS; mod = get_model(node, (uint8_t) ele_idx, id); if (!mod) { return MESH_STATUS_INVALID_MODEL; } if (id == CONFIG_SRV_MODEL || id == CONFIG_CLI_MODEL) return MESH_STATUS_INVALID_MODEL; if (id == PRV_BEACON_SRV_MODEL || id == PRV_BEACON_CLI_MODEL) return MESH_STATUS_INVALID_MODEL; if (!appkey_have_key(node_get_net(node), app_idx)) return MESH_STATUS_INVALID_APPKEY; /* * If deleting a binding that is not present, return success. * If adding a binding that already exists, return success. */ if (unbind ^ has_binding(mod->bindings, app_idx)) return MESH_STATUS_SUCCESS; vendor = IS_VENDOR(id); id = vendor ? id : MODEL_ID(id); if (unbind) { model_unbind_idx(node, ele_idx, mod, app_idx); if (!mesh_config_model_binding_del(node_config_get(node), addr, id, vendor, app_idx)) return MESH_STATUS_STORAGE_FAIL; l_debug("Unbind key %4.4x to model %8.8x", app_idx, mod->id); return MESH_STATUS_SUCCESS; } if (l_queue_length(mod->bindings) >= MAX_MODEL_BINDINGS) return MESH_STATUS_INSUFF_RESOURCES; if (!mesh_config_model_binding_add(node_config_get(node), addr, id, vendor, app_idx)) return MESH_STATUS_STORAGE_FAIL; model_bind_idx(node, ele_idx, mod, app_idx); return MESH_STATUS_SUCCESS; } static struct mesh_virtual *add_virtual(const uint8_t *v) { struct mesh_virtual *virt = l_queue_find(mesh_virtuals, find_virt_by_label, v); if (virt) { virt->ref_cnt++; return virt; } virt = l_new(struct mesh_virtual, 1); if (!mesh_crypto_virtual_addr(v, &virt->addr)) { l_free(virt); return NULL; } memcpy(virt->label, v, 16); virt->ref_cnt = 1; l_queue_push_head(mesh_virtuals, virt); return virt; } static int set_pub(struct mesh_model *mod, uint16_t pub_addr, uint16_t idx, bool cred_flag, uint8_t ttl, uint8_t period, uint8_t cnt, uint16_t interval) { if (!mod->pub) mod->pub = l_new(struct mesh_model_pub, 1); mod->pub->addr = pub_addr; mod->pub->credential = cred_flag; mod->pub->idx = idx; mod->pub->ttl = ttl; mod->pub->period = period; mod->pub->rtx.cnt = cnt; mod->pub->rtx.interval = interval; return MESH_STATUS_SUCCESS; } static int set_virt_pub(struct mesh_model *mod, const uint8_t *label, uint16_t idx, bool cred_flag, uint8_t ttl, uint8_t period, uint8_t cnt, uint16_t interval) { struct mesh_virtual *virt = NULL; virt = add_virtual(label); if (!virt) return MESH_STATUS_STORAGE_FAIL; if (!mod->pub) mod->pub = l_new(struct mesh_model_pub, 1); mod->pub->virt = virt; return set_pub(mod, virt->addr, idx, cred_flag, ttl, period, cnt, interval); } static int add_virt_sub(struct mesh_net *net, struct mesh_model *mod, const uint8_t *label, uint16_t *addr) { struct mesh_virtual *virt = l_queue_find(mod->virtuals, find_virt_by_label, label); if (!virt) { virt = add_virtual(label); if (!virt) return MESH_STATUS_INSUFF_RESOURCES; l_queue_push_head(mod->virtuals, virt); mesh_net_dst_reg(net, virt->addr); l_debug("Added virtual sub addr %4.4x", virt->addr); } if (addr) *addr = virt->addr; return MESH_STATUS_SUCCESS; } static int add_sub(struct mesh_net *net, struct mesh_model *mod, uint16_t addr) { if (!mod->subs) mod->subs = l_queue_new(); if (l_queue_find(mod->subs, simple_match, L_UINT_TO_PTR(addr))) return MESH_STATUS_SUCCESS; if ((l_queue_length(mod->subs) + l_queue_length(mod->virtuals)) >= MAX_MODEL_SUBS) return MESH_STATUS_INSUFF_RESOURCES; l_queue_push_tail(mod->subs, L_UINT_TO_PTR(addr)); mesh_net_dst_reg(net, addr); l_debug("Added group subscription %4.4x", addr); return MESH_STATUS_SUCCESS; } static void send_dev_key_msg_rcvd(struct mesh_node *node, uint8_t ele_idx, uint16_t src, uint16_t app_idx, uint16_t net_idx, uint16_t size, const uint8_t *data) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg; struct l_dbus_message_builder *builder; const char *owner; const char *path; bool remote = (app_idx != APP_IDX_DEV_LOCAL); owner = node_get_owner(node); path = node_get_element_path(node, ele_idx); if (!path || !owner) return; l_debug("Send \"DevKeyMessageReceived\""); msg = l_dbus_message_new_method_call(dbus, owner, path, MESH_ELEMENT_INTERFACE, "DevKeyMessageReceived"); builder = l_dbus_message_builder_new(msg); l_dbus_message_builder_append_basic(builder, 'q', &src); l_dbus_message_builder_append_basic(builder, 'b', &remote); l_dbus_message_builder_append_basic(builder, 'q', &net_idx); dbus_append_byte_array(builder, data, size); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); l_dbus_send(dbus, msg); } static void send_msg_rcvd(struct mesh_node *node, uint8_t ele_idx, uint16_t src, uint16_t dst, const struct mesh_virtual *virt, uint16_t app_idx, uint16_t size, const uint8_t *data) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg; struct l_dbus_message_builder *builder; const char *owner; const char *path; owner = node_get_owner(node); path = node_get_element_path(node, ele_idx); if (!path || !owner) return; l_debug("Send \"MessageReceived\""); msg = l_dbus_message_new_method_call(dbus, owner, path, MESH_ELEMENT_INTERFACE, "MessageReceived"); builder = l_dbus_message_builder_new(msg); l_dbus_message_builder_append_basic(builder, 'q', &src); l_dbus_message_builder_append_basic(builder, 'q', &app_idx); if (virt) { l_dbus_message_builder_enter_variant(builder, "ay"); dbus_append_byte_array(builder, virt->label, sizeof(virt->label)); l_dbus_message_builder_leave_variant(builder); } else { l_dbus_message_builder_enter_variant(builder, "q"); l_dbus_message_builder_append_basic(builder, 'q', &dst); l_dbus_message_builder_leave_variant(builder); } dbus_append_byte_array(builder, data, size); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); l_dbus_send(dbus, msg); } bool mesh_model_rx(struct mesh_node *node, bool szmict, uint32_t seq0, uint32_t iv_index, uint16_t net_idx, uint16_t src, uint16_t dst, uint8_t key_aid, const uint8_t *data, uint16_t size) { uint8_t *clear_text; struct mod_forward forward = { .src = src, .dst = dst, .data = NULL, .size = size - (szmict ? 8 : 4), .virt = NULL, }; struct mesh_net *net = node_get_net(node); uint8_t num_ele; int decrypt_idx, i, ele_idx; uint16_t addr; struct mesh_virtual *decrypt_virt = NULL; bool result = false; bool is_subscription; l_debug("iv_index %8.8x key_aid = %2.2x", iv_index, key_aid); if (!dst) return false; ele_idx = node_get_element_idx(node, dst); if (dst < 0x8000 && ele_idx < 0) /* Unicast and not addressed to us */ return false; clear_text = l_malloc(size); forward.data = clear_text; /* * The packet needs to be decoded by the correct key which * is hinted by key_aid, but is not necessarily definitive */ if (key_aid == APP_AID_DEV) decrypt_idx = dev_packet_decrypt(node, data, size, szmict, src, dst, key_aid, seq0, iv_index, clear_text); else if ((dst & 0xc000) == 0x8000) decrypt_idx = virt_packet_decrypt(net, data, size, szmict, src, dst, key_aid, seq0, iv_index, clear_text, &decrypt_virt); else decrypt_idx = app_packet_decrypt(net, data, size, szmict, src, dst, NULL, 0, key_aid, seq0, iv_index, clear_text); if (decrypt_idx < 0) { l_error("model.c - Failed to decrypt application payload"); result = false; goto done; } print_packet("Clr Rx", clear_text, size - (szmict ? 8 : 4)); forward.virt = decrypt_virt; forward.app_idx = decrypt_idx; forward.net_idx = net_idx; num_ele = node_get_num_elements(node); addr = node_get_primary(node); if (!num_ele || IS_UNASSIGNED(addr)) goto done; is_subscription = !(IS_UNICAST(dst)); for (i = 0; i < num_ele; i++) { struct l_queue *models; if (!is_subscription && ele_idx != i) continue; forward.unicast = addr + i; forward.has_dst = false; models = node_get_element_models(node, i); /* Internal models */ l_queue_foreach(models, forward_model, &forward); /* * Cycle through external models if the message has not been * handled by internal models */ if (forward.has_dst && !forward.done) { if ((decrypt_idx & APP_IDX_MASK) == decrypt_idx) send_msg_rcvd(node, i, src, dst, decrypt_virt, forward.app_idx, forward.size, forward.data); else if (decrypt_idx == APP_IDX_DEV_REMOTE || decrypt_idx == APP_IDX_DEV_LOCAL) send_dev_key_msg_rcvd(node, i, src, decrypt_idx, net_idx, forward.size, forward.data); } /* * Either the message has been processed internally or * has been passed on to an external model. */ result |= forward.has_dst | forward.done; /* If the message was to unicast address, we are done */ if (!is_subscription && ele_idx == i) break; /* * For the fixed group addresses, i.e., all-proxies, * all-friends, all-relays, all-nodes, the message is delivered * to a primary element only. */ if (IS_FIXED_GROUP_ADDRESS(dst)) break; } done: l_free(clear_text); return result; } int mesh_model_publish(struct mesh_node *node, uint32_t id, uint16_t src, bool segmented, uint16_t msg_len, const void *msg) { struct mesh_net *net = node_get_net(node); struct mesh_model *mod; uint8_t *label = NULL; uint16_t net_idx; bool result; int ele_idx; if (!net || msg_len > 380) return MESH_ERROR_INVALID_ARGS; /* If SRC is 0, use the Primary Element */ if (src == 0) src = mesh_net_get_address(net); ele_idx = node_get_element_idx(node, src); if (ele_idx < 0) return MESH_ERROR_NOT_FOUND; mod = get_model(node, (uint8_t) ele_idx, id); if (!mod) return MESH_ERROR_NOT_FOUND; if (!mod->pub) { l_debug("publication doesn't exist (model %x)", id); return MESH_ERROR_DOES_NOT_EXIST; } if (IS_UNASSIGNED(mod->pub->addr)) return MESH_ERROR_DOES_NOT_EXIST; if (mod->pub->virt) label = mod->pub->virt->label; net_idx = appkey_net_idx(net, mod->pub->idx); result = msg_send(node, mod->pub->credential != 0, src, mod->pub->addr, mod->pub->idx, net_idx, label, mod->pub->ttl, mod->pub->rtx.cnt, mod->pub->rtx.interval, segmented, msg, msg_len); return result ? MESH_ERROR_NONE : MESH_ERROR_FAILED; } bool mesh_model_send(struct mesh_node *node, uint16_t src, uint16_t dst, uint16_t app_idx, uint16_t net_idx, uint8_t ttl, bool segmented, uint16_t msg_len, const void *msg) { struct mesh_net *net = node_get_net(node); uint8_t cnt; uint16_t interval; /* If SRC is 0, use the Primary Element */ if (src == 0) src = node_get_primary(node); if (IS_UNASSIGNED(dst)) return false; mesh_net_transmit_params_get(net, &cnt, &interval); return msg_send(node, false, src, dst, app_idx, net_idx, NULL, ttl, cnt, interval, segmented, msg, msg_len); } int mesh_model_pub_set(struct mesh_node *node, uint16_t addr, uint32_t id, const uint8_t *pub_addr, uint16_t idx, bool cred_flag, uint8_t ttl, uint8_t period, uint8_t cnt, uint16_t interval, bool is_virt, uint16_t *pub_dst) { struct mesh_model *mod; int status, ele_idx = node_get_element_idx(node, addr); if (ele_idx < 0) return MESH_STATUS_INVALID_ADDRESS; mod = get_model(node, (uint8_t) ele_idx, id); if (!mod) return MESH_STATUS_INVALID_MODEL; if (!mod->pub_enabled || (mod->cbs && !(mod->cbs->pub))) return MESH_STATUS_INVALID_PUB_PARAM; if (!appkey_have_key(node_get_net(node), idx) || !has_binding(mod->bindings, idx)) return MESH_STATUS_INVALID_APPKEY; /* * If the publication address is set to unassigned address value, * remove the publication */ if (!is_virt && IS_UNASSIGNED(l_get_le16(pub_addr))) { remove_pub(node, ele_idx, mod); return MESH_STATUS_SUCCESS; } if (cred_flag && node_lpn_mode_get(node) != MESH_MODE_ENABLED) return MESH_STATUS_FEATURE_NO_SUPPORT; /* Check if the old publication destination is a virtual label */ if (mod->pub && mod->pub->virt) { unref_virt(mod->pub->virt); mod->pub->virt = NULL; } if (!is_virt) { status = set_pub(mod, l_get_le16(pub_addr), idx, cred_flag, ttl, period, cnt, interval); } else status = set_virt_pub(mod, pub_addr, idx, cred_flag, ttl, period, cnt, interval); if (status != MESH_STATUS_SUCCESS) return status; *pub_dst = mod->pub->addr; if (!mod->cbs) /* External model */ config_update_model_pub_period(node, ele_idx, id, pub_period_to_ms(period)); else { /* Internal model, call registered callbacks */ if (mod->cbs->pub) mod->cbs->pub(mod->pub); } return MESH_STATUS_SUCCESS; } struct mesh_model_pub *mesh_model_pub_get(struct mesh_node *node, uint16_t addr, uint32_t mod_id, int *status) { struct mesh_model *mod; int ele_idx = node_get_element_idx(node, addr); if (ele_idx < 0) { *status = MESH_STATUS_INVALID_ADDRESS; return NULL; } mod = get_model(node, (uint8_t) ele_idx, mod_id); if (!mod) { *status = MESH_STATUS_INVALID_MODEL; return NULL; } if (!mod->pub_enabled || (mod->cbs && !(mod->cbs->pub))) *status = MESH_STATUS_INVALID_PUB_PARAM; else *status = MESH_STATUS_SUCCESS; return mod->pub; } void mesh_model_free(void *data) { struct mesh_model *mod = data; l_queue_destroy(mod->bindings, NULL); l_queue_destroy(mod->subs, NULL); l_queue_destroy(mod->virtuals, unref_virt); l_free(mod->pub); l_free(mod); } static void remove_subs(struct mesh_node *node, struct mesh_model *mod) { const struct l_queue_entry *entry; struct mesh_net *net = node_get_net(node); entry = l_queue_get_entries(mod->subs); for (; entry; entry = entry->next) mesh_net_dst_unreg(net, (uint16_t) L_PTR_TO_UINT(entry->data)); l_queue_clear(mod->subs, NULL); l_queue_clear(mod->virtuals, unref_virt); } static struct mesh_model *model_new(uint32_t id) { struct mesh_model *mod = l_new(struct mesh_model, 1); mod->id = id; mod->virtuals = l_queue_new(); /* * Unless specifically indicated by an app, subscriptions and * publications are enabled by default */ mod->sub_enabled = true; mod->pub_enabled = true; return mod; } static void model_enable_pub(struct mesh_model *mod, bool enable) { mod->pub_enabled = enable; if (!mod->pub_enabled && mod->pub) { if (mod->pub->virt) unref_virt(mod->pub->virt); l_free(mod->pub); mod->pub = NULL; } } static void model_enable_sub(struct mesh_node *node, struct mesh_model *mod, bool enable) { mod->sub_enabled = enable; if (!mod->sub_enabled) remove_subs(node, mod); } static bool get_model_options(struct mesh_model *mod, struct l_dbus_message_iter *opts) { const char *key; struct l_dbus_message_iter var; bool opt; while (l_dbus_message_iter_next_entry(opts, &key, &var)) { if (!strcmp(key, "Publish")) { if (!l_dbus_message_iter_get_variant(&var, "b", &opt)) return false; mod->pub_enabled = opt; } else if (!strcmp(key, "Subscribe")) { if (!l_dbus_message_iter_get_variant(&var, "b", &opt)) return false; mod->sub_enabled = opt; } else return false; } return true; } bool mesh_model_add(struct mesh_node *node, struct l_queue *mods, uint32_t id, struct l_dbus_message_iter *opts) { struct mesh_model *mod; /* Disallow duplicates */ mod = l_queue_find(mods, match_model_id, L_UINT_TO_PTR(id)); if (mod) return false; mod = model_new(id); if (opts && !get_model_options(mod, opts)) { mesh_model_free(mod); return false; } l_queue_insert(mods, mod, compare_model_id, NULL); return true; } /* Internal models only */ static void restore_model_state(struct mesh_model *mod) { const struct mesh_model_ops *cbs; const struct l_queue_entry *b; cbs = mod->cbs; if (!cbs) return; if (!l_queue_isempty(mod->bindings) && cbs->bind) { for (b = l_queue_get_entries(mod->bindings); b; b = b->next) { if (cbs->bind(L_PTR_TO_UINT(b->data), ACTION_ADD) != MESH_STATUS_SUCCESS) break; } } if (mod->pub && cbs->pub) cbs->pub(mod->pub); } /* This registers an internal model, i.e. implemented within meshd */ bool mesh_model_register(struct mesh_node *node, uint8_t ele_idx, uint32_t id, const struct mesh_model_ops *cbs, void *user_data) { struct mesh_model *mod; mod = get_model(node, ele_idx, id); if (!mod) return MESH_STATUS_INVALID_MODEL; mod->cbs = cbs; mod->user_data = user_data; restore_model_state(mod); return true; } void mesh_model_app_key_delete(struct mesh_node *node, uint16_t ele_idx, struct l_queue *models, uint16_t app_idx) { const struct l_queue_entry *entry = l_queue_get_entries(models); for (; entry; entry = entry->next) { struct mesh_model *mod = entry->data; model_unbind_idx(node, ele_idx, mod, app_idx); } } int mesh_model_binding_del(struct mesh_node *node, uint16_t addr, uint32_t id, uint16_t app_idx) { return update_binding(node, addr, id, app_idx, true); } int mesh_model_binding_add(struct mesh_node *node, uint16_t addr, uint32_t id, uint16_t app_idx) { return update_binding(node, addr, id, app_idx, false); } int mesh_model_get_bindings(struct mesh_node *node, uint16_t addr, uint32_t id, uint8_t *buf, uint16_t buf_size, uint16_t *size) { struct mesh_model *mod; const struct l_queue_entry *entry; uint16_t n; uint32_t idx_pair; int i, ele_idx = node_get_element_idx(node, addr); if (ele_idx < 0) return MESH_STATUS_INVALID_ADDRESS; mod = get_model(node, (uint8_t) ele_idx, id); if (!mod) { *size = 0; return MESH_STATUS_INVALID_MODEL; } entry = l_queue_get_entries(mod->bindings); n = 0; i = 0; idx_pair = 0; for (; entry; entry = entry->next) { uint16_t app_idx = (uint16_t) (L_PTR_TO_UINT(entry->data)); if (!(i & 0x1)) { idx_pair = app_idx; } else { idx_pair <<= 12; idx_pair += app_idx; /* Unlikely, but check for overflow*/ if ((n + 3) > buf_size) { l_warn("Binding list too large"); goto done; } l_put_le32(idx_pair, buf); buf += 3; n += 3; } i++; } /* Process the last app key if present */ if (i & 0x1 && ((n + 2) <= buf_size)) { l_put_le16(idx_pair, buf); n += 2; } done: *size = n; return MESH_STATUS_SUCCESS; } int mesh_model_sub_get(struct mesh_node *node, uint16_t ele_addr, uint32_t id, uint8_t *buf, uint16_t buf_size, uint16_t *size) { int16_t n; struct mesh_model *mod; const struct l_queue_entry *entry; int ele_idx = node_get_element_idx(node, ele_addr); if (ele_idx < 0) return MESH_STATUS_INVALID_ADDRESS; mod = get_model(node, (uint8_t) ele_idx, id); if (!mod) return MESH_STATUS_INVALID_MODEL; if (!mod->sub_enabled || (mod->cbs && !(mod->cbs->sub))) return MESH_STATUS_NOT_SUB_MOD; entry = l_queue_get_entries(mod->subs); *size = 0; n = 0; for (; entry; entry = entry->next) { if ((n + 2) > buf_size) return MESH_STATUS_UNSPECIFIED_ERROR; l_put_le16((uint16_t) L_PTR_TO_UINT(entry->data), buf); buf += 2; n += 2; } entry = l_queue_get_entries(mod->virtuals); for (; entry; entry = entry->next) { struct mesh_virtual *virt = entry->data; if ((n + 2) > buf_size) return MESH_STATUS_UNSPECIFIED_ERROR; l_put_le16((uint16_t) L_PTR_TO_UINT(virt->addr), buf); buf += 2; n += 2; } *size = n; return MESH_STATUS_SUCCESS; } int mesh_model_sub_add(struct mesh_node *node, uint16_t ele_addr, uint32_t id, uint16_t addr) { struct mesh_model *mod; int status, ele_idx = node_get_element_idx(node, ele_addr); if (ele_idx < 0) return MESH_STATUS_INVALID_ADDRESS; mod = get_model(node, (uint8_t) ele_idx, id); if (!mod) return MESH_STATUS_INVALID_MODEL; if (!mod->sub_enabled || (mod->cbs && !(mod->cbs->sub))) return MESH_STATUS_NOT_SUB_MOD; status = add_sub(node_get_net(node), mod, addr); if (status != MESH_STATUS_SUCCESS) return status; if (!mod->cbs) /* External models */ cfg_update_model_subs(node, ele_idx, mod); return MESH_STATUS_SUCCESS; } int mesh_model_virt_sub_add(struct mesh_node *node, uint16_t ele_addr, uint32_t id, const uint8_t *label, uint16_t *pub_addr) { struct mesh_model *mod; int status, ele_idx = node_get_element_idx(node, ele_addr); if (ele_idx < 0) return MESH_STATUS_INVALID_ADDRESS; mod = get_model(node, (uint8_t) ele_idx, id); if (!mod) return MESH_STATUS_INVALID_MODEL; if (!mod->sub_enabled || (mod->cbs && !(mod->cbs->sub))) return MESH_STATUS_NOT_SUB_MOD; status = add_virt_sub(node_get_net(node), mod, label, pub_addr); if (status != MESH_STATUS_SUCCESS) return status; if (!mod->cbs) /* External models */ cfg_update_model_subs(node, ele_idx, mod); return MESH_STATUS_SUCCESS; } int mesh_model_sub_ovrt(struct mesh_node *node, uint16_t ele_addr, uint32_t id, uint16_t addr) { struct mesh_model *mod; int ele_idx = node_get_element_idx(node, ele_addr); if (ele_idx < 0) return MESH_STATUS_INVALID_ADDRESS; mod = get_model(node, (uint8_t) ele_idx, id); if (!mod) return MESH_STATUS_INVALID_MODEL; if (!mod->sub_enabled || (mod->cbs && !(mod->cbs->sub))) return MESH_STATUS_NOT_SUB_MOD; l_queue_clear(mod->subs, NULL); l_queue_clear(mod->virtuals, unref_virt); add_sub(node_get_net(node), mod, addr); if (!mod->cbs) /* External models */ cfg_update_model_subs(node, ele_idx, mod); return MESH_STATUS_SUCCESS; } int mesh_model_virt_sub_ovrt(struct mesh_node *node, uint16_t ele_addr, uint32_t id, const uint8_t *label, uint16_t *addr) { struct mesh_model *mod; int status, ele_idx = node_get_element_idx(node, ele_addr); if (ele_idx < 0) return MESH_STATUS_INVALID_ADDRESS; mod = get_model(node, (uint8_t) ele_idx, id); if (!mod) return MESH_STATUS_INVALID_MODEL; if (!mod->sub_enabled || (mod->cbs && !(mod->cbs->sub))) return MESH_STATUS_NOT_SUB_MOD; l_queue_clear(mod->subs, NULL); l_queue_clear(mod->virtuals, unref_virt); status = add_virt_sub(node_get_net(node), mod, label, addr); if (!mod->cbs) /* External models */ cfg_update_model_subs(node, ele_idx, mod); return status; } int mesh_model_sub_del(struct mesh_node *node, uint16_t ele_addr, uint32_t id, uint16_t addr) { struct mesh_model *mod; int ele_idx = node_get_element_idx(node, ele_addr); if (ele_idx < 0) return MESH_STATUS_INVALID_ADDRESS; mod = get_model(node, (uint8_t) ele_idx, id); if (!mod) return MESH_STATUS_INVALID_MODEL; if (!mod->sub_enabled || (mod->cbs && !(mod->cbs->sub))) return MESH_STATUS_NOT_SUB_MOD; if (l_queue_remove(mod->subs, L_UINT_TO_PTR(addr))) { mesh_net_dst_unreg(node_get_net(node), addr); if (!mod->cbs) /* External models */ cfg_update_model_subs(node, ele_idx, mod); } return MESH_STATUS_SUCCESS; } int mesh_model_virt_sub_del(struct mesh_node *node, uint16_t ele_addr, uint32_t id, const uint8_t *label, uint16_t *addr) { struct mesh_model *mod; struct mesh_virtual *virt; int ele_idx = node_get_element_idx(node, ele_addr); if (ele_idx < 0) return MESH_STATUS_INVALID_ADDRESS; mod = get_model(node, (uint8_t) ele_idx, id); if (!mod) return MESH_STATUS_INVALID_MODEL; if (!mod->sub_enabled || (mod->cbs && !(mod->cbs->sub))) return MESH_STATUS_NOT_SUB_MOD; virt = l_queue_remove_if(mod->virtuals, find_virt_by_label, label); if (virt) { *addr = virt->addr; unref_virt(virt); } else { *addr = UNASSIGNED_ADDRESS; return MESH_STATUS_SUCCESS; } if (l_queue_remove(mod->subs, L_UINT_TO_PTR(*addr))) { mesh_net_dst_unreg(node_get_net(node), *addr); if (!mod->cbs) /* External models */ cfg_update_model_subs(node, ele_idx, mod); } return MESH_STATUS_SUCCESS; } int mesh_model_sub_del_all(struct mesh_node *node, uint16_t ele_addr, uint32_t id) { struct mesh_model *mod; int ele_idx = node_get_element_idx(node, ele_addr); if (ele_idx < 0) return MESH_STATUS_INVALID_ADDRESS; mod = get_model(node, (uint8_t) ele_idx, id); if (!mod) return MESH_STATUS_INVALID_MODEL; if (!mod->sub_enabled || (mod->cbs && !(mod->cbs->sub))) return MESH_STATUS_NOT_SUB_MOD; remove_subs(node, mod); if (!mod->cbs) /* External models */ cfg_update_model_subs(node, ele_idx, mod); return MESH_STATUS_SUCCESS; } static struct mesh_model *model_setup(struct mesh_net *net, uint8_t ele_idx, struct mesh_config_model *db_mod) { struct mesh_model *mod; struct mesh_config_pub *pub = db_mod->pub; uint32_t i; if (db_mod->num_bindings > MAX_MODEL_BINDINGS) { l_warn("Binding list too long %u (max %u)", db_mod->num_bindings, MAX_MODEL_BINDINGS); return NULL; } mod = model_new(db_mod->vendor ? db_mod->id : SET_ID(SIG_VENDOR, db_mod->id)); /* Implicitly bind config server model to device key */ if (db_mod->id == CONFIG_SRV_MODEL || db_mod->id == PRV_BEACON_SRV_MODEL) { if (ele_idx != PRIMARY_ELE_IDX) { l_free(mod); return NULL; } l_queue_push_head(mod->bindings, L_UINT_TO_PTR(APP_IDX_DEV_LOCAL)); return mod; } if (db_mod->id == CONFIG_CLI_MODEL) { l_queue_push_head(mod->bindings, L_UINT_TO_PTR(APP_IDX_DEV_LOCAL)); return mod; } /* Add application key bindings if present */ if (db_mod->bindings) { mod->bindings = l_queue_new(); for (i = 0; i < db_mod->num_bindings; i++) l_queue_push_tail(mod->bindings, L_UINT_TO_PTR(db_mod->bindings[i])); } mod->pub_enabled = db_mod->pub_enabled; /* Add publication if enabled and present */ if (mod->pub_enabled && pub) { if (pub->virt) set_virt_pub(mod, pub->virt_addr, pub->idx, pub->credential, pub->ttl, pub->period, pub->cnt, pub->interval); else if (!IS_UNASSIGNED(pub->addr)) set_pub(mod, pub->addr, pub->idx, pub->credential, pub->ttl, pub->period, pub->cnt, pub->interval); } mod->sub_enabled = db_mod->sub_enabled; /* Add subscriptions if enabled and present */ if (!db_mod->subs || !mod->sub_enabled) return mod; for (i = 0; i < db_mod->num_subs; i++) { struct mesh_config_sub *sub = &db_mod->subs[i]; if (!sub->virt) add_sub(net, mod, sub->addr.grp); else add_virt_sub(net, mod, sub->addr.label, NULL); } return mod; } bool mesh_model_add_from_storage(struct mesh_node *node, uint8_t ele_idx, struct l_queue *mods, struct l_queue *db_mods) { struct mesh_net *net = node_get_net(node); const struct l_queue_entry *entry; /* Allow empty elements */ if (!db_mods) return true; entry = l_queue_get_entries(db_mods); for (; entry; entry = entry->next) { struct mesh_model *mod; struct mesh_config_model *db_mod; uint32_t id; db_mod = entry->data; id = db_mod->vendor ? db_mod->id : SET_ID(SIG_VENDOR, db_mod->id); if (l_queue_find(mods, match_model_id, L_UINT_TO_PTR(id))) return false; mod = model_setup(net, ele_idx, db_mod); if (!mod) return false; l_queue_insert(mods, mod, compare_model_id, NULL); } return true; } void mesh_model_convert_to_storage(struct l_queue *db_mods, struct l_queue *mods) { const struct l_queue_entry *entry = l_queue_get_entries(mods); for (; entry; entry = entry->next) { struct mesh_model *mod = entry->data; struct mesh_config_model *db_mod; db_mod = l_new(struct mesh_config_model, 1); db_mod->id = mod->id; db_mod->vendor = IS_VENDOR(mod->id); db_mod->pub_enabled = mod->pub_enabled; db_mod->sub_enabled = mod->sub_enabled; l_queue_push_tail(db_mods, db_mod); } } uint16_t mesh_model_opcode_set(uint32_t opcode, uint8_t *buf) { if (opcode <= 0x7e) { buf[0] = opcode; return 1; } if (opcode >= 0x8000 && opcode <= 0xbfff) { l_put_be16(opcode, buf); return 2; } if (opcode >= 0xc00000 && opcode <= 0xffffff) { buf[0] = (opcode >> 16) & 0xff; l_put_be16(opcode, buf + 1); return 3; } l_debug("Illegal Opcode %x", opcode); return 0; } bool mesh_model_opcode_get(const uint8_t *buf, uint16_t size, uint32_t *opcode, uint16_t *n) { if (!n || !opcode || size < 1) return false; switch (buf[0] & 0xc0) { case 0x00: case 0x40: /* RFU */ if (buf[0] == 0x7f) return false; *n = 1; *opcode = buf[0]; break; case 0x80: if (size < 2) return false; *n = 2; *opcode = l_get_be16(buf); break; case 0xc0: if (size < 3) return false; *n = 3; *opcode = l_get_be16(buf + 1); *opcode |= buf[0] << 16; break; default: print_packet("Bad", buf, size); return false; } return true; } void mesh_model_build_config(void *model, void *msg_builder) { struct l_dbus_message_builder *builder = msg_builder; struct mesh_model *mod = model; uint16_t id; if (is_internal(mod->id)) return; if (!l_queue_length(mod->subs) && !l_queue_length(mod->virtuals) && !mod->pub && !l_queue_length(mod->bindings)) return; l_dbus_message_builder_enter_struct(builder, "qa{sv}"); /* Model id */ id = MODEL_ID(mod->id); l_dbus_message_builder_append_basic(builder, 'q', &id); l_dbus_message_builder_enter_array(builder, "{sv}"); /* For vendor models, add vendor id */ if (IS_VENDOR(mod->id)) { uint16_t vendor = VENDOR_ID(mod->id); dbus_append_dict_entry_basic(builder, "Vendor", "q", &vendor); } /* Model bindings, if present */ if (l_queue_length(mod->bindings)) append_dict_uint16_array(builder, mod->bindings, "Bindings"); /* Model periodic publication interval, if present */ if (mod->pub) { uint32_t period = pub_period_to_ms(mod->pub->period); dbus_append_dict_entry_basic(builder, "PublicationPeriod", "u", &period); } if (l_queue_length(mod->subs) || l_queue_length(mod->virtuals)) append_dict_subs_array(builder, mod->subs, mod->virtuals, "Subscriptions"); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_leave_struct(builder); } void mesh_model_update_opts(struct mesh_node *node, uint8_t ele_idx, struct l_queue *curr, struct l_queue *updated) { uint16_t primary; const struct l_queue_entry *entry; primary = node_get_primary(node); entry = l_queue_get_entries(curr); for (; entry; entry = entry->next) { struct mesh_model *mod, *updated_mod = entry->data; uint32_t id = updated_mod->id; bool updated_opt, vendor = IS_VENDOR(id); mod = l_queue_find(curr, match_model_id, L_UINT_TO_PTR(id)); if (!mod) continue; if (!vendor) id = MODEL_ID(id); updated_opt = updated_mod->pub_enabled; if (mod->pub_enabled != updated_opt) { model_enable_pub(mod, updated_opt); mesh_config_model_pub_enable(node_config_get(node), primary + ele_idx, id, vendor, updated_opt); } updated_opt = updated_mod->sub_enabled; if (mod->pub_enabled != updated_opt) { model_enable_sub(node, mod, updated_opt); mesh_config_model_sub_enable(node_config_get(node), primary + ele_idx, id, vendor, updated_opt); } } } /* Populate composition buffer with model IDs */ uint16_t mesh_model_generate_composition(struct l_queue *mods, uint16_t buf_sz, uint8_t *buf) { const struct l_queue_entry *entry; uint8_t num_s = 0, num_v = 0; uint8_t *mod_buf; uint16_t n; /* Store models IDs, store num_s and num_v later */ mod_buf = buf; n = 2; entry = l_queue_get_entries(mods); /* Get SIG models */ for (; entry; entry = entry->next) { struct mesh_model *mod = entry->data; if (n + 2 > buf_sz) goto done; if (IS_VENDOR(mod->id)) continue; l_put_le16((uint16_t) (MODEL_ID(mod->id)), buf + n); n += 2; num_s++; } /* Get vendor models */ entry = l_queue_get_entries(mods); for (; entry; entry = entry->next) { struct mesh_model *mod = entry->data; uint16_t vendor_id; if (n + 4 > buf_sz) goto done; if (!IS_VENDOR(mod->id)) continue; vendor_id = (uint16_t) (VENDOR_ID(mod->id)); l_put_le16(vendor_id, buf + n); n += 2; l_put_le16((uint16_t) (MODEL_ID(mod->id)), buf + n); n += 2; num_v++; } done: mod_buf[0] = num_s; mod_buf[1] = num_v; return n; } void mesh_model_init(void) { mesh_virtuals = l_queue_new(); } void mesh_model_cleanup(void) { l_queue_destroy(mesh_virtuals, l_free); mesh_virtuals = NULL; } bluez-5.87/mesh/PaxHeaders/mesh-config-json.c0000644000000000000000000000005015063045254016125 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/mesh-config-json.c0000644000000000000000000016030115063045254015607 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mesh/mesh-defs.h" #include "mesh/util.h" #include "mesh/mesh-config.h" /* To prevent local node JSON cache thrashing, minimum update times */ #define MIN_SEQ_CACHE_TRIGGER 32 #define MIN_SEQ_CACHE_VALUE (2 * 32) #define MIN_SEQ_CACHE_TIME (5 * 60) #define CHECK_KEY_IDX_RANGE(x) ((x) <= 4095) struct mesh_config { json_object *jnode; char *node_dir_path; uint8_t uuid[16]; uint32_t write_seq; struct timeval write_time; struct l_queue *idles; }; struct write_info { struct mesh_config *cfg; void *user_data; mesh_config_status_func_t cb; }; static const char *cfgnode_name = "/node.json"; static const char *bak_ext = ".bak"; static const char *tmp_ext = ".tmp"; /* JSON key words */ static const char *unicastAddress = "unicastAddress"; static const char *deviceCan = "deviceCan"; static const char *deviceKey = "deviceKey"; static const char *defaultTTL = "defaultTTL"; static const char *sequenceNumber = "sequenceNumber"; static const char *netKeys = "netKeys"; static const char *appKeys = "appKeys"; static const char *elements = "elements"; static const char *models = "models"; static const char *modelId = "modelId"; static const char *address = "address"; static const char *bind = "bind"; static const char *publish = "publish"; static const char *subscribe = "subscribe"; static const char *boundNetKey = "boundNetKey"; static const char *keyRefresh = "keyRefresh"; static const char *subEnabled = "subEnabled"; static const char *pubEnabled = "pubEnabled"; static const char *retransmit = "retransmit"; /* Common JSON values */ static const char *enabled = "enabled"; static const char *disabled = "disabled"; static const char *unsupported = "unsupported"; static bool save_config(json_object *jnode, const char *fname) { FILE *outfile; const char *str; bool result = false; outfile = fopen(fname, "w"); if (!outfile) { l_error("Failed to save configuration to %s", fname); return false; } str = json_object_to_json_string_ext(jnode, JSON_C_TO_STRING_PRETTY); if (fwrite(str, sizeof(char), strlen(str), outfile) < strlen(str)) l_warn("Incomplete write of mesh configuration"); else result = true; fclose(outfile); return result; } static bool get_int(json_object *jobj, const char *keyword, int *value) { json_object *jvalue; if (!json_object_object_get_ex(jobj, keyword, &jvalue)) return false; *value = json_object_get_int(jvalue); if (errno == EINVAL) return false; return true; } static bool add_u64_value(json_object *jobj, const char *desc, const uint8_t u64[8]) { json_object *jstring; char hexstr[17]; hex2str((uint8_t *) u64, 8, hexstr, 17); jstring = json_object_new_string(hexstr); if (!jstring) return false; json_object_object_del(jobj, desc); json_object_object_add(jobj, desc, jstring); return true; } static bool add_key_value(json_object *jobj, const char *desc, const uint8_t key[16]) { json_object *jstring; char hexstr[33]; hex2str((uint8_t *) key, 16, hexstr, 33); jstring = json_object_new_string(hexstr); if (!jstring) return false; json_object_object_del(jobj, desc); json_object_object_add(jobj, desc, jstring); return true; } static int get_element_index(json_object *jnode, uint16_t ele_addr) { json_object *jvalue, *jelements; uint16_t addr, num_ele; char *str; if (!json_object_object_get_ex(jnode, unicastAddress, &jvalue)) return -1; str = (char *)json_object_get_string(jvalue); if (sscanf(str, "%04hx", &addr) != 1) return -1; if (!json_object_object_get_ex(jnode, elements, &jelements)) return -1; num_ele = json_object_array_length(jelements); if (ele_addr >= addr + num_ele || ele_addr < addr) return -1; return ele_addr - addr; } static json_object *get_element_model(json_object *jnode, int ele_idx, uint32_t mod_id, bool vendor) { json_object *jelements, *jelement, *jmodels; int i, num_mods, ret; size_t len; char buf[9]; if (!json_object_object_get_ex(jnode, elements, &jelements)) return NULL; jelement = json_object_array_get_idx(jelements, ele_idx); if (!jelement) return NULL; if (!json_object_object_get_ex(jelement, models, &jmodels)) return NULL; num_mods = json_object_array_length(jmodels); if (!num_mods) return NULL; if (!vendor) ret = snprintf(buf, 5, "%4.4x", (uint16_t)mod_id); else ret = snprintf(buf, 9, "%8.8x", mod_id); if (ret < 0) return NULL; len = ret; for (i = 0; i < num_mods; ++i) { json_object *jmodel, *jvalue; char *str; jmodel = json_object_array_get_idx(jmodels, i); if (!json_object_object_get_ex(jmodel, modelId, &jvalue)) return NULL; str = (char *)json_object_get_string(jvalue); if (!str) return NULL; if (!strncmp(str, buf, len)) return jmodel; } return NULL; } static bool jarray_has_string(json_object *jarray, char *str, size_t len) { int i, sz = json_object_array_length(jarray); for (i = 0; i < sz; ++i) { json_object *jentry; char *str_entry; jentry = json_object_array_get_idx(jarray, i); str_entry = (char *)json_object_get_string(jentry); if (!str_entry) continue; if (!strncmp(str, str_entry, len)) return true; } return false; } static void jarray_string_del(json_object *jarray, char *str, size_t len) { int i, sz = json_object_array_length(jarray); for (i = 0; i < sz; ++i) { json_object *jentry; char *str_entry; jentry = json_object_array_get_idx(jarray, i); str_entry = (char *)json_object_get_string(jentry); if (str_entry && !strncmp(str, str_entry, len)) { json_object_array_del_idx(jarray, i, 1); return; } } } static bool get_key_index(json_object *jobj, const char *keyword, uint16_t *index) { int idx; if (!get_int(jobj, keyword, &idx)) return false; if (!CHECK_KEY_IDX_RANGE(idx)) return false; *index = (uint16_t) idx; return true; } static json_object *get_key_object(json_object *jarray, uint16_t idx) { int i, sz = json_object_array_length(jarray); for (i = 0; i < sz; ++i) { json_object *jentry; uint16_t jidx; jentry = json_object_array_get_idx(jarray, i); if (!get_key_index(jentry, "index", &jidx)) return NULL; if (jidx == idx) return jentry; } return NULL; } static void jarray_key_del(json_object *jarray, int16_t idx) { int i, sz = json_object_array_length(jarray); for (i = 0; i < sz; ++i) { json_object *jentry; uint16_t nidx; jentry = json_object_array_get_idx(jarray, i); if (get_key_index(jentry, "index", &nidx) && nidx == idx) { json_object_array_del_idx(jarray, i, 1); return; } } } static bool read_unicast_address(json_object *jobj, uint16_t *unicast) { json_object *jvalue; char *str; if (!json_object_object_get_ex(jobj, unicastAddress, &jvalue)) return false; str = (char *)json_object_get_string(jvalue); if (sscanf(str, "%04hx", unicast) != 1) return false; return true; } static bool read_default_ttl(json_object *jobj, uint8_t *ttl) { json_object *jvalue; int val; /* defaultTTL is optional */ if (!json_object_object_get_ex(jobj, defaultTTL, &jvalue)) return true; val = json_object_get_int(jvalue); if (!val && errno == EINVAL) return false; if (val < 0 || val == 1 || val > TTL_MASK) return false; *ttl = (uint8_t) val; return true; } static bool read_seq_number(json_object *jobj, uint32_t *seq_number) { json_object *jvalue; int val; /* sequenceNumber is optional */ if (!json_object_object_get_ex(jobj, sequenceNumber, &jvalue)) return true; val = json_object_get_int(jvalue); if (!val && errno == EINVAL) return false; if (val < 0 || val > SEQ_MASK + 1) return false; *seq_number = (uint32_t) val; return true; } static bool read_iv_index(json_object *jobj, uint32_t *idx, bool *update) { int tmp; /* IV index */ if (!get_int(jobj, "IVindex", &tmp)) return false; *idx = (uint32_t) tmp; if (!get_int(jobj, "IVupdate", &tmp)) return false; *update = tmp ? true : false; return true; } static bool read_token(json_object *jobj, uint8_t token[8]) { json_object *jvalue; char *str; if (!token) return false; if (!json_object_object_get_ex(jobj, "token", &jvalue)) return false; str = (char *)json_object_get_string(jvalue); if (!str2hex(str, strlen(str), token, 8)) return false; return true; } static bool read_device_key(json_object *jobj, uint8_t key_buf[16]) { json_object *jvalue; char *str; if (!key_buf) return false; if (!json_object_object_get_ex(jobj, deviceKey, &jvalue)) return false; str = (char *)json_object_get_string(jvalue); if (!str2hex(str, strlen(str), key_buf, 16)) return false; return true; } static bool read_candidate(json_object *jobj, uint8_t key_buf[16]) { json_object *jvalue; char *str; if (!key_buf) return false; if (!json_object_object_get_ex(jobj, deviceCan, &jvalue)) return false; str = (char *)json_object_get_string(jvalue); if (!str2hex(str, strlen(str), key_buf, 16)) return false; return true; } static bool read_comp_pages(json_object *jobj, struct mesh_config_node *node) { json_object *jarray, *jentry; struct mesh_config_comp_page *page; int len; int i; if (!json_object_object_get_ex(jobj, "pages", &jarray)) return true; if (json_object_get_type(jarray) != json_type_array) return false; len = json_object_array_length(jarray); for (i = 0; i < len; i++) { size_t clen; char *str; jentry = json_object_array_get_idx(jarray, i); str = (char *)json_object_get_string(jentry); clen = strlen(str); if (clen < ((MIN_COMP_SIZE * 2) + 1)) continue; clen = (clen / 2) - 1; page = l_malloc(sizeof(struct mesh_config_comp_page) + clen); if (!str2hex(str + 2, clen * 2, page->data, clen)) goto parse_fail; if (sscanf(str, "%02hhx", &page->page_num) != 1) goto parse_fail; page->len = clen; l_queue_push_tail(node->pages, page); } return true; parse_fail: l_free(page); return false; } static bool read_app_keys(json_object *jobj, struct mesh_config_node *node) { json_object *jarray; int len; int i; if (!json_object_object_get_ex(jobj, appKeys, &jarray)) return true; if (json_object_get_type(jarray) != json_type_array) return false; /* Allow empty AppKey array */ len = json_object_array_length(jarray); if (!len) return true; for (i = 0; i < len; ++i) { json_object *jtemp, *jvalue; char *str; struct mesh_config_appkey *appkey; appkey = l_new(struct mesh_config_appkey, 1); l_queue_push_tail(node->appkeys, appkey); jtemp = json_object_array_get_idx(jarray, i); if (!get_key_index(jtemp, "index", &appkey->app_idx)) goto fail; if (!get_key_index(jtemp, boundNetKey, &appkey->net_idx)) goto fail; if (!json_object_object_get_ex(jtemp, "key", &jvalue)) goto fail; str = (char *)json_object_get_string(jvalue); if (!str2hex(str, strlen(str), appkey->new_key, 16)) goto fail; if (json_object_object_get_ex(jtemp, "oldKey", &jvalue)) str = (char *)json_object_get_string(jvalue); if (!str2hex(str, strlen(str), appkey->key, 16)) goto fail; } return true; fail: l_queue_destroy(node->appkeys, l_free); node->appkeys = NULL; return false; } static bool read_net_keys(json_object *jobj, struct mesh_config_node *node) { json_object *jarray; int len; int i; /* At least one NetKey must be present for a provisioned node */ if (!json_object_object_get_ex(jobj, netKeys, &jarray)) return false; if (json_object_get_type(jarray) != json_type_array) return false; len = json_object_array_length(jarray); if (!len) return false; for (i = 0; i < len; ++i) { json_object *jtemp, *jvalue; char *str; struct mesh_config_netkey *netkey; netkey = l_new(struct mesh_config_netkey, 1); l_queue_push_tail(node->netkeys, netkey); jtemp = json_object_array_get_idx(jarray, i); if (!get_key_index(jtemp, "index", &netkey->idx)) goto fail; if (!json_object_object_get_ex(jtemp, "key", &jvalue)) goto fail; str = (char *)json_object_get_string(jvalue); if (!str2hex(str, strlen(str), netkey->new_key, 16)) goto fail; if (!json_object_object_get_ex(jtemp, keyRefresh, &jvalue)) netkey->phase = KEY_REFRESH_PHASE_NONE; else netkey->phase = (uint8_t) json_object_get_int(jvalue); if (netkey->phase > KEY_REFRESH_PHASE_TWO) goto fail; if (json_object_object_get_ex(jtemp, "oldKey", &jvalue)) { if (netkey->phase == KEY_REFRESH_PHASE_NONE) goto fail; str = (char *)json_object_get_string(jvalue); } if (!str2hex(str, strlen(str), netkey->key, 16)) goto fail; } return true; fail: l_queue_destroy(node->netkeys, l_free); node->netkeys = NULL; return false; } static bool write_int(json_object *jobj, const char *desc, int val) { json_object *jvalue; jvalue = json_object_new_int(val); if (!jvalue) return false; json_object_object_del(jobj, desc); json_object_object_add(jobj, desc, jvalue); return true; } bool mesh_config_net_key_add(struct mesh_config *cfg, uint16_t idx, const uint8_t key[16]) { json_object *jnode, *jarray = NULL, *jentry = NULL; if (!cfg) return false; jnode = cfg->jnode; l_debug("netKey %4.4x", idx); json_object_object_get_ex(jnode, netKeys, &jarray); if (jarray) jentry = get_key_object(jarray, idx); /* Do not allow direct overwrite */ if (jentry) return false; jentry = json_object_new_object(); if (!jentry) return false; if (!write_int(jentry, "index", idx)) goto fail; if (!add_key_value(jentry, "key", key)) goto fail; json_object_object_add(jentry, keyRefresh, json_object_new_int(KEY_REFRESH_PHASE_NONE)); if (!jarray) { jarray = json_object_new_array(); if (!jarray) goto fail; json_object_object_add(jnode, netKeys, jarray); } json_object_array_add(jarray, jentry); return save_config(jnode, cfg->node_dir_path); fail: if (jentry) json_object_put(jentry); return false; } bool mesh_config_net_key_update(struct mesh_config *cfg, uint16_t idx, const uint8_t key[16]) { json_object *jnode, *jarray, *jentry, *jstring; const char *str; if (!cfg) return false; jnode = cfg->jnode; if (!json_object_object_get_ex(jnode, netKeys, &jarray)) return false; jentry = get_key_object(jarray, idx); /* Net key must be already recorded */ if (!jentry) return false; if (!json_object_object_get_ex(jentry, "key", &jstring)) return false; str = json_object_get_string(jstring); jstring = json_object_new_string(str); json_object_object_add(jentry, "oldKey", jstring); json_object_object_del(jentry, "key"); if (!add_key_value(jentry, "key", key)) return false; json_object_object_add(jentry, keyRefresh, json_object_new_int(KEY_REFRESH_PHASE_ONE)); return save_config(jnode, cfg->node_dir_path); } bool mesh_config_net_key_del(struct mesh_config *cfg, uint16_t idx) { json_object *jnode, *jarray; if (!cfg) return false; jnode = cfg->jnode; if (!json_object_object_get_ex(jnode, netKeys, &jarray)) return true; jarray_key_del(jarray, idx); if (!json_object_array_length(jarray)) json_object_object_del(jnode, netKeys); return save_config(jnode, cfg->node_dir_path); } bool mesh_config_write_device_key(struct mesh_config *cfg, const uint8_t *key) { if (!cfg || !add_key_value(cfg->jnode, deviceKey, key)) return false; return save_config(cfg->jnode, cfg->node_dir_path); } bool mesh_config_write_candidate(struct mesh_config *cfg, const uint8_t *key) { if (!cfg || !add_key_value(cfg->jnode, deviceCan, key)) return false; return save_config(cfg->jnode, cfg->node_dir_path); } bool mesh_config_read_candidate(struct mesh_config *cfg, uint8_t *key) { if (!cfg) return false; return read_candidate(cfg->jnode, key); } bool mesh_config_finalize_candidate(struct mesh_config *cfg) { uint8_t key[16]; if (!cfg) return false; if (!read_candidate(cfg->jnode, key)) return false; json_object_object_del(cfg->jnode, deviceCan); json_object_object_del(cfg->jnode, deviceKey); if (!add_key_value(cfg->jnode, deviceKey, key)) return false; return save_config(cfg->jnode, cfg->node_dir_path); } bool mesh_config_write_token(struct mesh_config *cfg, const uint8_t *token) { if (!cfg || !add_u64_value(cfg->jnode, "token", token)) return false; return save_config(cfg->jnode, cfg->node_dir_path); } bool mesh_config_app_key_add(struct mesh_config *cfg, uint16_t net_idx, uint16_t app_idx, const uint8_t key[16]) { json_object *jnode, *jarray = NULL, *jentry = NULL; if (!cfg) return false; jnode = cfg->jnode; json_object_object_get_ex(jnode, appKeys, &jarray); if (jarray) jentry = get_key_object(jarray, app_idx); /* Do not allow direct overrwrite */ if (jentry) return false; jentry = json_object_new_object(); if (!jentry) return false; if (!write_int(jentry, "index", app_idx)) goto fail; if (!write_int(jentry, boundNetKey, net_idx)) goto fail; if (!add_key_value(jentry, "key", key)) goto fail; if (!jarray) { jarray = json_object_new_array(); if (!jarray) goto fail; json_object_object_add(jnode, appKeys, jarray); } json_object_array_add(jarray, jentry); return save_config(jnode, cfg->node_dir_path); fail: if (jentry) json_object_put(jentry); return false; } bool mesh_config_app_key_update(struct mesh_config *cfg, uint16_t app_idx, const uint8_t key[16]) { json_object *jnode, *jarray, *jentry = NULL, *jstring = NULL; const char *str; if (!cfg) return false; jnode = cfg->jnode; if (!json_object_object_get_ex(jnode, appKeys, &jarray)) return false; /* The key entry should exist if the key is updated */ jentry = get_key_object(jarray, app_idx); if (!jentry) return false; if (!json_object_object_get_ex(jentry, "key", &jstring)) return false; str = json_object_get_string(jstring); jstring = json_object_new_string(str); json_object_object_add(jentry, "oldKey", jstring); json_object_object_del(jentry, "key"); /* TODO: "Rewind" if add_key_value fails */ if (!add_key_value(jentry, "key", key)) return false; return save_config(jnode, cfg->node_dir_path); } bool mesh_config_app_key_del(struct mesh_config *cfg, uint16_t net_idx, uint16_t idx) { json_object *jnode, *jarray; if (!cfg) return false; jnode = cfg->jnode; if (!json_object_object_get_ex(jnode, appKeys, &jarray)) return true; jarray_key_del(jarray, idx); if (!json_object_array_length(jarray)) json_object_object_del(jnode, appKeys); return save_config(jnode, cfg->node_dir_path); } bool mesh_config_model_binding_add(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, uint16_t app_idx) { json_object *jnode, *jmodel, *jstring, *jarray = NULL; int ele_idx, ret; char buf[5]; if (!cfg) return false; jnode = cfg->jnode; ret = snprintf(buf, 5, "%4.4x", app_idx); if (ret < 0) return false; ele_idx = get_element_index(jnode, ele_addr); if (ele_idx < 0) return false; jmodel = get_element_model(jnode, ele_idx, mod_id, vendor); if (!jmodel) return false; json_object_object_get_ex(jmodel, bind, &jarray); if (jarray && jarray_has_string(jarray, buf, 4)) return true; jstring = json_object_new_string(buf); if (!jstring) return false; if (!jarray) { jarray = json_object_new_array(); if (!jarray) { json_object_put(jstring); return false; } json_object_object_add(jmodel, bind, jarray); } json_object_array_add(jarray, jstring); return save_config(jnode, cfg->node_dir_path); } bool mesh_config_model_binding_del(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, uint16_t app_idx) { json_object *jnode, *jmodel, *jarray; int ele_idx, ret; char buf[5]; if (!cfg) return false; jnode = cfg->jnode; ret = snprintf(buf, 5, "%4.4x", app_idx); if (ret < 0) return false; ele_idx = get_element_index(jnode, ele_addr); if (ele_idx < 0) return false; jmodel = get_element_model(jnode, ele_idx, mod_id, vendor); if (!jmodel) return false; if (!json_object_object_get_ex(jmodel, bind, &jarray)) return true; jarray_string_del(jarray, buf, 4); if (!json_object_array_length(jarray)) json_object_object_del(jmodel, bind); return save_config(jnode, cfg->node_dir_path); } static void free_model(void *data) { struct mesh_config_model *mod = data; l_free(mod->bindings); l_free(mod->subs); l_free(mod->pub); l_free(mod); } static void free_element(void *data) { struct mesh_config_element *ele = data; l_queue_destroy(ele->models, free_model); l_free(ele); } static bool parse_bindings(json_object *jarray, struct mesh_config_model *mod) { int cnt; int i; cnt = json_object_array_length(jarray); if (cnt > 0xffff) return false; mod->num_bindings = cnt; /* Allow empty bindings list */ if (!cnt) return true; mod->bindings = l_new(uint16_t, cnt); for (i = 0; i < cnt; ++i) { uint16_t idx; const char *str; json_object *jvalue; jvalue = json_object_array_get_idx(jarray, i); if (!jvalue) return false; str = json_object_get_string(jvalue); if (sscanf(str, "%04hx", &idx) != 1) return false; if (!CHECK_KEY_IDX_RANGE(idx)) return false; mod->bindings[i] = (uint16_t) idx; } return true; } static struct mesh_config_pub *parse_model_publication(json_object *jpub) { json_object *jvalue; struct mesh_config_pub *pub; int len, value; char *str; if (!json_object_object_get_ex(jpub, address, &jvalue)) return NULL; str = (char *)json_object_get_string(jvalue); len = strlen(str); pub = l_new(struct mesh_config_pub, 1); switch (len) { case 4: if (sscanf(str, "%04hx", &pub->addr) != 1) goto fail; break; case 32: if (!str2hex(str, len, pub->virt_addr, 16)) goto fail; pub->virt = true; break; default: goto fail; } if (!get_key_index(jpub, "index", &pub->idx)) goto fail; if (!get_int(jpub, "ttl", &value)) goto fail; pub->ttl = (uint8_t) value; if (!get_int(jpub, "period", &value)) goto fail; pub->period = value; if (!get_int(jpub, "credentials", &value)) goto fail; pub->credential = (uint8_t) value; if (!json_object_object_get_ex(jpub, retransmit, &jvalue)) goto fail; if (!get_int(jvalue, "count", &value)) goto fail; pub->cnt = (uint8_t) value; if (!get_int(jvalue, "interval", &value)) goto fail; pub->interval = (uint8_t) value; return pub; fail: l_free(pub); return NULL; } static bool parse_model_subscriptions(json_object *jsubs, struct mesh_config_model *mod) { struct mesh_config_sub *subs; int i, cnt; if (json_object_get_type(jsubs) != json_type_array) return NULL; cnt = json_object_array_length(jsubs); /* Allow empty array */ if (!cnt) return true; subs = l_new(struct mesh_config_sub, cnt); for (i = 0; i < cnt; ++i) { char *str; int len; json_object *jvalue; jvalue = json_object_array_get_idx(jsubs, i); if (!jvalue) goto fail; str = (char *)json_object_get_string(jvalue); len = strlen(str); switch (len) { case 4: if (sscanf(str, "%04hx", &subs[i].addr.grp) != 1) goto fail; break; case 32: if (!str2hex(str, len, subs[i].addr.label, 16)) goto fail; subs[i].virt = true; break; default: goto fail; } } mod->num_subs = cnt; mod->subs = subs; return true; fail: l_free(subs); return false; } static bool parse_models(json_object *jmodels, struct mesh_config_element *ele) { int i, num_models; num_models = json_object_array_length(jmodels); if (!num_models) return true; for (i = 0; i < num_models; ++i) { json_object *jmodel, *jarray, *jvalue; struct mesh_config_model *mod; uint32_t id; int len; char *str; jmodel = json_object_array_get_idx(jmodels, i); if (!jmodel) goto fail; mod = l_new(struct mesh_config_model, 1); l_queue_push_tail(ele->models, mod); if (!json_object_object_get_ex(jmodel, modelId, &jvalue)) goto fail; str = (char *)json_object_get_string(jvalue); len = strlen(str); if (len == 4) { if (sscanf(str, "%04x", &id) != 1) goto fail; } else if (len == 8) { if (sscanf(str, "%08x", &id) != 1) goto fail; mod->vendor = true; } else goto fail; mod->id = id; if (len == 8) mod->vendor = true; if (json_object_object_get_ex(jmodel, bind, &jarray)) { if (json_object_get_type(jarray) != json_type_array || !parse_bindings(jarray, mod)) goto fail; } if (json_object_object_get_ex(jmodel, pubEnabled, &jvalue)) mod->pub_enabled = json_object_get_boolean(jvalue); else mod->pub_enabled = true; if (json_object_object_get_ex(jmodel, subEnabled, &jvalue)) mod->sub_enabled = json_object_get_boolean(jvalue); else mod->sub_enabled = true; if (json_object_object_get_ex(jmodel, publish, &jvalue)) { mod->pub = parse_model_publication(jvalue); if (!mod->pub) goto fail; } if (json_object_object_get_ex(jmodel, subscribe, &jarray)) { if (!parse_model_subscriptions(jarray, mod)) goto fail; } } return true; fail: l_queue_destroy(ele->models, free_model); return false; } static bool parse_elements(json_object *jelems, struct mesh_config_node *node) { int i, num_ele; if (json_object_get_type(jelems) != json_type_array) return false; num_ele = json_object_array_length(jelems); if (!num_ele) /* Allow "empty" nodes */ return true; for (i = 0; i < num_ele; ++i) { json_object *jelement; json_object *jmodels; json_object *jvalue; struct mesh_config_element *ele; int index; char *str; jelement = json_object_array_get_idx(jelems, i); if (!jelement) goto fail; if (!get_int(jelement, "elementIndex", &index) || index > num_ele) goto fail; ele = l_new(struct mesh_config_element, 1); ele->index = index; ele->models = l_queue_new(); l_queue_push_tail(node->elements, ele); if (!json_object_object_get_ex(jelement, "location", &jvalue)) goto fail; str = (char *)json_object_get_string(jvalue); if (sscanf(str, "%04hx", &(ele->location)) != 1) goto fail; if (json_object_object_get_ex(jelement, models, &jmodels)) { if (json_object_get_type(jmodels) != json_type_array || !parse_models(jmodels, ele)) goto fail; } } return true; fail: l_queue_destroy(node->elements, free_element); node->elements = NULL; return false; } static int get_mode(json_object *jvalue) { const char *str; str = json_object_get_string(jvalue); if (!str) return 0xffffffff; if (!strncasecmp(str, disabled, strlen(disabled))) return MESH_MODE_DISABLED; if (!strncasecmp(str, enabled, strlen(enabled))) return MESH_MODE_ENABLED; if (!strncasecmp(str, unsupported, strlen(unsupported))) return MESH_MODE_UNSUPPORTED; return 0xffffffff; } static void parse_features(json_object *jconfig, struct mesh_config_node *node) { json_object *jvalue, *jrelay; int mode, count; uint16_t interval; if (json_object_object_get_ex(jconfig, "proxy", &jvalue)) { mode = get_mode(jvalue); if (mode <= MESH_MODE_UNSUPPORTED) node->modes.proxy = mode; } if (json_object_object_get_ex(jconfig, "friend", &jvalue)) { mode = get_mode(jvalue); if (mode <= MESH_MODE_UNSUPPORTED) node->modes.friend = mode; } if (json_object_object_get_ex(jconfig, "lowPower", &jvalue)) { mode = get_mode(jvalue); if (mode <= MESH_MODE_UNSUPPORTED) node->modes.lpn = mode; } if (json_object_object_get_ex(jconfig, "beacon", &jvalue)) { mode = get_mode(jvalue); if (mode <= MESH_MODE_UNSUPPORTED) node->modes.beacon = mode; } if (json_object_object_get_ex(jconfig, "mpb", &jvalue)) { mode = get_mode(jvalue); if (mode <= MESH_MODE_UNSUPPORTED) node->modes.mpb = mode; if (node->modes.mpb == MESH_MODE_ENABLED) { if (json_object_object_get_ex(jconfig, "mpbPeriod", &jvalue)) node->modes.mpb_period = json_object_get_int(jvalue); } } if (!json_object_object_get_ex(jconfig, "relay", &jrelay)) return; if (json_object_object_get_ex(jrelay, "mode", &jvalue)) { mode = get_mode(jvalue); if (mode <= MESH_MODE_UNSUPPORTED) node->modes.relay.state = mode; else return; } else return; if (!json_object_object_get_ex(jrelay, "count", &jvalue)) return; /* TODO: check range */ count = json_object_get_int(jvalue); node->modes.relay.cnt = count; if (!json_object_object_get_ex(jrelay, "interval", &jvalue)) return; /* TODO: check range */ interval = json_object_get_int(jvalue); node->modes.relay.interval = interval; } static bool parse_composition(json_object *jcomp, struct mesh_config_node *node) { json_object *jvalue; char *str; /* All the fields in node composition are mandatory */ if (!json_object_object_get_ex(jcomp, "cid", &jvalue)) return false; str = (char *)json_object_get_string(jvalue); if (sscanf(str, "%04hx", &node->cid) != 1) return false; if (!json_object_object_get_ex(jcomp, "pid", &jvalue)) return false; str = (char *)json_object_get_string(jvalue); if (sscanf(str, "%04hx", &node->pid) != 1) return false; if (!json_object_object_get_ex(jcomp, "vid", &jvalue)) return false; str = (char *)json_object_get_string(jvalue); if (sscanf(str, "%04hx", &node->vid) != 1) return false; if (!json_object_object_get_ex(jcomp, "crpl", &jvalue)) return false; str = (char *)json_object_get_string(jvalue); if (sscanf(str, "%04hx", &node->crpl) != 1) return false; return true; } static bool read_net_transmit(json_object *jobj, struct mesh_config_node *node) { json_object *jrtx, *jvalue; uint16_t interval; uint8_t cnt; if (!json_object_object_get_ex(jobj, retransmit, &jrtx)) return true; if (!json_object_object_get_ex(jrtx, "count", &jvalue)) return false; /* TODO: add range checking */ cnt = (uint8_t) json_object_get_int(jvalue); if (!json_object_object_get_ex(jrtx, "interval", &jvalue)) return false; interval = (uint16_t) json_object_get_int(jvalue); node->net_transmit = l_new(struct mesh_config_transmit, 1); node->net_transmit->count = cnt; node->net_transmit->interval = interval; return true; } static bool read_node(json_object *jnode, struct mesh_config_node *node) { json_object *jvalue; if (!read_iv_index(jnode, &node->iv_index, &node->iv_update)) { l_info("Failed to read IV index"); return false; } if (!read_token(jnode, node->token)) { l_info("Failed to read node token"); return false; } if (!read_device_key(jnode, node->dev_key)) { l_info("Failed to read node device key"); return false; } if (!parse_composition(jnode, node)) { l_info("Failed to parse local node composition"); return false; } parse_features(jnode, node); if (!read_unicast_address(jnode, &node->unicast)) { l_info("Failed to parse unicast address"); return false; } if (!read_default_ttl(jnode, &node->ttl)) { l_info("Failed to parse default ttl"); return false; } if (!read_seq_number(jnode, &node->seq_number)) { l_info("Failed to parse sequence number"); return false; } /* Check for required "elements" property */ if (!json_object_object_get_ex(jnode, elements, &jvalue)) return false; if (!read_net_transmit(jnode, node)) { l_info("Failed to read node net transmit parameters"); return false; } if (!read_net_keys(jnode, node)) { l_info("Failed to read net keys"); return false; } if (!read_app_keys(jnode, node)) { l_info("Failed to read app keys"); return false; } if (!read_comp_pages(jnode, node)) { l_info("Failed to read Composition Pages"); return false; } if (!parse_elements(jvalue, node)) { l_info("Failed to parse elements"); return false; } return true; } static bool write_uint16_hex(json_object *jobj, const char *desc, uint16_t value) { json_object *jstring; int ret; char buf[5]; ret = snprintf(buf, 5, "%4.4x", value); if (ret < 0) return false; jstring = json_object_new_string(buf); if (!jstring) return false; json_object_object_del(jobj, desc); json_object_object_add(jobj, desc, jstring); return true; } static bool write_uint32_hex(json_object *jobj, const char *desc, uint32_t val) { json_object *jstring; int ret; char buf[9]; ret = snprintf(buf, 9, "%8.8x", val); if (ret < 0) return false; jstring = json_object_new_string(buf); if (!jstring) return false; json_object_object_del(jobj, desc); json_object_object_add(jobj, desc, jstring); return true; } static const char *mode_to_string(int mode) { switch (mode) { case MESH_MODE_DISABLED: return disabled; case MESH_MODE_ENABLED: return enabled; default: return unsupported; } } static bool write_mode(json_object *jobj, const char *desc, int value) { json_object *jstring; jstring = json_object_new_string(mode_to_string(value)); if (!jstring) return false; json_object_object_del(jobj, desc); json_object_object_add(jobj, desc, jstring); return true; } bool mesh_config_write_mode(struct mesh_config *cfg, const char *keyword, int value) { if (!cfg || !write_mode(cfg->jnode, keyword, value)) return false; return save_config(cfg->jnode, cfg->node_dir_path); } bool mesh_config_write_mode_ex(struct mesh_config *cfg, const char *keyword, int value, bool save) { if (!cfg) return false; if (save) return mesh_config_write_mode(cfg, keyword, value); else return write_mode(cfg->jnode, keyword, value); } static bool write_relay_mode(json_object *jobj, uint8_t mode, uint8_t count, uint16_t interval) { json_object *jrelay; json_object_object_del(jobj, "relay"); jrelay = json_object_new_object(); if (!jrelay) return false; if (!write_mode(jrelay, "mode", mode)) goto fail; if (!write_int(jrelay, "count", count)) goto fail; if (!write_int(jrelay, "interval", interval)) goto fail; json_object_object_add(jobj, "relay", jrelay); return true; fail: json_object_put(jrelay); return false; } bool mesh_config_write_unicast(struct mesh_config *cfg, uint16_t unicast) { if (!cfg || !write_uint16_hex(cfg->jnode, unicastAddress, unicast)) return false; return save_config(cfg->jnode, cfg->node_dir_path); } bool mesh_config_write_relay_mode(struct mesh_config *cfg, uint8_t mode, uint8_t count, uint16_t interval) { if (!cfg || !write_relay_mode(cfg->jnode, mode, count, interval)) return false; return save_config(cfg->jnode, cfg->node_dir_path); } bool mesh_config_write_mpb(struct mesh_config *cfg, uint8_t mode, uint8_t period) { if (!cfg || !write_mode(cfg->jnode, "mpb", mode)) return false; if (mode) { if (!write_int(cfg->jnode, "mpbPeriod", period)) return false; } return save_config(cfg->jnode, cfg->node_dir_path); } bool mesh_config_write_net_transmit(struct mesh_config *cfg, uint8_t cnt, uint16_t interval) { json_object *jnode, *jrtx; if (!cfg) return false; jnode = cfg->jnode; jrtx = json_object_new_object(); if (!jrtx) return false; if (!write_int(jrtx, "count", cnt)) goto fail; if (!write_int(jrtx, "interval", interval)) goto fail; json_object_object_del(jnode, retransmit); json_object_object_add(jnode, retransmit, jrtx); return save_config(cfg->jnode, cfg->node_dir_path); fail: json_object_put(jrtx); return false; } bool mesh_config_write_iv_index(struct mesh_config *cfg, uint32_t idx, bool update) { json_object *jnode; int tmp = update ? 1 : 0; if (!cfg) return false; jnode = cfg->jnode; if (!write_int(jnode, "IVindex", idx)) return false; if (!write_int(jnode, "IVupdate", tmp)) return false; return save_config(jnode, cfg->node_dir_path); } static void add_model(void *a, void *b) { struct mesh_config_model *mod = a; json_object *jmodels = b, *jmodel, *jval; bool result; jmodel = json_object_new_object(); if (!jmodel) return; result = (mod->vendor) ? write_uint32_hex(jmodel, modelId, mod->id) : write_uint16_hex(jmodel, modelId, (uint16_t) mod->id); if (!result) { json_object_put(jmodel); return; } jval = json_object_new_boolean(mod->sub_enabled); json_object_object_add(jmodel, subEnabled, jval); jval = json_object_new_boolean(mod->pub_enabled); json_object_object_add(jmodel, pubEnabled, jval); json_object_array_add(jmodels, jmodel); } /* Add unprovisioned node (local) */ static struct mesh_config *create_config(const char *cfg_path, const uint8_t uuid[16], struct mesh_config_node *node) { struct mesh_config_modes *modes = &node->modes; const struct l_queue_entry *entry; json_object *jnode, *jelems; struct mesh_config *cfg; if (!cfg_path || !node) return NULL; jnode = json_object_new_object(); /* CID, PID, VID, crpl */ if (!write_uint16_hex(jnode, "cid", node->cid)) return NULL; if (!write_uint16_hex(jnode, "pid", node->pid)) return NULL; if (!write_uint16_hex(jnode, "vid", node->vid)) return NULL; if (!write_uint16_hex(jnode, "crpl", node->crpl)) return NULL; /* Features: relay, LPN, friend, proxy*/ if (!write_relay_mode(jnode, modes->relay.state, modes->relay.cnt, modes->relay.interval)) return NULL; if (!write_mode(jnode, "lowPower", modes->lpn)) return NULL; if (!write_mode(jnode, "friend", modes->friend)) return NULL; if (!write_mode(jnode, "proxy", modes->proxy)) return NULL; /* Beaconing state */ if (!write_mode(jnode, "beacon", modes->beacon)) return NULL; if (!write_mode(jnode, "mpb", modes->mpb)) return NULL; if (modes->mpb) { if (!write_int(jnode, "mpbPeriod", modes->mpb_period)) return NULL; } /* Sequence number */ json_object_object_add(jnode, sequenceNumber, json_object_new_int(node->seq_number)); /* Default TTL */ json_object_object_add(jnode, defaultTTL, json_object_new_int(node->ttl)); /* Elements */ jelems = json_object_new_array(); if (!jelems) return NULL; entry = l_queue_get_entries(node->elements); for (; entry; entry = entry->next) { struct mesh_config_element *ele = entry->data; json_object *jelement, *jmodels; jelement = json_object_new_object(); if (!jelement) goto fail; json_object_array_add(jelems, jelement); if (!write_int(jelement, "elementIndex", ele->index)) goto fail; if (!write_uint16_hex(jelement, "location", ele->location)) goto fail; /* Models */ if (l_queue_isempty(ele->models)) continue; jmodels = json_object_new_array(); if (!jmodels) goto fail; json_object_object_add(jelement, models, jmodels); l_queue_foreach(ele->models, add_model, jmodels); } json_object_object_add(jnode, elements, jelems); cfg = l_new(struct mesh_config, 1); cfg->jnode = jnode; memcpy(cfg->uuid, uuid, 16); cfg->node_dir_path = l_strdup(cfg_path); cfg->write_seq = node->seq_number; cfg->idles = l_queue_new(); gettimeofday(&cfg->write_time, NULL); return cfg; fail: json_object_put(jelems); return NULL; } void mesh_config_reset(struct mesh_config *cfg, struct mesh_config_node *node) { json_object *jelems; const struct l_queue_entry *entry; if (!cfg || !cfg->jnode) return; /* TODO: Recreate Element Array */ jelems = json_object_new_array(); if (!jelems) return; entry = l_queue_get_entries(node->elements); for (; entry; entry = entry->next) { struct mesh_config_element *ele = entry->data; json_object *jelement, *jmodels; jelement = json_object_new_object(); if (!jelement) { json_object_put(jelems); return; } write_int(jelement, "elementIndex", ele->index); write_uint16_hex(jelement, "location", ele->location); json_object_array_add(jelems, jelement); /* Models */ if (l_queue_isempty(ele->models)) continue; jmodels = json_object_new_array(); if (!jmodels) { json_object_put(jelems); return; } json_object_object_add(jelement, models, jmodels); l_queue_foreach(ele->models, add_model, jmodels); } /* Replace element array */ json_object_object_del(cfg->jnode, elements); json_object_object_add(cfg->jnode, elements, jelems); } struct mesh_config *mesh_config_create(const char *cfgdir_name, const uint8_t uuid[16], struct mesh_config_node *db_node) { char uuid_buf[33]; char name_buf[PATH_MAX]; struct mesh_config *cfg; int ret; if (!hex2str((uint8_t *) uuid, 16, uuid_buf, sizeof(uuid_buf))) return NULL; ret = snprintf(name_buf, PATH_MAX, "%s/%s", cfgdir_name, uuid_buf); if (ret < 0) return NULL; /* Create a new directory and node.json file */ if (mkdir(name_buf, 0755) != 0) return NULL; ret = snprintf(name_buf, PATH_MAX, "%s/%s%s", cfgdir_name, uuid_buf, cfgnode_name); if (ret < 0) return NULL; l_debug("New node config %s", name_buf); cfg = create_config(name_buf, uuid, db_node); if (!cfg) return NULL; if (!mesh_config_save(cfg, true, NULL, NULL)) { mesh_config_release(cfg); return NULL; } return cfg; } static void finish_key_refresh(json_object *jobj, uint16_t net_idx) { json_object *jarray; int i, len; /* Clean up all the bound appkeys */ if (!json_object_object_get_ex(jobj, appKeys, &jarray)) return; len = json_object_array_length(jarray); for (i = 0; i < len; ++i) { json_object *jentry; uint16_t idx; jentry = json_object_array_get_idx(jarray, i); if (!get_key_index(jentry, boundNetKey, &idx)) continue; if (idx != net_idx) continue; json_object_object_del(jentry, "oldKey"); if (!get_key_index(jentry, "index", &idx)) continue; } } bool mesh_config_net_key_set_phase(struct mesh_config *cfg, uint16_t idx, uint8_t phase) { json_object *jnode, *jarray, *jentry = NULL; if (!cfg) return false; jnode = cfg->jnode; if (json_object_object_get_ex(jnode, netKeys, &jarray)) jentry = get_key_object(jarray, idx); if (!jentry) return false; json_object_object_del(jentry, keyRefresh); json_object_object_add(jentry, keyRefresh, json_object_new_int(phase)); if (phase == KEY_REFRESH_PHASE_NONE) { json_object_object_del(jentry, "oldKey"); finish_key_refresh(jnode, idx); } return save_config(jnode, cfg->node_dir_path); } bool mesh_config_model_pub_add(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, const struct mesh_config_pub *pub) { json_object *jnode, *jmodel, *jpub, *jrtx; bool res; int ele_idx; if (!cfg) return false; jnode = cfg->jnode; ele_idx = get_element_index(jnode, ele_addr); if (ele_idx < 0) return false; jmodel = get_element_model(jnode, ele_idx, mod_id, vendor); if (!jmodel) return false; json_object_object_del(jmodel, publish); jpub = json_object_new_object(); if (!jpub) return false; if (pub->virt) res = add_key_value(jpub, address, pub->virt_addr); else res = write_uint16_hex(jpub, address, pub->addr); if (!res) goto fail; if (!write_int(jpub, "index", pub->idx)) goto fail; if (!write_int(jpub, "ttl", pub->ttl)) goto fail; if (!write_int(jpub, "period", pub->period)) goto fail; if (!write_int(jpub, "credentials", pub->credential ? 1 : 0)) goto fail; jrtx = json_object_new_object(); if (!jrtx) goto fail; if (!write_int(jrtx, "count", pub->cnt)) goto fail; if (!write_int(jrtx, "interval", pub->interval)) goto fail; json_object_object_add(jpub, retransmit, jrtx); json_object_object_add(jmodel, publish, jpub); return save_config(jnode, cfg->node_dir_path); fail: json_object_put(jpub); return false; } static bool delete_model_property(json_object *jnode, uint16_t ele_addr, uint32_t mod_id, bool vendor, const char *keyword) { json_object *jmodel; int ele_idx; ele_idx = get_element_index(jnode, ele_addr); if (ele_idx < 0) return false; jmodel = get_element_model(jnode, ele_idx, mod_id, vendor); if (!jmodel) return false; json_object_object_del(jmodel, keyword); return true; } bool mesh_config_model_pub_del(struct mesh_config *cfg, uint16_t addr, uint32_t mod_id, bool vendor) { if (!cfg || !delete_model_property(cfg->jnode, addr, mod_id, vendor, publish)) return false; return save_config(cfg->jnode, cfg->node_dir_path); } static bool del_page(json_object *jarray, uint8_t page) { char buf[3]; int i, len, ret; if (!jarray) return false; ret = snprintf(buf, 3, "%2.2x", page); if (ret < 0) return false; len = json_object_array_length(jarray); for (i = 0; i < len; i++) { json_object *jentry; char *str; jentry = json_object_array_get_idx(jarray, i); str = (char *)json_object_get_string(jentry); /* Delete matching page */ if (!memcmp(str, buf, 2)) { json_object_array_del_idx(jarray, i, 1); break; } } return true; } void mesh_config_comp_page_del(struct mesh_config *cfg, uint8_t page) { json_object *jnode, *jarray = NULL; if (!cfg) return; jnode = cfg->jnode; json_object_object_get_ex(jnode, "pages", &jarray); if (del_page(jarray, page)) save_config(jnode, cfg->node_dir_path); } bool mesh_config_comp_page_add(struct mesh_config *cfg, uint8_t page, const uint8_t *data, uint16_t size) { json_object *jnode, *jstring, *jarray = NULL; char *buf; int len, ret; if (!cfg) return false; jnode = cfg->jnode; json_object_object_get_ex(jnode, "pages", &jarray); len = (size * 2) + 3; buf = l_malloc(len); ret = snprintf(buf, len, "%2.2x", page); if (ret < 0) { l_free(buf); return false; } hex2str((uint8_t *)data, size, buf + 2, len - 2); if (jarray && jarray_has_string(jarray, buf, len)) { l_free(buf); return true; } else if (!jarray) { jarray = json_object_new_array(); json_object_object_add(jnode, "pages", jarray); } else del_page(jarray, page); jstring = json_object_new_string(buf); json_object_array_add(jarray, jstring); l_free(buf); return save_config(jnode, cfg->node_dir_path); } bool mesh_config_model_sub_add(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, const struct mesh_config_sub *sub) { json_object *jnode, *jmodel, *jstring, *jarray = NULL; int ele_idx, len; char buf[33]; if (!cfg) return false; jnode = cfg->jnode; ele_idx = get_element_index(jnode, ele_addr); if (ele_idx < 0) return false; jmodel = get_element_model(jnode, ele_idx, mod_id, vendor); if (!jmodel) return false; if (!sub->virt) { len = snprintf(buf, 5, "%4.4x", sub->addr.grp); if (len < 0) return false; } else { hex2str((uint8_t *)sub->addr.label, 16, buf, 33); len = 32; } json_object_object_get_ex(jmodel, subscribe, &jarray); if (jarray && jarray_has_string(jarray, buf, len)) return true; jstring = json_object_new_string(buf); if (!jstring) return false; if (!jarray) { jarray = json_object_new_array(); if (!jarray) { json_object_put(jstring); return false; } json_object_object_add(jmodel, subscribe, jarray); } json_object_array_add(jarray, jstring); return save_config(jnode, cfg->node_dir_path); } bool mesh_config_model_sub_del(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, const struct mesh_config_sub *sub) { json_object *jnode, *jmodel, *jarray; char buf[33]; int len, ele_idx; if (!cfg) return false; jnode = cfg->jnode; ele_idx = get_element_index(jnode, ele_addr); if (ele_idx < 0) return false; jmodel = get_element_model(jnode, ele_idx, mod_id, vendor); if (!jmodel) return false; if (!json_object_object_get_ex(jmodel, subscribe, &jarray)) return true; if (!sub->virt) { len = snprintf(buf, 5, "%4.4x", sub->addr.grp); if (len < 0) return false; } else { hex2str((uint8_t *)sub->addr.label, 16, buf, 33); len = 32; } jarray_string_del(jarray, buf, len); if (!json_object_array_length(jarray)) json_object_object_del(jmodel, subscribe); return save_config(jnode, cfg->node_dir_path); } bool mesh_config_model_sub_del_all(struct mesh_config *cfg, uint16_t addr, uint32_t mod_id, bool vendor) { if (!cfg || !delete_model_property(cfg->jnode, addr, mod_id, vendor, subscribe)) return false; return save_config(cfg->jnode, cfg->node_dir_path); } bool mesh_config_model_pub_enable(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, bool enable) { json_object *jmodel, *jval; int ele_idx; if (!cfg) return false; ele_idx = get_element_index(cfg->jnode, ele_addr); if (ele_idx < 0) return false; jmodel = get_element_model(cfg->jnode, ele_idx, mod_id, vendor); if (!jmodel) return false; json_object_object_del(jmodel, "pubDisabled"); jval = json_object_new_boolean(!enable); json_object_object_add(jmodel, "pubDisabled", jval); if (!enable) json_object_object_del(jmodel, publish); return save_config(cfg->jnode, cfg->node_dir_path); } bool mesh_config_model_sub_enable(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, bool enable) { json_object *jmodel, *jval; int ele_idx; if (!cfg) return false; ele_idx = get_element_index(cfg->jnode, ele_addr); if (ele_idx < 0) return false; jmodel = get_element_model(cfg->jnode, ele_idx, mod_id, vendor); if (!jmodel) return false; json_object_object_del(jmodel, subEnabled); jval = json_object_new_boolean(enable); json_object_object_add(jmodel, subEnabled, jval); if (!enable) json_object_object_del(jmodel, subscribe); return save_config(cfg->jnode, cfg->node_dir_path); } bool mesh_config_write_seq_number(struct mesh_config *cfg, uint32_t seq, bool cache) { int value = 0; uint32_t cached = 0; if (!cfg) return false; if (!cache) { if (!write_int(cfg->jnode, sequenceNumber, seq)) return false; return mesh_config_save(cfg, true, NULL, NULL); } /* If resetting seq to Zero, make sure cached value reset as well */ if (seq && get_int(cfg->jnode, sequenceNumber, &value)) cached = (uint32_t)value; /* * When sequence number approaches value stored on disk, calculate * average time between sequence number updates, then overcommit the * sequence number by MIN_SEQ_CACHE_TIME seconds worth of traffic or * MIN_SEQ_CACHE_VALUE (whichever is greater) to avoid frequent writes * to disk and to protect against crashes. * * The real value will be saved when daemon shuts down properly. */ if (seq + MIN_SEQ_CACHE_TRIGGER >= cached) { struct timeval now; struct timeval elapsed; uint64_t elapsed_ms; gettimeofday(&now, NULL); timersub(&now, &cfg->write_time, &elapsed); elapsed_ms = elapsed.tv_sec * 1000 + elapsed.tv_usec / 1000; /* * If time since last write is zero, this means that * idle_save_config is already pending, so we don't need to do * anything. */ if (!elapsed_ms) return true; cached = seq + (seq - cfg->write_seq) * 1000 * MIN_SEQ_CACHE_TIME / elapsed_ms; if (cached < seq + MIN_SEQ_CACHE_VALUE) cached = seq + MIN_SEQ_CACHE_VALUE; /* Cap the seq cache maximum to fixed out-of-range value. * If daemon restarts with out-of-range value, no packets * are to be sent until IV Update procedure completes. */ if (cached > SEQ_MASK) cached = SEQ_MASK + 1; cfg->write_seq = seq; /* Don't rewrite NVM storage if unchanged */ if (value == (int) cached) return true; l_debug("Seq Cache: %d -> %d", seq, cached); if (!write_int(cfg->jnode, sequenceNumber, cached)) return false; return mesh_config_save(cfg, false, NULL, NULL); } return true; } bool mesh_config_write_ttl(struct mesh_config *cfg, uint8_t ttl) { if (!cfg || !write_int(cfg->jnode, defaultTTL, ttl)) return false; return save_config(cfg->jnode, cfg->node_dir_path); } bool mesh_config_update_company_id(struct mesh_config *cfg, uint16_t cid) { if (!cfg || !write_uint16_hex(cfg->jnode, "cid", cid)) return false; return save_config(cfg->jnode, cfg->node_dir_path); } bool mesh_config_update_product_id(struct mesh_config *cfg, uint16_t pid) { if (!cfg || !write_uint16_hex(cfg->jnode, "pid", pid)) return false; return save_config(cfg->jnode, cfg->node_dir_path); } bool mesh_config_update_version_id(struct mesh_config *cfg, uint16_t vid) { if (!cfg || !write_uint16_hex(cfg->jnode, "vid", vid)) return false; return save_config(cfg->jnode, cfg->node_dir_path); } bool mesh_config_update_crpl(struct mesh_config *cfg, uint16_t crpl) { if (!cfg || !write_uint16_hex(cfg->jnode, "crpl", crpl)) return false; return save_config(cfg->jnode, cfg->node_dir_path); } static bool load_node(const char *fname, const uint8_t uuid[16], mesh_config_node_func_t cb, void *user_data) { int fd; char *str; struct stat st; ssize_t sz; bool result = false; json_object *jnode; struct mesh_config_node node; if (!cb) { l_info("Node read callback is required"); return false; } l_info("Loading configuration from %s", fname); fd = open(fname, O_RDONLY); if (fd < 0) return false; if (fstat(fd, &st) == -1) { close(fd); return false; } str = (char *) l_new(char, st.st_size + 1); if (!str) { close(fd); return false; } sz = read(fd, str, st.st_size); if (sz != st.st_size) { l_error("Failed to read configuration file %s", fname); goto done; } jnode = json_tokener_parse(str); if (!jnode) goto done; memset(&node, 0, sizeof(node)); node.elements = l_queue_new(); node.netkeys = l_queue_new(); node.appkeys = l_queue_new(); node.pages = l_queue_new(); result = read_node(jnode, &node); if (result) { struct mesh_config *cfg = l_new(struct mesh_config, 1); cfg->jnode = jnode; memcpy(cfg->uuid, uuid, 16); cfg->node_dir_path = l_strdup(fname); cfg->write_seq = node.seq_number; cfg->idles = l_queue_new(); gettimeofday(&cfg->write_time, NULL); result = cb(&node, uuid, cfg, user_data); if (!result) { l_free(cfg->idles); l_free(cfg->node_dir_path); l_free(cfg); } } /* Done with the node: free resources */ l_free(node.net_transmit); l_queue_destroy(node.netkeys, l_free); l_queue_destroy(node.appkeys, l_free); l_queue_destroy(node.pages, l_free); l_queue_destroy(node.elements, free_element); if (!result) json_object_put(jnode); done: close(fd); if (str) l_free(str); return result; } static void release_idle(void *data) { struct l_idle *idle = data; l_idle_remove(idle); } void mesh_config_release(struct mesh_config *cfg) { if (!cfg) return; l_queue_destroy(cfg->idles, release_idle); l_free(cfg->node_dir_path); json_object_put(cfg->jnode); l_free(cfg); } static void idle_save_config(struct l_idle *idle, void *user_data) { struct write_info *info = user_data; char *fname_tmp, *fname_bak, *fname_cfg; bool result = false; fname_cfg = info->cfg->node_dir_path; fname_tmp = l_strdup_printf("%s%s", fname_cfg, tmp_ext); fname_bak = l_strdup_printf("%s%s", fname_cfg, bak_ext); remove(fname_tmp); result = save_config(info->cfg->jnode, fname_tmp); if (result) { remove(fname_bak); if (rename(fname_cfg, fname_bak) < 0 || rename(fname_tmp, fname_cfg) < 0) result = false; } remove(fname_tmp); l_free(fname_tmp); l_free(fname_bak); gettimeofday(&info->cfg->write_time, NULL); if (info->cb) info->cb(info->user_data, result); if (idle) { l_queue_remove(info->cfg->idles, idle); l_idle_remove(idle); } l_free(info); } bool mesh_config_save(struct mesh_config *cfg, bool no_wait, mesh_config_status_func_t cb, void *user_data) { struct write_info *info; if (!cfg) return false; info = l_new(struct write_info, 1); info->cfg = cfg; info->cb = cb; info->user_data = user_data; if (no_wait) { idle_save_config(NULL, info); } else { struct l_idle *idle; idle = l_idle_create(idle_save_config, info, NULL); l_queue_push_tail(cfg->idles, idle); } return true; } bool mesh_config_load_nodes(const char *cfgdir_name, mesh_config_node_func_t cb, void *user_data) { DIR *cfgdir; struct dirent *entry; size_t path_len = strlen(cfgdir_name) + strlen(cfgnode_name) + strlen(bak_ext); create_dir(cfgdir_name); cfgdir = opendir(cfgdir_name); if (!cfgdir) { l_error("Failed to open mesh node storage directory: %s", cfgdir_name); return false; } while ((entry = readdir(cfgdir)) != NULL) { char *dirname, *fname, *bak; uint8_t uuid[16]; size_t node_len; if (entry->d_type != DT_DIR) continue; /* Check path length */ node_len = strlen(entry->d_name); if (path_len + node_len + 1 >= PATH_MAX) continue; if (!str2hex(entry->d_name, node_len, uuid, sizeof(uuid))) continue; dirname = l_strdup_printf("%s/%s", cfgdir_name, entry->d_name); fname = l_strdup_printf("%s%s", dirname, cfgnode_name); if (!load_node(fname, uuid, cb, user_data)) { /* Fall-back to Backup version */ bak = l_strdup_printf("%s%s", fname, bak_ext); if (load_node(bak, uuid, cb, user_data)) { remove(fname); rename(bak, fname); } l_free(bak); } l_free(fname); l_free(dirname); } closedir(cfgdir); return true; } void mesh_config_destroy_nvm(struct mesh_config *cfg) { char *node_dir; const char *node_name; char uuid[33]; if (!cfg) return; node_dir = dirname(cfg->node_dir_path); l_debug("Delete node config %s", node_dir); if (!hex2str(cfg->uuid, 16, uuid, sizeof(uuid))) return; node_name = mesh_basename(node_dir); /* Make sure path name of node follows expected guidelines */ if (strcmp(node_name, uuid)) return; del_path(node_dir); } bluez-5.87/mesh/PaxHeaders/model.h0000644000000000000000000000005015063045254014064 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/model.h0000644000000000000000000001113715063045254013550 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2020 Intel Corporation. All rights reserved. * * */ struct mesh_model; #define MAX_MODEL_BINDINGS 10 #define MAX_MODEL_SUBS 10 #define ACTION_ADD 1 #define ACTION_UPDATE 2 #define ACTION_DELETE 3 /* For internal representation of SIG defined models */ #define SIG_VENDOR 0xFFFF #define IS_VENDOR(x) ((x) < ((uint32_t)(SIG_VENDOR) << 16)) #define SET_ID(v, m) ((((uint32_t) (v)) << 16) | (m)) #define MODEL_ID(x) ((x) & ~VENDOR_ID_MASK) #define VENDOR_ID(x) ((x) >> 16) struct mesh_virtual; struct mesh_model_pub { struct mesh_virtual *virt; uint16_t addr; uint16_t idx; struct { uint16_t interval; uint8_t cnt; } rtx; uint8_t ttl; uint8_t credential; uint8_t period; }; typedef void (*mesh_model_unregister)(void *user_data); typedef bool (*mesh_model_recv_cb)(uint16_t src, uint16_t unicast, uint16_t app_idx, uint16_t net_idx, const uint8_t *data, uint16_t len, const void *user_data); typedef int (*mesh_model_bind_cb)(uint16_t app_idx, int action); typedef int (*mesh_model_pub_cb)(struct mesh_model_pub *pub); typedef int (*mesh_model_sub_cb)(uint16_t sub_addr, int action); struct mesh_model_ops { mesh_model_unregister unregister; mesh_model_recv_cb recv; mesh_model_bind_cb bind; mesh_model_pub_cb pub; mesh_model_sub_cb sub; }; bool mesh_model_add(struct mesh_node *node, struct l_queue *mods, uint32_t id, struct l_dbus_message_iter *opts); void mesh_model_free(void *data); bool mesh_model_register(struct mesh_node *node, uint8_t ele_idx, uint32_t id, const struct mesh_model_ops *cbs, void *user_data); bool mesh_model_add_from_storage(struct mesh_node *node, uint8_t ele_idx, struct l_queue *mods, struct l_queue *db_mods); void mesh_model_convert_to_storage(struct l_queue *db_mods, struct l_queue *mods); struct mesh_model_pub *mesh_model_pub_get(struct mesh_node *node, uint16_t ele_addr, uint32_t id, int *status); int mesh_model_pub_set(struct mesh_node *node, uint16_t ele_addr, uint32_t id, const uint8_t *addr, uint16_t idx, bool cred_flag, uint8_t ttl, uint8_t period, uint8_t rtx_cnt, uint16_t rtx_interval, bool is_virt, uint16_t *dst); int mesh_model_binding_add(struct mesh_node *node, uint16_t ele_addr, uint32_t id, uint16_t idx); int mesh_model_binding_del(struct mesh_node *node, uint16_t ele_addr, uint32_t id, uint16_t idx); int mesh_model_get_bindings(struct mesh_node *node, uint16_t ele_addr, uint32_t id, uint8_t *buf, uint16_t buf_sz, uint16_t *len); int mesh_model_sub_add(struct mesh_node *node, uint16_t ele_addr, uint32_t id, uint16_t grp); int mesh_model_virt_sub_add(struct mesh_node *node, uint16_t ele_addr, uint32_t id, const uint8_t *label, uint16_t *addr); int mesh_model_sub_del(struct mesh_node *node, uint16_t ele_addr, uint32_t id, uint16_t grp); int mesh_model_virt_sub_del(struct mesh_node *node, uint16_t ele_addr, uint32_t id, const uint8_t *label, uint16_t *addr); int mesh_model_sub_del_all(struct mesh_node *node, uint16_t ele_addr, uint32_t id); int mesh_model_sub_ovrt(struct mesh_node *node, uint16_t ele_addr, uint32_t id, uint16_t addr); int mesh_model_virt_sub_ovrt(struct mesh_node *node, uint16_t ele_addr, uint32_t id, const uint8_t *label, uint16_t *addr); int mesh_model_sub_get(struct mesh_node *node, uint16_t ele_addr, uint32_t id, uint8_t *buf, uint16_t buf_size, uint16_t *size); bool mesh_model_send(struct mesh_node *node, uint16_t src, uint16_t dst, uint16_t app_idx, uint16_t net_idx, uint8_t ttl, bool segmented, uint16_t len, const void *data); int mesh_model_publish(struct mesh_node *node, uint32_t id, uint16_t src, bool segmented, uint16_t len, const void *data); bool mesh_model_rx(struct mesh_node *node, bool szmict, uint32_t seq0, uint32_t iv_index, uint16_t net_idx, uint16_t src, uint16_t dst, uint8_t key_aid, const uint8_t *data, uint16_t size); void mesh_model_app_key_delete(struct mesh_node *node, uint16_t ele_idx, struct l_queue *models, uint16_t app_idx); uint16_t mesh_model_opcode_set(uint32_t opcode, uint8_t *buf); bool mesh_model_opcode_get(const uint8_t *buf, uint16_t size, uint32_t *opcode, uint16_t *n); void mesh_model_build_config(void *model, void *msg_builder); void mesh_model_update_opts(struct mesh_node *node, uint8_t ele_idx, struct l_queue *curr, struct l_queue *updated); uint16_t mesh_model_generate_composition(struct l_queue *mods, uint16_t buf_sz, uint8_t *buf); void mesh_model_init(void); void mesh_model_cleanup(void); bluez-5.87/mesh/PaxHeaders/mesh-config.h0000644000000000000000000000005015063045254015163 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/mesh-config.h0000644000000000000000000001405115063045254014645 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ #define MIN_COMP_SIZE 14 struct mesh_config; struct mesh_config_sub { bool virt; union { uint16_t grp; uint8_t label[16]; } addr; }; struct mesh_config_pub { bool virt; uint32_t period; uint16_t addr; uint16_t idx; uint16_t interval; uint8_t ttl; uint8_t credential; uint8_t cnt; uint8_t virt_addr[16]; }; struct mesh_config_model { struct mesh_config_sub *subs; struct mesh_config_pub *pub; uint16_t *bindings; uint32_t id; bool vendor; bool sub_enabled; bool pub_enabled; uint32_t num_bindings; uint32_t num_subs; }; struct mesh_config_element { struct l_queue *models; uint16_t location; uint8_t index; }; struct mesh_config_modes { struct { uint16_t interval; uint8_t cnt; uint8_t state; } relay; uint8_t lpn; uint8_t friend; uint8_t proxy; uint8_t beacon; uint8_t mpb; uint8_t mpb_period; }; struct mesh_config_netkey { uint16_t idx; uint8_t key[16]; uint8_t new_key[16]; uint8_t phase; }; struct mesh_config_appkey { uint16_t net_idx; uint16_t app_idx; uint8_t key[16]; uint8_t new_key[16]; }; struct mesh_config_transmit { uint16_t interval; uint8_t count; }; struct mesh_config_comp_page { uint16_t len; uint8_t page_num; uint8_t data[]; }; struct mesh_config_node { struct l_queue *elements; struct l_queue *netkeys; struct l_queue *appkeys; struct l_queue *pages; uint32_t seq_number; uint32_t iv_index; bool iv_update; uint16_t cid; uint16_t pid; uint16_t vid; uint16_t crpl; uint16_t unicast; struct mesh_config_transmit *net_transmit; struct mesh_config_modes modes; uint8_t ttl; uint8_t dev_key[16]; uint8_t token[8]; }; typedef void (*mesh_config_status_func_t)(void *user_data, bool result); typedef bool (*mesh_config_node_func_t)(struct mesh_config_node *node, const uint8_t uuid[16], struct mesh_config *cfg, void *user_data); bool mesh_config_load_nodes(const char *cfgdir_name, mesh_config_node_func_t cb, void *user_data); void mesh_config_release(struct mesh_config *cfg); void mesh_config_destroy_nvm(struct mesh_config *cfg); bool mesh_config_save(struct mesh_config *cfg, bool no_wait, mesh_config_status_func_t cb, void *user_data); void mesh_config_reset(struct mesh_config *cfg, struct mesh_config_node *node); struct mesh_config *mesh_config_create(const char *cfgdir_name, const uint8_t uuid[16], struct mesh_config_node *node); bool mesh_config_write_net_transmit(struct mesh_config *cfg, uint8_t cnt, uint16_t interval); bool mesh_config_write_device_key(struct mesh_config *cfg, const uint8_t *key); bool mesh_config_write_candidate(struct mesh_config *cfg, const uint8_t *key); bool mesh_config_read_candidate(struct mesh_config *cfg, uint8_t *key); bool mesh_config_finalize_candidate(struct mesh_config *cfg); bool mesh_config_write_token(struct mesh_config *cfg, const uint8_t *token); bool mesh_config_write_seq_number(struct mesh_config *cfg, uint32_t seq, bool cache); bool mesh_config_write_unicast(struct mesh_config *cfg, uint16_t unicast); bool mesh_config_write_relay_mode(struct mesh_config *cfg, uint8_t mode, uint8_t count, uint16_t interval); bool mesh_config_write_mpb(struct mesh_config *cfg, uint8_t mode, uint8_t period); bool mesh_config_write_ttl(struct mesh_config *cfg, uint8_t ttl); bool mesh_config_write_mode(struct mesh_config *cfg, const char *keyword, int value); bool mesh_config_write_mode_ex(struct mesh_config *cfg, const char *keyword, int value, bool save); bool mesh_config_comp_page_add(struct mesh_config *cfg, uint8_t page, const uint8_t *data, uint16_t size); void mesh_config_comp_page_del(struct mesh_config *cfg, uint8_t page); bool mesh_config_model_binding_add(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, uint16_t app_idx); bool mesh_config_model_binding_del(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, uint16_t app_idx); bool mesh_config_model_pub_add(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, const struct mesh_config_pub *pub); bool mesh_config_model_pub_del(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor); bool mesh_config_model_sub_add(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, const struct mesh_config_sub *sub); bool mesh_config_model_sub_del(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, const struct mesh_config_sub *sub); bool mesh_config_model_sub_del_all(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor); bool mesh_config_model_pub_enable(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, bool enable); bool mesh_config_model_sub_enable(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, bool enable); bool mesh_config_app_key_add(struct mesh_config *cfg, uint16_t net_idx, uint16_t app_idx, const uint8_t key[16]); bool mesh_config_app_key_update(struct mesh_config *cfg, uint16_t app_idx, const uint8_t key[16]); bool mesh_config_app_key_del(struct mesh_config *cfg, uint16_t net_idx, uint16_t idx); bool mesh_config_net_key_add(struct mesh_config *cfg, uint16_t net_idx, const uint8_t key[16]); bool mesh_config_net_key_update(struct mesh_config *cfg, uint16_t idx, const uint8_t key[16]); bool mesh_config_net_key_del(struct mesh_config *cfg, uint16_t net_idx); bool mesh_config_net_key_set_phase(struct mesh_config *cfg, uint16_t idx, uint8_t phase); bool mesh_config_write_iv_index(struct mesh_config *cfg, uint32_t idx, bool update); bool mesh_config_update_company_id(struct mesh_config *cfg, uint16_t cid); bool mesh_config_update_product_id(struct mesh_config *cfg, uint16_t pid); bool mesh_config_update_version_id(struct mesh_config *cfg, uint16_t vid); bool mesh_config_update_crpl(struct mesh_config *cfg, uint16_t crpl); bluez-5.87/mesh/PaxHeaders/prv-beacon.h0000644000000000000000000000005014447506754015034 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/prv-beacon.h0000644000000000000000000000235514447506754014522 0ustar00rootroot/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2023 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. * */ struct mesh_node; #define PRV_BEACON_SRV_MODEL SET_ID(SIG_VENDOR, 0x0008) #define PRV_BEACON_CLI_MODEL SET_ID(SIG_VENDOR, 0x0009) /* Private Beacon opcodes */ #define OP_PRIVATE_BEACON_GET 0x8060 #define OP_PRIVATE_BEACON_SET 0x8061 #define OP_PRIVATE_BEACON_STATUS 0x8062 #define OP_PRIVATE_GATT_PROXY_GET 0x8063 #define OP_PRIVATE_GATT_PROXY_SET 0x8064 #define OP_PRIVATE_GATT_PROXY_STATUS 0x8065 #define OP_PRIVATE_NODE_ID_GET 0x8066 #define OP_PRIVATE_NODE_ID_SET 0x8067 #define OP_PRIVATE_NODE_ID_STATUS 0x8068 void prv_beacon_server_init(struct mesh_node *node, uint8_t ele_idx); bluez-5.87/mesh/PaxHeaders/cfgmod-server.c0000644000000000000000000000005015017252326015521 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/cfgmod-server.c0000644000000000000000000006226415017252326015214 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2020 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "mesh/mesh-defs.h" #include "mesh/node.h" #include "mesh/net.h" #include "mesh/appkey.h" #include "mesh/model.h" #include "mesh/mesh-config.h" #include "mesh/cfgmod.h" #define CREDFLAG_MASK 0x1000 #define CFG_GET_ID(vendor, pkt) ((vendor) ? \ (SET_ID(l_get_le16((pkt)), l_get_le16((pkt) + 2))) : \ (SET_ID(SIG_VENDOR, l_get_le16(pkt)))) /* Supported composition pages, sorted high to low */ static const uint8_t supported_pages[] = { 128, 0 }; static uint8_t msg[MAX_MSG_LEN]; static uint16_t set_pub_status(uint8_t status, uint16_t ele_addr, uint32_t id, uint16_t pub_addr, uint16_t idx, bool cred_flag, uint8_t ttl, uint8_t period, uint8_t rtx) { size_t n; n = mesh_model_opcode_set(OP_CONFIG_MODEL_PUB_STATUS, msg); msg[n++] = status; l_put_le16(ele_addr, msg + n); l_put_le16(pub_addr, msg + n + 2); idx |= cred_flag ? CREDFLAG_MASK : 0; l_put_le16(idx, msg + n + 4); n += 6; msg[n++] = ttl; msg[n++] = period; msg[n++] = rtx; if (!IS_VENDOR(id)) { l_put_le16(MODEL_ID(id), msg + n); n += 2; } else { l_put_le16(VENDOR_ID(id), msg + n); l_put_le16(MODEL_ID(id), msg + n + 2); n += 4; } return n; } static uint16_t config_pub_get(struct mesh_node *node, const uint8_t *pkt, bool vendor) { uint32_t id; uint16_t ele_addr; uint8_t rtx; struct mesh_model_pub *pub; int status; ele_addr = l_get_le16(pkt); id = CFG_GET_ID(vendor, pkt + 2); pub = mesh_model_pub_get(node, ele_addr, id, &status); if (pub && status == MESH_STATUS_SUCCESS) { rtx = pub->rtx.cnt + (((pub->rtx.interval / 50) - 1) << 3); return set_pub_status(status, ele_addr, id, pub->addr, pub->idx, pub->credential, pub->ttl, pub->period, rtx); } else return set_pub_status(status, ele_addr, id, 0, 0, 0, 0, 0, 0); } static uint16_t config_pub_set(struct mesh_node *node, const uint8_t *pkt, bool virt, bool vendor) { uint32_t id; uint16_t ele_addr, idx, pub_dst; const uint8_t *pub_addr; uint8_t ttl, period, rtx, cnt, interval; int status; bool cred_flag; ele_addr = l_get_le16(pkt); pub_addr = pkt + 2; pub_dst = l_get_le16(pub_addr); if (!virt && IS_VIRTUAL(pub_dst)) return 0; pkt += (virt ? 14 : 0); idx = l_get_le16(pkt + 4); cred_flag = !!(CREDFLAG_MASK & idx); idx &= APP_IDX_MASK; ttl = pkt[6]; period = pkt[7]; rtx = pkt[8]; id = CFG_GET_ID(vendor, pkt + 9); cnt = rtx & 0x7; interval = ((rtx >> 3) + 1) * 50; status = mesh_model_pub_set(node, ele_addr, id, pub_addr, idx, cred_flag, ttl, period, cnt, interval, virt, &pub_dst); l_debug("pub_set: status %d, ea %4.4x, ota: %4.4x, id: %x, idx: %3.3x", status, ele_addr, pub_dst, id, idx); if (status != MESH_STATUS_SUCCESS) return set_pub_status(status, ele_addr, id, 0, 0, 0, 0, 0, 0); if (IS_UNASSIGNED(pub_dst) && !virt) { ttl = period = idx = 0; /* Remove model publication from config file */ if (!mesh_config_model_pub_del(node_config_get(node), ele_addr, vendor ? id : MODEL_ID(id), vendor)) status = MESH_STATUS_STORAGE_FAIL; } else { struct mesh_config_pub db_pub = { .virt = virt, .addr = pub_dst, .idx = idx, .ttl = ttl, .credential = cred_flag, .period = period, .cnt = cnt, .interval = interval }; if (virt) memcpy(db_pub.virt_addr, pub_addr, 16); /* Save model publication to config file */ if (!mesh_config_model_pub_add(node_config_get(node), ele_addr, vendor ? id : MODEL_ID(id), vendor, &db_pub)) status = MESH_STATUS_STORAGE_FAIL; } return set_pub_status(status, ele_addr, id, pub_dst, idx, cred_flag, ttl, period, rtx); } static uint16_t cfg_sub_get_msg(struct mesh_node *node, const uint8_t *pkt, uint16_t size) { uint16_t ele_addr, n, sub_len; uint32_t id; int opcode; bool vendor = (size == 6); ele_addr = l_get_le16(pkt); id = CFG_GET_ID(vendor, pkt + 2); opcode = vendor ? OP_CONFIG_VEND_MODEL_SUB_LIST : OP_CONFIG_MODEL_SUB_LIST; n = mesh_model_opcode_set(opcode, msg); memcpy(msg + n + 1, pkt, size); msg[n] = mesh_model_sub_get(node, ele_addr, id, msg + n + 1 + size, MAX_MSG_LEN - (n + 1 + size), &sub_len); if (msg[n] == MESH_STATUS_SUCCESS) n += sub_len; n += (size + 1); return n; } static bool save_cfg_sub(struct mesh_node *node, uint16_t ele_addr, uint32_t id, bool vendor, const uint8_t *label, bool virt, uint16_t grp, uint32_t opcode) { struct mesh_config *cfg = node_config_get(node); struct mesh_config_sub db_sub = { .virt = virt, .addr.grp = grp }; id = (vendor) ? id : MODEL_ID(id); if (virt && label) memcpy(db_sub.addr.label, label, 16); if (opcode == OP_CONFIG_MODEL_SUB_VIRT_DELETE || opcode == OP_CONFIG_MODEL_SUB_DELETE) return mesh_config_model_sub_del(cfg, ele_addr, id, vendor, &db_sub); if (opcode == OP_CONFIG_MODEL_SUB_VIRT_OVERWRITE || opcode == OP_CONFIG_MODEL_SUB_OVERWRITE) if (!mesh_config_model_sub_del_all(cfg, ele_addr, id, vendor)) return false; return mesh_config_model_sub_add(cfg, ele_addr, id, vendor, &db_sub); } static uint16_t cfg_sub_add_msg(struct mesh_node *node, const uint8_t *pkt, bool vendor, uint32_t opcode) { uint16_t addr, ele_addr, n; uint32_t id; addr = l_get_le16(pkt + 2); if (!IS_GROUP(addr)) return 0; ele_addr = l_get_le16(pkt); id = CFG_GET_ID(vendor, pkt + 4); n = mesh_model_opcode_set(OP_CONFIG_MODEL_SUB_STATUS, msg); if (opcode == OP_CONFIG_MODEL_SUB_OVERWRITE) msg[n] = mesh_model_sub_ovrt(node, ele_addr, id, addr); else if (opcode == OP_CONFIG_MODEL_SUB_ADD) msg[n] = mesh_model_sub_add(node, ele_addr, id, addr); else msg[n] = mesh_model_sub_del(node, ele_addr, id, addr); if (msg[n] == MESH_STATUS_SUCCESS && !save_cfg_sub(node, ele_addr, id, vendor, NULL, false, addr, opcode)) msg[n] = MESH_STATUS_STORAGE_FAIL; if (vendor) { memcpy(msg + n + 1, pkt, 8); n += 9; } else { memcpy(msg + n + 1, pkt, 6); n += 7; } return n; } static uint16_t cfg_virt_sub_add_msg(struct mesh_node *node, const uint8_t *pkt, bool vendor, uint32_t opcode) { uint16_t addr, ele_addr, n; uint32_t id; const uint8_t *label; n = mesh_model_opcode_set(OP_CONFIG_MODEL_SUB_STATUS, msg); ele_addr = l_get_le16(pkt); label = pkt + 2; id = CFG_GET_ID(vendor, pkt + 18); if (opcode == OP_CONFIG_MODEL_SUB_VIRT_OVERWRITE) msg[n] = mesh_model_virt_sub_ovrt(node, ele_addr, id, label, &addr); else if (opcode == OP_CONFIG_MODEL_SUB_VIRT_ADD) msg[n] = mesh_model_virt_sub_add(node, ele_addr, id, label, &addr); else msg[n] = mesh_model_virt_sub_del(node, ele_addr, id, label, &addr); if (msg[n] == MESH_STATUS_SUCCESS && !save_cfg_sub(node, ele_addr, id, vendor, label, true, addr, opcode)) msg[n] = MESH_STATUS_STORAGE_FAIL; /* If processing failed, set addr field to zero in reply */ if (msg[n] != MESH_STATUS_SUCCESS) addr = UNASSIGNED_ADDRESS; l_put_le16(ele_addr, msg + n + 1); l_put_le16(addr, msg + n + 3); if (vendor) { l_put_le16(VENDOR_ID(id), msg + n + 5); l_put_le16(MODEL_ID(id), msg + n + 7); n += 9; } else { l_put_le16(MODEL_ID(id), msg + n + 5); n += 7; } return n; } static uint16_t config_sub_del_all(struct mesh_node *node, const uint8_t *pkt, bool vendor) { uint16_t ele_addr, n, grp = UNASSIGNED_ADDRESS; uint32_t id; n = mesh_model_opcode_set(OP_CONFIG_MODEL_SUB_STATUS, msg); ele_addr = l_get_le16(pkt); id = CFG_GET_ID(vendor, pkt + 2); msg[n] = mesh_model_sub_del_all(node, ele_addr, id); if (msg[n] == MESH_STATUS_SUCCESS) { struct mesh_config *cfg = node_config_get(node); if (!mesh_config_model_sub_del_all(cfg, ele_addr, vendor ? id : MODEL_ID(id), vendor)) msg[n] = MESH_STATUS_STORAGE_FAIL; } l_put_le16(ele_addr, msg + n + 1); l_put_le16(grp, msg + n + 3); if (vendor) { l_put_le16(VENDOR_ID(id), msg + n + 5); l_put_le16(MODEL_ID(id), msg + n + 7); n += 9; } else { l_put_le16(MODEL_ID(id), msg + n + 5); n += 7; } return n; } static uint16_t model_app_list(struct mesh_node *node, const uint8_t *pkt, uint16_t size) { uint16_t ele_addr, n, bnd_len; uint32_t id; int opcode; opcode = (size == 4) ? OP_MODEL_APP_LIST : OP_VEND_MODEL_APP_LIST; ele_addr = l_get_le16(pkt); n = mesh_model_opcode_set(opcode, msg); memcpy(msg + n + 1, pkt, size); id = CFG_GET_ID(size == 6, pkt + 2); msg[n] = mesh_model_get_bindings(node, ele_addr, id, msg + n + 1 + size, MAX_MSG_LEN - (n + 1 + size), &bnd_len); if (msg[n] == MESH_STATUS_SUCCESS) n += bnd_len; n += (size + 1); return n; } static uint16_t model_app_bind(struct mesh_node *node, const uint8_t *pkt, uint16_t size, bool unbind) { uint16_t ele_addr, idx, n; uint32_t id; idx = l_get_le16(pkt + 2); if (idx > APP_IDX_MAX) return 0; ele_addr = l_get_le16(pkt); id = CFG_GET_ID(size == 8, pkt + 4); n = mesh_model_opcode_set(OP_MODEL_APP_STATUS, msg); if (unbind) msg[n] = mesh_model_binding_del(node, ele_addr, id, idx); else msg[n] = mesh_model_binding_add(node, ele_addr, id, idx); memcpy(msg + n + 1, pkt, size); n += (size + 1); return n; } static uint16_t cfg_relay_msg(struct mesh_node *node, const uint8_t *pkt, int opcode) { uint8_t count; uint16_t interval; uint16_t n; if (opcode == OP_CONFIG_RELAY_SET) { count = (pkt[1] & 0x7) + 1; interval = ((pkt[1] >> 3) + 1) * 10; node_relay_mode_set(node, !!pkt[0], count, interval); } n = mesh_model_opcode_set(OP_CONFIG_RELAY_STATUS, msg); msg[n++] = node_relay_mode_get(node, &count, &interval); msg[n++] = (count - 1) + ((interval/10 - 1) << 3); return n; } static uint16_t cfg_key_refresh_phase(struct mesh_node *node, const uint8_t *pkt, int opcode) { struct mesh_net *net = node_get_net(node); uint16_t n, idx = l_get_le16(pkt); uint8_t phase; int status; n = mesh_model_opcode_set(OP_CONFIG_KEY_REFRESH_PHASE_STATUS, msg); status = mesh_net_key_refresh_phase_get(net, idx, &phase); if (status == MESH_STATUS_SUCCESS && opcode == OP_CONFIG_KEY_REFRESH_PHASE_SET) { if (pkt[2] == KEY_REFRESH_TRANS_TWO) { if (phase == KEY_REFRESH_PHASE_TWO) goto done; else if (phase != KEY_REFRESH_PHASE_ONE) return 0; } if (pkt[2] == KEY_REFRESH_TRANS_THREE && phase == KEY_REFRESH_PHASE_NONE) goto done; status = mesh_net_key_refresh_phase_set(net, idx, pkt[2]); l_debug("Set KR Phase: net=%3.3x transition=%d", idx, pkt[2]); if (status == MESH_STATUS_SUCCESS) mesh_net_key_refresh_phase_get(net, idx, &phase); } done: msg[n] = status; l_put_le16(idx, &msg[n + 1]); msg[n + 3] = (status != MESH_STATUS_SUCCESS) ? KEY_REFRESH_PHASE_NONE : phase; return n + 4; } static uint8_t uint32_to_log(uint32_t value) { uint32_t val = 1; uint8_t ret = 0; if (!value) return 0; else if (value > 0x10000) return 0xff; while (val <= value) { val <<= 1; ret++; } return ret; } static uint16_t hb_subscription_status(struct mesh_node *node, int status) { struct mesh_net *net = node_get_net(node); struct mesh_net_heartbeat_sub *sub = mesh_net_get_heartbeat_sub(net); struct timeval time_now; uint16_t n; gettimeofday(&time_now, NULL); time_now.tv_sec -= sub->start; if (time_now.tv_sec >= (long) sub->period) time_now.tv_sec = 0; else time_now.tv_sec = sub->period - time_now.tv_sec; l_debug("Sub Period (Log %2.2x) %d sec", uint32_to_log(time_now.tv_sec), (int) time_now.tv_sec); n = mesh_model_opcode_set(OP_CONFIG_HEARTBEAT_SUB_STATUS, msg); msg[n++] = status; l_put_le16(sub->src, msg + n); n += 2; l_put_le16(sub->dst, msg + n); n += 2; msg[n++] = uint32_to_log(time_now.tv_sec); msg[n++] = sub->count != 0xffff ? uint32_to_log(sub->count) : 0xff; msg[n++] = sub->min_hops; msg[n++] = sub->max_hops; return n; } static uint16_t hb_subscription_get(struct mesh_node *node, int status) { struct mesh_net *net = node_get_net(node); struct mesh_net_heartbeat_sub *sub = mesh_net_get_heartbeat_sub(net); /* * MshPRFv1.0.1 section 4.4.1.2.16, Heartbeat Subscription state: * If this is a GET request and the source or destination is unassigned, * all fields shall be set to zero in the status reply. */ if (IS_UNASSIGNED(sub->src) || IS_UNASSIGNED(sub->dst)) { uint16_t n; n = mesh_model_opcode_set(OP_CONFIG_HEARTBEAT_SUB_STATUS, msg); memset(msg + n, 0, 9); n += 9; return n; } return hb_subscription_status(node, status); } static uint16_t hb_subscription_set(struct mesh_node *node, const uint8_t *pkt) { uint16_t src, dst; uint8_t period_log; struct mesh_net *net; int status; src = l_get_le16(pkt); dst = l_get_le16(pkt + 2); /* SRC must be Unicast, DST can be any legal address except Virtual */ if ((!IS_UNASSIGNED(src) && !IS_UNICAST(src)) || IS_VIRTUAL(dst)) return 0; period_log = pkt[4]; if (period_log > 0x11) return 0; net = node_get_net(node); status = mesh_net_set_heartbeat_sub(net, src, dst, period_log); return hb_subscription_status(node, status); } static uint16_t hb_publication_get(struct mesh_node *node, int status) { struct mesh_net *net = node_get_net(node); struct mesh_net_heartbeat_pub *pub = mesh_net_get_heartbeat_pub(net); uint16_t n; n = mesh_model_opcode_set(OP_CONFIG_HEARTBEAT_PUB_STATUS, msg); msg[n++] = status; l_put_le16(pub->dst, msg + n); n += 2; msg[n++] = pub->count != 0xffff ? uint32_to_log(pub->count) : 0xff; msg[n++] = uint32_to_log(pub->period); msg[n++] = pub->ttl; l_put_le16(pub->features, msg + n); n += 2; l_put_le16(pub->net_idx, msg + n); n += 2; return n; } static uint16_t hb_publication_set(struct mesh_node *node, const uint8_t *pkt) { uint16_t dst, features, net_idx; uint8_t period_log, count_log, ttl; struct mesh_net *net; int status; dst = l_get_le16(pkt); count_log = pkt[2]; period_log = pkt[3]; ttl = pkt[4]; if (count_log > 0x11 && count_log != 0xff) return 0; if (period_log > 0x11 || ttl > TTL_MASK || IS_VIRTUAL(dst)) return 0; features = l_get_le16(pkt + 5) & 0xf; net_idx = l_get_le16(pkt + 7); net = node_get_net(node); status = mesh_net_set_heartbeat_pub(net, dst, features, net_idx, ttl, count_log, period_log); if (status != MESH_STATUS_SUCCESS) { uint16_t n; n = mesh_model_opcode_set(OP_CONFIG_HEARTBEAT_PUB_STATUS, msg); msg[n++] = status; memcpy(msg + n, pkt, 9); n += 9; return n; } else return hb_publication_get(node, status); } static void node_reset(void *user_data) { struct mesh_node *node = user_data; l_debug("Node Reset"); node_remove(node); } static uint16_t cfg_appkey_msg(struct mesh_node *node, const uint8_t *pkt, int opcode) { uint16_t n_idx, a_idx, n; struct mesh_net *net = node_get_net(node); n_idx = l_get_le16(pkt) & 0xfff; a_idx = l_get_le16(pkt + 1) >> 4; n = mesh_model_opcode_set(OP_APPKEY_STATUS, msg); if (opcode == OP_APPKEY_ADD) msg[n] = appkey_key_add(net, n_idx, a_idx, pkt + 3); else if (opcode == OP_APPKEY_UPDATE) msg[n] = appkey_key_update(net, n_idx, a_idx, pkt + 3); else msg[n] = appkey_key_delete(net, n_idx, a_idx); l_debug("AppKey Command %s: Net_Idx %3.3x, App_Idx %3.3x", (msg[n] == MESH_STATUS_SUCCESS) ? "success" : "fail", n_idx, a_idx); memcpy(msg + n + 1, &pkt[0], 3); return n + 4; } static uint16_t cfg_netkey_msg(struct mesh_node *node, const uint8_t *pkt, int opcode) { uint16_t n_idx, n; struct mesh_net *net = node_get_net(node); n_idx = l_get_le16(pkt); if (n_idx > NET_IDX_MAX) return 0; n = mesh_model_opcode_set(OP_NETKEY_STATUS, msg); if (opcode == OP_NETKEY_ADD) msg[n] = mesh_net_add_key(net, n_idx, pkt + 2); else if (opcode == OP_NETKEY_UPDATE) msg[n] = mesh_net_update_key(net, n_idx, pkt + 2); else msg[n] = mesh_net_del_key(net, n_idx); l_debug("NetKey Command %s: Net_Idx %3.3x", (msg[n] == MESH_STATUS_SUCCESS) ? "success" : "fail", n_idx); memcpy(msg + n + 1, &pkt[0], 2); return n + 3; } static uint16_t cfg_get_appkeys_msg(struct mesh_node *node, const uint8_t *pkt) { uint16_t n_idx, sz, n; n_idx = l_get_le16(pkt); n = mesh_model_opcode_set(OP_APPKEY_LIST, msg); l_put_le16(n_idx, msg + n + 1); msg[n] = appkey_list(node_get_net(node), n_idx, msg + n + 3, MAX_MSG_LEN - (n + 3), &sz); return n + 3 + sz; } static uint16_t cfg_poll_timeout_msg(struct mesh_node *node, const uint8_t *pkt) { uint16_t n, addr = l_get_le16(pkt); uint32_t poll_to; if (!IS_UNICAST(addr)) return 0; n = mesh_model_opcode_set(OP_CONFIG_POLL_TIMEOUT_STATUS, msg); l_put_le16(addr, msg + n); n += 2; poll_to = mesh_net_friend_timeout(node_get_net(node), addr); msg[n++] = poll_to; msg[n++] = poll_to >> 8; msg[n++] = poll_to >> 16; return n; } static uint16_t cfg_net_tx_msg(struct mesh_node *node, const uint8_t *pkt, int opcode) { uint8_t cnt; uint16_t interval, n; struct mesh_net *net = node_get_net(node); cnt = (pkt[0] & 0x7) + 1; interval = ((pkt[0] >> 3) + 1) * 10; if (opcode == OP_CONFIG_NETWORK_TRANSMIT_SET && mesh_config_write_net_transmit(node_config_get(node), cnt, interval)) mesh_net_transmit_params_set(net, cnt, interval); n = mesh_model_opcode_set(OP_CONFIG_NETWORK_TRANSMIT_STATUS, msg); mesh_net_transmit_params_get(net, &cnt, &interval); msg[n++] = (cnt - 1) + ((interval/10 - 1) << 3); return n; } static uint16_t get_composition(struct mesh_node *node, uint8_t page, uint8_t *buf) { const uint8_t *comp = NULL; uint16_t len = 0; size_t i; for (i = 0; i < sizeof(supported_pages); i++) { if (page < supported_pages[i]) continue; page = supported_pages[i]; comp = node_get_comp(node, page, &len); if (!page || len) break; } if (!len || !comp) return 0; *buf++ = page; memcpy(buf, comp, len); return len + 1; } static bool cfg_srv_pkt(uint16_t src, uint16_t dst, uint16_t app_idx, uint16_t net_idx, const uint8_t *data, uint16_t size, const void *user_data) { struct mesh_node *node = (struct mesh_node *) user_data; struct mesh_net *net; const uint8_t *pkt = data; uint32_t opcode; uint16_t n_idx; uint8_t state; bool virt = false; uint16_t n; if (app_idx != APP_IDX_DEV_LOCAL) return false; if (mesh_model_opcode_get(pkt, size, &opcode, &n)) { size -= n; pkt += n; } else return false; net = node_get_net(node); l_debug("CONFIG-SRV-opcode 0x%x size %u idx %3.3x", opcode, size, net_idx); n = 0; switch (opcode) { default: return false; case OP_DEV_COMP_GET: if (size != 1) return true; n = mesh_model_opcode_set(OP_DEV_COMP_STATUS, msg); n += get_composition(node, pkt[0], msg + n); break; case OP_CONFIG_DEFAULT_TTL_SET: if (size != 1 || pkt[0] > TTL_MASK || pkt[0] == 1) return true; node_default_ttl_set(node, pkt[0]); /* Fall Through */ case OP_CONFIG_DEFAULT_TTL_GET: if (opcode == OP_CONFIG_DEFAULT_TTL_GET && size != 0) return true; l_debug("Get/Set Default TTL"); n = mesh_model_opcode_set(OP_CONFIG_DEFAULT_TTL_STATUS, msg); msg[n++] = node_default_ttl_get(node); break; case OP_CONFIG_MODEL_PUB_VIRT_SET: if (size != 25 && size != 27) return true; virt = true; /* Fall Through */ case OP_CONFIG_MODEL_PUB_SET: if (!virt && (size != 11 && size != 13)) return true; n = config_pub_set(node, pkt, virt, size == 13 || size == 27); break; case OP_CONFIG_MODEL_PUB_GET: if (size != 4 && size != 6) return true; n = config_pub_get(node, pkt, size == 6); break; case OP_CONFIG_VEND_MODEL_SUB_GET: if (size != 6) return true; /* Fall Through */ case OP_CONFIG_MODEL_SUB_GET: if (size != 4 && opcode == OP_CONFIG_MODEL_SUB_GET) return true; n = cfg_sub_get_msg(node, pkt, size); break; case OP_CONFIG_MODEL_SUB_VIRT_OVERWRITE: case OP_CONFIG_MODEL_SUB_VIRT_DELETE: case OP_CONFIG_MODEL_SUB_VIRT_ADD: if (size != 20 && size != 22) return true; n = cfg_virt_sub_add_msg(node, pkt, size == 22, opcode); break; case OP_CONFIG_MODEL_SUB_OVERWRITE: case OP_CONFIG_MODEL_SUB_DELETE: case OP_CONFIG_MODEL_SUB_ADD: if (size != 6 && size != 8) return true; n = cfg_sub_add_msg(node, pkt, size == 8, opcode); break; case OP_CONFIG_MODEL_SUB_DELETE_ALL: if (size != 4 && size != 6) return true; n = config_sub_del_all(node, pkt, size == 6); break; case OP_CONFIG_RELAY_SET: if (size != 2 || pkt[0] > 0x01) return true; /* Fall Through */ case OP_CONFIG_RELAY_GET: if (opcode == OP_CONFIG_RELAY_GET && size != 0) return true; n = cfg_relay_msg(node, pkt, opcode); break; case OP_CONFIG_NETWORK_TRANSMIT_SET: if (size != 1) return true; /* Fall Through */ case OP_CONFIG_NETWORK_TRANSMIT_GET: if (opcode == OP_CONFIG_NETWORK_TRANSMIT_GET && size != 0) return true; n = cfg_net_tx_msg(node, pkt, opcode); break; case OP_CONFIG_PROXY_SET: if (size != 1 || pkt[0] > 0x01) return true; node_proxy_mode_set(node, !!pkt[0]); /* Fall Through */ case OP_CONFIG_PROXY_GET: if (opcode == OP_CONFIG_PROXY_GET && size != 0) return true; n = mesh_model_opcode_set(OP_CONFIG_PROXY_STATUS, msg); msg[n++] = node_proxy_mode_get(node); l_debug("Get/Set Config Proxy (%d)", msg[n-1]); break; case OP_NODE_IDENTITY_SET: if (size != 3) return true; /* Currently setting node identity not supported */ /* Fall Through */ case OP_NODE_IDENTITY_GET: if (opcode == OP_NODE_IDENTITY_GET && size != 2) return true; n_idx = l_get_le16(pkt); if (n_idx > NET_IDX_MAX) return true; n = mesh_model_opcode_set(OP_NODE_IDENTITY_STATUS, msg); msg[n++] = mesh_net_get_identity_mode(net, n_idx, &state); l_put_le16(n_idx, msg + n); n += 2; msg[n++] = state; l_debug("Get/Set Config Identity (%d)", state); break; case OP_CONFIG_BEACON_SET: if (size != 1 || pkt[0] > 0x01) return true; node_beacon_mode_set(node, !!pkt[0]); /* Fall Through */ case OP_CONFIG_BEACON_GET: if (opcode == OP_CONFIG_BEACON_GET && size != 0) return true; n = mesh_model_opcode_set(OP_CONFIG_BEACON_STATUS, msg); msg[n++] = node_beacon_mode_get(node); l_debug("Get/Set Config Beacon (%d)", msg[n-1]); break; case OP_CONFIG_FRIEND_SET: if (size != 1 || pkt[0] > 0x01) return true; node_friend_mode_set(node, !!pkt[0]); /* Fall Through */ case OP_CONFIG_FRIEND_GET: if (opcode == OP_CONFIG_FRIEND_GET && size != 0) return true; n = mesh_model_opcode_set(OP_CONFIG_FRIEND_STATUS, msg); msg[n++] = node_friend_mode_get(node); l_debug("Get/Set Friend (%d)", msg[n-1]); break; case OP_CONFIG_KEY_REFRESH_PHASE_SET: if (size != 3 || (pkt[2] != KEY_REFRESH_TRANS_THREE && pkt[2] != KEY_REFRESH_TRANS_TWO)) return true; /* Fall Through */ case OP_CONFIG_KEY_REFRESH_PHASE_GET: if (size != 2 && opcode == OP_CONFIG_KEY_REFRESH_PHASE_GET) return true; n = cfg_key_refresh_phase(node, pkt, opcode); break; case OP_APPKEY_ADD: case OP_APPKEY_UPDATE: if (size != 19) return true; /* Fall Through */ case OP_APPKEY_DELETE: if (opcode == OP_APPKEY_DELETE && size != 3) return true; n = cfg_appkey_msg(node, pkt, opcode); break; case OP_APPKEY_GET: if (size != 2) return true; n = cfg_get_appkeys_msg(node, pkt); break; case OP_NETKEY_ADD: case OP_NETKEY_UPDATE: if (size != 18) return true; /* Fall Through */ case OP_NETKEY_DELETE: if (opcode == OP_NETKEY_DELETE && size != 2) return true; n = cfg_netkey_msg(node, pkt, opcode); break; case OP_NETKEY_GET: if (size != 0) return true; n = mesh_model_opcode_set(OP_NETKEY_LIST, msg); size = MAX_MSG_LEN - n; if (mesh_net_key_list_get(net, msg + n, &size)) n += size; break; case OP_MODEL_APP_BIND: case OP_MODEL_APP_UNBIND: if (size != 6 && size != 8) return true; n = model_app_bind(node, pkt, size, opcode != OP_MODEL_APP_BIND); break; case OP_VEND_MODEL_APP_GET: if (size != 6) return true; n = model_app_list(node, pkt, size); break; case OP_MODEL_APP_GET: if (size != 4) return true; n = model_app_list(node, pkt, size); break; case OP_CONFIG_HEARTBEAT_PUB_SET: l_debug("Config Heartbeat Publication Set"); if (size != 9) return true; n = hb_publication_set(node, pkt); break; case OP_CONFIG_HEARTBEAT_PUB_GET: if (size != 0) return true; n = hb_publication_get(node, MESH_STATUS_SUCCESS); break; case OP_CONFIG_HEARTBEAT_SUB_SET: if (size != 5) return true; l_debug("Set HB Sub Period Log %2.2x", pkt[4]); n = hb_subscription_set(node, pkt); break; case OP_CONFIG_HEARTBEAT_SUB_GET: if (size != 0) return true; n = hb_subscription_get(node, MESH_STATUS_SUCCESS); break; case OP_CONFIG_POLL_TIMEOUT_GET: if (size != 2) return true; n = cfg_poll_timeout_msg(node, pkt); break; case OP_NODE_RESET: if (size != 0) return true; n = mesh_model_opcode_set(OP_NODE_RESET_STATUS, msg); /* Delay node removal to give it a chance to send the status */ l_idle_oneshot(node_reset, node, NULL); break; } if (n) mesh_model_send(node, dst, src, APP_IDX_DEV_LOCAL, net_idx, DEFAULT_TTL, false, n, msg); return true; } static void cfgmod_srv_unregister(void *user_data) { } static const struct mesh_model_ops ops = { .unregister = cfgmod_srv_unregister, .recv = cfg_srv_pkt, .bind = NULL, .sub = NULL, .pub = NULL }; void cfgmod_server_init(struct mesh_node *node, uint8_t ele_idx) { l_debug("%2.2x", ele_idx); mesh_model_register(node, ele_idx, CONFIG_SRV_MODEL, &ops, node); } bluez-5.87/mesh/PaxHeaders/mesh-main.conf0000644000000000000000000000005013622034624015336 xustar0020 atime=1770587295 20 ctime=1783094111 bluez-5.87/mesh/mesh-main.conf0000644000000000000000000000255113622034624015022 0ustar00rootroot[General] # Default setting for to indicate whether secure network beaconing # is enabled for a node whose Beacon state hasn't been configured # by a configuration client, i.e., for a newly provisioned, created # or imported node. # Defaults to true. #Beacon = true # Default setting for supporting relay. The setting applies # to all local nodes. # If the value is true, then a configuration client can either enable or disable # the relay feature per local node. # If the value is false, then the relay feature cannot be configured for # any local node. # Defaults to true. #Relay = true # Default setting for supporting Friendship. The setting applies # to all local nodes. # If the value is true, then a configuration client can either enable or disable # the Friendship feature per local node. # If the value is false, then the Friendship feature cannot be configured for # any local node. # Defaults to true. #Friendship = true # Default depth of replay protection list. This setting applies to # each individual node. # Valid range 1-65535. # Defaults to 100. #CRPL = 100 # Default size of friend queue: the number of messages that each Friend node can # store for the Low Power node. # Valid range: 0-32. # Defaults to 32. #FriendQueueSize = 32 # Provisioning timeout in seconds. # Setting this value to zero means there's no timeout. # Defaults to 60. #ProvTimeout = 60 bluez-5.87/mesh/PaxHeaders/mesh-mgmt.h0000644000000000000000000000005014447506754014676 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/mesh-mgmt.h0000644000000000000000000000146614447506754014366 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2019 SILVAIR sp. z o.o. All rights reserved. * * */ #include typedef void (*mesh_mgmt_read_info_func_t)(int index, bool added, bool powered, bool mesh, void *user_data); bool mesh_mgmt_list(mesh_mgmt_read_info_func_t cb, void *user_data); unsigned int mesh_mgmt_send(uint16_t opcode, uint16_t index, uint16_t length, const void *param, mgmt_request_func_t callback, void *user_data, mgmt_destroy_func_t destroy); unsigned int mesh_mgmt_register(uint16_t event, uint16_t index, mgmt_notify_func_t callback, void *user_data, mgmt_destroy_func_t destroy); bool mesh_mgmt_unregister(unsigned int id); void mesh_mgmt_destroy(void); void mesh_mgmt_clear(void); bluez-5.87/mesh/PaxHeaders/mesh-io-mgmt.h0000644000000000000000000000005014333256742015274 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/mesh-io-mgmt.h0000644000000000000000000000034214333256742014754 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ extern const struct mesh_io_api mesh_io_mgmt; bluez-5.87/mesh/PaxHeaders/provision.h0000644000000000000000000000005015063045254015014 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/provision.h0000644000000000000000000000736615063045254014511 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ /* * size: hard define (mesh.conf - OOB_NUMBEROOB_NUMBER) * oob size - 8 if alpha or numeric * else 1 if mask is non zero * else 0 */ struct bt_mesh; struct mesh_prov; struct mesh_agent; /* Provisioner Agent Response Types */ #define OOB_CANCEL 0x00 #define OOB_PRIV_KEY 0x01 #define OOB_PUB_KEY 0x02 #define OOB_NUMBER 0x03 #define OOB_STATIC 0x04 #define OOB_NUMBER_DISPLAY 0x05 /* Spec defined Provisioning message types */ #define PROV_INVITE 0x00 #define PROV_CAPS 0x01 #define PROV_START 0x02 #define PROV_PUB_KEY 0x03 #define PROV_INP_CMPLT 0x04 #define PROV_CONFIRM 0x05 #define PROV_RANDOM 0x06 #define PROV_DATA 0x07 #define PROV_COMPLETE 0x08 #define PROV_FAILED 0x09 #define PROV_NONE 0xFF /* Spec defined Error Codes */ #define PROV_ERR_SUCCESS 0x00 #define PROV_ERR_INVALID_PDU 0x01 #define PROV_ERR_INVALID_FORMAT 0x02 #define PROV_ERR_UNEXPECTED_PDU 0x03 #define PROV_ERR_CONFIRM_FAILED 0x04 #define PROV_ERR_INSUF_RESOURCE 0x05 #define PROV_ERR_DECRYPT_FAILED 0x06 #define PROV_ERR_UNEXPECTED_ERR 0x07 #define PROV_ERR_CANT_ASSIGN_ADDR 0x08 /* Internally generated Error Codes */ #define PROV_ERR_TIMEOUT 0xFF /* Provisioner Action values */ /* IN */ #define PROV_ACTION_PUSH 0x00 #define PROV_ACTION_TWIST 0x01 #define PROV_ACTION_IN_NUMERIC 0x02 #define PROV_ACTION_IN_ALPHA 0x03 /* OUT */ #define PROV_ACTION_BLINK 0x00 #define PROV_ACTION_BEEP 0x01 #define PROV_ACTION_VIBRATE 0x02 #define PROV_ACTION_OUT_NUMERIC 0x03 #define PROV_ACTION_OUT_ALPHA 0x04 /* OOB_Info defines from Table 3.54 of Mesh profile Specification v1.0 */ #define OOB_INFO_URI_HASH 0x0002 /* PB_REMOTE not supported from unprovisioned state */ #define PB_NPPI_00 0x00 #define PB_NPPI_01 0x01 #define PB_NPPI_02 0x02 #define PB_ADV 0x03 /* Internal only, and may be reassigned */ #define PB_GATT 0x04 /* Internal only, and may be reassigned */ #define PROV_FLAG_KR 0x01 #define PROV_FLAG_IVU 0x02 struct mesh_prov_node_info { uint32_t iv_index; uint16_t unicast; uint16_t net_index; uint8_t num_ele; uint8_t net_key[16]; uint8_t device_key[16]; uint8_t flags; /* IVU and KR bits */ }; typedef bool (*mesh_prov_acceptor_complete_func_t)(void *user_data, uint8_t status, const struct mesh_prov_node_info *info); typedef void (*mesh_prov_initiator_start_func_t)(void *user_data, int err); typedef bool (*mesh_prov_initiator_data_req_func_t)(void *user_data, uint8_t num_elem); typedef bool (*mesh_prov_initiator_complete_func_t)(void *user_data, uint8_t status, const struct mesh_prov_node_info *info); typedef void (*mesh_prov_initiator_scan_result_t)(void *user_data, uint16_t server, bool extended, const uint8_t *data, uint16_t len); /* This starts unprovisioned device beacon */ bool acceptor_start(uint8_t num_ele, uint8_t *uuid, uint16_t algorithms, uint32_t timeout, struct mesh_agent *agent, mesh_prov_acceptor_complete_func_t complete_cb, void *caller_data); void acceptor_cancel(void *user_data); bool initiator_start(uint8_t transport, uint16_t server, uint16_t svr_idx, uint8_t uuid[16], uint16_t max_ele, uint32_t timeout, /* in seconds from mesh.conf */ struct mesh_agent *agent, mesh_prov_initiator_start_func_t start_cb, mesh_prov_initiator_data_req_func_t data_req_cb, mesh_prov_initiator_complete_func_t complete_cb, void *node, void *caller_data); void initiator_prov_data(uint16_t net_idx, uint16_t primary, void *caller_data); void initiator_cancel(void *caller_data); void initiator_scan_reg(mesh_prov_initiator_scan_result_t scan_result, void *user_data); void initiator_scan_unreg(void *caller_data); bluez-5.87/mesh/PaxHeaders/mesh-io-generic.h0000644000000000000000000000005014015011623015724 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/mesh-io-generic.h0000644000000000000000000000034514015011623015407 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ extern const struct mesh_io_api mesh_io_generic; bluez-5.87/mesh/PaxHeaders/mesh-io-unit.h0000644000000000000000000000005014061461364015303 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/mesh-io-unit.h0000644000000000000000000000034214061461364014763 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2021 Intel Corporation. All rights reserved. * * */ extern const struct mesh_io_api mesh_io_unit; bluez-5.87/mesh/PaxHeaders/main.c0000644000000000000000000000005015063045254013703 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/main.c0000644000000000000000000001510715063045254013370 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2017-2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/mgmt.h" #include "mesh/mesh.h" #include "mesh/crypto.h" #include "mesh/dbus.h" #include "mesh/mesh-io.h" #include "mesh/util.h" static const char *storage_dir; static const char *mesh_conf_fname; static enum mesh_io_type io_type; static void *io_opts; static const struct option main_options[] = { { "io", required_argument, NULL, 'i' }, { "storage", required_argument, NULL, 's' }, { "config", required_argument, NULL, 'c' }, { "nodetach", no_argument, NULL, 'n' }, { "debug", no_argument, NULL, 'd' }, { "dbus-debug", no_argument, NULL, 'b' }, { "help", no_argument, NULL, 'h' }, { } }; static const char *io_usage = "\t(auto | generic:[hci] | unit:)\n" "\t\tauto - Use first available controller (MGMT or raw HCI)\n" "\t\tgeneric - Use raw HCI io on interface hci\n" "\t\tunit - Use test IO (for automatic testing only)\n"; static void usage(void) { fprintf(stderr, "Usage:\n" "\tbluetooth-meshd [options]\n"); fprintf(stderr, "Options:\n" "\t--io Use specified io (default: auto)\n" "\t--config Daemon configuration directory\n" "\t--storage Mesh node(s) configuration directory\n" "\t--nodetach Run in foreground\n" "\t--debug Enable debug output\n" "\t--dbus-debug Enable D-Bus debugging\n" "\t--help Show %s information\n", __func__); fprintf(stderr, "\n\t io: %s", io_usage); } static void do_debug(const char *str, void *user_data) { const char *prefix = user_data; l_info("%s%s", prefix, str); } static void mesh_ready_callback(void *user_data, bool success) { struct l_dbus *dbus = user_data; l_info("mesh_ready_callback"); if (!success) { l_error("Failed to start mesh"); l_main_quit(); return; } if (!dbus_init(dbus)) { l_error("Failed to initialize mesh D-Bus resources"); l_main_quit(); } } static void request_name_callback(struct l_dbus *dbus, bool success, bool queued, void *user_data) { if (!success && io_type != MESH_IO_TYPE_UNIT_TEST) { l_info("Request name failed"); l_main_quit(); return; } if (!mesh_init(storage_dir, mesh_conf_fname, io_type, io_opts, mesh_ready_callback, dbus)) { l_error("Failed to initialize mesh"); l_main_quit(); } } static void ready_callback(void *user_data) { struct l_dbus *dbus = user_data; l_info("D-Bus ready"); l_dbus_name_acquire(dbus, BLUEZ_MESH_NAME, false, false, false, request_name_callback, NULL); } static void disconnect_callback(void *user_data) { l_main_quit(); } static void kill_to(struct l_timeout *timeout, void *user_data) { l_timeout_remove(timeout); l_main_quit(); } static void signal_handler(uint32_t signo, void *user_data) { static bool terminated; if (terminated) return; l_info("Terminating"); mesh_cleanup(true); if (io_type != MESH_IO_TYPE_UNIT_TEST) l_timeout_create(1, kill_to, NULL, NULL); else l_main_quit(); terminated = true; } static bool parse_io(const char *optarg, enum mesh_io_type *type, void **opts) { if (strstr(optarg, "auto") == optarg) { int *index = l_new(int, 1); *type = MESH_IO_TYPE_AUTO; *opts = index; optarg += strlen("auto"); *index = MGMT_INDEX_NONE; return true; return false; } else if (strstr(optarg, "generic") == optarg) { int *index = l_new(int, 1); *type = MESH_IO_TYPE_GENERIC; *opts = index; optarg += strlen("generic"); if (!*optarg || *optarg != ':') return false; optarg++; if (sscanf(optarg, "hci%d", index) == 1) return true; if (sscanf(optarg, "%d", index) == 1) return true; return false; } else if (strstr(optarg, "unit") == optarg) { char *test_path; *type = MESH_IO_TYPE_UNIT_TEST; optarg += strlen("unit"); if (*optarg != ':') return false; optarg++; test_path = strdup(optarg); *opts = test_path; return true; } return false; } int main(int argc, char *argv[]) { int status; bool detached = true; bool dbus_debug = false; struct l_dbus *dbus = NULL; char *io = NULL; int hci_index; if (!l_main_init()) return -1; l_log_set_stderr(); if (!mesh_crypto_check_avail()) { l_error("Mesh Crypto functions unavailable"); status = l_main_run_with_signal(signal_handler, NULL); goto done; } for (;;) { int opt; opt = getopt_long(argc, argv, "u:i:s:c:ndbh", main_options, NULL); if (opt < 0) break; switch (opt) { case 'u': if (sscanf(optarg, "%d", &hci_index) == 1 || sscanf(optarg, "%d", &hci_index) == 1) io = l_strdup_printf("unit:%d", hci_index); else io = l_strdup(optarg); break; case 'i': if (sscanf(optarg, "hci%d", &hci_index) == 1 || sscanf(optarg, "%d", &hci_index) == 1) io = l_strdup_printf("generic:%s", optarg); else io = l_strdup(optarg); break; case 'n': detached = false; break; case 'd': enable_debug(); break; case 's': storage_dir = optarg; break; case 'c': mesh_conf_fname = optarg; break; case 'b': dbus_debug = true; break; case 'h': usage(); status = EXIT_SUCCESS; goto done; default: usage(); status = EXIT_FAILURE; goto done; } } if (!io) io = l_strdup_printf("auto"); if (!parse_io(io, &io_type, &io_opts)) { l_error("Invalid io: %s\n%s", io, io_usage); status = EXIT_FAILURE; goto done; } l_free(io); io = NULL; if (!detached) umask(0077); if (io_type != MESH_IO_TYPE_UNIT_TEST) dbus = l_dbus_new_default(L_DBUS_SYSTEM_BUS); else { dbus = l_dbus_new_default(L_DBUS_SESSION_BUS); prctl(PR_SET_PDEATHSIG, SIGSEGV); } if (!dbus) { l_error("unable to connect to D-Bus"); status = EXIT_FAILURE; goto done; } if (dbus_debug) l_dbus_set_debug(dbus, do_debug, "[DBUS] ", NULL); l_dbus_set_ready_handler(dbus, ready_callback, dbus, NULL); l_dbus_set_disconnect_handler(dbus, disconnect_callback, NULL, NULL); if (!l_dbus_object_manager_enable(dbus, "/")) { l_error("Failed to enable Object Manager"); status = EXIT_FAILURE; goto done; } status = l_main_run_with_signal(signal_handler, NULL); done: l_free(io); l_free(io_opts); mesh_cleanup(false); l_dbus_destroy(dbus); l_main_exit(); return status; } bluez-5.87/mesh/PaxHeaders/util.c0000644000000000000000000000005015221752714013736 xustar0020 atime=1783092684 20 ctime=1783094103 bluez-5.87/mesh/util.c0000644000000000000000000000603015221752714013416 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "mesh/util.h" static bool debug_enabled; void print_packet(const char *label, const void *data, uint16_t size) { struct timeval pkt_time; if (!debug_enabled) return; gettimeofday(&pkt_time, NULL); if (size > 0) { char *str; str = l_util_hexstring(data, size); l_debug("%05d.%03d %s: %s", (uint32_t) pkt_time.tv_sec % 100000, (uint32_t) pkt_time.tv_usec/1000, label, str); l_free(str); } else l_debug("%05d.%03d %s: empty", (uint32_t) pkt_time.tv_sec % 100000, (uint32_t) pkt_time.tv_usec/1000, label); } uint32_t get_timestamp_secs(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec; } bool str2hex(const char *str, uint16_t in_len, uint8_t *out, uint16_t out_len) { uint16_t i; if (in_len < out_len * 2) return false; for (i = 0; i < out_len; i++) { if (sscanf(&str[i * 2], "%02hhx", &out[i]) != 1) return false; } return true; } size_t hex2str(const uint8_t *in, size_t in_len, char *out, size_t out_len) { static const char hexdigits[] = "0123456789abcdef"; size_t i; if (in_len * 2 > (out_len - 1)) return 0; for (i = 0; i < in_len; i++) { out[i * 2] = hexdigits[in[i] >> 4]; out[i * 2 + 1] = hexdigits[in[i] & 0xf]; } out[in_len * 2] = '\0'; return i; } int create_dir(const char *dir_name) { struct stat st; const char *prev, *next; char dir[PATH_MAX + 1]; int err; err = stat(dir_name, &st); if (!err && S_ISREG(st.st_mode)) return 0; memset(dir, 0, PATH_MAX + 1); strcat(dir, "/"); prev = strchr(dir_name, '/'); while (prev) { next = strchr(prev + 1, '/'); if (!next) break; if (next - prev == 1) { prev = next; continue; } strncat(dir, prev + 1, next - prev); if (mkdir(dir, 0755) != 0 && errno != EEXIST) l_error("Failed to create dir(%d): %s", errno, dir); prev = next; } if (mkdir(dir_name, 0755) != 0 && errno != EEXIST) l_error("Failed to create dir(%d): %s", errno, dir_name); return 0; } static int del_fobject(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { switch (typeflag) { case FTW_DP: rmdir(fpath); l_debug("RMDIR %s", fpath); break; case FTW_SL: default: if (remove(fpath) < 0) l_error("Failed to remove(%d): %s", errno, fpath); l_debug("RM %s", fpath); break; } return 0; } void del_path(const char *path) { nftw(path, del_fobject, 5, FTW_DEPTH | FTW_PHYS); } void enable_debug(void) { debug_enabled = true; l_debug_enable("*"); } #if !HAVE_DECL_BASENAME #include const char *mesh_basename(const char *path) { const char *base = strrchr(path, '/'); return base ? base + 1 : path; } #endif bluez-5.87/mesh/PaxHeaders/prov-initiator.c0000644000000000000000000000005015200607060015735 xustar0020 atime=1783092683 20 ctime=1783094103 bluez-5.87/mesh/prov-initiator.c0000644000000000000000000006623315200607060015430 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "src/shared/ad.h" #include "src/shared/ecc.h" #include "src/shared/util.h" #include "mesh/mesh-defs.h" #include "mesh/util.h" #include "mesh/crypto.h" #include "mesh/net.h" #include "mesh/node.h" #include "mesh/model.h" #include "mesh/keyring.h" #include "mesh/prov.h" #include "mesh/provision.h" #include "mesh/pb-adv.h" #include "mesh/remprv.h" #include "mesh/mesh.h" #include "mesh/agent.h" #include "mesh/error.h" /* Quick size sanity check */ static const uint16_t expected_pdu_size[] = { 2, /* PROV_INVITE */ 12, /* PROV_CAPS */ 6, /* PROV_START */ 65, /* PROV_PUB_KEY */ 1, /* PROV_INP_CMPLT */ 17, /* PROV_CONFIRM */ 17, /* PROV_RANDOM */ 34, /* PROV_DATA */ 1, /* PROV_COMPLETE */ 2, /* PROV_FAILED */ }; #define BEACON_TYPE_UNPROVISIONED 0x00 static const uint8_t pkt_filter = BT_AD_MESH_PROV; enum int_state { INT_PROV_IDLE = 0, INT_PROV_INVITE_SENT, INT_PROV_INVITE_ACKED, INT_PROV_START_SENT, INT_PROV_START_ACKED, INT_PROV_KEY_SENT, INT_PROV_KEY_ACKED, INT_PROV_CONF_SENT, INT_PROV_CONF_ACKED, INT_PROV_RAND_SENT, INT_PROV_RAND_ACKED, INT_PROV_DATA_SENT, INT_PROV_DATA_ACKED, }; #define MAT_REMOTE_PUBLIC 0x01 #define MAT_LOCAL_PRIVATE 0x02 #define MAT_RAND_AUTH 0x04 #define MAT_SECRET (MAT_REMOTE_PUBLIC | MAT_LOCAL_PRIVATE) struct mesh_prov_initiator { mesh_prov_initiator_start_func_t start_cb; mesh_prov_initiator_complete_func_t complete_cb; mesh_prov_initiator_data_req_func_t data_req_cb; prov_trans_tx_t trans_tx; struct mesh_agent *agent; void *caller_data; void *trans_data; struct mesh_node *node; struct l_timeout *timeout; uint32_t to_secs; enum int_state state; uint16_t net_idx; uint16_t svr_idx; uint16_t unicast; uint16_t server; uint8_t transport; uint8_t material; uint8_t expected; int8_t previous; uint8_t out_num; uint8_t rpr_state; struct conf_input conf_inputs; uint8_t calc_key[16]; uint8_t salt[16]; uint8_t confirm[16]; uint8_t s_key[16]; uint8_t s_nonce[13]; uint8_t private_key[32]; uint8_t secret[32]; uint8_t rand_auth_workspace[48]; uint8_t uuid[16]; }; struct scan_req { mesh_prov_initiator_scan_result_t scan_result; struct mesh_node *node; int count; }; static struct mesh_prov_initiator *prov = NULL; static struct l_queue *scans; static void initiator_free(void) { if (prov) { l_timeout_remove(prov->timeout); if (!prov->server) mesh_send_cancel(&pkt_filter, sizeof(pkt_filter)); } pb_adv_unreg(prov); l_free(prov); prov = NULL; } static void int_prov_close(void *user_data, uint8_t reason) { struct mesh_prov_initiator *prov = user_data; struct mesh_prov_node_info info; uint8_t msg[4]; int n; if (prov->server) { n = mesh_model_opcode_set(OP_REM_PROV_LINK_CLOSE, msg); msg[n++] = reason == PROV_ERR_SUCCESS ? 0x00 : 0x02; mesh_model_send(prov->node, 0, prov->server, APP_IDX_DEV_REMOTE, prov->svr_idx, DEFAULT_TTL, true, n, msg); } if (reason != PROV_ERR_SUCCESS) { prov->complete_cb(prov->caller_data, reason, NULL); initiator_free(); return; } memcpy(info.device_key, prov->calc_key, 16); info.net_index = prov->net_idx; info.unicast = prov->unicast; info.num_ele = prov->conf_inputs.caps.num_ele; prov->complete_cb(prov->caller_data, PROV_ERR_SUCCESS, &info); initiator_free(); } static void swap_u256_bytes(uint8_t *u256) { int i; /* End-to-End byte reflection of 32 octet buffer */ for (i = 0; i < 16; i++) { u256[i] ^= u256[31 - i]; u256[31 - i] ^= u256[i]; u256[i] ^= u256[31 - i]; } } static void int_prov_open(void *user_data, prov_trans_tx_t trans_tx, void *trans_data, uint8_t transport) { struct mesh_prov_initiator *rx_prov = user_data; struct prov_invite_msg msg = { PROV_INVITE, { 30 }}; /* Only one provisioning session may be open at a time */ if (rx_prov != prov) return; /* Only one provisioning session may be open at a time */ if (prov->trans_tx && prov->trans_tx != trans_tx && prov->transport != transport) return; /* We only care here if transport does *not* match */ if (transport != prov->transport) return; /* Always use an ephemeral key when Initiator */ ecc_make_key(prov->conf_inputs.prv_pub_key, prov->private_key); swap_u256_bytes(prov->conf_inputs.prv_pub_key); swap_u256_bytes(prov->conf_inputs.prv_pub_key + 32); prov->material |= MAT_LOCAL_PRIVATE; prov->trans_tx = trans_tx; prov->trans_data = trans_data; prov->state = INT_PROV_INVITE_SENT; prov->expected = PROV_CAPS; prov->conf_inputs.invite.attention = msg.invite.attention; prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); return; } static bool prov_calc_secret(const uint8_t *pub, const uint8_t *priv, uint8_t *secret) { uint8_t tmp[64]; /* Convert to ECC byte order */ memcpy(tmp, pub, 64); swap_u256_bytes(tmp); swap_u256_bytes(tmp + 32); if (!ecdh_shared_secret(tmp, priv, secret)) return false; /* Convert to Mesh byte order */ swap_u256_bytes(secret); return true; } static bool int_credentials(struct mesh_prov_initiator *prov) { if (!memcmp(prov->conf_inputs.prv_pub_key, prov->conf_inputs.dev_pub_key, 64)) return false; if (!prov_calc_secret(prov->conf_inputs.dev_pub_key, prov->private_key, prov->secret)) return false; if (!mesh_crypto_s1(&prov->conf_inputs, sizeof(prov->conf_inputs), prov->salt)) return false; if (!mesh_crypto_prov_conf_key(prov->secret, prov->salt, prov->calc_key)) return false; l_getrandom(prov->rand_auth_workspace, 16); print_packet("PublicKeyProv", prov->conf_inputs.prv_pub_key, 64); print_packet("PublicKeyDev", prov->conf_inputs.dev_pub_key, 64); /* Print DBG out in Mesh order */ swap_u256_bytes(prov->private_key); print_packet("PrivateKeyLocal", prov->private_key, 32); print_packet("ConfirmationInputs", &prov->conf_inputs, sizeof(prov->conf_inputs)); print_packet("ECDHSecret", prov->secret, 32); print_packet("LocalRandom", prov->rand_auth_workspace, 16); print_packet("ConfirmationSalt", prov->salt, 16); print_packet("ConfirmationKey", prov->calc_key, 16); return true; } static uint8_t u16_high_bit(uint16_t mask) { uint8_t cnt = 0; if (!mask) return 0xff; while (mask & 0xfffe) { cnt++; mask >>= 1; } return cnt; } static uint32_t digit_mod(uint8_t power) { uint32_t ret = 1; while (power--) ret *= 10; return ret; } static void calc_local_material(const uint8_t *random) { /* Calculate SessionKey while the data is fresh */ mesh_crypto_prov_prov_salt(prov->salt, prov->rand_auth_workspace, random, prov->salt); mesh_crypto_session_key(prov->secret, prov->salt, prov->s_key); mesh_crypto_nonce(prov->secret, prov->salt, prov->s_nonce); print_packet("SessionKey", prov->s_key, sizeof(prov->s_key)); print_packet("Nonce", prov->s_nonce, sizeof(prov->s_nonce)); } static void send_confirm(struct mesh_prov_initiator *prov) { struct prov_conf_msg msg; msg.opcode = PROV_CONFIRM; mesh_crypto_aes_cmac(prov->calc_key, prov->rand_auth_workspace, 32, msg.conf); memcpy(prov->confirm, msg.conf, sizeof(prov->confirm)); prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); prov->state = INT_PROV_CONF_SENT; prov->expected = PROV_CONFIRM; } static void number_cb(void *user_data, int err, uint32_t number) { struct mesh_prov_initiator *rx_prov = user_data; struct prov_fail_msg msg; if (prov != rx_prov) return; if (err) { msg.opcode = PROV_FAILED; msg.reason = PROV_ERR_UNEXPECTED_ERR; prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); return; } /* Save two copies, to generate two confirmation values */ l_put_be32(number, prov->rand_auth_workspace + 28); l_put_be32(number, prov->rand_auth_workspace + 44); prov->material |= MAT_RAND_AUTH; send_confirm(prov); } static void static_cb(void *user_data, int err, uint8_t *key, uint32_t len) { struct mesh_prov_initiator *rx_prov = user_data; struct prov_fail_msg msg; if (prov != rx_prov) return; if (err || !key || len != 16) { msg.opcode = PROV_FAILED; msg.reason = PROV_ERR_UNEXPECTED_ERR; prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); return; } memcpy(prov->rand_auth_workspace + 16, key, 16); memcpy(prov->rand_auth_workspace + 32, key, 16); prov->material |= MAT_RAND_AUTH; send_confirm(prov); } static void send_pub_key(struct mesh_prov_initiator *prov) { struct prov_pub_key_msg msg; msg.opcode = PROV_PUB_KEY; memcpy(msg.pub_key, prov->conf_inputs.prv_pub_key, 64); prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); prov->state = INT_PROV_KEY_SENT; } static void pub_key_cb(void *user_data, int err, uint8_t *key, uint32_t len) { struct mesh_prov_initiator *rx_prov = user_data; struct prov_fail_msg msg; uint8_t fail_code[2]; if (prov != rx_prov) return; if (err || !key || len != 64) { msg.opcode = PROV_FAILED; msg.reason = PROV_ERR_UNEXPECTED_ERR; prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); return; } memcpy(prov->conf_inputs.dev_pub_key, key, 64); prov->material |= MAT_REMOTE_PUBLIC; if ((prov->material & MAT_SECRET) == MAT_SECRET) { if (!int_credentials(prov)) { fail_code[0] = PROV_FAILED; fail_code[1] = PROV_ERR_UNEXPECTED_ERR; prov->trans_tx(prov->trans_data, fail_code, 2); int_prov_close(prov, fail_code[1]); return; } } send_pub_key(prov); } static void send_random(struct mesh_prov_initiator *prov) { struct prov_rand_msg msg; msg.opcode = PROV_RANDOM; memcpy(msg.rand, prov->rand_auth_workspace, sizeof(msg.rand)); prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); prov->state = INT_PROV_RAND_SENT; prov->expected = PROV_RANDOM; } void initiator_prov_data(uint16_t net_idx, uint16_t primary, void *caller_data) { struct prov_data_msg prov_data; struct prov_fail_msg prov_fail; struct keyring_net_key key; struct mesh_net *net; uint32_t iv_index; uint8_t snb_flags; if (!prov || caller_data != prov->caller_data) return; if (prov->state != INT_PROV_RAND_ACKED) return; net = node_get_net(prov->node); prov->expected = PROV_COMPLETE; /* Calculate remote device key */ mesh_crypto_device_key(prov->secret, prov->salt, prov->calc_key); print_packet("DevKey", prov->calc_key, 16); /* Fill Prov Data Structure */ if (!keyring_get_net_key(prov->node, net_idx, &key)) { prov_fail.reason = PROV_ERR_UNEXPECTED_ERR; goto failure; } prov->unicast = primary; prov->net_idx = net_idx; mesh_net_get_snb_state(net, &snb_flags, &iv_index); prov_data.opcode = PROV_DATA; if (key.phase == KEY_REFRESH_PHASE_TWO) { memcpy(&prov_data.data.net_key, key.new_key, 16); snb_flags |= PROV_FLAG_KR; } else memcpy(&prov_data.data.net_key, key.old_key, 16); l_put_be16(net_idx, &prov_data.data.net_idx); l_put_u8(snb_flags, &prov_data.data.flags); l_put_be32(iv_index, &prov_data.data.iv_index); l_put_be16(primary, &prov_data.data.primary); print_packet("ProvData", &prov_data.data, sizeof(prov_data.data)); /* Encrypt Prov Data */ mesh_crypto_aes_ccm_encrypt(prov->s_nonce, prov->s_key, NULL, 0, &prov_data.data, sizeof(prov_data.data), &prov_data.data, sizeof(prov_data.mic)); print_packet("EncdData", &prov_data.data, sizeof(prov_data) - 1); prov->trans_tx(prov->trans_data, &prov_data, sizeof(prov_data)); prov->state = INT_PROV_DATA_SENT; return; failure: l_debug("Failing... %d", prov_fail.reason); prov_fail.opcode = PROV_FAILED; prov->trans_tx(prov->trans_data, &prov_fail, sizeof(prov_fail)); /* TODO: Call Complete Callback (Fail)*/ } static void get_random_key(struct mesh_prov_initiator *prov, uint8_t action, uint8_t size) { uint32_t oob_key; int i; if (action >= PROV_ACTION_IN_ALPHA) { uint8_t alpha; char tmp[17]; memset(tmp, 0, sizeof(tmp)); if (size > 16) size = 16; /* Create random alphanumeric string made of 0-9, a-z, A-Z */ for (i = 0; i < size; i++) { l_getrandom(&alpha, sizeof(alpha)); alpha %= (10 + 26 + 26); if (alpha < 10) alpha += '0'; else if (alpha < 10 + 26) alpha += 'a' - 10; else alpha += 'A' - 10 - 26; tmp[i] = (char) alpha; } memcpy(prov->rand_auth_workspace + 16, tmp, size); memcpy(prov->rand_auth_workspace + 32, tmp, size); return; } l_getrandom(&oob_key, sizeof(oob_key)); if (action <= PROV_ACTION_TWIST) oob_key %= size; else oob_key %= digit_mod(size); if (!oob_key) oob_key = size; /* Save two copies, for two confirmation values */ l_put_be32(oob_key, prov->rand_auth_workspace + 28); l_put_be32(oob_key, prov->rand_auth_workspace + 44); } static void int_prov_auth(void) { uint8_t fail_code[2]; uint32_t oob_key; prov->state = INT_PROV_KEY_ACKED; l_debug("auth_method: %d", prov->conf_inputs.start.auth_method); memset(prov->rand_auth_workspace + 16, 0, 32); switch (prov->conf_inputs.start.auth_method) { default: case 0: /* Auth Type 3c - No OOB */ prov->material |= MAT_RAND_AUTH; break; case 1: /* Auth Type 3c - Static OOB */ /* Prompt Agent for Static OOB */ fail_code[1] = mesh_agent_request_static(prov->agent, static_cb, prov); if (fail_code[1]) goto failure; break; case 2: /* Auth Type 3a - Output OOB */ /* Prompt Agent for Output OOB */ if (prov->conf_inputs.start.auth_action == PROV_ACTION_OUT_ALPHA) { fail_code[1] = mesh_agent_prompt_alpha( prov->agent, true, static_cb, prov); } else { fail_code[1] = mesh_agent_prompt_number( prov->agent, true, prov->conf_inputs.start.auth_action, number_cb, prov); } if (fail_code[1]) goto failure; break; case 3: /* Auth Type 3b - input OOB */ get_random_key(prov, prov->conf_inputs.start.auth_action, prov->conf_inputs.start.auth_size); oob_key = l_get_be32(prov->rand_auth_workspace + 28); /* Ask Agent to Display random key */ if (prov->conf_inputs.start.auth_action == PROV_ACTION_IN_ALPHA) { fail_code[1] = mesh_agent_display_string( prov->agent, (char *) prov->rand_auth_workspace + 16, NULL, prov); } else { fail_code[1] = mesh_agent_display_number( prov->agent, true, prov->conf_inputs.start.auth_action, oob_key, NULL, prov); } if (fail_code[1]) goto failure; break; } if (prov->material & MAT_RAND_AUTH) send_confirm(prov); return; failure: l_debug("Failing... %d", fail_code[1]); fail_code[0] = PROV_FAILED; prov->trans_tx(prov->trans_data, fail_code, 2); int_prov_close(prov, fail_code[1]); } static void int_prov_start_auth(const struct mesh_agent_prov_caps *prov_caps, const struct mesh_net_prov_caps *dev_caps, struct prov_start *start) { uint8_t pub_type = prov_caps->pub_type & dev_caps->pub_type; uint8_t static_type = prov_caps->static_type & dev_caps->static_type; uint16_t output_action = prov_caps->output_action & L_BE16_TO_CPU(dev_caps->output_action); uint8_t output_size = MIN(prov_caps->output_size, dev_caps->output_size); uint16_t input_action = prov_caps->input_action & L_BE16_TO_CPU(dev_caps->input_action); uint8_t input_size = MIN(prov_caps->input_size, dev_caps->input_size); if (pub_type) start->pub_key = 0x01; /* Parse OOB Options, prefer static, then out, then in */ if (static_type) { start->auth_method = 0x01; return; } if (output_size && output_action) { start->auth_method = 0x02; start->auth_action = u16_high_bit(output_action); start->auth_size = MIN(output_size, 8); return; } if (input_size && input_action) { start->auth_method = 0x03; start->auth_action = u16_high_bit(input_action); start->auth_size = MIN(input_size, 8); return; } } static void int_prov_rx(void *user_data, const void *dptr, uint16_t len) { struct mesh_prov_initiator *rx_prov = user_data; const uint8_t *data = dptr; uint8_t *out; uint8_t type = *data++; uint8_t fail_code[2]; if (rx_prov != prov || !prov->trans_tx) return; l_debug("Provisioning packet received type: %2.2x (%u octets)", type, len); if (type == prov->previous) { l_error("Ignore repeated %2.2x packet", type); return; } else if (type > prov->expected || type < prov->previous) { l_error("Expected %2.2x, Got:%2.2x", prov->expected, type); fail_code[1] = PROV_ERR_UNEXPECTED_PDU; goto failure; } if (type >= L_ARRAY_SIZE(expected_pdu_size)) { l_error("Invalid PDU type %2.2x", type); fail_code[1] = PROV_ERR_INVALID_FORMAT; goto failure; } if (len != expected_pdu_size[type]) { l_error("Expected PDU size %d, Got %d (type: %2.2x)", expected_pdu_size[type], len, type); fail_code[1] = PROV_ERR_INVALID_FORMAT; goto failure; } switch (type) { case PROV_CAPS: /* Capabilities */ prov->state = INT_PROV_INVITE_ACKED; memcpy(&prov->conf_inputs.caps, data, sizeof(prov->conf_inputs.caps)); l_debug("Got Num Ele %d", data[0]); l_debug("Got alg %4.4x", l_get_be16(data + 1)); l_debug("Got pub_type %d", data[3]); l_debug("Got static_type %d", data[4]); l_debug("Got output_size %d", data[5]); l_debug("Got output_action %d", l_get_be16(data + 6)); l_debug("Got input_size %d", data[8]); l_debug("Got input_action %d", l_get_be16(data + 9)); if (!(l_get_be16(data + 1) & 0x0001)) { l_error("Unsupported Algorithm"); fail_code[1] = PROV_ERR_INVALID_FORMAT; goto failure; } /* * Select auth mechanism from methods supported by both * parties. */ int_prov_start_auth(mesh_agent_get_caps(prov->agent), &prov->conf_inputs.caps, &prov->conf_inputs.start); if (prov->conf_inputs.start.pub_key == 0x01) { prov->expected = PROV_CONFIRM; /* Prompt Agent for remote Public Key */ mesh_agent_request_public_key(prov->agent, pub_key_cb, prov); /* Nothing else for us to do now */ } else prov->expected = PROV_PUB_KEY; out = l_malloc(1 + sizeof(prov->conf_inputs.start)); out[0] = PROV_START; memcpy(out + 1, &prov->conf_inputs.start, sizeof(prov->conf_inputs.start)); prov->state = INT_PROV_START_SENT; prov->trans_tx(prov->trans_data, out, sizeof(prov->conf_inputs.start) + 1); l_free(out); break; case PROV_PUB_KEY: /* Public Key */ /* If we expected Pub Key Out-Of-Band, then fail */ if (prov->conf_inputs.start.pub_key) { fail_code[1] = PROV_ERR_INVALID_PDU; goto failure; } memcpy(prov->conf_inputs.dev_pub_key, data, 64); prov->material |= MAT_REMOTE_PUBLIC; prov->expected = PROV_CONFIRM; if ((prov->material & MAT_SECRET) != MAT_SECRET) return; if (!int_credentials(prov)) { fail_code[1] = PROV_ERR_UNEXPECTED_ERR; goto failure; } int_prov_auth(); break; case PROV_INP_CMPLT: /* Provisioning Input Complete */ /* TODO: Cancel Agent prompt */ prov->material |= MAT_RAND_AUTH; send_confirm(prov); break; case PROV_CONFIRM: /* Confirmation */ prov->state = INT_PROV_CONF_ACKED; /* RXed Device Confirmation */ /* Disallow echoed values */ if (!memcmp(prov->confirm, data, 16)) { fail_code[1] = PROV_ERR_INVALID_PDU; goto failure; } memcpy(prov->confirm, data, 16); print_packet("ConfirmationDevice", prov->confirm, 16); send_random(prov); break; case PROV_RANDOM: /* Random */ prov->state = INT_PROV_RAND_ACKED; /* Disallow matching random values */ if (!memcmp(prov->rand_auth_workspace, data, 16)) { fail_code[1] = PROV_ERR_INVALID_PDU; goto failure; } /* RXed Device Confirmation */ calc_local_material(data); memcpy(prov->rand_auth_workspace + 16, data, 16); print_packet("RandomDevice", data, 16); mesh_crypto_aes_cmac(prov->calc_key, prov->rand_auth_workspace + 16, 32, prov->rand_auth_workspace); print_packet("Dev-Conf", prov->rand_auth_workspace, 16); if (memcmp(prov->rand_auth_workspace, prov->confirm, 16)) { l_error("Provisioning Failed-Confirm compare"); fail_code[1] = PROV_ERR_CONFIRM_FAILED; goto failure; } if (prov->transport == PB_NPPI_00 || prov->transport == PB_NPPI_02) { /* No App data needed */ initiator_prov_data(prov->svr_idx, prov->server, prov->caller_data); } else if (!prov->data_req_cb(prov->caller_data, prov->conf_inputs.caps.num_ele)) { l_error("Provisioning Failed-Data Get"); fail_code[1] = PROV_ERR_CANT_ASSIGN_ADDR; goto failure; } break; case PROV_COMPLETE: /* Complete */ l_debug("Provisioning Complete"); prov->state = INT_PROV_IDLE; int_prov_close(prov, PROV_ERR_SUCCESS); break; case PROV_FAILED: /* Failed */ l_error("Provisioning Failed (reason: %d)", data[0]); prov->state = INT_PROV_IDLE; int_prov_close(prov, data[0]); break; default: l_error("Unknown Pkt %2.2x", type); fail_code[1] = PROV_ERR_UNEXPECTED_PDU; goto failure; } if (prov) prov->previous = type; return; failure: l_debug("Failing... %d", fail_code[1]); fail_code[0] = PROV_FAILED; prov->trans_tx(prov->trans_data, fail_code, 2); int_prov_close(prov, fail_code[1]); } static void int_prov_ack(void *user_data, uint8_t msg_num) { struct mesh_prov_initiator *rx_prov = user_data; if (rx_prov != prov || !prov->trans_tx) return; switch (prov->state) { case INT_PROV_START_SENT: prov->state = INT_PROV_START_ACKED; if (!prov->conf_inputs.start.pub_key) send_pub_key(prov); break; case INT_PROV_DATA_SENT: prov->state = INT_PROV_DATA_ACKED; break; case INT_PROV_KEY_SENT: if (prov->conf_inputs.start.pub_key) int_prov_auth(); break; case INT_PROV_IDLE: case INT_PROV_INVITE_SENT: case INT_PROV_INVITE_ACKED: case INT_PROV_START_ACKED: case INT_PROV_KEY_ACKED: case INT_PROV_CONF_SENT: case INT_PROV_CONF_ACKED: case INT_PROV_RAND_SENT: case INT_PROV_RAND_ACKED: case INT_PROV_DATA_ACKED: default: break; } } static void initiator_open_cb(void *user_data, int err) { uint8_t msg[20]; int n; bool result; if (!prov) return; if (err != MESH_ERROR_NONE) goto fail; if (prov->server) { n = mesh_model_opcode_set(OP_REM_PROV_LINK_OPEN, msg); if (prov->transport <= PB_NPPI_02) { msg[n++] = prov->transport; } else { memcpy(msg + n, prov->uuid, 16); n += 16; } result = mesh_model_send(prov->node, 0, prov->server, APP_IDX_DEV_REMOTE, prov->svr_idx, DEFAULT_TTL, true, n, msg); } else { /* Always register for PB-ADV */ result = pb_adv_reg(true, int_prov_open, int_prov_close, int_prov_rx, int_prov_ack, prov->uuid, prov); } if (!result) { err = MESH_ERROR_FAILED; goto fail; } prov->start_cb(prov->caller_data, MESH_ERROR_NONE); return; fail: prov->start_cb(prov->caller_data, err); initiator_free(); } static void initiate_to(struct l_timeout *timeout, void *user_data) { struct mesh_prov_initiator *rx_prov = user_data; if (rx_prov != prov) { l_timeout_remove(timeout); return; } int_prov_close(user_data, PROV_ERR_TIMEOUT); } bool initiator_start(uint8_t transport, uint16_t server, uint16_t svr_idx, uint8_t uuid[16], uint16_t max_ele, uint32_t timeout, struct mesh_agent *agent, mesh_prov_initiator_start_func_t start_cb, mesh_prov_initiator_data_req_func_t data_req_cb, mesh_prov_initiator_complete_func_t complete_cb, void *node, void *caller_data) { /* Invoked from Add() method in mesh-api.txt, to add a * remote unprovisioned device network. */ if (prov) return false; prov = l_new(struct mesh_prov_initiator, 1); prov->to_secs = timeout; prov->node = node; prov->agent = agent; prov->complete_cb = complete_cb; prov->start_cb = start_cb; prov->data_req_cb = data_req_cb; prov->caller_data = caller_data; prov->previous = -1; prov->server = server; prov->svr_idx = svr_idx; prov->transport = transport; prov->timeout = l_timeout_create(timeout, initiate_to, prov, NULL); memcpy(prov->uuid, uuid, 16); mesh_agent_refresh(prov->agent, initiator_open_cb, prov); return true; } void initiator_cancel(void *user_data) { initiator_free(); } static void rpr_tx(void *user_data, const void *data, uint16_t len) { struct mesh_prov_initiator *prov = user_data; uint8_t msg[72]; int n; n = mesh_model_opcode_set(OP_REM_PROV_PDU_SEND, msg); msg[n++] = ++prov->out_num; memcpy(msg + n, data, len); l_debug("Send OB %2.2x, with packet type %d", msg[n], prov->out_num); n += len; prov->rpr_state = PB_REMOTE_STATE_OB_PKT_TX; mesh_model_send(prov->node, 0, prov->server, APP_IDX_DEV_REMOTE, prov->svr_idx, DEFAULT_TTL, true, n, msg); } static bool match_req_node(const void *a, const void *b) { const struct scan_req *req = a; const struct mesh_node *node = b; return req->node == node; } static bool remprv_cli_pkt(uint16_t src, uint16_t unicast, uint16_t app_idx, uint16_t net_idx, const uint8_t *data, uint16_t size, const void *user_data) { struct mesh_node *node = (struct mesh_node *) user_data; const uint8_t *pkt = data; struct scan_req *req; uint32_t opcode; uint16_t n; if (mesh_model_opcode_get(pkt, size, &opcode, &n)) { size -= n; pkt += n; } else return false; if (opcode < OP_REM_PROV_SCAN_CAP_GET || opcode > OP_REM_PROV_PDU_REPORT) return false; if (app_idx != APP_IDX_DEV_REMOTE && app_idx != APP_IDX_DEV_LOCAL) return true; /* Local Dev key only allowed for Loop-backs */ if (app_idx == APP_IDX_DEV_LOCAL && unicast != src) return true; if (prov && (prov->server != src || prov->node != node)) return true; n = 0; switch (opcode) { default: return false; /* Provisioning Opcodes */ case OP_REM_PROV_LINK_STATUS: if (size != 2 || !prov) break; if (pkt[0] == PB_REM_ERR_SUCCESS) prov->rpr_state = pkt[1]; break; case OP_REM_PROV_LINK_REPORT: if (size != 2 || !prov) return true; if (pkt[0] != PB_REM_ERR_SUCCESS) { if (pkt[0] == PB_REM_ERR_CLOSED_BY_DEVICE || pkt[0] == PB_REM_ERR_CLOSED_BY_SERVER) int_prov_close(prov, pkt[1]); break; } if (prov->rpr_state == PB_REMOTE_STATE_LINK_OPENING) int_prov_open(prov, rpr_tx, prov, prov->transport); else if (prov->rpr_state == PB_REMOTE_STATE_LINK_CLOSING) { prov->rpr_state = PB_REMOTE_STATE_IDLE; int_prov_close(prov, pkt[1]); break; } prov->rpr_state = pkt[1]; break; case OP_REM_PROV_PDU_REPORT: int_prov_rx(prov, pkt + 1, size - 1); break; case OP_REM_PROV_PDU_OB_REPORT: if (size != 1 || !prov) break; l_debug("Got Ack for OB %d", pkt[0]); if (prov->rpr_state == PB_REMOTE_STATE_OB_PKT_TX && pkt[0] == prov->out_num) int_prov_ack(prov, pkt[0]); break; /* Scan Opcodes */ case OP_REM_PROV_SCAN_CAP_STATUS: case OP_REM_PROV_SCAN_STATUS: break; case OP_REM_PROV_SCAN_REPORT: case OP_REM_PROV_EXT_SCAN_REPORT: req = l_queue_find(scans, match_req_node, node); if (req) { req->scan_result(node, src, opcode == OP_REM_PROV_EXT_SCAN_REPORT, pkt, size); } } return true; } void initiator_scan_reg(mesh_prov_initiator_scan_result_t scan_result, void *user_data) { struct scan_req *req; if (!scans) scans = l_queue_new(); req = l_queue_find(scans, match_req_node, user_data); if (!req) { req = l_new(struct scan_req, 1); l_queue_push_head(scans, req); } req->scan_result = scan_result; req->node = user_data; req->count++; } void initiator_scan_unreg(void *user_data) { struct scan_req *req; req = l_queue_find(scans, match_req_node, user_data); if (req) { req->count--; if (!req->count) { l_queue_remove(scans, req); l_free(req); } } } static void remprv_cli_unregister(void *user_data) { } static const struct mesh_model_ops ops = { .unregister = remprv_cli_unregister, .recv = remprv_cli_pkt, .bind = NULL, .sub = NULL, .pub = NULL }; void remote_prov_client_init(struct mesh_node *node, uint8_t ele_idx) { mesh_model_register(node, ele_idx, REM_PROV_CLI_MODEL, &ops, node); } bluez-5.87/mesh/PaxHeaders/appkey.c0000644000000000000000000000005014772767672014275 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/appkey.c0000644000000000000000000002236014772767672013761 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2017-2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #define _GNU_SOURCE #include #include "mesh/mesh-defs.h" #include "mesh/node.h" #include "mesh/net.h" #include "mesh/crypto.h" #include "mesh/util.h" #include "mesh/model.h" #include "mesh/mesh-config.h" #include "mesh/appkey.h" struct mesh_app_key { uint16_t net_idx; uint16_t app_idx; uint8_t key[16]; uint8_t key_aid; uint8_t new_key[16]; uint8_t new_key_aid; }; static bool match_key_index(const void *a, const void *b) { const struct mesh_app_key *key = a; uint16_t idx = L_PTR_TO_UINT(b); return key->app_idx == idx; } static bool match_bound_key(const void *a, const void *b) { const struct mesh_app_key *key = a; uint16_t idx = L_PTR_TO_UINT(b); return key->net_idx == idx; } static void finalize_key(void *a, void *b) { struct mesh_app_key *key = a; uint16_t net_idx = L_PTR_TO_UINT(b); if (key->net_idx != net_idx) return; if (key->new_key_aid == APP_AID_INVALID) return; key->key_aid = key->new_key_aid; key->new_key_aid = APP_AID_INVALID; memcpy(key->key, key->new_key, 16); } void appkey_finalize(struct mesh_net *net, uint16_t net_idx) { struct l_queue *app_keys; app_keys = mesh_net_get_app_keys(net); if (!app_keys) return; l_queue_foreach(app_keys, finalize_key, L_UINT_TO_PTR(net_idx)); } static struct mesh_app_key *app_key_new(void) { struct mesh_app_key *key = l_new(struct mesh_app_key, 1); key->new_key_aid = APP_AID_INVALID; return key; } static bool set_key(struct mesh_app_key *key, uint16_t app_idx, const uint8_t *key_value, bool is_new) { uint8_t key_aid; if (!mesh_crypto_k4(key_value, &key_aid)) return false; key_aid = KEY_ID_AKF | (key_aid << KEY_AID_SHIFT); if (!is_new) key->key_aid = key_aid; else key->new_key_aid = key_aid; memcpy(is_new ? key->new_key : key->key, key_value, 16); return true; } void appkey_key_free(void *data) { struct mesh_app_key *key = data; if (!key) return; l_free(key); } bool appkey_key_init(struct mesh_net *net, uint16_t net_idx, uint16_t app_idx, uint8_t *key_value, uint8_t *new_key_value) { struct mesh_app_key *key; struct l_queue *app_keys; if (net_idx > MAX_KEY_IDX || app_idx > MAX_KEY_IDX) return false; app_keys = mesh_net_get_app_keys(net); if (!app_keys) return NULL; if (!mesh_net_have_key(net, net_idx)) return false; key = app_key_new(); if (!key) return false; key->net_idx = net_idx; key->app_idx = app_idx; if (key_value && !set_key(key, app_idx, key_value, false)) { appkey_key_free(key); return false; } if (new_key_value && !set_key(key, app_idx, new_key_value, true)) { appkey_key_free(key); return false; } l_queue_push_tail(app_keys, key); return true; } const uint8_t *appkey_get_key(struct mesh_net *net, uint16_t app_idx, uint8_t *key_aid) { struct mesh_app_key *app_key; uint8_t phase; struct l_queue *app_keys; app_keys = mesh_net_get_app_keys(net); if (!app_keys) return NULL; app_key = l_queue_find(app_keys, match_key_index, L_UINT_TO_PTR(app_idx)); if (!app_key) return NULL; if (mesh_net_key_refresh_phase_get(net, app_key->net_idx, &phase) != MESH_STATUS_SUCCESS) return NULL; if (phase != KEY_REFRESH_PHASE_TWO) { *key_aid = app_key->key_aid; return app_key->key; } if (app_key->new_key_aid == APP_AID_INVALID) return NULL; *key_aid = app_key->new_key_aid; return app_key->new_key; } int appkey_get_key_idx(struct mesh_app_key *app_key, const uint8_t **key, uint8_t *key_aid, const uint8_t **new_key, uint8_t *new_key_aid) { if (!app_key) return -1; if (key && key_aid) { *key = app_key->key; *key_aid = app_key->key_aid; } if (new_key && new_key_aid) { *new_key = app_key->new_key; *new_key_aid = app_key->new_key_aid; } return app_key->app_idx; } bool appkey_have_key(struct mesh_net *net, uint16_t app_idx) { struct mesh_app_key *key; struct l_queue *app_keys; app_keys = mesh_net_get_app_keys(net); if (!app_keys) return false; key = l_queue_find(app_keys, match_key_index, L_UINT_TO_PTR(app_idx)); if (!key) return false; else return true; } uint16_t appkey_net_idx(struct mesh_net *net, uint16_t app_idx) { struct mesh_app_key *key; struct l_queue *app_keys; app_keys = mesh_net_get_app_keys(net); if (!app_keys) return NET_IDX_INVALID; key = l_queue_find(app_keys, match_key_index, L_UINT_TO_PTR(app_idx)); if (!key) return NET_IDX_INVALID; else return key->net_idx; } int appkey_key_update(struct mesh_net *net, uint16_t net_idx, uint16_t app_idx, const uint8_t *new_key) { struct mesh_app_key *key; struct l_queue *app_keys; uint8_t phase = KEY_REFRESH_PHASE_NONE; struct mesh_node *node; app_keys = mesh_net_get_app_keys(net); if (!app_keys) return MESH_STATUS_INSUFF_RESOURCES; if (!mesh_net_have_key(net, net_idx)) return MESH_STATUS_INVALID_NETKEY; key = l_queue_find(app_keys, match_key_index, L_UINT_TO_PTR(app_idx)); if (!key) return MESH_STATUS_INVALID_APPKEY; if (key->net_idx != net_idx) return MESH_STATUS_INVALID_BINDING; mesh_net_key_refresh_phase_get(net, net_idx, &phase); if (phase != KEY_REFRESH_PHASE_ONE) return MESH_STATUS_CANNOT_UPDATE; /* Check if the key has been already successfully updated */ if (memcmp(new_key, key->new_key, 16) == 0) return MESH_STATUS_SUCCESS; if (!set_key(key, app_idx, new_key, true)) return MESH_STATUS_INSUFF_RESOURCES; node = mesh_net_node_get(net); if (!mesh_config_app_key_update(node_config_get(node), app_idx, new_key)) return MESH_STATUS_STORAGE_FAIL; return MESH_STATUS_SUCCESS; } int appkey_key_add(struct mesh_net *net, uint16_t net_idx, uint16_t app_idx, const uint8_t *new_key) { struct mesh_app_key *key; struct l_queue *app_keys; struct mesh_node *node; app_keys = mesh_net_get_app_keys(net); if (!app_keys) return MESH_STATUS_INSUFF_RESOURCES; key = l_queue_find(app_keys, match_key_index, L_UINT_TO_PTR(app_idx)); if (key) { if (key->net_idx != net_idx) return MESH_STATUS_INVALID_NETKEY; else if (memcmp(new_key, key->key, 16) == 0) return MESH_STATUS_SUCCESS; else return MESH_STATUS_IDX_ALREADY_STORED; } if (!mesh_net_have_key(net, net_idx)) return MESH_STATUS_INVALID_NETKEY; if (l_queue_length(app_keys) >= MAX_APP_KEYS) return MESH_STATUS_INSUFF_RESOURCES; key = app_key_new(); if (!set_key(key, app_idx, new_key, false)) { appkey_key_free(key); return MESH_STATUS_INSUFF_RESOURCES; } node = mesh_net_node_get(net); if (!mesh_config_app_key_add(node_config_get(node), net_idx, app_idx, new_key)) { appkey_key_free(key); return MESH_STATUS_STORAGE_FAIL; } key->net_idx = net_idx; key->app_idx = app_idx; l_queue_push_tail(app_keys, key); return MESH_STATUS_SUCCESS; } int appkey_key_delete(struct mesh_net *net, uint16_t net_idx, uint16_t app_idx) { struct mesh_app_key *key; struct l_queue *app_keys; struct mesh_node *node; app_keys = mesh_net_get_app_keys(net); if (!app_keys) return MESH_STATUS_INVALID_APPKEY; key = l_queue_find(app_keys, match_key_index, L_UINT_TO_PTR(app_idx)); if (!key) return MESH_STATUS_SUCCESS; if (key->net_idx != net_idx) return MESH_STATUS_INVALID_NETKEY; node = mesh_net_node_get(net); node_app_key_delete(node, net_idx, app_idx); l_queue_remove(app_keys, key); appkey_key_free(key); if (!mesh_config_app_key_del(node_config_get(node), net_idx, app_idx)) return MESH_STATUS_STORAGE_FAIL; return MESH_STATUS_SUCCESS; } void appkey_delete_bound_keys(struct mesh_net *net, uint16_t net_idx) { struct l_queue *app_keys; struct mesh_node *node; struct mesh_app_key *key; app_keys = mesh_net_get_app_keys(net); if (!app_keys) return; node = mesh_net_node_get(net); key = l_queue_remove_if(app_keys, match_bound_key, L_UINT_TO_PTR(net_idx)); while (key) { node_app_key_delete(node, net_idx, key->app_idx); mesh_config_app_key_del(node_config_get(node), net_idx, key->app_idx); appkey_key_free(key); key = l_queue_remove_if(app_keys, match_bound_key, L_UINT_TO_PTR(net_idx)); } } uint8_t appkey_list(struct mesh_net *net, uint16_t net_idx, uint8_t *buf, uint16_t buf_size, uint16_t *size) { const struct l_queue_entry *entry; uint32_t idx_pair; int i; uint16_t datalen; struct l_queue *app_keys; *size = 0; if (!mesh_net_have_key(net, net_idx)) return MESH_STATUS_INVALID_NETKEY; app_keys = mesh_net_get_app_keys(net); if (!app_keys || l_queue_isempty(app_keys)) return MESH_STATUS_SUCCESS; idx_pair = 0; i = 0; datalen = 0; entry = l_queue_get_entries(app_keys); for (; entry; entry = entry->next) { struct mesh_app_key *key = entry->data; if (net_idx != key->net_idx) continue; if (!(i & 0x1)) { idx_pair = key->app_idx; } else { idx_pair <<= 12; idx_pair += key->app_idx; /* Unlikely, but check for overflow*/ if ((datalen + 3) > buf_size) { l_warn("Appkey list too large"); goto done; } l_put_le32(idx_pair, buf); buf += 3; datalen += 3; } i++; } /* Process the last app key if present */ if (i & 0x1 && ((datalen + 2) <= buf_size)) { l_put_le16(idx_pair, buf); datalen += 2; } done: *size = datalen; return MESH_STATUS_SUCCESS; } bluez-5.87/mesh/PaxHeaders/manager.h0000644000000000000000000000005014015011623014363 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/manager.h0000644000000000000000000000042314015011623014043 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2019 Intel Corporation. All rights reserved. * * */ bool manager_dbus_init(struct l_dbus *dbus); void manager_scan_cancel(struct mesh_node *node); bluez-5.87/mesh/PaxHeaders/mesh-io-api.h0000644000000000000000000000005014447506754015110 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/mesh-io-api.h0000644000000000000000000000272014447506754014572 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ struct mesh_io_private; typedef bool (*mesh_io_init_t)(struct mesh_io *io, void *opts, void *user_data); typedef bool (*mesh_io_destroy_t)(struct mesh_io *io); typedef bool (*mesh_io_caps_t)(struct mesh_io *io, struct mesh_io_caps *caps); typedef bool (*mesh_io_send_t)(struct mesh_io *io, struct mesh_io_send_info *info, const uint8_t *data, uint16_t len); typedef bool (*mesh_io_register_t)(struct mesh_io *io, const uint8_t *filter, uint8_t len, mesh_io_recv_func_t cb, void *user_data); typedef bool (*mesh_io_deregister_t)(struct mesh_io *io, const uint8_t *filter, uint8_t len); typedef bool (*mesh_io_tx_cancel_t)(struct mesh_io *io, const uint8_t *pattern, uint8_t len); struct mesh_io_api { mesh_io_init_t init; mesh_io_destroy_t destroy; mesh_io_caps_t caps; mesh_io_send_t send; mesh_io_register_t reg; mesh_io_deregister_t dereg; mesh_io_tx_cancel_t cancel; }; struct mesh_io_reg { mesh_io_recv_func_t cb; void *user_data; uint8_t len; uint8_t filter[]; }; struct mesh_io { int index; int favored_index; mesh_io_ready_func_t ready; struct l_queue *rx_regs; struct mesh_io_private *pvt; void *user_data; const struct mesh_io_api *api; }; struct mesh_io_table { enum mesh_io_type type; const struct mesh_io_api *api; }; bluez-5.87/mesh/PaxHeaders/mesh-io.c0000644000000000000000000000005015063045254014320 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/mesh-io.c0000644000000000000000000001564315063045254014012 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/mgmt.h" #include "src/shared/ad.h" #include "src/shared/mgmt.h" #include "mesh/mesh-defs.h" #include "mesh/mesh-mgmt.h" #include "mesh/mesh-io.h" #include "mesh/mesh-io-api.h" /* List of Mesh-IO Type headers */ #include "mesh/mesh-io-mgmt.h" #include "mesh/mesh-io-generic.h" #include "mesh/mesh-io-unit.h" struct loop_data { uint16_t len; uint8_t data[]; }; /* List of Supported Mesh-IO Types */ static const struct mesh_io_table table[] = { {MESH_IO_TYPE_MGMT, &mesh_io_mgmt}, {MESH_IO_TYPE_GENERIC, &mesh_io_generic}, {MESH_IO_TYPE_UNIT_TEST, &mesh_io_unit}, }; static const uint8_t unprv_filter[] = { BT_AD_MESH_BEACON, 0 }; static struct mesh_io *default_io; static struct l_timeout *loop_adv_to; static const struct mesh_io_api *io_api(enum mesh_io_type type) { uint16_t i; for (i = 0; i < L_ARRAY_SIZE(table); i++) { if (table[i].type == type) return table[i].api; } return NULL; } static void refresh_rx(void *a, void *b) { struct mesh_io_reg *rx_reg = a; struct mesh_io *io = b; if (io->api && io->api->reg) io->api->reg(io, rx_reg->filter, rx_reg->len, rx_reg->cb, rx_reg->user_data); } static void ctl_alert(int index, bool up, bool pwr, bool mesh, void *user_data) { enum mesh_io_type type = L_PTR_TO_UINT(user_data); const struct mesh_io_api *api = NULL; l_warn("index %u up:%d pwr: %d mesh: %d", index, up, pwr, mesh); /* If specific IO controller requested, honor it */ if (default_io->favored_index != MGMT_INDEX_NONE) { if (default_io->favored_index != index) return; if (!up | pwr) { l_warn("HCI%u failed to start generic IO %s", index, pwr ? ": already powered on" : ""); if (default_io->ready) default_io->ready(default_io->user_data, false); } } if (!up && default_io->index == index) { /* Our controller has disappeared */ if (default_io->api && default_io->api->destroy) { default_io->api->destroy(default_io); default_io->api = NULL; } /* Re-enumerate controllers */ mesh_mgmt_list(ctl_alert, user_data); return; } /* If we already have an API, keep using it */ if (!up || default_io->api) return; if (mesh && type != MESH_IO_TYPE_GENERIC) api = io_api(MESH_IO_TYPE_MGMT); else if (!pwr) api = io_api(MESH_IO_TYPE_GENERIC); if (api) { default_io->index = index; default_io->api = api; api->init(default_io, &index, default_io->user_data); l_queue_foreach(default_io->rx_regs, refresh_rx, default_io); } } static void free_io(struct mesh_io *io) { if (io) { if (io->api && io->api->destroy) io->api->destroy(io); l_queue_destroy(io->rx_regs, l_free); io->rx_regs = NULL; l_free(io); l_warn("Destroy %p", io); } } static struct mesh_io_reg *find_by_filter(struct l_queue *rx_regs, const uint8_t *filter, uint8_t len) { const struct l_queue_entry *entry; entry = l_queue_get_entries(rx_regs); for (; entry; entry = entry->next) { struct mesh_io_reg *rx_reg = entry->data; if (rx_reg->len == len && !memcmp(rx_reg->filter, filter, len)) return rx_reg; } return NULL; } struct mesh_io *mesh_io_new(enum mesh_io_type type, void *opts, mesh_io_ready_func_t cb, void *user_data) { const struct mesh_io_api *api = NULL; /* Only allow one IO */ if (default_io) return NULL; default_io = l_new(struct mesh_io, 1); default_io->ready = cb; default_io->user_data = user_data; default_io->favored_index = *(int *) opts; default_io->rx_regs = l_queue_new(); if (type >= MESH_IO_TYPE_AUTO) { if (!mesh_mgmt_list(ctl_alert, L_UINT_TO_PTR(type))) goto fail; return default_io; } api = io_api(type); if (!api || !api->init) goto fail; default_io->api = api; if (!api->init(default_io, opts, user_data)) goto fail; return default_io; fail: free_io(default_io); default_io = NULL; return NULL; } void mesh_io_destroy(struct mesh_io *io) { } bool mesh_io_get_caps(struct mesh_io *io, struct mesh_io_caps *caps) { if (io != default_io) return false; if (io && io->api && io->api->caps) return io->api->caps(io, caps); return false; } bool mesh_io_register_recv_cb(struct mesh_io *io, const uint8_t *filter, uint8_t len, mesh_io_recv_func_t cb, void *user_data) { struct mesh_io_reg *rx_reg; if (io == NULL) io = default_io; if (io != default_io || !cb || !filter || !len) return false; rx_reg = find_by_filter(io->rx_regs, filter, len); l_free(rx_reg); l_queue_remove(io->rx_regs, rx_reg); rx_reg = l_malloc(sizeof(struct mesh_io_reg) + len); rx_reg->cb = cb; rx_reg->len = len; rx_reg->user_data = user_data; memcpy(rx_reg->filter, filter, len); l_queue_push_head(io->rx_regs, rx_reg); if (io && io->api && io->api->reg) return io->api->reg(io, filter, len, cb, user_data); return false; } bool mesh_io_deregister_recv_cb(struct mesh_io *io, const uint8_t *filter, uint8_t len) { struct mesh_io_reg *rx_reg; if (io != default_io) return false; rx_reg = find_by_filter(io->rx_regs, filter, len); l_queue_remove(io->rx_regs, rx_reg); l_free(rx_reg); if (io && io->api && io->api->dereg) return io->api->dereg(io, filter, len); return false; } static void loop_foreach(void *data, void *user_data) { struct mesh_io_reg *rx_reg = data; struct loop_data *rx = user_data; if (!memcmp(rx_reg->filter, unprv_filter, sizeof(unprv_filter))) rx_reg->cb(rx_reg->user_data, NULL, rx->data, rx->len); } static void loop_rx(struct l_timeout *timeout, void *user_data) { struct loop_data *rx = user_data; l_queue_foreach(default_io->rx_regs, loop_foreach, rx); l_timeout_modify_ms(loop_adv_to, 500); } static void loop_destroy(void *user_data) { l_free(user_data); } static void loop_unprv_beacon(const uint8_t *data, uint16_t len) { struct loop_data *pkt = l_malloc(len + sizeof(struct loop_data)); memcpy(pkt->data, data, len); pkt->len = len; l_timeout_remove(loop_adv_to); loop_adv_to = l_timeout_create_ms(500, loop_rx, pkt, loop_destroy); } bool mesh_io_send(struct mesh_io *io, struct mesh_io_send_info *info, const uint8_t *data, uint16_t len) { if (io && io != default_io) return false; if (!io) io = default_io; /* Loop unprovisioned beacons for local clients */ if (!memcmp(data, unprv_filter, sizeof(unprv_filter))) loop_unprv_beacon(data, len); if (io && io->api && io->api->send) return io->api->send(io, info, data, len); return false; } bool mesh_io_send_cancel(struct mesh_io *io, const uint8_t *pattern, uint8_t len) { if (io && io != default_io) return false; if (!io) io = default_io; if (loop_adv_to && len >= 2 && !memcmp(pattern, unprv_filter, 2)) { l_timeout_remove(loop_adv_to); loop_adv_to = NULL; } if (io && io->api && io->api->cancel) return io->api->cancel(io, pattern, len); return false; } bluez-5.87/mesh/PaxHeaders/net-keys.h0000644000000000000000000000005015063045254014523 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/net-keys.h0000644000000000000000000000300215063045254014177 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2019 Intel Corporation. All rights reserved. * * */ #define BEACON_TYPE_SNB 0x01 #define BEACON_TYPE_MPB 0x02 #define BEACON_LEN_SNB 23 #define BEACON_LEN_MPB 28 #define BEACON_LEN_MAX BEACON_LEN_MPB #define KEY_REFRESH 0x01 #define IV_INDEX_UPDATE 0x02 #define NET_MPB_REFRESH_DEFAULT 60 void net_key_cleanup(void); bool net_key_confirm(uint32_t id, const uint8_t flooding[16]); bool net_key_retrieve(uint32_t id, uint8_t *flooding); uint32_t net_key_add(const uint8_t flooding[16]); uint32_t net_key_frnd_add(uint32_t flooding_id, uint16_t lpn, uint16_t frnd, uint16_t lp_cnt, uint16_t fn_cnt); void net_key_unref(uint32_t id); uint32_t net_key_decrypt(uint32_t iv_index, const uint8_t *pkt, size_t len, uint8_t **plain, size_t *plain_len); bool net_key_encrypt(uint32_t id, uint32_t iv_index, uint8_t *pkt, size_t len); uint32_t net_key_network_id(const uint8_t network[8]); uint32_t net_key_beacon(const uint8_t *data, uint16_t len, uint32_t *ivi, bool *ivu, bool *kr); bool net_key_snb_check(uint32_t id, uint32_t iv_index, bool kr, bool ivu, uint64_t cmac); void net_key_beacon_seen(uint32_t id); bool net_key_beacon_refresh(uint32_t id, uint32_t iv_index, bool kr, bool ivu, bool force); void net_key_beacon_enable(uint32_t id, bool mpb, uint8_t refresh_count); void net_key_beacon_disable(uint32_t id, bool mpb); uint32_t net_key_beacon_last_seen(uint32_t id); bluez-5.87/mesh/PaxHeaders/crypto.c0000644000000000000000000000005015063045254014277 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/crypto.c0000644000000000000000000005714415063045254013773 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include "mesh/mesh-defs.h" #include "mesh/net.h" #include "mesh/crypto.h" /* Multiply used Zero array */ static const uint8_t zero[16] = { 0, }; static bool aes_ecb_one(const uint8_t key[16], const uint8_t in[16], uint8_t out[16]) { void *cipher; bool result = false; cipher = l_cipher_new(L_CIPHER_AES, key, 16); if (cipher) { result = l_cipher_encrypt(cipher, in, out, 16); l_cipher_free(cipher); } return result; } static bool aes_cmac(void *checksum, const uint8_t *msg, size_t msg_len, uint8_t res[16]) { if (!l_checksum_update(checksum, msg, msg_len)) return false; if (16 == l_checksum_get_digest(checksum, res, 16)) return true; return false; } static bool aes_cmac_one(const uint8_t key[16], const void *msg, size_t msg_len, uint8_t res[16]) { void *checksum; bool result; checksum = l_checksum_new_cmac_aes(key, 16); if (!checksum) return false; result = l_checksum_update(checksum, msg, msg_len); if (result) { ssize_t len = l_checksum_get_digest(checksum, res, 16); result = (len == 16); } l_checksum_free(checksum); return result; } bool mesh_crypto_aes_cmac(const uint8_t key[16], const uint8_t *msg, size_t msg_len, uint8_t res[16]) { return aes_cmac_one(key, msg, msg_len, res); } bool mesh_crypto_aes_ccm_encrypt(const uint8_t nonce[13], const uint8_t key[16], const uint8_t *aad, uint16_t aad_len, const void *msg, uint16_t msg_len, void *out_msg, size_t mic_size) { void *cipher; bool result; cipher = l_aead_cipher_new(L_AEAD_CIPHER_AES_CCM, key, 16, mic_size); result = l_aead_cipher_encrypt(cipher, msg, msg_len, aad, aad_len, nonce, 13, out_msg, msg_len + mic_size); l_aead_cipher_free(cipher); return result; } bool mesh_crypto_aes_ccm_decrypt(const uint8_t nonce[13], const uint8_t key[16], const uint8_t *aad, uint16_t aad_len, const void *enc_msg, uint16_t enc_msg_len, void *out_msg, void *out_mic, size_t mic_size) { void *cipher; bool result; size_t out_msg_len = enc_msg_len - mic_size; cipher = l_aead_cipher_new(L_AEAD_CIPHER_AES_CCM, key, 16, mic_size); result = l_aead_cipher_decrypt(cipher, enc_msg, enc_msg_len, aad, aad_len, nonce, 13, out_msg, out_msg_len); if (result && out_mic) { if (mic_size == 4) *(uint32_t *)out_mic = l_get_be32(enc_msg + enc_msg_len - mic_size); else *(uint64_t *)out_mic = l_get_be64(enc_msg + enc_msg_len - mic_size); } l_aead_cipher_free(cipher); return result; } bool mesh_crypto_k1(const uint8_t ikm[16], const uint8_t salt[16], const void *info, size_t info_len, uint8_t okm[16]) { uint8_t res[16]; if (!aes_cmac_one(salt, ikm, 16, res)) return false; return aes_cmac_one(res, info, info_len, okm); } bool mesh_crypto_k2(const uint8_t n[16], const uint8_t *p, size_t p_len, uint8_t net_id[1], uint8_t enc_key[16], uint8_t priv_key[16]) { void *checksum; uint8_t output[16]; uint8_t t[16]; uint8_t *stage; bool success = false; stage = l_malloc(sizeof(output) + p_len + 1); if (!stage) return false; if (!mesh_crypto_s1("smk2", 4, stage)) goto fail; if (!aes_cmac_one(stage, n, 16, t)) goto fail; checksum = l_checksum_new_cmac_aes(t, 16); if (!checksum) goto fail; memcpy(stage, p, p_len); stage[p_len] = 1; if (!aes_cmac(checksum, stage, p_len + 1, output)) goto done; net_id[0] = output[15] & 0x7f; memcpy(stage, output, 16); memcpy(stage + 16, p, p_len); stage[p_len + 16] = 2; if (!aes_cmac(checksum, stage, p_len + 16 + 1, output)) goto done; memcpy(enc_key, output, 16); memcpy(stage, output, 16); memcpy(stage + 16, p, p_len); stage[p_len + 16] = 3; if (!aes_cmac(checksum, stage, p_len + 16 + 1, output)) goto done; memcpy(priv_key, output, 16); success = true; done: l_checksum_free(checksum); fail: l_free(stage); return success; } static bool crypto_128(const uint8_t n[16], const char *s, uint8_t out128[16]) { const uint8_t id128[] = { 'i', 'd', '1', '2', '8', 0x01 }; uint8_t salt[16]; if (!mesh_crypto_s1(s, 4, salt)) return false; return mesh_crypto_k1(n, salt, id128, sizeof(id128), out128); } bool mesh_crypto_nkik(const uint8_t n[16], uint8_t identity_key[16]) { return crypto_128(n, "nkik", identity_key); } bool mesh_crypto_identity(const uint8_t net_key[16], uint16_t addr, uint8_t id[16]) { uint8_t id_key[16]; uint8_t tmp[16]; if (!mesh_crypto_nkik(net_key, id_key)) return false; if (!l_get_be64(id + 8)) l_getrandom(id + 8, 8); memset(tmp, 0, sizeof(tmp)); memcpy(tmp + 6, id + 8, 8); l_put_be16(addr, tmp + 14); if (!aes_ecb_one(id_key, tmp, tmp)) return false; memcpy(id, tmp + 8, 8); return true; } bool mesh_crypto_nkbk(const uint8_t n[16], uint8_t beacon_key[16]) { return crypto_128(n, "nkbk", beacon_key); } bool mesh_crypto_nkpk(const uint8_t n[16], uint8_t private_key[16]) { return crypto_128(n, "nkpk", private_key); } bool mesh_crypto_k3(const uint8_t n[16], uint8_t out64[8]) { const uint8_t id64[] = { 'i', 'd', '6', '4', 0x01 }; uint8_t tmp[16]; uint8_t t[16]; if (!mesh_crypto_s1("smk3", 4, tmp)) return false; if (!aes_cmac_one(tmp, n, 16, t)) return false; if (!aes_cmac_one(t, id64, sizeof(id64), tmp)) return false; memcpy(out64, tmp + 8, 8); return true; } bool mesh_crypto_k4(const uint8_t a[16], uint8_t out6[1]) { const uint8_t id6[] = { 'i', 'd', '6', 0x01 }; uint8_t tmp[16]; uint8_t t[16]; if (!mesh_crypto_s1("smk4", 4, tmp)) return false; if (!aes_cmac_one(tmp, a, 16, t)) return false; if (!aes_cmac_one(t, id6, sizeof(id6), tmp)) return false; out6[0] = tmp[15] & 0x3f; return true; } bool mesh_crypto_beacon_cmac(const uint8_t encryption_key[16], const uint8_t network_id[8], uint32_t iv_index, bool kr, bool iu, uint64_t *cmac) { uint8_t msg[13], tmp[16]; if (!cmac) return false; msg[0] = kr ? 0x01 : 0x00; msg[0] |= iu ? 0x02 : 0x00; memcpy(msg + 1, network_id, 8); l_put_be32(iv_index, msg + 9); if (!aes_cmac_one(encryption_key, msg, 13, tmp)) return false; *cmac = l_get_be64(tmp); return true; } static void mesh_crypto_network_nonce(bool ctl, uint8_t ttl, uint32_t seq, uint16_t src, uint32_t iv_index, uint8_t nonce[13]) { nonce[0] = 0x00; nonce[1] = (ttl & TTL_MASK) | (ctl ? CTL : 0x00); nonce[2] = (seq >> 16) & 0xff; nonce[3] = (seq >> 8) & 0xff; nonce[4] = seq & 0xff; l_put_be16(src, nonce + 5); l_put_be16(0, nonce + 7); l_put_be32(iv_index, nonce + 9); } static void mesh_crypto_application_nonce(uint32_t seq, uint16_t src, uint16_t dst, uint32_t iv_index, bool aszmic, uint8_t nonce[13]) { nonce[0] = 0x01; nonce[1] = aszmic ? 0x80 : 0x00; nonce[2] = (seq >> 16) & 0xff; nonce[3] = (seq >> 8) & 0xff; nonce[4] = seq & 0xff; l_put_be16(src, nonce + 5); l_put_be16(dst, nonce + 7); l_put_be32(iv_index, nonce + 9); } static void mesh_crypto_device_nonce(uint32_t seq, uint16_t src, uint16_t dst, uint32_t iv_index, bool aszmic, uint8_t nonce[13]) { nonce[0] = 0x02; nonce[1] = aszmic ? 0x80 : 0x00; nonce[2] = (seq >> 16) & 0xff; nonce[3] = (seq >> 8) & 0xff; nonce[4] = seq & 0xff; l_put_be16(src, nonce + 5); l_put_be16(dst, nonce + 7); l_put_be32(iv_index, nonce + 9); } static void mesh_crypto_proxy_nonce(uint32_t seq, uint16_t src, uint32_t iv_index, uint8_t nonce[13]) { nonce[0] = 0x03; nonce[1] = 0; nonce[2] = (seq >> 16) & 0xff; nonce[3] = (seq >> 8) & 0xff; nonce[4] = seq & 0xff; l_put_be16(src, nonce + 5); l_put_be16(0, nonce + 7); l_put_be32(iv_index, nonce + 9); } bool mesh_crypto_session_key(const uint8_t secret[32], const uint8_t salt[16], uint8_t session_key[16]) { const uint8_t prsk[4] = { 'p', 'r', 's', 'k' }; if (!aes_cmac_one(salt, secret, 32, session_key)) return false; return aes_cmac_one(session_key, prsk, 4, session_key); } bool mesh_crypto_nonce(const uint8_t secret[32], const uint8_t salt[16], uint8_t nonce[13]) { const uint8_t prsn[4] = { 'p', 'r', 's', 'n' }; uint8_t tmp[16]; bool result; if (!aes_cmac_one(salt, secret, 32, tmp)) return false; result = aes_cmac_one(tmp, prsn, 4, tmp); if (result) memcpy(nonce, tmp + 3, 13); return result; } bool mesh_crypto_s1(const void *info, size_t len, uint8_t salt[16]) { return aes_cmac_one(zero, info, len, salt); } bool mesh_crypto_prov_prov_salt(const uint8_t conf_salt[16], const uint8_t prov_rand[16], const uint8_t dev_rand[16], uint8_t prov_salt[16]) { uint8_t tmp[16 * 3]; memcpy(tmp, conf_salt, 16); memcpy(tmp + 16, prov_rand, 16); memcpy(tmp + 32, dev_rand, 16); return aes_cmac_one(zero, tmp, sizeof(tmp), prov_salt); } bool mesh_crypto_prov_conf_key(const uint8_t secret[32], const uint8_t salt[16], uint8_t conf_key[16]) { const uint8_t prck[4] = { 'p', 'r', 'c', 'k' }; if (!aes_cmac_one(salt, secret, 32, conf_key)) return false; return aes_cmac_one(conf_key, prck, 4, conf_key); } bool mesh_crypto_device_key(const uint8_t secret[32], const uint8_t salt[16], uint8_t device_key[16]) { const uint8_t prdk[4] = { 'p', 'r', 'd', 'k' }; if (!aes_cmac_one(salt, secret, 32, device_key)) return false; return aes_cmac_one(device_key, prdk, 4, device_key); } bool mesh_crypto_virtual_addr(const uint8_t virtual_label[16], uint16_t *addr) { uint8_t tmp[16]; if (!mesh_crypto_s1("vtad", 4, tmp)) return false; if (!addr || !aes_cmac_one(tmp, virtual_label, 16, tmp)) return false; *addr = (l_get_be16(tmp + 14) & 0x3fff) | 0x8000; return true; } static void mesh_crypto_privacy_counter(uint32_t iv_index, const uint8_t *payload, uint8_t privacy_counter[16]) { memset(privacy_counter, 0, 5); l_put_be32(iv_index, privacy_counter + 5); memcpy(privacy_counter + 9, payload, 7); } static bool mesh_crypto_pecb(const uint8_t privacy_key[16], uint32_t iv_index, const uint8_t *payload, uint8_t pecb[16]) { mesh_crypto_privacy_counter(iv_index, payload, pecb); return aes_ecb_one(privacy_key, pecb, pecb); } static bool mesh_crypto_network_obfuscate(uint8_t *packet, const uint8_t privacy_key[16], uint32_t iv_index, bool ctl, uint8_t ttl, uint32_t seq, uint16_t src) { uint8_t pecb[16]; uint8_t *net_hdr = packet + 1; int i; if (!mesh_crypto_pecb(privacy_key, iv_index, packet + 7, pecb)) return false; l_put_be16(src, net_hdr + 4); l_put_be32(seq & SEQ_MASK, net_hdr); net_hdr[0] = ((!!ctl) << 7) | (ttl & TTL_MASK); for (i = 0; i < 6; i++) net_hdr[i] = pecb[i] ^ net_hdr[i]; return true; } static bool mesh_crypto_network_clarify(uint8_t *packet, const uint8_t privacy_key[16], uint32_t iv_index, bool *ctl, uint8_t *ttl, uint32_t *seq, uint16_t *src) { uint8_t pecb[16]; uint8_t *net_hdr = packet + 1; int i; if (!mesh_crypto_pecb(privacy_key, iv_index, packet + 7, pecb)) return false; for (i = 0; i < 6; i++) net_hdr[i] = pecb[i] ^ net_hdr[i]; *src = l_get_be16(net_hdr + 4); *seq = l_get_be32(net_hdr) & SEQ_MASK; *ttl = net_hdr[0] & TTL_MASK; *ctl = !!(net_hdr[0] & CTL); return true; } bool mesh_crypto_packet_build(bool ctl, uint8_t ttl, uint32_t seq, uint16_t src, uint16_t dst, uint8_t opcode, bool segmented, uint8_t key_aid, bool szmic, uint16_t seqZero, uint8_t segO, uint8_t segN, const uint8_t *payload, uint8_t payload_len, uint8_t *packet, uint8_t *packet_len) { uint32_t hdr; size_t n; if (seq > SEQ_MASK) return false; packet[0] = 0; l_put_be32(seq, packet + 1); packet[1] = (ctl ? CTL : 0) | (ttl & TTL_MASK); l_put_be16(src, packet + 5); l_put_be16(dst, packet + 7); n = 9; if (!ctl) { uint32_t tmp = segmented ? 0x1 : 0; hdr = tmp << SEG_HDR_SHIFT; hdr |= (key_aid & KEY_ID_MASK) << KEY_HDR_SHIFT; if (segmented) { hdr |= szmic << SZMIC_HDR_SHIFT; hdr |= (seqZero & SEQ_ZERO_MASK) << SEQ_ZERO_HDR_SHIFT; hdr |= (segO & SEG_MASK) << SEGO_HDR_SHIFT; hdr |= (segN & SEG_MASK) << SEGN_HDR_SHIFT; } l_put_be32(hdr, packet + n); /* Only first octet is valid for unsegmented messages */ if (segmented) n += 4; else n += 1; memcpy(packet + n, payload, payload_len); l_put_be32(0x00000000, packet + payload_len + n); if (packet_len) *packet_len = payload_len + n + 4; } else { if ((opcode & OPCODE_MASK) != opcode) return false; hdr = opcode << KEY_HDR_SHIFT; l_put_be32(hdr, packet + n); n += 1; memcpy(packet + n, payload, payload_len); n += payload_len; l_put_be64(0x0000000000000000, packet + n); if (packet_len) *packet_len = n + 8; } return true; } static bool network_header_parse(const uint8_t *packet, uint8_t packet_len, bool *ctl, uint8_t *ttl, uint32_t *seq, uint16_t *src, uint16_t *dst) { if (packet_len < 10) return false; /* Try to keep bits in the order they exist within the packet */ if (ctl) *ctl = !!(packet[1] & CTL); if (ttl) *ttl = packet[1] & TTL_MASK; if (seq) *seq = l_get_be32(packet + 1) & SEQ_MASK; if (src) *src = l_get_be16(packet + 5); if (dst) *dst = l_get_be16(packet + 7); return true; } bool mesh_crypto_packet_parse(const uint8_t *packet, uint8_t packet_len, bool *ctl, uint8_t *ttl, uint32_t *seq, uint16_t *src, uint16_t *dst, uint32_t *cookie, uint8_t *opcode, bool *segmented, uint8_t *key_aid, bool *szmic, bool *relay, uint16_t *seqZero, uint8_t *segO, uint8_t *segN, const uint8_t **payload, uint8_t *payload_len) { uint32_t hdr; uint16_t this_dst; bool is_segmented; if (!network_header_parse(packet, packet_len, ctl, ttl, seq, src, &this_dst)) return false; if (dst) *dst = this_dst; if (packet_len < 9 + 4) return false; hdr = l_get_be32(packet + 9); is_segmented = !!((hdr >> SEG_HDR_SHIFT) & 0x1); if (segmented) *segmented = is_segmented; if (*ctl) { uint8_t this_opcode = packet[9] & OPCODE_MASK; /* NetMIC */ packet_len -= 8; if (cookie) *cookie = l_get_be32(packet + 2) ^ packet[6]; if (opcode) *opcode = this_opcode; if (this_dst && this_opcode == NET_OP_SEG_ACKNOWLEDGE) { if (relay) *relay = !!((hdr >> RELAY_HDR_SHIFT) & 0x1); if (seqZero) *seqZero = (hdr >> SEQ_ZERO_HDR_SHIFT) & SEQ_ZERO_MASK; if (packet_len < 9) return false; *payload = packet + 9; *payload_len = packet_len - 9; } else { if (packet_len < 10) return false; *payload = packet + 10; *payload_len = packet_len - 10; } } else { /* NetMIC */ packet_len -= 4; if (cookie) *cookie = l_get_be32(packet + packet_len - 8); if (key_aid) *key_aid = (hdr >> KEY_HDR_SHIFT) & KEY_ID_MASK; if (is_segmented) { if (szmic) *szmic = !!((hdr >> SZMIC_HDR_SHIFT) & 0x1); if (seqZero) *seqZero = (hdr >> SEQ_ZERO_HDR_SHIFT) & SEQ_ZERO_MASK; if (segO) *segO = (hdr >> SEGO_HDR_SHIFT) & SEG_MASK; if (segN) *segN = (hdr >> SEGN_HDR_SHIFT) & SEG_MASK; if (packet_len < 13) return false; *payload = packet + 13; *payload_len = packet_len - 13; } else { if (packet_len < 10) return false; *payload = packet + 10; *payload_len = packet_len - 10; } } return true; } bool mesh_crypto_payload_encrypt(uint8_t *aad, const uint8_t *payload, uint8_t *out, uint16_t payload_len, uint16_t src, uint16_t dst, uint8_t key_aid, uint32_t seq, uint32_t iv_index, bool aszmic, const uint8_t app_key[16]) { uint8_t nonce[13]; if (payload_len < 1) return false; if (key_aid == APP_AID_DEV) mesh_crypto_device_nonce(seq, src, dst, iv_index, aszmic, nonce); else mesh_crypto_application_nonce(seq, src, dst, iv_index, aszmic, nonce); if (!mesh_crypto_aes_ccm_encrypt(nonce, app_key, aad, aad ? 16 : 0, payload, payload_len, out, aszmic ? 8 : 4)) return false; return true; } bool mesh_crypto_payload_decrypt(uint8_t *aad, uint16_t aad_len, const uint8_t *payload, uint16_t payload_len, bool aszmic, uint16_t src, uint16_t dst, uint8_t key_aid, uint32_t seq, uint32_t iv_index, uint8_t *out, const uint8_t app_key[16]) { uint8_t nonce[13]; uint32_t mic32; uint64_t mic64; if (payload_len < 5 || !out) return false; if (key_aid == APP_AID_DEV) mesh_crypto_device_nonce(seq, src, dst, iv_index, aszmic, nonce); else mesh_crypto_application_nonce(seq, src, dst, iv_index, aszmic, nonce); memcpy(out, payload, payload_len); if (aszmic) { if (!mesh_crypto_aes_ccm_decrypt(nonce, app_key, aad, aad_len, payload, payload_len, out, &mic64, sizeof(mic64))) return false; mic64 ^= l_get_be64(out + payload_len - 8); l_put_be64(mic64, out + payload_len - 8); if (mic64) return false; } else { if (!mesh_crypto_aes_ccm_decrypt(nonce, app_key, aad, aad_len, payload, payload_len, out, &mic32, sizeof(mic32))) return false; mic32 ^= l_get_be32(out + payload_len - 4); l_put_be32(mic32, out + payload_len - 4); if (mic32) return false; } return true; } static bool mesh_crypto_packet_encrypt(uint8_t *packet, uint8_t packet_len, const uint8_t network_key[16], uint32_t iv_index, bool proxy, bool ctl, uint8_t ttl, uint32_t seq, uint16_t src) { uint8_t nonce[13]; /* Detect Proxy packet by CTL == true && DST == 0x0000 */ if (ctl && proxy) mesh_crypto_proxy_nonce(seq, src, iv_index, nonce); else mesh_crypto_network_nonce(ctl, ttl, seq, src, iv_index, nonce); /* Check for Long net-MIC */ if (ctl) { if (!mesh_crypto_aes_ccm_encrypt(nonce, network_key, NULL, 0, packet + 7, packet_len - 7 - 8, packet + 7, 8)) return false; } else { if (!mesh_crypto_aes_ccm_encrypt(nonce, network_key, NULL, 0, packet + 7, packet_len - 7 - 4, packet + 7, 4)) return false; } return true; } bool mesh_crypto_packet_encode(uint8_t *packet, uint8_t packet_len, uint32_t iv_index, const uint8_t network_key[16], const uint8_t privacy_key[16]) { bool ctl; uint8_t ttl; uint32_t seq; uint16_t src; uint16_t dst; if (!network_header_parse(packet, packet_len, &ctl, &ttl, &seq, &src, &dst)) return false; if (!mesh_crypto_packet_encrypt(packet, packet_len, network_key, iv_index, !dst, ctl, ttl, seq, src)) return false; return mesh_crypto_network_obfuscate(packet, privacy_key, iv_index, ctl, ttl, seq, src); } static bool mesh_crypto_packet_decrypt(uint8_t *packet, uint8_t packet_len, const uint8_t network_key[16], uint32_t iv_index, bool proxy, bool ctl, uint8_t ttl, uint32_t seq, uint16_t src) { uint8_t nonce[13]; /* Pre-check SRC address for illegal values */ if (!IS_UNICAST(src)) return false; /* Detect Proxy packet by CTL == true && proxy == true */ if (ctl & proxy) mesh_crypto_proxy_nonce(seq, src, iv_index, nonce); else mesh_crypto_network_nonce(ctl, ttl, seq, src, iv_index, nonce); /* Check for Long MIC */ if (ctl) { uint64_t mic; if (!mesh_crypto_aes_ccm_decrypt(nonce, network_key, NULL, 0, packet + 7, packet_len - 7, packet + 7, &mic, sizeof(mic))) return false; mic ^= l_get_be64(packet + packet_len - 8); l_put_be64(mic, packet + packet_len - 8); if (mic) return false; } else { uint32_t mic; if (!mesh_crypto_aes_ccm_decrypt(nonce, network_key, NULL, 0, packet + 7, packet_len - 7, packet + 7, &mic, sizeof(mic))) return false; mic ^= l_get_be32(packet + packet_len - 4); l_put_be32(mic, packet + packet_len - 4); if (mic) return false; } return true; } bool mesh_crypto_packet_decode(const uint8_t *packet, uint8_t packet_len, bool proxy, uint8_t *out, uint32_t iv_index, const uint8_t network_key[16], const uint8_t privacy_key[16]) { bool ctl; uint8_t ttl; uint32_t seq; uint16_t src; if (packet_len < 14) return false; memcpy(out, packet, packet_len); if (!mesh_crypto_network_clarify(out, privacy_key, iv_index, &ctl, &ttl, &seq, &src)) return false; return mesh_crypto_packet_decrypt(out, packet_len, network_key, iv_index, proxy, ctl, ttl, seq, src); } bool mesh_crypto_packet_label(uint8_t *packet, uint8_t packet_len, uint16_t iv_index, uint8_t network_id) { packet[0] = (iv_index & 0x0001) << 7 | (network_id & 0x7f); return true; } /* reversed, 8-bit, poly=0x07 */ static const uint8_t crc_table[256] = { 0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75, 0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b, 0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69, 0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67, 0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d, 0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43, 0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51, 0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f, 0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05, 0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b, 0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19, 0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17, 0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d, 0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33, 0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21, 0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f, 0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95, 0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b, 0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89, 0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87, 0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad, 0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3, 0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1, 0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf, 0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5, 0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb, 0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9, 0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7, 0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd, 0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3, 0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1, 0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf }; uint8_t mesh_crypto_compute_fcs(const uint8_t *packet, uint8_t packet_len) { uint8_t fcs = 0xff; int i; for (i = 0; i < packet_len; i++) fcs = crc_table[fcs ^ packet[i]]; return 0xff - fcs; } bool mesh_crypto_check_fcs(const uint8_t *packet, uint8_t packet_len, uint8_t received_fcs) { uint8_t fcs = 0xff; int i; for (i = 0; i < packet_len; i++) fcs = crc_table[fcs ^ packet[i]]; fcs = crc_table[fcs ^ received_fcs]; return fcs == 0xcf; } /* This function performs a quick-check of ELL and Kernel AEAD encryption. * Some kernel versions before v4.9 have a known AEAD bug. If the system * running this test is using a v4.8 or earlier kernel, a failure here is * likely unless AEAD encryption has been backported. */ static const uint8_t crypto_test_result[] = { 0x75, 0x03, 0x7e, 0xe2, 0x89, 0x81, 0xbe, 0x59, 0xbc, 0xe6, 0xdd, 0x23, 0x63, 0x5b, 0x16, 0x61, 0xb7, 0x23, 0x92, 0xd4, 0x86, 0xee, 0x84, 0x29, 0x9a, 0x2a, 0xbf, 0x96 }; bool mesh_crypto_check_avail(void) { void *cipher; bool result; uint8_t i; union { struct { uint8_t key[16]; uint8_t aad[16]; uint8_t nonce[13]; uint8_t data[20]; uint8_t mic[8]; } crypto; uint8_t bytes[73]; } u; uint8_t out_msg[sizeof(u.crypto.data) + sizeof(u.crypto.mic)]; l_debug("Testing Crypto"); for (i = 0; i < sizeof(u); i++) { u.bytes[i] = 0x60 + i; } cipher = l_aead_cipher_new(L_AEAD_CIPHER_AES_CCM, u.crypto.key, sizeof(u.crypto.key), sizeof(u.crypto.mic)); if (!cipher) return false; result = l_aead_cipher_encrypt(cipher, u.crypto.data, sizeof(u.crypto.data), u.crypto.aad, sizeof(u.crypto.aad), u.crypto.nonce, sizeof(u.crypto.nonce), out_msg, sizeof(out_msg)); if (result) result = !memcmp(out_msg, crypto_test_result, sizeof(out_msg)); l_aead_cipher_free(cipher); return result; } bluez-5.87/mesh/PaxHeaders/pb-adv.c0000644000000000000000000000005015063045254014130 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/pb-adv.c0000644000000000000000000003011315063045254013607 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "src/shared/ad.h" #include "mesh/mesh-defs.h" #include "mesh/crypto.h" #include "mesh/net.h" #include "mesh/mesh-io.h" #include "mesh/mesh.h" #include "mesh/prov.h" #include "mesh/provision.h" #include "mesh/pb-adv.h" #include "mesh/util.h" struct pb_adv_session { mesh_prov_open_func_t open_cb; mesh_prov_close_func_t close_cb; mesh_prov_receive_func_t rx_cb; mesh_prov_ack_func_t ack_cb; struct l_timeout *tx_timeout; struct pb_adv_session *loop; uint32_t link_id; uint16_t exp_len; uint8_t exp_fcs; uint8_t exp_segs; uint8_t got_segs; uint8_t trans_num; uint8_t local_acked; uint8_t local_trans_num; uint8_t peer_trans_num; uint8_t last_peer_trans_num; uint8_t sar[80]; uint8_t uuid[16]; bool initiator; bool opened; void *user_data; }; #define PB_ADV_ACK 0x01 #define PB_ADV_OPEN_REQ 0x03 #define PB_ADV_OPEN_CFM 0x07 #define PB_ADV_CLOSE 0x0B #define PB_ADV_MTU 24 struct pb_ack { uint8_t ad_type; uint32_t link_id; uint8_t trans_num; uint8_t opcode; } __packed; struct pb_open_req{ uint8_t ad_type; uint32_t link_id; uint8_t trans_num; uint8_t opcode; uint8_t uuid[16]; } __packed; struct pb_open_cfm{ uint8_t ad_type; uint32_t link_id; uint8_t trans_num; uint8_t opcode; } __packed; struct pb_close_ind { uint8_t ad_type; uint32_t link_id; uint8_t trans_num; uint8_t opcode; uint8_t reason; } __packed; struct idle_rx { struct pb_adv_session *session; uint16_t len; uint8_t data[PB_ADV_MTU + 6]; }; static struct l_queue *pb_sessions = NULL; static const uint8_t filter[1] = { BT_AD_MESH_PROV }; static void pb_adv_packet(void *user_data, const uint8_t *pkt, uint16_t len); static void idle_rx_adv(void *user_data) { struct idle_rx *rx = user_data; pb_adv_packet(rx->session, rx->data, rx->len); l_free(rx); } static void pb_adv_send(struct pb_adv_session *session, uint8_t count, uint16_t interval, void *data, uint16_t len) { struct idle_rx *rx; if (session->loop) { rx = l_new(struct idle_rx, 1); rx->session = session->loop; rx->len = len; memcpy(rx->data, data, len); l_idle_oneshot(idle_rx_adv, rx, NULL); } else mesh_send_pkt(count, interval, data, len); } static void send_adv_segs(struct pb_adv_session *session, const uint8_t *data, uint16_t size) { uint16_t init_size; uint8_t buf[PB_ADV_MTU + 6] = { BT_AD_MESH_PROV }; uint8_t max_seg; uint8_t consumed; int i; if (!size) return; mesh_send_cancel(filter, sizeof(filter)); l_put_be32(session->link_id, buf + 1); buf[1 + 4] = ++session->local_trans_num; if (size > PB_ADV_MTU - 4) { max_seg = 1 + (((size - (PB_ADV_MTU - 4)) - 1) / (PB_ADV_MTU - 1)); init_size = PB_ADV_MTU - 4; } else { max_seg = 0; init_size = size; } l_debug("Sending %u fragments for %u octets", max_seg + 1, size); buf[6] = max_seg << 2; l_put_be16(size, buf + 7); buf[9] = mesh_crypto_compute_fcs(data, size); memcpy(buf + 10, data, init_size); l_debug("max_seg: %2.2x", max_seg); l_debug("size: %2.2x, CRC: %2.2x", size, buf[9]); pb_adv_send(session, MESH_IO_TX_COUNT_UNLIMITED, 500, buf, init_size + 10); consumed = init_size; for (i = 1; i <= max_seg; i++) { size_t seg_size; /* Amount of payload data being sent */ if (size - consumed > PB_ADV_MTU - 1) seg_size = PB_ADV_MTU - 1; else seg_size = size - consumed; buf[6] = (i << 2) | 0x02; memcpy(buf + 7, data + consumed, seg_size); pb_adv_send(session, MESH_IO_TX_COUNT_UNLIMITED, 500, buf, seg_size + 7); consumed += seg_size; } } static bool session_match (const void *a, const void *b) { return a == b; } static bool uuid_match (const void *a, const void *b) { const struct pb_adv_session *session = a; const uint8_t *uuid = b; return !memcmp(session->uuid, uuid, sizeof(session->uuid)); } static bool user_match (const void *a, const void *b) { const struct pb_adv_session *session = a; return session->user_data == b; } static void tx_timeout(struct l_timeout *timeout, void *user_data) { struct pb_adv_session *session = user_data; mesh_prov_close_func_t cb; if (!l_queue_find(pb_sessions, session_match, session)) return; mesh_send_cancel(filter, sizeof(filter)); l_debug("TX timeout"); cb = session->close_cb; user_data = session->user_data; cb(user_data, 1); } static void pb_adv_tx(void *user_data, const void *data, uint16_t len) { struct pb_adv_session *session = user_data; if (!l_queue_find(pb_sessions, session_match, session)) return; l_timeout_remove(session->tx_timeout); session->tx_timeout = l_timeout_create(30, tx_timeout, session, NULL); send_adv_segs(session, data, len); } static void send_open_req(struct pb_adv_session *session) { struct pb_open_req open_req = { BT_AD_MESH_PROV }; l_put_be32(session->link_id, &open_req.link_id); open_req.trans_num = 0; open_req.opcode = PB_ADV_OPEN_REQ; memcpy(open_req.uuid, session->uuid, 16); mesh_send_cancel(filter, sizeof(filter)); pb_adv_send(session, MESH_IO_TX_COUNT_UNLIMITED, 500, &open_req, sizeof(open_req)); } static void send_open_cfm(struct pb_adv_session *session) { struct pb_open_cfm open_cfm = { BT_AD_MESH_PROV }; l_put_be32(session->link_id, &open_cfm.link_id); open_cfm.trans_num = 0; open_cfm.opcode = PB_ADV_OPEN_CFM; mesh_send_cancel(filter, sizeof(filter)); pb_adv_send(session, MESH_IO_TX_COUNT_UNLIMITED, 500, &open_cfm, sizeof(open_cfm)); } static void send_ack(struct pb_adv_session *session, uint8_t trans_num) { struct pb_ack ack = { BT_AD_MESH_PROV }; if (!l_queue_find(pb_sessions, session_match, session)) return; l_put_be32(session->link_id, &ack.link_id); ack.trans_num = trans_num; ack.opcode = PB_ADV_ACK; pb_adv_send(session, MESH_IO_TX_COUNT_UNLIMITED, 500, &ack, sizeof(ack)); } static void send_close_ind(struct pb_adv_session *session, uint8_t reason) { struct pb_close_ind close_ind = { BT_AD_MESH_PROV }; if (!l_queue_find(pb_sessions, session_match, session)) return; l_put_be32(session->link_id, &close_ind.link_id); close_ind.trans_num = 0; close_ind.opcode = PB_ADV_CLOSE; close_ind.reason = reason; mesh_send_cancel(filter, sizeof(filter)); pb_adv_send(session, 10, 100, &close_ind, sizeof(close_ind)); } static void pb_adv_packet(void *user_data, const uint8_t *pkt, uint16_t len) { struct pb_adv_session *session = user_data; uint32_t link_id; size_t offset; uint8_t trans_num; uint8_t type; bool first; if (!l_queue_find(pb_sessions, session_match, session)) return; link_id = l_get_be32(pkt + 1); type = l_get_u8(pkt + 6); /* Validate new or existing Connection ID */ if (session->link_id) { if (session->link_id != link_id) return; } else if (type != 0x03) return; else if (!link_id) return; trans_num = l_get_u8(pkt + 5); pkt += 7; len -= 7; switch (type) { case PB_ADV_OPEN_CFM: /* * Ignore if: * 1. We are acceptor * 2. We are already provisioning on different link_id */ if (!session->initiator) return; first = !session->opened; session->opened = true; /* Only call Open callback once */ if (first) { l_debug("PB-ADV open confirmed"); session->local_trans_num = 0xFF; session->open_cb(session->user_data, pb_adv_tx, session, PB_ADV); } return; case PB_ADV_OPEN_REQ: /* * Ignore if: * 1. We are initiator * 2. Open request not addressed to us * 3. We are already provisioning on different link_id */ if (session->initiator) return; if (memcmp(pkt, session->uuid, 16)) return; first = !session->link_id; session->link_id = link_id; session->last_peer_trans_num = 0xFF; session->peer_trans_num = 0x00; session->local_trans_num = 0x7F; session->opened = true; /* Only call Open callback once */ if (first) { l_debug("PB-ADV open requested"); session->open_cb(session->user_data, pb_adv_tx, session, PB_ADV); } /* Send CFM once per received request */ send_open_cfm(session); break; case PB_ADV_CLOSE: l_debug("Link closed notification: %2.2x", pkt[0]); session->close_cb(session->user_data, pkt[0]); break; case PB_ADV_ACK: if (!session->opened) return; if (trans_num != session->local_trans_num) return; if (session->local_acked > trans_num) return; mesh_send_cancel(filter, sizeof(filter)); session->local_acked = trans_num; session->ack_cb(session->user_data, trans_num); break; default: /* DATA SEGMENT */ if (!session->opened) return; if (trans_num == session->last_peer_trans_num) { send_ack(session, trans_num); return; } switch(type & 0x03) { case 0x00: session->peer_trans_num = trans_num; session->exp_len = l_get_be16(pkt); l_debug("PB-ADV start with %u fragments, %d octets", type >> 2, session->exp_len); if (session->exp_len > sizeof(session->sar)) { l_debug("Incoming length exceeded: %d", session->exp_len); return; } session->exp_fcs = l_get_u8(pkt + 2); session->exp_segs = 0xff >> (7 - (type >> 2)); /* Save first segment */ memcpy(session->sar, pkt + 3, len - 3); session->got_segs |= 1; break; case 0x02: session->peer_trans_num = trans_num; offset = 20 + (((type >> 2) - 1) * 23); if (offset + len - 3 > sizeof(session->sar)) { l_debug("Length exceeded: %d", session->exp_len); return; } l_debug("Processing fragment %u", type >> 2); memcpy(session->sar + offset, pkt, len); session->got_segs |= 1 << (type >> 2); break; default: /* Malformed or unrecognized */ return; } if (session->got_segs != session->exp_segs) return; /* Validate RXed packet and pass up to Provisioning */ if (!mesh_crypto_check_fcs(session->sar, session->exp_len, session->exp_fcs)) { /* This can be a false negative if first * segment missed, and can almost always * be ignored. */ l_debug("Invalid FCS"); return; } send_ack(session, session->peer_trans_num); if (session->last_peer_trans_num != session->peer_trans_num) { session->got_segs = 0; session->last_peer_trans_num = session->peer_trans_num; session->rx_cb(session->user_data, session->sar, session->exp_len); } } } bool pb_adv_reg(bool initiator, mesh_prov_open_func_t open_cb, mesh_prov_close_func_t close_cb, mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb, const uint8_t *uuid, void *user_data) { struct pb_adv_session *session, *old_session; if (!pb_sessions) pb_sessions = l_queue_new(); old_session = l_queue_find(pb_sessions, uuid_match, uuid); /* Reject 2nd session if not looping back */ if (l_queue_length(pb_sessions) && !old_session) return false; /* Reject looping to more than one session or with same role*/ if (old_session && (old_session->loop || old_session->initiator == initiator)) return false; session = l_new(struct pb_adv_session, 1); session->open_cb = open_cb; session->close_cb = close_cb; session->rx_cb = rx_cb; session->ack_cb = ack_cb; session->user_data = user_data; session->initiator = initiator; memcpy(session->uuid, uuid, 16); l_queue_push_head(pb_sessions, session); if (initiator) { l_getrandom(&session->link_id, sizeof(session->link_id)); session->tx_timeout = l_timeout_create(60, tx_timeout, session, NULL); } /* Setup Loop-back if complementary session with same UUID */ if (old_session) { session->loop = old_session; old_session->loop = session; mesh_unreg_prov_rx(pb_adv_packet); if (initiator) send_open_req(session); else send_open_req(old_session); return true; } mesh_reg_prov_rx(pb_adv_packet, session); if (initiator) send_open_req(session); return true; } void pb_adv_unreg(void *user_data) { struct pb_adv_session *session = l_queue_find(pb_sessions, user_match, user_data); if (!session) return; l_timeout_remove(session->tx_timeout); session->tx_timeout = NULL; send_close_ind(session, 0); l_queue_remove(pb_sessions, session); l_free(session); if (!l_queue_length(pb_sessions)) { l_queue_destroy(pb_sessions, l_free); pb_sessions = NULL; } } bluez-5.87/mesh/PaxHeaders/mesh.h0000644000000000000000000000005014333256742013725 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/mesh.h0000644000000000000000000000317214333256742013411 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ #define BLUEZ_MESH_NAME "org.bluez.mesh" #define MESH_NETWORK_INTERFACE "org.bluez.mesh.Network1" #define MESH_NODE_INTERFACE "org.bluez.mesh.Node1" #define MESH_MANAGEMENT_INTERFACE "org.bluez.mesh.Management1" #define MESH_ELEMENT_INTERFACE "org.bluez.mesh.Element1" #define MESH_APPLICATION_INTERFACE "org.bluez.mesh.Application1" #define MESH_PROVISION_AGENT_INTERFACE "org.bluez.mesh.ProvisionAgent1" #define MESH_PROVISIONER_INTERFACE "org.bluez.mesh.Provisioner1" #define ERROR_INTERFACE "org.bluez.mesh.Error" enum mesh_io_type; typedef void (*mesh_ready_func_t)(void *user_data, bool success); typedef void (*prov_rx_cb_t)(void *user_data, const uint8_t *data, uint16_t len); bool mesh_init(const char *config_dir, const char *mesh_conf_fname, enum mesh_io_type type, void *opts, mesh_ready_func_t cb, void *user_data); void mesh_cleanup(bool signaled); bool mesh_dbus_init(struct l_dbus *dbus); const char *mesh_status_str(uint8_t err); bool mesh_send_pkt(uint8_t count, uint16_t interval, void *data, uint16_t len); bool mesh_send_cancel(const uint8_t *filter, uint8_t len); bool mesh_reg_prov_rx(prov_rx_cb_t cb, void *user_data); void mesh_unreg_prov_rx(prov_rx_cb_t cb); const char *mesh_prov_status_str(uint8_t status); const char *mesh_get_storage_dir(void); bool mesh_beacon_enabled(void); bool mesh_relay_supported(void); bool mesh_friendship_supported(void); uint16_t mesh_get_crpl(void); uint8_t mesh_get_friend_queue_size(void); bluez-5.87/mesh/PaxHeaders/org.bluez.mesh.service0000644000000000000000000000005013540747027017044 xustar0020 atime=1770587295 20 ctime=1783094111 bluez-5.87/mesh/org.bluez.mesh.service0000644000000000000000000000015113540747027016522 0ustar00rootroot[D-BUS Service] Name=org.bluez.mesh Exec=/bin/false User=root SystemdService=dbus-org.bluez.mesh.service bluez-5.87/mesh/PaxHeaders/net.c0000644000000000000000000000005015221752714013547 xustar0020 atime=1783092684 20 ctime=1783094103 bluez-5.87/mesh/net.c0000644000000000000000000026244515221752714013245 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include "src/shared/ad.h" #include "mesh/mesh-defs.h" #include "mesh/util.h" #include "mesh/crypto.h" #include "mesh/net-keys.h" #include "mesh/node.h" #include "mesh/net.h" #include "mesh/mesh-io.h" #include "mesh/friend.h" #include "mesh/mesh-config.h" #include "mesh/model.h" #include "mesh/appkey.h" #include "mesh/rpl.h" #define abs_diff(a, b) ((a) > (b) ? (a) - (b) : (b) - (a)) #define IV_IDX_DIFF_RANGE 42 /*#define IV_IDX_UPD_MIN (5 * 60) * 5 minute for Testing */ #define IV_IDX_UPD_MIN (60 * 60 * 96) /* 96 Hours - per Spec */ #define IV_IDX_UPD_HOLD (IV_IDX_UPD_MIN/2) #define IV_IDX_UPD_MAX (IV_IDX_UPD_MIN + IV_IDX_UPD_HOLD) #define iv_is_updating(net) ((net)->iv_upd_state == IV_UPD_UPDATING) #define IV_UPDATE_SEQ_TRIGGER 0x800000 /* Half of Seq-Nums expended */ #define SEG_TO 2 #define MSG_TO 60 #define SAR_DEL 10 #define DEFAULT_TRANSMIT_COUNT 1 #define DEFAULT_TRANSMIT_INTERVAL 100 #define SAR_KEY(src, seq0) ((((uint32_t)(seq0)) << 16) | (src)) #define FAST_CACHE_SIZE 8 enum _relay_advice { RELAY_NONE, /* Relay not enabled in node */ RELAY_ALLOWED, /* Relay enabled, msg not to node's unicast */ RELAY_DISALLOWED, /* Msg was unicast handled by this node */ RELAY_ALWAYS /* Relay enabled, msg to a group */ }; enum _iv_upd_state { /* Allows acceptance of any iv_index secure net beacon */ IV_UPD_INIT, /* Normal, can transition, accept current or old */ IV_UPD_NORMAL, /* Updating proc running, we use old, accept old or new */ IV_UPD_UPDATING, /* Normal, can *not* transition, accept current or old iv_index */ IV_UPD_NORMAL_HOLD, }; struct mesh_subnet { struct mesh_net *net; uint16_t idx; uint32_t net_key_tx; uint32_t net_key_cur; uint32_t net_key_upd; uint8_t key_refresh; uint8_t kr_phase; }; struct mesh_net { struct mesh_io *io; struct mesh_node *node; struct mesh_prov *prov; struct l_queue *app_keys; unsigned int pkt_id; unsigned int bea_id; unsigned int beacon_id; unsigned int sar_id_next; bool friend_enable; bool snb_enable; bool mpb_enable; bool proxy_enable; bool friend_seq; struct l_timeout *iv_update_timeout; enum _iv_upd_state iv_upd_state; bool iv_update; uint32_t instant; /* Controller Instant of recent Rx */ uint32_t iv_index; uint32_t seq_num; uint16_t src_addr; uint16_t last_addr; uint16_t tx_interval; uint16_t tx_cnt; uint8_t chan; /* Channel of recent Rx */ uint8_t default_ttl; uint8_t tid; uint8_t mpb_period; struct { bool enable; uint16_t interval; uint8_t count; } relay; /* Heartbeat info */ struct mesh_net_heartbeat_sub hb_sub; struct mesh_net_heartbeat_pub hb_pub; uint16_t features; struct l_queue *subnets; struct l_queue *msg_cache; struct l_queue *replay_cache; struct l_queue *sar_in; struct l_queue *sar_out; struct l_queue *sar_queue; struct l_queue *frnd_msgs; struct l_queue *friends; struct l_queue *negotiations; struct l_queue *destinations; }; struct mesh_msg { uint16_t src; uint32_t seq; uint32_t mic; }; struct mesh_sar { unsigned int id; struct l_timeout *seg_timeout; struct l_timeout *msg_timeout; uint32_t flags; uint32_t last_nak; uint32_t iv_index; uint32_t seqAuth; uint16_t seqZero; uint16_t app_idx; uint16_t net_idx; uint16_t src; uint16_t remote; uint16_t len; bool szmic; bool segmented; bool frnd; bool frnd_cred; bool delete; uint8_t ttl; uint8_t last_seg; uint8_t key_aid; uint8_t buf[4]; /* Large enough for ACK-Flags and MIC */ }; struct mesh_destination { uint16_t dst; uint16_t ref_cnt; }; struct net_queue_data { struct mesh_io_recv_info *info; struct mesh_net *net; const uint8_t *data; uint8_t *out; size_t out_size; enum _relay_advice relay_advice; uint32_t net_key_id; uint32_t iv_index; uint16_t len; bool seen; }; struct oneshot_tx { struct mesh_net *net; uint16_t interval; uint8_t cnt; uint8_t size; uint8_t packet[MESH_AD_MAX_LEN]; }; struct net_beacon_data { uint32_t net_key_id; uint32_t ivi; bool ivu; bool kr; bool processed; bool local; }; static struct l_queue *fast_cache; static struct l_queue *nets; static void net_rx(void *net_ptr, void *user_data); static inline struct mesh_subnet *get_primary_subnet(struct mesh_net *net) { return l_queue_peek_head(net->subnets); } static bool match_key_index(const void *a, const void *b) { const struct mesh_subnet *subnet = a; uint16_t idx = L_PTR_TO_UINT(b); return subnet->idx == idx; } static bool match_key_id(const void *a, const void *b) { const struct mesh_subnet *subnet = a; uint32_t net_key_id = L_PTR_TO_UINT(b); return (net_key_id == subnet->net_key_cur) || (net_key_id == subnet->net_key_upd); } static bool match_friend_key_id(const void *a, const void *b) { const struct mesh_friend *friend = a; uint32_t net_key_id = L_PTR_TO_UINT(b); return (net_key_id == friend->net_key_cur) || (net_key_id == friend->net_key_upd); } static void send_hb_publication(void *data) { struct mesh_net *net = data; struct mesh_net_heartbeat_pub *pub = &net->hb_pub; uint8_t msg[4]; int n = 0; if (pub->dst == UNASSIGNED_ADDRESS) return; msg[n++] = NET_OP_HEARTBEAT; msg[n++] = pub->ttl; l_put_be16(net->features, msg + n); n += 2; mesh_net_transport_send(net, 0, 0, mesh_net_get_iv_index(net), pub->ttl, 0, 0, pub->dst, msg, n); } static void trigger_heartbeat(struct mesh_net *net, uint16_t feature, bool enable) { l_debug("HB: %4.4x --> %d", feature, enable); if (enable) { if (net->features & feature) return; /* no change */ net->features |= feature; } else { if (!(net->features & feature)) return; /* no change */ net->features &= ~feature; } if (!(net->hb_pub.features & feature)) return; /* no interest in this feature */ l_idle_oneshot(send_hb_publication, net, NULL); } static bool match_by_friend(const void *a, const void *b) { const struct mesh_friend *frnd = a; uint16_t dst = L_PTR_TO_UINT(b); return frnd->lp_addr == dst; } static void free_friend_internals(struct mesh_friend *frnd) { if (frnd->pkt_cache) l_queue_destroy(frnd->pkt_cache, l_free); l_free(frnd->u.active.grp_list); frnd->u.active.grp_list = NULL; frnd->pkt_cache = NULL; net_key_unref(frnd->net_key_cur); net_key_unref(frnd->net_key_upd); frnd->net_key_cur = 0; frnd->net_key_upd = 0; } static void frnd_kr_phase1(void *a, void *b) { struct mesh_friend *frnd = a; uint32_t net_key_id = L_PTR_TO_UINT(b); frnd->net_key_upd = net_key_frnd_add(net_key_id, frnd->lp_addr, frnd->net->src_addr, frnd->lp_cnt, frnd->fn_cnt); } static void frnd_kr_phase2(void *a, void *b) { struct mesh_friend *frnd = a; /* * I think that a Friend should use Old Key as long as possible * Because a Friend Node will enter Phase 3 before it's LPN. * Alternatively, the FN could keep the Old Friend Keys until it * receives it's first Poll using the new keys (?) */ l_debug("Use Both KeySet %d && %d for %4.4x", frnd->net_key_cur, frnd->net_key_upd, frnd->lp_addr); } static void frnd_kr_phase3(void *a, void *b) { struct mesh_friend *frnd = a; l_debug("Replace KeySet %d with %d for %4.4x", frnd->net_key_cur, frnd->net_key_upd, frnd->lp_addr); net_key_unref(frnd->net_key_cur); frnd->net_key_cur = frnd->net_key_upd; frnd->net_key_upd = 0; } /* TODO: add net key idx? For now, use primary net key */ struct mesh_friend *mesh_friend_new(struct mesh_net *net, uint16_t dst, uint8_t ele_cnt, uint8_t frd, uint8_t frw, uint32_t fpt, uint16_t fn_cnt, uint16_t lp_cnt) { struct mesh_subnet *subnet; struct mesh_friend *frnd = l_queue_find(net->friends, match_by_friend, L_UINT_TO_PTR(dst)); if (frnd) { /* Kill all timers and empty cache for this friend */ free_friend_internals(frnd); l_timeout_remove(frnd->timeout); frnd->timeout = NULL; } else { frnd = l_new(struct mesh_friend, 1); l_queue_push_head(net->friends, frnd); } /* add _k2 */ frnd->net = net; frnd->lp_addr = dst; frnd->frd = frd; frnd->frw = frw; frnd->fn_cnt = fn_cnt; frnd->lp_cnt = lp_cnt; frnd->poll_timeout = fpt; frnd->ele_cnt = ele_cnt; frnd->pkt_cache = l_queue_new(); frnd->net_key_upd = 0; subnet = get_primary_subnet(net); /* TODO: the primary key must be present, do we need to add check?. */ frnd->net_key_cur = net_key_frnd_add(subnet->net_key_cur, dst, net->src_addr, lp_cnt, fn_cnt); if (!subnet->net_key_upd) return frnd; frnd->net_idx = subnet->idx; frnd->net_key_upd = net_key_frnd_add(subnet->net_key_upd, dst, net->src_addr, lp_cnt, fn_cnt); return frnd; } void mesh_friend_free(void *data) { struct mesh_friend *frnd = data; free_friend_internals(frnd); l_timeout_remove(frnd->timeout); l_free(frnd); } bool mesh_friend_clear(struct mesh_net *net, struct mesh_friend *frnd) { bool removed = l_queue_remove(net->friends, frnd); free_friend_internals(frnd); return removed; } void mesh_friend_sub_add(struct mesh_net *net, uint16_t lpn, uint8_t ele_cnt, uint8_t grp_cnt, const uint8_t *list) { uint16_t *new_list; uint16_t *grp_list; struct mesh_friend *frnd = l_queue_find(net->friends, match_by_friend, L_UINT_TO_PTR(lpn)); if (!frnd) return; new_list = l_malloc((grp_cnt + frnd->u.active.grp_cnt) * sizeof(uint16_t)); grp_list = frnd->u.active.grp_list; if (grp_list && frnd->u.active.grp_cnt) memcpy(new_list, grp_list, frnd->u.active.grp_cnt * sizeof(uint16_t)); memcpy(&new_list[frnd->u.active.grp_cnt], list, grp_cnt * sizeof(uint16_t)); l_free(grp_list); frnd->ele_cnt = ele_cnt; frnd->u.active.grp_list = new_list; frnd->u.active.grp_cnt += grp_cnt; } void mesh_friend_sub_del(struct mesh_net *net, uint16_t lpn, uint8_t cnt, const uint8_t *del_list) { uint16_t *grp_list; int16_t i, grp_cnt; size_t cnt16 = cnt * sizeof(uint16_t); struct mesh_friend *frnd = l_queue_find(net->friends, match_by_friend, L_UINT_TO_PTR(lpn)); if (!frnd) return; grp_cnt = frnd->u.active.grp_cnt; grp_list = frnd->u.active.grp_list; while (cnt-- && grp_cnt) { cnt16 -= sizeof(uint16_t); for (i = grp_cnt - 1; i >= 0; i--) { if (l_get_le16(del_list + cnt16) == grp_list[i]) { grp_cnt--; memcpy(&grp_list[i], &grp_list[i + 1], (grp_cnt - i) * sizeof(uint16_t)); break; } } } frnd->u.active.grp_cnt = grp_cnt; if (!grp_cnt) { l_free(frnd->u.active.grp_list); frnd->u.active.grp_list = NULL; } } uint32_t mesh_net_next_seq_num(struct mesh_net *net) { uint32_t seq = net->seq_num++; /* * Cap out-of-range seq_num max value to +1. Out of range * seq_nums will not be sent as they would violate spec. * This condition signals a runaway seq_num condition, and * the node must wait for a completed IV Index update procedure * before it can send again. */ if (net->seq_num > SEQ_MASK) net->seq_num = SEQ_MASK + 1; node_set_sequence_number(net->node, net->seq_num); return seq; } static struct mesh_sar *mesh_sar_new(size_t len) { size_t size = sizeof(struct mesh_sar) + len; struct mesh_sar *sar; sar = l_malloc(size); memset(sar, 0, size); return sar; } static void mesh_sar_free(void *data) { struct mesh_sar *sar = data; if (!sar) return; l_timeout_remove(sar->seg_timeout); l_timeout_remove(sar->msg_timeout); l_free(sar); } static void subnet_free(void *data) { struct mesh_subnet *subnet = data; struct mesh_net *net = subnet->net; if (net->snb_enable) net_key_beacon_disable(subnet->net_key_tx, false); if (net->mpb_enable) net_key_beacon_disable(subnet->net_key_tx, true); net_key_unref(subnet->net_key_cur); net_key_unref(subnet->net_key_upd); l_free(subnet); } static struct mesh_subnet *subnet_new(struct mesh_net *net, uint16_t idx) { struct mesh_subnet *subnet; subnet = l_new(struct mesh_subnet, 1); if (!subnet) return NULL; subnet->net = net; subnet->idx = idx; return subnet; } static void enable_snb(void *a, void *b) { struct mesh_subnet *subnet = a; struct mesh_net *net = b; if (net->snb_enable) net_key_beacon_enable(subnet->net_key_tx, false, 0); else net_key_beacon_disable(subnet->net_key_tx, false); } static void enable_mpb(void *a, void *b) { struct mesh_subnet *subnet = a; struct mesh_net *net = b; if (net->mpb_enable) net_key_beacon_enable(subnet->net_key_tx, true, net->mpb_period); else net_key_beacon_disable(subnet->net_key_tx, true); } static void enqueue_update(void *a, void *b); static void queue_friend_update(struct mesh_net *net) { struct mesh_subnet *subnet; struct mesh_friend *frnd; uint8_t flags = 0; if (l_queue_length(net->friends)) { struct mesh_friend_msg update = { .src = net->src_addr, .iv_index = mesh_net_get_iv_index(net), .last_len = 7, .ctl = true, }; frnd = l_queue_peek_head(net->friends); subnet = l_queue_find(net->subnets, match_key_index, L_UINT_TO_PTR(frnd->net_idx)); if (!subnet) return; if (subnet->kr_phase == KEY_REFRESH_PHASE_TWO) flags |= KEY_REFRESH; if (net->iv_update) flags |= IV_INDEX_UPDATE; update.u.one[0].hdr = NET_OP_FRND_UPDATE << OPCODE_HDR_SHIFT; update.u.one[0].seq = mesh_net_next_seq_num(net); update.u.one[0].data[0] = NET_OP_FRND_UPDATE; update.u.one[0].data[1] = flags; l_put_be32(net->iv_index, update.u.one[0].data + 2); update.u.one[0].data[6] = 0x01; /* More Data */ l_queue_foreach(net->friends, enqueue_update, &update); } } static void refresh_beacon(void *a, void *b) { struct mesh_subnet *subnet = a; struct mesh_net *net = b; net_key_beacon_refresh(subnet->net_key_tx, net->iv_index, subnet->kr_phase == KEY_REFRESH_PHASE_TWO, net->iv_update, false); } struct mesh_net *mesh_net_new(struct mesh_node *node) { struct mesh_net *net; net = l_new(struct mesh_net, 1); net->node = node; net->seq_num = DEFAULT_SEQUENCE_NUMBER; net->default_ttl = TTL_MASK; net->tx_cnt = DEFAULT_TRANSMIT_COUNT; net->tx_interval = DEFAULT_TRANSMIT_INTERVAL; net->subnets = l_queue_new(); net->msg_cache = l_queue_new(); net->sar_in = l_queue_new(); net->sar_out = l_queue_new(); net->sar_queue = l_queue_new(); net->frnd_msgs = l_queue_new(); net->destinations = l_queue_new(); net->app_keys = l_queue_new(); net->replay_cache = l_queue_new(); if (!nets) nets = l_queue_new(); if (!fast_cache) fast_cache = l_queue_new(); return net; } void mesh_net_free(void *user_data) { struct mesh_net *net = user_data; if (!net) return; l_queue_destroy(net->subnets, subnet_free); l_queue_destroy(net->msg_cache, l_free); l_queue_destroy(net->replay_cache, l_free); l_queue_destroy(net->sar_in, mesh_sar_free); l_queue_destroy(net->sar_out, mesh_sar_free); l_queue_destroy(net->sar_queue, mesh_sar_free); l_queue_destroy(net->frnd_msgs, l_free); l_queue_destroy(net->friends, mesh_friend_free); l_queue_destroy(net->negotiations, mesh_friend_free); l_queue_destroy(net->destinations, l_free); l_queue_destroy(net->app_keys, appkey_key_free); l_free(net); } void mesh_net_cleanup(void) { l_queue_destroy(fast_cache, l_free); fast_cache = NULL; l_queue_destroy(nets, mesh_net_free); nets = NULL; } bool mesh_net_set_seq_num(struct mesh_net *net, uint32_t seq) { if (!net) return false; net->seq_num = seq; node_set_sequence_number(net->node, net->seq_num); return true; } bool mesh_net_set_default_ttl(struct mesh_net *net, uint8_t ttl) { if (!net) return false; net->default_ttl = ttl; return true; } uint32_t mesh_net_get_seq_num(struct mesh_net *net) { if (!net) return 0; return net->seq_num; } uint8_t mesh_net_get_default_ttl(struct mesh_net *net) { if (!net) return 0; return net->default_ttl; } uint16_t mesh_net_get_address(struct mesh_net *net) { if (!net) return 0; return net->src_addr; } bool mesh_net_register_unicast(struct mesh_net *net, uint16_t address, uint8_t num_ele) { if (!net || !IS_UNICAST(address) || !num_ele) return false; net->src_addr = address; net->last_addr = address + num_ele - 1; if (net->last_addr < net->src_addr) return false; do { mesh_net_dst_reg(net, address); address++; num_ele--; } while (num_ele > 0); return true; } bool mesh_net_set_proxy_mode(struct mesh_net *net, bool enable) { if (!net) return false; /* No support for proxy yet */ if (enable) { l_error("Proxy not supported!"); return false; } trigger_heartbeat(net, FEATURE_PROXY, enable); return true; } bool mesh_net_set_friend_mode(struct mesh_net *net, bool enable) { l_debug("mesh_net_set_friend_mode - %d", enable); if (!net) return false; if (net->friend_enable == enable) return true; if (enable) { net->friends = l_queue_new(); net->negotiations = l_queue_new(); } else { l_queue_destroy(net->friends, mesh_friend_free); l_queue_destroy(net->negotiations, mesh_friend_free); net->friends = net->negotiations = NULL; } net->friend_enable = enable; trigger_heartbeat(net, FEATURE_FRIEND, enable); return true; } bool mesh_net_set_relay_mode(struct mesh_net *net, bool enable, uint8_t cnt, uint8_t interval) { if (!net) return false; net->relay.enable = enable; net->relay.count = cnt; net->relay.interval = interval; trigger_heartbeat(net, FEATURE_RELAY, enable); return true; } int mesh_net_get_identity_mode(struct mesh_net *net, uint16_t idx, uint8_t *mode) { struct mesh_subnet *subnet; if (!net) return MESH_STATUS_UNSPECIFIED_ERROR; subnet = l_queue_find(net->subnets, match_key_index, L_UINT_TO_PTR(idx)); if (!subnet) return MESH_STATUS_INVALID_NETKEY; /* Currently, proxy mode is not supported */ *mode = MESH_MODE_UNSUPPORTED; return MESH_STATUS_SUCCESS; } int mesh_net_del_key(struct mesh_net *net, uint16_t idx) { struct mesh_subnet *subnet; if (!net) return MESH_STATUS_UNSPECIFIED_ERROR; subnet = l_queue_find(net->subnets, match_key_index, L_UINT_TO_PTR(idx)); if (!subnet) return MESH_STATUS_SUCCESS; /* Cannot remove primary key */ if (l_queue_length(net->subnets) <= 1) return MESH_STATUS_CANNOT_REMOVE; /* Delete associated app keys */ appkey_delete_bound_keys(net, idx); /* Disable heartbeat publication on this subnet */ if (idx == net->hb_pub.net_idx) net->hb_pub.dst = UNASSIGNED_ADDRESS; /* TODO: cancel snb_enable on this subnet */ l_queue_remove(net->subnets, subnet); subnet_free(subnet); if (!mesh_config_net_key_del(node_config_get(net->node), idx)) return MESH_STATUS_STORAGE_FAIL; return MESH_STATUS_SUCCESS; } static struct mesh_subnet *add_key(struct mesh_net *net, uint16_t idx, const uint8_t *value) { struct mesh_subnet *subnet; subnet = subnet_new(net, idx); if (!subnet) return NULL; subnet->net_key_tx = subnet->net_key_cur = net_key_add(value); if (!subnet->net_key_cur) { l_free(subnet); return NULL; } net_key_beacon_refresh(subnet->net_key_tx, net->iv_index, false, net->iv_update, false); if (net->snb_enable) net_key_beacon_enable(subnet->net_key_tx, false, 0); if (net->mpb_enable) net_key_beacon_enable(subnet->net_key_tx, true, net->mpb_period); l_queue_push_tail(net->subnets, subnet); return subnet; } /* * This function is called when Configuration Server Model receives * a NETKEY_ADD command */ int mesh_net_add_key(struct mesh_net *net, uint16_t idx, const uint8_t *value) { struct mesh_subnet *subnet; subnet = l_queue_find(net->subnets, match_key_index, L_UINT_TO_PTR(idx)); if (subnet) { if (net_key_confirm(subnet->net_key_cur, value)) return MESH_STATUS_SUCCESS; else return MESH_STATUS_IDX_ALREADY_STORED; } subnet = add_key(net, idx, value); if (!subnet) return MESH_STATUS_INSUFF_RESOURCES; if (!mesh_config_net_key_add(node_config_get(net->node), idx, value)) { l_queue_remove(net->subnets, subnet); subnet_free(subnet); return MESH_STATUS_STORAGE_FAIL; } return MESH_STATUS_SUCCESS; } uint32_t mesh_net_get_iv_index(struct mesh_net *net) { if (!net) return 0xffffffff; return net->iv_index - net->iv_update; } /* TODO: net key index? */ void mesh_net_get_snb_state(struct mesh_net *net, uint8_t *flags, uint32_t *iv_index) { struct mesh_subnet *subnet; if (!net || !flags || !iv_index) return; *iv_index = net->iv_index; *flags = net->iv_update ? IV_INDEX_UPDATE : 0x00; subnet = get_primary_subnet(net); if (subnet) *flags |= subnet->key_refresh ? KEY_REFRESH : 0x00; } bool mesh_net_get_key(struct mesh_net *net, bool new_key, uint16_t idx, uint32_t *net_key_id) { struct mesh_subnet *subnet; if (!net) return false; subnet = l_queue_find(net->subnets, match_key_index, L_UINT_TO_PTR(idx)); if (!subnet) return false; if (!new_key) { *net_key_id = subnet->net_key_cur; return true; } if (!subnet->net_key_upd) return false; *net_key_id = subnet->net_key_upd; return true; } bool mesh_net_key_list_get(struct mesh_net *net, uint8_t *buf, uint16_t *size) { const struct l_queue_entry *entry; uint16_t num_keys, req_size, buf_size; struct mesh_subnet *subnet; if (!net || !buf || !size) return false; buf_size = *size; num_keys = l_queue_length(net->subnets); req_size = (num_keys / 2) * 3 + (num_keys % 2) * 2; if (buf_size < req_size) return false; *size = req_size; /* Pack NetKey indices in 3 octets */ for (entry = l_queue_get_entries(net->subnets); num_keys > 1;) { uint32_t idx_pair; subnet = entry->data; idx_pair = subnet->idx; idx_pair <<= 12; subnet = entry->next->data; idx_pair += subnet->idx; l_put_le32(idx_pair, buf); buf += 3; num_keys -= 2; entry = entry->next->next; } /* If odd number of NetKeys, fill in the end of the buffer */ if (num_keys % 2) { subnet = entry->data; l_put_le16(subnet->idx, buf); } return true; } bool mesh_net_get_frnd_seq(struct mesh_net *net) { if (!net) return false; return net->friend_seq; } void mesh_net_set_frnd_seq(struct mesh_net *net, bool seq) { if (!net) return; net->friend_seq = seq; } static bool match_cache(const void *a, const void *b) { const struct mesh_msg *msg = a; const struct mesh_msg *tst = b; if (msg->seq != tst->seq || msg->mic != tst->mic || msg->src != tst->src) return false; return true; } static bool msg_in_cache(struct mesh_net *net, uint16_t src, uint32_t seq, uint32_t mic) { struct mesh_msg *msg; struct mesh_msg tst = { .src = src, .seq = seq, .mic = mic, }; msg = l_queue_find(net->msg_cache, match_cache, &tst); if (msg) { l_debug("Suppressing duplicate %4.4x + %6.6x + %8.8x", src, seq, mic); return true; } msg = l_new(struct mesh_msg, 1); *msg = tst; l_queue_push_head(net->msg_cache, msg); l_debug("Add %4.4x + %6.6x + %8.8x", src, seq, mic); if (l_queue_length(net->msg_cache) > MSG_CACHE_SIZE) { msg = l_queue_peek_tail(net->msg_cache); /* Remove Tail (oldest msg in cache) */ l_debug("Remove %4.4x + %6.6x + %8.8x", msg->src, msg->seq, msg->mic); if (l_queue_remove(net->msg_cache, msg)) l_free(msg); } return false; } static bool match_sar_seq0(const void *a, const void *b) { const struct mesh_sar *sar = a; uint16_t seqZero = L_PTR_TO_UINT(b); return sar->seqZero == seqZero; } static bool match_sar_remote(const void *a, const void *b) { const struct mesh_sar *sar = a; uint16_t remote = L_PTR_TO_UINT(b); return sar->remote == remote; } static bool match_msg_timeout(const void *a, const void *b) { const struct mesh_sar *sar = a; const struct l_timeout *msg_timeout = b; return sar->msg_timeout == msg_timeout; } static bool match_seg_timeout(const void *a, const void *b) { const struct mesh_sar *sar = a; const struct l_timeout *seg_timeout = b; return sar->seg_timeout == seg_timeout; } static bool match_dest_dst(const void *a, const void *b) { const struct mesh_destination *dest = a; uint16_t dst = L_PTR_TO_UINT(b); return dst == dest->dst; } static bool match_frnd_dst(const void *a, const void *b) { const struct mesh_friend *frnd = a; uint16_t dst = L_PTR_TO_UINT(b); int16_t i, grp_cnt = frnd->u.active.grp_cnt; uint16_t *grp_list = frnd->u.active.grp_list; /* * Determine if this message is for this friends unicast * address, and/or one of it's group/virtual addresses */ if (dst >= frnd->lp_addr && dst < (frnd->lp_addr + frnd->ele_cnt)) return true; if (!(dst & 0x8000)) return false; for (i = 0; i < grp_cnt; i++) { if (dst == grp_list[i]) return true; } return false; } static bool is_lpn_friend(struct mesh_net *net, uint16_t addr) { void *tst; tst = l_queue_find(net->friends, match_frnd_dst, L_UINT_TO_PTR(addr)); return tst != NULL; } static bool is_us(struct mesh_net *net, uint16_t addr, bool src) { void *tst; if (IS_ALL_NODES(addr)) return true; if (addr == FRIENDS_ADDRESS) return net->friend_enable; if (addr == RELAYS_ADDRESS) return net->relay.enable; if (addr == PROXIES_ADDRESS) return net->proxy_enable; if (addr >= net->src_addr && addr <= net->last_addr) return true; tst = l_queue_find(net->destinations, match_dest_dst, L_UINT_TO_PTR(addr)); if (tst == NULL && !src) tst = l_queue_find(net->friends, match_frnd_dst, L_UINT_TO_PTR(addr)); return tst != NULL; } static struct mesh_friend_msg *mesh_friend_msg_new(uint8_t seg_max) { struct mesh_friend_msg *frnd_msg; if (seg_max) { size_t size = sizeof(struct mesh_friend_msg) - sizeof(struct mesh_friend_seg_one); size += (seg_max + 1) * sizeof(struct mesh_friend_seg_12); frnd_msg = l_malloc(size); memset(frnd_msg, 0, size); } else frnd_msg = l_new(struct mesh_friend_msg, 1); return frnd_msg; } static bool match_ack(const void *a, const void *b) { const struct mesh_friend_msg *old = a; const struct mesh_friend_msg *rx = b; uint32_t old_hdr; uint32_t new_hdr; /* Determine if old pkt is ACK to same SAR message that new ACK is */ if (!old->ctl || old->src != rx->src) return false; /* Check the quickest items first before digging deeper */ old_hdr = old->u.one[0].hdr & HDR_ACK_MASK; new_hdr = rx->u.one[0].hdr & HDR_ACK_MASK; return old_hdr == new_hdr; } static void enqueue_friend_pkt(void *a, void *b) { struct mesh_friend *frnd = a; struct mesh_friend_msg *pkt, *rx = b; size_t size; int16_t i; if (rx->done) return; /* * Determine if this message is for this friends unicast * address, and/or one of it's group/virtual addresses */ if (rx->dst >= frnd->lp_addr && (rx->dst - frnd->lp_addr) < frnd->ele_cnt) { rx->done = true; goto enqueue; } if (!(rx->dst & 0x8000)) return; if (!IS_ALL_NODES(rx->dst)) { for (i = 0; i < frnd->u.active.grp_cnt; i++) { if (rx->dst == frnd->u.active.grp_list[i]) goto enqueue; } return; } enqueue: /* Special handling for Seg Ack -- Only one per message queue */ if (((rx->u.one[0].hdr >> OPCODE_HDR_SHIFT) & OPCODE_MASK) == NET_OP_SEG_ACKNOWLEDGE) { void *old_head = l_queue_peek_head(frnd->pkt_cache); /* Suppress duplicate ACKs */ do { void *old = l_queue_remove_if(frnd->pkt_cache, match_ack, rx); if (!old) break; if (old_head == old) /* * If we are discarding head for any * reason, reset FRND SEQ */ frnd->u.active.last = frnd->u.active.seq; l_free(old); } while (true); } l_debug("%s for %4.4x from %4.4x ttl: %2.2x (seq: %6.6x) (ctl: %d)", __func__, frnd->lp_addr, rx->src, rx->ttl, rx->u.one[0].seq, rx->ctl); if (rx->cnt_in) { size = sizeof(struct mesh_friend_msg) - sizeof(struct mesh_friend_seg_one); size += (rx->cnt_in + 1) * sizeof(struct mesh_friend_seg_12); } else size = sizeof(struct mesh_friend_msg); pkt = l_malloc(size); memcpy(pkt, rx, size); l_queue_push_tail(frnd->pkt_cache, pkt); if (l_queue_length(frnd->pkt_cache) > FRND_CACHE_MAX) { /* * TODO: Guard against popping UPDATE packets * (disallowed per spec) */ pkt = l_queue_pop_head(frnd->pkt_cache); l_free(pkt); frnd->u.active.last = frnd->u.active.seq; } } static void enqueue_update(void *a, void *b) { struct mesh_friend *frnd = a; struct mesh_friend_msg *pkt = b; pkt->dst = frnd->lp_addr; pkt->done = false; enqueue_friend_pkt(frnd, pkt); } static uint32_t seq_auth(uint32_t seq, uint16_t seqZero) { uint32_t seqAuth = seqZero & SEQ_ZERO_MASK; seqAuth |= seq & (~SEQ_ZERO_MASK); if (seqAuth > seq) seqAuth -= (SEQ_ZERO_MASK + 1); return seqAuth; } static bool friend_packet_queue(struct mesh_net *net, uint32_t iv_index, bool ctl, uint8_t ttl, uint32_t seq, uint16_t src, uint16_t dst, uint32_t hdr, const uint8_t *data, uint16_t size) { struct mesh_friend_msg *frnd_msg; uint8_t seg_max = SEG_TOTAL(hdr); bool ret; if (seg_max && !IS_SEGMENTED(hdr)) return false; frnd_msg = mesh_friend_msg_new(seg_max); if (IS_SEGMENTED(hdr)) { uint32_t seqAuth = seq_auth(seq, hdr >> SEQ_ZERO_HDR_SHIFT); uint8_t i; for (i = 0; i <= seg_max; i++) { memcpy(frnd_msg->u.s12[i].data, data, 12); frnd_msg->u.s12[i].hdr = hdr; frnd_msg->u.s12[i].seq = seqAuth + i; data += 12; hdr += (1 << SEGO_HDR_SHIFT); } frnd_msg->cnt_in = seg_max; frnd_msg->last_len = size % 12; if (!frnd_msg->last_len) frnd_msg->last_len = 12; } else { uint8_t opcode = hdr >> OPCODE_HDR_SHIFT; if (ctl && opcode != NET_OP_SEG_ACKNOWLEDGE) { /* Don't cache Friend Ctl opcodes */ if (FRND_OPCODE(opcode)) { l_free(frnd_msg); return false; } memcpy(frnd_msg->u.one[0].data + 1, data, size); frnd_msg->last_len = size + 1; frnd_msg->u.one[0].data[0] = opcode; } else { memcpy(frnd_msg->u.one[0].data, data, size); frnd_msg->last_len = size; } frnd_msg->u.one[0].hdr = hdr; frnd_msg->u.one[0].seq = seq; } frnd_msg->iv_index = iv_index; frnd_msg->src = src; frnd_msg->dst = dst; frnd_msg->ctl = ctl; frnd_msg->ttl = ttl; /* Re-Package into Friend Delivery payload */ l_queue_foreach(net->friends, enqueue_friend_pkt, frnd_msg); ret = frnd_msg->done; /* TODO Optimization(?): Unicast messages keep this buffer */ l_free(frnd_msg); return ret; } static void friend_ack_rxed(struct mesh_net *net, uint32_t iv_index, uint32_t seq, uint16_t src, uint16_t dst, const uint8_t *pkt) { uint32_t hdr = l_get_be32(pkt) & ((SEQ_ZERO_MASK << SEQ_ZERO_HDR_SHIFT) | /* Preserve SeqZero */ ((uint32_t) 0x01 << RELAY_HDR_SHIFT)); /* Preserve Relay bit */ uint32_t flags = l_get_be32(pkt + 3); struct mesh_friend_msg frnd_ack = { .ctl = true, .iv_index = iv_index, .src = src, .dst = dst, .last_len = sizeof(flags), .u.one[0].seq = seq, .done = false, }; hdr |= NET_OP_SEG_ACKNOWLEDGE << OPCODE_HDR_SHIFT; frnd_ack.u.one[0].hdr = hdr; l_put_be32(flags, frnd_ack.u.one[0].data); l_queue_foreach(net->friends, enqueue_friend_pkt, &frnd_ack); } static bool send_seg(struct mesh_net *net, uint8_t cnt, uint16_t interval, struct mesh_sar *msg, uint8_t seg); static void send_frnd_ack(struct mesh_net *net, uint16_t src, uint16_t dst, uint32_t hdr, uint32_t flags) { uint32_t expected; uint8_t msg[7]; /* We don't ACK from multicast destinations */ if (src & 0x8000) return; /* Calculate the "Full ACK" mask */ expected = 0xffffffff >> (31 - SEG_TOTAL(hdr)); /* Clear Hdr bits that don't apply to Seg ACK */ hdr &= ~(((uint32_t) 0x01 << SEG_HDR_SHIFT) | (OPCODE_MASK << OPCODE_HDR_SHIFT) | ((uint32_t) 0x01 << SZMIC_HDR_SHIFT) | (SEG_MASK << SEGO_HDR_SHIFT) | (SEG_MASK << SEGN_HDR_SHIFT)); hdr |= NET_OP_SEG_ACKNOWLEDGE << OPCODE_HDR_SHIFT; hdr |= (uint32_t) 0x01 << RELAY_HDR_SHIFT; /* Clear all unexpected bits */ flags &= expected; l_put_be32(hdr, msg); l_put_be32(flags, msg + 3); l_debug("Send Friend ACK to Segs: %8.8x", flags); if (is_lpn_friend(net, dst)) { /* If we are acking our LPN Friend, queue, don't send */ friend_ack_rxed(net, mesh_net_get_iv_index(net), mesh_net_next_seq_num(net), 0, dst, msg); } else { mesh_net_transport_send(net, 0, 0, mesh_net_get_iv_index(net), DEFAULT_TTL, 0, 0, dst, msg, sizeof(msg)); } } static void send_net_ack(struct mesh_net *net, struct mesh_sar *sar, uint32_t flags) { uint8_t msg[7]; uint32_t hdr; uint16_t src = sar->src; uint16_t dst = sar->remote; /* We don't ACK from multicast destinations */ if (src & 0x8000) return; hdr = NET_OP_SEG_ACKNOWLEDGE << OPCODE_HDR_SHIFT; hdr |= sar->seqZero << SEQ_ZERO_HDR_SHIFT; if (is_lpn_friend(net, src)) hdr |= (uint32_t) 0x01 << RELAY_HDR_SHIFT; l_put_be32(hdr, msg); l_put_be32(flags, msg + 3); l_debug("Send%s ACK to Segs: %8.8x", sar->frnd ? " Friend" : "", flags); if (is_lpn_friend(net, dst)) { /* If we are acking our LPN Friend, queue, don't send */ friend_ack_rxed(net, mesh_net_get_iv_index(net), mesh_net_next_seq_num(net), src, dst, msg); return; } mesh_net_transport_send(net, 0, sar->net_idx, mesh_net_get_iv_index(net), DEFAULT_TTL, 0, src, dst, msg, sizeof(msg)); } static void inseg_to(struct l_timeout *seg_timeout, void *user_data) { struct mesh_net *net = user_data; struct mesh_sar *sar = l_queue_find(net->sar_in, match_seg_timeout, seg_timeout); l_timeout_remove(seg_timeout); if (!sar) return; /* Send NAK */ l_debug("Timeout %p %3.3x", sar, sar->app_idx); send_net_ack(net, sar, sar->flags); sar->seg_timeout = l_timeout_create(SEG_TO, inseg_to, net, NULL); } static void inmsg_to(struct l_timeout *msg_timeout, void *user_data) { struct mesh_net *net = user_data; struct mesh_sar *sar = l_queue_find(net->sar_in, match_msg_timeout, msg_timeout); if (!sar) { l_timeout_remove(msg_timeout); return; } if (!sar->delete) { /* * Incomplete timer expired, cancel SAR and start * delete timer */ l_timeout_remove(sar->seg_timeout); sar->seg_timeout = NULL; sar->delete = true; l_timeout_modify(sar->msg_timeout, SAR_DEL); return; } l_queue_remove(net->sar_in, sar); mesh_sar_free(sar); } static void outmsg_to(struct l_timeout *msg_timeout, void *user_data) { struct mesh_net *net = user_data; struct mesh_sar *sar = l_queue_remove_if(net->sar_out, match_msg_timeout, msg_timeout); l_timeout_remove(msg_timeout); if (!sar) return; sar->msg_timeout = NULL; mesh_sar_free(sar); } static void outseg_to(struct l_timeout *seg_timeout, void *user_data); static void send_queued_sar(struct mesh_net *net, uint16_t dst) { struct mesh_sar *sar = l_queue_remove_if(net->sar_queue, match_sar_remote, L_UINT_TO_PTR(dst)); if (!sar) return; /* Out to current outgoing, and immediate expire Seg TO */ l_queue_push_head(net->sar_out, sar); sar->seg_timeout = NULL; sar->msg_timeout = l_timeout_create(MSG_TO, outmsg_to, net, NULL); outseg_to(NULL, net); } static void ack_received(struct mesh_net *net, bool timeout, uint16_t src, uint16_t dst, uint16_t seq0, uint32_t ack_flag) { struct mesh_sar *outgoing; uint32_t seg_flag = 0x00000001; uint16_t i; l_debug("ACK Rxed (%x) (to:%d): %8.8x", seq0, timeout, ack_flag); outgoing = l_queue_find(net->sar_out, match_sar_seq0, L_UINT_TO_PTR(seq0)); if (!outgoing) { l_debug("Not Found: %4.4x", seq0); return; } /* * TODO -- If we receive from different * SRC than we are sending to, make sure the OBO flag is set */ if ((!timeout && !ack_flag) || (outgoing->flags & ack_flag) == outgoing->flags) { l_debug("ob_sar_removal (%x)", outgoing->flags); /* Note: ack_flags == 0x00000000 is a remote Cancel request */ l_queue_remove(net->sar_out, outgoing); send_queued_sar(net, outgoing->remote); mesh_sar_free(outgoing); return; } outgoing->last_nak |= ack_flag; for (i = 0; i <= SEG_MAX(true, outgoing->len); i++, seg_flag <<= 1) { if (seg_flag & ack_flag) { l_debug("Skipping Seg %d of %d", i, SEG_MAX(true, outgoing->len)); continue; } l_debug("Resend Seg %d net:%p dst:%x app_idx:%3.3x", i, net, outgoing->remote, outgoing->app_idx); send_seg(net, net->tx_cnt, net->tx_interval, outgoing, i); } l_timeout_remove(outgoing->seg_timeout); outgoing->seg_timeout = l_timeout_create(SEG_TO, outseg_to, net, NULL); } static void outseg_to(struct l_timeout *seg_timeout, void *user_data) { struct mesh_net *net = user_data; struct mesh_sar *sar = l_queue_find(net->sar_out, match_seg_timeout, seg_timeout); l_timeout_remove(seg_timeout); if (!sar) return; sar->seg_timeout = NULL; /* Re-Send missing segments by faking NACK */ ack_received(net, true, sar->remote, sar->src, sar->seqZero, sar->last_nak); } static bool match_replay_cache(const void *a, const void *b) { const struct mesh_rpl *rpe = a; uint16_t src = L_PTR_TO_UINT(b); return src == rpe->src; } static bool clean_old_iv_index(void *a, void *b) { struct mesh_rpl *rpe = a; uint32_t iv_index = L_PTR_TO_UINT(b); if (iv_index < 2) return false; if (rpe->iv_index < iv_index - 1) { l_free(rpe); return true; } return false; } static bool msg_check_replay_cache(struct mesh_net *net, uint16_t src, uint16_t crpl, uint32_t seq, uint32_t iv_index) { struct mesh_rpl *rpe; /* If anything missing reject this message by returning true */ if (!net || !net->node) return true; rpe = l_queue_find(net->replay_cache, match_replay_cache, L_UINT_TO_PTR(src)); if (rpe) { if (iv_index > rpe->iv_index) return false; /* Return true if (iv_index | seq) too low */ if (iv_index < rpe->iv_index || seq <= rpe->seq) { l_debug("Ignoring replayed packet"); return true; } } else if (l_queue_length(net->replay_cache) >= crpl) { /* SRC not in Replay Cache... see if there is space for it */ int ret = l_queue_foreach_remove(net->replay_cache, clean_old_iv_index, L_UINT_TO_PTR(iv_index)); /* Return true if no space could be freed */ if (!ret) { l_debug("Replay cache full"); return true; } } return false; } static void msg_add_replay_cache(struct mesh_net *net, uint16_t src, uint32_t seq, uint32_t iv_index) { struct mesh_rpl *rpe; if (!net || !net->replay_cache) return; rpe = l_queue_remove_if(net->replay_cache, match_replay_cache, L_UINT_TO_PTR(src)); if (!rpe) { rpe = l_new(struct mesh_rpl, 1); rpe->src = src; } rpe->seq = seq; rpe->iv_index = iv_index; rpl_put_entry(net->node, src, iv_index, seq); /* Optimize so that most recent conversations stay earliest in cache */ l_queue_push_head(net->replay_cache, rpe); } static bool msg_rxed(struct mesh_net *net, bool frnd, uint32_t iv_index, uint8_t ttl, uint32_t seq, uint16_t net_idx, uint16_t src, uint16_t dst, uint8_t key_aid, bool segmented, bool szmic, uint16_t seqZero, const uint8_t *data, uint16_t size) { uint32_t seqAuth = seq_auth(seq, seqZero); uint16_t crpl; /* Sanity check seqAuth */ if (seqAuth > seq) return false; /* Save un-decrypted messages for our friends */ if (!frnd && l_queue_length(net->friends)) { uint32_t hdr = key_aid << KEY_HDR_SHIFT; uint8_t frnd_ttl = ttl; /* If not from us, decrement for our hop */ if (src < net->src_addr || src > net->last_addr) { if (frnd_ttl > 1) frnd_ttl--; else goto not_for_friend; } if (szmic || size > 15) { hdr |= (uint32_t) 0x01 << SEG_HDR_SHIFT; hdr |= szmic << SZMIC_HDR_SHIFT; hdr |= (seqZero & SEQ_ZERO_MASK) << SEQ_ZERO_HDR_SHIFT; hdr |= SEG_MAX(true, size) << SEGN_HDR_SHIFT; } if (friend_packet_queue(net, iv_index, false, frnd_ttl, seq, src, dst, hdr, data, size)) return true; } not_for_friend: if (dst == FRIENDS_ADDRESS && !net->friend_enable) return false; if (dst == RELAYS_ADDRESS && !net->relay.enable) return false; if (dst == PROXIES_ADDRESS && !net->proxy_enable) return false; /* Don't process if already in RPL */ crpl = node_get_crpl(net->node); if (msg_check_replay_cache(net, src, crpl, seq, iv_index)) return false; if (!mesh_model_rx(net->node, szmic, seqAuth, iv_index, net_idx, src, dst, key_aid, data, size)) return false; /* If message has been handled by us, add to RPL */ msg_add_replay_cache(net, src, seq, iv_index); return true; } static uint16_t key_id_to_net_idx(struct mesh_net *net, uint32_t net_key_id, bool *frnd) { struct mesh_subnet *subnet; struct mesh_friend *friend; if (!net) return NET_IDX_INVALID; if (frnd) *frnd = false; subnet = l_queue_find(net->subnets, match_key_id, L_UINT_TO_PTR(net_key_id)); if (subnet) return subnet->idx; friend = l_queue_find(net->friends, match_friend_key_id, L_UINT_TO_PTR(net_key_id)); if (friend) { if (frnd) *frnd = true; return friend->net_idx; } friend = l_queue_find(net->negotiations, match_friend_key_id, L_UINT_TO_PTR(net_key_id)); if (friend) return friend->net_idx; else return NET_IDX_INVALID; } static bool match_frnd_sar_dst(const void *a, const void *b) { const struct mesh_friend_msg *frnd_msg = a; uint16_t dst = L_PTR_TO_UINT(b); return frnd_msg->dst == dst; } static void friend_seg_rxed(struct mesh_net *net, uint32_t iv_index, uint8_t ttl, uint32_t seq, uint16_t src, uint16_t dst, uint32_t hdr, const uint8_t *data, uint8_t size) { struct mesh_friend *frnd = NULL; struct mesh_friend_msg *frnd_msg = NULL; uint8_t cnt; uint8_t segN = hdr & 0x1f; uint8_t segO = ((hdr >> 5) & 0x1f); uint32_t expected = 0xffffffff >> (31 - segN); uint32_t this_seg_flag = 0x00000001 << segO; uint32_t largest = (0xffffffff << segO) & expected; uint32_t hdr_key = hdr & HDR_KEY_MASK; frnd = l_queue_find(net->friends, match_frnd_dst, L_UINT_TO_PTR(dst)); if (!frnd) return; if (frnd->u.active.last_hdr == hdr_key) { /* We are no longer receiving this msg. Resend final ACK */ send_frnd_ack(net, dst, src, frnd->u.active.last_hdr, 0xffffffff); return; } /* Check if we have a SAR-in-progress that matches incoming segment */ frnd_msg = l_queue_find(net->frnd_msgs, match_frnd_sar_dst, L_UINT_TO_PTR(dst)); if (frnd_msg) { /* Flush if SZMICN or IV Index has changed */ if (frnd_msg->iv_index != iv_index) frnd_msg->u.s12[0].hdr = 0; /* Flush incomplete old SAR message if it doesn't match */ if ((frnd_msg->u.s12[0].hdr & HDR_KEY_MASK) != hdr_key) { l_queue_remove(net->frnd_msgs, frnd_msg); l_free(frnd_msg); frnd_msg = NULL; } } if (!frnd_msg) { frnd_msg = mesh_friend_msg_new(segN); frnd_msg->iv_index = iv_index; frnd_msg->src = src; frnd_msg->dst = dst; frnd_msg->ttl = ttl; l_queue_push_tail(net->frnd_msgs, frnd_msg); } else if (frnd_msg->flags & this_seg_flag) /* Ignore dup segs */ return; cnt = frnd_msg->cnt_in; frnd_msg->flags |= this_seg_flag; frnd_msg->u.s12[cnt].hdr = hdr; frnd_msg->u.s12[cnt].seq = seq; memcpy(frnd_msg->u.s12[cnt].data, data, size); /* Last segment could be short */ if (segN == segO) frnd_msg->last_len = size; l_debug("RXed Seg %d, Flags %8.8x (cnt: %d)", segO, frnd_msg->flags, cnt); /* In reality, if one of these is true, then *both* must be true */ if ((cnt == segN) || (frnd_msg->flags == expected)) { l_debug("Full ACK"); send_frnd_ack(net, dst, src, hdr, frnd_msg->flags); if (frnd_msg->ttl > 1) { frnd_msg->ttl--; /* Add to friends cache */ l_queue_foreach(net->friends, enqueue_friend_pkt, frnd_msg); } /* Remove from "in progress" queue */ l_queue_remove(net->frnd_msgs, frnd_msg); /* TODO Optimization(?): Unicast messages keep this buffer */ l_free(frnd_msg); return; } /* Always ACK if this is the largest outstanding segment */ if ((largest & frnd_msg->flags) == largest) { l_debug("Partial ACK"); send_frnd_ack(net, dst, src, hdr, frnd_msg->flags); } frnd_msg->cnt_in++; } static bool seg_rxed(struct mesh_net *net, bool frnd, uint32_t iv_index, uint8_t ttl, uint32_t seq, uint16_t net_idx, uint16_t src, uint16_t dst, uint8_t key_aid, bool szmic, uint16_t seqZero, uint8_t segO, uint8_t segN, const uint8_t *data, uint8_t size) { struct mesh_sar *sar_in = NULL; uint16_t seg_off = 0; uint32_t expected, this_seg_flag, largest, seqAuth; bool reset_seg_to = true; /* * DST could receive additional Segments after * completing due to a lost ACK, so re-ACK and discard */ sar_in = l_queue_find(net->sar_in, match_sar_remote, L_UINT_TO_PTR(src)); /* Discard *old* incoming-SAR-in-progress if this segment newer */ seqAuth = seq_auth(seq, seqZero); if (sar_in && (sar_in->seqAuth != seqAuth || sar_in->iv_index != iv_index)) { bool newer; if (iv_index > sar_in->iv_index) newer = true; else if (iv_index == sar_in->iv_index) newer = seqAuth > sar_in->seqAuth; else newer = false; if (newer) { /* Cancel Old, start New */ l_queue_remove(net->sar_in, sar_in); mesh_sar_free(sar_in); sar_in = NULL; } else /* Ignore Old */ return false; } expected = 0xffffffff >> (31 - segN); if (sar_in) { l_debug("RXed (old: %04x %06x size:%d) %d of %d", seqZero, seq, size, segO, segN); /* Sanity Check--> certain things must match */ if (SEG_MAX(true, sar_in->len) != segN || sar_in->key_aid != key_aid) return false; if (sar_in->flags == expected) { /* Re-Send ACK for full msg */ send_net_ack(net, sar_in, expected); return true; } else if (sar_in->delete) /* Ignore cancelled */ return false; } else { uint16_t len = MAX_SEG_TO_LEN(segN); l_debug("RXed (new: %04x %06x size: %d len: %d) %d of %d", seqZero, seq, size, len, segO, segN); l_debug("Queue Size: %d", l_queue_length(net->sar_in)); sar_in = mesh_sar_new(len); sar_in->seqAuth = seqAuth; sar_in->iv_index = iv_index; sar_in->src = dst; sar_in->remote = src; sar_in->seqZero = seqZero; sar_in->key_aid = key_aid; sar_in->len = len; sar_in->last_seg = 0xff; sar_in->net_idx = net_idx; sar_in->msg_timeout = l_timeout_create(MSG_TO, inmsg_to, net, NULL); l_debug("First Seg %4.4x", sar_in->flags); l_queue_push_head(net->sar_in, sar_in); } seg_off = segO * MAX_SEG_LEN; memcpy(sar_in->buf + seg_off, data, size); this_seg_flag = 0x00000001 << segO; /* Don't reset Seg TO or NAK if we already have this seg */ if (this_seg_flag & sar_in->flags) reset_seg_to = false; sar_in->flags |= this_seg_flag; sar_in->ttl = ttl; /* Msg length only definitive on last segment */ if (segO == segN) sar_in->len = segN * MAX_SEG_LEN + size; if (sar_in->flags == expected) { /* Got it all */ send_net_ack(net, sar_in, expected); msg_rxed(net, frnd, iv_index, ttl, seq, net_idx, sar_in->remote, dst, key_aid, true, szmic, sar_in->seqZero, sar_in->buf, sar_in->len); /* Kill Inter-Seg timeout */ l_timeout_remove(sar_in->seg_timeout); sar_in->seg_timeout = NULL; /* Start delete timer */ sar_in->delete = true; l_timeout_modify(sar_in->msg_timeout, SAR_DEL); return true; } if (reset_seg_to) { /* Restart Inter-Seg Timeout */ l_timeout_remove(sar_in->seg_timeout); /* if this is the largest outstanding segment, send NAK now */ largest = (0xffffffff << segO) & expected; if ((largest & sar_in->flags) == largest) send_net_ack(net, sar_in, sar_in->flags); sar_in->seg_timeout = l_timeout_create(SEG_TO, inseg_to, net, NULL); } else largest = 0; l_debug("NAK: %d expected:%08x largest:%08x flags:%08x", reset_seg_to, expected, largest, sar_in->flags); return false; } static bool ctl_received(struct mesh_net *net, uint32_t net_key_id, uint32_t iv_index, uint8_t ttl, uint32_t seq, uint16_t src, uint16_t dst, uint8_t opcode, int8_t rssi, const uint8_t *pkt, uint8_t len) { uint8_t msg[12]; uint8_t rsp_ttl = DEFAULT_TTL; uint8_t n = 0; uint16_t net_idx; if (ttl > 1) { uint32_t hdr = opcode << OPCODE_HDR_SHIFT; uint8_t frnd_ttl = ttl - 1; if (friend_packet_queue(net, iv_index, true, frnd_ttl, seq, src, dst, hdr, pkt, len)) return true; } /* Don't process other peoples Unicast destinations */ if (dst < 0x8000 && (dst < net->src_addr || dst > net->last_addr)) return false; switch (opcode) { default: l_error("Unsupported Ctl Opcode: %2.2x", opcode); break; case NET_OP_FRND_POLL: if (len != 1 || ttl || pkt[0] > 1) return false; print_packet("Rx-NET_OP_FRND_POLL", pkt, len); friend_poll(net, src, !!(pkt[0]), l_queue_find(net->friends, match_by_friend, L_UINT_TO_PTR(src))); break; case NET_OP_FRND_REQUEST: if (!net->friend_enable) return false; if (!IS_ALL_NODES(dst) && dst != FRIENDS_ADDRESS) return false; if (len != 10 || ttl) return false; print_packet("Rx-NET_OP_FRND_REQUEST", pkt, len); net_idx = key_id_to_net_idx(net, net_key_id, NULL); friend_request(net, net_idx, src, pkt[0], pkt[1], l_get_be32(pkt + 1) & 0xffffff, l_get_be16(pkt + 5), pkt[7], l_get_be16(pkt + 8), rssi); break; case NET_OP_FRND_CLEAR_CONFIRM: if (len != 4) return false; print_packet("Rx-NET_OP_FRND_CLEAR_CONFIRM", pkt, len); friend_clear_confirm(net, src, l_get_be16(pkt), l_get_be16(pkt + 2)); break; case NET_OP_FRND_CLEAR: if (len != 4 || dst != net->src_addr) return false; print_packet("Rx-NET_OP_FRND_CLEAR", pkt, len); friend_clear(net, src, l_get_be16(pkt), l_get_be16(pkt + 2), l_queue_find(net->friends, match_by_friend, L_UINT_TO_PTR(l_get_be16(pkt)))); l_debug("Remaining Friends: %d", l_queue_length(net->friends)); break; case NET_OP_PROXY_SUB_ADD: if (ttl) return false; print_packet("Rx-NET_OP_PROXY_SUB_ADD", pkt, len); friend_sub_add(net, l_queue_find(net->friends, match_by_friend, L_UINT_TO_PTR(src)), pkt, len); break; case NET_OP_PROXY_SUB_REMOVE: if (ttl) return false; print_packet("Rx-NET_OP_PROXY_SUB_REMOVE", pkt, len); friend_sub_del(net, l_queue_find(net->friends, match_by_friend, L_UINT_TO_PTR(src)), pkt, len); break; case NET_OP_PROXY_SUB_CONFIRM: if (ttl) return false; print_packet("Rx-NET_OP_PROXY_SUB_CONFIRM", pkt, len); break; case NET_OP_HEARTBEAT: if (net->hb_sub.enabled && src == net->hb_sub.src) { uint8_t hops = pkt[0] - ttl + 1; print_packet("Rx-NET_OP_HEARTBEAT", pkt, len); if (net->hb_sub.count != 0xffff) net->hb_sub.count++; if (net->hb_sub.min_hops > hops) net->hb_sub.min_hops = hops; if (net->hb_sub.max_hops < hops) net->hb_sub.max_hops = hops; l_debug("HB: cnt:%4.4x min:%2.2x max:%2.2x", net->hb_sub.count, net->hb_sub.min_hops, net->hb_sub.max_hops); } break; } if (n) mesh_net_transport_send(net, 0, 0, mesh_net_get_iv_index(net), rsp_ttl, 0, dst & 0x8000 ? 0 : dst, src, msg, n); return true; } static bool find_fast_hash(const void *a, const void *b) { const uint64_t *entry = a; const uint64_t *test = b; return *entry == *test; } static bool check_fast_cache(uint64_t hash) { void *found = l_queue_find(fast_cache, find_fast_hash, &hash); uint64_t *new_hash; if (found) return false; if (l_queue_length(fast_cache) >= FAST_CACHE_SIZE) new_hash = l_queue_pop_head(fast_cache); else new_hash = l_malloc(sizeof(hash)); *new_hash = hash; l_queue_push_tail(fast_cache, new_hash); return true; } static bool match_by_dst(const void *a, const void *b) { const struct mesh_destination *dest = a; uint16_t dst = L_PTR_TO_UINT(b); return dest->dst == dst; } static void send_relay_pkt(struct mesh_net *net, uint8_t *data, uint8_t size) { uint8_t packet[MESH_AD_MAX_LEN]; struct mesh_io *io = net->io; struct mesh_io_send_info info = { .type = MESH_IO_TIMING_TYPE_GENERAL, .u.gen.interval = net->relay.interval, .u.gen.cnt = net->relay.count, .u.gen.min_delay = DEFAULT_MIN_DELAY, .u.gen.max_delay = DEFAULT_MAX_DELAY }; packet[0] = BT_AD_MESH_DATA; memcpy(packet + 1, data, size); mesh_io_send(io, &info, packet, size + 1); } static bool simple_match(const void *a, const void *b) { return a == b; } static void send_msg_pkt_oneshot(void *user_data) { struct oneshot_tx *tx = user_data; struct mesh_net *net; struct mesh_io_send_info info; struct net_queue_data net_data = { .info = NULL, .data = tx->packet + 1, .len = tx->size - 1, .relay_advice = RELAY_NONE, }; /* Send to local nodes first */ l_queue_foreach(nets, net_rx, &net_data); /* Make sure specific network still valid */ net = l_queue_find(nets, simple_match, tx->net); if (!net || net_data.relay_advice == RELAY_DISALLOWED) { l_free(tx); return; } info.type = MESH_IO_TIMING_TYPE_GENERAL; info.u.gen.interval = tx->interval; info.u.gen.cnt = tx->cnt; info.u.gen.min_delay = DEFAULT_MIN_DELAY; /* No extra randomization when sending regular mesh messages */ info.u.gen.max_delay = DEFAULT_MIN_DELAY; mesh_io_send(net->io, &info, tx->packet, tx->size); l_free(tx); } static void send_msg_pkt(struct mesh_net *net, uint8_t cnt, uint16_t interval, const uint8_t *packet, uint8_t size) { struct oneshot_tx *tx = l_new(struct oneshot_tx, 1); tx->net = net; tx->interval = interval; tx->cnt = cnt; tx->size = size; memcpy(tx->packet, packet, size); l_idle_oneshot(send_msg_pkt_oneshot, tx, NULL); } static enum _relay_advice packet_received(struct mesh_net *net, uint32_t net_key_id, uint16_t net_idx, bool frnd, uint32_t iv_index, const uint8_t *data, uint8_t size, int8_t rssi) { const uint8_t *msg; uint8_t app_msg_len; uint8_t net_ttl, key_aid, net_segO, net_segN, net_opcode; uint32_t net_seq, cache_cookie; uint16_t net_src, net_dst, net_seqZero; bool net_ctl, net_segmented, net_szmic, net_relay; print_packet("RX: Network [clr] :", data, size); if (!mesh_crypto_packet_parse(data, size, &net_ctl, &net_ttl, &net_seq, &net_src, &net_dst, &cache_cookie, &net_opcode, &net_segmented, &key_aid, &net_szmic, &net_relay, &net_seqZero, &net_segO, &net_segN, &msg, &app_msg_len)) { l_error("Failed to parse packet content"); return RELAY_NONE; } if (net_dst == 0) { l_error("illegal parms: DST: %4.4x Ctl: %d TTL: %2.2x", net_dst, net_ctl, net_ttl); return RELAY_NONE; } /* Ignore if we originally sent this */ if (is_us(net, net_src, true)) return RELAY_NONE; /* * As a Relay, suppress repeats of last N packets that pass through * The "cache_cookie" should be unique part of App message. */ if (msg_in_cache(net, net_src, net_seq, cache_cookie)) return RELAY_NONE; l_debug("RX: Network %04x -> %04x : TTL 0x%02x : IV : %8.8x SEQ 0x%06x", net_src, net_dst, net_ttl, iv_index, net_seq); if (is_us(net, net_dst, false) || (net_ctl && net_opcode == NET_OP_HEARTBEAT)) { l_debug("RX: App 0x%04x -> 0x%04x : TTL 0x%02x : SEQ 0x%06x", net_src, net_dst, net_ttl, net_seq); if (net_ctl) { l_debug("CTL - %4.4x RX", net_seqZero); if (net_opcode == NET_OP_SEG_ACKNOWLEDGE) { /* Illegal to send ACK to non-Unicast Addr */ if (net_dst & 0x8000) return RELAY_NONE; /* Pedantic check for correct size */ if (app_msg_len != 7) return RELAY_NONE; /* If this is an ACK to our friend queue-only */ if (is_lpn_friend(net, net_dst)) friend_ack_rxed(net, iv_index, net_seq, net_src, net_dst, msg); else ack_received(net, false, net_src, net_dst, net_seqZero, l_get_be32(msg + 3)); } else { ctl_received(net, net_key_id, iv_index, net_ttl, net_seq, net_src, net_dst, net_opcode, rssi, msg, app_msg_len); } } else if (net_segmented) { /* * If we accept SAR packets to non-Unicast, then * Friend Sar at least needs to be Unicast Only */ if (is_lpn_friend(net, net_dst) && !(net_dst & 0x8000)) { /* * Check TTL >= 2 before accepting segments * for Friends */ if (net_ttl >= 2) { friend_seg_rxed(net, iv_index, net_ttl, net_seq, net_src, net_dst, l_get_be32(data + 9), msg, app_msg_len); } } else { seg_rxed(net, NULL, iv_index, net_ttl, net_seq, net_idx, net_src, net_dst, key_aid, net_szmic, net_seqZero, net_segO, net_segN, msg, app_msg_len); } } else { msg_rxed(net, NULL, iv_index, net_ttl, net_seq, net_idx, net_src, net_dst, key_aid, false, false, net_seq & SEQ_ZERO_MASK, msg, app_msg_len); } /* If this is one of our Unicast addresses, disallow relay */ if (IS_UNICAST(net_dst)) return RELAY_DISALLOWED; } /* * Messages that are encrypted with friendship credentials * should *always* be relayed */ if (frnd) return RELAY_ALWAYS; /* If relay not enable, or no more hops allowed */ if (!net->relay.enable || net_ttl < 0x02) return RELAY_NONE; /* Group or Virtual destinations should *always* be relayed */ if (IS_GROUP(net_dst) || IS_VIRTUAL(net_dst)) return RELAY_ALWAYS; /* Unicast destinations for other nodes *may* be relayed */ else if (IS_UNICAST(net_dst)) return RELAY_ALLOWED; /* Otherwise, do not make a relay decision */ else return RELAY_NONE; } static void net_rx(void *net_ptr, void *user_data) { struct net_queue_data *data = user_data; struct mesh_net *net = net_ptr; enum _relay_advice relay_advice; uint8_t *out; size_t out_size; uint32_t net_key_id; uint16_t net_idx; int8_t rssi = 0; bool frnd; bool ivi_net = !!(net->iv_index & 1); bool ivi_pkt = !!(data->data[0] & 0x80); /* if IVI flag differs, use previous IV Index */ uint32_t iv_index = net->iv_index - (ivi_pkt ^ ivi_net); net_key_id = net_key_decrypt(iv_index, data->data, data->len, &out, &out_size); if (!net_key_id) return; if (!data->seen) { data->seen = true; print_packet("RX: Network [enc] :", data->data, data->len); } if (data->info) { net->instant = data->info->instant; net->chan = data->info->chan; rssi = data->info->rssi; } net_idx = key_id_to_net_idx(net, net_key_id, &frnd); if (net_idx == NET_IDX_INVALID) return; relay_advice = packet_received(net, net_key_id, net_idx, frnd, iv_index, out, out_size, rssi); if (relay_advice > data->relay_advice) { /* * If packet was encrypted with friendship credentials, * relay it using flooding credentials */ if (frnd && !mesh_net_get_key(net, false, net_idx, &net_key_id)) return; data->iv_index = iv_index; data->relay_advice = relay_advice; data->net_key_id = net_key_id; data->net = net; data->out = out; data->out_size = out_size; } } static void net_msg_recv(void *user_data, struct mesh_io_recv_info *info, const uint8_t *data, uint16_t len) { uint64_t hash; bool isNew; struct net_queue_data net_data = { .info = info, .data = data + 1, .len = len - 1, .relay_advice = RELAY_NONE, .seen = false, }; if (len < 9) return; hash = l_get_le64(data + 1); /* Only process packet once per reception */ isNew = check_fast_cache(hash); if (!isNew) return; l_queue_foreach(nets, net_rx, &net_data); if (net_data.relay_advice == RELAY_ALWAYS || net_data.relay_advice == RELAY_ALLOWED) { uint8_t ttl = net_data.out[1] & TTL_MASK; net_data.out[1] &= ~TTL_MASK; net_data.out[1] |= ttl - 1; net_key_encrypt(net_data.net_key_id, net_data.iv_index, net_data.out, net_data.out_size); send_relay_pkt(net_data.net, net_data.out, net_data.out_size); } } static void iv_upd_to(struct l_timeout *upd_timeout, void *user_data) { struct mesh_net *net = user_data; switch (net->iv_upd_state) { case IV_UPD_UPDATING: if (l_queue_length(net->sar_out) || l_queue_length(net->sar_queue)) { l_debug("don't leave IV Update until sar_out empty"); l_timeout_modify(net->iv_update_timeout, 10); break; } l_debug("iv_upd_state = IV_UPD_NORMAL_HOLD"); net->iv_upd_state = IV_UPD_NORMAL_HOLD; l_timeout_modify(net->iv_update_timeout, IV_IDX_UPD_MIN); if (net->iv_update) mesh_net_set_seq_num(net, 0); net->iv_update = false; mesh_config_write_iv_index(node_config_get(net->node), net->iv_index, false); l_queue_foreach(net->subnets, refresh_beacon, net); queue_friend_update(net); l_queue_clear(net->msg_cache, l_free); break; case IV_UPD_INIT: case IV_UPD_NORMAL_HOLD: case IV_UPD_NORMAL: l_timeout_remove(upd_timeout); net->iv_update_timeout = NULL; l_debug("iv_upd_state = IV_UPD_NORMAL"); net->iv_upd_state = IV_UPD_NORMAL; if (net->iv_update) mesh_net_set_seq_num(net, 0); net->iv_update = false; if (net->seq_num > IV_UPDATE_SEQ_TRIGGER) mesh_net_iv_index_update(net); break; } } static int key_refresh_phase_two(struct mesh_net *net, uint16_t idx) { struct mesh_subnet *subnet; if (!net) return MESH_STATUS_UNSPECIFIED_ERROR; subnet = l_queue_find(net->subnets, match_key_index, L_UINT_TO_PTR(idx)); if (!subnet || !subnet->net_key_upd) return MESH_STATUS_INVALID_NETKEY; l_debug("Key refresh procedure phase 2: start using new net TX keys"); if (subnet->kr_phase == KEY_REFRESH_PHASE_TWO) return MESH_STATUS_SUCCESS; /* Stop beaconing on old key */ if (net->snb_enable) net_key_beacon_disable(subnet->net_key_tx, false); if (net->mpb_enable) net_key_beacon_disable(subnet->net_key_tx, true); subnet->key_refresh = 1; subnet->net_key_tx = subnet->net_key_upd; /* * TODO: Provisioner may need to stay in phase three until * it hears beacons from all the nodes */ subnet->kr_phase = KEY_REFRESH_PHASE_TWO; /* Start beaconing on new key */ if (net->snb_enable) net_key_beacon_enable(subnet->net_key_tx, false, 0); if (net->mpb_enable) net_key_beacon_enable(subnet->net_key_tx, true, net->mpb_period); refresh_beacon(subnet, net); queue_friend_update(net); l_queue_foreach(net->friends, frnd_kr_phase2, net); if (!mesh_config_net_key_set_phase(node_config_get(net->node), idx, KEY_REFRESH_PHASE_TWO)) return MESH_STATUS_STORAGE_FAIL; return MESH_STATUS_SUCCESS; } static int key_refresh_finish(struct mesh_net *net, uint16_t idx) { struct mesh_subnet *subnet; if (!net) return MESH_STATUS_UNSPECIFIED_ERROR; subnet = l_queue_find(net->subnets, match_key_index, L_UINT_TO_PTR(idx)); if (!subnet || !subnet->net_key_upd) return MESH_STATUS_INVALID_NETKEY; if (subnet->kr_phase == KEY_REFRESH_PHASE_NONE) return MESH_STATUS_SUCCESS; l_debug("Key refresh phase 3: use new keys only, discard old ones"); /* Switch to using new keys, discard old ones */ net_key_unref(subnet->net_key_cur); subnet->net_key_tx = subnet->net_key_cur = subnet->net_key_upd; subnet->net_key_upd = 0; subnet->key_refresh = 0; subnet->kr_phase = KEY_REFRESH_PHASE_NONE; refresh_beacon(subnet, net); queue_friend_update(net); l_queue_foreach(net->friends, frnd_kr_phase3, net); appkey_finalize(net, idx); if (!mesh_config_net_key_set_phase(node_config_get(net->node), idx, KEY_REFRESH_PHASE_NONE)) return MESH_STATUS_STORAGE_FAIL; return MESH_STATUS_SUCCESS; } static bool update_kr_state(struct mesh_subnet *subnet, bool kr, uint32_t id) { /* Figure out the key refresh phase */ if (kr) { if (subnet->kr_phase == KEY_REFRESH_PHASE_ONE && id == subnet->net_key_upd) { l_debug("Beacon based KR phase 2 change"); return (key_refresh_phase_two(subnet->net, subnet->idx) == MESH_STATUS_SUCCESS); } } else { if (id == subnet->net_key_upd) { l_debug("Beacon based KR phase 3 change"); return (key_refresh_finish(subnet->net, subnet->idx) == MESH_STATUS_SUCCESS); } } return false; } static bool update_iv_ivu_state(struct mesh_net *net, uint32_t iv_index, bool ivu) { if ((iv_index - ivu) > (net->iv_index - net->iv_update)) { /* Don't accept IV_Index changes when performing SAR Out */ if (l_queue_length(net->sar_out)) return false; } /* If first beacon seen, accept without judgement */ if (net->iv_upd_state == IV_UPD_INIT) { if (ivu) { /* Ignore beacons with IVU if IV already updated */ if (iv_index == net->iv_index && !net->iv_update) return false; /* * Other devices will be accepting old or new iv_index, * but we don't know how far through update they are. * Starting permissive state will allow us maximum * (96 hours) to resync */ l_debug("iv_upd_state = IV_UPD_UPDATING"); net->iv_upd_state = IV_UPD_UPDATING; net->iv_update_timeout = l_timeout_create( IV_IDX_UPD_MIN, iv_upd_to, net, NULL); } else { l_debug("iv_upd_state = IV_UPD_NORMAL"); net->iv_upd_state = IV_UPD_NORMAL; } } else if (ivu) { /* Ignore beacons with IVU if they come too soon */ if (!net->iv_update && net->iv_upd_state == IV_UPD_NORMAL_HOLD) { l_error("Update attempted too soon"); return false; } /* Ignore beacons with IVU if IV already updated */ if (iv_index == net->iv_index) return false; /* Ignore beacon with invalid IV index value */ if (net->iv_update && iv_index == net->iv_index + 1) return false; if (!net->iv_update) { l_debug("iv_upd_state = IV_UPD_UPDATING"); net->iv_upd_state = IV_UPD_UPDATING; net->iv_update_timeout = l_timeout_create( IV_IDX_UPD_MIN, iv_upd_to, net, NULL); } } else if (net->iv_update) { l_error("IVU clear attempted too soon"); return false; } if ((iv_index - ivu) > (net->iv_index - net->iv_update)) mesh_net_set_seq_num(net, 0); if (ivu != net->iv_update || iv_index != net->iv_index) { struct mesh_config *cfg = node_config_get(net->node); mesh_config_write_iv_index(cfg, iv_index, ivu); /* Cleanup Replay Protection List NVM */ rpl_update(net->node, iv_index); } node_property_changed(net->node, "IVIndex"); net->iv_index = iv_index; net->iv_update = ivu; queue_friend_update(net); return true; } static void process_beacon(void *net_ptr, void *user_data) { bool updated = false; struct mesh_net *net = net_ptr; struct net_beacon_data *beacon_data = user_data; uint32_t ivi; bool ivu, kr, local_kr; struct mesh_subnet *subnet, *primary_subnet; ivi = beacon_data->ivi; /* Ignore out-of-range IV_Index for this network */ if ((net->iv_index + IV_IDX_DIFF_RANGE < ivi) || (ivi < net->iv_index)) return; /* Ignore beacons not in this universe */ subnet = l_queue_find(net->subnets, match_key_id, L_UINT_TO_PTR(beacon_data->net_key_id)); if (!subnet) return; /* * @MshPRFv1.0.1 section 3.10.5: IV Update procedure * If this node is a member of a primary subnet and receives a Secure * Network beacon on a secondary subnet with an IV Index greater than * the last known IV Index of the primary subnet, the Secure Network * beacon shall be ignored. */ primary_subnet = get_primary_subnet(net); if (primary_subnet && subnet != primary_subnet && ivi > net->iv_index) return; /* Get IVU and KR boolean bits from beacon */ ivu = beacon_data->ivu; kr = beacon_data->kr; local_kr = (subnet->kr_phase == KEY_REFRESH_PHASE_TWO); /* We have officially *seen* this beacon now */ beacon_data->processed = true; /* * Ignore local beacons and beacons that don't change anything, * unless we're doing IV Recovery */ if (!beacon_data->local) { if (net->iv_upd_state == IV_UPD_INIT || ivi != net->iv_index || ivu != net->iv_update) updated |= update_iv_ivu_state(net, ivi, ivu); if (kr != local_kr) updated |= update_kr_state(subnet, kr, beacon_data->net_key_id); if (updated) net_key_beacon_refresh(beacon_data->net_key_id, net->iv_index, subnet->kr_phase == KEY_REFRESH_PHASE_TWO, net->iv_update, false); } } static void beacon_recv(void *user_data, struct mesh_io_recv_info *info, const uint8_t *data, uint16_t len) { struct net_beacon_data beacon_data = { .local = false, .processed = false, }; beacon_data.net_key_id = net_key_beacon(data, len, &beacon_data.ivi, &beacon_data.ivu, &beacon_data.kr); if (!beacon_data.net_key_id) return; l_queue_foreach(nets, process_beacon, &beacon_data); if (beacon_data.processed) net_key_beacon_seen(beacon_data.net_key_id); } void net_local_beacon(uint32_t net_key_id, uint32_t ivi, bool ivu, bool kr) { struct net_beacon_data beacon_data = { .local = true, .processed = false, .net_key_id = net_key_id, .ivu = ivu, .kr = kr, .ivi = ivi, }; /* Deliver locally generated beacons to all nodes */ l_queue_foreach(nets, process_beacon, &beacon_data); } bool mesh_net_set_snb_mode(struct mesh_net *net, bool enable) { if (!net) return false; if (net->snb_enable == enable) return true; net->snb_enable = enable; if (enable) l_queue_foreach(net->subnets, refresh_beacon, net); l_queue_foreach(net->subnets, enable_snb, net); queue_friend_update(net); return true; } bool mesh_net_set_mpb_mode(struct mesh_net *net, bool enable, uint8_t period, bool initialize) { uint8_t old_period; bool old_enable; if (!net) return false; old_enable = net->mpb_enable; old_period = net->mpb_period; if (enable) net->mpb_period = period; if (old_enable == enable && old_period == net->mpb_period) return true; if (enable && !initialize) { /* If enable with different period, disable and re-enable */ net->mpb_enable = false; l_queue_foreach(net->subnets, enable_mpb, net); } net->mpb_enable = enable; if (enable) l_queue_foreach(net->subnets, refresh_beacon, net); l_queue_foreach(net->subnets, enable_mpb, net); queue_friend_update(net); return true; } /* This function is called when network keys are restored from storage. */ bool mesh_net_set_key(struct mesh_net *net, uint16_t idx, const uint8_t *key, const uint8_t *new_key, uint8_t phase) { struct mesh_subnet *subnet; /* Current key must be always present */ if (!key) return false; /* If key refresh is in progress, a new key must be present */ if (phase != KEY_REFRESH_PHASE_NONE && !new_key) return false; /* Check if the subnet with the specified index already exists */ subnet = l_queue_find(net->subnets, match_key_index, L_UINT_TO_PTR(idx)); if (subnet) return false; subnet = add_key(net, idx, key); if (!subnet) return false; if (new_key && phase) subnet->net_key_upd = net_key_add(new_key); /* Preserve key refresh state to generate secure beacon flags*/ if (phase == KEY_REFRESH_PHASE_TWO) { subnet->key_refresh = 1; subnet->net_key_tx = subnet->net_key_upd; if (net->snb_enable) { /* Switch beaconing key */ net_key_beacon_disable(subnet->net_key_cur, false); net_key_beacon_enable(subnet->net_key_upd, false, 0); } if (net->mpb_enable) { /* Switch beaconing key */ net_key_beacon_disable(subnet->net_key_cur, true); net_key_beacon_enable(subnet->net_key_upd, true, net->mpb_period); } } subnet->kr_phase = phase; net_key_beacon_refresh(subnet->net_key_tx, net->iv_index, subnet->kr_phase == KEY_REFRESH_PHASE_TWO, net->iv_update, false); return true; } bool mesh_net_attach(struct mesh_net *net, struct mesh_io *io) { bool first; if (!net) return false; first = l_queue_isempty(nets); if (first) { const uint8_t snb[] = {BT_AD_MESH_BEACON, 1}; const uint8_t mpb[] = {BT_AD_MESH_BEACON, 2}; const uint8_t pkt[] = {BT_AD_MESH_DATA}; if (!nets) nets = l_queue_new(); if (!fast_cache) fast_cache = l_queue_new(); mesh_io_register_recv_cb(io, snb, sizeof(snb), beacon_recv, NULL); mesh_io_register_recv_cb(io, mpb, sizeof(mpb), beacon_recv, NULL); mesh_io_register_recv_cb(io, pkt, sizeof(pkt), net_msg_recv, NULL); } if (l_queue_find(nets, simple_match, net)) return false; l_queue_push_head(nets, net); net->io = io; return true; } struct mesh_io *mesh_net_detach(struct mesh_net *net) { const uint8_t snb[] = {BT_AD_MESH_BEACON, 1}; const uint8_t mpb[] = {BT_AD_MESH_BEACON, 2}; const uint8_t pkt[] = {BT_AD_MESH_DATA}; struct mesh_io *io; uint8_t type = 0; if (!net || !net->io) return NULL; io = net->io; mesh_io_send_cancel(net->io, &type, 1); /* Only deregister io if this is the last network detached.*/ if (l_queue_length(nets) < 2) { mesh_io_deregister_recv_cb(io, snb, sizeof(snb)); mesh_io_deregister_recv_cb(io, mpb, sizeof(mpb)); mesh_io_deregister_recv_cb(io, pkt, sizeof(pkt)); } net->io = NULL; l_queue_remove(nets, net); return io; } bool mesh_net_iv_index_update(struct mesh_net *net) { if (net->iv_upd_state != IV_UPD_NORMAL) return false; l_debug("iv_upd_state = IV_UPD_UPDATING"); l_queue_clear(net->msg_cache, l_free); if (!mesh_config_write_iv_index(node_config_get(net->node), net->iv_index + 1, true)) return false; net->iv_upd_state = IV_UPD_UPDATING; net->iv_index++; net->iv_update = true; l_queue_foreach(net->subnets, refresh_beacon, net); queue_friend_update(net); net->iv_update_timeout = l_timeout_create( IV_IDX_UPD_MIN, iv_upd_to, net, NULL); return true; } bool mesh_net_dst_reg(struct mesh_net *net, uint16_t dst) { struct mesh_destination *dest = l_queue_find(net->destinations, match_by_dst, L_UINT_TO_PTR(dst)); if (IS_UNASSIGNED(dst) || IS_ALL_NODES(dst)) return false; if (!dest) { dest = l_new(struct mesh_destination, 1); if (dst < 0x8000) l_queue_push_head(net->destinations, dest); else l_queue_push_tail(net->destinations, dest); } dest->dst = dst; dest->ref_cnt++; return true; } bool mesh_net_dst_unreg(struct mesh_net *net, uint16_t dst) { struct mesh_destination *dest = l_queue_find(net->destinations, match_by_dst, L_UINT_TO_PTR(dst)); if (!dest) return false; if (dest->ref_cnt) dest->ref_cnt--; if (dest->ref_cnt) return true; l_queue_remove(net->destinations, dest); l_free(dest); return true; } static bool send_seg(struct mesh_net *net, uint8_t cnt, uint16_t interval, struct mesh_sar *msg, uint8_t segO) { struct mesh_subnet *subnet; uint8_t seg_len; uint8_t packet[MESH_AD_MAX_LEN]; uint8_t packet_len; uint8_t segN = SEG_MAX(msg->segmented, msg->len); uint16_t seg_off = SEG_OFF(segO); uint32_t seq_num; if (msg->segmented) { /* Send each segment on unique seq_num */ seq_num = mesh_net_next_seq_num(net); if (msg->len - seg_off > SEG_OFF(1)) seg_len = SEG_OFF(1); else seg_len = msg->len - seg_off; } else { /* Send on same seq_num used for Access Layer */ seq_num = msg->seqAuth; seg_len = msg->len; } /* Start IV Update procedure when we hit our trigger point */ if (!msg->frnd && net->seq_num > IV_UPDATE_SEQ_TRIGGER) mesh_net_iv_index_update(net); l_debug("segN %d segment %d seg_off %d", segN, segO, seg_off); /* TODO: Are we RXing on an LPN's behalf? Then set RLY bit */ packet[0] = BT_AD_MESH_DATA; if (!mesh_crypto_packet_build(false, msg->ttl, seq_num, msg->src, msg->remote, 0, msg->segmented, msg->key_aid, msg->szmic, msg->seqZero, segO, segN, msg->buf + seg_off, seg_len, packet + 1, &packet_len)) { l_error("Failed to build packet"); return false; } print_packet("Clr-Net Tx", packet + 1, packet_len); subnet = l_queue_find(net->subnets, match_key_index, L_UINT_TO_PTR(msg->net_idx)); if (!subnet) return false; if (!net_key_encrypt(subnet->net_key_tx, msg->iv_index, packet + 1, packet_len)) { l_error("Failed to encode packet"); return false; } send_msg_pkt(net, cnt, interval, packet, packet_len + 1); msg->last_seg = segO; return true; } void mesh_net_send_seg(struct mesh_net *net, uint32_t net_key_id, uint32_t iv_index, uint8_t ttl, uint32_t seq, uint16_t src, uint16_t dst, uint32_t hdr, const void *seg, uint16_t seg_len) { uint8_t packet[MESH_AD_MAX_LEN]; uint8_t packet_len; bool segmented = !!((hdr >> SEG_HDR_SHIFT) & 0x1); uint8_t key_aid = (hdr >> KEY_HDR_SHIFT) & KEY_ID_MASK; bool szmic = !!((hdr >> SZMIC_HDR_SHIFT) & 0x1); uint16_t seqZero = (hdr >> SEQ_ZERO_HDR_SHIFT) & SEQ_ZERO_MASK; uint8_t segO = (hdr >> SEGO_HDR_SHIFT) & SEG_MASK; uint8_t segN = (hdr >> SEGN_HDR_SHIFT) & SEG_MASK; /* * MshPRFv1.0.1 section 3.4.5.2, Interface output filter: * If TTL is set to 1, message shall be dropped. */ if (ttl == 1) return; /* TODO: Only used for current POLLed segments to LPNs */ l_debug("SEQ: %6.6x", seq + segO); l_debug("SEQ0: %6.6x", seq); l_debug("segO: %d", segO); packet[0] = BT_AD_MESH_DATA; if (!mesh_crypto_packet_build(false, ttl, seq, src, dst, 0, segmented, key_aid, szmic, seqZero, segO, segN, seg, seg_len, packet + 1, &packet_len)) { l_error("Failed to build packet"); return; } if (!net_key_encrypt(net_key_id, iv_index, packet + 1, packet_len)) { l_error("Failed to encode packet"); return; } send_msg_pkt(net, net->tx_cnt, net->tx_interval, packet, packet_len + 1); l_debug("TX: Friend Seg-%d %04x -> %04x : len %u) : TTL %d : SEQ %06x", segO, src, dst, packet_len, ttl, seq); print_packet("TX: Friend", packet + 1, packet_len); } bool mesh_net_app_send(struct mesh_net *net, bool frnd_cred, uint16_t src, uint16_t dst, uint8_t key_aid, uint16_t net_idx, uint8_t ttl, uint8_t cnt, uint16_t interval, uint32_t seq, uint32_t iv_index, bool segmented, bool szmic, const void *msg, uint16_t msg_len) { struct mesh_sar *payload = NULL; uint8_t seg, seg_max; bool result; if (!net || msg_len > 384) return false; if (!src) src = net->src_addr; if (!src || !dst) return false; if (ttl == DEFAULT_TTL) ttl = net->default_ttl; /* Long and sizmic messages *require* segmenting */ segmented |= szmic; seg_max = SEG_MAX(segmented, msg_len); segmented |= !!(seg_max); /* First enqueue to any Friends and internal models */ result = msg_rxed(net, false, iv_index, ttl, seq, net_idx, src, dst, key_aid, segmented, szmic, seq & SEQ_ZERO_MASK, msg, msg_len); /* * If addressed to a unicast address and successfully enqueued, * or delivered to one of our Unicast addresses we are done */ if ((result && IS_UNICAST(dst)) || src == dst || (dst >= net->src_addr && dst <= net->last_addr)) return true; /* * MshPRFv1.0.1 section 3.4.5.2, Interface output filter: * If TTL is set to 1, message shall be dropped. */ if (ttl == 1) return true; /* Setup OTA Network send */ payload = mesh_sar_new(msg_len); memcpy(payload->buf, msg, msg_len); payload->len = msg_len; payload->src = src; payload->remote = dst; payload->ttl = ttl; payload->szmic = szmic; payload->frnd_cred = frnd_cred; payload->key_aid = key_aid; payload->net_idx = net_idx; payload->iv_index = mesh_net_get_iv_index(net); payload->seqAuth = seq; payload->segmented = segmented; if (segmented) { payload->flags = 0xffffffff >> (31 - seg_max); payload->seqZero = seq & SEQ_ZERO_MASK; payload->id = ++net->sar_id_next; /* Single thread SAR messages to same Unicast DST */ if (l_queue_find(net->sar_out, match_sar_remote, L_UINT_TO_PTR(dst))) { /* Delay sending Outbound SAR unless prior * SAR to same DST has completed */ l_debug("OB-Queued SeqZero: %4.4x", payload->seqZero); l_queue_push_tail(net->sar_queue, payload); return true; } } result = true; if (!IS_UNICAST(dst) && segmented) { int i; for (i = 0; i < 4; i++) { for (seg = 0; seg <= seg_max && result; seg++) result = send_seg(net, cnt, interval, payload, seg); } } else { for (seg = 0; seg <= seg_max && result; seg++) result = send_seg(net, cnt, interval, payload, seg); } /* Reliable: Cache; Unreliable: Flush*/ if (result && segmented && IS_UNICAST(dst)) { l_queue_push_head(net->sar_out, payload); payload->seg_timeout = l_timeout_create(SEG_TO, outseg_to, net, NULL); payload->msg_timeout = l_timeout_create(MSG_TO, outmsg_to, net, NULL); payload->id = ++net->sar_id_next; } else mesh_sar_free(payload); return result; } void mesh_net_ack_send(struct mesh_net *net, uint32_t net_key_id, uint32_t iv_index, uint8_t ttl, uint32_t seq, uint16_t src, uint16_t dst, bool rly, uint16_t seqZero, uint32_t ack_flags) { uint32_t hdr; uint8_t data[7]; uint8_t pkt_len; uint8_t pkt[MESH_AD_MAX_LEN]; /* * MshPRFv1.0.1 section 3.4.5.2, Interface output filter: * If TTL is set to 1, message shall be dropped. */ if (ttl == 1) return; hdr = NET_OP_SEG_ACKNOWLEDGE << OPCODE_HDR_SHIFT; hdr |= rly << RELAY_HDR_SHIFT; hdr |= (seqZero & SEQ_ZERO_MASK) << SEQ_ZERO_HDR_SHIFT; l_put_be32(hdr, data); l_put_be32(ack_flags, data + 3); /* Not Segmented, no Key ID associated, no segO or segN */ pkt[0] = BT_AD_MESH_DATA; if (!mesh_crypto_packet_build(true, ttl, seq, src, dst, NET_OP_SEG_ACKNOWLEDGE, false, 0, false, seqZero, 0, 0, data + 1, 6, pkt + 1, &pkt_len)) return; if (!net_key_id) { struct mesh_subnet *subnet = get_primary_subnet(net); net_key_id = subnet->net_key_tx; } if (!net_key_encrypt(net_key_id, iv_index, pkt + 1, pkt_len)) { l_error("Failed to encode packet"); return; } send_msg_pkt(net, net->tx_cnt, net->tx_interval, pkt, pkt_len + 1); l_debug("TX: Friend ACK %04x -> %04x : len %u : TTL %d : SEQ %06x", src, dst, pkt_len, ttl, seq); print_packet("TX: Friend ACK", pkt + 1, pkt_len); } void mesh_net_transport_send(struct mesh_net *net, uint32_t net_key_id, uint16_t net_idx, uint32_t iv_index, uint8_t ttl, uint32_t seq, uint16_t src, uint16_t dst, const uint8_t *msg, uint16_t msg_len) { uint8_t pkt_len; uint8_t pkt[MESH_AD_MAX_LEN]; bool result = false; if (!net->src_addr) return; if (!src) src = net->src_addr; if (src == dst) return; if (ttl == DEFAULT_TTL) ttl = net->default_ttl; /* Range check the Opcode and msg length*/ if (*msg & 0xc0 || (9 + msg_len + 8 > MESH_NET_MAX_PDU_LEN)) return; /* * MshPRFv1.0.1 section 3.4.5.2, Interface output filter: * If TTL is set to 1, message shall be dropped. */ if (ttl == 1) return; /* Enqueue for Friend if forwardable and from us */ if (!net_key_id && src >= net->src_addr && src <= net->last_addr) { uint32_t hdr = msg[0] << OPCODE_HDR_SHIFT; uint8_t frnd_ttl = ttl; if (friend_packet_queue(net, iv_index, true, frnd_ttl, mesh_net_next_seq_num(net), src, dst, hdr, msg + 1, msg_len - 1)) return; } /* Deliver to Local entities if applicable */ if (!(dst & 0x8000) && src >= net->src_addr && src <= net->last_addr) result = ctl_received(net, net_key_id, iv_index, ttl, mesh_net_next_seq_num(net), src, dst, msg[0], 0, msg + 1, msg_len - 1); if (!net_key_id) { struct mesh_subnet *subnet = l_queue_find(net->subnets, match_key_index, L_UINT_TO_PTR(net_idx)); if (!subnet) return; net_key_id = subnet->net_key_tx; seq = mesh_net_next_seq_num(net); if (result || (dst >= net->src_addr && dst <= net->last_addr)) return; } pkt[0] = BT_AD_MESH_DATA; if (!mesh_crypto_packet_build(true, ttl, seq, src, dst, msg[0], false, 0, false, 0, 0, 0, msg + 1, msg_len - 1, pkt + 1, &pkt_len)) return; if (!net_key_encrypt(net_key_id, iv_index, pkt + 1, pkt_len)) { l_error("Failed to encode packet"); return; } if (!(IS_UNASSIGNED(dst))) send_msg_pkt(net, net->tx_cnt, net->tx_interval, pkt, pkt_len + 1); } int mesh_net_key_refresh_phase_set(struct mesh_net *net, uint16_t idx, uint8_t transition) { switch (transition) { case KEY_REFRESH_TRANS_TWO: return key_refresh_phase_two(net, idx); case KEY_REFRESH_TRANS_THREE: return key_refresh_finish(net, idx); default: return MESH_STATUS_UNSPECIFIED_ERROR; } } int mesh_net_key_refresh_phase_get(struct mesh_net *net, uint16_t idx, uint8_t *phase) { struct mesh_subnet *subnet; if (!net) return MESH_STATUS_UNSPECIFIED_ERROR; subnet = l_queue_find(net->subnets, match_key_index, L_UINT_TO_PTR(idx)); if (!subnet) return MESH_STATUS_INVALID_NETKEY; *phase = subnet->kr_phase; return MESH_STATUS_SUCCESS; } /* * This function is called when Configuration Server Model receives * a NETKEY_UPDATE command */ int mesh_net_update_key(struct mesh_net *net, uint16_t idx, const uint8_t *value) { struct mesh_subnet *subnet; if (!net) return MESH_STATUS_UNSPECIFIED_ERROR; subnet = l_queue_find(net->subnets, match_key_index, L_UINT_TO_PTR(idx)); if (!subnet) return MESH_STATUS_INVALID_NETKEY; /* Check if the key has been already successfully updated */ if (subnet->kr_phase == KEY_REFRESH_PHASE_ONE && net_key_confirm(subnet->net_key_upd, value)) return MESH_STATUS_SUCCESS; if (subnet->kr_phase != KEY_REFRESH_PHASE_NONE) return MESH_STATUS_CANNOT_UPDATE; if (subnet->net_key_upd) { net_key_unref(subnet->net_key_upd); l_debug("Warning: overwriting new keys"); } /* Preserve starting data */ subnet->net_key_upd = net_key_add(value); if (!subnet->net_key_upd) { l_error("Failed to start key refresh phase one"); return MESH_STATUS_CANNOT_UPDATE; } /* If we are a Friend-Node, generate all our new keys */ l_queue_foreach(net->friends, frnd_kr_phase1, L_UINT_TO_PTR(subnet->net_key_upd)); l_debug("key refresh phase 1: Key ID %d", subnet->net_key_upd); if (!mesh_config_net_key_update(node_config_get(net->node), idx, value)) return MESH_STATUS_STORAGE_FAIL; subnet->kr_phase = KEY_REFRESH_PHASE_ONE; return MESH_STATUS_SUCCESS; } struct mesh_net_heartbeat_sub *mesh_net_get_heartbeat_sub(struct mesh_net *net) { return &net->hb_sub; } struct mesh_net_heartbeat_pub *mesh_net_get_heartbeat_pub(struct mesh_net *net) { return &net->hb_pub; } void mesh_net_set_iv_index(struct mesh_net *net, uint32_t index, bool update) { net->iv_index = index; net->iv_update = update; } uint16_t mesh_net_get_primary_idx(struct mesh_net *net) { struct mesh_subnet *subnet; if (!net) return NET_IDX_INVALID; subnet = get_primary_subnet(net); if (!subnet) return NET_IDX_INVALID; return subnet->idx; } uint32_t mesh_net_friend_timeout(struct mesh_net *net, uint16_t addr) { struct mesh_friend *frnd = l_queue_find(net->friends, match_by_friend, L_UINT_TO_PTR(addr)); if (!frnd) return 0; else return frnd->poll_timeout; } struct l_queue *mesh_net_get_friends(struct mesh_net *net) { if (net) return net->friends; return NULL; } struct l_queue *mesh_net_get_negotiations(struct mesh_net *net) { if (net) return net->negotiations; return NULL; } struct mesh_node *mesh_net_node_get(struct mesh_net *net) { return net->node; } struct l_queue *mesh_net_get_app_keys(struct mesh_net *net) { if (!net) return NULL; if (!net->app_keys) net->app_keys = l_queue_new(); return net->app_keys; } bool mesh_net_have_key(struct mesh_net *net, uint16_t idx) { if (!net) return false; return (l_queue_find(net->subnets, match_key_index, L_UINT_TO_PTR(idx)) != NULL); } bool mesh_net_is_local_address(struct mesh_net *net, uint16_t src, uint16_t count) { const uint16_t last = src + count - 1; if (!net) return false; return (src >= net->src_addr && src <= net->last_addr) && (last >= net->src_addr && last <= net->last_addr); } void mesh_net_transmit_params_set(struct mesh_net *net, uint8_t count, uint16_t interval) { if (!net) return; net->tx_interval = interval; net->tx_cnt = count; } void mesh_net_transmit_params_get(struct mesh_net *net, uint8_t *count, uint16_t *interval) { if (!net) return; *interval = net->tx_interval; *count = net->tx_cnt; } struct mesh_io *mesh_net_get_io(struct mesh_net *net) { if (!net) return NULL; return net->io; } struct mesh_prov *mesh_net_get_prov(struct mesh_net *net) { if (!net) return NULL; return net->prov; } void mesh_net_set_prov(struct mesh_net *net, struct mesh_prov *prov) { if (!net) return; net->prov = prov; } static void refresh_instant(void *a, void *b) { struct mesh_subnet *subnet = a; struct mesh_net *net = b; uint32_t instant = net_key_beacon_last_seen(subnet->net_key_tx); if (net->instant < instant) net->instant = instant; } uint32_t mesh_net_get_instant(struct mesh_net *net) { if (!net) return 0; l_queue_foreach(net->subnets, refresh_instant, net); return net->instant; } static void hb_sub_timeout_func(struct l_timeout *timeout, void *user_data) { struct mesh_net *net = user_data; struct mesh_net_heartbeat_sub *sub = &net->hb_sub; l_debug("HB Subscription Ended"); l_timeout_remove(sub->timer); sub->timer = NULL; sub->enabled = false; } static uint32_t log_to_uint32(uint8_t log) { if (!log) return 0x0000; return (1 << (log - 1)); } int mesh_net_set_heartbeat_sub(struct mesh_net *net, uint16_t src, uint16_t dst, uint8_t period_log) { struct mesh_net_heartbeat_sub *sub = &net->hb_sub; struct timeval time_now; if (!net) return MESH_STATUS_UNSPECIFIED_ERROR; /* Check if the subscription should be disabled */ if (IS_UNASSIGNED(src) || IS_UNASSIGNED(dst) || !period_log) { if (IS_GROUP(sub->dst)) mesh_net_dst_unreg(net, sub->dst); /* Preserve collected data, but disable */ sub->enabled = false; sub->dst = UNASSIGNED_ADDRESS; sub->src = UNASSIGNED_ADDRESS; sub->period = 0; } else { if (sub->dst != dst) { if (IS_GROUP(sub->dst)) mesh_net_dst_unreg(net, sub->dst); if (IS_GROUP(dst)) mesh_net_dst_reg(net, dst); } sub->enabled = true; sub->src = src; sub->dst = dst; sub->count = 0; sub->period = log_to_uint32(period_log); sub->min_hops = 0x7f; sub->max_hops = 0x00; gettimeofday(&time_now, NULL); sub->start = time_now.tv_sec; } /* TODO: Save to node config */ if (!sub->enabled) { l_timeout_remove(sub->timer); sub->timer = NULL; return MESH_STATUS_SUCCESS; } if (!sub->timer) sub->timer = l_timeout_create(sub->period, hb_sub_timeout_func, net, NULL); else l_timeout_modify(sub->timer, sub->period); return MESH_STATUS_SUCCESS; } static void hb_pub_timeout_func(struct l_timeout *timeout, void *user_data) { struct mesh_net *net = user_data; struct mesh_net_heartbeat_pub *pub = &net->hb_pub; send_hb_publication(net); if (pub->count != 0xffff) pub->count--; if (pub->count > 0) l_timeout_modify(pub->timer, pub->period); else { l_timeout_remove(pub->timer); pub->timer = NULL; } } static void update_hb_pub_timer(struct mesh_net *net, struct mesh_net_heartbeat_pub *pub) { if (IS_UNASSIGNED(pub->dst) || pub->count == 0) { l_timeout_remove(pub->timer); pub->timer = NULL; return; } if (!pub->timer) pub->timer = l_timeout_create(pub->period, hb_pub_timeout_func, net, NULL); else l_timeout_modify(pub->timer, pub->period); } int mesh_net_set_heartbeat_pub(struct mesh_net *net, uint16_t dst, uint16_t features, uint16_t idx, uint8_t ttl, uint8_t count_log, uint8_t period_log) { struct mesh_subnet *subnet; struct mesh_net_heartbeat_pub *pub = &net->hb_pub; if (!net) return MESH_STATUS_UNSPECIFIED_ERROR; subnet = l_queue_find(net->subnets, match_key_index, L_UINT_TO_PTR(idx)); if (!subnet) return MESH_STATUS_INVALID_NETKEY; pub->dst = dst; if (pub->dst == UNASSIGNED_ADDRESS) { pub->count = 0; pub->period = 0; pub->ttl = 0; } else { pub->count = (count_log != 0xff) ? log_to_uint32(count_log) : 0xffff; pub->period = log_to_uint32(period_log); } pub->ttl = ttl; pub->features = features; pub->net_idx = idx; update_hb_pub_timer(net, pub); /* TODO: Save to node config */ return MESH_STATUS_SUCCESS; } bool mesh_net_load_rpl(struct mesh_net *net) { return rpl_get_list(net->node, net->replay_cache); } bluez-5.87/mesh/PaxHeaders/remprv.h0000644000000000000000000000005014447506754014313 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/remprv.h0000644000000000000000000000537114447506754014002 0ustar00rootroot/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2023 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. * */ #define REM_PROV_SRV_MODEL SET_ID(SIG_VENDOR, 0x0004) #define REM_PROV_CLI_MODEL SET_ID(SIG_VENDOR, 0x0005) #define PB_REMOTE_MAX_SCAN_QUEUE_SIZE 5 #define PB_REMOTE_STATE_IDLE 0x00 #define PB_REMOTE_STATE_LINK_OPENING 0x01 #define PB_REMOTE_STATE_LINK_ACTIVE 0x02 #define PB_REMOTE_STATE_OB_PKT_TX 0x03 #define PB_REMOTE_STATE_LINK_CLOSING 0x04 #define PB_REMOTE_TYPE_LOCAL 0x01 #define PB_REMOTE_TYPE_ADV 0x02 #define PB_REMOTE_TYPE_GATT 0x04 #define PB_REMOTE_SCAN_TYPE_NONE 0x00 #define PB_REMOTE_SCAN_TYPE_UNLIMITED 0x01 #define PB_REMOTE_SCAN_TYPE_LIMITED 0x02 #define PB_REMOTE_SCAN_TYPE_DETAILED 0x03 /* Remote Provisioning Opcode List */ #define OP_REM_PROV_SCAN_CAP_GET 0x804F #define OP_REM_PROV_SCAN_CAP_STATUS 0x8050 #define OP_REM_PROV_SCAN_GET 0x8051 #define OP_REM_PROV_SCAN_START 0x8052 #define OP_REM_PROV_SCAN_STOP 0x8053 #define OP_REM_PROV_SCAN_STATUS 0x8054 #define OP_REM_PROV_SCAN_REPORT 0x8055 #define OP_REM_PROV_EXT_SCAN_START 0x8056 #define OP_REM_PROV_EXT_SCAN_REPORT 0x8057 #define OP_REM_PROV_LINK_GET 0x8058 #define OP_REM_PROV_LINK_OPEN 0x8059 #define OP_REM_PROV_LINK_CLOSE 0x805A #define OP_REM_PROV_LINK_STATUS 0x805B #define OP_REM_PROV_LINK_REPORT 0x805C #define OP_REM_PROV_PDU_SEND 0x805D #define OP_REM_PROV_PDU_OB_REPORT 0x805E #define OP_REM_PROV_PDU_REPORT 0x805F /* Remote Provisioning Errors */ #define PB_REM_ERR_SUCCESS 0x00 #define PB_REM_ERR_SCANNING_CANNOT_START 0x01 #define PB_REM_ERR_INVALID_STATE 0x02 #define PB_REM_ERR_LIMITED_RESOURCES 0x03 #define PB_REM_ERR_CANNOT_OPEN 0x04 #define PB_REM_ERR_OPEN_FAILED 0x05 #define PB_REM_ERR_CLOSED_BY_DEVICE 0x06 #define PB_REM_ERR_CLOSED_BY_SERVER 0x07 #define PB_REM_ERR_CLOSED_BY_CLIENT 0x08 #define PB_REM_ERR_CLOSED_CANNOT_RX_PDU 0x09 #define PB_REM_ERR_CLOSED_CANNOT_TX_PDU 0x0A void remote_prov_server_init(struct mesh_node *node, uint8_t ele_idx); void remote_prov_client_init(struct mesh_node *node, uint8_t ele_idx); bool register_nppi_acceptor(mesh_prov_open_func_t open_cb, mesh_prov_close_func_t close_cb, mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb, void *user_data); bluez-5.87/mesh/PaxHeaders/manager.c0000644000000000000000000000005015063045254014371 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/manager.c0000644000000000000000000007700715063045254014065 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #define _GNU_SOURCE #include #include "src/shared/ad.h" #include "mesh/mesh-defs.h" #include "mesh/dbus.h" #include "mesh/error.h" #include "mesh/mesh.h" #include "mesh/mesh-io.h" #include "mesh/node.h" #include "mesh/model.h" #include "mesh/net.h" #include "mesh/keyring.h" #include "mesh/agent.h" #include "mesh/provision.h" #include "mesh/prov.h" #include "mesh/remprv.h" #include "mesh/manager.h" struct prov_remote_data { struct l_dbus_message *msg; struct mesh_agent *agent; struct mesh_node *node; uint32_t disc_watch; uint16_t original; uint16_t primary; uint16_t net_idx; uint8_t transport; uint8_t num_ele; uint8_t uuid[16]; }; struct scan_req { struct mesh_node *node; struct l_timeout *timeout; uint16_t server; uint16_t net_idx; uint8_t uuid[16]; int8_t rssi; bool ext; }; static struct l_queue *scans; static struct prov_remote_data *prov_pending; static const uint8_t prvb[2] = {BT_AD_MESH_BEACON, 0x00}; static bool by_scan(const void *a, const void *b) { return a == b; } static bool by_node(const void *a, const void *b) { const struct scan_req *req = a; const struct mesh_node *node = b; return req->node == node; } static bool by_node_svr(const void *a, const void *b) { const struct scan_req *req = a; const struct scan_req *test = b; return req->node == test->node && req->server == test->server; } static void scan_cancel(struct l_timeout *timeout, void *user_data) { struct scan_req *req = user_data; struct mesh_io *io; struct mesh_net *net; uint8_t msg[4]; int n; l_debug(""); req = l_queue_remove_if(scans, by_scan, req); if (!req) return; l_timeout_remove(req->timeout); if (req->server) { n = mesh_model_opcode_set(OP_REM_PROV_SCAN_STOP, msg); mesh_model_send(req->node, 0, req->server, APP_IDX_DEV_REMOTE, req->net_idx, DEFAULT_TTL, true, n, msg); } else { net = node_get_net(req->node); io = mesh_net_get_io(net); mesh_io_deregister_recv_cb(io, prvb, sizeof(prvb)); } initiator_scan_unreg(req->node); l_free(req); } static void free_pending_add_call(void) { if (!prov_pending) return; if (prov_pending->disc_watch) l_dbus_remove_watch(dbus_get_bus(), prov_pending->disc_watch); if (prov_pending->msg) l_dbus_message_unref(prov_pending->msg); l_free(prov_pending); prov_pending = NULL; } static void prov_disc_cb(struct l_dbus *bus, void *user_data) { if (!prov_pending) return; initiator_cancel(prov_pending); prov_pending->disc_watch = 0; free_pending_add_call(); } static void append_dict_entry_basic(struct l_dbus_message_builder *builder, const char *key, const char *signature, const void *data) { if (!builder) return; l_dbus_message_builder_enter_dict(builder, "sv"); l_dbus_message_builder_append_basic(builder, 's', key); l_dbus_message_builder_enter_variant(builder, signature); l_dbus_message_builder_append_basic(builder, signature[0], data); l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_leave_dict(builder); } static void send_add_failed(const char *owner, const char *path, uint8_t status) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message_builder *builder; struct l_dbus_message *msg; msg = l_dbus_message_new_method_call(dbus, owner, path, MESH_PROVISIONER_INTERFACE, "AddNodeFailed"); builder = l_dbus_message_builder_new(msg); dbus_append_byte_array(builder, prov_pending->uuid, 16); l_dbus_message_builder_append_basic(builder, 's', mesh_prov_status_str(status)); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); l_dbus_send(dbus, msg); free_pending_add_call(); } static bool add_cmplt(void *user_data, uint8_t status, const struct mesh_prov_node_info *info) { struct prov_remote_data *pending = user_data; struct mesh_node *node = pending->node; struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message_builder *builder; struct l_dbus_message *msg; bool result; if (pending != prov_pending) return false; if (status != PROV_ERR_SUCCESS) { send_add_failed(node_get_owner(node), node_get_app_path(node), status); return false; } /* If Unicast address changing, delete old dev key */ if (pending->transport == PB_NPPI_01) keyring_del_remote_dev_key_all(pending->node, pending->original); result = keyring_put_remote_dev_key(pending->node, info->unicast, info->num_ele, info->device_key); if (!result) { send_add_failed(node_get_owner(node), node_get_app_path(node), PROV_ERR_CANT_ASSIGN_ADDR); return false; } if (pending->transport > PB_NPPI_02) msg = l_dbus_message_new_method_call(dbus, node_get_owner(node), node_get_app_path(node), MESH_PROVISIONER_INTERFACE, "AddNodeComplete"); else msg = l_dbus_message_new_method_call(dbus, node_get_owner(node), node_get_app_path(node), MESH_PROVISIONER_INTERFACE, "ReprovComplete"); builder = l_dbus_message_builder_new(msg); if (pending->transport > PB_NPPI_02) dbus_append_byte_array(builder, pending->uuid, 16); else { uint8_t nppi = (uint8_t) pending->transport; l_dbus_message_builder_append_basic(builder, 'q', &pending->original); l_dbus_message_builder_append_basic(builder, 'y', &nppi); } l_dbus_message_builder_append_basic(builder, 'q', &info->unicast); l_dbus_message_builder_append_basic(builder, 'y', &info->num_ele); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); l_dbus_send(dbus, msg); free_pending_add_call(); return true; } static void mgr_prov_data (struct l_dbus_message *reply, void *user_data) { struct prov_remote_data *pending = user_data; uint16_t net_idx; uint16_t primary; if (pending != prov_pending) return; if (l_dbus_message_is_error(reply)) return; if (pending->transport == PB_NPPI_01) { /* If performing NPPI, we only get new primary unicast here */ if (!l_dbus_message_get_arguments(reply, "q", &primary)) return; net_idx = pending->net_idx; } else if (!l_dbus_message_get_arguments(reply, "qq", &net_idx, &primary)) return; pending->primary = primary; pending->net_idx = net_idx; initiator_prov_data(net_idx, primary, pending); } static bool add_data_get(void *user_data, uint8_t num_ele) { struct prov_remote_data *pending = user_data; struct l_dbus_message *msg; struct l_dbus *dbus; const char *app_path; const char *sender; if (pending != prov_pending) return false; dbus = dbus_get_bus(); app_path = node_get_app_path(pending->node); sender = node_get_owner(pending->node); if (pending->transport > PB_NPPI_02) { msg = l_dbus_message_new_method_call(dbus, sender, app_path, MESH_PROVISIONER_INTERFACE, "RequestProvData"); l_dbus_message_set_arguments(msg, "y", num_ele); } else if (pending->transport == PB_NPPI_01) { msg = l_dbus_message_new_method_call(dbus, sender, app_path, MESH_PROVISIONER_INTERFACE, "RequestReprovData"); l_dbus_message_set_arguments(msg, "qy", pending->original, num_ele); } else return false; l_dbus_send_with_reply(dbus, msg, mgr_prov_data, pending, NULL); pending->num_ele = num_ele; return true; } static void add_start(void *user_data, int err) { struct l_dbus_message *reply; l_debug("Start callback"); if (err == MESH_ERROR_NONE) reply = l_dbus_message_new_method_return(prov_pending->msg); else reply = dbus_error(prov_pending->msg, MESH_ERROR_FAILED, "Failed to start provisioning initiator"); l_dbus_send(dbus_get_bus(), reply); l_dbus_message_unref(prov_pending->msg); prov_pending->msg = NULL; } static struct l_dbus_message *reprovision_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; struct l_dbus_message_iter options, var; struct l_dbus_message *reply; struct mesh_net *net = node_get_net(node); const char *key; uint16_t subidx; uint16_t server = 0; uint8_t nppi = 0; l_debug("Reprovision request"); if (!l_dbus_message_get_arguments(msg, "qa{sv}", &server, &options)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); if (!IS_UNICAST(server)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad Unicast"); /* Default to nodes primary subnet index */ subidx = mesh_net_get_primary_idx(net); /* Get Provisioning Options */ while (l_dbus_message_iter_next_entry(&options, &key, &var)) { bool failed = true; if (!strcmp(key, "NPPI")) { if (l_dbus_message_iter_get_variant(&var, "y", &nppi)) { if (nppi <= 2) failed = false; } } else if (!strcmp(key, "Subnet")) { if (l_dbus_message_iter_get_variant(&var, "q", &subidx)) { if (subidx <= MAX_KEY_IDX) failed = false; } } if (failed) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Invalid options"); } /* AddNode cancels all outstanding Scanning from node */ manager_scan_cancel(node); /* Invoke Prov Initiator */ prov_pending = l_new(struct prov_remote_data, 1); prov_pending->transport = nppi; prov_pending->node = node; prov_pending->original = server; prov_pending->agent = node_get_agent(node); if (!node_is_provisioner(node) || (prov_pending->agent == NULL)) { reply = dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, "Missing Interfaces"); goto fail; } prov_pending->msg = l_dbus_message_ref(msg); initiator_start(prov_pending->transport, server, subidx, NULL, 99, 60, prov_pending->agent, add_start, add_data_get, add_cmplt, node, prov_pending); prov_pending->disc_watch = l_dbus_add_disconnect_watch(dbus, node_get_owner(node), prov_disc_cb, NULL, NULL); return NULL; fail: l_free(prov_pending); prov_pending = NULL; return reply; } static struct l_dbus_message *add_node_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; struct l_dbus_message_iter iter_uuid, options, var; struct l_dbus_message *reply; struct mesh_net *net = node_get_net(node); const char *key; uint8_t *uuid; uint32_t n = 0; uint16_t subidx; uint16_t sec = 60; uint16_t server = 0; l_debug("AddNode request"); if (!l_dbus_message_get_arguments(msg, "aya{sv}", &iter_uuid, &options)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); if (!l_dbus_message_iter_get_fixed_array(&iter_uuid, &uuid, &n) || n != 16) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad device UUID"); /* Default to nodes primary subnet index */ subidx = mesh_net_get_primary_idx(net); /* Get Provisioning Options */ while (l_dbus_message_iter_next_entry(&options, &key, &var)) { bool failed = true; if (!strcmp(key, "Seconds")) { if (l_dbus_message_iter_get_variant(&var, "q", &sec)) failed = false; } else if (!strcmp(key, "Server")) { if (l_dbus_message_iter_get_variant(&var, "q", &server)) { if (server < 0x8000) failed = false; } } else if (!strcmp(key, "Subnet")) { if (l_dbus_message_iter_get_variant(&var, "q", &subidx)) { if (subidx <= MAX_KEY_IDX) failed = false; } } if (failed) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Invalid options"); } /* Device Key update/Composition update requires remote server */ if (!n && !server) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Invalid options"); /* If no server specified, use local */ if (!server) server = node_get_primary(node); /* AddNode cancels all outstanding Scanning from node */ manager_scan_cancel(node); /* Invoke Prov Initiator */ prov_pending = l_new(struct prov_remote_data, 1); if (n) memcpy(prov_pending->uuid, uuid, 16); else uuid = NULL; prov_pending->transport = PB_ADV; prov_pending->node = node; prov_pending->agent = node_get_agent(node); if (!node_is_provisioner(node) || (prov_pending->agent == NULL)) { l_debug("Provisioner: %d", node_is_provisioner(node)); l_debug("Agent: %p", prov_pending->agent); reply = dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, "Missing Interfaces"); goto fail; } prov_pending->msg = l_dbus_message_ref(msg); initiator_start(PB_ADV, server, subidx, uuid, 99, sec, prov_pending->agent, add_start, add_data_get, add_cmplt, node, prov_pending); prov_pending->disc_watch = l_dbus_add_disconnect_watch(dbus, node_get_owner(node), prov_disc_cb, NULL, NULL); return NULL; fail: l_free(prov_pending); prov_pending = NULL; return reply; } static struct l_dbus_message *import_node_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; struct l_dbus_message_iter iter_key; uint16_t primary; uint8_t num_ele; uint8_t *key; uint32_t n; const char *sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node_get_owner(node))) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "qyay", &primary, &num_ele, &iter_key)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); if (!l_dbus_message_iter_get_fixed_array(&iter_key, &key, &n) || n != 16) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad device key"); if (!keyring_put_remote_dev_key(node, primary, num_ele, key)) return dbus_error(msg, MESH_ERROR_FAILED, NULL); return l_dbus_message_new_method_return(msg); } static struct l_dbus_message *delete_node_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; struct mesh_net *net = node_get_net(node); uint16_t primary; uint8_t num_ele; const char *sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node_get_owner(node))) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "qy", &primary, &num_ele)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); if (mesh_net_is_local_address(net, primary, num_ele)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Cannot remove local device key"); keyring_del_remote_dev_key(node, primary, num_ele); return l_dbus_message_new_method_return(msg); } static void manager_scan_result(void *user_data, uint16_t server, bool ext, const uint8_t *data, uint16_t len) { struct scan_req node_svr = { .node = user_data, .server = server, }; struct scan_req *req; struct l_dbus_message_builder *builder; struct l_dbus_message *msg; struct l_dbus *dbus; int16_t rssi; l_debug("scan_result %4.4x %p", server, user_data); req = l_queue_find(scans, by_node_svr, &node_svr); if (!req) { l_debug("No scan_result req"); return; } /* Filter repeats with weaker signal */ if (!memcmp(data + 1, req->uuid, sizeof(req->uuid))) { if (!ext && ((int8_t) data[0] <= req->rssi)) { l_debug("Already Seen"); return; } } if (!ext && ((int8_t) data[0] > req->rssi)) req->rssi = (int8_t) data[0]; rssi = req->rssi; memcpy(req->uuid, data + 1, sizeof(req->uuid)); dbus = dbus_get_bus(); msg = l_dbus_message_new_method_call(dbus, node_get_owner(req->node), node_get_app_path(req->node), MESH_PROVISIONER_INTERFACE, "ScanResult"); builder = l_dbus_message_builder_new(msg); l_dbus_message_builder_append_basic(builder, 'n', &rssi); dbus_append_byte_array(builder, data + 1, len - 1); l_dbus_message_builder_enter_array(builder, "{sv}"); append_dict_entry_basic(builder, "Server", "q", &server); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); l_dbus_send(dbus, msg); } static struct l_dbus_message *start_scan_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct scan_req new_req = { .node = user_data, .server = 0, .timeout = NULL, .ext = false, }; struct scan_req *req; struct mesh_net *net; uint8_t *uuid, *ext = NULL; uint8_t scan_req[21]; int n; uint32_t ext_len; uint32_t flen = 0; uint16_t sec = 60; const char *key; struct l_dbus_message_iter options, var; const char *sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node_get_owner(new_req.node))) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "a{sv}", &options)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); if (!node_is_provisioner(new_req.node)) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); net = node_get_net(new_req.node); new_req.net_idx = mesh_net_get_primary_idx(net); memset(new_req.uuid, 0, sizeof(new_req.uuid)); while (l_dbus_message_iter_next_entry(&options, &key, &var)) { bool failed = true; if (!strcmp(key, "Seconds")) { if (l_dbus_message_iter_get_variant(&var, "q", &sec)) failed = false; } else if (!strcmp(key, "Subnet")) { if (l_dbus_message_iter_get_variant(&var, "q", &new_req.net_idx)) { if (new_req.net_idx <= MAX_KEY_IDX) failed = false; } } else if (!strcmp(key, "Server")) { if (l_dbus_message_iter_get_variant(&var, "q", &new_req.server)) { if (new_req.server < 0x8000) failed = false; } } else if (!strcmp(key, "Filter")) { if (l_dbus_message_iter_get_variant(&var, "ay", &var)) { if (l_dbus_message_iter_get_fixed_array(&var, &uuid, &flen)) { if (flen == 16) { memcpy(new_req.uuid, uuid, flen); failed = false; } } } } else if (!strcmp(key, "Extended")) { if (l_dbus_message_iter_get_variant(&var, "ay", &var)) { if (l_dbus_message_iter_get_fixed_array(&var, &ext, &ext_len)) failed = false; } } if (failed) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Invalid options"); } if (!scans) scans = l_queue_new(); if (new_req.server) { if (!sec || sec > 60) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Invalid options"); } else { new_req.server = node_get_primary(new_req.node); if (!sec || sec > 60) sec = 60; } req = l_queue_remove_if(scans, by_node_svr, &new_req); if (!req) req = l_new(struct scan_req, 1); if (req->timeout) { l_timeout_remove(req->timeout); req->timeout = NULL; } *req = new_req; req->rssi = -128; if (sec) req->timeout = l_timeout_create(sec, scan_cancel, req, NULL); n = mesh_model_opcode_set(OP_REM_PROV_SCAN_START, scan_req); scan_req[n++] = 5; scan_req[n++] = sec; if (flen) { memcpy(scan_req + n, req->uuid, flen); n += flen; } mesh_model_send(req->node, 0, req->server, APP_IDX_DEV_REMOTE, req->net_idx, DEFAULT_TTL, true, n, scan_req); initiator_scan_reg(manager_scan_result, req->node); l_queue_push_tail(scans, req); return l_dbus_message_new_method_return(msg); } static struct l_dbus_message *cancel_scan_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; const char *sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node_get_owner(node)) || !node_is_provisioner(node)) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); manager_scan_cancel(node); return l_dbus_message_new_method_return(msg); } static struct l_dbus_message *store_new_subnet(struct mesh_node *node, struct l_dbus_message *msg, uint16_t net_idx, uint8_t *new_key) { struct keyring_net_key key; if (net_idx > MAX_KEY_IDX) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); if (keyring_get_net_key(node, net_idx, &key)) { /* Allow redundant calls only if key values match */ if (!memcmp(key.old_key, new_key, 16)) return l_dbus_message_new_method_return(msg); return dbus_error(msg, MESH_ERROR_ALREADY_EXISTS, NULL); } memcpy(key.old_key, new_key, 16); memcpy(key.new_key, new_key, 16); key.net_idx = net_idx; key.phase = KEY_REFRESH_PHASE_NONE; if (!keyring_put_net_key(node, net_idx, &key)) return dbus_error(msg, MESH_ERROR_FAILED, NULL); return l_dbus_message_new_method_return(msg); } static struct l_dbus_message *create_subnet_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; uint8_t key[16]; uint16_t net_idx; const char *sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node_get_owner(node))) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "q", &net_idx) || net_idx == PRIMARY_NET_IDX) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); /* Generate key and store */ l_getrandom(key, sizeof(key)); return store_new_subnet(node, msg, net_idx, key); } static struct l_dbus_message *update_subnet_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; struct keyring_net_key key; uint16_t net_idx; const char *sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node_get_owner(node))) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "q", &net_idx) || net_idx > MAX_KEY_IDX) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); if (!keyring_get_net_key(node, net_idx, &key)) return dbus_error(msg, MESH_ERROR_DOES_NOT_EXIST, NULL); switch (key.phase) { case KEY_REFRESH_PHASE_NONE: /* Generate Key and update phase */ l_getrandom(key.new_key, sizeof(key.new_key)); key.phase = KEY_REFRESH_PHASE_ONE; if (!keyring_put_net_key(node, net_idx, &key)) return dbus_error(msg, MESH_ERROR_FAILED, NULL); /* Fall Through */ case KEY_REFRESH_PHASE_ONE: /* Allow redundant calls to start Key Refresh */ return l_dbus_message_new_method_return(msg); default: break; } /* All other phases mean KR already in progress over-the-air */ return dbus_error(msg, MESH_ERROR_IN_PROGRESS, "Key Refresh in progress"); } static struct l_dbus_message *delete_subnet_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; uint16_t net_idx; const char *sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node_get_owner(node))) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "q", &net_idx) || net_idx > MAX_KEY_IDX) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); keyring_del_net_key(node, net_idx); return l_dbus_message_new_method_return(msg); } static struct l_dbus_message *import_subnet_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; struct l_dbus_message_iter iter_key; uint16_t net_idx; uint8_t *key; uint32_t n; const char *sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node_get_owner(node))) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "qay", &net_idx, &iter_key)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); if (!l_dbus_message_iter_get_fixed_array(&iter_key, &key, &n) || n != 16) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad network key"); return store_new_subnet(node, msg, net_idx, key); } static struct l_dbus_message *store_new_appkey(struct mesh_node *node, struct l_dbus_message *msg, uint16_t net_idx, uint16_t app_idx, uint8_t *new_key) { struct keyring_net_key net_key; struct keyring_app_key app_key; if (net_idx > MAX_KEY_IDX || app_idx > MAX_KEY_IDX) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); if (!keyring_get_net_key(node, net_idx, &net_key)) return dbus_error(msg, MESH_ERROR_DOES_NOT_EXIST, "Bound net key not found"); if (keyring_get_app_key(node, app_idx, &app_key)) { /* Allow redundant calls with identical values */ if (!memcmp(app_key.old_key, new_key, 16) && app_key.net_idx == net_idx) return l_dbus_message_new_method_return(msg); return dbus_error(msg, MESH_ERROR_ALREADY_EXISTS, NULL); } memcpy(app_key.old_key, new_key, 16); memcpy(app_key.new_key, new_key, 16); app_key.net_idx = net_idx; app_key.app_idx = app_idx; if (!keyring_put_app_key(node, app_idx, net_idx, &app_key)) return dbus_error(msg, MESH_ERROR_FAILED, NULL); return l_dbus_message_new_method_return(msg); } static struct l_dbus_message *create_appkey_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; uint16_t net_idx, app_idx; uint8_t key[16]; const char *sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node_get_owner(node))) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "qq", &net_idx, &app_idx)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); l_getrandom(key, sizeof(key)); return store_new_appkey(node, msg, net_idx, app_idx, key); } static struct l_dbus_message *update_appkey_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; struct keyring_net_key net_key; struct keyring_app_key app_key; uint16_t app_idx; const char *sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node_get_owner(node))) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "q", &app_idx) || app_idx > MAX_KEY_IDX) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); if (!keyring_get_app_key(node, app_idx, &app_key) || !keyring_get_net_key(node, app_key.net_idx, &net_key)) return dbus_error(msg, MESH_ERROR_DOES_NOT_EXIST, NULL); if (net_key.phase != KEY_REFRESH_PHASE_ONE) return dbus_error(msg, MESH_ERROR_FAILED, "Invalid Phase"); /* Generate Key if in acceptable phase */ l_getrandom(app_key.new_key, sizeof(app_key.new_key)); if (!keyring_put_app_key(node, app_idx, app_key.net_idx, &app_key)) return dbus_error(msg, MESH_ERROR_FAILED, NULL); return l_dbus_message_new_method_return(msg); } static struct l_dbus_message *delete_appkey_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; uint16_t app_idx; const char *sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node_get_owner(node))) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "q", &app_idx)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); keyring_del_app_key(node, app_idx); return l_dbus_message_new_method_return(msg); } static struct l_dbus_message *import_appkey_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; struct l_dbus_message_iter iter_key; uint16_t net_idx, app_idx; uint8_t *key; uint32_t n; const char *sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node_get_owner(node))) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "qqay", &net_idx, &app_idx, &iter_key)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); if (!l_dbus_message_iter_get_fixed_array(&iter_key, &key, &n) || n != 16) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad application key"); return store_new_appkey(node, msg, net_idx, app_idx, key); } static struct l_dbus_message *set_key_phase_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; struct keyring_net_key key; uint16_t net_idx; uint8_t phase; const char *sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node_get_owner(node))) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "qy", &net_idx, &phase) || phase == KEY_REFRESH_PHASE_ONE || phase > KEY_REFRESH_PHASE_THREE) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); if (!keyring_get_net_key(node, net_idx, &key)) return dbus_error(msg, MESH_ERROR_DOES_NOT_EXIST, NULL); /* Canceling Key Refresh only valid from Phase One */ if (phase == KEY_REFRESH_PHASE_NONE && key.phase >= KEY_REFRESH_PHASE_TWO) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); if (phase == KEY_REFRESH_PHASE_THREE) { /* If we are already in Phase None, then nothing to do */ if (key.phase == KEY_REFRESH_PHASE_NONE) return l_dbus_message_new_method_return(msg); memcpy(key.old_key, key.new_key, 16); key.phase = KEY_REFRESH_PHASE_THREE; if (!keyring_put_net_key(node, net_idx, &key)) return dbus_error(msg, MESH_ERROR_FAILED, NULL); if (!keyring_finalize_app_keys(node, net_idx)) return dbus_error(msg, MESH_ERROR_FAILED, NULL); key.phase = KEY_REFRESH_PHASE_NONE; } else key.phase = phase; if (!keyring_put_net_key(node, net_idx, &key)) return dbus_error(msg, MESH_ERROR_FAILED, NULL); return l_dbus_message_new_method_return(msg); } static struct l_dbus_message *export_keys_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { const char *sender = l_dbus_message_get_sender(msg); struct l_dbus_message_builder *builder; struct l_dbus_message *reply; struct mesh_node *node = user_data; l_debug("Export Keys"); if (strcmp(sender, node_get_owner(node))) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); reply = l_dbus_message_new_method_return(msg); builder = l_dbus_message_builder_new(reply); l_dbus_message_builder_enter_array(builder, "{sv}"); if (!keyring_build_export_keys_reply(node, builder)) { l_dbus_message_builder_destroy(builder); l_dbus_message_unref(reply); return dbus_error(msg, MESH_ERROR_FAILED, NULL); } l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); return reply; } static void setup_management_interface(struct l_dbus_interface *iface) { l_dbus_interface_method(iface, "AddNode", 0, add_node_call, "", "aya{sv}", "uuid", "options"); l_dbus_interface_method(iface, "ImportRemoteNode", 0, import_node_call, "", "qyay", "primary", "count", "dev_key"); l_dbus_interface_method(iface, "Reprovision", 0, reprovision_call, "", "qa{sv}", "unicast", "options"); l_dbus_interface_method(iface, "DeleteRemoteNode", 0, delete_node_call, "", "qy", "primary", "count"); l_dbus_interface_method(iface, "UnprovisionedScan", 0, start_scan_call, "", "a{sv}", "options"); l_dbus_interface_method(iface, "UnprovisionedScanCancel", 0, cancel_scan_call, "", ""); l_dbus_interface_method(iface, "CreateSubnet", 0, create_subnet_call, "", "q", "net_index"); l_dbus_interface_method(iface, "UpdateSubnet", 0, update_subnet_call, "", "q", "net_index"); l_dbus_interface_method(iface, "DeleteSubnet", 0, delete_subnet_call, "", "q", "net_index"); l_dbus_interface_method(iface, "ImportSubnet", 0, import_subnet_call, "", "qay", "net_index", "net_key"); l_dbus_interface_method(iface, "CreateAppKey", 0, create_appkey_call, "", "qq", "net_index", "app_index"); l_dbus_interface_method(iface, "UpdateAppKey", 0, update_appkey_call, "", "q", "app_index"); l_dbus_interface_method(iface, "DeleteAppKey", 0, delete_appkey_call, "", "q", "app_index"); l_dbus_interface_method(iface, "ImportAppKey", 0, import_appkey_call, "", "qqay", "net_index", "app_index", "app_key"); l_dbus_interface_method(iface, "SetKeyPhase", 0, set_key_phase_call, "", "qy", "net_index", "phase"); l_dbus_interface_method(iface, "ExportKeys", 0, export_keys_call, "a(qaya{sv})a(qay)", "", "net_keys", "dev_keys"); } bool manager_dbus_init(struct l_dbus *bus) { if (!l_dbus_register_interface(bus, MESH_MANAGEMENT_INTERFACE, setup_management_interface, NULL, false)) { l_debug("Unable to register %s interface", MESH_MANAGEMENT_INTERFACE); return false; } return true; } void manager_scan_cancel(struct mesh_node *node) { struct scan_req *req; while ((req = l_queue_find(scans, by_node, node))) scan_cancel(NULL, req); } bluez-5.87/mesh/PaxHeaders/node.c0000644000000000000000000000005015063045254013704 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/node.c0000644000000000000000000017014015063045254013370 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2017-2020 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "mesh/mesh-defs.h" #include "mesh/mesh.h" #include "mesh/net.h" #include "mesh/net-keys.h" #include "mesh/appkey.h" #include "mesh/mesh-config.h" #include "mesh/provision.h" #include "mesh/prov.h" #include "mesh/keyring.h" #include "mesh/model.h" #include "mesh/cfgmod.h" #include "mesh/remprv.h" #include "mesh/prv-beacon.h" #include "mesh/util.h" #include "mesh/error.h" #include "mesh/dbus.h" #include "mesh/agent.h" #include "mesh/manager.h" #include "mesh/rpl.h" #include "mesh/node.h" #define MESH_NODE_PATH_PREFIX "/node" /* Default values for a new locally created node */ #define DEFAULT_NEW_UNICAST 0x0001 #define DEFAULT_IV_INDEX 0x0000 /* Default element location: unknown */ #define DEFAULT_LOCATION 0x0000 enum request_type { REQUEST_TYPE_JOIN, REQUEST_TYPE_ATTACH, REQUEST_TYPE_CREATE, REQUEST_TYPE_IMPORT, }; struct node_element { char *path; struct l_queue *models; uint16_t location; uint8_t idx; }; struct node_composition { uint16_t cid; uint16_t pid; uint16_t vid; uint16_t crpl; }; struct mesh_node { struct mesh_net *net; struct l_queue *elements; struct l_queue *pages; char *app_path; char *owner; char *obj_path; struct mesh_agent *agent; struct mesh_config *cfg; char *storage_dir; uint32_t disc_watch; uint32_t seq_number; bool busy; bool provisioner; uint16_t primary; struct node_composition comp; struct { uint16_t interval; uint8_t cnt; uint8_t mode; } relay; uint8_t uuid[16]; uint8_t dev_key[16]; uint8_t token[8]; uint8_t num_ele; uint8_t ttl; uint8_t lpn; uint8_t proxy; uint8_t friend; uint8_t beacon; uint8_t mpb; uint8_t mpb_period; }; struct node_import { uint8_t dev_key[16]; uint8_t net_key[16]; uint16_t net_idx; struct { bool ivu; bool kr; } flags; uint32_t iv_index; uint16_t unicast; }; struct managed_obj_request { struct mesh_node *node; union { node_ready_func_t ready_cb; node_join_ready_func_t join_ready_cb; }; struct l_dbus_message *pending_msg; enum request_type type; union { struct mesh_node *attach; struct node_import *import; }; }; struct send_options { bool segmented; uint16_t vendor_id; }; static struct l_queue *nodes; static bool match_device_uuid(const void *a, const void *b) { const struct mesh_node *node = a; const uint8_t *uuid = b; return (memcmp(node->uuid, uuid, 16) == 0); } static bool match_token(const void *a, const void *b) { const struct mesh_node *node = a; const uint64_t *token = b; const uint64_t tmp = l_get_be64(node->token); return *token == tmp; } static bool match_element_idx(const void *a, const void *b) { const struct node_element *element = a; uint32_t index = L_PTR_TO_UINT(b); return (element->idx == index); } static int compare_element_idx(const void *a, const void *b, void *user_data) { uint32_t a_idx = ((const struct node_element *)a)->idx; uint32_t b_idx = ((const struct node_element *)b)->idx; if (a_idx < b_idx) return -1; if (a_idx > b_idx) return 1; return 0; } static bool match_element_path(const void *a, const void *b) { const struct node_element *element = a; const char *path = b; if (!element->path) return false; return (!strcmp(element->path, path)); } struct mesh_node *node_find_by_uuid(uint8_t uuid[16]) { return l_queue_find(nodes, match_device_uuid, uuid); } struct mesh_node *node_find_by_token(uint64_t token) { return l_queue_find(nodes, match_token, (void *) &token); } uint8_t *node_uuid_get(struct mesh_node *node) { if (!node) return NULL; return node->uuid; } static void set_defaults(struct mesh_node *node) { node->lpn = MESH_MODE_UNSUPPORTED; node->proxy = MESH_MODE_UNSUPPORTED; node->mpb = MESH_MODE_DISABLED; node->mpb_period = NET_MPB_REFRESH_DEFAULT; node->friend = (mesh_friendship_supported()) ? MESH_MODE_DISABLED : MESH_MODE_UNSUPPORTED; node->beacon = (mesh_beacon_enabled()) ? MESH_MODE_ENABLED : MESH_MODE_DISABLED; node->relay.mode = (mesh_relay_supported()) ? MESH_MODE_DISABLED : MESH_MODE_UNSUPPORTED; node->ttl = TTL_MASK; node->seq_number = DEFAULT_SEQUENCE_NUMBER; } static struct mesh_node *node_new(const uint8_t uuid[16]) { struct mesh_node *node; node = l_new(struct mesh_node, 1); node->net = mesh_net_new(node); node->elements = l_queue_new(); node->pages = l_queue_new(); memcpy(node->uuid, uuid, sizeof(node->uuid)); set_defaults(node); return node; } static void free_element_path(void *a, void *b) { struct node_element *element = a; l_free(element->path); element->path = NULL; } static void element_free(void *data) { struct node_element *element = data; l_queue_destroy(element->models, mesh_model_free); l_free(element->path); l_free(element); } static void free_node_dbus_resources(struct mesh_node *node) { if (!node) return; if (node->disc_watch) { l_dbus_remove_watch(dbus_get_bus(), node->disc_watch); node->disc_watch = 0; } l_queue_foreach(node->elements, free_element_path, NULL); l_free(node->owner); node->owner = NULL; l_free(node->app_path); node->app_path = NULL; if (node->obj_path) { l_dbus_object_remove_interface(dbus_get_bus(), node->obj_path, MESH_NODE_INTERFACE); l_dbus_object_remove_interface(dbus_get_bus(), node->obj_path, MESH_MANAGEMENT_INTERFACE); l_dbus_object_remove_interface(dbus_get_bus(), node->obj_path, L_DBUS_INTERFACE_PROPERTIES); l_free(node->obj_path); node->obj_path = NULL; } } static void free_node_resources(void *data) { struct mesh_node *node = data; /* Unregister io callbacks */ mesh_net_detach(node->net); /* In case of a provisioner, stop active scanning */ if (node->provisioner) manager_scan_cancel(node); /* Free dynamic resources */ free_node_dbus_resources(node); l_queue_destroy(node->elements, element_free); l_queue_destroy(node->pages, l_free); mesh_agent_remove(node->agent); mesh_config_release(node->cfg); mesh_net_free(node->net); l_free(node->storage_dir); l_free(node); } /* * This function is called to free resources and remove the * configuration files for the specified node. */ void node_remove(struct mesh_node *node) { if (!node) return; l_queue_remove(nodes, node); mesh_config_destroy_nvm(node->cfg); free_node_resources(node); } static bool add_element_from_storage(struct mesh_node *node, struct mesh_config_element *db_ele) { struct node_element *ele; ele = l_new(struct node_element, 1); if (!ele) return false; ele->idx = db_ele->index; ele->location = db_ele->location; ele->models = l_queue_new(); l_queue_push_tail(node->elements, ele); if (!mesh_model_add_from_storage(node, ele->idx, ele->models, db_ele->models)) return false; return true; } static bool add_elements_from_storage(struct mesh_node *node, struct mesh_config_node *db_node) { const struct l_queue_entry *entry; struct node_element *ele; entry = l_queue_get_entries(db_node->elements); for (; entry; entry = entry->next) if (!add_element_from_storage(node, entry->data)) return false; ele = l_queue_find(node->elements, match_element_idx, L_UINT_TO_PTR(PRIMARY_ELE_IDX)); if (!ele) return false; /* Add configuration server model on the primary element */ mesh_model_add(node, ele->models, CONFIG_SRV_MODEL, NULL); /* Add remote provisioning models on the primary element */ mesh_model_add(node, ele->models, REM_PROV_SRV_MODEL, NULL); if (node->provisioner) mesh_model_add(node, ele->models, REM_PROV_CLI_MODEL, NULL); return true; } static void set_net_key(void *a, void *b) { struct mesh_config_netkey *netkey = a; struct mesh_node *node = b; mesh_net_set_key(node->net, netkey->idx, netkey->key, netkey->new_key, netkey->phase); } static void set_appkey(void *a, void *b) { struct mesh_config_appkey *appkey = a; struct mesh_node *node = b; appkey_key_init(node->net, appkey->net_idx, appkey->app_idx, appkey->key, appkey->new_key); } static bool init_storage_dir(struct mesh_node *node) { char uuid[33]; char dir_name[PATH_MAX]; if (node->storage_dir) return true; if (!hex2str(node->uuid, 16, uuid, sizeof(uuid))) return false; snprintf(dir_name, PATH_MAX, "%s/%s", mesh_get_storage_dir(), uuid); if (strlen(dir_name) >= PATH_MAX) return false; create_dir(dir_name); node->storage_dir = l_strdup(dir_name); /* Initialize directory for storing RPL info */ return rpl_init(node->storage_dir); } static void init_net_settings(struct mesh_node *node) { struct mesh_net *net = node->net; mesh_net_set_proxy_mode(net, node->proxy == MESH_MODE_ENABLED); mesh_net_set_friend_mode(net, node->friend == MESH_MODE_ENABLED); mesh_net_set_relay_mode(net, node->relay.mode == MESH_MODE_ENABLED, node->relay.cnt, node->relay.interval); mesh_net_set_snb_mode(net, node->beacon == MESH_MODE_ENABLED); mesh_net_set_mpb_mode(net, node->mpb == MESH_MODE_ENABLED, node->mpb_period, true); } static bool init_from_storage(struct mesh_config_node *db_node, const uint8_t uuid[16], struct mesh_config *cfg, void *user_data) { unsigned int num_ele; struct mesh_node *node = node_new(uuid); if (!nodes) nodes = l_queue_new(); l_queue_push_tail(nodes, node); node->comp.cid = db_node->cid; node->comp.pid = db_node->pid; node->comp.vid = db_node->vid; node->comp.crpl = db_node->crpl; node->lpn = db_node->modes.lpn; node->proxy = db_node->modes.proxy; node->friend = db_node->modes.friend; node->relay.mode = db_node->modes.relay.state; node->relay.cnt = db_node->modes.relay.cnt; node->relay.interval = db_node->modes.relay.interval; node->beacon = db_node->modes.beacon; node->mpb = db_node->modes.mpb; node->mpb_period = db_node->modes.mpb_period; l_debug("relay %2.2x, proxy %2.2x, lpn %2.2x, friend %2.2x", node->relay.mode, node->proxy, node->lpn, node->friend); node->ttl = db_node->ttl; node->seq_number = db_node->seq_number; memcpy(node->dev_key, db_node->dev_key, 16); memcpy(node->token, db_node->token, 8); num_ele = l_queue_length(db_node->elements); if (num_ele > MAX_ELE_COUNT) goto fail; node->num_ele = num_ele; if (num_ele != 0 && !add_elements_from_storage(node, db_node)) goto fail; node->primary = db_node->unicast; if (!db_node->netkeys) goto fail; if (!IS_UNASSIGNED(node->primary) && !mesh_net_register_unicast(node->net, node->primary, num_ele)) goto fail; mesh_net_set_iv_index(node->net, db_node->iv_index, db_node->iv_update); /* Initialize directory for storing keyring and RPL info */ if (!init_storage_dir(node) || !mesh_net_load_rpl(node->net)) goto fail; if (db_node->net_transmit) mesh_net_transmit_params_set(node->net, db_node->net_transmit->count, db_node->net_transmit->interval); l_queue_foreach(db_node->netkeys, set_net_key, node); l_queue_foreach(db_node->appkeys, set_appkey, node); while (l_queue_length(db_node->pages)) { struct mesh_config_comp_page *page; /* Move the composition pages to the node struct */ page = l_queue_pop_head(db_node->pages); l_queue_push_tail(node->pages, page); } mesh_net_set_seq_num(node->net, node->seq_number); mesh_net_set_default_ttl(node->net, node->ttl); init_net_settings(node); /* Initialize configuration server model */ cfgmod_server_init(node, PRIMARY_ELE_IDX); /* Initialize remote provisioning models */ remote_prov_server_init(node, PRIMARY_ELE_IDX); remote_prov_client_init(node, PRIMARY_ELE_IDX); /* Initialize Private Beacon server model */ prv_beacon_server_init(node, PRIMARY_ELE_IDX); node->cfg = cfg; return true; fail: node_remove(node); return false; } static void cleanup_node(void *data) { struct mesh_node *node = data; uint32_t seq_num = mesh_net_get_seq_num(node->net); /* Preserve the last used sequence number */ mesh_config_write_seq_number(node->cfg, seq_num, false); free_node_resources(node); } /* * This function is called to free resources and write the current * sequence numbers to the configuration file for each known node. */ void node_cleanup_all(void) { l_queue_destroy(nodes, cleanup_node); l_dbus_unregister_interface(dbus_get_bus(), MESH_NODE_INTERFACE); l_dbus_unregister_interface(dbus_get_bus(), MESH_MANAGEMENT_INTERFACE); } bool node_is_provisioner(struct mesh_node *node) { return node->provisioner; } bool node_is_busy(struct mesh_node *node) { return node->busy; } void node_app_key_delete(struct mesh_node *node, uint16_t net_idx, uint16_t app_idx) { const struct l_queue_entry *entry; entry = l_queue_get_entries(node->elements); for (; entry; entry = entry->next) { struct node_element *ele = entry->data; mesh_model_app_key_delete(node, ele->idx, ele->models, app_idx); } } uint16_t node_get_primary(struct mesh_node *node) { if (!node) return UNASSIGNED_ADDRESS; else return node->primary; } bool node_refresh(struct mesh_node *node, bool hard, void *prov_info) { struct mesh_prov_node_info *info = prov_info; bool res = true; if (!node || !info) return false; if (!IS_UNICAST(info->unicast)) return false; /* Changing Unicast addresses requires a hard node reset */ if (!hard && info->unicast != node->primary) return false; /* * Hard refresh results in immediate use of new Device Key. * Soft refresh saves new device key as Candidate until we * successfully receive new incoming message on that key. */ if (hard) { if (!mesh_config_write_device_key(node->cfg, info->device_key)) return false; memcpy(node->dev_key, info->device_key, sizeof(node->dev_key)); } else if (!mesh_config_write_candidate(node->cfg, info->device_key)) return false; /* Replace Primary Unicast address if it has changed */ if (node->primary != info->unicast) { res = mesh_config_write_unicast(node->cfg, info->unicast); if (res) { node->primary = info->unicast; node->num_ele = info->num_ele; mesh_net_register_unicast(node->net, node->primary, node->num_ele); } } /* Replace Page 0 with Page 128 if it exists */ if (res) { if (node_replace_comp(node, 0, 128)) return true; } return res; } const uint8_t *node_get_device_key(struct mesh_node *node) { if (!node) return NULL; return node->dev_key; } bool node_get_device_key_candidate(struct mesh_node *node, uint8_t *key) { if (!node) return false; return mesh_config_read_candidate(node->cfg, key); } void node_finalize_candidate(struct mesh_node *node) { if (!node) return; if (mesh_config_read_candidate(node->cfg, node->dev_key)) mesh_config_finalize_candidate(node->cfg); } void node_set_token(struct mesh_node *node, uint8_t token[8]) { memcpy(node->token, token, 8); } const uint8_t *node_get_token(struct mesh_node *node) { if (!node) return NULL; else return node->token; } uint8_t node_get_num_elements(struct mesh_node *node) { return node->num_ele; } struct l_queue *node_get_element_models(struct mesh_node *node, uint8_t ele_idx) { struct node_element *ele; if (!node) return NULL; ele = l_queue_find(node->elements, match_element_idx, L_UINT_TO_PTR(ele_idx)); if (!ele) return NULL; return ele->models; } uint8_t node_default_ttl_get(struct mesh_node *node) { if (!node) return TTL_MASK; return node->ttl; } bool node_default_ttl_set(struct mesh_node *node, uint8_t ttl) { bool res; if (!node) return false; res = mesh_config_write_ttl(node->cfg, ttl); if (res) { node->ttl = ttl; mesh_net_set_default_ttl(node->net, ttl); } return res; } bool node_set_sequence_number(struct mesh_node *node, uint32_t seq) { if (!node) return false; node->seq_number = seq; return mesh_config_write_seq_number(node->cfg, node->seq_number, true); } uint32_t node_get_sequence_number(struct mesh_node *node) { if (!node) return 0xffffffff; return node->seq_number; } int node_get_element_idx(struct mesh_node *node, uint16_t ele_addr) { uint16_t addr; uint8_t num_ele; if (!node) return -1; num_ele = node_get_num_elements(node); if (!num_ele) return -2; addr = node_get_primary(node); if (ele_addr < addr || ele_addr >= addr + num_ele) return -3; else return ele_addr - addr; } uint16_t node_get_crpl(struct mesh_node *node) { if (!node) return 0; return node->comp.crpl; } uint8_t node_relay_mode_get(struct mesh_node *node, uint8_t *count, uint16_t *interval) { if (!node) { *count = 0; *interval = 0; return MESH_MODE_DISABLED; } *count = node->relay.cnt; *interval = node->relay.interval; return node->relay.mode; } uint8_t node_lpn_mode_get(struct mesh_node *node) { if (!node) return MESH_MODE_DISABLED; return node->lpn; } bool node_relay_mode_set(struct mesh_node *node, bool enable, uint8_t cnt, uint16_t interval) { bool res; if (!node || node->relay.mode == MESH_MODE_UNSUPPORTED) return false; res = mesh_config_write_relay_mode(node->cfg, enable, cnt, interval); if (res) { node->relay.mode = enable ? MESH_MODE_ENABLED : MESH_MODE_DISABLED; node->relay.cnt = cnt; node->relay.interval = interval; mesh_net_set_relay_mode(node->net, enable, cnt, interval); } return res; } bool node_proxy_mode_set(struct mesh_node *node, bool enable) { bool res; uint8_t proxy; if (!node || node->proxy == MESH_MODE_UNSUPPORTED) return false; proxy = enable ? MESH_MODE_ENABLED : MESH_MODE_DISABLED; res = mesh_config_write_mode(node->cfg, "proxy", proxy); if (res) { node->proxy = proxy; mesh_net_set_proxy_mode(node->net, enable); } return res; } uint8_t node_proxy_mode_get(struct mesh_node *node) { if (!node) return MESH_MODE_DISABLED; return node->proxy; } bool node_beacon_mode_set(struct mesh_node *node, bool enable) { bool res; uint8_t beacon; if (!node) return false; beacon = enable ? MESH_MODE_ENABLED : MESH_MODE_DISABLED; res = mesh_config_write_mode(node->cfg, "beacon", beacon); if (res) { node->beacon = beacon; mesh_net_set_snb_mode(node->net, enable); } return res; } uint8_t node_beacon_mode_get(struct mesh_node *node) { if (!node) return MESH_MODE_DISABLED; return node->beacon; } bool node_mpb_mode_set(struct mesh_node *node, bool enable, uint8_t period) { bool res; uint8_t beacon; if (!node) return false; beacon = enable ? MESH_MODE_ENABLED : MESH_MODE_DISABLED; res = mesh_config_write_mpb(node->cfg, beacon, period); if (res) { node->mpb = beacon; node->mpb_period = period; mesh_net_set_mpb_mode(node->net, enable, period, false); } return res; } uint8_t node_mpb_mode_get(struct mesh_node *node, uint8_t *period) { if (!node) return MESH_MODE_DISABLED; *period = node->mpb_period; return node->mpb; } bool node_friend_mode_set(struct mesh_node *node, bool enable) { bool res; uint8_t friend; if (!node || node->friend == MESH_MODE_UNSUPPORTED) return false; friend = enable ? MESH_MODE_ENABLED : MESH_MODE_DISABLED; res = mesh_config_write_mode(node->cfg, "friend", friend); if (res) { node->friend = friend; mesh_net_set_friend_mode(node->net, enable); } return res; } uint8_t node_friend_mode_get(struct mesh_node *node) { if (!node) return MESH_MODE_DISABLED; return node->friend; } static uint16_t node_generate_comp(struct mesh_node *node, uint8_t *buf, uint16_t sz) { uint16_t n, features, num_ele = 0; const struct l_queue_entry *entry; n = 0; l_put_le16(node->comp.cid, buf + n); n += 2; l_put_le16(node->comp.pid, buf + n); n += 2; l_put_le16(node->comp.vid, buf + n); n += 2; l_put_le16(node->comp.crpl, buf + n); n += 2; features = 0; if (node->relay.mode != MESH_MODE_UNSUPPORTED) features |= FEATURE_RELAY; if (node->proxy != MESH_MODE_UNSUPPORTED) features |= FEATURE_PROXY; if (node->friend != MESH_MODE_UNSUPPORTED) features |= FEATURE_FRIEND; if (node->lpn != MESH_MODE_UNSUPPORTED) features |= FEATURE_LPN; l_put_le16(features, buf + n); n += 2; entry = l_queue_get_entries(node->elements); for (; entry; entry = entry->next) { struct node_element *ele = entry->data; if (ele->idx != num_ele) return 0; num_ele++; /* At least fit location and zeros for number of models */ if ((n + 4) > sz) return n; l_put_le16(ele->location, buf + n); n += 2; n += mesh_model_generate_composition(ele->models, sz - n, buf + n); } if (!num_ele) return 0; return n; } static bool match_page(const void *a, const void *b) { const struct mesh_config_comp_page *page = a; uint8_t page_num = L_PTR_TO_UINT(b); return page->page_num == page_num; } static void convert_node_to_storage(struct mesh_node *node, struct mesh_config_node *db_node) { const struct l_queue_entry *entry; memset(db_node, 0, sizeof(struct mesh_config_node)); db_node->cid = node->comp.cid; db_node->pid = node->comp.pid; db_node->vid = node->comp.vid; db_node->crpl = node->comp.crpl; db_node->modes.lpn = node->lpn; db_node->modes.proxy = node->proxy; db_node->modes.friend = node->friend; db_node->modes.relay.state = node->relay.mode; db_node->modes.relay.cnt = node->relay.cnt; db_node->modes.relay.interval = node->relay.interval; db_node->modes.beacon = node->beacon; db_node->modes.mpb = node->mpb; db_node->modes.mpb_period = node->mpb_period; db_node->ttl = node->ttl; db_node->seq_number = node->seq_number; db_node->elements = l_queue_new(); entry = l_queue_get_entries(node->elements); for (; entry; entry = entry->next) { struct node_element *ele = entry->data; struct mesh_config_element *db_ele; db_ele = l_new(struct mesh_config_element, 1); db_ele->index = ele->idx; db_ele->location = ele->location; db_ele->models = l_queue_new(); mesh_model_convert_to_storage(db_ele->models, ele->models); l_queue_push_tail(db_node->elements, db_ele); } } static void free_db_storage(struct mesh_config_node *db_node) { const struct l_queue_entry *entry; /* Free temporarily allocated resources */ entry = l_queue_get_entries(db_node->elements); for (; entry; entry = entry->next) { struct mesh_config_element *db_ele = entry->data; l_queue_destroy(db_ele->models, l_free); } l_queue_destroy(db_node->elements, l_free); } static bool create_node_config(struct mesh_node *node, const uint8_t uuid[16]) { struct mesh_config_node db_node; const struct l_queue_entry *entry; const char *storage_dir; convert_node_to_storage(node, &db_node); storage_dir = mesh_get_storage_dir(); node->cfg = mesh_config_create(storage_dir, uuid, &db_node); if (node->cfg) init_storage_dir(node); /* Free temporarily allocated resources */ entry = l_queue_get_entries(db_node.elements); for (; entry; entry = entry->next) { struct mesh_config_element *db_ele = entry->data; l_queue_destroy(db_ele->models, l_free); } l_queue_destroy(db_node.elements, l_free); return node->cfg != NULL; } static void node_del_comp(struct mesh_node *node, uint8_t page_num) { struct mesh_config_comp_page *page; if (!node) return; page = l_queue_remove_if(node->pages, match_page, L_UINT_TO_PTR(page_num)); l_free(page); mesh_config_comp_page_del(node->cfg, page_num); } static bool node_set_comp(struct mesh_node *node, uint8_t page_num, const uint8_t *data, uint16_t len) { struct mesh_config_comp_page *page; if (len < MIN_COMP_SIZE) return false; page = l_queue_remove_if(node->pages, match_page, L_UINT_TO_PTR(page_num)); l_free(page); page = l_malloc(sizeof(struct mesh_config_comp_page) + len); page->len = len; page->page_num = page_num; memcpy(page->data, data, len); l_queue_push_tail(node->pages, page); return mesh_config_comp_page_add(node->cfg, page_num, page->data, len); } const uint8_t *node_get_comp(struct mesh_node *node, uint8_t page_num, uint16_t *len) { struct mesh_config_comp_page *page = NULL; if (node) page = l_queue_find(node->pages, match_page, L_UINT_TO_PTR(page_num)); if (!page) { *len = 0; return NULL; } *len = page->len; return page->data; } bool node_replace_comp(struct mesh_node *node, uint8_t retire, uint8_t with) { struct mesh_config_comp_page *old_page, *keep; bool status; if (!node) return false; keep = l_queue_find(node->pages, match_page, L_UINT_TO_PTR(with)); if (!keep) return false; old_page = l_queue_remove_if(node->pages, match_page, L_UINT_TO_PTR(retire)); l_free(old_page); keep->page_num = retire; status = mesh_config_comp_page_add(node->cfg, keep->page_num, keep->data, keep->len); if (with != retire) mesh_config_comp_page_del(node->cfg, with); return status; } static void attach_io(void *a, void *b) { struct mesh_node *node = a; struct mesh_io *io = b; if (node->net) mesh_net_attach(node->net, io); } /* Register callbacks for all nodes io */ void node_attach_io_all(struct mesh_io *io) { l_queue_foreach(nodes, attach_io, io); } /* Register node object with D-Bus */ static bool register_node_object(struct mesh_node *node) { char uuid[33]; if (!hex2str(node->uuid, sizeof(node->uuid), uuid, sizeof(uuid))) return false; node->obj_path = l_strdup_printf(BLUEZ_MESH_PATH MESH_NODE_PATH_PREFIX "%s", uuid); if (!l_dbus_object_add_interface(dbus_get_bus(), node->obj_path, MESH_NODE_INTERFACE, node)) return false; if (!l_dbus_object_add_interface(dbus_get_bus(), node->obj_path, MESH_MANAGEMENT_INTERFACE, node)) return false; if (!l_dbus_object_add_interface(dbus_get_bus(), node->obj_path, L_DBUS_INTERFACE_PROPERTIES, NULL)) return false; return true; } static void app_disc_cb(struct l_dbus *bus, void *user_data) { struct mesh_node *node = user_data; l_info("App %s disconnected (%u)", node->owner, node->disc_watch); node->disc_watch = 0; /* In case of a provisioner, stop active scanning */ if (node->provisioner) manager_scan_cancel(node); free_node_dbus_resources(node); } static bool get_sig_models_from_properties(struct mesh_node *node, struct node_element *ele, struct l_dbus_message_iter *property) { struct l_dbus_message_iter mods, var; uint16_t m_id; if (!ele->models) ele->models = l_queue_new(); if (!l_dbus_message_iter_get_variant(property, "a(qa{sv})", &mods)) return false; /* Bluetooth SIG defined models */ while (l_dbus_message_iter_next_entry(&mods, &m_id, &var)) { uint32_t id = SET_ID(SIG_VENDOR, m_id); /* * Allow Config Server & Private Beacon Models only on * the primary element */ if (ele->idx != PRIMARY_ELE_IDX) { if (id == CONFIG_SRV_MODEL) return false; if (id == PRV_BEACON_SRV_MODEL) return false; } if (!mesh_model_add(node, ele->models, id, &var)) return false; } return true; } static bool get_vendor_models_from_properties(struct mesh_node *node, struct node_element *ele, struct l_dbus_message_iter *property) { struct l_dbus_message_iter mods, var; uint16_t m_id, v_id; if (!ele->models) ele->models = l_queue_new(); if (!l_dbus_message_iter_get_variant(property, "a(qqa{sv})", &mods)) return false; /* Vendor defined models */ while (l_dbus_message_iter_next_entry(&mods, &v_id, &m_id, &var)) { uint32_t id = SET_ID(v_id, m_id); if (!mesh_model_add(node, ele->models, id, &var)) return false; } return true; } static bool get_element_properties(struct mesh_node *node, const char *path, struct l_dbus_message_iter *properties) { struct node_element *ele = l_new(struct node_element, 1); const char *key; struct l_dbus_message_iter var; bool idx = false; bool mods = false; bool vendor_mods = false; l_debug("path %s", path); ele->location = DEFAULT_LOCATION; while (l_dbus_message_iter_next_entry(properties, &key, &var)) { if (!strcmp(key, "Index")) { if (idx || !l_dbus_message_iter_get_variant(&var, "y", &ele->idx)) goto fail; idx = true; } else if (!strcmp(key, "Models")) { if (mods) goto fail; if (!get_sig_models_from_properties(node, ele, &var)) goto fail; mods = true; } else if (!strcmp(key, "VendorModels")) { if (vendor_mods) goto fail; if (!get_vendor_models_from_properties(node, ele, &var)) goto fail; vendor_mods = true; } else if (!strcmp(key, "Location")) { if (!l_dbus_message_iter_get_variant(&var, "q", &ele->location)) goto fail; } } /* Check for the presence of the required properties */ if (!idx || !mods || !vendor_mods) goto fail; if (l_queue_find(node->elements, match_element_idx, L_UINT_TO_PTR(ele->idx))) goto fail; l_queue_insert(node->elements, ele, compare_element_idx, NULL); ele->path = l_strdup(path); /* * Add configuration server model on the primary element. * We allow the application not to specify the presence of * the Configuration Server model, since it's implemented by the * daemon. If the model is present in the application properties, * the operation below will be a "no-op". */ if (ele->idx == PRIMARY_ELE_IDX) { mesh_model_add(node, ele->models, CONFIG_SRV_MODEL, NULL); mesh_model_add(node, ele->models, PRV_BEACON_SRV_MODEL, NULL); mesh_model_add(node, ele->models, REM_PROV_SRV_MODEL, NULL); if (node->provisioner) mesh_model_add(node, ele->models, REM_PROV_CLI_MODEL, NULL); } return true; fail: l_free(ele); return false; } static bool get_app_properties(struct mesh_node *node, const char *path, struct l_dbus_message_iter *properties) { const char *key; struct l_dbus_message_iter variant; bool cid = false; bool pid = false; bool vid = false; l_debug("path %s", path); node->comp.crpl = mesh_get_crpl(); while (l_dbus_message_iter_next_entry(properties, &key, &variant)) { if (!cid && !strcmp(key, "CompanyID")) { if (!l_dbus_message_iter_get_variant(&variant, "q", &node->comp.cid)) return false; cid = true; continue; } if (!pid && !strcmp(key, "ProductID")) { if (!l_dbus_message_iter_get_variant(&variant, "q", &node->comp.pid)) return false; pid = true; continue; } if (!vid && !strcmp(key, "VersionID")) { if (!l_dbus_message_iter_get_variant(&variant, "q", &node->comp.vid)) return false; vid = true; continue; } if (!strcmp(key, "CRPL")) { if (!l_dbus_message_iter_get_variant(&variant, "q", &node->comp.crpl)) return false; continue; } } if (!cid || !pid || !vid) return false; return true; } static void save_pages(void *data, void *user_data) { struct mesh_config_comp_page *page = data; struct mesh_node *node = user_data; mesh_config_comp_page_add(node->cfg, page->page_num, page->data, page->len); } static bool add_local_node(struct mesh_node *node, uint16_t unicast, bool kr, bool ivu, uint32_t iv_idx, const uint8_t dev_key[16], uint16_t net_key_idx, const uint8_t net_key[16]) { if (!nodes) nodes = l_queue_new(); l_queue_push_tail(nodes, node); if (!mesh_config_write_iv_index(node->cfg, iv_idx, ivu)) return false; mesh_net_set_iv_index(node->net, iv_idx, ivu); if (!mesh_config_write_unicast(node->cfg, unicast)) return false; l_getrandom(node->token, sizeof(node->token)); if (!mesh_config_write_token(node->cfg, node->token)) return false; memcpy(node->dev_key, dev_key, 16); if (!mesh_config_write_device_key(node->cfg, dev_key)) return false; node->primary = unicast; mesh_net_register_unicast(node->net, unicast, node->num_ele); if (mesh_net_add_key(node->net, net_key_idx, net_key) != MESH_STATUS_SUCCESS) return false; if (kr) { /* Duplicate net key, if the key refresh is on */ if (mesh_net_update_key(node->net, net_key_idx, net_key) != MESH_STATUS_SUCCESS) return false; if (mesh_net_key_refresh_phase_set(node->net, net_key_idx, KEY_REFRESH_PHASE_TWO) != MESH_STATUS_SUCCESS) return false; } l_queue_foreach(node->pages, save_pages, node); init_net_settings(node); /* Initialize internal server models */ cfgmod_server_init(node, PRIMARY_ELE_IDX); remote_prov_server_init(node, PRIMARY_ELE_IDX); remote_prov_client_init(node, PRIMARY_ELE_IDX); /* Initialize Private Beacon server model */ prv_beacon_server_init(node, PRIMARY_ELE_IDX); node->busy = true; return true; } static void update_composition(struct mesh_node *node, struct mesh_node *attach) { if (node->comp.cid != attach->comp.cid) mesh_config_update_company_id(attach->cfg, node->comp.cid); if (node->comp.pid != attach->comp.pid) mesh_config_update_product_id(attach->cfg, node->comp.pid); if (node->comp.vid != attach->comp.vid) mesh_config_update_version_id(attach->cfg, node->comp.vid); if (node->comp.crpl != attach->comp.crpl) mesh_config_update_crpl(attach->cfg, node->comp.crpl); attach->comp = node->comp; } static void update_model_options(struct mesh_node *node, struct mesh_node *attach) { uint32_t len, i; struct node_element *ele, *ele_attach; len = l_queue_length(node->elements); for (i = 0; i < len; i++) { ele = l_queue_find(node->elements, match_element_idx, L_UINT_TO_PTR(i)); ele_attach = l_queue_find(attach->elements, match_element_idx, L_UINT_TO_PTR(i)); if (!ele || !ele_attach) continue; mesh_model_update_opts(node, ele->idx, ele_attach->models, ele->models); } } static bool check_req_node(struct managed_obj_request *req) { struct mesh_node *node; const int offset = 8; uint16_t node_len, len; uint8_t comp[MAX_MSG_LEN - 2]; const uint8_t *node_comp; if (req->type != REQUEST_TYPE_ATTACH) { node = req->node; if (!create_node_config(node, node->uuid)) return false; } else node = req->attach; node_comp = node_get_comp(node, 0, &node_len); len = node_generate_comp(req->node, comp, sizeof(comp)); /* If no page 0 exists, then current composition as valid */ if (req->type != REQUEST_TYPE_ATTACH || !node_len) goto page_zero_valid; /* * If composition has materially changed, save new composition * in page 128 until next NPPI procedure. But we do allow * for CID, PID, VID and/or CRPL to freely change without * requiring a NPPI procedure. */ if (node_len != len || memcmp(&node_comp[offset], &comp[offset], node_len - offset)) return node_set_comp(node, 128, comp, len); page_zero_valid: /* If page 0 represents current App, ensure page 128 doesn't exist */ node_del_comp(node, 128); if (len == node_len && !memcmp(node_comp, comp, len)) return true; return node_set_comp(node, 0, comp, len); } static bool is_zero(const void *a, const void *b) { const struct node_element *element = a; return !element->idx; } static bool attach_req_node(struct mesh_node *attach, struct mesh_node *node) { const struct l_queue_entry *attach_entry; const struct l_queue_entry *node_entry; bool comp_changed = false; attach->obj_path = node->obj_path; node->obj_path = NULL; if (!register_node_object(attach)) { free_node_dbus_resources(attach); return false; } if (attach->num_ele != node->num_ele) { struct mesh_config_node db_node; struct node_element *old_ele, *new_ele; convert_node_to_storage(node, &db_node); /* * If composition has materially changed, we need to discard * everything we knew about elements in the old application, * and start from what they are telling us now. */ old_ele = l_queue_remove_if(attach->elements, is_zero, NULL); new_ele = l_queue_remove_if(node->elements, is_zero, NULL); element_free(new_ele); l_queue_destroy(attach->elements, element_free); attach->elements = node->elements; attach->num_ele = node->num_ele; /* Restore primary elements */ l_queue_push_head(attach->elements, old_ele); comp_changed = true; mesh_config_reset(attach->cfg, &db_node); free_db_storage(&db_node); } attach_entry = l_queue_get_entries(attach->elements); node_entry = l_queue_get_entries(node->elements); /* * Update existing node with paths collected in temporary node, * then remove the temporary. */ while (attach_entry && node_entry) { struct node_element *attach_ele = attach_entry->data; struct node_element *node_ele = node_entry->data; attach_ele->path = node_ele->path; node_ele->path = NULL; attach_entry = attach_entry->next; node_entry = node_entry->next; /* Only need the Primary element during Composition change */ if (comp_changed) break; } mesh_agent_remove(attach->agent); attach->agent = node->agent; node->agent = NULL; attach->provisioner = node->provisioner; attach->app_path = node->app_path; node->app_path = NULL; attach->owner = node->owner; node->owner = NULL; update_composition(node, attach); update_model_options(node, attach); if (comp_changed) node->elements = NULL; node_remove(node); return true; } static void get_managed_objects_cb(struct l_dbus_message *msg, void *user_data) { struct l_dbus_message_iter objects, interfaces; struct managed_obj_request *req = user_data; const char *path; struct mesh_node *node = req->node; struct node_import *import; bool have_app = false; unsigned int num_ele; struct keyring_net_key net_key; uint8_t dev_key[16]; if (req->type == REQUEST_TYPE_ATTACH) req->attach->busy = false; if (!msg || l_dbus_message_is_error(msg)) { l_error("Failed to get app's dbus objects"); goto fail; } if (!l_dbus_message_get_arguments(msg, "a{oa{sa{sv}}}", &objects)) { l_error("Failed to parse app's dbus objects"); goto fail; } while (l_dbus_message_iter_next_entry(&objects, &path, &interfaces)) { struct l_dbus_message_iter properties; const char *interface; while (l_dbus_message_iter_next_entry(&interfaces, &interface, &properties)) { bool res; if (!strcmp(MESH_ELEMENT_INTERFACE, interface)) { res = get_element_properties(node, path, &properties); if (!res) goto fail; } else if (!strcmp(MESH_APPLICATION_INTERFACE, interface)) { if (have_app) goto fail; req->node->app_path = l_strdup(path); res = get_app_properties(node, path, &properties); if (!res) goto fail; have_app = true; } else if (!strcmp(MESH_PROVISION_AGENT_INTERFACE, interface)) { const char *sender; sender = l_dbus_message_get_sender(msg); node->agent = mesh_agent_create(path, sender, &properties); if (!node->agent) goto fail; } else if (!strcmp(MESH_PROVISIONER_INTERFACE, interface)) { node->provisioner = true; } } } if (!have_app) { l_error("Interface %s not found", MESH_APPLICATION_INTERFACE); goto fail; } if (l_queue_isempty(node->elements)) { l_error("Interface %s not found", MESH_ELEMENT_INTERFACE); goto fail; } if (!l_queue_find(node->elements, match_element_idx, L_UINT_TO_PTR(PRIMARY_ELE_IDX))) { l_debug("Primary element not detected"); goto fail; } num_ele = l_queue_length(node->elements); if (num_ele > MAX_ELE_COUNT) goto fail; node->num_ele = num_ele; if (!check_req_node(req)) goto fail; switch (req->type) { case REQUEST_TYPE_ATTACH: if (!attach_req_node(req->attach, node)) goto fail; req->attach->disc_watch = l_dbus_add_disconnect_watch( dbus_get_bus(), req->attach->owner, app_disc_cb, req->attach, NULL); req->ready_cb(req->pending_msg, MESH_ERROR_NONE, req->attach); return; case REQUEST_TYPE_JOIN: if (!node->agent) { l_error("Interface %s not found", MESH_PROVISION_AGENT_INTERFACE); goto fail; } req->join_ready_cb(node, node->agent); return; case REQUEST_TYPE_IMPORT: import = req->import; if (!add_local_node(node, import->unicast, import->flags.kr, import->flags.ivu, import->iv_index, import->dev_key, import->net_idx, import->net_key)) goto fail; req->ready_cb(req->pending_msg, MESH_ERROR_NONE, node); l_free(import); return; case REQUEST_TYPE_CREATE: /* Generate device and primary network keys */ l_getrandom(dev_key, sizeof(dev_key)); l_getrandom(net_key.old_key, sizeof(net_key.old_key)); memcpy(net_key.new_key, net_key.old_key, sizeof(net_key.old_key)); net_key.net_idx = PRIMARY_NET_IDX; net_key.phase = KEY_REFRESH_PHASE_NONE; if (!add_local_node(node, DEFAULT_NEW_UNICAST, false, false, DEFAULT_IV_INDEX, dev_key, PRIMARY_NET_IDX, net_key.old_key)) goto fail; if (!keyring_put_remote_dev_key(node, DEFAULT_NEW_UNICAST, node->num_ele, dev_key)) goto fail; if (!keyring_put_net_key(node, PRIMARY_NET_IDX, &net_key)) goto fail; req->ready_cb(req->pending_msg, MESH_ERROR_NONE, node); return; default: goto fail; } fail: /* Handle failed requests */ node_remove(node); if (req->type == REQUEST_TYPE_JOIN) req->join_ready_cb(NULL, NULL); else req->ready_cb(req->pending_msg, MESH_ERROR_FAILED, NULL); if (req->type == REQUEST_TYPE_IMPORT) l_free(req->import); } static void send_managed_objects_request(const char *destination, const char *path, struct managed_obj_request *req) { struct l_dbus_message *msg; msg = l_dbus_message_new_method_call(dbus_get_bus(), destination, path, L_DBUS_INTERFACE_OBJECT_MANAGER, "GetManagedObjects"); l_dbus_message_set_arguments(msg, ""); dbus_send_with_timeout(dbus_get_bus(), msg, get_managed_objects_cb, req, l_free, DEFAULT_DBUS_TIMEOUT); } /* Establish relationship between application and mesh node */ void node_attach(const char *app_root, const char *sender, uint64_t token, node_ready_func_t cb, void *user_data) { struct managed_obj_request *req; struct mesh_node *node; node = l_queue_find(nodes, match_token, (void *) &token); if (!node) { cb(user_data, MESH_ERROR_NOT_FOUND, NULL); return; } /* Check if there is a pending request associated with this node */ if (node->busy) { cb(user_data, MESH_ERROR_BUSY, NULL); return; } /* Check if the node is already in use */ if (node->owner) { l_warn("The node is already in use"); cb(user_data, MESH_ERROR_ALREADY_EXISTS, NULL); return; } req = l_new(struct managed_obj_request, 1); /* * Create a temporary node to collect composition data from attaching * application. Existing node is passed in req->attach. */ req->node = node_new(node->uuid); req->node->owner = l_strdup(sender); req->ready_cb = cb; req->pending_msg = user_data; req->attach = node; req->type = REQUEST_TYPE_ATTACH; node->busy = true; send_managed_objects_request(sender, app_root, req); } /* Create a temporary pre-provisioned node */ void node_join(const char *app_root, const char *sender, const uint8_t *uuid, node_join_ready_func_t cb) { struct managed_obj_request *req; l_debug(""); req = l_new(struct managed_obj_request, 1); req->node = node_new(uuid); req->join_ready_cb = cb; req->type = REQUEST_TYPE_JOIN; send_managed_objects_request(sender, app_root, req); } void node_import(const char *app_root, const char *sender, const uint8_t *uuid, const uint8_t dev_key[16], const uint8_t net_key[16], uint16_t net_idx, bool kr, bool ivu, uint32_t iv_index, uint16_t unicast, node_ready_func_t cb, void *user_data) { struct managed_obj_request *req; l_debug(""); req = l_new(struct managed_obj_request, 1); req->node = node_new(uuid); req->ready_cb = cb; req->pending_msg = user_data; req->import = l_new(struct node_import, 1); memcpy(req->import->dev_key, dev_key, 16); memcpy(req->import->net_key, net_key, 16); req->import->net_idx = net_idx; req->import->flags.kr = kr; req->import->flags.ivu = ivu; req->import->iv_index = iv_index; req->import->unicast = unicast; req->type = REQUEST_TYPE_IMPORT; send_managed_objects_request(sender, app_root, req); } void node_create(const char *app_root, const char *sender, const uint8_t *uuid, node_ready_func_t cb, void *user_data) { struct managed_obj_request *req; l_debug(""); req = l_new(struct managed_obj_request, 1); req->node = node_new(uuid); req->ready_cb = cb; req->pending_msg = user_data; req->type = REQUEST_TYPE_CREATE; send_managed_objects_request(sender, app_root, req); } static void build_element_config(void *a, void *b) { struct node_element *ele = a; struct l_dbus_message_builder *builder = b; l_debug("Element %u", ele->idx); l_dbus_message_builder_enter_struct(builder, "ya(qa{sv})"); /* Element index */ l_dbus_message_builder_append_basic(builder, 'y', &ele->idx); l_dbus_message_builder_enter_array(builder, "(qa{sv})"); /* Iterate over models */ l_queue_foreach(ele->models, mesh_model_build_config, builder); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_leave_struct(builder); } void node_build_attach_reply(struct mesh_node *node, struct l_dbus_message *reply) { struct l_dbus_message_builder *builder; builder = l_dbus_message_builder_new(reply); /* Node object path */ l_dbus_message_builder_append_basic(builder, 'o', node->obj_path); /* Array of element configurations "a*/ l_dbus_message_builder_enter_array(builder, "(ya(qa{sv}))"); l_queue_foreach(node->elements, build_element_config, builder); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); } static bool parse_send_options(struct l_dbus_message_iter *itr, struct send_options *opts) { const char *key; struct l_dbus_message_iter var; opts->segmented = false; opts->vendor_id = SIG_VENDOR; while (l_dbus_message_iter_next_entry(itr, &key, &var)) { if (!strcmp(key, "ForceSegmented")) { if (!l_dbus_message_iter_get_variant(&var, "b", &opts->segmented)) return false; } if (!strcmp(key, "Vendor")) { if (!l_dbus_message_iter_get_variant(&var, "q", &opts->vendor_id)) return false; } } return true; } static struct l_dbus_message *send_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; const char *sender, *ele_path; struct l_dbus_message_iter dict, iter_data; struct send_options opts; struct node_element *ele; uint16_t dst, app_idx, net_idx, src; uint8_t *data; uint32_t len; l_debug("Send"); sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node->owner)) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "oqqa{sv}ay", &ele_path, &dst, &app_idx, &dict, &iter_data)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); ele = l_queue_find(node->elements, match_element_path, ele_path); if (!ele) return dbus_error(msg, MESH_ERROR_NOT_FOUND, "Element not found"); if (!parse_send_options(&dict, &opts)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); src = node_get_primary(node) + ele->idx; if (!l_dbus_message_iter_get_fixed_array(&iter_data, &data, &len) || !len || len > MAX_MSG_LEN) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Incorrect data"); if (app_idx & ~APP_IDX_MASK) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Invalid key index"); net_idx = appkey_net_idx(node_get_net(node), app_idx); if (net_idx == NET_IDX_INVALID) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Key not found"); if (!mesh_model_send(node, src, dst, app_idx, net_idx, DEFAULT_TTL, opts.segmented, len, data)) return dbus_error(msg, MESH_ERROR_FAILED, NULL); return l_dbus_message_new_method_return(msg); } static struct l_dbus_message *dev_key_send_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; const char *sender, *ele_path; struct l_dbus_message_iter iter_data, dict; struct send_options opts; struct node_element *ele; uint16_t dst, app_idx, net_idx, src; bool remote; uint8_t *data; uint32_t len; l_debug("DevKeySend"); sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node->owner)) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "oqbqa{sv}ay", &ele_path, &dst, &remote, &net_idx, &dict, &iter_data)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); /* Loopbacks to local servers must use *remote* addressing */ if (!remote && mesh_net_is_local_address(node->net, dst, 1)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); ele = l_queue_find(node->elements, match_element_path, ele_path); if (!ele) return dbus_error(msg, MESH_ERROR_NOT_FOUND, "Element not found"); if (!parse_send_options(&dict, &opts)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); src = node_get_primary(node) + ele->idx; if (!l_dbus_message_iter_get_fixed_array(&iter_data, &data, &len) || !len || len > MAX_MSG_LEN) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Incorrect data"); app_idx = remote ? APP_IDX_DEV_REMOTE : APP_IDX_DEV_LOCAL; if (!mesh_model_send(node, src, dst, app_idx, net_idx, DEFAULT_TTL, opts.segmented, len, data)) return dbus_error(msg, MESH_ERROR_NOT_FOUND, NULL); return l_dbus_message_new_method_return(msg); } static struct l_dbus_message *add_netkey_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; const char *sender, *ele_path; struct node_element *ele; uint16_t dst, sub_idx, net_idx, src; bool update; struct keyring_net_key key; uint8_t data[20]; l_debug("AddNetKey"); sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node->owner)) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "oqqqb", &ele_path, &dst, &sub_idx, &net_idx, &update)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); ele = l_queue_find(node->elements, match_element_path, ele_path); if (!ele) return dbus_error(msg, MESH_ERROR_NOT_FOUND, "Element not found"); src = node_get_primary(node) + ele->idx; if (!keyring_get_net_key(node, sub_idx, &key)) return dbus_error(msg, MESH_ERROR_NOT_FOUND, "NetKey not found"); if (!update) { l_put_be16(OP_NETKEY_ADD, data); if (key.phase != KEY_REFRESH_PHASE_TWO) memcpy(data + 4, key.old_key, 16); else memcpy(data + 4, key.new_key, 16); } else { if (key.phase != KEY_REFRESH_PHASE_ONE) return dbus_error(msg, MESH_ERROR_FAILED, "Cannot update"); l_put_be16(OP_NETKEY_UPDATE, data); memcpy(data + 4, key.new_key, 16); } l_put_le16(sub_idx, &data[2]); if (!mesh_model_send(node, src, dst, APP_IDX_DEV_REMOTE, net_idx, DEFAULT_TTL, false, 20, data)) return dbus_error(msg, MESH_ERROR_NOT_FOUND, NULL); return l_dbus_message_new_method_return(msg); } static struct l_dbus_message *add_appkey_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; const char *sender, *ele_path; struct node_element *ele; uint16_t dst, app_idx, net_idx, src; bool update; struct keyring_net_key net_key; struct keyring_app_key app_key; uint8_t data[20]; l_debug("AddAppKey"); sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node->owner)) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "oqqqb", &ele_path, &dst, &app_idx, &net_idx, &update)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); ele = l_queue_find(node->elements, match_element_path, ele_path); if (!ele) return dbus_error(msg, MESH_ERROR_NOT_FOUND, "Element not found"); src = node_get_primary(node) + ele->idx; if (!keyring_get_app_key(node, app_idx, &app_key)) return dbus_error(msg, MESH_ERROR_NOT_FOUND, "AppKey not found"); if (!keyring_get_net_key(node, app_key.net_idx, &net_key)) { return dbus_error(msg, MESH_ERROR_NOT_FOUND, "Bound NetKey not found"); } if (!update) { data[0] = OP_APPKEY_ADD; if (net_key.phase != KEY_REFRESH_PHASE_TWO) memcpy(data + 4, app_key.old_key, 16); else memcpy(data + 4, app_key.new_key, 16); } else { if (net_key.phase != KEY_REFRESH_PHASE_ONE) return dbus_error(msg, MESH_ERROR_FAILED, "Cannot update"); data[0] = OP_APPKEY_UPDATE; memcpy(data + 4, app_key.new_key, 16); } /* Pack bound NetKey and AppKey into 3 octets */ data[1] = app_key.net_idx; data[2] = ((app_key.net_idx >> 8) & 0xf) | ((app_idx << 4) & 0xf0); data[3] = app_idx >> 4; if (!mesh_model_send(node, src, dst, APP_IDX_DEV_REMOTE, net_idx, DEFAULT_TTL, false, 20, data)) return dbus_error(msg, MESH_ERROR_NOT_FOUND, NULL); return l_dbus_message_new_method_return(msg); } static struct l_dbus_message *publish_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; const char *sender, *ele_path; struct l_dbus_message_iter iter_data, dict; uint16_t mod_id, src; struct send_options opts; struct node_element *ele; uint8_t *data; uint32_t len, id; int result; l_debug("Publish"); sender = l_dbus_message_get_sender(msg); if (strcmp(sender, node->owner)) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "oqa{sv}ay", &ele_path, &mod_id, &dict, &iter_data)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); ele = l_queue_find(node->elements, match_element_path, ele_path); if (!ele) return dbus_error(msg, MESH_ERROR_NOT_FOUND, "Element not found"); if (!parse_send_options(&dict, &opts)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); src = node_get_primary(node) + ele->idx; if (!l_dbus_message_iter_get_fixed_array(&iter_data, &data, &len) || !len || len > MAX_MSG_LEN) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Incorrect data"); id = SET_ID(opts.vendor_id, mod_id); result = mesh_model_publish(node, id, src, opts.segmented, len, data); if (result != MESH_ERROR_NONE) return dbus_error(msg, result, NULL); return l_dbus_message_new_method_return(msg); } static bool features_getter(struct l_dbus *dbus, struct l_dbus_message *msg, struct l_dbus_message_builder *builder, void *user_data) { struct mesh_node *node = user_data; uint8_t friend = node_friend_mode_get(node); uint8_t lpn = node_lpn_mode_get(node); uint8_t proxy = node_proxy_mode_get(node); uint8_t count; uint16_t interval; uint8_t relay = node_relay_mode_get(node, &count, &interval); l_dbus_message_builder_enter_array(builder, "{sv}"); if (friend != MESH_MODE_UNSUPPORTED) dbus_append_dict_entry_basic(builder, "Friend", "b", &friend); if (lpn != MESH_MODE_UNSUPPORTED) dbus_append_dict_entry_basic(builder, "LowPower", "b", &lpn); if (proxy != MESH_MODE_UNSUPPORTED) dbus_append_dict_entry_basic(builder, "Proxy", "b", &proxy); if (relay != MESH_MODE_UNSUPPORTED) dbus_append_dict_entry_basic(builder, "Relay", "b", &relay); l_dbus_message_builder_leave_array(builder); return true; } static bool beacon_getter(struct l_dbus *dbus, struct l_dbus_message *msg, struct l_dbus_message_builder *builder, void *user_data) { struct mesh_node *node = user_data; bool beacon_mode = node_beacon_mode_get(node) == MESH_MODE_ENABLED; l_dbus_message_builder_append_basic(builder, 'b', &beacon_mode); return true; } static bool ivupdate_getter(struct l_dbus *dbus, struct l_dbus_message *msg, struct l_dbus_message_builder *builder, void *user_data) { struct mesh_node *node = user_data; struct mesh_net *net = node_get_net(node); uint8_t flags; uint32_t iv_index; bool ivu; mesh_net_get_snb_state(net, &flags, &iv_index); ivu = flags & IV_INDEX_UPDATE; l_dbus_message_builder_append_basic(builder, 'b', &ivu); return true; } static bool ivindex_getter(struct l_dbus *dbus, struct l_dbus_message *msg, struct l_dbus_message_builder *builder, void *user_data) { struct mesh_node *node = user_data; struct mesh_net *net = node_get_net(node); uint8_t flags; uint32_t iv_index; mesh_net_get_snb_state(net, &flags, &iv_index); l_dbus_message_builder_append_basic(builder, 'u', &iv_index); return true; } static bool seq_num_getter(struct l_dbus *dbus, struct l_dbus_message *msg, struct l_dbus_message_builder *builder, void *user_data) { struct mesh_node *node = user_data; struct mesh_net *net = node_get_net(node); uint32_t seq_nr = mesh_net_get_seq_num(net); l_dbus_message_builder_append_basic(builder, 'u', &seq_nr); return true; } static bool lastheard_getter(struct l_dbus *dbus, struct l_dbus_message *msg, struct l_dbus_message_builder *builder, void *user_data) { struct mesh_node *node = user_data; struct mesh_net *net = node_get_net(node); struct timeval now; uint32_t last_heard; gettimeofday(&now, NULL); last_heard = now.tv_sec - mesh_net_get_instant(net); l_dbus_message_builder_append_basic(builder, 'u', &last_heard); return true; } static bool addresses_getter(struct l_dbus *dbus, struct l_dbus_message *msg, struct l_dbus_message_builder *builder, void *user_data) { struct mesh_node *node = user_data; const struct l_queue_entry *entry; l_dbus_message_builder_enter_array(builder, "q"); entry = l_queue_get_entries(node->elements); for (; entry; entry = entry->next) { const struct node_element *ele = entry->data; uint16_t address = node->primary + ele->idx; l_dbus_message_builder_append_basic(builder, 'q', &address); } l_dbus_message_builder_leave_array(builder); return true; } static void setup_node_interface(struct l_dbus_interface *iface) { l_dbus_interface_method(iface, "Send", 0, send_call, "", "oqqa{sv}ay", "element_path", "destination", "key_index", "options", "data"); l_dbus_interface_method(iface, "DevKeySend", 0, dev_key_send_call, "", "oqbqa{sv}ay", "element_path", "destination", "remote", "net_index", "options", "data"); l_dbus_interface_method(iface, "AddNetKey", 0, add_netkey_call, "", "oqqqb", "element_path", "destination", "subnet_index", "net_index", "update"); l_dbus_interface_method(iface, "AddAppKey", 0, add_appkey_call, "", "oqqqb", "element_path", "destination", "app_index", "net_index", "update"); l_dbus_interface_method(iface, "Publish", 0, publish_call, "", "oqa{sv}ay", "element_path", "model_id", "options", "data"); l_dbus_interface_property(iface, "Features", 0, "a{sv}", features_getter, NULL); l_dbus_interface_property(iface, "Beacon", 0, "b", beacon_getter, NULL); l_dbus_interface_property(iface, "IvUpdate", 0, "b", ivupdate_getter, NULL); l_dbus_interface_property(iface, "IvIndex", 0, "u", ivindex_getter, NULL); l_dbus_interface_property(iface, "SequenceNumber", 0, "u", seq_num_getter, NULL); l_dbus_interface_property(iface, "SecondsSinceLastHeard", 0, "u", lastheard_getter, NULL); l_dbus_interface_property(iface, "Addresses", 0, "aq", addresses_getter, NULL); } void node_property_changed(struct mesh_node *node, const char *property) { struct l_dbus *bus = dbus_get_bus(); if (bus && node->obj_path) l_dbus_property_changed(dbus_get_bus(), node->obj_path, MESH_NODE_INTERFACE, property); } bool node_dbus_init(struct l_dbus *bus) { if (!l_dbus_register_interface(bus, MESH_NODE_INTERFACE, setup_node_interface, NULL, false)) { l_info("Unable to register %s interface", MESH_NODE_INTERFACE); return false; } return true; } const char *node_get_owner(struct mesh_node *node) { return node->owner; } const char *node_get_element_path(struct mesh_node *node, uint8_t ele_idx) { struct node_element *ele; ele = l_queue_find(node->elements, match_element_idx, L_UINT_TO_PTR(ele_idx)); if (!ele) return NULL; return ele->path; } bool node_add_pending_local(struct mesh_node *node, const struct mesh_prov_node_info *info) { bool kr = !!(info->flags & PROV_FLAG_KR); bool ivu = !!(info->flags & PROV_FLAG_IVU); return add_local_node(node, info->unicast, kr, ivu, info->iv_index, info->device_key, info->net_index, info->net_key); } struct mesh_config *node_config_get(struct mesh_node *node) { return node->cfg; } const char *node_get_storage_dir(struct mesh_node *node) { return node->storage_dir; } const char *node_get_app_path(struct mesh_node *node) { if (!node) return NULL; return node->app_path; } struct mesh_net *node_get_net(struct mesh_node *node) { return node->net; } struct mesh_agent *node_get_agent(struct mesh_node *node) { return node->agent; } bool node_load_from_storage(const char *storage_dir) { return mesh_config_load_nodes(storage_dir, init_from_storage, NULL); } /* * This is called for a new node that: * - has been created as a result of successful completion of Join() * or Create() or Import() methods * and * - has been confirmed via successful token delivery to the application * * After a node has been created, the information gathered during initial * GetManagedObjects() call is cleared. The subsequent call to Attach() would * verify node's integrity and re-initialize node's D-Bus resources. */ void node_finalize_new_node(struct mesh_node *node, struct mesh_io *io) { if (!node) return; free_node_dbus_resources(node); mesh_agent_remove(node->agent); node->agent = NULL; node->busy = false; /* Register callback for the node's io */ attach_io(node, io); } bluez-5.87/mesh/PaxHeaders/mesh-io-mgmt.c0000644000000000000000000000005015063045254015262 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/mesh-io-mgmt.c0000644000000000000000000004144015063045254014746 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2020 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "monitor/bt.h" #include "bluetooth/bluetooth.h" #include "bluetooth/bluetooth.h" #include "bluetooth/mgmt.h" #include "src/shared/ad.h" #include "src/shared/mgmt.h" #include "mesh/mesh-defs.h" #include "mesh/util.h" #include "mesh/mesh-mgmt.h" #include "mesh/mesh-io.h" #include "mesh/mesh-io-api.h" #include "mesh/mesh-io-mgmt.h" struct mesh_io_private { struct mesh_io *io; void *user_data; struct l_timeout *tx_timeout; struct l_timeout *dup_timeout; struct l_queue *dup_filters; struct l_queue *tx_pkts; struct tx_pkt *tx; unsigned int tx_id; unsigned int rx_id; uint16_t send_idx; uint16_t interval; uint8_t handle; bool sending; bool active; }; struct process_data { struct mesh_io_private *pvt; const uint8_t *data; uint8_t len; struct mesh_io_recv_info info; }; struct tx_pkt { struct mesh_io_send_info info; bool delete; uint8_t len; uint8_t pkt[MESH_AD_MAX_LEN]; }; struct tx_pattern { const uint8_t *data; uint8_t len; }; #define DUP_FILTER_TIME 1000 /* Accept one instance of unique message a second */ struct dup_filter { uint64_t data; uint32_t instant; uint8_t addr[6]; } __packed; static const uint8_t zero_addr[] = {0, 0, 0, 0, 0, 0}; static struct mesh_io_private *pvt; static uint32_t get_instant(void) { struct timeval tm; uint32_t instant; gettimeofday(&tm, NULL); instant = tm.tv_sec * 1000; instant += tm.tv_usec / 1000; return instant; } static uint32_t instant_remaining_ms(uint32_t instant) { instant -= get_instant(); return instant; } static bool find_by_addr(const void *a, const void *b) { const struct dup_filter *filter = a; return !memcmp(filter->addr, b, 6); } static bool find_by_adv(const void *a, const void *b) { const struct dup_filter *filter = a; uint64_t data = l_get_be64(b); return !memcmp(filter->addr, zero_addr, 6) && filter->data == data; } static void filter_timeout(struct l_timeout *timeout, void *user_data) { struct dup_filter *filter; uint32_t instant, delta; if (!pvt) goto done; instant = get_instant(); filter = l_queue_peek_tail(pvt->dup_filters); while (filter) { delta = instant - filter->instant; if (delta >= DUP_FILTER_TIME) { l_queue_remove(pvt->dup_filters, filter); l_free(filter); } else { l_timeout_modify(timeout, 1); return; } filter = l_queue_peek_tail(pvt->dup_filters); } pvt->dup_timeout = NULL; done: l_timeout_remove(timeout); } /* Ignore consecutive duplicate advertisements within timeout period */ static bool filter_dups(const uint8_t *addr, const uint8_t *adv, uint32_t instant) { struct dup_filter *filter; uint32_t instant_delta; uint64_t data = l_get_be64(adv); if (!addr) addr = zero_addr; if (adv[1] == BT_AD_MESH_PROV) { filter = l_queue_find(pvt->dup_filters, find_by_adv, adv); if (!filter && addr != zero_addr) return false; l_queue_remove(pvt->dup_filters, filter); } else { filter = l_queue_remove_if(pvt->dup_filters, find_by_addr, addr); } if (!filter) { filter = l_new(struct dup_filter, 1); memcpy(filter->addr, addr, 6); } /* Start filter expiration timer */ if (!l_queue_length(pvt->dup_filters)) pvt->dup_timeout = l_timeout_create(1, filter_timeout, NULL, NULL); l_queue_push_head(pvt->dup_filters, filter); instant_delta = instant - filter->instant; if (instant_delta >= DUP_FILTER_TIME || data != filter->data) { filter->instant = instant; filter->data = data; return false; } return true; } static void process_rx_callbacks(void *v_reg, void *v_rx) { struct mesh_io_reg *rx_reg = v_reg; struct process_data *rx = v_rx; if (!memcmp(rx->data, rx_reg->filter, rx_reg->len)) rx_reg->cb(rx_reg->user_data, &rx->info, rx->data, rx->len); } static void process_rx(uint16_t index, struct mesh_io_private *pvt, int8_t rssi, uint32_t instant, const uint8_t *addr, const uint8_t *data, uint8_t len) { struct process_data rx = { .pvt = pvt, .data = data, .len = len, .info.instant = instant, .info.addr = addr, .info.chan = 7, .info.rssi = rssi, }; /* Accept all traffic except beacons from any controller */ if (index != pvt->send_idx && data[0] == BT_AD_MESH_BEACON) return; print_packet("RX", data, len); l_queue_foreach(pvt->io->rx_regs, process_rx_callbacks, &rx); } static void send_cmplt(uint16_t index, uint16_t length, const void *param, void *user_data) { /* print_packet("Mesh Send Complete", param, length); */ } static void event_device_found(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_mesh_device_found *ev = param; struct mesh_io_private *pvt = user_data; const uint8_t *adv; const uint8_t *addr; uint32_t instant; uint16_t adv_len; uint16_t len = 0; if (ev->addr.type < 1 || ev->addr.type > 2) return; instant = get_instant(); adv = ev->eir; adv_len = ev->eir_len; addr = ev->addr.bdaddr.b; if (filter_dups(addr, adv, instant)) return; while (len < adv_len - 1) { uint8_t field_len = adv[0]; /* Check for the end of advertising data */ if (field_len == 0) break; len += field_len + 1; /* Do not continue data parsing if got incorrect length */ if (len > adv_len) break; if (adv[1] >= BT_AD_MESH_PROV && adv[1] <= BT_AD_MESH_BEACON) process_rx(index, pvt, ev->rssi, instant, addr, adv + 1, adv[0]); adv += field_len + 1; } } static bool simple_match(const void *a, const void *b) { return a == b; } static bool find_by_ad_type(const void *a, const void *b) { const struct tx_pkt *tx = a; uint8_t ad_type = L_PTR_TO_UINT(b); return !ad_type || ad_type == tx->pkt[0]; } static bool find_by_pattern(const void *a, const void *b) { const struct tx_pkt *tx = a; const struct tx_pattern *pattern = b; if (tx->len < pattern->len) return false; return (!memcmp(tx->pkt, pattern->data, pattern->len)); } static bool find_active(const void *a, const void *b) { const struct mesh_io_reg *rx_reg = a; /* Mesh specific AD types do *not* require active scanning, * so do not turn on Active Scanning on their account. */ if (rx_reg->filter[0] < BT_AD_MESH_PROV || rx_reg->filter[0] > BT_AD_MESH_BEACON) return true; return false; } static void mesh_up(uint8_t status, uint16_t length, const void *param, void *user_data) { int index = L_PTR_TO_UINT(user_data); l_debug("HCI%d Mesh up status: %d", index, status); } static void le_up(uint8_t status, uint16_t length, const void *param, void *user_data) { int index = L_PTR_TO_UINT(user_data); l_debug("HCI%d LE up status: %d", index, status); } static void ctl_up(uint8_t status, uint16_t length, const void *param, void *user_data) { int index = L_PTR_TO_UINT(user_data); uint16_t len; struct mgmt_cp_set_mesh *mesh; uint8_t mesh_ad_types[] = { BT_AD_MESH_DATA, BT_AD_MESH_BEACON, BT_AD_MESH_PROV }; l_debug("HCI%d is up status: %d", index, status); if (status) return; len = sizeof(struct mgmt_cp_set_mesh) + sizeof(mesh_ad_types); mesh = l_malloc(len); mesh->enable = 1; mesh->window = L_CPU_TO_LE16(0x1000); mesh->period = L_CPU_TO_LE16(0x1000); mesh->num_ad_types = sizeof(mesh_ad_types); memcpy(mesh->ad_types, mesh_ad_types, sizeof(mesh_ad_types)); pvt->rx_id = mesh_mgmt_register(MGMT_EV_MESH_DEVICE_FOUND, MGMT_INDEX_NONE, event_device_found, pvt, NULL); pvt->tx_id = mesh_mgmt_register(MGMT_EV_MESH_PACKET_CMPLT, index, send_cmplt, pvt, NULL); mesh_mgmt_send(MGMT_OP_SET_MESH_RECEIVER, index, len, mesh, mesh_up, L_UINT_TO_PTR(index), NULL); l_debug("done %d mesh startup", index); l_free(mesh); if (pvt->send_idx == MGMT_INDEX_NONE) { pvt->send_idx = index; if (pvt && pvt->io && pvt->io->ready) { pvt->io->ready(pvt->io->user_data, true); pvt->io->ready = NULL; } } } static void read_info_cb(uint8_t status, uint16_t length, const void *param, void *user_data) { unsigned char le[] = { 0x01 }; int index = L_PTR_TO_UINT(user_data); const struct mgmt_rp_read_info *rp = param; uint32_t current_settings, supported_settings; l_debug("hci %u status 0x%02x", index, status); if (!pvt) return; if (status != MGMT_STATUS_SUCCESS) { l_error("Failed to read info for hci index %u: %s (0x%02x)", index, mgmt_errstr(status), status); return; } if (length < sizeof(*rp)) { l_error("Read info response too short"); return; } current_settings = btohl(rp->current_settings); supported_settings = btohl(rp->supported_settings); if (!(supported_settings & MGMT_SETTING_LE)) { l_info("Controller hci %u does not support LE", index); return; } if (!(current_settings & MGMT_SETTING_POWERED)) { unsigned char power[] = { 0x01 }; /* TODO: Initialize this HCI controller */ l_info("Controller hci %u not in use", index); mesh_mgmt_send(MGMT_OP_SET_LE, index, sizeof(le), &le, le_up, L_UINT_TO_PTR(index), NULL); mesh_mgmt_send(MGMT_OP_SET_POWERED, index, sizeof(power), &power, ctl_up, L_UINT_TO_PTR(index), NULL); } else { l_info("Controller hci %u already in use (%x)", index, current_settings); /* Share this controller with bluetoothd */ mesh_mgmt_send(MGMT_OP_SET_LE, index, sizeof(le), &le, ctl_up, L_UINT_TO_PTR(index), NULL); } } static bool dev_init(struct mesh_io *io, void *opts, void *user_data) { uint16_t index = *(int *)opts; if (!io || pvt) return false; pvt = l_new(struct mesh_io_private, 1); pvt->send_idx = MGMT_INDEX_NONE; mesh_mgmt_send(MGMT_OP_READ_INFO, index, 0, NULL, read_info_cb, L_UINT_TO_PTR(index), NULL); pvt->dup_filters = l_queue_new(); pvt->tx_pkts = l_queue_new(); pvt->io = io; io->pvt = pvt; return true; } static bool dev_destroy(struct mesh_io *io) { unsigned char param[] = { 0x00 }; if (io->pvt != pvt) return true; mesh_mgmt_send(MGMT_OP_SET_POWERED, io->index, sizeof(param), ¶m, NULL, NULL, NULL); mesh_mgmt_unregister(pvt->rx_id); mesh_mgmt_unregister(pvt->tx_id); l_timeout_remove(pvt->tx_timeout); l_timeout_remove(pvt->dup_timeout); l_queue_destroy(pvt->dup_filters, l_free); l_queue_destroy(pvt->tx_pkts, l_free); io->pvt = NULL; l_free(pvt); pvt = NULL; return true; } static bool dev_caps(struct mesh_io *io, struct mesh_io_caps *caps) { struct mesh_io_private *pvt = io->pvt; if (!pvt || !caps) return false; caps->max_num_filters = 255; caps->window_accuracy = 50; return true; } static void send_cancel(struct mesh_io_private *pvt) { struct mgmt_cp_mesh_send_cancel remove; if (!pvt) return; if (pvt->handle) { remove.handle = pvt->handle; /* l_debug("Cancel TX"); */ mesh_mgmt_send(MGMT_OP_MESH_SEND_CANCEL, pvt->send_idx, sizeof(remove), &remove, NULL, NULL, NULL); } } static void tx_to(struct l_timeout *timeout, void *user_data); static void send_queued(uint8_t status, uint16_t length, const void *param, void *user_data) { struct tx_pkt *tx = user_data; if (status) l_debug("Mesh Send Failed: %d", status); else if (param && length >= 1) pvt->handle = *(uint8_t *) param; if (tx->delete) { l_queue_remove_if(pvt->tx_pkts, simple_match, tx); l_free(tx); pvt->tx = NULL; } } static void send_pkt(struct mesh_io_private *pvt, struct tx_pkt *tx, uint16_t interval) { uint8_t buffer[sizeof(struct mgmt_cp_mesh_send) + tx->len + 1]; struct mgmt_cp_mesh_send *send = (void *) buffer; uint16_t index; size_t len; if (!pvt) return; index = pvt->send_idx; len = sizeof(buffer); memset(send, 0, len); send->addr.type = BDADDR_LE_RANDOM; send->instant = 0; send->delay = 0; send->cnt = 1; send->adv_data_len = tx->len + 1; send->adv_data[0] = tx->len; memcpy(send->adv_data + 1, tx->pkt, tx->len); /* Filter looped back Provision packets */ if (tx->pkt[0] == BT_AD_MESH_PROV) filter_dups(NULL, send->adv_data, get_instant()); mesh_mgmt_send(MGMT_OP_MESH_SEND, index, len, send, send_queued, tx, NULL); /* print_packet("Mesh Send Start", tx->pkt, tx->len); */ pvt->tx = tx; } static void tx_to(struct l_timeout *timeout, void *user_data) { struct mesh_io_private *pvt = user_data; struct tx_pkt *tx; uint16_t ms; uint8_t count; if (!pvt) return; tx = l_queue_pop_head(pvt->tx_pkts); if (!tx) { l_timeout_remove(timeout); pvt->tx_timeout = NULL; send_cancel(pvt); pvt->tx = NULL; return; } if (tx->info.type == MESH_IO_TIMING_TYPE_GENERAL) { ms = tx->info.u.gen.interval; count = tx->info.u.gen.cnt; if (count != MESH_IO_TX_COUNT_UNLIMITED) tx->info.u.gen.cnt--; } else { ms = 25; count = 1; } tx->delete = (count == 1); send_pkt(pvt, tx, ms); if (count == 1) { /* Recalculate wakeup if we are responding to POLL */ tx = l_queue_peek_head(pvt->tx_pkts); if (tx && tx->info.type == MESH_IO_TIMING_TYPE_POLL_RSP) { ms = instant_remaining_ms(tx->info.u.poll_rsp.instant + tx->info.u.poll_rsp.delay); } } else l_queue_push_tail(pvt->tx_pkts, tx); if (timeout) { pvt->tx_timeout = timeout; l_timeout_modify_ms(timeout, ms); } else pvt->tx_timeout = l_timeout_create_ms(ms, tx_to, pvt, NULL); } static void tx_worker(void *user_data) { struct mesh_io_private *pvt = user_data; struct tx_pkt *tx; uint32_t delay; tx = l_queue_peek_head(pvt->tx_pkts); if (!tx) return; switch (tx->info.type) { case MESH_IO_TIMING_TYPE_GENERAL: if (tx->info.u.gen.min_delay == tx->info.u.gen.max_delay) delay = tx->info.u.gen.min_delay; else { l_getrandom(&delay, sizeof(delay)); delay %= tx->info.u.gen.max_delay - tx->info.u.gen.min_delay; delay += tx->info.u.gen.min_delay; } break; case MESH_IO_TIMING_TYPE_POLL: if (tx->info.u.poll.min_delay == tx->info.u.poll.max_delay) delay = tx->info.u.poll.min_delay; else { l_getrandom(&delay, sizeof(delay)); delay %= tx->info.u.poll.max_delay - tx->info.u.poll.min_delay; delay += tx->info.u.poll.min_delay; } break; case MESH_IO_TIMING_TYPE_POLL_RSP: /* Delay until Instant + Delay */ delay = instant_remaining_ms(tx->info.u.poll_rsp.instant + tx->info.u.poll_rsp.delay); if (delay > 255) delay = 0; break; default: return; } if (!delay) tx_to(pvt->tx_timeout, pvt); else if (pvt->tx_timeout) l_timeout_modify_ms(pvt->tx_timeout, delay); else pvt->tx_timeout = l_timeout_create_ms(delay, tx_to, pvt, NULL); } static bool send_tx(struct mesh_io *io, struct mesh_io_send_info *info, const uint8_t *data, uint16_t len) { struct tx_pkt *tx; bool sending = false; if (!info || !data || !len || len > sizeof(tx->pkt)) return false; tx = l_new(struct tx_pkt, 1); memcpy(&tx->info, info, sizeof(tx->info)); memcpy(&tx->pkt, data, len); tx->len = len; if (info->type == MESH_IO_TIMING_TYPE_POLL_RSP) l_queue_push_head(pvt->tx_pkts, tx); else { if (pvt->tx) sending = true; else sending = !l_queue_isempty(pvt->tx_pkts); l_queue_push_tail(pvt->tx_pkts, tx); } if (!sending) { l_timeout_remove(pvt->tx_timeout); pvt->tx_timeout = NULL; l_idle_oneshot(tx_worker, pvt, NULL); } return true; } static bool tx_cancel(struct mesh_io *io, const uint8_t *data, uint8_t len) { struct mesh_io_private *pvt = io->pvt; struct tx_pkt *tx; if (!data) return false; if (len == 1) { do { tx = l_queue_remove_if(pvt->tx_pkts, find_by_ad_type, L_UINT_TO_PTR(data[0])); l_free(tx); if (tx == pvt->tx) pvt->tx = NULL; } while (tx); } else { struct tx_pattern pattern = { .data = data, .len = len }; do { tx = l_queue_remove_if(pvt->tx_pkts, find_by_pattern, &pattern); l_free(tx); if (tx == pvt->tx) pvt->tx = NULL; } while (tx); } if (l_queue_isempty(pvt->tx_pkts)) { send_cancel(pvt); l_timeout_remove(pvt->tx_timeout); pvt->tx_timeout = NULL; } return true; } static bool recv_register(struct mesh_io *io, const uint8_t *filter, uint8_t len, mesh_io_recv_func_t cb, void *user_data) { bool active = false; if (io->pvt != pvt) return false; /* Look for any AD types requiring Active Scanning */ if (l_queue_find(io->rx_regs, find_active, NULL)) active = true; if (pvt->active != active) { pvt->active = active; /* TODO: Request active or passive scanning */ } return true; } static bool recv_deregister(struct mesh_io *io, const uint8_t *filter, uint8_t len) { bool active = false; if (io->pvt != pvt) return false; /* Look for any AD types requiring Active Scanning */ if (l_queue_find(io->rx_regs, find_active, NULL)) active = true; if (active != pvt->active) { pvt->active = active; /* TODO: Request active or passive scanning */ } return true; } const struct mesh_io_api mesh_io_mgmt = { .init = dev_init, .destroy = dev_destroy, .caps = dev_caps, .send = send_tx, .reg = recv_register, .dereg = recv_deregister, .cancel = tx_cancel, }; bluez-5.87/mesh/PaxHeaders/error.h0000644000000000000000000000005014015011623014102 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/error.h0000644000000000000000000000110514015011623013560 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ /* * Important: Changes in this table must be reflected in the * the entries of error_table[] in dbus.c */ enum mesh_error { MESH_ERROR_NONE, MESH_ERROR_FAILED, MESH_ERROR_NOT_AUTHORIZED, MESH_ERROR_NOT_FOUND, MESH_ERROR_INVALID_ARGS, MESH_ERROR_IN_PROGRESS, MESH_ERROR_BUSY, MESH_ERROR_ALREADY_EXISTS, MESH_ERROR_DOES_NOT_EXIST, MESH_ERROR_CANCELED, MESH_ERROR_NOT_IMPLEMENTED, }; bluez-5.87/mesh/PaxHeaders/agent.h0000644000000000000000000000005014015011623014047 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/agent.h0000644000000000000000000000370114015011623013531 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ struct mesh_agent; struct mesh_agent_prov_caps { uint32_t uri_hash; uint16_t oob_info; uint16_t output_action; uint16_t input_action; uint8_t pub_type; uint8_t static_type; uint8_t output_size; uint8_t input_size; }; typedef void (*mesh_agent_cb_t) (void *user_data, int err); typedef void (*mesh_agent_key_cb_t) (void *user_data, int err, uint8_t *key, uint32_t len); typedef void (*mesh_agent_number_cb_t) (void *user_data, int err, uint32_t number); void mesh_agent_init(void); void mesh_agent_cleanup(void); struct mesh_agent *mesh_agent_create(const char *path, const char *owner, struct l_dbus_message_iter *properties); void mesh_agent_refresh(struct mesh_agent *agent, mesh_agent_cb_t cb, void *user_data); void mesh_agent_remove(struct mesh_agent *agent); void mesh_agent_cancel(struct mesh_agent *agent); struct mesh_agent_prov_caps *mesh_agent_get_caps(struct mesh_agent *agent); int mesh_agent_display_number(struct mesh_agent *agent, bool initiator, uint8_t action, uint32_t count, mesh_agent_cb_t cb, void *user_data); int mesh_agent_prompt_number(struct mesh_agent *agent, bool initiator, uint8_t action, mesh_agent_number_cb_t cb, void *user_data); int mesh_agent_prompt_alpha(struct mesh_agent *agent, bool initiator, mesh_agent_key_cb_t cb, void *user_data); int mesh_agent_request_static(struct mesh_agent *agent, mesh_agent_key_cb_t cb, void *user_data); int mesh_agent_request_private_key(struct mesh_agent *agent, mesh_agent_key_cb_t cb, void *user_data); int mesh_agent_request_public_key(struct mesh_agent *agent, mesh_agent_key_cb_t cb, void *user_data); int mesh_agent_display_string(struct mesh_agent *agent, const char *str, mesh_agent_cb_t cb, void *user_data); bluez-5.87/mesh/PaxHeaders/mesh.c0000644000000000000000000000005015063045254013713 xustar0020 atime=1770587287 20 ctime=1783094103 bluez-5.87/mesh/mesh.c0000644000000000000000000005501315063045254013400 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #define _GNU_SOURCE #include #include "src/shared/ad.h" #include "mesh/mesh-io.h" #include "mesh/node.h" #include "mesh/net.h" #include "mesh/net-keys.h" #include "mesh/provision.h" #include "mesh/model.h" #include "mesh/dbus.h" #include "mesh/error.h" #include "mesh/agent.h" #include "mesh/mesh.h" #include "mesh/mesh-defs.h" /* * The default values for mesh configuration. Can be * overwritten by values from mesh-main.conf */ #define DEFAULT_PROV_TIMEOUT 60 #define DEFAULT_CRPL 100 #define DEFAULT_FRIEND_QUEUE_SZ 32 #define DEFAULT_ALGORITHMS 0x0001 struct scan_filter { uint8_t id; const char *pattern; }; struct bt_mesh { struct mesh_io *io; struct l_queue *filters; prov_rx_cb_t prov_rx; void *prov_data; uint32_t prov_timeout; bool beacon_enabled; bool friend_support; bool relay_support; bool lpn_support; bool proxy_support; uint16_t crpl; uint16_t algorithms; uint16_t req_index; uint8_t friend_queue_sz; uint8_t max_filters; bool initialized; }; struct join_data{ struct l_dbus_message *msg; char *sender; struct mesh_node *node; uint32_t disc_watch; uint8_t *uuid; }; struct mesh_init_request { mesh_ready_func_t cb; void *user_data; }; static struct bt_mesh mesh = { .algorithms = DEFAULT_ALGORITHMS, .prov_timeout = DEFAULT_PROV_TIMEOUT, .beacon_enabled = true, .friend_support = true, .relay_support = true, .lpn_support = false, .proxy_support = false, .crpl = DEFAULT_CRPL, .friend_queue_sz = DEFAULT_FRIEND_QUEUE_SZ, .initialized = false }; /* We allow only one outstanding Join request */ static struct join_data *join_pending; /* Pending method requests */ static struct l_queue *pending_queue; static const char *storage_dir; /* Forward static declarations */ static void def_attach(struct l_timeout *timeout, void *user_data); static void def_leave(struct l_timeout *timeout, void *user_data); static bool simple_match(const void *a, const void *b) { return a == b; } /* Used for any outbound traffic that doesn't have Friendship Constraints */ /* This includes Beacons, Provisioning and unrestricted Network Traffic */ bool mesh_send_pkt(uint8_t count, uint16_t interval, void *data, uint16_t len) { struct mesh_io_send_info info = { .type = MESH_IO_TIMING_TYPE_GENERAL, .u.gen.cnt = count, .u.gen.interval = interval, .u.gen.max_delay = 0, .u.gen.min_delay = 0, }; return mesh_io_send(mesh.io, &info, data, len); } bool mesh_send_cancel(const uint8_t *filter, uint8_t len) { return mesh_io_send_cancel(mesh.io, filter, len); } static void prov_rx(void *user_data, struct mesh_io_recv_info *info, const uint8_t *data, uint16_t len) { if (user_data != &mesh) return; if (mesh.prov_rx) mesh.prov_rx(mesh.prov_data, data, len); } bool mesh_reg_prov_rx(prov_rx_cb_t cb, void *user_data) { uint8_t prov_filter[] = {BT_AD_MESH_PROV}; if (mesh.prov_rx && mesh.prov_rx != cb) return false; mesh.prov_rx = cb; mesh.prov_data = user_data; return mesh_io_register_recv_cb(mesh.io, prov_filter, sizeof(prov_filter), prov_rx, &mesh); } void mesh_unreg_prov_rx(prov_rx_cb_t cb) { uint8_t prov_filter[] = {BT_AD_MESH_PROV}; if (mesh.prov_rx != cb) return; mesh.prov_rx = NULL; mesh.prov_data = NULL; mesh_io_deregister_recv_cb(mesh.io, prov_filter, sizeof(prov_filter)); } static void io_ready_callback(void *user_data, bool result) { struct mesh_init_request *req = user_data; if (mesh.initialized) return; mesh.initialized = true; if (result) node_attach_io_all(mesh.io); req->cb(req->user_data, result); l_free(req); } bool mesh_beacon_enabled(void) { return mesh.beacon_enabled; } bool mesh_relay_supported(void) { return mesh.relay_support; } bool mesh_friendship_supported(void) { return mesh.friend_support; } uint16_t mesh_get_crpl(void) { return mesh.crpl; } uint8_t mesh_get_friend_queue_size(void) { return mesh.friend_queue_sz; } static void parse_settings(const char *mesh_conf_fname) { struct l_settings *settings; char *str; uint32_t value; settings = l_settings_new(); if (!l_settings_load_from_file(settings, mesh_conf_fname)) goto done; str = l_settings_get_string(settings, "General", "Beacon"); if (str) { if (!strcasecmp(str, "true")) mesh.beacon_enabled = true; l_free(str); } str = l_settings_get_string(settings, "General", "Relay"); if (str) { if (!strcasecmp(str, "false")) mesh.relay_support = false; l_free(str); } str = l_settings_get_string(settings, "General", "Friendship"); if (str) { if (!strcasecmp(str, "false")) mesh.friend_support = false; l_free(str); } if (l_settings_get_uint(settings, "General", "CRPL", &value) && value <= 65535) mesh.crpl = value; if (l_settings_get_uint(settings, "General", "FriendQueueSize", &value) && value < 127) mesh.friend_queue_sz = value; if (l_settings_get_uint(settings, "General", "ProvTimeout", &value)) mesh.prov_timeout = value; done: l_settings_free(settings); } bool mesh_init(const char *config_dir, const char *mesh_conf_fname, enum mesh_io_type type, void *opts, mesh_ready_func_t cb, void *user_data) { struct mesh_io_caps caps; struct mesh_init_request *req; if (mesh.io) return true; mesh_model_init(); mesh_agent_init(); /* TODO: read mesh.conf */ mesh.prov_timeout = DEFAULT_PROV_TIMEOUT; mesh.algorithms = DEFAULT_ALGORITHMS; storage_dir = config_dir ? config_dir : MESH_STORAGEDIR; l_info("Loading node configuration from %s", storage_dir); if (!mesh_conf_fname) mesh_conf_fname = CONFIGDIR "/mesh-main.conf"; parse_settings(mesh_conf_fname); if (!node_load_from_storage(storage_dir)) return false; req = l_new(struct mesh_init_request, 1); req->cb = cb; req->user_data = user_data; mesh.io = mesh_io_new(type, opts, io_ready_callback, req); if (!mesh.io) { l_free(req); return false; } l_debug("io %p", mesh.io); mesh_io_get_caps(mesh.io, &caps); mesh.max_filters = caps.max_num_filters; pending_queue = l_queue_new(); return true; } static void pending_request_exit(void *data) { struct l_dbus_message *reply; struct l_dbus_message *msg = data; reply = dbus_error(msg, MESH_ERROR_FAILED, "Failed. Exiting"); l_dbus_send(dbus_get_bus(), reply); l_dbus_message_unref(msg); } static void free_pending_join_call(bool failed) { if (!join_pending) return; if (join_pending->disc_watch) l_dbus_remove_watch(dbus_get_bus(), join_pending->disc_watch); if (failed) node_remove(join_pending->node); l_free(join_pending->sender); l_free(join_pending); join_pending = NULL; } void mesh_cleanup(bool signaled) { struct l_dbus_message *reply; mesh_io_destroy(mesh.io); mesh.io = NULL; if (signaled) return; if (join_pending) { if (join_pending->msg) { reply = dbus_error(join_pending->msg, MESH_ERROR_FAILED, "Failed. Exiting"); l_dbus_send(dbus_get_bus(), reply); l_dbus_message_unref(join_pending->msg); } acceptor_cancel(&mesh); free_pending_join_call(true); } l_queue_destroy(pending_queue, pending_request_exit); mesh_agent_cleanup(); node_cleanup_all(); mesh_model_cleanup(); mesh_net_cleanup(); net_key_cleanup(); l_dbus_object_remove_interface(dbus_get_bus(), BLUEZ_MESH_PATH, MESH_NETWORK_INTERFACE); l_dbus_unregister_interface(dbus_get_bus(), MESH_NETWORK_INTERFACE); } const char *mesh_status_str(uint8_t err) { switch (err) { case MESH_STATUS_SUCCESS: return "Success"; case MESH_STATUS_INVALID_ADDRESS: return "Invalid Address"; case MESH_STATUS_INVALID_MODEL: return "Invalid Model"; case MESH_STATUS_INVALID_APPKEY: return "Invalid AppKey"; case MESH_STATUS_INVALID_NETKEY: return "Invalid NetKey"; case MESH_STATUS_INSUFF_RESOURCES: return "Insufficient Resources"; case MESH_STATUS_IDX_ALREADY_STORED: return "Key Idx Already Stored"; case MESH_STATUS_INVALID_PUB_PARAM: return "Invalid Publish Parameters"; case MESH_STATUS_NOT_SUB_MOD: return "Not a Subscribe Model"; case MESH_STATUS_STORAGE_FAIL: return "Storage Failure"; case MESH_STATUS_FEATURE_NO_SUPPORT: return "Feature Not Supported"; case MESH_STATUS_CANNOT_UPDATE: return "Cannot Update"; case MESH_STATUS_CANNOT_REMOVE: return "Cannot Remove"; case MESH_STATUS_CANNOT_BIND: return "Cannot bind"; case MESH_STATUS_UNABLE_CHANGE_STATE: return "Unable to change state"; case MESH_STATUS_CANNOT_SET: return "Cannot set"; case MESH_STATUS_UNSPECIFIED_ERROR: return "Unspecified error"; case MESH_STATUS_INVALID_BINDING: return "Invalid Binding"; default: return "Unknown"; } } /* This is being called if the app exits unexpectedly */ static void prov_disc_cb(struct l_dbus *bus, void *user_data) { if (!join_pending) return; acceptor_cancel(&mesh); join_pending->disc_watch = 0; free_pending_join_call(true); } const char *mesh_prov_status_str(uint8_t status) { switch (status) { case PROV_ERR_SUCCESS: return "success"; case PROV_ERR_INVALID_PDU: case PROV_ERR_INVALID_FORMAT: case PROV_ERR_UNEXPECTED_PDU: return "bad-pdu"; case PROV_ERR_CONFIRM_FAILED: return "confirmation-failed"; case PROV_ERR_INSUF_RESOURCE: return "out-of-resources"; case PROV_ERR_DECRYPT_FAILED: return "decryption-error"; case PROV_ERR_CANT_ASSIGN_ADDR: return "cannot-assign-addresses"; case PROV_ERR_TIMEOUT: return "timeout"; case PROV_ERR_UNEXPECTED_ERR: default: return "unexpected-error"; } } static void send_join_failed(const char *owner, const char *path, uint8_t status) { struct l_dbus_message *msg; struct l_dbus *dbus = dbus_get_bus(); msg = l_dbus_message_new_method_call(dbus, owner, path, MESH_APPLICATION_INTERFACE, "JoinFailed"); l_dbus_message_set_arguments(msg, "s", mesh_prov_status_str(status)); l_dbus_send(dbus_get_bus(), msg); free_pending_join_call(true); } static void prov_join_complete_reply_cb(struct l_dbus_message *msg, void *user_data) { bool failed = false; if (!msg || l_dbus_message_is_error(msg)) failed = true; if (!failed) node_finalize_new_node(join_pending->node, mesh.io); free_pending_join_call(failed); } static bool prov_complete_cb(void *user_data, uint8_t status, const struct mesh_prov_node_info *info) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg; const char *owner; const char *path; const uint8_t *token; l_debug("Provisioning complete %s", mesh_prov_status_str(status)); if (!join_pending) return false; owner = join_pending->sender; path = node_get_app_path(join_pending->node); if (status == PROV_ERR_SUCCESS && !node_add_pending_local(join_pending->node, info)) status = PROV_ERR_UNEXPECTED_ERR; if (status != PROV_ERR_SUCCESS) { send_join_failed(owner, path, status); return false; } token = node_get_token(join_pending->node); l_debug("Calling JoinComplete (prov)"); msg = l_dbus_message_new_method_call(dbus, owner, path, MESH_APPLICATION_INTERFACE, "JoinComplete"); l_dbus_message_set_arguments(msg, "t", l_get_be64(token)); dbus_send_with_timeout(dbus, msg, prov_join_complete_reply_cb, NULL, NULL, DEFAULT_DBUS_TIMEOUT); return true; } static void node_init_cb(struct mesh_node *node, struct mesh_agent *agent) { struct l_dbus_message *reply; uint8_t num_ele; bool is_error = false; struct l_dbus *dbus = dbus_get_bus(); if (!node) { reply = dbus_error(join_pending->msg, MESH_ERROR_FAILED, "Failed to create node from application"); is_error = true; goto done; } join_pending->node = node; num_ele = node_get_num_elements(node); if (!acceptor_start(num_ele, join_pending->uuid, mesh.algorithms, mesh.prov_timeout, agent, prov_complete_cb, &mesh)) { reply = dbus_error(join_pending->msg, MESH_ERROR_FAILED, "Failed to start provisioning acceptor"); is_error = true; } else reply = l_dbus_message_new_method_return(join_pending->msg); done: l_dbus_send(dbus, reply); l_dbus_message_unref(join_pending->msg); join_pending->msg = NULL; if (is_error) free_pending_join_call(true); else /* Setup disconnect watch */ join_pending->disc_watch = l_dbus_add_disconnect_watch(dbus, join_pending->sender, prov_disc_cb, NULL, NULL); } static struct l_dbus_message *join_network_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { const char *app_path, *sender; struct l_dbus_message_iter iter; uint32_t n; l_debug("Join network request"); if (join_pending) return dbus_error(msg, MESH_ERROR_BUSY, "Provisioning in progress"); if (!l_dbus_message_get_arguments(msg, "oay", &app_path, &iter)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); join_pending = l_new(struct join_data, 1); if (!l_dbus_message_iter_get_fixed_array(&iter, &join_pending->uuid, &n) || n != 16 || !l_uuid_is_valid(join_pending->uuid)) { l_free(join_pending); join_pending = NULL; return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad device UUID"); } if (node_find_by_uuid(join_pending->uuid)) { l_free(join_pending); join_pending = NULL; return dbus_error(msg, MESH_ERROR_ALREADY_EXISTS, "Node already exists"); } sender = l_dbus_message_get_sender(msg); join_pending->sender = l_strdup(sender); join_pending->msg = l_dbus_message_ref(msg); /* Try to create a temporary node */ node_join(app_path, sender, join_pending->uuid, node_init_cb); return NULL; } static struct l_dbus_message *cancel_join_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { struct l_dbus_message *reply; l_debug("Cancel Join"); if (!join_pending) return dbus_error(msg, MESH_ERROR_DOES_NOT_EXIST, "No join in progress"); acceptor_cancel(&mesh); /* Return error to the original Join call */ if (join_pending->msg) { reply = dbus_error(join_pending->msg, MESH_ERROR_FAILED, NULL); l_dbus_send(dbus_get_bus(), reply); l_dbus_message_unref(join_pending->msg); } reply = l_dbus_message_new_method_return(msg); l_dbus_message_set_arguments(reply, ""); free_pending_join_call(true); return reply; } static void attach_ready_cb(void *user_data, int status, struct mesh_node *node) { struct l_dbus_message *reply; struct l_dbus_message *pending_msg; pending_msg = l_queue_remove_if(pending_queue, simple_match, user_data); if (!pending_msg) return; if (status == MESH_ERROR_NONE) { reply = l_dbus_message_new_method_return(pending_msg); node_build_attach_reply(node, reply); } else reply = dbus_error(pending_msg, status, "Attach failed"); l_dbus_send(dbus_get_bus(), reply); l_dbus_message_unref(pending_msg); } static struct l_dbus_message *attach_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { uint64_t token; const char *app_path, *sender; struct l_dbus_message *pending_msg; struct mesh_node *node; l_debug("Attach"); if (!l_dbus_message_get_arguments(msg, "ot", &app_path, &token)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); node = node_find_by_token(token); if (!node) return dbus_error(msg, MESH_ERROR_NOT_FOUND, "Attach failed"); if (node_is_busy(node)) { if (user_data) return dbus_error(msg, MESH_ERROR_BUSY, NULL); /* Try once more in 1 second */ l_timeout_create(1, def_attach, l_dbus_message_ref(msg), NULL); return NULL; } sender = l_dbus_message_get_sender(msg); pending_msg = l_dbus_message_ref(msg); l_queue_push_tail(pending_queue, pending_msg); node_attach(app_path, sender, token, attach_ready_cb, pending_msg); return NULL; } static void def_attach(struct l_timeout *timeout, void *user_data) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg = user_data; struct l_dbus_message *reply; l_timeout_remove(timeout); reply = attach_call(dbus, msg, (void *) true); l_dbus_send(dbus, reply); l_dbus_message_unref(msg); } static struct l_dbus_message *leave_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { uint64_t token; struct mesh_node *node; l_debug("Leave"); if (!l_dbus_message_get_arguments(msg, "t", &token)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); node = node_find_by_token(token); if (!node) return dbus_error(msg, MESH_ERROR_NOT_FOUND, NULL); if (node_is_busy(node)) { if (user_data) return dbus_error(msg, MESH_ERROR_BUSY, NULL); /* Try once more in 1 second */ l_timeout_create(1, def_leave, l_dbus_message_ref(msg), NULL); return NULL; } node_remove(node); return l_dbus_message_new_method_return(msg); } static void def_leave(struct l_timeout *timeout, void *user_data) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *msg = user_data; struct l_dbus_message *reply; l_timeout_remove(timeout); reply = leave_call(dbus, msg, (void *) true); l_dbus_send(dbus, reply); l_dbus_message_unref(msg); } static void create_join_complete_reply_cb(struct l_dbus_message *msg, void *user_data) { struct mesh_node *node = user_data; if (!msg || l_dbus_message_is_error(msg)) { node_remove(node); return; } node_finalize_new_node(node, mesh.io); } static void create_node_ready_cb(void *user_data, int status, struct mesh_node *node) { struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message *reply; struct l_dbus_message *pending_msg; struct l_dbus_message *msg; const char *owner; const char *path; const uint8_t *token; pending_msg = l_queue_remove_if(pending_queue, simple_match, user_data); if (!pending_msg) return; if (status != MESH_ERROR_NONE) { reply = dbus_error(pending_msg, status, NULL); l_dbus_send(dbus_get_bus(), reply); l_dbus_message_unref(pending_msg); return; } reply = l_dbus_message_new_method_return(pending_msg); l_dbus_send(dbus, reply); owner = l_dbus_message_get_sender(pending_msg); path = node_get_app_path(node); token = node_get_token(node); l_debug("Calling JoinComplete (create)"); msg = l_dbus_message_new_method_call(dbus, owner, path, MESH_APPLICATION_INTERFACE, "JoinComplete"); l_dbus_message_set_arguments(msg, "t", l_get_be64(token)); dbus_send_with_timeout(dbus, msg, create_join_complete_reply_cb, node, NULL, DEFAULT_DBUS_TIMEOUT); l_dbus_message_unref(pending_msg); } static struct l_dbus_message *create_network_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { const char *app_path, *sender; struct l_dbus_message_iter iter_uuid; struct l_dbus_message *pending_msg; uint8_t *uuid; uint32_t n; l_debug("Create network request"); if (!l_dbus_message_get_arguments(msg, "oay", &app_path, &iter_uuid)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); if (!l_dbus_message_iter_get_fixed_array(&iter_uuid, &uuid, &n) || n != 16 || !l_uuid_is_valid(uuid)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad device UUID"); sender = l_dbus_message_get_sender(msg); pending_msg = l_dbus_message_ref(msg); l_queue_push_tail(pending_queue, pending_msg); node_create(app_path, sender, uuid, create_node_ready_cb, pending_msg); return NULL; } static struct l_dbus_message *import_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { const char *app_path, *sender; struct l_dbus_message *pending_msg = NULL; struct l_dbus_message_iter iter_uuid; struct l_dbus_message_iter iter_dev_key; struct l_dbus_message_iter iter_net_key; struct l_dbus_message_iter iter_flags; const char *key; struct l_dbus_message_iter var; uint8_t *uuid; uint8_t *dev_key; uint8_t *net_key; uint16_t net_idx; bool kr = false; bool ivu = false; uint32_t iv_index; uint16_t unicast; uint32_t n; l_debug("Import local node request"); if (!l_dbus_message_get_arguments(msg, "oayayayqa{sv}uq", &app_path, &iter_uuid, &iter_dev_key, &iter_net_key, &net_idx, &iter_flags, &iv_index, &unicast)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); if (!l_dbus_message_iter_get_fixed_array(&iter_uuid, &uuid, &n) || n != 16 || !l_uuid_is_valid(uuid)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad device UUID"); if (node_find_by_uuid(uuid)) return dbus_error(msg, MESH_ERROR_ALREADY_EXISTS, "Node already exists"); if (!l_dbus_message_iter_get_fixed_array(&iter_dev_key, &dev_key, &n) || n != 16) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad dev key"); if (!l_dbus_message_iter_get_fixed_array(&iter_net_key, &net_key, &n) || n != 16) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad net key"); if (net_idx > MAX_KEY_IDX) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad net index"); while (l_dbus_message_iter_next_entry(&iter_flags, &key, &var)) { if (!strcmp(key, "IvUpdate") && l_dbus_message_iter_get_variant(&var, "b", &ivu)) continue; if (!strcmp(key, "KeyRefresh") && l_dbus_message_iter_get_variant(&var, "b", &kr)) continue; return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad flags"); } if (!IS_UNICAST(unicast)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad address"); sender = l_dbus_message_get_sender(msg); pending_msg = l_dbus_message_ref(msg); l_queue_push_tail(pending_queue, pending_msg); node_import(app_path, sender, uuid, dev_key, net_key, net_idx, kr, ivu, iv_index, unicast, create_node_ready_cb, pending_msg); return NULL; } static void setup_network_interface(struct l_dbus_interface *iface) { l_dbus_interface_method(iface, "Join", 0, join_network_call, "", "oay", "app", "uuid"); l_dbus_interface_method(iface, "Cancel", 0, cancel_join_call, "", ""); l_dbus_interface_method(iface, "Attach", 0, attach_call, "oa(ya(qa{sv}))", "ot", "node", "configuration", "app", "token"); l_dbus_interface_method(iface, "Leave", 0, leave_call, "", "t", "token"); l_dbus_interface_method(iface, "CreateNetwork", 0, create_network_call, "", "oay", "app", "uuid"); l_dbus_interface_method(iface, "Import", 0, import_call, "", "oayayayqa{sv}uq", "app", "uuid", "dev_key", "net_key", "net_index", "flags", "iv_index", "unicast"); } bool mesh_dbus_init(struct l_dbus *dbus) { if (!l_dbus_register_interface(dbus, MESH_NETWORK_INTERFACE, setup_network_interface, NULL, false)) { l_info("Unable to register %s interface", MESH_NETWORK_INTERFACE); return false; } if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_PATH, MESH_NETWORK_INTERFACE, NULL)) { l_info("Unable to register the mesh object on '%s'", MESH_NETWORK_INTERFACE); l_dbus_unregister_interface(dbus, MESH_NETWORK_INTERFACE); return false; } l_info("Added Network Interface on %s", BLUEZ_MESH_PATH); return true; } const char *mesh_get_storage_dir(void) { return storage_dir; } bluez-5.87/mesh/PaxHeaders/prov-acceptor.c0000644000000000000000000000005015063045254015543 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/prov-acceptor.c0000644000000000000000000004705515063045254015237 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "src/shared/ad.h" #include "src/shared/ecc.h" #include "mesh/mesh-defs.h" #include "mesh/util.h" #include "mesh/crypto.h" #include "mesh/net.h" #include "mesh/prov.h" #include "mesh/provision.h" #include "mesh/remprv.h" #include "mesh/pb-adv.h" #include "mesh/mesh.h" #include "mesh/agent.h" /* Quick size sanity check */ static const uint16_t expected_pdu_size[] = { 2, /* PROV_INVITE */ 12, /* PROV_CAPS */ 6, /* PROV_START */ 65, /* PROV_PUB_KEY */ 1, /* PROV_INP_CMPLT */ 17, /* PROV_CONFIRM */ 17, /* PROV_RANDOM */ 34, /* PROV_DATA */ 1, /* PROV_COMPLETE */ 2, /* PROV_FAILED */ }; #define BEACON_TYPE_UNPROVISIONED 0x00 struct deferred_cmd { uint16_t len; uint8_t cmd[]; }; static const uint8_t pkt_filter = BT_AD_MESH_PROV; static const uint8_t bec_filter[] = {BT_AD_MESH_BEACON, BEACON_TYPE_UNPROVISIONED}; #define MAT_REMOTE_PUBLIC 0x01 #define MAT_LOCAL_PRIVATE 0x02 #define MAT_RAND_AUTH 0x04 #define MAT_SECRET (MAT_REMOTE_PUBLIC | MAT_LOCAL_PRIVATE) struct mesh_prov_acceptor { mesh_prov_acceptor_complete_func_t cmplt; prov_trans_tx_t trans_tx; struct l_queue *ob; void *agent; void *caller_data; void *trans_data; struct l_timeout *timeout; uint32_t to_secs; uint8_t out_opcode; uint8_t transport; uint8_t material; uint8_t expected; int8_t previous; bool failed; struct conf_input conf_inputs; uint8_t calc_key[16]; uint8_t salt[16]; uint8_t confirm[16]; uint8_t s_key[16]; uint8_t s_nonce[13]; uint8_t private_key[32]; uint8_t secret[32]; uint8_t rand_auth_workspace[48]; }; static struct mesh_prov_acceptor *prov = NULL; static void acceptor_free(void) { if (!prov) return; l_timeout_remove(prov->timeout); l_queue_destroy(prov->ob, l_free); mesh_send_cancel(bec_filter, sizeof(bec_filter)); mesh_send_cancel(&pkt_filter, sizeof(pkt_filter)); pb_adv_unreg(prov); l_free(prov); prov = NULL; } static void acp_prov_close(void *user_data, uint8_t reason) { struct mesh_prov_acceptor *rx_prov = user_data; if (rx_prov != prov) return; if (reason == PROV_ERR_SUCCESS) reason = PROV_ERR_UNEXPECTED_ERR; if (prov->cmplt) prov->cmplt(prov->caller_data, reason, NULL); prov->cmplt = NULL; acceptor_free(); } static void prov_send(struct mesh_prov_acceptor *prov, void *cmd, uint16_t len) { struct deferred_cmd *defer; if (prov->out_opcode == PROV_NONE) { prov->out_opcode = *(uint8_t *) cmd; prov->trans_tx(prov->trans_data, cmd, len); } else { defer = l_malloc(len + sizeof(struct deferred_cmd)); defer->len = len; memcpy(defer->cmd, cmd, len); l_queue_push_tail(prov->ob, defer); } } static void prov_to(struct l_timeout *timeout, void *user_data) { struct mesh_prov_acceptor *rx_prov = user_data; uint8_t fail_code[2] = {PROV_FAILED, PROV_ERR_UNEXPECTED_ERR}; if (rx_prov != prov) return; l_timeout_remove(prov->timeout); prov->timeout = NULL; if (prov->cmplt && prov->trans_tx) { prov->cmplt(prov->caller_data, PROV_ERR_TIMEOUT, NULL); prov->cmplt = NULL; prov_send(prov, fail_code, 2); prov->timeout = l_timeout_create(1, prov_to, prov, NULL); return; } acceptor_free(); } static void acp_prov_open(void *user_data, prov_trans_tx_t trans_tx, void *trans_data, uint8_t transport) { struct mesh_prov_acceptor *rx_prov = user_data; /* Only one provisioning session may be open at a time */ if (rx_prov != prov) return; /* Only one provisioning session may be open at a time */ if (prov->trans_tx && prov->trans_tx != trans_tx && prov->transport != transport) return; prov->trans_tx = trans_tx; prov->transport = transport; prov->trans_data = trans_data; prov->timeout = l_timeout_create(prov->to_secs, prov_to, prov, NULL); } static void swap_u256_bytes(uint8_t *u256) { int i; /* End-to-End byte reflection of 32 octet buffer */ for (i = 0; i < 16; i++) { u256[i] ^= u256[31 - i]; u256[31 - i] ^= u256[i]; u256[i] ^= u256[31 - i]; } } static bool prov_calc_secret(const uint8_t *pub, const uint8_t *priv, uint8_t *secret) { uint8_t tmp[64]; /* Convert to ECC byte order */ memcpy(tmp, pub, 64); swap_u256_bytes(tmp); swap_u256_bytes(tmp + 32); if (!ecdh_shared_secret(tmp, priv, secret)) return false; /* Convert to Mesh byte order */ swap_u256_bytes(secret); return true; } static bool acp_credentials(struct mesh_prov_acceptor *prov) { if (!memcmp(prov->conf_inputs.prv_pub_key, prov->conf_inputs.dev_pub_key, 64)) return false; if (!prov_calc_secret(prov->conf_inputs.prv_pub_key, prov->private_key, prov->secret)) return false; if (!mesh_crypto_s1(&prov->conf_inputs, sizeof(prov->conf_inputs), prov->salt)) return false; if (!mesh_crypto_prov_conf_key(prov->secret, prov->salt, prov->calc_key)) return false; l_getrandom(prov->rand_auth_workspace, 16); print_packet("PublicKeyProv", prov->conf_inputs.prv_pub_key, 64); print_packet("PublicKeyDev", prov->conf_inputs.dev_pub_key, 64); /* Normalize for debug out -- No longer needed for calculations */ swap_u256_bytes(prov->private_key); print_packet("PrivateKeyLocal", prov->private_key, 32); print_packet("ConfirmationInputs", &prov->conf_inputs, sizeof(prov->conf_inputs)); print_packet("ECDHSecret", prov->secret, 32); print_packet("LocalRandom", prov->rand_auth_workspace, 16); print_packet("ConfirmationSalt", prov->salt, 16); print_packet("ConfirmationKey", prov->calc_key, 16); return true; } static uint32_t digit_mod(uint8_t power) { uint32_t ret = 1; while (power--) ret *= 10; return ret; } static void number_cb(void *user_data, int err, uint32_t number) { struct mesh_prov_acceptor *rx_prov = user_data; struct prov_fail_msg msg; if (prov != rx_prov) return; if (err) { msg.opcode = PROV_FAILED; msg.reason = PROV_ERR_UNEXPECTED_ERR; prov_send(prov, &msg, sizeof(msg)); return; } /* Save two copies, to generate two confirmation values */ l_put_be32(number, prov->rand_auth_workspace + 28); l_put_be32(number, prov->rand_auth_workspace + 44); prov->material |= MAT_RAND_AUTH; msg.opcode = PROV_INP_CMPLT; prov_send(prov, &msg.opcode, 1); } static void static_cb(void *user_data, int err, uint8_t *key, uint32_t len) { struct mesh_prov_acceptor *rx_prov = user_data; struct prov_fail_msg msg; if (prov != rx_prov) return; if (err || !key || len != 16) { msg.opcode = PROV_FAILED; msg.reason = PROV_ERR_UNEXPECTED_ERR; prov_send(prov, &msg, sizeof(msg)); return; } /* Save two copies, to generate two confirmation values */ memcpy(prov->rand_auth_workspace + 16, key, 16); memcpy(prov->rand_auth_workspace + 32, key, 16); prov->material |= MAT_RAND_AUTH; if (prov->conf_inputs.start.auth_action == PROV_ACTION_IN_ALPHA) { msg.opcode = PROV_INP_CMPLT; prov_send(prov, &msg.opcode, 1); } } static void priv_key_cb(void *user_data, int err, uint8_t *key, uint32_t len) { struct mesh_prov_acceptor *rx_prov = user_data; struct prov_fail_msg msg; if (prov != rx_prov) return; if (err || !key || len != 32) { msg.opcode = PROV_FAILED; msg.reason = PROV_ERR_UNEXPECTED_ERR; prov_send(prov, &msg, sizeof(msg)); return; } /* API delivers Mesh byte order, switch to little endian */ swap_u256_bytes(key); memcpy(prov->private_key, key, 32); ecc_make_public_key(prov->private_key, prov->conf_inputs.dev_pub_key); /* Convert Public key to Mesh byte order */ swap_u256_bytes(prov->conf_inputs.dev_pub_key); swap_u256_bytes(prov->conf_inputs.dev_pub_key + 32); prov->material |= MAT_LOCAL_PRIVATE; if ((prov->material & MAT_SECRET) == MAT_SECRET) { if (!acp_credentials(prov)) { msg.opcode = PROV_FAILED; msg.reason = PROV_ERR_UNEXPECTED_ERR; prov_send(prov, &msg, sizeof(msg)); } } } static void send_caps(struct mesh_prov_acceptor *prov) { struct prov_caps_msg msg; msg.opcode = PROV_CAPS; memcpy(&msg.caps, &prov->conf_inputs.caps, sizeof(prov->conf_inputs.caps)); prov->expected = PROV_START; prov_send(prov, &msg, sizeof(msg)); } static void send_pub_key(struct mesh_prov_acceptor *prov) { struct prov_pub_key_msg msg; msg.opcode = PROV_PUB_KEY; memcpy(msg.pub_key, prov->conf_inputs.dev_pub_key, sizeof(msg.pub_key)); prov_send(prov, &msg, sizeof(msg)); } static bool send_conf(struct mesh_prov_acceptor *prov) { struct prov_conf_msg msg; msg.opcode = PROV_CONFIRM; mesh_crypto_aes_cmac(prov->calc_key, prov->rand_auth_workspace, 32, msg.conf); /* Fail if confirmations match */ if (!memcmp(msg.conf, prov->confirm, sizeof(msg.conf))) return false; prov_send(prov, &msg, sizeof(msg)); return true; } static void send_rand(struct mesh_prov_acceptor *prov) { struct prov_rand_msg msg; msg.opcode = PROV_RANDOM; memcpy(msg.rand, prov->rand_auth_workspace, sizeof(msg.rand)); prov_send(prov, &msg, sizeof(msg)); } static bool prov_start_check(struct prov_start *start, struct mesh_net_prov_caps *caps) { if (start->algorithm || start->pub_key > 1 || start->auth_method > 3) return false; if (start->pub_key && !caps->pub_type) return false; switch (start->auth_method) { case 0: /* No OOB */ if (start->auth_action != 0 || start->auth_size != 0) return false; break; case 1: /* Static OOB */ if (!caps->static_type || start->auth_action != 0 || start->auth_size != 0) return false; break; case 2: /* Output OOB */ if (!(L_BE16_TO_CPU(caps->output_action) & (1 << start->auth_action)) || start->auth_size == 0) return false; break; case 3: /* Input OOB */ if (!(L_BE16_TO_CPU(caps->input_action) & (1 << start->auth_action)) || start->auth_size == 0) return false; break; } return true; } static void acp_prov_rx(void *user_data, const void *dptr, uint16_t len) { struct mesh_prov_acceptor *rx_prov = user_data; const uint8_t *data = dptr; struct mesh_prov_node_info *info; struct prov_fail_msg fail; uint8_t type = *data++; uint32_t oob_key; uint64_t decode_mic; bool result; if (rx_prov != prov || !prov->trans_tx) return; l_debug("Provisioning packet received type: %2.2x (%u octets)", type, len); if (type >= L_ARRAY_SIZE(expected_pdu_size)) { l_error("Unknown PDU type: %2.2x", type); fail.reason = PROV_ERR_INVALID_PDU; goto failure; } if (type == prov->previous) { l_error("Ignore repeated %2.2x packet", type); return; } else if (prov->failed || type > prov->expected || type < prov->previous) { l_error("Expected %2.2x, Got:%2.2x", prov->expected, type); fail.reason = PROV_ERR_UNEXPECTED_PDU; goto failure; } if (len != expected_pdu_size[type]) { l_error("Expected PDU size %d, Got %d (type: %2.2x)", len, expected_pdu_size[type], type); fail.reason = PROV_ERR_INVALID_FORMAT; goto failure; } switch (type){ case PROV_INVITE: /* Prov Invite */ prov->conf_inputs.invite.attention = data[0]; send_caps(prov); break; case PROV_START: /* Prov Start */ memcpy(&prov->conf_inputs.start, data, sizeof(prov->conf_inputs.start)); if (!prov_start_check(&prov->conf_inputs.start, &prov->conf_inputs.caps)) { fail.reason = PROV_ERR_INVALID_FORMAT; goto failure; } if (prov->conf_inputs.start.pub_key) { /* Prompt Agent for Private Key of OOB */ mesh_agent_request_private_key(prov->agent, priv_key_cb, prov); } else { /* Ephemeral Public Key requested */ ecc_make_key(prov->conf_inputs.dev_pub_key, prov->private_key); swap_u256_bytes(prov->conf_inputs.dev_pub_key); swap_u256_bytes(prov->conf_inputs.dev_pub_key + 32); prov->material |= MAT_LOCAL_PRIVATE; } prov->expected = PROV_PUB_KEY; break; case PROV_PUB_KEY: /* Public Key */ /* Save Key */ memcpy(prov->conf_inputs.prv_pub_key, data, 64); prov->material |= MAT_REMOTE_PUBLIC; prov->expected = PROV_CONFIRM; if ((prov->material & MAT_SECRET) != MAT_SECRET) return; if (!acp_credentials(prov)) { fail.reason = PROV_ERR_UNEXPECTED_ERR; goto failure; } if (!prov->conf_inputs.start.pub_key) send_pub_key(prov); /* Start Step 3 */ switch (prov->conf_inputs.start.auth_method) { default: case 0: /* Auth Type 3c - No OOB */ break; case 1: /* Auth Type 3c - Static OOB */ /* Prompt Agent for Static OOB */ fail.reason = mesh_agent_request_static(prov->agent, static_cb, prov); if (fail.reason) goto failure; break; case 2: /* Auth Type 3a - Output OOB */ l_getrandom(&oob_key, sizeof(oob_key)); oob_key %= digit_mod(prov->conf_inputs.start.auth_size); /* Save two copies, for two confirmation values */ l_put_be32(oob_key, prov->rand_auth_workspace + 28); l_put_be32(oob_key, prov->rand_auth_workspace + 44); prov->material |= MAT_RAND_AUTH; if (prov->conf_inputs.start.auth_action == PROV_ACTION_OUT_ALPHA) { /* TODO: Construct NUL-term string to pass */ fail.reason = mesh_agent_display_string( prov->agent, NULL, NULL, prov); } else { /* Ask Agent to Display U32 */ fail.reason = mesh_agent_display_number( prov->agent, false, prov->conf_inputs.start.auth_action, oob_key, NULL, prov); } if (fail.reason) goto failure; break; case 3: /* Auth Type 3b - input OOB */ /* Prompt Agent for Input OOB */ if (prov->conf_inputs.start.auth_action == PROV_ACTION_IN_ALPHA) { fail.reason = mesh_agent_prompt_alpha( prov->agent, false, static_cb, prov); } else { fail.reason = mesh_agent_prompt_number( prov->agent, false, prov->conf_inputs.start.auth_action, number_cb, prov); } if (fail.reason) goto failure; break; } prov->expected = PROV_CONFIRM; break; case PROV_CONFIRM: /* Confirmation */ /* Save Provisioners confirmation for later compare */ memcpy(prov->confirm, data, 16); prov->expected = PROV_RANDOM; if (!send_conf(prov)) { fail.reason = PROV_ERR_INVALID_PDU; goto failure; } break; case PROV_RANDOM: /* Random Value */ /* Disallow matching random values */ if (!memcmp(prov->rand_auth_workspace, data, 16)) { fail.reason = PROV_ERR_INVALID_PDU; goto failure; } /* Calculate Session key (needed later) while data is fresh */ mesh_crypto_prov_prov_salt(prov->salt, data, prov->rand_auth_workspace, prov->salt); mesh_crypto_session_key(prov->secret, prov->salt, prov->s_key); mesh_crypto_nonce(prov->secret, prov->salt, prov->s_nonce); /* Calculate expected Provisioner Confirm */ memcpy(prov->rand_auth_workspace + 16, data, 16); mesh_crypto_aes_cmac(prov->calc_key, prov->rand_auth_workspace + 16, 32, prov->calc_key); /* Compare our calculation with Provisioners */ if (memcmp(prov->calc_key, prov->confirm, 16)) { fail.reason = PROV_ERR_CONFIRM_FAILED; goto failure; } /* Send Random value we used */ send_rand(prov); prov->expected = PROV_DATA; break; case PROV_DATA: /* Provisioning Data */ /* Calculate our device key */ mesh_crypto_device_key(prov->secret, prov->salt, prov->calc_key); /* Decrypt new node data into workspace */ mesh_crypto_aes_ccm_decrypt(prov->s_nonce, prov->s_key, NULL, 0, data, len - 1, prov->rand_auth_workspace, &decode_mic, sizeof(decode_mic)); /* Validate that the data hasn't been messed with in transit */ if (l_get_be64(data + 25) != decode_mic) { l_error("Provisioning Failed-MIC compare"); fail.reason = PROV_ERR_DECRYPT_FAILED; goto failure; } info = l_malloc(sizeof(struct mesh_prov_node_info)); memcpy(info->device_key, prov->calc_key, 16); memcpy(info->net_key, prov->rand_auth_workspace, 16); info->net_index = l_get_be16(prov->rand_auth_workspace + 16); info->flags = prov->rand_auth_workspace[18]; info->iv_index = l_get_be32(prov->rand_auth_workspace + 19); info->unicast = l_get_be16(prov->rand_auth_workspace + 23); info->num_ele = prov->conf_inputs.caps.num_ele; /* Send prov complete */ prov->rand_auth_workspace[0] = PROV_COMPLETE; prov->trans_tx(prov->trans_data, prov->rand_auth_workspace, 1); result = prov->cmplt(prov->caller_data, PROV_ERR_SUCCESS, info); prov->cmplt = NULL; l_free(info); if (result) { l_debug("PROV_COMPLETE"); goto cleanup; } else { fail.reason = PROV_ERR_UNEXPECTED_ERR; goto failure; } break; case PROV_FAILED: /* Provisioning Error -- abort */ /* TODO: Call Complete Callback (Fail)*/ prov->cmplt(prov->caller_data, data[0] ? data[0] : PROV_ERR_UNEXPECTED_ERR, NULL); prov->cmplt = NULL; goto cleanup; } if (prov) prov->previous = type; return; failure: fail.opcode = PROV_FAILED; prov_send(prov, &fail, sizeof(fail)); prov->failed = true; prov->previous = -1; if (prov->cmplt) prov->cmplt(prov->caller_data, fail.reason, NULL); prov->cmplt = NULL; cleanup: l_timeout_remove(prov->timeout); /* Give PB Link 5 seconds to end session */ prov->timeout = l_timeout_create(5, prov_to, prov, NULL); } static void acp_prov_ack(void *user_data, uint8_t msg_num) { struct mesh_prov_acceptor *rx_prov = user_data; struct deferred_cmd *deferred; if (rx_prov != prov) return; if (prov->out_opcode == PROV_NONE) return; prov->out_opcode = PROV_NONE; deferred = l_queue_pop_head(prov->ob); if (!deferred) return; prov_send(prov, deferred->cmd, deferred->len); l_free(deferred); } /* This starts unprovisioned device beacon */ bool acceptor_start(uint8_t num_ele, uint8_t *uuid, uint16_t algorithms, uint32_t timeout, struct mesh_agent *agent, mesh_prov_acceptor_complete_func_t complete_cb, void *caller_data) { struct mesh_agent_prov_caps *caps; uint8_t beacon[24] = {BT_AD_MESH_BEACON, BEACON_TYPE_UNPROVISIONED}; uint8_t len = sizeof(beacon) - sizeof(uint32_t); bool result; /* * Invoked from Join() method in mesh-api.txt, to join a * remote mesh network. May also be invoked with a NULL * uuid to perform a Device Key Refresh procedure. */ if (prov) return false; prov = l_new(struct mesh_prov_acceptor, 1); prov->to_secs = timeout; prov->agent = agent; prov->cmplt = complete_cb; prov->ob = l_queue_new(); prov->previous = -1; prov->failed = false; prov->out_opcode = PROV_NONE; prov->caller_data = caller_data; caps = mesh_agent_get_caps(agent); prov->conf_inputs.caps.num_ele = num_ele; l_put_be16(algorithms, &prov->conf_inputs.caps.algorithms); if (caps) { /* TODO: Should we sanity check values here or elsewhere? */ prov->conf_inputs.caps.pub_type = caps->pub_type; prov->conf_inputs.caps.static_type = caps->static_type; prov->conf_inputs.caps.output_size = caps->output_size; prov->conf_inputs.caps.input_size = caps->input_size; /* Store UINT16 values in Over-the-Air order, in packed * structure for crypto inputs */ l_put_be16(caps->output_action, &prov->conf_inputs.caps.output_action); l_put_be16(caps->input_action, &prov->conf_inputs.caps.input_action); /* Populate Caps fields of beacon */ l_put_be16(caps->oob_info, beacon + 18); if (caps->oob_info & OOB_INFO_URI_HASH) { l_put_be32(caps->uri_hash, beacon + 20); len += sizeof(uint32_t); } } if (uuid) { /* Compose Unprovisioned Beacon */ memcpy(beacon + 2, uuid, 16); /* Infinitely Beacon until Canceled, or Provisioning Starts */ result = mesh_send_pkt(0, 500, beacon, len); if (!result) goto error_fail; /* Always register for PB-ADV */ result = pb_adv_reg(false, acp_prov_open, acp_prov_close, acp_prov_rx, acp_prov_ack, uuid, prov); } else { /* Run Device Key Refresh Procedure */ result = register_nppi_acceptor(acp_prov_open, acp_prov_close, acp_prov_rx, acp_prov_ack, prov); } if (result) return true; error_fail: acceptor_free(); return false; } void acceptor_cancel(void *user_data) { acceptor_free(); } bluez-5.87/mesh/PaxHeaders/keyring.c0000644000000000000000000000005015063045254014427 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/keyring.c0000644000000000000000000003404615063045254014117 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "mesh/mesh-defs.h" #include "mesh/dbus.h" #include "mesh/node.h" #include "mesh/keyring.h" static const char *dev_key_dir = "/dev_keys"; static const char *app_key_dir = "/app_keys"; static const char *net_key_dir = "/net_keys"; static int open_key_file(struct mesh_node *node, const char *key_dir, uint16_t idx, int flags) { const char *node_path; char fname[PATH_MAX]; int ret; if (!node) return -1; node_path = node_get_storage_dir(node); if (flags & O_CREAT) { ret = snprintf(fname, PATH_MAX, "%s%s", node_path, key_dir); if (ret < 0) return -1; if (mkdir(fname, 0755) != 0 && errno != EEXIST) l_error("Failed to create dir(%d): %s", errno, fname); } ret = snprintf(fname, PATH_MAX, "%s%s/%3.3x", node_path, key_dir, idx); if (ret < 0) return -1; if (flags & O_CREAT) return open(fname, flags, 0600); else return open(fname, flags); } bool keyring_put_net_key(struct mesh_node *node, uint16_t net_idx, const struct keyring_net_key *key) { bool result = false; int fd; if (!key) return false; fd = open_key_file(node, net_key_dir, net_idx, O_WRONLY | O_CREAT | O_TRUNC); if (fd < 0) return false; if (write(fd, key, sizeof(*key)) == sizeof(*key)) result = true; close(fd); return result; } bool keyring_put_app_key(struct mesh_node *node, uint16_t app_idx, uint16_t net_idx, struct keyring_app_key *key) { bool result = false; int fd; if (!key) return false; fd = open_key_file(node, app_key_dir, app_idx, O_RDWR); if (fd >= 0) { struct keyring_app_key old_key; if (read(fd, &old_key, sizeof(old_key)) == sizeof(old_key)) { if (old_key.net_idx != net_idx) { close(fd); return false; } } lseek(fd, 0, SEEK_SET); } else fd = open_key_file(node, app_key_dir, app_idx, O_WRONLY | O_CREAT | O_TRUNC); if (fd < 0) return false; if (write(fd, key, sizeof(*key)) == sizeof(*key)) result = true; close(fd); return result; } static void finalize(int dir_fd, const char *fname, uint16_t net_idx) { struct keyring_app_key key; int fd; fd = openat(dir_fd, fname, O_RDWR); if (fd < 0) return; if (read(fd, &key, sizeof(key)) != sizeof(key) || key.net_idx != net_idx) goto done; l_debug("Finalize %s", fname); memcpy(key.old_key, key.new_key, 16); lseek(fd, 0, SEEK_SET); if (write(fd, &key, sizeof(key)) != sizeof(key)) goto done; done: close(fd); } bool keyring_finalize_app_keys(struct mesh_node *node, uint16_t net_idx) { const char *node_path; char key_dir[PATH_MAX]; DIR *dir; int ret, dir_fd; struct dirent *entry; if (!node) return false; node_path = node_get_storage_dir(node); ret = snprintf(key_dir, PATH_MAX, "%s%s", node_path, app_key_dir); if (ret < 0) return false; dir = opendir(key_dir); if (!dir) { if (errno == ENOENT) return true; l_error("Failed to open AppKey storage directory: %s", key_dir); return false; } dir_fd = dirfd(dir); while ((entry = readdir(dir)) != NULL) { /* AppKeys are stored in regular files */ if (entry->d_type == DT_REG) finalize(dir_fd, entry->d_name, net_idx); } closedir(dir); return true; } bool keyring_put_remote_dev_key(struct mesh_node *node, uint16_t unicast, uint8_t count, const uint8_t dev_key[16]) { const char *node_path; char key_file[PATH_MAX]; bool result = true; int ret, fd, i; if (!IS_UNICAST_RANGE(unicast, count)) return false; if (!node) return false; node_path = node_get_storage_dir(node); ret = snprintf(key_file, PATH_MAX, "%s%s", node_path, dev_key_dir); if (ret < 0) return false; if (mkdir(key_file, 0755) != 0 && errno != EEXIST) l_error("Failed to create dir(%d): %s", errno, key_file); for (i = 0; i < count; i++) { ret = snprintf(key_file, PATH_MAX, "%s%s/%4.4x", node_path, dev_key_dir, unicast + i); if (ret < 0) return false; l_debug("Put Dev Key %s", key_file); fd = open(key_file, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd >= 0) { if (write(fd, dev_key, 16) != 16) result = false; close(fd); } else result = false; } return result; } static bool get_key(struct mesh_node *node, const char *key_dir, uint16_t key_idx, void *key, ssize_t sz) { bool result = false; int fd; if (!key) return false; fd = open_key_file(node, key_dir, key_idx, O_RDONLY); if (fd >= 0) { if (read(fd, key, sz) == sz) result = true; close(fd); } return result; } bool keyring_get_net_key(struct mesh_node *node, uint16_t net_idx, struct keyring_net_key *key) { return get_key(node, net_key_dir, net_idx, key, sizeof(*key)); } bool keyring_get_app_key(struct mesh_node *node, uint16_t app_idx, struct keyring_app_key *key) { return get_key(node, app_key_dir, app_idx, key, sizeof(*key)); } bool keyring_get_remote_dev_key(struct mesh_node *node, uint16_t unicast, uint8_t dev_key[16]) { const char *node_path; char key_file[PATH_MAX]; bool result = false; int ret, fd; if (!IS_UNICAST(unicast)) return false; if (!node) return false; node_path = node_get_storage_dir(node); ret = snprintf(key_file, PATH_MAX, "%s%s/%4.4x", node_path, dev_key_dir, unicast); if (ret < 0) return false; fd = open(key_file, O_RDONLY); if (fd >= 0) { if (read(fd, dev_key, 16) == 16) result = true; close(fd); } return result; } bool keyring_del_net_key(struct mesh_node *node, uint16_t net_idx) { const char *node_path; char key_file[PATH_MAX]; int ret; if (!node) return false; node_path = node_get_storage_dir(node); ret = snprintf(key_file, PATH_MAX, "%s%s/%3.3x", node_path, net_key_dir, net_idx); if (ret < 0) return false; l_debug("RM Net Key %s", key_file); remove(key_file); /* TODO: See if it is easiest to delete all bound App keys here */ /* TODO: see nftw() */ return true; } bool keyring_del_app_key(struct mesh_node *node, uint16_t app_idx) { const char *node_path; char key_file[PATH_MAX]; int ret; if (!node) return false; node_path = node_get_storage_dir(node); ret = snprintf(key_file, PATH_MAX, "%s%s/%3.3x", node_path, app_key_dir, app_idx); if (ret < 0) return false; l_debug("RM App Key %s", key_file); remove(key_file); return true; } bool keyring_del_remote_dev_key(struct mesh_node *node, uint16_t unicast, uint8_t count) { const char *node_path; char key_file[PATH_MAX]; int ret, i; if (!IS_UNICAST_RANGE(unicast, count)) return false; if (!node) return false; node_path = node_get_storage_dir(node); for (i = 0; i < count; i++) { ret = snprintf(key_file, PATH_MAX, "%s%s/%4.4x", node_path, dev_key_dir, unicast + i); if (ret < 0) return false; l_debug("RM Dev Key %s", key_file); remove(key_file); } return true; } bool keyring_del_remote_dev_key_all(struct mesh_node *node, uint16_t unicast) { uint8_t dev_key[16]; uint8_t test_key[16]; uint8_t cnt = 1; if (!keyring_get_remote_dev_key(node, unicast, dev_key)) return false; while (keyring_get_remote_dev_key(node, unicast + cnt, test_key)) { if (memcmp(dev_key, test_key, sizeof(dev_key))) break; cnt++; } if (cnt > 1) return keyring_del_remote_dev_key(node, unicast + 1, cnt - 1); return true; } static DIR *open_key_dir(const char *node_path, const char *key_dir_name) { char dir_path[PATH_MAX]; DIR *key_dir; int ret; ret = snprintf(dir_path, PATH_MAX, "%s%s", node_path, key_dir_name); if (ret < 0) return NULL; key_dir = opendir(dir_path); if (!key_dir) l_error("Failed to open keyring storage directory: %s", dir_path); return key_dir; } static int open_key_dir_entry(int dir_fd, struct dirent *entry, uint8_t fname_len) { if (entry->d_type != DT_REG) return -1; /* Check the file name length */ if (strlen(entry->d_name) != fname_len) return -1; return openat(dir_fd, entry->d_name, O_RDONLY); } static void append_old_key(struct l_dbus_message_builder *builder, const uint8_t key[16]) { l_dbus_message_builder_enter_dict(builder, "sv"); l_dbus_message_builder_append_basic(builder, 's', "OldKey"); l_dbus_message_builder_enter_variant(builder, "ay"); dbus_append_byte_array(builder, key, 16); l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_leave_dict(builder); } static void build_app_keys_reply(const char *node_path, struct l_dbus_message_builder *builder, uint16_t net_idx, uint8_t phase) { DIR *key_dir; int key_dir_fd; struct dirent *entry; key_dir = open_key_dir(node_path, app_key_dir); if (!key_dir) return; key_dir_fd = dirfd(key_dir); l_dbus_message_builder_enter_dict(builder, "sv"); l_dbus_message_builder_append_basic(builder, 's', "AppKeys"); l_dbus_message_builder_enter_variant(builder, "a(qaya{sv})"); l_dbus_message_builder_enter_array(builder, "(qaya{sv})"); while ((entry = readdir(key_dir)) != NULL) { struct keyring_app_key key; int fd = open_key_dir_entry(key_dir_fd, entry, 3); if (fd < 0) continue; if (read(fd, &key, sizeof(key)) != sizeof(key) || key.net_idx != net_idx) { close(fd); continue; } close(fd); l_dbus_message_builder_enter_struct(builder, "qaya{sv}"); l_dbus_message_builder_append_basic(builder, 'q', &key.app_idx); dbus_append_byte_array(builder, key.new_key, 16); l_dbus_message_builder_enter_array(builder, "{sv}"); if (phase != KEY_REFRESH_PHASE_NONE) append_old_key(builder, key.old_key); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_leave_struct(builder); } l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_leave_dict(builder); closedir(key_dir); } static bool build_net_keys_reply(const char *node_path, struct l_dbus_message_builder *builder) { DIR *key_dir; int key_dir_fd; struct dirent *entry; bool result = false; key_dir = open_key_dir(node_path, net_key_dir); if (!key_dir) return false; key_dir_fd = dirfd(key_dir); l_dbus_message_builder_enter_dict(builder, "sv"); l_dbus_message_builder_append_basic(builder, 's', "NetKeys"); l_dbus_message_builder_enter_variant(builder, "a(qaya{sv})"); l_dbus_message_builder_enter_array(builder, "(qaya{sv})"); while ((entry = readdir(key_dir)) != NULL) { struct keyring_net_key key; int fd = open_key_dir_entry(key_dir_fd, entry, 3); if (fd < 0) continue; if (read(fd, &key, sizeof(key)) != sizeof(key)) { close(fd); goto done; } close(fd); /* * If network key is stuck in phase 3, keyring * write failed and this key info is unreliable. */ if (key.phase == KEY_REFRESH_PHASE_THREE) continue; l_dbus_message_builder_enter_struct(builder, "qaya{sv}"); l_dbus_message_builder_append_basic(builder, 'q', &key.net_idx); dbus_append_byte_array(builder, key.new_key, 16); l_dbus_message_builder_enter_array(builder, "{sv}"); if (key.phase != KEY_REFRESH_PHASE_NONE) { dbus_append_dict_entry_basic(builder, "Phase", "y", &key.phase); append_old_key(builder, key.old_key); } build_app_keys_reply(node_path, builder, key.net_idx, key.phase); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_leave_struct(builder); } l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_leave_dict(builder); result = true; done: closedir(key_dir); return result; } struct dev_key_entry { uint16_t unicast; uint8_t value[16]; }; static bool match_key_value(const void *a, const void *b) { const struct dev_key_entry *key = a; const uint8_t *value = b; return (memcmp(key->value, value, 16) == 0); } static void build_dev_key_entry(void *a, void *b) { struct dev_key_entry *key = a; struct l_dbus_message_builder *builder = b; l_dbus_message_builder_enter_struct(builder, "qay"); l_dbus_message_builder_append_basic(builder, 'q', &key->unicast); dbus_append_byte_array(builder, key->value, 16); l_dbus_message_builder_leave_struct(builder); } static bool build_dev_keys_reply(const char *node_path, struct l_dbus_message_builder *builder) { DIR *key_dir; int key_dir_fd; struct dirent *entry; struct l_queue *keys; bool result = false; key_dir = open_key_dir(node_path, dev_key_dir); /* * There is always at least one device key present for a local node. * Therefore, return false, if the directory does not exist. */ if (!key_dir) return false; key_dir_fd = dirfd(key_dir); keys = l_queue_new(); while ((entry = readdir(key_dir)) != NULL) { uint8_t buf[16]; uint16_t unicast; struct dev_key_entry *key; int fd = open_key_dir_entry(key_dir_fd, entry, 4); if (fd < 0) continue; if (read(fd, buf, 16) != 16) { close(fd); goto done; } close(fd); if (sscanf(entry->d_name, "%04hx", &unicast) != 1) goto done; key = l_queue_find(keys, match_key_value, buf); if (key) { if (key->unicast > unicast) key->unicast = unicast; continue; } key = l_new(struct dev_key_entry, 1); key->unicast = unicast; memcpy(key->value, buf, 16); l_queue_push_tail(keys, key); } l_dbus_message_builder_enter_dict(builder, "sv"); l_dbus_message_builder_append_basic(builder, 's', "DevKeys"); l_dbus_message_builder_enter_variant(builder, "a(qay)"); l_dbus_message_builder_enter_array(builder, "(qay)"); l_queue_foreach(keys, build_dev_key_entry, builder); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_leave_dict(builder); result = true; done: l_queue_destroy(keys, l_free); closedir(key_dir); return result; } bool keyring_build_export_keys_reply(struct mesh_node *node, struct l_dbus_message_builder *builder) { const char *node_path; if (!node) return false; node_path = node_get_storage_dir(node); if (!build_net_keys_reply(node_path, builder)) return false; return build_dev_keys_reply(node_path, builder); } bluez-5.87/mesh/PaxHeaders/node.h0000644000000000000000000000005015063045254013711 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/node.h0000644000000000000000000001057615063045254013403 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ struct mesh_net; struct mesh_node; struct mesh_io; struct mesh_agent; struct mesh_config; struct mesh_config_node; struct mesh_prov_node_info; typedef void (*node_ready_func_t) (void *user_data, int status, struct mesh_node *node); typedef void (*node_join_ready_func_t) (struct mesh_node *node, struct mesh_agent *agent); void node_remove(struct mesh_node *node); void node_join(const char *app_root, const char *sender, const uint8_t *uuid, node_join_ready_func_t cb); uint8_t *node_uuid_get(struct mesh_node *node); struct mesh_net *node_get_net(struct mesh_node *node); struct mesh_node *node_find_by_addr(uint16_t addr); struct mesh_node *node_find_by_uuid(uint8_t uuid[16]); struct mesh_node *node_find_by_token(uint64_t token); bool node_is_provisioner(struct mesh_node *node); bool node_is_busy(struct mesh_node *node); void node_app_key_delete(struct mesh_node *node, uint16_t net_idx, uint16_t app_idx); uint16_t node_get_primary(struct mesh_node *node); uint16_t node_get_primary_net_idx(struct mesh_node *node); void node_set_token(struct mesh_node *node, uint8_t token[8]); const uint8_t *node_get_token(struct mesh_node *node); const uint8_t *node_get_device_key(struct mesh_node *node); bool node_get_device_key_candidate(struct mesh_node *node, uint8_t *key); void node_finalize_candidate(struct mesh_node *node); void node_set_num_elements(struct mesh_node *node, uint8_t num_ele); uint8_t node_get_num_elements(struct mesh_node *node); uint8_t node_default_ttl_get(struct mesh_node *node); bool node_default_ttl_set(struct mesh_node *node, uint8_t ttl); bool node_set_sequence_number(struct mesh_node *node, uint32_t seq); uint32_t node_get_sequence_number(struct mesh_node *node); int node_get_element_idx(struct mesh_node *node, uint16_t ele_addr); struct l_queue *node_get_element_models(struct mesh_node *node, uint8_t ele_idx); uint16_t node_get_crpl(struct mesh_node *node); const uint8_t *node_get_comp(struct mesh_node *node, uint8_t page_num, uint16_t *len); bool node_replace_comp(struct mesh_node *node, uint8_t retire, uint8_t with); uint8_t node_lpn_mode_get(struct mesh_node *node); bool node_relay_mode_set(struct mesh_node *node, bool enable, uint8_t cnt, uint16_t interval); uint8_t node_relay_mode_get(struct mesh_node *node, uint8_t *cnt, uint16_t *interval); bool node_proxy_mode_set(struct mesh_node *node, bool enable); uint8_t node_proxy_mode_get(struct mesh_node *node); bool node_beacon_mode_set(struct mesh_node *node, bool enable); bool node_mpb_mode_set(struct mesh_node *node, bool enable, uint8_t period); uint8_t node_mpb_mode_get(struct mesh_node *node, uint8_t *period); uint8_t node_beacon_mode_get(struct mesh_node *node); bool node_friend_mode_set(struct mesh_node *node, bool enable); uint8_t node_friend_mode_get(struct mesh_node *node); const char *node_get_element_path(struct mesh_node *node, uint8_t ele_idx); const char *node_get_owner(struct mesh_node *node); const char *node_get_app_path(struct mesh_node *node); bool node_add_pending_local(struct mesh_node *node, const struct mesh_prov_node_info *info); void node_attach_io_all(struct mesh_io *io); void node_attach(const char *app_root, const char *sender, uint64_t token, node_ready_func_t cb, void *user_data); void node_build_attach_reply(struct mesh_node *node, struct l_dbus_message *reply); void node_create(const char *app_root, const char *sender, const uint8_t *uuid, node_ready_func_t cb, void *user_data); void node_import(const char *app_root, const char *sender, const uint8_t *uuid, const uint8_t dev_key[16], const uint8_t net_key[16], uint16_t net_idx, bool kr, bool ivu, uint32_t iv_index, uint16_t unicast, node_ready_func_t cb, void *user_data); bool node_dbus_init(struct l_dbus *bus); void node_cleanup_all(void); struct mesh_config *node_config_get(struct mesh_node *node); struct mesh_agent *node_get_agent(struct mesh_node *node); const char *node_get_storage_dir(struct mesh_node *node); bool node_load_from_storage(const char *storage_dir); void node_finalize_new_node(struct mesh_node *node, struct mesh_io *io); void node_property_changed(struct mesh_node *node, const char *property); bool node_refresh(struct mesh_node *node, bool hard, void *prov_info); bluez-5.87/mesh/PaxHeaders/mesh-defs.h0000644000000000000000000000005015063045254014637 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/mesh-defs.h0000644000000000000000000000704615063045254014327 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * */ /* * MshPRT_v1.1, section 3.3.1 / Core_v5.3, section 2.3.1.3 * Maximum length of AdvData without 'Length' field (30) */ #define MESH_AD_MAX_LEN (BT_AD_MAX_DATA_LEN - 1) /* Max size of a Network PDU, prior prepending AD type (29)*/ #define MESH_NET_MAX_PDU_LEN (MESH_AD_MAX_LEN - 1) #define FEATURE_RELAY 1 #define FEATURE_PROXY 2 #define FEATURE_FRIEND 4 #define FEATURE_LPN 8 #define MESH_MODE_DISABLED 0 #define MESH_MODE_ENABLED 1 #define MESH_MODE_UNSUPPORTED 2 #define KEY_REFRESH_PHASE_NONE 0x00 #define KEY_REFRESH_PHASE_ONE 0x01 #define KEY_REFRESH_PHASE_TWO 0x02 #define KEY_REFRESH_PHASE_THREE 0x03 #define KEY_REFRESH_TRANS_TWO 0x02 #define KEY_REFRESH_TRANS_THREE 0x03 #define DEFAULT_TTL 0xff #define TTL_MASK 0x7f /* Supported algorithms for provisioning */ #define ALG_FIPS_256_ECC 0x0001 /* Input OOB action bit flags */ #define OOB_IN_PUSH 0x0001 #define OOB_IN_TWIST 0x0002 #define OOB_IN_NUMBER 0x0004 #define OOB_IN_ALPHA 0x0008 /* Output OOB action bit flags */ #define OOB_OUT_BLINK 0x0001 #define OOB_OUT_BEEP 0x0002 #define OOB_OUT_VIBRATE 0x0004 #define OOB_OUT_NUMBER 0x0008 #define OOB_OUT_ALPHA 0x0010 /* Status codes */ #define MESH_STATUS_SUCCESS 0x00 #define MESH_STATUS_INVALID_ADDRESS 0x01 #define MESH_STATUS_INVALID_MODEL 0x02 #define MESH_STATUS_INVALID_APPKEY 0x03 #define MESH_STATUS_INVALID_NETKEY 0x04 #define MESH_STATUS_INSUFF_RESOURCES 0x05 #define MESH_STATUS_IDX_ALREADY_STORED 0x06 #define MESH_STATUS_INVALID_PUB_PARAM 0x07 #define MESH_STATUS_NOT_SUB_MOD 0x08 #define MESH_STATUS_STORAGE_FAIL 0x09 #define MESH_STATUS_FEATURE_NO_SUPPORT 0x0a #define MESH_STATUS_CANNOT_UPDATE 0x0b #define MESH_STATUS_CANNOT_REMOVE 0x0c #define MESH_STATUS_CANNOT_BIND 0x0d #define MESH_STATUS_UNABLE_CHANGE_STATE 0x0e #define MESH_STATUS_CANNOT_SET 0x0f #define MESH_STATUS_UNSPECIFIED_ERROR 0x10 #define MESH_STATUS_INVALID_BINDING 0x11 #define UNASSIGNED_ADDRESS 0x0000 #define PROXIES_ADDRESS 0xfffc #define FRIENDS_ADDRESS 0xfffd #define RELAYS_ADDRESS 0xfffe #define ALL_NODES_ADDRESS 0xffff #define VIRTUAL_ADDRESS_LOW 0x8000 #define VIRTUAL_ADDRESS_HIGH 0xbfff #define GROUP_ADDRESS_LOW 0xc000 #define GROUP_ADDRESS_HIGH 0xfeff #define FIXED_GROUP_LOW 0xff00 #define FIXED_GROUP_HIGH 0xffff #define NODE_IDENTITY_STOPPED 0x00 #define NODE_IDENTITY_RUNNING 0x01 #define NODE_IDENTITY_NOT_SUPPORTED 0x02 #define PRIMARY_ELE_IDX 0x00 #define PRIMARY_NET_IDX 0x0000 #define MAX_KEY_IDX 0x0fff #define MAX_MODEL_COUNT 0xff #define MAX_ELE_COUNT 0xff #define MAX_MSG_LEN 380 #define VENDOR_ID_MASK 0xffff0000 #define NET_IDX_INVALID 0xffff #define NET_NID_INVALID 0xff #define NET_IDX_MAX 0x0fff #define APP_IDX_MAX 0x0fff #define APP_AID_INVALID 0xff #define APP_IDX_MASK 0x0fff #define APP_IDX_DEV_REMOTE 0x6fff #define APP_IDX_DEV_LOCAL 0x7fff #define DEFAULT_SEQUENCE_NUMBER 0x000000 #define SEQ_MASK 0xffffff #define IS_UNASSIGNED(x) ((x) == UNASSIGNED_ADDRESS) #define IS_UNICAST(x) (((x) > UNASSIGNED_ADDRESS) && \ ((x) < VIRTUAL_ADDRESS_LOW)) #define IS_UNICAST_RANGE(x, c) (IS_UNICAST(x) && IS_UNICAST(x + c - 1)) #define IS_VIRTUAL(x) (((x) >= VIRTUAL_ADDRESS_LOW) && \ ((x) <= VIRTUAL_ADDRESS_HIGH)) #define IS_GROUP(x) ((((x) >= GROUP_ADDRESS_LOW) && \ ((x) < FIXED_GROUP_HIGH)) || \ ((x) == ALL_NODES_ADDRESS)) #define IS_FIXED_GROUP_ADDRESS(x) ((x) >= PROXIES_ADDRESS) #define IS_ALL_NODES(x) ((x) == ALL_NODES_ADDRESS) bluez-5.87/mesh/PaxHeaders/friend.h0000644000000000000000000000005015063045254014233 xustar0020 atime=1770587288 20 ctime=1783094103 bluez-5.87/mesh/friend.h0000644000000000000000000000225215063045254013715 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ #define OP_FRND_REQUEST 0x8040 #define OP_FRND_INQUIRY 0x8041 #define OP_FRND_CONFIRM 0x8042 #define OP_FRND_SUB_LIST_ADD 0x8043 #define OP_FRND_SUB_LIST_CONFIRM 0x8044 #define OP_FRND_SUB_LIST_REMOVE 0x8045 #define OP_FRND_NEGOTIATE 0x8046 #define OP_FRND_CLEAR 0x8047 void friend_poll(struct mesh_net *net, uint16_t src, bool seq, struct mesh_friend *frnd); void friend_request(struct mesh_net *net, uint16_t net_idx, uint16_t src, uint8_t minReq, uint8_t delay, uint32_t timeout, uint16_t prev, uint8_t num_elements, uint16_t cntr, int8_t rssi); void friend_clear_confirm(struct mesh_net *net, uint16_t src, uint16_t lpn, uint16_t lpnCounter); void friend_clear(struct mesh_net *net, uint16_t src, uint16_t lpn, uint16_t lpnCounter, struct mesh_friend *frnd); void friend_sub_add(struct mesh_net *net, struct mesh_friend *frnd, const uint8_t *pkt, uint8_t len); void friend_sub_del(struct mesh_net *net, struct mesh_friend *frnd, const uint8_t *pkt, uint8_t len); bluez-5.87/PaxHeaders/Makefile.tools0000644000000000000000000000005015221752714014460 xustar0020 atime=1783092736 20 ctime=1783094098 bluez-5.87/Makefile.tools0000644000000000000000000004640115221752714014146 0ustar00rootroot# SPDX-License-Identifier: GPL-2.0 if CLIENT bin_PROGRAMS += client/bluetoothctl client_bluetoothctl_SOURCES = client/main.c \ client/print.h client/print.c \ client/display.h client/display.c \ client/agent.h client/agent.c \ client/advertising.h \ client/advertising.c \ client/adv_monitor.h \ client/adv_monitor.c \ client/gatt.h client/gatt.c \ client/admin.h client/admin.c \ client/player.h client/player.c \ client/mgmt.h client/mgmt.c \ client/assistant.h client/assistant.c \ client/hci.h client/hci.c \ client/telephony.h client/telephony.c client_bluetoothctl_LDADD = lib/libbluetooth-internal.la \ gdbus/libgdbus-internal.la src/libshared-glib.la \ $(GLIB_LIBS) $(DBUS_LIBS) -lreadline endif if ZSH_COMPLETIONS zshcompletiondir=$(ZSH_COMPLETIONDIR) dist_zshcompletion_DATA = completion/zsh/_bluetoothctl endif if MONITOR bin_PROGRAMS += monitor/btmon monitor_btmon_SOURCES = monitor/main.c monitor/bt.h \ monitor/display.h monitor/display.c \ monitor/hcidump.h monitor/hcidump.c \ monitor/ellisys.h monitor/ellisys.c \ monitor/control.h monitor/control.c \ monitor/packet.h monitor/packet.c \ monitor/vendor.h monitor/vendor.c \ monitor/lmp.h monitor/lmp.c \ monitor/crc.h monitor/crc.c \ monitor/ll.h monitor/ll.c \ monitor/l2cap.h monitor/l2cap.c \ monitor/sdp.h monitor/sdp.c \ monitor/avctp.h monitor/avctp.c \ monitor/avdtp.h monitor/avdtp.c \ monitor/a2dp.h monitor/a2dp.c \ monitor/rfcomm.h monitor/rfcomm.c \ monitor/bnep.h monitor/bnep.c \ monitor/hwdb.h monitor/hwdb.c \ monitor/keys.h monitor/keys.c \ monitor/analyze.h monitor/analyze.c \ monitor/intel.h monitor/intel.c \ monitor/broadcom.h monitor/broadcom.c \ monitor/msft.h monitor/msft.c \ monitor/jlink.h monitor/jlink.c \ monitor/tty.h monitor/emulator.h \ monitor/att.h monitor/att.c \ src/log.h src/log.c \ src/textfile.h src/textfile.c \ src/settings.h src/settings.c monitor_btmon_LDADD = lib/libbluetooth-internal.la \ src/libshared-mainloop.la \ $(GLIB_LIBS) $(UDEV_LIBS) -ldl if MANPAGES man_MANS += doc/btmon.1 endif endif manual_pages += doc/btmon.1 if LOGGER pkglibexec_PROGRAMS += tools/btmon-logger tools_btmon_logger_SOURCES = tools/btmon-logger.c tools_btmon_logger_LDADD = src/libshared-mainloop.la if SYSTEMD systemdsystemunit_DATA += tools/bluetooth-logger.service endif endif if TESTING noinst_PROGRAMS += emulator/btvirt emulator/b1ee emulator/hfp \ peripheral/btsensor tools/3dsp \ tools/mgmt-tester tools/gap-tester \ tools/l2cap-tester tools/sco-tester \ tools/smp-tester tools/hci-tester \ tools/rfcomm-tester tools/bnep-tester \ tools/userchan-tester tools/iso-tester \ tools/mesh-tester tools/ioctl-tester \ tools/6lowpan-tester emulator_btvirt_SOURCES = emulator/main.c monitor/bt.h \ emulator/serial.h emulator/serial.c \ emulator/server.h emulator/server.c \ emulator/vhci.h emulator/vhci.c \ emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c \ emulator/smp.c \ emulator/phy.h emulator/phy.c \ emulator/le.h emulator/le.c emulator_btvirt_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la emulator_b1ee_SOURCES = emulator/b1ee.c emulator_b1ee_LDADD = src/libshared-mainloop.la emulator_hfp_SOURCES = emulator/hfp.c emulator_hfp_LDADD = src/libshared-mainloop.la peripheral_btsensor_SOURCES = peripheral/main.c \ peripheral/efivars.h peripheral/efivars.c \ peripheral/attach.h peripheral/attach.c \ peripheral/log.h peripheral/log.c \ peripheral/gap.h peripheral/gap.c \ peripheral/gatt.h peripheral/gatt.c peripheral_btsensor_LDADD = src/libshared-mainloop.la \ lib/libbluetooth-internal.la tools_3dsp_SOURCES = tools/3dsp.c monitor/bt.h tools_3dsp_LDADD = src/libshared-mainloop.la tools_mgmt_tester_SOURCES = tools/mgmt-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c \ emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c \ emulator/smp.c tools_mgmt_tester_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) tools_mesh_tester_SOURCES = tools/mesh-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c \ emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c \ emulator/smp.c tools_mesh_tester_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) tools_l2cap_tester_SOURCES = tools/l2cap-tester.c tools/tester.h monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c \ emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c \ emulator/smp.c tools_l2cap_tester_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) tools_rfcomm_tester_SOURCES = tools/rfcomm-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c \ emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c \ emulator/smp.c tools_rfcomm_tester_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) tools_bnep_tester_SOURCES = tools/bnep-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c \ emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c \ emulator/smp.c tools_bnep_tester_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) tools_smp_tester_SOURCES = tools/smp-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c \ emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c \ emulator/smp.c tools_smp_tester_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) tools_gap_tester_SOURCES = tools/gap-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c \ emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c \ emulator/smp.c tools_gap_tester_LDADD = lib/libbluetooth-internal.la \ gdbus/libgdbus-internal.la \ src/libshared-glib.la \ $(GLIB_LIBS) $(DBUS_LIBS) tools_sco_tester_SOURCES = tools/sco-tester.c tools/tester.h monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c \ emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c \ emulator/smp.c tools_sco_tester_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) tools_hci_tester_SOURCES = tools/hci-tester.c monitor/bt.h tools_hci_tester_LDADD = src/libshared-glib.la $(GLIB_LIBS) tools_userchan_tester_SOURCES = tools/userchan-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c \ emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c \ emulator/smp.c tools_userchan_tester_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) tools_iso_tester_SOURCES = tools/iso-tester.c tools/tester.h monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c \ emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c \ emulator/smp.c tools_iso_tester_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) tools_ioctl_tester_SOURCES = tools/ioctl-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c \ emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c \ emulator/smp.c tools_ioctl_tester_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) tools_6lowpan_tester_SOURCES = tools/6lowpan-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c \ emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c \ emulator/smp.c tools_6lowpan_tester_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) endif if TOOLS bin_PROGRAMS += tools/rctest tools/l2test tools/l2ping tools/bluemoon \ tools/hex2hcd tools/mpris-proxy tools/btattach tools/isotest noinst_PROGRAMS += tools/bdaddr tools/avinfo tools/avtest \ tools/scotest tools/hwdb \ tools/hcieventmask tools/hcisecfilter \ tools/btinfo tools/btconfig \ tools/btsnoop tools/btproxy \ tools/btiotest tools/bneptest \ tools/cltest tools/oobtest tools/advtest \ tools/seq2bseq tools/nokfw tools/rtlfw \ tools/bcmfw tools/create-image \ tools/eddystone tools/ibeacon \ tools/btgatt-client tools/btgatt-server \ tools/test-runner tools/check-selftest \ tools/gatt-service profiles/iap/iapd tools_bdaddr_SOURCES = tools/bdaddr.c src/oui.h src/oui.c tools_bdaddr_LDADD = lib/libbluetooth-internal.la $(UDEV_LIBS) tools_avinfo_LDADD = lib/libbluetooth-internal.la tools_avtest_LDADD = lib/libbluetooth-internal.la tools_scotest_LDADD = lib/libbluetooth-internal.la tools_hwdb_LDADD = lib/libbluetooth-internal.la tools_hcieventmask_LDADD = lib/libbluetooth-internal.la tools_btinfo_SOURCES = tools/btinfo.c monitor/bt.h tools_btinfo_LDADD = src/libshared-mainloop.la tools_btattach_SOURCES = tools/btattach.c monitor/bt.h tools_btattach_LDADD = src/libshared-mainloop.la tools_btconfig_SOURCES = tools/btconfig.c tools_btconfig_LDADD = src/libshared-mainloop.la tools_btsnoop_SOURCES = tools/btsnoop.c tools_btsnoop_LDADD = src/libshared-mainloop.la tools_btproxy_SOURCES = tools/btproxy.c monitor/bt.h tools_btproxy_LDADD = src/libshared-mainloop.la tools_btiotest_SOURCES = tools/btiotest.c btio/btio.h btio/btio.c tools_btiotest_LDADD = lib/libbluetooth-internal.la $(GLIB_LIBS) tools_bneptest_SOURCES = tools/bneptest.c \ btio/btio.h btio/btio.c \ src/log.h src/log.c \ profiles/network/bnep.h profiles/network/bnep.c tools_bneptest_LDADD = lib/libbluetooth-internal.la $(GLIB_LIBS) \ src/libshared-mainloop.la tools_cltest_SOURCES = tools/cltest.c tools_cltest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la tools_oobtest_SOURCES = tools/oobtest.c tools_oobtest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la tools_advtest_SOURCES = tools/advtest.c tools_advtest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la tools_seq2bseq_SOURCES = tools/seq2bseq.c tools_nokfw_SOURCES = tools/nokfw.c tools_rtlfw_SOURCES = tools/rtlfw.c tools_create_image_SOURCES = tools/create-image.c tools_eddystone_SOURCES = tools/eddystone.c monitor/bt.h tools_eddystone_LDADD = src/libshared-mainloop.la tools_ibeacon_SOURCES = tools/ibeacon.c monitor/bt.h tools_ibeacon_LDADD = src/libshared-mainloop.la tools_btgatt_client_SOURCES = tools/btgatt-client.c src/uuid-helper.c tools_btgatt_client_LDADD = src/libshared-mainloop.la \ lib/libbluetooth-internal.la tools_btgatt_server_SOURCES = tools/btgatt-server.c src/uuid-helper.c tools_btgatt_server_LDADD = src/libshared-mainloop.la \ lib/libbluetooth-internal.la tools_test_runner_CFLAGS = $(WARNING_CFLAGS) tools_test_runner_LDFLAGS = tools_rctest_LDADD = lib/libbluetooth-internal.la tools_l2test_LDADD = lib/libbluetooth-internal.la tools_l2ping_LDADD = lib/libbluetooth-internal.la tools_bluemoon_SOURCES = tools/bluemoon.c monitor/bt.h tools_bluemoon_LDADD = src/libshared-mainloop.la tools_hex2hcd_SOURCES = tools/hex2hcd.c tools/missing.h tools_mpris_proxy_SOURCES = tools/mpris-proxy.c tools_mpris_proxy_LDADD = gdbus/libgdbus-internal.la $(GLIB_LIBS) $(DBUS_LIBS) if SYSTEMD systemduserunit_DATA += tools/mpris-proxy.service endif tools_gatt_service_SOURCES = tools/gatt-service.c tools_gatt_service_LDADD = gdbus/libgdbus-internal.la \ src/libshared-mainloop.la $(GLIB_LIBS) $(DBUS_LIBS) tools_isotest_LDADD = lib/libbluetooth-internal.la profiles_iap_iapd_SOURCES = profiles/iap/main.c profiles_iap_iapd_LDADD = gdbus/libgdbus-internal.la $(GLIB_LIBS) $(DBUS_LIBS) if MANPAGES man_MANS += doc/rctest.1 doc/l2ping.1 doc/btattach.1 doc/isotest.1 \ doc/bluetoothctl.1 \ doc/bluetoothctl-mgmt.1 \ doc/bluetoothctl-monitor.1 doc/bluetoothctl-admin.1 \ doc/bluetoothctl-advertise.1 doc/bluetoothctl-endpoint.1 \ doc/bluetoothctl-gatt.1 doc/bluetoothctl-player.1 \ doc/bluetoothctl-scan.1 doc/bluetoothctl-transport.1 \ doc/bluetoothctl-assistant.1 doc/bluetoothctl-hci.1 \ doc/bluetoothctl-telephony.1 endif if MESH if DEPRECATED bin_PROGRAMS += tools/meshctl tools_meshctl_SOURCES = tools/meshctl.c \ tools/mesh/agent.h tools/mesh/agent.c \ tools/mesh/config-model.h\ tools/mesh-gatt/mesh-net.h \ tools/mesh-gatt/node.h tools/mesh-gatt/node.c \ tools/mesh-gatt/gatt.h tools/mesh-gatt/gatt.c \ tools/mesh-gatt/crypto.h\ tools/mesh-gatt/crypto.c \ tools/mesh-gatt/keys.h \ tools/mesh-gatt/net.h tools/mesh-gatt/net.c \ tools/mesh-gatt/prov.h tools/mesh-gatt/prov.c \ tools/mesh-gatt/util.h tools/mesh-gatt/util.c \ tools/mesh-gatt/prov-db.h \ tools/mesh-gatt/prov-db.c \ tools/mesh-gatt/config-client.c \ tools/mesh-gatt/config-server.c \ tools/mesh-gatt/onoff-model.h \ tools/mesh-gatt/onoff-model.c tools_meshctl_LDADD = gdbus/libgdbus-internal.la src/libshared-glib.la \ lib/libbluetooth-internal.la \ $(GLIB_LIBS) $(DBUS_LIBS) -ljson-c -lreadline endif bin_PROGRAMS += tools/mesh-cfgclient tools_mesh_cfgclient_SOURCES = tools/mesh-cfgclient.c \ tools/mesh/model.h tools/mesh/config-model.h \ tools/mesh/cfgcli.h tools/mesh/cfgcli.c \ tools/mesh/keys.h tools/mesh/keys.c \ tools/mesh/util.h tools/mesh/util.c \ tools/mesh/remote.h tools/mesh/remote.c \ tools/mesh/agent.h tools/mesh/agent.c \ tools/mesh/mesh-db.h tools/mesh/mesh-db.c \ mesh/util.h mesh/util.c \ mesh/crypto.h mesh/crypto.c tools_mesh_cfgclient_LDADD = lib/libbluetooth-internal.la src/libshared-ell.la \ $(ell_ldadd) -ljson-c -lreadline bin_PROGRAMS += tools/mesh-cfgtest tools_mesh_cfgtest_SOURCES = tools/mesh-cfgtest.c tools_mesh_cfgtest_LDADD = lib/libbluetooth-internal.la src/libshared-ell.la \ $(ell_ldadd) endif EXTRA_DIST += tools/mesh-gatt/local_node.json tools/mesh-gatt/prov_db.json if DEPRECATED bin_PROGRAMS += tools/hciattach tools/hciconfig tools/hcitool \ tools/rfcomm tools/sdptool tools/ciptool tools_hciattach_SOURCES = tools/hciattach.c tools/hciattach.h \ tools/hciattach_st.c \ tools/hciattach_ti.c \ tools/hciattach_tialt.c \ tools/hciattach_ath3k.c \ tools/hciattach_qualcomm.c \ tools/hciattach_intel.c \ tools/hciattach_bcm43xx.c tools_hciattach_LDADD = lib/libbluetooth-internal.la tools_hciconfig_SOURCES = tools/hciconfig.c tools_hciconfig_LDADD = lib/libbluetooth-internal.la tools_hcitool_SOURCES = tools/hcitool.c src/oui.h src/oui.c tools_hcitool_LDADD = lib/libbluetooth-internal.la $(UDEV_LIBS) tools_sdptool_SOURCES = tools/sdptool.c src/sdp-xml.h src/sdp-xml.c tools_sdptool_LDADD = lib/libbluetooth-internal.la $(GLIB_LIBS) tools_ciptool_LDADD = lib/libbluetooth-internal.la tools_rfcomm_LDADD = lib/libbluetooth-internal.la if MANPAGES man_MANS += doc/hciattach.1 doc/hciconfig.1 doc/hcitool.1 \ doc/rfcomm.1 doc/sdptool.1 doc/ciptool.1 endif endif endif manual_pages += doc/hciattach.1 doc/hciconfig.1 doc/hcitool.1 \ doc/rfcomm.1 doc/sdptool.1 doc/ciptool.1 \ doc/rctest.1 doc/l2ping.1 doc/btattach.1 \ doc/bdaddr.1 doc/isotest.1 doc/btmgmt.1 \ doc/bluetoothctl.1 \ doc/bluetoothctl-mgmt.1 \ doc/bluetoothctl-monitor.1 \ doc/bluetoothctl-admin.1 \ doc/bluetoothctl-advertise.1 \ doc/bluetoothctl-endpoint.1 \ doc/bluetoothctl-gatt.1 \ doc/bluetoothctl-player.1 \ doc/bluetoothctl-scan.1 \ doc/bluetoothctl-transport.1 \ doc/bluetoothctl-assistant.1 \ doc/bluetoothctl-hci.1 \ doc/bluetoothctl-telephony.1 if HID2HCI udevdir = $(UDEV_DIR) udev_PROGRAMS = tools/hid2hci tools_hid2hci_LDADD = $(UDEV_LIBS) if MANPAGES man_MANS += doc/hid2hci.1 endif endif manual_pages += doc/hid2hci.1 if READLINE noinst_PROGRAMS += tools/btmgmt tools/obex-client-tool tools/obex-server-tool \ tools/bluetooth-player tools/obexctl tools_obex_client_tool_SOURCES = $(gobex_sources) $(btio_sources) \ tools/obex-client-tool.c tools_obex_client_tool_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) -lreadline tools_obex_server_tool_SOURCES = $(gobex_sources) $(btio_sources) \ tools/obex-server-tool.c tools_obex_server_tool_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) tools_bluetooth_player_SOURCES = tools/bluetooth-player.c client/print.c \ client/player.c tools_bluetooth_player_LDADD = gdbus/libgdbus-internal.la \ src/libshared-glib.la \ $(GLIB_LIBS) $(DBUS_LIBS) -lreadline tools_obexctl_SOURCES = tools/obexctl.c tools_obexctl_LDADD = gdbus/libgdbus-internal.la src/libshared-glib.la \ $(GLIB_LIBS) $(DBUS_LIBS) -lreadline tools_btmgmt_SOURCES = tools/btmgmt.c src/uuid-helper.c client/display.c \ client/mgmt.c tools_btmgmt_LDADD = lib/libbluetooth-internal.la src/libshared-glib.la \ $(GLIB_LIBS) -lreadline if DEPRECATED noinst_PROGRAMS += attrib/gatttool attrib_gatttool_SOURCES = attrib/gatttool.c attrib/att.c attrib/gatt.c \ attrib/gattrib.c btio/btio.c \ attrib/gatttool.h attrib/interactive.c \ attrib/utils.c src/log.c client/display.c \ client/display.h attrib_gatttool_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) -lreadline endif endif if CUPS if CUPS_SERVERBIN cupsdir = $(CUPS_SERVERBIN)/backend else cupsdir = $(libdir)/cups/backend endif cups_PROGRAMS = profiles/cups/bluetooth profiles_cups_bluetooth_SOURCES = profiles/cups/main.c \ profiles/cups/cups.h \ profiles/cups/sdp.c \ profiles/cups/spp.c \ profiles/cups/hcrp.c profiles_cups_bluetooth_LDADD = $(GLIB_LIBS) $(DBUS_LIBS) \ lib/libbluetooth-internal.la \ gdbus/libgdbus-internal.la endif test_scripts += test/bluezutils.py \ test/dbusdef.py test/monitor-bluetooth test/list-devices \ test/test-discovery test/test-manager test/test-adapter \ test/test-device test/simple-agent \ test/simple-endpoint \ test/test-network test/test-profile \ test/service-record.dtd \ test/service-did.xml test/service-spp.xml test/service-opp.xml \ test/service-ftp.xml test/simple-player test/test-nap \ test/test-hfp test/opp-client test/ftp-client \ test/pbap-client test/map-client test/example-advertisement \ test/example-gatt-server test/example-gatt-client \ test/test-gatt-profile test/test-mesh test/agent.py if BTPCLIENT noinst_PROGRAMS += client/btpclient/btpclient client/btpclient/btpclientctl client_btpclient_btpclient_SOURCES = client/btpclient/btpclient.c \ client/btpclient/btpclient.h \ src/shared/btp.c src/shared/btp.h \ client/btpclient/bap.c client/btpclient/bap.h \ client/btpclient/core.c client/btpclient/core.h \ client/btpclient/gap.c client/btpclient/gap.h \ client/btpclient/gatt.c client/btpclient/gatt.h client_btpclient_btpclient_LDADD = lib/libbluetooth-internal.la \ src/libshared-ell.la $(ell_ldadd) client/btpclient/btpclient.$(OBJEXT): src/libshared-ell.la ell/internal client_btpclient_btpclientctl_SOURCES = client/btpclient/btpclientctl.c client/display.c client_btpclient_btpclientctl_LDADD = src/libshared-mainloop.la src/libshared-glib.la \ lib/libbluetooth-internal.la -lreadline endif bluez-5.87/PaxHeaders/config.h.in0000644000000000000000000000005015221752775013713 xustar0020 atime=1783092768 20 ctime=1783094098 bluez-5.87/config.h.in0000644000000000000000000001146315221752775013401 0ustar00rootroot/* config.h.in. Generated from configure.ac by autoheader. */ /* Directory for the configuration files */ #undef CONFIGDIR /* Define if external plugin support is required */ #undef EXTERNAL_PLUGINS /* Define to 1 if you have A2DP support. */ #undef HAVE_A2DP /* Define to 1 if you have ASHA support. */ #undef HAVE_ASHA /* Define to 1 if you have AVRCP support. */ #undef HAVE_AVRCP /* Define to 1 if you have the backtrace support. */ #undef HAVE_BACKTRACE_SUPPORT /* Define to 1 if you have the declaration of 'basename', and to 0 if you don't. */ #undef HAVE_DECL_BASENAME /* Define to 1 if you have the declaration of 'SCM_TSTAMP_COMPLETION', and to 0 if you don't. */ #undef HAVE_DECL_SCM_TSTAMP_COMPLETION /* Define to 1 if you have the declaration of 'SOF_TIMESTAMPING_TX_COMPLETION', and to 0 if you don't. */ #undef HAVE_DECL_SOF_TIMESTAMPING_TX_COMPLETION /* Define to 1 if you have the header file. */ #undef HAVE_DLFCN_H /* Define to 1 if you have the 'explicit_bzero' function. */ #undef HAVE_EXPLICIT_BZERO /* Define to 1 if you have the 'getrandom' function. */ #undef HAVE_GETRANDOM /* Define to 1 if you have GMAP support. */ #undef HAVE_GMAP /* Define to 1 if you have HFP support. */ #undef HAVE_HFP /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H /* Define to 1 if you have the 'asan' library (-lasan). */ #undef HAVE_LIBASAN /* Define to 1 if you have the 'lsan' library (-llsan). */ #undef HAVE_LIBLSAN /* Define to 1 if you have the 'ubsan' library (-lubsan). */ #undef HAVE_LIBUBSAN /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_IF_ALG_H /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_TYPES_H /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_UHID_H /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_UINPUT_H /* Define to 1 if you have the 'rawmemchr' function. */ #undef HAVE_RAWMEMCHR /* Define to 1 if you have the header file. */ #undef HAVE_READLINE_READLINE_H /* Define to 1 if you have the header file. */ #undef HAVE_STDARG_H /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H /* Define to 1 if you have the header file. */ #undef HAVE_STDIO_H /* Define to 1 if you have the header file. */ #undef HAVE_STDLIB_H /* Define to 1 if you have the header file. */ #undef HAVE_STRINGS_H /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_RANDOM_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STAT_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TYPES_H /* Define to 1 if you have TMAP support. */ #undef HAVE_TMAP /* Define to 1 if udev is required */ #undef HAVE_UDEV /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H /* Define to 1 if you have the header file. */ #undef HAVE_VALGRIND_MEMCHECK_H /* Define to 1 if you have VCP support. */ #undef HAVE_VCP /* Define to the sub-directory where libtool stores uninstalled libraries. */ #undef LT_OBJDIR /* Directory for the mesh daemon storage files */ #undef MESH_STORAGEDIR /* Name of package */ #undef PACKAGE /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT /* Define to the full name of this package. */ #undef PACKAGE_NAME /* Define to the full name and version of this package. */ #undef PACKAGE_STRING /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME /* Define to the home page for this package. */ #undef PACKAGE_URL /* Define to the version of this package. */ #undef PACKAGE_VERSION /* Define to 1 if all of the C89 standard headers exist (not just the ones required in a freestanding environment). This macro is provided for backward compatibility; new code need not use it. */ #undef STDC_HEADERS /* Directory for the storage files */ #undef STORAGEDIR /* Version number of package */ #undef VERSION /* Define to the equivalent of the C99 'restrict' keyword, or to nothing if this is not supported. Do not define if restrict is supported only directly. */ #undef restrict /* Work around a bug in older versions of Sun C++, which did not #define __restrict__ or support _Restrict or __restrict__ even though the corresponding Sun C compiler ended up with "#define restrict _Restrict" or "#define restrict __restrict__" in the previous line. This workaround can be removed once we assume Oracle Developer Studio 12.5 (2016) or later. */ #if defined __SUNPRO_CC && !defined __RESTRICT && !defined __restrict__ # define _Restrict # define __restrict__ #endif bluez-5.87/PaxHeaders/btio0000644000000000000000000000005015221755530012540 xustar0020 atime=1783094113 20 ctime=1783094104 bluez-5.87/btio/0000755000000000000000000000000015221755530012276 5ustar00rootrootbluez-5.87/btio/PaxHeaders/btio.c0000644000000000000000000000005015221752714013717 xustar0020 atime=1783093318 20 ctime=1783094101 bluez-5.87/btio/btio.c0000644000000000000000000013670715221752714013416 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2009-2010 Marcel Holtmann * Copyright (C) 2009-2010 Nokia Corporation * Copyright 2023-2025 NXP * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/l2cap.h" #include "bluetooth/rfcomm.h" #include "bluetooth/sco.h" #include "bluetooth/iso.h" #include "btio.h" #ifndef BT_FLUSHABLE #define BT_FLUSHABLE 8 #endif #define ERROR_FAILED(gerr, str, err) \ g_set_error(gerr, BT_IO_ERROR, err, \ str ": %s (%d)", strerror(err), err) #define DEFAULT_DEFER_TIMEOUT 30 typedef enum { BT_IO_L2CAP, BT_IO_RFCOMM, BT_IO_SCO, BT_IO_ISO, BT_IO_INVALID, } BtIOType; struct set_opts { bdaddr_t src; bdaddr_t dst; BtIOType type; uint8_t src_type; uint8_t dst_type; int defer; int sec_level; uint8_t channel; uint16_t psm; uint16_t cid; uint16_t mtu; int imtu; uint16_t omtu; int central; uint8_t mode; int flushable; uint32_t priority; uint16_t voice; struct bt_iso_qos qos; struct bt_iso_base base; uint8_t bc_sid; uint8_t bc_num_bis; uint8_t bc_bis[ISO_MAX_NUM_BIS]; }; struct connect { BtIOConnect connect; gpointer user_data; GDestroyNotify destroy; bdaddr_t dst; }; struct accept { BtIOConnect connect; gpointer user_data; GDestroyNotify destroy; }; struct server { BtIOConnect connect; BtIOConfirm confirm; gpointer user_data; GDestroyNotify destroy; }; static BtIOType bt_io_get_type(GIOChannel *io, GError **gerr) { int sk = g_io_channel_unix_get_fd(io); int domain, proto, err; socklen_t len; domain = 0; len = sizeof(domain); err = getsockopt(sk, SOL_SOCKET, SO_DOMAIN, &domain, &len); if (err < 0) { ERROR_FAILED(gerr, "getsockopt(SO_DOMAIN)", errno); return BT_IO_INVALID; } if (domain != AF_BLUETOOTH) { g_set_error(gerr, BT_IO_ERROR, EINVAL, "BtIO socket domain not AF_BLUETOOTH"); return BT_IO_INVALID; } proto = 0; len = sizeof(proto); err = getsockopt(sk, SOL_SOCKET, SO_PROTOCOL, &proto, &len); if (err < 0) { ERROR_FAILED(gerr, "getsockopt(SO_PROTOCOL)", errno); return BT_IO_INVALID; } switch (proto) { case BTPROTO_RFCOMM: return BT_IO_RFCOMM; case BTPROTO_SCO: return BT_IO_SCO; case BTPROTO_L2CAP: return BT_IO_L2CAP; case BTPROTO_ISO: return BT_IO_ISO; default: g_set_error(gerr, BT_IO_ERROR, EINVAL, "Unknown BtIO socket type"); return BT_IO_INVALID; } } static void server_remove(struct server *server) { if (server->destroy) server->destroy(server->user_data); g_free(server); } static void connect_remove(struct connect *conn) { if (conn->destroy) conn->destroy(conn->user_data); g_free(conn); } static void accept_remove(struct accept *accept) { if (accept->destroy) accept->destroy(accept->user_data); g_free(accept); } static gboolean check_nval(GIOChannel *io) { struct pollfd fds; memset(&fds, 0, sizeof(fds)); fds.fd = g_io_channel_unix_get_fd(io); fds.events = POLLNVAL; if (poll(&fds, 1, 0) > 0 && (fds.revents & POLLNVAL)) return TRUE; return FALSE; } static gboolean accept_cb(GIOChannel *io, GIOCondition cond, gpointer user_data) { struct accept *accept = user_data; GError *gerr = NULL; /* If the user aborted this accept attempt */ if ((cond & G_IO_NVAL) || check_nval(io)) return FALSE; if (cond & (G_IO_HUP | G_IO_ERR)) { int err, sk_err, sock = g_io_channel_unix_get_fd(io); socklen_t len = sizeof(sk_err); if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &sk_err, &len) < 0) err = -errno; else err = -sk_err; if (err < 0) ERROR_FAILED(&gerr, "HUP or ERR on socket", -err); } accept->connect(io, gerr, accept->user_data); g_clear_error(&gerr); return FALSE; } static gboolean connect_cb(GIOChannel *io, GIOCondition cond, gpointer user_data) { struct connect *conn = user_data; GError *gerr = NULL; int err, sk_err, sock; socklen_t len = sizeof(sk_err); char addr[18]; /* If the user aborted this connect attempt */ if ((cond & G_IO_NVAL) || check_nval(io)) return FALSE; sock = g_io_channel_unix_get_fd(io); if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &sk_err, &len) < 0) err = -errno; else err = -sk_err; if (err < 0) { ba2str(&conn->dst, addr); g_set_error(&gerr, BT_IO_ERROR, -err, "connect to %s: %s (%d)", addr, strerror(-err), -err); } conn->connect(io, gerr, conn->user_data); g_clear_error(&gerr); return FALSE; } static gboolean server_cb(GIOChannel *io, GIOCondition cond, gpointer user_data) { struct server *server = user_data; int srv_sock, cli_sock; GIOChannel *cli_io; /* If the user closed the server */ if ((cond & G_IO_NVAL) || check_nval(io)) return FALSE; srv_sock = g_io_channel_unix_get_fd(io); if (cond & G_IO_ERR) { int err, sk_err; socklen_t len = sizeof(sk_err); if (getsockopt(srv_sock, SOL_SOCKET, SO_ERROR, &sk_err, &len) < 0) err = errno; else err = sk_err; if (server->connect) { GError *gerr = NULL; ERROR_FAILED(&gerr, "ERR on socket", err); server->connect(io, gerr, server->user_data); g_clear_error(&gerr); } return FALSE; } cli_sock = accept(srv_sock, NULL, NULL); if (cli_sock < 0) { if (errno == EBADFD || errno == EOPNOTSUPP) return FALSE; return TRUE; } cli_io = g_io_channel_unix_new(cli_sock); g_io_channel_set_close_on_unref(cli_io, TRUE); g_io_channel_set_flags(cli_io, G_IO_FLAG_NONBLOCK, NULL); if (server->confirm) server->confirm(cli_io, server->user_data); else server->connect(cli_io, NULL, server->user_data); g_io_channel_unref(cli_io); return TRUE; } static void server_add(GIOChannel *io, BtIOConnect connect, BtIOConfirm confirm, gpointer user_data, GDestroyNotify destroy) { struct server *server; GIOCondition cond; server = g_new0(struct server, 1); server->connect = connect; server->confirm = confirm; server->user_data = user_data; server->destroy = destroy; cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; g_io_add_watch_full(io, G_PRIORITY_HIGH, cond, server_cb, server, (GDestroyNotify) server_remove); } static void connect_add(GIOChannel *io, BtIOConnect connect, bdaddr_t dst, gpointer user_data, GDestroyNotify destroy) { struct connect *conn; GIOCondition cond; conn = g_new0(struct connect, 1); conn->connect = connect; conn->user_data = user_data; conn->destroy = destroy; conn->dst = dst; cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL; g_io_add_watch_full(io, G_PRIORITY_HIGH, cond, connect_cb, conn, (GDestroyNotify) connect_remove); } static void accept_add(GIOChannel *io, BtIOConnect connect, gpointer user_data, GDestroyNotify destroy) { struct accept *accept; GIOCondition cond; accept = g_new0(struct accept, 1); accept->connect = connect; accept->user_data = user_data; accept->destroy = destroy; cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL; g_io_add_watch_full(io, G_PRIORITY_HIGH, cond, accept_cb, accept, (GDestroyNotify) accept_remove); } static int l2cap_bind(int sock, const bdaddr_t *src, uint8_t src_type, uint16_t psm, uint16_t cid, GError **err) { struct sockaddr_l2 addr; memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; bacpy(&addr.l2_bdaddr, src); if (cid) addr.l2_cid = htobs(cid); else addr.l2_psm = htobs(psm); addr.l2_bdaddr_type = src_type; if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { int error = -errno; ERROR_FAILED(err, "l2cap_bind", errno); return error; } return 0; } static int l2cap_connect(int sock, const bdaddr_t *dst, uint8_t dst_type, uint16_t psm, uint16_t cid) { int err; struct sockaddr_l2 addr; memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; bacpy(&addr.l2_bdaddr, dst); if (cid) addr.l2_cid = htobs(cid); else addr.l2_psm = htobs(psm); addr.l2_bdaddr_type = dst_type; err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) return -errno; return 0; } static int l2cap_set_central(int sock, int central) { int flags; socklen_t len; len = sizeof(flags); if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, &len) < 0) return -errno; if (central) { if (flags & L2CAP_LM_MASTER) return 0; flags |= L2CAP_LM_MASTER; } else { if (!(flags & L2CAP_LM_MASTER)) return 0; flags &= ~L2CAP_LM_MASTER; } if (setsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, sizeof(flags)) < 0) return -errno; return 0; } static int rfcomm_set_central(int sock, int central) { int flags; socklen_t len; len = sizeof(flags); if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, &len) < 0) return -errno; if (central) { if (flags & RFCOMM_LM_MASTER) return 0; flags |= RFCOMM_LM_MASTER; } else { if (!(flags & RFCOMM_LM_MASTER)) return 0; flags &= ~RFCOMM_LM_MASTER; } if (setsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, sizeof(flags)) < 0) return -errno; return 0; } static int l2cap_set_lm(int sock, int level) { int lm_map[] = { 0, L2CAP_LM_AUTH, L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT, L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT | L2CAP_LM_SECURE, }, opt = lm_map[level]; if (setsockopt(sock, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0) return -errno; return 0; } static int rfcomm_set_lm(int sock, int level) { int lm_map[] = { 0, RFCOMM_LM_AUTH, RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT, RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT | RFCOMM_LM_SECURE, }, opt = lm_map[level]; if (setsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &opt, sizeof(opt)) < 0) return -errno; return 0; } static gboolean set_sec_level(int sock, BtIOType type, int level, GError **err) { struct bt_security sec; int ret; if (level < BT_SECURITY_LOW || level > BT_SECURITY_FIPS) { g_set_error(err, BT_IO_ERROR, EINVAL, "Valid security level range is %d-%d", BT_SECURITY_LOW, BT_SECURITY_HIGH); return FALSE; } memset(&sec, 0, sizeof(sec)); sec.level = level; if (setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec, sizeof(sec)) == 0) return TRUE; if (errno != ENOPROTOOPT) { ERROR_FAILED(err, "setsockopt(BT_SECURITY)", errno); return FALSE; } if (level == BT_SECURITY_FIPS) { g_set_error(err, BT_IO_ERROR, EINVAL, "setsockopt(LM): " "FIPS security level is not supported"); return FALSE; } if (type == BT_IO_L2CAP) ret = l2cap_set_lm(sock, level); else ret = rfcomm_set_lm(sock, level); if (ret < 0) { ERROR_FAILED(err, "setsockopt(LM)", -ret); return FALSE; } return TRUE; } static int l2cap_get_lm(int sock, int *sec_level) { int opt; socklen_t len; len = sizeof(opt); if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &opt, &len) < 0) return -errno; *sec_level = 0; if (opt & L2CAP_LM_AUTH) *sec_level = BT_SECURITY_LOW; if (opt & L2CAP_LM_ENCRYPT) *sec_level = BT_SECURITY_MEDIUM; if (opt & L2CAP_LM_SECURE) *sec_level = BT_SECURITY_HIGH; return 0; } static int rfcomm_get_lm(int sock, int *sec_level) { int opt; socklen_t len; len = sizeof(opt); if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &opt, &len) < 0) return -errno; *sec_level = 0; if (opt & RFCOMM_LM_AUTH) *sec_level = BT_SECURITY_LOW; if (opt & RFCOMM_LM_ENCRYPT) *sec_level = BT_SECURITY_MEDIUM; if (opt & RFCOMM_LM_SECURE) *sec_level = BT_SECURITY_HIGH; return 0; } static gboolean get_sec_level(int sock, BtIOType type, int *level, GError **err) { struct bt_security sec; socklen_t len; int ret; memset(&sec, 0, sizeof(sec)); len = sizeof(sec); if (getsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec, &len) == 0) { *level = sec.level; return TRUE; } if (errno != ENOPROTOOPT) { ERROR_FAILED(err, "getsockopt(BT_SECURITY)", errno); return FALSE; } if (type == BT_IO_L2CAP) ret = l2cap_get_lm(sock, level); else ret = rfcomm_get_lm(sock, level); if (ret < 0) { ERROR_FAILED(err, "getsockopt(LM)", -ret); return FALSE; } return TRUE; } static int l2cap_set_flushable(int sock, gboolean flushable) { int f; f = flushable; if (setsockopt(sock, SOL_BLUETOOTH, BT_FLUSHABLE, &f, sizeof(f)) < 0) return -errno; return 0; } static int set_priority(int sock, uint32_t prio) { if (setsockopt(sock, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio)) < 0) return -errno; return 0; } static gboolean get_key_size(int sock, int *size, GError **err) { struct bt_security sec; socklen_t len; memset(&sec, 0, sizeof(sec)); len = sizeof(sec); if (getsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec, &len) == 0) { *size = sec.key_size; return TRUE; } return FALSE; } static uint8_t mode_l2mode(uint8_t mode) { switch (mode) { case BT_IO_MODE_BASIC: return L2CAP_MODE_BASIC; case BT_IO_MODE_ERTM: return L2CAP_MODE_ERTM; case BT_IO_MODE_STREAMING: return L2CAP_MODE_STREAMING; default: return UINT8_MAX; } } static gboolean set_l2opts(int sock, int imtu, uint16_t omtu, uint8_t mode, GError **err) { struct l2cap_options l2o; socklen_t len; memset(&l2o, 0, sizeof(l2o)); len = sizeof(l2o); if (getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) { ERROR_FAILED(err, "getsockopt(L2CAP_OPTIONS)", errno); return FALSE; } if (imtu != -1) l2o.imtu = imtu; if (omtu) l2o.omtu = omtu; if (mode) { l2o.mode = mode_l2mode(mode); if (l2o.mode == UINT8_MAX) { ERROR_FAILED(err, "Unsupported mode", errno); return FALSE; } } if (setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, sizeof(l2o)) < 0) { ERROR_FAILED(err, "setsockopt(L2CAP_OPTIONS)", errno); return FALSE; } return TRUE; } static gboolean set_le_imtu(int sock, uint16_t imtu, GError **err) { if (setsockopt(sock, SOL_BLUETOOTH, BT_RCVMTU, &imtu, sizeof(imtu)) < 0) { ERROR_FAILED(err, "setsockopt(BT_RCVMTU)", errno); return FALSE; } return TRUE; } static gboolean set_le_mode(int sock, uint8_t mode, GError **err) { if (setsockopt(sock, SOL_BLUETOOTH, BT_MODE, &mode, sizeof(mode)) < 0) { ERROR_FAILED(err, "setsockopt(BT_MODE)", errno); return FALSE; } return TRUE; } static gboolean l2cap_set(int sock, uint8_t src_type, int sec_level, int imtu, uint16_t omtu, uint8_t mode, int central, int flushable, uint32_t priority, GError **err) { if (imtu != -1 || omtu || mode) { gboolean ret = FALSE; if (src_type == BDADDR_BREDR) { ret = set_l2opts(sock, imtu, omtu, mode, err); /* Back to default behavior in case the first call * fails: it may happen if the used kernel still * doesn't support auto-tuning the MTU. */ if (!ret && !imtu) { /* Free existing error */ g_error_free(*err); ret = set_l2opts(sock, -1, omtu, mode, err); } } else { if (imtu != -1) ret = set_le_imtu(sock, imtu, err); if (ret && mode) ret = set_le_mode(sock, mode, err); } if (!ret) return ret; } if (central >= 0 && l2cap_set_central(sock, central) < 0) { ERROR_FAILED(err, "l2cap_set_central", errno); return FALSE; } if (flushable >= 0 && l2cap_set_flushable(sock, flushable) < 0) { ERROR_FAILED(err, "l2cap_set_flushable", errno); return FALSE; } if (priority > 0 && set_priority(sock, priority) < 0) { ERROR_FAILED(err, "set_priority", errno); return FALSE; } if (sec_level && !set_sec_level(sock, BT_IO_L2CAP, sec_level, err)) return FALSE; return TRUE; } static int rfcomm_bind(int sock, const bdaddr_t *src, uint8_t channel, GError **err) { struct sockaddr_rc addr; memset(&addr, 0, sizeof(addr)); addr.rc_family = AF_BLUETOOTH; bacpy(&addr.rc_bdaddr, src); addr.rc_channel = channel; if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { int error = -errno; ERROR_FAILED(err, "rfcomm_bind", errno); return error; } return 0; } static int rfcomm_connect(int sock, const bdaddr_t *dst, uint8_t channel) { int err; struct sockaddr_rc addr; memset(&addr, 0, sizeof(addr)); addr.rc_family = AF_BLUETOOTH; bacpy(&addr.rc_bdaddr, dst); addr.rc_channel = channel; err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) return -errno; return 0; } static gboolean rfcomm_set(int sock, int sec_level, int central, GError **err) { if (sec_level && !set_sec_level(sock, BT_IO_RFCOMM, sec_level, err)) return FALSE; if (central >= 0 && rfcomm_set_central(sock, central) < 0) { ERROR_FAILED(err, "rfcomm_set_central", errno); return FALSE; } return TRUE; } static int sco_bind(int sock, const bdaddr_t *src, GError **err) { struct sockaddr_sco addr; memset(&addr, 0, sizeof(addr)); addr.sco_family = AF_BLUETOOTH; bacpy(&addr.sco_bdaddr, src); if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { int error = -errno; ERROR_FAILED(err, "sco_bind", errno); return error; } return 0; } static int iso_bind(int sock, bool server, const bdaddr_t *src, uint8_t src_type, const bdaddr_t *dst, uint8_t dst_type, uint8_t bc_sid, uint8_t num_bis, uint8_t *bis, GError **err) { struct sockaddr_iso *addr = NULL; size_t addr_len; int ret = 0; /* If this is an ISO listener and the destination address * is not BDADDR_ANY, the listener should be bound to the * broadcaster address */ if (server && bacmp(dst, BDADDR_ANY)) addr_len = sizeof(*addr) + sizeof(*addr->iso_bc); else addr_len = sizeof(*addr); addr = malloc(addr_len); if (!addr) return -ENOMEM; memset(addr, 0, addr_len); addr->iso_family = AF_BLUETOOTH; bacpy(&addr->iso_bdaddr, src); addr->iso_bdaddr_type = src_type; if (addr_len > sizeof(*addr)) { bacpy(&addr->iso_bc->bc_bdaddr, dst); addr->iso_bc->bc_bdaddr_type = dst_type; addr->iso_bc->bc_sid = bc_sid; addr->iso_bc->bc_num_bis = num_bis; memcpy(addr->iso_bc->bc_bis, bis, addr->iso_bc->bc_num_bis); } if (!bind(sock, (struct sockaddr *)addr, addr_len)) goto done; ret = -errno; ERROR_FAILED(err, "iso_bind", errno); done: free(addr); return ret; } static int sco_connect(int sock, const bdaddr_t *dst) { struct sockaddr_sco addr; int err; memset(&addr, 0, sizeof(addr)); addr.sco_family = AF_BLUETOOTH; bacpy(&addr.sco_bdaddr, dst); err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) return -errno; return 0; } static int iso_connect(int sock, const bdaddr_t *dst, uint8_t dst_type) { struct sockaddr_iso addr; int err; memset(&addr, 0, sizeof(addr)); addr.iso_family = AF_BLUETOOTH; bacpy(&addr.iso_bdaddr, dst); addr.iso_bdaddr_type = dst_type; err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) return -errno; return 0; } static gboolean sco_set(int sock, uint16_t mtu, uint16_t voice, GError **err) { struct sco_options sco_opt; struct bt_voice bt_voice; socklen_t len; if (!mtu) goto voice; len = sizeof(sco_opt); memset(&sco_opt, 0, len); if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) { ERROR_FAILED(err, "getsockopt(SCO_OPTIONS)", errno); return FALSE; } sco_opt.mtu = mtu; if (setsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, sizeof(sco_opt)) < 0) { ERROR_FAILED(err, "setsockopt(SCO_OPTIONS)", errno); return FALSE; } voice: if (!voice) return TRUE; memset(&bt_voice, 0, sizeof(bt_voice)); bt_voice.setting = voice; if (setsockopt(sock, SOL_BLUETOOTH, BT_VOICE, &bt_voice, sizeof(bt_voice)) < 0) { ERROR_FAILED(err, "setsockopt(BT_VOICE)", errno); return FALSE; } return TRUE; } static gboolean iso_set_qos(int sock, struct bt_iso_qos *qos, GError **err) { if (setsockopt(sock, SOL_BLUETOOTH, BT_ISO_QOS, qos, sizeof(*qos)) < 0) { ERROR_FAILED(err, "setsockopt(BT_ISO_QOS)", errno); return FALSE; } return TRUE; } static gboolean iso_set_base(int sock, struct bt_iso_base *base, GError **err) { if (setsockopt(sock, SOL_BLUETOOTH, BT_ISO_BASE, base->base, base->base_len) < 0) { ERROR_FAILED(err, "setsockopt(BT_ISO_BASE)", errno); return FALSE; } return TRUE; } static gboolean parse_set_opts(struct set_opts *opts, GError **err, BtIOOption opt1, va_list args) { BtIOOption opt = opt1; const char *str; memset(opts, 0, sizeof(*opts)); /* Set defaults */ opts->type = BT_IO_SCO; opts->defer = DEFAULT_DEFER_TIMEOUT; opts->central = -1; opts->mode = L2CAP_MODE_BASIC; opts->flushable = -1; opts->priority = 0; opts->src_type = BDADDR_BREDR; opts->dst_type = BDADDR_BREDR; opts->imtu = -1; while (opt != BT_IO_OPT_INVALID) { switch (opt) { case BT_IO_OPT_SOURCE: str = va_arg(args, const char *); str2ba(str, &opts->src); break; case BT_IO_OPT_SOURCE_BDADDR: bacpy(&opts->src, va_arg(args, const bdaddr_t *)); break; case BT_IO_OPT_SOURCE_TYPE: opts->src_type = va_arg(args, int); break; case BT_IO_OPT_DEST: str2ba(va_arg(args, const char *), &opts->dst); break; case BT_IO_OPT_DEST_BDADDR: bacpy(&opts->dst, va_arg(args, const bdaddr_t *)); break; case BT_IO_OPT_DEST_TYPE: opts->dst_type = va_arg(args, int); break; case BT_IO_OPT_DEFER_TIMEOUT: opts->defer = va_arg(args, int); break; case BT_IO_OPT_SEC_LEVEL: opts->sec_level = va_arg(args, int); break; case BT_IO_OPT_CHANNEL: opts->type = BT_IO_RFCOMM; opts->channel = va_arg(args, int); break; case BT_IO_OPT_PSM: opts->type = BT_IO_L2CAP; opts->psm = va_arg(args, int); break; case BT_IO_OPT_CID: opts->type = BT_IO_L2CAP; opts->cid = va_arg(args, int); break; case BT_IO_OPT_MTU: opts->mtu = va_arg(args, int); opts->imtu = opts->mtu; opts->omtu = opts->mtu; break; case BT_IO_OPT_OMTU: opts->omtu = va_arg(args, int); if (!opts->mtu) opts->mtu = opts->omtu; break; case BT_IO_OPT_IMTU: opts->imtu = va_arg(args, int); if (!opts->mtu) opts->mtu = opts->imtu; break; case BT_IO_OPT_CENTRAL: opts->central = va_arg(args, gboolean); break; case BT_IO_OPT_MODE: opts->mode = va_arg(args, int); if (opts->mode == BT_IO_MODE_ISO) { opts->type = BT_IO_ISO; if (opts->src_type == BDADDR_BREDR) opts->src_type = BDADDR_LE_PUBLIC; if (opts->dst_type == BDADDR_BREDR) opts->dst_type = BDADDR_LE_PUBLIC; } break; case BT_IO_OPT_FLUSHABLE: opts->flushable = va_arg(args, gboolean); break; case BT_IO_OPT_PRIORITY: opts->priority = va_arg(args, int); break; case BT_IO_OPT_VOICE: opts->voice = va_arg(args, int); break; case BT_IO_OPT_QOS: opts->qos = *va_arg(args, struct bt_iso_qos *); break; case BT_IO_OPT_BASE: opts->base = *va_arg(args, struct bt_iso_base *); break; case BT_IO_OPT_ISO_BC_SID: opts->bc_sid = va_arg(args, int); break; case BT_IO_OPT_ISO_BC_NUM_BIS: opts->bc_num_bis = va_arg(args, int); break; case BT_IO_OPT_ISO_BC_BIS: memcpy(opts->bc_bis, va_arg(args, uint8_t *), opts->bc_num_bis); break; case BT_IO_OPT_INVALID: case BT_IO_OPT_KEY_SIZE: case BT_IO_OPT_SOURCE_CHANNEL: case BT_IO_OPT_DEST_CHANNEL: case BT_IO_OPT_HANDLE: case BT_IO_OPT_CLASS: case BT_IO_OPT_PHY: default: g_set_error(err, BT_IO_ERROR, EINVAL, "Unknown option %d", opt); return FALSE; } opt = va_arg(args, int); } return TRUE; } static gboolean get_src(int sock, void *src, socklen_t len, GError **err) { socklen_t olen; memset(src, 0, len); olen = len; if (getsockname(sock, src, &olen) < 0) { ERROR_FAILED(err, "getsockname", errno); return FALSE; } return TRUE; } static gboolean get_dst(int sock, void *dst, socklen_t len, GError **err) { socklen_t olen; memset(dst, 0, len); olen = len; if (getpeername(sock, dst, &olen) < 0) { ERROR_FAILED(err, "getpeername", errno); return FALSE; } return TRUE; } static int l2cap_get_info(int sock, uint16_t *handle, uint8_t *dev_class) { struct l2cap_conninfo info; socklen_t len; len = sizeof(info); if (getsockopt(sock, SOL_L2CAP, L2CAP_CONNINFO, &info, &len) < 0) return -errno; if (handle) *handle = info.hci_handle; if (dev_class) memcpy(dev_class, info.dev_class, 3); return 0; } static int l2cap_get_flushable(int sock, gboolean *flushable) { int f; socklen_t len; f = 0; len = sizeof(f); if (getsockopt(sock, SOL_BLUETOOTH, BT_FLUSHABLE, &f, &len) < 0) return -errno; if (f) *flushable = TRUE; else *flushable = FALSE; return 0; } static int get_priority(int sock, uint32_t *prio) { socklen_t len; len = sizeof(*prio); if (getsockopt(sock, SOL_SOCKET, SO_PRIORITY, prio, &len) < 0) return -errno; return 0; } static int get_phy(int sock, uint32_t *phy) { socklen_t len; len = sizeof(*phy); if (getsockopt(sock, SOL_BLUETOOTH, BT_PHY, phy, &len) < 0) return -errno; return 0; } static int get_le_imtu(int sock, uint16_t *mtu) { socklen_t len; len = sizeof(*mtu); if (getsockopt(sock, SOL_BLUETOOTH, BT_RCVMTU, mtu, &len) < 0) return -errno; return 0; } static int get_le_mode(int sock, uint8_t *mode) { socklen_t len; len = sizeof(*mode); if (getsockopt(sock, SOL_BLUETOOTH, BT_MODE, mode, &len) < 0) return -errno; return 0; } static gboolean l2cap_get(int sock, GError **err, BtIOOption opt1, va_list args) { BtIOOption opt = opt1; struct sockaddr_l2 src, dst; struct l2cap_options l2o; int flags; uint8_t dev_class[3]; uint16_t handle = 0; socklen_t len; gboolean flushable = FALSE, have_dst = FALSE; uint32_t priority, phy; if (!get_src(sock, &src, sizeof(src), err)) return FALSE; memset(&l2o, 0, sizeof(l2o)); if (src.l2_bdaddr_type != BDADDR_BREDR) { if (get_le_imtu(sock, &l2o.imtu) == 0) { /* Older kernels may not support BT_MODE */ get_le_mode(sock, &l2o.mode); goto parse_opts; } /* Non-LE CoC enabled kernels will return one of these * in which case we need to fall back to L2CAP_OPTIONS. */ if (errno != EPROTONOSUPPORT && errno != ENOPROTOOPT) { ERROR_FAILED(err, "getsockopt(BT_RCVMTU)", errno); return FALSE; } } len = sizeof(l2o); if (getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) { ERROR_FAILED(err, "getsockopt(L2CAP_OPTIONS)", errno); return FALSE; } parse_opts: while (opt != BT_IO_OPT_INVALID) { switch (opt) { case BT_IO_OPT_SOURCE: ba2str(&src.l2_bdaddr, va_arg(args, char *)); break; case BT_IO_OPT_SOURCE_BDADDR: bacpy(va_arg(args, bdaddr_t *), &src.l2_bdaddr); break; case BT_IO_OPT_DEST: if (!have_dst) have_dst = get_dst(sock, &dst, sizeof(dst), err); if (!have_dst) return FALSE; ba2str(&dst.l2_bdaddr, va_arg(args, char *)); break; case BT_IO_OPT_DEST_BDADDR: if (!have_dst) have_dst = get_dst(sock, &dst, sizeof(dst), err); if (!have_dst) return FALSE; bacpy(va_arg(args, bdaddr_t *), &dst.l2_bdaddr); break; case BT_IO_OPT_DEST_TYPE: if (!have_dst) have_dst = get_dst(sock, &dst, sizeof(dst), err); if (!have_dst) return FALSE; *(va_arg(args, uint8_t *)) = dst.l2_bdaddr_type; break; case BT_IO_OPT_DEFER_TIMEOUT: len = sizeof(int); if (getsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, va_arg(args, int *), &len) < 0) { ERROR_FAILED(err, "getsockopt(DEFER_SETUP)", errno); return FALSE; } break; case BT_IO_OPT_SEC_LEVEL: if (!get_sec_level(sock, BT_IO_L2CAP, va_arg(args, int *), err)) return FALSE; break; case BT_IO_OPT_KEY_SIZE: if (!get_key_size(sock, va_arg(args, int *), err)) return FALSE; break; case BT_IO_OPT_PSM: if (src.l2_psm) { *(va_arg(args, uint16_t *)) = btohs(src.l2_psm); break; } if (!have_dst) have_dst = get_dst(sock, &dst, sizeof(dst), err); if (!have_dst) return FALSE; *(va_arg(args, uint16_t *)) = btohs(dst.l2_psm); break; case BT_IO_OPT_CID: if (src.l2_cid) { *(va_arg(args, uint16_t *)) = btohs(src.l2_cid); break; } if (!have_dst) have_dst = get_dst(sock, &dst, sizeof(dst), err); if (!have_dst) return FALSE; *(va_arg(args, uint16_t *)) = btohs(dst.l2_cid); break; case BT_IO_OPT_OMTU: if (src.l2_bdaddr_type == BDADDR_BREDR) { *(va_arg(args, uint16_t *)) = l2o.omtu; break; } len = sizeof(l2o.omtu); if (getsockopt(sock, SOL_BLUETOOTH, BT_SNDMTU, &l2o.omtu, &len) < 0) { ERROR_FAILED(err, "getsockopt(BT_SNDMTU)", errno); return FALSE; } *(va_arg(args, uint16_t *)) = l2o.omtu; break; case BT_IO_OPT_IMTU: *(va_arg(args, uint16_t *)) = l2o.imtu; break; case BT_IO_OPT_CENTRAL: len = sizeof(flags); if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, &len) < 0) { ERROR_FAILED(err, "getsockopt(L2CAP_LM)", errno); return FALSE; } *(va_arg(args, gboolean *)) = (flags & L2CAP_LM_MASTER) ? TRUE : FALSE; break; case BT_IO_OPT_HANDLE: if (l2cap_get_info(sock, &handle, dev_class) < 0) { ERROR_FAILED(err, "L2CAP_CONNINFO", errno); return FALSE; } *(va_arg(args, uint16_t *)) = handle; break; case BT_IO_OPT_CLASS: if (l2cap_get_info(sock, &handle, dev_class) < 0) { ERROR_FAILED(err, "L2CAP_CONNINFO", errno); return FALSE; } memcpy(va_arg(args, uint8_t *), dev_class, 3); break; case BT_IO_OPT_MODE: *(va_arg(args, uint8_t *)) = l2o.mode; break; case BT_IO_OPT_FLUSHABLE: if (l2cap_get_flushable(sock, &flushable) < 0) { ERROR_FAILED(err, "get_flushable", errno); return FALSE; } *(va_arg(args, gboolean *)) = flushable; break; case BT_IO_OPT_PRIORITY: if (get_priority(sock, &priority) < 0) { ERROR_FAILED(err, "get_priority", errno); return FALSE; } *(va_arg(args, uint32_t *)) = priority; break; case BT_IO_OPT_PHY: if (get_phy(sock, &phy) < 0) { ERROR_FAILED(err, "get_phy", errno); return FALSE; } *(va_arg(args, uint32_t *)) = phy; break; case BT_IO_OPT_INVALID: case BT_IO_OPT_SOURCE_TYPE: case BT_IO_OPT_CHANNEL: case BT_IO_OPT_SOURCE_CHANNEL: case BT_IO_OPT_DEST_CHANNEL: case BT_IO_OPT_MTU: case BT_IO_OPT_VOICE: case BT_IO_OPT_QOS: case BT_IO_OPT_BASE: case BT_IO_OPT_ISO_BC_SID: case BT_IO_OPT_ISO_BC_NUM_BIS: case BT_IO_OPT_ISO_BC_BIS: default: g_set_error(err, BT_IO_ERROR, EINVAL, "Unknown option %d", opt); return FALSE; } opt = va_arg(args, int); } return TRUE; } static int rfcomm_get_info(int sock, uint16_t *handle, uint8_t *dev_class) { struct rfcomm_conninfo info; socklen_t len; len = sizeof(info); if (getsockopt(sock, SOL_RFCOMM, RFCOMM_CONNINFO, &info, &len) < 0) return -errno; if (handle) *handle = info.hci_handle; if (dev_class) memcpy(dev_class, info.dev_class, 3); return 0; } static gboolean rfcomm_get(int sock, GError **err, BtIOOption opt1, va_list args) { BtIOOption opt = opt1; struct sockaddr_rc src, dst; gboolean have_dst = FALSE; int flags; socklen_t len; uint8_t dev_class[3]; uint16_t handle = 0; uint32_t phy; if (!get_src(sock, &src, sizeof(src), err)) return FALSE; while (opt != BT_IO_OPT_INVALID) { switch (opt) { case BT_IO_OPT_SOURCE: ba2str(&src.rc_bdaddr, va_arg(args, char *)); break; case BT_IO_OPT_SOURCE_BDADDR: bacpy(va_arg(args, bdaddr_t *), &src.rc_bdaddr); break; case BT_IO_OPT_DEST: if (!have_dst) have_dst = get_dst(sock, &dst, sizeof(dst), err); if (!have_dst) return FALSE; ba2str(&dst.rc_bdaddr, va_arg(args, char *)); break; case BT_IO_OPT_DEST_BDADDR: if (!have_dst) have_dst = get_dst(sock, &dst, sizeof(dst), err); if (!have_dst) return FALSE; bacpy(va_arg(args, bdaddr_t *), &dst.rc_bdaddr); break; case BT_IO_OPT_DEFER_TIMEOUT: len = sizeof(int); if (getsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, va_arg(args, int *), &len) < 0) { ERROR_FAILED(err, "getsockopt(DEFER_SETUP)", errno); return FALSE; } break; case BT_IO_OPT_SEC_LEVEL: if (!get_sec_level(sock, BT_IO_RFCOMM, va_arg(args, int *), err)) return FALSE; break; case BT_IO_OPT_CHANNEL: if (src.rc_channel) { *(va_arg(args, uint8_t *)) = src.rc_channel; break; } if (!have_dst) have_dst = get_dst(sock, &dst, sizeof(dst), err); if (!have_dst) return FALSE; *(va_arg(args, uint8_t *)) = dst.rc_channel; break; case BT_IO_OPT_SOURCE_CHANNEL: *(va_arg(args, uint8_t *)) = src.rc_channel; break; case BT_IO_OPT_DEST_CHANNEL: if (!have_dst) have_dst = get_dst(sock, &dst, sizeof(dst), err); if (!have_dst) return FALSE; *(va_arg(args, uint8_t *)) = dst.rc_channel; break; case BT_IO_OPT_CENTRAL: len = sizeof(flags); if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, &len) < 0) { ERROR_FAILED(err, "getsockopt(RFCOMM_LM)", errno); return FALSE; } *(va_arg(args, gboolean *)) = (flags & RFCOMM_LM_MASTER) ? TRUE : FALSE; break; case BT_IO_OPT_HANDLE: if (rfcomm_get_info(sock, &handle, dev_class) < 0) { ERROR_FAILED(err, "RFCOMM_CONNINFO", errno); return FALSE; } *(va_arg(args, uint16_t *)) = handle; break; case BT_IO_OPT_CLASS: if (rfcomm_get_info(sock, &handle, dev_class) < 0) { ERROR_FAILED(err, "RFCOMM_CONNINFO", errno); return FALSE; } memcpy(va_arg(args, uint8_t *), dev_class, 3); break; case BT_IO_OPT_PHY: if (get_phy(sock, &phy) < 0) { ERROR_FAILED(err, "get_phy", errno); return FALSE; } *(va_arg(args, uint32_t *)) = phy; break; case BT_IO_OPT_SOURCE_TYPE: case BT_IO_OPT_DEST_TYPE: case BT_IO_OPT_KEY_SIZE: case BT_IO_OPT_PSM: case BT_IO_OPT_CID: case BT_IO_OPT_MTU: case BT_IO_OPT_OMTU: case BT_IO_OPT_IMTU: case BT_IO_OPT_MODE: case BT_IO_OPT_FLUSHABLE: case BT_IO_OPT_PRIORITY: case BT_IO_OPT_VOICE: case BT_IO_OPT_QOS: case BT_IO_OPT_BASE: case BT_IO_OPT_ISO_BC_SID: case BT_IO_OPT_ISO_BC_NUM_BIS: case BT_IO_OPT_ISO_BC_BIS: case BT_IO_OPT_INVALID: default: g_set_error(err, BT_IO_ERROR, EINVAL, "Unknown option %d", opt); return FALSE; } opt = va_arg(args, int); } return TRUE; } static int sco_get_info(int sock, uint16_t *handle, uint8_t *dev_class) { struct sco_conninfo info; socklen_t len; len = sizeof(info); if (getsockopt(sock, SOL_SCO, SCO_CONNINFO, &info, &len) < 0) return -errno; if (handle) *handle = info.hci_handle; if (dev_class) memcpy(dev_class, info.dev_class, 3); return 0; } static gboolean sco_get(int sock, GError **err, BtIOOption opt1, va_list args) { BtIOOption opt = opt1; struct sockaddr_sco src, dst; struct sco_options sco_opt; socklen_t len; uint8_t dev_class[3]; uint16_t handle = 0; uint32_t phy; len = sizeof(sco_opt); memset(&sco_opt, 0, len); if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) { ERROR_FAILED(err, "getsockopt(SCO_OPTIONS)", errno); return FALSE; } if (!get_src(sock, &src, sizeof(src), err)) return FALSE; if (!get_dst(sock, &dst, sizeof(dst), err)) return FALSE; while (opt != BT_IO_OPT_INVALID) { switch (opt) { case BT_IO_OPT_SOURCE: ba2str(&src.sco_bdaddr, va_arg(args, char *)); break; case BT_IO_OPT_SOURCE_BDADDR: bacpy(va_arg(args, bdaddr_t *), &src.sco_bdaddr); break; case BT_IO_OPT_DEST: ba2str(&dst.sco_bdaddr, va_arg(args, char *)); break; case BT_IO_OPT_DEST_BDADDR: bacpy(va_arg(args, bdaddr_t *), &dst.sco_bdaddr); break; case BT_IO_OPT_MTU: case BT_IO_OPT_IMTU: case BT_IO_OPT_OMTU: *(va_arg(args, uint16_t *)) = sco_opt.mtu; break; case BT_IO_OPT_HANDLE: if (sco_get_info(sock, &handle, dev_class) < 0) { ERROR_FAILED(err, "SCO_CONNINFO", errno); return FALSE; } *(va_arg(args, uint16_t *)) = handle; break; case BT_IO_OPT_CLASS: if (sco_get_info(sock, &handle, dev_class) < 0) { ERROR_FAILED(err, "SCO_CONNINFO", errno); return FALSE; } memcpy(va_arg(args, uint8_t *), dev_class, 3); break; case BT_IO_OPT_PHY: if (get_phy(sock, &phy) < 0) { ERROR_FAILED(err, "get_phy", errno); return FALSE; } *(va_arg(args, uint32_t *)) = phy; break; case BT_IO_OPT_SOURCE_TYPE: case BT_IO_OPT_DEST_TYPE: case BT_IO_OPT_DEFER_TIMEOUT: case BT_IO_OPT_SEC_LEVEL: case BT_IO_OPT_KEY_SIZE: case BT_IO_OPT_CHANNEL: case BT_IO_OPT_SOURCE_CHANNEL: case BT_IO_OPT_DEST_CHANNEL: case BT_IO_OPT_PSM: case BT_IO_OPT_CID: case BT_IO_OPT_CENTRAL: case BT_IO_OPT_MODE: case BT_IO_OPT_FLUSHABLE: case BT_IO_OPT_PRIORITY: case BT_IO_OPT_VOICE: case BT_IO_OPT_QOS: case BT_IO_OPT_BASE: case BT_IO_OPT_ISO_BC_SID: case BT_IO_OPT_ISO_BC_NUM_BIS: case BT_IO_OPT_ISO_BC_BIS: case BT_IO_OPT_INVALID: default: g_set_error(err, BT_IO_ERROR, EINVAL, "Unknown option %d", opt); return FALSE; } opt = va_arg(args, int); } return TRUE; } static bool get_bc_sid(int sock, uint8_t *sid, GError **err) { struct { struct sockaddr_iso iso; struct sockaddr_iso_bc bc; } addr; socklen_t olen; olen = sizeof(addr); memset(&addr, 0, olen); if (getpeername(sock, (void *)&addr, &olen) < 0) { ERROR_FAILED(err, "getpeername", errno); return false; } if (olen != sizeof(addr)) { ERROR_FAILED(err, "getpeername: size mismatch", EINVAL); return false; } *sid = addr.iso.iso_bc->bc_sid; return true; } static gboolean iso_get(int sock, GError **err, BtIOOption opt1, va_list args) { BtIOOption opt = opt1; struct sockaddr_iso src, dst; struct bt_iso_qos qos; struct bt_iso_base base; socklen_t len; uint32_t phy; uint8_t bc_sid; len = sizeof(qos); memset(&qos, 0, len); if (getsockopt(sock, SOL_BLUETOOTH, BT_ISO_QOS, &qos, &len) < 0) { ERROR_FAILED(err, "getsockopt(BT_ISO_QOS)", errno); return FALSE; } len = BASE_MAX_LENGTH; if (getsockopt(sock, SOL_BLUETOOTH, BT_ISO_BASE, &base.base, &len) < 0) { ERROR_FAILED(err, "getsockopt(BT_ISO_BASE)", errno); return FALSE; } base.base_len = len; if (!get_src(sock, &src, sizeof(src), err)) return FALSE; if (!get_dst(sock, &dst, sizeof(dst), err)) return FALSE; while (opt != BT_IO_OPT_INVALID) { switch (opt) { case BT_IO_OPT_SOURCE: ba2str(&src.iso_bdaddr, va_arg(args, char *)); break; case BT_IO_OPT_SOURCE_BDADDR: bacpy(va_arg(args, bdaddr_t *), &src.iso_bdaddr); break; case BT_IO_OPT_SOURCE_TYPE: *(va_arg(args, uint8_t *)) = src.iso_bdaddr_type; break; case BT_IO_OPT_DEST: ba2str(&dst.iso_bdaddr, va_arg(args, char *)); break; case BT_IO_OPT_DEST_BDADDR: bacpy(va_arg(args, bdaddr_t *), &dst.iso_bdaddr); break; case BT_IO_OPT_DEST_TYPE: *(va_arg(args, uint8_t *)) = dst.iso_bdaddr_type; break; case BT_IO_OPT_MTU: *(va_arg(args, uint16_t *)) = qos.ucast.out.sdu; break; case BT_IO_OPT_IMTU: *(va_arg(args, uint16_t *)) = qos.ucast.in.sdu; break; case BT_IO_OPT_OMTU: *(va_arg(args, uint16_t *)) = qos.ucast.out.sdu; break; case BT_IO_OPT_PHY: if (get_phy(sock, &phy) < 0) { ERROR_FAILED(err, "get_phy", errno); return FALSE; } *(va_arg(args, uint32_t *)) = phy; break; case BT_IO_OPT_QOS: *(va_arg(args, struct bt_iso_qos *)) = qos; break; case BT_IO_OPT_BASE: *(va_arg(args, struct bt_iso_base *)) = base; break; case BT_IO_OPT_ISO_BC_SID: if (!get_bc_sid(sock, &bc_sid, err)) return FALSE; *(va_arg(args, uint8_t *)) = bc_sid; break; case BT_IO_OPT_HANDLE: case BT_IO_OPT_CLASS: case BT_IO_OPT_DEFER_TIMEOUT: case BT_IO_OPT_SEC_LEVEL: case BT_IO_OPT_KEY_SIZE: case BT_IO_OPT_CHANNEL: case BT_IO_OPT_SOURCE_CHANNEL: case BT_IO_OPT_DEST_CHANNEL: case BT_IO_OPT_PSM: case BT_IO_OPT_CID: case BT_IO_OPT_CENTRAL: case BT_IO_OPT_MODE: case BT_IO_OPT_FLUSHABLE: case BT_IO_OPT_PRIORITY: case BT_IO_OPT_VOICE: case BT_IO_OPT_ISO_BC_NUM_BIS: case BT_IO_OPT_ISO_BC_BIS: case BT_IO_OPT_INVALID: default: g_set_error(err, BT_IO_ERROR, EINVAL, "Unknown option %d", opt); return FALSE; } opt = va_arg(args, int); } return TRUE; } static gboolean get_valist(GIOChannel *io, BtIOType type, GError **err, BtIOOption opt1, va_list args) { int sock; sock = g_io_channel_unix_get_fd(io); switch (type) { case BT_IO_L2CAP: return l2cap_get(sock, err, opt1, args); case BT_IO_RFCOMM: return rfcomm_get(sock, err, opt1, args); case BT_IO_SCO: return sco_get(sock, err, opt1, args); case BT_IO_ISO: return iso_get(sock, err, opt1, args); case BT_IO_INVALID: default: g_set_error(err, BT_IO_ERROR, EINVAL, "Unknown BtIO type %d", type); return FALSE; } } gboolean bt_io_accept(GIOChannel *io, BtIOConnect connect, gpointer user_data, GDestroyNotify destroy, GError **err) { int sock; char c; struct pollfd pfd; sock = g_io_channel_unix_get_fd(io); memset(&pfd, 0, sizeof(pfd)); pfd.fd = sock; pfd.events = POLLOUT; if (poll(&pfd, 1, 0) < 0) { ERROR_FAILED(err, "poll", errno); return FALSE; } if (!(pfd.revents & POLLOUT)) { if (read(sock, &c, 1) < 0) { ERROR_FAILED(err, "read", errno); return FALSE; } } accept_add(io, connect, user_data, destroy); return TRUE; } gboolean bt_io_bcast_accept(GIOChannel *io, BtIOConnect connect, gpointer user_data, GDestroyNotify destroy, GError * *err, BtIOOption opt1, ...) { int sock; char c; va_list args; struct sockaddr_iso *addr = NULL; uint8_t bc_num_bis = 0; uint8_t bc_bis[ISO_MAX_NUM_BIS] = {0}; BtIOOption opt = opt1; va_start(args, opt1); while (opt != BT_IO_OPT_INVALID) { if (opt == BT_IO_OPT_ISO_BC_NUM_BIS) { bc_num_bis = va_arg(args, int); } else if (opt == BT_IO_OPT_ISO_BC_BIS) { memcpy(bc_bis, va_arg(args, uint8_t *), bc_num_bis); } else { g_set_error(err, BT_IO_ERROR, EINVAL, "Invalid option %d", opt); break; } opt = va_arg(args, int); } va_end(args); if (*err) return FALSE; sock = g_io_channel_unix_get_fd(io); if (bc_num_bis) { addr = malloc(sizeof(*addr) + sizeof(*addr->iso_bc)); if (!addr) { ERROR_FAILED(err, "poll", ENOMEM); return FALSE; } memset(addr, 0, sizeof(*addr) + sizeof(*addr->iso_bc)); addr->iso_family = AF_BLUETOOTH; addr->iso_bc->bc_num_bis = bc_num_bis; memcpy(addr->iso_bc->bc_bis, bc_bis, addr->iso_bc->bc_num_bis); if (bind(sock, (struct sockaddr *)addr, sizeof(*addr) + sizeof(*addr->iso_bc)) < 0) { ERROR_FAILED(err, "bind", errno); } free(addr); if (*err) return FALSE; } if (read(sock, &c, 1) < 0) { ERROR_FAILED(err, "read", errno); return FALSE; } server_add(io, connect, NULL, user_data, destroy); return TRUE; } gboolean bt_io_set(GIOChannel *io, GError **err, BtIOOption opt1, ...) { va_list args; gboolean ret; struct set_opts opts; int sock; BtIOType type; va_start(args, opt1); ret = parse_set_opts(&opts, err, opt1, args); va_end(args); if (!ret) return ret; type = bt_io_get_type(io, err); if (type == BT_IO_INVALID) return FALSE; sock = g_io_channel_unix_get_fd(io); switch (type) { case BT_IO_L2CAP: return l2cap_set(sock, opts.src_type, opts.sec_level, opts.imtu, opts.omtu, opts.mode, opts.central, opts.flushable, opts.priority, err); case BT_IO_RFCOMM: return rfcomm_set(sock, opts.sec_level, opts.central, err); case BT_IO_SCO: return sco_set(sock, opts.mtu, opts.voice, err); case BT_IO_ISO: return iso_set_qos(sock, &opts.qos, err); case BT_IO_INVALID: default: g_set_error(err, BT_IO_ERROR, EINVAL, "Unknown BtIO type %d", type); return FALSE; } } gboolean bt_io_get(GIOChannel *io, GError **err, BtIOOption opt1, ...) { va_list args; gboolean ret; BtIOType type; type = bt_io_get_type(io, err); if (type == BT_IO_INVALID) return FALSE; va_start(args, opt1); ret = get_valist(io, type, err, opt1, args); va_end(args); return ret; } static GIOChannel *create_io(gboolean server, struct set_opts *opts, GError **err) { int sock; GIOChannel *io; switch (opts->type) { case BT_IO_L2CAP: sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (sock < 0) { ERROR_FAILED(err, "socket(SEQPACKET, L2CAP)", errno); return NULL; } if (l2cap_bind(sock, &opts->src, opts->src_type, server ? opts->psm : 0, opts->cid, err) < 0) goto failed; if (!l2cap_set(sock, opts->src_type, opts->sec_level, opts->imtu, opts->omtu, opts->mode, opts->central, opts->flushable, opts->priority, err)) goto failed; break; case BT_IO_RFCOMM: sock = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); if (sock < 0) { ERROR_FAILED(err, "socket(STREAM, RFCOMM)", errno); return NULL; } if (rfcomm_bind(sock, &opts->src, server ? opts->channel : 0, err) < 0) goto failed; if (!rfcomm_set(sock, opts->sec_level, opts->central, err)) goto failed; break; case BT_IO_SCO: sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); if (sock < 0) { ERROR_FAILED(err, "socket(SEQPACKET, SCO)", errno); return NULL; } if (sco_bind(sock, &opts->src, err) < 0) goto failed; if (!sco_set(sock, opts->mtu, opts->voice, err)) goto failed; break; case BT_IO_ISO: sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO); if (sock < 0) { ERROR_FAILED(err, "socket(SEQPACKET, ISO)", errno); return NULL; } if (iso_bind(sock, server, &opts->src, opts->src_type, &opts->dst, opts->dst_type, opts->bc_sid, opts->bc_num_bis, opts->bc_bis, err) < 0) goto failed; if (!iso_set_qos(sock, &opts->qos, err)) goto failed; if (opts->base.base_len) if (!iso_set_base(sock, &opts->base, err)) goto failed; break; case BT_IO_INVALID: default: g_set_error(err, BT_IO_ERROR, EINVAL, "Unknown BtIO type %d", opts->type); return NULL; } io = g_io_channel_unix_new(sock); g_io_channel_set_close_on_unref(io, TRUE); g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL); return io; failed: close(sock); return NULL; } GIOChannel *bt_io_connect(BtIOConnect connect, gpointer user_data, GDestroyNotify destroy, GError **gerr, BtIOOption opt1, ...) { GIOChannel *io; va_list args; struct set_opts opts; int err, sock; gboolean ret; char addr[18]; va_start(args, opt1); ret = parse_set_opts(&opts, gerr, opt1, args); va_end(args); if (ret == FALSE) return NULL; io = create_io(FALSE, &opts, gerr); if (io == NULL) return NULL; sock = g_io_channel_unix_get_fd(io); /* Use DEFER_SETUP when connecting using Ext-Flowctl or ISO */ if ((opts.mode == BT_IO_MODE_EXT_FLOWCTL && opts.defer) || (opts.mode == BT_IO_MODE_ISO && opts.defer)) { if (setsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, &opts.defer, sizeof(opts.defer)) < 0) { ERROR_FAILED(gerr, "setsockopt(BT_DEFER_SETUP)", errno); return NULL; } } switch (opts.type) { case BT_IO_L2CAP: err = l2cap_connect(sock, &opts.dst, opts.dst_type, opts.psm, opts.cid); break; case BT_IO_RFCOMM: err = rfcomm_connect(sock, &opts.dst, opts.channel); break; case BT_IO_SCO: err = sco_connect(sock, &opts.dst); break; case BT_IO_ISO: err = iso_connect(sock, &opts.dst, opts.dst_type); break; case BT_IO_INVALID: default: g_set_error(gerr, BT_IO_ERROR, EINVAL, "Unknown BtIO type %d", opts.type); return NULL; } if (err < 0) { ba2str(&opts.dst, addr); g_set_error(gerr, BT_IO_ERROR, -err, "connect to %s: %s (%d)", addr, strerror(-err), -err); g_io_channel_unref(io); return NULL; } connect_add(io, connect, opts.dst, user_data, destroy); return io; } GIOChannel *bt_io_listen(BtIOConnect connect, BtIOConfirm confirm, gpointer user_data, GDestroyNotify destroy, GError **err, BtIOOption opt1, ...) { GIOChannel *io; va_list args; struct set_opts opts; int sock; gboolean ret; va_start(args, opt1); ret = parse_set_opts(&opts, err, opt1, args); va_end(args); if (ret == FALSE) return NULL; io = create_io(TRUE, &opts, err); if (io == NULL) return NULL; sock = g_io_channel_unix_get_fd(io); if (confirm) if (setsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, &opts.defer, sizeof(opts.defer)) < 0) { ERROR_FAILED(err, "setsockopt(BT_DEFER_SETUP)", errno); return NULL; } if (listen(sock, 5) < 0) { ERROR_FAILED(err, "listen", errno); g_io_channel_unref(io); return NULL; } server_add(io, connect, confirm, user_data, destroy); return io; } GQuark bt_io_error_quark(void) { return g_quark_from_static_string("bt-io-error-quark"); } bluez-5.87/btio/PaxHeaders/btio.h0000644000000000000000000000005014536422313013721 xustar0020 atime=1783093317 20 ctime=1783094104 bluez-5.87/btio/btio.h0000644000000000000000000000420014536422313013376 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2009-2010 Marcel Holtmann * Copyright (C) 2009-2010 Nokia Corporation * Copyright 2023 NXP * * */ #ifndef BT_IO_H #define BT_IO_H #include #define BT_IO_ERROR bt_io_error_quark() GQuark bt_io_error_quark(void); typedef enum { BT_IO_OPT_INVALID = 0, BT_IO_OPT_SOURCE, BT_IO_OPT_SOURCE_BDADDR, BT_IO_OPT_SOURCE_TYPE, BT_IO_OPT_DEST, BT_IO_OPT_DEST_BDADDR, BT_IO_OPT_DEST_TYPE, BT_IO_OPT_DEFER_TIMEOUT, BT_IO_OPT_SEC_LEVEL, BT_IO_OPT_KEY_SIZE, BT_IO_OPT_CHANNEL, BT_IO_OPT_SOURCE_CHANNEL, BT_IO_OPT_DEST_CHANNEL, BT_IO_OPT_PSM, BT_IO_OPT_CID, BT_IO_OPT_MTU, BT_IO_OPT_OMTU, BT_IO_OPT_IMTU, BT_IO_OPT_CENTRAL, BT_IO_OPT_HANDLE, BT_IO_OPT_CLASS, BT_IO_OPT_MODE, BT_IO_OPT_FLUSHABLE, BT_IO_OPT_PRIORITY, BT_IO_OPT_VOICE, BT_IO_OPT_PHY, BT_IO_OPT_QOS, BT_IO_OPT_BASE, BT_IO_OPT_ISO_BC_SID, BT_IO_OPT_ISO_BC_NUM_BIS, BT_IO_OPT_ISO_BC_BIS, } BtIOOption; typedef enum { BT_IO_SEC_SDP = 0, BT_IO_SEC_LOW, BT_IO_SEC_MEDIUM, BT_IO_SEC_HIGH, } BtIOSecLevel; typedef enum { BT_IO_MODE_BASIC = 0, BT_IO_MODE_ERTM, BT_IO_MODE_STREAMING, BT_IO_MODE_LE_FLOWCTL, BT_IO_MODE_EXT_FLOWCTL, BT_IO_MODE_ISO } BtIOMode; typedef void (*BtIOConfirm)(GIOChannel *io, gpointer user_data); typedef void (*BtIOConnect)(GIOChannel *io, GError *err, gpointer user_data); gboolean bt_io_accept(GIOChannel *io, BtIOConnect connect, gpointer user_data, GDestroyNotify destroy, GError **err); gboolean bt_io_bcast_accept(GIOChannel *io, BtIOConnect connect, gpointer user_data, GDestroyNotify destroy, GError **err, BtIOOption opt1, ...); gboolean bt_io_set(GIOChannel *io, GError **err, BtIOOption opt1, ...); gboolean bt_io_get(GIOChannel *io, GError **err, BtIOOption opt1, ...); GIOChannel *bt_io_connect(BtIOConnect connect, gpointer user_data, GDestroyNotify destroy, GError **gerr, BtIOOption opt1, ...); GIOChannel *bt_io_listen(BtIOConnect connect, BtIOConfirm confirm, gpointer user_data, GDestroyNotify destroy, GError **err, BtIOOption opt1, ...); #endif bluez-5.87/PaxHeaders/Makefile.mesh0000644000000000000000000000005015142177620014253 xustar0020 atime=1783092736 20 ctime=1783094098 bluez-5.87/Makefile.mesh0000644000000000000000000000333115142177620013734 0ustar00rootroot# SPDX-License-Identifier: GPL-2.0 if MESH if DATAFILES dbus_DATA += mesh/bluetooth-mesh.conf conf_DATA += mesh/mesh-main.conf endif if SYSTEMD systemdsystemunit_DATA += mesh/bluetooth-mesh.service dbussystembus_DATA += mesh/org.bluez.mesh.service endif mesh_sources = mesh/mesh.h mesh/mesh.c \ mesh/net-keys.h mesh/net-keys.c \ mesh/mesh-io.h mesh/mesh-io.c \ mesh/mesh-mgmt.h mesh/mesh-mgmt.c \ mesh/error.h mesh/mesh-io-api.h \ mesh/mesh-io-unit.h mesh/mesh-io-unit.c \ mesh/mesh-io-mgmt.h mesh/mesh-io-mgmt.c \ mesh/mesh-io-generic.h mesh/mesh-io-generic.c \ mesh/net.h mesh/net.c \ mesh/crypto.h mesh/crypto.c \ mesh/friend.h mesh/friend.c \ mesh/appkey.h mesh/appkey.c \ mesh/node.h mesh/node.c \ mesh/provision.h mesh/prov.h \ mesh/model.h mesh/model.c \ mesh/cfgmod.h mesh/cfgmod-server.c \ mesh/remprv.h mesh/remprv-server.c \ mesh/mesh-config.h mesh/mesh-config-json.c \ mesh/util.h mesh/util.c \ mesh/dbus.h mesh/dbus.c \ mesh/agent.h mesh/agent.c \ mesh/prov-acceptor.c mesh/prov-initiator.c \ mesh/manager.h mesh/manager.c \ mesh/pb-adv.h mesh/pb-adv.c \ mesh/keyring.h mesh/keyring.c \ mesh/rpl.h mesh/rpl.c \ mesh/prv-beacon.h mesh/prvbeac-server.c \ mesh/mesh-defs.h pkglibexec_PROGRAMS += mesh/bluetooth-meshd mesh/mesh.$(OBJEXT): ell/internal mesh/main.$(OBJEXT): src/builtin.h lib/bluetooth/bluetooth.h mesh_bluetooth_meshd_SOURCES = $(mesh_sources) mesh/main.c mesh_bluetooth_meshd_LDADD = src/libshared-ell.la $(ell_ldadd) -ljson-c if MANPAGES man_MANS += doc/bluetooth-meshd.8 endif manual_pages += doc/bluetooth-meshd.8 endif EXTRA_DIST += mesh/bluetooth-mesh.conf mesh/org.bluez.mesh.service mesh/mesh-main.conf bluez-5.87/PaxHeaders/install-sh0000644000000000000000000000005015221753000013647 xustar0020 atime=1783092736 20 ctime=1783094098 bluez-5.87/install-sh0000755000000000000000000003612215221753000013337 0ustar00rootroot#!/bin/sh # install - install a program, script, or datafile scriptversion=2025-06-18.21; # UTC # This originates from X11R5 (mit/util/scripts/install.sh), which was # later released in X11R6 (xc/config/util/install.sh) with the # following copyright and license. # # Copyright (C) 1994 X Consortium # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- # TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the name of the X Consortium shall not # be used in advertising or otherwise to promote the sale, use or other deal- # ings in this Software without prior written authorization from the X Consor- # tium. # # # FSF changes to this file are in the public domain. # # Calling this script install-sh is preferred over install.sh, to prevent # 'make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. tab=' ' nl=' ' IFS=" $tab$nl" # Set DOITPROG to "echo" to test this script. doit=${DOITPROG-} doit_exec=${doit:-exec} # Put in absolute file names if you don't have them in your path; # or use environment vars. chgrpprog=${CHGRPPROG-chgrp} chmodprog=${CHMODPROG-chmod} chownprog=${CHOWNPROG-chown} cmpprog=${CMPPROG-cmp} cpprog=${CPPROG-cp} mkdirprog=${MKDIRPROG-mkdir} mvprog=${MVPROG-mv} rmprog=${RMPROG-rm} stripprog=${STRIPPROG-strip} posix_mkdir= # Desired mode of installed file. mode=0755 # Create dirs (including intermediate dirs) using mode 755. # This is like GNU 'install' as of coreutils 8.32 (2020). mkdir_umask=22 backupsuffix= chgrpcmd= chmodcmd=$chmodprog chowncmd= mvcmd=$mvprog rmcmd="$rmprog -f" stripcmd= src= dst= dir_arg= dst_arg= copy_on_change=false is_target_a_directory=possibly usage="\ Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE or: $0 [OPTION]... SRCFILES... DIRECTORY or: $0 [OPTION]... -t DIRECTORY SRCFILES... or: $0 [OPTION]... -d DIRECTORIES... In the 1st form, copy SRCFILE to DSTFILE. In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. In the 4th, create DIRECTORIES. Options: --help display this help and exit. --version display version info and exit. -c (ignored) -C install only if different (preserve data modification time) -d create directories instead of installing files. -g GROUP $chgrpprog installed files to GROUP. -m MODE $chmodprog installed files to MODE. -o USER $chownprog installed files to USER. -p pass -p to $cpprog. -s $stripprog installed files. -S SUFFIX attempt to back up existing files, with suffix SUFFIX. -t DIRECTORY install into DIRECTORY. -T report an error if DSTFILE is a directory. Environment variables override the default commands: CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG By default, rm is invoked with -f; when overridden with RMPROG, it's up to you to specify -f if you want it. If -S is not specified, no backups are attempted. Report bugs to . GNU Automake home page: . General help using GNU software: ." while test $# -ne 0; do case $1 in -c) ;; -C) copy_on_change=true;; -d) dir_arg=true;; -g) chgrpcmd="$chgrpprog $2" shift;; --help) echo "$usage"; exit $?;; -m) mode=$2 case $mode in *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) echo "$0: invalid mode: $mode" >&2 exit 1;; esac shift;; -o) chowncmd="$chownprog $2" shift;; -p) cpprog="$cpprog -p";; -s) stripcmd=$stripprog;; -S) backupsuffix="$2" shift;; -t) is_target_a_directory=always dst_arg=$2 # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac shift;; -T) is_target_a_directory=never;; --version) echo "$0 (GNU Automake) $scriptversion"; exit $?;; --) shift break;; -*) echo "$0: invalid option: $1" >&2 exit 1;; *) break;; esac shift done # We allow the use of options -d and -T together, by making -d # take the precedence; this is for compatibility with GNU install. if test -n "$dir_arg"; then if test -n "$dst_arg"; then echo "$0: target directory not allowed when installing a directory." >&2 exit 1 fi fi if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then # When -d is used, all remaining arguments are directories to create. # When -t is used, the destination is already specified. # Otherwise, the last argument is the destination. Remove it from $@. for arg do if test -n "$dst_arg"; then # $@ is not empty: it contains at least $arg. set fnord "$@" "$dst_arg" shift # fnord fi shift # arg dst_arg=$arg # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac done fi if test $# -eq 0; then if test -z "$dir_arg"; then echo "$0: no input file specified." >&2 exit 1 fi # It's OK to call 'install-sh -d' without argument. # This can happen when creating conditional directories. exit 0 fi if test -z "$dir_arg"; then if test $# -gt 1 || test "$is_target_a_directory" = always; then if test ! -d "$dst_arg"; then echo "$0: $dst_arg: Is not a directory." >&2 exit 1 fi fi fi if test -z "$dir_arg"; then do_exit='(exit $ret); exit $ret' trap "ret=129; $do_exit" 1 trap "ret=130; $do_exit" 2 trap "ret=141; $do_exit" 13 trap "ret=143; $do_exit" 15 # Set umask so as not to create temps with too-generous modes. # However, 'strip' requires both read and write access to temps. case $mode in # Optimize common cases. *644) cp_umask=133;; *755) cp_umask=22;; *[0-7]) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw='% 200' fi cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; *) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw=,u+rw fi cp_umask=$mode$u_plus_rw;; esac fi for src do # Protect names problematic for 'test' and other utilities. case $src in -* | [=\(\)!]) src=./$src;; esac if test -n "$dir_arg"; then dst=$src dstdir=$dst test -d "$dstdir" dstdir_status=$? # Don't chown directories that already exist. if test $dstdir_status = 0; then chowncmd="" fi else # Waiting for this to be detected by the "$cpprog $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if test ! -f "$src" && test ! -d "$src"; then echo "$0: $src does not exist." >&2 exit 1 fi if test -z "$dst_arg"; then echo "$0: no destination specified." >&2 exit 1 fi dst=$dst_arg # If destination is a directory, append the input filename. if test -d "$dst"; then if test "$is_target_a_directory" = never; then echo "$0: $dst_arg: Is a directory" >&2 exit 1 fi dstdir=$dst dstbase=`basename "$src"` case $dst in */) dst=$dst$dstbase;; *) dst=$dst/$dstbase;; esac dstdir_status=0 else dstdir=`dirname "$dst"` test -d "$dstdir" dstdir_status=$? fi fi case $dstdir in */) dstdirslash=$dstdir;; *) dstdirslash=$dstdir/;; esac obsolete_mkdir_used=false if test $dstdir_status != 0; then case $posix_mkdir in '') # With -d, create the new directory with the user-specified mode. # Otherwise, rely on $mkdir_umask. if test -n "$dir_arg"; then mkdir_mode=-m$mode else mkdir_mode= fi posix_mkdir=false # The $RANDOM variable is not portable (e.g., dash). Use it # here however when possible just to lower collision chance. tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ trap ' ret=$? rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null exit $ret ' 0 # Because "mkdir -p" follows existing symlinks and we likely work # directly in world-writable /tmp, make sure that the '$tmpdir' # directory is successfully created first before we actually test # 'mkdir -p'. if (umask $mkdir_umask && $mkdirprog $mkdir_mode "$tmpdir" && exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 then if test -z "$dir_arg" || { # Check for POSIX incompatibility with -m. # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or # other-writable bit of parent directory when it shouldn't. # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. test_tmpdir="$tmpdir/a" ls_ld_tmpdir=`ls -ld "$test_tmpdir"` case $ls_ld_tmpdir in d????-?r-*) different_mode=700;; d????-?--*) different_mode=755;; *) false;; esac && $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" } } then posix_mkdir=: fi rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" else # Remove any dirs left behind by ancient mkdir implementations. rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null fi trap '' 0;; esac if $posix_mkdir && ( umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" ) then : else # mkdir does not conform to POSIX, # or it failed possibly due to a race condition. Create the # directory the slow way, step by step, checking for races as we go. case $dstdir in /*) prefix='/';; [-=\(\)!]*) prefix='./';; *) prefix='';; esac oIFS=$IFS IFS=/ set -f set fnord $dstdir shift set +f IFS=$oIFS prefixes= for d do test X"$d" = X && continue prefix=$prefix$d if test -d "$prefix"; then prefixes= else if $posix_mkdir; then (umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break # Don't fail if two instances are running concurrently. test -d "$prefix" || exit 1 else case $prefix in *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; *) qprefix=$prefix;; esac prefixes="$prefixes '$qprefix'" fi fi prefix=$prefix/ done if test -n "$prefixes"; then # Don't fail if two instances are running concurrently. (umask $mkdir_umask && eval "\$doit_exec \$mkdirprog $prefixes") || test -d "$dstdir" || exit 1 obsolete_mkdir_used=true fi fi fi if test -n "$dir_arg"; then { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 else # Make a couple of temp file names in the proper directory. dsttmp=${dstdirslash}_inst.$$_ rmtmp=${dstdirslash}_rm.$$_ # Trap to clean up those temp files at exit. trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 # Copy the file name to the temp name. (umask $cp_umask && { test -z "$stripcmd" || { # Create $dsttmp read-write so that cp doesn't create it read-only, # which would cause strip to fail. if test -z "$doit"; then : >"$dsttmp" # No need to fork-exec 'touch'. else $doit touch "$dsttmp" fi } } && $doit_exec $cpprog "$src" "$dsttmp") && # and set any options; do chmod last to preserve setuid bits. # # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $cpprog $src $dsttmp" command. # { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && # If -C, don't bother to copy if it wouldn't change the file. if $copy_on_change && old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && set -f && set X $old && old=:$2:$4:$5:$6 && set X $new && new=:$2:$4:$5:$6 && set +f && test "$old" = "$new" && $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 then rm -f "$dsttmp" else # If $backupsuffix is set, and the file being installed # already exists, attempt a backup. Don't worry if it fails, # e.g., if mv doesn't support -f. if test -n "$backupsuffix" && test -f "$dst"; then $doit $mvcmd -f "$dst" "$dst$backupsuffix" 2>/dev/null fi # Rename the file to the real destination. $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || # The rename failed, perhaps because mv can't rename something else # to itself, or perhaps because mv is so ancient that it does not # support -f. { # Now remove or move aside any old file at destination location. # We try this two ways since rm can't unlink itself on some # systems and the destination file might be busy for other # reasons. In this case, the final cleanup might fail but the new # file should still install successfully. { test ! -f "$dst" || $doit $rmcmd "$dst" 2>/dev/null || { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && { $doit $rmcmd "$rmtmp" 2>/dev/null; :; } } || { echo "$0: cannot unlink or rename $dst" >&2 (exit 1); exit 1 } } && # Now rename the file to the real destination. $doit $mvcmd "$dsttmp" "$dst" } fi || exit 1 trap '' 0 fi done # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp nil t) # time-stamp-start: "scriptversion=" # time-stamp-format: "%Y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: bluez-5.87/PaxHeaders/attrib0000644000000000000000000000005015221755532013072 xustar0020 atime=1783094113 20 ctime=1783094106 bluez-5.87/attrib/0000755000000000000000000000000015221755532012630 5ustar00rootrootbluez-5.87/attrib/PaxHeaders/gatt.h0000644000000000000000000000005014015011623014241 xustar0020 atime=1783093674 20 ctime=1783094106 bluez-5.87/attrib/gatt.h0000644000000000000000000000647214015011623013733 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2010 Nokia Corporation * Copyright (C) 2010 Marcel Holtmann * * */ /* * GATT Characteristic Property bit field * Reference: Core SPEC 4.1 page 2183 (Table 3.5: Characteristic Properties * bit field) defines how the Characteristic Value can be used, or how the * characteristic descriptors (see Section 3.3.3 - page 2184) can be accessed. * In the core spec, regular properties are included in the characteristic * declaration, and the extended properties are defined as descriptor. */ #define GATT_CHR_PROP_BROADCAST 0x01 #define GATT_CHR_PROP_READ 0x02 #define GATT_CHR_PROP_WRITE_WITHOUT_RESP 0x04 #define GATT_CHR_PROP_WRITE 0x08 #define GATT_CHR_PROP_NOTIFY 0x10 #define GATT_CHR_PROP_INDICATE 0x20 #define GATT_CHR_PROP_AUTH 0x40 #define GATT_CHR_PROP_EXT_PROP 0x80 /* Client Characteristic Configuration bit field */ #define GATT_CLIENT_CHARAC_CFG_NOTIF_BIT 0x0001 #define GATT_CLIENT_CHARAC_CFG_IND_BIT 0x0002 typedef void (*gatt_cb_t) (uint8_t status, GSList *l, void *user_data); struct gatt_primary { char uuid[MAX_LEN_UUID_STR + 1]; gboolean changed; struct att_range range; }; struct gatt_included { char uuid[MAX_LEN_UUID_STR + 1]; uint16_t handle; struct att_range range; }; struct gatt_char { char uuid[MAX_LEN_UUID_STR + 1]; uint16_t handle; uint8_t properties; uint16_t value_handle; }; struct gatt_desc { char uuid[MAX_LEN_UUID_STR + 1]; uint16_t handle; uint16_t uuid16; }; guint gatt_discover_primary(GAttrib *attrib, bt_uuid_t *uuid, gatt_cb_t func, gpointer user_data); unsigned int gatt_find_included(GAttrib *attrib, uint16_t start, uint16_t end, gatt_cb_t func, gpointer user_data); guint gatt_discover_char(GAttrib *attrib, uint16_t start, uint16_t end, bt_uuid_t *uuid, gatt_cb_t func, gpointer user_data); guint gatt_read_char(GAttrib *attrib, uint16_t handle, GAttribResultFunc func, gpointer user_data); guint gatt_write_char(GAttrib *attrib, uint16_t handle, const uint8_t *value, size_t vlen, GAttribResultFunc func, gpointer user_data); guint gatt_discover_desc(GAttrib *attrib, uint16_t start, uint16_t end, bt_uuid_t *uuid, gatt_cb_t func, gpointer user_data); guint gatt_reliable_write_char(GAttrib *attrib, uint16_t handle, const uint8_t *value, size_t vlen, GAttribResultFunc func, gpointer user_data); guint gatt_execute_write(GAttrib *attrib, uint8_t flags, GAttribResultFunc func, gpointer user_data); guint gatt_write_cmd(GAttrib *attrib, uint16_t handle, const uint8_t *value, int vlen, GDestroyNotify notify, gpointer user_data); guint gatt_signed_write_cmd(GAttrib *attrib, uint16_t handle, const uint8_t *value, int vlen, struct bt_crypto *crypto, const uint8_t csrk[16], uint32_t sign_cnt, GDestroyNotify notify, gpointer user_data); guint gatt_read_char_by_uuid(GAttrib *attrib, uint16_t start, uint16_t end, bt_uuid_t *uuid, GAttribResultFunc func, gpointer user_data); guint gatt_exchange_mtu(GAttrib *attrib, uint16_t mtu, GAttribResultFunc func, gpointer user_data); gboolean gatt_parse_record(const sdp_record_t *rec, uuid_t *prim_uuid, uint16_t *psm, uint16_t *start, uint16_t *end); bluez-5.87/attrib/PaxHeaders/gatttool.c0000644000000000000000000000005015200607060015135 xustar0020 atime=1783092682 20 ctime=1783094100 bluez-5.87/attrib/gatttool.c0000644000000000000000000003417415200607060014627 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2010 Nokia Corporation * Copyright (C) 2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/hci_lib.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/shared/att-types.h" #include "src/shared/util.h" #include "att.h" #include "btio/btio.h" #include "gattrib.h" #include "gatt.h" #include "gatttool.h" static char *opt_src = NULL; static char *opt_dst = NULL; static char *opt_dst_type = NULL; static char *opt_value = NULL; static char *opt_sec_level = NULL; static bt_uuid_t *opt_uuid = NULL; static int opt_start = 0x0001; static int opt_end = 0xffff; static int opt_handle = -1; static int opt_mtu = 0; static int opt_psm = 0; static gboolean opt_primary = FALSE; static gboolean opt_characteristics = FALSE; static gboolean opt_char_read = FALSE; static gboolean opt_listen = FALSE; static gboolean opt_char_desc = FALSE; static gboolean opt_char_write = FALSE; static gboolean opt_char_write_req = FALSE; static gboolean opt_interactive = FALSE; static GMainLoop *event_loop; static gboolean got_error = FALSE; static GSourceFunc operation; struct characteristic_data { GAttrib *attrib; uint16_t start; uint16_t end; }; static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data) { GAttrib *attrib = user_data; uint8_t *opdu; uint16_t handle, i, olen = 0; size_t plen; handle = get_le16(&pdu[1]); switch (pdu[0]) { case ATT_OP_HANDLE_NOTIFY: g_print("Notification handle = 0x%04x value: ", handle); break; case ATT_OP_HANDLE_IND: g_print("Indication handle = 0x%04x value: ", handle); break; default: g_print("Invalid opcode\n"); return; } for (i = 3; i < len; i++) g_print("%02x ", pdu[i]); g_print("\n"); if (pdu[0] == ATT_OP_HANDLE_NOTIFY) return; opdu = g_attrib_get_buffer(attrib, &plen); olen = enc_confirmation(opdu, plen); if (olen > 0) g_attrib_send(attrib, 0, opdu, olen, NULL, NULL, NULL); } static gboolean listen_start(gpointer user_data) { GAttrib *attrib = user_data; g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, GATTRIB_ALL_HANDLES, events_handler, attrib, NULL); g_attrib_register(attrib, ATT_OP_HANDLE_IND, GATTRIB_ALL_HANDLES, events_handler, attrib, NULL); return FALSE; } static void connect_cb(GIOChannel *io, GError *err, gpointer user_data) { GAttrib *attrib; uint16_t mtu; uint16_t cid; GError *gerr = NULL; if (err) { g_printerr("%s\n", err->message); got_error = TRUE; g_main_loop_quit(event_loop); } bt_io_get(io, &gerr, BT_IO_OPT_IMTU, &mtu, BT_IO_OPT_CID, &cid, BT_IO_OPT_INVALID); if (gerr) { g_printerr("Can't detect MTU, using default: %s", gerr->message); g_error_free(gerr); mtu = ATT_DEFAULT_LE_MTU; } if (cid == BT_ATT_CID) mtu = ATT_DEFAULT_LE_MTU; attrib = g_attrib_new(io, mtu, false); if (opt_listen) g_idle_add(listen_start, attrib); operation(attrib); } static void primary_all_cb(uint8_t status, GSList *services, void *user_data) { GSList *l; if (status) { g_printerr("Discover all primary services failed: %s\n", att_ecode2str(status)); goto done; } for (l = services; l; l = l->next) { struct gatt_primary *prim = l->data; g_print("attr handle = 0x%04x, end grp handle = 0x%04x " "uuid: %s\n", prim->range.start, prim->range.end, prim->uuid); } done: g_main_loop_quit(event_loop); } static void primary_by_uuid_cb(uint8_t status, GSList *ranges, void *user_data) { GSList *l; if (status != 0) { g_printerr("Discover primary services by UUID failed: %s\n", att_ecode2str(status)); goto done; } for (l = ranges; l; l = l->next) { struct att_range *range = l->data; g_print("Starting handle: %04x Ending handle: %04x\n", range->start, range->end); } done: g_main_loop_quit(event_loop); } static gboolean primary(gpointer user_data) { GAttrib *attrib = user_data; if (opt_uuid) gatt_discover_primary(attrib, opt_uuid, primary_by_uuid_cb, NULL); else gatt_discover_primary(attrib, NULL, primary_all_cb, NULL); return FALSE; } static void char_discovered_cb(uint8_t status, GSList *characteristics, void *user_data) { GSList *l; if (status) { g_printerr("Discover all characteristics failed: %s\n", att_ecode2str(status)); goto done; } for (l = characteristics; l; l = l->next) { struct gatt_char *chars = l->data; g_print("handle = 0x%04x, char properties = 0x%02x, char value " "handle = 0x%04x, uuid = %s\n", chars->handle, chars->properties, chars->value_handle, chars->uuid); } done: g_main_loop_quit(event_loop); } static gboolean characteristics(gpointer user_data) { GAttrib *attrib = user_data; gatt_discover_char(attrib, opt_start, opt_end, opt_uuid, char_discovered_cb, NULL); return FALSE; } static void char_read_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { uint8_t value[plen]; ssize_t vlen; int i; if (status != 0) { g_printerr("Characteristic value/descriptor read failed: %s\n", att_ecode2str(status)); goto done; } vlen = dec_read_resp(pdu, plen, value, sizeof(value)); if (vlen < 0) { g_printerr("Protocol error\n"); goto done; } g_print("Characteristic value/descriptor: "); for (i = 0; i < vlen; i++) g_print("%02x ", value[i]); g_print("\n"); done: if (!opt_listen) g_main_loop_quit(event_loop); } static void char_read_by_uuid_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { struct att_data_list *list; int i; if (status != 0) { g_printerr("Read characteristics by UUID failed: %s\n", att_ecode2str(status)); goto done; } list = dec_read_by_type_resp(pdu, plen); if (list == NULL) goto done; for (i = 0; i < list->num; i++) { uint8_t *value = list->data[i]; int j; g_print("handle: 0x%04x \t value: ", get_le16(value)); value += 2; for (j = 0; j < list->len - 2; j++, value++) g_print("%02x ", *value); g_print("\n"); } att_data_list_free(list); done: g_main_loop_quit(event_loop); } static gboolean characteristics_read(gpointer user_data) { GAttrib *attrib = user_data; if (opt_uuid != NULL) { gatt_read_char_by_uuid(attrib, opt_start, opt_end, opt_uuid, char_read_by_uuid_cb, NULL); return FALSE; } if (opt_handle <= 0) { g_printerr("A valid handle is required\n"); g_main_loop_quit(event_loop); return FALSE; } gatt_read_char(attrib, opt_handle, char_read_cb, attrib); return FALSE; } static void mainloop_quit(gpointer user_data) { uint8_t *value = user_data; g_free(value); g_main_loop_quit(event_loop); } static gboolean characteristics_write(gpointer user_data) { GAttrib *attrib = user_data; uint8_t *value; size_t len; if (opt_handle <= 0) { g_printerr("A valid handle is required\n"); goto error; } if (opt_value == NULL || opt_value[0] == '\0') { g_printerr("A value is required\n"); goto error; } len = gatt_attr_data_from_string(opt_value, &value); if (len == 0) { g_printerr("Invalid value\n"); goto error; } gatt_write_cmd(attrib, opt_handle, value, len, mainloop_quit, value); g_free(value); return FALSE; error: g_main_loop_quit(event_loop); return FALSE; } static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { if (status != 0) { g_printerr("Characteristic Write Request failed: " "%s\n", att_ecode2str(status)); goto done; } if (!dec_write_resp(pdu, plen) && !dec_exec_write_resp(pdu, plen)) { g_printerr("Protocol error\n"); goto done; } g_print("Characteristic value was written successfully\n"); done: if (!opt_listen) g_main_loop_quit(event_loop); } static gboolean characteristics_write_req(gpointer user_data) { GAttrib *attrib = user_data; uint8_t *value; size_t len; if (opt_handle <= 0) { g_printerr("A valid handle is required\n"); goto error; } if (opt_value == NULL || opt_value[0] == '\0') { g_printerr("A value is required\n"); goto error; } len = gatt_attr_data_from_string(opt_value, &value); if (len == 0) { g_printerr("Invalid value\n"); goto error; } gatt_write_char(attrib, opt_handle, value, len, char_write_req_cb, NULL); g_free(value); return FALSE; error: g_main_loop_quit(event_loop); return FALSE; } static void char_desc_cb(uint8_t status, GSList *descriptors, void *user_data) { GSList *l; if (status) { g_printerr("Discover descriptors failed: %s\n", att_ecode2str(status)); return; } for (l = descriptors; l; l = l->next) { struct gatt_desc *desc = l->data; g_print("handle = 0x%04x, uuid = %s\n", desc->handle, desc->uuid); } if (!opt_listen) g_main_loop_quit(event_loop); } static gboolean characteristics_desc(gpointer user_data) { GAttrib *attrib = user_data; gatt_discover_desc(attrib, opt_start, opt_end, NULL, char_desc_cb, NULL); return FALSE; } static gboolean parse_uuid(const char *key, const char *value, gpointer user_data, GError **error) { if (!value) return FALSE; opt_uuid = g_try_malloc(sizeof(bt_uuid_t)); if (opt_uuid == NULL) return FALSE; if (bt_string_to_uuid(opt_uuid, value) < 0) return FALSE; return TRUE; } static const GOptionEntry primary_char_options[] = { { "start", 's' , 0, G_OPTION_ARG_INT, &opt_start, "Starting handle (optional)", "0x0001" }, { "end", 'e' , 0, G_OPTION_ARG_INT, &opt_end, "Ending handle (optional)", "0xffff" }, { "uuid", 'u', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, parse_uuid, "UUID16 or UUID128 (optional)", "0x1801"}, { NULL }, }; static const GOptionEntry char_rw_options[] = { { "handle", 'a' , 0, G_OPTION_ARG_INT, &opt_handle, "Read/Write characteristic by handle (required)", "0x0001" }, { "value", 'n' , 0, G_OPTION_ARG_STRING, &opt_value, "Write characteristic value (required for write operation)", "0x0001" }, {NULL}, }; static const GOptionEntry gatt_options[] = { { "primary", 0, 0, G_OPTION_ARG_NONE, &opt_primary, "Primary Service Discovery", NULL }, { "characteristics", 0, 0, G_OPTION_ARG_NONE, &opt_characteristics, "Characteristics Discovery", NULL }, { "char-read", 0, 0, G_OPTION_ARG_NONE, &opt_char_read, "Characteristics Value/Descriptor Read", NULL }, { "char-write", 0, 0, G_OPTION_ARG_NONE, &opt_char_write, "Characteristics Value Write Without Response (Write Command)", NULL }, { "char-write-req", 0, 0, G_OPTION_ARG_NONE, &opt_char_write_req, "Characteristics Value Write (Write Request)", NULL }, { "char-desc", 0, 0, G_OPTION_ARG_NONE, &opt_char_desc, "Characteristics Descriptor Discovery", NULL }, { "listen", 0, 0, G_OPTION_ARG_NONE, &opt_listen, "Listen for notifications and indications", NULL }, { "interactive", 'I', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, &opt_interactive, "Use interactive mode", NULL }, { NULL }, }; static const GOptionEntry options[] = { { "adapter", 'i', 0, G_OPTION_ARG_STRING, &opt_src, "Specify local adapter interface", "hciX" }, { "device", 'b', 0, G_OPTION_ARG_STRING, &opt_dst, "Specify remote Bluetooth address", "MAC" }, { "addr-type", 't', 0, G_OPTION_ARG_STRING, &opt_dst_type, "Set LE address type. Default: public", "[public | random]"}, { "mtu", 'm', 0, G_OPTION_ARG_INT, &opt_mtu, "Specify the MTU size", "MTU" }, { "psm", 'p', 0, G_OPTION_ARG_INT, &opt_psm, "Specify the PSM for GATT/ATT over BR/EDR", "PSM" }, { "sec-level", 'l', 0, G_OPTION_ARG_STRING, &opt_sec_level, "Set security level. Default: low", "[low | medium | high]"}, { NULL }, }; int main(int argc, char *argv[]) { GOptionContext *context; GOptionGroup *gatt_group, *params_group, *char_rw_group; GError *gerr = NULL; GIOChannel *chan; opt_dst_type = g_strdup("public"); opt_sec_level = g_strdup("low"); context = g_option_context_new(NULL); g_option_context_add_main_entries(context, options, NULL); /* GATT commands */ gatt_group = g_option_group_new("gatt", "GATT commands", "Show all GATT commands", NULL, NULL); g_option_context_add_group(context, gatt_group); g_option_group_add_entries(gatt_group, gatt_options); /* Primary Services and Characteristics arguments */ params_group = g_option_group_new("params", "Primary Services/Characteristics arguments", "Show all Primary Services/Characteristics arguments", NULL, NULL); g_option_context_add_group(context, params_group); g_option_group_add_entries(params_group, primary_char_options); /* Characteristics value/descriptor read/write arguments */ char_rw_group = g_option_group_new("char-read-write", "Characteristics Value/Descriptor Read/Write arguments", "Show all Characteristics Value/Descriptor Read/Write " "arguments", NULL, NULL); g_option_context_add_group(context, char_rw_group); g_option_group_add_entries(char_rw_group, char_rw_options); if (!g_option_context_parse(context, &argc, &argv, &gerr)) { g_printerr("%s\n", gerr->message); g_clear_error(&gerr); } if (opt_interactive) { interactive(opt_src, opt_dst, opt_dst_type, opt_psm); goto done; } if (opt_primary) operation = primary; else if (opt_characteristics) operation = characteristics; else if (opt_char_read) operation = characteristics_read; else if (opt_char_write) operation = characteristics_write; else if (opt_char_write_req) operation = characteristics_write_req; else if (opt_char_desc) operation = characteristics_desc; else { char *help = g_option_context_get_help(context, TRUE, NULL); g_print("%s\n", help); g_free(help); got_error = TRUE; goto done; } if (opt_dst == NULL) { g_print("Remote Bluetooth address required\n"); got_error = TRUE; goto done; } chan = gatt_connect(opt_src, opt_dst, opt_dst_type, opt_sec_level, opt_psm, opt_mtu, connect_cb, &gerr); if (chan == NULL) { g_printerr("%s\n", gerr->message); g_clear_error(&gerr); got_error = TRUE; goto done; } event_loop = g_main_loop_new(NULL, FALSE); g_main_loop_run(event_loop); g_main_loop_unref(event_loop); done: g_option_context_free(context); g_free(opt_src); g_free(opt_dst); g_free(opt_uuid); g_free(opt_sec_level); if (got_error) exit(EXIT_FAILURE); else exit(EXIT_SUCCESS); } bluez-5.87/attrib/PaxHeaders/gattrib.c0000644000000000000000000000005015063045254014744 xustar0020 atime=1783093727 20 ctime=1783094100 bluez-5.87/attrib/gattrib.c0000644000000000000000000002217615063045254014435 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2010 Nokia Corporation * Copyright (C) 2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "btio/btio.h" #include "src/log.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/gatt-helpers.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "attrib/att.h" #include "attrib/gattrib.h" struct _GAttrib { int ref_count; struct bt_att *att; struct bt_gatt_client *client; GIOChannel *io; GDestroyNotify destroy; gpointer destroy_user_data; struct queue *callbacks; uint8_t *buf; int buflen; struct queue *track_ids; }; struct attrib_callbacks { unsigned int id; GAttribResultFunc result_func; GAttribNotifyFunc notify_func; GDestroyNotify destroy_func; gpointer user_data; GAttrib *parent; uint16_t notify_handle; }; GAttrib *g_attrib_new(GIOChannel *io, guint16 mtu, bool ext_signed) { gint fd; GAttrib *attr; if (!io) return NULL; fd = g_io_channel_unix_get_fd(io); attr = new0(GAttrib, 1); if (!attr) return NULL; g_io_channel_ref(io); attr->io = io; attr->att = bt_att_new(fd, ext_signed); if (!attr->att) goto fail; bt_att_set_close_on_unref(attr->att, true); g_io_channel_set_close_on_unref(io, FALSE); if (!bt_att_set_mtu(attr->att, mtu)) goto fail; attr->buf = malloc0(mtu); attr->buflen = mtu; if (!attr->buf) goto fail; attr->callbacks = queue_new(); if (!attr->callbacks) goto fail; attr->track_ids = queue_new(); if (!attr->track_ids) goto fail; return g_attrib_ref(attr); fail: free(attr->buf); bt_att_unref(attr->att); g_io_channel_unref(io); free(attr); return NULL; } GAttrib *g_attrib_ref(GAttrib *attrib) { if (!attrib) return NULL; __sync_fetch_and_add(&attrib->ref_count, 1); DBG("%p: g_attrib_ref=%d ", attrib, attrib->ref_count); return attrib; } static void attrib_callbacks_destroy(void *data) { struct attrib_callbacks *cb = data; if (cb->destroy_func) cb->destroy_func(cb->user_data); free(data); } static void attrib_callbacks_remove(void *data) { struct attrib_callbacks *cb = data; if (!data || !queue_remove(cb->parent->callbacks, data)) return; attrib_callbacks_destroy(data); } void g_attrib_unref(GAttrib *attrib) { if (!attrib) return; DBG("%p: g_attrib_unref=%d ", attrib, attrib->ref_count - 1); if (__sync_sub_and_fetch(&attrib->ref_count, 1)) return; if (attrib->destroy) attrib->destroy(attrib->destroy_user_data); bt_gatt_client_unref(attrib->client); bt_att_unref(attrib->att); queue_destroy(attrib->callbacks, attrib_callbacks_destroy); queue_destroy(attrib->track_ids, NULL); free(attrib->buf); g_io_channel_unref(attrib->io); free(attrib); } GIOChannel *g_attrib_get_channel(GAttrib *attrib) { if (!attrib) return NULL; return attrib->io; } struct bt_att *g_attrib_get_att(GAttrib *attrib) { if (!attrib) return NULL; return attrib->att; } gboolean g_attrib_set_destroy_function(GAttrib *attrib, GDestroyNotify destroy, gpointer user_data) { if (!attrib) return FALSE; attrib->destroy = destroy; attrib->destroy_user_data = user_data; return TRUE; } static uint8_t *construct_full_pdu(uint8_t opcode, const void *pdu, uint16_t length) { uint8_t *buf = malloc0(length + 1); if (!buf) return NULL; buf[0] = opcode; if (pdu && length) memcpy(buf + 1, pdu, length); return buf; } static void attrib_callback_result(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { uint8_t *buf; struct attrib_callbacks *cb = user_data; guint8 status = 0; if (!cb) return; buf = construct_full_pdu(opcode, pdu, length); if (!buf) return; if (opcode == BT_ATT_OP_ERROR_RSP) { /* Error code is the third byte of the PDU data */ if (length < 4) status = BT_ATT_ERROR_UNLIKELY; else status = ((guint8 *)pdu)[3]; } if (cb->result_func) cb->result_func(status, buf, length + 1, cb->user_data); free(buf); } static void attrib_callback_notify(struct bt_att_chan *chan, uint16_t mtu, uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { uint8_t *buf; struct attrib_callbacks *cb = user_data; if (!cb || !cb->notify_func) return; if (cb->notify_handle != GATTRIB_ALL_HANDLES && length < 2) return; if (cb->notify_handle != GATTRIB_ALL_HANDLES && cb->notify_handle != get_le16(pdu)) return; buf = construct_full_pdu(opcode, pdu, length); if (!buf) return; cb->notify_func(buf, length + 1, cb->user_data); free(buf); } guint g_attrib_send(GAttrib *attrib, guint id, const guint8 *pdu, guint16 len, GAttribResultFunc func, gpointer user_data, GDestroyNotify notify) { struct attrib_callbacks *cb = NULL; bt_att_response_func_t response_cb = NULL; bt_att_destroy_func_t destroy_cb = NULL; if (!attrib) return 0; if (!pdu || !len) return 0; if (func || notify) { cb = new0(struct attrib_callbacks, 1); if (!cb) return 0; cb->result_func = func; cb->user_data = user_data; cb->destroy_func = notify; cb->parent = attrib; queue_push_head(attrib->callbacks, cb); response_cb = attrib_callback_result; destroy_cb = attrib_callbacks_remove; } if (id == 0) id = bt_att_send(attrib->att, pdu[0], (void *) pdu + 1, len - 1, response_cb, cb, destroy_cb); else { int err; err = bt_att_resend(attrib->att, id, pdu[0], (void *) pdu + 1, len - 1, response_cb, cb, destroy_cb); if (err) return 0; } if (!id) return id; /* * If user what us to use given id, lets keep track on that so we give * user a possibility to cancel ongoing request. */ if (cb) { cb->id = id; queue_push_tail(attrib->track_ids, UINT_TO_PTR(id)); } return id; } gboolean g_attrib_cancel(GAttrib *attrib, guint id) { if (!attrib) return FALSE; return bt_att_cancel(attrib->att, id); } static void cancel_request(void *data, void *user_data) { unsigned int id = PTR_TO_UINT(data); GAttrib *attrib = user_data; bt_att_cancel(attrib->att, id); } gboolean g_attrib_cancel_all(GAttrib *attrib) { if (!attrib) return FALSE; queue_foreach(attrib->track_ids, cancel_request, attrib); queue_remove_all(attrib->track_ids, NULL, NULL, NULL); return TRUE; } static void client_notify_cb(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { uint8_t *buf = newa(uint8_t, length + 2); put_le16(value_handle, buf); if (length) memcpy(buf + 2, value, length); attrib_callback_notify(NULL, 0, ATT_OP_HANDLE_NOTIFY, buf, length + 2, user_data); } guint g_attrib_register(GAttrib *attrib, guint8 opcode, guint16 handle, GAttribNotifyFunc func, gpointer user_data, GDestroyNotify notify) { struct attrib_callbacks *cb = NULL; if (!attrib) return 0; if (func || notify) { cb = new0(struct attrib_callbacks, 1); if (!cb) return 0; cb->notify_func = func; cb->notify_handle = handle; cb->user_data = user_data; cb->destroy_func = notify; cb->parent = attrib; queue_push_head(attrib->callbacks, cb); } if (opcode == ATT_OP_HANDLE_NOTIFY && attrib->client) { unsigned int id; id = bt_gatt_client_register_notify(attrib->client, handle, NULL, client_notify_cb, cb, attrib_callbacks_remove); if (id) return id; } if (opcode == GATTRIB_ALL_REQS) opcode = BT_ATT_ALL_REQUESTS; return bt_att_register(attrib->att, opcode, attrib_callback_notify, cb, attrib_callbacks_remove); } uint8_t *g_attrib_get_buffer(GAttrib *attrib, size_t *len) { uint16_t mtu; if (!attrib || !len) return NULL; mtu = bt_att_get_mtu(attrib->att); /* * Clients of this expect a buffer to use. * * Pdu encoding in shared/att verifies if whole buffer fits the mtu, * thus we should set the buflen also when mtu is reduced. But we * need to reallocate the buffer only if mtu is larger. */ if (mtu > attrib->buflen) attrib->buf = g_realloc(attrib->buf, mtu); attrib->buflen = mtu; *len = attrib->buflen; return attrib->buf; } gboolean g_attrib_set_mtu(GAttrib *attrib, int mtu) { if (!attrib) return FALSE; /* * Clients of this expect a buffer to use. * * Pdu encoding in sharred/att verifies if whole buffer fits the mtu, * thus we should set the buflen also when mtu is reduced. But we * need to reallocate the buffer only if mtu is larger. */ if (mtu > attrib->buflen) attrib->buf = g_realloc(attrib->buf, mtu); attrib->buflen = mtu; return bt_att_set_mtu(attrib->att, mtu); } gboolean g_attrib_attach_client(GAttrib *attrib, struct bt_gatt_client *client) { if (!attrib || !client) return FALSE; if (attrib->client) bt_gatt_client_unref(attrib->client); attrib->client = bt_gatt_client_clone(client); if (!attrib->client) return FALSE; return TRUE; } gboolean g_attrib_unregister(GAttrib *attrib, guint id) { if (!attrib) return FALSE; return bt_att_unregister(attrib->att, id); } gboolean g_attrib_unregister_all(GAttrib *attrib) { if (!attrib) return false; return bt_att_unregister_all(attrib->att); } bluez-5.87/attrib/PaxHeaders/gatttool.h0000644000000000000000000000005014015011623015137 xustar0020 atime=1770587287 20 ctime=1783094101 bluez-5.87/attrib/gatttool.h0000644000000000000000000000074614015011623014627 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011 Nokia Corporation * * */ int interactive(const char *src, const char *dst, const char *dst_type, int psm); GIOChannel *gatt_connect(const char *src, const char *dst, const char *dst_type, const char *sec_level, int psm, int mtu, BtIOConnect connect_cb, GError **gerr); size_t gatt_attr_data_from_string(const char *str, uint8_t **data); bluez-5.87/attrib/PaxHeaders/att-database.h0000644000000000000000000000005014015011623015634 xustar0020 atime=1770587292 20 ctime=1783094106 bluez-5.87/attrib/att-database.h0000644000000000000000000000142414015011623015316 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012 Texas Instruments Corporation * */ /* Requirements for read/write operations */ enum { ATT_NONE, /* No restrictions */ ATT_AUTHENTICATION, /* Authentication required */ ATT_AUTHORIZATION, /* Authorization required */ ATT_NOT_PERMITTED, /* Operation not permitted */ }; struct attribute { uint16_t handle; bt_uuid_t uuid; int read_req; /* Read requirement */ int write_req; /* Write requirement */ uint8_t (*read_cb)(struct attribute *a, struct btd_device *device, gpointer user_data); uint8_t (*write_cb)(struct attribute *a, struct btd_device *device, gpointer user_data); gpointer cb_user_data; size_t len; uint8_t *data; }; bluez-5.87/attrib/PaxHeaders/interactive.c0000644000000000000000000000005015200607060015615 xustar0020 atime=1783092682 20 ctime=1783094101 bluez-5.87/attrib/interactive.c0000644000000000000000000005034015200607060015300 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011 Nokia Corporation * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/shared/att-types.h" #include "src/shared/util.h" #include "btio/btio.h" #include "att.h" #include "gattrib.h" #include "gatt.h" #include "gatttool.h" #include "client/display.h" static GIOChannel *iochannel = NULL; static GAttrib *attrib = NULL; static GMainLoop *event_loop; static GString *prompt; static char *opt_src = NULL; static char *opt_dst = NULL; static char *opt_dst_type = NULL; static char *opt_sec_level = NULL; static int opt_psm = 0; static int opt_mtu = 0; static int start; static int end; static void cmd_help(int argcp, char **argvp); static enum state { STATE_DISCONNECTED, STATE_CONNECTING, STATE_CONNECTED } conn_state; #define error(fmt, arg...) \ rl_printf(COLOR_RED "Error: " COLOR_OFF fmt, ## arg) #define failed(fmt, arg...) \ rl_printf(COLOR_RED "Command Failed: " COLOR_OFF fmt, ## arg) static char *get_prompt(void) { if (conn_state == STATE_CONNECTED) g_string_assign(prompt, COLOR_BLUE); else g_string_assign(prompt, ""); if (opt_dst) g_string_append_printf(prompt, "[%17s]", opt_dst); else g_string_append_printf(prompt, "[%17s]", ""); if (conn_state == STATE_CONNECTED) g_string_append(prompt, COLOR_OFF); if (opt_psm) g_string_append(prompt, "[BR]"); else g_string_append(prompt, "[LE]"); g_string_append(prompt, "> "); return prompt->str; } static void set_state(enum state st) { conn_state = st; rl_set_prompt(get_prompt()); } static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data) { uint8_t *opdu; uint16_t handle, i, olen; size_t plen; GString *s; handle = get_le16(&pdu[1]); switch (pdu[0]) { case ATT_OP_HANDLE_NOTIFY: s = g_string_new(NULL); g_string_printf(s, "Notification handle = 0x%04x value: ", handle); break; case ATT_OP_HANDLE_IND: s = g_string_new(NULL); g_string_printf(s, "Indication handle = 0x%04x value: ", handle); break; default: error("Invalid opcode\n"); return; } for (i = 3; i < len; i++) g_string_append_printf(s, "%02x ", pdu[i]); rl_printf("%s\n", s->str); g_string_free(s, TRUE); if (pdu[0] == ATT_OP_HANDLE_NOTIFY) return; opdu = g_attrib_get_buffer(attrib, &plen); olen = enc_confirmation(opdu, plen); if (olen > 0) g_attrib_send(attrib, 0, opdu, olen, NULL, NULL, NULL); } static void connect_cb(GIOChannel *io, GError *err, gpointer user_data) { uint16_t mtu; uint16_t cid; if (err) { set_state(STATE_DISCONNECTED); error("%s\n", err->message); return; } bt_io_get(io, &err, BT_IO_OPT_IMTU, &mtu, BT_IO_OPT_CID, &cid, BT_IO_OPT_INVALID); if (err) { g_printerr("Can't detect MTU, using default: %s", err->message); g_error_free(err); mtu = ATT_DEFAULT_LE_MTU; } if (cid == BT_ATT_CID) mtu = ATT_DEFAULT_LE_MTU; attrib = g_attrib_new(iochannel, mtu, false); g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, GATTRIB_ALL_HANDLES, events_handler, attrib, NULL); g_attrib_register(attrib, ATT_OP_HANDLE_IND, GATTRIB_ALL_HANDLES, events_handler, attrib, NULL); set_state(STATE_CONNECTED); rl_printf("Connection successful\n"); } static void disconnect_io() { if (conn_state == STATE_DISCONNECTED) return; g_attrib_unref(attrib); attrib = NULL; opt_mtu = 0; g_io_channel_shutdown(iochannel, FALSE, NULL); g_io_channel_unref(iochannel); iochannel = NULL; set_state(STATE_DISCONNECTED); } static void primary_all_cb(uint8_t status, GSList *services, void *user_data) { GSList *l; if (status) { error("Discover all primary services failed: %s\n", att_ecode2str(status)); return; } if (services == NULL) { error("No primary service found\n"); return; } for (l = services; l; l = l->next) { struct gatt_primary *prim = l->data; rl_printf("attr handle: 0x%04x, end grp handle: 0x%04x uuid: %s\n", prim->range.start, prim->range.end, prim->uuid); } } static void primary_by_uuid_cb(uint8_t status, GSList *ranges, void *user_data) { GSList *l; if (status) { error("Discover primary services by UUID failed: %s\n", att_ecode2str(status)); return; } if (ranges == NULL) { error("No service UUID found\n"); return; } for (l = ranges; l; l = l->next) { struct att_range *range = l->data; rl_printf("Starting handle: 0x%04x Ending handle: 0x%04x\n", range->start, range->end); } } static void included_cb(uint8_t status, GSList *includes, void *user_data) { GSList *l; if (status) { error("Find included services failed: %s\n", att_ecode2str(status)); return; } if (includes == NULL) { rl_printf("No included services found for this range\n"); return; } for (l = includes; l; l = l->next) { struct gatt_included *incl = l->data; rl_printf("handle: 0x%04x, start handle: 0x%04x, " "end handle: 0x%04x uuid: %s\n", incl->handle, incl->range.start, incl->range.end, incl->uuid); } } static void char_cb(uint8_t status, GSList *characteristics, void *user_data) { GSList *l; if (status) { error("Discover all characteristics failed: %s\n", att_ecode2str(status)); return; } for (l = characteristics; l; l = l->next) { struct gatt_char *chars = l->data; rl_printf("handle: 0x%04x, char properties: 0x%02x, char value " "handle: 0x%04x, uuid: %s\n", chars->handle, chars->properties, chars->value_handle, chars->uuid); } } static void char_desc_cb(uint8_t status, GSList *descriptors, void *user_data) { GSList *l; if (status) { error("Discover descriptors failed: %s\n", att_ecode2str(status)); return; } for (l = descriptors; l; l = l->next) { struct gatt_desc *desc = l->data; rl_printf("handle: 0x%04x, uuid: %s\n", desc->handle, desc->uuid); } } static void char_read_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { uint8_t value[plen]; ssize_t vlen; int i; GString *s; if (status != 0) { error("Characteristic value/descriptor read failed: %s\n", att_ecode2str(status)); return; } vlen = dec_read_resp(pdu, plen, value, sizeof(value)); if (vlen < 0) { error("Protocol error\n"); return; } s = g_string_new("Characteristic value/descriptor: "); for (i = 0; i < vlen; i++) g_string_append_printf(s, "%02x ", value[i]); rl_printf("%s\n", s->str); g_string_free(s, TRUE); } static void char_read_by_uuid_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { struct att_data_list *list; int i; GString *s; if (status != 0) { error("Read characteristics by UUID failed: %s\n", att_ecode2str(status)); return; } list = dec_read_by_type_resp(pdu, plen); if (list == NULL) return; s = g_string_new(NULL); for (i = 0; i < list->num; i++) { uint8_t *value = list->data[i]; int j; g_string_printf(s, "handle: 0x%04x \t value: ", get_le16(value)); value += 2; for (j = 0; j < list->len - 2; j++, value++) g_string_append_printf(s, "%02x ", *value); rl_printf("%s\n", s->str); } att_data_list_free(list); g_string_free(s, TRUE); } static void cmd_exit(int argcp, char **argvp) { rl_callback_handler_remove(); g_main_loop_quit(event_loop); } static gboolean channel_watcher(GIOChannel *chan, GIOCondition cond, gpointer user_data) { disconnect_io(); return FALSE; } static void cmd_connect(int argcp, char **argvp) { GError *gerr = NULL; if (conn_state != STATE_DISCONNECTED) return; if (argcp > 1) { g_free(opt_dst); opt_dst = g_strdup(argvp[1]); g_free(opt_dst_type); if (argcp > 2) opt_dst_type = g_strdup(argvp[2]); else opt_dst_type = g_strdup("public"); } if (opt_dst == NULL) { error("Remote Bluetooth address required\n"); return; } rl_printf("Attempting to connect to %s\n", opt_dst); set_state(STATE_CONNECTING); iochannel = gatt_connect(opt_src, opt_dst, opt_dst_type, opt_sec_level, opt_psm, opt_mtu, connect_cb, &gerr); if (iochannel == NULL) { set_state(STATE_DISCONNECTED); error("%s\n", gerr->message); g_error_free(gerr); } else g_io_add_watch(iochannel, G_IO_HUP, channel_watcher, NULL); } static void cmd_disconnect(int argcp, char **argvp) { disconnect_io(); } static void cmd_primary(int argcp, char **argvp) { bt_uuid_t uuid; if (conn_state != STATE_CONNECTED) { failed("Disconnected\n"); return; } if (argcp == 1) { gatt_discover_primary(attrib, NULL, primary_all_cb, NULL); return; } if (bt_string_to_uuid(&uuid, argvp[1]) < 0) { error("Invalid UUID\n"); return; } gatt_discover_primary(attrib, &uuid, primary_by_uuid_cb, NULL); } static int strtohandle(const char *src) { char *e; int dst; errno = 0; dst = strtoll(src, &e, 16); if (errno != 0 || *e != '\0') return -EINVAL; return dst; } static void cmd_included(int argcp, char **argvp) { int start = 0x0001; int end = 0xffff; if (conn_state != STATE_CONNECTED) { failed("Disconnected\n"); return; } if (argcp > 1) { start = strtohandle(argvp[1]); if (start < 0) { error("Invalid start handle: %s\n", argvp[1]); return; } end = start; } if (argcp > 2) { end = strtohandle(argvp[2]); if (end < 0) { error("Invalid end handle: %s\n", argvp[2]); return; } } gatt_find_included(attrib, start, end, included_cb, NULL); } static void cmd_char(int argcp, char **argvp) { int start = 0x0001; int end = 0xffff; if (conn_state != STATE_CONNECTED) { failed("Disconnected\n"); return; } if (argcp > 1) { start = strtohandle(argvp[1]); if (start < 0) { error("Invalid start handle: %s\n", argvp[1]); return; } } if (argcp > 2) { end = strtohandle(argvp[2]); if (end < 0) { error("Invalid end handle: %s\n", argvp[2]); return; } } if (argcp > 3) { bt_uuid_t uuid; if (bt_string_to_uuid(&uuid, argvp[3]) < 0) { error("Invalid UUID\n"); return; } gatt_discover_char(attrib, start, end, &uuid, char_cb, NULL); return; } gatt_discover_char(attrib, start, end, NULL, char_cb, NULL); } static void cmd_char_desc(int argcp, char **argvp) { if (conn_state != STATE_CONNECTED) { failed("Disconnected\n"); return; } if (argcp > 1) { start = strtohandle(argvp[1]); if (start < 0) { error("Invalid start handle: %s\n", argvp[1]); return; } } else start = 0x0001; if (argcp > 2) { end = strtohandle(argvp[2]); if (end < 0) { error("Invalid end handle: %s\n", argvp[2]); return; } } else end = 0xffff; gatt_discover_desc(attrib, start, end, NULL, char_desc_cb, NULL); } static void cmd_read_hnd(int argcp, char **argvp) { int handle; if (conn_state != STATE_CONNECTED) { failed("Disconnected\n"); return; } if (argcp < 2) { error("Missing argument: handle\n"); return; } handle = strtohandle(argvp[1]); if (handle < 0) { error("Invalid handle: %s\n", argvp[1]); return; } gatt_read_char(attrib, handle, char_read_cb, attrib); } static void cmd_read_uuid(int argcp, char **argvp) { int start = 0x0001; int end = 0xffff; bt_uuid_t uuid; if (conn_state != STATE_CONNECTED) { failed("Disconnected\n"); return; } if (argcp < 2) { error("Missing argument: UUID\n"); return; } if (bt_string_to_uuid(&uuid, argvp[1]) < 0) { error("Invalid UUID\n"); return; } if (argcp > 2) { start = strtohandle(argvp[2]); if (start < 0) { error("Invalid start handle: %s\n", argvp[1]); return; } } if (argcp > 3) { end = strtohandle(argvp[3]); if (end < 0) { error("Invalid end handle: %s\n", argvp[2]); return; } } gatt_read_char_by_uuid(attrib, start, end, &uuid, char_read_by_uuid_cb, NULL); } static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { if (status != 0) { error("Characteristic Write Request failed: " "%s\n", att_ecode2str(status)); return; } if (!dec_write_resp(pdu, plen) && !dec_exec_write_resp(pdu, plen)) { error("Protocol error\n"); return; } rl_printf("Characteristic value was written successfully\n"); } static void cmd_char_write(int argcp, char **argvp) { uint8_t *value; size_t plen; int handle; if (conn_state != STATE_CONNECTED) { failed("Disconnected\n"); return; } if (argcp < 3) { rl_printf("Usage: %s \n", argvp[0]); return; } handle = strtohandle(argvp[1]); if (handle <= 0) { error("A valid handle is required\n"); return; } plen = gatt_attr_data_from_string(argvp[2], &value); if (plen == 0) { error("Invalid value\n"); return; } if (g_strcmp0("char-write-req", argvp[0]) == 0) gatt_write_char(attrib, handle, value, plen, char_write_req_cb, NULL); else gatt_write_cmd(attrib, handle, value, plen, NULL, NULL); g_free(value); } static void cmd_sec_level(int argcp, char **argvp) { GError *gerr = NULL; BtIOSecLevel sec_level; if (argcp < 2) { rl_printf("sec-level: %s\n", opt_sec_level); return; } if (strcasecmp(argvp[1], "medium") == 0) sec_level = BT_IO_SEC_MEDIUM; else if (strcasecmp(argvp[1], "high") == 0) sec_level = BT_IO_SEC_HIGH; else if (strcasecmp(argvp[1], "low") == 0) sec_level = BT_IO_SEC_LOW; else { rl_printf("Allowed values: low | medium | high\n"); return; } g_free(opt_sec_level); opt_sec_level = g_strdup(argvp[1]); if (conn_state != STATE_CONNECTED) return; if (opt_psm) { rl_printf("Change will take effect on reconnection\n"); return; } bt_io_set(iochannel, &gerr, BT_IO_OPT_SEC_LEVEL, sec_level, BT_IO_OPT_INVALID); if (gerr) { error("%s\n", gerr->message); g_error_free(gerr); } } static void exchange_mtu_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { uint16_t mtu; if (status != 0) { error("Exchange MTU Request failed: %s\n", att_ecode2str(status)); return; } if (!dec_mtu_resp(pdu, plen, &mtu)) { error("Protocol error\n"); return; } mtu = MIN(mtu, opt_mtu); /* Set new value for MTU in client */ if (g_attrib_set_mtu(attrib, mtu)) rl_printf("MTU was exchanged successfully: %d\n", mtu); else error("Error exchanging MTU\n"); } static void cmd_mtu(int argcp, char **argvp) { if (conn_state != STATE_CONNECTED) { failed("Disconnected\n"); return; } if (opt_psm) { failed("Operation is only available for LE transport.\n"); return; } if (argcp < 2) { rl_printf("Usage: mtu \n"); return; } if (opt_mtu) { failed("MTU exchange can only occur once per connection.\n"); return; } errno = 0; opt_mtu = strtoll(argvp[1], NULL, 0); if (errno != 0 || opt_mtu < ATT_DEFAULT_LE_MTU) { error("Invalid value. Minimum MTU size is %d\n", ATT_DEFAULT_LE_MTU); return; } gatt_exchange_mtu(attrib, opt_mtu, exchange_mtu_cb, NULL); } static const struct { const char *cmd; void (*func)(int argcp, char **argvp); const char *params; const char *desc; } commands[] = { { "help", cmd_help, "", "Show this help"}, { "exit", cmd_exit, "", "Exit interactive mode" }, { "quit", cmd_exit, "", "Exit interactive mode" }, { "connect", cmd_connect, "[address [address type]]", "Connect to a remote device" }, { "disconnect", cmd_disconnect, "", "Disconnect from a remote device" }, { "primary", cmd_primary, "[UUID]", "Primary Service Discovery" }, { "included", cmd_included, "[start hnd [end hnd]]", "Find Included Services" }, { "characteristics", cmd_char, "[start hnd [end hnd [UUID]]]", "Characteristics Discovery" }, { "char-desc", cmd_char_desc, "[start hnd] [end hnd]", "Characteristics Descriptor Discovery" }, { "char-read-hnd", cmd_read_hnd, "", "Characteristics Value/Descriptor Read by handle" }, { "char-read-uuid", cmd_read_uuid, " [start hnd] [end hnd]", "Characteristics Value/Descriptor Read by UUID" }, { "char-write-req", cmd_char_write, " ", "Characteristic Value Write (Write Request)" }, { "char-write-cmd", cmd_char_write, " ", "Characteristic Value Write (No response)" }, { "sec-level", cmd_sec_level, "[low | medium | high]", "Set security level. Default: low" }, { "mtu", cmd_mtu, "", "Exchange MTU for GATT/ATT" }, { NULL, NULL, NULL} }; static void cmd_help(int argcp, char **argvp) { int i; for (i = 0; commands[i].cmd; i++) rl_printf("%-15s %-30s %s\n", commands[i].cmd, commands[i].params, commands[i].desc); } static void parse_line(char *line_read) { char **argvp; int argcp; int i; if (line_read == NULL) { rl_printf("\n"); cmd_exit(0, NULL); return; } line_read = g_strstrip(line_read); if (*line_read == '\0') goto done; add_history(line_read); if (g_shell_parse_argv(line_read, &argcp, &argvp, NULL) == FALSE) goto done; for (i = 0; commands[i].cmd; i++) if (strcasecmp(commands[i].cmd, argvp[0]) == 0) break; if (commands[i].cmd) commands[i].func(argcp, argvp); else error("%s: command not found\n", argvp[0]); g_strfreev(argvp); done: free(line_read); } static gboolean prompt_read(GIOChannel *chan, GIOCondition cond, gpointer user_data) { if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) { g_io_channel_unref(chan); return FALSE; } rl_callback_read_char(); return TRUE; } static char *completion_generator(const char *text, int state) { static int index = 0, len = 0; const char *cmd = NULL; if (state == 0) { index = 0; len = strlen(text); } while ((cmd = commands[index].cmd) != NULL) { index++; if (strncmp(cmd, text, len) == 0) return strdup(cmd); } return NULL; } static char **commands_completion(const char *text, int start, int end) { if (start == 0) return rl_completion_matches(text, &completion_generator); else return NULL; } static guint setup_standard_input(void) { GIOChannel *channel; guint source; channel = g_io_channel_unix_new(fileno(stdin)); source = g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, prompt_read, NULL); g_io_channel_unref(channel); return source; } static gboolean signal_handler(GIOChannel *channel, GIOCondition condition, gpointer user_data) { static unsigned int __terminated = 0; struct signalfd_siginfo si; ssize_t result; int fd; if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { g_main_loop_quit(event_loop); return FALSE; } fd = g_io_channel_unix_get_fd(channel); result = read(fd, &si, sizeof(si)); if (result != sizeof(si)) return FALSE; switch (si.ssi_signo) { case SIGINT: rl_replace_line("", 0); rl_crlf(); rl_on_new_line(); rl_redisplay(); break; case SIGTERM: if (__terminated == 0) { rl_replace_line("", 0); rl_crlf(); g_main_loop_quit(event_loop); } __terminated = 1; break; } return TRUE; } static guint setup_signalfd(void) { GIOChannel *channel; guint source; sigset_t mask; int fd; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { perror("Failed to set signal mask"); return 0; } fd = signalfd(-1, &mask, 0); if (fd < 0) { perror("Failed to create signal descriptor"); return 0; } channel = g_io_channel_unix_new(fd); g_io_channel_set_close_on_unref(channel, TRUE); g_io_channel_set_encoding(channel, NULL, NULL); g_io_channel_set_buffered(channel, FALSE); source = g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, signal_handler, NULL); g_io_channel_unref(channel); return source; } int interactive(const char *src, const char *dst, const char *dst_type, int psm) { guint input; guint signal; opt_sec_level = g_strdup("low"); opt_src = g_strdup(src); opt_dst = g_strdup(dst); opt_dst_type = g_strdup(dst_type); opt_psm = psm; prompt = g_string_new(NULL); event_loop = g_main_loop_new(NULL, FALSE); input = setup_standard_input(); signal = setup_signalfd(); rl_attempted_completion_function = commands_completion; rl_erase_empty_line = 1; rl_callback_handler_install(get_prompt(), parse_line); g_main_loop_run(event_loop); rl_callback_handler_remove(); cmd_disconnect(0, NULL); g_source_remove(input); g_source_remove(signal); g_main_loop_unref(event_loop); g_string_free(prompt, TRUE); g_free(opt_src); g_free(opt_dst); g_free(opt_sec_level); return 0; } bluez-5.87/attrib/PaxHeaders/utils.c0000644000000000000000000000005015200607060014440 xustar0020 atime=1783092682 20 ctime=1783094101 bluez-5.87/attrib/utils.c0000644000000000000000000000432415200607060014124 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011 Nokia Corporation * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/hci_lib.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/shared/att-types.h" #include "btio/btio.h" #include "att.h" #include "gattrib.h" #include "gatt.h" #include "gatttool.h" GIOChannel *gatt_connect(const char *src, const char *dst, const char *dst_type, const char *sec_level, int psm, int mtu, BtIOConnect connect_cb, GError **gerr) { GIOChannel *chan; bdaddr_t sba, dba; uint8_t dest_type; GError *tmp_err = NULL; BtIOSecLevel sec; str2ba(dst, &dba); /* Local adapter */ if (src != NULL) { if (!strncmp(src, "hci", 3)) hci_devba(atoi(src + 3), &sba); else str2ba(src, &sba); } else bacpy(&sba, BDADDR_ANY); /* Not used for BR/EDR */ if (strcmp(dst_type, "random") == 0) dest_type = BDADDR_LE_RANDOM; else dest_type = BDADDR_LE_PUBLIC; if (strcmp(sec_level, "medium") == 0) sec = BT_IO_SEC_MEDIUM; else if (strcmp(sec_level, "high") == 0) sec = BT_IO_SEC_HIGH; else sec = BT_IO_SEC_LOW; if (psm == 0) chan = bt_io_connect(connect_cb, NULL, NULL, &tmp_err, BT_IO_OPT_SOURCE_BDADDR, &sba, BT_IO_OPT_SOURCE_TYPE, BDADDR_LE_PUBLIC, BT_IO_OPT_DEST_BDADDR, &dba, BT_IO_OPT_DEST_TYPE, dest_type, BT_IO_OPT_CID, BT_ATT_CID, BT_IO_OPT_SEC_LEVEL, sec, BT_IO_OPT_INVALID); else chan = bt_io_connect(connect_cb, NULL, NULL, &tmp_err, BT_IO_OPT_SOURCE_BDADDR, &sba, BT_IO_OPT_DEST_BDADDR, &dba, BT_IO_OPT_PSM, psm, BT_IO_OPT_IMTU, mtu, BT_IO_OPT_SEC_LEVEL, sec, BT_IO_OPT_INVALID); if (tmp_err) { g_propagate_error(gerr, tmp_err); return NULL; } return chan; } size_t gatt_attr_data_from_string(const char *str, uint8_t **data) { char tmp[3]; size_t size, i; size = strlen(str) / 2; *data = g_try_malloc0(size); if (*data == NULL) return 0; tmp[2] = '\0'; for (i = 0; i < size; i++) { memcpy(tmp, str + (i * 2), 2); (*data)[i] = (uint8_t) strtol(tmp, NULL, 16); } return size; } bluez-5.87/attrib/PaxHeaders/gatt.c0000644000000000000000000000005015063045254014247 xustar0020 atime=1783093725 20 ctime=1783094100 bluez-5.87/attrib/gatt.c0000644000000000000000000006514215063045254013740 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2010 Nokia Corporation * Copyright (C) 2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "bluetooth/uuid.h" #include "src/shared/util.h" #include "att.h" #include "gattrib.h" #include "gatt.h" struct discover_primary { int ref; GAttrib *attrib; unsigned int id; bt_uuid_t uuid; uint16_t start; GSList *primaries; gatt_cb_t cb; void *user_data; }; /* Used for the Included Services Discovery (ISD) procedure */ struct included_discovery { GAttrib *attrib; unsigned int id; int refs; int err; uint16_t start_handle; uint16_t end_handle; GSList *includes; gatt_cb_t cb; void *user_data; }; struct included_uuid_query { struct included_discovery *isd; struct gatt_included *included; }; struct discover_char { int ref; GAttrib *attrib; unsigned int id; bt_uuid_t *uuid; uint16_t end; uint16_t start; GSList *characteristics; gatt_cb_t cb; void *user_data; }; struct discover_desc { int ref; GAttrib *attrib; unsigned int id; bt_uuid_t *uuid; uint16_t start; uint16_t end; GSList *descriptors; gatt_cb_t cb; void *user_data; }; static void discover_primary_unref(void *data) { struct discover_primary *dp = data; dp->ref--; if (dp->ref > 0) return; g_slist_free_full(dp->primaries, g_free); g_attrib_unref(dp->attrib); g_free(dp); } static struct discover_primary *discover_primary_ref( struct discover_primary *dp) { dp->ref++; return dp; } static struct included_discovery *isd_ref(struct included_discovery *isd) { __sync_fetch_and_add(&isd->refs, 1); return isd; } static void isd_unref(struct included_discovery *isd) { if (__sync_sub_and_fetch(&isd->refs, 1) > 0) return; if (isd->err) isd->cb(isd->err, NULL, isd->user_data); else isd->cb(isd->err, isd->includes, isd->user_data); g_slist_free_full(isd->includes, g_free); g_attrib_unref(isd->attrib); g_free(isd); } static void discover_char_unref(void *data) { struct discover_char *dc = data; dc->ref--; if (dc->ref > 0) return; g_slist_free_full(dc->characteristics, g_free); g_attrib_unref(dc->attrib); free(dc->uuid); g_free(dc); } static struct discover_char *discover_char_ref(struct discover_char *dc) { dc->ref++; return dc; } static void discover_desc_unref(void *data) { struct discover_desc *dd = data; dd->ref--; if (dd->ref > 0) return; g_slist_free_full(dd->descriptors, g_free); g_attrib_unref(dd->attrib); free(dd->uuid); g_free(dd); } static struct discover_desc *discover_desc_ref(struct discover_desc *dd) { dd->ref++; return dd; } static void put_uuid_le(const bt_uuid_t *uuid, void *dst) { if (uuid->type == BT_UUID16) put_le16(uuid->value.u16, dst); else /* Convert from 128-bit BE to LE */ bswap_128(&uuid->value.u128, dst); } static void get_uuid128(uint8_t type, const void *val, bt_uuid_t *uuid) { if (type == BT_UUID16) { bt_uuid_t uuid16; bt_uuid16_create(&uuid16, get_le16(val)); bt_uuid_to_uuid128(&uuid16, uuid); } else { uint128_t u128; /* Convert from 128-bit LE to BE */ bswap_128(val, &u128); bt_uuid128_create(uuid, u128); } } static guint16 encode_discover_primary(uint16_t start, uint16_t end, bt_uuid_t *uuid, uint8_t *pdu, size_t len) { bt_uuid_t prim; guint16 plen; bt_uuid16_create(&prim, GATT_PRIM_SVC_UUID); if (uuid == NULL) { /* Discover all primary services */ plen = enc_read_by_grp_req(start, end, &prim, pdu, len); } else { uint8_t value[16]; size_t vlen; /* Discover primary service by service UUID */ put_uuid_le(uuid, value); vlen = bt_uuid_len(uuid); plen = enc_find_by_type_req(start, end, &prim, value, vlen, pdu, len); } return plen; } static void primary_by_uuid_cb(guint8 status, const guint8 *ipdu, guint16 iplen, gpointer user_data) { struct discover_primary *dp = user_data; GSList *ranges, *last; struct att_range *range; uint8_t *buf; guint16 oplen; int err = 0; size_t buflen; if (status) { err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status; goto done; } ranges = dec_find_by_type_resp(ipdu, iplen); if (ranges == NULL) goto done; dp->primaries = g_slist_concat(dp->primaries, ranges); last = g_slist_last(ranges); range = last->data; if (range->end == 0xffff) goto done; /* * If last handle is lower from previous start handle then it is smth * wrong. Let's stop search, otherwise we might enter infinite loop. */ if (range->end < dp->start) { err = ATT_ECODE_UNLIKELY; goto done; } dp->start = range->end + 1; buf = g_attrib_get_buffer(dp->attrib, &buflen); oplen = encode_discover_primary(dp->start, 0xffff, &dp->uuid, buf, buflen); if (oplen == 0) goto done; g_attrib_send(dp->attrib, dp->id, buf, oplen, primary_by_uuid_cb, discover_primary_ref(dp), discover_primary_unref); return; done: dp->cb(err, dp->primaries, dp->user_data); } static void primary_all_cb(guint8 status, const guint8 *ipdu, guint16 iplen, gpointer user_data) { struct discover_primary *dp = user_data; struct att_data_list *list; unsigned int i, err; uint16_t start, end; uint8_t type; if (status) { err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status; goto done; } list = dec_read_by_grp_resp(ipdu, iplen); if (list == NULL) { err = ATT_ECODE_IO; goto done; } if (list->len == 6) type = BT_UUID16; else if (list->len == 20) type = BT_UUID128; else { att_data_list_free(list); err = ATT_ECODE_INVALID_PDU; goto done; } for (i = 0, end = 0; i < list->num; i++) { const uint8_t *data = list->data[i]; struct gatt_primary *primary; bt_uuid_t uuid128; start = get_le16(&data[0]); end = get_le16(&data[2]); get_uuid128(type, &data[4], &uuid128); primary = g_try_new0(struct gatt_primary, 1); if (!primary) { att_data_list_free(list); err = ATT_ECODE_INSUFF_RESOURCES; goto done; } primary->range.start = start; primary->range.end = end; bt_uuid_to_string(&uuid128, primary->uuid, sizeof(primary->uuid)); dp->primaries = g_slist_append(dp->primaries, primary); } att_data_list_free(list); err = 0; /* * If last handle is lower from previous start handle then it is smth * wrong. Let's stop search, otherwise we might enter infinite loop. */ if (end < dp->start) { err = ATT_ECODE_UNLIKELY; goto done; } dp->start = end + 1; if (end != 0xffff) { size_t buflen; uint8_t *buf = g_attrib_get_buffer(dp->attrib, &buflen); guint16 oplen = encode_discover_primary(dp->start, 0xffff, NULL, buf, buflen); g_attrib_send(dp->attrib, dp->id, buf, oplen, primary_all_cb, discover_primary_ref(dp), discover_primary_unref); return; } done: dp->cb(err, dp->primaries, dp->user_data); } guint gatt_discover_primary(GAttrib *attrib, bt_uuid_t *uuid, gatt_cb_t func, gpointer user_data) { struct discover_primary *dp; size_t buflen; uint8_t *buf = g_attrib_get_buffer(attrib, &buflen); GAttribResultFunc cb; guint16 plen; plen = encode_discover_primary(0x0001, 0xffff, uuid, buf, buflen); if (plen == 0) return 0; dp = g_try_new0(struct discover_primary, 1); if (dp == NULL) return 0; dp->attrib = g_attrib_ref(attrib); dp->cb = func; dp->user_data = user_data; dp->start = 0x0001; if (uuid) { dp->uuid = *uuid; cb = primary_by_uuid_cb; } else cb = primary_all_cb; dp->id = g_attrib_send(attrib, 0, buf, plen, cb, discover_primary_ref(dp), discover_primary_unref); return dp->id; } static void resolve_included_uuid_cb(uint8_t status, const uint8_t *pdu, uint16_t len, gpointer user_data) { struct included_uuid_query *query = user_data; struct included_discovery *isd = query->isd; struct gatt_included *incl = query->included; unsigned int err = status; bt_uuid_t uuid128; size_t buflen; uint8_t *buf; if (err) goto done; buf = g_attrib_get_buffer(isd->attrib, &buflen); if (dec_read_resp(pdu, len, buf, buflen) != 16) { err = ATT_ECODE_IO; goto done; } get_uuid128(BT_UUID128, buf, &uuid128); bt_uuid_to_string(&uuid128, incl->uuid, sizeof(incl->uuid)); isd->includes = g_slist_append(isd->includes, incl); query->included = NULL; done: if (isd->err == 0) isd->err = err; } static void inc_query_free(void *data) { struct included_uuid_query *query = data; isd_unref(query->isd); g_free(query->included); g_free(query); } static guint resolve_included_uuid(struct included_discovery *isd, struct gatt_included *incl) { struct included_uuid_query *query; size_t buflen; uint8_t *buf = g_attrib_get_buffer(isd->attrib, &buflen); guint16 oplen = enc_read_req(incl->range.start, buf, buflen); query = g_new0(struct included_uuid_query, 1); query->isd = isd_ref(isd); query->included = incl; return g_attrib_send(isd->attrib, query->isd->id, buf, oplen, resolve_included_uuid_cb, query, inc_query_free); } static struct gatt_included *included_from_buf(const uint8_t *buf, gsize len) { struct gatt_included *incl = g_new0(struct gatt_included, 1); incl->handle = get_le16(&buf[0]); incl->range.start = get_le16(&buf[2]); incl->range.end = get_le16(&buf[4]); if (len == 8) { bt_uuid_t uuid128; get_uuid128(BT_UUID16, &buf[6], &uuid128); bt_uuid_to_string(&uuid128, incl->uuid, sizeof(incl->uuid)); } return incl; } static void find_included_cb(uint8_t status, const uint8_t *pdu, uint16_t len, gpointer user_data); static guint find_included(struct included_discovery *isd, uint16_t start) { bt_uuid_t uuid; size_t buflen; uint8_t *buf = g_attrib_get_buffer(isd->attrib, &buflen); guint16 oplen; bt_uuid16_create(&uuid, GATT_INCLUDE_UUID); oplen = enc_read_by_type_req(start, isd->end_handle, &uuid, buf, buflen); /* If id != 0 it means we are in the middle of include search */ if (isd->id) return g_attrib_send(isd->attrib, isd->id, buf, oplen, find_included_cb, isd_ref(isd), (GDestroyNotify) isd_unref); /* This is first call from the gattrib user */ isd->id = g_attrib_send(isd->attrib, 0, buf, oplen, find_included_cb, isd_ref(isd), (GDestroyNotify) isd_unref); return isd->id; } static void find_included_cb(uint8_t status, const uint8_t *pdu, uint16_t len, gpointer user_data) { struct included_discovery *isd = user_data; uint16_t last_handle = isd->end_handle; unsigned int err = status; struct att_data_list *list; int i; if (err == ATT_ECODE_ATTR_NOT_FOUND) err = 0; if (status) goto done; list = dec_read_by_type_resp(pdu, len); if (list == NULL) { err = ATT_ECODE_IO; goto done; } if (list->len != 6 && list->len != 8) { err = ATT_ECODE_IO; att_data_list_free(list); goto done; } for (i = 0; i < list->num; i++) { struct gatt_included *incl; incl = included_from_buf(list->data[i], list->len); last_handle = incl->handle; /* 128 bit UUID, needs resolving */ if (list->len == 6) { resolve_included_uuid(isd, incl); continue; } isd->includes = g_slist_append(isd->includes, incl); } att_data_list_free(list); /* * If last handle is lower from previous start handle then it is smth * wrong. Let's stop search, otherwise we might enter infinite loop. */ if (last_handle < isd->start_handle) { isd->err = ATT_ECODE_UNLIKELY; goto done; } isd->start_handle = last_handle + 1; if (last_handle < isd->end_handle) find_included(isd, isd->start_handle); done: if (isd->err == 0) isd->err = err; } unsigned int gatt_find_included(GAttrib *attrib, uint16_t start, uint16_t end, gatt_cb_t func, gpointer user_data) { struct included_discovery *isd; isd = g_new0(struct included_discovery, 1); isd->attrib = g_attrib_ref(attrib); isd->start_handle = start; isd->end_handle = end; isd->cb = func; isd->user_data = user_data; return find_included(isd, start); } static void char_discovered_cb(guint8 status, const guint8 *ipdu, guint16 iplen, gpointer user_data) { struct discover_char *dc = user_data; struct att_data_list *list; unsigned int i, err = 0; uint16_t last = 0; uint8_t type; /* We have all the characteristic now, lets send it up */ if (status == ATT_ECODE_ATTR_NOT_FOUND) { err = dc->characteristics ? 0 : status; goto done; } if (status) { err = status; goto done; } list = dec_read_by_type_resp(ipdu, iplen); if (list == NULL) { err = ATT_ECODE_IO; goto done; } if (list->len == 7) type = BT_UUID16; else type = BT_UUID128; for (i = 0; i < list->num; i++) { uint8_t *value = list->data[i]; struct gatt_char *chars; bt_uuid_t uuid128; last = get_le16(value); get_uuid128(type, &value[5], &uuid128); if (dc->uuid && bt_uuid_cmp(dc->uuid, &uuid128)) continue; chars = g_try_new0(struct gatt_char, 1); if (!chars) { att_data_list_free(list); err = ATT_ECODE_INSUFF_RESOURCES; goto done; } chars->handle = last; chars->properties = value[2]; chars->value_handle = get_le16(&value[3]); bt_uuid_to_string(&uuid128, chars->uuid, sizeof(chars->uuid)); dc->characteristics = g_slist_append(dc->characteristics, chars); } att_data_list_free(list); /* * If last handle is lower from previous start handle then it is smth * wrong. Let's stop search, otherwise we might enter infinite loop. */ if (last < dc->start) { err = ATT_ECODE_UNLIKELY; goto done; } dc->start = last + 1; if (last != 0 && (dc->start < dc->end)) { bt_uuid_t uuid; guint16 oplen; size_t buflen; uint8_t *buf; buf = g_attrib_get_buffer(dc->attrib, &buflen); bt_uuid16_create(&uuid, GATT_CHARAC_UUID); oplen = enc_read_by_type_req(dc->start, dc->end, &uuid, buf, buflen); if (oplen == 0) return; g_attrib_send(dc->attrib, dc->id, buf, oplen, char_discovered_cb, discover_char_ref(dc), discover_char_unref); return; } done: dc->cb(err, dc->characteristics, dc->user_data); } guint gatt_discover_char(GAttrib *attrib, uint16_t start, uint16_t end, bt_uuid_t *uuid, gatt_cb_t func, gpointer user_data) { size_t buflen; uint8_t *buf = g_attrib_get_buffer(attrib, &buflen); struct discover_char *dc; bt_uuid_t type_uuid; guint16 plen; bt_uuid16_create(&type_uuid, GATT_CHARAC_UUID); plen = enc_read_by_type_req(start, end, &type_uuid, buf, buflen); if (plen == 0) return 0; dc = g_try_new0(struct discover_char, 1); if (dc == NULL) return 0; dc->attrib = g_attrib_ref(attrib); dc->cb = func; dc->user_data = user_data; dc->end = end; dc->start = start; dc->uuid = util_memdup(uuid, sizeof(bt_uuid_t)); dc->id = g_attrib_send(attrib, 0, buf, plen, char_discovered_cb, discover_char_ref(dc), discover_char_unref); return dc->id; } guint gatt_read_char_by_uuid(GAttrib *attrib, uint16_t start, uint16_t end, bt_uuid_t *uuid, GAttribResultFunc func, gpointer user_data) { size_t buflen; uint8_t *buf = g_attrib_get_buffer(attrib, &buflen); guint16 plen; plen = enc_read_by_type_req(start, end, uuid, buf, buflen); if (plen == 0) return 0; return g_attrib_send(attrib, 0, buf, plen, func, user_data, NULL); } struct read_long_data { GAttrib *attrib; GAttribResultFunc func; gpointer user_data; guint8 *buffer; guint16 size; guint16 handle; guint id; int ref; }; static void read_long_destroy(gpointer user_data) { struct read_long_data *long_read = user_data; if (__sync_sub_and_fetch(&long_read->ref, 1) > 0) return; g_attrib_unref(long_read->attrib); if (long_read->buffer != NULL) g_free(long_read->buffer); g_free(long_read); } static void read_blob_helper(guint8 status, const guint8 *rpdu, guint16 rlen, gpointer user_data) { struct read_long_data *long_read = user_data; uint8_t *buf; size_t buflen; guint8 *tmp; guint16 plen; guint id; if (status != 0 || rlen == 1) { status = 0; goto done; } tmp = g_try_realloc(long_read->buffer, long_read->size + rlen - 1); if (tmp == NULL) { status = ATT_ECODE_INSUFF_RESOURCES; goto done; } memcpy(&tmp[long_read->size], &rpdu[1], rlen - 1); long_read->buffer = tmp; long_read->size += rlen - 1; buf = g_attrib_get_buffer(long_read->attrib, &buflen); if (rlen < buflen) goto done; plen = enc_read_blob_req(long_read->handle, long_read->size - 1, buf, buflen); id = g_attrib_send(long_read->attrib, long_read->id, buf, plen, read_blob_helper, long_read, read_long_destroy); if (id != 0) { __sync_fetch_and_add(&long_read->ref, 1); return; } status = ATT_ECODE_IO; done: long_read->func(status, long_read->buffer, long_read->size, long_read->user_data); } static void read_char_helper(guint8 status, const guint8 *rpdu, guint16 rlen, gpointer user_data) { struct read_long_data *long_read = user_data; size_t buflen; uint8_t *buf = g_attrib_get_buffer(long_read->attrib, &buflen); guint16 plen; guint id; if (status != 0 || rlen < buflen) goto done; long_read->buffer = g_malloc(rlen); if (long_read->buffer == NULL) { status = ATT_ECODE_INSUFF_RESOURCES; goto done; } memcpy(long_read->buffer, rpdu, rlen); long_read->size = rlen; plen = enc_read_blob_req(long_read->handle, rlen - 1, buf, buflen); id = g_attrib_send(long_read->attrib, long_read->id, buf, plen, read_blob_helper, long_read, read_long_destroy); if (id != 0) { __sync_fetch_and_add(&long_read->ref, 1); return; } status = ATT_ECODE_IO; done: long_read->func(status, rpdu, rlen, long_read->user_data); } guint gatt_read_char(GAttrib *attrib, uint16_t handle, GAttribResultFunc func, gpointer user_data) { uint8_t *buf; size_t buflen; guint16 plen; guint id; struct read_long_data *long_read; long_read = g_try_new0(struct read_long_data, 1); if (long_read == NULL) return 0; long_read->attrib = g_attrib_ref(attrib); long_read->func = func; long_read->user_data = user_data; long_read->handle = handle; buf = g_attrib_get_buffer(attrib, &buflen); plen = enc_read_req(handle, buf, buflen); id = g_attrib_send(attrib, 0, buf, plen, read_char_helper, long_read, read_long_destroy); if (id == 0) { g_attrib_unref(long_read->attrib); g_free(long_read); } else { __sync_fetch_and_add(&long_read->ref, 1); long_read->id = id; } return id; } struct write_long_data { GAttrib *attrib; GAttribResultFunc func; gpointer user_data; guint16 handle; uint16_t offset; uint8_t *value; size_t vlen; }; static guint execute_write(GAttrib *attrib, uint8_t flags, GAttribResultFunc func, gpointer user_data) { uint8_t *buf; size_t buflen; guint16 plen; buf = g_attrib_get_buffer(attrib, &buflen); plen = enc_exec_write_req(flags, buf, buflen); if (plen == 0) return 0; return g_attrib_send(attrib, 0, buf, plen, func, user_data, NULL); } static guint prepare_write(struct write_long_data *long_write); static void prepare_write_cb(guint8 status, const guint8 *rpdu, guint16 rlen, gpointer user_data) { struct write_long_data *long_write = user_data; if (status != 0) { long_write->func(status, rpdu, rlen, long_write->user_data); return; } /* Skip Prepare Write Response PDU header (5 bytes) */ long_write->offset += rlen - 5; if (long_write->offset == long_write->vlen) { execute_write(long_write->attrib, ATT_WRITE_ALL_PREP_WRITES, long_write->func, long_write->user_data); free(long_write->value); g_free(long_write); return; } prepare_write(long_write); } static guint prepare_write(struct write_long_data *long_write) { GAttrib *attrib = long_write->attrib; uint16_t handle = long_write->handle; uint16_t offset = long_write->offset; uint8_t *buf, *value = long_write->value + offset; size_t buflen, vlen = long_write->vlen - offset; guint16 plen; buf = g_attrib_get_buffer(attrib, &buflen); plen = enc_prep_write_req(handle, offset, value, vlen, buf, buflen); if (plen == 0) return 0; return g_attrib_send(attrib, 0, buf, plen, prepare_write_cb, long_write, NULL); } guint gatt_write_char(GAttrib *attrib, uint16_t handle, const uint8_t *value, size_t vlen, GAttribResultFunc func, gpointer user_data) { uint8_t *buf; size_t buflen; struct write_long_data *long_write; buf = g_attrib_get_buffer(attrib, &buflen); /* Use Write Request if payload fits on a single transfer, including 3 * bytes for the header. */ if (vlen <= buflen - 3) { uint16_t plen; plen = enc_write_req(handle, value, vlen, buf, buflen); if (plen == 0) return 0; return g_attrib_send(attrib, 0, buf, plen, func, user_data, NULL); } /* Write Long Characteristic Values */ long_write = g_try_new0(struct write_long_data, 1); if (long_write == NULL) return 0; long_write->attrib = attrib; long_write->func = func; long_write->user_data = user_data; long_write->handle = handle; long_write->value = util_memdup(value, vlen); long_write->vlen = vlen; return prepare_write(long_write); } guint gatt_execute_write(GAttrib *attrib, uint8_t flags, GAttribResultFunc func, gpointer user_data) { return execute_write(attrib, flags, func, user_data); } guint gatt_reliable_write_char(GAttrib *attrib, uint16_t handle, const uint8_t *value, size_t vlen, GAttribResultFunc func, gpointer user_data) { uint8_t *buf; guint16 plen; size_t buflen; buf = g_attrib_get_buffer(attrib, &buflen); plen = enc_prep_write_req(handle, 0, value, vlen, buf, buflen); if (!plen) return 0; return g_attrib_send(attrib, 0, buf, plen, func, user_data, NULL); } guint gatt_exchange_mtu(GAttrib *attrib, uint16_t mtu, GAttribResultFunc func, gpointer user_data) { uint8_t *buf; size_t buflen; guint16 plen; buf = g_attrib_get_buffer(attrib, &buflen); plen = enc_mtu_req(mtu, buf, buflen); return g_attrib_send(attrib, 0, buf, plen, func, user_data, NULL); } static void desc_discovered_cb(guint8 status, const guint8 *ipdu, guint16 iplen, gpointer user_data) { struct discover_desc *dd = user_data; struct att_data_list *list; unsigned int i, err = 0; guint8 format; uint16_t last = 0xffff; uint8_t type; gboolean uuid_found = FALSE; if (status == ATT_ECODE_ATTR_NOT_FOUND) { err = dd->descriptors ? 0 : status; goto done; } if (status) { err = status; goto done; } list = dec_find_info_resp(ipdu, iplen, &format); if (!list) { err = ATT_ECODE_IO; goto done; } if (format == ATT_FIND_INFO_RESP_FMT_16BIT) type = BT_UUID16; else type = BT_UUID128; for (i = 0; i < list->num; i++) { uint8_t *value = list->data[i]; struct gatt_desc *desc; bt_uuid_t uuid128; last = get_le16(value); get_uuid128(type, &value[2], &uuid128); if (dd->uuid) { if (bt_uuid_cmp(dd->uuid, &uuid128)) continue; else uuid_found = TRUE; } desc = g_try_new0(struct gatt_desc, 1); if (!desc) { att_data_list_free(list); err = ATT_ECODE_INSUFF_RESOURCES; goto done; } bt_uuid_to_string(&uuid128, desc->uuid, sizeof(desc->uuid)); desc->handle = last; if (type == BT_UUID16) desc->uuid16 = get_le16(&value[2]); dd->descriptors = g_slist_append(dd->descriptors, desc); if (uuid_found) break; } att_data_list_free(list); /* * If last handle is lower from previous start handle or if iterating * to the next handle from the last possible offset would overflow, then * something is wrong. Let's stop search, otherwise we might enter * infinite loop. */ if (last < dd->start || last == G_MAXUINT16) { err = ATT_ECODE_UNLIKELY; goto done; } dd->start = last + 1; if (last < dd->end && !uuid_found) { guint16 oplen; size_t buflen; uint8_t *buf; buf = g_attrib_get_buffer(dd->attrib, &buflen); oplen = enc_find_info_req(dd->start, dd->end, buf, buflen); if (oplen == 0) return; g_attrib_send(dd->attrib, dd->id, buf, oplen, desc_discovered_cb, discover_desc_ref(dd), discover_desc_unref); return; } done: dd->cb(err, dd->descriptors, dd->user_data); } guint gatt_discover_desc(GAttrib *attrib, uint16_t start, uint16_t end, bt_uuid_t *uuid, gatt_cb_t func, gpointer user_data) { size_t buflen; uint8_t *buf = g_attrib_get_buffer(attrib, &buflen); struct discover_desc *dd; guint16 plen; plen = enc_find_info_req(start, end, buf, buflen); if (plen == 0) return 0; dd = g_try_new0(struct discover_desc, 1); if (dd == NULL) return 0; dd->attrib = g_attrib_ref(attrib); dd->cb = func; dd->user_data = user_data; dd->start = start; dd->end = end; dd->uuid = util_memdup(uuid, sizeof(bt_uuid_t)); dd->id = g_attrib_send(attrib, 0, buf, plen, desc_discovered_cb, discover_desc_ref(dd), discover_desc_unref); return dd->id; } guint gatt_write_cmd(GAttrib *attrib, uint16_t handle, const uint8_t *value, int vlen, GDestroyNotify notify, gpointer user_data) { uint8_t *buf; size_t buflen; guint16 plen; buf = g_attrib_get_buffer(attrib, &buflen); plen = enc_write_cmd(handle, value, vlen, buf, buflen); return g_attrib_send(attrib, 0, buf, plen, NULL, user_data, notify); } guint gatt_signed_write_cmd(GAttrib *attrib, uint16_t handle, const uint8_t *value, int vlen, struct bt_crypto *crypto, const uint8_t csrk[16], uint32_t sign_cnt, GDestroyNotify notify, gpointer user_data) { uint8_t *buf; size_t buflen; guint16 plen; buf = g_attrib_get_buffer(attrib, &buflen); plen = enc_signed_write_cmd(handle, value, vlen, crypto, csrk, sign_cnt, buf, buflen); if (plen == 0) return 0; return g_attrib_send(attrib, 0, buf, plen, NULL, user_data, notify); } static sdp_data_t *proto_seq_find(sdp_list_t *proto_list) { sdp_list_t *list; uuid_t proto; sdp_uuid16_create(&proto, ATT_UUID); for (list = proto_list; list; list = list->next) { sdp_list_t *p; for (p = list->data; p; p = p->next) { sdp_data_t *seq = p->data; if (seq && seq->dtd == SDP_UUID16 && sdp_uuid16_cmp(&proto, &seq->val.uuid) == 0) return seq->next; } } return NULL; } static gboolean parse_proto_params(sdp_list_t *proto_list, uint16_t *psm, uint16_t *start, uint16_t *end) { sdp_data_t *seq1, *seq2; if (psm) *psm = sdp_get_proto_port(proto_list, L2CAP_UUID); /* Getting start and end handle */ seq1 = proto_seq_find(proto_list); if (!seq1 || seq1->dtd != SDP_UINT16) return FALSE; seq2 = seq1->next; if (!seq2 || seq2->dtd != SDP_UINT16) return FALSE; if (start) *start = seq1->val.uint16; if (end) *end = seq2->val.uint16; return TRUE; } gboolean gatt_parse_record(const sdp_record_t *rec, uuid_t *prim_uuid, uint16_t *psm, uint16_t *start, uint16_t *end) { sdp_list_t *list; uuid_t uuid; gboolean ret; if (sdp_get_service_classes(rec, &list) < 0) return FALSE; memcpy(&uuid, list->data, sizeof(uuid)); sdp_list_free(list, free); if (sdp_get_access_protos(rec, &list) < 0) return FALSE; ret = parse_proto_params(list, psm, start, end); sdp_list_foreach(list, (sdp_list_func_t) sdp_list_free, NULL); sdp_list_free(list, NULL); /* FIXME: replace by bt_uuid_t after uuid_t/sdp code cleanup */ if (ret && prim_uuid) memcpy(prim_uuid, &uuid, sizeof(uuid_t)); return ret; } bluez-5.87/attrib/PaxHeaders/gattrib.h0000644000000000000000000000005014447506754014765 xustar0020 atime=1783093674 20 ctime=1783094106 bluez-5.87/attrib/gattrib.h0000644000000000000000000000366714447506754014462 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2010 Nokia Corporation * Copyright (C) 2010 Marcel Holtmann * * */ #ifndef __GATTRIB_H #define __GATTRIB_H #ifdef __cplusplus extern "C" { #endif #define GATTRIB_ALL_REQS 0xFE #define GATTRIB_ALL_HANDLES 0x0000 struct bt_att; /* Forward declaration for compatibility */ struct bt_gatt_client; /* Forward declaration for compatibility */ struct _GAttrib; typedef struct _GAttrib GAttrib; typedef void (*GAttribResultFunc) (guint8 status, const guint8 *pdu, guint16 len, gpointer user_data); typedef void (*GAttribDisconnectFunc)(gpointer user_data); typedef void (*GAttribDebugFunc)(const char *str, gpointer user_data); typedef void (*GAttribNotifyFunc)(const guint8 *pdu, guint16 len, gpointer user_data); GAttrib *g_attrib_new(GIOChannel *io, guint16 mtu, bool ext_signed); GAttrib *g_attrib_ref(GAttrib *attrib); void g_attrib_unref(GAttrib *attrib); GIOChannel *g_attrib_get_channel(GAttrib *attrib); struct bt_att *g_attrib_get_att(GAttrib *attrib); gboolean g_attrib_set_destroy_function(GAttrib *attrib, GDestroyNotify destroy, gpointer user_data); guint g_attrib_send(GAttrib *attrib, guint id, const guint8 *pdu, guint16 len, GAttribResultFunc func, gpointer user_data, GDestroyNotify notify); gboolean g_attrib_cancel(GAttrib *attrib, guint id); gboolean g_attrib_cancel_all(GAttrib *attrib); guint g_attrib_register(GAttrib *attrib, guint8 opcode, guint16 handle, GAttribNotifyFunc func, gpointer user_data, GDestroyNotify notify); uint8_t *g_attrib_get_buffer(GAttrib *attrib, size_t *len); gboolean g_attrib_set_mtu(GAttrib *attrib, int mtu); gboolean g_attrib_attach_client(GAttrib *attrib, struct bt_gatt_client *client); gboolean g_attrib_unregister(GAttrib *attrib, guint id); gboolean g_attrib_unregister_all(GAttrib *attrib); #ifdef __cplusplus } #endif #endif bluez-5.87/attrib/PaxHeaders/att.c0000644000000000000000000000005015063045254014100 xustar0020 atime=1783093722 20 ctime=1783094100 bluez-5.87/attrib/att.c0000644000000000000000000005712115063045254013567 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2010 Nokia Corporation * Copyright (C) 2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "src/shared/util.h" #include "att.h" static inline void put_uuid_le(const bt_uuid_t *src, void *dst) { if (src->type == BT_UUID16) put_le16(src->value.u16, dst); else /* Convert from 128-bit BE to LE */ bswap_128(&src->value.u128, dst); } const char *att_ecode2str(uint8_t status) { switch (status) { case ATT_ECODE_INVALID_HANDLE: return "Invalid handle"; case ATT_ECODE_READ_NOT_PERM: return "Attribute can't be read"; case ATT_ECODE_WRITE_NOT_PERM: return "Attribute can't be written"; case ATT_ECODE_INVALID_PDU: return "Attribute PDU was invalid"; case ATT_ECODE_AUTHENTICATION: return "Attribute requires authentication before read/write"; case ATT_ECODE_REQ_NOT_SUPP: return "Server doesn't support the request received"; case ATT_ECODE_INVALID_OFFSET: return "Offset past the end of the attribute"; case ATT_ECODE_AUTHORIZATION: return "Attribute requires authorization before read/write"; case ATT_ECODE_PREP_QUEUE_FULL: return "Too many prepare writes have been queued"; case ATT_ECODE_ATTR_NOT_FOUND: return "No attribute found within the given range"; case ATT_ECODE_ATTR_NOT_LONG: return "Attribute can't be read/written using Read Blob Req"; case ATT_ECODE_INSUFF_ENCR_KEY_SIZE: return "Encryption Key Size is insufficient"; case ATT_ECODE_INVAL_ATTR_VALUE_LEN: return "Attribute value length is invalid"; case ATT_ECODE_UNLIKELY: return "Request attribute has encountered an unlikely error"; case ATT_ECODE_INSUFF_ENC: return "Encryption required before read/write"; case ATT_ECODE_UNSUPP_GRP_TYPE: return "Attribute type is not a supported grouping attribute"; case ATT_ECODE_INSUFF_RESOURCES: return "Insufficient Resources to complete the request"; case ATT_ECODE_IO: return "Internal application error: I/O"; case ATT_ECODE_TIMEOUT: return "A timeout occurred"; case ATT_ECODE_ABORTED: return "The operation was aborted"; default: return "Unexpected error code"; } } void att_data_list_free(struct att_data_list *list) { if (list == NULL) return; if (list->data) { int i; for (i = 0; i < list->num; i++) g_free(list->data[i]); } g_free(list->data); g_free(list); } struct att_data_list *att_data_list_alloc(uint16_t num, uint16_t len) { struct att_data_list *list; int i; if (len > UINT8_MAX) return NULL; list = g_new0(struct att_data_list, 1); list->len = len; list->num = num; list->data = g_malloc0(sizeof(uint8_t *) * num); for (i = 0; i < num; i++) list->data[i] = g_malloc0(sizeof(uint8_t) * len); return list; } static void get_uuid(uint8_t type, const void *val, bt_uuid_t *uuid) { if (type == BT_UUID16) bt_uuid16_create(uuid, get_le16(val)); else { uint128_t u128; /* Convert from 128-bit LE to BE */ bswap_128(val, &u128); bt_uuid128_create(uuid, u128); } } uint16_t enc_read_by_grp_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, uint8_t *pdu, size_t len) { uint16_t uuid_len; if (!uuid) return 0; if (uuid->type == BT_UUID16) uuid_len = 2; else if (uuid->type == BT_UUID128) uuid_len = 16; else return 0; /* Attribute Opcode (1 octet) */ pdu[0] = ATT_OP_READ_BY_GROUP_REQ; /* Starting Handle (2 octets) */ put_le16(start, &pdu[1]); /* Ending Handle (2 octets) */ put_le16(end, &pdu[3]); /* Attribute Group Type (2 or 16 octet UUID) */ put_uuid_le(uuid, &pdu[5]); return 5 + uuid_len; } uint16_t dec_read_by_grp_req(const uint8_t *pdu, size_t len, uint16_t *start, uint16_t *end, bt_uuid_t *uuid) { const size_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end); uint8_t type; if (pdu == NULL) return 0; if (start == NULL || end == NULL || uuid == NULL) return 0; if (pdu[0] != ATT_OP_READ_BY_GROUP_REQ) return 0; if (len == (min_len + 2)) type = BT_UUID16; else if (len == (min_len + 16)) type = BT_UUID128; else return 0; *start = get_le16(&pdu[1]); *end = get_le16(&pdu[3]); get_uuid(type, &pdu[5], uuid); return len; } uint16_t enc_read_by_grp_resp(struct att_data_list *list, uint8_t *pdu, size_t len) { int i; uint16_t w; uint8_t *ptr; if (list == NULL) return 0; if (len < list->len + sizeof(uint8_t) * 2) return 0; pdu[0] = ATT_OP_READ_BY_GROUP_RESP; pdu[1] = list->len; ptr = &pdu[2]; for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) { memcpy(ptr, list->data[i], list->len); ptr += list->len; w += list->len; } return w; } struct att_data_list *dec_read_by_grp_resp(const uint8_t *pdu, size_t len) { struct att_data_list *list; const uint8_t *ptr; uint16_t elen, num; int i; if (pdu[0] != ATT_OP_READ_BY_GROUP_RESP) return NULL; /* PDU must contain at least: * - Attribute Opcode (1 octet) * - Length (1 octet) * - Attribute Data List (at least one entry): * - Attribute Handle (2 octets) * - End Group Handle (2 octets) * - Attribute Value (at least 1 octet) */ if (len < 7) return NULL; elen = pdu[1]; /* Minimum Attribute Data List size */ if (elen < 5) return NULL; /* Reject incomplete Attribute Data List */ if ((len - 2) % elen) return NULL; num = (len - 2) / elen; list = att_data_list_alloc(num, elen); if (list == NULL) return NULL; ptr = &pdu[2]; for (i = 0; i < num; i++) { memcpy(list->data[i], ptr, list->len); ptr += list->len; } return list; } uint16_t enc_find_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, const uint8_t *value, size_t vlen, uint8_t *pdu, size_t len) { uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end) + sizeof(uint16_t); if (pdu == NULL) return 0; if (!uuid) return 0; if (uuid->type != BT_UUID16) return 0; if (vlen > len - min_len) vlen = len - min_len; pdu[0] = ATT_OP_FIND_BY_TYPE_REQ; put_le16(start, &pdu[1]); put_le16(end, &pdu[3]); put_le16(uuid->value.u16, &pdu[5]); if (vlen > 0) { memcpy(&pdu[7], value, vlen); return min_len + vlen; } return min_len; } uint16_t dec_find_by_type_req(const uint8_t *pdu, size_t len, uint16_t *start, uint16_t *end, bt_uuid_t *uuid, uint8_t *value, size_t *vlen) { if (pdu == NULL) return 0; if (len < 7) return 0; /* Attribute Opcode (1 octet) */ if (pdu[0] != ATT_OP_FIND_BY_TYPE_REQ) return 0; /* First requested handle number (2 octets) */ *start = get_le16(&pdu[1]); /* Last requested handle number (2 octets) */ *end = get_le16(&pdu[3]); /* 16-bit UUID to find (2 octets) */ bt_uuid16_create(uuid, get_le16(&pdu[5])); /* Attribute value to find */ *vlen = len - 7; if (*vlen > 0) memcpy(value, pdu + 7, *vlen); return len; } uint16_t enc_find_by_type_resp(GSList *matches, uint8_t *pdu, size_t len) { GSList *l; uint16_t offset; if (!pdu) return 0; pdu[0] = ATT_OP_FIND_BY_TYPE_RESP; for (l = matches, offset = 1; l && len >= (offset + sizeof(uint16_t) * 2); l = l->next, offset += sizeof(uint16_t) * 2) { struct att_range *range = l->data; put_le16(range->start, &pdu[offset]); put_le16(range->end, &pdu[offset + 2]); } return offset; } GSList *dec_find_by_type_resp(const uint8_t *pdu, size_t len) { struct att_range *range; GSList *matches; off_t offset; /* PDU should contain at least: * - Attribute Opcode (1 octet) * - Handles Information List (at least one entry): * - Found Attribute Handle (2 octets) * - Group End Handle (2 octets) */ if (pdu == NULL || len < 5) return NULL; if (pdu[0] != ATT_OP_FIND_BY_TYPE_RESP) return NULL; /* Reject incomplete Handles Information List */ if ((len - 1) % 4) return NULL; for (offset = 1, matches = NULL; len >= (offset + sizeof(uint16_t) * 2); offset += sizeof(uint16_t) * 2) { range = g_new0(struct att_range, 1); range->start = get_le16(&pdu[offset]); range->end = get_le16(&pdu[offset + 2]); matches = g_slist_append(matches, range); } return matches; } uint16_t enc_read_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, uint8_t *pdu, size_t len) { uint16_t uuid_len; if (!uuid) return 0; if (uuid->type == BT_UUID16) uuid_len = 2; else if (uuid->type == BT_UUID128) uuid_len = 16; else return 0; /* Attribute Opcode (1 octet) */ pdu[0] = ATT_OP_READ_BY_TYPE_REQ; /* Starting Handle (2 octets) */ put_le16(start, &pdu[1]); /* Ending Handle (2 octets) */ put_le16(end, &pdu[3]); /* Attribute Type (2 or 16 octet UUID) */ put_uuid_le(uuid, &pdu[5]); return 5 + uuid_len; } uint16_t dec_read_by_type_req(const uint8_t *pdu, size_t len, uint16_t *start, uint16_t *end, bt_uuid_t *uuid) { const size_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end); uint8_t type; if (pdu == NULL) return 0; if (start == NULL || end == NULL || uuid == NULL) return 0; if (len == (min_len + 2)) type = BT_UUID16; else if (len == (min_len + 16)) type = BT_UUID128; else return 0; if (pdu[0] != ATT_OP_READ_BY_TYPE_REQ) return 0; *start = get_le16(&pdu[1]); *end = get_le16(&pdu[3]); get_uuid(type, &pdu[5], uuid); return len; } uint16_t enc_read_by_type_resp(struct att_data_list *list, uint8_t *pdu, size_t len) { uint8_t *ptr; size_t i, w, l; if (list == NULL) return 0; if (pdu == NULL) return 0; l = MIN(len - 2, list->len); pdu[0] = ATT_OP_READ_BY_TYPE_RESP; pdu[1] = l; ptr = &pdu[2]; for (i = 0, w = 2; i < list->num && w + l <= len; i++) { memcpy(ptr, list->data[i], l); ptr += l; w += l; } return w; } struct att_data_list *dec_read_by_type_resp(const uint8_t *pdu, size_t len) { struct att_data_list *list; const uint8_t *ptr; uint16_t elen, num; int i; if (pdu[0] != ATT_OP_READ_BY_TYPE_RESP) return NULL; /* PDU must contain at least: * - Attribute Opcode (1 octet) * - Length (1 octet) * - Attribute Data List (at least one entry): * - Attribute Handle (2 octets) * - Attribute Value (at least 1 octet) */ if (len < 5) return NULL; elen = pdu[1]; /* Minimum Attribute Data List size */ if (elen < 3) return NULL; /* Reject incomplete Attribute Data List */ if ((len - 2) % elen) return NULL; num = (len - 2) / elen; list = att_data_list_alloc(num, elen); if (list == NULL) return NULL; ptr = &pdu[2]; for (i = 0; i < num; i++) { memcpy(list->data[i], ptr, list->len); ptr += list->len; } return list; } uint16_t enc_write_cmd(uint16_t handle, const uint8_t *value, size_t vlen, uint8_t *pdu, size_t len) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle); if (pdu == NULL) return 0; if (vlen > len - min_len) vlen = len - min_len; pdu[0] = ATT_OP_WRITE_CMD; put_le16(handle, &pdu[1]); if (vlen > 0) { memcpy(&pdu[3], value, vlen); return min_len + vlen; } return min_len; } uint16_t dec_write_cmd(const uint8_t *pdu, size_t len, uint16_t *handle, uint8_t *value, size_t *vlen) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle); if (pdu == NULL) return 0; if (value == NULL || vlen == NULL || handle == NULL) return 0; if (len < min_len) return 0; if (pdu[0] != ATT_OP_WRITE_CMD) return 0; *handle = get_le16(&pdu[1]); memcpy(value, pdu + min_len, len - min_len); *vlen = len - min_len; return len; } uint16_t enc_signed_write_cmd(uint16_t handle, const uint8_t *value, size_t vlen, struct bt_crypto *crypto, const uint8_t csrk[16], uint32_t sign_cnt, uint8_t *pdu, size_t len) { const uint16_t hdr_len = sizeof(pdu[0]) + sizeof(handle); const uint16_t min_len = hdr_len + ATT_SIGNATURE_LEN; if (pdu == NULL) return 0; if (vlen > len - min_len) vlen = len - min_len; pdu[0] = ATT_OP_SIGNED_WRITE_CMD; put_le16(handle, &pdu[1]); if (vlen > 0) memcpy(&pdu[hdr_len], value, vlen); if (!bt_crypto_sign_att(crypto, csrk, pdu, hdr_len + vlen, sign_cnt, &pdu[hdr_len + vlen])) return 0; return min_len + vlen; } uint16_t dec_signed_write_cmd(const uint8_t *pdu, size_t len, uint16_t *handle, uint8_t *value, size_t *vlen, uint8_t signature[12]) { const uint16_t hdr_len = sizeof(pdu[0]) + sizeof(*handle); const uint16_t min_len = hdr_len + ATT_SIGNATURE_LEN; if (pdu == NULL) return 0; if (value == NULL || vlen == NULL || handle == NULL) return 0; if (len < min_len) return 0; if (pdu[0] != ATT_OP_SIGNED_WRITE_CMD) return 0; *vlen = len - min_len; *handle = get_le16(&pdu[1]); memcpy(value, pdu + hdr_len, *vlen); memcpy(signature, pdu + hdr_len + *vlen, ATT_SIGNATURE_LEN); return len; } uint16_t enc_write_req(uint16_t handle, const uint8_t *value, size_t vlen, uint8_t *pdu, size_t len) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle); if (pdu == NULL) return 0; if (vlen > len - min_len) vlen = len - min_len; pdu[0] = ATT_OP_WRITE_REQ; put_le16(handle, &pdu[1]); if (vlen > 0) { memcpy(&pdu[3], value, vlen); return min_len + vlen; } return min_len; } uint16_t dec_write_req(const uint8_t *pdu, size_t len, uint16_t *handle, uint8_t *value, size_t *vlen) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle); if (pdu == NULL) return 0; if (value == NULL || vlen == NULL || handle == NULL) return 0; if (len < min_len) return 0; if (pdu[0] != ATT_OP_WRITE_REQ) return 0; *handle = get_le16(&pdu[1]); *vlen = len - min_len; if (*vlen > 0) memcpy(value, pdu + min_len, *vlen); return len; } uint16_t enc_write_resp(uint8_t *pdu) { if (pdu == NULL) return 0; pdu[0] = ATT_OP_WRITE_RESP; return sizeof(pdu[0]); } uint16_t dec_write_resp(const uint8_t *pdu, size_t len) { if (pdu == NULL) return 0; if (pdu[0] != ATT_OP_WRITE_RESP) return 0; return len; } uint16_t enc_read_req(uint16_t handle, uint8_t *pdu, size_t len) { if (pdu == NULL) return 0; /* Attribute Opcode (1 octet) */ pdu[0] = ATT_OP_READ_REQ; /* Attribute Handle (2 octets) */ put_le16(handle, &pdu[1]); return 3; } uint16_t enc_read_blob_req(uint16_t handle, uint16_t offset, uint8_t *pdu, size_t len) { if (pdu == NULL) return 0; /* Attribute Opcode (1 octet) */ pdu[0] = ATT_OP_READ_BLOB_REQ; /* Attribute Handle (2 octets) */ put_le16(handle, &pdu[1]); /* Value Offset (2 octets) */ put_le16(offset, &pdu[3]); return 5; } uint16_t dec_read_req(const uint8_t *pdu, size_t len, uint16_t *handle) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle); if (pdu == NULL) return 0; if (handle == NULL) return 0; if (len < min_len) return 0; if (pdu[0] != ATT_OP_READ_REQ) return 0; *handle = get_le16(&pdu[1]); return min_len; } uint16_t dec_read_blob_req(const uint8_t *pdu, size_t len, uint16_t *handle, uint16_t *offset) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) + sizeof(*offset); if (pdu == NULL) return 0; if (handle == NULL) return 0; if (offset == NULL) return 0; if (len < min_len) return 0; if (pdu[0] != ATT_OP_READ_BLOB_REQ) return 0; *handle = get_le16(&pdu[1]); *offset = get_le16(&pdu[3]); return min_len; } uint16_t enc_read_resp(uint8_t *value, size_t vlen, uint8_t *pdu, size_t len) { if (pdu == NULL) return 0; /* If the attribute value length is longer than the allowed PDU size, * send only the octets that fit on the PDU. The remaining octets can * be requested using the Read Blob Request. */ if (vlen > len - 1) vlen = len - 1; pdu[0] = ATT_OP_READ_RESP; memcpy(pdu + 1, value, vlen); return vlen + 1; } uint16_t enc_read_blob_resp(uint8_t *value, size_t vlen, uint16_t offset, uint8_t *pdu, size_t len) { if (pdu == NULL) return 0; vlen -= offset; if (vlen > len - 1) vlen = len - 1; pdu[0] = ATT_OP_READ_BLOB_RESP; memcpy(pdu + 1, &value[offset], vlen); return vlen + 1; } ssize_t dec_read_resp(const uint8_t *pdu, size_t len, uint8_t *value, size_t vlen) { if (pdu == NULL) return -EINVAL; if (pdu[0] != ATT_OP_READ_RESP) return -EINVAL; if (value == NULL) return len - 1; if (vlen < (len - 1)) return -ENOBUFS; memcpy(value, pdu + 1, len - 1); return len - 1; } uint16_t enc_error_resp(uint8_t opcode, uint16_t handle, uint8_t status, uint8_t *pdu, size_t len) { /* Attribute Opcode (1 octet) */ pdu[0] = ATT_OP_ERROR; /* Request Opcode In Error (1 octet) */ pdu[1] = opcode; /* Attribute Handle In Error (2 octets) */ put_le16(handle, &pdu[2]); /* Error Code (1 octet) */ pdu[4] = status; return 5; } uint16_t enc_find_info_req(uint16_t start, uint16_t end, uint8_t *pdu, size_t len) { if (pdu == NULL) return 0; /* Attribute Opcode (1 octet) */ pdu[0] = ATT_OP_FIND_INFO_REQ; /* Starting Handle (2 octets) */ put_le16(start, &pdu[1]); /* Ending Handle (2 octets) */ put_le16(end, &pdu[3]); return 5; } uint16_t dec_find_info_req(const uint8_t *pdu, size_t len, uint16_t *start, uint16_t *end) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end); if (pdu == NULL) return 0; if (len < min_len) return 0; if (start == NULL || end == NULL) return 0; if (pdu[0] != ATT_OP_FIND_INFO_REQ) return 0; *start = get_le16(&pdu[1]); *end = get_le16(&pdu[3]); return min_len; } uint16_t enc_find_info_resp(uint8_t format, struct att_data_list *list, uint8_t *pdu, size_t len) { uint8_t *ptr; size_t i, w; if (pdu == NULL) return 0; if (list == NULL) return 0; if (len < list->len + sizeof(uint8_t) * 2) return 0; pdu[0] = ATT_OP_FIND_INFO_RESP; pdu[1] = format; ptr = (void *) &pdu[2]; for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) { memcpy(ptr, list->data[i], list->len); ptr += list->len; w += list->len; } return w; } struct att_data_list *dec_find_info_resp(const uint8_t *pdu, size_t len, uint8_t *format) { struct att_data_list *list; uint8_t *ptr; uint16_t elen, num; int i; if (pdu == NULL) return 0; if (format == NULL) return 0; if (pdu[0] != ATT_OP_FIND_INFO_RESP) return 0; *format = pdu[1]; elen = sizeof(pdu[0]) + sizeof(*format); if (*format == 0x01) elen += 2; else if (*format == 0x02) elen += 16; num = (len - 2) / elen; ptr = (void *) &pdu[2]; list = att_data_list_alloc(num, elen); if (list == NULL) return NULL; for (i = 0; i < num; i++) { memcpy(list->data[i], ptr, list->len); ptr += list->len; } return list; } uint16_t enc_notification(uint16_t handle, uint8_t *value, size_t vlen, uint8_t *pdu, size_t len) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t); if (pdu == NULL) return 0; if (len < (vlen + min_len)) return 0; pdu[0] = ATT_OP_HANDLE_NOTIFY; put_le16(handle, &pdu[1]); memcpy(&pdu[3], value, vlen); return vlen + min_len; } uint16_t enc_indication(uint16_t handle, uint8_t *value, size_t vlen, uint8_t *pdu, size_t len) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t); if (pdu == NULL) return 0; if (len < (vlen + min_len)) return 0; pdu[0] = ATT_OP_HANDLE_IND; put_le16(handle, &pdu[1]); memcpy(&pdu[3], value, vlen); return vlen + min_len; } uint16_t dec_indication(const uint8_t *pdu, size_t len, uint16_t *handle, uint8_t *value, size_t vlen) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t); uint16_t dlen; if (pdu == NULL) return 0; if (pdu[0] != ATT_OP_HANDLE_IND) return 0; if (len < min_len) return 0; dlen = MIN(len - min_len, vlen); if (handle) *handle = get_le16(&pdu[1]); memcpy(value, &pdu[3], dlen); return dlen; } uint16_t enc_confirmation(uint8_t *pdu, size_t len) { if (pdu == NULL) return 0; /* Attribute Opcode (1 octet) */ pdu[0] = ATT_OP_HANDLE_CNF; return 1; } uint16_t enc_mtu_req(uint16_t mtu, uint8_t *pdu, size_t len) { if (pdu == NULL) return 0; /* Attribute Opcode (1 octet) */ pdu[0] = ATT_OP_MTU_REQ; /* Client Rx MTU (2 octets) */ put_le16(mtu, &pdu[1]); return 3; } uint16_t dec_mtu_req(const uint8_t *pdu, size_t len, uint16_t *mtu) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu); if (pdu == NULL) return 0; if (mtu == NULL) return 0; if (len < min_len) return 0; if (pdu[0] != ATT_OP_MTU_REQ) return 0; *mtu = get_le16(&pdu[1]); return min_len; } uint16_t enc_mtu_resp(uint16_t mtu, uint8_t *pdu, size_t len) { if (pdu == NULL) return 0; /* Attribute Opcode (1 octet) */ pdu[0] = ATT_OP_MTU_RESP; /* Server Rx MTU (2 octets) */ put_le16(mtu, &pdu[1]); return 3; } uint16_t dec_mtu_resp(const uint8_t *pdu, size_t len, uint16_t *mtu) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu); if (pdu == NULL) return 0; if (mtu == NULL) return 0; if (len < min_len) return 0; if (pdu[0] != ATT_OP_MTU_RESP) return 0; *mtu = get_le16(&pdu[1]); return min_len; } uint16_t enc_prep_write_req(uint16_t handle, uint16_t offset, const uint8_t *value, size_t vlen, uint8_t *pdu, size_t len) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle) + sizeof(offset); if (pdu == NULL) return 0; if (vlen > len - min_len) vlen = len - min_len; pdu[0] = ATT_OP_PREP_WRITE_REQ; put_le16(handle, &pdu[1]); put_le16(offset, &pdu[3]); if (vlen > 0) { memcpy(&pdu[5], value, vlen); return min_len + vlen; } return min_len; } uint16_t dec_prep_write_req(const uint8_t *pdu, size_t len, uint16_t *handle, uint16_t *offset, uint8_t *value, size_t *vlen) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) + sizeof(*offset); if (pdu == NULL) return 0; if (handle == NULL || offset == NULL || value == NULL || vlen == NULL) return 0; if (len < min_len) return 0; if (pdu[0] != ATT_OP_PREP_WRITE_REQ) return 0; *handle = get_le16(&pdu[1]); *offset = get_le16(&pdu[3]); *vlen = len - min_len; if (*vlen > 0) memcpy(value, pdu + min_len, *vlen); return len; } uint16_t enc_prep_write_resp(uint16_t handle, uint16_t offset, const uint8_t *value, size_t vlen, uint8_t *pdu, size_t len) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle) + sizeof(offset); if (pdu == NULL) return 0; if (vlen > len - min_len) vlen = len - min_len; pdu[0] = ATT_OP_PREP_WRITE_RESP; put_le16(handle, &pdu[1]); put_le16(offset, &pdu[3]); if (vlen > 0) { memcpy(&pdu[5], value, vlen); return min_len + vlen; } return min_len; } uint16_t dec_prep_write_resp(const uint8_t *pdu, size_t len, uint16_t *handle, uint16_t *offset, uint8_t *value, size_t *vlen) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) + sizeof(*offset); if (pdu == NULL) return 0; if (handle == NULL || offset == NULL || value == NULL || vlen == NULL) return 0; if (len < min_len) return 0; if (pdu[0] != ATT_OP_PREP_WRITE_REQ) return 0; *handle = get_le16(&pdu[1]); *offset = get_le16(&pdu[3]); *vlen = len - min_len; if (*vlen > 0) memcpy(value, pdu + min_len, *vlen); return len; } uint16_t enc_exec_write_req(uint8_t flags, uint8_t *pdu, size_t len) { if (pdu == NULL) return 0; if (flags > 1) return 0; /* Attribute Opcode (1 octet) */ pdu[0] = ATT_OP_EXEC_WRITE_REQ; /* Flags (1 octet) */ pdu[1] = flags; return 2; } uint16_t dec_exec_write_req(const uint8_t *pdu, size_t len, uint8_t *flags) { const uint16_t min_len = sizeof(pdu[0]) + sizeof(*flags); if (pdu == NULL) return 0; if (flags == NULL) return 0; if (len < min_len) return 0; if (pdu[0] != ATT_OP_EXEC_WRITE_REQ) return 0; *flags = pdu[1]; return min_len; } uint16_t enc_exec_write_resp(uint8_t *pdu) { if (pdu == NULL) return 0; /* Attribute Opcode (1 octet) */ pdu[0] = ATT_OP_EXEC_WRITE_RESP; return 1; } uint16_t dec_exec_write_resp(const uint8_t *pdu, size_t len) { const uint16_t min_len = sizeof(pdu[0]); if (pdu == NULL) return 0; if (len < min_len) return 0; if (pdu[0] != ATT_OP_EXEC_WRITE_RESP) return 0; return len; } bluez-5.87/attrib/PaxHeaders/att.h0000644000000000000000000000005015200607060014075 xustar0020 atime=1783092682 20 ctime=1783094106 bluez-5.87/attrib/att.h0000644000000000000000000001646015200607060013565 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2010 Nokia Corporation * Copyright (C) 2010 Marcel Holtmann * * */ #include "src/shared/crypto.h" /* Len of signature in write signed packet */ #define ATT_SIGNATURE_LEN 12 /* Attribute Protocol Opcodes */ #define ATT_OP_ERROR 0x01 #define ATT_OP_MTU_REQ 0x02 #define ATT_OP_MTU_RESP 0x03 #define ATT_OP_FIND_INFO_REQ 0x04 #define ATT_OP_FIND_INFO_RESP 0x05 #define ATT_OP_FIND_BY_TYPE_REQ 0x06 #define ATT_OP_FIND_BY_TYPE_RESP 0x07 #define ATT_OP_READ_BY_TYPE_REQ 0x08 #define ATT_OP_READ_BY_TYPE_RESP 0x09 #define ATT_OP_READ_REQ 0x0A #define ATT_OP_READ_RESP 0x0B #define ATT_OP_READ_BLOB_REQ 0x0C #define ATT_OP_READ_BLOB_RESP 0x0D #define ATT_OP_READ_MULTI_REQ 0x0E #define ATT_OP_READ_MULTI_RESP 0x0F #define ATT_OP_READ_BY_GROUP_REQ 0x10 #define ATT_OP_READ_BY_GROUP_RESP 0x11 #define ATT_OP_WRITE_REQ 0x12 #define ATT_OP_WRITE_RESP 0x13 #define ATT_OP_WRITE_CMD 0x52 #define ATT_OP_PREP_WRITE_REQ 0x16 #define ATT_OP_PREP_WRITE_RESP 0x17 #define ATT_OP_EXEC_WRITE_REQ 0x18 #define ATT_OP_EXEC_WRITE_RESP 0x19 #define ATT_OP_HANDLE_NOTIFY 0x1B #define ATT_OP_HANDLE_IND 0x1D #define ATT_OP_HANDLE_CNF 0x1E #define ATT_OP_SIGNED_WRITE_CMD 0xD2 /* Error codes for Error response PDU */ #define ATT_ECODE_INVALID_HANDLE 0x01 #define ATT_ECODE_READ_NOT_PERM 0x02 #define ATT_ECODE_WRITE_NOT_PERM 0x03 #define ATT_ECODE_INVALID_PDU 0x04 #define ATT_ECODE_AUTHENTICATION 0x05 #define ATT_ECODE_REQ_NOT_SUPP 0x06 #define ATT_ECODE_INVALID_OFFSET 0x07 #define ATT_ECODE_AUTHORIZATION 0x08 #define ATT_ECODE_PREP_QUEUE_FULL 0x09 #define ATT_ECODE_ATTR_NOT_FOUND 0x0A #define ATT_ECODE_ATTR_NOT_LONG 0x0B #define ATT_ECODE_INSUFF_ENCR_KEY_SIZE 0x0C #define ATT_ECODE_INVAL_ATTR_VALUE_LEN 0x0D #define ATT_ECODE_UNLIKELY 0x0E #define ATT_ECODE_INSUFF_ENC 0x0F #define ATT_ECODE_UNSUPP_GRP_TYPE 0x10 #define ATT_ECODE_INSUFF_RESOURCES 0x11 /* Application error */ #define ATT_ECODE_IO 0x80 #define ATT_ECODE_TIMEOUT 0x81 #define ATT_ECODE_ABORTED 0x82 #define ATT_MAX_VALUE_LEN 512 #define ATT_DEFAULT_L2CAP_MTU 48 #define ATT_DEFAULT_LE_MTU 23 /* Flags for Execute Write Request Operation */ #define ATT_CANCEL_ALL_PREP_WRITES 0x00 #define ATT_WRITE_ALL_PREP_WRITES 0x01 /* Find Information Response Formats */ #define ATT_FIND_INFO_RESP_FMT_16BIT 0x01 #define ATT_FIND_INFO_RESP_FMT_128BIT 0x02 struct att_data_list { uint16_t num; uint16_t len; uint8_t **data; }; struct att_range { uint16_t start; uint16_t end; }; struct att_data_list *att_data_list_alloc(uint16_t num, uint16_t len); void att_data_list_free(struct att_data_list *list); const char *att_ecode2str(uint8_t status); uint16_t enc_read_by_grp_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, uint8_t *pdu, size_t len); uint16_t dec_read_by_grp_req(const uint8_t *pdu, size_t len, uint16_t *start, uint16_t *end, bt_uuid_t *uuid); uint16_t enc_read_by_grp_resp(struct att_data_list *list, uint8_t *pdu, size_t len); uint16_t enc_find_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, const uint8_t *value, size_t vlen, uint8_t *pdu, size_t len); uint16_t dec_find_by_type_req(const uint8_t *pdu, size_t len, uint16_t *start, uint16_t *end, bt_uuid_t *uuid, uint8_t *value, size_t *vlen); uint16_t enc_find_by_type_resp(GSList *ranges, uint8_t *pdu, size_t len); GSList *dec_find_by_type_resp(const uint8_t *pdu, size_t len); struct att_data_list *dec_read_by_grp_resp(const uint8_t *pdu, size_t len); uint16_t enc_read_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, uint8_t *pdu, size_t len); uint16_t dec_read_by_type_req(const uint8_t *pdu, size_t len, uint16_t *start, uint16_t *end, bt_uuid_t *uuid); uint16_t enc_read_by_type_resp(struct att_data_list *list, uint8_t *pdu, size_t len); uint16_t enc_write_cmd(uint16_t handle, const uint8_t *value, size_t vlen, uint8_t *pdu, size_t len); uint16_t dec_write_cmd(const uint8_t *pdu, size_t len, uint16_t *handle, uint8_t *value, size_t *vlen); uint16_t enc_signed_write_cmd(uint16_t handle, const uint8_t *value, size_t vlen, struct bt_crypto *crypto, const uint8_t csrk[16], uint32_t sign_cnt, uint8_t *pdu, size_t len); uint16_t dec_signed_write_cmd(const uint8_t *pdu, size_t len, uint16_t *handle, uint8_t *value, size_t *vlen, uint8_t signature[12]); struct att_data_list *dec_read_by_type_resp(const uint8_t *pdu, size_t len); uint16_t enc_write_req(uint16_t handle, const uint8_t *value, size_t vlen, uint8_t *pdu, size_t len); uint16_t dec_write_req(const uint8_t *pdu, size_t len, uint16_t *handle, uint8_t *value, size_t *vlen); uint16_t enc_write_resp(uint8_t *pdu); uint16_t dec_write_resp(const uint8_t *pdu, size_t len); uint16_t enc_read_req(uint16_t handle, uint8_t *pdu, size_t len); uint16_t enc_read_blob_req(uint16_t handle, uint16_t offset, uint8_t *pdu, size_t len); uint16_t dec_read_req(const uint8_t *pdu, size_t len, uint16_t *handle); uint16_t dec_read_blob_req(const uint8_t *pdu, size_t len, uint16_t *handle, uint16_t *offset); uint16_t enc_read_resp(uint8_t *value, size_t vlen, uint8_t *pdu, size_t len); uint16_t enc_read_blob_resp(uint8_t *value, size_t vlen, uint16_t offset, uint8_t *pdu, size_t len); ssize_t dec_read_resp(const uint8_t *pdu, size_t len, uint8_t *value, size_t vlen); uint16_t enc_error_resp(uint8_t opcode, uint16_t handle, uint8_t status, uint8_t *pdu, size_t len); uint16_t enc_find_info_req(uint16_t start, uint16_t end, uint8_t *pdu, size_t len); uint16_t dec_find_info_req(const uint8_t *pdu, size_t len, uint16_t *start, uint16_t *end); uint16_t enc_find_info_resp(uint8_t format, struct att_data_list *list, uint8_t *pdu, size_t len); struct att_data_list *dec_find_info_resp(const uint8_t *pdu, size_t len, uint8_t *format); uint16_t enc_notification(uint16_t handle, uint8_t *value, size_t vlen, uint8_t *pdu, size_t len); uint16_t enc_indication(uint16_t handle, uint8_t *value, size_t vlen, uint8_t *pdu, size_t len); uint16_t dec_indication(const uint8_t *pdu, size_t len, uint16_t *handle, uint8_t *value, size_t vlen); uint16_t enc_confirmation(uint8_t *pdu, size_t len); uint16_t enc_mtu_req(uint16_t mtu, uint8_t *pdu, size_t len); uint16_t dec_mtu_req(const uint8_t *pdu, size_t len, uint16_t *mtu); uint16_t enc_mtu_resp(uint16_t mtu, uint8_t *pdu, size_t len); uint16_t dec_mtu_resp(const uint8_t *pdu, size_t len, uint16_t *mtu); uint16_t enc_prep_write_req(uint16_t handle, uint16_t offset, const uint8_t *value, size_t vlen, uint8_t *pdu, size_t len); uint16_t dec_prep_write_req(const uint8_t *pdu, size_t len, uint16_t *handle, uint16_t *offset, uint8_t *value, size_t *vlen); uint16_t enc_prep_write_resp(uint16_t handle, uint16_t offset, const uint8_t *value, size_t vlen, uint8_t *pdu, size_t len); uint16_t dec_prep_write_resp(const uint8_t *pdu, size_t len, uint16_t *handle, uint16_t *offset, uint8_t *value, size_t *vlen); uint16_t enc_exec_write_req(uint8_t flags, uint8_t *pdu, size_t len); uint16_t dec_exec_write_req(const uint8_t *pdu, size_t len, uint8_t *flags); uint16_t enc_exec_write_resp(uint8_t *pdu); uint16_t dec_exec_write_resp(const uint8_t *pdu, size_t len); bluez-5.87/PaxHeaders/missing0000644000000000000000000000005015221753000013242 xustar0020 atime=1783092745 20 ctime=1783094098 bluez-5.87/missing0000755000000000000000000001706515221753000012737 0ustar00rootroot#! /bin/sh # Common wrapper for a few potentially missing GNU and other programs. scriptversion=2025-06-18.21; # UTC # shellcheck disable=SC2006,SC2268 # we must support pre-POSIX shells # Copyright (C) 1996-2025 Free Software Foundation, Inc. # Originally written by Fran,cois Pinard , 1996. # 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, 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, see . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. if test $# -eq 0; then echo 1>&2 "Try '$0 --help' for more information" exit 1 fi case $1 in --is-lightweight) # Used by our autoconf macros to check whether the available missing # script is modern enough. exit 0 ;; --run) # Back-compat with the calling convention used by older automake. shift ;; -h|--h|--he|--hel|--help) echo "\ $0 [OPTION]... PROGRAM [ARGUMENT]... Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due to PROGRAM being missing or too old. Options: -h, --help display this help and exit -v, --version output version information and exit Supported PROGRAM values: aclocal autoconf autogen autoheader autom4te automake autoreconf bison flex help2man lex makeinfo perl yacc Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and 'g' are ignored when checking the name. Report bugs to . GNU Automake home page: . General help using GNU software: ." exit $? ;; -v|--v|--ve|--ver|--vers|--versi|--versio|--version) echo "missing (GNU Automake) $scriptversion" exit $? ;; -*) echo 1>&2 "$0: unknown '$1' option" echo 1>&2 "Try '$0 --help' for more information" exit 1 ;; esac # Run the given program, remember its exit status. "$@"; st=$? # If it succeeded, we are done. test $st -eq 0 && exit 0 # Also exit now if we it failed (or wasn't found), and '--version' was # passed; such an option is passed most likely to detect whether the # program is present and works. case $2 in --version|--help) exit $st;; esac # Exit code 63 means version mismatch. This often happens when the user # tries to use an ancient version of a tool on a file that requires a # minimum version. if test $st -eq 63; then msg="probably too old" elif test $st -eq 127; then # Program was missing. msg="missing on your system" else # Program was found and executed, but failed. Give up. exit $st fi perl_URL=https://www.perl.org/ flex_URL=https://github.com/westes/flex gnu_software_URL=https://www.gnu.org/software program_details () { case $1 in aclocal|automake|autoreconf) echo "The '$1' program is part of the GNU Automake package:" echo "<$gnu_software_URL/automake>" echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:" echo "<$gnu_software_URL/autoconf>" echo "<$gnu_software_URL/m4/>" echo "<$perl_URL>" ;; autoconf|autom4te|autoheader) echo "The '$1' program is part of the GNU Autoconf package:" echo "<$gnu_software_URL/autoconf/>" echo "It also requires GNU m4 and Perl in order to run:" echo "<$gnu_software_URL/m4/>" echo "<$perl_URL>" ;; *) : ;; esac } give_advice () { # Normalize program name to check for. normalized_program=`echo "$1" | sed ' s/^gnu-//; t s/^gnu//; t s/^g//; t'` printf '%s\n' "'$1' is $msg." configure_deps="'configure.ac' or m4 files included by 'configure.ac'" autoheader_deps="'acconfig.h'" automake_deps="'Makefile.am'" aclocal_deps="'acinclude.m4'" case $normalized_program in aclocal*) echo "You should only need it if you modified $aclocal_deps or" echo "$configure_deps." ;; autoconf*) echo "You should only need it if you modified $configure_deps." ;; autogen*) echo "You should only need it if you modified a '.def' or '.tpl' file." echo "You may want to install the GNU AutoGen package:" echo "<$gnu_software_URL/autogen/>" ;; autoheader*) echo "You should only need it if you modified $autoheader_deps or" echo "$configure_deps." ;; automake*) echo "You should only need it if you modified $automake_deps or" echo "$configure_deps." ;; autom4te*) echo "You might have modified some maintainer files that require" echo "the 'autom4te' program to be rebuilt." ;; autoreconf*) echo "You should only need it if you modified $aclocal_deps or" echo "$automake_deps or $autoheader_deps or $automake_deps or" echo "$configure_deps." ;; bison*|yacc*) echo "You should only need it if you modified a '.y' file." echo "You may want to install the GNU Bison package:" echo "<$gnu_software_URL/bison/>" ;; help2man*) echo "You should only need it if you modified a dependency" \ "of a man page." echo "You may want to install the GNU Help2man package:" echo "<$gnu_software_URL/help2man/>" ;; lex*|flex*) echo "You should only need it if you modified a '.l' file." echo "You may want to install the Fast Lexical Analyzer package:" echo "<$flex_URL>" ;; makeinfo*) echo "You should only need it if you modified a '.texi' file, or" echo "any other file indirectly affecting the aspect of the manual." echo "You might want to install the Texinfo package:" echo "<$gnu_software_URL/texinfo/>" echo "The spurious makeinfo call might also be the consequence of" echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might" echo "want to install GNU make:" echo "<$gnu_software_URL/make/>" ;; perl*) echo "You should only need it to run GNU Autoconf, GNU Automake, " echo " assorted other tools, or if you modified a Perl source file." echo "You may want to install the Perl 5 language interpreter:" echo "<$perl_URL>" ;; *) echo "You might have modified some files without having the proper" echo "tools for further handling them. Check the 'README' file, it" echo "often tells you about the needed prerequisites for installing" echo "this package. You may also peek at any GNU archive site, in" echo "case some other package contains this missing '$1' program." ;; esac program_details "$normalized_program" } give_advice "$1" | sed -e '1s/^/WARNING: /' \ -e '2,$s/^/ /' >&2 # Propagate the correct exit status (expected to be 127 for a program # not found, 63 for a program that failed due to version mismatch). exit $st # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp nil t) # time-stamp-start: "scriptversion=" # time-stamp-format: "%Y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: bluez-5.87/PaxHeaders/configure.ac0000644000000000000000000000005015221752714014147 xustar0020 atime=1783092709 20 ctime=1783094098 bluez-5.87/configure.ac0000644000000000000000000004457615221752714013650 0ustar00rootroot# SPDX-License-Identifier: GPL-2.0 AC_PREREQ(2.60) AC_INIT(bluez, 5.87) AM_INIT_AUTOMAKE([foreign subdir-objects color-tests silent-rules tar-pax no-dist-gzip dist-xz]) AC_CONFIG_HEADERS(config.h) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) AM_MAINTAINER_MODE AC_PREFIX_DEFAULT(/usr/local) PKG_PROG_PKG_CONFIG COMPILER_FLAGS AC_LANG([C]) AC_C_RESTRICT AC_PROG_CC AM_PROG_CC_C_O AC_PROG_CC_PIE AC_PROG_CC_ASAN AC_PROG_CC_LSAN AC_PROG_CC_UBSAN AC_PROG_INSTALL AC_PROG_MKDIR_P m4_define([_LT_AC_TAGCONFIG], []) m4_ifdef([AC_LIBTOOL_TAGS], [AC_LIBTOOL_TAGS([])]) LT_PREREQ(2.2) LT_INIT([disable-static]) if (test "$USE_MAINTAINER_MODE" = "yes"); then AC_CHECK_PROG(enable_coverage, [lcov], [yes], [no]) AC_CHECK_PROG(enable_dbus_run_session, [dbus-run-session], [yes]) AC_CHECK_PROG(enable_valgrind, [valgrind], [yes]) AC_CHECK_HEADERS(valgrind/memcheck.h) fi AM_CONDITIONAL(COVERAGE, test "${enable_coverage}" = "yes") AM_CONDITIONAL(DBUS_RUN_SESSION, test "${enable_dbus_run_session}" = "yes") MISC_FLAGS AM_CONDITIONAL(VALGRIND, test "${enable_valgrind}" = "yes" && test "$ASAN_LIB" != "yes" && test "LSAN_LIB" != "yes") AC_ARG_ENABLE(threads, AS_HELP_STRING([--enable-threads], [enable threading support]), [enable_threads=${enableval}]) AC_CHECK_FUNCS(explicit_bzero) AC_CHECK_FUNCS(getrandom) AC_CHECK_FUNCS(rawmemchr) AC_CHECK_FUNC(signalfd, dummy=yes, AC_MSG_ERROR(signalfd support is required)) AC_CHECK_LIB(rt, clock_gettime, dummy=yes, AC_MSG_ERROR(realtime clock support is required)) AC_CHECK_LIB(pthread, pthread_create, dummy=yes, AC_MSG_ERROR(posix thread support is required)) AC_CHECK_LIB(dl, dlopen, dummy=yes, AC_MSG_ERROR(dynamic linking loader is required)) AC_CHECK_HEADERS(stdarg.h stdio.h string.h linux/types.h linux/if_alg.h \ linux/uinput.h linux/uhid.h sys/random.h) # basename may be only available in libgen.h with the POSIX behavior, # not desired here AC_CHECK_DECLS([basename], [], AC_MSG_WARN([GNU basename extension not found]), [#define _GNU_SOURCE 1 #include ]) PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.36) PKG_CHECK_MODULES(DBUS, dbus-1 >= 1.10) AC_ARG_WITH([dbusconfdir], AS_HELP_STRING([--with-dbusconfdir=DIR], [path to D-Bus configuration directory]), [path_dbusconfdir=${withval}]) if (test -z "${path_dbusconfdir}"); then AC_MSG_CHECKING([D-Bus configuration directory]) path_dbusconfdir="`$PKG_CONFIG --variable=datadir dbus-1`" if (test -z "${path_dbusconfdir}"); then AC_MSG_ERROR([D-Bus configuration directory is required]) fi AC_MSG_RESULT([${path_dbusconfdir}]) fi AC_SUBST(DBUS_CONFDIR, [${path_dbusconfdir}]) AC_ARG_WITH([dbussystembusdir], AS_HELP_STRING([--with-dbussystembusdir=DIR], [path to D-Bus system bus services directory]), [path_dbussystembusdir=${withval}]) if (test -z "${path_dbussystembusdir}"); then AC_MSG_CHECKING([D-Bus system bus services dir]) path_dbussystembusdir="`$PKG_CONFIG --variable=system_bus_services_dir dbus-1`" if (test -z "${path_dbussystembusdir}"); then AC_MSG_ERROR([D-Bus system bus services directory is required]) fi AC_MSG_RESULT([${path_dbussystembusdir}]) fi AC_SUBST(DBUS_SYSTEMBUSDIR, [${path_dbussystembusdir}]) AC_ARG_WITH([dbussessionbusdir], AS_HELP_STRING([--with-dbussessionbusdir=DIR], [path to D-Bus session bus services directory]), [path_dbussessionbusdir=${withval}]) if (test -z "${path_dbussessionbusdir}"); then AC_MSG_CHECKING([D-Bus session bus services dir]) path_dbussessionbusdir="`$PKG_CONFIG --variable=session_bus_services_dir dbus-1`" if (test -z "${path_dbussessionbusdir}"); then AC_MSG_ERROR([D-Bus session bus services directory is required]) fi AC_MSG_RESULT([${path_dbussessionbusdir}]) fi AC_SUBST(DBUS_SESSIONBUSDIR, [${path_dbussessionbusdir}]) AC_ARG_WITH([zsh-completion-dir], AS_HELP_STRING([--with-zsh-completion-dir=DIR], [path to install zsh completions]), [path_zshcompletiondir=${withval}], [path_zshcompletiondir="yes"]) if (test "${path_zshcompletiondir}" = "yes"); then path_zshcompletiondir="$datarootdir/zsh/site-functions" AC_MSG_RESULT([${path_zshcompletiondir}]) fi AC_SUBST(ZSH_COMPLETIONDIR, [${path_zshcompletiondir}]) AM_CONDITIONAL(ZSH_COMPLETIONS, test "${path_zshcompletiondir}" != "no") AC_ARG_ENABLE(backtrace, AS_HELP_STRING([--enable-backtrace], [compile backtrace support]), [enable_backtrace=${enableval}]) if (test "${enable_backtrace}" = "yes"); then AC_CHECK_HEADER(elfutils/libdwfl.h, dummy=yes, AC_MSG_ERROR(elfutils support is required)) AC_DEFINE(HAVE_BACKTRACE_SUPPORT, 1, [Define to 1 if you have the backtrace support.]) BACKTRACE_CFLAGS="" BACKTRACE_LIBS="-ldw" AC_SUBST(BACKTRACE_CFLAGS) AC_SUBST(BACKTRACE_LIBS) fi AC_ARG_ENABLE(library, AS_HELP_STRING([--enable-library], [install Bluetooth library]), [enable_library=${enableval}]) AM_CONDITIONAL(LIBRARY, test "${enable_library}" = "yes") AC_ARG_ENABLE(test, AS_HELP_STRING([--enable-test], [enable test/example scripts]), [enable_test=${enableval}]) AM_CONDITIONAL(TEST, test "${enable_test}" = "yes") AC_ARG_ENABLE(nfc, AS_HELP_STRING([--enable-nfc], [enable NFC pairing]), [enable_nfc=${enableval}]) AM_CONDITIONAL(NFC, test "${enable_nfc}" = "yes") AC_ARG_ENABLE(a2dp, AS_HELP_STRING([--disable-a2dp], [disable A2DP profile]), [enable_a2dp=${enableval}]) AM_CONDITIONAL(A2DP, test "${enable_a2dp}" != "no") if test "${enable_a2dp}" != "no"; then AC_DEFINE(HAVE_A2DP, 1, [Define to 1 if you have A2DP support.]) fi AC_ARG_ENABLE(avrcp, AS_HELP_STRING([--disable-avrcp], [disable AVRCP profile]), [enable_avrcp=${enableval}]) AM_CONDITIONAL(AVRCP, test "${enable_avrcp}" != "no") if test "${enable_avrcp}" != "no"; then AC_DEFINE(HAVE_AVRCP, 1, [Define to 1 if you have AVRCP support.]) fi AC_ARG_ENABLE(network, AS_HELP_STRING([--disable-network], [disable network profiles]), [enable_network=${enableval}]) AM_CONDITIONAL(NETWORK, test "${enable_network}" != "no") AC_ARG_ENABLE(hid, AS_HELP_STRING([--disable-hid], [disable HID profile]), [enable_hid=${enableval}]) AM_CONDITIONAL(HID, test "${enable_hid}" != "no") AC_ARG_ENABLE(hog, AS_HELP_STRING([--disable-hog], [disable HoG profile]), [enable_hog=${enableval}]) AM_CONDITIONAL(HOG, test "${enable_hog}" != "no") AC_ARG_ENABLE(bap, AS_HELP_STRING([--disable-bap], [disable BAP profile]), [enable_bap=${enableval}]) AM_CONDITIONAL(BAP, test "${enable_bap}" != "no") AC_ARG_ENABLE(bass, AS_HELP_STRING([--disable-bass], [disable BASS service]), [enable_bass=${enableval}]) AM_CONDITIONAL(BASS, test "${enable_bass}" != "no") AC_ARG_ENABLE(mcp, AS_HELP_STRING([--disable-mcp], [disable MCP profile]), [enable_mcp=${enableval}]) AM_CONDITIONAL(MCP, test "${enable_mcp}" != "no") AC_ARG_ENABLE(ccp, AS_HELP_STRING([--disable-ccp], [disable CCP profile]), [enable_ccp=${enableval}]) AM_CONDITIONAL(CCP, test "${enable_ccp}" != "no") AC_ARG_ENABLE(vcp, AS_HELP_STRING([--disable-vcp], [disable VCP profile]), [enable_vcp=${enableval}]) AM_CONDITIONAL(VCP, test "${enable_vcp}" != "no") if test "${enable_vcp}" != "no"; then AC_DEFINE(HAVE_VCP, 1, [Define to 1 if you have VCP support.]) fi AC_ARG_ENABLE(micp, AS_HELP_STRING([--disable-micp], [disable MICP profile]), [enable_micp=${enableval}]) AM_CONDITIONAL(MICP, test "${enable_micp}" != "no") AC_ARG_ENABLE(csip, AS_HELP_STRING([--disable-csip], [disable CSIP profile]), [enable_csip=${enableval}]) AM_CONDITIONAL(CSIP, test "${enable_csip}" != "no") AC_ARG_ENABLE(tmap, AS_HELP_STRING([--disable-tmap], [disable TMAP profile]), [enable_tmap=${enableval}]) AM_CONDITIONAL(TMAP, test "${enable_tmap}" != "no") if test "${enable_tmap}" != "no"; then AC_DEFINE(HAVE_TMAP, 1, [Define to 1 if you have TMAP support.]) fi AC_ARG_ENABLE(gmap, AS_HELP_STRING([--disable-gmap], [disable GMAP profile]), [enable_gmap=${enableval}]) AM_CONDITIONAL(GMAP, test "${enable_gmap}" != "no") if test "${enable_gmap}" != "no"; then AC_DEFINE(HAVE_GMAP, 1, [Define to 1 if you have GMAP support.]) fi AC_ARG_ENABLE(asha, AS_HELP_STRING([--disable-asha], [disable ASHA support]), [enable_asha=${enableval}]) AM_CONDITIONAL(ASHA, test "${enable_asha}" != "no") if test "${enable_asha}" != "no"; then AC_DEFINE(HAVE_ASHA, 1, [Define to 1 if you have ASHA support.]) fi AC_ARG_ENABLE(hfp, AS_HELP_STRING([--disable-hfp], [disable HFP support]), [enable_hfp=${enableval}]) AM_CONDITIONAL(HFP, test "${enable_hfp}" != "no") if test "${enable_hfp}" != "no"; then AC_DEFINE(HAVE_HFP, 1, [Define to 1 if you have HFP support.]) fi AC_ARG_ENABLE(tools, AS_HELP_STRING([--disable-tools], [disable Bluetooth tools]), [enable_tools=${enableval}]) AM_CONDITIONAL(TOOLS, test "${enable_tools}" != "no") AC_ARG_ENABLE(monitor, AS_HELP_STRING([--disable-monitor], [disable Bluetooth monitor]), [enable_monitor=${enableval}]) AM_CONDITIONAL(MONITOR, test "${enable_monitor}" != "no") AC_ARG_ENABLE(udev, AS_HELP_STRING([--disable-udev], [disable udev device support]), [enable_udev=${enableval}]) if (test "${enable_udev}" != "no"); then PKG_CHECK_MODULES(UDEV, libudev >= 196) AC_DEFINE(HAVE_UDEV, 1, [Define to 1 if udev is required]) fi AC_ARG_WITH([udevdir], AS_HELP_STRING([--with-udevdir=DIR], [path to udev directory]), [path_udevdir=${withval}]) if (test "${enable_udev}" != "no" && test -z "${path_udevdir}"); then AC_MSG_CHECKING([udev directory]) path_udevdir="`$PKG_CONFIG --variable=udevdir udev`" if (test -z "${path_udevdir}"); then AC_MSG_ERROR([udev directory is required]) fi AC_MSG_RESULT([${path_udevdir}]) fi AC_SUBST(UDEV_DIR, [${path_udevdir}]) AC_ARG_ENABLE(cups, AS_HELP_STRING([--disable-cups], [disable CUPS printer support]), [enable_cups=${enableval}]) AM_CONDITIONAL(CUPS, test "${enable_cups}" != "no") if (test "${enable_cups}" != "no"); then AC_MSG_CHECKING([cups directory]) cups_serverbin=`$PKG_CONFIG cups --variable=cups_serverbin` AC_MSG_RESULT([${cups_serverbin}]) fi AM_CONDITIONAL(CUPS_SERVERBIN, test "${cups_serverbin}" != "") AS_IF([test "${cups_serverbin}" != ""],[ AC_SUBST(CUPS_SERVERBIN, ${cups_serverbin}) ]) AC_ARG_ENABLE(mesh, AS_HELP_STRING([--enable-mesh], [enable Mesh profile support]), [enable_mesh=${enableval}]) AM_CONDITIONAL(MESH, test "${enable_mesh}" = "yes") if (test "${enable_mesh}" = "yes"); then PKG_CHECK_MODULES(JSONC, json-c >= 0.13) fi AC_ARG_ENABLE(midi, AS_HELP_STRING([--enable-midi], [enable MIDI support]), [enable_midi=${enableval}]) AM_CONDITIONAL(MIDI, test "${enable_midi}" = "yes") if (test "${enable_midi}" = "yes"); then PKG_CHECK_MODULES(ALSA, alsa) fi AC_ARG_ENABLE(obex, AS_HELP_STRING([--disable-obex], [disable OBEX profile support]), [enable_obex=${enableval}]) if (test "${enable_obex}" != "no"); then PKG_CHECK_MODULES(ICAL, [libicalvcal >= 4.0.0], [], [PKG_CHECK_MODULES(ICAL, [libical])] ) fi AM_CONDITIONAL(OBEX, test "${enable_obex}" != "no") AC_ARG_ENABLE(btpclient, AS_HELP_STRING([--enable-btpclient], [enable BTP client]), [enable_btpclient=${enableval}]) AM_CONDITIONAL(BTPCLIENT, test "${enable_btpclient}" = "yes") AC_ARG_ENABLE([external_ell], AS_HELP_STRING([--enable-external-ell], [enable external Embedded Linux library]), [enable_external_ell=${enableval}]) if (test "${enable_external_ell}" = "yes"); then PKG_CHECK_MODULES(ELL, ell >= 0.39) fi if (test "${enable_external_ell}" != "yes" && (test "${enable_btpclient}" = "yes" || test "${enable_mesh}" = "yes")); then if (test ! -f ${srcdir}/ell/ell.h) && (test ! -f ${srcdir}/../ell/ell/ell.h); then AC_MSG_ERROR(ELL source is required or use --enable-external-ell) fi fi AM_CONDITIONAL(EXTERNAL_ELL, test "${enable_external_ell}" = "yes" || (test "${enable_btpclient}" != "yes" && test "${enable_mesh}" != "yes")) AM_CONDITIONAL(LIBSHARED_ELL, test "${enable_btpclient}" = "yes" || test "${enable_mesh}" = "yes") AC_ARG_ENABLE(client, AS_HELP_STRING([--disable-client], [disable command line client]), [enable_client=${enableval}]) AM_CONDITIONAL(CLIENT, test "${enable_client}" != "no") if (test "${enable_client}" != "no" || test "${enable_mesh}" = "yes"); then AC_CHECK_HEADERS(readline/readline.h, enable_readline=yes, AC_MSG_ERROR(readline header files are required)) fi AM_CONDITIONAL(READLINE, test "${enable_readline}" = "yes") AC_ARG_ENABLE(systemd, AS_HELP_STRING([--disable-systemd], [disable systemd integration]), [enable_systemd=${enableval}]) AM_CONDITIONAL(SYSTEMD, test "${enable_systemd}" != "no") AC_ARG_WITH([systemdsystemunitdir], AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [path to systemd system unit directory]), [path_systemunitdir=${withval}]) if (test "${enable_systemd}" != "no" && test -z "${path_systemunitdir}"); then AC_MSG_CHECKING([systemd system unit dir]) path_systemunitdir="`$PKG_CONFIG --variable=systemdsystemunitdir systemd`" if (test -z "${path_systemunitdir}"); then AC_MSG_ERROR([systemd system unit directory is required]) fi AC_MSG_RESULT([${path_systemunitdir}]) fi AC_SUBST(SYSTEMD_SYSTEMUNITDIR, [${path_systemunitdir}]) AC_ARG_WITH([systemduserunitdir], AS_HELP_STRING([--with-systemduserunitdir=DIR], [path to systemd user unit directory]), [path_userunitdir=${withval}]) if (test "${enable_systemd}" != "no" && test -z "${path_userunitdir}"); then AC_MSG_CHECKING([systemd user unit dir]) path_userunitdir="`$PKG_CONFIG --variable=systemduserunitdir systemd`" if (test -z "${path_userunitdir}"); then AC_MSG_ERROR([systemd user unit directory is required]) fi AC_MSG_RESULT([${path_userunitdir}]) fi AC_SUBST(SYSTEMD_USERUNITDIR, [${path_userunitdir}]) AC_ARG_ENABLE(datafiles, AS_HELP_STRING([--disable-datafiles], [do not install configuration and data files]), [enable_datafiles=${enableval}]) AM_CONDITIONAL(DATAFILES, test "${enable_datafiles}" != "no") AC_ARG_ENABLE(manpages, AS_HELP_STRING([--disable-manpages], [disable building of manual pages]), [enable_manpages=${enableval}]) if (test "${enable_manpages}" != "no"); then AC_CHECK_PROGS(RST2MAN, [rst2man rst2man.py], "no") if (test "${RST2MAN}" = "no" ); then AC_MSG_ERROR([rst2man is required]) fi fi AM_CONDITIONAL(MANPAGES, test "${enable_manpages}" != "no") AM_CONDITIONAL(RUN_RST2MAN, test "${enable_manpages}" != "no" && test "${RST2MAN}" != "no") AC_ARG_ENABLE(testing, AS_HELP_STRING([--enable-testing], [enable testing tools]), [enable_testing=${enableval}]) AM_CONDITIONAL(TESTING, test "${enable_testing}" = "yes") if (test "${enable_testing}" = "yes"); then AC_CHECK_DECLS([SOF_TIMESTAMPING_TX_COMPLETION, SCM_TSTAMP_COMPLETION], [], [], [[#include #include #include ]]) fi AC_ARG_ENABLE(experimental, AS_HELP_STRING([--enable-experimental], [enable experimental tools]), [enable_experimental=${enableval}]) AM_CONDITIONAL(EXPERIMENTAL, test "${enable_experimental}" = "yes") AC_ARG_ENABLE(deprecated, AS_HELP_STRING([--enable-deprecated], [enable deprecated tools]), [enable_deprecated=${enableval}]) AM_CONDITIONAL(DEPRECATED, test "${enable_deprecated}" = "yes") AC_ARG_ENABLE(external-plugins, AS_HELP_STRING([--enable-external-plugins], [enable support for external plugins]), [enable_external_plugins=${enableval}]) AM_CONDITIONAL(EXTERNAL_PLUGINS, test "${enable_external_plugins}" = "yes") if (test "${enable_external_plugins}" = "yes"); then AC_DEFINE(EXTERNAL_PLUGINS, 1, [Define if external plugin support is required]) else AC_DEFINE(EXTERNAL_PLUGINS, 0, [Define if external plugin support is required]) fi AC_ARG_ENABLE(sixaxis, AS_HELP_STRING([--enable-sixaxis], [enable sixaxis plugin]), [enable_sixaxis=${enableval}]) AM_CONDITIONAL(SIXAXIS, test "${enable_sixaxis}" = "yes" && test "${enable_udev}" != "no") AC_ARG_ENABLE(hid2hci, AS_HELP_STRING([--enable-hid2hci], [enable hid2hci tool]), [enable_hid2hci=${enableval}]) AM_CONDITIONAL(HID2HCI, test "${enable_hid2hci}" = "yes" && test "${enable_udev}" != "no") AC_ARG_ENABLE(logger, AS_HELP_STRING([--enable-logger], [enable HCI logger service]), [enable_logger=${enableval}]) AM_CONDITIONAL(LOGGER, test "${enable_logger}" = "yes") AC_ARG_ENABLE(admin, AS_HELP_STRING([--enable-admin], [enable admin policy plugin]), [enable_admin=${enableval}]) AM_CONDITIONAL(ADMIN, test "${enable_admin}" = "yes") if (test "${prefix}" = "NONE"); then dnl no prefix and no localstatedir, so default to /var if (test "$localstatedir" = '${prefix}/var'); then AC_SUBST([localstatedir], ['/var']) fi prefix="${ac_default_prefix}" fi if (test "${exec_prefix}" = "NONE"); then # exec_prefix defaults to prefix, although our manual handling of the # latter (above) confuses autoconf. Manually set the exec_prefix. exec_prefix="${prefix}" fi # Expand any variables containing relative references like ${prefix} and co. # # Otherwise we'll end up with literal references in the final binaries or # manuals, which is not something we really want. # pkgbindir="${bindir}" if (test "$bindir" = '${exec_prefix}/bin'); then pkgbindir="${exec_prefix}/bin" else pkgbindir="${bindir}" fi AC_SUBST(PKGBINDIR, "${pkgbindir}") if (test "$libexecdir" = '${exec_prefix}/libexec'); then pkglibexecdir="${exec_prefix}/libexec/bluetooth" else pkglibexecdir="${libexecdir}/bluetooth" fi AC_SUBST(PKGLIBEXECDIR, "${pkglibexecdir}") if (test "$localstatedir" = '${prefix}/var'); then storagedir="${prefix}/var/lib/bluetooth" else storagedir="${localstatedir}/lib/bluetooth" fi AC_DEFINE_UNQUOTED(STORAGEDIR, "${storagedir}", [Directory for the storage files]) if (test "$sysconfdir" = '${prefix}/etc'); then configdir="${prefix}/etc/bluetooth" else configdir="${sysconfdir}/bluetooth" fi AC_DEFINE_UNQUOTED(CONFIGDIR, "${configdir}", [Directory for the configuration files]) AC_SUBST(CONFIGDIR, "${configdir}") AC_DEFINE_UNQUOTED(MESH_STORAGEDIR, "${storagedir}/mesh", [Directory for the mesh daemon storage files]) AC_SUBST(MESH_STORAGEDIR, "${storagedir}/mesh") AC_ARG_WITH([phonebook], AS_HELP_STRING([--with-phonebook=PLUGIN], [obexd phonebook plugin (default=dummy)]), [plugin_phonebook=${withval}]) if (test -z "${plugin_phonebook}"); then plugin_phonebook=dummy fi if (test "${plugin_phonebook}" = "ebook"); then PKG_CHECK_MODULES(LIBEBOOK, libebook-1.2 >= 3.3) PKG_CHECK_MODULES(LIBEDATESERVER, libedataserver-1.2 >= 3.3) fi AC_SUBST(PLUGIN_PHONEBOOK, [${plugin_phonebook}]) AC_CONFIG_FILES( lib/bluez.pc Makefile doc/bluetooth-meshd.rst mesh/bluetooth-mesh.service obexd/src/obex.service obexd/src/org.bluez.obex.service doc/bluetoothd.rst src/bluetooth.service tools/bluetooth-logger.service tools/mpris-proxy.service ) AC_OUTPUT bluez-5.87/PaxHeaders/acinclude.m40000644000000000000000000000005015017252326014047 xustar0020 atime=1783092709 20 ctime=1783094098 bluez-5.87/acinclude.m40000644000000000000000000001166515017252326013541 0ustar00rootrootAC_DEFUN([AC_PROG_CC_PIE], [ AC_CACHE_CHECK([whether ${CC-cc} accepts -fPIE], ac_cv_prog_cc_pie, [ echo 'void f(){}' > conftest.c if test -z "`${CC-cc} -fPIE -pie -c conftest.c 2>&1`"; then ac_cv_prog_cc_pie=yes else ac_cv_prog_cc_pie=no fi rm -rf conftest* ]) ]) AC_DEFUN([AC_PROG_CC_ASAN], [ AC_CACHE_CHECK([whether ${CC-cc} accepts -fsanitize=address], ac_cv_prog_cc_asan, [ echo 'void f(){}' > asan.c if test -z "`${CC-cc} -fsanitize=address -c asan.c 2>&1`"; then ac_cv_prog_cc_asan=yes else ac_cv_prog_cc_asan=no fi rm -rf asan* ]) ]) AC_DEFUN([AC_PROG_CC_LSAN], [ AC_CACHE_CHECK([whether ${CC-cc} accepts -fsanitize=leak], ac_cv_prog_cc_lsan, [ echo 'void f(){}' > lsan.c if test -z "`${CC-cc} -fsanitize=leak -c lsan.c 2>&1`"; then ac_cv_prog_cc_lsan=yes else ac_cv_prog_cc_lsan=no fi rm -rf lsan* ]) ]) AC_DEFUN([AC_PROG_CC_UBSAN], [ AC_CACHE_CHECK([whether ${CC-cc} accepts -fsanitize=undefined], ac_cv_prog_cc_ubsan, [ echo 'void f(){}' > ubsan.c if test -z "`${CC-cc} -fsanitize=undefined -c ubsan.c 2>&1`"; then ac_cv_prog_cc_ubsan=yes else ac_cv_prog_cc_ubsan=no fi rm -rf ubsan* ]) ]) AC_DEFUN([COMPILER_FLAGS], [ with_cflags="" if (test "$USE_MAINTAINER_MODE" = "yes"); then with_cflags="$with_cflags -Wall -Werror -Wextra" with_cflags="$with_cflags -Wno-unused-parameter" with_cflags="$with_cflags -Wno-missing-field-initializers" with_cflags="$with_cflags -Wdeclaration-after-statement" with_cflags="$with_cflags -Wmissing-declarations" with_cflags="$with_cflags -Wredundant-decls" with_cflags="$with_cflags -Wcast-align" with_cflags="$with_cflags -Wswitch-enum" with_cflags="$with_cflags -Wformat -Wformat-security" with_cflags="$with_cflags -Wstringop-overflow" with_cflags="$with_cflags -DG_DISABLE_DEPRECATED" with_cflags="$with_cflags -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_36" with_cflags="$with_cflags -DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_36" fi AC_SUBST([WARNING_CFLAGS], $with_cflags) ]) AC_DEFUN([MISC_FLAGS], [ misc_cflags="" misc_ldflags="" AC_ARG_ENABLE(optimization, AS_HELP_STRING([--disable-optimization], [disable code optimization through compiler]), [ if (test "${enableval}" = "no"); then misc_cflags="$misc_cflags -O0" fi ]) AC_ARG_ENABLE(asan, AS_HELP_STRING([--enable-asan], [enable linking with address sanitizer]), [ save_LIBS=$LIBS AC_CHECK_LIB(asan, _init) LIBS=$save_LIBS if (test "${enableval}" = "yes" && test "${ac_cv_lib_asan__init}" = "yes" && test "${ac_cv_prog_cc_asan}" = "yes"); then misc_cflags="$misc_cflags -fsanitize=address"; misc_ldflags="$misc_ldflags -fsanitize=address" AC_SUBST([ASAN_LIB], ${ac_cv_lib_asan__init}) fi ]) AC_ARG_ENABLE(lsan, AS_HELP_STRING([--enable-lsan], [enable linking with address sanitizer]), [ save_LIBS=$LIBS AC_CHECK_LIB(lsan, _init) LIBS=$save_LIBS if (test "${enableval}" = "yes" && test "${ac_cv_lib_lsan__init}" = "yes" && test "${ac_cv_prog_cc_lsan}" = "yes"); then misc_cflags="$misc_cflags -fsanitize=leak"; misc_ldflags="$misc_ldflags -fsanitize=leak" AC_SUBST([ASAN_LIB], ${ac_cv_lib_lsan__init}) fi ]) AC_ARG_ENABLE(ubsan, AS_HELP_STRING([--enable-ubsan], [enable linking with address sanitizer]), [ save_LIBS=$LIBS AC_CHECK_LIB(ubsan, _init) LIBS=$save_LIBS if (test "${enableval}" = "yes" && test "${ac_cv_lib_ubsan__init}" = "yes" && test "${ac_cv_prog_cc_ubsan}" = "yes"); then misc_cflags="$misc_cflags -fsanitize=undefined"; misc_ldflags="$misc_ldflags -fsanitize=undefined"; fi ]) AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug], [enable compiling with debugging information]), [ if (test "${enableval}" = "yes" && test "${ac_cv_prog_cc_g}" = "yes"); then misc_cflags="$misc_cflags -g" fi ]) AC_ARG_ENABLE(pie, AS_HELP_STRING([--enable-pie], [enable position independent executables flag]), [ if (test "${enableval}" = "yes" && test "${ac_cv_prog_cc_pie}" = "yes"); then misc_cflags="$misc_cflags -fPIC" misc_ldflags="$misc_ldflags -pie -Wl,-z,now" fi ]) if (test "$enable_coverage" = "yes"); then misc_cflags="$misc_cflags --coverage" misc_ldflags="$misc_ldflags --coverage" fi if (test "$USE_MAINTAINER_MODE" = "yes"); then AC_CACHE_CHECK([whether ${CC-cc} accepts -D_FORTIFY_SOURCE=3], ac_cv_prog_cc_fortify_source_3, [ echo '#include ' > fortify.c if test -z "`${CC-cc} ${CFLAGS} ${misc_cflags} -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -c fortify.c 2>&1`"; then ac_cv_prog_cc_fortify_source_3=yes else ac_cv_prog_cc_fortify_source_3=no fi rm -f fortify.c fortify.o ]) if test "${ac_cv_prog_cc_fortify_source_3}" = "yes"; then misc_cflags="$misc_cflags -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3" fi fi misc_cflags="$misc_cflags -ffunction-sections -fdata-sections" misc_ldflags="$misc_ldflags -Wl,--gc-sections" AC_SUBST([MISC_CFLAGS], $misc_cflags) AC_SUBST([MISC_LDFLAGS], $misc_ldflags) ]) bluez-5.87/PaxHeaders/NEWS0000644000000000000000000000005011071665350012356 xustar0020 atime=1770587285 20 ctime=1783094098 bluez-5.87/NEWS0000644000000000000000000000000011071665350012025 0ustar00rootrootbluez-5.87/PaxHeaders/configure0000644000000000000000000000005015221753010013553 xustar0020 atime=1783092745 20 ctime=1783094098 bluez-5.87/configure0000755000000000000000000226001515221753010013245 0ustar00rootroot#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.73 for bluez 5.87. # # # Copyright (C) 1992-1996, 1998-2017, 2020-2026 Free Software Foundation, # Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # contradicts POSIX and common usage. Disable this. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case e in #( e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf '%s\n' "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then _as_can_reexec=no; export _as_can_reexec; # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac case $# in # (( 0) exec $CONFIG_SHELL $as_opts "$as_myself" ;; *) exec $CONFIG_SHELL $as_opts "$as_myself" "$@" ;; esac # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed 'exec'. printf '%s\n' "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # contradicts POSIX and common usage. Disable this. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else case e in #( e) case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ) then : else case e in #( e) exitcode=1; echo positional parameters were not saved. ;; esac fi test x\$exitcode = x0 || exit 1 blah=\$(echo \$(echo blah)) test x\"\$blah\" = xblah || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 test -n \"\${ZSH_VERSION+set}\${BASH_VERSION+set}\" || ( ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO PATH=/empty FPATH=/empty; export PATH FPATH test \"X\`printf %s \$ECHO\`\" = \"X\$ECHO\" \\ || test \"X\`print -r -- \$ECHO\`\" = \"X\$ECHO\" ) || exit 1 test \$(( 1 + 1 )) = 2 || exit 1" if (eval "$as_required") 2>/dev/null then : as_have_required=yes else case e in #( e) as_have_required=no ;; esac fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null then : else case e in #( e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$as_shell as_have_required=yes if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null then : break 2 fi fi done;; esac as_found=false done IFS=$as_save_IFS if $as_found then : else case e in #( e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$SHELL as_have_required=yes fi ;; esac fi if test "x$CONFIG_SHELL" != x then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac case $# in # (( 0) exec $CONFIG_SHELL $as_opts "$as_myself" ;; *) exec $CONFIG_SHELL $as_opts "$as_myself" "$@" ;; esac # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed 'exec'. printf '%s\n' "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi if test x$as_have_required = xno then : printf '%s\n' "$0: This script requires a shell more modern than all" printf '%s\n' "$0: the shells that I found on your system." if test ${ZSH_VERSION+y} ; then printf '%s\n' "$0: In particular, zsh $ZSH_VERSION has bugs and should" printf '%s\n' "$0: be upgraded to zsh 4.3.4 or later." else printf '%s\n' "$0: Please tell bug-autoconf@gnu.org about your system, $0: including any error possibly output before this $0: message. Then install a modern shell, or manually run $0: the script under such a shell if you do have one." fi exit 1 fi ;; esac fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf '%s\n' "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf '%s\n' X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else case e in #( e) as_fn_append () { eval $1=\$$1\$2 } ;; esac fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else case e in #( e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } ;; esac fi # as_fn_arith # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf '%s\n' "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf '%s\n' X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' t clear :clear s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { printf '%s\n' "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall # in an infinite loop. This has already happened in practice. _as_can_reexec=no; export _as_can_reexec # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" as_tr_sh="eval sed '$as_sed_sh'" # deprecated SHELL=${CONFIG_SHELL-/bin/sh} test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_CONFIG_STATUS= ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='bluez' PACKAGE_TARNAME='bluez' PACKAGE_VERSION='5.87' PACKAGE_STRING='bluez 5.87' PACKAGE_BUGREPORT='' PACKAGE_URL='' ac_default_prefix=/usr/local # Factoring default headers for most tests. ac_includes_default="\ #include #ifdef HAVE_STDIO_H # include #endif #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_INTTYPES_H # include #endif #ifdef HAVE_STDINT_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_UNISTD_H # include #endif" ac_header_c_list= ac_subst_vars='am__EXEEXT_FALSE am__EXEEXT_TRUE LTLIBOBJS LIBOBJS PLUGIN_PHONEBOOK LIBEDATESERVER_LIBS LIBEDATESERVER_CFLAGS LIBEBOOK_LIBS LIBEBOOK_CFLAGS MESH_STORAGEDIR CONFIGDIR PKGLIBEXECDIR PKGBINDIR ADMIN_FALSE ADMIN_TRUE LOGGER_FALSE LOGGER_TRUE HID2HCI_FALSE HID2HCI_TRUE SIXAXIS_FALSE SIXAXIS_TRUE EXTERNAL_PLUGINS_FALSE EXTERNAL_PLUGINS_TRUE DEPRECATED_FALSE DEPRECATED_TRUE EXPERIMENTAL_FALSE EXPERIMENTAL_TRUE TESTING_FALSE TESTING_TRUE RUN_RST2MAN_FALSE RUN_RST2MAN_TRUE MANPAGES_FALSE MANPAGES_TRUE RST2MAN DATAFILES_FALSE DATAFILES_TRUE SYSTEMD_USERUNITDIR SYSTEMD_SYSTEMUNITDIR SYSTEMD_FALSE SYSTEMD_TRUE READLINE_FALSE READLINE_TRUE CLIENT_FALSE CLIENT_TRUE LIBSHARED_ELL_FALSE LIBSHARED_ELL_TRUE EXTERNAL_ELL_FALSE EXTERNAL_ELL_TRUE ELL_LIBS ELL_CFLAGS BTPCLIENT_FALSE BTPCLIENT_TRUE OBEX_FALSE OBEX_TRUE ICAL_LIBS ICAL_CFLAGS ALSA_LIBS ALSA_CFLAGS MIDI_FALSE MIDI_TRUE JSONC_LIBS JSONC_CFLAGS MESH_FALSE MESH_TRUE CUPS_SERVERBIN CUPS_SERVERBIN_FALSE CUPS_SERVERBIN_TRUE CUPS_FALSE CUPS_TRUE UDEV_DIR UDEV_LIBS UDEV_CFLAGS MONITOR_FALSE MONITOR_TRUE TOOLS_FALSE TOOLS_TRUE HFP_FALSE HFP_TRUE ASHA_FALSE ASHA_TRUE GMAP_FALSE GMAP_TRUE TMAP_FALSE TMAP_TRUE CSIP_FALSE CSIP_TRUE MICP_FALSE MICP_TRUE VCP_FALSE VCP_TRUE CCP_FALSE CCP_TRUE MCP_FALSE MCP_TRUE BASS_FALSE BASS_TRUE BAP_FALSE BAP_TRUE HOG_FALSE HOG_TRUE HID_FALSE HID_TRUE NETWORK_FALSE NETWORK_TRUE AVRCP_FALSE AVRCP_TRUE A2DP_FALSE A2DP_TRUE NFC_FALSE NFC_TRUE TEST_FALSE TEST_TRUE LIBRARY_FALSE LIBRARY_TRUE BACKTRACE_LIBS BACKTRACE_CFLAGS ZSH_COMPLETIONS_FALSE ZSH_COMPLETIONS_TRUE ZSH_COMPLETIONDIR DBUS_SESSIONBUSDIR DBUS_SYSTEMBUSDIR DBUS_CONFDIR DBUS_LIBS DBUS_CFLAGS GLIB_LIBS GLIB_CFLAGS VALGRIND_FALSE VALGRIND_TRUE MISC_LDFLAGS MISC_CFLAGS ASAN_LIB DBUS_RUN_SESSION_FALSE DBUS_RUN_SESSION_TRUE COVERAGE_FALSE COVERAGE_TRUE enable_valgrind enable_dbus_run_session enable_coverage LT_SYS_LIBRARY_PATH OTOOL64 OTOOL LIPO NMEDIT DSYMUTIL MANIFEST_TOOL RANLIB ac_ct_AR AR DLLTOOL OBJDUMP FILECMD LN_S NM ac_ct_DUMPBIN DUMPBIN LD FGREP EGREP GREP SED host_os host_vendor host_cpu host build_os build_vendor build_cpu build LIBTOOL am__fastdepCC_FALSE am__fastdepCC_TRUE CCDEPMODE am__nodep AMDEPBACKSLASH AMDEP_FALSE AMDEP_TRUE am__include DEPDIR OBJEXT EXEEXT ac_ct_CC CPPFLAGS LDFLAGS CFLAGS CC WARNING_CFLAGS PKG_CONFIG_LIBDIR PKG_CONFIG_PATH PKG_CONFIG MAINT MAINTAINER_MODE_FALSE MAINTAINER_MODE_TRUE am__xargs_n am__rm_f_notfound AM_BACKSLASH AM_DEFAULT_VERBOSITY AM_DEFAULT_V AM_V CSCOPE ETAGS CTAGS am__untar am__tar AMTAR am__leading_dot SET_MAKE AWK mkdir_p MKDIR_P INSTALL_STRIP_PROGRAM STRIP install_sh MAKEINFO AUTOHEADER AUTOMAKE AUTOCONF ACLOCAL VERSION PACKAGE CYGPATH_W am__isrc INSTALL_DATA INSTALL_SCRIPT INSTALL_PROGRAM ECHO_T ECHO_N ECHO_C target_alias host_alias build_alias LIBS DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir runstatedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL am__quote' ac_subst_files='' ac_user_opts=' enable_option_checking enable_silent_rules enable_maintainer_mode enable_dependency_tracking enable_static enable_shared enable_pic with_pic enable_fast_install enable_aix_soname with_aix_soname with_gnu_ld with_sysroot enable_libtool_lock enable_optimization enable_asan enable_lsan enable_ubsan enable_debug enable_pie enable_threads with_dbusconfdir with_dbussystembusdir with_dbussessionbusdir with_zsh_completion_dir enable_backtrace enable_library enable_test enable_nfc enable_a2dp enable_avrcp enable_network enable_hid enable_hog enable_bap enable_bass enable_mcp enable_ccp enable_vcp enable_micp enable_csip enable_tmap enable_gmap enable_asha enable_hfp enable_tools enable_monitor enable_udev with_udevdir enable_cups enable_mesh enable_midi enable_obex enable_btpclient enable_external_ell enable_client enable_systemd with_systemdsystemunitdir with_systemduserunitdir enable_datafiles enable_manpages enable_testing enable_experimental enable_deprecated enable_external_plugins enable_sixaxis enable_hid2hci enable_logger enable_admin with_phonebook ' ac_precious_vars='build_alias host_alias target_alias PKG_CONFIG PKG_CONFIG_PATH PKG_CONFIG_LIBDIR CC CFLAGS LDFLAGS LIBS CPPFLAGS LT_SYS_LIBRARY_PATH GLIB_CFLAGS GLIB_LIBS DBUS_CFLAGS DBUS_LIBS UDEV_CFLAGS UDEV_LIBS JSONC_CFLAGS JSONC_LIBS ALSA_CFLAGS ALSA_LIBS ICAL_CFLAGS ICAL_LIBS ELL_CFLAGS ELL_LIBS LIBEBOOK_CFLAGS LIBEBOOK_LIBS LIBEDATESERVER_CFLAGS LIBEDATESERVER_LIBS' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -runstatedir | --runstatedir | --runstatedi | --runstated \ | --runstate | --runstat | --runsta | --runst | --runs \ | --run | --ru | --r) ac_prev=runstatedir ;; -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ | --run=* | --ru=* | --r=*) runstatedir=$ac_optarg ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: '$ac_option' Try '$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: '$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. printf '%s\n' "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && printf '%s\n' "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac done if test -n "$ac_prev"; then ac_option=--`printf '%s\n' $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) printf '%s\n' "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: '$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || printf '%s\n' X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF 'configure' configures bluez 5.87 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print 'checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for '--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or '..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, 'make install' will install all the files in '$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify an installation prefix other than '$ac_default_prefix' using '--prefix', for instance '--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/bluez] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF Program names: --program-prefix=PREFIX prepend PREFIX to installed program names --program-suffix=SUFFIX append SUFFIX to installed program names --program-transform-name=PROGRAM run sed PROGRAM on installed program names System types: --build=BUILD configure for building on BUILD [guessed] --host=HOST cross-compile to build programs to run on HOST [BUILD] _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in short | recursive ) echo "Configuration of bluez 5.87:";; esac cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-silent-rules less verbose build output (undo: "make V=1") --disable-silent-rules verbose build output (undo: "make V=0") --enable-maintainer-mode enable make rules and dependencies not useful (and sometimes confusing) to the casual installer --enable-dependency-tracking do not reject slow dependency extractors --disable-dependency-tracking speeds up one-time build --enable-static[=PKGS] build static libraries [default=no] --enable-shared[=PKGS] build shared libraries [default=yes] --enable-pic[=PKGS] try to use only PIC/non-PIC objects [default=use both] --enable-fast-install[=PKGS] optimize for fast installation [default=yes] --enable-aix-soname=aix|svr4|both shared library versioning (aka "SONAME") variant to provide on AIX, [default=aix]. --disable-libtool-lock avoid locking (might break parallel builds) --disable-optimization disable code optimization through compiler --enable-asan enable linking with address sanitizer --enable-lsan enable linking with address sanitizer --enable-ubsan enable linking with address sanitizer --enable-debug enable compiling with debugging information --enable-pie enable position independent executables flag --enable-threads enable threading support --enable-backtrace compile backtrace support --enable-library install Bluetooth library --enable-test enable test/example scripts --enable-nfc enable NFC pairing --disable-a2dp disable A2DP profile --disable-avrcp disable AVRCP profile --disable-network disable network profiles --disable-hid disable HID profile --disable-hog disable HoG profile --disable-bap disable BAP profile --disable-bass disable BASS service --disable-mcp disable MCP profile --disable-ccp disable CCP profile --disable-vcp disable VCP profile --disable-micp disable MICP profile --disable-csip disable CSIP profile --disable-tmap disable TMAP profile --disable-gmap disable GMAP profile --disable-asha disable ASHA support --disable-hfp disable HFP support --disable-tools disable Bluetooth tools --disable-monitor disable Bluetooth monitor --disable-udev disable udev device support --disable-cups disable CUPS printer support --enable-mesh enable Mesh profile support --enable-midi enable MIDI support --disable-obex disable OBEX profile support --enable-btpclient enable BTP client --enable-external-ell enable external Embedded Linux library --disable-client disable command line client --disable-systemd disable systemd integration --disable-datafiles do not install configuration and data files --disable-manpages disable building of manual pages --enable-testing enable testing tools --enable-experimental enable experimental tools --enable-deprecated enable deprecated tools --enable-external-plugins enable support for external plugins --enable-sixaxis enable sixaxis plugin --enable-hid2hci enable hid2hci tool --enable-logger enable HCI logger service --enable-admin enable admin policy plugin Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-gnu-ld assume the C compiler uses GNU ld [default=no] --with-sysroot[=DIR] Search for dependent libraries within DIR (or the compiler's sysroot if not specified). --with-dbusconfdir=DIR path to D-Bus configuration directory --with-dbussystembusdir=DIR path to D-Bus system bus services directory --with-dbussessionbusdir=DIR path to D-Bus session bus services directory --with-zsh-completion-dir=DIR path to install zsh completions --with-udevdir=DIR path to udev directory --with-systemdsystemunitdir=DIR path to systemd system unit directory --with-systemduserunitdir=DIR path to systemd user unit directory --with-phonebook=PLUGIN obexd phonebook plugin (default=dummy) Some influential environment variables: PKG_CONFIG path to pkg-config utility PKG_CONFIG_PATH directories to add to pkg-config's search path PKG_CONFIG_LIBDIR path overriding pkg-config's built-in search path CC C compiler command CFLAGS C compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory LT_SYS_LIBRARY_PATH User-defined run-time library search path. GLIB_CFLAGS C compiler flags for GLIB, overriding pkg-config GLIB_LIBS linker flags for GLIB, overriding pkg-config DBUS_CFLAGS C compiler flags for DBUS, overriding pkg-config DBUS_LIBS linker flags for DBUS, overriding pkg-config UDEV_CFLAGS C compiler flags for UDEV, overriding pkg-config UDEV_LIBS linker flags for UDEV, overriding pkg-config JSONC_CFLAGS C compiler flags for JSONC, overriding pkg-config JSONC_LIBS linker flags for JSONC, overriding pkg-config ALSA_CFLAGS C compiler flags for ALSA, overriding pkg-config ALSA_LIBS linker flags for ALSA, overriding pkg-config ICAL_CFLAGS C compiler flags for ICAL, overriding pkg-config ICAL_LIBS linker flags for ICAL, overriding pkg-config ELL_CFLAGS C compiler flags for ELL, overriding pkg-config ELL_LIBS linker flags for ELL, overriding pkg-config LIBEBOOK_CFLAGS C compiler flags for LIBEBOOK, overriding pkg-config LIBEBOOK_LIBS linker flags for LIBEBOOK, overriding pkg-config LIBEDATESERVER_CFLAGS C compiler flags for LIBEDATESERVER, overriding pkg-config LIBEDATESERVER_LIBS linker flags for LIBEDATESERVER, overriding pkg-config Use these variables to override the choices made by 'configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to the package provider. _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf '%s\n' "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf '%s\n' "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for configure.gnu first; this name is used for a wrapper for # Metaconfig's "Configure" on case-insensitive file systems. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else printf '%s\n' "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF bluez configure 5.87 generated by GNU Autoconf 2.73 Copyright (C) 2026 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## # ac_fn_c_try_compile LINENO # -------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext then : ac_retval=0 else case e in #( e) printf '%s\n' "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_compile # ac_fn_c_try_link LINENO # ----------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext } then : ac_retval=0 else case e in #( e) printf '%s\n' "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_link # ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists and can be compiled using the include files in # INCLUDES, setting the cache variable VAR accordingly. ac_fn_c_check_header_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$3=yes" else case e in #( e) eval "$3=no" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi eval ac_res=\$$3 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf '%s\n' "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_compile # ac_fn_c_check_func LINENO FUNC VAR # ---------------------------------- # Tests whether FUNC exists, setting the cache variable VAR accordingly ac_fn_c_check_func () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case declares $2. For example, HP-UX 11i declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes, which can conflict with char $2 (void); below. */ #include #undef $2 /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char $2 (void); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined __stub_$2 || defined __stub___$2 choke me #endif int main (void) { return $2 (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : eval "$3=yes" else case e in #( e) eval "$3=no" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext ;; esac fi eval ac_res=\$$3 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf '%s\n' "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_func # ac_fn_check_decl LINENO SYMBOL VAR INCLUDES EXTRA-OPTIONS FLAG-VAR # ------------------------------------------------------------------ # Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR # accordingly. Pass EXTRA-OPTIONS to the compiler, using FLAG-VAR. ac_fn_check_decl () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack as_decl_name=`echo $2|sed 's/ *(.*//'` { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5 printf %s "checking whether $as_decl_name is declared... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'` eval ac_save_FLAGS=\$$6 as_fn_append $6 " $5" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { #ifndef $as_decl_name #ifdef __cplusplus (void) $as_decl_use; #else (void) $as_decl_name; #endif #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$3=yes" else case e in #( e) eval "$3=no" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext eval $6=\$ac_save_FLAGS ;; esac fi eval ac_res=\$$3 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf '%s\n' "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_check_decl ac_configure_args_raw= for ac_arg do case $ac_arg in *\'*) ac_arg=`printf '%s\n' "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append ac_configure_args_raw " '$ac_arg'" done case $ac_configure_args_raw in *$as_nl*) ac_safe_unquote= ;; *) ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. ac_unsafe_a="$ac_unsafe_z#~" ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" ac_configure_args_raw=` printf '%s\n' "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; esac cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by bluez $as_me 5.87, which was generated by GNU Autoconf 2.73. Invocation command line was $ $0$ac_configure_args_raw _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac printf '%s\n' "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`printf '%s\n' "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # Dump the cache to stdout. It can be in a pipe (this is a requirement). ac_cache_dump () { # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 printf '%s\n' "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # 'set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # 'set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) } # Print debugging info to stdout. ac_dump_debugging_info () { echo printf '%s\n' "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo ac_cache_dump echo printf '%s\n' "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'*) ac_val=`printf '%s\n' "$ac_val" | sed "s/'/'\\\\\\\\''/g"`;; esac printf '%s\n' "$ac_var='$ac_val'" done | sort echo if test -n "$ac_subst_files"; then printf '%s\n' "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'*) ac_val=`printf '%s\n' "$ac_val" | sed "s/'/'\\\\\\\\''/g"`;; esac printf '%s\n' "$ac_var='$ac_val'" done | sort echo fi if test -s confdefs.h; then printf '%s\n' "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && printf '%s\n' "$as_me: caught signal $ac_signal" printf '%s\n' "$as_me: exit $exit_status" } # When interrupted or exit'd, cleanup temporary files, and complete # config.log. ac_exit_trap () { exit_status= # Sanitize IFS. IFS=" "" $as_nl" # Save into config.log some information that might help in debugging. ac_dump_debugging_info >&5 eval "rm -f $ac_clean_CONFIG_STATUS core *.core core.conftest.*" && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status } trap 'ac_exit_trap $?' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h printf '%s\n' "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. printf '%s\n' "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h printf '%s\n' "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h printf '%s\n' "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h printf '%s\n' "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h printf '%s\n' "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h printf '%s\n' "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. if test -n "$CONFIG_SITE"; then ac_site_files="$CONFIG_SITE" elif test "x$prefix" != xNONE; then ac_site_files="$prefix/share/config.site $prefix/etc/config.site" else ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" fi for ac_site_file in $ac_site_files do case $ac_site_file in #( */*) : ;; #( *) : ac_site_file=./$ac_site_file ;; esac if test -f "$ac_site_file" && test -r "$ac_site_file"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 printf '%s\n' "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See 'config.log' for more details" "$LINENO" 5; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 printf '%s\n' "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 printf '%s\n' "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Test code for whether the C compiler supports C23 (global declarations) ac_c_conftest_c23_globals=' /* Does the compiler advertise conformance to C17 or earlier? Although GCC 14 does not do that, even with -std=gnu23, it is close enough, and defines __STDC_VERSION == 202000L. */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ <= 201710L # error "Compiler advertises conformance to C17 or earlier" #endif // Check alignas. char alignas (double) c23_aligned_as_double; char alignas (0) c23_no_special_alignment; extern char c23_aligned_as_int; char alignas (0) alignas (int) c23_aligned_as_int; // Check alignof. enum { c23_int_alignment = alignof (int), c23_int_array_alignment = alignof (int[100]), c23_char_alignment = alignof (char) }; static_assert (0 < -alignof (int), "alignof is signed"); int function_with_unnamed_parameter (int) { return 0; } void c23_noreturn (); /* Test parsing of string and char UTF-8 literals (including hex escapes). The parens pacify GCC 15. */ bool use_u8 = (!sizeof u8"\xFF") == (!u8'\''x'\''); bool check_that_bool_works = true | false | !nullptr; #if !true # error "true does not work in #if" #endif #if false #elifdef __STDC_VERSION__ #else # error "#elifdef does not work" #endif #ifndef __has_c_attribute # error "__has_c_attribute not defined" #endif #ifndef __has_include # error "__has_include not defined" #endif #define LPAREN() ( #define FORTY_TWO(x) 42 #define VA_OPT_TEST(r, x, ...) __VA_OPT__ (FORTY_TWO r x)) static_assert (VA_OPT_TEST (LPAREN (), 0, <:-) == 42); static_assert (0b101010 == 42); static_assert (0B101010 == 42); static_assert (0xDEAD'\''BEEF == 3'\''735'\''928'\''559); static_assert (0.500'\''000'\''000 == 0.5); enum unsignedish : unsigned int { uione = 1 }; static_assert (0 < -uione); #include constexpr nullptr_t null_pointer = nullptr; static typeof (1 + 1L) two () { return 2; } static long int three () { return 3; } ' # Test code for whether the C compiler supports C23 (body of main). ac_c_conftest_c23_main=' { label_before_declaration: int arr[10] = {}; if (arr[0]) goto label_before_declaration; if (!arr[0]) goto label_at_end_of_block; label_at_end_of_block: } ok |= !null_pointer; ok |= two != three; ' # Test code for whether the C compiler supports C23 (complete). ac_c_conftest_c23_program="${ac_c_conftest_c23_globals} int main (int, char **) { int ok = 0; ${ac_c_conftest_c23_main} return ok; } " # Test code for whether the C compiler supports C89 (global declarations) ac_c_conftest_c89_globals=' /* Do not test the value of __STDC__, because some compilers define it to 0 or do not define it, while otherwise adequately conforming. */ #include #include struct stat; /* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ struct buf { int x; }; struct buf * (*rcsopen) (struct buf *, struct stat *, int); static char *e (char **p, int i) { return p[i]; } static char *f (char * (*g) (char **, int), char **p, ...) { char *s; va_list v; va_start (v,p); s = g (p, va_arg (v,int)); va_end (v); return s; } /* C89 style stringification. */ #define noexpand_stringify(a) #a const char *stringified = noexpand_stringify(arbitrary+token=sequence); /* C89 style token pasting. Exercises some of the corner cases that e.g. old MSVC gets wrong, but not very hard. */ #define noexpand_concat(a,b) a##b #define expand_concat(a,b) noexpand_concat(a,b) extern int vA; extern int vbee; #define aye A #define bee B int *pvA = &expand_concat(v,aye); int *pvbee = &noexpand_concat(v,bee); /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not \xHH hex character constants. These do not provoke an error unfortunately, instead are silently treated as an "x". The following induces an error, until -std is added to get proper ANSI mode. Curiously \x00 != x always comes out true, for an array size at least. It is necessary to write \x00 == 0 to get something that is true only with -std. */ int osf4_cc_array ['\''\x00'\'' == 0 ? 1 : -1]; /* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters inside strings and character constants. */ #define FOO(x) '\''x'\'' int xlc6_cc_array[FOO(a) == '\''x'\'' ? 1 : -1]; int test (int i, double x); struct s1 {int (*f) (int a);}; struct s2 {int (*f) (double a);}; int pairnames (int, char **, int *(*)(struct buf *, struct stat *, int), int, int);' # Test code for whether the C compiler supports C89 (body of main). ac_c_conftest_c89_main=' ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]); ' # Test code for whether the C compiler supports C99 (global declarations) ac_c_conftest_c99_globals=' /* Does the compiler advertise C99 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L # error "Compiler does not advertise C99 conformance" #endif // See if C++-style comments work. #include extern int puts (const char *); extern int printf (const char *, ...); extern int dprintf (int, const char *, ...); extern void *malloc (size_t); extern void free (void *); // Check varargs macros. These examples are taken from C99 6.10.3.5. // dprintf is used instead of fprintf to avoid needing to declare // FILE and stderr, and "aND" is used instead of "and" to work around // GCC bug 40564 which is irrelevant here. #define debug(...) dprintf (2, __VA_ARGS__) #define showlist(...) puts (#__VA_ARGS__) #define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__)) static void test_varargs_macros (void) { int x = 1234; int y = 5678; debug ("Flag"); debug ("X = %d\n", x); showlist (The first, second, aND third items.); report (x>y, "x is %d but y is %d", x, y); } // Check long long types. #define BIG64 18446744073709551615ull #define BIG32 4294967295ul #define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0) #if !BIG_OK #error "your preprocessor is broken" #endif #if BIG_OK #else #error "your preprocessor is broken" #endif static long long int bignum = -9223372036854775807LL; static unsigned long long int ubignum = BIG64; struct incomplete_array { int datasize; double data[]; }; struct named_init { int number; const wchar_t *name; double average; }; typedef const char *ccp; static inline int test_restrict (ccp restrict text) { // Iterate through items via the restricted pointer. // Also check for declarations in for loops. for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i) continue; return 0; } // Check varargs and va_copy. static bool test_varargs (const char *format, ...) { va_list args; va_start (args, format); va_list args_copy; va_copy (args_copy, args); const char *str = ""; int number = 0; float fnumber = 0; while (*format) { switch (*format++) { case '\''s'\'': // string str = va_arg (args_copy, const char *); break; case '\''d'\'': // int number = va_arg (args_copy, int); break; case '\''f'\'': // float fnumber = va_arg (args_copy, double); break; default: break; } } va_end (args_copy); va_end (args); return *str && number && fnumber; } ' # Test code for whether the C compiler supports C99 (body of main). ac_c_conftest_c99_main=' // Check bool. _Bool success = false; success |= (argc != 0); // Check restrict. if (test_restrict ("String literal") == 0) success = true; const char *restrict newvar = "Another string"; // Check varargs. success &= test_varargs ("s, d'\'' f .", "string", 65, 34.234); test_varargs_macros (); // Check flexible array members. static struct incomplete_array *volatile incomplete_array_pointer; struct incomplete_array *ia = incomplete_array_pointer; ia->datasize = 10; for (int i = 0; i < ia->datasize; ++i) ia->data[i] = i * 1.234; // Work around memory leak warnings. free (ia); // Check named initializers. struct named_init ni = { .number = 34, .name = L"Test wide string", .average = 543.34343, }; ni.number = 58; // Do not test for VLAs, as some otherwise-conforming compilers lack them. // C code should instead use __STDC_NO_VLA__; see Autoconf manual. // work around unused variable warnings ok |= (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == '\''x'\'' || ni.number != 58); ' # Test code for whether the C compiler supports C11 (global declarations) ac_c_conftest_c11_globals=' /* Does the compiler advertise C11 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L # error "Compiler does not advertise C11 conformance" #endif // Check _Alignas. char _Alignas (double) aligned_as_double; char _Alignas (0) no_special_alignment; extern char aligned_as_int; char _Alignas (0) _Alignas (int) aligned_as_int; // Check _Alignof. enum { int_alignment = _Alignof (int), int_array_alignment = _Alignof (int[100]), char_alignment = _Alignof (char) }; _Static_assert (0 < -_Alignof (int), "_Alignof is signed"); // Check _Noreturn. int _Noreturn does_not_return (void) { for (;;) continue; } // Check _Static_assert. struct test_static_assert { int x; _Static_assert (sizeof (int) <= sizeof (long int), "_Static_assert does not work in struct"); long int y; }; // Check UTF-8 literals. #define u8 syntax error! char const utf8_literal[] = u8"happens to be ASCII" "another string"; // Check duplicate typedefs. typedef long *long_ptr; typedef long int *long_ptr; typedef long_ptr long_ptr; // Anonymous structures and unions -- taken from C11 6.7.2.1 Example 1. struct anonymous { union { struct { int i; int j; }; struct { int k; long int l; } w; }; int m; } v1; ' # Test code for whether the C compiler supports C11 (body of main). ac_c_conftest_c11_main=' _Static_assert ((offsetof (struct anonymous, i) == offsetof (struct anonymous, w.k)), "Anonymous union alignment botch"); v1.i = 2; v1.w.k = 5; ok |= v1.i != 5; ' # Test code for whether the C compiler supports C11 (complete). ac_c_conftest_c11_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} ${ac_c_conftest_c11_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} ${ac_c_conftest_c11_main} return ok; } " # Test code for whether the C compiler supports C99 (complete). ac_c_conftest_c99_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} return ok; } " # Test code for whether the C compiler supports C89 (complete). ac_c_conftest_c89_program="${ac_c_conftest_c89_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} return ok; } " as_fn_append ac_header_c_list " stdio.h stdio_h HAVE_STDIO_H" as_fn_append ac_header_c_list " stdlib.h stdlib_h HAVE_STDLIB_H" as_fn_append ac_header_c_list " string.h string_h HAVE_STRING_H" as_fn_append ac_header_c_list " inttypes.h inttypes_h HAVE_INTTYPES_H" as_fn_append ac_header_c_list " stdint.h stdint_h HAVE_STDINT_H" as_fn_append ac_header_c_list " strings.h strings_h HAVE_STRINGS_H" as_fn_append ac_header_c_list " sys/stat.h sys_stat_h HAVE_SYS_STAT_H" as_fn_append ac_header_c_list " sys/types.h sys_types_h HAVE_SYS_TYPES_H" as_fn_append ac_header_c_list " unistd.h unistd_h HAVE_UNISTD_H" # Auxiliary files required by this configure script. ac_aux_files="config.guess config.sub ltmain.sh compile missing install-sh" # Locations in which to look for auxiliary files. ac_aux_dir_candidates="${srcdir}${PATH_SEPARATOR}${srcdir}/..${PATH_SEPARATOR}${srcdir}/../.." # Search for a directory containing all of the required auxiliary files, # $ac_aux_files, from the $PATH-style list $ac_aux_dir_candidates. # If we don't find one directory that contains all the files we need, # we report the set of missing files from the *first* directory in # $ac_aux_dir_candidates and give up. ac_missing_aux_files="" ac_first_candidate=: printf '%s\n' "$as_me:${as_lineno-$LINENO}: looking for aux files: $ac_aux_files" >&5 as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in $ac_aux_dir_candidates do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac as_found=: printf '%s\n' "$as_me:${as_lineno-$LINENO}: trying $as_dir" >&5 ac_aux_dir_found=yes ac_install_sh= for ac_aux in $ac_aux_files do # As a special case, if "install-sh" is required, that requirement # can be satisfied by any of "install-sh", "install.sh", or "shtool", # and $ac_install_sh is set appropriately for whichever one is found. if test x"$ac_aux" = x"install-sh" then if test -f "${as_dir}install-sh"; then printf '%s\n' "$as_me:${as_lineno-$LINENO}: ${as_dir}install-sh found" >&5 ac_install_sh="${as_dir}install-sh -c" elif test -f "${as_dir}install.sh"; then printf '%s\n' "$as_me:${as_lineno-$LINENO}: ${as_dir}install.sh found" >&5 ac_install_sh="${as_dir}install.sh -c" elif test -f "${as_dir}shtool"; then printf '%s\n' "$as_me:${as_lineno-$LINENO}: ${as_dir}shtool found" >&5 ac_install_sh="${as_dir}shtool install -c" else ac_aux_dir_found=no if $ac_first_candidate; then ac_missing_aux_files="${ac_missing_aux_files} install-sh" else break fi fi else if test -f "${as_dir}${ac_aux}"; then printf '%s\n' "$as_me:${as_lineno-$LINENO}: ${as_dir}${ac_aux} found" >&5 else ac_aux_dir_found=no if $ac_first_candidate; then ac_missing_aux_files="${ac_missing_aux_files} ${ac_aux}" else break fi fi fi done if test "$ac_aux_dir_found" = yes; then ac_aux_dir="$as_dir" break fi ac_first_candidate=false as_found=false done IFS=$as_save_IFS if $as_found then : else case e in #( e) as_fn_error $? "cannot find required auxiliary files:$ac_missing_aux_files" "$LINENO" 5 ;; esac fi # These three variables are undocumented and unsupported, # and are intended to be withdrawn in a future Autoconf release. # They can cause serious problems if a builder's source tree is in a directory # whose full name contains unusual characters. if test -f "${ac_aux_dir}config.guess"; then ac_config_guess="$SHELL ${ac_aux_dir}config.guess" fi if test -f "${ac_aux_dir}config.sub"; then ac_config_sub="$SHELL ${ac_aux_dir}config.sub" fi if test -f "$ac_aux_dir/configure"; then ac_configure="$SHELL ${ac_aux_dir}configure" fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5 printf '%s\n' "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5 printf '%s\n' "$as_me: error: '$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w= for ac_val in x $ac_old_val; do ac_old_val_w="$ac_old_val_w $ac_val" done ac_new_val_w= for ac_val in x $ac_new_val; do ac_new_val_w="$ac_new_val_w $ac_val" done if test "$ac_old_val_w" != "$ac_new_val_w"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5 printf '%s\n' "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5 printf '%s\n' "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5 printf '%s\n' "$as_me: former value: '$ac_old_val'" >&2;} { printf '%s\n' "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5 printf '%s\n' "$as_me: current value: '$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`printf '%s\n' "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 printf '%s\n' "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## # Determine whether it's possible to make 'echo' print without a newline. # These variables are no longer used directly by Autoconf, but are AC_SUBSTed # for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu am__api_version='1.18' # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install # SunOS /usr/etc/install # IRIX /sbin/install # AIX /bin/install # AmigaOS /C/install, which installs bootblocks on floppy discs # AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag # AFS /usr/afsws/bin/install, which mishandles nonexistent args # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. # Reject install programs that cannot install multiple files. { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 printf %s "checking for a BSD-compatible install... " >&6; } if test -z "$INSTALL"; then if test ${ac_cv_path_install+y} then : printf %s "(cached) " >&6 else case e in #( e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac # Account for fact that we put trailing slashes in our PATH walk. case $as_dir in #(( ./ | /[cC]/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ /usr/ucb/* ) ;; *) # OSF/1 and SCO ODT 3.0 have their own names for install. # Don't use installbsd from OSF/1 since it installs stuff as root # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_prog$ac_exec_ext"; then if test $ac_prog = install && grep dspmsg "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && grep pwplus "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else rm -rf conftest.one conftest.two conftest.dir echo one > conftest.one echo two > conftest.two mkdir conftest.dir if "$as_dir$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir/" && test -s conftest.one && test -s conftest.two && test -s conftest.dir/conftest.one && test -s conftest.dir/conftest.two then ac_cv_path_install="$as_dir$ac_prog$ac_exec_ext -c" break 3 fi fi fi done done ;; esac done IFS=$as_save_IFS rm -rf conftest.one conftest.two conftest.dir ;; esac fi if test ${ac_cv_path_install+y}; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a # value for INSTALL within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. INSTALL=$ac_install_sh fi fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 printf '%s\n' "$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether sleep supports fractional seconds" >&5 printf %s "checking whether sleep supports fractional seconds... " >&6; } if test ${am_cv_sleep_fractional_seconds+y} then : printf %s "(cached) " >&6 else case e in #( e) if sleep 0.001 2>/dev/null then : am_cv_sleep_fractional_seconds=yes else case e in #( e) am_cv_sleep_fractional_seconds=no ;; esac fi ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $am_cv_sleep_fractional_seconds" >&5 printf '%s\n' "$am_cv_sleep_fractional_seconds" >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking filesystem timestamp resolution" >&5 printf %s "checking filesystem timestamp resolution... " >&6; } if test ${am_cv_filesystem_timestamp_resolution+y} then : printf %s "(cached) " >&6 else case e in #( e) # Default to the worst case. am_cv_filesystem_timestamp_resolution=2 # Only try to go finer than 1 sec if sleep can do it. # Don't try 1 sec, because if 0.01 sec and 0.1 sec don't work, # - 1 sec is not much of a win compared to 2 sec, and # - it takes 2 seconds to perform the test whether 1 sec works. # # Instead, just use the default 2s on platforms that have 1s resolution, # accept the extra 1s delay when using $sleep in the Automake tests, in # exchange for not incurring the 2s delay for running the test for all # packages. # am_try_resolutions= if test "$am_cv_sleep_fractional_seconds" = yes; then # Even a millisecond often causes a bunch of false positives, # so just try a hundredth of a second. The time saved between .001 and # .01 is not terribly consequential. am_try_resolutions="0.01 0.1 $am_try_resolutions" fi # In order to catch current-generation FAT out, we must *modify* files # that already exist; the *creation* timestamp is finer. Use names # that make ls -t sort them differently when they have equal # timestamps than when they have distinct timestamps, keeping # in mind that ls -t prints the *newest* file first. rm -f conftest.ts? : > conftest.ts1 : > conftest.ts2 : > conftest.ts3 # Make sure ls -t actually works. Do 'set' in a subshell so we don't # clobber the current shell's arguments. (Outer-level square brackets # are removed by m4; they're present so that m4 does not expand # ; be careful, easy to get confused.) if ( set X `ls -t conftest.ts[12]` && { test "$*" != "X conftest.ts1 conftest.ts2" || test "$*" != "X conftest.ts2 conftest.ts1"; } ); then :; else # If neither matched, then we have a broken ls. This can happen # if, for instance, CONFIG_SHELL is bash and it inherits a # broken ls alias from the environment. This has actually # happened. Such a system could not be considered "sane". printf '%s\n' ""Bad output from ls -t: \"`ls -t conftest.ts[12]`\""" >&5 { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "ls -t produces unexpected output. Make sure there is not a broken ls alias in your environment. See 'config.log' for more details" "$LINENO" 5; } fi for am_try_res in $am_try_resolutions; do # Any one fine-grained sleep might happen to cross the boundary # between two values of a coarser actual resolution, but if we do # two fine-grained sleeps in a row, at least one of them will fall # entirely within a coarse interval. echo alpha > conftest.ts1 sleep $am_try_res echo beta > conftest.ts2 sleep $am_try_res echo gamma > conftest.ts3 # We assume that 'ls -t' will make use of high-resolution # timestamps if the operating system supports them at all. if (set X `ls -t conftest.ts?` && test "$2" = conftest.ts3 && test "$3" = conftest.ts2 && test "$4" = conftest.ts1); then # # Ok, ls -t worked. If we're at a resolution of 1 second, we're done, # because we don't need to test make. make_ok=true if test $am_try_res != 1; then # But if we've succeeded so far with a subsecond resolution, we # have one more thing to check: make. It can happen that # everything else supports the subsecond mtimes, but make doesn't; # notably on macOS, which ships make 3.81 from 2006 (the last one # released under GPLv2). https://bugs.gnu.org/68808 # # We test $MAKE if it is defined in the environment, else "make". # It might get overridden later, but our hope is that in practice # it does not matter: it is the system "make" which is (by far) # the most likely to be broken, whereas if the user overrides it, # probably they did so with a better, or at least not worse, make. # https://lists.gnu.org/archive/html/automake/2024-06/msg00051.html # # Create a Makefile (real tab character here): rm -f conftest.mk echo 'conftest.ts1: conftest.ts2' >conftest.mk echo ' touch conftest.ts2' >>conftest.mk # # Now, running # touch conftest.ts1; touch conftest.ts2; make # should touch ts1 because ts2 is newer. This could happen by luck, # but most often, it will fail if make's support is insufficient. So # test for several consecutive successes. # # (We reuse conftest.ts[12] because we still want to modify existing # files, not create new ones, per above.) n=0 make=${MAKE-make} until test $n -eq 3; do echo one > conftest.ts1 sleep $am_try_res echo two > conftest.ts2 # ts2 should now be newer than ts1 if $make -f conftest.mk | grep 'up to date' >/dev/null; then make_ok=false break # out of $n loop fi n=`expr $n + 1` done fi # if $make_ok; then # Everything we know to check worked out, so call this resolution good. am_cv_filesystem_timestamp_resolution=$am_try_res break # out of $am_try_res loop fi # Otherwise, we'll go on to check the next resolution. fi done rm -f conftest.ts? # (end _am_filesystem_timestamp_resolution) ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $am_cv_filesystem_timestamp_resolution" >&5 printf '%s\n' "$am_cv_filesystem_timestamp_resolution" >&6; } # This check should not be cached, as it may vary across builds of # different projects. { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5 printf %s "checking whether build environment is sane... " >&6; } # Reject unsafe characters in $srcdir or the absolute working directory # name. Accept space and tab only in the latter. am_lf=' ' case `pwd` in *[\\\"\#\$\&\'\`$am_lf]*) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5;; esac case $srcdir in *[\\\"\#\$\&\'\`$am_lf\ \ ]*) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } as_fn_error $? "unsafe srcdir value: '$srcdir'" "$LINENO" 5;; esac # Do 'set' in a subshell so we don't clobber the current shell's # arguments. Must try -L first in case configure is actually a # symlink; some systems play weird games with the mod time of symlinks # (eg FreeBSD returns the mod time of the symlink's containing # directory). am_build_env_is_sane=no am_has_slept=no rm -f conftest.file for am_try in 1 2; do echo "timestamp, slept: $am_has_slept" > conftest.file if ( set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` if test "$*" = "X"; then # -L didn't work. set X `ls -t "$srcdir/configure" conftest.file` fi test "$2" = conftest.file ); then am_build_env_is_sane=yes break fi # Just in case. sleep "$am_cv_filesystem_timestamp_resolution" am_has_slept=yes done { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $am_build_env_is_sane" >&5 printf '%s\n' "$am_build_env_is_sane" >&6; } if test "$am_build_env_is_sane" = no; then as_fn_error $? "newly created file is older than distributed files! Check your system clock" "$LINENO" 5 fi # If we didn't sleep, we still need to ensure time stamps of config.status and # generated files are strictly newer. am_sleep_pid= if test -e conftest.file || grep 'slept: no' conftest.file >/dev/null 2>&1 then : else case e in #( e) ( sleep "$am_cv_filesystem_timestamp_resolution" ) & am_sleep_pid=$! ;; esac fi rm -f conftest.file test "$program_prefix" != NONE && program_transform_name="s&^&$program_prefix&;$program_transform_name" # Use a double $ so make ignores it. test "$program_suffix" != NONE && program_transform_name="s&\$&$program_suffix&;$program_transform_name" # Double any \ or $. # By default was 's,x,x', remove it if useless. ac_script='s/[\\$]/&&/g;s/;s,x,x,$//' program_transform_name=`printf '%s\n' "$program_transform_name" | sed "$ac_script"` # Expand $ac_aux_dir to an absolute path. am_aux_dir=`cd "$ac_aux_dir" && pwd` if test x"${MISSING+set}" != xset; then MISSING="\${SHELL} '$am_aux_dir/missing'" fi # Use eval to expand $SHELL if eval "$MISSING --is-lightweight"; then am_missing_run="$MISSING " else am_missing_run= { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: 'missing' script is too old or missing" >&5 printf '%s\n' "$as_me: WARNING: 'missing' script is too old or missing" >&2;} fi if test x"${install_sh+set}" != xset; then case $am_aux_dir in *\ * | *\ *) install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; *) install_sh="\${SHELL} $am_aux_dir/install-sh" esac fi # Installed binaries are usually stripped using 'strip' when the user # run "make install-strip". However 'strip' might not be the right # tool to use in cross-compilation environments, therefore Automake # will honor the 'STRIP' environment variable to overrule this program. if test "$cross_compiling" != no; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. set dummy ${ac_tool_prefix}strip; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_STRIP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$STRIP"; then ac_cv_prog_STRIP="$STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_STRIP="${ac_tool_prefix}strip" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi STRIP=$ac_cv_prog_STRIP if test -n "$STRIP"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 printf '%s\n' "$STRIP" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_STRIP"; then ac_ct_STRIP=$STRIP # Extract the first word of "strip", so it can be a program name with args. set dummy strip; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_STRIP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_STRIP"; then ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_STRIP="strip" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP if test -n "$ac_ct_STRIP"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 printf '%s\n' "$ac_ct_STRIP" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_STRIP" = x; then STRIP=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac STRIP=$ac_ct_STRIP fi else STRIP="$ac_cv_prog_STRIP" fi fi INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for a race-free mkdir -p" >&5 printf %s "checking for a race-free mkdir -p... " >&6; } if test -z "$MKDIR_P"; then if test ${ac_cv_path_mkdir+y} then : printf %s "(cached) " >&6 else case e in #( e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in mkdir gmkdir; do for ac_exec_ext in '' $ac_executable_extensions; do as_fn_executable_p "$as_dir$ac_prog$ac_exec_ext" || continue case `"$as_dir$ac_prog$ac_exec_ext" --version 2>&1` in #( 'mkdir ('*'coreutils) '* | \ *'BusyBox '* | \ 'mkdir (fileutils) '4.1*) ac_cv_path_mkdir=$as_dir$ac_prog$ac_exec_ext break 3;; esac done done done IFS=$as_save_IFS ;; esac fi test -d ./--version && rmdir ./--version if test ${ac_cv_path_mkdir+y}; then MKDIR_P="$ac_cv_path_mkdir -p" else # As a last resort, use plain mkdir -p, # in the hope it doesn't have the bugs of ancient mkdir. MKDIR_P='mkdir -p' fi fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5 printf '%s\n' "$MKDIR_P" >&6; } for ac_prog in gawk mawk nawk awk 'busybox awk' do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_AWK+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$AWK"; then ac_cv_prog_AWK="$AWK" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_AWK="$ac_prog" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi AWK=$ac_cv_prog_AWK if test -n "$AWK"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5 printf '%s\n' "$AWK" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi test -n "$AWK" && break done { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 printf %s "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } set x ${MAKE-make} ac_make=`printf '%s\n' "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` if eval test \${ac_cv_prog_make_${ac_make}_set+y} then : printf %s "(cached) " >&6 else case e in #( e) cat >conftest.make <<\_ACEOF SHELL = /bin/sh all: @printf '%s\n' '@@@%%%=$(MAKE)=@@@%%%' _ACEOF # GNU make sometimes prints "make[1]: Entering ...", which would confuse us. case `${MAKE-make} -f conftest.make 2>/dev/null` in *@@@%%%=?*=@@@%%%*) eval ac_cv_prog_make_${ac_make}_set=yes;; *) eval ac_cv_prog_make_${ac_make}_set=no;; esac rm -f conftest.make ;; esac fi if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } SET_MAKE= else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } SET_MAKE="MAKE=${MAKE-make}" fi rm -rf .tst 2>/dev/null mkdir .tst 2>/dev/null if test -d .tst; then am__leading_dot=. else am__leading_dot=_ fi rmdir .tst 2>/dev/null AM_DEFAULT_VERBOSITY=1 # Check whether --enable-silent-rules was given. if test ${enable_silent_rules+y} then : enableval=$enable_silent_rules; fi am_make=${MAKE-make} { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5 printf %s "checking whether $am_make supports nested variables... " >&6; } if test ${am_cv_make_support_nested_variables+y} then : printf %s "(cached) " >&6 else case e in #( e) if printf '%s\n' 'TRUE=$(BAR$(V)) BAR0=false BAR1=true V=1 am__doit: @$(TRUE) .PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then am_cv_make_support_nested_variables=yes else am_cv_make_support_nested_variables=no fi ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5 printf '%s\n' "$am_cv_make_support_nested_variables" >&6; } AM_BACKSLASH='\' am__rm_f_notfound= if (rm -f && rm -fr && rm -rf) 2>/dev/null then : else case e in #( e) am__rm_f_notfound='""' ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking xargs -n works" >&5 printf %s "checking xargs -n works... " >&6; } if test ${am_cv_xargs_n_works+y} then : printf %s "(cached) " >&6 else case e in #( e) if test "`echo 1 2 3 | xargs -n2 echo`" = "1 2 3" then : am_cv_xargs_n_works=yes else case e in #( e) am_cv_xargs_n_works=no ;; esac fi ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $am_cv_xargs_n_works" >&5 printf '%s\n' "$am_cv_xargs_n_works" >&6; } if test "$am_cv_xargs_n_works" = yes then : am__xargs_n='xargs -n' else case e in #( e) am__xargs_n='am__xargs_n () { shift; sed "s/ /\\n/g" | while read am__xargs_n_arg; do "" "$am__xargs_n_arg"; done; }' ;; esac fi if test "`cd $srcdir && pwd`" != "`pwd`"; then # Use -I$(srcdir) only when $(srcdir) != ., so that make's output # is not polluted with repeated "-I." am__isrc=' -I$(srcdir)' # test to see if srcdir already configured if test -f $srcdir/config.status; then as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5 fi fi # test whether we have cygpath if test -z "$CYGPATH_W"; then if (cygpath --version) >/dev/null 2>/dev/null; then CYGPATH_W='cygpath -w' else CYGPATH_W=echo fi fi # Define the identity of the package. PACKAGE='bluez' VERSION='5.87' printf '%s\n' "#define PACKAGE \"$PACKAGE\"" >>confdefs.h printf '%s\n' "#define VERSION \"$VERSION\"" >>confdefs.h # Some tools Automake needs. ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"} AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"} AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"} AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"} MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} # For better backward compatibility. To be removed once Automake 1.9.x # dies out for good. For more background, see: # # mkdir_p='$(MKDIR_P)' # We need awk for the "check" target (and possibly the TAP driver). The # system "awk" is bad on some platforms. # Always define AMTAR for backward compatibility. Yes, it's still used # in the wild :-( We should find a proper way to deprecate it ... AMTAR='$${TAR-tar}' # We'll loop over all known methods to create a tar archive until one works. _am_tools='gnutar pax cpio none' { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking how to create a pax tar archive" >&5 printf %s "checking how to create a pax tar archive... " >&6; } # Go ahead even if we have the value already cached. We do so because we # need to set the values for the 'am__tar' and 'am__untar' variables. _am_tools=${am_cv_prog_tar_pax-$_am_tools} for _am_tool in $_am_tools; do case $_am_tool in gnutar) for _am_tar in tar gnutar gtar; do { echo "$as_me:$LINENO: $_am_tar --version" >&5 ($_am_tar --version) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && break done am__tar="$_am_tar --format=posix -chf - "'"$$tardir"' am__tar_="$_am_tar --format=posix -chf - "'"$tardir"' am__untar="$_am_tar -xf -" ;; plaintar) # Must skip GNU tar: if it does not support --format= it doesn't create # ustar tarball either. (tar --version) >/dev/null 2>&1 && continue am__tar='tar chf - "$$tardir"' am__tar_='tar chf - "$tardir"' am__untar='tar xf -' ;; pax) am__tar='pax -L -x pax -w "$$tardir"' am__tar_='pax -L -x pax -w "$tardir"' am__untar='pax -r' ;; cpio) am__tar='find "$$tardir" -print | cpio -o -H pax -L' am__tar_='find "$tardir" -print | cpio -o -H pax -L' am__untar='cpio -i -H pax -d' ;; none) am__tar=false am__tar_=false am__untar=false ;; esac # If the value was cached, stop now. We just wanted to have am__tar # and am__untar set. test -n "${am_cv_prog_tar_pax}" && break # tar/untar a dummy directory, and stop if the command works. rm -rf conftest.dir mkdir conftest.dir echo GrepMe > conftest.dir/file { echo "$as_me:$LINENO: tardir=conftest.dir && eval $am__tar_ >conftest.tar" >&5 (tardir=conftest.dir && eval $am__tar_ >conftest.tar) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } rm -rf conftest.dir if test -s conftest.tar; then { echo "$as_me:$LINENO: $am__untar &5 ($am__untar &5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } { echo "$as_me:$LINENO: cat conftest.dir/file" >&5 (cat conftest.dir/file) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } grep GrepMe conftest.dir/file >/dev/null 2>&1 && break fi done rm -rf conftest.dir if test ${am_cv_prog_tar_pax+y} then : printf %s "(cached) " >&6 else case e in #( e) am_cv_prog_tar_pax=$_am_tool ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_tar_pax" >&5 printf '%s\n' "$am_cv_prog_tar_pax" >&6; } # Variables for tags utilities; see am/tags.am if test -z "$CTAGS"; then CTAGS=ctags fi if test -z "$ETAGS"; then ETAGS=etags fi if test -z "$CSCOPE"; then CSCOPE=cscope fi ac_config_headers="$ac_config_headers config.h" AM_DEFAULT_VERBOSITY=0 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether to enable maintainer-specific portions of Makefiles" >&5 printf %s "checking whether to enable maintainer-specific portions of Makefiles... " >&6; } # Check whether --enable-maintainer-mode was given. if test ${enable_maintainer_mode+y} then : enableval=$enable_maintainer_mode; USE_MAINTAINER_MODE=$enableval else case e in #( e) USE_MAINTAINER_MODE=no ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $USE_MAINTAINER_MODE" >&5 printf '%s\n' "$USE_MAINTAINER_MODE" >&6; } if test $USE_MAINTAINER_MODE = yes; then MAINTAINER_MODE_TRUE= MAINTAINER_MODE_FALSE='#' else MAINTAINER_MODE_TRUE='#' MAINTAINER_MODE_FALSE= fi MAINT=$MAINTAINER_MODE_TRUE if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_PKG_CONFIG+y} then : printf %s "(cached) " >&6 else case e in #( e) case $PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_PKG_CONFIG="$as_dir$ac_word$ac_exec_ext" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac ;; esac fi PKG_CONFIG=$ac_cv_path_PKG_CONFIG if test -n "$PKG_CONFIG"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 printf '%s\n' "$PKG_CONFIG" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_path_PKG_CONFIG"; then ac_pt_PKG_CONFIG=$PKG_CONFIG # Extract the first word of "pkg-config", so it can be a program name with args. set dummy pkg-config; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_ac_pt_PKG_CONFIG+y} then : printf %s "(cached) " >&6 else case e in #( e) case $ac_pt_PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_ac_pt_PKG_CONFIG="$as_dir$ac_word$ac_exec_ext" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac ;; esac fi ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG if test -n "$ac_pt_PKG_CONFIG"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5 printf '%s\n' "$ac_pt_PKG_CONFIG" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_pt_PKG_CONFIG" = x; then PKG_CONFIG="" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac PKG_CONFIG=$ac_pt_PKG_CONFIG fi else PKG_CONFIG="$ac_cv_path_PKG_CONFIG" fi fi if test -n "$PKG_CONFIG"; then _pkg_min_version=0.9.0 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5 printf %s "checking pkg-config is at least version $_pkg_min_version... " >&6; } if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } PKG_CONFIG="" fi fi if test -z "$PKG_CONFIG"; then as_fn_error $? "pkg-config not found" "$LINENO" 5 fi with_cflags="" if (test "$USE_MAINTAINER_MODE" = "yes"); then with_cflags="$with_cflags -Wall -Werror -Wextra" with_cflags="$with_cflags -Wno-unused-parameter" with_cflags="$with_cflags -Wno-missing-field-initializers" with_cflags="$with_cflags -Wdeclaration-after-statement" with_cflags="$with_cflags -Wmissing-declarations" with_cflags="$with_cflags -Wredundant-decls" with_cflags="$with_cflags -Wcast-align" with_cflags="$with_cflags -Wswitch-enum" with_cflags="$with_cflags -Wformat -Wformat-security" with_cflags="$with_cflags -Wstringop-overflow" with_cflags="$with_cflags -DG_DISABLE_DEPRECATED" with_cflags="$with_cflags -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_36" with_cflags="$with_cflags -DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_36" fi WARNING_CFLAGS=$with_cflags ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu DEPDIR="${am__leading_dot}deps" ac_config_commands="$ac_config_commands depfiles" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} supports the include directive" >&5 printf %s "checking whether ${MAKE-make} supports the include directive... " >&6; } cat > confinc.mk << 'END' am__doit: @echo this is the am__doit target >confinc.out .PHONY: am__doit END am__include="#" am__quote= # BSD make does it like this. echo '.include "confinc.mk" # ignored' > confmf.BSD # Other make implementations (GNU, Solaris 10, AIX) do it like this. echo 'include confinc.mk # ignored' > confmf.GNU _am_result=no for s in GNU BSD; do { echo "$as_me:$LINENO: ${MAKE-make} -f confmf.$s && cat confinc.out" >&5 (${MAKE-make} -f confmf.$s && cat confinc.out) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } case $?:`cat confinc.out 2>/dev/null` in #( '0:this is the am__doit target') : case $s in #( BSD) : am__include='.include' am__quote='"' ;; #( *) : am__include='include' am__quote='' ;; esac ;; #( *) : ;; esac if test "$am__include" != "#"; then _am_result="yes ($s style)" break fi done rm -f confinc.* confmf.* { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: ${_am_result}" >&5 printf '%s\n' "${_am_result}" >&6; } # Check whether --enable-dependency-tracking was given. if test ${enable_dependency_tracking+y} then : enableval=$enable_dependency_tracking; fi if test "x$enable_dependency_tracking" != xno; then am_depcomp="$ac_aux_dir/depcomp" AMDEPBACKSLASH='\' am__nodep='_no' fi if test "x$enable_dependency_tracking" != xno; then AMDEP_TRUE= AMDEP_FALSE='#' else AMDEP_TRUE='#' AMDEP_FALSE= fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf '%s\n' "$CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf '%s\n' "$ac_ct_CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf '%s\n' "$CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then if test "$as_dir$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS if test $ac_prog_rejected = yes; then # We found a bogon in the path, so make sure we never use it. set dummy $ac_cv_prog_CC shift if test $# != 0; then # We chose a different compiler from the bogus one. # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" fi fi fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf '%s\n' "$CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then for ac_prog in cl.exe do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf '%s\n' "$CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi test -n "$CC" && break done fi if test -z "$CC"; then ac_ct_CC=$CC for ac_prog in cl.exe do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf '%s\n' "$ac_ct_CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi test -n "$ac_ct_CC" && break done if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}clang", so it can be a program name with args. set dummy ${ac_tool_prefix}clang; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}clang" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf '%s\n' "$CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "clang", so it can be a program name with args. set dummy clang; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="clang" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf '%s\n' "$ac_ct_CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi fi test -z "$CC" && { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH See 'config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion -version; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 printf %s "checking whether the C compiler works... " >&6; } ac_link_default=`printf '%s\n' "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" ac_rmfiles= for ac_file in $ac_files do case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; * ) ac_rmfiles="$ac_rmfiles $ac_file";; esac done rm -f $ac_rmfiles if { { ac_try="$ac_link_default" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # Autoconf-2.13 could set the ac_cv_exeext variable to 'no'. # So ignore a value of 'no', otherwise this would lead to 'EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. for ac_file in $ac_files '' do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; [ab].out ) # We found the default executable, but exeext='' is most # certainly right. break;; *.* ) if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not # safe: cross compilers may not add the suffix if given an '-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. break;; * ) break;; esac done test "$ac_cv_exeext" = no && ac_cv_exeext= else case e in #( e) ac_file='' ;; esac fi if test -z "$ac_file" then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } printf '%s\n' "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables See 'config.log' for more details" "$LINENO" 5; } else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 printf %s "checking for C compiler default output file name... " >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 printf '%s\n' "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 printf %s "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # If both 'conftest.exe' and 'conftest' are 'present' (well, observable) # catch 'conftest.exe'. For instance with Cygwin, 'ls conftest' will # work properly (i.e., refer to 'conftest.exe'), while it won't with # 'rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` break;; * ) break;; esac done else case e in #( e) { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link See 'config.log' for more details" "$LINENO" 5; } ;; esac fi rm -f conftest conftest$ac_cv_exeext { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 printf '%s\n' "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext ac_exeext=$EXEEXT cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { FILE *f = fopen ("conftest.out", "w"); if (!f) return 1; return ferror (f) || fclose (f) != 0; ; return 0; } _ACEOF ac_clean_files="$ac_clean_files conftest.out" # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 printf %s "checking whether we are cross compiling... " >&6; } if test "$cross_compiling" != yes; then { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if { ac_try='./conftest$ac_cv_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "cannot run C compiled programs. If you meant to cross compile, use '--host'. See 'config.log' for more details" "$LINENO" 5; } fi fi fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 printf '%s\n' "$cross_compiling" >&6; } rm -f conftest.$ac_ext conftest$ac_cv_exeext \ conftest.o conftest.obj conftest.out ac_clean_files=$ac_clean_files_save { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 printf %s "checking for suffix of object files... " >&6; } if test ${ac_cv_objext+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF rm -f conftest.o conftest.obj if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` break;; esac done else case e in #( e) printf '%s\n' "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile See 'config.log' for more details" "$LINENO" 5; } ;; esac fi rm -f conftest.$ac_cv_objext conftest.$ac_ext ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 printf '%s\n' "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5 printf %s "checking whether the compiler supports GNU C... " >&6; } if test ${ac_cv_c_compiler_gnu+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_compiler_gnu=yes else case e in #( e) ac_compiler_gnu=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 printf '%s\n' "$ac_cv_c_compiler_gnu" >&6; } ac_compiler_gnu=$ac_cv_c_compiler_gnu if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi ac_test_CFLAGS=${CFLAGS+y} ac_save_CFLAGS=$CFLAGS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 printf %s "checking whether $CC accepts -g... " >&6; } if test ${ac_cv_prog_cc_g+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes else case e in #( e) CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else case e in #( e) ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_c_werror_flag=$ac_save_c_werror_flag ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 printf '%s\n' "$ac_cv_prog_cc_g" >&6; } if test $ac_test_CFLAGS; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then CFLAGS="-g -O2" else CFLAGS="-g" fi else if test "$GCC" = yes; then CFLAGS="-O2" else CFLAGS= fi fi ac_prog_cc_stdc=no if test x$ac_prog_cc_stdc = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C23 features" >&5 printf %s "checking for $CC option to enable C23 features... " >&6; } if test ${ac_cv_prog_cc_c23+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c23=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c23_program _ACEOF for ac_arg in '' -std=gnu23 do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c23=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c23" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c23" = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf '%s\n' "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c23" = x then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf '%s\n' "none needed" >&6; } else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c23" >&5 printf '%s\n' "$ac_cv_prog_cc_c23" >&6; } CC="$CC $ac_cv_prog_cc_c23" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c23 ac_prog_cc_stdc=c23 ;; esac fi fi if test x$ac_prog_cc_stdc = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 printf %s "checking for $CC option to enable C11 features... " >&6; } if test ${ac_cv_prog_cc_c11+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c11=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c11_program _ACEOF for ac_arg in '' -std=gnu11 -std:c11 do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c11=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c11" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c11" = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf '%s\n' "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c11" = x then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf '%s\n' "none needed" >&6; } else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 printf '%s\n' "$ac_cv_prog_cc_c11" >&6; } CC="$CC $ac_cv_prog_cc_c11" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 ac_prog_cc_stdc=c11 ;; esac fi fi if test x$ac_prog_cc_stdc = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 printf %s "checking for $CC option to enable C99 features... " >&6; } if test ${ac_cv_prog_cc_c99+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c99=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c99_program _ACEOF for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99= do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c99=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c99" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c99" = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf '%s\n' "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c99" = x then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf '%s\n' "none needed" >&6; } else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 printf '%s\n' "$ac_cv_prog_cc_c99" >&6; } CC="$CC $ac_cv_prog_cc_c99" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 ac_prog_cc_stdc=c99 ;; esac fi fi if test x$ac_prog_cc_stdc = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 printf %s "checking for $CC option to enable C89 features... " >&6; } if test ${ac_cv_prog_cc_c89+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c89_program _ACEOF for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c89=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c89" = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf '%s\n' "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c89" = x then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf '%s\n' "none needed" >&6; } else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 printf '%s\n' "$ac_cv_prog_cc_c89" >&6; } CC="$CC $ac_cv_prog_cc_c89" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 ac_prog_cc_stdc=c89 ;; esac fi fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5 printf %s "checking whether $CC understands -c and -o together... " >&6; } if test ${am_cv_prog_cc_c_o+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF # Make sure it works both with $CC and with simple cc. # Following AC_PROG_CC_C_O, we do the test twice because some # compilers refuse to overwrite an existing .o file with -o, # though they will create one. am_cv_prog_cc_c_o=yes for am_i in 1 2; do if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5 ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } \ && test -f conftest2.$ac_objext; then : OK else am_cv_prog_cc_c_o=no break fi done # aligned with autoconf, so not including core; see bug#72225. rm -f -r a.out a.exe b.out conftest.$ac_ext conftest.$ac_objext \ conftest.dSYM conftest1.$ac_ext conftest1.$ac_objext conftest1.dSYM \ conftest2.$ac_ext conftest2.$ac_objext conftest2.dSYM unset am_i ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5 printf '%s\n' "$am_cv_prog_cc_c_o" >&6; } if test "$am_cv_prog_cc_c_o" != yes; then # Losing compiler, so override with the script. # FIXME: It is wrong to rewrite CC. # But if we don't then we get into trouble of one sort or another. # A longer-term fix would be to have automake use am__CC in this case, # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" CC="$am_aux_dir/compile $CC" fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu depcc="$CC" am_compiler_list= { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 printf %s "checking dependency style of $depcc... " >&6; } if test ${am_cv_CC_dependencies_compiler_type+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named 'D' -- because '-MD' means "put the output # in D". rm -rf conftest.dir mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_CC_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` fi am__universal=false case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with # Solaris 10 /bin/sh. echo '/* dummy */' > sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf # We check with '-c' and '-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle '-M -o', and we need to detect this. Also, some Intel # versions had trouble with output in subdirs. am__obj=sub/conftest.${OBJEXT-o} am__minus_obj="-o $am__obj" case $depmode in gcc) # This depmode causes a compiler race in universal mode. test "$am__universal" = false || continue ;; nosideeffect) # After this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested. if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; msvc7 | msvc7msys | msvisualcpp | msvcmsys) # This compiler won't grok '-c -o', but also, the minuso test has # not run yet. These depmodes are late enough in the game, and # so weak that their functioning should not be impacted. am__obj=conftest.${OBJEXT-o} am__minus_obj= ;; none) break ;; esac if depmode=$depmode \ source=sub/conftest.c object=$am__obj \ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ >/dev/null 2>conftest.err && grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && grep $am__obj sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thus: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_CC_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_CC_dependencies_compiler_type=none fi ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5 printf '%s\n' "$am_cv_CC_dependencies_compiler_type" >&6; } CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type if test "x$enable_dependency_tracking" != xno \ && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then am__fastdepCC_TRUE= am__fastdepCC_FALSE='#' else am__fastdepCC_TRUE='#' am__fastdepCC_FALSE= fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for C/C++ restrict keyword" >&5 printf %s "checking for C/C++ restrict keyword... " >&6; } if test ${ac_cv_c_restrict+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_c_restrict=no # Put '__restrict__' first, to avoid problems with glibc and non-GCC; see: # https://lists.gnu.org/archive/html/bug-autoconf/2016-02/msg00006.html # Put 'restrict' last, because C++ lacks it. for ac_kw in __restrict__ __restrict _Restrict restrict; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ typedef int *int_ptr; int foo (int_ptr $ac_kw ip) { return ip[0]; } int bar (int [$ac_kw]); /* Catch GCC bug 14050. */ int bar (int ip[$ac_kw]) { return ip[0]; } int main (void) { int s[1]; int *$ac_kw t = s; t[0] = 0; return foo (t) + bar (t); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_c_restrict=$ac_kw fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext test "$ac_cv_c_restrict" != no && break done ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_restrict" >&5 printf '%s\n' "$ac_cv_c_restrict" >&6; } case $ac_cv_c_restrict in restrict) ;; no) printf '%s\n' "#define restrict /**/" >>confdefs.h ;; *) printf '%s\n' "#define restrict $ac_cv_c_restrict" >>confdefs.h ;; esac ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf '%s\n' "$CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf '%s\n' "$ac_ct_CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf '%s\n' "$CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then if test "$as_dir$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS if test $ac_prog_rejected = yes; then # We found a bogon in the path, so make sure we never use it. set dummy $ac_cv_prog_CC shift if test $# != 0; then # We chose a different compiler from the bogus one. # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" fi fi fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf '%s\n' "$CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then for ac_prog in cl.exe do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf '%s\n' "$CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi test -n "$CC" && break done fi if test -z "$CC"; then ac_ct_CC=$CC for ac_prog in cl.exe do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf '%s\n' "$ac_ct_CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi test -n "$ac_ct_CC" && break done if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}clang", so it can be a program name with args. set dummy ${ac_tool_prefix}clang; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}clang" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf '%s\n' "$CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "clang", so it can be a program name with args. set dummy clang; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="clang" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf '%s\n' "$ac_ct_CC" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi fi test -z "$CC" && { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH See 'config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion -version; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf '%s\n' "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5 printf %s "checking whether the compiler supports GNU C... " >&6; } if test ${ac_cv_c_compiler_gnu+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_compiler_gnu=yes else case e in #( e) ac_compiler_gnu=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 printf '%s\n' "$ac_cv_c_compiler_gnu" >&6; } ac_compiler_gnu=$ac_cv_c_compiler_gnu if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi ac_test_CFLAGS=${CFLAGS+y} ac_save_CFLAGS=$CFLAGS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 printf %s "checking whether $CC accepts -g... " >&6; } if test ${ac_cv_prog_cc_g+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes else case e in #( e) CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else case e in #( e) ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_c_werror_flag=$ac_save_c_werror_flag ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 printf '%s\n' "$ac_cv_prog_cc_g" >&6; } if test $ac_test_CFLAGS; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then CFLAGS="-g -O2" else CFLAGS="-g" fi else if test "$GCC" = yes; then CFLAGS="-O2" else CFLAGS= fi fi ac_prog_cc_stdc=no if test x$ac_prog_cc_stdc = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C23 features" >&5 printf %s "checking for $CC option to enable C23 features... " >&6; } if test ${ac_cv_prog_cc_c23+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c23=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c23_program _ACEOF for ac_arg in '' -std=gnu23 do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c23=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c23" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c23" = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf '%s\n' "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c23" = x then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf '%s\n' "none needed" >&6; } else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c23" >&5 printf '%s\n' "$ac_cv_prog_cc_c23" >&6; } CC="$CC $ac_cv_prog_cc_c23" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c23 ac_prog_cc_stdc=c23 ;; esac fi fi if test x$ac_prog_cc_stdc = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 printf %s "checking for $CC option to enable C11 features... " >&6; } if test ${ac_cv_prog_cc_c11+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c11=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c11_program _ACEOF for ac_arg in '' -std=gnu11 -std:c11 do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c11=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c11" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c11" = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf '%s\n' "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c11" = x then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf '%s\n' "none needed" >&6; } else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 printf '%s\n' "$ac_cv_prog_cc_c11" >&6; } CC="$CC $ac_cv_prog_cc_c11" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 ac_prog_cc_stdc=c11 ;; esac fi fi if test x$ac_prog_cc_stdc = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 printf %s "checking for $CC option to enable C99 features... " >&6; } if test ${ac_cv_prog_cc_c99+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c99=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c99_program _ACEOF for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99= do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c99=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c99" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c99" = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf '%s\n' "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c99" = x then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf '%s\n' "none needed" >&6; } else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 printf '%s\n' "$ac_cv_prog_cc_c99" >&6; } CC="$CC $ac_cv_prog_cc_c99" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 ac_prog_cc_stdc=c99 ;; esac fi fi if test x$ac_prog_cc_stdc = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 printf %s "checking for $CC option to enable C89 features... " >&6; } if test ${ac_cv_prog_cc_c89+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c89_program _ACEOF for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c89=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c89" = xno then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf '%s\n' "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c89" = x then : { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf '%s\n' "none needed" >&6; } else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 printf '%s\n' "$ac_cv_prog_cc_c89" >&6; } CC="$CC $ac_cv_prog_cc_c89" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 ac_prog_cc_stdc=c89 ;; esac fi fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5 printf %s "checking whether $CC understands -c and -o together... " >&6; } if test ${am_cv_prog_cc_c_o+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF # Make sure it works both with $CC and with simple cc. # Following AC_PROG_CC_C_O, we do the test twice because some # compilers refuse to overwrite an existing .o file with -o, # though they will create one. am_cv_prog_cc_c_o=yes for am_i in 1 2; do if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5 ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } \ && test -f conftest2.$ac_objext; then : OK else am_cv_prog_cc_c_o=no break fi done # aligned with autoconf, so not including core; see bug#72225. rm -f -r a.out a.exe b.out conftest.$ac_ext conftest.$ac_objext \ conftest.dSYM conftest1.$ac_ext conftest1.$ac_objext conftest1.dSYM \ conftest2.$ac_ext conftest2.$ac_objext conftest2.dSYM unset am_i ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5 printf '%s\n' "$am_cv_prog_cc_c_o" >&6; } if test "$am_cv_prog_cc_c_o" != yes; then # Losing compiler, so override with the script. # FIXME: It is wrong to rewrite CC. # But if we don't then we get into trouble of one sort or another. # A longer-term fix would be to have automake use am__CC in this case, # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" CC="$am_aux_dir/compile $CC" fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu depcc="$CC" am_compiler_list= { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 printf %s "checking dependency style of $depcc... " >&6; } if test ${am_cv_CC_dependencies_compiler_type+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named 'D' -- because '-MD' means "put the output # in D". rm -rf conftest.dir mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_CC_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` fi am__universal=false case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with # Solaris 10 /bin/sh. echo '/* dummy */' > sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf # We check with '-c' and '-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle '-M -o', and we need to detect this. Also, some Intel # versions had trouble with output in subdirs. am__obj=sub/conftest.${OBJEXT-o} am__minus_obj="-o $am__obj" case $depmode in gcc) # This depmode causes a compiler race in universal mode. test "$am__universal" = false || continue ;; nosideeffect) # After this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested. if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; msvc7 | msvc7msys | msvisualcpp | msvcmsys) # This compiler won't grok '-c -o', but also, the minuso test has # not run yet. These depmodes are late enough in the game, and # so weak that their functioning should not be impacted. am__obj=conftest.${OBJEXT-o} am__minus_obj= ;; none) break ;; esac if depmode=$depmode \ source=sub/conftest.c object=$am__obj \ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ >/dev/null 2>conftest.err && grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && grep $am__obj sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thus: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_CC_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_CC_dependencies_compiler_type=none fi ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5 printf '%s\n' "$am_cv_CC_dependencies_compiler_type" >&6; } CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type if test "x$enable_dependency_tracking" != xno \ && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then am__fastdepCC_TRUE= am__fastdepCC_FALSE='#' else am__fastdepCC_TRUE='#' am__fastdepCC_FALSE= fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether ${CC-cc} accepts -fPIE" >&5 printf %s "checking whether ${CC-cc} accepts -fPIE... " >&6; } if test ${ac_cv_prog_cc_pie+y} then : printf %s "(cached) " >&6 else case e in #( e) echo 'void f(){}' > conftest.c if test -z "`${CC-cc} -fPIE -pie -c conftest.c 2>&1`"; then ac_cv_prog_cc_pie=yes else ac_cv_prog_cc_pie=no fi rm -rf conftest* ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_pie" >&5 printf '%s\n' "$ac_cv_prog_cc_pie" >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether ${CC-cc} accepts -fsanitize=address" >&5 printf %s "checking whether ${CC-cc} accepts -fsanitize=address... " >&6; } if test ${ac_cv_prog_cc_asan+y} then : printf %s "(cached) " >&6 else case e in #( e) echo 'void f(){}' > asan.c if test -z "`${CC-cc} -fsanitize=address -c asan.c 2>&1`"; then ac_cv_prog_cc_asan=yes else ac_cv_prog_cc_asan=no fi rm -rf asan* ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_asan" >&5 printf '%s\n' "$ac_cv_prog_cc_asan" >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether ${CC-cc} accepts -fsanitize=leak" >&5 printf %s "checking whether ${CC-cc} accepts -fsanitize=leak... " >&6; } if test ${ac_cv_prog_cc_lsan+y} then : printf %s "(cached) " >&6 else case e in #( e) echo 'void f(){}' > lsan.c if test -z "`${CC-cc} -fsanitize=leak -c lsan.c 2>&1`"; then ac_cv_prog_cc_lsan=yes else ac_cv_prog_cc_lsan=no fi rm -rf lsan* ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_lsan" >&5 printf '%s\n' "$ac_cv_prog_cc_lsan" >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether ${CC-cc} accepts -fsanitize=undefined" >&5 printf %s "checking whether ${CC-cc} accepts -fsanitize=undefined... " >&6; } if test ${ac_cv_prog_cc_ubsan+y} then : printf %s "(cached) " >&6 else case e in #( e) echo 'void f(){}' > ubsan.c if test -z "`${CC-cc} -fsanitize=undefined -c ubsan.c 2>&1`"; then ac_cv_prog_cc_ubsan=yes else ac_cv_prog_cc_ubsan=no fi rm -rf ubsan* ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_ubsan" >&5 printf '%s\n' "$ac_cv_prog_cc_ubsan" >&6; } case `pwd` in *\ * | *\ *) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&5 printf '%s\n' "$as_me: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&2;} ;; esac macro_version='2.5.4' macro_revision='2.5.4' ltmain=$ac_aux_dir/ltmain.sh # Make sure we can run config.sub. $SHELL "${ac_aux_dir}config.sub" sun4 >/dev/null 2>&1 || as_fn_error $? "cannot run $SHELL ${ac_aux_dir}config.sub" "$LINENO" 5 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 printf %s "checking build system type... " >&6; } if test ${ac_cv_build+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_build_alias=$build_alias test "x$ac_build_alias" = x && ac_build_alias=`$SHELL "${ac_aux_dir}config.guess"` test "x$ac_build_alias" = x && as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 ac_cv_build=`$SHELL "${ac_aux_dir}config.sub" $ac_build_alias` || as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $ac_build_alias failed" "$LINENO" 5 ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 printf '%s\n' "$ac_cv_build" >&6; } case $ac_cv_build in *-*-*) ;; *) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; esac build=$ac_cv_build ac_save_IFS=$IFS; IFS='-' set x $ac_cv_build shift build_cpu=$1 build_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: build_os=$* IFS=$ac_save_IFS case $build_os in *\ *) build_os=`printf '%s\n' "$build_os" | sed 's/ /-/g'`;; esac { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 printf %s "checking host system type... " >&6; } if test ${ac_cv_host+y} then : printf %s "(cached) " >&6 else case e in #( e) if test "x$host_alias" = x; then ac_cv_host=$ac_cv_build else ac_cv_host=`$SHELL "${ac_aux_dir}config.sub" $host_alias` || as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $host_alias failed" "$LINENO" 5 fi ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 printf '%s\n' "$ac_cv_host" >&6; } case $ac_cv_host in *-*-*) ;; *) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; esac host=$ac_cv_host ac_save_IFS=$IFS; IFS='-' set x $ac_cv_host shift host_cpu=$1 host_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: host_os=$* IFS=$ac_save_IFS case $host_os in *\ *) host_os=`printf '%s\n' "$host_os" | sed 's/ /-/g'`;; esac # Backslashify metacharacters that are still active within # double-quoted strings. sed_quote_subst='s/\(["`$\\]\)/\\\1/g' # Same as above, but do not quote variable references. double_quote_subst='s/\(["`\\]\)/\\\1/g' # Sed substitution to delay expansion of an escaped shell variable in a # double_quote_subst'ed string. delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' # Sed substitution to delay expansion of an escaped single quote. delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' # Sed substitution to avoid accidental globbing in evaled expressions no_glob_subst='s/\*/\\\*/g' ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking how to print strings" >&5 printf %s "checking how to print strings... " >&6; } # Test print first, because it will be a builtin if present. if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then ECHO='print -r --' elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then ECHO='printf %s\n' else # Use this function as a fallback that always works. func_fallback_echo () { eval 'cat <<_LTECHO_EOF $1 _LTECHO_EOF' } ECHO='func_fallback_echo' fi # func_echo_all arg... # Invoke $ECHO with all args, space-separated. func_echo_all () { $ECHO "" } case $ECHO in printf*) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: printf" >&5 printf '%s\n' "printf" >&6; } ;; print*) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: print -r" >&5 printf '%s\n' "print -r" >&6; } ;; *) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: cat" >&5 printf '%s\n' "cat" >&6; } ;; esac { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5 printf %s "checking for a sed that does not truncate output... " >&6; } if test ${ac_cv_path_SED+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ for ac_i in 1 2 3 4 5 6 7; do ac_script="$ac_script$as_nl$ac_script" done echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed { ac_script=; unset ac_script;} if test -z "$SED"; then ac_path_SED_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in sed gsed do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_SED="$as_dir$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_SED" || continue # Check for GNU ac_path_SED and select it if it is found. # Check for GNU $ac_path_SED case `"$ac_path_SED" --version 2>&1` in #( *GNU*) ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;; #( *) ac_count=0 printf %s 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" printf '%s\n' '' >> "conftest.nl" "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_SED_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_SED="$ac_path_SED" ac_path_SED_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_SED_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_SED"; then as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5 fi else ac_cv_path_SED=$SED fi ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5 printf '%s\n' "$ac_cv_path_SED" >&6; } SED="$ac_cv_path_SED" rm -f conftest.sed test -z "$SED" && SED=sed Xsed="$SED -e 1s/^X//" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 printf %s "checking for grep that handles long lines and -e... " >&6; } if test ${ac_cv_path_GREP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -z "$GREP"; then ac_path_GREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in grep ggrep do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_GREP="$as_dir$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_GREP" || continue # Check for GNU ac_path_GREP and select it if it is found. # Check for GNU $ac_path_GREP case `"$ac_path_GREP" --version 2>&1` in #( *GNU*) ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; #( *) ac_count=0 printf %s 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" printf '%s\n' 'GREP' >> "conftest.nl" "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_GREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_GREP_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_GREP"; then as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_GREP=$GREP fi ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 printf '%s\n' "$ac_cv_path_GREP" >&6; } GREP="$ac_cv_path_GREP" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 printf %s "checking for egrep... " >&6; } if test ${ac_cv_path_EGREP+y} then : printf %s "(cached) " >&6 else case e in #( e) if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 then ac_cv_path_EGREP="$GREP -E" else if test -z "$EGREP"; then ac_path_EGREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in egrep do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_EGREP="$as_dir$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_EGREP" || continue # Check for GNU ac_path_EGREP and select it if it is found. # Check for GNU $ac_path_EGREP case `"$ac_path_EGREP" --version 2>&1` in #( *GNU*) ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; #( *) ac_count=0 printf %s 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" printf '%s\n' 'EGREP' >> "conftest.nl" "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_EGREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_EGREP_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_EGREP"; then as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_EGREP=$EGREP fi fi ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 printf '%s\n' "$ac_cv_path_EGREP" >&6; } EGREP="$ac_cv_path_EGREP" EGREP_TRADITIONAL=$EGREP ac_cv_path_EGREP_TRADITIONAL=$EGREP { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for fgrep" >&5 printf %s "checking for fgrep... " >&6; } if test ${ac_cv_path_FGREP+y} then : printf %s "(cached) " >&6 else case e in #( e) if echo 'ab*c' | $GREP -F 'ab*c' >/dev/null 2>&1 then ac_cv_path_FGREP="$GREP -F" else if test -z "$FGREP"; then ac_path_FGREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in fgrep do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_FGREP="$as_dir$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_FGREP" || continue # Check for GNU ac_path_FGREP and select it if it is found. # Check for GNU $ac_path_FGREP case `"$ac_path_FGREP" --version 2>&1` in #( *GNU*) ac_cv_path_FGREP="$ac_path_FGREP" ac_path_FGREP_found=:;; #( *) ac_count=0 printf %s 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" printf '%s\n' 'FGREP' >> "conftest.nl" "$ac_path_FGREP" FGREP < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_FGREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_FGREP="$ac_path_FGREP" ac_path_FGREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_FGREP_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_FGREP"; then as_fn_error $? "no acceptable fgrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_FGREP=$FGREP fi fi ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_FGREP" >&5 printf '%s\n' "$ac_cv_path_FGREP" >&6; } FGREP="$ac_cv_path_FGREP" test -z "$GREP" && GREP=grep # Check whether --with-gnu-ld was given. if test ${with_gnu_ld+y} then : withval=$with_gnu_ld; test no = "$withval" || with_gnu_ld=yes else case e in #( e) with_gnu_ld=no ;; esac fi ac_prog=ld if test yes = "$GCC"; then # Check if gcc -print-prog-name=ld gives a path. { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5 printf %s "checking for ld used by $CC... " >&6; } case $host in *-*-mingw* | *-*-windows*) # gcc leaves a trailing carriage return, which upsets mingw ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; *) ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; esac case $ac_prog in # Accept absolute paths. [\\/]* | ?:[\\/]*) re_direlt='/[^/][^/]*/\.\./' # Canonicalize the pathname of ld ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` done test -z "$LD" && LD=$ac_prog ;; "") # If it fails, then pretend we aren't using GCC. ac_prog=ld ;; *) # If it is relative, then search for the first ld in PATH. with_gnu_ld=unknown ;; esac elif test yes = "$with_gnu_ld"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5 printf %s "checking for GNU ld... " >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5 printf %s "checking for non-GNU ld... " >&6; } fi if test ${lt_cv_path_LD+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -z "$LD"; then lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR for ac_dir in $PATH; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then lt_cv_path_LD=$ac_dir/$ac_prog # Check to see if the program is GNU ld. I'd rather use --version, # but apparently some variants of GNU ld only accept -v. # Break only if it was the GNU/non-GNU ld that we prefer. case `"$lt_cv_path_LD" -v 2>&1 &5 printf '%s\n' "$LD" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5 printf %s "checking if the linker ($LD) is GNU ld... " >&6; } if test ${lt_cv_prog_gnu_ld+y} then : printf %s "(cached) " >&6 else case e in #( e) # I'd rather use --version here, but apparently some GNU lds only accept -v. case `$LD -v 2>&1 &5 printf '%s\n' "$lt_cv_prog_gnu_ld" >&6; } with_gnu_ld=$lt_cv_prog_gnu_ld { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for BSD- or MS-compatible name lister (nm)" >&5 printf %s "checking for BSD- or MS-compatible name lister (nm)... " >&6; } if test ${lt_cv_path_NM+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$NM"; then # Let the user override the test. lt_cv_path_NM=$NM else lt_nm_to_check=${ac_tool_prefix}nm if test -n "$ac_tool_prefix" && test "$build" = "$host"; then lt_nm_to_check="$lt_nm_to_check nm" fi for lt_tmp_nm in $lt_nm_to_check; do lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. tmp_nm=$ac_dir/$lt_tmp_nm if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then # Check to see if the nm accepts a BSD-compat flag. # Adding the 'sed 1q' prevents false positives on HP-UX, which says: # nm: unknown option "B" ignored # Tru64's nm complains that /dev/null is an invalid object file # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty case $build_os in mingw* | windows*) lt_bad_file=conftest.nm/nofile ;; *) lt_bad_file=/dev/null ;; esac case `"$tmp_nm" -B $lt_bad_file 2>&1 | $SED '1q'` in *$lt_bad_file* | *'Invalid file or object type'*) lt_cv_path_NM="$tmp_nm -B" break 2 ;; *) case `"$tmp_nm" -p /dev/null 2>&1 | $SED '1q'` in */dev/null*) lt_cv_path_NM="$tmp_nm -p" break 2 ;; *) lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but continue # so that we can try to find one that supports BSD flags ;; esac ;; esac fi done IFS=$lt_save_ifs done : ${lt_cv_path_NM=no} fi ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_NM" >&5 printf '%s\n' "$lt_cv_path_NM" >&6; } if test no != "$lt_cv_path_NM"; then NM=$lt_cv_path_NM else # Didn't find any BSD compatible name lister, look for dumpbin. if test -n "$DUMPBIN"; then : # Let the user override the test. else if test -n "$ac_tool_prefix"; then for ac_prog in dumpbin "link -dump" do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_DUMPBIN+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$DUMPBIN"; then ac_cv_prog_DUMPBIN="$DUMPBIN" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_DUMPBIN="$ac_tool_prefix$ac_prog" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi DUMPBIN=$ac_cv_prog_DUMPBIN if test -n "$DUMPBIN"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $DUMPBIN" >&5 printf '%s\n' "$DUMPBIN" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi test -n "$DUMPBIN" && break done fi if test -z "$DUMPBIN"; then ac_ct_DUMPBIN=$DUMPBIN for ac_prog in dumpbin "link -dump" do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_DUMPBIN+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_DUMPBIN"; then ac_cv_prog_ac_ct_DUMPBIN="$ac_ct_DUMPBIN" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_DUMPBIN="$ac_prog" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_DUMPBIN=$ac_cv_prog_ac_ct_DUMPBIN if test -n "$ac_ct_DUMPBIN"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DUMPBIN" >&5 printf '%s\n' "$ac_ct_DUMPBIN" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi test -n "$ac_ct_DUMPBIN" && break done if test "x$ac_ct_DUMPBIN" = x; then DUMPBIN=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac DUMPBIN=$ac_ct_DUMPBIN fi fi case `$DUMPBIN -symbols -headers /dev/null 2>&1 | $SED '1q'` in *COFF*) DUMPBIN="$DUMPBIN -symbols -headers" ;; *) DUMPBIN=: ;; esac fi if test : != "$DUMPBIN"; then NM=$DUMPBIN fi fi test -z "$NM" && NM=nm { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking the name lister ($NM) interface" >&5 printf %s "checking the name lister ($NM) interface... " >&6; } if test ${lt_cv_nm_interface+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_nm_interface="BSD nm" echo "int some_variable = 0;" > conftest.$ac_ext (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&5) (eval "$ac_compile" 2>conftest.err) cat conftest.err >&5 (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&5) (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) cat conftest.err >&5 (eval echo "\"\$as_me:$LINENO: output\"" >&5) cat conftest.out >&5 if $GREP 'External.*some_variable' conftest.out > /dev/null; then lt_cv_nm_interface="MS dumpbin" fi rm -f conftest* ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_nm_interface" >&5 printf '%s\n' "$lt_cv_nm_interface" >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5 printf %s "checking whether ln -s works... " >&6; } LN_S=$as_ln_s if test "$LN_S" = "ln -s"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5 printf '%s\n' "no, using $LN_S" >&6; } fi # find the maximum length of command line arguments { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking the maximum length of command line arguments" >&5 printf %s "checking the maximum length of command line arguments... " >&6; } if test ${lt_cv_sys_max_cmd_len+y} then : printf %s "(cached) " >&6 else case e in #( e) i=0 teststring=ABCD case $build_os in msdosdjgpp*) # On DJGPP, this test can blow up pretty badly due to problems in libc # (any single argument exceeding 2000 bytes causes a buffer overrun # during glob expansion). Even if it were fixed, the result of this # check would be larger than it should be. lt_cv_sys_max_cmd_len=12288; # 12K is about right ;; gnu* | ironclad*) # Under GNU Hurd and Ironclad, this test is not required because there # is no limit to the length of command line arguments. # Libtool will interpret -1 as no limit whatsoever lt_cv_sys_max_cmd_len=-1; ;; cygwin* | mingw* | windows* | cegcc*) # On Win9x/ME, this test blows up -- it succeeds, but takes # about 5 minutes as the teststring grows exponentially. # Worse, since 9x/ME are not pre-emptively multitasking, # you end up with a "frozen" computer, even though with patience # the test eventually succeeds (with a max line length of 256k). # Instead, let's just punt: use the minimum linelength reported by # all of the supported platforms: 8192 (on NT/2K/XP). lt_cv_sys_max_cmd_len=8192; ;; mint*) # On MiNT this can take a long time and run out of memory. lt_cv_sys_max_cmd_len=8192; ;; amigaos*) # On AmigaOS with pdksh, this test takes hours, literally. # So we just punt and use a minimum line length of 8192. lt_cv_sys_max_cmd_len=8192; ;; darwin* | dragonfly* | freebsd* | midnightbsd* | netbsd* | openbsd*) # This has been around since 386BSD, at least. Likely further. if test -x /sbin/sysctl; then lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` elif test -x /usr/sbin/sysctl; then lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` else lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs fi # And add a safety zone lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` ;; interix*) # We know the value 262144 and hardcode it with a safety zone (like BSD) lt_cv_sys_max_cmd_len=196608 ;; os2*) # The test takes a long time on OS/2. lt_cv_sys_max_cmd_len=8192 ;; osf*) # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not # nice to cause kernel panics so lets avoid the loop below. # First set a reasonable default. lt_cv_sys_max_cmd_len=16384 # if test -x /sbin/sysconfig; then case `/sbin/sysconfig -q proc exec_disable_arg_limit` in *1*) lt_cv_sys_max_cmd_len=-1 ;; esac fi ;; sco3.2v5*) lt_cv_sys_max_cmd_len=102400 ;; sysv5* | sco5v6* | sysv4.2uw2*) kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` if test -n "$kargmax"; then lt_cv_sys_max_cmd_len=`echo $kargmax | $SED 's/.*[ ]//'` else lt_cv_sys_max_cmd_len=32768 fi ;; *) lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` if test -n "$lt_cv_sys_max_cmd_len" && \ test undefined != "$lt_cv_sys_max_cmd_len"; then lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` else # Make teststring a little bigger before we do anything with it. # a 1K string should be a reasonable start. for i in 1 2 3 4 5 6 7 8; do teststring=$teststring$teststring done SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} # If test is not a shell built-in, we'll probably end up computing a # maximum length that is only half of the actual maximum length, but # we can't tell. while { test X`env echo "$teststring$teststring" 2>/dev/null` \ = "X$teststring$teststring"; } >/dev/null 2>&1 && test 17 != "$i" # 1/2 MB should be enough do i=`expr $i + 1` teststring=$teststring$teststring done # Only check the string length outside the loop. lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` teststring= # Add a significant safety factor because C++ compilers can tack on # massive amounts of additional arguments before passing them to the # linker. It appears as though 1/2 is a usable value. lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` fi ;; esac ;; esac fi if test -n "$lt_cv_sys_max_cmd_len"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sys_max_cmd_len" >&5 printf '%s\n' "$lt_cv_sys_max_cmd_len" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none" >&5 printf '%s\n' "none" >&6; } fi max_cmd_len=$lt_cv_sys_max_cmd_len : ${CP="cp -f"} : ${MV="mv -f"} : ${RM="rm -f"} if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then lt_unset=unset else lt_unset=false fi # test EBCDIC or ASCII case `echo X|tr X '\101'` in A) # ASCII based system # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr lt_SP2NL='tr \040 \012' lt_NL2SP='tr \015\012 \040\040' ;; *) # EBCDIC based system lt_SP2NL='tr \100 \n' lt_NL2SP='tr \r\n \100\100' ;; esac { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to $host format" >&5 printf %s "checking how to convert $build file names to $host format... " >&6; } if test ${lt_cv_to_host_file_cmd+y} then : printf %s "(cached) " >&6 else case e in #( e) case $host in *-*-mingw* ) case $build in *-*-mingw* | *-*-windows* ) # actually msys lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 ;; *-*-cygwin* ) lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 ;; * ) # otherwise, assume *nix lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 ;; esac ;; *-*-cygwin* ) case $build in *-*-mingw* | *-*-windows* ) # actually msys lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin ;; *-*-cygwin* ) lt_cv_to_host_file_cmd=func_convert_file_noop ;; * ) # otherwise, assume *nix lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin ;; esac ;; * ) # unhandled hosts (and "normal" native builds) lt_cv_to_host_file_cmd=func_convert_file_noop ;; esac ;; esac fi to_host_file_cmd=$lt_cv_to_host_file_cmd { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_host_file_cmd" >&5 printf '%s\n' "$lt_cv_to_host_file_cmd" >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to toolchain format" >&5 printf %s "checking how to convert $build file names to toolchain format... " >&6; } if test ${lt_cv_to_tool_file_cmd+y} then : printf %s "(cached) " >&6 else case e in #( e) #assume ordinary cross tools, or native build. lt_cv_to_tool_file_cmd=func_convert_file_noop case $host in *-*-mingw* | *-*-windows* ) case $build in *-*-mingw* | *-*-windows* ) # actually msys lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 ;; esac ;; esac ;; esac fi to_tool_file_cmd=$lt_cv_to_tool_file_cmd { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_tool_file_cmd" >&5 printf '%s\n' "$lt_cv_to_tool_file_cmd" >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $LD option to reload object files" >&5 printf %s "checking for $LD option to reload object files... " >&6; } if test ${lt_cv_ld_reload_flag+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_ld_reload_flag='-r' ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_reload_flag" >&5 printf '%s\n' "$lt_cv_ld_reload_flag" >&6; } reload_flag=$lt_cv_ld_reload_flag case $reload_flag in "" | " "*) ;; *) reload_flag=" $reload_flag" ;; esac reload_cmds='$LD$reload_flag -o $output$reload_objs' case $host_os in cygwin* | mingw* | windows* | pw32* | cegcc*) if test yes != "$GCC"; then reload_cmds=false fi ;; darwin*) if test yes = "$GCC"; then reload_cmds='$LTCC $LTCFLAGS -nostdlib $wl-r -o $output$reload_objs' else reload_cmds='$LD$reload_flag -o $output$reload_objs' fi ;; esac # Extract the first word of "file", so it can be a program name with args. set dummy file; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_FILECMD+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$FILECMD"; then ac_cv_prog_FILECMD="$FILECMD" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_FILECMD="file" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_FILECMD" && ac_cv_prog_FILECMD=":" fi ;; esac fi FILECMD=$ac_cv_prog_FILECMD if test -n "$FILECMD"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $FILECMD" >&5 printf '%s\n' "$FILECMD" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}objdump", so it can be a program name with args. set dummy ${ac_tool_prefix}objdump; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_OBJDUMP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$OBJDUMP"; then ac_cv_prog_OBJDUMP="$OBJDUMP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_OBJDUMP="${ac_tool_prefix}objdump" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi OBJDUMP=$ac_cv_prog_OBJDUMP if test -n "$OBJDUMP"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $OBJDUMP" >&5 printf '%s\n' "$OBJDUMP" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_OBJDUMP"; then ac_ct_OBJDUMP=$OBJDUMP # Extract the first word of "objdump", so it can be a program name with args. set dummy objdump; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_OBJDUMP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_OBJDUMP"; then ac_cv_prog_ac_ct_OBJDUMP="$ac_ct_OBJDUMP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_OBJDUMP="objdump" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_OBJDUMP=$ac_cv_prog_ac_ct_OBJDUMP if test -n "$ac_ct_OBJDUMP"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OBJDUMP" >&5 printf '%s\n' "$ac_ct_OBJDUMP" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_OBJDUMP" = x; then OBJDUMP="false" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac OBJDUMP=$ac_ct_OBJDUMP fi else OBJDUMP="$ac_cv_prog_OBJDUMP" fi test -z "$OBJDUMP" && OBJDUMP=objdump { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking how to recognize dependent libraries" >&5 printf %s "checking how to recognize dependent libraries... " >&6; } if test ${lt_cv_deplibs_check_method+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_file_magic_cmd='$MAGIC_CMD' lt_cv_file_magic_test_file= lt_cv_deplibs_check_method='unknown' # Need to set the preceding variable on all platforms that support # interlibrary dependencies. # 'none' -- dependencies not supported. # 'unknown' -- same as none, but documents that we really don't know. # 'pass_all' -- all dependencies passed with no checks. # 'file_magic [[regex]]' -- check by looking for files in library path # that responds to the $file_magic_cmd with a given extended regex. # If you have 'file' or equivalent on your system and you're not sure # whether 'pass_all' will *always* work, you probably want this one. case $host_os in aix[4-9]*) lt_cv_deplibs_check_method=pass_all ;; beos*) lt_cv_deplibs_check_method=pass_all ;; bsdi[45]*) lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib)' lt_cv_file_magic_cmd='$FILECMD -L' lt_cv_file_magic_test_file=/shlib/libc.so ;; cygwin*) # func_win32_libid is a shell function defined in ltmain.sh lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' lt_cv_file_magic_cmd='func_win32_libid' ;; mingw* | windows* | pw32*) # Base MSYS/MinGW do not provide the 'file' command needed by # func_win32_libid shell function, so use a weaker test based on 'objdump', # unless we find 'file', for example because we are cross-compiling. if ( file / ) >/dev/null 2>&1; then lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' lt_cv_file_magic_cmd='func_win32_libid' else # Keep this pattern in sync with the one in func_win32_libid. lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64|pe-aarch64)' lt_cv_file_magic_cmd='$OBJDUMP -f' fi ;; cegcc*) # use the weaker test based on 'objdump'. See mingw*. lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' lt_cv_file_magic_cmd='$OBJDUMP -f' ;; darwin* | rhapsody*) lt_cv_deplibs_check_method=pass_all ;; freebsd* | dragonfly* | midnightbsd*) if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then case $host_cpu in i*86 ) # Not sure whether the presence of OpenBSD here was a mistake. # Let's accept both of them until this is cleared up. lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[3-9]86 (compact )?demand paged shared library' lt_cv_file_magic_cmd=$FILECMD lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` ;; esac else lt_cv_deplibs_check_method=pass_all fi ;; haiku*) lt_cv_deplibs_check_method=pass_all ;; hpux10.20* | hpux11*) lt_cv_file_magic_cmd=$FILECMD case $host_cpu in ia64*) lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - IA64' lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so ;; hppa*64*) lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]' lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl ;; *) lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|PA-RISC[0-9]\.[0-9]) shared library' lt_cv_file_magic_test_file=/usr/lib/libc.sl ;; esac ;; interix[3-9]*) # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|\.a)$' ;; irix5* | irix6* | nonstopux*) case $LD in *-32|*"-32 ") libmagic=32-bit;; *-n32|*"-n32 ") libmagic=N32;; *-64|*"-64 ") libmagic=64-bit;; *) libmagic=never-match;; esac lt_cv_deplibs_check_method=pass_all ;; # This must be glibc/ELF. linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) lt_cv_deplibs_check_method=pass_all ;; *-mlibc) lt_cv_deplibs_check_method=pass_all ;; netbsd* | netbsdelf*-gnu) if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' else lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|_pic\.a)$' fi ;; newos6*) lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (executable|dynamic lib)' lt_cv_file_magic_cmd=$FILECMD lt_cv_file_magic_test_file=/usr/lib/libnls.so ;; *nto* | *qnx*) lt_cv_deplibs_check_method=pass_all ;; openbsd*) if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|\.so|_pic\.a)$' else lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' fi ;; osf3* | osf4* | osf5*) lt_cv_deplibs_check_method=pass_all ;; rdos*) lt_cv_deplibs_check_method=pass_all ;; serenity*) lt_cv_deplibs_check_method=pass_all ;; solaris*) lt_cv_deplibs_check_method=pass_all ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) lt_cv_deplibs_check_method=pass_all ;; sysv4 | sysv4.3*) case $host_vendor in motorola) lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib) M[0-9][0-9]* Version [0-9]' lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` ;; ncr) lt_cv_deplibs_check_method=pass_all ;; sequent) lt_cv_file_magic_cmd='/bin/file' lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )' ;; sni) lt_cv_file_magic_cmd='/bin/file' lt_cv_deplibs_check_method="file_magic ELF [0-9][0-9]*-bit [LM]SB dynamic lib" lt_cv_file_magic_test_file=/lib/libc.so ;; siemens) lt_cv_deplibs_check_method=pass_all ;; pc) lt_cv_deplibs_check_method=pass_all ;; esac ;; tpf*) lt_cv_deplibs_check_method=pass_all ;; os2*) lt_cv_deplibs_check_method=pass_all ;; esac ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_deplibs_check_method" >&5 printf '%s\n' "$lt_cv_deplibs_check_method" >&6; } file_magic_glob= want_nocaseglob=no if test "$build" = "$host"; then case $host_os in mingw* | windows* | pw32*) if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then want_nocaseglob=yes else file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[\1]\/[\1]\/g;/g"` fi ;; esac fi file_magic_cmd=$lt_cv_file_magic_cmd deplibs_check_method=$lt_cv_deplibs_check_method test -z "$deplibs_check_method" && deplibs_check_method=unknown if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}dlltool", so it can be a program name with args. set dummy ${ac_tool_prefix}dlltool; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_DLLTOOL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$DLLTOOL"; then ac_cv_prog_DLLTOOL="$DLLTOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_DLLTOOL="${ac_tool_prefix}dlltool" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi DLLTOOL=$ac_cv_prog_DLLTOOL if test -n "$DLLTOOL"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $DLLTOOL" >&5 printf '%s\n' "$DLLTOOL" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_DLLTOOL"; then ac_ct_DLLTOOL=$DLLTOOL # Extract the first word of "dlltool", so it can be a program name with args. set dummy dlltool; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_DLLTOOL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_DLLTOOL"; then ac_cv_prog_ac_ct_DLLTOOL="$ac_ct_DLLTOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_DLLTOOL="dlltool" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_DLLTOOL=$ac_cv_prog_ac_ct_DLLTOOL if test -n "$ac_ct_DLLTOOL"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DLLTOOL" >&5 printf '%s\n' "$ac_ct_DLLTOOL" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_DLLTOOL" = x; then DLLTOOL="false" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac DLLTOOL=$ac_ct_DLLTOOL fi else DLLTOOL="$ac_cv_prog_DLLTOOL" fi test -z "$DLLTOOL" && DLLTOOL=dlltool { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking how to associate runtime and link libraries" >&5 printf %s "checking how to associate runtime and link libraries... " >&6; } if test ${lt_cv_sharedlib_from_linklib_cmd+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_sharedlib_from_linklib_cmd='unknown' case $host_os in cygwin* | mingw* | windows* | pw32* | cegcc*) # two different shell functions defined in ltmain.sh; # decide which one to use based on capabilities of $DLLTOOL case `$DLLTOOL --help 2>&1` in *--identify-strict*) lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib ;; *) lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback ;; esac ;; *) # fallback: assume linklib IS sharedlib lt_cv_sharedlib_from_linklib_cmd=$ECHO ;; esac ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sharedlib_from_linklib_cmd" >&5 printf '%s\n' "$lt_cv_sharedlib_from_linklib_cmd" >&6; } sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. set dummy ${ac_tool_prefix}ranlib; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_RANLIB+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$RANLIB"; then ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi RANLIB=$ac_cv_prog_RANLIB if test -n "$RANLIB"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 printf '%s\n' "$RANLIB" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_RANLIB"; then ac_ct_RANLIB=$RANLIB # Extract the first word of "ranlib", so it can be a program name with args. set dummy ranlib; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_RANLIB+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_RANLIB"; then ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_RANLIB="ranlib" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB if test -n "$ac_ct_RANLIB"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 printf '%s\n' "$ac_ct_RANLIB" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_RANLIB" = x; then RANLIB=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac RANLIB=$ac_ct_RANLIB fi else RANLIB="$ac_cv_prog_RANLIB" fi if test -n "$ac_tool_prefix"; then for ac_prog in ar do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_AR+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$AR"; then ac_cv_prog_AR="$AR" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_AR="$ac_tool_prefix$ac_prog" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi AR=$ac_cv_prog_AR if test -n "$AR"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 printf '%s\n' "$AR" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi test -n "$AR" && break done fi if test -z "$AR"; then ac_ct_AR=$AR for ac_prog in ar do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_AR+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_AR"; then ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_AR="$ac_prog" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_AR=$ac_cv_prog_ac_ct_AR if test -n "$ac_ct_AR"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5 printf '%s\n' "$ac_ct_AR" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi test -n "$ac_ct_AR" && break done if test "x$ac_ct_AR" = x; then AR="false" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac AR=$ac_ct_AR fi fi : ${AR=ar} # Use ARFLAGS variable as AR's operation code to sync the variable naming with # Automake. If both AR_FLAGS and ARFLAGS are specified, AR_FLAGS should have # higher priority because that's what people were doing historically (setting # ARFLAGS for automake and AR_FLAGS for libtool). FIXME: Make the AR_FLAGS # variable obsoleted/removed. test ${AR_FLAGS+y} || AR_FLAGS=${ARFLAGS-cr} lt_ar_flags=$AR_FLAGS # Make AR_FLAGS overridable by 'make ARFLAGS='. Don't try to run-time override # by AR_FLAGS because that was never working and AR_FLAGS is about to die. { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for archiver @FILE support" >&5 printf %s "checking for archiver @FILE support... " >&6; } if test ${lt_cv_ar_at_file+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_ar_at_file=no cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : echo conftest.$ac_objext > conftest.lst lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&5' { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 (eval $lt_ar_try) 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if test 0 -eq "$ac_status"; then # Ensure the archiver fails upon bogus file names. rm -f conftest.$ac_objext libconftest.a { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 (eval $lt_ar_try) 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if test 0 -ne "$ac_status"; then lt_cv_ar_at_file=@ fi fi rm -f conftest.* libconftest.a fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ar_at_file" >&5 printf '%s\n' "$lt_cv_ar_at_file" >&6; } if test no = "$lt_cv_ar_at_file"; then archiver_list_spec= else archiver_list_spec=$lt_cv_ar_at_file fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. set dummy ${ac_tool_prefix}strip; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_STRIP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$STRIP"; then ac_cv_prog_STRIP="$STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_STRIP="${ac_tool_prefix}strip" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi STRIP=$ac_cv_prog_STRIP if test -n "$STRIP"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 printf '%s\n' "$STRIP" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_STRIP"; then ac_ct_STRIP=$STRIP # Extract the first word of "strip", so it can be a program name with args. set dummy strip; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_STRIP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_STRIP"; then ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_STRIP="strip" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP if test -n "$ac_ct_STRIP"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 printf '%s\n' "$ac_ct_STRIP" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_STRIP" = x; then STRIP=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac STRIP=$ac_ct_STRIP fi else STRIP="$ac_cv_prog_STRIP" fi test -z "$STRIP" && STRIP=: test -z "$RANLIB" && RANLIB=: # Determine commands to create old-style static archives. old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' old_postinstall_cmds='chmod 644 $oldlib' old_postuninstall_cmds= if test -n "$RANLIB"; then old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" fi case $host_os in darwin*) lock_old_archive_extraction=yes ;; *) lock_old_archive_extraction=no ;; esac # If no C compiler was specified, use CC. LTCC=${LTCC-"$CC"} # If no C compiler flags were specified, use CFLAGS. LTCFLAGS=${LTCFLAGS-"$CFLAGS"} # Allow CC to be a program name with arguments. compiler=$CC # Check for command to grab the raw symbol name followed by C symbol from nm. { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking command to parse $NM output from $compiler object" >&5 printf %s "checking command to parse $NM output from $compiler object... " >&6; } if test ${lt_cv_sys_global_symbol_pipe+y} then : printf %s "(cached) " >&6 else case e in #( e) # These are sane defaults that work on at least a few old systems. # [They come from Ultrix. What could be older than Ultrix?!! ;)] # Character class describing NM global symbol codes. symcode='[BCDEGRST]' # Regexp to match symbols that can be accessed directly from C. sympat='\([_A-Za-z][_A-Za-z0-9]*\)' # Define system-specific variables. case $host_os in aix*) symcode='[BCDT]' ;; cygwin* | mingw* | windows* | pw32* | cegcc*) symcode='[ABCDGISTW]' ;; hpux*) if test ia64 = "$host_cpu"; then symcode='[ABCDEGRST]' fi ;; irix* | nonstopux*) symcode='[BCDEGRST]' ;; osf*) symcode='[BCDEGQRST]' ;; solaris*) symcode='[BCDRT]' ;; sco3.2v5*) symcode='[DT]' ;; sysv4.2uw2*) symcode='[DT]' ;; sysv5* | sco5v6* | unixware* | OpenUNIX*) symcode='[ABDT]' ;; sysv4) symcode='[DFNSTU]' ;; esac # If we're using GNU nm, then use its standard symbol codes. case `$NM -V 2>&1` in *GNU* | *'with BFD'*) symcode='[ABCDGIRSTW]' ;; esac if test "$lt_cv_nm_interface" = "MS dumpbin"; then # Gets list of data symbols to import. lt_cv_sys_global_symbol_to_import="$SED -n -e 's/^I .* \(.*\)$/\1/p'" # Adjust the below global symbol transforms to fixup imported variables. lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'" lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'" lt_c_name_lib_hook="\ -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\ -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'" else # Disable hooks by default. lt_cv_sys_global_symbol_to_import= lt_cdecl_hook= lt_c_name_hook= lt_c_name_lib_hook= fi # Transform an extracted symbol line into a proper C declaration. # Some systems (esp. on ia64) link data and code symbols differently, # so use this general approach. lt_cv_sys_global_symbol_to_cdecl="$SED -n"\ $lt_cdecl_hook\ " -e 's/^T .* \(.*\)$/extern int \1();/p'"\ " -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'" # Transform an extracted symbol line into symbol name and symbol address lt_cv_sys_global_symbol_to_c_name_address="$SED -n"\ $lt_c_name_hook\ " -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ " -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'" # Transform an extracted symbol line into symbol name with lib prefix and # symbol address. lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="$SED -n"\ $lt_c_name_lib_hook\ " -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ " -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\ " -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'" # Handle CRLF in mingw tool chain opt_cr= case $build_os in mingw* | windows*) opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp ;; esac # Try without a prefix underscore, then with it. for ac_symprfx in "" "_"; do # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. symxfrm="\\1 $ac_symprfx\\2 \\2" # Write the raw and C identifiers. if test "$lt_cv_nm_interface" = "MS dumpbin"; then # Fake it for dumpbin and say T for any non-static function, # D for any global variable and I for any imported variable. # Also find C++ and __fastcall symbols from MSVC++ or ICC, # which start with @ or ?. lt_cv_sys_global_symbol_pipe="$AWK '"\ " {last_section=section; section=\$ 3};"\ " /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ " /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ " /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\ " /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\ " /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\ " \$ 0!~/External *\|/{next};"\ " / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ " {if(hide[section]) next};"\ " {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\ " {split(\$ 0,a,/\||\r/); split(a[2],s)};"\ " s[1]~/^[@?]/{print f,s[1],s[1]; next};"\ " s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\ " ' prfx=^$ac_symprfx" else lt_cv_sys_global_symbol_pipe="$SED -n -e 's/^.*[ ]\($symcode$symcode*\)[ ][ ]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" fi lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | $SED '/ __gnu_lto/d'" # Check to see that the pipe works correctly. pipe_works=no rm -f conftest* cat > conftest.$ac_ext <<_LT_EOF #ifdef __cplusplus extern "C" { #endif char nm_test_var; void nm_test_func(void); void nm_test_func(void){} #ifdef __cplusplus } #endif int main(void){nm_test_var='a';nm_test_func();return(0);} _LT_EOF if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then # Now try to grab the symbols. nlist=conftest.nm $ECHO "$as_me:$LINENO: $NM conftest.$ac_objext | $lt_cv_sys_global_symbol_pipe > $nlist" >&5 if eval "$NM" conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist 2>&5 && test -s "$nlist"; then # Try sorting and uniquifying the output. if sort "$nlist" | uniq > "$nlist"T; then mv -f "$nlist"T "$nlist" else rm -f "$nlist"T fi # Make sure that we snagged all the symbols we need. if $GREP ' nm_test_var$' "$nlist" >/dev/null; then if $GREP ' nm_test_func$' "$nlist" >/dev/null; then cat <<_LT_EOF > conftest.$ac_ext /* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ #if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE /* DATA imports from DLLs on WIN32 can't be const, because runtime relocations are performed -- see ld's documentation on pseudo-relocs. */ # define LT_DLSYM_CONST #elif defined __osf__ /* This system does not cope well with relocations in const data. */ # define LT_DLSYM_CONST #else # define LT_DLSYM_CONST const #endif #ifdef __cplusplus extern "C" { #endif _LT_EOF # Now generate the symbol file. eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' cat <<_LT_EOF >> conftest.$ac_ext /* The mapping between symbol names and symbols. */ LT_DLSYM_CONST struct { const char *name; void *address; } lt__PROGRAM__LTX_preloaded_symbols[] = { { "@PROGRAM@", (void *) 0 }, _LT_EOF $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext cat <<\_LT_EOF >> conftest.$ac_ext {0, (void *) 0} }; /* This works around a problem in FreeBSD linker */ #ifdef FREEBSD_WORKAROUND static const void *lt_preloaded_setup() { return lt__PROGRAM__LTX_preloaded_symbols; } #endif #ifdef __cplusplus } #endif _LT_EOF # Now try linking the two files. mv conftest.$ac_objext conftstm.$ac_objext lt_globsym_save_LIBS=$LIBS lt_globsym_save_CFLAGS=$CFLAGS LIBS=conftstm.$ac_objext CFLAGS="$CFLAGS$lt_prog_compiler_no_builtin_flag" if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 (eval $ac_link) 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && test -s conftest$ac_exeext; then pipe_works=yes fi LIBS=$lt_globsym_save_LIBS CFLAGS=$lt_globsym_save_CFLAGS else echo "cannot find nm_test_func in $nlist" >&5 fi else echo "cannot find nm_test_var in $nlist" >&5 fi else echo "cannot run $lt_cv_sys_global_symbol_pipe" >&5 fi else echo "$progname: failed program was:" >&5 cat conftest.$ac_ext >&5 fi rm -rf conftest* conftst* # Do not use the global_symbol_pipe unless it works. if test yes = "$pipe_works"; then break else lt_cv_sys_global_symbol_pipe= fi done ;; esac fi if test -z "$lt_cv_sys_global_symbol_pipe"; then lt_cv_sys_global_symbol_to_cdecl= fi if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: failed" >&5 printf '%s\n' "failed" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: ok" >&5 printf '%s\n' "ok" >&6; } fi # Response file support. if test "$lt_cv_nm_interface" = "MS dumpbin"; then nm_file_list_spec='@' elif $NM --help 2>/dev/null | grep '[@]FILE' >/dev/null; then nm_file_list_spec='@' fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for sysroot" >&5 printf %s "checking for sysroot... " >&6; } # Check whether --with-sysroot was given. if test ${with_sysroot+y} then : withval=$with_sysroot; else case e in #( e) with_sysroot=no ;; esac fi lt_sysroot= case $with_sysroot in #( yes) if test yes = "$GCC"; then # Trim trailing / since we'll always append absolute paths and we want # to avoid //, if only for less confusing output for the user. lt_sysroot=`$CC --print-sysroot 2>/dev/null | $SED 's:/\+$::'` fi ;; #( /*) lt_sysroot=`echo "$with_sysroot" | $SED -e "$sed_quote_subst"` ;; #( no|'') ;; #( *) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $with_sysroot" >&5 printf '%s\n' "$with_sysroot" >&6; } as_fn_error $? "The sysroot must be an absolute path." "$LINENO" 5 ;; esac { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: ${lt_sysroot:-no}" >&5 printf '%s\n' "${lt_sysroot:-no}" >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for a working dd" >&5 printf %s "checking for a working dd... " >&6; } if test ${ac_cv_path_lt_DD+y} then : printf %s "(cached) " >&6 else case e in #( e) printf 0123456789abcdef0123456789abcdef >conftest.i cat conftest.i conftest.i >conftest2.i : ${lt_DD:=$DD} if test -z "$lt_DD"; then ac_path_lt_DD_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in dd do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_lt_DD="$as_dir$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_lt_DD" || continue if "$ac_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then cmp -s conftest.i conftest.out \ && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=: fi $ac_path_lt_DD_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_lt_DD"; then : fi else ac_cv_path_lt_DD=$lt_DD fi rm -f conftest.i conftest2.i conftest.out ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_lt_DD" >&5 printf '%s\n' "$ac_cv_path_lt_DD" >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking how to truncate binary pipes" >&5 printf %s "checking how to truncate binary pipes... " >&6; } if test ${lt_cv_truncate_bin+y} then : printf %s "(cached) " >&6 else case e in #( e) printf 0123456789abcdef0123456789abcdef >conftest.i cat conftest.i conftest.i >conftest2.i lt_cv_truncate_bin= if "$ac_cv_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then cmp -s conftest.i conftest.out \ && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1" fi rm -f conftest.i conftest2.i conftest.out test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q" ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_truncate_bin" >&5 printf '%s\n' "$lt_cv_truncate_bin" >&6; } # Calculate cc_basename. Skip known compiler wrappers and cross-prefix. func_cc_basename () { for cc_temp in $*""; do case $cc_temp in compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; \-*) ;; *) break;; esac done func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` } # Check whether --enable-libtool-lock was given. if test ${enable_libtool_lock+y} then : enableval=$enable_libtool_lock; fi test no = "$enable_libtool_lock" || enable_libtool_lock=yes # Some flags need to be propagated to the compiler or linker for good # libtool support. case $host in ia64-*-hpux*) # Find out what ABI is being produced by ac_compile, and set mode # options accordingly. echo 'int i;' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then case `$FILECMD conftest.$ac_objext` in *ELF-32*) HPUX_IA64_MODE=32 ;; *ELF-64*) HPUX_IA64_MODE=64 ;; esac fi rm -rf conftest* ;; *-*-irix6*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. echo '#line '$LINENO' "configure"' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then if test yes = "$lt_cv_prog_gnu_ld"; then case `$FILECMD conftest.$ac_objext` in *32-bit*) LD="${LD-ld} -melf32bsmip" ;; *N32*) LD="${LD-ld} -melf32bmipn32" ;; *64-bit*) LD="${LD-ld} -melf64bmip" ;; esac else case `$FILECMD conftest.$ac_objext` in *32-bit*) LD="${LD-ld} -32" ;; *N32*) LD="${LD-ld} -n32" ;; *64-bit*) LD="${LD-ld} -64" ;; esac fi fi rm -rf conftest* ;; mips64*-*linux*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. echo '#line '$LINENO' "configure"' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then emul=elf case `$FILECMD conftest.$ac_objext` in *32-bit*) emul="${emul}32" ;; *64-bit*) emul="${emul}64" ;; esac case `$FILECMD conftest.$ac_objext` in *MSB*) emul="${emul}btsmip" ;; *LSB*) emul="${emul}ltsmip" ;; esac case `$FILECMD conftest.$ac_objext` in *N32*) emul="${emul}n32" ;; esac LD="${LD-ld} -m $emul" fi rm -rf conftest* ;; x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \ s390*-*linux*|s390*-*tpf*|sparc*-*linux*|x86_64-gnu*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. Note that the listed cases only cover the # situations where additional linker options are needed (such as when # doing 32-bit compilation for a host where ld defaults to 64-bit, or # vice versa); the common cases where no linker options are needed do # not appear in the list. echo 'int i;' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then case `$FILECMD conftest.o` in *32-bit*) case $host in x86_64-*kfreebsd*-gnu) LD="${LD-ld} -m elf_i386_fbsd" ;; x86_64-*linux*|x86_64-gnu*) case `$FILECMD conftest.o` in *x86-64*) LD="${LD-ld} -m elf32_x86_64" ;; *) LD="${LD-ld} -m elf_i386" ;; esac ;; powerpc64le-*linux*) LD="${LD-ld} -m elf32lppclinux" ;; powerpc64-*linux*) LD="${LD-ld} -m elf32ppclinux" ;; s390x-*linux*) LD="${LD-ld} -m elf_s390" ;; sparc64-*linux*) LD="${LD-ld} -m elf32_sparc" ;; esac ;; *64-bit*) case $host in x86_64-*kfreebsd*-gnu) LD="${LD-ld} -m elf_x86_64_fbsd" ;; x86_64-*linux*|x86_64-gnu*) LD="${LD-ld} -m elf_x86_64" ;; powerpcle-*linux*) LD="${LD-ld} -m elf64lppc" ;; powerpc-*linux*) LD="${LD-ld} -m elf64ppc" ;; s390*-*linux*|s390*-*tpf*) LD="${LD-ld} -m elf64_s390" ;; sparc*-*linux*) LD="${LD-ld} -m elf64_sparc" ;; esac ;; esac fi rm -rf conftest* ;; *-*-sco3.2v5*) # On SCO OpenServer 5, we need -belf to get full-featured binaries. SAVE_CFLAGS=$CFLAGS CFLAGS="$CFLAGS -belf" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the C compiler needs -belf" >&5 printf %s "checking whether the C compiler needs -belf... " >&6; } if test ${lt_cv_cc_needs_belf+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : lt_cv_cc_needs_belf=yes else case e in #( e) lt_cv_cc_needs_belf=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_cc_needs_belf" >&5 printf '%s\n' "$lt_cv_cc_needs_belf" >&6; } if test yes != "$lt_cv_cc_needs_belf"; then # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf CFLAGS=$SAVE_CFLAGS fi ;; *-*solaris*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. echo 'int i;' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then case `$FILECMD conftest.o` in *64-bit*) case $lt_cv_prog_gnu_ld in yes*) case $host in i?86-*-solaris*|x86_64-*-solaris*) LD="${LD-ld} -m elf_x86_64" ;; sparc*-*-solaris*) LD="${LD-ld} -m elf64_sparc" ;; esac # GNU ld 2.21 introduced _sol2 emulations. Use them if available. if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then LD=${LD-ld}_sol2 fi ;; *) if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then LD="${LD-ld} -64" fi ;; esac ;; esac fi rm -rf conftest* ;; esac need_locks=$enable_libtool_lock if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}mt", so it can be a program name with args. set dummy ${ac_tool_prefix}mt; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_MANIFEST_TOOL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$MANIFEST_TOOL"; then ac_cv_prog_MANIFEST_TOOL="$MANIFEST_TOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_MANIFEST_TOOL="${ac_tool_prefix}mt" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi MANIFEST_TOOL=$ac_cv_prog_MANIFEST_TOOL if test -n "$MANIFEST_TOOL"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $MANIFEST_TOOL" >&5 printf '%s\n' "$MANIFEST_TOOL" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_MANIFEST_TOOL"; then ac_ct_MANIFEST_TOOL=$MANIFEST_TOOL # Extract the first word of "mt", so it can be a program name with args. set dummy mt; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_MANIFEST_TOOL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_MANIFEST_TOOL"; then ac_cv_prog_ac_ct_MANIFEST_TOOL="$ac_ct_MANIFEST_TOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_MANIFEST_TOOL="mt" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_MANIFEST_TOOL=$ac_cv_prog_ac_ct_MANIFEST_TOOL if test -n "$ac_ct_MANIFEST_TOOL"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_MANIFEST_TOOL" >&5 printf '%s\n' "$ac_ct_MANIFEST_TOOL" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_MANIFEST_TOOL" = x; then MANIFEST_TOOL=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac MANIFEST_TOOL=$ac_ct_MANIFEST_TOOL fi else MANIFEST_TOOL="$ac_cv_prog_MANIFEST_TOOL" fi test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if $MANIFEST_TOOL is a manifest tool" >&5 printf %s "checking if $MANIFEST_TOOL is a manifest tool... " >&6; } if test ${lt_cv_path_manifest_tool+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_path_manifest_tool=no echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&5 $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out cat conftest.err >&5 if $GREP 'Manifest Tool' conftest.out > /dev/null; then lt_cv_path_manifest_tool=yes fi rm -f conftest* ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_manifest_tool" >&5 printf '%s\n' "$lt_cv_path_manifest_tool" >&6; } if test yes != "$lt_cv_path_manifest_tool"; then MANIFEST_TOOL=: fi case $host_os in rhapsody* | darwin*) if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}dsymutil", so it can be a program name with args. set dummy ${ac_tool_prefix}dsymutil; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_DSYMUTIL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$DSYMUTIL"; then ac_cv_prog_DSYMUTIL="$DSYMUTIL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_DSYMUTIL="${ac_tool_prefix}dsymutil" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi DSYMUTIL=$ac_cv_prog_DSYMUTIL if test -n "$DSYMUTIL"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $DSYMUTIL" >&5 printf '%s\n' "$DSYMUTIL" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_DSYMUTIL"; then ac_ct_DSYMUTIL=$DSYMUTIL # Extract the first word of "dsymutil", so it can be a program name with args. set dummy dsymutil; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_DSYMUTIL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_DSYMUTIL"; then ac_cv_prog_ac_ct_DSYMUTIL="$ac_ct_DSYMUTIL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_DSYMUTIL="dsymutil" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_DSYMUTIL=$ac_cv_prog_ac_ct_DSYMUTIL if test -n "$ac_ct_DSYMUTIL"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DSYMUTIL" >&5 printf '%s\n' "$ac_ct_DSYMUTIL" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_DSYMUTIL" = x; then DSYMUTIL=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac DSYMUTIL=$ac_ct_DSYMUTIL fi else DSYMUTIL="$ac_cv_prog_DSYMUTIL" fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}nmedit", so it can be a program name with args. set dummy ${ac_tool_prefix}nmedit; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_NMEDIT+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$NMEDIT"; then ac_cv_prog_NMEDIT="$NMEDIT" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_NMEDIT="${ac_tool_prefix}nmedit" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi NMEDIT=$ac_cv_prog_NMEDIT if test -n "$NMEDIT"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $NMEDIT" >&5 printf '%s\n' "$NMEDIT" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_NMEDIT"; then ac_ct_NMEDIT=$NMEDIT # Extract the first word of "nmedit", so it can be a program name with args. set dummy nmedit; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_NMEDIT+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_NMEDIT"; then ac_cv_prog_ac_ct_NMEDIT="$ac_ct_NMEDIT" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_NMEDIT="nmedit" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_NMEDIT=$ac_cv_prog_ac_ct_NMEDIT if test -n "$ac_ct_NMEDIT"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_NMEDIT" >&5 printf '%s\n' "$ac_ct_NMEDIT" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_NMEDIT" = x; then NMEDIT=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac NMEDIT=$ac_ct_NMEDIT fi else NMEDIT="$ac_cv_prog_NMEDIT" fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}lipo", so it can be a program name with args. set dummy ${ac_tool_prefix}lipo; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_LIPO+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$LIPO"; then ac_cv_prog_LIPO="$LIPO" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_LIPO="${ac_tool_prefix}lipo" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi LIPO=$ac_cv_prog_LIPO if test -n "$LIPO"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $LIPO" >&5 printf '%s\n' "$LIPO" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_LIPO"; then ac_ct_LIPO=$LIPO # Extract the first word of "lipo", so it can be a program name with args. set dummy lipo; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_LIPO+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_LIPO"; then ac_cv_prog_ac_ct_LIPO="$ac_ct_LIPO" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_LIPO="lipo" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_LIPO=$ac_cv_prog_ac_ct_LIPO if test -n "$ac_ct_LIPO"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_LIPO" >&5 printf '%s\n' "$ac_ct_LIPO" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_LIPO" = x; then LIPO=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac LIPO=$ac_ct_LIPO fi else LIPO="$ac_cv_prog_LIPO" fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}otool", so it can be a program name with args. set dummy ${ac_tool_prefix}otool; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_OTOOL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$OTOOL"; then ac_cv_prog_OTOOL="$OTOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_OTOOL="${ac_tool_prefix}otool" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi OTOOL=$ac_cv_prog_OTOOL if test -n "$OTOOL"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $OTOOL" >&5 printf '%s\n' "$OTOOL" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_OTOOL"; then ac_ct_OTOOL=$OTOOL # Extract the first word of "otool", so it can be a program name with args. set dummy otool; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_OTOOL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_OTOOL"; then ac_cv_prog_ac_ct_OTOOL="$ac_ct_OTOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_OTOOL="otool" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_OTOOL=$ac_cv_prog_ac_ct_OTOOL if test -n "$ac_ct_OTOOL"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL" >&5 printf '%s\n' "$ac_ct_OTOOL" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_OTOOL" = x; then OTOOL=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac OTOOL=$ac_ct_OTOOL fi else OTOOL="$ac_cv_prog_OTOOL" fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}otool64", so it can be a program name with args. set dummy ${ac_tool_prefix}otool64; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_OTOOL64+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$OTOOL64"; then ac_cv_prog_OTOOL64="$OTOOL64" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_OTOOL64="${ac_tool_prefix}otool64" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi OTOOL64=$ac_cv_prog_OTOOL64 if test -n "$OTOOL64"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $OTOOL64" >&5 printf '%s\n' "$OTOOL64" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi fi if test -z "$ac_cv_prog_OTOOL64"; then ac_ct_OTOOL64=$OTOOL64 # Extract the first word of "otool64", so it can be a program name with args. set dummy otool64; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_OTOOL64+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_OTOOL64"; then ac_cv_prog_ac_ct_OTOOL64="$ac_ct_OTOOL64" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_OTOOL64="otool64" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_OTOOL64=$ac_cv_prog_ac_ct_OTOOL64 if test -n "$ac_ct_OTOOL64"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL64" >&5 printf '%s\n' "$ac_ct_OTOOL64" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test "x$ac_ct_OTOOL64" = x; then OTOOL64=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac OTOOL64=$ac_ct_OTOOL64 fi else OTOOL64="$ac_cv_prog_OTOOL64" fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for -single_module linker flag" >&5 printf %s "checking for -single_module linker flag... " >&6; } if test ${lt_cv_apple_cc_single_mod+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_apple_cc_single_mod=no if test -z "$LT_MULTI_MODULE"; then # By default we will add the -single_module flag. You can override # by either setting the environment variable LT_MULTI_MODULE # non-empty at configure time, or by adding -multi_module to the # link flags. rm -rf libconftest.dylib* echo "int foo(void){return 1;}" > conftest.c echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ -dynamiclib -Wl,-single_module conftest.c" >&5 $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ -dynamiclib -Wl,-single_module conftest.c 2>conftest.err _lt_result=$? # If there is a non-empty error log, and "single_module" # appears in it, assume the flag caused a linker warning if test -s conftest.err && $GREP single_module conftest.err; then cat conftest.err >&5 # Otherwise, if the output was created with a 0 exit code from # the compiler, it worked. elif test -f libconftest.dylib && test 0 = "$_lt_result"; then lt_cv_apple_cc_single_mod=yes else cat conftest.err >&5 fi rm -rf libconftest.dylib* rm -f conftest.* fi ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_apple_cc_single_mod" >&5 printf '%s\n' "$lt_cv_apple_cc_single_mod" >&6; } # Feature test to disable chained fixups since it is not # compatible with '-undefined dynamic_lookup' { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for -no_fixup_chains linker flag" >&5 printf %s "checking for -no_fixup_chains linker flag... " >&6; } if test ${lt_cv_support_no_fixup_chains+y} then : printf %s "(cached) " >&6 else case e in #( e) save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -Wl,-no_fixup_chains" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : lt_cv_support_no_fixup_chains=yes else case e in #( e) lt_cv_support_no_fixup_chains=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LDFLAGS=$save_LDFLAGS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_support_no_fixup_chains" >&5 printf '%s\n' "$lt_cv_support_no_fixup_chains" >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for -exported_symbols_list linker flag" >&5 printf %s "checking for -exported_symbols_list linker flag... " >&6; } if test ${lt_cv_ld_exported_symbols_list+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_ld_exported_symbols_list=no save_LDFLAGS=$LDFLAGS echo "_main" > conftest.sym LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : lt_cv_ld_exported_symbols_list=yes else case e in #( e) lt_cv_ld_exported_symbols_list=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LDFLAGS=$save_LDFLAGS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_exported_symbols_list" >&5 printf '%s\n' "$lt_cv_ld_exported_symbols_list" >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for -force_load linker flag" >&5 printf %s "checking for -force_load linker flag... " >&6; } if test ${lt_cv_ld_force_load+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_ld_force_load=no cat > conftest.c << _LT_EOF int forced_loaded() { return 2;} _LT_EOF echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&5 $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&5 echo "$AR $AR_FLAGS libconftest.a conftest.o" >&5 $AR $AR_FLAGS libconftest.a conftest.o 2>&5 echo "$RANLIB libconftest.a" >&5 $RANLIB libconftest.a 2>&5 cat > conftest.c << _LT_EOF int main(void) { return 0;} _LT_EOF echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&5 $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err _lt_result=$? if test -s conftest.err && $GREP force_load conftest.err; then cat conftest.err >&5 elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then lt_cv_ld_force_load=yes else cat conftest.err >&5 fi rm -f conftest.err libconftest.a conftest conftest.c rm -rf conftest.dSYM ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_force_load" >&5 printf '%s\n' "$lt_cv_ld_force_load" >&6; } case $host_os in rhapsody* | darwin1.[012]) _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; darwin1.*) _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; darwin*) case $MACOSX_DEPLOYMENT_TARGET,$host in 10.[012],*|,*powerpc*-darwin[5-8]*) _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; *) _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' if test yes = "$lt_cv_support_no_fixup_chains"; then as_fn_append _lt_dar_allow_undefined ' $wl-no_fixup_chains' fi ;; esac ;; esac if test yes = "$lt_cv_apple_cc_single_mod"; then _lt_dar_single_mod='$single_module' fi _lt_dar_needs_single_mod=no case $host_os in rhapsody* | darwin1.*) _lt_dar_needs_single_mod=yes ;; darwin*) # When targeting Mac OS X 10.4 (darwin 8) or later, # -single_module is the default and -multi_module is unsupported. # The toolchain on macOS 10.14 (darwin 18) and later cannot # target any OS version that needs -single_module. case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in 10.0,*-darwin[567].*|10.[0-3],*-darwin[5-9].*|10.[0-3],*-darwin1[0-7].*) _lt_dar_needs_single_mod=yes ;; esac ;; esac if test yes = "$lt_cv_ld_exported_symbols_list"; then _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym' else _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib' fi if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then _lt_dsymutil='~$DSYMUTIL $lib || :' else _lt_dsymutil= fi ;; esac # func_munge_path_list VARIABLE PATH # ----------------------------------- # VARIABLE is name of variable containing _space_ separated list of # directories to be munged by the contents of PATH, which is string # having a format: # "DIR[:DIR]:" # string "DIR[ DIR]" will be prepended to VARIABLE # ":DIR[:DIR]" # string "DIR[ DIR]" will be appended to VARIABLE # "DIRP[:DIRP]::[DIRA:]DIRA" # string "DIRP[ DIRP]" will be prepended to VARIABLE and string # "DIRA[ DIRA]" will be appended to VARIABLE # "DIR[:DIR]" # VARIABLE will be replaced by "DIR[ DIR]" func_munge_path_list () { case x$2 in x) ;; *:) eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\" ;; x:*) eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\" ;; *::*) eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\" ;; *) eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\" ;; esac } ac_header= ac_cache= for ac_item in $ac_header_c_list do if test $ac_cache; then ac_fn_c_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default" if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then printf '%s\n' "#define $ac_item 1" >> confdefs.h fi ac_header= ac_cache= elif test $ac_header; then ac_cache=$ac_item else ac_header=$ac_item fi done if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes then : printf '%s\n' "#define STDC_HEADERS 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default " if test "x$ac_cv_header_dlfcn_h" = xyes then : printf '%s\n' "#define HAVE_DLFCN_H 1" >>confdefs.h fi # Set options # Check whether --enable-static was given. if test ${enable_static+y} then : enableval=$enable_static; p=${PACKAGE-default} case $enableval in yes) enable_static=yes ;; no) enable_static=no ;; *) enable_static=no # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for pkg in $enableval; do IFS=$lt_save_ifs if test "X$pkg" = "X$p"; then enable_static=yes fi done IFS=$lt_save_ifs ;; esac else case e in #( e) enable_static=no ;; esac fi enable_dlopen=no enable_win32_dll=no # Check whether --enable-shared was given. if test ${enable_shared+y} then : enableval=$enable_shared; p=${PACKAGE-default} case $enableval in yes) enable_shared=yes ;; no) enable_shared=no ;; *) enable_shared=no # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for pkg in $enableval; do IFS=$lt_save_ifs if test "X$pkg" = "X$p"; then enable_shared=yes fi done IFS=$lt_save_ifs ;; esac else case e in #( e) enable_shared=yes ;; esac fi # Check whether --enable-pic was given. if test ${enable_pic+y} then : enableval=$enable_pic; lt_p=${PACKAGE-default} case $enableval in yes|no) pic_mode=$enableval ;; *) pic_mode=default # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for lt_pkg in $enableval; do IFS=$lt_save_ifs if test "X$lt_pkg" = "X$lt_p"; then pic_mode=yes fi done IFS=$lt_save_ifs ;; esac else case e in #( e) # Check whether --with-pic was given. if test ${with_pic+y} then : withval=$with_pic; lt_p=${PACKAGE-default} case $withval in yes|no) pic_mode=$withval ;; *) pic_mode=default # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for lt_pkg in $withval; do IFS=$lt_save_ifs if test "X$lt_pkg" = "X$lt_p"; then pic_mode=yes fi done IFS=$lt_save_ifs ;; esac else case e in #( e) pic_mode=default ;; esac fi ;; esac fi # Check whether --enable-fast-install was given. if test ${enable_fast_install+y} then : enableval=$enable_fast_install; p=${PACKAGE-default} case $enableval in yes) enable_fast_install=yes ;; no) enable_fast_install=no ;; *) enable_fast_install=no # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for pkg in $enableval; do IFS=$lt_save_ifs if test "X$pkg" = "X$p"; then enable_fast_install=yes fi done IFS=$lt_save_ifs ;; esac else case e in #( e) enable_fast_install=yes ;; esac fi shared_archive_member_spec= case $host,$enable_shared in power*-*-aix[5-9]*,yes) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking which variant of shared library versioning to provide" >&5 printf %s "checking which variant of shared library versioning to provide... " >&6; } # Check whether --enable-aix-soname was given. if test ${enable_aix_soname+y} then : enableval=$enable_aix_soname; case $enableval in aix|svr4|both) ;; *) as_fn_error $? "Unknown argument to --enable-aix-soname" "$LINENO" 5 ;; esac lt_cv_with_aix_soname=$enable_aix_soname else case e in #( e) # Check whether --with-aix-soname was given. if test ${with_aix_soname+y} then : withval=$with_aix_soname; case $withval in aix|svr4|both) ;; *) as_fn_error $? "Unknown argument to --with-aix-soname" "$LINENO" 5 ;; esac lt_cv_with_aix_soname=$with_aix_soname else case e in #( e) if test ${lt_cv_with_aix_soname+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_with_aix_soname=aix ;; esac fi ;; esac fi enable_aix_soname=$lt_cv_with_aix_soname ;; esac fi with_aix_soname=$enable_aix_soname { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $with_aix_soname" >&5 printf '%s\n' "$with_aix_soname" >&6; } if test aix != "$with_aix_soname"; then # For the AIX way of multilib, we name the shared archive member # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o', # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File. # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag, # the AIX toolchain works better with OBJECT_MODE set (default 32). if test 64 = "${OBJECT_MODE-32}"; then shared_archive_member_spec=shr_64 else shared_archive_member_spec=shr fi fi ;; *) with_aix_soname=aix ;; esac # This can be used to rebuild libtool when needed LIBTOOL_DEPS=$ltmain # Always use our own libtool. LIBTOOL='$(SHELL) $(top_builddir)/libtool' test -z "$LN_S" && LN_S="ln -s" if test -n "${ZSH_VERSION+set}"; then setopt NO_GLOB_SUBST fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for objdir" >&5 printf %s "checking for objdir... " >&6; } if test ${lt_cv_objdir+y} then : printf %s "(cached) " >&6 else case e in #( e) rm -f .libs 2>/dev/null mkdir .libs 2>/dev/null if test -d .libs; then lt_cv_objdir=.libs else # MS-DOS does not allow filenames that begin with a dot. lt_cv_objdir=_libs fi rmdir .libs 2>/dev/null ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_objdir" >&5 printf '%s\n' "$lt_cv_objdir" >&6; } objdir=$lt_cv_objdir printf '%s\n' "#define LT_OBJDIR \"$lt_cv_objdir/\"" >>confdefs.h case $host_os in aix3*) # AIX sometimes has problems with the GCC collect2 program. For some # reason, if we set the COLLECT_NAMES environment variable, the problems # vanish in a puff of smoke. if test set != "${COLLECT_NAMES+set}"; then COLLECT_NAMES= export COLLECT_NAMES fi ;; esac # Global variables: ofile=libtool can_build_shared=yes # All known linkers require a '.a' archive for static linking (except MSVC and # ICC, which need '.lib'). libext=a with_gnu_ld=$lt_cv_prog_gnu_ld old_CC=$CC old_CFLAGS=$CFLAGS # Set sane defaults for various variables test -z "$CC" && CC=cc test -z "$LTCC" && LTCC=$CC test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS test -z "$LD" && LD=ld test -z "$ac_objext" && ac_objext=o func_cc_basename $compiler cc_basename=$func_cc_basename_result # Only perform the check for file, if the check method requires it test -z "$MAGIC_CMD" && MAGIC_CMD=file case $deplibs_check_method in file_magic*) if test "$file_magic_cmd" = '$MAGIC_CMD'; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for ${ac_tool_prefix}file" >&5 printf %s "checking for ${ac_tool_prefix}file... " >&6; } if test ${lt_cv_path_MAGIC_CMD+y} then : printf %s "(cached) " >&6 else case e in #( e) case $MAGIC_CMD in [\\/*] | ?:[\\/]*) lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. ;; *) lt_save_MAGIC_CMD=$MAGIC_CMD lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" for ac_dir in $ac_dummy; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/${ac_tool_prefix}file"; then lt_cv_path_MAGIC_CMD=$ac_dir/"${ac_tool_prefix}file" if test -n "$file_magic_test_file"; then case $deplibs_check_method in "file_magic "*) file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` MAGIC_CMD=$lt_cv_path_MAGIC_CMD if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | $EGREP "$file_magic_regex" > /dev/null; then : else cat <<_LT_EOF 1>&2 *** Warning: the command libtool uses to detect shared libraries, *** $file_magic_cmd, produces output that libtool cannot recognize. *** The result is that libtool may fail to recognize shared libraries *** as such. This will affect the creation of libtool libraries that *** depend on shared libraries, but programs linked with such libtool *** libraries will work regardless of this problem. Nevertheless, you *** may want to report the problem to your system manager and/or to *** bug-libtool@gnu.org _LT_EOF fi ;; esac fi break fi done IFS=$lt_save_ifs MAGIC_CMD=$lt_save_MAGIC_CMD ;; esac ;; esac fi MAGIC_CMD=$lt_cv_path_MAGIC_CMD if test -n "$MAGIC_CMD"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 printf '%s\n' "$MAGIC_CMD" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi if test -z "$lt_cv_path_MAGIC_CMD"; then if test -n "$ac_tool_prefix"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for file" >&5 printf %s "checking for file... " >&6; } if test ${lt_cv_path_MAGIC_CMD+y} then : printf %s "(cached) " >&6 else case e in #( e) case $MAGIC_CMD in [\\/*] | ?:[\\/]*) lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. ;; *) lt_save_MAGIC_CMD=$MAGIC_CMD lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" for ac_dir in $ac_dummy; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/file"; then lt_cv_path_MAGIC_CMD=$ac_dir/"file" if test -n "$file_magic_test_file"; then case $deplibs_check_method in "file_magic "*) file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` MAGIC_CMD=$lt_cv_path_MAGIC_CMD if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | $EGREP "$file_magic_regex" > /dev/null; then : else cat <<_LT_EOF 1>&2 *** Warning: the command libtool uses to detect shared libraries, *** $file_magic_cmd, produces output that libtool cannot recognize. *** The result is that libtool may fail to recognize shared libraries *** as such. This will affect the creation of libtool libraries that *** depend on shared libraries, but programs linked with such libtool *** libraries will work regardless of this problem. Nevertheless, you *** may want to report the problem to your system manager and/or to *** bug-libtool@gnu.org _LT_EOF fi ;; esac fi break fi done IFS=$lt_save_ifs MAGIC_CMD=$lt_save_MAGIC_CMD ;; esac ;; esac fi MAGIC_CMD=$lt_cv_path_MAGIC_CMD if test -n "$MAGIC_CMD"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 printf '%s\n' "$MAGIC_CMD" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi else MAGIC_CMD=: fi fi fi ;; esac # Use C for the default configuration in the libtool script lt_save_CC=$CC ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu # Source file extension for C test sources. ac_ext=c # Object file extension for compiled C test sources. objext=o objext=$objext # Code to be used in simple compile tests lt_simple_compile_test_code="int some_variable = 0;" # Code to be used in simple link tests lt_simple_link_test_code='int main(void){return(0);}' # If no C compiler was specified, use CC. LTCC=${LTCC-"$CC"} # If no C compiler flags were specified, use CFLAGS. LTCFLAGS=${LTCFLAGS-"$CFLAGS"} # Allow CC to be a program name with arguments. compiler=$CC # Save the default compiler, since it gets overwritten when the other # tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. compiler_DEFAULT=$CC # save warnings/boilerplate of simple test code ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" >conftest.$ac_ext eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err _lt_compiler_boilerplate=`cat conftest.err` $RM conftest* ac_outfile=conftest.$ac_objext echo "$lt_simple_link_test_code" >conftest.$ac_ext eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err _lt_linker_boilerplate=`cat conftest.err` $RM -r conftest* if test -n "$compiler"; then lt_prog_compiler_no_builtin_flag= if test yes = "$GCC"; then case $cc_basename in nvcc*) lt_prog_compiler_no_builtin_flag=' -Xcompiler -fno-builtin' ;; *) lt_prog_compiler_no_builtin_flag=' -fno-builtin' ;; esac { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -fno-rtti -fno-exceptions" >&5 printf %s "checking if $compiler supports -fno-rtti -fno-exceptions... " >&6; } if test ${lt_cv_prog_compiler_rtti_exceptions+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_rtti_exceptions=no ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="-fno-rtti -fno-exceptions" ## exclude from sc_useless_quotes_in_assignment # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. # The option is referenced via a variable to avoid confusing sed. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_rtti_exceptions=yes fi fi $RM conftest* ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_rtti_exceptions" >&5 printf '%s\n' "$lt_cv_prog_compiler_rtti_exceptions" >&6; } if test yes = "$lt_cv_prog_compiler_rtti_exceptions"; then lt_prog_compiler_no_builtin_flag="$lt_prog_compiler_no_builtin_flag -fno-rtti -fno-exceptions" else : fi fi lt_prog_compiler_wl= lt_prog_compiler_pic= lt_prog_compiler_static= if test yes = "$GCC"; then lt_prog_compiler_wl='-Wl,' lt_prog_compiler_static='-static' case $host_os in aix*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static='-Bstatic' fi lt_prog_compiler_pic='-fPIC' ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support lt_prog_compiler_pic='-fPIC' ;; m68k) # FIXME: we need at least 68020 code to build shared libraries, but # adding the '-m68020' flag to GCC prevents building anything better, # like '-m68040'. lt_prog_compiler_pic='-m68020 -resident32 -malways-restore-a4' ;; esac ;; beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) # PIC is the default for these OSes. ;; mingw* | windows* | cygwin* | pw32* | os2* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). # Although the cygwin gcc ignores -fPIC, still need this for old-style # (--disable-auto-import) libraries lt_prog_compiler_pic='-DDLL_EXPORT' case $host_os in os2*) lt_prog_compiler_static='$wl-static' ;; esac ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files lt_prog_compiler_pic='-fno-common' ;; haiku*) # PIC is the default for Haiku. # The "-static" flag exists, but is broken. lt_prog_compiler_static= ;; hpux*) # PIC is the default for 64-bit PA HP-UX, but not for 32-bit # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag # sets the default TLS model and affects inlining. case $host_cpu in hppa*64*) # +Z the default ;; *) lt_prog_compiler_pic='-fPIC' ;; esac ;; interix[3-9]*) # Interix 3.x gcc -fpic/-fPIC options generate broken code. # Instead, we relocate shared libraries at runtime. ;; msdosdjgpp*) # Just because we use GCC doesn't mean we suddenly get shared libraries # on systems that don't support them. lt_prog_compiler_can_build_shared=no enable_shared=no ;; *nto* | *qnx*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic='-fPIC -shared' ;; sysv4*MP*) if test -d /usr/nec; then lt_prog_compiler_pic=-Kconform_pic fi ;; *) lt_prog_compiler_pic='-fPIC' ;; esac case $cc_basename in nvcc*) # Cuda Compiler Driver 2.2 lt_prog_compiler_wl='-Xlinker ' if test -n "$lt_prog_compiler_pic"; then lt_prog_compiler_pic="-Xcompiler $lt_prog_compiler_pic" fi ;; esac else # PORTME Check for flag to pass linker flags through the system compiler. case $host_os in aix*) lt_prog_compiler_wl='-Wl,' if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static='-Bstatic' else lt_prog_compiler_static='-bnso -bI:/lib/syscalls.exp' fi ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files lt_prog_compiler_pic='-fno-common' case $cc_basename in nagfor*) # NAG Fortran compiler lt_prog_compiler_wl='-Wl,-Wl,,' lt_prog_compiler_pic='-PIC' lt_prog_compiler_static='-Bstatic' ;; esac ;; mingw* | windows* | cygwin* | pw32* | os2* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). lt_prog_compiler_pic='-DDLL_EXPORT' case $host_os in os2*) lt_prog_compiler_static='$wl-static' ;; esac ;; hpux9* | hpux10* | hpux11*) lt_prog_compiler_wl='-Wl,' # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but # not for PA HP-UX. case $host_cpu in hppa*64*|ia64*) # +Z the default ;; *) lt_prog_compiler_pic='+Z' ;; esac # Is there a better lt_prog_compiler_static that works with the bundled CC? lt_prog_compiler_static='$wl-a ${wl}archive' ;; irix5* | irix6* | nonstopux*) lt_prog_compiler_wl='-Wl,' # PIC (with -KPIC) is the default. lt_prog_compiler_static='-non_shared' ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in # old Intel for x86_64, which still supported -KPIC. ecc*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-static' ;; *flang* | ftn | f18* | f95*) # Flang compiler. lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; # flang / f18. f95 an alias for gfortran or flang on Debian flang* | f18* | f95*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; # icc used to be incompatible with GCC. # ICC 10 doesn't accept -KPIC any more. icc* | ifort*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; # Lahey Fortran 8.1. lf95*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='--shared' lt_prog_compiler_static='--static' ;; nagfor*) # NAG Fortran compiler lt_prog_compiler_wl='-Wl,-Wl,,' lt_prog_compiler_pic='-PIC' lt_prog_compiler_static='-Bstatic' ;; tcc*) # Fabrice Bellard et al's Tiny C Compiler lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) # Portland Group compilers (*not* the Pentium gcc compiler, # which looks to be a dead project) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fpic' lt_prog_compiler_static='-Bstatic' ;; ccc*) lt_prog_compiler_wl='-Wl,' # All Alpha code is PIC. lt_prog_compiler_static='-non_shared' ;; xl* | bgxl* | bgf* | mpixl*) # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-qpic' lt_prog_compiler_static='-qstaticlink' ;; *) case `$CC -V 2>&1 | $SED 5q` in *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [1-7].* | *Sun*Fortran*\ 8.[0-3]*) # Sun Fortran 8.3 passes all unrecognized flags to the linker lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' lt_prog_compiler_wl='' ;; *Sun\ F* | *Sun*Fortran*) lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' lt_prog_compiler_wl='-Qoption ld ' ;; *Sun\ C*) # Sun C 5.9 lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' lt_prog_compiler_wl='-Wl,' ;; *Intel*\ [CF]*Compiler*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; *Portland\ Group*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fpic' lt_prog_compiler_static='-Bstatic' ;; esac ;; esac ;; newsos6) lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' ;; *-mlibc) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; *nto* | *qnx*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic='-fPIC -shared' ;; osf3* | osf4* | osf5*) lt_prog_compiler_wl='-Wl,' # All OSF/1 code is PIC. lt_prog_compiler_static='-non_shared' ;; rdos*) lt_prog_compiler_static='-non_shared' ;; serenity*) ;; solaris*) lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' case $cc_basename in f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) lt_prog_compiler_wl='-Qoption ld ';; *) lt_prog_compiler_wl='-Wl,';; esac ;; sunos4*) lt_prog_compiler_wl='-Qoption ld ' lt_prog_compiler_pic='-PIC' lt_prog_compiler_static='-Bstatic' ;; sysv4 | sysv4.2uw2* | sysv4.3*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' ;; sysv4*MP*) if test -d /usr/nec; then lt_prog_compiler_pic='-Kconform_pic' lt_prog_compiler_static='-Bstatic' fi ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' ;; unicos*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_can_build_shared=no ;; uts4*) lt_prog_compiler_pic='-pic' lt_prog_compiler_static='-Bstatic' ;; *) lt_prog_compiler_can_build_shared=no ;; esac fi case $host_os in # For platforms that do not support PIC, -DPIC is meaningless: *djgpp*) lt_prog_compiler_pic= ;; *) lt_prog_compiler_pic="$lt_prog_compiler_pic -DPIC" ;; esac { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 printf %s "checking for $compiler option to produce PIC... " >&6; } if test ${lt_cv_prog_compiler_pic+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_pic=$lt_prog_compiler_pic ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic" >&5 printf '%s\n' "$lt_cv_prog_compiler_pic" >&6; } lt_prog_compiler_pic=$lt_cv_prog_compiler_pic # # Check to make sure the PIC flag actually works. # if test -n "$lt_prog_compiler_pic"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic works" >&5 printf %s "checking if $compiler PIC flag $lt_prog_compiler_pic works... " >&6; } if test ${lt_cv_prog_compiler_pic_works+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_pic_works=no ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="$lt_prog_compiler_pic -DPIC" ## exclude from sc_useless_quotes_in_assignment # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. # The option is referenced via a variable to avoid confusing sed. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_pic_works=yes fi fi $RM conftest* ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works" >&5 printf '%s\n' "$lt_cv_prog_compiler_pic_works" >&6; } if test yes = "$lt_cv_prog_compiler_pic_works"; then case $lt_prog_compiler_pic in "" | " "*) ;; *) lt_prog_compiler_pic=" $lt_prog_compiler_pic" ;; esac else lt_prog_compiler_pic= lt_prog_compiler_can_build_shared=no fi fi # # Check to make sure the static flag actually works. # wl=$lt_prog_compiler_wl eval lt_tmp_static_flag=\"$lt_prog_compiler_static\" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 printf %s "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } if test ${lt_cv_prog_compiler_static_works+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_static_works=no save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS $lt_tmp_static_flag" echo "$lt_simple_link_test_code" > conftest.$ac_ext if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then # The linker can only warn and ignore the option if not recognized # So say no if there are warnings if test -s conftest.err; then # Append any errors to the config.log. cat conftest.err 1>&5 $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_static_works=yes fi else lt_cv_prog_compiler_static_works=yes fi fi $RM -r conftest* LDFLAGS=$save_LDFLAGS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works" >&5 printf '%s\n' "$lt_cv_prog_compiler_static_works" >&6; } if test yes = "$lt_cv_prog_compiler_static_works"; then : else lt_prog_compiler_static= fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 printf %s "checking if $compiler supports -c -o file.$ac_objext... " >&6; } if test ${lt_cv_prog_compiler_c_o+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_c_o=no $RM -r conftest 2>/dev/null mkdir conftest cd conftest mkdir out echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="-o out/conftest2.$ac_objext" # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then lt_cv_prog_compiler_c_o=yes fi fi chmod u+w . 2>&5 $RM conftest* # SGI C++ compiler will create directory out/ii_files/ for # template instantiation test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files $RM out/* && rmdir out cd .. $RM -r conftest $RM conftest* ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 printf '%s\n' "$lt_cv_prog_compiler_c_o" >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 printf %s "checking if $compiler supports -c -o file.$ac_objext... " >&6; } if test ${lt_cv_prog_compiler_c_o+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_c_o=no $RM -r conftest 2>/dev/null mkdir conftest cd conftest mkdir out echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="-o out/conftest2.$ac_objext" # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then lt_cv_prog_compiler_c_o=yes fi fi chmod u+w . 2>&5 $RM conftest* # SGI C++ compiler will create directory out/ii_files/ for # template instantiation test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files $RM out/* && rmdir out cd .. $RM -r conftest $RM conftest* ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 printf '%s\n' "$lt_cv_prog_compiler_c_o" >&6; } hard_links=nottested if test no = "$lt_cv_prog_compiler_c_o" && test no != "$need_locks"; then # do not overwrite the value of need_locks provided by the user { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5 printf %s "checking if we can lock with hard links... " >&6; } hard_links=yes $RM conftest* ln conftest.a conftest.b 2>/dev/null && hard_links=no touch conftest.a ln conftest.a conftest.b 2>&5 || hard_links=no ln conftest.a conftest.b 2>/dev/null && hard_links=no { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5 printf '%s\n' "$hard_links" >&6; } if test no = "$hard_links"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&5 printf '%s\n' "$as_me: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&2;} need_locks=warn fi else need_locks=no fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 printf %s "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } runpath_var= allow_undefined_flag= always_export_symbols=no archive_cmds= archive_expsym_cmds= compiler_needs_object=no enable_shared_with_static_runtimes=no export_dynamic_flag_spec= export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' hardcode_automatic=no hardcode_direct=no hardcode_direct_absolute=no hardcode_libdir_flag_spec= hardcode_libdir_separator= hardcode_minus_L=no hardcode_shlibpath_var=unsupported inherit_rpath=no link_all_deplibs=unknown module_cmds= module_expsym_cmds= old_archive_from_new_cmds= old_archive_from_expsyms_cmds= thread_safe_flag_spec= whole_archive_flag_spec= # include_expsyms should be a list of space-separated symbols to be *always* # included in the symbol list include_expsyms= # exclude_expsyms can be an extended regexp of symbols to exclude # it will be wrapped by ' (' and ')$', so one must not match beginning or # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc', # as well as any symbol that contains 'd'. exclude_expsyms='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out # platforms (ab)use it in PIC code, but their linkers get confused if # the symbol is explicitly referenced. Since portable code cannot # rely on this symbol name, it's probably fine to never include it in # preloaded symbol tables. # Exclude shared library initialization/finalization symbols. extract_expsyms_cmds= case $host_os in cygwin* | mingw* | windows* | pw32* | cegcc*) # FIXME: the MSVC++ and ICC port hasn't been tested in a loooong time # When not using gcc, we currently assume that we are using # Microsoft Visual C++ or Intel C++ Compiler. if test yes != "$GCC"; then with_gnu_ld=no fi ;; interix*) # we just hope/assume this is gcc and not c89 (= MSVC++ or ICC) with_gnu_ld=yes ;; linux* | k*bsd*-gnu | gnu*) link_all_deplibs=no ;; esac ld_shlibs=yes # On some targets, GNU ld is compatible enough with the native linker # that we're better off using the native interface for both. lt_use_gnu_ld_interface=no if test yes = "$with_gnu_ld"; then case $host_os in aix*) # The AIX port of GNU ld has always aspired to compatibility # with the native linker. However, as the warning in the GNU ld # block says, versions before 2.19.5* couldn't really create working # shared libraries, regardless of the interface used. case `$LD -v 2>&1` in *\ \(GNU\ Binutils\)\ 2.19.5*) ;; *\ \(GNU\ Binutils\)\ 2.[2-9]*) ;; *\ \(GNU\ Binutils\)\ [3-9]*) ;; *) lt_use_gnu_ld_interface=yes ;; esac ;; *) lt_use_gnu_ld_interface=yes ;; esac fi if test yes = "$lt_use_gnu_ld_interface"; then # If archive_cmds runs LD, not CC, wlarc should be empty wlarc='$wl' # Set some defaults for GNU ld with shared library support. These # are reset later if shared libraries are not supported. Putting them # here allows them to be overridden if necessary. runpath_var=LD_RUN_PATH hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' export_dynamic_flag_spec='$wl--export-dynamic' # ancient GNU ld didn't support --whole-archive et. al. if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then whole_archive_flag_spec=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' else whole_archive_flag_spec= fi supports_anon_versioning=no case `$LD -v | $SED -e 's/([^)]\+)\s\+//' 2>&1` in *GNU\ gold*) supports_anon_versioning=yes ;; *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11 *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... *\ 2.11.*) ;; # other 2.11 versions *) supports_anon_versioning=yes ;; esac # See if GNU ld supports shared libraries. case $host_os in aix[3-9]*) # On AIX/PPC, the GNU linker is very broken if test ia64 != "$host_cpu"; then ld_shlibs=no cat <<_LT_EOF 1>&2 *** Warning: the GNU linker, at least up to release 2.19, is reported *** to be unable to reliably create shared libraries on AIX. *** Therefore, libtool is disabling shared libraries support. If you *** really care for shared libraries, you may want to install binutils *** 2.20 or above, or modify your PATH so that a non-GNU linker is found. *** You will then need to restart the configuration process. _LT_EOF fi ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='' ;; m68k) archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes ;; esac ;; beos*) if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then allow_undefined_flag=unsupported # Joseph Beckenbach says some releases of gcc # support --undefined. This deserves some investigation. FIXME archive_cmds='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' else ld_shlibs=no fi ;; cygwin* | mingw* | windows* | pw32* | cegcc*) # _LT_TAGVAR(hardcode_libdir_flag_spec, ) is actually meaningless, # as there is no search path for DLLs. hardcode_libdir_flag_spec='-L$libdir' export_dynamic_flag_spec='$wl--export-all-symbols' allow_undefined_flag=unsupported always_export_symbols=no enable_shared_with_static_runtimes=yes export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols' exclude_expsyms='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname' file_list_spec='@' if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' # If the export-symbols file already is a .def file, use it as # is; otherwise, prepend EXPORTS... archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then cp $export_symbols $output_objdir/$soname.def; else echo EXPORTS > $output_objdir/$soname.def; cat $export_symbols >> $output_objdir/$soname.def; fi~ $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' else ld_shlibs=no fi ;; haiku*) archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' link_all_deplibs=no ;; os2*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes allow_undefined_flag=unsupported shrext_cmds=.dll archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ prefix_cmds="$SED"~ if test EXPORTS = "`$SED 1q $export_symbols`"; then prefix_cmds="$prefix_cmds -e 1d"; fi~ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' old_archive_from_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' enable_shared_with_static_runtimes=yes file_list_spec='@' ;; interix[3-9]*) hardcode_direct=no hardcode_shlibpath_var=no hardcode_libdir_flag_spec='$wl-rpath,$libdir' export_dynamic_flag_spec='$wl-E' # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. # Instead, shared libraries are loaded at an image base (0x10000000 by # default) and relocated if they conflict, which is a slow very memory # consuming and fragmenting process. To avoid this, we pick a random, # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link # time. Moving up from 0x10000000 also allows more sbrk(2) space. archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' archive_expsym_cmds='$SED "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' ;; gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) tmp_diet=no if test linux-dietlibc = "$host_os"; then case $cc_basename in diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) esac fi if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ && test no = "$tmp_diet" then tmp_addflag=' $pic_flag' tmp_sharedflag='-shared' case $cc_basename,$host_cpu in pgcc*) # Portland Group C compiler whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' tmp_addflag=' $pic_flag' ;; pgf77* | pgf90* | pgf95* | pgfortran*) # Portland Group f77 and f90 compilers whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' tmp_addflag=' $pic_flag -Mnomain' ;; ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 tmp_addflag=' -i_dynamic' ;; efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 tmp_addflag=' -i_dynamic -nofor_main' ;; ifc* | ifort*) # Intel Fortran compiler tmp_addflag=' -nofor_main' ;; lf95*) # Lahey Fortran 8.1 whole_archive_flag_spec= tmp_sharedflag='--shared' ;; nagfor*) # NAGFOR 5.3 tmp_sharedflag='-Wl,-shared' ;; xl[cC]* | bgxl[cC]* | mpixl[cC]*) # IBM XL C 8.0 on PPC (deal with xlf below) tmp_sharedflag='-qmkshrobj' tmp_addflag= ;; nvcc*) # Cuda Compiler Driver 2.2 whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' compiler_needs_object=yes ;; esac case `$CC -V 2>&1 | $SED 5q` in *Sun\ C*) # Sun C 5.9 whole_archive_flag_spec='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' compiler_needs_object=yes tmp_sharedflag='-G' ;; *Sun\ F*) # Sun Fortran 8.3 tmp_sharedflag='-G' ;; esac archive_cmds='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' if test yes = "$supports_anon_versioning"; then archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ echo "local: *; };" >> $output_objdir/$libname.ver~ $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' fi case $cc_basename in tcc*) hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' export_dynamic_flag_spec='-rdynamic' ;; xlf* | bgf* | bgxlf* | mpixlf*) # IBM XL Fortran 10.1 on PPC cannot create shared libs itself whole_archive_flag_spec='--whole-archive$convenience --no-whole-archive' hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' archive_cmds='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' if test yes = "$supports_anon_versioning"; then archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ echo "local: *; };" >> $output_objdir/$libname.ver~ $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' fi ;; esac else ld_shlibs=no fi ;; *-mlibc) archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; netbsd* | netbsdelf*-gnu) if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then archive_cmds='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' wlarc= else archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' fi ;; solaris*) if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then ld_shlibs=no cat <<_LT_EOF 1>&2 *** Warning: The releases 2.8.* of the GNU linker cannot reliably *** create shared libraries on Solaris systems. Therefore, libtool *** is disabling shared libraries support. We urge you to upgrade GNU *** binutils to release 2.9.1 or newer. Another option is to modify *** your PATH or compiler configuration so that the native linker is *** used, and then restart. _LT_EOF elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' else ld_shlibs=no fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) case `$LD -v 2>&1` in *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) ld_shlibs=no cat <<_LT_EOF 1>&2 *** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot *** reliably create shared libraries on SCO systems. Therefore, libtool *** is disabling shared libraries support. We urge you to upgrade GNU *** binutils to release 2.16.91.0.3 or newer. Another option is to modify *** your PATH or compiler configuration so that the native linker is *** used, and then restart. _LT_EOF ;; *) # For security reasons, it is highly recommended that you always # use absolute paths for naming shared libraries, and exclude the # DT_RUNPATH tag from executables and libraries. But doing so # requires that you compile everything twice, which is a pain. if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' else ld_shlibs=no fi ;; esac ;; sunos4*) archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' wlarc= hardcode_direct=yes hardcode_shlibpath_var=no ;; *) if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' else ld_shlibs=no fi ;; esac if test no = "$ld_shlibs"; then runpath_var= hardcode_libdir_flag_spec= export_dynamic_flag_spec= whole_archive_flag_spec= fi else # PORTME fill in a description of your system's linker (not GNU ld) case $host_os in aix3*) allow_undefined_flag=unsupported always_export_symbols=yes archive_expsym_cmds='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' # Note: this linker hardcodes the directories in LIBPATH if there # are no directories specified by -L. hardcode_minus_L=yes if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then # Neither direct hardcoding nor static linking is supported with a # broken collect2. hardcode_direct=unsupported fi ;; aix[4-9]*) if test ia64 = "$host_cpu"; then # On IA64, the linker does run time linking by default, so we don't # have to do anything special. aix_use_runtimelinking=no exp_sym_flag='-Bexport' no_entry_flag= else # If we're using GNU nm, then we don't want the "-C" option. # -C means demangle to GNU nm, but means don't demangle to AIX nm. # Without the "-l" option, or with the "-B" option, AIX nm treats # weak defined symbols like other global defined symbols, whereas # GNU nm marks them as "W". # While the 'weak' keyword is ignored in the Export File, we need # it in the Import File for the 'aix-soname' feature, so we have # to replace the "-B" option with "-P" for AIX nm. if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then export_symbols_cmds='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' else export_symbols_cmds='`func_echo_all $NM | $SED -e '\''s/B\([^B]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "L") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && (substr(\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' fi aix_use_runtimelinking=no # Test if we are trying to use run time linking or normal # AIX style linking. If -brtl is somewhere in LDFLAGS, we # have runtime linking enabled, and use it for executables. # For shared libraries, we enable/disable runtime linking # depending on the kind of the shared library created - # when "with_aix_soname,aix_use_runtimelinking" is: # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables # "aix,yes" lib.so shared, rtl:yes, for executables # lib.a static archive # "both,no" lib.so.V(shr.o) shared, rtl:yes # lib.a(lib.so.V) shared, rtl:no, for executables # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a(lib.so.V) shared, rtl:no # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a static archive case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) for ld_flag in $LDFLAGS; do if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then aix_use_runtimelinking=yes break fi done if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then # With aix-soname=svr4, we create the lib.so.V shared archives only, # so we don't have lib.a shared libs to link our executables. # We have to force runtime linking in this case. aix_use_runtimelinking=yes LDFLAGS="$LDFLAGS -Wl,-brtl" fi ;; esac exp_sym_flag='-bexport' no_entry_flag='-bnoentry' fi # When large executables or shared objects are built, AIX ld can # have problems creating the table of contents. If linking a library # or program results in "error TOC overflow" add -mminimal-toc to # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. archive_cmds='' hardcode_direct=yes hardcode_direct_absolute=yes hardcode_libdir_separator=':' link_all_deplibs=yes file_list_spec='$wl-f,' case $with_aix_soname,$aix_use_runtimelinking in aix,*) ;; # traditional, no import file svr4,* | *,yes) # use import file # The Import File defines what to hardcode. hardcode_direct=no hardcode_direct_absolute=no ;; esac if test yes = "$GCC"; then case $host_os in aix4.[012]|aix4.[012].*) # We only want to do this on AIX 4.2 and lower, the check # below for broken collect2 doesn't work under 4.3+ collect2name=`$CC -print-prog-name=collect2` if test -f "$collect2name" && strings "$collect2name" | $GREP resolve_lib_name >/dev/null then # We have reworked collect2 : else # We have old collect2 hardcode_direct=unsupported # It fails to find uninstalled libraries when the uninstalled # path is not listed in the libpath. Setting hardcode_minus_L # to unsupported forces relinking hardcode_minus_L=yes hardcode_libdir_flag_spec='-L$libdir' hardcode_libdir_separator= fi ;; esac shared_flag='-shared' if test yes = "$aix_use_runtimelinking"; then shared_flag="$shared_flag "'$wl-G' fi # Need to ensure runtime linking is disabled for the traditional # shared library, or the linker may eventually find shared libraries # /with/ Import File - we do not want to mix them. shared_flag_aix='-shared' shared_flag_svr4='-shared $wl-G' else # not using gcc if test ia64 = "$host_cpu"; then # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release # chokes on -Wl,-G. The following line is correct: shared_flag='-G' else if test yes = "$aix_use_runtimelinking"; then shared_flag='$wl-G' else shared_flag='$wl-bM:SRE' fi shared_flag_aix='$wl-bM:SRE' shared_flag_svr4='$wl-G' fi fi export_dynamic_flag_spec='$wl-bexpall' # It seems that -bexpall does not export symbols beginning with # underscore (_), so it is better to generate a list of symbols to export. always_export_symbols=yes if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then # Warning - without using the other runtime loading flags (-brtl), # -berok will link without error, but may produce a broken library. allow_undefined_flag='-berok' # Determine the default libpath from the value encoded in an # empty executable. if test set = "${lt_cv_aix_libpath+set}"; then aix_libpath=$lt_cv_aix_libpath else if test ${lt_cv_aix_libpath_+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : lt_aix_libpath_sed=' /Import File Strings/,/^$/ { /^0/ { s/^0 *\([^ ]*\) *$/\1/ p } }' lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` # Check for a 64-bit object if we didn't find anything. if test -z "$lt_cv_aix_libpath_"; then lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` fi fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext if test -z "$lt_cv_aix_libpath_"; then lt_cv_aix_libpath_=/usr/lib:/lib fi ;; esac fi aix_libpath=$lt_cv_aix_libpath_ fi hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath" archive_expsym_cmds='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag else if test ia64 = "$host_cpu"; then hardcode_libdir_flag_spec='$wl-R $libdir:/usr/lib:/lib' allow_undefined_flag="-z nodefs" archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" else # Determine the default libpath from the value encoded in an # empty executable. if test set = "${lt_cv_aix_libpath+set}"; then aix_libpath=$lt_cv_aix_libpath else if test ${lt_cv_aix_libpath_+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : lt_aix_libpath_sed=' /Import File Strings/,/^$/ { /^0/ { s/^0 *\([^ ]*\) *$/\1/ p } }' lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` # Check for a 64-bit object if we didn't find anything. if test -z "$lt_cv_aix_libpath_"; then lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` fi fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext if test -z "$lt_cv_aix_libpath_"; then lt_cv_aix_libpath_=/usr/lib:/lib fi ;; esac fi aix_libpath=$lt_cv_aix_libpath_ fi hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath" # Warning - without using the other run time loading flags, # -berok will link without error, but may produce a broken library. no_undefined_flag=' $wl-bernotok' allow_undefined_flag=' $wl-berok' if test yes = "$with_gnu_ld"; then # We only use this code for GNU lds that support --whole-archive. whole_archive_flag_spec='$wl--whole-archive$convenience $wl--no-whole-archive' else # Exported symbols can be pulled into shared objects from archives whole_archive_flag_spec='$convenience' fi archive_cmds_need_lc=yes archive_expsym_cmds='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' # -brtl affects multiple linker settings, -berok does not and is overridden later compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([, ]\\)%-berok\\1%g"`' if test svr4 != "$with_aix_soname"; then # This is similar to how AIX traditionally builds its shared libraries. archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' fi if test aix != "$with_aix_soname"; then archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' else # used by -dlpreopen to get the symbols archive_expsym_cmds="$archive_expsym_cmds"'~$MV $output_objdir/$realname.d/$soname $output_objdir' fi archive_expsym_cmds="$archive_expsym_cmds"'~$RM -r $output_objdir/$realname.d' fi fi ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='' ;; m68k) archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes ;; esac ;; bsdi[45]*) export_dynamic_flag_spec=-rdynamic ;; cygwin* | mingw* | windows* | pw32* | cegcc*) # When not using gcc, we currently assume that we are using # Microsoft Visual C++ or Intel C++ Compiler. # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. case $cc_basename in cl* | icl*) # Native MSVC or ICC hardcode_libdir_flag_spec=' ' allow_undefined_flag=unsupported always_export_symbols=yes file_list_spec='@' # Tell ltmain to make .lib files, not .a files. libext=lib # Tell ltmain to make .dll files, not .so files. shrext_cmds=.dll # FIXME: Setting linknames here is a bad hack. archive_cmds='$CC -Fe$output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then cp "$export_symbols" "$output_objdir/$soname.def"; echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; else $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; fi~ $CC -Fe$tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ linknames=' # The linker will not automatically build a static lib if we build a DLL. # _LT_TAGVAR(old_archive_from_new_cmds, )='true' enable_shared_with_static_runtimes=yes exclude_expsyms='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1,DATA/'\'' | $SED -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols' # Don't use ranlib old_postinstall_cmds='chmod 644 $oldlib' postlink_cmds='lt_outputfile="@OUTPUT@"~ lt_tool_outputfile="@TOOL_OUTPUT@"~ case $lt_outputfile in *.exe|*.EXE) ;; *) lt_outputfile=$lt_outputfile.exe lt_tool_outputfile=$lt_tool_outputfile.exe ;; esac~ if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; $RM "$lt_outputfile.manifest"; fi' ;; *) # Assume MSVC and ICC wrapper hardcode_libdir_flag_spec=' ' allow_undefined_flag=unsupported # Tell ltmain to make .lib files, not .a files. libext=lib # Tell ltmain to make .dll files, not .so files. shrext_cmds=.dll # FIXME: Setting linknames here is a bad hack. archive_cmds='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' # The linker will automatically build a .lib file if we build a DLL. old_archive_from_new_cmds='true' # FIXME: Should let the user specify the lib program. old_archive_cmds='lib -OUT:$oldlib$oldobjs$old_deplibs' enable_shared_with_static_runtimes=yes ;; esac ;; darwin* | rhapsody*) archive_cmds_need_lc=no hardcode_direct=no hardcode_automatic=yes hardcode_shlibpath_var=unsupported if test yes = "$lt_cv_ld_force_load"; then whole_archive_flag_spec='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' else whole_archive_flag_spec='' fi link_all_deplibs=yes allow_undefined_flag=$_lt_dar_allow_undefined case $cc_basename in ifort*|nagfor*) _lt_dar_can_shared=yes ;; *) _lt_dar_can_shared=$GCC ;; esac if test yes = "$_lt_dar_can_shared"; then output_verbose_link_cmd=func_echo_all archive_cmds="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" module_cmds="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" archive_expsym_cmds="$SED 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" module_expsym_cmds="$SED -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" else ld_shlibs=no fi ;; dgux*) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_libdir_flag_spec='-L$libdir' hardcode_shlibpath_var=no ;; # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor # support. Future versions do this automatically, but an explicit c++rt0.o # does not break anything, and helps significantly (at the cost of a little # extra space). freebsd2.2*) archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes hardcode_shlibpath_var=no ;; # Unfortunately, older versions of FreeBSD 2 do not have this feature. freebsd2.*) archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' hardcode_direct=yes hardcode_minus_L=yes hardcode_shlibpath_var=no ;; # FreeBSD 3 and greater uses gcc -shared to do shared libraries. freebsd* | dragonfly* | midnightbsd*) archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes hardcode_shlibpath_var=no ;; hpux9*) if test yes = "$GCC"; then archive_cmds='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' else archive_cmds='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' fi hardcode_libdir_flag_spec='$wl+b $wl$libdir' hardcode_libdir_separator=: hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes export_dynamic_flag_spec='$wl-E' ;; hpux10*) if test yes,no = "$GCC,$with_gnu_ld"; then archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' else archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' fi if test no = "$with_gnu_ld"; then hardcode_libdir_flag_spec='$wl+b $wl$libdir' hardcode_libdir_separator=: hardcode_direct=yes hardcode_direct_absolute=yes export_dynamic_flag_spec='$wl-E' # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes fi ;; hpux11*) if test yes,no = "$GCC,$with_gnu_ld"; then case $host_cpu in hppa*64*) archive_cmds='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' ;; ia64*) archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' ;; *) archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' ;; esac else case $host_cpu in hppa*64*) archive_cmds='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' ;; ia64*) archive_cmds='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' ;; *) # Older versions of the 11.00 compiler do not understand -b yet # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if $CC understands -b" >&5 printf %s "checking if $CC understands -b... " >&6; } if test ${lt_cv_prog_compiler__b+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler__b=no save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -b" echo "$lt_simple_link_test_code" > conftest.$ac_ext if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then # The linker can only warn and ignore the option if not recognized # So say no if there are warnings if test -s conftest.err; then # Append any errors to the config.log. cat conftest.err 1>&5 $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler__b=yes fi else lt_cv_prog_compiler__b=yes fi fi $RM -r conftest* LDFLAGS=$save_LDFLAGS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler__b" >&5 printf '%s\n' "$lt_cv_prog_compiler__b" >&6; } if test yes = "$lt_cv_prog_compiler__b"; then archive_cmds='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' else archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' fi ;; esac fi if test no = "$with_gnu_ld"; then hardcode_libdir_flag_spec='$wl+b $wl$libdir' hardcode_libdir_separator=: case $host_cpu in hppa*64*|ia64*) hardcode_direct=no hardcode_shlibpath_var=no ;; *) hardcode_direct=yes hardcode_direct_absolute=yes export_dynamic_flag_spec='$wl-E' # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes ;; esac fi ;; irix5* | irix6* | nonstopux*) if test yes = "$GCC"; then archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' # Try to use the -exported_symbol ld option, if it does not # work, assume that -exports_file does not work either and # implicitly export all symbols. # This should be the same for all languages, so no per-tag cache variable. { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the $host_os linker accepts -exported_symbol" >&5 printf %s "checking whether the $host_os linker accepts -exported_symbol... " >&6; } if test ${lt_cv_irix_exported_symbol+y} then : printf %s "(cached) " >&6 else case e in #( e) save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int foo (void) { return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : lt_cv_irix_exported_symbol=yes else case e in #( e) lt_cv_irix_exported_symbol=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LDFLAGS=$save_LDFLAGS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_irix_exported_symbol" >&5 printf '%s\n' "$lt_cv_irix_exported_symbol" >&6; } if test yes = "$lt_cv_irix_exported_symbol"; then archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib' fi link_all_deplibs=no else archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib' fi archive_cmds_need_lc='no' hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' hardcode_libdir_separator=: inherit_rpath=yes link_all_deplibs=yes ;; linux*) case $cc_basename in tcc*) # Fabrice Bellard et al's Tiny C Compiler ld_shlibs=yes archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' ;; esac ;; *-mlibc) ;; netbsd* | netbsdelf*-gnu) if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out else archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF fi hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes hardcode_shlibpath_var=no ;; newsos6) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_direct=yes hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' hardcode_libdir_separator=: hardcode_shlibpath_var=no ;; *nto* | *qnx*) ;; openbsd*) if test -f /usr/libexec/ld.so; then hardcode_direct=yes hardcode_shlibpath_var=no hardcode_direct_absolute=yes if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols' hardcode_libdir_flag_spec='$wl-rpath,$libdir' export_dynamic_flag_spec='$wl-E' else archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' hardcode_libdir_flag_spec='$wl-rpath,$libdir' fi else ld_shlibs=no fi ;; os2*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes allow_undefined_flag=unsupported shrext_cmds=.dll archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ prefix_cmds="$SED"~ if test EXPORTS = "`$SED 1q $export_symbols`"; then prefix_cmds="$prefix_cmds -e 1d"; fi~ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' old_archive_from_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' enable_shared_with_static_runtimes=yes file_list_spec='@' ;; osf3*) if test yes = "$GCC"; then allow_undefined_flag=' $wl-expect_unresolved $wl\*' archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' else allow_undefined_flag=' -expect_unresolved \*' archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' fi archive_cmds_need_lc='no' hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' hardcode_libdir_separator=: ;; osf4* | osf5*) # as osf3* with the addition of -msym flag if test yes = "$GCC"; then allow_undefined_flag=' $wl-expect_unresolved $wl\*' archive_cmds='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' else allow_undefined_flag=' -expect_unresolved \*' archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' archive_expsym_cmds='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp' # Both c and cxx compiler support -rpath directly hardcode_libdir_flag_spec='-rpath $libdir' fi archive_cmds_need_lc='no' hardcode_libdir_separator=: ;; serenity*) ;; solaris*) no_undefined_flag=' -z defs' if test yes = "$GCC"; then wlarc='$wl' archive_cmds='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' else case `$CC -V 2>&1` in *"Compilers 5.0"*) wlarc='' archive_cmds='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags' archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' ;; *) wlarc='$wl' archive_cmds='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' ;; esac fi hardcode_libdir_flag_spec='-R$libdir' hardcode_shlibpath_var=no case $host_os in solaris2.[0-5] | solaris2.[0-5].*) ;; *) # The compiler driver will combine and reorder linker options, # but understands '-z linker_flag'. GCC discards it without '$wl', # but is careful enough not to reorder. # Supported since Solaris 2.6 (maybe 2.5.1?) if test yes = "$GCC"; then whole_archive_flag_spec='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' else whole_archive_flag_spec='-z allextract$convenience -z defaultextract' fi ;; esac link_all_deplibs=yes ;; sunos4*) if test sequent = "$host_vendor"; then # Use $CC to link under sequent, because it throws in some extra .o # files that make .init and .fini sections work. archive_cmds='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags' else archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' fi hardcode_libdir_flag_spec='-L$libdir' hardcode_direct=yes hardcode_minus_L=yes hardcode_shlibpath_var=no ;; sysv4) case $host_vendor in sni) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_direct=yes # is this really true??? ;; siemens) ## LD is ld it makes a PLAMLIB ## CC just makes a GrossModule. archive_cmds='$LD -G -o $lib $libobjs $deplibs $linker_flags' reload_cmds='$CC -r -o $output$reload_objs' hardcode_direct=no ;; motorola) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_direct=no #Motorola manual says yes, but my tests say they lie ;; esac runpath_var='LD_RUN_PATH' hardcode_shlibpath_var=no ;; sysv4.3*) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_shlibpath_var=no export_dynamic_flag_spec='-Bexport' ;; sysv4*MP*) if test -d /usr/nec; then archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_shlibpath_var=no runpath_var=LD_RUN_PATH hardcode_runpath_var=yes ld_shlibs=yes fi ;; sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) no_undefined_flag='$wl-z,text' archive_cmds_need_lc=no hardcode_shlibpath_var=no runpath_var='LD_RUN_PATH' if test yes = "$GCC"; then archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' else archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' fi ;; sysv5* | sco3.2v5* | sco5v6*) # Note: We CANNOT use -z defs as we might desire, because we do not # link with -lc, and that would cause any symbols used from libc to # always be unresolved, which means just about no library would # ever link correctly. If we're not using GNU ld we use -z text # though, which does catch some bad symbols but isn't as heavy-handed # as -z defs. no_undefined_flag='$wl-z,text' allow_undefined_flag='$wl-z,nodefs' archive_cmds_need_lc=no hardcode_shlibpath_var=no hardcode_libdir_flag_spec='$wl-R,$libdir' hardcode_libdir_separator=':' link_all_deplibs=yes export_dynamic_flag_spec='$wl-Bexport' runpath_var='LD_RUN_PATH' if test yes = "$GCC"; then archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' else archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' fi ;; uts4*) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_libdir_flag_spec='-L$libdir' hardcode_shlibpath_var=no ;; *) ld_shlibs=no ;; esac if test sni = "$host_vendor"; then case $host in sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) export_dynamic_flag_spec='$wl-Blargedynsym' ;; esac fi fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs" >&5 printf '%s\n' "$ld_shlibs" >&6; } test no = "$ld_shlibs" && can_build_shared=no with_gnu_ld=$with_gnu_ld # # Do we need to explicitly link libc? # case "x$archive_cmds_need_lc" in x|xyes) # Assume -lc should be added archive_cmds_need_lc=yes if test yes,yes = "$GCC,$enable_shared"; then case $archive_cmds in *'~'*) # FIXME: we may have to deal with multi-command sequences. ;; '$CC '*) # Test whether the compiler implicitly links with -lc since on some # systems, -lgcc has to come before -lc. If gcc already passes -lc # to ld, don't add -lc before -lgcc. { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5 printf %s "checking whether -lc should be explicitly linked in... " >&6; } if test ${lt_cv_archive_cmds_need_lc+y} then : printf %s "(cached) " >&6 else case e in #( e) $RM conftest* echo "$lt_simple_compile_test_code" > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } 2>conftest.err; then soname=conftest lib=conftest libobjs=conftest.$ac_objext deplibs= wl=$lt_prog_compiler_wl pic_flag=$lt_prog_compiler_pic compiler_flags=-v linker_flags=-v verstring= output_objdir=. libname=conftest lt_save_allow_undefined_flag=$allow_undefined_flag allow_undefined_flag= if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5 (eval $archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then lt_cv_archive_cmds_need_lc=no else lt_cv_archive_cmds_need_lc=yes fi allow_undefined_flag=$lt_save_allow_undefined_flag else cat conftest.err 1>&5 fi $RM conftest* ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc" >&5 printf '%s\n' "$lt_cv_archive_cmds_need_lc" >&6; } archive_cmds_need_lc=$lt_cv_archive_cmds_need_lc ;; esac fi ;; esac { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5 printf %s "checking dynamic linker characteristics... " >&6; } if test yes = "$GCC"; then case $host_os in darwin*) lt_awk_arg='/^libraries:/,/LR/' ;; *) lt_awk_arg='/^libraries:/' ;; esac case $host_os in mingw* | windows* | cegcc*) lt_sed_strip_eq='s|=\([A-Za-z]:\)|\1|g' ;; *) lt_sed_strip_eq='s|=/|/|g' ;; esac lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` case $lt_search_path_spec in *\;*) # if the path contains ";" then we assume it to be the separator # otherwise default to the standard path separator (i.e. ":") - it is # assumed that no part of a normal pathname contains ";" but that should # okay in the real world where ";" in dirpaths is itself problematic. lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` ;; *) lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` ;; esac # Ok, now we have the path, separated by spaces, we can step through it # and add multilib dir if necessary... lt_tmp_lt_search_path_spec= lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` # ...but if some path component already ends with the multilib dir we assume # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer). case "$lt_multi_os_dir; $lt_search_path_spec " in "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*) lt_multi_os_dir= ;; esac for lt_sys_path in $lt_search_path_spec; do if test -d "$lt_sys_path$lt_multi_os_dir"; then lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir" elif test -n "$lt_multi_os_dir"; then test -d "$lt_sys_path" && \ lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" fi done lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' BEGIN {RS = " "; FS = "/|\n";} { lt_foo = ""; lt_count = 0; for (lt_i = NF; lt_i > 0; lt_i--) { if ($lt_i != "" && $lt_i != ".") { if ($lt_i == "..") { lt_count++; } else { if (lt_count == 0) { lt_foo = "/" $lt_i lt_foo; } else { lt_count--; } } } } if (lt_foo != "") { lt_freq[lt_foo]++; } if (lt_freq[lt_foo] == 1) { print lt_foo; } }'` # AWK program above erroneously prepends '/' to C:/dos/paths # for these hosts. case $host_os in mingw* | windows* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ $SED 's|/\([A-Za-z]:\)|\1|g'` ;; esac sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` else sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" fi library_names_spec= libname_spec='lib$name' soname_spec= shrext_cmds=.so postinstall_cmds= postuninstall_cmds= finish_cmds= finish_eval= shlibpath_var= shlibpath_overrides_runpath=unknown version_type=none dynamic_linker="$host_os ld.so" sys_lib_dlsearch_path_spec="/lib /usr/lib" need_lib_prefix=unknown hardcode_into_libs=no # when you set need_version to no, make sure it does not cause -set_version # flags to be left without arguments need_version=unknown case $host_os in aix3*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname.a' shlibpath_var=LIBPATH # AIX 3 has no versioning support, so we append a major version to the name. soname_spec='$libname$release$shared_ext$major' ;; aix[4-9]*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no hardcode_into_libs=yes if test ia64 = "$host_cpu"; then # AIX 5 supports IA64 library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH else # With GCC up to 2.95.x, collect2 would create an import file # for dependence libraries. The import file would start with # the line '#! .'. This would cause the generated library to # depend on '.', always an invalid library. This was fixed in # development snapshots of GCC prior to 3.0. case $host_os in aix4 | aix4.[01] | aix4.[01].*) if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' echo ' yes ' echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then : else can_build_shared=no fi ;; esac # Using Import Files as archive members, it is possible to support # filename-based versioning of shared library archives on AIX. While # this would work for both with and without runtime linking, it will # prevent static linking of such archives. So we do filename-based # shared library versioning with .so extension only, which is used # when both runtime linking and shared linking is enabled. # Unfortunately, runtime linking may impact performance, so we do # not want this to be the default eventually. Also, we use the # versioned .so libs for executables only if there is the -brtl # linker flag in LDFLAGS as well, or --enable-aix-soname=svr4 only. # To allow for filename-based versioning support, we need to create # libNAME.so.V as an archive file, containing: # *) an Import File, referring to the versioned filename of the # archive as well as the shared archive member, telling the # bitwidth (32 or 64) of that shared object, and providing the # list of exported symbols of that shared object, eventually # decorated with the 'weak' keyword # *) the shared object with the F_LOADONLY flag set, to really avoid # it being seen by the linker. # At run time we better use the real file rather than another symlink, # but for link time we create the symlink libNAME.so -> libNAME.so.V case $with_aix_soname,$aix_use_runtimelinking in # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct # soname into executable. Probably we can add versioning support to # collect2, so additional links can be useful in future. aix,yes) # traditional libtool dynamic_linker='AIX unversionable lib.so' # If using run time linking (on AIX 4.2 or later) use lib.so # instead of lib.a to let people know that these are not # typical AIX shared libraries. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' ;; aix,no) # traditional AIX only dynamic_linker='AIX lib.a(lib.so.V)' # We preserve .a as extension for shared libraries through AIX4.2 # and later when we are not doing run time linking. library_names_spec='$libname$release.a $libname.a' soname_spec='$libname$release$shared_ext$major' ;; svr4,*) # full svr4 only dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o)" library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' # We do not specify a path in Import Files, so LIBPATH fires. shlibpath_overrides_runpath=yes ;; *,yes) # both, prefer svr4 dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o), lib.a(lib.so.V)" library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' # unpreferred sharedlib libNAME.a needs extra handling postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' # We do not specify a path in Import Files, so LIBPATH fires. shlibpath_overrides_runpath=yes ;; *,no) # both, prefer aix dynamic_linker="AIX lib.a(lib.so.V), lib.so.V($shared_archive_member_spec.o)" library_names_spec='$libname$release.a $libname.a' soname_spec='$libname$release$shared_ext$major' # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' ;; esac shlibpath_var=LIBPATH fi ;; amigaos*) case $host_cpu in powerpc) # Since July 2007 AmigaOS4 officially supports .so libraries. # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' ;; m68k) library_names_spec='$libname.ixlibrary $libname.a' # Create ${libname}_ixlibrary.a entries in /sys/libs. finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' ;; esac ;; beos*) library_names_spec='$libname$shared_ext' dynamic_linker="$host_os ld.so" shlibpath_var=LIBRARY_PATH ;; bsdi[45]*) version_type=linux # correct to gnu/linux during the next big refactor need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' shlibpath_var=LD_LIBRARY_PATH sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" # the default ld.so.conf also contains /usr/contrib/lib and # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow # libtool to hard-code these into programs ;; cygwin* | mingw* | windows* | pw32* | cegcc*) version_type=windows shrext_cmds=.dll need_version=no need_lib_prefix=no case $GCC,$cc_basename in yes,*) # gcc library_names_spec='$libname.dll.a' # DLL is installed to $(libdir)/../bin by postinstall_cmds # If user builds GCC with multilib enabled, # it should just install on $(libdir) # not on $(libdir)/../bin or 32 bits dlls would override 64 bit ones. if test xyes = x"$multilib"; then postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ $install_prog $dir/$dlname $destdir/$dlname~ chmod a+x $destdir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib $destdir/$dlname'\'' || exit \$?; fi' else postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname~ chmod a+x \$dldir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; fi' fi postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' shlibpath_overrides_runpath=yes case $host_os in cygwin*) # Cygwin DLLs use 'cyg' prefix rather than 'lib' soname_spec='`echo $libname | $SED -e 's/^lib/cyg/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api" ;; mingw* | windows* | cegcc*) # MinGW DLLs use traditional 'lib' prefix soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' ;; pw32*) # pw32 DLLs use 'pw' prefix rather than 'lib' library_names_spec='`echo $libname | $SED -e 's/^lib/pw/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' ;; esac dynamic_linker='Win32 ld.exe' ;; *,cl* | *,icl*) # Native MSVC or ICC libname_spec='$name' soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' library_names_spec='$libname.dll.lib' case $build_os in mingw* | windows*) sys_lib_search_path_spec= lt_save_ifs=$IFS IFS=';' for lt_path in $LIB do IFS=$lt_save_ifs # Let DOS variable expansion print the short 8.3 style file name. lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" done IFS=$lt_save_ifs # Convert to MSYS style. sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'` ;; cygwin*) # Convert to unix form, then to dos form, then back to unix form # but this time dos style (no spaces!) so that the unix form looks # like /cygdrive/c/PROGRA~1:/cygdr... sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` ;; *) sys_lib_search_path_spec=$LIB if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then # It is most probably a Windows format PATH. sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` else sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` fi # FIXME: find the short name or the path components, as spaces are # common. (e.g. "Program Files" -> "PROGRA~1") ;; esac # DLL is installed to $(libdir)/../bin by postinstall_cmds postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' shlibpath_overrides_runpath=yes dynamic_linker='Win32 link.exe' ;; *) # Assume MSVC and ICC wrapper library_names_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext $libname.lib' dynamic_linker='Win32 ld.exe' ;; esac # FIXME: first we should search . and the directory the executable is in shlibpath_var=PATH ;; darwin* | rhapsody*) dynamic_linker="$host_os dyld" version_type=darwin need_lib_prefix=no need_version=no library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' soname_spec='$libname$release$major$shared_ext' shlibpath_overrides_runpath=yes shlibpath_var=DYLD_LIBRARY_PATH shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib" sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' ;; dgux*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH ;; freebsd* | dragonfly* | midnightbsd*) # DragonFly does not have aout. When/if they implement a new # versioning mechanism, adjust this. if test -x /usr/bin/objformat; then objformat=`/usr/bin/objformat` else case $host_os in freebsd[23].*) objformat=aout ;; *) objformat=elf ;; esac fi version_type=freebsd-$objformat case $version_type in freebsd-elf*) library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' need_version=no need_lib_prefix=no ;; freebsd-*) library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' need_version=yes ;; esac case $host_cpu in powerpc64) # On FreeBSD bi-arch platforms, a different variable is used for 32-bit # binaries. See . cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int test_pointer_size[sizeof (void *) - 5]; _ACEOF if ac_fn_c_try_compile "$LINENO" then : shlibpath_var=LD_LIBRARY_PATH else case e in #( e) shlibpath_var=LD_32_LIBRARY_PATH ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; *) shlibpath_var=LD_LIBRARY_PATH ;; esac case $host_os in freebsd2.*) shlibpath_overrides_runpath=yes ;; freebsd3.[01]* | freebsdelf3.[01]*) shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; *) # from 4.6 on, and DragonFly shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; esac ;; haiku*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no dynamic_linker="$host_os runtime_loader" library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LIBRARY_PATH shlibpath_overrides_runpath=no sys_lib_search_path_spec='/boot/system/non-packaged/develop/lib /boot/system/develop/lib' sys_lib_dlsearch_path_spec='/boot/home/config/non-packaged/lib /boot/home/config/lib /boot/system/non-packaged/lib /boot/system/lib' hardcode_into_libs=no ;; hpux9* | hpux10* | hpux11*) # Give a soname corresponding to the major version so that dld.sl refuses to # link against other versions. version_type=sunos need_lib_prefix=no need_version=no case $host_cpu in ia64*) shrext_cmds='.so' hardcode_into_libs=yes dynamic_linker="$host_os dld.so" shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' if test 32 = "$HPUX_IA64_MODE"; then sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" sys_lib_dlsearch_path_spec=/usr/lib/hpux32 else sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" sys_lib_dlsearch_path_spec=/usr/lib/hpux64 fi ;; hppa*64*) shrext_cmds='.sl' hardcode_into_libs=yes dynamic_linker="$host_os dld.sl" shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec ;; *) shrext_cmds='.sl' dynamic_linker="$host_os dld.sl" shlibpath_var=SHLIB_PATH shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' ;; esac # HP-UX runs *really* slowly unless shared libraries are mode 555, ... postinstall_cmds='chmod 555 $lib' # or fails outright, so override atomically: install_override_mode=555 ;; interix[3-9]*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; irix5* | irix6* | nonstopux*) case $host_os in nonstopux*) version_type=nonstopux ;; *) if test yes = "$lt_cv_prog_gnu_ld"; then version_type=linux # correct to gnu/linux during the next big refactor else version_type=irix fi ;; esac need_lib_prefix=no need_version=no soname_spec='$libname$release$shared_ext$major' library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' case $host_os in irix5* | nonstopux*) libsuff= shlibsuff= ;; *) case $LD in # libtool.m4 will add one of these switches to LD *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= libmagic=32-bit;; *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 libmagic=N32;; *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 libmagic=64-bit;; *) libsuff= shlibsuff= libmagic=never-match;; esac ;; esac shlibpath_var=LD_LIBRARY${shlibsuff}_PATH shlibpath_overrides_runpath=no sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" hardcode_into_libs=yes ;; # No shared lib support for Linux oldld, aout, or coff. linux*oldld* | linux*aout* | linux*coff*) dynamic_linker=no ;; linux*android*) version_type=none # Android doesn't support versioned libraries. need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext $libname$shared_ext' soname_spec='$libname$release$shared_ext' finish_cmds= shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes # This implies no fast_install, which is unacceptable. # Some rework will be needed to allow for fast_install # before this can be enabled. hardcode_into_libs=yes dynamic_linker='Android linker' # -rpath works at least for libraries that are not overridden by # libraries installed in system locations. hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' ;; # This must be glibc/ELF. linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no # Some binutils ld are patched to set DT_RUNPATH if test ${lt_cv_shlibpath_overrides_runpath+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_shlibpath_overrides_runpath=no save_LDFLAGS=$LDFLAGS save_libdir=$libdir eval "libdir=/foo; wl=\"$lt_prog_compiler_wl\"; \ LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec\"" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null then : lt_cv_shlibpath_overrides_runpath=yes fi fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LDFLAGS=$save_LDFLAGS libdir=$save_libdir ;; esac fi shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath # This implies no fast_install, which is unacceptable. # Some rework will be needed to allow for fast_install # before this can be enabled. hardcode_into_libs=yes # Ideally, we could use ldconfig to report *all* directories which are # searched for libraries, however this is still not possible. Aside from not # being certain /sbin/ldconfig is available, command # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, # even though it is searched at run-time. Try to do the best guess by # appending ld.so.conf contents (and includes) to the search path. if test -f /etc/ld.so.conf; then lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" fi # We used to test for /lib/ld.so.1 and disable shared libraries on # powerpc, because MkLinux only supported shared libraries with the # GNU dynamic linker. Since this was broken with cross compilers, # most powerpc-linux boxes support dynamic linking these days and # people can always --disable-shared, the test was removed, and we # assume the GNU/Linux dynamic linker is in use. dynamic_linker='GNU/Linux ld.so' ;; netbsdelf*-gnu) version_type=linux need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='NetBSD ld.elf_so' ;; netbsdelf*-gnu) version_type=linux need_lib_prefix=no need_version=no library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' soname_spec='${libname}${release}${shared_ext}$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='NetBSD ld.elf_so' ;; netbsd*) version_type=sunos need_lib_prefix=no need_version=no if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' dynamic_linker='NetBSD (a.out) ld.so' else library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='NetBSD ld.elf_so' fi shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; *-mlibc) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='mlibc ld.so' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; newsos6) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes ;; *nto* | *qnx*) version_type=qnx need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='ldqnx.so' ;; openbsd*) version_type=sunos sys_lib_dlsearch_path_spec=/usr/lib need_lib_prefix=no if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then need_version=no else need_version=yes fi library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes ;; os2*) libname_spec='$name' version_type=windows shrext_cmds=.dll need_version=no need_lib_prefix=no # OS/2 can only load a DLL with a base name of 8 characters or less. soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; v=$($ECHO $release$versuffix | tr -d .-); n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); $ECHO $n$v`$shared_ext' library_names_spec='${libname}_dll.$libext' dynamic_linker='OS/2 ld.exe' shlibpath_var=BEGINLIBPATH sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname~ chmod a+x \$dldir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; fi' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' ;; osf3* | osf4* | osf5*) version_type=osf need_lib_prefix=no need_version=no soname_spec='$libname$release$shared_ext$major' library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec ;; rdos*) dynamic_linker=no ;; serenity*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no dynamic_linker='SerenityOS LibELF' ;; solaris*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes # ldd complains unless libraries are executable postinstall_cmds='chmod +x $lib' ;; sunos4*) version_type=sunos library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes if test yes = "$with_gnu_ld"; then need_lib_prefix=no fi need_version=yes ;; sysv4 | sysv4.3*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH case $host_vendor in sni) shlibpath_overrides_runpath=no need_lib_prefix=no runpath_var=LD_RUN_PATH ;; siemens) need_lib_prefix=no ;; motorola) need_lib_prefix=no need_version=no shlibpath_overrides_runpath=no sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' ;; esac ;; sysv4*MP*) if test -d /usr/nec; then version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' soname_spec='$libname$shared_ext.$major' shlibpath_var=LD_LIBRARY_PATH fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) version_type=sco need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes if test yes = "$with_gnu_ld"; then sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' else sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' case $host_os in sco3.2v5*) sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" ;; esac fi sys_lib_dlsearch_path_spec='/usr/lib' ;; tpf*) # TPF is a cross-target only. Preferred cross-host = GNU/Linux. version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; uts4*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH ;; emscripten*) version_type=none need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext' soname_spec='$libname$release$shared_ext' finish_cmds= dynamic_linker="Emscripten linker" lt_prog_compiler_wl= lt_prog_compiler_pic= lt_prog_compiler_static= if test yes = "$GCC"; then lt_prog_compiler_wl='-Wl,' lt_prog_compiler_static='-static' case $host_os in aix*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static='-Bstatic' fi lt_prog_compiler_pic='-fPIC' ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support lt_prog_compiler_pic='-fPIC' ;; m68k) # FIXME: we need at least 68020 code to build shared libraries, but # adding the '-m68020' flag to GCC prevents building anything better, # like '-m68040'. lt_prog_compiler_pic='-m68020 -resident32 -malways-restore-a4' ;; esac ;; beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) # PIC is the default for these OSes. ;; mingw* | windows* | cygwin* | pw32* | os2* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). # Although the cygwin gcc ignores -fPIC, still need this for old-style # (--disable-auto-import) libraries lt_prog_compiler_pic='-DDLL_EXPORT' case $host_os in os2*) lt_prog_compiler_static='$wl-static' ;; esac ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files lt_prog_compiler_pic='-fno-common' ;; haiku*) # PIC is the default for Haiku. # The "-static" flag exists, but is broken. lt_prog_compiler_static= ;; hpux*) # PIC is the default for 64-bit PA HP-UX, but not for 32-bit # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag # sets the default TLS model and affects inlining. case $host_cpu in hppa*64*) # +Z the default ;; *) lt_prog_compiler_pic='-fPIC' ;; esac ;; interix[3-9]*) # Interix 3.x gcc -fpic/-fPIC options generate broken code. # Instead, we relocate shared libraries at runtime. ;; msdosdjgpp*) # Just because we use GCC doesn't mean we suddenly get shared libraries # on systems that don't support them. lt_prog_compiler_can_build_shared=no enable_shared=no ;; *nto* | *qnx*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic='-fPIC -shared' ;; sysv4*MP*) if test -d /usr/nec; then lt_prog_compiler_pic=-Kconform_pic fi ;; *) lt_prog_compiler_pic='-fPIC' ;; esac case $cc_basename in nvcc*) # Cuda Compiler Driver 2.2 lt_prog_compiler_wl='-Xlinker ' if test -n "$lt_prog_compiler_pic"; then lt_prog_compiler_pic="-Xcompiler $lt_prog_compiler_pic" fi ;; esac else # PORTME Check for flag to pass linker flags through the system compiler. case $host_os in aix*) lt_prog_compiler_wl='-Wl,' if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static='-Bstatic' else lt_prog_compiler_static='-bnso -bI:/lib/syscalls.exp' fi ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files lt_prog_compiler_pic='-fno-common' case $cc_basename in nagfor*) # NAG Fortran compiler lt_prog_compiler_wl='-Wl,-Wl,,' lt_prog_compiler_pic='-PIC' lt_prog_compiler_static='-Bstatic' ;; esac ;; mingw* | windows* | cygwin* | pw32* | os2* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). lt_prog_compiler_pic='-DDLL_EXPORT' case $host_os in os2*) lt_prog_compiler_static='$wl-static' ;; esac ;; hpux9* | hpux10* | hpux11*) lt_prog_compiler_wl='-Wl,' # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but # not for PA HP-UX. case $host_cpu in hppa*64*|ia64*) # +Z the default ;; *) lt_prog_compiler_pic='+Z' ;; esac # Is there a better lt_prog_compiler_static that works with the bundled CC? lt_prog_compiler_static='$wl-a ${wl}archive' ;; irix5* | irix6* | nonstopux*) lt_prog_compiler_wl='-Wl,' # PIC (with -KPIC) is the default. lt_prog_compiler_static='-non_shared' ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in # old Intel for x86_64, which still supported -KPIC. ecc*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-static' ;; *flang* | ftn | f18* | f95*) # Flang compiler. lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; # flang / f18. f95 an alias for gfortran or flang on Debian flang* | f18* | f95*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; # icc used to be incompatible with GCC. # ICC 10 doesn't accept -KPIC any more. icc* | ifort*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; # Lahey Fortran 8.1. lf95*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='--shared' lt_prog_compiler_static='--static' ;; nagfor*) # NAG Fortran compiler lt_prog_compiler_wl='-Wl,-Wl,,' lt_prog_compiler_pic='-PIC' lt_prog_compiler_static='-Bstatic' ;; tcc*) # Fabrice Bellard et al's Tiny C Compiler lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) # Portland Group compilers (*not* the Pentium gcc compiler, # which looks to be a dead project) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fpic' lt_prog_compiler_static='-Bstatic' ;; ccc*) lt_prog_compiler_wl='-Wl,' # All Alpha code is PIC. lt_prog_compiler_static='-non_shared' ;; xl* | bgxl* | bgf* | mpixl*) # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-qpic' lt_prog_compiler_static='-qstaticlink' ;; *) case `$CC -V 2>&1 | $SED 5q` in *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [1-7].* | *Sun*Fortran*\ 8.[0-3]*) # Sun Fortran 8.3 passes all unrecognized flags to the linker lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' lt_prog_compiler_wl='' ;; *Sun\ F* | *Sun*Fortran*) lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' lt_prog_compiler_wl='-Qoption ld ' ;; *Sun\ C*) # Sun C 5.9 lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' lt_prog_compiler_wl='-Wl,' ;; *Intel*\ [CF]*Compiler*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; *Portland\ Group*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fpic' lt_prog_compiler_static='-Bstatic' ;; esac ;; esac ;; newsos6) lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' ;; *-mlibc) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; *nto* | *qnx*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic='-fPIC -shared' ;; osf3* | osf4* | osf5*) lt_prog_compiler_wl='-Wl,' # All OSF/1 code is PIC. lt_prog_compiler_static='-non_shared' ;; rdos*) lt_prog_compiler_static='-non_shared' ;; serenity*) ;; solaris*) lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' case $cc_basename in f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) lt_prog_compiler_wl='-Qoption ld ';; *) lt_prog_compiler_wl='-Wl,';; esac ;; sunos4*) lt_prog_compiler_wl='-Qoption ld ' lt_prog_compiler_pic='-PIC' lt_prog_compiler_static='-Bstatic' ;; sysv4 | sysv4.2uw2* | sysv4.3*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' ;; sysv4*MP*) if test -d /usr/nec; then lt_prog_compiler_pic='-Kconform_pic' lt_prog_compiler_static='-Bstatic' fi ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' ;; unicos*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_can_build_shared=no ;; uts4*) lt_prog_compiler_pic='-pic' lt_prog_compiler_static='-Bstatic' ;; *) lt_prog_compiler_can_build_shared=no ;; esac fi case $host_os in # For platforms that do not support PIC, -DPIC is meaningless: *djgpp*) lt_prog_compiler_pic= ;; *) lt_prog_compiler_pic="$lt_prog_compiler_pic -DPIC" ;; esac { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 printf %s "checking for $compiler option to produce PIC... " >&6; } if test ${lt_cv_prog_compiler_pic+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_pic=$lt_prog_compiler_pic ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic" >&5 printf '%s\n' "$lt_cv_prog_compiler_pic" >&6; } lt_prog_compiler_pic=$lt_cv_prog_compiler_pic # # Check to make sure the PIC flag actually works. # if test -n "$lt_prog_compiler_pic"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic works" >&5 printf %s "checking if $compiler PIC flag $lt_prog_compiler_pic works... " >&6; } if test ${lt_cv_prog_compiler_pic_works+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_pic_works=no ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="$lt_prog_compiler_pic -DPIC" ## exclude from sc_useless_quotes_in_assignment # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. # The option is referenced via a variable to avoid confusing sed. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_pic_works=yes fi fi $RM conftest* ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works" >&5 printf '%s\n' "$lt_cv_prog_compiler_pic_works" >&6; } if test yes = "$lt_cv_prog_compiler_pic_works"; then case $lt_prog_compiler_pic in "" | " "*) ;; *) lt_prog_compiler_pic=" $lt_prog_compiler_pic" ;; esac else lt_prog_compiler_pic= lt_prog_compiler_can_build_shared=no fi fi # # Check to make sure the static flag actually works. # wl=$lt_prog_compiler_wl eval lt_tmp_static_flag=\"$lt_prog_compiler_static\" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 printf %s "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } if test ${lt_cv_prog_compiler_static_works+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_static_works=no save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS $lt_tmp_static_flag" echo "$lt_simple_link_test_code" > conftest.$ac_ext if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then # The linker can only warn and ignore the option if not recognized # So say no if there are warnings if test -s conftest.err; then # Append any errors to the config.log. cat conftest.err 1>&5 $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_static_works=yes fi else lt_cv_prog_compiler_static_works=yes fi fi $RM -r conftest* LDFLAGS=$save_LDFLAGS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works" >&5 printf '%s\n' "$lt_cv_prog_compiler_static_works" >&6; } if test yes = "$lt_cv_prog_compiler_static_works"; then : else lt_prog_compiler_static= fi ='-fPIC' archive_cmds='$CC -sSIDE_MODULE=2 -shared $libobjs $deplibs $compiler_flags -o $lib' archive_expsym_cmds='$SED "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -sSIDE_MODULE=2 -shared $libobjs $deplibs $compiler_flags -o $lib -s EXPORTED_FUNCTIONS=@$output_objdir/$soname.expsym' archive_cmds_need_lc=no no_undefined_flag= ;; *) dynamic_linker=no ;; esac { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5 printf '%s\n' "$dynamic_linker" >&6; } test no = "$dynamic_linker" && can_build_shared=no variables_saved_for_relink="PATH $shlibpath_var $runpath_var" if test yes = "$GCC"; then variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" fi if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec fi if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec fi # remember unaugmented sys_lib_dlsearch_path content for libtool script decls... configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec # ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" # to be used as default LT_SYS_LIBRARY_PATH value in generated libtool configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5 printf %s "checking how to hardcode library paths into programs... " >&6; } hardcode_action= if test -n "$hardcode_libdir_flag_spec" || test -n "$runpath_var" || test yes = "$hardcode_automatic"; then # We can hardcode non-existent directories. if test no != "$hardcode_direct" && # If the only mechanism to avoid hardcoding is shlibpath_var, we # have to relink, otherwise we might link with an installed library # when we should be linking with a yet-to-be-installed one ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, )" && test no != "$hardcode_minus_L"; then # Linking always hardcodes the temporary library directory. hardcode_action=relink else # We can link without hardcoding, and we can hardcode nonexisting dirs. hardcode_action=immediate fi else # We cannot hardcode anything, or else we can only hardcode existing # directories. hardcode_action=unsupported fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $hardcode_action" >&5 printf '%s\n' "$hardcode_action" >&6; } if test relink = "$hardcode_action" || test yes = "$inherit_rpath"; then # Fast installation is not supported enable_fast_install=no elif test yes = "$shlibpath_overrides_runpath" || test no = "$enable_shared"; then # Fast installation is not necessary enable_fast_install=needless fi if test yes != "$enable_dlopen"; then enable_dlopen=unknown enable_dlopen_self=unknown enable_dlopen_self_static=unknown else lt_cv_dlopen=no lt_cv_dlopen_libs= case $host_os in beos*) lt_cv_dlopen=load_add_on lt_cv_dlopen_libs= lt_cv_dlopen_self=yes ;; mingw* | windows* | pw32* | cegcc*) lt_cv_dlopen=LoadLibrary lt_cv_dlopen_libs= ;; cygwin*) lt_cv_dlopen=dlopen lt_cv_dlopen_libs= ;; darwin*) # if libdl is installed we need to link against it { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 printf %s "checking for dlopen in -ldl... " >&6; } if test ${ac_cv_lib_dl_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dl_dlopen=yes else case e in #( e) ac_cv_lib_dl_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 printf '%s\n' "$ac_cv_lib_dl_dlopen" >&6; } if test "x$ac_cv_lib_dl_dlopen" = xyes then : lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl else case e in #( e) lt_cv_dlopen=dyld lt_cv_dlopen_libs= lt_cv_dlopen_self=yes ;; esac fi ;; tpf*) # Don't try to run any link tests for TPF. We know it's impossible # because TPF is a cross-compiler, and we know how we open DSOs. lt_cv_dlopen=dlopen lt_cv_dlopen_libs= lt_cv_dlopen_self=no ;; *) ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load" if test "x$ac_cv_func_shl_load" = xyes then : lt_cv_dlopen=shl_load else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 printf %s "checking for shl_load in -ldld... " >&6; } if test ${ac_cv_lib_dld_shl_load+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldld $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char shl_load (void); int main (void) { return shl_load (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dld_shl_load=yes else case e in #( e) ac_cv_lib_dld_shl_load=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 printf '%s\n' "$ac_cv_lib_dld_shl_load" >&6; } if test "x$ac_cv_lib_dld_shl_load" = xyes then : lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld else case e in #( e) ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen" if test "x$ac_cv_func_dlopen" = xyes then : lt_cv_dlopen=dlopen else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 printf %s "checking for dlopen in -ldl... " >&6; } if test ${ac_cv_lib_dl_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dl_dlopen=yes else case e in #( e) ac_cv_lib_dl_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 printf '%s\n' "$ac_cv_lib_dl_dlopen" >&6; } if test "x$ac_cv_lib_dl_dlopen" = xyes then : lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5 printf %s "checking for dlopen in -lsvld... " >&6; } if test ${ac_cv_lib_svld_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lsvld $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_svld_dlopen=yes else case e in #( e) ac_cv_lib_svld_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5 printf '%s\n' "$ac_cv_lib_svld_dlopen" >&6; } if test "x$ac_cv_lib_svld_dlopen" = xyes then : lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5 printf %s "checking for dld_link in -ldld... " >&6; } if test ${ac_cv_lib_dld_dld_link+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldld $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dld_link (void); int main (void) { return dld_link (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dld_dld_link=yes else case e in #( e) ac_cv_lib_dld_dld_link=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5 printf '%s\n' "$ac_cv_lib_dld_dld_link" >&6; } if test "x$ac_cv_lib_dld_dld_link" = xyes then : lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld fi ;; esac fi ;; esac fi ;; esac fi ;; esac fi ;; esac fi ;; esac if test no = "$lt_cv_dlopen"; then enable_dlopen=no else enable_dlopen=yes fi case $lt_cv_dlopen in dlopen) save_CPPFLAGS=$CPPFLAGS test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" save_LDFLAGS=$LDFLAGS wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" save_LIBS=$LIBS LIBS="$lt_cv_dlopen_libs $LIBS" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether a program can dlopen itself" >&5 printf %s "checking whether a program can dlopen itself... " >&6; } if test ${lt_cv_dlopen_self+y} then : printf %s "(cached) " >&6 else case e in #( e) if test yes = "$cross_compiling"; then : lt_cv_dlopen_self=cross else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF #line $LINENO "configure" #include "confdefs.h" #if HAVE_DLFCN_H #include #endif #include #ifdef RTLD_GLOBAL # define LT_DLGLOBAL RTLD_GLOBAL #else # ifdef DL_GLOBAL # define LT_DLGLOBAL DL_GLOBAL # else # define LT_DLGLOBAL 0 # endif #endif /* We may have to define LT_DLLAZY_OR_NOW in the command line if we find out it does not work in some platform. */ #ifndef LT_DLLAZY_OR_NOW # ifdef RTLD_LAZY # define LT_DLLAZY_OR_NOW RTLD_LAZY # else # ifdef DL_LAZY # define LT_DLLAZY_OR_NOW DL_LAZY # else # ifdef RTLD_NOW # define LT_DLLAZY_OR_NOW RTLD_NOW # else # ifdef DL_NOW # define LT_DLLAZY_OR_NOW DL_NOW # else # define LT_DLLAZY_OR_NOW 0 # endif # endif # endif # endif #endif /* When -fvisibility=hidden is used, assume the code has been annotated correspondingly for the symbols needed. */ #if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) int fnord (void) __attribute__((visibility("default"))); #endif int fnord (void) { return 42; } int main (void) { void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); int status = $lt_dlunknown; if (self) { if (dlsym (self,"fnord")) status = $lt_dlno_uscore; else { if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; else puts (dlerror ()); } /* dlclose (self); */ } else puts (dlerror ()); return status; } _LT_EOF if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 (eval $ac_link) 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then (./conftest; exit; ) >&5 2>/dev/null lt_status=$? case x$lt_status in x$lt_dlno_uscore) lt_cv_dlopen_self=yes ;; x$lt_dlneed_uscore) lt_cv_dlopen_self=yes ;; x$lt_dlunknown|x*) lt_cv_dlopen_self=no ;; esac else : # compilation failed lt_cv_dlopen_self=no fi fi rm -fr conftest* ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self" >&5 printf '%s\n' "$lt_cv_dlopen_self" >&6; } if test yes = "$lt_cv_dlopen_self"; then wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether a statically linked program can dlopen itself" >&5 printf %s "checking whether a statically linked program can dlopen itself... " >&6; } if test ${lt_cv_dlopen_self_static+y} then : printf %s "(cached) " >&6 else case e in #( e) if test yes = "$cross_compiling"; then : lt_cv_dlopen_self_static=cross else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF #line $LINENO "configure" #include "confdefs.h" #if HAVE_DLFCN_H #include #endif #include #ifdef RTLD_GLOBAL # define LT_DLGLOBAL RTLD_GLOBAL #else # ifdef DL_GLOBAL # define LT_DLGLOBAL DL_GLOBAL # else # define LT_DLGLOBAL 0 # endif #endif /* We may have to define LT_DLLAZY_OR_NOW in the command line if we find out it does not work in some platform. */ #ifndef LT_DLLAZY_OR_NOW # ifdef RTLD_LAZY # define LT_DLLAZY_OR_NOW RTLD_LAZY # else # ifdef DL_LAZY # define LT_DLLAZY_OR_NOW DL_LAZY # else # ifdef RTLD_NOW # define LT_DLLAZY_OR_NOW RTLD_NOW # else # ifdef DL_NOW # define LT_DLLAZY_OR_NOW DL_NOW # else # define LT_DLLAZY_OR_NOW 0 # endif # endif # endif # endif #endif /* When -fvisibility=hidden is used, assume the code has been annotated correspondingly for the symbols needed. */ #if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) int fnord (void) __attribute__((visibility("default"))); #endif int fnord (void) { return 42; } int main (void) { void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); int status = $lt_dlunknown; if (self) { if (dlsym (self,"fnord")) status = $lt_dlno_uscore; else { if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; else puts (dlerror ()); } /* dlclose (self); */ } else puts (dlerror ()); return status; } _LT_EOF if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 (eval $ac_link) 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then (./conftest; exit; ) >&5 2>/dev/null lt_status=$? case x$lt_status in x$lt_dlno_uscore) lt_cv_dlopen_self_static=yes ;; x$lt_dlneed_uscore) lt_cv_dlopen_self_static=yes ;; x$lt_dlunknown|x*) lt_cv_dlopen_self_static=no ;; esac else : # compilation failed lt_cv_dlopen_self_static=no fi fi rm -fr conftest* ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self_static" >&5 printf '%s\n' "$lt_cv_dlopen_self_static" >&6; } fi CPPFLAGS=$save_CPPFLAGS LDFLAGS=$save_LDFLAGS LIBS=$save_LIBS ;; esac case $lt_cv_dlopen_self in yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; *) enable_dlopen_self=unknown ;; esac case $lt_cv_dlopen_self_static in yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; *) enable_dlopen_self_static=unknown ;; esac fi striplib= old_striplib= { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether stripping libraries is possible" >&5 printf %s "checking whether stripping libraries is possible... " >&6; } if test -z "$STRIP"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } else if $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then old_striplib="$STRIP --strip-debug" striplib="$STRIP --strip-unneeded" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } else case $host_os in darwin*) # FIXME - insert some real tests, host_os isn't really good enough striplib="$STRIP -x" old_striplib="$STRIP -S" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } ;; freebsd*) if $STRIP -V 2>&1 | $GREP "elftoolchain" >/dev/null; then old_striplib="$STRIP --strip-debug" striplib="$STRIP --strip-unneeded" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi ;; *) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } ;; esac fi fi # Report what library types will actually be built { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if libtool supports shared libraries" >&5 printf %s "checking if libtool supports shared libraries... " >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $can_build_shared" >&5 printf '%s\n' "$can_build_shared" >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether to build shared libraries" >&5 printf %s "checking whether to build shared libraries... " >&6; } test no = "$can_build_shared" && enable_shared=no # On AIX, shared libraries and static libraries use the same namespace, and # are all built from PIC. case $host_os in aix3*) test yes = "$enable_shared" && enable_static=no if test -n "$RANLIB"; then archive_cmds="$archive_cmds~\$RANLIB \$lib" postinstall_cmds='$RANLIB $lib' fi ;; aix[4-9]*) if test ia64 != "$host_cpu"; then case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in yes,aix,yes) ;; # shared object as lib.so file only yes,svr4,*) ;; # shared object as lib.so archive member only yes,*) enable_static=no ;; # shared object in lib.a archive as well esac fi ;; esac { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $enable_shared" >&5 printf '%s\n' "$enable_shared" >&6; } { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether to build static libraries" >&5 printf %s "checking whether to build static libraries... " >&6; } # Make sure either enable_shared or enable_static is yes. test yes = "$enable_shared" || enable_static=yes { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $enable_static" >&5 printf '%s\n' "$enable_static" >&6; } fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu CC=$lt_save_CC ac_config_commands="$ac_config_commands libtool" # Only expand once: if (test "$USE_MAINTAINER_MODE" = "yes"); then # Extract the first word of "lcov", so it can be a program name with args. set dummy lcov; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_enable_coverage+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$enable_coverage"; then ac_cv_prog_enable_coverage="$enable_coverage" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_enable_coverage="yes" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_enable_coverage" && ac_cv_prog_enable_coverage="no" fi ;; esac fi enable_coverage=$ac_cv_prog_enable_coverage if test -n "$enable_coverage"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $enable_coverage" >&5 printf '%s\n' "$enable_coverage" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi # Extract the first word of "dbus-run-session", so it can be a program name with args. set dummy dbus-run-session; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_enable_dbus_run_session+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$enable_dbus_run_session"; then ac_cv_prog_enable_dbus_run_session="$enable_dbus_run_session" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_enable_dbus_run_session="yes" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi enable_dbus_run_session=$ac_cv_prog_enable_dbus_run_session if test -n "$enable_dbus_run_session"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $enable_dbus_run_session" >&5 printf '%s\n' "$enable_dbus_run_session" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi # Extract the first word of "valgrind", so it can be a program name with args. set dummy valgrind; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_enable_valgrind+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$enable_valgrind"; then ac_cv_prog_enable_valgrind="$enable_valgrind" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_enable_valgrind="yes" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi enable_valgrind=$ac_cv_prog_enable_valgrind if test -n "$enable_valgrind"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $enable_valgrind" >&5 printf '%s\n' "$enable_valgrind" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi ac_fn_c_check_header_compile "$LINENO" "valgrind/memcheck.h" "ac_cv_header_valgrind_memcheck_h" "$ac_includes_default" if test "x$ac_cv_header_valgrind_memcheck_h" = xyes then : printf '%s\n' "#define HAVE_VALGRIND_MEMCHECK_H 1" >>confdefs.h fi fi if test "${enable_coverage}" = "yes"; then COVERAGE_TRUE= COVERAGE_FALSE='#' else COVERAGE_TRUE='#' COVERAGE_FALSE= fi if test "${enable_dbus_run_session}" = "yes"; then DBUS_RUN_SESSION_TRUE= DBUS_RUN_SESSION_FALSE='#' else DBUS_RUN_SESSION_TRUE='#' DBUS_RUN_SESSION_FALSE= fi misc_cflags="" misc_ldflags="" # Check whether --enable-optimization was given. if test ${enable_optimization+y} then : enableval=$enable_optimization; if (test "${enableval}" = "no"); then misc_cflags="$misc_cflags -O0" fi fi # Check whether --enable-asan was given. if test ${enable_asan+y} then : enableval=$enable_asan; save_LIBS=$LIBS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for _init in -lasan" >&5 printf %s "checking for _init in -lasan... " >&6; } if test ${ac_cv_lib_asan__init+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lasan $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char _init (void); int main (void) { return _init (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_asan__init=yes else case e in #( e) ac_cv_lib_asan__init=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_asan__init" >&5 printf '%s\n' "$ac_cv_lib_asan__init" >&6; } if test "x$ac_cv_lib_asan__init" = xyes then : printf '%s\n' "#define HAVE_LIBASAN 1" >>confdefs.h LIBS="-lasan $LIBS" fi LIBS=$save_LIBS if (test "${enableval}" = "yes" && test "${ac_cv_lib_asan__init}" = "yes" && test "${ac_cv_prog_cc_asan}" = "yes"); then misc_cflags="$misc_cflags -fsanitize=address"; misc_ldflags="$misc_ldflags -fsanitize=address" ASAN_LIB=${ac_cv_lib_asan__init} fi fi # Check whether --enable-lsan was given. if test ${enable_lsan+y} then : enableval=$enable_lsan; save_LIBS=$LIBS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for _init in -llsan" >&5 printf %s "checking for _init in -llsan... " >&6; } if test ${ac_cv_lib_lsan__init+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-llsan $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char _init (void); int main (void) { return _init (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_lsan__init=yes else case e in #( e) ac_cv_lib_lsan__init=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_lsan__init" >&5 printf '%s\n' "$ac_cv_lib_lsan__init" >&6; } if test "x$ac_cv_lib_lsan__init" = xyes then : printf '%s\n' "#define HAVE_LIBLSAN 1" >>confdefs.h LIBS="-llsan $LIBS" fi LIBS=$save_LIBS if (test "${enableval}" = "yes" && test "${ac_cv_lib_lsan__init}" = "yes" && test "${ac_cv_prog_cc_lsan}" = "yes"); then misc_cflags="$misc_cflags -fsanitize=leak"; misc_ldflags="$misc_ldflags -fsanitize=leak" ASAN_LIB=${ac_cv_lib_lsan__init} fi fi # Check whether --enable-ubsan was given. if test ${enable_ubsan+y} then : enableval=$enable_ubsan; save_LIBS=$LIBS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for _init in -lubsan" >&5 printf %s "checking for _init in -lubsan... " >&6; } if test ${ac_cv_lib_ubsan__init+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lubsan $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char _init (void); int main (void) { return _init (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_ubsan__init=yes else case e in #( e) ac_cv_lib_ubsan__init=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ubsan__init" >&5 printf '%s\n' "$ac_cv_lib_ubsan__init" >&6; } if test "x$ac_cv_lib_ubsan__init" = xyes then : printf '%s\n' "#define HAVE_LIBUBSAN 1" >>confdefs.h LIBS="-lubsan $LIBS" fi LIBS=$save_LIBS if (test "${enableval}" = "yes" && test "${ac_cv_lib_ubsan__init}" = "yes" && test "${ac_cv_prog_cc_ubsan}" = "yes"); then misc_cflags="$misc_cflags -fsanitize=undefined"; misc_ldflags="$misc_ldflags -fsanitize=undefined"; fi fi # Check whether --enable-debug was given. if test ${enable_debug+y} then : enableval=$enable_debug; if (test "${enableval}" = "yes" && test "${ac_cv_prog_cc_g}" = "yes"); then misc_cflags="$misc_cflags -g" fi fi # Check whether --enable-pie was given. if test ${enable_pie+y} then : enableval=$enable_pie; if (test "${enableval}" = "yes" && test "${ac_cv_prog_cc_pie}" = "yes"); then misc_cflags="$misc_cflags -fPIC" misc_ldflags="$misc_ldflags -pie -Wl,-z,now" fi fi if (test "$enable_coverage" = "yes"); then misc_cflags="$misc_cflags --coverage" misc_ldflags="$misc_ldflags --coverage" fi if (test "$USE_MAINTAINER_MODE" = "yes"); then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether ${CC-cc} accepts -D_FORTIFY_SOURCE=3" >&5 printf %s "checking whether ${CC-cc} accepts -D_FORTIFY_SOURCE=3... " >&6; } if test ${ac_cv_prog_cc_fortify_source_3+y} then : printf %s "(cached) " >&6 else case e in #( e) echo '#include ' > fortify.c if test -z "`${CC-cc} ${CFLAGS} ${misc_cflags} -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -c fortify.c 2>&1`"; then ac_cv_prog_cc_fortify_source_3=yes else ac_cv_prog_cc_fortify_source_3=no fi rm -f fortify.c fortify.o ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_fortify_source_3" >&5 printf '%s\n' "$ac_cv_prog_cc_fortify_source_3" >&6; } if test "${ac_cv_prog_cc_fortify_source_3}" = "yes"; then misc_cflags="$misc_cflags -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3" fi fi misc_cflags="$misc_cflags -ffunction-sections -fdata-sections" misc_ldflags="$misc_ldflags -Wl,--gc-sections" MISC_CFLAGS=$misc_cflags MISC_LDFLAGS=$misc_ldflags if test "${enable_valgrind}" = "yes" && test "$ASAN_LIB" != "yes" && test "LSAN_LIB" != "yes"; then VALGRIND_TRUE= VALGRIND_FALSE='#' else VALGRIND_TRUE='#' VALGRIND_FALSE= fi # Check whether --enable-threads was given. if test ${enable_threads+y} then : enableval=$enable_threads; enable_threads=${enableval} fi ac_fn_c_check_func "$LINENO" "explicit_bzero" "ac_cv_func_explicit_bzero" if test "x$ac_cv_func_explicit_bzero" = xyes then : printf '%s\n' "#define HAVE_EXPLICIT_BZERO 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "getrandom" "ac_cv_func_getrandom" if test "x$ac_cv_func_getrandom" = xyes then : printf '%s\n' "#define HAVE_GETRANDOM 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "rawmemchr" "ac_cv_func_rawmemchr" if test "x$ac_cv_func_rawmemchr" = xyes then : printf '%s\n' "#define HAVE_RAWMEMCHR 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "signalfd" "ac_cv_func_signalfd" if test "x$ac_cv_func_signalfd" = xyes then : dummy=yes else case e in #( e) as_fn_error $? "signalfd support is required" "$LINENO" 5 ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for clock_gettime in -lrt" >&5 printf %s "checking for clock_gettime in -lrt... " >&6; } if test ${ac_cv_lib_rt_clock_gettime+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lrt $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char clock_gettime (void); int main (void) { return clock_gettime (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_rt_clock_gettime=yes else case e in #( e) ac_cv_lib_rt_clock_gettime=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_rt_clock_gettime" >&5 printf '%s\n' "$ac_cv_lib_rt_clock_gettime" >&6; } if test "x$ac_cv_lib_rt_clock_gettime" = xyes then : dummy=yes else case e in #( e) as_fn_error $? "realtime clock support is required" "$LINENO" 5 ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for pthread_create in -lpthread" >&5 printf %s "checking for pthread_create in -lpthread... " >&6; } if test ${ac_cv_lib_pthread_pthread_create+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lpthread $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char pthread_create (void); int main (void) { return pthread_create (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_pthread_pthread_create=yes else case e in #( e) ac_cv_lib_pthread_pthread_create=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_pthread_create" >&5 printf '%s\n' "$ac_cv_lib_pthread_pthread_create" >&6; } if test "x$ac_cv_lib_pthread_pthread_create" = xyes then : dummy=yes else case e in #( e) as_fn_error $? "posix thread support is required" "$LINENO" 5 ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 printf %s "checking for dlopen in -ldl... " >&6; } if test ${ac_cv_lib_dl_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dl_dlopen=yes else case e in #( e) ac_cv_lib_dl_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 printf '%s\n' "$ac_cv_lib_dl_dlopen" >&6; } if test "x$ac_cv_lib_dl_dlopen" = xyes then : dummy=yes else case e in #( e) as_fn_error $? "dynamic linking loader is required" "$LINENO" 5 ;; esac fi ac_fn_c_check_header_compile "$LINENO" "stdarg.h" "ac_cv_header_stdarg_h" "$ac_includes_default" if test "x$ac_cv_header_stdarg_h" = xyes then : printf '%s\n' "#define HAVE_STDARG_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "stdio.h" "ac_cv_header_stdio_h" "$ac_includes_default" if test "x$ac_cv_header_stdio_h" = xyes then : printf '%s\n' "#define HAVE_STDIO_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "string.h" "ac_cv_header_string_h" "$ac_includes_default" if test "x$ac_cv_header_string_h" = xyes then : printf '%s\n' "#define HAVE_STRING_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "linux/types.h" "ac_cv_header_linux_types_h" "$ac_includes_default" if test "x$ac_cv_header_linux_types_h" = xyes then : printf '%s\n' "#define HAVE_LINUX_TYPES_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "linux/if_alg.h" "ac_cv_header_linux_if_alg_h" "$ac_includes_default" if test "x$ac_cv_header_linux_if_alg_h" = xyes then : printf '%s\n' "#define HAVE_LINUX_IF_ALG_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "linux/uinput.h" "ac_cv_header_linux_uinput_h" "$ac_includes_default" if test "x$ac_cv_header_linux_uinput_h" = xyes then : printf '%s\n' "#define HAVE_LINUX_UINPUT_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "linux/uhid.h" "ac_cv_header_linux_uhid_h" "$ac_includes_default" if test "x$ac_cv_header_linux_uhid_h" = xyes then : printf '%s\n' "#define HAVE_LINUX_UHID_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "sys/random.h" "ac_cv_header_sys_random_h" "$ac_includes_default" if test "x$ac_cv_header_sys_random_h" = xyes then : printf '%s\n' "#define HAVE_SYS_RANDOM_H 1" >>confdefs.h fi # basename may be only available in libgen.h with the POSIX behavior, # not desired here { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC options to detect undeclared functions" >&5 printf %s "checking for $CC options to detect undeclared functions... " >&6; } if test ${ac_cv_c_undeclared_builtin_options+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_save_CFLAGS=$CFLAGS ac_cv_c_undeclared_builtin_options='cannot detect' for ac_arg in '' -fno-builtin; do CFLAGS="$ac_save_CFLAGS $ac_arg" # This test program should *not* compile successfully. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { (void) strchr; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else case e in #( e) # This test program should compile successfully. # No library function is consistently available on # freestanding implementations, so test against a dummy # declaration. Include always-available headers on the # off chance that they somehow elicit warnings. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include extern void ac_decl (int, char *); int main (void) { (void) ac_decl (0, (char *) 0); (void) ac_decl; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : if test x"$ac_arg" = x then : ac_cv_c_undeclared_builtin_options='none needed' else case e in #( e) ac_cv_c_undeclared_builtin_options=$ac_arg ;; esac fi break fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext done CFLAGS=$ac_save_CFLAGS ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_undeclared_builtin_options" >&5 printf '%s\n' "$ac_cv_c_undeclared_builtin_options" >&6; } case $ac_cv_c_undeclared_builtin_options in #( 'cannot detect') : { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot make $CC report undeclared builtins See 'config.log' for more details" "$LINENO" 5; } ;; #( 'none needed') : ac_c_undeclared_builtin_options='' ;; #( *) : ac_c_undeclared_builtin_options=$ac_cv_c_undeclared_builtin_options ;; esac { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC options to ignore future-version functions" >&5 printf %s "checking for $CC options to ignore future-version functions... " >&6; } if test ${ac_cv_c_future_darwin_options+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_compile_saved="$ac_compile" ac_compile="$ac_compile -Werror=unguarded-availability-new" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #if ! (defined __APPLE__ && defined __MACH__) #error "-Werror=unguarded-availability-new not needed here" #endif int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_c_future_darwin_options='-Werror=unguarded-availability-new' else case e in #( e) ac_cv_c_future_darwin_options='none needed' ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_compile="$ac_compile_saved" ;; esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_future_darwin_options" >&5 printf '%s\n' "$ac_cv_c_future_darwin_options" >&6; } case $ac_cv_c_future_darwin_options in #( 'none needed') : ac_c_future_darwin_options='' ;; #( *) : ac_c_future_darwin_options=$ac_cv_c_future_darwin_options ;; esac ac_fn_check_decl "$LINENO" "basename" "ac_cv_have_decl_basename" "#define _GNU_SOURCE 1 #include " "$ac_c_undeclared_builtin_options$ac_c_future_darwin_options" "CFLAGS" if test "x$ac_cv_have_decl_basename" = xyes then : ac_have_decl=1 else case e in #( e) ac_have_decl=0 ;; esac fi printf '%s\n' "#define HAVE_DECL_BASENAME $ac_have_decl" >>confdefs.h if test $ac_have_decl = 1 then : else case e in #( e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: GNU basename extension not found" >&5 printf '%s\n' "$as_me: WARNING: GNU basename extension not found" >&2;} ;; esac fi pkg_failed=no { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for glib-2.0 >= 2.36" >&5 printf %s "checking for glib-2.0 >= 2.36... " >&6; } if test -n "$GLIB_CFLAGS"; then pkg_cv_GLIB_CFLAGS="$GLIB_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"glib-2.0 >= 2.36\""; } >&5 ($PKG_CONFIG --exists --print-errors "glib-2.0 >= 2.36") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_GLIB_CFLAGS=`$PKG_CONFIG --cflags "glib-2.0 >= 2.36" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$GLIB_LIBS"; then pkg_cv_GLIB_LIBS="$GLIB_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"glib-2.0 >= 2.36\""; } >&5 ($PKG_CONFIG --exists --print-errors "glib-2.0 >= 2.36") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_GLIB_LIBS=`$PKG_CONFIG --libs "glib-2.0 >= 2.36" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then GLIB_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "glib-2.0 >= 2.36" 2>&1` else GLIB_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "glib-2.0 >= 2.36" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$GLIB_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (glib-2.0 >= 2.36) were not met: $GLIB_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables GLIB_CFLAGS and GLIB_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables GLIB_CFLAGS and GLIB_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else GLIB_CFLAGS=$pkg_cv_GLIB_CFLAGS GLIB_LIBS=$pkg_cv_GLIB_LIBS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } fi pkg_failed=no { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for dbus-1 >= 1.10" >&5 printf %s "checking for dbus-1 >= 1.10... " >&6; } if test -n "$DBUS_CFLAGS"; then pkg_cv_DBUS_CFLAGS="$DBUS_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"dbus-1 >= 1.10\""; } >&5 ($PKG_CONFIG --exists --print-errors "dbus-1 >= 1.10") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_DBUS_CFLAGS=`$PKG_CONFIG --cflags "dbus-1 >= 1.10" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$DBUS_LIBS"; then pkg_cv_DBUS_LIBS="$DBUS_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"dbus-1 >= 1.10\""; } >&5 ($PKG_CONFIG --exists --print-errors "dbus-1 >= 1.10") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_DBUS_LIBS=`$PKG_CONFIG --libs "dbus-1 >= 1.10" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then DBUS_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "dbus-1 >= 1.10" 2>&1` else DBUS_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "dbus-1 >= 1.10" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$DBUS_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (dbus-1 >= 1.10) were not met: $DBUS_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables DBUS_CFLAGS and DBUS_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables DBUS_CFLAGS and DBUS_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else DBUS_CFLAGS=$pkg_cv_DBUS_CFLAGS DBUS_LIBS=$pkg_cv_DBUS_LIBS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } fi # Check whether --with-dbusconfdir was given. if test ${with_dbusconfdir+y} then : withval=$with_dbusconfdir; path_dbusconfdir=${withval} fi if (test -z "${path_dbusconfdir}"); then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking D-Bus configuration directory" >&5 printf %s "checking D-Bus configuration directory... " >&6; } path_dbusconfdir="`$PKG_CONFIG --variable=datadir dbus-1`" if (test -z "${path_dbusconfdir}"); then as_fn_error $? "D-Bus configuration directory is required" "$LINENO" 5 fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: ${path_dbusconfdir}" >&5 printf '%s\n' "${path_dbusconfdir}" >&6; } fi DBUS_CONFDIR=${path_dbusconfdir} # Check whether --with-dbussystembusdir was given. if test ${with_dbussystembusdir+y} then : withval=$with_dbussystembusdir; path_dbussystembusdir=${withval} fi if (test -z "${path_dbussystembusdir}"); then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking D-Bus system bus services dir" >&5 printf %s "checking D-Bus system bus services dir... " >&6; } path_dbussystembusdir="`$PKG_CONFIG --variable=system_bus_services_dir dbus-1`" if (test -z "${path_dbussystembusdir}"); then as_fn_error $? "D-Bus system bus services directory is required" "$LINENO" 5 fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: ${path_dbussystembusdir}" >&5 printf '%s\n' "${path_dbussystembusdir}" >&6; } fi DBUS_SYSTEMBUSDIR=${path_dbussystembusdir} # Check whether --with-dbussessionbusdir was given. if test ${with_dbussessionbusdir+y} then : withval=$with_dbussessionbusdir; path_dbussessionbusdir=${withval} fi if (test -z "${path_dbussessionbusdir}"); then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking D-Bus session bus services dir" >&5 printf %s "checking D-Bus session bus services dir... " >&6; } path_dbussessionbusdir="`$PKG_CONFIG --variable=session_bus_services_dir dbus-1`" if (test -z "${path_dbussessionbusdir}"); then as_fn_error $? "D-Bus session bus services directory is required" "$LINENO" 5 fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: ${path_dbussessionbusdir}" >&5 printf '%s\n' "${path_dbussessionbusdir}" >&6; } fi DBUS_SESSIONBUSDIR=${path_dbussessionbusdir} # Check whether --with-zsh-completion-dir was given. if test ${with_zsh_completion_dir+y} then : withval=$with_zsh_completion_dir; path_zshcompletiondir=${withval} else case e in #( e) path_zshcompletiondir="yes" ;; esac fi if (test "${path_zshcompletiondir}" = "yes"); then path_zshcompletiondir="$datarootdir/zsh/site-functions" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: ${path_zshcompletiondir}" >&5 printf '%s\n' "${path_zshcompletiondir}" >&6; } fi ZSH_COMPLETIONDIR=${path_zshcompletiondir} if test "${path_zshcompletiondir}" != "no"; then ZSH_COMPLETIONS_TRUE= ZSH_COMPLETIONS_FALSE='#' else ZSH_COMPLETIONS_TRUE='#' ZSH_COMPLETIONS_FALSE= fi # Check whether --enable-backtrace was given. if test ${enable_backtrace+y} then : enableval=$enable_backtrace; enable_backtrace=${enableval} fi if (test "${enable_backtrace}" = "yes"); then ac_fn_c_check_header_compile "$LINENO" "elfutils/libdwfl.h" "ac_cv_header_elfutils_libdwfl_h" "$ac_includes_default" if test "x$ac_cv_header_elfutils_libdwfl_h" = xyes then : dummy=yes else case e in #( e) as_fn_error $? "elfutils support is required" "$LINENO" 5 ;; esac fi printf '%s\n' "#define HAVE_BACKTRACE_SUPPORT 1" >>confdefs.h BACKTRACE_CFLAGS="" BACKTRACE_LIBS="-ldw" fi # Check whether --enable-library was given. if test ${enable_library+y} then : enableval=$enable_library; enable_library=${enableval} fi if test "${enable_library}" = "yes"; then LIBRARY_TRUE= LIBRARY_FALSE='#' else LIBRARY_TRUE='#' LIBRARY_FALSE= fi # Check whether --enable-test was given. if test ${enable_test+y} then : enableval=$enable_test; enable_test=${enableval} fi if test "${enable_test}" = "yes"; then TEST_TRUE= TEST_FALSE='#' else TEST_TRUE='#' TEST_FALSE= fi # Check whether --enable-nfc was given. if test ${enable_nfc+y} then : enableval=$enable_nfc; enable_nfc=${enableval} fi if test "${enable_nfc}" = "yes"; then NFC_TRUE= NFC_FALSE='#' else NFC_TRUE='#' NFC_FALSE= fi # Check whether --enable-a2dp was given. if test ${enable_a2dp+y} then : enableval=$enable_a2dp; enable_a2dp=${enableval} fi if test "${enable_a2dp}" != "no"; then A2DP_TRUE= A2DP_FALSE='#' else A2DP_TRUE='#' A2DP_FALSE= fi if test "${enable_a2dp}" != "no"; then printf '%s\n' "#define HAVE_A2DP 1" >>confdefs.h fi # Check whether --enable-avrcp was given. if test ${enable_avrcp+y} then : enableval=$enable_avrcp; enable_avrcp=${enableval} fi if test "${enable_avrcp}" != "no"; then AVRCP_TRUE= AVRCP_FALSE='#' else AVRCP_TRUE='#' AVRCP_FALSE= fi if test "${enable_avrcp}" != "no"; then printf '%s\n' "#define HAVE_AVRCP 1" >>confdefs.h fi # Check whether --enable-network was given. if test ${enable_network+y} then : enableval=$enable_network; enable_network=${enableval} fi if test "${enable_network}" != "no"; then NETWORK_TRUE= NETWORK_FALSE='#' else NETWORK_TRUE='#' NETWORK_FALSE= fi # Check whether --enable-hid was given. if test ${enable_hid+y} then : enableval=$enable_hid; enable_hid=${enableval} fi if test "${enable_hid}" != "no"; then HID_TRUE= HID_FALSE='#' else HID_TRUE='#' HID_FALSE= fi # Check whether --enable-hog was given. if test ${enable_hog+y} then : enableval=$enable_hog; enable_hog=${enableval} fi if test "${enable_hog}" != "no"; then HOG_TRUE= HOG_FALSE='#' else HOG_TRUE='#' HOG_FALSE= fi # Check whether --enable-bap was given. if test ${enable_bap+y} then : enableval=$enable_bap; enable_bap=${enableval} fi if test "${enable_bap}" != "no"; then BAP_TRUE= BAP_FALSE='#' else BAP_TRUE='#' BAP_FALSE= fi # Check whether --enable-bass was given. if test ${enable_bass+y} then : enableval=$enable_bass; enable_bass=${enableval} fi if test "${enable_bass}" != "no"; then BASS_TRUE= BASS_FALSE='#' else BASS_TRUE='#' BASS_FALSE= fi # Check whether --enable-mcp was given. if test ${enable_mcp+y} then : enableval=$enable_mcp; enable_mcp=${enableval} fi if test "${enable_mcp}" != "no"; then MCP_TRUE= MCP_FALSE='#' else MCP_TRUE='#' MCP_FALSE= fi # Check whether --enable-ccp was given. if test ${enable_ccp+y} then : enableval=$enable_ccp; enable_ccp=${enableval} fi if test "${enable_ccp}" != "no"; then CCP_TRUE= CCP_FALSE='#' else CCP_TRUE='#' CCP_FALSE= fi # Check whether --enable-vcp was given. if test ${enable_vcp+y} then : enableval=$enable_vcp; enable_vcp=${enableval} fi if test "${enable_vcp}" != "no"; then VCP_TRUE= VCP_FALSE='#' else VCP_TRUE='#' VCP_FALSE= fi if test "${enable_vcp}" != "no"; then printf '%s\n' "#define HAVE_VCP 1" >>confdefs.h fi # Check whether --enable-micp was given. if test ${enable_micp+y} then : enableval=$enable_micp; enable_micp=${enableval} fi if test "${enable_micp}" != "no"; then MICP_TRUE= MICP_FALSE='#' else MICP_TRUE='#' MICP_FALSE= fi # Check whether --enable-csip was given. if test ${enable_csip+y} then : enableval=$enable_csip; enable_csip=${enableval} fi if test "${enable_csip}" != "no"; then CSIP_TRUE= CSIP_FALSE='#' else CSIP_TRUE='#' CSIP_FALSE= fi # Check whether --enable-tmap was given. if test ${enable_tmap+y} then : enableval=$enable_tmap; enable_tmap=${enableval} fi if test "${enable_tmap}" != "no"; then TMAP_TRUE= TMAP_FALSE='#' else TMAP_TRUE='#' TMAP_FALSE= fi if test "${enable_tmap}" != "no"; then printf '%s\n' "#define HAVE_TMAP 1" >>confdefs.h fi # Check whether --enable-gmap was given. if test ${enable_gmap+y} then : enableval=$enable_gmap; enable_gmap=${enableval} fi if test "${enable_gmap}" != "no"; then GMAP_TRUE= GMAP_FALSE='#' else GMAP_TRUE='#' GMAP_FALSE= fi if test "${enable_gmap}" != "no"; then printf '%s\n' "#define HAVE_GMAP 1" >>confdefs.h fi # Check whether --enable-asha was given. if test ${enable_asha+y} then : enableval=$enable_asha; enable_asha=${enableval} fi if test "${enable_asha}" != "no"; then ASHA_TRUE= ASHA_FALSE='#' else ASHA_TRUE='#' ASHA_FALSE= fi if test "${enable_asha}" != "no"; then printf '%s\n' "#define HAVE_ASHA 1" >>confdefs.h fi # Check whether --enable-hfp was given. if test ${enable_hfp+y} then : enableval=$enable_hfp; enable_hfp=${enableval} fi if test "${enable_hfp}" != "no"; then HFP_TRUE= HFP_FALSE='#' else HFP_TRUE='#' HFP_FALSE= fi if test "${enable_hfp}" != "no"; then printf '%s\n' "#define HAVE_HFP 1" >>confdefs.h fi # Check whether --enable-tools was given. if test ${enable_tools+y} then : enableval=$enable_tools; enable_tools=${enableval} fi if test "${enable_tools}" != "no"; then TOOLS_TRUE= TOOLS_FALSE='#' else TOOLS_TRUE='#' TOOLS_FALSE= fi # Check whether --enable-monitor was given. if test ${enable_monitor+y} then : enableval=$enable_monitor; enable_monitor=${enableval} fi if test "${enable_monitor}" != "no"; then MONITOR_TRUE= MONITOR_FALSE='#' else MONITOR_TRUE='#' MONITOR_FALSE= fi # Check whether --enable-udev was given. if test ${enable_udev+y} then : enableval=$enable_udev; enable_udev=${enableval} fi if (test "${enable_udev}" != "no"); then pkg_failed=no { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for libudev >= 196" >&5 printf %s "checking for libudev >= 196... " >&6; } if test -n "$UDEV_CFLAGS"; then pkg_cv_UDEV_CFLAGS="$UDEV_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libudev >= 196\""; } >&5 ($PKG_CONFIG --exists --print-errors "libudev >= 196") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_UDEV_CFLAGS=`$PKG_CONFIG --cflags "libudev >= 196" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$UDEV_LIBS"; then pkg_cv_UDEV_LIBS="$UDEV_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libudev >= 196\""; } >&5 ($PKG_CONFIG --exists --print-errors "libudev >= 196") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_UDEV_LIBS=`$PKG_CONFIG --libs "libudev >= 196" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then UDEV_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libudev >= 196" 2>&1` else UDEV_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libudev >= 196" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$UDEV_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libudev >= 196) were not met: $UDEV_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables UDEV_CFLAGS and UDEV_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables UDEV_CFLAGS and UDEV_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else UDEV_CFLAGS=$pkg_cv_UDEV_CFLAGS UDEV_LIBS=$pkg_cv_UDEV_LIBS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } fi printf '%s\n' "#define HAVE_UDEV 1" >>confdefs.h fi # Check whether --with-udevdir was given. if test ${with_udevdir+y} then : withval=$with_udevdir; path_udevdir=${withval} fi if (test "${enable_udev}" != "no" && test -z "${path_udevdir}"); then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking udev directory" >&5 printf %s "checking udev directory... " >&6; } path_udevdir="`$PKG_CONFIG --variable=udevdir udev`" if (test -z "${path_udevdir}"); then as_fn_error $? "udev directory is required" "$LINENO" 5 fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: ${path_udevdir}" >&5 printf '%s\n' "${path_udevdir}" >&6; } fi UDEV_DIR=${path_udevdir} # Check whether --enable-cups was given. if test ${enable_cups+y} then : enableval=$enable_cups; enable_cups=${enableval} fi if test "${enable_cups}" != "no"; then CUPS_TRUE= CUPS_FALSE='#' else CUPS_TRUE='#' CUPS_FALSE= fi if (test "${enable_cups}" != "no"); then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking cups directory" >&5 printf %s "checking cups directory... " >&6; } cups_serverbin=`$PKG_CONFIG cups --variable=cups_serverbin` { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: ${cups_serverbin}" >&5 printf '%s\n' "${cups_serverbin}" >&6; } fi if test "${cups_serverbin}" != ""; then CUPS_SERVERBIN_TRUE= CUPS_SERVERBIN_FALSE='#' else CUPS_SERVERBIN_TRUE='#' CUPS_SERVERBIN_FALSE= fi if test "${cups_serverbin}" != "" then : CUPS_SERVERBIN=${cups_serverbin} fi # Check whether --enable-mesh was given. if test ${enable_mesh+y} then : enableval=$enable_mesh; enable_mesh=${enableval} fi if test "${enable_mesh}" = "yes"; then MESH_TRUE= MESH_FALSE='#' else MESH_TRUE='#' MESH_FALSE= fi if (test "${enable_mesh}" = "yes"); then pkg_failed=no { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for json-c >= 0.13" >&5 printf %s "checking for json-c >= 0.13... " >&6; } if test -n "$JSONC_CFLAGS"; then pkg_cv_JSONC_CFLAGS="$JSONC_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"json-c >= 0.13\""; } >&5 ($PKG_CONFIG --exists --print-errors "json-c >= 0.13") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_JSONC_CFLAGS=`$PKG_CONFIG --cflags "json-c >= 0.13" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$JSONC_LIBS"; then pkg_cv_JSONC_LIBS="$JSONC_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"json-c >= 0.13\""; } >&5 ($PKG_CONFIG --exists --print-errors "json-c >= 0.13") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_JSONC_LIBS=`$PKG_CONFIG --libs "json-c >= 0.13" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then JSONC_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "json-c >= 0.13" 2>&1` else JSONC_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "json-c >= 0.13" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$JSONC_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (json-c >= 0.13) were not met: $JSONC_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables JSONC_CFLAGS and JSONC_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables JSONC_CFLAGS and JSONC_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else JSONC_CFLAGS=$pkg_cv_JSONC_CFLAGS JSONC_LIBS=$pkg_cv_JSONC_LIBS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } fi fi # Check whether --enable-midi was given. if test ${enable_midi+y} then : enableval=$enable_midi; enable_midi=${enableval} fi if test "${enable_midi}" = "yes"; then MIDI_TRUE= MIDI_FALSE='#' else MIDI_TRUE='#' MIDI_FALSE= fi if (test "${enable_midi}" = "yes"); then pkg_failed=no { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for alsa" >&5 printf %s "checking for alsa... " >&6; } if test -n "$ALSA_CFLAGS"; then pkg_cv_ALSA_CFLAGS="$ALSA_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"alsa\""; } >&5 ($PKG_CONFIG --exists --print-errors "alsa") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_ALSA_CFLAGS=`$PKG_CONFIG --cflags "alsa" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$ALSA_LIBS"; then pkg_cv_ALSA_LIBS="$ALSA_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"alsa\""; } >&5 ($PKG_CONFIG --exists --print-errors "alsa") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_ALSA_LIBS=`$PKG_CONFIG --libs "alsa" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then ALSA_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "alsa" 2>&1` else ALSA_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "alsa" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$ALSA_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (alsa) were not met: $ALSA_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables ALSA_CFLAGS and ALSA_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables ALSA_CFLAGS and ALSA_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else ALSA_CFLAGS=$pkg_cv_ALSA_CFLAGS ALSA_LIBS=$pkg_cv_ALSA_LIBS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } fi fi # Check whether --enable-obex was given. if test ${enable_obex+y} then : enableval=$enable_obex; enable_obex=${enableval} fi if (test "${enable_obex}" != "no"); then pkg_failed=no { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for libicalvcal >= 4.0.0" >&5 printf %s "checking for libicalvcal >= 4.0.0... " >&6; } if test -n "$ICAL_CFLAGS"; then pkg_cv_ICAL_CFLAGS="$ICAL_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libicalvcal >= 4.0.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "libicalvcal >= 4.0.0") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_ICAL_CFLAGS=`$PKG_CONFIG --cflags "libicalvcal >= 4.0.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$ICAL_LIBS"; then pkg_cv_ICAL_LIBS="$ICAL_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libicalvcal >= 4.0.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "libicalvcal >= 4.0.0") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_ICAL_LIBS=`$PKG_CONFIG --libs "libicalvcal >= 4.0.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then ICAL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libicalvcal >= 4.0.0" 2>&1` else ICAL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libicalvcal >= 4.0.0" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$ICAL_PKG_ERRORS" >&5 pkg_failed=no { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for libical" >&5 printf %s "checking for libical... " >&6; } if test -n "$ICAL_CFLAGS"; then pkg_cv_ICAL_CFLAGS="$ICAL_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libical\""; } >&5 ($PKG_CONFIG --exists --print-errors "libical") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_ICAL_CFLAGS=`$PKG_CONFIG --cflags "libical" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$ICAL_LIBS"; then pkg_cv_ICAL_LIBS="$ICAL_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libical\""; } >&5 ($PKG_CONFIG --exists --print-errors "libical") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_ICAL_LIBS=`$PKG_CONFIG --libs "libical" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then ICAL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libical" 2>&1` else ICAL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libical" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$ICAL_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libical) were not met: $ICAL_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables ICAL_CFLAGS and ICAL_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables ICAL_CFLAGS and ICAL_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else ICAL_CFLAGS=$pkg_cv_ICAL_CFLAGS ICAL_LIBS=$pkg_cv_ICAL_LIBS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } fi elif test $pkg_failed = untried; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } pkg_failed=no { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for libical" >&5 printf %s "checking for libical... " >&6; } if test -n "$ICAL_CFLAGS"; then pkg_cv_ICAL_CFLAGS="$ICAL_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libical\""; } >&5 ($PKG_CONFIG --exists --print-errors "libical") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_ICAL_CFLAGS=`$PKG_CONFIG --cflags "libical" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$ICAL_LIBS"; then pkg_cv_ICAL_LIBS="$ICAL_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libical\""; } >&5 ($PKG_CONFIG --exists --print-errors "libical") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_ICAL_LIBS=`$PKG_CONFIG --libs "libical" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then ICAL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libical" 2>&1` else ICAL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libical" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$ICAL_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libical) were not met: $ICAL_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables ICAL_CFLAGS and ICAL_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables ICAL_CFLAGS and ICAL_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else ICAL_CFLAGS=$pkg_cv_ICAL_CFLAGS ICAL_LIBS=$pkg_cv_ICAL_LIBS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } fi else ICAL_CFLAGS=$pkg_cv_ICAL_CFLAGS ICAL_LIBS=$pkg_cv_ICAL_LIBS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } fi fi if test "${enable_obex}" != "no"; then OBEX_TRUE= OBEX_FALSE='#' else OBEX_TRUE='#' OBEX_FALSE= fi # Check whether --enable-btpclient was given. if test ${enable_btpclient+y} then : enableval=$enable_btpclient; enable_btpclient=${enableval} fi if test "${enable_btpclient}" = "yes"; then BTPCLIENT_TRUE= BTPCLIENT_FALSE='#' else BTPCLIENT_TRUE='#' BTPCLIENT_FALSE= fi # Check whether --enable-external_ell was given. if test ${enable_external_ell+y} then : enableval=$enable_external_ell; enable_external_ell=${enableval} fi if (test "${enable_external_ell}" = "yes"); then pkg_failed=no { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for ell >= 0.39" >&5 printf %s "checking for ell >= 0.39... " >&6; } if test -n "$ELL_CFLAGS"; then pkg_cv_ELL_CFLAGS="$ELL_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ell >= 0.39\""; } >&5 ($PKG_CONFIG --exists --print-errors "ell >= 0.39") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_ELL_CFLAGS=`$PKG_CONFIG --cflags "ell >= 0.39" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$ELL_LIBS"; then pkg_cv_ELL_LIBS="$ELL_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ell >= 0.39\""; } >&5 ($PKG_CONFIG --exists --print-errors "ell >= 0.39") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_ELL_LIBS=`$PKG_CONFIG --libs "ell >= 0.39" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then ELL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "ell >= 0.39" 2>&1` else ELL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "ell >= 0.39" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$ELL_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (ell >= 0.39) were not met: $ELL_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables ELL_CFLAGS and ELL_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables ELL_CFLAGS and ELL_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else ELL_CFLAGS=$pkg_cv_ELL_CFLAGS ELL_LIBS=$pkg_cv_ELL_LIBS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } fi fi if (test "${enable_external_ell}" != "yes" && (test "${enable_btpclient}" = "yes" || test "${enable_mesh}" = "yes")); then if (test ! -f ${srcdir}/ell/ell.h) && (test ! -f ${srcdir}/../ell/ell/ell.h); then as_fn_error $? "ELL source is required or use --enable-external-ell" "$LINENO" 5 fi fi if test "${enable_external_ell}" = "yes" || (test "${enable_btpclient}" != "yes" && test "${enable_mesh}" != "yes"); then EXTERNAL_ELL_TRUE= EXTERNAL_ELL_FALSE='#' else EXTERNAL_ELL_TRUE='#' EXTERNAL_ELL_FALSE= fi if test "${enable_btpclient}" = "yes" || test "${enable_mesh}" = "yes"; then LIBSHARED_ELL_TRUE= LIBSHARED_ELL_FALSE='#' else LIBSHARED_ELL_TRUE='#' LIBSHARED_ELL_FALSE= fi # Check whether --enable-client was given. if test ${enable_client+y} then : enableval=$enable_client; enable_client=${enableval} fi if test "${enable_client}" != "no"; then CLIENT_TRUE= CLIENT_FALSE='#' else CLIENT_TRUE='#' CLIENT_FALSE= fi if (test "${enable_client}" != "no" || test "${enable_mesh}" = "yes"); then for ac_header in readline/readline.h do : ac_fn_c_check_header_compile "$LINENO" "readline/readline.h" "ac_cv_header_readline_readline_h" "$ac_includes_default" if test "x$ac_cv_header_readline_readline_h" = xyes then : printf '%s\n' "#define HAVE_READLINE_READLINE_H 1" >>confdefs.h enable_readline=yes else case e in #( e) as_fn_error $? "readline header files are required" "$LINENO" 5 ;; esac fi done fi if test "${enable_readline}" = "yes"; then READLINE_TRUE= READLINE_FALSE='#' else READLINE_TRUE='#' READLINE_FALSE= fi # Check whether --enable-systemd was given. if test ${enable_systemd+y} then : enableval=$enable_systemd; enable_systemd=${enableval} fi if test "${enable_systemd}" != "no"; then SYSTEMD_TRUE= SYSTEMD_FALSE='#' else SYSTEMD_TRUE='#' SYSTEMD_FALSE= fi # Check whether --with-systemdsystemunitdir was given. if test ${with_systemdsystemunitdir+y} then : withval=$with_systemdsystemunitdir; path_systemunitdir=${withval} fi if (test "${enable_systemd}" != "no" && test -z "${path_systemunitdir}"); then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking systemd system unit dir" >&5 printf %s "checking systemd system unit dir... " >&6; } path_systemunitdir="`$PKG_CONFIG --variable=systemdsystemunitdir systemd`" if (test -z "${path_systemunitdir}"); then as_fn_error $? "systemd system unit directory is required" "$LINENO" 5 fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: ${path_systemunitdir}" >&5 printf '%s\n' "${path_systemunitdir}" >&6; } fi SYSTEMD_SYSTEMUNITDIR=${path_systemunitdir} # Check whether --with-systemduserunitdir was given. if test ${with_systemduserunitdir+y} then : withval=$with_systemduserunitdir; path_userunitdir=${withval} fi if (test "${enable_systemd}" != "no" && test -z "${path_userunitdir}"); then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking systemd user unit dir" >&5 printf %s "checking systemd user unit dir... " >&6; } path_userunitdir="`$PKG_CONFIG --variable=systemduserunitdir systemd`" if (test -z "${path_userunitdir}"); then as_fn_error $? "systemd user unit directory is required" "$LINENO" 5 fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: ${path_userunitdir}" >&5 printf '%s\n' "${path_userunitdir}" >&6; } fi SYSTEMD_USERUNITDIR=${path_userunitdir} # Check whether --enable-datafiles was given. if test ${enable_datafiles+y} then : enableval=$enable_datafiles; enable_datafiles=${enableval} fi if test "${enable_datafiles}" != "no"; then DATAFILES_TRUE= DATAFILES_FALSE='#' else DATAFILES_TRUE='#' DATAFILES_FALSE= fi # Check whether --enable-manpages was given. if test ${enable_manpages+y} then : enableval=$enable_manpages; enable_manpages=${enableval} fi if (test "${enable_manpages}" != "no"); then for ac_prog in rst2man rst2man.py do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_RST2MAN+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$RST2MAN"; then ac_cv_prog_RST2MAN="$RST2MAN" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_RST2MAN="$ac_prog" printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi RST2MAN=$ac_cv_prog_RST2MAN if test -n "$RST2MAN"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $RST2MAN" >&5 printf '%s\n' "$RST2MAN" >&6; } else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } fi test -n "$RST2MAN" && break done test -n "$RST2MAN" || RST2MAN=""no"" if (test "${RST2MAN}" = "no" ); then as_fn_error $? "rst2man is required" "$LINENO" 5 fi fi if test "${enable_manpages}" != "no"; then MANPAGES_TRUE= MANPAGES_FALSE='#' else MANPAGES_TRUE='#' MANPAGES_FALSE= fi if test "${enable_manpages}" != "no" && test "${RST2MAN}" != "no"; then RUN_RST2MAN_TRUE= RUN_RST2MAN_FALSE='#' else RUN_RST2MAN_TRUE='#' RUN_RST2MAN_FALSE= fi # Check whether --enable-testing was given. if test ${enable_testing+y} then : enableval=$enable_testing; enable_testing=${enableval} fi if test "${enable_testing}" = "yes"; then TESTING_TRUE= TESTING_FALSE='#' else TESTING_TRUE='#' TESTING_FALSE= fi if (test "${enable_testing}" = "yes"); then ac_fn_check_decl "$LINENO" "SOF_TIMESTAMPING_TX_COMPLETION" "ac_cv_have_decl_SOF_TIMESTAMPING_TX_COMPLETION" "#include #include #include " "$ac_c_undeclared_builtin_options$ac_c_future_darwin_options" "CFLAGS" if test "x$ac_cv_have_decl_SOF_TIMESTAMPING_TX_COMPLETION" = xyes then : ac_have_decl=1 else case e in #( e) ac_have_decl=0 ;; esac fi printf '%s\n' "#define HAVE_DECL_SOF_TIMESTAMPING_TX_COMPLETION $ac_have_decl" >>confdefs.h ac_fn_check_decl "$LINENO" "SCM_TSTAMP_COMPLETION" "ac_cv_have_decl_SCM_TSTAMP_COMPLETION" "#include #include #include " "$ac_c_undeclared_builtin_options$ac_c_future_darwin_options" "CFLAGS" if test "x$ac_cv_have_decl_SCM_TSTAMP_COMPLETION" = xyes then : ac_have_decl=1 else case e in #( e) ac_have_decl=0 ;; esac fi printf '%s\n' "#define HAVE_DECL_SCM_TSTAMP_COMPLETION $ac_have_decl" >>confdefs.h fi # Check whether --enable-experimental was given. if test ${enable_experimental+y} then : enableval=$enable_experimental; enable_experimental=${enableval} fi if test "${enable_experimental}" = "yes"; then EXPERIMENTAL_TRUE= EXPERIMENTAL_FALSE='#' else EXPERIMENTAL_TRUE='#' EXPERIMENTAL_FALSE= fi # Check whether --enable-deprecated was given. if test ${enable_deprecated+y} then : enableval=$enable_deprecated; enable_deprecated=${enableval} fi if test "${enable_deprecated}" = "yes"; then DEPRECATED_TRUE= DEPRECATED_FALSE='#' else DEPRECATED_TRUE='#' DEPRECATED_FALSE= fi # Check whether --enable-external-plugins was given. if test ${enable_external_plugins+y} then : enableval=$enable_external_plugins; enable_external_plugins=${enableval} fi if test "${enable_external_plugins}" = "yes"; then EXTERNAL_PLUGINS_TRUE= EXTERNAL_PLUGINS_FALSE='#' else EXTERNAL_PLUGINS_TRUE='#' EXTERNAL_PLUGINS_FALSE= fi if (test "${enable_external_plugins}" = "yes"); then printf '%s\n' "#define EXTERNAL_PLUGINS 1" >>confdefs.h else printf '%s\n' "#define EXTERNAL_PLUGINS 0" >>confdefs.h fi # Check whether --enable-sixaxis was given. if test ${enable_sixaxis+y} then : enableval=$enable_sixaxis; enable_sixaxis=${enableval} fi if test "${enable_sixaxis}" = "yes" && test "${enable_udev}" != "no"; then SIXAXIS_TRUE= SIXAXIS_FALSE='#' else SIXAXIS_TRUE='#' SIXAXIS_FALSE= fi # Check whether --enable-hid2hci was given. if test ${enable_hid2hci+y} then : enableval=$enable_hid2hci; enable_hid2hci=${enableval} fi if test "${enable_hid2hci}" = "yes" && test "${enable_udev}" != "no"; then HID2HCI_TRUE= HID2HCI_FALSE='#' else HID2HCI_TRUE='#' HID2HCI_FALSE= fi # Check whether --enable-logger was given. if test ${enable_logger+y} then : enableval=$enable_logger; enable_logger=${enableval} fi if test "${enable_logger}" = "yes"; then LOGGER_TRUE= LOGGER_FALSE='#' else LOGGER_TRUE='#' LOGGER_FALSE= fi # Check whether --enable-admin was given. if test ${enable_admin+y} then : enableval=$enable_admin; enable_admin=${enableval} fi if test "${enable_admin}" = "yes"; then ADMIN_TRUE= ADMIN_FALSE='#' else ADMIN_TRUE='#' ADMIN_FALSE= fi if (test "${prefix}" = "NONE"); then if (test "$localstatedir" = '${prefix}/var'); then localstatedir='/var' fi prefix="${ac_default_prefix}" fi if (test "${exec_prefix}" = "NONE"); then # exec_prefix defaults to prefix, although our manual handling of the # latter (above) confuses autoconf. Manually set the exec_prefix. exec_prefix="${prefix}" fi # Expand any variables containing relative references like ${prefix} and co. # # Otherwise we'll end up with literal references in the final binaries or # manuals, which is not something we really want. # pkgbindir="${bindir}" if (test "$bindir" = '${exec_prefix}/bin'); then pkgbindir="${exec_prefix}/bin" else pkgbindir="${bindir}" fi PKGBINDIR="${pkgbindir}" if (test "$libexecdir" = '${exec_prefix}/libexec'); then pkglibexecdir="${exec_prefix}/libexec/bluetooth" else pkglibexecdir="${libexecdir}/bluetooth" fi PKGLIBEXECDIR="${pkglibexecdir}" if (test "$localstatedir" = '${prefix}/var'); then storagedir="${prefix}/var/lib/bluetooth" else storagedir="${localstatedir}/lib/bluetooth" fi cat >>confdefs.h <<_ACEOF #define STORAGEDIR "${storagedir}" _ACEOF if (test "$sysconfdir" = '${prefix}/etc'); then configdir="${prefix}/etc/bluetooth" else configdir="${sysconfdir}/bluetooth" fi cat >>confdefs.h <<_ACEOF #define CONFIGDIR "${configdir}" _ACEOF CONFIGDIR="${configdir}" cat >>confdefs.h <<_ACEOF #define MESH_STORAGEDIR "${storagedir}/mesh" _ACEOF MESH_STORAGEDIR="${storagedir}/mesh" # Check whether --with-phonebook was given. if test ${with_phonebook+y} then : withval=$with_phonebook; plugin_phonebook=${withval} fi if (test -z "${plugin_phonebook}"); then plugin_phonebook=dummy fi if (test "${plugin_phonebook}" = "ebook"); then pkg_failed=no { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for libebook-1.2 >= 3.3" >&5 printf %s "checking for libebook-1.2 >= 3.3... " >&6; } if test -n "$LIBEBOOK_CFLAGS"; then pkg_cv_LIBEBOOK_CFLAGS="$LIBEBOOK_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libebook-1.2 >= 3.3\""; } >&5 ($PKG_CONFIG --exists --print-errors "libebook-1.2 >= 3.3") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_LIBEBOOK_CFLAGS=`$PKG_CONFIG --cflags "libebook-1.2 >= 3.3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$LIBEBOOK_LIBS"; then pkg_cv_LIBEBOOK_LIBS="$LIBEBOOK_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libebook-1.2 >= 3.3\""; } >&5 ($PKG_CONFIG --exists --print-errors "libebook-1.2 >= 3.3") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_LIBEBOOK_LIBS=`$PKG_CONFIG --libs "libebook-1.2 >= 3.3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then LIBEBOOK_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libebook-1.2 >= 3.3" 2>&1` else LIBEBOOK_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libebook-1.2 >= 3.3" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$LIBEBOOK_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libebook-1.2 >= 3.3) were not met: $LIBEBOOK_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables LIBEBOOK_CFLAGS and LIBEBOOK_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables LIBEBOOK_CFLAGS and LIBEBOOK_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else LIBEBOOK_CFLAGS=$pkg_cv_LIBEBOOK_CFLAGS LIBEBOOK_LIBS=$pkg_cv_LIBEBOOK_LIBS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } fi pkg_failed=no { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for libedataserver-1.2 >= 3.3" >&5 printf %s "checking for libedataserver-1.2 >= 3.3... " >&6; } if test -n "$LIBEDATESERVER_CFLAGS"; then pkg_cv_LIBEDATESERVER_CFLAGS="$LIBEDATESERVER_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libedataserver-1.2 >= 3.3\""; } >&5 ($PKG_CONFIG --exists --print-errors "libedataserver-1.2 >= 3.3") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_LIBEDATESERVER_CFLAGS=`$PKG_CONFIG --cflags "libedataserver-1.2 >= 3.3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$LIBEDATESERVER_LIBS"; then pkg_cv_LIBEDATESERVER_LIBS="$LIBEDATESERVER_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libedataserver-1.2 >= 3.3\""; } >&5 ($PKG_CONFIG --exists --print-errors "libedataserver-1.2 >= 3.3") 2>&5 ac_status=$? printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_LIBEDATESERVER_LIBS=`$PKG_CONFIG --libs "libedataserver-1.2 >= 3.3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then LIBEDATESERVER_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libedataserver-1.2 >= 3.3" 2>&1` else LIBEDATESERVER_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libedataserver-1.2 >= 3.3" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$LIBEDATESERVER_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libedataserver-1.2 >= 3.3) were not met: $LIBEDATESERVER_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables LIBEDATESERVER_CFLAGS and LIBEDATESERVER_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf '%s\n' "no" >&6; } { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables LIBEDATESERVER_CFLAGS and LIBEDATESERVER_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else LIBEDATESERVER_CFLAGS=$pkg_cv_LIBEDATESERVER_CFLAGS LIBEDATESERVER_LIBS=$pkg_cv_LIBEDATESERVER_LIBS { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf '%s\n' "yes" >&6; } fi fi PLUGIN_PHONEBOOK=${plugin_phonebook} ac_config_files="$ac_config_files lib/bluez.pc Makefile doc/bluetooth-meshd.rst mesh/bluetooth-mesh.service obexd/src/obex.service obexd/src/org.bluez.obex.service doc/bluetoothd.rst src/bluetooth.service tools/bluetooth-logger.service tools/mpris-proxy.service" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # 'ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* 'ac_cv_foo' will be assigned the # following values. _ACEOF ac_cache_dump | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 printf '%s\n' "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi else { printf '%s\n' "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 printf '%s\n' "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' DEFS=-DHAVE_CONFIG_H ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`printf '%s\n' "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking that generated files are newer than configure" >&5 printf %s "checking that generated files are newer than configure... " >&6; } if test -n "$am_sleep_pid"; then # Hide warnings about reused PIDs. wait $am_sleep_pid 2>/dev/null fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: done" >&5 printf '%s\n' "done" >&6; } case $enable_silent_rules in # ((( yes) AM_DEFAULT_VERBOSITY=0;; no) AM_DEFAULT_VERBOSITY=1;; esac if test $am_cv_make_support_nested_variables = yes; then AM_V='$(V)' AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' else AM_V=$AM_DEFAULT_VERBOSITY AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY fi if test -n "$EXEEXT"; then am__EXEEXT_TRUE= am__EXEEXT_FALSE='#' else am__EXEEXT_TRUE='#' am__EXEEXT_FALSE= fi if test -z "${MAINTAINER_MODE_TRUE}" && test -z "${MAINTAINER_MODE_FALSE}"; then as_fn_error $? "conditional \"MAINTAINER_MODE\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then as_fn_error $? "conditional \"AMDEP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then as_fn_error $? "conditional \"am__fastdepCC\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then as_fn_error $? "conditional \"am__fastdepCC\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${COVERAGE_TRUE}" && test -z "${COVERAGE_FALSE}"; then as_fn_error $? "conditional \"COVERAGE\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${DBUS_RUN_SESSION_TRUE}" && test -z "${DBUS_RUN_SESSION_FALSE}"; then as_fn_error $? "conditional \"DBUS_RUN_SESSION\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${VALGRIND_TRUE}" && test -z "${VALGRIND_FALSE}"; then as_fn_error $? "conditional \"VALGRIND\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${ZSH_COMPLETIONS_TRUE}" && test -z "${ZSH_COMPLETIONS_FALSE}"; then as_fn_error $? "conditional \"ZSH_COMPLETIONS\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${LIBRARY_TRUE}" && test -z "${LIBRARY_FALSE}"; then as_fn_error $? "conditional \"LIBRARY\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${TEST_TRUE}" && test -z "${TEST_FALSE}"; then as_fn_error $? "conditional \"TEST\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${NFC_TRUE}" && test -z "${NFC_FALSE}"; then as_fn_error $? "conditional \"NFC\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${A2DP_TRUE}" && test -z "${A2DP_FALSE}"; then as_fn_error $? "conditional \"A2DP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${AVRCP_TRUE}" && test -z "${AVRCP_FALSE}"; then as_fn_error $? "conditional \"AVRCP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${NETWORK_TRUE}" && test -z "${NETWORK_FALSE}"; then as_fn_error $? "conditional \"NETWORK\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${HID_TRUE}" && test -z "${HID_FALSE}"; then as_fn_error $? "conditional \"HID\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${HOG_TRUE}" && test -z "${HOG_FALSE}"; then as_fn_error $? "conditional \"HOG\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${BAP_TRUE}" && test -z "${BAP_FALSE}"; then as_fn_error $? "conditional \"BAP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${BASS_TRUE}" && test -z "${BASS_FALSE}"; then as_fn_error $? "conditional \"BASS\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${MCP_TRUE}" && test -z "${MCP_FALSE}"; then as_fn_error $? "conditional \"MCP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${CCP_TRUE}" && test -z "${CCP_FALSE}"; then as_fn_error $? "conditional \"CCP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${VCP_TRUE}" && test -z "${VCP_FALSE}"; then as_fn_error $? "conditional \"VCP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${MICP_TRUE}" && test -z "${MICP_FALSE}"; then as_fn_error $? "conditional \"MICP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${CSIP_TRUE}" && test -z "${CSIP_FALSE}"; then as_fn_error $? "conditional \"CSIP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${TMAP_TRUE}" && test -z "${TMAP_FALSE}"; then as_fn_error $? "conditional \"TMAP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${GMAP_TRUE}" && test -z "${GMAP_FALSE}"; then as_fn_error $? "conditional \"GMAP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${ASHA_TRUE}" && test -z "${ASHA_FALSE}"; then as_fn_error $? "conditional \"ASHA\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${HFP_TRUE}" && test -z "${HFP_FALSE}"; then as_fn_error $? "conditional \"HFP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${TOOLS_TRUE}" && test -z "${TOOLS_FALSE}"; then as_fn_error $? "conditional \"TOOLS\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${MONITOR_TRUE}" && test -z "${MONITOR_FALSE}"; then as_fn_error $? "conditional \"MONITOR\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${CUPS_TRUE}" && test -z "${CUPS_FALSE}"; then as_fn_error $? "conditional \"CUPS\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${CUPS_SERVERBIN_TRUE}" && test -z "${CUPS_SERVERBIN_FALSE}"; then as_fn_error $? "conditional \"CUPS_SERVERBIN\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${MESH_TRUE}" && test -z "${MESH_FALSE}"; then as_fn_error $? "conditional \"MESH\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${MIDI_TRUE}" && test -z "${MIDI_FALSE}"; then as_fn_error $? "conditional \"MIDI\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${OBEX_TRUE}" && test -z "${OBEX_FALSE}"; then as_fn_error $? "conditional \"OBEX\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${BTPCLIENT_TRUE}" && test -z "${BTPCLIENT_FALSE}"; then as_fn_error $? "conditional \"BTPCLIENT\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${EXTERNAL_ELL_TRUE}" && test -z "${EXTERNAL_ELL_FALSE}"; then as_fn_error $? "conditional \"EXTERNAL_ELL\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${LIBSHARED_ELL_TRUE}" && test -z "${LIBSHARED_ELL_FALSE}"; then as_fn_error $? "conditional \"LIBSHARED_ELL\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${CLIENT_TRUE}" && test -z "${CLIENT_FALSE}"; then as_fn_error $? "conditional \"CLIENT\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${READLINE_TRUE}" && test -z "${READLINE_FALSE}"; then as_fn_error $? "conditional \"READLINE\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${SYSTEMD_TRUE}" && test -z "${SYSTEMD_FALSE}"; then as_fn_error $? "conditional \"SYSTEMD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${DATAFILES_TRUE}" && test -z "${DATAFILES_FALSE}"; then as_fn_error $? "conditional \"DATAFILES\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${MANPAGES_TRUE}" && test -z "${MANPAGES_FALSE}"; then as_fn_error $? "conditional \"MANPAGES\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${RUN_RST2MAN_TRUE}" && test -z "${RUN_RST2MAN_FALSE}"; then as_fn_error $? "conditional \"RUN_RST2MAN\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${TESTING_TRUE}" && test -z "${TESTING_FALSE}"; then as_fn_error $? "conditional \"TESTING\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${EXPERIMENTAL_TRUE}" && test -z "${EXPERIMENTAL_FALSE}"; then as_fn_error $? "conditional \"EXPERIMENTAL\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${DEPRECATED_TRUE}" && test -z "${DEPRECATED_FALSE}"; then as_fn_error $? "conditional \"DEPRECATED\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${EXTERNAL_PLUGINS_TRUE}" && test -z "${EXTERNAL_PLUGINS_FALSE}"; then as_fn_error $? "conditional \"EXTERNAL_PLUGINS\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${SIXAXIS_TRUE}" && test -z "${SIXAXIS_FALSE}"; then as_fn_error $? "conditional \"SIXAXIS\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${HID2HCI_TRUE}" && test -z "${HID2HCI_FALSE}"; then as_fn_error $? "conditional \"HID2HCI\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${LOGGER_TRUE}" && test -z "${LOGGER_FALSE}"; then as_fn_error $? "conditional \"LOGGER\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${ADMIN_TRUE}" && test -z "${ADMIN_FALSE}"; then as_fn_error $? "conditional \"ADMIN\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi : "${CONFIG_STATUS=./config.status}" case $CONFIG_STATUS in #( -*) : CONFIG_STATUS=./$CONFIG_STATUS ;; #( */*) : ;; #( *) : CONFIG_STATUS=./$CONFIG_STATUS ;; esac ac_write_fail=0 ac_clean_CONFIG_STATUS='"$CONFIG_STATUS"' { printf '%s\n' "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 printf '%s\n' "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >"$CONFIG_STATUS" <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>"$CONFIG_STATUS" <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # contradicts POSIX and common usage. Disable this. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case e in #( e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf '%s\n' "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf '%s\n' "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else case e in #( e) as_fn_append () { eval $1=\$$1\$2 } ;; esac fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else case e in #( e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } ;; esac fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf '%s\n' X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf '%s\n' "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf '%s\n' X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" as_tr_sh="eval sed '$as_sed_sh'" # deprecated exec 6>&1 ## ------------------------------------- ## ## Main body of "$CONFIG_STATUS" script. ## ## ------------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x "$CONFIG_STATUS" || ac_write_fail=1 cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by bluez $as_me 5.87, which was generated by GNU Autoconf 2.73. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac case $ac_config_headers in *" "*) set x $ac_config_headers; shift; ac_config_headers=$*;; esac cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" config_headers="$ac_config_headers" config_commands="$ac_config_commands" _ACEOF cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ '$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE --header=FILE[:TEMPLATE] instantiate the configuration header FILE Configuration files: $config_files Configuration headers: $config_headers Configuration commands: $config_commands Report bugs to the package provider." _ACEOF ac_cs_config=`printf '%s\n' "$ac_configure_args" | sed "$ac_safe_unquote"` ac_cs_config_escaped=`printf '%s\n' "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ bluez config.status 5.87 configured by $0, generated by GNU Autoconf 2.73, with options \\"\$ac_cs_config\\" Copyright (C) 2026 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' INSTALL='$INSTALL' MKDIR_P='$MKDIR_P' AWK='$AWK' test -n "\$AWK" || { awk '' >"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) printf '%s\n' "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) printf '%s\n' "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`printf '%s\n' "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --header | --heade | --head | --hea ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`printf '%s\n' "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append CONFIG_HEADERS " '$ac_optarg'" ac_need_defaults=false;; --he | --h) # Conflict between --help and --header as_fn_error $? "ambiguous option: '$1' Try '$0 --help' for more information.";; --help | --hel | -h ) printf '%s\n' "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: '$1' Try '$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \printf '%s\n' "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX printf '%s\n' "$ac_log" } >&5 _ACEOF cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 # # INIT-COMMANDS # AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}" # The HP-UX ksh and POSIX shell print the target directory to stdout # if CDPATH is set. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH sed_quote_subst='$sed_quote_subst' double_quote_subst='$double_quote_subst' delay_variable_subst='$delay_variable_subst' macro_version='`$ECHO "$macro_version" | $SED "$delay_single_quote_subst"`' macro_revision='`$ECHO "$macro_revision" | $SED "$delay_single_quote_subst"`' enable_static='`$ECHO "$enable_static" | $SED "$delay_single_quote_subst"`' enable_shared='`$ECHO "$enable_shared" | $SED "$delay_single_quote_subst"`' pic_mode='`$ECHO "$pic_mode" | $SED "$delay_single_quote_subst"`' enable_fast_install='`$ECHO "$enable_fast_install" | $SED "$delay_single_quote_subst"`' shared_archive_member_spec='`$ECHO "$shared_archive_member_spec" | $SED "$delay_single_quote_subst"`' SHELL='`$ECHO "$SHELL" | $SED "$delay_single_quote_subst"`' ECHO='`$ECHO "$ECHO" | $SED "$delay_single_quote_subst"`' PATH_SEPARATOR='`$ECHO "$PATH_SEPARATOR" | $SED "$delay_single_quote_subst"`' host_alias='`$ECHO "$host_alias" | $SED "$delay_single_quote_subst"`' host='`$ECHO "$host" | $SED "$delay_single_quote_subst"`' host_os='`$ECHO "$host_os" | $SED "$delay_single_quote_subst"`' build_alias='`$ECHO "$build_alias" | $SED "$delay_single_quote_subst"`' build='`$ECHO "$build" | $SED "$delay_single_quote_subst"`' build_os='`$ECHO "$build_os" | $SED "$delay_single_quote_subst"`' SED='`$ECHO "$SED" | $SED "$delay_single_quote_subst"`' Xsed='`$ECHO "$Xsed" | $SED "$delay_single_quote_subst"`' GREP='`$ECHO "$GREP" | $SED "$delay_single_quote_subst"`' EGREP='`$ECHO "$EGREP" | $SED "$delay_single_quote_subst"`' FGREP='`$ECHO "$FGREP" | $SED "$delay_single_quote_subst"`' LD='`$ECHO "$LD" | $SED "$delay_single_quote_subst"`' NM='`$ECHO "$NM" | $SED "$delay_single_quote_subst"`' LN_S='`$ECHO "$LN_S" | $SED "$delay_single_quote_subst"`' max_cmd_len='`$ECHO "$max_cmd_len" | $SED "$delay_single_quote_subst"`' ac_objext='`$ECHO "$ac_objext" | $SED "$delay_single_quote_subst"`' exeext='`$ECHO "$exeext" | $SED "$delay_single_quote_subst"`' lt_unset='`$ECHO "$lt_unset" | $SED "$delay_single_quote_subst"`' lt_SP2NL='`$ECHO "$lt_SP2NL" | $SED "$delay_single_quote_subst"`' lt_NL2SP='`$ECHO "$lt_NL2SP" | $SED "$delay_single_quote_subst"`' lt_cv_to_host_file_cmd='`$ECHO "$lt_cv_to_host_file_cmd" | $SED "$delay_single_quote_subst"`' lt_cv_to_tool_file_cmd='`$ECHO "$lt_cv_to_tool_file_cmd" | $SED "$delay_single_quote_subst"`' reload_flag='`$ECHO "$reload_flag" | $SED "$delay_single_quote_subst"`' reload_cmds='`$ECHO "$reload_cmds" | $SED "$delay_single_quote_subst"`' FILECMD='`$ECHO "$FILECMD" | $SED "$delay_single_quote_subst"`' OBJDUMP='`$ECHO "$OBJDUMP" | $SED "$delay_single_quote_subst"`' deplibs_check_method='`$ECHO "$deplibs_check_method" | $SED "$delay_single_quote_subst"`' file_magic_cmd='`$ECHO "$file_magic_cmd" | $SED "$delay_single_quote_subst"`' file_magic_glob='`$ECHO "$file_magic_glob" | $SED "$delay_single_quote_subst"`' want_nocaseglob='`$ECHO "$want_nocaseglob" | $SED "$delay_single_quote_subst"`' DLLTOOL='`$ECHO "$DLLTOOL" | $SED "$delay_single_quote_subst"`' sharedlib_from_linklib_cmd='`$ECHO "$sharedlib_from_linklib_cmd" | $SED "$delay_single_quote_subst"`' AR='`$ECHO "$AR" | $SED "$delay_single_quote_subst"`' lt_ar_flags='`$ECHO "$lt_ar_flags" | $SED "$delay_single_quote_subst"`' AR_FLAGS='`$ECHO "$AR_FLAGS" | $SED "$delay_single_quote_subst"`' archiver_list_spec='`$ECHO "$archiver_list_spec" | $SED "$delay_single_quote_subst"`' STRIP='`$ECHO "$STRIP" | $SED "$delay_single_quote_subst"`' RANLIB='`$ECHO "$RANLIB" | $SED "$delay_single_quote_subst"`' old_postinstall_cmds='`$ECHO "$old_postinstall_cmds" | $SED "$delay_single_quote_subst"`' old_postuninstall_cmds='`$ECHO "$old_postuninstall_cmds" | $SED "$delay_single_quote_subst"`' old_archive_cmds='`$ECHO "$old_archive_cmds" | $SED "$delay_single_quote_subst"`' lock_old_archive_extraction='`$ECHO "$lock_old_archive_extraction" | $SED "$delay_single_quote_subst"`' CC='`$ECHO "$CC" | $SED "$delay_single_quote_subst"`' CFLAGS='`$ECHO "$CFLAGS" | $SED "$delay_single_quote_subst"`' compiler='`$ECHO "$compiler" | $SED "$delay_single_quote_subst"`' GCC='`$ECHO "$GCC" | $SED "$delay_single_quote_subst"`' lt_cv_sys_global_symbol_pipe='`$ECHO "$lt_cv_sys_global_symbol_pipe" | $SED "$delay_single_quote_subst"`' lt_cv_sys_global_symbol_to_cdecl='`$ECHO "$lt_cv_sys_global_symbol_to_cdecl" | $SED "$delay_single_quote_subst"`' lt_cv_sys_global_symbol_to_import='`$ECHO "$lt_cv_sys_global_symbol_to_import" | $SED "$delay_single_quote_subst"`' lt_cv_sys_global_symbol_to_c_name_address='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address" | $SED "$delay_single_quote_subst"`' lt_cv_sys_global_symbol_to_c_name_address_lib_prefix='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address_lib_prefix" | $SED "$delay_single_quote_subst"`' lt_cv_nm_interface='`$ECHO "$lt_cv_nm_interface" | $SED "$delay_single_quote_subst"`' nm_file_list_spec='`$ECHO "$nm_file_list_spec" | $SED "$delay_single_quote_subst"`' lt_sysroot='`$ECHO "$lt_sysroot" | $SED "$delay_single_quote_subst"`' lt_cv_truncate_bin='`$ECHO "$lt_cv_truncate_bin" | $SED "$delay_single_quote_subst"`' objdir='`$ECHO "$objdir" | $SED "$delay_single_quote_subst"`' MAGIC_CMD='`$ECHO "$MAGIC_CMD" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_no_builtin_flag='`$ECHO "$lt_prog_compiler_no_builtin_flag" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_pic='`$ECHO "$lt_prog_compiler_pic" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_wl='`$ECHO "$lt_prog_compiler_wl" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_static='`$ECHO "$lt_prog_compiler_static" | $SED "$delay_single_quote_subst"`' lt_cv_prog_compiler_c_o='`$ECHO "$lt_cv_prog_compiler_c_o" | $SED "$delay_single_quote_subst"`' need_locks='`$ECHO "$need_locks" | $SED "$delay_single_quote_subst"`' MANIFEST_TOOL='`$ECHO "$MANIFEST_TOOL" | $SED "$delay_single_quote_subst"`' DSYMUTIL='`$ECHO "$DSYMUTIL" | $SED "$delay_single_quote_subst"`' NMEDIT='`$ECHO "$NMEDIT" | $SED "$delay_single_quote_subst"`' LIPO='`$ECHO "$LIPO" | $SED "$delay_single_quote_subst"`' OTOOL='`$ECHO "$OTOOL" | $SED "$delay_single_quote_subst"`' OTOOL64='`$ECHO "$OTOOL64" | $SED "$delay_single_quote_subst"`' libext='`$ECHO "$libext" | $SED "$delay_single_quote_subst"`' shrext_cmds='`$ECHO "$shrext_cmds" | $SED "$delay_single_quote_subst"`' extract_expsyms_cmds='`$ECHO "$extract_expsyms_cmds" | $SED "$delay_single_quote_subst"`' archive_cmds_need_lc='`$ECHO "$archive_cmds_need_lc" | $SED "$delay_single_quote_subst"`' enable_shared_with_static_runtimes='`$ECHO "$enable_shared_with_static_runtimes" | $SED "$delay_single_quote_subst"`' export_dynamic_flag_spec='`$ECHO "$export_dynamic_flag_spec" | $SED "$delay_single_quote_subst"`' whole_archive_flag_spec='`$ECHO "$whole_archive_flag_spec" | $SED "$delay_single_quote_subst"`' compiler_needs_object='`$ECHO "$compiler_needs_object" | $SED "$delay_single_quote_subst"`' old_archive_from_new_cmds='`$ECHO "$old_archive_from_new_cmds" | $SED "$delay_single_quote_subst"`' old_archive_from_expsyms_cmds='`$ECHO "$old_archive_from_expsyms_cmds" | $SED "$delay_single_quote_subst"`' archive_cmds='`$ECHO "$archive_cmds" | $SED "$delay_single_quote_subst"`' archive_expsym_cmds='`$ECHO "$archive_expsym_cmds" | $SED "$delay_single_quote_subst"`' module_cmds='`$ECHO "$module_cmds" | $SED "$delay_single_quote_subst"`' module_expsym_cmds='`$ECHO "$module_expsym_cmds" | $SED "$delay_single_quote_subst"`' with_gnu_ld='`$ECHO "$with_gnu_ld" | $SED "$delay_single_quote_subst"`' allow_undefined_flag='`$ECHO "$allow_undefined_flag" | $SED "$delay_single_quote_subst"`' no_undefined_flag='`$ECHO "$no_undefined_flag" | $SED "$delay_single_quote_subst"`' hardcode_libdir_flag_spec='`$ECHO "$hardcode_libdir_flag_spec" | $SED "$delay_single_quote_subst"`' hardcode_libdir_separator='`$ECHO "$hardcode_libdir_separator" | $SED "$delay_single_quote_subst"`' hardcode_direct='`$ECHO "$hardcode_direct" | $SED "$delay_single_quote_subst"`' hardcode_direct_absolute='`$ECHO "$hardcode_direct_absolute" | $SED "$delay_single_quote_subst"`' hardcode_minus_L='`$ECHO "$hardcode_minus_L" | $SED "$delay_single_quote_subst"`' hardcode_shlibpath_var='`$ECHO "$hardcode_shlibpath_var" | $SED "$delay_single_quote_subst"`' hardcode_automatic='`$ECHO "$hardcode_automatic" | $SED "$delay_single_quote_subst"`' inherit_rpath='`$ECHO "$inherit_rpath" | $SED "$delay_single_quote_subst"`' link_all_deplibs='`$ECHO "$link_all_deplibs" | $SED "$delay_single_quote_subst"`' always_export_symbols='`$ECHO "$always_export_symbols" | $SED "$delay_single_quote_subst"`' export_symbols_cmds='`$ECHO "$export_symbols_cmds" | $SED "$delay_single_quote_subst"`' exclude_expsyms='`$ECHO "$exclude_expsyms" | $SED "$delay_single_quote_subst"`' include_expsyms='`$ECHO "$include_expsyms" | $SED "$delay_single_quote_subst"`' prelink_cmds='`$ECHO "$prelink_cmds" | $SED "$delay_single_quote_subst"`' postlink_cmds='`$ECHO "$postlink_cmds" | $SED "$delay_single_quote_subst"`' file_list_spec='`$ECHO "$file_list_spec" | $SED "$delay_single_quote_subst"`' variables_saved_for_relink='`$ECHO "$variables_saved_for_relink" | $SED "$delay_single_quote_subst"`' need_lib_prefix='`$ECHO "$need_lib_prefix" | $SED "$delay_single_quote_subst"`' need_version='`$ECHO "$need_version" | $SED "$delay_single_quote_subst"`' version_type='`$ECHO "$version_type" | $SED "$delay_single_quote_subst"`' runpath_var='`$ECHO "$runpath_var" | $SED "$delay_single_quote_subst"`' shlibpath_var='`$ECHO "$shlibpath_var" | $SED "$delay_single_quote_subst"`' shlibpath_overrides_runpath='`$ECHO "$shlibpath_overrides_runpath" | $SED "$delay_single_quote_subst"`' libname_spec='`$ECHO "$libname_spec" | $SED "$delay_single_quote_subst"`' library_names_spec='`$ECHO "$library_names_spec" | $SED "$delay_single_quote_subst"`' soname_spec='`$ECHO "$soname_spec" | $SED "$delay_single_quote_subst"`' install_override_mode='`$ECHO "$install_override_mode" | $SED "$delay_single_quote_subst"`' postinstall_cmds='`$ECHO "$postinstall_cmds" | $SED "$delay_single_quote_subst"`' postuninstall_cmds='`$ECHO "$postuninstall_cmds" | $SED "$delay_single_quote_subst"`' finish_cmds='`$ECHO "$finish_cmds" | $SED "$delay_single_quote_subst"`' finish_eval='`$ECHO "$finish_eval" | $SED "$delay_single_quote_subst"`' hardcode_into_libs='`$ECHO "$hardcode_into_libs" | $SED "$delay_single_quote_subst"`' sys_lib_search_path_spec='`$ECHO "$sys_lib_search_path_spec" | $SED "$delay_single_quote_subst"`' configure_time_dlsearch_path='`$ECHO "$configure_time_dlsearch_path" | $SED "$delay_single_quote_subst"`' configure_time_lt_sys_library_path='`$ECHO "$configure_time_lt_sys_library_path" | $SED "$delay_single_quote_subst"`' hardcode_action='`$ECHO "$hardcode_action" | $SED "$delay_single_quote_subst"`' enable_dlopen='`$ECHO "$enable_dlopen" | $SED "$delay_single_quote_subst"`' enable_dlopen_self='`$ECHO "$enable_dlopen_self" | $SED "$delay_single_quote_subst"`' enable_dlopen_self_static='`$ECHO "$enable_dlopen_self_static" | $SED "$delay_single_quote_subst"`' old_striplib='`$ECHO "$old_striplib" | $SED "$delay_single_quote_subst"`' striplib='`$ECHO "$striplib" | $SED "$delay_single_quote_subst"`' LTCC='$LTCC' LTCFLAGS='$LTCFLAGS' compiler='$compiler_DEFAULT' # A function that is used when there is no print builtin or printf. func_fallback_echo () { eval 'cat <<_LTECHO_EOF \$1 _LTECHO_EOF' } # Quote evaled strings. for var in SHELL \ ECHO \ PATH_SEPARATOR \ SED \ GREP \ EGREP \ FGREP \ LD \ NM \ LN_S \ lt_SP2NL \ lt_NL2SP \ reload_flag \ FILECMD \ OBJDUMP \ deplibs_check_method \ file_magic_cmd \ file_magic_glob \ want_nocaseglob \ DLLTOOL \ sharedlib_from_linklib_cmd \ AR \ archiver_list_spec \ STRIP \ RANLIB \ CC \ CFLAGS \ compiler \ lt_cv_sys_global_symbol_pipe \ lt_cv_sys_global_symbol_to_cdecl \ lt_cv_sys_global_symbol_to_import \ lt_cv_sys_global_symbol_to_c_name_address \ lt_cv_sys_global_symbol_to_c_name_address_lib_prefix \ lt_cv_nm_interface \ nm_file_list_spec \ lt_cv_truncate_bin \ lt_prog_compiler_no_builtin_flag \ lt_prog_compiler_pic \ lt_prog_compiler_wl \ lt_prog_compiler_static \ lt_cv_prog_compiler_c_o \ need_locks \ MANIFEST_TOOL \ DSYMUTIL \ NMEDIT \ LIPO \ OTOOL \ OTOOL64 \ shrext_cmds \ export_dynamic_flag_spec \ whole_archive_flag_spec \ compiler_needs_object \ with_gnu_ld \ allow_undefined_flag \ no_undefined_flag \ hardcode_libdir_flag_spec \ hardcode_libdir_separator \ exclude_expsyms \ include_expsyms \ file_list_spec \ variables_saved_for_relink \ libname_spec \ library_names_spec \ soname_spec \ install_override_mode \ finish_eval \ old_striplib \ striplib; do case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in *[\\\\\\\`\\"\\\$]*) eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes ;; *) eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" ;; esac done # Double-quote double-evaled strings. for var in reload_cmds \ old_postinstall_cmds \ old_postuninstall_cmds \ old_archive_cmds \ extract_expsyms_cmds \ old_archive_from_new_cmds \ old_archive_from_expsyms_cmds \ archive_cmds \ archive_expsym_cmds \ module_cmds \ module_expsym_cmds \ export_symbols_cmds \ prelink_cmds \ postlink_cmds \ postinstall_cmds \ postuninstall_cmds \ finish_cmds \ sys_lib_search_path_spec \ configure_time_dlsearch_path \ configure_time_lt_sys_library_path; do case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in *[\\\\\\\`\\"\\\$]*) eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes ;; *) eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" ;; esac done ac_aux_dir='$ac_aux_dir' # See if we are running on zsh, and set the options that allow our # commands through without removal of \ escapes INIT. if test -n "\${ZSH_VERSION+set}"; then setopt NO_GLOB_SUBST fi PACKAGE='$PACKAGE' VERSION='$VERSION' RM='$RM' ofile='$ofile' _ACEOF cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;; "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;; "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;; "lib/bluez.pc") CONFIG_FILES="$CONFIG_FILES lib/bluez.pc" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "doc/bluetooth-meshd.rst") CONFIG_FILES="$CONFIG_FILES doc/bluetooth-meshd.rst" ;; "mesh/bluetooth-mesh.service") CONFIG_FILES="$CONFIG_FILES mesh/bluetooth-mesh.service" ;; "obexd/src/obex.service") CONFIG_FILES="$CONFIG_FILES obexd/src/obex.service" ;; "obexd/src/org.bluez.obex.service") CONFIG_FILES="$CONFIG_FILES obexd/src/org.bluez.obex.service" ;; "doc/bluetoothd.rst") CONFIG_FILES="$CONFIG_FILES doc/bluetoothd.rst" ;; "src/bluetooth.service") CONFIG_FILES="$CONFIG_FILES src/bluetooth.service" ;; "tools/bluetooth-logger.service") CONFIG_FILES="$CONFIG_FILES tools/bluetooth-logger.service" ;; "tools/mpris-proxy.service") CONFIG_FILES="$CONFIG_FILES tools/mpris-proxy.service" ;; *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files test ${CONFIG_HEADERS+y} || CONFIG_HEADERS=$config_headers test ${CONFIG_COMMANDS+y} || CONFIG_COMMANDS=$config_commands fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to '$tmp'. $debug || { tmp= ac_tmp= trap 'exit_status=$? : "${ac_tmp:=$tmp}" { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with './config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | sed -n '$='` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | sed -n '$='` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >"$CONFIG_STATUS" || ac_write_fail=1 rm -f conf$$subs.awk cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" # Set up the scripts for CONFIG_HEADERS section. # No need to generate them if there are no CONFIG_HEADERS. # This happens for instance with './config.status Makefile'. if test -n "$CONFIG_HEADERS"; then cat >"$ac_tmp/defines.awk" <<\_ACAWK || BEGIN { _ACEOF # Transform confdefs.h into an awk script 'defines.awk', embedded as # here-document in config.status, that substitutes the proper values into # config.h.in to produce config.h. # Create a delimiter string that does not exist in confdefs.h, to ease # handling of long lines. ac_delim='%!_!# ' for ac_last_try in false false :; do ac_tt=`sed -n "/$ac_delim/p" confdefs.h` if test -z "$ac_tt"; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done # For the awk script, D is an array of macro values keyed by name, # likewise P contains macro parameters if any. Preserve backslash # newline sequences. ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* sed -n ' s/.\{148\}/&'"$ac_delim"'/g t rset :rset s/^[ ]*#[ ]*define[ ][ ]*/ / t def d :def s/\\$// t bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3"/p s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p d :bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3\\\\\\n"\\/p t cont s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p t cont d :cont n s/.\{148\}/&'"$ac_delim"'/g t clear :clear s/\\$// t bsnlc s/["\\]/\\&/g; s/^/"/; s/$/"/p d :bsnlc s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p b cont ' >"$CONFIG_STATUS" || ac_write_fail=1 cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 for (key in D) D_is_set[key] = 1 FS = "" } /^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { line = \$ 0 split(line, arg, " ") if (arg[1] == "#") { defundef = arg[2] mac1 = arg[3] } else { defundef = substr(arg[1], 2) mac1 = arg[2] } split(mac1, mac2, "(") #) macro = mac2[1] prefix = substr(line, 1, index(line, defundef) - 1) if (D_is_set[macro]) { suffix = P[macro] D[macro] while (suffix ~ /[\t ]$/) { suffix = substr(suffix, 1, length(suffix) - 1) } # Preserve the white space surrounding the "#". print prefix "define", macro suffix next } else { # Replace #undef with comments. This is necessary, for example, # in the case of _POSIX_SOURCE, which is predefined and required # on some systems where configure will not decide to define it. if (defundef == "undef") { print "/*", prefix defundef, macro, "*/" next } } } { print } _ACAWK _ACEOF cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 fi # test -n "$CONFIG_HEADERS" eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS" shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag '$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain ':'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: '$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`printf '%s\n' "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is 'configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` printf '%s\n' "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { printf '%s\n' "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 printf '%s\n' "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`printf '%s\n' "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || printf '%s\n' X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf '%s\n' "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf '%s\n' "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # case $INSTALL in [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; esac ac_MKDIR_P=$MKDIR_P case $MKDIR_P in [\\/$]* | ?:[\\/]* ) ;; */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;; esac _ACEOF cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= ac_sed_dataroot=' /datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 printf '%s\n' "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when '$srcdir' = '.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t s&@INSTALL@&$ac_INSTALL&;t t s&@MKDIR_P@&$ac_MKDIR_P&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable 'datarootdir' which seems to be undefined. Please make sure it is defined" >&5 printf '%s\n' "$as_me: WARNING: $ac_file contains a reference to the variable 'datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" case $ac_file in -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; :H) # # CONFIG_HEADER # if test x"$ac_file" != x-; then { printf '%s\n' "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" } >"$ac_tmp/config.h" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 printf '%s\n' "$as_me: $ac_file is unchanged" >&6;} else rm -f "$ac_file" mv "$ac_tmp/config.h" "$ac_file" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 fi else printf '%s\n' "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ || as_fn_error $? "could not create -" "$LINENO" 5 fi # Compute "$ac_file"'s index in $config_headers. _am_arg="$ac_file" _am_stamp_count=1 for _am_header in $config_headers :; do case $_am_header in $_am_arg | $_am_arg:* ) break ;; * ) _am_stamp_count=`expr $_am_stamp_count + 1` ;; esac done echo "timestamp for $_am_arg" >`$as_dirname -- "$_am_arg" || $as_expr X"$_am_arg" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$_am_arg" : 'X\(//\)[^/]' \| \ X"$_am_arg" : 'X\(//\)$' \| \ X"$_am_arg" : 'X\(/\)' \| . 2>/dev/null || printf '%s\n' X"$_am_arg" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'`/stamp-h$_am_stamp_count ;; :C) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 printf '%s\n' "$as_me: executing $ac_file commands" >&6;} ;; esac case $ac_file$ac_mode in "depfiles":C) test x"$AMDEP_TRUE" != x"" || { # Older Autoconf quotes --file arguments for eval, but not when files # are listed without --file. Let's play safe and only enable the eval # if we detect the quoting. # TODO: see whether this extra hack can be removed once we start # requiring Autoconf 2.70 or later. case $CONFIG_FILES in #( *\'*) : eval set x "$CONFIG_FILES" ;; #( *) : set x $CONFIG_FILES ;; #( *) : ;; esac shift # Used to flag and report bootstrapping failures. am_rc=0 for am_mf do # Strip MF so we end up with the name of the file. am_mf=`printf '%s\n' "$am_mf" | sed -e 's/:.*$//'` # Check whether this is an Automake generated Makefile which includes # dependency-tracking related rules and includes. # Grep'ing the whole file directly is not great: AIX grep has a line # limit of 2048, but all sed's we know have understand at least 4000. sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \ || continue am_dirpart=`$as_dirname -- "$am_mf" || $as_expr X"$am_mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$am_mf" : 'X\(//\)[^/]' \| \ X"$am_mf" : 'X\(//\)$' \| \ X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || printf '%s\n' X"$am_mf" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` am_filepart=`$as_basename -- "$am_mf" || $as_expr X/"$am_mf" : '.*/\([^/][^/]*\)/*$' \| \ X"$am_mf" : 'X\(//\)$' \| \ X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || printf '%s\n' X/"$am_mf" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` { echo "$as_me:$LINENO: cd "$am_dirpart" \ && sed -e '/# am--include-marker/d' "$am_filepart" \ | $MAKE -f - am--depfiles" >&5 (cd "$am_dirpart" \ && sed -e '/# am--include-marker/d' "$am_filepart" \ | $MAKE -f - am--depfiles) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } || am_rc=$? done if test $am_rc -ne 0; then { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "Something went wrong bootstrapping makefile fragments for automatic dependency tracking. If GNU make was not used, consider re-running the configure script with MAKE=\"gmake\" (or whatever is necessary). You can also try re-running configure with the '--disable-dependency-tracking' option to at least be able to build the package (albeit without support for automatic dependency tracking). See 'config.log' for more details" "$LINENO" 5; } fi { am_dirpart=; unset am_dirpart;} { am_filepart=; unset am_filepart;} { am_mf=; unset am_mf;} { am_rc=; unset am_rc;} rm -f conftest-deps.mk } ;; "libtool":C) # See if we are running on zsh, and set the options that allow our # commands through without removal of \ escapes. if test -n "${ZSH_VERSION+set}"; then setopt NO_GLOB_SUBST fi cfgfile=${ofile}T trap "$RM \"$cfgfile\"; exit 1" 1 2 15 $RM "$cfgfile" cat <<_LT_EOF >> "$cfgfile" #! $SHELL # Generated automatically by $as_me ($PACKAGE) $VERSION # NOTE: Changes made to this file will be lost: look at ltmain.sh. # Provide generalized library-building support services. # Written by Gordon Matzigkeit, 1996 # Copyright (C) 2024 Free Software Foundation, Inc. # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # GNU Libtool 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. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program or library that is built # using GNU Libtool, you may include this file under the same # distribution terms that you use for the rest of that program. # # GNU Libtool 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, see . # The names of the tagged configurations supported by this script. available_tags='' # Configured defaults for sys_lib_dlsearch_path munging. : \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"} # ### BEGIN LIBTOOL CONFIG # Which release of libtool.m4 was used? macro_version=$macro_version macro_revision=$macro_revision # Whether or not to build static libraries. build_old_libs=$enable_static # Whether or not to build shared libraries. build_libtool_libs=$enable_shared # What type of objects to build. pic_mode=$pic_mode # Whether or not to optimize for fast installation. fast_install=$enable_fast_install # Shared archive member basename,for filename based shared library versioning on AIX. shared_archive_member_spec=$shared_archive_member_spec # Shell to use when invoking shell scripts. SHELL=$lt_SHELL # An echo program that protects backslashes. ECHO=$lt_ECHO # The PATH separator for the build system. PATH_SEPARATOR=$lt_PATH_SEPARATOR # The host system. host_alias=$host_alias host=$host host_os=$host_os # The build system. build_alias=$build_alias build=$build build_os=$build_os # A sed program that does not truncate output. SED=$lt_SED # Sed that helps us avoid accidentally triggering echo(1) options like -n. Xsed="\$SED -e 1s/^X//" # A grep program that handles long lines. GREP=$lt_GREP # An ERE matcher. EGREP=$lt_EGREP # A literal string matcher. FGREP=$lt_FGREP # A BSD- or MS-compatible name lister. NM=$lt_NM # Whether we need soft or hard links. LN_S=$lt_LN_S # What is the maximum length of a command? max_cmd_len=$max_cmd_len # Object file suffix (normally "o"). objext=$ac_objext # Executable file suffix (normally ""). exeext=$exeext # whether the shell understands "unset". lt_unset=$lt_unset # turn spaces into newlines. SP2NL=$lt_lt_SP2NL # turn newlines into spaces. NL2SP=$lt_lt_NL2SP # convert \$build file names to \$host format. to_host_file_cmd=$lt_cv_to_host_file_cmd # convert \$build files to toolchain format. to_tool_file_cmd=$lt_cv_to_tool_file_cmd # A file(cmd) program that detects file types. FILECMD=$lt_FILECMD # An object symbol dumper. OBJDUMP=$lt_OBJDUMP # Method to check whether dependent libraries are shared objects. deplibs_check_method=$lt_deplibs_check_method # Command to use when deplibs_check_method = "file_magic". file_magic_cmd=$lt_file_magic_cmd # How to find potential files when deplibs_check_method = "file_magic". file_magic_glob=$lt_file_magic_glob # Find potential files using nocaseglob when deplibs_check_method = "file_magic". want_nocaseglob=$lt_want_nocaseglob # DLL creation program. DLLTOOL=$lt_DLLTOOL # Command to associate shared and link libraries. sharedlib_from_linklib_cmd=$lt_sharedlib_from_linklib_cmd # The archiver. AR=$lt_AR # Flags to create an archive (by configure). lt_ar_flags=$lt_ar_flags # Flags to create an archive. AR_FLAGS=\${ARFLAGS-"\$lt_ar_flags"} # How to feed a file listing to the archiver. archiver_list_spec=$lt_archiver_list_spec # A symbol stripping program. STRIP=$lt_STRIP # Commands used to install an old-style archive. RANLIB=$lt_RANLIB old_postinstall_cmds=$lt_old_postinstall_cmds old_postuninstall_cmds=$lt_old_postuninstall_cmds # Whether to use a lock for old archive extraction. lock_old_archive_extraction=$lock_old_archive_extraction # A C compiler. LTCC=$lt_CC # LTCC compiler flags. LTCFLAGS=$lt_CFLAGS # Take the output of nm and produce a listing of raw symbols and C names. global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe # Transform the output of nm in a proper C declaration. global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl # Transform the output of nm into a list of symbols to manually relocate. global_symbol_to_import=$lt_lt_cv_sys_global_symbol_to_import # Transform the output of nm in a C name address pair. global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address # Transform the output of nm in a C name address pair when lib prefix is needed. global_symbol_to_c_name_address_lib_prefix=$lt_lt_cv_sys_global_symbol_to_c_name_address_lib_prefix # The name lister interface. nm_interface=$lt_lt_cv_nm_interface # Specify filename containing input files for \$NM. nm_file_list_spec=$lt_nm_file_list_spec # The root where to search for dependent libraries,and where our libraries should be installed. lt_sysroot=$lt_sysroot # Command to truncate a binary pipe. lt_truncate_bin=$lt_lt_cv_truncate_bin # The name of the directory that contains temporary libtool files. objdir=$objdir # Used to examine libraries when file_magic_cmd begins with "file". MAGIC_CMD=$MAGIC_CMD # Must we lock files when doing compilation? need_locks=$lt_need_locks # Manifest tool. MANIFEST_TOOL=$lt_MANIFEST_TOOL # Tool to manipulate archived DWARF debug symbol files on Mac OS X. DSYMUTIL=$lt_DSYMUTIL # Tool to change global to local symbols on Mac OS X. NMEDIT=$lt_NMEDIT # Tool to manipulate fat objects and archives on Mac OS X. LIPO=$lt_LIPO # ldd/readelf like tool for Mach-O binaries on Mac OS X. OTOOL=$lt_OTOOL # ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4. OTOOL64=$lt_OTOOL64 # Old archive suffix (normally "a"). libext=$libext # Shared library suffix (normally ".so"). shrext_cmds=$lt_shrext_cmds # The commands to extract the exported symbol list from a shared archive. extract_expsyms_cmds=$lt_extract_expsyms_cmds # Variables whose values should be saved in libtool wrapper scripts and # restored at link time. variables_saved_for_relink=$lt_variables_saved_for_relink # Do we need the "lib" prefix for modules? need_lib_prefix=$need_lib_prefix # Do we need a version for libraries? need_version=$need_version # Library versioning type. version_type=$version_type # Shared library runtime path variable. runpath_var=$runpath_var # Shared library path variable. shlibpath_var=$shlibpath_var # Is shlibpath searched before the hard-coded library search path? shlibpath_overrides_runpath=$shlibpath_overrides_runpath # Format of library name prefix. libname_spec=$lt_libname_spec # List of archive names. First name is the real one, the rest are links. # The last name is the one that the linker finds with -lNAME library_names_spec=$lt_library_names_spec # The coded name of the library, if different from the real name. soname_spec=$lt_soname_spec # Permission mode override for installation of shared libraries. install_override_mode=$lt_install_override_mode # Command to use after installation of a shared archive. postinstall_cmds=$lt_postinstall_cmds # Command to use after uninstallation of a shared archive. postuninstall_cmds=$lt_postuninstall_cmds # Commands used to finish a libtool library installation in a directory. finish_cmds=$lt_finish_cmds # As "finish_cmds", except a single script fragment to be evaled but # not shown. finish_eval=$lt_finish_eval # Whether we should hardcode library paths into libraries. hardcode_into_libs=$hardcode_into_libs # Compile-time system search path for libraries. sys_lib_search_path_spec=$lt_sys_lib_search_path_spec # Detected run-time system search path for libraries. sys_lib_dlsearch_path_spec=$lt_configure_time_dlsearch_path # Explicit LT_SYS_LIBRARY_PATH set during ./configure time. configure_time_lt_sys_library_path=$lt_configure_time_lt_sys_library_path # Whether dlopen is supported. dlopen_support=$enable_dlopen # Whether dlopen of programs is supported. dlopen_self=$enable_dlopen_self # Whether dlopen of statically linked programs is supported. dlopen_self_static=$enable_dlopen_self_static # Commands to strip libraries. old_striplib=$lt_old_striplib striplib=$lt_striplib # The linker used to build libraries. LD=$lt_LD # How to create reloadable object files. reload_flag=$lt_reload_flag reload_cmds=$lt_reload_cmds # Commands used to build an old-style archive. old_archive_cmds=$lt_old_archive_cmds # A language specific compiler. CC=$lt_compiler # Is the compiler the GNU compiler? with_gcc=$GCC # Compiler flag to turn off builtin functions. no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag # Additional compiler flags for building library objects. pic_flag=$lt_lt_prog_compiler_pic # How to pass a linker flag through the compiler. wl=$lt_lt_prog_compiler_wl # Compiler flag to prevent dynamic linking. link_static_flag=$lt_lt_prog_compiler_static # Does compiler simultaneously support -c and -o options? compiler_c_o=$lt_lt_cv_prog_compiler_c_o # Whether or not to add -lc for building shared libraries. build_libtool_need_lc=$archive_cmds_need_lc # Whether or not to disallow shared libs when runtime libs are static. allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes # Compiler flag to allow reflexive dlopens. export_dynamic_flag_spec=$lt_export_dynamic_flag_spec # Compiler flag to generate shared objects directly from archives. whole_archive_flag_spec=$lt_whole_archive_flag_spec # Whether the compiler copes with passing no objects directly. compiler_needs_object=$lt_compiler_needs_object # Create an old-style archive from a shared archive. old_archive_from_new_cmds=$lt_old_archive_from_new_cmds # Create a temporary old-style archive to link instead of a shared archive. old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds # Commands used to build a shared archive. archive_cmds=$lt_archive_cmds archive_expsym_cmds=$lt_archive_expsym_cmds # Commands used to build a loadable module if different from building # a shared archive. module_cmds=$lt_module_cmds module_expsym_cmds=$lt_module_expsym_cmds # Whether we are building with GNU ld or not. with_gnu_ld=$lt_with_gnu_ld # Flag that allows shared libraries with undefined symbols to be built. allow_undefined_flag=$lt_allow_undefined_flag # Flag that enforces no undefined symbols. no_undefined_flag=$lt_no_undefined_flag # Flag to hardcode \$libdir into a binary during linking. # This must work even if \$libdir does not exist hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec # Whether we need a single "-rpath" flag with a separated argument. hardcode_libdir_separator=$lt_hardcode_libdir_separator # Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes # DIR into the resulting binary. hardcode_direct=$hardcode_direct # Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes # DIR into the resulting binary and the resulting library dependency is # "absolute",i.e. impossible to change by setting \$shlibpath_var if the # library is relocated. hardcode_direct_absolute=$hardcode_direct_absolute # Set to "yes" if using the -LDIR flag during linking hardcodes DIR # into the resulting binary. hardcode_minus_L=$hardcode_minus_L # Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR # into the resulting binary. hardcode_shlibpath_var=$hardcode_shlibpath_var # Set to "yes" if building a shared library automatically hardcodes DIR # into the library and all subsequent libraries and executables linked # against it. hardcode_automatic=$hardcode_automatic # Set to yes if linker adds runtime paths of dependent libraries # to runtime path list. inherit_rpath=$inherit_rpath # Whether libtool must link a program against all its dependency libraries. link_all_deplibs=$link_all_deplibs # Set to "yes" if exported symbols are required. always_export_symbols=$always_export_symbols # The commands to list exported symbols. export_symbols_cmds=$lt_export_symbols_cmds # Symbols that should not be listed in the preloaded symbols. exclude_expsyms=$lt_exclude_expsyms # Symbols that must always be exported. include_expsyms=$lt_include_expsyms # Commands necessary for linking programs (against libraries) with templates. prelink_cmds=$lt_prelink_cmds # Commands necessary for finishing linking programs. postlink_cmds=$lt_postlink_cmds # Specify filename containing input files. file_list_spec=$lt_file_list_spec # How to hardcode a shared library path into an executable. hardcode_action=$hardcode_action # ### END LIBTOOL CONFIG _LT_EOF cat <<'_LT_EOF' >> "$cfgfile" # ### BEGIN FUNCTIONS SHARED WITH CONFIGURE # func_munge_path_list VARIABLE PATH # ----------------------------------- # VARIABLE is name of variable containing _space_ separated list of # directories to be munged by the contents of PATH, which is string # having a format: # "DIR[:DIR]:" # string "DIR[ DIR]" will be prepended to VARIABLE # ":DIR[:DIR]" # string "DIR[ DIR]" will be appended to VARIABLE # "DIRP[:DIRP]::[DIRA:]DIRA" # string "DIRP[ DIRP]" will be prepended to VARIABLE and string # "DIRA[ DIRA]" will be appended to VARIABLE # "DIR[:DIR]" # VARIABLE will be replaced by "DIR[ DIR]" func_munge_path_list () { case x$2 in x) ;; *:) eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\" ;; x:*) eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\" ;; *::*) eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\" ;; *) eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\" ;; esac } # Calculate cc_basename. Skip known compiler wrappers and cross-prefix. func_cc_basename () { for cc_temp in $*""; do case $cc_temp in compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; \-*) ;; *) break;; esac done func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` } # ### END FUNCTIONS SHARED WITH CONFIGURE _LT_EOF case $host_os in aix3*) cat <<\_LT_EOF >> "$cfgfile" # AIX sometimes has problems with the GCC collect2 program. For some # reason, if we set the COLLECT_NAMES environment variable, the problems # vanish in a puff of smoke. if test set != "${COLLECT_NAMES+set}"; then COLLECT_NAMES= export COLLECT_NAMES fi _LT_EOF ;; esac ltmain=$ac_aux_dir/ltmain.sh # We use sed instead of cat because bash on DJGPP gets confused if # if finds mixed CR/LF and LF-only lines. Since sed operates in # text mode, it properly converts lines to CR/LF. This bash problem # is reportedly fixed, but why not run on old versions too? $SED '$q' "$ltmain" >> "$cfgfile" \ || (rm -f "$cfgfile"; exit 1) mv -f "$cfgfile" "$ofile" || (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") chmod +x "$ofile" ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_CONFIG_STATUS= test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: case $CONFIG_STATUS in #( -*) : ac_no_opts=-- ;; #( *) : ac_no_opts= ;; esac ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $ac_no_opts "$CONFIG_STATUS" $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 printf '%s\n' "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi bluez-5.87/PaxHeaders/COPYING0000644000000000000000000000005011071665350012712 xustar0020 atime=1770587285 20 ctime=1783094098 bluez-5.87/COPYING0000644000000000000000000004312611071665350012401 0ustar00rootroot GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. bluez-5.87/PaxHeaders/Makefile.am0000644000000000000000000000005015200607060013703 xustar0020 atime=1783092682 20 ctime=1783094098 bluez-5.87/Makefile.am0000644000000000000000000006546415200607060013403 0ustar00rootroot# SPDX-License-Identifier: GPL-2.0 AM_MAKEFLAGS = --no-print-directory AM_CPPFLAGS = lib_LTLIBRARIES = noinst_LIBRARIES = noinst_LTLIBRARIES = bin_PROGRAMS = noinst_PROGRAMS = CLEANFILES = EXTRA_DIST = pkglibexecdir = @PKGLIBEXECDIR@ pkglibexec_PROGRAMS = pkgincludedir = $(includedir)/bluetooth pkginclude_HEADERS = AM_CFLAGS = $(MISC_CFLAGS) $(WARNING_CFLAGS) $(UDEV_CFLAGS) $(LIBEBOOK_CFLAGS) \ $(LIBEDATASERVER_CFLAGS) $(ell_cflags) AM_LDFLAGS = $(MISC_LDFLAGS) confdir = $(sysconfdir)/bluetooth statedir = $(localstatedir)/lib/bluetooth bluetoothd-fix-permissions: install -dm755 $(DESTDIR)$(confdir) install -dm700 $(DESTDIR)$(statedir) if DATAFILES dbusdir = $(DBUS_CONFDIR)/dbus-1/system.d dbus_DATA = src/bluetooth.conf if OBEX dbus_DATA += obexd/src/obex.conf endif conf_DATA = src/main.conf conf_DATA += profiles/input/input.conf conf_DATA += profiles/network/network.conf state_DATA = endif if SYSTEMD systemdsystemunitdir = $(SYSTEMD_SYSTEMUNITDIR) systemdsystemunit_DATA = src/bluetooth.service systemduserunitdir = $(SYSTEMD_USERUNITDIR) systemduserunit_DATA = dbussystembusdir = $(DBUS_SYSTEMBUSDIR) dbussystembus_DATA = src/org.bluez.service endif EXTRA_DIST += src/org.bluez.service plugindir = $(libdir)/bluetooth/plugins build_plugindir = $(plugindir) if MANPAGES man_MANS = endif manual_pages = plugin_LTLIBRARIES = lib_sources = lib/bluetooth/bluetooth.c lib/bluetooth/hci.c lib/bluetooth/sdp.c lib_headers = lib/bluetooth/bluetooth.h lib/bluetooth/hci.h lib/bluetooth/hci_lib.h \ lib/bluetooth/sco.h lib/bluetooth/l2cap.h lib/bluetooth/sdp.h lib/bluetooth/sdp_lib.h \ lib/bluetooth/rfcomm.h lib/bluetooth/bnep.h lib/bluetooth/cmtp.h lib/bluetooth/hidp.h extra_headers = lib/bluetooth/mgmt.h lib/bluetooth/uuid.h lib/bluetooth/iso.h extra_sources = lib/bluetooth/uuid.c BUILT_SOURCES = $(ell_built_sources) src/builtin.h if LIBRARY pkginclude_HEADERS += $(lib_headers) lib_LTLIBRARIES += lib/libbluetooth.la lib_libbluetooth_la_SOURCES = $(lib_headers) $(lib_sources) lib_libbluetooth_la_LDFLAGS = $(AM_LDFLAGS) -version-info 22:16:19 endif noinst_LTLIBRARIES += lib/libbluetooth-internal.la lib_libbluetooth_internal_la_SOURCES = $(lib_headers) $(lib_sources) \ $(extra_headers) $(extra_sources) noinst_LTLIBRARIES += gdbus/libgdbus-internal.la gdbus_libgdbus_internal_la_SOURCES = gdbus/gdbus.h \ gdbus/mainloop.c gdbus/watch.c \ gdbus/object.c gdbus/client.c gdbus/polkit.c if EXTERNAL_ELL ell_cflags = @ELL_CFLAGS@ ell_ldadd = @ELL_LIBS@ ell_built_sources = ell/shared else ell_cflags = ell_ldadd = ell/libell-internal.la ell_built_sources = ell/shared ell/internal ell/ell.h noinst_LTLIBRARIES += ell/libell-internal.la ell_headers = ell/util.h \ ell/log.h \ ell/queue.h \ ell/hashmap.h \ ell/random.h \ ell/signal.h \ ell/time.h \ ell/time-private.h \ ell/timeout.h \ ell/cipher.h \ ell/checksum.h \ ell/io.h \ ell/idle.h \ ell/main.h \ ell/settings.h \ ell/strv.h \ ell/string.h \ ell/utf8.h \ ell/dbus.h \ ell/dbus-service.h \ ell/dbus-client.h \ ell/key.h \ ell/cert.h \ ell/pem.h \ ell/base64.h \ ell/asn1-private.h \ ell/cert-private.h \ ell/pem-private.h \ ell/uuid.h \ ell/useful.h \ ell/main-private.h \ ell/tester.h \ ell/tls.h \ ell/tls-private.h \ ell/ecc.h \ ell/ecc-private.h \ ell/cleanup.h \ ell/ecdh.h ell_sources = ell/private.h ell/missing.h \ ell/util.c \ ell/log.c \ ell/queue.c \ ell/hashmap.c \ ell/random.c \ ell/signal.c \ ell/time.c \ ell/timeout.c \ ell/io.c \ ell/idle.c \ ell/main.c \ ell/settings.c \ ell/strv.c \ ell/string.c \ ell/cipher.c \ ell/checksum.c \ ell/pem.c \ ell/cert.c \ ell/cert-crypto.c \ ell/key.c \ ell/base64.c \ ell/utf8.c \ ell/dbus-private.h \ ell/dbus.c \ ell/dbus-message.c \ ell/dbus-util.c \ ell/dbus-service.c \ ell/dbus-client.c \ ell/dbus-name-cache.c \ ell/dbus-filter.c \ ell/gvariant-private.h \ ell/gvariant-util.c \ ell/siphash-private.h \ ell/siphash.c \ ell/uuid.c \ ell/tester.c \ ell/tls.c \ ell/tls-extensions.c \ ell/tls-suites.c \ ell/tls-record.c \ ell/ecc.c \ ell/ecc-external.c \ ell/ecdh.c ell_shared = ell/useful.h ell_libell_internal_la_SOURCES = $(ell_headers) $(ell_sources) $(ell_shared) endif CLEANFILES += $(ell_built_sources) noinst_LTLIBRARIES += src/libshared-glib.la src/libshared-mainloop.la if LIBSHARED_ELL noinst_LTLIBRARIES += src/libshared-ell.la endif shared_sources = src/shared/io.h src/shared/timeout.h \ src/shared/queue.h src/shared/queue.c \ src/shared/util.h src/shared/util.c \ src/shared/mgmt.h src/shared/mgmt.c \ src/shared/crypto.h src/shared/crypto.c \ src/shared/ecc.h src/shared/ecc.c \ src/shared/ringbuf.h src/shared/ringbuf.c \ src/shared/tester.h\ src/shared/hci.h src/shared/hci.c \ src/shared/hci-crypto.h src/shared/hci-crypto.c \ src/shared/hfp.h src/shared/hfp.c \ src/shared/uhid.h src/shared/uhid.c \ src/shared/pcap.h src/shared/pcap.c \ src/shared/btsnoop.h src/shared/btsnoop.c \ src/shared/ad.h src/shared/ad.c \ src/shared/att-types.h \ src/shared/att.h src/shared/att.c \ src/shared/gatt-helpers.h src/shared/gatt-helpers.c \ src/shared/gatt-client.h src/shared/gatt-client.c \ src/shared/gatt-server.h src/shared/gatt-server.c \ src/shared/gatt-db.h src/shared/gatt-db.c \ src/shared/gap.h src/shared/gap.c \ src/shared/log.h src/shared/log.c \ src/shared/bap.h src/shared/bap.c src/shared/ascs.h \ src/shared/bap-debug.h src/shared/bap-debug.c \ src/shared/mcs.h src/shared/mcp.h src/shared/mcp.c \ src/shared/vcp.c src/shared/vcp.h \ src/shared/micp.c src/shared/micp.h \ src/shared/csip.c src/shared/csip.h \ src/shared/bass.h src/shared/bass.c \ src/shared/ccp.h src/shared/ccp.c \ src/shared/tmap.c src/shared/tmap.h \ src/shared/gmap.c src/shared/gmap.h \ src/shared/lc3.h src/shared/tty.h \ src/shared/bap-defs.h \ src/shared/asha.h src/shared/asha.c \ src/shared/battery.h src/shared/battery.c \ src/shared/uinput.h src/shared/uinput.c \ src/shared/rap.h src/shared/rap.c if READLINE shared_sources += src/shared/shell.c src/shared/shell.h endif src_libshared_glib_la_SOURCES = $(shared_sources) \ src/shared/io-glib.c \ src/shared/timeout-glib.c \ src/shared/mainloop-glib.c \ src/shared/mainloop-notify.h \ src/shared/mainloop-notify.c \ src/shared/tester.c src_libshared_glib_la_LDFLAGS = $(AM_LDFLAGS) src_libshared_glib_la_CFLAGS = $(AM_CFLAGS) src_libshared_mainloop_la_SOURCES = $(shared_sources) \ src/shared/io-mainloop.c \ src/shared/timeout-mainloop.c \ src/shared/mainloop.h src/shared/mainloop.c \ src/shared/mainloop-notify.h \ src/shared/mainloop-notify.c src_libshared_mainloop_la_LDFLAGS = $(AM_LDFLAGS) src_libshared_mainloop_la_CFLAGS = $(AM_CFLAGS) if LIBSHARED_ELL src_libshared_ell_la_SOURCES = $(shared_sources) \ src/shared/io-ell.c \ src/shared/timeout-ell.c \ src/shared/mainloop.h \ src/shared/mainloop-ell.c src_libshared_ell_la_LDFLAGS = $(AM_LDFLAGS) src_libshared_ell_la_CFLAGS = $(AM_CFLAGS) endif attrib_sources = attrib/att.h attrib/att-database.h attrib/att.c \ attrib/gatt.h attrib/gatt.c \ attrib/gattrib.h attrib/gattrib.c btio_sources = btio/btio.h btio/btio.c gobex_sources = gobex/gobex.h gobex/gobex.c \ gobex/gobex-defs.h gobex/gobex-defs.c \ gobex/gobex-packet.c gobex/gobex-packet.h \ gobex/gobex-header.c gobex/gobex-header.h \ gobex/gobex-transfer.c gobex/gobex-debug.h \ gobex/gobex-apparam.c gobex/gobex-apparam.h builtin_modules = builtin_sources = builtin_cppflags = builtin_ldadd = include Makefile.plugins pkglibexec_PROGRAMS += src/bluetoothd bluetoothd_internal_sources = \ $(attrib_sources) $(btio_sources) \ src/log.h src/log.c \ src/backtrace.h src/backtrace.c \ src/rfkill.c src/btd.h src/sdpd.h \ src/sdpd-server.c src/sdpd-request.c \ src/sdpd-service.c src/sdpd-database.c \ src/gatt-database.h src/gatt-database.c \ src/sdp-xml.h src/sdp-xml.c \ src/sdp-client.h src/sdp-client.c \ src/textfile.h src/textfile.c \ src/uuid-helper.h src/uuid-helper.c \ src/storage.h src/storage.c \ src/advertising.h src/advertising.c \ src/agent.h src/agent.c \ src/error.h src/error.c \ src/adapter.h src/adapter.c \ src/profile.h src/profile.c \ src/service.h src/service.c \ src/gatt-client.h src/gatt-client.c \ src/device.h src/device.c \ src/dbus-common.c src/dbus-common.h \ src/eir.h src/eir.c \ src/adv_monitor.h src/adv_monitor.c \ src/battery.h src/battery.c \ src/settings.h src/settings.c \ src/set.h src/set.c \ src/bearer.h src/bearer.c bluetoothd_internal_ldadd = lib/libbluetooth-internal.la \ gdbus/libgdbus-internal.la \ src/libshared-glib.la \ $(BACKTRACE_LIBS) $(GLIB_LIBS) $(DBUS_LIBS) -ldl -lrt src_bluetoothd_SOURCES = $(builtin_sources) \ $(bluetoothd_internal_sources) \ src/plugin.h src/plugin.c \ src/main.c src_bluetoothd_LDADD = $(bluetoothd_internal_ldadd) \ $(builtin_ldadd) if EXTERNAL_PLUGINS src_bluetoothd_SOURCES += src/bluetooth.ver src_bluetoothd_LDFLAGS = $(AM_LDFLAGS) -Wl,--export-dynamic \ -Wl,--version-script=$(srcdir)/src/bluetooth.ver endif src_bluetoothd_CPPFLAGS = $(AM_CPPFLAGS) -DBLUETOOTH_PLUGIN_BUILTIN \ -DPLUGINDIR=\""$(build_plugindir)"\" \ $(BACKTRACE_CFLAGS) $(builtin_cppflags) src_bluetoothd_SHORTNAME = bluetoothd builtin_files = src/builtin.h nodist_src_bluetoothd_SOURCES = $(builtin_files) CLEANFILES += $(builtin_files) if MANPAGES man_MANS += doc/bluetoothd.8 man_MANS += doc/hci.7 doc/mgmt.7 doc/l2cap.7 doc/rfcomm.7 doc/sco.7 doc/iso.7 \ doc/btsnoop.7 man_MANS += doc/org.bluez.Adapter.5 doc/org.bluez.Device.5 \ doc/org.bluez.Bearer.LE.5 doc/org.bluez.Bearer.BREDR.5 \ doc/org.bluez.DeviceSet.5 doc/org.bluez.AgentManager.5 \ doc/org.bluez.Agent.5 doc/org.bluez.ProfileManager.5 \ doc/org.bluez.Profile.5 doc/org.bluez.NetworkServer.5 \ doc/org.bluez.Network.5 doc/org.bluez.Input.5 \ doc/org.bluez.BatteryProviderManager.5 \ doc/org.bluez.BatteryProvider.5 doc/org.bluez.Battery.5 \ doc/org.bluez.AdminPolicySet.5 \ doc/org.bluez.AdminPolicyStatus.5 man_MANS += doc/org.bluez.Media.5 doc/org.bluez.MediaControl.5 \ doc/org.bluez.MediaPlayer.5 doc/org.bluez.MediaFolder.5 \ doc/org.bluez.MediaItem.5 doc/org.bluez.MediaEndpoint.5 \ doc/org.bluez.MediaTransport.5 doc/org.bluez.MediaAssistant.5 man_MANS += doc/org.bluez.GattManager.5 doc/org.bluez.GattProfile.5 \ doc/org.bluez.GattService.5 \ doc/org.bluez.GattCharacteristic.5 \ doc/org.bluez.GattDescriptor.5 \ doc/org.bluez.LEAdvertisingManager.5 \ doc/org.bluez.LEAdvertisement.5 \ doc/org.bluez.AdvertisementMonitorManager.5 \ doc/org.bluez.AdvertisementMonitor.5 man_MANS += doc/org.bluez.obex.Client.5 doc/org.bluez.obex.Session.5 \ doc/org.bluez.obex.Transfer.5 \ doc/org.bluez.obex.ObjectPush.5 \ doc/org.bluez.obex.FileTransfer.5 \ doc/org.bluez.obex.Synchronization.5 \ doc/org.bluez.obex.PhonebookAccess.5 \ doc/org.bluez.obex.MessageAccess.5 \ doc/org.bluez.obex.Message.5 \ doc/org.bluez.obex.AgentManager.5 doc/org.bluez.obex.Agent.5 \ doc/org.bluez.obex.Image.5 man_MANS += doc/org.bluez.Telephony.5 doc/org.bluez.Call.5 man_MANS += doc/org.bluez.ThermometerManager.5 \ doc/org.bluez.Thermometer.5 \ doc/org.bluez.ThermometerWatcher.5 endif manual_pages += doc/bluetoothd.8 manual_pages += doc/hci.7 doc/mgmt.7 doc/l2cap.7 doc/rfcomm.7 doc/sco.7 \ doc/iso.7 doc/btsnoop.7 manual_pages += doc/org.bluez.Adapter.5 doc/org.bluez.Device.5 \ doc/org.bluez.Bearer.LE.5 doc/org.bluez.Bearer.BREDR.5 \ doc/org.bluez.DeviceSet.5 doc/org.bluez.AgentManager.5 \ doc/org.bluez.Agent.5 doc/org.bluez.ProfileManager.5 \ doc/org.bluez.Profile.5 doc/org.bluez.NetworkServer.5 \ doc/org.bluez.Network.5 doc/org.bluez.Input.5\ doc/org.bluez.BatteryProviderManager.5 \ doc/org.bluez.BatteryProvider.5 doc/org.bluez.Battery.5 \ doc/org.bluez.AdminPolicySet.5 \ doc/org.bluez.AdminPolicyStatus.5 manual_pages += doc/org.bluez.Media.5 doc/org.bluez.MediaControl.5 \ doc/org.bluez.MediaPlayer.5 doc/org.bluez.MediaFolder.5 \ doc/org.bluez.MediaItem.5 doc/org.bluez.MediaEndpoint.5 \ doc/org.bluez.MediaTransport.5 doc/org.bluez.MediaAssistant.5 manual_pages += doc/org.bluez.GattManager.5 doc/org.bluez.GattProfile.5 \ doc/org.bluez.GattService.5 \ doc/org.bluez.GattCharacteristic.5 \ doc/org.bluez.GattDescriptor.5 \ doc/org.bluez.LEAdvertisingManager.5 \ doc/org.bluez.LEAdvertisement.5 \ doc/org.bluez.AdvertisementMonitorManager.5 \ doc/org.bluez.AdvertisementMonitor.5 manual_pages += doc/org.bluez.obex.Client.5 doc/org.bluez.obex.Session.5 \ doc/org.bluez.obex.Transfer.5 \ doc/org.bluez.obex.ObjectPush.5 \ doc/org.bluez.obex.FileTransfer.5 \ doc/org.bluez.obex.Synchronization.5 \ doc/org.bluez.obex.PhonebookAccess.5 \ doc/org.bluez.obex.MessageAccess.5 \ doc/org.bluez.obex.Message.5 \ doc/org.bluez.obex.AgentManager.5 doc/org.bluez.obex.Agent.5 \ doc/org.bluez.obex.Image.5 manual_pages += doc/org.bluez.Telephony.5 doc/org.bluez.Call.5 manual_pages += doc/org.bluez.ThermometerManager.5 \ doc/org.bluez.Thermometer.5 \ doc/org.bluez.ThermometerWatcher.5 EXTRA_DIST += src/genbuiltin src/bluetooth.conf \ src/main.conf profiles/network/network.conf \ profiles/input/input.conf obexd/src/obex.conf test_scripts = unit_tests = include Makefile.tools include Makefile.obexd include Makefile.mesh if SYSTEMD install-data-hook: obexd-add-service-symlink else install-data-hook: bluetoothd-fix-permissions obexd-add-service-symlink endif uninstall-hook: obexd-remove-service-symlink if HID2HCI rulesdir = $(UDEV_DIR)/rules.d rules_DATA = tools/97-hid2hci.rules CLEANFILES += $(rules_DATA) endif EXTRA_DIST += tools/hid2hci.rules if TEST testdir = $(pkglibdir)/test test_SCRIPTS = $(test_scripts) endif EXTRA_DIST += $(test_scripts) EXTRA_DIST += doc/assigned-numbers.rst doc/supported-features.txt \ doc/test-coverage.txt \ doc/test-runner.rst \ doc/settings-storage.txt EXTRA_DIST += doc/hci-protocol.rst doc/mgmt-protocol.rst \ doc/l2cap-protocol.rst doc/rfcomm-protocol.rst \ doc/sco-protocol.rst doc/iso-protocol.rst EXTRA_DIST += doc/org.bluez.Adapter.rst doc/org.bluez.Device.rst \ doc/org.bluez.Bearer.LE.rst doc/org.bluez.Bearer.BREDR.rst \ doc/org.bluez.DeviceSet.rst doc/org.bluez.AgentManager.rst \ doc/org.bluez.Agent.rst doc/org.bluez.ProfileManager.rst \ doc/org.bluez.Profile.rst doc/org.bluez.NetworkServer.rst \ doc/org.bluez.Network.rst doc/org.bluez.Input.rst \ doc/org.bluez.BatteryProviderManager.rst \ doc/org.bluez.BatteryProvider.rst doc/org.bluez.Battery.rst \ doc/org.bluez.AdminPolicySet.rst \ doc/org.bluez.AdminPolicyStatus.rst EXTRA_DIST += doc/org.bluez.Media.rst doc/org.bluez.MediaControl.rst \ doc/org.bluez.MediaPlayer.rst doc/org.bluez.MediaFolder.rst \ doc/org.bluez.MediaItem.rst doc/org.bluez.MediaEndpoint.rst \ doc/org.bluez.MediaTransport.rst doc/org.bluez.MediaAssistant.rst EXTRA_DIST += doc/org.bluez.GattManager.rst doc/org.bluez.GattProfile.rst\ doc/org.bluez.GattService.rst \ doc/org.bluez.GattCharacteristic.rst \ doc/org.bluez.GattDescriptor.rst \ doc/org.bluez.LEAdvertisingManager.rst \ doc/org.bluez.LEAdvertisement.rst \ doc/org.bluez.AdvertisementMonitorManager.rst \ doc/org.bluez.AdvertisementMonitor.rst EXTRA_DIST += doc/org.bluez.obex.Client.rst doc/org.bluez.obex.Session.rst \ doc/org.bluez.obex.Transfer.rst \ doc/org.bluez.obex.ObjectPush.rst \ doc/org.bluez.obex.FileTransfer.rst \ doc/org.bluez.obex.Synchronization.rst \ doc/org.bluez.obex.PhonebookAccess.rst \ doc/org.bluez.obex.MessageAccess.rst \ doc/org.bluez.obex.Message.rst \ doc/org.bluez.obex.AgentManager.rst doc/org.bluez.obex.Agent.rst \ doc/org.bluez.obex.Image.rst EXTRA_DIST += doc/org.bluez.Telephony.rst doc/org.bluez.Call.rst EXTRA_DIST += doc/org.bluez.ThermometerManager.rst \ doc/org.bluez.Thermometer.rst \ doc/org.bluez.ThermometerWatcher.rst EXTRA_DIST += doc/pics-opp.txt doc/pixit-opp.txt \ doc/pts-opp.txt EXTRA_DIST += doc/btsnoop-protocol.rst EXTRA_DIST += doc/btmon-advertising.rst EXTRA_DIST += doc/btmon-le-audio.rst EXTRA_DIST += doc/btmon-cs.rst EXTRA_DIST += doc/btmon-l2cap.rst EXTRA_DIST += doc/btmon-smp.rst EXTRA_DIST += doc/btmon-gatt.rst EXTRA_DIST += doc/btmon-connections.rst EXTRA_DIST += doc/btmon-hci-init.rst EXTRA_DIST += doc/btmon-classic-audio.rst doc/btmon-a2dp.rst EXTRA_DIST += doc/btmon-hfp.rst EXTRA_DIST += doc/btmon-mgmt.rst EXTRA_DIST += tools/magic.btsnoop AM_CPPFLAGS += $(DBUS_CFLAGS) $(GLIB_CFLAGS) -I$(srcdir)/lib unit_tests += unit/test-tester unit_test_tester_SOURCES = unit/test-tester.c unit_test_tester_LDADD = src/libshared-glib.la lib/libbluetooth-internal.la \ $(GLIB_LIBS) unit_tests += unit/test-eir unit_test_eir_SOURCES = unit/test-eir.c src/eir.c src/uuid-helper.c unit_test_eir_LDADD = src/libshared-glib.la lib/libbluetooth-internal.la \ $(GLIB_LIBS) unit_tests += unit/test-uuid unit_test_uuid_SOURCES = unit/test-uuid.c unit_test_uuid_LDADD = src/libshared-glib.la lib/libbluetooth-internal.la \ $(GLIB_LIBS) unit_tests += unit/test-textfile unit_test_textfile_SOURCES = unit/test-textfile.c src/textfile.h src/textfile.c unit_test_textfile_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_tests += unit/test-crc unit_test_crc_SOURCES = unit/test-crc.c monitor/crc.h monitor/crc.c unit_test_crc_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_tests += unit/test-crypto unit_test_crypto_SOURCES = unit/test-crypto.c unit_test_crypto_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_tests += unit/test-ecc unit_test_ecc_SOURCES = unit/test-ecc.c unit_test_ecc_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_tests += unit/test-ringbuf unit/test-queue unit_test_ringbuf_SOURCES = unit/test-ringbuf.c unit_test_ringbuf_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_queue_SOURCES = unit/test-queue.c unit_test_queue_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_tests += unit/test-mgmt unit_test_mgmt_SOURCES = unit/test-mgmt.c unit_test_mgmt_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_tests += unit/test-uhid unit_test_uhid_SOURCES = unit/test-uhid.c unit_test_uhid_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_tests += unit/test-profile unit_test_profile_SOURCES = unit/test-profile.c \ unit/btd.c $(bluetoothd_internal_sources) unit_test_profile_LDADD = $(bluetoothd_internal_ldadd) unit_tests += unit/test-sdp unit_test_sdp_SOURCES = unit/test-sdp.c \ src/sdpd.h src/sdpd-database.c \ src/log.h src/log.c \ src/sdpd-service.c src/sdpd-request.c unit_test_sdp_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) unit_tests += unit/test-avdtp unit_test_avdtp_SOURCES = unit/test-avdtp.c \ src/log.h src/log.c \ unit/avdtp.c unit/avdtp.h unit_test_avdtp_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_tests += unit/test-avctp unit_test_avctp_SOURCES = unit/test-avctp.c \ src/log.h src/log.c \ unit/avctp.c unit/avctp.h unit_test_avctp_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_tests += unit/test-avrcp unit_test_avrcp_SOURCES = unit/test-avrcp.c \ src/log.h src/log.c \ unit/avctp.c unit/avctp.h \ unit/avrcp-lib.c unit/avrcp-lib.h unit_test_avrcp_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) unit_tests += unit/test-hfp unit_test_hfp_SOURCES = unit/test-hfp.c unit_test_hfp_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_tests += unit/test-gdbus-client unit_test_gdbus_client_SOURCES = unit/test-gdbus-client.c unit_test_gdbus_client_LDADD = gdbus/libgdbus-internal.la \ src/libshared-glib.la $(GLIB_LIBS) $(DBUS_LIBS) if OBEX unit_tests += unit/test-gobex-header unit/test-gobex-packet unit/test-gobex \ unit/test-gobex-transfer unit/test-gobex-apparam unit_test_gobex_SOURCES = $(gobex_sources) unit/util.c unit/util.h \ unit/test-gobex.c unit_test_gobex_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_gobex_packet_SOURCES = $(gobex_sources) unit/util.c unit/util.h \ unit/test-gobex-packet.c unit_test_gobex_packet_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_gobex_header_SOURCES = $(gobex_sources) unit/util.c unit/util.h \ unit/test-gobex-header.c unit_test_gobex_header_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_gobex_transfer_SOURCES = $(gobex_sources) unit/util.c unit/util.h \ unit/test-gobex-transfer.c unit_test_gobex_transfer_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_gobex_apparam_SOURCES = $(gobex_sources) unit/util.c unit/util.h \ unit/test-gobex-apparam.c unit_test_gobex_apparam_LDADD = src/libshared-glib.la $(GLIB_LIBS) endif unit_tests += unit/test-lib unit_test_lib_SOURCES = unit/test-lib.c unit_test_lib_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_tests += unit/test-util unit_test_util_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_tests += unit/test-gatt unit_test_gatt_SOURCES = unit/test-gatt.c unit_test_gatt_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_tests += unit/test-hog unit_test_hog_SOURCES = unit/test-hog.c \ $(btio_sources) \ profiles/input/hog-lib.h profiles/input/hog-lib.c \ profiles/scanparam/scpp.h profiles/scanparam/scpp.c \ profiles/battery/bas.h profiles/battery/bas.c \ profiles/deviceinfo/dis.h profiles/deviceinfo/dis.c \ src/log.h src/log.c \ attrib/att.h attrib/att.c \ attrib/gatt.h attrib/gatt.c \ attrib/gattrib.h attrib/gattrib.c unit_test_hog_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_tests += unit/test-gattrib unit_test_gattrib_SOURCES = unit/test-gattrib.c attrib/gattrib.c \ $(btio_sources) src/log.h src/log.c unit_test_gattrib_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la \ $(GLIB_LIBS) $(DBUS_LIBS) -ldl -lrt unit_tests += unit/test-bap unit_test_bap_SOURCES = unit/test-bap.c unit_test_bap_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_tests += unit/test-micp unit_test_micp_SOURCES = unit/test-micp.c unit_test_micp_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_tests += unit/test-bass unit_test_bass_SOURCES = unit/test-bass.c $(btio_sources) unit_test_bass_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_tests += unit/test-vcp unit_test_vcp_SOURCES = unit/test-vcp.c $(btio_sources) unit_test_vcp_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_tests += unit/test-tmap unit_test_tmap_SOURCES = unit/test-tmap.c $(btio_sources) unit_test_tmap_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_tests += unit/test-gmap unit_test_gmap_SOURCES = unit/test-gmap.c $(btio_sources) unit_test_gmap_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_tests += unit/test-mcp unit_test_mcp_SOURCES = unit/test-mcp.c $(btio_sources) unit_test_mcp_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_tests += unit/test-battery unit_test_battery_SOURCES = unit/test-battery.c unit_test_battery_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_tests += unit/test-rap unit_test_rap_SOURCES = unit/test-rap.c $(btio_sources) unit_test_rap_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) if MIDI unit_tests += unit/test-midi unit_test_midi_CPPFLAGS = $(AM_CPPFLAGS) $(ALSA_CFLAGS) -DMIDI_TEST unit_test_midi_SOURCES = unit/test-midi.c \ profiles/midi/libmidi.h \ profiles/midi/libmidi.c unit_test_midi_LDADD = src/libshared-glib.la \ $(GLIB_LIBS) $(ALSA_LIBS) endif if MESH unit_tests += unit/test-mesh-crypto unit_test_mesh_crypto_CPPFLAGS = $(ell_cflags) unit_test_mesh_crypto_SOURCES = unit/test-mesh-crypto.c \ mesh/crypto.h ell/internal ell/ell.h unit_test_mesh_crypto_LDADD = $(ell_ldadd) endif if MAINTAINER_MODE noinst_PROGRAMS += $(unit_tests) endif TESTS = $(unit_tests) AM_TESTS_ENVIRONMENT = MALLOC_CHECK_=3 MALLOC_PERTURB_=69 if DBUS_RUN_SESSION AM_TESTS_ENVIRONMENT += dbus-run-session -- endif if VALGRIND LOG_COMPILER = valgrind --error-exitcode=1 --num-callers=30 LOG_FLAGS = --trace-children=yes --leak-check=full --show-reachable=no \ --suppressions=$(srcdir)/tools/valgrind.supp --quiet endif pkgconfigdir = $(libdir)/pkgconfig if LIBRARY pkgconfig_DATA = lib/bluez.pc endif EXTRA_DIST += $(manual_pages) $(patsubst %.1,%.rst, \ $(patsubst %.8,%.rst,$(manual_pages))) # pkg-config gives absolute paths that do not work with distcheck that wants to use its own $(prefix) AM_DISTCHECK_CONFIGURE_FLAGS = --disable-datafiles --enable-library \ --enable-midi \ --enable-manpages \ --enable-mesh \ --enable-btpclient \ --disable-systemd \ --disable-udev \ --disable-cups \ --with-dbusconfdir=$${dc_install_base}/$(DBUS_CONFDIR) \ --with-dbussystembusdir=$${dc_install_base}/$(DBUS_SYSTEMBUSDIR) \ --with-dbussessionbusdir=$${dc_install_base}/$(DBUS_SESSIONBUSDIR) DISTCLEANFILES = $(pkgconfig_DATA) $(unit_tests) $(manual_pages) MAINTAINERCLEANFILES = Makefile.in \ aclocal.m4 configure config.h.in config.sub config.guess \ ltmain.sh depcomp compile missing install-sh mkinstalldirs test-driver if RUN_RST2MAN RST2MAN_PROCESS = $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ $(RST2MAN) --strict --no-raw \ --no-generator --no-datestamp $< $@ else RST2MAN_PROCESS = $(AM_V_GEN)test -f $@ || \ { echo "Generated manual page $@ does not exist"; false; } endif %.1: %.rst Makefile $(RST2MAN_PROCESS) %.5: %.rst Makefile $(RST2MAN_PROCESS) %.7: %-protocol.rst Makefile $(RST2MAN_PROCESS) %.8: %.rst Makefile $(RST2MAN_PROCESS) src/builtin.h: src/genbuiltin $(builtin_sources) $(AM_V_GEN)$(srcdir)/src/genbuiltin $(builtin_modules) > $@ tools/%.rules: $(AM_V_at)$(MKDIR_P) tools $(AM_V_GEN)cp $(srcdir)/$(subst 97-,,$@) $@ ell/shared: Makefile $(AM_V_at)$(MKDIR_P) ell $(AM_V_GEN)for f in $(ell_shared) ; do \ if [ ! -f $$f ] ; then \ $(LN_S) -t ell -f $(abs_srcdir)/../ell/$$f ; \ fi \ done > $@ ell/internal: Makefile $(AM_V_at)$(MKDIR_P) ell $(AM_V_GEN)for f in $(ell_headers) $(ell_sources) ; do \ if [ ! -f $$f ] ; then \ $(LN_S) -t ell -f $(abs_srcdir)/../ell/$$f ; \ fi \ done > $@ ell/ell.h: Makefile $(AM_V_at)echo -n > $@ $(AM_V_GEN)for f in $(ell_headers) ; do \ echo "#include <$$f>" >> $@ ; \ done maintainer-clean-local: -rm -rf ell clean-coverage: if COVERAGE @lcov --directory $(top_builddir) --zerocounters $(RM) -r coverage $(top_builddir)/coverage.info coverage: check @lcov --compat-libtool --directory $(top_builddir) --capture \ --output-file $(top_builddir)/coverage.info $(AM_V_at)$(MKDIR_P) coverage @genhtml -o coverage/ $(top_builddir)/coverage.info endif clean-local: clean-coverage -find $(top_builddir) -name "*.gcno" -delete -find $(top_builddir) -name "*.gcda" -delete bluez-5.87/PaxHeaders/ell0000644000000000000000000000005015221755536012365 xustar0020 atime=1783094113 20 ctime=1783094110 bluez-5.87/ell/0000755000000000000000000000000015221755536012123 5ustar00rootrootbluez-5.87/ell/PaxHeaders/queue.c0000644000000000000000000000005014601374755013733 xustar0020 atime=1783076250 20 ctime=1783094099 bluez-5.87/ell/queue.c0000644000000000000000000002465714601374755013432 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #include "queue.h" #include "private.h" #include "useful.h" /** * SECTION:queue * @short_description: Queue support * * Queue support */ /** * l_queue: * * Opaque object representing the queue. */ struct l_queue { struct l_queue_entry *head; struct l_queue_entry *tail; unsigned int entries; }; /** * l_queue_new: * * Create a new queue. * * No error handling is needed since. In case of real memory allocation * problems abort() will be called. * * Returns: a newly allocated #l_queue object **/ LIB_EXPORT struct l_queue *l_queue_new(void) { struct l_queue *queue; queue = l_new(struct l_queue, 1); queue->head = NULL; queue->tail = NULL; queue->entries = 0; return queue; } /** * l_queue_destroy: * @queue: queue object * @destroy: destroy function * * Free queue and call @destroy on all remaining entries. **/ LIB_EXPORT void l_queue_destroy(struct l_queue *queue, l_queue_destroy_func_t destroy) { l_queue_clear(queue, destroy); l_free(queue); } /** * l_queue_clear: * @queue: queue object * @destroy: destroy function * * Clear queue and call @destroy on all remaining entries. **/ LIB_EXPORT void l_queue_clear(struct l_queue *queue, l_queue_destroy_func_t destroy) { struct l_queue_entry *entry; if (unlikely(!queue)) return; entry = queue->head; while (entry) { struct l_queue_entry *tmp = entry; if (destroy) destroy(entry->data); entry = entry->next; l_free(tmp); } queue->head = NULL; queue->tail = NULL; queue->entries = 0; } /** * l_queue_push_tail: * @queue: queue object * @data: pointer to data * * Adds @data pointer at the end of the queue. * * Returns: #true when data has been added and #false in case an invalid * @queue object has been provided **/ LIB_EXPORT bool l_queue_push_tail(struct l_queue *queue, void *data) { struct l_queue_entry *entry; if (unlikely(!queue)) return false; entry = l_new(struct l_queue_entry, 1); entry->data = data; entry->next = NULL; if (queue->tail) queue->tail->next = entry; queue->tail = entry; if (!queue->head) queue->head = entry; queue->entries++; return true; } /** * l_queue_push_head: * @queue: queue object * @data: pointer to data * * Adds @data pointer at the start of the queue. * * Returns: #true when data has been added and #false in case an invalid * @queue object has been provided **/ LIB_EXPORT bool l_queue_push_head(struct l_queue *queue, void *data) { struct l_queue_entry *entry; if (unlikely(!queue)) return false; entry = l_new(struct l_queue_entry, 1); entry->data = data; entry->next = queue->head; queue->head = entry; if (!queue->tail) queue->tail = entry; queue->entries++; return true; } /** * l_queue_pop_head: * @queue: queue object * * Removes the first element of the queue an returns it. * * Returns: data pointer to first element or #NULL in case an empty queue **/ LIB_EXPORT void *l_queue_pop_head(struct l_queue *queue) { struct l_queue_entry *entry; void *data; if (unlikely(!queue)) return NULL; if (!queue->head) return NULL; entry = queue->head; if (!queue->head->next) { queue->head = NULL; queue->tail = NULL; } else queue->head = queue->head->next; data = entry->data; l_free(entry); queue->entries--; return data; } /** * l_queue_peek_head: * @queue: queue object * * Peeks at the first element of the queue an returns it. * * Returns: data pointer to first element or #NULL in case an empty queue **/ LIB_EXPORT void *l_queue_peek_head(struct l_queue *queue) { struct l_queue_entry *entry; if (unlikely(!queue)) return NULL; if (!queue->head) return NULL; entry = queue->head; return entry->data; } /** * l_queue_peek_tail: * @queue: queue object * * Peeks at the last element of the queue an returns it. * * Returns: data pointer to first element or #NULL in case an empty queue **/ LIB_EXPORT void *l_queue_peek_tail(struct l_queue *queue) { struct l_queue_entry *entry; if (unlikely(!queue)) return NULL; if (!queue->tail) return NULL; entry = queue->tail; return entry->data; } /** * l_queue_insert: * @queue: queue object * @data: pointer to data * @function: compare function * @user_data: user data given to compare function * * Inserts @data pointer at a position in the queue determined by the * compare @function. @function should return >= 0 if the @data (first * parameter) should be inserted after the current entry (second parameter) * and should return < 0 if before it. * * Returns: #true when data has been added and #false in case of failure **/ LIB_EXPORT bool l_queue_insert(struct l_queue *queue, void *data, l_queue_compare_func_t function, void *user_data) { struct l_queue_entry *entry, *prev, *cur; int cmp; if (unlikely(!queue || !function)) return false; entry = l_new(struct l_queue_entry, 1); entry->data = data; entry->next = NULL; if (!queue->head) { queue->head = entry; queue->tail = entry; goto done; } for (prev = NULL, cur = queue->head; cur; prev = cur, cur = cur->next) { cmp = function(entry->data, cur->data, user_data); if (cmp >= 0) continue; if (prev == NULL) { entry->next = queue->head; queue->head = entry; goto done; } entry->next = cur; prev->next = entry; goto done; } queue->tail->next = entry; queue->tail = entry; done: queue->entries++; return true; } /** * l_queue_find: * @queue: queue object * @function: match function * @user_data: user data given to compare function * * Finds an entry in the queue by running the match @function * * Returns: Matching entry or NULL if no entry can be found **/ LIB_EXPORT void *l_queue_find(struct l_queue *queue, l_queue_match_func_t function, const void *user_data) { struct l_queue_entry *entry; if (unlikely(!queue || !function)) return NULL; for (entry = queue->head; entry; entry = entry->next) if (function(entry->data, user_data)) return entry->data; return NULL; } /** * l_queue_remove: * @queue: queue object * @data: pointer to data * * Remove given @data from the queue. * * Returns: #true when data has been removed and #false when data could not * be found or an invalid @queue object has been provided **/ LIB_EXPORT bool l_queue_remove(struct l_queue *queue, void *data) { struct l_queue_entry *entry, *prev; if (unlikely(!queue)) return false; for (entry = queue->head, prev = NULL; entry; prev = entry, entry = entry->next) { if (entry->data != data) continue; if (prev) prev->next = entry->next; else queue->head = entry->next; if (!entry->next) queue->tail = prev; l_free(entry); queue->entries--; return true; } return false; } /** * l_queue_reverse: * @queue: queue object * * Reverse entries in the queue. * * Returns: #true on success and #false on failure **/ LIB_EXPORT bool l_queue_reverse(struct l_queue *queue) { struct l_queue_entry *entry, *prev = NULL; if (unlikely(!queue)) return false; entry = queue->head; while (entry) { struct l_queue_entry *next = entry->next; entry->next = prev; prev = entry; entry = next; } queue->tail = queue->head; queue->head = prev; return true; } /** * l_queue_foreach: * @queue: queue object * @function: callback function * @user_data: user data given to callback function * * Call @function for every given data in @queue. **/ LIB_EXPORT void l_queue_foreach(struct l_queue *queue, l_queue_foreach_func_t function, void *user_data) { struct l_queue_entry *entry; if (unlikely(!queue || !function)) return; for (entry = queue->head; entry; entry = entry->next) function(entry->data, user_data); } /** * l_queue_foreach_remove: * @queue: queue object * @function: callback function * @user_data: user data given to callback function * * Remove all entries in the @queue where @function returns #true. * * Returns: number of removed entries **/ LIB_EXPORT unsigned int l_queue_foreach_remove(struct l_queue *queue, l_queue_remove_func_t function, void *user_data) { struct l_queue_entry *entry, *prev = NULL; unsigned int count = 0; if (unlikely(!queue || !function)) return 0; entry = queue->head; while (entry) { if (function(entry->data, user_data)) { struct l_queue_entry *tmp = entry; if (prev) prev->next = entry->next; else queue->head = entry->next; if (!entry->next) queue->tail = prev; entry = entry->next; l_free(tmp); count++; } else { prev = entry; entry = entry->next; } } queue->entries -= count; return count; } /** * l_queue_remove_if * @queue: queue object * @function: callback function * @user_data: user data given to callback function * * Remove the first entry in the @queue where the function returns #true. * * Returns: NULL if no entry was found, or the entry data if removal was * successful. **/ LIB_EXPORT void *l_queue_remove_if(struct l_queue *queue, l_queue_match_func_t function, const void *user_data) { struct l_queue_entry *entry, *prev = NULL; if (unlikely(!queue || !function)) return NULL; entry = queue->head; while (entry) { if (function(entry->data, user_data)) { struct l_queue_entry *tmp = entry; void *data; if (prev) prev->next = entry->next; else queue->head = entry->next; if (!entry->next) queue->tail = prev; data = tmp->data; l_free(tmp); queue->entries--; return data; } prev = entry; entry = entry->next; } return NULL; } /** * l_queue_length: * @queue: queue object * * Returns: entries of the queue **/ LIB_EXPORT unsigned int l_queue_length(struct l_queue *queue) { if (unlikely(!queue)) return 0; return queue->entries; } /** * l_queue_isempty: * @queue: queue object * * Returns: #true if @queue is empty and #false is not **/ LIB_EXPORT bool l_queue_isempty(struct l_queue *queue) { if (unlikely(!queue)) return true; return queue->entries == 0; } /** * l_queue_get_entries: * @queue: queue object * * This function gives direct, read-only access to the internal list structure * of the queue. This can be used to efficiently traverse the elements. * * Returns: A pointer to the head of the queue. **/ LIB_EXPORT const struct l_queue_entry *l_queue_get_entries( const struct l_queue *queue) { if (unlikely(!queue)) return NULL; return queue->head; } bluez-5.87/ell/PaxHeaders/tester.h0000644000000000000000000000005014560467756014132 xustar0020 atime=1783076232 20 ctime=1783094099 bluez-5.87/ell/tester.h0000644000000000000000000000466714560467756013630 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2021 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_TESTER_H #define __ELL_TESTER_H #include #include #include #ifdef __cplusplus extern "C" { #endif struct l_tester; enum l_tester_stage { L_TESTER_STAGE_INVALID, L_TESTER_STAGE_PRE_SETUP, L_TESTER_STAGE_SETUP, L_TESTER_STAGE_RUN, L_TESTER_STAGE_TEARDOWN, L_TESTER_STAGE_POST_TEARDOWN, }; typedef void (*l_tester_destroy_func_t)(void *user_data); typedef void (*l_tester_data_func_t)(const void *test_data); typedef void (*l_tester_finish_func_t)(struct l_tester *tester); typedef void (*l_tester_wait_func_t)(void *user_data); struct l_tester *l_tester_new(const char *prefix, const char *substring, bool list_cases); void l_tester_destroy(struct l_tester *tester); void l_tester_start(struct l_tester *tester, l_tester_finish_func_t finish_func); bool l_tester_summarize(struct l_tester *tester); void l_tester_add_full(struct l_tester *tester, const char *name, const void *test_data, l_tester_data_func_t pre_setup_func, l_tester_data_func_t setup_func, l_tester_data_func_t test_func, l_tester_data_func_t teardown_func, l_tester_data_func_t post_teardown_func, unsigned int timeout, void *user_data, l_tester_destroy_func_t destroy); void l_tester_add(struct l_tester *tester, const char *name, const void *test_data, l_tester_data_func_t setup_func, l_tester_data_func_t test_func, l_tester_data_func_t teardown_func); void l_tester_pre_setup_complete(struct l_tester *tester); void l_tester_pre_setup_failed(struct l_tester *tester); void l_tester_setup_complete(struct l_tester *tester); void l_tester_setup_failed(struct l_tester *tester); void l_tester_test_passed(struct l_tester *tester); void l_tester_test_failed(struct l_tester *tester); void l_tester_test_abort(struct l_tester *tester); void l_tester_teardown_complete(struct l_tester *tester); void l_tester_teardown_failed(struct l_tester *tester); void l_tester_post_teardown_complete(struct l_tester *tester); void l_tester_post_teardown_failed(struct l_tester *tester); enum l_tester_stage l_tester_get_stage(struct l_tester *tester); void *l_tester_get_data(struct l_tester *tester); void l_tester_wait(struct l_tester *tester, unsigned int seconds, l_tester_wait_func_t func, void *user_data); #ifdef __cplusplus } #endif #endif /* __ELL_TESTER_H */ bluez-5.87/ell/PaxHeaders/cert-private.h0000644000000000000000000000005014504767710015220 xustar0020 atime=1783076232 20 ctime=1783094099 bluez-5.87/ell/cert-private.h0000644000000000000000000000230614504767710014702 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2018 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ struct asn1_oid; struct l_certchain *certchain_new_from_leaf(struct l_cert *leaf); void certchain_link_issuer(struct l_certchain *chain, struct l_cert *ca); const uint8_t *cert_get_extension(struct l_cert *cert, const struct asn1_oid *ext_id, bool *out_critical, size_t *out_len); struct l_key *cert_key_from_pkcs8_private_key_info(const uint8_t *der, size_t der_len); struct l_key *cert_key_from_pkcs8_encrypted_private_key_info(const uint8_t *der, size_t der_len, const char *passphrase); struct l_key *cert_key_from_pkcs1_rsa_private_key(const uint8_t *der, size_t der_len); struct cert_pkcs12_hash { enum l_checksum_type alg; unsigned int len; unsigned int u; unsigned int v; struct asn1_oid oid; }; uint8_t *cert_pkcs12_pbkdf(const char *password, const struct cert_pkcs12_hash *hash, const uint8_t *salt, size_t salt_len, unsigned int iterations, uint8_t id, size_t key_len); struct l_cipher *cert_cipher_from_pkcs_alg_id(const uint8_t *id_asn1, size_t id_asn1_len, const char *password, bool *out_is_block); bluez-5.87/ell/PaxHeaders/cleanup.h0000644000000000000000000000005014560467756014253 xustar0020 atime=1783076231 20 ctime=1783094099 bluez-5.87/ell/cleanup.h0000644000000000000000000000055714560467756013743 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2021 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #define __L_AUTODESTRUCT(func) \ __attribute((cleanup(_l_ ## func ## _cleanup))) #define DEFINE_CLEANUP_FUNC(func) \ inline __attribute__((always_inline)) \ void _l_ ## func ## _cleanup(void *p) { func(*(void **) p); } bluez-5.87/ell/PaxHeaders/siphash-private.h0000644000000000000000000000005014504767710015722 xustar0020 atime=1783076309 20 ctime=1783094099 bluez-5.87/ell/siphash-private.h0000644000000000000000000000040714504767710015404 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include void _siphash24(uint8_t out[8], const uint8_t *in, size_t inlen, const uint8_t k[16]); bluez-5.87/ell/PaxHeaders/tls.c0000644000000000000000000000005014560467756013421 xustar0020 atime=1783076312 20 ctime=1783094099 bluez-5.87/ell/tls.c0000644000000000000000000032004714560467756013110 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2015 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include "useful.h" #include "private.h" #include "tls.h" #include "checksum.h" #include "cipher.h" #include "random.h" #include "queue.h" #include "pem.h" #include "pem-private.h" #include "asn1-private.h" #include "cert.h" #include "cert-private.h" #include "tls-private.h" #include "key.h" #include "strv.h" #include "missing.h" #include "string.h" #include "settings.h" #include "time.h" #include "time-private.h" bool tls10_prf(const void *secret, size_t secret_len, const char *label, const void *seed, size_t seed_len, uint8_t *out, size_t out_len) { uint8_t p_hash2[out_len]; uint8_t l_s1 = (secret_len + 1) / 2; unsigned int i; /* * RFC2246 section 5: * S1 and S2 are the two halves of the secret, and each is the same * length. S1 is taken from the first half of the secret, S2 from the * second half. Their length is created by rounding up the length of * the overall secret, divided by two; thus, if the original secret is * an odd number of bytes long, the last byte of S1 will be the same as * the first byte of S2. */ if (!tls12_prf(L_CHECKSUM_MD5, secret, l_s1, label, seed, seed_len, out, out_len)) return false; if (secret_len > 0) secret += secret_len - l_s1; if (!tls12_prf(L_CHECKSUM_SHA1, secret, l_s1, label, seed, seed_len, p_hash2, out_len)) return false; for (i = 0; i < out_len; i++) out[i] ^= p_hash2[i]; return true; } bool tls12_prf(enum l_checksum_type type, const void *secret, size_t secret_len, const char *label, const void *seed, size_t seed_len, uint8_t *out, size_t out_len) { struct l_checksum *hmac = l_checksum_new_hmac(type, secret, secret_len); size_t a_len, chunk_len, prfseed_len = strlen(label) + seed_len; uint8_t a[64 + prfseed_len], prfseed[prfseed_len]; if (!hmac) return false; /* Generate the hash seed or A(0) as label + seed */ memcpy(prfseed, label, strlen(label)); memcpy(prfseed + strlen(label), seed, seed_len); memcpy(a, prfseed, prfseed_len); a_len = prfseed_len; while (out_len) { /* Generate A(i) */ l_checksum_reset(hmac); l_checksum_update(hmac, a, a_len); a_len = l_checksum_get_digest(hmac, a, sizeof(a)); /* Append seed & generate output */ memcpy(a + a_len, prfseed, prfseed_len); l_checksum_reset(hmac); l_checksum_update(hmac, a, a_len + prfseed_len); chunk_len = l_checksum_get_digest(hmac, out, out_len); out += chunk_len; out_len -= chunk_len; } l_checksum_free(hmac); return true; } static bool tls_prf_get_bytes(struct l_tls *tls, const void *secret, size_t secret_len, const char *label, const void *seed, size_t seed_len, uint8_t *buf, size_t len) { if (tls->negotiated_version >= L_TLS_V12) return tls12_prf(tls->prf_hmac->l_id, secret, secret_len, label, seed, seed_len, buf, len); return tls10_prf(secret, secret_len, label, seed, seed_len, buf, len); } LIB_EXPORT bool l_tls_prf_get_bytes(struct l_tls *tls, bool use_master_secret, const char *label, uint8_t *buf, size_t len) { uint8_t seed[64]; bool r; if (unlikely(!tls || !tls->prf_hmac)) return false; memcpy(seed + 0, tls->pending.client_random, 32); memcpy(seed + 32, tls->pending.server_random, 32); if (use_master_secret) r = tls_prf_get_bytes(tls, tls->pending.master_secret, 48, label, seed, 64, buf, len); else r = tls_prf_get_bytes(tls, "", 0, label, seed, 64, buf, len); explicit_bzero(seed, 64); return r; } static void tls_write_random(uint8_t *buf) { l_put_be32(time(NULL), buf); l_getrandom(buf + 4, 28); } static void tls_drop_handshake_hash(struct l_tls *tls, enum handshake_hash_type hash) { if (tls->handshake_hash[hash]) { l_checksum_free(tls->handshake_hash[hash]); tls->handshake_hash[hash] = NULL; } } static void tls_reset_handshake(struct l_tls *tls) { enum handshake_hash_type hash; explicit_bzero(tls->pending.key_block, sizeof(tls->pending.key_block)); if (tls->pending.cipher_suite && tls->pending.cipher_suite->key_xchg->free_params) tls->pending.cipher_suite->key_xchg->free_params(tls); l_cert_free(tls->peer_cert); l_key_free(tls->peer_pubkey); tls->peer_cert = NULL; tls->peer_pubkey = NULL; tls->peer_pubkey_size = 0; tls->peer_authenticated = false; tls->negotiated_curve = NULL; tls->negotiated_ff_group = NULL; for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) tls_drop_handshake_hash(tls, hash); TLS_SET_STATE(TLS_HANDSHAKE_WAIT_START); tls->cert_requested = 0; tls->cert_sent = 0; tls->session_id_size = 0; tls->session_id_size_replaced = 0; tls->session_id_new = false; l_free(l_steal_ptr(tls->session_peer_identity)); tls->session_resumed = false; } static void tls_cleanup_handshake(struct l_tls *tls) { explicit_bzero(tls->pending.client_random, 32); explicit_bzero(tls->pending.server_random, 32); explicit_bzero(tls->pending.master_secret, 48); } static bool tls_change_cipher_spec(struct l_tls *tls, bool txrx, const char **error) { struct tls_bulk_encryption_algorithm *enc; struct tls_mac_algorithm *mac; int key_offset; static char error_buf[200]; if (tls->cipher_type[txrx] == TLS_CIPHER_AEAD) { if (tls->aead_cipher[txrx]) { l_aead_cipher_free(tls->aead_cipher[txrx]); tls->aead_cipher[txrx] = NULL; } } else { if (tls->cipher[txrx]) { l_cipher_free(tls->cipher[txrx]); tls->cipher[txrx] = NULL; } } tls->cipher_type[txrx] = TLS_CIPHER_STREAM; if (tls->mac[txrx]) { l_checksum_free(tls->mac[txrx]); tls->mac[txrx] = NULL; } tls->mac_length[txrx] = 0; tls->block_length[txrx] = 0; tls->record_iv_length[txrx] = 0; if (tls->fixed_iv_length[txrx]) { explicit_bzero(tls->fixed_iv[txrx], tls->fixed_iv_length[txrx]); tls->fixed_iv_length[txrx] = 0; } tls->auth_tag_length[txrx] = 0; tls->seq_num[txrx] = 0; tls->cipher_suite[txrx] = tls->pending.cipher_suite; if (!tls->cipher_suite[txrx]) return true; key_offset = 0; if (tls->cipher_suite[txrx]->mac) { mac = tls->cipher_suite[txrx]->mac; /* Server write / client read is 2nd in the key block */ if ((tls->server && txrx) || (!tls->server && !txrx)) key_offset += mac->mac_length; tls->mac[txrx] = l_checksum_new_hmac(mac->hmac_type, tls->pending.key_block + key_offset, mac->mac_length); /* Wipe out the now unneeded part of the key block */ explicit_bzero(tls->pending.key_block + key_offset, mac->mac_length); if (!tls->mac[txrx]) { if (error) { *error = error_buf; snprintf(error_buf, sizeof(error_buf), "Can't create %s's %s HMAC", tls->cipher_suite[txrx]->name, txrx ? "Tx" : "Rx"); } return false; } tls->mac_length[txrx] = mac->mac_length; key_offset = 2 * mac->mac_length; } if (tls->cipher_suite[txrx]->encryption) { void *cipher; enc = tls->cipher_suite[txrx]->encryption; /* Server write / client read is 4th in the key block */ if ((tls->server && txrx) || (!tls->server && !txrx)) key_offset += enc->key_length; if (enc->cipher_type == TLS_CIPHER_AEAD) { cipher = l_aead_cipher_new(enc->l_aead_id, tls->pending.key_block + key_offset, enc->key_length, enc->auth_tag_length); tls->aead_cipher[txrx] = cipher; } else { cipher = l_cipher_new(enc->l_id, tls->pending.key_block + key_offset, enc->key_length); tls->cipher[txrx] = cipher; } /* Wipe out the now unneeded part of the key block */ explicit_bzero(tls->pending.key_block + key_offset, enc->key_length); if (!cipher) { if (error) { *error = error_buf; snprintf(error_buf, sizeof(error_buf), "Can't create %s's %s cipher", tls->cipher_suite[txrx]->name, txrx ? "Tx" : "Rx"); } return false; } tls->cipher_type[txrx] = enc->cipher_type; tls->record_iv_length[txrx] = enc->iv_length - enc->fixed_iv_length; tls->block_length[txrx] = enc->block_length; tls->auth_tag_length[txrx] = enc->auth_tag_length; if ((tls->server && txrx) || (!tls->server && !txrx)) key_offset += enc->key_length; else key_offset += 2 * enc->key_length; } if (tls->negotiated_version <= L_TLS_V10 && tls->cipher_suite[txrx]->encryption && tls->cipher_suite[txrx]->encryption->cipher_type == TLS_CIPHER_BLOCK) { enc = tls->cipher_suite[txrx]->encryption; /* Server write / client read is 6th in the key block */ if ((tls->server && txrx) || (!tls->server && !txrx)) key_offset += enc->iv_length; l_cipher_set_iv(tls->cipher[txrx], tls->pending.key_block + key_offset, enc->iv_length); /* Wipe out the now unneeded part of the key block */ explicit_bzero(tls->pending.key_block + key_offset, enc->iv_length); } else if (tls->cipher_suite[txrx]->encryption && tls->cipher_suite[txrx]->encryption->fixed_iv_length) { enc = tls->cipher_suite[txrx]->encryption; /* Server write / client read is 6th in the key block */ if ((tls->server && txrx) || (!tls->server && !txrx)) key_offset += enc->fixed_iv_length; tls->fixed_iv_length[txrx] = enc->fixed_iv_length; memcpy(tls->fixed_iv[txrx], tls->pending.key_block + key_offset, enc->fixed_iv_length); /* Wipe out the now unneeded part of the key block */ explicit_bzero(tls->pending.key_block + key_offset, enc->fixed_iv_length); } return true; } static void tls_reset_cipher_spec(struct l_tls *tls, bool txrx) { /* Reset everything to the TLS_NULL_WITH_NULL_NULL state */ tls->pending.cipher_suite = NULL; tls_change_cipher_spec(tls, txrx, NULL); } static bool tls_cipher_suite_is_compatible_no_key_xchg(struct l_tls *tls, const struct tls_cipher_suite *suite, const char **error) { static char error_buf[200]; struct l_cert *leaf; enum l_tls_version min_version = tls->negotiated_version ?: tls->min_version; enum l_tls_version max_version = tls->negotiated_version ?: tls->max_version; if (suite->encryption && suite->encryption->cipher_type == TLS_CIPHER_AEAD) { if (max_version < L_TLS_V12) { if (error) { *error = error_buf; snprintf(error_buf, sizeof(error_buf), "Cipher suite %s uses an AEAD " "cipher (TLS 1.2+) but " TLS_VER_FMT " was negotiated or is the max " "version allowed", suite->name, TLS_VER_ARGS(tls->max_version)); } return false; } if (!l_aead_cipher_is_supported(suite->encryption->l_aead_id)) { if (error) { *error = error_buf; snprintf(error_buf, sizeof(error_buf), "Cipher suite %s's AEAD cipher " "algorithm not supported by " "the kernel", suite->name); } return false; } } else if (suite->encryption) { /* Block or stream cipher */ if (!l_cipher_is_supported(suite->encryption->l_id)) { if (error) { *error = error_buf; snprintf(error_buf, sizeof(error_buf), "Cipher suite %s's block/stream" " cipher algorithm not " "supported by the kernel", suite->name); } return false; } } if (suite->mac && !l_checksum_is_supported(suite->mac->hmac_type, true)) { if (error) { *error = error_buf; snprintf(error_buf, sizeof(error_buf), "Cipher suite %s's HMAC algorithm not " "supported by the kernel", suite->name); } return false; } if ( (max_version < L_TLS_V12 && (!l_checksum_is_supported(L_CHECKSUM_MD5, true) || !l_checksum_is_supported(L_CHECKSUM_SHA1, true))) || (min_version >= L_TLS_V12 && !l_checksum_is_supported( suite->prf_hmac != L_CHECKSUM_NONE ? suite->prf_hmac : L_CHECKSUM_SHA256, true))) { if (error) { *error = error_buf; snprintf(error_buf, sizeof(error_buf), "Cipher suite %s's PRF algorithm not " "supported by the kernel", suite->name); } return false; } /* * If the certificate is compatible with the signature algorithm it * also must be compatible with the key exchange mechanism because * the cipher suites are defined so that the same certificates can * be used by both. */ leaf = l_certchain_get_leaf(tls->cert); if (leaf && suite->signature && !suite->signature->validate_cert_key_type(leaf)) { if (error) { *error = error_buf; snprintf(error_buf, sizeof(error_buf), "Local certificate has key type " "incompatible with cipher suite %s's " "signature algorithm", suite->name); } return false; } return true; } /* * Assumes that Client Hello extensions have been processed and that we * will want to start a new session using this cipher suite, including the * key exchange. This is unlike tls_cipher_suite_is_compatible_no_key_xchg() * which runs fewer checks and must succeed even for a cipher suite loaded * during session resumption. */ bool tls_cipher_suite_is_compatible(struct l_tls *tls, const struct tls_cipher_suite *suite, const char **error) { static char error_buf[200]; if (!tls_cipher_suite_is_compatible_no_key_xchg(tls, suite, error)) return false; if (suite->key_xchg->need_ffdh && !l_key_is_supported(L_KEY_FEATURE_DH)) { if (error) { *error = error_buf; snprintf(error_buf, sizeof(error_buf), "Cipher suite %s's key exchange " "mechanism needs kernel DH support", suite->name); } return false; } /* * On the server we know what elliptic curve we'll be using as soon * as we've processed the ClientHello so for EC-based key exchange * methods require that a curve has been selected. */ if (suite->key_xchg->need_ecc && tls->server && !tls->negotiated_curve) { if (error) { *error = error_buf; snprintf(error_buf, sizeof(error_buf), "No common supported elliptic curves " "with the client, can't use %s", suite->name); } return false; } /* Similarly for FF DH groups */ if (suite->key_xchg->need_ffdh && tls->server && !tls->negotiated_ff_group) { if (error) { *error = error_buf; snprintf(error_buf, sizeof(error_buf), "No common supported finite-field " "groups with the client, can't use %s", suite->name); } return false; } return true; } static struct tls_cipher_suite *tls_find_cipher_suite(const uint8_t *id) { struct tls_cipher_suite **suite; for (suite = tls_cipher_suite_pref; *suite; suite++) if ((*suite)->id[0] == id[0] && (*suite)->id[1] == id[1]) return *suite; return NULL; } static struct tls_compression_method tls_compression_pref[] = { { 0, "CompressionMethod.null", }, }; static struct tls_compression_method *tls_find_compression_method( const uint8_t id) { int i; for (i = 0; i < (int) L_ARRAY_SIZE(tls_compression_pref); i++) if (tls_compression_pref[i].id == id) return &tls_compression_pref[i]; return NULL; } const struct tls_hash_algorithm tls_handshake_hash_data[] = { [HANDSHAKE_HASH_SHA384] = { 5, HANDSHAKE_HASH_SHA384, L_CHECKSUM_SHA384, "SHA384" }, [HANDSHAKE_HASH_SHA256] = { 4, HANDSHAKE_HASH_SHA256, L_CHECKSUM_SHA256, "SHA256" }, [HANDSHAKE_HASH_MD5] = { 1, HANDSHAKE_HASH_MD5, L_CHECKSUM_MD5, "MD5" }, [HANDSHAKE_HASH_SHA1] = { 2, HANDSHAKE_HASH_SHA1, L_CHECKSUM_SHA1, "SHA1" }, }; static bool tls_init_handshake_hash(struct l_tls *tls) { enum handshake_hash_type hash; bool tls10 = tls->max_version < L_TLS_V12; for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) { /* Skip hash types we already know we won't need */ if (tls10 && hash != HANDSHAKE_HASH_SHA1 && hash != HANDSHAKE_HASH_MD5) continue; if (tls->handshake_hash[hash]) { TLS_DEBUG("Handshake hash %s already exists", tls_handshake_hash_data[hash].name); goto err; } tls->handshake_hash[hash] = l_checksum_new( tls_handshake_hash_data[hash].l_id); if (!tls->handshake_hash[hash]) { TLS_DEBUG("Can't create %s hash", tls_handshake_hash_data[hash].name); goto err; } } return true; err: for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) tls_drop_handshake_hash(tls, hash); return false; } static const struct tls_hash_algorithm *tls_set_prf_hmac(struct l_tls *tls) { enum handshake_hash_type hash; if (tls->pending.cipher_suite->prf_hmac == L_CHECKSUM_NONE) { tls->prf_hmac = &tls_handshake_hash_data[HANDSHAKE_HASH_SHA256]; return tls->prf_hmac; } for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) if (tls_handshake_hash_data[hash].l_id == tls->pending.cipher_suite->prf_hmac) { tls->prf_hmac = &tls_handshake_hash_data[hash]; return tls->prf_hmac; } return NULL; } static bool tls_domain_match_mask(const char *name, size_t name_len, const char *mask, size_t mask_len) { bool at_start = true; while (1) { const char *name_seg_end = memchr(name, '.', name_len); const char *mask_seg_end = memchr(mask, '.', mask_len); size_t name_seg_len = name_seg_end ? (size_t) (name_seg_end - name) : name_len; size_t mask_seg_len = mask_seg_end ? (size_t) (mask_seg_end - mask) : mask_len; if (mask_seg_len == 1 && mask[0] == '*') { /* * A * at the beginning of the mask matches any * number of labels. */ if (at_start && name_seg_end && tls_domain_match_mask(name_seg_end + 1, name_len - name_seg_len - 1, mask, mask_len)) return true; goto ok_next; } if (name_seg_len != mask_seg_len || memcmp(name, mask, name_seg_len)) return false; ok_next: /* If either string ends here both must end here */ if (!name_seg_end || !mask_seg_end) return !name_seg_end && !mask_seg_end; at_start = false; name = name_seg_end + 1; name_len -= name_seg_len + 1; mask = mask_seg_end + 1; mask_len -= mask_seg_len + 1; } } static const struct asn1_oid subject_alt_name_oid = { 3, { 0x55, 0x1d, 0x11 } }; static const struct asn1_oid dn_common_name_oid = { 3, { 0x55, 0x04, 0x03 } }; #define SAN_DNS_NAME_ID ASN1_CONTEXT_IMPLICIT(2) static bool tls_cert_domains_match_mask(struct l_cert *cert, char **mask, char **error_msg) { const uint8_t *san, *dn, *end; size_t san_len, dn_len; uint8_t san_tag; const char *cn = NULL; size_t cn_len; char **i; struct l_string *dns_names = NULL; int dns_name_count = 0; /* * Locate SubjectAltName (RFC5280 Section 4.2.1.6) and descend into * the sole SEQUENCE element, check if any DNSName matches. */ san = cert_get_extension(cert, &subject_alt_name_oid, NULL, &san_len); if (san) { san = asn1_der_find_elem(san, san_len, 0, &san_tag, &san_len); if (unlikely(!san || san_tag != ASN1_ID_SEQUENCE)) goto parse_error; end = san + san_len; while (san < end) { const uint8_t *value; uint8_t tag; size_t len; value = asn1_der_find_elem(san, end - san, SAN_DNS_NAME_ID, &tag, &len); if (!value) break; /* Type is implicitly IA5STRING */ for (i = mask; *i; i++) if (tls_domain_match_mask((const char *) value, len, *i, strlen(*i))) { l_string_free(dns_names); return true; } if (!dns_names) { dns_names = l_string_new(128); l_string_append(dns_names, "tried DNSName(s) "); l_string_append_fixed(dns_names, (char *) value, len); } else if (dns_name_count < 20) { l_string_append(dns_names, ", "); l_string_append_fixed(dns_names, (char *) value, len); } san = value + len; dns_name_count++; } } /* * Retrieve the Common Name from the Subject DN and check if it * matches. * * We look at the Common Name only if no DNSNames were present in * the certificate, following Wi-Fi Alliance's Hotspot 2.0 * Specification v3.1 section 7.3.3.2 step 2: * "Verify in the AAA server certificate that the domain name from * the FQDN [...] is a suffix match of the domain name in at least * one of the DNSName SubjectAltName extensions. If a SubjectAltName * of type DNSName is not present, then the domain name from the * FQDN shall be a suffix match to the CommonName portion of the * SubjectName. If neither of these conditions holds, then * verification fails." */ if (dns_name_count) { if (dns_name_count > 20) l_string_append_printf(dns_names, " and %i other", dns_name_count - 20); *error_msg = l_string_unwrap(dns_names); return false; } dn = l_cert_get_dn(cert, &dn_len); if (unlikely(!dn)) goto parse_error; end = dn + dn_len; while (dn < end) { const uint8_t *set, *seq, *oid, *name; uint8_t tag; size_t len, oid_len, name_len; set = asn1_der_find_elem(dn, end - dn, 0, &tag, &len); if (unlikely(!set || tag != ASN1_ID_SET)) goto parse_error; dn = set + len; seq = asn1_der_find_elem(set, len, 0, &tag, &len); if (unlikely(!seq || tag != ASN1_ID_SEQUENCE)) goto parse_error; oid = asn1_der_find_elem(seq, len, 0, &tag, &oid_len); if (unlikely(!oid || tag != ASN1_ID_OID)) goto parse_error; name = asn1_der_find_elem(seq, len, 1, &tag, &name_len); if (unlikely(!name || (tag != ASN1_ID_PRINTABLESTRING && tag != ASN1_ID_UTF8STRING && tag != ASN1_ID_IA5STRING))) continue; if (asn1_oid_eq(&dn_common_name_oid, oid_len, oid)) { cn = (const char *) name; cn_len = name_len; break; } } if (unlikely(!cn)) goto parse_error; for (i = mask; *i; i++) if (tls_domain_match_mask(cn, cn_len, *i, strlen(*i))) return true; *error_msg = l_strdup_printf("tried CommonName %.*s", (int) cn_len, cn); return false; parse_error: *error_msg = l_strdup("couldn't locate DNSName or CommonName"); return false; } static const char *tls_get_cache_group_name(struct l_tls *tls, const uint8_t *session_id, size_t session_id_size) { _auto_(l_free) char *session_id_str = NULL; static char group_name[256]; if (!tls->server) return tls->session_prefix; session_id_str = l_util_hexstring(session_id, session_id_size); snprintf(group_name, sizeof(group_name), "%s-%s", tls->session_prefix, session_id_str); return group_name; } static void tls_forget_cached_session(struct l_tls *tls, const char *group_name, const uint8_t *session_id, size_t session_id_size, bool call_back) { if (!group_name) group_name = tls_get_cache_group_name(tls, session_id, session_id_size); l_settings_remove_group(tls->session_settings, group_name); if (call_back && tls->session_update_cb) { tls->in_callback = true; tls->session_update_cb(tls->session_update_user_data); tls->in_callback = false; } } static bool tls_load_cached_session(struct l_tls *tls, const char *group_name, const uint8_t *session_id, size_t session_id_size, const char *session_id_str) { _auto_(l_free) uint8_t *master_secret = NULL; int version; _auto_(l_free) uint8_t *cipher_suite_id = NULL; struct tls_cipher_suite *cipher_suite; unsigned int compression_method_id; _auto_(l_free) char *peer_identity = NULL; size_t size; const char *error; if (l_settings_has_key(tls->session_settings, group_name, "SessionExpiryTime")) { uint64_t expiry_time; if (unlikely(!l_settings_get_uint64(tls->session_settings, group_name, "SessionExpiryTime", &expiry_time))) goto warn_corrupt; if (time_realtime_now() > expiry_time) { TLS_DEBUG("Cached session %s is expired, removing it, " "will start a new session", session_id_str); goto forget; } } if (unlikely(!l_settings_get_int(tls->session_settings, group_name, "SessionVersion", &version) || version < TLS_MIN_VERSION || version > TLS_MAX_VERSION)) goto warn_corrupt; master_secret = l_settings_get_bytes(tls->session_settings, group_name, "SessionMasterSecret", &size); if (unlikely(!master_secret || size != 48)) goto warn_corrupt; cipher_suite_id = l_settings_get_bytes(tls->session_settings, group_name, "SessionCipherSuite", &size); if (unlikely(!cipher_suite_id || size != 2 || !(cipher_suite = tls_find_cipher_suite(cipher_suite_id)))) goto warn_corrupt; /* * While we could attempt to resume a session even though we're now * configured with, say, a different certificate type than what we * had when we cached that session, that is too questionable of a * scenario to support it. We don't specifically check that all of * the authentication data is the same, e.g. we don't save the * certificate serial number or path, but ensure the cached cipher * suite is compatible with current authentication data. * * We filter the cipher suites in our Client Hello to only offer the * ones compatible with current configuration so if we also include * a Session ID from a session who's cipher suite is not one of those * listed in that same Client Hello, the server is likely to notice * and either start a new session or send a fatal Alert. * * It is up to the user to keep multiple cache instances if it needs * to save multiple sessions. */ if (unlikely(!tls_cipher_suite_is_compatible_no_key_xchg(tls, cipher_suite, &error))) { TLS_DEBUG("Cached session %s cipher suite not compatible: %s", session_id_str, error); goto forget; } if (unlikely(!l_settings_get_uint(tls->session_settings, group_name, "SessionCompressionMethod", &compression_method_id) || !tls_find_compression_method(compression_method_id))) goto warn_corrupt; if (l_settings_has_key(tls->session_settings, group_name, "SessionPeerIdentity")) { peer_identity = l_settings_get_string(tls->session_settings, group_name, "SessionPeerIdentity"); if (unlikely(!peer_identity || !cipher_suite->signature)) goto warn_corrupt; } tls->session_id_size = session_id_size; memcpy(tls->session_id, session_id, session_id_size); tls->session_id_new = false; tls->client_version = version; memcpy(tls->pending.master_secret, master_secret, 48); memcpy(tls->session_cipher_suite_id, cipher_suite_id, 2); tls->session_compression_method_id = compression_method_id; l_free(tls->session_peer_identity); tls->session_peer_identity = l_steal_ptr(peer_identity); return true; warn_corrupt: TLS_DEBUG("Cached session %s data is corrupt or has unsupported " "parameters, removing it, will start a new session", session_id_str); forget: tls_forget_cached_session(tls, group_name, session_id, session_id_size, true); return false; } static bool tls_load_cached_client_session(struct l_tls *tls) { /* * The following settings are required: * SessionID, * SessionMasterSecret, * SessionVersion, * SessionCipherSuite, * SessionCompressionMethod, * and these two are optional: * SessionExpiryTime, * SessionPeerIdentity. */ _auto_(l_free) uint8_t *session_id = NULL; size_t session_id_size; _auto_(l_free) char *session_id_str = NULL; const char *group_name = tls_get_cache_group_name(tls, NULL, 0); tls->session_id_size = 0; tls->session_id_new = false; if (!tls->session_settings || !l_settings_has_key(tls->session_settings, group_name, "SessionID")) /* No session cached, no error */ return false; session_id = l_settings_get_bytes(tls->session_settings, group_name, "SessionID", &session_id_size); if (unlikely(!session_id || session_id_size < 1 || session_id_size > 32)) { TLS_DEBUG("Bad cached session ID format"); tls_forget_cached_session(tls, group_name, NULL, 0, true); return false; } session_id_str = l_util_hexstring(session_id, session_id_size); return tls_load_cached_session(tls, group_name, session_id, session_id_size, session_id_str); } static bool tls_load_cached_server_session(struct l_tls *tls, const uint8_t *session_id, size_t session_id_size) { _auto_(l_free) char *session_id_str = l_util_hexstring(session_id, session_id_size); const char *target_group_name = tls_get_cache_group_name(tls, session_id, session_id_size); _auto_(l_strv_free) char **groups = l_settings_get_groups(tls->session_settings); char **group; unsigned int cnt = 0; size_t prefix_len = strlen(tls->session_prefix); uint64_t now = time_realtime_now(); char *oldest_session_group = NULL; uint64_t oldest_session_expiry = UINT64_MAX; bool found = false; bool changed = false; bool loaded = false; tls->session_id_size = 0; tls->session_id_new = false; /* Clean up expired entries and enforce session count limit */ for (group = groups; *group; group++) { uint64_t expiry_time; if (memcmp(*group, tls->session_prefix, prefix_len) || (*group)[prefix_len] != '-') continue; /* Group seems to be a session cache entry */ if (unlikely(!l_settings_get_uint64(tls->session_settings, *group, "SessionExpiryTime", &expiry_time)) || expiry_time <= now) { TLS_DEBUG("Cached session %s is expired or invalid, " "purging entry", *group + prefix_len + 1); l_settings_remove_group(tls->session_settings, *group); changed = true; continue; } cnt++; if (!strcmp(*group + prefix_len + 1, target_group_name + prefix_len + 1)) { found = true; continue; /* Don't purge this entry */ } if (expiry_time < oldest_session_expiry) { oldest_session_group = *group; oldest_session_expiry = expiry_time; } } /* * Enforce tls->session_count_max by dropping the entry for the * oldest session (more specifically the one closest to its expiry * time) in the cache, that is not the session we're trying to * load. If the target session was not found, do this as soon as * tls->session_count_max is reached rather than when it's exceeded * so as to make room for a new entry. If we end up not saving * a new session due to an error before the handshake finish, we'll * be left with tls->session_count_max - 1 entries and will have * purged that entry unnecessarily but the cache will have room for * a future session. Same when we did find the target session but * later had to forget it because of a fatal alert during or after * the handshake. * * If we did find the target session but we later find that we * can't resume it, we will forget it so the new session can take * the old session's place and the limit is not exceeded, see * discussion in tls_server_resume_error. */ if (tls->session_count_max && oldest_session_group && cnt >= tls->session_count_max + (found ? 1 : 0)) { l_settings_remove_group(tls->session_settings, oldest_session_group); changed = true; } if (!found) { TLS_DEBUG("Requested session %s not found in cache, will " "start a new session", session_id_str); goto call_back; } loaded = tls_load_cached_session(tls, target_group_name, session_id, session_id_size, session_id_str); /* * If tls_load_cached_session() returned false it will have called * session_update_cb for us. */ if (!loaded) changed = false; call_back: if (changed && tls->session_update_cb) { tls->in_callback = true; tls->session_update_cb(tls->session_update_user_data); tls->in_callback = false; } return loaded; } #define SWITCH_ENUM_TO_STR(val) \ case (val): \ return L_STRINGIFY(val); static const char *tls_handshake_type_to_str(enum tls_handshake_type type) { static char buf[100]; switch (type) { SWITCH_ENUM_TO_STR(TLS_HELLO_REQUEST) SWITCH_ENUM_TO_STR(TLS_CLIENT_HELLO) SWITCH_ENUM_TO_STR(TLS_SERVER_HELLO) SWITCH_ENUM_TO_STR(TLS_CERTIFICATE) SWITCH_ENUM_TO_STR(TLS_SERVER_KEY_EXCHANGE) SWITCH_ENUM_TO_STR(TLS_CERTIFICATE_REQUEST) SWITCH_ENUM_TO_STR(TLS_SERVER_HELLO_DONE) SWITCH_ENUM_TO_STR(TLS_CERTIFICATE_VERIFY) SWITCH_ENUM_TO_STR(TLS_CLIENT_KEY_EXCHANGE) SWITCH_ENUM_TO_STR(TLS_FINISHED) } snprintf(buf, sizeof(buf), "tls_handshake_type(%i)", type); return buf; } static void tls_send_alert(struct l_tls *tls, bool fatal, enum l_tls_alert_desc alert_desc) { uint8_t buf[2]; TLS_DEBUG("Sending a %s Alert: %s", fatal ? "Fatal" : "Warning", l_tls_alert_to_str(alert_desc)); buf[0] = fatal ? 2 : 1; buf[1] = alert_desc; tls_tx_record(tls, TLS_CT_ALERT, buf, 2); } /* * Callers make sure this is about the last function before returning * from the stack frames up to the exported library call so that the * user-supplied disconnected callback here is free to use l_tls_free * for example. */ void tls_disconnect(struct l_tls *tls, enum l_tls_alert_desc desc, enum l_tls_alert_desc local_desc) { bool forget_session = false; /* Save session_id_size before tls_reset_handshake() */ size_t session_id_size = tls->session_id_size; if ((desc || local_desc) && tls->session_settings && session_id_size && !tls->session_id_new) /* * RFC5246 Section 7.2: "Alert messages with a level of fatal * result in the immediate termination of the connection. In * this case, other connections corresponding to the session * may continue, but the session identifier MUST be * invalidated, preventing the failed session from being used * to establish new connections." * * and 7.2.1: "Note that as of TLS 1.1, failure to properly * close a connection no longer requires that a session not * be resumed." * * I.e. we need to remove the session from the cache here but * not on l_tls_close(). */ forget_session = true; tls_send_alert(tls, true, desc); tls_reset_handshake(tls); tls_cleanup_handshake(tls); tls_reset_cipher_spec(tls, 0); tls_reset_cipher_spec(tls, 1); tls->negotiated_version = 0; tls->ready = false; tls->renegotiation_info.secure_renegotiation = false; if (forget_session) { tls_forget_cached_session(tls, NULL, tls->session_id, session_id_size, true); if (tls->pending_destroy) return; } tls->disconnected(local_desc ?: desc, local_desc && !desc, tls->user_data); } void tls_tx_handshake(struct l_tls *tls, int type, uint8_t *buf, size_t length) { int i; TLS_DEBUG("Sending a %s of %zi bytes", tls_handshake_type_to_str(type), length - TLS_HANDSHAKE_HEADER_SIZE); /* Fill in the handshake header */ buf[0] = type; buf[1] = (length - TLS_HANDSHAKE_HEADER_SIZE) >> 16; buf[2] = (length - TLS_HANDSHAKE_HEADER_SIZE) >> 8; buf[3] = (length - TLS_HANDSHAKE_HEADER_SIZE) >> 0; for (i = 0; i < __HANDSHAKE_HASH_COUNT; i++) if (tls->handshake_hash[i]) l_checksum_update(tls->handshake_hash[i], buf, length); tls_tx_record(tls, TLS_CT_HANDSHAKE, buf, length); } static ssize_t tls_append_hello_extensions(struct l_tls *tls, struct l_queue *extensions, uint8_t *buf, size_t len) { uint8_t *ptr = buf; uint8_t *extensions_len_ptr = ptr; bool client_hello = !tls->server; unsigned int i = 0; const struct l_queue_entry *entry = l_queue_get_entries(extensions); if (len < 2) return -ENOSPC; ptr += 2; len -= 2; while (1) { const struct tls_hello_extension *extension; ssize_t ext_len; ssize_t (*ext_write)(struct l_tls *tls, uint8_t *buf, size_t len); if (client_hello) { extension = &tls_extensions[i++]; if (!extension->name) break; ext_write = extension->client_write; } else { uint16_t ext_id; if (!entry) break; ext_id = L_PTR_TO_UINT(entry->data); entry = entry->next; for (i = 0; tls_extensions[i].name; i++) if (tls_extensions[i].id == ext_id) break; extension = &tls_extensions[i]; if (!extension->name) continue; ext_write = extension->server_write; } /* * Note: could handle NULL client_write with non-NULL * server_handle or server_handle_absent as "server-oriented" * extension (7.4.1.4) and write empty extension_data and * similarly require empty extension_data in * tls_handle_client_hello if client_handle NULL. */ if (!ext_write) continue; if (len < 4) return -ENOSPC; ext_len = ext_write(tls, ptr + 4, len - 4); if (ext_len == -ENOMSG) continue; if (ext_len < 0) { TLS_DEBUG("%s extension's %s_write: %s", extension->name, client_hello ? "client" : "server", strerror(-ext_len)); return ext_len; } l_put_be16(extension->id, ptr + 0); l_put_be16(ext_len, ptr + 2); ptr += 4 + ext_len; len -= 4 + ext_len; } if (ptr > extensions_len_ptr + 2) l_put_be16(ptr - (extensions_len_ptr + 2), extensions_len_ptr); else /* Skip the length if no extensions */ ptr = extensions_len_ptr; return ptr - buf; } static bool tls_send_client_hello(struct l_tls *tls) { uint8_t buf[1024 + L_ARRAY_SIZE(tls_compression_pref)]; uint8_t *ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; uint8_t *len_ptr; unsigned int i; ssize_t r; struct tls_cipher_suite **suite; /* Fill in the Client Hello body */ *ptr++ = (uint8_t) (tls->client_version >> 8); *ptr++ = (uint8_t) (tls->client_version >> 0); tls_write_random(tls->pending.client_random); memcpy(ptr, tls->pending.client_random, 32); ptr += 32; if (tls->session_id_size) { *ptr++ = tls->session_id_size; memcpy(ptr, tls->session_id, tls->session_id_size); ptr += tls->session_id_size; } else *ptr++ = 0; len_ptr = ptr; ptr += 2; for (suite = tls->cipher_suite_pref_list; *suite; suite++) { const char *error; if (!tls_cipher_suite_is_compatible(tls, *suite, &error)) { TLS_DEBUG("non-fatal: %s", error); continue; } *ptr++ = (*suite)->id[0]; *ptr++ = (*suite)->id[1]; } if (ptr == len_ptr + 2) { TLS_DEBUG("No compatible cipher suites, check kernel config, " "certificate's key type and TLS version range"); return false; } l_put_be16((ptr - len_ptr - 2), len_ptr); *ptr++ = L_ARRAY_SIZE(tls_compression_pref); for (i = 0; i < L_ARRAY_SIZE(tls_compression_pref); i++) *ptr++ = tls_compression_pref[i].id; r = tls_append_hello_extensions(tls, NULL, ptr, buf + sizeof(buf) - ptr); if (r < 0) return false; ptr += r; tls_tx_handshake(tls, TLS_CLIENT_HELLO, buf, ptr - buf); return true; } static bool tls_send_server_hello(struct l_tls *tls, struct l_queue *extensions) { uint8_t buf[1024]; uint8_t *ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; ssize_t r; /* Fill in the Server Hello body */ *ptr++ = tls->negotiated_version >> 8; *ptr++ = tls->negotiated_version >> 0; tls_write_random(tls->pending.server_random); memcpy(ptr, tls->pending.server_random, 32); ptr += 32; if (tls->session_id_size) { *ptr++ = tls->session_id_size; memcpy(ptr, tls->session_id, tls->session_id_size); ptr += tls->session_id_size; } else *ptr++ = 0; *ptr++ = tls->pending.cipher_suite->id[0]; *ptr++ = tls->pending.cipher_suite->id[1]; *ptr++ = tls->pending.compression_method->id; r = tls_append_hello_extensions(tls, extensions, ptr, buf + sizeof(buf) - ptr); if (r < 0) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Error appending extensions: %s", strerror(-r)); return false; } ptr += r; tls_tx_handshake(tls, TLS_SERVER_HELLO, buf, ptr - buf); return true; } static bool tls_cert_list_add_size(struct l_cert *cert, void *user_data) { size_t *total = user_data; size_t der_len; l_cert_get_der_data(cert, &der_len); *total += 3 + der_len; return false; } static bool tls_cert_list_append(struct l_cert *cert, void *user_data) { uint8_t **ptr = user_data; const uint8_t *der; size_t der_len; der = l_cert_get_der_data(cert, &der_len); *(*ptr)++ = der_len >> 16; *(*ptr)++ = der_len >> 8; *(*ptr)++ = der_len >> 0; memcpy(*ptr, der, der_len); *ptr += der_len; return false; } static bool tls_send_certificate(struct l_tls *tls) { uint8_t *buf, *ptr; size_t total; if (tls->server && !tls->cert) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, TLS_ALERT_BAD_CERT, "Certificate needed in server mode"); return false; } /* * TODO: check that the certificate is compatible with hash and * signature algorithms lists supplied to us in the Client Hello * extensions (if we're a 1.2+ server) or in the Certificate Request * (if we act as a 1.2+ client). * * - for the hash and signature_algorithms list, check all * certs in the cert chain. * * - also if !cipher_suite->key_xchg->key_exchange_msg, check that the * end entity certificate's key type matches and is usable with some * hash/signature pair. * * - on client check if any of the supplied DNs (if any) match * anything in our cert chain. */ total = 0; l_certchain_walk_from_leaf(tls->cert, tls_cert_list_add_size, &total); buf = l_malloc(128 + total); ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; /* Fill in the Certificate body */ *ptr++ = total >> 16; *ptr++ = total >> 8; *ptr++ = total >> 0; l_certchain_walk_from_leaf(tls->cert, tls_cert_list_append, &ptr); tls_tx_handshake(tls, TLS_CERTIFICATE, buf, ptr - buf); l_free(buf); if (tls->cert) tls->cert_sent = true; return true; } /* * Note: ClientCertificateType.rsa_sign value coincides with the * SignatureAlgorithm.rsa value but other values in those enum are * different so we don't mix them, can't extract them from * tls->pending.cipher_suite->signature. */ static uint8_t tls_cert_type_pref[] = { 1, /* RSA_sign */ }; static bool tls_send_certificate_request(struct l_tls *tls) { uint8_t *buf, *ptr, *dn_ptr; size_t len; const struct l_queue_entry *entry; unsigned int i; size_t dn_total = 0; for (entry = l_queue_get_entries(tls->ca_certs); entry; entry = entry->next) { struct l_cert *ca_cert = entry->data; size_t dn_size; if (l_cert_get_dn(ca_cert, &dn_size)) dn_total += 10 + dn_size; } len = 256 + L_ARRAY_SIZE(tls_cert_type_pref) + dn_total; buf = l_malloc(len); ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; /* Fill in the Certificate Request body */ *ptr++ = L_ARRAY_SIZE(tls_cert_type_pref); for (i = 0; i < L_ARRAY_SIZE(tls_cert_type_pref); i++) *ptr++ = tls_cert_type_pref[i]; if (tls->negotiated_version >= L_TLS_V12) { ssize_t ret = tls_write_signature_algorithms(tls, ptr, buf + len - ptr); if (ret < 0) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "tls_write_signature_algorithms: %s", strerror(-ret)); l_free(buf); return false; } ptr += ret; } dn_ptr = ptr; ptr += 2; /* Leave space for the total DN size */ for (entry = l_queue_get_entries(tls->ca_certs); entry; entry = entry->next) { struct l_cert *ca_cert = entry->data; size_t dn_size; const uint8_t *dn = l_cert_get_dn(ca_cert, &dn_size); uint8_t *cur_dn_ptr = ptr; if (!dn) continue; ptr += 2; /* Leave space for current DN size */ *ptr++ = ASN1_ID_SEQUENCE; /* DER outer SEQUENCE tag */ asn1_write_definite_length(&ptr, dn_size); /* length */ memcpy(ptr, dn, dn_size); /* value */ ptr += dn_size; l_put_be16(ptr - cur_dn_ptr - 2, cur_dn_ptr); } l_put_be16(ptr - dn_ptr - 2, dn_ptr); /* DistinguishedNames size */ tls_tx_handshake(tls, TLS_CERTIFICATE_REQUEST, buf, ptr - buf); l_free(buf); return true; } static void tls_send_server_hello_done(struct l_tls *tls) { uint8_t buf[32]; /* No body */ tls_tx_handshake(tls, TLS_SERVER_HELLO_DONE, buf, TLS_HANDSHAKE_HEADER_SIZE); } static void tls_update_key_block(struct l_tls *tls) { uint8_t seed[64]; int key_block_size = 0; if (tls->pending.cipher_suite->encryption) key_block_size += 2 * tls->pending.cipher_suite->encryption->key_length; if (tls->pending.cipher_suite->mac) key_block_size += 2 * tls->pending.cipher_suite->mac->mac_length; if (tls->pending.cipher_suite->encryption && tls->negotiated_version <= L_TLS_V10 && tls->pending.cipher_suite->encryption->cipher_type == TLS_CIPHER_BLOCK) key_block_size += 2 * tls->pending.cipher_suite->encryption->iv_length; if (tls->pending.cipher_suite->encryption) key_block_size += 2 * tls->pending.cipher_suite->encryption-> fixed_iv_length; /* Reverse order from the master secret seed */ memcpy(seed + 0, tls->pending.server_random, 32); memcpy(seed + 32, tls->pending.client_random, 32); tls_prf_get_bytes(tls, tls->pending.master_secret, 48, "key expansion", seed, 64, tls->pending.key_block, key_block_size); explicit_bzero(seed, 64); } void tls_generate_master_secret(struct l_tls *tls, const uint8_t *pre_master_secret, int pre_master_secret_len) { uint8_t seed[64]; memcpy(seed + 0, tls->pending.client_random, 32); memcpy(seed + 32, tls->pending.server_random, 32); tls_prf_get_bytes(tls, pre_master_secret, pre_master_secret_len, "master secret", seed, 64, tls->pending.master_secret, 48); explicit_bzero(seed, 64); /* Directly generate the key block while we're at it */ tls_update_key_block(tls); } static void tls_get_handshake_hash(struct l_tls *tls, enum handshake_hash_type type, uint8_t *out) { struct l_checksum *hash = l_checksum_clone(tls->handshake_hash[type]); if (!hash) return; l_checksum_get_digest(hash, out, l_checksum_digest_length( tls_handshake_hash_data[type].l_id)); l_checksum_free(hash); } static bool tls_get_handshake_hash_by_type(struct l_tls *tls, enum handshake_hash_type type, const uint8_t *data, size_t data_len, uint8_t *out, size_t *out_len) { if (!tls->handshake_hash[type]) return false; if (out_len) *out_len = l_checksum_digest_length( tls_handshake_hash_data[type].l_id); tls_get_handshake_hash(tls, type, out); return true; } static bool tls_send_certificate_verify(struct l_tls *tls) { uint8_t buf[2048]; int i; ssize_t sign_len; /* Fill in the Certificate Verify body */ sign_len = tls->pending.cipher_suite->signature->sign(tls, buf + TLS_HANDSHAKE_HEADER_SIZE, 2048 - TLS_HANDSHAKE_HEADER_SIZE, tls_get_handshake_hash_by_type, NULL, 0); if (sign_len < 0) return false; /* Stop maintaining handshake message hashes other than the PRF hash */ if (tls->negotiated_version >= L_TLS_V12) for (i = 0; i < __HANDSHAKE_HASH_COUNT; i++) if (&tls_handshake_hash_data[i] != tls->prf_hmac) tls_drop_handshake_hash(tls, i); tls_tx_handshake(tls, TLS_CERTIFICATE_VERIFY, buf, sign_len + TLS_HANDSHAKE_HEADER_SIZE); return true; } static void tls_send_change_cipher_spec(struct l_tls *tls) { uint8_t buf = 1; tls_tx_record(tls, TLS_CT_CHANGE_CIPHER_SPEC, &buf, 1); } size_t tls_verify_data_length(struct l_tls *tls, unsigned int index) { /* * RFC 5246, Section 7.4.9: * * In previous versions of TLS, the verify_data was always 12 octets * long. In the current version of TLS, it depends on the cipher * suite. Any cipher suite which does not explicitly specify * verify_data_length has a verify_data_length equal to 12. */ return maxsize(tls->cipher_suite[index]->verify_data_length, 12); } static bool tls_save_verify_data(struct l_tls *tls, bool txrx, const uint8_t *vd, size_t vdl) { uint8_t *buf; if (tls->server == txrx) { if (vdl > sizeof(tls->renegotiation_info.server_verify_data)) goto error; buf = tls->renegotiation_info.server_verify_data; } else { if (vdl > sizeof(tls->renegotiation_info.client_verify_data)) goto error; buf = tls->renegotiation_info.client_verify_data; } memcpy(buf, vd, vdl); return true; error: TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "tls->renegotiation_info.*verify too small for %s, " "report an ell bug", tls->cipher_suite[txrx]->name); return false; } static bool tls_send_finished(struct l_tls *tls) { uint8_t buf[512]; uint8_t *ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; uint8_t seed[HANDSHAKE_HASH_MAX_SIZE * 2]; size_t vdl = tls_verify_data_length(tls, 1); size_t seed_len; if (tls->negotiated_version >= L_TLS_V12) { /* Same hash type as that used for the PRF (usually SHA256) */ tls_get_handshake_hash(tls, tls->prf_hmac->type, seed); seed_len = l_checksum_digest_length(tls->prf_hmac->l_id); } else { tls_get_handshake_hash(tls, HANDSHAKE_HASH_MD5, seed + 0); tls_get_handshake_hash(tls, HANDSHAKE_HASH_SHA1, seed + 16); seed_len = 36; } tls_prf_get_bytes(tls, tls->pending.master_secret, 48, tls->server ? "server finished" : "client finished", seed, seed_len, ptr, vdl); if (!tls_save_verify_data(tls, 1, ptr, vdl)) return false; ptr += vdl; tls_tx_handshake(tls, TLS_FINISHED, buf, ptr - buf); return true; } static bool tls_verify_finished(struct l_tls *tls, const uint8_t *received, size_t len) { size_t vdl = tls_verify_data_length(tls, 0); uint8_t expected[vdl]; uint8_t *seed; size_t seed_len; if (len != vdl) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "TLS_FINISHED length not %zu", vdl); return false; } if (tls->negotiated_version >= L_TLS_V12) { enum handshake_hash_type hash = tls->prf_hmac->type; seed = tls->prev_digest[hash]; seed_len = l_checksum_digest_length(tls->prf_hmac->l_id); } else { seed = alloca(36); memcpy(seed + 0, tls->prev_digest[HANDSHAKE_HASH_MD5], 16); memcpy(seed + 16, tls->prev_digest[HANDSHAKE_HASH_SHA1], 20); seed_len = 36; } tls_prf_get_bytes(tls, tls->pending.master_secret, 48, tls->server ? "client finished" : "server finished", seed, seed_len, expected, vdl); if (memcmp(received, expected, len)) { TLS_DISCONNECT(TLS_ALERT_DECRYPT_ERROR, 0, "TLS_FINISHED contents don't match"); return false; } if (!tls_save_verify_data(tls, 0, received, vdl)) return false; return true; } static bool tls_ptr_match(const void *a, const void *b) { return a == b; } static bool tls_handle_hello_extensions(struct l_tls *tls, const uint8_t *buf, size_t len, struct l_queue *seen) { unsigned int i; const struct tls_hello_extension *extension; bool client_hello = tls->server; uint16_t extensions_size; if (!len) return true; if (len < 2 || len > 2 + 65535) goto decode_error; extensions_size = l_get_be16(buf); len -= 2; buf += 2; if (len != extensions_size) goto decode_error; while (len) { uint16_t ext_id; size_t ext_len; bool (*handler)(struct l_tls *tls, const uint8_t *buf, size_t len); if (len < 4) goto decode_error; ext_id = l_get_be16(buf + 0); ext_len = l_get_be16(buf + 2); buf += 4; len -= 4; if (ext_len > len) goto decode_error; /* * RFC 5246, Section 7.4.1.4: "There MUST NOT be more than * one extension of the same type." */ if (l_queue_find(seen, tls_ptr_match, L_UINT_TO_PTR(ext_id))) { TLS_DEBUG("Duplicate extension %u", ext_id); goto decode_error; } l_queue_push_tail(seen, L_UINT_TO_PTR(ext_id)); extension = NULL; for (i = 0; tls_extensions[i].name; i++) if (tls_extensions[i].id == ext_id) { extension = &tls_extensions[i]; break; } if (!extension) goto next; handler = client_hello ? extension->client_handle : extension->server_handle; /* * RFC 5246, Section 7.4.1.4: "If a client receives an * extension type in ServerHello that it did not request in * the associated ClientHello, it MUST abort the handshake * with an unsupported_extension fatal alert." * There are however servers that include an unsolicited * Supported Point Format extension where the handshake * still completes fine if the extension is ignored so we * do this instead. */ if (!client_hello && !handler) { TLS_DEBUG("non-fatal: %s extension not expected in " "a ServerHello", extension->name); goto next; } if (!handler(tls, buf, ext_len)) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "Hello %s extension parse error", extension->name); return false; } next: buf += ext_len; len -= ext_len; } /* * Trigger any actions needed when an extension is missing and its * handler has not been called yet. */ for (i = 0; tls_extensions[i].name; i++) { bool (*handler)(struct l_tls *tls); extension = &tls_extensions[i]; handler = client_hello ? extension->client_handle_absent : extension->server_handle_absent; if (!handler) continue; if (l_queue_find(seen, tls_ptr_match, L_UINT_TO_PTR(extension->id))) continue; if (!handler(tls)) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "Hello %s extension missing", extension->name); return false; } } return true; decode_error: TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "Hello extensions decode error"); return false; } static void tls_server_resume_error(struct l_tls *tls) { /* * When Client Hello parameters don't match the parameters of the * cached session that was requested by the client, we'll probably * start and cache a new session. Even though RFC 5246 doesn't * specifically mandate that the requested session be forgotten * (there's no fatal Alert in that case), we overwrite the old * session's entry in the cache with the new session's data to * avoid keeping many sessions related to one client in the cache. * In theory this allows an attacker to connect as a client and * invalidate a legitimate client's session entry in our cache, * DoSing the session resumption mechanism so that clients have * to go through the full handshake. In practice there are many * ways for an attacker to do that even without this. * * Our client mode only caches one last session anyway, other * implementations may work that way too. */ memcpy(tls->session_id_replaced, tls->session_id, tls->session_id_size); tls->session_id_size_replaced = tls->session_id_size; tls->session_id_size = 0; tls->session_id_new = false; l_free(l_steal_ptr(tls->session_peer_identity)); } /* RFC 5746 */ static const uint8_t tls_empty_renegotiation_info_scsv[2] = { 0x00, 0xff }; static const uint16_t tls_renegotiation_info_id = 0xff01; static void tls_handle_client_hello(struct l_tls *tls, const uint8_t *buf, size_t len) { uint16_t cipher_suites_size; uint8_t session_id_size, compression_methods_size; const uint8_t *cipher_suites; const uint8_t *compression_methods; int i; struct l_queue *extensions_offered = NULL; enum l_tls_alert_desc alert_desc = TLS_ALERT_HANDSHAKE_FAIL; bool resuming = false; _auto_(l_free) char *session_id_str = NULL; struct tls_cipher_suite *backup_suite = NULL; struct tls_compression_method *backup_cm = NULL; /* Do we have enough for ProtocolVersion + Random + SessionID size? */ if (len < 2 + 32 + 1) goto decode_error; memcpy(tls->pending.client_random, buf + 2, 32); session_id_size = buf[34]; len -= 35; if (unlikely(session_id_size > 32)) goto decode_error; /* * Do we have enough to hold the actual session ID + 2 byte field for * cipher_suite len + minimum of a single cipher suite identifier */ if (len < (size_t) session_id_size + 4) goto decode_error; len -= session_id_size + 2; cipher_suites_size = l_get_be16(buf + 35 + session_id_size); cipher_suites = buf + 37 + session_id_size; /* * Check that size is not odd, more than 0 and we have enough * data in the packet for cipher_suites_size + 2 bytes for * compression_methods_size + a single compression method */ if (len < (size_t) cipher_suites_size + 2 || (cipher_suites_size & 1) || cipher_suites_size == 0) goto decode_error; len -= cipher_suites_size + 1; compression_methods_size = cipher_suites[cipher_suites_size]; compression_methods = cipher_suites + cipher_suites_size + 1; if (len < (size_t) compression_methods_size || compression_methods_size == 0) goto decode_error; len -= compression_methods_size; if (session_id_size && tls->session_settings && tls_load_cached_server_session(tls, buf + 35, session_id_size)) { /* * Attempt a session resumption but note later checks may * spoil this. */ resuming = true; session_id_str = l_util_hexstring(tls->session_id, tls->session_id_size); } if (tls->pending_destroy) return; extensions_offered = l_queue_new(); if (!tls_handle_hello_extensions(tls, compression_methods + compression_methods_size, len, extensions_offered)) goto cleanup; /* Save client_version for Premaster Secret verification */ tls->client_version = l_get_be16(buf); if (tls->client_version < tls->min_version) { TLS_DISCONNECT(TLS_ALERT_PROTOCOL_VERSION, 0, "Client version too low: %02x", tls->client_version); goto cleanup; } tls->negotiated_version = tls->client_version > tls->max_version ? tls->max_version : tls->client_version; /* Stop maintaining handshake message hashes other than MD1 and SHA. */ if (tls->negotiated_version < L_TLS_V12) for (i = 0; i < __HANDSHAKE_HASH_COUNT; i++) if (i != HANDSHAKE_HASH_SHA1 && i != HANDSHAKE_HASH_MD5) tls_drop_handshake_hash(tls, i); TLS_DEBUG("Negotiated TLS " TLS_VER_FMT, TLS_VER_ARGS(tls->negotiated_version)); if (!tls->cipher_suite_pref_list) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "No usable cipher suites"); goto cleanup; } if (!tls->renegotiation_info.secure_renegotiation || tls->ready) { for (i = 0; i < cipher_suites_size; i += 2) if (l_get_be16(cipher_suites + i) == l_get_be16( tls_empty_renegotiation_info_scsv)) break; if (i < cipher_suites_size) { if (unlikely(tls->ready)) { TLS_DISCONNECT(TLS_ALERT_ILLEGAL_PARAM, 0, "Empty renegotiation_info in " "renegotiation Client Hello"); goto cleanup; } /* * RFC 5746 Section 3.6, act as if we had received an * empty renegotiation_info extension. */ tls->renegotiation_info.secure_renegotiation = true; l_queue_push_tail(extensions_offered, L_UINT_TO_PTR( tls_renegotiation_info_id)); } } /* Select a cipher suite according to client's preference list */ while (cipher_suites_size) { struct tls_cipher_suite *suite = tls_find_cipher_suite(cipher_suites); struct tls_cipher_suite **iter; const char *error; for (iter = tls->cipher_suite_pref_list; *iter; iter++) if (*iter == suite) break; if (!suite) TLS_DEBUG("non-fatal: Cipher suite %04x unknown", l_get_be16(cipher_suites)); else if (!tls_cipher_suite_is_compatible(tls, suite, &error)) TLS_DEBUG("non-fatal: %s", error); else if (!*iter) { /* * We have at least one matching compatible suite but * it is not allowed in this security profile. If the * handshake ends up failing then we blame the security * profile. */ alert_desc = TLS_ALERT_INSUFFICIENT_SECURITY; TLS_DEBUG("non-fatal: Cipher suite %s disallowed " "by config", suite->name); } else if (resuming && memcmp(tls->session_cipher_suite_id, suite->id, 2)) { /* * For now skip this cipher suite because we're trying * to find the one from the cached session state. But * keep it as a backup in case we end up starting a new * session. */ if (!backup_suite) backup_suite = suite; } else { tls->pending.cipher_suite = suite; break; } cipher_suites += 2; cipher_suites_size -= 2; } if (unlikely(!cipher_suites_size && backup_suite)) { TLS_DEBUG("Cached session %s's cipher suite %04x " "unavailable, will start a new session", session_id_str, l_get_be16(tls->session_cipher_suite_id)); tls->pending.cipher_suite = backup_suite; resuming = false; tls_server_resume_error(tls); } else if (unlikely(!cipher_suites_size)) { TLS_DISCONNECT(alert_desc, 0, "No common cipher suites matching negotiated " "TLS version and our certificate's key type"); goto cleanup; } if (!tls_set_prf_hmac(tls)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Error selecting the PRF HMAC"); goto cleanup; } /* Select a compression method */ /* CompressionMethod.null must be present in the vector */ while (compression_methods_size) { struct tls_compression_method *cm = tls_find_compression_method(*compression_methods); if (!cm) TLS_DEBUG("non-fatal: Compression %02x unknown", *compression_methods); else if (resuming && *compression_methods != tls->session_compression_method_id) { /* * For now skip this compression method because we're * trying to find the one from the cached session state. * But keep it as a backup in case we end up starting * a new * session. */ if (!backup_cm) backup_cm = cm; } else { tls->pending.compression_method = cm; break; } compression_methods++; compression_methods_size--; } if (unlikely(!compression_methods_size && backup_cm)) { TLS_DEBUG("Cached session %s's compression method %02x " "unavailable, will start a new session", session_id_str, tls->session_compression_method_id); tls->pending.compression_method = backup_cm; if (backup_suite) tls->pending.cipher_suite = backup_suite; resuming = false; tls_server_resume_error(tls); } else if (unlikely(!compression_methods_size)) { TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, "No common compression methods"); goto cleanup; } if (resuming) TLS_DEBUG("Negotiated resumption of cached session %s", session_id_str); TLS_DEBUG("Negotiated %s", tls->pending.cipher_suite->name); TLS_DEBUG("Negotiated %s", tls->pending.compression_method->name); if (!resuming && tls->session_settings) { tls->session_id_new = true; tls->session_id_size = 32; l_getrandom(tls->session_id, 32); } if (!tls_send_server_hello(tls, extensions_offered)) goto cleanup; l_queue_destroy(extensions_offered, NULL); if (resuming) { const char *error; tls_update_key_block(tls); tls_send_change_cipher_spec(tls); if (!tls_change_cipher_spec(tls, 1, &error)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "change_cipher_spec: %s", error); return; } if (!tls_send_finished(tls)) return; TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC); return; } if (tls->pending.cipher_suite->signature && tls->cert) if (!tls_send_certificate(tls)) return; if (tls->pending.cipher_suite->key_xchg->send_server_key_exchange) if (!tls->pending.cipher_suite->key_xchg-> send_server_key_exchange(tls)) return; /* TODO: don't bother if configured to not authenticate client */ if (tls->pending.cipher_suite->signature && tls->ca_certs) if (!tls_send_certificate_request(tls)) return; tls_send_server_hello_done(tls); if (tls->pending.cipher_suite->signature && tls->ca_certs) TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CERTIFICATE); else TLS_SET_STATE(TLS_HANDSHAKE_WAIT_KEY_EXCHANGE); return; decode_error: TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "ClientHello decode error"); cleanup: l_queue_destroy(extensions_offered, NULL); } static void tls_handle_server_hello(struct l_tls *tls, const uint8_t *buf, size_t len) { uint8_t session_id_size, cipher_suite_id[2], compression_method_id; const char *error; struct tls_cipher_suite **iter; int i; struct l_queue *extensions_seen; bool result; uint16_t version; bool resuming = false; /* Do we have enough for ProtocolVersion + Random + SessionID len ? */ if (len < 2 + 32 + 1) goto decode_error; version = l_get_be16(buf); memcpy(tls->pending.server_random, buf + 2, 32); session_id_size = buf[34]; len -= 35; /* Do we have enough for SessionID + CipherSuite ID + Compression ID */ if (len < (size_t) session_id_size + 2 + 1) goto decode_error; cipher_suite_id[0] = buf[35 + session_id_size + 0]; cipher_suite_id[1] = buf[35 + session_id_size + 1]; compression_method_id = buf[35 + session_id_size + 2]; len -= session_id_size + 2 + 1; if (session_id_size > 32) goto decode_error; if (tls->session_id_size) { _auto_(l_free) char *session_id_str = l_util_hexstring(tls->session_id, tls->session_id_size); if (session_id_size == tls->session_id_size && !memcmp(buf + 35, tls->session_id, session_id_size)) { TLS_DEBUG("Negotiated resumption of cached session %s", session_id_str); resuming = true; } else { TLS_DEBUG("Server decided not to resume cached session " "%s, sent %s session ID", session_id_str, session_id_size ? "a new" : "no"); tls->session_id_size = 0; } } if (session_id_size && !resuming && tls->session_settings) { tls->session_id_new = true; tls->session_id_size = session_id_size; memcpy(tls->session_id, buf + 35, session_id_size); } extensions_seen = l_queue_new(); result = tls_handle_hello_extensions(tls, buf + 38 + session_id_size, len, extensions_seen); l_queue_destroy(extensions_seen, NULL); if (!result) return; if (version < tls->min_version || version > tls->max_version) { TLS_DISCONNECT(version < tls->min_version ? TLS_ALERT_PROTOCOL_VERSION : TLS_ALERT_ILLEGAL_PARAM, 0, "Unsupported version %02x", version); return; } tls->negotiated_version = version; /* Stop maintaining handshake message hashes other than MD1 and SHA. */ if (tls->negotiated_version < L_TLS_V12) for (i = 0; i < __HANDSHAKE_HASH_COUNT; i++) if (i != HANDSHAKE_HASH_SHA1 && i != HANDSHAKE_HASH_MD5) tls_drop_handshake_hash(tls, i); TLS_DEBUG("Negotiated TLS " TLS_VER_FMT, TLS_VER_ARGS(tls->negotiated_version)); /* Set the new cipher suite and compression method structs */ tls->pending.cipher_suite = tls_find_cipher_suite(cipher_suite_id); if (!tls->pending.cipher_suite) { TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, "Unknown cipher suite %04x", l_get_be16(cipher_suite_id)); return; } for (iter = tls->cipher_suite_pref_list; *iter; iter++) if (*iter == tls->pending.cipher_suite) break; if (!*iter) { TLS_DISCONNECT(TLS_ALERT_INSUFFICIENT_SECURITY, 0, "Selected cipher suite %s disallowed by config", tls->pending.cipher_suite->name); return; } if (!tls_cipher_suite_is_compatible(tls, tls->pending.cipher_suite, &error)) { TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, "Selected cipher suite not compatible: %s", error); return; } if (!tls_set_prf_hmac(tls)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Error selecting the PRF HMAC"); return; } TLS_DEBUG("Negotiated %s", tls->pending.cipher_suite->name); tls->pending.compression_method = tls_find_compression_method(compression_method_id); if (!tls->pending.compression_method) { TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, "Unknown compression method %i", compression_method_id); return; } TLS_DEBUG("Negotiated %s", tls->pending.compression_method->name); if (resuming) { /* * Now that we've validated the Server Hello parameters and * know that they're supported by this version of ell and * consistent with the current configuration, ensure that * they're identical with the ones in the cached session * being resumed. This serves as a sanity check for * rare situations like a corrupt session cache file or * a file written by a newer ell version. */ if (tls->negotiated_version != tls->client_version || memcmp(cipher_suite_id, tls->session_cipher_suite_id, 2) || compression_method_id != tls->session_compression_method_id) { TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, "Session parameters don't match"); return; } tls_update_key_block(tls); TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC); } else if (tls->pending.cipher_suite->signature) TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CERTIFICATE); else TLS_SET_STATE(TLS_HANDSHAKE_WAIT_KEY_EXCHANGE); return; decode_error: TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "ServerHello decode error"); } static void tls_handle_certificate(struct l_tls *tls, const uint8_t *buf, size_t len) { size_t total; _auto_(l_certchain_free) struct l_certchain *certchain = NULL; struct l_cert *leaf; size_t der_len; const uint8_t *der; bool dummy; const char *error_str; char *subject_str; if (len < 3) goto decode_error; /* Length checks */ total = *buf++ << 16; total |= *buf++ << 8; total |= *buf++ << 0; if (total + 3 != len) goto decode_error; if (tls_parse_certificate_list(buf, total, &certchain) < 0) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "Error decoding peer certificate chain"); return; } /* * "Note that a client MAY send no certificates if it does not have any * appropriate certificate to send in response to the server's * authentication request." -- for now we unconditionally accept * an empty certificate chain from the client. Later on we need to * make this configurable, if we don't want to authenticate the * client then also don't bother sending a Certificate Request. */ if (!certchain) { if (!tls->server) { TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, "Server sent no certificate chain"); return; } TLS_SET_STATE(TLS_HANDSHAKE_WAIT_KEY_EXCHANGE); return; } if (tls->cert_dump_path) { int r = pem_write_certificate_chain(certchain, tls->cert_dump_path); if (r < 0) TLS_DEBUG("Error %i (%s) writing the peer certchain " "to %s", -r, strerror(-r), tls->cert_dump_path); else TLS_DEBUG("Peer certchain written to %s", tls->cert_dump_path); } /* * Validate the certificate chain's consistency and validate it * against our CAs if we have any. */ if (!l_certchain_verify(certchain, tls->ca_certs, &error_str)) { if (tls->ca_certs) { TLS_DISCONNECT(TLS_ALERT_BAD_CERT, 0, "Peer certchain verification failed " "consistency check%s: %s", tls->ca_certs ? " or against local CA certs" : "", error_str); return; } /* * Until the mainstream kernel can handle the occasionally * used certificates without the AKID extension (both root, * which is legal, and non-root, which is iffy but still * happens) don't fail on peer certificate chain verification * failure when CA certificates were not provided. Knowing * that the chain is self-consistent alone doesn't * authenticate the peer in any way. Only warn when it looks * like the chain is bad but parses and we can get the peer * public key from it below. */ TLS_DEBUG("Peer certchain verification failed (%s.) No local " "CA certs provided so proceeding anyway. This " "failure can signal a security issue or a " "known kernel problem with some certificates.", error_str); } /* * RFC5246 7.4.2: * "The end entity certificate's public key (and associated * restrictions) MUST be compatible with the selected key exchange * algorithm." */ leaf = l_certchain_get_leaf(certchain); if (!tls->pending.cipher_suite->signature-> validate_cert_key_type(leaf)) { TLS_DISCONNECT(TLS_ALERT_UNSUPPORTED_CERT, 0, "Peer certificate key type incompatible with " "pending cipher suite %s", tls->pending.cipher_suite->name); return; } if (tls->subject_mask && !tls_cert_domains_match_mask(leaf, tls->subject_mask, &subject_str)) { char *mask = l_strjoinv(tls->subject_mask, '|'); TLS_DISCONNECT(TLS_ALERT_BAD_CERT, 0, "Peer certificate's subject domain " "doesn't match mask %s: %s", mask, subject_str); l_free(mask); l_free(subject_str); return; } /* Save the end-entity certificate and free the chain */ der = l_cert_get_der_data(leaf, &der_len); tls->peer_cert = l_cert_new_from_der(der, der_len); tls->peer_pubkey = l_cert_get_pubkey(tls->peer_cert); if (!tls->peer_pubkey) { TLS_DISCONNECT(TLS_ALERT_UNSUPPORTED_CERT, 0, "Error loading peer public key to kernel"); return; } switch (l_cert_get_pubkey_type(tls->peer_cert)) { case L_CERT_KEY_RSA: if (!l_key_get_info(tls->peer_pubkey, L_KEY_RSA_PKCS1_V1_5, L_CHECKSUM_NONE, &tls->peer_pubkey_size, &dummy)) goto pubkey_unsupported; break; case L_CERT_KEY_ECC: if (!l_key_get_info(tls->peer_pubkey, L_KEY_ECDSA_X962, L_CHECKSUM_SHA1, &tls->peer_pubkey_size, &dummy)) goto pubkey_unsupported; break; case L_CERT_KEY_UNKNOWN: TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Unknown public key type"); return; } tls->peer_pubkey_size /= 8; if (tls->server || tls->pending.cipher_suite->key_xchg-> handle_server_key_exchange) TLS_SET_STATE(TLS_HANDSHAKE_WAIT_KEY_EXCHANGE); else TLS_SET_STATE(TLS_HANDSHAKE_WAIT_HELLO_DONE); return; pubkey_unsupported: TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Can't l_key_get_info for peer public key"); return; decode_error: TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "TLS_CERTIFICATE decode error"); } static void tls_handle_certificate_request(struct l_tls *tls, const uint8_t *buf, size_t len) { unsigned int cert_type_len, dn_len, i; tls->cert_requested = 1; cert_type_len = *buf++; if (len < 1 + cert_type_len + 2) goto decode_error; for (i = 0; i < sizeof(tls_cert_type_pref); i++) if (memchr(buf, tls_cert_type_pref[i], cert_type_len)) break; if (i == sizeof(tls_cert_type_pref)) { TLS_DISCONNECT(TLS_ALERT_UNSUPPORTED_CERT, 0, "Requested certificate types not supported"); return; } buf += cert_type_len; len -= 1 + cert_type_len; /* * TODO: parse and save certificate_types, * supported_signature_algorithms and certificate_authorities * lists for use in tls_send_certificate. */ if (tls->negotiated_version >= L_TLS_V12) { enum handshake_hash_type hash; ssize_t ret = tls_parse_signature_algorithms(tls, buf, len); if (ret == -ENOTSUP) { TLS_DISCONNECT(TLS_ALERT_UNSUPPORTED_CERT, 0, "No supported signature hash type"); return; } if (ret < 0) goto decode_error; len -= ret; buf += ret; /* * We can now safely stop maintaining handshake message * hashes other than the PRF hash and the one selected for * signing. */ for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) if (&tls_handshake_hash_data[hash] != tls->prf_hmac && hash != tls->signature_hash) tls_drop_handshake_hash(tls, hash); } dn_len = l_get_be16(buf); if (2 + dn_len != len) goto decode_error; return; decode_error: TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "CertificateRequest decode error"); } static void tls_handle_server_hello_done(struct l_tls *tls, const uint8_t *buf, size_t len) { const char *error; if (len) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "ServerHello not empty"); return; } if (tls->cert_requested) if (!tls_send_certificate(tls)) return; if (!tls->pending.cipher_suite->key_xchg->send_client_key_exchange(tls)) return; if (tls->cert_sent) if (!tls_send_certificate_verify(tls)) return; tls_send_change_cipher_spec(tls); if (!tls_change_cipher_spec(tls, 1, &error)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "change_cipher_spec: %s", error); return; } if (!tls_send_finished(tls)) return; TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC); } static bool tls_get_prev_digest_by_type(struct l_tls *tls, enum handshake_hash_type type, const uint8_t *data, size_t data_len, uint8_t *out, size_t *out_len) { size_t len; if (!tls->handshake_hash[type]) return false; len = l_checksum_digest_length(tls_handshake_hash_data[type].l_id); memcpy(out, tls->prev_digest[type], len); if (out_len) *out_len = len; return 0; } static void tls_handle_certificate_verify(struct l_tls *tls, const uint8_t *buf, size_t len) { int i; if (!tls->pending.cipher_suite->signature->verify(tls, buf, len, tls_get_prev_digest_by_type, NULL, 0)) return; /* Stop maintaining handshake message hashes other than the PRF hash */ if (tls->negotiated_version >= L_TLS_V12) for (i = 0; i < __HANDSHAKE_HASH_COUNT; i++) if (&tls_handshake_hash_data[i] != tls->prf_hmac) tls_drop_handshake_hash(tls, i); /* * The client's certificate is now verified based on the following * logic: * - If we received an (expected) Certificate Verify, we must have * sent a Certificate Request. * - If we sent a Certificate Request that's because * tls->ca_certs is non-NULL. * - If tls->ca_certs is non-NULL then tls_handle_certificate * will have checked the whole certificate chain to be valid and * additionally trusted by our CAs if known. * - Additionally cipher_suite->signature->verify has just confirmed * that the peer owns the end-entity certificate because it was * able to sign the contents of the handshake messages and that * signature could be verified with the public key from that * certificate. */ tls->peer_authenticated = true; TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC); } struct dn_element_info { const char *str; const struct asn1_oid oid; }; static const struct dn_element_info dn_elements[] = { { "CN", { 3, { 0x55, 0x04, 0x03 } } }, { "SN", { 3, { 0x55, 0x04, 0x04 } } }, { "serialNumber", { 3, { 0x55, 0x04, 0x05 } } }, { "C", { 3, { 0x55, 0x04, 0x06 } } }, { "ST", { 3, { 0x55, 0x04, 0x07 } } }, { "L", { 3, { 0x55, 0x04, 0x08 } } }, { "street", { 3, { 0x55, 0x04, 0x09 } } }, { "O", { 3, { 0x55, 0x04, 0x0a } } }, { "OU", { 3, { 0x55, 0x04, 0x0b } } }, { "title", { 3, { 0x55, 0x04, 0x0c } } }, { "telephoneNumber", { 3, { 0x55, 0x04, 0x14 } } }, { "givenName", { 3, { 0x55, 0x04, 0x2a } } }, { "initials", { 3, { 0x55, 0x04, 0x2b } } }, { "emailAddress", { 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01 } } }, { "domainComponent", { 10, { 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19 } } }, {} }; static void tls_str_escape_append(struct l_string *out, char *str, size_t len) { while (len--) { switch (*str) { case '\\': case '/': case '=': l_string_append_c(out, '\\'); l_string_append_c(out, *str); break; default: l_string_append_c(out, *str); break; } str++; } } static char *tls_get_peer_identity_str(struct l_cert *cert) { const uint8_t *dn, *end; size_t dn_size; struct l_string *id_str; if (!cert) return NULL; dn = l_cert_get_dn(cert, &dn_size); if (!dn) return NULL; id_str = l_string_new(200); end = dn + dn_size; while (dn < end) { const uint8_t *set, *seq, *oid, *name; uint8_t tag; size_t len, oid_len, name_len; const struct dn_element_info *info; set = asn1_der_find_elem(dn, end - dn, 0, &tag, &len); if (!set || tag != ASN1_ID_SET) goto error; dn = set + len; seq = asn1_der_find_elem(set, len, 0, &tag, &len); if (!seq || tag != ASN1_ID_SEQUENCE) goto error; oid = asn1_der_find_elem(seq, len, 0, &tag, &oid_len); if (!oid || tag != ASN1_ID_OID) goto error; name = asn1_der_find_elem(seq, len, 1, &tag, &name_len); if (!name || (tag != ASN1_ID_PRINTABLESTRING && tag != ASN1_ID_UTF8STRING && tag != ASN1_ID_IA5STRING)) continue; for (info = dn_elements; info->str; info++) if (asn1_oid_eq(&info->oid, oid_len, oid)) break; if (!info->str) continue; l_string_append_c(id_str, '/'); l_string_append(id_str, info->str); l_string_append_c(id_str, '='); tls_str_escape_append(id_str, (char *) name, name_len); } return l_string_unwrap(id_str); error: l_string_free(id_str); return NULL; } static void tls_finished(struct l_tls *tls) { _auto_(l_free) char *peer_cert_identity = NULL; char *peer_identity = NULL; uint64_t peer_cert_expiry; bool resuming = tls->session_id_size && !tls->session_id_new; bool session_update = false; bool renegotiation = tls->ready; if (tls->peer_authenticated && !resuming) { peer_cert_identity = tls_get_peer_identity_str(tls->peer_cert); if (!peer_cert_identity) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "tls_get_peer_identity_str failed"); return; } peer_identity = peer_cert_identity; if (tls->session_id_new && !l_cert_get_valid_times(tls->peer_cert, NULL, &peer_cert_expiry)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "l_cert_get_valid_times failed"); return; } } else if (tls->peer_authenticated && resuming) peer_identity = tls->session_peer_identity; if (tls->session_settings && tls->session_id_new) { _auto_(l_free) char *session_id_str = l_util_hexstring(tls->session_id, tls->session_id_size); uint64_t expiry = tls->session_lifetime ? time_realtime_now() + tls->session_lifetime : 0; const char *group_name = tls_get_cache_group_name(tls, tls->session_id, tls->session_id_size); if (tls->peer_authenticated && (!expiry || peer_cert_expiry < expiry)) expiry = peer_cert_expiry; if (!tls->server) l_settings_set_bytes(tls->session_settings, group_name, "SessionID", tls->session_id, tls->session_id_size); l_settings_set_bytes(tls->session_settings, group_name, "SessionMasterSecret", tls->pending.master_secret, 48); l_settings_set_int(tls->session_settings, group_name, "SessionVersion", tls->negotiated_version); l_settings_set_bytes(tls->session_settings, group_name, "SessionCipherSuite", tls->pending.cipher_suite->id, 2); l_settings_set_uint(tls->session_settings, group_name, "SessionCompressionMethod", tls->pending.compression_method->id); if (expiry) l_settings_set_uint64(tls->session_settings, group_name, "SessionExpiryTime", expiry); else /* We may be overwriting an older session's data */ l_settings_remove_key(tls->session_settings, group_name, "SessionExpiryTime"); if (tls->peer_authenticated) l_settings_set_string(tls->session_settings, group_name, "SessionPeerIdentity", peer_identity); else /* We may be overwriting an older session's data */ l_settings_remove_key(tls->session_settings, group_name, "SessionPeerIdentity"); TLS_DEBUG("Saving new session %s to cache", session_id_str); session_update = true; if (tls->session_id_size_replaced) { tls_forget_cached_session(tls, NULL, tls->session_id_replaced, tls->session_id_size_replaced, false); tls->session_id_size_replaced = 0; } } /* Free up the resources used in the handshake */ tls_reset_handshake(tls); TLS_SET_STATE(TLS_HANDSHAKE_DONE); tls->ready = true; tls->session_resumed = resuming; if (session_update && tls->session_update_cb) { tls->in_callback = true; tls->session_update_cb(tls->session_update_user_data); tls->in_callback = false; if (tls->pending_destroy) return; } if (!renegotiation) { tls->in_callback = true; tls->ready_handle(peer_identity, tls->user_data); tls->in_callback = false; } tls_cleanup_handshake(tls); } static void tls_handle_handshake(struct l_tls *tls, int type, const uint8_t *buf, size_t len) { bool resuming; TLS_DEBUG("Handling a %s of %zi bytes", tls_handshake_type_to_str(type), len); switch (type) { case TLS_HELLO_REQUEST: if (tls->server) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Message invalid in server mode"); break; } if (len != 0) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "HelloRequest not empty"); break; } /* * May be sent by the server at any time but "SHOULD be ignored * by the client if it arrives in the middle of a handshake" * and "MAY be ignored by the client if it does not wish to * renegotiate a session". */ if (tls->state != TLS_HANDSHAKE_DONE) { TLS_DEBUG("Message invalid in state %s", tls_handshake_state_to_str(tls->state)); break; } if (!tls_send_client_hello(tls)) break; TLS_SET_STATE(TLS_HANDSHAKE_WAIT_HELLO); break; case TLS_CLIENT_HELLO: if (!tls->server) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Message invalid in client mode"); break; } if (tls->state != TLS_HANDSHAKE_WAIT_HELLO && tls->state != TLS_HANDSHAKE_DONE) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Message invalid in state %s", tls_handshake_state_to_str(tls->state)); break; } tls_handle_client_hello(tls, buf, len); break; case TLS_SERVER_HELLO: if (tls->server) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Message invalid in server mode"); break; } if (tls->state != TLS_HANDSHAKE_WAIT_HELLO) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Message invalid in state %s", tls_handshake_state_to_str(tls->state)); break; } tls_handle_server_hello(tls, buf, len); break; case TLS_CERTIFICATE: if (tls->state != TLS_HANDSHAKE_WAIT_CERTIFICATE) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Message invalid in state %s", tls_handshake_state_to_str(tls->state)); break; } tls_handle_certificate(tls, buf, len); break; case TLS_SERVER_KEY_EXCHANGE: if (tls->server) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Message invalid in server mode"); break; } if (tls->state != TLS_HANDSHAKE_WAIT_KEY_EXCHANGE) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Message invalid in state %s", tls_handshake_state_to_str(tls->state)); break; } TLS_SET_STATE(TLS_HANDSHAKE_WAIT_HELLO_DONE); tls->pending.cipher_suite->key_xchg->handle_server_key_exchange( tls, buf, len); break; case TLS_CERTIFICATE_REQUEST: if (tls->server) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Message invalid in server mode"); break; } /* * Server sends this optionally so in the WAIT_HELLO_DONE * state we accept either this or a Server Hello Done (below). */ if (tls->state != TLS_HANDSHAKE_WAIT_HELLO_DONE || tls->cert_requested || !tls->pending.cipher_suite->signature) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Message invalid in current state " "or certificate check not supported " "in pending cipher suite"); break; } tls_handle_certificate_request(tls, buf, len); break; case TLS_SERVER_HELLO_DONE: if (tls->state != TLS_HANDSHAKE_WAIT_HELLO_DONE) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Message invalid in state %s", tls_handshake_state_to_str(tls->state)); break; } tls_handle_server_hello_done(tls, buf, len); break; case TLS_CERTIFICATE_VERIFY: if (tls->state != TLS_HANDSHAKE_WAIT_CERTIFICATE_VERIFY) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Message invalid in state %s", tls_handshake_state_to_str(tls->state)); break; } tls_handle_certificate_verify(tls, buf, len); break; case TLS_CLIENT_KEY_EXCHANGE: if (!tls->server) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Message invalid in client mode"); break; } if (tls->state != TLS_HANDSHAKE_WAIT_KEY_EXCHANGE) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Message invalid in state %s", tls_handshake_state_to_str(tls->state)); break; } /* * If we accepted a client Certificate message with a * certificate that has signing capability (TODO: check * usage bitmask), Certificate Verify is received next. It * sounds as if this is mandatory for the client although * this isn't 100% clear. */ if (tls->peer_pubkey) TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CERTIFICATE_VERIFY); else TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC); tls->pending.cipher_suite->key_xchg->handle_client_key_exchange( tls, buf, len); break; case TLS_FINISHED: if (tls->state != TLS_HANDSHAKE_WAIT_FINISHED) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Message invalid in state %s", tls_handshake_state_to_str(tls->state)); break; } if (!tls_verify_finished(tls, buf, len)) break; resuming = tls->session_id_size && !tls->session_id_new; if ((tls->server && !resuming) || (!tls->server && resuming)) { const char *error; tls_send_change_cipher_spec(tls); if (!tls_change_cipher_spec(tls, 1, &error)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "change_cipher_spec: %s", error); break; } if (!tls_send_finished(tls)) break; } /* * When starting a new session on the client, the server's * certificate is now verified regardless of the key exchange * method, based on the following logic: * * - tls->ca_certs is non-NULL so tls_handle_certificate * (always called on the client) must have veritifed the * server's certificate chain to be valid and additionally * trusted by our CA. * * - the peer owns the end-entity certificate because: * either: * * * (RSA key exchange algorithm case) the correct * receival of this Finished message confirms the * possession of the master secret, it is verified by * both the successful decryption and the MAC of this * message (either should be enough) because we entered * the TLS_HANDSHAKE_WAIT_FINISHED state only after * encryption and MAC were enabled in ChangeCipherSpec. * To obtain the master secret the server must have been * able to decrypt the pre_master_secret which we had * encrypted with the public key from that certificate. * * * (ECDHE and DHE key exchange algorithms) server was * able to sign the client random together with the * ServerKeyExchange parameters using its certified key * pair. * * If we're resuming a cached session, we have authenticated * this peer before and the successful decryption of this * message confirms their identity hasn't changed. */ if (tls->cipher_suite[0]->signature && ((!tls->server && !resuming && tls->ca_certs) || (resuming && tls->session_peer_identity))) tls->peer_authenticated = true; tls_finished(tls); break; default: TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Invalid message"); } } LIB_EXPORT struct l_tls *l_tls_new(bool server, l_tls_write_cb_t app_data_handler, l_tls_write_cb_t tx_handler, l_tls_ready_cb_t ready_handler, l_tls_disconnect_cb_t disconnect_handler, void *user_data) { struct l_tls *tls; if (!l_key_is_supported(L_KEY_FEATURE_CRYPTO)) return NULL; tls = l_new(struct l_tls, 1); tls->server = server; tls->rx = app_data_handler; tls->tx = tx_handler; tls->ready_handle = ready_handler; tls->disconnected = disconnect_handler; tls->user_data = user_data; tls->cipher_suite_pref_list = tls_cipher_suite_pref; tls->min_version = TLS_MIN_VERSION; tls->max_version = TLS_MAX_VERSION; tls->session_lifetime = 24 * 3600 * L_USEC_PER_SEC; /* If we're the server wait for the Client Hello already */ if (tls->server) TLS_SET_STATE(TLS_HANDSHAKE_WAIT_HELLO); else TLS_SET_STATE(TLS_HANDSHAKE_WAIT_START); return tls; } LIB_EXPORT void l_tls_free(struct l_tls *tls) { enum handshake_hash_type hash; if (unlikely(!tls)) return; if (tls->in_callback) { tls->pending_destroy = true; return; } l_tls_set_cacert(tls, NULL); l_tls_set_auth_data(tls, NULL, NULL); l_tls_set_domain_mask(tls, NULL); l_tls_set_cert_dump_path(tls, NULL); l_tls_set_session_cache(tls, NULL, NULL, 0, 0, NULL, NULL); tls_reset_handshake(tls); tls_cleanup_handshake(tls); tls_reset_cipher_spec(tls, 0); tls_reset_cipher_spec(tls, 1); if (tls->record_buf) l_free(tls->record_buf); if (tls->message_buf) l_free(tls->message_buf); for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) tls_drop_handshake_hash(tls, hash); if (tls->debug_destroy) tls->debug_destroy(tls->debug_data); if (tls->cipher_suite_pref_list != tls_cipher_suite_pref) l_free(tls->cipher_suite_pref_list); l_free(tls); } LIB_EXPORT void l_tls_write(struct l_tls *tls, const uint8_t *data, size_t len) { if (unlikely(!tls->ready)) { return; } tls_tx_record(tls, TLS_CT_APPLICATION_DATA, data, len); } bool tls_handle_message(struct l_tls *tls, const uint8_t *message, int len, enum tls_content_type type, uint16_t version) { enum handshake_hash_type hash; const char *error; switch (type) { case TLS_CT_CHANGE_CIPHER_SPEC: if (len != 1 || message[0] != 0x01) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "ChangeCipherSpec msg decode error"); return false; } if (tls->state != TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "ChangeCipherSpec invalid in state %s", tls_handshake_state_to_str(tls->state)); return false; } if (!tls_change_cipher_spec(tls, 0, &error)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "change_cipher_spec: %s", error); return false; } TLS_SET_STATE(TLS_HANDSHAKE_WAIT_FINISHED); return true; case TLS_CT_ALERT: /* Verify AlertLevel */ if (message[0] != 0x01 && message[0] != 0x02) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "Received bad AlertLevel %i", message[0]); return false; } /* * On a fatal alert we are obligated to respond with a * fatal alert and disconnect but also not complain if * the connection has been torn down by the peer before * we were able to send our alert. However on a non-fatal * alert (warning) we're also allowed to panic and send * a fatal alert, then disconnect, so we do that * regardless of the alert level. */ TLS_DISCONNECT(TLS_ALERT_CLOSE_NOTIFY, message[1], "Peer sent a %s Alert: %s", message[0] == 0x02 ? "Fatal" : "Warning", l_tls_alert_to_str(message[1])); return false; case TLS_CT_HANDSHAKE: /* Start hashing the handshake contents on first message */ if (tls->server && message[0] == TLS_CLIENT_HELLO && (tls->state == TLS_HANDSHAKE_WAIT_HELLO || tls->state == TLS_HANDSHAKE_DONE)) if (!tls_init_handshake_hash(tls)) return false; /* * Corner case: When handling a Certificate Verify or a * Finished message we need access to the messages hash from * before this message was transmitted on the Tx side so we * can verify it matches the hash the sender included in the * message. We save it here for that purpose. Everywhere * else we need to update the hash before handling the new * message because 1. we may need the new hash to build our * own Certificate Verify or Finished messages, and 2. we * update the message hash with newly transmitted messages * inside tls_tx_handshake which may be called as part of * handling incoming message, and if we didn't call * l_checksum_update before, the calls would end up being * out of order. */ if (message[0] == TLS_CERTIFICATE_VERIFY || message[0] == TLS_FINISHED) for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) { if (!tls->handshake_hash[hash]) continue; tls_get_handshake_hash(tls, hash, tls->prev_digest[hash]); } /* * RFC 5246, Section 7.4.1.1: * This message MUST NOT be included in the message hashes * that are maintained throughout the handshake and used in * the Finished messages and the certificate verify message. */ if (message[0] != TLS_HELLO_REQUEST) for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) { if (!tls->handshake_hash[hash]) continue; l_checksum_update(tls->handshake_hash[hash], message, len); } tls_handle_handshake(tls, message[0], message + TLS_HANDSHAKE_HEADER_SIZE, len - TLS_HANDSHAKE_HEADER_SIZE); if (tls->pending_destroy) { l_tls_free(tls); return false; } return true; case TLS_CT_APPLICATION_DATA: if (!tls->ready) { TLS_DISCONNECT(TLS_ALERT_UNEXPECTED_MESSAGE, 0, "Application data message before " "handshake finished"); return false; } if (!len) return true; tls->in_callback = true; tls->rx(message, len, tls->user_data); tls->in_callback = false; if (tls->pending_destroy) { l_tls_free(tls); return false; } return true; } return false; } LIB_EXPORT bool l_tls_start(struct l_tls *tls) { if (tls->max_version < tls->min_version) return false; if (!tls->cipher_suite_pref_list) return false; /* This is a nop in server mode */ if (tls->server) return true; if (tls->state != TLS_HANDSHAKE_WAIT_START) { TLS_DEBUG("Call invalid in state %s", tls_handshake_state_to_str(tls->state)); return false; } if (!tls_init_handshake_hash(tls)) return false; /* * If we're going to try resuming a cached session, send the Client * Hello with the version we think is supported. * * RFC5246 Appendix E.1: * "Whenever a client already knows the highest protocol version known * to a server (for example, when resuming a session), it SHOULD * initiate the connection in that native protocol." * * Don't directly set tls->{min,max}_version as that would make the * handshake fail if the server decides to start a new session with * a new version instead of resuming, which it is allowed to do. */ tls->client_version = tls->max_version; tls_load_cached_client_session(tls); if (tls->pending_destroy) { l_tls_free(tls); return false; } if (!tls_send_client_hello(tls)) return false; TLS_SET_STATE(TLS_HANDSHAKE_WAIT_HELLO); return true; } LIB_EXPORT void l_tls_close(struct l_tls *tls) { tls->record_buf_len = 0; tls->message_buf_len = 0; TLS_DISCONNECT(TLS_ALERT_CLOSE_NOTIFY, 0, "Closing session"); } LIB_EXPORT void l_tls_reset(struct l_tls *tls) { /* * Similar to l_tls_close but without sending the alert or a * disconnect callback. */ tls_reset_handshake(tls); tls_cleanup_handshake(tls); tls_reset_cipher_spec(tls, 0); tls_reset_cipher_spec(tls, 1); tls->negotiated_version = 0; tls->ready = false; tls->record_flush = true; tls->record_buf_len = 0; tls->message_buf_len = 0; } LIB_EXPORT bool l_tls_set_cacert(struct l_tls *tls, struct l_queue *ca_certs) { if (tls->ca_certs) { l_queue_destroy(tls->ca_certs, (l_queue_destroy_func_t) l_cert_free); tls->ca_certs = NULL; } if (ca_certs) { if (!l_key_is_supported(L_KEY_FEATURE_RESTRICT)) { TLS_DEBUG("keyctl restrict support missing, " "check kernel configuration"); return false; } tls->ca_certs = ca_certs; } return true; } LIB_EXPORT bool l_tls_set_auth_data(struct l_tls *tls, struct l_certchain *certchain, struct l_key *priv_key) { if (tls->cert) { l_certchain_free(tls->cert); tls->cert = NULL; } if (tls->priv_key) { l_key_free(tls->priv_key); tls->priv_key = NULL; tls->priv_key_size = 0; } if (certchain) tls->cert = certchain; if (priv_key) { bool is_public = true; tls->priv_key = priv_key; if (!l_key_get_info(tls->priv_key, L_KEY_RSA_PKCS1_V1_5, L_CHECKSUM_NONE, &tls->priv_key_size, &is_public) || is_public) { TLS_DEBUG("Not a private key or l_key_get_info failed"); tls->cert = NULL; tls->priv_key = NULL; tls->priv_key_size = 0; return false; } tls->priv_key_size /= 8; } return true; } bool tls_set_cipher_suites(struct l_tls *tls, const char **suite_list) { struct tls_cipher_suite **suite; if (tls->cipher_suite_pref_list != tls_cipher_suite_pref) l_free(tls->cipher_suite_pref_list); if (!suite_list) { /* Use our default cipher suite preference list */ tls->cipher_suite_pref_list = tls_cipher_suite_pref; return true; } tls->cipher_suite_pref_list = l_new(struct tls_cipher_suite *, l_strv_length((char **) suite_list) + 1); suite = tls->cipher_suite_pref_list; for (; *suite_list; suite_list++) { unsigned int i; for (i = 0; tls_cipher_suite_pref[i]; i++) if (!strcmp(tls_cipher_suite_pref[i]->name, *suite_list)) break; if (tls_cipher_suite_pref[i]) *suite++ = tls_cipher_suite_pref[i]; else TLS_DEBUG("Cipher suite %s is not supported", *suite_list); } if (suite > tls->cipher_suite_pref_list) return true; TLS_DEBUG("None of the supplied suite names is supported"); l_free(suite); tls->cipher_suite_pref_list = NULL; return false; } LIB_EXPORT void l_tls_set_version_range(struct l_tls *tls, enum l_tls_version min_version, enum l_tls_version max_version) { tls->min_version = (min_version && min_version > TLS_MIN_VERSION) ? min_version : TLS_MIN_VERSION; tls->max_version = (max_version && max_version < TLS_MAX_VERSION) ? max_version : TLS_MAX_VERSION; } /** * l_tls_set_domain_mask: * @tls: TLS object being configured * @mask: NULL-terminated array of domain masks * * Sets a mask for domain names contained in the peer certificate * (eg. the subject Common Name) to be matched against. If none of the * domains match the any mask, authentication will fail. At least one * domain has to match at least one mask from the list. * * The masks are each split into segments at the dot characters and each * segment must match the corresponding label of the domain name -- * a domain name is a sequence of labels joined by dots. An asterisk * segment in the mask matches any label. An asterisk segment at the * beginning of the mask matches one or more consecutive labels from * the beginning of the domain string. */ LIB_EXPORT void l_tls_set_domain_mask(struct l_tls *tls, char **mask) { l_strv_free(tls->subject_mask); tls->subject_mask = l_strv_copy(mask); } /** * l_tls_set_session_cache: * @tls: TLS object being configured * @settings: l_settings object to read and write session data from/to or * NULL to disable caching session states. The object must remain valid * until this method is called with a different value. * @group_prefix: prefix to build group names inside @settings. Note: * existing settings in groups starting with the prefix may be * overwritten or removed. * @lifetime: a CLOCK_REALTIME-based microsecond resolution lifetime for * cached sessions. The RFC recommends 24 hours. * @max_sessions: limit on the number of sessions in the cache, or 0 for * unlimited. Ignored in client mode. * @update_cb: a callback to be invoked whenever the settings in @settings * have been updated and may need to be written to persistent storage if * desired, or NULL. * @user_data: user data pointer to pass to @update_cb. * * Enables caching and resuming session states as described in RFC 5246 for * faster setup. l_tls will maintain the required session state data in * @settings including removing expired or erroneous sessions. * * A client's cache contains at most one session state since the client * must request one specific Session ID from the server when resuming a * session. The resumption will only work while the state is cached by * both the server and the client so clients should keep separate @settings * objects or use separate groups inside one object for every discrete * server they may want to connect to. * * Multiple l_tls clients connecting to the same server can share one cache * to allow reusing an established session that is still active (actual * concurrency is not supported as there's no locking.) */ LIB_EXPORT void l_tls_set_session_cache(struct l_tls *tls, struct l_settings *settings, const char *group_prefix, uint64_t lifetime, unsigned int max_sessions, l_tls_session_update_cb_t update_cb, void *user_data) { if (unlikely(!tls)) return; tls->session_settings = settings; tls->session_lifetime = lifetime; tls->session_count_max = max_sessions; tls->session_update_cb = update_cb; tls->session_update_user_data = user_data; l_free(tls->session_prefix); tls->session_prefix = l_strdup(group_prefix); } LIB_EXPORT bool l_tls_get_session_resumed(struct l_tls *tls) { if (unlikely(!tls || !tls->ready)) return false; return tls->session_resumed; } LIB_EXPORT const char *l_tls_alert_to_str(enum l_tls_alert_desc desc) { switch (desc) { case TLS_ALERT_CLOSE_NOTIFY: return "close_notify"; case TLS_ALERT_UNEXPECTED_MESSAGE: return "unexpected_message"; case TLS_ALERT_BAD_RECORD_MAC: return "bad_record_mac"; case TLS_ALERT_DECRYPT_FAIL_RESERVED: return "decryption_failure_RESERVED"; case TLS_ALERT_RECORD_OVERFLOW: return "record_overflow"; case TLS_ALERT_DECOMPRESS_FAIL: return "decompression_failure"; case TLS_ALERT_HANDSHAKE_FAIL: return "handshake_failure"; case TLS_ALERT_NO_CERT_RESERVED: return "no_certificate_RESERVED"; case TLS_ALERT_BAD_CERT: return "bad_certificate"; case TLS_ALERT_UNSUPPORTED_CERT: return "unsupported_certificate"; case TLS_ALERT_CERT_REVOKED: return "certificate_revoked"; case TLS_ALERT_CERT_EXPIRED: return "certificate_expired"; case TLS_ALERT_CERT_UNKNOWN: return "certificate_unknown"; case TLS_ALERT_ILLEGAL_PARAM: return "illegal_parameter"; case TLS_ALERT_UNKNOWN_CA: return "unknown_ca"; case TLS_ALERT_ACCESS_DENIED: return "access_denied"; case TLS_ALERT_DECODE_ERROR: return "decode_error"; case TLS_ALERT_DECRYPT_ERROR: return "decrypt_error"; case TLS_ALERT_EXPORT_RES_RESERVED: return "export_restriction_RESERVED"; case TLS_ALERT_PROTOCOL_VERSION: return "protocol_version"; case TLS_ALERT_INSUFFICIENT_SECURITY: return "insufficient_security"; case TLS_ALERT_INTERNAL_ERROR: return "internal_error"; case TLS_ALERT_USER_CANCELED: return "user_canceled"; case TLS_ALERT_NO_RENEGOTIATION: return "no_renegotiation"; case TLS_ALERT_UNSUPPORTED_EXTENSION: return "unsupported_extension"; } return NULL; } const char *tls_handshake_state_to_str(enum tls_handshake_state state) { static char buf[100]; switch (state) { SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_START) SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_HELLO) SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_CERTIFICATE) SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_KEY_EXCHANGE) SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_HELLO_DONE) SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_CERTIFICATE_VERIFY) SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC) SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_WAIT_FINISHED) SWITCH_ENUM_TO_STR(TLS_HANDSHAKE_DONE) } snprintf(buf, sizeof(buf), "tls_handshake_state(%i)", state); return buf; } int tls_parse_certificate_list(const void *data, size_t len, struct l_certchain **out_certchain) { const uint8_t *buf = data; struct l_certchain *chain = NULL; while (len) { struct l_cert *cert; size_t cert_len; if (len < 3) goto decode_error; cert_len = *buf++ << 16; cert_len |= *buf++ << 8; cert_len |= *buf++ << 0; if (cert_len + 3 > len) goto decode_error; cert = l_cert_new_from_der(buf, cert_len); if (!cert) goto decode_error; if (!chain) { chain = certchain_new_from_leaf(cert); if (!chain) goto decode_error; } else certchain_link_issuer(chain, cert); buf += cert_len; len -= cert_len + 3; } if (out_certchain) *out_certchain = chain; else l_certchain_free(chain); return 0; decode_error: l_certchain_free(chain); return -EBADMSG; } LIB_EXPORT bool l_tls_set_debug(struct l_tls *tls, l_tls_debug_cb_t function, void *user_data, l_tls_destroy_cb_t destroy) { if (unlikely(!tls)) return false; if (tls->debug_destroy) tls->debug_destroy(tls->debug_data); tls->debug_handler = function; tls->debug_destroy = destroy; tls->debug_data = user_data; return true; } LIB_EXPORT bool l_tls_set_cert_dump_path(struct l_tls *tls, const char *path) { l_free(tls->cert_dump_path); tls->cert_dump_path = path ? l_strdup(path) : NULL; return true; } bluez-5.87/ell/PaxHeaders/gvariant-private.h0000644000000000000000000000005014504767710016076 xustar0020 atime=1783076292 20 ctime=1783094099 bluez-5.87/ell/gvariant-private.h0000644000000000000000000000442314504767710015562 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ struct l_dbus_message_iter; struct dbus_builder; bool _gvariant_iter_init(struct l_dbus_message_iter *iter, struct l_dbus_message *message, const char *sig_start, const char *sig_end, const void *data, size_t len); bool _gvariant_iter_next_entry_basic(struct l_dbus_message_iter *iter, char type, void *out_p); bool _gvariant_iter_enter_struct(struct l_dbus_message_iter *iter, struct l_dbus_message_iter *structure); bool _gvariant_iter_enter_variant(struct l_dbus_message_iter *iter, struct l_dbus_message_iter *variant); bool _gvariant_iter_enter_array(struct l_dbus_message_iter *iter, struct l_dbus_message_iter *array); bool _gvariant_iter_skip_entry(struct l_dbus_message_iter *iter); bool _gvariant_valid_signature(const char *sig); int _gvariant_get_alignment(const char *signature); bool _gvariant_is_fixed_size(const char *signature); int _gvariant_get_fixed_size(const char *signature); int _gvariant_num_children(const char *sig); struct dbus_builder *_gvariant_builder_new(void *body, size_t body_size); void _gvariant_builder_free(struct dbus_builder *builder); bool _gvariant_builder_append_basic(struct dbus_builder *builder, char type, const void *value); bool _gvariant_builder_mark(struct dbus_builder *builder); bool _gvariant_builder_rewind(struct dbus_builder *builder); char *_gvariant_builder_finish(struct dbus_builder *builder, void **body, size_t *body_size); bool _gvariant_builder_enter_struct(struct dbus_builder *builder, const char *signature); bool _gvariant_builder_leave_struct(struct dbus_builder *builder); bool _gvariant_builder_enter_dict(struct dbus_builder *builder, const char *signature); bool _gvariant_builder_leave_dict(struct dbus_builder *builder); bool _gvariant_builder_enter_variant(struct dbus_builder *builder, const char *signature); bool _gvariant_builder_leave_variant(struct dbus_builder *builder); bool _gvariant_builder_enter_array(struct dbus_builder *builder, const char *signature); bool _gvariant_builder_leave_array(struct dbus_builder *builder); size_t _gvariant_message_finalize(size_t header_end, void *body, size_t body_size, const char *signature); bluez-5.87/ell/PaxHeaders/pem-private.h0000644000000000000000000000005014504767710015044 xustar0020 atime=1783076232 20 ctime=1783094099 bluez-5.87/ell/pem-private.h0000644000000000000000000000163214504767710014527 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2019 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define _GNU_SOURCE #include #include struct l_certchain; struct pem_file_info { int fd; struct stat st; uint8_t *data; }; int pem_file_open(struct pem_file_info *info, const char *filename); void pem_file_close(struct pem_file_info *info); const char *pem_next(const void *buf, size_t buf_len, char **type_label, size_t *base64_len, const char **endp, bool strict); uint8_t *pem_load_buffer(const void *buf, size_t buf_len, char **out_type_label, size_t *out_len, char **out_headers, const char **out_endp); struct l_key *pem_load_private_key(uint8_t *content, size_t len, char *label, const char *passphrase, char *headers, bool *encrypted); int pem_write_certificate_chain(const struct l_certchain *cert, const char *filename); bluez-5.87/ell/PaxHeaders/gvariant-util.c0000644000000000000000000000005014601374755015375 xustar0020 atime=1783076306 20 ctime=1783094099 bluez-5.87/ell/gvariant-util.c0000644000000000000000000007170714601374755015072 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include "private.h" #include "useful.h" #include "util.h" #include "queue.h" #include "string.h" #include "log.h" #include "dbus.h" #include "dbus-private.h" #include "gvariant-private.h" static const char *simple_types = "sogybnqiuxtdh"; static const char *variable_types = "sogav"; static const char *fixed_types = "bynqhiuxtd"; /* * The alignment of a container type is equal to the largest alignment of * any potential child of that container. This means that, even if an array * of 32-bit integers is empty, it still must be aligned to the nearest * multiple of 4 bytes. It also means that the variant type (described below) * has an alignment of 8 (since it could potentially contain a value of any * other type and the maximum alignment is 8). */ static int get_basic_alignment(const char type) { switch (type) { case 'b': return 1; case 'y': return 1; case 'n': case 'q': return 2; case 'i': case 'u': return 4; case 'x': case 't': case 'd': return 8; case 's': case 'g': case 'o': return 1; case 'h': return 4; case 'v': return 8; default: return 0; } } static int get_basic_fixed_size(const char type) { switch (type) { case 'b': return 1; case 'y': return 1; case 'n': case 'q': return 2; case 'i': case 'u': return 4; case 'x': case 't': case 'd': return 8; case 'h': return 4; default: return 0; } } static const char *validate_next_type(const char *sig, int *out_alignment) { char s = *sig; int alignment; if (s == '\0') return NULL; if (strchr(simple_types, s) || s == 'v') { *out_alignment = get_basic_alignment(s); return sig + 1; } switch (s) { case 'a': return validate_next_type(++sig, out_alignment); case '{': s = *++sig; /* Dictionary keys can only be simple types */ if (!strchr(simple_types, s)) return NULL; alignment = get_basic_alignment(s); sig = validate_next_type(sig + 1, out_alignment); if (!sig) return NULL; if (*sig != '}') return NULL; if (alignment > *out_alignment) *out_alignment = alignment; return sig + 1; case '(': { int max_alignment = 1, alignment; sig++; while (sig && *sig != ')') { sig = validate_next_type(sig, &alignment); if (alignment > max_alignment) max_alignment = alignment; } if (!sig) return NULL; if (*sig != ')') return NULL; *out_alignment = max_alignment; return sig + 1; } } return NULL; } bool _gvariant_valid_signature(const char *sig) { const char *s = sig; int a; if (strlen(sig) > 255) return false; do { s = validate_next_type(s, &a); if (!s) return false; } while (*s); return true; } int _gvariant_num_children(const char *sig) { const char *s = sig; int a; int num_children = 0; if (strlen(sig) > 255) return false; do { s = validate_next_type(s, &a); if (!s) return -1; num_children += 1; } while (*s); return num_children; } int _gvariant_get_alignment(const char *sig) { int max_alignment = 1, alignment; const char *s = sig; /* 8 is the largest alignment possible, so quit if we reach it */ while (*s && max_alignment != 8) { s = validate_next_type(s, &alignment); if (!s) return 0; if (alignment > max_alignment) max_alignment = alignment; } return max_alignment; } bool _gvariant_is_fixed_size(const char *sig) { while (*sig != 0) { if (strchr(variable_types, sig[0])) return false; sig += 1; } return true; } int _gvariant_get_fixed_size(const char *sig) { const char *s = sig; const char *p; int size = 0; int alignment; int max_alignment = 1; int r; while (*s) { if (strchr(variable_types, *s)) return 0; if (strchr(fixed_types, *s)) { alignment = get_basic_alignment(*s); if (alignment > max_alignment) max_alignment = alignment; size = align_len(size, alignment); size += get_basic_fixed_size(*s); s++; continue; } if (*s == '}' || *s == ')') break; p = validate_next_type(s, &alignment); if (!p) return 0; if (alignment > max_alignment) max_alignment = alignment; size = align_len(size, alignment); /* Handle special case of unit type */ if (s[0] == '(' && s[1] == ')') r = 1; else r = _gvariant_get_fixed_size(s + 1); if (r == 0) return 0; size += r; s = p; } size = align_len(size, max_alignment); return size; } static inline size_t offset_length(size_t size, size_t n_offsets) { if (size + n_offsets <= 0xff) return 1; if (size + n_offsets * 2 <= 0xffff) return 2; if (size + n_offsets * 4 <= 0xffffffff) return 4; return 8; } static inline size_t read_word_le(const void *p, size_t sz) { union { uint16_t u16; uint32_t u32; uint64_t u64; } x; if (sz == 1) return *(uint8_t *) p; memcpy(&x, p, sz); if (sz == 2) return le16toh(x.u16); if (sz == 4) return le32toh(x.u32); return le64toh(x.u64); } static inline void write_word_le(void *p, size_t value, size_t sz) { union { uint16_t u16; uint32_t u32; uint64_t u64; } x; if (sz == 1) { *(uint8_t *) p = value; return; } if (sz == 2) x.u16 = htole16((uint16_t) value); else if (sz == 4) x.u32 = htole32((uint32_t) value); else x.u64 = htole64((uint64_t) value); memcpy(p, &x, sz); } static bool gvariant_iter_init_internal(struct l_dbus_message_iter *iter, struct l_dbus_message *message, enum dbus_container_type type, const char *sig_start, const char *sig_end, const void *data, size_t len) { const char *p; int i; int v; char subsig[256]; unsigned int num_variable = 0; unsigned int offset_len = offset_length(len, 0); size_t last_offset; struct gvariant_type_info { uint8_t sig_start; uint8_t sig_end; bool fixed_size : 1; unsigned int alignment : 4; size_t end; /* Index past the end of the type */ } *children; int n_children; if (sig_end) { size_t len = sig_end - sig_start; memcpy(subsig, sig_start, len); subsig[len] = '\0'; } else strcpy(subsig, sig_start); iter->message = message; iter->sig_start = sig_start; iter->sig_len = strlen(subsig); iter->sig_pos = 0; iter->data = data; iter->len = len; iter->pos = 0; if (subsig[0] != '\0') { n_children = _gvariant_num_children(subsig); if (n_children < 0) return false; children = l_new(struct gvariant_type_info, n_children); } else { n_children = 0; children = NULL; } for (p = sig_start, i = 0; i < n_children; i++) { int alignment; size_t size; size_t len; children[i].sig_start = p - sig_start; p = validate_next_type(p, &alignment); children[i].sig_end = p - sig_start; len = children[i].sig_end - children[i].sig_start; memcpy(subsig, sig_start + children[i].sig_start, len); subsig[len] = '\0'; children[i].alignment = alignment; children[i].fixed_size = _gvariant_is_fixed_size(subsig); if (children[i].fixed_size) { size = _gvariant_get_fixed_size(subsig); children[i].end = size; } else if (i + 1 < n_children) num_variable += 1; } if (len < num_variable * offset_len) goto fail; last_offset = len - num_variable * offset_len; if (num_variable > 0) iter->offsets = iter->data + len - offset_len; else iter->offsets = NULL; for (i = 0, v = 0; i < n_children; i++) { size_t o; if (children[i].fixed_size) { if (i == 0) continue; o = align_len(children[i-1].end, children[i].alignment); children[i].end += o; if (children[i].end > len) goto fail; continue; } if (num_variable == 0) { children[i].end = last_offset; continue; } v += 1; children[i].end = read_word_le(data + len - offset_len * v, offset_len); num_variable -= 1; if (children[i].end > len) goto fail; } iter->container_type = type; if (type == DBUS_CONTAINER_TYPE_ARRAY && !children[0].fixed_size) { size_t offset = read_word_le(iter->data + iter->len - offset_len, offset_len); iter->offsets = iter->data + offset; } l_free(children); return true; fail: l_free(children); return false; } bool _gvariant_iter_init(struct l_dbus_message_iter *iter, struct l_dbus_message *message, const char *sig_start, const char *sig_end, const void *data, size_t len) { return gvariant_iter_init_internal(iter, message, DBUS_CONTAINER_TYPE_STRUCT, sig_start, sig_end, data, len); } static const void *next_item(struct l_dbus_message_iter *iter, size_t *out_item_size) { const void *start; const char *p; char sig[256]; int alignment; bool fixed_size; bool last_member; unsigned int sig_len; unsigned int offset_len; memcpy(sig, iter->sig_start + iter->sig_pos, iter->sig_len - iter->sig_pos); sig[iter->sig_len - iter->sig_pos] = '\0'; /* * Find the next type and make a note whether it is the last in the * structure. Arrays will always have a single complete type, so * last_member will always be true. */ p = validate_next_type(sig, &alignment); if (!p) return NULL; sig_len = p - sig; last_member = *p == '\0'; sig[sig_len] = '\0'; fixed_size = _gvariant_is_fixed_size(sig); if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY) iter->sig_pos += sig_len; iter->pos = align_len(iter->pos, alignment); if (fixed_size) { *out_item_size = _gvariant_get_fixed_size(sig); goto done; } if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY && last_member) { unsigned int len = iter->len; offset_len = offset_length(iter->len, 0); if (iter->offsets && iter->offsets + offset_len < iter->data + len) len = iter->offsets + offset_len - iter->data; *out_item_size = len - iter->pos; goto done; } if (iter->offsets >= iter->data + iter->len) return NULL; offset_len = offset_length(iter->len, 0); *out_item_size = read_word_le(iter->offsets, offset_len) - iter->pos; /* In structures the offsets are in reverse order */ if (iter->container_type == DBUS_CONTAINER_TYPE_ARRAY) iter->offsets += offset_len; else iter->offsets -= offset_len; done: start = iter->data + iter->pos; if (start >= iter->data + iter->len) return NULL; iter->pos += *out_item_size; return start; } bool _gvariant_iter_next_entry_basic(struct l_dbus_message_iter *iter, char type, void *out) { size_t item_size = 0; const void *start; uint8_t uint8_val; uint16_t uint16_val; uint32_t uint32_val; uint64_t uint64_val; int16_t int16_val; int32_t int32_val; int64_t int64_val; if (iter->pos >= iter->len) return false; if (iter->sig_start[iter->sig_pos] != type) return false; start = next_item(iter, &item_size); if (!start) return false; switch (type) { case 'o': case 's': case 'g': { const void *end = memchr(start, 0, item_size); if (!end) return false; *(const char**) out = start; break; } case 'b': uint8_val = l_get_u8(start); *(bool *) out = !!uint8_val; break; case 'y': uint8_val = l_get_u8(start); *(uint8_t *) out = uint8_val; break; case 'n': int16_val = l_get_s16(start); *(int16_t *) out = int16_val; break; case 'q': uint16_val = l_get_u16(start); *(uint16_t *) out = uint16_val; break; case 'i': int32_val = l_get_s32(start); *(int32_t *) out = int32_val; break; case 'h': case 'u': uint32_val = l_get_u32(start); *(uint32_t *) out = uint32_val; break; case 'x': int64_val = l_get_s64(start); *(int64_t *) out = int64_val; break; case 't': uint64_val = l_get_u64(start); *(uint64_t *) out = uint64_val; break; case 'd': uint64_val = l_get_u64(start); *(uint64_t *) out = uint64_val; break; } return true; } bool _gvariant_iter_enter_struct(struct l_dbus_message_iter *iter, struct l_dbus_message_iter *structure) { bool is_dict = iter->sig_start[iter->sig_pos] == '{'; bool is_struct = iter->sig_start[iter->sig_pos] == '('; const char *sig_start = iter->sig_start + iter->sig_pos + 1; const char *sig_end; const void *start; size_t item_size; enum dbus_container_type type; if (!is_dict && !is_struct) return false; start = next_item(iter, &item_size); if (!start) return false; /* For ARRAY containers the sig_pos is never incremented */ if (iter->container_type == DBUS_CONTAINER_TYPE_ARRAY) sig_end = iter->sig_start + iter->sig_len - 1; else sig_end = iter->sig_start + iter->sig_pos - 1; type = is_dict ? DBUS_CONTAINER_TYPE_DICT_ENTRY : DBUS_CONTAINER_TYPE_STRUCT; return gvariant_iter_init_internal(structure, iter->message, type, sig_start, sig_end, start, item_size); } bool _gvariant_iter_enter_variant(struct l_dbus_message_iter *iter, struct l_dbus_message_iter *variant) { size_t item_size; const void *start, *end, *nul; char signature[256]; if (iter->sig_start[iter->sig_pos] != 'v') return false; start = next_item(iter, &item_size); if (!start) return false; /* Find the signature */ end = start + item_size; nul = memrchr(start, 0, end - start); if (!nul) return false; if (end - nul - 1 > 255) return false; memcpy(signature, nul + 1, end - nul - 1); signature[end - nul - 1] = '\0'; if (_gvariant_num_children(signature) != 1) return false; return gvariant_iter_init_internal(variant, iter->message, DBUS_CONTAINER_TYPE_VARIANT, nul + 1, end, start, nul - start); } bool _gvariant_iter_enter_array(struct l_dbus_message_iter *iter, struct l_dbus_message_iter *array) { const char *sig_start; const char *sig_end; size_t item_size; const void *start; if (iter->sig_start[iter->sig_pos] != 'a') return false; sig_start = iter->sig_start + iter->sig_pos + 1; start = next_item(iter, &item_size); if (!start) return false; /* For ARRAY containers the sig_pos is never incremented */ if (iter->container_type == DBUS_CONTAINER_TYPE_ARRAY) sig_end = iter->sig_start + iter->sig_len; else sig_end = iter->sig_start + iter->sig_pos; return gvariant_iter_init_internal(array, iter->message, DBUS_CONTAINER_TYPE_ARRAY, sig_start, sig_end, start, item_size); } bool _gvariant_iter_skip_entry(struct l_dbus_message_iter *iter) { size_t size; if (!next_item(iter, &size)) return false; return true; } struct dbus_builder { struct l_string *signature; void *body; size_t body_size; size_t body_pos; struct l_queue *containers; struct { struct container *container; int sig_end; size_t body_pos; size_t offset_index; bool variable_is_last : 1; } mark; }; struct container { size_t *offsets; size_t offsets_size; size_t offset_index; size_t start; bool variable_is_last : 1; enum dbus_container_type type; char signature[256]; uint8_t sigindex; }; static inline size_t grow_body(struct dbus_builder *builder, size_t len, unsigned int alignment) { size_t size = align_len(builder->body_pos, alignment); if (size + len > builder->body_size) { builder->body = l_realloc(builder->body, size + len); builder->body_size = size + len; } if (size - builder->body_pos > 0) memset(builder->body + builder->body_pos, 0, size - builder->body_pos); builder->body_pos = size + len; return size; } static inline bool grow_offsets(struct container *container) { size_t needed; if (container->offset_index < container->offsets_size) return true; needed = container->offsets_size * 2; if (needed > USHRT_MAX) return false; if (needed == 0) needed = 8; container->offsets = l_realloc(container->offsets, needed * sizeof(size_t)); container->offsets_size = needed; return true; } static struct container *container_new(enum dbus_container_type type, const char *signature, size_t start) { struct container *ret; ret = l_new(struct container, 1); ret->type = type; strcpy(ret->signature, signature); ret->start = start; return ret; } static void container_free(struct container *container) { l_free(container->offsets); l_free(container); } static void container_append_struct_offsets(struct container *container, struct dbus_builder *builder) { size_t offset_size; int i; size_t start; if (container->variable_is_last) container->offset_index -= 1; if (container->offset_index == 0) return; offset_size = offset_length(builder->body_pos, container->offset_index); start = grow_body(builder, offset_size * container->offset_index, 1); for (i = container->offset_index - 1; i >= 0; i--) { write_word_le(builder->body + start, container->offsets[i], offset_size); start += offset_size; } } static void container_append_array_offsets(struct container *container, struct dbus_builder *builder) { size_t offset_size; unsigned int i; size_t start; if (container->offset_index == 0) return; offset_size = offset_length(builder->body_pos, container->offset_index); start = grow_body(builder, offset_size * container->offset_index, 1); for (i = 0; i < container->offset_index; i++) { write_word_le(builder->body + start, container->offsets[i], offset_size); start += offset_size; } } struct dbus_builder *_gvariant_builder_new(void *body, size_t body_size) { struct dbus_builder *builder; struct container *root; builder = l_new(struct dbus_builder, 1); builder->signature = l_string_new(63); builder->containers = l_queue_new(); root = container_new(DBUS_CONTAINER_TYPE_STRUCT, "", 0); l_queue_push_head(builder->containers, root); builder->body = body; builder->body_size = body_size; builder->body_pos = body_size; builder->mark.container = root; return builder; } void _gvariant_builder_free(struct dbus_builder *builder) { if (unlikely(!builder)) return; l_string_free(builder->signature); l_queue_destroy(builder->containers, (l_queue_destroy_func_t) container_free); l_free(builder->body); l_free(builder); } static bool enter_struct_dict_common(struct dbus_builder *builder, const char *signature, enum dbus_container_type type, const char open, const char close) { size_t qlen = l_queue_length(builder->containers); struct container *container = l_queue_peek_head(builder->containers); int alignment; size_t start; if (qlen == 1) { if (l_string_length(builder->signature) + strlen(signature) + 2 > 255) return false; } else { /* Verify Signatures Match */ char expect[256]; const char *start; const char *end; start = container->signature + container->sigindex; end = validate_next_type(start, &alignment) - 1; if (*start != open || *end != close) return false; memcpy(expect, start + 1, end - start - 1); expect[end - start - 1] = '\0'; if (strcmp(expect, signature)) return false; } alignment = _gvariant_get_alignment(signature); start = grow_body(builder, 0, alignment); container = container_new(type, signature, start); l_queue_push_head(builder->containers, container); return true; } bool _gvariant_builder_enter_struct(struct dbus_builder *builder, const char *signature) { if (signature[0] && !_gvariant_valid_signature(signature)) return false; return enter_struct_dict_common(builder, signature, DBUS_CONTAINER_TYPE_STRUCT, '(', ')'); } bool _gvariant_builder_enter_dict(struct dbus_builder *builder, const char *signature) { if (_gvariant_num_children(signature) != 2) return false; if (!strchr(simple_types, signature[0])) return false; return enter_struct_dict_common(builder, signature, DBUS_CONTAINER_TYPE_DICT_ENTRY, '{', '}'); } static bool leave_struct_dict_common(struct dbus_builder *builder, enum dbus_container_type type, const char open, const char close) { struct container *container = l_queue_peek_head(builder->containers); size_t qlen = l_queue_length(builder->containers); struct container *parent; if (unlikely(qlen <= 1)) return false; if (unlikely(container->type != type)) return false; l_queue_pop_head(builder->containers); qlen -= 1; parent = l_queue_peek_head(builder->containers); if (_gvariant_is_fixed_size(container->signature)) { int alignment = _gvariant_get_alignment(container->signature); grow_body(builder, 0, alignment); /* Empty struct or "unit type" is encoded as a zero byte */ if (container->signature[0] == '\0') { size_t start = grow_body(builder, 1, 1); memset(builder->body + start, 0, 1); } parent->variable_is_last = false; } else { size_t offset; if (!grow_offsets(parent)) return false; container_append_struct_offsets(container, builder); offset = builder->body_pos - parent->start; parent->offsets[parent->offset_index++] = offset; parent->variable_is_last = true; } if (qlen == 1) l_string_append_printf(builder->signature, "%c%s%c", open, container->signature, close); else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY) parent->sigindex += strlen(container->signature) + 2; container_free(container); return true; } bool _gvariant_builder_leave_struct(struct dbus_builder *builder) { return leave_struct_dict_common(builder, DBUS_CONTAINER_TYPE_STRUCT, '(', ')'); } bool _gvariant_builder_leave_dict(struct dbus_builder *builder) { return leave_struct_dict_common(builder, DBUS_CONTAINER_TYPE_DICT_ENTRY, '{', '}'); } bool _gvariant_builder_enter_variant(struct dbus_builder *builder, const char *signature) { size_t qlen = l_queue_length(builder->containers); struct container *container = l_queue_peek_head(builder->containers); size_t start; if (_gvariant_num_children(signature) != 1) return false; if (qlen == 1) { if (l_string_length(builder->signature) + 1 > 255) return false; } else if (container->signature[container->sigindex] != 'v') return false; start = grow_body(builder, 0, 8); container = container_new(DBUS_CONTAINER_TYPE_VARIANT, signature, start); l_queue_push_head(builder->containers, container); return true; } bool _gvariant_builder_leave_variant(struct dbus_builder *builder) { struct container *container = l_queue_peek_head(builder->containers); size_t qlen = l_queue_length(builder->containers); struct container *parent; size_t start; size_t siglen; size_t offset; if (unlikely(qlen <= 1)) return false; if (unlikely(container->type != DBUS_CONTAINER_TYPE_VARIANT)) return false; l_queue_pop_head(builder->containers); qlen -= 1; parent = l_queue_peek_head(builder->containers); siglen = strlen(container->signature); start = grow_body(builder, siglen + 1, 1); memset(builder->body + start, 0, 1); memcpy(builder->body + start + 1, container->signature, siglen); if (!grow_offsets(parent)) return false; offset = builder->body_pos - parent->start; parent->offsets[parent->offset_index++] = offset; parent->variable_is_last = true; if (qlen == 1) l_string_append_c(builder->signature, 'v'); else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY) parent->sigindex += 1; container_free(container); return true; } bool _gvariant_builder_enter_array(struct dbus_builder *builder, const char *signature) { size_t qlen = l_queue_length(builder->containers); struct container *container = l_queue_peek_head(builder->containers); size_t start; int alignment; if (_gvariant_num_children(signature) != 1) return false; if (qlen == 1) { if (l_string_length(builder->signature) + strlen(signature) + 1 > 255) return false; } else { /* Verify Signatures Match */ char expect[256]; const char *start; const char *end; start = container->signature + container->sigindex; end = validate_next_type(start, &alignment); if (*start != 'a') return false; memcpy(expect, start + 1, end - start - 1); expect[end - start - 1] = '\0'; if (strcmp(expect, signature)) return false; } alignment = _gvariant_get_alignment(signature); start = grow_body(builder, 0, alignment); container = container_new(DBUS_CONTAINER_TYPE_ARRAY, signature, start); l_queue_push_head(builder->containers, container); return true; } bool _gvariant_builder_leave_array(struct dbus_builder *builder) { struct container *container = l_queue_peek_head(builder->containers); size_t qlen = l_queue_length(builder->containers); struct container *parent; size_t offset; if (unlikely(qlen <= 1)) return false; if (unlikely(container->type != DBUS_CONTAINER_TYPE_ARRAY)) return false; l_queue_pop_head(builder->containers); qlen -= 1; parent = l_queue_peek_head(builder->containers); if (!_gvariant_is_fixed_size(container->signature)) container_append_array_offsets(container, builder); if (!grow_offsets(parent)) return false; offset = builder->body_pos - parent->start; parent->offsets[parent->offset_index++] = offset; parent->variable_is_last = true; if (qlen == 1) l_string_append_printf(builder->signature, "a%s", container->signature); else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY) parent->sigindex += strlen(container->signature) + 1; container_free(container); return true; } bool _gvariant_builder_append_basic(struct dbus_builder *builder, char type, const void *value) { struct container *container = l_queue_peek_head(builder->containers); size_t start; unsigned int alignment; size_t len; size_t offset; if (unlikely(!builder)) return false; if (unlikely(!strchr(simple_types, type))) return false; alignment = get_basic_alignment(type); if (!alignment) return false; if (l_queue_length(builder->containers) == 1) l_string_append_c(builder->signature, type); else if (container->signature[container->sigindex] != type) return false; len = get_basic_fixed_size(type); if (len) { start = grow_body(builder, len, alignment); memcpy(builder->body + start, value, len); container->variable_is_last = false; if (container->type != DBUS_CONTAINER_TYPE_ARRAY) container->sigindex += 1; return true; } if (!grow_offsets(container)) return false; len = strlen(value) + 1; start = grow_body(builder, len, alignment); memcpy(builder->body + start, value, len); offset = builder->body_pos - container->start; container->offsets[container->offset_index++] = offset; container->variable_is_last = true; if (container->type != DBUS_CONTAINER_TYPE_ARRAY) container->sigindex += 1; return true; } bool _gvariant_builder_mark(struct dbus_builder *builder) { struct container *container = l_queue_peek_head(builder->containers); builder->mark.container = container; if (l_queue_length(builder->containers) == 1) builder->mark.sig_end = l_string_length(builder->signature); else builder->mark.sig_end = container->sigindex; builder->mark.body_pos = builder->body_pos; builder->mark.offset_index = container->offset_index; builder->mark.variable_is_last = container->variable_is_last; return true; } bool _gvariant_builder_rewind(struct dbus_builder *builder) { struct container *container; while ((container = l_queue_peek_head(builder->containers)) != builder->mark.container) { container_free(container); l_queue_pop_head(builder->containers); } builder->body_pos = builder->mark.body_pos; container->offset_index = builder->mark.offset_index; container->variable_is_last = builder->mark.variable_is_last; if (l_queue_length(builder->containers) == 1) l_string_truncate(builder->signature, builder->mark.sig_end); else container->sigindex = builder->mark.sig_end; return true; } char *_gvariant_builder_finish(struct dbus_builder *builder, void **body, size_t *body_size) { char *signature; struct container *root; uint8_t *variant_buf; size_t size; if (unlikely(!builder)) return NULL; if (unlikely(l_queue_length(builder->containers) != 1)) return NULL; root = l_queue_peek_head(builder->containers); signature = l_string_unwrap(builder->signature); builder->signature = NULL; if (_gvariant_is_fixed_size(signature)) { int alignment = _gvariant_get_alignment(signature); grow_body(builder, 0, alignment); /* Empty struct or "unit type" is encoded as a zero byte */ if (signature[0] == '\0') { size_t start = grow_body(builder, 1, 1); memset(builder->body + start, 0, 1); } } else container_append_struct_offsets(root, builder); /* * Make sure there's enough space after the body for the variant * signature written here but not included in the body size and * one framing offset value to be written in * _gvariant_message_finalize. */ size = 3 + strlen(signature) + 8; if (builder->body_pos + size > builder->body_size) builder->body = l_realloc(builder->body, builder->body_pos + size); variant_buf = builder->body + builder->body_pos; *variant_buf++ = 0; *variant_buf++ = '('; variant_buf = mempcpy(variant_buf, signature, strlen(signature)); *variant_buf++ = ')'; *body = builder->body; *body_size = builder->body_pos; builder->body = NULL; builder->body_size = 0; return signature; } /* * Write the header's framing offset after the body variant which is the * last piece of data in the message after the header, the padding and * the builder has written the message body. */ size_t _gvariant_message_finalize(size_t header_end, void *body, size_t body_size, const char *signature) { size_t offset_start; size_t offset_size; offset_start = body_size + 3 + strlen(signature); offset_size = offset_length(align_len(header_end, 8) + offset_start, 1); write_word_le(body + offset_start, header_end, offset_size); return align_len(header_end, 8) + offset_start + offset_size; } bluez-5.87/ell/PaxHeaders/dbus.c0000644000000000000000000000005015040645364013537 xustar0020 atime=1783076288 20 ctime=1783094099 bluez-5.87/ell/dbus.c0000644000000000000000000013165515040645364013233 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "io.h" #include "idle.h" #include "queue.h" #include "hashmap.h" #include "dbus.h" #include "private.h" #include "useful.h" #include "dbus-private.h" #define DEFAULT_SYSTEM_BUS_ADDRESS "unix:path=/var/run/dbus/system_bus_socket" #define DBUS_SERVICE_DBUS "org.freedesktop.DBus" #define DBUS_PATH_DBUS "/org/freedesktop/DBus" #define DBUS_MAXIMUM_MATCH_RULE_LENGTH 1024 enum auth_state { WAITING_FOR_OK, WAITING_FOR_AGREE_UNIX_FD, SETUP_DONE }; struct l_dbus_ops { char version; bool (*send_message)(struct l_dbus *bus, struct l_dbus_message *message); struct l_dbus_message *(*recv_message)(struct l_dbus *bus); void (*free)(struct l_dbus *bus); struct _dbus_name_ops name_ops; struct _dbus_filter_ops filter_ops; uint32_t (*name_acquire)(struct l_dbus *dbus, const char *name, bool allow_replacement, bool replace_existing, bool queue, l_dbus_name_acquire_func_t callback, void *user_data); }; struct l_dbus { struct l_io *io; char *guid; bool negotiate_unix_fd; bool support_unix_fd; bool is_ready; char *unique_name; unsigned int next_id; uint32_t next_serial; struct l_queue *message_queue; struct l_hashmap *message_list; struct l_hashmap *signal_list; l_dbus_ready_func_t ready_handler; l_dbus_destroy_func_t ready_destroy; void *ready_data; l_dbus_disconnect_func_t disconnect_handler; l_dbus_destroy_func_t disconnect_destroy; void *disconnect_data; l_dbus_debug_func_t debug_handler; l_dbus_destroy_func_t debug_destroy; void *debug_data; struct _dbus_object_tree *tree; struct _dbus_name_cache *name_cache; struct _dbus_filter *filter; bool name_notify_enabled; const struct l_dbus_ops *driver; }; struct l_dbus_classic { struct l_dbus super; void *auth_command; enum auth_state auth_state; bool skip_hello; struct l_hashmap *match_strings; int *fd_buf; unsigned int num_fds; }; struct message_callback { uint32_t serial; struct l_dbus_message *message; l_dbus_message_func_t callback; l_dbus_destroy_func_t destroy; void *user_data; }; struct signal_callback { unsigned int id; l_dbus_message_func_t callback; l_dbus_destroy_func_t destroy; void *user_data; }; static void message_queue_destroy(void *data) { struct message_callback *callback = data; l_dbus_message_unref(callback->message); if (callback->destroy) callback->destroy(callback->user_data); l_free(callback); } static void message_list_destroy(void *value) { message_queue_destroy(value); } static void signal_list_destroy(void *value) { struct signal_callback *callback = value; if (callback->destroy) callback->destroy(callback->user_data); l_free(callback); } static bool message_write_handler(struct l_io *io, void *user_data) { struct l_dbus *dbus = user_data; struct l_dbus_message *message; struct message_callback *callback; const void *header, *body; size_t header_size, body_size; callback = l_queue_pop_head(dbus->message_queue); if (!callback) return false; message = callback->message; if (_dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL && callback->callback == NULL) l_dbus_message_set_no_reply(message, true); _dbus_message_set_serial(message, callback->serial); if (!dbus->driver->send_message(dbus, message)) { message_queue_destroy(callback); return false; } header = _dbus_message_get_header(message, &header_size); body = _dbus_message_get_body(message, &body_size); l_util_hexdump_two(false, header, header_size, body, body_size, dbus->debug_handler, dbus->debug_data); if (callback->callback == NULL) { message_queue_destroy(callback); goto done; } l_hashmap_insert(dbus->message_list, L_UINT_TO_PTR(callback->serial), callback); done: if (l_queue_isempty(dbus->message_queue)) return false; /* Only continue sending messages if the connection is ready */ return dbus->is_ready; } static void handle_method_return(struct l_dbus *dbus, struct l_dbus_message *message) { struct message_callback *callback; uint32_t reply_serial; reply_serial = _dbus_message_get_reply_serial(message); if (reply_serial == 0) return; callback = l_hashmap_remove(dbus->message_list, L_UINT_TO_PTR(reply_serial)); if (!callback) return; if (callback->callback) callback->callback(message, callback->user_data); message_queue_destroy(callback); } static void handle_error(struct l_dbus *dbus, struct l_dbus_message *message) { struct message_callback *callback; uint32_t reply_serial; reply_serial = _dbus_message_get_reply_serial(message); if (reply_serial == 0) return; callback = l_hashmap_remove(dbus->message_list, L_UINT_TO_PTR(reply_serial)); if (!callback) return; if (callback->callback) callback->callback(message, callback->user_data); message_queue_destroy(callback); } static void process_signal(const void *key, void *value, void *user_data) { struct signal_callback *callback = value; struct l_dbus_message *message = user_data; if (callback->callback) callback->callback(message, callback->user_data); } static void handle_signal(struct l_dbus *dbus, struct l_dbus_message *message) { l_hashmap_foreach(dbus->signal_list, process_signal, message); } static bool message_read_handler(struct l_io *io, void *user_data) { struct l_dbus *dbus = user_data; struct l_dbus_message *message; const void *header, *body; size_t header_size, body_size; enum dbus_message_type msgtype; message = dbus->driver->recv_message(dbus); if (!message) return true; header = _dbus_message_get_header(message, &header_size); body = _dbus_message_get_body(message, &body_size); l_util_hexdump_two(true, header, header_size, body, body_size, dbus->debug_handler, dbus->debug_data); msgtype = _dbus_message_get_type(message); switch (msgtype) { case DBUS_MESSAGE_TYPE_METHOD_RETURN: handle_method_return(dbus, message); break; case DBUS_MESSAGE_TYPE_ERROR: handle_error(dbus, message); break; case DBUS_MESSAGE_TYPE_SIGNAL: handle_signal(dbus, message); break; case DBUS_MESSAGE_TYPE_METHOD_CALL: if (!_dbus_object_tree_dispatch(dbus->tree, dbus, message)) { struct l_dbus_message *error; error = l_dbus_message_new_error(message, "org.freedesktop.DBus.Error.NotFound", "No matching method found"); l_dbus_send(dbus, error); } break; } l_dbus_message_unref(message); return true; } static uint32_t send_message(struct l_dbus *dbus, bool priority, struct l_dbus_message *message, l_dbus_message_func_t function, void *user_data, l_dbus_destroy_func_t destroy) { struct message_callback *callback; enum dbus_message_type type; const char *path; type = _dbus_message_get_type(message); if ((type == DBUS_MESSAGE_TYPE_METHOD_RETURN || type == DBUS_MESSAGE_TYPE_ERROR) && _dbus_message_get_reply_serial(message) == 0) { l_dbus_message_unref(message); return 0; } /* Default empty signature for method return messages */ if (type == DBUS_MESSAGE_TYPE_METHOD_RETURN && !l_dbus_message_get_signature(message)) l_dbus_message_set_arguments(message, ""); callback = l_new(struct message_callback, 1); callback->serial = dbus->next_serial++; callback->message = message; callback->callback = function; callback->destroy = destroy; callback->user_data = user_data; if (priority) { l_queue_push_head(dbus->message_queue, callback); l_io_set_write_handler(dbus->io, message_write_handler, dbus, NULL); return callback->serial; } path = l_dbus_message_get_path(message); if (path) _dbus_object_tree_signals_flush(dbus, path); l_queue_push_tail(dbus->message_queue, callback); if (dbus->is_ready) l_io_set_write_handler(dbus->io, message_write_handler, dbus, NULL); return callback->serial; } static void bus_ready(struct l_dbus *dbus) { dbus->is_ready = true; if (dbus->ready_handler) dbus->ready_handler(dbus->ready_data); l_io_set_read_handler(dbus->io, message_read_handler, dbus, NULL); /* Check for messages added before the connection was ready */ if (l_queue_isempty(dbus->message_queue)) return; l_io_set_write_handler(dbus->io, message_write_handler, dbus, NULL); } static void hello_callback(struct l_dbus_message *message, void *user_data) { struct l_dbus *dbus = user_data; const char *signature; const char *unique_name; signature = l_dbus_message_get_signature(message); if (!signature || strcmp(signature, "s")) { close(l_io_get_fd(dbus->io)); return; } if (!l_dbus_message_get_arguments(message, "s", &unique_name)) { close(l_io_get_fd(dbus->io)); return; } dbus->unique_name = l_strdup(unique_name); bus_ready(dbus); } static bool auth_write_handler(struct l_io *io, void *user_data) { struct l_dbus_classic *classic = user_data; struct l_dbus *dbus = &classic->super; ssize_t written, len; int fd; fd = l_io_get_fd(io); if (!classic->auth_command) return false; len = strlen(classic->auth_command); if (!len) return false; written = L_TFR(send(fd, classic->auth_command, len, 0)); if (written < 0) return false; l_util_hexdump(false, classic->auth_command, written, dbus->debug_handler, dbus->debug_data); if (written < len) { memmove(classic->auth_command, classic->auth_command + written, len + 1 - written); return true; } l_free(classic->auth_command); classic->auth_command = NULL; if (classic->auth_state == SETUP_DONE) { struct l_dbus_message *message; if (classic->skip_hello) { bus_ready(dbus); return true; } l_io_set_read_handler(dbus->io, message_read_handler, dbus, NULL); message = l_dbus_message_new_method_call(dbus, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, L_DBUS_INTERFACE_DBUS, "Hello"); l_dbus_message_set_arguments(message, ""); send_message(dbus, true, message, hello_callback, dbus, NULL); return true; } return false; } static bool auth_read_handler(struct l_io *io, void *user_data) { struct l_dbus_classic *classic = user_data; struct l_dbus *dbus = &classic->super; char buffer[64]; char *ptr, *end; ssize_t offset, len; int fd; fd = l_io_get_fd(io); ptr = buffer; offset = 0; while (1) { len = L_TFR(recv(fd, ptr + offset, sizeof(buffer) - offset, MSG_DONTWAIT)); if (len < 0) { if (errno != EAGAIN) return false; break; } offset += len; } ptr = buffer; len = offset; if (!ptr || len < 3) return true; end = strstr(ptr, "\r\n"); if (!end) return true; if (end - ptr + 2 != len) return true; l_util_hexdump(true, ptr, len, dbus->debug_handler, dbus->debug_data); *end = '\0'; switch (classic->auth_state) { case WAITING_FOR_OK: if (!strncmp(ptr, "OK ", 3)) { enum auth_state state; const char *command; if (dbus->negotiate_unix_fd) { command = "NEGOTIATE_UNIX_FD\r\n"; state = WAITING_FOR_AGREE_UNIX_FD; } else { command = "BEGIN\r\n"; state = SETUP_DONE; } l_free(dbus->guid); dbus->guid = l_strdup(ptr + 3); classic->auth_command = l_strdup(command); classic->auth_state = state; break; } else if (!strncmp(ptr, "REJECTED ", 9)) { static const char *command = "AUTH ANONYMOUS\r\n"; dbus->negotiate_unix_fd = true; classic->auth_command = l_strdup(command); classic->auth_state = WAITING_FOR_OK; } break; case WAITING_FOR_AGREE_UNIX_FD: if (!strncmp(ptr, "AGREE_UNIX_FD", 13)) { static const char *command = "BEGIN\r\n"; dbus->support_unix_fd = true; classic->auth_command = l_strdup(command); classic->auth_state = SETUP_DONE; break; } else if (!strncmp(ptr, "ERROR", 5)) { static const char *command = "BEGIN\r\n"; dbus->support_unix_fd = false; classic->auth_command = l_strdup(command); classic->auth_state = SETUP_DONE; break; } break; case SETUP_DONE: break; } l_io_set_write_handler(io, auth_write_handler, dbus, NULL); return true; } static void disconnect_handler(struct l_io *io, void *user_data) { struct l_dbus *dbus = user_data; dbus->is_ready = false; l_util_debug(dbus->debug_handler, dbus->debug_data, "disconnect"); if (dbus->disconnect_handler) dbus->disconnect_handler(dbus->disconnect_data); } static void dbus_init(struct l_dbus *dbus, int fd) { dbus->io = l_io_new(fd); l_io_set_close_on_destroy(dbus->io, true); l_io_set_disconnect_handler(dbus->io, disconnect_handler, dbus, NULL); dbus->is_ready = false; dbus->next_id = 1; dbus->next_serial = 1; dbus->message_queue = l_queue_new(); dbus->message_list = l_hashmap_new(); dbus->signal_list = l_hashmap_new(); dbus->tree = _dbus_object_tree_new(); } static void classic_free(struct l_dbus *dbus) { struct l_dbus_classic *classic = l_container_of(dbus, struct l_dbus_classic, super); unsigned int i; for (i = 0; i < classic->num_fds; i++) close(classic->fd_buf[i]); l_free(classic->fd_buf); l_free(classic->auth_command); l_hashmap_destroy(classic->match_strings, l_free); l_free(classic); } static bool classic_send_message(struct l_dbus *dbus, struct l_dbus_message *message) { int fd = l_io_get_fd(dbus->io); struct msghdr msg; struct iovec iov[2], *iovpos; ssize_t r; int *fds = NULL; uint32_t num_fds = 0; struct cmsghdr *cmsg; int iovlen; iov[0].iov_base = _dbus_message_get_header(message, &iov[0].iov_len); iov[1].iov_base = _dbus_message_get_body(message, &iov[1].iov_len); if (dbus->support_unix_fd) fds = _dbus_message_get_fds(message, &num_fds); iovpos = iov; iovlen = 2; while (1) { memset(&msg, 0, sizeof(msg)); msg.msg_iov = iovpos; msg.msg_iovlen = iovlen; if (num_fds) { msg.msg_control = alloca(CMSG_SPACE(num_fds * sizeof(int))); msg.msg_controllen = CMSG_LEN(num_fds * sizeof(int)); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = msg.msg_controllen; cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; memcpy(CMSG_DATA(cmsg), fds, num_fds * sizeof(int)); } r = L_TFR(sendmsg(fd, &msg, 0)); if (r < 0) return false; while ((size_t) r >= iovpos->iov_len) { r -= iovpos->iov_len; iovpos++; iovlen--; if (!iovlen) break; } if (!iovlen) break; iovpos->iov_base += r; iovpos->iov_len -= r; /* The FDs have been transmitted, don't retransmit */ num_fds = 0; } return true; } static struct l_dbus_message *classic_recv_message(struct l_dbus *dbus) { struct l_dbus_classic *classic = l_container_of(dbus, struct l_dbus_classic, super); int fd = l_io_get_fd(dbus->io); struct dbus_header hdr; struct msghdr msg; struct iovec iov[2], *iovpos; struct cmsghdr *cmsg; ssize_t len, r; void *header, *body; size_t header_size, body_size; union { uint8_t bytes[CMSG_SPACE(16 * sizeof(int))]; struct cmsghdr align; } fd_buf; int *fds = NULL; uint32_t num_fds = 0; int iovlen; struct l_dbus_message *message; unsigned int i; len = recv(fd, &hdr, DBUS_HEADER_SIZE, MSG_PEEK | MSG_DONTWAIT); if (len != DBUS_HEADER_SIZE) return NULL; header_size = align_len(DBUS_HEADER_SIZE + hdr.dbus1.field_length, 8); header = l_malloc(header_size); body_size = hdr.dbus1.body_length; body = l_malloc(body_size); iov[0].iov_base = header; iov[0].iov_len = header_size; iov[1].iov_base = body; iov[1].iov_len = body_size; iovpos = iov; iovlen = 2; while (1) { memset(&msg, 0, sizeof(msg)); msg.msg_iov = iovpos; msg.msg_iovlen = iovlen; msg.msg_control = &fd_buf; msg.msg_controllen = sizeof(fd_buf); r = L_TFR(recvmsg(fd, &msg, MSG_CMSG_CLOEXEC | MSG_WAITALL)); if (r < 0) goto cmsg_fail; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) continue; num_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); fds = (void *) CMSG_DATA(cmsg); /* Set FD_CLOEXEC on all file descriptors */ for (i = 0; i < num_fds; i++) { long flags; flags = fcntl(fds[i], F_GETFD, NULL); if (flags < 0) continue; if (!(flags & FD_CLOEXEC)) fcntl(fds[i], F_SETFD, flags | FD_CLOEXEC); } classic->fd_buf = l_realloc(classic->fd_buf, (classic->num_fds + num_fds) * sizeof(int)); memcpy(classic->fd_buf + classic->num_fds, fds, num_fds * sizeof(int)); classic->num_fds += num_fds; } while ((size_t) r >= iovpos->iov_len) { r -= iovpos->iov_len; iovpos++; iovlen--; if (!iovlen) break; } if (!iovlen) break; iovpos->iov_base += r; iovpos->iov_len -= r; } if (hdr.endian != DBUS_NATIVE_ENDIAN) { l_util_debug(dbus->debug_handler, dbus->debug_data, "Endianness incorrect"); goto bad_msg; } if (hdr.version != 1) { l_util_debug(dbus->debug_handler, dbus->debug_data, "Protocol version incorrect"); goto bad_msg; } num_fds = _dbus_message_unix_fds_from_header(header, header_size); if (num_fds > classic->num_fds) goto bad_msg; message = dbus_message_build(header, header_size, body, body_size, classic->fd_buf, num_fds); if (message && num_fds) { if (classic->num_fds > num_fds) { memmove(classic->fd_buf, classic->fd_buf + num_fds, (classic->num_fds - num_fds) * sizeof(int)); classic->num_fds -= num_fds; } else { l_free(classic->fd_buf); classic->fd_buf = NULL; classic->num_fds = 0; } } if (message) return message; bad_msg: cmsg_fail: for (i = 0; i < classic->num_fds; i++) close(classic->fd_buf[i]); l_free(classic->fd_buf); classic->fd_buf = NULL; classic->num_fds = 0; l_free(header); l_free(body); return NULL; } static bool classic_add_match(struct l_dbus *dbus, unsigned int id, const struct _dbus_filter_condition *rule, int rule_len) { struct l_dbus_classic *classic = l_container_of(dbus, struct l_dbus_classic, super); char *match_str; struct l_dbus_message *message; match_str = _dbus_filter_rule_to_str(rule, rule_len); l_hashmap_insert(classic->match_strings, L_UINT_TO_PTR(id), match_str); message = l_dbus_message_new_method_call(dbus, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, L_DBUS_INTERFACE_DBUS, "AddMatch"); l_dbus_message_set_arguments(message, "s", match_str); send_message(dbus, false, message, NULL, NULL, NULL); return true; } static bool classic_remove_match(struct l_dbus *dbus, unsigned int id) { struct l_dbus_classic *classic = l_container_of(dbus, struct l_dbus_classic, super); char *match_str = l_hashmap_remove(classic->match_strings, L_UINT_TO_PTR(id)); struct l_dbus_message *message; if (!match_str) return false; message = l_dbus_message_new_method_call(dbus, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, L_DBUS_INTERFACE_DBUS, "RemoveMatch"); l_dbus_message_set_arguments(message, "s", match_str); send_message(dbus, false, message, NULL, NULL, NULL); l_free(match_str); return true; } static void name_owner_changed_cb(struct l_dbus_message *message, void *user_data) { struct l_dbus *dbus = user_data; char *name, *old, *new; if (!l_dbus_message_get_arguments(message, "sss", &name, &old, &new)) return; _dbus_name_cache_notify(dbus->name_cache, name, new); } struct get_name_owner_request { struct l_dbus_message *message; struct l_dbus *dbus; }; static void get_name_owner_reply_cb(struct l_dbus_message *reply, void *user_data) { struct get_name_owner_request *req = user_data; const char *name, *owner; /* No name owner yet */ if (l_dbus_message_is_error(reply)) return; /* Shouldn't happen */ if (!l_dbus_message_get_arguments(reply, "s", &owner)) return; /* Shouldn't happen */ if (!l_dbus_message_get_arguments(req->message, "s", &name)) return; _dbus_name_cache_notify(req->dbus->name_cache, name, owner); } static bool classic_get_name_owner(struct l_dbus *bus, const char *name) { struct get_name_owner_request *req; /* Name resolution is not performed for DBUS_SERVICE_DBUS */ if (!strcmp(name, DBUS_SERVICE_DBUS)) return false; req = l_new(struct get_name_owner_request, 1); req->dbus = bus; req->message = l_dbus_message_new_method_call(bus, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, L_DBUS_INTERFACE_DBUS, "GetNameOwner"); l_dbus_message_set_arguments(req->message, "s", name); send_message(bus, false, req->message, get_name_owner_reply_cb, req, l_free); if (!bus->name_notify_enabled) { static struct _dbus_filter_condition rule[] = { { L_DBUS_MATCH_TYPE, "signal" }, { L_DBUS_MATCH_SENDER, DBUS_SERVICE_DBUS }, { L_DBUS_MATCH_PATH, DBUS_PATH_DBUS }, { L_DBUS_MATCH_INTERFACE, L_DBUS_INTERFACE_DBUS }, { L_DBUS_MATCH_MEMBER, "NameOwnerChanged" }, }; if (!bus->filter) bus->filter = _dbus_filter_new(bus, &bus->driver->filter_ops, bus->name_cache); _dbus_filter_add_rule(bus->filter, rule, L_ARRAY_SIZE(rule), name_owner_changed_cb, bus); bus->name_notify_enabled = true; } return true; } struct name_request { l_dbus_name_acquire_func_t callback; void *user_data; struct l_dbus *dbus; }; enum dbus_name_flag { DBUS_NAME_FLAG_ALLOW_REPLACEMENT = 0x1, DBUS_NAME_FLAG_REPLACE_EXISTING = 0x2, DBUS_NAME_FLAG_DO_NOT_QUEUE = 0x4, }; enum dbus_name_reply { DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER = 1, DBUS_REQUEST_NAME_REPLY_IN_QUEUE = 2, DBUS_REQUEST_NAME_REPLY_EXISTS = 3, DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER = 4, }; static void request_name_reply_cb(struct l_dbus_message *reply, void *user_data) { struct name_request *req = user_data; bool success = false, queued = false; uint32_t retval; if (!req->callback) return; /* No name owner yet */ if (l_dbus_message_is_error(reply)) goto call_back; /* Shouldn't happen */ if (!l_dbus_message_get_arguments(reply, "u", &retval)) goto call_back; success = (retval == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) || (retval == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) || (retval == DBUS_REQUEST_NAME_REPLY_IN_QUEUE); queued = (retval == DBUS_REQUEST_NAME_REPLY_IN_QUEUE); call_back: req->callback(req->dbus, success, queued, req->user_data); } static uint32_t classic_name_acquire(struct l_dbus *dbus, const char *name, bool allow_replacement, bool replace_existing, bool queue, l_dbus_name_acquire_func_t callback, void *user_data) { struct name_request *req; struct l_dbus_message *message; uint32_t flags = 0; req = l_new(struct name_request, 1); req->dbus = dbus; req->user_data = user_data; req->callback = callback; message = l_dbus_message_new_method_call(dbus, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, L_DBUS_INTERFACE_DBUS, "RequestName"); if (allow_replacement) flags |= DBUS_NAME_FLAG_ALLOW_REPLACEMENT; if (replace_existing) flags |= DBUS_NAME_FLAG_REPLACE_EXISTING; if (!queue) flags |= DBUS_NAME_FLAG_DO_NOT_QUEUE; l_dbus_message_set_arguments(message, "su", name, flags); return send_message(dbus, false, message, request_name_reply_cb, req, free); } static const struct l_dbus_ops classic_ops = { .version = 1, .send_message = classic_send_message, .recv_message = classic_recv_message, .free = classic_free, .name_ops = { .get_name_owner = classic_get_name_owner, }, .filter_ops = { .add_match = classic_add_match, .remove_match = classic_remove_match, }, .name_acquire = classic_name_acquire, }; static struct l_dbus *setup_dbus1(int fd, const char *guid, bool skip_hello) { static const unsigned char creds = 0x00; char uid[6], hexuid[12], *ptr = hexuid; struct l_dbus *dbus; struct l_dbus_classic *classic; ssize_t written; unsigned int i; if (snprintf(uid, sizeof(uid), "%d", geteuid()) < 1) { close(fd); return NULL; } for (i = 0; i < strlen(uid); i++) ptr += sprintf(ptr, "%02x", uid[i]); /* Send special credentials-passing nul byte */ written = L_TFR(send(fd, &creds, 1, 0)); if (written < 1) { close(fd); return NULL; } classic = l_new(struct l_dbus_classic, 1); dbus = &classic->super; dbus->driver = &classic_ops; classic->match_strings = l_hashmap_new(); dbus_init(dbus, fd); dbus->guid = l_strdup(guid); classic->auth_command = l_strdup_printf("AUTH EXTERNAL %s\r\n", hexuid); classic->auth_state = WAITING_FOR_OK; classic->skip_hello = skip_hello; dbus->negotiate_unix_fd = true; dbus->support_unix_fd = false; l_io_set_read_handler(dbus->io, auth_read_handler, dbus, NULL); l_io_set_write_handler(dbus->io, auth_write_handler, dbus, NULL); return dbus; } static struct l_dbus *setup_unix(char *params) { char *path = NULL, *guid = NULL; bool abstract = false; struct sockaddr_un addr; size_t len; int fd; while (params) { char *key = strsep(¶ms, ","); char *value; if (!key) break; value = strchr(key, '='); if (!value) continue; *value++ = '\0'; if (!strcmp(key, "path")) { path = value; abstract = false; } else if (!strcmp(key, "abstract")) { path = value; abstract = true; } else if (!strcmp(key, "guid")) guid = value; } if (!path) return NULL; fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); if (fd < 0) return NULL; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; len = strlen(path); if (abstract) { if (len > sizeof(addr.sun_path) - 1) { close(fd); return NULL; } addr.sun_path[0] = '\0'; strncpy(addr.sun_path + 1, path, sizeof(addr.sun_path) - 2); len++; } else { if (len > sizeof(addr.sun_path)) { close(fd); return NULL; } strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); } if (connect(fd, (struct sockaddr *) &addr, sizeof(addr.sun_family) + len) < 0) { close(fd); return NULL; } return setup_dbus1(fd, guid, false); } static bool setup_tcp_cb(struct l_io *io, void *user_data) { static const unsigned char creds = 0x00; struct l_dbus *dbus = user_data; struct l_dbus_classic *classic; ssize_t written; int fd = l_io_get_fd(io); /* Send special credentials-passing nul byte */ written = L_TFR(send(fd, &creds, 1, 0)); if (written < 1) { l_util_debug(dbus->debug_handler, dbus->debug_handler, "error writing NUL byte"); close(fd); return false; } dbus->driver = &classic_ops; dbus->negotiate_unix_fd = false; dbus->support_unix_fd = false; classic = l_container_of(dbus, struct l_dbus_classic, super); classic->match_strings = l_hashmap_new(); classic->auth_command = l_strdup("AUTH ANONYMOUS\r\n"); classic->auth_state = WAITING_FOR_OK; l_io_set_read_handler(dbus->io, auth_read_handler, dbus, NULL); l_io_set_write_handler(dbus->io, auth_write_handler, dbus, NULL); return auth_write_handler(dbus->io, dbus); } static struct l_dbus *setup_tcp(char *params) { char *host = NULL; char *port = NULL; char *family = NULL; struct addrinfo hints = { 0 }; struct addrinfo *res; struct addrinfo *iter; struct l_dbus *dbus = NULL; while (params) { char *key = strsep(¶ms, ","); char *value; value = strchr(key, '='); if (!value) continue; *value++ = '\0'; if (!strcmp(key, "host")) host = value; else if (!strcmp(key, "port")) port = value; else if (!strcmp(key, "family")) family = value; } if (!host || !port) return NULL; if (!family) hints.ai_family = AF_UNSPEC; else if (!strcmp(family, "ipv4")) hints.ai_family = AF_INET; else if (!strcmp(family, "ipv6")) hints.ai_family = AF_INET6; else return NULL; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; hints.ai_protocol = IPPROTO_TCP; if (getaddrinfo(host, port, &hints, &res) != 0) return NULL; for (iter = res; iter; iter = iter->ai_next) { int fd; struct l_dbus_classic *classic; fd = socket(iter->ai_family, iter->ai_socktype | SOCK_NONBLOCK, iter->ai_protocol); if (fd < 0) continue; if (connect(fd, iter->ai_addr, iter->ai_addrlen) < 0) { if (errno != EINPROGRESS) { close(fd); continue; } } classic = l_new(struct l_dbus_classic, 1); dbus = &classic->super; dbus_init(dbus, fd); l_io_set_write_handler(dbus->io, setup_tcp_cb, dbus, NULL); break; } freeaddrinfo(res); return dbus; } static struct l_dbus *setup_address(const char *address) { struct l_dbus *dbus = NULL; char *address_copy; address_copy = strdupa(address); while (address_copy) { char *transport = strsep(&address_copy, ";"); char *params; if (!transport) break; params = strchr(transport, ':'); if (params) *params++ = '\0'; if (!strcmp(transport, "unix")) { /* Function will modify params string */ dbus = setup_unix(params); break; } else if (!strcmp(transport, "tcp")) { dbus = setup_tcp(params); break; } } return dbus; } LIB_EXPORT struct l_dbus *l_dbus_new(const char *address) { if (unlikely(!address)) return NULL; return setup_address(address); } LIB_EXPORT struct l_dbus *l_dbus_new_default(enum l_dbus_bus bus) { const char *address; switch (bus) { case L_DBUS_SYSTEM_BUS: address = getenv("DBUS_SYSTEM_BUS_ADDRESS"); if (!address) address = DEFAULT_SYSTEM_BUS_ADDRESS; break; case L_DBUS_SESSION_BUS: address = getenv("DBUS_SESSION_BUS_ADDRESS"); if (!address) return NULL; break; default: return NULL; } return setup_address(address); } LIB_EXPORT struct l_dbus *l_dbus_new_private(int fd) { return setup_dbus1(fd, NULL, true); } LIB_EXPORT void l_dbus_destroy(struct l_dbus *dbus) { if (unlikely(!dbus)) return; if (dbus->ready_destroy) dbus->ready_destroy(dbus->ready_data); _dbus_filter_free(dbus->filter); _dbus_name_cache_free(dbus->name_cache); l_hashmap_destroy(dbus->signal_list, signal_list_destroy); l_hashmap_destroy(dbus->message_list, message_list_destroy); l_queue_destroy(dbus->message_queue, message_queue_destroy); l_io_destroy(dbus->io); if (dbus->disconnect_destroy) dbus->disconnect_destroy(dbus->disconnect_data); if (dbus->debug_destroy) dbus->debug_destroy(dbus->debug_data); l_free(dbus->guid); l_free(dbus->unique_name); _dbus_object_tree_free(dbus->tree); dbus->driver->free(dbus); } LIB_EXPORT bool l_dbus_set_ready_handler(struct l_dbus *dbus, l_dbus_ready_func_t function, void *user_data, l_dbus_destroy_func_t destroy) { if (unlikely(!dbus)) return false; if (dbus->ready_destroy) dbus->ready_destroy(dbus->ready_data); dbus->ready_handler = function; dbus->ready_destroy = destroy; dbus->ready_data = user_data; return true; } LIB_EXPORT bool l_dbus_set_disconnect_handler(struct l_dbus *dbus, l_dbus_disconnect_func_t function, void *user_data, l_dbus_destroy_func_t destroy) { if (unlikely(!dbus)) return false; if (dbus->disconnect_destroy) dbus->disconnect_destroy(dbus->disconnect_data); dbus->disconnect_handler = function; dbus->disconnect_destroy = destroy; dbus->disconnect_data = user_data; return true; } LIB_EXPORT bool l_dbus_set_debug(struct l_dbus *dbus, l_dbus_debug_func_t function, void *user_data, l_dbus_destroy_func_t destroy) { if (unlikely(!dbus)) return false; if (dbus->debug_destroy) dbus->debug_destroy(dbus->debug_data); dbus->debug_handler = function; dbus->debug_destroy = destroy; dbus->debug_data = user_data; /* l_io_set_debug(dbus->io, function, user_data, NULL); */ return true; } LIB_EXPORT uint32_t l_dbus_send_with_reply(struct l_dbus *dbus, struct l_dbus_message *message, l_dbus_message_func_t function, void *user_data, l_dbus_destroy_func_t destroy) { if (unlikely(!dbus || !message)) return 0; return send_message(dbus, false, message, function, user_data, destroy); } LIB_EXPORT uint32_t l_dbus_send(struct l_dbus *dbus, struct l_dbus_message *message) { if (unlikely(!dbus || !message)) return 0; return send_message(dbus, false, message, NULL, NULL, NULL); } static bool remove_entry(void *data, void *user_data) { struct message_callback *callback = data; uint32_t serial = L_PTR_TO_UINT(user_data); if (callback->serial == serial) { message_queue_destroy(callback); return true; } return false; } LIB_EXPORT bool l_dbus_cancel(struct l_dbus *dbus, uint32_t serial) { struct message_callback *callback; unsigned int count; if (unlikely(!dbus || !serial)) return false; callback = l_hashmap_remove(dbus->message_list, L_UINT_TO_PTR(serial)); if (callback) { message_queue_destroy(callback); return true; } count = l_queue_foreach_remove(dbus->message_queue, remove_entry, L_UINT_TO_PTR(serial)); if (!count) return false; return true; } LIB_EXPORT unsigned int l_dbus_register(struct l_dbus *dbus, l_dbus_message_func_t function, void *user_data, l_dbus_destroy_func_t destroy) { struct signal_callback *callback; if (unlikely(!dbus)) return 0; callback = l_new(struct signal_callback, 1); callback->id = dbus->next_id++; callback->callback = function; callback->destroy = destroy; callback->user_data = user_data; l_hashmap_insert(dbus->signal_list, L_UINT_TO_PTR(callback->id), callback); return callback->id; } LIB_EXPORT bool l_dbus_unregister(struct l_dbus *dbus, unsigned int id) { struct signal_callback *callback; if (unlikely(!dbus || !id)) return false; callback = l_hashmap_remove(dbus->signal_list, L_UINT_TO_PTR(id)); if (!callback) return false; signal_list_destroy(callback); return true; } LIB_EXPORT uint32_t l_dbus_method_call(struct l_dbus *dbus, const char *destination, const char *path, const char *interface, const char *method, l_dbus_message_func_t setup, l_dbus_message_func_t function, void *user_data, l_dbus_destroy_func_t destroy) { struct l_dbus_message *message; if (unlikely(!dbus)) return 0; message = l_dbus_message_new_method_call(dbus, destination, path, interface, method); if (setup) setup(message, user_data); else l_dbus_message_set_arguments(message, ""); return send_message(dbus, false, message, function, user_data, destroy); } uint8_t _dbus_get_version(struct l_dbus *dbus) { return dbus->driver->version; } int _dbus_get_fd(struct l_dbus *dbus) { return l_io_get_fd(dbus->io); } struct _dbus_object_tree *_dbus_get_tree(struct l_dbus *dbus) { return dbus->tree; } /** * l_dbus_register_interface: * @dbus: D-Bus connection as returned by @l_dbus_new* * @interface: interface name string * @setup_func: function that sets up the methods, signals and properties by * using the #dbus-service.h API. * @destroy: optional destructor to be called every time an instance of this * interface is being removed from an object on this bus. * @handle_old_style_properties: whether to automatically handle SetProperty and * GetProperties for any properties registered by * @setup_func. * * Registers an interface. If successful the interface can then be added * to any number of objects with @l_dbus_object_add_interface. * * Returns: whether the interface was successfully registered **/ LIB_EXPORT bool l_dbus_register_interface(struct l_dbus *dbus, const char *interface, l_dbus_interface_setup_func_t setup_func, l_dbus_destroy_func_t destroy, bool handle_old_style_properties) { if (unlikely(!dbus)) return false; if (unlikely(!dbus->tree)) return false; return _dbus_object_tree_register_interface(dbus->tree, interface, setup_func, destroy, handle_old_style_properties); } LIB_EXPORT bool l_dbus_unregister_interface(struct l_dbus *dbus, const char *interface) { if (unlikely(!dbus)) return false; if (unlikely(!dbus->tree)) return false; return _dbus_object_tree_unregister_interface(dbus->tree, interface); } /** * l_dbus_register_object: * @dbus: D-Bus connection * @path: new object path * @user_data: user pointer to be passed to @destroy if any * @destroy: optional destructor to be called when object dropped from the tree * @...: NULL-terminated list of 0 or more interfaces to be present on the * object from the moment of creation. For every interface the interface * name string is expected followed by the @user_data pointer same as * would be passed as @l_dbus_object_add_interface's last two parameters. * * Create a new D-Bus object on the tree visible to D-Bus peers. For example: * success = l_dbus_register_object(bus, "/org/example/ExampleManager", * NULL, NULL, * "org.example.Manager", * manager_data, * NULL); * * Returns: whether the object path was successfully registered **/ LIB_EXPORT bool l_dbus_register_object(struct l_dbus *dbus, const char *path, void *user_data, l_dbus_destroy_func_t destroy, ...) { va_list args; const char *interface; void *if_user_data; bool r = true; if (unlikely(!dbus)) return false; if (unlikely(!dbus->tree)) return false; if (!_dbus_object_tree_new_object(dbus->tree, path, user_data, destroy)) return false; va_start(args, destroy); while ((interface = va_arg(args, const char *))) { if_user_data = va_arg(args, void *); if (!_dbus_object_tree_add_interface(dbus->tree, path, interface, if_user_data)) { _dbus_object_tree_object_destroy(dbus->tree, path); r = false; break; } } va_end(args); return r; } LIB_EXPORT bool l_dbus_unregister_object(struct l_dbus *dbus, const char *object) { if (unlikely(!dbus)) return false; if (unlikely(!dbus->tree)) return false; return _dbus_object_tree_object_destroy(dbus->tree, object); } /** * l_dbus_object_add_interface: * @dbus: D-Bus connection * @object: object path as passed to @l_dbus_register_object * @interface: interface name as passed to @l_dbus_register_interface * @user_data: user data pointer to be passed to any method and property * callbacks provided by the @setup_func and to the @destroy * callback as passed to @l_dbus_register_interface * * Creates an instance of given interface at the given path in the * connection's object tree. If no object was registered at this path * before @l_dbus_register_object gets called automatically. * * The addition of an interface to the object may trigger a query of * all the properties on this interface and * #org.freedesktop.DBus.ObjectManager.InterfacesAdded signals. * * Returns: whether the interface was successfully added. **/ LIB_EXPORT bool l_dbus_object_add_interface(struct l_dbus *dbus, const char *object, const char *interface, void *user_data) { if (unlikely(!dbus)) return false; if (unlikely(!dbus->tree)) return false; return _dbus_object_tree_add_interface(dbus->tree, object, interface, user_data); } LIB_EXPORT bool l_dbus_object_remove_interface(struct l_dbus *dbus, const char *object, const char *interface) { if (unlikely(!dbus)) return false; if (unlikely(!dbus->tree)) return false; return _dbus_object_tree_remove_interface(dbus->tree, object, interface); } LIB_EXPORT void *l_dbus_object_get_data(struct l_dbus *dbus, const char *object, const char *interface) { if (unlikely(!dbus)) return NULL; if (unlikely(!dbus->tree)) return NULL; return _dbus_object_tree_get_interface_data(dbus->tree, object, interface); } LIB_EXPORT bool l_dbus_object_set_data(struct l_dbus *dbus, const char *object, const char *interface, void *user_data) { if (unlikely(!dbus)) return false; if (unlikely(!dbus->tree)) return false; return _dbus_object_tree_set_interface_data(dbus->tree, object, interface, user_data); } LIB_EXPORT bool l_dbus_object_manager_enable(struct l_dbus *dbus, const char *root) { if (unlikely(!dbus)) return false; if (unlikely(!dbus->tree)) return false; return _dbus_object_tree_add_interface(dbus->tree, root, L_DBUS_INTERFACE_OBJECT_MANAGER, dbus); } LIB_EXPORT unsigned int l_dbus_add_disconnect_watch(struct l_dbus *dbus, const char *name, l_dbus_watch_func_t disconnect_func, void *user_data, l_dbus_destroy_func_t destroy) { return l_dbus_add_service_watch(dbus, name, NULL, disconnect_func, user_data, destroy); } LIB_EXPORT unsigned int l_dbus_add_service_watch(struct l_dbus *dbus, const char *name, l_dbus_watch_func_t connect_func, l_dbus_watch_func_t disconnect_func, void *user_data, l_dbus_destroy_func_t destroy) { if (!name) return 0; if (!dbus->name_cache) dbus->name_cache = _dbus_name_cache_new(dbus, &dbus->driver->name_ops); return _dbus_name_cache_add_watch(dbus->name_cache, name, connect_func, disconnect_func, user_data, destroy); } LIB_EXPORT bool l_dbus_remove_watch(struct l_dbus *dbus, unsigned int id) { if (!dbus->name_cache) return false; return _dbus_name_cache_remove_watch(dbus->name_cache, id); } /** * l_dbus_add_signal_watch: * @dbus: D-Bus connection * @sender: bus name to match the signal sender against or NULL to * match any sender * @path: object path to match the signal path against or NULL to * match any path * @interface: interface name to match the signal interface against * or NULL to match any interface * @member: name to match the signal name against or NULL to match any * signal * @...: a list of further conditions to be met by the signal followed * by three more mandatory parameters: * enum l_dbus_match_type list_end_marker, * l_dbus_message_func callback, * void *user_data, * The value L_DBUS_MATCH_NONE must be passed as the end of list * marker, followed by the signal match callback and user_data. * In the list, every condition is a pair of parameters: * enum l_dbus_match_type match_type, const char *value. * * Subscribe to a group of signals based on a set of conditions that * compare the signal's header fields and string arguments against given * values. For example: * signal_id = l_dbus_add_signal_watch(bus, "org.example", "/" * "org.example.Manager", * "PropertyChanged", * L_DBUS_MATCH_ARGUMENT(0), * "ExampleProperty", * L_DBUS_MATCH_NONE * manager_property_change_cb, * NULL); * * Returns: a non-zero signal filter identifier that can be passed to * l_dbus_remove_signal_watch to remove this filter rule, or * zero on failure. **/ LIB_EXPORT unsigned int l_dbus_add_signal_watch(struct l_dbus *dbus, const char *sender, const char *path, const char *interface, const char *member, ...) { struct _dbus_filter_condition *rule; int rule_len; va_list args; const char *value; l_dbus_message_func_t signal_func; enum l_dbus_match_type type; void *user_data; unsigned int id; va_start(args, member); rule_len = 0; while (true) { type = va_arg(args, enum l_dbus_match_type); if (type == L_DBUS_MATCH_NONE) break; va_arg(args, const char *); rule_len++; } va_end(args); rule = l_new(struct _dbus_filter_condition, rule_len + 5); rule_len = 0; rule[rule_len].type = L_DBUS_MATCH_TYPE; rule[rule_len++].value = "signal"; if (sender) { rule[rule_len].type = L_DBUS_MATCH_SENDER; rule[rule_len++].value = sender; } if (path) { rule[rule_len].type = L_DBUS_MATCH_PATH; rule[rule_len++].value = path; } if (interface) { rule[rule_len].type = L_DBUS_MATCH_INTERFACE; rule[rule_len++].value = interface; } if (member) { rule[rule_len].type = L_DBUS_MATCH_MEMBER; rule[rule_len++].value = member; } va_start(args, member); while (true) { type = va_arg(args, enum l_dbus_match_type); if (type == L_DBUS_MATCH_NONE) break; value = va_arg(args, const char *); rule[rule_len].type = type; rule[rule_len++].value = value; } signal_func = va_arg(args, l_dbus_message_func_t); user_data = va_arg(args, void *); va_end(args); if (!dbus->filter) { if (!dbus->name_cache) dbus->name_cache = _dbus_name_cache_new(dbus, &dbus->driver->name_ops); dbus->filter = _dbus_filter_new(dbus, &dbus->driver->filter_ops, dbus->name_cache); } id = _dbus_filter_add_rule(dbus->filter, rule, rule_len, signal_func, user_data); l_free(rule); return id; } LIB_EXPORT bool l_dbus_remove_signal_watch(struct l_dbus *dbus, unsigned int id) { if (!dbus->filter) return false; return _dbus_filter_remove_rule(dbus->filter, id); } /** * l_dbus_name_acquire: * @dbus: D-Bus connection * @name: Well-known bus name to be acquired * @allow_replacement: Whether to allow another peer's name request to * take the name ownership away from this connection * @replace_existing: Whether to allow D-Bus to take the name's ownership * away from another peer in case the name is already * owned and allows replacement. Ignored if name is * currently free. * @queue: Whether to allow the name request to be queued by D-Bus in * case it cannot be acquired now, rather than to return a failure. * @callback: Callback to receive the request result when done. * * Acquire a well-known bus name (service name) on the bus. * * Returns: a non-zero request serial that can be passed to l_dbus_cancel * while waiting for the callback or zero if the callback has * has happened while l_dbus_name_acquire was running. **/ LIB_EXPORT uint32_t l_dbus_name_acquire(struct l_dbus *dbus, const char *name, bool allow_replacement, bool replace_existing, bool queue, l_dbus_name_acquire_func_t callback, void *user_data) { return dbus->driver->name_acquire(dbus, name, allow_replacement, replace_existing, queue, callback, user_data); } bluez-5.87/ell/PaxHeaders/dbus.h0000644000000000000000000000005014661631567013554 xustar0020 atime=1783076232 20 ctime=1783094098 bluez-5.87/ell/dbus.h0000644000000000000000000002235114661631567013240 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_DBUS_H #define __ELL_DBUS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define L_DBUS_INTERFACE_DBUS "org.freedesktop.DBus" #define L_DBUS_INTERFACE_INTROSPECTABLE "org.freedesktop.DBus.Introspectable" #define L_DBUS_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties" #define L_DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" enum l_dbus_bus { L_DBUS_SYSTEM_BUS, L_DBUS_SESSION_BUS, }; enum l_dbus_match_type { L_DBUS_MATCH_NONE = 0, L_DBUS_MATCH_TYPE, L_DBUS_MATCH_SENDER, L_DBUS_MATCH_PATH, L_DBUS_MATCH_INTERFACE, L_DBUS_MATCH_MEMBER, L_DBUS_MATCH_ARG0, }; #define L_DBUS_MATCH_ARGUMENT(i) (L_DBUS_MATCH_ARG0 + (i)) struct l_dbus; struct l_dbus_interface; struct l_dbus_message_builder; typedef void (*l_dbus_ready_func_t) (void *user_data); typedef void (*l_dbus_disconnect_func_t) (void *user_data); typedef void (*l_dbus_debug_func_t) (const char *str, void *user_data); typedef void (*l_dbus_destroy_func_t) (void *user_data); typedef void (*l_dbus_interface_setup_func_t) (struct l_dbus_interface *); typedef void (*l_dbus_watch_func_t) (struct l_dbus *dbus, void *user_data); typedef void (*l_dbus_name_acquire_func_t) (struct l_dbus *dbus, bool success, bool queued, void *user_data); struct l_dbus *l_dbus_new(const char *address); struct l_dbus *l_dbus_new_default(enum l_dbus_bus bus); struct l_dbus *l_dbus_new_private(int fd); void l_dbus_destroy(struct l_dbus *dbus); bool l_dbus_set_ready_handler(struct l_dbus *dbus, l_dbus_ready_func_t function, void *user_data, l_dbus_destroy_func_t destroy); bool l_dbus_set_disconnect_handler(struct l_dbus *dbus, l_dbus_disconnect_func_t function, void *user_data, l_dbus_destroy_func_t destroy); bool l_dbus_set_debug(struct l_dbus *dbus, l_dbus_debug_func_t function, void *user_data, l_dbus_destroy_func_t destroy); struct l_dbus_message; struct l_dbus_message_iter { struct l_dbus_message *message; const char *sig_start; uint8_t sig_len; uint8_t sig_pos; const void *data; size_t len; size_t pos; char container_type; const void *offsets; }; struct l_dbus_message *l_dbus_message_new_method_call(struct l_dbus *dbus, const char *destination, const char *path, const char *interface, const char *method); struct l_dbus_message *l_dbus_message_new_signal(struct l_dbus *dbus, const char *path, const char *interface, const char *name); struct l_dbus_message *l_dbus_message_new_method_return( struct l_dbus_message *method_call); struct l_dbus_message *l_dbus_message_new_error_valist( struct l_dbus_message *method_call, const char *name, const char *format, va_list args) __attribute__((format(printf, 3, 0))); struct l_dbus_message *l_dbus_message_new_error( struct l_dbus_message *method_call, const char *name, const char *format, ...) __attribute__((format(printf, 3, 4))); struct l_dbus_message *l_dbus_message_ref(struct l_dbus_message *message); void l_dbus_message_unref(struct l_dbus_message *message); const char *l_dbus_message_get_path(struct l_dbus_message *message); const char *l_dbus_message_get_interface(struct l_dbus_message *message); const char *l_dbus_message_get_member(struct l_dbus_message *message); const char *l_dbus_message_get_destination(struct l_dbus_message *message); const char *l_dbus_message_get_sender(struct l_dbus_message *message); const char *l_dbus_message_get_signature(struct l_dbus_message *message); bool l_dbus_message_set_no_reply(struct l_dbus_message *message, bool on); bool l_dbus_message_get_no_reply(struct l_dbus_message *message); bool l_dbus_message_set_no_autostart(struct l_dbus_message *message, bool on); bool l_dbus_message_get_no_autostart(struct l_dbus_message *message); typedef void (*l_dbus_message_func_t) (struct l_dbus_message *message, void *user_data); uint32_t l_dbus_send_with_reply(struct l_dbus *dbus, struct l_dbus_message *message, l_dbus_message_func_t function, void *user_data, l_dbus_destroy_func_t destroy); uint32_t l_dbus_send(struct l_dbus *dbus, struct l_dbus_message *message); bool l_dbus_cancel(struct l_dbus *dbus, uint32_t serial); unsigned int l_dbus_register(struct l_dbus *dbus, l_dbus_message_func_t function, void *user_data, l_dbus_destroy_func_t destroy); bool l_dbus_unregister(struct l_dbus *dbus, unsigned int id); uint32_t l_dbus_method_call(struct l_dbus *dbus, const char *destination, const char *path, const char *interface, const char *method, l_dbus_message_func_t setup, l_dbus_message_func_t function, void *user_data, l_dbus_destroy_func_t destroy); bool l_dbus_message_is_error(struct l_dbus_message *message); bool l_dbus_message_get_error(struct l_dbus_message *message, const char **name, const char **text); bool l_dbus_message_get_arguments(struct l_dbus_message *message, const char *signature, ...); bool l_dbus_message_get_arguments_valist(struct l_dbus_message *message, const char *signature, va_list args); bool l_dbus_message_iter_next_entry(struct l_dbus_message_iter *iter, ...); bool l_dbus_message_iter_get_variant(struct l_dbus_message_iter *iter, const char *signature, ...); bool l_dbus_message_iter_get_fixed_array(struct l_dbus_message_iter *iter, void *out, uint32_t *n_elem); bool l_dbus_message_set_arguments(struct l_dbus_message *message, const char *signature, ...); bool l_dbus_message_set_arguments_valist(struct l_dbus_message *message, const char *signature, va_list args); struct l_dbus_message_builder *l_dbus_message_builder_new( struct l_dbus_message *message); void l_dbus_message_builder_destroy(struct l_dbus_message_builder *builder); bool l_dbus_message_builder_append_basic(struct l_dbus_message_builder *builder, char type, const void *value); bool l_dbus_message_builder_enter_container( struct l_dbus_message_builder *builder, char container_type, const char *signature); bool l_dbus_message_builder_leave_container( struct l_dbus_message_builder *builder, char container_type); bool l_dbus_message_builder_enter_struct(struct l_dbus_message_builder *builder, const char *signature); bool l_dbus_message_builder_leave_struct( struct l_dbus_message_builder *builder); bool l_dbus_message_builder_enter_dict(struct l_dbus_message_builder *builder, const char *signature); bool l_dbus_message_builder_leave_dict(struct l_dbus_message_builder *builder); bool l_dbus_message_builder_enter_array(struct l_dbus_message_builder *builder, const char *signature); bool l_dbus_message_builder_leave_array(struct l_dbus_message_builder *builder); bool l_dbus_message_builder_enter_variant( struct l_dbus_message_builder *builder, const char *signature); bool l_dbus_message_builder_leave_variant( struct l_dbus_message_builder *builder); bool l_dbus_message_builder_append_from_iter( struct l_dbus_message_builder *builder, struct l_dbus_message_iter *from); bool l_dbus_message_builder_append_from_valist( struct l_dbus_message_builder *builder, const char *signature, va_list args); struct l_dbus_message *l_dbus_message_builder_finalize( struct l_dbus_message_builder *builder); bool l_dbus_register_interface(struct l_dbus *dbus, const char *interface, l_dbus_interface_setup_func_t setup_func, l_dbus_destroy_func_t destroy, bool handle_old_style_properties); bool l_dbus_unregister_interface(struct l_dbus *dbus, const char *interface); bool l_dbus_register_object(struct l_dbus *dbus, const char *path, void *user_data, l_dbus_destroy_func_t destroy, ...); bool l_dbus_unregister_object(struct l_dbus *dbus, const char *object); bool l_dbus_object_add_interface(struct l_dbus *dbus, const char *object, const char *interface, void *user_data); bool l_dbus_object_remove_interface(struct l_dbus *dbus, const char *object, const char *interface); void *l_dbus_object_get_data(struct l_dbus *dbus, const char *object, const char *interface); bool l_dbus_object_set_data(struct l_dbus *dbus, const char *object, const char *interface, void *user_data); bool l_dbus_object_manager_enable(struct l_dbus *dbus, const char *root); unsigned int l_dbus_add_service_watch(struct l_dbus *dbus, const char *name, l_dbus_watch_func_t connect_func, l_dbus_watch_func_t disconnect_func, void *user_data, l_dbus_destroy_func_t destroy); unsigned int l_dbus_add_disconnect_watch(struct l_dbus *dbus, const char *name, l_dbus_watch_func_t disconnect_func, void *user_data, l_dbus_destroy_func_t destroy); bool l_dbus_remove_watch(struct l_dbus *dbus, unsigned int id); unsigned int l_dbus_add_signal_watch(struct l_dbus *dbus, const char *sender, const char *path, const char *interface, const char *member, ...); bool l_dbus_remove_signal_watch(struct l_dbus *dbus, unsigned int id); uint32_t l_dbus_name_acquire(struct l_dbus *dbus, const char *name, bool allow_replacement, bool replace_existing, bool queue, l_dbus_name_acquire_func_t callback, void *user_data); #ifdef __cplusplus } #endif #endif /* __ELL_DBUS_H */ bluez-5.87/ell/PaxHeaders/cipher.c0000644000000000000000000000005014526443132014051 xustar0020 atime=1783076265 20 ctime=1783094099 bluez-5.87/ell/cipher.c0000644000000000000000000006100714526443132013536 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2015 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "useful.h" #include "cipher.h" #include "private.h" #include "random.h" #include "missing.h" #ifndef HAVE_LINUX_IF_ALG_H #ifndef HAVE_LINUX_TYPES_H typedef uint8_t __u8; typedef uint16_t __u16; typedef uint32_t __u32; #else #include #endif #ifndef AF_ALG #define AF_ALG 38 #define PF_ALG AF_ALG #endif struct sockaddr_alg { __u16 salg_family; __u8 salg_type[14]; __u32 salg_feat; __u32 salg_mask; __u8 salg_name[64]; }; struct af_alg_iv { __u32 ivlen; __u8 iv[0]; }; /* Socket options */ #define ALG_SET_KEY 1 #define ALG_SET_IV 2 #define ALG_SET_OP 3 /* Operations */ #define ALG_OP_DECRYPT 0 #define ALG_OP_ENCRYPT 1 #else #include #endif #ifndef SOL_ALG #define SOL_ALG 279 #endif #ifndef ALG_SET_AEAD_ASSOCLEN #define ALG_SET_AEAD_ASSOCLEN 4 #endif #ifndef ALG_SET_AEAD_AUTHSIZE #define ALG_SET_AEAD_AUTHSIZE 5 #endif #define is_valid_type(type) ((type) <= L_CIPHER_RC2_CBC) static uint32_t supported_ciphers; static uint32_t supported_aead_ciphers; struct l_cipher { int type; const struct local_impl *local; union { int sk; void *local_data; }; }; struct l_aead_cipher { int type; int sk; }; struct local_impl { void *(*cipher_new)(enum l_cipher_type, const void *key, size_t key_length); void (*cipher_free)(void *data); bool (*set_iv)(void *data, const uint8_t *iv, size_t iv_length); ssize_t (*operate)(void *data, __u32 operation, const struct iovec *in, size_t in_cnt, const struct iovec *out, size_t out_cnt); }; static int create_alg(const char *alg_type, const char *alg_name, const void *key, size_t key_length, size_t tag_length) { struct sockaddr_alg salg; int sk; int ret; sk = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); if (sk < 0) return -errno; memset(&salg, 0, sizeof(salg)); salg.salg_family = AF_ALG; strcpy((char *) salg.salg_type, alg_type); strcpy((char *) salg.salg_name, alg_name); if (bind(sk, (struct sockaddr *) &salg, sizeof(salg)) < 0) { close(sk); return -1; } if (setsockopt(sk, SOL_ALG, ALG_SET_KEY, key, key_length) < 0) { close(sk); return -1; } if (tag_length && setsockopt(sk, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, tag_length)) { close(sk); return -1; } ret = accept4(sk, NULL, 0, SOCK_CLOEXEC); close(sk); return ret; } static const char *cipher_type_to_name(enum l_cipher_type type) { switch (type) { case L_CIPHER_AES: return "ecb(aes)"; case L_CIPHER_AES_CBC: return "cbc(aes)"; case L_CIPHER_AES_CTR: return "ctr(aes)"; case L_CIPHER_ARC4: return NULL; case L_CIPHER_DES: return "ecb(des)"; case L_CIPHER_DES_CBC: return "cbc(des)"; case L_CIPHER_DES3_EDE_CBC: return "cbc(des3_ede)"; case L_CIPHER_RC2_CBC: return NULL; } return NULL; } static const struct local_impl local_arc4; static const struct local_impl local_rc2_cbc; static const struct local_impl *local_impl_ciphers[] = { [L_CIPHER_ARC4] = &local_arc4, [L_CIPHER_RC2_CBC] = &local_rc2_cbc, }; #define HAVE_LOCAL_IMPLEMENTATION(type) \ ((type) < L_ARRAY_SIZE(local_impl_ciphers) && \ local_impl_ciphers[(type)]) LIB_EXPORT struct l_cipher *l_cipher_new(enum l_cipher_type type, const void *key, size_t key_length) { struct l_cipher *cipher; const char *alg_name; if (unlikely(!key)) return NULL; if (!is_valid_type(type)) return NULL; cipher = l_new(struct l_cipher, 1); cipher->type = type; alg_name = cipher_type_to_name(type); if (HAVE_LOCAL_IMPLEMENTATION(type)) { cipher->local = local_impl_ciphers[type]; cipher->local_data = cipher->local->cipher_new(type, key, key_length); if (!cipher->local_data) goto error_free; return cipher; } cipher->sk = create_alg("skcipher", alg_name, key, key_length, 0); if (cipher->sk < 0) goto error_free; return cipher; error_free: l_free(cipher); return NULL; } static const char *aead_cipher_type_to_name(enum l_aead_cipher_type type) { switch (type) { case L_AEAD_CIPHER_AES_CCM: return "ccm(aes)"; case L_AEAD_CIPHER_AES_GCM: return "gcm(aes)"; } return NULL; } LIB_EXPORT struct l_aead_cipher *l_aead_cipher_new(enum l_aead_cipher_type type, const void *key, size_t key_length, size_t tag_length) { struct l_aead_cipher *cipher; const char *alg_name; if (unlikely(!key)) return NULL; if (type != L_AEAD_CIPHER_AES_CCM && type != L_AEAD_CIPHER_AES_GCM) return NULL; cipher = l_new(struct l_aead_cipher, 1); cipher->type = type; alg_name = aead_cipher_type_to_name(type); cipher->sk = create_alg("aead", alg_name, key, key_length, tag_length); if (cipher->sk >= 0) return cipher; l_free(cipher); return NULL; } LIB_EXPORT void l_cipher_free(struct l_cipher *cipher) { if (unlikely(!cipher)) return; if (cipher->local) cipher->local->cipher_free(cipher->local_data); else close(cipher->sk); l_free(cipher); } LIB_EXPORT void l_aead_cipher_free(struct l_aead_cipher *cipher) { if (unlikely(!cipher)) return; close(cipher->sk); l_free(cipher); } static ssize_t operate_cipher(int sk, __u32 operation, const void *in, size_t in_len, const void *ad, size_t ad_len, const void *iv, size_t iv_len, void *out, size_t out_len) { char *c_msg_buf; size_t c_msg_size; struct msghdr msg; struct cmsghdr *c_msg; struct iovec iov[2]; ssize_t result; c_msg_size = CMSG_SPACE(sizeof(operation)); c_msg_size += ad_len ? CMSG_SPACE(sizeof(uint32_t)) : 0; c_msg_size += iv_len ? CMSG_SPACE(sizeof(struct af_alg_iv) + iv_len) : 0; c_msg_buf = alloca(c_msg_size); memset(c_msg_buf, 0, c_msg_size); memset(&msg, 0, sizeof(msg)); msg.msg_iov = iov; msg.msg_control = c_msg_buf; msg.msg_controllen = c_msg_size; c_msg = CMSG_FIRSTHDR(&msg); c_msg->cmsg_level = SOL_ALG; c_msg->cmsg_type = ALG_SET_OP; c_msg->cmsg_len = CMSG_LEN(sizeof(operation)); memcpy(CMSG_DATA(c_msg), &operation, sizeof(operation)); if (ad_len) { uint32_t *ad_data; c_msg = CMSG_NXTHDR(&msg, c_msg); c_msg->cmsg_level = SOL_ALG; c_msg->cmsg_type = ALG_SET_AEAD_ASSOCLEN; c_msg->cmsg_len = CMSG_LEN(sizeof(*ad_data)); ad_data = (void *) CMSG_DATA(c_msg); *ad_data = ad_len; iov[0].iov_base = (void *) ad; iov[0].iov_len = ad_len; msg.msg_iovlen = 1; if (in) { iov[1].iov_base = (void *) in; iov[1].iov_len = in_len; msg.msg_iovlen = 2; } } else { iov[0].iov_base = (void *) in; iov[0].iov_len = in_len; msg.msg_iovlen = 1; } if (iv_len) { struct af_alg_iv *algiv; c_msg = CMSG_NXTHDR(&msg, c_msg); c_msg->cmsg_level = SOL_ALG; c_msg->cmsg_type = ALG_SET_IV; c_msg->cmsg_len = CMSG_LEN(sizeof(*algiv) + iv_len); algiv = (void *)CMSG_DATA(c_msg); algiv->ivlen = iv_len; memcpy(algiv->iv, iv, iv_len); } result = sendmsg(sk, &msg, 0); if (result < 0) return -errno; if (ad_len) { /* * When AEAD additional data is passed to sendmsg() for * use in computing the tag, those bytes also appear at * the beginning of the encrypt or decrypt results. Rather * than force the caller to pad their result buffer with * the correct number of bytes for the additional data, * the necessary space is allocated here and then the * duplicate AAD is discarded. */ iov[0].iov_base = l_malloc(ad_len); iov[0].iov_len = ad_len; iov[1].iov_base = (void *) out; iov[1].iov_len = out_len; msg.msg_iovlen = 2; msg.msg_control = NULL; msg.msg_controllen = 0; result = recvmsg(sk, &msg, 0); if (result >= (ssize_t) ad_len) result -= ad_len; else if (result > 0) result = 0; l_free(iov[0].iov_base); } else { result = read(sk, out, out_len); } if (result < 0) return -errno; return result; } static ssize_t operate_cipherv(int sk, __u32 operation, const struct iovec *in, size_t in_cnt, const struct iovec *out, size_t out_cnt) { char *c_msg_buf; size_t c_msg_size; struct msghdr msg; struct cmsghdr *c_msg; ssize_t result; c_msg_size = CMSG_SPACE(sizeof(operation)); c_msg_buf = alloca(c_msg_size); memset(c_msg_buf, 0, c_msg_size); memset(&msg, 0, sizeof(msg)); msg.msg_iov = (struct iovec *) in; msg.msg_iovlen = in_cnt; msg.msg_control = c_msg_buf; msg.msg_controllen = c_msg_size; c_msg = CMSG_FIRSTHDR(&msg); c_msg->cmsg_level = SOL_ALG; c_msg->cmsg_type = ALG_SET_OP; c_msg->cmsg_len = CMSG_LEN(sizeof(operation)); memcpy(CMSG_DATA(c_msg), &operation, sizeof(operation)); result = sendmsg(sk, &msg, 0); if (result < 0) return -errno; result = readv(sk, out, out_cnt); if (result < 0) return -errno; return result; } LIB_EXPORT bool l_cipher_encrypt(struct l_cipher *cipher, const void *in, void *out, size_t len) { if (unlikely(!cipher)) return false; if (unlikely(!in) || unlikely(!out)) return false; if (cipher->local) { struct iovec in_iov = { (void *) in, len }; struct iovec out_iov = { out, len }; return cipher->local->operate(cipher->local_data, ALG_OP_ENCRYPT, &in_iov, 1, &out_iov, 1) >= 0; } return operate_cipher(cipher->sk, ALG_OP_ENCRYPT, in, len, NULL, 0, NULL, 0, out, len) >= 0; } LIB_EXPORT bool l_cipher_encryptv(struct l_cipher *cipher, const struct iovec *in, size_t in_cnt, const struct iovec *out, size_t out_cnt) { if (unlikely(!cipher)) return false; if (unlikely(!in) || unlikely(!out)) return false; if (cipher->local) return cipher->local->operate(cipher->local_data, ALG_OP_ENCRYPT, in, in_cnt, out, out_cnt) >= 0; return operate_cipherv(cipher->sk, ALG_OP_ENCRYPT, in, in_cnt, out, out_cnt) >= 0; } LIB_EXPORT bool l_cipher_decrypt(struct l_cipher *cipher, const void *in, void *out, size_t len) { if (unlikely(!cipher)) return false; if (unlikely(!in) || unlikely(!out)) return false; if (cipher->local) { struct iovec in_iov = { (void *) in, len }; struct iovec out_iov = { out, len }; return cipher->local->operate(cipher->local_data, ALG_OP_DECRYPT, &in_iov, 1, &out_iov, 1) >= 0; } return operate_cipher(cipher->sk, ALG_OP_DECRYPT, in, len, NULL, 0, NULL, 0, out, len) >= 0; } LIB_EXPORT bool l_cipher_decryptv(struct l_cipher *cipher, const struct iovec *in, size_t in_cnt, const struct iovec *out, size_t out_cnt) { if (unlikely(!cipher)) return false; if (unlikely(!in) || unlikely(!out)) return false; if (cipher->local) return cipher->local->operate(cipher->local_data, ALG_OP_DECRYPT, in, in_cnt, out, out_cnt) >= 0; return operate_cipherv(cipher->sk, ALG_OP_DECRYPT, in, in_cnt, out, out_cnt) >= 0; } LIB_EXPORT bool l_cipher_set_iv(struct l_cipher *cipher, const uint8_t *iv, size_t iv_length) { char c_msg_buf[CMSG_SPACE(4 + iv_length)]; struct msghdr msg; struct cmsghdr *c_msg; uint32_t len = iv_length; if (unlikely(!cipher)) return false; if (cipher->local) { if (!cipher->local->set_iv) return false; return cipher->local->set_iv(cipher->local_data, iv, iv_length); } memset(&c_msg_buf, 0, sizeof(c_msg_buf)); memset(&msg, 0, sizeof(struct msghdr)); msg.msg_control = c_msg_buf; msg.msg_controllen = sizeof(c_msg_buf); c_msg = CMSG_FIRSTHDR(&msg); c_msg->cmsg_level = SOL_ALG; c_msg->cmsg_type = ALG_SET_IV; c_msg->cmsg_len = CMSG_LEN(4 + iv_length); memcpy(CMSG_DATA(c_msg) + 0, &len, 4); memcpy(CMSG_DATA(c_msg) + 4, iv, iv_length); msg.msg_iov = NULL; msg.msg_iovlen = 0; if (sendmsg(cipher->sk, &msg, MSG_MORE) < 0) return false; return true; } #define CCM_IV_SIZE 16 static size_t l_aead_cipher_get_ivlen(struct l_aead_cipher *cipher) { switch (cipher->type) { case L_AEAD_CIPHER_AES_CCM: return CCM_IV_SIZE; case L_AEAD_CIPHER_AES_GCM: return 12; } return 0; } /* RFC3610 Section 2.3 */ static ssize_t build_ccm_iv(const void *nonce, uint8_t nonce_len, uint8_t (*iv)[CCM_IV_SIZE]) { const size_t iv_overhead = 2; int lprime = 15 - nonce_len - 1; if (unlikely(nonce_len + iv_overhead > CCM_IV_SIZE || lprime > 7)) return -EINVAL; (*iv)[0] = lprime; memcpy(*iv + 1, nonce, nonce_len); memset(*iv + 1 + nonce_len, 0, lprime + 1); return CCM_IV_SIZE; } LIB_EXPORT bool l_aead_cipher_encrypt(struct l_aead_cipher *cipher, const void *in, size_t in_len, const void *ad, size_t ad_len, const void *nonce, size_t nonce_len, void *out, size_t out_len) { uint8_t ccm_iv[CCM_IV_SIZE]; const uint8_t *iv; ssize_t iv_len; if (unlikely(!cipher)) return false; if (unlikely(!in && !ad) || unlikely(!out)) return false; if (unlikely(!in && in_len) || unlikely(!ad && ad_len)) return false; if (cipher->type == L_AEAD_CIPHER_AES_CCM) { iv_len = build_ccm_iv(nonce, nonce_len, &ccm_iv); if (unlikely(iv_len < 0)) return false; iv = ccm_iv; } else { if (unlikely(nonce_len != l_aead_cipher_get_ivlen(cipher))) return false; iv = nonce; iv_len = nonce_len; } return operate_cipher(cipher->sk, ALG_OP_ENCRYPT, in, in_len, ad, ad_len, iv, iv_len, out, out_len) == (ssize_t)out_len; } LIB_EXPORT bool l_aead_cipher_decrypt(struct l_aead_cipher *cipher, const void *in, size_t in_len, const void *ad, size_t ad_len, const void *nonce, size_t nonce_len, void *out, size_t out_len) { uint8_t ccm_iv[CCM_IV_SIZE]; const uint8_t *iv; ssize_t iv_len; if (unlikely(!cipher)) return false; if (unlikely(!in) || unlikely(!out)) return false; if (cipher->type == L_AEAD_CIPHER_AES_CCM) { iv_len = build_ccm_iv(nonce, nonce_len, &ccm_iv); if (unlikely(iv_len < 0)) return false; iv = ccm_iv; } else { if (unlikely(nonce_len != l_aead_cipher_get_ivlen(cipher))) return false; iv = nonce; iv_len = nonce_len; } return operate_cipher(cipher->sk, ALG_OP_DECRYPT, in, in_len, ad, ad_len, iv, iv_len, out, out_len) == (ssize_t)out_len; } static void init_supported() { static bool initialized = false; struct sockaddr_alg salg; int sk; enum l_cipher_type c; enum l_aead_cipher_type a; if (likely(initialized)) return; initialized = true; for (c = 0; c < L_ARRAY_SIZE(local_impl_ciphers); c++) if (HAVE_LOCAL_IMPLEMENTATION(c)) supported_ciphers |= 1 << c; sk = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); if (sk < 0) return; memset(&salg, 0, sizeof(salg)); salg.salg_family = AF_ALG; strcpy((char *) salg.salg_type, "skcipher"); for (c = L_CIPHER_AES; c <= L_CIPHER_DES3_EDE_CBC; c++) { const char *name = cipher_type_to_name(c); if (!name) continue; strcpy((char *) salg.salg_name, name); if (bind(sk, (struct sockaddr *) &salg, sizeof(salg)) < 0) continue; supported_ciphers |= 1 << c; } strcpy((char *) salg.salg_type, "aead"); for (a = L_AEAD_CIPHER_AES_CCM; a <= L_AEAD_CIPHER_AES_GCM; a++) { strcpy((char *) salg.salg_name, aead_cipher_type_to_name(a)); if (bind(sk, (struct sockaddr *) &salg, sizeof(salg)) < 0) continue; supported_aead_ciphers |= 1 << a; } close(sk); } LIB_EXPORT bool l_cipher_is_supported(enum l_cipher_type type) { if (!is_valid_type(type)) return false; init_supported(); return supported_ciphers & (1 << type); } LIB_EXPORT bool l_aead_cipher_is_supported(enum l_aead_cipher_type type) { if (type != L_AEAD_CIPHER_AES_CCM && type != L_AEAD_CIPHER_AES_GCM) return false; init_supported(); return supported_aead_ciphers & (1 << type); } /* ARC4 implementation copyright (c) 2001 Niels Möller */ static void arc4_set_key(uint8_t *S, const uint8_t *key, size_t key_length) { unsigned int i; uint8_t j; for (i = 0; i < 256; i++) S[i] = i; for (i = j = 0; i < 256; i++) { j += S[i] + key[i % key_length]; SWAP(S[i], S[j]); } } struct arc4_state { struct arc4_state_ctx { uint8_t S[256]; uint8_t i; uint8_t j; } ctx[2]; }; static void *local_arc4_new(enum l_cipher_type type, const void *key, size_t key_length) { struct arc4_state *s; if (unlikely(key_length == 0 || key_length > 256)) return NULL; s = l_new(struct arc4_state, 1); arc4_set_key(s->ctx[0].S, key, key_length); s->ctx[1] = s->ctx[0]; return s; } static void local_arc4_free(void *data) { explicit_bzero(data, sizeof(struct arc4_state)); l_free(data); } static ssize_t local_arc4_operate(void *data, __u32 operation, const struct iovec *in, size_t in_cnt, const struct iovec *out, size_t out_cnt) { struct arc4_state *s = data; struct iovec cur_in; struct iovec cur_out; struct arc4_state_ctx *ctx = &s->ctx[operation == ALG_OP_ENCRYPT ? 1 : 0]; if (!in_cnt || !out_cnt) return 0; cur_in = *in; cur_out = *out; while (1) { while (!cur_in.iov_len) { cur_in = *in++; if (!--in_cnt) return 0; } while (!cur_out.iov_len) { cur_out = *out++; if (!--out_cnt) return 0; } ctx->j += ctx->S[++ctx->i]; SWAP(ctx->S[ctx->i], ctx->S[ctx->j]); *(uint8_t *) cur_out.iov_base++ = *(uint8_t *) cur_in.iov_base++ ^ ctx->S[(ctx->S[ctx->i] + ctx->S[ctx->j]) & 0xff]; cur_in.iov_len--; cur_out.iov_len--; } } static const struct local_impl local_arc4 = { local_arc4_new, local_arc4_free, NULL, local_arc4_operate, }; struct rc2_state { union { uint16_t xkey[64]; uint8_t xkey8[128]; }; struct rc2_state_ctx { union { uint16_t x[4]; uint64_t x64; }; } ctx[2]; }; /* Simplified from the 1996 public-domain implementation */ static void rc2_keyschedule(struct rc2_state *s, const uint8_t *key, size_t key_len, size_t bits) { static const uint8_t permute[256] = { 217,120,249,196, 25,221,181,237, 40,233,253,121, 74,160,216,157, 198,126, 55,131, 43,118, 83,142, 98, 76,100,136, 68,139,251,162, 23,154, 89,245,135,179, 79, 19, 97, 69,109,141, 9,129,125, 50, 189,143, 64,235,134,183,123, 11,240,149, 33, 34, 92,107, 78,130, 84,214,101,147,206, 96,178, 28,115, 86,192, 20,167,140,241,220, 18,117,202, 31, 59,190,228,209, 66, 61,212, 48,163, 60,182, 38, 111,191, 14,218, 70,105, 7, 87, 39,242, 29,155,188,148, 67, 3, 248, 17,199,246,144,239, 62,231, 6,195,213, 47,200,102, 30,215, 8,232,234,222,128, 82,238,247,132,170,114,172, 53, 77,106, 42, 150, 26,210,113, 90, 21, 73,116, 75,159,208, 94, 4, 24,164,236, 194,224, 65,110, 15, 81,203,204, 36,145,175, 80,161,244,112, 57, 153,124, 58,133, 35,184,180,122,252, 2, 54, 91, 37, 85,151, 49, 45, 93,250,152,227,138,146,174, 5,223, 41, 16,103,108,186,201, 211, 0,230,207,225,158,168, 44, 99, 22, 1, 63, 88,226,137,169, 13, 56, 52, 27,171, 51,255,176,187, 72, 12, 95,185,177,205, 46, 197,243,219, 71,229,165,156,119, 10,166, 32,104,254,127,193,173 }; uint8_t x; unsigned int i; memcpy(&s->xkey8, key, key_len); /* Step 1: expand input key to 128 bytes */ x = s->xkey8[key_len - 1]; for (i = 0; key_len < 128; key_len++, i++) s->xkey8[key_len] = x = permute[(x + s->xkey8[i]) & 255]; /* Step 2: reduce effective key size to "bits" */ key_len = (bits + 7) >> 3; i = 128 - key_len; s->xkey8[i] = x = permute[s->xkey8[i] & (255 >> (7 & -bits))]; while (i--) s->xkey8[i] = x = permute[x ^ s->xkey8[i + key_len]]; /* Step 3: copy to xkey in little-endian order */ for (i = 0; i < 64; i++) s->xkey[i] = L_CPU_TO_LE16(s->xkey[i]); } static uint64_t rc2_operate(struct rc2_state *s, uint64_t in, __u32 operation) { int i; union { uint16_t x16[4]; uint64_t x64; } x; x.x64 = in; if (operation == ALG_OP_ENCRYPT) { const uint16_t *xkey = s->xkey; for (i = 0; i < 16; i++) { x.x16[0] += (x.x16[1] & ~x.x16[3]) + (x.x16[2] & x.x16[3]) + *xkey++; x.x16[0] = (x.x16[0] << 1) | (x.x16[0] >> 15); x.x16[1] += (x.x16[2] & ~x.x16[0]) + (x.x16[3] & x.x16[0]) + *xkey++; x.x16[1] = (x.x16[1] << 2) | (x.x16[1] >> 14); x.x16[2] += (x.x16[3] & ~x.x16[1]) + (x.x16[0] & x.x16[1]) + *xkey++; x.x16[2] = (x.x16[2] << 3) | (x.x16[2] >> 13); x.x16[3] += (x.x16[0] & ~x.x16[2]) + (x.x16[1] & x.x16[2]) + *xkey++; x.x16[3] = (x.x16[3] << 5) | (x.x16[3] >> 11); if (i == 4 || i == 10) { x.x16[0] += s->xkey[x.x16[3] & 63]; x.x16[1] += s->xkey[x.x16[0] & 63]; x.x16[2] += s->xkey[x.x16[1] & 63]; x.x16[3] += s->xkey[x.x16[2] & 63]; } } } else { const uint16_t *xkey = s->xkey + 63; for (i = 0; i < 16; i++) { x.x16[3] = (x.x16[3] << 11) | (x.x16[3] >> 5); x.x16[3] -= (x.x16[0] & ~x.x16[2]) + (x.x16[1] & x.x16[2]) + *xkey--; x.x16[2] = (x.x16[2] << 13) | (x.x16[2] >> 3); x.x16[2] -= (x.x16[3] & ~x.x16[1]) + (x.x16[0] & x.x16[1]) + *xkey--; x.x16[1] = (x.x16[1] << 14) | (x.x16[1] >> 2); x.x16[1] -= (x.x16[2] & ~x.x16[0]) + (x.x16[3] & x.x16[0]) + *xkey--; x.x16[0] = (x.x16[0] << 15) | (x.x16[0] >> 1); x.x16[0] -= (x.x16[1] & ~x.x16[3]) + (x.x16[2] & x.x16[3]) + *xkey--; if (i == 4 || i == 10) { x.x16[3] -= s->xkey[x.x16[2] & 63]; x.x16[2] -= s->xkey[x.x16[1] & 63]; x.x16[1] -= s->xkey[x.x16[0] & 63]; x.x16[0] -= s->xkey[x.x16[3] & 63]; } } } return x.x64; } static void *local_rc2_cbc_new(enum l_cipher_type type, const void *key, size_t key_length) { struct rc2_state *s; if (unlikely(key_length == 0 || key_length > 128)) return NULL; /* * The key length and the effective "strength" bits are separate * parameters but they match in our current use cases. */ s = l_new(struct rc2_state, 1); rc2_keyschedule(s, key, key_length, key_length * 8); return s; } static void local_rc2_cbc_free(void *data) { explicit_bzero(data, sizeof(struct rc2_state)); l_free(data); } static bool local_rc2_cbc_set_iv(void *data, const uint8_t *iv, size_t iv_length) { struct rc2_state *s = data; if (unlikely(iv_length != 8)) return false; s->ctx[0].x[0] = l_get_le16(iv + 0); s->ctx[0].x[1] = l_get_le16(iv + 2); s->ctx[0].x[2] = l_get_le16(iv + 4); s->ctx[0].x[3] = l_get_le16(iv + 6); s->ctx[1].x64 = s->ctx[0].x64; return true; } static ssize_t local_rc2_cbc_operate(void *data, __u32 operation, const struct iovec *in, size_t in_cnt, const struct iovec *out, size_t out_cnt) { struct rc2_state *s = data; struct iovec cur_in = {}; struct iovec cur_out = {}; struct rc2_state_ctx *ctx = &s->ctx[operation == ALG_OP_ENCRYPT ? 1 : 0]; #define CONSUME_IN(bytes, eof_ok) \ cur_in.iov_len -= (bytes); \ while (!cur_in.iov_len) { \ if (!in_cnt) { \ if (eof_ok) \ break; \ else \ return -1; \ } \ \ cur_in = *in++; \ in_cnt--; \ } #define CONSUME_OUT(bytes) \ cur_out.iov_len -= (bytes); \ while (!cur_out.iov_len) { \ if (!out_cnt) \ return 0; \ \ cur_out = *out++; \ out_cnt--; \ } CONSUME_IN(0, true) CONSUME_OUT(0) while (cur_in.iov_len) { union { uint16_t x16[4]; uint64_t x64; } inblk; if (cur_in.iov_len >= 8) { #define CUR_IN16 (*(uint16_t **) &cur_in.iov_base) inblk.x16[0] = l_get_le16(CUR_IN16++); inblk.x16[1] = l_get_le16(CUR_IN16++); inblk.x16[2] = l_get_le16(CUR_IN16++); inblk.x16[3] = l_get_le16(CUR_IN16++); CONSUME_IN(8, true) } else { inblk.x16[0] = *(uint8_t *) cur_in.iov_base++; CONSUME_IN(1, false) inblk.x16[0] |= (*(uint8_t *) cur_in.iov_base++) << 8; CONSUME_IN(1, false) inblk.x16[1] = *(uint8_t *) cur_in.iov_base++; CONSUME_IN(1, false) inblk.x16[1] |= (*(uint8_t *) cur_in.iov_base++) << 8; CONSUME_IN(1, false) inblk.x16[2] = *(uint8_t *) cur_in.iov_base++; CONSUME_IN(1, false) inblk.x16[2] |= (*(uint8_t *) cur_in.iov_base++) << 8; CONSUME_IN(1, false) inblk.x16[3] = *(uint8_t *) cur_in.iov_base++; CONSUME_IN(1, false) inblk.x16[3] |= (*(uint8_t *) cur_in.iov_base++) << 8; CONSUME_IN(1, true) } if (operation == ALG_OP_ENCRYPT) ctx->x64 = rc2_operate(s, inblk.x64 ^ ctx->x64, operation); else ctx->x64 ^= rc2_operate(s, inblk.x64, operation); if (cur_out.iov_len >= 8) { #define CUR_OUT16 (*(uint16_t **) &cur_out.iov_base) l_put_le16(ctx->x[0], CUR_OUT16++); l_put_le16(ctx->x[1], CUR_OUT16++); l_put_le16(ctx->x[2], CUR_OUT16++); l_put_le16(ctx->x[3], CUR_OUT16++); CONSUME_OUT(8) } else { *(uint8_t *) cur_out.iov_base++ = ctx->x[0]; CONSUME_OUT(1) *(uint8_t *) cur_out.iov_base++ = ctx->x[0] >> 8; CONSUME_OUT(1) *(uint8_t *) cur_out.iov_base++ = ctx->x[1]; CONSUME_OUT(1) *(uint8_t *) cur_out.iov_base++ = ctx->x[1] >> 8; CONSUME_OUT(1) *(uint8_t *) cur_out.iov_base++ = ctx->x[2]; CONSUME_OUT(1) *(uint8_t *) cur_out.iov_base++ = ctx->x[2] >> 8; CONSUME_OUT(1) *(uint8_t *) cur_out.iov_base++ = ctx->x[3]; CONSUME_OUT(1) *(uint8_t *) cur_out.iov_base++ = ctx->x[3] >> 8; CONSUME_OUT(1) } /* Save ciphertext as IV for next CBC block */ if (operation == ALG_OP_DECRYPT) ctx->x64 = inblk.x64; inblk.x64 = 0; } return 0; } static const struct local_impl local_rc2_cbc = { local_rc2_cbc_new, local_rc2_cbc_free, local_rc2_cbc_set_iv, local_rc2_cbc_operate, }; bluez-5.87/ell/PaxHeaders/ell.h0000644000000000000000000000005015221753054013360 xustar0020 atime=1783093491 20 ctime=1783094110 bluez-5.87/ell/ell.h0000644000000000000000000000164115221753054013043 0ustar00rootroot#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include bluez-5.87/ell/PaxHeaders/checksum.c0000644000000000000000000000005014770744372014414 xustar0020 atime=1783076268 20 ctime=1783094099 bluez-5.87/ell/checksum.c0000644000000000000000000002364314770744372014105 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include "useful.h" #include "checksum.h" #include "private.h" #ifndef HAVE_LINUX_IF_ALG_H #ifndef HAVE_LINUX_TYPES_H typedef uint8_t __u8; typedef uint16_t __u16; typedef uint32_t __u32; #else #include #endif #ifndef AF_ALG #define AF_ALG 38 #define PF_ALG AF_ALG #endif struct sockaddr_alg { __u16 salg_family; __u8 salg_type[14]; __u32 salg_feat; __u32 salg_mask; __u8 salg_name[64]; }; /* Socket options */ #define ALG_SET_KEY 1 #else #include #endif #ifndef SOL_ALG #define SOL_ALG 279 #endif struct checksum_info { const char *name; uint8_t digest_len; bool supported; }; static struct checksum_info checksum_algs[] = { [L_CHECKSUM_MD4] = { .name = "md4", .digest_len = 16 }, [L_CHECKSUM_MD5] = { .name = "md5", .digest_len = 16 }, [L_CHECKSUM_SHA1] = { .name = "sha1", .digest_len = 20 }, [L_CHECKSUM_SHA224] = { .name = "sha224", .digest_len = 28 }, [L_CHECKSUM_SHA256] = { .name = "sha256", .digest_len = 32 }, [L_CHECKSUM_SHA384] = { .name = "sha384", .digest_len = 48 }, [L_CHECKSUM_SHA512] = { .name = "sha512", .digest_len = 64 }, [L_CHECKSUM_SHA3_224] = { .name = "sha3-224", .digest_len = 28 }, [L_CHECKSUM_SHA3_256] = { .name = "sha3-256", .digest_len = 32 }, [L_CHECKSUM_SHA3_384] = { .name = "sha3-384", .digest_len = 48 }, [L_CHECKSUM_SHA3_512] = { .name = "sha3-512", .digest_len = 64 }, }; static struct checksum_info checksum_cmac_aes_alg = { .name = "cmac(aes)", .digest_len = 16 }; static struct checksum_info checksum_hmac_algs[] = { [L_CHECKSUM_MD4] = { .name = "hmac(md4)", .digest_len = 16 }, [L_CHECKSUM_MD5] = { .name = "hmac(md5)", .digest_len = 16 }, [L_CHECKSUM_SHA1] = { .name = "hmac(sha1)", .digest_len = 20 }, [L_CHECKSUM_SHA224] = { .name = "hmac(sha224)", .digest_len = 28 }, [L_CHECKSUM_SHA256] = { .name = "hmac(sha256)", .digest_len = 32 }, [L_CHECKSUM_SHA384] = { .name = "hmac(sha384)", .digest_len = 48 }, [L_CHECKSUM_SHA512] = { .name = "hmac(sha512)", .digest_len = 64 }, [L_CHECKSUM_SHA3_224] = { .name = "hmac(sha3-224)", .digest_len = 28 }, [L_CHECKSUM_SHA3_256] = { .name = "hmac(sha3-256)", .digest_len = 32 }, [L_CHECKSUM_SHA3_384] = { .name = "hmac(sha3-384)", .digest_len = 48 }, [L_CHECKSUM_SHA3_512] = { .name = "hmac(sha3-512)", .digest_len = 64 }, }; static const struct { struct checksum_info *list; size_t n; } checksum_info_table[] = { { checksum_algs, L_ARRAY_SIZE(checksum_algs) }, { &checksum_cmac_aes_alg, 1 }, { checksum_hmac_algs, L_ARRAY_SIZE(checksum_hmac_algs) }, {} }; /** * SECTION:checksum * @short_description: Checksum handling * * Checksum handling */ #define is_valid_index(array, i) ((i) >= 0 && (i) < L_ARRAY_SIZE(array)) /** * l_checksum: * * Opaque object representing the checksum. */ struct l_checksum { int sk; const struct checksum_info *alg_info; }; static int create_alg(const char *alg) { struct sockaddr_alg salg; int sk; sk = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); if (sk < 0) return -1; memset(&salg, 0, sizeof(salg)); salg.salg_family = AF_ALG; strcpy((char *) salg.salg_type, "hash"); strcpy((char *) salg.salg_name, alg); if (bind(sk, (struct sockaddr *) &salg, sizeof(salg)) < 0) { close(sk); return -1; } return sk; } static struct l_checksum *checksum_new_common(const char *alg, int sockopt, const void *data, size_t len, struct checksum_info *info) { struct l_checksum *checksum; int fd; fd = create_alg(alg); if (fd < 0) return NULL; if (data) { if (setsockopt(fd, SOL_ALG, sockopt, data, len) < 0) { close(fd); return NULL; } } checksum = l_new(struct l_checksum, 1); checksum->sk = accept4(fd, NULL, 0, SOCK_CLOEXEC); close(fd); if (checksum->sk < 0) { l_free(checksum); return NULL; } checksum->alg_info = info; return checksum; } /** * l_checksum_new: * @type: checksum type * * Creates new #l_checksum, using the checksum algorithm @type. * * Returns: a newly allocated #l_checksum object. **/ LIB_EXPORT struct l_checksum *l_checksum_new(enum l_checksum_type type) { if (!is_valid_index(checksum_algs, type) || !checksum_algs[type].name) return NULL; return checksum_new_common(checksum_algs[type].name, 0, NULL, 0, &checksum_algs[type]); } LIB_EXPORT struct l_checksum *l_checksum_new_cmac_aes(const void *key, size_t key_len) { return checksum_new_common("cmac(aes)", ALG_SET_KEY, key, key_len, &checksum_cmac_aes_alg); } LIB_EXPORT struct l_checksum *l_checksum_new_hmac(enum l_checksum_type type, const void *key, size_t key_len) { if (!is_valid_index(checksum_hmac_algs, type) || !checksum_hmac_algs[type].name) return NULL; return checksum_new_common(checksum_hmac_algs[type].name, ALG_SET_KEY, key, key_len, &checksum_hmac_algs[type]); } /** * l_checksum_clone: * @checksum: parent checksum object * * Creates a new checksum with an independent copy of parent @checksum's * state. l_checksum_get_digest can then be called on the parent or the * clone without affecting the state of the other object. **/ LIB_EXPORT struct l_checksum *l_checksum_clone(struct l_checksum *checksum) { struct l_checksum *clone; if (unlikely(!checksum)) return NULL; clone = l_new(struct l_checksum, 1); clone->sk = accept4(checksum->sk, NULL, 0, SOCK_CLOEXEC); if (clone->sk < 0) { l_free(clone); return NULL; } clone->alg_info = checksum->alg_info; return clone; } /** * l_checksum_free: * @checksum: checksum object * * Frees the memory allocated for @checksum. **/ LIB_EXPORT void l_checksum_free(struct l_checksum *checksum) { if (unlikely(!checksum)) return; close(checksum->sk); l_free(checksum); } /** * l_checksum_reset: * @checksum: checksum object * * Resets the internal state of @checksum. **/ LIB_EXPORT void l_checksum_reset(struct l_checksum *checksum) { if (unlikely(!checksum)) return; send(checksum->sk, NULL, 0, 0); } /** * l_checksum_update: * @checksum: checksum object * @data: data pointer * @len: length of data * * Updates checksum from @data pointer with @len bytes. * * Returns: true if the operation succeeded, false otherwise. **/ LIB_EXPORT bool l_checksum_update(struct l_checksum *checksum, const void *data, size_t len) { ssize_t written; if (unlikely(!checksum)) return false; written = send(checksum->sk, data, len, MSG_MORE); if (written < 0) return false; return true; } /** * l_checksum_updatev: * @checksum: checksum object * @iov: iovec pointer * @iov_len: Number of iovec entries * * This is a iovec based version of l_checksum_update; it updates the checksum * based on contents of @iov and @iov_len. * * Returns: true if the operation succeeded, false otherwise. **/ LIB_EXPORT bool l_checksum_updatev(struct l_checksum *checksum, const struct iovec *iov, size_t iov_len) { struct msghdr msg; ssize_t written; if (unlikely(!checksum)) return false; if (unlikely(!iov) || unlikely(!iov_len)) return false; memset(&msg, 0, sizeof(msg)); msg.msg_iov = (struct iovec *) iov; msg.msg_iovlen = iov_len; written = sendmsg(checksum->sk, &msg, MSG_MORE); if (written < 0) return false; return true; } /** * l_checksum_get_digest: * @checksum: checksum object * @digest: output data buffer * @len: length of output buffer * * Writes the digest from @checksum as raw binary data into the provided * buffer or, if the buffer is shorter, the initial @len bytes of the digest * data. * * Returns: Number of bytes written, or negative value if an error occurred. **/ LIB_EXPORT ssize_t l_checksum_get_digest(struct l_checksum *checksum, void *digest, size_t len) { ssize_t result; if (unlikely(!checksum)) return -EINVAL; if (unlikely(!digest)) return -EFAULT; if (unlikely(!len)) return -EINVAL; result = recv(checksum->sk, digest, len, 0); if (result < 0) return -errno; if ((size_t) result < len && result < checksum->alg_info->digest_len) return -EIO; return result; } /** * l_checksum_get_string: * @checksum: checksum object * * Gets the digest from @checksum as hex encoded string. * * Returns: a newly allocated hex string **/ LIB_EXPORT char *l_checksum_get_string(struct l_checksum *checksum) { unsigned char digest[64]; if (unlikely(!checksum)) return NULL; l_checksum_get_digest(checksum, digest, sizeof(digest)); return l_util_hexstring(digest, checksum->alg_info->digest_len); } static void init_supported() { static bool initialized = false; struct sockaddr_alg salg; int sk; unsigned int i, j; if (likely(initialized)) return; initialized = true; sk = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); if (sk < 0) return; memset(&salg, 0, sizeof(salg)); salg.salg_family = AF_ALG; strcpy((char *) salg.salg_type, "hash"); for (i = 0; checksum_info_table[i].list; i++) for (j = 0; j < checksum_info_table[i].n; j++) { struct checksum_info *info; info = &checksum_info_table[i].list[j]; if (!info->name) continue; strcpy((char *) salg.salg_name, info->name); if (bind(sk, (struct sockaddr *) &salg, sizeof(salg)) < 0) continue; info->supported = true; } close(sk); } LIB_EXPORT bool l_checksum_is_supported(enum l_checksum_type type, bool check_hmac) { const struct checksum_info *list; init_supported(); if (!check_hmac) { if (!is_valid_index(checksum_algs, type)) return false; list = checksum_algs; } else { if (!is_valid_index(checksum_hmac_algs, type)) return false; list = checksum_hmac_algs; } return list[type].supported; } LIB_EXPORT bool l_checksum_cmac_aes_supported() { init_supported(); return checksum_cmac_aes_alg.supported; } LIB_EXPORT ssize_t l_checksum_digest_length(enum l_checksum_type type) { return is_valid_index(checksum_algs, type) ? checksum_algs[type].digest_len : 0; } bluez-5.87/ell/PaxHeaders/utf8.c0000644000000000000000000000005014601374755013475 xustar0020 atime=1783076287 20 ctime=1783094099 bluez-5.87/ell/utf8.c0000644000000000000000000002433014601374755013160 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2024 Cruise, LLC * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "strv.h" #include "utf8.h" #include "private.h" #include "useful.h" /** * SECTION:utf8 * @short_description: UTF-8 utility function * * UTF-8 string handling support */ LIB_EXPORT unsigned char l_ascii_table[256] = { [0x00 ... 0x08] = L_ASCII_CNTRL, [0x09 ... 0x0D] = L_ASCII_CNTRL | L_ASCII_SPACE, [0x0E ... 0x1F] = L_ASCII_CNTRL, [0x20] = L_ASCII_PRINT | L_ASCII_SPACE, [0x21 ... 0x2F] = L_ASCII_PRINT | L_ASCII_PUNCT, [0x30 ... 0x39] = L_ASCII_DIGIT | L_ASCII_XDIGIT | L_ASCII_PRINT, [0x3A ... 0x40] = L_ASCII_PRINT | L_ASCII_PUNCT, [0x41 ... 0x46] = L_ASCII_PRINT | L_ASCII_XDIGIT | L_ASCII_UPPER, [0x47 ... 0x5A] = L_ASCII_PRINT | L_ASCII_UPPER, [0x5B ... 0x60] = L_ASCII_PRINT | L_ASCII_PUNCT, [0x61 ... 0x66] = L_ASCII_PRINT | L_ASCII_XDIGIT | L_ASCII_LOWER, [0x67 ... 0x7A] = L_ASCII_PRINT | L_ASCII_LOWER, [0x7B ... 0x7E] = L_ASCII_PRINT | L_ASCII_PUNCT, [0x7F] = L_ASCII_CNTRL, [0x80 ... 0xFF] = 0, }; /** * l_ascii_strdown * @str: a pointer to an ASCII string * @len: maximum bytes to process or negative if string is null terminated * * Returns: Newly allocated string with all upper case characters converted * to lower case. **/ LIB_EXPORT char *l_ascii_strdown(const char *str, ssize_t len) { size_t slen; size_t i; char *ret; if (!str) return NULL; if (len < 0) slen = strlen(str); else slen = len; ret = l_malloc(slen + 1); for (i = 0; i < slen && str[i]; i++) ret[i] = l_ascii_tolower(str[i]); ret[i] = '\0'; return ret; } /** * l_ascii_strup * @str: a pointer to an ASCII string * @len: maximum bytes to process or negative if string is null terminated * * Returns: Newly allocated string with all lower case characters converted * to upper case. **/ LIB_EXPORT char *l_ascii_strup(const char *str, ssize_t len) { size_t slen; size_t i; char *ret; if (!str) return NULL; if (len < 0) slen = strlen(str); else slen = len; ret = l_malloc(slen + 1); for (i = 0; i < slen && str[i]; i++) ret[i] = l_ascii_toupper(str[i]); ret[i] = '\0'; return ret; } static inline bool __attribute__ ((always_inline)) valid_unicode(wchar_t c) { if (c <= 0xd7ff) return true; if (c < 0xe000 || c > 0x10ffff) return false; if (c >= 0xfdd0 && c <= 0xfdef) return false; if ((c & 0xfffe) == 0xfffe) return false; return true; } /** * l_utf8_get_codepoint * @str: a pointer to codepoint data * @len: maximum bytes to read * @cp: destination for codepoint * * Returns: number of bytes read, or -1 for invalid coddepoint **/ LIB_EXPORT int l_utf8_get_codepoint(const char *str, size_t len, wchar_t *cp) { static const wchar_t mins[3] = { 1 << 7, 1 << 11, 1 << 16 }; unsigned int expect_bytes; wchar_t val; size_t i; if (len == 0) return 0; if ((signed char) str[0] > 0) { *cp = str[0]; return 1; } expect_bytes = __builtin_clz(~((unsigned int)str[0] << 24)); if (expect_bytes < 2 || expect_bytes > 4) goto error; if (expect_bytes > len) goto error; val = str[0] & (0xff >> (expect_bytes + 1)); for (i = 1; i < expect_bytes; i++) { if ((str[i] & 0xc0) != 0x80) goto error; val <<= 6; val |= str[i] & 0x3f; } if (val < mins[expect_bytes - 2]) goto error; if (valid_unicode(val) == false) goto error; *cp = val; return expect_bytes; error: return -1; } /** * l_utf8_validate: * @str: a pointer to character data * @len: max bytes to validate * @end: return location for end of valid data * * Validates UTF-8 encoded text. If @end is non-NULL, then the end of * the valid range will be stored there (i.e. the start of the first * invalid character if some bytes were invalid, or the end of the text * being validated otherwise). * * Returns: Whether the text was valid UTF-8 **/ LIB_EXPORT bool l_utf8_validate(const char *str, size_t len, const char **end) { size_t pos = 0; int ret; wchar_t val; while (pos < len && str[pos]) { ret = l_utf8_get_codepoint(str + pos, len - pos, &val); if (ret < 0) goto error; pos += ret; } error: if (end) *end = str + pos; if (pos != len) return false; return true; } /** * l_utf8_strlen: * @str: a pointer to character data * * Computes the number of UTF-8 characters (not bytes) in the string given * by @str. * * Returns: The number of UTF-8 characters in the string **/ LIB_EXPORT size_t l_utf8_strlen(const char *str) { size_t l = 0; size_t i; unsigned char b; for (i = 0; str[i]; i++) { b = str[i]; if ((b >> 6) == 2) l += 1; } return i - l; } static inline int __attribute__ ((always_inline)) utf8_length(wchar_t c) { if (c <= 0x7f) return 1; if (c <= 0x7ff) return 2; if (c <= 0xffff) return 3; return 4; } static inline uint16_t __attribute__ ((always_inline)) surrogate_value(uint16_t h, uint16_t l) { return 0x10000 + (h - 0xd800) * 0x400 + l - 0xdc00; } /* * l_utf8_from_wchar: * @c: a wide-character to convert * @out_buf: Buffer to write out to * * Assumes c is valid unicode and out_buf contains enough space for a single * utf8 character (maximum 4 bytes) * Returns: number of characters written */ LIB_EXPORT size_t l_utf8_from_wchar(wchar_t c, char *out_buf) { int len = utf8_length(c); int i; if (len == 1) { out_buf[0] = c; return 1; } for (i = len - 1; i; i--) { out_buf[i] = (c & 0x3f) | 0x80; c >>= 6; } out_buf[0] = (0xff << (8 - len)) | c; return len; } /** * l_utf8_from_utf16: * @utf16: Array of UTF16 characters * @utf16_size: The size of the @utf16 array in bytes. Must be a multiple of 2. * * Returns: A newly-allocated buffer containing UTF16 encoded string converted * to UTF8. The UTF8 string will always be null terminated, even if the * original UTF16 string was not. **/ LIB_EXPORT char *l_utf8_from_utf16(const void *utf16, ssize_t utf16_size) { char *utf8; size_t utf8_len = 0; wchar_t high_surrogate = 0; ssize_t i = 0; uint16_t in; wchar_t c; if (unlikely(utf16_size % 2)) return NULL; while (utf16_size < 0 || i < utf16_size) { in = l_get_u16(utf16 + i); if (!in) break; if (in >= 0xdc00 && in < 0xe000) { if (high_surrogate) c = surrogate_value(high_surrogate, in); else return NULL; high_surrogate = 0; } else { if (high_surrogate) return NULL; if (in >= 0xd800 && in < 0xdc00) { high_surrogate = in; goto next; } c = in; } if (!valid_unicode(c)) return NULL; utf8_len += utf8_length(c); next: i += 2; } if (high_surrogate) return NULL; utf8 = l_malloc(utf8_len + 1); utf8_len = 0; i = 0; while (utf16_size < 0 || i < utf16_size) { in = l_get_u16(utf16 + i); if (!in) break; if (in >= 0xd800 && in < 0xdc00) { high_surrogate = in; i += 2; in = l_get_u16(utf16 + i); c = surrogate_value(high_surrogate, in); } else c = in; utf8_len += l_utf8_from_wchar(c, utf8 + utf8_len); i += 2; } utf8[utf8_len] = '\0'; return utf8; } /** * l_utf8_to_utf16: * @utf8: UTF8 formatted string * @out_size: The size in bytes of the converted utf16 string * * Converts a UTF8 formatted string to UTF16. It is assumed that the string * is valid UTF8 and no sanity checking is performed. * * Returns: A newly-allocated buffer containing UTF8 encoded string converted * to UTF16. The UTF16 string will always be null terminated. **/ LIB_EXPORT void *l_utf8_to_utf16(const char *utf8, size_t *out_size) { const char *c; wchar_t wc; int len; uint16_t *utf16; size_t n_utf16; if (unlikely(!utf8)) return NULL; c = utf8; n_utf16 = 0; while (*c) { len = l_utf8_get_codepoint(c, 4, &wc); if (len < 0) return NULL; if (wc < 0x10000) n_utf16 += 1; else n_utf16 += 2; c += len; } utf16 = l_malloc((n_utf16 + 1) * 2); c = utf8; n_utf16 = 0; while (*c) { len = l_utf8_get_codepoint(c, 4, &wc); if (wc >= 0x10000) { utf16[n_utf16++] = (wc - 0x1000) / 0x400 + 0xd800; utf16[n_utf16++] = (wc - 0x1000) % 0x400 + 0xdc00; } else utf16[n_utf16++] = wc; c += len; } utf16[n_utf16] = 0; if (out_size) *out_size = (n_utf16 + 1) * 2; return utf16; } /** * l_utf8_from_ucs2be: * @ucs2be: Array of UCS2 characters in big-endian format * @ucs2be_size: The size of the @ucs2 array in bytes. Must be a multiple of 2. * * Returns: A newly-allocated buffer containing UCS2BE encoded string converted * to UTF8. The UTF8 string will always be null terminated, even if the * original UCS2BE string was not. **/ LIB_EXPORT char *l_utf8_from_ucs2be(const void *ucs2be, ssize_t ucs2be_size) { char *utf8; size_t utf8_len = 0; ssize_t i = 0; uint16_t in; if (unlikely(ucs2be_size % 2)) return NULL; while (ucs2be_size < 0 || i < ucs2be_size) { in = l_get_be16(ucs2be + i); if (!in) break; if (in >= 0xd800 && in < 0xe000) return NULL; if (!valid_unicode(in)) return NULL; utf8_len += utf8_length(in); i += 2; } utf8 = l_malloc(utf8_len + 1); utf8_len = 0; i = 0; while (ucs2be_size < 0 || i < ucs2be_size) { in = l_get_be16(ucs2be + i); if (!in) break; utf8_len += l_utf8_from_wchar(in, utf8 + utf8_len); i += 2; } utf8[utf8_len] = '\0'; return utf8; } /** * l_utf8_to_ucs2be: * @utf8: UTF8 formatted string * @out_size: The size in bytes of the converted ucs2be string * * Converts a UTF8 formatted string to UCS2BE. It is assumed that the string * is valid UTF8 and no sanity checking is performed. * * Returns: A newly-allocated buffer containing UTF8 encoded string converted * to UCS2BE. The UCS2BE string will always be null terminated. **/ LIB_EXPORT void *l_utf8_to_ucs2be(const char *utf8, size_t *out_size) { const char *c; wchar_t wc; int len; uint16_t *ucs2be; size_t n_ucs2be; if (unlikely(!utf8)) return NULL; c = utf8; n_ucs2be = 0; while (*c) { len = l_utf8_get_codepoint(c, 4, &wc); if (len < 0) return NULL; if (wc >= 0x10000) return NULL; n_ucs2be += 1; c += len; } ucs2be = l_malloc((n_ucs2be + 1) * 2); c = utf8; n_ucs2be = 0; while (*c) { len = l_utf8_get_codepoint(c, 4, &wc); ucs2be[n_ucs2be++] = L_CPU_TO_BE16(wc); c += len; } ucs2be[n_ucs2be] = 0; if (out_size) *out_size = (n_ucs2be + 1) * 2; return ucs2be; } bluez-5.87/ell/PaxHeaders/timeout.h0000644000000000000000000000005014667515664014312 xustar0020 atime=1783076231 20 ctime=1783094098 bluez-5.87/ell/timeout.h0000644000000000000000000000224414667515664013775 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_TIMEOUT_H #define __ELL_TIMEOUT_H #include #ifdef __cplusplus extern "C" { #endif struct l_timeout; typedef void (*l_timeout_notify_cb_t) (struct l_timeout *timeout, void *user_data); typedef void (*l_timeout_destroy_cb_t) (void *user_data); struct l_timeout *l_timeout_create(unsigned int seconds, l_timeout_notify_cb_t callback, void *user_data, l_timeout_destroy_cb_t destroy); struct l_timeout *l_timeout_create_ms(uint64_t milliseconds, l_timeout_notify_cb_t callback, void *user_data, l_timeout_destroy_cb_t destroy); void l_timeout_modify(struct l_timeout *timeout, unsigned int seconds); void l_timeout_modify_ms(struct l_timeout *timeout, uint64_t milliseconds); void l_timeout_remove(struct l_timeout *timeout); void l_timeout_set_callback(struct l_timeout *timeout, l_timeout_notify_cb_t callback, void *user_data, l_timeout_destroy_cb_t destroy); bool l_timeout_remaining(struct l_timeout *timeout, uint64_t *remaining); #ifdef __cplusplus } #endif #endif /* __ELL_TIMEOUT_H */ bluez-5.87/ell/PaxHeaders/timeout.c0000644000000000000000000000005014667515664014305 xustar0020 atime=1783076255 20 ctime=1783094099 bluez-5.87/ell/timeout.c0000644000000000000000000001644414667515664013777 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "useful.h" #include "timeout.h" #include "main-private.h" #include "private.h" #include "time-private.h" /** * SECTION:timeout * @short_description: Timeout support * * Timeout support */ /** * l_timeout: * * Opaque object representing the timeout. */ struct l_timeout { int fd; l_timeout_notify_cb_t callback; l_timeout_destroy_cb_t destroy; void *user_data; }; static void timeout_destroy(void *user_data) { struct l_timeout *timeout = user_data; close(timeout->fd); timeout->fd = -1; if (timeout->destroy) timeout->destroy(timeout->user_data); } static void timeout_callback(int fd, uint32_t events, void *user_data) { struct l_timeout *timeout = user_data; uint64_t expired; ssize_t result; result = read(timeout->fd, &expired, sizeof(expired)); if (result != sizeof(expired)) return; if (timeout->callback) timeout->callback(timeout, timeout->user_data); } static inline int timeout_set(int fd, unsigned int seconds, long nanoseconds) { struct itimerspec itimer; memset(&itimer, 0, sizeof(itimer)); itimer.it_interval.tv_sec = 0; itimer.it_interval.tv_nsec = 0; itimer.it_value.tv_sec = seconds; itimer.it_value.tv_nsec = nanoseconds; return timerfd_settime(fd, 0, &itimer, NULL); } static bool convert_ms(uint64_t milliseconds, unsigned int *seconds, long *nanoseconds) { uint64_t big_seconds = milliseconds / 1000; if (big_seconds > UINT_MAX) return false; *seconds = big_seconds; *nanoseconds = (milliseconds % 1000) * 1000000L; return true; } /** * timeout_create_with_nanoseconds: * @seconds: number of seconds * @nanoseconds: number of nanoseconds * @callback: timeout callback function * @user_data: user data provided to timeout callback function * @destroy: destroy function for user data * * Create new timeout callback handling. * * The timeout will only fire once. The timeout handling needs to be rearmed * with one of the l_timeout_modify functions to trigger again. * * Returns: a newly allocated #l_timeout object. On failure, the function * returns NULL. **/ static struct l_timeout *timeout_create_with_nanoseconds(unsigned int seconds, long nanoseconds, l_timeout_notify_cb_t callback, void *user_data, l_timeout_destroy_cb_t destroy) { struct l_timeout *timeout; int err; if (unlikely(!callback)) return NULL; timeout = l_new(struct l_timeout, 1); timeout->callback = callback; timeout->destroy = destroy; timeout->user_data = user_data; timeout->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); if (timeout->fd < 0) { l_free(timeout); return NULL; } if (seconds > 0 || nanoseconds > 0) { if (timeout_set(timeout->fd, seconds, nanoseconds) < 0) { close(timeout->fd); l_free(timeout); return NULL; } } err = watch_add(timeout->fd, EPOLLIN | EPOLLONESHOT, timeout_callback, timeout, timeout_destroy); if (err < 0) { l_free(timeout); return NULL; } return timeout; } /** * l_timeout_create: * @seconds: timeout in seconds * @callback: timeout callback function * @user_data: user data provided to timeout callback function * @destroy: destroy function for user data * * Create new timeout callback handling. * * The timeout will only fire once. The timeout handling needs to be rearmed * with one of the l_timeout_modify functions to trigger again. * * Returns: a newly allocated #l_timeout object. On failure, the function * returns NULL. **/ LIB_EXPORT struct l_timeout *l_timeout_create(unsigned int seconds, l_timeout_notify_cb_t callback, void *user_data, l_timeout_destroy_cb_t destroy) { return timeout_create_with_nanoseconds(seconds, 0, callback, user_data, destroy); } /** * l_timeout_create_ms: * @milliseconds: timeout in milliseconds * @callback: timeout callback function * @user_data: user data provided to timeout callback function * @destroy: destroy function for user data * * Create new timeout callback handling. * * The timeout will only fire once. The timeout handling needs to be rearmed * with one of the l_timeout_modify functions to trigger again. * * Returns: a newly allocated #l_timeout object. On failure, the function * returns NULL. **/ LIB_EXPORT struct l_timeout *l_timeout_create_ms(uint64_t milliseconds, l_timeout_notify_cb_t callback, void *user_data, l_timeout_destroy_cb_t destroy) { unsigned int seconds; long nanoseconds; if (!convert_ms(milliseconds, &seconds, &nanoseconds)) return NULL; return timeout_create_with_nanoseconds(seconds, nanoseconds, callback, user_data, destroy); } /** * l_timeout_modify: * @timeout: timeout object * @seconds: timeout in seconds * * Modify an existing @timeout and rearm it. **/ LIB_EXPORT void l_timeout_modify(struct l_timeout *timeout, unsigned int seconds) { if (unlikely(!timeout)) return; if (unlikely(timeout->fd < 0)) return; if (seconds > 0) { if (timeout_set(timeout->fd, seconds, 0) < 0) return; } watch_modify(timeout->fd, EPOLLIN | EPOLLONESHOT, true); } /** * l_timeout_modify_ms: * @timeout: timeout object * @milliseconds: number of milliseconds * * Modify an existing @timeout and rearm it. **/ LIB_EXPORT void l_timeout_modify_ms(struct l_timeout *timeout, uint64_t milliseconds) { if (unlikely(!timeout)) return; if (unlikely(timeout->fd < 0)) return; if (milliseconds > 0) { unsigned int sec; long nanosec; if (!convert_ms(milliseconds, &sec, &nanosec) || timeout_set(timeout->fd, sec, nanosec) < 0) return; } watch_modify(timeout->fd, EPOLLIN | EPOLLONESHOT, true); } /** * l_timeout_remove: * @timeout: timeout object * * Remove timeout handling. **/ LIB_EXPORT void l_timeout_remove(struct l_timeout *timeout) { if (unlikely(!timeout)) return; watch_remove(timeout->fd, false); l_free(timeout); } /** * l_timeout_set_callback: * @timeout: timeout object * @callback: The new callback * @user_data: The new user_data * @destroy: The new destroy function * * Sets the new notify callback for @timeout. If the old user_data object had * a destroy function set, then that function will be called. */ LIB_EXPORT void l_timeout_set_callback(struct l_timeout *timeout, l_timeout_notify_cb_t callback, void *user_data, l_timeout_destroy_cb_t destroy) { if (unlikely(!timeout)) return; if (timeout->destroy) timeout->destroy(timeout->user_data); timeout->callback = callback; timeout->user_data = user_data; timeout->destroy = destroy; } /** * l_timeout_get_remaining: * * Get the remaining time for a timeout in microseconds * * @timeout: timeout object * @remaining: microseconds remaining on timer * * Returns: True if successfully got remaining time * False if failure to get remaining time **/ LIB_EXPORT bool l_timeout_remaining(struct l_timeout *timeout, uint64_t *remaining) { struct itimerspec current; if (unlikely(!timeout)) return false; if (timerfd_gettime(timeout->fd, ¤t) < 0) return false; if (remaining) *remaining = _time_from_timespec(¤t.it_value); return true; } bluez-5.87/ell/PaxHeaders/cert.c0000644000000000000000000000005015142661620013533 xustar0020 atime=1783076271 20 ctime=1783094099 bluez-5.87/ell/cert.c0000644000000000000000000013721315142661620013223 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2018 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include "private.h" #include "useful.h" #include "key.h" #include "queue.h" #include "asn1-private.h" #include "cipher.h" #include "pem-private.h" #include "time.h" #include "time-private.h" #include "utf8.h" #include "cert.h" #include "cert-private.h" #include "tls.h" #include "tls-private.h" #include "missing.h" #define X509_CERTIFICATE_POS 0 #define X509_TBSCERTIFICATE_POS 0 #define X509_TBSCERT_VERSION_POS ASN1_CONTEXT_EXPLICIT(0) #define X509_TBSCERT_SERIAL_POS 0 #define X509_TBSCERT_SIGNATURE_POS 1 #define X509_ALGORITHM_ID_ALGORITHM_POS 0 #define X509_ALGORITHM_ID_PARAMS_POS 1 #define X509_TBSCERT_ISSUER_DN_POS 2 #define X509_TBSCERT_VALIDITY_POS 3 #define X509_TBSCERT_SUBJECT_DN_POS 4 #define X509_TBSCERT_SUBJECT_KEY_POS 5 #define X509_SUBJECT_KEY_ALGORITHM_POS 0 #define X509_SUBJECT_KEY_VALUE_POS 1 #define X509_TBSCERT_ISSUER_UID_POS ASN1_CONTEXT_IMPLICIT(1) #define X509_TBSCERT_SUBJECT_UID_POS ASN1_CONTEXT_IMPLICIT(2) #define X509_TBSCERT_EXTENSIONS_POS ASN1_CONTEXT_EXPLICIT(3) #define X509_SIGNATURE_ALGORITHM_POS 1 #define X509_SIGNATURE_VALUE_POS 2 struct l_cert { enum l_cert_key_type pubkey_type; struct l_cert *issuer; struct l_cert *issued; size_t asn1_len; uint8_t asn1[]; }; struct l_certchain { struct l_cert *leaf; /* Bottom of the doubly-linked list */ struct l_cert *ca; /* Top of the doubly-linked list */ }; static const struct pkcs1_encryption_oid { enum l_cert_key_type key_type; struct asn1_oid oid; } pkcs1_encryption_oids[] = { { /* rsaEncryption */ L_CERT_KEY_RSA, { .asn1_len = 9, .asn1 = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 } }, }, { /* ecPublicKey */ L_CERT_KEY_ECC, { .asn1_len = 7, .asn1 = { 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01 } }, }, }; static bool cert_set_pubkey_type(struct l_cert *cert) { const uint8_t *key_type; size_t key_type_len; int i; key_type = asn1_der_find_elem_by_path(cert->asn1, cert->asn1_len, ASN1_ID_OID, &key_type_len, X509_CERTIFICATE_POS, X509_TBSCERTIFICATE_POS, X509_TBSCERT_SUBJECT_KEY_POS, X509_SUBJECT_KEY_ALGORITHM_POS, X509_ALGORITHM_ID_ALGORITHM_POS, -1); if (!key_type) return false; for (i = 0; i < (int) L_ARRAY_SIZE(pkcs1_encryption_oids); i++) if (asn1_oid_eq(&pkcs1_encryption_oids[i].oid, key_type_len, key_type)) break; if (i == L_ARRAY_SIZE(pkcs1_encryption_oids)) cert->pubkey_type = L_CERT_KEY_UNKNOWN; else cert->pubkey_type = pkcs1_encryption_oids[i].key_type; return true; } LIB_EXPORT struct l_cert *l_cert_new_from_der(const uint8_t *buf, size_t buf_len) { const uint8_t *seq = buf; size_t seq_len = buf_len; size_t content_len; struct l_cert *cert; /* Sanity check: outer element is a SEQUENCE */ if (seq_len-- < 1 || *seq++ != ASN1_ID_SEQUENCE) return NULL; /* Sanity check: the SEQUENCE spans the whole buffer */ content_len = asn1_parse_definite_length(&seq, &seq_len); if (content_len < 64 || content_len != seq_len) return NULL; /* * We could require the signature algorithm and the key algorithm * to be one of our supported types here but instead we only * require that when the user wants to verify this certificate or * get the public key respectively. */ cert = l_malloc(sizeof(struct l_cert) + buf_len); cert->issuer = NULL; cert->issued = NULL; cert->asn1_len = buf_len; memcpy(cert->asn1, buf, buf_len); /* Sanity check: structure is correct up to the Public Key Algorithm */ if (!cert_set_pubkey_type(cert)) { l_free(cert); return NULL; } return cert; } LIB_EXPORT void l_cert_free(struct l_cert *cert) { l_free(cert); } LIB_EXPORT const uint8_t *l_cert_get_der_data(struct l_cert *cert, size_t *out_len) { if (unlikely(!cert)) return NULL; *out_len = cert->asn1_len; return cert->asn1; } LIB_EXPORT const uint8_t *l_cert_get_dn(struct l_cert *cert, size_t *out_len) { if (unlikely(!cert)) return NULL; return asn1_der_find_elem_by_path(cert->asn1, cert->asn1_len, ASN1_ID_SEQUENCE, out_len, X509_CERTIFICATE_POS, X509_TBSCERTIFICATE_POS, X509_TBSCERT_SUBJECT_DN_POS, -1); } static uint64_t cert_parse_asn1_time(const uint8_t *data, size_t len, uint8_t tag) { struct tm tm = {}; int tz_hours; int tz_mins; int century; int msecs = 0; time_t tt; unsigned int i; for (i = 0; i < len && i < 15; i++) if (unlikely(!l_ascii_isdigit(data[i]))) break; if (tag == ASN1_ID_UTCTIME) { if (unlikely(!L_IN_SET(i, 10, 12))) return L_TIME_INVALID; century = 19; } else if (tag == ASN1_ID_GENERALIZEDTIME) { if (unlikely(!L_IN_SET(i, 10, 12, 14))) return L_TIME_INVALID; century = (data[0] - '0') * 10 + (data[1] - '0'); if (century < 19) return L_TIME_INVALID; if (len >= i + 4 && data[i] == '.') { if (unlikely(!l_ascii_isdigit(data[i + 1]) || !l_ascii_isdigit(data[i + 2]) || !l_ascii_isdigit(data[i + 3]))) return L_TIME_INVALID; i++; msecs += (data[i++] - '0') * 100; msecs += (data[i++] - '0') * 10; msecs += (data[i++] - '0'); } data += 2; len -= 2; i -= 2; } else return L_TIME_INVALID; if (unlikely((len != i + 1 || data[i] != 'Z') && (len != i + 5 || (data[i] != '+' && data[i] != '-')))) return L_TIME_INVALID; tm.tm_year = (data[0] - '0') * 10 + (data[1] - '0'); tm.tm_mon = (data[2] - '0') * 10 + (data[3] - '0'); tm.tm_mday = (data[4] - '0') * 10 + (data[5] - '0'); tm.tm_hour = (data[6] - '0') * 10 + (data[7] - '0'); if (unlikely(tm.tm_mon < 1 || tm.tm_mon > 12 || tm.tm_mday < 1 || tm.tm_mday > 31 || tm.tm_hour > 23)) return L_TIME_INVALID; if (i >= 10) { tm.tm_min = (data[8] - '0') * 10 + (data[9] - '0'); if (unlikely(tm.tm_min > 59)) return L_TIME_INVALID; } if (i >= 12) { tm.tm_sec = (data[10] - '0') * 10 + (data[11] - '0'); if (unlikely(tm.tm_sec > 59)) return L_TIME_INVALID; } /* RFC5280 Section 4.1.2.5.1 */ if (tag == ASN1_ID_UTCTIME && tm.tm_year < 50) century = 20; tm.tm_year += (century - 19) * 100; /* Month number is 1-based in UTCTime and 0-based in struct tm */ tm.tm_mon -= 1; tt = timegm(&tm); if (unlikely(tt == (time_t) -1)) return L_TIME_INVALID; if (len == i + 5) { data += i; for (i = 1; i < 5; i++) if (unlikely(!l_ascii_isdigit(data[i]))) return L_TIME_INVALID; tz_hours = (data[1] - '0') * 10 + (data[2] - '0'); tz_mins = (data[3] - '0') * 10 + (data[4] - '0'); if (unlikely(tz_hours > 14 || tz_mins > 59)) return L_TIME_INVALID; /* The sign converts UTC to local so invert it */ if (data[0] == '+') tt -= tz_hours * 3600 + tz_mins * 60; else tt += tz_hours * 3600 + tz_mins * 60; } return (uint64_t) tt * L_USEC_PER_SEC + msecs * L_USEC_PER_MSEC; } LIB_EXPORT bool l_cert_get_valid_times(struct l_cert *cert, uint64_t *out_not_before_time, uint64_t *out_not_after_time) { const uint8_t *validity; const uint8_t *not_before; const uint8_t *not_after; size_t seq_size; size_t not_before_size; size_t not_after_size; uint8_t not_before_tag; uint8_t not_after_tag; uint64_t not_before_time = 0; uint64_t not_after_time = 0; if (unlikely(!cert)) return false; validity = asn1_der_find_elem_by_path(cert->asn1, cert->asn1_len, ASN1_ID_SEQUENCE, &seq_size, X509_CERTIFICATE_POS, X509_TBSCERTIFICATE_POS, X509_TBSCERT_VALIDITY_POS, -1); if (unlikely(!validity)) return false; not_before = asn1_der_find_elem(validity, seq_size, 0, ¬_before_tag, ¬_before_size); if (!not_before) return false; seq_size -= not_before_size + (not_before - validity); validity = not_before + not_before_size; not_after = asn1_der_find_elem(validity, seq_size, 0, ¬_after_tag, ¬_after_size); if (!not_after) return false; if (out_not_before_time) { not_before_time = cert_parse_asn1_time(not_before, not_before_size, not_before_tag); if (not_before_time == L_TIME_INVALID) return false; } if (out_not_after_time) { /* * RFC5280 Section 4.1.2.5: "To indicate that a certificate * has no well-defined expiration date, the notAfter SHOULD * be assigned the GeneralizedTime value of 99991231235959Z." */ if (not_after_size == 15 && !memcmp(not_after, "99991231235959Z", 15)) not_after_time = 0; else { not_after_time = cert_parse_asn1_time(not_after, not_after_size, not_after_tag); if (not_after_time == L_TIME_INVALID) return false; } } if (out_not_before_time) *out_not_before_time = not_before_time; if (out_not_after_time) *out_not_after_time = not_after_time; return true; } const uint8_t *cert_get_extension(struct l_cert *cert, const struct asn1_oid *ext_id, bool *out_critical, size_t *out_len) { const uint8_t *ext, *end; size_t ext_len; if (unlikely(!cert)) return NULL; ext = asn1_der_find_elem_by_path(cert->asn1, cert->asn1_len, ASN1_ID_SEQUENCE, &ext_len, X509_CERTIFICATE_POS, X509_TBSCERTIFICATE_POS, X509_TBSCERT_EXTENSIONS_POS, -1); if (unlikely(!ext)) return NULL; end = ext + ext_len; while (ext < end) { const uint8_t *seq, *oid, *data; uint8_t tag; size_t len, oid_len, data_len; bool critical; seq = asn1_der_find_elem(ext, end - ext, 0, &tag, &len); if (unlikely(!seq || tag != ASN1_ID_SEQUENCE)) return NULL; ext = seq + len; oid = asn1_der_find_elem(seq, len, 0, &tag, &oid_len); if (unlikely(!oid || tag != ASN1_ID_OID)) return NULL; if (!asn1_oid_eq(ext_id, oid_len, oid)) continue; data = asn1_der_find_elem(seq, len, 1, &tag, &data_len); critical = false; if (data && tag == ASN1_ID_BOOLEAN) { if (data_len != 1) return NULL; critical = *data != 0; /* Tolerate BER booleans */ data = asn1_der_find_elem(seq, len, 2, &tag, &data_len); } if (unlikely(!data || tag != ASN1_ID_OCTET_STRING)) return NULL; if (out_critical) *out_critical = critical; if (out_len) *out_len = data_len; return data; } return NULL; } LIB_EXPORT enum l_cert_key_type l_cert_get_pubkey_type(struct l_cert *cert) { if (unlikely(!cert)) return L_CERT_KEY_UNKNOWN; return cert->pubkey_type; } /* * Note: Returns a new l_key object to be freed by the caller. */ LIB_EXPORT struct l_key *l_cert_get_pubkey(struct l_cert *cert) { if (unlikely(!cert)) return NULL; /* Use kernel's ASN.1 certificate parser to find the key data for us */ switch (cert->pubkey_type) { case L_CERT_KEY_RSA: return l_key_new(L_KEY_RSA, cert->asn1, cert->asn1_len); case L_CERT_KEY_ECC: return l_key_new(L_KEY_ECC, cert->asn1, cert->asn1_len); case L_CERT_KEY_UNKNOWN: break; } return NULL; } /* * Note: takes ownership of the certificate. The certificate is * assumed to be new and not linked into any certchain object. */ struct l_certchain *certchain_new_from_leaf(struct l_cert *leaf) { struct l_certchain *chain; chain = l_new(struct l_certchain, 1); chain->leaf = leaf; chain->ca = leaf; return chain; } /* * Note: takes ownership of the certificate. The certificate is * assumed to be new and not linked into any certchain object. */ void certchain_link_issuer(struct l_certchain *chain, struct l_cert *ca) { ca->issued = chain->ca; chain->ca->issuer = ca; chain->ca = ca; } static struct l_cert *certchain_pop_ca(struct l_certchain *chain) { struct l_cert *ca = chain->ca; if (!ca) return NULL; if (ca->issued) { chain->ca = ca->issued; ca->issued->issuer = NULL; ca->issued = NULL; } else { chain->ca = NULL; chain->leaf = NULL; } return ca; } LIB_EXPORT void l_certchain_free(struct l_certchain *chain) { while (chain && chain->ca) l_cert_free(certchain_pop_ca(chain)); l_free(chain); } LIB_EXPORT struct l_cert *l_certchain_get_leaf(struct l_certchain *chain) { if (unlikely(!chain)) return NULL; return chain->leaf; } /* * Call @cb for each certificate in the chain starting from the leaf * certificate. Stop if a call returns @true. */ LIB_EXPORT void l_certchain_walk_from_leaf(struct l_certchain *chain, l_cert_walk_cb_t cb, void *user_data) { struct l_cert *cert; if (unlikely(!chain)) return; for (cert = chain->leaf; cert; cert = cert->issuer) if (cb(cert, user_data)) break; } /* * Call @cb for each certificate in the chain starting from the root * certificate. Stop if a call returns @true. */ LIB_EXPORT void l_certchain_walk_from_ca(struct l_certchain *chain, l_cert_walk_cb_t cb, void *user_data) { struct l_cert *cert; if (unlikely(!chain)) return; for (cert = chain->ca; cert; cert = cert->issued) if (cb(cert, user_data)) break; } static struct l_keyring *cert_set_to_keyring(struct l_cert **certs, char *error) { struct l_keyring *ring; int i = 1; int count; ring = l_keyring_new(); if (!ring) return NULL; for (count = 0; certs[count]; count++); for (; *certs; certs++) { struct l_cert *cert = *certs; struct l_key *key = l_cert_get_pubkey(cert); if (!key) { sprintf(error, "Can't get public key from certificate " "%i / %i in certificate set", i, count); goto cleanup; } if (!l_keyring_link(ring, key)) { l_key_free(key); sprintf(error, "Can't link the public key from " "certificate %i / %i to target keyring", i, count); goto cleanup; } l_key_free_norevoke(key); i++; } return ring; cleanup: l_keyring_free(ring); return NULL; } static bool cert_is_in_set(struct l_cert *cert, struct l_cert **set) { for (; *set; set++) { struct l_cert *cert2 = *set; if (cert == cert2) return true; if (cert->asn1_len == cert2->asn1_len && !memcmp(cert->asn1, cert2->asn1, cert->asn1_len)) return true; } return false; } static struct l_cert **cert_set_filter_by_validity(struct l_queue *set, uint64_t now, int *out_total, int *out_valid) { const struct l_queue_entry *entry; _auto_(l_free) struct l_cert **valid; *out_total = l_queue_length(set); *out_valid = 0; valid = l_new(struct l_cert *, *out_total + 1); for (entry = l_queue_get_entries(set); entry; entry = entry->next) { struct l_cert *cert = entry->data; uint64_t not_before; uint64_t not_after; if (!l_cert_get_valid_times(cert, ¬_before, ¬_after)) return NULL; if (now < not_before || (not_after && now > not_after)) continue; valid[(*out_valid)++] = cert; } return l_steal_ptr(valid); } static struct l_key *cert_try_link(struct l_cert *cert, struct l_keyring *ring) { struct l_key *key; key = l_key_new(L_KEY_RSA, cert->asn1, cert->asn1_len); if (!key) return NULL; if (l_keyring_link(ring, key)) return key; l_key_free(key); return NULL; } #define RETURN_ERROR(msg, args...) \ do { \ if (error) { \ *error = error_buf; \ snprintf(error_buf, sizeof(error_buf), msg, ## args); \ } \ return false; \ } while (0) LIB_EXPORT bool l_certchain_verify(struct l_certchain *chain, struct l_queue *ca_certs, const char **error) { struct l_keyring *ca_ring = NULL; _auto_(l_keyring_free) struct l_keyring *verify_ring = NULL; struct l_cert *cert; struct l_key *prev_key = NULL; int verified = 0; int ca_match = 0; int i; static char error_buf[1024]; int total = 0; uint64_t now; _auto_(l_free) struct l_cert **ca_certs_valid = NULL; int ca_certs_total_count = 0; int ca_certs_valid_count = 0; if (unlikely(!chain || !chain->leaf)) RETURN_ERROR("Chain empty"); for (cert = chain->ca; cert; cert = cert->issued, total++); now = time_realtime_now(); for (cert = chain->ca, i = 0; cert; cert = cert->issued, i++) { uint64_t not_before; uint64_t not_after; char time_str[100]; if (unlikely(!l_cert_get_valid_times(cert, ¬_before, ¬_after))) RETURN_ERROR("Can't parse validity in certificate " "%i / %i", i + 1, total); if (unlikely(now < not_before)) { time_t t = not_before / L_USEC_PER_SEC; struct tm *tm = gmtime(&t); if (!tm || !strftime(time_str, sizeof(time_str), "%a %F %T UTC", tm)) strcpy(time_str, ""); RETURN_ERROR("Certificate %i / %i not valid before %s", i + 1, total, time_str); } if (unlikely(not_after && now > not_after)) { time_t t = not_after / L_USEC_PER_SEC; struct tm *tm = gmtime(&t); if (!tm || !strftime(time_str, sizeof(time_str), "%a %F %T UTC", tm)) strcpy(time_str, ""); RETURN_ERROR("Certificate %i / %i expired on %s", i + 1, total, time_str); } } if (ca_certs) { if (unlikely(l_queue_isempty(ca_certs))) RETURN_ERROR("No trusted CA certificates"); ca_certs_valid = cert_set_filter_by_validity(ca_certs, now, &ca_certs_total_count, &ca_certs_valid_count); if (unlikely(!ca_certs_valid)) RETURN_ERROR("Can't parse validity in CA cert(s)"); if (unlikely(!ca_certs_valid_count)) RETURN_ERROR("All trusted CA certs are expired or " "not-yet-valid"); for (cert = chain->ca, i = 0; cert; cert = cert->issued, i++) if (cert_is_in_set(cert, ca_certs_valid)) { ca_match = i + 1; break; } } verify_ring = l_keyring_new(); if (!verify_ring) RETURN_ERROR("Can't create verify keyring"); cert = chain->ca; /* * For TLS compatibility the trusted root CA certificate is * optionally present in the chain. * * RFC5246 7.4.2: * "Because certificate validation requires that root keys be * distributed independently, the self-signed certificate that * specifies the root certificate authority MAY be omitted from * the chain, under the assumption that the remote end must * already possess it in order to validate it in any case." * * The following is an optimization to skip verifying the root * cert in the chain if it is bitwise-identical to one of the * trusted CA certificates. In that case we don't have to load * all of the trusted certificates into the kernel, link them * to @ca_ring or link @ca_ring to @verify_ring, instead we * load the first certificate into @verify_ring before we set * the restrict mode on it, same as when no trusted CAs are * provided. * * Note this happens to work around a kernel issue preventing * self-signed certificates missing the optional AKID extension * from being linked to a restricted keyring. That issue would * have affected us if the trusted CA set included such * certificate and the same certificate was at the root of * the chain. */ if (ca_certs && !ca_match) { ca_ring = cert_set_to_keyring(ca_certs_valid, error_buf); if (!ca_ring) { if (error) *error = error_buf; return false; } if (!l_keyring_link_nested(verify_ring, ca_ring)) { l_keyring_free(ca_ring); RETURN_ERROR("Can't link CA ring to verify ring"); } } else prev_key = cert_try_link(cert, verify_ring); /* * The top, unverified certificate(s) are linked to the keyring and * we can now force verification of any new certificates linked. */ if (!l_keyring_restrict(verify_ring, L_KEYRING_RESTRICT_ASYM_CHAIN, NULL)) { l_key_free(prev_key); l_keyring_free(ca_ring); RETURN_ERROR("Can't restrict verify keyring"); } if (ca_ring) { /* * Verify the first certificate outside of the loop, then * revoke the trusted CAs' keys so that only the newly * verified cert's public key remains in the ring. */ prev_key = cert_try_link(cert, verify_ring); l_keyring_free(ca_ring); } cert = cert->issued; /* Verify the rest of the chain */ while (prev_key && cert) { struct l_key *new_key = cert_try_link(cert, verify_ring); /* * Free and revoke the issuer's public key again leaving only * new_key in verify_ring to ensure the next certificate linked * is signed by the owner of this key. */ l_key_free(prev_key); prev_key = new_key; cert = cert->issued; verified++; } if (!prev_key) { char str1[100]; char str2[100] = ""; if (ca_match) snprintf(str1, sizeof(str1), "%i / %i matched a trusted" " certificate, root not verified", ca_match, total); else snprintf(str1, sizeof(str1), "root %sverified against " "trusted CA(s)", ca_certs && verified ? "" : "not "); if (ca_certs && !ca_match && !verified && ca_certs_valid_count < ca_certs_total_count) snprintf(str2, sizeof(str2), ", %i out of %i trused " "CA(s) were expired or not-yet-valid", ca_certs_total_count - ca_certs_valid_count, ca_certs_total_count); RETURN_ERROR("Linking certificate %i / %i failed, %s%s", verified + 1, total, str1, str2); } l_key_free(prev_key); return true; } struct l_key *cert_key_from_pkcs8_private_key_info(const uint8_t *der, size_t der_len) { return l_key_new(L_KEY_RSA, der, der_len); } /* * The passphrase, if given, must have been validated as UTF-8 unless the * caller knows that PKCS#12 encryption algorithms are not used. * Use l_utf8_validate. */ struct l_key *cert_key_from_pkcs8_encrypted_private_key_info(const uint8_t *der, size_t der_len, const char *passphrase) { const uint8_t *key_info, *alg_id, *data; uint8_t tag; size_t key_info_len, alg_id_len, data_len, tmp_len; struct l_cipher *alg; uint8_t *decrypted; struct l_key *pkey; bool r; bool is_block; size_t decrypted_len; /* Technically this is BER, not limited to DER */ key_info = asn1_der_find_elem(der, der_len, 0, &tag, &key_info_len); if (!key_info || tag != ASN1_ID_SEQUENCE) return NULL; alg_id = asn1_der_find_elem(key_info, key_info_len, 0, &tag, &alg_id_len); if (!alg_id || tag != ASN1_ID_SEQUENCE) return NULL; data = asn1_der_find_elem(key_info, key_info_len, 1, &tag, &data_len); if (!data || tag != ASN1_ID_OCTET_STRING || data_len < 8 || (data_len & 7) != 0) return NULL; if (asn1_der_find_elem(der, der_len, 2, &tag, &tmp_len)) return NULL; alg = cert_cipher_from_pkcs_alg_id(alg_id, alg_id_len, passphrase, &is_block); if (!alg) return NULL; decrypted = l_malloc(data_len); r = l_cipher_decrypt(alg, data, decrypted, data_len); l_cipher_free(alg); if (!r) { l_free(decrypted); return NULL; } decrypted_len = data_len; /* * For block ciphers strip padding as defined in RFC8018 * (for PKCS#5 v1) or RFC1423 / RFC5652 (for v2). */ if (is_block) { uint8_t pad = decrypted[data_len - 1]; pkey = NULL; if (pad > data_len || pad > 16 || pad == 0) goto cleanup; if (!l_secure_memeq(decrypted + data_len - pad, pad - 1U, pad)) goto cleanup; decrypted_len -= pad; } pkey = cert_key_from_pkcs8_private_key_info(decrypted, decrypted_len); cleanup: explicit_bzero(decrypted, data_len); l_free(decrypted); return pkey; } struct l_key *cert_key_from_pkcs1_rsa_private_key(const uint8_t *der, size_t der_len) { const uint8_t *data; uint8_t tag; size_t data_len; const uint8_t *key_data; size_t key_data_len; int i; uint8_t *private_key; size_t private_key_len; uint8_t *one_asymmetric_key; uint8_t *ptr; struct l_key *pkey; static const uint8_t version0[] = { ASN1_ID_INTEGER, 0x01, 0x00 }; static const uint8_t pkcs1_rsa_encryption[] = { ASN1_ID_SEQUENCE, 0x0d, ASN1_ID_OID, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, ASN1_ID_NULL, 0x00, }; /* * Sanity check that it's a version 0 or 1 RSAPrivateKey structure * with the 8 integers. */ key_data = asn1_der_find_elem(der, der_len, 0, &tag, &key_data_len); if (!key_data || tag != ASN1_ID_SEQUENCE) return NULL; data = asn1_der_find_elem(key_data, key_data_len, 0, &tag, &data_len); if (!data || tag != ASN1_ID_INTEGER || data_len != 1 || (data[0] != 0x00 && data[0] != 0x01)) return NULL; for (i = 1; i < 9; i++) { data = asn1_der_find_elem(key_data, key_data_len, i, &tag, &data_len); if (!data || tag != ASN1_ID_INTEGER || data_len < 1) return NULL; } private_key = l_malloc(10 + der_len); ptr = private_key; *ptr++ = ASN1_ID_OCTET_STRING; asn1_write_definite_length(&ptr, der_len); memcpy(ptr, der, der_len); ptr += der_len; private_key_len = ptr - private_key; one_asymmetric_key = l_malloc(32 + private_key_len); ptr = one_asymmetric_key; *ptr++ = ASN1_ID_SEQUENCE; asn1_write_definite_length(&ptr, sizeof(version0) + sizeof(pkcs1_rsa_encryption) + private_key_len); memcpy(ptr, version0, sizeof(version0)); ptr += sizeof(version0); memcpy(ptr, pkcs1_rsa_encryption, sizeof(pkcs1_rsa_encryption)); ptr += sizeof(pkcs1_rsa_encryption); memcpy(ptr, private_key, private_key_len); ptr += private_key_len; explicit_bzero(private_key, private_key_len); l_free(private_key); pkey = cert_key_from_pkcs8_private_key_info(one_asymmetric_key, ptr - one_asymmetric_key); explicit_bzero(one_asymmetric_key, ptr - one_asymmetric_key); l_free(one_asymmetric_key); return pkey; } static const uint8_t *cert_unpack_pkcs7_content_info(const uint8_t *container, size_t container_len, int pos, const struct asn1_oid *expected_oid, struct asn1_oid *out_oid, uint8_t *out_tag, size_t *out_len) { const uint8_t *content_info; size_t content_info_len; const uint8_t *type; size_t type_len; const uint8_t *ret; uint8_t tag; if (!(content_info = asn1_der_find_elem(container, container_len, pos, &tag, &content_info_len)) || tag != ASN1_ID_SEQUENCE) return NULL; if (!(type = asn1_der_find_elem(content_info, content_info_len, 0, &tag, &type_len)) || tag != ASN1_ID_OID || type_len > sizeof(out_oid->asn1)) return NULL; if (expected_oid && !asn1_oid_eq(expected_oid, type_len, type)) return NULL; if (!(ret = asn1_der_find_elem(content_info, content_info_len, ASN1_CONTEXT_EXPLICIT(0), out_tag, out_len)) || ret + *out_len != content_info + content_info_len) return NULL; if (out_oid) { out_oid->asn1_len = type_len; memcpy(out_oid->asn1, type, type_len); } return ret; } /* RFC5652 Section 8 */ static uint8_t *cert_decrypt_pkcs7_encrypted_data(const uint8_t *data, size_t data_len, const char *password, struct asn1_oid *out_oid, size_t *out_len) { const uint8_t *version; size_t version_len; const uint8_t *encrypted_info; size_t encrypted_info_len; const uint8_t *type; size_t type_len; const uint8_t *alg_id; size_t alg_id_len; const uint8_t *encrypted; size_t encrypted_len; uint8_t tag; struct l_cipher *alg; uint8_t *plaintext; int i; bool ok; bool is_block; if (!(version = asn1_der_find_elem(data, data_len, 0, &tag, &version_len)) || tag != ASN1_ID_INTEGER || version_len != 1 || !L_IN_SET(version[0], 0, 2)) return NULL; if (!(encrypted_info = asn1_der_find_elem(data, data_len, 1, &tag, &encrypted_info_len)) || tag != ASN1_ID_SEQUENCE) return NULL; if (!(type = asn1_der_find_elem(encrypted_info, encrypted_info_len, 0, &tag, &type_len)) || tag != ASN1_ID_OID || type_len > sizeof(out_oid->asn1)) return NULL; if (!(alg_id = asn1_der_find_elem(encrypted_info, encrypted_info_len, 1, &tag, &alg_id_len)) || tag != ASN1_ID_SEQUENCE) return NULL; /* Not optional in our case, defined [0] IMPLICIT OCTET STRING */ if (!(encrypted = asn1_der_find_elem(encrypted_info, encrypted_info_len, ASN1_CONTEXT_IMPLICIT(0), &tag, &encrypted_len)) || tag != ASN1_ID(ASN1_CLASS_CONTEXT, 0, 0) || encrypted_len < 8) return NULL; if (!(alg = cert_cipher_from_pkcs_alg_id(alg_id, alg_id_len, password, &is_block))) return NULL; plaintext = l_malloc(encrypted_len); ok = l_cipher_decrypt(alg, encrypted, plaintext, encrypted_len); l_cipher_free(alg); if (!ok) { l_free(plaintext); return NULL; } if (is_block) { bool ok = true; /* Also validate the padding */ if (encrypted_len < plaintext[encrypted_len - 1] || plaintext[encrypted_len - 1] > 16) { plaintext[encrypted_len - 1] = 1; ok = false; } for (i = 1; i < plaintext[encrypted_len - 1]; i++) if (plaintext[encrypted_len - 1 - i] != plaintext[encrypted_len - 1]) ok = false; if (!ok) { explicit_bzero(plaintext, encrypted_len); l_free(plaintext); return NULL; } encrypted_len -= plaintext[encrypted_len - 1]; } if (out_oid) { out_oid->asn1_len = type_len; memcpy(out_oid->asn1, type, type_len); } *out_len = encrypted_len; return plaintext; } /* RFC7292 Appendix A. */ static const struct cert_pkcs12_hash pkcs12_mac_algs[] = { { L_CHECKSUM_MD5, 16, 16, 64, { 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0f, 0x02, 0x05 } } }, { L_CHECKSUM_SHA1, 20, 20, 64, { 5, { 0x2b, 0x0e, 0x03, 0x02, 0x1a } } }, { L_CHECKSUM_SHA224, 28, 28, 64, { 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04 } } }, { L_CHECKSUM_SHA256, 32, 32, 64, { 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 } } }, { L_CHECKSUM_SHA384, 48, 48, 128, { 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 } } }, { L_CHECKSUM_SHA512, 64, 64, 128, { 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 } } }, { L_CHECKSUM_SHA512, 64, 28, 128, { 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x05 } } }, { L_CHECKSUM_SHA512, 64, 32, 128, { 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x06 } } }, }; static const struct asn1_oid pkcs12_key_bag_oid = { 11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x01 } }; static const struct asn1_oid pkcs12_pkcs8_shrouded_key_bag_oid = { 11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x02 } }; static const struct asn1_oid pkcs12_cert_bag_oid = { 11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x03 } }; static const struct asn1_oid pkcs12_safe_contents_bag_oid = { 11, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x06 } }; static const struct asn1_oid pkcs9_x509_certificate_oid = { 10, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x16, 0x01 } }; /* RFC7292 Section 4.2.3 */ static bool cert_parse_pkcs12_cert_bag(const uint8_t *data, size_t data_len, struct l_certchain **out_certchain) { const uint8_t *cert_bag; size_t cert_bag_len; const uint8_t *cert_id; size_t cert_id_len; const uint8_t *cert_value; size_t cert_value_len; uint8_t tag; struct l_cert *cert; if (!(cert_bag = asn1_der_find_elem(data, data_len, 0, &tag, &cert_bag_len)) || tag != ASN1_ID_SEQUENCE) return false; if (!(cert_id = asn1_der_find_elem(cert_bag, cert_bag_len, 0, &tag, &cert_id_len)) || tag != ASN1_ID_OID) return false; if (!(cert_value = asn1_der_find_elem(cert_bag, cert_bag_len, ASN1_CONTEXT_EXPLICIT(0), &tag, &cert_value_len)) || tag != ASN1_ID_OCTET_STRING || cert_value + cert_value_len != data + data_len) return false; /* Skip unsupported certificate types */ if (!asn1_oid_eq(&pkcs9_x509_certificate_oid, cert_id_len, cert_id)) return true; if (!(cert = l_cert_new_from_der(cert_value, cert_value_len))) return false; if (!*out_certchain) *out_certchain = certchain_new_from_leaf(cert); else certchain_link_issuer(*out_certchain, cert); return true; } static bool cert_parse_pkcs12_safe_contents(const uint8_t *data, size_t data_len, const char *password, struct l_certchain **out_certchain, struct l_key **out_privkey) { const uint8_t *safe_contents; size_t safe_contents_len; uint8_t tag; if (!(safe_contents = asn1_der_find_elem(data, data_len, 0, &tag, &safe_contents_len)) || tag != ASN1_ID_SEQUENCE || data + data_len != safe_contents + safe_contents_len) return false; /* RFC7292 Section 4.2 */ while (safe_contents_len) { const uint8_t *safe_bag; size_t safe_bag_len; const uint8_t *bag_id; size_t bag_id_len; const uint8_t *bag_value; int bag_value_len; /* RFC7292 Section 4.2 */ if (!(safe_bag = asn1_der_find_elem(safe_contents, safe_contents_len, 0, &tag, &safe_bag_len)) || tag != ASN1_ID_SEQUENCE) return false; if (!(bag_id = asn1_der_find_elem(safe_bag, safe_bag_len, 0, &tag, &bag_id_len)) || tag != ASN1_ID_OID) return false; /* * The bagValue is EXPLICITly tagged but we don't want to * unpack the inner TLV yet so don't use asn1_der_find_elem. */ safe_bag_len -= bag_id + bag_id_len - safe_bag; safe_bag = bag_id + bag_id_len; if (safe_bag_len < 4) return false; tag = *safe_bag++; safe_bag_len--; bag_value_len = asn1_parse_definite_length(&safe_bag, &safe_bag_len); bag_value = safe_bag; if (bag_value_len < 0 || bag_value_len > (int) safe_bag_len || tag != ASN1_ID(ASN1_CLASS_CONTEXT, 1, 0)) return false; /* PKCS#9 attributes ignored */ safe_contents_len -= (safe_bag + safe_bag_len - safe_contents); safe_contents = safe_bag + safe_bag_len; if (asn1_oid_eq(&pkcs12_key_bag_oid, bag_id_len, bag_id)) { if (!out_privkey || *out_privkey) continue; *out_privkey = cert_key_from_pkcs8_private_key_info(bag_value, bag_value_len); if (!*out_privkey) return false; } else if (asn1_oid_eq(&pkcs12_pkcs8_shrouded_key_bag_oid, bag_id_len, bag_id)) { if (!out_privkey || *out_privkey) continue; *out_privkey = cert_key_from_pkcs8_encrypted_private_key_info( bag_value, bag_value_len, password); if (!*out_privkey) return false; } else if (asn1_oid_eq(&pkcs12_cert_bag_oid, bag_id_len, bag_id)) { if (!out_certchain) continue; if (!cert_parse_pkcs12_cert_bag(bag_value, bag_value_len, out_certchain)) return false; } else if (asn1_oid_eq(&pkcs12_safe_contents_bag_oid, bag_id_len, bag_id)) { /* TODO: depth check */ if (!(cert_parse_pkcs12_safe_contents(bag_value, bag_value_len, password, out_certchain, out_privkey))) return false; } } return true; } static bool cert_check_pkcs12_integrity(const uint8_t *mac_data, size_t mac_data_len, const uint8_t *auth_safe, size_t auth_safe_len, const char *password) { const uint8_t *mac; size_t mac_len; const uint8_t *mac_salt; size_t mac_salt_len; const uint8_t *iterations_data; size_t iterations_len; unsigned int iterations; const uint8_t *digest_alg; size_t digest_alg_len; const uint8_t *digest; size_t digest_len; const uint8_t *alg_id; size_t alg_id_len; const struct cert_pkcs12_hash *mac_hash; L_AUTO_FREE_VAR(uint8_t *, key) = NULL; struct l_checksum *hmac; uint8_t hmac_val[64]; uint8_t tag; bool ok; unsigned int i; if (!(mac = asn1_der_find_elem(mac_data, mac_data_len, 0, &tag, &mac_len)) || tag != ASN1_ID_SEQUENCE) return false; if (!(mac_salt = asn1_der_find_elem(mac_data, mac_data_len, 1, &tag, &mac_salt_len)) || tag != ASN1_ID_OCTET_STRING || mac_salt_len > 1024) return false; if (!(iterations_data = asn1_der_find_elem(mac_data, mac_data_len, 2, &tag, &iterations_len)) || tag != ASN1_ID_INTEGER || iterations_len > 4) return false; for (iterations = 0; iterations_len; iterations_len--) iterations = (iterations << 8) | *iterations_data++; if (iterations < 1 || iterations > 8192) return false; /* RFC2315 Section 9.4 */ if (!(digest_alg = asn1_der_find_elem(mac, mac_len, 0, &tag, &digest_alg_len)) || tag != ASN1_ID_SEQUENCE) return false; if (!(digest = asn1_der_find_elem(mac, mac_len, 1, &tag, &digest_len)) || tag != ASN1_ID_OCTET_STRING) return false; if (!(alg_id = asn1_der_find_elem(digest_alg, digest_alg_len, 0, &tag, &alg_id_len)) || tag != ASN1_ID_OID) return false; /* This is going to be used for both the MAC and its key derivation */ for (i = 0; i < L_ARRAY_SIZE(pkcs12_mac_algs); i++) if (asn1_oid_eq(&pkcs12_mac_algs[i].oid, alg_id_len, alg_id)) { mac_hash = &pkcs12_mac_algs[i]; break; } if (i == L_ARRAY_SIZE(pkcs12_mac_algs) || digest_len != mac_hash->u) return false; if (!(key = cert_pkcs12_pbkdf(password, mac_hash, mac_salt, mac_salt_len, iterations, 3, mac_hash->u))) return false; hmac = l_checksum_new_hmac(mac_hash->alg, key, mac_hash->u); explicit_bzero(key, mac_hash->u); if (!hmac) return false; ok = l_checksum_update(hmac, auth_safe, auth_safe_len) && l_checksum_get_digest(hmac, hmac_val, mac_hash->len) > 0; l_checksum_free(hmac); if (!ok) return false; /* * SHA-512/224 and SHA-512/256 are not supported. We can truncate the * output for key derivation but we can't do this inside the HMAC * algorithms based on these hashes. We skip the MAC verification * if one of these hashes is used (identified by .u != .len) */ if (mac_hash->u != mac_hash->len) return true; return l_secure_memcmp(hmac_val, digest, digest_len) == 0; } /* RFC5652 Section 4 */ static const struct asn1_oid pkcs7_data_oid = { 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01 } }; /* RFC5652 Section 8 */ static const struct asn1_oid pkcs7_encrypted_data_oid = { 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x06 } }; static bool cert_parse_auth_safe_content(const uint8_t *data, size_t data_len, uint8_t tag, const struct asn1_oid *data_oid, const char *password, struct l_certchain **out_certchain, struct l_key **out_privkey) { if (asn1_oid_eq(&pkcs7_encrypted_data_oid, data_oid->asn1_len, data_oid->asn1)) { uint8_t *plaintext; size_t plaintext_len; struct asn1_oid oid; bool ok; if (tag != ASN1_ID_SEQUENCE) return false; /* * This is same as PKCS#7 encryptedData but the ciphers * used are from PKCS#12 (broken but still the default * everywhere) and PKCS#5 (recommended). */ plaintext = cert_decrypt_pkcs7_encrypted_data(data, data_len, password, &oid, &plaintext_len); if (!plaintext) return false; /* * Since we only support PKCS#7 data and encryptedData * types, and there's no point re-encrypting * encryptedData, the plaintext must be a PKCS#7 * "data". */ ok = asn1_oid_eq(&pkcs7_data_oid, oid.asn1_len, oid.asn1) && cert_parse_pkcs12_safe_contents(plaintext, plaintext_len, password, out_certchain, out_privkey); explicit_bzero(plaintext, plaintext_len); l_free(plaintext); if (!ok) return false; } else if (asn1_oid_eq(&pkcs7_data_oid, data_oid->asn1_len, data_oid->asn1)) { if (tag != ASN1_ID_OCTET_STRING) return false; if (!cert_parse_pkcs12_safe_contents(data, data_len, password, out_certchain, out_privkey)) return false; } /* envelopedData support not needed */ return true; } static bool cert_parse_pkcs12_pfx(const uint8_t *ptr, size_t len, const char *password, struct l_certchain **out_certchain, struct l_key **out_privkey) { const uint8_t *version; size_t version_len; const uint8_t *auth_safe; size_t auth_safe_len; const uint8_t *mac_data; size_t mac_data_len; const uint8_t *auth_safe_seq; size_t auth_safe_seq_len; uint8_t tag; unsigned int i; struct l_certchain *certchain = NULL; struct l_key *privkey = NULL; /* RFC7292 Section 4 */ if (!(version = asn1_der_find_elem(ptr, len, 0, &tag, &version_len)) || tag != ASN1_ID_INTEGER) return false; if (version_len != 1 || version[0] != 3) return false; /* * Since we only support the password-based integrity mode, the * authSafe must be of PKCS#7 type "data" and not "signedData". */ if (!(auth_safe = cert_unpack_pkcs7_content_info(ptr, len, 1, &pkcs7_data_oid, NULL, &tag, &auth_safe_len)) || tag != ASN1_ID_OCTET_STRING) return false; /* * openssl can generate PFX structures without macData not signed * with a public key so handle this case, otherwise the macData * would not be optional. */ if (auth_safe + auth_safe_len == ptr + len) goto integrity_check_done; if (!(mac_data = asn1_der_find_elem(ptr, len, 2, &tag, &mac_data_len)) || tag != ASN1_ID_SEQUENCE) return false; if (!cert_check_pkcs12_integrity(mac_data, mac_data_len, auth_safe, auth_safe_len, password)) return false; integrity_check_done: if (!(auth_safe_seq = asn1_der_find_elem(auth_safe, auth_safe_len, 0, &tag, &auth_safe_seq_len)) || tag != ASN1_ID_SEQUENCE || auth_safe + auth_safe_len != auth_safe_seq + auth_safe_seq_len) return false; i = 0; while (1) { struct asn1_oid data_oid; const uint8_t *data; size_t data_len; if (!(data = cert_unpack_pkcs7_content_info(auth_safe_seq, auth_safe_seq_len, i++, NULL, &data_oid, &tag, &data_len))) goto error; if (!cert_parse_auth_safe_content(data, data_len, tag, &data_oid, password, out_certchain ? &certchain : NULL, out_privkey ? &privkey : NULL)) goto error; if (data + data_len == auth_safe_seq + auth_safe_seq_len) break; } if (out_certchain) *out_certchain = certchain; if (out_privkey) *out_privkey = privkey; return true; error: if (certchain) l_certchain_free(certchain); if (privkey) l_key_free(privkey); return false; } static int cert_try_load_der_format(const uint8_t *content, size_t content_len, const char *password, struct l_certchain **out_certchain, struct l_key **out_privkey, bool *out_encrypted) { const uint8_t *seq; size_t seq_len; size_t elem_len; uint8_t tag; if (!(seq = asn1_der_find_elem(content, content_len, 0, &tag, &seq_len))) /* May not have been a DER file after all */ return -ENOMSG; /* * See if the first sub-element is another sequence, then, out of * the formats that we currently support this can only be a raw * certificate. If integer, it's going to be PKCS#12. If we wish * to add any more formats we'll probably need to start guessing * from the filename suffix. */ if (!asn1_der_find_elem(seq, seq_len,0, &tag, &elem_len)) return -ENOMSG; if (tag == ASN1_ID_SEQUENCE) { if (out_certchain) { struct l_cert *cert; if (!(cert = l_cert_new_from_der(content, content_len))) return -EINVAL; *out_certchain = certchain_new_from_leaf(cert); if (out_privkey) *out_privkey = NULL; if (out_encrypted) *out_encrypted = false; return 0; } return -EINVAL; } if (tag == ASN1_ID_INTEGER) { /* * Since we don't support public key-protected PKCS#12 * modes, we always require the password at least for the * integrity check. Strictly speaking encryption may not * actually be in use. We also don't support files with * different integrity and privacy passwords, they must * be identical if privacy is enabled. */ if (out_encrypted) *out_encrypted = true; if (!password) { if (!out_encrypted) return -EINVAL; if (out_certchain) *out_certchain = NULL; if (out_privkey) *out_privkey = NULL; return 0; } if (cert_parse_pkcs12_pfx(seq, seq_len, password, out_certchain, out_privkey)) return 0; else return -EINVAL; } return -ENOMSG; } static bool cert_try_load_pem_format(const char *content, size_t content_len, const char *password, struct l_certchain **out_certchain, struct l_key **out_privkey, bool *out_encrypted) { bool error = false; bool done = false; struct l_certchain *certchain = NULL; struct l_key *privkey = NULL; bool encrypted = false; while (!done && !error && content_len) { uint8_t *der; size_t der_len; char *type_label; char *headers; const char *endp; if (!(der = pem_load_buffer(content, content_len, &type_label, &der_len, &headers, &endp))) break; content_len -= endp - content; content = endp; if (out_certchain && L_IN_STRSET(type_label, "CERTIFICATE")) { struct l_cert *cert; if (!(cert = l_cert_new_from_der(der, der_len))) { error = true; goto next; } if (!certchain) certchain = certchain_new_from_leaf(cert); else certchain_link_issuer(certchain, cert); goto next; } /* Only use the first private key found */ if (out_privkey && !privkey && L_IN_STRSET(type_label, "PRIVATE KEY", "ENCRYPTED PRIVATE KEY", "RSA PRIVATE KEY")) { privkey = pem_load_private_key(der, der_len, type_label, password, headers, &encrypted); if (!privkey) { if (certchain) { l_certchain_free(certchain); certchain = NULL; } if (password) error = true; else error = !encrypted || !out_encrypted; done = true; } continue; } /* Cisco/gnutls-type PEM-encoded PKCS#12, probably rare */ if (L_IN_STRSET(type_label, "PKCS12")) { encrypted = true; if (!password) { if (certchain && out_privkey) { l_certchain_free(certchain); certchain = NULL; } error = !out_encrypted; done = true; goto next; } error = !cert_parse_pkcs12_pfx(der, der_len, password, out_certchain ? &certchain : NULL, out_privkey ? &privkey : NULL); goto next; } next: explicit_bzero(der, der_len); l_free(der); l_free(type_label); l_free(headers); } if (error) { if (certchain) l_certchain_free(certchain); if (privkey) l_key_free(privkey); return false; } if (out_certchain) *out_certchain = certchain; if (out_privkey) *out_privkey = privkey; if (out_encrypted) *out_encrypted = encrypted; return true; } /* * Look at a file, try to detect which of the few X.509 certificate and/or * private key container formats it uses and load any certificates in it as * a certificate chain object, and load the first private key as an l_key * object. * * Currently supported are: * PEM X.509 certificates * PEM PKCS#8 encrypted and unencrypted private keys * PEM legacy PKCS#1 encrypted and unencrypted private keys * Raw X.509 certificates (.cer, .der, .crt) * PKCS#12 certificates * PKCS#12 encrypted private keys * * The raw format contains exactly one certificate, PEM and PKCS#12 files * can contain any combination of certificates and private keys. * * The password must have been validated as UTF-8 (use l_utf8_validate) * unless the caller knows that no PKCS#12-defined encryption algorithm * or MAC is used. * * Returns false on "unrecoverable" errors, and *out_certchain, * *out_privkey and *out_encrypted (if provided) are not modified. However * when true is returned, *out_certchain and *out_privkey (if provided) may * be set to NULL when nothing could be loaded only due to missing password, * and *out_encrypted (if provided) will be set accordingly. It will also * be set on success to indicate whether the password was used. * *out_certchain and/or *out_privkey will also be NULL if the container * was loaded but there were no certificates or private keys in it. */ LIB_EXPORT bool l_cert_load_container_file(const char *filename, const char *password, struct l_certchain **out_certchain, struct l_key **out_privkey, bool *out_encrypted) { struct pem_file_info file; bool error = true; if (unlikely(!filename)) return false; if (pem_file_open(&file, filename) < 0) return false; if (file.st.st_size < 1) goto close; /* See if we have a DER sequence tag at the start */ if (file.data[0] == ASN1_ID_SEQUENCE) { int err; err = cert_try_load_der_format(file.data, file.st.st_size, password, out_certchain, out_privkey, out_encrypted); if (!err) { error = false; goto close; } if (err != -ENOMSG) goto close; /* Try other formats */ } /* * For backwards compatibility try the TLS internal struct Certificate * format as may be captured by PCAP (no future support guaranteed). */ if (out_certchain && !password && file.st.st_size && tls_parse_certificate_list(file.data, file.st.st_size, out_certchain) == 0) { error = false; if (out_privkey) *out_privkey = NULL; if (out_encrypted) *out_encrypted = false; goto close; } /* * RFC 7486 allows whitespace and possibly other data before the * PEM "encapsulation boundary" so rather than check if the start * of the data looks like PEM, we fall back to this format if the * data didn't look like anything else we knew about. Note this * succeeds for empty files and files without any PEM markers, * returning NULL chain and privkey. */ if (cert_try_load_pem_format((const char *) file.data, file.st.st_size, password, out_certchain, out_privkey, out_encrypted)) error = false; close: pem_file_close(&file); return !error; } bluez-5.87/ell/PaxHeaders/log.h0000644000000000000000000000005014567163422013373 xustar0020 atime=1783076231 20 ctime=1783094098 bluez-5.87/ell/log.h0000644000000000000000000000554214567163422013062 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_LOG_H #define __ELL_LOG_H #include #include #ifdef __cplusplus extern "C" { #endif #define L_LOG_ERR 3 #define L_LOG_WARNING 4 #define L_LOG_NOTICE 5 #define L_LOG_INFO 6 #define L_LOG_DEBUG 7 typedef void (*l_log_func_t) (int priority, const char *file, const char *line, const char *func, const char *format, va_list ap); void l_log_set_ident(const char *ident); void l_log_set_handler(l_log_func_t function); void l_log_set_null(void); void l_log_set_stderr(void); void l_log_set_syslog(void); void l_log_set_journal(void); void l_log_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) __attribute__((format(printf, 5, 6))); #define l_log(priority, format, ...) l_log_with_location(priority, \ __FILE__, L_STRINGIFY(__LINE__), \ __func__, format "\n", ##__VA_ARGS__) struct l_debug_desc { const char *file; const char *func; #define L_DEBUG_FLAG_DEFAULT (0) #define L_DEBUG_FLAG_PRINT (1 << 0) unsigned int flags; } __attribute__((aligned(8))); /* * Set the retain attribute so that the section cannot be discarded by ld * --gc-sections -z start-stop-gc. Older compilers would warn for the unknown * attribute, so just disable -Wattributes. */ #define L_DEBUG_SYMBOL(symbol, format, ...) do { \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wattributes\"") \ static struct l_debug_desc symbol \ __attribute__((used, retain, section("__ell_debug"), aligned(8))) = { \ .file = __FILE__, .func = __func__, \ .flags = L_DEBUG_FLAG_DEFAULT, \ }; \ _Pragma("GCC diagnostic pop") \ if (symbol.flags & L_DEBUG_FLAG_PRINT) \ l_log(L_LOG_DEBUG, "%s:%s() " format, __FILE__, \ __func__ , ##__VA_ARGS__); \ } while (0) void l_debug_enable_full(const char *pattern, struct l_debug_desc *start, struct l_debug_desc *end); void l_debug_add_section(struct l_debug_desc *start, struct l_debug_desc *end); #define l_debug_enable(pattern) do { \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wredundant-decls\"") \ extern struct l_debug_desc __start___ell_debug[]; \ extern struct l_debug_desc __stop___ell_debug[]; \ l_debug_enable_full(pattern, __start___ell_debug, __stop___ell_debug); \ _Pragma("GCC diagnostic pop") \ } while (0) void l_debug_disable(void); #define l_error(format, ...) l_log(L_LOG_ERR, format, ##__VA_ARGS__) #define l_warn(format, ...) l_log(L_LOG_WARNING, format, ##__VA_ARGS__) #define l_notice(format, ...) l_log(L_LOG_NOTICE, format, ##__VA_ARGS__) #define l_info(format, ...) l_log(L_LOG_INFO, format, ##__VA_ARGS__) #define l_debug(format, ...) L_DEBUG_SYMBOL(__debug_desc, format, ##__VA_ARGS__) #ifdef __cplusplus } #endif #endif /* __ELL_LOG_H */ bluez-5.87/ell/PaxHeaders/main.h0000644000000000000000000000005014504767710013537 xustar0020 atime=1783076232 20 ctime=1783094098 bluez-5.87/ell/main.h0000644000000000000000000000120314504767710013214 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_MAIN_H #define __ELL_MAIN_H #include #include #ifdef __cplusplus extern "C" { #endif bool l_main_init(void); int l_main_prepare(void); void l_main_iterate(int timeout); int l_main_run(void); bool l_main_exit(void); bool l_main_quit(void); typedef void (*l_main_signal_cb_t) (uint32_t signo, void *user_data); int l_main_run_with_signal(l_main_signal_cb_t callback, void *user_data); int l_main_get_epoll_fd(void); #ifdef __cplusplus } #endif #endif /* __ELL_MAIN_H */ bluez-5.87/ell/PaxHeaders/pem.h0000644000000000000000000000005014504767710013374 xustar0020 atime=1783076232 20 ctime=1783094098 bluez-5.87/ell/pem.h0000644000000000000000000000206514504767710013060 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2015 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_PEM_H #define __ELL_PEM_H #ifdef __cplusplus extern "C" { #endif struct l_queue; struct l_key; struct l_cert; struct l_certchain; uint8_t *l_pem_load_buffer(const void *buf, size_t buf_len, char **type_label, size_t *out_len); uint8_t *l_pem_load_file(const char *filename, char **type_label, size_t *len); struct l_certchain *l_pem_load_certificate_chain(const char *filename); struct l_certchain *l_pem_load_certificate_chain_from_data(const void *buf, size_t len); struct l_queue *l_pem_load_certificate_list(const char *filename); struct l_queue *l_pem_load_certificate_list_from_data(const void *buf, size_t len); struct l_key *l_pem_load_private_key(const char *filename, const char *passphrase, bool *encrypted); struct l_key *l_pem_load_private_key_from_data(const void *buf, size_t len, const char *passphrase, bool *encrypted); #ifdef __cplusplus } #endif #endif /* __ELL_PEM_H */ bluez-5.87/ell/PaxHeaders/util.h0000644000000000000000000000005015040645364013564 xustar0020 atime=1783076231 20 ctime=1783094098 bluez-5.87/ell/util.h0000644000000000000000000003661515040645364013260 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_UTIL_H #define __ELL_UTIL_H #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define l_container_of(ptr, type, member) ({ \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wcast-align\"") \ const __typeof__(((type *) 0)->member) *__mptr = (ptr); \ (type *)((char *) __mptr - offsetof(type, member)); \ _Pragma("GCC diagnostic pop") \ }) #define L_STRINGIFY(val) L_STRINGIFY_ARG(val) #define L_STRINGIFY_ARG(contents) #contents #define L_WARN_ON(condition) __extension__ ({ \ bool r = !!(condition); \ if (__builtin_expect(r, 0)) \ l_warn("WARNING: %s:%s() condition %s failed", \ __FILE__, __func__, \ #condition); \ r; \ }) /* * If ELL headers and interfaces end up getting compiled in a C++ * environment, even though ELL itself is a C source based and is * compiled as such, certain assignments may be flagged by the C++ * compiler as errors or warnings. The following portable casts should * be used in such cases, with a preference towards L_PERMISSIVE_CAST * where possible since it is not a cast in C and, therefore, will not * mask otherwise-legitimate warnings in that environment. */ #ifdef __cplusplus #define L_CONST_CAST(t, v) const_cast(v) #define L_REINTERPRET_CAST(t, v) reinterpret_cast(v) #define L_STATIC_CAST(t, v) static_cast(v) #define L_PERMISSIVE_CAST(t, v) L_STATIC_CAST(t, v) #else #define L_CONST_CAST(t, v) ((t)(v)) #define L_REINTERPRET_CAST(t, v) ((t)(v)) #define L_STATIC_CAST(t, v) ((t)(v)) #define L_PERMISSIVE_CAST(t, v) (v) #endif #define L_PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) #define L_UINT_TO_PTR(u) ((void *) ((uintptr_t) (u))) #define L_PTR_TO_INT(p) ((int) ((intptr_t) (p))) #define L_INT_TO_PTR(u) ((void *) ((intptr_t) (u))) #define L_GET_UNALIGNED(ptr) __extension__ \ ({ \ struct __attribute__((packed)) { \ __typeof__(*(ptr)) __v; \ } *__p = (__typeof__(__p)) (ptr); \ __p->__v; \ }) #define L_PUT_UNALIGNED(val, ptr) \ do { \ struct __attribute__((packed)) { \ __typeof__(*(ptr)) __v; \ } *__p = (__typeof__(__p)) (ptr); \ __p->__v = (val); \ } while(0) #if __BYTE_ORDER == __LITTLE_ENDIAN #define L_LE16_TO_CPU(val) (val) #define L_LE32_TO_CPU(val) (val) #define L_LE64_TO_CPU(val) (val) #define L_CPU_TO_LE16(val) (val) #define L_CPU_TO_LE32(val) (val) #define L_CPU_TO_LE64(val) (val) #define L_BE16_TO_CPU(val) bswap_16(val) #define L_BE32_TO_CPU(val) bswap_32(val) #define L_BE64_TO_CPU(val) bswap_64(val) #define L_CPU_TO_BE16(val) bswap_16(val) #define L_CPU_TO_BE32(val) bswap_32(val) #define L_CPU_TO_BE64(val) bswap_64(val) #elif __BYTE_ORDER == __BIG_ENDIAN #define L_LE16_TO_CPU(val) bswap_16(val) #define L_LE32_TO_CPU(val) bswap_32(val) #define L_LE64_TO_CPU(val) bswap_64(val) #define L_CPU_TO_LE16(val) bswap_16(val) #define L_CPU_TO_LE32(val) bswap_32(val) #define L_CPU_TO_LE64(val) bswap_64(val) #define L_BE16_TO_CPU(val) (val) #define L_BE32_TO_CPU(val) (val) #define L_BE64_TO_CPU(val) (val) #define L_CPU_TO_BE16(val) (val) #define L_CPU_TO_BE32(val) (val) #define L_CPU_TO_BE64(val) (val) #else #error "Unknown byte order" #endif #if __STDC_VERSION__ <= 199409L #define inline __inline__ #endif static inline uint8_t l_get_u8(const void *ptr) { return *((const uint8_t *) ptr); } static inline void l_put_u8(uint8_t val, void *ptr) { *((uint8_t *) ptr) = val; } static inline uint16_t l_get_u16(const void *ptr) { return L_GET_UNALIGNED((const uint16_t *) ptr); } static inline void l_put_u16(uint16_t val, void *ptr) { L_PUT_UNALIGNED(val, (uint16_t *) ptr); } static inline uint32_t l_get_u32(const void *ptr) { return L_GET_UNALIGNED((const uint32_t *) ptr); } static inline void l_put_u32(uint32_t val, void *ptr) { L_PUT_UNALIGNED(val, (uint32_t *) ptr); } static inline uint64_t l_get_u64(const void *ptr) { return L_GET_UNALIGNED((const uint64_t *) ptr); } static inline void l_put_u64(uint64_t val, void *ptr) { L_PUT_UNALIGNED(val, (uint64_t *) ptr); } static inline int16_t l_get_s16(const void *ptr) { return L_GET_UNALIGNED((const int16_t *) ptr); } static inline int32_t l_get_s32(const void *ptr) { return L_GET_UNALIGNED((const int32_t *) ptr); } static inline int64_t l_get_s64(const void *ptr) { return L_GET_UNALIGNED((const int64_t *) ptr); } static inline uint16_t l_get_le16(const void *ptr) { return L_LE16_TO_CPU(L_GET_UNALIGNED((const uint16_t *) ptr)); } static inline uint16_t l_get_be16(const void *ptr) { return L_BE16_TO_CPU(L_GET_UNALIGNED((const uint16_t *) ptr)); } static inline uint32_t l_get_le32(const void *ptr) { return L_LE32_TO_CPU(L_GET_UNALIGNED((const uint32_t *) ptr)); } static inline uint32_t l_get_be32(const void *ptr) { return L_BE32_TO_CPU(L_GET_UNALIGNED((const uint32_t *) ptr)); } static inline uint64_t l_get_le64(const void *ptr) { return L_LE64_TO_CPU(L_GET_UNALIGNED((const uint64_t *) ptr)); } static inline uint64_t l_get_be64(const void *ptr) { return L_BE64_TO_CPU(L_GET_UNALIGNED((const uint64_t *) ptr)); } static inline void l_put_le16(uint16_t val, void *ptr) { L_PUT_UNALIGNED(L_CPU_TO_LE16(val), (uint16_t *) ptr); } static inline void l_put_be16(uint16_t val, void *ptr) { L_PUT_UNALIGNED(L_CPU_TO_BE16(val), (uint16_t *) ptr); } static inline void l_put_le32(uint32_t val, void *ptr) { L_PUT_UNALIGNED(L_CPU_TO_LE32(val), (uint32_t *) ptr); } static inline void l_put_be32(uint32_t val, void *ptr) { L_PUT_UNALIGNED(L_CPU_TO_BE32(val), (uint32_t *) ptr); } static inline void l_put_le64(uint64_t val, void *ptr) { L_PUT_UNALIGNED(L_CPU_TO_LE64(val), (uint64_t *) ptr); } static inline void l_put_be64(uint64_t val, void *ptr) { L_PUT_UNALIGNED(L_CPU_TO_BE64(val), (uint64_t *) ptr); } #define L_AUTO_FREE_VAR(vartype,varname) \ vartype varname __attribute__((cleanup(auto_free))) #define L_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) void *l_malloc(size_t size) __attribute__ ((warn_unused_result, malloc)); void *l_memdup(const void *mem, size_t size) __attribute__ ((warn_unused_result, malloc)); static inline void * __attribute__((nonnull(1))) l_memcpy(void *dest, const void *src, size_t n) { if (!n) return dest; return __builtin_memcpy(dest, src, n); } void l_free(void *ptr); DEFINE_CLEANUP_FUNC(l_free); void *l_realloc(void *mem, size_t size) __attribute__ ((warn_unused_result, malloc)); static inline void auto_free(void *a) { void **p = (void **)a; l_free(*p); } #define l_steal_ptr(ptr) \ (__extension__ ({ typeof(ptr) _tmp = (ptr); (ptr) = NULL; _tmp; })) /** * l_new: * @type: type of structure * @count: amount of structures * * Returns: pointer to allocated memory **/ #define l_new(type, count) \ (type *) (__extension__ ({ \ size_t __n = (size_t) (count); \ size_t __s = sizeof(type); \ void *__p; \ __p = l_malloc(__n * __s); \ memset(__p, 0, __n * __s); \ __p; \ })) /** * l_newa: * @type: type of structure * @count: amount of structures * * Allocates stack space for @count structures of @type. Memory is allocated * using alloca and initialized to 0. * * Returns: Pointer to memory allocated on the stack. */ #define l_newa(type, count) \ (type *) (__extension__ ({ \ size_t __n = (size_t) (count); \ size_t __s = sizeof(type); \ void *__p; \ __p = alloca(__n * __s); \ memset(__p, 0, __n * __s); \ __p; \ })) char *l_strdup(const char *str); char *l_strndup(const char *str, size_t max); char *l_strdup_printf(const char *format, ...) __attribute__((format(printf, 1, 2))); char *l_strdup_vprintf(const char *format, va_list args) __attribute__((format(printf, 1, 0))); size_t l_strlcpy(char* dst, const char *src, size_t len); bool l_str_has_prefix(const char *str, const char *prefix); bool l_str_has_suffix(const char *str, const char *suffix); bool l_streq0(const char *a, const char *b); char *l_util_oidstring(const void *buf, size_t len); char *l_util_hexstring(const void *buf, size_t len); char *l_util_hexstring_upper(const void *buf, size_t len); char *l_util_hexstringv(const struct iovec *iov, size_t n_iov); char *l_util_hexstringv_upper(const struct iovec *iov, size_t n_iov); unsigned char *l_util_from_hexstring(const char *str, size_t *out_len); typedef void (*l_util_hexdump_func_t) (const char *str, void *user_data); void l_util_hexdump(bool in, const void *buf, size_t len, l_util_hexdump_func_t function, void *user_data); void l_util_hexdump_two(bool in, const void *buf1, size_t len1, const void *buf2, size_t len2, l_util_hexdump_func_t function, void *user_data); void l_util_hexdumpv(bool in, const struct iovec *iov, size_t n_iov, l_util_hexdump_func_t function, void *user_data); void l_util_debug(l_util_hexdump_func_t function, void *user_data, const char *format, ...) __attribute__((format(printf, 3, 4))); const char *l_util_get_debugfs_path(void); #define L_TFR(expression) \ (__extension__ \ ({ long int __result; \ do __result = (long int) (expression); \ while (__result == -1L && errno == EINTR); \ __result; })) /* Enables declaring _auto_(close) int fd = <-1 or L_TFR(open(...))>; */ inline __attribute__((always_inline)) void _l_close_cleanup(void *p) { int fd = *(int *) p; if (fd >= 0) L_TFR(close(fd)); } #define _L_IN_SET_CMP(val, type, cmp, ...) __extension__ ({ \ const type __v = (val); \ const typeof(__v) __elems[] = {__VA_ARGS__}; \ size_t __i; \ const size_t __n = L_ARRAY_SIZE(__elems); \ bool __r = false; \ for (__i = 0; __i < __n && !__r; __i++) \ __r = (cmp); \ __r; \ }) /* Warning: evaluates all set elements even after @val has matched one */ #define L_IN_SET(val, ...) \ _L_IN_SET_CMP((val), __auto_type, __v == __elems[__i], ##__VA_ARGS__) #define L_IN_STRSET(val, ...) \ _L_IN_SET_CMP((val), char *, __v == __elems[__i] || \ (__v && __elems[__i] && \ !strcmp(__v, __elems[__i])), ##__VA_ARGS__) #define _L_BIT_TO_MASK(bits, nr) __extension__ ({ \ typeof(*(bits)) _one = 1U; \ const unsigned int _shift = (nr) % (sizeof(_one) * 8); \ _one << _shift; \ }) #define _L_BIT_TO_OFFSET(bits, nr) __extension__ ({ \ __auto_type _bits = (bits); \ const size_t _offset = (nr) / (sizeof(*_bits) * 8); \ _bits + _offset; \ }) #define L_BIT_SET(bits, nr) __extension__ ({ \ size_t _nr = (nr); \ __auto_type _offset = _L_BIT_TO_OFFSET(bits, _nr); \ *_offset |= _L_BIT_TO_MASK(_offset, _nr); \ }) #define L_BIT_CLEAR(bits, nr) __extension__ ({ \ size_t _nr = (nr); \ __auto_type _offset = _L_BIT_TO_OFFSET(bits, _nr); \ *_offset &= ~_L_BIT_TO_MASK(_offset, _nr); \ }) #define L_BIT_TEST(bits, nr) __extension__ ({ \ size_t _nr = (nr); \ __auto_type _offset = _L_BIT_TO_OFFSET(bits, _nr); \ (*_offset & _L_BIT_TO_MASK(_offset, _nr)) != 0; \ }) #define L_BITS_SET(bits, ...) __extension__ ({ \ const unsigned int __elems[] = {__VA_ARGS__}; \ size_t __i; \ for (__i = 0; __i < L_ARRAY_SIZE(__elems); __i++) \ L_BIT_SET(bits, __elems[__i]); \ }) #define L_BITS_CLEAR(bits, ...) __extension__ ({ \ const unsigned int __elems[] = {__VA_ARGS__}; \ size_t __i; \ for (__i = 0; __i < L_ARRAY_SIZE(__elems); __i++) \ L_BIT_CLEAR(bits, __elems[__i]); \ }) /* * Taken from https://github.com/chmike/cst_time_memcmp, adding a volatile to * ensure the compiler does not try to optimize the constant time behavior. * The code has been modified to add comments and project specific code * styling. * This specific piece of code is subject to the following copyright: * * The MIT License (MIT) * * Copyright (c) 2015 Christophe Meessen * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * This function performs a secure memory comparison of two buffers of size * bytes, representing an integer (byte order is big endian). It returns * a negative, zero or positif value if a < b, a == b or a > b respectively. */ static inline int l_secure_memcmp(const void *a, const void *b, size_t size) { const volatile uint8_t *aa = L_PERMISSIVE_CAST(const volatile uint8_t *, a); const volatile uint8_t *bb = L_PERMISSIVE_CAST(const volatile uint8_t *, b); int res = 0, diff, mask; /* * We will compare all bytes, starting with the less significant. When * we find a non-zero difference, we update the result accordingly. */ if (size > 0) { /* * The following couple of lines can be summarized as a * constant time/memory access version of: * if (diff != 0) res = diff; * * From the previous operation, we know that diff is in * [-255, 255] * * The following figure show the possible value of mask, based * on different cases of diff: * * diff | diff-1 | ~diff | ((diff-1) & ~diff) | mask * ------|------------|------------|--------------------|------ * < 0 | 0xFFFFFFXX | 0x000000YY | 0x000000ZZ | 0 * == 0 | 0xFFFFFFFF | 0xFFFFFFFF | 0xFFFFFFFF | 0xF..F * > 0 | 0x000000XX | 0xFFFFFFYY | 0x000000ZZ | 0 * * Hence, the mask allows to keep res when diff == 0, and to * set res to diff otherwise. */ do { --size; diff = aa[size] - bb[size]; mask = (((diff - 1) & ~diff) >> 8); res = (res & mask) | diff; } while (size != 0); } return res; } bool l_memeq(const void *field, size_t size, uint8_t byte); bool l_secure_memeq(const void *field, size_t size, uint8_t byte); static inline bool l_memeqzero(const void *field, size_t size) { return l_memeq(field, size, 0); } static inline void l_secure_select(bool select_left, const void *left, const void *right, void *out, size_t len) { const uint8_t *l = L_PERMISSIVE_CAST(const uint8_t *, left); const uint8_t *r = L_PERMISSIVE_CAST(const uint8_t *, right); uint8_t *o = L_PERMISSIVE_CAST(uint8_t *, out); uint8_t mask = -(!!select_left); size_t i; for (i = 0; i < len; i++) o[i] = r[i] ^ ((l[i] ^ r[i]) & mask); } int l_safe_atou32(const char *s, uint32_t *out_u); int l_safe_atox32(const char *s, uint32_t *out_u); int l_safe_atox16(const char *s, uint16_t *out_u); int l_safe_atox8(const char *s, uint8_t *out_u); size_t l_util_pagesize(void); #ifdef __cplusplus } #endif #endif /* __ELL_UTIL_H */ bluez-5.87/ell/PaxHeaders/settings.h0000644000000000000000000000005014504767710014453 xustar0020 atime=1783076232 20 ctime=1783094098 bluez-5.87/ell/settings.h0000644000000000000000000001127214504767710014137 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_SETTINGS_H #define __ELL_SETTINGS_H #include #include #include #ifdef __cplusplus extern "C" { #endif struct l_settings; typedef void (*l_settings_debug_cb_t) (const char *str, void *user_data); typedef void (*l_settings_destroy_cb_t) (void *user_data); struct l_settings *l_settings_new(void); struct l_settings *l_settings_clone(const struct l_settings *settings); void l_settings_free(struct l_settings *settings); DEFINE_CLEANUP_FUNC(l_settings_free); bool l_settings_load_from_data(struct l_settings *settings, const char *data, size_t len); char *l_settings_to_data(const struct l_settings *settings, size_t *len); bool l_settings_load_from_file(struct l_settings *settings, const char *filename); bool l_settings_set_debug(struct l_settings *settings, l_settings_debug_cb_t callback, void *user_data, l_settings_destroy_cb_t destroy); char **l_settings_get_groups(const struct l_settings *settings); char **l_settings_get_keys(const struct l_settings *settings, const char *group_name); bool l_settings_add_group(struct l_settings *settings, const char *group_name); bool l_settings_has_group(const struct l_settings *settings, const char *group_name); bool l_settings_has_key(const struct l_settings *settings, const char *group_name, const char *key); const char *l_settings_get_value(const struct l_settings *settings, const char *group_name, const char *key); bool l_settings_set_value(struct l_settings *settings, const char *group_name, const char *key, const char *value); bool l_settings_get_bool(const struct l_settings *settings, const char *group_name, const char *key, bool *out); bool l_settings_set_bool(struct l_settings *settings, const char *group_name, const char *key, bool in); bool l_settings_get_int(const struct l_settings *settings, const char *group_name, const char *key, int *out); bool l_settings_set_int(struct l_settings *settings, const char *group_name, const char *key, int in); bool l_settings_get_uint(const struct l_settings *settings, const char *group_name, const char *key, unsigned int *out); bool l_settings_set_uint(struct l_settings *settings, const char *group_name, const char *key, unsigned int in); bool l_settings_get_int64(const struct l_settings *settings, const char *group_name, const char *key, int64_t *out); bool l_settings_set_int64(struct l_settings *settings, const char *group_name, const char *key, int64_t in); bool l_settings_get_uint64(const struct l_settings *settings, const char *group_name, const char *key, uint64_t *out); bool l_settings_set_uint64(struct l_settings *settings, const char *group_name, const char *key, uint64_t in); char *l_settings_get_string(const struct l_settings *settings, const char *group_name, const char *key); bool l_settings_set_string(struct l_settings *settings, const char *group_name, const char *key, const char *value); char **l_settings_get_string_list(const struct l_settings *settings, const char *group_name, const char *key, char delimiter); bool l_settings_set_string_list(struct l_settings *settings, const char *group_name, const char *key, char **list, char delimiter); bool l_settings_get_double(const struct l_settings *settings, const char *group_name, const char *key, double *out); bool l_settings_set_double(struct l_settings *settings, const char *group_name, const char *key, double in); bool l_settings_get_float(const struct l_settings *settings, const char *group_name, const char *key, float *out); bool l_settings_set_float(struct l_settings *settings, const char *group_name, const char *key, float in); uint8_t *l_settings_get_bytes(const struct l_settings *settings, const char *group_name, const char *key, size_t *out_len); bool l_settings_set_bytes(struct l_settings *settings, const char *group_name, const char *key, const uint8_t *value, size_t value_len); bool l_settings_remove_key(struct l_settings *settings, const char *group_name, const char *key); bool l_settings_remove_group(struct l_settings *settings, const char *group_name); bool l_settings_remove_embedded_groups(struct l_settings *settings); char **l_settings_get_embedded_groups(struct l_settings *settings); bool l_settings_has_embedded_group(struct l_settings *settings, const char *group); const char *l_settings_get_embedded_value(struct l_settings *settings, const char *group_name, const char **out_type); #ifdef __cplusplus } #endif #endif /* __ELL_SETTINGS_H */ bluez-5.87/ell/PaxHeaders/private.h0000644000000000000000000000005014773301370014257 xustar0020 atime=1783076247 20 ctime=1783094099 bluez-5.87/ell/private.h0000644000000000000000000000040614773301370013740 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include #define LIB_EXPORT __attribute__ ((visibility("default"))) #define STRLOC __FILE__ ":" L_STRINGIFY(__LINE__) bluez-5.87/ell/PaxHeaders/siphash.c0000644000000000000000000000005014504767710014245 xustar0020 atime=1783076309 20 ctime=1783094099 bluez-5.87/ell/siphash.c0000644000000000000000000000476014504767710013735 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #include "siphash-private.h" /* * Based on public domain SipHash reference C implementation * * Written in 2012 by * Jean-Philippe Aumasson * Daniel J. Bernstein * */ #define ROTL(x,b) (uint64_t) (((x) << (b)) | ((x) >> (64 - (b)))) #define U32TO8_LE(p, v) \ (p)[0] = (uint8_t) ((v)); \ (p)[1] = (uint8_t) ((v) >> 8); \ (p)[2] = (uint8_t) ((v) >> 16); \ (p)[3] = (uint8_t) ((v) >> 24); #define U64TO8_LE(p, v) \ U32TO8_LE((p), (uint32_t) ((v))); \ U32TO8_LE((p) + 4, (uint32_t) ((v) >> 32)); #define U8TO64_LE(p) \ (((uint64_t) ((p)[0])) | \ ((uint64_t) ((p)[1]) << 8) | \ ((uint64_t) ((p)[2]) << 16) | \ ((uint64_t) ((p)[3]) << 24) | \ ((uint64_t) ((p)[4]) << 32) | \ ((uint64_t) ((p)[5]) << 40) | \ ((uint64_t) ((p)[6]) << 48) | \ ((uint64_t) ((p)[7]) << 56)) #define SIPROUND \ do { \ v0 += v1; v1=ROTL(v1, 13); \ v1 ^= v0; v0=ROTL(v0, 32); \ v2 += v3; v3=ROTL(v3, 16); \ v3 ^= v2; \ v0 += v3; v3=ROTL(v3, 21); \ v3 ^= v0; \ v2 += v1; v1=ROTL(v1, 17); \ v1 ^= v2; v2=ROTL(v2, 32); \ } while(0) void _siphash24(uint8_t out[8], const uint8_t *in, size_t inlen, const uint8_t k[16]) { /* "somepseudorandomlygeneratedbytes" */ uint64_t v0 = 0x736f6d6570736575ULL; uint64_t v1 = 0x646f72616e646f6dULL; uint64_t v2 = 0x6c7967656e657261ULL; uint64_t v3 = 0x7465646279746573ULL; uint64_t b; uint64_t k0 = U8TO64_LE(k); uint64_t k1 = U8TO64_LE(k + 8); uint64_t m; const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t)); const int left = inlen & 7; b = ((uint64_t) inlen) << 56; v3 ^= k1; v2 ^= k0; v1 ^= k1; v0 ^= k0; for (; in != end; in += 8) { m = U8TO64_LE(in); v3 ^= m; SIPROUND; SIPROUND; v0 ^= m; } switch (left) { case 7: b |= ((uint64_t) in[6]) << 48; /* fall through */ case 6: b |= ((uint64_t) in[5]) << 40; /* fall through */ case 5: b |= ((uint64_t) in[4]) << 32; /* fall through */ case 4: b |= ((uint64_t) in[3]) << 24; /* fall through */ case 3: b |= ((uint64_t) in[2]) << 16; /* fall through */ case 2: b |= ((uint64_t) in[1]) << 8; /* fall through */ case 1: b |= ((uint64_t) in[0]); break; case 0: break; } v3 ^= b; SIPROUND; SIPROUND; v0 ^= b; v2 ^= 0xff; SIPROUND; SIPROUND; SIPROUND; SIPROUND; b = v0 ^ v1 ^ v2 ^ v3; U64TO8_LE(out, b) } bluez-5.87/ell/PaxHeaders/strv.c0000644000000000000000000000005015040645364013600 xustar0020 atime=1783076262 20 ctime=1783094099 bluez-5.87/ell/strv.c0000644000000000000000000001466615040645364013276 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include "strv.h" #include "private.h" #include "useful.h" /** * SECTION:strv * @short_description: String array functions * * String array functions */ /** * l_strfreev: * @strlist: String list to free * * Frees a list of strings **/ LIB_EXPORT void l_strfreev(char **strlist) { l_strv_free(strlist); } /** * l_strsplit: * @str: String to split * @sep: The delimiter character * * Splits a string into pieces which do not contain the delimiter character. * As a special case, an empty string is returned as an empty array, e.g. * an array with just the NULL element. * * Note that this function only works with ASCII delimiters. * * Returns: A newly allocated %NULL terminated string array. This array * should be freed using l_strfreev(). **/ LIB_EXPORT char **l_strsplit(const char *str, const char sep) { int len; int i; const char *p; char **ret; if (unlikely(!str)) return NULL; if (str[0] == '\0') return l_new(char *, 1); for (p = str, len = 1; *p; p++) if (*p == sep) len += 1; ret = l_new(char *, len + 1); i = 0; p = str; len = 0; while (p[len]) { if (p[len] != sep) { len += 1; continue; } ret[i++] = l_strndup(p, len); p += len + 1; len = 0; } ret[i++] = l_strndup(p, len); return ret; } /** * l_strsplit_set: * @str: String to split * @separators: A set of delimiters * * Splits a string into pieces which do not contain the delimiter characters * that can be found in @separators. * As a special case, an empty string is returned as an empty array, e.g. * an array with just the NULL element. * * Note that this function only works with ASCII delimiters. * * Returns: A newly allocated %NULL terminated string array. This array * should be freed using l_strfreev(). **/ LIB_EXPORT char **l_strsplit_set(const char *str, const char *separators) { int len; int i; const char *p; char **ret; bool sep_table[256]; if (unlikely(!str)) return NULL; if (str[0] == '\0') return l_new(char *, 1); memset(sep_table, 0, sizeof(sep_table)); for (p = separators; *p; p++) sep_table[(unsigned char) *p] = true; for (p = str, len = 1; *p; p++) if (sep_table[(unsigned char) *p] == true) len += 1; ret = l_new(char *, len + 1); i = 0; p = str; len = 0; while (p[len]) { if (sep_table[(unsigned char) p[len]] != true) { len += 1; continue; } ret[i++] = l_strndup(p, len); p += len + 1; len = 0; } ret[i++] = l_strndup(p, len); return ret; } /** * l_strjoinv: * @str_array: a %NULL terminated array of strings to join * @delim: Delimiting character * * Joins strings contanied in the @str_array into one long string delimited * by @delim. * * Returns: A newly allocated string that should be freed using l_free() */ LIB_EXPORT char *l_strjoinv(char **str_array, const char delim) { size_t len = 0; unsigned int i; char *ret; char *p; if (unlikely(!str_array)) return NULL; if (!str_array[0]) return l_strdup(""); for (i = 0; str_array[i]; i++) len += strlen(str_array[i]); len += 1 + i - 1; ret = l_malloc(len); p = stpcpy(ret, str_array[0]); for (i = 1; str_array[i]; i++) { *p++ = delim; p = stpcpy(p, str_array[i]); } return ret; } /** * l_strv_new: * * Returns: new empty string array **/ LIB_EXPORT char **l_strv_new(void) { return l_new(char *, 1); } /** * l_strv_free: * @str_array: a %NULL terminated array of strings * * Frees strings in @str_array and @str_array itself **/ LIB_EXPORT void l_strv_free(char **str_array) { if (likely(str_array)) { int i; for (i = 0; str_array[i]; i++) l_free(str_array[i]); l_free(str_array); } } /** * l_strv_length: * @str_array: a %NULL terminated array of strings * * Returns: the number of strings in @str_array */ LIB_EXPORT unsigned int l_strv_length(char **str_array) { unsigned int i = 0; if (unlikely(!str_array)) return 0; while (str_array[i]) i += 1; return i; } /** * l_strv_contains: * @str_array: a %NULL terminated array of strings * @item: An item to search for, must be not %NULL * * Returns: #true if @str_array contains item */ LIB_EXPORT bool l_strv_contains(char **str_array, const char *item) { unsigned int i = 0; if (unlikely(!str_array || !item)) return false; while (str_array[i]) { if (!strcmp(str_array[i], item)) return true; i += 1; } return false; } /** * l_strv_append: * @str_array: a %NULL terminated array of strings or %NULL * @str: A string to be appended at the end of @str_array * * Returns: New %NULL terminated array of strings with @str added */ LIB_EXPORT char **l_strv_append(char **str_array, const char *str) { char **ret; unsigned int i, len; if (unlikely(!str)) return str_array; len = l_strv_length(str_array); ret = l_new(char *, len + 2); for (i = 0; i < len; i++) ret[i] = str_array[i]; ret[i] = l_strdup(str); l_free(str_array); return ret; } LIB_EXPORT char **l_strv_append_printf(char **str_array, const char *format, ...) { va_list args; char **ret; va_start(args, format); ret = l_strv_append_vprintf(str_array, format, args); va_end(args); return ret; } LIB_EXPORT char **l_strv_append_vprintf(char **str_array, const char *format, va_list args) { char **ret; unsigned int i, len; if (unlikely(!format)) return str_array; len = l_strv_length(str_array); ret = l_new(char *, len + 2); for (i = 0; i < len; i++) ret[i] = str_array[i]; ret[i] = l_strdup_vprintf(format, args); l_free(str_array); return ret; } /** * l_strv_copy: * @str_array: a %NULL terminated array of strings or %NULL * * Returns: An independent copy of @str_array. */ LIB_EXPORT char **l_strv_copy(char **str_array) { int i, len; char **copy; if (unlikely(!str_array)) return NULL; for (len = 0; str_array[len]; len++); copy = l_malloc(sizeof(char *) * (len + 1)); for (i = len; i >= 0; i--) copy[i] = l_strdup(str_array[i]); return copy; } /** * l_strv_eq: * @a: a %NULL terminated array of strings or %NULL * @b: another %NULL terminated array of strings or %NULL * * Returns: Whether @a and @b's contents are identical, including the * order, or @a and @b are both %NULL. */ LIB_EXPORT bool l_strv_eq(char **a, char **b) { if (!a || !b) return a == b; for (; *a; a++, b++) if (!*b || strcmp(*a, *b)) return false; return !*b; } bluez-5.87/ell/PaxHeaders/dbus-service.h0000644000000000000000000000005014504767710015206 xustar0020 atime=1783076232 20 ctime=1783094098 bluez-5.87/ell/dbus-service.h0000644000000000000000000000375114504767710014675 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_SERVICE_H #define __ELL_SERVICE_H #include #include #ifdef __cplusplus extern "C" { #endif struct l_dbus; struct l_dbus_interface; struct l_dbus_message; enum l_dbus_method_flag { L_DBUS_METHOD_FLAG_DEPRECATED = 1, L_DBUS_METHOD_FLAG_NOREPLY = 2, L_DBUS_METHOD_FLAG_ASYNC = 4, }; enum l_dbus_signal_flag { L_DBUS_SIGNAL_FLAG_DEPRECATED = 1, }; enum l_dbus_property_flag { L_DBUS_PROPERTY_FLAG_DEPRECATED = 1, L_DBUS_PROPERTY_FLAG_AUTO_EMIT = 2, }; typedef struct l_dbus_message *(*l_dbus_interface_method_cb_t) (struct l_dbus *, struct l_dbus_message *message, void *user_data); typedef void (*l_dbus_property_complete_cb_t) (struct l_dbus *, struct l_dbus_message *, struct l_dbus_message *error); typedef struct l_dbus_message *(*l_dbus_property_set_cb_t) (struct l_dbus *, struct l_dbus_message *message, struct l_dbus_message_iter *new_value, l_dbus_property_complete_cb_t complete, void *user_data); typedef bool (*l_dbus_property_get_cb_t) (struct l_dbus *, struct l_dbus_message *message, struct l_dbus_message_builder *builder, void *user_data); bool l_dbus_interface_method(struct l_dbus_interface *interface, const char *name, uint32_t flags, l_dbus_interface_method_cb_t cb, const char *return_sig, const char *param_sig, ...); bool l_dbus_interface_signal(struct l_dbus_interface *interface, const char *name, uint32_t flags, const char *signature, ...); bool l_dbus_interface_property(struct l_dbus_interface *interface, const char *name, uint32_t flags, const char *signature, l_dbus_property_get_cb_t getter, l_dbus_property_set_cb_t setter); bool l_dbus_property_changed(struct l_dbus *dbus, const char *path, const char *interface, const char *property); #ifdef __cplusplus } #endif #endif /* __ELL_DBUS_SERVICE_H */ bluez-5.87/ell/PaxHeaders/ecdh.h0000644000000000000000000000005014504767710013516 xustar0020 atime=1783076232 20 ctime=1783094099 bluez-5.87/ell/ecdh.h0000644000000000000000000000150314504767710013176 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2018 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_ECDH_H #define __ELL_ECDH_H #ifdef __cplusplus extern "C" { #endif struct l_ecc_curve; struct l_ecc_point; struct l_ecc_scalar; /* * Generate a private/public key pair. private/public are out parameters and * must be freed. */ bool l_ecdh_generate_key_pair(const struct l_ecc_curve *curve, struct l_ecc_scalar **out_private, struct l_ecc_point **out_public); /* * Generate a shared secret from a private/public key. secret is an out * parameters and must be freed. */ bool l_ecdh_generate_shared_secret(const struct l_ecc_scalar *private_key, const struct l_ecc_point *other_public, struct l_ecc_scalar **secret); #ifdef __cplusplus } #endif #endif /* __ELL_ECDH_H */ bluez-5.87/ell/PaxHeaders/idle.c0000644000000000000000000000005014560467756013534 xustar0020 atime=1783076257 20 ctime=1783094099 bluez-5.87/ell/idle.c0000644000000000000000000000557714560467756013233 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "useful.h" #include "idle.h" #include "main-private.h" #include "private.h" /** * SECTION:idle * @short_description: Idle processing support * * Idle processing support */ /** * l_idle: * * Opaque object representing the idle time event. */ struct l_idle { union { l_idle_notify_cb_t callback; l_idle_oneshot_cb_t oneshot; }; l_idle_destroy_cb_t destroy; void *user_data; int id; }; static void idle_destroy(void *user_data) { struct l_idle *idle = user_data; if (idle->destroy) idle->destroy(idle->user_data); l_free(idle); } static void idle_callback(void *user_data) { struct l_idle *idle = user_data; if (idle->callback) idle->callback(idle, idle->user_data); } static void oneshot_callback(void *user_data) { struct l_idle *idle = user_data; if (idle->oneshot) idle->oneshot(idle->user_data); idle_remove(idle->id); } /** * l_idle_create: * @callback: idle callback function * @user_data: user data provided to idle callback function * @destroy: destroy function for user data * * Create a new idle event processing object. * * The idle callback will be called until canceled using l_idle_remove(). * * Returns: a newly allocated #l_idle object **/ LIB_EXPORT struct l_idle *l_idle_create(l_idle_notify_cb_t callback, void *user_data, l_idle_destroy_cb_t destroy) { struct l_idle *idle; if (unlikely(!callback)) return NULL; idle = l_new(struct l_idle, 1); idle->callback = callback; idle->destroy = destroy; idle->user_data = user_data; idle->id = idle_add(idle_callback, idle, 0, idle_destroy); if (idle->id < 0) { l_free(idle); return NULL; } return idle; } /** * l_idle_oneshot: * @callback: idle callback function * @user_data: user data provided to idle callback function * @destroy: destroy function for user data * * Create a new idle event processing object. The callback will be called * only once at which point the object will be destroyed. * * Returns: true if the oneshot idle object could be created successfully. **/ LIB_EXPORT bool l_idle_oneshot(l_idle_oneshot_cb_t callback, void *user_data, l_idle_destroy_cb_t destroy) { struct l_idle *idle; if (unlikely(!callback)) return NULL; idle = l_new(struct l_idle, 1); idle->oneshot = callback; idle->destroy = destroy; idle->user_data = user_data; idle->id = idle_add(oneshot_callback, idle, IDLE_FLAG_NO_WARN_DANGLING, idle_destroy); if (idle->id < 0) { l_free(idle); return false; } return true; } /** * l_idle_remove: * @idle: idle object * * Remove idle event processing object. **/ LIB_EXPORT void l_idle_remove(struct l_idle *idle) { if (unlikely(!idle)) return; idle_remove(idle->id); } bluez-5.87/ell/PaxHeaders/cert.h0000644000000000000000000000005014504767710013550 xustar0020 atime=1783076232 20 ctime=1783094098 bluez-5.87/ell/cert.h0000644000000000000000000000376614504767710013245 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2018 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_CERT_H #define __ELL_CERT_H #include #include #ifdef __cplusplus extern "C" { #endif struct l_queue; struct l_cert; struct l_certchain; enum l_cert_key_type { L_CERT_KEY_RSA, L_CERT_KEY_ECC, L_CERT_KEY_UNKNOWN, }; typedef bool (*l_cert_walk_cb_t)(struct l_cert *cert, void *user_data); struct l_cert *l_cert_new_from_der(const uint8_t *buf, size_t buf_len); void l_cert_free(struct l_cert *cert); DEFINE_CLEANUP_FUNC(l_cert_free); const uint8_t *l_cert_get_der_data(struct l_cert *cert, size_t *out_len); const uint8_t *l_cert_get_dn(struct l_cert *cert, size_t *out_len); bool l_cert_get_valid_times(struct l_cert *cert, uint64_t *out_not_before_time, uint64_t *out_not_after_time); enum l_cert_key_type l_cert_get_pubkey_type(struct l_cert *cert); struct l_key *l_cert_get_pubkey(struct l_cert *cert); void l_certchain_free(struct l_certchain *chain); DEFINE_CLEANUP_FUNC(l_certchain_free); struct l_cert *l_certchain_get_leaf(struct l_certchain *chain); void l_certchain_walk_from_leaf(struct l_certchain *chain, l_cert_walk_cb_t cb, void *user_data); void l_certchain_walk_from_ca(struct l_certchain *chain, l_cert_walk_cb_t cb, void *user_data); bool l_certchain_verify(struct l_certchain *chain, struct l_queue *ca_certs, const char **error); bool l_cert_load_container_file(const char *filename, const char *password, struct l_certchain **out_certchain, struct l_key **out_privkey, bool *out_encrypted); bool l_cert_pkcs5_pbkdf1(enum l_checksum_type type, const char *password, const uint8_t *salt, size_t salt_len, unsigned int iter_count, uint8_t *out_dk, size_t dk_len); bool l_cert_pkcs5_pbkdf2(enum l_checksum_type type, const char *password, const uint8_t *salt, size_t salt_len, unsigned int iter_count, uint8_t *out_dk, size_t dk_len); #ifdef __cplusplus } #endif #endif /* __ELL_CERT_H */ bluez-5.87/ell/PaxHeaders/ecc.h0000644000000000000000000000005014770744372013351 xustar0020 atime=1783076232 20 ctime=1783094099 bluez-5.87/ell/ecc.h0000644000000000000000000001012314770744372013027 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2018 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_ECC_H #define __ELL_ECC_H #include #include #ifdef __cplusplus extern "C" { #endif #define L_ECC_MAX_DIGITS 9 #define L_ECC_SCALAR_MAX_BYTES L_ECC_MAX_DIGITS * 8 #define L_ECC_POINT_MAX_BYTES L_ECC_SCALAR_MAX_BYTES * 2 struct l_ecc_curve; struct l_ecc_point; struct l_ecc_scalar; enum l_ecc_point_type { L_ECC_POINT_TYPE_COMPLIANT = 0x01, L_ECC_POINT_TYPE_COMPRESSED_BIT0 = 0x02, L_ECC_POINT_TYPE_COMPRESSED_BIT1 = 0x03, L_ECC_POINT_TYPE_FULL = 0x04, }; const unsigned int *l_ecc_supported_ike_groups(void); const unsigned int *l_ecc_supported_tls_groups(void); const struct l_ecc_curve *l_ecc_curve_from_name(const char *name); const struct l_ecc_curve *l_ecc_curve_from_ike_group(unsigned int group); const struct l_ecc_curve *l_ecc_curve_from_tls_group(unsigned int group); const char *l_ecc_curve_get_name(const struct l_ecc_curve *curve); unsigned int l_ecc_curve_get_ike_group(const struct l_ecc_curve *curve); unsigned int l_ecc_curve_get_tls_group(const struct l_ecc_curve *curve); struct l_ecc_scalar *l_ecc_curve_get_order(const struct l_ecc_curve *curve); struct l_ecc_scalar *l_ecc_curve_get_prime(const struct l_ecc_curve *curve); size_t l_ecc_curve_get_scalar_bytes(const struct l_ecc_curve *curve); struct l_ecc_point *l_ecc_point_new(const struct l_ecc_curve *curve); struct l_ecc_point *l_ecc_point_from_data(const struct l_ecc_curve *curve, enum l_ecc_point_type type, const void *data, size_t len); struct l_ecc_point *l_ecc_point_from_sswu(const struct l_ecc_scalar *u); struct l_ecc_point *l_ecc_point_clone(const struct l_ecc_point *p); const struct l_ecc_curve *l_ecc_point_get_curve(const struct l_ecc_point *p); ssize_t l_ecc_point_get_x(const struct l_ecc_point *p, void *x, size_t xlen); ssize_t l_ecc_point_get_y(const struct l_ecc_point *p, void *y, size_t ylen); bool l_ecc_point_y_isodd(const struct l_ecc_point *p); ssize_t l_ecc_point_get_data(const struct l_ecc_point *p, void *buf, size_t len); void l_ecc_point_free(struct l_ecc_point *p); DEFINE_CLEANUP_FUNC(l_ecc_point_free); struct l_ecc_scalar *l_ecc_scalar_new(const struct l_ecc_curve *curve, const void *buf, size_t len); struct l_ecc_scalar *l_ecc_scalar_clone(const struct l_ecc_scalar *s); struct l_ecc_scalar *l_ecc_scalar_new_random( const struct l_ecc_curve *curve); struct l_ecc_scalar *l_ecc_scalar_new_modp(const struct l_ecc_curve *curve, const void *buf, size_t len); struct l_ecc_scalar *l_ecc_scalar_new_modn(const struct l_ecc_curve *curve, const void *buf, size_t len); struct l_ecc_scalar *l_ecc_scalar_new_reduced_1_to_n( const struct l_ecc_curve *curve, const void *buf, size_t len); ssize_t l_ecc_scalar_get_data(const struct l_ecc_scalar *c, void *buf, size_t len); void l_ecc_scalar_free(struct l_ecc_scalar *c); DEFINE_CLEANUP_FUNC(l_ecc_scalar_free); /* Constant operations */ bool l_ecc_scalar_add(struct l_ecc_scalar *ret, const struct l_ecc_scalar *a, const struct l_ecc_scalar *b, const struct l_ecc_scalar *mod); /* Point operations */ bool l_ecc_point_multiply(struct l_ecc_point *ret, const struct l_ecc_scalar *scalar, const struct l_ecc_point *point); bool l_ecc_point_multiply_g(struct l_ecc_point *ret, const struct l_ecc_scalar *scalar); bool l_ecc_point_add(struct l_ecc_point *ret, const struct l_ecc_point *a, const struct l_ecc_point *b); bool l_ecc_point_inverse(struct l_ecc_point *p); /* extra operations needed for SAE */ bool l_ecc_scalar_multiply(struct l_ecc_scalar *ret, const struct l_ecc_scalar *a, const struct l_ecc_scalar *b); int l_ecc_scalar_legendre(struct l_ecc_scalar *value); bool l_ecc_scalar_sum_x(struct l_ecc_scalar *ret, const struct l_ecc_scalar *x); bool l_ecc_scalars_are_equal(const struct l_ecc_scalar *a, const struct l_ecc_scalar *b); bool l_ecc_points_are_equal(const struct l_ecc_point *a, const struct l_ecc_point *b); bool l_ecc_point_is_infinity(const struct l_ecc_point *p); #ifdef __cplusplus } #endif #endif /* __ELL_ECC_H */ bluez-5.87/ell/PaxHeaders/tester.c0000644000000000000000000000005014504767710014114 xustar0020 atime=1783076311 20 ctime=1783094099 bluez-5.87/ell/tester.c0000644000000000000000000003732214504767710013604 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2021 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include "idle.h" #include "log.h" #include "private.h" #include "queue.h" #include "time.h" #include "timeout.h" #include "useful.h" #include "tester.h" /** * SECTION:tester * @short_description: Non-interactive test framework * * Non-interactive test framework */ #define COLOR_OFF "\x1B[0m" #define COLOR_BLACK "\x1B[0;30m" #define COLOR_RED "\x1B[0;31m" #define COLOR_GREEN "\x1B[0;32m" #define COLOR_YELLOW "\x1B[0;33m" #define COLOR_BLUE "\x1B[0;34m" #define COLOR_MAGENTA "\x1B[0;35m" #define COLOR_HIGHLIGHT "\x1B[1;39m" #define print_text(color, fmt, args...) \ l_info(color fmt COLOR_OFF, ## args) #define print_summary(label, color, value, fmt, args...) \ l_info("%-52s " color "%-10s" COLOR_OFF fmt, \ label, value, ## args) #define print_progress(name, color, fmt, args...) \ l_info(COLOR_HIGHLIGHT "%s" COLOR_OFF " - " \ color fmt COLOR_OFF, name, ## args) enum test_result { TEST_RESULT_NOT_RUN, TEST_RESULT_PASSED, TEST_RESULT_FAILED, TEST_RESULT_TIMED_OUT, }; struct l_tester { uint64_t start_time; struct l_queue *tests; const struct l_queue_entry *test_entry; bool list_cases; const char *prefix; const char *substring; l_tester_finish_func_t finish_callback; }; struct test_case { uint64_t start_time; uint64_t end_time; char *name; enum test_result result; enum l_tester_stage stage; const void *test_data; l_tester_data_func_t pre_setup_func; l_tester_data_func_t setup_func; l_tester_data_func_t test_func; l_tester_data_func_t teardown_func; l_tester_data_func_t post_teardown_func; unsigned int timeout; struct l_timeout *run_timer; l_tester_destroy_func_t destroy; void *user_data; bool teardown; }; static void destroy_test(void *data) { struct test_case *test = data; l_timeout_remove(test->run_timer); if (test->destroy) test->destroy(test->user_data); l_free(test->name); l_free(test); } static uint64_t get_elapsed_time(uint64_t base) { uint64_t now; now = l_time_now(); return l_time_diff(base, now); } static void teardown_callback(void *user_data) { struct l_tester *tester = user_data; struct test_case *test; test = tester->test_entry->data; test->stage = L_TESTER_STAGE_TEARDOWN; test->teardown = false; print_progress(test->name, COLOR_MAGENTA, "teardown"); if (test->teardown_func) test->teardown_func(test->test_data); else l_tester_teardown_complete(tester); } static void test_timeout(struct l_timeout *timer, void *user_data) { struct l_tester *tester = user_data; struct test_case *test; test = tester->test_entry->data; l_timeout_remove(timer); test->run_timer = NULL; test->result = TEST_RESULT_TIMED_OUT; print_progress(test->name, COLOR_RED, "test timed out"); l_idle_oneshot(teardown_callback, tester, NULL); } static void next_test_case(struct l_tester *tester) { struct test_case *test; if (tester->test_entry) tester->test_entry = tester->test_entry->next; else tester->test_entry = l_queue_get_entries(tester->tests); if (!tester->test_entry) { if (tester->finish_callback) tester->finish_callback(tester); return; } test = tester->test_entry->data; print_progress(test->name, COLOR_BLACK, "init"); test->start_time = get_elapsed_time(tester->start_time); if (test->timeout > 0) test->run_timer = l_timeout_create(test->timeout, test_timeout, tester, NULL); test->stage = L_TESTER_STAGE_PRE_SETUP; if (test->pre_setup_func) test->pre_setup_func(test->test_data); else l_tester_pre_setup_complete(tester); } static void setup_callback(void *user_data) { struct l_tester *tester = user_data; struct test_case *test = tester->test_entry->data; test->stage = L_TESTER_STAGE_SETUP; print_progress(test->name, COLOR_BLUE, "setup"); if (test->setup_func) test->setup_func(test->test_data); else l_tester_setup_complete(tester); } static void run_callback(void *user_data) { struct l_tester *tester = user_data; struct test_case *test = tester->test_entry->data; test->stage = L_TESTER_STAGE_RUN; print_progress(test->name, COLOR_BLACK, "run"); test->test_func(test->test_data); } static void done_callback(void *user_data) { struct l_tester *tester = user_data; struct test_case *test = tester->test_entry->data; test->end_time = get_elapsed_time(tester->start_time); print_progress(test->name, COLOR_BLACK, "done"); next_test_case(tester); } LIB_EXPORT void *l_tester_get_data(struct l_tester *tester) { struct test_case *test; if (unlikely(!tester)) return NULL; if (!tester->test_entry) return NULL; test = tester->test_entry->data; return test->user_data; } LIB_EXPORT void l_tester_pre_setup_complete(struct l_tester *tester) { struct test_case *test; if (unlikely(!tester)) return; if (!tester->test_entry) return; test = tester->test_entry->data; if (test->stage != L_TESTER_STAGE_PRE_SETUP) return; l_idle_oneshot(setup_callback, tester, NULL); } LIB_EXPORT void l_tester_pre_setup_failed(struct l_tester *tester) { struct test_case *test; if (unlikely(!tester)) return; if (!tester->test_entry) return; test = tester->test_entry->data; if (test->stage != L_TESTER_STAGE_PRE_SETUP) return; print_progress(test->name, COLOR_RED, "pre setup failed"); l_timeout_remove(test->run_timer); test->run_timer = NULL; l_idle_oneshot(done_callback, tester, NULL); } LIB_EXPORT void l_tester_setup_complete(struct l_tester *tester) { struct test_case *test; if (unlikely(!tester)) return; if (!tester->test_entry) return; test = tester->test_entry->data; if (test->stage != L_TESTER_STAGE_SETUP) return; print_progress(test->name, COLOR_BLUE, "setup complete"); l_idle_oneshot(run_callback, tester, NULL); } LIB_EXPORT void l_tester_setup_failed(struct l_tester *tester) { struct test_case *test; if (unlikely(!tester)) return; if (!tester->test_entry) return; test = tester->test_entry->data; if (test->stage != L_TESTER_STAGE_SETUP) return; test->stage = L_TESTER_STAGE_POST_TEARDOWN; l_timeout_remove(test->run_timer); test->run_timer = NULL; print_progress(test->name, COLOR_RED, "setup failed"); print_progress(test->name, COLOR_MAGENTA, "teardown"); test->post_teardown_func(test->test_data); } static void test_result(struct l_tester *tester, enum test_result result) { struct test_case *test; if (unlikely(!tester)) return; if (!tester->test_entry) return; test = tester->test_entry->data; if (test->stage != L_TESTER_STAGE_RUN) return; l_timeout_remove(test->run_timer); test->run_timer = NULL; test->result = result; switch (result) { case TEST_RESULT_PASSED: print_progress(test->name, COLOR_GREEN, "test passed"); break; case TEST_RESULT_FAILED: print_progress(test->name, COLOR_RED, "test failed"); break; case TEST_RESULT_NOT_RUN: print_progress(test->name, COLOR_YELLOW, "test not run"); break; case TEST_RESULT_TIMED_OUT: print_progress(test->name, COLOR_RED, "test timed out"); break; } if (test->teardown) return; test->teardown = true; l_idle_oneshot(teardown_callback, tester, NULL); } LIB_EXPORT void l_tester_test_passed(struct l_tester *tester) { if (unlikely(!tester)) return; test_result(tester, TEST_RESULT_PASSED); } LIB_EXPORT void l_tester_test_failed(struct l_tester *tester) { if (unlikely(!tester)) return; test_result(tester, TEST_RESULT_FAILED); } LIB_EXPORT void l_tester_test_abort(struct l_tester *tester) { if (unlikely(!tester)) return; test_result(tester, TEST_RESULT_NOT_RUN); } LIB_EXPORT void l_tester_teardown_complete(struct l_tester *tester) { struct test_case *test; if (unlikely(!tester)) return; if (!tester->test_entry) return; test = tester->test_entry->data; if (test->stage != L_TESTER_STAGE_TEARDOWN) return; test->stage = L_TESTER_STAGE_POST_TEARDOWN; if (test->post_teardown_func) test->post_teardown_func(test->test_data); else l_tester_post_teardown_complete(tester); } LIB_EXPORT void l_tester_teardown_failed(struct l_tester *tester) { struct test_case *test; if (unlikely(!tester)) return; if (!tester->test_entry) return; test = tester->test_entry->data; if (test->stage != L_TESTER_STAGE_TEARDOWN) return; test->stage = L_TESTER_STAGE_POST_TEARDOWN; l_tester_post_teardown_failed(tester); } LIB_EXPORT void l_tester_post_teardown_complete(struct l_tester *tester) { struct test_case *test; if (unlikely(!tester)) return; if (!tester->test_entry) return; test = tester->test_entry->data; if (test->stage != L_TESTER_STAGE_POST_TEARDOWN) return; print_progress(test->name, COLOR_MAGENTA, "teardown complete"); l_idle_oneshot(done_callback, tester, NULL); } LIB_EXPORT void l_tester_post_teardown_failed(struct l_tester *tester) { struct test_case *test; if (unlikely(!tester)) return; if (!tester->test_entry) return; test = tester->test_entry->data; if (test->stage != L_TESTER_STAGE_POST_TEARDOWN) return; print_progress(test->name, COLOR_RED, "teardown failed"); l_idle_oneshot(done_callback, tester, NULL); } struct wait_data { unsigned int seconds; struct test_case *test; l_tester_wait_func_t func; void *user_data; }; static void wait_callback(struct l_timeout *timer, void *user_data) { struct wait_data *wait = user_data; struct test_case *test = wait->test; wait->seconds--; if (wait->seconds > 0) { print_progress(test->name, COLOR_BLACK, "%u seconds left", wait->seconds); return; } print_progress(test->name, COLOR_BLACK, "waiting done"); wait->func(wait->user_data); l_free(wait); l_timeout_remove(timer); } LIB_EXPORT void l_tester_wait(struct l_tester *tester, unsigned int seconds, l_tester_wait_func_t func, void *user_data) { struct test_case *test; struct wait_data *wait; if (unlikely(!tester)) return; if (!func || seconds < 1) return; if (!tester->test_entry) return; test = tester->test_entry->data; wait = l_new(struct wait_data, 1); wait->seconds = seconds; wait->test = test; wait->func = func; wait->user_data = user_data; l_timeout_create(seconds, wait_callback, wait, NULL); print_progress(test->name, COLOR_BLACK, "waiting %u seconds", seconds); } /** * l_tester_add_full: * @tester: tester instance * @name: test case name * @test_data: test data * @pre_setup_func: test pre-setup function * @setup_func: test setup function * @test_func: test function * @teardown_func: test teardown function * @teardown_func: test post-teardown function * @timeout: test teardown function * @user_data: user data * @destroy: user data destroy function * * Add a new test case. **/ LIB_EXPORT void l_tester_add_full(struct l_tester *tester, const char *name, const void *test_data, l_tester_data_func_t pre_setup_func, l_tester_data_func_t setup_func, l_tester_data_func_t test_func, l_tester_data_func_t teardown_func, l_tester_data_func_t post_teardown_func, unsigned int timeout, void *user_data, l_tester_destroy_func_t destroy) { struct test_case *test; if (unlikely(!tester || !test_func)) return; if (tester->prefix && !l_str_has_prefix(name, tester->prefix)) { if (destroy) destroy(user_data); return; } if (tester->substring && !strstr(name, tester->substring)) { if (destroy) destroy(user_data); return; } if (tester->list_cases) { l_info("%s", name); if (destroy) destroy(user_data); return; } test = l_new(struct test_case, 1); test->name = l_strdup(name); test->result = TEST_RESULT_NOT_RUN; test->stage = L_TESTER_STAGE_INVALID; test->test_data = test_data; test->pre_setup_func = pre_setup_func; test->setup_func = setup_func; test->test_func = test_func; test->teardown_func = teardown_func; test->post_teardown_func = post_teardown_func; test->timeout = timeout; test->destroy = destroy; test->user_data = user_data; l_queue_push_tail(tester->tests, test); } /** * l_tester_add: * @tester: tester instance * @name: test case name * @test_data: test data * @setup_func: test setup function * @test_func: test function * @teardown_func: test teardown function * * Add a new test with default settings for timeout and no pre-setup procedure. **/ LIB_EXPORT void l_tester_add(struct l_tester *tester, const char *name, const void *test_data, l_tester_data_func_t setup_func, l_tester_data_func_t test_func, l_tester_data_func_t teardown_func) { l_tester_add_full(tester, name, test_data, NULL, setup_func, test_func, teardown_func, NULL, 0, NULL, NULL); } /** * l_tester_new: * * Initialize tester framework. * * Returns: new tester instance **/ LIB_EXPORT struct l_tester *l_tester_new(const char *prefix, const char *substring, bool list_cases) { struct l_tester *tester = l_new(struct l_tester, 1); tester->prefix = prefix; tester->substring = substring; tester->list_cases = list_cases; tester->tests = l_queue_new(); return tester; } /** * l_tester_start: * @tester: tester instance * * Kick off execution of the test queue * **/ LIB_EXPORT void l_tester_start(struct l_tester *tester, l_tester_finish_func_t finish_func) { if (unlikely(!tester)) return; if (!tester->tests) return; tester->finish_callback = finish_func; tester->start_time = l_time_now(); next_test_case(tester); } /** * l_tester_summarize: * @tester: tester instance * * Print summary of all added test cases. * * Returns: true, if all the tests passed * false, if any of the tests failed **/ LIB_EXPORT bool l_tester_summarize(struct l_tester *tester) { unsigned int not_run = 0, passed = 0, failed = 0; double execution_time; const struct l_queue_entry *entry; if (unlikely(!tester)) return false; l_info(COLOR_HIGHLIGHT "%s" COLOR_OFF, "\n\nTest Summary\n------------"); entry = l_queue_get_entries(tester->tests); for (; entry; entry = entry->next) { struct test_case *test = entry->data; double exec_time; exec_time = (test->end_time - test->start_time) / (double)L_USEC_PER_SEC; switch (test->result) { case TEST_RESULT_NOT_RUN: print_summary(test->name, COLOR_YELLOW, "Not Run", ""); not_run++; break; case TEST_RESULT_PASSED: print_summary(test->name, COLOR_GREEN, "Passed", "%8.3f seconds", exec_time); passed++; break; case TEST_RESULT_FAILED: print_summary(test->name, COLOR_RED, "Failed", "%8.3f seconds", exec_time); failed++; break; case TEST_RESULT_TIMED_OUT: print_summary(test->name, COLOR_RED, "Timed out", "%8.3f seconds", exec_time); failed++; break; } } l_info("Total: %d, " COLOR_GREEN "Passed: %d (%.1f%%)" COLOR_OFF ", " COLOR_RED "Failed: %d" COLOR_OFF ", " COLOR_YELLOW "Not Run: %d" COLOR_OFF, not_run + passed + failed, passed, (not_run + passed + failed) ? (float) passed * 100 / (not_run + passed + failed) : 0, failed, not_run); execution_time = get_elapsed_time(tester->start_time); l_info("Overall execution time: %8.3f seconds", execution_time / (double)L_USEC_PER_SEC); return failed; } /** * l_tester_destroy: * @tester: tester instance * * Free up the teter framework resources * **/ LIB_EXPORT void l_tester_destroy(struct l_tester *tester) { if (unlikely(!tester)) return; l_queue_destroy(tester->tests, destroy_test); l_free(tester); } /** * l_tester_get_stage: * @tester: tester instance * * Get the current test stage * * Returns: the stage of the current test that is being processing. * **/ LIB_EXPORT enum l_tester_stage l_tester_get_stage(struct l_tester *tester) { struct test_case *test; if (unlikely(!tester)) return L_TESTER_STAGE_INVALID; if (!tester->test_entry) return L_TESTER_STAGE_INVALID; test = tester->test_entry->data; return test->stage; } bluez-5.87/ell/PaxHeaders/idle.h0000644000000000000000000000005014504767710013530 xustar0020 atime=1783076232 20 ctime=1783094098 bluez-5.87/ell/idle.h0000644000000000000000000000134614504767710013215 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_IDLE_H #define __ELL_IDLE_H #include #ifdef __cplusplus extern "C" { #endif struct l_idle; typedef void (*l_idle_notify_cb_t) (struct l_idle *idle, void *user_data); typedef void (*l_idle_oneshot_cb_t) (void *user_data); typedef void (*l_idle_destroy_cb_t) (void *user_data); struct l_idle *l_idle_create(l_idle_notify_cb_t callback, void *user_data, l_idle_destroy_cb_t destroy); void l_idle_remove(struct l_idle *idle); bool l_idle_oneshot(l_idle_oneshot_cb_t callback, void *user_data, l_idle_destroy_cb_t destroy); #ifdef __cplusplus } #endif #endif /* __ELL_IDLE_H */ bluez-5.87/ell/PaxHeaders/time.c0000644000000000000000000000005014667515664013555 xustar0020 atime=1783076254 20 ctime=1783094099 bluez-5.87/ell/time.c0000644000000000000000000000620014667515664013234 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2019 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include "time.h" #include "time-private.h" #include "random.h" #include "private.h" uint64_t _time_from_timespec(const struct timespec *ts) { return ts->tv_sec * L_USEC_PER_SEC + ts->tv_nsec / L_NSEC_PER_USEC; } static uint64_t _time_from_timeval(const struct timeval *tv) { return tv->tv_sec * L_USEC_PER_SEC + tv->tv_usec; } /** * l_time_now: * * Get the running clocktime in microseconds * * Returns: Current clock time in microseconds **/ LIB_EXPORT uint64_t l_time_now(void) { struct timespec now; clock_gettime(CLOCK_BOOTTIME, &now); return _time_from_timespec(&now); } uint64_t time_realtime_now(void) { struct timespec now; clock_gettime(CLOCK_REALTIME, &now); return _time_from_timespec(&now); } /** * l_time_after * * Returns: True if time a is after time b **/ /** * l_time_before * * Returns: True if time a is before time b **/ /** * l_time_offset * * @time: Start time to calculate offset * @offset: Amount of time to add to 'time' * * Adds an offset to a time value. This checks for overflow, and if detected * returns UINT64_MAX. * * Returns: A time value 'time' + 'offset'. Or UINT64_MAX if time + offset * exceeds UINT64_MAX. **/ /* Compute ms + RAND*ms where RAND is in range -0.1 .. 0.1 */ uint64_t _time_fuzz_msecs(uint64_t ms) { /* We do this by subtracting 0.1ms and adding 0.1ms * rand[0 .. 2] */ return ms - ms / 10 + (l_getrandom_uint32() % (2 * L_MSEC_PER_SEC)) * ms / 10 / L_MSEC_PER_SEC; } uint64_t _time_pick_interval_secs(uint32_t min_secs, uint32_t max_secs) { uint64_t min_ms = min_secs * L_MSEC_PER_SEC; uint64_t max_ms = max_secs * L_MSEC_PER_SEC; return l_getrandom_uint32() % (max_ms + 1 - min_ms) + min_ms; } /* Compute a time in ms based on seconds + max_offset * [-1.0 .. 1.0] */ uint64_t _time_fuzz_secs(uint32_t secs, uint32_t max_offset) { uint64_t ms = secs * L_MSEC_PER_SEC; uint64_t r = l_getrandom_uint32(); max_offset *= L_MSEC_PER_SEC; if (r & 0x80000000) ms += (r & 0x7fffffff) % max_offset; else ms -= (r & 0x7fffffff) % max_offset; return ms; } /* * Convert a *recent* CLOCK_REALTIME-based timestamp to a * CLOCK_BOOTTIME-based usec count consistent with l_time functions. * The longer the time since the input timestamp the higher the * probability of the two clocks having diverged and the higher the * expected error magnitude. */ uint64_t _time_realtime_to_boottime(const struct timeval *ts) { uint64_t now_realtime; uint64_t now_boottime = l_time_now(); struct timespec timespec; uint64_t ts_realtime; uint64_t offset; clock_gettime(CLOCK_REALTIME, ×pec); now_realtime = _time_from_timespec(×pec); ts_realtime = _time_from_timeval(ts); offset = l_time_diff(ts_realtime, now_realtime); /* Most likely case, timestamp in the past */ if (l_time_before(ts_realtime, now_realtime)) { if (offset > now_boottime) return 0; return now_boottime - offset; } return l_time_offset(now_boottime, offset); } bluez-5.87/ell/PaxHeaders/time.h0000644000000000000000000000005014560467756013562 xustar0020 atime=1783076231 20 ctime=1783094098 bluez-5.87/ell/time.h0000644000000000000000000000224714560467756013250 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2019 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_TIME_H #define __ELL_TIME_H #include #include #ifdef __cplusplus extern "C" { #endif #define L_USEC_PER_SEC 1000000ULL #define L_MSEC_PER_SEC 1000ULL #define L_USEC_PER_MSEC 1000ULL #define L_NSEC_PER_SEC 1000000000ULL #define L_NSEC_PER_MSEC 1000000ULL #define L_NSEC_PER_USEC 1000ULL #define L_TIME_INVALID ((uint64_t) -1) uint64_t l_time_now(void); static inline bool l_time_after(uint64_t a, uint64_t b) { return a > b; } static inline bool l_time_before(uint64_t a, uint64_t b) { return l_time_after(b, a); } static inline uint64_t l_time_offset(uint64_t time, uint64_t offset) { /* check overflow */ if (offset > UINT64_MAX - time) return UINT64_MAX; return time + offset; } static inline uint64_t l_time_diff(uint64_t a, uint64_t b) { return (a < b) ? b - a : a - b; } static inline uint64_t l_time_to_secs(uint64_t time) { return time / L_USEC_PER_SEC; } static inline uint64_t l_time_to_msecs(uint64_t time) { return time / L_USEC_PER_MSEC; } #ifdef __cplusplus } #endif #endif /* __ELL_TIME_H */ bluez-5.87/ell/PaxHeaders/checksum.h0000644000000000000000000000005014770744372014421 xustar0020 atime=1783076232 20 ctime=1783094098 bluez-5.87/ell/checksum.h0000644000000000000000000000303514770744372014103 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_CHECKSUM_H #define __ELL_CHECKSUM_H #include #include #include #ifdef __cplusplus extern "C" { #endif struct l_checksum; enum l_checksum_type { L_CHECKSUM_NONE, L_CHECKSUM_MD4, L_CHECKSUM_MD5, L_CHECKSUM_SHA1, L_CHECKSUM_SHA224, L_CHECKSUM_SHA256, L_CHECKSUM_SHA384, L_CHECKSUM_SHA512, L_CHECKSUM_SHA3_224, L_CHECKSUM_SHA3_256, L_CHECKSUM_SHA3_384, L_CHECKSUM_SHA3_512, }; struct l_checksum *l_checksum_new(enum l_checksum_type type); struct l_checksum *l_checksum_new_cmac_aes(const void *key, size_t key_len); struct l_checksum *l_checksum_new_hmac(enum l_checksum_type type, const void *key, size_t key_len); struct l_checksum *l_checksum_clone(struct l_checksum *checksum); void l_checksum_free(struct l_checksum *checksum); void l_checksum_reset(struct l_checksum *checksum); bool l_checksum_update(struct l_checksum *checksum, const void *data, size_t len); bool l_checksum_updatev(struct l_checksum *checksum, const struct iovec *iov, size_t iov_len); ssize_t l_checksum_get_digest(struct l_checksum *checksum, void *digest, size_t len); char *l_checksum_get_string(struct l_checksum *checksum); bool l_checksum_is_supported(enum l_checksum_type type, bool check_hmac); bool l_checksum_cmac_aes_supported(void); ssize_t l_checksum_digest_length(enum l_checksum_type type); #ifdef __cplusplus } #endif #endif /* __ELL_CHECKSUM_H */ bluez-5.87/ell/PaxHeaders/io.c0000644000000000000000000000005014560467756013226 xustar0020 atime=1783076256 20 ctime=1783094099 bluez-5.87/ell/io.c0000644000000000000000000001775214560467756012723 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "useful.h" #include "main-private.h" #include "io.h" #include "private.h" /** * SECTION:io * @short_description: IO support * * IO support */ /** * l_io: * * Opaque object representing the IO. */ struct l_io { int fd; uint32_t events; bool close_on_destroy; l_io_read_cb_t read_handler; l_io_destroy_cb_t read_destroy; void *read_data; l_io_write_cb_t write_handler; l_io_destroy_cb_t write_destroy; void *write_data; l_io_disconnect_cb_t disconnect_handler; l_io_destroy_cb_t disconnect_destroy; void *disconnect_data; l_io_debug_cb_t debug_handler; l_io_destroy_cb_t debug_destroy; void *debug_data; }; static void io_cleanup(void *user_data) { struct l_io *io = user_data; l_util_debug(io->debug_handler, io->debug_data, "cleanup <%p>", io); if (io->write_destroy) io->write_destroy(io->write_data); io->write_handler = NULL; io->write_data = NULL; if (io->read_destroy) io->read_destroy(io->read_data); io->read_handler = NULL; io->read_data = NULL; if (io->close_on_destroy) close(io->fd); io->fd = -1; } static void io_closed(struct l_io *io) { /* * Save off copies of disconnect_handler, disconnect_destroy * and disconnect_data in case the handler calls io_destroy */ l_io_disconnect_cb_t handler = io->disconnect_handler; l_io_destroy_cb_t destroy = io->disconnect_destroy; void *disconnect_data = io->disconnect_data; io->disconnect_handler = NULL; io->disconnect_destroy = NULL; io->disconnect_data = NULL; if (handler) handler(io, disconnect_data); if (destroy) destroy(disconnect_data); } static void io_callback(int fd, uint32_t events, void *user_data) { struct l_io *io = user_data; if ((events & EPOLLIN) && io->read_handler) { l_util_debug(io->debug_handler, io->debug_data, "read event <%p>", io); if (!io->read_handler(io, io->read_data)) { if (io->read_destroy) io->read_destroy(io->read_data); io->read_handler = NULL; io->read_destroy = NULL; io->read_data = NULL; io->events &= ~EPOLLIN; if (watch_modify(io->fd, io->events, false) == -EBADF) { io->close_on_destroy = false; watch_clear(io->fd); io_closed(io); return; } } } if (unlikely(events & (EPOLLERR | EPOLLHUP))) { bool close_on_destroy = io->close_on_destroy; int fd = io->fd; l_util_debug(io->debug_handler, io->debug_data, "disconnect event <%p>", io); io_closed(io); watch_remove(fd, !close_on_destroy); return; } if ((events & EPOLLOUT) && io->write_handler) { l_util_debug(io->debug_handler, io->debug_data, "write event <%p>", io); if (!io->write_handler(io, io->write_data)) { if (io->write_destroy) io->write_destroy(io->write_data); io->write_handler = NULL; io->write_destroy = NULL; io->write_data = NULL; io->events &= ~EPOLLOUT; if (watch_modify(io->fd, io->events, false) == -EBADF) { io->close_on_destroy = false; watch_clear(io->fd); io_closed(io); return; } } } } /** * l_io_new: * @fd: file descriptor * * Create new IO handling for a given file descriptor. * * Returns: a newly allocated #l_io object **/ LIB_EXPORT struct l_io *l_io_new(int fd) { struct l_io *io; int err; if (unlikely(fd < 0)) return NULL; io = l_new(struct l_io, 1); io->fd = fd; io->events = EPOLLHUP | EPOLLERR; io->close_on_destroy = false; err = watch_add(io->fd, io->events, io_callback, io, io_cleanup); if (err) { l_free(io); return NULL; } return io; } /** * l_io_destroy: * @io: IO object * * Free IO object and close file descriptor (if enabled). **/ LIB_EXPORT void l_io_destroy(struct l_io *io) { if (unlikely(!io)) return; if (io->fd != -1) watch_remove(io->fd, !io->close_on_destroy); io_closed(io); if (io->debug_destroy) io->debug_destroy(io->debug_data); l_free(io); } /** * l_io_get_fd: * @io: IO object * * Returns: file descriptor associated with @io **/ LIB_EXPORT int l_io_get_fd(struct l_io *io) { if (unlikely(!io)) return -1; return io->fd; } /** * l_io_set_close_on_destroy: * @io: IO object * @do_close: setting for destroy handling * * Set the automatic closing of the file descriptor when destroying @io. * * Returns: #true on success and #false on failure **/ LIB_EXPORT bool l_io_set_close_on_destroy(struct l_io *io, bool do_close) { if (unlikely(!io)) return false; io->close_on_destroy = do_close; return true; } /** * l_io_set_read_handler: * @io: IO object * @callback: read handler callback function * @user_data: user data provided to read handler callback function * @destroy: destroy function for user data * * Set read function. * * Returns: #true on success and #false on failure **/ LIB_EXPORT bool l_io_set_read_handler(struct l_io *io, l_io_read_cb_t callback, void *user_data, l_io_destroy_cb_t destroy) { uint32_t events; int err; if (unlikely(!io || io->fd < 0)) return false; l_util_debug(io->debug_handler, io->debug_data, "set read handler <%p>", io); if (io->read_destroy) io->read_destroy(io->read_data); if (callback) events = io->events | EPOLLIN; else events = io->events & ~EPOLLIN; io->read_handler = callback; io->read_destroy = destroy; io->read_data = user_data; if (events == io->events) return true; err = watch_modify(io->fd, events, false); if (err) return false; io->events = events; return true; } /** * l_io_set_write_handler: * @io: IO object * @callback: write handler callback function * @user_data: user data provided to write handler callback function * @destroy: destroy function for user data * * Set write function. * * Returns: #true on success and #false on failure **/ LIB_EXPORT bool l_io_set_write_handler(struct l_io *io, l_io_write_cb_t callback, void *user_data, l_io_destroy_cb_t destroy) { uint32_t events; int err; if (unlikely(!io || io->fd < 0)) return false; l_util_debug(io->debug_handler, io->debug_data, "set write handler <%p>", io); if (io->write_handler == callback && io->write_destroy == destroy && io->write_data == user_data) return true; if (io->write_destroy) io->write_destroy(io->write_data); if (callback) events = io->events | EPOLLOUT; else events = io->events & ~EPOLLOUT; io->write_handler = callback; io->write_destroy = destroy; io->write_data = user_data; if (events == io->events) return true; err = watch_modify(io->fd, events, false); if (err) return false; io->events = events; return true; } /** * l_io_set_disconnect_handler: * @io: IO object * @callback: disconnect handler callback function * @user_data: user data provided to disconnect handler callback function * @destroy: destroy function for user data * * Set disconnect function. * * Returns: #true on success and #false on failure **/ LIB_EXPORT bool l_io_set_disconnect_handler(struct l_io *io, l_io_disconnect_cb_t callback, void *user_data, l_io_destroy_cb_t destroy) { if (unlikely(!io || io->fd < 0)) return false; l_util_debug(io->debug_handler, io->debug_data, "set disconnect handler <%p>", io); if (io->disconnect_destroy) io->disconnect_destroy(io->disconnect_data); io->disconnect_handler = callback; io->disconnect_destroy = destroy; io->disconnect_data = user_data; return true; } /** * l_io_set_debug: * @io: IO object * @callback: debug callback function * @user_data: user data provided to debug callback function * @destroy: destroy function for user data * * Set debug function. * * Returns: #true on success and #false on failure **/ LIB_EXPORT bool l_io_set_debug(struct l_io *io, l_io_debug_cb_t callback, void *user_data, l_io_destroy_cb_t destroy) { if (unlikely(!io)) return false; if (io->debug_destroy) io->debug_destroy(io->debug_data); io->debug_handler = callback; io->debug_destroy = destroy; io->debug_data = user_data; return true; } bluez-5.87/ell/PaxHeaders/uuid.c0000644000000000000000000000005014504767710013554 xustar0020 atime=1783076310 20 ctime=1783094099 bluez-5.87/ell/uuid.c0000644000000000000000000001305614504767710013242 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include "checksum.h" #include "random.h" #include "private.h" #include "useful.h" #include "uuid.h" const uint8_t L_UUID_NAMESPACE_DNS[16] = { 0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8, }; const uint8_t L_UUID_NAMESPACE_URL[16] = { 0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8, }; const uint8_t L_UUID_NAMESPACE_OID[16] = { 0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8, }; const uint8_t L_UUID_NAMESPACE_X500[16] = { 0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8, }; /* RFC 4122, Section 4.3 */ static bool name_from_namespace(int version, const uint8_t nsid[16], const void *name, size_t name_size, uint8_t out_uuid[16]) { enum l_checksum_type type; struct l_checksum *hash; struct iovec iov[2]; if (unlikely(!out_uuid)) return false; switch (version) { case 3: type = L_CHECKSUM_MD5; break; case 5: type = L_CHECKSUM_SHA1; break; default: return false; } hash = l_checksum_new(type); if (!hash) return false; iov[0].iov_base = (void *) nsid; iov[0].iov_len = 16; iov[1].iov_base = (void *) name; iov[1].iov_len = name_size; /* Compute the hash of the name space ID concatenated with the name. */ l_checksum_updatev(hash, iov, 2); /* o Set octets zero through 3 of the time_low field to octets zero * through 3 of the hash. * * o Set octets zero and one of the time_mid field to octets 4 and 5 of * the hash. * * o Set octets zero and one of the time_hi_and_version field to octets * 6 and 7 of the hash. * o Set the four most significant bits (bits 12 through 15) of the * time_hi_and_version field to the appropriate 4-bit version number * from Section 4.1.3. * * o Set the clock_seq_hi_and_reserved field to octet 8 of the hash. * o Set the two most significant bits (bits 6 and 7) of the * clock_seq_hi_and_reserved to zero and one, respectively. * * o Set the clock_seq_low field to octet 9 of the hash. * * o Set octets zero through five of the node field to octets 10 * through 15 of the hash. */ l_checksum_get_digest(hash, out_uuid, 16); /* Set 4 MSB bits of time_hi_and_version field */ out_uuid[6] &= 0x0f; out_uuid[6] |= version << 4; /* Set 2 MSB of clock_seq_hi_and_reserved to 10 */ out_uuid[8] &= 0x3f; out_uuid[8] |= 0x80; l_checksum_free(hash); return true; } LIB_EXPORT bool l_uuid_v3(const uint8_t nsid[16], const void *name, size_t name_size, uint8_t out_uuid[16]) { return name_from_namespace(3, nsid, name, name_size, out_uuid); } LIB_EXPORT bool l_uuid_v5(const uint8_t nsid[16], const void *name, size_t name_size, uint8_t out_uuid[16]) { return name_from_namespace(5, nsid, name, name_size, out_uuid); } /* RFC 4122, Section 4.4 */ LIB_EXPORT bool l_uuid_v4(uint8_t out_uuid[16]) { if (unlikely(!out_uuid)) return false; if (!l_getrandom(out_uuid, 16)) return false; /* * o Set the two most significant bits (bits 6 and 7) of the * clock_seq_hi_and_reserved to zero and one, respectively. * * o Set the four most significant bits (bits 12 through 15) of the * time_hi_and_version field to the 4-bit version number from * Section 4.1.3. * * o Set all the other bits to randomly (or pseudo-randomly) chosen * values. */ /* Set 4 MSB bits of time_hi_and_version field */ out_uuid[6] &= 0x0f; out_uuid[6] |= 4 << 4; /* Set 2 MSB of clock_seq_hi_and_reserved to 10 */ out_uuid[8] &= 0x3f; out_uuid[8] |= 0x80; return true; } /** * l_uuid_is_valid: * @uuid: UUID to check. * * Checks whether the given UUID is valid according to RFC 4122. This function * checks that the version field is set properly and the variant of the UUID * is set to RFC 4122. * * Returns: Whether the UUID is valid **/ LIB_EXPORT bool l_uuid_is_valid(const uint8_t uuid[16]) { unsigned int version; unsigned int variant; if (!uuid) return false; variant = uuid[8] >> 6; if (variant != 2) return false; version = uuid[6] >> 4; if (version < 1 || version > 5) return false; return true; } LIB_EXPORT enum l_uuid_version l_uuid_get_version(const uint8_t uuid[16]) { unsigned int version; version = uuid[6] >> 4; return version; } LIB_EXPORT bool l_uuid_to_string(const uint8_t uuid[16], char *dest, size_t dest_size) { int n; n = snprintf(dest, dest_size, "%02x%02x%02x%02x-%02x%02x-%02x%02x-" "%02x%02x-%02x%02x%02x%02x%02x%02x", uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7], uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]); if (n < 0 || (size_t) n >= dest_size) return false; return true; } LIB_EXPORT bool l_uuid_from_string(const char *src, uint8_t uuid[16]) { uint8_t buf[16]; int n; /* * textual representation: 32 hex digits + 4 group separators */ if (strlen(src) < 16 * 2 + 4) return false; n = sscanf(src, "%02hhx%02hhx%02hhx%02hhx-" "%02hhx%02hhx-" "%02hhx%02hhx-" "%02hhx%02hhx-" "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", &buf[0], &buf[1], &buf[2], &buf[3], &buf[4], &buf[5], &buf[6], &buf[7], &buf[8], &buf[9], &buf[10], &buf[11], &buf[12], &buf[13], &buf[14], &buf[15]); if (n != 16) return false; if (!l_uuid_is_valid(buf)) return false; memcpy(uuid, buf, sizeof(buf)); return true; } bluez-5.87/ell/PaxHeaders/base64.h0000644000000000000000000000005014504767710013677 xustar0020 atime=1783076232 20 ctime=1783094098 bluez-5.87/ell/base64.h0000644000000000000000000000064014504767710013360 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2015 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_BASE64_H #define __ELL_BASE64_H #ifdef __cplusplus extern "C" { #endif uint8_t *l_base64_decode(const char *in, size_t in_len, size_t *n_written); char *l_base64_encode(const uint8_t *in, size_t in_len, int columns); #ifdef __cplusplus } #endif #endif /* __ELL_BASE64_H */ bluez-5.87/ell/PaxHeaders/ecdh.c0000644000000000000000000000005014773301370013503 xustar0020 atime=1783076335 20 ctime=1783094099 bluez-5.87/ell/ecdh.c0000644000000000000000000000451614773301370013172 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2018 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #include #include "private.h" #include "ecc-private.h" #include "ecc.h" #include "ecdh.h" #include "random.h" #include "useful.h" /* * Some sane maximum for calculating the public key. */ #define ECDH_MAX_ITERATIONS 20 /* * IETF draft-jivsov-ecc-compact-00 Section 4.2.1 * * The following algorithm calculates a key pair {k, Q=k*G=(x,y)}, where k is * the private key and Q=(x,y) is the public key. * * Black box generation: * 1. Generate a key pair {k, Q=k*G=(x,y)} with KG * 2. if( y != min(y,p-y) ) goto step 1 * 3. output {k, Q=(x,y)} as a key pair */ LIB_EXPORT bool l_ecdh_generate_key_pair(const struct l_ecc_curve *curve, struct l_ecc_scalar **out_private, struct l_ecc_point **out_public) { bool compliant = false; int iter = 0; uint64_t p2[L_ECC_MAX_DIGITS]; if (unlikely(!curve || !out_private || !out_public)) return false; _ecc_calculate_p2(curve, p2); *out_public = l_ecc_point_new(curve); while (!compliant && iter++ < ECDH_MAX_ITERATIONS) { /* aborts if no valid private key can be generated */ *out_private = l_ecc_scalar_new_random(curve); _ecc_point_mult(*out_public, &curve->g, (*out_private)->c, NULL, curve->p); /* ensure public key is compliant */ if (_vli_cmp((*out_public)->y, p2, curve->ndigits) >= 0) { compliant = true; break; } l_ecc_scalar_free(*out_private); *out_private = NULL; } if (!compliant) { l_ecc_point_free(*out_public); *out_public = NULL; return false; } return true; } LIB_EXPORT bool l_ecdh_generate_shared_secret( const struct l_ecc_scalar *private_key, const struct l_ecc_point *other_public, struct l_ecc_scalar **secret) { const struct l_ecc_curve *curve = private_key->curve; struct l_ecc_scalar *z; struct l_ecc_point *product; if (unlikely(!private_key || !other_public || !secret)) return false; /* aborts if no valid scalar can be generated */ z = l_ecc_scalar_new_random(curve); product = l_ecc_point_new(curve); _ecc_point_mult(product, other_public, private_key->c, z->c, curve->p); *secret = _ecc_constant_new(curve, product->x, curve->ndigits * 8); l_ecc_point_free(product); l_ecc_scalar_free(z); return true; } bluez-5.87/ell/PaxHeaders/dbus-service.c0000644000000000000000000000005014661631567015205 xustar0020 atime=1783076298 20 ctime=1783094099 bluez-5.87/ell/dbus-service.c0000644000000000000000000015241414661631567014675 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include "useful.h" #include "queue.h" #include "string.h" #include "hashmap.h" #include "dbus.h" #include "dbus-service.h" #include "dbus-private.h" #include "private.h" #include "idle.h" #define XML_ID "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" #define XML_DTD "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd" #define XML_HEAD "\n" static const char *static_introspectable = "\t\n" "\t\t\n" "\t\t\t\n" "\t\t\n\t\n"; struct _dbus_method { l_dbus_interface_method_cb_t cb; uint32_t flags; unsigned char name_len; char metainfo[]; }; struct _dbus_signal { uint32_t flags; unsigned char name_len; char metainfo[]; }; struct _dbus_property { l_dbus_property_get_cb_t getter; l_dbus_property_set_cb_t setter; uint32_t flags; unsigned char name_len; char metainfo[]; }; struct l_dbus_interface { struct l_queue *methods; struct l_queue *signals; struct l_queue *properties; bool handle_old_style_properties; void (*instance_destroy)(void *); char name[]; }; struct child_node { struct object_node *node; struct child_node *next; char subpath[]; }; struct interface_instance { struct l_dbus_interface *interface; void *user_data; }; struct object_node { struct object_node *parent; struct l_queue *instances; struct child_node *children; void *user_data; void (*destroy) (void *); }; struct object_manager { char *path; struct l_dbus *dbus; struct l_queue *announce_added; struct l_queue *announce_removed; }; struct interface_add_record { char *path; struct object_node *object; struct l_queue *instances; }; struct interface_remove_record { char *path; struct object_node *object; struct l_queue *interface_names; }; struct property_change_record { char *path; struct object_node *object; struct interface_instance *instance; struct l_queue *properties; }; struct _dbus_object_tree { struct l_hashmap *interfaces; struct l_hashmap *objects; struct object_node *root; struct l_queue *object_managers; struct l_queue *property_changes; struct l_idle *emit_signals_work; bool flushing; }; void _dbus_method_introspection(struct _dbus_method *info, struct l_string *buf) { const char *sig; const char *end; const char *pname; unsigned int offset = info->name_len + 1; l_string_append_printf(buf, "\t\t\n", info->metainfo); sig = info->metainfo + offset; offset += strlen(sig) + 1; for (; *sig; sig++) { end = _dbus_signature_end(sig); pname = info->metainfo + offset; l_string_append_printf(buf, "\t\t\t\n", pname, (int) (end - sig + 1), sig); sig = end; offset += strlen(pname) + 1; } sig = info->metainfo + offset; offset += strlen(sig) + 1; for (; *sig; sig++) { end = _dbus_signature_end(sig); pname = info->metainfo + offset; l_string_append_printf(buf, "\t\t\t\n", pname, (int) (end - sig + 1), sig); sig = end; offset += strlen(pname) + 1; } if (info->flags & L_DBUS_METHOD_FLAG_DEPRECATED) l_string_append(buf, "\t\t\t\n"); if (info->flags & L_DBUS_METHOD_FLAG_NOREPLY) l_string_append(buf, "\t\t\t\n"); l_string_append(buf, "\t\t\n"); } void _dbus_signal_introspection(struct _dbus_signal *info, struct l_string *buf) { const char *sig; const char *end; const char *pname; unsigned int offset = info->name_len + 1; l_string_append_printf(buf, "\t\t\n", info->metainfo); sig = info->metainfo + offset; offset += strlen(sig) + 1; for (; *sig; sig++) { end = _dbus_signature_end(sig); pname = info->metainfo + offset; l_string_append_printf(buf, "\t\t\t\n", pname, (int) (end - sig + 1), sig); sig = end; offset += strlen(pname) + 1; } if (info->flags & L_DBUS_SIGNAL_FLAG_DEPRECATED) l_string_append(buf, "\t\t\t\n"); l_string_append(buf, "\t\t\n"); } void _dbus_property_introspection(struct _dbus_property *info, struct l_string *buf) { unsigned int offset = info->name_len + 1; const char *signature = info->metainfo + offset; l_string_append_printf(buf, "\t\tmetainfo, signature); if (info->setter) l_string_append(buf, "access=\"readwrite\""); else l_string_append(buf, "access=\"read\""); if (info->flags & L_DBUS_METHOD_FLAG_DEPRECATED) { l_string_append(buf, ">\n"); l_string_append(buf, "\t\t\t\n"); l_string_append(buf, "\t\t\n"); } else l_string_append(buf, "/>\n"); } void _dbus_interface_introspection(struct l_dbus_interface *interface, struct l_string *buf) { l_string_append_printf(buf, "\t\n", interface->name); l_queue_foreach(interface->methods, (l_queue_foreach_func_t) _dbus_method_introspection, buf); l_queue_foreach(interface->signals, (l_queue_foreach_func_t) _dbus_signal_introspection, buf); l_queue_foreach(interface->properties, (l_queue_foreach_func_t) _dbus_property_introspection, buf); l_string_append(buf, "\t\n"); } #define COPY_PARAMS(dest, signature, args) \ do { \ const char *pname; \ const char *sig; \ dest = stpcpy(dest, signature) + 1; \ for (sig = signature; *sig; sig++) { \ sig = _dbus_signature_end(sig); \ pname = va_arg(args, const char *); \ dest = stpcpy(dest, pname) + 1; \ } \ } while(0) #define SIZE_PARAMS(signature, args) \ ({ \ unsigned int len = strlen(signature) + 1; \ const char *pname; \ const char *sig; \ for (sig = signature; *sig; sig++) { \ sig = _dbus_signature_end(sig); \ if (!sig) { \ len = 0; \ break; \ } \ pname = va_arg(args, const char *); \ len += strlen(pname) + 1; \ } \ len; \ }) LIB_EXPORT bool l_dbus_interface_method(struct l_dbus_interface *interface, const char *name, uint32_t flags, l_dbus_interface_method_cb_t cb, const char *return_sig, const char *param_sig, ...) { va_list args; unsigned int return_info_len; unsigned int param_info_len; struct _dbus_method *info; char *p; if (!_dbus_valid_method(name)) return false; if (unlikely(!return_sig || !param_sig)) return false; if (return_sig[0] && !_dbus_valid_signature(return_sig)) return false; if (param_sig[0] && !_dbus_valid_signature(param_sig)) return false; /* Pre-calculate the needed meta-info length */ va_start(args, param_sig); return_info_len = SIZE_PARAMS(return_sig, args); param_info_len = SIZE_PARAMS(param_sig, args); va_end(args); if (!return_info_len || !param_info_len) return false; info = l_malloc(sizeof(*info) + return_info_len + param_info_len + strlen(name) + 1); info->cb = cb; info->flags = flags; info->name_len = strlen(name); strcpy(info->metainfo, name); va_start(args, param_sig); /* * We store param signature + parameter names first, to speed up * lookups during the message dispatch procedures. */ p = info->metainfo + info->name_len + param_info_len + 1; COPY_PARAMS(p, return_sig, args); p = info->metainfo + info->name_len + 1; COPY_PARAMS(p, param_sig, args); va_end(args); l_queue_push_tail(interface->methods, info); return true; } LIB_EXPORT bool l_dbus_interface_signal(struct l_dbus_interface *interface, const char *name, uint32_t flags, const char *signature, ...) { va_list args; unsigned int metainfo_len; struct _dbus_signal *info; char *p; if (!_dbus_valid_method(name)) return false; if (unlikely(!signature)) return false; if (signature[0] && !_dbus_valid_signature(signature)) return false; /* Pre-calculate the needed meta-info length */ va_start(args, signature); metainfo_len = SIZE_PARAMS(signature, args); va_end(args); if (!metainfo_len) return false; metainfo_len += strlen(name) + 1; info = l_malloc(sizeof(*info) + metainfo_len); info->flags = flags; info->name_len = strlen(name); p = stpcpy(info->metainfo, name) + 1; va_start(args, signature); COPY_PARAMS(p, signature, args); va_end(args); l_queue_push_tail(interface->signals, info); return true; } LIB_EXPORT bool l_dbus_interface_property(struct l_dbus_interface *interface, const char *name, uint32_t flags, const char *signature, l_dbus_property_get_cb_t getter, l_dbus_property_set_cb_t setter) { unsigned int metainfo_len; struct _dbus_property *info; char *p; if (!_dbus_valid_method(name)) return false; if (unlikely(!signature || !getter)) return false; if (_dbus_num_children(signature) != 1) return false; /* Pre-calculate the needed meta-info length */ metainfo_len = strlen(name) + 1; metainfo_len += strlen(signature) + 1; info = l_malloc(sizeof(*info) + metainfo_len); info->flags = flags; info->name_len = strlen(name); info->getter = getter; info->setter = setter; p = stpcpy(info->metainfo, name) + 1; strcpy(p, signature); l_queue_push_tail(interface->properties, info); return true; } struct l_dbus_interface *_dbus_interface_new(const char *name) { struct l_dbus_interface *interface; interface = l_malloc(sizeof(*interface) + strlen(name) + 1); interface->methods = l_queue_new(); interface->signals = l_queue_new(); interface->properties = l_queue_new(); strcpy(interface->name, name); return interface; } void _dbus_interface_free(struct l_dbus_interface *interface) { l_queue_destroy(interface->methods, l_free); l_queue_destroy(interface->signals, l_free); l_queue_destroy(interface->properties, l_free); l_free(interface); } static bool match_method(const void *a, const void *b) { const struct _dbus_method *method = a; const char *name = b; if (!strcmp(method->metainfo, name)) return true; return false; } struct _dbus_method *_dbus_interface_find_method(struct l_dbus_interface *i, const char *method) { return l_queue_find(i->methods, match_method, (char *) method); } static bool match_signal(const void *a, const void *b) { const struct _dbus_signal *signal = a; const char *name = b; if (!strcmp(signal->metainfo, name)) return true; return false; } struct _dbus_signal *_dbus_interface_find_signal(struct l_dbus_interface *i, const char *signal) { return l_queue_find(i->signals, match_signal, (char *) signal); } static bool match_property(const void *a, const void *b) { const struct _dbus_property *property = a; const char *name = b; if (!strcmp(property->metainfo, name)) return true; return false; } struct _dbus_property *_dbus_interface_find_property(struct l_dbus_interface *i, const char *property) { return l_queue_find(i->properties, match_property, (char *) property); } static void interface_instance_free(struct interface_instance *instance) { if (instance->interface->instance_destroy) instance->interface->instance_destroy(instance->user_data); l_free(instance); } static bool match_interface_instance(const void *a, const void *b) { const struct interface_instance *instance = a; const char *name = b; if (!strcmp(instance->interface->name, name)) return true; return false; } static bool match_interface_instance_ptr(const void *a, const void *b) { const struct interface_instance *instance = a; return instance->interface == b; } static void interface_add_record_free(void *data) { struct interface_add_record *rec = data; l_free(rec->path); l_queue_destroy(rec->instances, NULL); l_free(rec); } static void interface_removed_record_free(void *data) { struct interface_remove_record *rec = data; l_free(rec->path); l_queue_destroy(rec->interface_names, l_free); l_free(rec); } static void property_change_record_free(void *data) { struct property_change_record *rec = data; l_free(rec->path); l_queue_destroy(rec->properties, NULL); l_free(rec); } static void properties_setup_func(struct l_dbus_interface *); static void object_manager_setup_func(struct l_dbus_interface *); struct _dbus_object_tree *_dbus_object_tree_new() { struct _dbus_object_tree *tree; tree = l_new(struct _dbus_object_tree, 1); tree->interfaces = l_hashmap_new(); l_hashmap_set_hash_function(tree->interfaces, l_str_hash); l_hashmap_set_compare_function(tree->interfaces, (l_hashmap_compare_func_t)strcmp); tree->objects = l_hashmap_string_new(); tree->root = l_new(struct object_node, 1); tree->property_changes = l_queue_new(); _dbus_object_tree_register_interface(tree, L_DBUS_INTERFACE_PROPERTIES, properties_setup_func, NULL, false); tree->object_managers = l_queue_new(); _dbus_object_tree_register_interface(tree, L_DBUS_INTERFACE_OBJECT_MANAGER, object_manager_setup_func, NULL, false); return tree; } static void subtree_free(struct object_node *node) { struct child_node *child; while (node->children) { child = node->children; node->children = child->next; subtree_free(child->node); l_free(child); } l_queue_destroy(node->instances, (l_queue_destroy_func_t) interface_instance_free); if (node->destroy) node->destroy(node->user_data); l_free(node); } static void object_manager_free(void *data) { struct object_manager *manager = data; l_free(manager->path); l_queue_destroy(manager->announce_added, interface_add_record_free); l_queue_destroy(manager->announce_removed, interface_removed_record_free); l_free(manager); } void _dbus_object_tree_free(struct _dbus_object_tree *tree) { subtree_free(tree->root); l_hashmap_destroy(tree->interfaces, (l_hashmap_destroy_func_t) _dbus_interface_free); l_hashmap_destroy(tree->objects, NULL); l_queue_destroy(tree->object_managers, object_manager_free); l_queue_destroy(tree->property_changes, property_change_record_free); if (tree->emit_signals_work) l_idle_remove(tree->emit_signals_work); l_free(tree); } static struct object_node *makepath_recurse(struct object_node *node, const char *path) { const char *end; struct child_node *child; if (*path == '\0') return node; path += 1; end = strchrnul(path, '/'); child = node->children; while (child) { if (!strncmp(child->subpath, path, end - path) && child->subpath[end - path] == '\0') goto done; child = child->next; } child = l_malloc(sizeof(*child) + end - path + 1); child->node = l_new(struct object_node, 1); child->node->parent = node; memcpy(child->subpath, path, end - path); child->subpath[end-path] = '\0'; child->next = node->children; node->children = child; done: return makepath_recurse(child->node, end); } struct object_node *_dbus_object_tree_makepath(struct _dbus_object_tree *tree, const char *path) { if (path[0] == '/' && path[1] == '\0') return tree->root; return makepath_recurse(tree->root, path); } static struct object_node *lookup_recurse(struct object_node *node, const char *path) { const char *end; struct child_node *child; if (*path == '\0') return node; path += 1; end = strchrnul(path, '/'); child = node->children; while (child) { if (!strncmp(child->subpath, path, end - path) && child->subpath[end - path] == '\0') return lookup_recurse(child->node, end); child = child->next; } return NULL; } struct object_node *_dbus_object_tree_lookup(struct _dbus_object_tree *tree, const char *path) { if (path[0] == '/' && path[1] == '\0') return tree->root; return lookup_recurse(tree->root, path); } void _dbus_object_tree_prune_node(struct object_node *node) { struct object_node *parent = node->parent; struct child_node *p = NULL, *c; while (parent) { for (c = parent->children, p = NULL; c; p = c, c = c->next) { if (c->node != node) continue; if (p) p->next = c->next; else parent->children = c->next; subtree_free(c->node); l_free(c); break; } if (parent->children != NULL) return; if (parent->instances) return; node = parent; parent = node->parent; } } struct object_node *_dbus_object_tree_new_object(struct _dbus_object_tree *tree, const char *path, void *user_data, void (*destroy) (void *)) { struct object_node *node; if (!_dbus_valid_object_path(path)) return NULL; if (l_hashmap_lookup(tree->objects, path)) return NULL; node = _dbus_object_tree_makepath(tree, path); node->user_data = user_data; node->destroy = destroy; /* * Registered objects in the tree are marked by being present in the * tree->objects hash and having non-null node->instances. Remaining * nodes are intermediate path elements added and removed * automatically. */ node->instances = l_queue_new(); l_hashmap_insert(tree->objects, path, node); return node; } bool _dbus_object_tree_object_destroy(struct _dbus_object_tree *tree, const char *path) { struct object_node *node; const struct l_queue_entry *entry; const struct interface_instance *instance; node = l_hashmap_lookup(tree->objects, path); if (!node) return false; while ((entry = l_queue_get_entries(node->instances))) { instance = entry->data; if (!_dbus_object_tree_remove_interface(tree, path, instance->interface->name)) return false; } l_hashmap_remove(tree->objects, path); l_queue_destroy(node->instances, NULL); node->instances = NULL; if (node->destroy) { node->destroy(node->user_data); node->destroy = NULL; } if (!node->children) _dbus_object_tree_prune_node(node); return true; } static bool get_properties_dict(struct l_dbus *dbus, struct l_dbus_message *message, struct l_dbus_message_builder *builder, const struct l_dbus_interface *interface, void *user_data) { const struct l_queue_entry *entry; const struct _dbus_property *property; const char *signature; l_dbus_message_builder_enter_array(builder, "{sv}"); _dbus_message_builder_mark(builder); for (entry = l_queue_get_entries(interface->properties); entry; entry = entry->next) { property = entry->data; signature = property->metainfo + strlen(property->metainfo) + 1; l_dbus_message_builder_enter_dict(builder, "sv"); l_dbus_message_builder_append_basic(builder, 's', property->metainfo); l_dbus_message_builder_enter_variant(builder, signature); if (!property->getter(dbus, message, builder, user_data)) { if (!_dbus_message_builder_rewind(builder)) return false; continue; } l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_leave_dict(builder); _dbus_message_builder_mark(builder); } l_dbus_message_builder_leave_array(builder); return true; } static struct l_dbus_message *build_interfaces_removed_signal( const struct object_manager *manager, const struct interface_remove_record *rec) { struct l_dbus_message *signal; struct l_dbus_message_builder *builder; const struct l_queue_entry *entry; signal = l_dbus_message_new_signal(manager->dbus, manager->path, L_DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesRemoved"); builder = l_dbus_message_builder_new(signal); l_dbus_message_builder_append_basic(builder, 'o', rec->path); l_dbus_message_builder_enter_array(builder, "s"); for (entry = l_queue_get_entries(rec->interface_names); entry; entry = entry->next) l_dbus_message_builder_append_basic(builder, 's', entry->data); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); return signal; } static struct l_dbus_message *build_interfaces_added_signal( const struct object_manager *manager, const struct interface_add_record *rec) { struct l_dbus_message *signal; struct l_dbus_message_builder *builder; const struct l_queue_entry *entry; const struct interface_instance *instance; signal = l_dbus_message_new_signal(manager->dbus, manager->path, L_DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesAdded"); builder = l_dbus_message_builder_new(signal); l_dbus_message_builder_append_basic(builder, 'o', rec->path); l_dbus_message_builder_enter_array(builder, "{sa{sv}}"); for (entry = l_queue_get_entries(rec->instances); entry; entry = entry->next) { instance = entry->data; l_dbus_message_builder_enter_dict(builder, "sa{sv}"); l_dbus_message_builder_append_basic(builder, 's', instance->interface->name); if (!get_properties_dict(manager->dbus, signal, builder, instance->interface, instance->user_data)) { l_dbus_message_builder_destroy(builder); l_dbus_message_unref(signal); return NULL; } l_dbus_message_builder_leave_dict(builder); } l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); return signal; } static struct l_dbus_message *build_old_property_changed_signal( struct l_dbus *dbus, const struct property_change_record *rec, const struct _dbus_property *property) { struct l_dbus_message *signal; struct l_dbus_message_builder *builder; const char *signature; signature = property->metainfo + strlen(property->metainfo) + 1; signal = l_dbus_message_new_signal(dbus, rec->path, rec->instance->interface->name, "PropertyChanged"); builder = l_dbus_message_builder_new(signal); l_dbus_message_builder_append_basic(builder, 's', property->metainfo); l_dbus_message_builder_enter_variant(builder, signature); if (!property->getter(dbus, signal, builder, rec->instance->user_data)) { l_dbus_message_builder_destroy(builder); l_dbus_message_unref(signal); return NULL; } l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); return signal; } static struct l_dbus_message *build_properties_changed_signal( struct l_dbus *dbus, const struct property_change_record *rec) { struct l_dbus_message *signal; struct l_dbus_message_builder *builder; const struct l_queue_entry *entry; const struct _dbus_property *property; const char *signature; struct l_queue *invalidated; signal = l_dbus_message_new_signal(dbus, rec->path, L_DBUS_INTERFACE_PROPERTIES, "PropertiesChanged"); builder = l_dbus_message_builder_new(signal); invalidated = l_queue_new(); l_dbus_message_builder_append_basic(builder, 's', rec->instance->interface->name); l_dbus_message_builder_enter_array(builder, "{sv}"); for (entry = l_queue_get_entries(rec->properties); entry; entry = entry->next) { property = entry->data; signature = property->metainfo + strlen(property->metainfo) + 1; _dbus_message_builder_mark(builder); l_dbus_message_builder_enter_dict(builder, "sv"); l_dbus_message_builder_append_basic(builder, 's', property->metainfo); l_dbus_message_builder_enter_variant(builder, signature); if (!property->getter(dbus, signal, builder, rec->instance->user_data)) { if (!_dbus_message_builder_rewind(builder)) { l_dbus_message_unref(signal); signal = NULL; goto done; } l_queue_push_tail(invalidated, (void *) property); continue; } l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_leave_dict(builder); } l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_enter_array(builder, "s"); while ((property = l_queue_pop_head(invalidated))) l_dbus_message_builder_append_basic(builder, 's', property->metainfo); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_finalize(builder); done: l_dbus_message_builder_destroy(builder); l_queue_destroy(invalidated, NULL); return signal; } struct emit_signals_data { struct l_dbus *dbus; struct object_manager *manager; struct object_node *node; }; static bool emit_interfaces_removed(void *data, void *user_data) { struct interface_remove_record *rec = data; struct emit_signals_data *es = user_data; struct l_dbus_message *signal; if (es->node && rec->object != es->node) return false; signal = build_interfaces_removed_signal(es->manager, rec); interface_removed_record_free(rec); if (signal) l_dbus_send(es->manager->dbus, signal); return true; } static bool emit_interfaces_added(void *data, void *user_data) { struct interface_add_record *rec = data; struct emit_signals_data *es = user_data; struct l_dbus_message *signal; if (es->node && rec->object != es->node) return false; signal = build_interfaces_added_signal(es->manager, rec); interface_add_record_free(rec); if (signal) l_dbus_send(es->manager->dbus, signal); return true; } static bool emit_properties_changed(void *data, void *user_data) { struct property_change_record *rec = data; struct emit_signals_data *es = user_data; struct l_dbus_message *signal; const struct l_queue_entry *entry; if (es->node && rec->object != es->node) return false; if (rec->instance->interface->handle_old_style_properties) for (entry = l_queue_get_entries(rec->properties); entry; entry = entry->next) { signal = build_old_property_changed_signal(es->dbus, rec, entry->data); if (signal) l_dbus_send(es->dbus, signal); } if (l_queue_find(rec->object->instances, match_interface_instance, L_DBUS_INTERFACE_PROPERTIES)) { signal = build_properties_changed_signal(es->dbus, rec); if (signal) l_dbus_send(es->dbus, signal); } property_change_record_free(rec); return true; } void _dbus_object_tree_signals_flush(struct l_dbus *dbus, const char *path) { struct _dbus_object_tree *tree = _dbus_get_tree(dbus); const struct l_queue_entry *entry; struct emit_signals_data data; bool all_done = true; if (!tree->emit_signals_work || tree->flushing) return; tree->flushing = true; data.dbus = dbus; data.node = path ? _dbus_object_tree_lookup(tree, path) : NULL; for (entry = l_queue_get_entries(tree->object_managers); entry; entry = entry->next) { data.manager = entry->data; l_queue_foreach_remove(data.manager->announce_removed, emit_interfaces_removed, &data); if (!l_queue_isempty(data.manager->announce_removed)) all_done = false; l_queue_foreach_remove(data.manager->announce_added, emit_interfaces_added, &data); if (!l_queue_isempty(data.manager->announce_added)) all_done = false; } l_queue_foreach_remove(tree->property_changes, emit_properties_changed, &data); if (!l_queue_isempty(tree->property_changes)) all_done = false; if (all_done) { l_idle_remove(tree->emit_signals_work); tree->emit_signals_work = NULL; } tree->flushing = false; } static void emit_signals(struct l_idle *idle, void *user_data) { struct l_dbus *dbus = user_data; _dbus_object_tree_signals_flush(dbus, NULL); } static void schedule_emit_signals(struct l_dbus *dbus) { struct _dbus_object_tree *tree = _dbus_get_tree(dbus); if (tree->emit_signals_work) return; tree->emit_signals_work = l_idle_create(emit_signals, dbus, NULL); } static bool match_property_changes_instance(const void *a, const void *b) { const struct property_change_record *rec = a; return rec->instance == b; } static bool match_pointer(const void *a, const void *b) { return a == b; } bool _dbus_object_tree_property_changed(struct l_dbus *dbus, const char *path, const char *interface_name, const char *property_name) { struct property_change_record *rec; struct object_node *object; struct interface_instance *instance; struct _dbus_property *property; struct _dbus_object_tree *tree = _dbus_get_tree(dbus); object = l_hashmap_lookup(tree->objects, path); if (!object) return false; instance = l_queue_find(object->instances, match_interface_instance, interface_name); if (!instance) return false; property = _dbus_interface_find_property(instance->interface, property_name); if (!property) return false; rec = l_queue_find(tree->property_changes, match_property_changes_instance, instance); if (rec) { if (l_queue_find(rec->properties, match_pointer, property)) return true; } else { rec = l_new(struct property_change_record, 1); rec->path = l_strdup(path); rec->object = object; rec->instance = instance; rec->properties = l_queue_new(); l_queue_push_tail(tree->property_changes, rec); } l_queue_push_tail(rec->properties, property); schedule_emit_signals(dbus); return true; } static void pending_property_set_done_common(struct l_dbus *dbus, struct l_dbus_message *message, struct l_dbus_message *reply, bool auto_emit) { const char *member; const char *interface_name; const char *property_name; struct l_dbus_message_iter variant; if (!reply) { reply = l_dbus_message_new_method_return(message); l_dbus_message_set_arguments(reply, ""); } l_dbus_send(dbus, l_dbus_message_ref(reply)); member = l_dbus_message_get_member(message); if (!strcmp(member, "SetProperty")) { if (!l_dbus_message_get_arguments(message, "sv", &property_name, &variant)) goto done; interface_name = l_dbus_message_get_interface(message); } else if (strcmp(member, "Set") || !l_dbus_message_get_arguments(message, "ssv", &interface_name, &property_name, &variant)) goto done; if (auto_emit) _dbus_object_tree_property_changed(dbus, l_dbus_message_get_path(message), interface_name, property_name); done: l_dbus_message_unref(message); l_dbus_message_unref(reply); } static void pending_property_set_done_emit(struct l_dbus *dbus, struct l_dbus_message *message, struct l_dbus_message *reply) { pending_property_set_done_common(dbus, message, reply, true); } static void pending_property_set_done(struct l_dbus *dbus, struct l_dbus_message *message, struct l_dbus_message *reply) { pending_property_set_done_common(dbus, message, reply, false); } static struct l_dbus_message *old_set_property(struct l_dbus *dbus, struct l_dbus_message *message, void *user_data) { struct l_dbus_interface *interface; const char *property_name; const struct _dbus_property *property; struct l_dbus_message_iter variant; struct _dbus_object_tree *tree = _dbus_get_tree(dbus); struct l_dbus_message *reply; l_dbus_property_complete_cb_t complete_cb; interface = l_hashmap_lookup(tree->interfaces, l_dbus_message_get_interface(message)); /* If we got here the interface must exist */ if (!l_dbus_message_get_arguments(message, "sv", &property_name, &variant)) return l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "InvalidArgs", "Invalid arguments"); property = _dbus_interface_find_property(interface, property_name); if (!property) return l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "InvalidArgs", "Unknown Property %s", property_name); if (!property->setter) return l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "InvalidArgs", "Property %s is read-only", property_name); if (property->flags & L_DBUS_PROPERTY_FLAG_AUTO_EMIT) complete_cb = pending_property_set_done_emit; else complete_cb = pending_property_set_done; reply = property->setter(dbus, l_dbus_message_ref(message), &variant, complete_cb, user_data); if (reply) complete_cb(dbus, message, reply); return NULL; } static struct l_dbus_message *old_get_properties(struct l_dbus *dbus, struct l_dbus_message *message, void *user_data) { const struct l_dbus_interface *interface; struct l_dbus_message *reply; struct l_dbus_message_builder *builder; struct _dbus_object_tree *tree = _dbus_get_tree(dbus); interface = l_hashmap_lookup(tree->interfaces, l_dbus_message_get_interface(message)); /* If we got here the interface must exist */ reply = l_dbus_message_new_method_return(message); builder = l_dbus_message_builder_new(reply); if (!get_properties_dict(dbus, message, builder, interface, user_data)) { l_dbus_message_unref(reply); reply = l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "Failed", "Getting properties failed"); } else l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); return reply; } bool _dbus_object_tree_register_interface(struct _dbus_object_tree *tree, const char *interface, void (*setup_func)(struct l_dbus_interface *), void (*destroy) (void *), bool old_style_properties) { struct l_dbus_interface *dbi; if (!_dbus_valid_interface(interface)) return false; /* * Check to make sure we do not have this interface already * registered */ dbi = l_hashmap_lookup(tree->interfaces, interface); if (dbi) return false; dbi = _dbus_interface_new(interface); dbi->instance_destroy = destroy; dbi->handle_old_style_properties = old_style_properties; /* Add our methods first so we don't have to check for conflicts. */ if (old_style_properties) { l_dbus_interface_method(dbi, "SetProperty", 0, old_set_property, "", "sv", "name", "value"); l_dbus_interface_method(dbi, "GetProperties", 0, old_get_properties, "a{sv}", "", "properties"); l_dbus_interface_signal(dbi, "PropertyChanged", 0, "sv", "name", "value"); } setup_func(dbi); l_hashmap_insert(tree->interfaces, dbi->name, dbi); return true; } struct interface_check { struct _dbus_object_tree *tree; const char *interface; }; static void check_interface_used(const void *key, void *value, void *user_data) { const char *path = key; struct object_node *node = value; struct interface_check *state = user_data; if (!l_queue_find(node->instances, match_interface_instance, (char *) state->interface)) return; _dbus_object_tree_remove_interface(state->tree, path, state->interface); } bool _dbus_object_tree_unregister_interface(struct _dbus_object_tree *tree, const char *interface_name) { struct l_dbus_interface *interface; struct interface_check state = { tree, interface_name }; interface = l_hashmap_lookup(tree->interfaces, interface_name); if (!interface) return false; /* Check that the interface is not in use */ l_hashmap_foreach(tree->objects, check_interface_used, &state); l_hashmap_remove(tree->interfaces, interface_name); _dbus_interface_free(interface); return true; } static void collect_instances(struct object_node *node, const char *path, struct l_queue *announce) { const struct l_queue_entry *entry; struct interface_add_record *change_rec; const struct child_node *child; if (!node->instances) goto recurse; change_rec = l_new(struct interface_add_record, 1); change_rec->path = l_strdup(path); change_rec->object = node; change_rec->instances = l_queue_new(); for (entry = l_queue_get_entries(node->instances); entry; entry = entry->next) l_queue_push_tail(change_rec->instances, entry->data); l_queue_push_tail(announce, change_rec); recurse: if (!strcmp(path, "/")) path = ""; for (child = node->children; child; child = child->next) { char *child_path; child_path = l_strdup_printf("%s/%s", path, child->subpath); collect_instances(child->node, child_path, announce); l_free(child_path); } } static bool match_interfaces_added_object(const void *a, const void *b) { const struct interface_add_record *rec = a; return rec->object == b; } static bool match_interfaces_removed_object(const void *a, const void *b) { const struct interface_remove_record *rec = a; return rec->object == b; } bool _dbus_object_tree_add_interface(struct _dbus_object_tree *tree, const char *path, const char *interface, void *user_data) { struct object_node *object; struct l_dbus_interface *dbi; struct interface_instance *instance; const struct l_queue_entry *entry; struct object_manager *manager; size_t path_len; struct interface_add_record *change_rec; dbi = l_hashmap_lookup(tree->interfaces, interface); if (!dbi) return false; object = l_hashmap_lookup(tree->objects, path); if (!object) { object = _dbus_object_tree_new_object(tree, path, NULL, NULL); if (!object) return false; } /* * Check to make sure we do not have this interface already * registered for this object */ if (l_queue_find(object->instances, match_interface_instance_ptr, dbi)) return false; instance = l_new(struct interface_instance, 1); instance->interface = dbi; instance->user_data = user_data; l_queue_push_tail(object->instances, instance); for (entry = l_queue_get_entries(tree->object_managers); entry; entry = entry->next) { manager = entry->data; path_len = strlen(manager->path); if (strncmp(path, manager->path, path_len) || (path[path_len] != '\0' && path[path_len] != '/' && path_len > 1)) continue; change_rec = l_queue_find(manager->announce_added, match_interfaces_added_object, object); if (!change_rec) { change_rec = l_new(struct interface_add_record, 1); change_rec->path = l_strdup(path); change_rec->object = object; change_rec->instances = l_queue_new(); l_queue_push_tail(manager->announce_added, change_rec); } /* No need to check for duplicates here */ l_queue_push_tail(change_rec->instances, instance); schedule_emit_signals(manager->dbus); } if (!strcmp(interface, L_DBUS_INTERFACE_OBJECT_MANAGER)) { manager = l_new(struct object_manager, 1); manager->path = l_strdup(path); manager->dbus = instance->user_data; manager->announce_added = l_queue_new(); manager->announce_removed = l_queue_new(); l_queue_push_tail(tree->object_managers, manager); /* Emit InterfacesAdded for interfaces added before OM */ collect_instances(object, path, manager->announce_added); if (manager->dbus && !l_queue_isempty(manager->announce_added)) schedule_emit_signals(manager->dbus); } return true; } static struct interface_instance *_dbus_object_tree_find_interface( struct _dbus_object_tree *tree, const char *path, const char *interface) { struct object_node *object; object = l_hashmap_lookup(tree->objects, path); if (!object) return NULL; return l_queue_find(object->instances, match_interface_instance, interface); } void *_dbus_object_tree_get_interface_data(struct _dbus_object_tree *tree, const char *path, const char *interface) { struct interface_instance *instance; instance = _dbus_object_tree_find_interface(tree, path, interface); if (!instance) return NULL; return instance->user_data; } bool _dbus_object_tree_set_interface_data(struct _dbus_object_tree *tree, const char *path, const char *interface, void *user_data) { struct interface_instance *instance; instance = _dbus_object_tree_find_interface(tree, path, interface); if (!instance) return false; instance->user_data = user_data; return true; } static bool match_object_manager_path(const void *a, const void *b) { const struct object_manager *manager = a; return !strcmp(manager->path, b); } bool _dbus_object_tree_remove_interface(struct _dbus_object_tree *tree, const char *path, const char *interface) { struct object_node *node; struct interface_instance *instance; const struct l_queue_entry *entry; struct object_manager *manager; size_t path_len; struct interface_add_record *interfaces_added_rec; struct interface_remove_record *interfaces_removed_rec; struct property_change_record *property_change_rec; node = l_hashmap_lookup(tree->objects, path); if (!node) return false; instance = l_queue_remove_if(node->instances, match_interface_instance, (char *) interface); if (!instance) return false; if (!strcmp(interface, L_DBUS_INTERFACE_OBJECT_MANAGER)) { manager = l_queue_remove_if(tree->object_managers, match_object_manager_path, (char *) path); if (manager) object_manager_free(manager); } for (entry = l_queue_get_entries(tree->object_managers); entry; entry = entry->next) { manager = entry->data; path_len = strlen(manager->path); if (strncmp(path, manager->path, path_len) || (path[path_len] != '\0' && path[path_len] != '/' && path_len > 1)) continue; interfaces_added_rec = l_queue_find(manager->announce_added, match_interfaces_added_object, node); if (interfaces_added_rec && l_queue_remove( interfaces_added_rec->instances, instance)) { if (l_queue_isempty(interfaces_added_rec->instances)) { l_queue_remove(manager->announce_added, interfaces_added_rec); interface_add_record_free(interfaces_added_rec); } continue; } interfaces_removed_rec = l_queue_find(manager->announce_removed, match_interfaces_removed_object, node); if (!interfaces_removed_rec) { interfaces_removed_rec = l_new(struct interface_remove_record, 1); interfaces_removed_rec->path = l_strdup(path); interfaces_removed_rec->object = node; interfaces_removed_rec->interface_names = l_queue_new(); l_queue_push_tail(manager->announce_removed, interfaces_removed_rec); } /* No need to check for duplicates here */ l_queue_push_tail(interfaces_removed_rec->interface_names, l_strdup(interface)); schedule_emit_signals(manager->dbus); } property_change_rec = l_queue_remove_if(tree->property_changes, match_property_changes_instance, instance); if (property_change_rec) property_change_record_free(property_change_rec); interface_instance_free(instance); return true; } static void generate_interface_instance(void *data, void *user) { struct interface_instance *instance = data; struct l_string *buf = user; _dbus_interface_introspection(instance->interface, buf); } void _dbus_object_tree_introspect(struct _dbus_object_tree *tree, const char *path, struct l_string *buf) { struct object_node *node; struct child_node *child; bool path_is_object = true; node = l_hashmap_lookup(tree->objects, path); if (!node) { path_is_object = false; node = _dbus_object_tree_lookup(tree, path); } l_string_append(buf, XML_HEAD); l_string_append(buf, "\n"); if (node) { /* * We emit org.freedesktop.DBus.Introspectable only in case the * object node corresponds to a registered object, i.e. * exposes anything other than: * - org.freedesktop.DBus.Introspectable * - org.freedesktop.DBus.Peer * - org.freedesktop.DBus.Properties */ if (path_is_object) l_string_append(buf, static_introspectable); l_queue_foreach(node->instances, generate_interface_instance, buf); for (child = node->children; child; child = child->next) l_string_append_printf(buf, "\t\n", child->subpath); } l_string_append(buf, "\n"); } bool _dbus_object_tree_dispatch(struct _dbus_object_tree *tree, struct l_dbus *dbus, struct l_dbus_message *message) { const char *path; const char *interface; const char *member; const char *msg_sig; const char *sig; struct object_node *node; struct interface_instance *instance; struct _dbus_method *method; struct l_dbus_message *reply; path = l_dbus_message_get_path(message); interface = l_dbus_message_get_interface(message); member = l_dbus_message_get_member(message); msg_sig = l_dbus_message_get_signature(message); /* * Nothing in the spec explicitly forbids this, but handling of such * messages is left up to the implementation. * * TODO: Another route is to go looking for a matching method under this * object and call it. */ if (!interface) return false; if (!msg_sig) msg_sig = ""; if (!strcmp(interface, "org.freedesktop.DBus.Introspectable") && !strcmp(member, "Introspect") && !strcmp(msg_sig, "")) { struct l_string *buf; char *xml; buf = l_string_new(0); _dbus_object_tree_introspect(tree, path, buf); xml = l_string_unwrap(buf); reply = l_dbus_message_new_method_return(message); l_dbus_message_set_arguments(reply, "s", xml); l_dbus_send(dbus, reply); l_free(xml); return true; } node = l_hashmap_lookup(tree->objects, path); if (!node) return false; instance = l_queue_find(node->instances, match_interface_instance, (char *) interface); if (!instance) return false; method = _dbus_interface_find_method(instance->interface, member); if (!method) return false; sig = method->metainfo + method->name_len + 1; if (strcmp(msg_sig, sig)) return false; reply = method->cb(dbus, message, instance->user_data); if (reply) l_dbus_send(dbus, reply); return true; } LIB_EXPORT bool l_dbus_property_changed(struct l_dbus *dbus, const char *path, const char *interface, const char *property) { return _dbus_object_tree_property_changed(dbus, path, interface, property); } static struct l_dbus_message *properties_get(struct l_dbus *dbus, struct l_dbus_message *message, void *user_data) { const struct interface_instance *instance; const char *interface_name, *property_name, *signature; const struct _dbus_property *property; struct _dbus_object_tree *tree = _dbus_get_tree(dbus); const struct object_node *object; struct l_dbus_message *reply; struct l_dbus_message_builder *builder; if (!l_dbus_message_get_arguments(message, "ss", &interface_name, &property_name)) return l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "InvalidArgs", "Invalid arguments"); object = l_hashmap_lookup(tree->objects, l_dbus_message_get_path(message)); /* If we got here the object must exist */ instance = l_queue_find(object->instances, match_interface_instance, (char *) interface_name); if (!instance) return l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "InvalidArgs", "Object has no interface %s", interface_name); property = _dbus_interface_find_property(instance->interface, property_name); if (!property) return l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "InvalidArgs", "Unknown Property %s", property_name); reply = l_dbus_message_new_method_return(message); builder = l_dbus_message_builder_new(reply); signature = property->metainfo + strlen(property->metainfo) + 1; l_dbus_message_builder_enter_variant(builder, signature); if (property->getter(dbus, message, builder, instance->user_data)) { l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_finalize(builder); } else { l_dbus_message_unref(reply); reply = l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "Failed", "Getting property value " "failed"); } l_dbus_message_builder_destroy(builder); return reply; } static struct l_dbus_message *properties_set(struct l_dbus *dbus, struct l_dbus_message *message, void *user_data) { struct l_dbus_interface *interface; const struct interface_instance *instance; const char *interface_name, *property_name; const struct _dbus_property *property; struct l_dbus_message_iter variant; struct _dbus_object_tree *tree = _dbus_get_tree(dbus); const struct object_node *object; struct l_dbus_message *reply; l_dbus_property_complete_cb_t complete_cb; if (!l_dbus_message_get_arguments(message, "ssv", &interface_name, &property_name, &variant)) return l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "InvalidArgs", "Invalid arguments"); interface = l_hashmap_lookup(tree->interfaces, interface_name); if (!interface) return l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "InvalidArgs", "Unknown Interface %s", interface_name); property = _dbus_interface_find_property(interface, property_name); if (!property) return l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "InvalidArgs", "Unknown Property %s", property_name); if (!property->setter) return l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "InvalidArgs", "Property %s is read-only", property_name); object = l_hashmap_lookup(tree->objects, l_dbus_message_get_path(message)); /* If we got here the object must exist */ instance = l_queue_find(object->instances, match_interface_instance_ptr, interface); if (!instance) return l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "InvalidArgs", "Object has no interface %s", interface_name); if (property->flags & L_DBUS_PROPERTY_FLAG_AUTO_EMIT) complete_cb = pending_property_set_done_emit; else complete_cb = pending_property_set_done; reply = property->setter(dbus, l_dbus_message_ref(message), &variant, complete_cb, instance->user_data); if (reply) complete_cb(dbus, message, reply); return NULL; } static struct l_dbus_message *properties_get_all(struct l_dbus *dbus, struct l_dbus_message *message, void *user_data) { const struct interface_instance *instance; const char *interface_name; struct _dbus_object_tree *tree = _dbus_get_tree(dbus); const struct object_node *object; struct l_dbus_message *reply; struct l_dbus_message_builder *builder; if (!l_dbus_message_get_arguments(message, "s", &interface_name)) return l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "InvalidArgs", "Invalid arguments"); object = l_hashmap_lookup(tree->objects, l_dbus_message_get_path(message)); /* If we got here the object must exist */ instance = l_queue_find(object->instances, match_interface_instance, (char *) interface_name); if (!instance) return l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "InvalidArgs", "Object has no interface %s", interface_name); reply = l_dbus_message_new_method_return(message); builder = l_dbus_message_builder_new(reply); if (!get_properties_dict(dbus, message, builder, instance->interface, instance->user_data)) { l_dbus_message_unref(reply); reply = l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "Failed", "Getting property values " "failed"); } else l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); return reply; } static void properties_setup_func(struct l_dbus_interface *interface) { l_dbus_interface_method(interface, "Get", 0, properties_get, "v", "ss", "value", "interface_name", "property_name"); l_dbus_interface_method(interface, "Set", 0, properties_set, "", "ssv", "interface_name", "property_name", "value"); l_dbus_interface_method(interface, "GetAll", 0, properties_get_all, "a{sv}", "s", "props", "interface_name"); l_dbus_interface_signal(interface, "PropertiesChanged", 0, "sa{sv}as", "interface_name", "changed_properties", "invalidated_properties"); } static bool collect_objects(struct l_dbus *dbus, struct l_dbus_message *message, struct l_dbus_message_builder *builder, const struct object_node *node, const char *path) { const struct l_queue_entry *entry; const struct child_node *child; char *child_path; const struct interface_instance *instance; bool r; if (!node->instances) goto recurse; l_dbus_message_builder_enter_dict(builder, "oa{sa{sv}}"); l_dbus_message_builder_append_basic(builder, 'o', path); l_dbus_message_builder_enter_array(builder, "{sa{sv}}"); for (entry = l_queue_get_entries(node->instances); entry; entry = entry->next) { instance = entry->data; l_dbus_message_builder_enter_dict(builder, "sa{sv}"); l_dbus_message_builder_append_basic(builder, 's', instance->interface->name); if (!get_properties_dict(dbus, message, builder, instance->interface, instance->user_data)) return false; l_dbus_message_builder_leave_dict(builder); } l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_leave_dict(builder); recurse: if (!strcmp(path, "/")) path = ""; for (child = node->children; child; child = child->next) { child_path = l_strdup_printf("%s/%s", path, child->subpath); r = collect_objects(dbus, message, builder, child->node, child_path); l_free(child_path); if (!r) return false; } return true; } struct l_dbus_message *_dbus_object_tree_get_objects( struct _dbus_object_tree *tree, struct l_dbus *dbus, const char *path, struct l_dbus_message *message) { const struct object_node *node; struct l_dbus_message *reply; struct l_dbus_message_builder *builder; node = l_hashmap_lookup(tree->objects, path); reply = l_dbus_message_new_method_return(message); builder = l_dbus_message_builder_new(reply); l_dbus_message_builder_enter_array(builder, "{oa{sa{sv}}}"); if (!collect_objects(dbus, message, builder, node, path)) { l_dbus_message_builder_destroy(builder); l_dbus_message_unref(reply); return l_dbus_message_new_error(message, "org.freedesktop.DBus.Error." "Failed", "Getting property values " "failed"); } l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); return reply; } static struct l_dbus_message *get_managed_objects(struct l_dbus *dbus, struct l_dbus_message *message, void *user_data) { struct _dbus_object_tree *tree = _dbus_get_tree(dbus); const char *path = l_dbus_message_get_path(message); return _dbus_object_tree_get_objects(tree, dbus, path, message); } static void object_manager_setup_func(struct l_dbus_interface *interface) { l_dbus_interface_method(interface, "GetManagedObjects", 0, get_managed_objects, "a{oa{sa{sv}}}", "", "objpath_interfaces_and_properties"); l_dbus_interface_signal(interface, "InterfacesAdded", 0, "oa{sa{sv}}", "object_path", "interfaces_and_properties"); l_dbus_interface_signal(interface, "InterfacesRemoved", 0, "oas", "object_path", "interfaces"); } bluez-5.87/ell/PaxHeaders/time-private.h0000644000000000000000000000005015142661620015211 xustar0020 atime=1783076231 20 ctime=1783094098 bluez-5.87/ell/time-private.h0000644000000000000000000000074715142661620014702 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2020 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ struct timeval; struct timespec; uint64_t _time_pick_interval_secs(uint32_t min_secs, uint32_t max_secs); uint64_t _time_fuzz_msecs(uint64_t ms); uint64_t _time_fuzz_secs(uint32_t secs, uint32_t max_offset); uint64_t _time_realtime_to_boottime(const struct timeval *ts); uint64_t time_realtime_now(void); uint64_t _time_from_timespec(const struct timespec *ts); bluez-5.87/ell/PaxHeaders/key.c0000644000000000000000000000005014770744372013402 xustar0020 atime=1783076284 20 ctime=1783094099 bluez-5.87/ell/key.c0000644000000000000000000004263014770744372013070 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2016 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include "private.h" #include "useful.h" #include "key.h" #include "string.h" #include "random.h" #include "missing.h" #ifndef KEYCTL_DH_COMPUTE #define KEYCTL_DH_COMPUTE 23 #endif #ifndef KEYCTL_PKEY_QUERY #define KEYCTL_PKEY_QUERY 24 #define KEYCTL_PKEY_ENCRYPT 25 #define KEYCTL_PKEY_DECRYPT 26 #define KEYCTL_PKEY_SIGN 27 #define KEYCTL_PKEY_VERIFY 28 #define KEYCTL_SUPPORTS_ENCRYPT 0x01 #define KEYCTL_SUPPORTS_DECRYPT 0x02 #define KEYCTL_SUPPORTS_SIGN 0x04 #define KEYCTL_SUPPORTS_VERIFY 0x08 struct keyctl_pkey_query { uint32_t supported_ops; uint32_t key_size; uint16_t max_data_size; uint16_t max_sig_size; uint16_t max_enc_size; uint16_t max_dec_size; uint32_t __spare[10]; }; struct keyctl_pkey_params { int32_t key_id; uint32_t in_len; union { uint32_t out_len; uint32_t in2_len; }; uint32_t __spare[7]; }; /* Work around the missing (pre-4.7) or broken (4.14.{70,71,72} and * 4.18.{8,9,10}) kernel declaration of struct keyctl_dh_params */ struct dh_params { int32_t private; int32_t prime; int32_t base; }; #else /* When KEYCTL_PKEY_QUERY is defined by the kernel, the * struct keyctl_dh_params declaration is valid. */ #define dh_params keyctl_dh_params #endif #ifndef KEYCTL_RESTRICT_KEYRING #define KEYCTL_RESTRICT_KEYRING 29 #endif static int32_t internal_keyring; struct l_key { int type; int32_t serial; }; struct l_keyring { int32_t serial; }; static const char * const key_type_names[] = { [L_KEY_RAW] = "user", [L_KEY_RSA] = "asymmetric", [L_KEY_ECC] = "asymmetric", }; static long kernel_add_key(const char *type, const char *description, const void *payload, size_t len, int32_t keyring) { long result; result = syscall(__NR_add_key, type, description, payload, len, keyring); return result >= 0 ? result : -errno; } static long kernel_read_key(int32_t serial, const void *payload, size_t len) { long result; result = syscall(__NR_keyctl, KEYCTL_READ, serial, payload, len); return result >= 0 ? result : -errno; } static long kernel_update_key(int32_t serial, const void *payload, size_t len) { long result; result = syscall(__NR_keyctl, KEYCTL_UPDATE, serial, payload, len); return result >= 0 ? result : -errno; } static long kernel_invalidate_key(int32_t serial) { long result; result = syscall(__NR_keyctl, KEYCTL_INVALIDATE, serial); return result >= 0 ? result : -errno; } static long kernel_link_key(int32_t key_serial, int32_t ring_serial) { long result; result = syscall(__NR_keyctl, KEYCTL_LINK, key_serial, ring_serial); return result >= 0 ? result : -errno; } static long kernel_unlink_key(int32_t key_serial, int32_t ring_serial) { long result; result = syscall(__NR_keyctl, KEYCTL_UNLINK, key_serial, ring_serial); return result >= 0 ? result : -errno; } static char *format_key_info(const char *encoding, const char *hash) { struct l_string *info; if (!encoding && !hash) return NULL; info = l_string_new(0); if (encoding) l_string_append_printf(info, "enc=%s ", encoding); if (hash) l_string_append_printf(info, "hash=%s", hash); return l_string_unwrap(info); } static long kernel_query_key(int32_t key_serial, const char *encoding, const char *hash, size_t *size, bool *public) { long result; struct keyctl_pkey_query query; char *info = format_key_info(encoding, hash); memset(&query, 0, sizeof(query)); result = syscall(__NR_keyctl, KEYCTL_PKEY_QUERY, key_serial, 0, info ?: "", &query); if (result == 0) { *size = query.key_size; *public = ((query.supported_ops & KEYCTL_SUPPORTS_ENCRYPT) && !(query.supported_ops & KEYCTL_SUPPORTS_DECRYPT)); } l_free(info); return result >= 0 ? result : -errno; } static long kernel_dh_compute(int32_t private, int32_t prime, int32_t base, void *payload, size_t len) { long result; struct dh_params params = { .private = private, .prime = prime, .base = base }; result = syscall(__NR_keyctl, KEYCTL_DH_COMPUTE, ¶ms, payload, len, NULL); return result >= 0 ? result : -errno; } static long kernel_restrict_keyring(int32_t serial, const char *keytype, const char *restriction) { long result; result = syscall(__NR_keyctl, KEYCTL_RESTRICT_KEYRING, serial, keytype, restriction); return result >= 0 ? result : -errno; } static long kernel_key_eds(int op, int32_t serial, const char *encoding, const char *hash, const void *in, void *out, size_t len_in, size_t len_out) { long result; struct keyctl_pkey_params params = { .key_id = serial, .in_len = len_in, .out_len = len_out }; char *info = format_key_info(encoding, hash); memset(out, 0, len_out); result = syscall(__NR_keyctl, op, ¶ms, info ?: "", in, out); l_free(info); return result >= 0 ? result : -errno; } static long kernel_key_verify(int32_t serial, const char *encoding, const char *hash, const void *data, size_t data_len, const void *sig, size_t sig_len) { struct keyctl_pkey_params params = { .key_id = serial, .in_len = data_len, .in2_len = sig_len, }; char *info = format_key_info(encoding, hash); long result; result = syscall(__NR_keyctl, KEYCTL_PKEY_VERIFY, ¶ms, info ?: "", data, sig); l_free(info); return result >= 0 ? result : -errno; } static bool setup_internal_keyring(void) { internal_keyring = kernel_add_key("keyring", "ell-internal", NULL, 0, KEY_SPEC_THREAD_KEYRING); if (internal_keyring <= 0) { internal_keyring = 0; return false; } return true; } LIB_EXPORT struct l_key *l_key_new(enum l_key_type type, const void *payload, size_t payload_length) { struct l_key *key; char *description; static unsigned long key_idx; if (unlikely(!payload)) return NULL; if (unlikely((size_t)type >= L_ARRAY_SIZE(key_type_names))) return NULL; if (!internal_keyring && !setup_internal_keyring()) return NULL; key = l_new(struct l_key, 1); key->type = type; description = l_strdup_printf("ell-key-%lu", key_idx++); key->serial = kernel_add_key(key_type_names[type], description, payload, payload_length, internal_keyring); l_free(description); if (key->serial < 0) { l_free(key); key = NULL; } /* * TODO: Query asymmetric key algorithm from the kernel and * ensure that it matches the expected l_key_type. This can * currently be found by digging through /proc/keys, but a * keyctl() op makes more sense. */ return key; } LIB_EXPORT void l_key_free(struct l_key *key) { if (unlikely(!key)) return; /* * Use invalidate as, unlike revoke, this doesn't delay the * key garbage collection and causes the quota used by the * key to be released sooner and more predictably. */ kernel_invalidate_key(key->serial); l_free(key); } LIB_EXPORT void l_key_free_norevoke(struct l_key *key) { if (unlikely(!key)) return; kernel_unlink_key(key->serial, internal_keyring); l_free(key); } LIB_EXPORT bool l_key_update(struct l_key *key, const void *payload, size_t len) { long error; if (unlikely(!key)) return false; error = kernel_update_key(key->serial, payload, len); return error == 0; } LIB_EXPORT bool l_key_extract(struct l_key *key, void *payload, size_t *len) { long keylen; if (unlikely(!key)) return false; keylen = kernel_read_key(key->serial, payload, *len); if (keylen < 0 || (size_t)keylen > *len) { explicit_bzero(payload, *len); return false; } *len = keylen; return true; } LIB_EXPORT ssize_t l_key_get_payload_size(struct l_key *key) { return kernel_read_key(key->serial, NULL, 0); } static const char *lookup_cipher(enum l_key_cipher_type cipher) { switch (cipher) { case L_KEY_RSA_PKCS1_V1_5: return "pkcs1"; case L_KEY_RSA_RAW: return "raw"; case L_KEY_ECDSA_X962: return "x962"; } return NULL; } static const char *lookup_checksum(enum l_checksum_type checksum) { const char* ret = NULL; switch (checksum) { case L_CHECKSUM_NONE: break; case L_CHECKSUM_MD4: ret = "md4"; break; case L_CHECKSUM_MD5: ret = "md5"; break; case L_CHECKSUM_SHA1: ret = "sha1"; break; case L_CHECKSUM_SHA224: ret = "sha224"; break; case L_CHECKSUM_SHA256: ret = "sha256"; break; case L_CHECKSUM_SHA384: ret = "sha384"; break; case L_CHECKSUM_SHA512: ret = "sha512"; break; case L_CHECKSUM_SHA3_224: ret = "sha3-224"; break; case L_CHECKSUM_SHA3_256: ret = "sha3-256"; break; case L_CHECKSUM_SHA3_384: ret = "sha3-384"; break; case L_CHECKSUM_SHA3_512: ret = "sha3-512"; break; } return ret; } LIB_EXPORT bool l_key_get_info(struct l_key *key, enum l_key_cipher_type cipher, enum l_checksum_type checksum, size_t *bits, bool *public) { if (unlikely(!key)) return false; return !kernel_query_key(key->serial, lookup_cipher(cipher), lookup_checksum(checksum), bits, public); } LIB_EXPORT struct l_key *l_key_generate_dh_private(const void *prime_buf, size_t prime_len) { uint8_t *buf; const uint8_t *prime = prime_buf; size_t prime_bits; unsigned int i; size_t private_bytes; size_t random_bytes; struct l_key *private; /* Find the prime's bit length excluding leading 0s */ for (i = 0; i < prime_len && !prime[i]; i++); if (i == prime_len || (i == prime_len - 1 && prime[i] < 5)) return NULL; prime_bits = (prime_len - i) * 8 - __builtin_clz(prime[i]); /* * Generate a random DH private value conforming to 1 < x < p - 1. * To do this covering all possible values in this range with the * same probability of generating each value generally requires * looping. Instead we generate a value in the range * [2 ^ (prime_bits - 2), 2 ^ (prime_bits - 1) - 1] by forcing bit * prime_bits - 2 to 1, i.e. the range in PKCS #3 Section 7.1 for * l equal to prime_bits - 1. This means we're using between * one half and one quarter of the full [2, p - 2] range, i.e. * between 1 and 2 bits fewer. Note that since p is odd * p - 1 has the same bit length as p and so our maximum value * 2 ^ (prime_bits - 1) - 1 is still less than p - 1. */ private_bytes = ((prime_bits - 1) + 7) / 8; random_bytes = ((prime_bits - 2) + 7) / 8; buf = l_malloc(private_bytes); l_getrandom(buf + private_bytes - random_bytes, random_bytes); buf[0] &= (1 << ((prime_bits - 2) % 8)) - 1; buf[0] |= 1 << ((prime_bits - 2) % 8); private = l_key_new(L_KEY_RAW, buf, private_bytes); explicit_bzero(buf, private_bytes); l_free(buf); return private; } static bool compute_common(struct l_key *base, struct l_key *private, struct l_key *prime, void *payload, size_t *len) { long result_len; bool usable_payload = *len != 0; result_len = kernel_dh_compute(private->serial, prime->serial, base->serial, payload, *len); if (result_len > 0) { *len = result_len; return usable_payload; } return false; } LIB_EXPORT bool l_key_compute_dh_public(struct l_key *generator, struct l_key *private, struct l_key *prime, void *payload, size_t *len) { return compute_common(generator, private, prime, payload, len); } LIB_EXPORT bool l_key_compute_dh_secret(struct l_key *other_public, struct l_key *private, struct l_key *prime, void *payload, size_t *len) { return compute_common(other_public, private, prime, payload, len); } static int be_bignum_compare(const uint8_t *a, size_t a_len, const uint8_t *b, size_t b_len) { unsigned int i; if (a_len >= b_len) { for (i = 0; i < a_len - b_len; i++) if (a[i]) return 1; return memcmp(a + i, b, b_len); } for (i = 0; i < b_len - a_len; i++) if (b[i]) return -1; return memcmp(a, b + i, a_len); } /* * Validate that @payload is within range for a private and public key for * a DH computation in the finite field group defined by modulus @prime_buf, * both numbers stored as big-endian integers. We require a key in the * [2, prime - 2] (inclusive) interval. PKCS #3 does not exclude 1 as a * private key but other specs do. */ LIB_EXPORT bool l_key_validate_dh_payload(const void *payload, size_t len, const void *prime_buf, size_t prime_len) { static const uint8_t one[] = { 1 }; uint8_t prime_1[prime_len]; /* * Produce prime - 1 for the payload < prime - 1 check. * prime is odd so just zero the LSB. */ memcpy(prime_1, prime_buf, prime_len); if (prime_len < 1 || !(prime_1[prime_len - 1] & 1)) return false; prime_1[prime_len - 1] &= ~1; if (be_bignum_compare(payload, len, one, 1) <= 0) return false; if (be_bignum_compare(payload, len, prime_1, prime_len) >= 0) return false; return true; } /* Common code for encrypt/decrypt/sign */ static ssize_t eds_common(struct l_key *key, enum l_key_cipher_type cipher, enum l_checksum_type checksum, const void *in, void *out, size_t len_in, size_t len_out, int op) { if (unlikely(!key)) return -EINVAL; return kernel_key_eds(op, key->serial, lookup_cipher(cipher), lookup_checksum(checksum), in, out, len_in, len_out); } LIB_EXPORT ssize_t l_key_encrypt(struct l_key *key, enum l_key_cipher_type cipher, enum l_checksum_type checksum, const void *in, void *out, size_t len_in, size_t len_out) { ssize_t ret_len; ret_len = eds_common(key, cipher, checksum, in, out, len_in, len_out, KEYCTL_PKEY_ENCRYPT); return ret_len; } LIB_EXPORT ssize_t l_key_decrypt(struct l_key *key, enum l_key_cipher_type cipher, enum l_checksum_type checksum, const void *in, void *out, size_t len_in, size_t len_out) { ssize_t ret_len; ret_len = eds_common(key, cipher, checksum, in, out, len_in, len_out, KEYCTL_PKEY_DECRYPT); if (ret_len < 0) goto done; done: return ret_len; } LIB_EXPORT ssize_t l_key_sign(struct l_key *key, enum l_key_cipher_type cipher, enum l_checksum_type checksum, const void *in, void *out, size_t len_in, size_t len_out) { ssize_t ret_len; ret_len = eds_common(key, cipher, checksum, in, out, len_in, len_out, KEYCTL_PKEY_SIGN); return ret_len; } LIB_EXPORT bool l_key_verify(struct l_key *key, enum l_key_cipher_type cipher, enum l_checksum_type checksum, const void *data, const void *sig, size_t len_data, size_t len_sig) { long result; if (unlikely(!key)) return false; result = kernel_key_verify(key->serial, lookup_cipher(cipher), lookup_checksum(checksum), data, len_data, sig, len_sig); return result >= 0; } LIB_EXPORT struct l_keyring *l_keyring_new(void) { struct l_keyring *keyring; char *description; static unsigned long keyring_idx; if (!internal_keyring && !setup_internal_keyring()) return NULL; keyring = l_new(struct l_keyring, 1); description = l_strdup_printf("ell-keyring-%lu", keyring_idx++); keyring->serial = kernel_add_key("keyring", description, NULL, 0, internal_keyring); l_free(description); if (keyring->serial < 0) { l_free(keyring); return NULL; } return keyring; } LIB_EXPORT bool l_keyring_restrict(struct l_keyring *keyring, enum l_keyring_restriction res, const struct l_keyring *trusted) { char *restriction = NULL; long result; switch (res) { case L_KEYRING_RESTRICT_ASYM: case L_KEYRING_RESTRICT_ASYM_CHAIN: { char *option = ""; if (res == L_KEYRING_RESTRICT_ASYM_CHAIN) option = ":chain"; restriction = l_strdup_printf("key_or_keyring:%d%s", trusted ? trusted->serial : 0, option); break; } default: /* Unsupported type */ return NULL; } result = kernel_restrict_keyring(keyring->serial, "asymmetric", restriction); l_free(restriction); return result == 0; } LIB_EXPORT void l_keyring_free(struct l_keyring *keyring) { if (unlikely(!keyring)) return; kernel_invalidate_key(keyring->serial); l_free(keyring); } LIB_EXPORT void l_keyring_free_norevoke(struct l_keyring *keyring) { if (unlikely(!keyring)) return; kernel_unlink_key(keyring->serial, internal_keyring); l_free(keyring); } LIB_EXPORT bool l_keyring_link(struct l_keyring *keyring, const struct l_key *key) { long error; if (unlikely(!keyring) || unlikely(!key)) return false; error = kernel_link_key(key->serial, keyring->serial); return error == 0; } LIB_EXPORT bool l_keyring_unlink(struct l_keyring *keyring, const struct l_key *key) { long error; if (unlikely(!keyring) || unlikely(!key)) return false; error = kernel_unlink_key(key->serial, keyring->serial); return error == 0; } LIB_EXPORT bool l_keyring_link_nested(struct l_keyring *keyring, const struct l_keyring *nested) { long error; if (unlikely(!keyring) || unlikely(!nested)) return false; error = kernel_link_key(nested->serial, keyring->serial); return error == 0; } LIB_EXPORT bool l_keyring_unlink_nested(struct l_keyring *keyring, const struct l_keyring *nested) { long error; if (unlikely(!keyring) || unlikely(!nested)) return false; error = kernel_unlink_key(nested->serial, keyring->serial); return error == 0; } LIB_EXPORT bool l_key_is_supported(uint32_t features) { long result; if (features & L_KEY_FEATURE_DH) { result = syscall(__NR_keyctl, KEYCTL_DH_COMPUTE, NULL, "x", 1, NULL); if (result == -1 && errno == EOPNOTSUPP) return false; } if (features & L_KEY_FEATURE_RESTRICT) { result = syscall(__NR_keyctl, KEYCTL_RESTRICT_KEYRING, 0, "asymmetric", ""); if (result == -1 && errno == EOPNOTSUPP) return false; } if (features & L_KEY_FEATURE_CRYPTO) { result = syscall(__NR_keyctl, KEYCTL_PKEY_QUERY, 0, 0, "", 0); if (result == -1 && errno == EOPNOTSUPP) return false; } return true; } bluez-5.87/ell/PaxHeaders/key.h0000644000000000000000000000005014504767710013403 xustar0020 atime=1783076232 20 ctime=1783094098 bluez-5.87/ell/key.h0000644000000000000000000000611414504767710013066 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2016 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_KEY_H #define __ELL_KEY_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif struct l_key; struct l_keyring; enum l_key_feature { L_KEY_FEATURE_DH = 1 << 0, L_KEY_FEATURE_RESTRICT = 1 << 1, L_KEY_FEATURE_CRYPTO = 1 << 2, }; enum l_key_type { L_KEY_RAW = 0, L_KEY_RSA, L_KEY_ECC, }; enum l_keyring_restriction { L_KEYRING_RESTRICT_ASYM = 0, L_KEYRING_RESTRICT_ASYM_CHAIN, }; enum l_key_cipher_type { L_KEY_RSA_PKCS1_V1_5, L_KEY_RSA_RAW, L_KEY_ECDSA_X962, }; struct l_key *l_key_new(enum l_key_type type, const void *payload, size_t payload_length); void l_key_free(struct l_key *key); void l_key_free_norevoke(struct l_key *key); bool l_key_update(struct l_key *key, const void *payload, size_t len); bool l_key_extract(struct l_key *key, void *payload, size_t *len); ssize_t l_key_get_payload_size(struct l_key *key); bool l_key_get_info(struct l_key *key, enum l_key_cipher_type cipher, enum l_checksum_type checksum, size_t *bits, bool *out_public); struct l_key *l_key_generate_dh_private(const void *prime_buf, size_t prime_len); bool l_key_compute_dh_public(struct l_key *generator, struct l_key *private_key, struct l_key *prime, void *payload, size_t *len); bool l_key_compute_dh_secret(struct l_key *other_public, struct l_key *private_key, struct l_key *prime, void *payload, size_t *len); bool l_key_validate_dh_payload(const void *payload, size_t len, const void *prime_buf, size_t prime_len); ssize_t l_key_encrypt(struct l_key *key, enum l_key_cipher_type cipher, enum l_checksum_type checksum, const void *in, void *out, size_t len_in, size_t len_out); ssize_t l_key_decrypt(struct l_key *key, enum l_key_cipher_type cipher, enum l_checksum_type checksum, const void *in, void *out, size_t len_in, size_t len_out); ssize_t l_key_sign(struct l_key *key, enum l_key_cipher_type cipher, enum l_checksum_type checksum, const void *in, void *out, size_t len_in, size_t len_out); bool l_key_verify(struct l_key *key, enum l_key_cipher_type cipher, enum l_checksum_type checksum, const void *data, const void *sig, size_t len_data, size_t len_sig); struct l_keyring *l_keyring_new(void); bool l_keyring_restrict(struct l_keyring *keyring, enum l_keyring_restriction res, const struct l_keyring *trust); void l_keyring_free(struct l_keyring *keyring); DEFINE_CLEANUP_FUNC(l_keyring_free); void l_keyring_free_norevoke(struct l_keyring *keyring); DEFINE_CLEANUP_FUNC(l_keyring_free_norevoke); bool l_keyring_link(struct l_keyring *keyring, const struct l_key *key); bool l_keyring_unlink(struct l_keyring *keyring, const struct l_key *key); bool l_keyring_link_nested(struct l_keyring *keyring, const struct l_keyring *nested); bool l_keyring_unlink_nested(struct l_keyring *keyring, const struct l_keyring *nested); bool l_key_is_supported(uint32_t features); #ifdef __cplusplus } #endif #endif /* __ELL_KEY_H */ bluez-5.87/ell/PaxHeaders/ecc-external.c0000644000000000000000000000005014770744372015164 xustar0020 atime=1783076332 20 ctime=1783094099 bluez-5.87/ell/ecc-external.c0000644000000000000000000007125114770744372014653 0ustar00rootroot/* * Copyright (c) 2013, Kenneth MacKay * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "private.h" #include "ecc.h" #include "ecc-private.h" #include "random.h" typedef struct { uint64_t m_low; uint64_t m_high; } uint128_t; static void vli_clear(uint64_t *vli, unsigned int ndigits) { unsigned int i; for (i = 0; i < ndigits; i++) vli[i] = 0; } /* Returns true if vli == 0, false otherwise. */ static bool vli_is_zero(const uint64_t *vli, unsigned int ndigits) { unsigned int i; for (i = 0; i < ndigits; i++) { if (vli[i]) return false; } return true; } /* Returns nonzero if bit bit of vli is set. */ static uint64_t vli_test_bit(const uint64_t *vli, unsigned int bit) { return (vli[bit / 64] & ((uint64_t) 1 << (bit % 64))); } /* Sets dest = src. */ static void vli_set(uint64_t *dest, const uint64_t *src, unsigned int ndigits) { unsigned int i; for (i = 0; i < ndigits; i++) dest[i] = src[i]; } /* Returns sign of left - right. */ int _vli_cmp(const uint64_t *left, const uint64_t *right, unsigned int ndigits) { int i; for (i = ndigits - 1; i >= 0; i--) { if (left[i] > right[i]) return 1; if (left[i] < right[i]) return -1; } return 0; } /* Computes result = in << c, returning carry. Can modify in place * (if result == in). 0 < shift < 64. */ static uint64_t vli_lshift(uint64_t *result, const uint64_t *in, unsigned int shift, unsigned int ndigits) { uint64_t carry = 0; unsigned int i; for (i = 0; i < ndigits; i++) { uint64_t temp = in[i]; result[i] = (temp << shift) | carry; carry = temp >> (64 - shift); } return carry; } /* Computes vli = vli >> 1. */ void _vli_rshift1(uint64_t *vli, unsigned int ndigits) { uint64_t *end = vli; uint64_t carry = 0; vli += ndigits; while (vli-- > end) { uint64_t temp = *vli; *vli = (temp >> 1) | carry; carry = temp << 63; } } /* Computes result = left + right, returning carry. Can modify in place. */ uint64_t _vli_add(uint64_t *result, const uint64_t *left, const uint64_t *right, unsigned int ndigits) { uint64_t carry = 0; unsigned int i; for (i = 0; i < ndigits; i++) { uint64_t sum; sum = left[i] + right[i] + carry; if (sum != left[i]) carry = (sum < left[i]); result[i] = sum; } return carry; } /* Computes result = left - right, returning borrow. Can modify in place. */ uint64_t _vli_sub(uint64_t *result, const uint64_t *left, const uint64_t *right, unsigned int ndigits) { uint64_t borrow = 0; unsigned int i; for (i = 0; i < ndigits; i++) { uint64_t diff; diff = left[i] - right[i] - borrow; if (diff != left[i]) borrow = (diff > left[i]); result[i] = diff; } return borrow; } static uint128_t mul_64_64(uint64_t left, uint64_t right) { uint64_t a0 = left & 0xffffffffull; uint64_t a1 = left >> 32; uint64_t b0 = right & 0xffffffffull; uint64_t b1 = right >> 32; uint64_t m0 = a0 * b0; uint64_t m1 = a0 * b1; uint64_t m2 = a1 * b0; uint64_t m3 = a1 * b1; uint128_t result; m2 += (m0 >> 32); m2 += m1; /* Overflow */ if (m2 < m1) m3 += 0x100000000ull; result.m_low = (m0 & 0xffffffffull) | (m2 << 32); result.m_high = m3 + (m2 >> 32); return result; } static uint128_t add_128_128(uint128_t a, uint128_t b) { uint128_t result; result.m_low = a.m_low + b.m_low; result.m_high = a.m_high + b.m_high + (result.m_low < a.m_low); return result; } static void vli_mult(uint64_t *result, const uint64_t *left, const uint64_t *right, unsigned int ndigits) { uint128_t r01 = { 0, 0 }; uint64_t r2 = 0; unsigned int i, k; /* Compute each digit of result in sequence, maintaining the * carries. */ for (k = 0; k < ndigits * 2 - 1; k++) { unsigned int min; if (k < ndigits) min = 0; else min = (k + 1) - ndigits; for (i = min; i <= k && i < ndigits; i++) { uint128_t product; product = mul_64_64(left[i], right[k - i]); r01 = add_128_128(r01, product); r2 += (r01.m_high < product.m_high); } result[k] = r01.m_low; r01.m_low = r01.m_high; r01.m_high = r2; r2 = 0; } result[ndigits * 2 - 1] = r01.m_low; } static void vli_square(uint64_t *result, const uint64_t *left, unsigned int ndigits) { uint128_t r01 = { 0, 0 }; uint64_t r2 = 0; unsigned int i, k; for (k = 0; k < ndigits * 2 - 1; k++) { unsigned int min; if (k < ndigits) min = 0; else min = (k + 1) - ndigits; for (i = min; i <= k && i <= k - i; i++) { uint128_t product; product = mul_64_64(left[i], left[k - i]); if (i < k - i) { r2 += product.m_high >> 63; product.m_high = (product.m_high << 1) | (product.m_low >> 63); product.m_low <<= 1; } r01 = add_128_128(r01, product); r2 += (r01.m_high < product.m_high); } result[k] = r01.m_low; r01.m_low = r01.m_high; r01.m_high = r2; r2 = 0; } result[ndigits * 2 - 1] = r01.m_low; } /* Computes result = (left + right) % mod. * Assumes that left < mod and right < mod, result != mod. */ void _vli_mod_add(uint64_t *result, const uint64_t *left, const uint64_t *right, const uint64_t *mod, unsigned int ndigits) { uint64_t carry; carry = _vli_add(result, left, right, ndigits); /* result > mod (result = mod + remainder), so subtract mod to * get remainder. */ if (carry || _vli_cmp(result, mod, ndigits) >= 0) _vli_sub(result, result, mod, ndigits); } /* Computes result = (left - right) % mod. * Assumes that left < mod and right < mod, result != mod. */ void _vli_mod_sub(uint64_t *result, const uint64_t *left, const uint64_t *right, const uint64_t *mod, unsigned int ndigits) { uint64_t borrow = _vli_sub(result, left, right, ndigits); /* In this case, p_result == -diff == (max int) - diff. * Since -x % d == d - x, we can get the correct result from * result + mod (with overflow). */ if (borrow) _vli_add(result, result, mod, ndigits); } /* Counts the number of 64-bit "digits" in vli. */ static unsigned int _vli_num_digits(const uint64_t *vli, unsigned int ndigits) { int i; /* Search from the end until we find a non-zero digit. * We do it in reverse because we expect that most digits will * be nonzero. */ for (i = ndigits - 1; i >= 0 && vli[i] == 0; i--); return (i + 1); } /* Counts the number of bits required for vli. */ static unsigned int _vli_num_bits(const uint64_t *vli, unsigned int ndigits) { unsigned int i, num_digits; uint64_t digit; num_digits = _vli_num_digits(vli, ndigits); if (num_digits == 0) return 0; digit = vli[num_digits - 1]; for (i = 0; digit; i++) digit >>= 1; return ((num_digits - 1) * 64 + i); } /* Computes result = product % mod, where product is 2N words long. * Currently only designed to work for curve_p or curve_n. */ void _vli_mmod_slow(uint64_t *result, const uint64_t *product, const uint64_t *mod, unsigned int ndigits) { uint64_t mod_m[2 * L_ECC_MAX_DIGITS]; uint64_t tmp[2 * L_ECC_MAX_DIGITS]; uint64_t *v[2] = { tmp, (uint64_t *) product }; uint64_t carry = 0; unsigned int i; /* Shift mod so its highest set bit is at the maximum position. */ int shift = (ndigits * 2 * 64) - _vli_num_bits(mod, ndigits); int word_shift = shift / 64; int bit_shift = shift % 64; vli_clear(mod_m, word_shift); if (bit_shift > 0) { for (i = 0; i < ndigits; ++i) { mod_m[word_shift + i] = (mod[i] << bit_shift) | carry; carry = mod[i] >> (64 - bit_shift); } } else vli_set(mod_m + word_shift, mod, ndigits); for (i = 1; shift >= 0; --shift) { uint64_t borrow = 0; unsigned int j; for (j = 0; j < ndigits * 2; ++j) { uint64_t diff = v[i][j] - mod_m[j] - borrow; if (diff != v[i][j]) borrow = (diff > v[i][j]); v[1 - i][j] = diff; } i = !(i ^ borrow); /* Swap the index if there was no borrow */ _vli_rshift1(mod_m, ndigits); mod_m[ndigits - 1] |= mod_m[ndigits] << (64 - 1); _vli_rshift1(mod_m + ndigits, ndigits); } vli_set(result, v[i], ndigits); } /* Computes p_result = p_product % curve_p. * See algorithm 5 and 6 from * http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf */ static void vli_mmod_fast_192(uint64_t *result, const uint64_t *product, const uint64_t *curve_prime, uint64_t *tmp) { const unsigned int ndigits = 3; int carry; vli_set(result, product, ndigits); vli_set(tmp, &product[3], ndigits); carry = _vli_add(result, result, tmp, ndigits); tmp[0] = 0; tmp[1] = product[3]; tmp[2] = product[4]; carry += _vli_add(result, result, tmp, ndigits); tmp[0] = tmp[1] = product[5]; tmp[2] = 0; carry += _vli_add(result, result, tmp, ndigits); while (carry || _vli_cmp(curve_prime, result, ndigits) != 1) carry -= _vli_sub(result, result, curve_prime, ndigits); } /* Computes result = product % curve_prime * from http://www.nsa.gov/ia/_files/nist-routines.pdf */ static void vli_mmod_fast_224(uint64_t *result, const uint64_t *product, const uint64_t *curve_prime, uint64_t *tmp) { int carry = 0; const unsigned int ndigits = 4; /* t */ vli_set(result, product, ndigits); result[3] &= 0xffffffff; /* s1 */ tmp[0] = 0; tmp[1] = product[3] & 0xffffffff00000000ull; tmp[2] = product[4]; tmp[3] = product[5] & 0xffffffff; _vli_add(result, result, tmp, ndigits); /* s2 */ tmp[1] = product[5] & 0xffffffff00000000ull; tmp[2] = product[6]; tmp[3] = 0; _vli_add(result, result, tmp, ndigits); /* d1 */ tmp[0] = (product[3] >> 32) | (product[4] << 32); tmp[1] = (product[4] >> 32) | (product[5] << 32); tmp[2] = (product[5] >> 32) | (product[6] << 32); tmp[3] = product[6] >> 32; carry -= _vli_sub(result, result, tmp, ndigits); /* d2 */ tmp[0] = (product[5] >> 32) | (product[6] << 32); tmp[1] = product[6] >> 32; tmp[2] = tmp[3] = 0; carry -= _vli_sub(result, result, tmp, ndigits); if (carry < 0) { do { carry += _vli_add(result, result, curve_prime, ndigits); } while (carry < 0); } else { while (carry || _vli_cmp(curve_prime, result, ndigits) != 1) carry -= _vli_sub(result, result, curve_prime, ndigits); } } /* Computes result = product % curve_prime * from http://www.nsa.gov/ia/_files/nist-routines.pdf */ static void vli_mmod_fast_256(uint64_t *result, const uint64_t *product, const uint64_t *curve_prime, uint64_t *tmp) { int carry; const unsigned int ndigits = 4; /* t */ vli_set(result, product, ndigits); /* s1 */ tmp[0] = 0; tmp[1] = product[5] & 0xffffffff00000000ull; tmp[2] = product[6]; tmp[3] = product[7]; carry = vli_lshift(tmp, tmp, 1, ndigits); carry += _vli_add(result, result, tmp, ndigits); /* s2 */ tmp[1] = product[6] << 32; tmp[2] = (product[6] >> 32) | (product[7] << 32); tmp[3] = product[7] >> 32; carry += vli_lshift(tmp, tmp, 1, ndigits); carry += _vli_add(result, result, tmp, ndigits); /* s3 */ tmp[0] = product[4]; tmp[1] = product[5] & 0xffffffff; tmp[2] = 0; tmp[3] = product[7]; carry += _vli_add(result, result, tmp, ndigits); /* s4 */ tmp[0] = (product[4] >> 32) | (product[5] << 32); tmp[1] = (product[5] >> 32) | (product[6] & 0xffffffff00000000ull); tmp[2] = product[7]; tmp[3] = (product[6] >> 32) | (product[4] << 32); carry += _vli_add(result, result, tmp, ndigits); /* d1 */ tmp[0] = (product[5] >> 32) | (product[6] << 32); tmp[1] = (product[6] >> 32); tmp[2] = 0; tmp[3] = (product[4] & 0xffffffff) | (product[5] << 32); carry -= _vli_sub(result, result, tmp, ndigits); /* d2 */ tmp[0] = product[6]; tmp[1] = product[7]; tmp[2] = 0; tmp[3] = (product[4] >> 32) | (product[5] & 0xffffffff00000000ull); carry -= _vli_sub(result, result, tmp, ndigits); /* d3 */ tmp[0] = (product[6] >> 32) | (product[7] << 32); tmp[1] = (product[7] >> 32) | (product[4] << 32); tmp[2] = (product[4] >> 32) | (product[5] << 32); tmp[3] = (product[6] << 32); carry -= _vli_sub(result, result, tmp, ndigits); /* d4 */ tmp[0] = product[7]; tmp[1] = product[4] & 0xffffffff00000000ull; tmp[2] = product[5]; tmp[3] = product[6] & 0xffffffff00000000ull; carry -= _vli_sub(result, result, tmp, ndigits); if (carry < 0) { do { carry += _vli_add(result, result, curve_prime, ndigits); } while (carry < 0); } else { while (carry || _vli_cmp(curve_prime, result, ndigits) != 1) carry -= _vli_sub(result, result, curve_prime, ndigits); } } /* * The NIST algorithms define S values, which are comprised of 32 bit C values * of the original product we are trying to reduce. Since we are working with * 64 bit 'digits', we need to convert these C values into 64 bit chunks. This * macro mainly makes code readability easier since we can directly pass the * two C indexes (h and l). Some of these C values are zero, which is also a * value C index. In this case -1 should be passed to indicate zero. */ #define ECC_SET_S(prod, h, l) ({ \ uint64_t r = 0; \ if (h == -1) { \ /* zero, don't do anything */ \ } else if (h & 1) \ r |= (prod[h / 2] & 0xffffffff00000000ull); \ else \ r |= (prod[h / 2] << 32); \ if (l == -1) { \ /* zero, don't do anything */ \ } else if (l & 1) \ r |= (prod[l / 2] >> 32); \ else \ r |= (prod[l / 2] & 0xffffffff); \ r; \ }) static void vli_mmod_fast_384(uint64_t *result, const uint64_t *product, const uint64_t *curve_prime, uint64_t *tmp) { int carry; const unsigned int ndigits = 6; /* t */ vli_set(result, product, ndigits); /* s1 */ tmp[0] = 0; tmp[1] = 0; tmp[2] = ECC_SET_S(product, 22, 21); tmp[3] = ECC_SET_S(product, -1, 23); tmp[4] = 0; tmp[5] = 0; carry = vli_lshift(tmp, tmp, 1, ndigits); carry += _vli_add(result, result, tmp, ndigits); /* s2 */ tmp[0] = product[6]; tmp[1] = product[7]; tmp[2] = product[8]; tmp[3] = product[9]; tmp[4] = product[10]; tmp[5] = product[11]; carry += _vli_add(result, result, tmp, ndigits); /* s3 */ tmp[0] = ECC_SET_S(product, 22, 21); tmp[1] = ECC_SET_S(product, 12, 23); tmp[2] = ECC_SET_S(product, 14, 13); tmp[3] = ECC_SET_S(product, 16, 15); tmp[4] = ECC_SET_S(product, 18, 17); tmp[5] = ECC_SET_S(product, 20, 19); carry += _vli_add(result, result, tmp, ndigits); /* s4 */ tmp[0] = ECC_SET_S(product, 23, -1); tmp[1] = ECC_SET_S(product, 20, -1); tmp[2] = ECC_SET_S(product, 13, 12); tmp[3] = ECC_SET_S(product, 15, 14); tmp[4] = ECC_SET_S(product, 17, 16); tmp[5] = ECC_SET_S(product, 19, 18); carry += _vli_add(result, result, tmp, ndigits); /* s5 */ tmp[0] = 0; tmp[1] = 0; tmp[2] = ECC_SET_S(product, 21, 20); tmp[3] = ECC_SET_S(product, 23, 22); tmp[4] = 0; tmp[5] = 0; carry += _vli_add(result, result, tmp, ndigits); /* s6 */ tmp[0] = ECC_SET_S(product, -1, 20); tmp[1] = ECC_SET_S(product, 21, -1); tmp[2] = ECC_SET_S(product, 23, 22); tmp[3] = 0; tmp[4] = 0; tmp[5] = 0; carry += _vli_add(result, result, tmp, ndigits); /* s7 */ tmp[0] = ECC_SET_S(product, 12, 23); tmp[1] = ECC_SET_S(product, 14, 13); tmp[2] = ECC_SET_S(product, 16, 15); tmp[3] = ECC_SET_S(product, 18, 17); tmp[4] = ECC_SET_S(product, 20, 19); tmp[5] = ECC_SET_S(product, 22, 21); carry -= _vli_sub(result, result, tmp, ndigits); /* s8 */ tmp[0] = ECC_SET_S(product, 20, -1); tmp[1] = ECC_SET_S(product, 22, 21); tmp[2] = ECC_SET_S(product, -1, 23); tmp[3] = 0; tmp[4] = 0; tmp[5] = 0; carry -= _vli_sub(result, result, tmp, ndigits); /* s9 */ tmp[0] = 0; tmp[1] = ECC_SET_S(product, 23, -1); tmp[2] = ECC_SET_S(product, -1, 23); tmp[3] = 0; tmp[4] = 0; tmp[5] = 0; carry -= _vli_sub(result, result, tmp, ndigits); if (carry < 0) { do { carry += _vli_add(result, result, curve_prime, ndigits); } while (carry < 0); } else { while (carry || _vli_cmp(curve_prime, result, ndigits) != 1) carry -= _vli_sub(result, result, curve_prime, ndigits); } } /* Computes result = product % curve_prime * from http://www.nsa.gov/ia/_files/nist-routines.pdf */ static void vli_mmod_fast_521(uint64_t *result, const uint64_t *product, const uint64_t *curve_prime, uint64_t *tmp) { const unsigned int ndigits = 9; size_t i; /* Initialize result with lowest 521 bits from product */ vli_set(result, product, ndigits); result[8] &= 0x1ff; for (i = 0; i < ndigits; i++) tmp[i] = (product[8 + i] >> 9) | (product[9 + i] << 55); tmp[8] &= 0x1ff; _vli_mod_add(result, result, tmp, curve_prime, ndigits); } /* Computes result = product % curve_prime * from http://www.nsa.gov/ia/_files/nist-routines.pdf */ bool _vli_mmod_fast(uint64_t *result, const uint64_t *product, const uint64_t *curve_prime, unsigned int ndigits) { uint64_t tmp[2 * L_ECC_MAX_DIGITS]; unsigned int nbits = _vli_num_bits(curve_prime, ndigits); switch (nbits) { case 192: vli_mmod_fast_192(result, product, curve_prime, tmp); break; case 224: vli_mmod_fast_224(result, product, curve_prime, tmp); break; case 256: vli_mmod_fast_256(result, product, curve_prime, tmp); break; case 384: vli_mmod_fast_384(result, product, curve_prime, tmp); break; case 521: vli_mmod_fast_521(result, product, curve_prime, tmp); break; default: return false; } return true; } /* Computes result = (left * right) % curve_p. */ void _vli_mod_mult_fast(uint64_t *result, const uint64_t *left, const uint64_t *right, const uint64_t *curve_prime, unsigned int ndigits) { uint64_t product[2 * L_ECC_MAX_DIGITS]; vli_mult(product, left, right, ndigits); _vli_mmod_fast(result, product, curve_prime, ndigits); } /* Computes result = left^2 % curve_p. */ void _vli_mod_square_fast(uint64_t *result, const uint64_t *left, const uint64_t *curve_prime, unsigned int ndigits) { uint64_t product[2 * L_ECC_MAX_DIGITS]; vli_square(product, left, ndigits); _vli_mmod_fast(result, product, curve_prime, ndigits); } #define EVEN(vli) (!(vli[0] & 1)) /* Computes result = (1 / p_input) % mod. All VLIs are the same size. * See "From Euclid's GCD to Montgomery Multiplication to the Great Divide" * https://labs.oracle.com/techrep/2001/smli_tr-2001-95.pdf */ void _vli_mod_inv(uint64_t *result, const uint64_t *input, const uint64_t *mod, unsigned int ndigits) { uint64_t a[L_ECC_MAX_DIGITS], b[L_ECC_MAX_DIGITS]; uint64_t u[L_ECC_MAX_DIGITS], v[L_ECC_MAX_DIGITS]; uint64_t carry; int cmp_result; if (vli_is_zero(input, ndigits)) { vli_clear(result, ndigits); return; } vli_set(a, input, ndigits); vli_set(b, mod, ndigits); vli_clear(u, ndigits); u[0] = 1; vli_clear(v, ndigits); while ((cmp_result = _vli_cmp(a, b, ndigits)) != 0) { carry = 0; if (EVEN(a)) { _vli_rshift1(a, ndigits); if (!EVEN(u)) carry = _vli_add(u, u, mod, ndigits); _vli_rshift1(u, ndigits); if (carry) u[ndigits - 1] |= 0x8000000000000000ull; } else if (EVEN(b)) { _vli_rshift1(b, ndigits); if (!EVEN(v)) carry = _vli_add(v, v, mod, ndigits); _vli_rshift1(v, ndigits); if (carry) v[ndigits - 1] |= 0x8000000000000000ull; } else if (cmp_result > 0) { _vli_sub(a, a, b, ndigits); _vli_rshift1(a, ndigits); if (_vli_cmp(u, v, ndigits) < 0) _vli_add(u, u, mod, ndigits); _vli_sub(u, u, v, ndigits); if (!EVEN(u)) carry = _vli_add(u, u, mod, ndigits); _vli_rshift1(u, ndigits); if (carry) u[ndigits - 1] |= 0x8000000000000000ull; } else { _vli_sub(b, b, a, ndigits); _vli_rshift1(b, ndigits); if (_vli_cmp(v, u, ndigits) < 0) _vli_add(v, v, mod, ndigits); _vli_sub(v, v, u, ndigits); if (!EVEN(v)) carry = _vli_add(v, v, mod, ndigits); _vli_rshift1(v, ndigits); if (carry) v[ndigits - 1] |= 0x8000000000000000ull; } } vli_set(result, u, ndigits); } /* ------ Point operations ------ */ /* Point multiplication algorithm using Montgomery's ladder with co-Z * coordinates. From http://eprint.iacr.org/2011/338.pdf */ /* Double in place */ static void ecc_point_double_jacobian(uint64_t *x1, uint64_t *y1, uint64_t *z1, const uint64_t *curve_prime, unsigned int ndigits) { /* t1 = x, t2 = y, t3 = z */ uint64_t t4[L_ECC_MAX_DIGITS]; uint64_t t5[L_ECC_MAX_DIGITS]; if (vli_is_zero(z1, ndigits)) return; /* t4 = y1^2 */ _vli_mod_square_fast(t4, y1, curve_prime, ndigits); /* t5 = x1*y1^2 = A */ _vli_mod_mult_fast(t5, x1, t4, curve_prime, ndigits); /* t4 = y1^4 */ _vli_mod_square_fast(t4, t4, curve_prime, ndigits); /* t2 = y1*z1 = z3 */ _vli_mod_mult_fast(y1, y1, z1, curve_prime, ndigits); /* t3 = z1^2 */ _vli_mod_square_fast(z1, z1, curve_prime, ndigits); /* t1 = x1 + z1^2 */ _vli_mod_add(x1, x1, z1, curve_prime, ndigits); /* t3 = 2*z1^2 */ _vli_mod_add(z1, z1, z1, curve_prime, ndigits); /* t3 = x1 - z1^2 */ _vli_mod_sub(z1, x1, z1, curve_prime, ndigits); /* t1 = x1^2 - z1^4 */ _vli_mod_mult_fast(x1, x1, z1, curve_prime, ndigits); /* t3 = 2*(x1^2 - z1^4) */ _vli_mod_add(z1, x1, x1, curve_prime, ndigits); /* t1 = 3*(x1^2 - z1^4) */ _vli_mod_add(x1, x1, z1, curve_prime, ndigits); if (vli_test_bit(x1, 0)) { uint64_t carry = _vli_add(x1, x1, curve_prime, ndigits); _vli_rshift1(x1, ndigits); x1[ndigits - 1] |= carry << 63; } else { _vli_rshift1(x1, ndigits); } /* t1 = 3/2*(x1^2 - z1^4) = B */ /* t3 = B^2 */ _vli_mod_square_fast(z1, x1, curve_prime, ndigits); /* t3 = B^2 - A */ _vli_mod_sub(z1, z1, t5, curve_prime, ndigits); /* t3 = B^2 - 2A = x3 */ _vli_mod_sub(z1, z1, t5, curve_prime, ndigits); /* t5 = A - x3 */ _vli_mod_sub(t5, t5, z1, curve_prime, ndigits); /* t1 = B * (A - x3) */ _vli_mod_mult_fast(x1, x1, t5, curve_prime, ndigits); /* t4 = B * (A - x3) - y1^4 = y3 */ _vli_mod_sub(t4, x1, t4, curve_prime, ndigits); vli_set(x1, z1, ndigits); vli_set(z1, y1, ndigits); vli_set(y1, t4, ndigits); } /* Modify (x1, y1) => (x1 * z^2, y1 * z^3) */ static void apply_z(uint64_t *x1, uint64_t *y1, uint64_t *z, const uint64_t *curve_prime, unsigned int ndigits) { uint64_t t1[L_ECC_MAX_DIGITS]; _vli_mod_square_fast(t1, z, curve_prime, ndigits); /* z^2 */ _vli_mod_mult_fast(x1, x1, t1, curve_prime, ndigits); /* x1 * z^2 */ _vli_mod_mult_fast(t1, t1, z, curve_prime, ndigits); /* z^3 */ _vli_mod_mult_fast(y1, y1, t1, curve_prime, ndigits); /* y1 * z^3 */ } /* P = (x1, y1) => 2P, (x2, y2) => P' */ static void xycz_initial_double(uint64_t *x1, uint64_t *y1, uint64_t *x2, uint64_t *y2, uint64_t *p_initial_z, const uint64_t *curve_prime, unsigned int ndigits) { uint64_t z[L_ECC_MAX_DIGITS]; vli_set(x2, x1, ndigits); vli_set(y2, y1, ndigits); vli_clear(z, ndigits); z[0] = 1; if (p_initial_z) vli_set(z, p_initial_z, ndigits); apply_z(x1, y1, z, curve_prime, ndigits); ecc_point_double_jacobian(x1, y1, z, curve_prime, ndigits); apply_z(x2, y2, z, curve_prime, ndigits); } /* Input P = (x1, y1, Z), Q = (x2, y2, Z) * Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3) * or P => P', Q => P + Q */ static void xycz_add(uint64_t *x1, uint64_t *y1, uint64_t *x2, uint64_t *y2, const uint64_t *curve_prime, unsigned int ndigits) { /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ uint64_t t5[L_ECC_MAX_DIGITS]; /* t5 = x2 - x1 */ _vli_mod_sub(t5, x2, x1, curve_prime, ndigits); /* t5 = (x2 - x1)^2 = A */ _vli_mod_square_fast(t5, t5, curve_prime, ndigits); /* t1 = x1*A = B */ _vli_mod_mult_fast(x1, x1, t5, curve_prime, ndigits); /* t3 = x2*A = C */ _vli_mod_mult_fast(x2, x2, t5, curve_prime, ndigits); /* t4 = y2 - y1 */ _vli_mod_sub(y2, y2, y1, curve_prime, ndigits); /* t5 = (y2 - y1)^2 = D */ _vli_mod_square_fast(t5, y2, curve_prime, ndigits); /* t5 = D - B */ _vli_mod_sub(t5, t5, x1, curve_prime, ndigits); /* t5 = D - B - C = x3 */ _vli_mod_sub(t5, t5, x2, curve_prime, ndigits); /* t3 = C - B */ _vli_mod_sub(x2, x2, x1, curve_prime, ndigits); /* t2 = y1*(C - B) */ _vli_mod_mult_fast(y1, y1, x2, curve_prime, ndigits); /* t3 = B - x3 */ _vli_mod_sub(x2, x1, t5, curve_prime, ndigits); /* t4 = (y2 - y1)*(B - x3) */ _vli_mod_mult_fast(y2, y2, x2, curve_prime, ndigits); /* t4 = y3 */ _vli_mod_sub(y2, y2, y1, curve_prime, ndigits); vli_set(x2, t5, ndigits); } /* Input P = (x1, y1, Z), Q = (x2, y2, Z) * Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3) * or P => P - Q, Q => P + Q */ static void xycz_add_c(uint64_t *x1, uint64_t *y1, uint64_t *x2, uint64_t *y2, const uint64_t *curve_prime, unsigned int ndigits) { /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ uint64_t t5[L_ECC_MAX_DIGITS]; uint64_t t6[L_ECC_MAX_DIGITS]; uint64_t t7[L_ECC_MAX_DIGITS]; /* t5 = x2 - x1 */ _vli_mod_sub(t5, x2, x1, curve_prime, ndigits); /* t5 = (x2 - x1)^2 = A */ _vli_mod_square_fast(t5, t5, curve_prime, ndigits); /* t1 = x1*A = B */ _vli_mod_mult_fast(x1, x1, t5, curve_prime, ndigits); /* t3 = x2*A = C */ _vli_mod_mult_fast(x2, x2, t5, curve_prime, ndigits); /* t4 = y2 + y1 */ _vli_mod_add(t5, y2, y1, curve_prime, ndigits); /* t4 = y2 - y1 */ _vli_mod_sub(y2, y2, y1, curve_prime, ndigits); /* t6 = C - B */ _vli_mod_sub(t6, x2, x1, curve_prime, ndigits); /* t2 = y1 * (C - B) */ _vli_mod_mult_fast(y1, y1, t6, curve_prime, ndigits); /* t6 = B + C */ _vli_mod_add(t6, x1, x2, curve_prime, ndigits); /* t3 = (y2 - y1)^2 */ _vli_mod_square_fast(x2, y2, curve_prime, ndigits); /* t3 = x3 */ _vli_mod_sub(x2, x2, t6, curve_prime, ndigits); /* t7 = B - x3 */ _vli_mod_sub(t7, x1, x2, curve_prime, ndigits); /* t4 = (y2 - y1)*(B - x3) */ _vli_mod_mult_fast(y2, y2, t7, curve_prime, ndigits); /* t4 = y3 */ _vli_mod_sub(y2, y2, y1, curve_prime, ndigits); /* t7 = (y2 + y1)^2 = F */ _vli_mod_square_fast(t7, t5, curve_prime, ndigits); /* t7 = x3' */ _vli_mod_sub(t7, t7, t6, curve_prime, ndigits); /* t6 = x3' - B */ _vli_mod_sub(t6, t7, x1, curve_prime, ndigits); /* t6 = (y2 + y1)*(x3' - B) */ _vli_mod_mult_fast(t6, t6, t5, curve_prime, ndigits); /* t2 = y3' */ _vli_mod_sub(y1, t6, y1, curve_prime, ndigits); vli_set(x1, t7, ndigits); } void _ecc_point_mult(struct l_ecc_point *result, const struct l_ecc_point *point, const uint64_t *scalar, uint64_t *initial_z, const uint64_t *curve_prime) { /* R0 and R1 */ const struct l_ecc_curve *curve = point->curve; uint64_t rx[2][L_ECC_MAX_DIGITS]; uint64_t ry[2][L_ECC_MAX_DIGITS]; uint64_t z[L_ECC_MAX_DIGITS]; uint64_t sk[2][L_ECC_MAX_DIGITS]; int i, nb; unsigned int ndigits = curve->ndigits; unsigned int nbits = _vli_num_bits(curve->n, ndigits); int num_bits; int carry; carry = _vli_add(sk[0], scalar, curve->n, ndigits); _vli_add(sk[1], sk[0], curve->n, ndigits); scalar = sk[!carry]; /* secp224r1 and secp521r1 curves */ if (nbits == 224 || nbits == 521) num_bits = nbits + 2; else num_bits = sizeof(uint64_t) * ndigits * 8 + 1; vli_set(rx[1], point->x, ndigits); vli_set(ry[1], point->y, ndigits); xycz_initial_double(rx[1], ry[1], rx[0], ry[0], initial_z, curve_prime, ndigits); for (i = num_bits - 2; i > 0; i--) { nb = !vli_test_bit(scalar, i); xycz_add_c(rx[1 - nb], ry[1 - nb], rx[nb], ry[nb], curve_prime, ndigits); xycz_add(rx[nb], ry[nb], rx[1 - nb], ry[1 - nb], curve_prime, ndigits); } nb = !vli_test_bit(scalar, 0); xycz_add_c(rx[1 - nb], ry[1 - nb], rx[nb], ry[nb], curve_prime, ndigits); /* Find final 1/Z value. */ /* X1 - X0 */ _vli_mod_sub(z, rx[1], rx[0], curve_prime, ndigits); /* Yb * (X1 - X0) */ _vli_mod_mult_fast(z, z, ry[1 - nb], curve_prime, ndigits); /* xP * Yb * (X1 - X0) */ _vli_mod_mult_fast(z, z, point->x, curve_prime, ndigits); /* 1 / (xP * Yb * (X1 - X0)) */ _vli_mod_inv(z, z, curve_prime, ndigits); /* yP / (xP * Yb * (X1 - X0)) */ _vli_mod_mult_fast(z, z, point->y, curve_prime, ndigits); /* Xb * yP / (xP * Yb * (X1 - X0)) */ _vli_mod_mult_fast(z, z, rx[1 - nb], curve_prime, ndigits); /* End 1/Z calculation */ xycz_add(rx[nb], ry[nb], rx[1 - nb], ry[1 - nb], curve_prime, ndigits); apply_z(rx[0], ry[0], z, curve_prime, ndigits); vli_set(result->x, rx[0], ndigits); vli_set(result->y, ry[0], ndigits); } /* Returns true if p_point is the point at infinity, false otherwise. */ bool _ecc_point_is_zero(const struct l_ecc_point *point) { return (vli_is_zero(point->x, point->curve->ndigits) && vli_is_zero(point->y, point->curve->ndigits)); } bluez-5.87/ell/PaxHeaders/pem.c0000644000000000000000000000005014567163422013366 xustar0020 atime=1783076269 20 ctime=1783094099 bluez-5.87/ell/pem.c0000644000000000000000000004363014567163422013055 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2015 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include "useful.h" #include "private.h" #include "key.h" #include "cert.h" #include "queue.h" #include "pem.h" #include "base64.h" #include "utf8.h" #include "asn1-private.h" #include "cipher.h" #include "cert-private.h" #include "missing.h" #include "pem-private.h" #define PEM_START_BOUNDARY "-----BEGIN " #define PEM_END_BOUNDARY "-----END " static const char *is_start_boundary(const void *buf, size_t buf_len, size_t *label_len) { const char *start, *end, *ptr; int prev_special, special; const char *buf_ptr = buf; if (buf_len < strlen(PEM_START_BOUNDARY)) return NULL; /* Check we have a "-----BEGIN " (RFC7468 section 2) */ if (memcmp(buf, PEM_START_BOUNDARY, strlen(PEM_START_BOUNDARY))) return NULL; /* * Check we have a string of printable characters in which no * two consecutive characters are "special" nor is the first or the * final character "special". These special characters are space * and hyphen. (RFC7468 section 3) * The loop will end on the second hyphen of the final "-----" if * no error found earlier. */ start = buf + strlen(PEM_START_BOUNDARY); end = start; prev_special = 1; while (end < buf_ptr + buf_len && l_ascii_isprint(*end)) { special = *end == ' ' || *end == '-'; if (prev_special && special) break; end++; prev_special = special; } /* Rewind to the first '-', but handle empty labels */ if (end != start) end--; /* Check we have a "-----" (RFC7468 section 2) */ if (end + 5 > buf_ptr + buf_len || memcmp(end, "-----", 5)) return NULL; /* Check all remaining characters are horizontal whitespace (WSP) */ for (ptr = end + 5; ptr < buf_ptr + buf_len; ptr++) if (*ptr != ' ' && *ptr != '\t') return NULL; *label_len = end - start; return start; } static bool is_end_boundary(const void *buf, size_t buf_len, const char *label, size_t label_len) { const char *buf_ptr = buf; size_t len = strlen(PEM_END_BOUNDARY) + label_len + 5; if (buf_len < len) return false; if (memcmp(buf_ptr, PEM_END_BOUNDARY, strlen(PEM_END_BOUNDARY)) || memcmp(buf_ptr + strlen(PEM_END_BOUNDARY), label, label_len) || memcmp(buf_ptr + (len - 5), "-----", 5)) return false; /* Check all remaining characters are horizontal whitespace (WSP) */ for (; len < buf_len; len++) if (buf_ptr[len] != ' ' && buf_ptr[len] != '\t') return false; return true; } const char *pem_next(const void *buf, size_t buf_len, char **type_label, size_t *base64_len, const char **endp, bool strict) { const char *buf_ptr = buf; const char *base64_data = NULL, *eol; const char *label = NULL; size_t label_len = 0; const char *start = NULL; /* * The base64 parser uses the RFC7468 laxbase64text grammar but we * do full checks on the encapsulation boundary lines, i.e. no * leading spaces allowed, making sure quoted text and similar * are not confused for actual PEM "textual encoding". */ while (buf_len) { for (eol = buf_ptr; eol < buf_ptr + buf_len; eol++) if (*eol == '\r' || *eol == '\n') break; if (!base64_data) { label = is_start_boundary(buf_ptr, eol - buf_ptr, &label_len); if (label) { start = label - strlen("-----BEGIN "); base64_data = eol; } else if (strict) break; } else if (start && is_end_boundary(buf_ptr, eol - buf_ptr, label, label_len)) { if (type_label) *type_label = l_strndup(label, label_len); if (base64_len) *base64_len = buf_ptr - base64_data; if (endp) { if (eol == buf_ptr + buf_len) *endp = eol; else *endp = eol + 1; } return base64_data; } if (eol == buf_ptr + buf_len) break; buf_len -= eol + 1 - buf_ptr; buf_ptr = eol + 1; if (buf_len && *eol == '\r' && *buf_ptr == '\n') { buf_ptr++; buf_len--; } } /* If we found no label signal EOF rather than parse error */ if (!base64_data && endp) *endp = NULL; return NULL; } uint8_t *pem_load_buffer(const void *buf, size_t buf_len, char **out_type_label, size_t *out_len, char **out_headers, const char **out_endp) { size_t base64_len; const char *base64; char *label; const char *headers = NULL; size_t headers_len; uint8_t *ret; base64 = pem_next(buf, buf_len, &label, &base64_len, out_endp, false); if (!base64) return NULL; if (memchr(base64, ':', base64_len)) { const char *start; const char *end; while (base64_len && l_ascii_isspace(*base64)) { base64++; base64_len--; } start = base64; if (!(end = memmem(start, base64_len, "\n\n", 2)) && !(end = memmem(start, base64_len, "\n\r\n", 3))) goto err; /* Check that each header line has a key and a colon */ while (start < end) { const char *lf = rawmemchr(start, '\n'); const char *colon = memchr(start, ':', lf - start); if (!colon) goto err; for (; start < colon; start++) if (l_ascii_isalnum(*start)) break; if (start == colon) goto err; start = lf + 1; } headers = base64; headers_len = end - base64; base64_len -= headers_len + 2; base64 = end + 2; } ret = l_base64_decode(base64, base64_len, out_len); if (ret) { *out_type_label = label; if (out_headers) { if (headers) *out_headers = l_strndup(headers, headers_len); else *out_headers = NULL; } return ret; } err: l_free(label); return NULL; } LIB_EXPORT uint8_t *l_pem_load_buffer(const void *buf, size_t buf_len, char **type_label, size_t *out_len) { return pem_load_buffer(buf, buf_len, type_label, out_len, NULL, NULL); } int pem_file_open(struct pem_file_info *info, const char *filename) { info->fd = open(filename, O_RDONLY); if (info->fd < 0) return -errno; if (fstat(info->fd, &info->st) < 0) { int r = -errno; close(info->fd); return r; } info->data = mmap(NULL, info->st.st_size, PROT_READ, MAP_SHARED, info->fd, 0); if (info->data == MAP_FAILED) { int r = -errno; close(info->fd); return r; } return 0; } void pem_file_close(struct pem_file_info *info) { munmap(info->data, info->st.st_size); close(info->fd); } static uint8_t *pem_load_file(const char *filename, char **out_type_label, size_t *out_len, char **out_headers) { struct pem_file_info file; uint8_t *result; if (unlikely(!filename)) return NULL; if (pem_file_open(&file, filename) < 0) return NULL; result = pem_load_buffer(file.data, file.st.st_size, out_type_label, out_len, out_headers, NULL); pem_file_close(&file); return result; } LIB_EXPORT uint8_t *l_pem_load_file(const char *filename, char **out_type_label, size_t *out_len) { return pem_load_file(filename, out_type_label, out_len, NULL); } static struct l_certchain *pem_list_to_chain(struct l_queue *list) { struct l_certchain *chain; if (!list) return NULL; chain = certchain_new_from_leaf(l_queue_pop_head(list)); while (!l_queue_isempty(list)) certchain_link_issuer(chain, l_queue_pop_head(list)); l_queue_destroy(list, NULL); return chain; } LIB_EXPORT struct l_certchain *l_pem_load_certificate_chain_from_data( const void *buf, size_t len) { struct l_queue *list = l_pem_load_certificate_list_from_data(buf, len); if (!list) return NULL; return pem_list_to_chain(list); } LIB_EXPORT struct l_certchain *l_pem_load_certificate_chain( const char *filename) { struct l_queue *list = l_pem_load_certificate_list(filename); if (!list) return NULL; return pem_list_to_chain(list); } static bool pem_write_one_cert(struct l_cert *cert, void *user_data) { int *fd = user_data; const uint8_t *der; size_t der_len; struct iovec iov[3]; ssize_t r; der = l_cert_get_der_data(cert, &der_len); iov[0].iov_base = "-----BEGIN CERTIFICATE-----\n"; iov[0].iov_len = strlen(iov[0].iov_base); iov[1].iov_base = l_base64_encode(der, der_len, 64); iov[1].iov_len = strlen(iov[1].iov_base); iov[2].iov_base = "\n-----END CERTIFICATE-----\n"; iov[2].iov_len = strlen(iov[2].iov_base); r = L_TFR(writev(*fd, iov, 3)); l_free(iov[1].iov_base); if (r == (ssize_t) (iov[0].iov_len + iov[1].iov_len + iov[2].iov_len)) return false; if (r < 0) *fd = -errno; else *fd = -EIO; return true; } int pem_write_certificate_chain(const struct l_certchain *chain, const char *filename) { int fd = L_TFR(open(filename, O_CREAT | O_WRONLY | O_CLOEXEC, 0600)); int err = fd; if (err < 0) return -errno; l_certchain_walk_from_leaf((struct l_certchain *) chain, pem_write_one_cert, &err); close(fd); return err < 0 ? err : 0; } LIB_EXPORT struct l_queue *l_pem_load_certificate_list_from_data( const void *buf, size_t len) { const char *ptr, *end; struct l_queue *list = NULL; ptr = buf; end = buf + len; while (ptr && ptr < end) { uint8_t *der; size_t der_len; char *label = NULL; struct l_cert *cert; const char *base64; size_t base64_len; bool is_certificate; base64 = pem_next(ptr, end - ptr, &label, &base64_len, &ptr, false); if (!base64) { if (!ptr) break; /* if ptr was not reset to NULL; parse error */ goto error; } is_certificate = !strcmp(label, "CERTIFICATE"); l_free(label); if (!is_certificate) goto error; der = l_base64_decode(base64, base64_len, &der_len); if (!der) goto error; cert = l_cert_new_from_der(der, der_len); l_free(der); if (!cert) goto error; if (!list) list = l_queue_new(); l_queue_push_tail(list, cert); } return list; error: l_queue_destroy(list, (l_queue_destroy_func_t) l_cert_free); return NULL; } LIB_EXPORT struct l_queue *l_pem_load_certificate_list(const char *filename) { struct pem_file_info file; struct l_queue *list = NULL; if (unlikely(!filename)) return NULL; if (pem_file_open(&file, filename) < 0) return NULL; list = l_pem_load_certificate_list_from_data(file.data, file.st.st_size); pem_file_close(&file); return list; } #define SKIP_WHITESPACE(str) \ while (l_ascii_isspace(*(str))) \ (str)++; static const char *parse_rfc1421_dek_info(char *headers, const char **out_params) { const char *proc_type = NULL; char *dek_info = NULL; char *comma; while (headers) { char *lf = strchrnul(headers, '\n'); char *key; key = headers; SKIP_WHITESPACE(key); headers = (*lf == '\n') ? lf + 1 : NULL; if (!memcmp(key, "X-", 2)) key += 2; if (!memcmp(key, "Proc-Type:", 10)) { if (proc_type) return NULL; proc_type = key + 10; SKIP_WHITESPACE(proc_type); } else if (!memcmp(key, "DEK-Info:", 9)) { if (dek_info) return NULL; dek_info = key + 9; SKIP_WHITESPACE(dek_info); } else continue; while (l_ascii_isspace(lf[-1])) lf--; *lf = '\0'; } if (!proc_type || !dek_info) return NULL; /* Skip the version field (should be 3 or 4) */ proc_type = strchr(proc_type, ','); if (!proc_type) return NULL; proc_type++; SKIP_WHITESPACE(proc_type); /* Section 4.6.1.1 */ if (strcmp(proc_type, "ENCRYPTED")) return NULL; comma = strchr(dek_info, ','); if (comma) { *out_params = comma + 1; SKIP_WHITESPACE(*out_params); while (comma > dek_info && l_ascii_isspace(comma[-1])) comma--; *comma = '\0'; } else *out_params = NULL; return dek_info; } static struct l_cipher *cipher_from_dek_info(const char *algid, const char *params, const char *passphrase, size_t *block_len) { enum l_cipher_type type; struct l_cipher *cipher; struct l_checksum *md5; uint8_t key[32]; size_t key_len; bool ok; L_AUTO_FREE_VAR(uint8_t *, iv) = NULL; size_t iv_len; if (!strcmp(algid, "DES-CBC")) { type = L_CIPHER_DES_CBC; key_len = 8; iv_len = 8; } else if (!strcmp(algid, "DES-EDE3-CBC")) { type = L_CIPHER_DES3_EDE_CBC; key_len = 24; iv_len = 8; } else if (!strcmp(algid, "AES-128-CBC")) { type = L_CIPHER_AES_CBC; key_len = 16; iv_len = 16; } else if (!strcmp(algid, "AES-192-CBC")) { type = L_CIPHER_AES_CBC; key_len = 24; iv_len = 16; } else if (!strcmp(algid, "AES-256-CBC")) { type = L_CIPHER_AES_CBC; key_len = 32; iv_len = 16; } else return NULL; if (!params || strlen(params) != 2 * iv_len) return NULL; *block_len = iv_len; iv = l_util_from_hexstring(params, &iv_len); if (!iv) return NULL; /* * The encryption key is the MD5(password | IV[:8]), this comes from * opessl's crypto/evp/evp_key.c:EVP_BytesToKey() and doesn't seem to * be backed by any standard: * https://web.archive.org/web/20190528100132/https://latacora.singles/2018/08/03/the-default-openssh.html */ md5 = l_checksum_new(L_CHECKSUM_MD5); if (!md5) return NULL; ok = l_checksum_update(md5, passphrase, strlen(passphrase)) && l_checksum_update(md5, iv, 8) && l_checksum_get_digest(md5, key, 16) == 16; if (ok && key_len > 16) { l_checksum_reset(md5); ok = l_checksum_update(md5, key, 16) && l_checksum_update(md5, passphrase, strlen(passphrase)) && l_checksum_update(md5, iv, 8) && l_checksum_get_digest(md5, key + 16, 16) == 16; } l_checksum_free(md5); if (!ok) { cipher = NULL; goto cleanup; } cipher = l_cipher_new(type, key, key_len); if (!cipher) goto cleanup; if (l_cipher_set_iv(cipher, iv, iv_len)) goto cleanup; l_cipher_free(cipher); cipher = NULL; cleanup: explicit_bzero(key, sizeof(key)); return cipher; } struct l_key *pem_load_private_key(uint8_t *content, size_t len, char *label, const char *passphrase, char *headers, bool *encrypted) { struct l_key *pkey; /* * RFC7468 Section 10-compatible unencrypted private key label * (also mentioned in PKCS#8/RFC5958 Section 5), encodes * the PKCS#8/RFC5958 PrivateKeyInfo structure -- supported * directly by the pkcs8-key-parser kernel module. */ if (!strcmp(label, "PRIVATE KEY")) { /* RFC822 Headers explicitly disallowed in RFC7468 */ if (headers) goto err; pkey = cert_key_from_pkcs8_private_key_info(content, len); goto done; } /* * RFC7468 Section 11-compatible encrypted private key label * (also mentioned in PKCS#8/RFC5958 Section 5), encodes * the PKCS#8/RFC5958 EncryptedPrivateKeyInfo structure. We * decrypt it into a plain PrivateKeyInfo for the * pkcs8-key-parser module. */ if (!strcmp(label, "ENCRYPTED PRIVATE KEY")) { if (encrypted) *encrypted = true; if (!passphrase) goto err; /* RFC822 Headers explicitly disallowed in RFC7468 */ if (headers) goto err; pkey = cert_key_from_pkcs8_encrypted_private_key_info(content, len, passphrase); goto done; } /* * Legacy RSA private key label aka. SSLeay format, understood by * most software but not documented in an RFC. Encodes the * PKCS#1/RFC8017 RSAPrivateKey structure. We wrap it in a PKCS#8 * PrivateKeyInfo for the pkcs8-key-parser module. */ if (!strcmp(label, "RSA PRIVATE KEY")) { const char *dekalgid; const char *dekparameters; /* * "openssl rsa ..." can produce encrypted PKCS#1-formatted * keys. These are incompatible with RFC7468 parsing because * of the RFC822 headers present but the format is the same * as documented in RFC1421. The encryption algorithms are * supposed to be the ones defined in RFC1423 but that would * be only DES-CBC while openssl allows other algorithms. * When decrypted we get the RSAPrivateKey struct and proceed * like with the unencrypted format. */ dekalgid = parse_rfc1421_dek_info(headers, &dekparameters); if (dekalgid) { struct l_cipher *alg; bool r; size_t block_len; uint8_t pad; if (encrypted) *encrypted = true; if (!passphrase) goto err; alg = cipher_from_dek_info(dekalgid, dekparameters, passphrase, &block_len); if (!alg) goto err; if (len % block_len || !len) { l_cipher_free(alg); goto err; } r = l_cipher_decrypt(alg, content, content, len); l_cipher_free(alg); if (!r) goto err; /* Remove padding like in RFC1423 Section 1.1 */ pad = content[len - 1]; if (pad > block_len || pad == 0) goto err; if (!l_secure_memeq(content + len - pad, pad - 1U, pad)) goto err; len -= pad; } pkey = cert_key_from_pkcs1_rsa_private_key(content, len); goto done; } /* Label not known */ err: pkey = NULL; done: explicit_bzero(content, len); l_free(content); l_free(label); l_free(headers); return pkey; } LIB_EXPORT struct l_key *l_pem_load_private_key_from_data(const void *buf, size_t buf_len, const char *passphrase, bool *encrypted) { uint8_t *content; char *label; size_t len; char *headers; if (encrypted) *encrypted = false; content = pem_load_buffer(buf, buf_len, &label, &len, &headers, NULL); if (!content) return NULL; return pem_load_private_key(content, len, label, passphrase, headers, encrypted); } /** * l_pem_load_private_key * @filename: path string to the PEM file to load * @passphrase: private key encryption passphrase or NULL for unencrypted * @encrypted: receives indication whether the file was encrypted if non-NULL * * Load the PEM encoded RSA Private Key file at @filename. If it is an * encrypted private key and @passphrase was non-NULL, the file is * decrypted. If it's unencrypted @passphrase is ignored. @encrypted * stores information of whether the file was encrypted, both in a * success case and on error when NULL is returned. This can be used to * check if a passphrase is required without prior information. * * The passphrase, if given, must have been validated as UTF-8 unless the * caller knows that PKCS#12 encryption algorithms are not used. * Use l_utf8_validate. * * Returns: An l_key object to be freed with an l_key_free* function, * or NULL. **/ LIB_EXPORT struct l_key *l_pem_load_private_key(const char *filename, const char *passphrase, bool *encrypted) { uint8_t *content; char *label; size_t len; char *headers; if (encrypted) *encrypted = false; content = pem_load_file(filename, &label, &len, &headers); if (!content) return NULL; return pem_load_private_key(content, len, label, passphrase, headers, encrypted); } bluez-5.87/ell/PaxHeaders/string.c0000644000000000000000000000005014661631567014120 xustar0020 atime=1783076263 20 ctime=1783094099 bluez-5.87/ell/string.c0000644000000000000000000002224014661631567013601 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #include #include "strv.h" #include "string.h" #include "private.h" #include "useful.h" /** * SECTION:string * @short_description: Growable string buffer * * Growable string buffer support */ /** * l_string: * * Opaque object representing the string buffer. */ struct l_string { size_t max; size_t len; char *str; }; static void grow_string(struct l_string *str, size_t extra) { if (str->len + extra < str->max) return; str->max = str->len + extra + 1; if (str->max < l_util_pagesize()) str->max = roundup_pow_of_two(str->max); else str->max = align_len(str->max, l_util_pagesize()); str->str = l_realloc(str->str, str->max); } /** * l_string_new: * @initial_length: Initial length of the groable string * * Create new growable string. If the @initial_length is 0, then a safe * default is chosen. * * Returns: a newly allocated #l_string object. **/ LIB_EXPORT struct l_string *l_string_new(size_t initial_length) { static const size_t DEFAULT_INITIAL_LENGTH = 127; struct l_string *ret; ret = l_new(struct l_string, 1); if (initial_length == 0) initial_length = DEFAULT_INITIAL_LENGTH; grow_string(ret, initial_length); ret->str[0] = '\0'; return ret; } /** * l_string_free: * @string: growable string object * * Free the growable string object and all associated data **/ LIB_EXPORT void l_string_free(struct l_string *string) { if (unlikely(!string)) return; l_free(string->str); l_free(string); } /** * l_string_unwrap: * @string: growable string object * * Free the growable string object and return the internal string data. * The caller is responsible for freeing the string data using l_free(), * and the string object is no longer usable. * * Returns: @string's internal buffer **/ LIB_EXPORT char *l_string_unwrap(struct l_string *string) { char *result; if (unlikely(!string)) return NULL; result = string->str; l_free(string); return result; } /** * l_string_append: * @dest: growable string object * @src: C-style string to copy * * Appends the contents of @src to @dest. The internal buffer of @dest is * grown if necessary. * * Returns: @dest **/ LIB_EXPORT struct l_string *l_string_append(struct l_string *dest, const char *src) { size_t size; if (unlikely(!dest || !src)) return NULL; size = strlen(src); grow_string(dest, size); memcpy(dest->str + dest->len, src, size); dest->len += size; dest->str[dest->len] = '\0'; return dest; } /** * l_string_append_c: * @dest: growable string object * @c: Character * * Appends character given by @c to @dest. The internal buffer of @dest is * grown if necessary. * * Returns: @dest **/ LIB_EXPORT struct l_string *l_string_append_c(struct l_string *dest, const char c) { if (unlikely(!dest)) return NULL; grow_string(dest, 1); dest->str[dest->len++] = c; dest->str[dest->len] = '\0'; return dest; } /** * l_string_append_fixed: * @dest: growable string object * @src: Character array to copy from * @max: Maximum number of characters to copy * * Appends the contents of a fixed size string array @src to @dest. * The internal buffer of @dest is grown if necessary. Up to a maximum of * @max characters are copied. If a null is encountered in the first @max * characters, the string is copied only up to the NULL character. * * Returns: @dest **/ LIB_EXPORT struct l_string *l_string_append_fixed(struct l_string *dest, const char *src, size_t max) { const char *nul; if (unlikely(!dest || !src || !max)) return NULL; nul = memchr(src, 0, max); if (nul) max = nul - src; grow_string(dest, max); memcpy(dest->str + dest->len, src, max); dest->len += max; dest->str[dest->len] = '\0'; return dest; } /** * l_string_append_vprintf: * @dest: growable string object * @format: the string format. See the sprintf() documentation * @args: the parameters to insert * * Appends a formatted string to the growable string buffer. This function * is equivalent to l_string_append_printf except that the arguments are * passed as a va_list. **/ LIB_EXPORT void l_string_append_vprintf(struct l_string *dest, const char *format, va_list args) { size_t len; size_t have_space; va_list args_copy; if (unlikely(!dest)) return; #if __STDC_VERSION__ > 199409L va_copy(args_copy, args); #else __va_copy(args_copy, args); #endif have_space = dest->max - dest->len; len = vsnprintf(dest->str + dest->len, have_space, format, args); if (len >= have_space) { grow_string(dest, len); len = vsprintf(dest->str + dest->len, format, args_copy); } dest->len += len; va_end(args_copy); } /** * l_string_append_printf: * @dest: growable string object * @format: the string format. See the sprintf() documentation * @...: the parameters to insert * * Appends a formatted string to the growable string buffer, growing it as * necessary. **/ LIB_EXPORT void l_string_append_printf(struct l_string *dest, const char *format, ...) { va_list args; if (unlikely(!dest)) return; va_start(args, format); l_string_append_vprintf(dest, format, args); va_end(args); } /** * l_string_length: * @string: growable string object * * Returns: bytes used in the string. **/ LIB_EXPORT unsigned int l_string_length(struct l_string *string) { if (unlikely(!string)) return 0; return string->len; } LIB_EXPORT struct l_string *l_string_truncate(struct l_string *string, size_t new_size) { if (unlikely(!string)) return NULL; if (new_size >= string->len) return string; string->len = new_size; string->str[new_size] = '\0'; return string; } struct arg { size_t max_len; size_t cur_len; char *chars; }; static inline void arg_init(struct arg *arg) { arg->max_len = 0; arg->cur_len = 0; arg->chars = NULL; } static void arg_putchar(struct arg *arg, char ch) { if (arg->cur_len == arg->max_len) { arg->max_len += 32; /* Grow by at least 32 bytes */ arg->chars = l_realloc(arg->chars, 1 + arg->max_len); } arg->chars[arg->cur_len++] = ch; arg->chars[arg->cur_len] = '\0'; } static void arg_putmem(struct arg *arg, const void *mem, size_t len) { if (len == 0) return; if (arg->cur_len + len > arg->max_len) { size_t growby = len * 2; if (growby < 32) growby = 32; arg->max_len += growby; arg->chars = l_realloc(arg->chars, 1 + arg->max_len); } memcpy(arg->chars + arg->cur_len, mem, len); arg->cur_len += len; arg->chars[arg->cur_len] = '\0'; } static bool parse_backslash(struct arg *arg, const char *args, size_t *pos) { /* We're at the backslash, not within double quotes */ char c = args[*pos + 1]; switch (c) { case 0: return false; case '\n': break; default: arg_putchar(arg, c); break; } *pos += 1; return true; } static bool parse_quoted_backslash(struct arg *arg, const char *args, size_t *pos) { /* We're at the backslash, within double quotes */ char c = args[*pos + 1]; switch (c) { case 0: return false; case '\n': break; case '"': case '\\': arg_putchar(arg, c); break; default: arg_putchar(arg, '\\'); arg_putchar(arg, c); break; } *pos += 1; return true; } static bool parse_single_quote(struct arg *arg, const char *args, size_t *pos) { /* We're just past the single quote */ size_t start = *pos; for (; args[*pos]; *pos += 1) { if (args[*pos] != '\'') continue; arg_putmem(arg, args + start, *pos - start); return true; } /* Unterminated ' */ return false; } static bool parse_double_quote(struct arg *arg, const char *args, size_t *pos) { /* We're just past the double quote */ for (; args[*pos]; *pos += 1) { char c = args[*pos]; switch (c) { case '"': return true; case '\\': if (!parse_quoted_backslash(arg, args, pos)) return false; break; default: arg_putchar(arg, c); break; } } /* Unterminated */ return false; } static void add_arg(char ***args, char *arg, int *n_args) { *args = l_realloc(*args, sizeof(char *) * (2 + *n_args)); (*args)[*n_args] = arg; (*args)[*n_args + 1] = NULL; *n_args += 1; } LIB_EXPORT char **l_parse_args(const char *args, int *out_n_args) { size_t i; struct arg arg; char **ret = l_realloc(NULL, sizeof(char *)); int n_args = 0; ret[0] = NULL; arg_init(&arg); for (i = 0; args[i]; i++) { switch (args[i]) { case '\\': if (!parse_backslash(&arg, args, &i)) goto error; break; case '"': i += 1; if (!parse_double_quote(&arg, args, &i)) goto error; /* Add an empty string */ if (!arg.cur_len) add_arg(&ret, l_strdup(""), &n_args); break; case '\'': i += 1; if (!parse_single_quote(&arg, args, &i)) goto error; /* Add an empty string */ if (!arg.cur_len) add_arg(&ret, l_strdup(""), &n_args); break; default: if (!strchr(" \t", args[i])) { if (args[i] == '\n') goto error; arg_putchar(&arg, args[i]); continue; } if (arg.cur_len) add_arg(&ret, arg.chars, &n_args); arg_init(&arg); break; } } if (arg.cur_len) add_arg(&ret, arg.chars, &n_args); if (out_n_args) *out_n_args = n_args; return ret; error: l_free(arg.chars); l_strfreev(ret); return NULL; } bluez-5.87/ell/PaxHeaders/internal0000644000000000000000000000005015221753053014171 xustar0020 atime=1783092779 20 ctime=1783094110 bluez-5.87/ell/internal0000644000000000000000000000000015221753053013640 0ustar00rootrootbluez-5.87/ell/PaxHeaders/tls-private.h0000644000000000000000000000005014504767710015065 xustar0020 atime=1783076232 20 ctime=1783094099 bluez-5.87/ell/tls-private.h0000644000000000000000000002376114504767710014557 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define TLS_MAX_VERSION L_TLS_V12 #define TLS_MIN_VERSION L_TLS_V10 enum tls_cipher_type { TLS_CIPHER_STREAM, TLS_CIPHER_BLOCK, TLS_CIPHER_AEAD, }; struct tls_bulk_encryption_algorithm { enum tls_cipher_type cipher_type; union { enum l_cipher_type l_id; enum l_aead_cipher_type l_aead_id; }; size_t key_length; size_t iv_length; size_t fixed_iv_length; size_t block_length; size_t auth_tag_length; }; /* * Support the minimum required set of handshake hash types for the * Certificate Verify digital signature and the Finished PRF seed so we * don't have to accumulate all of messages full contents until the * Finished message. If we're sent a hash of a different type (in TLS 1.2+) * and need to verify we'll give up. * SHA1 and MD5 are explicitly required by versions < 1.2 and 1.2 requires * that the Finished hash is the same as used for the PRF so we need to * keep at least the hashes our supported cipher suites specify for the PRF. */ enum handshake_hash_type { HANDSHAKE_HASH_SHA384, HANDSHAKE_HASH_SHA256, HANDSHAKE_HASH_MD5, HANDSHAKE_HASH_SHA1, __HANDSHAKE_HASH_COUNT, }; #define HANDSHAKE_HASH_MAX_SIZE 48 struct tls_hash_algorithm { uint8_t tls_id; enum handshake_hash_type type; enum l_checksum_type l_id; const char *name; }; extern const struct tls_hash_algorithm tls_handshake_hash_data[]; typedef bool (*tls_get_hash_t)(struct l_tls *tls, enum handshake_hash_type type, const uint8_t *data, size_t data_len, uint8_t *out, size_t *out_len); struct tls_signature_algorithm { uint8_t id; bool (*validate_cert_key_type)(struct l_cert *cert); ssize_t (*sign)(struct l_tls *tls, uint8_t *out, size_t out_len, tls_get_hash_t get_hash, const uint8_t *data, size_t data_len); bool (*verify)(struct l_tls *tls, const uint8_t *in, size_t in_len, tls_get_hash_t get_hash, const uint8_t *data, size_t data_len); }; struct tls_key_exchange_algorithm { bool need_ecc; bool need_ffdh; bool (*send_server_key_exchange)(struct l_tls *tls); void (*handle_server_key_exchange)(struct l_tls *tls, const uint8_t *buf, size_t len); bool (*send_client_key_exchange)(struct l_tls *tls); void (*handle_client_key_exchange)(struct l_tls *tls, const uint8_t *buf, size_t len); void (*free_params)(struct l_tls *tls); }; struct tls_mac_algorithm { uint8_t id; enum l_checksum_type hmac_type; size_t mac_length; }; struct tls_cipher_suite { uint8_t id[2]; const char *name; size_t verify_data_length; struct tls_bulk_encryption_algorithm *encryption; struct tls_signature_algorithm *signature; struct tls_key_exchange_algorithm *key_xchg; struct tls_mac_algorithm *mac; enum l_checksum_type prf_hmac; }; extern struct tls_cipher_suite *tls_cipher_suite_pref[]; struct tls_compression_method { int id; const char *name; }; struct tls_hello_extension { const char *name; const char *short_name; uint16_t id; ssize_t (*client_write)(struct l_tls *tls, uint8_t *buf, size_t len); /* Handle a Client Hello extension (on server), can't be NULL */ bool (*client_handle)(struct l_tls *tls, const uint8_t *buf, size_t len); /* Handle a Client Hello extension's absence (on server) */ bool (*client_handle_absent)(struct l_tls *tls); ssize_t (*server_write)(struct l_tls *tls, uint8_t *buf, size_t len); /* Handle a Server Hello extension (on client) */ bool (*server_handle)(struct l_tls *tls, const uint8_t *buf, size_t len); /* Handle a Server Hello extension's absence (on client) */ bool (*server_handle_absent)(struct l_tls *tls); }; extern const struct tls_hello_extension tls_extensions[]; struct tls_named_group { const char *name; uint16_t id; enum { TLS_GROUP_TYPE_EC, TLS_GROUP_TYPE_FF, } type; union { struct { const uint8_t *prime; size_t prime_len; unsigned int generator; } ff; }; }; enum tls_handshake_state { TLS_HANDSHAKE_WAIT_START, TLS_HANDSHAKE_WAIT_HELLO, TLS_HANDSHAKE_WAIT_CERTIFICATE, TLS_HANDSHAKE_WAIT_KEY_EXCHANGE, TLS_HANDSHAKE_WAIT_HELLO_DONE, TLS_HANDSHAKE_WAIT_CERTIFICATE_VERIFY, TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC, TLS_HANDSHAKE_WAIT_FINISHED, TLS_HANDSHAKE_DONE, }; enum tls_content_type { TLS_CT_CHANGE_CIPHER_SPEC = 20, TLS_CT_ALERT = 21, TLS_CT_HANDSHAKE = 22, TLS_CT_APPLICATION_DATA = 23, }; enum tls_handshake_type { TLS_HELLO_REQUEST = 0, TLS_CLIENT_HELLO = 1, TLS_SERVER_HELLO = 2, TLS_CERTIFICATE = 11, TLS_SERVER_KEY_EXCHANGE = 12, TLS_CERTIFICATE_REQUEST = 13, TLS_SERVER_HELLO_DONE = 14, TLS_CERTIFICATE_VERIFY = 15, TLS_CLIENT_KEY_EXCHANGE = 16, TLS_FINISHED = 20, }; struct l_tls { bool server; l_tls_write_cb_t tx, rx; l_tls_ready_cb_t ready_handle; l_tls_disconnect_cb_t disconnected; void *user_data; l_tls_debug_cb_t debug_handler; l_tls_destroy_cb_t debug_destroy; void *debug_data; char *cert_dump_path; enum l_tls_version min_version; enum l_tls_version max_version; struct l_queue *ca_certs; struct l_certchain *cert; struct l_key *priv_key; size_t priv_key_size; char **subject_mask; struct tls_cipher_suite **cipher_suite_pref_list; struct l_settings *session_settings; char *session_prefix; uint64_t session_lifetime; unsigned int session_count_max; l_tls_session_update_cb_t session_update_cb; void *session_update_user_data; bool in_callback; bool pending_destroy; /* Record layer */ uint8_t *record_buf; int record_buf_len; int record_buf_max_len; bool record_flush; uint8_t *message_buf; int message_buf_len; int message_buf_max_len; enum tls_content_type message_content_type; /* Handshake protocol layer */ enum tls_handshake_state state; struct l_checksum *handshake_hash[__HANDSHAKE_HASH_COUNT]; uint8_t prev_digest[__HANDSHAKE_HASH_COUNT][HANDSHAKE_HASH_MAX_SIZE]; enum l_tls_version client_version; enum l_tls_version negotiated_version; bool cert_requested, cert_sent; bool peer_authenticated; struct l_cert *peer_cert; struct l_key *peer_pubkey; size_t peer_pubkey_size; enum handshake_hash_type signature_hash; const struct tls_hash_algorithm *prf_hmac; const struct tls_named_group *negotiated_curve; const struct tls_named_group *negotiated_ff_group; uint8_t session_id[32]; size_t session_id_size; uint8_t session_id_replaced[32]; size_t session_id_size_replaced; bool session_id_new; uint8_t session_cipher_suite_id[2]; uint8_t session_compression_method_id; char *session_peer_identity; bool session_resumed; struct { bool secure_renegotiation; /* Max .verify_data_length over supported cipher suites */ uint8_t client_verify_data[12]; uint8_t server_verify_data[12]; } renegotiation_info; /* SecurityParameters current and pending */ struct { struct tls_cipher_suite *cipher_suite; struct tls_compression_method *compression_method; uint8_t master_secret[48]; uint8_t client_random[32]; uint8_t server_random[32]; /* * Max key block size per 6.3 v1.1 is 136 bytes but if we * allow AES_256_CBC_SHA256 with v1.0 we get 128 per section * 6.3 v1.2 + two IVs of 32 bytes. */ uint8_t key_block[192]; void *key_xchg_params; } pending; enum tls_cipher_type cipher_type[2]; struct tls_cipher_suite *cipher_suite[2]; union { struct l_cipher *cipher[2]; struct l_aead_cipher *aead_cipher[2]; }; struct l_checksum *mac[2]; size_t mac_length[2]; size_t block_length[2]; size_t record_iv_length[2]; size_t fixed_iv_length[2]; uint8_t fixed_iv[2][32]; size_t auth_tag_length[2]; uint64_t seq_num[2]; /* * Some of the key and IV parts of the "current" state are kept * inside the cipher and mac states in the kernel so we don't * duplicate them here. */ bool ready; }; bool tls10_prf(const void *secret, size_t secret_len, const char *label, const void *seed, size_t seed_len, uint8_t *out, size_t out_len); bool tls12_prf(enum l_checksum_type type, const void *secret, size_t secret_len, const char *label, const void *seed, size_t seed_len, uint8_t *out, size_t out_len); void tls_disconnect(struct l_tls *tls, enum l_tls_alert_desc desc, enum l_tls_alert_desc local_desc); void tls_tx_record(struct l_tls *tls, enum tls_content_type type, const uint8_t *data, size_t len); bool tls_handle_message(struct l_tls *tls, const uint8_t *message, int len, enum tls_content_type type, uint16_t version); #define TLS_HANDSHAKE_HEADER_SIZE 4 void tls_tx_handshake(struct l_tls *tls, int type, uint8_t *buf, size_t length); bool tls_cipher_suite_is_compatible(struct l_tls *tls, const struct tls_cipher_suite *suite, const char **error); /* Optionally limit allowed cipher suites to a custom set */ bool tls_set_cipher_suites(struct l_tls *tls, const char **suite_list); void tls_generate_master_secret(struct l_tls *tls, const uint8_t *pre_master_secret, int pre_master_secret_len); size_t tls_verify_data_length(struct l_tls *tls, unsigned int index); const struct tls_named_group *tls_find_group(uint16_t id); const struct tls_named_group *tls_find_ff_group(const uint8_t *prime, size_t prime_len, const uint8_t *generator, size_t generator_len); ssize_t tls_write_signature_algorithms(struct l_tls *tls, uint8_t *buf, size_t len); ssize_t tls_parse_signature_algorithms(struct l_tls *tls, const uint8_t *buf, size_t len); int tls_parse_certificate_list(const void *data, size_t len, struct l_certchain **out_certchain); #define TLS_DEBUG(fmt, args...) \ l_util_debug(tls->debug_handler, tls->debug_data, "%s:%i " fmt, \ __func__, __LINE__, ## args) #define TLS_SET_STATE(new_state) \ do { \ TLS_DEBUG("New state %s", \ tls_handshake_state_to_str(new_state)); \ tls->state = new_state; \ } while (0) #define TLS_DISCONNECT(desc, local_desc, fmt, args...) \ do { \ TLS_DEBUG("Disconnect desc=%s local-desc=%s reason=" fmt,\ l_tls_alert_to_str(desc), \ l_tls_alert_to_str(local_desc), ## args);\ tls_disconnect(tls, desc, local_desc); \ } while (0) #define TLS_VER_FMT "1.%i" #define TLS_VER_ARGS(version) (((version) & 0xff) - 1) const char *tls_handshake_state_to_str(enum tls_handshake_state state); bluez-5.87/ell/PaxHeaders/ecc-private.h0000644000000000000000000000005014526443132015006 xustar0020 atime=1783076232 20 ctime=1783094099 bluez-5.87/ell/ecc-private.h0000644000000000000000000000735514526443132014501 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2018 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include "ecc.h" #include "util.h" struct l_ecc_curve; struct l_ecc_point { uint64_t x[L_ECC_MAX_DIGITS]; uint64_t y[L_ECC_MAX_DIGITS]; const struct l_ecc_curve *curve; }; struct l_ecc_curve { unsigned int ndigits; unsigned int ike_group; unsigned int tls_group; const char *name; struct l_ecc_point g; uint64_t p[L_ECC_MAX_DIGITS]; uint64_t n[L_ECC_MAX_DIGITS]; uint64_t b[L_ECC_MAX_DIGITS]; int z; }; struct l_ecc_scalar { uint64_t c[L_ECC_MAX_DIGITS]; const struct l_ecc_curve *curve; }; /* * Performs a secure memory comparison of two uint64_t buffers of size bytes * representing an integer. Blobs are ordered in little endian. It returns * a negative, zero or positif value if a < b, a == b or a > b respectively. */ static inline int secure_memcmp_64(const uint64_t *a, const uint64_t *b, size_t size) { uint64_t aa_64, bb_64; int res = 0, mask; size_t i = 0; if (size) { /* * Arrays store blobs in LE, we will process each blob as a * byte array of size 8 using l_secure_memcmp. We need to make * sure to feed a BE byte array to avoid unexpected behavior * on different architectures. */ do { aa_64 = L_CPU_TO_BE64(a[i]); bb_64 = L_CPU_TO_BE64(b[i]); mask = l_secure_memcmp(&aa_64, &bb_64, 8); res = (mask & res) | mask; i++; } while (i != size); } return res; } void _ecc_be2native(uint64_t *dest, const uint64_t *bytes, unsigned int ndigits); void _ecc_native2be(uint64_t *dest, const uint64_t *native, unsigned int ndigits); void _vli_mod_inv(uint64_t *result, const uint64_t *input, const uint64_t *mod, unsigned int ndigits); void _vli_mod_sub(uint64_t *result, const uint64_t *left, const uint64_t *right, const uint64_t *mod, unsigned int ndigits); void _vli_mod_add(uint64_t *result, const uint64_t *left, const uint64_t *right, const uint64_t *mod, unsigned int ndigits); void _vli_rshift1(uint64_t *vli, unsigned int ndigits); void _vli_mmod_slow(uint64_t *result, const uint64_t *product, const uint64_t *mod, unsigned int ndigits); bool _vli_mmod_fast(uint64_t *result, const uint64_t *product, const uint64_t *curve_prime, unsigned int ndigits); void _vli_mod_mult_fast(uint64_t *result, const uint64_t *left, const uint64_t *right, const uint64_t *curve_prime, unsigned int ndigits); void _vli_mod_square_fast(uint64_t *result, const uint64_t *left, const uint64_t *curve_prime, unsigned int ndigits); void _vli_mod_exp(uint64_t *result, const uint64_t *base, const uint64_t *exp, const uint64_t *mod, unsigned int ndigits); int _vli_cmp(const uint64_t *left, const uint64_t *right, unsigned int ndigits); bool _vli_is_zero_or_one(const uint64_t *vli, unsigned int ndigits); uint64_t _vli_add(uint64_t *result, const uint64_t *left, const uint64_t *right, unsigned int ndigits); uint64_t _vli_sub(uint64_t *result, const uint64_t *left, const uint64_t *right, unsigned int ndigits); int _vli_legendre(uint64_t *val, const uint64_t *p, unsigned int ndigits); bool _ecc_point_is_zero(const struct l_ecc_point *point); void _ecc_calculate_p2(const struct l_ecc_curve *curve, uint64_t *p2); bool _ecc_compute_y(const struct l_ecc_curve *curve, uint64_t *y, const uint64_t *x); void _ecc_point_mult(struct l_ecc_point *result, const struct l_ecc_point *point, const uint64_t *scalar, uint64_t *initial_z, const uint64_t *curve_prime); void _ecc_point_add(struct l_ecc_point *ret, const struct l_ecc_point *p, const struct l_ecc_point *q, const uint64_t *curve_prime); struct l_ecc_scalar *_ecc_constant_new(const struct l_ecc_curve *curve, const void *buf, size_t len); bluez-5.87/ell/PaxHeaders/main-private.h0000644000000000000000000000005014504767710015207 xustar0020 atime=1783076232 20 ctime=1783094099 bluez-5.87/ell/main-private.h0000644000000000000000000000140614504767710014671 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2021 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ typedef void (*watch_event_cb_t) (int fd, uint32_t events, void *user_data); typedef void (*watch_destroy_cb_t) (void *user_data); typedef void (*idle_event_cb_t) (void *user_data); typedef void (*idle_destroy_cb_t) (void *user_data); int watch_add(int fd, uint32_t events, watch_event_cb_t callback, void *user_data, watch_destroy_cb_t destroy); int watch_modify(int fd, uint32_t events, bool force); int watch_remove(int fd, bool epoll_del); int watch_clear(int fd); #define IDLE_FLAG_NO_WARN_DANGLING 0x10000000 int idle_add(idle_event_cb_t callback, void *user_data, uint32_t flags, idle_destroy_cb_t destroy); void idle_remove(int id); bluez-5.87/ell/PaxHeaders/main.c0000644000000000000000000000005015110070741013513 xustar0020 atime=1783076257 20 ctime=1783094099 bluez-5.87/ell/main.c0000644000000000000000000002705615110070741013206 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "signal.h" #include "queue.h" #include "log.h" #include "useful.h" #include "main.h" #include "main-private.h" #include "private.h" #include "timeout.h" /** * SECTION:main * @short_description: Main loop handling * * Main loop handling */ #define MAX_EPOLL_EVENTS 10 #define IDLE_FLAG_DISPATCHING 1 #define IDLE_FLAG_DESTROYED 2 #define WATCH_FLAG_DISPATCHING 1 #define WATCH_FLAG_DESTROYED 2 #define WATCHDOG_TRIGGER_FREQ 2 static int epoll_fd = -1; static bool epoll_running; static bool epoll_terminate; static int idle_id; static int notify_fd; static struct l_timeout *watchdog; static struct l_queue *idle_list; struct watch_data { int fd; uint32_t events; uint32_t flags; watch_event_cb_t callback; watch_destroy_cb_t destroy; void *user_data; }; #define DEFAULT_WATCH_ENTRIES 128 static unsigned int watch_entries; static struct watch_data **watch_list; struct idle_data { idle_event_cb_t callback; idle_destroy_cb_t destroy; void *user_data; uint32_t flags; int id; }; static inline bool __attribute__ ((always_inline)) create_epoll(void) { unsigned int i; epoll_fd = epoll_create1(EPOLL_CLOEXEC); if (epoll_fd < 0) return false; watch_list = malloc(DEFAULT_WATCH_ENTRIES * sizeof(void *)); if (!watch_list) goto close_epoll; idle_list = l_queue_new(); idle_id = 0; watch_entries = DEFAULT_WATCH_ENTRIES; for (i = 0; i < watch_entries; i++) watch_list[i] = NULL; return true; close_epoll: close(epoll_fd); epoll_fd = -1; return false; } int watch_add(int fd, uint32_t events, watch_event_cb_t callback, void *user_data, watch_destroy_cb_t destroy) { struct watch_data *data; struct epoll_event ev; int err; if (unlikely(fd < 0 || !callback)) return -EINVAL; if (epoll_fd < 0) return -EIO; if (L_WARN_ON((unsigned int) fd > watch_entries - 1)) return -ERANGE; data = l_new(struct watch_data, 1); data->fd = fd; data->events = events; data->flags = 0; data->callback = callback; data->destroy = destroy; data->user_data = user_data; memset(&ev, 0, sizeof(ev)); ev.events = events; ev.data.ptr = data; err = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, data->fd, &ev); if (err < 0) { l_free(data); return -errno; } watch_list[fd] = data; return 0; } int watch_modify(int fd, uint32_t events, bool force) { struct watch_data *data; struct epoll_event ev; int err; if (unlikely(fd < 0)) return -EINVAL; if ((unsigned int) fd > watch_entries - 1) return -ERANGE; data = watch_list[fd]; if (!data) return -ENXIO; if (data->events == events && !force) return 0; memset(&ev, 0, sizeof(ev)); ev.events = events; ev.data.ptr = data; err = epoll_ctl(epoll_fd, EPOLL_CTL_MOD, data->fd, &ev); if (err < 0) return -errno; data->events = events; return 0; } int watch_clear(int fd) { struct watch_data *data; if (unlikely(fd < 0)) return -EINVAL; if ((unsigned int) fd > watch_entries - 1) return -ERANGE; data = watch_list[fd]; if (!data) return -ENXIO; watch_list[fd] = NULL; if (data->destroy) data->destroy(data->user_data); if (data->flags & WATCH_FLAG_DISPATCHING) data->flags |= WATCH_FLAG_DESTROYED; else l_free(data); return 0; } int watch_remove(int fd, bool epoll_del) { int err = watch_clear(fd); if (err < 0) return err; if (!epoll_del) goto done; err = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL); if (err < 0) return -errno; done: return err; } static bool idle_remove_by_id(void *data, void *user_data) { struct idle_data *idle = data; int id = L_PTR_TO_INT(user_data); if (idle->id != id) return false; if (idle->destroy) idle->destroy(idle->user_data); if (idle->flags & IDLE_FLAG_DISPATCHING) { idle->flags |= IDLE_FLAG_DESTROYED; return false; } l_free(idle); return true; } static bool idle_prune(void *data, void *user_data) { struct idle_data *idle = data; if ((idle->flags & IDLE_FLAG_DESTROYED) == 0) return false; l_free(idle); return true; } int idle_add(idle_event_cb_t callback, void *user_data, uint32_t flags, idle_destroy_cb_t destroy) { struct idle_data *data; if (unlikely(!callback)) return -EINVAL; if (epoll_fd < 0) return -EIO; data = l_new(struct idle_data, 1); data->callback = callback; data->destroy = destroy; data->user_data = user_data; data->flags = flags; if (!l_queue_push_tail(idle_list, data)) { l_free(data); return -ENOMEM; } data->id = idle_id++; if (idle_id == INT_MAX) idle_id = 0; return data->id; } void idle_remove(int id) { l_queue_foreach_remove(idle_list, idle_remove_by_id, L_INT_TO_PTR(id)); } static void idle_destroy(void *data) { struct idle_data *idle = data; if (!(idle->flags & IDLE_FLAG_NO_WARN_DANGLING)) l_error("Dangling idle descriptor %p, %d found", data, idle->id); if (idle->destroy) idle->destroy(idle->user_data); l_free(idle); } static void idle_dispatch(void *data, void *user_data) { struct idle_data *idle = data; if (!idle->callback) return; idle->flags |= IDLE_FLAG_DISPATCHING; idle->callback(idle->user_data); idle->flags &= ~IDLE_FLAG_DISPATCHING; } static int sd_notify(const char *state) { int err; if (notify_fd <= 0) return -ENOTCONN; err = send(notify_fd, state, strlen(state), MSG_NOSIGNAL); if (err < 0) return -errno; return 0; } static void watchdog_callback(struct l_timeout *timeout, void *user_data) { int msec = L_PTR_TO_INT(user_data); sd_notify("WATCHDOG=1"); l_timeout_modify_ms(timeout, msec); } static void create_sd_notify_socket(void) { const char *sock; struct sockaddr_un addr; const char *watchdog_usec; socklen_t len; int msec; /* check if NOTIFY_SOCKET has been set */ sock = getenv("NOTIFY_SOCKET"); if (!sock) return; /* check for abstract socket or absolute path */ if (sock[0] != '@' && sock[0] != '/') return; notify_fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (notify_fd < 0) { notify_fd = 0; return; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, sock, sizeof(addr.sun_path) - 1); len = offsetof(struct sockaddr_un, sun_path) + strlen(sock); if (addr.sun_path[0] == '@') addr.sun_path[0] = '\0'; if (connect(notify_fd, (struct sockaddr *) &addr, len) < 0) { close(notify_fd); notify_fd = 0; return; } watchdog_usec = getenv("WATCHDOG_USEC"); if (!watchdog_usec) return; msec = atoi(watchdog_usec) / 1000; if (msec < WATCHDOG_TRIGGER_FREQ) return; msec /= WATCHDOG_TRIGGER_FREQ; watchdog = l_timeout_create_ms(msec, watchdog_callback, L_INT_TO_PTR(msec), NULL); } /** * l_main_init: * * Initialize the main loop. This must be called before l_main_run() * and any other function that directly or indirectly sets up an idle * or watch. A safe rule-of-thumb is to call it before any function * prefixed with "l_". * * Returns: true if initialization was successful, false otherwise. **/ LIB_EXPORT bool l_main_init(void) { if (unlikely(epoll_running)) return false; if (!create_epoll()) return false; create_sd_notify_socket(); epoll_terminate = false; return true; } /** * l_main_prepare: * * Prepare the iteration of the main loop * * Returns: The timeout to use. This will be 0 if idle-event processing is * currently pending, or -1 otherwise. This value can be used to pass to * l_main_iterate. */ LIB_EXPORT int l_main_prepare(void) { return l_queue_isempty(idle_list) ? -1 : 0; } /** * l_main_iterate: * * Run one iteration of the main event loop */ LIB_EXPORT void l_main_iterate(int timeout) { struct epoll_event events[MAX_EPOLL_EVENTS]; struct watch_data *data; int n, nfds; nfds = epoll_wait(epoll_fd, events, MAX_EPOLL_EVENTS, timeout); for (n = 0; n < nfds; n++) { data = events[n].data.ptr; data->flags |= WATCH_FLAG_DISPATCHING; } for (n = 0; n < nfds; n++) { data = events[n].data.ptr; if (data->flags & WATCH_FLAG_DESTROYED) continue; data->callback(data->fd, events[n].events, data->user_data); } for (n = 0; n < nfds; n++) { data = events[n].data.ptr; if (data->flags & WATCH_FLAG_DESTROYED) l_free(data); else data->flags = 0; } l_queue_foreach(idle_list, idle_dispatch, NULL); l_queue_foreach_remove(idle_list, idle_prune, NULL); } /** * l_main_run: * * Run the main loop * * The loop may be restarted by invoking this function after a * previous invocation returns, provided that l_main_exit() has not * been called first. * * Returns: #EXIT_SUCCESS after successful execution or #EXIT_FAILURE in * case of failure **/ LIB_EXPORT int l_main_run(void) { int timeout; /* Has l_main_init() been called? */ if (unlikely(epoll_fd < 0)) return EXIT_FAILURE; if (unlikely(epoll_running)) return EXIT_FAILURE; sd_notify("READY=1"); epoll_running = true; for (;;) { if (epoll_terminate) break; timeout = l_main_prepare(); l_main_iterate(timeout); } epoll_running = false; if (notify_fd) { close(notify_fd); notify_fd = 0; l_timeout_remove(watchdog); watchdog = NULL; } return EXIT_SUCCESS; } /** * l_main_exit: * * Clean up after main loop completes. * **/ LIB_EXPORT bool l_main_exit(void) { unsigned int i; if (epoll_running) { l_error("Cleanup attempted on running main loop"); return false; } for (i = 0; i < watch_entries; i++) { struct watch_data *data = watch_list[i]; if (!data) continue; epoll_ctl(epoll_fd, EPOLL_CTL_DEL, data->fd, NULL); if (data->destroy) data->destroy(data->user_data); else l_error("Dangling file descriptor %d found", data->fd); l_free(data); } watch_entries = 0; free(watch_list); watch_list = NULL; l_queue_destroy(idle_list, idle_destroy); idle_list = NULL; close(epoll_fd); epoll_fd = -1; return true; } /** * l_main_quit: * * Terminate the running main loop * * Returns: #true when terminating the main loop or #false in case of failure **/ LIB_EXPORT bool l_main_quit(void) { if (unlikely(!epoll_running)) return false; epoll_terminate = true; return true; } struct signal_data { l_main_signal_cb_t callback; void *user_data; }; static void sigint_handler(void *user_data) { struct signal_data *data = user_data; if (data->callback) data->callback(SIGINT, data->user_data); } static void sigterm_handler(void *user_data) { struct signal_data *data = user_data; if (data->callback) data->callback(SIGTERM, data->user_data); } /** * l_main_run_with_signal: * * Run the main loop with signal handling for SIGINT and SIGTERM * * Returns: #EXIT_SUCCESS after successful execution or #EXIT_FAILURE in * case of failure **/ LIB_EXPORT int l_main_run_with_signal(l_main_signal_cb_t callback, void *user_data) { struct signal_data *data; struct l_signal *sigint; struct l_signal *sigterm; int result; data = l_new(struct signal_data, 1); data->callback = callback; data->user_data = user_data; sigint = l_signal_create(SIGINT, sigint_handler, data, NULL); sigterm = l_signal_create(SIGTERM, sigterm_handler, data, NULL); result = l_main_run(); l_signal_remove(sigint); l_signal_remove(sigterm); l_free(data); return result; } /** * l_main_get_epoll_fd: * * Can be used to obtain the epoll file descriptor in order to integrate * the ell main event loop with other event loops. * * Returns: epoll file descriptor **/ LIB_EXPORT int l_main_get_epoll_fd(void) { return epoll_fd; } bluez-5.87/ell/PaxHeaders/util.c0000644000000000000000000000005014773301370013555 xustar0020 atime=1783076247 20 ctime=1783094099 bluez-5.87/ell/util.c0000644000000000000000000004155714773301370013252 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "utf8.h" #include "util.h" #include "useful.h" #include "private.h" /** * SECTION:util * @short_description: Utility functions * * Utility functions */ /** * l_malloc: * @size: memory size to allocate * * If for any reason the memory allocation fails, then execution will be * halted via abort(). * * In case @size is 0 then #NULL will be returned. * * Returns: pointer to allocated memory **/ LIB_EXPORT void *l_malloc(size_t size) { if (likely(size)) { void *ptr; ptr = malloc(size); if (ptr) return ptr; fprintf(stderr, "%s:%s(): failed to allocate %zd bytes\n", STRLOC, __func__, size); abort(); } return NULL; } /** * l_realloc: * @mem: previously allocated memory, or NULL * @size: memory size to allocate * * If for any reason the memory allocation fails, then execution will be * halted via abort(). * * In case @mem is NULL, this function acts like l_malloc. * In case @size is 0 then #NULL will be returned. * * Returns: pointer to allocated memory **/ LIB_EXPORT void *l_realloc(void *mem, size_t size) { if (likely(size)) { void *ptr; ptr = realloc(mem, size); if (ptr) return ptr; fprintf(stderr, "%s:%s(): failed to re-allocate %zd bytes\n", STRLOC, __func__, size); abort(); } else l_free(mem); return NULL; } /** * l_memdup: * @mem: pointer to memory you want to duplicate * @size: memory size * * If for any reason the memory allocation fails, then execution will be * halted via abort(). * * In case @size is 0 then #NULL will be returned. * * Returns: pointer to duplicated memory buffer **/ LIB_EXPORT void *l_memdup(const void *mem, size_t size) { void *ptr; ptr = l_malloc(size); memcpy(ptr, mem, size); return ptr; } /** * l_free: * @ptr: memory pointer * * Free the allocated memory area. **/ LIB_EXPORT void l_free(void *ptr) { free(ptr); } /** * l_strdup: * @str: string pointer * * Allocates and duplicates string * * Returns: a newly allocated string **/ LIB_EXPORT char *l_strdup(const char *str) { if (likely(str)) { char *tmp; tmp = strdup(str); if (tmp) return tmp; fprintf(stderr, "%s:%s(): failed to allocate string\n", STRLOC, __func__); abort(); } return NULL; } /** * l_strndup: * @str: string pointer * @max: Maximum number of characters to copy * * Allocates and duplicates string. If the string is longer than @max * characters, only @max are copied and a null terminating character * is added. * * Returns: a newly allocated string **/ LIB_EXPORT char *l_strndup(const char *str, size_t max) { if (likely(str)) { char *tmp; tmp = strndup(str, max); if (tmp) return tmp; fprintf(stderr, "%s:%s(): failed to allocate string\n", STRLOC, __func__); abort(); } return NULL; } /** * l_strdup_printf: * @format: string format * @...: parameters to insert into format string * * Returns: a newly allocated string **/ LIB_EXPORT char *l_strdup_printf(const char *format, ...) { va_list args; char *str; int len; va_start(args, format); len = vasprintf(&str, format, args); va_end(args); if (len < 0) { fprintf(stderr, "%s:%s(): failed to allocate string\n", STRLOC, __func__); abort(); return NULL; } return str; } /** * l_strdup_vprintf: * @format: string format * @args: parameters to insert into format string * * Returns: a newly allocated string **/ LIB_EXPORT char *l_strdup_vprintf(const char *format, va_list args) { char *str; int len; len = vasprintf(&str, format, args); if (len < 0) { fprintf(stderr, "%s:%s(): failed to allocate string\n", STRLOC, __func__); abort(); return NULL; } return str; } /** * l_strlcpy: * @dst: Destination buffer for string * @src: Source buffer containing null-terminated string to copy * @len: Maximum destination buffer space to use * * Copies a string from the @src buffer to the @dst buffer, using no * more than @len bytes in @dst. @dst is guaranteed to be * null-terminated. The caller can determine if the copy was truncated by * checking if the return value is greater than or equal to @len. * * NOTE: Passing in a NULL string results in a no-op * * Returns: The length of the @src string, not including the null * terminator. */ LIB_EXPORT size_t l_strlcpy(char *dst, const char *src, size_t len) { size_t src_len = src ? strlen(src) : 0; if (!src) goto done; if (len) { if (src_len < len) { len = src_len + 1; } else { len -= 1; dst[len] = '\0'; } memcpy(dst, src, len); } done: return src_len; } /** * l_str_has_prefix: * @str: A string to be examined * @delim: Prefix string * * Determines if the string given by @str is prefixed by string given by * @prefix. * * Returns: True if @str was prefixed by @prefix. False otherwise. */ LIB_EXPORT bool l_str_has_prefix(const char *str, const char *prefix) { size_t str_len; size_t prefix_len; if (unlikely(!str)) return false; if (unlikely(!prefix)) return false; str_len = strlen(str); prefix_len = strlen(prefix); if (str_len < prefix_len) return false; return !strncmp(str, prefix, prefix_len); } /** * l_str_has_suffix: * @str: A string to be examined * @suffix: Suffix string * * Determines if the string given by @str ends with the specified @suffix. * * Returns: True if @str ends with the specified @suffix. False otherwise. */ LIB_EXPORT bool l_str_has_suffix(const char *str, const char *suffix) { size_t str_len; size_t suffix_len; size_t len_diff; if (unlikely(!str)) return false; if (unlikely(!suffix)) return false; str_len = strlen(str); suffix_len = strlen(suffix); if (str_len < suffix_len) return false; len_diff = str_len - suffix_len; return !strcmp(&str[len_diff], suffix); } /** * l_streq0: * @a: First operand * @b: Second operand * * Returns: True if @a and @b are both NULL or both non-NULL and identical * according to strcmp. False otherwise. */ LIB_EXPORT bool l_streq0(const char *a, const char *b) { return a == b || (a && b && !strcmp(a, b)); } /** * l_util_oidstring: * @buf: buffer pointer * @len: length of buffer * * Returns: a newly allocated string for OID in numeric representation. * In case of error, Null is returned. **/ LIB_EXPORT char *l_util_oidstring(const void *buf, size_t len) { const unsigned char *ptr = buf; char *str; size_t str_len; size_t str_max; size_t i; if (unlikely(!buf) || unlikely(len < 2)) return NULL; /* A very simple initial estimation */ str_max = 4 + (len - 1) * 2; str = l_malloc(str_max); str_len = snprintf(str, str_max, "%u.%u", ptr[0] / 40, ptr[0] % 40); /* In the unlikely case the initial buffer is too small */ if (unlikely(str_len >= str_max)) { str_max = str_len + 1; str = l_realloc(str, str_max); str_len = sprintf(str, "%u.%u", ptr[0] / 40, ptr[0] % 40); } i = 1; while (i < len) { unsigned long value = 0; unsigned char byte; size_t have_space; size_t l; do { /* * Shift the current value by 7 bits to make room * for the lower 7 bits of the byte in the stream. */ byte = ptr[i++]; value = (value << 7) | (byte & 0x7F); /* This is a malformed OID */ if (i > len) { l_free(str); return NULL; } } while (byte & 0x80); have_space = str_max - str_len; l = snprintf(str + str_len, have_space, ".%lu", value); if (l >= have_space) { str_max = str_len + l + 1; str = l_realloc(str, str_max); l = sprintf(str + str_len, ".%lu", value); } str_len += l; } return str; } static char *hexstring_common(const unsigned char *buf, size_t len, const char hexdigits[static 16]) { char *str; size_t i; if (unlikely(!buf) || unlikely(!len)) return NULL; str = l_malloc(len * 2 + 1); for (i = 0; i < len; i++) { str[(i * 2) + 0] = hexdigits[buf[i] >> 4]; str[(i * 2) + 1] = hexdigits[buf[i] & 0xf]; } str[len * 2] = '\0'; return str; } static char *hexstringv_common(const struct iovec *iov, size_t n_iov, const char hexdigits[static 16]) { char *str; size_t i, j, c; size_t len; if (unlikely(!iov || !n_iov)) return NULL; for (i = 0, len = 0; i < n_iov; i++) len += iov[i].iov_len; str = l_malloc(len * 2 + 1); c = 0; for (i = 0; i < n_iov; i++) { const uint8_t *buf = iov[i].iov_base; for (j = 0; j < iov[i].iov_len; j++) { str[c++] = hexdigits[buf[j] >> 4]; str[c++] = hexdigits[buf[j] & 0xf]; } } str[len * 2] = '\0'; return str; } /** * l_util_hexstring: * @buf: buffer pointer * @len: length of buffer * * Returns: a newly allocated hex string. Note that the string will contain * lower case hex digits a-f. If you require upper case hex digits, use * @l_util_hexstring_upper **/ LIB_EXPORT char *l_util_hexstring(const void *buf, size_t len) { static const char hexdigits[] = "0123456789abcdef"; return hexstring_common(buf, len, hexdigits); } /** * l_util_hexstring_upper: * @buf: buffer pointer * @len: length of buffer * * Returns: a newly allocated hex string. Note that the string will contain * upper case hex digits a-f. If you require lower case hex digits, use * @l_util_hexstring **/ LIB_EXPORT char *l_util_hexstring_upper(const void *buf, size_t len) { static const char hexdigits[] = "0123456789ABCDEF"; return hexstring_common(buf, len, hexdigits); } /** * l_util_hexstringv: * @iov: iovec * @n_iov: length of the iovec * * Returns: a newly allocated hex string. Note that the string will contain * lower case hex digits a-f. If you require upper case hex digits, use * @l_util_hexstringv_upper **/ LIB_EXPORT char *l_util_hexstringv(const struct iovec *iov, size_t n_iov) { static const char hexdigits[] = "0123456789abcdef"; return hexstringv_common(iov, n_iov, hexdigits); } /** * l_util_hexstringv_upper: * @iov: iovec * @n_iov: length of the iovec * * Returns: a newly allocated hex string. Note that the string will contain * upper case hex digits a-f. If you require lower case hex digits, use * @l_util_hexstringv **/ LIB_EXPORT char *l_util_hexstringv_upper(const struct iovec *iov, size_t n_iov) { static const char hexdigits[] = "0123456789ABCDEF"; return hexstringv_common(iov, n_iov, hexdigits); } /** * l_util_from_hexstring: * @str: Null-terminated string containing the hex-encoded bytes * @out_len: Number of bytes decoded * * Returns: a newly allocated byte array. Empty strings are treated as * an error condition. **/ LIB_EXPORT unsigned char *l_util_from_hexstring(const char *str, size_t *out_len) { size_t i, j; size_t len; char c; unsigned char *buf; if (unlikely(!str)) return NULL; for (i = 0; str[i]; i++) { c = str[i]; if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) continue; return NULL; } if (!i) return NULL; if ((i % 2) != 0) return NULL; len = i; buf = l_malloc(i >> 1); for (i = 0, j = 0; i < len; i++, j++) { c = str[i]; if (c >= '0' && c <= '9') buf[j] = c - '0'; else if (c >= 'A' && c <= 'F') buf[j] = 10 + c - 'A'; else if (c >= 'a' && c <= 'f') buf[j] = 10 + c - 'a'; i += 1; c = str[i]; if (c >= '0' && c <= '9') buf[j] = buf[j] * 16 + c - '0'; else if (c >= 'A' && c <= 'F') buf[j] = buf[j] * 16 + 10 + c - 'A'; else if (c >= 'a' && c <= 'f') buf[j] = buf[j] * 16 + 10 + c - 'a'; } if (out_len) *out_len = j; return buf; } static void hexdump(const char dir, const unsigned char *buf, size_t len, l_util_hexdump_func_t function, void *user_data) { static const char hexdigits[] = "0123456789abcdef"; char str[68]; size_t i; if (unlikely(!len)) return; str[0] = dir; for (i = 0; i < len; i++) { str[((i % 16) * 3) + 1] = ' '; str[((i % 16) * 3) + 2] = hexdigits[buf[i] >> 4]; str[((i % 16) * 3) + 3] = hexdigits[buf[i] & 0xf]; str[(i % 16) + 51] = l_ascii_isprint(buf[i]) ? buf[i] : '.'; if ((i + 1) % 16 == 0) { str[49] = ' '; str[50] = ' '; str[67] = '\0'; function(str, user_data); str[0] = ' '; } } if (i % 16 > 0) { size_t j; for (j = (i % 16); j < 16; j++) { str[(j * 3) + 1] = ' '; str[(j * 3) + 2] = ' '; str[(j * 3) + 3] = ' '; str[j + 51] = ' '; } str[49] = ' '; str[50] = ' '; str[67] = '\0'; function(str, user_data); } } LIB_EXPORT void l_util_hexdump(bool in, const void *buf, size_t len, l_util_hexdump_func_t function, void *user_data) { if (likely(!function)) return; hexdump(in ? '<' : '>', buf, len, function, user_data); } LIB_EXPORT void l_util_hexdump_two(bool in, const void *buf1, size_t len1, const void *buf2, size_t len2, l_util_hexdump_func_t function, void *user_data) { if (likely(!function)) return; hexdump(in ? '<' : '>', buf1, len1, function, user_data); hexdump(' ', buf2, len2, function, user_data); } LIB_EXPORT void l_util_hexdumpv(bool in, const struct iovec *iov, size_t n_iov, l_util_hexdump_func_t function, void *user_data) { static const char hexdigits[] = "0123456789abcdef"; char str[68]; size_t i; size_t len; size_t c; const uint8_t *buf; if (likely(!function)) return; if (unlikely(!iov || !n_iov)) return; str[0] = in ? '<' : '>'; for (i = 0, len = 0; i < n_iov; i++) len += iov[i].iov_len; c = 0; buf = iov[0].iov_base; for (i = 0; i < len; i++, c++) { if (c == iov[0].iov_len) { c = 0; iov += 1; buf = iov[0].iov_base; } str[((i % 16) * 3) + 1] = ' '; str[((i % 16) * 3) + 2] = hexdigits[buf[c] >> 4]; str[((i % 16) * 3) + 3] = hexdigits[buf[c] & 0xf]; str[(i % 16) + 51] = l_ascii_isprint(buf[c]) ? buf[c] : '.'; if ((i + 1) % 16 == 0) { str[49] = ' '; str[50] = ' '; str[67] = '\0'; function(str, user_data); str[0] = ' '; } } if (i % 16 > 0) { size_t j; for (j = (i % 16); j < 16; j++) { str[(j * 3) + 1] = ' '; str[(j * 3) + 2] = ' '; str[(j * 3) + 3] = ' '; str[j + 51] = ' '; } str[49] = ' '; str[50] = ' '; str[67] = '\0'; function(str, user_data); } } LIB_EXPORT void l_util_debug(l_util_hexdump_func_t function, void *user_data, const char *format, ...) { va_list args; char *str; int len; if (likely(!function)) return; if (unlikely(!format)) return; va_start(args, format); len = vasprintf(&str, format, args); va_end(args); if (unlikely(len < 0)) return; function(str, user_data); free(str); } /** * l_util_get_debugfs_path: * * Returns: a pointer to mount point of debugfs **/ LIB_EXPORT const char *l_util_get_debugfs_path(void) { static char path[PATH_MAX + 1]; static bool found = false; char type[100]; FILE *fp; if (found) return path; fp = fopen("/proc/mounts", "r"); if (!fp) return NULL; while (fscanf(fp, "%*s %" L_STRINGIFY(PATH_MAX) "s %99s %*s %*d %*d\n", path, type) == 2) { if (!strcmp(type, "debugfs")) { found = true; break; } } fclose(fp); if (!found) return NULL; return path; } LIB_EXPORT bool l_memeq(const void *field, size_t size, uint8_t byte) { const uint8_t *mem = field; size_t i; for (i = 0; i < size; i++) if (mem[i] != byte) return false; return true; } __attribute__((noinline)) static int __secure_memeq(const void *field, size_t size, uint8_t byte) { unsigned int diff = 0; size_t i; for (i = 0; i < size; i++) { diff |= ((uint8_t *) field)[i] ^ byte; DO_NOT_OPTIMIZE(diff); } return diff; } LIB_EXPORT bool l_secure_memeq(const void *field, size_t size, uint8_t byte) { return __secure_memeq(field, size, byte) == 0 ? true : false; } static int safe_atou(const char *s, int base, unsigned int *out_u) { unsigned long int r; unsigned int t; char *endp; errno = 0; t = r = strtoul(s, &endp, base); if (unlikely(errno > 0)) return -errno; if (endp == s || *endp != '\0') return -EINVAL; if (unlikely(r != t)) return -ERANGE; if (out_u) *out_u = t; return 0; } LIB_EXPORT int l_safe_atou32(const char *s, uint32_t *out_u) { if (unlikely(!s)) return -EINVAL; if (!l_ascii_isdigit(s[0])) return -EINVAL; /* Don't allow leading zeros */ if (s[0] == '0' && s[1] != '\0') return -EINVAL; return safe_atou(s, 10, out_u); } LIB_EXPORT int l_safe_atox8(const char *s, uint8_t *out_x) { uint32_t x; int r; r = l_safe_atox32(s, &x); if (r < 0) return r; if (x > UINT8_MAX) return -ERANGE; if (out_x) *out_x = x; return 0; } LIB_EXPORT int l_safe_atox16(const char *s, uint16_t *out_x) { uint32_t x; int r; r = l_safe_atox32(s, &x); if (r < 0) return r; if (x > UINT16_MAX) return -ERANGE; if (out_x) *out_x = x; return 0; } LIB_EXPORT int l_safe_atox32(const char *s, uint32_t *out_x) { if (unlikely(!s)) return -EINVAL; if (!l_ascii_isxdigit(s[0])) return -EINVAL; return safe_atou(s, 16, out_x); } LIB_EXPORT size_t l_util_pagesize(void) { static size_t page_size = 0; if (likely(page_size > 0)) return page_size; page_size = sysconf(_SC_PAGESIZE); return page_size; } bluez-5.87/ell/PaxHeaders/ecc.c0000644000000000000000000000005015023043151013317 xustar0020 atime=1783076329 20 ctime=1783094099 bluez-5.87/ell/ecc.c0000644000000000000000000007245415023043151013014 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2018 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "ecc.h" #include "ecc-private.h" #include "random.h" #include "useful.h" #include "private.h" #include "missing.h" /* * RFC 5114 - Section 2.4 192-bit Random ECP Group */ #define P192_CURVE_P { 0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFFFFFFFFFEull, \ 0xFFFFFFFFFFFFFFFFull } #define P192_CURVE_GX { 0xF4FF0AFD82FF1012ull, 0x7CBF20EB43A18800ull, \ 0x188DA80EB03090F6ull } #define P192_CURVE_GY { 0x73F977A11E794811ull, 0x631011ED6B24CDD5ull, \ 0x07192B95FFC8DA78ull } #define P192_CURVE_N { 0x146BC9B1B4D22831ull, 0xFFFFFFFF99DEF836ull, \ 0xFFFFFFFFFFFFFFFFull } #define P192_CURVE_B { 0xFEB8DEECC146B9B1ull, 0x0FA7E9AB72243049ull, \ 0x64210519E59C80E7ull } static const struct l_ecc_curve p192 = { .name = "secp192r1", .ike_group = 25, .tls_group = 19, .ndigits = 3, .g = { .x = P192_CURVE_GX, .y = P192_CURVE_GY, .curve = &p192 }, .p = P192_CURVE_P, .n = P192_CURVE_N, .b = P192_CURVE_B, }; /* * RFC 5114 - Section 2.5 224-bit Random ECP Group */ #define P224_CURVE_P { 0x0000000000000001ull, 0xFFFFFFFF00000000ull, \ 0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFFull } #define P224_CURVE_GX { 0x343280D6115C1D21ull, 0x4A03C1D356C21122ull, \ 0x6BB4BF7F321390B9ull, 0xB70E0CBDull } #define P224_CURVE_GY { 0x44D5819985007E34ull, 0xCD4375A05A074764ull, \ 0xB5F723FB4C22DFE6ull, 0xBD376388ull } #define P224_CURVE_N { 0x13DD29455C5C2A3Dull, 0xFFFF16A2E0B8F03Eull, \ 0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFFull } #define P224_CURVE_B { 0x270B39432355FFB4ull, 0x5044B0B7D7BFD8BAull, \ 0x0C04B3ABF5413256ull, 0xB4050A85ull } static const struct l_ecc_curve p224 = { .name = "secp224r1", .ike_group = 26, .tls_group = 21, .ndigits = 4, .g = { .x = P224_CURVE_GX, .y = P224_CURVE_GY, .curve = &p224 }, .p = P224_CURVE_P, .n = P224_CURVE_N, .b = P224_CURVE_B, }; /* * RFC 5114 - Section 2.6 256-bit Random ECP Group */ #define P256_CURVE_P { 0xFFFFFFFFFFFFFFFFull, 0x00000000FFFFFFFFull, \ 0x0000000000000000ull, 0xFFFFFFFF00000001ull } #define P256_CURVE_GX { 0xF4A13945D898C296ull, 0x77037D812DEB33A0ull, \ 0xF8BCE6E563A440F2ull, 0x6B17D1F2E12C4247ull } #define P256_CURVE_GY { 0xCBB6406837BF51F5ull, 0x2BCE33576B315ECEull, \ 0x8EE7EB4A7C0F9E16ull, 0x4FE342E2FE1A7F9Bull } #define P256_CURVE_N { 0xF3B9CAC2FC632551ull, 0xBCE6FAADA7179E84ull, \ 0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFF00000000ull } #define P256_CURVE_B { 0x3BCE3C3E27D2604Bull, 0x651D06B0CC53B0F6ull, \ 0xB3EBBD55769886BCull, 0x5AC635D8AA3A93E7ull } static const struct l_ecc_curve p256 = { .name = "secp256r1", .ike_group = 19, .tls_group = 23, .ndigits = 4, .g = { .x = P256_CURVE_GX, .y = P256_CURVE_GY, .curve = &p256 }, .p = P256_CURVE_P, .n = P256_CURVE_N, .b = P256_CURVE_B, .z = -10, }; /* * RFC 5114 - Section 2.7 384-bit Random ECP Group */ #define P384_CURVE_P { 0x00000000FFFFFFFFull, 0xFFFFFFFF00000000ull, \ 0xFFFFFFFFFFFFFFFEull, 0xFFFFFFFFFFFFFFFFull, \ 0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFFFFFFFFFFull } #define P384_CURVE_GX { 0x3A545E3872760AB7ull, 0x5502F25DBF55296Cull, \ 0x59F741E082542A38ull, 0x6E1D3B628BA79B98ull, \ 0x8EB1C71EF320AD74ull, 0xAA87CA22BE8B0537ull } #define P384_CURVE_GY { 0x7A431D7C90EA0E5Full, 0x0A60B1CE1D7E819Dull, \ 0xE9DA3113B5F0B8C0ull, 0xF8F41DBD289A147Cull, \ 0x5D9E98BF9292DC29ull, 0x3617DE4A96262C6Full } #define P384_CURVE_N { 0xECEC196ACCC52973ull, 0x581A0DB248B0A77Aull, \ 0xC7634D81F4372DDFull, 0xFFFFFFFFFFFFFFFFull, \ 0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFFFFFFFFFFull } #define P384_CURVE_B { 0x2A85C8EDD3EC2AEFull, 0xC656398D8A2ED19Dull, \ 0x0314088F5013875Aull, 0x181D9C6EFE814112ull, \ 0x988E056BE3F82D19ull, 0xB3312FA7E23EE7E4ull } static const struct l_ecc_curve p384 = { .name = "secp384r1", .ike_group = 20, .tls_group = 24, .ndigits = 6, .g = { .x = P384_CURVE_GX, .y = P384_CURVE_GY, .curve = &p384 }, .p = P384_CURVE_P, .n = P384_CURVE_N, .b = P384_CURVE_B, .z = -12, }; /* * RFC 5114 - Section 2.8 521-bit Random ECP Group */ #define P521_CURVE_P { 0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFFFFFFFFFFull, \ 0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFFFFFFFFFFull, \ 0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFFFFFFFFFFull, \ 0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFFFFFFFFFFull, \ 0x000001FFull } #define P521_CURVE_GX { 0xF97E7E31C2E5BD66ull, 0x3348B3C1856A429Bull, \ 0xFE1DC127A2FFA8DEull, 0xA14B5E77EFE75928ull, \ 0xF828AF606B4D3DBAull, 0x9C648139053FB521ull, \ 0x9E3ECB662395B442ull, 0x858E06B70404E9CDull, \ 0x000000C6ull } #define P521_CURVE_GY { 0x88BE94769FD16650ull, 0x353C7086A272C240ull, \ 0xC550B9013FAD0761ull, 0x97EE72995EF42640ull, \ 0x17AFBD17273E662Cull, 0x98F54449579B4468ull, \ 0x5C8A5FB42C7D1BD9ull, 0x39296A789A3BC004ull, \ 0x00000118ull } #define P521_CURVE_N { 0xBB6FB71E91386409ull, 0x3BB5C9B8899C47AEull, \ 0x7FCC0148F709A5D0ull, 0x51868783BF2F966Bull, \ 0xFFFFFFFFFFFFFFFAull, 0xFFFFFFFFFFFFFFFFull, \ 0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFFFFFFFFFFull, \ 0x000001FFull } #define P521_CURVE_B { 0xEF451FD46B503F00ull, 0x3573DF883D2C34F1ull, \ 0x1652C0BD3BB1BF07ull, 0x56193951EC7E937Bull, \ 0xB8B489918EF109E1ull, 0xA2DA725B99B315F3ull, \ 0x929A21A0B68540EEull, 0x953EB9618E1C9A1Full, \ 0x00000051ull } static const struct l_ecc_curve p521 = { .name = "secp521r1", .ike_group = 21, .tls_group = 25, .ndigits = 9, .g = { .x = P521_CURVE_GX, .y = P521_CURVE_GY, .curve = &p521 }, .p = P521_CURVE_P, .n = P521_CURVE_N, .b = P521_CURVE_B, }; static const struct l_ecc_curve *curves[] = { &p384, &p256, &p521, &p224, &p192, }; /* Returns supported IKE groups, sorted by the highest effective key size */ LIB_EXPORT const unsigned int *l_ecc_supported_ike_groups(void) { static unsigned int supported_ike_groups[L_ARRAY_SIZE(curves) + 1]; static bool ike_first = true; if (ike_first) { unsigned int i; for (i = 0; i < L_ARRAY_SIZE(curves); i++) supported_ike_groups[i] = curves[i]->ike_group; supported_ike_groups[i] = 0; ike_first = false; } return supported_ike_groups; } /* Returns supported TLS groups, sorted by the highest effective key size */ LIB_EXPORT const unsigned int *l_ecc_supported_tls_groups(void) { static unsigned int supported_tls_groups[L_ARRAY_SIZE(curves) + 1]; static bool tls_first = true; if (tls_first) { unsigned int i; for (i = 0; i < L_ARRAY_SIZE(curves); i++) supported_tls_groups[i] = curves[i]->tls_group; supported_tls_groups[i] = 0; tls_first = false; } return supported_tls_groups; } LIB_EXPORT const struct l_ecc_curve *l_ecc_curve_from_name(const char *name) { int i; if (unlikely(!name)) return NULL; for (i = 0; curves[i]; i++) { if (!strcmp(curves[i]->name, name)) return curves[i]; } return NULL; } LIB_EXPORT const struct l_ecc_curve *l_ecc_curve_from_ike_group( unsigned int group) { unsigned int i; for (i = 0; i < L_ARRAY_SIZE(curves); i++) { if (curves[i]->ike_group == group) return curves[i]; } return NULL; } LIB_EXPORT const struct l_ecc_curve *l_ecc_curve_from_tls_group( unsigned int group) { unsigned int i; for (i = 0; i < L_ARRAY_SIZE(curves); i++) { if (curves[i]->tls_group == group) return curves[i]; } return NULL; } LIB_EXPORT const char *l_ecc_curve_get_name(const struct l_ecc_curve *curve) { if (unlikely(!curve)) return NULL; return curve->name; } LIB_EXPORT unsigned int l_ecc_curve_get_ike_group( const struct l_ecc_curve *curve) { if (unlikely(!curve)) return 0; return curve->ike_group; } LIB_EXPORT unsigned int l_ecc_curve_get_tls_group( const struct l_ecc_curve *curve) { if (unlikely(!curve)) return 0; return curve->tls_group; } LIB_EXPORT struct l_ecc_scalar *l_ecc_curve_get_order( const struct l_ecc_curve *curve) { return _ecc_constant_new(curve, curve->n, curve->ndigits * 8); } LIB_EXPORT struct l_ecc_scalar *l_ecc_curve_get_prime( const struct l_ecc_curve *curve) { if (unlikely(!curve)) return NULL; return _ecc_constant_new(curve, curve->p, curve->ndigits * 8); } LIB_EXPORT size_t l_ecc_curve_get_scalar_bytes(const struct l_ecc_curve *curve) { if (unlikely(!curve)) return 0; return curve->ndigits * 8; } static bool ecc_valid_point(struct l_ecc_point *point) { const struct l_ecc_curve *curve = point->curve; uint64_t tmp1[L_ECC_MAX_DIGITS]; uint64_t tmp2[L_ECC_MAX_DIGITS]; uint64_t _3[L_ECC_MAX_DIGITS] = { 3 }; /* -a = 3 */ unsigned int ndigits = curve->ndigits; /* The point at infinity is invalid. */ if (_ecc_point_is_zero(point)) return false; /* x and y must be smaller than p. */ if (_vli_cmp(curve->p, point->x, ndigits) != 1 || _vli_cmp(curve->p, point->y, ndigits) != 1) return false; /* Computes result = y^2. */ _vli_mod_square_fast(tmp1, point->y, curve->p, ndigits); /* Computes result = x^3 + ax + b. result must not overlap x. */ /* r = x^2 */ _vli_mod_square_fast(tmp2, point->x, curve->p, ndigits); /* r = x^2 - 3 */ _vli_mod_sub(tmp2, tmp2, _3, curve->p, ndigits); /* r = x^3 - 3x */ _vli_mod_mult_fast(tmp2, tmp2, point->x, curve->p, ndigits); /* r = x^3 - 3x + b */ _vli_mod_add(tmp2, tmp2, curve->b, curve->p, ndigits); /* Make sure that y^2 == x^3 + ax + b */ return (_vli_cmp(tmp1, tmp2, ndigits) == 0); } void _ecc_be2native(uint64_t *dest, const uint64_t *bytes, unsigned int ndigits) { unsigned int i; uint64_t tmp[2 * L_ECC_MAX_DIGITS]; for (i = 0; i < ndigits; i++) tmp[ndigits - 1 - i] = l_get_be64(&bytes[i]); memcpy(dest, tmp, ndigits * 8); } void _ecc_native2be(uint64_t *dest, const uint64_t *native, unsigned int ndigits) { unsigned int i; uint64_t tmp[L_ECC_MAX_DIGITS]; for (i = 0; i < ndigits; i++) l_put_be64(native[ndigits - 1 - i], &tmp[i]); memcpy(dest, tmp, ndigits * 8); } static void ecc_compute_y_sqr(const struct l_ecc_curve *curve, uint64_t *y_sqr, const uint64_t *x) { uint64_t sum[L_ECC_MAX_DIGITS] = { 0 }; uint64_t tmp[L_ECC_MAX_DIGITS] = { 0 }; uint64_t _3[L_ECC_MAX_DIGITS] = { 3ull }; /* -a = 3 */ /* x^3 */ _vli_mod_square_fast(sum, x, curve->p, curve->ndigits); _vli_mod_mult_fast(sum, sum, x, curve->p, curve->ndigits); /* x^3 - ax */ _vli_mod_mult_fast(tmp, _3, x, curve->p, curve->ndigits); _vli_mod_sub(sum, sum, tmp, curve->p, curve->ndigits); /* x^3 - ax + b */ _vli_mod_add(sum, sum, curve->b, curve->p, curve->ndigits); memcpy(y_sqr, sum, curve->ndigits * 8); } /* * Compute sqrt(y^2) * Since our prime p satisfies p = 3 (mod 4), we can say: * * y = (y^2)^((p + 1) / 4) * * This avoids the need for a square root function. */ static void ecc_compute_sqrt(const struct l_ecc_curve *curve, uint64_t *y, const uint64_t *y_sqr) { uint64_t expo[L_ECC_MAX_DIGITS]; uint64_t one[L_ECC_MAX_DIGITS] = { 1ull }; memcpy(expo, curve->p, curve->ndigits * 8); /* (p + 1) / 4 == (p >> 2) + 1 */ _vli_rshift1(expo, curve->ndigits); _vli_rshift1(expo, curve->ndigits); _vli_mod_add(expo, expo, one, curve->p, curve->ndigits); /* sum ^ ((p + 1) / 4) */ _vli_mod_exp(y, y_sqr, expo, curve->p, curve->ndigits); } bool _ecc_compute_y(const struct l_ecc_curve *curve, uint64_t *y, const uint64_t *x) { uint64_t sum[L_ECC_MAX_DIGITS] = { 0 }; uint64_t check[L_ECC_MAX_DIGITS] = { 0 }; /* y = sqrt(x^3 + ax + b) (mod p) */ ecc_compute_y_sqr(curve, sum, x); ecc_compute_sqrt(curve, y, sum); /* square y to ensure we have a correct value */ _vli_mod_mult_fast(check, y, y, curve->p, curve->ndigits); if (_vli_cmp(check, sum, curve->ndigits) != 0) return false; return true; } /* * IETF - Compact representation of an elliptic curve point: * https://tools.ietf.org/id/draft-jivsov-ecc-compact-00.xml * * "min(y,p-y) can be calculated with the help of the pre-calculated value * p2=(p-1)/2. min(y,p-y) is y if yp, one, curve->p, curve->ndigits); _vli_rshift1(p2, curve->ndigits); } /* * IETF draft-jivsov-ecc-compact-00 Section 4.1 * Encoding and decoding of an elliptic curve point * ... * Decoding: * Given the compact representation of Q, return canonical representation * of Q=(x,y) as follows: * 1. y' = sqrt( x^3 + a*x + b ), where y'>0 * 2. y = min(y',p-y') * 3. Q=(x,y) is the canonical representation of the point */ static bool decode_point(const struct l_ecc_curve *curve, uint64_t *x, struct l_ecc_point *point) { uint64_t y_min[L_ECC_MAX_DIGITS]; uint64_t p2[L_ECC_MAX_DIGITS]; if (!_ecc_compute_y(curve, y_min, (uint64_t *)x)) return false; _ecc_calculate_p2(curve, p2); if (_vli_cmp(y_min, p2, curve->ndigits) >= 0) _vli_mod_sub(point->y, curve->p, y_min, curve->p, curve->ndigits); else memcpy(point->y, y_min, curve->ndigits * 8); memcpy(point->x, x, curve->ndigits * 8); return true; } /* (rx, ry) = (px, py) + (qx, qy) */ void _ecc_point_add(struct l_ecc_point *ret, const struct l_ecc_point *p, const struct l_ecc_point *q, const uint64_t *curve_prime) { /* * s = (py - qy)/(px - qx) * * rx = s^2 - px - qx * ry = s(px - rx) - py */ uint64_t s[L_ECC_MAX_DIGITS]; uint64_t kp1[L_ECC_MAX_DIGITS]; uint64_t kp2[L_ECC_MAX_DIGITS]; uint64_t resx[L_ECC_MAX_DIGITS]; uint64_t resy[L_ECC_MAX_DIGITS]; unsigned int ndigits = p->curve->ndigits; memset(s, 0, ndigits * 8); /* kp1 = py - qy */ _vli_mod_sub(kp1, q->y, p->y, curve_prime, ndigits); /* kp2 = px - qx */ _vli_mod_sub(kp2, q->x, p->x, curve_prime, ndigits); /* s = kp1/kp2 */ _vli_mod_inv(kp2, kp2, curve_prime, ndigits); _vli_mod_mult_fast(s, kp1, kp2, curve_prime, ndigits); /* rx = s^2 - px - qx */ _vli_mod_mult_fast(kp1, s, s, curve_prime, ndigits); _vli_mod_sub(kp1, kp1, p->x, curve_prime, ndigits); _vli_mod_sub(resx, kp1, q->x, curve_prime, ndigits); /* ry = s(px - rx) - py */ _vli_mod_sub(kp1, p->x, resx, curve_prime, ndigits); _vli_mod_mult_fast(kp1, s, kp1, curve_prime, ndigits); _vli_mod_sub(resy, kp1, p->y, curve_prime, ndigits); memcpy(ret->x, resx, ndigits * 8); memcpy(ret->y, resy, ndigits * 8); } /* result = (base ^ exp) % p */ void _vli_mod_exp(uint64_t *result, const uint64_t *base, const uint64_t *exp, const uint64_t *mod, unsigned int ndigits) { unsigned int i; int bit; uint64_t n[L_ECC_MAX_DIGITS]; uint64_t r[L_ECC_MAX_DIGITS] = { 1 }; memcpy(n, base, ndigits * 8); for (i = 0; i < ndigits; i++) { for (bit = 0; bit < 64; bit++) { uint64_t tmp[L_ECC_MAX_DIGITS]; if (exp[i] & (1ull << bit)) { _vli_mod_mult_fast(tmp, r, n, mod, ndigits); memcpy(r, tmp, ndigits * 8); } _vli_mod_mult_fast(tmp, n, n, mod, ndigits); memcpy(n, tmp, ndigits * 8); } } memcpy(result, r, ndigits * 8); } __attribute__((noinline)) static int vli_equal(const uint64_t *a, const uint64_t *b, unsigned int ndigits) { uint64_t diff = 0; unsigned int i; for (i = 0; i < ndigits; i++) { diff |= a[i] ^ b[i]; __asm__ ("" : "=r" (diff) : "0" (diff)); } return (~diff & (diff - 1)) >> 63; } int _vli_legendre(uint64_t *val, const uint64_t *p, unsigned int ndigits) { uint64_t tmp[L_ECC_MAX_DIGITS]; uint64_t exp[L_ECC_MAX_DIGITS]; uint64_t _1[L_ECC_MAX_DIGITS] = { 1ull }; uint64_t _0[L_ECC_MAX_DIGITS] = { 0 }; /* check that val ^ ((p - 1) / 2) == [1, 0 or -1] */ _vli_sub(exp, p, _1, ndigits); _vli_rshift1(exp, ndigits); _vli_mod_exp(tmp, val, exp, p, ndigits); if (_vli_cmp(tmp, _1, ndigits) == 0) return 1; if (_vli_cmp(tmp, _0, ndigits) == 0) return 0; return -1; } bool _vli_is_zero_or_one(const uint64_t *vli, unsigned int ndigits) { uint64_t _1[L_ECC_MAX_DIGITS] = { 1ull }; int ret; ret = secure_select(vli_equal(vli, _1, ndigits), true, false); ret = secure_select(l_secure_memeq(vli, ndigits * 8, 0), true, ret); return ret; } LIB_EXPORT struct l_ecc_point *l_ecc_point_new(const struct l_ecc_curve *curve) { struct l_ecc_point *p = l_new(struct l_ecc_point, 1); p->curve = curve; return p; } LIB_EXPORT struct l_ecc_point *l_ecc_point_from_data( const struct l_ecc_curve *curve, enum l_ecc_point_type type, const void *data, size_t len) { struct l_ecc_point *p; size_t bytes = curve->ndigits * 8; uint64_t tmp[L_ECC_MAX_DIGITS]; bool sub; if (!data) return NULL; /* Verify the data length matches a full point or X coordinate */ if (type == L_ECC_POINT_TYPE_FULL) { if (len != bytes * 2) return NULL; } else if (len != bytes) return NULL; p = l_ecc_point_new(curve); _ecc_be2native(p->x, (void *) data, curve->ndigits); switch (type) { case L_ECC_POINT_TYPE_COMPLIANT: if (!decode_point(curve, p->x, p)) goto failed; break; case L_ECC_POINT_TYPE_COMPRESSED_BIT0: case L_ECC_POINT_TYPE_COMPRESSED_BIT1: if (!_ecc_compute_y(curve, p->y, p->x)) goto failed; /* * This is determining whether or not to subtract the Y * coordinate from P. According to ANSI X9.62 an even Y should * be prefixed with 02 (BIT0) and an odd Y should be prefixed * with 03 (BIT1). If this is not the case, subtract Y from P. * * ANSI X9.62 * 4.3.6 Point-to-Octet-String Conversion * * 2. If the compressed form is used, then do the following: * 2.1. Compute the bit ~Yp . (See Section 4.2.) * 2.2. Assign the value 02 to the single octet PC if ~Yp * is 0, or the value 03 if ~Yp is 1. * 2.3. The result is the octet string PO = PC || X */ sub = secure_select(type == L_ECC_POINT_TYPE_COMPRESSED_BIT0, p->y[0] & 1, !(p->y[0] & 1)); _vli_mod_sub(tmp, curve->p, p->y, curve->p, curve->ndigits); l_secure_select(sub, tmp, p->y, p->y, curve->ndigits * 8); break; case L_ECC_POINT_TYPE_FULL: _ecc_be2native(p->y, (void *) data + bytes, curve->ndigits); if (!ecc_valid_point(p)) goto failed; break; } return p; failed: l_free(p); return NULL; } LIB_EXPORT struct l_ecc_point *l_ecc_point_from_sswu( const struct l_ecc_scalar *u) { const struct l_ecc_curve *curve = u->curve; unsigned int ndigits = curve->ndigits; uint64_t z[L_ECC_MAX_DIGITS] = { abs(curve->z) }; uint64_t _3[L_ECC_MAX_DIGITS] = { 3ull }; /* -a = 3 */ uint64_t u2z[L_ECC_MAX_DIGITS]; uint64_t t1[L_ECC_MAX_DIGITS]; uint64_t t2[L_ECC_MAX_DIGITS]; uint64_t m[L_ECC_MAX_DIGITS]; uint64_t t[L_ECC_MAX_DIGITS]; uint64_t x1l[L_ECC_MAX_DIGITS]; uint64_t x1r[L_ECC_MAX_DIGITS]; uint64_t x1[L_ECC_MAX_DIGITS]; uint64_t gx1[L_ECC_MAX_DIGITS]; uint64_t x2[L_ECC_MAX_DIGITS]; uint64_t gx2[L_ECC_MAX_DIGITS]; /* reuse m/t/x1l,x1r, they are unused by the time x/v/y/p-y is needed */ uint64_t *x = m; uint64_t *v = t; uint64_t *yl = x1l; uint64_t *yr = x1r; bool l; struct l_ecc_point *P; /* * m = (z^2 * u^4 + z * u^2) modulo p * u2z = u^2 * z * t2 = u2z^2 * m = t2 - u2z since for all our curves z is negative */ _vli_mod_square_fast(u2z, u->c, curve->p, ndigits); _vli_mod_mult_fast(u2z, u2z, z, curve->p, ndigits); _vli_mod_square_fast(t2, u2z, curve->p, ndigits); _vli_mod_sub(m, t2, u2z, curve->p, ndigits); /* * l = CEQ(m, 0) * t = inv0(m) where inv0(x) is calculated as x^(p-2) modulo p */ l = l_secure_memeq(m, sizeof(m), 0); memset(t2, 0, sizeof(t2)); t2[0] = 2ull; _vli_mod_sub(t1, curve->p, t2, curve->p, ndigits); _vli_mod_exp(t, m, t1, curve->p, ndigits); /* Calculate: b / z*a, both z and a are negative */ _vli_mod_mult_fast(t1, z, _3, curve->p, ndigits); _vli_mod_inv(t1, t1, curve->p, ndigits); _vli_mod_mult_fast(x1l, curve->b, t1, curve->p, ndigits); /* t = 1 + t */ memset(t2, 0, sizeof(t2)); t2[0] = 1ull; _vli_mod_add(t, t, t2, curve->p, ndigits); /* t1 = 1 / a */ _vli_mod_inv(t1, _3, curve->p, ndigits); /* x1r = b * t1 * t */ _vli_mod_mult_fast(x1r, curve->b, t1, curve->p, ndigits); _vli_mod_mult_fast(x1r, x1r, t, curve->p, ndigits); /* x1 = CSEL(l, (b / (z*a) modulo p), ((-b/a) * (1 + t)) modulo p) */ l_secure_select(l, x1l, x1r, x1, ndigits * 8); /* gx1 = (x1^3 + a*x1 + b) modulo p */ ecc_compute_y_sqr(curve, gx1, x1); /* x2 = (z*u^2*x1) modulo p, z is negative, hence the second op */ _vli_mod_mult_fast(x2, u2z, x1, curve->p, ndigits); _vli_mod_sub(x2, curve->p, x2, curve->p, ndigits); /* gx2 = (x2^3 + a*x2 + b) modulo p */ ecc_compute_y_sqr(curve, gx2, x2); /* * l = gx1 is a quadratic residue modulo p * x is a quadratic residue if x^((p-1)/2) modulo p is zero or one */ _vli_mod_sub(t1, curve->p, t2, curve->p, ndigits); _vli_rshift1(t1, ndigits); _vli_mod_exp(t2, gx1, t1, curve->p, ndigits); l = _vli_is_zero_or_one(t2, ndigits); /* v = CSEL(l, gx1, gx2) */ l_secure_select(l, gx1, gx2, v, ndigits * 8); /* x = CSEL(l, x1, x2) */ l_secure_select(l, x1, x2, x, ndigits * 8); /* y = sqrt(v) */ ecc_compute_sqrt(curve, yl, v); /* l = CEQ(LSB(u), LSB(y)) */ l = !((u->c[0] & 1ull) ^ (yl[0] & 1ull)); /* p - y */ _vli_mod_sub(yr, curve->p, yl, curve->p, ndigits); /* P = CSEL(l, (x,y), (x, p-y)) */ P = l_ecc_point_new(curve); memcpy(P->x, x, ndigits * 8); l_secure_select(l, yl, yr, P->y, ndigits * 8); return P; } LIB_EXPORT struct l_ecc_point *l_ecc_point_clone(const struct l_ecc_point *p) { if (!p) return NULL; return l_memdup(p, sizeof(*p)); } LIB_EXPORT const struct l_ecc_curve *l_ecc_point_get_curve( const struct l_ecc_point *p) { if (!p) return NULL; return p->curve; } LIB_EXPORT ssize_t l_ecc_point_get_x(const struct l_ecc_point *p, void *x, size_t xlen) { if (xlen < p->curve->ndigits * 8) return -EMSGSIZE; _ecc_native2be(x, p->x, p->curve->ndigits); return p->curve->ndigits * 8; } LIB_EXPORT ssize_t l_ecc_point_get_y(const struct l_ecc_point *p, void *y, size_t ylen) { if (ylen < p->curve->ndigits * 8) return -EMSGSIZE; _ecc_native2be(y, p->y, p->curve->ndigits); return p->curve->ndigits * 8; } LIB_EXPORT bool l_ecc_point_y_isodd(const struct l_ecc_point *p) { return p->y[0] & 1; } LIB_EXPORT ssize_t l_ecc_point_get_data(const struct l_ecc_point *p, void *buf, size_t len) { if (len < (p->curve->ndigits * 8) * 2) return -EMSGSIZE; _ecc_native2be(buf, (uint64_t *) p->x, p->curve->ndigits); _ecc_native2be(buf + (p->curve->ndigits * 8), (uint64_t *) p->y, p->curve->ndigits); return (p->curve->ndigits * 8) * 2; } LIB_EXPORT void l_ecc_point_free(struct l_ecc_point *p) { if (unlikely(!p)) return; explicit_bzero(p->x, p->curve->ndigits * 8); explicit_bzero(p->y, p->curve->ndigits * 8); l_free(p); } struct l_ecc_scalar *_ecc_constant_new(const struct l_ecc_curve *curve, const void *buf, size_t len) { struct l_ecc_scalar *c; if (unlikely(!curve)) return NULL; if (buf && len != curve->ndigits * 8) return NULL; c = l_new(struct l_ecc_scalar, 1); c->curve = curve; if (buf) memcpy(c->c, buf, len); return c; } LIB_EXPORT struct l_ecc_scalar *l_ecc_scalar_new( const struct l_ecc_curve *curve, const void *buf, size_t len) { struct l_ecc_scalar *c; c = _ecc_constant_new(curve, NULL, 0); if (!c) return NULL; if (!buf) return c; _ecc_be2native(c->c, buf, curve->ndigits); if (!_vli_is_zero_or_one(c->c, curve->ndigits) && secure_memcmp_64(curve->n, c->c, curve->ndigits) > 0) return c; l_ecc_scalar_free(c); return NULL; } LIB_EXPORT struct l_ecc_scalar *l_ecc_scalar_clone(const struct l_ecc_scalar *s) { if (!s) return NULL; return l_memdup(s, sizeof(*s)); } /* * Build a scalar = value modulo p where p is the prime number for a given * curve. bytes can contain a number with up to 2x number of digits as the * curve. This is used in Hash to Curve calculations. */ LIB_EXPORT struct l_ecc_scalar *l_ecc_scalar_new_modp( const struct l_ecc_curve *curve, const void *bytes, size_t len) { struct l_ecc_scalar *c; uint64_t tmp[2 * L_ECC_MAX_DIGITS]; unsigned int ndigits = len / 8; if (!bytes) return NULL; if (len % 8) return NULL; if (ndigits > curve->ndigits * 2) return NULL; c = _ecc_constant_new(curve, NULL, 0); if (!c) return NULL; memset(tmp, 0, sizeof(tmp)); _ecc_be2native(tmp, bytes, ndigits); _vli_mmod_fast(c->c, tmp, curve->p, curve->ndigits); if (!_vli_is_zero_or_one(c->c, curve->ndigits) && secure_memcmp_64(curve->n, c->c, curve->ndigits) > 0) return c; l_ecc_scalar_free(c); return NULL; } LIB_EXPORT struct l_ecc_scalar *l_ecc_scalar_new_modn( const struct l_ecc_curve *curve, const void *bytes, size_t len) { struct l_ecc_scalar *c; uint64_t tmp[2 * L_ECC_MAX_DIGITS]; unsigned int ndigits = len / 8; if (!bytes) return NULL; if (len % 8) return NULL; if (ndigits > curve->ndigits * 2) return NULL; c = _ecc_constant_new(curve, NULL, 0); if (!c) return NULL; memset(tmp, 0, sizeof(tmp)); _ecc_be2native(tmp, bytes, ndigits); _vli_mmod_slow(c->c, tmp, curve->n, curve->ndigits); if (!_vli_is_zero_or_one(c->c, curve->ndigits) && secure_memcmp_64(curve->n, c->c, curve->ndigits) > 0) return c; l_ecc_scalar_free(c); return NULL; } /* * Takes a buffer of the same size as the curve and scales it to a range * 1..n using value = (value mod (n - 1)) + 1. For the curves we support * this can be done using a subtraction operation due to the size of n */ LIB_EXPORT struct l_ecc_scalar *l_ecc_scalar_new_reduced_1_to_n( const struct l_ecc_curve *curve, const void *buf, size_t len) { uint64_t _1[L_ECC_MAX_DIGITS] = { 1ull }; uint64_t tmp[L_ECC_MAX_DIGITS]; struct l_ecc_scalar *c; if (!buf) return NULL; if (len != curve->ndigits * 8) return NULL; c = _ecc_constant_new(curve, NULL, 0); if (!c) return NULL; _vli_sub(tmp, curve->n, _1, curve->ndigits); _ecc_be2native(c->c, buf, curve->ndigits); if (_vli_cmp(c->c, tmp, curve->ndigits) >= 0) _vli_sub(c->c, c->c, tmp, curve->ndigits); _vli_add(c->c, c->c, _1, curve->ndigits); return c; } #define ECC_RANDOM_MAX_ITERATIONS 20 LIB_EXPORT struct l_ecc_scalar *l_ecc_scalar_new_random( const struct l_ecc_curve *curve) { int iter = 0; uint64_t r[L_ECC_MAX_DIGITS]; while (iter++ < ECC_RANDOM_MAX_ITERATIONS) { if (!l_getrandom(r, curve->ndigits * 8)) continue; if (curve == &p521) r[8] &= 0x1ff; else if (curve == &p224) r[3] &= 0xffffffff; if (_vli_cmp(r, curve->p, curve->ndigits) > 0 || _vli_cmp(r, curve->n, curve->ndigits) > 0 || _vli_is_zero_or_one(r, curve->ndigits)) continue; return _ecc_constant_new(curve, r, curve->ndigits * 8); } /* * In the really unlikely case that ECC_RANDOM_MAX_ITERATIONS * number of times the random data is not a valid scalar, * then just abort. If this happens something is really * wrong with the random source and there is no point to * continue operation. */ fprintf(stderr, "%s:%s(): failed to allocate valid scalar\n", STRLOC, __func__); abort(); return NULL; } LIB_EXPORT ssize_t l_ecc_scalar_get_data(const struct l_ecc_scalar *c, void *buf, size_t len) { if (len < c->curve->ndigits * 8) return -EMSGSIZE; _ecc_native2be(buf, (uint64_t *) c->c, c->curve->ndigits); return c->curve->ndigits * 8; } LIB_EXPORT void l_ecc_scalar_free(struct l_ecc_scalar *c) { if (unlikely(!c)) return; explicit_bzero(c->c, c->curve->ndigits * 8); l_free(c); } LIB_EXPORT bool l_ecc_scalar_add(struct l_ecc_scalar *ret, const struct l_ecc_scalar *a, const struct l_ecc_scalar *b, const struct l_ecc_scalar *mod) { if (unlikely(!ret || !a || !b || !mod)) return false; _vli_mod_add(ret->c, a->c, b->c, mod->c, a->curve->ndigits); return true; } LIB_EXPORT bool l_ecc_point_multiply(struct l_ecc_point *ret, const struct l_ecc_scalar *scalar, const struct l_ecc_point *point) { if (unlikely(!ret || !scalar || !point)) return false; _ecc_point_mult(ret, point, scalar->c, NULL, scalar->curve->p); return true; } LIB_EXPORT bool l_ecc_point_multiply_g(struct l_ecc_point *ret, const struct l_ecc_scalar *scalar) { if (unlikely(!ret || !scalar)) return false; _ecc_point_mult(ret, &scalar->curve->g, scalar->c, NULL, scalar->curve->p); return true; } LIB_EXPORT bool l_ecc_point_add(struct l_ecc_point *ret, const struct l_ecc_point *a, const struct l_ecc_point *b) { if (unlikely(!ret || !a || !b)) return false; _ecc_point_add(ret, a, b, a->curve->p); return true; } LIB_EXPORT bool l_ecc_point_inverse(struct l_ecc_point *p) { if (unlikely(!p)) return false; _vli_mod_sub(p->y, p->curve->p, p->y, p->curve->p, p->curve->ndigits); return true; } LIB_EXPORT bool l_ecc_scalar_multiply(struct l_ecc_scalar *ret, const struct l_ecc_scalar *a, const struct l_ecc_scalar *b) { if (unlikely(!ret || !a || !b)) return false; _vli_mod_mult_fast(ret->c, a->c, b->c, a->curve->p, a->curve->ndigits); return true; } LIB_EXPORT int l_ecc_scalar_legendre(struct l_ecc_scalar *value) { if (unlikely(!value)) return -1; return _vli_legendre(value->c, value->curve->p, value->curve->ndigits); } LIB_EXPORT bool l_ecc_scalar_sum_x(struct l_ecc_scalar *ret, const struct l_ecc_scalar *x) { if (unlikely(!ret || !x)) return false; ecc_compute_y_sqr(x->curve, ret->c, x->c); return true; } LIB_EXPORT bool l_ecc_scalars_are_equal(const struct l_ecc_scalar *a, const struct l_ecc_scalar *b) { if (unlikely(!a || !b)) return false; return (memcmp(a->c, b->c, a->curve->ndigits * 8) == 0); } LIB_EXPORT bool l_ecc_points_are_equal(const struct l_ecc_point *a, const struct l_ecc_point *b) { if (unlikely(!a || !b)) return false; return ((memcmp(a->x, b->x, a->curve->ndigits * 8) == 0) && (memcmp(a->y, b->y, a->curve->ndigits * 8) == 0)); } LIB_EXPORT bool l_ecc_point_is_infinity(const struct l_ecc_point *p) { return _ecc_point_is_zero(p); } bluez-5.87/ell/PaxHeaders/tls-suites.c0000644000000000000000000000005015064530125014710 xustar0020 atime=1783076324 20 ctime=1783094099 bluez-5.87/ell/tls-suites.c0000644000000000000000000011732515064530125014402 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2018 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include "util.h" #include "tls.h" #include "cipher.h" #include "checksum.h" #include "cert.h" #include "tls-private.h" #include "key.h" #include "random.h" #include "ecc.h" #include "ecdh.h" #include "missing.h" enum signature_algorithm { SIGNATURE_ALGORITHM_ANONYMOUS = 0, SIGNATURE_ALGORITHM_RSA = 1, SIGNATURE_ALGORITHM_DSA = 2, SIGNATURE_ALGORITHM_ECDSA = 3, }; static enum handshake_hash_type find_hash_by_id(uint8_t id) { enum handshake_hash_type hash; for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) if (tls_handshake_hash_data[hash].tls_id == id) break; return hash; } /* * Sanitize DigitallySigned struct input, making sure the lengths * are valid and correspond to what we expect. * * Returns: start of the opaque portion */ static const uint8_t *validate_digitally_signed(struct l_tls *tls, const uint8_t *in, size_t in_len, enum signature_algorithm expected_alg, uint16_t *opaque_len) { size_t offset = 2; uint16_t len; if (tls->negotiated_version < L_TLS_V12) offset = 0; if (in_len < offset + 2) goto size_error; len = l_get_be16(in + offset); if (len != in_len - offset - 2) goto size_error; if (tls->negotiated_version >= L_TLS_V12) { if (in[1] != expected_alg) { TLS_DISCONNECT(TLS_ALERT_DECRYPT_ERROR, 0, "Unknown signature algorithm %i", in[1]); return NULL; } } *opaque_len = len; return in + offset + 2; size_error: TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "Signature msg too " "short (%zi) or signature length doesn't match", in_len); return NULL; } static bool tls_rsa_validate_cert_key(struct l_cert *cert) { return l_cert_get_pubkey_type(cert) == L_CERT_KEY_RSA; } static ssize_t tls_rsa_sign(struct l_tls *tls, uint8_t *out, size_t out_len, tls_get_hash_t get_hash, const uint8_t *data, size_t data_len) { ssize_t result = -EMSGSIZE; enum l_checksum_type sign_checksum_type; uint8_t sign_input[HANDSHAKE_HASH_MAX_SIZE + 36]; size_t sign_input_len; uint8_t *ptr = out; if (!tls->priv_key || !tls->priv_key_size) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, TLS_ALERT_BAD_CERT, "No private key loaded"); return -ENOKEY; } if (tls->negotiated_version >= L_TLS_V12) { const struct tls_hash_algorithm *hash_type = &tls_handshake_hash_data[tls->signature_hash]; /* Build the DigitallySigned struct */ if (out_len < 2) /* Is there space for the algorithm IDs */ goto error; get_hash(tls, tls->signature_hash, data, data_len, sign_input, &sign_input_len); sign_checksum_type = hash_type->l_id; *ptr++ = hash_type->tls_id; *ptr++ = 1; /* RSA_sign */ out_len -= 2; } else { get_hash(tls, HANDSHAKE_HASH_MD5, data, data_len, sign_input + 0, NULL); get_hash(tls, HANDSHAKE_HASH_SHA1, data, data_len, sign_input + 16, NULL); sign_checksum_type = L_CHECKSUM_NONE; sign_input_len = 36; } if (out_len < tls->priv_key_size + 2) goto error; l_put_be16(tls->priv_key_size, ptr); result = l_key_sign(tls->priv_key, L_KEY_RSA_PKCS1_V1_5, sign_checksum_type, sign_input, ptr + 2, sign_input_len, tls->priv_key_size); ptr += tls->priv_key_size + 2; if (result == (ssize_t) tls->priv_key_size) return ptr - out; /* Success */ error: TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Signing the hash failed: %s", strerror(-result)); return result; } static bool tls_rsa_verify(struct l_tls *tls, const uint8_t *in, size_t in_len, tls_get_hash_t get_hash, const uint8_t *data, size_t data_len) { enum l_checksum_type sign_checksum_type; uint8_t expected[HANDSHAKE_HASH_MAX_SIZE + 36]; size_t expected_len; const uint8_t *opaque; uint16_t opaque_len; bool success; opaque = validate_digitally_signed(tls, in, in_len, SIGNATURE_ALGORITHM_RSA, &opaque_len); if (!opaque) return false; /* Only the default hash type supported */ if (opaque_len != tls->peer_pubkey_size) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "Signature length %hu not equal %zi", opaque_len, tls->peer_pubkey_size); return false; } if (tls->negotiated_version >= L_TLS_V12) { enum handshake_hash_type hash = find_hash_by_id(in[0]); if (hash == __HANDSHAKE_HASH_COUNT) { TLS_DISCONNECT(TLS_ALERT_DECRYPT_ERROR, 0, "Unknown hash type %i", in[0]); return false; } get_hash(tls, hash, data, data_len, expected, &expected_len); sign_checksum_type = tls_handshake_hash_data[hash].l_id; /* * Note: Next we let the l_key_verify's underlying kernel * operation prepend the OID to the hash to build the * DigestInfo struct. However according to 4.7 we need to * support at least two forms of the signed content in the * verification: * - DigestInfo with NULL AlgorithmIdentifier.parameters, * - DigestInfo with empty AlgorithmIdentifier.parameters, * * while the kernel only understands the former encoding. * Note PKCS#1 versions 2.0 and later section A.2.4 do * mandate NULL AlgorithmIdentifier.parameters. * * Additionally PKCS#1 v1.5 said BER is used in place of DER * for DigestInfo encoding which adds more ambiguity in the * encoding. */ } else { get_hash(tls, HANDSHAKE_HASH_MD5, data, data_len, expected + 0, NULL); get_hash(tls, HANDSHAKE_HASH_SHA1, data, data_len, expected + 16, NULL); expected_len = 36; sign_checksum_type = L_CHECKSUM_NONE; /* * Note: Within the RSA padding for signatures PKCS#1 1.5 * allows the block format to be either 0 or 1, while PKCS#1 * v2.0+ mandates block type 1 making the signatures * unambiguous. TLS 1.0 doesn't additionally specify which * block type is to be used (TLS 1.2 does) meaning that both * PKCS#1 v1.5 types are allowed. The l_key_verify's * underlying kernel implementation only accepts block type * 1. If this ever becomes an issue we'd need to go back to * using L_KEY_RSA_RAW and our own PKCS#1 v1.5 verify logic. */ } success = l_key_verify(tls->peer_pubkey, L_KEY_RSA_PKCS1_V1_5, sign_checksum_type, expected, opaque, expected_len, tls->peer_pubkey_size); if (!success) TLS_DISCONNECT(TLS_ALERT_DECRYPT_ERROR, 0, "Peer signature verification failed"); else TLS_DEBUG("Peer signature verified"); return success; } static struct tls_signature_algorithm tls_rsa_signature = { .id = 1, /* SignatureAlgorithm.rsa */ .validate_cert_key_type = tls_rsa_validate_cert_key, .sign = tls_rsa_sign, .verify = tls_rsa_verify, }; static bool tls_ecdsa_validate_cert_key(struct l_cert *cert) { return l_cert_get_pubkey_type(cert) == L_CERT_KEY_ECC; } static bool tls_ecdsa_verify(struct l_tls *tls, const uint8_t *in, size_t in_len, tls_get_hash_t get_hash, const uint8_t *data, size_t data_len) { /* RFC 8422, Section 5.10: "SHA-1 is used in TLS 1.1 and earlier" */ enum handshake_hash_type hash = HANDSHAKE_HASH_SHA1; enum l_checksum_type sign_checksum_type; const uint8_t *opaque; uint16_t opaque_len; uint8_t expected[HANDSHAKE_HASH_MAX_SIZE]; size_t expected_len; bool success; opaque = validate_digitally_signed(tls, in, in_len, SIGNATURE_ALGORITHM_ECDSA, &opaque_len); if (!opaque) return false; if (tls->negotiated_version >= L_TLS_V12) { hash = find_hash_by_id(in[0]); if (hash == __HANDSHAKE_HASH_COUNT) { TLS_DISCONNECT(TLS_ALERT_DECRYPT_ERROR, 0, "Unknown hash type %i", in[0]); return false; } /* Hash should match the curve, refer to RFC 5480, Section 4 */ switch (tls->peer_pubkey_size) { case 32: if (hash != HANDSHAKE_HASH_SHA256 && hash != HANDSHAKE_HASH_SHA384) goto bad_hash; break; case 48: if (hash != HANDSHAKE_HASH_SHA384) goto bad_hash; break; bad_hash: default: TLS_DISCONNECT(TLS_ALERT_DECRYPT_ERROR, 0, "Invalid hash %i", in[0]); } } get_hash(tls, hash, data, data_len, expected, &expected_len); sign_checksum_type = tls_handshake_hash_data[hash].l_id; success = l_key_verify(tls->peer_pubkey, L_KEY_ECDSA_X962, sign_checksum_type, expected, opaque, expected_len, opaque_len); if (!success) TLS_DISCONNECT(TLS_ALERT_DECRYPT_ERROR, 0, "Peer signature verification failed"); else TLS_DEBUG("Peer signature verified"); return success; } static struct tls_signature_algorithm tls_ecdsa_signature = { .id = 3, /* SignatureAlgorithm.ecdsa */ .validate_cert_key_type = tls_ecdsa_validate_cert_key, .verify = tls_ecdsa_verify, }; static bool tls_send_rsa_client_key_xchg(struct l_tls *tls) { uint8_t buf[1024 + 32]; uint8_t *ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; uint8_t pre_master_secret[48]; ssize_t bytes_encrypted; if (!tls->peer_pubkey) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Peer public key not received"); return false; } /* Must match the version in tls_send_client_hello */ pre_master_secret[0] = (uint8_t) (tls->client_version >> 8); pre_master_secret[1] = (uint8_t) (tls->client_version >> 0); l_getrandom(pre_master_secret + 2, 46); if (tls->peer_pubkey_size + 32 > (int) sizeof(buf)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Peer public key too big: %zi", tls->peer_pubkey_size); return false; } l_put_be16(tls->peer_pubkey_size, ptr); bytes_encrypted = l_key_encrypt(tls->peer_pubkey, L_KEY_RSA_PKCS1_V1_5, L_CHECKSUM_NONE, pre_master_secret, ptr + 2, 48, tls->peer_pubkey_size); ptr += tls->peer_pubkey_size + 2; if (bytes_encrypted != (ssize_t) tls->peer_pubkey_size) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Encrypting PreMasterSecret failed: %s", strerror(-bytes_encrypted)); return false; } tls_tx_handshake(tls, TLS_CLIENT_KEY_EXCHANGE, buf, ptr - buf); tls_generate_master_secret(tls, pre_master_secret, 48); explicit_bzero(pre_master_secret, 48); return true; } static void tls_handle_rsa_client_key_xchg(struct l_tls *tls, const uint8_t *buf, size_t len) { uint8_t pre_master_secret[48], random_secret[46]; ssize_t bytes_decrypted; if (!tls->priv_key || !tls->priv_key_size) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, TLS_ALERT_BAD_CERT, "No private key"); return; } if (len != tls->priv_key_size + 2) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "ClientKeyExchange len %zi not %zi", len, tls->priv_key_size + 2); return; } len = l_get_be16(buf); if (len != tls->priv_key_size) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "EncryptedPreMasterSecret len %zi not %zi", len, tls->priv_key_size); return; } bytes_decrypted = l_key_decrypt(tls->priv_key, L_KEY_RSA_PKCS1_V1_5, L_CHECKSUM_NONE, buf + 2, pre_master_secret, tls->priv_key_size, 48); /* * Assume correct premaster secret client version which according * to the TLS1.2 spec is unlikely in client implementations SSLv3 * and prior. Spec suggests either not supporting them or adding * a configurable override for <= SSLv3 clients. For now we have * no need to support them. * * On any decode error randomise the Pre Master Secret as per the * countermeasures in 7.4.7.1 and don't generate any alerts. */ l_getrandom(random_secret, 46); pre_master_secret[0] = tls->client_version >> 8; pre_master_secret[1] = tls->client_version >> 0; if (bytes_decrypted != 48) { memcpy(pre_master_secret + 2, random_secret, 46); TLS_DEBUG("Error decrypting PreMasterSecret: %s", strerror(-bytes_decrypted)); } tls_generate_master_secret(tls, pre_master_secret, 48); explicit_bzero(pre_master_secret, 48); explicit_bzero(random_secret, 46); } static struct tls_key_exchange_algorithm tls_rsa_key_xchg = { .send_client_key_exchange = tls_send_rsa_client_key_xchg, .handle_client_key_exchange = tls_handle_rsa_client_key_xchg, }; /* Used by both DHE and ECDHE */ static bool tls_get_dh_params_hash(struct l_tls *tls, enum handshake_hash_type type, const uint8_t *data, size_t data_len, uint8_t *out, size_t *out_len) { struct l_checksum *checksum; ssize_t ret; /* * The ServerKeyExchange signature hash input format for RSA_sign is * not really specified in either RFC 8422 or RFC 5246 explicitly * but we use this format by analogy to DHE_RSA which uses RSA_sign * as well. Also matches ecdsa, ed25519 and ed448 formats. */ struct iovec iov[] = { { .iov_base = tls->pending.client_random, .iov_len = 32 }, { .iov_base = tls->pending.server_random, .iov_len = 32 }, { .iov_base = (void *) data, .iov_len = data_len }, }; checksum = l_checksum_new(tls_handshake_hash_data[type].l_id); if (!checksum) return false; l_checksum_updatev(checksum, iov, L_ARRAY_SIZE(iov)); ret = l_checksum_get_digest(checksum, out, HANDSHAKE_HASH_MAX_SIZE); l_checksum_free(checksum); if (ret < 0) return false; if (out_len) *out_len = ret; return true; } struct tls_ecdhe_params { const struct l_ecc_curve *curve; struct l_ecc_scalar *private; struct l_ecc_point *public; }; static void tls_free_ecdhe_params(struct l_tls *tls) { struct tls_ecdhe_params *params = tls->pending.key_xchg_params; if (!params) return; tls->pending.key_xchg_params = NULL; l_ecc_scalar_free(params->private); l_ecc_point_free(params->public); l_free(params); } static size_t tls_write_ecpoint(uint8_t *buf, size_t len, const struct tls_named_group *curve, const struct l_ecc_point *point) { size_t point_bytes; /* RFC 8422, Section 5.4.1 */ point_bytes = l_ecc_point_get_data(point, buf + 2, len - 2); buf[0] = 1 + point_bytes; /* length */ buf[1] = 4; /* form: uncompressed */ return 2 + point_bytes; } static size_t tls_write_server_ecdh_params(struct l_tls *tls, uint8_t *buf, size_t len) { struct tls_ecdhe_params *params = tls->pending.key_xchg_params; /* RFC 8422, Section 5.4 */ buf[0] = 3; /* curve_type: named_curve */ l_put_be16(tls->negotiated_curve->id, buf + 1); return 3 + tls_write_ecpoint(buf + 3, len - 3, tls->negotiated_curve, params->public); } static bool tls_send_ecdhe_server_key_xchg(struct l_tls *tls) { uint8_t buf[1024]; uint8_t *ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; struct tls_ecdhe_params *params; ssize_t sign_len; const uint8_t *server_ecdh_params_ptr; /* * RFC 8422, Section 5.4 * * If we're getting here we can assume that tls->pending.key_xchg_params * is NULL, tls->priv_key is our signing key and tls->negotiated_curve * is non-NULL. */ params = l_new(struct tls_ecdhe_params, 1); params->curve = l_ecc_curve_from_tls_group(tls->negotiated_curve->id); tls->pending.key_xchg_params = params; if (!l_ecdh_generate_key_pair(params->curve, ¶ms->private, ¶ms->public)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Generating ECDH key pair failed"); return false; } server_ecdh_params_ptr = ptr; ptr += tls_write_server_ecdh_params(tls, ptr, buf + sizeof(buf) - ptr); if (tls->pending.cipher_suite->signature) { sign_len = tls->pending.cipher_suite->signature->sign(tls, ptr, buf + sizeof(buf) - ptr, tls_get_dh_params_hash, server_ecdh_params_ptr, ptr - server_ecdh_params_ptr); if (sign_len < 0) return false; ptr += sign_len; } tls_tx_handshake(tls, TLS_SERVER_KEY_EXCHANGE, buf, ptr - buf); return true; } static void tls_handle_ecdhe_server_key_xchg(struct l_tls *tls, const uint8_t *buf, size_t len) { struct tls_ecdhe_params *params; uint16_t namedcurve; const uint8_t *server_ecdh_params_ptr = buf; size_t point_bytes; /* RFC 8422, Section 5.4 */ if (len < 5) goto decode_error; if (*buf != 3) { TLS_DISCONNECT(TLS_ALERT_ILLEGAL_PARAM, 0, "Unsupported (deprecated?) ECCurveType %u", *buf); return; } namedcurve = l_get_be16(buf + 1); buf += 3; len -= 3; tls->negotiated_curve = tls_find_group(namedcurve); if (!tls->negotiated_curve || tls->negotiated_curve->type != TLS_GROUP_TYPE_EC) { TLS_DISCONNECT(TLS_ALERT_ILLEGAL_PARAM, 0, "Unsupported NamedCurve %u", namedcurve); return; } TLS_DEBUG("Negotiated %s", tls->negotiated_curve->name); if (*buf < 1) goto decode_error; point_bytes = *buf++ - 1; if (*buf != 4) { /* uncompressed */ TLS_DISCONNECT(TLS_ALERT_ILLEGAL_PARAM, 0, "Unsupported (deprecated?) PointConversionForm " "%u", *buf); return; } buf++; len -= 2; if (len < point_bytes) goto decode_error; /* * RFC 8422, Section 5.11: "A receiving party MUST check that the * x and y parameters from the peer's public value satisfy the * curve equation, y^2 = x^3 + ax + b mod p." * This happens in l_ecc_point_from_data when the L_ECC_POINT_TYPE_FULL * format is used. */ params = l_new(struct tls_ecdhe_params, 1); params->curve = l_ecc_curve_from_tls_group(tls->negotiated_curve->id); params->public = l_ecc_point_from_data(params->curve, L_ECC_POINT_TYPE_FULL, buf, point_bytes); tls->pending.key_xchg_params = params; buf += point_bytes; len -= point_bytes; if (!params->public || point_bytes != 2 * l_ecc_curve_get_scalar_bytes(params->curve)) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "ServerKeyExchange.params.public decode error"); return; } if (tls->pending.cipher_suite->signature) { if (!tls->pending.cipher_suite->signature->verify(tls, buf, len, tls_get_dh_params_hash, server_ecdh_params_ptr, buf - server_ecdh_params_ptr)) return; } else { if (len) goto decode_error; } TLS_SET_STATE(TLS_HANDSHAKE_WAIT_HELLO_DONE); return; decode_error: TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "ServerKeyExchange decode error"); } static bool tls_send_ecdhe_client_key_xchg(struct l_tls *tls) { uint8_t buf[1024]; uint8_t *ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; uint8_t pre_master_secret[128]; ssize_t pre_master_secret_len; struct tls_ecdhe_params *params = tls->pending.key_xchg_params; struct l_ecc_point *our_public; struct l_ecc_scalar *secret; /* RFC 8422, Section 5.7 */ if (!l_ecdh_generate_key_pair(params->curve, ¶ms->private, &our_public)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Generating ECDH key pair failed"); return false; } ptr += tls_write_ecpoint(ptr, buf + sizeof(buf) - ptr, tls->negotiated_curve, our_public); l_ecc_point_free(our_public); /* * Neither 5.4 or 5.7 "Actions" paragraphs say when the ECDH shared * secret is calculated but we can either do this in * tls_handle_ecdhe_server_key_xchg or here. In both cases we only * need to store the public key in the client's key_xchg_params and * can free all of the params after sending the ClientKeyExchange. * By doing this calculation here we're aligned with RSA and also * with the server mode where the shared secret can only be * calculated after the ClientKeyExchange is received. */ if (!l_ecdh_generate_shared_secret(params->private, params->public, &secret)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Generating ECDH shared-secret failed"); return false; } tls_free_ecdhe_params(tls); pre_master_secret_len = l_ecc_scalar_get_data(secret, pre_master_secret, sizeof(pre_master_secret)); l_ecc_scalar_free(secret); if (pre_master_secret_len < 0) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "l_ecc_scalar_get_data(secret) failed"); return false; } tls_tx_handshake(tls, TLS_CLIENT_KEY_EXCHANGE, buf, ptr - buf); tls_generate_master_secret(tls, pre_master_secret, pre_master_secret_len); explicit_bzero(pre_master_secret, pre_master_secret_len); return true; } static void tls_handle_ecdhe_client_key_xchg(struct l_tls *tls, const uint8_t *buf, size_t len) { struct tls_ecdhe_params *params = tls->pending.key_xchg_params; uint8_t pre_master_secret[128]; ssize_t pre_master_secret_len; struct l_ecc_point *other_public; struct l_ecc_scalar *secret; size_t point_bytes = 2 * l_ecc_curve_get_scalar_bytes(params->curve); /* RFC 8422, Section 5.7 */ if (len < 2) goto decode_error; if (*buf++ != 1 + point_bytes) goto decode_error; if (*buf != 4) { /* uncompressed */ TLS_DISCONNECT(TLS_ALERT_ILLEGAL_PARAM, 0, "Unsupported (deprecated?) PointConversionForm " "%u", *buf); return; } buf++; len -= 2; if (len != point_bytes) goto decode_error; /* * RFC 8422, Section 5.11: "A receiving party MUST check that the * x and y parameters from the peer's public value satisfy the * curve equation, y^2 = x^3 + ax + b mod p." * This happens in l_ecc_point_from_data when the L_ECC_POINT_TYPE_FULL * format is used. */ other_public = l_ecc_point_from_data(params->curve, L_ECC_POINT_TYPE_FULL, buf, len); if (!other_public) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "ClientKeyExchange.exchange_keys.ecdh_Yc " "decode error"); return; } if (!l_ecdh_generate_shared_secret(params->private, other_public, &secret)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Generating ECDH shared-secret failed"); l_ecc_point_free(other_public); return; } tls_free_ecdhe_params(tls); l_ecc_point_free(other_public); pre_master_secret_len = l_ecc_scalar_get_data(secret, pre_master_secret, sizeof(pre_master_secret)); l_ecc_scalar_free(secret); if (pre_master_secret_len < 0) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "l_ecc_scalar_get_data(secret) failed"); return; } tls_generate_master_secret(tls, pre_master_secret, pre_master_secret_len); explicit_bzero(pre_master_secret, pre_master_secret_len); return; decode_error: TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "ClientKeyExchange decode error"); } static struct tls_key_exchange_algorithm tls_ecdhe = { .need_ecc = true, .send_server_key_exchange = tls_send_ecdhe_server_key_xchg, .handle_server_key_exchange = tls_handle_ecdhe_server_key_xchg, .send_client_key_exchange = tls_send_ecdhe_client_key_xchg, .handle_client_key_exchange = tls_handle_ecdhe_client_key_xchg, .free_params = tls_free_ecdhe_params, }; /* Maximum FF DH prime modulus size in bytes */ #define TLS_DHE_MAX_SIZE 1024 struct tls_dhe_params { size_t prime_len; struct l_key *prime; struct l_key *generator; struct l_key *private; struct l_key *public; }; static void tls_free_dhe_params(struct l_tls *tls) { struct tls_dhe_params *params = tls->pending.key_xchg_params; if (!params) return; tls->pending.key_xchg_params = NULL; l_key_free(params->prime); l_key_free(params->generator); l_key_free(params->private); l_key_free(params->public); l_free(params); } static bool tls_send_dhe_server_key_xchg(struct l_tls *tls) { uint8_t buf[1024 + TLS_DHE_MAX_SIZE * 3]; uint8_t *ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; struct tls_dhe_params *params; const uint8_t *prime_buf; uint8_t generator_buf = tls->negotiated_ff_group->ff.generator; uint8_t public_buf[TLS_DHE_MAX_SIZE]; size_t public_len; unsigned int zeros = 0; ssize_t sign_len; const uint8_t *server_dh_params_ptr; params = l_new(struct tls_dhe_params, 1); prime_buf = tls->negotiated_ff_group->ff.prime; params->prime_len = tls->negotiated_ff_group->ff.prime_len; params->prime = l_key_new(L_KEY_RAW, prime_buf, params->prime_len); params->generator = l_key_new(L_KEY_RAW, &generator_buf, 1); if (!params->prime || !params->generator) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "l_key_new failed"); goto free_params; } params->private = l_key_generate_dh_private(prime_buf, params->prime_len); if (!params->private) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "l_key_generate_dh_private failed"); goto free_params; } memset(public_buf, 0, sizeof(public_buf)); public_len = params->prime_len; if (!l_key_compute_dh_public(params->generator, params->private, params->prime, public_buf, &public_len)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "l_key_compute_dh_public failed"); goto free_params; } while (zeros < public_len && public_buf[zeros] == 0x00) zeros++; server_dh_params_ptr = ptr; /* RFC 5246, Section 7.4.3 */ l_put_be16(params->prime_len, ptr); memcpy(ptr + 2, prime_buf, params->prime_len); ptr += 2 + params->prime_len; l_put_be16(1, ptr); memcpy(ptr + 2, &generator_buf, 1); ptr += 2 + 1; l_put_be16(public_len - zeros, ptr); memcpy(ptr + 2, public_buf + zeros, public_len - zeros); ptr += 2 + public_len - zeros; if (tls->pending.cipher_suite->signature) { sign_len = tls->pending.cipher_suite->signature->sign(tls, ptr, buf + sizeof(buf) - ptr, tls_get_dh_params_hash, server_dh_params_ptr, ptr - server_dh_params_ptr); if (sign_len < 0) goto free_params; ptr += sign_len; } tls->pending.key_xchg_params = params; tls_tx_handshake(tls, TLS_SERVER_KEY_EXCHANGE, buf, ptr - buf); return true; free_params: l_key_free(params->prime); l_key_free(params->generator); l_key_free(params->private); l_free(params); return false; } static void tls_handle_dhe_server_key_xchg(struct l_tls *tls, const uint8_t *buf, size_t len) { struct tls_dhe_params *params = NULL; const uint8_t *prime_buf; const uint8_t *generator_buf; size_t generator_len; const uint8_t *public_buf; size_t public_len; const uint8_t *server_dh_params_ptr = buf; if (len < 2) goto decode_error; params = l_new(struct tls_dhe_params, 1); params->prime_len = l_get_be16(buf); if (len < 2 + params->prime_len + 2) goto decode_error; prime_buf = buf + 2; buf += 2 + params->prime_len; len -= 2 + params->prime_len; /* Strip leading zeros for the length checks later */ while (params->prime_len && prime_buf[0] == 0x00) { prime_buf++; params->prime_len--; } generator_len = l_get_be16(buf); if (len < 2 + generator_len + 2) goto decode_error; generator_buf = buf + 2; buf += 2 + generator_len; len -= 2 + generator_len; public_len = l_get_be16(buf); if (len < 2 + public_len) goto decode_error; public_buf = buf + 2; buf += 2 + public_len; len -= 2 + public_len; /* * Validate the values received. Without requiring RFC 7919 from * the server, and there are many servers that don't implement it * yet, we basically have to blindly accept the provided prime value. * We have no way to confirm that it's actually prime or that it's a * "safe prime" or that it forms a group without small sub-groups. * There's also no way to whitelist all valid values. But we do a * basic sanity check and require it to be 1536-bit or longer, the * minimum length required by the Linux kernel for keyctl_dh_compute(). * The generator must also be at least within the min & max interval * for the private/public values. */ if (params->prime_len > TLS_DHE_MAX_SIZE || params->prime_len < 192 || !(prime_buf[params->prime_len - 1] & 1)) { TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, "Server DH prime modulus invalid"); goto free_params; } if (!l_key_validate_dh_payload(generator_buf, generator_len, prime_buf, params->prime_len)) { TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, "Server DH generator value invalid"); goto free_params; } /* * Just output a warning if the server sent group parameters not * offered in our RFC 7919 Supported Groups extension. */ if (!tls_find_ff_group(prime_buf, params->prime_len, generator_buf, generator_len)) TLS_DEBUG("Warning: using server's custom %i-bit FF DH group", (int) (params->prime_len * 8)); /* * RFC 7919 Section 3.0: * "the client MUST verify that dh_Ys is in the range * 1 < dh_Ys < dh_p - 1. If dh_Ys is not in this range, the client * MUST terminate the connection with a fatal handshake_failure(40) * alert." */ if (!l_key_validate_dh_payload(public_buf, public_len, prime_buf, params->prime_len)) { TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, "Server DH public value invalid"); goto free_params; } params->prime = l_key_new(L_KEY_RAW, prime_buf, params->prime_len); params->generator = l_key_new(L_KEY_RAW, generator_buf, generator_len); params->public = l_key_new(L_KEY_RAW, public_buf, public_len); if (!params->prime || !params->generator || !params->public) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "l_key_new failed"); goto free_params; } /* Do this now so we don't need prime_buf in send_client_key_xchg */ params->private = l_key_generate_dh_private(prime_buf, params->prime_len); if (!params->private) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "l_key_generate_dh_private failed"); goto free_params; } tls->pending.key_xchg_params = params; if (tls->pending.cipher_suite->signature) { if (!tls->pending.cipher_suite->signature->verify(tls, buf, len, tls_get_dh_params_hash, server_dh_params_ptr, buf - server_dh_params_ptr)) return; } else { if (len) goto decode_error; } TLS_SET_STATE(TLS_HANDSHAKE_WAIT_HELLO_DONE); return; decode_error: TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "ServerKeyExchange decode error"); free_params: if (params) { l_key_free(params->prime); l_key_free(params->generator); l_key_free(params->public); l_free(params); } } static bool tls_send_dhe_client_key_xchg(struct l_tls *tls) { struct tls_dhe_params *params = tls->pending.key_xchg_params; uint8_t buf[128 + params->prime_len]; uint8_t *ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; uint8_t public_buf[params->prime_len]; size_t public_len; unsigned int zeros = 0; uint8_t pre_master_secret[params->prime_len]; size_t pre_master_secret_len; public_len = params->prime_len; memset(public_buf, 0, sizeof(public_buf)); if (!l_key_compute_dh_public(params->generator, params->private, params->prime, public_buf, &public_len)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "l_key_compute_dh_public failed"); return false; } while (zeros < public_len && public_buf[zeros] == 0x00) zeros++; l_put_be16(public_len - zeros, ptr); memcpy(ptr + 2, public_buf + zeros, public_len - zeros); ptr += 2 + public_len - zeros; pre_master_secret_len = params->prime_len; zeros = 0; if (!l_key_compute_dh_secret(params->public, params->private, params->prime, pre_master_secret, &pre_master_secret_len)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Generating DH shared-secret failed"); return false; } while (zeros < pre_master_secret_len && pre_master_secret[zeros] == 0x00) zeros++; tls_tx_handshake(tls, TLS_CLIENT_KEY_EXCHANGE, buf, ptr - buf); tls_free_dhe_params(tls); tls_generate_master_secret(tls, pre_master_secret + zeros, pre_master_secret_len - zeros); explicit_bzero(pre_master_secret, pre_master_secret_len); return true; } static void tls_handle_dhe_client_key_xchg(struct l_tls *tls, const uint8_t *buf, size_t len) { struct tls_dhe_params *params = tls->pending.key_xchg_params; uint8_t pre_master_secret[params->prime_len]; size_t pre_master_secret_len; size_t public_len; unsigned int zeros = 0; if (len < 2) goto decode_error; public_len = l_get_be16(buf); buf += 2; len -= 2; if (public_len != len) goto decode_error; /* * RFC 7919 Section 4: * "the server MUST verify that 1 < dh_Yc < dh_p - 1. If dh_Yc is * out of range, the server MUST terminate the connection with * a fatal handshake_failure(40) alert." */ if (!l_key_validate_dh_payload(buf, public_len, tls->negotiated_ff_group->ff.prime, params->prime_len)) { TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, "Client DH public value invalid"); return; } params->public = l_key_new(L_KEY_RAW, buf, public_len); if (!params->public) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "l_key_new failed"); return; } pre_master_secret_len = params->prime_len; if (!l_key_compute_dh_secret(params->public, params->private, params->prime, pre_master_secret, &pre_master_secret_len)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Generating DH shared-secret failed"); return; } while (zeros < pre_master_secret_len && pre_master_secret[zeros] == 0x00) zeros++; tls_free_dhe_params(tls); tls_generate_master_secret(tls, pre_master_secret + zeros, pre_master_secret_len - zeros); explicit_bzero(pre_master_secret, pre_master_secret_len); return; decode_error: TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "ClientKeyExchange decode error"); } static struct tls_key_exchange_algorithm tls_dhe = { .need_ecc = true, .send_server_key_exchange = tls_send_dhe_server_key_xchg, .handle_server_key_exchange = tls_handle_dhe_server_key_xchg, .send_client_key_exchange = tls_send_dhe_client_key_xchg, .handle_client_key_exchange = tls_handle_dhe_client_key_xchg, .free_params = tls_free_dhe_params, }; static struct tls_bulk_encryption_algorithm tls_aes128 = { .cipher_type = TLS_CIPHER_BLOCK, .l_id = L_CIPHER_AES_CBC, .key_length = 16, .iv_length = 16, .block_length = 16, }, tls_aes256 = { .cipher_type = TLS_CIPHER_BLOCK, .l_id = L_CIPHER_AES_CBC, .key_length = 32, .iv_length = 16, .block_length = 16, }, tls_3des_ede = { .cipher_type = TLS_CIPHER_BLOCK, .l_id = L_CIPHER_DES3_EDE_CBC, .key_length = 24, .iv_length = 8, .block_length = 8, }, tls_aes128_gcm = { .cipher_type = TLS_CIPHER_AEAD, .l_aead_id = L_AEAD_CIPHER_AES_GCM, .key_length = 16, .iv_length = 12, .fixed_iv_length = 4, .auth_tag_length = 16, }, tls_aes256_gcm = { .cipher_type = TLS_CIPHER_AEAD, .l_aead_id = L_AEAD_CIPHER_AES_GCM, .key_length = 32, .iv_length = 12, .fixed_iv_length = 4, .auth_tag_length = 16, }; static struct tls_mac_algorithm tls_sha = { .id = 2, .hmac_type = L_CHECKSUM_SHA1, .mac_length = 20, }, tls_sha256 = { .id = 4, .hmac_type = L_CHECKSUM_SHA256, .mac_length = 32, }, tls_sha384 = { .id = 5, .hmac_type = L_CHECKSUM_SHA384, .mac_length = 48, }; static struct tls_cipher_suite tls_rsa_with_3des_ede_cbc_sha = { .id = { 0x00, 0x0a }, .name = "TLS_RSA_WITH_3DES_EDE_CBC_SHA", .encryption = &tls_3des_ede, .mac = &tls_sha, .signature = &tls_rsa_signature, .key_xchg = &tls_rsa_key_xchg, }, tls_dhe_rsa_with_3des_ede_cbc_sha = { .id = { 0x00, 0x16 }, .name = "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", .encryption = &tls_3des_ede, .mac = &tls_sha, .signature = &tls_rsa_signature, .key_xchg = &tls_dhe, }, tls_rsa_with_aes_128_cbc_sha = { .id = { 0x00, 0x2f }, .name = "TLS_RSA_WITH_AES_128_CBC_SHA", .encryption = &tls_aes128, .mac = &tls_sha, .signature = &tls_rsa_signature, .key_xchg = &tls_rsa_key_xchg, }, tls_dhe_rsa_with_aes_128_cbc_sha = { .id = { 0x00, 0x33 }, .name = "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", .encryption = &tls_aes128, .mac = &tls_sha, .signature = &tls_rsa_signature, .key_xchg = &tls_dhe, }, tls_rsa_with_aes_256_cbc_sha = { .id = { 0x00, 0x35 }, .name = "TLS_RSA_WITH_AES_256_CBC_SHA", .encryption = &tls_aes256, .mac = &tls_sha, .signature = &tls_rsa_signature, .key_xchg = &tls_rsa_key_xchg, }, tls_dhe_rsa_with_aes_256_cbc_sha = { .id = { 0x00, 0x39 }, .name = "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", .encryption = &tls_aes256, .mac = &tls_sha, .signature = &tls_rsa_signature, .key_xchg = &tls_dhe, }, tls_rsa_with_aes_128_cbc_sha256 = { .id = { 0x00, 0x3c }, .name = "TLS_RSA_WITH_AES_128_CBC_SHA256", .encryption = &tls_aes128, .mac = &tls_sha256, .signature = &tls_rsa_signature, .key_xchg = &tls_rsa_key_xchg, }, tls_rsa_with_aes_256_cbc_sha256 = { .id = { 0x00, 0x3d }, .name = "TLS_RSA_WITH_AES_256_CBC_SHA256", .encryption = &tls_aes256, .mac = &tls_sha256, .signature = &tls_rsa_signature, .key_xchg = &tls_rsa_key_xchg, }, tls_dhe_rsa_with_aes_128_cbc_sha256 = { .id = { 0x00, 0x67 }, .name = "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", .encryption = &tls_aes128, .mac = &tls_sha256, .signature = &tls_rsa_signature, .key_xchg = &tls_dhe, }, tls_dhe_rsa_with_aes_256_cbc_sha256 = { .id = { 0x00, 0x6b }, .name = "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", .encryption = &tls_aes256, .mac = &tls_sha256, .signature = &tls_rsa_signature, .key_xchg = &tls_dhe, }, tls_rsa_with_aes_128_gcm_sha256 = { .id = { 0x00, 0x9c }, .name = "TLS_RSA_WITH_AES_128_GCM_SHA256", .encryption = &tls_aes128_gcm, .signature = &tls_rsa_signature, .key_xchg = &tls_rsa_key_xchg, }, tls_rsa_with_aes_256_gcm_sha384 = { .id = { 0x00, 0x9d }, .name = "TLS_RSA_WITH_AES_256_GCM_SHA384", .encryption = &tls_aes256_gcm, .prf_hmac = L_CHECKSUM_SHA384, .signature = &tls_rsa_signature, .key_xchg = &tls_rsa_key_xchg, }, tls_dhe_rsa_with_aes_128_gcm_sha256 = { .id = { 0x00, 0x9e }, .name = "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", .encryption = &tls_aes128_gcm, .signature = &tls_rsa_signature, .key_xchg = &tls_dhe, }, tls_dhe_rsa_with_aes_256_gcm_sha384 = { .id = { 0x00, 0x9f }, .name = "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", .encryption = &tls_aes256_gcm, .prf_hmac = L_CHECKSUM_SHA384, .signature = &tls_rsa_signature, .key_xchg = &tls_dhe, }, tls_ecdhe_rsa_with_3des_ede_cbc_sha = { .id = { 0xc0, 0x12 }, .name = "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", .encryption = &tls_3des_ede, .mac = &tls_sha, .signature = &tls_rsa_signature, .key_xchg = &tls_ecdhe, }, tls_ecdhe_rsa_with_aes_128_cbc_sha = { .id = { 0xc0, 0x13 }, .name = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", .encryption = &tls_aes128, .mac = &tls_sha, .signature = &tls_rsa_signature, .key_xchg = &tls_ecdhe, }, tls_ecdhe_rsa_with_aes_256_cbc_sha = { .id = { 0xc0, 0x14 }, .name = "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", .encryption = &tls_aes256, .mac = &tls_sha, .signature = &tls_rsa_signature, .key_xchg = &tls_ecdhe, }, tls_ecdhe_rsa_with_aes_128_cbc_sha256 = { .id = { 0xc0, 0x27 }, .name = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", .encryption = &tls_aes128, .mac = &tls_sha256, .signature = &tls_rsa_signature, .key_xchg = &tls_ecdhe, }, tls_ecdhe_rsa_with_aes_256_cbc_sha384 = { .id = { 0xc0, 0x28 }, .name = "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", .encryption = &tls_aes256, .mac = &tls_sha384, .prf_hmac = L_CHECKSUM_SHA384, .signature = &tls_rsa_signature, .key_xchg = &tls_ecdhe, }, tls_ecdhe_rsa_with_aes_128_gcm_sha256 = { .id = { 0xc0, 0x2f }, .name = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", .encryption = &tls_aes128_gcm, .signature = &tls_rsa_signature, .key_xchg = &tls_ecdhe, }, tls_ecdhe_rsa_with_aes_256_gcm_sha384 = { .id = { 0xc0, 0x30 }, .name = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", .encryption = &tls_aes256_gcm, .prf_hmac = L_CHECKSUM_SHA384, .signature = &tls_rsa_signature, .key_xchg = &tls_ecdhe, }, tls_ecdhe_ecdsa_with_3des_ede_cbc_sha = { .id = { 0xc0, 0x08 }, .name = "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", .encryption = &tls_3des_ede, .mac = &tls_sha, .signature = &tls_ecdsa_signature, .key_xchg = &tls_ecdhe, }, tls_ecdhe_ecdsa_with_aes_128_cbc_sha = { .id = { 0xc0, 0x09 }, .name = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", .encryption = &tls_aes128, .mac = &tls_sha, .signature = &tls_ecdsa_signature, .key_xchg = &tls_ecdhe, }, tls_ecdhe_ecdsa_with_aes_256_cbc_sha = { .id = { 0xc0, 0x0a }, .name = "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", .encryption = &tls_aes256, .mac = &tls_sha, .signature = &tls_ecdsa_signature, .key_xchg = &tls_ecdhe, }, tls_ecdhe_ecdsa_with_aes_128_gcm_sha256 = { .id = { 0xc0, 0x2b }, .name = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", .encryption = &tls_aes128_gcm, .signature = &tls_ecdsa_signature, .key_xchg = &tls_ecdhe, }, tls_ecdhe_ecdsa_with_aes_256_gcm_sha384 = { .id = { 0xc0, 0x2c }, .name = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", .encryption = &tls_aes256_gcm, .prf_hmac = L_CHECKSUM_SHA384, .signature = &tls_ecdsa_signature, .key_xchg = &tls_ecdhe, }; struct tls_cipher_suite *tls_cipher_suite_pref[] = { &tls_ecdhe_rsa_with_aes_256_cbc_sha, &tls_ecdhe_ecdsa_with_aes_256_cbc_sha, &tls_ecdhe_rsa_with_aes_128_cbc_sha, &tls_ecdhe_ecdsa_with_aes_128_cbc_sha, &tls_dhe_rsa_with_aes_256_cbc_sha, &tls_dhe_rsa_with_aes_128_cbc_sha, &tls_rsa_with_aes_256_cbc_sha, &tls_rsa_with_aes_128_cbc_sha, &tls_ecdhe_rsa_with_aes_256_cbc_sha384, &tls_ecdhe_rsa_with_aes_128_cbc_sha256, &tls_dhe_rsa_with_aes_256_cbc_sha256, &tls_dhe_rsa_with_aes_128_cbc_sha256, &tls_rsa_with_aes_256_cbc_sha256, &tls_rsa_with_aes_128_cbc_sha256, &tls_ecdhe_rsa_with_aes_256_gcm_sha384, &tls_ecdhe_rsa_with_aes_128_gcm_sha256, &tls_ecdhe_ecdsa_with_aes_256_gcm_sha384, &tls_ecdhe_ecdsa_with_aes_128_gcm_sha256, &tls_dhe_rsa_with_aes_256_gcm_sha384, &tls_dhe_rsa_with_aes_128_gcm_sha256, &tls_rsa_with_aes_256_gcm_sha384, &tls_rsa_with_aes_128_gcm_sha256, &tls_ecdhe_rsa_with_3des_ede_cbc_sha, &tls_ecdhe_ecdsa_with_3des_ede_cbc_sha, &tls_dhe_rsa_with_3des_ede_cbc_sha, &tls_rsa_with_3des_ede_cbc_sha, NULL, }; bluez-5.87/ell/PaxHeaders/random.c0000644000000000000000000000005014504767710014066 xustar0020 atime=1783076253 20 ctime=1783094099 bluez-5.87/ell/random.c0000644000000000000000000000311114504767710013543 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2015 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include "random.h" #include "private.h" #include "missing.h" #ifndef GRND_NONBLOCK #define GRND_NONBLOCK 0x0001 #endif #ifndef GRND_RANDOM #define GRND_RANDOM 0x0002 #endif static inline int getrandom(void *buffer, size_t count, unsigned flags) { return syscall(__NR_getrandom, buffer, count, flags); } /** * l_getrandom: * @buf: buffer to fill with random data * @len: length of random data requested * * Request a number of randomly generated bytes given by @len and put them * into buffer @buf. * * Returns: true if the random data could be generated, false otherwise. **/ LIB_EXPORT bool l_getrandom(void *buf, size_t len) { while (len) { int ret; ret = L_TFR(getrandom(buf, len, 0)); if (ret < 0) return false; buf += ret; len -= ret; } return true; } LIB_EXPORT bool l_getrandom_is_supported() { static bool initialized = false; static bool supported = true; uint8_t buf[4]; int ret; if (initialized) return supported; ret = getrandom(buf, sizeof(buf), GRND_NONBLOCK); if (ret < 0 && errno == ENOSYS) supported = false; initialized = true; return supported; } LIB_EXPORT uint32_t l_getrandom_uint32(void) { int ret; uint32_t u; ret = getrandom(&u, sizeof(u), GRND_NONBLOCK); if (ret == sizeof(u)) return u; return random() * RAND_MAX + random(); } bluez-5.87/ell/PaxHeaders/utf8.h0000644000000000000000000000005014560467756013512 xustar0020 atime=1783076232 20 ctime=1783094098 bluez-5.87/ell/utf8.h0000644000000000000000000000561514560467756013202 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2024 Cruise, LLC * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_UTF8_H #define __ELL_UTF8_H #include #include #include #ifdef __cplusplus extern "C" { #endif extern unsigned char l_ascii_table[]; enum l_ascii { L_ASCII_CNTRL = 0x80, L_ASCII_PRINT = 0x40, L_ASCII_PUNCT = 0x20, L_ASCII_SPACE = 0x10, L_ASCII_XDIGIT = 0x08, L_ASCII_UPPER = 0x04, L_ASCII_LOWER = 0x02, L_ASCII_DIGIT = 0x01, L_ASCII_ALPHA = L_ASCII_LOWER | L_ASCII_UPPER, L_ASCII_ALNUM = L_ASCII_ALPHA | L_ASCII_DIGIT, L_ASCII_GRAPH = L_ASCII_ALNUM | L_ASCII_PUNCT, }; #define l_ascii_isalnum(c) \ ((l_ascii_table[(unsigned char) (c)] & L_ASCII_ALNUM) != 0) #define l_ascii_isalpha(c) \ ((l_ascii_table[(unsigned char) (c)] & L_ASCII_ALPHA) != 0) #define l_ascii_iscntrl(c) \ ((l_ascii_table[(unsigned char) (c)] & L_ASCII_CNTRL) != 0) #define l_ascii_isdigit(c) \ ((l_ascii_table[(unsigned char) (c)] & L_ASCII_DIGIT) != 0) #define l_ascii_isgraph(c) \ ((l_ascii_table[(unsigned char) (c)] & L_ASCII_GRAPH) != 0) #define l_ascii_islower(c) \ ((l_ascii_table[(unsigned char) (c)] & L_ASCII_LOWER) != 0) #define l_ascii_isprint(c) \ ((l_ascii_table[(unsigned char) (c)] & L_ASCII_PRINT) != 0) #define l_ascii_ispunct(c) \ ((l_ascii_table[(unsigned char) (c)] & L_ASCII_PUNCT) != 0) #define l_ascii_isspace(c) \ ((l_ascii_table[(unsigned char) (c)] & L_ASCII_SPACE) != 0) #define l_ascii_isupper(c) \ ((l_ascii_table[(unsigned char) (c)] & L_ASCII_UPPER) != 0) #define l_ascii_isxdigit(c) \ ((l_ascii_table[(unsigned char) (c)] & L_ASCII_XDIGIT) != 0) #if __STDC_VERSION__ <= 199409L #define inline __inline__ #endif static inline __attribute__ ((always_inline)) bool l_ascii_isblank(unsigned char c) { if (c == ' ' || c == '\t') return true; return false; } static inline __attribute__ ((always_inline)) bool l_ascii_isascii(int c) { if (c <= 127) return true; return false; } static inline __attribute__ ((always_inline)) char l_ascii_toupper(char c) { if (!l_ascii_islower(c)) return c; return c - 32; } static inline __attribute__ ((always_inline)) char l_ascii_tolower(char c) { if (!l_ascii_isupper(c)) return c; return c + 32; } char *l_ascii_strdown(const char *str, ssize_t len); char *l_ascii_strup(const char *str, ssize_t len); bool l_utf8_validate(const char *src, size_t len, const char **end); size_t l_utf8_strlen(const char *str); int l_utf8_get_codepoint(const char *str, size_t len, wchar_t *cp); size_t l_utf8_from_wchar(wchar_t c, char *out_buf); char *l_utf8_from_utf16(const void *utf16, ssize_t utf16_size); void *l_utf8_to_utf16(const char *utf8, size_t *out_size); char *l_utf8_from_ucs2be(const void *ucs2be, ssize_t ucs2be_size); void *l_utf8_to_ucs2be(const char *utf8, size_t *out_size); #ifdef __cplusplus } #endif #endif /* __ELL_UTF8_H */ bluez-5.87/ell/PaxHeaders/dbus-util.c0000644000000000000000000000005014504767710014516 xustar0020 atime=1783076296 20 ctime=1783094099 bluez-5.87/ell/dbus-util.c0000644000000000000000000006213514504767710014206 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include "dbus.h" #include "private.h" #include "useful.h" #include "dbus-private.h" #include "string.h" #include "queue.h" #include "utf8.h" #define DBUS_MAX_INTERFACE_LEN 255 #define DBUS_MAX_METHOD_LEN 255 static const char *simple_types = "sogybnqiuxtdh"; static int get_alignment(const char type) { switch (type) { case 'b': return 4; case 'y': return 1; case 'n': case 'q': return 2; case 'u': case 'i': return 4; case 'x': case 't': case 'd': return 8; case 's': case 'o': return 4; case 'g': return 1; case 'a': return 4; case '(': case '{': return 8; case 'v': return 1; case 'h': return 4; default: return 0; } } static int get_basic_size(const char type) { switch (type) { case 'b': return 4; case 'y': return 1; case 'n': case 'q': return 2; case 'i': case 'u': return 4; case 'x': case 't': return 8; case 'd': return 8; case 'h': return 4; default: return 0; } } static inline bool is_valid_character(const char c, bool bus_name) { if (c >= 'a' && c <= 'z') return true; if (c >= 'A' && c <= 'Z') return true; if (c >= '0' && c <= '9') return true; if (c == '_') return true; if (c == '-' && bus_name) return true; return false; } bool _dbus_valid_object_path(const char *path) { unsigned int i; char c = '\0'; if (path == NULL) return false; if (path[0] == '\0') return false; if (path[0] && !path[1] && path[0] == '/') return true; if (path[0] != '/') return false; for (i = 0; path[i]; i++) { if (path[i] == '/' && c == '/') return false; c = path[i]; if (is_valid_character(path[i], false) || path[i] == '/') continue; return false; } if (path[i-1] == '/') return false; return true; } static const char *validate_next_type(const char *sig) { char s = *sig; if (s == '\0') return NULL; if (strchr(simple_types, s) || s == 'v') return sig + 1; switch (s) { case 'a': s = *++sig; if (s == '{') { s = *++sig; /* Dictionary keys can only be simple types */ if (!strchr(simple_types, s)) return NULL; sig = validate_next_type(sig + 1); if (!sig) return NULL; if (*sig != '}') return NULL; return sig + 1; } return validate_next_type(sig); case '(': sig++; do sig = validate_next_type(sig); while (sig && *sig != ')'); if (!sig) return NULL; return sig + 1; } return NULL; } static bool valid_dict_signature(const char *sig) { char s = *sig; if (s != '{') return false; s = *++sig; if (!strchr(simple_types, s)) return false; sig = validate_next_type(sig + 1); if (!sig) return false; if (sig[0] != '}') return false; if (sig[1] != '\0') return false; return true; } bool _dbus_valid_signature(const char *sig) { const char *s = sig; do { s = validate_next_type(s); if (!s) return false; } while (*s); return true; } int _dbus_num_children(const char *sig) { const char *s = sig; int num_children = 0; do { s = validate_next_type(s); if (!s) return -1; num_children += 1; } while (*s); return num_children; } static bool valid_member_name(const char *start, const char *end, bool bus_name) { const char *p; if ((end - start) < 1) return false; if (*start >= '0' && *start <= '9') return false; for (p = start; p < end; p++) if (!is_valid_character(*p, bus_name)) return false; return true; } bool _dbus_valid_method(const char *method) { unsigned int i; if (!method) return false; if (method[0] == '\0' || strlen(method) > DBUS_MAX_METHOD_LEN) return false; if (method[0] >= '0' && method[0] <= '9') return false; for (i = 0; method[i]; i++) if (!is_valid_character(method[i], false)) return false; return true; } bool _dbus_valid_interface(const char *interface) { const char *sep; if (!interface) return false; if (interface[0] == '\0' || strlen(interface) > DBUS_MAX_INTERFACE_LEN) return false; sep = strchrnul(interface, '.'); if (*sep == '\0') return false; while (true) { if (!valid_member_name(interface, sep, false)) return false; if (*sep == '\0') break; interface = sep + 1; sep = strchrnul(interface, '.'); } return true; } bool _dbus_parse_unique_name(const char *name, uint64_t *out_id) { char *endp = NULL; uint64_t r; if (!l_str_has_prefix(name, ":1.")) return false; name += 3; /* * Disallow '+' or '-' at the beginning of the string * must have at least one digit */ if (!l_ascii_isdigit(*name)) return false; errno = 0; r = strtoull(name, &endp, 10); if (!endp || *endp || errno) return false; if (out_id) *out_id = r; return true; } bool _dbus_valid_bus_name(const char *bus_name) { const char *sep; if (!bus_name) return false; if (bus_name[0] == '\0' || strlen(bus_name) > DBUS_MAX_INTERFACE_LEN) return false; if (_dbus_parse_unique_name(bus_name, NULL)) return true; sep = strchrnul(bus_name, '.'); if (*sep == '\0') return false; while (true) { if (!valid_member_name(bus_name, sep, true)) return false; if (*sep == '\0') break; bus_name = sep + 1; sep = strchrnul(bus_name, '.'); } return true; } const char *_dbus_signature_end(const char *signature) { const char *ptr = signature; unsigned int indent = 0; char expect; switch (*signature) { case '(': expect = ')'; break; case '{': expect = '}'; break; case 'a': return _dbus_signature_end(signature + 1); default: return signature; } for (ptr = signature; *ptr != '\0'; ptr++) { if (*ptr == *signature) indent++; else if (*ptr == expect) if (!--indent) return ptr; } return NULL; } bool _dbus1_header_is_valid(void *data, size_t size) { struct dbus_header *hdr; size_t header_len; if (size < sizeof(struct dbus_header)) return false; hdr = data; if (hdr->endian != DBUS_NATIVE_ENDIAN) header_len = bswap_32(hdr->dbus1.field_length); else header_len = hdr->dbus1.field_length; header_len += sizeof(struct dbus_header); return size >= header_len; } static inline void dbus1_iter_init_internal(struct l_dbus_message_iter *iter, struct l_dbus_message *message, enum dbus_container_type type, const char *sig_start, const char *sig_end, const void *data, size_t len, size_t pos) { size_t sig_len; iter->message = message; if (sig_end) sig_len = sig_end - sig_start; else sig_len = strlen(sig_start); iter->sig_start = sig_start; iter->sig_len = sig_len; iter->sig_pos = 0; iter->data = data; iter->len = pos + len; iter->pos = pos; iter->container_type = type; } void _dbus1_iter_init(struct l_dbus_message_iter *iter, struct l_dbus_message *message, const char *sig_start, const char *sig_end, const void *data, size_t len) { dbus1_iter_init_internal(iter, message, DBUS_CONTAINER_TYPE_STRUCT, sig_start, sig_end, data, len, 0); } static const char *calc_len_next_item(const char *signature, const void *data, size_t data_pos, size_t data_len, size_t *out_len) { unsigned int alignment; size_t pos; size_t len; const char *sig_end; const char *var_sig; alignment = get_alignment(*signature); if (alignment == 0) return NULL; pos = align_len(data_pos, alignment); if (pos > data_len) return NULL; switch (*signature) { case 'o': case 's': if (pos + 5 > data_len) return NULL; pos += l_get_u32(data + pos) + 5; break; case 'g': if (pos + 2 > data_len) return NULL; pos += l_get_u8(data + pos) + 2; break; case 'y': pos += 1; break; case 'n': case 'q': pos += 2; break; case 'b': case 'i': case 'u': case 'h': pos += 4; break; case 'x': case 't': case 'd': pos += 8; break; case 'a': if (pos + 4 > data_len) return NULL; len = l_get_u32(data + pos); pos += 4; alignment = get_alignment(signature[1]); pos = align_len(pos, alignment); pos += len; sig_end = _dbus_signature_end(signature) + 1; goto done; case '(': sig_end = signature + 1; while (*sig_end != ')') { sig_end = calc_len_next_item(sig_end, data, pos, data_len, &len); if (!sig_end) return NULL; pos += len; } sig_end += 1; goto done; case '{': sig_end = calc_len_next_item(signature + 1, data, pos, data_len, &len); if (!sig_end) return NULL; pos += len; sig_end = calc_len_next_item(sig_end, data, pos, data_len, &len); if (!sig_end) return NULL; pos += len; sig_end += 1; goto done; case 'v': if (!calc_len_next_item("g", data, pos, data_len, &len)) return NULL; var_sig = data + pos + 1; pos += len; if (!calc_len_next_item(var_sig, data, pos, data_len, &len)) return NULL; pos += len; break; default: return NULL; } sig_end = signature + 1; done: if (pos > data_len) return NULL; *out_len = pos - data_pos; return sig_end; } bool _dbus1_iter_next_entry_basic(struct l_dbus_message_iter *iter, char type, void *out) { const char *str_val; uint8_t uint8_val; uint16_t uint16_val; uint32_t uint32_val; uint64_t uint64_val; int16_t int16_val; int32_t int32_val; int64_t int64_val; size_t pos; if (iter->pos >= iter->len) return false; pos = align_len(iter->pos, get_alignment(type)); switch (type) { case 'o': case 's': if (pos + 5 > iter->len) return false; uint32_val = l_get_u32(iter->data + pos); str_val = iter->data + pos + 4; *(const void **) out = str_val; iter->pos = pos + uint32_val + 5; break; case 'g': if (pos + 2 > iter->len) return false; uint8_val = l_get_u8(iter->data + pos); str_val = iter->data + pos + 1; *(const void **) out = str_val; iter->pos = pos + uint8_val + 2; break; case 'b': if (pos + 4 > iter->len) return false; uint32_val = l_get_u32(iter->data + pos); *(bool *) out = !!uint32_val; iter->pos = pos + 4; break; case 'y': if (pos + 1 > iter->len) return false; uint8_val = l_get_u8(iter->data + pos); *(uint8_t *) out = uint8_val; iter->pos = pos + 1; break; case 'n': if (pos + 2 > iter->len) return false; int16_val = l_get_s16(iter->data + pos); *(int16_t *) out = int16_val; iter->pos = pos + 2; break; case 'q': if (pos + 2 > iter->len) return false; uint16_val = l_get_u16(iter->data + pos); *(uint16_t *) out = uint16_val; iter->pos = pos + 2; break; case 'i': if (pos + 4 > iter->len) return false; int32_val = l_get_s32(iter->data + pos); *(int32_t *) out = int32_val; iter->pos = pos + 4; break; case 'u': case 'h': if (pos + 4 > iter->len) return false; uint32_val = l_get_u32(iter->data + pos); *(uint32_t *) out = uint32_val; iter->pos = pos + 4; break; case 'x': if (pos + 8 > iter->len) return false; int64_val = l_get_s64(iter->data + pos); *(int64_t *) out= int64_val; iter->pos = pos + 8; break; case 't': case 'd': if (pos + 8 > iter->len) return false; uint64_val = l_get_u64(iter->data + pos); *(uint64_t *) out = uint64_val; iter->pos = pos + 8; break; default: return false; } if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY) iter->sig_pos += 1; return true; } bool _dbus1_iter_get_fixed_array(struct l_dbus_message_iter *iter, void *out, uint32_t *n_elem) { char type; uint32_t size; if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY) return false; type = iter->sig_start[iter->sig_pos]; size = get_basic_size(type); /* Fail if the array is not a fixed size or contains file descriptors */ if (!size || type == 'h') return false; /* * enter_array should already align us to our container type, so * there is no need to align pos here */ *(const void **) out = iter->data + iter->pos; *n_elem = (iter->len - iter->pos) / size; return true; } bool _dbus1_iter_enter_struct(struct l_dbus_message_iter *iter, struct l_dbus_message_iter *structure) { size_t len; size_t pos; const char *sig_start; const char *sig_end; bool is_dict = iter->sig_start[iter->sig_pos] == '{'; bool is_struct = iter->sig_start[iter->sig_pos] == '('; if (!is_dict && !is_struct) return false; pos = align_len(iter->pos, 8); if (pos >= iter->len) return false; sig_start = iter->sig_start + iter->sig_pos + 1; sig_end = _dbus_signature_end(iter->sig_start + iter->sig_pos); if (!calc_len_next_item(iter->sig_start + iter->sig_pos, iter->data, pos, iter->len, &len)) return false; dbus1_iter_init_internal(structure, iter->message, DBUS_CONTAINER_TYPE_STRUCT, sig_start, sig_end, iter->data, len, pos); if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY) iter->sig_pos += sig_end - sig_start + 2; iter->pos = pos + len; return true; } bool _dbus1_iter_enter_variant(struct l_dbus_message_iter *iter, struct l_dbus_message_iter *variant) { size_t pos; uint8_t sig_len; size_t len; const char *sig_start; if (iter->sig_start[iter->sig_pos] != 'v') return false; pos = align_len(iter->pos, 1); if (pos + 2 > iter->len) return false; sig_len = l_get_u8(iter->data + pos); sig_start = iter->data + pos + 1; if (!calc_len_next_item(sig_start, iter->data, pos + sig_len + 2, iter->len, &len)) return false; dbus1_iter_init_internal(variant, iter->message, DBUS_CONTAINER_TYPE_VARIANT, sig_start, NULL, iter->data, len, pos + sig_len + 2); if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY) iter->sig_pos += 1; iter->pos = pos + sig_len + 2 + len; return true; } bool _dbus1_iter_enter_array(struct l_dbus_message_iter *iter, struct l_dbus_message_iter *array) { size_t pos; size_t len; const char *sig_start; const char *sig_end; if (iter->sig_start[iter->sig_pos] != 'a') return false; sig_start = iter->sig_start + iter->sig_pos + 1; sig_end = _dbus_signature_end(sig_start) + 1; pos = align_len(iter->pos, 4); if (pos + 4 > iter->len) return false; len = l_get_u32(iter->data + pos); pos += 4; pos = align_len(pos, get_alignment(*sig_start)); dbus1_iter_init_internal(array, iter->message, DBUS_CONTAINER_TYPE_ARRAY, sig_start, sig_end, iter->data, len, pos); if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY) iter->sig_pos += sig_end - sig_start + 1; iter->pos = pos + len; return true; } bool _dbus1_iter_skip_entry(struct l_dbus_message_iter *iter) { size_t len; const char *sig_end; sig_end = calc_len_next_item(iter->sig_start + iter->sig_pos, iter->data, iter->pos, iter->len, &len); if (!sig_end) return false; iter->pos += len; iter->sig_pos = sig_end - iter->sig_start; return true; } struct dbus_builder { struct l_string *signature; void *body; size_t body_size; size_t body_pos; struct l_queue *containers; struct { struct container *container; int sig_end; size_t body_pos; } mark; }; struct container { size_t start; enum dbus_container_type type; char signature[256]; uint8_t sigindex; }; static struct container *container_new(enum dbus_container_type type, const char *signature, size_t start) { struct container *ret; ret = l_new(struct container, 1); ret->type = type; strcpy(ret->signature, signature); ret->start = start; return ret; } static void container_free(struct container *container) { l_free(container); } static inline size_t grow_body(struct dbus_builder *builder, size_t len, unsigned int alignment) { size_t size = align_len(builder->body_pos, alignment); if (size + len > builder->body_size) { builder->body = l_realloc(builder->body, size + len); builder->body_size = size + len; } if (size - builder->body_pos > 0) memset(builder->body + builder->body_pos, 0, size - builder->body_pos); builder->body_pos = size + len; return size; } struct dbus_builder *_dbus1_builder_new(void *body, size_t body_size) { struct dbus_builder *builder; struct container *root; builder = l_new(struct dbus_builder, 1); builder->signature = l_string_new(63); builder->containers = l_queue_new(); root = container_new(DBUS_CONTAINER_TYPE_STRUCT, "", 0); l_queue_push_head(builder->containers, root); builder->body = body; builder->body_size = body_size; builder->body_pos = body_size; builder->mark.container = root; builder->mark.sig_end = 0; builder->mark.body_pos = 0; return builder; } void _dbus1_builder_free(struct dbus_builder *builder) { if (unlikely(!builder)) return; l_string_free(builder->signature); l_queue_destroy(builder->containers, (l_queue_destroy_func_t) container_free); l_free(builder->body); l_free(builder); } bool _dbus1_builder_append_basic(struct dbus_builder *builder, char type, const void *value) { struct container *container = l_queue_peek_head(builder->containers); size_t start; unsigned int alignment; size_t len; if (unlikely(!builder)) return false; if (unlikely(!strchr(simple_types, type))) return false; alignment = get_alignment(type); if (!alignment) return false; if (l_queue_length(builder->containers) == 1) l_string_append_c(builder->signature, type); else if (container->signature[container->sigindex] != type) return false; len = get_basic_size(type); if (len) { uint32_t b; start = grow_body(builder, len, alignment); if (type == 'b') { b = *(bool *)value; memcpy(builder->body + start, &b, len); } else memcpy(builder->body + start, value, len); if (container->type != DBUS_CONTAINER_TYPE_ARRAY) container->sigindex += 1; return true; } len = strlen(value); if (type == 'g') { start = grow_body(builder, len + 2, 1); l_put_u8(len, builder->body + start); strcpy(builder->body + start + 1, value); } else { start = grow_body(builder, len + 5, 4); l_put_u32(len, builder->body + start); strcpy(builder->body + start + 4, value); } if (container->type != DBUS_CONTAINER_TYPE_ARRAY) container->sigindex += 1; return true; } static bool enter_struct_dict_common(struct dbus_builder *builder, const char *signature, enum dbus_container_type type, const char open, const char close) { size_t qlen = l_queue_length(builder->containers); struct container *container = l_queue_peek_head(builder->containers); size_t start; if (qlen == 1) { if (l_string_length(builder->signature) + strlen(signature) + 2 > 255) return false; } else { /* Verify Signatures Match */ char expect[256]; const char *start; const char *end; start = container->signature + container->sigindex; end = _dbus_signature_end(start); if (*start != open || *end != close) return false; memcpy(expect, start + 1, end - start - 1); expect[end - start - 1] = '\0'; if (strcmp(expect, signature)) return false; } start = grow_body(builder, 0, 8); container = container_new(type, signature, start); l_queue_push_head(builder->containers, container); return true; } bool _dbus1_builder_enter_struct(struct dbus_builder *builder, const char *signature) { if (!_dbus_valid_signature(signature)) return false; return enter_struct_dict_common(builder, signature, DBUS_CONTAINER_TYPE_STRUCT, '(', ')'); } bool _dbus1_builder_enter_dict(struct dbus_builder *builder, const char *signature) { if (_dbus_num_children(signature) != 2) return false; if (!strchr(simple_types, signature[0])) return false; return enter_struct_dict_common(builder, signature, DBUS_CONTAINER_TYPE_DICT_ENTRY, '{', '}'); } static bool leave_struct_dict_common(struct dbus_builder *builder, enum dbus_container_type type, const char open, const char close) { struct container *container = l_queue_peek_head(builder->containers); size_t qlen = l_queue_length(builder->containers); struct container *parent; if (unlikely(qlen <= 1)) return false; if (unlikely(container->type != type)) return false; l_queue_pop_head(builder->containers); qlen -= 1; parent = l_queue_peek_head(builder->containers); if (qlen == 1) l_string_append_printf(builder->signature, "%c%s%c", open, container->signature, close); else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY) parent->sigindex += strlen(container->signature) + 2; container_free(container); return true; } bool _dbus1_builder_leave_struct(struct dbus_builder *builder) { return leave_struct_dict_common(builder, DBUS_CONTAINER_TYPE_STRUCT, '(', ')'); } bool _dbus1_builder_leave_dict(struct dbus_builder *builder) { return leave_struct_dict_common(builder, DBUS_CONTAINER_TYPE_DICT_ENTRY, '{', '}'); } bool _dbus1_builder_enter_variant(struct dbus_builder *builder, const char *signature) { size_t qlen = l_queue_length(builder->containers); struct container *container = l_queue_peek_head(builder->containers); size_t start; size_t siglen; if (_dbus_num_children(signature) != 1) return false; if (qlen == 1) { if (l_string_length(builder->signature) + 1 > 255) return false; } else if (container->signature[container->sigindex] != 'v') return false; siglen = strlen(signature); start = grow_body(builder, siglen + 2, 1); l_put_u8(siglen, builder->body + start); strcpy(builder->body + start + 1, signature); container = container_new(DBUS_CONTAINER_TYPE_VARIANT, signature, start); l_queue_push_head(builder->containers, container); return true; } bool _dbus1_builder_leave_variant(struct dbus_builder *builder) { struct container *container = l_queue_peek_head(builder->containers); size_t qlen = l_queue_length(builder->containers); struct container *parent; if (unlikely(qlen <= 1)) return false; if (unlikely(container->type != DBUS_CONTAINER_TYPE_VARIANT)) return false; l_queue_pop_head(builder->containers); qlen -= 1; parent = l_queue_peek_head(builder->containers); if (qlen == 1) l_string_append_c(builder->signature, 'v'); else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY) parent->sigindex += 1; container_free(container); return true; } bool _dbus1_builder_enter_array(struct dbus_builder *builder, const char *signature) { size_t qlen = l_queue_length(builder->containers); struct container *container = l_queue_peek_head(builder->containers); size_t start; int alignment; if (_dbus_num_children(signature) != 1 && !valid_dict_signature(signature)) return false; if (qlen == 1) { if (l_string_length(builder->signature) + strlen(signature) + 1 > 255) return false; } else { /* Verify Signatures Match */ char expect[256]; const char *start; const char *end; start = container->signature + container->sigindex; end = validate_next_type(start); if (*start != 'a') return false; memcpy(expect, start + 1, end - start - 1); expect[end - start - 1] = '\0'; if (strcmp(expect, signature)) return false; } /* First grow the body enough to cover preceding length */ start = grow_body(builder, 4, 4); /* Now align to element alignment */ alignment = get_alignment(*signature); grow_body(builder, 0, alignment); container = container_new(DBUS_CONTAINER_TYPE_ARRAY, signature, start); l_queue_push_head(builder->containers, container); return true; } bool _dbus1_builder_leave_array(struct dbus_builder *builder) { struct container *container = l_queue_peek_head(builder->containers); size_t qlen = l_queue_length(builder->containers); struct container *parent; size_t alignment; size_t array_start; if (unlikely(qlen <= 1)) return false; if (unlikely(container->type != DBUS_CONTAINER_TYPE_ARRAY)) return false; l_queue_pop_head(builder->containers); qlen -= 1; parent = l_queue_peek_head(builder->containers); if (qlen == 1) l_string_append_printf(builder->signature, "a%s", container->signature); else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY) parent->sigindex += strlen(container->signature) + 1; /* Update array length */ alignment = get_alignment(container->signature[0]); array_start = align_len(container->start + 4, alignment); l_put_u32(builder->body_pos - array_start, builder->body + container->start); container_free(container); return true; } bool _dbus1_builder_mark(struct dbus_builder *builder) { struct container *container = l_queue_peek_head(builder->containers); builder->mark.container = container; if (l_queue_length(builder->containers) == 1) builder->mark.sig_end = l_string_length(builder->signature); else builder->mark.sig_end = container->sigindex; builder->mark.body_pos = builder->body_pos; return true; } bool _dbus1_builder_rewind(struct dbus_builder *builder) { struct container *container; while ((container = l_queue_peek_head(builder->containers)) != builder->mark.container) { container_free(container); l_queue_pop_head(builder->containers); } builder->body_pos = builder->mark.body_pos; if (l_queue_length(builder->containers) == 1) l_string_truncate(builder->signature, builder->mark.sig_end); else container->sigindex = builder->mark.sig_end; return true; } char *_dbus1_builder_finish(struct dbus_builder *builder, void **body, size_t *body_size) { char *signature; if (unlikely(!builder)) return NULL; if (unlikely(l_queue_length(builder->containers) != 1)) return NULL; signature = l_string_unwrap(builder->signature); builder->signature = NULL; *body = builder->body; *body_size = builder->body_pos; builder->body = NULL; builder->body_size = 0; return signature; } bluez-5.87/ell/PaxHeaders/tls.h0000644000000000000000000000005014504767710013415 xustar0020 atime=1783076232 20 ctime=1783094099 bluez-5.87/ell/tls.h0000644000000000000000000001065414504767710013104 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2015 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_TLS_H #define __ELL_TLS_H #ifdef __cplusplus extern "C" { #endif enum l_tls_version { L_TLS_V10 = ((3 << 8) | 1), L_TLS_V11 = ((3 << 8) | 2), L_TLS_V12 = ((3 << 8) | 3), L_TLS_V13 = ((3 << 8) | 4), /* Not supported */ }; struct l_tls; struct l_key; struct l_certchain; struct l_queue; struct l_settings; enum l_tls_alert_desc { TLS_ALERT_CLOSE_NOTIFY = 0, TLS_ALERT_UNEXPECTED_MESSAGE = 10, TLS_ALERT_BAD_RECORD_MAC = 20, TLS_ALERT_DECRYPT_FAIL_RESERVED = 21, TLS_ALERT_RECORD_OVERFLOW = 22, TLS_ALERT_DECOMPRESS_FAIL = 30, TLS_ALERT_HANDSHAKE_FAIL = 40, TLS_ALERT_NO_CERT_RESERVED = 41, TLS_ALERT_BAD_CERT = 42, TLS_ALERT_UNSUPPORTED_CERT = 43, TLS_ALERT_CERT_REVOKED = 44, TLS_ALERT_CERT_EXPIRED = 45, TLS_ALERT_CERT_UNKNOWN = 46, TLS_ALERT_ILLEGAL_PARAM = 47, TLS_ALERT_UNKNOWN_CA = 48, TLS_ALERT_ACCESS_DENIED = 49, TLS_ALERT_DECODE_ERROR = 50, TLS_ALERT_DECRYPT_ERROR = 51, TLS_ALERT_EXPORT_RES_RESERVED = 60, TLS_ALERT_PROTOCOL_VERSION = 70, TLS_ALERT_INSUFFICIENT_SECURITY = 71, TLS_ALERT_INTERNAL_ERROR = 80, TLS_ALERT_USER_CANCELED = 90, TLS_ALERT_NO_RENEGOTIATION = 100, TLS_ALERT_UNSUPPORTED_EXTENSION = 110, }; typedef void (*l_tls_write_cb_t)(const uint8_t *data, size_t len, void *user_data); typedef void (*l_tls_ready_cb_t)(const char *peer_identity, void *user_data); typedef void (*l_tls_disconnect_cb_t)(enum l_tls_alert_desc reason, bool remote, void *user_data); typedef void (*l_tls_debug_cb_t)(const char *str, void *user_data); typedef void (*l_tls_destroy_cb_t)(void *user_data); typedef void (*l_tls_session_update_cb_t)(void *user_data); /* * app_data_handler gets called with newly received decrypted data. * tx_handler gets called to send TLS payloads off to remote end. * ready_handler gets called when l_tls_write calls are first accepted. */ struct l_tls *l_tls_new(bool server, l_tls_write_cb_t app_data_handler, l_tls_write_cb_t tx_handler, l_tls_ready_cb_t ready_handler, l_tls_disconnect_cb_t disconnect_handler, void *user_data); void l_tls_free(struct l_tls *tls); /* Begin sending connection setup messages to the server */ bool l_tls_start(struct l_tls *tls); /* Properly disconnect a connected session */ void l_tls_close(struct l_tls *tls); /* Reset to initial state without a graceful disconnect or callback */ void l_tls_reset(struct l_tls *tls); /* Submit plaintext data to be encrypted and transmitted */ void l_tls_write(struct l_tls *tls, const uint8_t *data, size_t len); /* Submit TLS payload from underlying transport to be decrypted */ void l_tls_handle_rx(struct l_tls *tls, const uint8_t *data, size_t len); /* * If peer is to be authenticated, supply the CA certificates. On success * the l_tls object takes ownership of the queue and the individual l_cert * objects and they should not be freed by the caller afterwards. */ bool l_tls_set_cacert(struct l_tls *tls, struct l_queue *ca_certs); /* * If we are to be authenticated, supply our certificate and private key. * On the client this is optional. On success, the l_tls object takes * ownership of the certchain and the key objects and they should not be * freed by the caller afterwards. * TODO: it may also be useful for the caller to be able to supply one * certificate of each type so they can be used depending on which is compatible * with the negotiated parameters. */ bool l_tls_set_auth_data(struct l_tls *tls, struct l_certchain *certchain, struct l_key *priv_key); void l_tls_set_version_range(struct l_tls *tls, enum l_tls_version min_version, enum l_tls_version max_version); void l_tls_set_domain_mask(struct l_tls *tls, char **mask); void l_tls_set_session_cache(struct l_tls *tls, struct l_settings *settings, const char *group_prefix, uint64_t lifetime, unsigned int max_sessions, l_tls_session_update_cb_t update_cb, void *user_data); bool l_tls_get_session_resumed(struct l_tls *tls); const char *l_tls_alert_to_str(enum l_tls_alert_desc desc); enum l_checksum_type; bool l_tls_prf_get_bytes(struct l_tls *tls, bool use_master_secret, const char *label, uint8_t *buf, size_t len); bool l_tls_set_debug(struct l_tls *tls, l_tls_debug_cb_t function, void *user_data, l_tls_destroy_cb_t destroy); bool l_tls_set_cert_dump_path(struct l_tls *tls, const char *path); #ifdef __cplusplus } #endif #endif /* __ELL_TLS_H */ bluez-5.87/ell/PaxHeaders/base64.c0000644000000000000000000000005014504767710013672 xustar0020 atime=1783076286 20 ctime=1783094099 bluez-5.87/ell/base64.c0000644000000000000000000000552114504767710013356 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2015 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #include #include "utf8.h" #include "base64.h" #include "private.h" #include "useful.h" #include LIB_EXPORT uint8_t *l_base64_decode(const char *in, size_t in_len, size_t *n_written) { const char *ptr, *in_end = in + in_len; const char *base64_end = NULL; uint8_t *out_buf, *out; int base64_len = 0, pad_len = 0; uint16_t reg = 0; for (ptr = in; ptr < in_end; ptr++) { if (l_ascii_isspace(*ptr)) /* Whitespace */ continue; else if (*ptr == '=') { /* Final padding */ if (!pad_len) base64_end = ptr; pad_len++; } else if (!pad_len && (l_ascii_isalnum(*ptr) || *ptr == '+' || *ptr == '/')) /* Base64 character */ base64_len++; else /* Bad character */ return NULL; } if (ptr != in_end) return NULL; if ((base64_len & 3) == 1) /* Invalid length */ return NULL; if (pad_len != align_len(base64_len, 4) - base64_len) return NULL; /* No padding */ if (!base64_end) base64_end = ptr; *n_written = base64_len * 3 / 4; out_buf = l_malloc(*n_written); out = out_buf; base64_len = 0; for (ptr = in; ptr < base64_end; ptr++) { if (l_ascii_isspace(*ptr)) /* Whitespace */ continue; /* Base64 character */ reg <<= 6; if (l_ascii_isupper(*ptr)) reg |= *ptr - 'A' + 0; else if (l_ascii_islower(*ptr)) reg |= *ptr - 'a' + 26; else if (l_ascii_isdigit(*ptr)) reg |= *ptr - '0' + 52; else if (*ptr == '+') reg |= 62; else if (*ptr == '/') reg |= 63; if ((base64_len & 3) == 1) *out++ = reg >> 4; else if ((base64_len & 3) == 2) *out++ = reg >> 2; else if ((base64_len & 3) == 3) *out++ = reg >> 0; base64_len++; } return out_buf; } LIB_EXPORT char *l_base64_encode(const uint8_t *in, size_t in_len, int columns) { const uint8_t *in_end = in + in_len; char *out_buf, *out; size_t out_len; uint32_t reg; uint8_t idx; int i, pad = 4; int col = 0; /* For simplicity allow multiples of 4 only */ if (columns & 3) return NULL; out_len = (in_len + 2) / 3 * 4; if (columns && out_len) out_len += (out_len - 4) / columns; out_buf = l_malloc(out_len + 1); out = out_buf; while (in < in_end) { reg = *in++ << 16; if (in < in_end) reg |= *in++ << 8; else pad--; if (in < in_end) reg |= *in++ << 0; else pad--; if (columns && col == columns) { *out++ = '\n'; col = 0; } col += 4; for (i = 0; i < pad; i++) { idx = (reg >> 18) & 63; reg <<= 6; if (idx < 26) *out++ = idx + 'A'; else if (idx < 52) *out++ = idx - 26 + 'a'; else if (idx < 62) *out++ = idx - 52 + '0'; else *out++ = (idx == 62) ? '+' : '/'; } } for (; pad < 4; pad++) *out++ = '='; *out = '\0'; return out_buf; } bluez-5.87/ell/PaxHeaders/strv.h0000644000000000000000000000005014504767710013611 xustar0020 atime=1783076232 20 ctime=1783094098 bluez-5.87/ell/strv.h0000644000000000000000000000213414504767710013272 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_STRV_H #define __ELL_STRV_H #include #include #include #ifdef __cplusplus extern "C" { #endif void l_strfreev(char **strlist); char **l_strsplit(const char *str, const char sep); char **l_strsplit_set(const char *str, const char *separators); char *l_strjoinv(char **str_array, const char delim); char **l_strv_new(void); void l_strv_free(char **str_array); DEFINE_CLEANUP_FUNC(l_strv_free); unsigned int l_strv_length(char **str_array); bool l_strv_contains(char **str_array, const char *item); char **l_strv_append(char **str_array, const char *str); char **l_strv_append_printf(char **str_array, const char *format, ...) __attribute__((format(printf, 2, 3))); char **l_strv_append_vprintf(char **str_array, const char *format, va_list args) __attribute__((format(printf, 2, 0))); char **l_strv_copy(char **str_array); bool l_strv_eq(char **a, char **b); #ifdef __cplusplus } #endif #endif /* __ELL_STRV_H */ bluez-5.87/ell/PaxHeaders/io.h0000644000000000000000000000005014504767710013222 xustar0020 atime=1783076232 20 ctime=1783094098 bluez-5.87/ell/io.h0000644000000000000000000000240114504767710012700 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_IO_H #define __ELL_IO_H #include #ifdef __cplusplus extern "C" { #endif struct l_io; typedef void (*l_io_debug_cb_t) (const char *str, void *user_data); typedef bool (*l_io_read_cb_t) (struct l_io *io, void *user_data); typedef bool (*l_io_write_cb_t) (struct l_io *io, void *user_data); typedef void (*l_io_disconnect_cb_t) (struct l_io *io, void *user_data); typedef void (*l_io_destroy_cb_t) (void *user_data); struct l_io *l_io_new(int fd); void l_io_destroy(struct l_io *io); int l_io_get_fd(struct l_io *io); bool l_io_set_close_on_destroy(struct l_io *io, bool do_close); bool l_io_set_read_handler(struct l_io *io, l_io_read_cb_t callback, void *user_data, l_io_destroy_cb_t destroy); bool l_io_set_write_handler(struct l_io *io, l_io_write_cb_t callback, void *user_data, l_io_destroy_cb_t destroy); bool l_io_set_disconnect_handler(struct l_io *io, l_io_disconnect_cb_t callback, void *user_data, l_io_destroy_cb_t destroy); bool l_io_set_debug(struct l_io *io, l_io_debug_cb_t callback, void *user_data, l_io_destroy_cb_t destroy); #ifdef __cplusplus } #endif #endif /* __ELL_IO_H */ bluez-5.87/ell/PaxHeaders/tls-extensions.c0000644000000000000000000000005014560467756015616 xustar0020 atime=1783076323 20 ctime=1783094099 bluez-5.87/ell/tls-extensions.c0000644000000000000000000011563614560467756015313 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2018 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #include #include "util.h" #include "tls.h" #include "cipher.h" #include "checksum.h" #include "cert.h" #include "tls-private.h" /* Most extensions are not used when resuming a cached session */ #define SKIP_ON_RESUMPTION() \ do { \ if (tls->session_id_size && !tls->session_id_new) \ return -ENOMSG; \ } while (0); /* RFC 7919, Section A.1 */ static const uint8_t tls_ffdhe2048_prime[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xad, 0xf8, 0x54, 0x58, 0xa2, 0xbb, 0x4a, 0x9a, 0xaf, 0xdc, 0x56, 0x20, 0x27, 0x3d, 0x3c, 0xf1, 0xd8, 0xb9, 0xc5, 0x83, 0xce, 0x2d, 0x36, 0x95, 0xa9, 0xe1, 0x36, 0x41, 0x14, 0x64, 0x33, 0xfb, 0xcc, 0x93, 0x9d, 0xce, 0x24, 0x9b, 0x3e, 0xf9, 0x7d, 0x2f, 0xe3, 0x63, 0x63, 0x0c, 0x75, 0xd8, 0xf6, 0x81, 0xb2, 0x02, 0xae, 0xc4, 0x61, 0x7a, 0xd3, 0xdf, 0x1e, 0xd5, 0xd5, 0xfd, 0x65, 0x61, 0x24, 0x33, 0xf5, 0x1f, 0x5f, 0x06, 0x6e, 0xd0, 0x85, 0x63, 0x65, 0x55, 0x3d, 0xed, 0x1a, 0xf3, 0xb5, 0x57, 0x13, 0x5e, 0x7f, 0x57, 0xc9, 0x35, 0x98, 0x4f, 0x0c, 0x70, 0xe0, 0xe6, 0x8b, 0x77, 0xe2, 0xa6, 0x89, 0xda, 0xf3, 0xef, 0xe8, 0x72, 0x1d, 0xf1, 0x58, 0xa1, 0x36, 0xad, 0xe7, 0x35, 0x30, 0xac, 0xca, 0x4f, 0x48, 0x3a, 0x79, 0x7a, 0xbc, 0x0a, 0xb1, 0x82, 0xb3, 0x24, 0xfb, 0x61, 0xd1, 0x08, 0xa9, 0x4b, 0xb2, 0xc8, 0xe3, 0xfb, 0xb9, 0x6a, 0xda, 0xb7, 0x60, 0xd7, 0xf4, 0x68, 0x1d, 0x4f, 0x42, 0xa3, 0xde, 0x39, 0x4d, 0xf4, 0xae, 0x56, 0xed, 0xe7, 0x63, 0x72, 0xbb, 0x19, 0x0b, 0x07, 0xa7, 0xc8, 0xee, 0x0a, 0x6d, 0x70, 0x9e, 0x02, 0xfc, 0xe1, 0xcd, 0xf7, 0xe2, 0xec, 0xc0, 0x34, 0x04, 0xcd, 0x28, 0x34, 0x2f, 0x61, 0x91, 0x72, 0xfe, 0x9c, 0xe9, 0x85, 0x83, 0xff, 0x8e, 0x4f, 0x12, 0x32, 0xee, 0xf2, 0x81, 0x83, 0xc3, 0xfe, 0x3b, 0x1b, 0x4c, 0x6f, 0xad, 0x73, 0x3b, 0xb5, 0xfc, 0xbc, 0x2e, 0xc2, 0x20, 0x05, 0xc5, 0x8e, 0xf1, 0x83, 0x7d, 0x16, 0x83, 0xb2, 0xc6, 0xf3, 0x4a, 0x26, 0xc1, 0xb2, 0xef, 0xfa, 0x88, 0x6b, 0x42, 0x38, 0x61, 0x28, 0x5c, 0x97, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }; /* RFC 7919, Section A.2 */ static const uint8_t tls_ffdhe3072_prime[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xad, 0xf8, 0x54, 0x58, 0xa2, 0xbb, 0x4a, 0x9a, 0xaf, 0xdc, 0x56, 0x20, 0x27, 0x3d, 0x3c, 0xf1, 0xd8, 0xb9, 0xc5, 0x83, 0xce, 0x2d, 0x36, 0x95, 0xa9, 0xe1, 0x36, 0x41, 0x14, 0x64, 0x33, 0xfb, 0xcc, 0x93, 0x9d, 0xce, 0x24, 0x9b, 0x3e, 0xf9, 0x7d, 0x2f, 0xe3, 0x63, 0x63, 0x0c, 0x75, 0xd8, 0xf6, 0x81, 0xb2, 0x02, 0xae, 0xc4, 0x61, 0x7a, 0xd3, 0xdf, 0x1e, 0xd5, 0xd5, 0xfd, 0x65, 0x61, 0x24, 0x33, 0xf5, 0x1f, 0x5f, 0x06, 0x6e, 0xd0, 0x85, 0x63, 0x65, 0x55, 0x3d, 0xed, 0x1a, 0xf3, 0xb5, 0x57, 0x13, 0x5e, 0x7f, 0x57, 0xc9, 0x35, 0x98, 0x4f, 0x0c, 0x70, 0xe0, 0xe6, 0x8b, 0x77, 0xe2, 0xa6, 0x89, 0xda, 0xf3, 0xef, 0xe8, 0x72, 0x1d, 0xf1, 0x58, 0xa1, 0x36, 0xad, 0xe7, 0x35, 0x30, 0xac, 0xca, 0x4f, 0x48, 0x3a, 0x79, 0x7a, 0xbc, 0x0a, 0xb1, 0x82, 0xb3, 0x24, 0xfb, 0x61, 0xd1, 0x08, 0xa9, 0x4b, 0xb2, 0xc8, 0xe3, 0xfb, 0xb9, 0x6a, 0xda, 0xb7, 0x60, 0xd7, 0xf4, 0x68, 0x1d, 0x4f, 0x42, 0xa3, 0xde, 0x39, 0x4d, 0xf4, 0xae, 0x56, 0xed, 0xe7, 0x63, 0x72, 0xbb, 0x19, 0x0b, 0x07, 0xa7, 0xc8, 0xee, 0x0a, 0x6d, 0x70, 0x9e, 0x02, 0xfc, 0xe1, 0xcd, 0xf7, 0xe2, 0xec, 0xc0, 0x34, 0x04, 0xcd, 0x28, 0x34, 0x2f, 0x61, 0x91, 0x72, 0xfe, 0x9c, 0xe9, 0x85, 0x83, 0xff, 0x8e, 0x4f, 0x12, 0x32, 0xee, 0xf2, 0x81, 0x83, 0xc3, 0xfe, 0x3b, 0x1b, 0x4c, 0x6f, 0xad, 0x73, 0x3b, 0xb5, 0xfc, 0xbc, 0x2e, 0xc2, 0x20, 0x05, 0xc5, 0x8e, 0xf1, 0x83, 0x7d, 0x16, 0x83, 0xb2, 0xc6, 0xf3, 0x4a, 0x26, 0xc1, 0xb2, 0xef, 0xfa, 0x88, 0x6b, 0x42, 0x38, 0x61, 0x1f, 0xcf, 0xdc, 0xde, 0x35, 0x5b, 0x3b, 0x65, 0x19, 0x03, 0x5b, 0xbc, 0x34, 0xf4, 0xde, 0xf9, 0x9c, 0x02, 0x38, 0x61, 0xb4, 0x6f, 0xc9, 0xd6, 0xe6, 0xc9, 0x07, 0x7a, 0xd9, 0x1d, 0x26, 0x91, 0xf7, 0xf7, 0xee, 0x59, 0x8c, 0xb0, 0xfa, 0xc1, 0x86, 0xd9, 0x1c, 0xae, 0xfe, 0x13, 0x09, 0x85, 0x13, 0x92, 0x70, 0xb4, 0x13, 0x0c, 0x93, 0xbc, 0x43, 0x79, 0x44, 0xf4, 0xfd, 0x44, 0x52, 0xe2, 0xd7, 0x4d, 0xd3, 0x64, 0xf2, 0xe2, 0x1e, 0x71, 0xf5, 0x4b, 0xff, 0x5c, 0xae, 0x82, 0xab, 0x9c, 0x9d, 0xf6, 0x9e, 0xe8, 0x6d, 0x2b, 0xc5, 0x22, 0x36, 0x3a, 0x0d, 0xab, 0xc5, 0x21, 0x97, 0x9b, 0x0d, 0xea, 0xda, 0x1d, 0xbf, 0x9a, 0x42, 0xd5, 0xc4, 0x48, 0x4e, 0x0a, 0xbc, 0xd0, 0x6b, 0xfa, 0x53, 0xdd, 0xef, 0x3c, 0x1b, 0x20, 0xee, 0x3f, 0xd5, 0x9d, 0x7c, 0x25, 0xe4, 0x1d, 0x2b, 0x66, 0xc6, 0x2e, 0x37, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }; /* RFC 7919, Section A.3 */ static const uint8_t tls_ffdhe4096_prime[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xad, 0xf8, 0x54, 0x58, 0xa2, 0xbb, 0x4a, 0x9a, 0xaf, 0xdc, 0x56, 0x20, 0x27, 0x3d, 0x3c, 0xf1, 0xd8, 0xb9, 0xc5, 0x83, 0xce, 0x2d, 0x36, 0x95, 0xa9, 0xe1, 0x36, 0x41, 0x14, 0x64, 0x33, 0xfb, 0xcc, 0x93, 0x9d, 0xce, 0x24, 0x9b, 0x3e, 0xf9, 0x7d, 0x2f, 0xe3, 0x63, 0x63, 0x0c, 0x75, 0xd8, 0xf6, 0x81, 0xb2, 0x02, 0xae, 0xc4, 0x61, 0x7a, 0xd3, 0xdf, 0x1e, 0xd5, 0xd5, 0xfd, 0x65, 0x61, 0x24, 0x33, 0xf5, 0x1f, 0x5f, 0x06, 0x6e, 0xd0, 0x85, 0x63, 0x65, 0x55, 0x3d, 0xed, 0x1a, 0xf3, 0xb5, 0x57, 0x13, 0x5e, 0x7f, 0x57, 0xc9, 0x35, 0x98, 0x4f, 0x0c, 0x70, 0xe0, 0xe6, 0x8b, 0x77, 0xe2, 0xa6, 0x89, 0xda, 0xf3, 0xef, 0xe8, 0x72, 0x1d, 0xf1, 0x58, 0xa1, 0x36, 0xad, 0xe7, 0x35, 0x30, 0xac, 0xca, 0x4f, 0x48, 0x3a, 0x79, 0x7a, 0xbc, 0x0a, 0xb1, 0x82, 0xb3, 0x24, 0xfb, 0x61, 0xd1, 0x08, 0xa9, 0x4b, 0xb2, 0xc8, 0xe3, 0xfb, 0xb9, 0x6a, 0xda, 0xb7, 0x60, 0xd7, 0xf4, 0x68, 0x1d, 0x4f, 0x42, 0xa3, 0xde, 0x39, 0x4d, 0xf4, 0xae, 0x56, 0xed, 0xe7, 0x63, 0x72, 0xbb, 0x19, 0x0b, 0x07, 0xa7, 0xc8, 0xee, 0x0a, 0x6d, 0x70, 0x9e, 0x02, 0xfc, 0xe1, 0xcd, 0xf7, 0xe2, 0xec, 0xc0, 0x34, 0x04, 0xcd, 0x28, 0x34, 0x2f, 0x61, 0x91, 0x72, 0xfe, 0x9c, 0xe9, 0x85, 0x83, 0xff, 0x8e, 0x4f, 0x12, 0x32, 0xee, 0xf2, 0x81, 0x83, 0xc3, 0xfe, 0x3b, 0x1b, 0x4c, 0x6f, 0xad, 0x73, 0x3b, 0xb5, 0xfc, 0xbc, 0x2e, 0xc2, 0x20, 0x05, 0xc5, 0x8e, 0xf1, 0x83, 0x7d, 0x16, 0x83, 0xb2, 0xc6, 0xf3, 0x4a, 0x26, 0xc1, 0xb2, 0xef, 0xfa, 0x88, 0x6b, 0x42, 0x38, 0x61, 0x1f, 0xcf, 0xdc, 0xde, 0x35, 0x5b, 0x3b, 0x65, 0x19, 0x03, 0x5b, 0xbc, 0x34, 0xf4, 0xde, 0xf9, 0x9c, 0x02, 0x38, 0x61, 0xb4, 0x6f, 0xc9, 0xd6, 0xe6, 0xc9, 0x07, 0x7a, 0xd9, 0x1d, 0x26, 0x91, 0xf7, 0xf7, 0xee, 0x59, 0x8c, 0xb0, 0xfa, 0xc1, 0x86, 0xd9, 0x1c, 0xae, 0xfe, 0x13, 0x09, 0x85, 0x13, 0x92, 0x70, 0xb4, 0x13, 0x0c, 0x93, 0xbc, 0x43, 0x79, 0x44, 0xf4, 0xfd, 0x44, 0x52, 0xe2, 0xd7, 0x4d, 0xd3, 0x64, 0xf2, 0xe2, 0x1e, 0x71, 0xf5, 0x4b, 0xff, 0x5c, 0xae, 0x82, 0xab, 0x9c, 0x9d, 0xf6, 0x9e, 0xe8, 0x6d, 0x2b, 0xc5, 0x22, 0x36, 0x3a, 0x0d, 0xab, 0xc5, 0x21, 0x97, 0x9b, 0x0d, 0xea, 0xda, 0x1d, 0xbf, 0x9a, 0x42, 0xd5, 0xc4, 0x48, 0x4e, 0x0a, 0xbc, 0xd0, 0x6b, 0xfa, 0x53, 0xdd, 0xef, 0x3c, 0x1b, 0x20, 0xee, 0x3f, 0xd5, 0x9d, 0x7c, 0x25, 0xe4, 0x1d, 0x2b, 0x66, 0x9e, 0x1e, 0xf1, 0x6e, 0x6f, 0x52, 0xc3, 0x16, 0x4d, 0xf4, 0xfb, 0x79, 0x30, 0xe9, 0xe4, 0xe5, 0x88, 0x57, 0xb6, 0xac, 0x7d, 0x5f, 0x42, 0xd6, 0x9f, 0x6d, 0x18, 0x77, 0x63, 0xcf, 0x1d, 0x55, 0x03, 0x40, 0x04, 0x87, 0xf5, 0x5b, 0xa5, 0x7e, 0x31, 0xcc, 0x7a, 0x71, 0x35, 0xc8, 0x86, 0xef, 0xb4, 0x31, 0x8a, 0xed, 0x6a, 0x1e, 0x01, 0x2d, 0x9e, 0x68, 0x32, 0xa9, 0x07, 0x60, 0x0a, 0x91, 0x81, 0x30, 0xc4, 0x6d, 0xc7, 0x78, 0xf9, 0x71, 0xad, 0x00, 0x38, 0x09, 0x29, 0x99, 0xa3, 0x33, 0xcb, 0x8b, 0x7a, 0x1a, 0x1d, 0xb9, 0x3d, 0x71, 0x40, 0x00, 0x3c, 0x2a, 0x4e, 0xce, 0xa9, 0xf9, 0x8d, 0x0a, 0xcc, 0x0a, 0x82, 0x91, 0xcd, 0xce, 0xc9, 0x7d, 0xcf, 0x8e, 0xc9, 0xb5, 0x5a, 0x7f, 0x88, 0xa4, 0x6b, 0x4d, 0xb5, 0xa8, 0x51, 0xf4, 0x41, 0x82, 0xe1, 0xc6, 0x8a, 0x00, 0x7e, 0x5e, 0x65, 0x5f, 0x6a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }; /* RFC 7919, Section A.4 */ static const uint8_t tls_ffdhe6144_prime[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xad, 0xf8, 0x54, 0x58, 0xa2, 0xbb, 0x4a, 0x9a, 0xaf, 0xdc, 0x56, 0x20, 0x27, 0x3d, 0x3c, 0xf1, 0xd8, 0xb9, 0xc5, 0x83, 0xce, 0x2d, 0x36, 0x95, 0xa9, 0xe1, 0x36, 0x41, 0x14, 0x64, 0x33, 0xfb, 0xcc, 0x93, 0x9d, 0xce, 0x24, 0x9b, 0x3e, 0xf9, 0x7d, 0x2f, 0xe3, 0x63, 0x63, 0x0c, 0x75, 0xd8, 0xf6, 0x81, 0xb2, 0x02, 0xae, 0xc4, 0x61, 0x7a, 0xd3, 0xdf, 0x1e, 0xd5, 0xd5, 0xfd, 0x65, 0x61, 0x24, 0x33, 0xf5, 0x1f, 0x5f, 0x06, 0x6e, 0xd0, 0x85, 0x63, 0x65, 0x55, 0x3d, 0xed, 0x1a, 0xf3, 0xb5, 0x57, 0x13, 0x5e, 0x7f, 0x57, 0xc9, 0x35, 0x98, 0x4f, 0x0c, 0x70, 0xe0, 0xe6, 0x8b, 0x77, 0xe2, 0xa6, 0x89, 0xda, 0xf3, 0xef, 0xe8, 0x72, 0x1d, 0xf1, 0x58, 0xa1, 0x36, 0xad, 0xe7, 0x35, 0x30, 0xac, 0xca, 0x4f, 0x48, 0x3a, 0x79, 0x7a, 0xbc, 0x0a, 0xb1, 0x82, 0xb3, 0x24, 0xfb, 0x61, 0xd1, 0x08, 0xa9, 0x4b, 0xb2, 0xc8, 0xe3, 0xfb, 0xb9, 0x6a, 0xda, 0xb7, 0x60, 0xd7, 0xf4, 0x68, 0x1d, 0x4f, 0x42, 0xa3, 0xde, 0x39, 0x4d, 0xf4, 0xae, 0x56, 0xed, 0xe7, 0x63, 0x72, 0xbb, 0x19, 0x0b, 0x07, 0xa7, 0xc8, 0xee, 0x0a, 0x6d, 0x70, 0x9e, 0x02, 0xfc, 0xe1, 0xcd, 0xf7, 0xe2, 0xec, 0xc0, 0x34, 0x04, 0xcd, 0x28, 0x34, 0x2f, 0x61, 0x91, 0x72, 0xfe, 0x9c, 0xe9, 0x85, 0x83, 0xff, 0x8e, 0x4f, 0x12, 0x32, 0xee, 0xf2, 0x81, 0x83, 0xc3, 0xfe, 0x3b, 0x1b, 0x4c, 0x6f, 0xad, 0x73, 0x3b, 0xb5, 0xfc, 0xbc, 0x2e, 0xc2, 0x20, 0x05, 0xc5, 0x8e, 0xf1, 0x83, 0x7d, 0x16, 0x83, 0xb2, 0xc6, 0xf3, 0x4a, 0x26, 0xc1, 0xb2, 0xef, 0xfa, 0x88, 0x6b, 0x42, 0x38, 0x61, 0x1f, 0xcf, 0xdc, 0xde, 0x35, 0x5b, 0x3b, 0x65, 0x19, 0x03, 0x5b, 0xbc, 0x34, 0xf4, 0xde, 0xf9, 0x9c, 0x02, 0x38, 0x61, 0xb4, 0x6f, 0xc9, 0xd6, 0xe6, 0xc9, 0x07, 0x7a, 0xd9, 0x1d, 0x26, 0x91, 0xf7, 0xf7, 0xee, 0x59, 0x8c, 0xb0, 0xfa, 0xc1, 0x86, 0xd9, 0x1c, 0xae, 0xfe, 0x13, 0x09, 0x85, 0x13, 0x92, 0x70, 0xb4, 0x13, 0x0c, 0x93, 0xbc, 0x43, 0x79, 0x44, 0xf4, 0xfd, 0x44, 0x52, 0xe2, 0xd7, 0x4d, 0xd3, 0x64, 0xf2, 0xe2, 0x1e, 0x71, 0xf5, 0x4b, 0xff, 0x5c, 0xae, 0x82, 0xab, 0x9c, 0x9d, 0xf6, 0x9e, 0xe8, 0x6d, 0x2b, 0xc5, 0x22, 0x36, 0x3a, 0x0d, 0xab, 0xc5, 0x21, 0x97, 0x9b, 0x0d, 0xea, 0xda, 0x1d, 0xbf, 0x9a, 0x42, 0xd5, 0xc4, 0x48, 0x4e, 0x0a, 0xbc, 0xd0, 0x6b, 0xfa, 0x53, 0xdd, 0xef, 0x3c, 0x1b, 0x20, 0xee, 0x3f, 0xd5, 0x9d, 0x7c, 0x25, 0xe4, 0x1d, 0x2b, 0x66, 0x9e, 0x1e, 0xf1, 0x6e, 0x6f, 0x52, 0xc3, 0x16, 0x4d, 0xf4, 0xfb, 0x79, 0x30, 0xe9, 0xe4, 0xe5, 0x88, 0x57, 0xb6, 0xac, 0x7d, 0x5f, 0x42, 0xd6, 0x9f, 0x6d, 0x18, 0x77, 0x63, 0xcf, 0x1d, 0x55, 0x03, 0x40, 0x04, 0x87, 0xf5, 0x5b, 0xa5, 0x7e, 0x31, 0xcc, 0x7a, 0x71, 0x35, 0xc8, 0x86, 0xef, 0xb4, 0x31, 0x8a, 0xed, 0x6a, 0x1e, 0x01, 0x2d, 0x9e, 0x68, 0x32, 0xa9, 0x07, 0x60, 0x0a, 0x91, 0x81, 0x30, 0xc4, 0x6d, 0xc7, 0x78, 0xf9, 0x71, 0xad, 0x00, 0x38, 0x09, 0x29, 0x99, 0xa3, 0x33, 0xcb, 0x8b, 0x7a, 0x1a, 0x1d, 0xb9, 0x3d, 0x71, 0x40, 0x00, 0x3c, 0x2a, 0x4e, 0xce, 0xa9, 0xf9, 0x8d, 0x0a, 0xcc, 0x0a, 0x82, 0x91, 0xcd, 0xce, 0xc9, 0x7d, 0xcf, 0x8e, 0xc9, 0xb5, 0x5a, 0x7f, 0x88, 0xa4, 0x6b, 0x4d, 0xb5, 0xa8, 0x51, 0xf4, 0x41, 0x82, 0xe1, 0xc6, 0x8a, 0x00, 0x7e, 0x5e, 0x0d, 0xd9, 0x02, 0x0b, 0xfd, 0x64, 0xb6, 0x45, 0x03, 0x6c, 0x7a, 0x4e, 0x67, 0x7d, 0x2c, 0x38, 0x53, 0x2a, 0x3a, 0x23, 0xba, 0x44, 0x42, 0xca, 0xf5, 0x3e, 0xa6, 0x3b, 0xb4, 0x54, 0x32, 0x9b, 0x76, 0x24, 0xc8, 0x91, 0x7b, 0xdd, 0x64, 0xb1, 0xc0, 0xfd, 0x4c, 0xb3, 0x8e, 0x8c, 0x33, 0x4c, 0x70, 0x1c, 0x3a, 0xcd, 0xad, 0x06, 0x57, 0xfc, 0xcf, 0xec, 0x71, 0x9b, 0x1f, 0x5c, 0x3e, 0x4e, 0x46, 0x04, 0x1f, 0x38, 0x81, 0x47, 0xfb, 0x4c, 0xfd, 0xb4, 0x77, 0xa5, 0x24, 0x71, 0xf7, 0xa9, 0xa9, 0x69, 0x10, 0xb8, 0x55, 0x32, 0x2e, 0xdb, 0x63, 0x40, 0xd8, 0xa0, 0x0e, 0xf0, 0x92, 0x35, 0x05, 0x11, 0xe3, 0x0a, 0xbe, 0xc1, 0xff, 0xf9, 0xe3, 0xa2, 0x6e, 0x7f, 0xb2, 0x9f, 0x8c, 0x18, 0x30, 0x23, 0xc3, 0x58, 0x7e, 0x38, 0xda, 0x00, 0x77, 0xd9, 0xb4, 0x76, 0x3e, 0x4e, 0x4b, 0x94, 0xb2, 0xbb, 0xc1, 0x94, 0xc6, 0x65, 0x1e, 0x77, 0xca, 0xf9, 0x92, 0xee, 0xaa, 0xc0, 0x23, 0x2a, 0x28, 0x1b, 0xf6, 0xb3, 0xa7, 0x39, 0xc1, 0x22, 0x61, 0x16, 0x82, 0x0a, 0xe8, 0xdb, 0x58, 0x47, 0xa6, 0x7c, 0xbe, 0xf9, 0xc9, 0x09, 0x1b, 0x46, 0x2d, 0x53, 0x8c, 0xd7, 0x2b, 0x03, 0x74, 0x6a, 0xe7, 0x7f, 0x5e, 0x62, 0x29, 0x2c, 0x31, 0x15, 0x62, 0xa8, 0x46, 0x50, 0x5d, 0xc8, 0x2d, 0xb8, 0x54, 0x33, 0x8a, 0xe4, 0x9f, 0x52, 0x35, 0xc9, 0x5b, 0x91, 0x17, 0x8c, 0xcf, 0x2d, 0xd5, 0xca, 0xce, 0xf4, 0x03, 0xec, 0x9d, 0x18, 0x10, 0xc6, 0x27, 0x2b, 0x04, 0x5b, 0x3b, 0x71, 0xf9, 0xdc, 0x6b, 0x80, 0xd6, 0x3f, 0xdd, 0x4a, 0x8e, 0x9a, 0xdb, 0x1e, 0x69, 0x62, 0xa6, 0x95, 0x26, 0xd4, 0x31, 0x61, 0xc1, 0xa4, 0x1d, 0x57, 0x0d, 0x79, 0x38, 0xda, 0xd4, 0xa4, 0x0e, 0x32, 0x9c, 0xd0, 0xe4, 0x0e, 0x65, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }; /* RFC 7919, Section A.5 */ static const uint8_t tls_ffdhe8192_prime[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xad, 0xf8, 0x54, 0x58, 0xa2, 0xbb, 0x4a, 0x9a, 0xaf, 0xdc, 0x56, 0x20, 0x27, 0x3d, 0x3c, 0xf1, 0xd8, 0xb9, 0xc5, 0x83, 0xce, 0x2d, 0x36, 0x95, 0xa9, 0xe1, 0x36, 0x41, 0x14, 0x64, 0x33, 0xfb, 0xcc, 0x93, 0x9d, 0xce, 0x24, 0x9b, 0x3e, 0xf9, 0x7d, 0x2f, 0xe3, 0x63, 0x63, 0x0c, 0x75, 0xd8, 0xf6, 0x81, 0xb2, 0x02, 0xae, 0xc4, 0x61, 0x7a, 0xd3, 0xdf, 0x1e, 0xd5, 0xd5, 0xfd, 0x65, 0x61, 0x24, 0x33, 0xf5, 0x1f, 0x5f, 0x06, 0x6e, 0xd0, 0x85, 0x63, 0x65, 0x55, 0x3d, 0xed, 0x1a, 0xf3, 0xb5, 0x57, 0x13, 0x5e, 0x7f, 0x57, 0xc9, 0x35, 0x98, 0x4f, 0x0c, 0x70, 0xe0, 0xe6, 0x8b, 0x77, 0xe2, 0xa6, 0x89, 0xda, 0xf3, 0xef, 0xe8, 0x72, 0x1d, 0xf1, 0x58, 0xa1, 0x36, 0xad, 0xe7, 0x35, 0x30, 0xac, 0xca, 0x4f, 0x48, 0x3a, 0x79, 0x7a, 0xbc, 0x0a, 0xb1, 0x82, 0xb3, 0x24, 0xfb, 0x61, 0xd1, 0x08, 0xa9, 0x4b, 0xb2, 0xc8, 0xe3, 0xfb, 0xb9, 0x6a, 0xda, 0xb7, 0x60, 0xd7, 0xf4, 0x68, 0x1d, 0x4f, 0x42, 0xa3, 0xde, 0x39, 0x4d, 0xf4, 0xae, 0x56, 0xed, 0xe7, 0x63, 0x72, 0xbb, 0x19, 0x0b, 0x07, 0xa7, 0xc8, 0xee, 0x0a, 0x6d, 0x70, 0x9e, 0x02, 0xfc, 0xe1, 0xcd, 0xf7, 0xe2, 0xec, 0xc0, 0x34, 0x04, 0xcd, 0x28, 0x34, 0x2f, 0x61, 0x91, 0x72, 0xfe, 0x9c, 0xe9, 0x85, 0x83, 0xff, 0x8e, 0x4f, 0x12, 0x32, 0xee, 0xf2, 0x81, 0x83, 0xc3, 0xfe, 0x3b, 0x1b, 0x4c, 0x6f, 0xad, 0x73, 0x3b, 0xb5, 0xfc, 0xbc, 0x2e, 0xc2, 0x20, 0x05, 0xc5, 0x8e, 0xf1, 0x83, 0x7d, 0x16, 0x83, 0xb2, 0xc6, 0xf3, 0x4a, 0x26, 0xc1, 0xb2, 0xef, 0xfa, 0x88, 0x6b, 0x42, 0x38, 0x61, 0x1f, 0xcf, 0xdc, 0xde, 0x35, 0x5b, 0x3b, 0x65, 0x19, 0x03, 0x5b, 0xbc, 0x34, 0xf4, 0xde, 0xf9, 0x9c, 0x02, 0x38, 0x61, 0xb4, 0x6f, 0xc9, 0xd6, 0xe6, 0xc9, 0x07, 0x7a, 0xd9, 0x1d, 0x26, 0x91, 0xf7, 0xf7, 0xee, 0x59, 0x8c, 0xb0, 0xfa, 0xc1, 0x86, 0xd9, 0x1c, 0xae, 0xfe, 0x13, 0x09, 0x85, 0x13, 0x92, 0x70, 0xb4, 0x13, 0x0c, 0x93, 0xbc, 0x43, 0x79, 0x44, 0xf4, 0xfd, 0x44, 0x52, 0xe2, 0xd7, 0x4d, 0xd3, 0x64, 0xf2, 0xe2, 0x1e, 0x71, 0xf5, 0x4b, 0xff, 0x5c, 0xae, 0x82, 0xab, 0x9c, 0x9d, 0xf6, 0x9e, 0xe8, 0x6d, 0x2b, 0xc5, 0x22, 0x36, 0x3a, 0x0d, 0xab, 0xc5, 0x21, 0x97, 0x9b, 0x0d, 0xea, 0xda, 0x1d, 0xbf, 0x9a, 0x42, 0xd5, 0xc4, 0x48, 0x4e, 0x0a, 0xbc, 0xd0, 0x6b, 0xfa, 0x53, 0xdd, 0xef, 0x3c, 0x1b, 0x20, 0xee, 0x3f, 0xd5, 0x9d, 0x7c, 0x25, 0xe4, 0x1d, 0x2b, 0x66, 0x9e, 0x1e, 0xf1, 0x6e, 0x6f, 0x52, 0xc3, 0x16, 0x4d, 0xf4, 0xfb, 0x79, 0x30, 0xe9, 0xe4, 0xe5, 0x88, 0x57, 0xb6, 0xac, 0x7d, 0x5f, 0x42, 0xd6, 0x9f, 0x6d, 0x18, 0x77, 0x63, 0xcf, 0x1d, 0x55, 0x03, 0x40, 0x04, 0x87, 0xf5, 0x5b, 0xa5, 0x7e, 0x31, 0xcc, 0x7a, 0x71, 0x35, 0xc8, 0x86, 0xef, 0xb4, 0x31, 0x8a, 0xed, 0x6a, 0x1e, 0x01, 0x2d, 0x9e, 0x68, 0x32, 0xa9, 0x07, 0x60, 0x0a, 0x91, 0x81, 0x30, 0xc4, 0x6d, 0xc7, 0x78, 0xf9, 0x71, 0xad, 0x00, 0x38, 0x09, 0x29, 0x99, 0xa3, 0x33, 0xcb, 0x8b, 0x7a, 0x1a, 0x1d, 0xb9, 0x3d, 0x71, 0x40, 0x00, 0x3c, 0x2a, 0x4e, 0xce, 0xa9, 0xf9, 0x8d, 0x0a, 0xcc, 0x0a, 0x82, 0x91, 0xcd, 0xce, 0xc9, 0x7d, 0xcf, 0x8e, 0xc9, 0xb5, 0x5a, 0x7f, 0x88, 0xa4, 0x6b, 0x4d, 0xb5, 0xa8, 0x51, 0xf4, 0x41, 0x82, 0xe1, 0xc6, 0x8a, 0x00, 0x7e, 0x5e, 0x0d, 0xd9, 0x02, 0x0b, 0xfd, 0x64, 0xb6, 0x45, 0x03, 0x6c, 0x7a, 0x4e, 0x67, 0x7d, 0x2c, 0x38, 0x53, 0x2a, 0x3a, 0x23, 0xba, 0x44, 0x42, 0xca, 0xf5, 0x3e, 0xa6, 0x3b, 0xb4, 0x54, 0x32, 0x9b, 0x76, 0x24, 0xc8, 0x91, 0x7b, 0xdd, 0x64, 0xb1, 0xc0, 0xfd, 0x4c, 0xb3, 0x8e, 0x8c, 0x33, 0x4c, 0x70, 0x1c, 0x3a, 0xcd, 0xad, 0x06, 0x57, 0xfc, 0xcf, 0xec, 0x71, 0x9b, 0x1f, 0x5c, 0x3e, 0x4e, 0x46, 0x04, 0x1f, 0x38, 0x81, 0x47, 0xfb, 0x4c, 0xfd, 0xb4, 0x77, 0xa5, 0x24, 0x71, 0xf7, 0xa9, 0xa9, 0x69, 0x10, 0xb8, 0x55, 0x32, 0x2e, 0xdb, 0x63, 0x40, 0xd8, 0xa0, 0x0e, 0xf0, 0x92, 0x35, 0x05, 0x11, 0xe3, 0x0a, 0xbe, 0xc1, 0xff, 0xf9, 0xe3, 0xa2, 0x6e, 0x7f, 0xb2, 0x9f, 0x8c, 0x18, 0x30, 0x23, 0xc3, 0x58, 0x7e, 0x38, 0xda, 0x00, 0x77, 0xd9, 0xb4, 0x76, 0x3e, 0x4e, 0x4b, 0x94, 0xb2, 0xbb, 0xc1, 0x94, 0xc6, 0x65, 0x1e, 0x77, 0xca, 0xf9, 0x92, 0xee, 0xaa, 0xc0, 0x23, 0x2a, 0x28, 0x1b, 0xf6, 0xb3, 0xa7, 0x39, 0xc1, 0x22, 0x61, 0x16, 0x82, 0x0a, 0xe8, 0xdb, 0x58, 0x47, 0xa6, 0x7c, 0xbe, 0xf9, 0xc9, 0x09, 0x1b, 0x46, 0x2d, 0x53, 0x8c, 0xd7, 0x2b, 0x03, 0x74, 0x6a, 0xe7, 0x7f, 0x5e, 0x62, 0x29, 0x2c, 0x31, 0x15, 0x62, 0xa8, 0x46, 0x50, 0x5d, 0xc8, 0x2d, 0xb8, 0x54, 0x33, 0x8a, 0xe4, 0x9f, 0x52, 0x35, 0xc9, 0x5b, 0x91, 0x17, 0x8c, 0xcf, 0x2d, 0xd5, 0xca, 0xce, 0xf4, 0x03, 0xec, 0x9d, 0x18, 0x10, 0xc6, 0x27, 0x2b, 0x04, 0x5b, 0x3b, 0x71, 0xf9, 0xdc, 0x6b, 0x80, 0xd6, 0x3f, 0xdd, 0x4a, 0x8e, 0x9a, 0xdb, 0x1e, 0x69, 0x62, 0xa6, 0x95, 0x26, 0xd4, 0x31, 0x61, 0xc1, 0xa4, 0x1d, 0x57, 0x0d, 0x79, 0x38, 0xda, 0xd4, 0xa4, 0x0e, 0x32, 0x9c, 0xcf, 0xf4, 0x6a, 0xaa, 0x36, 0xad, 0x00, 0x4c, 0xf6, 0x00, 0xc8, 0x38, 0x1e, 0x42, 0x5a, 0x31, 0xd9, 0x51, 0xae, 0x64, 0xfd, 0xb2, 0x3f, 0xce, 0xc9, 0x50, 0x9d, 0x43, 0x68, 0x7f, 0xeb, 0x69, 0xed, 0xd1, 0xcc, 0x5e, 0x0b, 0x8c, 0xc3, 0xbd, 0xf6, 0x4b, 0x10, 0xef, 0x86, 0xb6, 0x31, 0x42, 0xa3, 0xab, 0x88, 0x29, 0x55, 0x5b, 0x2f, 0x74, 0x7c, 0x93, 0x26, 0x65, 0xcb, 0x2c, 0x0f, 0x1c, 0xc0, 0x1b, 0xd7, 0x02, 0x29, 0x38, 0x88, 0x39, 0xd2, 0xaf, 0x05, 0xe4, 0x54, 0x50, 0x4a, 0xc7, 0x8b, 0x75, 0x82, 0x82, 0x28, 0x46, 0xc0, 0xba, 0x35, 0xc3, 0x5f, 0x5c, 0x59, 0x16, 0x0c, 0xc0, 0x46, 0xfd, 0x82, 0x51, 0x54, 0x1f, 0xc6, 0x8c, 0x9c, 0x86, 0xb0, 0x22, 0xbb, 0x70, 0x99, 0x87, 0x6a, 0x46, 0x0e, 0x74, 0x51, 0xa8, 0xa9, 0x31, 0x09, 0x70, 0x3f, 0xee, 0x1c, 0x21, 0x7e, 0x6c, 0x38, 0x26, 0xe5, 0x2c, 0x51, 0xaa, 0x69, 0x1e, 0x0e, 0x42, 0x3c, 0xfc, 0x99, 0xe9, 0xe3, 0x16, 0x50, 0xc1, 0x21, 0x7b, 0x62, 0x48, 0x16, 0xcd, 0xad, 0x9a, 0x95, 0xf9, 0xd5, 0xb8, 0x01, 0x94, 0x88, 0xd9, 0xc0, 0xa0, 0xa1, 0xfe, 0x30, 0x75, 0xa5, 0x77, 0xe2, 0x31, 0x83, 0xf8, 0x1d, 0x4a, 0x3f, 0x2f, 0xa4, 0x57, 0x1e, 0xfc, 0x8c, 0xe0, 0xba, 0x8a, 0x4f, 0xe8, 0xb6, 0x85, 0x5d, 0xfe, 0x72, 0xb0, 0xa6, 0x6e, 0xde, 0xd2, 0xfb, 0xab, 0xfb, 0xe5, 0x8a, 0x30, 0xfa, 0xfa, 0xbe, 0x1c, 0x5d, 0x71, 0xa8, 0x7e, 0x2f, 0x74, 0x1e, 0xf8, 0xc1, 0xfe, 0x86, 0xfe, 0xa6, 0xbb, 0xfd, 0xe5, 0x30, 0x67, 0x7f, 0x0d, 0x97, 0xd1, 0x1d, 0x49, 0xf7, 0xa8, 0x44, 0x3d, 0x08, 0x22, 0xe5, 0x06, 0xa9, 0xf4, 0x61, 0x4e, 0x01, 0x1e, 0x2a, 0x94, 0x83, 0x8f, 0xf8, 0x8c, 0xd6, 0x8c, 0x8b, 0xb7, 0xc5, 0xc6, 0x42, 0x4c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }; /* RFC 3526, Section 3 */ static const uint8_t tls_dh14_prime[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x37, 0xed, 0x6b, 0x0b, 0xff, 0x5c, 0xb6, 0xf4, 0x06, 0xb7, 0xed, 0xee, 0x38, 0x6b, 0xfb, 0x5a, 0x89, 0x9f, 0xa5, 0xae, 0x9f, 0x24, 0x11, 0x7c, 0x4b, 0x1f, 0xe6, 0x49, 0x28, 0x66, 0x51, 0xec, 0xe4, 0x5b, 0x3d, 0xc2, 0x00, 0x7c, 0xb8, 0xa1, 0x63, 0xbf, 0x05, 0x98, 0xda, 0x48, 0x36, 0x1c, 0x55, 0xd3, 0x9a, 0x69, 0x16, 0x3f, 0xa8, 0xfd, 0x24, 0xcf, 0x5f, 0x83, 0x65, 0x5d, 0x23, 0xdc, 0xa3, 0xad, 0x96, 0x1c, 0x62, 0xf3, 0x56, 0x20, 0x85, 0x52, 0xbb, 0x9e, 0xd5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6d, 0x67, 0x0c, 0x35, 0x4e, 0x4a, 0xbc, 0x98, 0x04, 0xf1, 0x74, 0x6c, 0x08, 0xca, 0x18, 0x21, 0x7c, 0x32, 0x90, 0x5e, 0x46, 0x2e, 0x36, 0xce, 0x3b, 0xe3, 0x9e, 0x77, 0x2c, 0x18, 0x0e, 0x86, 0x03, 0x9b, 0x27, 0x83, 0xa2, 0xec, 0x07, 0xa2, 0x8f, 0xb5, 0xc5, 0x5d, 0xf0, 0x6f, 0x4c, 0x52, 0xc9, 0xde, 0x2b, 0xcb, 0xf6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7c, 0xea, 0x95, 0x6a, 0xe5, 0x15, 0xd2, 0x26, 0x18, 0x98, 0xfa, 0x05, 0x10, 0x15, 0x72, 0x8e, 0x5a, 0x8a, 0xac, 0xaa, 0x68, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }; static const struct tls_named_group tls_group_pref[] = { { "secp256r1", 23, TLS_GROUP_TYPE_EC }, { "secp384r1", 24, TLS_GROUP_TYPE_EC }, { "ffdhe2048", 256, TLS_GROUP_TYPE_FF, .ff = { .prime = tls_ffdhe2048_prime, .prime_len = sizeof(tls_ffdhe2048_prime), .generator = 2, }, }, { "ffdhe3072", 257, TLS_GROUP_TYPE_FF, .ff = { .prime = tls_ffdhe3072_prime, .prime_len = sizeof(tls_ffdhe3072_prime), .generator = 2, }, }, { "ffdhe4096", 258, TLS_GROUP_TYPE_FF, .ff = { .prime = tls_ffdhe4096_prime, .prime_len = sizeof(tls_ffdhe4096_prime), .generator = 2, }, }, { "ffdhe6144", 259, TLS_GROUP_TYPE_FF, .ff = { .prime = tls_ffdhe6144_prime, .prime_len = sizeof(tls_ffdhe6144_prime), .generator = 2, }, }, { "ffdhe8192", 260, TLS_GROUP_TYPE_FF, .ff = { .prime = tls_ffdhe8192_prime, .prime_len = sizeof(tls_ffdhe8192_prime), .generator = 2, }, }, }; /* * For now hardcode a default group for non-RFC7919 clients - same group * as some other TLS servers use, which is actually a downside because the * more common the group parameters are the less secure they are assumed * to be, but it is also a test that the group is sufficiently good. * * Eventually we need to make this configurable so that a unique * likely-prime number generated by either 'openssl dhparam' or * 'ssh-keygen -G' can be set, or parse /etc/ssh/moduli to select * a random pre-generated FFDH group each time. */ static const struct tls_named_group tls_default_ffdh_group = { "RFC3526/Oakley Group 14", 0, TLS_GROUP_TYPE_FF, .ff = { .prime = tls_dh14_prime, .prime_len = sizeof(tls_dh14_prime), .generator = 2, }, }; /* RFC 8422, Section 5.1 + RFC 7919 */ static ssize_t tls_elliptic_curves_client_write(struct l_tls *tls, uint8_t *buf, size_t len) { uint8_t *ptr = buf; unsigned int i; if (len < 2 + L_ARRAY_SIZE(tls_group_pref) * 2) return -ENOMEM; l_put_be16(L_ARRAY_SIZE(tls_group_pref) * 2, ptr); ptr += 2; for (i = 0; i < L_ARRAY_SIZE(tls_group_pref); i++) { l_put_be16(tls_group_pref[i].id, ptr); ptr += 2; } return ptr - buf; } static bool tls_elliptic_curves_client_handle(struct l_tls *tls, const uint8_t *buf, size_t len) { bool ffdh_offered = false; if (len < 2) return false; if (l_get_be16(buf) != len - 2 || (len & 1)) return false; buf += 2; len -= 2; /* * We select one group for DH and one group for ECDH and we'll * let the cipher suite selection logic decide which one is actually * used. It will take into account the client's cipher suite * preference but it could just as well look at the strengths of * the groups chosen. This is not done for simplicity but RFC 7919 * suggests the Supported Groups should actually overrule the * cipher suite preference list in case of a conflict: * "A server that encounters such a contradiction when selecting * between an ECDHE or FFDHE key exchange mechanism while trying * to respect client preferences SHOULD give priority to the * Supported Groups extension (...) but MAY resolve the * contradiction any way it sees fit." * * Not implemented: "If a non-anonymous FFDHE cipher suite is * selected and the TLS client has used this extension to offer * an FFDHE group of comparable or greater strength than the server's * public key, the server SHOULD select an FFDHE group at least * as strong as the server's public key." */ while (len) { unsigned int i; uint16_t id; const struct tls_named_group *group = NULL; id = l_get_be16(buf); buf += 2; len -= 2; if (id >> 8 == 1) /* RFC 7919 ids */ ffdh_offered = true; for (i = 0; i < L_ARRAY_SIZE(tls_group_pref); i++) if (tls_group_pref[i].id == id) { group = &tls_group_pref[i]; break; } if (!group) continue; switch (group->type) { case TLS_GROUP_TYPE_EC: if (!tls->negotiated_curve) tls->negotiated_curve = group; break; case TLS_GROUP_TYPE_FF: if (!tls->negotiated_ff_group) tls->negotiated_ff_group = group; break; } } /* * Note we need to treat DH slightly differently from ECDH groups * here because the extension is defined in RFC 8422 and if the * client offers no elliptic curves we can't use ECDH at all: * "If a server (...) is unable to complete the ECC handshake while * restricting itself to the enumerated curves (...), it MUST NOT * negotiate the use of an ECC cipher suite. Depending on what * other cipher suites are proposed by the client and supported by * the server, this may result in a fatal handshake failure alert * due to the lack of common cipher suites." * * On the other hand if the client offers no FFDH groups we can * only assume the client is okay with us picking a group. Note * the "includes any FFDHE group" part in RFC 7919 Section 4: * "If a compatible TLS server receives a Supported Groups * extension from a client that includes any FFDHE group (i.e., * any codepoint between 256 and 511, inclusive, even if unknown * to the server), and if none of the client-proposed FFDHE groups * are known and acceptable to the server, then the server MUST * NOT select an FFDHE cipher suite." */ if (tls->negotiated_curve) TLS_DEBUG("Negotiated %s", tls->negotiated_curve->name); else TLS_DEBUG("non-fatal: No common supported elliptic curves " "for ECDHE"); if (tls->negotiated_ff_group) TLS_DEBUG("Negotiated %s", tls->negotiated_ff_group->name); else if (ffdh_offered) TLS_DEBUG("non-fatal: No common supported finite-field groups " "for DHE"); else tls->negotiated_ff_group = &tls_default_ffdh_group; return true; } static bool tls_elliptic_curves_client_absent(struct l_tls *tls) { unsigned int i; for (i = 0; i < L_ARRAY_SIZE(tls_group_pref); i++) if (tls_group_pref[i].type == TLS_GROUP_TYPE_EC) { tls->negotiated_curve = &tls_group_pref[i]; break; } tls->negotiated_ff_group = &tls_default_ffdh_group; return true; } static bool tls_ec_point_formats_client_handle(struct l_tls *tls, const uint8_t *buf, size_t len) { if (len < 2) return false; if (buf[0] != len - 1) return false; if (!memchr(buf + 1, 0, len - 1)) { TLS_DEBUG("Uncompressed point format missing"); return false; } return true; } /* * For compatibility with clients respond to a valid Client Hello Supported * Point Formats extension with the hardcoded confirmation that we do * support the single valid point format. As a client we never send this * extension so we never have to handle a server response to it either. */ static ssize_t tls_ec_point_formats_server_write(struct l_tls *tls, uint8_t *buf, size_t len) { SKIP_ON_RESUMPTION(); /* RFC 4492 Section 4 */ if (len < 2) return -ENOMEM; buf[0] = 0x01; /* ec_point_format_list length */ buf[1] = 0x00; /* uncompressed */ return 2; } /* * This is used to append the list of signature algorithm and hash type * combinations we support to the Signature Algorithms client hello * extension (on the client) and the Certificate Request message (on the * server). In both cases we need to list the algorithms we support for * two use cases: certificate chain verification and signing/verifying * Server Key Exchange params (server->client) or Certificate Verify * data (client->server). * * For the server side RFC 5462, Section 7.4.1.4.1 says: * "If the client [...] is willing to use them for verifying * messages sent by the server, i.e., server certificates and * server key exchange [...] it MUST send the * signature_algorithms extension, listing the algorithms it * is willing to accept." * * As for the certificate chains we mostly rely on the kernel to do * this so when we receive the list we do not currently verify the * that the whole chain uses only algorithms from the list on either * side (TODO). But we know that the chain verification in the kernel * can use a superset of the hash algorithms l_checksum supports. * For the Server Key Exchange and Certificate Verify signatures we * use l_checksum but we need to map the TLS-specific hash IDs to * enum l_checksum_type using the tls_handshake_hash_data list in * signature->sign() and signature->verify(), so we use * tls_handshake_hash_data as the definitive list of allowed hash * algorithms. * * Our supported signature algorithms can work with any hash type so we * basically have to send all possible combinations of the signature * algorithm IDs from the supported cipher suites (except anonymous) * with the hash algorithms we can use for signature verification, * i.e. those in the tls_handshake_hash_data table. */ ssize_t tls_write_signature_algorithms(struct l_tls *tls, uint8_t *buf, size_t len) { uint8_t *ptr = buf; unsigned int i, j; struct tls_cipher_suite **suite; uint8_t sig_alg_ids[16]; uint8_t hash_ids[16]; unsigned int sig_alg_cnt = 0; unsigned int hash_cnt = 0; for (suite = tls->cipher_suite_pref_list; *suite; suite++) { uint8_t id; if (!(*suite)->signature) continue; id = (*suite)->signature->id; if (memchr(sig_alg_ids, id, sig_alg_cnt)) continue; if (!tls_cipher_suite_is_compatible(tls, *suite, NULL)) continue; if (sig_alg_cnt >= sizeof(sig_alg_ids)) return -ENOMEM; sig_alg_ids[sig_alg_cnt++] = id; } for (i = 0; i < __HANDSHAKE_HASH_COUNT; i++) { const struct tls_hash_algorithm *hash = &tls_handshake_hash_data[i]; bool supported; /* * The hash types in the Signature Algorithms extension are * all supported hashes but the ones in the Certificate * Request (server->client) must be in the set for which we * maintain handshake message hashes because that is going * to be used in Certificate Verify. */ if (tls->server) supported = !!tls->handshake_hash[i]; else supported = l_checksum_is_supported(hash->l_id, false); if (supported) hash_ids[hash_cnt++] = hash->tls_id; } if (len < 2 + sig_alg_cnt * hash_cnt * 2) return -ENOMEM; l_put_be16(sig_alg_cnt * hash_cnt * 2, ptr); ptr += 2; for (i = 0; i < sig_alg_cnt; i++) for (j = 0; j < hash_cnt; j++) { *ptr++ = hash_ids[j]; *ptr++ = sig_alg_ids[i]; } return ptr - buf; } ssize_t tls_parse_signature_algorithms(struct l_tls *tls, const uint8_t *buf, size_t len) { const uint8_t *ptr = buf; enum handshake_hash_type first_supported, hash; const struct tls_hash_algorithm *preferred; struct tls_cipher_suite **suite; uint8_t sig_alg_ids[16]; unsigned int sig_alg_cnt = 0; /* * This only makes sense as a variable-length field, assume * there's a typo in RFC5246 7.4.4 here. */ if (len < 4) return -EINVAL; if (l_get_be16(ptr) > len - 2) return -EINVAL; len = l_get_be16(ptr); ptr += 2; if (len & 1) return -EINVAL; for (suite = tls->cipher_suite_pref_list; *suite; suite++) { uint8_t id; if (!(*suite)->signature) continue; id = (*suite)->signature->id; if (memchr(sig_alg_ids, id, sig_alg_cnt)) continue; if (!tls_cipher_suite_is_compatible(tls, *suite, NULL)) continue; if (sig_alg_cnt >= sizeof(sig_alg_ids)) return -ENOMEM; sig_alg_ids[sig_alg_cnt++] = id; } /* * In 1.2 we force our preference for SHA256/SHA384 (depending on * cipher suite's PRF hmac) if it is supported by the peer because * that must be supported anyway for the PRF and the Finished hash * meaning that we only need to keep one hash instead of two. * If not available fall back to the first common hash algorithm. */ first_supported = -1; if (tls->prf_hmac) preferred = tls->prf_hmac; else preferred = &tls_handshake_hash_data[HANDSHAKE_HASH_SHA256]; while (len) { uint8_t hash_id = *ptr++; uint8_t sig_alg_id = *ptr++; bool supported; len -= 2; /* Ignore hash types for signatures other than ours */ if (tls->pending.cipher_suite && (!tls->pending.cipher_suite->signature || tls->pending.cipher_suite->signature->id != sig_alg_id)) continue; if (!tls->pending.cipher_suite && !memchr(sig_alg_ids, sig_alg_id, sig_alg_cnt)) continue; if (hash_id == preferred->tls_id) { for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) if (&tls_handshake_hash_data[hash] == preferred) break; break; } if ((int) first_supported != -1) continue; for (hash = 0; hash < __HANDSHAKE_HASH_COUNT; hash++) if (hash_id == tls_handshake_hash_data[hash].tls_id) break; if (hash == __HANDSHAKE_HASH_COUNT) continue; if (tls->server) supported = l_checksum_is_supported( tls_handshake_hash_data[hash].l_id, false); else supported = !!tls->handshake_hash[hash]; if (supported) first_supported = hash; } if (len) tls->signature_hash = hash; else if ((int) first_supported != -1) tls->signature_hash = first_supported; else return -ENOTSUP; return ptr + len - buf; } /* RFC 5246, Section 7.4.1.4.1 */ static ssize_t tls_signature_algorithms_client_write(struct l_tls *tls, uint8_t *buf, size_t len) { /* * "Note: this extension is not meaningful for TLS versions * prior to 1.2. Clients MUST NOT offer it if they are offering * prior versions." */ if (tls->max_version < L_TLS_V12) return -ENOMSG; return tls_write_signature_algorithms(tls, buf, len); } static bool tls_signature_algorithms_client_handle(struct l_tls *tls, const uint8_t *buf, size_t len) { ssize_t ret; /* * "However, even if clients do offer it, the rules specified in * [TLSEXT] require servers to ignore extensions they do not * understand." */ if (tls->max_version < L_TLS_V12) return true; ret = tls_parse_signature_algorithms(tls, buf, len); if (ret == -ENOTSUP) TLS_DEBUG("No common signature algorithms"); /* * TODO: also check our certificate chain against the parsed * signature algorithms. */ return ret == (ssize_t) len; } static bool tls_signature_algorithms_client_absent(struct l_tls *tls) { /* * "If the client does not send the signature_algorithms extension, * the server MUST do the following: * - [...] behave as if client had sent the value {sha1,rsa}. * - [...] behave as if client had sent the value {sha1,dsa}. * - [...] behave as if client had sent the value {sha1,ecdsa}. */ if (tls->max_version >= L_TLS_V12) tls->signature_hash = HANDSHAKE_HASH_SHA1; return true; } /* RFC 5746, Section 3.2 */ static ssize_t tls_renegotiation_info_client_write(struct l_tls *tls, uint8_t *buf, size_t len) { /* * Section 4.2 implies we should send the client_verify_data on * renegotiation even if .secure_renegotiation is false, if we want * to go through with the renegotiation in the first place. */ if (tls->ready) { size_t vdl = tls_verify_data_length(tls, 1); if (len < vdl + 1) return -ENOMEM; buf[0] = vdl; memcpy(buf + 1, tls->renegotiation_info.client_verify_data, vdl); return 1 + vdl; } else { if (len < 1) return -ENOMEM; buf[0] = 0x00; /* Empty "renegotiated_connection" */ return 1; } } static ssize_t tls_renegotiation_info_server_write(struct l_tls *tls, uint8_t *buf, size_t len) { if (tls->ready) { size_t rx_vdl = tls_verify_data_length(tls, 0); size_t tx_vdl = tls_verify_data_length(tls, 1); if (len < rx_vdl + tx_vdl + 1) return -ENOMEM; buf[0] = rx_vdl + tx_vdl; memcpy(buf + 1, tls->renegotiation_info.client_verify_data, rx_vdl); memcpy(buf + 1 + rx_vdl, tls->renegotiation_info.server_verify_data, tx_vdl); return 1 + rx_vdl + tx_vdl; } else { if (len < 1) return -ENOMEM; buf[0] = 0x00; /* Empty "renegotiated_connection" */ return 1; } } static bool tls_renegotiation_info_client_handle(struct l_tls *tls, const uint8_t *buf, size_t len) { if (tls->ready) { size_t vdl = tls_verify_data_length(tls, 0); return len >= 1 + vdl && tls->renegotiation_info.secure_renegotiation && !memcmp(tls->renegotiation_info.client_verify_data, buf + 1, vdl); } /* * RFC 5746 Section 3.6: "The server MUST then verify that the length * of the "renegotiated_connection" field is zero, ..." */ if (len < 1 || buf[0] != 0x00) return false; tls->renegotiation_info.secure_renegotiation = true; return true; } static bool tls_renegotiation_info_server_handle(struct l_tls *tls, const uint8_t *buf, size_t len) { if (tls->ready) { size_t rx_vdl = tls_verify_data_length(tls, 0); size_t tx_vdl = tls_verify_data_length(tls, 1); return len >= 1 + rx_vdl + tx_vdl && tls->renegotiation_info.secure_renegotiation && !memcmp(tls->renegotiation_info.client_verify_data, buf + 1, tx_vdl) && !memcmp(tls->renegotiation_info.server_verify_data, buf + 1 + tx_vdl, rx_vdl); } /* * RFC 5746 Section 3.4: "The client MUST then verify that the length * of the "renegotiated_connection" field is zero, ..." */ if (len < 1 || buf[0] != 0x00) return false; tls->renegotiation_info.secure_renegotiation = true; return true; } static bool tls_renegotiation_info_absent(struct l_tls *tls) { /* * RFC 5746 Section 4.2: "It is possible that un-upgraded servers * will request that the client renegotiate. It is RECOMMENDED * that clients refuse this renegotiation request." and Section 4.4: * "It is RECOMMENDED that servers not permit legacy renegotiation." * * This may need to be made configurable, for now follow the * recommendation and don't renegotiate. */ if (tls->ready) return false; /* * The normal policy otherwise is that the extension must be * present in renegotiation if the previous Client or Server Hello * did include this extension, or the SCSV in the Client Hello case. */ return !tls->ready || !tls->renegotiation_info.secure_renegotiation; } const struct tls_hello_extension tls_extensions[] = { { "Supported Groups", "elliptic_curves", 10, tls_elliptic_curves_client_write, tls_elliptic_curves_client_handle, tls_elliptic_curves_client_absent, NULL, NULL, NULL, }, { "Supported Point Formats", "ec_point_formats", 11, NULL, tls_ec_point_formats_client_handle, NULL, tls_ec_point_formats_server_write, NULL, NULL, }, { "Signature Algorithms", "signature_algoritms", 13, tls_signature_algorithms_client_write, tls_signature_algorithms_client_handle, tls_signature_algorithms_client_absent, NULL, NULL, NULL, }, { "Secure Renegotiation", "renegotiation_info", 0xff01, tls_renegotiation_info_client_write, tls_renegotiation_info_client_handle, tls_renegotiation_info_absent, tls_renegotiation_info_server_write, tls_renegotiation_info_server_handle, tls_renegotiation_info_absent, }, {} }; const struct tls_named_group *tls_find_group(uint16_t id) { unsigned int i; for (i = 0; i < L_ARRAY_SIZE(tls_group_pref); i++) if (tls_group_pref[i].id == id) return &tls_group_pref[i]; return NULL; } const struct tls_named_group *tls_find_ff_group(const uint8_t *prime, size_t prime_len, const uint8_t *generator, size_t generator_len) { unsigned int i; if (generator_len != 1) return NULL; for (i = 0; i < L_ARRAY_SIZE(tls_group_pref); i++) { const struct tls_named_group *g = &tls_group_pref[i]; if (g->type != TLS_GROUP_TYPE_FF) continue; if (g->ff.prime_len != prime_len || memcmp(prime, g->ff.prime, prime_len)) continue; if (g->ff.generator != *generator) continue; return g; } return NULL; } bluez-5.87/ell/PaxHeaders/uuid.h0000644000000000000000000000005014504767710013561 xustar0020 atime=1783076232 20 ctime=1783094099 bluez-5.87/ell/uuid.h0000644000000000000000000000217514504767710013247 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2015 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_UUID_H #define __ELL_UUID_H #include #include #ifdef __cplusplus extern "C" { #endif enum l_uuid_version { L_UUID_VERSION_1_TIME = 1, L_UUID_VERSION_2_DCE = 2, L_UUID_VERSION_3_MD5 = 3, L_UUID_VERSION_4_RANDOM = 4, L_UUID_VERSION_5_SHA1 = 5, }; extern const uint8_t L_UUID_NAMESPACE_DNS[]; extern const uint8_t L_UUID_NAMESPACE_URL[]; extern const uint8_t L_UUID_NAMESPACE_OID[]; extern const uint8_t L_UUID_NAMESPACE_X500[]; bool l_uuid_v3(const uint8_t nsid[16], const void *name, size_t name_size, uint8_t out_uuid[16]); bool l_uuid_v4(uint8_t out_uuid[16]); bool l_uuid_v5(const uint8_t nsid[16], const void *name, size_t name_size, uint8_t out_uuid[16]); bool l_uuid_is_valid(const uint8_t uuid[16]); enum l_uuid_version l_uuid_get_version(const uint8_t uuid[16]); bool l_uuid_to_string(const uint8_t uuid[16], char *dest, size_t dest_size); bool l_uuid_from_string(const char *src, uint8_t uuid[16]); #ifdef __cplusplus } #endif #endif /* __ELL_UTIL_H */ bluez-5.87/ell/PaxHeaders/dbus-client.h0000644000000000000000000000005014504767710015024 xustar0020 atime=1783076232 20 ctime=1783094098 bluez-5.87/ell/dbus-client.h0000644000000000000000000000475214504767710014515 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2017 Codecoup * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_DBUS_CLIENT_H #define __ELL_DBUS_CLIENT_H #include #ifdef __cplusplus extern "C" { #endif struct l_dbus; struct l_dbus_message; struct l_dbus_client; struct l_dbus_proxy; typedef void (*l_dbus_client_ready_func_t)(struct l_dbus_client *client, void *user_data); typedef void (*l_dbus_client_proxy_func_t) (struct l_dbus_proxy *proxy, void *user_data); typedef void (*l_dbus_client_proxy_result_func_t) (struct l_dbus_proxy *proxy, struct l_dbus_message *result, void *user_data); typedef void (*l_dbus_client_property_function_t) (struct l_dbus_proxy *proxy, const char *name, struct l_dbus_message *msg, void *user_data); struct l_dbus_client *l_dbus_client_new(struct l_dbus *dbus, const char *service, const char *path); void l_dbus_client_destroy(struct l_dbus_client *client); bool l_dbus_client_set_connect_handler(struct l_dbus_client *client, l_dbus_watch_func_t function, void *user_data, l_dbus_destroy_func_t destroy); bool l_dbus_client_set_disconnect_handler(struct l_dbus_client *client, l_dbus_watch_func_t function, void *user_data, l_dbus_destroy_func_t destroy); bool l_dbus_client_set_ready_handler(struct l_dbus_client *client, l_dbus_client_ready_func_t function, void *user_data, l_dbus_destroy_func_t destroy); bool l_dbus_client_set_proxy_handlers(struct l_dbus_client *client, l_dbus_client_proxy_func_t proxy_added, l_dbus_client_proxy_func_t proxy_removed, l_dbus_client_property_function_t property_changed, void *user_data, l_dbus_destroy_func_t destroy); const char *l_dbus_proxy_get_path(struct l_dbus_proxy *proxy); const char *l_dbus_proxy_get_interface(struct l_dbus_proxy *proxy); bool l_dbus_proxy_get_property(struct l_dbus_proxy *proxy, const char *name, const char *signature, ...); bool l_dbus_proxy_set_property(struct l_dbus_proxy *proxy, l_dbus_client_proxy_result_func_t result, void *user_data, l_dbus_destroy_func_t destroy, const char *name, const char *signature, ...); uint32_t l_dbus_proxy_method_call(struct l_dbus_proxy *proxy, const char *method, l_dbus_message_func_t setup, l_dbus_client_proxy_result_func_t reply, void *user_data, l_dbus_destroy_func_t destroy); #ifdef __cplusplus } #endif #endif /* __ELL_DBUS_CLIENT_H */ bluez-5.87/ell/PaxHeaders/queue.h0000644000000000000000000000005014567163422013736 xustar0020 atime=1783076231 20 ctime=1783094098 bluez-5.87/ell/queue.h0000644000000000000000000000372614567163422013427 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_QUEUE_H #define __ELL_QUEUE_H #include #ifdef __cplusplus extern "C" { #endif typedef void (*l_queue_foreach_func_t) (void *data, void *user_data); typedef void (*l_queue_destroy_func_t) (void *data); typedef int (*l_queue_compare_func_t) (const void *a, const void *b, void *user_data); typedef bool (*l_queue_match_func_t) (const void *data, const void *user_data); typedef bool (*l_queue_remove_func_t) (void *data, void *user_data); struct l_queue; struct l_queue_entry { void *data; struct l_queue_entry *next; }; struct l_queue *l_queue_new(void); void l_queue_destroy(struct l_queue *queue, l_queue_destroy_func_t destroy); void l_queue_clear(struct l_queue *queue, l_queue_destroy_func_t destroy); bool l_queue_push_tail(struct l_queue *queue, void *data); bool l_queue_push_head(struct l_queue *queue, void *data); void *l_queue_pop_head(struct l_queue *queue); void *l_queue_peek_head(struct l_queue *queue); void *l_queue_peek_tail(struct l_queue *queue); bool l_queue_insert(struct l_queue *queue, void *data, l_queue_compare_func_t function, void *user_data); void *l_queue_find(struct l_queue *queue, l_queue_match_func_t function, const void *user_data); bool l_queue_remove(struct l_queue *queue, void *data); void *l_queue_remove_if(struct l_queue *queue, l_queue_match_func_t function, const void *user_data); bool l_queue_reverse(struct l_queue *queue); void l_queue_foreach(struct l_queue *queue, l_queue_foreach_func_t function, void *user_data); unsigned int l_queue_foreach_remove(struct l_queue *queue, l_queue_remove_func_t function, void *user_data); unsigned int l_queue_length(struct l_queue *queue); bool l_queue_isempty(struct l_queue *queue); const struct l_queue_entry *l_queue_get_entries(const struct l_queue *queue); #ifdef __cplusplus } #endif #endif /* __ELL_QUEUE_H */ bluez-5.87/ell/PaxHeaders/dbus-client.c0000644000000000000000000000005015040645364015013 xustar0020 atime=1783076302 20 ctime=1783094099 bluez-5.87/ell/dbus-client.c0000644000000000000000000004224315040645364014501 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2017 Codecoup * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #include "dbus.h" #include "dbus-client.h" #include "queue.h" #include "useful.h" #include "private.h" struct l_dbus_client { struct l_dbus *dbus; unsigned int watch; unsigned int added_watch; unsigned int removed_watch; char *service; uint32_t objects_call; l_dbus_watch_func_t connect_cb; void *connect_cb_data; l_dbus_destroy_func_t connect_cb_data_destroy; l_dbus_watch_func_t disconnect_cb; void *disconnect_cb_data; l_dbus_destroy_func_t disconnect_cb_data_destroy; l_dbus_client_ready_func_t ready_cb; void *ready_cb_data; l_dbus_destroy_func_t ready_cb_data_destroy; l_dbus_client_proxy_func_t proxy_added_cb; l_dbus_client_proxy_func_t proxy_removed_cb; l_dbus_client_property_function_t properties_changed_cb; void *proxy_cb_data; l_dbus_destroy_func_t proxy_cb_data_destroy; struct l_queue *proxies; }; struct proxy_property { char *name; struct l_dbus_message *msg; }; struct l_dbus_proxy { struct l_dbus_client *client; char *interface; char *path; uint32_t properties_watch; bool ready; struct l_queue *properties; struct l_queue *pending_calls; }; LIB_EXPORT const char *l_dbus_proxy_get_path(struct l_dbus_proxy *proxy) { if (unlikely(!proxy)) return NULL; return proxy->path; } LIB_EXPORT const char *l_dbus_proxy_get_interface(struct l_dbus_proxy *proxy) { if (unlikely(!proxy)) return NULL; return proxy->interface; } static bool property_match_by_name(const void *a, const void *b) { const struct proxy_property *prop = a; const char *name = b; return !strcmp(prop->name, name); } static struct proxy_property *find_property(struct l_dbus_proxy *proxy, const char *name) { return l_queue_find(proxy->properties, property_match_by_name, name); } static struct proxy_property *get_property(struct l_dbus_proxy *proxy, const char *name) { struct proxy_property *prop; prop = find_property(proxy, name); if (prop) return prop; prop = l_new(struct proxy_property, 1); prop->name = l_strdup(name); l_queue_push_tail(proxy->properties, prop); return prop; } LIB_EXPORT bool l_dbus_proxy_get_property(struct l_dbus_proxy *proxy, const char *name, const char *signature, ...) { struct proxy_property *prop; va_list args; bool res; if (unlikely(!proxy)) return false; prop = find_property(proxy, name); if (!prop) return false; va_start(args, signature); res = l_dbus_message_get_arguments_valist(prop->msg, signature, args); va_end(args); return res; } static void property_free(void *data) { struct proxy_property *prop = data; if (prop->msg) l_dbus_message_unref(prop->msg); l_free(prop->name); l_free(prop); } static void cancel_pending_calls(struct l_dbus_proxy *proxy) { const struct l_queue_entry *entry; for (entry = l_queue_get_entries(proxy->pending_calls); entry; entry = entry->next) { uint32_t call_id = L_PTR_TO_UINT(entry->data); l_dbus_cancel(proxy->client->dbus, call_id); } } static void dbus_proxy_destroy(struct l_dbus_proxy *proxy) { if (unlikely(!proxy)) return; if (proxy->properties_watch) l_dbus_remove_signal_watch(proxy->client->dbus, proxy->properties_watch); cancel_pending_calls(proxy); l_queue_destroy(proxy->pending_calls, NULL); l_queue_destroy(proxy->properties, property_free); l_free(proxy->interface); l_free(proxy->path); l_free(proxy); } struct method_call_request { struct l_dbus_proxy *proxy; uint32_t call_id; l_dbus_message_func_t setup; l_dbus_client_proxy_result_func_t result; void *user_data; l_dbus_destroy_func_t destroy; }; static void method_call_request_free(void *user_data) { struct method_call_request *req = user_data; l_queue_remove(req->proxy->pending_calls, L_UINT_TO_PTR(req->call_id)); if (req->destroy) req->destroy(req->user_data); l_free(req); } static void method_call_setup(struct l_dbus_message *message, void *user_data) { struct method_call_request *req = user_data; if (req->setup) req->setup(message, req->user_data); else l_dbus_message_set_arguments(message, ""); } static void method_call_reply(struct l_dbus_message *message, void *user_data) { struct method_call_request *req = user_data; if (req->result) req->result(req->proxy, message, req->user_data); } LIB_EXPORT bool l_dbus_proxy_set_property(struct l_dbus_proxy *proxy, l_dbus_client_proxy_result_func_t result, void *user_data, l_dbus_destroy_func_t destroy, const char *name, const char *signature, ...) { struct l_dbus_client *client = proxy->client; struct l_dbus_message_builder *builder; struct method_call_request *req; struct l_dbus_message *message; struct proxy_property *prop; va_list args; if (unlikely(!proxy)) return false; prop = find_property(proxy, name); if (!prop) return false; if (strcmp(l_dbus_message_get_signature(prop->msg), signature)) return false; message = l_dbus_message_new_method_call(client->dbus, client->service, proxy->path, L_DBUS_INTERFACE_PROPERTIES, "Set"); if (!message) return false; builder = l_dbus_message_builder_new(message); if (!builder) { l_dbus_message_unref(message); return false; } l_dbus_message_builder_append_basic(builder, 's', proxy->interface); l_dbus_message_builder_append_basic(builder, 's', name); l_dbus_message_builder_enter_variant(builder, signature); va_start(args, signature); l_dbus_message_builder_append_from_valist(builder, signature, args); va_end(args); l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); req = l_new(struct method_call_request, 1); req->proxy = proxy; req->result = result; req->user_data = user_data; req->destroy = destroy; req->call_id = l_dbus_send_with_reply(client->dbus, message, method_call_reply, req, method_call_request_free); if (!req->call_id) { l_free(req); return false; } l_queue_push_tail(proxy->pending_calls, L_UINT_TO_PTR(req->call_id)); return true; } LIB_EXPORT uint32_t l_dbus_proxy_method_call(struct l_dbus_proxy *proxy, const char *method, l_dbus_message_func_t setup, l_dbus_client_proxy_result_func_t reply, void *user_data, l_dbus_destroy_func_t destroy) { struct method_call_request *req; if (unlikely(!proxy)) return 0; req = l_new(struct method_call_request, 1); req->proxy = proxy; req->setup = setup; req->result = reply; req->user_data = user_data; req->destroy = destroy; req->call_id = l_dbus_method_call(proxy->client->dbus, proxy->client->service, proxy->path, proxy->interface, method, method_call_setup, method_call_reply, req, method_call_request_free); if (!req->call_id) { l_free(req); return 0; } l_queue_push_tail(proxy->pending_calls, L_UINT_TO_PTR(req->call_id)); return req->call_id; } static void proxy_update_property(struct l_dbus_proxy *proxy, const char *name, struct l_dbus_message_iter *property) { struct l_dbus_message_builder *builder; struct proxy_property *prop = get_property(proxy, name); l_dbus_message_unref(prop->msg); if (!property) { prop->msg = NULL; goto done; } prop->msg = l_dbus_message_new_signal(proxy->client->dbus, proxy->path, proxy->interface, name); if (!prop->msg) return; builder = l_dbus_message_builder_new(prop->msg); l_dbus_message_builder_append_from_iter(builder, property); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); done: if (proxy->client->properties_changed_cb && proxy->ready) proxy->client->properties_changed_cb(proxy, name, prop->msg, proxy->client->proxy_cb_data); } static void proxy_invalidate_properties(struct l_dbus_proxy *proxy, struct l_dbus_message_iter* props) { const char *name; while (l_dbus_message_iter_next_entry(props, &name)) proxy_update_property(proxy, name, NULL); } static void proxy_update_properties(struct l_dbus_proxy *proxy, struct l_dbus_message_iter* props) { struct l_dbus_message_iter variant; const char *name; while (l_dbus_message_iter_next_entry(props, &name, &variant)) proxy_update_property(proxy, name, &variant); } static void properties_changed_callback(struct l_dbus_message *message, void *user_data) { struct l_dbus_proxy *proxy = user_data; const char *interface; struct l_dbus_message_iter changed; struct l_dbus_message_iter invalidated; if (!l_dbus_message_get_arguments(message, "sa{sv}as", &interface, &changed, &invalidated)) return; proxy_update_properties(proxy, &changed); proxy_invalidate_properties(proxy, &invalidated); } static struct l_dbus_proxy *dbus_proxy_new(struct l_dbus_client *client, const char *path, const char *interface) { struct l_dbus_proxy *proxy = l_new(struct l_dbus_proxy, 1); proxy->properties_watch = l_dbus_add_signal_watch(client->dbus, client->service, path, L_DBUS_INTERFACE_PROPERTIES, "PropertiesChanged", L_DBUS_MATCH_ARGUMENT(0), interface, L_DBUS_MATCH_NONE, properties_changed_callback, proxy); if (!proxy->properties_watch) { l_free(proxy); return NULL; } proxy->client = client; proxy->interface = l_strdup(interface); proxy->path = l_strdup(path); proxy->properties = l_queue_new(); proxy->pending_calls = l_queue_new(); l_queue_push_tail(client->proxies, proxy); return proxy; } static bool is_ignorable(const char *interface) { static const struct { const char *interface; } interfaces_to_ignore[] = { { L_DBUS_INTERFACE_OBJECT_MANAGER }, { L_DBUS_INTERFACE_INTROSPECTABLE }, { L_DBUS_INTERFACE_PROPERTIES }, }; size_t i; for (i = 0; i < L_ARRAY_SIZE(interfaces_to_ignore); i++) if (!strcmp(interfaces_to_ignore[i].interface, interface)) return true; return false; } static struct l_dbus_proxy *find_proxy(struct l_dbus_client *client, const char *path, const char *interface) { const struct l_queue_entry *entry; for (entry = l_queue_get_entries(client->proxies); entry; entry = entry->next) { struct l_dbus_proxy *proxy = entry->data; if (!strcmp(proxy->interface, interface) && !strcmp(proxy->path, path)) return proxy; } return NULL; } static void parse_interface(struct l_dbus_client *client, const char *path, const char *interface, struct l_dbus_message_iter *properties) { struct l_dbus_proxy *proxy; if (is_ignorable(interface)) return; proxy = find_proxy(client, path, interface); if (!proxy) proxy = dbus_proxy_new(client, path, interface); if (!proxy) return; proxy_update_properties(proxy, properties); if (!proxy->ready) { proxy->ready = true; if (client->proxy_added_cb) client->proxy_added_cb(proxy, client->proxy_cb_data); } } static void parse_object(struct l_dbus_client *client, const char *path, struct l_dbus_message_iter *object) { const char *interface; struct l_dbus_message_iter properties; if (!path) return; while (l_dbus_message_iter_next_entry(object, &interface, &properties)) parse_interface(client, path, interface, &properties); } static void interfaces_added_callback(struct l_dbus_message *message, void *user_data) { struct l_dbus_client *client = user_data; struct l_dbus_message_iter object; const char *path; if (!l_dbus_message_get_arguments(message, "oa{sa{sv}}", &path, &object)) return; parse_object(client, path, &object); } static void interfaces_removed_callback(struct l_dbus_message *message, void *user_data) { struct l_dbus_client *client = user_data; struct l_dbus_message_iter interfaces; const char *interface; const char *path; if (!l_dbus_message_get_arguments(message, "oas", &path, &interfaces)) return; while (l_dbus_message_iter_next_entry(&interfaces, &interface)) { struct l_dbus_proxy *proxy; proxy = find_proxy(client, path, interface); if (!proxy) continue; l_queue_remove(proxy->client->proxies, proxy); if (client->proxy_removed_cb) client->proxy_removed_cb(proxy, client->proxy_cb_data); dbus_proxy_destroy(proxy); } } static void get_managed_objects_reply(struct l_dbus_message *message, void *user_data) { struct l_dbus_client *client = user_data; struct l_dbus_message_iter objects; struct l_dbus_message_iter object; const char *path; client->objects_call = 0; if (l_dbus_message_is_error(message)) return; if (!l_dbus_message_get_arguments(message, "a{oa{sa{sv}}}", &objects)) return; while (l_dbus_message_iter_next_entry(&objects, &path, &object)) parse_object(client, path, &object); client->added_watch = l_dbus_add_signal_watch(client->dbus, client->service, "/", L_DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesAdded", L_DBUS_MATCH_NONE, interfaces_added_callback, client); client->removed_watch = l_dbus_add_signal_watch(client->dbus, client->service, "/", L_DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesRemoved", L_DBUS_MATCH_NONE, interfaces_removed_callback, client); if (client->ready_cb) client->ready_cb(client, client->ready_cb_data); } static void service_appeared_callback(struct l_dbus *dbus, void *user_data) { struct l_dbus_client *client = user_data; /* TODO should we allow to set different root? */ client->objects_call = l_dbus_method_call(dbus, client->service, "/", L_DBUS_INTERFACE_OBJECT_MANAGER, "GetManagedObjects", NULL, get_managed_objects_reply, client, NULL); if (client->connect_cb) client->connect_cb(client->dbus, client->connect_cb_data); } static void service_disappeared_callback(struct l_dbus *dbus, void *user_data) { struct l_dbus_client *client = user_data; if (client->disconnect_cb) client->disconnect_cb(client->dbus, client->disconnect_cb_data); l_queue_clear(client->proxies, (l_queue_destroy_func_t)dbus_proxy_destroy); } LIB_EXPORT struct l_dbus_client *l_dbus_client_new(struct l_dbus *dbus, const char *service, const char *path) { struct l_dbus_client *client = l_new(struct l_dbus_client, 1); client->dbus = dbus; client->service = l_strdup(service); client->watch = l_dbus_add_service_watch(dbus, service, service_appeared_callback, service_disappeared_callback, client, NULL); if (!client->watch) { l_free(client->service); l_free(client); return NULL; } client->proxies = l_queue_new(); return client; } LIB_EXPORT void l_dbus_client_destroy(struct l_dbus_client *client) { if (unlikely(!client)) return; if (client->watch) l_dbus_remove_watch(client->dbus, client->watch); if (client->added_watch) l_dbus_remove_signal_watch(client->dbus, client->added_watch); if (client->removed_watch) l_dbus_remove_signal_watch(client->dbus, client->removed_watch); if (client->connect_cb_data_destroy) client->connect_cb_data_destroy(client->connect_cb_data); if (client->disconnect_cb_data_destroy) client->disconnect_cb_data_destroy(client->disconnect_cb_data); if (client->ready_cb_data_destroy) client->ready_cb_data_destroy(client->ready_cb_data); if (client->proxy_cb_data_destroy) client->proxy_cb_data_destroy(client->proxy_cb_data); if (client->objects_call) l_dbus_cancel(client->dbus, client->objects_call); l_queue_destroy(client->proxies, (l_queue_destroy_func_t)dbus_proxy_destroy); l_free(client->service); l_free(client); } LIB_EXPORT bool l_dbus_client_set_connect_handler(struct l_dbus_client *client, l_dbus_watch_func_t function, void *user_data, l_dbus_destroy_func_t destroy) { if (unlikely(!client)) return false; if (client->connect_cb_data_destroy) client->connect_cb_data_destroy(client->connect_cb_data); client->connect_cb = function; client->connect_cb_data = user_data; client->connect_cb_data_destroy = destroy; return true; } LIB_EXPORT bool l_dbus_client_set_disconnect_handler(struct l_dbus_client *client, l_dbus_watch_func_t function, void *user_data, l_dbus_destroy_func_t destroy) { if (unlikely(!client)) return false; if(client->disconnect_cb_data_destroy) client->disconnect_cb_data_destroy(client->disconnect_cb_data); client->disconnect_cb = function; client->disconnect_cb_data = user_data; client->disconnect_cb_data_destroy = destroy; return true; } LIB_EXPORT bool l_dbus_client_set_ready_handler(struct l_dbus_client *client, l_dbus_client_ready_func_t function, void *user_data, l_dbus_destroy_func_t destroy) { if (unlikely(!client)) return false; if (client->ready_cb_data_destroy) client->ready_cb_data_destroy(client->ready_cb_data); client->ready_cb = function; client->ready_cb_data = user_data; client->ready_cb_data_destroy = destroy; return true; } LIB_EXPORT bool l_dbus_client_set_proxy_handlers(struct l_dbus_client *client, l_dbus_client_proxy_func_t proxy_added, l_dbus_client_proxy_func_t proxy_removed, l_dbus_client_property_function_t property_changed, void *user_data, l_dbus_destroy_func_t destroy) { if (unlikely(!client)) return false; if (client->proxy_cb_data_destroy) client->proxy_cb_data_destroy(client->proxy_cb_data); client->proxy_added_cb = proxy_added; client->proxy_removed_cb = proxy_removed; client->properties_changed_cb = property_changed; client->proxy_cb_data = user_data; client->proxy_cb_data_destroy = destroy; return true; } bluez-5.87/ell/PaxHeaders/dbus-message.c0000644000000000000000000000005014504767710015165 xustar0020 atime=1783076292 20 ctime=1783094099 bluez-5.87/ell/dbus-message.c0000644000000000000000000013216314504767710014654 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include "util.h" #include "private.h" #include "useful.h" #include "dbus.h" #include "dbus-private.h" #include "gvariant-private.h" #define DBUS_MESSAGE_LITTLE_ENDIAN ('l') #define DBUS_MESSAGE_BIG_ENDIAN ('B') #define DBUS_MESSAGE_PROTOCOL_VERSION 1 #define DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED 0x01 #define DBUS_MESSAGE_FLAG_NO_AUTO_START 0x02 #define DBUS_MESSAGE_FIELD_PATH 1 #define DBUS_MESSAGE_FIELD_INTERFACE 2 #define DBUS_MESSAGE_FIELD_MEMBER 3 #define DBUS_MESSAGE_FIELD_ERROR_NAME 4 #define DBUS_MESSAGE_FIELD_REPLY_SERIAL 5 #define DBUS_MESSAGE_FIELD_DESTINATION 6 #define DBUS_MESSAGE_FIELD_SENDER 7 #define DBUS_MESSAGE_FIELD_SIGNATURE 8 #define DBUS_MESSAGE_FIELD_UNIX_FDS 9 #define DBUS_MAX_NESTING 32 struct l_dbus_message { int refcount; void *header; size_t header_size; size_t header_end; char *signature; void *body; size_t body_size; char *path; char *interface; char *member; char *error_name; uint32_t reply_serial; char *destination; char *sender; int fds[16]; uint32_t num_fds; bool sealed : 1; bool signature_free : 1; }; struct l_dbus_message_builder { struct l_dbus_message *message; struct dbus_builder *builder; struct builder_driver *driver; }; static inline bool _dbus_message_is_gvariant(struct l_dbus_message *msg) { struct dbus_header *hdr = msg->header; return hdr->version == 2; } void *_dbus_message_get_header(struct l_dbus_message *msg, size_t *out_size) { if (out_size) *out_size = msg->header_size; return msg->header; } void *_dbus_message_get_body(struct l_dbus_message *msg, size_t *out_size) { if (out_size) *out_size = msg->body_size; return msg->body; } /* Get a buffer containing the final message contents except the header */ void *_dbus_message_get_footer(struct l_dbus_message *msg, size_t *out_size) { size_t size; if (_dbus_message_is_gvariant(msg)) { size = _gvariant_message_finalize(msg->header_end, msg->body, msg->body_size, msg->signature); size -= msg->header_size; } else size = msg->body_size; if (out_size) *out_size = size; return msg->body; } int *_dbus_message_get_fds(struct l_dbus_message *msg, uint32_t *num_fds) { *num_fds = msg->num_fds; return msg->fds; } void _dbus_message_set_serial(struct l_dbus_message *msg, uint32_t serial) { struct dbus_header *hdr = msg->header; hdr->dbus1.serial = serial; } uint32_t _dbus_message_get_serial(struct l_dbus_message *msg) { struct dbus_header *hdr = msg->header; return hdr->dbus1.serial; } LIB_EXPORT bool l_dbus_message_set_no_reply(struct l_dbus_message *msg, bool on) { struct dbus_header *hdr; if (unlikely(!msg)) return false; hdr = msg->header; if (on) hdr->flags |= DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED; else hdr->flags &= ~DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED; return true; } LIB_EXPORT bool l_dbus_message_get_no_reply(struct l_dbus_message *msg) { struct dbus_header *hdr; if (unlikely(!msg)) return false; hdr = msg->header; if (hdr->flags & DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED) return true; return false; } LIB_EXPORT bool l_dbus_message_set_no_autostart(struct l_dbus_message *msg, bool on) { struct dbus_header *hdr; if (unlikely(!msg)) return false; hdr = msg->header; if (on) hdr->flags |= DBUS_MESSAGE_FLAG_NO_AUTO_START; else hdr->flags &= ~DBUS_MESSAGE_FLAG_NO_AUTO_START; return true; } LIB_EXPORT bool l_dbus_message_get_no_autostart(struct l_dbus_message *msg) { struct dbus_header *hdr; if (unlikely(!msg)) return false; hdr = msg->header; if (hdr->flags & DBUS_MESSAGE_FLAG_NO_AUTO_START) return true; return false; } static struct l_dbus_message *message_new_common(uint8_t type, uint8_t flags, uint8_t version) { struct l_dbus_message *message; struct dbus_header *hdr; message = l_new(struct l_dbus_message, 1); message->refcount = 1; /* * We allocate the header with the initial 12 bytes (up to the field * length) so that we can store the basic information here. For * GVariant we need 16 bytes. */ message->header_size = version == 1 ? 12 : 16; message->header_end = message->header_size; message->header = l_realloc(NULL, message->header_size); hdr = message->header; hdr->endian = DBUS_NATIVE_ENDIAN; hdr->message_type = type; hdr->flags = flags; hdr->version = version; return message; } struct l_dbus_message *_dbus_message_new_method_call(uint8_t version, const char *destination, const char *path, const char *interface, const char *method) { struct l_dbus_message *message; message = message_new_common(DBUS_MESSAGE_TYPE_METHOD_CALL, 0, version); message->destination = l_strdup(destination); message->path = l_strdup(path); message->interface = l_strdup(interface); message->member = l_strdup(method); return message; } LIB_EXPORT struct l_dbus_message *l_dbus_message_new_method_call( struct l_dbus *dbus, const char *destination, const char *path, const char *interface, const char *method) { uint8_t version; if (unlikely(!dbus)) return NULL; version = _dbus_get_version(dbus); return _dbus_message_new_method_call(version, destination, path, interface, method); } struct l_dbus_message *_dbus_message_new_signal(uint8_t version, const char *path, const char *interface, const char *name) { struct l_dbus_message *message; message = message_new_common(DBUS_MESSAGE_TYPE_SIGNAL, DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED, version); message->path = l_strdup(path); message->interface = l_strdup(interface); message->member = l_strdup(name); return message; } LIB_EXPORT struct l_dbus_message *l_dbus_message_new_signal(struct l_dbus *dbus, const char *path, const char *interface, const char *name) { uint8_t version; if (unlikely(!dbus)) return NULL; version = _dbus_get_version(dbus); return _dbus_message_new_signal(version, path, interface, name); } LIB_EXPORT struct l_dbus_message *l_dbus_message_new_method_return( struct l_dbus_message *method_call) { struct l_dbus_message *message; struct dbus_header *hdr = method_call->header; const char *sender; message = message_new_common(DBUS_MESSAGE_TYPE_METHOD_RETURN, DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED, hdr->version); if (!l_dbus_message_get_no_reply(method_call)) message->reply_serial = _dbus_message_get_serial(method_call); sender = l_dbus_message_get_sender(method_call); if (sender) message->destination = l_strdup(sender); return message; } struct l_dbus_message *_dbus_message_new_error(uint8_t version, uint32_t reply_serial, const char *destination, const char *name, const char *error) { struct l_dbus_message *reply; if (!_dbus_valid_interface(name)) return NULL; reply = message_new_common(DBUS_MESSAGE_TYPE_ERROR, DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED, version); reply->error_name = l_strdup(name); reply->destination = l_strdup(destination); reply->reply_serial = reply_serial; if (!l_dbus_message_set_arguments(reply, "s", error)) { l_dbus_message_unref(reply); return NULL; } return reply; } LIB_EXPORT struct l_dbus_message *l_dbus_message_new_error_valist( struct l_dbus_message *method_call, const char *name, const char *format, va_list args) { char str[1024]; struct dbus_header *hdr = method_call->header; uint32_t reply_serial = 0; vsnprintf(str, sizeof(str), format, args); if (!l_dbus_message_get_no_reply(method_call)) reply_serial = _dbus_message_get_serial(method_call); return _dbus_message_new_error(hdr->version, reply_serial, l_dbus_message_get_sender(method_call), name, str); } LIB_EXPORT struct l_dbus_message *l_dbus_message_new_error( struct l_dbus_message *method_call, const char *name, const char *format, ...) { va_list args; struct l_dbus_message *reply; va_start(args, format); reply = l_dbus_message_new_error_valist(method_call, name, format, args); va_end(args); return reply; } LIB_EXPORT struct l_dbus_message *l_dbus_message_ref(struct l_dbus_message *message) { if (unlikely(!message)) return NULL; __atomic_fetch_add(&message->refcount, 1, __ATOMIC_SEQ_CST); return message; } LIB_EXPORT void l_dbus_message_unref(struct l_dbus_message *message) { unsigned int i; if (unlikely(!message)) return; if (__atomic_sub_fetch(&message->refcount, 1, __ATOMIC_SEQ_CST)) return; for (i = 0; i < message->num_fds; i++) close(message->fds[i]); if (!message->sealed) { l_free(message->destination); l_free(message->path); l_free(message->interface); l_free(message->member); l_free(message->error_name); l_free(message->sender); } if (message->signature_free) l_free(message->signature); l_free(message->header); l_free(message->body); l_free(message); } const char *_dbus_message_get_nth_string_argument( struct l_dbus_message *message, int n) { struct l_dbus_message_iter iter; const char *signature, *value; void *body; size_t size; char type; bool (*skip_entry)(struct l_dbus_message_iter *); bool (*get_basic)(struct l_dbus_message_iter *, char, void *); signature = l_dbus_message_get_signature(message); body = _dbus_message_get_body(message, &size); if (!signature) return NULL; if (_dbus_message_is_gvariant(message)) { if (!_gvariant_iter_init(&iter, message, signature, NULL, body, size)) return NULL; skip_entry = _gvariant_iter_skip_entry; get_basic = _gvariant_iter_next_entry_basic; } else { _dbus1_iter_init(&iter, message, signature, NULL, body, size); skip_entry = _dbus1_iter_skip_entry; get_basic = _dbus1_iter_next_entry_basic; } while (n--) if (!skip_entry(&iter)) return NULL; if (!iter.sig_start) return NULL; type = iter.sig_start[iter.sig_pos]; if (!strchr("sog", type)) return NULL; if (!get_basic(&iter, type, &value)) return NULL; return value; } static bool message_iter_next_entry_valist(struct l_dbus_message_iter *orig, va_list args) { static const char *simple_types = "sogybnqiuxtd"; struct l_dbus_message_iter *iter = orig; const char *signature = orig->sig_start + orig->sig_pos; const char *end; struct l_dbus_message_iter *sub_iter; struct l_dbus_message_iter stack[DBUS_MAX_NESTING]; unsigned int indent = 0; uint32_t uint32_val; int fd; void *arg; bool (*get_basic)(struct l_dbus_message_iter *, char ,void *); bool (*enter_struct)(struct l_dbus_message_iter *, struct l_dbus_message_iter *); bool (*enter_array)(struct l_dbus_message_iter *, struct l_dbus_message_iter *); bool (*enter_variant)(struct l_dbus_message_iter *, struct l_dbus_message_iter *); if (_dbus_message_is_gvariant(orig->message)) { get_basic = _gvariant_iter_next_entry_basic; enter_struct = _gvariant_iter_enter_struct; enter_array = _gvariant_iter_enter_array; enter_variant = _gvariant_iter_enter_variant; } else { get_basic = _dbus1_iter_next_entry_basic; enter_struct = _dbus1_iter_enter_struct; enter_array = _dbus1_iter_enter_array; enter_variant = _dbus1_iter_enter_variant; } while (signature < orig->sig_start + orig->sig_len) { if (strchr(simple_types, *signature)) { arg = va_arg(args, void *); if (!get_basic(iter, *signature, arg)) return false; signature += 1; continue; } switch (*signature) { case 'h': if (!get_basic(iter, 'h', &uint32_val)) return false; if (uint32_val < iter->message->num_fds) fd = fcntl(iter->message->fds[uint32_val], F_DUPFD_CLOEXEC, 3); else fd = -1; *va_arg(args, int *) = fd; signature += 1; break; case '(': case '{': signature += 1; indent += 1; if (indent > DBUS_MAX_NESTING) return false; if (!enter_struct(iter, &stack[indent - 1])) return false; iter = &stack[indent - 1]; break; case ')': case '}': /* * Sanity check in case of an unmatched paren/brace * that isn't caught elsewhere. */ if (unlikely(indent == 0)) return false; signature += 1; indent -= 1; if (indent == 0) iter = orig; else iter = &stack[indent - 1]; break; case 'a': sub_iter = va_arg(args, void *); if (!enter_array(iter, sub_iter)) return false; end = _dbus_signature_end(signature + 1); signature = end + 1; break; case 'v': sub_iter = va_arg(args, void *); if (!enter_variant(iter, sub_iter)) return false; signature += 1; break; default: return false; } } return true; } static inline bool message_iter_next_entry(struct l_dbus_message_iter *iter, ...) { va_list args; bool result; va_start(args, iter); result = message_iter_next_entry_valist(iter, args); va_end(args); return result; } static bool get_header_field_from_iter_valist(struct l_dbus_message *message, uint8_t type, char data_type, va_list args) { struct l_dbus_message_iter header; struct l_dbus_message_iter array, iter; uint8_t endian, message_type, flags, version; uint32_t body_length, serial; bool found; if (!message->sealed) return false; if (_dbus_message_is_gvariant(message)) { uint64_t field_type; if (!_gvariant_iter_init(&header, message, "a(tv)", NULL, message->header + 16, message->header_end - 16)) return false; if (!_gvariant_iter_enter_array(&header, &array)) return false; while ((found = message_iter_next_entry(&array, &field_type, &iter))) if (field_type == type) break; } else { uint8_t field_type; _dbus1_iter_init(&header, message, "yyyyuua(yv)", NULL, message->header, message->header_size); if (!message_iter_next_entry(&header, &endian, &message_type, &flags, &version, &body_length, &serial, &array)) return false; while ((found = message_iter_next_entry(&array, &field_type, &iter))) if (field_type == type) break; } if (!found) return false; if (iter.sig_start[iter.sig_pos] != data_type) return false; return message_iter_next_entry_valist(&iter, args); } static inline bool get_header_field(struct l_dbus_message *message, uint8_t type, int data_type, ...) { va_list args; bool result; va_start(args, data_type); result = get_header_field_from_iter_valist(message, type, data_type, args); va_end(args); return result; } static bool valid_header(const struct dbus_header *hdr) { if (hdr->endian != DBUS_MESSAGE_LITTLE_ENDIAN && hdr->endian != DBUS_MESSAGE_BIG_ENDIAN) return false; if (hdr->message_type < DBUS_MESSAGE_TYPE_METHOD_CALL || hdr->message_type > DBUS_MESSAGE_TYPE_SIGNAL) return false; if (hdr->version != 1 && hdr->version != 2) return false; if (hdr->version == 1) { if (hdr->dbus1.serial == 0) return false; } return true; } unsigned int _dbus_message_unix_fds_from_header(const void *data, size_t size) { struct l_dbus_message message; uint32_t unix_fds; message.header = (uint8_t *) data; message.header_size = size; message.body_size = 0; message.sealed = true; if (!get_header_field(&message, DBUS_MESSAGE_FIELD_UNIX_FDS, 'u', &unix_fds)) return 0; return unix_fds; } struct l_dbus_message *dbus_message_from_blob(const void *data, size_t size, int fds[], uint32_t num_fds) { const struct dbus_header *hdr = data; struct l_dbus_message *message; size_t body_pos; unsigned int i; if (unlikely(size < DBUS_HEADER_SIZE)) return NULL; message = l_new(struct l_dbus_message, 1); message->refcount = 1; if (hdr->version == 1) { message->header_size = align_len(DBUS_HEADER_SIZE + hdr->dbus1.field_length, 8); message->body_size = hdr->dbus1.body_length; if (message->header_size + message->body_size < size) goto free; body_pos = message->header_size; } else { struct l_dbus_message_iter iter; struct l_dbus_message_iter header, variant, body; /* * GVariant message structure as per * https://wiki.gnome.org/Projects/GLib/GDBus/Version2 * is "(yyyyuta{tv}v)". As noted this is equivalent to * some other types, this one lets us get iterators for * the header and the body in the fewest steps. */ if (!_gvariant_iter_init(&iter, message, "(yyyyuta{tv})v", NULL, data, size)) goto free; if (!_gvariant_iter_enter_struct(&iter, &header)) goto free; if (!_gvariant_iter_enter_variant(&iter, &variant)) goto free; if (!_gvariant_iter_enter_struct(&variant, &body)) goto free; message->header_size = align_len(header.len - header.pos, 8); message->body_size = body.len - body.pos; message->signature = l_strndup(body.sig_start + body.sig_pos, body.sig_len - body.sig_pos); message->signature_free = true; message->header_end = header.len; body_pos = body.data + body.pos - data; } message->header = l_malloc(message->header_size); message->body = l_malloc(message->body_size); memcpy(message->header, data, message->header_size); memcpy(message->body, data + body_pos, message->body_size); message->sealed = true; /* If the field is absent message->signature will remain NULL */ if (hdr->version == 1) get_header_field(message, DBUS_MESSAGE_FIELD_SIGNATURE, 'g', &message->signature); if (num_fds) { uint32_t unix_fds, orig_fds = num_fds; if (!get_header_field(message, DBUS_MESSAGE_FIELD_UNIX_FDS, 'u', &unix_fds)) goto free; if (num_fds > unix_fds) num_fds = unix_fds; if (num_fds > L_ARRAY_SIZE(message->fds)) num_fds = L_ARRAY_SIZE(message->fds); for (i = num_fds; i < orig_fds; i++) close(fds[i]); message->num_fds = num_fds; memcpy(message->fds, fds, num_fds * sizeof(int)); } return message; free: l_dbus_message_unref(message); return NULL; } struct l_dbus_message *dbus_message_build(void *header, size_t header_size, void *body, size_t body_size, int fds[], uint32_t num_fds) { const struct dbus_header *hdr = header; struct l_dbus_message *message; unsigned int i; if (unlikely(header_size < DBUS_HEADER_SIZE)) return NULL; if (unlikely(!valid_header(hdr))) return NULL; /* * With GVariant we need to know the signature, use * dbus_message_from_blob instead. */ if (unlikely(hdr->version != 1)) return NULL; message = l_new(struct l_dbus_message, 1); message->refcount = 1; message->header_size = header_size; message->header = header; message->body_size = body_size; message->body = body; message->sealed = true; if (num_fds) { uint32_t unix_fds, orig_fds = num_fds; if (!get_header_field(message, DBUS_MESSAGE_FIELD_UNIX_FDS, 'u', &unix_fds)) { l_free(message); return NULL; } if (num_fds > unix_fds) num_fds = unix_fds; if (num_fds > L_ARRAY_SIZE(message->fds)) num_fds = L_ARRAY_SIZE(message->fds); for (i = num_fds; i < orig_fds; i++) close(fds[i]); message->num_fds = num_fds; memcpy(message->fds, fds, num_fds * sizeof(int)); } /* If the field is absent message->signature will remain NULL */ get_header_field(message, DBUS_MESSAGE_FIELD_SIGNATURE, 'g', &message->signature); return message; } bool dbus_message_compare(struct l_dbus_message *message, const void *data, size_t size) { struct l_dbus_message *other; bool ret = false; other = dbus_message_from_blob(data, size, NULL, 0); if (message->signature) { if (!other->signature) goto done; if (strcmp(message->signature, other->signature)) goto done; } else { if (other->signature) goto done; } if (message->body_size != other->body_size) goto done; if (message->header_size != other->header_size) goto done; ret = !memcmp(message->body, other->body, message->body_size); done: l_dbus_message_unref(other); return ret; } struct builder_driver { bool (*append_basic)(struct dbus_builder *, char, const void *); bool (*enter_struct)(struct dbus_builder *, const char *); bool (*leave_struct)(struct dbus_builder *); bool (*enter_dict)(struct dbus_builder *, const char *); bool (*leave_dict)(struct dbus_builder *); bool (*enter_array)(struct dbus_builder *, const char *); bool (*leave_array)(struct dbus_builder *); bool (*enter_variant)(struct dbus_builder *, const char *); bool (*leave_variant)(struct dbus_builder *); char *(*finish)(struct dbus_builder *, void **, size_t *); bool (*mark)(struct dbus_builder *); bool (*rewind)(struct dbus_builder *); struct dbus_builder *(*new)(void *, size_t); void (*free)(struct dbus_builder *); }; static struct builder_driver dbus1_driver = { .append_basic = _dbus1_builder_append_basic, .enter_struct = _dbus1_builder_enter_struct, .leave_struct = _dbus1_builder_leave_struct, .enter_dict = _dbus1_builder_enter_dict, .leave_dict = _dbus1_builder_leave_dict, .enter_variant = _dbus1_builder_enter_variant, .leave_variant = _dbus1_builder_leave_variant, .enter_array = _dbus1_builder_enter_array, .leave_array = _dbus1_builder_leave_array, .finish = _dbus1_builder_finish, .mark = _dbus1_builder_mark, .rewind = _dbus1_builder_rewind, .new = _dbus1_builder_new, .free = _dbus1_builder_free, }; static struct builder_driver gvariant_driver = { .append_basic = _gvariant_builder_append_basic, .enter_struct = _gvariant_builder_enter_struct, .leave_struct = _gvariant_builder_leave_struct, .enter_dict = _gvariant_builder_enter_dict, .leave_dict = _gvariant_builder_leave_dict, .enter_variant = _gvariant_builder_enter_variant, .leave_variant = _gvariant_builder_leave_variant, .enter_array = _gvariant_builder_enter_array, .leave_array = _gvariant_builder_leave_array, .finish = _gvariant_builder_finish, .mark = _gvariant_builder_mark, .rewind = _gvariant_builder_rewind, .new = _gvariant_builder_new, .free = _gvariant_builder_free, }; static void add_field(struct dbus_builder *builder, struct builder_driver *driver, uint8_t field, const char *type, const void *value) { if (driver == &gvariant_driver) { uint64_t long_field = field; driver->enter_struct(builder, "tv"); driver->append_basic(builder, 't', &long_field); } else { driver->enter_struct(builder, "yv"); driver->append_basic(builder, 'y', &field); } driver->enter_variant(builder, type); driver->append_basic(builder, type[0], value); driver->leave_variant(builder); driver->leave_struct(builder); } static void build_header(struct l_dbus_message *message, const char *signature) { struct dbus_builder *builder; struct builder_driver *driver; char *generated_signature; size_t header_size; bool gvariant; gvariant = _dbus_message_is_gvariant(message); if (gvariant) driver = &gvariant_driver; else driver = &dbus1_driver; builder = driver->new(message->header, message->header_size); driver->enter_array(builder, gvariant ? "(tv)" : "(yv)"); if (message->path) { add_field(builder, driver, DBUS_MESSAGE_FIELD_PATH, "o", message->path); l_free(message->path); message->path = NULL; } if (message->member) { add_field(builder, driver, DBUS_MESSAGE_FIELD_MEMBER, "s", message->member); l_free(message->member); message->member = NULL; } if (message->interface) { add_field(builder, driver, DBUS_MESSAGE_FIELD_INTERFACE, "s", message->interface); l_free(message->interface); message->interface = NULL; } if (message->destination) { add_field(builder, driver, DBUS_MESSAGE_FIELD_DESTINATION, "s", message->destination); l_free(message->destination); message->destination = NULL; } if (message->error_name != 0) { add_field(builder, driver, DBUS_MESSAGE_FIELD_ERROR_NAME, "s", message->error_name); l_free(message->error_name); message->error_name = NULL; } if (message->reply_serial != 0) { if (gvariant) { uint64_t reply_serial = message->reply_serial; add_field(builder, driver, DBUS_MESSAGE_FIELD_REPLY_SERIAL, "t", &reply_serial); } else { add_field(builder, driver, DBUS_MESSAGE_FIELD_REPLY_SERIAL, "u", &message->reply_serial); } message->reply_serial = 0; } if (message->sender) { add_field(builder, driver, DBUS_MESSAGE_FIELD_SENDER, "s", message->sender); l_free(message->sender); message->sender = NULL; } if (signature[0] != '\0' && !gvariant) add_field(builder, driver, DBUS_MESSAGE_FIELD_SIGNATURE, "g", signature); if (message->num_fds) add_field(builder, driver, DBUS_MESSAGE_FIELD_UNIX_FDS, "u", &message->num_fds); driver->leave_array(builder); generated_signature = driver->finish(builder, &message->header, &header_size); l_free(generated_signature); driver->free(builder); if (!_dbus_message_is_gvariant(message)) { struct dbus_header *hdr = message->header; hdr->dbus1.body_length = message->body_size; } /* We must align the end of the header to an 8-byte boundary */ message->header_size = align_len(header_size, 8); message->header = l_realloc(message->header, message->header_size); memset(message->header + header_size, 0, message->header_size - header_size); message->header_end = header_size; } struct container { char type; const char *sig_start; const char *sig_end; unsigned int n_items; }; static bool append_arguments(struct l_dbus_message *message, const char *signature, va_list args) { struct l_dbus_message_builder *builder; bool ret; builder = l_dbus_message_builder_new(message); if (!builder) return false; if (!l_dbus_message_builder_append_from_valist(builder, signature, args)) { l_dbus_message_builder_destroy(builder); return false; } l_dbus_message_builder_finalize(builder); ret = strcmp(signature, builder->message->signature) == 0; l_dbus_message_builder_destroy(builder); return ret; } LIB_EXPORT bool l_dbus_message_get_error(struct l_dbus_message *message, const char **name, const char **text) { struct dbus_header *hdr; const char *str; if (unlikely(!message)) return false; hdr = message->header; if (hdr->message_type != DBUS_MESSAGE_TYPE_ERROR) return false; if (!message->signature) return false; if (message->signature[0] != 's') return false; str = _dbus_message_get_nth_string_argument(message, 0); if (!str) return false; if (!message->error_name) get_header_field(message, DBUS_MESSAGE_FIELD_ERROR_NAME, 's', &message->error_name); if (name) *name = message->error_name; if (text) *text = str; return true; } LIB_EXPORT bool l_dbus_message_is_error(struct l_dbus_message *message) { struct dbus_header *hdr; if (unlikely(!message)) return false; hdr = message->header; return hdr->message_type == DBUS_MESSAGE_TYPE_ERROR; } LIB_EXPORT bool l_dbus_message_get_arguments_valist( struct l_dbus_message *message, const char *signature, va_list args) { struct l_dbus_message_iter iter; if (unlikely(!message)) return false; if (!message->signature) { /* An empty signature is valid */ if (!signature || *signature == '\0') return true; return false; } if (!signature || strcmp(message->signature, signature)) return false; if (_dbus_message_is_gvariant(message)) { if (!_gvariant_iter_init(&iter, message, message->signature, NULL, message->body, message->body_size)) return false; } else _dbus1_iter_init(&iter, message, message->signature, NULL, message->body, message->body_size); return message_iter_next_entry_valist(&iter, args); } LIB_EXPORT bool l_dbus_message_get_arguments(struct l_dbus_message *message, const char *signature, ...) { va_list args; bool result; va_start(args, signature); result = l_dbus_message_get_arguments_valist(message, signature, args); va_end(args); return result; } LIB_EXPORT bool l_dbus_message_set_arguments(struct l_dbus_message *message, const char *signature, ...) { va_list args; bool result; if (unlikely(!message)) return false; if (unlikely(message->sealed)) return false; if (!signature) return true; va_start(args, signature); result = append_arguments(message, signature, args); va_end(args); return result; } LIB_EXPORT bool l_dbus_message_set_arguments_valist( struct l_dbus_message *message, const char *signature, va_list args) { bool result; if (unlikely(!message)) return false; if (!signature) return true; result = append_arguments(message, signature, args); return result; } LIB_EXPORT const char *l_dbus_message_get_path(struct l_dbus_message *message) { if (unlikely(!message)) return NULL; if (!message->path && message->sealed) get_header_field(message, DBUS_MESSAGE_FIELD_PATH, 'o', &message->path); return message->path; } LIB_EXPORT const char *l_dbus_message_get_interface(struct l_dbus_message *message) { if (unlikely(!message)) return NULL; if (!message->interface && message->sealed) get_header_field(message, DBUS_MESSAGE_FIELD_INTERFACE, 's', &message->interface); return message->interface; } LIB_EXPORT const char *l_dbus_message_get_member(struct l_dbus_message *message) { if (unlikely(!message)) return NULL; if (!message->member && message->sealed) get_header_field(message, DBUS_MESSAGE_FIELD_MEMBER, 's', &message->member); return message->member; } LIB_EXPORT const char *l_dbus_message_get_destination(struct l_dbus_message *message) { if (unlikely(!message)) return NULL; if (!message->destination && message->sealed) get_header_field(message, DBUS_MESSAGE_FIELD_DESTINATION, 's', &message->destination); return message->destination; } LIB_EXPORT const char *l_dbus_message_get_sender(struct l_dbus_message *message) { if (unlikely(!message)) return NULL; if (!message->sender && message->sealed) get_header_field(message, DBUS_MESSAGE_FIELD_SENDER, 's', &message->sender); return message->sender; } LIB_EXPORT const char *l_dbus_message_get_signature( struct l_dbus_message *message) { if (unlikely(!message)) return NULL; return message->signature; } uint32_t _dbus_message_get_reply_serial(struct l_dbus_message *message) { if (unlikely(!message)) return 0; if (message->reply_serial == 0 && message->sealed) { if (_dbus_message_is_gvariant(message)) { uint64_t reply_serial = 0; get_header_field(message, DBUS_MESSAGE_FIELD_REPLY_SERIAL, 't', &reply_serial); message->reply_serial = reply_serial; } else get_header_field(message, DBUS_MESSAGE_FIELD_REPLY_SERIAL, 'u', &message->reply_serial); } return message->reply_serial; } enum dbus_message_type _dbus_message_get_type(struct l_dbus_message *message) { struct dbus_header *header; header = message->header; return header->message_type; } const char * _dbus_message_get_type_as_string(struct l_dbus_message *message) { struct dbus_header *header; header = message->header; switch (header->message_type) { case DBUS_MESSAGE_TYPE_METHOD_CALL: return "method_call"; case DBUS_MESSAGE_TYPE_METHOD_RETURN: return "method_return"; case DBUS_MESSAGE_TYPE_ERROR: return "error"; case DBUS_MESSAGE_TYPE_SIGNAL: return "signal"; } return NULL; } uint8_t _dbus_message_get_endian(struct l_dbus_message *message) { struct dbus_header *header = message->header; return header->endian; } uint8_t _dbus_message_get_version(struct l_dbus_message *message) { struct dbus_header *header = message->header; return header->version; } LIB_EXPORT bool l_dbus_message_iter_next_entry(struct l_dbus_message_iter *iter, ...) { va_list args; bool result; if (unlikely(!iter)) return false; va_start(args, iter); result = message_iter_next_entry_valist(iter, args); va_end(args); return result; } LIB_EXPORT bool l_dbus_message_iter_get_variant( struct l_dbus_message_iter *iter, const char *signature, ...) { va_list args; bool result; if (unlikely(!iter)) return false; if (!iter->sig_start || strlen(signature) != iter->sig_len || memcmp(iter->sig_start, signature, iter->sig_len)) return false; va_start(args, signature); result = message_iter_next_entry_valist(iter, args); va_end(args); return result; } LIB_EXPORT bool l_dbus_message_iter_get_fixed_array( struct l_dbus_message_iter *iter, void *out, uint32_t *n_elem) { if (unlikely(!iter)) return false; if (_dbus_message_is_gvariant(iter->message)) return false; return _dbus1_iter_get_fixed_array(iter, out, n_elem); } void _dbus_message_set_sender(struct l_dbus_message *message, const char *sender) { if (!_dbus_message_is_gvariant(message)) return; l_free(message->sender); message->sender = l_strdup(sender); } void _dbus_message_set_destination(struct l_dbus_message *message, const char *destination) { if (!_dbus_message_is_gvariant(message)) return; l_free(message->destination); message->destination = l_strdup(destination); } LIB_EXPORT struct l_dbus_message_builder *l_dbus_message_builder_new( struct l_dbus_message *message) { struct l_dbus_message_builder *ret; if (unlikely(!message)) return NULL; if (message->sealed) return NULL; ret = l_new(struct l_dbus_message_builder, 1); ret->message = l_dbus_message_ref(message); if (_dbus_message_is_gvariant(message)) ret->driver = &gvariant_driver; else ret->driver = &dbus1_driver; ret->builder = ret->driver->new(NULL, 0); return ret; } LIB_EXPORT void l_dbus_message_builder_destroy( struct l_dbus_message_builder *builder) { if (unlikely(!builder)) return; builder->driver->free(builder->builder); l_dbus_message_unref(builder->message); l_free(builder); } LIB_EXPORT bool l_dbus_message_builder_append_basic( struct l_dbus_message_builder *builder, char type, const void *value) { if (unlikely(!builder)) return false; return builder->driver->append_basic(builder->builder, type, value); } LIB_EXPORT bool l_dbus_message_builder_enter_container( struct l_dbus_message_builder *builder, char container_type, const char *signature) { if (unlikely(!builder)) return false; switch (container_type) { case DBUS_CONTAINER_TYPE_ARRAY: return builder->driver->enter_array(builder->builder, signature); case DBUS_CONTAINER_TYPE_DICT_ENTRY: return builder->driver->enter_dict(builder->builder, signature); case DBUS_CONTAINER_TYPE_STRUCT: return builder->driver->enter_struct(builder->builder, signature); case DBUS_CONTAINER_TYPE_VARIANT: return builder->driver->enter_variant(builder->builder, signature); default: break; } return false; } LIB_EXPORT bool l_dbus_message_builder_leave_container( struct l_dbus_message_builder *builder, char container_type) { if (unlikely(!builder)) return false; switch (container_type) { case DBUS_CONTAINER_TYPE_ARRAY: return builder->driver->leave_array(builder->builder); case DBUS_CONTAINER_TYPE_DICT_ENTRY: return builder->driver->leave_dict(builder->builder); case DBUS_CONTAINER_TYPE_STRUCT: return builder->driver->leave_struct(builder->builder); case DBUS_CONTAINER_TYPE_VARIANT: return builder->driver->leave_variant(builder->builder); default: break; } return false; } LIB_EXPORT bool l_dbus_message_builder_enter_struct( struct l_dbus_message_builder *builder, const char *signature) { return l_dbus_message_builder_enter_container(builder, 'r', signature); } LIB_EXPORT bool l_dbus_message_builder_leave_struct( struct l_dbus_message_builder *builder) { return l_dbus_message_builder_leave_container(builder, 'r'); } LIB_EXPORT bool l_dbus_message_builder_enter_array( struct l_dbus_message_builder *builder, const char *signature) { return l_dbus_message_builder_enter_container(builder, 'a', signature); } LIB_EXPORT bool l_dbus_message_builder_leave_array( struct l_dbus_message_builder *builder) { return l_dbus_message_builder_leave_container(builder, 'a'); } LIB_EXPORT bool l_dbus_message_builder_enter_dict( struct l_dbus_message_builder *builder, const char *signature) { return l_dbus_message_builder_enter_container(builder, 'e', signature); } LIB_EXPORT bool l_dbus_message_builder_leave_dict( struct l_dbus_message_builder *builder) { return l_dbus_message_builder_leave_container(builder, 'e'); } LIB_EXPORT bool l_dbus_message_builder_enter_variant( struct l_dbus_message_builder *builder, const char *signature) { return l_dbus_message_builder_enter_container(builder, 'v', signature); } LIB_EXPORT bool l_dbus_message_builder_leave_variant( struct l_dbus_message_builder *builder) { return l_dbus_message_builder_leave_container(builder, 'v'); } /** * l_dbus_message_builder_append_from_iter: * @builder: message builder to receive a new value * @from: message iterator to have its position moved by one value * * Copy one value from a message iterator onto a message builder. The * value's signature is also copied. * * Returns: whether the value was correctly copied. On failure both * the @from iterator and the @builder may have their positions * moved to somewhere within the new value if it's of a * container type. **/ LIB_EXPORT bool l_dbus_message_builder_append_from_iter( struct l_dbus_message_builder *builder, struct l_dbus_message_iter *from) { static const char *simple_types = "sogybnqiuxtd"; char type = from->sig_start[from->sig_pos]; char container_type; char signature[256]; struct l_dbus_message_iter iter; void *basic_ptr; uint64_t basic; uint32_t uint32_val; bool (*get_basic)(struct l_dbus_message_iter *, char, void *); bool (*enter_func)(struct l_dbus_message_iter *, struct l_dbus_message_iter *); bool (*enter_struct)(struct l_dbus_message_iter *, struct l_dbus_message_iter *); bool (*enter_array)(struct l_dbus_message_iter *, struct l_dbus_message_iter *); bool (*enter_variant)(struct l_dbus_message_iter *, struct l_dbus_message_iter *); if (_dbus_message_is_gvariant(from->message)) { get_basic = _gvariant_iter_next_entry_basic; enter_struct = _gvariant_iter_enter_struct; enter_array = _gvariant_iter_enter_array; enter_variant = _gvariant_iter_enter_variant; } else { get_basic = _dbus1_iter_next_entry_basic; enter_struct = _dbus1_iter_enter_struct; enter_array = _dbus1_iter_enter_array; enter_variant = _dbus1_iter_enter_variant; } if (strchr(simple_types, type)) { if (strchr("sog", type)) { if (!get_basic(from, type, &basic_ptr)) return false; } else { basic_ptr = &basic; if (!get_basic(from, type, basic_ptr)) return false; } if (!l_dbus_message_builder_append_basic(builder, type, basic_ptr)) return false; return true; } switch (type) { case 'h': if (!get_basic(from, type, &uint32_val)) return false; if (!l_dbus_message_builder_append_basic(builder, type, &builder->message->num_fds)) return false; if (builder->message->num_fds < L_ARRAY_SIZE(builder->message->fds)) { int fd; if (uint32_val < from->message->num_fds) fd = fcntl(from->message->fds[uint32_val], F_DUPFD_CLOEXEC, 3); else fd = -1; builder->message->fds[builder->message->num_fds++] = fd; } return true; case '(': enter_func = enter_struct; container_type = DBUS_CONTAINER_TYPE_STRUCT; break; case '{': enter_func = enter_struct; container_type = DBUS_CONTAINER_TYPE_DICT_ENTRY; break; case 'a': enter_func = enter_array; container_type = DBUS_CONTAINER_TYPE_ARRAY; break; case 'v': enter_func = enter_variant; container_type = DBUS_CONTAINER_TYPE_VARIANT; break; default: return false; } if (!enter_func(from, &iter)) return false; memcpy(signature, iter.sig_start, iter.sig_len); signature[iter.sig_len] = '\0'; if (!l_dbus_message_builder_enter_container(builder, container_type, signature)) return false; if (container_type == DBUS_CONTAINER_TYPE_ARRAY) while(l_dbus_message_builder_append_from_iter(builder, &iter)); else while (iter.sig_pos < iter.sig_len) if (!l_dbus_message_builder_append_from_iter(builder, &iter)) return false; if (!l_dbus_message_builder_leave_container(builder, container_type)) return false; return true; } LIB_EXPORT bool l_dbus_message_builder_append_from_valist( struct l_dbus_message_builder *builder, const char *signature, va_list args) { struct builder_driver *driver; char subsig[256]; const char *sigend; /* Nesting requires an extra stack entry for the base level */ struct container stack[DBUS_MAX_NESTING + 1]; unsigned int stack_index = 0; if (unlikely(!builder)) return false; driver = builder->driver; stack[stack_index].type = DBUS_CONTAINER_TYPE_STRUCT; stack[stack_index].sig_start = signature; stack[stack_index].sig_end = signature + strlen(signature); stack[stack_index].n_items = 0; while (stack_index != 0 || stack[0].sig_start != stack[0].sig_end) { const char *s; const char *str; if (stack[stack_index].type == DBUS_CONTAINER_TYPE_ARRAY && stack[stack_index].n_items == 0) stack[stack_index].sig_start = stack[stack_index].sig_end; if (stack[stack_index].sig_start == stack[stack_index].sig_end) { bool ret; /* * Sanity check in case of an invalid signature that * isn't caught elsewhere. */ if (unlikely(stack_index == 0)) return false; switch (stack[stack_index].type) { case DBUS_CONTAINER_TYPE_STRUCT: ret = driver->leave_struct(builder->builder); break; case DBUS_CONTAINER_TYPE_DICT_ENTRY: ret = driver->leave_dict(builder->builder); break; case DBUS_CONTAINER_TYPE_VARIANT: ret = driver->leave_variant(builder->builder); break; case DBUS_CONTAINER_TYPE_ARRAY: ret = driver->leave_array(builder->builder); break; default: ret = false; } if (!ret) return false; stack_index -= 1; continue; } s = stack[stack_index].sig_start; if (stack[stack_index].type != DBUS_CONTAINER_TYPE_ARRAY) stack[stack_index].sig_start += 1; else stack[stack_index].n_items -= 1; switch (*s) { case 'o': case 's': case 'g': str = va_arg(args, const char *); if (!driver->append_basic(builder->builder, *s, str)) return false; break; case 'b': case 'y': { uint8_t y = (uint8_t) va_arg(args, int); if (!driver->append_basic(builder->builder, *s, &y)) return false; break; } case 'n': case 'q': { uint16_t n = (uint16_t) va_arg(args, int); if (!driver->append_basic(builder->builder, *s, &n)) return false; break; } case 'i': case 'u': { uint32_t u = va_arg(args, uint32_t); if (!driver->append_basic(builder->builder, *s, &u)) return false; break; } case 'h': { int fd = va_arg(args, int); struct l_dbus_message *message = builder->message; if (!driver->append_basic(builder->builder, *s, &message->num_fds)) return false; if (message->num_fds < L_ARRAY_SIZE(message->fds)) message->fds[message->num_fds++] = fcntl(fd, F_DUPFD_CLOEXEC, 3); break; } case 'x': case 't': { uint64_t x = va_arg(args, uint64_t); if (!driver->append_basic(builder->builder, *s, &x)) return false; break; } case 'd': { double d = va_arg(args, double); if (!driver->append_basic(builder->builder, *s, &d)) return false; break; } case '(': case '{': if (stack_index == DBUS_MAX_NESTING) return false; sigend = _dbus_signature_end(s); memcpy(subsig, s + 1, sigend - s - 1); subsig[sigend - s - 1] = '\0'; if (*s == '(' && !driver->enter_struct(builder->builder, subsig)) return false; if (*s == '{' && !driver->enter_dict(builder->builder, subsig)) return false; if (stack[stack_index].type != DBUS_CONTAINER_TYPE_ARRAY) stack[stack_index].sig_start = sigend + 1; stack_index += 1; stack[stack_index].sig_start = s + 1; stack[stack_index].sig_end = sigend; stack[stack_index].n_items = 0; stack[stack_index].type = *s == '(' ? DBUS_CONTAINER_TYPE_STRUCT : DBUS_CONTAINER_TYPE_DICT_ENTRY; break; case 'v': if (stack_index == DBUS_MAX_NESTING) return false; str = va_arg(args, const char *); if (!str) return false; if (!driver->enter_variant(builder->builder, str)) return false; stack_index += 1; stack[stack_index].type = DBUS_CONTAINER_TYPE_VARIANT; stack[stack_index].sig_start = str; stack[stack_index].sig_end = str + strlen(str); stack[stack_index].n_items = 0; break; case 'a': if (stack_index == DBUS_MAX_NESTING) return false; sigend = _dbus_signature_end(s + 1) + 1; memcpy(subsig, s + 1, sigend - s - 1); subsig[sigend - s - 1] = '\0'; if (!driver->enter_array(builder->builder, subsig)) return false; if (stack[stack_index].type != DBUS_CONTAINER_TYPE_ARRAY) stack[stack_index].sig_start = sigend; stack_index += 1; stack[stack_index].sig_start = s + 1; stack[stack_index].sig_end = sigend; stack[stack_index].n_items = va_arg(args, unsigned int); stack[stack_index].type = DBUS_CONTAINER_TYPE_ARRAY; break; default: return false; } } return true; } LIB_EXPORT struct l_dbus_message *l_dbus_message_builder_finalize( struct l_dbus_message_builder *builder) { char *generated_signature; if (unlikely(!builder)) return NULL; generated_signature = builder->driver->finish(builder->builder, &builder->message->body, &builder->message->body_size); build_header(builder->message, generated_signature); builder->message->sealed = true; builder->message->signature = generated_signature; builder->message->signature_free = true; return builder->message; } bool _dbus_message_builder_mark(struct l_dbus_message_builder *builder) { if (unlikely(!builder)) return false; return builder->driver->mark(builder->builder); } bool _dbus_message_builder_rewind(struct l_dbus_message_builder *builder) { if (unlikely(!builder)) return false; return builder->driver->rewind(builder->builder); } bluez-5.87/ell/PaxHeaders/signal.c0000644000000000000000000000005014560467756014074 xustar0020 atime=1783076253 20 ctime=1783094099 bluez-5.87/ell/signal.c0000644000000000000000000001147314560467756013563 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "util.h" #include "io.h" #include "queue.h" #include "signal.h" #include "private.h" /** * SECTION:signal * @short_description: Unix signal support * * Unix signal support */ /** * l_signal: * * Opaque object representing the signal. */ struct l_signal { struct signal_desc *desc; l_signal_notify_cb_t callback; void *user_data; l_signal_destroy_cb_t destroy; }; struct signal_desc { uint32_t signo; struct l_queue *callbacks; }; static struct l_io *signalfd_io = NULL; static struct l_queue *signal_list = NULL; static sigset_t signal_mask; static void handle_callback(struct signal_desc *desc) { const struct l_queue_entry *entry; for (entry = l_queue_get_entries(desc->callbacks); entry; entry = entry->next) { struct l_signal *signal = entry->data; if (signal->callback) signal->callback(signal->user_data); } } static bool desc_match_signo(const void *a, const void *b) { const struct signal_desc *desc = a; uint32_t signo = L_PTR_TO_UINT(b); return (desc->signo == signo); } static bool signalfd_read_cb(struct l_io *io, void *user_data) { int fd = l_io_get_fd(io); struct signal_desc *desc; struct signalfd_siginfo si; ssize_t result; result = read(fd, &si, sizeof(si)); if (result != sizeof(si)) return true; desc = l_queue_find(signal_list, desc_match_signo, L_UINT_TO_PTR(si.ssi_signo)); if (desc) handle_callback(desc); return true; } static bool signalfd_add(int signo) { int fd; if (!signalfd_io) { fd = -1; sigemptyset(&signal_mask); } else fd = l_io_get_fd(signalfd_io); sigaddset(&signal_mask, signo); fd = signalfd(fd, &signal_mask, SFD_CLOEXEC); if (fd < 0) return false; if (signalfd_io) return true; signalfd_io = l_io_new(fd); if (!signalfd_io) { close(fd); return false; } l_io_set_close_on_destroy(signalfd_io, true); if (!l_io_set_read_handler(signalfd_io, signalfd_read_cb, NULL, NULL)) { l_io_destroy(signalfd_io); return false; } signal_list = l_queue_new(); return true; } static void signalfd_remove(int signo) { if (!signalfd_io) return; sigdelset(&signal_mask, signo); if (!sigisemptyset(&signal_mask)) { signalfd(l_io_get_fd(signalfd_io), &signal_mask, SFD_CLOEXEC); return; } l_io_destroy(signalfd_io); signalfd_io = NULL; l_queue_destroy(signal_list, NULL); signal_list = NULL; } /** * l_signal_create: * @callback: signal callback function * @user_data: user data provided to signal callback function * @destroy: destroy function for user data * * Create new signal callback handling for a given set of signals. * * Returns: a newly allocated #l_signal object **/ LIB_EXPORT struct l_signal *l_signal_create(uint32_t signo, l_signal_notify_cb_t callback, void *user_data, l_signal_destroy_cb_t destroy) { struct l_signal *signal; struct signal_desc *desc; sigset_t mask, oldmask; if (signo <= 1 || signo >= _NSIG) return NULL; signal = l_new(struct l_signal, 1); signal->callback = callback; signal->destroy = destroy; signal->user_data = user_data; desc = l_queue_find(signal_list, desc_match_signo, L_UINT_TO_PTR(signo)); if (desc) goto done; sigemptyset(&mask); sigaddset(&mask, signo); if (sigprocmask(SIG_BLOCK, &mask, &oldmask) < 0) { l_free(signal); return NULL; } if (!signalfd_add(signo)) { sigprocmask(SIG_SETMASK, &oldmask, NULL); l_free(signal); return NULL; } desc = l_new(struct signal_desc, 1); desc->signo = signo; desc->callbacks = l_queue_new(); l_queue_push_tail(signal_list, desc); done: l_queue_push_tail(desc->callbacks, signal); signal->desc = desc; return signal; } /** * l_signal_remove: * @signal: signal object * * Remove signal handling. **/ LIB_EXPORT void l_signal_remove(struct l_signal *signal) { struct signal_desc *desc; sigset_t mask; if (!signal) return; desc = signal->desc; l_queue_remove(desc->callbacks, signal); /* * As long as the signal descriptor has callbacks registered, it is * still needed to be active. */ if (!l_queue_isempty(desc->callbacks)) goto done; if (!l_queue_remove(signal_list, desc)) goto done; sigemptyset(&mask); sigaddset(&mask, desc->signo); /* * When the number of signals goes to zero, then this will close * the signalfd file descriptor, otherwise it will only adjust the * signal mask to account for the removed signal. * */ signalfd_remove(desc->signo); sigprocmask(SIG_UNBLOCK, &mask, NULL); l_queue_destroy(desc->callbacks, NULL); l_free(desc); done: if (signal->destroy) signal->destroy(signal->user_data); l_free(signal); } bluez-5.87/ell/PaxHeaders/cipher.h0000644000000000000000000000005014504767710014065 xustar0020 atime=1783076231 20 ctime=1783094098 bluez-5.87/ell/cipher.h0000644000000000000000000000365514504767710013557 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2015 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_CIPHER_H #define __ELL_CIPHER_H #ifdef __cplusplus extern "C" { #endif struct l_cipher; enum l_cipher_type { L_CIPHER_AES = 0, L_CIPHER_AES_CBC, L_CIPHER_AES_CTR, L_CIPHER_ARC4, L_CIPHER_DES, L_CIPHER_DES_CBC, L_CIPHER_DES3_EDE_CBC, L_CIPHER_RC2_CBC, }; struct l_cipher *l_cipher_new(enum l_cipher_type type, const void *key, size_t key_length); void l_cipher_free(struct l_cipher *cipher); bool l_cipher_encrypt(struct l_cipher *cipher, const void *in, void *out, size_t len); bool l_cipher_encryptv(struct l_cipher *cipher, const struct iovec *in, size_t in_cnt, const struct iovec *out, size_t out_cnt); bool l_cipher_decrypt(struct l_cipher *cipher, const void *in, void *out, size_t len); bool l_cipher_decryptv(struct l_cipher *cipher, const struct iovec *in, size_t in_cnt, const struct iovec *out, size_t out_cnt); bool l_cipher_set_iv(struct l_cipher *cipher, const uint8_t *iv, size_t iv_length); struct l_aead_cipher; enum l_aead_cipher_type { L_AEAD_CIPHER_AES_CCM = 0, L_AEAD_CIPHER_AES_GCM, }; struct l_aead_cipher *l_aead_cipher_new(enum l_aead_cipher_type type, const void *key, size_t key_length, size_t tag_length); void l_aead_cipher_free(struct l_aead_cipher *cipher); bool l_aead_cipher_encrypt(struct l_aead_cipher *cipher, const void *in, size_t in_len, const void *ad, size_t ad_len, const void *nonce, size_t nonce_len, void *out, size_t out_len); bool l_aead_cipher_decrypt(struct l_aead_cipher *cipher, const void *in, size_t in_len, const void *ad, size_t ad_len, const void *nonce, size_t nonce_len, void *out, size_t out_len); bool l_cipher_is_supported(enum l_cipher_type type); bool l_aead_cipher_is_supported(enum l_aead_cipher_type type); #ifdef __cplusplus } #endif #endif /* __ELL_CIPHER_H */ bluez-5.87/ell/PaxHeaders/string.h0000644000000000000000000000005014504767710014121 xustar0020 atime=1783076232 20 ctime=1783094098 bluez-5.87/ell/string.h0000644000000000000000000000227614504767710013611 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_STRING_H #define __ELL_STRING_H #include #include #ifdef __cplusplus extern "C" { #endif struct l_string; struct l_string *l_string_new(size_t initial_length); void l_string_free(struct l_string *string); DEFINE_CLEANUP_FUNC(l_string_free); char *l_string_unwrap(struct l_string *string); struct l_string *l_string_append(struct l_string *dest, const char *src); struct l_string *l_string_append_c(struct l_string *dest, const char c); struct l_string *l_string_append_fixed(struct l_string *dest, const char *src, size_t max); void l_string_append_vprintf(struct l_string *dest, const char *format, va_list args) __attribute__((format(printf, 2, 0))); void l_string_append_printf(struct l_string *dest, const char *format, ...) __attribute__((format(printf, 2, 3))); struct l_string *l_string_truncate(struct l_string *string, size_t new_size); unsigned int l_string_length(struct l_string *string); char **l_parse_args(const char *args, int *out_n_args); #ifdef __cplusplus } #endif #endif /* __ELL_STRING_H */ bluez-5.87/ell/PaxHeaders/dbus-private.h0000644000000000000000000000005014661631567015224 xustar0020 atime=1783076288 20 ctime=1783094099 bluez-5.87/ell/dbus-private.h0000644000000000000000000002516314661631567014714 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include enum dbus_message_type { DBUS_MESSAGE_TYPE_METHOD_CALL = 1, DBUS_MESSAGE_TYPE_METHOD_RETURN = 2, DBUS_MESSAGE_TYPE_ERROR = 3, DBUS_MESSAGE_TYPE_SIGNAL = 4, }; enum dbus_container_type { DBUS_CONTAINER_TYPE_ARRAY = 'a', DBUS_CONTAINER_TYPE_STRUCT = 'r', DBUS_CONTAINER_TYPE_VARIANT = 'v', DBUS_CONTAINER_TYPE_DICT_ENTRY = 'e', }; #if __BYTE_ORDER == __LITTLE_ENDIAN #define DBUS_NATIVE_ENDIAN 'l' #elif __BYTE_ORDER == __BIG_ENDIAN #define DBUS_NATIVE_ENDIAN 'B' #else #error "Unknown byte order" #endif struct dbus_header { uint8_t endian; uint8_t message_type; uint8_t flags; uint8_t version; union { struct { uint32_t body_length; uint32_t serial; uint32_t field_length; } __attribute__ ((packed)) dbus1; }; } __attribute__ ((packed)); #define DBUS_HEADER_SIZE 16 struct dbus_builder; struct l_string; struct l_dbus_interface; struct _dbus_method; struct _dbus_signal; struct _dbus_property; struct l_dbus_message_iter; struct l_dbus_message; struct l_dbus; struct _dbus_filter; struct _dbus_filter_condition; struct _dbus_filter_ops; void _dbus1_iter_init(struct l_dbus_message_iter *iter, struct l_dbus_message *message, const char *sig_start, const char *sig_end, const void *data, size_t len); bool _dbus1_iter_next_entry_basic(struct l_dbus_message_iter *iter, char type, void *out); bool _dbus1_iter_get_fixed_array(struct l_dbus_message_iter *iter, void *out, uint32_t *n_elem); bool _dbus1_iter_enter_struct(struct l_dbus_message_iter *iter, struct l_dbus_message_iter *structure); bool _dbus1_iter_enter_variant(struct l_dbus_message_iter *iter, struct l_dbus_message_iter *variant); bool _dbus1_iter_enter_array(struct l_dbus_message_iter *iter, struct l_dbus_message_iter *array); bool _dbus1_iter_skip_entry(struct l_dbus_message_iter *iter); struct dbus_builder *_dbus1_builder_new(void *body, size_t body_size); void _dbus1_builder_free(struct dbus_builder *builder); bool _dbus1_builder_append_basic(struct dbus_builder *builder, char type, const void *value); bool _dbus1_builder_enter_struct(struct dbus_builder *builder, const char *signature); bool _dbus1_builder_leave_struct(struct dbus_builder *builder); bool _dbus1_builder_enter_dict(struct dbus_builder *builder, const char *signature); bool _dbus1_builder_leave_dict(struct dbus_builder *builder); bool _dbus1_builder_enter_variant(struct dbus_builder *builder, const char *signature); bool _dbus1_builder_leave_variant(struct dbus_builder *builder); bool _dbus1_builder_enter_array(struct dbus_builder *builder, const char *signature); bool _dbus1_builder_leave_array(struct dbus_builder *builder); char *_dbus1_builder_finish(struct dbus_builder *builder, void **body, size_t *body_size); bool _dbus1_builder_mark(struct dbus_builder *builder); bool _dbus1_builder_rewind(struct dbus_builder *builder); void *_dbus_message_get_body(struct l_dbus_message *msg, size_t *out_size); void *_dbus_message_get_header(struct l_dbus_message *msg, size_t *out_size); void *_dbus_message_get_footer(struct l_dbus_message *msg, size_t *out_size); int *_dbus_message_get_fds(struct l_dbus_message *msg, uint32_t *num_fds); void _dbus_message_set_serial(struct l_dbus_message *msg, uint32_t serial); uint32_t _dbus_message_get_serial(struct l_dbus_message *msg); uint32_t _dbus_message_get_reply_serial(struct l_dbus_message *message); void _dbus_message_set_sender(struct l_dbus_message *message, const char *sender); void _dbus_message_set_destination(struct l_dbus_message *message, const char *destination); enum dbus_message_type _dbus_message_get_type(struct l_dbus_message *message); const char * _dbus_message_get_type_as_string(struct l_dbus_message *message); uint8_t _dbus_message_get_version(struct l_dbus_message *message); uint8_t _dbus_message_get_endian(struct l_dbus_message *message); const char *_dbus_message_get_nth_string_argument( struct l_dbus_message *message, int n); struct l_dbus_message *_dbus_message_new_method_call(uint8_t version, const char *destination, const char *path, const char *interface, const char *method); struct l_dbus_message *_dbus_message_new_signal(uint8_t version, const char *path, const char *interface, const char *name); struct l_dbus_message *_dbus_message_new_error(uint8_t version, uint32_t reply_serial, const char *destination, const char *name, const char *error); struct l_dbus_message *dbus_message_from_blob(const void *data, size_t size, int fds[], uint32_t num_fds); struct l_dbus_message *dbus_message_build(void *header, size_t header_size, void *body, size_t body_size, int fds[], uint32_t num_fds); bool dbus_message_compare(struct l_dbus_message *message, const void *data, size_t size); bool _dbus_message_builder_mark(struct l_dbus_message_builder *builder); bool _dbus_message_builder_rewind(struct l_dbus_message_builder *builder); unsigned int _dbus_message_unix_fds_from_header(const void *data, size_t size); const char *_dbus_signature_end(const char *signature); bool _dbus_valid_object_path(const char *path); bool _dbus_valid_signature(const char *sig); int _dbus_num_children(const char *sig); bool _dbus_valid_interface(const char *interface); bool _dbus_valid_method(const char *method); bool _dbus_parse_unique_name(const char *name, uint64_t *out_id); bool _dbus_valid_bus_name(const char *bus_name); bool _dbus1_header_is_valid(void *data, size_t size); void _dbus_method_introspection(struct _dbus_method *info, struct l_string *buf); void _dbus_signal_introspection(struct _dbus_signal *info, struct l_string *buf); void _dbus_property_introspection(struct _dbus_property *info, struct l_string *buf); void _dbus_interface_introspection(struct l_dbus_interface *interface, struct l_string *buf); struct l_dbus_interface *_dbus_interface_new(const char *name); void _dbus_interface_free(struct l_dbus_interface *interface); struct _dbus_method *_dbus_interface_find_method(struct l_dbus_interface *i, const char *method); struct _dbus_signal *_dbus_interface_find_signal(struct l_dbus_interface *i, const char *signal); struct _dbus_property *_dbus_interface_find_property(struct l_dbus_interface *i, const char *property); struct _dbus_object_tree *_dbus_object_tree_new(void); void _dbus_object_tree_free(struct _dbus_object_tree *tree); struct object_node *_dbus_object_tree_makepath(struct _dbus_object_tree *tree, const char *path); struct object_node *_dbus_object_tree_lookup(struct _dbus_object_tree *tree, const char *path); void _dbus_object_tree_prune_node(struct object_node *node); struct object_node *_dbus_object_tree_new_object(struct _dbus_object_tree *tree, const char *path, void *user_data, void (*destroy) (void *)); bool _dbus_object_tree_object_destroy(struct _dbus_object_tree *tree, const char *path); bool _dbus_object_tree_register_interface(struct _dbus_object_tree *tree, const char *interface, void (*setup_func)(struct l_dbus_interface *), void (*destroy) (void *), bool old_style_properties); bool _dbus_object_tree_unregister_interface(struct _dbus_object_tree *tree, const char *interface); bool _dbus_object_tree_add_interface(struct _dbus_object_tree *tree, const char *path, const char *interface, void *user_data); bool _dbus_object_tree_remove_interface(struct _dbus_object_tree *tree, const char *path, const char *interface); void *_dbus_object_tree_get_interface_data(struct _dbus_object_tree *tree, const char *path, const char *interface); bool _dbus_object_tree_set_interface_data(struct _dbus_object_tree *tree, const char *path, const char *interface, void *user_data); void _dbus_object_tree_introspect(struct _dbus_object_tree *tree, const char *path, struct l_string *buf); bool _dbus_object_tree_dispatch(struct _dbus_object_tree *tree, struct l_dbus *dbus, struct l_dbus_message *message); struct l_dbus_message *_dbus_object_tree_get_objects( struct _dbus_object_tree *tree, struct l_dbus *dbus, const char *path, struct l_dbus_message *message); bool _dbus_object_tree_property_changed(struct l_dbus *dbus, const char *path, const char *interface_name, const char *property_name); void _dbus_object_tree_signals_flush(struct l_dbus *dbus, const char *path); typedef void (*_dbus_name_owner_change_func_t)(const char *name, uint64_t old_owner, uint64_t new_owner, void *user_data); uint8_t _dbus_get_version(struct l_dbus *dbus); int _dbus_get_fd(struct l_dbus *dbus); struct _dbus_object_tree *_dbus_get_tree(struct l_dbus *dbus); struct _dbus_name_ops { bool (*get_name_owner)(struct l_dbus *bus, const char *name); }; struct _dbus_name_cache; struct _dbus_name_cache *_dbus_name_cache_new(struct l_dbus *bus, const struct _dbus_name_ops *driver); void _dbus_name_cache_free(struct _dbus_name_cache *cache); bool _dbus_name_cache_add(struct _dbus_name_cache *cache, const char *name); bool _dbus_name_cache_remove(struct _dbus_name_cache *cache, const char *name); const char *_dbus_name_cache_lookup(struct _dbus_name_cache *cache, const char *name); void _dbus_name_cache_notify(struct _dbus_name_cache *cache, const char *name, const char *owner); unsigned int _dbus_name_cache_add_watch(struct _dbus_name_cache *cache, const char *name, l_dbus_watch_func_t connect_func, l_dbus_watch_func_t disconnect_func, void *user_data, l_dbus_destroy_func_t destroy); bool _dbus_name_cache_remove_watch(struct _dbus_name_cache *cache, unsigned int id); struct _dbus_filter_condition { enum l_dbus_match_type type; const char *value; }; struct _dbus_filter_ops { bool skip_register; bool (*add_match)(struct l_dbus *bus, unsigned int id, const struct _dbus_filter_condition *rule, int rule_len); bool (*remove_match)(struct l_dbus *bus, unsigned int id); }; struct _dbus_filter *_dbus_filter_new(struct l_dbus *dbus, const struct _dbus_filter_ops *driver, struct _dbus_name_cache *name_cache); void _dbus_filter_free(struct _dbus_filter *filter); unsigned int _dbus_filter_add_rule(struct _dbus_filter *filter, const struct _dbus_filter_condition *rule, int rule_len, l_dbus_message_func_t signal_func, void *user_data); bool _dbus_filter_remove_rule(struct _dbus_filter *filter, unsigned int id); char *_dbus_filter_rule_to_str(const struct _dbus_filter_condition *rule, int rule_len); void _dbus_filter_dispatch(struct l_dbus_message *message, void *user_data); bluez-5.87/ell/PaxHeaders/settings.c0000644000000000000000000000005014721132752014437 xustar0020 atime=1783076259 20 ctime=1783094099 bluez-5.87/ell/settings.c0000644000000000000000000007422114721132752014126 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #if __STDC_VERSION__ <= 199409L #define _DEFAULT_SOURCE /* for strto{u}ll() */ #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include "useful.h" #include "strv.h" #include "utf8.h" #include "string.h" #include "queue.h" #include "settings.h" #include "private.h" #include "missing.h" #include "pem-private.h" struct setting_data { char *key; char *value; }; struct embedded_group_data { char *name; char type[32]; size_t len; char data[]; }; struct group_data { char *name; struct l_queue *settings; }; struct l_settings { l_settings_debug_cb_t debug_handler; l_settings_destroy_cb_t debug_destroy; void *debug_data; struct l_queue *groups; struct l_queue *embedded_groups; }; static void setting_destroy(void *data) { struct setting_data *pair = data; l_free(pair->key); explicit_bzero(pair->value, strlen(pair->value)); l_free(pair->value); l_free(pair); } static void group_destroy(void *data) { struct group_data *group = data; l_free(group->name); l_queue_destroy(group->settings, setting_destroy); l_free(group); } static void embedded_group_destroy(void *data) { struct embedded_group_data *group = data; l_free(group->name); l_free(group); } LIB_EXPORT struct l_settings *l_settings_new(void) { struct l_settings *settings; settings = l_new(struct l_settings, 1); settings->groups = l_queue_new(); settings->embedded_groups = l_queue_new(); return settings; } static void copy_key_value_foreach(void *data, void *user_data) { struct setting_data *s = data; struct l_queue *settings = user_data; struct setting_data *copy = l_new(struct setting_data, 1); copy->key = l_strdup(s->key); copy->value = l_strdup(s->value); l_queue_push_head(settings, copy); } static void copy_group_foreach(void *data, void *user_data) { struct group_data *group = data; struct l_queue *groups = user_data; struct group_data *copy = l_new(struct group_data, 1); copy->name = l_strdup(group->name); copy->settings = l_queue_new(); l_queue_push_head(groups, copy); l_queue_foreach(group->settings, copy_key_value_foreach, copy->settings); } static void copy_embedded_foreach(void *data, void *user_data) { struct embedded_group_data *embedded = data; struct l_queue *groups = user_data; struct embedded_group_data *copy; copy = l_memdup(embedded, sizeof(struct embedded_group_data) + embedded->len + 1); copy->name = l_strdup(embedded->name); l_queue_push_tail(groups, copy); } LIB_EXPORT struct l_settings *l_settings_clone( const struct l_settings *settings) { struct l_settings *copy; if (unlikely(!settings)) return NULL; copy = l_settings_new(); l_queue_foreach(settings->groups, copy_group_foreach, copy->groups); l_queue_foreach(settings->embedded_groups, copy_embedded_foreach, copy->embedded_groups); return copy; } LIB_EXPORT void l_settings_free(struct l_settings *settings) { if (unlikely(!settings)) return; if (settings->debug_destroy) settings->debug_destroy(settings->debug_data); l_queue_destroy(settings->groups, group_destroy); l_queue_destroy(settings->embedded_groups, embedded_group_destroy); l_free(settings); } static char *unescape_value(const char *value) { char *ret; char *n; const char *o; ret = l_new(char, strlen(value) + 1); for (n = ret, o = value; *o; o++, n++) { if (*o != '\\') { *n = *o; continue; } o += 1; switch (*o) { case 's': *n = ' '; break; case 'n': *n = '\n'; break; case 't': *n = '\t'; break; case 'r': *n = '\r'; break; case '\\': *n = '\\'; break; default: explicit_bzero(ret, n - ret); l_free(ret); return NULL; } } return ret; } static char *escape_value(const char *value) { size_t i; size_t j; char *ret; bool lead_whitespace; for (i = 0, j = 0, lead_whitespace = true; value[i]; i++) { switch (value[i]) { case ' ': case '\t': if (lead_whitespace) j += 1; break; case '\n': case '\r': case '\\': j += 1; /* fall through */ default: lead_whitespace = false; } } ret = l_malloc(i + j + 1); for (i = 0, j = 0, lead_whitespace = true; value[i]; i++) { switch (value[i]) { case ' ': if (lead_whitespace) { ret[j++] = '\\'; ret[j++] = 's'; } else ret[j++] = value[i]; break; case '\t': if (lead_whitespace) { ret[j++] = '\\'; ret[j++] = 't'; } else ret[j++] = value[i]; break; case '\n': ret[j++] = '\\'; ret[j++] = 'n'; lead_whitespace = false; break; case '\r': ret[j++] = '\\'; ret[j++] = 'r'; lead_whitespace = false; break; case '\\': ret[j++] = '\\'; ret[j++] = '\\'; lead_whitespace = false; break; default: ret[j++] = value[i]; lead_whitespace = false; } } ret[j] = '\0'; return ret; } static ssize_t parse_pem(const char *data, size_t len) { const char *ptr; const char *end; size_t count = 0; ptr = data; end = data + len; while (ptr && ptr < end) { const char *pem_start = ptr; if (!pem_next(ptr, len, NULL, NULL, &ptr, true)) { if (ptr) return -EINVAL; break; } len -= ptr - pem_start; count += ptr - pem_start; } return count; } struct group_extension { char *name; ssize_t (*parse)(const char *data, size_t len); }; static const struct group_extension pem_extension = { .name = "pem", .parse = parse_pem, }; static const struct group_extension *extensions[] = { &pem_extension, NULL }; static const struct group_extension *find_group_extension(const char *type, size_t len) { unsigned int i; for (i = 0; extensions[i]; i++) { if (!strncmp(type, extensions[i]->name, len)) return extensions[i]; } return NULL; } static ssize_t parse_embedded_group(struct l_settings *setting, const char *data, size_t line_len, size_t len, size_t line) { struct embedded_group_data *group; const struct group_extension *ext; const char *ptr; const char *type; size_t type_len; const char *name; size_t name_len; ssize_t bytes; /* Must be at least [@a@b] */ if (line_len < 6) goto invalid_group; /* caller checked data[1] == '@', next char is type */ type = data + 2; ptr = memchr(type, '@', line_len - 2); type_len = ptr - type; if (!ptr || type_len > 31 || type_len < 1) goto invalid_group; if (ptr + 1 > data + line_len) goto invalid_group; name = ptr + 1; /* subtract [@@ + type */ ptr = memchr(name, ']', line_len - 3 - type_len); name_len = ptr - name; if (!ptr || name_len < 1) goto invalid_group; ext = find_group_extension(type, type_len); if (!ext) goto invalid_group; if (ptr + 2 > data + len) { l_util_debug(setting->debug_handler, setting->debug_data, "Embedded group had no payload"); return -EINVAL; } bytes = ext->parse(ptr + 2, len - line_len); if (bytes < 0) { l_util_debug(setting->debug_handler, setting->debug_data, "Failed to parse embedded group data"); return -EINVAL; } group = l_malloc(sizeof(struct embedded_group_data) + bytes + 1); group->name = l_strndup(name, name_len); memcpy(group->type, type, type_len); group->type[type_len] = '\0'; group->len = bytes; memcpy(group->data, ptr + 2, bytes); group->data[bytes] = '\0'; l_queue_push_tail(setting->embedded_groups, group); return bytes; invalid_group: l_util_debug(setting->debug_handler, setting->debug_data, "Invalid embedded group at line %zd", line); return -EINVAL; } static bool parse_group(struct l_settings *settings, const char *data, size_t len, size_t line) { size_t i = 1; size_t end; struct group_data *group; while (i < len && data[i] != ']') { if (l_ascii_isprint(data[i]) == false || data[i] == '[') { l_util_debug(settings->debug_handler, settings->debug_data, "Invalid group name at line %zd", line); return false; } i += 1; } if (i >= len) { l_util_debug(settings->debug_handler, settings->debug_data, "Unterminated group name at line %zd", line); return false; } end = i; i += 1; while (i < len && l_ascii_isblank(data[i])) i += 1; if (i != len) { l_util_debug(settings->debug_handler, settings->debug_data, "Junk characters at the end of line %zd", line); return false; } group = l_new(struct group_data, 1); group->name = l_strndup(data + 1, end - 1); group->settings = l_queue_new(); l_queue_push_tail(settings->groups, group); return true; } static bool validate_key_character(char c) { if (l_ascii_isalnum(c)) return true; if (c == '_' || c == '-' || c == '.') return true; return false; } static unsigned int parse_key(struct l_settings *settings, const char *data, size_t len, size_t line) { unsigned int i; unsigned int end; struct group_data *group; struct setting_data *pair; for (i = 0; i < len; i++) { if (validate_key_character(data[i])) continue; if (l_ascii_isblank(data[i])) break; l_util_debug(settings->debug_handler, settings->debug_data, "Invalid character in Key on line %zd", line); return 0; } end = i; /* Make sure the rest of the characters are blanks */ while (i < len) { if (l_ascii_isblank(data[i++])) continue; l_util_debug(settings->debug_handler, settings->debug_data, "Garbage after Key on line %zd", line); return 0; } group = l_queue_peek_tail(settings->groups); pair = l_new(struct setting_data, 1); pair->key = l_strndup(data, end); l_queue_push_head(group->settings, pair); return end; } static bool parse_value(struct l_settings *settings, const char *data, size_t len, size_t line) { unsigned int end = len; struct group_data *group; struct setting_data *pair; group = l_queue_peek_tail(settings->groups); pair = l_queue_pop_head(group->settings); if (!l_utf8_validate(data, len, NULL)) { l_util_debug(settings->debug_handler, settings->debug_data, "Invalid UTF8 in value on line: %zd", line); l_free(pair->key); l_free(pair); return false; } pair->value = l_strndup(data, end); l_queue_push_tail(group->settings, pair); return true; } static bool parse_keyvalue(struct l_settings *settings, const char *data, size_t len, size_t line) { const char *equal = memchr(data, '=', len); if (!equal) { l_util_debug(settings->debug_handler, settings->debug_data, "Delimiter '=' not found on line: %zd", line); return false; } if (equal == data) { l_util_debug(settings->debug_handler, settings->debug_data, "Empty key on line: %zd", line); return false; } if (!parse_key(settings, data, equal - data, line)) return false; equal += 1; while (equal < data + len && l_ascii_isblank(*equal)) equal += 1; return parse_value(settings, equal, len - (equal - data), line); } LIB_EXPORT bool l_settings_load_from_data(struct l_settings *settings, const char *data, size_t len) { size_t pos = 0; bool r = true; bool has_group = false; const char *eol; size_t line = 1; size_t line_len; if (unlikely(!settings || !data || !len)) return false; while (pos < len && r) { if (l_ascii_isblank(data[pos])) { pos += 1; continue; } if (data[pos] == '\n') { line += 1; pos += 1; continue; } eol = memchr(data + pos, '\n', len - pos); if (!eol) eol = data + len; line_len = eol - data - pos; if (line_len > 1 && data[pos] == '[' && data[pos + 1] == '@') { ssize_t ret; ret = parse_embedded_group(settings, data + pos, line_len, len - pos, line); if (ret < 0) return false; /* * This is the offset for the actual raw data, the * group line will be offset below */ pos += ret; } else if (data[pos] == '[') { r = parse_group(settings, data + pos, line_len, line); if (r) has_group = true; } else if (data[pos] != '#') { if (!has_group) return false; r = parse_keyvalue(settings, data + pos, line_len, line); } pos += line_len; } return r; } LIB_EXPORT char *l_settings_to_data(const struct l_settings *settings, size_t *len) { struct l_string *buf; char *ret; const struct l_queue_entry *group_entry; if (unlikely(!settings)) return NULL; buf = l_string_new(255); group_entry = l_queue_get_entries(settings->groups); while (group_entry) { struct group_data *group = group_entry->data; const struct l_queue_entry *setting_entry; l_string_append_printf(buf, "[%s]\n", group->name); setting_entry = l_queue_get_entries(group->settings); while (setting_entry) { struct setting_data *setting = setting_entry->data; l_string_append_printf(buf, "%s=%s\n", setting->key, setting->value); setting_entry = setting_entry->next; } if (group_entry->next) l_string_append_c(buf, '\n'); group_entry = group_entry->next; } group_entry = l_queue_get_entries(settings->embedded_groups); if (group_entry && l_queue_length(settings->groups) > 0) l_string_append_c(buf, '\n'); while (group_entry) { struct embedded_group_data *group = group_entry->data; l_string_append_printf(buf, "[@%s@%s]\n%s", group->type, group->name, group->data); if (group_entry->next) l_string_append_c(buf, '\n'); group_entry = group_entry->next; } ret = l_string_unwrap(buf); if (len) *len = strlen(ret); return ret; } LIB_EXPORT bool l_settings_load_from_file(struct l_settings *settings, const char *filename) { int fd; struct stat st; char *data; bool r; if (unlikely(!settings || !filename)) return false; fd = open(filename, O_RDONLY); if (fd < 0) { l_util_debug(settings->debug_handler, settings->debug_data, "Could not open %s (%s)", filename, strerror(errno)); return false; } if (fstat(fd, &st) < 0) { l_util_debug(settings->debug_handler, settings->debug_data, "Could not stat %s (%s)", filename, strerror(errno)); close(fd); return false; } /* Nothing to do, assume success */ if (st.st_size == 0) { close(fd); return true; } data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { l_util_debug(settings->debug_handler, settings->debug_data, "Could not mmap %s (%s)", filename, strerror(errno)); close(fd); return false; } r = l_settings_load_from_data(settings, data, st.st_size); munmap(data, st.st_size); close(fd); return r; } LIB_EXPORT bool l_settings_set_debug(struct l_settings *settings, l_settings_debug_cb_t callback, void *user_data, l_settings_destroy_cb_t destroy) { if (unlikely(!settings)) return false; if (settings->debug_destroy) settings->debug_destroy(settings->debug_data); settings->debug_handler = callback; settings->debug_destroy = destroy; settings->debug_data = user_data; return true; } static bool group_match(const void *a, const void *b) { const struct group_data *group = a; const char *name = b; return !strcmp(group->name, name); } struct gather_data { int cur; char **v; }; static void gather_groups(void *data, void *user_data) { struct group_data *group_data = data; struct gather_data *gather = user_data; gather->v[gather->cur++] = l_strdup(group_data->name); } LIB_EXPORT char **l_settings_get_groups(const struct l_settings *settings) { char **ret; struct gather_data gather; if (unlikely(!settings)) return NULL; ret = l_new(char *, l_queue_length(settings->groups) + 1); gather.v = ret; gather.cur = 0; l_queue_foreach(settings->groups, gather_groups, &gather); return ret; } LIB_EXPORT bool l_settings_has_group(const struct l_settings *settings, const char *group_name) { struct group_data *group; if (unlikely(!settings)) return false; group = l_queue_find(settings->groups, group_match, group_name); return !!group; } static bool key_match(const void *a, const void *b) { const struct setting_data *setting = a; const char *key = b; return !strcmp(setting->key, key); } static void gather_keys(void *data, void *user_data) { struct setting_data *setting_data = data; struct gather_data *gather = user_data; gather->v[gather->cur++] = l_strdup(setting_data->key); } LIB_EXPORT char **l_settings_get_keys(const struct l_settings *settings, const char *group_name) { char **ret; struct group_data *group_data; struct gather_data gather; if (unlikely(!settings)) return NULL; group_data = l_queue_find(settings->groups, group_match, group_name); if (!group_data) return NULL; ret = l_new(char *, l_queue_length(group_data->settings) + 1); gather.v = ret; gather.cur = 0; l_queue_foreach(group_data->settings, gather_keys, &gather); return ret; } LIB_EXPORT bool l_settings_has_key(const struct l_settings *settings, const char *group_name, const char *key) { struct group_data *group; struct setting_data *setting; if (unlikely(!settings)) return false; group = l_queue_find(settings->groups, group_match, group_name); if (!group) return false; setting = l_queue_find(group->settings, key_match, key); return !!setting; } LIB_EXPORT const char *l_settings_get_value(const struct l_settings *settings, const char *group_name, const char *key) { struct group_data *group; struct setting_data *setting; if (unlikely(!settings)) return NULL; group = l_queue_find(settings->groups, group_match, group_name); if (!group) return NULL; setting = l_queue_find(group->settings, key_match, key); if (!setting) return NULL; return setting->value; } static bool validate_group_name(const char *group_name) { int i; for (i = 0; group_name[i]; i++) { if (!l_ascii_isprint(group_name[i])) return false; if (group_name[i] == ']' || group_name[i] == '[') return false; } return true; } LIB_EXPORT bool l_settings_add_group(struct l_settings *settings, const char *group_name) { struct group_data *group; if (unlikely(!settings || !group_name)) return false; if (!validate_group_name(group_name)) { l_util_debug(settings->debug_handler, settings->debug_data, "Invalid group name %s", group_name); return false; } group = l_queue_find(settings->groups, group_match, group_name); if (group) { l_util_debug(settings->debug_handler, settings->debug_data, "Group %s exists", group_name); return true; } group = l_new(struct group_data, 1); group->name = l_strdup(group_name); group->settings = l_queue_new(); l_queue_push_tail(settings->groups, group); return true; } static bool validate_key(const char *key) { int i; for (i = 0; key[i]; i++) { if (!validate_key_character(key[i])) return false; } return true; } static bool set_value(struct l_settings *settings, const char *group_name, const char *key, char *value) { struct group_data *group; struct setting_data *pair; if (!validate_group_name(group_name)) { l_util_debug(settings->debug_handler, settings->debug_data, "Invalid group name %s", group_name); goto error; } if (!validate_key(key)) { l_util_debug(settings->debug_handler, settings->debug_data, "Invalid key %s", key); goto error; } group = l_queue_find(settings->groups, group_match, group_name); if (!group) { group = l_new(struct group_data, 1); group->name = l_strdup(group_name); group->settings = l_queue_new(); l_queue_push_tail(settings->groups, group); goto add_pair; } pair = l_queue_find(group->settings, key_match, key); if (!pair) { add_pair: pair = l_new(struct setting_data, 1); pair->key = l_strdup(key); pair->value = value; l_queue_push_tail(group->settings, pair); return true; } explicit_bzero(pair->value, strlen(pair->value)); l_free(pair->value); pair->value = value; return true; error: explicit_bzero(value, strlen(value)); l_free(value); return false; } LIB_EXPORT bool l_settings_set_value(struct l_settings *settings, const char *group_name, const char *key, const char *value) { if (unlikely(!settings || !value)) return false; return set_value(settings, group_name, key, l_strdup(value)); } LIB_EXPORT bool l_settings_get_bool(const struct l_settings *settings, const char *group_name, const char *key, bool *out) { const char *value; value = l_settings_get_value(settings, group_name, key); if (!value) return false; if (!strcasecmp(value, "true") || !strcmp(value, "1")) { if (out) *out = true; return true; } if (!strcasecmp(value, "false") || !strcmp(value, "0")) { if (out) *out = false; return true; } l_util_debug(settings->debug_handler, settings->debug_data, "Could not interpret %s as a bool", value); return false; } LIB_EXPORT bool l_settings_set_bool(struct l_settings *settings, const char *group_name, const char *key, bool in) { static const char *true_str = "true"; static const char *false_str = "false"; const char *v; if (in == false) v = false_str; else v = true_str; return l_settings_set_value(settings, group_name, key, v); } LIB_EXPORT bool l_settings_get_int(const struct l_settings *settings, const char *group_name, const char *key, int *out) { const char *value = l_settings_get_value(settings, group_name, key); long int r; int t; char *endp; if (!value) return false; if (*value == '\0') goto error; errno = 0; t = r = strtol(value, &endp, 0); if (*endp != '\0') goto error; if (unlikely(errno == ERANGE || r != t)) goto error; if (out) *out = r; return true; error: l_util_debug(settings->debug_handler, settings->debug_data, "Could not interpret %s as an int", value); return false; } LIB_EXPORT bool l_settings_set_int(struct l_settings *settings, const char *group_name, const char *key, int in) { char buf[64]; snprintf(buf, sizeof(buf), "%d", in); return l_settings_set_value(settings, group_name, key, buf); } LIB_EXPORT bool l_settings_get_uint(const struct l_settings *settings, const char *group_name, const char *key, unsigned int *out) { const char *value = l_settings_get_value(settings, group_name, key); if (!value) return false; if (l_safe_atou32(value, out) < 0) goto error; return true; error: l_util_debug(settings->debug_handler, settings->debug_data, "Could not interpret %s as a uint", value); return false; } LIB_EXPORT bool l_settings_set_uint(struct l_settings *settings, const char *group_name, const char *key, unsigned int in) { char buf[64]; snprintf(buf, sizeof(buf), "%u", in); return l_settings_set_value(settings, group_name, key, buf); } LIB_EXPORT bool l_settings_get_int64(const struct l_settings *settings, const char *group_name, const char *key, int64_t *out) { const char *value = l_settings_get_value(settings, group_name, key); int64_t r; char *endp; if (!value) return false; if (*value == '\0') goto error; errno = 0; r = strtoll(value, &endp, 0); if (*endp != '\0') goto error; if (unlikely(errno == ERANGE)) goto error; if (out) *out = r; return true; error: l_util_debug(settings->debug_handler, settings->debug_data, "Could not interpret %s as an int64", value); return false; } LIB_EXPORT bool l_settings_set_int64(struct l_settings *settings, const char *group_name, const char *key, int64_t in) { char buf[64]; snprintf(buf, sizeof(buf), "%" PRId64, in); return l_settings_set_value(settings, group_name, key, buf); } LIB_EXPORT bool l_settings_get_uint64(const struct l_settings *settings, const char *group_name, const char *key, uint64_t *out) { const char *value = l_settings_get_value(settings, group_name, key); uint64_t r; char *endp; if (!value) return false; /* Do not allow '+' or '-' or empty string */ if (!l_ascii_isdigit(*value)) goto error; errno = 0; r = strtoull(value, &endp, 0); if (*endp != '\0') goto error; if (unlikely(errno == ERANGE)) goto error; if (out) *out = r; return true; error: l_util_debug(settings->debug_handler, settings->debug_data, "Could not interpret %s as a uint64", value); return false; } LIB_EXPORT bool l_settings_set_uint64(struct l_settings *settings, const char *group_name, const char *key, uint64_t in) { char buf[64]; snprintf(buf, sizeof(buf), "%" PRIu64, in); return l_settings_set_value(settings, group_name, key, buf); } LIB_EXPORT char *l_settings_get_string(const struct l_settings *settings, const char *group_name, const char *key) { const char *value = l_settings_get_value(settings, group_name, key); if (!value) return NULL; return unescape_value(value); } LIB_EXPORT bool l_settings_set_string(struct l_settings *settings, const char *group_name, const char *key, const char *value) { char *buf; if (unlikely(!settings || !value)) return false; buf = escape_value(value); return set_value(settings, group_name, key, buf); } LIB_EXPORT char **l_settings_get_string_list(const struct l_settings *settings, const char *group_name, const char *key, const char delimiter) { const char *value = l_settings_get_value(settings, group_name, key); char *str; char **ret; if (!value) return NULL; str = unescape_value(value); if (str == NULL) return NULL; ret = l_strsplit(str, delimiter); l_free(str); return ret; } LIB_EXPORT bool l_settings_set_string_list(struct l_settings *settings, const char *group_name, const char *key, char **value, char delimiter) { char *buf; char *tmp; if (unlikely(!settings || !value)) return false; tmp = l_strjoinv(value, delimiter); buf = escape_value(tmp); l_free(tmp); return set_value(settings, group_name, key, buf); } LIB_EXPORT bool l_settings_get_double(const struct l_settings *settings, const char *group_name, const char *key, double *out) { const char *value = l_settings_get_value(settings, group_name, key); char *endp; double r; if (!value) return NULL; if (*value == '\0') goto error; errno = 0; r = strtod(value, &endp); if (*endp != '\0') goto error; if (unlikely(errno == ERANGE)) goto error; if (out) *out = r; return true; error: l_util_debug(settings->debug_handler, settings->debug_data, "Could not interpret %s as a double", value); return false; } LIB_EXPORT bool l_settings_set_double(struct l_settings *settings, const char *group_name, const char *key, double in) { L_AUTO_FREE_VAR(char *, buf); buf = l_strdup_printf("%f", in); return l_settings_set_value(settings, group_name, key, buf); } LIB_EXPORT bool l_settings_get_float(const struct l_settings *settings, const char *group_name, const char *key, float *out) { const char *value = l_settings_get_value(settings, group_name, key); char *endp; float r; if (!value) return NULL; if (*value == '\0') goto error; errno = 0; r = strtof(value, &endp); if (*endp != '\0') goto error; if (unlikely(errno == ERANGE)) goto error; if (out) *out = r; return true; error: l_util_debug(settings->debug_handler, settings->debug_data, "Could not interpret %s as a float", value); return false; } LIB_EXPORT bool l_settings_set_float(struct l_settings *settings, const char *group_name, const char *key, float in) { L_AUTO_FREE_VAR(char *, buf); buf = l_strdup_printf("%f", (double)in); return l_settings_set_value(settings, group_name, key, buf); } LIB_EXPORT uint8_t *l_settings_get_bytes(const struct l_settings *settings, const char *group_name, const char *key, size_t *out_len) { const char *value = l_settings_get_value(settings, group_name, key); if (!value) return NULL; if (value[0] == '\0') { *out_len = 0; /* Return something that can be l_freed but is not a NULL */ return l_memdup("", 1); } return l_util_from_hexstring(value, out_len); } LIB_EXPORT bool l_settings_set_bytes(struct l_settings *settings, const char *group_name, const char *key, const uint8_t *value, size_t value_len) { char *buf; if (unlikely(!settings || !value)) return false; if (value_len) buf = l_util_hexstring(value, value_len); else buf = l_strdup(""); return set_value(settings, group_name, key, buf); } LIB_EXPORT bool l_settings_remove_group(struct l_settings *settings, const char *group_name) { struct group_data *group; if (unlikely(!settings)) return false; group = l_queue_remove_if(settings->groups, group_match, group_name); if (!group) return false; group_destroy(group); return true; } LIB_EXPORT bool l_settings_remove_key(struct l_settings *settings, const char *group_name, const char *key) { struct group_data *group; struct setting_data *setting; if (unlikely(!settings)) return false; group = l_queue_find(settings->groups, group_match, group_name); if (!group) return false; setting = l_queue_remove_if(group->settings, key_match, key); if (!setting) return false; setting_destroy(setting); return true; } static void gather_embedded_groups(void *data, void *user_data) { struct embedded_group_data *group_data = data; struct gather_data *gather = user_data; gather->v[gather->cur++] = l_strdup(group_data->name); } LIB_EXPORT char **l_settings_get_embedded_groups(struct l_settings *settings) { char **ret; struct gather_data gather; if (unlikely(!settings)) return NULL; ret = l_new(char *, l_queue_length(settings->groups) + 1); gather.v = ret; gather.cur = 0; l_queue_foreach(settings->embedded_groups, gather_embedded_groups, &gather); return ret; } static bool embedded_group_match(const void *a, const void *b) { const struct embedded_group_data *group = a; const char *name = b; return !strcmp(group->name, name); } LIB_EXPORT bool l_settings_has_embedded_group(struct l_settings *settings, const char *group) { struct embedded_group_data *group_data; if (unlikely(!settings)) return false; group_data = l_queue_find(settings->embedded_groups, embedded_group_match, group); return group_data != NULL; } LIB_EXPORT const char *l_settings_get_embedded_value( struct l_settings *settings, const char *group_name, const char **out_type) { struct embedded_group_data *group; if (unlikely(!settings)) return NULL; group = l_queue_find(settings->embedded_groups, embedded_group_match, group_name); if (!group) return NULL; if (out_type) *out_type = group->type; return group->data; } LIB_EXPORT bool l_settings_remove_embedded_groups(struct l_settings *settings) { if (unlikely(!settings)) return false; l_queue_clear(settings->embedded_groups, embedded_group_destroy); return true; } bluez-5.87/ell/PaxHeaders/useful.h0000644000000000000000000000005014661631567014122 xustar0020 atime=1783076232 20 ctime=1783094099 bluez-5.87/ell/useful.h0000644000000000000000000000367314661631567013614 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2021 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define align_len(len, boundary) (((len)+(boundary)-1) & ~((boundary)-1)) #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #define SWAP(l, r) \ do { typeof(l) __tmp = (l); (l) = (r); (r) = __tmp; } while (0) #ifndef __always_inline #define __always_inline inline __attribute__((always_inline)) #endif static inline size_t minsize(size_t a, size_t b) { if (a <= b) return a; return b; } static inline size_t maxsize(size_t a, size_t b) { if (a >= b) return a; return b; } static inline void set_bit(void *addr, unsigned int bit) { unsigned char *field = addr; field[bit / 8] |= 1U << (bit % 8); } static inline int test_bit(const void *addr, unsigned int bit) { const unsigned char *field = addr; return (field[bit / 8] & (1U << (bit % 8))) != 0; } static inline unsigned char bit_field(const unsigned char oct, unsigned int start, unsigned int n_bits) { unsigned char mask = (1U << n_bits) - 1U; return (oct >> start) & mask; } /* Must be called with n >= 2 and n <= ULONG_MAX / 2 + 1 */ static inline unsigned long roundup_pow_of_two(unsigned long n) { return 1UL << (sizeof(unsigned long) * 8 - __builtin_clzl(n - 1)); } #define DIV_ROUND_CLOSEST(x, divisor) \ ({ \ typeof(divisor) _d = (divisor); \ typeof(x) _x = (x) + _d / 2; \ _x / _d; \ }) #ifndef _auto_ #define _auto_(func) \ __L_AUTODESTRUCT(func) #endif /* * Trick the compiler into thinking that var might be changed somehow by * the asm */ #define DO_NOT_OPTIMIZE(var) \ __asm__ ("" : "=r" (var) : "0" (var)); static inline int secure_select(int select_left, int l, int r) { int mask = -(!!select_left); return r ^ ((l ^ r) & mask); } #define struct_alloc(structname, ...) \ (struct structname *) l_memdup(&(struct structname) { __VA_ARGS__ }, \ sizeof(struct structname)) bluez-5.87/ell/PaxHeaders/hashmap.h0000644000000000000000000000005014504767710014234 xustar0020 atime=1783076231 20 ctime=1783094098 bluez-5.87/ell/hashmap.h0000644000000000000000000000405514504767710013721 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_HASHMAP_H #define __ELL_HASHMAP_H #include #ifdef __cplusplus extern "C" { #endif typedef void (*l_hashmap_foreach_func_t) (const void *key, void *value, void *user_data); typedef void (*l_hashmap_destroy_func_t) (void *value); typedef unsigned int (*l_hashmap_hash_func_t) (const void *p); typedef int (*l_hashmap_compare_func_t) (const void *a, const void *b); typedef void *(*l_hashmap_key_new_func_t) (const void *p); typedef void (*l_hashmap_key_free_func_t) (void *p); typedef bool (*l_hashmap_remove_func_t)(const void *key, void *value, void *user_data); struct l_hashmap; unsigned int l_str_hash(const void *p); struct l_hashmap *l_hashmap_new(void); struct l_hashmap *l_hashmap_string_new(void); bool l_hashmap_set_hash_function(struct l_hashmap *hashmap, l_hashmap_hash_func_t func); bool l_hashmap_set_compare_function(struct l_hashmap *hashmap, l_hashmap_compare_func_t func); bool l_hashmap_set_key_copy_function(struct l_hashmap *hashmap, l_hashmap_key_new_func_t func); bool l_hashmap_set_key_free_function(struct l_hashmap *hashmap, l_hashmap_key_free_func_t func); void l_hashmap_destroy(struct l_hashmap *hashmap, l_hashmap_destroy_func_t destroy); bool l_hashmap_insert(struct l_hashmap *hashmap, const void *key, void *value); bool l_hashmap_replace(struct l_hashmap *hashmap, const void *key, void *value, void **old_value); void *l_hashmap_remove(struct l_hashmap *hashmap, const void *key); void *l_hashmap_lookup(struct l_hashmap *hashmap, const void *key); void l_hashmap_foreach(struct l_hashmap *hashmap, l_hashmap_foreach_func_t function, void *user_data); unsigned int l_hashmap_foreach_remove(struct l_hashmap *hashmap, l_hashmap_remove_func_t function, void *user_data); unsigned int l_hashmap_size(struct l_hashmap *hashmap); bool l_hashmap_isempty(struct l_hashmap *hashmap); #ifdef __cplusplus } #endif #endif /* __ELL_HASHMAP_H */ bluez-5.87/ell/PaxHeaders/tls-record.c0000644000000000000000000000005014504767710014664 xustar0020 atime=1783076327 20 ctime=1783094099 bluez-5.87/ell/tls-record.c0000644000000000000000000004201314504767710014345 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2015 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include "private.h" #include "tls.h" #include "checksum.h" #include "cipher.h" #include "cert.h" #include "tls-private.h" #include "random.h" /* Implementation-specific max Record Layer fragment size (must be < 16kB) */ #define TX_RECORD_MAX_LEN 4096 /* TLSPlaintext + TLSCompressed + TLSCiphertext headers + seq_num sizes */ #define TX_RECORD_MAX_HEADERS (5 + 5 + 8 + 5) #define TX_RECORD_MAX_MAC 64 /* Head room and tail room for the buffer passed to the cipher */ #define TX_RECORD_HEADROOM TX_RECORD_MAX_HEADERS #define TX_RECORD_TAILROOM TX_RECORD_MAX_MAC static void tls_write_mac(struct l_tls *tls, uint8_t *compressed, uint16_t compressed_len, uint8_t *out_buf, bool txrx) { uint8_t *in_buf; /* Prepend the sequence number to the TLSCompressed buffer */ in_buf = compressed - 8; l_put_be64(tls->seq_num[txrx]++, in_buf); if (tls->mac[txrx]) { l_checksum_reset(tls->mac[txrx]); l_checksum_update(tls->mac[txrx], in_buf, compressed_len + 8); l_checksum_get_digest(tls->mac[txrx], out_buf, tls->mac_length[txrx]); } } static void tls_tx_record_plaintext(struct l_tls *tls, uint8_t *plaintext, uint16_t plaintext_len) { uint8_t *compressed; uint16_t compressed_len; uint8_t *cipher_input; uint16_t cipher_input_len; uint8_t *ciphertext; uint16_t ciphertext_len; uint8_t padding_length; uint8_t buf[TX_RECORD_HEADROOM + TX_RECORD_MAX_LEN + TX_RECORD_TAILROOM]; uint8_t iv[32]; uint8_t *assocdata; int offset; /* * TODO: if DEFLATE is selected in current state, use a new buffer * on stack to write a TLSCompressed structure there, otherwise use * the provided buffer. Since only null compression is supported * today we always use the provided buffer. */ compressed_len = plaintext_len - 5; compressed = plaintext; /* Build a TLSCompressed struct */ compressed[0] = plaintext[0]; /* Copy type and version fields */ compressed[1] = plaintext[1]; compressed[2] = plaintext[2]; compressed[3] = compressed_len >> 8; compressed[4] = compressed_len >> 0; switch (tls->cipher_type[1]) { case TLS_CIPHER_STREAM: /* Append the MAC after TLSCompressed.fragment, if needed */ tls_write_mac(tls, compressed, compressed_len + 5, compressed + compressed_len + 5, true); cipher_input = compressed + 5; cipher_input_len = compressed_len + tls->mac_length[1]; if (!tls->cipher[1]) { ciphertext = cipher_input; ciphertext_len = cipher_input_len; } else { ciphertext = buf + TX_RECORD_HEADROOM; ciphertext_len = cipher_input_len; l_cipher_encrypt(tls->cipher[1], cipher_input, ciphertext, cipher_input_len); } break; case TLS_CIPHER_BLOCK: /* Append the MAC after TLSCompressed.fragment, if needed */ cipher_input = compressed + 5; tls_write_mac(tls, compressed, compressed_len + 5, cipher_input + compressed_len, true); cipher_input_len = compressed_len + tls->mac_length[1]; /* Add minimum padding */ padding_length = (~cipher_input_len) & (tls->block_length[1] - 1); memset(cipher_input + cipher_input_len, padding_length, padding_length + 1); cipher_input_len += padding_length + 1; /* Generate an IV */ ciphertext = buf + TX_RECORD_HEADROOM; offset = 0; if (tls->negotiated_version >= L_TLS_V12) { l_getrandom(ciphertext, tls->record_iv_length[1]); l_cipher_set_iv(tls->cipher[1], ciphertext, tls->record_iv_length[1]); offset = tls->record_iv_length[1]; } else if (tls->negotiated_version >= L_TLS_V11) { l_getrandom(iv, tls->record_iv_length[1]); l_cipher_encrypt(tls->cipher[1], iv, ciphertext, tls->record_iv_length[1]); offset = tls->record_iv_length[1]; } l_cipher_encrypt(tls->cipher[1], cipher_input, ciphertext + offset, cipher_input_len); ciphertext_len = offset + cipher_input_len; break; case TLS_CIPHER_AEAD: /* Prepend seq_num to TLSCompressed.type + .version + .length */ assocdata = compressed - 8; l_put_be64(tls->seq_num[1]++, assocdata); cipher_input = compressed + 5; cipher_input_len = compressed_len; /* * Build the IV. The explicit part generation method is * actually cipher suite-specific but our only AEAD cipher * suites only require this part to be unique for each * record. For future suites there may need to be a callback * that generates the per-record IV or an enum for the suite * to select one of a few IV types. * * Note kernel's rfc4106(gcm(...)) algorithm could potentially * be used to build the IV. */ memcpy(iv, tls->fixed_iv[1], tls->fixed_iv_length[1]); l_put_le64(tls->seq_num[1], iv + tls->fixed_iv_length[1]); if (tls->record_iv_length[1] > 8) memset(iv + tls->fixed_iv_length[1] + 8, 42, tls->record_iv_length[1] - 8); /* Build the GenericAEADCipher struct */ ciphertext = buf + TX_RECORD_HEADROOM; memcpy(ciphertext, iv + tls->fixed_iv_length[1], tls->record_iv_length[1]); l_aead_cipher_encrypt(tls->aead_cipher[1], cipher_input, cipher_input_len, assocdata, 13, iv, tls->fixed_iv_length[1] + tls->record_iv_length[1], ciphertext + tls->record_iv_length[1], cipher_input_len + tls->auth_tag_length[1]); ciphertext_len = tls->record_iv_length[1] + cipher_input_len + tls->auth_tag_length[1]; break; default: return; } /* Build a TLSCiphertext struct */ ciphertext -= 5; ciphertext[0] = plaintext[0]; /* Copy type and version fields */ ciphertext[1] = plaintext[1]; ciphertext[2] = plaintext[2]; ciphertext[3] = ciphertext_len >> 8; ciphertext[4] = ciphertext_len >> 0; tls->tx(ciphertext, ciphertext_len + 5, tls->user_data); } void tls_tx_record(struct l_tls *tls, enum tls_content_type type, const uint8_t *data, size_t len) { uint8_t buf[TX_RECORD_HEADROOM + TX_RECORD_MAX_LEN + TX_RECORD_TAILROOM]; uint8_t *fragment, *plaintext; uint16_t fragment_len; uint16_t version = tls->negotiated_version ?: tls->min_version; if (type == TLS_CT_ALERT) tls->record_flush = true; while (len) { fragment = buf + TX_RECORD_HEADROOM; fragment_len = len < TX_RECORD_MAX_LEN ? len : TX_RECORD_MAX_LEN; /* Build a TLSPlaintext struct */ plaintext = fragment - 5; plaintext[0] = type; plaintext[1] = (uint8_t) (version >> 8); plaintext[2] = (uint8_t) (version >> 0); plaintext[3] = fragment_len >> 8; plaintext[4] = fragment_len >> 0; memcpy(plaintext + 5, data, fragment_len); tls_tx_record_plaintext(tls, plaintext, fragment_len + 5); data += fragment_len; len -= fragment_len; } } static bool tls_handle_plaintext(struct l_tls *tls, const uint8_t *plaintext, int len, uint8_t type, uint16_t version) { if (len > (1 << 14)) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "Plaintext message too long: %i", len); return false; } switch (type) { case TLS_CT_CHANGE_CIPHER_SPEC: case TLS_CT_APPLICATION_DATA: return tls_handle_message(tls, plaintext, len, type, version); /* * We need to perform input reassembly twice at different levels: * once to make sure we're handling complete TLSCiphertext messages, * in l_tls_handle_rx(), and again here so that the Alert and * Handshake message type handlers deal with complete messages. * This does not affect ChangeCipherSpec messages because they're * just a single byte and there are never more than one such message * in a row. Similarly it doesn't affect application data because * the application is not guaranteed that message boundaries are * preserved in any way and we don't know its message lengths anyway. * It does affect Alert because these messages are 2 byte long and * could potentially be split over two TLSPlaintext messages but * there are never more than one Alert in a TLSPlaintext for the same * reason as with ChangeCipherSpec. Handshake messages are the * most affected although the need to do the reassembly twice still * seems wasteful considering most of these messages are sent in * plaintext and TLSCiphertext maps to TLSPlaintext records. */ case TLS_CT_ALERT: case TLS_CT_HANDSHAKE: break; default: TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "Unknown content type %i", type); return false; } if (tls->message_buf_len && type != tls->message_content_type) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "Message fragment type %i doesn't match " "previous type %i", type, tls->message_content_type); return false; } tls->message_content_type = type; while (1) { int header_len, need_len; int chunk_len; /* Do we have a full header in tls->message_buf? */ header_len = (type == TLS_CT_ALERT) ? 2 : 4; need_len = header_len; if (tls->message_buf_len >= header_len) { if (type == TLS_CT_HANDSHAKE) { uint32_t hs_len = (tls->message_buf[1] << 16) | (tls->message_buf[2] << 8) | (tls->message_buf[3] << 0); if (hs_len > (1 << 14)) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "Handshake message " "too long: %i", (int) hs_len); return false; } need_len += hs_len; } /* Do we have a full structure? */ if (tls->message_buf_len == need_len) { if (!tls_handle_message(tls, tls->message_buf, need_len, type, version)) return false; tls->message_buf_len = 0; if (tls->record_flush) break; continue; } if (!len) break; } /* Try to fill up tls->message_buf up to need_len */ if (tls->message_buf_max_len < need_len) { tls->message_buf_max_len = need_len; tls->message_buf = l_realloc(tls->message_buf, need_len); } need_len -= tls->message_buf_len; chunk_len = need_len; if (len < chunk_len) chunk_len = len; memcpy(tls->message_buf + tls->message_buf_len, plaintext, chunk_len); tls->message_buf_len += chunk_len; plaintext += chunk_len; len -= chunk_len; if (chunk_len < need_len) break; } return true; } static bool tls_handle_ciphertext(struct l_tls *tls) { uint8_t type; uint16_t version; uint16_t fragment_len; uint8_t mac_buf[TX_RECORD_MAX_MAC], i, padding_len; int cipher_output_len, error; uint8_t *compressed; int compressed_len; uint8_t iv[32]; uint8_t *assocdata; type = tls->record_buf[0]; version = l_get_be16(tls->record_buf + 1); fragment_len = l_get_be16(tls->record_buf + 3); if (fragment_len > (1 << 14) + 2048) { TLS_DISCONNECT(TLS_ALERT_RECORD_OVERFLOW, 0, "Record fragment too long: %u", fragment_len); return false; } if ((tls->negotiated_version && tls->negotiated_version != version) || (!tls->negotiated_version && tls->record_buf[1] != 0x03 /* Appending E.1 */)) { TLS_DISCONNECT(TLS_ALERT_PROTOCOL_VERSION, 0, "Record version mismatch: %02x", version); return false; } if (fragment_len < tls->mac_length[0]) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "Record fragment too short: %u", fragment_len); return false; } compressed = alloca(8 + 5 + fragment_len); /* Copy the type and version fields */ compressed[8] = type; l_put_be16(version, compressed + 9); switch (tls->cipher_type[0]) { case TLS_CIPHER_STREAM: cipher_output_len = fragment_len; compressed_len = cipher_output_len - tls->mac_length[0]; l_put_be16(compressed_len, compressed + 11); if (!tls->cipher[0]) memcpy(compressed + 13, tls->record_buf + 5, cipher_output_len); else if (!l_cipher_decrypt(tls->cipher[0], tls->record_buf + 5, compressed + 13, cipher_output_len)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Decrypting record fragment failed"); return false; } /* Calculate the MAC if needed */ tls_write_mac(tls, compressed + 8, 5 + compressed_len, mac_buf, false); if (memcmp(mac_buf, compressed + 13 + compressed_len, tls->mac_length[0])) { TLS_DISCONNECT(TLS_ALERT_BAD_RECORD_MAC, 0, "Record fragment MAC mismatch"); return false; } compressed += 13; break; case TLS_CIPHER_BLOCK: i = 0; if (tls->negotiated_version >= L_TLS_V11) i = tls->record_iv_length[0]; if (fragment_len <= tls->mac_length[0] + i) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "Record fragment too short: %u", fragment_len); return false; } cipher_output_len = fragment_len - i; if (cipher_output_len % tls->block_length[0] != 0) { /* * In strict TLS 1.0 TLS_ALERT_DECRYPT_FAIL_RESERVED * should be returned here but that was declared * unsafe in the TLS 1.1 spec. */ TLS_DISCONNECT(TLS_ALERT_BAD_RECORD_MAC, 0, "Fragment data len %i not a multiple " "of block length %zi", cipher_output_len, tls->block_length[0]); return false; } if (tls->negotiated_version >= L_TLS_V12) { if (!l_cipher_set_iv(tls->cipher[0], tls->record_buf + 5, tls->record_iv_length[0])) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Setting fragment IV failed"); return false; } } else if (tls->negotiated_version >= L_TLS_V11) if (!l_cipher_decrypt(tls->cipher[0], tls->record_buf + 5, iv, tls->record_iv_length[0])) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Setting fragment IV failed"); return false; } if (!l_cipher_decrypt(tls->cipher[0], tls->record_buf + 5 + i, compressed + 13, cipher_output_len)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Fragment decryption failed"); return false; } /* * RFC 5246, page 24: * In order to defend against this attack, implementations * MUST ensure that record processing time is essentially the * same whether or not the padding is correct. In general, * the best way to do this is to compute the MAC even if the * padding is incorrect, and only then reject the packet. For * instance, if the pad appears to be incorrect, the * implementation might assume a zero-length pad and then * compute the MAC. */ padding_len = compressed[13 + cipher_output_len - 1]; error = 0; if (padding_len + tls->mac_length[0] + 1 > (size_t) cipher_output_len) { /* * In strict TLS 1.0 TLS_ALERT_DECRYPT_FAIL_RESERVED * should be returned here but that was declared * unsafe in the TLS 1.1 spec. */ padding_len = 0; error = 1; } compressed_len = cipher_output_len - 1 - padding_len - tls->mac_length[0]; l_put_be16(compressed_len, compressed + 11); error |= !l_secure_memeq(compressed + 13 + cipher_output_len - 1 - padding_len, padding_len, padding_len); /* Calculate the MAC if needed */ tls_write_mac(tls, compressed + 8, 5 + compressed_len, mac_buf, false); if ((tls->mac_length[0] && memcmp(mac_buf, compressed + 13 + compressed_len, tls->mac_length[0])) || error) { TLS_DISCONNECT(TLS_ALERT_BAD_RECORD_MAC, 0, "Record fragment MAC mismatch"); return false; } compressed += 13; break; case TLS_CIPHER_AEAD: if (fragment_len <= tls->record_iv_length[0] + tls->auth_tag_length[0]) { TLS_DISCONNECT(TLS_ALERT_DECODE_ERROR, 0, "Record fragment too short: %u", fragment_len); return false; } compressed_len = fragment_len - tls->record_iv_length[0] - tls->auth_tag_length[0]; l_put_be16(compressed_len, compressed + 11); /* Prepend seq_num to TLSCompressed.type + .version + .length */ assocdata = compressed; l_put_be64(tls->seq_num[0]++, assocdata); compressed += 13; /* Build the IV */ memcpy(iv, tls->fixed_iv[0], tls->fixed_iv_length[0]); memcpy(iv + tls->fixed_iv_length[0], tls->record_buf + 5, tls->record_iv_length[0]); if (!l_aead_cipher_decrypt(tls->aead_cipher[0], tls->record_buf + 5 + tls->record_iv_length[0], fragment_len - tls->record_iv_length[0], assocdata, 13, iv, tls->fixed_iv_length[0] + tls->record_iv_length[0], compressed, compressed_len)) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "Decrypting record fragment failed"); return false; } break; default: return false; } /* DEFLATE not supported so just pass on compressed / compressed_len */ return tls_handle_plaintext(tls, compressed, compressed_len, type, version); } LIB_EXPORT void l_tls_handle_rx(struct l_tls *tls, const uint8_t *data, size_t len) { int need_len; int chunk_len; tls->record_flush = false; /* Reassemble TLSCiphertext structures from the received chunks */ while (1) { /* Do we have a full header in tls->record_buf? */ if (tls->record_buf_len >= 5) { need_len = 5 + l_get_be16(tls->record_buf + 3); /* Do we have a full structure? */ if (tls->record_buf_len == need_len) { if (!tls_handle_ciphertext(tls)) return; tls->record_buf_len = 0; need_len = 5; if (tls->record_flush) break; } if (!len) break; } else need_len = 5; /* Try to fill up tls->record_buf up to need_len */ if (tls->record_buf_max_len < need_len) { tls->record_buf_max_len = need_len; tls->record_buf = l_realloc(tls->record_buf, need_len); } need_len -= tls->record_buf_len; chunk_len = need_len; if (len < (size_t) chunk_len) chunk_len = len; memcpy(tls->record_buf + tls->record_buf_len, data, chunk_len); tls->record_buf_len += chunk_len; data += chunk_len; len -= chunk_len; if (chunk_len < need_len) break; } } bluez-5.87/ell/PaxHeaders/signal.h0000644000000000000000000000005014504767710014070 xustar0020 atime=1783076231 20 ctime=1783094098 bluez-5.87/ell/signal.h0000644000000000000000000000113714504767710013553 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_SIGNAL_H #define __ELL_SIGNAL_H #include #ifdef __cplusplus extern "C" { #endif struct l_signal; typedef void (*l_signal_notify_cb_t) (void *user_data); typedef void (*l_signal_destroy_cb_t) (void *user_data); struct l_signal *l_signal_create(uint32_t signo, l_signal_notify_cb_t callback, void *user_data, l_signal_destroy_cb_t destroy); void l_signal_remove(struct l_signal *signal); #ifdef __cplusplus } #endif #endif /* __ELL_SIGNAL_H */ bluez-5.87/ell/PaxHeaders/random.h0000644000000000000000000000005014560467756014104 xustar0020 atime=1783076231 20 ctime=1783094098 bluez-5.87/ell/random.h0000644000000000000000000000067514560467756013575 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2015 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __ELL_RANDOM_H #define __ELL_RANDOM_H #include #include #include #ifdef __cplusplus extern "C" { #endif bool l_getrandom(void *buf, size_t len); bool l_getrandom_is_supported(void); uint32_t l_getrandom_uint32(void); #ifdef __cplusplus } #endif #endif /* __ELL_RANDOM_H */ bluez-5.87/ell/PaxHeaders/asn1-private.h0000644000000000000000000000005014504767710015125 xustar0020 atime=1783076232 20 ctime=1783094099 bluez-5.87/ell/asn1-private.h0000644000000000000000000001046614504767710014615 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2017 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define ASN1_ID(class, pc, tag) (((class) << 6) | ((pc) << 5) | (tag)) #define ASN1_CLASS_UNIVERSAL 0 #define ASN1_CLASS_CONTEXT 2 #define ASN1_ID_SEQUENCE ASN1_ID(ASN1_CLASS_UNIVERSAL, 1, 0x10) #define ASN1_ID_SET ASN1_ID(ASN1_CLASS_UNIVERSAL, 1, 0x11) #define ASN1_ID_BOOLEAN ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x01) #define ASN1_ID_INTEGER ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x02) #define ASN1_ID_BIT_STRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x03) #define ASN1_ID_OCTET_STRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x04) #define ASN1_ID_NULL ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x05) #define ASN1_ID_OID ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x06) #define ASN1_ID_UTF8STRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x0c) #define ASN1_ID_PRINTABLESTRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x13) #define ASN1_ID_IA5STRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x16) #define ASN1_ID_UTCTIME ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x17) #define ASN1_ID_GENERALIZEDTIME ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x18) struct asn1_oid { uint8_t asn1_len; uint8_t asn1[11]; }; #define asn1_oid_eq(oid1, oid2_len, oid2_string) \ ((oid1)->asn1_len == (oid2_len) && \ !memcmp((oid1)->asn1, (oid2_string), (oid2_len))) #if __STDC_VERSION__ <= 199409L #define inline __inline__ #endif static inline int asn1_parse_definite_length(const uint8_t **buf, size_t *len) { int n; size_t result = 0; /* Decrease the buffer length left */ if ((*len)-- < 1) return -1; /* * If short form length, move the pointer to start of data and * return the data length. */ if (!(**buf & 0x80)) return *(*buf)++; n = *(*buf)++ & 0x7f; if ((size_t) n > *len) return -1; *len -= n; while (n--) result = (result << 8) | *(*buf)++; return result; } static inline void asn1_write_definite_length(uint8_t **buf, size_t len) { int n; if (len < 0x80) { *(*buf)++ = len; return; } for (n = 1; len >> (n * 8); n++); *(*buf)++ = 0x80 | n; while (n--) *(*buf)++ = len >> (n * 8); } #define ASN1_CONTEXT_IMPLICIT(tag) (0x1000 | (tag)) #define ASN1_CONTEXT_EXPLICIT(tag) (0x2000 | (tag)) /* * Return the tag, length and value of the @index'th * non-context-specific-tagged element in a DER SEQUENCE or one who's * ASN1_CONTEXT_IMPLICIT(tag) matches @index or the inner element of * the one who's ASN1_CONTEXT_EXPLICIT(tag) matches @index. */ static inline const uint8_t *asn1_der_find_elem(const uint8_t *buf, size_t len_in, int index, uint8_t *tag, size_t *len_out) { int n = 0; while (1) { int tlv_len; if (len_in < 2) return NULL; *tag = *buf++; len_in--; tlv_len = asn1_parse_definite_length((void *) &buf, &len_in); if (tlv_len < 0 || (size_t) tlv_len > len_in) return NULL; if (*tag >> 6 != ASN1_CLASS_CONTEXT) { if (n++ == index) { *len_out = tlv_len; return buf; } } else if ((*tag & 0x1f) == (index & 0xfff)) { /* Context-specific tag */ if (index & 0x1000) { /* Implicit */ *len_out = tlv_len; return buf; } else if (index & 0x2000) { /* Explicit */ const uint8_t *outer = buf; int inner_len; if (!(*tag & 0x20)) /* Primitive */ return NULL; if (tlv_len < 2) return NULL; *tag = *buf++; inner_len = asn1_parse_definite_length( (void *) &buf, &len_in); if (outer + tlv_len != buf + inner_len) return NULL; *len_out = inner_len; return buf; } } buf += tlv_len; len_in -= tlv_len; } } /* Return an element in a DER SEQUENCE structure by path */ static inline const uint8_t *asn1_der_find_elem_by_path(const uint8_t *buf, size_t len_in, uint8_t tag, size_t *len_out, ...) { int index; va_list vl; va_start(vl, len_out); index = va_arg(vl, int); while (index != -1) { uint8_t elem_tag; uint8_t expect_tag; int prev_index = index; buf = asn1_der_find_elem(buf, len_in, index, &elem_tag, &len_in); if (!buf) { va_end(vl); return NULL; } index = va_arg(vl, int); if (prev_index & 0x1000) expect_tag = ASN1_ID(ASN1_CLASS_CONTEXT, index != -1 ? 1 : ((elem_tag >> 5) & 1), prev_index & 0xfff); else expect_tag = (index == -1) ? tag : ASN1_ID_SEQUENCE; if (elem_tag != expect_tag) { va_end(vl); return NULL; } } va_end(vl); *len_out = len_in; return buf; } bluez-5.87/ell/PaxHeaders/hashmap.c0000644000000000000000000000005014560467756014240 xustar0020 atime=1783076251 20 ctime=1783094099 bluez-5.87/ell/hashmap.c0000644000000000000000000003521214560467756013724 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #include "hashmap.h" #include "private.h" #include "useful.h" /** * SECTION:hashmap * @short_description: Hash table support * * Hash table support */ #define NBUCKETS 127 struct entry { void *key; void *value; struct entry *next; unsigned int hash; }; /** * l_hashmap: * * Opaque object representing the hash table. */ struct l_hashmap { l_hashmap_hash_func_t hash_func; l_hashmap_compare_func_t compare_func; l_hashmap_key_new_func_t key_new_func; l_hashmap_key_free_func_t key_free_func; unsigned int entries; struct entry buckets[NBUCKETS]; }; static inline void *get_key_new(const struct l_hashmap *hashmap, const void *key) { if (hashmap->key_new_func) return hashmap->key_new_func(key); return (void *)key; } static inline void free_key(const struct l_hashmap *hashmap, void *key) { if (hashmap->key_free_func) hashmap->key_free_func(key); } static inline unsigned int hash_superfast(const uint8_t *key, unsigned int len) { /* * Paul Hsieh (http://www.azillionmonkeys.com/qed/hash.html) * used by WebCore (http://webkit.org/blog/8/hashtables-part-2/), * EFL's eina, kmod and possible others. */ unsigned int tmp, hash = len, rem = len & 3; len /= 4; /* Main loop */ for (; len > 0; len--) { hash += l_get_u16(key); tmp = (l_get_u16(key + 2) << 11) ^ hash; hash = (hash << 16) ^ tmp; key += 4; hash += hash >> 11; } /* Handle end cases */ switch (rem) { case 3: hash += l_get_u16(key); hash ^= hash << 16; hash ^= key[2] << 18; hash += hash >> 11; break; case 2: hash += l_get_u16(key); hash ^= hash << 11; hash += hash >> 17; break; case 1: hash += *key; hash ^= hash << 10; hash += hash >> 1; break; } /* Force "avalanching" of final 127 bits */ hash ^= hash << 3; hash += hash >> 5; hash ^= hash << 4; hash += hash >> 17; hash ^= hash << 25; hash += hash >> 6; return hash; } static unsigned int direct_hash_func(const void *p) { return L_PTR_TO_UINT(p); } static int direct_compare_func(const void *a, const void *b) { return a < b ? -1 : (a > b ? 1 : 0); } /** * l_hashmap_new: * * Create a new hash table. The keys are managed as pointers, that is, * the pointer value is hashed and looked up. * * No error handling is needed since. In case of real memory allocation * problems abort() will be called. * * See also l_hashmap_string_new(). * * Returns: a newly allocated #l_hashmap object **/ LIB_EXPORT struct l_hashmap *l_hashmap_new(void) { struct l_hashmap *hashmap; hashmap = l_new(struct l_hashmap, 1); hashmap->hash_func = direct_hash_func; hashmap->compare_func = direct_compare_func; hashmap->entries = 0; return hashmap; } LIB_EXPORT unsigned int l_str_hash(const void *p) { const char *s = p; size_t len = strlen(s); return hash_superfast((const uint8_t *)s, len); } /** * l_hashmap_string_new: * * Create a new hash table. The keys are considered strings and are * copied. * * No error handling is needed since. In case of real memory allocation * problems abort() will be called. * * See also l_hashmap_new(). * * Returns: a newly allocated #l_hashmap object **/ LIB_EXPORT struct l_hashmap *l_hashmap_string_new(void) { struct l_hashmap *hashmap; hashmap = l_new(struct l_hashmap, 1); hashmap->hash_func = l_str_hash; hashmap->compare_func = (l_hashmap_compare_func_t) strcmp; hashmap->key_new_func = (l_hashmap_key_new_func_t) l_strdup; hashmap->key_free_func = l_free; hashmap->entries = 0; return hashmap; } /** * l_hashmap_set_hash_function: * @hashmap: hash table object * @func: Key hashing function * * Sets the hashing function to be used by this object. * * This function can only be called when the @hashmap is empty. * * Returns: #true when the hashing function could be updated successfully, * and #false otherwise. **/ LIB_EXPORT bool l_hashmap_set_hash_function(struct l_hashmap *hashmap, l_hashmap_hash_func_t func) { if (unlikely(!hashmap)) return false; if (hashmap->entries != 0) return false; hashmap->hash_func = func; return true; } /** * l_hashmap_set_compare_function: * @hashmap: hash table object * @func: Key compare function * * Sets the key comparison function to be used by this object. * * This function can only be called when the @hashmap is empty. * * Returns: #true when the comparison function could be updated successfully, * and #false otherwise. **/ LIB_EXPORT bool l_hashmap_set_compare_function(struct l_hashmap *hashmap, l_hashmap_compare_func_t func) { if (unlikely(!hashmap)) return false; if (hashmap->entries != 0) return false; hashmap->compare_func = func; return true; } /** * l_hashmap_set_key_copy_function: * @hashmap: hash table object * @func: Key duplication function * * Sets the key duplication function to be used by this object. If the * function is NULL, then the keys are assigned directly. * * This function can only be called when the @hashmap is empty. * * Returns: #true when the key copy function could be updated successfully, * and #false otherwise. **/ LIB_EXPORT bool l_hashmap_set_key_copy_function(struct l_hashmap *hashmap, l_hashmap_key_new_func_t func) { if (unlikely(!hashmap)) return false; if (hashmap->entries != 0) return false; hashmap->key_new_func = func; return true; } /** * l_hashmap_set_key_free_function: * @hashmap: hash table object * @func: Key destructor function * * Sets the key destructor function to be used by this object. This function * should undo the result of the function specified in * l_hashmap_set_key_copy_function(). This function can be NULL, in which * case no destructor is called. * * This function can only be called when the @hashmap is empty. * * Returns: #true when the key free function could be updated successfully, * and #false otherwise. **/ LIB_EXPORT bool l_hashmap_set_key_free_function(struct l_hashmap *hashmap, l_hashmap_key_free_func_t func) { if (unlikely(!hashmap)) return false; if (hashmap->entries != 0) return false; hashmap->key_free_func = func; return true; } /** * l_hashmap_destroy: * @hashmap: hash table object * @destroy: destroy function * * Free hash table and call @destroy on all remaining entries. * * NOTE: While the destroy is in progress, the hashmap is assumed to be * invariant. The behavior of adding or removing entries while a destroy * operation is in progress is undefined. **/ LIB_EXPORT void l_hashmap_destroy(struct l_hashmap *hashmap, l_hashmap_destroy_func_t destroy) { unsigned int i; if (unlikely(!hashmap)) return; for (i = 0; i < NBUCKETS; i++) { struct entry *entry, *next, *head = &hashmap->buckets[i]; if (!head->next) continue; for (entry = head;; entry = next) { if (destroy) destroy(entry->value); free_key(hashmap, entry->key); next = entry->next; if (entry != head) l_free(entry); if (next == head) break; } } l_free(hashmap); } /** * l_hashmap_insert: * @hashmap: hash table object * @key: key pointer * @value: value pointer * * Insert new @value entry with @key. Note that entries with a duplicate key * are allowed. If a duplicate entry in inserted, it will be added in order * of insertion. @l_hashmap_lookup and @l_hashmap_remove will remove the * first matching entry. * * Returns: #true when value has been added and #false in case of failure **/ LIB_EXPORT bool l_hashmap_insert(struct l_hashmap *hashmap, const void *key, void *value) { struct entry *entry, *head; unsigned int hash; void *key_new; if (unlikely(!hashmap)) return false; key_new = get_key_new(hashmap, key); hash = hashmap->hash_func(key_new); head = &hashmap->buckets[hash % NBUCKETS]; if (!head->next) { head->key = key_new; head->value = value; head->hash = hash; head->next = head; goto done; } entry = l_new(struct entry, 1); entry->key = key_new; entry->value = value; entry->hash = hash; entry->next = head; while (head->next != entry->next) head = head->next; head->next = entry; done: hashmap->entries++; return true; } /** * l_hashmap_replace: * @hashmap: hash table object * @key: key pointer * @value: value pointer * @old_value: old value that has been replaced. * * Replace the first entry with @key by @value or insert a new @value entry * with @key. If the entry was replaced, then the old value is returned * in @old_value, otherwise @old_value is assigned a #NULL. * * Returns: #true when value has been added and #false in case of failure **/ LIB_EXPORT bool l_hashmap_replace(struct l_hashmap *hashmap, const void *key, void *value, void **old_value) { struct entry *entry; struct entry *head; unsigned int hash; void *key_new; if (unlikely(!hashmap)) return false; key_new = get_key_new(hashmap, key); hash = hashmap->hash_func(key_new); head = &hashmap->buckets[hash % NBUCKETS]; if (!head->next) { head->key = key_new; head->value = value; head->hash = hash; head->next = head; goto done; } for (entry = head;; entry = entry->next) { if (entry->hash != hash) goto next; if (hashmap->compare_func(key, entry->key)) goto next; if (old_value) *old_value = entry->value; entry->value = value; free_key(hashmap, key_new); return true; next: if (entry->next == head) break; } entry = l_new(struct entry, 1); entry->key = key_new; entry->value = value; entry->hash = hash; entry->next = head; while (head->next != entry->next) head = head->next; head->next = entry; done: if (old_value) *old_value = NULL; hashmap->entries++; return true; } /** * l_hashmap_remove: * @hashmap: hash table object * @key: key pointer * * Remove entry for @key. * * Returns: value pointer of the removed entry or #NULL in case of failure **/ LIB_EXPORT void *l_hashmap_remove(struct l_hashmap *hashmap, const void *key) { struct entry *entry, *head, *prev; unsigned int hash; if (unlikely(!hashmap)) return NULL; hash = hashmap->hash_func(key); head = &hashmap->buckets[hash % NBUCKETS]; if (!head->next) return NULL; for (entry = head, prev = NULL;; prev = entry, entry = entry->next) { void *value; if (entry->hash != hash) goto next; if (hashmap->compare_func(key, entry->key)) goto next; value = entry->value; if (entry == head) { if (entry->next == head) { free_key(hashmap, entry->key); head->key = NULL; head->value = NULL; head->hash = 0; head->next = NULL; } else { entry = entry->next; free_key(hashmap, head->key); head->key = entry->key; head->value = entry->value; head->hash = entry->hash; head->next = entry->next; l_free(entry); } } else { prev->next = entry->next; free_key(hashmap, entry->key); l_free(entry); } hashmap->entries--; return value; next: if (entry->next == head) break; } return NULL; } /** * l_hashmap_lookup: * @hashmap: hash table object * @key: key pointer * * Lookup entry for @key. * * Returns: value pointer for @key or #NULL in case of failure **/ LIB_EXPORT void *l_hashmap_lookup(struct l_hashmap *hashmap, const void *key) { struct entry *entry, *head; unsigned int hash; if (unlikely(!hashmap)) return NULL; hash = hashmap->hash_func(key); head = &hashmap->buckets[hash % NBUCKETS]; if (!head->next) return NULL; for (entry = head;; entry = entry->next) { if (entry->hash == hash && !hashmap->compare_func(key, entry->key)) return entry->value; if (entry->next == head) break; } return NULL; } /** * l_hashmap_foreach: * @hashmap: hash table object * @function: callback function * @user_data: user data given to callback function * * Call @function for every entry in @hashmap. * * NOTE: While the foreach is in progress, the hashmap is assumed to be * invariant. The behavior of adding or removing entries while a foreach * operation is in progress is undefined. **/ LIB_EXPORT void l_hashmap_foreach(struct l_hashmap *hashmap, l_hashmap_foreach_func_t function, void *user_data) { unsigned int i; if (unlikely(!hashmap || !function)) return; for (i = 0; i < NBUCKETS; i++) { struct entry *entry, *head = &hashmap->buckets[i]; if (!head->next) continue; for (entry = head;; entry = entry->next) { function(entry->key, entry->value, user_data); if (entry->next == head) break; } } } /** * l_hashmap_foreach_remove: * @hashmap: hash table object * @function: callback function * @user_data: user data given to callback function * * Call @function for every entry in @hashmap. If the @function returns * true, then the object will be removed from the hashmap. * * NOTE: While the foreach is in progress, the hashmap is assumed to be * invariant. The behavior of adding or removing entries while a foreach * operation is in progress is undefined. * * Returns: Number of entries removed. **/ LIB_EXPORT unsigned int l_hashmap_foreach_remove(struct l_hashmap *hashmap, l_hashmap_remove_func_t function, void *user_data) { unsigned int i; unsigned int nremoved = 0; if (unlikely(!hashmap || !function)) return 0; for (i = 0; i < NBUCKETS; i++) { struct entry *head = &hashmap->buckets[i]; struct entry *entry; struct entry *prev; bool remove; if (head->next == NULL) continue; entry = head; prev = NULL; while (true) { remove = function(entry->key, entry->value, user_data); if (!remove) goto next; nremoved += 1; hashmap->entries -= 1; if (entry == head) { if (entry->next == head) { free_key(hashmap, entry->key); head->key = NULL; head->value = NULL; head->hash = 0; head->next = NULL; break; } else { entry = entry->next; free_key(hashmap, head->key); head->key = entry->key; head->value = entry->value; head->hash = entry->hash; head->next = entry->next; l_free(entry); entry = head; continue; } } else { prev->next = entry->next; free_key(hashmap, entry->key); l_free(entry); entry = prev->next; if (entry == head) break; continue; } next: if (entry->next == head) break; prev = entry; entry = entry->next; } } return nremoved; } /** * l_hashmap_size: * @hashmap: hash table object * * Returns: entries in the hash table **/ LIB_EXPORT unsigned int l_hashmap_size(struct l_hashmap *hashmap) { if (unlikely(!hashmap)) return 0; return hashmap->entries; } /** * l_hashmap_isempty: * @hashmap: hash table object * * Returns: #true if hash table is empty and #false if not **/ LIB_EXPORT bool l_hashmap_isempty(struct l_hashmap *hashmap) { if (unlikely(!hashmap)) return true; return hashmap->entries == 0; } bluez-5.87/ell/PaxHeaders/dbus-filter.c0000644000000000000000000000005015040645364015022 xustar0020 atime=1783076305 20 ctime=1783094099 bluez-5.87/ell/dbus-filter.c0000644000000000000000000002221315040645364014503 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2016 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include "util.h" #include "queue.h" #include "hashmap.h" #include "string.h" #include "dbus.h" #include "dbus-private.h" #include "gvariant-private.h" #include "private.h" #include "useful.h" #define NODE_TYPE_CALLBACK L_DBUS_MATCH_NONE struct filter_node { enum l_dbus_match_type type; union { struct { char *value; struct filter_node *children; bool remote_rule; } match; struct { l_dbus_message_func_t func; void *user_data; } callback; }; unsigned int id; struct filter_node *next; }; struct _dbus_filter { struct l_dbus *dbus; struct filter_node *root; unsigned int signal_id; unsigned int last_id; const struct _dbus_filter_ops *driver; struct _dbus_name_cache *name_cache; }; static void filter_subtree_free(struct filter_node *node) { struct filter_node *child, *next; if (node->type == NODE_TYPE_CALLBACK) { l_free(node); return; } next = node->match.children; l_free(node->match.value); l_free(node); while (next) { child = next; next = child->next; filter_subtree_free(child); } } static void dbus_filter_destroy(void *data) { struct _dbus_filter *filter = data; if (filter->root) filter_subtree_free(filter->root); l_free(filter); } static void filter_dispatch_match_recurse(struct _dbus_filter *filter, struct filter_node *node, struct l_dbus_message *message) { const char *value = NULL; const char *alt_value = NULL; struct filter_node *child; switch ((int) node->type) { case NODE_TYPE_CALLBACK: node->callback.func(message, node->callback.user_data); return; case L_DBUS_MATCH_SENDER: value = l_dbus_message_get_sender(message); break; case L_DBUS_MATCH_TYPE: value = _dbus_message_get_type_as_string(message); break; case L_DBUS_MATCH_PATH: value = l_dbus_message_get_path(message); break; case L_DBUS_MATCH_INTERFACE: value = l_dbus_message_get_interface(message); break; case L_DBUS_MATCH_MEMBER: value = l_dbus_message_get_member(message); break; case L_DBUS_MATCH_ARG0...(L_DBUS_MATCH_ARG0 + 63): value = _dbus_message_get_nth_string_argument(message, node->type - L_DBUS_MATCH_ARG0); break; } if (!value) return; if (node->type == L_DBUS_MATCH_SENDER && filter->name_cache) alt_value = _dbus_name_cache_lookup(filter->name_cache, node->match.value); if (strcmp(value, node->match.value) && (!alt_value || strcmp(value, alt_value))) return; for (child = node->match.children; child; child = child->next) filter_dispatch_match_recurse(filter, child, message); } void _dbus_filter_dispatch(struct l_dbus_message *message, void *user_data) { struct _dbus_filter *filter = user_data; if (filter->root) filter_dispatch_match_recurse(filter, filter->root, message); } struct _dbus_filter *_dbus_filter_new(struct l_dbus *dbus, const struct _dbus_filter_ops *driver, struct _dbus_name_cache *name_cache) { struct _dbus_filter *filter; filter = l_new(struct _dbus_filter, 1); filter->dbus = dbus; filter->driver = driver; filter->name_cache = name_cache; if (!filter->driver->skip_register) filter->signal_id = l_dbus_register(dbus, _dbus_filter_dispatch, filter, dbus_filter_destroy); return filter; } void _dbus_filter_free(struct _dbus_filter *filter) { if (!filter) return; if (!filter->driver->skip_register) l_dbus_unregister(filter->dbus, filter->signal_id); else dbus_filter_destroy(filter); } static int condition_compare(const void *a, const void *b) { const struct _dbus_filter_condition *condition_a = a, *condition_b = b; return condition_a->type - condition_b->type; } static bool remove_recurse(struct _dbus_filter *filter, struct filter_node **node, unsigned int id) { struct filter_node *tmp; for (; *node; node = &(*node)->next) { if ((*node)->type == NODE_TYPE_CALLBACK && (*node)->id == id) break; if ((*node)->type != NODE_TYPE_CALLBACK && remove_recurse(filter, &(*node)->match.children, id)) break; } if (!*node) return false; if ((*node)->type == NODE_TYPE_CALLBACK || !(*node)->match.children) { tmp = *node; *node = tmp->next; if (tmp->match.remote_rule) filter->driver->remove_match(filter->dbus, tmp->id); if (tmp->type == L_DBUS_MATCH_SENDER && filter->name_cache && !_dbus_parse_unique_name(tmp->match.value, NULL)) _dbus_name_cache_remove(filter->name_cache, tmp->match.value); filter_subtree_free(tmp); } return true; } unsigned int _dbus_filter_add_rule(struct _dbus_filter *filter, const struct _dbus_filter_condition *rule, int rule_len, l_dbus_message_func_t signal_func, void *user_data) { struct filter_node **node_ptr = &filter->root; struct filter_node *node; struct filter_node *parent = filter->root; bool remote_rule = false; struct _dbus_filter_condition sorted[rule_len]; struct _dbus_filter_condition *unused; struct _dbus_filter_condition *condition; struct _dbus_filter_condition *end = sorted + rule_len; memcpy(sorted, rule, sizeof(sorted)); qsort(sorted, rule_len, sizeof(*condition), condition_compare); /* * Find or create a path in the tree with a node for each * condition in the rule, loop until all conditions have been * used. */ unused = sorted; while (unused < end) { /* * Find a child of the node that matches any unused * condition. Note there could be multiple matches, we're * happy with the first we can find. */ while (*node_ptr) { node = *node_ptr; for (condition = unused; condition < end; condition++) { if (condition->type > node->type) { condition = end; break; } if (condition->type < node->type || condition->type == L_DBUS_MATCH_NONE) continue; if (!strcmp(node->match.value, condition->value)) break; } if (condition < end) break; node_ptr = &node->next; } /* Add a node */ if (!*node_ptr) { condition = unused; node = l_new(struct filter_node, 1); node->type = condition->type; node->match.value = l_strdup(condition->value); *node_ptr = node; if (node->type == L_DBUS_MATCH_SENDER && filter->name_cache && !_dbus_parse_unique_name( node->match.value, NULL)) _dbus_name_cache_add(filter->name_cache, node->match.value); } _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") /* * Mark the condition used. We do this by setting * condition->type to an invalid value unless it is the * first condition left in which case we can push the * rule start. Another option is to always push the rule * start and memmove the still unused conditions by one * if necessary. */ condition->type = L_DBUS_MATCH_NONE; while (unused < end && unused[0].type == L_DBUS_MATCH_NONE) unused++; node_ptr = &node->match.children; parent = node; /* * Only have to call AddMatch if none of the parent nodes * have yet created an AddMatch rule on the server. */ remote_rule |= node->match.remote_rule; _Pragma("GCC diagnostic pop") } node = l_new(struct filter_node, 1); node->type = NODE_TYPE_CALLBACK; node->callback.func = signal_func; node->callback.user_data = user_data; node->id = ++filter->last_id; node->next = *node_ptr; *node_ptr = node; if (!remote_rule) { if (!filter->driver->add_match(filter->dbus, node->id, rule, rule_len)) goto err; _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") parent->id = node->id; parent->match.remote_rule = true; _Pragma("GCC diagnostic pop") } return node->id; err: /* Remove all the nodes we may have added */ node->id = (unsigned int) -1; remove_recurse(filter, &filter->root, node->id); return 0; } bool _dbus_filter_remove_rule(struct _dbus_filter *filter, unsigned int id) { return remove_recurse(filter, &filter->root, id); } char *_dbus_filter_rule_to_str(const struct _dbus_filter_condition *rule, int rule_len) { struct l_string *str = l_string_new(63); char *key, arg_buf[6]; const char *value, *endp; for (; rule_len; rule++, rule_len--) { switch ((int) rule->type) { case L_DBUS_MATCH_SENDER: key = "sender"; break; case L_DBUS_MATCH_TYPE: key = "type"; break; case L_DBUS_MATCH_PATH: key = "path"; break; case L_DBUS_MATCH_INTERFACE: key = "interface"; break; case L_DBUS_MATCH_MEMBER: key = "member"; break; case L_DBUS_MATCH_ARG0...(L_DBUS_MATCH_ARG0 + 63): key = arg_buf; snprintf(arg_buf, sizeof(arg_buf), "arg%i", rule->type - L_DBUS_MATCH_ARG0); break; default: l_string_free(str); return NULL; } l_string_append(str, key); l_string_append(str, "='"); /* We only need to escape single-quotes in the values */ value = rule->value; while ((endp = strchr(value, '\''))) { l_string_append_fixed(str, value, endp - value); l_string_append(str, "'\\''"); value = endp + 1; } l_string_append(str, value); l_string_append_c(str, '\''); if (rule_len > 1) l_string_append_c(str, ','); } return l_string_unwrap(str); } bluez-5.87/ell/PaxHeaders/cert-crypto.c0000644000000000000000000000005014770744372015065 xustar0020 atime=1783076280 20 ctime=1783094099 bluez-5.87/ell/cert-crypto.c0000644000000000000000000004547214770744372014562 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2020 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include "checksum.h" #include "cipher.h" #include "useful.h" #include "utf8.h" #include "asn1-private.h" #include "private.h" #include "missing.h" #include "cert.h" #include "cert-private.h" /* RFC8018 section 5.1 */ LIB_EXPORT bool l_cert_pkcs5_pbkdf1(enum l_checksum_type type, const char *password, const uint8_t *salt, size_t salt_len, unsigned int iter_count, uint8_t *out_dk, size_t dk_len) { size_t hash_len, t_len; uint8_t t[20 + salt_len + strlen(password)]; struct l_checksum *checksum; switch (type) { case L_CHECKSUM_NONE: case L_CHECKSUM_MD4: return false; case L_CHECKSUM_MD5: hash_len = 16; break; case L_CHECKSUM_SHA1: hash_len = 20; break; case L_CHECKSUM_SHA224: case L_CHECKSUM_SHA256: case L_CHECKSUM_SHA384: case L_CHECKSUM_SHA512: case L_CHECKSUM_SHA3_224: case L_CHECKSUM_SHA3_256: case L_CHECKSUM_SHA3_384: case L_CHECKSUM_SHA3_512: return false; default: return false; } if (dk_len > hash_len) return false; checksum = l_checksum_new(type); if (!checksum) return false; memcpy(t, password, strlen(password)); memcpy(t + strlen(password), salt, salt_len); t_len = strlen(password) + salt_len; while (iter_count) { l_checksum_reset(checksum); if (!l_checksum_update(checksum, t, t_len)) break; if (l_checksum_get_digest(checksum, t, hash_len) != (ssize_t) hash_len) break; t_len = hash_len; iter_count--; } l_checksum_free(checksum); if (!iter_count) memcpy(out_dk, t, dk_len); explicit_bzero(t, sizeof(t)); return !iter_count; } /* RFC8018 section 5.2 */ LIB_EXPORT bool l_cert_pkcs5_pbkdf2(enum l_checksum_type type, const char *password, const uint8_t *salt, size_t salt_len, unsigned int iter_count, uint8_t *out_dk, size_t dk_len) { size_t h_len; struct l_checksum *checksum; unsigned int i; switch (type) { case L_CHECKSUM_NONE: case L_CHECKSUM_MD4: case L_CHECKSUM_MD5: return false; case L_CHECKSUM_SHA1: h_len = 20; break; case L_CHECKSUM_SHA224: h_len = 28; break; case L_CHECKSUM_SHA256: h_len = 32; break; case L_CHECKSUM_SHA384: h_len = 48; break; case L_CHECKSUM_SHA512: h_len = 64; break; case L_CHECKSUM_SHA3_224: case L_CHECKSUM_SHA3_256: case L_CHECKSUM_SHA3_384: case L_CHECKSUM_SHA3_512: return false; default: return false; } checksum = l_checksum_new_hmac(type, password, strlen(password)); if (!checksum) return false; for (i = 1; dk_len; i++) { unsigned int j, k; uint8_t u[salt_len + 64]; size_t u_len; size_t block_len = h_len; if (block_len > dk_len) block_len = dk_len; memset(out_dk, 0, block_len); memcpy(u, salt, salt_len); l_put_be32(i, u + salt_len); u_len = salt_len + 4; for (j = 0; j < iter_count; j++) { l_checksum_reset(checksum); if (!l_checksum_update(checksum, u, u_len)) break; if (l_checksum_get_digest(checksum, u, h_len) != (ssize_t) h_len) break; u_len = h_len; for (k = 0; k < block_len; k++) out_dk[k] ^= u[k]; } if (j < iter_count) break; out_dk += block_len; dk_len -= block_len; } l_checksum_free(checksum); return !dk_len; } /* RFC7292 Appendix B */ uint8_t *cert_pkcs12_pbkdf(const char *password, const struct cert_pkcs12_hash *hash, const uint8_t *salt, size_t salt_len, unsigned int iterations, uint8_t id, size_t key_len) { /* All lengths in bytes instead of bits */ size_t passwd_len = password ? 2 * strlen(password) + 2 : 0; uint8_t *bmpstring; /* Documented as v(ceiling(s/v)), usually will just equal v */ unsigned int s_len = (salt_len + hash->v - 1) & ~(hash->v - 1); /* Documented as p(ceiling(s/p)), usually will just equal v */ unsigned int p_len = password ? (passwd_len + hash->v - 1) & ~(hash->v - 1) : 0; uint8_t di[hash->v + s_len + p_len]; uint8_t *ptr; unsigned int j; uint8_t *key; unsigned int bytes; struct l_checksum *h = l_checksum_new(hash->alg); if (!h) return NULL; /* * The BMPString encoding, in practice same as UCS-2, can end up * at 2 * strlen(password) + 2 bytes or shorter depending on the * characters used. Recalculate p_len after we know it. * Important: The password must be valid UTF-8 here. */ if (p_len) { if (!(bmpstring = l_utf8_to_ucs2be(password, &passwd_len))) { l_checksum_free(h); return NULL; } p_len = (passwd_len + hash->v - 1) & ~(hash->v - 1); } memset(di, id, hash->v); ptr = di + hash->v; for (j = salt_len; j < s_len; j += salt_len, ptr += salt_len) memcpy(ptr, salt, salt_len); if (s_len) { memcpy(ptr, salt, s_len + salt_len - j); ptr += s_len + salt_len - j; } _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") if (p_len) { for (j = passwd_len; j < p_len; j += passwd_len, ptr += passwd_len) memcpy(ptr, bmpstring, passwd_len); memcpy(ptr, bmpstring, p_len + passwd_len - j); explicit_bzero(bmpstring, passwd_len); l_free(bmpstring); } _Pragma("GCC diagnostic pop") key = l_malloc(key_len + hash->len); for (bytes = 0; bytes < key_len; bytes += hash->u) { uint8_t b[hash->v]; uint8_t *input = di; unsigned int input_len = hash->v + s_len + p_len; for (j = 0; j < iterations; j++) { if (!l_checksum_update(h, input, input_len) || l_checksum_get_digest(h, key + bytes, hash->len) <= 0) { l_checksum_free(h); l_free(key); return NULL; } input = key + bytes; input_len = hash->u; l_checksum_reset(h); } if (bytes + hash->u >= key_len) break; for (j = 0; j < hash->v - hash->u; j += hash->u) memcpy(b + j, input, hash->u); memcpy(b + j, input, hash->v - j); ptr = di + hash->v; for (j = 0; j < s_len + p_len; j += hash->v, ptr += hash->v) { unsigned int k; uint16_t carry = 1; /* * Not specified in the RFC7292 but implementations * sum these octet strings as big-endian integers. * We could use 64-bit additions here but the benefit * may not compensate the cost of the byteswapping. */ for (k = hash->v - 1; k > 0; k--) { carry = ptr[k] + b[k] + carry; ptr[k] = carry; carry >>= 8; } ptr[k] += b[k] + carry; explicit_bzero(&carry, sizeof(carry)); } explicit_bzero(b, sizeof(b)); } explicit_bzero(di, sizeof(di)); l_checksum_free(h); return key; } /* RFC7292 Appendix A */ static const struct cert_pkcs12_hash pkcs12_sha1_hash = { .alg = L_CHECKSUM_SHA1, .len = 20, .u = 20, .v = 64, .oid = { 5, { 0x2b, 0x0e, 0x03, 0x02, 0x1a } }, }; /* RFC8018 Section A.2 */ static struct asn1_oid pkcs5_pbkdf2_oid = { 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c } }; /* RFC8018 Section A.4 */ static struct asn1_oid pkcs5_pbes2_oid = { 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0d } }; /* RFC8018 Section A.3 */ static const struct pkcs5_pbes1_encryption_oid { enum l_checksum_type checksum_type; enum l_cipher_type cipher_type; struct asn1_oid oid; } pkcs5_pbes1_encryption_oids[] = { { /* pbeWithMD5AndDES-CBC */ L_CHECKSUM_MD5, L_CIPHER_DES_CBC, { 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x03 } }, }, { /* pbeWithSHA1AndDES-CBC */ L_CHECKSUM_SHA1, L_CIPHER_DES_CBC, { 9, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0a } }, }, /* MD2- and RC2-based schemes 1, 4, 6 and 11 not supported */ }; /* RFC7292 Appendix C */ static const struct pkcs12_encryption_oid { enum l_cipher_type cipher_type; unsigned int key_length; unsigned int iv_length; bool copy_k1; /* Expand the 2-Key 3DES key for 3-Key 3DES */ bool is_block; struct asn1_oid oid; } pkcs12_encryption_oids[] = { { /* pbeWithSHAAnd128BitRC4 */ .cipher_type = L_CIPHER_ARC4, .key_length = 16, .oid = { 10, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x01, 0x01, } } }, { /* pbeWithSHAAnd40BitRC4 */ .cipher_type = L_CIPHER_ARC4, .key_length = 5, .oid = { 10, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x01, 0x02, } } }, { /* pbeWithSHAAnd3-KeyTripleDES-CBC */ .cipher_type = L_CIPHER_DES3_EDE_CBC, .key_length = 24, .iv_length = 8, .is_block = true, .oid = { 10, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x01, 0x03, } } }, { /* pbeWithSHAAnd2-KeyTripleDES-CBC */ .cipher_type = L_CIPHER_DES3_EDE_CBC, .key_length = 16, .iv_length = 8, .copy_k1 = true, .is_block = true, .oid = { 10, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x01, 0x04, } } }, { /* pbeWithSHAAnd128BitRC2-CBC */ .cipher_type = L_CIPHER_RC2_CBC, .key_length = 16, .iv_length = 8, .is_block = true, .oid = { 10, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x01, 0x05, } } }, { /* pbeWithSHAAnd40BitRC2-CBC */ .cipher_type = L_CIPHER_RC2_CBC, .key_length = 5, .iv_length = 8, .is_block = true, .oid = { 10, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x01, 0x06, } } }, }; static const struct pkcs5_digest_alg_oid { enum l_checksum_type type; struct asn1_oid oid; } pkcs5_digest_alg_oids[] = { { /* hmacWithSHA1 */ L_CHECKSUM_SHA1, { 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x07 } }, }, { /* hmacWithSHA224 */ L_CHECKSUM_SHA224, { 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x08 } }, }, { /* hmacWithSHA256 */ L_CHECKSUM_SHA256, { 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09 } }, }, { /* hmacWithSHA384 */ L_CHECKSUM_SHA384, { 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0a } }, }, { /* hmacWithSHA512 */ L_CHECKSUM_SHA512, { 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x0b } }, }, /* hmacWithSHA512-224 and hmacWithSHA512-256 not supported */ }; static const struct pkcs5_enc_alg_oid { enum l_cipher_type cipher_type; uint8_t key_size, iv_size; struct asn1_oid oid; } pkcs5_enc_alg_oids[] = { { /* desCBC */ L_CIPHER_DES_CBC, 8, 8, { 5, { 0x2b, 0x0e, 0x03, 0x02, 0x07 } }, }, { /* des-EDE3-CBC */ L_CIPHER_DES3_EDE_CBC, 24, 8, { 8, { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x03, 0x07 } }, }, /* RC2/RC5-based schemes 2 and 9 not supported */ { /* aes128-CBC-PAD */ L_CIPHER_AES_CBC, 16, 16, { 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x02 } }, }, { /* aes192-CBC-PAD */ L_CIPHER_AES_CBC, 24, 16, { 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x16 } }, }, { /* aes256-CBC-PAD */ L_CIPHER_AES_CBC, 32, 16, { 9, { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2a } }, }, }; static struct l_cipher *cipher_from_pkcs5_pbes2_params( const uint8_t *pbes2_params, size_t pbes2_params_len, const char *password) { uint8_t tag; const uint8_t *kdf_sequence, *enc_sequence, *oid, *params, *salt, *iter_count_buf, *key_len_buf, *prf_sequence; size_t kdf_len, enc_len, params_len, salt_len, key_len, tmp_len; unsigned int i, iter_count, pos; enum l_checksum_type prf_alg = L_CHECKSUM_NONE; const struct pkcs5_enc_alg_oid *enc_scheme = NULL; uint8_t derived_key[64]; struct l_cipher *cipher; /* RFC8018 section A.4 */ kdf_sequence = asn1_der_find_elem(pbes2_params, pbes2_params_len, 0, &tag, &kdf_len); if (!kdf_sequence || tag != ASN1_ID_SEQUENCE) return NULL; enc_sequence = asn1_der_find_elem(pbes2_params, pbes2_params_len, 1, &tag, &enc_len); if (!enc_sequence || tag != ASN1_ID_SEQUENCE) return NULL; if (asn1_der_find_elem(pbes2_params, pbes2_params_len, 2, &tag, &tmp_len)) return NULL; /* RFC8018 section A.2 */ oid = asn1_der_find_elem(kdf_sequence, kdf_len, 0, &tag, &tmp_len); if (!oid || tag != ASN1_ID_OID) return NULL; if (!asn1_oid_eq(&pkcs5_pbkdf2_oid, tmp_len, oid)) return NULL; params = asn1_der_find_elem(kdf_sequence, kdf_len, 1, &tag, ¶ms_len); if (!params || tag != ASN1_ID_SEQUENCE) return NULL; if (asn1_der_find_elem(kdf_sequence, kdf_len, 2, &tag, &tmp_len)) return NULL; salt = asn1_der_find_elem(params, params_len, 0, &tag, &salt_len); if (!salt || tag != ASN1_ID_OCTET_STRING || salt_len < 1 || salt_len > 512) return NULL; iter_count_buf = asn1_der_find_elem(params, params_len, 1, &tag, &tmp_len); if (!iter_count_buf || tag != ASN1_ID_INTEGER || tmp_len < 1 || tmp_len > 4) return NULL; iter_count = 0; while (tmp_len--) iter_count = (iter_count << 8) | *iter_count_buf++; pos = 2; key_len_buf = asn1_der_find_elem(params, params_len, pos, &tag, &tmp_len); if (key_len_buf && tag == ASN1_ID_INTEGER) { if (tmp_len != 1) return NULL; pos++; key_len = 0; while (tmp_len--) key_len = (key_len << 8) | *key_len_buf++; } else key_len = 0; prf_sequence = asn1_der_find_elem(params, params_len, pos, &tag, &tmp_len); if (prf_sequence && tag == ASN1_ID_SEQUENCE) { pos++; oid = asn1_der_find_elem(prf_sequence, tmp_len, 0, &tag, &tmp_len); if (!oid || tag != ASN1_ID_OID) return NULL; for (i = 0; i < L_ARRAY_SIZE(pkcs5_digest_alg_oids); i++) if (asn1_oid_eq(&pkcs5_digest_alg_oids[i].oid, tmp_len, oid)) prf_alg = pkcs5_digest_alg_oids[i].type; if (prf_alg == L_CHECKSUM_NONE) return NULL; } else prf_alg = L_CHECKSUM_SHA1; oid = asn1_der_find_elem(enc_sequence, enc_len, 0, &tag, &tmp_len); if (!oid || tag != ASN1_ID_OID) return NULL; for (i = 0; i < L_ARRAY_SIZE(pkcs5_enc_alg_oids); i++) { if (asn1_oid_eq(&pkcs5_enc_alg_oids[i].oid, tmp_len, oid)) { enc_scheme = &pkcs5_enc_alg_oids[i]; break; } } if (!enc_scheme) return NULL; params = asn1_der_find_elem(enc_sequence, enc_len, 1, &tag, ¶ms_len); if (!params) return NULL; /* RFC8018 section B.2 */ /* * Since we don't support the RC2/RC5 PBES2 ciphers, our parameters * only have an obligatory OCTET STRING IV parameter and a fixed key * length. */ if (tag != ASN1_ID_OCTET_STRING || params_len != enc_scheme->iv_size) return NULL; if (key_len && enc_scheme->key_size != key_len) return NULL; key_len = enc_scheme->key_size; if (asn1_der_find_elem(enc_sequence, enc_len, 2, &tag, &tmp_len)) return NULL; /* RFC8018 section 6.2 */ if (!l_cert_pkcs5_pbkdf2(prf_alg, password, salt, salt_len, iter_count, derived_key, key_len)) return NULL; cipher = l_cipher_new(enc_scheme->cipher_type, derived_key, key_len); if (cipher && !l_cipher_set_iv(cipher, params, enc_scheme->iv_size)) { l_cipher_free(cipher); cipher = NULL; } explicit_bzero(derived_key, 16); return cipher; } static struct l_cipher *cipher_from_pkcs12_alg_id( const struct pkcs12_encryption_oid *scheme, const uint8_t *params, size_t params_len, const char *password, bool *out_is_block) { uint8_t tag; const uint8_t *salt; const uint8_t *iterations_data; size_t salt_len; size_t iterations_len; unsigned int iterations; uint8_t *key; size_t key_len; struct l_cipher *cipher; /* Same parameters as in PKCS#5 */ salt = asn1_der_find_elem(params, params_len, 0, &tag, &salt_len); if (!salt || tag != ASN1_ID_OCTET_STRING) return NULL; iterations_data = asn1_der_find_elem(params, params_len, 1, &tag, &iterations_len); if (!iterations_data || tag != ASN1_ID_INTEGER || iterations_len < 1 || iterations_len > 4) return NULL; for (iterations = 0; iterations_len; iterations_len--) iterations = (iterations << 8) | *iterations_data++; if (iterations < 1 || iterations > 8192) return NULL; if (iterations_data != params + params_len) return NULL; key_len = scheme->key_length; key = cert_pkcs12_pbkdf(password, &pkcs12_sha1_hash, salt, salt_len, iterations, 1, key_len); if (!key) return NULL; if (scheme->copy_k1) { /* * 2-Key 3DES is like L_CIPHER_DES3_EDE_CBC except the last * of the 3 8-byte keys is not generated using a KDF and * instead is a copy of the first key. In other words * the first half of the 16-byte key material is appended * at the end to produce the 24 bytes for DES3_EDE_CBC. */ uint8_t *key2 = l_malloc(24); memcpy(key2, key, 16); memcpy(key2 + 16, key, 8); explicit_bzero(key, key_len); l_free(key); key = key2; key_len = 24; } cipher = l_cipher_new(scheme->cipher_type, key, key_len); explicit_bzero(key, key_len); l_free(key); if (!cipher) return NULL; if (scheme->iv_length) { uint8_t *iv = cert_pkcs12_pbkdf(password, &pkcs12_sha1_hash, salt, salt_len, iterations, 2, scheme->iv_length); if (!iv || !l_cipher_set_iv(cipher, iv, scheme->iv_length)) { l_cipher_free(cipher); cipher = NULL; } if (iv) explicit_bzero(iv, scheme->iv_length); l_free(iv); } if (out_is_block) *out_is_block = scheme->is_block; return cipher; } struct l_cipher *cert_cipher_from_pkcs_alg_id(const uint8_t *id_asn1, size_t id_asn1_len, const char *password, bool *out_is_block) { uint8_t tag; const uint8_t *oid, *params, *salt, *iter_count_buf; size_t oid_len, params_len, tmp_len; unsigned int i, iter_count; const struct pkcs5_pbes1_encryption_oid *pbes1_scheme = NULL; uint8_t derived_key[16]; struct l_cipher *cipher; oid = asn1_der_find_elem(id_asn1, id_asn1_len, 0, &tag, &oid_len); if (!oid || tag != ASN1_ID_OID) return NULL; params = asn1_der_find_elem(id_asn1, id_asn1_len, 1, &tag, ¶ms_len); if (!params || tag != ASN1_ID_SEQUENCE) return NULL; if (asn1_der_find_elem(id_asn1, id_asn1_len, 2, &tag, &tmp_len)) return NULL; if (asn1_oid_eq(&pkcs5_pbes2_oid, oid_len, oid)) { if (out_is_block) *out_is_block = true; return cipher_from_pkcs5_pbes2_params(params, params_len, password); } /* RFC8018 section A.3 */ for (i = 0; i < L_ARRAY_SIZE(pkcs5_pbes1_encryption_oids); i++) { if (asn1_oid_eq(&pkcs5_pbes1_encryption_oids[i].oid, oid_len, oid)) { pbes1_scheme = &pkcs5_pbes1_encryption_oids[i]; break; } } /* Check if this is a PKCS#12 OID */ if (!pbes1_scheme) { for (i = 0; i < L_ARRAY_SIZE(pkcs12_encryption_oids); i++) if (asn1_oid_eq(&pkcs12_encryption_oids[i].oid, oid_len, oid)) return cipher_from_pkcs12_alg_id( &pkcs12_encryption_oids[i], params, params_len, password, out_is_block); return NULL; } salt = asn1_der_find_elem(params, params_len, 0, &tag, &tmp_len); if (!salt || tag != ASN1_ID_OCTET_STRING || tmp_len != 8) return NULL; iter_count_buf = asn1_der_find_elem(params, params_len, 1, &tag, &tmp_len); if (!iter_count_buf || tag != ASN1_ID_INTEGER || tmp_len < 1 || tmp_len > 4) return NULL; iter_count = 0; while (tmp_len--) iter_count = (iter_count << 8) | *iter_count_buf++; if (asn1_der_find_elem(params, params_len, 2, &tag, &tmp_len)) return NULL; /* RFC8018 section 6.1 */ if (!l_cert_pkcs5_pbkdf1(pbes1_scheme->checksum_type, password, salt, 8, iter_count, derived_key, 16)) return NULL; cipher = l_cipher_new(pbes1_scheme->cipher_type, derived_key + 0, 8); if (cipher && !l_cipher_set_iv(cipher, derived_key + 8, 8)) { l_cipher_free(cipher); cipher = NULL; } explicit_bzero(derived_key, 16); if (out_is_block) *out_is_block = true; return cipher; } bluez-5.87/ell/PaxHeaders/missing.h0000644000000000000000000000005014504767710014264 xustar0020 atime=1783076253 20 ctime=1783094099 bluez-5.87/ell/missing.h0000644000000000000000000000276214504767710013754 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #ifndef __NR_getrandom # if defined __x86_64__ # define __NR_getrandom 318 # elif defined(__i386__) # define __NR_getrandom 355 # elif defined(__arm__) # define __NR_getrandom 384 # elif defined(__aarch64__) # define __NR_getrandom 278 # elif defined(__ia64__) # define __NR_getrandom 1339 # elif defined(__m68k__) # define __NR_getrandom 352 # elif defined(__s390x__) # define __NR_getrandom 349 # elif defined(__powerpc__) # define __NR_getrandom 359 # elif defined _MIPS_SIM # if _MIPS_SIM == _MIPS_SIM_ABI32 # define __NR_getrandom 4353 # endif # if _MIPS_SIM == _MIPS_SIM_NABI32 # define __NR_getrandom 6317 # endif # if _MIPS_SIM == _MIPS_SIM_ABI64 # define __NR_getrandom 5313 # endif # else # warning "__NR_getrandom unknown for your architecture" # define __NR_getrandom 0xffffffff # endif #endif #ifndef HAVE_EXPLICIT_BZERO static inline void explicit_bzero(void *s, size_t n) { memset(s, 0, n); __asm__ __volatile__ ("" : : "r"(s) : "memory"); } #endif #ifndef SO_BINDTOIFINDEX #define SO_BINDTOIFINDEX 62 #endif #ifndef HAVE_RAWMEMCHR static inline void *rawmemchr(const void *s, int c) { _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wstringop-overflow=\"") return memchr(s, c, PTRDIFF_MAX); _Pragma("GCC diagnostic pop") } #endif bluez-5.87/ell/PaxHeaders/log.c0000644000000000000000000000005014504767710013367 xustar0020 atime=1783076249 20 ctime=1783094099 bluez-5.87/ell/log.c0000644000000000000000000002045414504767710013055 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2011-2014 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "queue.h" #include "log.h" #include "private.h" struct debug_section { struct l_debug_desc *start; struct l_debug_desc *end; }; struct l_queue *debug_sections; /** * SECTION:log * @short_description: Logging framework * * Logging framework */ /** * l_debug_desc: * * Debug descriptor. */ static void log_null(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap) { } static l_log_func_t log_func = log_null; static const char *log_ident = ""; static int log_fd = -1; static unsigned long log_pid; static inline void close_log(void) { if (log_fd > 0) { close(log_fd); log_fd = -1; } } static int open_log(const char *path) { struct sockaddr_un addr; log_fd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (log_fd < 0) return -1; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); if (connect(log_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { close_log(); return -1; } return 0; } /** * l_log_set_ident: * @ident: string identifier * * Sets the log identifier string. **/ LIB_EXPORT void l_log_set_ident(const char *ident) { log_ident = ident; } /** * l_log_set_handler: * @function: log handler function * * Sets the log handler function. **/ LIB_EXPORT void l_log_set_handler(l_log_func_t function) { L_DEBUG_SYMBOL(__debug_intern, ""); close_log(); if (!function) { log_func = log_null; return; } log_func = function; } /** * l_log_set_null: * * Disable logging. **/ LIB_EXPORT void l_log_set_null(void) { close_log(); log_func = log_null; } __attribute__((format(printf, 5, 0))) static void log_stderr(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap) { vfprintf(stderr, format, ap); } /** * l_log_set_stderr: * * Enable logging to stderr. **/ LIB_EXPORT void l_log_set_stderr(void) { close_log(); log_func = log_stderr; } __attribute__((format(printf, 5, 0))) static void log_syslog(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap) { struct msghdr msg; struct iovec iov[2]; char hdr[64], *str; int hdr_len, str_len; str_len = vasprintf(&str, format, ap); if (str_len < 0) return; hdr_len = snprintf(hdr, sizeof(hdr), "<%i>%s[%lu]: ", priority, log_ident, (unsigned long) log_pid); iov[0].iov_base = hdr; iov[0].iov_len = hdr_len; iov[1].iov_base = str; iov[1].iov_len = str_len; memset(&msg, 0, sizeof(msg)); msg.msg_iov = iov; msg.msg_iovlen = 2; sendmsg(log_fd, &msg, 0); free(str); } /** * l_log_set_syslog: * * Enable logging to syslog. **/ LIB_EXPORT void l_log_set_syslog(void) { close_log(); if (open_log("/dev/log") < 0) { log_func = log_null; return; } log_pid = getpid(); log_func = log_syslog; } __attribute__((format(printf, 5, 0))) static void log_journal(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap) { struct msghdr msg; struct iovec iov[12]; char prio[16], *str; int prio_len, str_len; str_len = vasprintf(&str, format, ap); if (str_len < 0) return; prio_len = snprintf(prio, sizeof(prio), "PRIORITY=%u\n", priority); iov[0].iov_base = "MESSAGE="; iov[0].iov_len = 8; iov[1].iov_base = str; iov[1].iov_len = str_len; iov[2].iov_base = prio; iov[2].iov_len = prio_len; iov[3].iov_base = "CODE_FILE="; iov[3].iov_len = 10; iov[4].iov_base = (char *) file; iov[4].iov_len = strlen(file); iov[5].iov_base = "\n"; iov[5].iov_len = 1; iov[6].iov_base = "CODE_LINE="; iov[6].iov_len = 10; iov[7].iov_base = (char *) line; iov[7].iov_len = strlen(line); iov[8].iov_base = "\n"; iov[8].iov_len = 1; iov[9].iov_base = "CODE_FUNC="; iov[9].iov_len = 10; iov[10].iov_base = (char *) func; iov[10].iov_len = strlen(func); iov[11].iov_base = "\n"; iov[11].iov_len = 1; memset(&msg, 0, sizeof(msg)); msg.msg_iov = iov; msg.msg_iovlen = 12; sendmsg(log_fd, &msg, 0); free(str); } /** * l_log_set_journal: * * Enable logging to journal. **/ LIB_EXPORT void l_log_set_journal(void) { close_log(); if (open_log("/run/systemd/journal/socket") < 0) { log_func = log_null; return; } log_pid = getpid(); log_func = log_journal; } /** * l_log_with_location: * @priority: priority level * @file: source file * @line: source line * @func: source function * @format: format string * @...: format arguments * * Log information. **/ LIB_EXPORT void l_log_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) { va_list ap; va_start(ap, format); log_func(priority, file, line, func, format, ap); va_end(ap); } /** * l_error: * @format: format string * @...: format arguments * **/ /** * l_warn: * @format: format string * @...: format arguments * **/ /** * l_info: * @format: format string * @...: format arguments * **/ /** * l_debug: * @format: format string * @...: format arguments **/ static const char *debug_pattern; static void debug_enable(struct l_debug_desc *start, struct l_debug_desc *stop) { struct l_debug_desc *desc; char *pattern_copy; if (!debug_pattern) return; pattern_copy = strdupa(debug_pattern); while (pattern_copy) { char *str = strsep(&pattern_copy, ":,"); if (!str) break; for (desc = start; desc < stop; desc++) { if (!fnmatch(str, desc->file, 0)) desc->flags |= L_DEBUG_FLAG_PRINT; if (!fnmatch(str, desc->func, 0)) desc->flags |= L_DEBUG_FLAG_PRINT; } } } static void debug_disable(struct l_debug_desc *start, struct l_debug_desc *stop) { struct l_debug_desc *desc; for (desc = start; desc < stop; desc++) desc->flags &= ~L_DEBUG_FLAG_PRINT; } /** * l_debug_add_section: * @start: start of the debug section * @stop: stop of the debug section * * Add information about a debug section. This is used by shared libraries * to tell ell about their debug section start & stopping points. This is used * to make l_debug statements work across all shared libraries that might be * linked into the executable */ LIB_EXPORT void l_debug_add_section(struct l_debug_desc *start, struct l_debug_desc *end) { const struct l_queue_entry *entry; struct debug_section *new_section; if (!debug_sections) { debug_sections = l_queue_new(); goto add; } for (entry = l_queue_get_entries(debug_sections); entry; entry = entry->next) { const struct debug_section *section = entry->data; if (section->start == start && section->end == end) return; } add: new_section = l_new(struct debug_section, 1); new_section->start = start; new_section->end = end; l_queue_push_head(debug_sections, new_section); } /** * l_debug_enable_full: * @pattern: debug pattern * @start: start of the debug section * @stop: end of the debug section * * Enable debug sections based on @pattern. **/ LIB_EXPORT void l_debug_enable_full(const char *pattern, struct l_debug_desc *start, struct l_debug_desc *end) { const struct l_queue_entry *entry; if (!pattern) return; debug_pattern = pattern; l_debug_add_section(start, end); for (entry = l_queue_get_entries(debug_sections); entry; entry = entry->next) { const struct debug_section *section = entry->data; debug_enable(section->start, section->end); } } /** * l_debug_disable: * * Disable all debug sections. **/ LIB_EXPORT void l_debug_disable(void) { const struct l_queue_entry *entry; for (entry = l_queue_get_entries(debug_sections); entry; entry = entry->next) { const struct debug_section *section = entry->data; debug_disable(section->start, section->end); } debug_pattern = NULL; } __attribute__((constructor)) static void register_debug_section() { extern struct l_debug_desc __start___ell_debug[]; extern struct l_debug_desc __stop___ell_debug[]; l_debug_add_section(__start___ell_debug, __stop___ell_debug); } __attribute__((destructor(65535))) static void free_debug_sections() { l_queue_destroy(debug_sections, l_free); } bluez-5.87/ell/PaxHeaders/dbus-name-cache.c0000644000000000000000000000005014504767710015522 xustar0020 atime=1783076304 20 ctime=1783094099 bluez-5.87/ell/dbus-name-cache.c0000644000000000000000000001365414504767710015214 0ustar00rootroot/* * Embedded Linux library * Copyright (C) 2016 Intel Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include "util.h" #include "hashmap.h" #include "idle.h" #include "dbus.h" #include "dbus-private.h" struct _dbus_name_cache { struct l_dbus *bus; struct l_hashmap *names; const struct _dbus_name_ops *driver; unsigned int last_watch_id; struct l_idle *watch_remove_work; }; struct service_watch { l_dbus_watch_func_t connect_func; l_dbus_watch_func_t disconnect_func; l_dbus_destroy_func_t destroy; void *user_data; unsigned int id; struct service_watch *next; }; struct name_cache_entry { int ref_count; char *unique_name; struct service_watch *watches; }; struct _dbus_name_cache *_dbus_name_cache_new(struct l_dbus *bus, const struct _dbus_name_ops *driver) { struct _dbus_name_cache *cache; cache = l_new(struct _dbus_name_cache, 1); cache->bus = bus; cache->driver = driver; return cache; } static void service_watch_destroy(void *data) { struct service_watch *watch = data; if (watch->destroy) watch->destroy(watch->user_data); l_free(watch); } static void name_cache_entry_destroy(void *data) { struct name_cache_entry *entry = data; struct service_watch *watch; while (entry->watches) { watch = entry->watches; entry->watches = watch->next; service_watch_destroy(watch); } l_free(entry->unique_name); l_free(entry); } void _dbus_name_cache_free(struct _dbus_name_cache *cache) { if (!cache) return; if (cache->watch_remove_work) l_idle_remove(cache->watch_remove_work); l_hashmap_destroy(cache->names, name_cache_entry_destroy); l_free(cache); } bool _dbus_name_cache_add(struct _dbus_name_cache *cache, const char *name) { struct name_cache_entry *entry; if (!_dbus_valid_bus_name(name)) return false; if (!cache->names) cache->names = l_hashmap_string_new(); entry = l_hashmap_lookup(cache->names, name); if (!entry) { entry = l_new(struct name_cache_entry, 1); l_hashmap_insert(cache->names, name, entry); cache->driver->get_name_owner(cache->bus, name); } entry->ref_count++; return true; } bool _dbus_name_cache_remove(struct _dbus_name_cache *cache, const char *name) { struct name_cache_entry *entry; entry = l_hashmap_lookup(cache->names, name); if (!entry) return false; if (--entry->ref_count) return true; l_hashmap_remove(cache->names, name); name_cache_entry_destroy(entry); return true; } const char *_dbus_name_cache_lookup(struct _dbus_name_cache *cache, const char *name) { struct name_cache_entry *entry; entry = l_hashmap_lookup(cache->names, name); if (!entry) return NULL; return entry->unique_name; } void _dbus_name_cache_notify(struct _dbus_name_cache *cache, const char *name, const char *owner) { struct name_cache_entry *entry; struct service_watch *watch; bool prev_connected, connected; if (!cache) return; entry = l_hashmap_lookup(cache->names, name); if (!entry) return; prev_connected = !!entry->unique_name; connected = owner && *owner != '\0'; l_free(entry->unique_name); entry->unique_name = connected ? l_strdup(owner) : NULL; /* * This check also means we notify all watchers who have a connected * callback when we first learn that the service is in fact connected. */ if (connected == prev_connected) return; for (watch = entry->watches; watch; watch = watch->next) if (connected && watch->connect_func) watch->connect_func(cache->bus, watch->user_data); else if (!connected && watch->disconnect_func) watch->disconnect_func(cache->bus, watch->user_data); } unsigned int _dbus_name_cache_add_watch(struct _dbus_name_cache *cache, const char *name, l_dbus_watch_func_t connect_func, l_dbus_watch_func_t disconnect_func, void *user_data, l_dbus_destroy_func_t destroy) { struct name_cache_entry *entry; struct service_watch *watch; if (!_dbus_name_cache_add(cache, name)) return 0; watch = l_new(struct service_watch, 1); watch->id = ++cache->last_watch_id; watch->connect_func = connect_func; watch->disconnect_func = disconnect_func; watch->user_data = user_data; watch->destroy = destroy; entry = l_hashmap_lookup(cache->names, name); watch->next = entry->watches; entry->watches = watch; if (entry->unique_name && connect_func) watch->connect_func(cache->bus, watch->user_data); return watch->id; } static bool service_watch_remove(const void *key, void *value, void *user_data) { struct name_cache_entry *entry = value; struct service_watch **watch, *tmp; for (watch = &entry->watches; *watch;) { if ((*watch)->id) { watch = &(*watch)->next; continue; } tmp = *watch; *watch = tmp->next; service_watch_destroy(tmp); entry->ref_count--; } if (entry->ref_count) return false; name_cache_entry_destroy(entry); return true; } static void service_watch_remove_all(struct l_idle *idle, void *user_data) { struct _dbus_name_cache *cache = user_data; l_idle_remove(cache->watch_remove_work); cache->watch_remove_work = NULL; l_hashmap_foreach_remove(cache->names, service_watch_remove, cache); } static void service_watch_mark(const void *key, void *value, void *user_data) { struct name_cache_entry *entry = value; struct service_watch *watch; unsigned int *id = user_data; if (!*id) return; for (watch = entry->watches; watch; watch = watch->next) if (watch->id == *id) { watch->id = 0; watch->connect_func = NULL; watch->disconnect_func = NULL; if (watch->destroy) { watch->destroy(watch->user_data); watch->destroy = NULL; } *id = 0; break; } } bool _dbus_name_cache_remove_watch(struct _dbus_name_cache *cache, unsigned int id) { l_hashmap_foreach(cache->names, service_watch_mark, &id); if (id) return false; if (!cache->watch_remove_work) cache->watch_remove_work = l_idle_create( service_watch_remove_all, cache, NULL); return true; } bluez-5.87/PaxHeaders/doc0000644000000000000000000000005015221755541012352 xustar0020 atime=1783094113 20 ctime=1783094113 bluez-5.87/doc/0000755000000000000000000000000015221755541012110 5ustar00rootrootbluez-5.87/doc/PaxHeaders/bluetoothctl-mgmt.10000644000000000000000000000005015221755445016166 xustar0020 atime=1783094053 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-mgmt.10000644000000000000000000013641115221755445015655 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "bluetoothctl-mgmt" "1" "July 2023" "BlueZ" "Linux System Administration" .SH Name bluetoothctl-mgmt \- Management Submenu .SH SYNOPSIS .sp \fBbluetoothctl\fP [\-\-options] [mgmt.commands] .sp \fBNote:\fP .INDENT 0.0 .INDENT 3.5 This submenu communicates directly with the kernel via the Bluetooth Management socket (mgmt), bypassing the BlueZ D\-Bus API. Commands use the Bluetooth Management API defined in the kernel documentation. .UNINDENT .UNINDENT .SH Mgmt Commands .SS select .sp Select a different index .INDENT 0.0 .TP .B Usage \fB> select \fP .TP .B Controller index number (0\-based) to select .TP .B Example Select controller index 0 (hci0) .nf \fB> select 0\fP .fi .sp .TP .B Example Select controller index 1 (hci1) .nf \fB> select 1\fP .fi .sp .TP .B Example Select controller index 2 (hci2) .nf \fB> select 2\fP .fi .sp .UNINDENT .SS revision .sp Get the MGMT Revision .INDENT 0.0 .TP .B Usage \fB> revision\fP .TP .B Example Display MGMT API revision information .nf \fB> revision\fP .fi .sp .UNINDENT .SS commands .sp List supported commands .INDENT 0.0 .TP .B Usage \fB> commands\fP .TP .B Example List all supported management commands .nf \fB> commands\fP .fi .sp .UNINDENT .SS config .sp Show configuration info .INDENT 0.0 .TP .B Usage \fB> config\fP .TP .B Example Show current controller configuration .nf \fB> config\fP .fi .sp .UNINDENT .SS info .sp Show controller info .INDENT 0.0 .TP .B Usage \fB> info\fP .TP .B Example Show detailed controller information .nf \fB> info\fP .fi .sp .UNINDENT .SS extinfo .sp Show extended controller info .INDENT 0.0 .TP .B Usage \fB> extinfo\fP .TP .B Example Show extended controller information .nf \fB> extinfo\fP .fi .sp .UNINDENT .SS auto\-power .sp Power all available features .INDENT 0.0 .TP .B Usage \fB> auto\-power\fP .TP .B Example Automatically power on all available controller features .nf \fB> auto\-power\fP .fi .sp .UNINDENT .SS power .sp Toggle powered state .INDENT 0.0 .TP .B Usage \fB> power \fP .TP .B Power state \- \(dqon\(dq to enable controller, \(dqoff\(dq to disable .TP .B Example Power on the controller .nf \fB> power on\fP .fi .sp .TP .B Example Power off the controller .nf \fB> power off\fP .fi .sp .UNINDENT .SS discov .sp Toggle discoverable state .INDENT 0.0 .TP .B Usage \fB> discov [timeout]\fP .TP .B Discoverable mode \- \(dqyes\(dq for general, \(dqno\(dq to disable, \(dqlimited\(dq for limited discoverable .TP .B [timeout] Optional timeout in seconds for discoverable mode duration .TP .B Example Make controller discoverable indefinitely .nf \fB> discov yes\fP .fi .sp .TP .B Example Make controller non\-discoverable .nf \fB> discov no\fP .fi .sp .TP .B Example Enable limited discoverable mode .nf \fB> discov limited\fP .fi .sp .TP .B Example Make discoverable for 30 seconds .nf \fB> discov yes 30\fP .fi .sp .TP .B Example Make discoverable for 2 minutes .nf \fB> discov yes 120\fP .fi .sp .TP .B Example Make discoverable for 5 minutes .nf \fB> discov yes 300\fP .fi .sp .TP .B Example Limited discoverable for 60 seconds .nf \fB> discov limited 60\fP .fi .sp .UNINDENT .SS connectable .sp Toggle connectable state .INDENT 0.0 .TP .B Usage \fB> connectable \fP .TP .B Connectable state \- \(dqon\(dq to allow connections, \(dqoff\(dq to reject .TP .B Example Make controller connectable .nf \fB> connectable on\fP .fi .sp .TP .B Example Make controller non\-connectable .nf \fB> connectable off\fP .fi .sp .UNINDENT .SS fast\-conn .sp Toggle fast connectable state .INDENT 0.0 .TP .B Usage \fB> fast\-conn \fP .TP .B Fast connectable state \- \(dqon\(dq to enable fast connection mode, \(dqoff\(dq to disable .TP .B Example Enable fast connectable mode .nf \fB> fast\-conn on\fP .fi .sp .TP .B Example Disable fast connectable mode .nf \fB> fast\-conn off\fP .fi .sp .UNINDENT .SS bondable .sp Toggle bondable state .INDENT 0.0 .TP .B Usage \fB> bondable \fP .TP .B Bondable state \- \(dqon\(dq to enable bonding capability, \(dqoff\(dq to disable .TP .B Example Enable bonding capability .nf \fB> bondable on\fP .fi .sp .TP .B Example Disable bonding capability .nf \fB> bondable off\fP .fi .sp .UNINDENT .SS pairable .sp Toggle bondable state .INDENT 0.0 .TP .B Usage \fB> pairable \fP .TP .B Pairable state \- \(dqon\(dq to enable pairing capability, \(dqoff\(dq to disable .TP .B Example Enable pairing capability .nf \fB> pairable on\fP .fi .sp .TP .B Example Disable pairing capability .nf \fB> pairable off\fP .fi .sp .UNINDENT .SS linksec .sp Toggle link level security .INDENT 0.0 .TP .B Usage \fB> linksec \fP .TP .B Link level security \- \(dqon\(dq to enable, \(dqoff\(dq to disable .TP .B Example Enable link level security .nf \fB> linksec on\fP .fi .sp .TP .B Example Disable link level security .nf \fB> linksec off\fP .fi .sp .UNINDENT .SS ssp .sp Toggle SSP mode .INDENT 0.0 .TP .B Usage \fB> ssp \fP .TP .B Secure Simple Pairing mode \- \(dqon\(dq to enable SSP, \(dqoff\(dq to disable .TP .B Example Enable Secure Simple Pairing .nf \fB> ssp on\fP .fi .sp .TP .B Example Disable Secure Simple Pairing .nf \fB> ssp off\fP .fi .sp .UNINDENT .SS sc .sp Toggle SC support .INDENT 0.0 .TP .B Usage \fB> sc \fP .TP .B Secure Connections support \- \(dqon\(dq to enable, \(dqoff\(dq to disable, \(dqonly\(dq for exclusive use .TP .B Example Enable Secure Connections support .nf \fB> sc on\fP .fi .sp .TP .B Example Disable Secure Connections support .nf \fB> sc off\fP .fi .sp .TP .B Example Use Secure Connections exclusively .nf \fB> sc only\fP .fi .sp .UNINDENT .SS hs .sp Toggle HS support .INDENT 0.0 .TP .B Usage \fB> hs \fP .TP .B High Speed support \- \(dqon\(dq to enable 802.11 High Speed, \(dqoff\(dq to disable .TP .B Example Enable High Speed support .nf \fB> hs on\fP .fi .sp .TP .B Example Disable High Speed support .nf \fB> hs off\fP .fi .sp .UNINDENT .SS le .sp Toggle LE support .INDENT 0.0 .TP .B Usage \fB> le \fP .TP .B Low Energy support \- \(dqon\(dq to enable LE functionality, \(dqoff\(dq to disable .TP .B Example Enable Low Energy support .nf \fB> le on\fP .fi .sp .TP .B Example Disable Low Energy support .nf \fB> le off\fP .fi .sp .UNINDENT .SS advertising .sp Toggle LE advertising .INDENT 0.0 .TP .B Usage \fB> advertising \fP .TP .B LE advertising state \- \(dqon\(dq to enable advertising, \(dqoff\(dq to disable .TP .B Example Enable LE advertising .nf \fB> advertising on\fP .fi .sp .TP .B Example Disable LE advertising .nf \fB> advertising off\fP .fi .sp .UNINDENT .SS bredr .sp Toggle BR/EDR support .INDENT 0.0 .TP .B Usage \fB> bredr \fP .TP .B BR/EDR support \- \(dqon\(dq to enable Classic Bluetooth, \(dqoff\(dq to disable .TP .B Example Enable BR/EDR (Classic Bluetooth) support .nf \fB> bredr on\fP .fi .sp .TP .B Example Disable BR/EDR support .nf \fB> bredr off\fP .fi .sp .UNINDENT .SS privacy .sp Toggle privacy support .INDENT 0.0 .TP .B Usage \fB> privacy [irk]\fP .TP .B Privacy support \- \(dqon\(dq to enable privacy features, \(dqoff\(dq to disable .TP .B [irk] Optional 32\-character hexadecimal Identity Resolving Key .TP .B Example Enable privacy with auto\-generated IRK .nf \fB> privacy on\fP .fi .sp .TP .B Example Disable privacy .nf \fB> privacy off\fP .fi .sp .TP .B Example Enable privacy with specific IRK .nf \fB> privacy on 0123456789abcdef0123456789abcdef\fP .fi .sp .TP .B Example Enable privacy with different IRK .nf \fB> privacy on 1234567890abcdef1234567890abcdef\fP .fi .sp .UNINDENT .SS class .sp Set device major/minor class .INDENT 0.0 .TP .B Usage \fB> class \fP .TP .B Major device class code (hexadecimal) .TP .B Minor device class code (hexadecimal) .TP .B Example Set class to Audio/Video \- Wearable Headset .nf \fB> class 0x04 0x01\fP .fi .sp .TP .B Example Set class to Audio/Video \- Hands\-free .nf \fB> class 0x04 0x02\fP .fi .sp .TP .B Example Set class to Audio/Video \- Headphones .nf \fB> class 0x04 0x06\fP .fi .sp .TP .B Example Set class to Audio/Video \- VCR .nf \fB> class 0x04 0x0B\fP .fi .sp .TP .B Example Set class to Computer \- Desktop workstation .nf \fB> class 0x01 0x01\fP .fi .sp .TP .B Example Set class to Computer \- Server .nf \fB> class 0x01 0x02\fP .fi .sp .TP .B Example Set class to Computer \- Laptop .nf \fB> class 0x01 0x03\fP .fi .sp .TP .B Example Set class to Computer \- Handheld PC/PDA .nf \fB> class 0x01 0x04\fP .fi .sp .TP .B Example Set class to Phone \- Cellular .nf \fB> class 0x02 0x01\fP .fi .sp .TP .B Example Set class to Phone \- Cordless .nf \fB> class 0x02 0x02\fP .fi .sp .TP .B Example Set class to Phone \- Smart phone .nf \fB> class 0x02 0x03\fP .fi .sp .UNINDENT .SS disconnect .sp Disconnect device .INDENT 0.0 .TP .B Usage \fB> disconnect [\-t type] \fP .TP .B [\-t type] Optional address type (public, random, le_public, le_random) .TP .B Bluetooth address of device to disconnect .TP .B Example Disconnect device using public address .nf \fB> disconnect 00:11:22:33:44:55\fP .fi .sp .TP .B Example Disconnect another device .nf \fB> disconnect AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Disconnect device with public address type .nf \fB> disconnect \-t public 00:11:22:33:44:55\fP .fi .sp .TP .B Example Disconnect device with random address type .nf \fB> disconnect \-t random AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Disconnect LE device with public address .nf \fB> disconnect \-t le_public 11:22:33:44:55:66\fP .fi .sp .TP .B Example Disconnect LE device with random address .nf \fB> disconnect \-t le_random CC:DD:EE:FF:00:11\fP .fi .sp .UNINDENT .SS con .sp List connections .INDENT 0.0 .TP .B Usage \fB> con\fP .TP .B Example List all active connections .nf \fB> con\fP .fi .sp .UNINDENT .SS find .sp Discover nearby devices .INDENT 0.0 .TP .B Usage \fB> find [\-l|\-b] [\-L]\fP .TP .B [\-l|\-b] Discovery type \- \(dq\-l\(dq for LE only, \(dq\-b\(dq for BR/EDR only (default: both) .TP .B [\-L] Limited discovery mode flag .TP .B Example Discover both LE and BR/EDR devices .nf \fB> find\fP .fi .sp .TP .B Example Discover LE devices only .nf \fB> find \-l\fP .fi .sp .TP .B Example Discover BR/EDR devices only .nf \fB> find \-b\fP .fi .sp .TP .B Example Discover devices in limited mode .nf \fB> find \-L\fP .fi .sp .TP .B Example Discover LE devices in limited mode .nf \fB> find \-l \-L\fP .fi .sp .TP .B Example Discover BR/EDR devices in limited mode .nf \fB> find \-b \-L\fP .fi .sp .UNINDENT .SS find\-service .sp Discover nearby service .INDENT 0.0 .TP .B Usage \fB> find\-service [\-u UUID] [\-r RSSI_Threshold] [\-l|\-b]\fP .TP .B [\-u UUID] Service UUID to search for (16\-bit, 32\-bit, or 128\-bit) .TP .B [\-r RSSI_Threshold] Minimum RSSI value in dBm for device filtering .TP .B [\-l|\-b] Discovery type \- \(dq\-l\(dq for LE only, \(dq\-b\(dq for BR/EDR only .TP .B Example Find devices with Generic Access service .nf \fB> find\-service \-u 0x1800\fP .fi .sp .TP .B Example Find devices with Battery Service .nf \fB> find\-service \-u 0x180F\fP .fi .sp .TP .B Example Find devices with custom service .nf \fB> find\-service \-u 12345678\-1234\-5678\-9abc\-123456789abc\fP .fi .sp .TP .B Example Find devices with RSSI ≥ \-60 dBm .nf \fB> find\-service \-r \-60\fP .fi .sp .TP .B Example Find devices with RSSI ≥ \-80 dBm .nf \fB> find\-service \-r \-80\fP .fi .sp .TP .B Example Find Battery Service devices with RSSI ≥ \-70 dBm .nf \fB> find\-service \-u 0x180F \-r \-70\fP .fi .sp .TP .B Example Find Generic Access on LE devices with strong signal .nf \fB> find\-service \-u 0x1800 \-r \-50 \-l\fP .fi .sp .TP .B Example Find Serial Port Profile on BR/EDR devices .nf \fB> find\-service \-u 0x1101 \-b\fP .fi .sp .TP .B Example Find Battery Service on LE devices .nf \fB> find\-service \-u 0x180F \-l\fP .fi .sp .UNINDENT .SS stop\-find .sp Stop discovery .INDENT 0.0 .TP .B Usage \fB> stop\-find [\-l|\-b]\fP .TP .B [\-l|\-b] Discovery type to stop \- \(dq\-l\(dq for LE only, \(dq\-b\(dq for BR/EDR only (default: all) .TP .B Example Stop all discovery .nf \fB> stop\-find\fP .fi .sp .TP .B Example Stop LE discovery only .nf \fB> stop\-find \-l\fP .fi .sp .TP .B Example Stop BR/EDR discovery only .nf \fB> stop\-find \-b\fP .fi .sp .UNINDENT .SS name .sp Set local name .INDENT 0.0 .TP .B Usage \fB> name [shortname]\fP .TP .B Complete local name for the controller .TP .B [shortname] Optional shortened local name .TP .B Example Set complete local name .nf \fB> name \(dqMy Bluetooth Device\(dq\fP .fi .sp .TP .B Example Set name without spaces .nf \fB> name MyComputer\fP .fi .sp .TP .B Example Set name with hyphens .nf \fB> name \(dqHome\-Office\-PC\(dq\fP .fi .sp .TP .B Example Set both complete and shortened names .nf \fB> name \(dqMy Long Device Name\(dq MyDevice\fP .fi .sp .TP .B Example Set descriptive and short names .nf \fB> name \(dqProfessional Workstation\(dq ProWork\fP .fi .sp .TP .B Example Set audio device names .nf \fB> name \(dqBluetooth Audio Headset\(dq BT\-Audio\fP .fi .sp .UNINDENT .SS pair .sp Pair with a remote device .INDENT 0.0 .TP .B Usage \fB> pair [\-c cap] [\-t type] \fP .TP .B [\-c cap] IO capability (DisplayOnly, DisplayYesNo, KeyboardOnly, NoInputNoOutput, KeyboardDisplay) .TP .B [\-t type] Address type (public, random, le_public, le_random) .TP .B Bluetooth address of device to pair with .TP .B Example Pair with device using default settings .nf \fB> pair 00:11:22:33:44:55\fP .fi .sp .TP .B Example Pair with another device .nf \fB> pair AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Pair with display\-only IO capability .nf \fB> pair \-c DisplayOnly 00:11:22:33:44:55\fP .fi .sp .TP .B Example Pair with display and yes/no capability .nf \fB> pair \-c DisplayYesNo AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Pair with keyboard\-only capability .nf \fB> pair \-c KeyboardOnly 11:22:33:44:55:66\fP .fi .sp .TP .B Example Pair with no I/O capability .nf \fB> pair \-c NoInputNoOutput CC:DD:EE:FF:00:11\fP .fi .sp .TP .B Example Pair with keyboard and display capability .nf \fB> pair \-c KeyboardDisplay 22:33:44:55:66:77\fP .fi .sp .TP .B Example Pair with public address device .nf \fB> pair \-t public 00:11:22:33:44:55\fP .fi .sp .TP .B Example Pair with random address device .nf \fB> pair \-t random AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Pair with LE public address device .nf \fB> pair \-t le_public 11:22:33:44:55:66\fP .fi .sp .TP .B Example Pair with LE random address device .nf \fB> pair \-t le_random CC:DD:EE:FF:00:11\fP .fi .sp .TP .B Example Pair LE device with keyboard/display capability .nf \fB> pair \-c KeyboardDisplay \-t le_public 00:11:22:33:44:55\fP .fi .sp .TP .B Example Pair random address device with display/yes\-no capability .nf \fB> pair \-c DisplayYesNo \-t random AA:BB:CC:DD:EE:FF\fP .fi .sp .UNINDENT .SS cancelpair .sp Cancel pairing .INDENT 0.0 .TP .B Usage \fB> cancelpair [\-t type] \fP .TP .B [\-t type] Address type (public, random, le_public, le_random) .TP .B Bluetooth address of device to cancel pairing with .TP .B Example Cancel ongoing pairing with device .nf \fB> cancelpair 00:11:22:33:44:55\fP .fi .sp .TP .B Example Cancel pairing with another device .nf \fB> cancelpair AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Cancel pairing with public address device .nf \fB> cancelpair \-t public 00:11:22:33:44:55\fP .fi .sp .TP .B Example Cancel pairing with LE random address device .nf \fB> cancelpair \-t le_random AA:BB:CC:DD:EE:FF\fP .fi .sp .UNINDENT .SS unpair .sp Unpair device .INDENT 0.0 .TP .B Usage \fB> unpair [\-t type] \fP .TP .B [\-t type] Address type (public, random, le_public, le_random) .TP .B Bluetooth address of device to unpair .TP .B Example Remove pairing with device .nf \fB> unpair 00:11:22:33:44:55\fP .fi .sp .TP .B Example Remove pairing with another device .nf \fB> unpair AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Unpair public address device .nf \fB> unpair \-t public 00:11:22:33:44:55\fP .fi .sp .TP .B Example Unpair LE public address device .nf \fB> unpair \-t le_public 11:22:33:44:55:66\fP .fi .sp .TP .B Example Unpair LE random address device .nf \fB> unpair \-t le_random CC:DD:EE:FF:00:11\fP .fi .sp .UNINDENT .SS keys .sp Load Link Keys .INDENT 0.0 .TP .B Usage \fB> keys\fP .TP .B Example Load stored link keys .nf \fB> keys\fP .fi .sp .UNINDENT .SS ltks .sp Load Long Term Keys .INDENT 0.0 .TP .B Usage \fB> ltks\fP .TP .B Example Load stored LE long term keys .nf \fB> ltks\fP .fi .sp .UNINDENT .SS irks .sp Load Identity Resolving Keys .INDENT 0.0 .TP .B Usage \fB> irks [\-\-local index] [\-\-file file path]\fP .TP .B [\-\-local index] Local controller index to use .TP .B [\-\-file file path] Custom IRK configuration file path .TP .B Example Load IRKs with default local index and file .nf \fB> irks\fP .fi .sp .TP .B Example Load IRKs for controller index 0 .nf \fB> irks \-\-local 0\fP .fi .sp .TP .B Example Load IRKs for controller index 1 .nf \fB> irks \-\-local 1\fP .fi .sp .TP .B Example Load IRKs from custom configuration file .nf \fB> irks \-\-file /etc/bluetooth/irks.conf\fP .fi .sp .TP .B Example Load IRKs from user file .nf \fB> irks \-\-file /home/user/bluetooth_irks.txt\fP .fi .sp .TP .B Example Load IRKs for hci0 from specific file .nf \fB> irks \-\-local 0 \-\-file /etc/bluetooth/hci0_irks.conf\fP .fi .sp .UNINDENT .SS block .sp Block Device .INDENT 0.0 .TP .B Usage \fB> block [\-t type] \fP .TP .B [\-t type] Address type (public, random, le_public, le_random) .TP .B Bluetooth address of device to block .TP .B Example Block device using default address type .nf \fB> block 00:11:22:33:44:55\fP .fi .sp .TP .B Example Block another device .nf \fB> block AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Block device with public address .nf \fB> block \-t public 00:11:22:33:44:55\fP .fi .sp .TP .B Example Block device with random address .nf \fB> block \-t random AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Block LE device with public address .nf \fB> block \-t le_public 11:22:33:44:55:66\fP .fi .sp .TP .B Example Block LE device with random address .nf \fB> block \-t le_random CC:DD:EE:FF:00:11\fP .fi .sp .UNINDENT .SS unblock .sp Unblock Device .INDENT 0.0 .TP .B Usage \fB> unblock [\-t type] \fP .TP .B [\-t type] Address type (public, random, le_public, le_random) .TP .B Bluetooth address of device to unblock .TP .B Example Unblock device using default address type .nf \fB> unblock 00:11:22:33:44:55\fP .fi .sp .TP .B Example Unblock another device .nf \fB> unblock AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Unblock device with public address .nf \fB> unblock \-t public 00:11:22:33:44:55\fP .fi .sp .TP .B Example Unblock device with random address .nf \fB> unblock \-t random AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Unblock LE device with public address .nf \fB> unblock \-t le_public 11:22:33:44:55:66\fP .fi .sp .TP .B Example Unblock LE device with random address .nf \fB> unblock \-t le_random CC:DD:EE:FF:00:11\fP .fi .sp .UNINDENT .SS add\-uuid .sp Add UUID .INDENT 0.0 .TP .B Usage \fB> add\-uuid \fP .TP .B Service UUID (16\-bit, 32\-bit, or 128\-bit format) .TP .B Service class hint bitmask (hexadecimal) .TP .B Example Add Serial Port Profile with object transfer hint .nf \fB> add\-uuid 0x1101 0x100000\fP .fi .sp .TP .B Example Add A2DP with audio hint .nf \fB> add\-uuid 0x110E 0x200000\fP .fi .sp .TP .B Example Add HFP with audio hint .nf \fB> add\-uuid 0x111E 0x200000\fP .fi .sp .TP .B Example Add custom service UUID with no hint .nf \fB> add\-uuid 12345678\-1234\-5678\-9abc\-123456789abc 0x000000\fP .fi .sp .TP .B Example Add custom UUID with object transfer hint .nf \fB> add\-uuid ABCD1234\-ABCD\-1234\-ABCD\-123456789ABC 0x100000\fP .fi .sp .TP .B Example Add Generic Access with no specific hint .nf \fB> add\-uuid 0x1800 0x000000\fP .fi .sp .TP .B Example Add Battery Service with no hint .nf \fB> add\-uuid 0x180F 0x000000\fP .fi .sp .UNINDENT .SS rm\-uuid .sp Remove UUID .INDENT 0.0 .TP .B Usage \fB> rm\-uuid \fP .TP .B Service UUID to remove (16\-bit, 32\-bit, or 128\-bit format) .TP .B Example Remove Serial Port Profile UUID .nf \fB> rm\-uuid 0x1101\fP .fi .sp .TP .B Example Remove A2DP UUID .nf \fB> rm\-uuid 0x110E\fP .fi .sp .TP .B Example Remove custom service UUID .nf \fB> rm\-uuid 12345678\-1234\-5678\-9abc\-123456789abc\fP .fi .sp .UNINDENT .SS clr\-uuids .sp Clear UUIDs .INDENT 0.0 .TP .B Usage \fB> clr\-uuids\fP .TP .B Example Clear all registered UUIDs .nf \fB> clr\-uuids\fP .fi .sp .UNINDENT .SS local\-oob .sp Local OOB data .INDENT 0.0 .TP .B Usage \fB> local\-oob\fP .TP .B Example Generate and display local OOB authentication data .nf \fB> local\-oob\fP .fi .sp .UNINDENT .SS remote\-oob .sp Remote OOB data .INDENT 0.0 .TP .B Usage \fB> remote\-oob [\-t ] [\-r ] [\-h ] [\-R ] [\-H ] \fP .TP .B [\-t ] Address type (public, random, le_public, le_random) .TP .B [\-r ] P\-192 random value (32 hexadecimal characters) .TP .B [\-h ] P\-192 hash value (32 hexadecimal characters) .TP .B [\-R ] P\-256 random value (64 hexadecimal characters) .TP .B [\-H ] P\-256 hash value (64 hexadecimal characters) .TP .B Remote device Bluetooth address .TP .B Example Set remote OOB data for device (minimal) .nf \fB> remote\-oob 00:11:22:33:44:55\fP .fi .sp .TP .B Example Set P\-192 random and hash values .nf \fB> remote\-oob \-r 0123456789abcdef0123456789abcdef \-h fedcba9876543210fedcba9876543210 00:11:22:33:44:55\fP .fi .sp .TP .B Example Set P\-256 random and hash values .nf \fB> remote\-oob \-R 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \-H fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210 AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Set OOB data for LE public address device .nf \fB> remote\-oob \-t le_public 11:22:33:44:55:66\fP .fi .sp .TP .B Example Set OOB data for LE random address device .nf \fB> remote\-oob \-t le_random CC:DD:EE:FF:00:11\fP .fi .sp .TP .B Example Set complete OOB data with both P\-192 and P\-256 values .nf \fB> remote\-oob \-t public \-r 0123456789abcdef0123456789abcdef \-h fedcba9876543210fedcba9876543210 \-R 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \-H fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210 00:11:22:33:44:55\fP .fi .sp .UNINDENT .SS did .sp Set Device ID .INDENT 0.0 .TP .B Usage \fB> did :::\fP .TP .B ::: Device ID in format source:vendor:product:version (all hexadecimal) .TP .B Example Set USB source with Linux Foundation vendor ID .nf \fB> did 0x0002:0x1D6B:0x0001:0x0100\fP .fi .sp .TP .B Example Set USB device with Cypress vendor ID .nf \fB> did 0x0002:0x04B4:0x1234:0x0200\fP .fi .sp .TP .B Example Set Bluetooth SIG source with Broadcom vendor .nf \fB> did 0x0001:0x000F:0x0001:0x0100\fP .fi .sp .TP .B Example Set Bluetooth SIG source with Apple vendor .nf \fB> did 0x0001:0x004C:0x0001:0x0100\fP .fi .sp .TP .B Example Set custom USB device ID .nf \fB> did 0x0002:0xFFFF:0x1234:0x5678\fP .fi .sp .TP .B Example Set Qualcomm Bluetooth device .nf \fB> did 0x0001:0x05F1:0xABCD:0x0001\fP .fi .sp .UNINDENT .SS static\-addr .sp Set static address .INDENT 0.0 .TP .B Usage \fB> static\-addr
\fP .TP .B
Static random Bluetooth address (must have bits 47\-46 set to 11) .TP .B Example Set static random address (bit 47=1, bit 46=1) .nf \fB> static\-addr C0:00:00:00:00:01\fP .fi .sp .TP .B Example Set another static random address .nf \fB> static\-addr D0:11:22:33:44:55\fP .fi .sp .TP .B Example Set different static address .nf \fB> static\-addr E0:AA:BB:CC:DD:EE\fP .fi .sp .UNINDENT .SS public\-addr .sp Set public address .INDENT 0.0 .TP .B Usage \fB> public\-addr
\fP .TP .B
Public Bluetooth address to set for the controller .TP .B Example Set public Bluetooth address .nf \fB> public\-addr 00:11:22:33:44:55\fP .fi .sp .TP .B Example Set different public address .nf \fB> public\-addr AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Set custom public address .nf \fB> public\-addr 12:34:56:78:9A:BC\fP .fi .sp .UNINDENT .SS ext\-config .sp External configuration .INDENT 0.0 .TP .B Usage \fB> ext\-config \fP .TP .B External configuration \- \(dqon\(dq to enable external config, \(dqoff\(dq to disable .TP .B Example Enable external configuration .nf \fB> ext\-config on\fP .fi .sp .TP .B Example Disable external configuration .nf \fB> ext\-config off\fP .fi .sp .UNINDENT .SS debug\-keys .sp Toggle debug keys .INDENT 0.0 .TP .B Usage \fB> debug\-keys \fP .TP .B Debug keys support \- \(dqon\(dq to enable debug keys, \(dqoff\(dq to disable .TP .B Example Enable debug key support .nf \fB> debug\-keys on\fP .fi .sp .TP .B Example Disable debug key support .nf \fB> debug\-keys off\fP .fi .sp .UNINDENT .SS conn\-info .sp Get connection information .INDENT 0.0 .TP .B Usage \fB> conn\-info [\-t type] \fP .TP .B [\-t type] Address type (public, random, le_public, le_random) .TP .B Bluetooth address of connected device .TP .B Example Get connection information for device .nf \fB> conn\-info 00:11:22:33:44:55\fP .fi .sp .TP .B Example Get info for another device .nf \fB> conn\-info AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Get info for public address device .nf \fB> conn\-info \-t public 00:11:22:33:44:55\fP .fi .sp .TP .B Example Get info for LE public address device .nf \fB> conn\-info \-t le_public 11:22:33:44:55:66\fP .fi .sp .TP .B Example Get info for LE random address device .nf \fB> conn\-info \-t le_random CC:DD:EE:FF:00:11\fP .fi .sp .UNINDENT .SS io\-cap .sp Set IO Capability .INDENT 0.0 .TP .B Usage \fB> io\-cap \fP .TP .B IO capability (DisplayOnly, DisplayYesNo, KeyboardOnly, NoInputNoOutput, KeyboardDisplay) .TP .B Example Set IO capability to display only .nf \fB> io\-cap DisplayOnly\fP .fi .sp .TP .B Example Set IO capability to display with yes/no .nf \fB> io\-cap DisplayYesNo\fP .fi .sp .TP .B Example Set IO capability to keyboard only .nf \fB> io\-cap KeyboardOnly\fP .fi .sp .TP .B Example Set IO capability to no input/output .nf \fB> io\-cap NoInputNoOutput\fP .fi .sp .TP .B Example Set IO capability to keyboard and display .nf \fB> io\-cap KeyboardDisplay\fP .fi .sp .UNINDENT .SS scan\-params .sp Set Scan Parameters .INDENT 0.0 .TP .B Usage \fB> scan\-params \fP .TP .B Scan interval in hexadecimal (0x0004 to 0x4000, units of 0.625ms) .TP .B Scan window in hexadecimal (0x0004 to 0x4000, units of 0.625ms) .TP .B Example Set fast scan (16ms interval, 16ms window) .nf \fB> scan\-params 0x0010 0x0010\fP .fi .sp .TP .B Example Set moderate fast scan (32ms interval, 32ms window) .nf \fB> scan\-params 0x0020 0x0020\fP .fi .sp .TP .B Example Set balanced scan (96ms interval, 48ms window) .nf \fB> scan\-params 0x0060 0x0030\fP .fi .sp .TP .B Example Set slow scan (256ms interval, 80ms window) .nf \fB> scan\-params 0x0100 0x0050\fP .fi .sp .TP .B Example Set background scan (2.56s interval, 18ms window) .nf \fB> scan\-params 0x0800 0x0012\fP .fi .sp .TP .B Example Set very slow background scan .nf \fB> scan\-params 0x1000 0x0020\fP .fi .sp .UNINDENT .SS get\-clock .sp Get Clock Information .INDENT 0.0 .TP .B Usage \fB> get\-clock [address]\fP .TP .B [address] Optional remote device Bluetooth address (omit for local clock) .TP .B Example Get local Bluetooth clock information .nf \fB> get\-clock\fP .fi .sp .TP .B Example Get clock information for remote device .nf \fB> get\-clock 00:11:22:33:44:55\fP .fi .sp .TP .B Example Get clock for another remote device .nf \fB> get\-clock AA:BB:CC:DD:EE:FF\fP .fi .sp .UNINDENT .SS add\-device .sp Add Device .INDENT 0.0 .TP .B Usage \fB> add\-device [\-a action] [\-t type]
\fP .TP .B [\-a action] Device action (auto, allow, deny) \- \(dqauto\(dq for auto\-connect, \(dqallow\(dq for allow\-only, \(dqdeny\(dq to block .TP .B [\-t type] Address type (public, random, le_public, le_random) .TP .B
Bluetooth address of device to add .TP .B Example Add device with auto\-connect action and default type .nf \fB> add\-device 00:11:22:33:44:55\fP .fi .sp .TP .B Example Add another device with defaults .nf \fB> add\-device AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Add device with auto\-connect action .nf \fB> add\-device \-a auto 00:11:22:33:44:55\fP .fi .sp .TP .B Example Add device with allow\-only action (no auto\-connect) .nf \fB> add\-device \-a allow 11:22:33:44:55:66\fP .fi .sp .TP .B Example Add device with deny action (blocked) .nf \fB> add\-device \-a deny CC:DD:EE:FF:00:11\fP .fi .sp .TP .B Example Add device with public address type .nf \fB> add\-device \-t public 00:11:22:33:44:55\fP .fi .sp .TP .B Example Add device with random address type .nf \fB> add\-device \-t random AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Add LE device with public address .nf \fB> add\-device \-t le_public 11:22:33:44:55:66\fP .fi .sp .TP .B Example Add LE device with random address .nf \fB> add\-device \-t le_random CC:DD:EE:FF:00:11\fP .fi .sp .TP .B Example Add LE public device with auto\-connect .nf \fB> add\-device \-a auto \-t le_public 00:11:22:33:44:55\fP .fi .sp .TP .B Example Add random address device with allow action .nf \fB> add\-device \-a allow \-t random AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Add LE random device with deny action .nf \fB> add\-device \-a deny \-t le_random CC:DD:EE:FF:00:11\fP .fi .sp .UNINDENT .SS del\-device .sp Remove Device .INDENT 0.0 .TP .B Usage \fB> del\-device [\-t type]
\fP .TP .B [\-t type] Address type (public, random, le_public, le_random) .TP .B
Bluetooth address of device to remove .TP .B Example Remove device using default address type .nf \fB> del\-device 00:11:22:33:44:55\fP .fi .sp .TP .B Example Remove another device .nf \fB> del\-device AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Remove device with public address type .nf \fB> del\-device \-t public 00:11:22:33:44:55\fP .fi .sp .TP .B Example Remove device with random address type .nf \fB> del\-device \-t random AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Remove LE device with public address .nf \fB> del\-device \-t le_public 11:22:33:44:55:66\fP .fi .sp .TP .B Example Remove LE device with random address .nf \fB> del\-device \-t le_random CC:DD:EE:FF:00:11\fP .fi .sp .UNINDENT .SS clr\-devices .sp Clear Devices .INDENT 0.0 .TP .B Usage \fB> clr\-devices\fP .TP .B Example Clear all devices from the device list .nf \fB> clr\-devices\fP .fi .sp .UNINDENT .SS bredr\-oob .sp Local OOB data (BR/EDR) .INDENT 0.0 .TP .B Usage \fB> bredr\-oob\fP .TP .B Example Generate and display local BR/EDR OOB authentication data .nf \fB> bredr\-oob\fP .fi .sp .UNINDENT .SS le\-oob .sp Local OOB data (LE) .INDENT 0.0 .TP .B Usage \fB> le\-oob\fP .TP .B Example Generate and display local LE OOB authentication data .nf \fB> le\-oob\fP .fi .sp .UNINDENT .SS advinfo .sp Show advertising features .INDENT 0.0 .TP .B Usage \fB> advinfo\fP .TP .B Example Display advertising capabilities and supported features .nf \fB> advinfo\fP .fi .sp .UNINDENT .SS advsize .sp Show advertising size info .INDENT 0.0 .TP .B Usage \fB> advsize [options] \fP .TP .B [options] Advertising options flags .TP .B Advertising instance identifier (0\-based) .TP .B Example Show advertising data size information for instance 0 .nf \fB> advsize 0\fP .fi .sp .TP .B Example Show size information for advertising instance 1 .nf \fB> advsize 1\fP .fi .sp .TP .B Example Show size information for advertising instance 5 .nf \fB> advsize 5\fP .fi .sp .UNINDENT .SS add\-adv .sp Add advertising instance .INDENT 0.0 .TP .B Usage \fB> add\-adv [options] \fP .TP .B [options] Advertising options (\-c connectable, \-s scannable, \-g general discoverable, \-l limited discoverable, \-m managed flags, \-p TX power) .TP .B Advertising instance identifier (0\-based) .TP .B Example Add advertising instance 0 with default settings .nf \fB> add\-adv 0\fP .fi .sp .TP .B Example Add advertising instance 1 .nf \fB> add\-adv 1\fP .fi .sp .TP .B Example Add advertising instance 5 .nf \fB> add\-adv 5\fP .fi .sp .TP .B Example Add connectable advertising instance 0 .nf \fB> add\-adv \-c 0\fP .fi .sp .TP .B Example Add scannable advertising instance 1 .nf \fB> add\-adv \-s 1\fP .fi .sp .TP .B Example Add general discoverable advertising instance 2 .nf \fB> add\-adv \-g 2\fP .fi .sp .TP .B Example Add limited discoverable advertising instance 3 .nf \fB> add\-adv \-l 3\fP .fi .sp .TP .B Example Add managed flags advertising instance 4 .nf \fB> add\-adv \-m 4\fP .fi .sp .TP .B Example Add TX power advertising instance 5 .nf \fB> add\-adv \-p 5\fP .fi .sp .TP .B Example Add connectable and scannable advertising instance .nf \fB> add\-adv \-c \-s 0\fP .fi .sp .TP .B Example Add general discoverable with TX power .nf \fB> add\-adv \-g \-p 1\fP .fi .sp .TP .B Example Add connectable, general discoverable, and scannable .nf \fB> add\-adv \-c \-g \-s 2\fP .fi .sp .UNINDENT .SS rm\-adv .sp Remove advertising instance .INDENT 0.0 .TP .B Usage \fB> rm\-adv \fP .TP .B Advertising instance identifier (0\-based) to remove .TP .B Example Remove advertising instance 0 .nf \fB> rm\-adv 0\fP .fi .sp .TP .B Example Remove advertising instance 1 .nf \fB> rm\-adv 1\fP .fi .sp .TP .B Example Remove advertising instance 5 .nf \fB> rm\-adv 5\fP .fi .sp .UNINDENT .SS clr\-adv .sp Clear advertising instances .INDENT 0.0 .TP .B Usage \fB> clr\-adv\fP .TP .B Example Clear all advertising instances .nf \fB> clr\-adv\fP .fi .sp .UNINDENT .SS add\-ext\-adv\-params .sp Add extended advertising params .INDENT 0.0 .TP .B Usage \fB> add\-ext\-adv\-params [options] \fP .TP .B [options] Extended advertising options (\-c connectable, \-s scannable, \-d directed, \-h high duty cycle, \-l legacy PDU, \-a anonymous, \-i include TX power) .TP .B Extended advertising instance identifier (0\-based) .TP .B Example Add extended advertising parameters for instance 0 .nf \fB> add\-ext\-adv\-params 0\fP .fi .sp .TP .B Example Add extended advertising parameters for instance 1 .nf \fB> add\-ext\-adv\-params 1\fP .fi .sp .TP .B Example Add connectable extended advertising for instance 0 .nf \fB> add\-ext\-adv\-params \-c 0\fP .fi .sp .TP .B Example Add scannable extended advertising for instance 1 .nf \fB> add\-ext\-adv\-params \-s 1\fP .fi .sp .TP .B Example Add directed extended advertising for instance 2 .nf \fB> add\-ext\-adv\-params \-d 2\fP .fi .sp .TP .B Example Add high duty cycle extended advertising for instance 3 .nf \fB> add\-ext\-adv\-params \-h 3\fP .fi .sp .TP .B Example Add legacy PDU extended advertising for instance 4 .nf \fB> add\-ext\-adv\-params \-l 4\fP .fi .sp .TP .B Example Add anonymous extended advertising for instance 5 .nf \fB> add\-ext\-adv\-params \-a 5\fP .fi .sp .TP .B Example Add include TX power extended advertising for instance 6 .nf \fB> add\-ext\-adv\-params \-i 6\fP .fi .sp .TP .B Example Add connectable and scannable extended advertising .nf \fB> add\-ext\-adv\-params \-c \-s 0\fP .fi .sp .TP .B Example Add connectable extended advertising with TX power .nf \fB> add\-ext\-adv\-params \-c \-i 1\fP .fi .sp .TP .B Example Add scannable legacy PDU extended advertising .nf \fB> add\-ext\-adv\-params \-s \-l 2\fP .fi .sp .UNINDENT .SS add\-ext\-adv\-data .sp Add extended advertising data .INDENT 0.0 .TP .B Usage \fB> add\-ext\-adv\-data [options] \fP .TP .B [options] Data options (\-s for scan response data, \-c for complete data) .TP .B Extended advertising instance identifier (0\-based) .TP .B Example Add advertising data to extended instance 0 .nf \fB> add\-ext\-adv\-data 0\fP .fi .sp .TP .B Example Add advertising data to extended instance 1 .nf \fB> add\-ext\-adv\-data 1\fP .fi .sp .TP .B Example Add scan response data to extended instance 0 .nf \fB> add\-ext\-adv\-data \-s 0\fP .fi .sp .TP .B Example Add scan response data to extended instance 1 .nf \fB> add\-ext\-adv\-data \-s 1\fP .fi .sp .TP .B Example Add complete advertising data to instance 0 .nf \fB> add\-ext\-adv\-data \-c 0\fP .fi .sp .TP .B Example Add complete scan response data to instance 1 .nf \fB> add\-ext\-adv\-data \-c \-s 1\fP .fi .sp .UNINDENT .SS appearance .sp Set appearance .INDENT 0.0 .TP .B Usage \fB> appearance \fP .TP .B Appearance value (16\-bit integer) representing device type .TP .B Example Set appearance to Unknown .nf \fB> appearance 0\fP .fi .sp .TP .B Example Set appearance to Generic Phone .nf \fB> appearance 64\fP .fi .sp .TP .B Example Set appearance to Generic Computer .nf \fB> appearance 128\fP .fi .sp .TP .B Example Set appearance to Generic Audio/Video device .nf \fB> appearance 832\fP .fi .sp .TP .B Example Set appearance to Speaker .nf \fB> appearance 833\fP .fi .sp .TP .B Example Set appearance to Microphone .nf \fB> appearance 834\fP .fi .sp .TP .B Example Set appearance to Headset .nf \fB> appearance 835\fP .fi .sp .TP .B Example Set appearance to Headphones .nf \fB> appearance 836\fP .fi .sp .TP .B Example Set appearance to Generic HID .nf \fB> appearance 960\fP .fi .sp .TP .B Example Set appearance to Keyboard .nf \fB> appearance 961\fP .fi .sp .TP .B Example Set appearance to Mouse .nf \fB> appearance 962\fP .fi .sp .TP .B Example Set appearance to Joystick .nf \fB> appearance 963\fP .fi .sp .TP .B Example Set appearance to Generic Health Sensor .nf \fB> appearance 1344\fP .fi .sp .TP .B Example Set appearance to Heart Rate Sensor .nf \fB> appearance 1345\fP .fi .sp .TP .B Example Set appearance to Blood Pressure Monitor .nf \fB> appearance 1346\fP .fi .sp .TP .B Example Set appearance to Generic Sports and Fitness .nf \fB> appearance 1472\fP .fi .sp .TP .B Example Set appearance to Location Display .nf \fB> appearance 1473\fP .fi .sp .TP .B Example Set appearance to Location Navigation Display .nf \fB> appearance 1474\fP .fi .sp .UNINDENT .SS phy .sp Get/Set PHY Configuration .INDENT 0.0 .TP .B Usage \fB> phy [LE1MTX] [LE1MRX] [LE2MTX] [LE2MRX] [LECODEDTX] [LECODEDRX] [BR1M1SLOT] [BR1M3SLOT] [BR1M5SLOT][EDR2M1SLOT] [EDR2M3SLOT] [EDR2M5SLOT][EDR3M1SLOT] [EDR3M3SLOT] [EDR3M5SLOT]\fP .TP .B [PHY flags] PHY configuration flags to enable (LE1MTX, LE1MRX, LE2MTX, LE2MRX, LECODEDTX, LECODEDRX, BR1M1SLOT, etc.) .TP .B Example Display current PHY configuration .nf \fB> phy\fP .fi .sp .TP .B Example Enable LE 1M TX PHY .nf \fB> phy LE1MTX\fP .fi .sp .TP .B Example Enable LE 1M RX PHY .nf \fB> phy LE1MRX\fP .fi .sp .TP .B Example Enable LE 2M TX PHY .nf \fB> phy LE2MTX\fP .fi .sp .TP .B Example Enable LE 2M RX PHY .nf \fB> phy LE2MRX\fP .fi .sp .TP .B Example Enable LE Coded TX PHY .nf \fB> phy LECODEDTX\fP .fi .sp .TP .B Example Enable LE Coded RX PHY .nf \fB> phy LECODEDRX\fP .fi .sp .TP .B Example Enable BR 1M 1\-slot packets .nf \fB> phy BR1M1SLOT\fP .fi .sp .TP .B Example Enable BR 1M 3\-slot packets .nf \fB> phy BR1M3SLOT\fP .fi .sp .TP .B Example Enable BR 1M 5\-slot packets .nf \fB> phy BR1M5SLOT\fP .fi .sp .TP .B Example Enable EDR 2M 1\-slot packets .nf \fB> phy EDR2M1SLOT\fP .fi .sp .TP .B Example Enable EDR 2M 3\-slot packets .nf \fB> phy EDR2M3SLOT\fP .fi .sp .TP .B Example Enable EDR 2M 5\-slot packets .nf \fB> phy EDR2M5SLOT\fP .fi .sp .TP .B Example Enable EDR 3M 1\-slot packets .nf \fB> phy EDR3M1SLOT\fP .fi .sp .TP .B Example Enable EDR 3M 3\-slot packets .nf \fB> phy EDR3M3SLOT\fP .fi .sp .TP .B Example Enable EDR 3M 5\-slot packets .nf \fB> phy EDR3M5SLOT\fP .fi .sp .TP .B Example Enable LE 1M TX and RX PHYs .nf \fB> phy LE1MTX LE1MRX\fP .fi .sp .TP .B Example Enable LE 1M and 2M TX/RX PHYs .nf \fB> phy LE1MTX LE1MRX LE2MTX LE2MRX\fP .fi .sp .TP .B Example Enable 1\-slot packets for all BR/EDR PHYs .nf \fB> phy BR1M1SLOT EDR2M1SLOT EDR3M1SLOT\fP .fi .sp .UNINDENT .SS wbs .sp Toggle Wideband\-Speech support .INDENT 0.0 .TP .B Usage \fB> wbs \fP .TP .B Wideband Speech support \- \(dqon\(dq to enable for audio profiles, \(dqoff\(dq to disable .TP .B Example Enable Wideband Speech support for audio profiles .nf \fB> wbs on\fP .fi .sp .TP .B Example Disable Wideband Speech support .nf \fB> wbs off\fP .fi .sp .UNINDENT .SS secinfo .sp Show security information .INDENT 0.0 .TP .B Usage \fB> secinfo\fP .TP .B Example Display security features and capabilities .nf \fB> secinfo\fP .fi .sp .UNINDENT .SS expinfo .sp Show experimental features .INDENT 0.0 .TP .B Usage \fB> expinfo\fP .TP .B Example Display available experimental features and their status .nf \fB> expinfo\fP .fi .sp .UNINDENT .SS exp\-debug .sp Set debug feature .INDENT 0.0 .TP .B Usage \fB> exp\-debug \fP .TP .B Experimental debug features \- \(dqon\(dq to enable, \(dqoff\(dq to disable .TP .B Example Enable experimental debug features .nf \fB> exp\-debug on\fP .fi .sp .TP .B Example Disable experimental debug features .nf \fB> exp\-debug off\fP .fi .sp .UNINDENT .SS exp\-privacy .sp Set LL privacy feature .INDENT 0.0 .TP .B Usage \fB> exp\-privacy \fP .TP .B Experimental Link Layer privacy \- \(dqon\(dq to enable LL privacy features, \(dqoff\(dq to disable .TP .B Example Enable experimental Link Layer privacy features .nf \fB> exp\-privacy on\fP .fi .sp .TP .B Example Disable experimental Link Layer privacy features .nf \fB> exp\-privacy off\fP .fi .sp .UNINDENT .SS exp\-quality .sp Set bluetooth quality report feature .INDENT 0.0 .TP .B Usage \fB> exp\-quality \fP .TP .B Experimental quality reporting \- \(dqon\(dq to enable Bluetooth quality reports, \(dqoff\(dq to disable .TP .B Example Enable experimental Bluetooth quality reporting .nf \fB> exp\-quality on\fP .fi .sp .TP .B Example Disable experimental Bluetooth quality reporting .nf \fB> exp\-quality off\fP .fi .sp .UNINDENT .SS exp\-offload .sp Toggle codec support .INDENT 0.0 .TP .B Usage \fB> exp\-offload \fP .TP .B Experimental codec offload \- \(dqon\(dq to enable codec offload support, \(dqoff\(dq to disable .TP .B Example Enable experimental codec offload support .nf \fB> exp\-offload on\fP .fi .sp .TP .B Example Disable experimental codec offload support .nf \fB> exp\-offload off\fP .fi .sp .UNINDENT .SS exp\-iso .sp Toggle ISO Socket support .INDENT 0.0 .TP .B Usage \fB> exp\-iso \fP .TP .B Experimental ISO Socket support \- \(dqon\(dq to enable ISO Socket support, \(dqoff\(dq to disable .TP .B Example Enable experimental ISO Socket support .nf \fB> exp\-offload on\fP .fi .sp .TP .B Example Disable experimental ISO Socket support .nf \fB> exp\-offload off\fP .fi .sp .UNINDENT .SS read\-sysconfig .sp Read System Configuration .INDENT 0.0 .TP .B Usage \fB> read\-sysconfig\fP .TP .B Example Read current system configuration parameters .nf \fB> read\-sysconfig\fP .fi .sp .UNINDENT .SS set\-sysconfig .sp Set System Configuration .INDENT 0.0 .TP .B Usage \fB> set\-sysconfig <\-v|\-h> [options...]\fP .TP .B <\-v|\-h> Verbose output or help flag .TP .B [options...] System configuration options (\-\-br\-page\-scan\-type, \-\-br\-page\-scan\-interval, etc.) .TP .B Example Show help for system configuration options .nf \fB> set\-sysconfig \-h\fP .fi .sp .TP .B Example Set system configuration with verbose output .nf \fB> set\-sysconfig \-v\fP .fi .sp .TP .B Example Set BR/EDR page scan type with verbose output .nf \fB> set\-sysconfig \-v \-\-br\-page\-scan\-type=0\fP .fi .sp .TP .B Example Set BR/EDR page scan interval .nf \fB> set\-sysconfig \-v \-\-br\-page\-scan\-interval=0x800\fP .fi .sp .TP .B Example Set BR/EDR page scan window .nf \fB> set\-sysconfig \-v \-\-br\-page\-scan\-window=0x12\fP .fi .sp .TP .B Example Set BR/EDR inquiry scan type .nf \fB> set\-sysconfig \-v \-\-br\-inquiry\-scan\-type=0\fP .fi .sp .TP .B Example Set BR/EDR link supervision timeout .nf \fB> set\-sysconfig \-v \-\-br\-link\-supervision\-timeout=0x2000\fP .fi .sp .TP .B Example Set LE minimum connection interval .nf \fB> set\-sysconfig \-v \-\-le\-min\-connection\-interval=0x18\fP .fi .sp .TP .B Example Set LE maximum connection interval .nf \fB> set\-sysconfig \-v \-\-le\-max\-connection\-interval=0x28\fP .fi .sp .TP .B Example Set multiple BR/EDR scan parameters .nf \fB> set\-sysconfig \-v \-\-br\-page\-scan\-type=0 \-\-br\-page\-scan\-interval=0x800 \-\-br\-page\-scan\-window=0x12\fP .fi .sp .TP .B Example Set multiple LE connection parameters .nf \fB> set\-sysconfig \-v \-\-le\-min\-connection\-interval=0x18 \-\-le\-max\-connection\-interval=0x28 \-\-le\-connection\-latency=0\fP .fi .sp .UNINDENT .SS get\-flags .sp Get device flags .INDENT 0.0 .TP .B Usage \fB> get\-flags [\-t type]
\fP .TP .B [\-t type] Address type (public, random, le_public, le_random) .TP .B
Bluetooth address of device to query flags for .TP .B Example Get device flags for device using default type .nf \fB> get\-flags 00:11:22:33:44:55\fP .fi .sp .TP .B Example Get flags for another device .nf \fB> get\-flags AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Get flags for public address device .nf \fB> get\-flags \-t public 00:11:22:33:44:55\fP .fi .sp .TP .B Example Get flags for random address device .nf \fB> get\-flags \-t random AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Get flags for LE public address device .nf \fB> get\-flags \-t le_public 11:22:33:44:55:66\fP .fi .sp .TP .B Example Get flags for LE random address device .nf \fB> get\-flags \-t le_random CC:DD:EE:FF:00:11\fP .fi .sp .UNINDENT .SS set\-flags .sp Set device flags .INDENT 0.0 .TP .B Usage \fB> set\-flags [\-f flags] [\-t type]
\fP .TP .B [\-f flags] Device flags bitmask in hexadecimal (0x01=remote wake, 0x02=privacy) .TP .B [\-t type] Address type (public, random, le_public, le_random) .TP .B
Bluetooth address of device to set flags for .TP .B Example Set default flags for device .nf \fB> set\-flags 00:11:22:33:44:55\fP .fi .sp .TP .B Example Set default flags for another device .nf \fB> set\-flags AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Set remote wake flag for device .nf \fB> set\-flags \-f 0x01 00:11:22:33:44:55\fP .fi .sp .TP .B Example Set device privacy flag .nf \fB> set\-flags \-f 0x02 AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Set both remote wake and privacy flags .nf \fB> set\-flags \-f 0x03 11:22:33:44:55:66\fP .fi .sp .TP .B Example Set remote wake for public address device .nf \fB> set\-flags \-f 0x01 \-t public 00:11:22:33:44:55\fP .fi .sp .TP .B Example Set privacy flag for LE public device .nf \fB> set\-flags \-f 0x02 \-t le_public 11:22:33:44:55:66\fP .fi .sp .TP .B Example Set multiple flags for LE random device .nf \fB> set\-flags \-f 0x03 \-t le_random CC:DD:EE:FF:00:11\fP .fi .sp .TP .B Example Clear all flags for device .nf \fB> set\-flags \-f 0x00 00:11:22:33:44:55\fP .fi .sp .TP .B Example Clear all flags for LE public device .nf \fB> set\-flags \-f 0x00 \-t le_public 11:22:33:44:55:66\fP .fi .sp .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/hciattach.10000644000000000000000000000005015221755501014435 xustar0020 atime=1783094081 20 ctime=1783094112 bluez-5.87/doc/hciattach.10000644000000000000000000001125615221755501014123 0ustar00rootroot'\" t .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "hciattach" "1" "Jan 22, 2002" "BlueZ" "Linux System Administration" .SH Name hciattach \- attach serial devices via UART HCI to BlueZ stack .SH SYNOPSIS .sp \fBhciattach\fP [\fIOPTIONS\fP] <\fItty\fP> <\fItype|id\fP> [\fIspeed\fP] [\fIflow\fP] [\fIsleep\fP] [\fIbdaddr\fP] .sp \fBhciattach\fP \-1 .SH DESCRIPTION .sp \fBhciattach(1)\fP is used to attach a serial UART to the Bluetooth stack as HCI transport interface. .SH OPTIONS .INDENT 0.0 .TP .B \-i Send break .TP .B \-n Don\(aqt detach from controlling terminal. .TP .B \-p Print the PID when detaching. .TP .BI \-t \ timeout Specify an initialization timeout. Default is 5 seconds. .TP .BI \-s \ speed Specify an initial speed instead of the hardware default. .TP .B \-l List all available configurations. .TP .B \-r Set the HCI device into raw mode. The kernel and bluetooth daemon will ignore it. .TP .B \-h\fP,\fB \-\-help Show help options .UNINDENT .SH ARGUMENTS .INDENT 0.0 .TP .B \fIshow\fP This specifies the serial device to attach. A leading /dev can be omitted. .sp Examples: \fB/dev/ttyS1 ttyS2\fP .TP .B \fItype|id\fP The \fItype\fP or \fIid\fP of the Bluetooth device that is to be attached, i.e. vendor or other device specific identifier. Currently supported types are .UNINDENT .TS box center; l|l. T{ \fItype\fP T} T{ Description T} _ T{ any T} T{ Unspecified HCI_UART interface, no vendor specific options T} _ T{ ericsson T} T{ Ericsson based modules T} _ T{ digi T} T{ Digianswer based cards T} _ T{ xircom T} T{ Xircom PCMCIA cards: Credit Card Adapter and Real Port Adapter T} _ T{ csr T} T{ CSR Casira serial adapter or BrainBoxes serial dongle (BL642) T} _ T{ bboxes T} T{ BrainBoxes PCMCIA card (BL620) T} _ T{ swave T} T{ Silicon Wave kits T} _ T{ bcsp T} T{ Serial adapters using CSR chips with BCSP serial protocol T} _ T{ ath3k T} T{ Atheros AR300x based serial Bluetooth device T} _ T{ intel T} T{ Intel Bluetooth device T} .TE .TS box center; l|l. T{ .nf Supported ID (manufacturer id, product id) .fi T} T{ Description T} _ T{ 0x0105, 0x080a T} T{ Xircom PCMCIA cards: Credit Card Adapter and Real Port Adapter T} _ T{ 0x0160, 0x0002 T} T{ BrainBoxes PCMCIA card (BL620) T} .TE .INDENT 0.0 .TP .B \fIspeed\fP The \fIspeed\fP specifies the UART speed to use. Baudrates higher than 115200bps require vendor specific initializations that are not implemented for all types of devices. In general the following speeds are supported: .sp Supported vendor devices are automatically initialised to their respective best settings. .UNINDENT .TS box center; l. T{ 9600 T} _ T{ 19200 T} _ T{ 38400 T} _ T{ 57600 T} _ T{ 115200 T} _ T{ 230400 T} _ T{ 460800 T} _ T{ 921600 T} .TE .INDENT 0.0 .TP .B \fIflow\fP If the \fIflow\fP is appended to the list of options then hardware flow control is forced on the serial link (\fBCRTSCTS\fP). All above mentioned device types have flow set by default. To force no flow control use \fInoflow\fP instead. .TP .B \fIsleep|nosleep\fP Enables hardware specific power management feature. If \fIsleep\fP is appended to the list of options then this feature is enabled. To disable this feature use \fInosleep\fP instead. All above mentioned device types have \fInosleep\fP set by default. .sp Note: This option will only be valid for hardware which support hardware specific power management enable option from host. .TP .B \fIbdaddr\fP The bdaddr specifies the Bluetooth Address to use. Some devices (like the STLC2500) do not store the Bluetooth address in hardware memory. Instead it must be uploaded during the initialization process. If this argument is specified, then the address will be used to initialize the device. Otherwise, a default address will be used. .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Author Maxim Krasnyansky , Nils Faerber .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/sdptool.10000644000000000000000000000005015221755511014172 xustar0020 atime=1783094089 20 ctime=1783094113 bluez-5.87/doc/sdptool.10000644000000000000000000000710115221755511013652 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "sdptool" "1" "" "BlueZ" "Linux System Administration" .SH Name sdptool \- control and interrogate SDP servers .SH SYNOPSIS .sp \fBsdptool\fP [\fIOPTIONS\fP] [\fICOMMAND\fP [\fIPARAMETERS\fP]] .SH DESCRIPTION .sp \fBsdptool(1)\fP provides the interface for performing SDP queries on Bluetooth devices, and administering a local SDP database. .SH COMMANDS .sp The following commands are available. In all cases \fBbdaddr\fP specifies the device to search or browse. If \fIlocal\fP is used for \fBbdaddr\fP, then the local SDP database is searched. .sp Services are identified and manipulated with a 4\-byte \fBrecord_handle\fP (NOT the service name). To find a service\(aqs \fBrecord_handle\fP, look for the \(dqService RecHandle\(dq line in the \fBsearch\fP or \fBbrowse\fP results .INDENT 0.0 .TP .B search [\-\-bdaddr bdaddr] [\-\-tree] [\-\-raw] [\-\-xml] service_name Search for services.. .sp Known service names are \fBDID\fP, \fBSP\fP, \fBDUN\fP, \fBLAN\fP, \fBFAX\fP, \fBOPUSH\fP, \fBFTP\fP, \fBHS\fP, \fBHF\fP, \fBHFAG\fP, \fBSAP\fP, \fBNAP\fP, \fBGN\fP, \fBPANU\fP, \fBHCRP\fP, \fBHID\fP, \fBCIP\fP, \fBA2SRC\fP, \fBA2SNK\fP, \fBAVRCT\fP, \fBAVRTG\fP, \fBUDIUE\fP, \fBUDITE\fP and \fBSYNCML\fP\&. .TP .B browse [\-\-tree] [\-\-raw] [\-\-xml] [bdaddr] Browse all available services on the device specified by a Bluetooth address as a parameter. .TP .B records [\-\-tree] [\-\-raw] [\-\-xml] bdaddr Retrieve all possible service records. .TP .B add [ \-\-handle=N \-\-channel=N ] Add a service to the local SDP database. .sp You can specify a handle for this record using the \fB\-\-handle\fP option. .sp You can specify a channel to add the service on using the \fB\-\-channel\fP option. .sp NOTE: Local adapters configuration will not be updated and this command should be used only for SDP testing. .TP .B del record_handle Remove a service from the local SDP database. .sp NOTE: Local adapters configuration will not be updated and this command should be used only for SDP testing. .TP .B get [\-\-tree] [\-\-raw] [\-\-xml] [\-\-bdaddr bdaddr] record_handle Retrieve a service from the local SDP database. .TP .B setattr record_handle attrib_id attrib_value Set or add an attribute to an SDP record. .TP .B setseq record_handle attrib_id attrib_values Set or add an attribute sequence to an SDP record. .UNINDENT .SH OPTIONS .INDENT 0.0 .TP .B \-\-help Displays help on using sdptool. .UNINDENT .SH EXAMPLES .INDENT 0.0 .INDENT 3.5 .sp .EX $ sdptool browse 00:80:98:24:15:6D $ sdptool browse local $ sdptool add DUN $ sdptool del 0x10000 .EE .UNINDENT .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Author Maxim Krasnyansky , Edd Dumbill .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/btsnoop-protocol.rst0000644000000000000000000000005015200607060016471 xustar0020 atime=1783092683 20 ctime=1783094112 bluez-5.87/doc/btsnoop-protocol.rst0000644000000000000000000001104515200607060016153 0ustar00rootroot======= btsnoop ======= ------------------------------------------- BTSnoop/Monitor protocol documentation ------------------------------------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: February 2026 :Manual section: 7 :Manual group: Linux System Administration SYNOPSIS ======== This document describes the BTSnoop/Monitor formats used to record and transport HCI/PHY traffic. The formats are used by tools such as btmon and the BlueZ btsnoop implementation. DESCRIPTION =========== Opcode definitions ------------------ The following opcodes are used by the BTSnoop/Monitor formats. The numeric values match the definitions in ``src/shared/btsnoop.h``. .. list-table:: BTSnoop opcodes :header-rows: 1 * - Name - Code (dec) - Code (hex) - Meaning * - BTSNOOP_OPCODE_NEW_INDEX - 0 - 0x0000 - New controller index (parameters: type, bus, bdaddr, name) * - BTSNOOP_OPCODE_DEL_INDEX - 1 - 0x0001 - Deleted controller index * - BTSNOOP_OPCODE_COMMAND_PKT - 2 - 0x0002 - HCI Command packet * - BTSNOOP_OPCODE_EVENT_PKT - 3 - 0x0003 - HCI Event packet * - BTSNOOP_OPCODE_ACL_TX_PKT - 4 - 0x0004 - Outgoing ACL packet * - BTSNOOP_OPCODE_ACL_RX_PKT - 5 - 0x0005 - Incoming ACL packet * - BTSNOOP_OPCODE_SCO_TX_PKT - 6 - 0x0006 - Outgoing SCO packet * - BTSNOOP_OPCODE_SCO_RX_PKT - 7 - 0x0007 - Incoming SCO packet * - BTSNOOP_OPCODE_OPEN_INDEX - 8 - 0x0008 - HCI transport for the specified controller opened * - BTSNOOP_OPCODE_CLOSE_INDEX - 9 - 0x0009 - HCI transport for the specified controller closed * - BTSNOOP_OPCODE_INDEX_INFO - 10 - 0x000a - Index information (parameters: bdaddr, manufacturer) * - BTSNOOP_OPCODE_VENDOR_DIAG - 11 - 0x000b - Vendor diagnostic information * - BTSNOOP_OPCODE_SYSTEM_NOTE - 12 - 0x000c - System note * - BTSNOOP_OPCODE_USER_LOGGING - 13 - 0x000d - User logging (parameters: priority, ident_len, ident) * - BTSNOOP_OPCODE_CTRL_OPEN - 14 - 0x000e - Control channel opened * - BTSNOOP_OPCODE_CTRL_CLOSE - 15 - 0x000f - Control channel closed * - BTSNOOP_OPCODE_CTRL_COMMAND - 16 - 0x0010 - Control command packet * - BTSNOOP_OPCODE_CTRL_EVENT - 17 - 0x0011 - Control event packet * - BTSNOOP_OPCODE_ISO_TX_PKT - 18 - 0x0012 - Outgoing ISO packet * - BTSNOOP_OPCODE_ISO_RX_PKT - 19 - 0x0013 - Incoming ISO packet New Index --------- Code: 0x0000 Parameters: - Type (1 octet) - Bus (1 octet) - BD_Addr (6 octets) - Name (8 octets) This opcode indicates that a new controller instance with a given index was added. With some transports (for example a single TTY device) the index is implicitly 0. Deleted Index ------------- Code: 0x0001 Indicates that the controller with a specific index was removed. TTY-based protocol ------------------ The TTY protocol used by btmon with the ``--tty`` option is a little-endian packet format. Each packet uses this header:: struct tty_hdr { uint16_t data_len; uint16_t opcode; uint8_t flags; uint8_t hdr_len; uint8_t ext_hdr[0]; } __attribute__ ((packed)); The payload starts at ``ext_hdr + hdr_len`` and has length ``data_len - 4 - hdr_len``. Extended header format ---------------------- Each extension field is encoded as:: struct { uint8_t type; uint8_t value[length]; }; Defined types: .. list-table:: Extended header types :header-rows: 1 * - Type - Length - Meaning * - 1 - 1 byte - Command drops (dropped HCI command packets) * - 2 - 1 byte - Event drops (dropped HCI event packets) * - 3 - 1 byte - ACL TX drops * - 4 - 1 byte - ACL RX drops * - 5 - 1 byte - SCO TX drops * - 6 - 1 byte - SCO RX drops * - 7 - 1 byte - Other drops * - 8 - 4 bytes - 32-bit timestamp (1/10th ms) The drops fields contain the number of packets the implementation had to drop since the last reported drop count. Extension fields must be sorted by increasing ``type`` so unknown types can be skipped and the payload location discovered. RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org SEE ALSO ======== btmon(1) bluez-5.87/doc/PaxHeaders/org.bluez.GattManager.50000644000000000000000000000005015221755342016614 xustar0020 atime=1783093986 20 ctime=1783094112 bluez-5.87/doc/org.bluez.GattManager.50000644000000000000000000001143315221755342016277 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.GattManager" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.GattManager \- BlueZ D-Bus GattManager API documentation .SH Description .sp GATT Manager allows external applications to register GATT services and profiles. .sp Registering a profile allows applications to subscribe to \fIremote/client\fP services. .sp Registering a service allows applications to publish a \fIlocal/server\fP GATT service, which then becomes available to remote devices. A GATT service is represented by a D\-Bus object hierarchy where the root node corresponds to a service and the child nodes represent characteristics and descriptors that belong to that service. Each node must implement one of \fBorg.bluez.GattService(5)\fP, \fBorg.bluez.GattCharacteristic(5)\fP or \fBorg.bluez.GattDescriptor(5)\fP interfaces, based on the attribute it represents. Each node must also implement the standard D\-Bus Properties interface to expose their properties. These objects collectively represent a GATT service definition. .sp To make service registration simple, \fBbluetoothd(8)\fP requires that all objects that belong to a GATT service be grouped under a D\-Bus Object Manager that solely manages the objects of that service. Hence, the standard DBus.ObjectManager interface must be available on the root service path. An example application hierarchy containing two separate GATT services may look like this: .INDENT 0.0 .INDENT 3.5 .sp .EX \-> /com/example | \- org.freedesktop.DBus.ObjectManager | \-> /com/example/service0 | | \- org.freedesktop.DBus.Properties | | \- org.bluez.GattService1 | | | \-> /com/example/service0/char0 | | \- org.freedesktop.DBus.Properties | | \- org.bluez.GattCharacteristic1 | | | \-> /com/example/service0/char1 | | \- org.freedesktop.DBus.Properties | | \- org.bluez.GattCharacteristic1 | | | \-> /com/example/service0/char1/desc0 | \- org.freedesktop.DBus.Properties | \- org.bluez.GattDescriptor1 | \-> /com/example/service1 | \- org.freedesktop.DBus.Properties | \- org.bluez.GattService1 | \-> /com/example/service1/char0 \- org.freedesktop.DBus.Properties \- org.bluez.GattCharacteristic1 .EE .UNINDENT .UNINDENT .sp When a service is registered, \fBbluetoothd(8)\fP will automatically obtain information about all objects using the service\(aqs Object Manager. Once a service has been registered, the objects of a service should not be removed. If \fBbluetoothd(8)\fP receives an InterfacesRemoved signal from a service\(aqs Object Manager, it will immediately unregister the service. Similarly, if the application disconnects from the bus, all of its registered services will be automatically unregistered. InterfacesAdded signals will be ignored. .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.GattManager1 .TP .B Object path [variable prefix]/{hci0,hci1,...} .TP .B Used by \fBbluetoothctl\-gatt(1)\fP .UNINDENT .SS Methods .SS void RegisterApplication(object application, dict options) .sp Registers a local GATT services hierarchy as described above (GATT Server) and/or GATT profiles (GATT Client). .sp The application object path together with the D\-Bus system bus connection ID define the identification of the application registering a GATT based service (\fBorg.bluez.GattService(5)\fP) and/or profile (\fBorg.bluez.GattProfile(5)\fP). .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.AlreadyExists .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > gatt.register\-application [UUID ...] .UNINDENT .SS void UnregisterApplication(object application) .sp This unregisters the services and/or profiles that has been previously registered using \fBRegisterApplication()\fP\&. The object path parameter must match the same value that has been used on registration. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.DoesNotExist .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > gatt.unregister\-application .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/bluetoothctl-player.10000644000000000000000000000005015221755462016515 xustar0020 atime=1783094066 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-player.10000644000000000000000000002125715221755462016205 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "bluetoothctl-player" "1" "November 2022" "BlueZ" "Linux System Administration" .SH Name bluetoothctl-player \- Media Player Submenu .SH SYNOPSIS .sp \fBbluetoothctl\fP [\-\-options] [player.commands] .sp This submenu controls media playback using the \fBorg.bluez.MediaPlayer(5)\fP interface. .SH Media Player Commands .SS list .sp List available players. .INDENT 0.0 .TP .B Usage \fB> list\fP .TP .B Example Display all available media players (local and remote) .nf \fB> list\fP .fi .sp .UNINDENT .SS show .sp Show player information. .INDENT 0.0 .TP .B Usage \fB> show [player]\fP .TP .B Uses \fBorg.bluez.MediaPlayer(5)\fP properties .TP .B [player] Media player path or identifier (optional, shows currently selected player if omitted) .TP .B Example Show information for currently selected player .nf \fB> show\fP .fi .sp .TP .B Example Show remote player information .nf \fB> show /org/bluez/hci0/dev_00_11_22_33_44_55/player0\fP .fi .sp .UNINDENT .SS select .sp Select default player. .INDENT 0.0 .TP .B Usage \fB> select \fP .TP .B Media player path or identifier to select as default .TP .B Example Select remote Bluetooth media player .nf \fB> select /org/bluez/hci0/dev_00_11_22_33_44_55/player0\fP .fi .sp .UNINDENT .SS play .sp Start playback. .INDENT 0.0 .TP .B Usage \fB> play [item]\fP .TP .B Uses \fBorg.bluez.MediaPlayer(5)\fP method \fBPlay\fP .TP .B [item] Media item path to play (optional, starts playback of current item if omitted) .TP .B Example Start playback on current player .nf \fB> play\fP .fi .sp .TP .B Example Play specific item from media browser .nf \fB> play /org/bluez/item/1\fP .fi .sp .TP .B Example Play named track item .nf \fB> play /org/bluez/item/track_001\fP .fi .sp .TP .B Example Play track from specific album folder .nf \fB> play /org/bluez/item/album_rock/track_05\fP .fi .sp .UNINDENT .SS pause .sp Pause playback. .INDENT 0.0 .TP .B Usage \fB> pause\fP .TP .B Uses \fBorg.bluez.MediaPlayer(5)\fP method \fBPause\fP .TP .B Example Pause current playback .nf \fB> pause\fP .fi .sp .UNINDENT .SS stop .sp Stop playback. .INDENT 0.0 .TP .B Usage \fB> stop\fP .TP .B Uses \fBorg.bluez.MediaPlayer(5)\fP method \fBStop\fP .TP .B Example Stop current playback .nf \fB> stop\fP .fi .sp .UNINDENT .SS next .sp Jump to next item. .INDENT 0.0 .TP .B Usage \fB> next\fP .TP .B Uses \fBorg.bluez.MediaPlayer(5)\fP method \fBNext\fP .TP .B Example Skip to next track .nf \fB> next\fP .fi .sp .UNINDENT .SS previous .sp Jump to previous item. .INDENT 0.0 .TP .B Usage \fB> previous\fP .TP .B Uses \fBorg.bluez.MediaPlayer(5)\fP method \fBPrevious\fP .TP .B Example Go back to previous track .nf \fB> previous\fP .fi .sp .UNINDENT .SS fast\-forward .sp Fast forward playback. .INDENT 0.0 .TP .B Usage \fB> fast\-forward\fP .TP .B Uses \fBorg.bluez.MediaPlayer(5)\fP method \fBFastForward\fP .TP .B Example Enable fast forward mode .nf \fB> fast\-forward\fP .fi .sp .UNINDENT .SS rewind .sp Rewind playback. .INDENT 0.0 .TP .B Usage \fB> rewind\fP .TP .B Uses \fBorg.bluez.MediaPlayer(5)\fP method \fBRewind\fP .TP .B Example Enable rewind mode .nf \fB> rewind\fP .fi .sp .UNINDENT .SS equalizer .sp Enable/Disable equalizer. .INDENT 0.0 .TP .B Usage \fB> equalizer \fP .TP .B Uses \fBorg.bluez.MediaPlayer(5)\fP property \fBEqualizer\fP .TP .B Enable (on) or disable (off) the equalizer .TP .B Example Enable equalizer .nf \fB> equalizer on\fP .fi .sp .TP .B Example Disable equalizer .nf \fB> equalizer off\fP .fi .sp .UNINDENT .SS repeat .sp Set repeat mode. .INDENT 0.0 .TP .B Usage \fB> repeat \fP .TP .B Uses \fBorg.bluez.MediaPlayer(5)\fP property \fBRepeat\fP .TP .B Set repeat mode \- singletrack (current track), alltrack (all tracks), group (current group/album), or off (no repeat) .TP .B Example Disable repeat mode .nf \fB> repeat off\fP .fi .sp .TP .B Example Repeat current track .nf \fB> repeat singletrack\fP .fi .sp .TP .B Example Repeat entire playlist/queue .nf \fB> repeat alltrack\fP .fi .sp .TP .B Example Repeat current group/album .nf \fB> repeat group\fP .fi .sp .UNINDENT .SS shuffle .sp Set shuffle mode. .INDENT 0.0 .TP .B Usage \fB> shuffle \fP .TP .B Uses \fBorg.bluez.MediaPlayer(5)\fP property \fBShuffle\fP .TP .B Set shuffle mode \- alltracks (shuffle all tracks), group (shuffle within current group), or off (no shuffle) .TP .B Example Disable shuffle mode .nf \fB> shuffle off\fP .fi .sp .TP .B Example Shuffle all tracks in playlist .nf \fB> shuffle alltracks\fP .fi .sp .TP .B Example Shuffle tracks within current group .nf \fB> shuffle group\fP .fi .sp .UNINDENT .SS scan .sp Set scan mode. .INDENT 0.0 .TP .B Usage \fB> scan \fP .TP .B Uses \fBorg.bluez.MediaPlayer(5)\fP property \fBScan\fP .TP .B Set scan mode \- alltracks (scan through all tracks), group (scan within current group), or off (no scan) .TP .B Example Disable scan mode .nf \fB> scan off\fP .fi .sp .TP .B Example Scan through all tracks .nf \fB> scan alltracks\fP .fi .sp .TP .B Example Scan through current group only .nf \fB> scan group\fP .fi .sp .UNINDENT .SS change\-folder .sp Change current folder. .INDENT 0.0 .TP .B Usage \fB> change\-folder \fP .TP .B Folder path to navigate to, or \(dq..\(dq to go up one directory level .TP .B Example Navigate to Albums folder .nf \fB> change\-folder /org/bluez/item/Albums\fP .fi .sp .TP .B Example Navigate to Artists folder .nf \fB> change\-folder /org/bluez/item/Artists\fP .fi .sp .TP .B Example Navigate to Playlists folder .nf \fB> change\-folder /org/bluez/item/Playlists\fP .fi .sp .TP .B Example Go up one directory level .nf \fB> change\-folder ..\fP .fi .sp .UNINDENT .SS list\-items .sp List items of current folder. .INDENT 0.0 .TP .B Usage \fB> list\-items [start] [end]\fP .TP .B [start] Starting index for item list (optional, defaults to 0) .TP .B [end] Ending index for item list (optional, lists all items from start if omitted) .TP .B Example List all items in current folder .nf \fB> list\-items\fP .fi .sp .TP .B Example List first 10 items (0\-10) .nf \fB> list\-items 0 10\fP .fi .sp .TP .B Example List items 5 through 15 .nf \fB> list\-items 5 15\fP .fi .sp .TP .B Example List items 10 through 20 .nf \fB> list\-items 10 20\fP .fi .sp .TP .B Example List first 50 items .nf \fB> list\-items 0 50\fP .fi .sp .UNINDENT .SS search .sp Search items containing string. .INDENT 0.0 .TP .B Usage \fB> search \fP .TP .B Search term to find matching items (songs, albums, artists, etc.) .TP .B Example Search for Beatles songs/albums .nf \fB> search \(dqThe Beatles\(dq\fP .fi .sp .TP .B Example Search for items containing \(dqrock\(dq .nf \fB> search \(dqrock\(dq\fP .fi .sp .TP .B Example Search for items from 2023 .nf \fB> search \(dq2023\(dq\fP .fi .sp .TP .B Example Search for playlists .nf \fB> search \(dqplaylist\(dq\fP .fi .sp .UNINDENT .SS queue .sp Add item to playlist queue. .INDENT 0.0 .TP .B Usage \fB> queue \fP .TP .B Media item path to add to the playback queue .TP .B Example Add specific track to queue .nf \fB> queue /org/bluez/item/track_001\fP .fi .sp .TP .B Example Add entire album to queue .nf \fB> queue /org/bluez/item/album_rock\fP .fi .sp .TP .B Example Add playlist to queue .nf \fB> queue /org/bluez/item/playlist_favorites\fP .fi .sp .UNINDENT .SS show\-item .sp Show item information. .INDENT 0.0 .TP .B Usage \fB> show\-item \fP .TP .B Media item path to display detailed information for .TP .B Example Show details of specific track .nf \fB> show\-item /org/bluez/item/track_001\fP .fi .sp .TP .B Example Show details of album .nf \fB> show\-item /org/bluez/item/album_rock\fP .fi .sp .TP .B Example Show details of artist .nf \fB> show\-item /org/bluez/item/artist_beatles\fP .fi .sp .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/pts-opp.txt0000644000000000000000000000005012572170650014570 xustar0020 atime=1770587296 20 ctime=1783094112 bluez-5.87/doc/pts-opp.txt0000644000000000000000000000631712572170650014260 0ustar00rootrootPTS test results for OPP PTS version: 6.2.0 Tested: 26-Aug-2015 Results: PASS test passed FAIL test failed INC test is inconclusive N/A test is disabled due to PICS setup NONE test result is none ------------------------------------------------------------------------------- Test Name Result Notes ------------------------------------------------------------------------------- TC_CLIENT_BC_BV_02_I PASS TC_CLIENT_BC_BV_04_I PASS TC_CLIENT_BCE_BV_01_I PASS TC_CLIENT_BCE_BV_03_I N/A TC_CLIENT_BCE_BV_04_I PASS TC_CLIENT_BCE_BV_05_I PASS TC_CLIENT_BCE_BV_06_I PASS TC_CLIENT_BCE_BV_07_I PASS TC_CLIENT_BCP_BV_01_I PASS TC_CLIENT_BCP_BV_02_I PASS TC_CLIENT_BCP_BV_03_I N/A TC_CLIENT_BCP_BV_04_I PASS TC_CLIENT_BCP_BV_05_I PASS TC_CLIENT_CON_BV_01_C PASS TC_CLIENT_OPH_BI_01_C PASS TC_CLIENT_OPH_BV_01_I PASS TC_CLIENT_OPH_BV_02_I N/A TC_CLIENT_OPH_BV_03_I PASS TC_CLIENT_OPH_BV_04_I PASS TC_CLIENT_OPH_BV_05_I PASS TC_CLIENT_OPH_BV_07_I N/A TC_CLIENT_OPH_BV_08_I PASS TC_CLIENT_OPH_BV_09_I N/A TC_CLIENT_OPH_BV_10_I N/A TC_CLIENT_OPH_BV_11_I N/A TC_CLIENT_OPH_BV_12_I PASS TC_CLIENT_OPH_BV_13_I N/A TC_CLIENT_OPH_BV_14_I N/A TC_CLIENT_OPH_BV_15_I N/A TC_CLIENT_OPH_BV_16_I PASS TC_CLIENT_OPH_BV_17_I N/A TC_CLIENT_OPH_BV_18_I N/A TC_CLIENT_OPH_BV_19_I PASS Send file other than vCard TC_CLIENT_OPH_BV_20_I PASS TC_CLIENT_OPH_BV_22_I PASS Send file greater than 2 MB TC_CLIENT_OPH_BV_23_I N/A TC_CLIENT_OPH_BV_24_I N/A TC_CLIENT_OPH_BV_25_I N/A TC_CLIENT_OPH_BV_26_I N/A TC_CLIENT_SRM_BV_01_C N/A TC_CLIENT_SRM_BV_03_C N/A TC_CLIENT_SRM_BV_05_C N/A TC_CLIENT_SRM_BV_07_C N/A TC_CLIENT_SRMP_BI_01_C N/A TC_CLIENT_SRMP_BV_01_C N/A TC_CLIENT_SRMP_BV_04_C N/A TC_CLIENT_SRMP_BV_05_C N/A TC_CLIENT_SRMP_BV_06_C N/A TC_SERVER_BC_BV_01_I PASS TC_SERVER_BC_BV_03_I PASS TC_SERVER_BCE_BV_01_I PASS TC_SERVER_BCE_BV_03_I PASS TC_SERVER_BCE_BV_04_I PASS TC_SERVER_BCE_BV_05_I PASS TC_SERVER_BCE_BV_06_I PASS TC_SERVER_BCE_BV_07_I PASS TC_SERVER_BCP_BV_01_I PASS TC_SERVER_BCP_BV_02_I N/A TC_SERVER_BCP_BV_03_I PASS TC_SERVER_BCP_BV_04_I PASS TC_SERVER_BCP_BV_05_I PASS TC_SERVER_CON_BV_02_C PASS TC_SERVER_OPH_BV_01_I PASS TC_SERVER_OPH_BV_02_I PASS TC_SERVER_OPH_BV_03_I PASS TC_SERVER_OPH_BV_04_I PASS TC_SERVER_OPH_BV_05_I PASS TC_SERVER_OPH_BV_07_I N/A TC_SERVER_OPH_BV_08_I N/A TC_SERVER_OPH_BV_09_I PASS TC_SERVER_OPH_BV_10_I PASS TC_SERVER_OPH_BV_11_I N/A TC_SERVER_OPH_BV_12_I N/A TC_SERVER_OPH_BV_13_I PASS TC_SERVER_OPH_BV_14_I PASS TC_SERVER_OPH_BV_15_I N/A TC_SERVER_OPH_BV_16_I N/A TC_SERVER_OPH_BV_17_I PASS TC_SERVER_OPH_BV_18_I PASS TC_SERVER_OPH_BV_19_I PASS TC_SERVER_OPH_BV_21_I N/A TC_SERVER_OPH_BV_22_I PASS TC_SERVER_OPH_BV_23_I PASS TC_SERVER_OPH_BV_24_I N/A TC_SERVER_OPH_BV_25_I N/A TC_SERVER_OPH_BV_26_I N/A TC_SERVER_ROB_BV_01_C PASS TC_SERVER_ROB_BV_02_C PASS TC_SERVER_SRM_BI_02_C N/A TC_SERVER_SRM_BI_03_C PASS TC_SERVER_SRM_BI_05_C N/A TC_SERVER_SRM_BV_04_C N/A TC_SERVER_SRM_BV_08_C N/A TC_SERVER_SRMP_BV_02_C N/A TC_SERVER_SRMP_BV_03_C N/A TC_CLIENT_OPH_BV_27_I N/A TC_CLIENT_OPH_BV_34_I PASS TC_SERVER_OPH_BV_27_I N/A TC_SERVER_OPH_BV_30_I PASS TC_SERVER_OPH_BV_31_I PASS TC_SERVER_OPH_BV_32_I PASS TC_SERVER_OPH_BV_33_I N/A TC_SERVER_OPH_BV_34_I PASS ------------------------------------------------------------------------------- bluez-5.87/doc/PaxHeaders/org.bluez.Call.rst0000644000000000000000000000005015142177620015740 xustar0020 atime=1783094028 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Call.rst0000644000000000000000000000774415142177620015435 0ustar00rootroot=============== org.bluez.Call1 =============== -------------------------------------------- BlueZ D-Bus Telephony Call API documentation -------------------------------------------- :Version: BlueZ :Date: May 2025 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.Call1 [experimental] :Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/telephony#/call# :Used by: **bluetoothctl-telephony(1)** Methods ------- void Answer() ````````````` Answers an incoming call. Only valid if the state of the call is "incoming". Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed Examples: :bluetoothctl: > telephony.answer void Hangup() ````````````` Hangs up the call. For an incoming call, the call is hung up using ATH or equivalent. For a waiting call, the remote party is notified by using the User Determined User Busy (UDUB) condition. This is generally implemented using CHLD=0. Please note that the GSM specification does not allow the release of a held call when a waiting call exists. This is because 27.007 allows CHLD=1X to operate only on active calls. Hence a held call cannot be hung up without affecting the state of the incoming call (e.g. using other CHLD alternatives). Most manufacturers provide vendor extensions that do allow the state of the held call to be modified using CHLD=1X or equivalent. It should be noted that Bluetooth HFP specifies the classic 27.007 behavior and does not allow CHLD=1X to modify the state of held calls. Based on the discussion above, it should also be noted that releasing a particular party of a held multiparty call might not be possible on some implementations. It is recommended for the applications to structure their UI accordingly. NOTE: Releasing active calls does not produce side-effects. That is the state of held or waiting calls is not affected. As an exception, in the case where a single active call and a waiting call are present, releasing the active call will result in the waiting call transitioning to the 'incoming' state. Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed Examples: :bluetoothctl: > telephony.hangup Properties ---------- string LineIdentification [readonly] ```````````````````````````````````` Contains the Line Identification information returned by the network, if present. For incoming calls this is effectively the CLIP. For outgoing calls this attribute will hold the dialed number, or the COLP if received by the audio gateway. Please note that COLP may be different from the dialed number. A special "withheld" value means the remote party refused to provide caller ID and the "override category" option was not provisioned for the current subscriber. string IncomingLine [readonly, optional] ```````````````````````````````````````` Contains the Called Line Identification information returned by the network. This is only available for incoming calls and indicates the local subscriber number which was dialed by the remote party. This is useful for subscribers which have a multiple line service with their network provider and would like to know what line the call is coming in on. string Name [readonly] `````````````````````` Contains the Name Identification information returned by the network, if present. boolean Multiparty [readonly] ````````````````````````````` Contains the indication if the call is part of a multiparty call or not. Notifications if a call becomes part or leaves a multiparty call are sent. string State [readonly] ``````````````````````` Contains the state of the current call. Possible values: :"active": The call is active :"held": The call is on hold :"dialing": The call is being dialed :"alerting": The remote party is being alerted :"incoming": Incoming call in progress :"waiting": Call is waiting :"response_and_hold": Incoming call has been set on hold :"disconnected": No further use of this object is allowed, it will be destroyed shortly bluez-5.87/doc/PaxHeaders/bluetoothctl.10000644000000000000000000000005015221755443015222 xustar0020 atime=1783094051 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl.10000644000000000000000000005162015221755443014707 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "bluetoothctl" "1" "March 2024" "BlueZ" "Linux System Administration" .SH Name bluetoothctl \- Bluetooth Control Command Line Tool .SH SYNOPSIS .sp \fBbluetoothctl\fP [\fB\-a\fP \fIcapability\fP] [\fB\-e\fP] [\fB\-m\fP] [\fB\-t\fP \fIseconds\fP] [\fB\-v\fP] [\fB\-h\fP] .SH DESCRIPTION .sp \fBbluetoothctl(1)\fP interactive bluetooth control tool. The tool works with Bluetooth Classic (BR/EDR) and Bluetooth Low Energy (LE) controllers. .sp The tool is menu driven but can be automated from the command line. Examples are given in the automation section. .sp This tool communicates with the BlueZ daemon via D\-Bus, primarily using the \fBorg.bluez.Adapter(5)\fP and \fBorg.bluez.Device(5)\fP interfaces. Each command documents which D\-Bus interface and method/property it uses. .SH OPTIONS .INDENT 0.0 .TP .BI \-a \ capability\fR,\fB \ \-\-agent \ capability Register agent handler: .TP .B \-e\fP,\fB \-\-endpoints Register Media endpoints .TP .B \-m\fP,\fB \-\-monitor Enable monitor output .TP .BI \-t \ seconds\fR,\fB \ \-\-timeout \ seconds Timeout in seconds for non\-interactive mode .TP .B \-v\fP,\fB \-\-version Display version .TP .B \-h\fP,\fB \-\-help Display help .UNINDENT .SH Commands .SS list .sp List available controllers. .INDENT 0.0 .TP .B Usage \fB> list\fP .TP .B Example Display all Bluetooth controllers available on the system .nf \fB> list\fP .fi .sp .UNINDENT .SS show .sp Controller information. .INDENT 0.0 .TP .B Usage \fB> show [ctrl]\fP .TP .B Uses \fBorg.bluez.Adapter(5)\fP properties .TP .B [ctrl] Bluetooth controller address .TP .B Example Display information for currently selected controller .nf \fB> show\fP .fi .sp .TP .B Example Show information for controller 00:11:22:33:44:55 .nf \fB> show 00:11:22:33:44:55\fP .fi .sp .UNINDENT .SS select .sp Select default controller. .INDENT 0.0 .TP .B Usage \fB> select \fP .TP .B Bluetooth controller address .TP .B Example Select controller 00:11:22:33:44:55 .nf \fB> select 00:11:22:33:44:55\fP .fi .sp .UNINDENT .SS devices .sp List available devices, with an optional property as the filter. .INDENT 0.0 .TP .B Usage \fB> devices [Paired/Bonded/Trusted/Connected]\fP .TP .B [Paired/Bonded/Trusted/Connected] Filter to show only devices with specific property (optional) .TP .B Example List all discovered devices .nf \fB> devices\fP .fi .sp .TP .B Example List only paired devices .nf \fB> devices Paired\fP .fi .sp .TP .B Example List only bonded devices .nf \fB> devices Bonded\fP .fi .sp .TP .B Example List only trusted devices .nf \fB> devices Trusted\fP .fi .sp .TP .B Example List only currently connected devices .nf \fB> devices Connected\fP .fi .sp .UNINDENT .SS system\-alias .sp Set controller alias. .INDENT 0.0 .TP .B Usage \fB> system\-alias \fP .TP .B Uses \fBorg.bluez.Adapter(5)\fP property \fBAlias\fP .TP .B New alias name for the Bluetooth controller (required) .TP .B Example Set controller alias with spaces (note quotes) .nf \fB> system\-alias \(dqMy Desktop Bluetooth\(dq\fP .fi .sp .TP .B Example Set controller alias without spaces .nf \fB> system\-alias MyLaptop\fP .fi .sp .TP .B Example Set controller alias with hyphens .nf \fB> system\-alias \(dqHome\-Office\-PC\(dq\fP .fi .sp .UNINDENT .SS reset\-alias .sp Reset controller alias. .INDENT 0.0 .TP .B Usage \fB> reset\-alias\fP .TP .B Uses \fBorg.bluez.Adapter(5)\fP property \fBAlias\fP .TP .B Example Reset controller alias to default (usually the hostname) .nf \fB> reset\-alias\fP .fi .sp .UNINDENT .SS power .sp Set controller power. .sp When the controller is powered off, the USB port the controller is attached to is put into a suspend state. .INDENT 0.0 .TP .B Usage \fB> power \fP .TP .B Uses \fBorg.bluez.Adapter(5)\fP property \fBPowered\fP .TP .B Power state \- \(dqon\(dq to enable or \(dqoff\(dq to disable the controller (required) .TP .B Example Turn on the Bluetooth controller .nf \fB> power on\fP .fi .sp .TP .B Example Turn off the Bluetooth controller .nf \fB> power off\fP .fi .sp .UNINDENT .SS advertise .sp Enable/disable advertising with given type. .sp When the controller advertises an LE device (peripheral) it will generate a random address for its own privacy if the controller is capable of it, it will use its public address if it does not support the feature (address of the device). .sp A device can advertise if it initiated the connection to another advertising device. .INDENT 0.0 .TP .B Usage \fB> advertise \fP .TP .B Uses \fBorg.bluez.LEAdvertisingManager(5)\fP method \fBRegisterAdvertisement\fP .TP .B Advertising mode \- \(dqon\(dq, \(dqoff\(dq, \(dqperipheral\(dq, or \(dqbroadcast\(dq (required) .TP .B Example Enable basic LE advertising .nf \fB> advertise on\fP .fi .sp .TP .B Example Disable LE advertising .nf \fB> advertise off\fP .fi .sp .TP .B Example Advertise as peripheral device .nf \fB> advertise peripheral\fP .fi .sp .TP .B Example Advertise as broadcast source .nf \fB> advertise broadcast\fP .fi .sp .UNINDENT .SS set\-alias .sp Set device alias. .INDENT 0.0 .TP .B Usage \fB> set\-alias \fP .TP .B Uses \fBorg.bluez.Device(5)\fP property \fBAlias\fP .TP .B New alias name for the device (required) .TP .B Example Set device alias with spaces (note quotes) .nf \fB> set\-alias \(dqMy Headphones\(dq\fP .fi .sp .TP .B Example Set device alias without spaces .nf \fB> set\-alias MyMouse\fP .fi .sp .TP .B Example Set device alias with hyphens .nf \fB> set\-alias \(dqKitchen\-Speaker\(dq\fP .fi .sp .UNINDENT .SS scan .sp Scan for devices. .sp For LE, scanning is an important requirement before connecting or pairing. .sp The purpose of scanning is to find devices that are advertising with their discoverable flag set (either limited or general). Once you have found the address then you can connect or pair. .INDENT 0.0 .TP .B Usage \fB> scan \fP .TP .B Uses \fBorg.bluez.Adapter(5)\fP methods \fBStartDiscovery\fP, \fBStopDiscovery\fP .TP .B Scan mode \- \(dqon\(dq, \(dqoff\(dq, \(dqbredr\(dq for Classic, or \(dqle\(dq for Low Energy (required) .TP .B Example Start scanning for all device types (LE + Classic) .nf \fB> scan on\fP .fi .sp .TP .B Example Stop scanning .nf \fB> scan off\fP .fi .sp .TP .B Example Scan for Low Energy devices only .nf \fB> scan le\fP .fi .sp .TP .B Example Scan for BR/EDR (Classic) devices only .nf \fB> scan bredr\fP .fi .sp .UNINDENT .SS pair .sp Pair with device. .sp This will pair with a device and then trust and connect to it. If the device is already paired this will first remove the pairing. .sp The command can either be used while the controller is in the connected or not connected state. .sp If the controller is already connected then the pair command can be used without an arguments. If the controller is not connected, the pair command can be given the address of a device with an active scan report and it will initiate the connection before pairing. .sp Before pairing the agent must be selected to choose the authentication mechanism. .INDENT 0.0 .TP .B Usage \fB> pair [dev]\fP .TP .B Uses \fBorg.bluez.Device(5)\fP method \fBPair\fP .TP .B [dev] Device MAC address (XX:XX:XX:XX:XX:XX) (optional, uses current device if omitted) .TP .B Example Pair with device using MAC address .nf \fB> pair 00:11:22:33:44:55\fP .fi .sp .TP .B Example Pair with another device .nf \fB> pair AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Pair using device object path .nf \fB> pair /org/bluez/hci0/dev_00_11_22_33_44_55\fP .fi .sp .UNINDENT .SS pairable .sp Set controller pairable mode. .sp This enables/disables pairing. If pairing is disabled then the controller will not accept any pairing requests. .INDENT 0.0 .TP .B Usage \fB> pairable \fP .TP .B Uses \fBorg.bluez.Adapter(5)\fP property \fBPairable\fP .TP .B Pairable mode \- \(dqon\(dq to accept or \(dqoff\(dq to reject pairing requests (required) .TP .B Example Enable pairing (accept pairing requests) .nf \fB> pairable on\fP .fi .sp .TP .B Example Disable pairing (reject pairing requests) .nf \fB> pairable off\fP .fi .sp .UNINDENT .SS discoverable .sp Set discoverable mode. .sp This enables/disables discoverable mode. If discoverable is disabled then the controller will not respond to any scan requests. .sp In LE if discoverable if off the controller will just passively scan and not make scan requests to advertising devices. If on it will make the advertising requests. .sp It will use a random address if supported by the controller. The length of time \(dqdiscoverable on\(dq is valid is determined by discoverable\-timeout command. .INDENT 0.0 .TP .B Usage \fB> discoverable \fP .TP .B Uses \fBorg.bluez.Adapter(5)\fP property \fBDiscoverable\fP .TP .B Discoverable mode \- \(dqon\(dq to be discoverable or \(dqoff\(dq to be non\-discoverable (required) .TP .B Example Make controller discoverable to other devices .nf \fB> discoverable on\fP .fi .sp .TP .B Example Make controller non\-discoverable .nf \fB> discoverable off\fP .fi .sp .UNINDENT .SS discoverable\-timeout .sp Set discoverable timeout. .sp The time in seconds that \(dqdiscoverable on\(dq is valid. .INDENT 0.0 .TP .B Usage \fB> discoverable\-timeout [value]\fP .TP .B Uses \fBorg.bluez.Adapter(5)\fP property \fBDiscoverableTimeout\fP .TP .B [value] Timeout duration in seconds for discoverable mode (0 for unlimited, optional) .TP .B Example Show current discoverable timeout value .nf \fB> discoverable\-timeout\fP .fi .sp .TP .B Example Set discoverable timeout to 30 seconds .nf \fB> discoverable\-timeout 30\fP .fi .sp .TP .B Example Set discoverable timeout to 2 minutes .nf \fB> discoverable\-timeout 120\fP .fi .sp .TP .B Example Set discoverable timeout to 5 minutes .nf \fB> discoverable\-timeout 300\fP .fi .sp .TP .B Example Set unlimited discoverable timeout .nf \fB> discoverable\-timeout 0\fP .fi .sp .UNINDENT .SS agent .sp Enable/disable agent with given capability. .sp This chooses the local authentication mechanism of the controller. It is needed for pairing and allows you to choose the IO capabilities of the controller. .sp The valid agent capabilities are: DisplayOnly, DisplayYesNo, KeyboardDisplay, KeyboardOnly, NoInputNoOutput. .INDENT 0.0 .TP .B Usage \fB> agent \fP .TP .B Uses \fBorg.bluez.AgentManager(5)\fP method \fBRegisterAgent\fP .TP .B Agent mode \- \(dqon\(dq, \(dqoff\(dq, \(dqauto\(dq, or capability type (DisplayOnly, DisplayYesNo, KeyboardDisplay, KeyboardOnly, NoInputNoOutput) (required) .TP .B Example Enable agent with default capability .nf \fB> agent on\fP .fi .sp .TP .B Example Disable agent .nf \fB> agent off\fP .fi .sp .TP .B Example Enable agent that can only display pairing codes .nf \fB> agent DisplayOnly\fP .fi .sp .TP .B Example Enable agent that can display codes and accept/reject .nf \fB> agent DisplayYesNo\fP .fi .sp .TP .B Example Enable agent that can display and input pairing codes .nf \fB> agent KeyboardDisplay\fP .fi .sp .TP .B Example Enable agent that can only input pairing codes .nf \fB> agent KeyboardOnly\fP .fi .sp .TP .B Example Enable agent with no input/output (JustWorks) .nf \fB> agent NoInputNoOutput\fP .fi .sp .UNINDENT .SS default\-agent .sp Set current agent as the default one. .sp After selecting the agent this will make it the default agent. .INDENT 0.0 .TP .B Usage \fB> default\-agent\fP .TP .B Uses \fBorg.bluez.AgentManager(5)\fP method \fBRequestDefaultAgent\fP .TP .B Example Set the current agent as default .nf \fB> default\-agent\fP .fi .sp .UNINDENT .SS trust .sp Trust device. .INDENT 0.0 .TP .B Usage \fB> trust [dev]\fP .TP .B Uses \fBorg.bluez.Device(5)\fP property \fBTrusted\fP .TP .B [dev] Device MAC address (XX:XX:XX:XX:XX:XX) or object path (optional, uses current device if omitted) .TP .B Example Trust device using MAC address .nf \fB> trust 00:11:22:33:44:55\fP .fi .sp .TP .B Example Trust another device .nf \fB> trust AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Trust device using object path .nf \fB> trust /org/bluez/hci0/dev_00_11_22_33_44_55\fP .fi .sp .UNINDENT .SS untrust .sp Untrust device. .INDENT 0.0 .TP .B Usage \fB> untrust [dev]\fP .TP .B Uses \fBorg.bluez.Device(5)\fP property \fBTrusted\fP .TP .B [dev] Device MAC address (XX:XX:XX:XX:XX:XX) or object path (optional, uses current device if omitted) .TP .B Example Remove trust from device using MAC address .nf \fB> untrust 00:11:22:33:44:55\fP .fi .sp .TP .B Example Remove trust from another device .nf \fB> untrust AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Remove trust using object path .nf \fB> untrust /org/bluez/hci0/dev_00_11_22_33_44_55\fP .fi .sp .UNINDENT .SS block .sp Block device. .INDENT 0.0 .TP .B Usage \fB> block [dev]\fP .TP .B Uses \fBorg.bluez.Device(5)\fP property \fBBlocked\fP .TP .B [dev] Device MAC address (XX:XX:XX:XX:XX:XX) or object path (optional, uses current device if omitted) .TP .B Example Block device using MAC address .nf \fB> block 00:11:22:33:44:55\fP .fi .sp .TP .B Example Block another device .nf \fB> block AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Block device using object path .nf \fB> block /org/bluez/hci0/dev_00_11_22_33_44_55\fP .fi .sp .UNINDENT .SS unblock .sp Unblock device .INDENT 0.0 .TP .B Usage \fB> unblock [dev]\fP .TP .B Uses \fBorg.bluez.Device(5)\fP property \fBBlocked\fP .TP .B [dev] Device MAC address (XX:XX:XX:XX:XX:XX) or object path (optional, uses current device if omitted) .TP .B Example Unblock device using MAC address .nf \fB> unblock 00:11:22:33:44:55\fP .fi .sp .TP .B Example Unblock another device .nf \fB> unblock AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Unblock device using object path .nf \fB> unblock /org/bluez/hci0/dev_00_11_22_33_44_55\fP .fi .sp .UNINDENT .SS remove .sp Remove device. .INDENT 0.0 .TP .B Usage \fB> remove \fP .TP .B Uses \fBorg.bluez.Adapter(5)\fP method \fBRemoveDevice\fP .TP .B Device MAC address (XX:XX:XX:XX:XX:XX) or object path (required) .TP .B Example Remove device using MAC address .nf \fB> remove 00:11:22:33:44:55\fP .fi .sp .TP .B Example Remove another device .nf \fB> remove AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Remove device using object path .nf \fB> remove /org/bluez/hci0/dev_00_11_22_33_44_55\fP .fi .sp .UNINDENT .SS connect .sp Connect device. .sp This will initiate a connection to a device. .sp By default this commands tries to connect all the profiles the remote device supports and have been flagged as auto\-connectable. In case when the UUID of the remote service is given only that service will be connected. The UUID can be either a short form (16\-bit UUID) or a long form (128\-bit UUID). There are also some special values for well\-known profiles like \(dqa2dp\-sink\(dq, \(dqa2dp\-source\(dq, \(dqhfp\-hf\(dq, \(dqhfp\-ag\(dq, \(dqftp\(dq or \(dqspp\(dq. .sp To connect with an LE device the controller must have an active scan report of the device it wants to connect to. .sp If no advertising report is received before the timeout a le\-connection\-abort\-by\-local error will be issued. In that case either try again to connect assuming the device is advertising. .INDENT 0.0 .TP .B Usage \fB> connect [uuid]\fP .TP .B Uses \fBorg.bluez.Device(5)\fP methods \fBConnect\fP, \fBConnectProfile\fP .TP .B Device MAC address (XX:XX:XX:XX:XX:XX) or object path (required) .TP .B [uuid] Specific service UUID to connect to (16\-bit, 128\-bit UUID, or profile name like \(dqa2dp\-sink\(dq, \(dqhfp\-hf\(dq, etc.) (optional) .TP .B Example Connect to device (all supported profiles) .nf \fB> connect 1C:48:F9:9D:81:5C\fP .fi .sp .TP .B Example Connect to HSP Headset profile .nf \fB> connect 1C:48:F9:9D:81:5C hsp\-hs\fP .fi .sp .TP .B Example Connect to A2DP Sink profile .nf \fB> connect 1C:48:F9:9D:81:5C a2dp\-sink\fP .fi .sp .TP .B Example Connect to A2DP Source profile .nf \fB> connect 1C:48:F9:9D:81:5C a2dp\-source\fP .fi .sp .TP .B Example Connect to HFP Hands\-Free profile .nf \fB> connect 1C:48:F9:9D:81:5C hfp\-hf\fP .fi .sp .TP .B Example Connect to HFP Audio Gateway profile .nf \fB> connect 1C:48:F9:9D:81:5C hfp\-ag\fP .fi .sp .TP .B Example Connect to File Transfer Profile .nf \fB> connect 1C:48:F9:9D:81:5C ftp\fP .fi .sp .TP .B Example Connect to Serial Port Profile .nf \fB> connect 1C:48:F9:9D:81:5C spp\fP .fi .sp .TP .B Example Connect using full 128\-bit UUID (HSP) .nf \fB> connect 1C:48:F9:9D:81:5C 00001108\-0000\-1000\-8000\-00805f9b34fb\fP .fi .sp .TP .B Example Connect using short 16\-bit UUID (HSP) .nf \fB> connect 1C:48:F9:9D:81:5C 0x1108\fP .fi .sp .TP .B Example Connect to A2DP profile using short UUID .nf \fB> connect 1C:48:F9:9D:81:5C 0x110E\fP .fi .sp .UNINDENT .SS disconnect .sp Disconnect device. .sp By default this commands disconnects all profiles and then terminates the connection. In case when the UUID of the remote service is given only that service will be disconnected. .sp For LE when disconnecting from an active connection the device address is not needed. .INDENT 0.0 .TP .B Usage \fB> disconnect [dev] [uuid]\fP .TP .B Uses \fBorg.bluez.Device(5)\fP methods \fBDisconnect\fP, \fBDisconnectProfile\fP .TP .B [dev] Device MAC address (XX:XX:XX:XX:XX:XX) or object path (optional, uses current device if omitted) .TP .B [uuid] Specific service UUID to disconnect from (16\-bit, 128\-bit UUID, or profile name) (optional) .TP .B Example Disconnect all profiles and terminate connection .nf \fB> disconnect 1C:48:F9:9D:81:5C\fP .fi .sp .TP .B Example Disconnect only A2DP Sink profile .nf \fB> disconnect 1C:48:F9:9D:81:5C a2dp\-sink\fP .fi .sp .TP .B Example Disconnect only HFP Hands\-Free profile .nf \fB> disconnect 1C:48:F9:9D:81:5C hfp\-hf\fP .fi .sp .TP .B Example Disconnect only Serial Port Profile .nf \fB> disconnect 1C:48:F9:9D:81:5C spp\fP .fi .sp .TP .B Example Disconnect A2DP profile using short UUID .nf \fB> disconnect 1C:48:F9:9D:81:5C 0x110E\fP .fi .sp .TP .B Example Disconnect HSP profile using full UUID .nf \fB> disconnect 1C:48:F9:9D:81:5C 00001108\-0000\-1000\-8000\-00805f9b34fb\fP .fi .sp .UNINDENT .SS info .sp Device information. .INDENT 0.0 .TP .B Usage \fB> info [dev/set]\fP .TP .B Uses \fBorg.bluez.Device(5)\fP properties .TP .B [dev/set] Device MAC address (XX:XX:XX:XX:XX:XX), object path, or DeviceSet (optional, uses current device if omitted) .TP .B Example Show detailed information for device .nf \fB> info 1C:48:F9:9D:81:5C\fP .fi .sp .TP .B Example Show information for another device .nf \fB> info 00:11:22:33:44:55\fP .fi .sp .TP .B Example Show device info using object path .nf \fB> info /org/bluez/hci0/dev_1C_48_F9_9D_81_5C\fP .fi .sp .UNINDENT .SS bearer .sp Get/Set preferred bearer. .INDENT 0.0 .TP .B Usage \fB> bearer [last\-seen/bredr/le]\fP .TP .B Uses \fBorg.bluez.Device(5)\fP property \fBPreferredBearer\fP .TP .B Device MAC address (XX:XX:XX:XX:XX:XX) or object path (required) .TP .B [last\-seen/bredr/le] Preferred bearer type \- \(dqlast\-seen\(dq, \(dqbredr\(dq for Classic, or \(dqle\(dq for Low Energy (optional) .TP .B Example get preferred bearer .nf > bearer .in +2 PreferredBearer: last\-seen .in -2 .fi .sp .TP .B Example set preferred bearer to LE .nf > bearer le [CHG] Device PreferredBearer: le Changing le succeeded .fi .sp .TP .B Example set preferred bearer to BREDR .nf > bearer bredr [CHG] Device PreferredBearer: bredr Changing bredr succeeded .fi .sp .UNINDENT .SH Advertise Submenu .sp See \fBbluetoothctl\-advertise(1)\fP\&. .SH Monitor Submenu .sp See \fBbluetoothctl\-monitor(1)\fP .SH Scan Submenu .sp See \fBbluetoothctl\-scan(1)\fP .SH Gatt Submenu .sp See \fBbluetoothctl\-gatt(1)\fP .SH Admin Submenu .sp See \fBbluetoothctl\-admin(1)\fP .SH Player Submenu .sp See \fBbluetoothctl\-player(1)\fP .SH Endpoint Submenu .sp See \fBbluetoothctl\-endpoint(1)\fP .SH Transport Submenu .sp See \fBbluetoothctl\-transport(1)\fP .SH Management Submenu .sp See \fBbluetoothctl\-mgmt(1)\fP .SH Assistant Submenu .sp See \fBbluetoothctl\-assistant(1)\fP .SH LE Submenu .sp See \fBbluetoothctl\-le(1)\fP .SH BREDR Submenu .sp See \fBbluetoothctl\-bredr(1)\fP .SH AUTOMATION .sp Two common ways to automate the tool are to use Here Docs or the program expect. Using Here Docs to show information about the Bluetooth controller. .INDENT 0.0 .INDENT 3.5 .sp .EX bluetoothctl < .SH REPORTING BUGS .sp \% .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/bluetoothctl-telephony.rst0000644000000000000000000000005015142177620017674 xustar0020 atime=1783094075 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-telephony.rst0000644000000000000000000001053715142177620017363 0ustar00rootroot====================== bluetoothctl-telephony ====================== ----------------- Telephony Submenu ----------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: May 2025 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **bluetoothctl** [--options] [telephony.commands] This submenu manages telephony features using the **org.bluez.Telephony(5)** and **org.bluez.Call(5)** interfaces. Telephony Commands ================== list ---- List available audio gateways. :Usage: **> list** :Example Display all available HFP audio gateways: | **> list** show ---- Show audio gateway information. :Usage: **> show [telephony]** :Uses: **org.bluez.Telephony(5)** properties :[telephony]: Audio gateway device path (optional, shows current if omitted) :Example Show information for currently selected audio gateway: | **> show** :Example Show information for specific audio gateway device: | **> show /org/bluez/hci0/dev_00_11_22_33_44_55** :Example Show information for another audio gateway: | **> show /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF** select ------ Select default audio gateway. :Usage: **> select ** :: Audio gateway device path to set as default :Example Select specific audio gateway as default: | **> select /org/bluez/hci0/dev_00_11_22_33_44_55** :Example Select different audio gateway as default: | **> select /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF** dial ---- Dial number. :Usage: **> dial [telephony]** :Uses: **org.bluez.Telephony(5)** method **Dial** :: Phone number to dial :[telephony]: Audio gateway device path (optional, uses current if omitted) :Example Dial number using currently selected audio gateway: | **> dial 555-1234** :Example Dial international number using current audio gateway: | **> dial +1-555-123-4567** :Example Dial emergency number using current audio gateway: | **> dial 911** :Example Dial number using specific audio gateway: | **> dial 555-1234 /org/bluez/hci0/dev_00_11_22_33_44_55** :Example Dial UK number using specific audio gateway: | **> dial +44-20-7946-0958 /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF** :Example Dial with caller ID blocking: | **> dial *67-555-1234** :Example Dial with caller ID hiding (GSM format): | **> dial #31#555-1234** hangup-all ---------- Hangup all calls. :Usage: **> hangup-all [telephony]** :Uses: **org.bluez.Telephony(5)** method **HangupAll** :[telephony]: Audio gateway device path (optional, uses current if omitted) :Example Terminate all active and waiting calls on current gateway: | **> hangup-all** :Example Terminate all calls on specific audio gateway: | **> hangup-all /org/bluez/hci0/dev_00_11_22_33_44_55** list-calls ---------- List available calls. :Usage: **> list-calls** :Example Display all active, waiting, and held calls: | **> list-calls** show-call --------- Show call information. :Usage: **> show-call ** :Uses: **org.bluez.Call(5)** properties :: Call object path to display information for :Example Show information for specific call: | **> show-call /org/bluez/hci0/dev_00_11_22_33_44_55/call1** :Example Show information for another call: | **> show-call /org/bluez/hci0/dev_00_11_22_33_44_55/call2** :Example Show call information from different device: | **> show-call /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/call1** answer ------ Answer call. :Usage: **> answer ** :Uses: **org.bluez.Call(5)** method **Answer** :: Call object path to answer :Example Answer incoming call: | **> answer /org/bluez/hci0/dev_00_11_22_33_44_55/call1** :Example Answer waiting call: | **> answer /org/bluez/hci0/dev_00_11_22_33_44_55/call2** :Example Answer call from different device: | **> answer /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/call1** hangup ------ Hangup call. :Usage: **> hangup ** :Uses: **org.bluez.Call(5)** method **Hangup** :: Call object path to terminate :Example Terminate specific active call: | **> hangup /org/bluez/hci0/dev_00_11_22_33_44_55/call1** :Example Terminate waiting or held call: | **> hangup /org/bluez/hci0/dev_00_11_22_33_44_55/call2** :Example Terminate call from different device: | **> hangup /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/call1** RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.obex.Transfer.rst0000644000000000000000000000005015063045254017604 xustar0020 atime=1783094007 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.Transfer.rst0000644000000000000000000000501615063045254017267 0ustar00rootroot======================= org.bluez.obex.Transfer ======================= ------------------------------------------- BlueZ D-Bus OBEX Transfer API documentation ------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez.obex :Interface: org.bluez.obex.Transfer1 :Object path: [Session object path]/transfer{#} Methods ------- void Cancel() ````````````` Cancels the current transference. Possible errors: :org.bluez.obex.Error.NotAuthorized: :org.bluez.obex.Error.InProgress: :org.bluez.obex.Error.Failed: void Suspend() `````````````` Suspends transference. Possible errors: :org.bluez.obex.Error.NotAuthorized: :org.bluez.obex.Error.NotInProgress: If transfer is still in with **Status** **"queued"**. void Resume() ````````````` Resumes transference previously suspended with use of **Suspend()** method. Possible errors: :org.bluez.obex.Error.NotAuthorized: :org.bluez.obex.Error.NotInProgress: If transfer is still in with **Status** **"queued"**. Properties ---------- string Status [readonly] ```````````````````````` Indicates the current status of the transfer. Possible values: :"queued": :"active": :"suspended": :"complete": :"error": object Session [readonly] ````````````````````````` The object path of the session the transfer belongs to. string Name [readonly, optional] ```````````````````````````````` Name of the object being transferred. Either Name or Type or both will be present. string Type [readonly, optional] ```````````````````````````````` Type of the object transferred being transferred. Either Name or Type or both will be present. uint64 Time [readonly, optional] ```````````````````````````````` Time of the object being transferred if this is provided by the remote party. uint64 Size [readonly, optional] ```````````````````````````````` Size of the object being transferred. If the size is unknown, then this property will not be present. uint64 Transferred [readonly, optional] ``````````````````````````````````````` Number of bytes transferred. For transfers with **Status** set to **"queued"**, this value will not be present. string Filename [readonly, optional] ```````````````````````````````````` Complete name of the file being received or sent. For incoming object push transaction, this will be the proposed default location and name. It can be overwritten by the **AuthorizePush()** in **org.bluez.obex.Agent(5)** and will be then updated accordingly. bluez-5.87/doc/PaxHeaders/org.bluez.GattDescriptor.50000644000000000000000000000005015221755352017361 xustar0020 atime=1783093994 20 ctime=1783094112 bluez-5.87/doc/org.bluez.GattDescriptor.50000644000000000000000000001047415221755352017050 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.GattDescriptor" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.GattDescriptor \- BlueZ D-Bus GattDescriptor API documentation .SH Description .sp GATT local/server and remote/client descriptor attribute representation share the same high\-level D\-Bus API. .sp Local/Server refers to GATT based descriptors exported by a plugin or an external application. .sp Remote/Client refers to GATT descriptors exported by the peer. .SH Interface .SS Client .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.GattDescriptor1 .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/service#/char#/descriptor# .TP .B Used by \fBbluetoothctl\-gatt(1)\fP .UNINDENT .SS Server .INDENT 0.0 .TP .B Service unique name .TP .B Interface org.bluez.GattDescriptor1 .TP .B Object path freely definable .UNINDENT .SS Methods .SS array{byte} ReadValue(dict flags) .sp Issues a request to read the value of the descriptor and returns the value if the operation was successful. .sp Possible options: .INDENT 0.0 .TP .B uint16 offset Read start offset in bytes. .TP .B object device (server only) Device object. .TP .B string link Link type (Server only). .sp Possible values: .INDENT 7.0 .TP .B \(dqBR/EDR\(dq .TP .B \(dqLE\(dq .UNINDENT .UNINDENT .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.InProgress .TP .B org.bluez.Error.NotPermitted .TP .B org.bluez.Error.NotAuthorized .TP .B org.bluez.Error.NotSupported .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl [gatt] > read [offset] .UNINDENT .SS void WriteValue(array{byte} value, dict flags) .sp Issues a request to write the value of the descriptor. .sp Possible flags: .INDENT 0.0 .TP .B uint16 offset Write start offset in bytes. .TP .B uint16 mtu Exchanged MTU (Server only). .TP .B object device Device path (Server only). .TP .B string link Link type (Server only). .sp Possible values: .INDENT 7.0 .TP .B \(dqBR/EDR\(dq .TP .B \(dqLE\(dq .UNINDENT .TP .B boolean prepare\-authorize True if prepare authorization request. .UNINDENT .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.InProgress .TP .B org.bluez.Error.NotPermitted .TP .B org.bluez.Error.InvalidValueLength .TP .B org.bluez.Error.NotAuthorized .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.ImproperlyConfigured .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl [gatt] > write [offset] [type] .UNINDENT .SS Properties .SS string UUID [read\-only] .sp 128\-bit descriptor UUID. .SS object Characteristic [read\-only] .sp Object path of the GATT characteristic the descriptor belongs to. .SS array{byte} Value [read\-only, optional] .sp The cached value of the descriptor. This property gets updated only after a successful read request, upon which a PropertiesChanged signal will be emitted. .SS array{string} Flags [read\-only] .sp Defines how the descriptor value can be used. .sp Possible values: .INDENT 0.0 .TP .B \(dqread\(dq .TP .B \(dqwrite\(dq .TP .B \(dqencrypt\-read\(dq .TP .B \(dqencrypt\-write\(dq .TP .B \(dqencrypt\-authenticated\-read\(dq .TP .B \(dqencrypt\-authenticated\-write\(dq .TP .B \(dqsecure\-read\(dq (Server Only) .TP .B \(dqsecure\-write\(dq (Server Only) .TP .B \(dqauthorize\(dq .UNINDENT .SS uint16 Handle [read\-only] (Client Only) .sp Descriptor handle. .SS uint16 Handle [read\-write, optional] (Server Only) .sp Descriptor handle. When available in the server it would attempt to use to allocate into the database which may fail, to auto allocate the value 0x0000 shall be used which will cause the allocated handle to be set once registered. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/hcitool.rst0000644000000000000000000000005015142177620014620 xustar0020 atime=1783094085 20 ctime=1783094113 bluez-5.87/doc/hcitool.rst0000644000000000000000000001375015142177620014307 0ustar00rootroot======= hcitool ======= ------------------------------- Configure Bluetooth connections ------------------------------- :Authors: - Maxim Krasnyansky - Marcel Holtmann - Fabrizio Gennari :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: Nov 12, 2002 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **hcitool** -h **hcitool** *COMMAND* --help **hcitool** [-i *hciX*] [*COMMAND* [*PARAMETERS*]] DESCRIPTION =========== **hcitool(1)** is used to configure Bluetooth connections and send some special command to Bluetooth devices. If no **command** is given, or if the option **-h** is used, *hcitool* prints some usage information and exits. OPTIONS ======= -i The command is applied to device *hciX*, which must be the name of an installed Bluetooth device. If not specified, the command will be sent to the first available Bluetooth device. -h Gives a list of possible commands COMMANDS ======== dev Display local devices inq Inquire remote devices. For each discovered device, Bluetooth device address, clock offset and class are printed. scan Inquire remote devices. For each discovered device, device name are printed. name <*bdaddr*> Print device name of remote device with Bluetooth address *bdaddr*. info <*bdaddr*> Print device name, version and supported features of remote device with Bluetooth address *bdaddr*. spinq Start periodic inquiry process. No inquiry results are printed. epinq Exit periodic inquiry process. cmd <*ogf*> <*ocf*> [*parameters*] Submit an arbitrary HCI command to local device. *ogf*, *ocf* and parameters are hexadecimal bytes. con Display active baseband connections cc [--*role*\=c|p] [--*pkt-type*\=<*ptype*>] <*bdaddr*> Create baseband connection to remote device with Bluetooth address *bdaddr*. Option **--pkt-type** specifies a list of allowed packet types. <*ptype*> is a comma-separated list of packet types, where the possible packet types are **DM1**, **DM3**, **DM5**, **DH1**, **DH3**, **DH5**, **HV1**, **HV2**, **HV3**. Default is to allow all packet types. Option **--role** can have value **c** (do not allow role switch, stay central) or **p** (allow role switch, become peripheral if the peer asks to become central). Default is **c**. dc <*bdaddr*> [*reason*] Delete baseband connection from remote device with Bluetooth address *bdaddr*. The reason can be one of the Bluetooth HCI error codes. Default is **19** for user ended connections. The value must be given in decimal. sr <*bdaddr*> <*role*> Switch role for the baseband connection from the remote device to **central** or **peripheral**. cpt <*bdaddr*> <*ptypes*> Change packet types for baseband connection to device with Bluetooth address *bdaddr*. *ptypes* is a comma-separated list of packet types, where the possible packet types are **DM1**, **DM3**, **DM5**, **DH1**, **DH3**, **DH5**, **HV1**, **HV2**, **HV3**. rssi <*bdaddr*> Display received signal strength information for the connection to the device with Bluetooth address *bdaddr*. lq <*bdaddr*> Display link quality for the connection to the device with Bluetooth address *bdaddr*. tpl <*bdaddr*> [*type*] Display transmit power level for the connection to the device with Bluetooth address *bdaddr*. The *type* can be **0** for the current transmit power level (which is default) or **1** for the maximum transmit power level. afh <*bdaddr*> Display AFH channel map for the connection to the device with Bluetooth address *bdaddr*. lp <*bdaddr*> [*value*] With no value, displays link policy settings for the connection to the device with Bluetooth address *bdaddr*. If *value* is given, sets the link policy settings for that connection to *value*. Possible values are **RSWITCH**, **HOLD**, **SNIFF** and **PARK**. lst <*bdaddr*> [*value*] With no value, displays link supervision timeout for the connection to the device with Bluetooth address *bdaddr*. If *value* is given, sets the link supervision timeout for that connection to *value* slots, or to infinite if value is 0. auth <*bdaddr*> Request authentication for the device with Bluetooth address *bdaddr*. enc <*bdaddr*> [*encrypt*] **enable** or **disable** the encryption for the device with Bluetooth address *bdaddr*. key <*bdaddr*> Change the connection link key for the device with Bluetooth address *bdaddr*. clkoff <*bdaddr*> Read the clock offset for the device with Bluetooth address *bdaddr*. clock [*bdaddr*] [*clock*] Read the clock for the device with Bluetooth address *bdaddr*. The *clock* can be **0** for the local clock or **1** for the piconet clock (which is default). lescan [--*privacy*] [--*passive*] [--*acceptlist*] [--*discovery*\=g|l] [--*duplicates*] Start LE scan leinfo [--*static*] [--*random*] <*bdaddr*> Get LE remote information lealadd [--*random*] <*bdaddr*> Add device to LE Accept List lealrm <*bdaddr*> Remove device from LE Accept List lealsz Read size of LE Accept List lealclr Clear LE Accept List lerladd [--*local_irk*] [--*peer_irk*] [--*random*] <*bdaddr*> Add device to LE Resolving List lerlrm <*bdaddr*> Remove device from LE Resolving List lerlclr Clear LE Resolving List lerlsz Read size of LE Resolving List lerlon Enable LE Address Resolution lerloff Disable LE Address Resolution lecc [--*static*] [--*random*] <*bdaddr*> | [--*acceptlist*] Create a LE Connection ledc <*handle*> [*reason*] Disconnect a LE Connection lecup <*handle*> <*min*> <*max*> <*latency*> <*timeout*> LE Connection Update RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.obex.PhonebookAccess.50000644000000000000000000000005015221755377020434 xustar0020 atime=1783094015 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.PhonebookAccess.50000644000000000000000000002145515221755377020124 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.obex.PhonebookAccess" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.obex.PhonebookAccess \- BlueZ D-Bus OBEX PhonebookAccess API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez.obex .TP .B Interface org.bluez.obex.PhonebookAccess1 .TP .B Object path [Session object path] .UNINDENT .SS Methods .SS void Select(string location, string phonebook) .sp Selects the phonebook object for other operations. .sp Should be call before all the other operations. .sp Possible location values: .INDENT 0.0 .TP .B \(dqint\(dq, \(dqinternal\(dq (default) Store in the Internal memory. .TP .B \(dqsim{#}\(dq Store in the sim number. .UNINDENT .sp Possible phonebook values: .INDENT 0.0 .TP .B \(dqpb\(dq Store as contact. .TP .B \(dqich\(dq Store as incoming call. .TP .B \(dqoch\(dq Store as outgoing call. .TP .B \(dqmch\(dq Store as missing call. .TP .B \(dqcch\(dq Store as a combination of incoming, outgoing and missing call. .UNINDENT .sp \(dqspd\(dq: .INDENT 0.0 .INDENT 3.5 Store as speed dials entry ( only for \(dqinternal\(dq ) .UNINDENT .UNINDENT .sp \(dqfav\(dq: .INDENT 0.0 .INDENT 3.5 Store as favorites entry ( only for \(dqinternal\(dq ) .UNINDENT .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS object, dict PullAll(string targetfile, dict filters) .sp Returns the entire phonebook object from the PSE server in plain string with vcard format, and store it in a local file. .sp If an empty target file is given, a name will be automatically generated for the temporary file. .sp The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. .sp The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see \fBorg.bluez.obex.Transfer(5)\fP for the possible list of properties. .sp Possible filters: .INDENT 0.0 .TP .B string Format Items vcard format. .sp Possible values: .INDENT 7.0 .TP .B \(dqvcard21\(dq (default) .TP .B \(dqvcard30\(dq .UNINDENT .TP .B string Order Items order. .sp Possible values: .INDENT 7.0 .TP .B \(dq\(dq .TP .B \(dqindexed\(dq .TP .B \(dqalphanumeric\(dq .TP .B \(dqphonetic\(dq .UNINDENT .TP .B uint16 Offset (default 0) Offset of the first item. .TP .B uint16 MaxCount (default 65535) Maximum number of items. .TP .B array{string} Fields (default all fields) Item vcard fields. .sp See \fBListFilterFields()\fP for possible values. .TP .B array{string} FilterAll Filter items by fields using AND logic, cannot be used together with \fBFilterAny\fP\&. .sp See \fBListFilterFields()\fP for possible values. .TP .B array{string} FilterAny Filter items by fields using OR logic, cannot be used together with \fBFilterAll\fP\&. .sp See \fBListFilterFields()\fP for possible values. .TP .B bool ResetNewMissedCalls Reset new the missed calls items, shall only be used for folders mch and cch. .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Forbidden .UNINDENT .SS array{string vcard, string name} List(dict filters) .sp Returns array of vcard\-listing data where every entry consists of a pair of strings containing the vcard handle and the contact name. .sp For example: .INDENT 0.0 .TP .B \(dq1.vcf\(dq \(dqJohn\(dq .UNINDENT .sp Possible filters: .INDENT 0.0 .TP .B string Order Contact order. .sp Possible values: .INDENT 7.0 .TP .B \(dq\(dq .TP .B \(dqindexed\(dq .TP .B \(dqalphanumeric\(dq .TP .B \(dqphonetic\(dq .UNINDENT .TP .B uint16 Offset Start offset. .TP .B uint16 MaxCount Maximum number of contacts. .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Forbidden .UNINDENT .SS object, dict Pull(string vcard, string targetfile, dict filters) .sp Retrieves the vcard in the current phonebook object and store it in a local file. .sp If an empty target file is given, a name will be automatically generated for the temporary file. .sp The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. .sp The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see \fBorg.bluez.obex.Transfer(5)\fP for the possible list of properties. .sp Possible filters: .INDENT 0.0 .TP .B string Format Contact data format. .sp Possible values: .INDENT 7.0 .TP .B \(dq\(dq .TP .B \(dqvcard21\(dq .TP .B \(dqvcard30\(dq .UNINDENT .TP .B array{string} Fields See \fBListFilterFields()\fP for possible values. .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Forbidden .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS array{string vcard, string name} Search(string field, string value, dict filters) .sp Searches for entries matching the given condition and return an array of vcard\-listing data where every entry consists of a pair of strings containing the vcard handle and the contact name. .sp Possible field values: .INDENT 0.0 .INDENT 3.5 .INDENT 0.0 .TP .B \(dqname\(dq (default) Search by name. .TP .B \(dqnumber\(dq Search by number. .TP .B \(dqsound\(dq Search by sound. .UNINDENT .UNINDENT .UNINDENT .sp value: the string value to search for .sp Possible filters: .INDENT 0.0 .TP .B string Order Contact order. .sp Possible values: .INDENT 7.0 .TP .B \(dq\(dq .TP .B \(dqindexed\(dq .TP .B \(dqalphanumeric\(dq .TP .B \(dqphonetic\(dq .UNINDENT .TP .B uint16 Offset Start offset. .TP .B uint16 MaxCount Maximum number of contacts. .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Forbidden .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS uint16 GetSize() .sp Returns the number of entries in the selected phonebook object that are actually used (i.e. indexes that correspond to non\-NULL entries). .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.Forbidden .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS void UpdateVersion() .sp Attempts to update PrimaryCounter and SecondaryCounter. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.NotSupported .TP .B org.bluez.obex.Error.Forbidden .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS array{string} ListFilterFields() .sp Returns all Available fields that can be used in Fields filter. .sp Possible return: .INDENT 0.0 .TP .B \(dqVERSION\(dq .TP .B \(dqFN\(dq .TP .B \(dqN\(dq .TP .B \(dqPHOTO\(dq .TP .B \(dqBDAY\(dq .TP .B \(dqADR\(dq .TP .B \(dqLABEL\(dq .TP .B \(dqTEL\(dq .TP .B \(dqEMAIL\(dq .TP .B \(dqMAILER\(dq .TP .B \(dqTZ\(dq .TP .B \(dqGEO\(dq .TP .B \(dqTITLE\(dq .TP .B \(dqROLE\(dq .TP .B \(dqLOGO\(dq .TP .B \(dqAGENT\(dq .TP .B \(dqORG\(dq .TP .B \(dqNOTE\(dq .TP .B \(dqREV\(dq .TP .B \(dqSOUND\(dq .TP .B \(dqURL\(dq .TP .B \(dqUID\(dq .TP .B \(dqKEY\(dq .TP .B \(dqNICKNAME\(dq .TP .B \(dqCATEGORIES\(dq .TP .B \(dqPROID\(dq .TP .B \(dqCLASS\(dq .TP .B \(dqSORT\-STRING\(dq .TP .B \(dqX\-IRMC\-CALL\-DATETIME\(dq .TP .B \(dqX\-BT\-SPEEDDIALKEY\(dq .TP .B \(dqX\-BT\-UCI\(dq .TP .B \(dqX\-BT\-UID\(dq .TP .B \(dqBIT\-{#}\(dq .UNINDENT .sp Possible errors: None .SS Properties .SS string Folder [readonly] .sp Current folder. .SS string DatabaseIdentifier [readonly, optional] .sp 128 bits persistent database identifier. .sp Possible values: .INDENT 0.0 .INDENT 3.5 32\-character hexadecimal such as A1A2A3A4B1B2C1C2D1D2E1E2E3E4E5E6 .UNINDENT .UNINDENT .SS string PrimaryCounter [readonly, optional] .sp 128 bits primary version counter. .sp Possible values: .INDENT 0.0 .INDENT 3.5 32\-character hexadecimal such as A1A2A3A4B1B2C1C2D1D2E1E2E3E4E5E6 .UNINDENT .UNINDENT .SS string SecondaryCounter [readonly, optional] .sp 128 bits secondary version counter. .sp Possible values: .INDENT 0.0 .INDENT 3.5 32\-character hexadecimal such as A1A2A3A4B1B2C1C2D1D2E1E2E3E4E5E6 .UNINDENT .UNINDENT .SS bool FixedImageSize [readonly, optional] .sp Indicate support for fixed image size. .sp Possible values: .INDENT 0.0 .INDENT 3.5 True if image is JPEG 300x300 pixels otherwise False. .UNINDENT .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/pixit-opp.txt0000644000000000000000000000005012572170650015117 xustar0020 atime=1770587296 20 ctime=1783094112 bluez-5.87/doc/pixit-opp.txt0000644000000000000000000000156212572170650014604 0ustar00rootrootOPP PIXIT for the PTS tool. PTS version: 6.2.0 * - different than PTS defaults & - should be set to IUT Bluetooth address Required PIXIT settings ------------------------------------------------------------------------------- Parameter Name Value ------------------------------------------------------------------------------- TSPX_supported_extension bmp TSPX_unsupported_extension pts TSPX_client_class_of_device 100104 TSPX_server_class_of_device 100104 TSPX_auth_password 0000 TSPX_auth_user_id PTS TSPX_l2cap_psm 1003 TSPX_rfcomm_channel 8 TSPX_no_confirmations FALSE TSPX_bd_addr_iut 112233445566 (*&) TSPX_delete_link_key FALSE TSPX_pin_code 0000 TSPX_security_enabled FALSE TSPX_time_guard 300000 TSPX_use_implicit_send TRUE ------------------------------------------------------------------------------- bluez-5.87/doc/PaxHeaders/btmon.10000644000000000000000000000005015221755427013633 xustar0020 atime=1783094039 20 ctime=1783094112 bluez-5.87/doc/btmon.10000644000000000000000000067346615221755427013342 0ustar00rootroot'\" t .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "btmon" "1" "April 2021" "BlueZ" "Linux System Administration" .SH Name btmon \- Bluetooth monitor .SH SYNOPSIS .sp \fBbtmon\fP [\fIOPTIONS\fP ...] .SH DESCRIPTION .sp The btmon(1) command provides access to the Bluetooth subsystem monitor infrastructure for reading HCI traces. .SH OPTIONS .INDENT 0.0 .TP .BI \-r \ FILE\fR,\fB \ \-\-read \ FILE Read traces in btsnoop format from \fIFILE\fP\&. .TP .BI \-w \ FILE\fR,\fB \ \-\-write \ FILE Save traces in btsnoop format to \fIFILE\fP\&. .TP .BI \-a \ FILE\fR,\fB \ \-\-analyze \ FILE Analyze traces in btsnoop format from \fIFILE\fP\&. It displays the devices found in the \fIFILE\fP with its packets by type. If gnuplot is installed on the system it also attempts to plot packet latency graph. .TP .BI \-s \ SOCKET\fR,\fB \ \-\-server \ SOCKET Start monitor server socket. .TP .BI \-p \ PRIORITY\fR,\fB \ \-\-priority \ PRIORITY Show only priority or lower for user log. .UNINDENT .TS box center; l|l. T{ \fIPRIORITY\fP T} T{ NAME T} _ T{ \fB3\fP T} T{ Error T} _ T{ \fB4\fP T} T{ Warning T} _ T{ \fB6\fP T} T{ Information (Default) T} _ T{ \fB7\fP T} T{ Debug. \fBdebug\fP can be used. T} .TE .INDENT 0.0 .TP .BI \-i \ NUM\fR,\fB \ \-\-index \ NUM Show only specified controller. \fIhciNUM\fP is also acceptable. This is useful to capture the traces from the specific controller when the multiple controllers are presented. .TP .BI \-d \ TTY\fR,\fB \ \-\-tty \ TTY Read data from \fITTY\fP\&. .TP .BI \-B \ SPEED\fR,\fB \ \-\-rate \ SPEED Set TTY speed. The default \fISPEED\fP is 115300 .TP .BI \-V \ COMPID\fR,\fB \ \-\-vendor \ COMPID Set the default company identifier. The \fICOMPID\fP is a unique number assigned by the Bluetooth SIG to a member company and can be found/searched from the Bluetooth SIG webpage. .sp For example, Intel is 2 and Realtek is 93. .TP .B \-M\fP,\fB \-\-mgmt Open channel for mgmt events. .TP .B \-K\fP,\fB \-\-kernel Open kmsg for kernel messages. .TP .B \-t\fP,\fB \-\-time Show a time instead of time offset. .TP .B \-T\fP,\fB \-\-date Show a time and date information instead of time offset. .TP .B \-N\fP,\fB \-\-no\-time Suppress the time offset display entirely. .TP .B \-S\fP,\fB \-\-sco Dump SCO traffic in raw hex format. .TP .B \-A\fP,\fB \-\-a2dp Dump A2DP stream traffic in a raw hex format. .TP .B \-I\fP,\fB \-\-iso Dump ISO stream traffic in raw hex format. Required to see LE Audio isochronous data in the output. .TP .BI \-E \ IP\fR,\fB \ \-\-ellisys \ IP Send Ellisys HCI Injection. .TP .B \-P\fP,\fB \-\-no\-pager Disable pager usage while reading the log file. .TP .BI \-J \ OPTIONS\fR,\fB \ \-\-jlink \ OPTIONS Read data from RTT. Each options are comma(,) separated without spaces. .UNINDENT .TS box center; l|l. T{ \fIOPTIONS\fP T} T{ Description T} _ T{ \fBDEVICE\fP T} T{ Required. Set the target device. T} _ T{ \fBSERIALNO\fP T} T{ (Optional) Set the USB serial number. Default is \fB0\fP\&. T} _ T{ \fBINTERFACE\fP T} T{ (Optional) Target interface. Default is \fBswd\fP\&. T} _ T{ \fBSPEED\fP T} T{ (Optional) Set target interface speed in kHz. Default is \fB1000\fP\&. T} .TE .INDENT 0.0 .TP .BI \-R \ OPTIONS\fR,\fB \ \-\-rtt \ OPTIONS RTT control block parameters. Each options are comma(,) separated without spaces. .UNINDENT .TS box center; l|l. T{ \fIOPTIONS\fP T} T{ Description T} _ T{ \fBADDRESS\fP T} T{ (Optional) Address of RTT buffer. Default is \fB0x00\fP T} _ T{ \fBAREA\fP T} T{ (Optional) Size of range to search in RTT buffer. Default is \fB0\fP T} _ T{ \fBNAME\fP T} T{ (Optional) Buffer name. Default is \fBbtmonitor\fP T} .TE .INDENT 0.0 .TP .BI \-C \ WIDTH\fR,\fB \ \-\-columns \ WIDTH Output width if not a terminal .TP .BI \-c \ MODE\fR,\fB \ \-\-color \ MODE Set output color. The possible \fIMODE\fP values are: \fBauto|always|never\fP\&. .sp Default value is \fBauto\fP .TP .B \-v\fP,\fB \-\-version Show version .TP .B \-h\fP,\fB \-\-help Show help options .UNINDENT .SH READING THE OUTPUT .sp btmon output is organized as a stream of frames, each representing a single event in the Bluetooth subsystem. Understanding the output format is essential for debugging Bluetooth issues. .SS Line Prefixes .sp Every frame begins with a single\-character prefix that identifies its source and type: .TS box center; l|l|l. T{ Prefix T} T{ Meaning T} T{ Description T} _ T{ \fB<\fP T} T{ \fBHCI Command / Data TX\fP T} T{ Sent from host to controller (outgoing). HCI commands, ACL/SCO/ISO data transmitted to the controller. T} _ T{ \fB>\fP T} T{ \fBHCI Event / Data RX\fP T} T{ Received from controller to host (incoming). HCI events, ACL/SCO/ISO data received from the controller. T} _ T{ \fB@\fP T} T{ \fBManagement traffic\fP T} T{ Management interface (MGMT) commands and events between bluetoothd and the kernel management layer. T} _ T{ \fB=\fP T} T{ \fBSystem notes\fP T} T{ System\-level annotations: kernel information, index changes, process log messages, and D\-Bus signals. T} .TE .SS HCI Traffic (\fB<\fP and \fB>\fP) .sp HCI frames represent the actual communication between the host software and the Bluetooth controller hardware. .sp \fBAnatomy of an HCI command line\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: Reset (0x03|0x0003) plen 0 #5 [hci0] 12:35:01.843185 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ Timestamp │ │ │ │ │ │ └─ Controller │ │ │ │ │ └─ Frame number │ │ │ │ └─ Parameter length (bytes) │ │ │ └─ Full opcode (16\-bit) │ │ └─ OGF|OCF (Opcode Group / Command Field) │ └─ Command name (human\-readable) └─ Direction: < = Host to Controller (outgoing) .EE .UNINDENT .UNINDENT .sp \fBAnatomy of an HCI event line\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: Command Complete (0x0e) plen 4 #6 [hci0] 12:35:01.864922 │ │ │ │ │ │ │ │ │ │ │ └─ Timestamp │ │ │ │ └─ Controller │ │ │ └─ Frame number │ │ └─ Parameter length │ └─ Event code │ └─ Direction: > = Controller to Host (incoming) .EE .UNINDENT .UNINDENT .sp The \fB<\fP direction means the host is \fBsending\fP to the controller (commands and data). The \fB>\fP direction means the controller is \fBsending\fP to the host (events and data). Think of it from the controller\(aqs perspective: \fB<\fP is input going into the controller, \fB>\fP is output coming from it. .sp \fBHCI commands with parameters\fP are followed by indented detail lines: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE Set Extende.. (0x08|0x0039) plen 2 #1 [hci0] 12:35:01.738352 Extended advertising: Disabled (0x00) Number of sets: Disable all sets (0x00) .EE .UNINDENT .UNINDENT .sp \fBHCI event responses\fP reference the command they complete: .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: Command Complete (0x0e) plen 4 #6 [hci0] 12:35:01.864922 Reset (0x03|0x0003) ncmd 2 Status: Success (0x00) .EE .UNINDENT .UNINDENT .sp Here \fBncmd 2\fP indicates the controller can accept 2 more commands (HCI flow control). The indented body shows the command this event completes and the result status. .sp \fBLE Meta Events\fP contain a subevent type: .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: LE Meta Event (0x3e) plen 31 #487 [hci0] 12:36:18.974201 LE Enhanced Connection Complete (0x0a) Status: Success (0x00) Handle: 2048 Role: Peripheral (0x01) Peer address type: Public (0x00) Peer address: AA:BB:CC:DD:EE:FF (OUI Company) Connection interval: 60.00 msec (0x0030) Connection latency: 0 (0x0000) Supervision timeout: 9600 msec (0x03c0) .EE .UNINDENT .UNINDENT .sp \fBACL Data\fP shows data plane traffic with handle and protocol decoding: .INDENT 0.0 .INDENT 3.5 .sp .EX < LE\-ACL: Handle 2048 [66:B0:26:F1:D3:BC] [1/6] flags 0x00 dlen 16 #493 [hci0] 12:36:18.977915 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ Timestamp │ │ │ │ │ │ │ │ └─ Controller │ │ │ │ │ │ │ └─ Frame number │ │ │ │ │ │ └─ Data length │ │ │ │ │ └─ flags │ │ │ │ └─ Buffer tracking (optional) │ │ │ └─ Peer address (optional) │ │ └─ Handle number │ └─ Connection\-type\-aware label (e.g. BR\-ACL, LE\-ACL, BR\-SCO, LE\-ISO) └─ Direction marker: \(aq<\(aq = host\->controller (TX), \(aq>\(aq = controller\->host (RX) .EE .UNINDENT .UNINDENT .sp ACL data is automatically decoded into higher\-layer protocols: .INDENT 0.0 .INDENT 3.5 .sp .EX < LE\-ACL: Handle 2048 [2/6] flags 0x00 dlen 7 #494 [hci0] 12:36:18.978488 ATT: Exchange MTU Request (0x02) len 2 Client RX MTU: 517 > LE\-ACL: Handle 2048 flags 0x02 dlen 11 #497 [hci0] 12:36:19.000048 SMP: Pairing Request (0x01) len 6 IO capability: NoInputNoOutput (0x03) OOB data: Authentication data not present (0x00) Authentication requirement: Bonding, MITM, SC, No Keypresses, CT2 (0x2d) Max encryption key size: 16 .EE .UNINDENT .UNINDENT .SS Management Traffic (\fB@\fP) .sp Lines starting with \fB@\fP show management interface traffic \-\- the structured command/event protocol between \fBbluetoothd\fP and the kernel (see \fBdoc/mgmt\-protocol.rst\fP). .sp \fBAnatomy of a management line\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Command: Set Powered (0x0005) plen 1 {0x0001} [hci0] 12:35:04.033564 │ │ │ │ │ │ │ │ │ │ │ └─ Timestamp │ │ │ │ └─ Controller │ │ │ └─ MGMT socket ID │ │ └─ Parameter length │ └─ MGMT opcode └─ @ = Management channel .EE .UNINDENT .UNINDENT .sp The \fB{0x0001}\fP is the management socket identifier \-\- it distinguishes between multiple management clients (e.g. bluetoothd and btmgmt running simultaneously). .sp \fBMGMT Open/Close\fP track when processes connect to the management channel: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Open: bluetoothd (privileged) version 1.23 {0x0001} 12:34:49.881936 @ MGMT Close: bluetoothd {0x0001} 12:35:01.866256 .EE .UNINDENT .UNINDENT .sp These show the process name, privilege level, and protocol version. .sp \fBMGMT commands with parameters\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Command: Set Powered (0x0005) plen 1 {0x0001} [hci0] 12:35:04.033564 Powered: Enabled (0x01) .EE .UNINDENT .UNINDENT .sp \fBMGMT events\fP (responses and notifications): .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Event: Command Complete (0x0001) plen 7 {0x0001} [hci0] 12:35:04.114789 Set Powered (0x0005) plen 4 Status: Success (0x00) Current settings: 0x004e0ac1 Powered Secure Simple Pairing .EE .UNINDENT .UNINDENT .sp \fBMGMT without controller index\fP (global operations): .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Command: Read Management Ver.. (0x0001) plen 0 {0x0001} 12:35:04.027771 @ MGMT Event: Command Complete (0x0001) plen 6 {0x0001} 12:35:04.027776 .EE .UNINDENT .UNINDENT .sp Notice there is no \fB[hci0]\fP \-\- these operate at the system level, not on a specific controller. .SS System Notes (\fB=\fP) .sp Lines starting with \fB=\fP are system\-level annotations injected by the kernel or by processes via the monitor channel. They are \fBnot\fP HCI or MGMT protocol traffic. .sp \fBKernel information\fP (shown at startup): .INDENT 0.0 .INDENT 3.5 .sp .EX = Note: Linux version 6.16.0\-rc6\-0903 (x86_64) 12:34:49.881926 = Note: Bluetooth subsystem version 2.22 12:34:49.881930 .EE .UNINDENT .UNINDENT .sp \fBIndex lifecycle\fP (controller added/removed/opened/closed): .INDENT 0.0 .INDENT 3.5 .sp .EX = New Index: 00:11:22:33:44:55 (Primary,USB,hci0) [hci0] 12:34:49.881932 = Open Index: 00:11:22:33:44:55 [hci0] 12:34:49.881933 = Index Info: 00:11:22:33:44:55 (OUI Company) [hci0] 12:34:49.881934 = Close Index: 00:11:22:33:44:55 [hci0] 12:35:01.865125 .EE .UNINDENT .UNINDENT .INDENT 0.0 .IP \(bu 2 \fBNew Index\fP \-\- a controller was registered with the kernel .IP \(bu 2 \fBOpen Index\fP \-\- a controller was activated .IP \(bu 2 \fBIndex Info\fP \-\- controller vendor information .IP \(bu 2 \fBClose Index\fP \-\- a controller was deactivated .UNINDENT .sp \fBProcess log messages\fP (debug output from bluetoothd and other daemons): .INDENT 0.0 .INDENT 3.5 .sp .EX = bluetoothd: src/adapter.c:connected_callback() hci0 devic.. 12:36:18.975307 │ │ │ │ │ └─ Timestamp │ └─ Source file, function, and message (may be truncated) └─ Process name .EE .UNINDENT .UNINDENT .sp These appear when \fBbluetoothd\fP is running with debug enabled (\fB\-d\fP) or when a process writes to the kernel logging channel. They show the source file path, function name, and a log message \-\- invaluable for correlating daemon\-internal decisions with the HCI traffic around them. .sp \fBD\-Bus activity\fP (signals and method calls): .INDENT 0.0 .INDENT 3.5 .sp .EX = bluetoothd: [:1.21220:method_call] > org.freedesktop.DBus.. 12:34:53.912508 = bluetoothd: [:1.21220:method_return] < [#5] 12:34:53.912546 = bluetoothd: [signal] org.freedesktop.DBus.ObjectManager.I.. 12:36:18.975691 .EE .UNINDENT .UNINDENT .sp The format is \fB[bus_name:message_type]\fP followed by \fB>\fP (outgoing) or \fB<\fP (incoming). Note that \fB>\fP and \fB<\fP within D\-Bus system notes indicate D\-Bus message direction, not HCI direction. .SS Right\-Side Metadata .sp Every line has metadata right\-aligned at the end. The exact fields depend on the line type: .INDENT 0.0 .INDENT 3.5 .sp .EX ┌─ Main content (left\-aligned, variable width) │ ┌─ Frame # ─┐ ┌Controller┐ ┌─ Timestamp ─┐ │ │ │ │ │ │ │ < HCI Command: Reset (0x03|0x0003) plen 0 #5 [hci0] 12:35:01.843185 > HCI Event: Command Complete (0x0e) plen 4 #6 [hci0] 12:35:01.864922 @ MGMT Command: Set Powered (0x0005) plen 1 {0x0001} [hci0] 12:35:04.033564 = Note: Linux version 6.16.0\-rc6\-0903 (x86_64) 12:34:49.881926 = Open Index: 00:11:22:33:44:55 [hci0] 12:34:49.881933 .EE .UNINDENT .UNINDENT .sp \fBFrame number\fP (\fB#N\fP): Sequential counter for HCI frames only. Useful for identifying specific packets in a trace. Only HCI traffic (\fB<\fP and \fB>\fP) gets frame numbers \-\- MGMT (\fB@\fP) and system notes (\fB=\fP) do not. .sp \fBController\fP (\fB[hciN]\fP): Identifies which Bluetooth controller the frame belongs to. Absent for global operations (kernel notes, MGMT commands without a controller index). .sp \fBMGMT socket ID\fP (\fB{0xNNNN}\fP): Shown on \fB@\fP lines instead of frame numbers. Identifies which management socket (process) sent the command. .sp \fBTimestamp\fP: Always the rightmost field. The format depends on the command\-line option used: .TS box center; l|l|l. T{ Option T} T{ Format T} T{ Example T} _ T{ \fI(default)\fP T} T{ Seconds since trace start T} T{ \fB0.881932\fP T} _ T{ \fB\-t\fP T} T{ Time of day (HH:MM:SS.usec) T} T{ \fB12:35:01.843185\fP T} _ T{ \fB\-T\fP T} T{ Full date and time T} T{ \fB2026\-01\-13 12:34:49.881926\fP T} .TE .SS Indented Detail Lines .sp Lines indented below a frame header contain the decoded payload of that frame. The indentation level indicates the protocol layer: .INDENT 0.0 .IP \(bu 2 \fBFirst level\fP (6 spaces): direct payload of the HCI/MGMT frame .IP \(bu 2 \fBSecond level\fP (8 spaces): decoded fields within the payload .IP \(bu 2 \fBThird level\fP (10+ spaces): nested protocol data (e.g. L2CAP inside ACL, ATT inside L2CAP) .UNINDENT .sp Example of protocol layering in ACL data: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL: Handle 2048 flags 0x02 dlen 11 #497 [hci0] 12:36:19.000048 SMP: Pairing Request (0x01) len 6 ← L2CAP/SMP layer IO capability: NoInputNoOutput (0x03) ← SMP fields OOB data: Authentication data not present (0x00) Authentication requirement: Bonding, MITM, SC (0x2d) Max encryption key size: 16 .EE .UNINDENT .UNINDENT .SS Timestamp Notes .sp When reading btsnoop files with \fB\-t\fP or \fB\-T\fP, timestamps reflect the wall\-clock time recorded in the btsnoop file. The precision depends on the source: .INDENT 0.0 .IP \(bu 2 \fBLive capture\fP (\fBbtmon\fP monitor channel): Microsecond precision from the kernel. .IP \(bu 2 \fBbtsnoop files\fP: The btsnoop format stores timestamps as microseconds since epoch, so full microsecond precision is preserved. Trailing zeros in the display (e.g., \fB14:38:46.589000\fP) indicate the original capture source had millisecond granularity. .UNINDENT .sp The default timestamp mode shows seconds elapsed since the first packet in the trace, which is useful for measuring intervals between events without needing to know the absolute time. .SS Frame Numbers vs Line Numbers .sp btmon assigns sequential \fBframe numbers\fP (\fB#N\fP) to HCI packets. These are stable identifiers for specific packets regardless of output formatting. However, when processing btmon text output with tools like \fBgrep\fP or \fBsed\fP, the relevant unit is \fBline numbers\fP in the output file. The two are unrelated: .INDENT 0.0 .IP \(bu 2 A single frame may produce many output lines (header + decoded fields). .IP \(bu 2 Frame numbers only apply to HCI traffic (\fB<\fP and \fB>\fP). MGMT (\fB@\fP) and system notes (\fB=\fP) do not have frame numbers. .IP \(bu 2 When referencing specific packets, prefer frame numbers (\fB#487\fP) over line numbers, as frame numbers are stable across different terminal widths and formatting options. .UNINDENT .SS Practical Reading Guide .sp \fBTypical command\-response pair\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: Read BD ADDR (0x04|0x0009) plen 0 #13 [hci0] 12:35:04.057866 > HCI Event: Command Complete (0x0e) plen 10 #14 [hci0] 12:35:04.058750 Read BD ADDR (0x04|0x0009) ncmd 1 Status: Success (0x00) Address: 00:11:22:33:44:55 (OUI Company) .EE .UNINDENT .UNINDENT .sp Read this as: Frame #13, the host asked the controller for its Bluetooth address. Frame #14, the controller replied with success and the address \fB00:11:22:33:44:55\fP\&. The response arrived ~0.9ms later. .sp \fBTypical MGMT flow showing relation to HCI\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Command: Set Powered (0x0005) plen 1 {0x0001} [hci0] 12:35:04.033564 Powered: Enabled (0x01) < HCI Command: Reset (0x03|0x0003) plen 0 #7 [hci0] 12:35:04.033907 > HCI Event: Command Complete (0x0e) plen 4 #8 [hci0] 12:35:04.055753 Reset (0x03|0x0003) ncmd 2 Status: Success (0x00) ... (more HCI commands to configure the controller) ... @ MGMT Event: Command Complete (0x0001) plen 7 {0x0001} [hci0] 12:35:04.114789 Set Powered (0x0005) plen 4 Status: Success (0x00) .EE .UNINDENT .UNINDENT .sp Read this as: bluetoothd sent \fBSet Powered\fP via MGMT. The kernel translated this into a sequence of HCI commands (Reset, then configuration). After all HCI commands completed, the kernel sent the MGMT Command Complete event back to bluetoothd. .sp \fBConnection establishment flow\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: LE Meta Event (0x3e) plen 31 #487 [hci0] 12:36:18.974201 LE Enhanced Connection Complete (0x0a) Status: Success (0x00) Handle: 2048 Role: Peripheral (0x01) Peer address: AA:BB:CC:DD:EE:FF (OUI Company) @ MGMT Event: Device Connec.. (0x000b) plen 13 {0x0001} [hci0] 12:36:18.974319 = bluetoothd: src/adapter.c:connected_callback() hci0 devic.. 12:36:18.975307 < ACL: Handle 2048 [1.. flags 0x00 dlen 16 #493 [hci0] 12:36:18.977915 LE L2CAP: Connection Parameter Update Request (0x12) ident 1 len 8 < ACL: Handle 2048 [2/6] flags 0x00 dlen 7 #494 [hci0] 12:36:18.978488 ATT: Exchange MTU Request (0x02) len 2 Client RX MTU: 517 .EE .UNINDENT .UNINDENT .sp Read this as: The controller reported a new LE connection (HCI event). The kernel forwarded this as a MGMT Device Connected event. bluetoothd logged its \fBconnected_callback()\fP\&. Then data exchange began \-\- an L2CAP parameter update and ATT MTU negotiation over the new ACL connection. .SH ANALYZE MODE .sp The \fB\-a\fP (\fB\-\-analyze\fP) option reads a btsnoop file and produces a statistical summary instead of the full decoded trace. .SS Usage .INDENT 0.0 .INDENT 3.5 .sp .EX $ btmon \-a hcidump.log .EE .UNINDENT .UNINDENT .SS Output Contents .sp Analyze mode reports, for each controller found in the trace: .INDENT 0.0 .IP \(bu 2 \fBPacket counts\fP: Total HCI packets broken down by type (commands, events, ACL, SCO, ISO, vendor diagnostics, system notes, user logs, control messages). .IP \(bu 2 \fBPer\-connection statistics\fP: For each connection handle found: .INDENT 2.0 .IP \(bu 2 Connection type (BR\-ACL, LE\-ACL, BR\-SCO, BR\-ESCO, LE\-ISO) .IP \(bu 2 Device address .IP \(bu 2 TX and RX packet counts and completion counts .IP \(bu 2 Latency statistics (min, max, median) in milliseconds .IP \(bu 2 Packet size statistics (min, max, average) in octets .IP \(bu 2 Throughput estimate in Kb/s .UNINDENT .IP \(bu 2 \fBPer\-channel statistics\fP: For each L2CAP channel within a connection, the same packet/latency/size statistics. .IP \(bu 2 .INDENT 2.0 .TP .B \fBLatency plots\fP: If \fBgnuplot\fP is installed, ASCII\-art latency distribution plots are rendered in the terminal. .UNINDENT .UNINDENT .SH PROTOCOL ERROR CODES .sp btmon automatically decodes error codes from multiple protocol layers. This section provides a reference for interpreting errors seen across ATT, SMP, and L2CAP layers. .SS ATT Error Codes .sp ATT errors appear in \fBError Response (0x01)\fP PDUs. Beyond the GATT discovery context (where \fBAttribute Not Found\fP is normal), these errors indicate real problems: .TS box center; l|l|l. T{ Code T} T{ Error T} T{ Diagnostic Meaning T} _ T{ 0x01 T} T{ Invalid Handle T} T{ Client used a handle that does not exist T} _ T{ 0x02 T} T{ Read Not Permitted T} T{ Characteristic does not allow reads T} _ T{ 0x03 T} T{ Write Not Permitted T} T{ Characteristic does not allow writes T} _ T{ 0x05 T} T{ Authentication Insufficient T} T{ Operation requires an authenticated (MITM\-protected) bond. Triggers SMP pairing if not yet bonded. T} _ T{ 0x06 T} T{ Request Not Supported T} T{ Server does not support this ATT operation T} _ T{ 0x07 T} T{ Invalid Offset T} T{ Read/write blob offset exceeds attribute length T} _ T{ 0x08 T} T{ Authorization Insufficient T} T{ Server requires additional authorization T} _ T{ 0x09 T} T{ Prepare Queue Full T} T{ Too many prepared writes queued T} _ T{ 0x0a T} T{ Attribute Not Found T} T{ No attributes in requested range. Normal termination for GATT discovery procedures. T} _ T{ 0x0b T} T{ Attribute Not Long T} T{ Attribute cannot be read with Read Blob T} _ T{ 0x0c T} T{ Insufficient Encryption Key Size T} T{ Encryption key is too short T} _ T{ 0x0d T} T{ Invalid Attribute Value Length T} T{ Write value length is incorrect for this attribute T} _ T{ 0x0e T} T{ Unlikely Error T} T{ Generic unlikely error T} _ T{ 0x0f T} T{ Insufficient Encryption T} T{ Link is not encrypted. Triggers encryption setup. T} _ T{ 0x10 T} T{ Unsupported Group Type T} T{ Attribute type is not a valid grouping type T} _ T{ 0x11 T} T{ Insufficient Resources T} T{ Server out of resources T} _ T{ 0x12 T} T{ Value Not Allowed T} T{ Value is not within permitted range T} _ T{ 0x80\-0x9f T} T{ Application Error T} T{ Application\-specific error; meaning depends on the profile/service. ASCS uses these for ASE\-specific errors. T} _ T{ 0xfc T} T{ Write Request Rejected T} T{ Write rejected (CSIP, ASCS) T} _ T{ 0xfd T} T{ CCC Descriptor Improperly Configured T} T{ CCC must be enabled before certain operations T} _ T{ 0xfe T} T{ Procedure Already in Progress T} T{ Another procedure is already running T} _ T{ 0xff T} T{ Out of Range T} T{ Value is outside valid range T} .TE .SS L2CAP Connection Response Results .sp L2CAP Connection Response and LE Connection Response include a result code: .TS box center; l|l|l. T{ Code T} T{ Result T} T{ Diagnostic Meaning T} _ T{ 0x0000 T} T{ Connection successful T} T{ Channel established normally T} _ T{ 0x0001 T} T{ Connection pending T} T{ Connection in progress (BR/EDR only) T} _ T{ 0x0002 T} T{ Connection refused \- PSM not supported T} T{ Remote does not have a server for this protocol T} _ T{ 0x0003 T} T{ Connection refused \- security block T} T{ Security requirements not met T} _ T{ 0x0004 T} T{ Connection refused \- no resources T} T{ Remote ran out of channel resources T} _ T{ 0x0005 T} T{ Connection refused \- invalid Source CID T} T{ Source CID is invalid or already in use T} _ T{ 0x0006 T} T{ Connection refused \- Source CID already allocated T} T{ CID collision T} _ T{ 0x0007 T} T{ Connection refused \- unacceptable parameters T} T{ LE credit\-based: MTU, MPS, or credits unacceptable T} _ T{ 0x0008 T} T{ Connection refused \- invalid parameters T} T{ Parameter values are invalid T} _ T{ 0x0009 T} T{ Connection refused \- insufficient authentication T} T{ Not authenticated T} _ T{ 0x000a T} T{ Connection refused \- insufficient authorization T} T{ Not authorized T} _ T{ 0x000b T} T{ Connection refused \- insufficient encryption key size T} T{ Encryption key too short T} _ T{ 0x000c T} T{ Connection refused \- insufficient encryption T} T{ Link not encrypted T} .TE .SS Automating Error Detection .sp \fBFind all ATT errors\fP (excluding normal discovery termination): .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqError Response\(dq output.txt .EE .UNINDENT .UNINDENT .sp Then check whether each error is \fBAttribute Not Found (0x0a)\fP within a discovery sequence (normal) or a different error code (problem). .sp \fBFind all authentication/encryption related errors\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqAuthentication Insufficient\e|Insufficient Encryption\e|Insufficient Security\e|security block\(dq output.txt .EE .UNINDENT .UNINDENT .sp These indicate the link needs pairing or encryption. Check whether SMP pairing follows. .sp \fBFind all L2CAP channel rejections\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqConnection refused\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBCross\-layer error correlation\fP: .sp Errors often cascade across layers. Common patterns: .INDENT 0.0 .IP 1. 3 ATT \fBInsufficient Encryption\fP (0x0f) → triggers HCI \fBLE Start Encryption\fP → \fBEncryption Change\fP success → ATT operation retried .IP 2. 3 ATT \fBAuthentication Insufficient\fP (0x05) → triggers SMP \fBPairing Request\fP → pairing completes → ATT operation retried .IP 3. 3 SMP \fBPairing Failed\fP → \fBDisconnect Complete\fP with reason \fBAuthentication Failure (0x05)\fP .IP 4. 3 L2CAP \fBConnection refused \- security block\fP → triggers SMP pairing .UNINDENT .SH PROTOCOL FLOWS .\" This file is included by btmon.rst. . .SH HCI INITIALIZATION SEQUENCE .sp Every btsnoop trace that captures controller startup begins with a dense block of HCI commands and events. This is the kernel\(aqs Bluetooth subsystem initializing the controller through a multi\-stage sequence defined in \fBnet/bluetooth/hci_sync.c\fP\&. Understanding this sequence helps distinguish normal initialization traffic from application\-level issues. .SS Overview .sp The kernel initializes a Bluetooth controller in four stages after opening the HCI device. Each stage sends a batch of HCI commands and waits for their completion before proceeding to the next. The full call chain is: .INDENT 0.0 .INDENT 3.5 .sp .EX hci_power_on_sync └─ hci_dev_open_sync └─ hci_dev_init_sync ├─ hci_dev_setup_sync (driver setup + quirks) └─ hci_init_sync ├─ Stage 1: Reset + identity ├─ Stage 2: Capabilities + buffer sizes ├─ Stage 3: Event masks + policy └─ Stage 4: Final configuration .EE .UNINDENT .UNINDENT .sp After all four stages complete, a post\-init phase (\fBhci_powered_update_sync\fP) configures runtime parameters like SSP, advertising, and scan settings. .sp For unconfigured devices (e.g. controllers that need firmware or a BD address programmed), only a minimal \fBStage 0\fP runs to identify the hardware. .SS Stage 0: Reset and Basic Identity (Unconfigured Only) .sp This stage runs only for unconfigured controllers that need setup before full initialization. .sp \fBCommands sent:\fP .TS box center; l|l. T{ HCI Command T} T{ Purpose T} _ T{ \fBHCI_Reset\fP T} T{ Reset the controller (skipped if \fBRESET_ON_CLOSE\fP quirk) T} _ T{ \fBHCI_Read_Local_Version_Information\fP T} T{ Read hardware/firmware version T} _ T{ \fBHCI_Read_BD_ADDR\fP T} T{ Read the controller\(aqs Bluetooth address T} .TE .SS Stage 1: Reset and Read Local Features .sp Resets the controller and reads core identity and capability information. .sp \fBCommands sent:\fP .TS box center; l|l. T{ HCI Command T} T{ Purpose T} _ T{ \fBHCI_Reset\fP T} T{ Reset the controller T} _ T{ \fBHCI_Read_Local_Supported_Features\fP T} T{ Read LMP feature bitmask (BR/EDR, LE, SSP, etc.) T} _ T{ \fBHCI_Read_Local_Version_Information\fP T} T{ Read HCI version, LMP version, manufacturer T} _ T{ \fBHCI_Read_BD_ADDR\fP T} T{ Read the public Bluetooth address T} .TE .SS Stage 2: Read Capabilities and Setup .sp Reads detailed capabilities, enables core features, and reads buffer sizes. This stage has three phases: common commands, BR/EDR\-specific commands, and LE\-specific commands. .SS Common Commands .TS box center; l|l. T{ HCI Command T} T{ Purpose T} _ T{ \fBHCI_Read_Local_Supported_Commands\fP T} T{ Read the supported command bitmask (HCI 1.2+) T} _ T{ \fBHCI_Write_Simple_Pairing_Mode\fP (enable) T} T{ Enable SSP if supported and configured T} _ T{ \fBHCI_Write_Extended_Inquiry_Response\fP (clear) T} T{ Clear EIR data when SSP is disabled T} _ T{ \fBHCI_Write_Inquiry_Mode\fP T} T{ Set inquiry mode (RSSI or Extended, based on features) T} _ T{ \fBHCI_Read_Inquiry_Response_Transmit_Power_Level\fP T} T{ Read inquiry TX power if supported T} _ T{ \fBHCI_Read_Local_Extended_Features\fP (page 1) T} T{ Read extended feature page 1 (SSP host, LE host, etc.) T} _ T{ \fBHCI_Write_Authentication_Enable\fP T} T{ Sync authentication state with \fBLINK_SECURITY\fP flag T} .TE .SS BR/EDR Commands (if BR/EDR capable) .TS box center; l|l. T{ HCI Command T} T{ Purpose T} _ T{ \fBHCI_Read_Buffer_Size\fP T} T{ Read ACL/SCO buffer sizes and count T} _ T{ \fBHCI_Read_Class_of_Device\fP T} T{ Read current device class T} _ T{ \fBHCI_Read_Local_Name\fP T} T{ Read the stored local name T} _ T{ \fBHCI_Read_Voice_Setting\fP T} T{ Read SCO voice setting (if supported) T} _ T{ \fBHCI_Read_Number_of_Supported_IAC\fP T} T{ Read number of supported inquiry access codes T} _ T{ \fBHCI_Read_Current_IAC_LAP\fP T} T{ Read current IAC LAP values T} _ T{ \fBHCI_Set_Event_Filter\fP (clear all) T} T{ Clear any stored event filters T} _ T{ \fBHCI_Write_Connection_Accept_Timeout\fP T} T{ Set connection accept timeout (~20 seconds) T} _ T{ \fBHCI_Write_Synchronous_Flow_Control_Enable\fP T} T{ Enable SCO flow control if supported T} .TE .SS LE Commands (if LE capable) .TS box center; l|l. T{ HCI Command T} T{ Purpose T} _ T{ \fBLE_Read_Local_Supported_Features\fP T} T{ Read LE feature bitmask T} _ T{ \fBLE_Read_All_Local_Supported_Features\fP T} T{ Read extended LE features (if supported) T} _ T{ \fBLE_Read_Buffer_Size\fP [v2] or [v1] T} T{ Read LE ACL (and ISO) buffer sizes; v2 used when ISO capable T} _ T{ \fBLE_Read_Supported_States\fP T} T{ Read the LE state combination table T} .TE .SS Stage 3: Event Masks, Link Policy, and Features .sp Configures which events the controller should report, sets link policy, and reads extended feature pages. This is the longest stage. .SS Event Masks and Link Policy .TS box center; l|l. T{ HCI Command T} T{ Purpose T} _ T{ \fBHCI_Set_Event_Mask\fP T} T{ Configure the main event mask based on controller capabilities T} _ T{ \fBHCI_Read_Stored_Link_Key\fP T} T{ Read all stored link keys T} _ T{ \fBHCI_Write_Default_Link_Policy_Settings\fP T} T{ Enable role switch, hold, sniff, park based on LMP features T} _ T{ \fBHCI_Read_Page_Scan_Activity\fP T} T{ Read page scan interval and window T} _ T{ \fBHCI_Read_Default_Erroneous_Data_Reporting\fP T} T{ Read error data reporting state (for wideband speech) T} _ T{ \fBHCI_Read_Page_Scan_Type\fP T} T{ Read page scan type (standard or interlaced) T} _ T{ \fBHCI_Read_Local_Extended_Features\fP (pages 2..N) T} T{ Read all remaining extended feature pages T} .TE .sp \fBEvent mask details:\fP For dual\-mode controllers the kernel enables events for inquiry results (RSSI and extended), SSP (IO capability, user confirmation, passkey), synchronous connections, sniff subrating, encryption refresh, link supervision, and LE meta\-events. For LE\-only controllers a minimal mask covers only command completion, hardware errors, disconnection, and encryption changes. .SS LE Event Mask and Capabilities .TS box center; l|l. T{ HCI Command T} T{ Purpose T} _ T{ \fBLE_Set_Event_Mask\fP T} T{ Configure which LE sub\-events are reported T} _ T{ \fBLE_Read_Advertising_Channel_Tx_Power\fP T} T{ Read advertising TX power (legacy advertising only) T} _ T{ \fBLE_Read_Transmit_Power\fP T} T{ Read min/max transmit power range T} _ T{ \fBLE_Read_Accept_List_Size\fP T} T{ Read filter accept list capacity T} _ T{ \fBLE_Clear_Accept_List\fP T} T{ Clear the filter accept list T} _ T{ \fBLE_Read_Resolving_List_Size\fP T} T{ Read resolving list capacity (LL Privacy) T} _ T{ \fBLE_Clear_Resolving_List\fP T} T{ Clear the resolving list T} _ T{ \fBLE_Set_Resolvable_Private_Address_Timeout\fP T} T{ Set RPA rotation timeout T} _ T{ \fBLE_Read_Maximum_Data_Length\fP T} T{ Read max TX/RX octets and time (Data Length Extension) T} _ T{ \fBLE_Read_Suggested_Default_Data_Length\fP T} T{ Read current default data length T} _ T{ \fBLE_Read_Number_of_Supported_Advertising_Sets\fP T} T{ Read extended advertising set capacity T} _ T{ \fBHCI_Write_LE_Host_Supported\fP T} T{ Notify controller of host LE support (dual\-mode only) T} _ T{ \fBLE_Set_Host_Feature\fP T} T{ Enable CIS Central (bit 32) and/or Channel Sounding (bit 47) T} .TE .sp \fBLE event mask details:\fP The kernel enables LE sub\-events based on features: connection complete (enhanced if available), advertising reports (extended if available), long term key request, connection parameter request, data length change, PHY update, channel selection algorithm, periodic advertising events, CIS established/request (if CIS capable), BIG create/sync/info (if BIS capable), and channel sounding events (if CS capable). .SS Stage 4: Final Configuration .sp Performs final setup: deletes stale keys, sets event mask page 2, reads codec information, enables Secure Connections, and configures LE data length and PHY defaults. .SS Keys, Codecs, and Secure Connections .TS box center; l|l. T{ HCI Command T} T{ Purpose T} _ T{ \fBHCI_Delete_Stored_Link_Key\fP (all) T} T{ Delete all stored link keys from controller T} _ T{ \fBHCI_Set_Event_Mask_Page_2\fP T} T{ Enable page 2 events (authenticated payload timeout, etc.) T} _ T{ \fBHCI_Read_Local_Supported_Codecs\fP [v2] or [v1] T} T{ Read supported codec IDs; v2 includes transport type info T} _ T{ \fBHCI_Read_Local_Pairing_Options\fP T} T{ Read default pairing options (max encryption key size) T} _ T{ \fBHCI_Get_MWS_Transport_Layer_Configuration\fP T} T{ Read MWS coexistence config if supported T} _ T{ \fBHCI_Read_Synchronization_Train_Parameters\fP T} T{ Read sync train params (Connectionless Peripheral Broadcast) T} _ T{ \fBHCI_Write_Secure_Connections_Support\fP (enable) T} T{ Enable Secure Connections if SSP active T} _ T{ \fBHCI_Write_Default_Erroneous_Data_Reporting\fP T} T{ Enable/disable based on wideband speech setting T} .TE .SS LE Data Length and PHY Defaults .TS box center; l|l. T{ HCI Command T} T{ Purpose T} _ T{ \fBLE_Write_Suggested_Default_Data_Length\fP T} T{ Set default TX octets/time for new connections T} _ T{ \fBLE_Set_Default_PHY\fP T} T{ Set preferred PHY (1M always; 2M and Coded if supported) T} .TE .SS Post\-Initialization .sp After the four stages complete, \fBhci_powered_update_sync\fP runs to apply runtime configuration: .TS box center; l|l. T{ Action T} T{ Purpose T} _ T{ \fBHCI_Write_Simple_Pairing_Mode\fP T} T{ Re\-enable SSP + Secure Connections if configured T} _ T{ \fBHCI_Write_LE_Host_Supported\fP T} T{ Sync LE host support state T} _ T{ LE advertising setup T} T{ Configure advertising parameters and data T} _ T{ \fBHCI_Write_Authentication_Enable\fP T} T{ Sync authentication enable state T} _ T{ Scan/class/name/EIR updates T} T{ Configure page scan, device class, local name, EIR data T} _ T{ \fBLE_Set_Random_Address\fP T} T{ Set static random address if no public address T} .TE .SS Reading the Init Sequence in a Trace .sp When examining a btsnoop trace, the initialization block is the first thing after the controller is opened. A typical dual\-mode controller trace starts with: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: Reset > HCI Event: Command Complete (Reset) < HCI Command: Read Local Supported Features > HCI Event: Command Complete (Read Local Supported Features) < HCI Command: Read Local Version Information > HCI Event: Command Complete (Read Local Version Information) < HCI Command: Read BD ADDR > HCI Event: Command Complete (Read BD ADDR) \&... [Stage 2\-4 commands follow] .EE .UNINDENT .UNINDENT .sp \fBKey things to look for:\fP .INDENT 0.0 .IP \(bu 2 \fBMissing commands\fP: If expected commands are absent, the controller may not support the corresponding feature. For example, no \fBLE_Read_Buffer_Size\fP means the controller is BR/EDR only. .IP \(bu 2 \fBCommand failures\fP: A \fBStatus\fP other than \fB0x00\fP in a Command Complete event during init usually indicates a broken controller or unsupported feature. The kernel handles most gracefully, but persistent errors may prevent the adapter from functioning. .IP \(bu 2 \fBBuffer sizes\fP: The values returned by \fBRead_Buffer_Size\fP and \fBLE_Read_Buffer_Size\fP determine how many in\-flight packets the controller can hold. Small buffer counts can cause throughput issues. .IP \(bu 2 \fBFeature bits\fP: The \fBRead_Local_Supported_Features\fP response reveals what the controller supports (LE, SSP, eSCO, etc.). Cross reference with the commands that follow — the kernel only sends commands for features the controller reports supporting. .IP \(bu 2 \fBEvent mask\fP: The \fBSet_Event_Mask\fP command shows exactly which events the host wants to receive. If an expected event never appears in the trace, check whether it was enabled in the mask. .IP \(bu 2 \fBLE\-only controllers\fP: These skip all BR/EDR commands (\fBRead_Buffer_Size\fP, \fBRead_Local_Name\fP, link policy, etc.) and use a minimal event mask. The trace will be noticeably shorter. .IP \(bu 2 \fBVendor commands\fP: Some controllers (Intel, Broadcom, Qualcomm, Realtek, MediaTek) insert vendor\-specific HCI commands between stages for firmware download, configuration, or patch application. These appear as opcode groups \fB0x3F\fP (vendor) and are driver\-specific. .UNINDENT .\" This file is included by btmon.rst. . .SH CONNECTION TRACKING .sp HCI uses \fBconnection handles\fP (16\-bit integers) to identify individual connections. Understanding how handles map to devices is essential for reading traces. .SS Handle Types .sp Different connection types use different handle ranges, but these ranges are controller\-specific and not standardized. The connection type can be determined by looking at the event that created the handle: .TS box center; l|l|l. T{ Type T} T{ Creation Event T} T{ Description T} _ T{ BR/EDR ACL T} T{ Connection Complete T} T{ Classic Bluetooth data connection T} _ T{ LE ACL T} T{ LE (Enhanced) Connection Complete T} T{ Low Energy data connection T} _ T{ CIS T} T{ LE CIS Established T} T{ Connected Isochronous Stream (LE Audio) T} _ T{ BIS T} T{ LE BIG Complete T} T{ Broadcast Isochronous Stream (LE Audio) T} _ T{ SCO/eSCO T} T{ Synchronous Connection Complete T} T{ Voice/audio synchronous connection (classic) T} .TE .sp A single device may have multiple handles simultaneously. For example, an LE Audio device will have an LE ACL handle for control traffic and one or more CIS handles for audio streams. The \fBLE CIS Established\fP event includes the ACL connection handle that the CIS is associated with. .SS Controller Buffer Tracking .sp Buffer tracking may show a indicator in square brackets: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL: Handle 2048 [1/6] flags 0x00 dlen 16 .EE .UNINDENT .UNINDENT .sp The \fB[1/6]\fP means this is buffer slot 1 of 6 available controller ACL buffers. This reflects the host\-side HCI flow control: the host tracks how many buffers the controller has available and shows the current usage. When the controller sends \fBNumber of Completed Packets\fP events, buffers are freed and the count decreases. .SH HCI ERROR AND DISCONNECT REASON CODES .sp HCI status and disconnect reason codes use the same code space. These appear in \fBStatus:\fP and \fBReason:\fP fields throughout the trace. btmon decodes them automatically, but the hex values are useful for searching and filtering. .SS Common Disconnect Reasons .TS box center; l|l|l. T{ Code T} T{ Name T} T{ Diagnostic Meaning T} _ T{ 0x05 T} T{ Authentication Failure T} T{ Pairing or encryption setup failed. Key may be stale or devices have mismatched security databases. T} _ T{ 0x08 T} T{ Connection Timeout T} T{ The supervision timer expired. The remote device moved out of range or stopped responding. This is an RF link loss. T} _ T{ 0x13 T} T{ Remote User Terminated Connection T} T{ The remote device intentionally disconnected. This is the normal graceful disconnect. T} _ T{ 0x14 T} T{ Remote Device Terminated due to Low Resources T} T{ The remote device ran out of resources (memory, connection slots). T} _ T{ 0x15 T} T{ Remote Device Terminated due to Power Off T} T{ The remote device is powering down. T} _ T{ 0x16 T} T{ Connection Terminated By Local Host T} T{ The local BlueZ stack intentionally disconnected. Normal when bluetoothd initiates disconnect. T} _ T{ 0x1f T} T{ Unspecified Error T} T{ Generic error. Often indicates a firmware issue. T} _ T{ 0x22 T} T{ LMP/LL Response Timeout T} T{ Link layer procedure timed out. The remote device stopped responding to LL control PDUs. T} _ T{ 0x28 T} T{ Instant Passed T} T{ A timing\-critical operation missed its deadline. Often seen with connection parameter updates. T} _ T{ 0x2f T} T{ Insufficient Security T} T{ The required security level (encryption, MITM protection) was not met. T} _ T{ 0x3b T} T{ Unacceptable Connection Parameters T} T{ The remote rejected a connection parameter update. T} _ T{ 0x3d T} T{ Connection Terminated due to MIC Failure T} T{ Encryption integrity check failed. Possible key mismatch or corruption. T} _ T{ 0x3e T} T{ Connection Failed to be Established T} T{ Connection attempt failed entirely (e.g., the remote device did not respond to connection requests). T} _ T{ 0x3f T} T{ MAC Connection Failed T} T{ MAC\-level connection failure. T} _ T{ 0x44 T} T{ Operation Cancelled by Host T} T{ The host cancelled the operation before it completed. T} .TE .SS Full Error Code Table .sp The complete set of HCI error codes (0x00\-0x45) is defined in the Bluetooth Core Specification, Volume 1, Part F. btmon decodes all of them automatically in \fBStatus:\fP and \fBReason:\fP fields. The source mapping is in \fBmonitor/packet.c\fP (\fBerror2str_table\fP). .\" This file is included by btmon.rst. . .SH RECONSTRUCTING A GATT DATABASE FROM SNOOP TRACES .sp A btsnoop trace contains the complete ATT protocol exchange used by GATT clients and servers to discover each other\(aqs services. By reading the discovery requests and responses, it is possible to reconstruct the full GATT database of a remote device \-\- even without access to the device itself. .sp This section explains the GATT discovery procedure and how each ATT operation appears in \fBbtmon\fP output. .SS Overview of GATT Discovery .sp GATT discovery is a multi\-phase process where a client queries the server\(aqs attribute database using ATT protocol operations. The phases are: .INDENT 0.0 .IP 1. 3 \fBPrimary Service Discovery\fP \-\- Find all primary services and their handle ranges. .IP 2. 3 \fBSecondary Service Discovery\fP \-\- Find any secondary (included\-only) services. .IP 3. 3 \fBIncluded Service Discovery\fP \-\- Find which services include other services. .IP 4. 3 \fBCharacteristic Discovery\fP \-\- Find all characteristics within each service. .IP 5. 3 \fBDescriptor Discovery\fP \-\- Find all descriptors for each characteristic. .IP 6. 3 \fBCharacteristic Value Reading\fP \-\- Read the values of readable characteristics. .UNINDENT .sp Each phase uses a specific ATT operation and produces a request/response pattern in the trace. The client repeats each request with advancing handle ranges until the server responds with \fBAttribute Not Found\fP, indicating the end of that phase. .SS Phase 1: Primary Service Discovery (Read By Group Type) .sp The client discovers primary services using \fBRead By Group Type Request\fP with the \fBPrimary Service\fP UUID (0x2800) as the group type. .sp \fBRequest\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 11 #516 [hci0] 0.124726 ATT: Read By Group Type Request (0x10) len 6 Handle range: 0x0001\-0xffff Attribute group type: Primary Service (0x2800) .EE .UNINDENT .UNINDENT .sp The first request always starts at handle 0x0001 and searches through 0xffff (the entire handle space). .sp \fBResponse\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 42 #523 [hci0] 0.240151 ATT: Read By Group Type Response (0x11) len 37 Attribute data length: 6 Attribute group list: 6 entries Handle range: 0x0001\-0x0009 UUID: Generic Access Profile (0x1800) Handle range: 0x000a\-0x0011 UUID: Generic Attribute Profile (0x1801) Handle range: 0x0012\-0x0014 UUID: Device Information (0x180a) Handle range: 0x0015\-0x0039 UUID: Generic Telephony Bearer (0x184c) Handle range: 0x003a\-0x0059 UUID: Generic Media Control (0x1849) Handle range: 0x005a\-0x005c UUID: Telephony and Media Audio (0x1855) .EE .UNINDENT .UNINDENT .sp Each entry provides: .INDENT 0.0 .IP \(bu 2 \fBHandle range\fP \-\- The start and end handle of the service. All attributes belonging to this service (characteristics, descriptors) have handles within this range. .IP \(bu 2 \fBUUID\fP \-\- The service UUID. Standard 16\-bit UUIDs are shown with their name (e.g., \fBGeneric Access Profile\fP). 128\-bit vendor\-specific UUIDs appear as full UUID strings. .UNINDENT .sp The client continues by sending another request starting after the last handle in the response: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 11 #525 [hci0] 0.240641 ATT: Read By Group Type Request (0x10) len 6 Handle range: 0x005d\-0xffff Attribute group type: Primary Service (0x2800) .EE .UNINDENT .UNINDENT .sp This continues until the server responds with \fBAttribute Not Found\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 9 #532 [hci0] 0.360069 ATT: Error Response (0x01) len 4 Read By Group Type Request (0x10) Handle: 0x005d Error: Attribute Not Found (0x0a) .EE .UNINDENT .UNINDENT .sp This error indicates that no more primary services exist beyond handle 0x005d. The client now has the complete list of primary services. .sp \fBNote:\fP .INDENT 0.0 .INDENT 3.5 The \fBAttribute data length\fP field indicates the size of each entry in the response. A value of 6 means 16\-bit UUIDs (2 bytes start handle + 2 bytes end handle + 2 bytes UUID). A value of 20 means 128\-bit UUIDs (2 + 2 + 16). If the server has both 16\-bit and 128\-bit service UUIDs, they are returned in separate responses because all entries in a single response must have the same length. .UNINDENT .UNINDENT .SS Phase 2: Secondary Service Discovery .sp After primary services, the client may discover secondary services using the same \fBRead By Group Type Request\fP but with the \fBSecondary Service\fP UUID (0x2801): .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 11 #534 [hci0] 0.360752 ATT: Read By Group Type Request (0x10) len 6 Handle range: 0x0001\-0xffff Attribute group type: Secondary Service (0x2801) .EE .UNINDENT .UNINDENT .sp If no secondary services exist, the server responds with \fBAttribute Not Found\fP\&. Secondary services are not directly accessible to clients \-\- they are only reachable via include references from primary services. .SS Phase 3: Included Service Discovery (Read By Type) .sp To discover which services include other services, the client uses \fBRead By Type Request\fP with the \fBInclude\fP UUID (0x2802): .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 11 #540 [hci0] 0.480731 ATT: Read By Type Request (0x08) len 6 Handle range: 0x0001\-0x005c Attribute type: Include (0x2802) .EE .UNINDENT .UNINDENT .sp The handle range typically spans the entire discovered database. Each include declaration in the response identifies a service that is included by the service containing that handle. .SS Phase 4: Characteristic Discovery (Read By Type) .sp For each service, the client discovers its characteristics using \fBRead By Type Request\fP with the \fBCharacteristic\fP UUID (0x2803). The handle range is limited to the service\(aqs handle range. .sp \fBRequest\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 11 #531 [hci0] 0.360063 ATT: Read By Type Request (0x08) len 6 Handle range: 0x0008\-0x0011 Attribute type: Characteristic (0x2803) .EE .UNINDENT .UNINDENT .sp \fBResponse\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 27 #533 [hci0] 0.360714 ATT: Read By Type Response (0x09) len 22 Attribute data length: 7 Attribute data list: 3 entries Handle: 0x0009 Value[5]: 200a00052a Properties: 0x20 Indicate (0x20) Value Handle: 0x000a Value UUID: Service Changed (0x2a05) Handle: 0x000c Value[5]: 0a0d00292b Properties: 0x0a Read (0x02) Write (0x08) Value Handle: 0x000d Value UUID: Client Supported Features (0x2b29) Handle: 0x000e Value[5]: 020f002a2b Properties: 0x02 Read (0x02) Value Handle: 0x000f Value UUID: Database Hash (0x2b2a) .EE .UNINDENT .UNINDENT .sp Each characteristic entry provides: .INDENT 0.0 .IP \(bu 2 \fBHandle\fP \-\- The handle of the characteristic declaration attribute. .IP \(bu 2 \fBProperties\fP \-\- A bitmask indicating supported operations: .TS box center; l|l|l. T{ Bit T} T{ Property T} T{ Description T} _ T{ 0x01 T} T{ Broadcast T} T{ Can be broadcast in advertising data T} _ T{ 0x02 T} T{ Read T} T{ Can be read T} _ T{ 0x04 T} T{ Write Without Response T} T{ Can be written without acknowledgment T} _ T{ 0x08 T} T{ Write T} T{ Can be written with acknowledgment T} _ T{ 0x10 T} T{ Notify T} T{ Server can send notifications T} _ T{ 0x20 T} T{ Indicate T} T{ Server can send indications T} _ T{ 0x40 T} T{ Authenticated Signed Writes T} T{ Supports signed write commands T} _ T{ 0x80 T} T{ Extended Properties T} T{ Has extended properties descriptor T} .TE .IP \(bu 2 \fBValue Handle\fP \-\- The handle where the characteristic\(aqs value is stored (always declaration handle + 1). .IP \(bu 2 \fBValue UUID\fP \-\- The UUID identifying the characteristic type. .UNINDENT .sp The client continues with advancing handle ranges until it receives \fBAttribute Not Found\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 9 #572 [hci0] 1.200228 ATT: Error Response (0x01) len 4 Read By Type Request (0x08) Handle: 0x005c Error: Attribute Not Found (0x0a) .EE .UNINDENT .UNINDENT .SS Phase 5: Descriptor Discovery (Find Information) .sp Descriptors occupy the handles between a characteristic\(aqs value handle and the next characteristic declaration (or end of service). The client discovers them using \fBFind Information Request\fP\&. .sp \fBRequest\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 9 #556 [hci0] 0.959965 ATT: Find Information Request (0x04) len 4 Handle range: 0x000b\-0x000b .EE .UNINDENT .UNINDENT .sp The handle range covers the gap between the characteristic value handle and the next characteristic declaration handle. .sp \fBResponse\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 10 #561 [hci0] 0.961049 ATT: Find Information Response (0x05) len 5 Format: UUID\-16 (0x01) Handle: 0x000b UUID: Client Characteristic Configuration (0x2902) .EE .UNINDENT .UNINDENT .sp Common descriptor UUIDs: .TS box center; l|l|l. T{ UUID T} T{ Name T} T{ Purpose T} _ T{ 0x2900 T} T{ Characteristic Extended Properties T} T{ Additional property bits T} _ T{ 0x2901 T} T{ Characteristic User Description T} T{ Human\-readable description string T} _ T{ 0x2902 T} T{ Client Characteristic Configuration (CCC) T} T{ Enable/disable notifications or indications T} _ T{ 0x2903 T} T{ Server Characteristic Configuration T} T{ Server\-side broadcast configuration T} _ T{ 0x2904 T} T{ Characteristic Presentation Format T} T{ Data format, exponent, unit T} .TE .SS Phase 6: Reading Characteristic Values .sp After discovery, the client may read characteristic values using \fBRead Request\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 7 #577 [hci0] 1.380203 ATT: Read Request (0x0a) len 2 Handle: 0x000f < ACL Data TX: Handle 2048 flags 0x00 dlen 21 #579 [hci0] 1.380774 ATT: Read Response (0x0b) len 16 Value[16]: a470d508da8751a2a50b79da0250bfda .EE .UNINDENT .UNINDENT .sp The \fBHandle\fP in the request corresponds to a characteristic value handle from the discovery phase. btmon shows the raw value bytes; the interpretation depends on the characteristic UUID. .SS Find By Type Value (Targeted Service Search) .sp In addition to discovering all services, a client can search for a specific service UUID using \fBFind By Type Value Request\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 13 #513 [hci0] 0.124195 ATT: Find By Type Value Request (0x06) len 8 Handle range: 0x0001\-0xffff Attribute type: Primary Service (0x2800) UUID: Generic Attribute Profile (0x1801) < ACL Data TX: Handle 2048 flags 0x00 dlen 9 #515 [hci0] 0.124684 ATT: Find By Type Value Response (0x07) len 4 Handle range: 0x0008\-0x0011 .EE .UNINDENT .UNINDENT .sp This returns only the handle range for the matching service, without iterating through all services. If the service is not found: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 9 #524 [hci0] 0.240607 ATT: Error Response (0x01) len 4 Find By Type Value Request (0x06) Handle: 0x0012 Error: Attribute Not Found (0x0a) .EE .UNINDENT .UNINDENT .SS Bidirectional Discovery .sp Both devices in a connection can act as GATT client and server simultaneously. In a btsnoop trace, you may see interleaved discovery in both directions: .INDENT 0.0 .IP \(bu 2 \fBTX (\(ga\(ga<\(ga\(ga) requests + RX (\(ga\(ga>\(ga\(ga) responses\fP \-\- The local device (whose trace this is) is acting as a GATT client, discovering the remote device\(aqs services. .IP \(bu 2 \fBRX (\(ga\(ga>\(ga\(ga) requests + TX (\(ga\(ga<\(ga\(ga) responses\fP \-\- The remote device is acting as a GATT client, discovering the local device\(aqs services. .UNINDENT .sp For example, the local server responding to the remote\(aqs discovery: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 11 #584 [hci0] 1.512006 ATT: Read By Group Type Request (0x10) len 6 Handle range: 0x0001\-0xffff Attribute group type: Primary Service (0x2800) < ACL Data TX: Handle 2048 flags 0x00 dlen 66 #586 [hci0] 1.518778 ATT: Read By Group Type Response (0x11) len 61 Attribute data length: 6 Attribute group list: 10 entries Handle range: 0x0001\-0x0007 UUID: Generic Access Profile (0x1800) Handle range: 0x0008\-0x0011 UUID: Generic Attribute Profile (0x1801) Handle range: 0x0012\-0x0014 UUID: Device Information (0x180a) Handle range: 0x0015\-0x001e UUID: Coordinated Set Identification (0x1846) Handle range: 0x001f\-0x0020 UUID: Common Audio (0x1853) Handle range: 0x0021\-0x0024 UUID: Microphone Control (0x184d) Handle range: 0x0041\-0x004b UUID: Volume Control (0x1844) Handle range: 0x006b\-0x0073 UUID: Broadcast Audio Scan (0x184f) Handle range: 0x0074\-0x0086 UUID: Published Audio Capabilities (0x1850) Handle range: 0x0087\-0x0096 UUID: Audio Stream Control (0x184e) .EE .UNINDENT .UNINDENT .sp This shows the local device\(aqs own GATT database as seen by the remote. To reconstruct the remote device\(aqs database, focus on the TX requests and RX responses (the local device acting as client). .SS Building the Attribute Table .sp To reconstruct the GATT database, extract the discovery responses and organize them into a table. Using the trace above as an example, the remote device at address 00:11:22:33:44:55 has: .sp \fBServices\fP (from Read By Group Type Response): .INDENT 0.0 .INDENT 3.5 .sp .EX Handle Range UUID Service Name ────────────── ────────────────────────────── ──────────────────────────── 0x0001\-0x0009 0x1800 Generic Access Profile 0x000a\-0x0011 0x1801 Generic Attribute Profile 0x0012\-0x0014 0x180a Device Information 0x0015\-0x0039 0x184c Generic Telephony Bearer 0x003a\-0x0059 0x1849 Generic Media Control 0x005a\-0x005c 0x1855 Telephony and Media Audio .EE .UNINDENT .UNINDENT .sp \fBCharacteristics\fP (from Read By Type Response, within GAP 0x0001\-0x0009): .INDENT 0.0 .INDENT 3.5 .sp .EX Handle Value Handle Properties UUID Name ────── ──────────── ────────── ────── ──────────────────────────────── 0x0002 0x0003 Read 0x2a00 Device Name 0x0004 0x0005 Read 0x2a01 Appearance 0x0006 0x0007 Read 0x2a04 Peripheral Preferred Conn Params 0x0008 0x0009 Read 0x2aa6 Central Address Resolution .EE .UNINDENT .UNINDENT .sp \fBCharacteristics\fP (within GATT 0x000a\-0x0011): .INDENT 0.0 .INDENT 3.5 .sp .EX Handle Value Handle Properties UUID Name ────── ──────────── ─────────────── ────── ──────────────────────────── 0x000b 0x000c Indicate 0x2a05 Service Changed 0x000e 0x000f Read, Write 0x2b29 Client Supported Features 0x0010 0x0011 Read 0x2b2a Database Hash .EE .UNINDENT .UNINDENT .sp \fBDescriptors\fP (from Find Information Response): .INDENT 0.0 .INDENT 3.5 .sp .EX Handle UUID Name ────── ────── ──────────────────────────────────── 0x000d 0x2902 Client Characteristic Configuration .EE .UNINDENT .UNINDENT .sp The CCC descriptor at handle 0x000d belongs to the Service Changed characteristic (0x000c), because it falls between that value handle and the next characteristic declaration at 0x000e. .\" This file is included by btmon.rst. . .SH SMP PAIRING FLOW .sp The Security Manager Protocol (SMP) handles pairing, key generation, and key distribution between Bluetooth devices. SMP traffic appears inside L2CAP on fixed CID 0x0006 (LE) or CID 0x0007 (BR/EDR). btmon decodes all SMP operations automatically. .SS Pairing Phases .sp SMP pairing proceeds in three phases. Each phase produces a distinct pattern in the btmon output. .sp \fBPhase 1: Feature Exchange\fP .sp Pairing begins when one device sends a Security Request (peripheral) or the host initiates pairing directly. The initiator sends a Pairing Request and the responder replies with a Pairing Response: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 11 #497 [hci0] 0.026107 SMP: Pairing Request (0x01) len 6 IO capability: NoInputNoOutput (0x03) OOB data: Authentication data not present (0x00) Authentication requirement: Bonding, MITM, SC, CT2 (0x2d) Max encryption key size: 16 Initiator key distribution: IdKey Sign (0x06) Responder key distribution: IdKey Sign (0x06) < ACL Data TX: Handle 2048 flags 0x00 dlen 11 #499 [hci0] 0.026894 SMP: Pairing Response (0x02) len 6 IO capability: KeyboardDisplay (0x04) OOB data: Authentication data not present (0x00) Authentication requirement: Bonding, SC, CT2 (0x29) Max encryption key size: 16 Initiator key distribution: IdKey (0x02) Responder key distribution: IdKey (0x02) .EE .UNINDENT .UNINDENT .sp Key fields to check: .INDENT 0.0 .IP \(bu 2 \fBAuthentication requirement\fP \-\- The \fBSC\fP flag indicates Secure Connections. Its absence means Legacy Pairing. .IP \(bu 2 \fBIO capability\fP \-\- Determines the association model (Just Works, Passkey Entry, Numeric Comparison, OOB). .IP \(bu 2 \fBKey distribution\fP \-\- Which keys each side will send after encryption is established. \fBIdKey\fP = Identity Resolving Key (IRK), \fBEncKey\fP = Long Term Key (legacy only), \fBSign\fP = CSRK. .UNINDENT .sp \fBPhase 2: Authentication (Secure Connections)\fP .sp For Secure Connections pairing (\fBSC\fP flag set), both devices exchange public keys, then perform confirm/random value exchange: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 69 #501 [hci0] 0.098224 SMP: Pairing Public Key (0x0c) len 64 X: 1a2b3c4d... Y: 5e6f7a8b... < ACL Data TX: Handle 2048 flags 0x00 dlen 69 #503 [hci0] 0.148556 SMP: Pairing Public Key (0x0c) len 64 X: 9c8d7e6f... Y: 0a1b2c3d... < ACL Data TX: Handle 2048 flags 0x00 dlen 21 #505 [hci0] 0.149003 SMP: Pairing Confirm (0x03) len 16 Confirm value: a1b2c3d4e5f6... > ACL Data RX: Handle 2048 flags 0x02 dlen 21 #507 [hci0] 0.212884 SMP: Pairing Random (0x04) len 16 Random value: 1122334455... < ACL Data TX: Handle 2048 flags 0x00 dlen 21 #509 [hci0] 0.213100 SMP: Pairing Random (0x04) len 16 Random value: 6677889900... > ACL Data RX: Handle 2048 flags 0x02 dlen 21 #511 [hci0] 0.278003 SMP: Pairing DHKey Check (0x0d) len 16 E: aabbccddee... < ACL Data TX: Handle 2048 flags 0x00 dlen 21 #513 [hci0] 0.278450 SMP: Pairing DHKey Check (0x0d) len 16 E: ffeeddccbb... .EE .UNINDENT .UNINDENT .sp After DHKey Check, the initiator starts encryption at the HCI level: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE Start Encryption (0x08|0x0019) plen 28 #515 [hci0] 0.279002 > HCI Event: Encryption Change (0x08) plen 4 #517 [hci0] 0.342556 Status: Success (0x00) Handle: 2048 Encryption: Enabled with AES\-CCM (0x01) .EE .UNINDENT .UNINDENT .sp \fBPhase 2: Authentication (Legacy Pairing)\fP .sp Legacy pairing (no \fBSC\fP flag) skips the Public Key and DHKey Check exchanges. Only Confirm and Random values are exchanged: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 21 #501 [hci0] 0.098224 SMP: Pairing Confirm (0x03) len 16 Confirm value: ... > ACL Data RX: Handle 2048 flags 0x02 dlen 21 #503 [hci0] 0.162556 SMP: Pairing Confirm (0x03) len 16 Confirm value: ... < ACL Data TX: Handle 2048 flags 0x00 dlen 21 #505 [hci0] 0.163003 SMP: Pairing Random (0x04) len 16 Random value: ... > ACL Data RX: Handle 2048 flags 0x02 dlen 21 #507 [hci0] 0.228884 SMP: Pairing Random (0x04) len 16 Random value: ... .EE .UNINDENT .UNINDENT .sp \fBPhase 3: Key Distribution\fP .sp After encryption is established, each device distributes keys as negotiated in Phase 1: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 21 #519 [hci0] 0.343002 SMP: Identity Information (0x08) len 16 Identity resolving key: 00112233445566778899aabbccddeeff > ACL Data RX: Handle 2048 flags 0x02 dlen 12 #521 [hci0] 0.343556 SMP: Identity Address Information (0x09) len 7 Address type: Public (0x00) Address: 00:11:22:33:44:55 .EE .UNINDENT .UNINDENT .sp The Identity Address Information reveals the device\(aqs true public or static random address (as opposed to a Resolvable Private Address used during connection). .sp For Legacy Pairing, LTK distribution also appears: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 21 #519 [hci0] 0.343002 SMP: Encryption Information (0x06) len 16 Long term key: 00112233... > ACL Data RX: Handle 2048 flags 0x02 dlen 15 #521 [hci0] 0.343556 SMP: Central Identification (0x07) len 10 EDIV: 0x1234 Rand: 0x0123456789abcdef .EE .UNINDENT .UNINDENT .SS Pairing Failure .sp When pairing fails, one device sends a Pairing Failed PDU: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 6 #505 [hci0] 0.213002 SMP: Pairing Failed (0x05) len 1 Reason: Authentication requirements (0x03) .EE .UNINDENT .UNINDENT .sp SMP failure reasons: .TS box center; l|l|l. T{ Code T} T{ Reason T} T{ Diagnostic Meaning T} _ T{ 0x01 T} T{ Passkey Entry Failed T} T{ User cancelled or entered wrong passkey T} _ T{ 0x02 T} T{ OOB Not Available T} T{ OOB data expected but not provided T} _ T{ 0x03 T} T{ Authentication Requirements T} T{ Devices cannot agree on security level (e.g., one requires MITM but IO caps only allow Just Works) T} _ T{ 0x04 T} T{ Confirm Value Failed T} T{ Cryptographic check failed; possible MITM attack T} _ T{ 0x05 T} T{ Pairing Not Supported T} T{ Remote does not support pairing T} _ T{ 0x06 T} T{ Encryption Key Size T} T{ Cannot agree on key size T} _ T{ 0x07 T} T{ Command Not Supported T} T{ Received unrecognized SMP command T} _ T{ 0x08 T} T{ Unspecified Reason T} T{ Generic failure T} _ T{ 0x09 T} T{ Repeated Attempts T} T{ Pairing rate\-limited; wait before retry T} _ T{ 0x0a T} T{ Invalid Parameters T} T{ Invalid fields in SMP command T} _ T{ 0x0b T} T{ DHKey Check Failed T} T{ ECDH key agreement failed (SC only) T} _ T{ 0x0c T} T{ Numeric Comparison Failed T} T{ User rejected numeric comparison T} _ T{ 0x0d T} T{ BR/EDR Pairing In Progress T} T{ Classic pairing already active T} _ T{ 0x0e T} T{ Cross\-Transport Key Derivation Not Allowed T} T{ CTKD rejected by policy T} .TE .SS Automating Pairing Analysis .sp \fBIdentify all pairing attempts\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqPairing Request\e|Pairing Response\e|Pairing Failed\e|Pairing Public Key\e|DHKey Check\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBCheck pairing method (Secure Connections vs Legacy)\fP: .INDENT 0.0 .IP \(bu 2 If \fBPairing Public Key\fP appears between Request/Response and Confirm: Secure Connections. .IP \(bu 2 If only Confirm/Random follow Request/Response: Legacy Pairing. .IP \(bu 2 Check the \fBAuthentication requirement\fP line for the \fBSC\fP flag. .UNINDENT .sp \fBDetect pairing failures\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqPairing Failed\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBCorrelate pairing with encryption\fP: .sp After successful pairing, expect \fBEncryption Change\fP with \fBStatus: Success\fP\&. Search for: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqEncryption Change\e|Encryption:\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBIdentify re\-pairing on reconnect\fP: .sp Reconnections to a bonded device should show \fBEncryption Change\fP without SMP traffic (using stored keys). If SMP Pairing Request appears on reconnection, the bond was lost on one side. .sp \fBFull pairing diagnosis pattern\fP: .INDENT 0.0 .IP 1. 3 Find \fBPairing Request\fP \-\- note the handle, IO capabilities, auth requirements .IP 2. 3 Find \fBPairing Response\fP \-\- compare IO capabilities to determine association model .IP 3. 3 Check for \fBPairing Failed\fP \-\- if present, the reason code identifies the failure .IP 4. 3 Check for \fBEncryption Change\fP with \fBStatus: Success\fP \-\- confirms pairing completed .IP 5. 3 Check for \fBIdentity Address Information\fP \-\- reveals the device\(aqs true address .UNINDENT .\" This file is included by btmon.rst. . .SH L2CAP CHANNEL TRACKING .sp L2CAP (Logical Link Control and Adaptation Protocol) multiplexes multiple logical channels over a single ACL connection. btmon decodes L2CAP signaling automatically and routes data to higher\-layer protocol decoders based on the channel. .SS Fixed Channels .sp Fixed channels have pre\-assigned Channel Identifiers (CIDs) and do not require signaling to establish: .TS box center; l|l|l. T{ CID T} T{ Protocol T} T{ Description T} _ T{ 0x0001 T} T{ L2CAP Signaling (BR/EDR) T} T{ Channel management for classic connections T} _ T{ 0x0002 T} T{ Connectionless Reception T} T{ Connectionless L2CAP data T} _ T{ 0x0003 T} T{ AMP Manager T} T{ AMP (Alternate MAC/PHY) control T} _ T{ 0x0004 T} T{ ATT T} T{ Attribute Protocol (GATT operations) T} _ T{ 0x0005 T} T{ L2CAP Signaling (LE) T} T{ Channel management for LE connections T} _ T{ 0x0006 T} T{ SMP (LE) T} T{ Security Manager Protocol T} _ T{ 0x0007 T} T{ SMP (BR/EDR) T} T{ Security Manager over classic transport T} .TE .sp In btmon output, fixed channel traffic is decoded directly without any L2CAP signaling preamble. For example, ATT on CID 0x0004 appears as: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 7 #494 [hci0] 0.004488 ATT: Exchange MTU Request (0x02) len 2 Client RX MTU: 517 .EE .UNINDENT .UNINDENT .SS Dynamic Channels (BR/EDR) .sp Classic Bluetooth uses L2CAP signaling on CID 0x0001 to establish dynamic channels. Each channel is identified by a PSM (Protocol/Service Multiplexer) that determines which protocol runs on it. .sp \fBChannel establishment\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 256 flags 0x02 dlen 16 #142 [hci0] 2.034556 L2CAP: Connection Request (0x02) ident 3 len 4 PSM: 25 (0x0019) Source CID: 0x0040 < ACL Data TX: Handle 256 flags 0x00 dlen 20 #144 [hci0] 2.035002 L2CAP: Connection Response (0x03) ident 3 len 8 Destination CID: 0x0041 Source CID: 0x0040 Result: Connection successful (0x0000) Status: No further information available (0x0000) .EE .UNINDENT .UNINDENT .sp After connection, configuration is exchanged: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 256 flags 0x02 dlen 20 #146 [hci0] 2.035556 L2CAP: Configure Request (0x04) ident 4 len 8 Destination CID: 0x0041 Flags: 0x0000 Option: MTU (0x01) [2] MTU: 1024 < ACL Data TX: Handle 256 flags 0x00 dlen 18 #148 [hci0] 2.036003 L2CAP: Configure Response (0x05) ident 4 len 6 Source CID: 0x0040 Flags: 0x0000 Result: Success (0x0000) .EE .UNINDENT .UNINDENT .sp Common PSM\-to\-protocol mappings: .TS box center; l|l|l. T{ PSM T} T{ Protocol T} T{ Description T} _ T{ 0x0001 T} T{ SDP T} T{ Service Discovery Protocol T} _ T{ 0x0003 T} T{ RFCOMM T} T{ Serial port emulation (SPP, HFP, etc.) T} _ T{ 0x000f T} T{ BNEP T} T{ Bluetooth Network Encapsulation Protocol T} _ T{ 0x0017 T} T{ AVCTP T} T{ Audio/Video Control Transport (AVRCP) T} _ T{ 0x0019 T} T{ AVDTP T} T{ Audio/Video Distribution Transport (A2DP) T} _ T{ 0x001b T} T{ AVCTP Browsing T} T{ AVRCP browsing channel T} _ T{ 0x001f T} T{ ATT (BR/EDR) T} T{ Attribute Protocol over classic transport T} _ T{ 0x0027 T} T{ EATT T} T{ Enhanced Attribute Protocol T} .TE .SS LE Credit\-Based Channels .sp LE connections use L2CAP signaling on CID 0x0005 for dynamic channels. The LE Credit Based Connection mechanism provides flow control: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 18 #600 [hci0] 1.824003 LE L2CAP: LE Connection Request (0x14) ident 1 len 10 PSM: 39 (0x0027) Source CID: 0x0040 MTU: 517 MPS: 251 Credits: 10 > ACL Data RX: Handle 2048 flags 0x02 dlen 18 #602 [hci0] 1.886556 LE L2CAP: LE Connection Response (0x15) ident 1 len 10 Destination CID: 0x0041 MTU: 517 MPS: 251 Credits: 10 Result: Connection successful (0x0000) .EE .UNINDENT .UNINDENT .sp EATT (Enhanced ATT) uses PSM 0x0027 over LE Credit\-Based channels to provide multiple parallel ATT bearers. .SS Connection Parameter Updates .sp LE peripherals frequently request connection parameter changes via L2CAP signaling: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 16 #493 [hci0] 0.003915 LE L2CAP: Connection Parameter Update Request (0x12) ident 1 len 8 Min interval: 24 Max interval: 40 Peripheral latency: 0 Timeout multiplier: 256 > ACL Data RX: Handle 2048 flags 0x02 dlen 10 #495 [hci0] 0.066003 LE L2CAP: Connection Parameter Update Response (0x13) ident 1 len 2 Result: Connection Parameters accepted (0x0000) .EE .UNINDENT .UNINDENT .sp A result of \fBConnection Parameters rejected (0x0001)\fP means the central denied the request. .SS Automating L2CAP Analysis .sp \fBFind all L2CAP channel establishments\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqConnection Request\e|Connection Response\e|LE Connection Request\e|LE Connection Response\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBTrack PSM usage\fP (identifies which protocols are active): .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqPSM:\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBFind connection parameter update issues\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqParameter Update Request\e|Parameter Update Response\e|Parameters rejected\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBFind EATT channel setup\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqPSM: 39\e|Enhanced Credit\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBTrace a specific L2CAP channel\fP: To follow traffic on a dynamic channel, note the Source CID and Destination CID from the Connection Request/Response pair. Then search for those CIDs in subsequent data frames. .SS Throughput Estimation .sp btmon\(aqs \fB\-\-analyze\fP (\fB\-a\fP) mode computes per\-channel statistics including throughput. For each L2CAP channel it reports: .INDENT 0.0 .IP \(bu 2 \fBSpeed\fP: Computed as \fBbytes * 8 / latency_sum_ms\fP where \fBlatency_sum_ms\fP is the sum of inter\-packet deltas (not wall\-clock duration). This means idle gaps are excluded, so the figure represents the \fIactive sending rate\fP rather than application\-level throughput. .IP \(bu 2 \fBMin/Avg/Max latency\fP: Per\-packet inter\-arrival time range. .UNINDENT .sp Example output: .INDENT 0.0 .INDENT 3.5 .sp .EX Found TX L2CAP channel with CID 64 PSM 128 (0x0080) Mode: LE Credit MTU: 672 MPS: 490 TX packets: 29120/29114 TX Latency: 1\-79 msec (~29 msec) TX size: 494\-494 octets (~494 octets) TX speed: ~571 Kb/s .EE .UNINDENT .UNINDENT .sp \fBChannel details shown in analyze mode:\fP .INDENT 0.0 .IP \(bu 2 \fBFixed channels\fP (CID <= 7) display their protocol name (e.g., \fBATT\fP, \fBL2CAP Signaling (LE)\fP). .IP \(bu 2 \fBPSM\fP is shown in both decimal and hexadecimal. .IP \(bu 2 \fBMode\fP is decoded from Configure Request options (BR/EDR) or LE signaling (\fBBasic\fP, \fBERTM\fP, \fBLE Credit\fP, \fBEnhanced Credit\fP, etc.). .IP \(bu 2 \fBMTU\fP and \fBMPS\fP are extracted from signaling exchanges. .UNINDENT .sp \fBThroughput caveats:\fP .INDENT 0.0 .IP 1. 3 The speed value uses inter\-packet latency sums as the denominator, not wall\-clock elapsed time. If the sender pauses (e.g., waiting for credits), the idle period is not counted, which inflates the reported speed relative to overall throughput. .IP 2. 3 TX latency is measured from command submission to completion event. RX latency is the inter\-arrival time between consecutive packets. These measure different things, so TX and RX speeds for the same channel are not directly comparable. .IP 3. 3 For windowed throughput (min/avg/max), btsnoop\-analyzer uses 1\-second sampling windows over wall\-clock time, which provides a more realistic view of application\-level bandwidth variation. .UNINDENT .sp \fBAutomated throughput extraction\fP from btmon analyze output: .INDENT 0.0 .INDENT 3.5 .sp .EX btmon \-a trace.btsnoop 2>/dev/null | grep \-E \(dqspeed:|Mode:|MTU:|MPS:|PSM\(dq .EE .UNINDENT .UNINDENT .\" This file is included by btmon.rst. . .SH LE AUDIO PROTOCOL FLOW .sp LE Audio uses a multi\-layer protocol stack visible in btmon traces. The setup sequence involves ATT operations on specific GATT characteristics (PACS, ASCS) followed by HCI\-level CIS/BIG management. btmon fully decodes all layers. .SS PACS: Published Audio Capabilities .sp Before audio streaming begins, devices exchange codec capabilities via the Published Audio Capabilities Service (PACS). The client reads PACS characteristics to learn what the remote device supports. .sp \fBSink PAC read\fP (remote device\(aqs receive capabilities): .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 7 #550 [hci0] 0.824003 ATT: Read Request (0x0a) len 2 Handle: 0x0075 > ACL Data RX: Handle 2048 flags 0x02 dlen 30 #552 [hci0] 0.886556 ATT: Read Response (0x0b) len 25 Handle: 0x0075 Number of PAC(s): 1 Codec: LC3 (0x06) Codec Specific Capabilities: #0 Sampling Frequency: 8000 Hz 16000 Hz 24000 Hz 32000 Hz 48000 Hz Frame Duration: 7.5 ms 10 ms Audio Channel Counts: 1 Frame Length: 26 \- 240 .EE .UNINDENT .UNINDENT .sp The PAC record shows codec capabilities using LTV (Length\-Type\-Value) encoding. Key fields: .INDENT 0.0 .IP \(bu 2 \fBCodec\fP \-\- \fBLC3 (0x06)\fP is the mandatory LE Audio codec .IP \(bu 2 \fBSampling Frequency\fP \-\- Supported sample rates (bitmask) .IP \(bu 2 \fBFrame Duration\fP \-\- Supported frame durations (7.5 ms and/or 10 ms) .IP \(bu 2 \fBAudio Channel Counts\fP \-\- Supported channel counts .IP \(bu 2 \fBFrame Length\fP \-\- Min and max octets per codec frame .UNINDENT .sp \fBAudio Locations\fP (channel assignment): .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 9 #554 [hci0] 0.948003 ATT: Read Response (0x0b) len 4 Handle: 0x0077 Location: Front Left .EE .UNINDENT .UNINDENT .sp \fBAvailable Audio Contexts\fP (current use cases): .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 9 #558 [hci0] 1.012556 ATT: Read Response (0x0b) len 4 Handle: 0x007b Sink Context: Media Conversational Source Context: Unspecified .EE .UNINDENT .UNINDENT .SS ASCS: Audio Stream Control .sp The Audio Stream Control Service (ASCS) manages the ASE (Audio Stream Endpoint) state machine. Each ASE transitions through a defined set of states as streaming is set up and torn down. .sp \fBASE State Machine\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX Idle ──► Codec Configured ──► QoS Configured ──► Enabling ──► Streaming │ Idle ◄── Releasing ◄──────── Disabling ◄─────────────────────────┘ .EE .UNINDENT .UNINDENT .sp \fBASE Status notification\fP (state change): .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 20 #580 [hci0] 1.456003 ATT: Handle Value Notification (0x1b) len 15 Handle: 0x0088 ASE ID: 0x01 State: Codec Configured (0x01) Framing: Unframed PDUs supported (0x00) PHY: 0x02 LE 2M PHY (0x02) RTN: 2 Max Transport Latency: 10 Presentation Delay Min: 20000 us Presentation Delay Max: 40000 us Preferred Presentation Delay Min: 20000 us Preferred Presentation Delay Max: 40000 us Codec: LC3 (0x06) Sampling Frequency: 48000 Hz Frame Duration: 10 ms Audio Channel Allocation: Front Left Frame Length: 120 .EE .UNINDENT .UNINDENT .sp \fBASE Control Point operations\fP drive state transitions. The client writes to the ASE Control Point characteristic to issue commands: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 25 #582 [hci0] 1.518003 ATT: Write Request (0x12) len 20 Handle: 0x008b ASE Control Point: Config Codec (0x01) ASE ID: 0x01 Target Latency: Low Latency (0x01) PHY: LE 2M PHY Codec: LC3 (0x06) Sampling Frequency: 48000 Hz Frame Duration: 10 ms Audio Channel Allocation: Front Left Frame Length: 120 .EE .UNINDENT .UNINDENT .sp ASE Control Point commands: .TS box center; l|l|l. T{ Opcode T} T{ Command T} T{ Purpose T} _ T{ 0x01 T} T{ Config Codec T} T{ Select codec and parameters (Idle → Codec Configured) T} _ T{ 0x02 T} T{ Config QoS T} T{ Set CIG/CIS IDs and QoS params (Codec Configured → QoS Configured) T} _ T{ 0x03 T} T{ Enable T} T{ Start ASE with metadata (QoS Configured → Enabling) T} _ T{ 0x04 T} T{ Receiver Start Ready T} T{ Signal receiver readiness (Enabling → Streaming, server\-side) T} _ T{ 0x05 T} T{ Disable T} T{ Stop streaming (Streaming → Disabling) T} _ T{ 0x06 T} T{ Receiver Stop Ready T} T{ Signal receiver stopped T} _ T{ 0x07 T} T{ Update Metadata T} T{ Change metadata during streaming T} _ T{ 0x08 T} T{ Release T} T{ Tear down ASE (any → Releasing → Idle) T} .TE .SS Bidirectional Streams (Source + Sink) .sp A device may expose multiple ASEs with different directions. In the conversational (telephony) use case, the remote device typically has at least one \fBSink\fP ASE (receives audio, e.g., speaker) and one \fBSource\fP ASE (sends audio, e.g., microphone). Both ASEs share the same CIG and often the same CIS, using the bidirectional capability of Connected Isochronous Streams. .sp \fBDirection indicators in btmon traces:\fP .INDENT 0.0 .IP \(bu 2 The \fBReceiver Start Ready\fP command is only sent for \fBSource\fP ASEs (ASEs that send audio toward the local device). The server issues this command to indicate it is ready to transmit. .IP \(bu 2 The \fBReceiver Stop Ready\fP command likewise applies to Source ASEs. .IP \(bu 2 \fBSink\fP ASEs transition directly from Enabling to Streaming without \fBReceiver Start Ready\fP\&. .UNINDENT .sp \fBTypical bidirectional setup sequence\fP (two ASEs on one CIS): .INDENT 0.0 .INDENT 3.5 .sp .EX Config Codec ASE ID=1 (Sink) → Codec Configured Config Codec ASE ID=3 (Source) → Codec Configured Config QoS ASE ID=1 → QoS Configured (CIG=X, CIS=Y) Config QoS ASE ID=3 → QoS Configured (CIG=X, CIS=Y) Enable ASE ID=1 → Enabling → Streaming (immediate) Enable ASE ID=3 → Enabling CIS Established (Success) Setup ISO Data Path Input (Host→Controller, for Sink) Setup ISO Data Path Output (Controller→Host, for Source) Receiver Start Ready ASE ID=3 → Streaming .EE .UNINDENT .UNINDENT .sp Both ASEs may reach Streaming at different times. The Sink ASE can start receiving audio as soon as CIS is established, while the Source ASE waits for the \fBReceiver Start Ready\fP handshake. .sp \fBNote:\fP .INDENT 0.0 .INDENT 3.5 \fBThis is normal behavior.\fP Seeing one Source ASE and one Sink ASE on the same connection is the standard bidirectional (conversational) configuration. It is \fBnot\fP an error or misconfiguration. Both directions sharing a single CIS is efficient and expected. .UNINDENT .UNINDENT .sp \fBMultiple ASEs per direction\fP are also valid. For example, a stereo headset may expose two Sink ASEs (left and right channels) and one Source ASE (mono microphone), each with its own GATT handle for ASE state notifications. .SS CIS Setup and Teardown .sp After ASE QoS Configuration, the host creates Connected Isochronous Streams (CIS) at the HCI level. .sp \fBCIG Parameters\fP (configure the CIS group): .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE Set CIG Parameters (0x08|0x0062) plen 26 #590 [hci0] 1.624003 CIG ID: 0x00 Central to Peripheral SDU Interval: 10000 us Peripheral to Central SDU Interval: 10000 us SCA: 0x00 Packing: Sequential (0x00) Framing: Unframed (0x00) Central to Peripheral Max Latency: 10 ms Peripheral to Central Max Latency: 10 ms Number of CIS: 1 CIS ID: 0x00 Central to Peripheral Max SDU: 120 Peripheral to Central Max SDU: 0 Central to Peripheral PHY: LE 2M PHY Peripheral to Central PHY: LE 2M PHY Central to Peripheral RTN: 2 Peripheral to Central RTN: 2 > HCI Event: Command Complete (0x0e) plen 8 #592 [hci0] 1.624556 LE Set CIG Parameters (0x08|0x0062) ncmd 1 Status: Success (0x00) CIG ID: 0x00 Number of Handles: 1 Connection Handle: 2064 .EE .UNINDENT .UNINDENT .sp \fBCIS Creation\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE Create CIS (0x08|0x0064) plen 9 #594 [hci0] 1.688003 Number of CIS: 1 CIS Handle: 2064 ACL Handle: 2048 > HCI Event: LE Meta Event (0x3e) plen 29 #596 [hci0] 1.756556 LE CIS Established (0x19) Status: Success (0x00) Connection Handle: 2064 CIG Sync Delay: 5000 us CIS Sync Delay: 5000 us Central to Peripheral Latency: 10000 us Peripheral to Central Latency: 10000 us Central to Peripheral PHY: LE 2M PHY Peripheral to Central PHY: LE 2M PHY NSE: 3 Central to Peripheral BN: 1 Peripheral to Central BN: 0 Central to Peripheral FT: 2 Peripheral to Central FT: 2 Max PDU C to P: 120 Max PDU P to C: 0 ISO Interval: 10.00 msec (0x0008) .EE .UNINDENT .UNINDENT .sp Note that the CIS Handle (2064) is different from the ACL Handle (2048). CIS data packets use the CIS handle. .sp \fBISO Data Path Setup\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE Setup ISO Data Path (0x08|0x006e) plen 13 #598 [hci0] 1.820003 Handle: 2064 Data Path Direction: Input (Host to Controller) (0x00) Data Path ID: HCI (0x00) Coding Format: LC3 (0x06) Company ID: 0x0000 Vendor Codec ID: 0x0000 Controller Delay: 0 us .EE .UNINDENT .UNINDENT .sp After this, ISO data packets flow on the CIS handle: .INDENT 0.0 .INDENT 3.5 .sp .EX < ISO Data TS: Handle 2064 flags 0x02 dlen 124 #600 [hci0] 1.884003 .EE .UNINDENT .UNINDENT .SS Broadcast Audio (BIG) .sp Broadcast Isochronous Streams use BIG (Broadcast Isochronous Group) instead of CIS. The setup involves periodic advertising with BASE (Broadcast Audio Source Endpoint) announcements. .sp \fBBASE announcement\fP (in periodic advertising data): .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: LE Meta Event (0x3e) plen 80 #200 [hci0] 0.500003 LE Periodic Advertising Report (0x0f) ... Service Data: Basic Audio Announcement (0x1851) Presentation Delay: 40000 us Number of Subgroups: 1 Number of BIS: 2 Codec: LC3 (0x06) Sampling Frequency: 48000 Hz Frame Duration: 10 ms Frame Length: 120 BIS #1 Audio Channel Allocation: Front Left BIS #2 Audio Channel Allocation: Front Right .EE .UNINDENT .UNINDENT .sp \fBBIG creation\fP (source side): .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE Create BIG (0x08|0x0068) plen 31 #210 [hci0] 0.600003 BIG Handle: 0x00 Advertising Handle: 0x01 Number of BIS: 2 SDU Interval: 10000 us Max SDU: 120 Max Latency: 10 ms RTN: 2 PHY: LE 2M PHY Packing: Sequential (0x00) Framing: Unframed (0x00) Encryption: Unencrypted (0x00) .EE .UNINDENT .UNINDENT .sp \fBBIG sync\fP (receiver side): .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE BIG Create Sync (0x08|0x006b) plen 15 #220 [hci0] 0.700003 BIG Handle: 0x00 Sync Handle: 0x0001 Encryption: Unencrypted (0x00) Number of BIS: 2 BIS: 0x01 BIS: 0x02 .EE .UNINDENT .UNINDENT .SS BIG Sync Receiver Flow .sp A receiver must complete a specific sequence of steps before it can receive broadcast audio. The critical prerequisite chain is: .INDENT 0.0 .IP 1. 3 \fBSynchronize to periodic advertising\fP \-\- the receiver must first discover and sync to the broadcaster\(aqs periodic advertising train. .IP 2. 3 \fBReceive PA Reports with BASE\fP \-\- the periodic advertising data contains the BASE (Broadcast Audio Source Endpoint) structure describing the broadcast\(aqs codec configuration. .IP 3. 3 \fBReceive BIG Info Advertising Report\fP \-\- this event tells the receiver that a BIG exists on the periodic advertising train and provides its parameters (number of BIS, encryption, SDU interval, etc.). \fBThis is the critical gate\fP: without receiving BIG Info, the receiver cannot issue \fBLE BIG Create Sync\fP\&. .IP 4. 3 \fBIssue LE BIG Create Sync\fP \-\- using the sync handle from step 1 and parameters from step 3. .IP 5. 3 \fBReceive BIG Sync Established\fP \-\- the controller confirms sync with BIS connection handles. .IP 6. 3 \fBSetup ISO Data Path\fP \-\- configure the data path for each BIS. .IP 7. 3 \fBISO Data flows\fP \-\- broadcast audio packets arrive. .UNINDENT .sp If any step fails or is missing, the subsequent steps cannot proceed. The most common failure pattern is that \fBBIG Info is never received\fP (e.g., the periodic advertising data does not contain a BIG, or PA sync was lost before BIG Info arrived), which means \fBLE BIG Create Sync\fP is never sent. .SS Without PAST (Direct PA Sync) .sp When the receiver scans for and syncs to periodic advertising directly (without assistance from a Broadcast Assistant): .sp \fBStep 1 \-\- Create PA sync\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE Periodic Advertising Create Sync (0x08|0x0044) plen 14 #100 [hci0] 0.100003 Options: 0x0000 SID: 0x01 Adv Address Type: Public (0x00) Adv Address: XX:XX:XX:XX:XX:XX Skip: 0x0000 Sync Timeout: 2000 msec (0x00c8) Sync CTE Type: 0x0000 .EE .UNINDENT .UNINDENT .sp \fBStep 2 \-\- PA Sync Established\fP (sync handle assigned): .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: LE Meta Event (0x3e) plen 16 #105 [hci0] 0.150003 LE Periodic Advertising Sync Established (0x0e) Status: Success (0x00) Sync Handle: 0x0001 Advertising SID: 0x01 Advertiser Address Type: Public (0x00) Advertiser Address: XX:XX:XX:XX:XX:XX Advertiser PHY: LE 2M PHY (0x02) Periodic Advertising Interval: 10.000 msec (0x0008) Advertiser Clock Accuracy: 0x05 .EE .UNINDENT .UNINDENT .sp \fBStep 3 \-\- PA Reports\fP (contain BASE data): .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: LE Meta Event (0x3e) plen 80 #110 [hci0] 0.200003 LE Periodic Advertising Report (0x0f) Sync Handle: 0x0001 ... Service Data: Basic Audio Announcement (0x1851) .EE .UNINDENT .UNINDENT .sp \fBStep 4 \-\- BIG Info Advertising Report\fP (critical gate): .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: LE Meta Event (0x3e) plen 24 #120 [hci0] 0.300003 LE BIG Info Advertising Report (0x22) Sync Handle: 0x0001 Number BIS: 2 NSE: 4 ISO Interval: 10.000 msec (0x0008) BN: 2 PTO: 1 IRC: 2 Maximum PDU: 120 SDU Interval: 10000 us Maximum SDU: 120 PHY: LE 2M PHY (0x02) Framing: Unframed (0x00) Encryption: 0x00 .EE .UNINDENT .UNINDENT .sp This event is generated by the controller each time it receives a periodic advertising packet that contains BIG Info. It provides all parameters the receiver needs to decide whether to sync to the BIG. Key fields: .INDENT 0.0 .IP \(bu 2 \fBNumber BIS\fP \-\- how many BIS streams are available. .IP \(bu 2 \fBSDU Interval\fP and \fBMaximum SDU\fP \-\- audio frame timing and size. .IP \(bu 2 \fBEncryption\fP \-\- whether a Broadcast Code is required (0x01) or not (0x00). If encrypted, the receiver must supply the correct Broadcast Code in \fBLE BIG Create Sync\fP\&. .IP \(bu 2 \fBSync Handle\fP \-\- must match a currently active PA sync. .UNINDENT .sp \fBStep 5 \-\- BIG Create Sync\fP (using sync handle + BIG Info): .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE BIG Create Sync (0x08|0x006b) plen 15 #130 [hci0] 0.400003 BIG Handle: 0x00 BIG Sync Handle: 0x0001 Encryption: Unencrypted (0x00) Broadcast Code: 00000000000000000000000000000000 Maximum Number Subevents: 0x00 Timeout: 2000 ms (0x00c8) Number of BIS: 2 BIS: 0x01 BIS: 0x02 .EE .UNINDENT .UNINDENT .sp \fBStep 6 \-\- BIG Sync Established\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: LE Meta Event (0x3e) plen 20 #135 [hci0] 0.450003 LE BIG Sync Established (0x1d) Status: Success (0x00) BIG Handle: 0x00 Transport Latency: 10000 us NSE: 4 BN: 2 PTO: 1 IRC: 2 Maximum PDU: 120 ISO Interval: 10.000 msec (0x0008) Connection Handle: 0x0010 Connection Handle: 0x0011 .EE .UNINDENT .UNINDENT .sp On success, the controller assigns BIS connection handles (0x0010, 0x0011 above). A non\-zero Status indicates failure \-\- common errors: .INDENT 0.0 .IP \(bu 2 \fB0x3e\fP (Connection Failed to be Established) \-\- BIG parameters do not match or the BIG is no longer present. .IP \(bu 2 \fB0x3f\fP (Limit Reached) \-\- controller resources exhausted. .UNINDENT .sp \fBStep 7 \-\- Setup ISO Data Path\fP (for each BIS): .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE Setup ISO Data Path (0x08|0x006e) plen 13 #140 [hci0] 0.500003 Connection Handle: 0x0010 Data Path Direction: Output (Controller to Host) (0x01) Data Path ID: HCI (0x00) < HCI Command: LE Setup ISO Data Path (0x08|0x006e) plen 13 #145 [hci0] 0.550003 Connection Handle: 0x0011 Data Path Direction: Output (Controller to Host) (0x01) Data Path ID: HCI (0x00) .EE .UNINDENT .UNINDENT .sp \fBStep 8 \-\- ISO Data flows\fP on BIS handles: .INDENT 0.0 .INDENT 3.5 .sp .EX > ISO Data: Handle 0x0010 flags 0x02 dlen 124 #150 [hci0] 0.600003 > ISO Data: Handle 0x0011 flags 0x02 dlen 124 #151 [hci0] 0.600003 .EE .UNINDENT .UNINDENT .SS With PAST (Periodic Advertising Sync Transfer) .sp When a Broadcast Assistant (e.g., a phone) helps a Scan Delegator (e.g., a hearing aid) sync to a broadcast, it can transfer the PA sync via PAST over an existing ACL connection. This avoids the delegator having to scan for and sync to the PA train itself. .sp The BASS (Broadcast Audio Scan Service) protocol coordinates this: .INDENT 0.0 .IP 1. 3 The assistant writes an \fBAdd Source\fP operation to the delegator\(aqs BASS control point with \fBPA Sync\fP set to \fB0x01\fP (Synchronize via PAST). .IP 2. 3 The delegator prepares to receive the transfer by setting PAST parameters. .IP 3. 3 The assistant transfers its PA sync to the delegator. .IP 4. 3 The delegator receives the PAST event with a sync handle. .IP 5. 3 From here, the flow continues as in the non\-PAST case (PA Reports → BIG Info → BIG Create Sync → etc.). .UNINDENT .sp \fBBASS Add Source\fP (assistant writes to delegator, seen in ATT): .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 64 flags 0x00 dlen 27 #300 [hci0] 1.000003 ATT: Write Command (0x52) len 22 Handle: 0x0025 Data: 04... Opcode: Add Source (0x04) Advertiser Address Type: Public (0x00) Advertiser Address: XX:XX:XX:XX:XX:XX Advertising SID: 0x01 PA Sync: Synchronize to PA \- PAST (0x01) PA Interval: 0x0008 Number of Subgroups: 1 BIS Sync: 0x00000003 Metadata Length: 0 .EE .UNINDENT .UNINDENT .sp PA Sync values in the Add Source operation: .INDENT 0.0 .IP \(bu 2 \fB0x00\fP \-\- Do not synchronize to PA .IP \(bu 2 \fB0x01\fP \-\- Synchronize to PA, PAST available .IP \(bu 2 \fB0x02\fP \-\- Synchronize to PA, PAST not available (delegator must scan and sync directly) .UNINDENT .sp \fBPAST Parameters\fP (delegator prepares to receive transfer): .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE Periodic Advertising Sync Transfer Parameters (0x08|0x005c) plen 8 #310 [hci0] 1.100003 Connection handle: 64 Mode: Enabled with report events enabled (0x02) Skip: 0x00 Sync timeout: 2000 msec (0x00c8) Sync CTE Type: 0x0000 .EE .UNINDENT .UNINDENT .sp \fBPAST Transfer\fP (assistant sends its PA sync): .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE Periodic Advertising Sync Transfer (0x08|0x005a) plen 6 #320 [hci0] 1.200003 Connection handle: 64 Service data: 0x0001 Sync handle: 1 .EE .UNINDENT .UNINDENT .sp \fBPAST Received\fP (delegator gets the sync handle): .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: LE Meta Event (0x3e) plen 19 #325 [hci0] 1.250003 LE Periodic Advertising Sync Transfer Received (0x18) Status: Success (0x00) Handle: 64 Connection handle: 64 Service data: 0x0001 Sync handle: 1 SID: 0x01 Address type: Public (0x00) Address: XX:XX:XX:XX:XX:XX PHY: LE 2M PHY (0x02) Periodic advertising Interval: 10.000 Clock Accuracy: 0x05 .EE .UNINDENT .UNINDENT .sp On success, the delegator now has a PA sync (\fBSync handle: 1\fP) and will begin receiving PA Reports and BIG Info events, continuing from step 3 of the non\-PAST flow above. .sp \fBNote:\fP .INDENT 0.0 .INDENT 3.5 \fBRace condition\fP: The PAST Parameters command must be sent \fBbefore\fP the assistant sends the PAST Transfer. In BlueZ, the PA sync state in BASS is set after probing completes to avoid the remote sending PAST before the kernel has enabled PAST Parameters on the HCI level. .UNINDENT .UNINDENT .SS BIG Sync Teardown .sp \fBReceiver\-initiated teardown\fP \-\- the receiver terminates its BIG sync: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE BIG Terminate Sync (0x08|0x006c) plen 1 #500 [hci0] 5.000003 BIG Handle: 0x00 > HCI Event: Command Complete (0x0e) plen 5 #501 [hci0] 5.001003 LE BIG Terminate Sync (0x08|0x006c) ncmd 1 Status: Success (0x00) BIG Handle: 0x00 .EE .UNINDENT .UNINDENT .sp \fBBroadcaster\-initiated teardown\fP \-\- the broadcaster terminates its BIG, and the receiver gets a BIG Sync Lost event: .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: LE Meta Event (0x3e) plen 2 #510 [hci0] 6.000003 LE BIG Sync Lost (0x1e) BIG Handle: 0x00 Reason: Connection Terminated By Local Host (0x16) .EE .UNINDENT .UNINDENT .sp The \fBReason\fP field indicates why sync was lost: .INDENT 0.0 .IP \(bu 2 \fB0x08\fP (Connection Timeout) \-\- BIG packets not received within the sync timeout. .IP \(bu 2 \fB0x13\fP (Remote User Terminated Connection) \-\- broadcaster stopped the BIG intentionally. .IP \(bu 2 \fB0x16\fP (Connection Terminated By Local Host) \-\- local controller terminated. .IP \(bu 2 \fB0x3e\fP (Connection Failed to be Established) \-\- could not establish sync initially. .UNINDENT .sp \fBSource\-side BIG termination\fP (broadcaster tears down): .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE Terminate BIG (0x08|0x006a) plen 2 #520 [hci0] 7.000003 BIG Handle: 0x00 Reason: Connection Terminated By Local Host (0x16) > HCI Event: LE Meta Event (0x3e) plen 2 #521 [hci0] 7.001003 LE BIG Terminate (0x1c) BIG Handle: 0x00 Reason: Connection Terminated By Local Host (0x16) .EE .UNINDENT .UNINDENT .SS BIG Sync Failure Diagnosis .sp When analyzing a trace where BIG sync fails, check the following in order: .INDENT 0.0 .IP 1. 3 \fBIs PA sync established?\fP \-\- Look for \fBLE Periodic Advertising Sync Established\fP with \fBStatus: Success\fP\&. If missing, the receiver never synced to the PA train. .IP 2. 3 \fBAre PA Reports arriving?\fP \-\- Look for \fBLE Periodic Advertising Report\fP events. If absent after PA sync, the PA train may have been lost. .IP 3. 3 \fBIs BIG Info received?\fP \-\- Look for \fBLE BIG Info Advertising Report\fP\&. \fBIf this event never appears, the BIG does not exist on this PA train\fP, or the broadcaster has not yet started it. Without BIG Info, \fBLE BIG Create Sync\fP cannot be sent. .IP 4. 3 \fBIs BIG Create Sync sent?\fP \-\- If BIG Info was received but \fBLE BIG Create Sync\fP was never sent, the host\-side logic failed to act on the BIG Info (e.g., mismatched codec, encryption mismatch, application\-level issue). .IP 5. 3 \fBDoes BIG Sync Established succeed?\fP \-\- Check the \fBStatus\fP field. A non\-zero status means the controller could not sync to the BIG. .IP 6. 3 \fBIs ISO Data Path set up?\fP \-\- Look for \fBLE Setup ISO Data Path\fP for each BIS handle from BIG Sync Established. .IP 7. 3 \fBIs ISO Data flowing?\fP \-\- Look for \fBISO Data\fP packets on the BIS handles. .UNINDENT .SS Automating LE Audio Analysis .sp \fBIdentify LE Audio activity\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqASE Control Point\e|ASE ID\e|State:.*Codec Configured\e|State:.*QoS Configured\e|State:.*Enabling\e|State:.*Streaming\e|State:.*Releasing\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBTrack ASE state transitions\fP for a specific ASE: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqASE ID:\(dq output.txt .EE .UNINDENT .UNINDENT .sp Then examine the \fBState:\fP line following each ASE ID match. .sp \fBCheck codec configuration\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqCodec: LC3\e|Sampling Frequency:\e|Frame Duration:\e|Frame Length:\e|Audio Channel\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBVerify CIS establishment\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqSet CIG Parameters\e|Create CIS\e|CIS Established\e|Setup ISO Data Path\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBDetect CIS failures\fP \-\- check the Status field after \fBCIS Established\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqCIS Established\(dq output.txt .EE .UNINDENT .UNINDENT .sp Then examine the following line for \fBStatus:\fP\&. .sp \fBDetect broadcast audio\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqBasic Audio Announcement\e|Create BIG\e|BIG Complete\e|BIG Create Sync\e|BIG Sync\e|BIG Info\e|BIG Terminate\e|BIG Sync Lost\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBTrace BIG Sync receiver flow\fP \-\- verify each prerequisite step: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqPeriodic Advertising Create Sync\e|Periodic Advertising Sync Established\e|BIG Info Advertising Report\e|BIG Create Sync\e|BIG Sync Established\e|BIG Sync Lost\e|BIG Terminate\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBDetect PAST\-based sync\fP \-\- check for Periodic Advertising Sync Transfer: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqSync Transfer Parameters\e|Sync Transfer (0x08\e|PAST Received\e|PA Sync:.*PAST\e|Add Source\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBCheck BIG Info arrival\fP \-\- the critical gate for BIG sync. If this is absent, the receiver has no BIG to sync to: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqBIG Info Advertising Report\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBFull LE Audio diagnosis pattern\fP: .sp \fIUnicast (CIS) flow:\fP .INDENT 0.0 .IP 1. 3 Find PACS reads \-\- verify codec compatibility between devices .IP 2. 3 Find ASE Control Point writes \-\- trace the Config Codec → Config QoS → Enable sequence .IP 3. 3 Find ASE state notifications \-\- verify each transition succeeds .IP 4. 3 Find CIG Parameters and CIS creation \-\- verify HCI\-level setup .IP 5. 3 Find \fBCIS Established\fP \-\- check Status for success .IP 6. 3 Find \fBSetup ISO Data Path\fP \-\- verify data path configuration .IP 7. 3 Find ISO Data packets \-\- confirm audio is flowing .IP 8. 3 On failure, check ASE Control Point notification responses for error codes (Response Code and Response Reason fields) .UNINDENT .sp \fIBroadcast (BIG) receiver flow:\fP .INDENT 0.0 .IP 1. 3 Find \fBPeriodic Advertising Create Sync\fP or \fBPAST Received\fP \-\- how did PA sync start? .IP 2. 3 Find \fBPeriodic Advertising Sync Established\fP or \fBPAST Received\fP with \fBStatus: Success\fP \-\- is PA synced? .IP 3. 3 Find \fBPeriodic Advertising Report\fP with \fBBasic Audio Announcement\fP \-\- is BASE data arriving? .IP 4. 3 Find \fBBIG Info Advertising Report\fP \-\- \fBcritical\fP: does the BIG exist? If missing, the BIG cannot be synced. .IP 5. 3 Find \fBBIG Create Sync\fP \-\- did the host request BIG sync? .IP 6. 3 Find \fBBIG Sync Established\fP \-\- check \fBStatus\fP for success. .IP 7. 3 Find \fBSetup ISO Data Path\fP for each BIS handle. .IP 8. 3 Find \fBISO Data\fP on BIS handles \-\- confirm audio is flowing. .IP 9. 3 On failure, check for \fBBIG Sync Lost\fP and examine \fBReason\fP\&. .UNINDENT .\" This file is included by btmon.rst. . .SH CHANNEL SOUNDING PROTOCOL FLOW .sp Channel Sounding (CS) enables precise distance measurement between two Bluetooth LE devices. It uses dedicated HCI commands and events to configure and execute measurement procedures that exchange tones and timing packets on multiple channels. btmon fully decodes all CS operations including step\-level result data. .sp CS uses two roles: the \fBInitiator\fP sends CS packets first in each step, and the \fBReflector\fP responds. Both devices must support CS (feature bits 46\-\-48 in the LE feature set). .SS Capability Exchange .sp Before any CS activity, both devices must learn each other\(aqs CS capabilities. The local controller\(aqs capabilities are read once, and the remote device\(aqs capabilities are fetched over the air. .sp \fBRead local CS capabilities\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE CS Read Local Supported Capabilities (0x08|0x0089) plen 0 #100 [hci0] > HCI Event: Command Complete (0x0e) plen 42 #101 [hci0] LE CS Read Local Supported Capabilities (0x08|0x0089) ncmd 1 Status: Success (0x00) Num Config Supported: 4 Max Consecutive Procedures Supported: 255 Num Antennas Supported: 2 Max Antenna Paths Supported: 4 Roles Supported: 0x03 Initiator Reflector Modes Supported: 0x03 RTT Capability: 0x03 RTT AA Only N: 10 RTT Sounding N: 10 RTT Random Payload N: 10 CS Sync PHYs Supported: 0x02 LE 2M T_IP1 Times Supported: 0x0005 10 us 30 us T_IP2 Times Supported: 0x0005 10 us 30 us T_FCS Times Supported: 0x0009 15 us 50 us T_PM Times Supported: 0x0003 10 us 20 us .EE .UNINDENT .UNINDENT .sp Key capability fields: .INDENT 0.0 .IP \(bu 2 \fBRoles Supported\fP \-\- Bitmask: bit 0 = Initiator, bit 1 = Reflector. A device must support at least one role. .IP \(bu 2 \fBModes Supported\fP \-\- Bitmask of CS measurement modes the controller can perform (Mode 1 = RTT, Mode 2 = PBR tone, Mode 3 = both). .IP \(bu 2 \fBCS Sync PHYs\fP \-\- Physical layers available for CS sync exchange. \fBLE 2M\fP is the most common. .IP \(bu 2 \fBNum Antennas / Max Antenna Paths\fP \-\- Determines multi\-antenna Phase\-Based Ranging capability. .IP \(bu 2 \fBT_IP / T_FCS / T_PM times\fP \-\- Timing parameters the controller supports for inter\-step and tone durations. .UNINDENT .sp \fBRead remote CS capabilities\fP (over the air via LL): .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE CS Read Remote Supported Capabilities (0x08|0x008a) plen 2 #110 [hci0] Handle: 2048 > HCI Event: Command Status (0x0f) plen 4 #111 [hci0] LE CS Read Remote Supported Capabilities (0x08|0x008a) ncmd 1 Status: Success (0x00) > HCI Event: LE Meta Event (0x3e) plen 42 #115 [hci0] LE CS Read Remote Supported Capabilities Complete (0x2c) Status: Success (0x00) Handle: 2048 Num Config Supported: 4 Max Consecutive Procedures Supported: 128 Num Antennas Supported: 1 Max Antenna Paths Supported: 1 Roles Supported: 0x03 Initiator Reflector Modes Supported: 0x03 ... .EE .UNINDENT .UNINDENT .sp This is an asynchronous command \-\- the controller sends Command Status immediately, then the completion event arrives after the LL exchange. If this fails with a non\-zero status, the remote device may not support CS or the connection may have dropped. .sp Cached capabilities can be written directly instead of reading over the air, using \fBLE CS Write Cached Remote Supported Capabilities\fP\&. .SS Security Enable .sp CS security must be enabled on the connection before creating configurations. This performs a CS security start procedure over the LL to exchange nonces: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE CS Security Enable (0x08|0x008c) plen 2 #120 [hci0] Handle: 2048 > HCI Event: Command Status (0x0f) plen 4 #121 [hci0] LE CS Security Enable (0x08|0x008c) ncmd 1 Status: Success (0x00) > HCI Event: LE Meta Event (0x3e) plen 3 #125 [hci0] LE CS Security Enable Complete (0x2e) Status: Success (0x00) Handle: 2048 .EE .UNINDENT .UNINDENT .sp Security Enable must complete successfully before configuration or procedure commands. If this fails, the link may lack encryption or the remote does not support CS security. .SS Default Settings .sp Default settings configure which roles the local device is willing to accept and the antenna/power preferences for this connection: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE CS Set Default Settings (0x08|0x008d) plen 5 #130 [hci0] Handle: 2048 Role Enable: 0x03 Initiator Reflector CS Sync Antenna Selection: 0x01 Max TX Power: 20 > HCI Event: Command Complete (0x0e) plen 5 #131 [hci0] LE CS Set Default Settings (0x08|0x008d) ncmd 1 Status: Success (0x00) Handle: 2048 .EE .UNINDENT .UNINDENT .INDENT 0.0 .IP \(bu 2 \fBRole Enable\fP \-\- Bitmask of roles the device is willing to perform. Both sides typically enable both roles for flexibility. .IP \(bu 2 \fBCS Sync Antenna Selection\fP \-\- Preferred antenna for CS sync exchange. .IP \(bu 2 \fBMax TX Power\fP \-\- Upper bound on transmit power for CS procedures (dBm, signed). .UNINDENT .SS FAE Table Exchange .sp The Frequency Actuation Error (FAE) table contains per\-channel calibration data that improves distance accuracy. Like capabilities, it can be read from the remote or written from a cache. .sp \fBRead remote FAE table\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE CS Read Remote FAE Table (0x08|0x008e) plen 2 #135 [hci0] Handle: 2048 > HCI Event: Command Status (0x0f) plen 4 #136 [hci0] LE CS Read Remote FAE Table (0x08|0x008e) ncmd 1 Status: Success (0x00) > HCI Event: LE Meta Event (0x3e) plen 75 #140 [hci0] LE CS Read Remote FAE Table Complete (0x2d) Status: Success (0x00) Handle: 2048 .EE .UNINDENT .UNINDENT .sp The FAE table is 72 bytes (one byte per channel). Values are signed offsets in 0.5 ppm units. A value of \fB0x7f\fP means the channel is unused or not measured. .SS Configuration .sp A CS configuration defines the measurement parameters: mode, role, channel map, and step counts. Up to 4 configurations can exist simultaneously per connection (\fBconfig_id\fP 0\-\-3). .sp \fBCreate a CS configuration\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE CS Create Config (0x08|0x0090) plen 20 #150 [hci0] Handle: 2048 Config ID: 0 Create Context: 0x00 Main Mode Type: 0x01 Sub Mode Type: 0xff Min Main Mode Steps: 2 Max Main Mode Steps: 5 Main Mode Repetition: 0 Mode 0 Steps: 3 Role: Initiator (0x00) RTT Type: 0x01 CS Sync PHY: LE 2M (0x01) Channel Map: ffffffffff7f0000000000000000 Channel Map Repetition: 1 Channel Selection Type: 0x00 Ch3c Shape: 0x00 Ch3c Jump: 0x00 > HCI Event: Command Status (0x0f) plen 4 #151 [hci0] LE CS Create Config (0x08|0x0090) ncmd 1 Status: Success (0x00) > HCI Event: LE Meta Event (0x3e) plen 30 #155 [hci0] LE CS Config Complete (0x2f) Status: Success (0x00) Handle: 2048 Config ID: 0 Action: 0x00 Main Mode Type: 0x01 Sub Mode Type: 0xff Min Main Mode Steps: 2 Max Main Mode Steps: 5 Main Mode Repetition: 0 Mode 0 Steps: 3 Role: Initiator (0x00) RTT Type: 0x01 CS Sync PHY: LE 2M (0x01) Channel Map: ffffffffff7f0000000000000000 Channel Map Repetition: 1 Channel Selection Type: 0x00 Ch3c Shape: 0x00 Ch3c Jump: 0x00 T_IP1 Time: 30 us T_IP2 Time: 30 us T_FCS Time: 50 us T_PM Time: 10 us .EE .UNINDENT .UNINDENT .sp Key configuration fields: .INDENT 0.0 .IP \(bu 2 \fBMain Mode Type\fP \-\- The primary CS measurement mode: \fB0x01\fP = Mode 1 (RTT only), \fB0x02\fP = Mode 2 (PBR/tone only), \fB0x03\fP = Mode 3 (RTT + PBR combined). .IP \(bu 2 \fBSub Mode Type\fP \-\- Secondary mode interleaved with main mode. \fB0xff\fP = no sub mode. .IP \(bu 2 \fBMode 0 Steps\fP \-\- Number of frequency calibration (sync) steps per subevent. Mode 0 is always present for frequency offset compensation. .IP \(bu 2 \fBMain/Min/Max Mode Steps\fP \-\- Controls how many measurement steps are performed per subevent. .IP \(bu 2 \fBRole\fP \-\- \fB0x00\fP = Initiator, \fB0x01\fP = Reflector. This is the local device\(aqs role in this configuration. .IP \(bu 2 \fBRTT Type\fP \-\- Round\-trip time measurement variant (AA only, sounding sequence, random sequence). .IP \(bu 2 \fBChannel Map\fP \-\- 10\-byte bitmask of channels available for CS. Must have at least 15 channels enabled. .IP \(bu 2 \fBT_IP1/T_IP2/T_FCS/T_PM\fP \-\- Timing parameters selected by the controller from the intersection of both devices\(aq capabilities. Reported in the Config Complete event. .UNINDENT .sp The Config Complete event confirms the negotiated parameters. The controller may adjust timing values based on both devices\(aq capabilities. .sp \fBRemove a CS configuration\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE CS Remove Config (0x08|0x0091) plen 3 #160 [hci0] Handle: 2048 Config ID: 0 > HCI Event: Command Status (0x0f) plen 4 #161 [hci0] LE CS Remove Config (0x08|0x0091) ncmd 1 Status: Success (0x00) .EE .UNINDENT .UNINDENT .SS Procedure Parameters .sp Before enabling a procedure, its scheduling and antenna parameters are configured: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE CS Set Procedure Parameters (0x08|0x0093) plen 16 #170 [hci0] Handle: 2048 Config ID: 0 Max Procedure Len: 200 Min Procedure Interval: 10 Max Procedure Interval: 20 Max Procedure Count: 0 Min Subevent Len: 5000 us Max Subevent Len: 10000 us Tone Antenna Config Selection: 0x01 PHY: LE 2M (0x02) TX Power Delta: 0 Preferred Peer Antenna: 0x01 SNR Control Initiator: 0x00 SNR Control Reflector: 0x00 > HCI Event: Command Complete (0x0e) plen 5 #171 [hci0] LE CS Set Procedure Parameters (0x08|0x0093) ncmd 1 Status: Success (0x00) Handle: 2048 .EE .UNINDENT .UNINDENT .INDENT 0.0 .IP \(bu 2 \fBMax Procedure Len\fP \-\- Maximum duration of a single CS procedure in units of 0.625 ms. .IP \(bu 2 \fBProcedure Interval\fP \-\- Spacing between repeated procedures (in connection events). .IP \(bu 2 \fBMax Procedure Count\fP \-\- \fB0\fP means indefinite repetition until explicitly disabled. .IP \(bu 2 \fBSubevent Len\fP \-\- Duration bounds for each subevent within a procedure (in microseconds, 24\-bit). .IP \(bu 2 \fBTone Antenna Config Selection\fP \-\- Which antenna pattern to use for tone exchange. .IP \(bu 2 \fBSNR Control\fP \-\- Signal\-to\-noise ratio requirements for Initiator and Reflector. .UNINDENT .SS Procedure Enable / Disable .sp Once configuration and parameters are set, the CS procedure is started and stopped with enable/disable commands. .sp \fBEnable a CS procedure\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE CS Procedure Enable (0x08|0x0094) plen 4 #180 [hci0] Handle: 2048 Config ID: 0 Enable: 0x01 > HCI Event: Command Status (0x0f) plen 4 #181 [hci0] LE CS Procedure Enable (0x08|0x0094) ncmd 1 Status: Success (0x00) > HCI Event: LE Meta Event (0x3e) plen 20 #185 [hci0] LE CS Procedure Enable Complete (0x30) Status: Success (0x00) Handle: 2048 Config ID: 0 State: 0x01 Tone Antenna Config Selection: 0x01 Selected TX Power: 12 Subevent Len: 5000 us Subevents Per Event: 2 Subevent Interval: 3750 Event Interval: 10 Procedure Interval: 10 Procedure Count: 100 Max Procedure Len: 200 .EE .UNINDENT .UNINDENT .sp The Procedure Enable Complete event confirms the actual scheduling parameters chosen by the controller. Important fields: .INDENT 0.0 .IP \(bu 2 \fBState\fP \-\- \fB0x01\fP = procedure enabled, \fB0x00\fP = disabled. .IP \(bu 2 \fBSubevents Per Event\fP \-\- How many subevents fit in each connection event. .IP \(bu 2 \fBProcedure Count\fP \-\- Actual number of procedures that will be executed (may differ from the requested maximum). .UNINDENT .sp \fBDisable a CS procedure\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE CS Procedure Enable (0x08|0x0094) plen 4 #300 [hci0] Handle: 2048 Config ID: 0 Enable: 0x00 > HCI Event: Command Status (0x0f) plen 4 #301 [hci0] LE CS Procedure Enable (0x08|0x0094) ncmd 1 Status: Success (0x00) > HCI Event: LE Meta Event (0x3e) plen 20 #305 [hci0] LE CS Procedure Enable Complete (0x30) Status: Success (0x00) Handle: 2048 Config ID: 0 State: 0x00 ... .EE .UNINDENT .UNINDENT .SS Channel Classification .sp The host can restrict which channels CS uses by marking noisy channels as unused: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE CS Set Channel Classification (0x08|0x0092) plen 10 #145 [hci0] Channel Map: ffffffffff7f0000000000 > HCI Event: Command Complete (0x0e) plen 1 #146 [hci0] LE CS Set Channel Classification (0x08|0x0092) ncmd 1 Status: Success (0x00) .EE .UNINDENT .UNINDENT .sp The channel map is a 10\-byte (80\-bit) bitmask. Bit N = 1 means channel N is available for CS. At least 15 channels must remain enabled or the procedure will abort. .SS Subevent Results .sp While a CS procedure is running, the controller reports measurement results via subevent result events. Each subevent contains multiple steps, and each step contains mode\-specific measurement data. .sp \fBSubevent Result\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: LE Meta Event (0x3e) plen 50 #200 [hci0] LE CS Subevent Result (0x31) Handle: 2048 Config ID: 0 Start ACL Conn Event Counter: 1200 Procedure Counter: 0 Frequency Compensation: 0x0000 Reference Power Level: \-20 Procedure Done Status: Partial results (0x01) Subevent Done Status: All results complete (0x00) Abort Reason: 0x00 Num Antenna Paths: 1 Num Steps Reported: 8 Step Data: Mode: 0 Channel: 10 Length: 5 Packet Quality: 0x00 Packet RSSI: \-45 Packet Antenna: 0 Measured Freq Offset: 150 Mode: 1 Channel: 20 Length: 6 Packet Quality: 0x00 Packet NADM: Attack is extremely unlikely (0x00) Packet RSSI: \-42 ToA/ToD: 1234 Packet Antenna: 0 Mode: 2 Channel: 30 Length: 5 Antenna Permutation Index: 0 PCT[0]: I=1024, Q=\-512 Tone Quality: High (0x00) ... .EE .UNINDENT .UNINDENT .sp Result events can be split across multiple events when data is large: .sp \fBSubevent Result Continue\fP (continuation fragment): .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: LE Meta Event (0x3e) plen 40 #202 [hci0] LE CS Subevent Result Continue (0x32) Handle: 2048 Config ID: 0 Procedure Done Status: Partial results (0x01) Subevent Done Status: All results complete (0x00) Abort Reason: 0x00 Num Antenna Paths: 1 Num Steps Reported: 4 Step Data: ... .EE .UNINDENT .UNINDENT .SS CS Step Modes .sp Each CS subevent consists of steps executed on different channels. Each step operates in one of four modes: .sp \fBMode 0 \-\- Frequency Calibration (CS Sync)\fP .sp Mode 0 steps are always present at the start of each subevent. They exchange a known CS sync sequence to calibrate frequency offsets between the two devices. .sp Fields reported: .INDENT 0.0 .IP \(bu 2 \fBPacket Quality\fP \-\- \fB0x00\fP = CS Access Address matched, \fB0x01\fP = bit errors detected, \fB0x02\fP = not found. .IP \(bu 2 \fBPacket RSSI\fP \-\- Received signal strength (signed, dBm). .IP \(bu 2 \fBPacket Antenna\fP \-\- Antenna index used. .IP \(bu 2 \fBMeasured Freq Offset\fP \-\- Frequency offset in 0.01 ppm units (15\-bit value). Only present when data length is 5. .UNINDENT .sp \fBMode 1 \-\- Round\-Trip Time (RTT)\fP .sp Mode 1 measures the time\-of\-arrival and time\-of\-departure to compute round\-trip delay for distance estimation. .sp Fields reported: .INDENT 0.0 .IP \(bu 2 \fBPacket Quality\fP \-\- Same as Mode 0. .IP \(bu 2 \fBPacket NADM\fP \-\- Normalized Attack Detector Metric, indicates likelihood that the RTT measurement has been tampered with: .INDENT 2.0 .IP \(bu 2 \fB0x00\fP = attack extremely unlikely .IP \(bu 2 \fB0x01\fP = attack very unlikely .IP \(bu 2 \fB0x02\fP = attack unlikely .IP \(bu 2 \fB0x03\fP = attack possible .IP \(bu 2 \fB0x04\fP = attack likely .IP \(bu 2 \fB0x05\fP = attack very likely .IP \(bu 2 \fB0x06\fP = attack extremely likely .IP \(bu 2 \fB0xff\fP = unknown .UNINDENT .IP \(bu 2 \fBToA/ToD\fP \-\- Time\-of\-Arrival minus Time\-of\-Departure difference (signed 16\-bit). \fB0x8000\fP = measurement not available. .IP \(bu 2 \fBPCT1/PCT2\fP \-\- Phase Correction Terms (I/Q, 12 bits each). Present only with certain RTT types. .UNINDENT .sp \fBMode 2 \-\- Phase\-Based Ranging (PBR / Tone Exchange)\fP .sp Mode 2 exchanges tones for high\-precision phase measurement. Multiple antenna paths can be measured simultaneously. .sp Fields reported: .INDENT 0.0 .IP \(bu 2 \fBAntenna Permutation Index\fP \-\- Identifies the antenna switching pattern used. .IP \(bu 2 \fBPCT\fP (per antenna path) \-\- Phase Correction Term with I component (bits 0\-\-11) and Q component (bits 12\-\-23). .IP \(bu 2 \fBTone Quality Indicator\fP \-\- Low nibble: .INDENT 2.0 .IP \(bu 2 \fB0x00\fP = high quality .IP \(bu 2 \fB0x01\fP = medium quality .IP \(bu 2 \fB0x02\fP = low quality .IP \(bu 2 \fB0x03\fP = not available .UNINDENT .sp High nibble (tone extension slot): .INDENT 2.0 .IP \(bu 2 \fB0x00\fP = not a tone extension slot .IP \(bu 2 \fB0x01\fP = tone extension slot, tone not expected .IP \(bu 2 \fB0x02\fP = tone extension slot, tone expected .UNINDENT .UNINDENT .sp \fBMode 3 \-\- Combined RTT + Tone\fP .sp Mode 3 combines Mode 1 and Mode 2 in a single step. The initial fields match Mode 1 (quality, NADM, RSSI, ToA/ToD, antenna), followed by Mode 2 tone data. .SS Result Status Fields .sp Each subevent result event contains status fields that indicate whether the procedure and subevent completed normally. .sp \fBProcedure Done Status\fP: .INDENT 0.0 .IP \(bu 2 \fB0x00\fP \-\- All results complete for this CS procedure .IP \(bu 2 \fB0x01\fP \-\- Partial results, more events will follow .IP \(bu 2 \fB0x0f\fP \-\- All subsequent procedures aborted .UNINDENT .sp \fBSubevent Done Status\fP: .INDENT 0.0 .IP \(bu 2 \fB0x00\fP \-\- All results complete for this subevent .IP \(bu 2 \fB0x01\fP \-\- Partial results, continuation event will follow .IP \(bu 2 \fB0x0f\fP \-\- Current subevent aborted .UNINDENT .sp \fBAbort Reason\fP (packed byte): .sp Low nibble (procedure abort reason): .INDENT 0.0 .IP \(bu 2 \fB0x00\fP \-\- No abort .IP \(bu 2 \fB0x01\fP \-\- Aborted by local Host or remote request .IP \(bu 2 \fB0x02\fP \-\- Channel map has fewer than 15 channels .IP \(bu 2 \fB0x03\fP \-\- Channel map update instant has passed .IP \(bu 2 \fB0x0f\fP \-\- Unspecified reason .UNINDENT .sp High nibble (subevent abort reason): .INDENT 0.0 .IP \(bu 2 \fB0x00\fP \-\- No abort .IP \(bu 2 \fB0x01\fP \-\- Aborted by local Host or remote request .IP \(bu 2 \fB0x02\fP \-\- No CS_SYNC (Mode 0) received .IP \(bu 2 \fB0x03\fP \-\- Scheduling conflict or limited resources .IP \(bu 2 \fB0x0f\fP \-\- Unspecified reason .UNINDENT .SS CS State Machine .sp A CS configuration transitions through these states on each device. The state is not reported explicitly in a single field \-\- it is inferred from the sequence of HCI commands and events: .INDENT 0.0 .INDENT 3.5 .sp .EX ┌──────────┐ │ IDLE │ └────┬─────┘ │ LE CS Read Local/Remote Supported Capabilities │ LE CS Security Enable Complete │ LE CS Set Default Settings ▼ ┌──────────────────┐ │ CAPABILITIES │ Both devices\(aq CS parameters are known. │ EXCHANGED │ Security is established. └────┬─────────────┘ │ LE CS Create Config │ ──► LE CS Config Complete (status=0x00) ▼ ┌──────────────────┐ │ CONFIGURED │ Config ID N exists with negotiated params. │ (config_id=N) │ Can create up to 4 configs simultaneously. └────┬─────────────┘ │ LE CS Set Procedure Parameters ▼ ┌──────────────────┐ │ PARAMETERS SET │ Scheduling and antenna params are locked. │ (config_id=N) │ └────┬─────────────┘ │ LE CS Procedure Enable (enable=1) │ ──► LE CS Procedure Enable Complete (state=1) ▼ ┌──────────────────┐ │ PROCEDURE │ Controller is actively measuring. │ RUNNING │ Subevent Result events stream in. │ (config_id=N) │ └────┬─────────────┘ │ LE CS Procedure Enable (enable=0) │ ──► LE CS Procedure Enable Complete (state=0) ▼ ┌──────────────────┐ │ CONFIGURED │ Config still exists, can re\-enable. │ (config_id=N) │ └────┬─────────────┘ │ LE CS Remove Config ▼ ┌──────────┐ │ IDLE │ └──────────┘ .EE .UNINDENT .UNINDENT .sp Multiple configurations can coexist. Removing one config does not affect others. .SS Typical CS Setup Sequence .sp A complete HCI\-only CS distance measurement session follows this flow. The left column shows the Host ──► Controller commands, and the right column shows the resulting state: .INDENT 0.0 .INDENT 3.5 .sp .EX Host ──► Controller State ═══════════════════════════════════════════════════════════════ LE CS Read Local Supported Capabilities IDLE ◄── Command Complete (capabilities) │ LE CS Read Remote Supported Capabilities │ ◄── Command Status │ ◄── LE CS Read Remote Supp. Cap. Complete │ │ LE CS Security Enable │ ◄── Command Status │ ◄── LE CS Security Enable Complete │ │ LE CS Set Default Settings │ ◄── Command Complete ▼ CAPABILITIES EXCHANGED LE CS Read Remote FAE Table (optional) │ ◄── LE CS Read Remote FAE Complete │ │ LE CS Set Channel Classification (optional) │ ◄── Command Complete │ │ LE CS Create Config (config_id=0) │ ◄── Command Status │ ◄── LE CS Config Complete ▼ CONFIGURED (id=0) LE CS Set Procedure Parameters (config_id=0) │ ◄── Command Complete ▼ PARAMETERS SET (id=0) LE CS Procedure Enable (id=0, enable=1) │ ◄── Command Status │ ◄── LE CS Procedure Enable Complete (state=1)▼ PROCEDURE RUNNING (id=0) ◄── LE CS Subevent Result ┐ │ ◄── LE CS Subevent Result Cont. │(repeated) │ ◄── LE CS Subevent Result ┘ │ │ LE CS Procedure Enable (id=0, enable=0) │ ◄── Command Status │ ◄── LE CS Procedure Enable Complete (state=0)▼ CONFIGURED (id=0) LE CS Remove Config (id=0) (optional) │ ◄── Command Status ▼ IDLE .EE .UNINDENT .UNINDENT .sp Steps above the first \fBLE CS Create Config\fP are one\-time setup per connection. The config/params/enable/disable cycle can be repeated. .SS CS Test Mode .sp CS Test mode allows the controller to run CS procedures without a remote device, for manufacturing and validation purposes: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE CS Test (0x08|0x0095) plen 34 #400 [hci0] Main Mode Type: 0x01 Sub Mode Type: 0xff Main Mode Repetition: 0 Mode 0 Steps: 3 Role: Initiator (0x00) RTT Type: 0x01 CS Sync PHY: LE 2M (0x01) CS Sync Antenna Selection: 0x01 Subevent Len: 5000 us Subevent Interval: 0 Max Num Subevents: 1 Transmit Power Level: 10 T_IP1 Time: 30 us T_IP2 Time: 30 us T_FCS Time: 50 us T_PM Time: 10 us T_SW Time: 0 us Tone Antenna Config Selection: 0x01 SNR Control Initiator: 0x00 SNR Control Reflector: 0x00 DRBG Nonce: 0x0000 Channel Map Repetition: 1 Override Config: 0x0000 > HCI Event: Command Complete (0x0e) plen 5 #401 [hci0] LE CS Test (0x08|0x0095) ncmd 1 Status: Success (0x00) .EE .UNINDENT .UNINDENT .sp Results arrive as normal \fBLE CS Subevent Result\fP events. The test is terminated with: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE CS Test End (0x08|0x0096) plen 0 #450 [hci0] > HCI Event: LE Meta Event (0x3e) plen 1 #451 [hci0] LE CS Test End Complete (0x33) Status: Success (0x00) .EE .UNINDENT .UNINDENT .SS Common Issues .INDENT 0.0 .TP .B \fBSecurity Enable fails\fP The connection must be encrypted before CS Security Enable. Check that SMP pairing and LE Encrypt have completed. Also verify both devices advertise CS support in their LE feature set. .TP .B \fBConfig Complete with error\fP The controller may reject a configuration if the requested parameters are incompatible with the intersection of both devices\(aq capabilities. Check that main mode, PHY, and timing parameters fall within both devices\(aq supported ranges. .TP .B \fBChannel map too small\fP CS requires at least 15 channels. If channel classification or interference eliminates too many channels, the procedure aborts with abort reason \fB0x02\fP (low nibble). .TP .B \fBNo Mode 0 sync received\fP Subevent abort reason \fB0x02\fP (high nibble) means the Initiator\(aqs Mode 0 sync packet was not received by the Reflector (or vice versa). This indicates an RF issue, scheduling miss, or device out of range. .TP .B \fBProcedure aborted by host\fP Abort reason \fB0x01\fP in either nibble means the local host or remote device requested termination, typically via Procedure Enable with enable=0. .TP .B \fBToA/ToD not available (0x8000)\fP The controller could not compute a valid time measurement for this step. Common with weak signals or when the CS Access Address was not detected (packet quality = 0x02). .UNINDENT .SH RANGING SERVICE (RAS) / RANGING PROFILE (RAP) .sp The Ranging Service (RAS) is a GATT service that transfers CS measurement data between devices at the application layer. It complements the HCI\-level CS procedure by providing a standardized way to exchange, acknowledge, and retrieve ranging results over ATT. The Ranging Profile (RAP) defines how a device discovers and interacts with RAS on a remote device. .sp btmon decodes all RAS characteristics automatically when it sees ATT operations on the RAS UUIDs. .sp RAS Service UUID: \fB0x185B\fP .SS RAS Characteristics .TS box center; l|l|l|l. T{ Characteristic T} T{ UUID T} T{ Properties T} T{ Description T} _ T{ RAS Features T} T{ 0x2C14 T} T{ Read T} T{ Bitmask of supported RAS capabilities T} _ T{ RAS Real\-time Ranging Data T} T{ 0x2C15 T} T{ Notify, Indicate T} T{ Streaming ranging results as they are produced T} _ T{ RAS On\-demand Ranging Data T} T{ 0x2C16 T} T{ Notify, Indicate T} T{ Ranging results retrieved on request T} _ T{ RAS Control Point T} T{ 0x2C17 T} T{ Write Without Response, Indicate T} T{ Command interface for data retrieval and filtering T} _ T{ RAS Ranging Data Ready T} T{ 0x2C18 T} T{ Read, Notify, Indicate T} T{ Signals that a new ranging dataset is available T} _ T{ RAS Ranging Data Overwritten T} T{ 0x2C19 T} T{ Read, Notify, Indicate T} T{ Signals that a stored dataset was overwritten T} .TE .sp All characteristics except RAS Features use Client Characteristic Configuration (CCC) descriptors to enable notifications/indications. The typical GATT attribute layout uses 18 handles total. .SS RAS Features .sp The RAS Features characteristic is a 4\-byte bitmask read by the client to discover the server\(aqs capabilities: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 11 #200 [hci0] ATT: Read Response (0x0b) len 4 Handle: 0x0003 Features: 0x0000000f Real\-time Ranging Data (0x00000001) Retrieve Lost Ranging Data Segments (0x00000002) Abort Operation (0x00000004) Filter Ranging Data (0x00000008) .EE .UNINDENT .UNINDENT .sp Feature bits: .INDENT 0.0 .IP \(bu 2 \fBBit 0\fP \-\- Real\-time Ranging Data: the server can stream results via the Real\-time Ranging Data characteristic as CS procedures run. .IP \(bu 2 \fBBit 1\fP \-\- Retrieve Lost Ranging Data Segments: the client can request retransmission of missing data segments. .IP \(bu 2 \fBBit 2\fP \-\- Abort Operation: the client can cancel an in\-progress data transfer. .IP \(bu 2 \fBBit 3\fP \-\- Filter Ranging Data: the client can configure which fields are included in ranging data notifications. .UNINDENT .SS RAS Ranging Data Ready .sp When a CS procedure completes and results are stored, the server notifies the client that data is available for retrieval: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 9 #250 [hci0] ATT: Handle Value Notification (0x1b) len 2 Handle: 0x000e Counter: 42 .EE .UNINDENT .UNINDENT .sp The counter value identifies the specific ranging dataset. The client uses this counter in subsequent Control Point commands to request or acknowledge the data. .SS RAS Ranging Data Overwritten .sp If the server\(aqs buffer is full and older results are discarded, this characteristic notifies the client: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 9 #260 [hci0] ATT: Handle Value Notification (0x1b) len 2 Handle: 0x0011 Overwritten Count: 38 .EE .UNINDENT .UNINDENT .sp This tells the client that ranging data with counter value 38 (and possibly earlier) has been overwritten and can no longer be retrieved. .SS RAS Control Point .sp The RAS Control Point is the command interface for managing ranging data transfers. The client writes commands, and the server responds via indications on the same characteristic. .sp \fBOpcodes:\fP .TS box center; l|l|l. T{ Opcode T} T{ Command T} T{ Description T} _ T{ 0x00 T} T{ Get Ranging Data T} T{ Request on\-demand transfer of a specific dataset T} _ T{ 0x01 T} T{ ACK Ranging Data T} T{ Acknowledge receipt of a dataset T} _ T{ 0x02 T} T{ Retrieve Lost Ranging Data Segments T} T{ Re\-request specific missing segments T} _ T{ 0x03 T} T{ Abort Operation T} T{ Cancel an in\-progress data transfer T} _ T{ 0x04 T} T{ Set Filter T} T{ Configure which data fields are included T} .TE .sp \fBGet Ranging Data\fP \-\- requests transfer of a stored dataset: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 10 #270 [hci0] ATT: Write Command (0x52) len 3 Handle: 0x000b Opcode: Get Ranging Data (0x00) Ranging Counter: 0x002a .EE .UNINDENT .UNINDENT .sp After this command, the server sends the data as a sequence of notifications on the On\-demand Ranging Data characteristic (0x2C16). .sp \fBACK Ranging Data\fP \-\- confirms the client received a complete dataset, allowing the server to free the buffer: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 10 #290 [hci0] ATT: Write Command (0x52) len 3 Handle: 0x000b Opcode: ACK Ranging Data (0x01) Ranging Counter: 0x002a .EE .UNINDENT .UNINDENT .sp \fBRetrieve Lost Ranging Data Segments\fP \-\- requests retransmission of specific segments that were missed or corrupted: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 12 #295 [hci0] ATT: Write Command (0x52) len 5 Handle: 0x000b Opcode: Retrieve Lost Ranging Data Segments (0x02) Ranging Counter: 0x002a First Segment Index: 3 Last Segment Index: 5 .EE .UNINDENT .UNINDENT .sp A Last Segment Index of \fB0xFF\fP means \(dqall remaining segments from the first index onward.\(dq .sp \fBAbort Operation\fP \-\- cancels any in\-progress data transfer: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 8 #298 [hci0] ATT: Write Command (0x52) len 1 Handle: 0x000b Opcode: Abort Operation (0x03) .EE .UNINDENT .UNINDENT .sp \fBSet Filter\fP \-\- configures which fields to include in ranging data notifications: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 2048 flags 0x00 dlen 10 #265 [hci0] ATT: Write Command (0x52) len 3 Handle: 0x000b Opcode: Set Filter (0x04) Filter Configuration: 0x0001 Mode: 1 Filter Bit Mask: 0x0000 .EE .UNINDENT .UNINDENT .sp The filter configuration is a 16\-bit value: bits [1:0] select the filter mode, and bits [15:2] provide a field bitmask. .SS Ranging Data Format .sp Both Real\-time (0x2C15) and On\-demand (0x2C16) Ranging Data characteristics use the same segmented data format. Large datasets are split across multiple ATT notifications. .sp \fBSegmentation Header\fP (1 byte, present in every notification): .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 2048 flags 0x02 dlen 30 #275 [hci0] ATT: Handle Value Notification (0x1b) len 25 Handle: 0x0005 Segmentation Header: 0x01 First Segment: True Last Segment: False Segment Index: 0 Ranging Data Body: Ranging Counter: 0x02a Configuration ID: 0 Selected TX Power: 12 dBm Antenna Paths Mask: 0x03 Antenna Path 1 (0x01) Antenna Path 2 (0x02) Subevent #0: Start ACL Connection Event: 1200 Frequency Compensation: 150 (0.01 ppm) Ranging Done Status: Partial results (0x1) Subevent Done Status: All results complete (0x0) Ranging Abort Reason: No abort (0x0) Subevent Abort Reason: No abort (0x0) Reference Power Level: \-20 dBm Number of Steps Reported: 8 Remaining Ranging Data Segment: ... .EE .UNINDENT .UNINDENT .sp Segmentation header fields: .INDENT 0.0 .IP \(bu 2 \fBFirst Segment\fP (bit 0) \-\- \fBTrue\fP for the first notification of a dataset. Only the first segment contains the Ranging Header. .IP \(bu 2 \fBLast Segment\fP (bit 1) \-\- \fBTrue\fP for the final notification. When both bits are set, the entire dataset fits in one notification. .IP \(bu 2 \fBSegment Index\fP (bits [7:2]) \-\- Sequential index for reassembly. .UNINDENT .sp \fBRanging Header\fP (4 bytes, only in first segment): .INDENT 0.0 .IP \(bu 2 \fBRanging Counter\fP (bits [11:0]) \-\- Matches the counter from the Data Ready notification; identifies the CS procedure instance. .IP \(bu 2 \fBConfiguration ID\fP (bits [15:12]) \-\- The CS configuration ID (0\-\-3) used for this measurement. .IP \(bu 2 \fBSelected TX Power\fP (1 byte, signed) \-\- Actual transmit power used, in dBm. .IP \(bu 2 \fBAntenna Paths Mask\fP (1 byte) \-\- Bitmask indicating which antenna paths have data: bit 0 = path 1, bit 1 = path 2, etc. .UNINDENT .sp \fBSubevent Header\fP (per subevent within the ranging data): .INDENT 0.0 .IP \(bu 2 \fBStart ACL Connection Event\fP \-\- The ACL connection event counter at which this CS subevent started. .IP \(bu 2 \fBFrequency Compensation\fP \-\- Measured frequency offset in 0.01 ppm units (signed 16\-bit). .IP \(bu 2 \fBRanging Done Status\fP \-\- Same codes as HCI subevent results: \fB0x0\fP = complete, \fB0x1\fP = partial, \fB0xF\fP = aborted. .IP \(bu 2 \fBSubevent Done Status\fP \-\- \fB0x0\fP = complete, \fB0xF\fP = aborted. .IP \(bu 2 \fBRanging/Subevent Abort Reason\fP \-\- Same abort reason codes as HCI: \fB0x0\fP = no abort, \fB0x1\fP = host/remote request, \fB0x2\fP = channel map / no sync, \fB0x3\fP = scheduling, \fB0xF\fP = unspecified. .IP \(bu 2 \fBReference Power Level\fP \-\- RSSI reference in dBm (signed). .IP \(bu 2 \fBNumber of Steps Reported\fP \-\- Count of CS step results that follow. .UNINDENT .sp The step data following the subevent header uses the same mode\-specific format as HCI CS Subevent Result events (Mode 0\-\-3), described in the CS Step Modes section above. .sp Continuation segments contain only the Segmentation Header followed by raw ranging data bytes that are reassembled with the previous segments. .SS RAS Data Transfer State Machine .sp The RAS data transfer for a single ranging dataset transitions through these states: .INDENT 0.0 .INDENT 3.5 .sp .EX ┌───────────────┐ │ IDLE │ No active data transfer. └─────┬─────────┘ │ Server: CS procedure completes (HCI level) │ Server: notifies Ranging Data Ready (counter=N) ▼ ┌───────────────┐ │ DATA READY │ Dataset N is buffered on server. │ (counter=N) │ Client has been notified. └─────┬─────────┘ │ Client writes: Get Ranging Data (counter=N) │ ── OR ── (real\-time mode: automatic push) ▼ ┌───────────────┐ │ TRANSFERRING │ Server sends segmented notifications. │ (counter=N) │ Segment Index increments: 0, 1, 2, ... └─────┬──────┬──┘ │ │ Segment lost? Client writes: │ │ Retrieve Lost Segments (counter=N, first, last) │ │ Server re\-sends missing segments. │ │ (loops back to TRANSFERRING) │ │ │ │ Client writes: Abort Operation │ └──────────────────────┐ │ Last Segment received │ ▼ ▼ ┌───────────────┐ ┌───────────────┐ │ COMPLETE │ │ ABORTED │ │ (counter=N) │ └───────┬───────┘ └─────┬─────────┘ │ │ Client writes: │ │ ACK Ranging Data │ │ (counter=N) │ ▼ ▼ ┌───────────────┐ │ IDLE │ Server may free buffer for counter=N. └───────────────┘ .EE .UNINDENT .UNINDENT .sp If the server\(aqs buffer fills before the client retrieves data, a Ranging Data Overwritten notification is sent and the dataset transitions directly from DATA READY to overwritten (lost). .SS Typical RAS Data Flow .sp A complete RAS session for on\-demand data retrieval: .INDENT 0.0 .INDENT 3.5 .sp .EX 1. Client discovers RAS (UUID 0x185B) via GATT primary service discovery 2. Client reads RAS Features (0x2C14) to learn capabilities 3. Client enables CCC notifications on: \- RAS Ranging Data Ready (0x2C18) \- RAS Ranging Data Overwritten (0x2C19) \- RAS On\-demand Ranging Data (0x2C16) or RAS Real\-time Ranging Data (0x2C15) 4. CS procedure runs (HCI level) 5. Server notifies Ranging Data Ready with counter=N 6. Client writes Control Point: Get Ranging Data (counter=N) 7. Server sends On\-demand Ranging Data notifications (segmented) 8. Client writes Control Point: ACK Ranging Data (counter=N) .EE .UNINDENT .UNINDENT .sp For real\-time streaming, step 6 is skipped \-\- the server pushes data on the Real\-time Ranging Data characteristic (0x2C15) as each CS subevent completes, using the same segmented format. .sp If segments are lost (e.g., due to ATT MTU limitations or missed notifications), the client uses Retrieve Lost Ranging Data Segments to request retransmission of specific segment indices. .SS Combined HCI + GATT Flow .sp The following chart shows the full interleaved timeline of HCI CS operations and GATT RAS operations as they appear in a btmon trace. The left column is HCI traffic (\fB< HCI\fP / \fB> HCI\fP), the center shows GATT ATT traffic (\fB< ACL\fP / \fB> ACL\fP), and the right column shows the combined state: .INDENT 0.0 .INDENT 3.5 .sp .EX HCI (CS) GATT (RAS) State ══════════════════════════════════════════════════════════════════════════ \-\-\- One\-time connection setup \-\-\- < LE CS Read Local Supp. Cap. IDLE > Command Complete │ < LE CS Read Remote Supp. Cap. │ > Command Status │ > LE CS Read Remote Supp. │ Cap. Complete │ < LE CS Security Enable │ > Command Status │ > LE CS Security Enable │ Complete │ < LE CS Set Default Settings │ > Command Complete ▼ CAPS EXCHANGED < ATT: Find By Type Value │ (RAS UUID 0x185B) │ > ATT: Find By Type Value │ Response │ < ATT: Read Request │ (RAS Features 0x2C14) │ > ATT: Read Response │ Features: 0x0000000f │ < ATT: Write Request │ (CCC: Ready 0x2C18) │ > ATT: Write Response │ < ATT: Write Request │ (CCC: On\-demand 0x2C16) │ > ATT: Write Response ▼ RAS READY \-\-\- Per\-measurement cycle (repeatable) \-\-\- < LE CS Create Config (id=0) │ > Command Status │ > LE CS Config Complete ▼ CONFIGURED (id=0) < LE CS Set Proc. Params (id=0) │ > Command Complete ▼ PARAMS SET (id=0) < LE CS Proc. Enable │ (id=0, enable=1) │ > Command Status │ > LE CS Proc. Enable │ Complete (state=1) ▼ PROCEDURE RUNNING > LE CS Subevent Result ─┐ │ > LE CS Subevent Result │ (measurement data │ Continue │ streams from │ > LE CS Subevent Result │ controller) │ > LE CS Subevent Result ─┘ │ │ > LE CS Subevent Result │ (Procedure Done=0x00) ▼ PROCEDURE COMPLETE > ATT: Notification │ (Data Ready 0x2C18) │ Counter: N ▼ DATA READY (N) < ATT: Write Command │ (Control Point 0x2C17) │ Get Ranging Data │ Counter: N ▼ TRANSFERRING (N) > ATT: Notification │ (On\-demand 0x2C16) │ Seg[0]: First=T Last=F │ Ranging Header + data │ > ATT: Notification │ (On\-demand 0x2C16) │ Seg[1]: First=F Last=F │ > ATT: Notification │ (On\-demand 0x2C16) │ Seg[2]: First=F Last=T ▼ TRANSFER COMPLETE (N) < ATT: Write Command │ (Control Point 0x2C17) │ ACK Ranging Data │ Counter: N ▼ IDLE (buffer freed) \-\-\- Cleanup (optional) \-\-\- < LE CS Remove Config (id=0) > Command Status IDLE .EE .UNINDENT .UNINDENT .SS Real\-time Streaming Flow .sp When the server supports real\-time ranging data (Features bit 0), results are pushed immediately as CS subevents complete, without waiting for a Get Ranging Data request: .INDENT 0.0 .INDENT 3.5 .sp .EX HCI (CS) GATT (RAS) State ══════════════════════════════════════════════════════════════════════════ < LE CS Proc. Enable PARAMS SET (id=0, enable=1) > LE CS Proc. Enable Complete ▼ PROCEDURE RUNNING > LE CS Subevent Result ──┐ │ > ATT: Notification │ (Real\-time 0x2C15) │ Seg[0]: First=T Last=T │ Ranging Header + │ subevent data > LE CS Subevent Result ──┤ │ > ATT: Notification │ (Real\-time 0x2C15) │ Seg[0]: First=T Last=F │ > ATT: Notification │ (Real\-time 0x2C15) │ Seg[1]: First=F Last=T > LE CS Subevent Result ──┘ > ATT: Notification (Real\-time 0x2C15) ... > LE CS Subevent Result │ (Procedure Done=0x00) ▼ PROCEDURE COMPLETE < ATT: Write Command ACK Ranging Data Counter: N ▼ IDLE .EE .UNINDENT .UNINDENT .sp In real\-time mode, each HCI Subevent Result triggers one or more GATT notifications immediately. The Ranging Counter in the data header increments with each procedure. If a notification segment is lost, the client can use Retrieve Lost Segments after the procedure completes (if Features bit 1 is set). .SS Lost Segment Recovery Flow .sp When segments are missed during transfer, the client requests retransmission: .INDENT 0.0 .INDENT 3.5 .sp .EX GATT (RAS) State ══════════════════════════════════════════════════════════════ > ATT: Notification (On\-demand 0x2C16) TRANSFERRING Seg[0]: First=T Last=F ── received > ATT: Notification (On\-demand 0x2C16) Seg[1]: First=F Last=F ── received │ Seg[2] ── LOST (not received) │ │ > ATT: Notification (On\-demand 0x2C16) │ Seg[3]: First=F Last=T ── received │ │ (Client detects gap: Seg[2] missing) ▼ INCOMPLETE < ATT: Write Command (Control Point 0x2C17) Retrieve Lost Ranging Data Segments Counter: N First Segment Index: 2 Last Segment Index: 2 ▼ RECOVERING > ATT: Notification (On\-demand 0x2C16) Seg[2]: First=F Last=F ── re\-sent ▼ TRANSFER COMPLETE < ATT: Write Command (Control Point 0x2C17) ACK Ranging Data Counter: N ▼ IDLE .EE .UNINDENT .UNINDENT .SS RAS Common Issues .INDENT 0.0 .TP .B \fBFeatures read returns 0x00000000\fP The server does not support any optional RAS features. Only basic on\-demand data retrieval via Control Point is available. .TP .B \fBRanging Data Ready not received\fP Check that CCC notifications are enabled on handle 0x2C18. Also verify the CS procedure actually completed \-\- if the procedure was aborted, no Data Ready notification is sent. .TP .B \fBSegmentation reassembly errors\fP Verify that Segment Index values are sequential and that the First Segment flag is set on the initial notification. A missing segment triggers the Retrieve Lost Segments mechanism if the server supports it (Features bit 1). .TP .B \fBData Overwritten before retrieval\fP The server has limited buffer space. If the client does not retrieve and ACK data promptly, older datasets are overwritten. The Data Overwritten notification indicates the counter value of the lost data. .TP .B \fBControl Point write rejected\fP The connection must be encrypted. RAS Control Point requires write permission with encryption (\fBWRITE_ENCRYPT\fP). Verify that SMP pairing has completed. .UNINDENT .\" This file is included by btmon.rst. . .SH CLASSIC AUDIO PROTOCOL FLOW .sp Classic Bluetooth audio uses two main profiles: A2DP for high\-quality stereo streaming and HFP for voice calls. Both run over BR/EDR ACL connections and use different transport mechanisms for audio data \-\- A2DP uses L2CAP\-based AVDTP media channels while HFP uses SCO/eSCO synchronous connections. .\" This file is included by btmon-classic-audio.rst. . .SS A2DP: Advanced Audio Distribution .sp A2DP uses AVDTP (Audio/Video Distribution Transport Protocol) over L2CAP to negotiate codec parameters and stream high\-quality audio. btmon fully decodes AVDTP signaling, codec capabilities, and AVRCP remote control messages. .SS AVDTP Signaling Channel .sp A2DP begins with an L2CAP connection on PSM 25 (0x0019). The first L2CAP connection on this PSM carries AVDTP signaling; a second connection on the same PSM carries media transport data: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 1 flags 0x00 dlen 12 L2CAP: Connection Request (0x02) ident 1 len 4 PSM: 25 (0x0019) Source CID: 64 > ACL Data RX: Handle 1 flags 0x02 dlen 16 L2CAP: Connection Response (0x03) ident 1 len 8 Destination CID: 64 Source CID: 64 Result: Connection successful (0x0000) Status: No further information available (0x0000) .EE .UNINDENT .UNINDENT .sp After L2CAP configuration completes, AVDTP signaling begins on this channel. .SS Stream Endpoint Discovery .sp The initiator discovers available Stream Endpoints (SEPs) on the remote device: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 1 flags 0x00 dlen 6 Channel: 64 len 2 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Discover (0x01) Command (0x00) type 0x00 label 0 nosp 0 > ACL Data RX: Handle 1 flags 0x02 dlen 10 Channel: 64 len 6 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Discover (0x01) Response Accept (0x02) type 0x00 label 0 nosp 0 ACP SEID: 1 Media Type: Audio (0x00) SEP Type: SNK (0x01) In use: No .EE .UNINDENT .UNINDENT .sp Each SEP has a SEID (Stream Endpoint Identifier), media type, and role (Source or Sink). \fBIn use: Yes\fP means the endpoint is already streaming. .SS Codec Capability Discovery .sp After discovering SEIDs, the initiator queries each endpoint\(aqs capabilities: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 1 flags 0x00 dlen 7 Channel: 64 len 3 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Get All Capabilities (0x0c) Command (0x00) type 0x00 label 1 nosp 0 ACP SEID: 1 > ACL Data RX: Handle 1 flags 0x02 dlen 30 Channel: 64 len 26 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Get All Capabilities (0x0c) Response Accept (0x02) type 0x00 label 1 nosp 0 Service Category: Media Transport (0x01) Service Category: Media Codec (0x07) Media Type: Audio (0x00) Media Codec: SBC (0x00) Frequency: 0xf0 16000 32000 44100 48000 Channel Mode: 0x0f Mono Dual Channel Stereo Joint Stereo Block Length: 0xf0 4 8 12 16 Subbands: 0x0c 4 8 Allocation Method: 0x03 SNR Loudness Minimum Bitpool: 2 Maximum Bitpool: 53 Service Category: Content Protection (0x04) Content Protection Type: SCMS\-T (0x0002) Service Category: Delay Reporting (0x08) .EE .UNINDENT .UNINDENT .sp Capability responses list all supported values as bitmasks. Key service categories: .INDENT 0.0 .IP \(bu 2 \fBMedia Transport\fP (0x01) \-\- Always present, indicates the endpoint supports a media transport channel .IP \(bu 2 \fBMedia Codec\fP (0x07) \-\- Codec type and supported parameters .IP \(bu 2 \fBContent Protection\fP (0x04) \-\- Copy protection (SCMS\-T or DTCP) .IP \(bu 2 \fBDelay Reporting\fP (0x08) \-\- Endpoint supports delay reporting .UNINDENT .SS Supported Codecs .sp btmon decodes several A2DP codecs. Each has a different parameter format. .sp \fBSBC\fP (mandatory A2DP codec): .INDENT 0.0 .INDENT 3.5 .sp .EX Media Codec: SBC (0x00) Frequency: 44100 (0x20) Channel Mode: Joint Stereo (0x01) Block Length: 16 (0x10) Subbands: 8 (0x04) Allocation Method: Loudness (0x01) Minimum Bitpool: 2 Maximum Bitpool: 53 .EE .UNINDENT .UNINDENT .sp \fBAAC\fP (MPEG\-2,4): .INDENT 0.0 .INDENT 3.5 .sp .EX Media Codec: MPEG\-2,4 AAC (0x02) Object Type: MPEG\-4 AAC LC (0x40) Frequency: 44100 (0x0100) Channels: 2 (0x04) Bitrate: 256000bps VBR: No .EE .UNINDENT .UNINDENT .sp \fBaptX\fP (Qualcomm, vendor\-specific): .INDENT 0.0 .INDENT 3.5 .sp .EX Media Codec: Non\-A2DP (0xff) Vendor ID: Qualcomm Technologies International, Ltd. (APT) (0x0000004f) Vendor Specific Codec ID: aptX (0x0001) Frequency: 44100 (0x20) Channel Mode: Stereo (0x02) .EE .UNINDENT .UNINDENT .sp \fBaptX HD\fP (Qualcomm, vendor\-specific): .INDENT 0.0 .INDENT 3.5 .sp .EX Media Codec: Non\-A2DP (0xff) Vendor ID: Qualcomm Technologies, Inc. (0x000000d7) Vendor Specific Codec ID: aptX HD (0x0024) Frequency: 44100 (0x20) Channel Mode: Stereo (0x02) .EE .UNINDENT .UNINDENT .sp \fBLDAC\fP (Sony, vendor\-specific): .INDENT 0.0 .INDENT 3.5 .sp .EX Media Codec: Non\-A2DP (0xff) Vendor ID: Sony Corporation (0x0000012d) Vendor Specific Codec ID: LDAC (0x00aa) .EE .UNINDENT .UNINDENT .sp Vendor codecs appear as \fBNon\-A2DP (0xff)\fP with decoded Vendor ID and Codec ID. Capability responses list all supported values as bitmasks; configuration responses show the single selected value. .SS Stream Configuration .sp After selecting a codec and parameters, the initiator sends Set Configuration: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 1 flags 0x00 dlen 20 Channel: 64 len 16 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Set Configuration (0x03) Command (0x00) type 0x00 label 2 nosp 0 ACP SEID: 1 INT SEID: 1 Service Category: Media Transport (0x01) Service Category: Media Codec (0x07) Media Type: Audio (0x00) Media Codec: SBC (0x00) Frequency: 44100 (0x20) Channel Mode: Joint Stereo (0x01) Block Length: 16 (0x10) Subbands: 8 (0x04) Allocation Method: Loudness (0x01) Minimum Bitpool: 2 Maximum Bitpool: 53 > ACL Data RX: Handle 1 flags 0x02 dlen 6 Channel: 64 len 2 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Set Configuration (0x03) Response Accept (0x02) type 0x00 label 2 nosp 0 .EE .UNINDENT .UNINDENT .sp Configuration responses show single selected values instead of bitmask lists. \fBACP SEID\fP is the remote endpoint, \fBINT SEID\fP is the local endpoint. .sp If configuration is rejected: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 1 flags 0x02 dlen 8 Channel: 64 len 4 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Set Configuration (0x03) Response Reject (0x03) type 0x00 label 2 nosp 0 Service Category: Media Codec (0x07) Error code: Unsupported Configuration (0x29) .EE .UNINDENT .UNINDENT .sp Common AVDTP error codes: .TS box center; l|l|l. T{ Code T} T{ Name T} T{ Meaning T} _ T{ 0x01 T} T{ Bad Header Format T} T{ Malformed AVDTP header T} _ T{ 0x11 T} T{ Bad ACP SEID T} T{ Unknown endpoint ID T} _ T{ 0x12 T} T{ SEP In Use T} T{ Endpoint already streaming T} _ T{ 0x13 T} T{ SEP Not In Use T} T{ Endpoint not configured T} _ T{ 0x29 T} T{ Unsupported Configuration T} T{ Requested parameters not supported T} _ T{ 0x31 T} T{ Bad State T} T{ Command not valid in current state T} .TE .SS Open and Start .sp After configuration, the stream is opened and then started: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 1 flags 0x00 dlen 7 Channel: 64 len 3 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Open (0x06) Command (0x00) type 0x00 label 3 nosp 0 ACP SEID: 1 > ACL Data RX: Handle 1 flags 0x02 dlen 6 Channel: 64 len 2 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Open (0x06) Response Accept (0x02) type 0x00 label 3 nosp 0 .EE .UNINDENT .UNINDENT .sp After Open succeeds, a \fBsecond L2CAP connection\fP on PSM 25 is established for the media transport channel. Then Start begins streaming: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 1 flags 0x00 dlen 7 Channel: 64 len 3 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Start (0x07) Command (0x00) type 0x00 label 4 nosp 0 ACP SEID: 1 > ACL Data RX: Handle 1 flags 0x02 dlen 6 Channel: 64 len 2 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Start (0x07) Response Accept (0x02) type 0x00 label 4 nosp 0 .EE .UNINDENT .UNINDENT .SS Media Data .sp After Start, encoded audio flows on the media transport channel (second L2CAP connection on PSM 25). btmon does \fBnot\fP decode media packet contents by default \-\- media data is suppressed. Only the L2CAP channel header is visible when the \fB\-\-show\-a2dp\-stream\fP filter is enabled, with the payload shown as a raw hex dump. .sp The AVDTP state machine visible in the trace: .TS box center; l|l|l. T{ State T} T{ Triggered By T} T{ Description T} _ T{ Idle T} T{ Initial / Abort response T} T{ No stream configured T} _ T{ Configured T} T{ Set Configuration accept T} T{ Codec and parameters selected T} _ T{ Open T} T{ Open accept / Suspend accept T} T{ Transport channel ready, not streaming T} _ T{ Streaming T} T{ Start accept T} T{ Audio data flowing T} _ T{ Closing T} T{ Close command sent T} T{ Tearing down stream T} _ T{ Aborting T} T{ Abort command sent T} T{ Forced teardown T} .TE .SS Suspend, Close, and Abort .sp \fBSuspend\fP pauses streaming without tearing down the transport: .INDENT 0.0 .INDENT 3.5 .sp .EX AVDTP: Suspend (0x09) Command (0x00) type 0x00 label 5 nosp 0 ACP SEID: 1 AVDTP: Suspend (0x09) Response Accept (0x02) type 0x00 label 5 nosp 0 .EE .UNINDENT .UNINDENT .sp \fBClose\fP tears down the stream (returns to Idle): .INDENT 0.0 .INDENT 3.5 .sp .EX AVDTP: Close (0x08) Command (0x00) type 0x00 label 6 nosp 0 ACP SEID: 1 AVDTP: Close (0x08) Response Accept (0x02) type 0x00 label 6 nosp 0 .EE .UNINDENT .UNINDENT .sp \fBAbort\fP forces immediate teardown: .INDENT 0.0 .INDENT 3.5 .sp .EX AVDTP: Abort (0x0a) Command (0x00) type 0x00 label 7 nosp 0 ACP SEID: 1 AVDTP: Abort (0x0a) Response Accept (0x02) type 0x00 label 7 nosp 0 .EE .UNINDENT .UNINDENT .SS Delay Reporting .sp When the sink supports delay reporting, it sends a Delay Report to inform the source of its rendering delay: .INDENT 0.0 .INDENT 3.5 .sp .EX AVDTP: Delay Report (0x0d) Command (0x00) type 0x00 label 8 nosp 0 ACP SEID: 1 Delay: 15.0ms AVDTP: Delay Report (0x0d) Response Accept (0x02) type 0x00 label 8 nosp 0 .EE .UNINDENT .UNINDENT .SS AVRCP Remote Control .sp AVRCP (Audio/Video Remote Control Profile) uses L2CAP PSM 23 (control) and PSM 27 (browsing). btmon decodes AVCTP framing and AVRCP PDUs. .sp \fBVolume control\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 1 flags 0x00 dlen 17 Channel: 66 len 13 [PSM 23 mode Basic (0x00)] {chan 2} AVCTP Control: Response: type 0x00 label 0 PID 0x110e AV/C: Accepted: address 0x48 opcode 0x00 Subunit: Panel Opcode: Vendor Dependent Company ID: 0x001958 AVRCP: SetAbsoluteVolume pt Single len 0x0001 Volume: 50.39% (64/127) .EE .UNINDENT .UNINDENT .sp \fBPlayback status\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX AVRCP: GetPlayStatus pt Single len 0x0009 SongLength: 0x00038270 (230000 milliseconds) SongPosition: 0x00000000 (0 milliseconds) PlayStatus: 0x01 (PLAYING) .EE .UNINDENT .UNINDENT .sp \fBTrack metadata\fP (GetElementAttributes response): .INDENT 0.0 .INDENT 3.5 .sp .EX AVRCP: GetElementAttributes pt Single len 0x0050 AttributeCount: 0x02 Attribute: 0x00000001 (Title) CharsetID: 0x006a (UTF\-8) AttributeValueLength: 0x000c AttributeValue: My Song Name Attribute: 0x00000002 (Artist) CharsetID: 0x006a (UTF\-8) AttributeValueLength: 0x000b AttributeValue: The Artist .EE .UNINDENT .UNINDENT .sp \fBPassthrough commands\fP (play, pause, skip): .INDENT 0.0 .INDENT 3.5 .sp .EX AVCTP Control: Command: type 0x00 label 1 PID 0x110e AV/C: Control: address 0x48 opcode 0x7c Subunit: Panel Opcode: Passthrough Operation: 0x44 (PLAY Pressed) Length: 0x00 .EE .UNINDENT .UNINDENT .sp \fBEvent notifications\fP (volume change, track change): .INDENT 0.0 .INDENT 3.5 .sp .EX AVRCP: RegisterNotification pt Single len 0x0005 EventID: 0x0d (EVENT_VOLUME_CHANGED) Volume: 50.39% (64/127) .EE .UNINDENT .UNINDENT .SS Automating A2DP Analysis .sp \fBIdentify A2DP activity\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqAVDTP:\e|Media Codec:\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBTrack AVDTP state transitions\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqAVDTP:.*Command\e|AVDTP:.*Response\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBCheck codec negotiation\fP (what was selected): .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqSet Configuration\e|Media Codec:\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBVerify stream is flowing\fP \-\- look for Start accept followed by media data: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqAVDTP: Start\e|PSM 25.*chan 1\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBDetect codec mismatch\fP \-\- look for Set Configuration reject: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqResponse Reject\e|Error code:\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBTrack AVRCP volume and playback\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqSetAbsoluteVolume\e|Volume:\e|GetPlayStatus\e|PlayStatus:\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBFull A2DP diagnosis pattern\fP: .INDENT 0.0 .IP 1. 3 Find L2CAP Connection Request for PSM 25 \-\- confirms AVDTP channel setup .IP 2. 3 Find Discover response \-\- shows available endpoints and whether any are already in use .IP 3. 3 Find Get All Capabilities response \-\- verify codec support overlap .IP 4. 3 Find Set Configuration \-\- verify agreed codec and parameters .IP 5. 3 Find Open and Start \-\- confirm stream setup succeeded .IP 6. 3 Find second L2CAP connection on PSM 25 \-\- media transport channel .IP 7. 3 On failure, check for Response Reject with error code .IP 8. 3 If audio cuts out, look for Suspend or Close, or check for ACL disconnection .UNINDENT .\" This file is included by btmon-classic-audio.rst. . .SS HFP: Hands\-Free Profile .sp HFP carries voice audio over SCO/eSCO connections with call control over RFCOMM. btmon decodes RFCOMM framing and SCO/eSCO connection setup. AT commands (the HFP control protocol) appear as raw hex dumps within RFCOMM data frames \-\- btmon does not parse AT command syntax. .SS SDP Discovery .sp HFP uses SDP to discover the remote device\(aqs Hands\-Free or Audio Gateway service record and its RFCOMM channel number: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 1 flags 0x00 dlen 25 Channel: 64 len 21 [PSM 1 mode Basic (0x00)] {chan 0} SDP: Service Search Attribute Request (0x06) tid 1 len 16 Search pattern: [len 5] Sequence (6) with 3 byte(s) [8 extra bits] len 5 UUID (3) with 2 byte(s) [0 extra bits] len 3 Handsfree Audio Gateway (0x111f) Max record count: 65535 Attribute list: [len 5] Sequence (6) with 3 byte(s) [8 extra bits] len 5 Unsigned Integer (1) with 4 byte(s) [0 extra bits] len 5 0x0000ffff Continuation state: 0 .EE .UNINDENT .UNINDENT .sp The response contains the service record with the RFCOMM channel: .INDENT 0.0 .INDENT 3.5 .sp .EX > ACL Data RX: Handle 1 flags 0x02 dlen 89 Channel: 64 len 85 [PSM 1 mode Basic (0x00)] {chan 0} SDP: Service Search Attribute Response (0x07) tid 1 len 80 Attribute bytes: 77 Attribute list: [len 75] {position 0} Attribute: Service Class ID List (0x0001) [len 2] Handsfree Audio Gateway (0x111f) Attribute: Protocol Descriptor List (0x0004) [len 2] L2CAP (0x0100) RFCOMM (0x0003) Channel: 1 Attribute: Bluetooth Profile Descriptor List (0x0009) [len 2] Handsfree (0x111e) Version: 0x0108 Continuation state: 0 .EE .UNINDENT .UNINDENT .sp Key fields to extract: .INDENT 0.0 .IP \(bu 2 \fBService Class\fP \-\- \fBHandsfree (0x111e)\fP for HF role, \fBHandsfree Audio Gateway (0x111f)\fP for AG role .IP \(bu 2 \fBRFCOMM Channel\fP \-\- The channel number under \fBRFCOMM (0x0003)\fP in the Protocol Descriptor List (e.g., channel 1) .IP \(bu 2 \fBProfile Version\fP \-\- Under the Bluetooth Profile Descriptor List (e.g., \fB0x0108\fP = HFP 1.8, \fB0x0109\fP = HFP 1.9) .UNINDENT .SS RFCOMM Connection Setup .sp RFCOMM runs over L2CAP PSM 3. The connection proceeds in stages: multiplexer session on DLCI 0, parameter negotiation, then the data channel on the target DLCI. .sp \fBL2CAP connection for RFCOMM\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 1 flags 0x00 dlen 12 L2CAP: Connection Request (0x02) ident 2 len 4 PSM: 3 (0x0003) Source CID: 65 > ACL Data RX: Handle 1 flags 0x02 dlen 16 L2CAP: Connection Response (0x03) ident 2 len 8 Destination CID: 65 Source CID: 65 Result: Connection successful (0x0000) Status: No further information available (0x0000) .EE .UNINDENT .UNINDENT .sp \fBSABM/UA on DLCI 0\fP (multiplexer session): .INDENT 0.0 .INDENT 3.5 .sp .EX < ACL Data TX: Handle 1 flags 0x00 dlen 12 Channel: 65 len 4 [PSM 3 mode Basic (0x00)] {chan 1} RFCOMM: Set Async Balance Mode (SABM) (0x2f) Address: 0x03 cr 1 dlci 0x00 Control: 0x3f poll/final 1 Length: 0 FCS: 0x1c > ACL Data RX: Handle 1 flags 0x02 dlen 12 Channel: 65 len 4 [PSM 3 mode Basic (0x00)] {chan 1} RFCOMM: Unnumbered Ack (UA) (0x63) Address: 0x03 cr 1 dlci 0x00 Control: 0x73 poll/final 1 Length: 0 FCS: 0xd7 .EE .UNINDENT .UNINDENT .sp \fBParameter Negotiation\fP (MCC on DLCI 0): .INDENT 0.0 .INDENT 3.5 .sp .EX Channel: 65 len 14 [PSM 3 mode Basic (0x00)] {chan 1} RFCOMM: Unnumbered Info with Header Check (UIH) (0xef) Address: 0x03 cr 1 dlci 0x00 Control: 0xef poll/final 0 Length: 10 FCS: 0x70 MCC Message type: DLC Parameter Negotiation CMD (0x20) Length: 8 dlci 2 frame_type 0 credit_flow 15 pri 7 ack_timer 0 frame_size 127 max_retrans 0 credits 7 .EE .UNINDENT .UNINDENT .sp The DLCI in the PN command identifies the target channel. For RFCOMM channel N, DLCI = N * 2 (or N * 2 + 1 depending on the initiator role). .sp \fBSABM/UA on target DLCI\fP (data channel): .INDENT 0.0 .INDENT 3.5 .sp .EX Channel: 65 len 4 [PSM 3 mode Basic (0x00)] {chan 1} RFCOMM: Set Async Balance Mode (SABM) (0x2f) Address: 0x0b cr 1 dlci 0x02 Control: 0x3f poll/final 1 Length: 0 FCS: 0x59 Channel: 65 len 4 [PSM 3 mode Basic (0x00)] {chan 1} RFCOMM: Unnumbered Ack (UA) (0x63) Address: 0x0b cr 1 dlci 0x02 Control: 0x73 poll/final 1 Length: 0 FCS: 0x92 .EE .UNINDENT .UNINDENT .sp \fBModem Status Command\fP (signals readiness): .INDENT 0.0 .INDENT 3.5 .sp .EX Channel: 65 len 8 [PSM 3 mode Basic (0x00)] {chan 1} RFCOMM: Unnumbered Info with Header Check (UIH) (0xef) Address: 0x03 cr 1 dlci 0x00 Control: 0xef poll/final 0 Length: 4 FCS: 0x70 MCC Message type: Modem Status Command CMD (0x38) Length: 2 dlci 2 fc 0 rtc 1 rtr 1 ic 0 dv 1 .EE .UNINDENT .UNINDENT .SS AT Command Exchange (SLC Setup) .sp HFP uses AT commands over RFCOMM for call control and feature negotiation. btmon shows these as RFCOMM UIH frames with the AT command text visible in the hex dump. .sp \fBAT command in RFCOMM UIH frame\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX Channel: 65 len 21 [PSM 3 mode Basic (0x00)] {chan 1} RFCOMM: Unnumbered Info with Header Check (UIH) (0xef) Address: 0x09 cr 0 dlci 0x02 Control: 0xff poll/final 1 Length: 14 FCS: 0x86 Credits: 1 41 54 2b 42 52 53 46 3d 32 30 35 35 0d AT+BRSF=2055. .EE .UNINDENT .UNINDENT .sp The AT command text is readable in the ASCII column of the hex dump (right side). The Service Level Connection (SLC) setup sequence exchanges device features and capabilities: .INDENT 0.0 .IP 1. 3 \fBAT+BRSF=\fP / \fB+BRSF:\fP \-\- Supported features bitmask exchange .IP 2. 3 \fBAT+BAC=1,2\fP \-\- Available codecs (if codec negotiation supported). Codec IDs: 1 = CVSD, 2 = mSBC, 3 = LC3\-SWB .IP 3. 3 \fBAT+CIND=?\fP / \fB+CIND:(...)\fP \-\- Indicator mapping query .IP 4. 3 \fBAT+CIND?\fP / \fB+CIND:values\fP \-\- Current indicator values .IP 5. 3 \fBAT+CMER=3,0,0,1\fP \-\- Enable indicator status reporting .IP 6. 3 \fBAT+CHLD=?\fP / \fB+CHLD:(0,1,2,3,4)\fP \-\- Three\-way calling support .UNINDENT .sp Key HFP feature bits (from \fBAT+BRSF\fP): .TS box center; l|l|l. T{ Bit T} T{ HF Feature T} T{ AG Feature T} _ T{ 0 T} T{ EC/NR T} T{ Three\-way calling T} _ T{ 1 T} T{ Three\-way calling T} T{ EC/NR T} _ T{ 2 T} T{ CLI presentation T} T{ Voice recognition T} _ T{ 3 T} T{ Voice recognition T} T{ In\-band ring tone T} _ T{ 4 T} T{ Remote volume T} T{ Voice tag T} _ T{ 5 T} T{ Enhanced call status T} T{ Reject call T} _ T{ 6 T} T{ Enhanced call control T} T{ Enhanced call status T} _ T{ 7 T} T{ Codec negotiation T} T{ Enhanced call control T} _ T{ 8 T} T{ HF indicators T} T{ Extended error codes T} _ T{ 9 T} T{ eSCO S4 (T2) T} T{ Codec negotiation T} _ T{ 11 T} T{ T} T{ eSCO S4 (T2) T} .TE .SS Codec Connection Setup .sp When both sides support codec negotiation (feature bit 7 on HF, bit 9 on AG), the AG selects a codec before establishing the audio link. This appears as AT commands in RFCOMM UIH frames: .INDENT 0.0 .INDENT 3.5 .sp .EX AG \-> HF: +BCS:2 (select mSBC) HF \-> AG: AT+BCS=2 (confirm mSBC) AG \-> HF: OK .EE .UNINDENT .UNINDENT .sp HFP codec IDs (used in \fBAT+BAC\fP and \fBAT+BCS\fP): .TS box center; l|l|l. T{ ID T} T{ Codec T} T{ Description T} _ T{ 1 T} T{ CVSD T} T{ Narrow band (8 kHz), mandatory T} _ T{ 2 T} T{ mSBC T} T{ Wide band speech (16 kHz) T} _ T{ 3 T} T{ LC3\-SWB T} T{ Super wide band (32 kHz), HFP 1.9+ T} .TE .sp These HFP\-level codec IDs differ from HCI codec IDs. .SS Voice Setting .sp Before SCO/eSCO setup, the host configures the voice setting. For CVSD, the air coding format is CVSD; for mSBC or LC3\-SWB, it must be set to Transparent Data: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: Write Voice Setting (0x0c|0x0026) plen 2 Setting: 0x0063 Input Coding: Linear Input Data Format: 2\(aqs complement Input Sample Size: 16\-bit # of bits padding at MSB: 0 Air Coding Format: Transparent Data .EE .UNINDENT .UNINDENT .sp For CVSD: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: Write Voice Setting (0x0c|0x0026) plen 2 Setting: 0x0060 Input Coding: Linear Input Data Format: 2\(aqs complement Input Sample Size: 16\-bit # of bits padding at MSB: 0 Air Coding Format: CVSD .EE .UNINDENT .UNINDENT .SS SCO/eSCO Connection Setup .sp After codec negotiation, the host establishes a synchronous connection for voice audio. .sp \fBSetup Synchronous Connection\fP (basic): .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: Setup Synchronous Connection (0x01|0x0028) plen 17 Handle: 1 Transmit bandwidth: 8000 Receive bandwidth: 8000 Max latency: 13 Setting: 0x0063 Input Coding: Linear Input Data Format: 2\(aqs complement Input Sample Size: 16\-bit # of bits padding at MSB: 0 Air Coding Format: Transparent Data Retransmission effort: Optimize for link quality (0x02) Packet type: 0x0008 EV3 may be used .EE .UNINDENT .UNINDENT .sp \fBEnhanced Setup Synchronous Connection\fP (codec\-aware): .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: Enhanced Setup Synchronous Connection (0x01|0x003d) plen 59 Handle: 1 Transmit bandwidth: 8000 Receive bandwidth: 8000 Transmit Coding Format: Codec: mSBC (0x05) Receive Coding Format: Codec: mSBC (0x05) Transmit Codec Frame Size: 60 Receive Codec Frame Size: 60 Input Coding Format: Codec: mSBC (0x05) Output Coding Format: Codec: mSBC (0x05) Input Coded Data Size: 16 Output Coded Data Size: 16 Input PCM Data Format: 2\(aqs complement Output PCM Data Format: 2\(aqs complement Input PCM Sample Payload MSB Position: 0 Output PCM Sample Payload MSB Position: 0 Input Data Path: HCI Output Data Path: HCI Input Transport Unit Size: 60 Output Transport Unit Size: 60 Max latency: 13 Packet type: 0x0008 EV3 may be used Retransmission effort: Optimize for link quality (0x02) .EE .UNINDENT .UNINDENT .sp HCI codec IDs displayed by btmon: .TS box center; l|l|l. T{ ID T} T{ btmon Name T} T{ Used For T} _ T{ 0x02 T} T{ CVSD T} T{ Narrow band voice T} _ T{ 0x03 T} T{ Transparent T} T{ Transparent data mode T} _ T{ 0x04 T} T{ Linear PCM T} T{ Uncompressed PCM input/output T} _ T{ 0x05 T} T{ mSBC T} T{ Wide band speech T} _ T{ 0x06 T} T{ LC3 T} T{ Super wide band (LC3\-SWB) T} .TE .sp \fBSynchronous Connection Complete\fP (result): .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: Synchronous Connection Complete (0x2c) plen 17 Status: Success (0x00) Handle: 257 Address: 11:22:33:44:55:66 (OUI 11\-22\-33) Link type: eSCO (0x02) Transmission interval: 0x0c Retransmission window: 0x06 RX packet length: 60 TX packet length: 60 Air mode: Transparent (0x03) .EE .UNINDENT .UNINDENT .sp Key fields: .INDENT 0.0 .IP \(bu 2 \fBLink type\fP \-\- \fBSCO (0x00)\fP for legacy, \fBeSCO (0x02)\fP for enhanced (used by mSBC and LC3\-SWB) .IP \(bu 2 \fBAir mode\fP \-\- \fBCVSD (0x02)\fP for narrow band, \fBTransparent (0x03)\fP for mSBC or LC3\-SWB .IP \(bu 2 \fBRX/TX packet length\fP \-\- 60 bytes typical for mSBC T2 settings .UNINDENT .SS SCO Data Packets .sp After the synchronous connection is established, voice data flows as SCO/eSCO data packets: .INDENT 0.0 .INDENT 3.5 .sp .EX > BR\-ESCO: Handle 257 flags 0x00 dlen 60 < BR\-ESCO: Handle 257 flags 0x00 dlen 60 .EE .UNINDENT .UNINDENT .sp btmon labels packets based on the connection type established in Synchronous Connection Complete: .INDENT 0.0 .IP \(bu 2 \fBBR\-SCO\fP \-\- Legacy SCO connection .IP \(bu 2 \fBBR\-ESCO\fP \-\- Enhanced SCO connection (mSBC, LC3\-SWB, or eSCO CVSD) .UNINDENT .sp SCO data payload is \fBnot displayed by default\fP\&. It requires the \fB\-\-show\-sco\-data\fP filter to see the hex dump of voice data. .SS Codec\-Specific Connection Summary .sp \fBCVSD (narrow band)\fP: .INDENT 0.0 .IP \(bu 2 HFP codec ID: 1 .IP \(bu 2 HCI codec: CVSD (0x02) .IP \(bu 2 Voice setting: 0x0060 (Air Coding Format: CVSD) .IP \(bu 2 Air mode: CVSD (0x02) .IP \(bu 2 Link type: SCO or eSCO .IP \(bu 2 Typical packet size: 48 bytes (HV3) or 60 bytes (EV3) .UNINDENT .sp \fBmSBC (wide band speech)\fP: .INDENT 0.0 .IP \(bu 2 HFP codec ID: 2 .IP \(bu 2 HCI codec: mSBC (0x05) .IP \(bu 2 Voice setting: 0x0063 (Air Coding Format: Transparent Data) .IP \(bu 2 Air mode: Transparent (0x03) .IP \(bu 2 Link type: eSCO .IP \(bu 2 Typical packet size: 60 bytes (EV3, T2 settings) .UNINDENT .sp \fBLC3\-SWB (super wide band)\fP: .INDENT 0.0 .IP \(bu 2 HFP codec ID: 3 .IP \(bu 2 HCI codec: LC3 (0x06) .IP \(bu 2 Voice setting: 0x0063 (Air Coding Format: Transparent Data) .IP \(bu 2 Air mode: Transparent (0x03) .IP \(bu 2 Link type: eSCO .IP \(bu 2 Note: btmon displays \fBLC3\fP, not \fBLC3\-SWB\fP .UNINDENT .SS Automating HFP Analysis .sp \fBIdentify HFP activity\fP \-\- look for RFCOMM on PSM 3: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqPSM: 3\e|RFCOMM:\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBRead AT commands\fP \-\- search hex dump ASCII for AT command patterns: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqAT+B\e|AT+C\e|+BRSF\e|+CIND\e|+CHLD\e|+BCS\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBCheck codec negotiation\fP \-\- look for BCS (Bluetooth Codec Selection): .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dq+BCS\e|AT+BAC\e|AT+BCS\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBVerify SCO/eSCO setup\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqSetup Synchronous\e|Enhanced Setup Synchronous\e|Synchronous Connection Complete\e|Write Voice Setting\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBCheck voice codec\fP \-\- confirm air mode and coding format: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqAir mode:\e|Air Coding Format:\e|Codec:\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBDetect SCO failures\fP \-\- check Synchronous Connection Complete status: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqSynchronous Connection Complete\(dq output.txt .EE .UNINDENT .UNINDENT .sp Then examine the next line for \fBStatus:\fP\&. Common failures: .INDENT 0.0 .IP \(bu 2 \fBConnection Rejected due to Limited Resources (0x0d)\fP \-\- controller cannot allocate bandwidth .IP \(bu 2 \fBSCO Offset Rejected (0x2b)\fP \-\- timing parameters rejected .IP \(bu 2 \fBSCO Interval Rejected (0x2c)\fP \-\- interval parameters rejected .UNINDENT .sp \fBTrack call state\fP \-\- look for CIEV indicator updates: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dq+CIEV\e|AT+CHUP\e|ATD\e|ATA\e|AT+CLCC\e|RING\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBFull HFP diagnosis pattern\fP: .INDENT 0.0 .IP 1. 4 Find SDP query for UUID 0x111e/0x111f \-\- confirms HFP discovery .IP 2. 4 Find L2CAP Connection Request for PSM 3 \-\- RFCOMM channel setup .IP 3. 4 Find RFCOMM SABM/UA on DLCI 0 then target DLCI \-\- multiplexer and data channel .IP 4. 4 Find AT+BRSF in hex dumps \-\- feature exchange, check codec negotiation bit .IP 5. 4 Find AT+BAC in hex dumps \-\- available codecs reported .IP 6. 4 Find +BCS/AT+BCS in hex dumps \-\- codec selected for audio .IP 7. 4 Find Write Voice Setting \-\- verify air coding format matches codec .IP 8. 4 Find Setup Synchronous Connection or Enhanced variant \-\- SCO setup .IP 9. 4 Find Synchronous Connection Complete \-\- check Status, Link type, Air mode .IP 10. 4 Find BR\-SCO or BR\-ESCO packets \-\- voice data flowing .UNINDENT .\" This file is included by btmon.rst. . .SH ADVERTISING AND SCANNING .sp btmon decodes advertising data structures automatically. Advertising and scan response data appears in HCI LE advertising report events and in advertising command parameters. .SS Advertising Reports .sp When the controller reports received advertisements: .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: LE Meta Event (0x3e) plen 43 #120 [hci0] 0.500003 LE Extended Advertising Report (0x0d) Event type: 0x0013 Props: 0x0013 Connectable Scannable Complete Address type: Random (0x01) Address: 00:11:22:33:44:55 Primary PHY: LE 1M Secondary PHY: LE 2M SID: 0x01 TX power: 0 dBm RSSI: \-55 dBm (0xc9) Data length: 18 .EE .UNINDENT .UNINDENT .sp The advertising data (AD) structures within the report are decoded as typed fields: .sp \fBCommon AD types btmon decodes\fP: .TS box center; l|l|l. T{ AD Type T} T{ Name T} T{ Example in btmon output T} _ T{ 0x01 T} T{ Flags T} T{ \fBFlags: 0x06\fP with decoded bits (LE General Discoverable, BR/EDR Not Supported) T} _ T{ 0x02/0x03 T} T{ Incomplete/Complete 16\-bit UUIDs T} T{ \fB16\-bit Service UUIDs (complete): 2 entries\fP followed by UUID list T} _ T{ 0x06/0x07 T} T{ Incomplete/Complete 128\-bit UUIDs T} T{ \fB128\-bit Service UUIDs (complete): 1 entry\fP T} _ T{ 0x08/0x09 T} T{ Shortened/Complete Local Name T} T{ \fBName (complete): MyDevice\fP T} _ T{ 0x0a T} T{ TX Power Level T} T{ \fBTX power: 4 dBm\fP T} _ T{ 0x16 T} T{ Service Data (16\-bit UUID) T} T{ \fBService Data (UUID 0x184e): ...\fP with protocol\-specific decoding T} _ T{ 0xff T} T{ Manufacturer Specific Data T} T{ \fBCompany: Apple, Inc. (76)\fP followed by hex data T} .TE .sp \fBTypical advertising report\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: LE Meta Event (0x3e) plen 38 #120 [hci0] 0.500003 LE Extended Advertising Report (0x0d) Address: 00:11:22:33:44:55 RSSI: \-62 dBm (0xc2) Flags: 0x06 LE General Discoverable Mode BR/EDR Not Supported Name (complete): LE\-Audio\-Left 16\-bit Service UUIDs (complete): 3 entries Published Audio Capabilities (0x1850) Audio Stream Control (0x184e) Common Audio (0x1853) Service Data (UUID 0x1852): 01a2b3 Appearance: Earbud (0x0941) .EE .UNINDENT .UNINDENT .SS Extended Advertising .sp Modern controllers use extended advertising commands and events. The setup sequence in btmon: .INDENT 0.0 .INDENT 3.5 .sp .EX < HCI Command: LE Set Extended Adv Parameters (0x08|0x0036) plen 25 #50 [hci0] 0.100003 Handle: 0x01 Properties: 0x0000 Min advertising interval: 160.000 msec (0x0100) Max advertising interval: 160.000 msec (0x0100) Channel map: 37, 38, 39 (0x07) Own address type: Random (0x01) Peer address type: Public (0x00) PHY: LE 1M, LE 2M SID: 0x01 TX power: 7 dBm < HCI Command: LE Set Extended Adv Data (0x08|0x0037) plen 35 #52 [hci0] 0.101003 Handle: 0x01 Operation: Complete extended advertising data (0x01) Fragment preference: No fragmentation (0x01) .EE .UNINDENT .UNINDENT .SS Periodic Advertising (LE Audio) .sp LE Audio broadcast sources use periodic advertising to transmit BASE announcements containing codec configuration: .INDENT 0.0 .INDENT 3.5 .sp .EX > HCI Event: LE Meta Event (0x3e) plen 80 #200 [hci0] 0.500003 LE Periodic Advertising Report (0x0f) Sync handle: 0x0001 TX power: 0 dBm RSSI: \-45 dBm CTE Type: No CTE (0xff) Data status: Complete (0x00) Data length: 60 Service Data: Basic Audio Announcement (0x1851) Presentation Delay: 40000 us Number of Subgroups: 1 Codec: LC3 (0x06) Sampling Frequency: 48000 Hz Frame Duration: 10 ms Frame Length: 120 .EE .UNINDENT .UNINDENT .SS Automating Advertising Analysis .sp \fBFind all advertising reports\fP (devices seen): .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqAdvertising Report\e|Address:.*RSSI:\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBExtract device names\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqName (complete):\e|Name (short):\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBFind LE Audio devices\fP (by service UUIDs in advertising): .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqAudio Stream Control\e|Published Audio Capabilities\e|Common Audio\e|Basic Audio Announcement\e|Broadcast Audio\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBTrack advertising setup\fP (local device configuring advertising): .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqSet Extended Adv\e|Set Advertising\e|Set Scan Response\e|Adv Enable\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBFind periodic advertising\fP (broadcast audio): .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqPeriodic Advertising\e|PA Sync\e|PA Report\e|Basic Audio Announcement\e|Broadcast.*Announcement\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBIdentify devices by appearance\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqAppearance:\(dq output.txt .EE .UNINDENT .UNINDENT .\" This file is included by btmon.rst. . .SH MGMT PROTOCOL FLOWS .sp The Management Interface (MGMT) is the structured command/event protocol between userspace (typically \fBbluetoothd\fP) and the kernel Bluetooth subsystem. In btmon output, MGMT traffic is prefixed with \fB@\fP and provides insight into adapter configuration, device discovery, pairing, and connection management at a higher level than raw HCI. This section covers common MGMT protocol flows as seen through btmon. .sp For MGMT output format basics (line anatomy, Open/Close, parameters, events), see the \(dqManagement Traffic\(dq subsection in READING THE OUTPUT above. .SS Initialization and Version Handshake .sp When \fBbluetoothd\fP starts, it opens a management socket and queries the kernel for protocol version and supported features. This handshake must succeed before any controller operations: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Open: bluetoothd (privileged) version 1.23 {0x0001} 12:34:49.881936 @ MGMT Command: Read Management Ver.. (0x0001) plen 0 {0x0001} 12:34:49.882003 @ MGMT Event: Command Complete (0x0001) plen 6 {0x0001} 12:34:49.882010 Read Management Version Information (0x0001) plen 3 Status: Success (0x00) Version: 1.23 @ MGMT Command: Read Management Sup.. (0x0002) plen 0 {0x0001} 12:34:49.882050 @ MGMT Event: Command Complete (0x0001) plen 58 {0x0001} 12:34:49.882055 Read Supported Commands (0x0002) plen 55 Status: Success (0x00) Num of commands: 120 Num of events: 38 .EE .UNINDENT .UNINDENT .sp After the version check, \fBbluetoothd\fP reads the controller list and queries each controller\(aqs information: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Command: Read Controller Index List (0x0003) plen 0 {0x0001} 12:34:49.882100 @ MGMT Event: Command Complete (0x0001) plen 7 {0x0001} 12:34:49.882105 Read Controller Index List (0x0003) plen 4 Status: Success (0x00) Num controllers: 1 Controller: hci0 @ MGMT Command: Read Controller Inf.. (0x0004) plen 0 {0x0001} [hci0] 12:34:49.882200 @ MGMT Event: Command Complete (0x0001) plen 283 {0x0001} [hci0] 12:34:49.882210 Read Controller Information (0x0004) plen 280 Status: Success (0x00) Address: 00:11:22:33:44:55 Bluetooth version: 5.4 Manufacturer: Intel (2) Supported settings: 0x003effff Current settings: 0x00000080 .EE .UNINDENT .UNINDENT .sp Key things to check in the initialization flow: .INDENT 0.0 .IP \(bu 2 \fBMGMT Open\fP \-\- Confirms \fBbluetoothd\fP connected. If missing, the daemon did not start or crashed before reaching MGMT setup. .IP \(bu 2 \fBVersion mismatch\fP \-\- If \fBbluetoothd\fP expects a newer MGMT version than the kernel provides, some features may be unavailable. .IP \(bu 2 \fBNum controllers: 0\fP \-\- No Bluetooth hardware detected. Check \fBdmesg\fP for driver issues. .IP \(bu 2 \fBCurrent settings\fP \-\- The bitmask shows what is currently enabled on the controller (Powered, LE, BR/EDR, SSP, etc.). .UNINDENT .SS Adapter Configuration .sp After reading controller info, \fBbluetoothd\fP configures the adapter with a series of MGMT commands before powering it on. The exact sequence depends on \fBmain.conf\fP settings and adapter capabilities. .sp \fBLoading stored keys\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Command: Load Link Keys (0x0012) plen 3 {0x0001} [hci0] 12:34:50.001200 Debug keys: Disabled (0x00) Key count: 0 @ MGMT Event: Command Complete (0x0001) plen 4 {0x0001} [hci0] 12:34:50.001220 Load Link Keys (0x0012) plen 1 Status: Success (0x00) @ MGMT Command: Load Long Term Keys (0x0013) plen 2 {0x0001} [hci0] 12:34:50.001300 Key count: 0 @ MGMT Event: Command Complete (0x0001) plen 4 {0x0001} [hci0] 12:34:50.001315 Load Long Term Keys (0x0013) plen 1 Status: Success (0x00) @ MGMT Command: Load Identity Resolving Keys (0x0030) plen 2 {0x0001} [hci0] 12:34:50.001400 Key count: 0 @ MGMT Event: Command Complete (0x0001) plen 4 {0x0001} [hci0] 12:34:50.001415 Load Identity Resolving Keys (0x0030) plen 1 Status: Success (0x00) .EE .UNINDENT .UNINDENT .sp When bonded devices exist, the Key count will be non\-zero and btmon will list each key entry. A large number of keys can indicate many paired devices; this is normal. .sp \fBSetting adapter properties\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Command: Set Secure Connections (0x002d) plen 1 {0x0001} [hci0] 12:34:50.002100 Secure connections: Enabled (0x01) @ MGMT Event: Command Complete (0x0001) plen 7 {0x0001} [hci0] 12:34:50.002120 Set Secure Connections (0x002d) plen 4 Status: Success (0x00) Current settings: 0x004e0a81 @ MGMT Command: Set Bondable (0x0009) plen 1 {0x0001} [hci0] 12:34:50.002200 Bondable: Enabled (0x01) @ MGMT Event: Command Complete (0x0001) plen 7 {0x0001} [hci0] 12:34:50.002220 Set Bondable (0x0009) plen 4 Status: Success (0x00) Current settings: 0x004e0a91 .EE .UNINDENT .UNINDENT .sp \fBPowering on\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Command: Set Powered (0x0005) plen 1 {0x0001} [hci0] 12:35:04.033564 Powered: Enabled (0x01) @ MGMT Event: Command Complete (0x0001) plen 7 {0x0001} [hci0] 12:35:04.114789 Set Powered (0x0005) plen 4 Status: Success (0x00) Current settings: 0x004e0ac1 .EE .UNINDENT .UNINDENT .sp Between the \fBSet Powered\fP command and its response, btmon will show the HCI commands the kernel sends to initialize the radio (see HCI INITIALIZATION SEQUENCE). .SS Discovery .sp Device discovery is initiated through MGMT rather than raw HCI commands. \fBbluetoothd\fP (or \fBbtmgmt\fP) sends a Start Discovery command specifying which transport types to scan. .sp \fBStarting discovery\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Command: Start Discovery (0x0023) plen 1 {0x0001} [hci0] 12:36:00.100200 Address type: 0x07 BR/EDR LE Public LE Random @ MGMT Event: Command Complete (0x0001) plen 5 {0x0001} [hci0] 12:36:00.100500 Start Discovery (0x0023) plen 2 Status: Success (0x00) Address type: 0x07 .EE .UNINDENT .UNINDENT .sp After this, btmon shows the HCI\-level scan commands the kernel issues (\fBLE Set Scan Parameters\fP, \fBLE Set Scan Enable\fP, and/or \fBInquiry\fP for BR/EDR). Discovered devices appear as MGMT events: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Event: Device Found (0x0012) plen 38 {0x0001} [hci0] 12:36:00.250003 LE Address: AA:BB:CC:DD:EE:FF (Random) RSSI: \-62 Flags: 0x0000 EIR Data: Name (complete): My Device TX power: 0 .EE .UNINDENT .UNINDENT .sp \fBService discovery\fP uses a filtered variant: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Command: Start Service Discovery (0x003a) plen 19 {0x0001} [hci0] 12:36:10.100200 Address type: 0x06 LE Public LE Random RSSI threshold: \-127 UUIDs: 1 UUID: Heart Rate (0x180d) @ MGMT Event: Command Complete (0x0001) plen 5 {0x0001} [hci0] 12:36:10.100500 Start Service Discovery (0x003a) plen 2 Status: Success (0x00) .EE .UNINDENT .UNINDENT .sp \fBStopping discovery\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Command: Stop Discovery (0x0024) plen 1 {0x0001} [hci0] 12:36:15.200100 Address type: 0x07 @ MGMT Event: Command Complete (0x0001) plen 5 {0x0001} [hci0] 12:36:15.200400 Stop Discovery (0x0024) plen 2 Status: Success (0x00) @ MGMT Event: Discovering (0x0013) plen 2 {0x0001} [hci0] 12:36:15.200500 Address type: 0x07 Discovery: Disabled (0x00) .EE .UNINDENT .UNINDENT .sp Discovery problems to look for: .INDENT 0.0 .IP \(bu 2 \fBStatus: Busy (0x0a)\fP on Start Discovery \-\- Another discovery session is already active. .IP \(bu 2 \fBStatus: Not Powered (0x0f)\fP \-\- The adapter is not powered on. .IP \(bu 2 \fBStatus: RFKilled (0x12)\fP \-\- The radio is blocked by rfkill. .IP \(bu 2 \fBNo Device Found events\fP \-\- The remote device may not be advertising, or address type filtering may be wrong. .UNINDENT .SS Pairing and Bonding via MGMT .sp MGMT provides a high\-level pairing interface. The host sends a \fBPair Device\fP command; the kernel coordinates SMP or SSP as appropriate and reports the result. .sp \fBInitiating pairing\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Command: Pair Device (0x0019) plen 8 {0x0001} [hci0] 12:37:00.500200 LE Address: AA:BB:CC:DD:EE:FF (Random) Capability: KeyboardDisplay (0x04) .EE .UNINDENT .UNINDENT .sp After this command, btmon shows the underlying SMP or SSP exchange (see SMP PAIRING FLOW and HCI INITIALIZATION SEQUENCE for details). User interaction events may appear: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Event: User Confirmation Request (0x000f) plen 12 {0x0001} [hci0] 12:37:01.200100 LE Address: AA:BB:CC:DD:EE:FF (Random) Value: 123456 @ MGMT Command: User Confirmation Reply (0x001e) plen 6 {0x0001} [hci0] 12:37:03.800200 LE Address: AA:BB:CC:DD:EE:FF (Random) @ MGMT Event: Command Complete (0x0001) plen 10 {0x0001} [hci0] 12:37:03.800350 User Confirmation Reply (0x001e) plen 7 Status: Success (0x00) .EE .UNINDENT .UNINDENT .sp Or for passkey entry: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Event: User Passkey Request (0x0010) plen 6 {0x0001} [hci0] 12:37:01.200100 LE Address: AA:BB:CC:DD:EE:FF (Random) @ MGMT Command: User Passkey Reply (0x0020) plen 10 {0x0001} [hci0] 12:37:05.100200 LE Address: AA:BB:CC:DD:EE:FF (Random) Passkey: 123456 .EE .UNINDENT .UNINDENT .sp \fBPairing success\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Event: New Long Term Key (0x000a) plen 37 {0x0001} [hci0] 12:37:06.100200 Store hint: Yes (0x01) LE Address: AA:BB:CC:DD:EE:FF (Random) Key type: Authenticated P\-256 (0x03) Central: 0x00 Encryption size: 16 @ MGMT Event: New Identity Resolving Key (0x0018) plen 30 {0x0001} [hci0] 12:37:06.100300 Store hint: Yes (0x01) Random address: AA:BB:CC:DD:EE:FF LE Address: 11:22:33:44:55:66 (Public) Key: 00112233445566778899aabbccddeeff @ MGMT Event: Command Complete (0x0001) plen 10 {0x0001} [hci0] 12:37:06.100500 Pair Device (0x0019) plen 7 Status: Success (0x00) LE Address: AA:BB:CC:DD:EE:FF (Random) .EE .UNINDENT .UNINDENT .sp Key events to check after pairing: .INDENT 0.0 .IP \(bu 2 \fBNew Long Term Key\fP with \fBStore hint: Yes\fP \-\- The kernel is telling userspace to persist this key for future reconnections. .IP \(bu 2 \fBNew Identity Resolving Key\fP \-\- Reveals the device\(aqs real address behind an RPA. This is essential for address resolution on reconnect. .IP \(bu 2 \fBKey type\fP \-\- \fBAuthenticated P\-256\fP indicates Secure Connections with MITM protection. \fBUnauthenticated P\-256\fP means Just Works SC. \fBAuthenticated\fP (no P\-256) means Legacy with MITM. .UNINDENT .sp \fBPairing failure\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Event: Command Complete (0x0001) plen 10 {0x0001} [hci0] 12:37:06.100500 Pair Device (0x0019) plen 7 Status: Authentication Failed (0x05) LE Address: AA:BB:CC:DD:EE:FF (Random) .EE .UNINDENT .UNINDENT .sp MGMT pairing error codes: .TS box center; l|l|l. T{ Code T} T{ Status T} T{ Diagnostic Meaning T} _ T{ 0x03 T} T{ Failed T} T{ Generic failure; check SMP or HCI errors preceding this T} _ T{ 0x04 T} T{ Connect Failed T} T{ Could not establish connection before pairing T} _ T{ 0x05 T} T{ Authentication Failed T} T{ SMP or SSP authentication did not succeed; check SMP Pairing Failed reason or HCI Authentication Failure event T} _ T{ 0x08 T} T{ Timeout T} T{ Pairing timed out; remote device may have gone out of range T} _ T{ 0x0b T} T{ Rejected T} T{ Remote device rejected the pairing request T} _ T{ 0x0d T} T{ Invalid Parameters T} T{ Bad address type or capability value in the Pair Device command T} .TE .SS Device Connection and Disconnection .sp MGMT reports connection lifecycle events that complement the HCI\-level events shown elsewhere in the trace. .sp \fBDevice connected\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Event: Device Connected (0x000b) plen 13 {0x0001} [hci0] 12:36:18.974319 LE Address: AA:BB:CC:DD:EE:FF (Random) Flags: 0x0000 EIR Data Length: 0 .EE .UNINDENT .UNINDENT .sp This event follows the HCI \fBLE (Enhanced) Connection Complete\fP event. It tells userspace that a new connection is ready. .sp \fBDevice disconnected\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Event: Device Disconnected (0x000c) plen 8 {0x0001} [hci0] 12:38:20.500200 LE Address: AA:BB:CC:DD:EE:FF (Random) Reason: Connection timeout (0x01) .EE .UNINDENT .UNINDENT .sp Disconnect reasons reported by MGMT: .TS box center; l|l|l. T{ Code T} T{ Reason T} T{ Diagnostic Meaning T} _ T{ 0x00 T} T{ Unspecified T} T{ No reason provided; typically a local disconnect T} _ T{ 0x01 T} T{ Connection timeout T} T{ Link supervision timeout expired; device may be out of range T} _ T{ 0x02 T} T{ Connection terminated by local host T} T{ Local side initiated the disconnection T} _ T{ 0x03 T} T{ Connection terminated by remote host T} T{ Remote device initiated the disconnection T} .TE .sp \fBUnpair Device\fP removes stored keys and optionally disconnects: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Command: Unpair Device (0x001a) plen 7 {0x0001} [hci0] 12:39:00.100200 LE Address: AA:BB:CC:DD:EE:FF (Random) Disconnect: Enabled (0x01) @ MGMT Event: Command Complete (0x0001) plen 10 {0x0001} [hci0] 12:39:00.100500 Unpair Device (0x001a) plen 7 Status: Success (0x00) .EE .UNINDENT .UNINDENT .SS Advertising via MGMT .sp Modern BlueZ configures advertising through MGMT commands rather than sending HCI LE advertising commands directly. This provides a multi\-client advertising infrastructure. .sp \fBAdding an advertisement\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Command: Add Advertising (0x003e) plen 22 {0x0001} [hci0] 12:40:00.100200 Instance: 1 Flags: 0x0006 The connectable flag will be managed The limited discoverable flag will be managed Duration: 0 Timeout: 0 Advertising data length: 6 Scan response length: 0 @ MGMT Event: Command Complete (0x0001) plen 5 {0x0001} [hci0] 12:40:00.100500 Add Advertising (0x003e) plen 2 Status: Success (0x00) Instance: 1 .EE .UNINDENT .UNINDENT .sp \fBRemoving an advertisement\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Command: Remove Advertising (0x003f) plen 1 {0x0001} [hci0] 12:41:00.100200 Instance: 1 @ MGMT Event: Command Complete (0x0001) plen 5 {0x0001} [hci0] 12:41:00.100500 Remove Advertising (0x003f) plen 2 Status: Success (0x00) Instance: 1 .EE .UNINDENT .UNINDENT .sp For the HCI\-level advertising commands that result from these MGMT operations, see ADVERTISING AND SCANNING\&. .SS Error Diagnosis .sp MGMT Command Status and Command Complete events carry error codes that are distinct from HCI error codes. When diagnosing failures, check both layers. .sp \fBMGMT error in Command Complete\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Event: Command Complete (0x0001) plen 4 {0x0001} [hci0] 12:42:00.100200 Set Powered (0x0005) plen 1 Status: RFKilled (0x12) .EE .UNINDENT .UNINDENT .sp \fBMGMT Command Status\fP (asynchronous command accepted or rejected): .INDENT 0.0 .INDENT 3.5 .sp .EX @ MGMT Event: Command Status (0x0002) plen 3 {0x0001} [hci0] 12:42:01.100200 Start Discovery (0x0023) plen 0 Status: Busy (0x0a) .EE .UNINDENT .UNINDENT .sp MGMT error codes: .TS box center; l|l|l. T{ Code T} T{ Status T} T{ Diagnostic Meaning T} _ T{ 0x00 T} T{ Success T} T{ Command completed successfully T} _ T{ 0x01 T} T{ Unknown Command T} T{ Kernel does not recognize the command; MGMT version may be too old T} _ T{ 0x02 T} T{ Not Connected T} T{ Operation requires an active connection that does not exist T} _ T{ 0x03 T} T{ Failed T} T{ Generic failure; check HCI events for root cause T} _ T{ 0x04 T} T{ Connect Failed T} T{ Connection attempt failed at HCI level T} _ T{ 0x05 T} T{ Authentication Failed T} T{ Pairing or authentication procedure failed T} _ T{ 0x06 T} T{ Not Paired T} T{ Operation requires an existing bond; device not paired T} _ T{ 0x07 T} T{ No Resources T} T{ Kernel ran out of resources (memory, handles, etc.) T} _ T{ 0x08 T} T{ Timeout T} T{ Operation timed out T} _ T{ 0x09 T} T{ Already Connected T} T{ Connection already exists to target device T} _ T{ 0x0a T} T{ Busy T} T{ Another operation is in progress (e.g., discovery running) T} _ T{ 0x0b T} T{ Rejected T} T{ Operation rejected by remote device or policy T} _ T{ 0x0c T} T{ Not Supported T} T{ Feature not supported by this controller or kernel version T} _ T{ 0x0d T} T{ Invalid Parameters T} T{ Bad parameters in the command T} _ T{ 0x0e T} T{ Disconnected T} T{ Connection was lost during the operation T} _ T{ 0x0f T} T{ Not Powered T} T{ Controller is not powered on; call Set Powered first T} _ T{ 0x10 T} T{ Cancelled T} T{ Operation was cancelled by the user or another command T} _ T{ 0x11 T} T{ Invalid Index T} T{ Controller index does not exist T} _ T{ 0x12 T} T{ RFKilled T} T{ Radio is disabled by rfkill; unblock with \fBrfkill unblock bluetooth\fP T} _ T{ 0x13 T} T{ Already Paired T} T{ Device is already paired; unpair first if re\-pairing is needed T} _ T{ 0x14 T} T{ Permission Denied T} T{ Process lacks required privileges (CAP_NET_ADMIN) T} .TE .sp \fBCorrelating MGMT and HCI errors\fP: When MGMT reports \fBFailed\fP (0x03) or \fBAuthentication Failed\fP (0x05), look at the HCI events immediately preceding the MGMT response. The HCI\-level error (e.g., \fBAuthentication Failure (0x05)\fP, \fBConnection Timeout (0x08)\fP) provides the specific hardware\-level cause. .SS Automating MGMT Analysis .sp \fBList all MGMT commands and events\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dq@ MGMT\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBFind MGMT errors (non\-zero status)\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dq@ MGMT\(dq output.txt | grep \-v \(dqStatus: Success\(dq .EE .UNINDENT .UNINDENT .sp \fBTrack adapter power state changes\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqSet Powered\e|RFKilled\e|Not Powered\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBFind all discovery activity\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqStart Discovery\e|Stop Discovery\e|Device Found\e|Discovering\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBFind pairing activity via MGMT\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqPair Device\e|User Confirmation\e|User Passkey\e|New Long Term Key\e|New Identity Resolving Key\e|Authentication Failed\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBFind connection lifecycle events\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dqDevice Connected\e|Device Disconnected\e|Unpair Device\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBIdentify which MGMT clients are active\fP: .INDENT 0.0 .INDENT 3.5 .sp .EX grep \-n \(dq@ MGMT Open\e|@ MGMT Close\(dq output.txt .EE .UNINDENT .UNINDENT .sp \fBFull MGMT diagnosis pattern\fP: .INDENT 0.0 .IP 1. 3 Find \fBMGMT Open\fP \-\- confirm \fBbluetoothd\fP connected and note socket ID .IP 2. 3 Check \fBRead Controller Information\fP \-\- verify address, version, and current settings .IP 3. 3 Find \fBSet Powered\fP \-\- confirm the adapter was powered on successfully .IP 4. 3 Search for non\-Success status codes \-\- these indicate failures .IP 5. 3 For pairing issues, find \fBPair Device\fP and trace forward to its \fBCommand Complete\fP response .IP 6. 3 For connection issues, find \fBDevice Connected\fP/\fBDevice Disconnected\fP and check the disconnect reason .UNINDENT .SH EXAMPLES .SS Capture the traces from hci0 to hcidump.log file .INDENT 0.0 .INDENT 3.5 .sp .EX $ btmon \-i hci0 \-w hcidump.log .EE .UNINDENT .UNINDENT .SS Open the trace file .INDENT 0.0 .INDENT 3.5 .sp .EX $ btmon \-r hcidump.log .EE .UNINDENT .UNINDENT .SS Open the trace file with wall\-clock timestamps .INDENT 0.0 .INDENT 3.5 .sp .EX $ btmon \-t \-r hcidump.log .EE .UNINDENT .UNINDENT .SS Open the trace file with full date and time .INDENT 0.0 .INDENT 3.5 .sp .EX $ btmon \-T \-r hcidump.log .EE .UNINDENT .UNINDENT .SH AUTOMATED TRACE ANALYSIS .sp This section provides guidance for analyzing btmon traces programmatically or with AI assistance. Each topic references the detailed protocol section earlier in this document. .SS Recommended Workflow .INDENT 0.0 .IP 1. 4 \fBGet an overview\fP: Start with \fBbtmon \-a \fP to see packet counts, connection handles, device addresses, and traffic volumes. .IP 2. 4 \fBDecode with timestamps\fP: Use \fBbtmon \-t \-r > output.txt\fP to produce a text file with wall\-clock timestamps for analysis. .IP 3. 4 \fBIdentify connections\fP: Search for connection establishment events to build a handle\-to\-address mapping: .INDENT 4.0 .INDENT 3.5 .sp .EX grep \-n \(dqConnection Complete\e|Enhanced Connection Complete\e|CIS Established\(dq output.txt .EE .UNINDENT .UNINDENT .IP 4. 4 \fBTrack disconnections\fP: Search for disconnect events and their reasons: .INDENT 4.0 .INDENT 3.5 .sp .EX grep \-n \(dqDisconnect Complete\(dq output.txt .EE .UNINDENT .UNINDENT .sp Then examine the lines following each match for the \fBReason:\fP field. See HCI ERROR AND DISCONNECT REASON CODES for interpretation. .IP 5. 4 \fBCheck pairing/security\fP: Search for SMP activity (see SMP PAIRING FLOW): .INDENT 4.0 .INDENT 3.5 .sp .EX grep \-n \(dqPairing Request\e|Pairing Response\e|Pairing Failed\e|Encryption Change\(dq output.txt .EE .UNINDENT .UNINDENT .IP 6. 4 \fBIdentify LE Audio\fP: Search for ASCS and CIS activity (see LE AUDIO PROTOCOL FLOW): .INDENT 4.0 .INDENT 3.5 .sp .EX grep \-n \(dqASE Control Point\e|CIG Parameters\e|Create Connected Isochronous\e|CIS Established\e|Setup ISO Data Path\(dq output.txt .EE .UNINDENT .UNINDENT .IP 7. 4 \fBCheck for errors\fP: Search across all protocol layers (see PROTOCOL ERROR CODES): .INDENT 4.0 .INDENT 3.5 .sp .EX # HCI\-level errors grep \-n \(dqStatus:\(dq output.txt | grep \-v \(dqSuccess\(dq # ATT\-level errors grep \-n \(dqError Response\(dq output.txt # SMP failures grep \-n \(dqPairing Failed\(dq output.txt # L2CAP rejections grep \-n \(dqConnection refused\(dq output.txt .EE .UNINDENT .UNINDENT .IP 8. 4 \fBExtract GATT discovery\fP: Filter the GATT service/characteristic discovery traffic (see RECONSTRUCTING A GATT DATABASE FROM SNOOP TRACES): .INDENT 4.0 .INDENT 3.5 .sp .EX # Find all service discovery responses grep \-n \(dqRead By Group Type Response\e|Attribute group list\e|Handle range.*UUID\(dq output.txt # Find all characteristic discovery responses grep \-n \(dqRead By Type Response\e|Properties:\e|Value Handle:\e|Value UUID:\(dq output.txt # Find all descriptor discovery responses grep \-n \(dqFind Information Response\e|Format:\e|Handle:.*UUID:\(dq output.txt # Find targeted service searches grep \-n \(dqFind By Type Value\(dq output.txt .EE .UNINDENT .UNINDENT .IP 9. 4 \fBCheck L2CAP channels\fP: Identify protocol usage and channel issues (see L2CAP CHANNEL TRACKING): .INDENT 4.0 .INDENT 3.5 .sp .EX grep \-n \(dqPSM:\e|Connection Request\e|Connection Response\e|Parameter Update\(dq output.txt .EE .UNINDENT .UNINDENT .IP 10. 4 \fBCheck advertising\fP: See what devices are visible and what they advertise (see ADVERTISING AND SCANNING): .INDENT 4.0 .INDENT 3.5 .sp .EX grep \-n \(dqAdvertising Report\e|Name (complete):\e|Appearance:\(dq output.txt .EE .UNINDENT .UNINDENT .UNINDENT .SS Key Patterns for Connection Lifecycle .sp A complete connection lifecycle for an LE ACL connection follows this pattern in the trace: .INDENT 0.0 .IP 1. 3 \fBLE Enhanced Connection Complete\fP \-\- connection established, note the Handle and Peer address .IP 2. 3 \fBLE Connection Update Complete\fP \-\- connection parameters changed (may occur zero or more times) .IP 3. 3 \fBEncryption Change\fP \-\- link encrypted (may show encryption algorithm). See SMP PAIRING FLOW for the SMP exchange that precedes this. .IP 4. 3 ACL Data with ATT/SMP/L2CAP \-\- service discovery and data exchange. See RECONSTRUCTING A GATT DATABASE FROM SNOOP TRACES for GATT, L2CAP CHANNEL TRACKING for channel setup, and PROTOCOL ERROR CODES for error interpretation. .IP 5. 3 \fBDisconnect Complete\fP \-\- connection ended, check Reason field. See HCI ERROR AND DISCONNECT REASON CODES for reason codes. .UNINDENT .sp For LE Audio connections, additional steps appear between 3 and 5 (see LE AUDIO PROTOCOL FLOW for full details): .INDENT 0.0 .IP \(bu 2 ATT operations on PACS/ASCS characteristics (codec negotiation) .IP \(bu 2 \fBLE Set CIG Parameters\fP command and response .IP \(bu 2 \fBLE Create CIS\fP command .IP \(bu 2 \fBLE CIS Established\fP event (note the CIS handle) .IP \(bu 2 \fBLE Setup ISO Data Path\fP command .IP \(bu 2 ISO Data TX/RX (audio streaming) .IP \(bu 2 \fBDisconnect Complete\fP on CIS handle (stream ended) .IP \(bu 2 \fBLE Remove CIG\fP (group removed) .UNINDENT .SS Common Debugging Scenarios .sp \fBPairing failure diagnosis\fP: .INDENT 0.0 .IP 1. 3 Find \fBPairing Request\fP \-\- note IO capabilities and auth requirements .IP 2. 3 Find \fBPairing Response\fP \-\- compare to determine association model .IP 3. 3 If \fBPairing Failed\fP appears, the reason code identifies the failure (see SMP PAIRING FLOW) .IP 4. 3 If \fBEncryption Change\fP shows \fBStatus: Success\fP, pairing succeeded .IP 5. 3 If no SMP traffic on reconnect but \fBEncryption Change\fP fails, the bond was lost on one side .UNINDENT .sp \fBAudio streaming failure diagnosis\fP (see LE AUDIO PROTOCOL FLOW): .INDENT 0.0 .IP 1. 3 Check PACS reads \-\- do both devices support compatible codecs? .IP 2. 3 Check ASE Control Point Config Codec \-\- was it accepted? .IP 3. 3 Check ASE state notifications \-\- did the ASE reach Streaming? .IP 4. 3 Check \fBCIS Established\fP \-\- was Status Success? .IP 5. 3 Check \fBSetup ISO Data Path\fP \-\- was it configured? .IP 6. 3 Check for ISO Data packets \-\- is audio actually flowing? .UNINDENT .sp \fBGATT operation rejected\fP (see PROTOCOL ERROR CODES): .INDENT 0.0 .IP 1. 3 Find \fBError Response\fP \-\- note the error code .IP 2. 3 \fBInsufficient Encryption\fP (0x0f) → expect \fBLE Start Encryption\fP to follow .IP 3. 3 \fBAuthentication Insufficient\fP (0x05) → expect SMP pairing to follow .IP 4. 3 After security is established, the ATT operation should be retried .UNINDENT .sp \fBConnection parameter negotiation failure\fP: .INDENT 0.0 .IP 1. 3 Find \fBConnection Parameter Update Request\fP (L2CAP level) .IP 2. 3 Check the Response \-\- \fBrejected\fP means the central denied it .IP 3. 3 Alternatively, find \fBLE Connection Update Complete\fP (HCI level) .IP 4. 3 Check Status \-\- non\-zero means the controller rejected the update .UNINDENT .SS Vendor\-Specific Events .sp Vendor\-specific HCI events (event code 0xFF) contain controller\-manufacturer diagnostic data. btmon decodes some vendor events for known manufacturers (Intel, Broadcom, etc.) but many sub\-events show as \fBUnknown\fP with raw hex data. These are expected and generally not actionable without vendor documentation. .sp Intel controllers emit extended telemetry events (subevent 0x8780) that include connection quality metrics, error counters, and firmware state. Partial decoding is available in \fBmonitor/intel.c\fP\&. .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH SEE ALSO .sp btsnoop(7) .SH Author Marcel Holtmann , Tedd Ho-Jeong An .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.obex.AgentManager.50000644000000000000000000000005015221755405017707 xustar0020 atime=1783094021 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.AgentManager.50000644000000000000000000000327415221755405017376 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.obex.AgentManager" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.obex.AgentManager \- BlueZ D-Bus OBEX AgentManager API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez.obex .TP .B Interface org.bluez.obex.AgentManager1 .TP .B Object path /org/bluez/obex .UNINDENT .SS Methods .SS void RegisterAgent(object agent) .sp Registers an agent, which must implement \fBorg.bluez.obex.Agent(5)\fP, to request authorization of the user to accept/reject objects. .sp Object push service needs to authorize each received object. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.AlreadyExists .UNINDENT .SS void UnregisterAgent(object agent) .sp Unregisters the agent that has been previously registered using \fBRegisterAgent()\fP\&. .sp The object path parameter must match the same value that has been used on registration. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.DoesNotExist .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.Input.rst0000644000000000000000000000005015063045254016163 xustar0020 atime=1783093959 20 ctime=1783094111 bluez-5.87/doc/org.bluez.Input.rst0000644000000000000000000000211715063045254015645 0ustar00rootroot=============== org.bluez.Input =============== ----------------------------------- BlueZ D-Bus Input API documentation ----------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.Input1 :Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} Properties ---------- string ReconnectMode [readonly] ``````````````````````````````` Indicates the Connectability mode of the HID device as defined by the HID Profile specification, Section 5.4.2. This mode is based in the two properties HIDReconnectInitiate (see Section 5.3.4.6) and HIDNormallyConnectable (see Section 5.3.4.14) which define the following four possible values: :"none": Device and host are not required to automatically restore the connection. :"host": Bluetooth HID host restores connection. :"device": Bluetooth HID device restores connection. :"any": Bluetooth HID device shall attempt to restore the lost connection, but Bluetooth HID Host may also restore the connection. bluez-5.87/doc/PaxHeaders/org.bluez.Battery.rst0000644000000000000000000000005015063045254016476 xustar0020 atime=1783093965 20 ctime=1783094111 bluez-5.87/doc/org.bluez.Battery.rst0000644000000000000000000000162615063045254016164 0ustar00rootroot================= org.bluez.Battery ================= ------------------------------------- BlueZ D-Bus Battery API documentation ------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.Battery1 :Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} Properties ---------- byte Percentage [readonly] `````````````````````````` The percentage of battery left as an unsigned 8-bit integer. string Source [readonly, optional] `````````````````````````````````` Describes where the battery information comes from. This property is informational only and may be useful for debugging purposes. Providers from **org.bluez.BatteryProvider(5)** may make use of this property to indicate where the battery report comes from (e.g. "HFP 1.7", "HID", or the profile UUID). bluez-5.87/doc/PaxHeaders/btmon-smp.rst0000644000000000000000000000005015200607060015062 xustar0020 atime=1783092683 20 ctime=1783094112 bluez-5.87/doc/btmon-smp.rst0000644000000000000000000002077415200607060014555 0ustar00rootroot.. This file is included by btmon.rst. SMP PAIRING FLOW ================ The Security Manager Protocol (SMP) handles pairing, key generation, and key distribution between Bluetooth devices. SMP traffic appears inside L2CAP on fixed CID 0x0006 (LE) or CID 0x0007 (BR/EDR). btmon decodes all SMP operations automatically. Pairing Phases -------------- SMP pairing proceeds in three phases. Each phase produces a distinct pattern in the btmon output. **Phase 1: Feature Exchange** Pairing begins when one device sends a Security Request (peripheral) or the host initiates pairing directly. The initiator sends a Pairing Request and the responder replies with a Pairing Response:: > ACL Data RX: Handle 2048 flags 0x02 dlen 11 #497 [hci0] 0.026107 SMP: Pairing Request (0x01) len 6 IO capability: NoInputNoOutput (0x03) OOB data: Authentication data not present (0x00) Authentication requirement: Bonding, MITM, SC, CT2 (0x2d) Max encryption key size: 16 Initiator key distribution: IdKey Sign (0x06) Responder key distribution: IdKey Sign (0x06) < ACL Data TX: Handle 2048 flags 0x00 dlen 11 #499 [hci0] 0.026894 SMP: Pairing Response (0x02) len 6 IO capability: KeyboardDisplay (0x04) OOB data: Authentication data not present (0x00) Authentication requirement: Bonding, SC, CT2 (0x29) Max encryption key size: 16 Initiator key distribution: IdKey (0x02) Responder key distribution: IdKey (0x02) Key fields to check: - **Authentication requirement** -- The ``SC`` flag indicates Secure Connections. Its absence means Legacy Pairing. - **IO capability** -- Determines the association model (Just Works, Passkey Entry, Numeric Comparison, OOB). - **Key distribution** -- Which keys each side will send after encryption is established. ``IdKey`` = Identity Resolving Key (IRK), ``EncKey`` = Long Term Key (legacy only), ``Sign`` = CSRK. **Phase 2: Authentication (Secure Connections)** For Secure Connections pairing (``SC`` flag set), both devices exchange public keys, then perform confirm/random value exchange:: > ACL Data RX: Handle 2048 flags 0x02 dlen 69 #501 [hci0] 0.098224 SMP: Pairing Public Key (0x0c) len 64 X: 1a2b3c4d... Y: 5e6f7a8b... < ACL Data TX: Handle 2048 flags 0x00 dlen 69 #503 [hci0] 0.148556 SMP: Pairing Public Key (0x0c) len 64 X: 9c8d7e6f... Y: 0a1b2c3d... < ACL Data TX: Handle 2048 flags 0x00 dlen 21 #505 [hci0] 0.149003 SMP: Pairing Confirm (0x03) len 16 Confirm value: a1b2c3d4e5f6... > ACL Data RX: Handle 2048 flags 0x02 dlen 21 #507 [hci0] 0.212884 SMP: Pairing Random (0x04) len 16 Random value: 1122334455... < ACL Data TX: Handle 2048 flags 0x00 dlen 21 #509 [hci0] 0.213100 SMP: Pairing Random (0x04) len 16 Random value: 6677889900... > ACL Data RX: Handle 2048 flags 0x02 dlen 21 #511 [hci0] 0.278003 SMP: Pairing DHKey Check (0x0d) len 16 E: aabbccddee... < ACL Data TX: Handle 2048 flags 0x00 dlen 21 #513 [hci0] 0.278450 SMP: Pairing DHKey Check (0x0d) len 16 E: ffeeddccbb... After DHKey Check, the initiator starts encryption at the HCI level:: < HCI Command: LE Start Encryption (0x08|0x0019) plen 28 #515 [hci0] 0.279002 > HCI Event: Encryption Change (0x08) plen 4 #517 [hci0] 0.342556 Status: Success (0x00) Handle: 2048 Encryption: Enabled with AES-CCM (0x01) **Phase 2: Authentication (Legacy Pairing)** Legacy pairing (no ``SC`` flag) skips the Public Key and DHKey Check exchanges. Only Confirm and Random values are exchanged:: < ACL Data TX: Handle 2048 flags 0x00 dlen 21 #501 [hci0] 0.098224 SMP: Pairing Confirm (0x03) len 16 Confirm value: ... > ACL Data RX: Handle 2048 flags 0x02 dlen 21 #503 [hci0] 0.162556 SMP: Pairing Confirm (0x03) len 16 Confirm value: ... < ACL Data TX: Handle 2048 flags 0x00 dlen 21 #505 [hci0] 0.163003 SMP: Pairing Random (0x04) len 16 Random value: ... > ACL Data RX: Handle 2048 flags 0x02 dlen 21 #507 [hci0] 0.228884 SMP: Pairing Random (0x04) len 16 Random value: ... **Phase 3: Key Distribution** After encryption is established, each device distributes keys as negotiated in Phase 1:: > ACL Data RX: Handle 2048 flags 0x02 dlen 21 #519 [hci0] 0.343002 SMP: Identity Information (0x08) len 16 Identity resolving key: 00112233445566778899aabbccddeeff > ACL Data RX: Handle 2048 flags 0x02 dlen 12 #521 [hci0] 0.343556 SMP: Identity Address Information (0x09) len 7 Address type: Public (0x00) Address: 00:11:22:33:44:55 The Identity Address Information reveals the device's true public or static random address (as opposed to a Resolvable Private Address used during connection). For Legacy Pairing, LTK distribution also appears:: > ACL Data RX: Handle 2048 flags 0x02 dlen 21 #519 [hci0] 0.343002 SMP: Encryption Information (0x06) len 16 Long term key: 00112233... > ACL Data RX: Handle 2048 flags 0x02 dlen 15 #521 [hci0] 0.343556 SMP: Central Identification (0x07) len 10 EDIV: 0x1234 Rand: 0x0123456789abcdef Pairing Failure --------------- When pairing fails, one device sends a Pairing Failed PDU:: > ACL Data RX: Handle 2048 flags 0x02 dlen 6 #505 [hci0] 0.213002 SMP: Pairing Failed (0x05) len 1 Reason: Authentication requirements (0x03) SMP failure reasons: .. list-table:: :header-rows: 1 :widths: 8 35 57 * - Code - Reason - Diagnostic Meaning * - 0x01 - Passkey Entry Failed - User cancelled or entered wrong passkey * - 0x02 - OOB Not Available - OOB data expected but not provided * - 0x03 - Authentication Requirements - Devices cannot agree on security level (e.g., one requires MITM but IO caps only allow Just Works) * - 0x04 - Confirm Value Failed - Cryptographic check failed; possible MITM attack * - 0x05 - Pairing Not Supported - Remote does not support pairing * - 0x06 - Encryption Key Size - Cannot agree on key size * - 0x07 - Command Not Supported - Received unrecognized SMP command * - 0x08 - Unspecified Reason - Generic failure * - 0x09 - Repeated Attempts - Pairing rate-limited; wait before retry * - 0x0a - Invalid Parameters - Invalid fields in SMP command * - 0x0b - DHKey Check Failed - ECDH key agreement failed (SC only) * - 0x0c - Numeric Comparison Failed - User rejected numeric comparison * - 0x0d - BR/EDR Pairing In Progress - Classic pairing already active * - 0x0e - Cross-Transport Key Derivation Not Allowed - CTKD rejected by policy Automating Pairing Analysis ---------------------------- **Identify all pairing attempts**:: grep -n "Pairing Request\|Pairing Response\|Pairing Failed\|Pairing Public Key\|DHKey Check" output.txt **Check pairing method (Secure Connections vs Legacy)**: - If ``Pairing Public Key`` appears between Request/Response and Confirm: Secure Connections. - If only Confirm/Random follow Request/Response: Legacy Pairing. - Check the ``Authentication requirement`` line for the ``SC`` flag. **Detect pairing failures**:: grep -n "Pairing Failed" output.txt **Correlate pairing with encryption**: After successful pairing, expect ``Encryption Change`` with ``Status: Success``. Search for:: grep -n "Encryption Change\|Encryption:" output.txt **Identify re-pairing on reconnect**: Reconnections to a bonded device should show ``Encryption Change`` without SMP traffic (using stored keys). If SMP Pairing Request appears on reconnection, the bond was lost on one side. **Full pairing diagnosis pattern**: 1. Find ``Pairing Request`` -- note the handle, IO capabilities, auth requirements 2. Find ``Pairing Response`` -- compare IO capabilities to determine association model 3. Check for ``Pairing Failed`` -- if present, the reason code identifies the failure 4. Check for ``Encryption Change`` with ``Status: Success`` -- confirms pairing completed 5. Check for ``Identity Address Information`` -- reveals the device's true address bluez-5.87/doc/PaxHeaders/org.bluez.MediaEndpoint.50000644000000000000000000000005015221755334017143 xustar0020 atime=1783093980 20 ctime=1783094112 bluez-5.87/doc/org.bluez.MediaEndpoint.50000644000000000000000000002400715221755334016627 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.MediaEndpoint" "5" "September 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.MediaEndpoint \- BlueZ D-Bus MediaEndpoint API documentation .SH Interface .INDENT 0.0 .TP .B Service unique name (Server role) org.bluez (Client role) .TP .B Interface org.bluez.MediaEndpoint1 .TP .B Object path freely definable (Server role) [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/sep# (Client role) .TP .B Used by \fBbluetoothctl\-endpoint(1)\fP .UNINDENT .SS Methods .SS void SetConfiguration(object transport, dict properties) .sp Set configuration for the transport. .INDENT 0.0 .TP .B object transport Configured transport object. .TP .B dict properties Configured \fBorg.bluez.MediaTransport(5)\fP properties. .UNINDENT .sp For client role transport must be set with a server endpoint object which will be configured and the properties must contain the following properties: .INDENT 0.0 .TP .B array{byte} Capabilities [Mandatory] See Capabilities property. .TP .B array{byte} Metadata [ISO only] See Metadata property. .TP .B dict QoS [ISO only] See \fBorg.bluez.MediaTransport(5)\fP QoS property. .UNINDENT .INDENT 0.0 .TP .B Possible errors for A2DP endpoints: .INDENT 7.0 .TP .B org.bluez.Error.A2DP.InvalidCodecType .TP .B org.bluez.Error.A2DP.NotSupportedCodecType .TP .B org.bluez.Error.A2DP.InvalidSamplingFrequency .TP .B org.bluez.Error.A2DP.NotSupportedSamplingFrequency .TP .B org.bluez.Error.A2DP.InvalidChannelMode .TP .B org.bluez.Error.A2DP.NotSupportedChannelMode .TP .B org.bluez.Error.A2DP.InvalidSubbands .TP .B org.bluez.Error.A2DP.NotSupportedSubbands .TP .B org.bluez.Error.A2DP.InvalidAllocationMethod .TP .B org.bluez.Error.A2DP.NotSupportedAllocationMethod .TP .B org.bluez.Error.A2DP.InvalidMinimumBitpoolValue .TP .B org.bluez.Error.A2DP.NotSupportedMinimumBitpoolValue .TP .B org.bluez.Error.A2DP.InvalidMaximumBitpoolValue .TP .B org.bluez.Error.A2DP.NotSupportedMaximumBitpoolValue .TP .B org.bluez.Error.A2DP.InvalidLayer .TP .B org.bluez.Error.A2DP.NotSupportedLayer .TP .B org.bluez.Error.A2DP.NotSupportedCRC .TP .B org.bluez.Error.A2DP.NotSupportedMPF .TP .B org.bluez.Error.A2DP.NotSupportedVBR .TP .B org.bluez.Error.A2DP.InvalidBitRate .TP .B org.bluez.Error.A2DP.NotSupportedBitRate .TP .B org.bluez.Error.A2DP.InvalidObjectType .TP .B org.bluez.Error.A2DP.NotSupportedObjectType .TP .B org.bluez.Error.A2DP.InvalidChannels .TP .B org.bluez.Error.A2DP.NotSupportedChannels .TP .B org.bluez.Error.A2DP.InvalidVersion .TP .B org.bluez.Error.A2DP.NotSupportedVersion .TP .B org.bluez.Error.A2DP.NotSupportedMaximumSUL .TP .B org.bluez.Error.A2DP.InvalidBlockLength .TP .B org.bluez.Error.A2DP.InvalidCPType .TP .B org.bluez.Error.A2DP.InvalidCPFormat .TP .B org.bluez.Error.A2DP.InvalidCodecParameter .TP .B org.bluez.Error.A2DP.NotSupportedCodecParameter .TP .B org.bluez.Error.A2DP.InvalidDRC .TP .B org.bluez.Error.A2DP.NotSupportedDRC .UNINDENT .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > endpoint.config [preset] .UNINDENT .SS array{byte} SelectConfiguration(array{byte} capabilities) .sp Select preferable configuration from the supported capabilities. .sp Returns a configuration which can be used to setup a transport, see \fBorg.bluez.MediaTransport(5)\fP for possible values. .sp Note: There is no need to cache the selected configuration since on success the configuration is send back as parameter of SetConfiguration. .SS dict SelectProperties(dict capabilities) .sp Select BAP unicast configuration from the supported capabilities: .INDENT 0.0 .TP .B object Endpoint .TP .B array{byte} Capabilities .TP .B array{byte} Metadata .TP .B uint32 Locations .TP .B uint32_t ChannelAllocation .TP .B dict QoS .INDENT 7.0 .TP .B byte Framing .TP .B byte PHY .TP .B uint16 MaximumLatency .TP .B uint32 MinimumDelay .TP .B uint32 MaximumDelay .TP .B uint32 PreferredMinimumDelay .TP .B uint32 PreferredMaximumDelay .UNINDENT .UNINDENT .sp See MediaEndpoint Properties for their possible values. .sp Returns a configuration which can be used to setup a transport: .INDENT 0.0 .TP .B array{byte} Capabilities .TP .B array{byte} Metadata [optional] .TP .B dict QoS .UNINDENT .sp See SetConfiguration for their possible values. .sp Note: There is no need to cache the selected properties since on success the configuration is send back as parameter of SetConfiguration. .SS void ClearConfiguration(object transport) .sp Clear transport configuration. .sp \fBServer role:\fP [ISO only] .sp Close the stream associated with the given transport. If the path given is the path of this endpoint, all its streams are closed. .SS void Reconfigure(dict properties) .sp [ISO only] .sp Reconfigure a BAP unicast endpoint. This closes all existing streams of the endpoint, and restarts the configuration selection flow which e.g. triggers calls to \fISelectProperties\fP allowing the sound server to modify the configuration. .sp The following arguments are taken in \fIproperties\fP: .INDENT 0.0 .TP .B boolean Defer [optional] If True, mark endpoint for reconfiguration, but postpone it until a non\-deferred \fIReconfigure()\fP operation is made on an endpoint of the same device. .sp This is necessary to use when reconfiguring source and sink streams with the intention that they be combined into the same CIG, possibly forming bidirectional CIS. .UNINDENT .SS void Release() .sp This method gets called when \fBbluetoothd(8)\fP unregisters the endpoint. .sp An endpoint can use it to do cleanup tasks. There is no need to unregister the endpoint, because when this method gets called it has already been unregistered. .SS MediaEndpoint Properties .SS string UUID [readonly, optional] .sp UUID of the profile which the endpoint is for. .SS byte Codec [readonly, optional] .sp Assigned number of codec that the endpoint implements. .sp The values should match the profile specification which is indicated by the UUID. .SS uint32_t Vendor [readonly, Optional] .sp Vendor\-specific Company ID, Codec ID tuple that the endpoint implements. .sp It shall be set to appropriate value when Vendor Specific Codec (0xff) is used. .SS array{byte} Capabilities [readonly, optional] .sp Capabilities blob, it is used as it is so the size and byte order must match. .SS array{byte} Metadata [readonly, Optional] .sp Metadata blob, it is used as it is so the size and byte order must match. .SS object Device [readonly, optional] .sp Device object which the endpoint is belongs to. .SS bool DelayReporting [readonly, optional] .sp Indicates if endpoint supports Delay Reporting. .SS uint32 Locations [readonly, optional, ISO only, experimental] .sp Indicates endpoint supported locations. .SS uint16 SupportedContext [readonly, optional, ISO only, experimental] .sp Indicates endpoint supported audio context. .SS uint16 Context [readonly, optional, ISO only, experimental] .sp Indicates endpoint available audio context. .SS dict QoS [readonly, optional, ISO only, experimental] .sp Indicates QoS capabilities. .INDENT 0.0 .TP .B byte Framing Indicates endpoint support framing. .sp Possible Values: .INDENT 7.0 .TP .B 0x00 Unframed PDUs supported. .TP .B 0x01 Unframed PDUs not supported. .UNINDENT .TP .B byte PHY Indicates endpoint preferred PHY. .sp Possible values: .INDENT 7.0 .TP .B bit 0 LE 1M preferred. .TP .B bit 1 LE 2M preferred. .TP .B bit 2 LE Coded preferred. .UNINDENT .TP .B byte Retransmissions Indicates endpoint preferred number of retransmissions. .TP .B uint16 MaximumLatency Indicates endpoint maximum latency. .TP .B uint32 MinimumDelay Indicates endpoint minimum presentation delay. .TP .B uint32 MaximumDelay Indicates endpoint maximum presentation delay. .TP .B uint32 PreferredMinimumDelay Indicates endpoint preferred minimum presentation delay. .TP .B uint32 PreferredMaximumDelay Indicates endpoint preferred maximum presentation delay. .UNINDENT .SS dict SupportedFeatures [readonly, ISO only, experimental] .sp Dictionary representing supported features for different services. Keys are service UUIDs, values arrays of strings. .sp Services: .INDENT 0.0 .TP .B array{string} 00001855\-0000\-1000\-8000\-00805f9b34fb TMAP Features. Possible items: .INDENT 7.0 .TP .B \(dqcg\(dq Call Gateway .TP .B \(dqct\(dq Call Terminal .TP .B \(dqums\(dq Unicast Media Sender .TP .B \(dqumr\(dq Unicast Media Receiver .TP .B \(dqbms\(dq Broadcast Media Sender .TP .B \(dqbmr\(dq Broadcast Media Receiver .UNINDENT .TP .B array{string} 00001858\-0000\-1000\-8000\-00805f9b34fb GMAP Features. Possible items: .INDENT 7.0 .TP .B \(dqugg\(dq Unicast Game Gateway .TP .B \(dqugt\(dq Unicast Game Terminal .TP .B \(dqbgs\(dq Broadcast Game Sender .TP .B \(dqbgr\(dq Broadcast Game Receiver .TP .B \(dqugg\-multiplex\(dq UGG Multiplex feature support .TP .B \(dqugg\-96kbps\-source\(dq UGG 96 kbps Source feature support .TP .B \(dqugg\-multisink\(dq UGG Multisink feature support .TP .B \(dqugt\-source\(dq UGT Source feature support .TP .B \(dqugt\-80kbps\-source\(dq UGT 80 kbps Source feature support .TP .B \(dqugt\-sink\(dq UGT Sink feature support .TP .B \(dqugt\-64kbps\-sink\(dq UGT 64 kbps Sink feature support .TP .B \(dqugt\-multiplex\(dq UGT Multiplex feature support .TP .B \(dqugt\-multisink\(dq UGT Multisink feature support .TP .B \(dqugt\-multisource\(dq UGT Multisource feature support .TP .B \(dqbgs\-96kbps\(dq BGS 96 kbps feature support .TP .B \(dqbgr\-multisink\(dq BGR Multisink feature support .TP .B \(dqbgr\-multiplex\(dq BGR Multiplex feature support .UNINDENT .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.ThermometerWatcher.50000644000000000000000000000005015221755422020232 xustar0020 atime=1783094034 20 ctime=1783094112 bluez-5.87/doc/org.bluez.ThermometerWatcher.50000644000000000000000000000457215221755422017723 0ustar00rootroot'\" t .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.ThermometerWatcher" "5" "July 2011" "BlueZ" "Linux System Administration" .SH Name org.bluez.ThermometerWatcher \- BlueZ D-Bus Health Thermometer Watcher API documentation .SH Interface .INDENT 0.0 .TP .B Service unique name .TP .B Interface org.bluez.ThermometerWatcher1 .TP .B Object path freely definable .UNINDENT .SS Methods .SS void MeasurementReceived(dict measurement) .sp This callback gets called when a measurement has been scanned in the thermometer. .sp Possible measurement values: .INDENT 0.0 .TP .B int16 Exponent .TP .B int32 Mantissa Exponent and Mantissa values as extracted from float value defined by IEEE\-11073\-20601. .sp Measurement value is calculated as (Mantissa) * (10^Exponent) .sp For special cases Exponent is set to 0 and Mantissa is set to one of following values: .UNINDENT .TS box center; l|l. T{ Value T} T{ Description T} _ T{ +(2^23 \- 1) T} T{ NaN (invalid or missing data) T} _ T{ \-(2^23) T} T{ NRes T} _ T{ +(2^23 \- 2) T} T{ +Infinity T} _ T{ \-(2^23 \- 2) T} T{ \-Infinity T} .TE .INDENT 0.0 .TP .B string Unit .INDENT 7.0 .TP .B Possible values: \(dqcelsius\(dq or \(dqfahrenheit\(dq .UNINDENT .TP .B uint64 Time (optional) Time of measurement, if supported by device. Expressed in seconds since epoch. .TP .B string Type (optional) Only present if measurement type is known. .INDENT 7.0 .TP .B Possible values: \(dqarmpit\(dq, \(dqbody\(dq, \(dqear\(dq, \(dqfinger\(dq, \(dqintestines\(dq, \(dqmouth\(dq, \(dqrectum\(dq, \(dqtoe\(dq, \(dqtympanum\(dq .UNINDENT .TP .B string Measurement Possible values: \(dqfinal\(dq or \(dqintermediate\(dq .UNINDENT .SH Author Santiago Carot-Nemesio .\" End of generated man page. bluez-5.87/doc/PaxHeaders/hid2hci.10000644000000000000000000000005015221755521014021 xustar0020 atime=1783094097 20 ctime=1783094113 bluez-5.87/doc/hid2hci.10000644000000000000000000000343015221755521013502 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "hid2hci" "1" "May 15, 2009" "BlueZ" "Linux System Administration" .SH Name hid2hci \- Bluetooth HID to HCI mode switching utility .SH SYNOPSIS .sp \fBhid2hci\fP [\fIOPTIONS\fP] .SH DESCRIPTION .sp \fBhid2hci(1)\fP is used to set up switch supported Bluetooth devices into the HCI mode and back. .SH OPTIONS .INDENT 0.0 .TP .B \-\-mode=[\fImode\fP] Sets the mode to the device into. The possible values for \fImode\fP are \fBhid\fP, \fBhci\fP\&. .TP .B \-\-method=[\fImethod\fP] Which vendor method to use for switching the device. The possible values for \fImethod\fP are \fBcsr\fP, \fBcsr2\fP, \fBlogitech\-hdi\fP, \fBdell\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-devpath Specifies the device path in /sys .TP .B \-\-help Gives a list of possible options. .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Author Marcel Holtmann .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.MediaControl.rst0000644000000000000000000000005015063045254017444 xustar0020 atime=1783093972 20 ctime=1783094111 bluez-5.87/doc/org.bluez.MediaControl.rst0000644000000000000000000000272415063045254017132 0ustar00rootroot====================== org.bluez.MediaControl ====================== ------------------------------------------ BlueZ D-Bus MediaControl API documentation ------------------------------------------ :Version: BlueZ :Date: September 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.MediaControl1 :Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} Methods ------- void Play() [Deprecated] ```````````````````````` Resume playback. void Pause() [Deprecated] ````````````````````````` Pause playback. void Stop() [Deprecated] ```````````````````````` Stop playback. void Next() [Deprecated] ```````````````````````` Next item. void Previous() [Deprecated] ```````````````````````````` Previous item. void VolumeUp() [Deprecated] ```````````````````````````` Adjust remote volume one step up void VolumeDown() [Deprecated] `````````````````````````````` Adjust remote volume one step down void FastForward() [Deprecated] ``````````````````````````````` Fast forward playback, this action is only stopped when another method in this interface is called. void Rewind() [Deprecated] `````````````````````````` Rewind playback, this action is only stopped when another method in this interface is called. Properties ---------- boolean Connected [readonly] ```````````````````````````` object Player [readonly, optional] `````````````````````````````````` Addressed Player object path. bluez-5.87/doc/PaxHeaders/assigned-numbers.rst0000644000000000000000000000005015142177620016425 xustar0020 atime=1778585134 20 ctime=1783094111 bluez-5.87/doc/assigned-numbers.rst0000644000000000000000000000074215142177620016111 0ustar00rootroot=============== RFCOMM Channels =============== Since there are a limited amount of possible RFCOMM channels (1-31) they've been pre-allocated for currently known profiles in order to avoid conflicts. .. csv-table:: :header: "Profile", "Channel" :widths: auto DUN, 1 SPP, 3 HSP HS, 6 HFP HF, 7 OPP, 9 FTP, 10 BIP, 11 HSP AG, 12 HFP AG, 13 SYNCH (IrMC), 14 PBAP, 15 MAP MAS, 16 MAP MNS, 17 SyncEvolution, 19 PC/Ovi Suite, 24 SyncML Client, 25 SyncML Server, 26 bluez-5.87/doc/PaxHeaders/org.bluez.GattDescriptor.rst0000644000000000000000000000005015142177620020023 xustar0020 atime=1783093994 20 ctime=1783094112 bluez-5.87/doc/org.bluez.GattDescriptor.rst0000644000000000000000000000672715142177620017520 0ustar00rootroot======================== org.bluez.GattDescriptor ======================== -------------------------------------------- BlueZ D-Bus GattDescriptor API documentation -------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Description =========== GATT local/server and remote/client descriptor attribute representation share the same high-level D-Bus API. Local/Server refers to GATT based descriptors exported by a plugin or an external application. Remote/Client refers to GATT descriptors exported by the peer. Interface ========= Client ------ :Service: org.bluez :Interface: org.bluez.GattDescriptor1 :Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/service#/char#/descriptor# :Used by: **bluetoothctl-gatt(1)** Server ------ :Service: unique name :Interface: org.bluez.GattDescriptor1 :Object path: freely definable Methods ------- array{byte} ReadValue(dict flags) ````````````````````````````````` Issues a request to read the value of the descriptor and returns the value if the operation was successful. Possible options: :uint16 offset: Read start offset in bytes. :object device (server only): Device object. :string link: Link type (Server only). Possible values: :"BR/EDR": :"LE": Possible Errors: :org.bluez.Error.Failed: :org.bluez.Error.InProgress: :org.bluez.Error.NotPermitted: :org.bluez.Error.NotAuthorized: :org.bluez.Error.NotSupported: Examples: :bluetoothctl: [gatt] > read [offset] void WriteValue(array{byte} value, dict flags) `````````````````````````````````````````````` Issues a request to write the value of the descriptor. Possible flags: :uint16 offset: Write start offset in bytes. :uint16 mtu: Exchanged MTU (Server only). :object device: Device path (Server only). :string link: Link type (Server only). Possible values: :"BR/EDR": :"LE": :boolean prepare-authorize: True if prepare authorization request. Possible Errors: :org.bluez.Error.Failed: :org.bluez.Error.InProgress: :org.bluez.Error.NotPermitted: :org.bluez.Error.InvalidValueLength: :org.bluez.Error.NotAuthorized: :org.bluez.Error.NotSupported: :org.bluez.Error.ImproperlyConfigured: Examples: :bluetoothctl: [gatt] > write [offset] [type] Properties ---------- string UUID [read-only] ``````````````````````` 128-bit descriptor UUID. object Characteristic [read-only] ````````````````````````````````` Object path of the GATT characteristic the descriptor belongs to. array{byte} Value [read-only, optional] ``````````````````````````````````````` The cached value of the descriptor. This property gets updated only after a successful read request, upon which a PropertiesChanged signal will be emitted. array{string} Flags [read-only] ``````````````````````````````` Defines how the descriptor value can be used. Possible values: :"read": :"write": :"encrypt-read": :"encrypt-write": :"encrypt-authenticated-read": :"encrypt-authenticated-write": :"secure-read" (Server Only): :"secure-write" (Server Only): :"authorize": uint16 Handle [read-only] (Client Only) ``````````````````````````````````````` Descriptor handle. uint16 Handle [read-write, optional] (Server Only) `````````````````````````````````````````````````` Descriptor handle. When available in the server it would attempt to use to allocate into the database which may fail, to auto allocate the value 0x0000 shall be used which will cause the allocated handle to be set once registered. bluez-5.87/doc/PaxHeaders/org.bluez.GattManager.rst0000644000000000000000000000005015142177620017257 xustar0020 atime=1783093986 20 ctime=1783094111 bluez-5.87/doc/org.bluez.GattManager.rst0000644000000000000000000001015315142177620016740 0ustar00rootroot===================== org.bluez.GattManager ===================== ----------------------------------------- BlueZ D-Bus GattManager API documentation ----------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Description =========== GATT Manager allows external applications to register GATT services and profiles. Registering a profile allows applications to subscribe to *remote/client* services. Registering a service allows applications to publish a *local/server* GATT service, which then becomes available to remote devices. A GATT service is represented by a D-Bus object hierarchy where the root node corresponds to a service and the child nodes represent characteristics and descriptors that belong to that service. Each node must implement one of **org.bluez.GattService(5)**, **org.bluez.GattCharacteristic(5)** or **org.bluez.GattDescriptor(5)** interfaces, based on the attribute it represents. Each node must also implement the standard D-Bus Properties interface to expose their properties. These objects collectively represent a GATT service definition. To make service registration simple, **bluetoothd(8)** requires that all objects that belong to a GATT service be grouped under a D-Bus Object Manager that solely manages the objects of that service. Hence, the standard DBus.ObjectManager interface must be available on the root service path. An example application hierarchy containing two separate GATT services may look like this: .. code-block:: -> /com/example | - org.freedesktop.DBus.ObjectManager | -> /com/example/service0 | | - org.freedesktop.DBus.Properties | | - org.bluez.GattService1 | | | -> /com/example/service0/char0 | | - org.freedesktop.DBus.Properties | | - org.bluez.GattCharacteristic1 | | | -> /com/example/service0/char1 | | - org.freedesktop.DBus.Properties | | - org.bluez.GattCharacteristic1 | | | -> /com/example/service0/char1/desc0 | - org.freedesktop.DBus.Properties | - org.bluez.GattDescriptor1 | -> /com/example/service1 | - org.freedesktop.DBus.Properties | - org.bluez.GattService1 | -> /com/example/service1/char0 - org.freedesktop.DBus.Properties - org.bluez.GattCharacteristic1 When a service is registered, **bluetoothd(8)** will automatically obtain information about all objects using the service's Object Manager. Once a service has been registered, the objects of a service should not be removed. If **bluetoothd(8)** receives an InterfacesRemoved signal from a service's Object Manager, it will immediately unregister the service. Similarly, if the application disconnects from the bus, all of its registered services will be automatically unregistered. InterfacesAdded signals will be ignored. Interface ========= :Service: org.bluez :Interface: org.bluez.GattManager1 :Object path: [variable prefix]/{hci0,hci1,...} :Used by: **bluetoothctl-gatt(1)** Methods ------- void RegisterApplication(object application, dict options) `````````````````````````````````````````````````````````` Registers a local GATT services hierarchy as described above (GATT Server) and/or GATT profiles (GATT Client). The application object path together with the D-Bus system bus connection ID define the identification of the application registering a GATT based service (**org.bluez.GattService(5)**) and/or profile (**org.bluez.GattProfile(5)**). Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.AlreadyExists: Examples: :bluetoothctl: > gatt.register-application [UUID ...] void UnregisterApplication(object application) `````````````````````````````````````````````` This unregisters the services and/or profiles that has been previously registered using **RegisterApplication()**. The object path parameter must match the same value that has been used on registration. Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.DoesNotExist: Examples: :bluetoothctl: > gatt.unregister-application bluez-5.87/doc/PaxHeaders/org.bluez.DeviceSet.rst0000644000000000000000000000005015063045254016737 xustar0020 atime=1783093946 20 ctime=1783094111 bluez-5.87/doc/org.bluez.DeviceSet.rst0000644000000000000000000000326015063045254016421 0ustar00rootroot=================== org.bluez.DeviceSet =================== --------------------------------------- BlueZ D-Bus DeviceSet API documentation --------------------------------------- :Version: BlueZ :Date: September 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.DeviceSet1 :Object path: [variable prefix]/{hci0,hci1,...}/set_{sirk} Methods ------- void Connect() [experimental] ````````````````````````````` Connects all **devices** members of the set, each member is connected in sequence as they were added/loaded following the same procedure as described in **Device1.Connect**. Possible errors: :org.bluez.Error.NotReady: :org.bluez.Error.Failed: :org.bluez.Error.InProgress: :org.bluez.Error.AlreadyConnected: void Disconnect() [experimental] ```````````````````````````````` Disconnects all **devices** members of the set, each member is disconnected in sequence as they were connected following the same procedure as described in **Device1.Disconnect**. Possible errors: :org.bluez.Error.NotConnected: Properties ---------- object Adapter [readonly, experimental] ``````````````````````````````````````` The object path of the adapter the set belongs to. bool AutoConnect [read-write, experimental] ``````````````````````````````````````````` Indicates if the **devices** members of the set shall be automatically connected once any of its members is connected. array(object) Devices [ready-only, experimental] ```````````````````````````````````````````````` List of devices objects that are members of the set. byte Size [read-only, experimental] ``````````````````````````````````` Set members size. bluez-5.87/doc/PaxHeaders/org.bluez.ProfileManager.rst0000644000000000000000000000005015063045254017757 xustar0020 atime=1783093952 20 ctime=1783094111 bluez-5.87/doc/org.bluez.ProfileManager.rst0000644000000000000000000000636015063045254017445 0ustar00rootroot======================== org.bluez.ProfileManager ======================== -------------------------------------------- BlueZ D-Bus ProfileManager API documentation -------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.ProfileManager1 :Object path: /org/bluez Methods ------- void RegisterProfile(object profile, string uuid, dict options) ``````````````````````````````````````````````````````````````` Registers profile agent. The object path defines the path of the profile that will be called when there is a connection and must implement **org.bluez.Profile(5)** interface. If an application disconnects from the bus all its registered profiles will be removed. Possible uuid values: :"0000111f-0000-1000-8000-00805f9b34fb": HFP AG, default profile Version is 1.7, profile Features is 0b001001 and RFCOMM channel is 13. Authentication is required. :"0000111e-0000-1000-8000-00805f9b34fb": HFP HS, default profile Version is 1.7, profile Features is 0b000000 and RFCOMM channel is 7. Authentication is required. :"00001112-0000-1000-8000-00805f9b34fb": HSP AG, default profile Version is 1.2, RFCOMM channel is 12 and Authentication is required. Does not support any Features, option is ignored. :"00001108-0000-1000-8000-00805f9b34fb": HSP HS, default profile Version is 1.2, profile Features is 0b0 and RFCOMM channel is 6. Authentication is required. Features is one bit value, specify capability of Remote Audio Volume Control (by default turned off). :"": Vendor defined UUID, no defaults, must set options. Possible options values: :string Name: Human readable name for the profile :string Service: The primary service class UUID (if different from the actual profile UUID). :string Role: For asymmetric profiles that do not have UUIDs available to uniquely identify each side this parameter allows specifying the precise local role. Possible values: :"client": :"server": :uint16 Channel: RFCOMM channel number that is used for client and server UUIDs. If applicable it will be used in the SDP record as well. :uint16 PSM: PSM number that is used for client and server UUIDs. If applicable it will be used in the SDP record as well. :boolean RequireAuthentication: Pairing is required before connections will be established. No devices will be connected if not paired. :boolean RequireAuthorization: Request authorization before any connection will be established. :boolean AutoConnect: In case of a client UUID this will force connection of the RFCOMM or L2CAP channels when a remote device is connected. :string ServiceRecord: Provide a manual SDP record. :uint16 Version: Profile version (for SDP record) :uint16 Features: Profile features (for SDP record) Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.AlreadyExists: void UnregisterProfile(object profile) `````````````````````````````````````` Unregisters profile object that has been previously registered using **RegisterProfile**. The object path parameter must match the same value that has been used on registration. Possible errors: :org.bluez.Error.DoesNotExist: bluez-5.87/doc/PaxHeaders/bluetoothctl-monitor.10000644000000000000000000000005015221755447016713 xustar0020 atime=1783094055 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-monitor.10000644000000000000000000001511715221755447016401 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "bluetoothctl-monitor" "1" "July 2023" "BlueZ" "Linux System Administration" .SH Name bluetoothctl-monitor \- Monitor Submenu .SH SYNOPSIS .sp \fBbluetoothctl\fP [\-\-options] [monitor.commands] .sp This submenu configures advertisement monitors using the \fBorg.bluez.AdvertisementMonitor(5)\fP and \fBorg.bluez.AdvertisementMonitorManager(5)\fP interfaces. .SH Monitor Commands .SS set\-rssi\-threshold .sp Set RSSI threshold parameter .INDENT 0.0 .TP .B Usage \fB> set\-rssi\-threshold \fP .TP .B Lower RSSI threshold value in dBm for monitoring .TP .B Higher RSSI threshold value in dBm for monitoring .TP .B Example Set low threshold to \-80 dBm and high threshold to \-40 dBm .nf \fB> set\-rssi\-threshold \-80 \-40\fP .fi .sp .TP .B Example Set low threshold to \-70 dBm and high threshold to \-30 dBm .nf \fB> set\-rssi\-threshold \-70 \-30\fP .fi .sp .TP .B Example Set very sensitive low threshold and moderate high threshold .nf \fB> set\-rssi\-threshold \-90 \-50\fP .fi .sp .TP .B Example Set less sensitive thresholds for close range monitoring .nf \fB> set\-rssi\-threshold \-60 \-20\fP .fi .sp .UNINDENT .SS set\-rssi\-timeout .sp Set RSSI timeout parameter .INDENT 0.0 .TP .B Usage \fB> set\-rssi\-timeout \fP .TP .B Timeout value in seconds for low RSSI threshold .TP .B Timeout value in seconds for high RSSI threshold .TP .B Example Set low timeout to 5 seconds, high timeout to 10 seconds .nf \fB> set\-rssi\-timeout 5 10\fP .fi .sp .TP .B Example Set quick timeout response for both thresholds .nf \fB> set\-rssi\-timeout 1 5\fP .fi .sp .TP .B Example Set longer timeout periods for stable monitoring .nf \fB> set\-rssi\-timeout 10 30\fP .fi .sp .TP .B Example Set asymmetric timeout values .nf \fB> set\-rssi\-timeout 3 15\fP .fi .sp .UNINDENT .SS set\-rssi\-sampling\-period .sp Set RSSI sampling period parameter .INDENT 0.0 .TP .B Usage \fB> set\-rssi\-sampling\-period \fP .TP .B Sampling period in 100ms units (0\-255, where 0 means report all, 255 means report only first) .TP .B Example Set sampling period to 100ms (1 unit) .nf \fB> set\-rssi\-sampling\-period 1\fP .fi .sp .TP .B Example Set sampling period to 500ms (5 units) .nf \fB> set\-rssi\-sampling\-period 5\fP .fi .sp .TP .B Example Set sampling period to 1 second (10 units) .nf \fB> set\-rssi\-sampling\-period 10\fP .fi .sp .TP .B Example Report all advertisements (no sampling) .nf \fB> set\-rssi\-sampling\-period 0\fP .fi .sp .TP .B Example Report only first advertisement .nf \fB> set\-rssi\-sampling\-period 255\fP .fi .sp .UNINDENT .SS add\-or\-pattern .sp Register \(aqor pattern\(aq type monitor with the specified RSSI parameters .sp Each pattern requires 3 arguments: Multiple patterns can be specified to create an OR condition. .INDENT 0.0 .TP .B Usage \fB> add\-or\-pattern [start_pos ad_type content ...]\fP .TP .B Uses \fBorg.bluez.AdvertisementMonitorManager(5)\fP method \fBRegisterMonitor\fP .TP .B Byte position in advertisement data where pattern matching starts (0\-based) .TP .B Advertisement data type (e.g., 9 for Complete Local Name, 1 for Flags) .TP .B Hexadecimal pattern content to match .TP .B Example Monitor for \(dqSamsung\(dq in Complete Local Name (type 9) .nf \fB> add\-or\-pattern 0 9 53616d73756e67\fP .fi .sp .TP .B Example Monitor for devices with Flags type (type 1) containing 0x06 .nf \fB> add\-or\-pattern 0 1 06\fP .fi .sp .TP .B Example Monitor for two different patterns (OR condition) .nf \fB> add\-or\-pattern 0 9 53616d73756e67 0 9 4170706c65\fP .fi .sp .TP .B Example Monitor for specific manufacturer data (type 255) .nf \fB> add\-or\-pattern 0 255 4c000215\fP .fi .sp .UNINDENT .SS get\-pattern .sp Get advertisement monitor .INDENT 0.0 .TP .B Usage \fB> get\-pattern \fP .TP .B Monitor ID number to retrieve details for, or \(dqall\(dq for all active monitors .TP .B Example Get details of monitor ID 0 .nf \fB> get\-pattern 0\fP .fi .sp .TP .B Example Get details of monitor ID 1 .nf \fB> get\-pattern 1\fP .fi .sp .TP .B Example Get details of monitor ID 5 .nf \fB> get\-pattern 5\fP .fi .sp .TP .B Example Get details of all active monitors .nf \fB> get\-pattern all\fP .fi .sp .UNINDENT .SS remove\-pattern .sp Remove advertisement monitor .INDENT 0.0 .TP .B Usage \fB> remove\-pattern \fP .TP .B Uses \fBorg.bluez.AdvertisementMonitorManager(5)\fP method \fBUnregisterMonitor\fP .TP .B Monitor ID number to remove, or \(dqall\(dq to remove all active monitors .TP .B Example Remove monitor with ID 0 .nf \fB> remove\-pattern 0\fP .fi .sp .TP .B Example Remove monitor with ID 1 .nf \fB> remove\-pattern 1\fP .fi .sp .TP .B Example Remove monitor with ID 3 .nf \fB> remove\-pattern 3\fP .fi .sp .TP .B Example Remove all active advertisement monitors .nf \fB> remove\-pattern all\fP .fi .sp .UNINDENT .SS get\-supported\-info .sp Get advertisement manager supported features and supported monitor types .INDENT 0.0 .TP .B Usage \fB> get\-supported\-info\fP .TP .B Uses \fBorg.bluez.AdvertisementMonitorManager(5)\fP properties .TP .B Example Display supported monitor features and types .nf \fB> get\-supported\-info\fP .fi .sp .UNINDENT .SS print\-usage .sp Print the command usage .INDENT 0.0 .TP .B Usage \fB> print\-usage \fP .TP .B Command name to display detailed usage information for .TP .B Example Show detailed usage for add\-or\-pattern command .nf \fB> print\-usage add\-or\-pattern\fP .fi .sp .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/bluetoothctl-telephony.10000644000000000000000000000005015221755473017232 xustar0020 atime=1783094075 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-telephony.10000644000000000000000000001333715221755473016722 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "bluetoothctl-telephony" "1" "May 2025" "BlueZ" "Linux System Administration" .SH Name bluetoothctl-telephony \- Telephony Submenu .SH SYNOPSIS .sp \fBbluetoothctl\fP [\-\-options] [telephony.commands] .sp This submenu manages telephony features using the \fBorg.bluez.Telephony(5)\fP and \fBorg.bluez.Call(5)\fP interfaces. .SH Telephony Commands .SS list .sp List available audio gateways. .INDENT 0.0 .TP .B Usage \fB> list\fP .TP .B Example Display all available HFP audio gateways .nf \fB> list\fP .fi .sp .UNINDENT .SS show .sp Show audio gateway information. .INDENT 0.0 .TP .B Usage \fB> show [telephony]\fP .TP .B Uses \fBorg.bluez.Telephony(5)\fP properties .TP .B [telephony] Audio gateway device path (optional, shows current if omitted) .TP .B Example Show information for currently selected audio gateway .nf \fB> show\fP .fi .sp .TP .B Example Show information for specific audio gateway device .nf \fB> show /org/bluez/hci0/dev_00_11_22_33_44_55\fP .fi .sp .TP .B Example Show information for another audio gateway .nf \fB> show /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF\fP .fi .sp .UNINDENT .SS select .sp Select default audio gateway. .INDENT 0.0 .TP .B Usage \fB> select \fP .TP .B Audio gateway device path to set as default .TP .B Example Select specific audio gateway as default .nf \fB> select /org/bluez/hci0/dev_00_11_22_33_44_55\fP .fi .sp .TP .B Example Select different audio gateway as default .nf \fB> select /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF\fP .fi .sp .UNINDENT .SS dial .sp Dial number. .INDENT 0.0 .TP .B Usage \fB> dial [telephony]\fP .TP .B Uses \fBorg.bluez.Telephony(5)\fP method \fBDial\fP .TP .B Phone number to dial .TP .B [telephony] Audio gateway device path (optional, uses current if omitted) .TP .B Example Dial number using currently selected audio gateway .nf \fB> dial 555\-1234\fP .fi .sp .TP .B Example Dial international number using current audio gateway .nf \fB> dial +1\-555\-123\-4567\fP .fi .sp .TP .B Example Dial emergency number using current audio gateway .nf \fB> dial 911\fP .fi .sp .TP .B Example Dial number using specific audio gateway .nf \fB> dial 555\-1234 /org/bluez/hci0/dev_00_11_22_33_44_55\fP .fi .sp .TP .B Example Dial UK number using specific audio gateway .nf \fB> dial +44\-20\-7946\-0958 /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF\fP .fi .sp .TP .B Example Dial with caller ID blocking .nf \fB> dial *67\-555\-1234\fP .fi .sp .TP .B Example Dial with caller ID hiding (GSM format) .nf \fB> dial #31#555\-1234\fP .fi .sp .UNINDENT .SS hangup\-all .sp Hangup all calls. .INDENT 0.0 .TP .B Usage \fB> hangup\-all [telephony]\fP .TP .B Uses \fBorg.bluez.Telephony(5)\fP method \fBHangupAll\fP .TP .B [telephony] Audio gateway device path (optional, uses current if omitted) .TP .B Example Terminate all active and waiting calls on current gateway .nf \fB> hangup\-all\fP .fi .sp .TP .B Example Terminate all calls on specific audio gateway .nf \fB> hangup\-all /org/bluez/hci0/dev_00_11_22_33_44_55\fP .fi .sp .UNINDENT .SS list\-calls .sp List available calls. .INDENT 0.0 .TP .B Usage \fB> list\-calls\fP .TP .B Example Display all active, waiting, and held calls .nf \fB> list\-calls\fP .fi .sp .UNINDENT .SS show\-call .sp Show call information. .INDENT 0.0 .TP .B Usage \fB> show\-call \fP .TP .B Uses \fBorg.bluez.Call(5)\fP properties .TP .B Call object path to display information for .TP .B Example Show information for specific call .nf \fB> show\-call /org/bluez/hci0/dev_00_11_22_33_44_55/call1\fP .fi .sp .TP .B Example Show information for another call .nf \fB> show\-call /org/bluez/hci0/dev_00_11_22_33_44_55/call2\fP .fi .sp .TP .B Example Show call information from different device .nf \fB> show\-call /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/call1\fP .fi .sp .UNINDENT .SS answer .sp Answer call. .INDENT 0.0 .TP .B Usage \fB> answer \fP .TP .B Uses \fBorg.bluez.Call(5)\fP method \fBAnswer\fP .TP .B Call object path to answer .TP .B Example Answer incoming call .nf \fB> answer /org/bluez/hci0/dev_00_11_22_33_44_55/call1\fP .fi .sp .TP .B Example Answer waiting call .nf \fB> answer /org/bluez/hci0/dev_00_11_22_33_44_55/call2\fP .fi .sp .TP .B Example Answer call from different device .nf \fB> answer /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/call1\fP .fi .sp .UNINDENT .SS hangup .sp Hangup call. .INDENT 0.0 .TP .B Usage \fB> hangup \fP .TP .B Uses \fBorg.bluez.Call(5)\fP method \fBHangup\fP .TP .B Call object path to terminate .TP .B Example Terminate specific active call .nf \fB> hangup /org/bluez/hci0/dev_00_11_22_33_44_55/call1\fP .fi .sp .TP .B Example Terminate waiting or held call .nf \fB> hangup /org/bluez/hci0/dev_00_11_22_33_44_55/call2\fP .fi .sp .TP .B Example Terminate call from different device .nf \fB> hangup /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/call1\fP .fi .sp .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.Bearer.BREDR.rst0000644000000000000000000000005015063045254017121 xustar0020 atime=1783093944 20 ctime=1783094111 bluez-5.87/doc/org.bluez.Bearer.BREDR.rst0000644000000000000000000000731315063045254016606 0ustar00rootroot====================== org.bluez.Bearer.BREDR ====================== ------------------------------------------ BlueZ D-Bus Bearer BREDR API documentation ------------------------------------------ :Version: BlueZ :Date: July 2025 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.Bearer.BREDR1 :Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} Methods ------- void Connect() [experimental] ````````````````````````````` Connects all BREDR profiles the remote device supports that can be connected to and have been flagged as auto-connectable. If only subset of profiles is already connected it will try to connect currently disconnected ones. If at least one profile was connected successfully this method will indicate success. Possible errors: :org.bluez.Error.NotReady: :org.bluez.Error.Failed: :org.bluez.Error.InProgress: :org.bluez.Error.AlreadyConnected: void Disconnect() [experimental] ```````````````````````````````` Disconnects all connected profiles and then terminates low-level ACL connection. ACL connection will be terminated even if some profiles were not disconnected properly e.g. due to misbehaving device. This method can be also used to cancel a preceding Connect call before a reply to it has been received. For non-trusted devices connected calling this method will disable incoming connections until Connect method is called again. Possible errors: :org.bluez.Error.NotConnected: Signals ------- void Disconnected(string reason, string message) [experimental] ``````````````````````````````````````````````````````````````` This signal is launched when a device is disconnected, with the reason of the disconnection. This could be used by client application, depending on internal policy, to try to reconnect to the device in case of timeout or unknown disconnection, or to try to connect to another device. Possible reasons: :org.bluez.Reason.Unknown: :org.bluez.Reason.Timeout: Connection timeout. The link supervision timeout has expired for a connection or the synchronization timeout has expired for a broadcast. :org.bluez.Reason.Local: Connection terminated by local host. The local device terminated the connection, terminated synchronization with a broadcaster, or terminated broadcasting packets. :org.bluez.Reason.Remote: Connection terminated by remote host. This disconnection can be due to: - the user on the remote device either terminated the connection or stopped broadcasting packets, - the remote device terminated the connection because of low resources, - the remote device terminated the connection because the device is about to power off. :org.bluez.Reason.Authentication: Connection terminated due to an authentication failure. :org.bluez.Reason.Suspend: Connection terminated by local host for suspend. Properties ---------- object Adapter [readonly, experimental] ``````````````````````````````````````` The object path of the adapter the set belongs to. boolean Paired [readonly, experimental] ``````````````````````````````````````` Indicates if the remote device is paired to BREDR bearer. Paired means the pairing process where devices exchange the information to establish an encrypted connection has been completed. boolean Bonded [readonly, experimental] ``````````````````````````````````````` Indicates if the remote device is bonded to BREDR bearer. Bonded means the information exchanged on pairing process has been stored and will be persisted. boolean Connected [readonly, experimental] `````````````````````````````````````````` Indicates if the remote device is currently connected to BREDR bearer. A PropertiesChanged signal indicate changes to this status. bluez-5.87/doc/PaxHeaders/org.bluez.Agent.rst0000644000000000000000000000005015063045254016122 xustar0020 atime=1783093950 20 ctime=1783094111 bluez-5.87/doc/org.bluez.Agent.rst0000644000000000000000000001012115063045254015576 0ustar00rootroot=============== org.bluez.Agent =============== ----------------------------------- BlueZ D-Bus Agent API documentation ----------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: unique name :Interface: org.bluez.Agent1 :Object path: freely definable Methods ------- void Release() `````````````` This method gets called when **bluetoothd(8)** unregisters the agent. An agent can use it to do cleanup tasks. There is no need to unregister the agent, because when this method gets called it has already been unregistered. string RequestPinCode(object device) ```````````````````````````````````` This method gets called when **bluetoothd(8)** needs to get the passkey for an authentication. The return value should be a string of 1-16 characters length. The string can be alphanumeric. Possible errors: :org.bluez.Error.Rejected: :org.bluez.Error.Canceled: void DisplayPinCode(object device, string pincode) `````````````````````````````````````````````````` This method gets called when **bluetoothd(8)** needs to display a pincode for an authentication. An empty reply should be returned. When the pincode needs no longer to be displayed, the Cancel method of the agent will be called. This is used during the pairing process of keyboards that don't support Bluetooth 2.1 Secure Simple Pairing, in contrast to DisplayPasskey which is used for those that do. This method will only ever be called once since older keyboards do not support typing notification. Note that the PIN will always be a 6-digit number, zero-padded to 6 digits. This is for harmony with the later specification. Possible errors: :org.bluez.Error.Rejected: :org.bluez.Error.Canceled: uint32 RequestPasskey(object device) ```````````````````````````````````` This method gets called when **bluetoothd(8)** needs to get the passkey for an authentication. The return value should be a numeric value between 0-999999. Possible errors: :org.bluez.Error.Rejected: :org.bluez.Error.Canceled: void DisplayPasskey(object device, uint32 passkey, uint16 entered) `````````````````````````````````````````````````````````````````` This method gets called when **bluetoothd(8)** needs to display a passkey for an authentication. The entered parameter indicates the number of already typed keys on the remote side. An empty reply should be returned. When the passkey needs no longer to be displayed, the Cancel method of the agent will be called. During the pairing process this method might be called multiple times to update the entered value. Note that the passkey will always be a 6-digit number, so the display should be zero-padded at the start if the value contains less than 6 digits. void RequestConfirmation(object device, uint32 passkey) ``````````````````````````````````````````````````````` This method gets called when **bluetoothd(8)** needs to confirm a passkey for an authentication. To confirm the value it should return an empty reply or an error in case the passkey is invalid. Note that the passkey will always be a 6-digit number, so the display should be zero-padded at the start if the value contains less than 6 digits. Possible errors: :org.bluez.Error.Rejected: :org.bluez.Error.Canceled: void RequestAuthorization(object device) ```````````````````````````````````````` This method gets called to request the user to authorize an incoming pairing attempt which would in other circumstances trigger the just-works model, or when the user plugged in a device that implements cable pairing. In the latter case, the device would not be connected to the adapter via Bluetooth yet. Possible errors: :org.bluez.Error.Rejected: :org.bluez.Error.Canceled: void AuthorizeService(object device, string uuid) ````````````````````````````````````````````````` This method gets called when **bluetoothd(8)** needs to authorize a connection/service request. Possible errors: :org.bluez.Error.Rejected: :org.bluez.Error.Canceled: void Cancel() ````````````` This method gets called to indicate that the agent request failed before a reply was returned. bluez-5.87/doc/PaxHeaders/org.bluez.Adapter.rst0000644000000000000000000000005015142177620016445 xustar0020 atime=1783093938 20 ctime=1783094111 bluez-5.87/doc/org.bluez.Adapter.rst0000644000000000000000000003006515142177620016132 0ustar00rootroot================= org.bluez.Adapter ================= ------------------------------------- BlueZ D-Bus Adapter API documentation ------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.Adapter1 :Object path: [variable prefix]/{hci0,hci1,...} :Used by: **bluetoothctl(1)**, **bluetoothctl-scan(1)** Methods ------- void StartDiscovery() ````````````````````` Starts device discovery session which may include starting an inquiry and/or scanning procedures and remote device name resolving. Use **StopDiscovery** to release the sessions acquired. This process will start creating Device objects as new devices are discovered. During discovery RSSI delta-threshold is imposed. Each client can request a single device discovery session per adapter. Possible errors: :org.bluez.Error.NotReady: :org.bluez.Error.Failed: :org.bluez.Error.InProgress: Examples: :bluetoothctl: > scan void StopDiscovery() ```````````````````` Stops device discovery session started by **StartDiscovery**. Note that a discovery procedure is shared between all discovery sessions thus calling StopDiscovery will only release a single session and discovery will stop when all sessions from all clients have finished. Possible errors: :org.bluez.Error.NotReady: :org.bluez.Error.Failed: :org.bluez.Error.NotAuthorized: void RemoveDevice(object device) ```````````````````````````````` Removes the remote device object at the given path including cached information such as bonding information. Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.Failed: Examples: :bluetoothctl: > remove void SetDiscoveryFilter(dict filter) ```````````````````````````````````` Sets the device discovery filter for the caller. When this method is called with no filter parameter, filter is removed. Possible filter values: :array{string} UUIDs: Filter by service UUIDs, empty means match *any* UUID. When a remote device is found that advertises any UUID from UUIDs, it will be reported if: - **Pathloss** and **RSSI** are both empty. - only **Pathloss** param is set, device advertise TX power, and computed pathloss is less than Pathloss param. - only **RSSI** param is set, and received RSSI is higher than RSSI param. :int16 RSSI: RSSI threshold value. PropertiesChanged signals will be emitted for already existing Device objects, with updated RSSI value. If one or more discovery filters have been set, the RSSI delta-threshold, that is imposed by StartDiscovery by default, will not be applied. :uint16 Pathloss: Pathloss threshold value. PropertiesChanged signals will be emitted for already existing Device objects, with updated Pathloss value. :string Transport (Default "auto"): Transport parameter determines the type of scan. Possible values: :"auto": Interleaved scan, use LE, BREDR, or both, depending on what's currently enabled. :"bredr": BR/EDR inquiry only. :"le": LE scan only. :bool DuplicateData (Default false): Disables duplicate detection of advertisement data. When enabled PropertiesChanged signals will be generated for either ManufacturerData and ServiceData every time they are discovered. :bool Discoverable (Default false): Make adapter discoverable while discovering, if the adapter is already discoverable setting this filter won't do anything. :string Pattern (Default none): Discover devices where the pattern matches either the prefix of the address or device name which is convenient way to limited the number of device objects created during a discovery. When set disregards device discoverable flags. Note: The pattern matching is ignored if there are other client that don't set any pattern as it work as a logical OR, also setting empty string "" pattern will match any device found. :bool AutoConnect (Default false): Connect to discovered devices automatically if a Pattern has been set and it matches the device address or name and it is connectable. When discovery filter is set, Device objects will be created as new devices with matching criteria are discovered regardless of they are connectable or discoverable which enables listening to non-connectable and non-discoverable devices. When multiple clients call SetDiscoveryFilter, their filters are internally merged, and notifications about new devices are sent to all clients. Therefore, each client must check that device updates actually match its filter. When SetDiscoveryFilter is called multiple times by the same client, last filter passed will be active for given client. SetDiscoveryFilter can be called before StartDiscovery. It is useful when client will create first discovery session, to ensure that proper scan will be started right after call to StartDiscovery. Possible errors: :org.bluez.Error.NotReady: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: Examples: :bluetoothctl: [scan] > uuids [all/uuid1 uuid2 ...] :bluetoothctl: [scan] > rssi [rssi] :bluetoothctl: [scan] > pathloss [pathloss] :bluetoothctl: [scan] > transport [auto/bredr/le] :bluetoothctl: [scan] > duplicate-data [on/off] :bluetoothctl: [scan] > discoverable [on/off] :bluetoothctl: [scan] > pattern [value] :bluetoothctl: [scan] > clear [filter] array{string} GetDiscoveryFilters() ``````````````````````````````````` Returns available filters that can be given to **SetDiscoveryFilter**. Possible errors: None object ConnectDevice(dict properties) [experimental] ```````````````````````````````````````````````````` Connects to device without need of performing General Discovery. Connection mechanism is similar to Connect method on **org.bluez.Device1(5)** interface with exception that this method returns success when physical connection is established and you can specify bearer to connect with parameter. After this method returns, services discovery will continue and any supported profile will be connected. There is no need for calling Connect on Device1 after this call. If connection was successful this method returns object path to created device object or device that already exist. Possible properties values: :string Address (Mandatory): The Bluetooth device address of the remote device. :string AddressType (Default "BR/EDR"): The Bluetooth device Address Type. This is address type that should be used for initial connection. Possible values: :"public": Public address :"random": Random address Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.AlreadyExists: :org.bluez.Error.NotSupported: :org.bluez.Error.NotReady: :org.bluez.Error.Failed: Properties ---------- string Address [readonly] ````````````````````````` The Bluetooth device address. string AddressType [readonly] ````````````````````````````` The Bluetooth Address Type. For dual-mode and BR/EDR only adapter this defaults to "public". Single mode LE adapters may have either value. With privacy enabled this contains type of Identity Address and not type of address used for connection. Possible values: :"public": Public address. :"random": Random address. string Name [readonly] `````````````````````` The Bluetooth system name (pretty hostname). This property is either a static system default or controlled by an external daemon providing access to the pretty hostname configuration. string Alias [readwrite] ```````````````````````` The Bluetooth friendly name. This value can be changed. In case no alias is set, it will return the system provided name. Setting an empty string as alias will convert it back to the system provided name. When resetting the alias with an empty string, the property will default back to system name. On a well configured system, this property never needs to be changed since it defaults to the system name and provides the pretty hostname. Only if the local name needs to be different from the pretty hostname, this property should be used as last resort. Examples: :bluetoothctl: > system-alias :bluetoothctl: > reset-alias uint32 Class [readonly] ``````````````````````` The Bluetooth class of device. This property represents the value that is either automatically configured by DMI/ACPI information or provided as static configuration. boolean Connectable [readwrite] ``````````````````````````````` Set an adapter to connectable or non-connectable. This is a global setting and should only be used by the settings application. Setting this property to false will set the Discoverable property of the adapter to false as well, which will not be reverted if Connectable is set back to true. If required, the application will need to manually set Discoverable to true. Note that this property only affects incoming connections. boolean Powered [readwrite] ``````````````````````````` Switch an adapter on or off. This will also set the appropriate connectable state of the controller. The value of this property is not persistent. After restart or unplugging of the adapter it will reset back to false. Examples: :bluetoothctl: > power string PowerState [readonly, experimental] `````````````````````````````````````````` The power state of an adapter. The power state will show whether the adapter is turning off, or turning on, as well as being on or off. Possible values: :"on": Powered on. :"off": Powered off :"off-enabling": Transitioning from "off" to "on". :"on-disabling": Transitioning from "on" to "off". :"off-blocked": Blocked by rfkill. boolean Discoverable [readwrite] (Default: false) ````````````````````````````````````````````````` Switch an adapter to discoverable or non-discoverable to either make it visible or hide it. This is a global setting and should only be used by the settings application. If the DiscoverableTimeout is set to a non-zero value then the system will set this value back to false after the timer expired. In case the adapter is switched off, setting this value will fail. When changing the Powered property the new state of this property will be updated via a PropertiesChanged signal. Examples: :bluetoothctl: > discoverable boolean Pairable [readwrite] (Default: true) ```````````````````````````````````````````` Switch an adapter to pairable or non-pairable. This is a global setting and should only be used by the settings application. Note that this property only affects incoming pairing requests. Examples: :bluetoothctl: > pairable uint32 PairableTimeout [readwrite] (Default: 0) ``````````````````````````````````````````````` The pairable timeout in seconds. A value of zero means that the timeout is disabled and it will stay in pairable mode forever. uint32 DiscoverableTimeout [readwrite] (Default: 180) ````````````````````````````````````````````````````` The discoverable timeout in seconds. A value of zero means that the timeout is disabled and it will stay in discoverable/limited mode forever. Examples: :bluetoothctl: > discoverable-timeout [value] boolean Discovering [readonly] `````````````````````````````` Indicates that a device discovery procedure is active. array{string} UUIDs [readonly] `````````````````````````````` List of 128-bit UUIDs that represents the available local services. string Modalias [readonly, optional] ```````````````````````````````````` Local Device ID information in modalias format used by the kernel and udev. array{string} Roles [readonly] `````````````````````````````` List of supported roles. Possible values: :"central": Supports the central role. :"peripheral": Supports the peripheral role. :"central-peripheral": Supports both roles concurrently. array{string} ExperimentalFeatures [readonly, optional] ``````````````````````````````````````````````````````` List of 128-bit UUIDs that represents the experimental features currently enabled. uint16 Manufacturer [readonly] `````````````````````````````` The manufacturer of the device, as a uint16 company identifier defined by the Core Bluetooth Specification. byte Version [readonly] ``````````````````````` The Bluetooth version supported by the device, as a core version code defined by the Core Bluetooth Specification. bluez-5.87/doc/PaxHeaders/bluetoothctl-hci.rst0000644000000000000000000000005015142177620016430 xustar0020 atime=1783094073 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-hci.rst0000644000000000000000000001146315142177620016116 0ustar00rootroot================ bluetoothctl-hci ================ ----------- HCI Submenu ----------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: December 2024 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **bluetoothctl** [--options] [hci.commands] .. note:: This submenu communicates directly with the Bluetooth controller via HCI socket, bypassing the BlueZ D-Bus API. Commands use the Host Controller Interface (HCI) protocol. Commands ======== open ---- Open HCI channel. :Usage: **> open ** :index: HCI controller index number (e.g., 0, 1, 2) :chan: Channel type (raw or user) :Example Open user channel (controller needs to be powered off first): | **> power off** | **> hci.open 0 user** | **HCI index 0 user channel opened** :Example Open user channel for HCI controller 0: | **> hci.open 0 user** :Example Open user channel for HCI controller 1: | **> hci.open 1 user** :Example Open user channel for HCI controller 2: | **> hci.open 2 user** :Example Open raw channel for HCI controller 0: | **> hci.open 0 raw** :Example Open raw channel for HCI controller 1: | **> hci.open 1 raw** cmd --- Send HCI command. :Usage: **> cmd [parameters...]** :opcode: HCI command opcode in hexadecimal format (e.g., 0x0c03) :parameters: Optional command parameters as hexadecimal bytes :Example Send HCI Reset command: | **> hci.cmd 0x0c03** | **HCI Command complete:** | **00** :Example Send HCI Reset command (no parameters): | **> hci.cmd 0x0c03** :Example Send HCI Read Local Version Information: | **> hci.cmd 0x1003** :Example Send HCI Read BD Address: | **> hci.cmd 0x1009** :Example Send HCI Set Event Filter with parameter 0x01: | **> hci.cmd 0x0c01 0x01** :Example Send LE Set Advertising Parameters with 2 parameters: | **> hci.cmd 0x200f 0x00 0x08** :Example Send LE Set Advertising Data with length and data bytes: | **> hci.cmd 0x2008 0x20 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08** :Example Send HCI Set Event Mask command: | **> hci.cmd 0x0c05 0x02** :Example Send HCI Write Scan Enable: | **> hci.cmd 0x0c2a 0x01** :Example Send HCI Write Class of Device: | **> hci.cmd 0x0c23 0x00 0x08** send ---- Send HCI data packet. :Usage: **> send [data...]** :type: Packet type (acl, sco, or iso) :handle: Connection handle in hexadecimal format (e.g., 0x0000) :data: Optional data bytes in hexadecimal format :Example Send ACL data packet to connection handle 0x0000: | **> hci.send acl 0x0000** :Example Send empty ACL packet to handle 0x0000: | **> hci.send acl 0x0000** :Example Send ACL packet with "Hello" data to handle 0x0001: | **> hci.send acl 0x0001 0x48 0x65 0x6c 0x6c 0x6f** :Example Send ACL packet with custom data to handle 0x0010: | **> hci.send acl 0x0010 0x01 0x02 0x03 0x04** :Example Send empty SCO packet to handle 0x0000: | **> hci.send sco 0x0000** :Example Send SCO packet with audio data: | **> hci.send sco 0x0001 0xaa 0xbb 0xcc** :Example Send SCO packet with sine wave pattern: | **> hci.send sco 0x0002 0x80 0x00 0x80 0x00** :Example Send empty ISO packet to handle 0x0000: | **> hci.send iso 0x0000** :Example Send ISO packet with sample data: | **> hci.send iso 0x0001 0x12 0x34 0x56 0x78** :Example Send ISO packet for LE Audio stream: | **> hci.send iso 0x0020 0xa0 0xa1 0xa2 0xa3** register -------- Register HCI event handler. :Usage: **> register ** :event: HCI event code in hexadecimal format (e.g., 0x0e) :Example Register handler for Command Complete event: | **> register 0x0e** :Example Register handler for Command Status event: | **> register 0x0f** :Example Register handler for Connection Complete event: | **> register 0x03** :Example Register handler for Disconnection Complete event: | **> register 0x05** :Example Register handler for LE Meta events: | **> register 0x3e** :Example Register handler for Inquiry Complete event: | **> register 0x02** :Example Register handler for Role Change event: | **> register 0x22** unregister ---------- Unregister HCI event handler. :Usage: **> unregister ** :event: HCI event code in hexadecimal format (e.g., 0x0e) :Example Unregister Command Complete event handler: | **> unregister 0x0e** :Example Unregister Command Status event handler: | **> unregister 0x0f** :Example Unregister Connection Complete event handler: | **> unregister 0x03** :Example Unregister Disconnection Complete event handler: | **> unregister 0x05** :Example Unregister LE Meta event handler: | **> unregister 0x3e** close ----- Close HCI channel. Closes the currently open HCI channel. :Usage: **> close** :Example Close the current HCI channel: | **> close** RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.obex.FileTransfer.rst0000644000000000000000000000005015063045254020404 xustar0020 atime=1783094011 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.FileTransfer.rst0000644000000000000000000000671015063045254020071 0ustar00rootroot=========================== org.bluez.obex.FileTransfer =========================== ----------------------------------------------- BlueZ D-Bus OBEX FileTransfer API documentation ----------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez.obex :Interface: org.bluez.obex.FileTransfer1 :Object path: [Session object path] Methods ------- void ChangeFolder(string folder) ```````````````````````````````` Changes the current folder of the remote device. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: void CreateFolder(string folder) ```````````````````````````````` Creates a new folder in the remote device. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: array{dict} ListFolder() ```````````````````````` Returns a dictionary containing information about the current folder content. Possible return values: :string Name: Object name in UTF-8 format. :string Type: Either "folder" or "file". :uint64 Size: Object size or number of items in folder. :string Permission: Group, owner and other permission. :uint64 Modified: Last change. :uint64 Accessed: Last access. :uint64 Created: Creation date. Possible errors: :org.bluez.obex.Error.Failed: object, dict GetFile(string targetfile, string sourcefile) `````````````````````````````````````````````````````````` Copies the contents of the source file (from remote device) to the target file (on local filesystem). If an empty target file is given, a name will be automatically generated for the temporary file. The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see **org.bluez.obex.Transfer(5)** for the possible list of properties. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: object, dict PutFile(string sourcefile, string targetfile) `````````````````````````````````````````````````````````` Copies the contents of the source file (from local filesystem) to the target file (on remote device). The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see **org.bluez.obex.Transfer(5)** for the possible list of properties. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: void CopyFile(string sourcefile, string targetfile) ``````````````````````````````````````````````````` Copies the contents from source file to target file on the remote device. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: void MoveFile(string sourcefile, string targetfile) ``````````````````````````````````````````````````` Moves a file within the remote device from source file to the target file. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: void Delete(string file) ```````````````````````` Deletes the specified file/folder. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: bluez-5.87/doc/PaxHeaders/btmon-gatt.rst0000644000000000000000000000005015200607060015222 xustar0020 atime=1783092683 20 ctime=1783094112 bluez-5.87/doc/btmon-gatt.rst0000644000000000000000000004033015200607060014703 0ustar00rootroot.. This file is included by btmon.rst. RECONSTRUCTING A GATT DATABASE FROM SNOOP TRACES ================================================= A btsnoop trace contains the complete ATT protocol exchange used by GATT clients and servers to discover each other's services. By reading the discovery requests and responses, it is possible to reconstruct the full GATT database of a remote device -- even without access to the device itself. This section explains the GATT discovery procedure and how each ATT operation appears in ``btmon`` output. Overview of GATT Discovery --------------------------- GATT discovery is a multi-phase process where a client queries the server's attribute database using ATT protocol operations. The phases are: 1. **Primary Service Discovery** -- Find all primary services and their handle ranges. 2. **Secondary Service Discovery** -- Find any secondary (included-only) services. 3. **Included Service Discovery** -- Find which services include other services. 4. **Characteristic Discovery** -- Find all characteristics within each service. 5. **Descriptor Discovery** -- Find all descriptors for each characteristic. 6. **Characteristic Value Reading** -- Read the values of readable characteristics. Each phase uses a specific ATT operation and produces a request/response pattern in the trace. The client repeats each request with advancing handle ranges until the server responds with ``Attribute Not Found``, indicating the end of that phase. Phase 1: Primary Service Discovery (Read By Group Type) -------------------------------------------------------- The client discovers primary services using ``Read By Group Type Request`` with the ``Primary Service`` UUID (0x2800) as the group type. **Request**:: < ACL Data TX: Handle 2048 flags 0x00 dlen 11 #516 [hci0] 0.124726 ATT: Read By Group Type Request (0x10) len 6 Handle range: 0x0001-0xffff Attribute group type: Primary Service (0x2800) The first request always starts at handle 0x0001 and searches through 0xffff (the entire handle space). **Response**:: > ACL Data RX: Handle 2048 flags 0x02 dlen 42 #523 [hci0] 0.240151 ATT: Read By Group Type Response (0x11) len 37 Attribute data length: 6 Attribute group list: 6 entries Handle range: 0x0001-0x0009 UUID: Generic Access Profile (0x1800) Handle range: 0x000a-0x0011 UUID: Generic Attribute Profile (0x1801) Handle range: 0x0012-0x0014 UUID: Device Information (0x180a) Handle range: 0x0015-0x0039 UUID: Generic Telephony Bearer (0x184c) Handle range: 0x003a-0x0059 UUID: Generic Media Control (0x1849) Handle range: 0x005a-0x005c UUID: Telephony and Media Audio (0x1855) Each entry provides: - **Handle range** -- The start and end handle of the service. All attributes belonging to this service (characteristics, descriptors) have handles within this range. - **UUID** -- The service UUID. Standard 16-bit UUIDs are shown with their name (e.g., ``Generic Access Profile``). 128-bit vendor-specific UUIDs appear as full UUID strings. The client continues by sending another request starting after the last handle in the response:: < ACL Data TX: Handle 2048 flags 0x00 dlen 11 #525 [hci0] 0.240641 ATT: Read By Group Type Request (0x10) len 6 Handle range: 0x005d-0xffff Attribute group type: Primary Service (0x2800) This continues until the server responds with ``Attribute Not Found``:: > ACL Data RX: Handle 2048 flags 0x02 dlen 9 #532 [hci0] 0.360069 ATT: Error Response (0x01) len 4 Read By Group Type Request (0x10) Handle: 0x005d Error: Attribute Not Found (0x0a) This error indicates that no more primary services exist beyond handle 0x005d. The client now has the complete list of primary services. .. note:: The ``Attribute data length`` field indicates the size of each entry in the response. A value of 6 means 16-bit UUIDs (2 bytes start handle + 2 bytes end handle + 2 bytes UUID). A value of 20 means 128-bit UUIDs (2 + 2 + 16). If the server has both 16-bit and 128-bit service UUIDs, they are returned in separate responses because all entries in a single response must have the same length. Phase 2: Secondary Service Discovery -------------------------------------- After primary services, the client may discover secondary services using the same ``Read By Group Type Request`` but with the ``Secondary Service`` UUID (0x2801):: < ACL Data TX: Handle 2048 flags 0x00 dlen 11 #534 [hci0] 0.360752 ATT: Read By Group Type Request (0x10) len 6 Handle range: 0x0001-0xffff Attribute group type: Secondary Service (0x2801) If no secondary services exist, the server responds with ``Attribute Not Found``. Secondary services are not directly accessible to clients -- they are only reachable via include references from primary services. Phase 3: Included Service Discovery (Read By Type) ---------------------------------------------------- To discover which services include other services, the client uses ``Read By Type Request`` with the ``Include`` UUID (0x2802):: < ACL Data TX: Handle 2048 flags 0x00 dlen 11 #540 [hci0] 0.480731 ATT: Read By Type Request (0x08) len 6 Handle range: 0x0001-0x005c Attribute type: Include (0x2802) The handle range typically spans the entire discovered database. Each include declaration in the response identifies a service that is included by the service containing that handle. Phase 4: Characteristic Discovery (Read By Type) -------------------------------------------------- For each service, the client discovers its characteristics using ``Read By Type Request`` with the ``Characteristic`` UUID (0x2803). The handle range is limited to the service's handle range. **Request**:: > ACL Data RX: Handle 2048 flags 0x02 dlen 11 #531 [hci0] 0.360063 ATT: Read By Type Request (0x08) len 6 Handle range: 0x0008-0x0011 Attribute type: Characteristic (0x2803) **Response**:: < ACL Data TX: Handle 2048 flags 0x00 dlen 27 #533 [hci0] 0.360714 ATT: Read By Type Response (0x09) len 22 Attribute data length: 7 Attribute data list: 3 entries Handle: 0x0009 Value[5]: 200a00052a Properties: 0x20 Indicate (0x20) Value Handle: 0x000a Value UUID: Service Changed (0x2a05) Handle: 0x000c Value[5]: 0a0d00292b Properties: 0x0a Read (0x02) Write (0x08) Value Handle: 0x000d Value UUID: Client Supported Features (0x2b29) Handle: 0x000e Value[5]: 020f002a2b Properties: 0x02 Read (0x02) Value Handle: 0x000f Value UUID: Database Hash (0x2b2a) Each characteristic entry provides: - **Handle** -- The handle of the characteristic declaration attribute. - **Properties** -- A bitmask indicating supported operations: .. list-table:: :header-rows: 1 :widths: 10 30 60 * - Bit - Property - Description * - 0x01 - Broadcast - Can be broadcast in advertising data * - 0x02 - Read - Can be read * - 0x04 - Write Without Response - Can be written without acknowledgment * - 0x08 - Write - Can be written with acknowledgment * - 0x10 - Notify - Server can send notifications * - 0x20 - Indicate - Server can send indications * - 0x40 - Authenticated Signed Writes - Supports signed write commands * - 0x80 - Extended Properties - Has extended properties descriptor - **Value Handle** -- The handle where the characteristic's value is stored (always declaration handle + 1). - **Value UUID** -- The UUID identifying the characteristic type. The client continues with advancing handle ranges until it receives ``Attribute Not Found``:: > ACL Data RX: Handle 2048 flags 0x02 dlen 9 #572 [hci0] 1.200228 ATT: Error Response (0x01) len 4 Read By Type Request (0x08) Handle: 0x005c Error: Attribute Not Found (0x0a) Phase 5: Descriptor Discovery (Find Information) -------------------------------------------------- Descriptors occupy the handles between a characteristic's value handle and the next characteristic declaration (or end of service). The client discovers them using ``Find Information Request``. **Request**:: > ACL Data RX: Handle 2048 flags 0x02 dlen 9 #556 [hci0] 0.959965 ATT: Find Information Request (0x04) len 4 Handle range: 0x000b-0x000b The handle range covers the gap between the characteristic value handle and the next characteristic declaration handle. **Response**:: < ACL Data TX: Handle 2048 flags 0x00 dlen 10 #561 [hci0] 0.961049 ATT: Find Information Response (0x05) len 5 Format: UUID-16 (0x01) Handle: 0x000b UUID: Client Characteristic Configuration (0x2902) Common descriptor UUIDs: .. list-table:: :header-rows: 1 :widths: 15 40 45 * - UUID - Name - Purpose * - 0x2900 - Characteristic Extended Properties - Additional property bits * - 0x2901 - Characteristic User Description - Human-readable description string * - 0x2902 - Client Characteristic Configuration (CCC) - Enable/disable notifications or indications * - 0x2903 - Server Characteristic Configuration - Server-side broadcast configuration * - 0x2904 - Characteristic Presentation Format - Data format, exponent, unit Phase 6: Reading Characteristic Values ---------------------------------------- After discovery, the client may read characteristic values using ``Read Request``:: > ACL Data RX: Handle 2048 flags 0x02 dlen 7 #577 [hci0] 1.380203 ATT: Read Request (0x0a) len 2 Handle: 0x000f < ACL Data TX: Handle 2048 flags 0x00 dlen 21 #579 [hci0] 1.380774 ATT: Read Response (0x0b) len 16 Value[16]: a470d508da8751a2a50b79da0250bfda The ``Handle`` in the request corresponds to a characteristic value handle from the discovery phase. btmon shows the raw value bytes; the interpretation depends on the characteristic UUID. Find By Type Value (Targeted Service Search) ---------------------------------------------- In addition to discovering all services, a client can search for a specific service UUID using ``Find By Type Value Request``:: > ACL Data RX: Handle 2048 flags 0x02 dlen 13 #513 [hci0] 0.124195 ATT: Find By Type Value Request (0x06) len 8 Handle range: 0x0001-0xffff Attribute type: Primary Service (0x2800) UUID: Generic Attribute Profile (0x1801) < ACL Data TX: Handle 2048 flags 0x00 dlen 9 #515 [hci0] 0.124684 ATT: Find By Type Value Response (0x07) len 4 Handle range: 0x0008-0x0011 This returns only the handle range for the matching service, without iterating through all services. If the service is not found:: < ACL Data TX: Handle 2048 flags 0x00 dlen 9 #524 [hci0] 0.240607 ATT: Error Response (0x01) len 4 Find By Type Value Request (0x06) Handle: 0x0012 Error: Attribute Not Found (0x0a) Bidirectional Discovery ------------------------ Both devices in a connection can act as GATT client and server simultaneously. In a btsnoop trace, you may see interleaved discovery in both directions: - **TX (``<``) requests + RX (``>``) responses** -- The local device (whose trace this is) is acting as a GATT client, discovering the remote device's services. - **RX (``>``) requests + TX (``<``) responses** -- The remote device is acting as a GATT client, discovering the local device's services. For example, the local server responding to the remote's discovery:: > ACL Data RX: Handle 2048 flags 0x02 dlen 11 #584 [hci0] 1.512006 ATT: Read By Group Type Request (0x10) len 6 Handle range: 0x0001-0xffff Attribute group type: Primary Service (0x2800) < ACL Data TX: Handle 2048 flags 0x00 dlen 66 #586 [hci0] 1.518778 ATT: Read By Group Type Response (0x11) len 61 Attribute data length: 6 Attribute group list: 10 entries Handle range: 0x0001-0x0007 UUID: Generic Access Profile (0x1800) Handle range: 0x0008-0x0011 UUID: Generic Attribute Profile (0x1801) Handle range: 0x0012-0x0014 UUID: Device Information (0x180a) Handle range: 0x0015-0x001e UUID: Coordinated Set Identification (0x1846) Handle range: 0x001f-0x0020 UUID: Common Audio (0x1853) Handle range: 0x0021-0x0024 UUID: Microphone Control (0x184d) Handle range: 0x0041-0x004b UUID: Volume Control (0x1844) Handle range: 0x006b-0x0073 UUID: Broadcast Audio Scan (0x184f) Handle range: 0x0074-0x0086 UUID: Published Audio Capabilities (0x1850) Handle range: 0x0087-0x0096 UUID: Audio Stream Control (0x184e) This shows the local device's own GATT database as seen by the remote. To reconstruct the remote device's database, focus on the TX requests and RX responses (the local device acting as client). Building the Attribute Table ----------------------------- To reconstruct the GATT database, extract the discovery responses and organize them into a table. Using the trace above as an example, the remote device at address 00:11:22:33:44:55 has: **Services** (from Read By Group Type Response):: Handle Range UUID Service Name ────────────── ────────────────────────────── ──────────────────────────── 0x0001-0x0009 0x1800 Generic Access Profile 0x000a-0x0011 0x1801 Generic Attribute Profile 0x0012-0x0014 0x180a Device Information 0x0015-0x0039 0x184c Generic Telephony Bearer 0x003a-0x0059 0x1849 Generic Media Control 0x005a-0x005c 0x1855 Telephony and Media Audio **Characteristics** (from Read By Type Response, within GAP 0x0001-0x0009):: Handle Value Handle Properties UUID Name ────── ──────────── ────────── ────── ──────────────────────────────── 0x0002 0x0003 Read 0x2a00 Device Name 0x0004 0x0005 Read 0x2a01 Appearance 0x0006 0x0007 Read 0x2a04 Peripheral Preferred Conn Params 0x0008 0x0009 Read 0x2aa6 Central Address Resolution **Characteristics** (within GATT 0x000a-0x0011):: Handle Value Handle Properties UUID Name ────── ──────────── ─────────────── ────── ──────────────────────────── 0x000b 0x000c Indicate 0x2a05 Service Changed 0x000e 0x000f Read, Write 0x2b29 Client Supported Features 0x0010 0x0011 Read 0x2b2a Database Hash **Descriptors** (from Find Information Response):: Handle UUID Name ────── ────── ──────────────────────────────────── 0x000d 0x2902 Client Characteristic Configuration The CCC descriptor at handle 0x000d belongs to the Service Changed characteristic (0x000c), because it falls between that value handle and the next characteristic declaration at 0x000e. bluez-5.87/doc/PaxHeaders/org.bluez.NetworkServer.rst0000644000000000000000000000005015063045254017704 xustar0020 atime=1783093956 20 ctime=1783094111 bluez-5.87/doc/org.bluez.NetworkServer.rst0000644000000000000000000000272415063045254017372 0ustar00rootroot======================= org.bluez.NetworkServer ======================= ------------------------------------------- BlueZ D-Bus NetworkServer API documentation ------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.NetworkServer1 :Object path: /org/bluez/{hci0,hci1,...} Methods ------- void Register(string uuid, string bridge) ````````````````````````````````````````` Registers server for the provided UUID. Every new connection to this server will be added the bridge interface. Possible uuid values: :"panu", "00001115-0000-1000-8000-00805f9b34fb": Personal Network User role. :"nap", "00001116-0000-1000-8000-00805f9b34fb": Network Access Point role. :"gn", "00001117-0000-1000-8000-00805f9b34fb": Group Network role. Initially no network server SDP is provided. Only after this method a SDP record will be available and the BNEP server will be ready for incoming connections. Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.AlreadyExists: :org.bluez.Error.Failed: void Unregister(string uuid) ```````````````````````````` Unregisters the server for provided UUID which was previously registered with **Register()** method. All servers will be automatically unregistered when the calling application terminates. Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.Failed: bluez-5.87/doc/PaxHeaders/btmon-connections.rst0000644000000000000000000000005015200607060016605 xustar0020 atime=1783092683 20 ctime=1783094112 bluez-5.87/doc/btmon-connections.rst0000644000000000000000000001101515200607060016264 0ustar00rootroot.. This file is included by btmon.rst. CONNECTION TRACKING =================== HCI uses **connection handles** (16-bit integers) to identify individual connections. Understanding how handles map to devices is essential for reading traces. Handle Types ------------ Different connection types use different handle ranges, but these ranges are controller-specific and not standardized. The connection type can be determined by looking at the event that created the handle: .. list-table:: :header-rows: 1 :widths: 15 25 60 * - Type - Creation Event - Description * - BR/EDR ACL - Connection Complete - Classic Bluetooth data connection * - LE ACL - LE (Enhanced) Connection Complete - Low Energy data connection * - CIS - LE CIS Established - Connected Isochronous Stream (LE Audio) * - BIS - LE BIG Complete - Broadcast Isochronous Stream (LE Audio) * - SCO/eSCO - Synchronous Connection Complete - Voice/audio synchronous connection (classic) A single device may have multiple handles simultaneously. For example, an LE Audio device will have an LE ACL handle for control traffic and one or more CIS handles for audio streams. The ``LE CIS Established`` event includes the ACL connection handle that the CIS is associated with. Controller Buffer Tracking -------------------------- Buffer tracking may show a indicator in square brackets:: < ACL: Handle 2048 [1/6] flags 0x00 dlen 16 The ``[1/6]`` means this is buffer slot 1 of 6 available controller ACL buffers. This reflects the host-side HCI flow control: the host tracks how many buffers the controller has available and shows the current usage. When the controller sends ``Number of Completed Packets`` events, buffers are freed and the count decreases. HCI ERROR AND DISCONNECT REASON CODES ====================================== HCI status and disconnect reason codes use the same code space. These appear in ``Status:`` and ``Reason:`` fields throughout the trace. btmon decodes them automatically, but the hex values are useful for searching and filtering. Common Disconnect Reasons ------------------------- .. list-table:: :header-rows: 1 :widths: 8 40 52 * - Code - Name - Diagnostic Meaning * - 0x05 - Authentication Failure - Pairing or encryption setup failed. Key may be stale or devices have mismatched security databases. * - 0x08 - Connection Timeout - The supervision timer expired. The remote device moved out of range or stopped responding. This is an RF link loss. * - 0x13 - Remote User Terminated Connection - The remote device intentionally disconnected. This is the normal graceful disconnect. * - 0x14 - Remote Device Terminated due to Low Resources - The remote device ran out of resources (memory, connection slots). * - 0x15 - Remote Device Terminated due to Power Off - The remote device is powering down. * - 0x16 - Connection Terminated By Local Host - The local BlueZ stack intentionally disconnected. Normal when bluetoothd initiates disconnect. * - 0x1f - Unspecified Error - Generic error. Often indicates a firmware issue. * - 0x22 - LMP/LL Response Timeout - Link layer procedure timed out. The remote device stopped responding to LL control PDUs. * - 0x28 - Instant Passed - A timing-critical operation missed its deadline. Often seen with connection parameter updates. * - 0x2f - Insufficient Security - The required security level (encryption, MITM protection) was not met. * - 0x3b - Unacceptable Connection Parameters - The remote rejected a connection parameter update. * - 0x3d - Connection Terminated due to MIC Failure - Encryption integrity check failed. Possible key mismatch or corruption. * - 0x3e - Connection Failed to be Established - Connection attempt failed entirely (e.g., the remote device did not respond to connection requests). * - 0x3f - MAC Connection Failed - MAC-level connection failure. * - 0x44 - Operation Cancelled by Host - The host cancelled the operation before it completed. Full Error Code Table --------------------- The complete set of HCI error codes (0x00-0x45) is defined in the Bluetooth Core Specification, Volume 1, Part F. btmon decodes all of them automatically in ``Status:`` and ``Reason:`` fields. The source mapping is in ``monitor/packet.c`` (``error2str_table``). bluez-5.87/doc/PaxHeaders/org.bluez.Media.50000644000000000000000000000005015221755323015440 xustar0020 atime=1783093971 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Media.50000644000000000000000000001045415221755323015125 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.Media" "5" "April 2025" "BlueZ" "Linux System Administration" .SH Name org.bluez.Media \- BlueZ D-Bus Media API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.Media1 .TP .B Object path [variable prefix]/{hci0,hci1,...} .TP .B Used by \fBbluetoothctl\-endpoint(1)\fP .UNINDENT .SS Methods .SS void RegisterEndpoint(object endpoint, dict properties) .sp Register a local end point to sender, the sender can register as many end points as it likes. .sp Note: If the sender disconnects the end points are automatically unregistered. .sp possible properties: .INDENT 0.0 .TP .B string UUID UUID of the profile which the endpoint is for. .sp UUID must be in the list of SupportedUUIDS. .TP .B byte Codec Assigned number of codec that the endpoint implements. The values should match the profile specification which is indicated by the UUID. .TP .B uint32_t Vendor [Optional] Vendor\-specific Company ID, Codec ID tuple that the endpoint implements. .sp It shall be set to appropriate value when Vendor Specific Codec (0xff) is used. .TP .B array{byte} Capabilities Capabilities blob, it is used as it is so the size and byte order must match. .TP .B array{byte} Metadata [Optional] Metadata blob, it is used as it is so the size and byte order must match. .UNINDENT .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.NotSupported emitted when interface for the end\-point is disabled .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > endpoint.register [capabilities...] .UNINDENT .SS void UnregisterEndpoint(object endpoint) .sp Unregister sender end point. .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > endpoint.unregister .UNINDENT .SS void RegisterPlayer(object player, dict properties) .sp Register a media player object to sender, the sender can register as many objects as it likes. .sp Object must implement at least \fBorg.mpris.MediaPlayer2.Player\fP as defined in MPRIS 2.2 spec: .sp \% .sp Note: If the sender disconnects its objects are automatically unregistered. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.NotSupported .UNINDENT .SS void UnregisterPlayer(object player) .sp Unregister sender media player. .SS void RegisterApplication(object root, dict options) .sp Register endpoints an player objects within root object which must implement \fBorg.freedesktop.DBus.ObjectManager\fP\&. .sp The application object path together with the D\-Bus system bus connection ID define the identification of the application. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.AlreadyExists .UNINDENT .SS void UnregisterApplication(object application) .sp This unregisters the services that has been previously registered. .sp The object path parameter must match the same value that has been used on registration. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.DoesNotExist .UNINDENT .SS Properties .SS array{string} SupportedUUIDs [readonly] .sp List of 128\-bit UUIDs that represents the supported Endpoint registration. .SS array{string} SupportedFeatures [readonly] .sp List of strings that represent supported special features. .sp Possible values: .INDENT 0.0 .TP .B \(dqtx\-timestamping\(dq Bluetooth TX timestamping in media stream sockets is supported by BlueZ and kernel. Applications may check kernel support for specific timestamp types via SIOCETHTOOL. .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.BatteryProviderManager.rst0000644000000000000000000000005015063045254021504 xustar0020 atime=1783093961 20 ctime=1783094111 bluez-5.87/doc/org.bluez.BatteryProviderManager.rst0000644000000000000000000000316015063045254021165 0ustar00rootroot================================ org.bluez.BatteryProviderManager ================================ ---------------------------------------------------- BlueZ D-Bus BatteryProviderManager API documentation ---------------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Description ============ A battery provider starts by registering itself as a battery provider with the **RegisterBatteryProvider()** method passing an object path as the provider ID. Then, it can start exposing **org.bluez.BatteryProvider(5)** objects having the path starting with the given provider ID. It can also remove objects at any time. The objects and their properties exposed by battery providers will be reflected on **org.bluez.Battery(5)** interface. **bluetoothd(8)** will stop monitoring these exposed and removed objects after UnregisterBatteryProvider is called for that provider ID. Interface ========= :Service: org.bluez :Interface: org.bluez.BatteryProviderManager1 :Object path: /org/bluez/{hci0,hci1,...} Methods ------- void RegisterBatteryProvider(object provider) ````````````````````````````````````````````` Registers a battery provider. A registered battery provider can then expose objects with **org.bluez.BatteryProvider(5)** interface. void UnregisterBatteryProvider(object provider) ``````````````````````````````````````````````` Unregisters a battery provider previously registered with **RegisterBatteryProvider()**. After unregistration, the **org.bluez.BatteryProvider(5)** objects provided by this client are ignored by **bluetoothd(8)**. bluez-5.87/doc/PaxHeaders/org.bluez.MediaAssistant.rst0000644000000000000000000000005015221752714017777 xustar0020 atime=1783093984 20 ctime=1783094111 bluez-5.87/doc/org.bluez.MediaAssistant.rst0000644000000000000000000000402215221752714017456 0ustar00rootroot======================== org.bluez.MediaAssistant ======================== -------------------------------------------- BlueZ D-Bus MediaAssistant API documentation -------------------------------------------- :Version: BlueZ :Date: June 2024 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.MediaAssistant1 :Object path: /org/bluez/{hci0,hci1,...}/dev_{BDADDR}/src_{BDADDR}/sid#/bis# :Used by: **bluetoothctl-assistant(1)** Methods ------- void Push(dict properties) ```````````````````````````````````````````````````````` Send stream information to the remote device. If the assistant is in the "active" state, this method uses BASS_Modify_Source to update the existing source on the remote device instead of adding a new one with BASS_Add_Source. :dict properties: Indicate stream properties that will be sent to the peer. Values: :array{byte} Metadata [ISO only]: See Metadata property. :dict QoS [ISO only]: See QoS property. :object Device [ISO only, State=local only]: Push to a specific device. Device must be connected and with an active BASS session. Examples: :bluetoothctl: > assistant.push Properties ---------- string State [readonly] ``````````````````````` Indicates the state of the assistant object. Possible values are: :"idle": assistant object was created for the stream :"pending": assistant object was pushed (stream information was sent to the peer) :"requesting": remote device requires Broadcast_Code :"active": remote device started receiving stream :"local": assistant object was created for a local stream array{byte} Metadata [readwrite, ISO Only, experimental] ```````````````````````````````````````````````````````` Indicates stream Metadata. dict QoS [readwrite, ISO only, experimental] ```````````````````````````````````````````` Indicates stream QoS capabilities. Values: :byte Encryption: Indicates whether the stream is encrypted. :array{byte} BCode Indicates Broadcast_Code to decrypt stream. bluez-5.87/doc/PaxHeaders/bluetoothctl-gatt.rst0000644000000000000000000000005015142177620016624 xustar0020 atime=1783094063 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-gatt.rst0000644000000000000000000004314215142177620016311 0ustar00rootroot================= bluetoothctl-gatt ================= ------------------------- Generic Attribute Submenu ------------------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: November 2022 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **bluetoothctl** [--options] [gatt.commands] This submenu interacts with GATT services, characteristics, and descriptors using the **org.bluez.GattService(5)**, **org.bluez.GattCharacteristic(5)**, **org.bluez.GattDescriptor(5)**, and **org.bluez.GattManager(5)** interfaces. Generic Attribute Commands ========================== list-attributes --------------- Lists the attributes of either the local device or a remote device, encompassing services, characteristics, and handles. This command provides a comprehensive overview of the available Bluetooth attributes, which can be further interacted with using other commands. :Usage: **> list-attributes [dev/local]** :[dev/local]: Device MAC address, object path, or "local" to list locally registered attributes (optional, lists all if omitted) :Example List attributes for specific service: | **> list-attributes /org/bluez/hci0/dev_00_11_22_33_44_55/service001a** :Example List attributes for specific characteristic: | **> list-attributes /org/bluez/hci0/dev_00_11_22_33_44_55/service001a/char001c** :Example List attributes for service on different device: | **> list-attributes /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/service0020** :Example List all Generic Access service attributes: | **> list-attributes 0x1800** :Example List all Battery Service attributes: | **> list-attributes 0x180F** :Example List all Device Name characteristic attributes: | **> list-attributes 0x2A00** :Example List attributes using full UUID: | **> list-attributes 00001801-0000-1000-8000-00805f9b34fb** :Example List attributes for custom service UUID: | **> list-attributes 12345678-1234-5678-9abc-123456789abc** select-attribute ---------------- Selects a specific attribute on either the local or remote device for subsequent operations. Before you can read or write to an attribute, you must select it with this command. This establishes a context for many other commands (read, write, notify, etc.), specifying the attribute they should operate on. :Usage: **> select-attribute [attribute/UUID]** :: GATT attribute path, UUID, or "local" to select from local attributes :[attribute/UUID]: Additional attribute path or UUID when using "local" (optional) :Example Select specific characteristic: | **> select-attribute /org/bluez/hci0/dev_00_11_22_33_44_55/service001a/char001c** :Example Select specific descriptor: | **> select-attribute /org/bluez/hci0/dev_00_11_22_33_44_55/service0020/char0022/desc0024** :Example Select service on different device: | **> select-attribute /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/service001a** :Example Select Device Name characteristic: | **> select-attribute 0x2A00** :Example Select Battery Level characteristic: | **> select-attribute 0x2A19** :Example Select Client Characteristic Configuration descriptor: | **> select-attribute 0x2902** :Example Select Temperature characteristic using full UUID: | **> select-attribute 00002a6e-0000-1000-8000-00805f9b34fb** :Example Select custom characteristic: | **> select-attribute 12345678-1234-5678-9abc-123456789abc** attribute-info -------------- Displays detailed information about an attribute. If no attribute or UUID is specified, it shows information about the currently selected attribute. This command is useful for understanding the properties and capabilities of an attribute. :Usage: **> attribute-info [attribute/UUID]** :[attribute/UUID]: GATT attribute path or UUID to get info for (optional, uses current if omitted) :Example Show information for currently selected attribute: | **> attribute-info** :Example Show info for specific characteristic: | **> attribute-info /org/bluez/hci0/dev_00_11_22_33_44_55/service001a/char001c** :Example Show info for Battery Level characteristic: | **> attribute-info 0x2A19** :Example Show info for Device Name characteristic: | **> attribute-info 0x2A00** :Example Show info for Temperature characteristic: | **> attribute-info 00002a6e-0000-1000-8000-00805f9b34fb** read ---- Reads the value of an attribute. Requires that an attribute be selected beforehand with select-attribute. The optional offset parameter can be used for attributes that allow partial reads. :Usage: **> read [offset]** :Uses: **org.bluez.GattCharacteristic(5)** method **ReadValue** :[offset]: Byte offset to start reading from (optional, defaults to 0) :Example Read attribute value from offset 0: | **> read** :Example Read attribute value from offset 0 (explicit): | **> read 0** :Example Read attribute value starting from offset 4: | **> read 4** :Example Read attribute value starting from offset 10: | **> read 10** :Example Read attribute value starting from offset 16: | **> read 16** write ----- Writes a value to an attribute. This command necessitates that an attribute be selected in advance using select-attribute. Data must be provided in hexadecimal format. Optional offset and type parameters can accommodate specific write requirements. :Usage: **> write [offset] [type]** :Uses: **org.bluez.GattCharacteristic(5)** method **WriteValue** :: Hexadecimal data bytes to write to the attribute :[offset]: Byte offset to start writing at (optional, defaults to 0) :[type]: Write type - request, command, or reliable (optional, defaults to request) :Example Write "Hello" to attribute (ASCII bytes): | **> write "48 65 6C 6C 6F"** :Example Write binary data to attribute: | **> write "01 02 03 04"** :Example Write single byte value: | **> write "FF"** :Example Write 2 bytes starting at offset 4: | **> write "01 02" 4** :Example Write with write request (default): | **> write "48 65 6C 6C 6F" 0 request** :Example Write with write command (no response): | **> write "01 02 03" 0 command** :Example Write with reliable write: | **> write "FF EE DD" 0 reliable** acquire-write ------------- Acquires a Write file descriptor for a previously selected attribute. This is useful for applications that need a file descriptor to perform write operations. :Usage: **> acquire-write** :Uses: **org.bluez.GattCharacteristic(5)** method **AcquireWrite** :Example Acquire write file descriptor for current attribute: | **> acquire-write** release-write ------------- Releases the Write file descriptor acquired with acquire-write. This command is necessary to clean up resources after you're done with the write operation. :Usage: **> release-write** :Example Release write file descriptor for current attribute: | **> release-write** acquire-notify -------------- Acquires a Notify file descriptor for a previously selected attribute. This enables applications to listen for notifications on attribute value changes. :Usage: **> acquire-notify** :Uses: **org.bluez.GattCharacteristic(5)** method **AcquireNotify** :Example Acquire notify file descriptor for current attribute: | **> acquire-notify** release-notify -------------- Releases the Notify file descriptor obtained with acquire-notify. Ensures resources are freed once notification listening is no longer needed. :Usage: **> release-notify** :Example Release notify file descriptor for current attribute: | **> release-notify** notify ------ Enables or disables notifications for attribute value changes. Before this command can be used, the relevant attribute must be selected. This command allows applications to be notified of attribute changes without polling. :Usage: **> notify ** :Uses: **org.bluez.GattCharacteristic(5)** methods **StartNotify**, **StopNotify** :: Enable or disable notifications for the current attribute :Example Enable notifications for current attribute: | **> notify on** :Example Disable notifications for current attribute: | **> notify off** clone ----- Creates a clone of a device or attribute. This can be useful for creating a backup or working with a copy for testing purposes. :Usage: **> clone [dev/attribute/UUID]** :[dev/attribute/UUID]: Device MAC address, attribute path, or UUID to clone (optional, clones current if omitted) :Example Clone entire device GATT database: | **> clone 00:11:22:33:44:55** :Example Clone another device: | **> clone AA:BB:CC:DD:EE:FF** :Example Clone specific service: | **> clone /org/bluez/hci0/dev_00_11_22_33_44_55/service001a** :Example Clone specific characteristic: | **> clone /org/bluez/hci0/dev_00_11_22_33_44_55/service001a/char001c** :Example Clone Generic Access service: | **> clone 0x1800** :Example Clone Battery Service: | **> clone 0x180F** :Example Clone Battery Level characteristic: | **> clone 0x2A19** register-application -------------------- Registers a new application with the Bluetooth system, allowing for the management of services, characteristics, and descriptors under this application. :Usage: **> register-application [UUID ...]** :Uses: **org.bluez.GattManager(5)** method **RegisterApplication** :[UUID ...]: Optional list of service UUIDs to register with the application :Example Register GATT application without specific UUIDs: | **> register-application** :Example Register application with Battery Service: | **> register-application 0x180F** :Example Register with Generic Access and Battery Service: | **> register-application 0x1800 0x180F** :Example Register application with custom service UUID: | **> register-application 12345678-1234-5678-9abc-123456789abc** :Example Register with multiple standard services: | **> register-application 0x1800 0x180F 0x180A** unregister-application ---------------------- Removes a previously registered application from the Bluetooth system. :Usage: **> unregister-application** :Uses: **org.bluez.GattManager(5)** method **UnregisterApplication** :Example Unregister current GATT application: | **> unregister-application** register-service ---------------- Adds a new service under a registered application. This command is crucial for defining new services that devices can offer. :Usage: **> register-service [handle]** :: Service UUID to register (16-bit, 32-bit, or 128-bit format) :[handle]: Specific attribute handle to assign (optional, auto-assigned if omitted) :Example Register Generic Access service (auto handle): | **> register-service 0x1800** :Example Register Battery Service (auto handle): | **> register-service 0x180F** :Example Register Device Information service (auto handle): | **> register-service 0x180A** :Example Register Generic Access service at handle 1: | **> register-service 0x1800 0x0001** :Example Register Battery Service at handle 16: | **> register-service 0x180F 0x0010** :Example Register Device Info service at handle 32: | **> register-service 0x180A 0x0020** :Example Register custom service (auto handle): | **> register-service 12345678-1234-5678-9abc-123456789abc** :Example Register custom service at specific handle: | **> register-service 12345678-1234-5678-9abc-123456789abc 0x0050** unregister-service ------------------ Removes a service from a registered application, effectively ceasing its availability. :Usage: **> unregister-service ** :: Service UUID or object path of the service to unregister :Example Unregister Generic Access service: | **> unregister-service 0x1800** :Example Unregister Battery Service: | **> unregister-service 0x180F** :Example Unregister custom service: | **> unregister-service 12345678-1234-5678-9abc-123456789abc** :Example Unregister service by object path: | **> unregister-service /org/bluez/example/service0** :Example Unregister another service: | **> unregister-service /org/bluez/example/service1** register-includes ----------------- Marks a service as included within another service, allowing for service hierarchies and complex service structures. :Usage: **> register-includes [handle]** :: Service UUID to register as included service :[handle]: Specific attribute handle to assign (optional, auto-assigned if omitted) :Example Register Generic Access as included service: | **> register-includes 0x1800** :Example Register Battery Service as included at handle 21: | **> register-includes 0x180F 0x0015** :Example Register custom service as included: | **> register-includes 12345678-1234-5678-9abc-123456789abc** unregister-includes ------------------- Removes an included service relationship, simplifying the service structure. :Usage: **> unregister-includes ** :: Parent service UUID that contains the included service :: Included service UUID to remove from the parent service :Example Unregister Battery Service inclusion from Generic Access: | **> unregister-includes 0x1800 0x180F** :Example Unregister Device Info inclusion from custom service: | **> unregister-includes 12345678-1234-5678-9abc-123456789abc 0x180A** register-characteristic ----------------------- Introduces a new characteristic under a service, specifying its properties and access permissions with flags. :Usage: **> register-characteristic [handle]** :: Characteristic UUID to register (16-bit, 32-bit, or 128-bit format) :: Comma-separated list of characteristic properties and permissions :[handle]: Specific attribute handle to assign (optional, auto-assigned if omitted) :Example Register Device Name (read-only): | **> register-characteristic 0x2A00 read** :Example Register Battery Level (read + notifications): | **> register-characteristic 0x2A19 read,notify** :Example Register Heart Rate Measurement (notify-only): | **> register-characteristic 0x2A37 notify** :Example Register Device Name (read + write): | **> register-characteristic 0x2A00 read,write** :Example Register Battery Level (read + write + notify): | **> register-characteristic 0x2A19 read,write,notify** :Example Register Temperature (read + indications): | **> register-characteristic 0x2A6E read,indicate** :Example Register Device Name at handle 3: | **> register-characteristic 0x2A00 read 0x0003** :Example Register Battery Level at handle 19: | **> register-characteristic 0x2A19 read,notify 0x0013** :Example Register custom characteristic with full capabilities: | **> register-characteristic 12345678-1234-5678-9abc-123456789abc read,write,notify** :Example Register custom write-only characteristic: | **> register-characteristic ABCD1234-ABCD-1234-ABCD-123456789ABC write-without-response** :Example Register with all flags: | **> register-characteristic 0x2A00 read,write,write-without-response,notify,indicate** unregister-characteristic ------------------------- Eliminates a characteristic from a service, removing its functionality. :Usage: **> unregister-characteristic ** :: Characteristic UUID or object path of the characteristic to unregister :Example Unregister Device Name characteristic: | **> unregister-characteristic 0x2A00** :Example Unregister Battery Level characteristic: | **> unregister-characteristic 0x2A19** :Example Unregister custom characteristic: | **> unregister-characteristic 12345678-1234-5678-9abc-123456789abc** :Example Unregister characteristic by object path: | **> unregister-characteristic /org/bluez/example/service0/char0** :Example Unregister another characteristic: | **> unregister-characteristic /org/bluez/example/service1/char1** register-descriptor ------------------- Adds a descriptor to a characteristic, further defining its behavior and access controls. :Usage: **> register-descriptor [handle]** :: Descriptor UUID to register (16-bit, 32-bit, or 128-bit format) :: Comma-separated list of descriptor properties and permissions :[handle]: Specific attribute handle to assign (optional, auto-assigned if omitted) :Example Register Client Characteristic Configuration: | **> register-descriptor 0x2902 read,write** :Example Register Characteristic User Description: | **> register-descriptor 0x2901 read** :Example Register Characteristic Presentation Format: | **> register-descriptor 0x2904 read** :Example Register CCCD at handle 5: | **> register-descriptor 0x2902 read,write 0x0005** :Example Register User Description at handle 21: | **> register-descriptor 0x2901 read 0x0015** :Example Register custom descriptor: | **> register-descriptor 12345678-1234-5678-9abc-123456789abc read,write** :Example Register custom read-only descriptor: | **> register-descriptor ABCD1234-ABCD-1234-ABCD-123456789ABC read** :Example Register Valid Range descriptor: | **> register-descriptor 0x2906 read** :Example Register External Report Reference: | **> register-descriptor 0x2907 read** :Example Register Report Reference: | **> register-descriptor 0x2908 read** unregister-descriptor --------------------- Removes a descriptor from a characteristic, simplifying its behavior. :Usage: **> unregister-descriptor ** :: Descriptor UUID or object path of the descriptor to unregister :Example Unregister Client Characteristic Configuration: | **> unregister-descriptor 0x2902** :Example Unregister Characteristic User Description: | **> unregister-descriptor 0x2901** :Example Unregister custom descriptor: | **> unregister-descriptor 12345678-1234-5678-9abc-123456789abc** :Example Unregister descriptor by object path: | **> unregister-descriptor /org/bluez/example/service0/char0/desc0** :Example Unregister another descriptor: | **> unregister-descriptor /org/bluez/example/service1/char1/desc1** RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/test-coverage.txt0000644000000000000000000000005015110075565015736 xustar0020 atime=1770585999 20 ctime=1783094111 bluez-5.87/doc/test-coverage.txt0000644000000000000000000000443115110075565015421 0ustar00rootrootBlueZ test coverage ******************* Automated unit testing ====================== Application Count Description ------------------------------------------- test-crc 9 Link Layer CRC-24 checksum test-eir 14 EIR and AD parsing test-lib 14 SDP library functions test-sdp 133 SDP qualification test cases test-uuid 30 UUID conversion handling test-mgmt 9 Management interface handling test-crypto 5 Cryptographic toolbox helpers test-textfile 4 Old textfile storage format test-ringbuf 3 Ring buffer functionality test-queue 6 Queue handling functionality test-uhid 6 Userspace HID functionality test-hfp 29 HFP Audio Gateway functionality test-avdtp 60 AVDTP qualification test cases test-avctp 9 AVCTP qualification test cases test-avrcp 110 AVRCP qualification test cases test-gobex 31 Generic OBEX functionality test-gobex-packet 9 OBEX packet handling test-gobex-header 28 OBEX header handling test-gobex-apparam 18 OBEX apparam handling test-gobex-transfer 36 OBEX transfer handling test-gdbus-client 13 D-Bus client handling test-gatt 180 GATT qualification test cases test-hog 6 HID Over GATT qualification test cases test-battery 10 Battery charge test cases ----- 771 Automated end-to-end testing ============================ Application Count Description ------------------------------------------- mgmt-tester 331 Kernel management interface testing l2cap-tester 33 Kernel L2CAP implementation testing rfcomm-tester 9 Kernel RFCOMM implementation testing bnep-tester 1 Kernel BNEP implementation testing smp-tester 8 Kernel SMP implementation testing sco-tester 8 Kernel SCO implementation testing gap-tester 1 Daemon D-Bus API testing hci-tester 14 Controller hardware testing userchan-tester 3 Kernel HCI User Channel testting ----- 408 Android end-to-end testing ========================== Application Count Description ------------------------------------------- android-tester 194 Android HAL interface testing ipc-tester 132 Android IPC resistance testing ----- 326 Android automated unit testing ============================== Application Count Description ------------------------------------------- test-ipc 14 Android IPC library functions ----- 14 bluez-5.87/doc/PaxHeaders/bluetoothctl-hci.10000644000000000000000000000005015221755472015765 xustar0020 atime=1783094074 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-hci.10000644000000000000000000001451015221755472015447 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "bluetoothctl-hci" "1" "December 2024" "BlueZ" "Linux System Administration" .SH Name bluetoothctl-hci \- HCI Submenu .SH SYNOPSIS .sp \fBbluetoothctl\fP [\-\-options] [hci.commands] .sp \fBNote:\fP .INDENT 0.0 .INDENT 3.5 This submenu communicates directly with the Bluetooth controller via HCI socket, bypassing the BlueZ D\-Bus API. Commands use the Host Controller Interface (HCI) protocol. .UNINDENT .UNINDENT .SH Commands .SS open .sp Open HCI channel. .INDENT 0.0 .TP .B Usage \fB> open \fP .TP .B index HCI controller index number (e.g., 0, 1, 2) .TP .B chan Channel type (raw or user) .TP .B Example Open user channel (controller needs to be powered off first) .nf \fB> power off\fP \fB> hci.open 0 user\fP \fBHCI index 0 user channel opened\fP .fi .sp .TP .B Example Open user channel for HCI controller 0 .nf \fB> hci.open 0 user\fP .fi .sp .TP .B Example Open user channel for HCI controller 1 .nf \fB> hci.open 1 user\fP .fi .sp .TP .B Example Open user channel for HCI controller 2 .nf \fB> hci.open 2 user\fP .fi .sp .TP .B Example Open raw channel for HCI controller 0 .nf \fB> hci.open 0 raw\fP .fi .sp .TP .B Example Open raw channel for HCI controller 1 .nf \fB> hci.open 1 raw\fP .fi .sp .UNINDENT .SS cmd .sp Send HCI command. .INDENT 0.0 .TP .B Usage \fB> cmd [parameters...]\fP .TP .B opcode HCI command opcode in hexadecimal format (e.g., 0x0c03) .TP .B parameters Optional command parameters as hexadecimal bytes .TP .B Example Send HCI Reset command .nf \fB> hci.cmd 0x0c03\fP \fBHCI Command complete:\fP \fB00\fP .fi .sp .TP .B Example Send HCI Reset command (no parameters) .nf \fB> hci.cmd 0x0c03\fP .fi .sp .TP .B Example Send HCI Read Local Version Information .nf \fB> hci.cmd 0x1003\fP .fi .sp .TP .B Example Send HCI Read BD Address .nf \fB> hci.cmd 0x1009\fP .fi .sp .TP .B Example Send HCI Set Event Filter with parameter 0x01 .nf \fB> hci.cmd 0x0c01 0x01\fP .fi .sp .TP .B Example Send LE Set Advertising Parameters with 2 parameters .nf \fB> hci.cmd 0x200f 0x00 0x08\fP .fi .sp .TP .B Example Send LE Set Advertising Data with length and data bytes .nf \fB> hci.cmd 0x2008 0x20 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08\fP .fi .sp .TP .B Example Send HCI Set Event Mask command .nf \fB> hci.cmd 0x0c05 0x02\fP .fi .sp .TP .B Example Send HCI Write Scan Enable .nf \fB> hci.cmd 0x0c2a 0x01\fP .fi .sp .TP .B Example Send HCI Write Class of Device .nf \fB> hci.cmd 0x0c23 0x00 0x08\fP .fi .sp .UNINDENT .SS send .sp Send HCI data packet. .INDENT 0.0 .TP .B Usage \fB> send [data...]\fP .TP .B type Packet type (acl, sco, or iso) .TP .B handle Connection handle in hexadecimal format (e.g., 0x0000) .TP .B data Optional data bytes in hexadecimal format .TP .B Example Send ACL data packet to connection handle 0x0000 .nf \fB> hci.send acl 0x0000\fP .fi .sp .TP .B Example Send empty ACL packet to handle 0x0000 .nf \fB> hci.send acl 0x0000\fP .fi .sp .TP .B Example Send ACL packet with \(dqHello\(dq data to handle 0x0001 .nf \fB> hci.send acl 0x0001 0x48 0x65 0x6c 0x6c 0x6f\fP .fi .sp .TP .B Example Send ACL packet with custom data to handle 0x0010 .nf \fB> hci.send acl 0x0010 0x01 0x02 0x03 0x04\fP .fi .sp .TP .B Example Send empty SCO packet to handle 0x0000 .nf \fB> hci.send sco 0x0000\fP .fi .sp .TP .B Example Send SCO packet with audio data .nf \fB> hci.send sco 0x0001 0xaa 0xbb 0xcc\fP .fi .sp .TP .B Example Send SCO packet with sine wave pattern .nf \fB> hci.send sco 0x0002 0x80 0x00 0x80 0x00\fP .fi .sp .TP .B Example Send empty ISO packet to handle 0x0000 .nf \fB> hci.send iso 0x0000\fP .fi .sp .TP .B Example Send ISO packet with sample data .nf \fB> hci.send iso 0x0001 0x12 0x34 0x56 0x78\fP .fi .sp .TP .B Example Send ISO packet for LE Audio stream .nf \fB> hci.send iso 0x0020 0xa0 0xa1 0xa2 0xa3\fP .fi .sp .UNINDENT .SS register .sp Register HCI event handler. .INDENT 0.0 .TP .B Usage \fB> register \fP .TP .B event HCI event code in hexadecimal format (e.g., 0x0e) .TP .B Example Register handler for Command Complete event .nf \fB> register 0x0e\fP .fi .sp .TP .B Example Register handler for Command Status event .nf \fB> register 0x0f\fP .fi .sp .TP .B Example Register handler for Connection Complete event .nf \fB> register 0x03\fP .fi .sp .TP .B Example Register handler for Disconnection Complete event .nf \fB> register 0x05\fP .fi .sp .TP .B Example Register handler for LE Meta events .nf \fB> register 0x3e\fP .fi .sp .TP .B Example Register handler for Inquiry Complete event .nf \fB> register 0x02\fP .fi .sp .TP .B Example Register handler for Role Change event .nf \fB> register 0x22\fP .fi .sp .UNINDENT .SS unregister .sp Unregister HCI event handler. .INDENT 0.0 .TP .B Usage \fB> unregister \fP .TP .B event HCI event code in hexadecimal format (e.g., 0x0e) .TP .B Example Unregister Command Complete event handler .nf \fB> unregister 0x0e\fP .fi .sp .TP .B Example Unregister Command Status event handler .nf \fB> unregister 0x0f\fP .fi .sp .TP .B Example Unregister Connection Complete event handler .nf \fB> unregister 0x03\fP .fi .sp .TP .B Example Unregister Disconnection Complete event handler .nf \fB> unregister 0x05\fP .fi .sp .TP .B Example Unregister LE Meta event handler .nf \fB> unregister 0x3e\fP .fi .sp .UNINDENT .SS close .sp Close HCI channel. .sp Closes the currently open HCI channel. .INDENT 0.0 .TP .B Usage \fB> close\fP .TP .B Example Close the current HCI channel .nf \fB> close\fP .fi .sp .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.ProfileManager.50000644000000000000000000000005015221755300017307 xustar0020 atime=1783093952 20 ctime=1783094112 bluez-5.87/doc/org.bluez.ProfileManager.50000644000000000000000000001001015221755300016760 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.ProfileManager" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.ProfileManager \- BlueZ D-Bus ProfileManager API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.ProfileManager1 .TP .B Object path /org/bluez .UNINDENT .SS Methods .SS void RegisterProfile(object profile, string uuid, dict options) .sp Registers profile agent. .sp The object path defines the path of the profile that will be called when there is a connection and must implement \fBorg.bluez.Profile(5)\fP interface. .sp If an application disconnects from the bus all its registered profiles will be removed. .sp Possible uuid values: .INDENT 0.0 .TP .B \(dq0000111f\-0000\-1000\-8000\-00805f9b34fb\(dq HFP AG, default profile Version is 1.7, profile Features is 0b001001 and RFCOMM channel is 13. Authentication is required. .TP .B \(dq0000111e\-0000\-1000\-8000\-00805f9b34fb\(dq HFP HS, default profile Version is 1.7, profile Features is 0b000000 and RFCOMM channel is 7. Authentication is required. .TP .B \(dq00001112\-0000\-1000\-8000\-00805f9b34fb\(dq HSP AG, default profile Version is 1.2, RFCOMM channel is 12 and Authentication is required. Does not support any Features, option is ignored. .TP .B \(dq00001108\-0000\-1000\-8000\-00805f9b34fb\(dq HSP HS, default profile Version is 1.2, profile Features is 0b0 and RFCOMM channel is 6. Authentication is required. .sp Features is one bit value, specify capability of Remote Audio Volume Control (by default turned off). .TP .B \(dq\(dq Vendor defined UUID, no defaults, must set options. .UNINDENT .sp Possible options values: .INDENT 0.0 .TP .B string Name Human readable name for the profile .TP .B string Service The primary service class UUID (if different from the actual profile UUID). .TP .B string Role For asymmetric profiles that do not have UUIDs available to uniquely identify each side this parameter allows specifying the precise local role. .sp Possible values: .INDENT 7.0 .TP .B \(dqclient\(dq .TP .B \(dqserver\(dq .UNINDENT .TP .B uint16 Channel RFCOMM channel number that is used for client and server UUIDs. .sp If applicable it will be used in the SDP record as well. .TP .B uint16 PSM PSM number that is used for client and server UUIDs. .sp If applicable it will be used in the SDP record as well. .TP .B boolean RequireAuthentication Pairing is required before connections will be established. .sp No devices will be connected if not paired. .TP .B boolean RequireAuthorization Request authorization before any connection will be established. .TP .B boolean AutoConnect In case of a client UUID this will force connection of the RFCOMM or L2CAP channels when a remote device is connected. .TP .B string ServiceRecord Provide a manual SDP record. .TP .B uint16 Version Profile version (for SDP record) .TP .B uint16 Features Profile features (for SDP record) .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.AlreadyExists .UNINDENT .SS void UnregisterProfile(object profile) .sp Unregisters profile object that has been previously registered using \fBRegisterProfile\fP\&. .sp The object path parameter must match the same value that has been used on registration. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.DoesNotExist .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/bluetoothctl-endpoint.rst0000644000000000000000000000005015142177620017505 xustar0020 atime=1783094061 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-endpoint.rst0000644000000000000000000002134715142177620017175 0ustar00rootroot===================== bluetoothctl-endpoint ===================== ---------------- Endpoint Submenu ---------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: November 2022 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **bluetoothctl** [--options] [endpoint.commands] This submenu manages media endpoints for audio streaming using the **org.bluez.MediaEndpoint(5)** and **org.bluez.Media(5)** interfaces. Endpoint Commands ================= list ---- List available endpoints. :Usage: **> list [local]** :[local]: Only show locally registered endpoints (optional, shows all if omitted) :Example List all available endpoints (local and remote): | **> list** :Example List only locally registered endpoints: | **> list local** show ---- Endpoint information. :Usage: **> show [endpoint]** :Uses: **org.bluez.MediaEndpoint(5)** properties :[endpoint]: Path to specific endpoint to display (optional, shows current selection if omitted) :Example Show information for currently selected endpoint: | **> show** :Example Show local endpoint information: | **> show /local/endpoint/ep0** :Example Show remote endpoint information: | **> show /org/bluez/hci0/dev_00_11_22_33_44_55/ep1** :Example Show another local endpoint: | **> show /local/endpoint/ep1** register -------- Register Endpoint. :Usage: **> register [capabilities...]** :Uses: **org.bluez.Media(5)** method **RegisterEndpoint** :: Bluetooth service UUID for the endpoint type (required) :: Codec ID in hex format, optionally with company ID (required) :[capabilities...]: Optional codec-specific capability data in hex format :Example LC3 BAP source: | **>endpoint.register 00002bcb-0000-1000-8000-00805f9b34fb 0x06** | **>Auto Accept (yes/no):** y | **>Max Transports (auto/value):** a | **>Locations:** a | **>Supported Context (value):** 3 | **>Context (value):** 3 | **>CIG (auto/value):** a | **>CIS (auto/value):** a :Example LC3 BAP sink with extra capabilities: | **>endpoint.register 00002bc9-0000-1000-8000-00805f9b34fb 0x06 "0x03 0xe5 0x03 0x00 0x02 0xe6 0x07"** | **>Enter Metadata (value/no):** n | **>Auto Accept (yes/no):** y | **>Max Transports (auto/value):** a | **>Locations:** a | **>Supported Context (value):** 3 | **>Context (value):** 3 | **>CIG (auto/value):** a | **>CIS (auto/value):** a :Example LC3 BAP Broadcast source: | **>endpoint.register 00001852-0000-1000-8000-00805f9b34fb 0x06** | **>Auto Accept (yes/no):** y | **>Max Transports (auto/value):** a | **>Locations:** 3 | **>Supported Context (value):** 1 :Example LC3 BAP Broadcast sink: | **>endpoint.register 00001851-0000-1000-8000-00805f9b34fb 0x06** | **>Auto Accept (yes/no):** y | **>Max Transports (auto/value):** a | **>Locations:** 3 | **>Supported Context (value):** 1 Note: If running the setup with an audio server that has LE Audio support (such as PipeWire) it will automatically register endpoints according to the configured roles. For more details about configuring a Broadcast Source with PipeWire check: https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/master/doc/dox/config/pipewire-props.7.md unregister ---------- Unregister Endpoint. :Usage: **> unregister ** :Uses: **org.bluez.Media(5)** method **UnregisterEndpoint** :: Either the service UUID or the object path of the endpoint to unregister :Example Unregister LC3 source endpoint by UUID: | **> unregister 00002bcb-0000-1000-8000-00805f9b34fb** :Example Unregister LC3 sink endpoint by UUID: | **> unregister 00002bc9-0000-1000-8000-00805f9b34fb** :Example Unregister broadcast source endpoint by UUID: | **> unregister 00001852-0000-1000-8000-00805f9b34fb** :Example Unregister broadcast sink endpoint by UUID: | **> unregister 00001851-0000-1000-8000-00805f9b34fb** :Example Unregister endpoint by object path: | **> unregister /local/endpoint/ep0** :Example Unregister another endpoint by object path: | **> unregister /local/endpoint/ep1** :Example Unregister source endpoint by path: | **> unregister /local/endpoint/source0** :Example Unregister sink endpoint by path: | **> unregister /local/endpoint/sink0** Note: If the endpoint was registered by an audio server, it can't be unregistered from bluetoothctl. This must be done by the audio server as well. config ------ Configure Endpoint. :Usage: **> config [preset]** :Uses: **org.bluez.MediaEndpoint(5)** method **SetConfiguration** :: Path to the remote endpoint to configure :: Path to the local endpoint to use for the configuration :[preset]: Audio quality preset name (optional, auto-detected if omitted) :Example Configure remote endpoint with local endpoint (auto preset): | **> config /org/bluez/hci0/dev_00_11_22_33_44_55/ep0 /local/endpoint/ep0** :Example Configure LE Audio Unicast with BAP setting 32_1_1: | **> config /org/bluez/hci0/dev_00_11_22_33_44_55/ep0 /local/endpoint/ep0 32_1_1** :Example Configure LE Audio Broadcast Source: | **> config /org/bluez/hci0/dev_00_11_22_33_44_55/bis0 /local/endpoint/broadcast0** Note: If the endpoint was registered by an audio server, it can't be configured from bluetoothctl. This must be done by the audio server as well. presets ------- List available presets. :Usage: **> presets / [codec[:company]] [preset] [codec config] [metadata]** :/: Either endpoint path or service UUID to work with presets :[codec[:company]]: Codec ID with optional company identifier :[preset]: Preset name to display details or create custom preset :[codec config]: Custom codec configuration data in hex format :[metadata]: Additional metadata for the preset :Example using endpoint: | **>presets /local/endpoint/ep0 32_1_1** | **>presets /local/endpoint/ep0** | Preset 32_1_1 | Configuration.**>0: len 0x02 type 0x01 | Configuration.Sampling Frequency: 32 Khz (0x06) | Configuration.**>1: len 0x02 type 0x02 | Configuration.Frame Duration: 7.5 ms (0x00) | Configuration.**>2: len 0x03 type 0x04 | Configuration.Frame Length: 60 (0x003c) :Example List all available presets for local endpoint: | **>presets /local/endpoint/ep0** :Example List all presets for another endpoint: | **>presets /local/endpoint/ep1** :Example using UUID: | **>presets 00002bc9-0000-1000-8000-00805f9b34fb 0x06 32_1_1** | **>presets 00002bc9-0000-1000-8000-00805f9b34fb 0x06** | ... | ***32_1_1** :Example List all LC3 sink presets: | **>presets 00002bc9-0000-1000-8000-00805f9b34fb 0x06** :Example List all LC3 source presets: | **>presets 00002bcb-0000-1000-8000-00805f9b34fb 0x06** :Example List all broadcast source presets: | **>presets 00001852-0000-1000-8000-00805f9b34fb 0x06** :Example List all broadcast sink presets: | **>presets 00001851-0000-1000-8000-00805f9b34fb 0x06** :Example Show details for 48kHz stereo preset: | **>presets 00002bc9-0000-1000-8000-00805f9b34fb 0x06 48_2_1** :Example Show details for 24kHz mono high-quality preset: | **>presets 00002bc9-0000-1000-8000-00805f9b34fb 0x06 24_1_2** :Example Show details for 16kHz mono preset: | **>presets 00002bc9-0000-1000-8000-00805f9b34fb 0x06 16_1_1** :Example setting up LC3 custom preset: | **>presets 00002bc9-0000-1000-8000-00805f9b34fb 0x06 custom** | **>[Codec] Enter frequency (Khz):** 48 | **>[Codec] Enter frame duration (ms):** 10 | **>[Codec] Enter channel allocation:** 3 | **>[Codec] Enter frame length:** 100 | **>[QoS] Enter Target Latency (Low, Balance, High):** Low | **>[QoS] Enter SDU Interval (us):** 1000 | **>[QoS] Enter Framing (Unframed, Framed):** Unframed | **>[QoS] Enter PHY (1M, 2M):** 2M | **>[QoS] Enter Max SDU:** 200 | **>[QoS] Enter RTN:** 3 | **>[QoS] Enter Max Transport Latency (ms):** 10 | **>[QoS] Enter Presentation Delay (us):** 20000 | **>presets 00002bc9-0000-1000-8000-00805f9b34fb 0x06** | ... | ***custom** :Example setting up LC3 custom preset with extra configuration: | **>presets 00002bc9-0000-1000-8000-00805f9b34fb 0x06 custom "0x03 0xe8 0x00 0x00 0x02 0xe9 0x00"** | **>[Codec] Enter frequency (Khz):** 48 | **>[Codec] Enter frame duration (ms):** 10 | **>[Codec] Enter channel allocation:** 3 | **>[Codec] Enter frame length:** 100 | **>[QoS] Enter Target Latency (Low, Balance, High):** Low | **>[QoS] Enter SDU Interval (us):** 1000 | **>[QoS] Enter Framing (Unframed, Framed):** Unframed | **>[QoS] Enter PHY (1M, 2M):** 2M | **>[QoS] Enter Max SDU:** 200 | **>[QoS] Enter RTN:** 3 | **>[QoS] Enter Max Transport Latency (ms):** 10 | **>[QoS] Enter Presentation Delay (us):** 20000 | **>presets 00002bc9-0000-1000-8000-00805f9b34fb 0x06** | ... | ***custom** RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.MediaPlayer.rst0000644000000000000000000000005015142177620017261 xustar0020 atime=1783093974 20 ctime=1783094111 bluez-5.87/doc/org.bluez.MediaPlayer.rst0000644000000000000000000001371215142177620016746 0ustar00rootroot===================== org.bluez.MediaPlayer ===================== ----------------------------------------- BlueZ D-Bus MediaPlayer API documentation ----------------------------------------- :Version: BlueZ :Date: September 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez (Controller role) :Interface: org.bluez.MediaPlayer1 :Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/player# :Used by: **bluetoothctl-player(1)** Methods ------- void Play() ``````````` Resume playback. Possible Errors: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: Examples: :bluetoothctl: > player.play [item] void Pause() ```````````` Pause playback. Possible Errors: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: Examples: :bluetoothctl: > player.pause void Stop() ``````````` Stop playback. Possible Errors: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: Examples: :bluetoothctl: > player.stop void Next() ``````````` Next item. Possible Errors: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: Examples: :bluetoothctl: > player.next void Previous() ``````````````` Previous item. Possible Errors: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: Examples: :bluetoothctl: > player.previous void FastForward() `````````````````` Fast forward playback, this action is only stopped when another method in this interface is called. Possible Errors: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: Examples: :bluetoothctl: > player.fast-forward void Rewind() ````````````` Rewind playback, this action is only stopped when another method in this interface is called. Possible Errors: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: Examples: :bluetoothctl: > player.rewind void Press(byte avc_key) ```````````````````````` Press a specific key to send as passthrough command. The key will be released automatically. Use Hold() instead if the intention is to hold down the key. Possible Errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: void Hold(byte avc_key) ``````````````````````` Press and hold a specific key to send as passthrough command. It is the responsibility of the caller to make sure that Release() is called after calling this method. The held key will also be released when any other method in this interface is called. Possible Errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: void Release() `````````````` Release the previously held key invoked using Hold(). Possible Errors: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: Properties ---------- string Equalizer [readwrite] ```````````````````````````` Indicates Player Equalizer setting. Possible values: :"off": :"on": Examples: :bluetoothctl: [player] > equalizer string Repeat [readwrite] ````````````````````````` Indicates Player Repeat setting. Possible values: :"off": :"singletrack": :"alltracks": :"group": Examples: :bluetoothctl: [player] > repeat string Shuffle [readwrite] `````````````````````````` Indicates Player Suffle setting. Possible values: :"off": :"alltracks": :"group": Examples: :bluetoothctl: [player] > shuffle string Scan [readwrite] ``````````````````````` Indicates Player Scan setting. Possible values: :"off": :"alltracks": :"group": Examples: :bluetoothctl: [player] > scan string Status [readonly] ```````````````````````` Indicates Player Status setting. Possible status: :"playing": :"stopped": :"paused": :"forward-seek": :"reverse-seek": :"error": uint32 Position [readonly] `````````````````````````` Playback position in milliseconds. Changing the position may generate additional events that will be sent to the remote device. When position is 0 it means the track is starting and when it's greater than or equal to track's duration the track has ended. Note that even if duration is not available in metadata it's possible to signal its end by setting position to the maximum uint32 value. dict Track [readonly] ````````````````````` Track metadata. Possible values: :string Title: Track title name :string Artist: Track artist name :string Album: Track album name :string Genre: Track genre name :uint32 NumberOfTracks: Number of tracks in total :uint32 TrackNumber: Track number :uint32 Duration: Track duration in milliseconds :string ImgHandle: [experimental] Track image handle, available and valid only during the lifetime of an OBEX BIP connection to the ObexPort. object Device [readonly] ```````````````````````` Device object path. string Name [readonly] `````````````````````` Player name. string Type [readonly] `````````````````````` Player type. Possible values: :"Audio": :"Video": :"Audio Broadcasting": :"Video Broadcasting": string Subtype [readonly] ````````````````````````` Player subtype. Possible values: :"Audio Book": :"Podcast": boolean Browsable [readonly] ```````````````````````````` If present indicates the player can be browsed using MediaFolder interface. Possible values: :True: Supported and active :False: Supported but inactive Note: If supported but inactive clients can enable it by using **org.bluez.MediaFolder(5)** interface but it might interfere in the playback of other players. boolean Searchable [readonly] ````````````````````````````` If present indicates the player can be searched using MediaFolder interface. Possible values: :True: Supported and active :False: Supported but inactive Note: If supported but inactive clients can enable it by using **org.bluez.MediaFolder(5)** interface but it might interfere in the playback of other players. object Playlist ``````````````` Playlist object path. uint16 ObexPort [readonly, experimental] ```````````````````````````````````````` If present indicates the player can get cover art using BIP over OBEX on this PSM port. bluez-5.87/doc/PaxHeaders/org.bluez.LEAdvertisingManager.rst0000644000000000000000000000005015142177620021060 xustar0020 atime=1783093996 20 ctime=1783094112 bluez-5.87/doc/org.bluez.LEAdvertisingManager.rst0000644000000000000000000000650315142177620020545 0ustar00rootroot============================== org.bluez.LEAdvertisingManager ============================== ------------------------------------------------- BlueZ D-Bus LEAvertisingManager API documentation ------------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= The Advertising Manager allows external applications to register Advertisement Data which should be broadcast to devices. Advertisement Data elements must follow the API for LE Advertisement Data described above. :Service: org.bluez :Interface: org.bluez.LEAdvertisingManager1 :Object path: /org/bluez/{hci0,hci1,...} :Used by: **bluetoothctl(1)** Methods ------- void RegisterAdvertisement(object advertisement, dict options) `````````````````````````````````````````````````````````````` Registers an advertisement object to be sent over the LE Advertising channel. The service must implement **org.bluez.LEAdvertisement(5)** interface. Possible errors: :org.bluez.Error.InvalidArguments: Indicates that the object has invalid or conflicting properties. :org.bluez.Error.AlreadyExists: Indicates the object is already registered. :org.bluez.Error.InvalidLength: Indicates that the data provided generates a data packet which is too long. :org.bluez.Error.NotPermitted: Indicates the maximum number of advertisement instances has been reached. Examples: :bluetoothctl: > advertise void UnregisterAdvertisement(object advertisement) `````````````````````````````````````````````````` Unregisters an advertisement that has been previously registered using **RegisterAdvertisement()**. The object path parameter must match the same value that has been used on registration. Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.DoesNotExist: Properties ---------- byte ActiveInstances [readonly] ``````````````````````````````` Number of active advertising instances. byte SupportedInstances [readonly] `````````````````````````````````` Number of available advertising instances. array{string} SupportedIncludes [readonly] `````````````````````````````````````````` List of supported system includes. Possible values: :"tx-power": :"appearance": :"local-name": :"rsi": array{string} SupportedSecondaryChannels [readonly] ``````````````````````````````````````````````````` List of supported Secondary channels. Secondary channels can be used to advertise with the corresponding PHY. Possible values: :"1M": :"2M": :"Coded": dict SupportedCapabilities [readonly] ````````````````````````````````````` Enumerates Advertising-related controller capabilities useful to the client. Possible Values: :byte MaxAdvLen: Max advertising data length :byte MaxScnRspLen: Max advertising scan response length :int16 MinTxPower: Min advertising tx power (dBm) :int16 MaxTxPower: Max advertising tx power (dBm) array{string} SupportedFeatures [readonly,optional] ``````````````````````````````````````````````````` List of supported platform features. If no features are available on the platform, the SupportedFeatures array will be empty. Possible values: :"CanSetTxPower": Indicates whether platform can specify tx power on each advertising instance. :"HardwareOffload": Indicates whether multiple advertising will be offloaded to the controller. bluez-5.87/doc/PaxHeaders/iso-protocol.rst0000644000000000000000000000005015142177620015610 xustar0020 atime=1783093933 20 ctime=1783094111 bluez-5.87/doc/iso-protocol.rst0000644000000000000000000003300415142177620015271 0ustar00rootroot=== iso === ------------- ISO transport ------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: July 2025 :Manual section: 7 :Manual group: Linux System Administration SYNOPSIS (since Linux 6.0 [experimental]) ========================================= .. code-block:: #include #include #include iso_socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO); DESCRIPTION =========== Bluetooth Isochronous Channels is a feature introduced in Bluetooth 5.2 that allow for the transmission of multiple, synchronized audio streams between devices. For unicast/multi-stream audio, connected isochronous group (CIG), and connected isochronous stream (CIS) are used. A CIG is created by the central and it can include one or more CISs. A CIS is a point-to-point data transportation stream between a central and a certain peripheral, and is a bidirectional communication protocol with acknowledgment. For broadcast audio, broadcast isochronous group (BIG) and broadcast isochronous stream (BIS) are used. There are two types of devices: isochronous broadcaster and synchronized receiver. A BIG is created by an isochronous broadcaster, and it can include one or more BISs. A BIS is a one-to-many data transportation stream. SOCKET ADDRESS ============== .. code-block:: struct sockaddr_iso_bc { bdaddr_t bc_bdaddr; uint8_t bc_bdaddr_type; uint8_t bc_sid; uint8_t bc_num_bis; uint8_t bc_bis[ISO_MAX_NUM_BIS]; }; struct sockaddr_iso { sa_family_t iso_family; bdaddr_t iso_bdaddr; uint8_t iso_bdaddr_type; struct sockaddr_iso_bc iso_bc[]; }; Unicast example: .. code-block:: struct sockaddr_iso addr; memset(&addr, 0, sizeof(addr)); addr.iso_family = AF_BLUETOOTH; bacpy(&addr.iso_bdaddr, bdaddr); addr.iso_bdaddr_type = BDADDR_LE_PUBLIC; Broadcast example: .. code-block:: struct sockaddr_iso *addr; size_t addr_len; addr_len = sizeof(*addr) + sizeof(*addr->iso_bc); memset(addr, 0, addr_len); addr->iso_family = AF_BLUETOOTH; bacpy(&addr->iso_bdaddr, bdaddr); addr->iso_bdaddr_type = BDADDR_LE_PUBLIC; Broadcast Source (Broadcaster) example: .. code-block:: struct sockaddr_iso *addr; size_t addr_len; addr_len = sizeof(*addr) + sizeof(*addr->iso_bc); memset(addr, 0, addr_len); addr->iso_family = AF_BLUETOOTH; /* Set address to BDADR_ANY(00:00:00:00:00:00) */ bacpy(&addr->iso_bdaddr, BADDR_ANY); addr->iso_bdaddr_type = BDADDR_LE_PUBLIC; /* Connect to Broadcast address */ connect(iso_socket, (struct sockaddr *)addr, addr_len); Broadcast Sink (Receiver) example: .. code-block:: struct sockaddr_iso *addr; size_t addr_len; addr_len = sizeof(*addr) + sizeof(*addr->iso_bc); memset(addr, 0, addr_len); addr->iso_family = AF_BLUETOOTH; /* Set destination to Broadcaster address */ bacpy(&addr->iso_bdaddr, bdaddr); addr->iso_bdaddr_type = BDADDR_LE_PUBLIC; /* Bind to Broadcaster address */ bind(iso_socket, (struct sockaddr *)addr, addr_len); Broadcast Source (Broadcaster) or Broadcast Sink (Receiver) Periodic Advertising Sync Transfer (PAST): .. code-block:: struct sockaddr_iso *addr; size_t addr_len; addr_len = sizeof(*addr) + sizeof(*addr->iso_bc); memset(addr, 0, addr_len); addr->iso_family = AF_BLUETOOTH; /* Set destination address to PAST destination address */ bacpy(&addr->iso_bc->bc_bdaddr, (void *) bdaddr); addr->iso_bc->bc_bdaddr_type = bdaddr_type; /* Rebind already connected socket to PAST address */ bind(iso_socket, (struct sockaddr *)addr, addr_len); SOCKET OPTIONS (SOL_BLUETOOTH) ============================== The socket options listed below can be set by using **setsockopt(2)** and read with **getsockopt(2)** with the socket level set to SOL_BLUETOOTH. BT_SECURITY ----------- Channel security level, possible values: .. csv-table:: :header: "Value", "Security Level", "Link Key Type", "Encryption" :widths: auto **BT_SECURITY_SDP**, 0 (SDP Only), None, Not required **BT_SECURITY_LOW**, 1 (Low), Unauthenticated, Not required **BT_SECURITY_MEDIUM**, 2 (Medium - default), Unauthenticated, Desired **BT_SECURITY_HIGH**, 3 (High), Authenticated, Required **BT_SECURITY_FIPS** (since Linux 3.15), 4 (Secure Only), Authenticated (P-256 based Secure Simple Pairing and Secure Authentication), Required Example: .. code-block:: int level = BT_SECURITY_HIGH; int err = setsockopt(iso_socket, SOL_BLUETOOTH, BT_SECURITY, &level, sizeof(level)); if (err == -1) { perror("setsockopt"); return 1; } BT_DEFER_SETUP -------------- Channel defer connection setup, this control if the connection procedure needs to be authorized by userspace before responding which allows authorization at profile level, possible values: .. csv-table:: :header: "Value", "Description", "Authorization" :widths: auto **0**, Disable (default), Not required **1**, Enable, Required Example: .. code-block:: int defer_setup = 1; int err = setsockopt(iso_socket, SOL_BLUETOOTH, BT_DEFER_SETUP, &defer_setup, sizeof(defer_setup)); if (err == -1) { perror("setsockopt"); return err; } err = listen(iso_socket, 5); if (err) { perror("listen"); return err; } struct sockaddr_iso remote_addr = {0}; socklen_t addr_len = sizeof(remote_addr); int new_socket = accept(iso_socket, (struct sockaddr*)&remote_addr, &addr_len); if (new_socket < 0) { perror("accept"); return new_socket; } /* To complete the connection setup of new_socket read 1 byte */ char c; struct pollfd pfd; memset(&pfd, 0, sizeof(pfd)); pfd.fd = new_socket; pfd.events = POLLOUT; err = poll(&pfd, 1, 0); if (err) { perror("poll"); return err; } if (!(pfd.revents & POLLOUT)) { err = read(sk, &c, 1); if (err < 0) { perror("read"); return err; } } BT_PKT_STATUS ------------- Enable reporting packet status via `BT_SCM_PKT_STATUS` CMSG on received packets. Possible values: .. csv-table:: :header: "Value", "Description" :widths: auto **0**, Disable (default) **1**, Enable :BT_SCM_PKT_STATUS: Level ``SOL_BLUETOOTH`` CMSG with data:: uint8_t pkt_status; The values are equal to the "Packet_Status_Flag" defined in Core Specification v6.1, 5.4.5. HCI ISO Data packets: https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-61/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-9b5fb085-278b-5084-ac33-bee2839abe6b .. csv-table:: :header: "pkt_status", "Description" :widths: auto **0x0**, "Valid data. The complete SDU was received correctly." **0x1**, "Possibly invalid data. The contents of the ISO_SDU_Fragment, may contain errors or part of the SDU may be missing. This is reported as 'data with possible errors'." **0x2**, "Part(s) of the SDU were not received correctly. This is reported as 'lost data'." BT_PKT_SEQNUM (since Linux 6.17-rc1) ------------------------------------ Enable reporting packet ISO sequence number via `BT_SCM_PKT_SEQNUM` CMSG on received packets. Possible values: .. csv-table:: :header: "Value", "Description" :widths: auto **0**, Disable (default) **1**, Enable :BT_SCM_PKT_SEQNUM: Level ``SOL_BLUETOOTH`` CMSG with data:: uint16_t pkt_seqnum; The values are equal to the "Packet_Sequence_Number" defined in Core Specification v6.1, 5.4.5. HCI ISO Data packets: https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-61/out/en/host-controller-interface/host-controller-interface-functional-specification.html#UUID-9b5fb085-278b-5084-ac33-bee2839abe6b Example (Enable sequence numbers): .. code-block:: uint32_t opt = 1; if (setsockopt(fd, SOL_BLUETOOTH, BT_PKT_SEQNUM, &opt, sizeof(opt)) < 0) goto error; Example (Read packet and its sequence number): .. code-block:: char data_buf[256]; uint16_t seqnum; union { char buf[CMSG_SPACE(sizeof(uint16_t))]; struct cmsghdr align; } control; struct iovec data = { .iov_base = data_buf, .iov_len = sizeof(data_buf), }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct scm_timestamping tss; res = recvmsg(fd, &msg, 0); if (res < 0) goto error; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_BLUETOOTH && cmsg->cmsg_type == BT_PKT_SEQNUM) memcpy(&seqnum, CMSG_DATA(cmsg), sizeof(seqnum)); } SOCKET OPTIONS (SOL_SOCKET) =========================== ``SOL_SOCKET`` level socket options that modify generic socket features (``SO_SNDBUF``, ``SO_RCVBUF``, etc.) have their usual meaning, see **socket(7)**. The ``SOL_SOCKET`` level ISO socket options that have Bluetooth-specific handling in kernel are listed below. SO_TIMESTAMPING, SO_TIMESTAMP, SO_TIMESTAMPNS --------------------------------------------- See https://docs.kernel.org/networking/timestamping.html For ISO sockets, software RX timestamps are supported. Software TX timestamps (SOF_TIMESTAMPING_TX_SOFTWARE) are supported since Linux 6.15. The software RX timestamp is the time when the kernel received the packet from the controller driver. The ``SCM_TSTAMP_SND`` timestamp is emitted when packet is sent to the controller driver. The ``SCM_TSTAMP_COMPLETION`` timestamp is emitted when controller reports the packet completed. Completion timestamps are only supported on controllers that have ISO flow control. Other TX timestamp types are not supported. You can use ``SIOCETHTOOL`` to query supported flags. The timestamps are in ``CLOCK_REALTIME`` time. Example (Enable RX timestamping): .. code-block:: int flags = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE; setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)); Example (Read packet and its RX timestamp): .. code-block:: char data_buf[256]; union { char buf[CMSG_SPACE(sizeof(struct scm_timestamping))]; struct cmsghdr align; } control; struct iovec data = { .iov_base = data_buf, .iov_len = sizeof(data_buf), }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct scm_timestamping tss; res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT); if (res < 0) goto error; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); } tstamp_clock_realtime = tss.ts[0]; Example (Enable TX timestamping): .. code-block:: int flags = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_OPT_ID; setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)); Example (Read TX timestamps): .. code-block:: union { char buf[CMSG_SPACE(sizeof(struct scm_timestamping))]; struct cmsghdr align; } control; struct iovec data = { .iov_base = NULL, .iov_len = 0 }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct cmsghdr *cmsg; struct scm_timestamping tss; struct sock_extended_err serr; int res; res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT); if (res < 0) goto error; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); else if (cmsg->cmsg_level == SOL_BLUETOOTH && cmsg->cmsg_type == BT_SCM_ERROR) memcpy(&serr, CMSG_DATA(cmsg), sizeof(serr)); } tstamp_clock_realtime = tss.ts[0]; tstamp_type = serr->ee_info; /* SCM_TSTAMP_SND or SCM_TSTAMP_COMPLETION */ tstamp_seqnum = serr->ee_data; IOCTLS ====== The following ioctls with operation specific for ISO sockets are available. SIOCETHTOOL (since Linux 6.16-rc1) ---------------------------------- Supports only command `ETHTOOL_GET_TS_INFO`, which may be used to query supported `SOF_TIMESTAMPING_*` flags. The `SOF_TIMESTAMPING_OPT_*` flags are always available as applicable. Example: .. code-block:: #include #include #include #include #include ... struct ifreq ifr = {}; struct ethtool_ts_info cmd = {}; int sk; snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "hci0"); ifr.ifr_data = (void *)&cmd; cmd.cmd = ETHTOOL_GET_TS_INFO; sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO); if (sk < 0) goto error; if (ioctl(sk, SIOCETHTOOL, &ifr)) goto error; sof_available = cmd.so_timestamping; RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org SEE ALSO ======== socket(7), isotest(1) bluez-5.87/doc/PaxHeaders/org.bluez.ThermometerWatcher.rst0000644000000000000000000000005015142177620020676 xustar0020 atime=1783094034 20 ctime=1783094112 bluez-5.87/doc/org.bluez.ThermometerWatcher.rst0000644000000000000000000000317415142177620020364 0ustar00rootroot============================ org.bluez.ThermometerWatcher ============================ -------------------------------------------------------- BlueZ D-Bus Health Thermometer Watcher API documentation -------------------------------------------------------- :Version: BlueZ :Date: July 2011 :Author: Santiago Carot-Nemesio :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: unique name :Interface: org.bluez.ThermometerWatcher1 :Object path: freely definable Methods ------- void MeasurementReceived(dict measurement) `````````````````````````````````````````` This callback gets called when a measurement has been scanned in the thermometer. Possible measurement values: :int16 Exponent: :int32 Mantissa: Exponent and Mantissa values as extracted from float value defined by IEEE-11073-20601. Measurement value is calculated as (Mantissa) * (10^Exponent) For special cases Exponent is set to 0 and Mantissa is set to one of following values: .. csv-table:: :header: "Value", "Description" :widths: auto +(2^23 - 1), NaN (invalid or missing data) -(2^23), NRes +(2^23 - 2), +Infinity -(2^23 - 2), -Infinity :string Unit: Possible values: "celsius" or "fahrenheit" :uint64 Time (optional): Time of measurement, if supported by device. Expressed in seconds since epoch. :string Type (optional): Only present if measurement type is known. Possible values: "armpit", "body", "ear", "finger", "intestines", "mouth", "rectum", "toe", "tympanum" :string Measurement: Possible values: "final" or "intermediate" bluez-5.87/doc/PaxHeaders/bluetoothd.80000644000000000000000000000005015221755234014670 xustar0020 atime=1783093916 20 ctime=1783094112 bluez-5.87/doc/bluetoothd.80000644000000000000000000000707015221755234014355 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "bluetoothd" "8" "March, 2004" "BlueZ" "System management commands" .SH Name bluetoothd \- Bluetooth daemon .SH SYNOPSIS .sp \fBbluetoothd\fP [\-\-version] | [\-\-help] .sp \fBbluetoothd\fP [\-\-nodetach] [\-\-compat] [\-\-experimental] [\-\-debug=<\fIfiles\fP>] [\-\-plugin=<\fIplugins\fP>] [\-\-noplugin=<\fIplugins\fP>] .SH DESCRIPTION .sp This manual page documents briefly the \fBbluetoothd\fP daemon, which manages all the Bluetooth devices. \fBbluetoothd\fP can also provide a number of services via the D\-Bus message bus system. .SH OPTIONS .INDENT 0.0 .TP .B \-v\fP,\fB \-\-version Print bluetoothd version and exit. .TP .B \-h\fP,\fB \-\-help Print bluetoothd options and exit. .TP .B \-n\fP,\fB \-\-nodetach Enable logging in foreground. Directs log output to the controlling terminal in addition to syslog. .TP .B \-f\fP,\fB \-\-configfile Specifies an explicit config file path instead of relying on the default path(\fI/usr/local/etc/bluetooth/main.conf\fP) for the config file. .UNINDENT .INDENT 0.0 .TP .B \-d, \-\-debug=::... Sets how much information bluetoothd sends to the log destination (usually syslog\(aqs \(dqdaemon\(dq facility). If the file options are omitted, then debugging information from all the source files are printed. If file options are present, then only debug prints from that source file are printed. The option can be a pattern containing \(dq*\(dq and \(dq?\(dq characters. .sp Example: \-\-debug=src/adapter.c:src/agent.c .TP .B \-p, \-\-plugin=,,.. Load these plugins only. The option can be a pattern containing \(dq*\(dq and \(dq?\(dq characters. .TP .B \-P, \-\-noplugin=,,.. Never load these plugins. The option can be a pattern containing \(dq*\(dq and \(dq?\(dq characters. .UNINDENT .INDENT 0.0 .TP .B \-C\fP,\fB \-\-compat Provide deprecated command line interfaces. .TP .B \-E\fP,\fB \-\-experimental Enable D\-Bus experimental interfaces. These interfaces are not guaranteed to be compatible or present in future releases. .TP .B \-T\fP,\fB \-\-testing Enable D\-Bus testing interfaces. These interfaces are only meant for test validation of the internals of bluetoothd and shall not never be used by anything other than that. .UNINDENT .INDENT 0.0 .TP .B \-K, \-\-kernel=,,... Enable Kernel experimental features. Kernel experimental features are considered unstable and may be removed from future kernel releases. .UNINDENT .SH FILES .INDENT 0.0 .TP .B \fI/usr/local/etc/bluetooth/main.conf\fP Location of the global configuration file. .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Author Marcel Holtmann, Philipp Matthias Hahn, Fredrik Noring .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.ThermometerManager.rst0000644000000000000000000000005015142177620020653 xustar0020 atime=1783094030 20 ctime=1783094112 bluez-5.87/doc/org.bluez.ThermometerManager.rst0000644000000000000000000000303115142177620020331 0ustar00rootroot============================ org.bluez.ThermometerManager ============================ ------------------------------------------------------- BlueZ D-Bus Health ThermometerManager API documentation ------------------------------------------------------- :Version: BlueZ :Date: July 2011 :Author: Santiago Carot-Nemesio :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.ThermometerManager1 :Object path: [variable prefix]/{hci0,hci1,...} Methods ------- void RegisterWatcher(object agent) `````````````````````````````````` Registers a watcher to monitor scanned measurements. This agent will be notified about final temperature measurements. Possible Errors: :org.bluez.Error.InvalidArguments: void UnregisterWatcher(object agent) ```````````````````````````````````` Unregisters a watcher. void EnableIntermediateMeasurement(object agent) ```````````````````````````````````````````````` Enables intermediate measurement notifications for this agent. Intermediate measurements will be enabled only for thermometers which support it. Possible Errors: :org.bluez.Error.InvalidArguments: void DisableIntermediateMeasurement(object agent) ````````````````````````````````````````````````` Disables intermediate measurement notifications for this agent. It will disable notifications in thermometers when the last agent removes the watcher for intermediate measurements. Possible Errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.NotFound: bluez-5.87/doc/PaxHeaders/org.bluez.MediaTransport.rst0000644000000000000000000000005015200607060020010 xustar0020 atime=1783092683 20 ctime=1783094111 bluez-5.87/doc/org.bluez.MediaTransport.rst0000644000000000000000000001675315200607060017505 0ustar00rootroot======================== org.bluez.MediaTransport ======================== -------------------------------------------- BlueZ D-Bus MediaTransport API documentation -------------------------------------------- :Version: BlueZ :Date: July 2024 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.MediaTransport1 :Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/fd# :Used by: **bluetoothctl-transport(1)** Methods ------- fd, uint16, uint16 Acquire() ```````````````````````````` Acquire transport file descriptor and the MTU for read and write respectively. Possible Errors: :org.bluez.Error.NotAuthorized: :org.bluez.Error.Failed: Examples: :bluetoothctl: > transport.acquire [transport1...] fd, uint16, uint16 TryAcquire() ``````````````````````````````` Acquire transport file descriptor only if the transport is in "pending" state at the time the message is received by BlueZ. Otherwise no request will be sent to the remote device and the function will just fail with **org.bluez.Error.NotAvailable**. Possible Errors: :org.bluez.Error.NotAuthorized: :org.bluez.Error.Failed: :org.bluez.Error.NotAvailable: void Release() `````````````` Releases file descriptor. Examples: :bluetoothctl: > transport.release [transport1...] void Select() ````````````` Applicable only for transports created by a broadcast sink. This moves the transport from "idle" to "broadcasting". This allows callers to select which BISes he wishes to sync to via a 2 step process: 1. the user calls the method, changing the transport's state to broadcasting 2. the audio server detects that the transport is in the 'broadcasting' state and automatically acquires it Possible Errors: :org.bluez.Error.NotAuthorized: Examples: :bluetoothctl: > transport.select [transport1...] void Unselect() ``````````````` Applicable only for transports created by a broadcast sink. This moves the transport from "broadcasting" or "active" to "idle". This allows callers to terminate the sync to a BIS to via a 2 step process: 1. the user calls this method, changing the transport's state to idle 2. the audio server detects this event and releases the transport Possible Errors: :org.bluez.Error.NotAuthorized: Examples: :bluetoothctl: > transport.unselect [transport1...] Properties ---------- object Device [readonly] ```````````````````````` Device object which the transport is connected to. string UUID [readonly] `````````````````````` UUID of the profile which the transport is for. byte Codec [readonly] ````````````````````` Assigned number of codec that the transport support. The values should match the profile specification which is indicated by the UUID. array{byte} Configuration [readonly] ```````````````````````````````````` Configuration blob, it is used as it is so the size and byte order must match. string State [readonly] ``````````````````````` Indicates the state of the transport. Possible values are: :"idle": not streaming :"pending": streaming but not acquired :"broadcasting": streaming but not acquired, applicable only for transports created by a broadcast sink :"active": streaming and acquired uint16 Delay [readwrite, optional] `````````````````````````````````` Transport delay in 1/10 of millisecond, this property is only writeable when the transport corresponds to a sink endpoint and it was acquired by the sender. uint16 Volume [readwrite, optional] ``````````````````````````````````` Indicates volume level of the transport, this property is only writeable when the transport was acquired by the sender. Possible Values: :0-127 (A2DP): :0-255 (BAP): Examples: :bluetoothctl: > transport.volume [value] object Endpoint [readonly, optional, experimental] `````````````````````````````````````````````````` Endpoint object which the transport is associated with. uint32 Location [readonly, ISO only, experimental] `````````````````````````````````````````````````` Indicates transport Audio Location. array{byte} Metadata [readwrite, ISO Only, experimental] ```````````````````````````````````````````````````````` Indicates transport Metadata. array{object} Links [readonly, optional, CIS only, experimental] ```````````````````````````````````````````````````````````````` Linked transport objects which the transport is associated with. array{object} Links [readwrite, BIS only, experimental] ``````````````````````````````````````````````````````` For a Broadcast Sink, the BIG sync procedure requires all desired streams to be enumerated from the start and it cannot be later reconfigured by adding or removing BISes. To avoid terminating and recreating the BIG sync every time a new transport is selected for acquire, all transports selected via **Select** need to be linked together. When the first transport is acquired via Transport.Acquire, all links are included in the BIG sync command. An acquired transport will create and set fds for all its links. Then, each link needs to be acquired separately, to get the fd and start receiving audio. dict QoS [readwrite, optional, ISO only, experimental] `````````````````````````````````````````````````````` Only present when QoS is configured. Possible values for Unicast: :byte CIG: Indicates configured CIG. Possible values: :0x00 - 0xef: Valid ID range. :0xff: Auto allocate. :byte CIS: Indicates configured CIS. Possible values: :0x00 - 0xef: Valid ID range. :0xff: Auto allocate. :byte Framing: Indicates configured framing. Possible values: :0x00: Unframed. :0x01: Framed. :uint32 PresentationDelay: Indicates configured transport presentation delay (us). :byte TargetLatency: Indicates the requested target latency. Possible values: :0x01: Low Latency. :0x02: Balanced Latency/Reliability. :0x03: High Reliability. Possible values for Broadcast: :byte BIG: Indicates configured QoS BIG. :byte BIS: Indicates configured BIS. :byte SyncFactor: Indicates configured broadcast sync factor. :byte Packing: Indicates configured packing. :byte Framing: Indicates configured framing. :array{byte} BCode: Indicates the string used for encryption/decryption. :byte encryption: Indicates if the stream is encrypted. :byte Options: Indicates configured broadcast options. :uint16 Skip: Indicates configured broadcast skip. :byte SyncTimeout: Indicates configured broadcast sync timeout. :byte SyncType: Indicates configured broadcast sync CTE type. :byte MSE: Indicates configured broadcast MSE. :uint16 Timeout: Indicates configured broadcast timeout. Possible values for both Unicast and Broadcast: :uint32 Interval: Indicates configured ISO interval (us). :uint16 Latency: Indicates configured transport latency (ms). :uint16 SDU: Indicates configured maximum SDU. :byte PHY: Indicates configured PHY. Possible values: :bit 0: LE 1M :bit 1: LE 2M :bit 2: LE Coded :byte Retransmissions: Indicates configured retransmissions. boolean Desynchronized [readwrite, optional, CIS only, testing, experimental] ````````````````````````````````````````````````````````````````````````````` Available only when D-Bus testing interfaces are enabled and the transport has the **Links** property. Controls whether linked transports are acquired together. When set to False (default), acquiring one linked transport automatically acquires all links. When set to True, links are not acquired automatically and each link must be acquired separately. bluez-5.87/doc/PaxHeaders/org.bluez.GattCharacteristic.rst0000644000000000000000000000005015142177620020635 xustar0020 atime=1783093992 20 ctime=1783094112 bluez-5.87/doc/org.bluez.GattCharacteristic.rst0000644000000000000000000002232215142177620020317 0ustar00rootroot============================ org.bluez.GattCharacteristic ============================ ------------------------------------------------ BlueZ D-Bus GattCharacteristic API documentation ------------------------------------------------ :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Description =========== GATT local/server and remote/client characteristic attribute representation share the same high-level D-Bus API. Local/Server refers to GATT based characteristics exported by a plugin or an external application. Remote/Client refers to GATT characteristics exported by the peer. Interface ========= Client ------ :Service: org.bluez :Interface: org.bluez.GattCharacteristic1 :Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/service#/char# :Used by: **bluetoothctl-gatt(1)** Server ------ :Service: unique name :Interface: org.bluez.GattCharacteristic1 :Object path: freely definable Methods ------- array{byte} ReadValue(dict options) ``````````````````````````````````` Issues a request to read the value of the characteristic and returns the value if the operation was successful. Possible options: :uint16 offset: Read start offset in bytes. :uint16 mtu (server only): Exchange MTU in bytes. :object device (server only): Device object. :string link (server only): Link type. Possible values: :"BR/EDR": :"LE": Possible Errors: :org.bluez.Error.Failed: Possible values: string 0x80 - 0x9f :org.bluez.Error.InProgress: :org.bluez.Error.NotPermitted: :org.bluez.Error.NotAuthorized: :org.bluez.Error.InvalidOffset: :org.bluez.Error.NotSupported: Examples: :bluetoothctl: > gatt.read [offset] void WriteValue(array{byte} value, dict options) ```````````````````````````````````````````````` Issues a request to write the value of the characteristic. Possible options: :uint16 offset: Write start offset in bytes. :string type: Possible values: :"command": Use Write without response procedure. :"request": Use Write with response procedure. :"reliable": Use Reliable Write procedure. :uint16 mtu: Exchanged MTU (Server only). :object device: Device path (Server only). :string link: Link type (Server only). Possible values: :"BR/EDR": :"LE": :boolean prepare-authorize: True if prepare authorization request. Possible Errors: :org.bluez.Error.Failed: Possible values: string 0x80 - 0x9f :org.bluez.Error.InProgress: :org.bluez.Error.NotPermitted: :org.bluez.Error.InvalidValueLength: :org.bluez.Error.NotAuthorized: :org.bluez.Error.NotSupported: :org.bluez.Error.ImproperlyConfigured: Examples: :bluetoothctl: > gatt.write [offset] [type] fd, uint16 AcquireWrite(dict options) [optional] ```````````````````````````````````````````````` Acquire file descriptor and MTU for writing. Only sockets are supported. Usage of WriteValue will be locked causing it to return NotPermitted error. For server the MTU returned shall be equal or smaller than the negotiated MTU. For client it only works with characteristic that has **WriteAcquired** property which relies on write-without-response **Flag**. To release the lock the client shall close the file descriptor, a HUP is generated in case the device is disconnected. Note: the MTU can only be negotiated once and is symmetric therefore this method may be delayed in order to have the exchange MTU completed, because of that the file descriptor is closed during reconnections as the MTU has to be renegotiated. Possible options: :object device: Object Device (Server only). :uint16 mtu: Exchanged MTU (Server only). :string link: Link type (Server only). Possible values: :"BR/EDR": :"LE": Possible Errors: :org.bluez.Error.Failed: :org.bluez.Error.NotSupported: Examples: :bluetoothctl: > gatt.acquire-write fd, uint16 AcquireNotify(dict options) [optional] ````````````````````````````````````````````````` Acquire file descriptor and MTU for notify. Only sockets are support. Usage of StartNotify will be locked causing it to return **org.bluez.Error.NotPermitted**. For server the MTU returned shall be equal or smaller than the negotiated MTU. Only works with characteristic that has **NotifyAcquired** property which relies on presence of **"notify" or "indicate"** **Flag** and no other client have called **StartNotify()**. Notification are enabled during this procedure so **StartNotify()** shall not be called, any notification will be dispatched via file descriptor therefore the Value property is not affected during the time where notify has been acquired. To release the lock the client shall close the file descriptor, a HUP is generated in case the device is disconnected. As a client if indication procedure is used the confirmation is generated automatically once received, for a server if the file descriptor is writable (POLLOUT) then upon receiving a confirmation from the client one byte (0x01) is written to the file descriptor. Note: the MTU can only be negotiated once and is symmetric therefore this method may be delayed in order to have the exchange MTU completed, because of that the file descriptor is closed during reconnections as the MTU has to be renegotiated. Possible options: :object device: Object Device (Server only). :uint16 mtu: Exchanged MTU (Server only). :string link: Link type (Server only). Possible values: :"BR/EDR": :"LE": Possible Errors: :org.bluez.Error.Failed: :org.bluez.Error.NotSupported: :org.bluez.Error.NotPermitted: Examples: :bluetoothctl: > gatt.acquire-notify void StartNotify() `````````````````` Starts a notification session from this characteristic if it supports value notifications or indications. Possible Errors: :org.bluez.Error.Failed: :org.bluez.Error.NotPermitted: :org.bluez.Error.InProgress: :org.bluez.Error.NotConnected: :org.bluez.Error.NotSupported: Examples: :bluetoothctl: > gatt.notify void StopNotify() ````````````````` Stops or cancel session previously created by **StartNotify()**. Note that notifications from a characteristic are shared between sessions thus calling StopNotify will release a single session. Possible Errors: :org.bluez.Error.Failed: void Confirm() [noreply, optional] (Server only) ```````````````````````````````````````````````` Confirms value was received. Possible Errors: org.bluez.Error.Failed Properties ---------- string UUID [read-only] ``````````````````````` 128-bit characteristic UUID. object Service [read-only] `````````````````````````` Object path of the GATT service the characteristic belongs to. array{byte} Value [read-only, optional] ``````````````````````````````````````` The cached value of the characteristic. This property gets updated only after a successful read request and when a notification or indication is received, upon which a PropertiesChanged signal will be emitted. boolean WriteAcquired [read-only, optional] ``````````````````````````````````````````` True, if this characteristic has been acquired by any client using AcquireWrite. For client properties is omitted in case 'write-without-response' flag is not set. For server the presence of this property indicates that AcquireWrite is supported. boolean NotifyAcquired [read-only, optional] ```````````````````````````````````````````` True, if this characteristic has been acquired by any client using AcquireNotify. For client this properties is omitted in case 'notify' flag is not set. For server the presence of this property indicates that AcquireNotify is supported. boolean Notifying [read-only, optional] ``````````````````````````````````````` True, if notifications or indications on this characteristic are currently enabled. array{string} Flags [read-only] ``````````````````````````````` Defines how the characteristic value can be used. See Core spec "Table 3.5: Characteristic Properties bit field", and "Table 3.8: Characteristic Extended Properties bit field". The "x-notify" and "x-indicate" flags restrict access to notifications and indications by imposing write restrictions on a characteristic's client characteristic configuration descriptor. Possible values: :"broadcast": :"read": :"write-without-response": :"write": :"notify": :"indicate": :"authenticated-signed-writes": :"extended-properties": :"reliable-write": :"writable-auxiliaries": :"encrypt-read": :"encrypt-write": :"encrypt-notify" (Server only): :"encrypt-indicate" (Server only): :"encrypt-authenticated-read": :"encrypt-authenticated-write": :"encrypt-authenticated-notify" (Server only): :"encrypt-authenticated-indicate" (Server only): :"secure-read" (Server only): :"secure-write" (Server only): :"secure-notify" (Server only): :"secure-indicate" (Server only): :"authorize": uint16 Handle [read-only] (Client Only) ``````````````````````````````````````` Characteristic handle. uint16 Handle [read-write, optional] (Server Only) `````````````````````````````````````````````````` Characteristic handle. When available in the server it would attempt to use to allocate into the database which may fail, to auto allocate the value 0x0000 shall be used which will cause the allocated handle to be set once registered. uint16 MTU [read-only] `````````````````````` Characteristic MTU, this is valid both for **ReadValue()** and **WriteValue()** but either method can use long procedures when supported. bluez-5.87/doc/PaxHeaders/bluetoothctl.rst0000644000000000000000000000005015142177620015667 xustar0020 atime=1783094050 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl.rst0000644000000000000000000004323615142177620015360 0ustar00rootroot============ bluetoothctl ============ ----------------------------------- Bluetooth Control Command Line Tool ----------------------------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: March 2024 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **bluetoothctl** [**-a** *capability*] [**-e**] [**-m**] [**-t** *seconds*] [**-v**] [**-h**] DESCRIPTION =========== **bluetoothctl(1)** interactive bluetooth control tool. The tool works with Bluetooth Classic (BR/EDR) and Bluetooth Low Energy (LE) controllers. The tool is menu driven but can be automated from the command line. Examples are given in the automation section. This tool communicates with the BlueZ daemon via D-Bus, primarily using the **org.bluez.Adapter(5)** and **org.bluez.Device(5)** interfaces. Each command documents which D-Bus interface and method/property it uses. OPTIONS ======= -a capability, --agent capability Register agent handler: -e, --endpoints Register Media endpoints -m, --monitor Enable monitor output -t seconds, --timeout seconds Timeout in seconds for non-interactive mode -v, --version Display version -h, --help Display help Commands ======== list ---- List available controllers. :Usage: **> list** :Example Display all Bluetooth controllers available on the system: | **> list** show ---- Controller information. :Usage: **> show [ctrl]** :Uses: **org.bluez.Adapter(5)** properties :[ctrl]: Bluetooth controller address :Example Display information for currently selected controller: | **> show** :Example Show information for controller 00:11:22:33:44:55: | **> show 00:11:22:33:44:55** select ------ Select default controller. :Usage: **> select ** :: Bluetooth controller address :Example Select controller 00:11:22:33:44:55: | **> select 00:11:22:33:44:55** devices ------- List available devices, with an optional property as the filter. :Usage: **> devices [Paired/Bonded/Trusted/Connected]** :[Paired/Bonded/Trusted/Connected]: Filter to show only devices with specific property (optional) :Example List all discovered devices: | **> devices** :Example List only paired devices: | **> devices Paired** :Example List only bonded devices: | **> devices Bonded** :Example List only trusted devices: | **> devices Trusted** :Example List only currently connected devices: | **> devices Connected** system-alias ------------ Set controller alias. :Usage: **> system-alias ** :Uses: **org.bluez.Adapter(5)** property **Alias** :: New alias name for the Bluetooth controller (required) :Example Set controller alias with spaces (note quotes): | **> system-alias "My Desktop Bluetooth"** :Example Set controller alias without spaces: | **> system-alias MyLaptop** :Example Set controller alias with hyphens: | **> system-alias "Home-Office-PC"** reset-alias ----------- Reset controller alias. :Usage: **> reset-alias** :Uses: **org.bluez.Adapter(5)** property **Alias** :Example Reset controller alias to default (usually the hostname): | **> reset-alias** power ----- Set controller power. When the controller is powered off, the USB port the controller is attached to is put into a suspend state. :Usage: **> power ** :Uses: **org.bluez.Adapter(5)** property **Powered** :: Power state - "on" to enable or "off" to disable the controller (required) :Example Turn on the Bluetooth controller: | **> power on** :Example Turn off the Bluetooth controller: | **> power off** advertise --------- Enable/disable advertising with given type. When the controller advertises an LE device (peripheral) it will generate a random address for its own privacy if the controller is capable of it, it will use its public address if it does not support the feature (address of the device). A device can advertise if it initiated the connection to another advertising device. :Usage: **> advertise ** :Uses: **org.bluez.LEAdvertisingManager(5)** method **RegisterAdvertisement** :: Advertising mode - "on", "off", "peripheral", or "broadcast" (required) :Example Enable basic LE advertising: | **> advertise on** :Example Disable LE advertising: | **> advertise off** :Example Advertise as peripheral device: | **> advertise peripheral** :Example Advertise as broadcast source: | **> advertise broadcast** set-alias --------- Set device alias. :Usage: **> set-alias ** :Uses: **org.bluez.Device(5)** property **Alias** :: New alias name for the device (required) :Example Set device alias with spaces (note quotes): | **> set-alias "My Headphones"** :Example Set device alias without spaces: | **> set-alias MyMouse** :Example Set device alias with hyphens: | **> set-alias "Kitchen-Speaker"** scan ---- Scan for devices. For LE, scanning is an important requirement before connecting or pairing. The purpose of scanning is to find devices that are advertising with their discoverable flag set (either limited or general). Once you have found the address then you can connect or pair. :Usage: **> scan ** :Uses: **org.bluez.Adapter(5)** methods **StartDiscovery**, **StopDiscovery** :: Scan mode - "on", "off", "bredr" for Classic, or "le" for Low Energy (required) :Example Start scanning for all device types (LE + Classic): | **> scan on** :Example Stop scanning: | **> scan off** :Example Scan for Low Energy devices only: | **> scan le** :Example Scan for BR/EDR (Classic) devices only: | **> scan bredr** pair ---- Pair with device. This will pair with a device and then trust and connect to it. If the device is already paired this will first remove the pairing. The command can either be used while the controller is in the connected or not connected state. If the controller is already connected then the pair command can be used without an arguments. If the controller is not connected, the pair command can be given the address of a device with an active scan report and it will initiate the connection before pairing. Before pairing the agent must be selected to choose the authentication mechanism. :Usage: **> pair [dev]** :Uses: **org.bluez.Device(5)** method **Pair** :[dev]: Device MAC address (XX:XX:XX:XX:XX:XX) (optional, uses current device if omitted) :Example Pair with device using MAC address: | **> pair 00:11:22:33:44:55** :Example Pair with another device: | **> pair AA:BB:CC:DD:EE:FF** :Example Pair using device object path: | **> pair /org/bluez/hci0/dev_00_11_22_33_44_55** pairable -------- Set controller pairable mode. This enables/disables pairing. If pairing is disabled then the controller will not accept any pairing requests. :Usage: **> pairable ** :Uses: **org.bluez.Adapter(5)** property **Pairable** :: Pairable mode - "on" to accept or "off" to reject pairing requests (required) :Example Enable pairing (accept pairing requests): | **> pairable on** :Example Disable pairing (reject pairing requests): | **> pairable off** discoverable ------------ Set discoverable mode. This enables/disables discoverable mode. If discoverable is disabled then the controller will not respond to any scan requests. In LE if discoverable if off the controller will just passively scan and not make scan requests to advertising devices. If on it will make the advertising requests. It will use a random address if supported by the controller. The length of time "discoverable on" is valid is determined by discoverable-timeout command. :Usage: **> discoverable ** :Uses: **org.bluez.Adapter(5)** property **Discoverable** :: Discoverable mode - "on" to be discoverable or "off" to be non-discoverable (required) :Example Make controller discoverable to other devices: | **> discoverable on** :Example Make controller non-discoverable: | **> discoverable off** discoverable-timeout -------------------- Set discoverable timeout. The time in seconds that "discoverable on" is valid. :Usage: **> discoverable-timeout [value]** :Uses: **org.bluez.Adapter(5)** property **DiscoverableTimeout** :[value]: Timeout duration in seconds for discoverable mode (0 for unlimited, optional) :Example Show current discoverable timeout value: | **> discoverable-timeout** :Example Set discoverable timeout to 30 seconds: | **> discoverable-timeout 30** :Example Set discoverable timeout to 2 minutes: | **> discoverable-timeout 120** :Example Set discoverable timeout to 5 minutes: | **> discoverable-timeout 300** :Example Set unlimited discoverable timeout: | **> discoverable-timeout 0** agent ----- Enable/disable agent with given capability. This chooses the local authentication mechanism of the controller. It is needed for pairing and allows you to choose the IO capabilities of the controller. The valid agent capabilities are: DisplayOnly, DisplayYesNo, KeyboardDisplay, KeyboardOnly, NoInputNoOutput. :Usage: **> agent ** :Uses: **org.bluez.AgentManager(5)** method **RegisterAgent** :: Agent mode - "on", "off", "auto", or capability type (DisplayOnly, DisplayYesNo, KeyboardDisplay, KeyboardOnly, NoInputNoOutput) (required) :Example Enable agent with default capability: | **> agent on** :Example Disable agent: | **> agent off** :Example Enable agent that can only display pairing codes: | **> agent DisplayOnly** :Example Enable agent that can display codes and accept/reject: | **> agent DisplayYesNo** :Example Enable agent that can display and input pairing codes: | **> agent KeyboardDisplay** :Example Enable agent that can only input pairing codes: | **> agent KeyboardOnly** :Example Enable agent with no input/output (JustWorks): | **> agent NoInputNoOutput** default-agent ------------- Set current agent as the default one. After selecting the agent this will make it the default agent. :Usage: **> default-agent** :Uses: **org.bluez.AgentManager(5)** method **RequestDefaultAgent** :Example Set the current agent as default: | **> default-agent** trust ----- Trust device. :Usage: **> trust [dev]** :Uses: **org.bluez.Device(5)** property **Trusted** :[dev]: Device MAC address (XX:XX:XX:XX:XX:XX) or object path (optional, uses current device if omitted) :Example Trust device using MAC address: | **> trust 00:11:22:33:44:55** :Example Trust another device: | **> trust AA:BB:CC:DD:EE:FF** :Example Trust device using object path: | **> trust /org/bluez/hci0/dev_00_11_22_33_44_55** untrust ------- Untrust device. :Usage: **> untrust [dev]** :Uses: **org.bluez.Device(5)** property **Trusted** :[dev]: Device MAC address (XX:XX:XX:XX:XX:XX) or object path (optional, uses current device if omitted) :Example Remove trust from device using MAC address: | **> untrust 00:11:22:33:44:55** :Example Remove trust from another device: | **> untrust AA:BB:CC:DD:EE:FF** :Example Remove trust using object path: | **> untrust /org/bluez/hci0/dev_00_11_22_33_44_55** block ----- Block device. :Usage: **> block [dev]** :Uses: **org.bluez.Device(5)** property **Blocked** :[dev]: Device MAC address (XX:XX:XX:XX:XX:XX) or object path (optional, uses current device if omitted) :Example Block device using MAC address: | **> block 00:11:22:33:44:55** :Example Block another device: | **> block AA:BB:CC:DD:EE:FF** :Example Block device using object path: | **> block /org/bluez/hci0/dev_00_11_22_33_44_55** unblock ------- Unblock device :Usage: **> unblock [dev]** :Uses: **org.bluez.Device(5)** property **Blocked** :[dev]: Device MAC address (XX:XX:XX:XX:XX:XX) or object path (optional, uses current device if omitted) :Example Unblock device using MAC address: | **> unblock 00:11:22:33:44:55** :Example Unblock another device: | **> unblock AA:BB:CC:DD:EE:FF** :Example Unblock device using object path: | **> unblock /org/bluez/hci0/dev_00_11_22_33_44_55** remove ------ Remove device. :Usage: **> remove ** :Uses: **org.bluez.Adapter(5)** method **RemoveDevice** :: Device MAC address (XX:XX:XX:XX:XX:XX) or object path (required) :Example Remove device using MAC address: | **> remove 00:11:22:33:44:55** :Example Remove another device: | **> remove AA:BB:CC:DD:EE:FF** :Example Remove device using object path: | **> remove /org/bluez/hci0/dev_00_11_22_33_44_55** connect ------- Connect device. This will initiate a connection to a device. By default this commands tries to connect all the profiles the remote device supports and have been flagged as auto-connectable. In case when the UUID of the remote service is given only that service will be connected. The UUID can be either a short form (16-bit UUID) or a long form (128-bit UUID). There are also some special values for well-known profiles like "a2dp-sink", "a2dp-source", "hfp-hf", "hfp-ag", "ftp" or "spp". To connect with an LE device the controller must have an active scan report of the device it wants to connect to. If no advertising report is received before the timeout a le-connection-abort-by-local error will be issued. In that case either try again to connect assuming the device is advertising. :Usage: **> connect [uuid]** :Uses: **org.bluez.Device(5)** methods **Connect**, **ConnectProfile** :: Device MAC address (XX:XX:XX:XX:XX:XX) or object path (required) :[uuid]: Specific service UUID to connect to (16-bit, 128-bit UUID, or profile name like "a2dp-sink", "hfp-hf", etc.) (optional) :Example Connect to device (all supported profiles): | **> connect 1C:48:F9:9D:81:5C** :Example Connect to HSP Headset profile: | **> connect 1C:48:F9:9D:81:5C hsp-hs** :Example Connect to A2DP Sink profile: | **> connect 1C:48:F9:9D:81:5C a2dp-sink** :Example Connect to A2DP Source profile: | **> connect 1C:48:F9:9D:81:5C a2dp-source** :Example Connect to HFP Hands-Free profile: | **> connect 1C:48:F9:9D:81:5C hfp-hf** :Example Connect to HFP Audio Gateway profile: | **> connect 1C:48:F9:9D:81:5C hfp-ag** :Example Connect to File Transfer Profile: | **> connect 1C:48:F9:9D:81:5C ftp** :Example Connect to Serial Port Profile: | **> connect 1C:48:F9:9D:81:5C spp** :Example Connect using full 128-bit UUID (HSP): | **> connect 1C:48:F9:9D:81:5C 00001108-0000-1000-8000-00805f9b34fb** :Example Connect using short 16-bit UUID (HSP): | **> connect 1C:48:F9:9D:81:5C 0x1108** :Example Connect to A2DP profile using short UUID: | **> connect 1C:48:F9:9D:81:5C 0x110E** disconnect ---------- Disconnect device. By default this commands disconnects all profiles and then terminates the connection. In case when the UUID of the remote service is given only that service will be disconnected. For LE when disconnecting from an active connection the device address is not needed. :Usage: **> disconnect [dev] [uuid]** :Uses: **org.bluez.Device(5)** methods **Disconnect**, **DisconnectProfile** :[dev]: Device MAC address (XX:XX:XX:XX:XX:XX) or object path (optional, uses current device if omitted) :[uuid]: Specific service UUID to disconnect from (16-bit, 128-bit UUID, or profile name) (optional) :Example Disconnect all profiles and terminate connection: | **> disconnect 1C:48:F9:9D:81:5C** :Example Disconnect only A2DP Sink profile: | **> disconnect 1C:48:F9:9D:81:5C a2dp-sink** :Example Disconnect only HFP Hands-Free profile: | **> disconnect 1C:48:F9:9D:81:5C hfp-hf** :Example Disconnect only Serial Port Profile: | **> disconnect 1C:48:F9:9D:81:5C spp** :Example Disconnect A2DP profile using short UUID: | **> disconnect 1C:48:F9:9D:81:5C 0x110E** :Example Disconnect HSP profile using full UUID: | **> disconnect 1C:48:F9:9D:81:5C 00001108-0000-1000-8000-00805f9b34fb** info ---- Device information. :Usage: **> info [dev/set]** :Uses: **org.bluez.Device(5)** properties :[dev/set]: Device MAC address (XX:XX:XX:XX:XX:XX), object path, or DeviceSet (optional, uses current device if omitted) :Example Show detailed information for device: | **> info 1C:48:F9:9D:81:5C** :Example Show information for another device: | **> info 00:11:22:33:44:55** :Example Show device info using object path: | **> info /org/bluez/hci0/dev_1C_48_F9_9D_81_5C** bearer ------ Get/Set preferred bearer. :Usage: **> bearer [last-seen/bredr/le]** :Uses: **org.bluez.Device(5)** property **PreferredBearer** :: Device MAC address (XX:XX:XX:XX:XX:XX) or object path (required) :[last-seen/bredr/le]: Preferred bearer type - "last-seen", "bredr" for Classic, or "le" for Low Energy (optional) :Example get preferred bearer: | > bearer | PreferredBearer: last-seen :Example set preferred bearer to LE: | > bearer le | [CHG] Device PreferredBearer: le | Changing le succeeded :Example set preferred bearer to BREDR: | > bearer bredr | [CHG] Device PreferredBearer: bredr | Changing bredr succeeded Advertise Submenu ================= See **bluetoothctl-advertise(1)**. Monitor Submenu =============== See **bluetoothctl-monitor(1)** Scan Submenu ============ See **bluetoothctl-scan(1)** Gatt Submenu ============ See **bluetoothctl-gatt(1)** Admin Submenu ============= See **bluetoothctl-admin(1)** Player Submenu ============== See **bluetoothctl-player(1)** Endpoint Submenu ================ See **bluetoothctl-endpoint(1)** Transport Submenu ================= See **bluetoothctl-transport(1)** Management Submenu ================== See **bluetoothctl-mgmt(1)** Assistant Submenu ================== See **bluetoothctl-assistant(1)** LE Submenu ================== See **bluetoothctl-le(1)** BREDR Submenu ================== See **bluetoothctl-bredr(1)** AUTOMATION ========== Two common ways to automate the tool are to use Here Docs or the program expect. Using Here Docs to show information about the Bluetooth controller. .. code:: bluetoothctl <: Example: 0x26 0x01 0x01... array{string} ScanResponseServiceUUIDs [readonly, optional, experimental] ````````````````````````````````````````````````````````````````````````` List of UUIDs to include in the "Service UUID" field of the Scan Response Data. dict ScanResponseManufacturerData [readonly, optional, experimental] ```````````````````````````````````````````````````````````````````` Manufacturer Data fields to include in the Scan Response Data. Keys are the Manufacturer ID to associate with the data. array{string} ScanResponseSolicitUUIDs [readonly, optional, experimental] ````````````````````````````````````````````````````````````````````````` List of UUIDs to include in the "Service Solicitation" field of the Scan Response Data. dict ScanResponseServiceData [readonly, optional, experimental] ``````````````````````````````````````````````````````````````` Service Data elements to include in the Scan Response Data. The keys are the UUID to associate with the data. dict ScanResponseData [readonly, optional, experimental] ```````````````````````````````````````````````````````` Scan Response Data to include. Key is the advertising type and value is the data as byte array. bool Discoverable [readonly, optional] `````````````````````````````````````` Advertise as general discoverable. When present this will override adapter Discoverable property. Note: This property shall not be set when **Type** is set to "broadcast". uint16 DiscoverableTimeout [readonly, optional] ``````````````````````````````````````````````` The discoverable timeout in seconds. A value of zero means that the timeout is disabled and it will stay in discoverable/limited mode forever. Note: This property shall not be set when **Type** is set to "broadcast". array{string} Includes [readonly, optional] ``````````````````````````````````````````` List of features to be included in the advertising packet. Possible values: See **org.bluez.LEAdvertisingManager(5)** **SupportedIncludes** property. string LocalName [readonly, optional] ````````````````````````````````````` Local name to be used in the advertising report. If the string is too big to fit into the packet it will be truncated. If this property is available 'local-name' cannot be present in the **Includes**. uint16 Appearance [readonly, optional] `````````````````````````````````````` Appearance to be used in the advertising report. Possible values: as found on GAP Service. uint16 Duration [readonly, optional] ```````````````````````````````````` Rotation duration of the advertisement in seconds. If there are other applications advertising no duration is set the default is 2 seconds. uint16 Timeout [readonly, optional] ````````````````````````````````````` Timeout of the advertisement in seconds. This defines the lifetime of the advertisement. string SecondaryChannel [readonly, optional] ```````````````````````````````````````````` Secondary channel to be used. Primary channel is always set to "1M" except when "Coded" is set. Possible value: :"1M" (default): :"2M": :"Coded": uint32 MinInterval [readonly, optional] ``````````````````````````````````````` Minimum advertising interval to be used by the advertising set, in milliseconds. Acceptable values are in the range [20ms, 10,485s]. If the provided MinInterval is larger than the provided MaxInterval, the registration will return failure. uint32 MaxInterval [readonly, optional] ``````````````````````````````````````` Maximum advertising interval to be used by the advertising set, in milliseconds. Acceptable values are in the range [20ms, 10,485s]. If the provided MinInterval is larger than the provided MaxInterval, the registration will return failure. int16 TxPower [readonly, optional] `````````````````````````````````` Requested transmission power of this advertising set. The provided value is used only if the "CanSetTxPower" feature is enabled on the **org.bluez.LEAdvertisingManager(5)**. Values must be in range [-127 to +20], where units are in dBm. byte Instance [read-write, optional] `````````````````````````````````````` Instance of the advertisement. Set by bluetoothd after advertisement is registered with **org.bluez.LEAdvertisingManager(5)**. bluez-5.87/doc/PaxHeaders/bluetoothctl-gatt.10000644000000000000000000000005015221755460016156 xustar0020 atime=1783094064 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-gatt.10000644000000000000000000005121515221755460015643 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "bluetoothctl-gatt" "1" "November 2022" "BlueZ" "Linux System Administration" .SH Name bluetoothctl-gatt \- Generic Attribute Submenu .SH SYNOPSIS .sp \fBbluetoothctl\fP [\-\-options] [gatt.commands] .sp This submenu interacts with GATT services, characteristics, and descriptors using the \fBorg.bluez.GattService(5)\fP, \fBorg.bluez.GattCharacteristic(5)\fP, \fBorg.bluez.GattDescriptor(5)\fP, and \fBorg.bluez.GattManager(5)\fP interfaces. .SH Generic Attribute Commands .SS list\-attributes .sp Lists the attributes of either the local device or a remote device, encompassing services, characteristics, and handles. This command provides a comprehensive overview of the available Bluetooth attributes, which can be further interacted with using other commands. .INDENT 0.0 .TP .B Usage \fB> list\-attributes [dev/local]\fP .TP .B [dev/local] Device MAC address, object path, or \(dqlocal\(dq to list locally registered attributes (optional, lists all if omitted) .TP .B Example List attributes for specific service .nf \fB> list\-attributes /org/bluez/hci0/dev_00_11_22_33_44_55/service001a\fP .fi .sp .TP .B Example List attributes for specific characteristic .nf \fB> list\-attributes /org/bluez/hci0/dev_00_11_22_33_44_55/service001a/char001c\fP .fi .sp .TP .B Example List attributes for service on different device .nf \fB> list\-attributes /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/service0020\fP .fi .sp .TP .B Example List all Generic Access service attributes .nf \fB> list\-attributes 0x1800\fP .fi .sp .TP .B Example List all Battery Service attributes .nf \fB> list\-attributes 0x180F\fP .fi .sp .TP .B Example List all Device Name characteristic attributes .nf \fB> list\-attributes 0x2A00\fP .fi .sp .TP .B Example List attributes using full UUID .nf \fB> list\-attributes 00001801\-0000\-1000\-8000\-00805f9b34fb\fP .fi .sp .TP .B Example List attributes for custom service UUID .nf \fB> list\-attributes 12345678\-1234\-5678\-9abc\-123456789abc\fP .fi .sp .UNINDENT .SS select\-attribute .sp Selects a specific attribute on either the local or remote device for subsequent operations. Before you can read or write to an attribute, you must select it with this command. This establishes a context for many other commands (read, write, notify, etc.), specifying the attribute they should operate on. .INDENT 0.0 .TP .B Usage \fB> select\-attribute [attribute/UUID]\fP .TP .B GATT attribute path, UUID, or \(dqlocal\(dq to select from local attributes .TP .B [attribute/UUID] Additional attribute path or UUID when using \(dqlocal\(dq (optional) .TP .B Example Select specific characteristic .nf \fB> select\-attribute /org/bluez/hci0/dev_00_11_22_33_44_55/service001a/char001c\fP .fi .sp .TP .B Example Select specific descriptor .nf \fB> select\-attribute /org/bluez/hci0/dev_00_11_22_33_44_55/service0020/char0022/desc0024\fP .fi .sp .TP .B Example Select service on different device .nf \fB> select\-attribute /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/service001a\fP .fi .sp .TP .B Example Select Device Name characteristic .nf \fB> select\-attribute 0x2A00\fP .fi .sp .TP .B Example Select Battery Level characteristic .nf \fB> select\-attribute 0x2A19\fP .fi .sp .TP .B Example Select Client Characteristic Configuration descriptor .nf \fB> select\-attribute 0x2902\fP .fi .sp .TP .B Example Select Temperature characteristic using full UUID .nf \fB> select\-attribute 00002a6e\-0000\-1000\-8000\-00805f9b34fb\fP .fi .sp .TP .B Example Select custom characteristic .nf \fB> select\-attribute 12345678\-1234\-5678\-9abc\-123456789abc\fP .fi .sp .UNINDENT .SS attribute\-info .sp Displays detailed information about an attribute. If no attribute or UUID is specified, it shows information about the currently selected attribute. This command is useful for understanding the properties and capabilities of an attribute. .INDENT 0.0 .TP .B Usage \fB> attribute\-info [attribute/UUID]\fP .TP .B [attribute/UUID] GATT attribute path or UUID to get info for (optional, uses current if omitted) .TP .B Example Show information for currently selected attribute .nf \fB> attribute\-info\fP .fi .sp .TP .B Example Show info for specific characteristic .nf \fB> attribute\-info /org/bluez/hci0/dev_00_11_22_33_44_55/service001a/char001c\fP .fi .sp .TP .B Example Show info for Battery Level characteristic .nf \fB> attribute\-info 0x2A19\fP .fi .sp .TP .B Example Show info for Device Name characteristic .nf \fB> attribute\-info 0x2A00\fP .fi .sp .TP .B Example Show info for Temperature characteristic .nf \fB> attribute\-info 00002a6e\-0000\-1000\-8000\-00805f9b34fb\fP .fi .sp .UNINDENT .SS read .sp Reads the value of an attribute. Requires that an attribute be selected beforehand with select\-attribute. The optional offset parameter can be used for attributes that allow partial reads. .INDENT 0.0 .TP .B Usage \fB> read [offset]\fP .TP .B Uses \fBorg.bluez.GattCharacteristic(5)\fP method \fBReadValue\fP .TP .B [offset] Byte offset to start reading from (optional, defaults to 0) .TP .B Example Read attribute value from offset 0 .nf \fB> read\fP .fi .sp .TP .B Example Read attribute value from offset 0 (explicit) .nf \fB> read 0\fP .fi .sp .TP .B Example Read attribute value starting from offset 4 .nf \fB> read 4\fP .fi .sp .TP .B Example Read attribute value starting from offset 10 .nf \fB> read 10\fP .fi .sp .TP .B Example Read attribute value starting from offset 16 .nf \fB> read 16\fP .fi .sp .UNINDENT .SS write .sp Writes a value to an attribute. This command necessitates that an attribute be selected in advance using select\-attribute. Data must be provided in hexadecimal format. Optional offset and type parameters can accommodate specific write requirements. .INDENT 0.0 .TP .B Usage \fB> write [offset] [type]\fP .TP .B Uses \fBorg.bluez.GattCharacteristic(5)\fP method \fBWriteValue\fP .TP .B Hexadecimal data bytes to write to the attribute .TP .B [offset] Byte offset to start writing at (optional, defaults to 0) .TP .B [type] Write type \- request, command, or reliable (optional, defaults to request) .TP .B Example Write \(dqHello\(dq to attribute (ASCII bytes) .nf \fB> write \(dq48 65 6C 6C 6F\(dq\fP .fi .sp .TP .B Example Write binary data to attribute .nf \fB> write \(dq01 02 03 04\(dq\fP .fi .sp .TP .B Example Write single byte value .nf \fB> write \(dqFF\(dq\fP .fi .sp .TP .B Example Write 2 bytes starting at offset 4 .nf \fB> write \(dq01 02\(dq 4\fP .fi .sp .TP .B Example Write with write request (default) .nf \fB> write \(dq48 65 6C 6C 6F\(dq 0 request\fP .fi .sp .TP .B Example Write with write command (no response) .nf \fB> write \(dq01 02 03\(dq 0 command\fP .fi .sp .TP .B Example Write with reliable write .nf \fB> write \(dqFF EE DD\(dq 0 reliable\fP .fi .sp .UNINDENT .SS acquire\-write .sp Acquires a Write file descriptor for a previously selected attribute. This is useful for applications that need a file descriptor to perform write operations. .INDENT 0.0 .TP .B Usage \fB> acquire\-write\fP .TP .B Uses \fBorg.bluez.GattCharacteristic(5)\fP method \fBAcquireWrite\fP .TP .B Example Acquire write file descriptor for current attribute .nf \fB> acquire\-write\fP .fi .sp .UNINDENT .SS release\-write .sp Releases the Write file descriptor acquired with acquire\-write. This command is necessary to clean up resources after you\(aqre done with the write operation. .INDENT 0.0 .TP .B Usage \fB> release\-write\fP .TP .B Example Release write file descriptor for current attribute .nf \fB> release\-write\fP .fi .sp .UNINDENT .SS acquire\-notify .sp Acquires a Notify file descriptor for a previously selected attribute. This enables applications to listen for notifications on attribute value changes. .INDENT 0.0 .TP .B Usage \fB> acquire\-notify\fP .TP .B Uses \fBorg.bluez.GattCharacteristic(5)\fP method \fBAcquireNotify\fP .TP .B Example Acquire notify file descriptor for current attribute .nf \fB> acquire\-notify\fP .fi .sp .UNINDENT .SS release\-notify .sp Releases the Notify file descriptor obtained with acquire\-notify. Ensures resources are freed once notification listening is no longer needed. .INDENT 0.0 .TP .B Usage \fB> release\-notify\fP .TP .B Example Release notify file descriptor for current attribute .nf \fB> release\-notify\fP .fi .sp .UNINDENT .SS notify .sp Enables or disables notifications for attribute value changes. Before this command can be used, the relevant attribute must be selected. This command allows applications to be notified of attribute changes without polling. .INDENT 0.0 .TP .B Usage \fB> notify \fP .TP .B Uses \fBorg.bluez.GattCharacteristic(5)\fP methods \fBStartNotify\fP, \fBStopNotify\fP .TP .B Enable or disable notifications for the current attribute .TP .B Example Enable notifications for current attribute .nf \fB> notify on\fP .fi .sp .TP .B Example Disable notifications for current attribute .nf \fB> notify off\fP .fi .sp .UNINDENT .SS clone .sp Creates a clone of a device or attribute. This can be useful for creating a backup or working with a copy for testing purposes. .INDENT 0.0 .TP .B Usage \fB> clone [dev/attribute/UUID]\fP .TP .B [dev/attribute/UUID] Device MAC address, attribute path, or UUID to clone (optional, clones current if omitted) .TP .B Example Clone entire device GATT database .nf \fB> clone 00:11:22:33:44:55\fP .fi .sp .TP .B Example Clone another device .nf \fB> clone AA:BB:CC:DD:EE:FF\fP .fi .sp .TP .B Example Clone specific service .nf \fB> clone /org/bluez/hci0/dev_00_11_22_33_44_55/service001a\fP .fi .sp .TP .B Example Clone specific characteristic .nf \fB> clone /org/bluez/hci0/dev_00_11_22_33_44_55/service001a/char001c\fP .fi .sp .TP .B Example Clone Generic Access service .nf \fB> clone 0x1800\fP .fi .sp .TP .B Example Clone Battery Service .nf \fB> clone 0x180F\fP .fi .sp .TP .B Example Clone Battery Level characteristic .nf \fB> clone 0x2A19\fP .fi .sp .UNINDENT .SS register\-application .sp Registers a new application with the Bluetooth system, allowing for the management of services, characteristics, and descriptors under this application. .INDENT 0.0 .TP .B Usage \fB> register\-application [UUID ...]\fP .TP .B Uses \fBorg.bluez.GattManager(5)\fP method \fBRegisterApplication\fP .TP .B [UUID ...] Optional list of service UUIDs to register with the application .TP .B Example Register GATT application without specific UUIDs .nf \fB> register\-application\fP .fi .sp .TP .B Example Register application with Battery Service .nf \fB> register\-application 0x180F\fP .fi .sp .TP .B Example Register with Generic Access and Battery Service .nf \fB> register\-application 0x1800 0x180F\fP .fi .sp .TP .B Example Register application with custom service UUID .nf \fB> register\-application 12345678\-1234\-5678\-9abc\-123456789abc\fP .fi .sp .TP .B Example Register with multiple standard services .nf \fB> register\-application 0x1800 0x180F 0x180A\fP .fi .sp .UNINDENT .SS unregister\-application .sp Removes a previously registered application from the Bluetooth system. .INDENT 0.0 .TP .B Usage \fB> unregister\-application\fP .TP .B Uses \fBorg.bluez.GattManager(5)\fP method \fBUnregisterApplication\fP .TP .B Example Unregister current GATT application .nf \fB> unregister\-application\fP .fi .sp .UNINDENT .SS register\-service .sp Adds a new service under a registered application. This command is crucial for defining new services that devices can offer. .INDENT 0.0 .TP .B Usage \fB> register\-service [handle]\fP .TP .B Service UUID to register (16\-bit, 32\-bit, or 128\-bit format) .TP .B [handle] Specific attribute handle to assign (optional, auto\-assigned if omitted) .TP .B Example Register Generic Access service (auto handle) .nf \fB> register\-service 0x1800\fP .fi .sp .TP .B Example Register Battery Service (auto handle) .nf \fB> register\-service 0x180F\fP .fi .sp .TP .B Example Register Device Information service (auto handle) .nf \fB> register\-service 0x180A\fP .fi .sp .TP .B Example Register Generic Access service at handle 1 .nf \fB> register\-service 0x1800 0x0001\fP .fi .sp .TP .B Example Register Battery Service at handle 16 .nf \fB> register\-service 0x180F 0x0010\fP .fi .sp .TP .B Example Register Device Info service at handle 32 .nf \fB> register\-service 0x180A 0x0020\fP .fi .sp .TP .B Example Register custom service (auto handle) .nf \fB> register\-service 12345678\-1234\-5678\-9abc\-123456789abc\fP .fi .sp .TP .B Example Register custom service at specific handle .nf \fB> register\-service 12345678\-1234\-5678\-9abc\-123456789abc 0x0050\fP .fi .sp .UNINDENT .SS unregister\-service .sp Removes a service from a registered application, effectively ceasing its availability. .INDENT 0.0 .TP .B Usage \fB> unregister\-service \fP .TP .B Service UUID or object path of the service to unregister .TP .B Example Unregister Generic Access service .nf \fB> unregister\-service 0x1800\fP .fi .sp .TP .B Example Unregister Battery Service .nf \fB> unregister\-service 0x180F\fP .fi .sp .TP .B Example Unregister custom service .nf \fB> unregister\-service 12345678\-1234\-5678\-9abc\-123456789abc\fP .fi .sp .TP .B Example Unregister service by object path .nf \fB> unregister\-service /org/bluez/example/service0\fP .fi .sp .TP .B Example Unregister another service .nf \fB> unregister\-service /org/bluez/example/service1\fP .fi .sp .UNINDENT .SS register\-includes .sp Marks a service as included within another service, allowing for service hierarchies and complex service structures. .INDENT 0.0 .TP .B Usage \fB> register\-includes [handle]\fP .TP .B Service UUID to register as included service .TP .B [handle] Specific attribute handle to assign (optional, auto\-assigned if omitted) .TP .B Example Register Generic Access as included service .nf \fB> register\-includes 0x1800\fP .fi .sp .TP .B Example Register Battery Service as included at handle 21 .nf \fB> register\-includes 0x180F 0x0015\fP .fi .sp .TP .B Example Register custom service as included .nf \fB> register\-includes 12345678\-1234\-5678\-9abc\-123456789abc\fP .fi .sp .UNINDENT .SS unregister\-includes .sp Removes an included service relationship, simplifying the service structure. .INDENT 0.0 .TP .B Usage \fB> unregister\-includes \fP .TP .B Parent service UUID that contains the included service .TP .B Included service UUID to remove from the parent service .TP .B Example Unregister Battery Service inclusion from Generic Access .nf \fB> unregister\-includes 0x1800 0x180F\fP .fi .sp .TP .B Example Unregister Device Info inclusion from custom service .nf \fB> unregister\-includes 12345678\-1234\-5678\-9abc\-123456789abc 0x180A\fP .fi .sp .UNINDENT .SS register\-characteristic .sp Introduces a new characteristic under a service, specifying its properties and access permissions with flags. .INDENT 0.0 .TP .B Usage \fB> register\-characteristic [handle]\fP .TP .B Characteristic UUID to register (16\-bit, 32\-bit, or 128\-bit format) .TP .B Comma\-separated list of characteristic properties and permissions .TP .B [handle] Specific attribute handle to assign (optional, auto\-assigned if omitted) .TP .B Example Register Device Name (read\-only) .nf \fB> register\-characteristic 0x2A00 read\fP .fi .sp .TP .B Example Register Battery Level (read + notifications) .nf \fB> register\-characteristic 0x2A19 read,notify\fP .fi .sp .TP .B Example Register Heart Rate Measurement (notify\-only) .nf \fB> register\-characteristic 0x2A37 notify\fP .fi .sp .TP .B Example Register Device Name (read + write) .nf \fB> register\-characteristic 0x2A00 read,write\fP .fi .sp .TP .B Example Register Battery Level (read + write + notify) .nf \fB> register\-characteristic 0x2A19 read,write,notify\fP .fi .sp .TP .B Example Register Temperature (read + indications) .nf \fB> register\-characteristic 0x2A6E read,indicate\fP .fi .sp .TP .B Example Register Device Name at handle 3 .nf \fB> register\-characteristic 0x2A00 read 0x0003\fP .fi .sp .TP .B Example Register Battery Level at handle 19 .nf \fB> register\-characteristic 0x2A19 read,notify 0x0013\fP .fi .sp .TP .B Example Register custom characteristic with full capabilities .nf \fB> register\-characteristic 12345678\-1234\-5678\-9abc\-123456789abc read,write,notify\fP .fi .sp .TP .B Example Register custom write\-only characteristic .nf \fB> register\-characteristic ABCD1234\-ABCD\-1234\-ABCD\-123456789ABC write\-without\-response\fP .fi .sp .TP .B Example Register with all flags .nf \fB> register\-characteristic 0x2A00 read,write,write\-without\-response,notify,indicate\fP .fi .sp .UNINDENT .SS unregister\-characteristic .sp Eliminates a characteristic from a service, removing its functionality. .INDENT 0.0 .TP .B Usage \fB> unregister\-characteristic \fP .TP .B Characteristic UUID or object path of the characteristic to unregister .TP .B Example Unregister Device Name characteristic .nf \fB> unregister\-characteristic 0x2A00\fP .fi .sp .TP .B Example Unregister Battery Level characteristic .nf \fB> unregister\-characteristic 0x2A19\fP .fi .sp .TP .B Example Unregister custom characteristic .nf \fB> unregister\-characteristic 12345678\-1234\-5678\-9abc\-123456789abc\fP .fi .sp .TP .B Example Unregister characteristic by object path .nf \fB> unregister\-characteristic /org/bluez/example/service0/char0\fP .fi .sp .TP .B Example Unregister another characteristic .nf \fB> unregister\-characteristic /org/bluez/example/service1/char1\fP .fi .sp .UNINDENT .SS register\-descriptor .sp Adds a descriptor to a characteristic, further defining its behavior and access controls. .INDENT 0.0 .TP .B Usage \fB> register\-descriptor [handle]\fP .TP .B Descriptor UUID to register (16\-bit, 32\-bit, or 128\-bit format) .TP .B Comma\-separated list of descriptor properties and permissions .TP .B [handle] Specific attribute handle to assign (optional, auto\-assigned if omitted) .TP .B Example Register Client Characteristic Configuration .nf \fB> register\-descriptor 0x2902 read,write\fP .fi .sp .TP .B Example Register Characteristic User Description .nf \fB> register\-descriptor 0x2901 read\fP .fi .sp .TP .B Example Register Characteristic Presentation Format .nf \fB> register\-descriptor 0x2904 read\fP .fi .sp .TP .B Example Register CCCD at handle 5 .nf \fB> register\-descriptor 0x2902 read,write 0x0005\fP .fi .sp .TP .B Example Register User Description at handle 21 .nf \fB> register\-descriptor 0x2901 read 0x0015\fP .fi .sp .TP .B Example Register custom descriptor .nf \fB> register\-descriptor 12345678\-1234\-5678\-9abc\-123456789abc read,write\fP .fi .sp .TP .B Example Register custom read\-only descriptor .nf \fB> register\-descriptor ABCD1234\-ABCD\-1234\-ABCD\-123456789ABC read\fP .fi .sp .TP .B Example Register Valid Range descriptor .nf \fB> register\-descriptor 0x2906 read\fP .fi .sp .TP .B Example Register External Report Reference .nf \fB> register\-descriptor 0x2907 read\fP .fi .sp .TP .B Example Register Report Reference .nf \fB> register\-descriptor 0x2908 read\fP .fi .sp .UNINDENT .SS unregister\-descriptor .sp Removes a descriptor from a characteristic, simplifying its behavior. .INDENT 0.0 .TP .B Usage \fB> unregister\-descriptor \fP .TP .B Descriptor UUID or object path of the descriptor to unregister .TP .B Example Unregister Client Characteristic Configuration .nf \fB> unregister\-descriptor 0x2902\fP .fi .sp .TP .B Example Unregister Characteristic User Description .nf \fB> unregister\-descriptor 0x2901\fP .fi .sp .TP .B Example Unregister custom descriptor .nf \fB> unregister\-descriptor 12345678\-1234\-5678\-9abc\-123456789abc\fP .fi .sp .TP .B Example Unregister descriptor by object path .nf \fB> unregister\-descriptor /org/bluez/example/service0/char0/desc0\fP .fi .sp .TP .B Example Unregister another descriptor .nf \fB> unregister\-descriptor /org/bluez/example/service1/char1/desc1\fP .fi .sp .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/bdaddr.rst0000644000000000000000000000005015142177620014377 xustar0020 atime=1783094093 20 ctime=1783094113 bluez-5.87/doc/bdaddr.rst0000644000000000000000000000447015142177620014065 0ustar00rootroot====== bdaddr ====== ------------------------------------------------- Utility for changing the Bluetooth device address ------------------------------------------------- :Authors: - Marcel Holtmann - Adam Laurie :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: Sep 27, 2005 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **bdaddr** **bdaddr** -h **bdaddr** [-i <*dev*>] [-r] [-t] [*new_bdaddr*] DESCRIPTION =========== **bdaddr(1)** is used to query or set the local Bluetooth device address (BD_ADDR). If run with no arguments, **bdaddr** prints the chip manufacturer's name, and the current BD_ADDR. If the IEEE OUI index file "oui.txt" is installed on the system, the BD_ADDR owner will be displayed. If the optional [*new_bdaddr*] argument is given, the device will be reprogrammed with that address. This can either be permanent or temporary, as specified by the -t flag. In both cases, the device must be reset before the new address will become active. This can be done with a 'soft' reset by specifying the -r flag, or a 'hard' reset by removing and replugging the device. A 'hard' reset will cause the address to revert to the current non-volatile value. **bdaddr** uses manufacturer specific commands to set the address, and is therefore device specific. For this reason, not all devices are supported, and not all options are supported on all devices. Current supported manufacturers are: **Ericsson**, **Cambridge Silicon Radio (CSR)**, **Texas Instruments (TI)**, **Zeevo** and **ST Microelectronics (ST)**. OPTIONS ======= -h Gives a list of possible commands. -i Specify a particular device to operate on. If not specified, default is the first available device. -r Reset device and make new BD_ADDR active. CSR devices only. -t Temporary change. Do not write to non-volatile memory. CSR devices only. FILES ===== /usr/share/misc/oui.txt IEEE Organizationally Unique Identifier consolidated file. Manually update from: http://standards.ieee.org/regauth/oui/oui.txt RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/btmon-advertising.rst0000644000000000000000000000005015200607060016602 xustar0020 atime=1783092683 20 ctime=1783094112 bluez-5.87/doc/btmon-advertising.rst0000644000000000000000000001165115200607060016267 0ustar00rootroot.. This file is included by btmon.rst. ADVERTISING AND SCANNING ========================== btmon decodes advertising data structures automatically. Advertising and scan response data appears in HCI LE advertising report events and in advertising command parameters. Advertising Reports -------------------- When the controller reports received advertisements:: > HCI Event: LE Meta Event (0x3e) plen 43 #120 [hci0] 0.500003 LE Extended Advertising Report (0x0d) Event type: 0x0013 Props: 0x0013 Connectable Scannable Complete Address type: Random (0x01) Address: 00:11:22:33:44:55 Primary PHY: LE 1M Secondary PHY: LE 2M SID: 0x01 TX power: 0 dBm RSSI: -55 dBm (0xc9) Data length: 18 The advertising data (AD) structures within the report are decoded as typed fields: **Common AD types btmon decodes**: .. list-table:: :header-rows: 1 :widths: 10 30 60 * - AD Type - Name - Example in btmon output * - 0x01 - Flags - ``Flags: 0x06`` with decoded bits (LE General Discoverable, BR/EDR Not Supported) * - 0x02/0x03 - Incomplete/Complete 16-bit UUIDs - ``16-bit Service UUIDs (complete): 2 entries`` followed by UUID list * - 0x06/0x07 - Incomplete/Complete 128-bit UUIDs - ``128-bit Service UUIDs (complete): 1 entry`` * - 0x08/0x09 - Shortened/Complete Local Name - ``Name (complete): MyDevice`` * - 0x0a - TX Power Level - ``TX power: 4 dBm`` * - 0x16 - Service Data (16-bit UUID) - ``Service Data (UUID 0x184e): ...`` with protocol-specific decoding * - 0xff - Manufacturer Specific Data - ``Company: Apple, Inc. (76)`` followed by hex data **Typical advertising report**:: > HCI Event: LE Meta Event (0x3e) plen 38 #120 [hci0] 0.500003 LE Extended Advertising Report (0x0d) Address: 00:11:22:33:44:55 RSSI: -62 dBm (0xc2) Flags: 0x06 LE General Discoverable Mode BR/EDR Not Supported Name (complete): LE-Audio-Left 16-bit Service UUIDs (complete): 3 entries Published Audio Capabilities (0x1850) Audio Stream Control (0x184e) Common Audio (0x1853) Service Data (UUID 0x1852): 01a2b3 Appearance: Earbud (0x0941) Extended Advertising --------------------- Modern controllers use extended advertising commands and events. The setup sequence in btmon:: < HCI Command: LE Set Extended Adv Parameters (0x08|0x0036) plen 25 #50 [hci0] 0.100003 Handle: 0x01 Properties: 0x0000 Min advertising interval: 160.000 msec (0x0100) Max advertising interval: 160.000 msec (0x0100) Channel map: 37, 38, 39 (0x07) Own address type: Random (0x01) Peer address type: Public (0x00) PHY: LE 1M, LE 2M SID: 0x01 TX power: 7 dBm < HCI Command: LE Set Extended Adv Data (0x08|0x0037) plen 35 #52 [hci0] 0.101003 Handle: 0x01 Operation: Complete extended advertising data (0x01) Fragment preference: No fragmentation (0x01) Periodic Advertising (LE Audio) -------------------------------- LE Audio broadcast sources use periodic advertising to transmit BASE announcements containing codec configuration:: > HCI Event: LE Meta Event (0x3e) plen 80 #200 [hci0] 0.500003 LE Periodic Advertising Report (0x0f) Sync handle: 0x0001 TX power: 0 dBm RSSI: -45 dBm CTE Type: No CTE (0xff) Data status: Complete (0x00) Data length: 60 Service Data: Basic Audio Announcement (0x1851) Presentation Delay: 40000 us Number of Subgroups: 1 Codec: LC3 (0x06) Sampling Frequency: 48000 Hz Frame Duration: 10 ms Frame Length: 120 Automating Advertising Analysis --------------------------------- **Find all advertising reports** (devices seen):: grep -n "Advertising Report\|Address:.*RSSI:" output.txt **Extract device names**:: grep -n "Name (complete):\|Name (short):" output.txt **Find LE Audio devices** (by service UUIDs in advertising):: grep -n "Audio Stream Control\|Published Audio Capabilities\|Common Audio\|Basic Audio Announcement\|Broadcast Audio" output.txt **Track advertising setup** (local device configuring advertising):: grep -n "Set Extended Adv\|Set Advertising\|Set Scan Response\|Adv Enable" output.txt **Find periodic advertising** (broadcast audio):: grep -n "Periodic Advertising\|PA Sync\|PA Report\|Basic Audio Announcement\|Broadcast.*Announcement" output.txt **Identify devices by appearance**:: grep -n "Appearance:" output.txt bluez-5.87/doc/PaxHeaders/org.bluez.obex.AgentManager.rst0000644000000000000000000000005015063045254020351 xustar0020 atime=1783094021 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.AgentManager.rst0000644000000000000000000000207315063045254020034 0ustar00rootroot=========================== org.bluez.obex.AgentManager =========================== ----------------------------------------------- BlueZ D-Bus OBEX AgentManager API documentation ----------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez.obex :Interface: org.bluez.obex.AgentManager1 :Object path: /org/bluez/obex Methods ------- void RegisterAgent(object agent) ```````````````````````````````` Registers an agent, which must implement **org.bluez.obex.Agent(5)**, to request authorization of the user to accept/reject objects. Object push service needs to authorize each received object. Possible errors: :org.bluez.obex.Error.AlreadyExists: void UnregisterAgent(object agent) `````````````````````````````````` Unregisters the agent that has been previously registered using **RegisterAgent()**. The object path parameter must match the same value that has been used on registration. Possible errors: :org.bluez.obex.Error.DoesNotExist: bluez-5.87/doc/PaxHeaders/org.bluez.obex.Agent.rst0000644000000000000000000000005015063045254017056 xustar0020 atime=1783094023 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.Agent.rst0000644000000000000000000000252715063045254016545 0ustar00rootroot==================== org.bluez.obex.Agent ==================== ---------------------------------------- BlueZ D-Bus OBEX Agent API documentation ---------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: unique name :Interface: org.bluez.obex.Agent1 :Object path: freely definable Methods ------- void Release() `````````````` This method gets called when **obexd(8)** daemon unregisters the agent. An agent can use it to do cleanup tasks. There is no need to unregister the agent, because when this method gets called it has already been unregistered. string AuthorizePush(object transfer) ````````````````````````````````````` This method gets called when the **obexd(8)** needs to accept/reject a Bluetooth object push request. Returns the full path (including the filename) or the folder name suffixed with '/' where the object shall be stored. The transfer object, see **org.bluez.obex.Transfer(5)** will contain a Filename property that contains the default location and name that can be returned. Possible errors: :org.bluez.obex.Error.Rejected: :org.bluez.obex.Error.Canceled: void Cancel() ````````````` This method gets called to indicate that the agent request failed before a reply was returned. It cancels the previous request. bluez-5.87/doc/PaxHeaders/org.bluez.BatteryProvider.50000644000000000000000000000005015221755313017545 xustar0020 atime=1783093963 20 ctime=1783094112 bluez-5.87/doc/org.bluez.BatteryProvider.50000644000000000000000000000264415221755313017234 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.BatteryProvider" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.BatteryProvider \- BlueZ D-Bus BatteryProvider API documentation .SH Interface .INDENT 0.0 .TP .B Service .TP .B Interface org.bluez.BatteryProvider1 .TP .B Object path {provider_root}/{unique battery object path} .UNINDENT .SS Properties .sp Objects provided on this interface contain the same properties as \fBorg.bluez.Battery(5)\fP interface. Additionally, this interface needs to have the Device property indicating the object path of the device this battery provides. .SS object Device [readonly] .sp The object path of the device that has this battery. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/rfcomm.70000644000000000000000000000005015221755251014000 xustar0020 atime=1783093929 20 ctime=1783094112 bluez-5.87/doc/rfcomm.70000644000000000000000000001374215221755251013470 0ustar00rootroot'\" t .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "rfcomm" "7" "May 2024" "BlueZ" "Linux System Administration" .SH Name rfcomm \- RFCOMM protocol .SH SYNOPSIS .INDENT 0.0 .INDENT 3.5 .sp .EX #include #include #include rfcomm_socket = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); .EE .UNINDENT .UNINDENT .SH DESCRIPTION .sp The RFCOMM protocol provides emulation of serial ports over the L2CAP(7) protocol. The protocol is based on the ETSI standard TS 07.10. .sp RFCOMM is a simple transport protocol, with additional provisions for emulating the 9 circuits of RS\-232 (EIATIA\-232\-E) serial ports. .SH SOCKET ADDRESS .INDENT 0.0 .INDENT 3.5 .sp .EX struct sockaddr_rc { sa_family_t rc_family; unsigned short rc_bdaddr; unsigned char rc_channel; }; .EE .UNINDENT .UNINDENT .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX struct sockaddr_rc addr; memset(&addr, 0, sizeof(addr)); addr.rc_family = AF_BLUETOOTH; bacpy(&addr.rc_bdaddr, bdaddr); addr.rc_channel = channel; .EE .UNINDENT .UNINDENT .SH SOCKET OPTIONS .sp The socket options listed below can be set by using \fBsetsockopt(2)\fP and read with \fBgetsockopt(2)\fP with the socket level set to SOL_BLUETOOTH. .SS BT_SECURITY (since Linux 2.6.30) .sp Channel security level, possible values: .TS box center; l|l|l|l. T{ Value T} T{ Security Level T} T{ Link Key Type T} T{ Encryption T} _ T{ \fBBT_SECURITY_SDP\fP T} T{ 0 (SDP Only) T} T{ None T} T{ Not required T} _ T{ \fBBT_SECURITY_LOW\fP T} T{ 1 (Low) T} T{ Unauthenticated T} T{ Not required T} _ T{ \fBBT_SECURITY_MEDIUM\fP T} T{ 2 (Medium \- default) T} T{ Unauthenticated T} T{ Desired T} _ T{ \fBBT_SECURITY_HIGH\fP T} T{ 3 (High) T} T{ Authenticated T} T{ Required T} _ T{ \fBBT_SECURITY_FIPS\fP (since Linux 3.15) T} T{ 4 (Secure Only) T} T{ Authenticated (P\-256 based Secure Simple Pairing and Secure Authentication) T} T{ Required T} .TE .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX int level = BT_SECURITY_HIGH; int err = setsockopt(rfcomm_socket, SOL_BLUETOOTH, BT_SECURITY, &level, sizeof(level)); if (err == \-1) { perror(\(dqsetsockopt\(dq); return 1; } .EE .UNINDENT .UNINDENT .SS BT_DEFER_SETUP (since Linux 2.6.30) .sp Channel defer connection setup, this control if the connection procedure needs to be authorized by userspace before responding which allows authorization at profile level, possible values: .TS box center; l|l|l. T{ Value T} T{ Description T} T{ Authorization T} _ T{ \fB0\fP T} T{ Disable (default) T} T{ Not required T} _ T{ \fB1\fP T} T{ Enable T} T{ Required T} .TE .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX int defer_setup = 1; int err = setsockopt(rfcomm_socket, SOL_BLUETOOTH, BT_DEFER_SETUP, &defer_setup, sizeof(defer_setup)); if (err == \-1) { perror(\(dqsetsockopt\(dq); return err; } err = listen(rfcomm_socket, 5); if (err) { perror(\(dqlisten\(dq); return err; } struct sockaddr_rc remote_addr = {0}; socklen_t addr_len = sizeof(remote_addr); int new_socket = accept(rfcomm_socket, (struct sockaddr*)&remote_addr, &addr_len); if (new_socket < 0) { perror(\(dqaccept\(dq); return new_socket; } /* To complete the connection setup of new_socket read 1 byte */ char c; struct pollfd pfd; memset(&pfd, 0, sizeof(pfd)); pfd.fd = new_socket; pfd.events = POLLOUT; err = poll(&pfd, 1, 0); if (err) { perror(\(dqpoll\(dq); return err; } if (!(pfd.revents & POLLOUT)) { err = read(sk, &c, 1); if (err < 0) { perror(\(dqread\(dq); return err; } } .EE .UNINDENT .UNINDENT .SS BT_FLUSHABLE (since Linux 2.6.39) .sp Channel flushable flag, this control if the channel data can be flushed or not, possible values: .TS box center; l|l|l. T{ Define T} T{ Value T} T{ Description T} _ T{ \fBBT_FLUSHABLE_OFF\fP T} T{ 0x00 (default) T} T{ Do not flush data T} _ T{ \fBBT_FLUSHABLE_ON\fP T} T{ 0x01 T} T{ Flush data T} .TE .SS BT_CHANNEL_POLICY (since Linux 3.10) .sp High\-speed (AMP) channel policy, possible values: .TS box center; l|l|l. T{ Define T} T{ Value T} T{ Description T} _ T{ \fBBT_CHANNEL_POLICY_BREDR_ONLY\fP T} T{ 0 (default) T} T{ BR/EDR only T} _ T{ \fBBT_CHANNEL_POLICY_BREDR_PREFERRED\fP T} T{ 1 T} T{ BR/EDR Preferred T} _ T{ \fBBT_CHANNEL_POLICY_BREDR_PREFERRED\fP T} T{ 2 T} T{ AMP Preferred T} .TE .SS BT_PHY (since Linux 5.10) .sp Channel supported PHY(s), possible values: .TS box center; l|l|l. T{ Define T} T{ Value T} T{ Description T} _ T{ \fBBT_PHY_BR_1M_1SLOT\fP T} T{ BIT 0 T} T{ BR 1Mbps 1SLOT T} _ T{ \fBBT_PHY_BR_1M_3SLOT\fP T} T{ BIT 1 T} T{ BR 1Mbps 3SLOT T} _ T{ \fBBT_PHY_BR_1M_5SLOT\fP T} T{ BIT 2 T} T{ BR 1Mbps 5SLOT T} _ T{ \fBBT_PHY_BR_2M_1SLOT\fP T} T{ BIT 3 T} T{ EDR 2Mbps 1SLOT T} _ T{ \fBBT_PHY_BR_2M_3SLOT\fP T} T{ BIT 4 T} T{ EDR 2Mbps 3SLOT T} _ T{ \fBBT_PHY_BR_2M_5SLOT\fP T} T{ BIT 5 T} T{ EDR 2Mbps 5SLOT T} _ T{ \fBBT_PHY_BR_3M_1SLOT\fP T} T{ BIT 6 T} T{ EDR 3Mbps 1SLOT T} _ T{ \fBBT_PHY_BR_3M_3SLOT\fP T} T{ BIT 7 T} T{ EDR 3Mbps 3SLOT T} _ T{ \fBBT_PHY_BR_3M_5SLOT\fP T} T{ BIT 8 T} T{ EDR 3Mbps 5SLOT T} .TE .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH SEE ALSO .sp socket(7), rctest(1) .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.AdvertisementMonitor.rst0000644000000000000000000000005015142177620021247 xustar0020 atime=1783094002 20 ctime=1783094112 bluez-5.87/doc/org.bluez.AdvertisementMonitor.rst0000644000000000000000000001152615142177620020735 0ustar00rootroot============================== org.bluez.AdvertisementMonitor ============================== -------------------------------------------------- BlueZ D-Bus AdvertisementMonitor API documentation -------------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Description =========== This API allows an client to specify a job of monitoring advertisements by registering the root of hierarchy and then exposing advertisement monitors under the root with filtering conditions, thresholds of RSSI and timers of RSSI thresholds. Once a monitoring job is activated by **bluetoothd(8)**, the client can expect to get notified on the targeted advertisements no matter if there is an ongoing discovery session (see **StartDiscovery()** in **org.bluez.Adapter(5)**). Interface ========= :Service: org.bluez :Interface: org.bluez.AdvertisementMonitor1 [experimental] :Object path: freely definable :Used by: **bluetoothctl-monitor(1)** Methods ------- void Release() [noreply] ```````````````````````` This gets called as a signal for a client to perform clean-up when: - Monitor cannot be activated after it was exposed - Monitor has been deactivated. void Activate() [noreply] ````````````````````````` After a monitor was exposed, this gets called as a signal for client to get acknowledged when a monitor has been activated, so the client can expect to receive calls on **DeviceFound()** or **DeviceLost()**. void DeviceFound(object device) [noreply] ````````````````````````````````````````` This gets called to notify the client of finding the targeted device. Once receiving the call, the client should start to monitor the corresponding device to retrieve the changes on RSSI and advertisement content. void DeviceLost(object device) [noreply] ```````````````````````````````````````` This gets called to notify the client of losing the targeted device. Once receiving this call, the client should stop monitoring the corresponding device. Properties ---------- string Type [read-only] ``````````````````````` The type of the monitor. See **SupportedMonitorTypes** in **org.bluez.AdvertisementMonitorManager(5)** for the available options. int16 RSSILowThreshold [read-only, optional] ```````````````````````````````````````````` Used in conjunction with **RSSILowTimeout** to determine whether a device becomes out-of-range. Valid range is -127 to 20 (dBm), while 127 indicates unset. int16 RSSIHighThreshold [read-only, optional] ````````````````````````````````````````````` Used in conjunction with RSSIHighTimeout to determine whether a device becomes in-range. Valid range is -127 to 20 (dBm), while 127 indicates unset. uint16 RSSILowTimeout [read-only, optional] ``````````````````````````````````````````` The time it takes to consider a device as out-of-range. If this many seconds elapses without receiving any signal at least as strong as **RSSILowThreshold**, a currently in-range device will be considered as out-of-range (lost). Valid range is 1 to 300 (seconds), while 0 indicates unset. uint16 RSSIHighTimeout [read-only, optional] ```````````````````````````````````````````` The time it takes to consider a device as in-range. If this many seconds elapses while we continuously receive signals at least as strong as **RSSIHighThreshold**, a currently out-of-range device will be considered as in-range (found). Valid range is 1 to 300 (seconds), while 0 indicates unset. uint16 RSSISamplingPeriod [read-only, optional] ``````````````````````````````````````````````` Grouping rules on how to propagate the received advertisement packets to the client. Possible values: :0: All advertisement packets from in-range devices would be propagated. :255: Only the first advertisement packet of in-range devices would be propagated. If the device becomes lost, then the first packet when it is found again will also be propagated. :1 to 254: Advertisement packets would be grouped into 100ms * N time period. Packets in the same group will only be reported once, with the RSSI value being averaged out. Currently this is unimplemented in user space, so the value is only used to be forwarded to the kernel. array{(uint8, uint8, array{byte})} Patterns [read-only, optional] ````````````````````````````````````````````````````````````````` If the **Type** property is set to **"or_patterns"**, then this property must exist and have at least one entry in the array. The structure of a pattern contains the following: :uint8 start_position: The index in an AD data field where the search should start. The beginning of an AD data field is index 0. :uint8 AD_data_type: See https://www.bluetooth.com/specifications/assigned-numbers/ generic-access-profile/ for the possible allowed value. :array{byte} content_of_pattern: This is the value of the pattern. The maximum length of the bytes is 31. bluez-5.87/doc/PaxHeaders/org.bluez.Network.rst0000644000000000000000000000005015063045254016515 xustar0020 atime=1783093957 20 ctime=1783094111 bluez-5.87/doc/org.bluez.Network.rst0000644000000000000000000000325415063045254016202 0ustar00rootroot================= org.bluez.Network ================= ------------------------------------- BlueZ D-Bus Network API documentation ------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.Network1 :Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} Methods ------- string Connect(string uuid) ``````````````````````````` Connects to the network device and return the network interface name. Possible uuid values: :"panu", "00001115-0000-1000-8000-00805f9b34fb": Personal Network User role. :"nap", "00001116-0000-1000-8000-00805f9b34fb": Network Access Point role. :"gn", "00001117-0000-1000-8000-00805f9b34fb": Group Network role. The connection will be closed and network device released either upon calling **Disconnect()** or when the client disappears from the message bus. Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.NotSupported: :org.bluez.Error.InProgress: :org.bluez.Error.Failed: void Disconnect() ````````````````` Disconnects from the network device. To abort a connection attempt in case of errors or timeouts in the client it is fine to call this method. Possible errors: :org.bluez.Error.Failed: :org.bluez.Error.NotConnected: Properties ---------- boolean Connected [readonly] ```````````````````````````` Indicates if the device is connected. string Interface [readonly, optional] ````````````````````````````````````` Indicates the network interface name when available. string UUID [readonly, optional] ```````````````````````````````` Indicates the connection role when available. bluez-5.87/doc/PaxHeaders/settings-storage.txt0000644000000000000000000000005015142177620016470 xustar0020 atime=1778585135 20 ctime=1783094111 bluez-5.87/doc/settings-storage.txt0000644000000000000000000002243015142177620016152 0ustar00rootrootBlueZ settings storage ********************** Purpose ======= The purpose of this document is to describe the directory structure of BlueZ settings storage. In effect, this document will serve as the primary, up to date source of BlueZ storage information. It is intended as reference for developers. Direct access to the storage outside from bluetoothd is highly discouraged. Adapter and remote device info are read form the storage during object initialization. Write to storage is performed immediately on every value change. Default storage directory is /var/lib/bluetooth. This can be adjusted by the --localstatedir configure switch. Default is --localstatedir=/var. When loading as a service the storage directory can be set via StateDirectory environment variable: https://www.freedesktop.org/software/systemd/man/systemd.exec.html All files are in ini-file format. Storage directory structure =========================== The storage root directory contains an optional addresses file that's used for managing adapters that come without a pre-allocated address. The format of the addresses file is: [Static] = Each adapter with an assigned address has its own subdirectory under the root, named based on the address, which contains: - a settings file for the local adapter - an attributes file containing attributes of supported LE services - an admin policy file containing current values of admin policies - a cache directory containing: - one file per device, named by remote device address, which contains device name - one directory per remote device, named by remote device address, which contains: - an info file - an attributes file containing attributes of remote LE services - a ccc file containing persistent Client Characteristic Configuration (CCC) descriptor information for GATT characteristics So the directory structure is: /var/lib/bluetooth// ./settings ./attributes ./admin_policy_settings ./cache/ ./ ./ ... .// ./info ./attributes ./ccc .// ./info ./attributes ... Settings file format ==================== Settings file contains one [General] group with adapter info like: Alias String Friendly user provided name advertised for this adapter This value overwrites the system name (pretty hostname) Discoverable Boolean Discoverability of the adapter PairableTimeout Integer How long to stay in pairable mode before going back to non-pairable. The value is in seconds. 0 = disable timer, i.e. stay pairable forever DiscoverableTimeout Integer How long to stay in discoverable mode before going back to non-discoverable. The value is in seconds. 0 = disable timer, i.e. stay discoverable forever Sample: [General] Name=My PC Discoverable=false Pairable=true DiscoverableTimeout=0 Identity file format ==================== Identity file contains one [General] group that holds identity information such as keys and addresses: IdentityResolvingKey String 128-bit value of the IRK Sample: [General] IdentityResolvingKey=00112233445566778899aabbccddeeff Attributes file format ====================== The attributes file lists all attributes supported by the local adapter or remote device. Attributes are stored using their handle as group name (decimal format). Each group contains: UUID String 128-bit UUID of the attribute Value String Value of the attribute as hexadecimal encoded string EndGroupHandle Integer End group handle in decimal format Sample: [1] UUID=00002800-0000-1000-8000-00805f9b34fb Value=0018 [4] UUID=00002803-0000-1000-8000-00805f9b34fb Value=020600002A [6] UUID=00002a00-0000-1000-8000-00805f9b34fb Value=4578616D706C6520446576696365 Admin Policy file format ====================== The admin policy file stores the current value of each admin policy. [General] group contains: ServiceAllowlist List of List of service UUID allowed by strings adapter in 128-bits format, separated by ','. Default is empty. Sample: [General] ServiceAllowlist= CCC file format ====================== The ccc file stores the current CCC descriptor values for GATT characteristics which have notification/indication enabled by the remote device. Information is stored using CCC attribute handle as group name (in decimal format). Each group contains: Value String CCC descriptor value encoded in hexadecimal Cache directory file format ============================ Each file, named by remote device address, may includes multiple groups (General, ServiceRecords, Attributes, Endpoints, NameResolving). In ServiceRecords, SDP records are stored using their handle as key (hexadecimal format). In "Attributes" group GATT database is stored using attribute handle as key (hexadecimal format). Value associated with this handle is serialized form of all data required to re-create given attribute. ":" is used to separate fields. In "Endpoints" group A2DP remote endpoints are stored using the seid as key (hexadecimal format) and ":" is used to separate fields. It may also contain an entry which key is set to "LastUsed" which represented the last endpoint used. In "NameResolving", information regarding remote name resolving are stored to prevent wasting time resolving name for unresponsive devices. [General] group contains: Name String Remote device friendly name ShortName String Remote device shortened name [ServiceRecords] group contains <0x...> String SDP record as hexadecimal encoded string In [Attributes] group value always starts with attribute type, that determines how to interpret rest of value: Primary service: 2800:end_handle:uuid Secondary service: 2801:end_handle:uuid Included service: 2802:start_handle:end_handle:uuid Characteristic: 2803:value_handle:properties:uuid Descriptor: value:uuid uuid Sample Attributes section: [Attributes] 0001=2800:0005:1801 0002=2803:0003:20:2a05 0014=2800:001c:1800 0015=2803:0016:02:2a00 0017=2803:0018:02:2a01 0019=2803:001a:02:2aa6 0028=2800:ffff:0000180d-0000-1000-8000-00805f9b34fb 0029=2803:002a:10:00002a37-0000-1000-8000-00805f9b34fb 002b=2803:002c:02:00002a38-0000-1000-8000-00805f9b34fb 002d=2803:002e:08:00002a39-0000-1000-8000-00805f9b34fb [Endpoints] group contains: :::: String First field is the endpoint type, followed by codec type and delay reporting and its capabilities as hexadecimal encoded string. LastUsed:: String LastUsed has two fields which are the local and remote seids as hexadecimal encoded string. [NameResolving] group contains: FailedTime Integer The last time we failed to complete name resolving procedure, measured from an arbitrary, fixed point in the past. Info file format ================ Info file may includes multiple groups (General, Device ID, Link key and Long term key) related to a remote device. [General] group contains: Name String Remote device friendly name Alias String Alias name Class String Device class in hexadecimal, i.e. 0x000000 Appearance String Device appearance in hexadecimal, i.e. 0x0000 SupportedTechnologies List of List of technologies supported by strings device, separated by ";" Technologies can be BR/EDR or LE AddressType String An address can be "static" or "public" Trusted Boolean True if the remote device is trusted Blocked Boolean True if the remote device is blocked Services List of List of service UUIDs advertised by strings remote in 128-bits UUID format, separated by ";" PreferredBearer String Preferred bearer for remote device LastUsedBearer String Last used bearer for remote device [DeviceID] group contains: Source Integer Assigner of Device ID Vendor Integer Device vendor Product Integer Device product Version Integer Device version [LinkKey] group contains: Key String Key in hexadecimal format Type Integer Type of link key PINLength Integer Length of PIN [LongTermKey] group contains: Key String Long term key in hexadecimal format Authenticated Boolean True if remote device has been authenticated EncSize Integer Encrypted size EDiv Integer Encrypted diversifier Rand Integer Randomizer [PeripheralLongTermKey] group contains: Same as the [LongTermKey] group, except for peripheral keys. [ConnectionParameters] group contains: MinInterval Integer Minimum Connection Interval MaxInterval Integer Maximum Connection Interval Latency Integer Connection Latency Timeout Integer Supervision Timeout [LocalSignatureKey] and [RemoteSignatureKey] groups contain: Key String Key in hexadecimal format Counter Integer Signing counter Authenticated Boolean True if the key is authenticated [ServiceChanged] This section holds information related to Service Changed characteristic of GATT core service. CCC_LE Integer CCC value for LE transport CCC_BR/EDR Integer CCC value for BR/EDR transport bluez-5.87/doc/PaxHeaders/bluetoothctl-assistant.rst0000644000000000000000000000005015142177620017676 xustar0020 atime=1783094071 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-assistant.rst0000644000000000000000000000572515142177620017370 0ustar00rootroot====================== bluetoothctl-assistant ====================== ----------------- Assistant Submenu ----------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: August 2024 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **bluetoothctl** [--options] [assistant.commands] This submenu manages BAP Broadcast Assistants using the **org.bluez.MediaAssistant(5)** interface. Assistant Commands ================== list ---- List available assistants. :Usage: **> list** :Example Display all available BAP Broadcast Assistants: | **> list** show ---- Show assistant information. :Usage: **> show [assistant]** :Uses: **org.bluez.MediaAssistant(5)** properties :[assistant]: BAP Broadcast Assistant path (optional, shows current if omitted) :Example Show information for currently selected assistant: | **> show** :Example Show specific BAP Broadcast Assistant information: | **> show /org/bluez/hci0/src_05_1F_EE_F3_F8_7D/dev_00_60_37_31_7E_3F/bis1** push ---- Send stream information to peer. This command is used by a BAP Broadcast Assistant to send information about a broadcast stream to a peer BAP Scan Delegator. The information is sent via a GATT Write Command for the BASS Broadcast Audio Scan Control Point characteristic. After issuing the command, the user is prompted to enter stream metadata LTVs to send to the peer. If the auto option is chosen, the Broadcast Assistant will send the default metadata discovered about the stream. Otherwise, the default metadata will be overwritten by the LTVs entered by the user. If the stream is encrypted, the user will also be prompted to enter the Broadcast Code. This is the key to decrypt the stream. On the UI level, the Broadcast Code shall be represented as a string of at least 4 octets, and no more than 16 octets when represented in UTF-8. The string will be sent to the peer via GATT as an array of 16 octets. If the auto value is chosen when prompted for the Broadcast Code, a zero filled array will be sent to the peer. Otherwise, the string entered by the user will be sent as an array of bytes. :Usage: **> push ** :Uses: **org.bluez.MediaAssistant(5)** method **Push** :: BAP Broadcast Assistant path to send stream information to :Example Push stream info with automatic metadata and broadcast code: | **> push /org/bluez/hci0/src_05_1F_EE_F3_F8_7D/dev_00_60_37_31_7E_3F/bis1** | **[Assistant] Enter Metadata (auto/value): auto** | **[Assistant] Enter Broadcast Code (auto/value): auto** :Example Push stream info with custom metadata and broadcast code: | **> push /org/bluez/hci0/src_05_1F_EE_F3_F8_7D/dev_00_60_37_31_7E_3F/bis1** | **[Assistant] Enter Metadata (auto/value): 0x03 0x02 0x04 0x00** | **[Assistant] Enter Broadcast Code (auto/value): Borne House** RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.obex.ObjectPush.rst0000644000000000000000000000005015063045254020066 xustar0020 atime=1783094009 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.ObjectPush.rst0000644000000000000000000000476615063045254017564 0ustar00rootroot========================= org.bluez.obex.ObjectPush ========================= --------------------------------------------- BlueZ D-Bus OBEX ObjectPush API documentation --------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez.obex :Interface: org.bluez.obex.ObjectPush1 :Object path: [Session object path] Methods ------- object, dict SendFile(string sourcefile) ```````````````````````````````````````` Sends local file to the remote device. The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see **org.bluez.obex.Transfer(5)** for the possible list of properties. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: object, dict PullBusinessCard(string targetfile) ```````````````````````````````````````````````` Request the business card from a remote device and store it in the local file. If an empty target file is given, a name will be automatically generated for the temporary file. The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see **org.bluez.obex.Transfer(5)** for the possible list of properties. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: object, dict ExchangeBusinessCards(string clientfile, string targetfile) ```````````````````````````````````````````````````````````````````````` Push the client's business card to the remote device and then retrieve the remote business card and store it in a local file. If an empty target file is given, a name will be automatically generated for the temporary file. The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see **org.bluez.obex.Transfer(5)** for the possible list of properties. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: bluez-5.87/doc/PaxHeaders/btattach.rst0000644000000000000000000000005015142177620014751 xustar0020 atime=1783094045 20 ctime=1783094113 bluez-5.87/doc/btattach.rst0000644000000000000000000000256315142177620014440 0ustar00rootroot======== btattach ======== ------------------------------------ Attach serial devices to BlueZ stack ------------------------------------ :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: November 2015 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **btattach** [**-B** *device*] [**-A** *device*] [**-P** *protocol*] [**-R**] DESCRIPTION =========== **btattach(1)** is used to attach a serial UART to the Bluetooth stack as a transport interface. OPTIONS ======= -B device, --brder device Attach a BR/EDR controller -A device, --amp device Attach an AMP controller -P protocol, --protocol protocol Specify the protocol type for talking to the device. Supported values are: .. list-table:: :header-rows: 1 :widths: auto * - *protocol* * - h4 * - bcsp * - 3wire * - h4ds * - ll * - ath3k * - intel * - bcm * - qca -S baudrate, --speed baudrate Specify which baudrate to use -N, --noflowctl Disable flow control -v, --version Show version -h, --help Show help options RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.Profile.50000644000000000000000000000005015221755302016016 xustar0020 atime=1783093954 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Profile.50000644000000000000000000000331215221755302015476 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.Profile" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.Profile \- BlueZ D-Bus Profile API documentation .SH Interface .INDENT 0.0 .TP .B Service unique name .TP .B Interface org.bluez.Profile1 .TP .B Object path freely definable .UNINDENT .SS Methods .SS void Release() [noreply] .sp This method gets called when \fBbluetoothd(8)\fP unregisters the profile. .sp A profile can use it to do cleanup tasks. There is no need to unregister the profile, because when this method gets called it has already been unregistered. .SS void NewConnection(object device, fd, dict fd_properties) .sp This method gets called when a new service level connection has been made and authorized. .sp Possible fd_properties values: .INDENT 0.0 .TP .B uint16 Version [optional] Profile version. .TP .B uint16 Features [optional] Profile features. .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.Rejected .TP .B org.bluez.Error.Canceled .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/l2cap.70000644000000000000000000000005015221755247013523 xustar0020 atime=1783093927 20 ctime=1783094112 bluez-5.87/doc/l2cap.70000644000000000000000000003005715221755247013211 0ustar00rootroot'\" t .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "l2cap" "7" "May 2024" "BlueZ" "Linux System Administration" .SH Name l2cap \- L2CAP protocol .SH SYNOPSIS .INDENT 0.0 .INDENT 3.5 .sp .EX #include #include #include l2cap_socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); .EE .UNINDENT .UNINDENT .SH DESCRIPTION .sp L2CAP is a protocol that provides an interface for higher\-level protocols to send and receive data over a Bluetooth connection. L2CAP sits on top of the Bluetooth Host Controller Interface (HCI) and provides a set of channels that can be used by higher\-level protocols to transmit data. .sp L2CAP provides a number of services to higher\-level protocols, including segmentation and reassembly of large data packets and flow control to prevent overloading of the receiver. L2CAP also supports multiple channels per connection, allowing for concurrent data transmission using different protocols. .SH SOCKET ADDRESS .INDENT 0.0 .INDENT 3.5 .sp .EX struct sockaddr_l2 { sa_family_t l2_family; unsigned short l2_psm; bdaddr_t l2_bdaddr; unsigned short l2_cid; uint8_t l2_bdaddr_type; }; .EE .UNINDENT .UNINDENT .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX struct sockaddr_l2 addr; memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; bacpy(&addr.l2_bdaddr, bdaddr); if (cid) addr.l2_cid = htobs(cid); else addr.l2_psm = htobs(psm); addr.l2_bdaddr_type = bdaddr_type; .EE .UNINDENT .UNINDENT .SH SOCKET OPTIONS (SOL_BLUETOOTH) .sp The socket options listed below can be set by using \fBsetsockopt(2)\fP and read with \fBgetsockopt(2)\fP with the socket level set to SOL_BLUETOOTH. .SS BT_SECURITY (since Linux 2.6.30) .sp Channel security level, possible values: .TS box center; l|l|l|l. T{ Value T} T{ Security Level T} T{ Link Key Type T} T{ Encryption T} _ T{ \fBBT_SECURITY_SDP\fP T} T{ 0 (SDP Only) T} T{ None T} T{ Not required T} _ T{ \fBBT_SECURITY_LOW\fP T} T{ 1 (Low) T} T{ Unauthenticated T} T{ Not required T} _ T{ \fBBT_SECURITY_MEDIUM\fP T} T{ 2 (Medium \- default) T} T{ Unauthenticated T} T{ Desired T} _ T{ \fBBT_SECURITY_HIGH\fP T} T{ 3 (High) T} T{ Authenticated T} T{ Required T} _ T{ \fBBT_SECURITY_FIPS\fP (since Linux 3.15) T} T{ 4 (Secure Only) T} T{ Authenticated (P\-256 based Secure Simple Pairing and Secure Authentication) T} T{ Required T} .TE .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX int level = BT_SECURITY_HIGH; int err = setsockopt(l2cap_socket, SOL_BLUETOOTH, BT_SECURITY, &level, sizeof(level)); if (err == \-1) { perror(\(dqsetsockopt\(dq); return 1; } .EE .UNINDENT .UNINDENT .SS BT_DEFER_SETUP (since Linux 2.6.30) .sp Channel defer connection setup, this control if the connection procedure needs to be authorized by userspace before responding which allows authorization at profile level, possible values: .TS box center; l|l|l. T{ Value T} T{ Description T} T{ Authorization T} _ T{ \fB0\fP T} T{ Disable (default) T} T{ Not required T} _ T{ \fB1\fP T} T{ Enable T} T{ Required T} .TE .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX int defer_setup = 1; int err = setsockopt(l2cap_socket, SOL_BLUETOOTH, BT_DEFER_SETUP, &defer_setup, sizeof(defer_setup)); if (err == \-1) { perror(\(dqsetsockopt\(dq); return err; } err = listen(l2cap_socket, 5); if (err) { perror(\(dqlisten\(dq); return err; } struct sockaddr_l2 remote_addr = {0}; socklen_t addr_len = sizeof(remote_addr); int new_socket = accept(l2cap_socket, (struct sockaddr*)&remote_addr, &addr_len); if (new_socket < 0) { perror(\(dqaccept\(dq); return new_socket; } /* To complete the connection setup of new_socket read 1 byte */ char c; struct pollfd pfd; memset(&pfd, 0, sizeof(pfd)); pfd.fd = new_socket; pfd.events = POLLOUT; err = poll(&pfd, 1, 0); if (err) { perror(\(dqpoll\(dq); return err; } if (!(pfd.revents & POLLOUT)) { err = read(sk, &c, 1); if (err < 0) { perror(\(dqread\(dq); return err; } } .EE .UNINDENT .UNINDENT .SS BT_FLUSHABLE (since Linux 2.6.39) .sp Channel flushable flag, this control if the channel data can be flushed or not, possible values: .TS box center; l|l|l. T{ Define T} T{ Value T} T{ Description T} _ T{ \fBBT_FLUSHABLE_OFF\fP T} T{ 0x00 (default) T} T{ Do not flush data T} _ T{ \fBBT_FLUSHABLE_ON\fP T} T{ 0x01 T} T{ Flush data T} .TE .SS BT_POWER (since Linux 3.1) .sp Channel power policy, this control if the channel shall force exit of sniff mode or not, possible values: .TS box center; l|l|l. T{ Define T} T{ Value T} T{ Description T} _ T{ \fBBT_POWER_FORCE_ACTIVE_OFF\fP T} T{ 0x00 T} T{ Don\(aqt force exit of sniff mode T} _ T{ \fBBT_POWER_FORCE_ACTIVE_ON\fP T} T{ 0x01 (default) T} T{ Force exit of sniff mode T} .TE .SS BT_CHANNEL_POLICY (since Linux 3.10) .sp High\-speed (AMP) channel policy, possible values: .TS box center; l|l|l. T{ Define T} T{ Value T} T{ Description T} _ T{ \fBBT_CHANNEL_POLICY_BREDR_ONLY\fP T} T{ 0 (default) T} T{ BR/EDR only T} _ T{ \fBBT_CHANNEL_POLICY_BREDR_PREFERRED\fP T} T{ 1 T} T{ BR/EDR Preferred T} _ T{ \fBBT_CHANNEL_POLICY_BREDR_PREFERRED\fP T} T{ 2 T} T{ AMP Preferred T} .TE .SS BT_PHY (since Linux 5.10) .sp Channel supported PHY(s), possible values: .TS box center; l|l|l. T{ Define T} T{ Value T} T{ Description T} _ T{ \fBBT_PHY_BR_1M_1SLOT\fP T} T{ BIT 0 T} T{ BR 1Mbps 1SLOT T} _ T{ \fBBT_PHY_BR_1M_3SLOT\fP T} T{ BIT 1 T} T{ BR 1Mbps 3SLOT T} _ T{ \fBBT_PHY_BR_1M_5SLOT\fP T} T{ BIT 2 T} T{ BR 1Mbps 5SLOT T} _ T{ \fBBT_PHY_BR_2M_1SLOT\fP T} T{ BIT 3 T} T{ EDR 2Mbps 1SLOT T} _ T{ \fBBT_PHY_BR_2M_3SLOT\fP T} T{ BIT 4 T} T{ EDR 2Mbps 3SLOT T} _ T{ \fBBT_PHY_BR_2M_5SLOT\fP T} T{ BIT 5 T} T{ EDR 2Mbps 5SLOT T} _ T{ \fBBT_PHY_BR_3M_1SLOT\fP T} T{ BIT 6 T} T{ EDR 3Mbps 1SLOT T} _ T{ \fBBT_PHY_BR_3M_3SLOT\fP T} T{ BIT 7 T} T{ EDR 3Mbps 3SLOT T} _ T{ \fBBT_PHY_BR_3M_5SLOT\fP T} T{ BIT 8 T} T{ EDR 3Mbps 5SLOT T} _ T{ \fBBT_PHY_LE_1M_TX\fP T} T{ BIT 9 T} T{ LE 1Mbps TX T} _ T{ \fBBT_PHY_LE_1M_RX\fP T} T{ BIT 10 T} T{ LE 1Mbps RX T} _ T{ \fBBT_PHY_LE_2M_TX\fP T} T{ BIT 11 T} T{ LE 2Mbps TX T} _ T{ \fBBT_PHY_LE_2M_RX\fP T} T{ BIT 12 T} T{ LE 2Mbps RX T} _ T{ \fBBT_PHY_LE_CODED_TX\fP T} T{ BIT 13 T} T{ LE Coded TX T} _ T{ \fBBT_PHY_LE_CODED_RX\fP T} T{ BIT 14 T} T{ LE Coded RX T} .TE .SS BT_MODE (since Linux 5.10) .sp Channel Mode, possible values: .TS box center; l|l|l|l. T{ Define T} T{ Value T} T{ Description T} T{ Link T} _ T{ \fBBT_MODE_BASIC\fP T} T{ 0x00 (default) T} T{ Basic mode T} T{ Any T} _ T{ \fBBT_MODE_ERTM\fP T} T{ 0x01 T} T{ Enhanced Retransmission mode T} T{ BR/EDR T} _ T{ \fBBT_MODE_STREAM\fP T} T{ 0x02 T} T{ Stream mode T} T{ BR/EDR T} _ T{ \fBBT_MODE_LE_FLOWCTL\fP T} T{ 0x03 T} T{ Credit based flow control mode T} T{ LE T} _ T{ \fBBT_MODE_EXT_FLOWCTL\fP T} T{ 0x04 T} T{ Extended Credit based flow control mode T} T{ Any T} .TE .SH SOCKET OPTIONS (SOL_SOCKET) .sp \fBSOL_SOCKET\fP level socket options that modify generic socket features (\fBSO_SNDBUF\fP, \fBSO_RCVBUF\fP, etc.) have their usual meaning, see \fBsocket(7)\fP\&. .sp The \fBSOL_SOCKET\fP level L2CAP socket options that have Bluetooth\-specific handling in kernel are listed below. .SS SO_TIMESTAMPING, SO_TIMESTAMP, SO_TIMESTAMPNS .sp See \% .sp For L2CAP sockets, software RX timestamps are supported. Software TX timestamps (SOF_TIMESTAMPING_TX_SOFTWARE, SOF_TIMESTAMPING_TX_COMPLETION) are supported since Linux 6.15. .sp The software RX timestamp is the time when the kernel received the packet from the controller driver. .sp The \fBSCM_TSTAMP_SND\fP timestamp is emitted when packet is sent to the controller driver. The \fBSCM_TSTAMP_COMPLETION\fP timestamp is emitted when controller reports the packet completed. Other TX timestamp types are not supported. .sp You can use \fBSIOCETHTOOL\fP to query supported flags. .sp The timestamps are in \fBCLOCK_REALTIME\fP time. .sp Example (Enable RX timestamping): .INDENT 0.0 .INDENT 3.5 .sp .EX int flags = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE; setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)); .EE .UNINDENT .UNINDENT .sp Example (Read packet and its RX timestamp): .INDENT 0.0 .INDENT 3.5 .sp .EX char data_buf[256]; union { char buf[CMSG_SPACE(sizeof(struct scm_timestamping))]; struct cmsghdr align; } control; struct iovec data = { .iov_base = data_buf, .iov_len = sizeof(data_buf), }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct scm_timestamping tss; res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT); if (res < 0) goto error; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg\->cmsg_level == SOL_SOCKET && cmsg\->cmsg_type == SCM_TIMESTAMPING) memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); } tstamp_clock_realtime = tss.ts[0]; .EE .UNINDENT .UNINDENT .sp Example (Enable TX timestamping): .INDENT 0.0 .INDENT 3.5 .sp .EX int flags = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_TX_COMPLETION | SOF_TIMESTAMPING_OPT_ID; setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)); .EE .UNINDENT .UNINDENT .sp Example (Read TX timestamps): .INDENT 0.0 .INDENT 3.5 .sp .EX union { char buf[2 * CMSG_SPACE(sizeof(struct scm_timestamping))]; struct cmsghdr align; } control; struct iovec data = { .iov_base = NULL, .iov_len = 0 }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct cmsghdr *cmsg; struct scm_timestamping tss; struct sock_extended_err serr; int res; res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT); if (res < 0) goto error; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg\->cmsg_level == SOL_SOCKET && cmsg\->cmsg_type == SCM_TIMESTAMPING) memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); else if (cmsg\->cmsg_level == SOL_BLUETOOTH && cmsg\->cmsg_type == BT_SCM_ERROR) memcpy(&serr, CMSG_DATA(cmsg), sizeof(serr)); } tstamp_clock_realtime = tss.ts[0]; tstamp_type = serr\->ee_info; /* SCM_TSTAMP_SND or SCM_TSTAMP_COMPLETION */ tstamp_seqnum = serr\->ee_data; .EE .UNINDENT .UNINDENT .SH IOCTLS .sp The following ioctls with operation specific for L2CAP sockets are available. .SS SIOCETHTOOL (since Linux 6.16\-rc1) .sp Supports only command \fIETHTOOL_GET_TS_INFO\fP, which may be used to query supported \fISOF_TIMESTAMPING_*\fP flags. The \fISOF_TIMESTAMPING_OPT_*\fP flags are always available as applicable. .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX #include #include #include #include #include \&... struct ifreq ifr = {}; struct ethtool_ts_info cmd = {}; int sk; snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), \(dqhci0\(dq); ifr.ifr_data = (void *)&cmd; cmd.cmd = ETHTOOL_GET_TS_INFO; sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (sk < 0) goto error; if (ioctl(sk, SIOCETHTOOL, &ifr)) goto error; sof_available = cmd.so_timestamping; .EE .UNINDENT .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH SEE ALSO .sp socket(7), l2test(1) .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/bluetoothd.rst0000644000000000000000000000005015221753037015330 xustar0020 atime=1783092778 20 ctime=1783094113 bluez-5.87/doc/bluetoothd.rst0000644000000000000000000000534615221753037015021 0ustar00rootroot========== bluetoothd ========== ---------------- Bluetooth daemon ---------------- :Authors: - Marcel Holtmann - Philipp Matthias Hahn - Fredrik Noring :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: March, 2004 :Manual section: 8 :Manual group: System management commands SYNOPSIS ======== **bluetoothd** [--version] | [--help] **bluetoothd** [--nodetach] [--compat] [--experimental] [--debug=<*files*>] [--plugin=<*plugins*>] [--noplugin=<*plugins*>] DESCRIPTION =========== This manual page documents briefly the **bluetoothd** daemon, which manages all the Bluetooth devices. **bluetoothd** can also provide a number of services via the D-Bus message bus system. OPTIONS ======= -v, --version Print bluetoothd version and exit. -h, --help Print bluetoothd options and exit. -n, --nodetach Enable logging in foreground. Directs log output to the controlling terminal in addition to syslog. -f, --configfile Specifies an explicit config file path instead of relying on the default path(*/usr/local/etc/bluetooth/main.conf*) for the config file. -d, --debug=::... Sets how much information bluetoothd sends to the log destination (usually syslog's "daemon" facility). If the file options are omitted, then debugging information from all the source files are printed. If file options are present, then only debug prints from that source file are printed. The option can be a pattern containing "*" and "?" characters. Example: --debug=src/adapter.c:src/agent.c -p, --plugin=,,.. Load these plugins only. The option can be a pattern containing "*" and "?" characters. -P, --noplugin=,,.. Never load these plugins. The option can be a pattern containing "*" and "?" characters. -C, --compat Provide deprecated command line interfaces. -E, --experimental Enable D-Bus experimental interfaces. These interfaces are not guaranteed to be compatible or present in future releases. -T, --testing Enable D-Bus testing interfaces. These interfaces are only meant for test validation of the internals of bluetoothd and shall not never be used by anything other than that. -K, --kernel=,,... Enable Kernel experimental features. Kernel experimental features are considered unstable and may be removed from future kernel releases. FILES ===== */usr/local/etc/bluetooth/main.conf* Location of the global configuration file. RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.Agent.50000644000000000000000000000005015221755276015466 xustar0020 atime=1783093950 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Agent.50000644000000000000000000001127315221755276015153 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.Agent" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.Agent \- BlueZ D-Bus Agent API documentation .SH Interface .INDENT 0.0 .TP .B Service unique name .TP .B Interface org.bluez.Agent1 .TP .B Object path freely definable .UNINDENT .SS Methods .SS void Release() .sp This method gets called when \fBbluetoothd(8)\fP unregisters the agent. .sp An agent can use it to do cleanup tasks. There is no need to unregister the agent, because when this method gets called it has already been unregistered. .SS string RequestPinCode(object device) .sp This method gets called when \fBbluetoothd(8)\fP needs to get the passkey for an authentication. .sp The return value should be a string of 1\-16 characters length. The string can be alphanumeric. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.Rejected .TP .B org.bluez.Error.Canceled .UNINDENT .SS void DisplayPinCode(object device, string pincode) .sp This method gets called when \fBbluetoothd(8)\fP needs to display a pincode for an authentication. .sp An empty reply should be returned. When the pincode needs no longer to be displayed, the Cancel method of the agent will be called. .sp This is used during the pairing process of keyboards that don\(aqt support Bluetooth 2.1 Secure Simple Pairing, in contrast to DisplayPasskey which is used for those that do. .sp This method will only ever be called once since older keyboards do not support typing notification. .sp Note that the PIN will always be a 6\-digit number, zero\-padded to 6 digits. This is for harmony with the later specification. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.Rejected .TP .B org.bluez.Error.Canceled .UNINDENT .SS uint32 RequestPasskey(object device) .sp This method gets called when \fBbluetoothd(8)\fP needs to get the passkey for an authentication. .sp The return value should be a numeric value between 0\-999999. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.Rejected .TP .B org.bluez.Error.Canceled .UNINDENT .SS void DisplayPasskey(object device, uint32 passkey, uint16 entered) .sp This method gets called when \fBbluetoothd(8)\fP needs to display a passkey for an authentication. .sp The entered parameter indicates the number of already typed keys on the remote side. .sp An empty reply should be returned. When the passkey needs no longer to be displayed, the Cancel method of the agent will be called. .sp During the pairing process this method might be called multiple times to update the entered value. .sp Note that the passkey will always be a 6\-digit number, so the display should be zero\-padded at the start if the value contains less than 6 digits. .SS void RequestConfirmation(object device, uint32 passkey) .sp This method gets called when \fBbluetoothd(8)\fP needs to confirm a passkey for an authentication. .sp To confirm the value it should return an empty reply or an error in case the passkey is invalid. .sp Note that the passkey will always be a 6\-digit number, so the display should be zero\-padded at the start if the value contains less than 6 digits. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.Rejected .TP .B org.bluez.Error.Canceled .UNINDENT .SS void RequestAuthorization(object device) .sp This method gets called to request the user to authorize an incoming pairing attempt which would in other circumstances trigger the just\-works model, or when the user plugged in a device that implements cable pairing. In the latter case, the device would not be connected to the adapter via Bluetooth yet. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.Rejected .TP .B org.bluez.Error.Canceled .UNINDENT .SS void AuthorizeService(object device, string uuid) .sp This method gets called when \fBbluetoothd(8)\fP needs to authorize a connection/service request. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.Rejected .TP .B org.bluez.Error.Canceled .UNINDENT .SS void Cancel() .sp This method gets called to indicate that the agent request failed before a reply was returned. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/mgmt-protocol.rst0000644000000000000000000000005015142177620015762 xustar0020 atime=1783093920 20 ctime=1783094111 bluez-5.87/doc/mgmt-protocol.rst0000644000000000000000000050216415142177620015453 0ustar00rootroot==== mgmt ==== ------------------------------------------- Bluetooth Management Protocol documentation ------------------------------------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: April 2025 :Manual section: 7 :Manual group: Linux System Administration SYNOPSIS ======== The Bluetooth management sockets can be created by setting the hci_channel member of struct sockaddr_hci to HCI_CHANNEL_CONTROL (see hci(7)) when creating a raw HCI socket:: int mgmt_create(void) { struct sockaddr_hci addr; int fd; fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, BTPROTO_HCI); if (fd < 0) return -errno; memset(&addr, 0, sizeof(addr)); addr.hci_family = AF_BLUETOOTH; addr.hci_dev = HCI_DEV_NONE; addr.hci_channel = HCI_CHANNEL_CONTROL; if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { int err = -errno; close(fd); return err; } return fd; } The process creating the mgmt socket is required to have the CAP_NET_ADMIN capability (e.g. root would have this). DESCRIPTION =========== This document describes the format of data used for communicating with the kernel using a so-called Bluetooth Management sockets. These sockets are available starting with Linux kernel version 3.4 The following kernel versions introduced new commands, new events or important fixes to the Bluetooth Management API: .. csv-table:: :header: "Version", "Kernel Version", "Commands", "Events" :widths: auto 1.1, 3.4, Set Device ID, 1.2, 3.7,,Passkey Notify 1.3, 3.9,, 1.4, 3.13, Set Advertising, ,,Set BR/EDR, ,,Set Static Address, ,,Set Scan Parameters, 1.5, 3.15, Set Secure Connections, New Identity Resolving Key ,,Set Debug Keys, New Signature Resolving Key ,,Set Privacy, ,,Load Identity Resolving Keys, 1.6, 3.16, Get Connection Information, 1.7, 3.17, Get Clock Information, ,,Add Device, Device Added ,,Remove Device, Device Removed ,,Load Connection Parameters, New Connection Parameter ,,Read Unconfigured Index List, Unconfigured Index Added ,,Read Controller Configuration Information, Unconfigured Index Removed ,,Set External Configuration, New Configuration Options ,,Set Public Address commands, 1.8, 3.19, Start Service Discovery, 1.9, 4.1, Read Local Out Of Band Extended Data, Extended Index Added ,,Read Extended Controller Index List, Extended Index Removed ,,Read Advertising Features, Local Out Of Band Extended Data Updated ,,Add Advertising, Advertising Added ,,Remove Advertising, Advertising Removed 1.10, 4.2,, 1.11, 4.5, Get Advertising Size Information, ,,Start Limited Discovery, 1.12, 4.6,, 1.13, 4.8,, 1.14, 4.9, Read Extended Controller Information, Extended Controller Information Changed ,,Set Appearance, 1.15, 5.5, Get PHY Configuration, PHY Configuration Changed ,,Set PHY Configuration, ,,Load Blocked Keys, 1.16, 5.6, Set Wideband Speech, 1.17, 5.7, Read Controller Capabilities, Experimental Feature Changed ,,Read Experimental Features Information, ,,Set Experimental Feature, 1.18, 5.8, Read Default System Configuration, Default System Configuration Changed ,,Set Default System Configuration, Default Runtime Configuration Changed ,,Read Default Runtime Configuration, Default Runtime Configuration Changed ,,Set Default Runtime Configuration, Device Flags Changed ,,Get Device Flags, Advertisement Monitor Added ,,Set Device Flags, Advertisement Monitor Removed ,,Read Advertisement Monitor Features, Controller Suspend ,,Add Advertisement Patterns Monitor, Controller Resume ,,Remove Advertisement Monitor, Advertisement Monitor Device Found ,,,Advertisement Monitor Device Lost 1.19, 5.10, Add Extended Advertising Parameters, ,,Add Extended Advertising Data, 1.20, 5.11, Add Advertisement Patterns Monitor With RSSI Threshold, 1.21, 6.0, Set Mesh Receiver, Mesh Device Found ,,Read Mesh Features, Mesh Packet Transmit Complete ,,Transmit Mesh Packet, ,,Cancel Transmit Mesh Packet, Packet Structures ================= Commands: .. code-block:: 0 4 8 12 16 22 24 28 31 35 39 43 47 +-------------------+-------------------+-------------------+ | Command Code | Controller Index | Parameter Length | +-------------------+-------------------+-------------------+ | | Events: .. code-block:: 0 4 8 12 16 22 24 28 31 35 39 43 47 +-------------------+-------------------+-------------------+ | Event Code | Controller Index | Parameter Length | +-------------------+-------------------+-------------------+ | | All fields are in little-endian byte order (least significant byte first). Controller Index can have a special value to indicate that command or event is not related to any controller. Possible values: .. csv-table:: :header: "Value", "Description" :widths: auto 0x0000-0xFFFE, 0xFFFF, Error Codes =========== The following values have been defined for use with the Command Status and Command Complete events: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Success 0x01, Unknown Command 0x02, Not Connected 0x03, Failed 0x04, Connect Failed 0x05, Authentication Failed 0x06, Not Paired 0x07, No Resources 0x08, Timeout 0x09, Already Connected 0x0A, Busy 0x0B, Rejected 0x0C, Not Supported 0x0D, Invalid Parameters 0x0E, Disconnected 0x0F,Not Powered 0x10, Cancelled 0x11, Invalid Index 0x12, RFKilled 0x13, Already Paired 0x14, Permission Denied As a general rule all commands generate the events as specified below, however invalid lengths or unknown commands will always generate a Command Status response (with Unknown Command or Invalid Parameters status). Sending a command with an invalid Controller Index value will also always generate a Command Status event with the Invalid Index status code. Commands -------- Read Management Version Information ``````````````````````````````````` :Command Code: 0x0001 :Controller Index: :Command Parameters: :Return Parameters: Version (1 Octets) :...: Revision (2 Octets) This command returns the Management version and revision. Besides, being informational the information can be used to determine whether certain behavior has changed or bugs fixed when interacting with the kernel. This command generates a Command Complete event on success or a Command Status event on failure. Read Management Supported Commands `````````````````````````````````` :Command Code: 0x0002 :Controller Index: :Command Parameters: :Return Parameters: Num_Of_Commands (2 Octets) :...: Num_Of_Events (2 Octets) :...: Command[] (2 Octets) :...: ...[] :...: Event[] (2 Octets) :...: ...[] This command returns the list of supported Management commands and events. The commands Read Management Version Information and Read management Supported Commands are not included in this list. Both commands are always supported and mandatory. The events Command Status and Command Complete are not included in this list. Both are implicit and mandatory. This command generates a Command Complete event on success or a Command Status event on failure. Read Controller Index List `````````````````````````` :Command Code: 0x0003 :Controller Index: :Command Parameters: :Return Parameters: Num_Controllers (2 Octets) :...: Controller_Index[] (2 Octets) This command returns the list of currently known controllers. Controllers added or removed after calling this command can be monitored using the Index Added and Index Removed events. This command generates a Command Complete event on success or a Command Status event on failure. Read Controller Information ``````````````````````````` :Command Code: 0x0004 :Controller Index: :Command Parameters: :Return Parameters: Address (6 Octets) :...: Bluetooth_Version (1 Octet) :...: Manufacturer (2 Octets) :...: Supported_Settings (4 Octets) :...: Current_Settings (4 Octets) :...: Class_Of_Device (3 Octets) :...: Name (249 Octets) :...: Short_Name (11 Octets) This command is used to retrieve the current state and basic information of a controller. It is typically used right after getting the response to the Read Controller Index List command or an Index Added event. The Address parameter describes the controllers public address and it can be expected that it is set. However in case of single mode Low Energy only controllers it can be 00:00:00:00:00:00. To power on the controller in this case, it is required to configure a static address using Set Static Address command first. If the public address is set, then it will be used as identity address for the controller. If no public address is available, then the configured static address will be used as identity address. In the case of a dual-mode controller with public address that is configured as Low Energy only device (BR/EDR switched off), the static address is used when set and public address otherwise. If no short name is set the Short_Name parameter will be empty (begin with a nul byte). Current_Settings and Supported_Settings is a bitmask with currently the following available bits: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, Powered 1, Connectable 2, Fast Connectable 3, Discoverable 4, Bondable 5, Link Level Security (Sec. mode 3) 6, Secure Simple Pairing 7, Basic Rate/Enhanced Data Rate 8, High Speed 9, Low Energy 10, Advertising 11, Secure Connections 12, Debug Keys 13, Privacy 14, Controller Configuration 15, Static Address 16, PHY Configuration 17, Wideband Speech 18, Connected Isochronous Stream - Central 19, Connected Isochronous Stream - Peripheral 20, Isochronous Broadcaster 21, Synchronized Receiver 22, LL Privacy 23, PAST Sender 24, PAST Receiver This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Set Powered ``````````` :Command Code: 0x0005 :Controller Index: :Command Parameters: Powered (1 Octet) :Return Parameters: Current_Settings (4 Octets) This command is used to power on or off a controller. The allowed Powered command parameter values are 0x00 and 0x01. All other values will return Invalid Parameters. If discoverable setting is activated with a timeout, then switching the controller off will expire this timeout and disable discoverable. Settings programmed via Set Advertising and Add/Remove Advertising while the controller was powered off will be activated when powering the controller on. Switching the controller off will permanently cancel and remove all advertising instances with a timeout set, i.e. time limited advertising instances are not being remembered across power cycles. Advertising Removed events will be issued accordingly. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Busy: :Invalid Parameters: :Invalid Index: Set Discoverable ```````````````` :Command Code: 0x0006 :Controller Index: :Command Parameters: Discoverable (1 Octet) :...: Timeout (2 Octets) :Return Parameters: Current_Settings (4 Octets) This command is used to set the discoverable property of a controller. The allowed Discoverable command parameter values are: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Not Discoverable 0x01, General Discoverable 0x02 (since 1.4), Limited Discoverable Timeout is the time in seconds and is only meaningful when Discoverable is set to 0x01 or 0x02. Providing a timeout with 0x00 return Invalid Parameters. For 0x02, the timeout value is required. This command is only available for BR/EDR capable controllers (e.g. not for single-mode LE ones). It will return Not Supported otherwise. This command can be used when the controller is not powered and all settings will be programmed once powered. However using a timeout when the controller is not powered will return Not Powered error. When switching discoverable on and the connectable setting is off it will return Rejected error. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Busy: :Rejected: :Not Supported: :Invalid Parameters: :Not Powered: :Invalid Index: Set Connectable ``````````````` :Command Code: 0x0007 :Controller Index: :Command Parameters: Connectable (1 Octet) :Return Parameters: Current_Settings (4 Octets) This command is used to set the connectable property of a controller. The allowed Connectable command parameter values are 0x00 and 0x01. All other values will return Invalid Parameters. This command is available for BR/EDR, LE-only and also dual mode controllers. For BR/EDR is changes the page scan setting and for LE controllers it changes the advertising type. For dual mode controllers it affects both settings. For LE capable controllers the connectable setting takes effect when advertising is enabled (peripheral) or when directed advertising events are received (central). This command can be used when the controller is not powered and all settings will be programmed once powered. When switching connectable off, it will also switch off the discoverable setting. Switching connectable back on will not restore a previous discoverable. It will stay off and needs to be manually switched back on. When switching connectable off, it will expire a discoverable setting with a timeout. This setting does not affect known devices from Add Device command. These devices are always allowed to connect. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Busy: :Not Supported: :Invalid Parameters: :Invalid Index: Set Fast Connectable ```````````````````` :Command Code: 0x0008 :Controller Index: :Command Parameters: Enable (1 Octet) :Return Parameters: Current_Settings (4 Octets) This command is used to set the controller into a connectable state where the page scan parameters have been set in a way to favor faster connect times with the expense of higher power consumption. The allowed values of the Enable command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. This command is only available for BR/EDR capable controllers (e.g. not for single-mode LE ones). It will return Not Supported otherwise. This command can be used when the controller is not powered and all settings will be programmed once powered. The setting will be remembered during power down/up toggles. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Failed: :Busy: :Not Supported: :Invalid Parameters: :Invalid Index: Set Bondable ```````````` :Command Code: 0x0009 :Controller Index: :Command Parameters: Bondable (1 Octet) :Return Parameters: Current_Settings (4 Octets) This command is used to set the bondable property of an controller. The allowed values for the Bondable command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. This command can be used when the controller is not powered and all settings will be programmed once powered. Turning bondable on will not automatically switch the controller into connectable mode. That needs to be done separately. The setting will be remembered during power down/up toggles. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Set Link Security ````````````````` :Command Code: 0x000A :Controller Index: :Command Parameters: Link_Security (1 Octet) :Return Parameters: Current_Settings (4 Octets) This command is used to either enable or disable link level security for an controller (also known as Security Mode 3). The allowed values for the Link_Security command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. This command is only available for BR/EDR capable controllers (e.g. not for single-mode LE ones). It will return Not Supported otherwise. This command can be used when the controller is not powered and all settings will be programmed once powered. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Busy: :Not Supported: :Invalid Parameters: :Invalid Index: Set Secure Simple Pairing ````````````````````````` :Command Code: 0x000B :Controller Index: :Command Parameters: Secure_Simple_Pairing (1 Octet) :Return Parameters: Current_Settings (4 Octets) This command is used to enable/disable Secure Simple Pairing support for a controller. The allowed values for the Secure_Simple_Pairing command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. This command is only available for BR/EDR capable controllers supporting the core specification version 2.1 or greater (e.g. not for single-mode LE controllers or pre-2.1 ones). This command can be used when the controller is not powered and all settings will be programmed once powered. In case the controller does not support Secure Simple Pairing, the command will fail regardless with Not Supported error. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Busy: :Not Supported: :Invalid Parameters: :Invalid Index: Set High Speed `````````````` :Command Code: 0x000C :Controller Index: :Command Parameters: High_Speed (1 Octet) :Return Parameters: Current_Settings (4 Octets) This command is used to enable/disable Bluetooth High Speed support for a controller. The allowed values for the High_Speed command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. This command is only available for BR/EDR capable controllers (e.g. not for single-mode LE ones). This command can be used when the controller is not powered and all settings will be programmed once powered. To enable High Speed support, it is required that Secure Simple Pairing support is enabled first. High Speed support is not possible for connections without Secure Simple Pairing. When switching Secure Simple Pairing off, the support for High Speed will be switched off as well. Switching Secure Simple Pairing back on, will not re-enable High Speed support. That needs to be done manually. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Not Supported: :Invalid Parameters: :Invalid Index: Set Low Energy `````````````` :Command Code: 0x000D :Controller Index: :Command Parameters: Low_Energy (1 Octet) :Return Parameters: Current_Settings (4 Octets) This command is used to enable/disable Low Energy support for a controller. The allowed values of the Low_Energy command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. This command is only available for LE capable controllers and will yield in a Not Supported error otherwise. This command can be used when the controller is not powered and all settings will be programmed once powered. In case the kernel subsystem does not support Low Energy or the controller does not either, the command will fail regardless. Disabling LE support will permanently disable and remove all advertising instances configured with the Add Advertising command. Advertising Removed events will be issued accordingly. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Busy: :Not Supported: :Invalid Parameters: :Invalid Index: Set Device Class ```````````````` :Command Code: 0x000E :Controller Index: :Command Parameters: Major_Class (1 Octet) :...: Minor_Class (1 Octet) :Return Parameters: Class_Of_Device (3 Octets) This command is used to set the major and minor device class for BR/EDR capable controllers. This command will also implicitly disable caching of pending CoD and EIR updates. This command is only available for BR/EDR capable controllers (e.g. not for single-mode LE ones). This command can be used when the controller is not powered and all settings will be programmed once powered. In case the controller is powered off, 0x000000 will be returned for the class of device parameter. And after power on the new value will be announced via class of device changed event. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Busy: :Not Supported: :Invalid Parameters: :Invalid Index: Set Local Name `````````````` :Command Code: 0x000F :Controller Index: :Command Parameters: Name (249 Octets) :...: Short_Name (11 Octets) :Return Parameters: Name (249 Octets) :...: Short_Name (11 Octets) This command is used to set the local name of a controller. The command parameters also include a short name which will be used in case the full name doesn't fit within EIR/AD data. The name parameters need to always end with a null byte (failure to do so will cause the command to fail). This command can be used when the controller is not powered and all settings will be programmed once powered. The values of name and short name will be remembered when switching the controller off and back on again. So the name and short name only have to be set once when a new controller is found and will stay until removed. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Add UUID ```````` :Command Code: 0x0010 :Controller Index: :Command Parameters: UUID (16 Octets) :...: SVC_Hint (1 Octet) :Return Parameters: Class_Of_Device (3 Octets) This command is used to add a UUID to be published in EIR data. The accompanied SVC_Hint parameter is used to tell the kernel whether the service class bits of the Class of Device value need modifying due to this UUID. This command can be used when the controller is not powered and all settings will be programmed once powered. In case the controller is powered off, 0x000000 will be returned for the class of device parameter. And after power on the new value will be announced via class of device changed event. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Busy: :Invalid Parameters: :Invalid Index: Remove UUID ``````````` :Command Code: 0x0011 :Controller Index: :Command Parameters: UUID (16 Octets) :Return Parameters: Class_Of_Device (3 Octets) This command is used to remove a UUID previously added using the Add UUID command. When the UUID parameter is an empty UUID (16 x 0x00), then all previously loaded UUIDs will be removed. This command can be used when the controller is not powered and all settings will be programmed once powered. In case the controller is powered off, 0x000000 will be returned for the class of device parameter. And after power on the new value will be announced via class of device changed event. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Busy: :Invalid Parameters: :Invalid Index: Load Link Keys `````````````` :Command Code: 0x0012 :Controller Index: :Command Parameters: Debug_Keys (1 Octet) :...: Key_Count (2 Octets) :...: Address[] (6 Octets) :...: Address_Type[] (1 Octet) :...: Key_Type[] (1 Octet) :...: Value[] (16 Octets) :...: PIN_Length[] (1 Octet) :...: ...[] :Return Parameters: This command is used to feed the kernel with currently known link keys. The command does not need to be called again upon the receipt of New Link Key events since the kernel updates its list automatically. The Debug_Keys parameter is used to tell the kernel whether to accept the usage of debug keys or not. The allowed values for this parameter are 0x00 and 0x01. All other values will return an Invalid Parameters response. Usage of the Debug_Keys parameter is deprecated and has been replaced with the Set Debug Keys command. When setting the Debug_Keys option via Load Link Keys command it has the same affect as setting it via Set Debug Keys and applies to all keys in the system. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0, BR/EDR 1, Reserved (not in use) 2, Reserved (not in use) Public and random LE addresses are not valid and will be rejected. Currently defined Key_Type values are: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Combination key 0x01, Local Unit key 0x02, Remote Unit key 0x03, Debug Combination key 0x04, Unauthenticated Combination key from P-192 0x05, Authenticated Combination key from P-192 0x06, Changed Combination key 0x07, Unauthenticated Combination key from P-256 0x08, Authenticated Combination key from P-256 This command can be used when the controller is not powered. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Load Long Term Keys ``````````````````` :Command Code: 0x0013 :Controller Index: :Command Parameters: Key_Count (2 Octets) :...: Address[] (6 Octets) :...: Address_Type[] (1 Octet) :...: Key_Type[] (1 Octet) :...: Central[] (1 Octet) :...: Encryption_Size[] (1 Octet) :...: Encryption_Diversifier[] (2 Octets) :...: Random_Number[] (8 Octets) :...: Value[] (16 Octets) :...: ...[] :Return Parameters: This command is used to feed the kernel with currently known (SMP) Long Term Keys. The command does not need to be called again upon the receipt of New Long Term Key events since the kernel updates its list automatically. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Reserved (not in use) 0x01, LE Public 0x02, LE Random The provided Address and Address_Type are the identity of a device. So either its public address or static random address. Unresolvable random addresses and resolvable random addresses are not valid and will be rejected. Currently defined Key_Type values are: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Unauthenticated key 0x01, Authenticated key This command can be used when the controller is not powered. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Disconnect `````````` :Command Code: 0x0014 :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is used to force the disconnection of a currently connected device. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random This command can only be used when the controller is powered. This command generates a Command Complete event on success or failure. Possible errors: :Not Connected: :Busy: :Invalid Parameters: :Not Powered: :Invalid Index: Get Connections ``````````````` :Command Code: 0x0015 :Controller Index: :Command Parameters: :Return Parameters: Connection_Count (2 Octets) :...: Address[] (6 Octets) :...: Address_Type[] (1 Octet) :...: ...[] This command is used to retrieve a list of currently connected devices. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random For devices using resolvable random addresses with a known identity resolving key, the Address and Address_Type will contain the identity information. This command can only be used when the controller is powered. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Not Powered: :Invalid Index: PIN Code Reply `````````````` :Command Code: 0x0016 :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: PIN_Length (1 Octet) :...: PIN_Code (16 Octets) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is used to respond to a PIN Code request event. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random This command can only be used when the controller is powered. This command generates a Command Complete event on success or failure. Possible errors: :Not Connected: :Invalid Parameters: :Not Powered: :Invalid Index: PIN Code Negative Reply ``````````````````````` :Command Code: 0x0017 :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is used to return a negative response to a PIN Code Request event. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random This command can only be used when the controller is powered. This command generates a Command Complete event on success or failure. Possible errors: :Not Connected: :Invalid Parameters: :Not Powered: :Invalid Index: Set IO Capability ````````````````` :Command Code: 0x0018 :Controller Index: :Command Parameters: IO_Capability (1 Octet) :Return Parameters: This command is used to set the IO Capability used for pairing. The command accepts both SSP and SMP values. Possible values for the IO_Capability parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, DisplayOnly 0x01, DisplayYesNo 0x02, KeyboardOnly 0x03, NoInputNoOutput 0x04, KeyboardDisplay Passing a value 0x04 (KeyboardDisplay) will cause the kernel to convert it to 0x01 (DisplayYesNo) in the case of a BR/EDR connection (as KeyboardDisplay is specific to SMP). This command can be used when the controller is not powered. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Pair Device ``````````` :Command Code: 0x0019 :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: IO_Capability (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is used to trigger pairing with a remote device. The IO_Capability command parameter is used to temporarily (for this pairing event only) override the global IO Capability (set using the Set IO Capability command). Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random Possible values for the IO_Capability parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, DisplayOnly 0x01, DisplayYesNo 0x02, KeyboardOnly 0x03, NoInputNoOutput 0x04, KeyboardDisplay Passing a value 0x04 (KeyboardDisplay) will cause the kernel to convert it to 0x01 (DisplayYesNo) in the case of a BR/EDR connection (as KeyboardDisplay is specific to SMP). The Address and Address_Type of the return parameters will return the identity address if known. In case of resolvable random address given as command parameters and the remote provides an identity resolving key, the return parameters will provide the resolved address. To allow tracking of which resolvable random address changed into which identity address, the New Identity Resolving Key event will be sent before receiving Command Complete event for this command. This command can only be used when the controller is powered. This command generates a Command Complete event on success or failure. Reject status is used when requested transport is not enabled. Not Supported status is used if controller is not capable with requested transport. Possible errors: :Rejected: :Not Supported: :Connect Failed: :Busy: :Invalid Parameters: :Not Powered: :Invalid Index: :Already Paired: Cancel Pair Device `````````````````` :Command Code: 0x001A :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) The Address and Address_Type parameters should match what was given to a preceding Pair Device command. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random This command can only be used when the controller is powered. This command generates a Command Complete event on success or failure. Possible errors: :Invalid Parameters: :Not Powered: :Invalid Index: Unpair Device ````````````` :Command Code: 0x001B :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Disconnect (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) Removes all keys associated with the remote device. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random The Disconnect parameter tells the kernel whether to forcefully disconnect any existing connections to the device. It should in practice always be 1 except for some special GAP qualification test-cases where a key removal without disconnecting is needed. When unpairing a device its link key, long term key and if provided identity resolving key will be purged. For devices using resolvable random addresses where the identity resolving key was available, after this command they will now no longer be resolved. The device will essentially become private again. This command can only be used when the controller is powered. This command generates a Command Complete event on success or failure. Possible errors: :Not Paired: :Invalid Parameters: :Not Powered: :Invalid Index: User Confirmation Reply ``````````````````````` :Command Code: 0x001C :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is used to respond to a User Confirmation Request event. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random This command can only be used when the controller is powered. This command generates a Command Complete event on success or failure. Possible errors: :Not Connected: :Invalid Parameters: :Not Powered: :Invalid Index: User Confirmation Negative Reply ```````````````````````````````` :Command Code: 0x001D :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is used to return a negative response to a User Confirmation Request event. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random This command can only be used when the controller is powered. This command generates a Command Complete event on success or failure. Possible errors: :Not Connected: :Invalid Parameters: :Not Powered: :Invalid Index: User Passkey Reply `````````````````` :Command Code: 0x001E :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Passkey (4 Octets) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is used to respond to a User Confirmation Passkey Request event. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random This command can only be used when the controller is powered. This command generates a Command Complete event on success or failure. Possible errors: :Not Connected: :Invalid Parameters: :Not Powered: :Invalid Index: User Passkey Negative Reply ``````````````````````````` :Command Code: 0x001F :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is used to return a negative response to a User Passkey Request event. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random This command can only be used when the controller is powered. This command generates a Command Complete event on success or failure. Possible errors: :Not Connected: :Invalid Parameters: :Not Powered: :Invalid Index: Read Local Out Of Band Data ``````````````````````````` :Command Code: 0x0020 :Controller Index: :Command Parameters: :Return Parameters: Hash_192 (16 Octets) :...: Randomizer_192 (16 Octets) :...: Hash_256 (16 Octets, Optional) :...: Randomizer_256 (16 Octets, Optional) This command is used to read the local Out of Band data. This command can only be used when the controller is powered. If Secure Connections support is enabled, then this command will return P-192 versions of hash and randomizer as well as P-256 versions of both. Values returned by this command become invalid when the controller is powered down. After each power-cycle it is required to call this command again to get updated values. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Not Supported: :Busy: :Invalid Parameters: :Not Powered: :Invalid Index: Add Remote Out Of Band Data ``````````````````````````` :Command Code: 0x0021 :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Hash_192 (16 Octets) :...: Randomizer_192 (16 Octets) :...: Hash_256 (16 Octets, Optional) :...: Randomizer_256 (16 Octets, Optional) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is used to provide Out of Band data for a remote device. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random Provided Out Of Band data is persistent over power down/up toggles. This command also accept optional P-256 versions of hash and randomizer. If they are not provided, then they are set to zero value. The P-256 versions of both can also be provided when the support for Secure Connections is not enabled. However in that case they will never be used. To only provide the P-256 versions of hash and randomizer, it is valid to leave both P-192 fields as zero values. If Secure Connections is disabled, then of course this is the same as not providing any data at all. When providing data for remote LE devices, then the Hash_192 and Randomizer_192 fields are not used and shell be set to zero. The Hash_256 and Randomizer_256 fields can be used for LE secure connections Out Of Band data. If only LE secure connections data is provided the Hash_P192 and Randomizer_P192 fields can be set to zero. Currently there is no support for providing the Security Manager TK Value for LE legacy pairing. If Secure Connections Only mode has been enabled, then providing Hash_P192 and Randomizer_P192 is not allowed. They are required to be set to zero values. This command can be used when the controller is not powered and all settings will be programmed once powered. This command generates a Command Complete event on success or failure. Possible errors: :Failed: :Invalid Parameters: :Not Powered: :Invalid Index: Remove Remote Out Of Band Data `````````````````````````````` :Command Code: 0x0022 :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is used to remove data added using the Add Remote Out Of Band Data command. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random When the Address parameter is 00:00:00:00:00:00, then all previously added data will be removed. This command can be used when the controller is not powered and all settings will be programmed once powered. This command generates a Command Complete event on success or failure. Possible errors: :Invalid Parameters: :Not Powered: :Invalid Index: Start Discovery ``````````````` :Command Code: 0x0023 :Controller Index: :Command Parameters: Address_Type (1 Octet) :Return Parameters: Address_Type (1 Octet) This command is used to start the process of discovering remote devices. A Device Found event will be sent for each discovered device. Possible values for the Address_Type parameter are a bit-wise or of the following bits: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, BR/EDR 1, LE Public 2, LE Random By combining these e.g. the following values are possible: .. csv-table:: :header: "Value", "Description" :widths: auto 0x01, BR/EDR 0x06, LE (public & random) 0x07, BR/EDR/LE (interleaved discovery) This command can only be used when the controller is powered. This command generates a Command Complete event on success or failure. Possible errors: :Busy: :Not Supported: :Invalid Parameters: :Not Powered: :Invalid Index: Stop Discovery `````````````` :Command Code: 0x0024 :Controller Index: :Command Parameters: Address_Type (1 Octet) :Return Parameters: Address_Type (1 Octet) This command is used to stop the discovery process started using the Start Discovery command. This command can only be used when the controller is powered. This command generates a Command Complete event on success or failure. Possible errors: :Rejected: :Invalid Parameters: :Invalid Index: Confirm Name ```````````` :Command Code: 0x0025 :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Name_Known (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is only valid during device discovery and is expected for each Device Found event with the Confirm Name flag set. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random The Name_Known parameter should be set to 0x01 if user space knows the name for the device and 0x00 if it doesn't. If set to 0x00 the kernel will perform a name resolving procedure for the device in question. This command can only be used when the controller is powered. This command generates a Command Complete event on success or failure. Possible errors: :Failed: :Invalid Parameters: :Invalid Index: Block Device ```````````` :Command Code: 0x0026 :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is used to add a device to the list of devices which should be blocked from being connected to the local controller. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random For Low Energy devices, the blocking of a device takes precedence over auto-connection actions provided by Add Device. Blocked devices will not be auto-connected or even reported when found during background scanning. If the controller is connectable direct advertising from blocked devices will also be ignored. Connections created from advertising of the controller will be dropped if the device is blocked. This command can be used when the controller is not powered. This command generates a Command Complete event on success or failure. Possible errors: :Failed: :Invalid Parameters: :Invalid Index: Unblock Device `````````````` :Command Code: 0x0027 :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is used to remove a device from the list of blocked devices (where it was added to using the Block Device command). Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random When the Address parameter is 00:00:00:00:00:00, then all previously blocked devices will be unblocked. This command can be used when the controller is not powered. This command generates a Command Complete event on success or failure. Possible errors: :Invalid Parameters: :Invalid Index: Set Device ID ````````````` :Command Code: 0x0028 :Controller Index: :Command Parameters: Source (2 Octets) :...: Vendor (2 Octets) :...: Product (2 Octets) :...: Version (2 Octets) :Return Parameters: This command can be used when the controller is not powered and all settings will be programmed once powered. The Source parameter selects the organization that assigned the Vendor parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x0000, Disable Device ID 0x0001, Bluetooth SIG 0x0002, USB Implementer's Forum The information is put into the EIR data. If the controller does not support EIR or if SSP is disabled, this command will still succeed. The information is stored for later use and will survive toggling SSP on and off. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Set Advertising (since 1.4) ``````````````````````````` :Command Code: 0x0029 :Controller Index: :Command Parameters: Advertising (1 Octet) :Return Parameters: Current_Settings (4 Octets) This command is used to enable LE advertising on a controller that supports it. The allowed values for the Advertising command parameter are: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Disable advertising 0x01, Enables advertising 0x02 (since 1.9), Enabled advertising in connectable mode Using value 0x01 means that when connectable setting is disabled, the advertising happens with undirected non-connectable advertising packets and a non-resolvable random address is used. If connectable setting is enabled, then undirected connectable advertising packets and the identity address or resolvable private address are used. LE Devices configured via Add Device command with Action 0x01 have no effect when using Advertising value 0x01 since only the connectable setting is taken into account. To utilize undirected connectable advertising without changing the connectable setting, the value 0x02 can be utilized. It makes the device connectable via LE without the requirement for being connectable on BR/EDR (and/or LE). The value 0x02 should be the preferred mode of operation when implementing peripheral mode. Using this command will temporarily deactivate any configuration made by the Add Advertising command. This command takes precedence. Once a Set Advertising command with value 0x00 is issued any previously made configurations via Add/Remove Advertising, including such changes made while Set Advertising was active, will be re-enabled. A pre-requisite is that LE is already enabled, otherwise this command will return a "rejected" response. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Busy: :Rejected: :Not Supported: :Invalid Parameters: :Invalid Index: Set BR/EDR (since 1.4) `````````````````````` :Command Code: 0x002A :Controller Index: :Command Parameters: BR/EDR (1 Octet) :Return Parameters: Current_Settings (4 Octets) This command is used to enable or disable BR/EDR support on a dual-mode controller. The allowed values for the Advertising command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. A pre-requisite is that LE is already enabled, otherwise this command will return a "rejected" response. Enabling BR/EDR can be done both when powered on and powered off, however disabling it can only be done when powered off (otherwise the command will again return "rejected"). Disabling BR/EDR will automatically disable all other BR/EDR related settings. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Busy: :Rejected: :Not Supported: :Invalid Parameters: :Invalid Index: Set Static Address (since 1.4) `````````````````````````````` :Command Code: 0x002B :Controller Index: :Command Parameters: Address (6 Octets) :Return Parameters: Current_Settings (4 Octets) This command allows for setting the static random address. It is only supported on controllers with LE support. The static random address is suppose to be valid for the lifetime of the controller or at least until the next power cycle. To ensure such behavior, setting of the address is limited to when the controller is powered off. The special BDADDR_ANY address (00:00:00:00:00:00) can be used to disable the static address. When a controller has a public address (which is required for all dual-mode controllers), this address is not used. If a dual-mode controller is configured as Low Energy only devices (BR/EDR has been switched off), then the static address is used. Only when the controller information reports BDADDR_ANY (00:00:00:00:00:00), it is required to configure a static address first. If privacy mode is enabled and the controller is single mode LE only without a public address, the static random address is used as identity address. The Static Address flag from the current settings can also be used to determine if the configured static address is in use or not. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Rejected: :Not Supported: :Invalid Parameters: :Invalid Index: Set Scan Parameters (since 1.4) ``````````````````````````````` :Command Code: 0x002C :Controller Index: :Command Parameters: Interval (2 Octets) Window (2 Octets) :Return Parameters: This command allows for setting the Low Energy scan parameters used for connection establishment and passive scanning. It is only supported on controllers with LE support. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Rejected: :Not Supported: :Invalid Parameters: :Invalid Index: Set Secure Connections (since 1.5) `````````````````````````````````` :Command Code: 0x002D :Controller Index: :Command Parameters: Secure_Connections (1 Octet) :Return Parameters: Current_Settings (4 Octets) This command is used to enable/disable Secure Connections support for a controller. The allowed values for the Secure_Connections command parameter are 0x00, 0x01 and 0x02. All other values will return Invalid Parameters. The value 0x00 disables Secure Connections, the value 0x01 enables Secure Connections and the value 0x02 enables Secure Connections Only mode. This command is only available for LE capable controllers as well as controllers supporting the core specification version 4.1 or greater. This command can be used when the controller is not powered and all settings will be programmed once powered. In case the controller does not support Secure Connections the command will fail regardless with Not Supported error. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Busy: :Not Supported: :Invalid Parameters: :Invalid Index: Set Debug Keys (since 1.5) `````````````````````````` :Command Code: 0x002E :Controller Index: :Command Parameters: Debug_Keys (1 Octet) :Return Parameters: Current_Settings (4 Octets) This command is used to tell the kernel whether to accept the usage of debug keys or not. The allowed values for this parameter are: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Discard keys on disconnect 0x01, Discard keys on reboot 0x02 (since 1.7), Discard keys on reboot (SSP debug mode) With a value of 0x00 any generated debug key will be discarded as soon as the connection terminates. With a value of 0x01 generated debug keys will be kept and can be used for future connections. However debug keys are always marked as non persistent and should not be stored. This means a reboot or changing the value back to 0x00 will delete them. With a value of 0x02 generated debug keys will be kept and can be used for future connections. This has the same affect as with value 0x01. However in addition this value will also enter the controller mode to generate debug keys for each new pairing. Changing the value back to 0x01 or 0x00 will disable the controller mode for generating debug keys. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Busy: :Not Supported: :Invalid Parameters: :Invalid Index: Set Privacy (since 1.5) ``````````````````````` :Command Code: 0x002F :Controller Index: :Command Parameters: Privacy (1 Octet) :...: Identity_Resolving_Key (16 Octets) :Return Parameters: Current_Settings (4 Octets) This command is used to enable Low Energy Privacy feature using resolvable private addresses. The value 0x00 disables privacy mode, the values 0x01 and 0x02 enable privacy mode. With value 0x01 the kernel will always use the privacy mode. This means resolvable private address is used when the controller is discoverable and also when pairing is initiated. With value 0x02 the kernel will use a limited privacy mode with a resolvable private address except when the controller is bondable and discoverable, in which case the identity address is used. Exposing the identity address when bondable and discoverable or during initiated pairing can be a privacy issue. For dual-mode controllers this can be neglected since its public address will be exposed over BR/EDR anyway. The benefit of exposing the identity address for pairing purposes is that it makes matching up devices with dual-mode topology during device discovery now possible. If the privacy value 0x02 is used, then also the GATT database should expose the Privacy Characteristic so that remote devices can determine if the privacy feature is in use or not. When the controller has a public address (mandatory for dual-mode controllers) it is used as identity address. In case the controller is single mode LE only without a public address, it is required to configure a static random address first. The privacy mode can only be enabled when an identity address is available. The Identity_Resolving_Key is the local key assigned for the local resolvable private address. Possible errors: :Busy: :Not Supported: :Invalid Parameters: :Invalid Index: Load Identity Resolving Keys (since 1.5) ```````````````````````````````````````` :Command Code: 0x0030 :Controller Index: :Command Parameters: Key_Count (2 Octets) :...: Address[] (6 Octets) :...: Address_Type[] (1 Octet) :...: Value[] (16 Octets) :...: ...[] :Return Parameters: This command is used to feed the kernel with currently known identity resolving keys. The command does not need to be called again upon the receipt of New Identity Resolving Key events since the kernel updates its list automatically. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Reserved (not in use) 0x01, LE Public 0x02, LE Random The provided Address and Address_Type are the identity of a device. So either its public address or static random address. Unresolvable random addresses and resolvable random addresses are not valid and will be rejected. This command can be used when the controller is not powered. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Get Connection Information (since 1.6) `````````````````````````````````````` :Command Code: 0x0031 :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: RSSI (1 Octet) :...: TX_Power (1 Octet) :...: Max_TX_Power (1 Octet) This command is used to get connection information. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Reserved (not in use) 0x01, LE Public 0x02, LE Random TX_Power and Max_TX_Power can be set to 127 if values are invalid or unknown. A value of 127 for RSSI indicates that it is not available. This command generates a Command Complete event on success and on failure. In case of failure only Address and Address_Type fields are valid and values of remaining parameters are considered invalid and shall be ignored. Possible errors: :Not Connected: :Not Powered: :Invalid Parameters: :Invalid Index: Get Clock Information (since 1.7) ````````````````````````````````` :Command Code: 0x0032 :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Local_Clock (4 Octets) :...: Piconet_Clock (4 Octets) :...: Accuracy (2 Octets) This command is used to get local and piconet clock information. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, Reserved (not in use) 0x02, Reserved (not in use) The Accuracy can be set to 0xffff which means the value is unknown. If the Address is set to 00:00:00:00:00:00, then only the Local_Clock field has a valid value. The Piconet_Clock and Accuracy fields are invalid and shall be ignored. This command generates a Command Complete event on success and on failure. In case of failure only Address and Address_Type fields are valid and values of remaining parameters are considered invalid and shall be ignored. Possible errors: :Not Connected: :Not Powered: :Invalid Parameters: :Invalid Index: Add Device (since 1.7) `````````````````````` :Command Code: 0x0033 :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Action (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is used to add a device to the action list. The action list allows scanning for devices and enables incoming connections from known devices. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random Possible values for the Action parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Background scan for device 0x01, Allow incoming connection 0x02, Auto-connect remote device With the Action 0x00, when the device is found, a new Device Found event will be sent indicating this device is available. This action is only valid for LE Public and LE Random address types. With the Action 0x01, the device is allowed to connect. For BR/EDR address type this means an incoming connection. For LE Public and LE Random address types, a connection will be established to devices using directed advertising. If successful a Device Connected event will be sent. With the Action 2, when the device is found, it will be connected and if successful a Device Connected event will be sent. This action is only valid for LE Public and LE Random address types. When a device is blocked using Block Device command, then it is valid to add the device here, but all actions will be ignored until the device is unblocked. Devices added with Action 1 are allowed to connect even if the connectable setting is off. This acts as list of known trusted devices. This command can be used when the controller is not powered and all settings will be programmed once powered. This command generates a Command Complete event on success or failure. Possible errors: :Failed: :Invalid Parameters: :Invalid Index: Remove Device (since 1.7) ````````````````````````` :Command Code: 0x0034 :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is used to remove a device from the action list previously added by using the Add Device command. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random When the Address parameter is 00:00:00:00:00:00, then all previously added devices will be removed. This command can be used when the controller is not powered and all settings will be programmed once powered. This command generates a Command Complete event on success or failure. Possible errors: :Invalid Parameters: :Invalid Index: Load Connection Parameters (since 1.7) `````````````````````````````````````` :Command Code: 0x0035 :Controller Index: :Command Parameters: Param_Count (2 Octets) :...: Address[] (6 Octets) :...: Address_Type[] (1 Octet) :...: Min_Connection_Interval[] (2 Octets) :...: Max_Connection_Interval[] (2 Octets) :...: Connection_Latency[] (2 Octets) :...: Supervision_Timeout[] (2 Octets) :...: ...[] :Return Parameters: This command is used to load connection parameters from several devices into kernel. Currently this is only supported on controllers with Low Energy support. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random The provided Address and Address_Type are the identity of a device. So either its public address or static random address. The Min_Connection_Interval, Max_Connection_Interval, Connection_Latency and Supervision_Timeout parameters should be configured as described in Core 4.1 spec, Vol 2, 7.8.12. This command can be used when the controller is not powered. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: :Not Supported: Read Unconfigured Controller Index List (since 1.7) ``````````````````````````````````````````````````` :Command Code: 0x0036 :Controller Index: :Command Parameters: :Return Parameters: Num_Controllers (2 Octets) :...: Controller_Index[i] (2 Octets) This command returns the list of currently unconfigured controllers. Unconfigured controllers added after calling this command can be monitored using the Unconfigured Index Added event. An unconfigured controller can either move to a configured state by indicating Unconfigured Index Removed event followed by an Index Added event; or it can be removed from the system which would be indicated by the Unconfigured Index Removed event. Only controllers that require configuration will be listed with this command. A controller that is fully configured will not be listed even if it supports configuration changes. This command generates a Command Complete event on success or a Command Status event on failure. Read Controller Configuration Information (since 1.7) ````````````````````````````````````````````````````` :Command Code: 0x0037 :Controller Index: :Command Parameters: :Return Parameters: Manufacturer (2 Octets) :...: Supported_Options (4 Octets) :...: Missing_Options (4 Octets) This command is used to retrieve the supported configuration options of a controller and the missing configuration options. The missing options are required to be configured before the controller is considered fully configured and ready for standard operation. The command is typically used right after getting the response to Read Unconfigured Controller Index List command or Unconfigured Index Added event. Supported_Options and Missing_Options is a bitmask with currently the following available bits: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, External configuration 1, Bluetooth public address configuration It is valid to call this command on controllers that do not require any configuration. It is possible that a fully configured controller offers additional support for configuration. For example a controller may contain a valid Bluetooth public device address, but also allows to configure it from the host stack. In this case the general support for configurations will be indicated by the Controller Configuration settings. For controllers where no configuration options are available that setting option will not be present. When all configurations have been completed and as a result the Missing_Options mask would become empty, then the now ready controller will be announced via Index Added event. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Set External Configuration (since 1.7) `````````````````````````````````````` :Command Code: 0x0038 :Controller Index: :Command Parameters: Configuration (1 Octet) :Return Parameters: Missing_Options (4 Octets) This command allows to change external configuration option to indicate that a controller is now configured or unconfigured. The value 0x00 sets unconfigured state and the value 0x01 sets configured state of the controller. It is not mandatory that this configuration option is provided by a controller. If it is provided, the configuration has to happen externally using user channel operation or via vendor specific methods. Setting this option and when Missing_Options returns zero, this means that the controller will switch to configured state and it can be expected that it will be announced via Index Added event. Wrongly configured controllers might still cause an error when trying to power them via Set Powered command. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Rejected: :Not Supported: :Invalid Parameters: :Invalid Index: Set Public Address (since 1.7) `````````````````````````````` :Command Code: 0x0039 :Controller Index: :Command Parameters: Address (6 Octets) :Return Parameters: Missing_Options (4 Octets) This command allows configuration of public address. Since a vendor specific procedure is required, this command might not be supported by all controllers. Actually most likely only a handful embedded controllers will offer support for this command. When the support for Bluetooth public address configuration is indicated in the supported options mask, then this command can be used to configure the public address. It is only possible to configure the public address when the controller is powered off. For an unconfigured controller and when Missing_Options returns an empty mask, this means that a Index Added event for the now fully configured controller can be expected. For a fully configured controller, the current controller index will become invalid and an Unconfigured Index Removed event will be sent. Once the address has been successfully changed an Index Added event will be sent. There is no guarantee that the controller index stays the same. All previous configured parameters and settings are lost when this command succeeds. The controller has to be treated as new one. Use this command for a fully configured controller only when you really know what you are doing. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Rejected: :Not Supported: :Invalid Parameters: :Invalid Index: Start Service Discovery (since 1.8) ``````````````````````````````````` :Command Code: 0x003a :Controller Index: :Command Parameters: Address_Type (1 Octet) :...: RSSI_Threshold (1 Octet) :...: UUID_Count (2 Octets) :...: UUID[i] (16 Octets) :Return Parameters: Address_Type (1 Octet) This command is used to start the process of discovering remote devices with a specific UUID. A Device Found event will be sent for each discovered device. Possible values for the Address_Type parameter are a bit-wise or of the following bits: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, BR/EDR 1, LE Public 2, LE Random By combining these e.g. the following values are possible: .. csv-table:: :header: "Value", "Description" :widths: auto 0x01, BR/EDR 0x06, LE (public & random) 0x07, BR/EDR/LE (interleaved discovery) The service discovery uses active scanning for Low Energy scanning and will search for UUID in both advertising data and scan response data. Found devices that have a RSSI value smaller than RSSI_Threshold are not reported via DeviceFound event. Setting a value of 127 will cause all devices to be reported. The list of UUIDs identifies a logical OR. Only one of the UUIDs have to match to cause a DeviceFound event. Providing an empty list of UUIDs with Num_UUID set to 0 which means that DeviceFound events are send out for all devices above the RSSI_Threshold. In case RSSI_Threshold is set to 127 and UUID_Count is 0, then this command behaves exactly the same as Start Discovery. When the discovery procedure starts the Discovery event will notify this similar to Start Discovery. This command can only be used when the controller is powered. This command generates a Command Complete event on success or failure. Possible errors: :Busy: :Not Supported: :Invalid Parameters: :Not Powered: :Invalid Index: Read Local Out Of Band Extended Data (since 1.9) ```````````````````````````````````````````````` :Command Code: 0x003b :Controller Index: :Command Parameters: Address_Type (1 Octet) :Return Parameters: Address_Type (1 Octet) :...: EIR_Data_Length (2 Octets) :...: EIR_Data (0-65535 Octets) This command is used to read the local Out of Band data information and provide them encoded as extended inquiry response information or advertising data. Possible values for the Address_Type parameter are a bit-wise or of the following bits: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, BR/EDR 1, LE Public 2, LE Random By combining these e.g. the following values are possible: .. csv-table:: :header: "Value", "Description" :widths: auto 0x01, BR/EDR 0x06, LE (public & random) 0x07, Reserved (not in use) For BR/EDR controller (Address_Type 1) the returned information will contain the following information: Class of Device Simple Pairing Hash C-192 (optional) Simple Pairing Randomizer R-192 (optional) Simple Pairing Hash C-256 (optional) Simple Pairing Randomizer R-256 (optional) Service Class UUID (optional) Bluetooth Local Name (optional) The Simple Pairing Hash C-256 and Simple Pairing Randomizer R-256 fields are only included when secure connections has been enabled. The Device Address (BD_ADDR) is not included in the EIR_Data and needs to be taken from controller information. For LE controller (Address_Type 6) the returned information will contain the following information: LE Bluetooth Device Address LE Role LE Secure Connections Confirmation Value (optional) LE Secure Connections Random Value (optional) Appearance (optional) Local Name (optional) Flags The LE Secure Connections Confirmation Value and LE Secure Connections Random Value fields are only included when secure connections has been enabled. The Security Manager TK Value from the Bluetooth specification can not be provided by this command. The Out Of Band information here are for asymmetric exchanges based on Diffie-Hellman key exchange. The Security Manager TK Value is a symmetric random number that has to be acquired and agreed upon differently. The returned information from BR/EDR controller and LE controller types are not related to each other. Once they have been used over an Out Of Band link, a new set of information shall be requested. When Secure Connections Only mode has been enabled, then the fields for Simple Pairing Hash C-192 and Simple Pairing Randomizer R-192 are not returned. Only the fields for the strong secure connections pairing are included. This command can only be used when the controller is powered. Values returned by this command become invalid when the controller is powered down. After each power-cycle it is required to call this command again to get updated information. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Not Supported: :Busy: :Invalid Parameters: :Not Powered: :Invalid Index: Read Extended Controller Index List (since 1.9) ``````````````````````````````````````````````` :Command Code: 0x003c :Controller Index: :Command Parameters: :Return Parameters: Num_Controllers (2 Octets) :...: Controller_Index[i] (2 Octets) :...: Controller_Type[i] (1 Octet) :...: Controller_Bus[i] (1 Octet) This command returns the list of currently known controllers. It includes configured, unconfigured and alternate controllers. Controllers added or removed after calling this command can be monitored using the Extended Index Added and Extended Index Removed events. The existing Index Added, Index Removed, Unconfigured Index Added and Unconfigured Index Removed are no longer sent after this command has been used at least once. Instead of calling Read Controller Index List and Read Unconfigured Controller Index List, this command combines all the information and can be used to retrieve the controller list. The Controller_Type parameter has these values: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Primary Controller (BR/EDR and/or LE) 0x01, Unconfigured Controller (BR/EDR and/or LE) 0x02, Alternate MAC/PHY Controller (AMP) The 0x00 and 0x01 types indicate a primary BR/EDR and/or LE controller. The difference is just if they need extra configuration or if they are fully configured. Controllers in configured state will be listed as 0x00 and controllers in unconfigured state will be listed as 0x01. A controller that is fully configured and supports configuration changes will be listed as 0x00. Alternate MAC/PHY controllers will be listed as 0x02. They do not support the difference between configured and unconfigured state. The Controller_Bus parameter has these values: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Virtual 0x01, USB 0x02, PCMCIA 0x03, UART 0x04, RS232 0x05, PCI 0x06, SDIO 0x07, SPI 0x08, I2C 0x09, SMD 0x0A, VIRTIO 0x0B, IPC Controllers marked as RAW only operation are currently not listed by this command. This command generates a Command Complete event on success or a Command Status event on failure. Read Advertising Features (since 1.9) ````````````````````````````````````` :Command Code: 0x003d :Controller Index: :Command Parameters: :Return Parameters: Supported_Flags (4 Octets) :...: Max_Adv_Data_Len (1 Octet) :...: Max_Scan_Rsp_Len (1 Octet) :...: Max_Instances (1 Octet) :...: Num_Instances (1 Octet) :...: Instance[] (1 Octet) :...: ...[] This command is used to read the advertising features supported by the controller and stack. With the Supported_Flags field the possible values for the Flags field in Add Advertising command provided: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, Switch into Connectable mode 1, Advertise as Discoverable 2, Advertise as Limited Discoverable 3, Add Flags field to Adv_Data 4, Add TX Power field to Adv_Data 5 (since 1.14), Add Appearance field to Scan_Rsp 6 (since 1.14), Add Local Name in Scan_Rsp 7 (since 1.15), Secondary Channel with LE 1M 8 (since 1.15), Secondary Channel with LE 2M 9 (since 1.15), Secondary Channel with LE Coded The Flags bit 0 indicates support for connectable advertising and for switching to connectable advertising independent of the connectable global setting. When this flag is not supported, then the global connectable setting determines if undirected connectable, undirected scannable or undirected non-connectable advertising is used. It also determines the use of non-resolvable random address versus identity address or resolvable private address. The Flags bit 1 indicates support for advertising with discoverable mode enabled. Users of this flag will decrease the Max_Adv_Data_Len by 3 octets. In this case the advertising data flags are managed and added in front of the provided advertising data. The Flags bit 2 indicates support for advertising with limited discoverable mode enabled. Users of this flag will decrease the Max_Adv_Data_Len by 3 octets. In this case the advertising data flags are managed and added in front of the provided advertising data. The Flags bit 3 indicates support for automatically keeping the Flags field of the advertising data updated. Users of this flag will decrease the Max_Adv_Data_Len by 3 octets and need to keep that in mind. The Flags field will be added in front of the advertising data provided by the user. Note that with Flags bit 1 and Flags bit 2, this one will be implicitly used even if it is not marked as supported. The Flags bit 4 indicates support for automatically adding the TX Power value to the advertising data. Users of this flag will decrease the Max_Adv_Data_Len by 3 octets. The TX Power field will be added at the end of the user provided advertising data. If the controller does not support TX Power information, then this bit will not be set. The Flags bit 5 indicates support for automatically adding the Appearance value to the scan response data. Users of this flag will decrease the Max_Scan_Rsp_len by 4 octets. The Appearance field will be added in front of the scan response data provided by the user. If the appearance value is not supported, then this bit will not be set. The Flags bit 6 indicates support for automatically adding the Local Name value to the scan response data. This flag indicates an opportunistic approach for the Local Name. If enough space in the scan response data is available, it will be added. If the space is limited a short version or no name information. The Local Name will be added at the end of the scan response data. The Flags bit 7 indicates support for advertising in secondary channel in LE 1M PHY. The Flags bit 8 indicates support for advertising in secondary channel in LE 2M PHY. Primary channel would be on 1M. The Flags bit 9 indicates support for advertising in secondary channel in LE CODED PHY. The valid range for Instance identifiers is 1-254. The value 0 is reserved for internal use and the value 255 is reserved for future extensions. However the Max_Instances value for indicating the number of supported Instances can be also 0 if the controller does not support any advertising. The Max_Adv_Data_Len and Max_Scan_Rsp_Len provides extra information about the maximum length of the data fields. For now this will always return the value 31. Different flags however might decrease the actual available length in these data fields. With Num_Instances and Instance array the currently occupied Instance identifiers can be retrieved. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Add Advertising (since 1.9) ``````````````````````````` :Command Code: 0x003e :Controller Index: :Command Parameters: Instance (1 Octet) :...: Flags (4 Octets) :...: Duration (2 Octets) :...: Timeout (2 Octets) :...: Adv_Data_Len (1 Octet) :...: Scan_Rsp_Len (1 Octet) :...: Adv_Data (0-255 Octets) :...: Scan_Rsp (0-255 Octets) :Return Parameters: Instance (1 Octet) This command is used to configure an advertising instance that can be used to switch a Bluetooth Low Energy controller into advertising mode. Added advertising information with this command will not be visible immediately if advertising is enabled via the Set Advertising command. The usage of the Set Advertising command takes precedence over this command. Instance information is stored and will be advertised once advertising via Set Advertising has been disabled. The Instance identifier is a value between 1 and the number of supported instances. The value 0 is reserved. With the Flags value the type of advertising is controlled and the following flags are defined: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, Switch into Connectable mode 1, Advertise as Discoverable 2, Advertise as Limited Discoverable 3, Add Flags field to Adv_Data 4, Add TX Power field to Adv_Data 5 (since 1.14), Add Appearance field to Scan_Rsp 6 (since 1.14), Add Local Name in Scan_Rsp 7 (since 1.15), Secondary Channel with LE 1M 8 (since 1.15), Secondary Channel with LE 2M 9 (since 1.15), Secondary Channel with LE Coded When the connectable flag is set, then the controller will use undirected connectable advertising. The value of the connectable setting can be overwritten this way. This is useful to switch a controller into connectable mode only for LE operation. This is similar to the mode 0x02 from the Set Advertising command. When the connectable flag is not set, then the controller will use advertising based on the connectable setting. When using non-connectable or scannable advertising, the controller will be programmed with a non-resolvable random address. When the system is connectable, then the identity address or resolvable private address will be used. Using the connectable flag is useful for peripheral mode support where BR/EDR (and/or LE) is controlled by Add Device. This allows making the peripheral connectable without having to interfere with the global connectable setting. If Scan_Rsp_Len is zero and connectable flag is not set and the global connectable setting is off, then non-connectable advertising is used. If Scan_Rsp_Len is larger than zero and connectable flag is not set and the global advertising is off, then scannable advertising is used. This small difference is supported to provide less air traffic for devices implementing broadcaster role. Secondary channel flags can be used to advertise in secondary channel with the corresponding PHYs. These flag bits are mutually exclusive and setting multiple will result in Invalid Parameter error. Choosing either LE 1M or LE 2M will result in using extended advertising on the primary channel with LE 1M and the respectively LE 1M or LE 2M on the secondary channel. Choosing LE Coded will result in using extended advertising on the primary and secondary channels with LE Coded. Choosing none of these flags will result in legacy advertising. The Duration parameter configures the length of an Instance. The value is in seconds. A value of 0 indicates a default value is chosen for the Duration. The default is 2 seconds. If only one advertising Instance has been added, then the Duration value will be ignored. It only applies for the case where multiple Instances are configured. In that case every Instance will be available for the Duration time and after that it switches to the next one. This is a simple round-robin based approach. The Timeout parameter configures the life-time of an Instance. In case the value 0 is used it indicates no expiration time. If a timeout value is provided, then the advertising Instance will be automatically removed when the timeout passes. The value for the timeout is in seconds. Powering down a controller will invalidate all advertising Instances and it is not possible to add a new Instance with a timeout when the controller is powered down. When a Timeout is provided, then the Duration subtracts from the actual Timeout value of that Instance. For example an Instance with Timeout of 5 and Duration of 2 will be scheduled exactly 3 times, twice with 2 seconds and once with one second. Other Instances have no influence on the Timeout. Re-adding an already existing instance (i.e. issuing the Add Advertising command with an Instance identifier of an existing instance) will update that instance's configuration. An instance being added or changed while another instance is being advertised will not be visible immediately but only when the new/changed instance is being scheduled by the round robin advertising algorithm. Changes to an instance that is currently being advertised will cancel that instance and switch to the next instance. The changes will be visible the next time the instance is scheduled for advertising. In case a single instance is active, this means that changes will be visible right away. A pre-requisite is that LE is already enabled, otherwise this command will return a "rejected" response. This command can be used when the controller is not powered and all settings will be programmed once powered. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Failed: :Rejected: :Not Supported: :Invalid Parameters: :Invalid Index: Remove Advertising (since 1.9) `````````````````````````````` :Command Code: 0x003f :Controller Index: :Command Parameters: Instance (1 Octet) :Return Parameters: Instance (1 Octet) This command is used to remove an advertising instance that can be used to switch a Bluetooth Low Energy controller into advertising mode. When the Instance parameter is zero, then all previously added advertising Instances will be removed. Removing advertising information with this command will not be visible as long as advertising is enabled via the Set Advertising command. The usage of the Set Advertising command takes precedence over this command. Changes to Instance information are stored and will be advertised once advertising via Set Advertising has been disabled. Removing an instance while it is being advertised will immediately cancel the instance, even when it has been advertised less then its configured Timeout or Duration. This command can be used when the controller is not powered and all settings will be programmed once powered. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Get Advertising Size Information (since 1.11) ````````````````````````````````````````````` :Command Code: 0x0040 :Controller Index: :Command Parameters: Instance (1 Octet) :...: Flags (4 Octets) :Return Parameters: Instance (1 Octet) :...: Flags (4 Octets) :...: Max_Adv_Data_Len (1 Octet) :...: Max_Scan_Rsp_Len (1 Octet) The Read Advertising Features command returns the overall maximum size of advertising data and scan response data fields. That size is valid when no Flags are used. However when certain Flags are used, then the size might decrease. This command can be used to request detailed information about the maximum available size. The following Flags values are defined: .. csv-table:: :header: "Value", "Description" :widths: auto 0, Switch into Connectable mode 1, Advertise as Discoverable 2, Advertise as Limited Discoverable 3, Add Flags field to Adv_Data 4, Add TX Power field to Adv_Data 5, Add Appearance field to Scan_Rsp 6, Add Local Name in Scan_Rsp To get accurate information about the available size, the same Flags values should be used with the Add Advertising command. The Max_Adv_Data_Len and Max_Scan_Rsp_Len fields provide information about the maximum length of the data fields for the given Flags values. When the Flags field is zero, then these fields would contain the same values as Read Advertising Features. Possible errors: :Invalid Parameters: :Invalid Index: Start Limited Discovery (since 1.11) ```````````````````````````````````` :Command Code: 0x0041 :Controller Index: :Command Parameters: Address_Type (1 Octet) :Return Parameters: Address_Type (1 Octet) This command is used to start the process of discovering remote devices using the limited discovery procedure. A Device Found event will be sent for each discovered device. Possible values for the Address_Type parameter are a bit-wise or of the following bits: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, BR/EDR 1, LE Public 2, LE Random By combining these e.g. the following values are possible: .. csv-table:: :header: "Value", "Description" :widths: auto 0x01, BR/EDR 0x06, LE (public & random) 0x07, BR/EDR/LE (interleaved discovery) The limited discovery uses active scanning for Low Energy scanning and will search for devices with the limited discoverability flag configured. On BR/EDR it uses LIAC and filters on the limited discoverability flag of the class of device. When the discovery procedure starts the Discovery event will notify this similar to Start Discovery. This command can only be used when the controller is powered. This command generates a Command Complete event on success or failure. Possible errors: :Busy: :Not Supported: :Invalid Parameters: :Not Powered: :Invalid Index: Read Extended Controller Information (since 1.14) ````````````````````````````````````````````````` :Command Code: 0x0042 :Controller Index: :Command Parameters: :Return Parameters: Address (6 Octets) :...: Bluetooth_Version (1 Octet) :...: Manufacturer (2 Octets) :...: Supported_Settings (4 Octets) :...: Current_Settings (4 Octets) :...: EIR_Data_Length (2 Octets) :...: EIR_Data (0-65535 Octets) This command is used to retrieve the current state and basic information of a controller. It is typically used right after getting the response to the Read Controller Index List command or an Index Added event (or its extended counterparts). The Address parameter describes the controllers public address and it can be expected that it is set. However in case of single mode Low Energy only controllers it can be 00:00:00:00:00:00. To power on the controller in this case, it is required to configure a static address using Set Static Address command first. If the public address is set, then it will be used as identity address for the controller. If no public address is available, then the configured static address will be used as identity address. In the case of a dual-mode controller with public address that is configured as Low Energy only device (BR/EDR switched off), the static address is used when set and public address otherwise. Current_Settings and Supported_Settings is a bitmask with currently the following available bits: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, Powered 1, Connectable 2, Fast Connectable 3, Discoverable 4, Bondable 5, Link Level Security (Sec. mode 3) 6, Secure Simple Pairing 7, Basic Rate/Enhanced Data Rate 8, High Speed 9, Low Energy 10, Advertising 11, Secure Connections 12, Debug Keys 13, Privacy 14, Controller Configuration 15, Static Address 16, PHY Configuration 17, Wideband Speech 18, Connected Isochronous Stream - Central 19, Connected Isochronous Stream - Peripheral The EIR_Data field contains information about class of device, local name and other values. Not all of them might be present. For example a Low Energy only device does not contain class of device information. When any of the values in the EIR_Data field changes, the event Extended Controller Information Changed will be used to inform clients about the updated information. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Set Appearance (since 1.14) ``````````````````````````` :Command Code: 0x0043 :Controller Index: :Command Parameters: Appearance (2 Octets) :Return Parameters: This command is used to set the appearance value of a controller. This command can be used when the controller is not powered and all settings will be programmed once powered. The value of appearance will be remembered when switching the controller off and back on again. So the appearance only have to be set once when a new controller is found and will stay until removed. This command generates a Command Complete event on success or a Command Status event on failure. This command is only available for LE capable controllers. It will return Not Supported otherwise. Possible errors: :Not Supported: :Invalid Parameters: :Invalid Index: Get PHY Configuration (since 1.15) `````````````````````````````````` :Command Code: 0x0044 :Controller Index: :Command Parameters: :Return Parameters: Supported_PHYs (4 Octet) :...: Configurable_PHYs (4 Octets) :...: Selected_PHYs (4 Octet) The PHYs parameters are a bitmask with currently the following available bits: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, BR 1M 1-Slot 1, BR 1M 3-Slot 2, BR 1M 5-Slot 3, EDR 2M 1-Slot 4, EDR 2M 3-Slot 5, EDR 2M 5-Slot 6, ERDR 3M 1-Slot 7, EDR 3M 3-Slot 8, EDR 3M 5-Slot 9, LE 1M TX 10, LE 1M RX 11, LE 2M TX 12, LE 2M RX 13, LE Coded TX 14, LE Coded RX If BR/EDR is supported, then BR 1M 1-Slot is supported by default and can also not be deselected. If LE is supported, then LE 1M TX and LE 1M RX are supported by default. Disabling BR/EDR completely or respectively LE has no impact on the PHY configuration. It is remembered over power cycles. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Set PHY Configuration (since 1.15) `````````````````````````````````` :Command Code: 0x0045 :Controller Index: :Command Parameters: Selected_PHYs (4 Octet) :Return Parameters: This command is used to set the default PHY to the controller. This will be stored and used for all the subsequent scanning and connection initiation. The list of supported PHYs can be retrieved via the Get PHY Configuration command. Selecting unsupported or deselecting default PHYs will result in an Invalid Parameter error. This can be called at any point to change the Selected PHYs. Refer Get PHY Configuration command for PHYs parameter. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Load Blocked Keys (since 1.15) `````````````````````````````` :Command Code: 0x0046 :Controller Index: :Command Parameters: Key_Count (2 Octets) :...: Key_Type[] (1 Octet) :...: Value[] (16 Octets) :...: ...[] :Return Parameters: This command is used to feed the kernel a list of keys that are known to be vulnerable. If the pairing procedure produces any of these keys, they will be silently dropped and any attempt to enable encryption rejected. Currently defined Key_Type values are: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Link Key (BR/EDR) 0x01, Long Term Key (LE) 0x02, Identity Resolving Key (LE) This command can be used when the controller is not powered. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Set Wideband Speech (since 1.16) ```````````````````````````````` :Command Code: 0x0047 :Controller Index: :Command Parameters: Wideband_Speech (1 Octet) :Return Parameters: Current_Settings (4 Octets) This command is used to enable/disable Wideband Speech support for a controller. The allowed values for the Wideband_Speech command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. The value 0x00 disables Wideband Speech, the value 0x01 enables Wideband Speech. This command is only available for BR/EDR capable controllers and require controller specific support. This command can be used when the controller is not powered and all settings will be programmed once powered. In case the controller does not support Wideband Speech the command will fail regardless with Not Supported error. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Busy: :Not Supported: :Invalid Parameters: :Invalid Index: Read Controller Capabilities (since 1.17) ````````````````````````````````````````` :Command Code: 0x0048 :Controller Index: :Command Parameters: :Return Parameters: Capabilities_Data_Length (2 Octets) :...: Capabilities_Data[] (0-65535 Octets) This command is used to retrieve the supported capabilities by the controller or the host stack. The Capabilities_Data_Length and Capabilities_Data parameters provide a list of security settings, features and information. It uses the same format as EIR_Data, but with the namespace defined here. .. csv-table:: :header: "Data Type", "Name" :widths: auto 0x01, Flags 0x02, Max Encryption Key Size (BR/EDR) 0x03, Max Encryption Key Size (LE) 0x04, Supported Tx Power (LE) Flags (data type 0x01) .. csv-table:: :header: "Bit", "Description" :widths: auto 0, Remote public key validation (BR/EDR) 1, Remote public key validation (LE) 2, Encryption key size enforcement (BR/EDR) 3, Encryption key size enforcement (LE) Max Encryption Key Size (data types 0x02 and 0x03) When the field is present, then it provides 1 Octet value indicating the max encryption key size. If the field is not present, then it is unknown what the max encryption key size of the controller or host is in use. Supported LE Tx Power (data type 0x04) When present, this 2-octet field provides the min and max LE Tx power supported by the controller, respectively, as reported by the LE Read Transmit Power HCI command. If this field is not available, it indicates that the LE Read Transmit Power HCI command was not available. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Read Experimental Features Information (since 1.17) ``````````````````````````````````````````````````` :Command Code: 0x0049 :Controller Index: or :Command Parameters: :Return Parameters: Feature_Count (2 Octets) :...: UUID[] (16 Octets) :...: Flags[] (4 Octets) :...: ...[] This command is used to retrieve the supported experimental features by the host stack. The UUID values are not defined here. They can change over time and are on purpose not stable. Features that mature will be removed at some point. The mapping of feature UUID to the actual functionality of a given feature is out of scope here. The following bits are defined for the Flags parameter: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, Feature active 1, Causes change in supported settings This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Set Experimental Feature (since 1.17) ````````````````````````````````````` :Command Code: 0x004a :Controller Index: or :Command Parameters: UUID (16 Octets) :...: Action (1 Octet) :Return Parameters: UUID (16 Octets) :...: Flags (4 Octets) This command is used to change the setting of an experimental feature of the host stack. The UUID value must be a supported value returned from the Read Experimental Features Information command. The Action parameter is UUID specific, but in most cases it will be just a simple on/off toggle with these values: .. csv-table:: :header: "Bit", "Description" :widths: auto 0x00, Disable feature 0x01, Enable feature It depends on the feature if the command can be used when the controller is powered up. See Flags parameter of Read Experimental Features Information command for details if the controller has to be powered down first. The following bits are defined for the Flags return parameter: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, Feature active 1, Supported settings changed When a feature causes the change of supported settings, then it is a good idea to re-read the controller information. When the UUID parameter is an empty UUID (16 x 0x00), then all experimental features will be deactivated. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Not Powered: :Invalid Index: Read Default System Configuration (since 1.18) `````````````````````````````````````````````` :Command Code: 0x004b :Controller Index: :Command Parameters: :Return Parameters: Parameter_Type[] (2 Octet) :...: Value_Length[] (1 Octet) :...: Value (0-255[] Octets) :...: ...[] This command is used to read a list of default controller parameters. Currently defined Parameter_Type values are: .. csv-table:: :header: "Value", "Description" :widths: auto 0x0000, BR/EDR Page Scan Type 0x0001, BR/EDR Page Scan Interval 0x0002, BR/EDR Page Scan Window 0x0003, BR/EDR Inquiry Scan Type 0x0004, BR/EDR Inquiry Scan Interval 0x0005, BR/EDR Inquiry Scan Window 0x0006, BR/EDR Link Supervision Timeout 0x0007, BR/EDR Page Timeout 0x0008, BR/EDR Min Sniff Interval 0x0009, BR/EDR Max Sniff Interval 0x000a, LE Advertisement Min Interval 0x000b, LE Advertisement Max Interval 0x000c, LE Multi Advertisement Rotation Interval 0x000d, LE Scanning Interval for auto connect 0x000e, LE Scanning Window for auto connect 0x000f, LE Scanning Interval for wake scenarios 0x0010, LE Scanning Window for wake scenarios 0x0011, LE Scanning Interval for discovery 0x0012, LE Scanning Window for discovery 0x0013, LE Scanning Interval for adv monitoring 0x0014, LE Scanning Window for adv monitoring 0x0015, LE Scanning Interval for connect 0x0016, LE Scanning Window for connect 0x0017, LE Min Connection Interval 0x0018, LE Max Connection Interval 0x0019, LE Connection Latency 0x001a, LE Connection Supervision Timeout 0x001b, LE Autoconnect Timeout 0x001d, LE Allow List Scanning Duration for adv monitoring 0x001e, LE No Filter Scanning Duration for adv monitoring 0x001f, LE Enable Interleave Scan for adv monitoring 0x0020, BR/EDR Connection Idle Timeout This command can be used at any time and will return a list of supported default parameters as well as their current value. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Set Default System Configuration (since 1.18) ````````````````````````````````````````````` :Command Code: 0x004c :Controller Index: :Command Parameters: Parameter_Type[] (2 Octet) :...: Value_Length[] (1 Octet) :...: Value[] (0-255 Octets) :...: ...[] :Return Parameters: This command is used to set a list of default controller parameters. See Read Default System Configuration command for list of supported Parameter_Type values. This command can be used when the controller is not powered and all supported parameters will be programmed once powered. When providing unsupported values or invalid values, no parameter value will be changed and all values discarded. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Rejected: :Not Supported: :Invalid Parameters: :Invalid Index: Read Default Runtime Configuration (since 1.18) ``````````````````````````````````````````````` :Command Code: 0x004d :Controller Index: :Command Parameters: :Return Parameters: Parameter_Type[] (2 Octet) :...: Value_Length[] (1 Octet) :...: Value[] (0-255 Octets) :...: ...[] This command is used to read a list of default runtime parameters. Currently no Parameter_Type values are defined and an empty list will be returned. This command can be used at any time and will return a list of supported default parameters as well as their current value. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Set Default Runtime Configuration (since 1.18) `````````````````````````````````````````````` :Command Code: 0x004e :Controller Index: :Command Parameters: Parameter_Type[] (2 Octet) :...: Value_Length[] (1 Octet) :...: Value[] (0-255 Octets) :...: ...[] :Return Parameters: This command is used to set a list of default runtime parameters. See Read Default Runtime Configuration command for list of supported Parameter_Type values. This command can be used at any time and will change the runtime default. Changes however will not apply to existing connections or currently active operations. When providing unsupported values or invalid values, no parameter value will be changed and all values discarded. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Rejected: :Not Supported: :Invalid Parameters: :Invalid Index: Get Device Flags (since 1.18) ````````````````````````````` :Command Code: 0x004f :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Supported_Flags (4 Octets) :...: Current_Flags (4 Octets) This command is used to retrieve additional flags and settings for devices that are added via Add Device command. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random The Flags parameters are a bitmask with currently the following available bits: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, Remote Wakeup enabled 1, Device Privacy Mode enabled 2, Address Resolution enabled 3, PAST enabled This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Set Device Flags (since 1.18) ````````````````````````````` :Command Code: 0x0050 :Controller Index: :Command Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Current_Flags (4 Octets) :Return Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This command is used to configure additional flags and settings for devices that are added via Add Device command. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random The list of supported Flags can be retrieved via the Get Device Flags or Device Flags Changed command. Selecting unsupported flags will result in an Invalid Parameter error; Refer to the Get Device Flags command for a detailed description of the Flags parameters. This command can be used when the controller is not powered and all settings will be programmed once powered. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Invalid Parameters: :Invalid Index: Read Advertisement Monitor Features (since 1.18) ```````````````````````````````````````````````` :Command Code: 0x0051 :Controller Index: :Command Parameters: :Return Parameters: Supported_Features (4 Octets) :...: Enabled_Features (4 Octets) :...: Max_Num_Handles (2 Octets) :...: Max_Num_Patterns (1 Octet) :...: Num_Handles (2 Octets) :...: Handle[] (2 Octets) :...: ...[] This command is used to read the advertisement monitor features supported by the controller and stack. Supported_Features lists all related features supported by the controller while Enabled_Features lists the ones currently used by the kernel. Supported_Features and Enabled_Features are bitmasks with currently the following available bits: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, Advertisement content monitoring based on patterns with logic OR. Max_Num_Handles indicates the maximum number of supported advertisement monitors. Note that the actual number of supported ones might be less depending on the limitation of the controller. Max_Num_Pattern indicates the maximum number of supported patterns in an advertisement patterns monitor. Note that the actual number of supported ones might be less depending on the limitation of the controller. Num_Handles indicates the number of added advertisement monitors, and it is followed by a list of handles. This command can be used when the controller is not powered. Add Advertisement Patterns Monitor (since 1.18) ``````````````````````````````````````````````` :Command Code: 0x0052 :Controller Index: :Command Parameters: Pattern_Count (1 Octet) :...: AD_Type[] (1 Octet) :...: Offset[] (1 Octet) :...: Length[] (1 Octet) :...: Value[] (31 Octets) :...: ...[] :Return Parameters: Monitor_Handle (2 Octets) This command is used to add an advertisement monitor whose filtering conditions are patterns. The kernel will trigger scanning if there is at least one monitor added. If the controller supports advertisement filtering, the kernel would offload the content filtering to the controller in order to reduce power consumption; otherwise the kernel ignores the content of the monitor. Note that if the there are more than one patterns, OR logic would applied among patterns during filtering. In other words, any advertisement matching at least one pattern in a given monitor would be considered as a match. A pattern contains the following fields: :AD_Data_Type: Advertising Data Type. The possible values are defined in Core :...: Specification Supplement. :Offset: The start index where pattern matching shall be performed with :...: in the AD data. :Length: The length of the pattern value in bytes. :Value: The value of the pattern in bytes. Here is an example of a pattern. .. code-block:: { 0x16, // Service Data - 16-bit UUID 0x02, // Skip the UUID part. 0x04, // Length of the value {0x11, 0x22, 0x33, 0x44}, } This command can be used when the controller is not powered and all settings will be programmed once powered. Possible errors: :Failed: :Busy: :No Resources: :Invalid Parameters: Remove Advertisement Monitor (since 1.18) ````````````````````````````````````````` :Command Code: 0x0053 :Controller Index: :Command Parameters: Monitor_Handle (2 Octets) :Return Parameters: Monitor_Handle (2 Octets) This command is used to remove advertisement monitor(s). The kernel would remove the monitor(s) with Monitor_Handle and update the LE scanning. When the Monitor_Handle is set to zero, then all previously added handles will be removed. Removing a monitor while it is being added will be ignored. This command can be used when the controller is not powered and all settings will be programmed once powered. Possible errors: :Failed: :Busy: Add Extended Advertising Parameters (since 1.19) ```````````````````````````````````````````````` :Command Code: 0x0054 :Controller Index: :Command Parameters: Instance (1 Octet) :...: Flags (4 Octets) :...: Duration (2 Octets) :...: Timeout (2 Octets) :...: MinInterval (4 Octets) :...: MaxInterval (4 Octets) :...: TxPower (1 Octet) :Return Parameters: Instance (1 Octet) :...: TxPower (1 Octet) :...: MaxAdvDataLen (1 Octet) :...: MaxScanRspLen (1 Octet) This command is used to configure the parameters for Bluetooth Low Energy advertising instance. This command is expected to be followed by an Add Extended Advertising Data command to complete and enable the advertising instance. Added advertising information with this command will not be visible immediately if advertising is enabled via the Set Advertising command. The usage of the Set Advertising command takes precedence over this command. Instance information is stored and will be advertised once advertising via Set Advertising has been disabled. The Instance identifier is a value between 1 and the number of supported instances. The value 0 is reserved. With the Flags value the type of advertising is controlled and the following flags are defined: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, Switch into Connectable mode 1, Advertise as Discoverable 2, Advertise as Limited Discoverable 3, Add Flags field to Adv_Data 4, Add TX Power field to Adv_Data 5, Add Appearance field to Scan_Rsp 6, Add Local Name in Scan_Rsp 7, Secondary Channel with LE 1M 8, Secondary Channel with LE 2M 9, Secondary Channel with LE Coded 10, Indicate tx power can be specified 11, Indicate HW supports the advertising offload 12, The Duration parameter should be used 13, The Timeout parameter should be used 14, The Interval parameters should be used 15, The Tx Power parameter should be used 16, The advertisement will contain a scan response When the connectable flag is set, then the controller will use undirected connectable advertising. The value of the connectable setting can be overwritten this way. This is useful to switch a controller into connectable mode only for LE operation. This is similar to the mode 0x02 from the Set Advertising command. When the connectable flag is not set, then the controller will use advertising based on the connectable setting. When using non-connectable or scannable advertising, the controller will be programmed with a non-resolvable random address. When the system is connectable, then the identity address or resolvable private address will be used. Using the connectable flag is useful for peripheral mode support where BR/EDR (and/or LE) is controlled by Add Device. This allows making the peripheral connectable without having to interfere with the global connectable setting. Secondary channel flags can be used to advertise in secondary channel with the corresponding PHYs. These flag bits are mutually exclusive and setting multiple will result in Invalid Parameter error. Choosing either LE 1M or LE 2M will result in using extended advertising on the primary channel with LE 1M and the respectively LE 1M or LE 2M on the secondary channel. Choosing LE Coded will result in using extended advertising on the primary and secondary channels with LE Coded. Choosing none of these flags will result in legacy advertising. To allow future parameters to be optionally extended in this structure, the flags member has been used to specify which of the structure fields were purposefully set by the caller. Unspecified parameters will be given sensible defaults by the kernel before the advertisement is registered. The Duration parameter configures the length of an Instance. The value is in seconds. The default is 2 seconds. If only one advertising Instance has been added, then the Duration value will be ignored. It only applies for the case where multiple Instances are configured. In that case every Instance will be available for the Duration time and after that it switches to the next one. This is a simple round-robin based approach. The Timeout parameter configures the life-time of an Instance. In case the value 0 is used it indicates no expiration time. If a timeout value is provided, then the advertising Instance will be automatically removed when the timeout passes. The value for the timeout is in seconds. Powering down a controller will invalidate all advertising Instances and it is not possible to add a new Instance with a timeout when the controller is powered down. When a Timeout is provided, then the Duration subtracts from the actual Timeout value of that Instance. For example an Instance with Timeout of 5 and Duration of 2 will be scheduled exactly 3 times, twice with 2 seconds and once with one second. Other Instances have no influence on the Timeout. MinInterval and MaxInterval define the minimum and maximum advertising intervals, with units as number of .625ms advertising slots. The Max interval is expected to be greater than or equal to the Min interval, and both must have values in the range [0x000020, 0xFFFFFF]. If either condition is not met, the registration will fail. The provided Tx Power parameter will only be used if the controller supports it, which can be determined by the presence of the CanSetTxPower member of the Read Advertising Features command. The acceptable range for requested Tx Power is defined in the spec (Version 5.2 | Vol 4, Part E, page 2585) to be [-127, +20] dBm, and the controller will select a power value up to the requested one. The transmission power selected by the controller is not guaranteed to match the requested one, so the reply will contain the power chosen by the controller. If the requested Tx Power is outside the valid range, the registration will fail. When flag bit 16 is enabled, it indicates that the subsequent request to set advertising data will contain a scan response, and that the parameters should set a PDU type that is scannable. Re-adding an already existing instance (i.e. issuing the Add Extended Advertising Parameters command with an Instance identifier of an existing instance) will update that instance's configuration. In this case where no new instance is added, no Advertising Added event will be generated. However, if the update of the instance fails, the instance will be removed, and an Advertising Removed event will be generated. An instance being added or changed while another instance is being advertised will not be visible immediately but only when the new/changed instance is being scheduled by the round robin advertising algorithm. Changes to an instance that is currently being advertised will cancel that instance and switch to the next instance. The changes will be visible the next time the instance is scheduled for advertising. In case a single instance is active, this means that changes will be visible right away. The MaxAdvDataLen return parameter indicates how large the data payload can be in the subsequent Add Extended Advertising Data Command, as it accounts for the data required for the selected flags. Similarly, the MaxScanRspLen return parameter indicates how large the scan response can be. LE must already be enabled, and the controller must be powered, otherwise a "rejected" status will be returned. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Failed: :Rejected: :Not Supported: :Invalid Parameters: :Busy: Add Extended Advertising Data (since 1.19) `````````````````````````````````````````` :Command Code: 0x0055 :Controller Index: :Command Parameters: Instance (1 Octet) :...: Advertising Data Length (1 Octet) :...: Scan Response Length (1 Octet) :...: Advertising Data (0-255 Octets) :...: Scan Response (0-255 Octets) :Return Parameters: Instance (1 Octet) The Add Extended Advertising Data command is used to update the advertising data of an existing advertising instance known to the kernel. It is expected to be called after an Add Extended Advertising Parameters command, as part of the advertisement registration process. If extended advertising is available, this call will initiate HCI commands to set the instance's advertising data, set scan response data, and then enable the instance. If extended advertising is unavailable, the advertising instance structure maintained in kernel will have its advertising data and scan response updated, and the instance will either be scheduled immediately or left in the queue for later advertisement as part of round-robin advertisement rotation in software. If Scan_Rsp_Len is zero and the flags defined in Add Extended Advertising Parameters command do not have connectable flag set and the global connectable setting is off, then non-connectable advertising is used. If Scan_Rsp_Len is larger than zero and connectable flag is not set and the global advertising is off, then scannable advertising is used. This small difference is supported to provide less air traffic for devices implementing broadcaster role. If the Instance provided does not match a known instance, or if the provided advertising data or scan response are in an unrecognized format, an "Invalid Parameters" status will be returned. If a "Set LE" or Advertising command is still in progress, a "Busy" status will be returned. If the controller is not powered, a "rejected" status will be returned. This command generates a Command Complete event on success or a Command Status event on failure. Possible errors: :Failed: :Rejected: :Invalid Parameters: :Busy: Add Advertisement Patterns Monitor With RSSI Threshold (since 1.20) ``````````````````````````````````````````````````````````````````` :Command Code: 0x0056 :Controller Index: :Command Parameters: High_Threshold (1 Octet) :...: High_Threshold_Timer (2 Octets) :...: Low_Threshold (1 Octet) :...: Low_Threshold_Timer (2 Octets) :...: Sampling_Period (1 Octet) :...: Pattern_Count (1 Octet) :...: AD_Type[] (1 Octet) :...: Offset[] (1 Octet) :...: Length[] (1 Octet) :...: Value[] (31 Octets) :...: ...[] :Return Parameters: Monitor_Handle (2 Octets) This command is essentially the same as Add Advertisement Patterns Monitor Command (0x0052), but with an additional RSSI parameters. As such, if the kernel supports advertisement filtering, then the advertisement data will be filtered in accordance with the set RSSI parameters. Otherwise, it would behave exactly the same as the Add Advertisement Patterns Monitor Command. Devices would be considered "in-range" if the RSSI of the received adv packets are greater than High_Threshold dBm for High_Threshold_Timer seconds. Similarly, devices would be considered lost if no received adv have RSSI greater than Low_Threshold dBm for Low_Threshold_Timer seconds. Only adv packets of "in-range" device would be propagated. The meaning of Sampling_Period is as follows: :0x00: All adv packets from "in-range" devices would be propagated. :0xFF: Only the first adv data of "in-range" devices would be :...: propagated. If the device becomes lost, then the first data when :...: it is found again will also be propagated. :other: Advertisement data would be grouped into 100ms * N time period. Data in the same group will only be reported once, with the RSSI value being averaged out. Possible errors: :Failed: :Busy: :No Resources: :Invalid Parameters: Set Mesh Receiver (since 1.21) `````````````````````````````` :Command Code: 0x0057 :Controller Index: :Command Parameters: Enable (1 Octets) :...: Window (2 Octets) :...: Period (2 Octets) :...: Num AD Types (1 Octets) :...: AD Types[] This command Enables or Disables Mesh Receiving. When enabled passive scanning remains enabled for this controller. The Window/Period values are used to set the Scan Parameters when no other scanning is being done. Num AD Types and AD Types parameter, filter Advertising and Scan responses by AD type. Responses that do not contain at least one of the requested AD types will be ignored. Otherwise they will be delivered with the Mesh Device Found event. Possible errors: :Failed: :No Resources: :Invalid Parameters: Read Mesh Features (since 1.21) ``````````````````````````````` :Command Code: 0x0058 :Controller Index: :Command Parameters: :Return Parameters: Index (2 Octets) :...: Max Handles (1 Octets) :...: Used Handles (1 Octets) :...: Handle[] This command is used to both verify that Outbound Mesh packet support is enabled, and to indicate the number of packets that can and are simultaneously queued. Index identifies the HCI Controller that this information is valid for. Max Handles indicates the maximum number of packets that may be queued. Used Handles indicates the number of packets awaiting transmission. Handle is an array of the currently outstanding packets. Possible errors: :Failed: :No Resources: :Invalid Parameters: Transmit Mesh Packet (since 1.21) ````````````````````````````````` :Command Code: 0x0059 :Controller Index: :Command Parameters: Addr (6 octets) :...: Addr Type (1 Octets) :...: Instant (8 Octets) :...: Delay (2 Octets) :...: Count (1 Octets) :...: Data Length (1 Octets) :...: Data[] (variable) :Return Parameters: Handle (1 Octets) This command sends a Mesh Packet as a NONCONN LE Advertisement. The Addr + Addr Type parameters specify the address to use in the outbound advertising packet. If BD_ADDR_ANY and LE_RANDOM is set, the kernel will create a single use non-resolvable address. The Instant parameter is used in combination with the Delay parameter, to finely time the sending of the Advertising packet. It should be set to the Instant value tag of a received incoming Mesh Device Found Event. It is only useful in POLL-RESPONSE situations where a response must be sent within a negotiated time window. The value of the Instant parameter should not be interpreted by the host, and only has meaning to the controller. The Delay parameter, if 0x0000, will cause the packet to be sent at the earliest opportunity. If non-Zero, and the controller supports delayed delivery, the Instant and Delay parameters will be used to delay the outbound packet. While the Instant is not defined, the Delay is specified in milliseconds. The Count parameter must be sent to a non-Zero value indicating the number of times this packet will be sent before transmission completes. If the Delay parameter is non-Zero, then Count must be 1 only. The Data parameter is an octet array of the AD Type and Mesh Packet. This command will return immediately, and if it succeeds, will generate a Mesh Packet Transmission Complete event when after the packet has been sent. Possible errors: :Failed: :Busy: :No Resources: :Invalid Parameters: Cancel Transmit Mesh Packet (since 1.21) ```````````````````````````````````````` :Command Code: 0x005A :Controller Index: :Command Parameters: Handle (1 Octets) This command may be used to cancel an outbound transmission request. The Handle parameter is the returned handle from a successful Transmit Mesh Packet request. If Zero is specified as the handle, all outstanding send requests are canceled. For each mesh packet canceled, the Mesh Packet Transmission Complete event will be generated, regardless of whether the packet was sent successfully. Possible errors: :Failed: :Invalid Parameters: Send HCI command and wait for event ``````````````````````````````````` :Command Code: 0x005B :Controller Index: :Command Parameters: Opcode (2 Octets) :...: Event (1 Octet) :...: Timeout (1 Octet) :...: Parameter Length (2 Octets) :...: Parameter[] (variable) :Return Parameters: Response (1-variable Octets) This command may be used to send a HCI command and wait for an (optional) event. The HCI command is specified by the Opcode, any arbitrary is supported including vendor commands, but contrary to the like of Raw/User channel it is run as an HCI command send by the kernel since it uses its command synchronization thus it is possible to wait for a specific event as a response. Setting event to 0x00 will cause the command to wait for either HCI Command Status or HCI Command Complete. Timeout is specified in seconds, setting it to 0 will cause the default timeout to be used. Possible errors: :Failed: :Invalid Parameters: Events ------ Command Complete ```````````````` :Event Code: 0x0001 :Controller Index: or :Event Parameters: Command_Opcode (2 Octets) :...: Status (1 Octet) :...: Return_Parameters[] (variable) This event is an indication that a command has completed. The fixed set of parameters includes the opcode to identify the command that completed as well as a status value to indicate success or failure. The rest of the parameters are command specific and documented in the section for each command separately. Command Status `````````````` :Event Code: 0x0002 :Controller Index: or :Event Parameters: Command_Opcode (2 Octets) :...: Status (1 Octet) The command status event is used to indicate an early status for a pending command. In the case that the status indicates failure (anything else except success status) this also means that the command has finished executing. Controller Error ```````````````` :Event Code: 0x0003 :Controller Index: :Event Parameters: Error_Code (1 Octet) This event maps straight to the HCI Hardware Error event and is used to indicate something wrong with the controller hardware. Index Added ``````````` :Event Code: 0x0004 :Controller Index: :Event Parameters: This event indicates that a new controller has been added to the system. It is usually followed by a Read Controller Information command. Once the Read Extended Controller Index List command has been used at least once, the Extended Index Added event will be send instead of this one. Index Removed ````````````` :Event Code: 0x0005 :Controller Index: :Event Parameters: This event indicates that a controller has been removed from the system. Once the Read Extended Controller Index List command has been used at least once, the Extended Index Removed event will be send instead of this one. New Settings ```````````` :Event Code: 0x0006 :Controller Index: :Event Parameters: Current_Settings (4 Octets) This event indicates that one or more of the settings for a controller has changed. Class Of Device Changed ``````````````````````` :Event Code: 0x0007 :Controller Index: :Event Parameters: Class_Of_Device (3 Octets) This event indicates that the Class of Device value for the controller has changed. When the controller is powered off the Class of Device value will always be reported as zero. Local Name Changed `````````````````` :Event Code: 0x0008 :Controller Index: :Event Parameters: Name (249 Octets) :...: Short_Name (11 Octets) This event indicates that the local name of the controller has changed. New Link Key ```````````` :Event Code: 0x0009 :Controller Index: :Event Parameters: Store_Hint (1 Octet) :...: Address (6 Octets) :...: Address_Type (1 Octet) :...: Key_Type (1 Octet) :...: Value (16 Octets) :...: PIN_Length (1 Octet) This event indicates that a new link key has been generated for a remote device. The Store_Hint parameter indicates whether the host is expected to store the key persistently or not (e.g. this would not be set if the authentication requirement was "No Bonding"). Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0, BR/EDR 1, Reserved (not in use) 2, Reserved (not in use) Public and random LE addresses are not valid and will be rejected. Currently defined Key_Type values are: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Combination key 0x01, Local Unit key 0x02, Remote Unit key 0x03, Debug Combination key 0x04, Unauthenticated Combination key from P-192 0x05, Authenticated Combination key from P-192 0x06, Changed Combination key 0x07, Unauthenticated Combination key from P-256 0x08, Authenticated Combination key from P-256 Receiving this event indicates that a pairing procedure has been completed. New Long Term Key ````````````````` :Event Code: 0x000A :Controller Index: :Event Parameters: Store_Hint (1 Octet) :...: Address (6 Octets) :...: Address_Type (1 Octet) :...: Key_Type (1 Octet) :...: Central (1 Octet) :...: Encryption Size (1 Octet) :...: Enc. Diversifier (2 Octets) :...: Random Number (8 Octets) :...: Value (16 Octets) This event indicates that a new long term key has been generated for a remote device. The Store_Hint parameter indicates whether the host is expected to store the key persistently or not (e.g. this would not be set if the authentication requirement was "No Bonding"). Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0, Reserved (not in use) 1, LE Public 2, LE Random The provided Address and Address_Type are the identity of a device. So either its public address or static random address. For unresolvable random addresses and resolvable random addresses without identity information and identity resolving key, the Store_Hint will be set to not store the long term key. Currently defined Key_Type values are: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Unauthenticated legacy key 0x01, Authenticated legacy key 0x02, Unauthenticated key from P-256 0x03, Authenticated key from P-256 0x04, Debug key from P-256 Receiving this event indicates that a pairing procedure has been completed. Device Connected ```````````````` :Event Code: 0x000B :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Flags (4 Octets) :...: EIR_Data_Length (2 Octets) :...: EIR_Data (0-65535 Octets) This event indicates that a successful baseband connection has been created to the remote device. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0, BR/EDR 1, LE Public 2, LE Random For devices using resolvable random addresses with a known identity resolving key, the Address and Address_Type will contain the identity information. It is possible that devices get connected via its resolvable random address and after New Identity Resolving Key event start using its identity. The following bits are defined for the Flags parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0, Reserved (not in use) 1, Legacy Pairing 2, Reserved (not in use) 3, Initiated Connection 4, Reserved (not in use) Device Disconnected ``````````````````` :Event Code: 0x000C :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Reason (1 Octet) This event indicates that the baseband connection was lost to a remote device. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random For devices using resolvable random addresses with a known identity resolving key, the Address and Address_Type will contain the identity information. Possible values for the Reason parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Unspecified 0x01, Connection timeout 0x02, Connection terminated by local host 0x03, Connection terminated by remote host 0x04, Connection terminated due to authentication failure 0x05, Connection terminated by local host for suspend Note that the local/remote distinction just determines which side terminated the low-level connection, regardless of the disconnection of the higher-level profiles. This can sometimes be misleading and thus must be used with care. For example, some hardware combinations would report a locally initiated disconnection even if the user turned Bluetooth off in the remote side. Connect Failed `````````````` :Event Code: 0x000D :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Status (1 Octet) This event indicates that a connection attempt failed to a remote device. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random For devices using resolvable random addresses with a known identity resolving key, the Address and Address_Type will contain the identity information. PIN Code Request ```````````````` :Event Code: 0x000E :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Secure (1 Octet) This event is used to request a PIN Code reply from user space. The reply should either be returned using the PIN Code Reply or the PIN Code Negative Reply command. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random :Secure: 0x01 secure PIN code required 0x00 secure PIN code not required User Confirmation Request ````````````````````````` :Event Code: 0x000F :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Confirm_Hint (1 Octet) :...: Value (4 Octets) This event is used to request a user confirmation request from user space. .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random If the Confirm_Hint parameter value is 0x01 this means that a simple "Yes/No" confirmation should be presented to the user instead of a full numerical confirmation (in which case the parameter value will be 0x00). User space should respond to this command either using the User Confirmation Reply or the User Confirmation Negative Reply command. User Passkey Request ```````````````````` :Event Code: 0x0010 :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This event is used to request a passkey from user space. The response to this event should either be the User Passkey Reply command or the User Passkey Negative Reply command. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random Authentication Failed ````````````````````` :Event Code: 0x0011 :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Status (1 Octet) This event indicates that there was an authentication failure with a remote device. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random Device Found ```````````` :Event Code: 0x0012 :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: RSSI (1 Octet) :...: Flags (4 Octets) :...: EIR_Data_Length (2 Octets) :...: EIR_Data (0-65535 Octets) This event indicates that a device was found during device discovery. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random The following bits are defined for the Flags parameter: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, Confirm name 1, Legacy Pairing 2, Not Connectable 3, Reserved (not in use) 4, Name Request Failed 5, Scan Response For the RSSI field a value of 127 indicates that the RSSI is not available. That can happen with Bluetooth 1.1 and earlier controllers or with bad radio conditions. The Confirm name flag indicates that the kernel wants to know whether user space knows the name for this device or not. If this flag is set user space should respond to it using the Confirm Name command. The Legacy Pairing flag indicates that Legacy Pairing is likely to occur when pairing with this device. An application could use this information to optimize the pairing process by locally pre-generating a PIN code and thereby eliminate the risk of local input timeout when pairing. Note that there is a risk of false-positives for this flag so user space should be able to handle getting something else as a PIN Request when pairing. The Not Connectable flag indicates that the device will not accept any connections. This can be indicated by Low Energy devices that are in broadcaster role. The Name Request Failed flag indicates that name resolving procedure has ended with failure for this device. The user space should use this information to determine when is a good time to retry the name resolving procedure. Discovering ``````````` :Event Code: 0x0013 :Controller Index: :Event Parameters: Address_Type (1 Octet) :...: Discovering (1 Octet) This event indicates that the controller has started discovering devices. This discovering state can come and go multiple times between a Start Discovery and a Stop Discovery commands. The Start Service Discovery command will also trigger this event. The valid values for the Discovering parameter are 0x01 (enabled) and 0x00 (disabled). Device Blocked `````````````` :Event Code: 0x0014 :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This event indicates that a device has been blocked using the Block Device command. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random The event will only be sent to Management sockets other than the one through which the command was sent. Device Unblocked ```````````````` :Event Code: 0x0015 :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This event indicates that a device has been unblocked using the Unblock Device command. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random The event will only be sent to Management sockets other than the one through which the command was sent. Device Unpaired ``````````````` :Event Code: 0x0016 :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This event indicates that a device has been unpaired (i.e. all its keys have been removed from the kernel) using the Unpair Device command. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random For devices using resolvable random addresses with a known identity resolving key, the event parameters will contain the identity. After receiving this event, the device will become essentially private again. The event will only be sent to Management sockets other than the one through which the Unpair Device command was sent. Passkey Notify `````````````` :Event Code: 0x0017 :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Passkey (4 Octets) :...: Entered (1 Octet) This event is used to request passkey notification to the user. Unlike the other authentication events it does not need responding to using any Management command. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random The Passkey parameter indicates the passkey to be shown to the user whereas the Entered parameter indicates how many characters the user has entered on the remote side. New Identity Resolving Key (since 1.5) `````````````````````````````````````` :Event Code: 0x0018 :Controller Index: :Event Parameters: Store_Hint (1 Octet) :...: Random_Address (6 Octets) :...: Address (6 Octets) :...: Address_Type (1 Octet) :...: Value (16 Octets) This event indicates that a new identity resolving key has been generated for a remote device. The Store_Hint parameter indicates whether the host is expected to store the key persistently or not. The Random_Address provides the resolvable random address that was resolved into an identity. A value of 00:00:00:00:00:00 indicates that the identity resolving key was provided for a public address or static random address. Once this event has been send for a resolvable random address, all further events mapping this device will send out using the identity address information. This event also indicates that now the identity address should be used for commands instead of the resolvable random address. It is possible that some devices allow discovering via its identity address, but after pairing using resolvable private address only. In such a case Store_Hint will be 0x00 and the Random_Address will indicate 00:00:00:00:00:00. For these devices, the Privacy Characteristic of the remote GATT database should be consulted to decide if the identity resolving key must be stored persistently or not. Devices using Set Privacy command with the option 0x02 would be such type of device. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0, Reserved (not in use) 1, LE Public 2, LE Random The provided Address and Address_Type are the identity of a device. So either its public address or static random address. New Signature Resolving Key (since 1.5) ``````````````````````````````````````` :Event Code: 0x0019 :Controller Index: :Event Parameters: Store_Hint (1 Octet) :...: Address (6 Octets) :...: Address_Type (1 Octet) :...: Type (1 Octet) :...: Value (16 Octets) This event indicates that a new signature resolving key has been generated for either the central or peripheral device. The Store_Hint parameter indicates whether the host is expected to store the key persistently or not. The Type parameter has the following possible values: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Unauthenticated local CSRK 0x01, Unauthenticated remote CSRK 0x02, Authenticated local CSRK 0x03, Authenticated remote CSRK The local keys are used for signing data to be sent to the remote device, whereas the remote keys are used to verify signatures received from the remote device. The local signature resolving key will be generated with each pairing request. Only after receiving this event with the Type indicating a local key is it possible to use ATT Signed Write procedures. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Reserved (not in use) 0x01, LE Public 0x02, LE Random The provided Address and Address_Type are the identity of a device. So either its public address or static random address. Device Added (since 1.7) ```````````````````````` :Event Code: 0x001a :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Action (1 Octet) This event indicates that a device has been added using the Add Device command. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random The event will only be sent to management sockets other than the one through which the command was sent. Device Removed (since 1.7) `````````````````````````` :Event Code: 0x001b :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) This event indicates that a device has been removed using the Remove Device command. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random The event will only be sent to management sockets other than the one through which the command was sent. New Connection Parameter (since 1.7) ```````````````````````````````````` :Event Code: 0x001c :Controller Index: :Event Parameters: Store_Hint (1 Octet) :...: Address (6 Octets) :...: Address_Type (1 Octet) :...: Min_Connection_Interval (2 Octets) :...: Max_Connection_Interval (2 Octets) :...: Connection_Latency (2 Octets) :...: Supervision_Timeout (2 Octets) This event indicates a new set of connection parameters from a peripheral device. The Store_Hint parameter indicates whether the host is expected to store this information persistently or not. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Reserved (not in use) 0x01, LE Public 0x02, LE Random The Min_Connection_Interval, Max_Connection_Interval, Connection_Latency and Supervision_Timeout parameters are encoded as described in Core 4.1 spec, Vol 2, 7.7.65.3. Unconfigured Index Added (since 1.7) ```````````````````````````````````` :Event Code: 0x001d :Controller Index: :Event Parameters: This event indicates that a new unconfigured controller has been added to the system. It is usually followed by a Read Controller Configuration Information command. Only when a controller requires further configuration, it will be announced with this event. If it supports configuration, but does not require it, then an Index Added event will be used. Once the Read Extended Controller Index List command has been used at least once, the Extended Index Added event will be send instead of this one. Unconfigured Index Removed (since 1.7) `````````````````````````````````````` :Event Code: 0x001e :Controller Index: :Event Parameters: This event indicates that an unconfigured controller has been removed from the system. Once the Read Extended Controller Index List command has been used at least once, the Extended Index Removed event will be send instead of this one. New Configuration Options (since 1.7) ````````````````````````````````````` :Event Code: 0x001f :Controller Index: :Event Parameters: Missing_Options (4 Octets) This event indicates that one or more of the options for the controller configuration has changed. Extended Index Added (since 1.9) ```````````````````````````````` :Event Code: 0x0020 :Controller Index: :Event Parameters: Controller_Type (1 Octet) :...: Controller_Bus (1 Octet) This event indicates that a new controller index has been added to the system. This event will only be used after Read Extended Controller Index List has been used at least once. If it has not been used, then Index Added and Unconfigured Index Added are sent instead. Extended Index Removed (since 1.9) `````````````````````````````````` :Event Code: 0x0021 :Controller Index: :Event Parameters: Controller_Type (1 Octet) :...: Controller_Bus (1 Octet) This event indicates that an existing controller index has been removed from the system. This event will only be used after Read Extended Controller Index List has been used at least once. If it has not been used, then Index Removed and Unconfigured Index Removed are sent instead. Local Out Of Band Extended Data Updated (since 1.9) ``````````````````````````````````````````````````` :Event Code: 0x0022 :Controller Index: :Event Parameters: Address_Type (1 Octet) :...: EIR_Data_Length (2 Octets) :...: EIR_Data (0-65535 Octets) This event is used when the Read Local Out Of Band Extended Data command has been used and some other user requested a new set of local out-of-band data. This allows for the original caller to adjust the data. Possible values for the Address_Type parameter are a bit-wise or of the following bits: .. csv-table:: :header: "Bit", "Description" :widths: auto 0, BR/EDR 1, LE Public 2, LE Random By combining these e.g. the following values are possible: .. csv-table:: :header: "Value", "Description" :widths: auto 0x01, BR/EDR 0x06, LE (public & random) 0x07, Reserved (not in use) The value for EIR_Data_Length and content for EIR_Data is the same as described in Read Local Out Of Band Extended Data command. When LE Privacy is used and LE Secure Connections out-of-band data has been requested, then this event will be emitted every time the Resolvable Private Address (RPA) gets changed. The new RPA will be included in the EIR_Data. The event will only be sent to management sockets other than the one through which the command was sent. It will additionally also only be sent to sockets that have used the command at least once. Advertising Added (since 1.9) ````````````````````````````` :Event Code: 0x0023 :Controller Index: :Event Parameters: Instance (1 Octet) This event indicates that an advertising instance has been added using the Add Advertising command. The event will only be sent to management sockets other than the one through which the command was sent. Advertising Removed (since 1.9) ``````````````````````````````` :Event Code: 0x0024 :Controller Index: :Event Parameters: Instance (1 Octet) This event indicates that an advertising instance has been removed using the Remove Advertising command. The event will only be sent to management sockets other than the one through which the command was sent. Extended Controller Information Changed (since 1.14) ```````````````````````````````````````````````````` :Event Code: 0x0025 :Controller Index: :Event Parameters: EIR_Data_Length (2 Octets) :...: EIR_Data (0-65535 Octets) This event indicates that controller information has been updated and new values are used. This includes the local name, class of device, device id and LE address information. This event will only be used after Read Extended Controller Information command has been used at least once. If it has not been used the legacy events are used. The event will only be sent to management sockets other than the one through which the change was triggered. PHY Configuration Changed (since 1.14) `````````````````````````````````````` :Event Code: 0x0026 :Controller Index: :Event Parameters: Selected_PHYs (4 Octets) This event indicates that default PHYs have been updated. This event will only be used after Set PHY Configuration command has been used at least once. The event will only be sent to management sockets other than the one through which the change was triggered. Refer Get PHY Configuration command for PHYs parameter. Experimental Feature Changed (since 1.17) ````````````````````````````````````````` :Event Code: 0x0027 :Controller Index: :Event Parameters: UUID (16 Octets) :...: Flags (4 Octets) This event indicates that the status of an experimental feature has been changed. The event will only be sent to management sockets other than the one through which the change was triggered. Refer to Set Experimental Feature command for the Flags parameter. Default System Configuration Changed (since 1.18) ````````````````````````````````````````````````` :Event Code: 0x0028 :Controller Index: :Event Parameters: Parameter_Type[] (2 Octet) :...: Value_Length[] (1 Octet) :...: Value[] (0-255 Octets) This event indicates the change of default system parameter values. The event will only be sent to management sockets other than the one through which the change was triggered. In addition it will only be sent to sockets that have issues the Read Default System Configuration command. Refer to Read Default System configuration command for the supported Parameter_Type values. Default Runtime Configuration Changed (since 1.18) `````````````````````````````````````````````````` :Event Code: 0x0029 :Controller Index: :Event Parameters: Parameter_Type[] (2 Octet) :...: Value_Length[] (1 Octet) :...: Value[] (0-255 Octets) This event indicates the change of default runtime parameter values. The event will only be sent to management sockets other than the one through which the change was triggered. In addition it will only be sent to sockets that have issues the Read Default Runtime Configuration command. Refer to Read Default Runtime configuration command for the supported Parameter_Type values. Device Flags Changed (since 1.18) ````````````````````````````````` :Event Code: 0x002a :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: Supported_Flags (4 Octets) :...: Current_Flags (4 Octets) This event indicates that the device flags have been changed via the Set Device Flags command or that a new device has been added via the Add Device command. In the latter case it is send right after the Device Added event. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random The event will only be sent to management sockets other than the one through which the command was sent. In case this event is triggered by Add Device then it is sent to all management sockets. Advertisement Monitor Added (since 1.18) ```````````````````````````````````````` :Event Code: 0x002b :Controller Index: :Event Parameters: Monitor_Handle (2 Octets) This event indicates that an advertisement monitor has been added using the Add Advertisement Patterns Monitor command. The event will only be sent to management sockets other than the one through which the command was sent. Advertisement Monitor Removed (since 1.18) `````````````````````````````````````````` :Event Code: 0x002c :Controller Index: :Event Parameters: Monitor_Handle (2 Octets) This event indicates that an advertisement monitor has been removed using the Remove Advertisement Monitor command. The event will only be sent to management sockets other than the one through which the command was sent. Controller Suspend (since 1.18) ``````````````````````````````` :Event Code: 0x002d :Controller Index: :Event Parameters: Suspend_State (1 octet) This event indicates that the controller is suspended for host suspend. Possible values for the Suspend_State parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Running (not disconnected) 0x01, Disconnected and not scanning 0x02, Page scanning and/or passive scanning. The value 0x00 is used for the running state and may be sent if the controller could not be configured to suspend properly. This event will be sent to all management sockets. Controller Resume (since 1.18) `````````````````````````````` :Event Code: 0x002e :Controller Index: :Event Parameters: Wake_Reason (1 octet) :...: Address (6 octets) :...: Address_Type (1 octet) This event indicates that the controller has resumed from suspend. Possible values for the Wake_Reason parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, Resume from non-Bluetooth wake source 0x01, Wake due to unexpected event 0x02, Remote wake due to peer device connection Currently, we expect that only peer reconnections should wake us from the suspended state. Any other events that occurred while the system should have been suspended results in wake due to unexpected event. If the Wake_Reason is Remote wake due to connection, the address of the peer device that caused the event will be shared in Address and Address_Type. Otherwise, Address and Address_Type will both be zero. This event will be sent to all management sockets. Advertisement Monitor Device Found (since 1.18) ``````````````````````````````````````````````` :Event Code: 0x002f :Controller Index: :Event Parameters: Monitor_Handle (2 Octets) :...: Address (6 Octets) :...: Address_Type (1 Octet) :...: RSSI (1 Octet) :...: Flags (4 Octets) :...: AD_Data_Length (2 Octets) :...: AD_Data (0-65535 Octets) This event indicates that the controller has started tracking a device matching an Advertisement Monitor with handle Monitor_Handle. Monitor_Handle 0 indicates that we are not active scanning and this is a subsequent advertisement report for already matched Advertisement Monitor or the controller offloading support is not available so need to report all advertisements for software based filtering. The address of the device being tracked will be shared in Address and Address_Type. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0x00, BR/EDR 0x01, LE Public 0x02, LE Random For the RSSI field a value of 127 indicates that the RSSI is not available. That can happen with Bluetooth 1.1 and earlier controllers or with bad radio conditions. This event will be sent to all management sockets. Advertisement Monitor Device Lost (since 1.18) `````````````````````````````````````````````` :Event Code: 0x0030 :Controller Index: :Event Parameters: Monitor_Handle (2 Octets) :...: Address (6 Octets) :...: Address_Type (1 Octet) This event indicates that the controller has stopped tracking a device that was being tracked by an Advertisement Monitor with the handle Monitor_Handle. The address of the device being tracked will be shared in Address and Address_Type. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0, Reserved (not in use) 1, LE Public 2, LE Random This event will be sent to all management sockets. Mesh Device Found (since 1.21) `````````````````````````````` :Event Code: 0x0031 :Controller Index: :Event Parameters: Address (6 Octets) :...: Address_Type (1 Octet) :...: RSSI (1 Octet) :...: Instant (8 Octets) :...: Flags (4 Octets) :...: AD_Data_Length (2 Octets) :...: AD_Data (0-65535 Octets) This event indicates that the controller has received an Advertisement or Scan Result containing an AD Type matching the Mesh scan set. The address of the sending device is returned, and must be a valid LE Address_Type. Possible values for the Address_Type parameter: .. csv-table:: :header: "Value", "Description" :widths: auto 0, Reserved (not in use) 1, LE Public 2, LE Random The RSSI field is a signed octet, and is the RSSI reported by the receiving controller. The Instant field is 64 bit value that represents the instant in time the packet was received. It's value is not intended to be interpreted by the host, and is only useful if the host wants to make a timed response to the received packet (i.e. a Poll/Response). AD_Length and AD_Data contains the Info structure of Advertising and Scan rsults. To receive this event, AD filters must be requested with the Set Mesh Receiver command, specifying which AD Types to return. All AD structures will be received in this event if any of the filtered AD Types are present. This event will be sent to all management sockets. Mesh Packet Transmit Complete (since 1.21) `````````````````````````````````````````` :Event Code: 0x0032 :Controller Index: :Event Parameters: Handle (1 Octets) This event indicates that a requested outbound Mesh packet has completed and no longer occupies a transmit slot. This event will be sent to all management sockets. bluez-5.87/doc/PaxHeaders/bluetoothd.rst.in0000644000000000000000000000005015142177620015735 xustar0020 atime=1783092767 20 ctime=1783094098 bluez-5.87/doc/bluetoothd.rst.in0000644000000000000000000000531415142177620015421 0ustar00rootroot========== bluetoothd ========== ---------------- Bluetooth daemon ---------------- :Authors: - Marcel Holtmann - Philipp Matthias Hahn - Fredrik Noring :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: March, 2004 :Manual section: 8 :Manual group: System management commands SYNOPSIS ======== **bluetoothd** [--version] | [--help] **bluetoothd** [--nodetach] [--compat] [--experimental] [--debug=<*files*>] [--plugin=<*plugins*>] [--noplugin=<*plugins*>] DESCRIPTION =========== This manual page documents briefly the **bluetoothd** daemon, which manages all the Bluetooth devices. **bluetoothd** can also provide a number of services via the D-Bus message bus system. OPTIONS ======= -v, --version Print bluetoothd version and exit. -h, --help Print bluetoothd options and exit. -n, --nodetach Enable logging in foreground. Directs log output to the controlling terminal in addition to syslog. -f, --configfile Specifies an explicit config file path instead of relying on the default path(*@CONFIGDIR@/main.conf*) for the config file. -d, --debug=::... Sets how much information bluetoothd sends to the log destination (usually syslog's "daemon" facility). If the file options are omitted, then debugging information from all the source files are printed. If file options are present, then only debug prints from that source file are printed. The option can be a pattern containing "*" and "?" characters. Example: --debug=src/adapter.c:src/agent.c -p, --plugin=,,.. Load these plugins only. The option can be a pattern containing "*" and "?" characters. -P, --noplugin=,,.. Never load these plugins. The option can be a pattern containing "*" and "?" characters. -C, --compat Provide deprecated command line interfaces. -E, --experimental Enable D-Bus experimental interfaces. These interfaces are not guaranteed to be compatible or present in future releases. -T, --testing Enable D-Bus testing interfaces. These interfaces are only meant for test validation of the internals of bluetoothd and shall not never be used by anything other than that. -K, --kernel=,,... Enable Kernel experimental features. Kernel experimental features are considered unstable and may be removed from future kernel releases. FILES ===== *@CONFIGDIR@/main.conf* Location of the global configuration file. RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.MediaPlayer.50000644000000000000000000000005015221755327016621 xustar0020 atime=1783093975 20 ctime=1783094112 bluez-5.87/doc/org.bluez.MediaPlayer.50000644000000000000000000001661715221755327016315 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.MediaPlayer" "5" "September 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.MediaPlayer \- BlueZ D-Bus MediaPlayer API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez (Controller role) .TP .B Interface org.bluez.MediaPlayer1 .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/player# .TP .B Used by \fBbluetoothctl\-player(1)\fP .UNINDENT .SS Methods .SS void Play() .sp Resume playback. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > player.play [item] .UNINDENT .SS void Pause() .sp Pause playback. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > player.pause .UNINDENT .SS void Stop() .sp Stop playback. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > player.stop .UNINDENT .SS void Next() .sp Next item. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > player.next .UNINDENT .SS void Previous() .sp Previous item. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > player.previous .UNINDENT .SS void FastForward() .sp Fast forward playback, this action is only stopped when another method in this interface is called. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > player.fast\-forward .UNINDENT .SS void Rewind() .sp Rewind playback, this action is only stopped when another method in this interface is called. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > player.rewind .UNINDENT .SS void Press(byte avc_key) .sp Press a specific key to send as passthrough command. .sp The key will be released automatically. Use Hold() instead if the intention is to hold down the key. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .SS void Hold(byte avc_key) .sp Press and hold a specific key to send as passthrough command. It is the responsibility of the caller to make sure that Release() is called after calling this method. .sp The held key will also be released when any other method in this interface is called. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .SS void Release() .sp Release the previously held key invoked using Hold(). .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .SS Properties .SS string Equalizer [readwrite] .sp Indicates Player Equalizer setting. .sp Possible values: .INDENT 0.0 .TP .B \(dqoff\(dq .TP .B \(dqon\(dq .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl [player] > equalizer .UNINDENT .SS string Repeat [readwrite] .sp Indicates Player Repeat setting. .sp Possible values: .INDENT 0.0 .TP .B \(dqoff\(dq .TP .B \(dqsingletrack\(dq .TP .B \(dqalltracks\(dq .TP .B \(dqgroup\(dq .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl [player] > repeat .UNINDENT .SS string Shuffle [readwrite] .sp Indicates Player Suffle setting. .sp Possible values: .INDENT 0.0 .TP .B \(dqoff\(dq .TP .B \(dqalltracks\(dq .TP .B \(dqgroup\(dq .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl [player] > shuffle .UNINDENT .SS string Scan [readwrite] .sp Indicates Player Scan setting. .sp Possible values: .INDENT 0.0 .TP .B \(dqoff\(dq .TP .B \(dqalltracks\(dq .TP .B \(dqgroup\(dq .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl [player] > scan .UNINDENT .SS string Status [readonly] .sp Indicates Player Status setting. .sp Possible status: .INDENT 0.0 .TP .B \(dqplaying\(dq .TP .B \(dqstopped\(dq .TP .B \(dqpaused\(dq .TP .B \(dqforward\-seek\(dq .TP .B \(dqreverse\-seek\(dq .TP .B \(dqerror\(dq .UNINDENT .SS uint32 Position [readonly] .sp Playback position in milliseconds. .sp Changing the position may generate additional events that will be sent to the remote device. When position is 0 it means the track is starting and when it\(aqs greater than or equal to track\(aqs duration the track has ended. .sp Note that even if duration is not available in metadata it\(aqs possible to signal its end by setting position to the maximum uint32 value. .SS dict Track [readonly] .sp Track metadata. .sp Possible values: .INDENT 0.0 .TP .B string Title Track title name .TP .B string Artist Track artist name .TP .B string Album Track album name .TP .B string Genre Track genre name .TP .B uint32 NumberOfTracks Number of tracks in total .TP .B uint32 TrackNumber Track number .TP .B uint32 Duration Track duration in milliseconds .TP .B string ImgHandle [experimental] .sp Track image handle, available and valid only during the lifetime of an OBEX BIP connection to the ObexPort. .UNINDENT .SS object Device [readonly] .sp Device object path. .SS string Name [readonly] .sp Player name. .SS string Type [readonly] .sp Player type. .sp Possible values: .INDENT 0.0 .TP .B \(dqAudio\(dq .TP .B \(dqVideo\(dq .TP .B \(dqAudio Broadcasting\(dq .TP .B \(dqVideo Broadcasting\(dq .UNINDENT .SS string Subtype [readonly] .sp Player subtype. .sp Possible values: .INDENT 0.0 .TP .B \(dqAudio Book\(dq .TP .B \(dqPodcast\(dq .UNINDENT .SS boolean Browsable [readonly] .sp If present indicates the player can be browsed using MediaFolder interface. .sp Possible values: .INDENT 0.0 .TP .B True Supported and active .TP .B False Supported but inactive .UNINDENT .sp Note: If supported but inactive clients can enable it by using \fBorg.bluez.MediaFolder(5)\fP interface but it might interfere in the playback of other players. .SS boolean Searchable [readonly] .sp If present indicates the player can be searched using MediaFolder interface. .sp Possible values: .INDENT 0.0 .TP .B True Supported and active .TP .B False Supported but inactive .UNINDENT .sp Note: If supported but inactive clients can enable it by using \fBorg.bluez.MediaFolder(5)\fP interface but it might interfere in the playback of other players. .SS object Playlist .sp Playlist object path. .SS uint16 ObexPort [readonly, experimental] .sp If present indicates the player can get cover art using BIP over OBEX on this PSM port. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/bluetoothctl-scan.rst0000644000000000000000000000005015142177620016611 xustar0020 atime=1783094067 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-scan.rst0000644000000000000000000002060615142177620016276 0ustar00rootroot================= bluetoothctl-scan ================= ------------ Scan Submenu ------------ :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: July 2023 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **bluetoothctl** [--options] [scan.commands] This submenu configures discovery filters using **org.bluez.Adapter(5)** method **SetDiscoveryFilter**. Scan Commands ============= uuids ----- Set/Get UUIDs filter. :Usage: **> uuids [all/uuid1 uuid2 ...]** :Uses: **org.bluez.Adapter(5)** method **SetDiscoveryFilter** property **UUIDs** :[all/uuid1 uuid2 ...]: List of UUIDs to filter for during scanning (default: none, use "all" to scan for any UUID) :Example Show current UUID filter settings: | **> uuids** :Example Filter for Generic Access service only: | **> uuids 0x1800** :Example Filter for Battery service only: | **> uuids 0x180F** :Example Filter for Generic Access and Battery services: | **> uuids 0x1800 0x180F** :Example Filter for Device Info, Battery, and Environmental Sensing: | **> uuids 0x180A 0x180F 0x181A** :Example Filter for Generic Access using full UUID: | **> uuids 00001800-0000-1000-8000-00805f9b34fb** :Example Filter using mixed short and long forms: | **> uuids 00001800-0000-1000-8000-00805f9b34fb 0000180f-0000-1000-8000-00805f9b34fb** :Example Remove UUID filtering (scan for all devices): | **> uuids all** rssi ---- Set/Get RSSI filter, and clears pathloss. This sets the minimum rssi value for reporting device advertisements. The value is in dBm. If one or more discovery filters have been set, the RSSI delta-threshold imposed by starting discovery by default will not be applied. :Usage: **> rssi [rssi]** :Uses: **org.bluez.Adapter(5)** method **SetDiscoveryFilter** property **RSSI** :[rssi]: Minimum RSSI threshold value in dBm (optional, shows current if omitted) :Example Show current RSSI filter setting: | **> rssi** :Example Only report devices with RSSI ≥ -60 dBm (close range): | **> rssi -60** :Example Report devices with RSSI ≥ -80 dBm (medium range): | **> rssi -80** :Example Report devices with RSSI ≥ -90 dBm (extended range): | **> rssi -90** :Example Report only very close devices (RSSI ≥ -40 dBm): | **> rssi -40** :Example Report devices with very weak signals (maximum sensitivity): | **> rssi -100** pathloss -------- Set/Get Pathloss filter, and clears RSSI. This sets the maximum pathloss value for reporting device advertisements. The value is in dB. If one or more discovery filters have been set, the RSSI delta-threshold imposed by starting discovery by default will not be applied. :Usage: **> pathloss [pathloss]** :Uses: **org.bluez.Adapter(5)** method **SetDiscoveryFilter** property **Pathloss** :[pathloss]: Maximum pathloss threshold value in dB (optional, shows current if omitted) :Example Show current pathloss filter setting: | **> pathloss** :Example Report devices with maximum 4 dB pathloss (very close): | **> pathloss 4** :Example Report devices with maximum 10 dB pathloss (close range): | **> pathloss 10** :Example Report devices with maximum 20 dB pathloss (medium range): | **> pathloss 20** :Example Report devices with maximum 30 dB pathloss (extended range): | **> pathloss 30** :Example Report devices with maximum 50 dB pathloss (maximum range): | **> pathloss 50** transport --------- Set/Get transport filter. Transport parameter determines the type of scan. The default is auto. Possible values: - "auto": interleaved scan - "bredr": BR/EDR inquiry - "le": LE scan only If "le" or "bredr" Transport is requested and the controller doesn't support it, an org.bluez.Error.Failed error will be returned. If "auto" transport is requested, the scan will use LE, BREDR, or both, depending on what's currently enabled on the controller. :Usage: **> transport [auto/bredr/le]** :[auto/bredr/le]: Transport type for scanning (optional, shows current if omitted) :Example Show current transport filter setting: | **> transport** :Example Use automatic transport selection (LE + BR/EDR): | **> transport auto** :Example Scan only Low Energy devices: | **> transport le** :Example Scan only BR/EDR (Classic Bluetooth) devices: | **> transport bredr** duplicate-data -------------- Set/Get duplicate data filter. Disables duplicate detection of advertisement data. When enabled, PropertiesChanged signals will be generated for ManufacturerData and ServiceData every time they are discovered. :Usage: **> duplicate-data [on/off]** :[on/off]: Enable or disable duplicate advertisement data reporting (optional, shows current if omitted) :Example Show current duplicate data filter setting: | **> duplicate-data** :Example Enable reporting of duplicate advertisement data: | **> duplicate-data on** :Example Disable duplicate data reporting (filter duplicates): | **> duplicate-data off** discoverable ------------ Set/Get discoverable filter. Makes the adapter discoverable while discovering. If the adapter is already discoverable, setting this filter won't have any effect. :Usage: **> discoverable [on/off]** :[on/off]: Make adapter discoverable during scanning (optional, shows current if omitted) :Example Show current discoverable filter setting: | **> discoverable** :Example Make adapter discoverable during scanning: | **> discoverable on** :Example Keep adapter non-discoverable during scanning: | **> discoverable off** pattern ------- Set/Get pattern filter. Discover devices where the pattern matches either the prefix of the address or the device name, which is a convenient way to limit the number of device objects created during a discovery. When set, it disregards device discoverable flags. :Note: The pattern matching is ignored if there are other clients that don't set any pattern, as it works as a logical OR. Also, setting an empty string "" pattern will match any device found. :Usage: **> pattern [value]** :[value]: Pattern to match device address prefix or name (optional, shows current if omitted) :Example Show current pattern filter setting: | **> pattern** :Example Discover devices with "Samsung" in the name: | **> pattern Samsung** :Example Discover devices with "iPhone" in the name: | **> pattern iPhone** :Example Discover devices with "Headphones" in the name: | **> pattern Headphones** :Example Discover devices with addresses starting with 00:11:22: | **> pattern 00:11:22** :Example Discover devices with addresses starting with AA:BB:CC: | **> pattern AA:BB:CC** :Example Clear pattern filter (match any device): | **> pattern ""** auto-connect ------------ Set/Get auto-connect filter. Connect to discovered devices automatically if pattern filter has been set and it matches the device address or name and the device is connectable. :Usage: **> auto-connect [on/off]** :[on/off]: Automatically connect to discovered devices matching pattern filter (optional, shows current if omitted) :Example Show current auto-connect filter setting: | **> auto-connect** :Example Enable automatic connection to matching devices: | **> auto-connect on** :Example Disable automatic connection (manual connection required): | **> auto-connect off** clear ----- Clears discovery filter. :Usage: **> clear [uuids/rssi/pathloss/transport/duplicate-data/discoverable/pattern/auto-connect]** :[uuids/rssi/pathloss/transport/duplicate-data/discoverable/pattern/auto-connect]: Specific filter(s) to clear (optional, clears all if omitted) :Example Clear all discovery filters: | **> clear** :Example Clear only UUID filter: | **> clear uuids** :Example Clear only RSSI filter: | **> clear rssi** :Example Clear only pathloss filter: | **> clear pathloss** :Example Clear only transport filter: | **> clear transport** :Example Clear only duplicate data filter: | **> clear duplicate-data** :Example Clear only discoverable filter: | **> clear discoverable** :Example Clear only pattern filter: | **> clear pattern** :Example Clear only auto-connect filter: | **> clear auto-connect** :Example Clear UUID and RSSI filters: | **> clear uuids rssi** :Example Clear transport and pattern filters: | **> clear transport pattern** :Example Clear pattern and auto-connect filters: | **> clear pattern auto-connect** :Example Clear RSSI, pathloss, and discoverable filters: | **> clear rssi pathloss discoverable** RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/btmon-classic-audio.rst0000644000000000000000000000005015200607060017003 xustar0020 atime=1783092683 20 ctime=1783094112 bluez-5.87/doc/btmon-classic-audio.rst0000644000000000000000000000070615200607060016467 0ustar00rootroot.. This file is included by btmon.rst. CLASSIC AUDIO PROTOCOL FLOW ============================= Classic Bluetooth audio uses two main profiles: A2DP for high-quality stereo streaming and HFP for voice calls. Both run over BR/EDR ACL connections and use different transport mechanisms for audio data -- A2DP uses L2CAP-based AVDTP media channels while HFP uses SCO/eSCO synchronous connections. .. include:: btmon-a2dp.rst .. include:: btmon-hfp.rst bluez-5.87/doc/PaxHeaders/supported-features.txt0000644000000000000000000000005014766002272017030 xustar0020 atime=1770587295 20 ctime=1783094111 bluez-5.87/doc/supported-features.txt0000644000000000000000000000203714766002272016513 0ustar00rootrootSupported features in BlueZ =========================== Note that some profiles/roles will depend on external components such as oFono or ConnMan. Profile/protocol Version Role(s) --------------------------------------------------------------------------- GAP 4.2 (LE) Central, Peripheral, Observer, Broadcaster L2CAP 4.2 Server, Client SDP 4.2 Server, Client GATT 4.2 Server, Client SDAP 1.1 Server, Client RFCOMM 1.1 Server, Client SPP 1.2 Server, Client PXP 1.0 Reporter, Monitor HOGP 1.0 Host HTP 1.0 TIP 1.0 CSCP 1.0 Collector SAP 1.1 Server DUN 1.1 Server, Client DID 1.3 Server, Client HFP 1.6 AG, HF HSP 1.2 AG, HS GAVDTP 1.2 Source, Sink AVDTP 1.3 Source, Sink A2DP 1.3 Source, Sink AVCTP 1.3 CT, TG AVRCP 1.5 CT, TG GOEP 2.0 Client, Server FTP 1.3 Client, Server OPP 1.2 Client, Server SYNCH 1.1 Client PBAP 1.1 Client, Server MAP 1.0 Server MAP 1.4 Client HID 1.1 Host BNEP 1.0 PAN 1.0 PANU, NAP, GN HCRP 1.2 MCAP 1.0 HDP 1.0 bluez-5.87/doc/PaxHeaders/sdptool.rst0000644000000000000000000000005015142177620014643 xustar0020 atime=1783094089 20 ctime=1783094113 bluez-5.87/doc/sdptool.rst0000644000000000000000000000543015142177620014326 0ustar00rootroot======= sdptool ======= ----------------------------------- control and interrogate SDP servers ----------------------------------- :Authors: - Maxim Krasnyansky - Edd Dumbill :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **sdptool** [*OPTIONS*] [*COMMAND* [*PARAMETERS*]] DESCRIPTION =========== **sdptool(1)** provides the interface for performing SDP queries on Bluetooth devices, and administering a local SDP database. COMMANDS ======== The following commands are available. In all cases **bdaddr** specifies the device to search or browse. If *local* is used for **bdaddr**, then the local SDP database is searched. Services are identified and manipulated with a 4-byte **record_handle** (NOT the service name). To find a service's **record_handle**, look for the "Service RecHandle" line in the **search** or **browse** results search [--bdaddr bdaddr] [--tree] [--raw] [--xml] service_name Search for services.. Known service names are **DID**, **SP**, **DUN**, **LAN**, **FAX**, **OPUSH**, **FTP**, **HS**, **HF**, **HFAG**, **SAP**, **NAP**, **GN**, **PANU**, **HCRP**, **HID**, **CIP**, **A2SRC**, **A2SNK**, **AVRCT**, **AVRTG**, **UDIUE**, **UDITE** and **SYNCML**. browse [--tree] [--raw] [--xml] [bdaddr] Browse all available services on the device specified by a Bluetooth address as a parameter. records [--tree] [--raw] [--xml] bdaddr Retrieve all possible service records. add [ --handle=N --channel=N ] Add a service to the local SDP database. You can specify a handle for this record using the **--handle** option. You can specify a channel to add the service on using the **--channel** option. NOTE: Local adapters configuration will not be updated and this command should be used only for SDP testing. del record_handle Remove a service from the local SDP database. NOTE: Local adapters configuration will not be updated and this command should be used only for SDP testing. get [--tree] [--raw] [--xml] [--bdaddr bdaddr] record_handle Retrieve a service from the local SDP database. setattr record_handle attrib_id attrib_value Set or add an attribute to an SDP record. setseq record_handle attrib_id attrib_values Set or add an attribute sequence to an SDP record. OPTIONS ======= --help Displays help on using sdptool. EXAMPLES ======== .. code-block:: $ sdptool browse 00:80:98:24:15:6D $ sdptool browse local $ sdptool add DUN $ sdptool del 0x10000 RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.Device.rst0000644000000000000000000000005015142177620016264 xustar0020 atime=1783093940 20 ctime=1783094111 bluez-5.87/doc/org.bluez.Device.rst0000644000000000000000000003016315142177620015750 0ustar00rootroot================ org.bluez.Device ================ ------------------------------------ BlueZ D-Bus Device API documentation ------------------------------------ :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.Device1 :Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} :Used by: **bluetoothctl(1)** Methods ------- void Connect() `````````````` Connects all profiles the remote device supports that can be connected to and have been flagged as auto-connectable. If only subset of profiles is already connected it will try to connect currently disconnected ones. If at least one profile was connected successfully this method will indicate success. For dual-mode devices only one bearer is connected at time, the conditions are in the following order: 1. Connect the disconnected bearer if already connected. 2. Connect first the bonded bearer. If no bearers are bonded or both are skip and check latest seen bearer. 3. Connect last used bearer, in case the timestamps are the same BR/EDR takes precedence, or in case **PreferredBearer** has been set to a specific bearer then that is used instead. Possible errors: :org.bluez.Error.NotReady: :org.bluez.Error.Failed: :org.bluez.Error.InProgress: :org.bluez.Error.AlreadyConnected: :org.bluez.Error.BREDR.ProfileUnavailable: Examples: :bluetoothctl: > connect [uuid] void Disconnect() ````````````````` Disconnects all connected profiles and then terminates low-level ACL connection. ACL connection will be terminated even if some profiles were not disconnected properly e.g. due to misbehaving device. This method can be also used to cancel a preceding Connect call before a reply to it has been received. For non-trusted devices connected over LE bearer calling this method will disable incoming connections until Connect method is called again. Possible errors: :org.bluez.Error.NotConnected: Examples: :bluetoothctl: > disconnect [dev] [uuid] void ConnectProfile(string uuid) ```````````````````````````````` Connects a specific profile of this device. The UUID provided is the remote service UUID for the profile. Possible errors: :org.bluez.Error.Failed: :org.bluez.Error.InProgress: :org.bluez.Error.InvalidArguments: :org.bluez.Error.NotAvailable: :org.bluez.Error.NotReady: void DisconnectProfile(string uuid) ``````````````````````````````````` Disconnects a specific profile of this device. The profile needs to be registered client profile. There is no connection tracking for a profile, so as long as the profile is registered this will always succeed. Possible errors: :org.bluez.Error.Failed: :org.bluez.Error.InProgress: :org.bluez.Error.InvalidArguments: :org.bluez.Error.NotSupported: void Pair() ``````````` Connects to the remote device and initiate pairing procedure then proceed with service discovery. If the application has registered its own agent, then that specific agent will be used. Otherwise it will use the default agent. Only for applications like a pairing wizard it would make sense to have its own agent. In almost all other cases the default agent will handle this just fine. In case there is no application agent and also no default agent present, this method will fail. Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.Failed: :org.bluez.Error.AlreadyExists: :org.bluez.Error.AuthenticationCanceled: :org.bluez.Error.AuthenticationFailed: :org.bluez.Error.AuthenticationRejected: :org.bluez.Error.AuthenticationTimeout: :org.bluez.Error.ConnectionAttemptFailed: Examples: :bluetoothctl: > pair [dev] void CancelPairing() ```````````````````` Cancels a pairing operation initiated by the **Pair** method. Possible errors: :org.bluez.Error.DoesNotExist: :org.bluez.Error.Failed: Examples: :bluetoothctl: > cancel-pairing [dev] array{array{byte}} GetServiceRecords() [experimental] ````````````````````````````````````````````````````` Returns all currently known BR/EDR service records for the device. Each individual byte array represents a raw SDP record, as defined by the Bluetooth Service Discovery Protocol specification. This method is intended to be only used by compatibility layers like Wine, that need to provide access to raw SDP records to support foreign Bluetooth APIs. General applications should instead use the Profile API for services-related functionality. Possible errors: :org.bluez.Error.Failed: :org.bluez.Error.NotReady: :org.bluez.Error.NotConnected: :org.bluez.Error.DoesNotExist: Signals ------- void Disconnected(string reason, string message) ```````````````````````````````````````````````` This signal is launched when a device is disconnected, with the reason of the disconnection. This could be used by client application, depending on internal policy, to try to reconnect to the device in case of timeout or unknown disconnection, or to try to connect to another device. Possible reasons: :org.bluez.Reason.Unknown: :org.bluez.Reason.Timeout: Connection timeout. The link supervision timeout has expired for a connection or the synchronization timeout has expired for a broadcast. :org.bluez.Reason.Local: Connection terminated by local host. The local device terminated the connection, terminated synchronization with a broadcaster, or terminated broadcasting packets. :org.bluez.Reason.Remote: Connection terminated by remote host. This disconnection can be due to: - the user on the remote device either terminated the connection or stopped broadcasting packets, - the remote device terminated the connection because of low resources, - the remote device terminated the connection because the device is about to power off. :org.bluez.Reason.Authentication: Connection terminated due to an authentication failure. :org.bluez.Reason.Suspend: Connection terminated by local host for suspend. Properties ---------- string Address [readonly] ````````````````````````` The Bluetooth device address of the remote device. string AddressType [readonly] ````````````````````````````` The Bluetooth device Address Type. For dual-mode and BR/EDR only devices this defaults to "public". Single mode LE devices may have either value. If remote device uses privacy than before pairing this represents address type used for connection and Identity Address after pairing. Possible values: :"public": Public address :"random": Random address string Name [readonly, optional] ```````````````````````````````` The Bluetooth remote name. This value is only present for completeness. It is better to always use the **Alias** property when displaying the devices name. If the **Alias** property is unset, it will reflect this value which makes it more convenient. string Icon [readonly, optional] ```````````````````````````````` Proposed icon name according to the freedesktop.org icon naming specification. uint32 Class [readonly, optional] ````````````````````````````````` The Bluetooth class of device of the remote device. uint16 Appearance [readonly, optional] `````````````````````````````````````` External appearance of device, as found on GAP service. array{string} UUIDs [readonly, optional] ```````````````````````````````````````` List of 128-bit UUIDs that represents the available remote services. boolean Paired [readonly] ````````````````````````` Indicates if the remote device is paired. Paired means the pairing process where devices exchange the information to establish an encrypted connection has been completed. boolean Bonded [readonly] ````````````````````````` Indicates if the remote device is bonded. Bonded means the information exchanged on pairing process has been stored and will be persisted. boolean Connected [readonly] ```````````````````````````` Indicates if the remote device is currently connected. A PropertiesChanged signal indicate changes to this status. boolean Trusted [readwrite] ``````````````````````````` Indicates if the remote is seen as trusted. This setting can be changed by the application. Examples: :bluetoothctl: > trust [dev] :bluetoothctl: > untrust [dev] boolean Blocked [readwrite] ``````````````````````````` If set to true any incoming connections from the device will be immediately rejected. Any device drivers will also be removed and no new ones will be probed as long as the device is blocked. Examples: :bluetoothctl: > block [dev] :bluetoothctl: > unblock [dev] boolean WakeAllowed [readwrite] ``````````````````````````````` If set to true this device will be allowed to wake the host from system suspend. Examples: :bluetoothctl: > wake [dev] [on/off] string Alias [readwrite] ```````````````````````` The name alias for the remote device. The alias can be used to have a different friendly name for the remote device. In case no alias is set, it will return the remote device name. Setting an empty string as alias will convert it back to the remote device name. When resetting the alias with an empty string, the property will default back to the remote name. Examples: :bluetoothctl: > set-alias object Adapter [readonly] ````````````````````````` The object path of the adapter the device belongs to. boolean LegacyPairing [readonly] ```````````````````````````````` Set to true if the device only supports the pre-2.1 pairing mechanism. This property is useful during device discovery to anticipate whether legacy or simple pairing will occur if pairing is initiated. Note that this property can exhibit false-positives in the case of Bluetooth 2.1 (or newer) devices that have disabled Extended Inquiry Response support. boolean CablePairing [readonly] ``````````````````````````````` Set to true if the device was cable paired and it doesn't support the canonical bonding with encryption, e.g. the Sixaxis gamepad. If true, BlueZ will establish a connection without enforcing encryption. string Modalias [readonly, optional] ```````````````````````````````````` Remote Device ID information in modalias format used by the kernel and udev. int16 RSSI [readonly, optional] ``````````````````````````````` Received Signal Strength Indicator of the remote device (inquiry or advertising). int16 TxPower [readonly, optional] `````````````````````````````````` Advertised transmitted power level (inquiry or advertising). dict ManufacturerData [readonly, optional] `````````````````````````````````````````` Manufacturer specific advertisement data. Keys are 16 bits Manufacturer ID followed by its byte array value. dict ServiceData [readonly, optional] ````````````````````````````````````` Service advertisement data. Keys are the UUIDs in string format followed by its byte array value. bool ServicesResolved [readonly] ```````````````````````````````` Indicate whether or not service discovery has been resolved. array{byte} AdvertisingFlags [readonly] ``````````````````````````````````````` The Advertising Data Flags of the remote device. dict AdvertisingData [readonly] ``````````````````````````````` The Advertising Data of the remote device. Keys are 1 byte AD Type followed by data as byte array. Note: Only types considered safe to be handled by application are exposed. Possible values: :: Example: 0x26 0x01 0x01... array{object, dict} Sets [readonly, experimental] ````````````````````````````````````````````````` The object paths of the sets the device belongs to followed by a dictionary which can contain the following: :byte Rank: Rank of the device in the Set. string PreferredBearer [readwrite, optional, experimental] `````````````````````````````````````````````````````````` Indicate the preferred bearer when initiating a connection, only available for dual-mode devices. When changing from "bredr" to "le" the device will be removed from the 'auto-connect' list so it won't automatically be connected when advertising. Note: Changes only take effect when the device is disconnected. Possible values: :"last-used": Connect to last used bearer first. Default. :"bredr": Connect to BR/EDR first. :"le": Connect to LE first. :"last-seen": Connect to last seen bearer first. Examples: :bluetoothctl: > bearer [last-seen/bredr/le] bluez-5.87/doc/PaxHeaders/mgmt.70000644000000000000000000000005015221755244013463 xustar0020 atime=1783093924 20 ctime=1783094112 bluez-5.87/doc/mgmt.70000644000000000000000000054107515221755244013160 0ustar00rootroot'\" t .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "mgmt" "7" "April 2025" "BlueZ" "Linux System Administration" .SH Name mgmt \- Bluetooth Management Protocol documentation .SH SYNOPSIS .sp The Bluetooth management sockets can be created by setting the hci_channel member of struct sockaddr_hci to HCI_CHANNEL_CONTROL (see hci(7)) when creating a raw HCI socket: .INDENT 0.0 .INDENT 3.5 .sp .EX int mgmt_create(void) { struct sockaddr_hci addr; int fd; fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, BTPROTO_HCI); if (fd < 0) return \-errno; memset(&addr, 0, sizeof(addr)); addr.hci_family = AF_BLUETOOTH; addr.hci_dev = HCI_DEV_NONE; addr.hci_channel = HCI_CHANNEL_CONTROL; if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { int err = \-errno; close(fd); return err; } return fd; } .EE .UNINDENT .UNINDENT .sp The process creating the mgmt socket is required to have the CAP_NET_ADMIN capability (e.g. root would have this). .SH DESCRIPTION .sp This document describes the format of data used for communicating with the kernel using a so\-called Bluetooth Management sockets. These sockets are available starting with Linux kernel version 3.4 .sp The following kernel versions introduced new commands, new events or important fixes to the Bluetooth Management API: .TS box center; l|l|l|l. T{ Version T} T{ Kernel Version T} T{ Commands T} T{ Events T} _ T{ 1.1 T} T{ 3.4 T} T{ Set Device ID T} T{ T} _ T{ 1.2 T} T{ 3.7 T} T{ T} T{ Passkey Notify T} _ T{ 1.3 T} T{ 3.9 T} T{ T} T{ T} _ T{ 1.4 T} T{ 3.13 T} T{ Set Advertising T} T{ T} _ T{ T} T{ T} T{ Set BR/EDR T} T{ T} _ T{ T} T{ T} T{ Set Static Address T} T{ T} _ T{ T} T{ T} T{ Set Scan Parameters T} T{ T} _ T{ 1.5 T} T{ 3.15 T} T{ Set Secure Connections T} T{ New Identity Resolving Key T} _ T{ T} T{ T} T{ Set Debug Keys T} T{ New Signature Resolving Key T} _ T{ T} T{ T} T{ Set Privacy T} T{ T} _ T{ T} T{ T} T{ Load Identity Resolving Keys T} T{ T} _ T{ 1.6 T} T{ 3.16 T} T{ Get Connection Information T} T{ T} _ T{ 1.7 T} T{ 3.17 T} T{ Get Clock Information T} T{ T} _ T{ T} T{ T} T{ Add Device T} T{ Device Added T} _ T{ T} T{ T} T{ Remove Device T} T{ Device Removed T} _ T{ T} T{ T} T{ Load Connection Parameters T} T{ New Connection Parameter T} _ T{ T} T{ T} T{ Read Unconfigured Index List T} T{ Unconfigured Index Added T} _ T{ T} T{ T} T{ Read Controller Configuration Information T} T{ Unconfigured Index Removed T} _ T{ T} T{ T} T{ Set External Configuration T} T{ New Configuration Options T} _ T{ T} T{ T} T{ Set Public Address commands T} T{ T} _ T{ 1.8 T} T{ 3.19 T} T{ Start Service Discovery T} T{ T} _ T{ 1.9 T} T{ 4.1 T} T{ Read Local Out Of Band Extended Data T} T{ Extended Index Added T} _ T{ T} T{ T} T{ Read Extended Controller Index List T} T{ Extended Index Removed T} _ T{ T} T{ T} T{ Read Advertising Features T} T{ Local Out Of Band Extended Data Updated T} _ T{ T} T{ T} T{ Add Advertising T} T{ Advertising Added T} _ T{ T} T{ T} T{ Remove Advertising T} T{ Advertising Removed T} _ T{ 1.10 T} T{ 4.2 T} T{ T} T{ T} _ T{ 1.11 T} T{ 4.5 T} T{ Get Advertising Size Information T} T{ T} _ T{ T} T{ T} T{ Start Limited Discovery T} T{ T} _ T{ 1.12 T} T{ 4.6 T} T{ T} T{ T} _ T{ 1.13 T} T{ 4.8 T} T{ T} T{ T} _ T{ 1.14 T} T{ 4.9 T} T{ Read Extended Controller Information T} T{ Extended Controller Information Changed T} _ T{ T} T{ T} T{ Set Appearance T} T{ T} _ T{ 1.15 T} T{ 5.5 T} T{ Get PHY Configuration T} T{ PHY Configuration Changed T} _ T{ T} T{ T} T{ Set PHY Configuration T} T{ T} _ T{ T} T{ T} T{ Load Blocked Keys T} T{ T} _ T{ 1.16 T} T{ 5.6 T} T{ Set Wideband Speech T} T{ T} _ T{ 1.17 T} T{ 5.7 T} T{ Read Controller Capabilities T} T{ Experimental Feature Changed T} _ T{ T} T{ T} T{ Read Experimental Features Information T} T{ T} _ T{ T} T{ T} T{ Set Experimental Feature T} T{ T} _ T{ 1.18 T} T{ 5.8 T} T{ Read Default System Configuration T} T{ Default System Configuration Changed T} _ T{ T} T{ T} T{ Set Default System Configuration T} T{ Default Runtime Configuration Changed T} _ T{ T} T{ T} T{ Read Default Runtime Configuration T} T{ Default Runtime Configuration Changed T} _ T{ T} T{ T} T{ Set Default Runtime Configuration T} T{ Device Flags Changed T} _ T{ T} T{ T} T{ Get Device Flags T} T{ Advertisement Monitor Added T} _ T{ T} T{ T} T{ Set Device Flags T} T{ Advertisement Monitor Removed T} _ T{ T} T{ T} T{ Read Advertisement Monitor Features T} T{ Controller Suspend T} _ T{ T} T{ T} T{ Add Advertisement Patterns Monitor T} T{ Controller Resume T} _ T{ T} T{ T} T{ Remove Advertisement Monitor T} T{ Advertisement Monitor Device Found T} _ T{ T} T{ T} T{ T} T{ Advertisement Monitor Device Lost T} _ T{ 1.19 T} T{ 5.10 T} T{ Add Extended Advertising Parameters T} T{ T} _ T{ T} T{ T} T{ Add Extended Advertising Data T} T{ T} _ T{ 1.20 T} T{ 5.11 T} T{ Add Advertisement Patterns Monitor With RSSI Threshold T} T{ T} _ T{ 1.21 T} T{ 6.0 T} T{ Set Mesh Receiver T} T{ Mesh Device Found T} _ T{ T} T{ T} T{ Read Mesh Features T} T{ Mesh Packet Transmit Complete T} _ T{ T} T{ T} T{ Transmit Mesh Packet T} T{ T} _ T{ T} T{ T} T{ Cancel Transmit Mesh Packet T} T{ T} .TE .SH Packet Structures .sp Commands: .INDENT 0.0 .INDENT 3.5 .sp .EX 0 4 8 12 16 22 24 28 31 35 39 43 47 +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ | Command Code | Controller Index | Parameter Length | +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ | | .EE .UNINDENT .UNINDENT .sp Events: .INDENT 0.0 .INDENT 3.5 .sp .EX 0 4 8 12 16 22 24 28 31 35 39 43 47 +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ | Event Code | Controller Index | Parameter Length | +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ | | .EE .UNINDENT .UNINDENT .sp All fields are in little\-endian byte order (least significant byte first). .sp Controller Index can have a special value to indicate that command or event is not related to any controller. Possible values: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x0000\-0xFFFE T} T{ T} _ T{ 0xFFFF T} T{ T} .TE .SH Error Codes .sp The following values have been defined for use with the Command Status and Command Complete events: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Success T} _ T{ 0x01 T} T{ Unknown Command T} _ T{ 0x02 T} T{ Not Connected T} _ T{ 0x03 T} T{ Failed T} _ T{ 0x04 T} T{ Connect Failed T} _ T{ 0x05 T} T{ Authentication Failed T} _ T{ 0x06 T} T{ Not Paired T} _ T{ 0x07 T} T{ No Resources T} _ T{ 0x08 T} T{ Timeout T} _ T{ 0x09 T} T{ Already Connected T} _ T{ 0x0A T} T{ Busy T} _ T{ 0x0B T} T{ Rejected T} _ T{ 0x0C T} T{ Not Supported T} _ T{ 0x0D T} T{ Invalid Parameters T} _ T{ 0x0E T} T{ Disconnected T} _ T{ 0x0F T} T{ Not Powered T} _ T{ 0x10 T} T{ Cancelled T} _ T{ 0x11 T} T{ Invalid Index T} _ T{ 0x12 T} T{ RFKilled T} _ T{ 0x13 T} T{ Already Paired T} _ T{ 0x14 T} T{ Permission Denied T} .TE .sp As a general rule all commands generate the events as specified below, however invalid lengths or unknown commands will always generate a Command Status response (with Unknown Command or Invalid Parameters status). Sending a command with an invalid Controller Index value will also always generate a Command Status event with the Invalid Index status code. .SS Commands .SS Read Management Version Information .INDENT 0.0 .TP .B Command Code 0x0001 .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Version (1 Octets) .TP .B \&... Revision (2 Octets) .UNINDENT .sp This command returns the Management version and revision. Besides, being informational the information can be used to determine whether certain behavior has changed or bugs fixed when interacting with the kernel. .sp This command generates a Command Complete event on success or a Command Status event on failure. .SS Read Management Supported Commands .INDENT 0.0 .TP .B Command Code 0x0002 .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Num_Of_Commands (2 Octets) .TP .B \&... Num_Of_Events (2 Octets) .TP .B \&... Command[] (2 Octets) .TP .B \&... \&...[] .TP .B \&... Event[] (2 Octets) .TP .B \&... \&...[] .UNINDENT .sp This command returns the list of supported Management commands and events. .sp The commands Read Management Version Information and Read management Supported Commands are not included in this list. Both commands are always supported and mandatory. .sp The events Command Status and Command Complete are not included in this list. Both are implicit and mandatory. .sp This command generates a Command Complete event on success or a Command Status event on failure. .SS Read Controller Index List .INDENT 0.0 .TP .B Command Code 0x0003 .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Num_Controllers (2 Octets) .TP .B \&... Controller_Index[] (2 Octets) .UNINDENT .sp This command returns the list of currently known controllers. Controllers added or removed after calling this command can be monitored using the Index Added and Index Removed events. .sp This command generates a Command Complete event on success or a Command Status event on failure. .SS Read Controller Information .INDENT 0.0 .TP .B Command Code 0x0004 .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Address (6 Octets) .TP .B \&... Bluetooth_Version (1 Octet) .TP .B \&... Manufacturer (2 Octets) .TP .B \&... Supported_Settings (4 Octets) .TP .B \&... Current_Settings (4 Octets) .TP .B \&... Class_Of_Device (3 Octets) .TP .B \&... Name (249 Octets) .TP .B \&... Short_Name (11 Octets) .UNINDENT .sp This command is used to retrieve the current state and basic information of a controller. It is typically used right after getting the response to the Read Controller Index List command or an Index Added event. .sp The Address parameter describes the controllers public address and it can be expected that it is set. However in case of single mode Low Energy only controllers it can be 00:00:00:00:00:00. To power on the controller in this case, it is required to configure a static address using Set Static Address command first. .sp If the public address is set, then it will be used as identity address for the controller. If no public address is available, then the configured static address will be used as identity address. .sp In the case of a dual\-mode controller with public address that is configured as Low Energy only device (BR/EDR switched off), the static address is used when set and public address otherwise. .sp If no short name is set the Short_Name parameter will be empty (begin with a nul byte). .sp Current_Settings and Supported_Settings is a bitmask with currently the following available bits: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ Powered T} _ T{ 1 T} T{ Connectable T} _ T{ 2 T} T{ Fast Connectable T} _ T{ 3 T} T{ Discoverable T} _ T{ 4 T} T{ Bondable T} _ T{ 5 T} T{ Link Level Security (Sec. mode 3) T} _ T{ 6 T} T{ Secure Simple Pairing T} _ T{ 7 T} T{ Basic Rate/Enhanced Data Rate T} _ T{ 8 T} T{ High Speed T} _ T{ 9 T} T{ Low Energy T} _ T{ 10 T} T{ Advertising T} _ T{ 11 T} T{ Secure Connections T} _ T{ 12 T} T{ Debug Keys T} _ T{ 13 T} T{ Privacy T} _ T{ 14 T} T{ Controller Configuration T} _ T{ 15 T} T{ Static Address T} _ T{ 16 T} T{ PHY Configuration T} _ T{ 17 T} T{ Wideband Speech T} _ T{ 18 T} T{ Connected Isochronous Stream \- Central T} _ T{ 19 T} T{ Connected Isochronous Stream \- Peripheral T} _ T{ 20 T} T{ Isochronous Broadcaster T} _ T{ 21 T} T{ Synchronized Receiver T} _ T{ 22 T} T{ LL Privacy T} _ T{ 23 T} T{ PAST Sender T} _ T{ 24 T} T{ PAST Receiver T} .TE .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Powered .INDENT 0.0 .TP .B Command Code 0x0005 .TP .B Controller Index .TP .B Command Parameters Powered (1 Octet) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command is used to power on or off a controller. The allowed Powered command parameter values are 0x00 and 0x01. All other values will return Invalid Parameters. .sp If discoverable setting is activated with a timeout, then switching the controller off will expire this timeout and disable discoverable. .sp Settings programmed via Set Advertising and Add/Remove Advertising while the controller was powered off will be activated when powering the controller on. .sp Switching the controller off will permanently cancel and remove all advertising instances with a timeout set, i.e. time limited advertising instances are not being remembered across power cycles. Advertising Removed events will be issued accordingly. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Discoverable .INDENT 0.0 .TP .B Command Code 0x0006 .TP .B Controller Index .TP .B Command Parameters Discoverable (1 Octet) .TP .B \&... Timeout (2 Octets) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command is used to set the discoverable property of a controller. The allowed Discoverable command parameter values are: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Not Discoverable T} _ T{ 0x01 T} T{ General Discoverable T} _ T{ 0x02 (since 1.4) T} T{ Limited Discoverable T} .TE .sp Timeout is the time in seconds and is only meaningful when Discoverable is set to 0x01 or 0x02. Providing a timeout with 0x00 return Invalid Parameters. For 0x02, the timeout value is required. .sp This command is only available for BR/EDR capable controllers (e.g. not for single\-mode LE ones). It will return Not Supported otherwise. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp However using a timeout when the controller is not powered will return Not Powered error. .sp When switching discoverable on and the connectable setting is off it will return Rejected error. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Rejected .TP .B Not Supported .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS Set Connectable .INDENT 0.0 .TP .B Command Code 0x0007 .TP .B Controller Index .TP .B Command Parameters Connectable (1 Octet) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command is used to set the connectable property of a controller. The allowed Connectable command parameter values are 0x00 and 0x01. All other values will return Invalid Parameters. .sp This command is available for BR/EDR, LE\-only and also dual mode controllers. For BR/EDR is changes the page scan setting and for LE controllers it changes the advertising type. For dual mode controllers it affects both settings. .sp For LE capable controllers the connectable setting takes effect when advertising is enabled (peripheral) or when directed advertising events are received (central). .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp When switching connectable off, it will also switch off the discoverable setting. Switching connectable back on will not restore a previous discoverable. It will stay off and needs to be manually switched back on. .sp When switching connectable off, it will expire a discoverable setting with a timeout. .sp This setting does not affect known devices from Add Device command. These devices are always allowed to connect. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Fast Connectable .INDENT 0.0 .TP .B Command Code 0x0008 .TP .B Controller Index .TP .B Command Parameters Enable (1 Octet) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command is used to set the controller into a connectable state where the page scan parameters have been set in a way to favor faster connect times with the expense of higher power consumption. .sp The allowed values of the Enable command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. .sp This command is only available for BR/EDR capable controllers (e.g. not for single\-mode LE ones). It will return Not Supported otherwise. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp The setting will be remembered during power down/up toggles. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B Busy .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Bondable .INDENT 0.0 .TP .B Command Code 0x0009 .TP .B Controller Index .TP .B Command Parameters Bondable (1 Octet) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command is used to set the bondable property of an controller. The allowed values for the Bondable command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp Turning bondable on will not automatically switch the controller into connectable mode. That needs to be done separately. .sp The setting will be remembered during power down/up toggles. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Link Security .INDENT 0.0 .TP .B Command Code 0x000A .TP .B Controller Index .TP .B Command Parameters Link_Security (1 Octet) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command is used to either enable or disable link level security for an controller (also known as Security Mode 3). The allowed values for the Link_Security command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. .sp This command is only available for BR/EDR capable controllers (e.g. not for single\-mode LE ones). It will return Not Supported otherwise. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Secure Simple Pairing .INDENT 0.0 .TP .B Command Code 0x000B .TP .B Controller Index .TP .B Command Parameters Secure_Simple_Pairing (1 Octet) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command is used to enable/disable Secure Simple Pairing support for a controller. The allowed values for the Secure_Simple_Pairing command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. .sp This command is only available for BR/EDR capable controllers supporting the core specification version 2.1 or greater (e.g. not for single\-mode LE controllers or pre\-2.1 ones). .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp In case the controller does not support Secure Simple Pairing, the command will fail regardless with Not Supported error. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set High Speed .INDENT 0.0 .TP .B Command Code 0x000C .TP .B Controller Index .TP .B Command Parameters High_Speed (1 Octet) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command is used to enable/disable Bluetooth High Speed support for a controller. The allowed values for the High_Speed command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. .sp This command is only available for BR/EDR capable controllers (e.g. not for single\-mode LE ones). .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp To enable High Speed support, it is required that Secure Simple Pairing support is enabled first. High Speed support is not possible for connections without Secure Simple Pairing. .sp When switching Secure Simple Pairing off, the support for High Speed will be switched off as well. Switching Secure Simple Pairing back on, will not re\-enable High Speed support. That needs to be done manually. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Low Energy .INDENT 0.0 .TP .B Command Code 0x000D .TP .B Controller Index .TP .B Command Parameters Low_Energy (1 Octet) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command is used to enable/disable Low Energy support for a controller. The allowed values of the Low_Energy command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. .sp This command is only available for LE capable controllers and will yield in a Not Supported error otherwise. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp In case the kernel subsystem does not support Low Energy or the controller does not either, the command will fail regardless. .sp Disabling LE support will permanently disable and remove all advertising instances configured with the Add Advertising command. Advertising Removed events will be issued accordingly. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Device Class .INDENT 0.0 .TP .B Command Code 0x000E .TP .B Controller Index .TP .B Command Parameters Major_Class (1 Octet) .TP .B \&... Minor_Class (1 Octet) .TP .B Return Parameters Class_Of_Device (3 Octets) .UNINDENT .sp This command is used to set the major and minor device class for BR/EDR capable controllers. .sp This command will also implicitly disable caching of pending CoD and EIR updates. .sp This command is only available for BR/EDR capable controllers (e.g. not for single\-mode LE ones). .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp In case the controller is powered off, 0x000000 will be returned for the class of device parameter. And after power on the new value will be announced via class of device changed event. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Local Name .INDENT 0.0 .TP .B Command Code 0x000F .TP .B Controller Index .TP .B Command Parameters Name (249 Octets) .TP .B \&... Short_Name (11 Octets) .TP .B Return Parameters Name (249 Octets) .TP .B \&... Short_Name (11 Octets) .UNINDENT .sp This command is used to set the local name of a controller. The command parameters also include a short name which will be used in case the full name doesn\(aqt fit within EIR/AD data. .sp The name parameters need to always end with a null byte (failure to do so will cause the command to fail). .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp The values of name and short name will be remembered when switching the controller off and back on again. So the name and short name only have to be set once when a new controller is found and will stay until removed. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Add UUID .INDENT 0.0 .TP .B Command Code 0x0010 .TP .B Controller Index .TP .B Command Parameters UUID (16 Octets) .TP .B \&... SVC_Hint (1 Octet) .TP .B Return Parameters Class_Of_Device (3 Octets) .UNINDENT .sp This command is used to add a UUID to be published in EIR data. The accompanied SVC_Hint parameter is used to tell the kernel whether the service class bits of the Class of Device value need modifying due to this UUID. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp In case the controller is powered off, 0x000000 will be returned for the class of device parameter. And after power on the new value will be announced via class of device changed event. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Remove UUID .INDENT 0.0 .TP .B Command Code 0x0011 .TP .B Controller Index .TP .B Command Parameters UUID (16 Octets) .TP .B Return Parameters Class_Of_Device (3 Octets) .UNINDENT .sp This command is used to remove a UUID previously added using the Add UUID command. .sp When the UUID parameter is an empty UUID (16 x 0x00), then all previously loaded UUIDs will be removed. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp In case the controller is powered off, 0x000000 will be returned for the class of device parameter. And after power on the new value will be announced via class of device changed event. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Load Link Keys .INDENT 0.0 .TP .B Command Code 0x0012 .TP .B Controller Index .TP .B Command Parameters Debug_Keys (1 Octet) .TP .B \&... Key_Count (2 Octets) .TP .B \&... Address[] (6 Octets) .TP .B \&... Address_Type[] (1 Octet) .TP .B \&... Key_Type[] (1 Octet) .TP .B \&... Value[] (16 Octets) .TP .B \&... PIN_Length[] (1 Octet) .TP .B \&... \&...[] .TP .B Return Parameters .UNINDENT .sp This command is used to feed the kernel with currently known link keys. The command does not need to be called again upon the receipt of New Link Key events since the kernel updates its list automatically. .sp The Debug_Keys parameter is used to tell the kernel whether to accept the usage of debug keys or not. The allowed values for this parameter are 0x00 and 0x01. All other values will return an Invalid Parameters response. .sp Usage of the Debug_Keys parameter is deprecated and has been replaced with the Set Debug Keys command. When setting the Debug_Keys option via Load Link Keys command it has the same affect as setting it via Set Debug Keys and applies to all keys in the system. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0 T} T{ BR/EDR T} _ T{ 1 T} T{ Reserved (not in use) T} _ T{ 2 T} T{ Reserved (not in use) T} .TE .sp Public and random LE addresses are not valid and will be rejected. .sp Currently defined Key_Type values are: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Combination key T} _ T{ 0x01 T} T{ Local Unit key T} _ T{ 0x02 T} T{ Remote Unit key T} _ T{ 0x03 T} T{ Debug Combination key T} _ T{ 0x04 T} T{ Unauthenticated Combination key from P\-192 T} _ T{ 0x05 T} T{ Authenticated Combination key from P\-192 T} _ T{ 0x06 T} T{ Changed Combination key T} _ T{ 0x07 T} T{ Unauthenticated Combination key from P\-256 T} _ T{ 0x08 T} T{ Authenticated Combination key from P\-256 T} .TE .sp This command can be used when the controller is not powered. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Load Long Term Keys .INDENT 0.0 .TP .B Command Code 0x0013 .TP .B Controller Index .TP .B Command Parameters Key_Count (2 Octets) .TP .B \&... Address[] (6 Octets) .TP .B \&... Address_Type[] (1 Octet) .TP .B \&... Key_Type[] (1 Octet) .TP .B \&... Central[] (1 Octet) .TP .B \&... Encryption_Size[] (1 Octet) .TP .B \&... Encryption_Diversifier[] (2 Octets) .TP .B \&... Random_Number[] (8 Octets) .TP .B \&... Value[] (16 Octets) .TP .B \&... \&...[] .TP .B Return Parameters .UNINDENT .sp This command is used to feed the kernel with currently known (SMP) Long Term Keys. The command does not need to be called again upon the receipt of New Long Term Key events since the kernel updates its list automatically. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Reserved (not in use) T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The provided Address and Address_Type are the identity of a device. So either its public address or static random address. .sp Unresolvable random addresses and resolvable random addresses are not valid and will be rejected. .sp Currently defined Key_Type values are: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Unauthenticated key T} _ T{ 0x01 T} T{ Authenticated key T} .TE .sp This command can be used when the controller is not powered. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Disconnect .INDENT 0.0 .TP .B Command Code 0x0014 .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is used to force the disconnection of a currently connected device. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Not Connected .TP .B Busy .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS Get Connections .INDENT 0.0 .TP .B Command Code 0x0015 .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Connection_Count (2 Octets) .TP .B \&... Address[] (6 Octets) .TP .B \&... Address_Type[] (1 Octet) .TP .B \&... \&...[] .UNINDENT .sp This command is used to retrieve a list of currently connected devices. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp For devices using resolvable random addresses with a known identity resolving key, the Address and Address_Type will contain the identity information. .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS PIN Code Reply .INDENT 0.0 .TP .B Command Code 0x0016 .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... PIN_Length (1 Octet) .TP .B \&... PIN_Code (16 Octets) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is used to respond to a PIN Code request event. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Not Connected .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS PIN Code Negative Reply .INDENT 0.0 .TP .B Command Code 0x0017 .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is used to return a negative response to a PIN Code Request event. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Not Connected .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS Set IO Capability .INDENT 0.0 .TP .B Command Code 0x0018 .TP .B Controller Index .TP .B Command Parameters IO_Capability (1 Octet) .TP .B Return Parameters .UNINDENT .sp This command is used to set the IO Capability used for pairing. The command accepts both SSP and SMP values. .sp Possible values for the IO_Capability parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ DisplayOnly T} _ T{ 0x01 T} T{ DisplayYesNo T} _ T{ 0x02 T} T{ KeyboardOnly T} _ T{ 0x03 T} T{ NoInputNoOutput T} _ T{ 0x04 T} T{ KeyboardDisplay T} .TE .sp Passing a value 0x04 (KeyboardDisplay) will cause the kernel to convert it to 0x01 (DisplayYesNo) in the case of a BR/EDR connection (as KeyboardDisplay is specific to SMP). .sp This command can be used when the controller is not powered. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Pair Device .INDENT 0.0 .TP .B Command Code 0x0019 .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... IO_Capability (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is used to trigger pairing with a remote device. The IO_Capability command parameter is used to temporarily (for this pairing event only) override the global IO Capability (set using the Set IO Capability command). .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp Possible values for the IO_Capability parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ DisplayOnly T} _ T{ 0x01 T} T{ DisplayYesNo T} _ T{ 0x02 T} T{ KeyboardOnly T} _ T{ 0x03 T} T{ NoInputNoOutput T} _ T{ 0x04 T} T{ KeyboardDisplay T} .TE .sp Passing a value 0x04 (KeyboardDisplay) will cause the kernel to convert it to 0x01 (DisplayYesNo) in the case of a BR/EDR connection (as KeyboardDisplay is specific to SMP). .sp The Address and Address_Type of the return parameters will return the identity address if known. In case of resolvable random address given as command parameters and the remote provides an identity resolving key, the return parameters will provide the resolved address. .sp To allow tracking of which resolvable random address changed into which identity address, the New Identity Resolving Key event will be sent before receiving Command Complete event for this command. .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or failure. .sp Reject status is used when requested transport is not enabled. .sp Not Supported status is used if controller is not capable with requested transport. .sp Possible errors: .INDENT 0.0 .TP .B Rejected .TP .B Not Supported .TP .B Connect Failed .TP .B Busy .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .TP .B Already Paired .UNINDENT .SS Cancel Pair Device .INDENT 0.0 .TP .B Command Code 0x001A .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp The Address and Address_Type parameters should match what was given to a preceding Pair Device command. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS Unpair Device .INDENT 0.0 .TP .B Command Code 0x001B .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Disconnect (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp Removes all keys associated with the remote device. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The Disconnect parameter tells the kernel whether to forcefully disconnect any existing connections to the device. It should in practice always be 1 except for some special GAP qualification test\-cases where a key removal without disconnecting is needed. .sp When unpairing a device its link key, long term key and if provided identity resolving key will be purged. .sp For devices using resolvable random addresses where the identity resolving key was available, after this command they will now no longer be resolved. The device will essentially become private again. .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Not Paired .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS User Confirmation Reply .INDENT 0.0 .TP .B Command Code 0x001C .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is used to respond to a User Confirmation Request event. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Not Connected .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS User Confirmation Negative Reply .INDENT 0.0 .TP .B Command Code 0x001D .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is used to return a negative response to a User Confirmation Request event. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Not Connected .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS User Passkey Reply .INDENT 0.0 .TP .B Command Code 0x001E .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Passkey (4 Octets) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is used to respond to a User Confirmation Passkey Request event. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ .INDENT 0.0 .TP .B widths auto .UNINDENT T} T{ T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Not Connected .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS User Passkey Negative Reply .INDENT 0.0 .TP .B Command Code 0x001F .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is used to return a negative response to a User Passkey Request event. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Not Connected .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS Read Local Out Of Band Data .INDENT 0.0 .TP .B Command Code 0x0020 .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Hash_192 (16 Octets) .TP .B \&... Randomizer_192 (16 Octets) .TP .B \&... Hash_256 (16 Octets, Optional) .TP .B \&... Randomizer_256 (16 Octets, Optional) .UNINDENT .sp This command is used to read the local Out of Band data. .sp This command can only be used when the controller is powered. .sp If Secure Connections support is enabled, then this command will return P\-192 versions of hash and randomizer as well as P\-256 versions of both. .sp Values returned by this command become invalid when the controller is powered down. After each power\-cycle it is required to call this command again to get updated values. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Not Supported .TP .B Busy .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS Add Remote Out Of Band Data .INDENT 0.0 .TP .B Command Code 0x0021 .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Hash_192 (16 Octets) .TP .B \&... Randomizer_192 (16 Octets) .TP .B \&... Hash_256 (16 Octets, Optional) .TP .B \&... Randomizer_256 (16 Octets, Optional) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is used to provide Out of Band data for a remote device. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp Provided Out Of Band data is persistent over power down/up toggles. .sp This command also accept optional P\-256 versions of hash and randomizer. If they are not provided, then they are set to zero value. .sp The P\-256 versions of both can also be provided when the support for Secure Connections is not enabled. However in that case they will never be used. .sp To only provide the P\-256 versions of hash and randomizer, it is valid to leave both P\-192 fields as zero values. If Secure Connections is disabled, then of course this is the same as not providing any data at all. .sp When providing data for remote LE devices, then the Hash_192 and Randomizer_192 fields are not used and shell be set to zero. .sp The Hash_256 and Randomizer_256 fields can be used for LE secure connections Out Of Band data. If only LE secure connections data is provided the Hash_P192 and Randomizer_P192 fields can be set to zero. Currently there is no support for providing the Security Manager TK Value for LE legacy pairing. .sp If Secure Connections Only mode has been enabled, then providing Hash_P192 and Randomizer_P192 is not allowed. They are required to be set to zero values. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS Remove Remote Out Of Band Data .INDENT 0.0 .TP .B Command Code 0x0022 .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is used to remove data added using the Add Remote Out Of Band Data command. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp When the Address parameter is 00:00:00:00:00:00, then all previously added data will be removed. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS Start Discovery .INDENT 0.0 .TP .B Command Code 0x0023 .TP .B Controller Index .TP .B Command Parameters Address_Type (1 Octet) .TP .B Return Parameters Address_Type (1 Octet) .UNINDENT .sp This command is used to start the process of discovering remote devices. A Device Found event will be sent for each discovered device. .sp Possible values for the Address_Type parameter are a bit\-wise or of the following bits: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ BR/EDR T} _ T{ 1 T} T{ LE Public T} _ T{ 2 T} T{ LE Random T} .TE .sp By combining these e.g. the following values are possible: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x01 T} T{ BR/EDR T} _ T{ 0x06 T} T{ LE (public & random) T} _ T{ 0x07 T} T{ BR/EDR/LE (interleaved discovery) T} .TE .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Not Supported .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS Stop Discovery .INDENT 0.0 .TP .B Command Code 0x0024 .TP .B Controller Index .TP .B Command Parameters Address_Type (1 Octet) .TP .B Return Parameters Address_Type (1 Octet) .UNINDENT .sp This command is used to stop the discovery process started using the Start Discovery command. .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Rejected .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Confirm Name .INDENT 0.0 .TP .B Command Code 0x0025 .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Name_Known (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is only valid during device discovery and is expected for each Device Found event with the Confirm Name flag set. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The Name_Known parameter should be set to 0x01 if user space knows the name for the device and 0x00 if it doesn\(aqt. If set to 0x00 the kernel will perform a name resolving procedure for the device in question. .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Block Device .INDENT 0.0 .TP .B Command Code 0x0026 .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is used to add a device to the list of devices which should be blocked from being connected to the local controller. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp For Low Energy devices, the blocking of a device takes precedence over auto\-connection actions provided by Add Device. Blocked devices will not be auto\-connected or even reported when found during background scanning. If the controller is connectable direct advertising from blocked devices will also be ignored. .sp Connections created from advertising of the controller will be dropped if the device is blocked. .sp This command can be used when the controller is not powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Unblock Device .INDENT 0.0 .TP .B Command Code 0x0027 .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is used to remove a device from the list of blocked devices (where it was added to using the Block Device command). .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp When the Address parameter is 00:00:00:00:00:00, then all previously blocked devices will be unblocked. .sp This command can be used when the controller is not powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Device ID .INDENT 0.0 .TP .B Command Code 0x0028 .TP .B Controller Index .TP .B Command Parameters Source (2 Octets) .TP .B \&... Vendor (2 Octets) .TP .B \&... Product (2 Octets) .TP .B \&... Version (2 Octets) .TP .B Return Parameters .UNINDENT .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp The Source parameter selects the organization that assigned the Vendor parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x0000 T} T{ Disable Device ID T} _ T{ 0x0001 T} T{ Bluetooth SIG T} _ T{ 0x0002 T} T{ USB Implementer\(aqs Forum T} .TE .sp The information is put into the EIR data. If the controller does not support EIR or if SSP is disabled, this command will still succeed. The information is stored for later use and will survive toggling SSP on and off. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Advertising (since 1.4) .INDENT 0.0 .TP .B Command Code 0x0029 .TP .B Controller Index .TP .B Command Parameters Advertising (1 Octet) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command is used to enable LE advertising on a controller that supports it. The allowed values for the Advertising command parameter are: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Disable advertising T} _ T{ 0x01 T} T{ Enables advertising T} _ T{ 0x02 (since 1.9) T} T{ Enabled advertising in connectable mode T} .TE .sp Using value 0x01 means that when connectable setting is disabled, the advertising happens with undirected non\-connectable advertising packets and a non\-resolvable random address is used. If connectable setting is enabled, then undirected connectable advertising packets and the identity address or resolvable private address are used. .sp LE Devices configured via Add Device command with Action 0x01 have no effect when using Advertising value 0x01 since only the connectable setting is taken into account. .sp To utilize undirected connectable advertising without changing the connectable setting, the value 0x02 can be utilized. It makes the device connectable via LE without the requirement for being connectable on BR/EDR (and/or LE). .sp The value 0x02 should be the preferred mode of operation when implementing peripheral mode. .sp Using this command will temporarily deactivate any configuration made by the Add Advertising command. This command takes precedence. Once a Set Advertising command with value 0x00 is issued any previously made configurations via Add/Remove Advertising, including such changes made while Set Advertising was active, will be re\-enabled. .sp A pre\-requisite is that LE is already enabled, otherwise this command will return a \(dqrejected\(dq response. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Rejected .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set BR/EDR (since 1.4) .INDENT 0.0 .TP .B Command Code 0x002A .TP .B Controller Index .TP .B Command Parameters BR/EDR (1 Octet) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command is used to enable or disable BR/EDR support on a dual\-mode controller. The allowed values for the Advertising command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. .sp A pre\-requisite is that LE is already enabled, otherwise this command will return a \(dqrejected\(dq response. Enabling BR/EDR can be done both when powered on and powered off, however disabling it can only be done when powered off (otherwise the command will again return \(dqrejected\(dq). Disabling BR/EDR will automatically disable all other BR/EDR related settings. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Rejected .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Static Address (since 1.4) .INDENT 0.0 .TP .B Command Code 0x002B .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command allows for setting the static random address. It is only supported on controllers with LE support. The static random address is suppose to be valid for the lifetime of the controller or at least until the next power cycle. To ensure such behavior, setting of the address is limited to when the controller is powered off. .sp The special BDADDR_ANY address (00:00:00:00:00:00) can be used to disable the static address. .sp When a controller has a public address (which is required for all dual\-mode controllers), this address is not used. If a dual\-mode controller is configured as Low Energy only devices (BR/EDR has been switched off), then the static address is used. Only when the controller information reports BDADDR_ANY (00:00:00:00:00:00), it is required to configure a static address first. .sp If privacy mode is enabled and the controller is single mode LE only without a public address, the static random address is used as identity address. .sp The Static Address flag from the current settings can also be used to determine if the configured static address is in use or not. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Rejected .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Scan Parameters (since 1.4) .INDENT 0.0 .TP .B Command Code 0x002C .TP .B Controller Index .TP .B Command Parameters Interval (2 Octets) Window (2 Octets) .TP .B Return Parameters .UNINDENT .sp This command allows for setting the Low Energy scan parameters used for connection establishment and passive scanning. It is only supported on controllers with LE support. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Rejected .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Secure Connections (since 1.5) .INDENT 0.0 .TP .B Command Code 0x002D .TP .B Controller Index .TP .B Command Parameters Secure_Connections (1 Octet) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command is used to enable/disable Secure Connections support for a controller. The allowed values for the Secure_Connections command parameter are 0x00, 0x01 and 0x02. All other values will return Invalid Parameters. .sp The value 0x00 disables Secure Connections, the value 0x01 enables Secure Connections and the value 0x02 enables Secure Connections Only mode. .sp This command is only available for LE capable controllers as well as controllers supporting the core specification version 4.1 or greater. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp In case the controller does not support Secure Connections the command will fail regardless with Not Supported error. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Debug Keys (since 1.5) .INDENT 0.0 .TP .B Command Code 0x002E .TP .B Controller Index .TP .B Command Parameters Debug_Keys (1 Octet) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command is used to tell the kernel whether to accept the usage of debug keys or not. The allowed values for this parameter are: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Discard keys on disconnect T} _ T{ 0x01 T} T{ Discard keys on reboot T} _ T{ 0x02 (since 1.7) T} T{ Discard keys on reboot (SSP debug mode) T} .TE .sp With a value of 0x00 any generated debug key will be discarded as soon as the connection terminates. .sp With a value of 0x01 generated debug keys will be kept and can be used for future connections. However debug keys are always marked as non persistent and should not be stored. This means a reboot or changing the value back to 0x00 will delete them. .sp With a value of 0x02 generated debug keys will be kept and can be used for future connections. This has the same affect as with value 0x01. However in addition this value will also enter the controller mode to generate debug keys for each new pairing. Changing the value back to 0x01 or 0x00 will disable the controller mode for generating debug keys. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Privacy (since 1.5) .INDENT 0.0 .TP .B Command Code 0x002F .TP .B Controller Index .TP .B Command Parameters Privacy (1 Octet) .TP .B \&... Identity_Resolving_Key (16 Octets) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command is used to enable Low Energy Privacy feature using resolvable private addresses. .sp The value 0x00 disables privacy mode, the values 0x01 and 0x02 enable privacy mode. .sp With value 0x01 the kernel will always use the privacy mode. This means resolvable private address is used when the controller is discoverable and also when pairing is initiated. .sp With value 0x02 the kernel will use a limited privacy mode with a resolvable private address except when the controller is bondable and discoverable, in which case the identity address is used. .sp Exposing the identity address when bondable and discoverable or during initiated pairing can be a privacy issue. For dual\-mode controllers this can be neglected since its public address will be exposed over BR/EDR anyway. The benefit of exposing the identity address for pairing purposes is that it makes matching up devices with dual\-mode topology during device discovery now possible. .sp If the privacy value 0x02 is used, then also the GATT database should expose the Privacy Characteristic so that remote devices can determine if the privacy feature is in use or not. .sp When the controller has a public address (mandatory for dual\-mode controllers) it is used as identity address. In case the controller is single mode LE only without a public address, it is required to configure a static random address first. The privacy mode can only be enabled when an identity address is available. .sp The Identity_Resolving_Key is the local key assigned for the local resolvable private address. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Load Identity Resolving Keys (since 1.5) .INDENT 0.0 .TP .B Command Code 0x0030 .TP .B Controller Index .TP .B Command Parameters Key_Count (2 Octets) .TP .B \&... Address[] (6 Octets) .TP .B \&... Address_Type[] (1 Octet) .TP .B \&... Value[] (16 Octets) .TP .B \&... \&...[] .TP .B Return Parameters .UNINDENT .sp This command is used to feed the kernel with currently known identity resolving keys. The command does not need to be called again upon the receipt of New Identity Resolving Key events since the kernel updates its list automatically. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Reserved (not in use) T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The provided Address and Address_Type are the identity of a device. So either its public address or static random address. .sp Unresolvable random addresses and resolvable random addresses are not valid and will be rejected. .sp This command can be used when the controller is not powered. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Get Connection Information (since 1.6) .INDENT 0.0 .TP .B Command Code 0x0031 .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... RSSI (1 Octet) .TP .B \&... TX_Power (1 Octet) .TP .B \&... Max_TX_Power (1 Octet) .UNINDENT .sp This command is used to get connection information. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Reserved (not in use) T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp TX_Power and Max_TX_Power can be set to 127 if values are invalid or unknown. A value of 127 for RSSI indicates that it is not available. .sp This command generates a Command Complete event on success and on failure. In case of failure only Address and Address_Type fields are valid and values of remaining parameters are considered invalid and shall be ignored. .sp Possible errors: .INDENT 0.0 .TP .B Not Connected .TP .B Not Powered .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Get Clock Information (since 1.7) .INDENT 0.0 .TP .B Command Code 0x0032 .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Local_Clock (4 Octets) .TP .B \&... Piconet_Clock (4 Octets) .TP .B \&... Accuracy (2 Octets) .UNINDENT .sp This command is used to get local and piconet clock information. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ Reserved (not in use) T} _ T{ 0x02 T} T{ Reserved (not in use) T} .TE .sp The Accuracy can be set to 0xffff which means the value is unknown. .sp If the Address is set to 00:00:00:00:00:00, then only the Local_Clock field has a valid value. The Piconet_Clock and Accuracy fields are invalid and shall be ignored. .sp This command generates a Command Complete event on success and on failure. In case of failure only Address and Address_Type fields are valid and values of remaining parameters are considered invalid and shall be ignored. .sp Possible errors: .INDENT 0.0 .TP .B Not Connected .TP .B Not Powered .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Add Device (since 1.7) .INDENT 0.0 .TP .B Command Code 0x0033 .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Action (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is used to add a device to the action list. The action list allows scanning for devices and enables incoming connections from known devices. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp Possible values for the Action parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Background scan for device T} _ T{ 0x01 T} T{ Allow incoming connection T} _ T{ 0x02 T} T{ Auto\-connect remote device T} .TE .sp With the Action 0x00, when the device is found, a new Device Found event will be sent indicating this device is available. This action is only valid for LE Public and LE Random address types. .sp With the Action 0x01, the device is allowed to connect. For BR/EDR address type this means an incoming connection. For LE Public and LE Random address types, a connection will be established to devices using directed advertising. If successful a Device Connected event will be sent. .sp With the Action 2, when the device is found, it will be connected and if successful a Device Connected event will be sent. This action is only valid for LE Public and LE Random address types. .sp When a device is blocked using Block Device command, then it is valid to add the device here, but all actions will be ignored until the device is unblocked. .sp Devices added with Action 1 are allowed to connect even if the connectable setting is off. This acts as list of known trusted devices. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Remove Device (since 1.7) .INDENT 0.0 .TP .B Command Code 0x0034 .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is used to remove a device from the action list previously added by using the Add Device command. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp When the Address parameter is 00:00:00:00:00:00, then all previously added devices will be removed. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Load Connection Parameters (since 1.7) .INDENT 0.0 .TP .B Command Code 0x0035 .TP .B Controller Index .TP .B Command Parameters Param_Count (2 Octets) .TP .B \&... Address[] (6 Octets) .TP .B \&... Address_Type[] (1 Octet) .TP .B \&... Min_Connection_Interval[] (2 Octets) .TP .B \&... Max_Connection_Interval[] (2 Octets) .TP .B \&... Connection_Latency[] (2 Octets) .TP .B \&... Supervision_Timeout[] (2 Octets) .TP .B \&... \&...[] .TP .B Return Parameters .UNINDENT .sp This command is used to load connection parameters from several devices into kernel. Currently this is only supported on controllers with Low Energy support. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The provided Address and Address_Type are the identity of a device. So either its public address or static random address. .sp The Min_Connection_Interval, Max_Connection_Interval, Connection_Latency and Supervision_Timeout parameters should be configured as described in Core 4.1 spec, Vol 2, 7.8.12. .sp This command can be used when the controller is not powered. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .TP .B Not Supported .UNINDENT .SS Read Unconfigured Controller Index List (since 1.7) .INDENT 0.0 .TP .B Command Code 0x0036 .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Num_Controllers (2 Octets) .TP .B \&... Controller_Index[i] (2 Octets) .UNINDENT .sp This command returns the list of currently unconfigured controllers. Unconfigured controllers added after calling this command can be monitored using the Unconfigured Index Added event. .sp An unconfigured controller can either move to a configured state by indicating Unconfigured Index Removed event followed by an Index Added event; or it can be removed from the system which would be indicated by the Unconfigured Index Removed event. .sp Only controllers that require configuration will be listed with this command. A controller that is fully configured will not be listed even if it supports configuration changes. .sp This command generates a Command Complete event on success or a Command Status event on failure. .SS Read Controller Configuration Information (since 1.7) .INDENT 0.0 .TP .B Command Code 0x0037 .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Manufacturer (2 Octets) .TP .B \&... Supported_Options (4 Octets) .TP .B \&... Missing_Options (4 Octets) .UNINDENT .sp This command is used to retrieve the supported configuration options of a controller and the missing configuration options. .sp The missing options are required to be configured before the controller is considered fully configured and ready for standard operation. The command is typically used right after getting the response to Read Unconfigured Controller Index List command or Unconfigured Index Added event. .sp Supported_Options and Missing_Options is a bitmask with currently the following available bits: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ External configuration T} _ T{ 1 T} T{ Bluetooth public address configuration T} .TE .sp It is valid to call this command on controllers that do not require any configuration. It is possible that a fully configured controller offers additional support for configuration. .sp For example a controller may contain a valid Bluetooth public device address, but also allows to configure it from the host stack. In this case the general support for configurations will be indicated by the Controller Configuration settings. For controllers where no configuration options are available that setting option will not be present. .sp When all configurations have been completed and as a result the Missing_Options mask would become empty, then the now ready controller will be announced via Index Added event. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set External Configuration (since 1.7) .INDENT 0.0 .TP .B Command Code 0x0038 .TP .B Controller Index .TP .B Command Parameters Configuration (1 Octet) .TP .B Return Parameters Missing_Options (4 Octets) .UNINDENT .sp This command allows to change external configuration option to indicate that a controller is now configured or unconfigured. .sp The value 0x00 sets unconfigured state and the value 0x01 sets configured state of the controller. .sp It is not mandatory that this configuration option is provided by a controller. If it is provided, the configuration has to happen externally using user channel operation or via vendor specific methods. .sp Setting this option and when Missing_Options returns zero, this means that the controller will switch to configured state and it can be expected that it will be announced via Index Added event. .sp Wrongly configured controllers might still cause an error when trying to power them via Set Powered command. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Rejected .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Public Address (since 1.7) .INDENT 0.0 .TP .B Command Code 0x0039 .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B Return Parameters Missing_Options (4 Octets) .UNINDENT .sp This command allows configuration of public address. Since a vendor specific procedure is required, this command might not be supported by all controllers. Actually most likely only a handful embedded controllers will offer support for this command. .sp When the support for Bluetooth public address configuration is indicated in the supported options mask, then this command can be used to configure the public address. .sp It is only possible to configure the public address when the controller is powered off. .sp For an unconfigured controller and when Missing_Options returns an empty mask, this means that a Index Added event for the now fully configured controller can be expected. .sp For a fully configured controller, the current controller index will become invalid and an Unconfigured Index Removed event will be sent. Once the address has been successfully changed an Index Added event will be sent. There is no guarantee that the controller index stays the same. .sp All previous configured parameters and settings are lost when this command succeeds. The controller has to be treated as new one. Use this command for a fully configured controller only when you really know what you are doing. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Rejected .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Start Service Discovery (since 1.8) .INDENT 0.0 .TP .B Command Code 0x003a .TP .B Controller Index .TP .B Command Parameters Address_Type (1 Octet) .TP .B \&... RSSI_Threshold (1 Octet) .TP .B \&... UUID_Count (2 Octets) .TP .B \&... UUID[i] (16 Octets) .TP .B Return Parameters Address_Type (1 Octet) .UNINDENT .sp This command is used to start the process of discovering remote devices with a specific UUID. A Device Found event will be sent for each discovered device. .sp Possible values for the Address_Type parameter are a bit\-wise or of the following bits: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ BR/EDR T} _ T{ 1 T} T{ LE Public T} _ T{ 2 T} T{ LE Random T} .TE .sp By combining these e.g. the following values are possible: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x01 T} T{ BR/EDR T} _ T{ 0x06 T} T{ LE (public & random) T} _ T{ 0x07 T} T{ BR/EDR/LE (interleaved discovery) T} .TE .sp The service discovery uses active scanning for Low Energy scanning and will search for UUID in both advertising data and scan response data. .sp Found devices that have a RSSI value smaller than RSSI_Threshold are not reported via DeviceFound event. Setting a value of 127 will cause all devices to be reported. .sp The list of UUIDs identifies a logical OR. Only one of the UUIDs have to match to cause a DeviceFound event. Providing an empty list of UUIDs with Num_UUID set to 0 which means that DeviceFound events are send out for all devices above the RSSI_Threshold. .sp In case RSSI_Threshold is set to 127 and UUID_Count is 0, then this command behaves exactly the same as Start Discovery. .sp When the discovery procedure starts the Discovery event will notify this similar to Start Discovery. .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Not Supported .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS Read Local Out Of Band Extended Data (since 1.9) .INDENT 0.0 .TP .B Command Code 0x003b .TP .B Controller Index .TP .B Command Parameters Address_Type (1 Octet) .TP .B Return Parameters Address_Type (1 Octet) .TP .B \&... EIR_Data_Length (2 Octets) .TP .B \&... EIR_Data (0\-65535 Octets) .UNINDENT .sp This command is used to read the local Out of Band data information and provide them encoded as extended inquiry response information or advertising data. .sp Possible values for the Address_Type parameter are a bit\-wise or of the following bits: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ BR/EDR T} _ T{ 1 T} T{ LE Public T} _ T{ 2 T} T{ LE Random T} .TE .sp By combining these e.g. the following values are possible: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x01 T} T{ BR/EDR T} _ T{ 0x06 T} T{ LE (public & random) T} _ T{ 0x07 T} T{ Reserved (not in use) T} .TE .sp For BR/EDR controller (Address_Type 1) the returned information will contain the following information: .INDENT 0.0 .INDENT 3.5 Class of Device Simple Pairing Hash C\-192 (optional) Simple Pairing Randomizer R\-192 (optional) Simple Pairing Hash C\-256 (optional) Simple Pairing Randomizer R\-256 (optional) Service Class UUID (optional) Bluetooth Local Name (optional) .UNINDENT .UNINDENT .sp The Simple Pairing Hash C\-256 and Simple Pairing Randomizer R\-256 fields are only included when secure connections has been enabled. .sp The Device Address (BD_ADDR) is not included in the EIR_Data and needs to be taken from controller information. .sp For LE controller (Address_Type 6) the returned information will contain the following information: .INDENT 0.0 .INDENT 3.5 LE Bluetooth Device Address LE Role LE Secure Connections Confirmation Value (optional) LE Secure Connections Random Value (optional) Appearance (optional) Local Name (optional) Flags .UNINDENT .UNINDENT .sp The LE Secure Connections Confirmation Value and LE Secure Connections Random Value fields are only included when secure connections has been enabled. .sp The Security Manager TK Value from the Bluetooth specification can not be provided by this command. The Out Of Band information here are for asymmetric exchanges based on Diffie\-Hellman key exchange. The Security Manager TK Value is a symmetric random number that has to be acquired and agreed upon differently. .sp The returned information from BR/EDR controller and LE controller types are not related to each other. Once they have been used over an Out Of Band link, a new set of information shall be requested. .sp When Secure Connections Only mode has been enabled, then the fields for Simple Pairing Hash C\-192 and Simple Pairing Randomizer R\-192 are not returned. Only the fields for the strong secure connections pairing are included. .sp This command can only be used when the controller is powered. .sp Values returned by this command become invalid when the controller is powered down. After each power\-cycle it is required to call this command again to get updated information. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Not Supported .TP .B Busy .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS Read Extended Controller Index List (since 1.9) .INDENT 0.0 .TP .B Command Code 0x003c .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Num_Controllers (2 Octets) .TP .B \&... Controller_Index[i] (2 Octets) .TP .B \&... Controller_Type[i] (1 Octet) .TP .B \&... Controller_Bus[i] (1 Octet) .UNINDENT .sp This command returns the list of currently known controllers. It includes configured, unconfigured and alternate controllers. .sp Controllers added or removed after calling this command can be monitored using the Extended Index Added and Extended Index Removed events. .sp The existing Index Added, Index Removed, Unconfigured Index Added and Unconfigured Index Removed are no longer sent after this command has been used at least once. .sp Instead of calling Read Controller Index List and Read Unconfigured Controller Index List, this command combines all the information and can be used to retrieve the controller list. .sp The Controller_Type parameter has these values: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Primary Controller (BR/EDR and/or LE) T} _ T{ 0x01 T} T{ Unconfigured Controller (BR/EDR and/or LE) T} _ T{ 0x02 T} T{ Alternate MAC/PHY Controller (AMP) T} .TE .sp The 0x00 and 0x01 types indicate a primary BR/EDR and/or LE controller. The difference is just if they need extra configuration or if they are fully configured. .sp Controllers in configured state will be listed as 0x00 and controllers in unconfigured state will be listed as 0x01. A controller that is fully configured and supports configuration changes will be listed as 0x00. .sp Alternate MAC/PHY controllers will be listed as 0x02. They do not support the difference between configured and unconfigured state. .sp The Controller_Bus parameter has these values: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Virtual T} _ T{ 0x01 T} T{ USB T} _ T{ 0x02 T} T{ PCMCIA T} _ T{ 0x03 T} T{ UART T} _ T{ 0x04 T} T{ RS232 T} _ T{ 0x05 T} T{ PCI T} _ T{ 0x06 T} T{ SDIO T} _ T{ 0x07 T} T{ SPI T} _ T{ 0x08 T} T{ I2C T} _ T{ 0x09 T} T{ SMD T} _ T{ 0x0A T} T{ VIRTIO T} _ T{ 0x0B T} T{ IPC T} .TE .sp Controllers marked as RAW only operation are currently not listed by this command. .sp This command generates a Command Complete event on success or a Command Status event on failure. .SS Read Advertising Features (since 1.9) .INDENT 0.0 .TP .B Command Code 0x003d .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Supported_Flags (4 Octets) .TP .B \&... Max_Adv_Data_Len (1 Octet) .TP .B \&... Max_Scan_Rsp_Len (1 Octet) .TP .B \&... Max_Instances (1 Octet) .TP .B \&... Num_Instances (1 Octet) .TP .B \&... Instance[] (1 Octet) .TP .B \&... \&...[] .UNINDENT .sp This command is used to read the advertising features supported by the controller and stack. .sp With the Supported_Flags field the possible values for the Flags field in Add Advertising command provided: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ Switch into Connectable mode T} _ T{ 1 T} T{ Advertise as Discoverable T} _ T{ 2 T} T{ Advertise as Limited Discoverable T} _ T{ 3 T} T{ Add Flags field to Adv_Data T} _ T{ 4 T} T{ Add TX Power field to Adv_Data T} _ T{ 5 (since 1.14) T} T{ Add Appearance field to Scan_Rsp T} _ T{ 6 (since 1.14) T} T{ Add Local Name in Scan_Rsp T} _ T{ 7 (since 1.15) T} T{ Secondary Channel with LE 1M T} _ T{ 8 (since 1.15) T} T{ Secondary Channel with LE 2M T} _ T{ 9 (since 1.15) T} T{ Secondary Channel with LE Coded T} .TE .sp The Flags bit 0 indicates support for connectable advertising and for switching to connectable advertising independent of the connectable global setting. When this flag is not supported, then the global connectable setting determines if undirected connectable, undirected scannable or undirected non\-connectable advertising is used. It also determines the use of non\-resolvable random address versus identity address or resolvable private address. .sp The Flags bit 1 indicates support for advertising with discoverable mode enabled. Users of this flag will decrease the Max_Adv_Data_Len by 3 octets. In this case the advertising data flags are managed and added in front of the provided advertising data. .sp The Flags bit 2 indicates support for advertising with limited discoverable mode enabled. Users of this flag will decrease the Max_Adv_Data_Len by 3 octets. In this case the advertising data flags are managed and added in front of the provided advertising data. .sp The Flags bit 3 indicates support for automatically keeping the Flags field of the advertising data updated. Users of this flag will decrease the Max_Adv_Data_Len by 3 octets and need to keep that in mind. The Flags field will be added in front of the advertising data provided by the user. Note that with Flags bit 1 and Flags bit 2, this one will be implicitly used even if it is not marked as supported. .sp The Flags bit 4 indicates support for automatically adding the TX Power value to the advertising data. Users of this flag will decrease the Max_Adv_Data_Len by 3 octets. The TX Power field will be added at the end of the user provided advertising data. If the controller does not support TX Power information, then this bit will not be set. .sp The Flags bit 5 indicates support for automatically adding the Appearance value to the scan response data. Users of this flag will decrease the Max_Scan_Rsp_len by 4 octets. The Appearance field will be added in front of the scan response data provided by the user. If the appearance value is not supported, then this bit will not be set. .sp The Flags bit 6 indicates support for automatically adding the Local Name value to the scan response data. This flag indicates an opportunistic approach for the Local Name. If enough space in the scan response data is available, it will be added. If the space is limited a short version or no name information. The Local Name will be added at the end of the scan response data. .sp The Flags bit 7 indicates support for advertising in secondary channel in LE 1M PHY. .sp The Flags bit 8 indicates support for advertising in secondary channel in LE 2M PHY. Primary channel would be on 1M. .sp The Flags bit 9 indicates support for advertising in secondary channel in LE CODED PHY. .sp The valid range for Instance identifiers is 1\-254. The value 0 is reserved for internal use and the value 255 is reserved for future extensions. However the Max_Instances value for indicating the number of supported Instances can be also 0 if the controller does not support any advertising. .sp The Max_Adv_Data_Len and Max_Scan_Rsp_Len provides extra information about the maximum length of the data fields. For now this will always return the value 31. Different flags however might decrease the actual available length in these data fields. .sp With Num_Instances and Instance array the currently occupied Instance identifiers can be retrieved. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Add Advertising (since 1.9) .INDENT 0.0 .TP .B Command Code 0x003e .TP .B Controller Index .TP .B Command Parameters Instance (1 Octet) .TP .B \&... Flags (4 Octets) .TP .B \&... Duration (2 Octets) .TP .B \&... Timeout (2 Octets) .TP .B \&... Adv_Data_Len (1 Octet) .TP .B \&... Scan_Rsp_Len (1 Octet) .TP .B \&... Adv_Data (0\-255 Octets) .TP .B \&... Scan_Rsp (0\-255 Octets) .TP .B Return Parameters Instance (1 Octet) .UNINDENT .sp This command is used to configure an advertising instance that can be used to switch a Bluetooth Low Energy controller into advertising mode. .sp Added advertising information with this command will not be visible immediately if advertising is enabled via the Set Advertising command. The usage of the Set Advertising command takes precedence over this command. Instance information is stored and will be advertised once advertising via Set Advertising has been disabled. .sp The Instance identifier is a value between 1 and the number of supported instances. The value 0 is reserved. .sp With the Flags value the type of advertising is controlled and the following flags are defined: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ Switch into Connectable mode T} _ T{ 1 T} T{ Advertise as Discoverable T} _ T{ 2 T} T{ Advertise as Limited Discoverable T} _ T{ 3 T} T{ Add Flags field to Adv_Data T} _ T{ 4 T} T{ Add TX Power field to Adv_Data T} _ T{ 5 (since 1.14) T} T{ Add Appearance field to Scan_Rsp T} _ T{ 6 (since 1.14) T} T{ Add Local Name in Scan_Rsp T} _ T{ 7 (since 1.15) T} T{ Secondary Channel with LE 1M T} _ T{ 8 (since 1.15) T} T{ Secondary Channel with LE 2M T} _ T{ 9 (since 1.15) T} T{ Secondary Channel with LE Coded T} .TE .sp When the connectable flag is set, then the controller will use undirected connectable advertising. The value of the connectable setting can be overwritten this way. This is useful to switch a controller into connectable mode only for LE operation. This is similar to the mode 0x02 from the Set Advertising command. .sp When the connectable flag is not set, then the controller will use advertising based on the connectable setting. When using non\-connectable or scannable advertising, the controller will be programmed with a non\-resolvable random address. When the system is connectable, then the identity address or resolvable private address will be used. .sp Using the connectable flag is useful for peripheral mode support where BR/EDR (and/or LE) is controlled by Add Device. This allows making the peripheral connectable without having to interfere with the global connectable setting. .sp If Scan_Rsp_Len is zero and connectable flag is not set and the global connectable setting is off, then non\-connectable advertising is used. If Scan_Rsp_Len is larger than zero and connectable flag is not set and the global advertising is off, then scannable advertising is used. This small difference is supported to provide less air traffic for devices implementing broadcaster role. .sp Secondary channel flags can be used to advertise in secondary channel with the corresponding PHYs. These flag bits are mutually exclusive and setting multiple will result in Invalid Parameter error. Choosing either LE 1M or LE 2M will result in using extended advertising on the primary channel with LE 1M and the respectively LE 1M or LE 2M on the secondary channel. Choosing LE Coded will result in using extended advertising on the primary and secondary channels with LE Coded. Choosing none of these flags will result in legacy advertising. .sp The Duration parameter configures the length of an Instance. The value is in seconds. .sp A value of 0 indicates a default value is chosen for the Duration. The default is 2 seconds. .sp If only one advertising Instance has been added, then the Duration value will be ignored. It only applies for the case where multiple Instances are configured. In that case every Instance will be available for the Duration time and after that it switches to the next one. This is a simple round\-robin based approach. .sp The Timeout parameter configures the life\-time of an Instance. In case the value 0 is used it indicates no expiration time. If a timeout value is provided, then the advertising Instance will be automatically removed when the timeout passes. The value for the timeout is in seconds. Powering down a controller will invalidate all advertising Instances and it is not possible to add a new Instance with a timeout when the controller is powered down. .sp When a Timeout is provided, then the Duration subtracts from the actual Timeout value of that Instance. For example an Instance with Timeout of 5 and Duration of 2 will be scheduled exactly 3 times, twice with 2 seconds and once with one second. Other Instances have no influence on the Timeout. .sp Re\-adding an already existing instance (i.e. issuing the Add Advertising command with an Instance identifier of an existing instance) will update that instance\(aqs configuration. .sp An instance being added or changed while another instance is being advertised will not be visible immediately but only when the new/changed instance is being scheduled by the round robin advertising algorithm. .sp Changes to an instance that is currently being advertised will cancel that instance and switch to the next instance. The changes will be visible the next time the instance is scheduled for advertising. In case a single instance is active, this means that changes will be visible right away. .sp A pre\-requisite is that LE is already enabled, otherwise this command will return a \(dqrejected\(dq response. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B Rejected .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Remove Advertising (since 1.9) .INDENT 0.0 .TP .B Command Code 0x003f .TP .B Controller Index .TP .B Command Parameters Instance (1 Octet) .TP .B Return Parameters Instance (1 Octet) .UNINDENT .sp This command is used to remove an advertising instance that can be used to switch a Bluetooth Low Energy controller into advertising mode. .sp When the Instance parameter is zero, then all previously added advertising Instances will be removed. .sp Removing advertising information with this command will not be visible as long as advertising is enabled via the Set Advertising command. The usage of the Set Advertising command takes precedence over this command. Changes to Instance information are stored and will be advertised once advertising via Set Advertising has been disabled. .sp Removing an instance while it is being advertised will immediately cancel the instance, even when it has been advertised less then its configured Timeout or Duration. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Get Advertising Size Information (since 1.11) .INDENT 0.0 .TP .B Command Code 0x0040 .TP .B Controller Index .TP .B Command Parameters Instance (1 Octet) .TP .B \&... Flags (4 Octets) .TP .B Return Parameters Instance (1 Octet) .TP .B \&... Flags (4 Octets) .TP .B \&... Max_Adv_Data_Len (1 Octet) .TP .B \&... Max_Scan_Rsp_Len (1 Octet) .UNINDENT .sp The Read Advertising Features command returns the overall maximum size of advertising data and scan response data fields. That size is valid when no Flags are used. However when certain Flags are used, then the size might decrease. This command can be used to request detailed information about the maximum available size. .sp The following Flags values are defined: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0 T} T{ Switch into Connectable mode T} _ T{ 1 T} T{ Advertise as Discoverable T} _ T{ 2 T} T{ Advertise as Limited Discoverable T} _ T{ 3 T} T{ Add Flags field to Adv_Data T} _ T{ 4 T} T{ Add TX Power field to Adv_Data T} _ T{ 5 T} T{ Add Appearance field to Scan_Rsp T} _ T{ 6 T} T{ Add Local Name in Scan_Rsp T} .TE .sp To get accurate information about the available size, the same Flags values should be used with the Add Advertising command. .sp The Max_Adv_Data_Len and Max_Scan_Rsp_Len fields provide information about the maximum length of the data fields for the given Flags values. When the Flags field is zero, then these fields would contain the same values as Read Advertising Features. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Start Limited Discovery (since 1.11) .INDENT 0.0 .TP .B Command Code 0x0041 .TP .B Controller Index .TP .B Command Parameters Address_Type (1 Octet) .TP .B Return Parameters Address_Type (1 Octet) .UNINDENT .sp This command is used to start the process of discovering remote devices using the limited discovery procedure. A Device Found event will be sent for each discovered device. .sp Possible values for the Address_Type parameter are a bit\-wise or of the following bits: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ BR/EDR T} _ T{ 1 T} T{ LE Public T} _ T{ 2 T} T{ LE Random T} .TE .sp By combining these e.g. the following values are possible: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x01 T} T{ BR/EDR T} _ T{ 0x06 T} T{ LE (public & random) T} _ T{ 0x07 T} T{ BR/EDR/LE (interleaved discovery) T} .TE .sp The limited discovery uses active scanning for Low Energy scanning and will search for devices with the limited discoverability flag configured. On BR/EDR it uses LIAC and filters on the limited discoverability flag of the class of device. .sp When the discovery procedure starts the Discovery event will notify this similar to Start Discovery. .sp This command can only be used when the controller is powered. .sp This command generates a Command Complete event on success or failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Not Supported .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS Read Extended Controller Information (since 1.14) .INDENT 0.0 .TP .B Command Code 0x0042 .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Address (6 Octets) .TP .B \&... Bluetooth_Version (1 Octet) .TP .B \&... Manufacturer (2 Octets) .TP .B \&... Supported_Settings (4 Octets) .TP .B \&... Current_Settings (4 Octets) .TP .B \&... EIR_Data_Length (2 Octets) .TP .B \&... EIR_Data (0\-65535 Octets) .UNINDENT .sp This command is used to retrieve the current state and basic information of a controller. It is typically used right after getting the response to the Read Controller Index List command or an Index Added event (or its extended counterparts). .sp The Address parameter describes the controllers public address and it can be expected that it is set. However in case of single mode Low Energy only controllers it can be 00:00:00:00:00:00. To power on the controller in this case, it is required to configure a static address using Set Static Address command first. .sp If the public address is set, then it will be used as identity address for the controller. If no public address is available, then the configured static address will be used as identity address. .sp In the case of a dual\-mode controller with public address that is configured as Low Energy only device (BR/EDR switched off), the static address is used when set and public address otherwise. .sp Current_Settings and Supported_Settings is a bitmask with currently the following available bits: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ Powered T} _ T{ 1 T} T{ Connectable T} _ T{ 2 T} T{ Fast Connectable T} _ T{ 3 T} T{ Discoverable T} _ T{ 4 T} T{ Bondable T} _ T{ 5 T} T{ Link Level Security (Sec. mode 3) T} _ T{ 6 T} T{ Secure Simple Pairing T} _ T{ 7 T} T{ Basic Rate/Enhanced Data Rate T} _ T{ 8 T} T{ High Speed T} _ T{ 9 T} T{ Low Energy T} _ T{ 10 T} T{ Advertising T} _ T{ 11 T} T{ Secure Connections T} _ T{ 12 T} T{ Debug Keys T} _ T{ 13 T} T{ Privacy T} _ T{ 14 T} T{ Controller Configuration T} _ T{ 15 T} T{ Static Address T} _ T{ 16 T} T{ PHY Configuration T} _ T{ 17 T} T{ Wideband Speech T} _ T{ 18 T} T{ Connected Isochronous Stream \- Central T} _ T{ 19 T} T{ Connected Isochronous Stream \- Peripheral T} .TE .sp The EIR_Data field contains information about class of device, local name and other values. Not all of them might be present. For example a Low Energy only device does not contain class of device information. .sp When any of the values in the EIR_Data field changes, the event Extended Controller Information Changed will be used to inform clients about the updated information. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Appearance (since 1.14) .INDENT 0.0 .TP .B Command Code 0x0043 .TP .B Controller Index .TP .B Command Parameters Appearance (2 Octets) .TP .B Return Parameters .UNINDENT .sp This command is used to set the appearance value of a controller. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp The value of appearance will be remembered when switching the controller off and back on again. So the appearance only have to be set once when a new controller is found and will stay until removed. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp This command is only available for LE capable controllers. .sp It will return Not Supported otherwise. .sp Possible errors: .INDENT 0.0 .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Get PHY Configuration (since 1.15) .INDENT 0.0 .TP .B Command Code 0x0044 .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Supported_PHYs (4 Octet) .TP .B \&... Configurable_PHYs (4 Octets) .TP .B \&... Selected_PHYs (4 Octet) .UNINDENT .sp The PHYs parameters are a bitmask with currently the following available bits: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ BR 1M 1\-Slot T} _ T{ 1 T} T{ BR 1M 3\-Slot T} _ T{ 2 T} T{ BR 1M 5\-Slot T} _ T{ 3 T} T{ EDR 2M 1\-Slot T} _ T{ 4 T} T{ EDR 2M 3\-Slot T} _ T{ 5 T} T{ EDR 2M 5\-Slot T} _ T{ 6 T} T{ ERDR 3M 1\-Slot T} _ T{ 7 T} T{ EDR 3M 3\-Slot T} _ T{ 8 T} T{ EDR 3M 5\-Slot T} _ T{ 9 T} T{ LE 1M TX T} _ T{ 10 T} T{ LE 1M RX T} _ T{ 11 T} T{ LE 2M TX T} _ T{ 12 T} T{ LE 2M RX T} _ T{ 13 T} T{ LE Coded TX T} _ T{ 14 T} T{ LE Coded RX T} .TE .sp If BR/EDR is supported, then BR 1M 1\-Slot is supported by default and can also not be deselected. If LE is supported, then LE 1M TX and LE 1M RX are supported by default. .sp Disabling BR/EDR completely or respectively LE has no impact on the PHY configuration. It is remembered over power cycles. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set PHY Configuration (since 1.15) .INDENT 0.0 .TP .B Command Code 0x0045 .TP .B Controller Index .TP .B Command Parameters Selected_PHYs (4 Octet) .TP .B Return Parameters .UNINDENT .sp This command is used to set the default PHY to the controller. .sp This will be stored and used for all the subsequent scanning and connection initiation. .sp The list of supported PHYs can be retrieved via the Get PHY Configuration command. Selecting unsupported or deselecting default PHYs will result in an Invalid Parameter error. .sp This can be called at any point to change the Selected PHYs. .sp Refer Get PHY Configuration command for PHYs parameter. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Load Blocked Keys (since 1.15) .INDENT 0.0 .TP .B Command Code 0x0046 .TP .B Controller Index .TP .B Command Parameters Key_Count (2 Octets) .TP .B \&... Key_Type[] (1 Octet) .TP .B \&... Value[] (16 Octets) .TP .B \&... \&...[] .TP .B Return Parameters .UNINDENT .sp This command is used to feed the kernel a list of keys that are known to be vulnerable. .sp If the pairing procedure produces any of these keys, they will be silently dropped and any attempt to enable encryption rejected. .sp Currently defined Key_Type values are: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Link Key (BR/EDR) T} _ T{ 0x01 T} T{ Long Term Key (LE) T} _ T{ 0x02 T} T{ Identity Resolving Key (LE) T} .TE .sp This command can be used when the controller is not powered. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Wideband Speech (since 1.16) .INDENT 0.0 .TP .B Command Code 0x0047 .TP .B Controller Index .TP .B Command Parameters Wideband_Speech (1 Octet) .TP .B Return Parameters Current_Settings (4 Octets) .UNINDENT .sp This command is used to enable/disable Wideband Speech support for a controller. The allowed values for the Wideband_Speech command parameter are 0x00 and 0x01. All other values will return Invalid Parameters. .sp The value 0x00 disables Wideband Speech, the value 0x01 enables Wideband Speech. .sp This command is only available for BR/EDR capable controllers and require controller specific support. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp In case the controller does not support Wideband Speech the command will fail regardless with Not Supported error. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Busy .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Read Controller Capabilities (since 1.17) .INDENT 0.0 .TP .B Command Code 0x0048 .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Capabilities_Data_Length (2 Octets) .TP .B \&... Capabilities_Data[] (0\-65535 Octets) .UNINDENT .sp This command is used to retrieve the supported capabilities by the controller or the host stack. .sp The Capabilities_Data_Length and Capabilities_Data parameters provide a list of security settings, features and information. It uses the same format as EIR_Data, but with the namespace defined here. .TS box center; l|l. T{ Data Type T} T{ Name T} _ T{ 0x01 T} T{ Flags T} _ T{ 0x02 T} T{ Max Encryption Key Size (BR/EDR) T} _ T{ 0x03 T} T{ Max Encryption Key Size (LE) T} _ T{ 0x04 T} T{ Supported Tx Power (LE) T} .TE .sp Flags (data type 0x01) .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ Remote public key validation (BR/EDR) T} _ T{ 1 T} T{ Remote public key validation (LE) T} _ T{ 2 T} T{ Encryption key size enforcement (BR/EDR) T} _ T{ 3 T} T{ Encryption key size enforcement (LE) T} .TE .sp Max Encryption Key Size (data types 0x02 and 0x03) .INDENT 0.0 .INDENT 3.5 When the field is present, then it provides 1 Octet value indicating the max encryption key size. If the field is not present, then it is unknown what the max encryption key size of the controller or host is in use. .UNINDENT .UNINDENT .sp Supported LE Tx Power (data type 0x04) .INDENT 0.0 .INDENT 3.5 When present, this 2\-octet field provides the min and max LE Tx power supported by the controller, respectively, as reported by the LE Read Transmit Power HCI command. If this field is not available, it indicates that the LE Read Transmit Power HCI command was not available. .UNINDENT .UNINDENT .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Read Experimental Features Information (since 1.17) .INDENT 0.0 .TP .B Command Code 0x0049 .TP .B Controller Index or .TP .B Command Parameters .TP .B Return Parameters Feature_Count (2 Octets) .TP .B \&... UUID[] (16 Octets) .TP .B \&... Flags[] (4 Octets) .TP .B \&... \&...[] .UNINDENT .sp This command is used to retrieve the supported experimental features by the host stack. .sp The UUID values are not defined here. They can change over time and are on purpose not stable. Features that mature will be removed at some point. The mapping of feature UUID to the actual functionality of a given feature is out of scope here. .sp The following bits are defined for the Flags parameter: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ Feature active T} _ T{ 1 T} T{ Causes change in supported settings T} .TE .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Experimental Feature (since 1.17) .INDENT 0.0 .TP .B Command Code 0x004a .TP .B Controller Index or .TP .B Command Parameters UUID (16 Octets) .TP .B \&... Action (1 Octet) .TP .B Return Parameters UUID (16 Octets) .TP .B \&... Flags (4 Octets) .UNINDENT .sp This command is used to change the setting of an experimental feature of the host stack. .sp The UUID value must be a supported value returned from the Read Experimental Features Information command. .sp The Action parameter is UUID specific, but in most cases it will be just a simple on/off toggle with these values: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0x00 T} T{ Disable feature T} _ T{ 0x01 T} T{ Enable feature T} .TE .sp It depends on the feature if the command can be used when the controller is powered up. See Flags parameter of Read Experimental Features Information command for details if the controller has to be powered down first. .sp The following bits are defined for the Flags return parameter: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ Feature active T} _ T{ 1 T} T{ Supported settings changed T} .TE .sp When a feature causes the change of supported settings, then it is a good idea to re\-read the controller information. .sp When the UUID parameter is an empty UUID (16 x 0x00), then all experimental features will be deactivated. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Not Powered .TP .B Invalid Index .UNINDENT .SS Read Default System Configuration (since 1.18) .INDENT 0.0 .TP .B Command Code 0x004b .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Parameter_Type[] (2 Octet) .TP .B \&... Value_Length[] (1 Octet) .TP .B \&... Value (0\-255[] Octets) .TP .B \&... \&...[] .UNINDENT .sp This command is used to read a list of default controller parameters. .sp Currently defined Parameter_Type values are: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x0000 T} T{ BR/EDR Page Scan Type T} _ T{ 0x0001 T} T{ BR/EDR Page Scan Interval T} _ T{ 0x0002 T} T{ BR/EDR Page Scan Window T} _ T{ 0x0003 T} T{ BR/EDR Inquiry Scan Type T} _ T{ 0x0004 T} T{ BR/EDR Inquiry Scan Interval T} _ T{ 0x0005 T} T{ BR/EDR Inquiry Scan Window T} _ T{ 0x0006 T} T{ BR/EDR Link Supervision Timeout T} _ T{ 0x0007 T} T{ BR/EDR Page Timeout T} _ T{ 0x0008 T} T{ BR/EDR Min Sniff Interval T} _ T{ 0x0009 T} T{ BR/EDR Max Sniff Interval T} _ T{ 0x000a T} T{ LE Advertisement Min Interval T} _ T{ 0x000b T} T{ LE Advertisement Max Interval T} _ T{ 0x000c T} T{ LE Multi Advertisement Rotation Interval T} _ T{ 0x000d T} T{ LE Scanning Interval for auto connect T} _ T{ 0x000e T} T{ LE Scanning Window for auto connect T} _ T{ 0x000f T} T{ LE Scanning Interval for wake scenarios T} _ T{ 0x0010 T} T{ LE Scanning Window for wake scenarios T} _ T{ 0x0011 T} T{ LE Scanning Interval for discovery T} _ T{ 0x0012 T} T{ LE Scanning Window for discovery T} _ T{ 0x0013 T} T{ LE Scanning Interval for adv monitoring T} _ T{ 0x0014 T} T{ LE Scanning Window for adv monitoring T} _ T{ 0x0015 T} T{ LE Scanning Interval for connect T} _ T{ 0x0016 T} T{ LE Scanning Window for connect T} _ T{ 0x0017 T} T{ LE Min Connection Interval T} _ T{ 0x0018 T} T{ LE Max Connection Interval T} _ T{ 0x0019 T} T{ LE Connection Latency T} _ T{ 0x001a T} T{ LE Connection Supervision Timeout T} _ T{ 0x001b T} T{ LE Autoconnect Timeout T} _ T{ 0x001d T} T{ LE Allow List Scanning Duration for adv monitoring T} _ T{ 0x001e T} T{ LE No Filter Scanning Duration for adv monitoring T} _ T{ 0x001f T} T{ LE Enable Interleave Scan for adv monitoring T} _ T{ 0x0020 T} T{ BR/EDR Connection Idle Timeout T} .TE .sp This command can be used at any time and will return a list of supported default parameters as well as their current value. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Default System Configuration (since 1.18) .INDENT 0.0 .TP .B Command Code 0x004c .TP .B Controller Index .TP .B Command Parameters Parameter_Type[] (2 Octet) .TP .B \&... Value_Length[] (1 Octet) .TP .B \&... Value[] (0\-255 Octets) .TP .B \&... \&...[] .TP .B Return Parameters .UNINDENT .sp This command is used to set a list of default controller parameters. .sp See Read Default System Configuration command for list of supported Parameter_Type values. .sp This command can be used when the controller is not powered and all supported parameters will be programmed once powered. .sp When providing unsupported values or invalid values, no parameter value will be changed and all values discarded. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Rejected .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Read Default Runtime Configuration (since 1.18) .INDENT 0.0 .TP .B Command Code 0x004d .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Parameter_Type[] (2 Octet) .TP .B \&... Value_Length[] (1 Octet) .TP .B \&... Value[] (0\-255 Octets) .TP .B \&... \&...[] .UNINDENT .sp This command is used to read a list of default runtime parameters. .sp Currently no Parameter_Type values are defined and an empty list will be returned. .sp This command can be used at any time and will return a list of supported default parameters as well as their current value. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Default Runtime Configuration (since 1.18) .INDENT 0.0 .TP .B Command Code 0x004e .TP .B Controller Index .TP .B Command Parameters Parameter_Type[] (2 Octet) .TP .B \&... Value_Length[] (1 Octet) .TP .B \&... Value[] (0\-255 Octets) .TP .B \&... \&...[] .TP .B Return Parameters .UNINDENT .sp This command is used to set a list of default runtime parameters. .sp See Read Default Runtime Configuration command for list of supported Parameter_Type values. .sp This command can be used at any time and will change the runtime default. Changes however will not apply to existing connections or currently active operations. .sp When providing unsupported values or invalid values, no parameter value will be changed and all values discarded. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Rejected .TP .B Not Supported .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Get Device Flags (since 1.18) .INDENT 0.0 .TP .B Command Code 0x004f .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Supported_Flags (4 Octets) .TP .B \&... Current_Flags (4 Octets) .UNINDENT .sp This command is used to retrieve additional flags and settings for devices that are added via Add Device command. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The Flags parameters are a bitmask with currently the following available bits: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ Remote Wakeup enabled T} _ T{ 1 T} T{ Device Privacy Mode enabled T} _ T{ 2 T} T{ Address Resolution enabled T} _ T{ 3 T} T{ PAST enabled T} .TE .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Set Device Flags (since 1.18) .INDENT 0.0 .TP .B Command Code 0x0050 .TP .B Controller Index .TP .B Command Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Current_Flags (4 Octets) .TP .B Return Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This command is used to configure additional flags and settings for devices that are added via Add Device command. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The list of supported Flags can be retrieved via the Get Device Flags or Device Flags Changed command. Selecting unsupported flags will result in an Invalid Parameter error; .sp Refer to the Get Device Flags command for a detailed description of the Flags parameters. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Invalid Parameters .TP .B Invalid Index .UNINDENT .SS Read Advertisement Monitor Features (since 1.18) .INDENT 0.0 .TP .B Command Code 0x0051 .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Supported_Features (4 Octets) .TP .B \&... Enabled_Features (4 Octets) .TP .B \&... Max_Num_Handles (2 Octets) .TP .B \&... Max_Num_Patterns (1 Octet) .TP .B \&... Num_Handles (2 Octets) .TP .B \&... Handle[] (2 Octets) .TP .B \&... \&...[] .UNINDENT .sp This command is used to read the advertisement monitor features supported by the controller and stack. Supported_Features lists all related features supported by the controller while Enabled_Features lists the ones currently used by the kernel. .sp Supported_Features and Enabled_Features are bitmasks with currently the following available bits: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ Advertisement content monitoring based on patterns with logic OR. T} .TE .sp Max_Num_Handles indicates the maximum number of supported advertisement monitors. Note that the actual number of supported ones might be less depending on the limitation of the controller. .sp Max_Num_Pattern indicates the maximum number of supported patterns in an advertisement patterns monitor. Note that the actual number of supported ones might be less depending on the limitation of the controller. .sp Num_Handles indicates the number of added advertisement monitors, and it is followed by a list of handles. .sp This command can be used when the controller is not powered. .SS Add Advertisement Patterns Monitor (since 1.18) .INDENT 0.0 .TP .B Command Code 0x0052 .TP .B Controller Index .TP .B Command Parameters Pattern_Count (1 Octet) .TP .B \&... AD_Type[] (1 Octet) .TP .B \&... Offset[] (1 Octet) .TP .B \&... Length[] (1 Octet) .TP .B \&... Value[] (31 Octets) .TP .B \&... \&...[] .TP .B Return Parameters Monitor_Handle (2 Octets) .UNINDENT .sp This command is used to add an advertisement monitor whose filtering conditions are patterns. The kernel will trigger scanning if there is at least one monitor added. If the controller supports advertisement filtering, the kernel would offload the content filtering to the controller in order to reduce power consumption; otherwise the kernel ignores the content of the monitor. Note that if the there are more than one patterns, OR logic would applied among patterns during filtering. In other words, any advertisement matching at least one pattern in a given monitor would be considered as a match. .sp A pattern contains the following fields: .INDENT 0.0 .TP .B AD_Data_Type Advertising Data Type. The possible values are defined in Core .TP .B \&... Specification Supplement. .TP .B Offset The start index where pattern matching shall be performed with .TP .B \&... in the AD data. .TP .B Length The length of the pattern value in bytes. .TP .B Value The value of the pattern in bytes. .UNINDENT .sp Here is an example of a pattern. .INDENT 0.0 .INDENT 3.5 .sp .EX { 0x16, // Service Data \- 16\-bit UUID 0x02, // Skip the UUID part. 0x04, // Length of the value {0x11, 0x22, 0x33, 0x44}, } .EE .UNINDENT .UNINDENT .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B Busy .TP .B No Resources .TP .B Invalid Parameters .UNINDENT .SS Remove Advertisement Monitor (since 1.18) .INDENT 0.0 .TP .B Command Code 0x0053 .TP .B Controller Index .TP .B Command Parameters Monitor_Handle (2 Octets) .TP .B Return Parameters Monitor_Handle (2 Octets) .UNINDENT .sp This command is used to remove advertisement monitor(s). The kernel would remove the monitor(s) with Monitor_Handle and update the LE scanning. .sp When the Monitor_Handle is set to zero, then all previously added handles will be removed. .sp Removing a monitor while it is being added will be ignored. .sp This command can be used when the controller is not powered and all settings will be programmed once powered. .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B Busy .UNINDENT .SS Add Extended Advertising Parameters (since 1.19) .INDENT 0.0 .TP .B Command Code 0x0054 .TP .B Controller Index .TP .B Command Parameters Instance (1 Octet) .TP .B \&... Flags (4 Octets) .TP .B \&... Duration (2 Octets) .TP .B \&... Timeout (2 Octets) .TP .B \&... MinInterval (4 Octets) .TP .B \&... MaxInterval (4 Octets) .TP .B \&... TxPower (1 Octet) .TP .B Return Parameters Instance (1 Octet) .TP .B \&... TxPower (1 Octet) .TP .B \&... MaxAdvDataLen (1 Octet) .TP .B \&... MaxScanRspLen (1 Octet) .UNINDENT .sp This command is used to configure the parameters for Bluetooth Low Energy advertising instance. This command is expected to be followed by an Add Extended Advertising Data command to complete and enable the advertising instance. .sp Added advertising information with this command will not be visible immediately if advertising is enabled via the Set Advertising command. The usage of the Set Advertising command takes precedence over this command. Instance information is stored and will be advertised once advertising via Set Advertising has been disabled. .sp The Instance identifier is a value between 1 and the number of supported instances. The value 0 is reserved. .sp With the Flags value the type of advertising is controlled and the following flags are defined: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ Switch into Connectable mode T} _ T{ 1 T} T{ Advertise as Discoverable T} _ T{ 2 T} T{ Advertise as Limited Discoverable T} _ T{ 3 T} T{ Add Flags field to Adv_Data T} _ T{ 4 T} T{ Add TX Power field to Adv_Data T} _ T{ 5 T} T{ Add Appearance field to Scan_Rsp T} _ T{ 6 T} T{ Add Local Name in Scan_Rsp T} _ T{ 7 T} T{ Secondary Channel with LE 1M T} _ T{ 8 T} T{ Secondary Channel with LE 2M T} _ T{ 9 T} T{ Secondary Channel with LE Coded T} _ T{ 10 T} T{ Indicate tx power can be specified T} _ T{ 11 T} T{ Indicate HW supports the advertising offload T} _ T{ 12 T} T{ The Duration parameter should be used T} _ T{ 13 T} T{ The Timeout parameter should be used T} _ T{ 14 T} T{ The Interval parameters should be used T} _ T{ 15 T} T{ The Tx Power parameter should be used T} _ T{ 16 T} T{ The advertisement will contain a scan response T} .TE .sp When the connectable flag is set, then the controller will use undirected connectable advertising. The value of the connectable setting can be overwritten this way. This is useful to switch a controller into connectable mode only for LE operation. This is similar to the mode 0x02 from the Set Advertising command. .sp When the connectable flag is not set, then the controller will use advertising based on the connectable setting. When using non\-connectable or scannable advertising, the controller will be programmed with a non\-resolvable random address. When the system is connectable, then the identity address or resolvable private address will be used. .sp Using the connectable flag is useful for peripheral mode support where BR/EDR (and/or LE) is controlled by Add Device. This allows making the peripheral connectable without having to interfere with the global connectable setting. .sp Secondary channel flags can be used to advertise in secondary channel with the corresponding PHYs. These flag bits are mutually exclusive and setting multiple will result in Invalid Parameter error. Choosing either LE 1M or LE 2M will result in using extended advertising on the primary channel with LE 1M and the respectively LE 1M or LE 2M on the secondary channel. Choosing LE Coded will result in using extended advertising on the primary and secondary channels with LE Coded. Choosing none of these flags will result in legacy advertising. .sp To allow future parameters to be optionally extended in this structure, the flags member has been used to specify which of the structure fields were purposefully set by the caller. Unspecified parameters will be given sensible defaults by the kernel before the advertisement is registered. .sp The Duration parameter configures the length of an Instance. The value is in seconds. The default is 2 seconds. .sp If only one advertising Instance has been added, then the Duration value will be ignored. It only applies for the case where multiple Instances are configured. In that case every Instance will be available for the Duration time and after that it switches to the next one. This is a simple round\-robin based approach. .sp The Timeout parameter configures the life\-time of an Instance. In case the value 0 is used it indicates no expiration time. If a timeout value is provided, then the advertising Instance will be automatically removed when the timeout passes. The value for the timeout is in seconds. Powering down a controller will invalidate all advertising Instances and it is not possible to add a new Instance with a timeout when the controller is powered down. .sp When a Timeout is provided, then the Duration subtracts from the actual Timeout value of that Instance. For example an Instance with Timeout of 5 and Duration of 2 will be scheduled exactly 3 times, twice with 2 seconds and once with one second. Other Instances have no influence on the Timeout. .sp MinInterval and MaxInterval define the minimum and maximum advertising intervals, with units as number of .625ms advertising slots. The Max interval is expected to be greater than or equal to the Min interval, and both must have values in the range [0x000020, 0xFFFFFF]. If either condition is not met, the registration will fail. .sp The provided Tx Power parameter will only be used if the controller supports it, which can be determined by the presence of the CanSetTxPower member of the Read Advertising Features command. .sp The acceptable range for requested Tx Power is defined in the spec (Version 5.2 | Vol 4, Part E, page 2585) to be [\-127, +20] dBm, and the controller will select a power value up to the requested one. The transmission power selected by the controller is not guaranteed to match the requested one, so the reply will contain the power chosen by the controller. If the requested Tx Power is outside the valid range, the registration will fail. .sp When flag bit 16 is enabled, it indicates that the subsequent request to set advertising data will contain a scan response, and that the parameters should set a PDU type that is scannable. .sp Re\-adding an already existing instance (i.e. issuing the Add Extended Advertising Parameters command with an Instance identifier of an existing instance) will update that instance\(aqs configuration. In this case where no new instance is added, no Advertising Added event will be generated. However, if the update of the instance fails, the instance will be removed, and an Advertising Removed event will be generated. .sp An instance being added or changed while another instance is being advertised will not be visible immediately but only when the new/changed instance is being scheduled by the round robin advertising algorithm. .sp Changes to an instance that is currently being advertised will cancel that instance and switch to the next instance. The changes will be visible the next time the instance is scheduled for advertising. In case a single instance is active, this means that changes will be visible right away. .sp The MaxAdvDataLen return parameter indicates how large the data payload can be in the subsequent Add Extended Advertising Data Command, as it accounts for the data required for the selected flags. Similarly, the MaxScanRspLen return parameter indicates how large the scan response can be. .sp LE must already be enabled, and the controller must be powered, otherwise a \(dqrejected\(dq status will be returned. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B Rejected .TP .B Not Supported .TP .B Invalid Parameters .TP .B Busy .UNINDENT .SS Add Extended Advertising Data (since 1.19) .INDENT 0.0 .TP .B Command Code 0x0055 .TP .B Controller Index .TP .B Command Parameters Instance (1 Octet) .TP .B \&... Advertising Data Length (1 Octet) .TP .B \&... Scan Response Length (1 Octet) .TP .B \&... Advertising Data (0\-255 Octets) .TP .B \&... Scan Response (0\-255 Octets) .TP .B Return Parameters Instance (1 Octet) .UNINDENT .sp The Add Extended Advertising Data command is used to update the advertising data of an existing advertising instance known to the kernel. It is expected to be called after an Add Extended Advertising Parameters command, as part of the advertisement registration process. .sp If extended advertising is available, this call will initiate HCI commands to set the instance\(aqs advertising data, set scan response data, and then enable the instance. If extended advertising is unavailable, the advertising instance structure maintained in kernel will have its advertising data and scan response updated, and the instance will either be scheduled immediately or left in the queue for later advertisement as part of round\-robin advertisement rotation in software. .sp If Scan_Rsp_Len is zero and the flags defined in Add Extended Advertising Parameters command do not have connectable flag set and the global connectable setting is off, then non\-connectable advertising is used. If Scan_Rsp_Len is larger than zero and connectable flag is not set and the global advertising is off, then scannable advertising is used. This small difference is supported to provide less air traffic for devices implementing broadcaster role. .sp If the Instance provided does not match a known instance, or if the provided advertising data or scan response are in an unrecognized format, an \(dqInvalid Parameters\(dq status will be returned. .sp If a \(dqSet LE\(dq or Advertising command is still in progress, a \(dqBusy\(dq status will be returned. .sp If the controller is not powered, a \(dqrejected\(dq status will be returned. .sp This command generates a Command Complete event on success or a Command Status event on failure. .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B Rejected .TP .B Invalid Parameters .TP .B Busy .UNINDENT .SS Add Advertisement Patterns Monitor With RSSI Threshold (since 1.20) .INDENT 0.0 .TP .B Command Code 0x0056 .TP .B Controller Index .TP .B Command Parameters High_Threshold (1 Octet) .TP .B \&... High_Threshold_Timer (2 Octets) .TP .B \&... Low_Threshold (1 Octet) .TP .B \&... Low_Threshold_Timer (2 Octets) .TP .B \&... Sampling_Period (1 Octet) .TP .B \&... Pattern_Count (1 Octet) .TP .B \&... AD_Type[] (1 Octet) .TP .B \&... Offset[] (1 Octet) .TP .B \&... Length[] (1 Octet) .TP .B \&... Value[] (31 Octets) .TP .B \&... \&...[] .TP .B Return Parameters Monitor_Handle (2 Octets) .UNINDENT .sp This command is essentially the same as Add Advertisement Patterns Monitor Command (0x0052), but with an additional RSSI parameters. As such, if the kernel supports advertisement filtering, then the advertisement data will be filtered in accordance with the set RSSI parameters. Otherwise, it would behave exactly the same as the Add Advertisement Patterns Monitor Command. .sp Devices would be considered \(dqin\-range\(dq if the RSSI of the received adv packets are greater than High_Threshold dBm for High_Threshold_Timer seconds. Similarly, devices would be considered lost if no received adv have RSSI greater than Low_Threshold dBm for Low_Threshold_Timer seconds. Only adv packets of \(dqin\-range\(dq device would be propagated. .sp The meaning of Sampling_Period is as follows: .INDENT 0.0 .TP .B 0x00 All adv packets from \(dqin\-range\(dq devices would be propagated. .TP .B 0xFF Only the first adv data of \(dqin\-range\(dq devices would be .TP .B \&... propagated. If the device becomes lost, then the first data when .TP .B \&... it is found again will also be propagated. .TP .B other Advertisement data would be grouped into 100ms * N time period. Data in the same group will only be reported once, with the RSSI value being averaged out. .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B Busy .TP .B No Resources .TP .B Invalid Parameters .UNINDENT .SS Set Mesh Receiver (since 1.21) .INDENT 0.0 .TP .B Command Code 0x0057 .TP .B Controller Index .TP .B Command Parameters Enable (1 Octets) .TP .B \&... Window (2 Octets) .TP .B \&... Period (2 Octets) .TP .B \&... Num AD Types (1 Octets) .TP .B \&... AD Types[] .UNINDENT .sp This command Enables or Disables Mesh Receiving. When enabled passive scanning remains enabled for this controller. .sp The Window/Period values are used to set the Scan Parameters when no other scanning is being done. .sp Num AD Types and AD Types parameter, filter Advertising and Scan responses by AD type. Responses that do not contain at least one of the requested AD types will be ignored. Otherwise they will be delivered with the Mesh Device Found event. .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B No Resources .TP .B Invalid Parameters .UNINDENT .SS Read Mesh Features (since 1.21) .INDENT 0.0 .TP .B Command Code 0x0058 .TP .B Controller Index .TP .B Command Parameters .TP .B Return Parameters Index (2 Octets) .TP .B \&... Max Handles (1 Octets) .TP .B \&... Used Handles (1 Octets) .TP .B \&... Handle[] .UNINDENT .sp This command is used to both verify that Outbound Mesh packet support is enabled, and to indicate the number of packets that can and are simultaneously queued. .sp Index identifies the HCI Controller that this information is valid for. .sp Max Handles indicates the maximum number of packets that may be queued. .sp Used Handles indicates the number of packets awaiting transmission. .sp Handle is an array of the currently outstanding packets. .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B No Resources .TP .B Invalid Parameters .UNINDENT .SS Transmit Mesh Packet (since 1.21) .INDENT 0.0 .TP .B Command Code 0x0059 .TP .B Controller Index .TP .B Command Parameters Addr (6 octets) .TP .B \&... Addr Type (1 Octets) .TP .B \&... Instant (8 Octets) .TP .B \&... Delay (2 Octets) .TP .B \&... Count (1 Octets) .TP .B \&... Data Length (1 Octets) .TP .B \&... Data[] (variable) .TP .B Return Parameters Handle (1 Octets) .UNINDENT .sp This command sends a Mesh Packet as a NONCONN LE Advertisement. .sp The Addr + Addr Type parameters specify the address to use in the outbound advertising packet. If BD_ADDR_ANY and LE_RANDOM is set, the kernel will create a single use non\-resolvable address. .sp The Instant parameter is used in combination with the Delay parameter, to finely time the sending of the Advertising packet. It should be set to the Instant value tag of a received incoming Mesh Device Found Event. It is only useful in POLL\-RESPONSE situations where a response must be sent within a negotiated time window. The value of the Instant parameter should not be interpreted by the host, and only has meaning to the controller. .sp The Delay parameter, if 0x0000, will cause the packet to be sent at the earliest opportunity. If non\-Zero, and the controller supports delayed delivery, the Instant and Delay parameters will be used to delay the outbound packet. While the Instant is not defined, the Delay is specified in milliseconds. .sp The Count parameter must be sent to a non\-Zero value indicating the number of times this packet will be sent before transmission completes. If the Delay parameter is non\-Zero, then Count must be 1 only. .sp The Data parameter is an octet array of the AD Type and Mesh Packet. .sp This command will return immediately, and if it succeeds, will generate a Mesh Packet Transmission Complete event when after the packet has been sent. .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B Busy .TP .B No Resources .TP .B Invalid Parameters .UNINDENT .SS Cancel Transmit Mesh Packet (since 1.21) .INDENT 0.0 .TP .B Command Code 0x005A .TP .B Controller Index .TP .B Command Parameters Handle (1 Octets) .UNINDENT .sp This command may be used to cancel an outbound transmission request. .sp The Handle parameter is the returned handle from a successful Transmit Mesh Packet request. If Zero is specified as the handle, all outstanding send requests are canceled. .sp For each mesh packet canceled, the Mesh Packet Transmission Complete event will be generated, regardless of whether the packet was sent successfully. .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B Invalid Parameters .UNINDENT .SS Send HCI command and wait for event .INDENT 0.0 .TP .B Command Code 0x005B .TP .B Controller Index .TP .B Command Parameters Opcode (2 Octets) .TP .B \&... Event (1 Octet) .TP .B \&... Timeout (1 Octet) .TP .B \&... Parameter Length (2 Octets) .TP .B \&... Parameter[] (variable) .TP .B Return Parameters Response (1\-variable Octets) .UNINDENT .sp This command may be used to send a HCI command and wait for an (optional) event. .sp The HCI command is specified by the Opcode, any arbitrary is supported including vendor commands, but contrary to the like of Raw/User channel it is run as an HCI command send by the kernel since it uses its command synchronization thus it is possible to wait for a specific event as a response. .sp Setting event to 0x00 will cause the command to wait for either HCI Command Status or HCI Command Complete. .sp Timeout is specified in seconds, setting it to 0 will cause the default timeout to be used. .sp Possible errors: .INDENT 0.0 .TP .B Failed .TP .B Invalid Parameters .UNINDENT .SS Events .SS Command Complete .INDENT 0.0 .TP .B Event Code 0x0001 .TP .B Controller Index or .TP .B Event Parameters Command_Opcode (2 Octets) .TP .B \&... Status (1 Octet) .TP .B \&... Return_Parameters[] (variable) .UNINDENT .sp This event is an indication that a command has completed. The fixed set of parameters includes the opcode to identify the command that completed as well as a status value to indicate success or failure. The rest of the parameters are command specific and documented in the section for each command separately. .SS Command Status .INDENT 0.0 .TP .B Event Code 0x0002 .TP .B Controller Index or .TP .B Event Parameters Command_Opcode (2 Octets) .TP .B \&... Status (1 Octet) .UNINDENT .sp The command status event is used to indicate an early status for a pending command. In the case that the status indicates failure (anything else except success status) this also means that the command has finished executing. .SS Controller Error .INDENT 0.0 .TP .B Event Code 0x0003 .TP .B Controller Index .TP .B Event Parameters Error_Code (1 Octet) .UNINDENT .sp This event maps straight to the HCI Hardware Error event and is used to indicate something wrong with the controller hardware. .SS Index Added .INDENT 0.0 .TP .B Event Code 0x0004 .TP .B Controller Index .TP .B Event Parameters .UNINDENT .sp This event indicates that a new controller has been added to the system. It is usually followed by a Read Controller Information command. .sp Once the Read Extended Controller Index List command has been used at least once, the Extended Index Added event will be send instead of this one. .SS Index Removed .INDENT 0.0 .TP .B Event Code 0x0005 .TP .B Controller Index .TP .B Event Parameters .UNINDENT .sp This event indicates that a controller has been removed from the system. .sp Once the Read Extended Controller Index List command has been used at least once, the Extended Index Removed event will be send instead of this one. .SS New Settings .INDENT 0.0 .TP .B Event Code 0x0006 .TP .B Controller Index .TP .B Event Parameters Current_Settings (4 Octets) .UNINDENT .sp This event indicates that one or more of the settings for a controller has changed. .SS Class Of Device Changed .INDENT 0.0 .TP .B Event Code 0x0007 .TP .B Controller Index .TP .B Event Parameters Class_Of_Device (3 Octets) .UNINDENT .sp This event indicates that the Class of Device value for the controller has changed. When the controller is powered off the Class of Device value will always be reported as zero. .SS Local Name Changed .INDENT 0.0 .TP .B Event Code 0x0008 .TP .B Controller Index .TP .B Event Parameters Name (249 Octets) .TP .B \&... Short_Name (11 Octets) .UNINDENT .sp This event indicates that the local name of the controller has changed. .SS New Link Key .INDENT 0.0 .TP .B Event Code 0x0009 .TP .B Controller Index .TP .B Event Parameters Store_Hint (1 Octet) .TP .B \&... Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Key_Type (1 Octet) .TP .B \&... Value (16 Octets) .TP .B \&... PIN_Length (1 Octet) .UNINDENT .sp This event indicates that a new link key has been generated for a remote device. .sp The Store_Hint parameter indicates whether the host is expected to store the key persistently or not (e.g. this would not be set if the authentication requirement was \(dqNo Bonding\(dq). .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0 T} T{ BR/EDR T} _ T{ 1 T} T{ Reserved (not in use) T} _ T{ 2 T} T{ Reserved (not in use) T} .TE .sp Public and random LE addresses are not valid and will be rejected. .sp Currently defined Key_Type values are: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Combination key T} _ T{ 0x01 T} T{ Local Unit key T} _ T{ 0x02 T} T{ Remote Unit key T} _ T{ 0x03 T} T{ Debug Combination key T} _ T{ 0x04 T} T{ Unauthenticated Combination key from P\-192 T} _ T{ 0x05 T} T{ Authenticated Combination key from P\-192 T} _ T{ 0x06 T} T{ Changed Combination key T} _ T{ 0x07 T} T{ Unauthenticated Combination key from P\-256 T} _ T{ 0x08 T} T{ Authenticated Combination key from P\-256 T} .TE .sp Receiving this event indicates that a pairing procedure has been completed. .SS New Long Term Key .INDENT 0.0 .TP .B Event Code 0x000A .TP .B Controller Index .TP .B Event Parameters Store_Hint (1 Octet) .TP .B \&... Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Key_Type (1 Octet) .TP .B \&... Central (1 Octet) .TP .B \&... Encryption Size (1 Octet) .TP .B \&... Enc. Diversifier (2 Octets) .TP .B \&... Random Number (8 Octets) .TP .B \&... Value (16 Octets) .UNINDENT .sp This event indicates that a new long term key has been generated for a remote device. .sp The Store_Hint parameter indicates whether the host is expected to store the key persistently or not (e.g. this would not be set if the authentication requirement was \(dqNo Bonding\(dq). .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0 T} T{ Reserved (not in use) T} _ T{ 1 T} T{ LE Public T} _ T{ 2 T} T{ LE Random T} .TE .sp The provided Address and Address_Type are the identity of a device. So either its public address or static random address. .sp For unresolvable random addresses and resolvable random addresses without identity information and identity resolving key, the Store_Hint will be set to not store the long term key. .sp Currently defined Key_Type values are: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Unauthenticated legacy key T} _ T{ 0x01 T} T{ Authenticated legacy key T} _ T{ 0x02 T} T{ Unauthenticated key from P\-256 T} _ T{ 0x03 T} T{ Authenticated key from P\-256 T} _ T{ 0x04 T} T{ Debug key from P\-256 T} .TE .sp Receiving this event indicates that a pairing procedure has been completed. .SS Device Connected .INDENT 0.0 .TP .B Event Code 0x000B .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Flags (4 Octets) .TP .B \&... EIR_Data_Length (2 Octets) .TP .B \&... EIR_Data (0\-65535 Octets) .UNINDENT .sp This event indicates that a successful baseband connection has been created to the remote device. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0 T} T{ BR/EDR T} _ T{ 1 T} T{ LE Public T} _ T{ 2 T} T{ LE Random T} .TE .sp For devices using resolvable random addresses with a known identity resolving key, the Address and Address_Type will contain the identity information. .sp It is possible that devices get connected via its resolvable random address and after New Identity Resolving Key event start using its identity. .sp The following bits are defined for the Flags parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0 T} T{ Reserved (not in use) T} _ T{ 1 T} T{ Legacy Pairing T} _ T{ 2 T} T{ Reserved (not in use) T} _ T{ 3 T} T{ Initiated Connection T} _ T{ 4 T} T{ Reserved (not in use) T} .TE .SS Device Disconnected .INDENT 0.0 .TP .B Event Code 0x000C .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Reason (1 Octet) .UNINDENT .sp This event indicates that the baseband connection was lost to a remote device. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp For devices using resolvable random addresses with a known identity resolving key, the Address and Address_Type will contain the identity information. .sp Possible values for the Reason parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Unspecified T} _ T{ 0x01 T} T{ Connection timeout T} _ T{ 0x02 T} T{ Connection terminated by local host T} _ T{ 0x03 T} T{ Connection terminated by remote host T} _ T{ 0x04 T} T{ Connection terminated due to authentication failure T} _ T{ 0x05 T} T{ Connection terminated by local host for suspend T} .TE .sp Note that the local/remote distinction just determines which side terminated the low\-level connection, regardless of the disconnection of the higher\-level profiles. .sp This can sometimes be misleading and thus must be used with care. For example, some hardware combinations would report a locally initiated disconnection even if the user turned Bluetooth off in the remote side. .SS Connect Failed .INDENT 0.0 .TP .B Event Code 0x000D .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Status (1 Octet) .UNINDENT .sp This event indicates that a connection attempt failed to a remote device. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp For devices using resolvable random addresses with a known identity resolving key, the Address and Address_Type will contain the identity information. .SS PIN Code Request .INDENT 0.0 .TP .B Event Code 0x000E .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Secure (1 Octet) .UNINDENT .sp This event is used to request a PIN Code reply from user space. The reply should either be returned using the PIN Code Reply or the PIN Code Negative Reply command. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .INDENT 0.0 .TP .B Secure 0x01 secure PIN code required 0x00 secure PIN code not required .UNINDENT .SS User Confirmation Request .INDENT 0.0 .TP .B Event Code 0x000F .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Confirm_Hint (1 Octet) .TP .B \&... Value (4 Octets) .UNINDENT .sp This event is used to request a user confirmation request from user space. .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp If the Confirm_Hint parameter value is 0x01 this means that a simple \(dqYes/No\(dq confirmation should be presented to the user instead of a full numerical confirmation (in which case the parameter value will be 0x00). .sp User space should respond to this command either using the User Confirmation Reply or the User Confirmation Negative Reply command. .SS User Passkey Request .INDENT 0.0 .TP .B Event Code 0x0010 .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This event is used to request a passkey from user space. The response to this event should either be the User Passkey Reply command or the User Passkey Negative Reply command. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .SS Authentication Failed .INDENT 0.0 .TP .B Event Code 0x0011 .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Status (1 Octet) .UNINDENT .sp This event indicates that there was an authentication failure with a remote device. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .SS Device Found .INDENT 0.0 .TP .B Event Code 0x0012 .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... RSSI (1 Octet) .TP .B \&... Flags (4 Octets) .TP .B \&... EIR_Data_Length (2 Octets) .TP .B \&... EIR_Data (0\-65535 Octets) .UNINDENT .sp This event indicates that a device was found during device discovery. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The following bits are defined for the Flags parameter: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ Confirm name T} _ T{ 1 T} T{ Legacy Pairing T} _ T{ 2 T} T{ Not Connectable T} _ T{ 3 T} T{ Reserved (not in use) T} _ T{ 4 T} T{ Name Request Failed T} _ T{ 5 T} T{ Scan Response T} .TE .sp For the RSSI field a value of 127 indicates that the RSSI is not available. That can happen with Bluetooth 1.1 and earlier controllers or with bad radio conditions. .sp The Confirm name flag indicates that the kernel wants to know whether user space knows the name for this device or not. If this flag is set user space should respond to it using the Confirm Name command. .sp The Legacy Pairing flag indicates that Legacy Pairing is likely to occur when pairing with this device. An application could use this information to optimize the pairing process by locally pre\-generating a PIN code and thereby eliminate the risk of local input timeout when pairing. Note that there is a risk of false\-positives for this flag so user space should be able to handle getting something else as a PIN Request when pairing. .sp The Not Connectable flag indicates that the device will not accept any connections. This can be indicated by Low Energy devices that are in broadcaster role. .sp The Name Request Failed flag indicates that name resolving procedure has ended with failure for this device. The user space should use this information to determine when is a good time to retry the name resolving procedure. .SS Discovering .INDENT 0.0 .TP .B Event Code 0x0013 .TP .B Controller Index .TP .B Event Parameters Address_Type (1 Octet) .TP .B \&... Discovering (1 Octet) .UNINDENT .sp This event indicates that the controller has started discovering devices. This discovering state can come and go multiple times between a Start Discovery and a Stop Discovery commands. .sp The Start Service Discovery command will also trigger this event. .sp The valid values for the Discovering parameter are 0x01 (enabled) and 0x00 (disabled). .SS Device Blocked .INDENT 0.0 .TP .B Event Code 0x0014 .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This event indicates that a device has been blocked using the Block Device command. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The event will only be sent to Management sockets other than the one through which the command was sent. .SS Device Unblocked .INDENT 0.0 .TP .B Event Code 0x0015 .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This event indicates that a device has been unblocked using the Unblock Device command. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The event will only be sent to Management sockets other than the one through which the command was sent. .SS Device Unpaired .INDENT 0.0 .TP .B Event Code 0x0016 .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This event indicates that a device has been unpaired (i.e. all its keys have been removed from the kernel) using the Unpair Device command. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp For devices using resolvable random addresses with a known identity resolving key, the event parameters will contain the identity. After receiving this event, the device will become essentially private again. .sp The event will only be sent to Management sockets other than the one through which the Unpair Device command was sent. .SS Passkey Notify .INDENT 0.0 .TP .B Event Code 0x0017 .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Passkey (4 Octets) .TP .B \&... Entered (1 Octet) .UNINDENT .sp This event is used to request passkey notification to the user. Unlike the other authentication events it does not need responding to using any Management command. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The Passkey parameter indicates the passkey to be shown to the user whereas the Entered parameter indicates how many characters the user has entered on the remote side. .SS New Identity Resolving Key (since 1.5) .INDENT 0.0 .TP .B Event Code 0x0018 .TP .B Controller Index .TP .B Event Parameters Store_Hint (1 Octet) .TP .B \&... Random_Address (6 Octets) .TP .B \&... Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Value (16 Octets) .UNINDENT .sp This event indicates that a new identity resolving key has been generated for a remote device. .sp The Store_Hint parameter indicates whether the host is expected to store the key persistently or not. .sp The Random_Address provides the resolvable random address that was resolved into an identity. A value of 00:00:00:00:00:00 indicates that the identity resolving key was provided for a public address or static random address. .sp Once this event has been send for a resolvable random address, all further events mapping this device will send out using the identity address information. .sp This event also indicates that now the identity address should be used for commands instead of the resolvable random address. .sp It is possible that some devices allow discovering via its identity address, but after pairing using resolvable private address only. In such a case Store_Hint will be 0x00 and the Random_Address will indicate 00:00:00:00:00:00. For these devices, the Privacy Characteristic of the remote GATT database should be consulted to decide if the identity resolving key must be stored persistently or not. .sp Devices using Set Privacy command with the option 0x02 would be such type of device. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0 T} T{ Reserved (not in use) T} _ T{ 1 T} T{ LE Public T} _ T{ 2 T} T{ LE Random T} .TE .sp The provided Address and Address_Type are the identity of a device. So either its public address or static random address. .SS New Signature Resolving Key (since 1.5) .INDENT 0.0 .TP .B Event Code 0x0019 .TP .B Controller Index .TP .B Event Parameters Store_Hint (1 Octet) .TP .B \&... Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Type (1 Octet) .TP .B \&... Value (16 Octets) .UNINDENT .sp This event indicates that a new signature resolving key has been generated for either the central or peripheral device. .sp The Store_Hint parameter indicates whether the host is expected to store the key persistently or not. .sp The Type parameter has the following possible values: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Unauthenticated local CSRK T} _ T{ 0x01 T} T{ Unauthenticated remote CSRK T} _ T{ 0x02 T} T{ Authenticated local CSRK T} _ T{ 0x03 T} T{ Authenticated remote CSRK T} .TE .sp The local keys are used for signing data to be sent to the remote device, whereas the remote keys are used to verify signatures received from the remote device. .sp The local signature resolving key will be generated with each pairing request. Only after receiving this event with the Type indicating a local key is it possible to use ATT Signed Write procedures. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Reserved (not in use) T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The provided Address and Address_Type are the identity of a device. So either its public address or static random address. .SS Device Added (since 1.7) .INDENT 0.0 .TP .B Event Code 0x001a .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Action (1 Octet) .UNINDENT .sp This event indicates that a device has been added using the Add Device command. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The event will only be sent to management sockets other than the one through which the command was sent. .SS Device Removed (since 1.7) .INDENT 0.0 .TP .B Event Code 0x001b .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This event indicates that a device has been removed using the Remove Device command. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The event will only be sent to management sockets other than the one through which the command was sent. .SS New Connection Parameter (since 1.7) .INDENT 0.0 .TP .B Event Code 0x001c .TP .B Controller Index .TP .B Event Parameters Store_Hint (1 Octet) .TP .B \&... Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Min_Connection_Interval (2 Octets) .TP .B \&... Max_Connection_Interval (2 Octets) .TP .B \&... Connection_Latency (2 Octets) .TP .B \&... Supervision_Timeout (2 Octets) .UNINDENT .sp This event indicates a new set of connection parameters from a peripheral device. .sp The Store_Hint parameter indicates whether the host is expected to store this information persistently or not. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Reserved (not in use) T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The Min_Connection_Interval, Max_Connection_Interval, Connection_Latency and Supervision_Timeout parameters are encoded as described in Core 4.1 spec, Vol 2, 7.7.65.3. .SS Unconfigured Index Added (since 1.7) .INDENT 0.0 .TP .B Event Code 0x001d .TP .B Controller Index .TP .B Event Parameters .UNINDENT .sp This event indicates that a new unconfigured controller has been added to the system. It is usually followed by a Read Controller Configuration Information command. .sp Only when a controller requires further configuration, it will be announced with this event. If it supports configuration, but does not require it, then an Index Added event will be used. .sp Once the Read Extended Controller Index List command has been used at least once, the Extended Index Added event will be send instead of this one. .SS Unconfigured Index Removed (since 1.7) .INDENT 0.0 .TP .B Event Code 0x001e .TP .B Controller Index .TP .B Event Parameters .UNINDENT .sp This event indicates that an unconfigured controller has been removed from the system. .sp Once the Read Extended Controller Index List command has been used at least once, the Extended Index Removed event will be send instead of this one. .SS New Configuration Options (since 1.7) .INDENT 0.0 .TP .B Event Code 0x001f .TP .B Controller Index .TP .B Event Parameters Missing_Options (4 Octets) .UNINDENT .sp This event indicates that one or more of the options for the controller configuration has changed. .SS Extended Index Added (since 1.9) .INDENT 0.0 .TP .B Event Code 0x0020 .TP .B Controller Index .TP .B Event Parameters Controller_Type (1 Octet) .TP .B \&... Controller_Bus (1 Octet) .UNINDENT .sp This event indicates that a new controller index has been added to the system. .sp This event will only be used after Read Extended Controller Index List has been used at least once. If it has not been used, then Index Added and Unconfigured Index Added are sent instead. .SS Extended Index Removed (since 1.9) .INDENT 0.0 .TP .B Event Code 0x0021 .TP .B Controller Index .TP .B Event Parameters Controller_Type (1 Octet) .TP .B \&... Controller_Bus (1 Octet) .UNINDENT .sp This event indicates that an existing controller index has been removed from the system. .sp This event will only be used after Read Extended Controller Index List has been used at least once. If it has not been used, then Index Removed and Unconfigured Index Removed are sent instead. .SS Local Out Of Band Extended Data Updated (since 1.9) .INDENT 0.0 .TP .B Event Code 0x0022 .TP .B Controller Index .TP .B Event Parameters Address_Type (1 Octet) .TP .B \&... EIR_Data_Length (2 Octets) .TP .B \&... EIR_Data (0\-65535 Octets) .UNINDENT .sp This event is used when the Read Local Out Of Band Extended Data command has been used and some other user requested a new set of local out\-of\-band data. This allows for the original caller to adjust the data. .sp Possible values for the Address_Type parameter are a bit\-wise or of the following bits: .TS box center; l|l. T{ Bit T} T{ Description T} _ T{ 0 T} T{ BR/EDR T} _ T{ 1 T} T{ LE Public T} _ T{ 2 T} T{ LE Random T} .TE .sp By combining these e.g. the following values are possible: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x01 T} T{ BR/EDR T} _ T{ 0x06 T} T{ LE (public & random) T} _ T{ 0x07 T} T{ Reserved (not in use) T} .TE .sp The value for EIR_Data_Length and content for EIR_Data is the same as described in Read Local Out Of Band Extended Data command. .sp When LE Privacy is used and LE Secure Connections out\-of\-band data has been requested, then this event will be emitted every time the Resolvable Private Address (RPA) gets changed. The new RPA will be included in the EIR_Data. .sp The event will only be sent to management sockets other than the one through which the command was sent. It will additionally also only be sent to sockets that have used the command at least once. .SS Advertising Added (since 1.9) .INDENT 0.0 .TP .B Event Code 0x0023 .TP .B Controller Index .TP .B Event Parameters Instance (1 Octet) .UNINDENT .sp This event indicates that an advertising instance has been added using the Add Advertising command. .sp The event will only be sent to management sockets other than the one through which the command was sent. .SS Advertising Removed (since 1.9) .INDENT 0.0 .TP .B Event Code 0x0024 .TP .B Controller Index .TP .B Event Parameters Instance (1 Octet) .UNINDENT .sp This event indicates that an advertising instance has been removed using the Remove Advertising command. .sp The event will only be sent to management sockets other than the one through which the command was sent. .SS Extended Controller Information Changed (since 1.14) .INDENT 0.0 .TP .B Event Code 0x0025 .TP .B Controller Index .TP .B Event Parameters EIR_Data_Length (2 Octets) .TP .B \&... EIR_Data (0\-65535 Octets) .UNINDENT .sp This event indicates that controller information has been updated and new values are used. This includes the local name, class of device, device id and LE address information. .sp This event will only be used after Read Extended Controller Information command has been used at least once. If it has not been used the legacy events are used. .sp The event will only be sent to management sockets other than the one through which the change was triggered. .SS PHY Configuration Changed (since 1.14) .INDENT 0.0 .TP .B Event Code 0x0026 .TP .B Controller Index .TP .B Event Parameters Selected_PHYs (4 Octets) .UNINDENT .sp This event indicates that default PHYs have been updated. .sp This event will only be used after Set PHY Configuration command has been used at least once. .sp The event will only be sent to management sockets other than the one through which the change was triggered. .sp Refer Get PHY Configuration command for PHYs parameter. .SS Experimental Feature Changed (since 1.17) .INDENT 0.0 .TP .B Event Code 0x0027 .TP .B Controller Index .TP .B Event Parameters UUID (16 Octets) .TP .B \&... Flags (4 Octets) .UNINDENT .sp This event indicates that the status of an experimental feature has been changed. .sp The event will only be sent to management sockets other than the one through which the change was triggered. .sp Refer to Set Experimental Feature command for the Flags parameter. .SS Default System Configuration Changed (since 1.18) .INDENT 0.0 .TP .B Event Code 0x0028 .TP .B Controller Index .TP .B Event Parameters Parameter_Type[] (2 Octet) .TP .B \&... Value_Length[] (1 Octet) .TP .B \&... Value[] (0\-255 Octets) .UNINDENT .sp This event indicates the change of default system parameter values. .sp The event will only be sent to management sockets other than the one through which the change was triggered. In addition it will only be sent to sockets that have issues the Read Default System Configuration command. .sp Refer to Read Default System configuration command for the supported Parameter_Type values. .SS Default Runtime Configuration Changed (since 1.18) .INDENT 0.0 .TP .B Event Code 0x0029 .TP .B Controller Index .TP .B Event Parameters Parameter_Type[] (2 Octet) .TP .B \&... Value_Length[] (1 Octet) .TP .B \&... Value[] (0\-255 Octets) .UNINDENT .sp This event indicates the change of default runtime parameter values. .sp The event will only be sent to management sockets other than the one through which the change was triggered. In addition it will only be sent to sockets that have issues the Read Default Runtime Configuration command. .sp Refer to Read Default Runtime configuration command for the supported Parameter_Type values. .SS Device Flags Changed (since 1.18) .INDENT 0.0 .TP .B Event Code 0x002a .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... Supported_Flags (4 Octets) .TP .B \&... Current_Flags (4 Octets) .UNINDENT .sp This event indicates that the device flags have been changed via the Set Device Flags command or that a new device has been added via the Add Device command. In the latter case it is send right after the Device Added event. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp The event will only be sent to management sockets other than the one through which the command was sent. .sp In case this event is triggered by Add Device then it is sent to all management sockets. .SS Advertisement Monitor Added (since 1.18) .INDENT 0.0 .TP .B Event Code 0x002b .TP .B Controller Index .TP .B Event Parameters Monitor_Handle (2 Octets) .UNINDENT .sp This event indicates that an advertisement monitor has been added using the Add Advertisement Patterns Monitor command. .sp The event will only be sent to management sockets other than the one through which the command was sent. .SS Advertisement Monitor Removed (since 1.18) .INDENT 0.0 .TP .B Event Code 0x002c .TP .B Controller Index .TP .B Event Parameters Monitor_Handle (2 Octets) .UNINDENT .sp This event indicates that an advertisement monitor has been removed using the Remove Advertisement Monitor command. .sp The event will only be sent to management sockets other than the one through which the command was sent. .SS Controller Suspend (since 1.18) .INDENT 0.0 .TP .B Event Code 0x002d .TP .B Controller Index .TP .B Event Parameters Suspend_State (1 octet) .UNINDENT .sp This event indicates that the controller is suspended for host suspend. .sp Possible values for the Suspend_State parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Running (not disconnected) T} _ T{ 0x01 T} T{ Disconnected and not scanning T} _ T{ 0x02 T} T{ Page scanning and/or passive scanning. T} .TE .sp The value 0x00 is used for the running state and may be sent if the controller could not be configured to suspend properly. .sp This event will be sent to all management sockets. .SS Controller Resume (since 1.18) .INDENT 0.0 .TP .B Event Code 0x002e .TP .B Controller Index .TP .B Event Parameters Wake_Reason (1 octet) .TP .B \&... Address (6 octets) .TP .B \&... Address_Type (1 octet) .UNINDENT .sp This event indicates that the controller has resumed from suspend. .sp Possible values for the Wake_Reason parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ Resume from non\-Bluetooth wake source T} _ T{ 0x01 T} T{ Wake due to unexpected event T} _ T{ 0x02 T} T{ Remote wake due to peer device connection T} .TE .sp Currently, we expect that only peer reconnections should wake us from the suspended state. Any other events that occurred while the system should have been suspended results in wake due to unexpected event. .sp If the Wake_Reason is Remote wake due to connection, the address of the peer device that caused the event will be shared in Address and Address_Type. Otherwise, Address and Address_Type will both be zero. .sp This event will be sent to all management sockets. .SS Advertisement Monitor Device Found (since 1.18) .INDENT 0.0 .TP .B Event Code 0x002f .TP .B Controller Index .TP .B Event Parameters Monitor_Handle (2 Octets) .TP .B \&... Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... RSSI (1 Octet) .TP .B \&... Flags (4 Octets) .TP .B \&... AD_Data_Length (2 Octets) .TP .B \&... AD_Data (0\-65535 Octets) .UNINDENT .sp This event indicates that the controller has started tracking a device matching an Advertisement Monitor with handle Monitor_Handle. .sp Monitor_Handle 0 indicates that we are not active scanning and this is a subsequent advertisement report for already matched Advertisement Monitor or the controller offloading support is not available so need to report all advertisements for software based filtering. .sp The address of the device being tracked will be shared in Address and Address_Type. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0x00 T} T{ BR/EDR T} _ T{ 0x01 T} T{ LE Public T} _ T{ 0x02 T} T{ LE Random T} .TE .sp For the RSSI field a value of 127 indicates that the RSSI is not available. That can happen with Bluetooth 1.1 and earlier controllers or with bad radio conditions. .sp This event will be sent to all management sockets. .SS Advertisement Monitor Device Lost (since 1.18) .INDENT 0.0 .TP .B Event Code 0x0030 .TP .B Controller Index .TP .B Event Parameters Monitor_Handle (2 Octets) .TP .B \&... Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .UNINDENT .sp This event indicates that the controller has stopped tracking a device that was being tracked by an Advertisement Monitor with the handle Monitor_Handle. .sp The address of the device being tracked will be shared in Address and Address_Type. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0 T} T{ Reserved (not in use) T} _ T{ 1 T} T{ LE Public T} _ T{ 2 T} T{ LE Random T} .TE .sp This event will be sent to all management sockets. .SS Mesh Device Found (since 1.21) .INDENT 0.0 .TP .B Event Code 0x0031 .TP .B Controller Index .TP .B Event Parameters Address (6 Octets) .TP .B \&... Address_Type (1 Octet) .TP .B \&... RSSI (1 Octet) .TP .B \&... Instant (8 Octets) .TP .B \&... Flags (4 Octets) .TP .B \&... AD_Data_Length (2 Octets) .TP .B \&... AD_Data (0\-65535 Octets) .UNINDENT .sp This event indicates that the controller has received an Advertisement or Scan Result containing an AD Type matching the Mesh scan set. .sp The address of the sending device is returned, and must be a valid LE Address_Type. .sp Possible values for the Address_Type parameter: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ 0 T} T{ Reserved (not in use) T} _ T{ 1 T} T{ LE Public T} _ T{ 2 T} T{ LE Random T} .TE .sp The RSSI field is a signed octet, and is the RSSI reported by the receiving controller. .sp The Instant field is 64 bit value that represents the instant in time the packet was received. It\(aqs value is not intended to be interpreted by the host, and is only useful if the host wants to make a timed response to the received packet (i.e. a Poll/Response). .sp AD_Length and AD_Data contains the Info structure of Advertising and Scan rsults. To receive this event, AD filters must be requested with the Set Mesh Receiver command, specifying which AD Types to return. All AD structures will be received in this event if any of the filtered AD Types are present. .sp This event will be sent to all management sockets. .SS Mesh Packet Transmit Complete (since 1.21) .INDENT 0.0 .TP .B Event Code 0x0032 .TP .B Controller Index .TP .B Event Parameters Handle (1 Octets) .UNINDENT .sp This event indicates that a requested outbound Mesh packet has completed and no longer occupies a transmit slot. .sp This event will be sent to all management sockets. .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/isotest.10000644000000000000000000000005015221755440014201 xustar0020 atime=1783094048 20 ctime=1783094113 bluez-5.87/doc/isotest.10000644000000000000000000001222115221755440013660 0ustar00rootroot'\" t .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "isotest" "1" "May 4, 2022" "BlueZ" "Linux System Administration" .SH Name isotest \- ISO testing .SH SYNOPSIS .sp \fBisotest\fP <\fIMODE\fP> [\fIOPTIONS\fP] [\fIbdaddr\fP] [\fIbdaddr1\fP]... .SH DESCRIPTION .sp \fBisotest(1)\fP is used to test Isochronous (CIS/BIS) communications on the BlueZ stack .SH MODES .INDENT 0.0 .TP .B \-d, \-\-dump=[FILE] Listen and dump incoming data (CIS server/BIS broadcaster) and optionally save the contents to \fIFILE\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-c\fP,\fB \-\-reconnect Reconnect (CIS client). .TP .B \-m\fP,\fB \-\-multiple Multiple connects (CIS client). .UNINDENT .INDENT 0.0 .TP .B \-r, \-\-receive=[FILE] Receive (CIS server/BIS broadcast receiver) and optionally save the contents to \fIFILE\fP\&. .TP .B \-s, \-\-send=[FILE] Connect and send (CIS client/BIS broadcaster), can optionally use contents from \fIFILE\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-n\fP,\fB \-\-silent Connect and be silent (CIS client/BIS broadcaster). .UNINDENT .SH OPTIONS .INDENT 0.0 .TP .BI \-b\fP,\fB \-\-bytes\fB= Send or Receive packet size .TP .BI \-i\fP,\fB \-\-index\fB= Select the specified HCI device index. \fIhciNUM\fP is also acceptable. .TP .BI \-j\fP,\fB \-\-jitter\fB= Socket jitter buffer. .UNINDENT .sp \-h, \-\-help .INDENT 0.0 .TP .B \-q\fP,\fB \-\-quiet Disables packet logging. .TP .BI \-t\fP,\fB \-\-timeout\fB= Socket send timeout. .TP .B \-C\fP,\fB \-\-continue Continuously send packets starting over in case of a file. .TP .BI \-W\fP,\fB \-\-defer\fB= Enable deferred setup. .TP .BI \-M\fP,\fB \-\-mtu\fB= Socket QoS SDU. .UNINDENT .INDENT 0.0 .TP .B \-S, \-\-sca/adv\-interval= Socket QoS CIS SCA/BIS advertising interval. .UNINDENT .INDENT 0.0 .TP .BI \-P\fP,\fB \-\-packing\fB= Socket QoS Packing. .UNINDENT .TS box center; l|l. T{ \fIPACKING\fP T} T{ Description T} _ T{ \fB0x00\fP T} T{ Sequential T} _ T{ \fB0x01\fP T} T{ Interleaved T} .TE .INDENT 0.0 .TP .BI \-F\fP,\fB \-\-framing\fB= Socket QoS Framing. .UNINDENT .TS box center; l|l. T{ \fIFRAMING\fP T} T{ Description T} _ T{ \fB0x00\fP T} T{ Unframed T} _ T{ \fB0x01\fP T} T{ Framed T} .TE .INDENT 0.0 .TP .BI \-I\fP,\fB \-\-interval\fB= Socket QoS Interval. .TP .BI \-L\fP,\fB \-\-latency\fB= Socket QoS Latency. .TP .BI \-Y\fP,\fB \-\-phy\fB= Socket QoS PHY. .UNINDENT .TS box center; l|l. T{ \fIPHY\fP T} T{ Description T} _ T{ \fB0x01\fP T} T{ LE 1M T} _ T{ \fB0x02\fP T} T{ LE 2M T} _ T{ \fB0x03\fP T} T{ LE Coded T} .TE .INDENT 0.0 .TP .BI \-R\fP,\fB \-\-rtn\fB= Socket QoS retransmissions. .TP .BI \-B\fP,\fB \-\-preset\fB= Socket QoS preset. .UNINDENT .sp \-G, \-\-CIG/BIG= Socket QoS CIG/BIG ID. .sp \-T, \-\-CIS/BIS= Socket QoS CIS/BIS ID. .INDENT 0.0 .TP .BI \-V\fP,\fB \-\-type\fB= Socket destination address type: .UNINDENT .TS box center; l|l. T{ \fITYPE\fP T} T{ Description T} _ T{ \fBle_public\fP T} T{ LE Public Address T} _ T{ \fBle_random\fP T} T{ LE Random Address T} .TE .INDENT 0.0 .TP .BI \-e\fP,\fB \-\-enc\fB= Socket QoS BIG Encryption .UNINDENT .TS box center; l|l. T{ \fIENCRYPTION\fP T} T{ Description T} _ T{ \fB0x00\fP T} T{ BIG unencrypted T} _ T{ \fB0x01\fP T} T{ BIG encrypted T} .TE .INDENT 0.0 .TP .BI \-k\fP,\fB \-\-bcode\fB= Socket QoS Broadcast Code .TP .BI \-N\fP,\fB \-\-nbis\fB= Number of BISes to create as part of a BIG (BIS broadcaster) or to synchronize to (BIS broadcast receiver) .UNINDENT .SH EXAMPLES .SS Unicast Central .INDENT 0.0 .INDENT 3.5 .sp .EX $ tools/isotest \-s XX:XX:XX:XX:XX:XX .EE .UNINDENT .UNINDENT .SS Unicast Central connecting to 2 peers using CIG 0x01 .INDENT 0.0 .INDENT 3.5 .sp .EX $ tools/isotest \-G 0x01 \-s XX:XX:XX:XX:XX:XX YY:YY:YY:YY:YY:YY .EE .UNINDENT .UNINDENT .SS Unicast Peripheral .INDENT 0.0 .INDENT 3.5 .sp .EX $ tools/isotest \-d .EE .UNINDENT .UNINDENT .SS Broadcaster .INDENT 0.0 .INDENT 3.5 .sp .EX $ tools/isotest \-s 00:00:00:00:00:00 .EE .UNINDENT .UNINDENT .SS Broadcast Receiver using hci1 .INDENT 0.0 .INDENT 3.5 .sp .EX $ tools/isotest \-i hci1 \-d XX:XX:XX:XX:XX:XX .EE .UNINDENT .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Author Luiz Augusto Von Dentz .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/bluetoothctl-transport.10000644000000000000000000000005015221755466017261 xustar0020 atime=1783094070 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-transport.10000644000000000000000000002072215221755466016745 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "bluetoothctl-transport" "1" "November 2022" "BlueZ" "Linux System Administration" .SH Name bluetoothctl-transport \- Media Transport Submenu .SH SYNOPSIS .sp \fBbluetoothctl\fP [\-\-options] [transport.commands] .sp This submenu manages media transports for audio streaming using the \fBorg.bluez.MediaTransport(5)\fP interface. .SH Media Transport Commands .SS list .sp List available transports. .INDENT 0.0 .TP .B Usage \fB> list\fP .TP .B Example Display all available media transports (A2DP, LE Audio, etc.) .nf \fB> list\fP .fi .sp .UNINDENT .SS show .sp Show transport information. .INDENT 0.0 .TP .B Usage \fB> show [transport]\fP .TP .B Uses \fBorg.bluez.MediaTransport(5)\fP properties .TP .B [transport] Specific transport object path (optional, shows current if omitted) .TP .B Example Show information for currently selected transport .nf \fB> show\fP .fi .sp .TP .B Example Show transport information .nf \fB> show /org/bluez/hci0/dev_00_11_22_33_44_55/fd0\fP .fi .sp .TP .B Example Show Broadcast Isochronous Stream transport .nf \fB> show /org/bluez/hci0/dev_11_22_33_44_55_66/bis1\fP .fi .sp .UNINDENT .SS acquire .sp Acquire transport. .INDENT 0.0 .TP .B Usage \fB> acquire [transport1...]\fP .TP .B Uses \fBorg.bluez.MediaTransport(5)\fP method \fBAcquire\fP .TP .B Media transport object path to acquire for audio streaming .TP .B [transport1...] Additional transport paths for multi\-stream acquisition (optional) .TP .B Example Acquire transport .nf \fB> acquire /org/bluez/hci0/dev_00_11_22_33_44_55/fd0\fP .fi .sp .TP .B Example Acquire multiple transports .nf \fB> acquire /org/bluez/hci0/dev_00_11_22_33_44_55/fd0 /org/bluez/hci0/dev_00_11_22_33_44_55/fd1\fP .fi .sp .UNINDENT .sp Note: .sp If running the setup with an audio server that has LE Audio support (such as PipeWire) it will automatically acquire transports according to the configured roles. .SS select .sp Select transport. For transports created on a Broadcast Sink device only. This moves the transport to the \(dqbroadcasting\(dq state, pending acquire. .INDENT 0.0 .TP .B Usage \fB> select [transport1...]\fP .TP .B Uses \fBorg.bluez.MediaTransport(5)\fP method \fBSelect\fP .TP .B Broadcast sink transport path to move to broadcasting state .TP .B [transport1...] Additional transport paths for multi\-stream selection (optional) .TP .B Example Select single Broadcast Isochronous Stream .nf \fB> select /org/bluez/hci0/dev_00_11_22_33_44_55/bis1\fP .fi .sp .TP .B Example Select stereo broadcast streams .nf \fB> select /org/bluez/hci0/dev_00_11_22_33_44_55/bis1 /org/bluez/hci0/dev_00_11_22_33_44_55/bis2\fP .fi .sp .UNINDENT .sp Note: .sp If the select command receives a list of transports, they will first be linked using the \(dqLinks\(dq MediaTransport property. They will then be selected one by one, by calling the \(dqSelect\(dq MediaTransport method. After the first transport is acquired, the Broadcast Sink will create fds for the associated stream and all its links. Each link can then be acquired one by one, setting the fd for the transport and starting to receive audio. .sp The select command does not require a local endpoint to be registered beforehand. This is because if the setup runs with an audio server that has LE Audio support (such as PipeWire), the audio server is the one to register endpoints and the transports are created as a result. Once a transport is selected, the audio server will automatically acquire. .SS unselect .sp Unselect transport. For transports created on a Broadcast Sink device only. This moves the transport to the \(dqidle\(dq state, pending release by the audio server. If the transport was acquired by bluetoothctl it can be released straight away, without having to be unselected. .INDENT 0.0 .TP .B Usage \fB> unselect [transport1...]\fP .TP .B Broadcast sink transport path to move to idle state .TP .B [transport1...] Additional transport paths for multi\-stream unselection (optional) .TP .B Example Unselect broadcast stream transport .nf \fB> unselect /org/bluez/hci0/dev_00_11_22_33_44_55/bis1\fP .fi .sp .TP .B Example Unselect multiple broadcast streams .nf \fB> unselect /org/bluez/hci0/dev_00_11_22_33_44_55/bis1 /org/bluez/hci0/dev_00_11_22_33_44_55/bis2\fP .fi .sp .UNINDENT .sp Note: If running the setup with an audio server that has LE Audio support (such as PipeWire), it will prompt it to automatically release the transport. .SS release .sp Release transport. .INDENT 0.0 .TP .B Usage \fB> release [transport1...]\fP .TP .B Uses \fBorg.bluez.MediaTransport(5)\fP method \fBRelease\fP .TP .B Media transport object path to release from audio streaming .TP .B [transport1...] Additional transport paths for multi\-stream release (optional) .TP .B Example Release transport .nf \fB> release /org/bluez/hci0/dev_00_11_22_33_44_55/fd0\fP .fi .sp .TP .B Example Release multiple transports .nf \fB> release /org/bluez/hci0/dev_00_11_22_33_44_55/fd0 /org/bluez/hci0/dev_00_11_22_33_44_55/fd1\fP .fi .sp .UNINDENT .sp Note: .sp Transports acquired by an audio server, can only be released by said audio server. .SS send .sp Send contents of a file. .INDENT 0.0 .TP .B Usage \fB> send [transport1...]\fP .TP .B Media transport object path to send audio data through .TP .B Path to audio file to transmit (supports WAV, MP3, PCM formats) .TP .B [transport1...] Additional transport paths for multi\-stream sending (optional) .TP .B Example Send encoded audio file via transport .nf \fB> send /org/bluez/hci0/dev_00_11_22_33_44_55/fd0 /home/user/music.\fP .fi .sp .TP .B Example Send to multiple transports simultaneously .nf \fB> send /org/bluez/hci0/dev_00_11_22_33_44_55/fd0 /home/user/stereo\-left. /org/bluez/hci0/dev_00_11_22_33_44_55/fd1 /home/user/stereo\-rigth.\fP .fi .sp .UNINDENT .SS receive .sp Get/Set file to receive. .INDENT 0.0 .TP .B Usage \fB> receive [filename]\fP .TP .B Media transport object path to receive audio data from .TP .B [filename] Path to save received audio data (optional, shows current if omitted) .TP .B Example Show current receive file for transport .nf \fB> receive /org/bluez/hci0/dev_00_11_22_33_44_55/fd0\fP .fi .sp .TP .B Example Set file to receive audio data .nf \fB> receive /org/bluez/hci0/dev_00_11_22_33_44_55/fd0 /tmp/recorded_audio.wav\fP .fi .sp .TP .B Example Set file for broadcast audio capture (note quotes) .nf \fB> receive /org/bluez/hci0/dev_11_22_33_44_55_66/bis1 \(dq/home/user/My Recordings/broadcast.wav\(dq\fP .fi .sp .UNINDENT .SS volume .sp Get/Set transport volume. .INDENT 0.0 .TP .B Usage \fB> volume [value]\fP .TP .B Uses \fBorg.bluez.MediaTransport(5)\fP property \fBVolume\fP .TP .B Media transport object path to control volume for .TP .B [value] Volume level (0\-127, optional, shows current if omitted) .TP .B Example Show current volume level .nf \fB> volume /org/bluez/hci0/dev_00_11_22_33_44_55/fd0\fP .fi .sp .TP .B Example Set volume to 100 .nf \fB> volume /org/bluez/hci0/dev_00_11_22_33_44_55/fd0 100\fP .fi .sp .UNINDENT .SS metadata .sp Get/Set Transport Metadata. .INDENT 0.0 .TP .B Usage \fB> metadata [value...]\fP .TP .B Media transport object path .TP .B [value...] Metadata value as hex string (optional, shows current if omitted) .TP .B Example Show current metadata for transport .nf \fB> metadata /org/bluez/hci0/dev_00_11_22_33_44_55/fd0\fP .fi .sp .TP .B Example Set metadata value .nf \fB> metadata /org/bluez/hci0/dev_00_11_22_33_44_55/fd0 0x03020100\fP .fi .sp .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.Call.50000644000000000000000000000005015221755415015276 xustar0020 atime=1783094029 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Call.50000644000000000000000000001123715221755415014763 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.Call1" "5" "May 2025" "BlueZ" "Linux System Administration" .SH Name org.bluez.Call1 \- BlueZ D-Bus Telephony Call API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.Call1 [experimental] .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/telephony#/call# .TP .B Used by \fBbluetoothctl\-telephony(1)\fP .UNINDENT .SS Methods .SS void Answer() .sp Answers an incoming call. Only valid if the state of the call is \(dqincoming\(dq. .sp Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > telephony.answer .UNINDENT .SS void Hangup() .sp Hangs up the call. .sp For an incoming call, the call is hung up using ATH or equivalent. For a waiting call, the remote party is notified by using the User Determined User Busy (UDUB) condition. This is generally implemented using CHLD=0. .sp Please note that the GSM specification does not allow the release of a held call when a waiting call exists. This is because 27.007 allows CHLD=1X to operate only on active calls. Hence a held call cannot be hung up without affecting the state of the incoming call (e.g. using other CHLD alternatives). Most manufacturers provide vendor extensions that do allow the state of the held call to be modified using CHLD=1X or equivalent. It should be noted that Bluetooth HFP specifies the classic 27.007 behavior and does not allow CHLD=1X to modify the state of held calls. .sp Based on the discussion above, it should also be noted that releasing a particular party of a held multiparty call might not be possible on some implementations. It is recommended for the applications to structure their UI accordingly. .sp NOTE: Releasing active calls does not produce side\-effects. That is the state of held or waiting calls is not affected. As an exception, in the case where a single active call and a waiting call are present, releasing the active call will result in the waiting call transitioning to the \(aqincoming\(aq state. .sp Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > telephony.hangup .UNINDENT .SS Properties .SS string LineIdentification [readonly] .sp Contains the Line Identification information returned by the network, if present. For incoming calls this is effectively the CLIP. For outgoing calls this attribute will hold the dialed number, or the COLP if received by the audio gateway. .sp Please note that COLP may be different from the dialed number. A special \(dqwithheld\(dq value means the remote party refused to provide caller ID and the \(dqoverride category\(dq option was not provisioned for the current subscriber. .SS string IncomingLine [readonly, optional] .sp Contains the Called Line Identification information returned by the network. This is only available for incoming calls and indicates the local subscriber number which was dialed by the remote party. This is useful for subscribers which have a multiple line service with their network provider and would like to know what line the call is coming in on. .SS string Name [readonly] .sp Contains the Name Identification information returned by the network, if present. .SS boolean Multiparty [readonly] .sp Contains the indication if the call is part of a multiparty call or not. .sp Notifications if a call becomes part or leaves a multiparty call are sent. .SS string State [readonly] .sp Contains the state of the current call. .sp Possible values: .INDENT 0.0 .TP .B \(dqactive\(dq The call is active .TP .B \(dqheld\(dq The call is on hold .TP .B \(dqdialing\(dq The call is being dialed .TP .B \(dqalerting\(dq The remote party is being alerted .TP .B \(dqincoming\(dq Incoming call in progress .TP .B \(dqwaiting\(dq Call is waiting .TP .B \(dqresponse_and_hold\(dq Incoming call has been set on hold .TP .B \(dqdisconnected\(dq No further use of this object is allowed, it will be destroyed shortly .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.obex.Message.50000644000000000000000000000005015221755403016740 xustar0020 atime=1783094019 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.Message.50000644000000000000000000000716615221755403016433 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.obex.Message" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.obex.Message \- BlueZ D-Bus OBEX Message API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez.obex .TP .B Interface org.bluez.obex.Message1 .TP .B Object path [Session object path]/message{#} .UNINDENT .SS Methods .SS object, dict Get(string targetfile, boolean attachment) .sp Download message and store it in the target file. .sp If an empty target file is given, a temporary file will be automatically generated. .sp The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. .sp The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see \fBorg.bluez.obex.Transfer(5)\fP for the possible list of properties. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS Properties .SS string Folder [readonly] .sp Folder which the message belongs to .SS string Subject [readonly] .sp Message subject .SS string Timestamp [readonly] .sp Message timestamp .SS string Sender [readonly] .sp Message sender name .SS string SenderAddress [readonly] .sp Message sender address .SS string ReplyTo [readonly] .sp Message Reply\-To address .SS string Recipient [readonly] .sp Message recipient name .SS string RecipientAddress [readonly] .sp Message recipient address .SS string Type [readonly] .sp Message type .sp Possible values: .INDENT 0.0 .TP .B \(dqemail\(dq .TP .B \(dqsms\-gsm\(dq .TP .B \(dqsms\-cdma\(dq .TP .B \(dqmms\(dq .UNINDENT .SS uint64 Size [readonly] .sp Message size in bytes .SS string Status [readonly] .sp Message reception status .sp Possible values: .INDENT 0.0 .TP .B \(dqcomplete\(dq .TP .B \(dqfractioned\(dq .TP .B \(dqnotification\(dq .UNINDENT .SS boolean Priority [readonly] .sp Message priority flag .SS boolean Read [read/write] .sp Message read flag .SS boolean Deleted [writeonly] .sp Message deleted flag .SS boolean Sent [readonly] .sp Message sent flag .SS boolean Protected [readonly] .sp Message protected flag .SS string DeliveryStatus [readonly] [optional] .sp Message delivery status .sp Possible values: .INDENT 0.0 .TP .B \(dqdelivered\(dq .TP .B \(dqsent\(dq .TP .B \(dqunknown\(dq .UNINDENT .SS uint64 ConversationId [readonly] [required] .sp Message conversation id sent by Server which servers as Unique identification of the conversation. .SS string ConversationName [readonly] [optional] .sp Human readable name of the conversation .SS string Direction [readonly] [required] .sp Indicate the direction of the message .sp Possible values: .INDENT 0.0 .TP .B \(dqincoming\(dq .TP .B \(dqoutgoing\(dq .TP .B \(dqoutgoingdraft\(dq .TP .B \(dqoutgoingpending\(dq .UNINDENT .SS string AttachmentMimeTypes [readonly] [optional] .sp MIME type of the attachment .\" End of generated man page. bluez-5.87/doc/PaxHeaders/pics-opp.txt0000644000000000000000000000005012572170650014720 xustar0020 atime=1770587296 20 ctime=1783094112 bluez-5.87/doc/pics-opp.txt0000644000000000000000000002240512572170650014404 0ustar00rootrootOPP PICS for the PTS tool. PTS version: 6.2.0 * - different than PTS defaults # - not yet implemented/supported M - mandatory O - optional Roles ------------------------------------------------------------------------------- Parameter Name Selected Description ------------------------------------------------------------------------------- TSPC_OPP_1_1 True (*) Role: Object Push Client (C.1) TSPC_OPP_1_2 True (*) Role: Object Push Server (C.1) ------------------------------------------------------------------------------- C.1: Mandatory to support at least one of the defined roles. ------------------------------------------------------------------------------- Client Profile Version ------------------------------------------------------------------------------- Parameter Name Selected Description ------------------------------------------------------------------------------- TSPC_OPP_1b_1 False Client supports OPP version 1.1. (C.1) TSPC_OPP_1b_2 True (*) Client supports OPP version 1.2. (C.1) ------------------------------------------------------------------------------- C.1: It is mandatory to support at least one of the profile versions. ------------------------------------------------------------------------------- Client Application Features ------------------------------------------------------------------------------- Parameter Name Selected Description ------------------------------------------------------------------------------- TSPC_OPP_2_1 True Client: Perform Service Discovery request (M) TSPC_OPP_2_2 True Client: Authentication/PIN exchange supported. (M) TSPC_OPP_2_2a True (*) Client: Require Authentication/PIN by default. (O) TSPC_OPP_2_3 True Client: Object Push (M) TSPC_OPP_2_4 True (*) Client: vCard 2.1 (C.3) TSPC_OPP_2_5 False Client: vCalender 1.0 (O) TSPC_OPP_2_6 False Client: vMsg as defined in IrMC 1.1 (O) TSPC_OPP_2_7 False Client: vNote as defined in IrMC 1.1 (O) TSPC_OPP_2_8 True (*) Client: Support content formats other than those declared in TSPC_OPP_2_4 through TSPC_OPP_2_7. (O) TSPC_OPP_2_8a False Client: Support specific set of other content formats. (C.4) TSPC_OPP_2_8b True (*) Client: Support all content formats. (C.4) TSPC_OPP_2_9 True (*) Client: Push multiple vCard objects. (O) TSPC_OPP_2_9a True (*) Client: Push multiple vCard objects using different PUT operations. (C.5) TSPC_OPP_2_9b False Client: Push multiple vCard objects using the same PUT operation. (C.5) TSPC_OPP_2_10 True (*) Client: Push multiple vCalender objects. (O) TSPC_OPP_2_10a True Client: Push multiple vCalendar objects using different PUT operations. (C.6) TSPC_OPP_2_10b False Client: Push multiple vCalendar objects using the same PUT operation. (C.6) TSPC_OPP_2_11 True (*) Client: Push multiple vMsg objects. (O) TSPC_OPP_2_11a True (*) Client: Push multiple vMsg objects using different PUT operations. (C.7) TSPC_OPP_2_11b False Client: Push multiple vMsg objects using the same PUT operation. (C.7) TSPC_OPP_2_12 True (*) Client: Push multiple vNote objects. (O) TSPC_OPP_2_12a True (*) Client: Push multiple vNote objects using different PUT operations. (C.8) TSPC_OPP_2_12b False Client: Push multiple vNote objects using the same PUT operation. (C.8) TSPC_OPP_2_13 True (*) Client: Pull business card (O) TSPC_OPP_2_14 True (*) Client: vCard 2.1 (C.1) TSPC_OPP_2_15 True (*) Client: Exchange business card (O) TSPC_OPP_2_16 False Client: vCard 2.1 (C.2) TSPC_OPP_2_17 True (*) GOEP v2 (C.9) TSPC_OPP_2_18 True (*) GOEP v2 Backward Compatibility (C.9) TSPC_OPP_2_19 True (*) OBEX over L2CAP (C.9) TSPC_OPP_2_20 False OBEX Reliable Session (C.10) TSPC_OPP_2_21 False OBEX SRM (C.10) TSPC_OPP_2_22 False Send OBEX SRMP header (C.10) TSPC_OPP_2_23 False Receive OBEX SRMP header (C.11) ------------------------------------------------------------------------------- C.1: Mandatory to Support IF (TSPC_OPP_2_13) Business Card Pull is supported. C.2: Mandatory to Support IF (TSPC_OPP_2_15) Business Card Exchange is supported. C.3: vCard 2.1 support is required for devices containing phonebook applications. vCard 2.1 support optional for other devices. C.4: Mandatory to support one of TSPC_OPP_2_8a or TSPC_OPP_2_8b if TSPC_OPP_2_8 is supported. Otherwise, both items are excluded. C.5: Mandatory to support at least one of TSPC_OPP_2_9a and TSPC_OPP_2_9b if TSPC_OPP_2_9 is supported. Otherwise, both items are excluded. C.6: Mandatory to support at least one of TSPC_OPP_2_10a and TSPC_OPP_2_10b if TSPC_OPP_2_10 is supported. Otherwise, both items are excluded. C.7: Mandatory to support at least one of TSPC_OPP_2_11a and TSPC_OPP_2_11b if TSPC_OPP_2_11 is supported. Otherwise, both items are excluded. C.8: Mandatory to support at least one of TSPC_OPP_2_12a and TSPC_OPP_2_12b if TSPC_OPP_2_12 is supported. Otherwise, both items are excluded. C.9: Mandatory if TSPC_OPP_1b_2 supported. C.10: Optional to support if TSPC_OPP_1b_2 supported else excluded. C.11: Mandatory if TSPC_OPP_17 and TSPC_OPP_21 supported else excluded. ------------------------------------------------------------------------------- Server Profile Version ------------------------------------------------------------------------------- Parameter Name Selected Description ------------------------------------------------------------------------------- TSPC_OPP_2b_1 False Server supports OPP version 1.1. TSPC_OPP_2b_2 True (*) Server supports OPP version 1.2. ------------------------------------------------------------------------------- C.1: It is mandatory to support at least one of the profile versions. ------------------------------------------------------------------------------- Server Application Features ------------------------------------------------------------------------------- Parameter Name Selected Description ------------------------------------------------------------------------------- TSPC_OPP_3_1 True Server: Provide information on supported contents type on service discovery request. (M) TSPC_OPP_3_2 True Server: Authentication/PIN exchange supported. (M) TSPC_OPP_3_3 True Server: Object Push (M) TSPC_OPP_3_3a True (*) Server: Receive multiple objects in the same PUT operation. (O) TSPC_OPP_3_4 True (*) Server: vCard 2.1 (C.3) TSPC_OPP_3_5 False Server: vCalender 1.0 format (O) TSPC_OPP_3_6 False Server: vMsg as defined in IrMC 1.1 (O) TSPC_OPP_3_7 False Server: vNote as defined in IrMC 1.1 (O) TSPC_OPP_3_8 True (*) Server: Support content formats other than those declared in TSPC_OPP_3_4 through TSPC_OPP_3_7. (O) TSPC_OPP_3_8a False Server: Support specific set of other content formats. (C.4) TSPC_OPP_3_8b True (*) Server: Support all content formats. (C.4) TSPC_OPP_3_9 True (*) Server: Object Push vCard reject. (O) TSPC_OPP_3_10 True (*) Server: Object Push vCal reject. (O) TSPC_OPP_3_11 True (*) Server: Object Push vMsg reject. (O) TSPC_OPP_3_12 True (*) Server: Object Push vNote reject. (O) TSPC_OPP_3_13 True (*) Server: Business card pull (O.1) TSPC_OPP_3_14 True (*) Server: vCard 2.1 (C.1) TSPC_OPP_3_15 True (*) Server: Business card pull reject. (O) TSPC_OPP_3_16 True (*) Server: Business card exchange (O.2) TSPC_OPP_3_17 True (*) Server: vCard 2.1 (C.2) TSPC_OPP_3_18 True (*) Server: Business card exchange reject. (O) TSPC_OPP_3_19 True (*) GOEP v2 (C.5) TSPC_OPP_3_20 True (*) GOEP v2 Backward Compatibility (C.5) TSPC_OPP_3_21 True (*) OBEX over L2CAP (C.5) TSPC_OPP_3_22 False OBEX Reliable Session (C.16) TSPC_OPP_3_23 False OBEX SRM (C.6) TSPC_OPP_3_24 False Send OBEX SRMP header (C.6) TSPC_OPP_3_25 False Receive OBEX SRMP header (C.7) ------------------------------------------------------------------------------- O.1: IF NOT Supported, an error message must be sent on request for Business Card Pull. O.2: IF NOT Supported, an error message must be sent on request for Business Card Exchange. C.1: Mandatory to Support IF (TSPC_OPP_3_13) Business Card Pull is supported. C.2: Mandatory to Support IF (TSPC_OPP_3_16) Business Card Exchange is supported. C.3: vCard 2.1 support is required for devices containing phonebook applications. vCard 2.1 support optional for other devices. C.4: Mandatory to support one of TSPC_OPP_3_8a or TSPC_OPP_3_8b if TSPC_OPP_3_8 is supported. Otherwise, both items are excluded. C.5: Mandatory if TSPC_OPP_2b_2 supported. C.6: Optional to support if TSPC_OPP_2b_2 supported, else excluded. C.7: Mandatory if TSPC_OPP_3_19 and TSPC_OPP_3_23 supported else excluded. ------------------------------------------------------------------------------- Additional OPP Capabilities ------------------------------------------------------------------------------- Parameter Name Selected Description ------------------------------------------------------------------------------- TSPC_OPP_4_1 False Abort-Push Operation (O) TSPC_OPP_4_2 False Intentionally Left Blank (N/A) TSPC_OPP_4_3 True (*) Multiple vCards transferred as a single vObject (C.1) TSPC_OPP_4_4 True (*) Multiple vCards transfer (C.1) TSPC_OPP_4_5 True (*) vCards with multiple Phone Number Fields (C.1) TSPC_OPP_4_6 True (*) Push vCal to Different Time Zone Server (C.1) ------------------------------------------------------------------------------- C.1: Optional if TSPC_OPP_1_2 is supported, otherwise excluded. ------------------------------------------------------------------------------- bluez-5.87/doc/PaxHeaders/org.bluez.Battery.50000644000000000000000000000005015221755315016034 xustar0020 atime=1783093965 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Battery.50000644000000000000000000000300715221755315015515 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.Battery" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.Battery \- BlueZ D-Bus Battery API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.Battery1 .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} .UNINDENT .SS Properties .SS byte Percentage [readonly] .sp The percentage of battery left as an unsigned 8\-bit integer. .SS string Source [readonly, optional] .sp Describes where the battery information comes from. .sp This property is informational only and may be useful for debugging purposes. .sp Providers from \fBorg.bluez.BatteryProvider(5)\fP may make use of this property to indicate where the battery report comes from (e.g. \(dqHFP 1.7\(dq, \(dqHID\(dq, or the profile UUID). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.GattCharacteristic.50000644000000000000000000000005015221755350020171 xustar0020 atime=1783093992 20 ctime=1783094112 bluez-5.87/doc/org.bluez.GattCharacteristic.50000644000000000000000000002500115221755350017650 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.GattCharacteristic" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.GattCharacteristic \- BlueZ D-Bus GattCharacteristic API documentation .SH Description .sp GATT local/server and remote/client characteristic attribute representation share the same high\-level D\-Bus API. .sp Local/Server refers to GATT based characteristics exported by a plugin or an external application. .sp Remote/Client refers to GATT characteristics exported by the peer. .SH Interface .SS Client .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.GattCharacteristic1 .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/service#/char# .TP .B Used by \fBbluetoothctl\-gatt(1)\fP .UNINDENT .SS Server .INDENT 0.0 .TP .B Service unique name .TP .B Interface org.bluez.GattCharacteristic1 .TP .B Object path freely definable .UNINDENT .SS Methods .SS array{byte} ReadValue(dict options) .sp Issues a request to read the value of the characteristic and returns the value if the operation was successful. .sp Possible options: .INDENT 0.0 .TP .B uint16 offset Read start offset in bytes. .TP .B uint16 mtu (server only) Exchange MTU in bytes. .TP .B object device (server only) Device object. .TP .B string link (server only) Link type. .sp Possible values: .INDENT 7.0 .TP .B \(dqBR/EDR\(dq .TP .B \(dqLE\(dq .UNINDENT .UNINDENT .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.Failed Possible values: string 0x80 \- 0x9f .TP .B org.bluez.Error.InProgress .TP .B org.bluez.Error.NotPermitted .TP .B org.bluez.Error.NotAuthorized .TP .B org.bluez.Error.InvalidOffset .TP .B org.bluez.Error.NotSupported .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > gatt.read [offset] .UNINDENT .SS void WriteValue(array{byte} value, dict options) .sp Issues a request to write the value of the characteristic. .sp Possible options: .INDENT 0.0 .TP .B uint16 offset Write start offset in bytes. .TP .B string type Possible values: .INDENT 7.0 .TP .B \(dqcommand\(dq Use Write without response procedure. .TP .B \(dqrequest\(dq Use Write with response procedure. .TP .B \(dqreliable\(dq Use Reliable Write procedure. .UNINDENT .TP .B uint16 mtu Exchanged MTU (Server only). .TP .B object device Device path (Server only). .TP .B string link Link type (Server only). .sp Possible values: .INDENT 7.0 .TP .B \(dqBR/EDR\(dq .TP .B \(dqLE\(dq .UNINDENT .TP .B boolean prepare\-authorize True if prepare authorization request. .UNINDENT .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.Failed Possible values: string 0x80 \- 0x9f .TP .B org.bluez.Error.InProgress .TP .B org.bluez.Error.NotPermitted .TP .B org.bluez.Error.InvalidValueLength .TP .B org.bluez.Error.NotAuthorized .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.ImproperlyConfigured .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > gatt.write [offset] [type] .UNINDENT .SS fd, uint16 AcquireWrite(dict options) [optional] .sp Acquire file descriptor and MTU for writing. Only sockets are supported. Usage of WriteValue will be locked causing it to return NotPermitted error. .sp For server the MTU returned shall be equal or smaller than the negotiated MTU. .sp For client it only works with characteristic that has \fBWriteAcquired\fP property which relies on write\-without\-response \fBFlag\fP\&. .sp To release the lock the client shall close the file descriptor, a HUP is generated in case the device is disconnected. .sp Note: the MTU can only be negotiated once and is symmetric therefore this method may be delayed in order to have the exchange MTU completed, because of that the file descriptor is closed during reconnections as the MTU has to be renegotiated. .sp Possible options: .INDENT 0.0 .TP .B object device Object Device (Server only). .TP .B uint16 mtu Exchanged MTU (Server only). .TP .B string link Link type (Server only). .sp Possible values: .INDENT 7.0 .TP .B \(dqBR/EDR\(dq .TP .B \(dqLE\(dq .UNINDENT .UNINDENT .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.NotSupported .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > gatt.acquire\-write .UNINDENT .SS fd, uint16 AcquireNotify(dict options) [optional] .sp Acquire file descriptor and MTU for notify. Only sockets are support. .sp Usage of StartNotify will be locked causing it to return \fBorg.bluez.Error.NotPermitted\fP\&. .sp For server the MTU returned shall be equal or smaller than the negotiated MTU. .sp Only works with characteristic that has \fBNotifyAcquired\fP property which relies on presence of \fB\(dqnotify\(dq or \(dqindicate\(dq\fP \fBFlag\fP and no other client have called \fBStartNotify()\fP\&. .sp Notification are enabled during this procedure so \fBStartNotify()\fP shall not be called, any notification will be dispatched via file descriptor therefore the Value property is not affected during the time where notify has been acquired. .sp To release the lock the client shall close the file descriptor, a HUP is generated in case the device is disconnected. .sp As a client if indication procedure is used the confirmation is generated automatically once received, for a server if the file descriptor is writable (POLLOUT) then upon receiving a confirmation from the client one byte (0x01) is written to the file descriptor. .sp Note: the MTU can only be negotiated once and is symmetric therefore this method may be delayed in order to have the exchange MTU completed, because of that the file descriptor is closed during reconnections as the MTU has to be renegotiated. .sp Possible options: .INDENT 0.0 .TP .B object device Object Device (Server only). .TP .B uint16 mtu Exchanged MTU (Server only). .TP .B string link Link type (Server only). .sp Possible values: .INDENT 7.0 .TP .B \(dqBR/EDR\(dq .TP .B \(dqLE\(dq .UNINDENT .UNINDENT .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.NotPermitted .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > gatt.acquire\-notify .UNINDENT .SS void StartNotify() .sp Starts a notification session from this characteristic if it supports value notifications or indications. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.NotPermitted .TP .B org.bluez.Error.InProgress .TP .B org.bluez.Error.NotConnected .TP .B org.bluez.Error.NotSupported .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > gatt.notify .UNINDENT .SS void StopNotify() .sp Stops or cancel session previously created by \fBStartNotify()\fP\&. .sp Note that notifications from a characteristic are shared between sessions thus calling StopNotify will release a single session. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.Failed .UNINDENT .SS void Confirm() [noreply, optional] (Server only) .sp Confirms value was received. .sp Possible Errors: .sp org.bluez.Error.Failed .SS Properties .SS string UUID [read\-only] .sp 128\-bit characteristic UUID. .SS object Service [read\-only] .sp Object path of the GATT service the characteristic belongs to. .SS array{byte} Value [read\-only, optional] .sp The cached value of the characteristic. This property gets updated only after a successful read request and when a notification or indication is received, upon which a PropertiesChanged signal will be emitted. .SS boolean WriteAcquired [read\-only, optional] .sp True, if this characteristic has been acquired by any client using AcquireWrite. .sp For client properties is omitted in case \(aqwrite\-without\-response\(aq flag is not set. .sp For server the presence of this property indicates that AcquireWrite is supported. .SS boolean NotifyAcquired [read\-only, optional] .sp True, if this characteristic has been acquired by any client using AcquireNotify. .sp For client this properties is omitted in case \(aqnotify\(aq flag is not set. .sp For server the presence of this property indicates that AcquireNotify is supported. .SS boolean Notifying [read\-only, optional] .sp True, if notifications or indications on this characteristic are currently enabled. .SS array{string} Flags [read\-only] .sp Defines how the characteristic value can be used. See Core spec \(dqTable 3.5: Characteristic Properties bit field\(dq, and \(dqTable 3.8: Characteristic Extended Properties bit field\(dq. .sp The \(dqx\-notify\(dq and \(dqx\-indicate\(dq flags restrict access to notifications and indications by imposing write restrictions on a characteristic\(aqs client characteristic configuration descriptor. .sp Possible values: .INDENT 0.0 .TP .B \(dqbroadcast\(dq .TP .B \(dqread\(dq .TP .B \(dqwrite\-without\-response\(dq .TP .B \(dqwrite\(dq .TP .B \(dqnotify\(dq .TP .B \(dqindicate\(dq .TP .B \(dqauthenticated\-signed\-writes\(dq .TP .B \(dqextended\-properties\(dq .TP .B \(dqreliable\-write\(dq .TP .B \(dqwritable\-auxiliaries\(dq .TP .B \(dqencrypt\-read\(dq .TP .B \(dqencrypt\-write\(dq .TP .B \(dqencrypt\-notify\(dq (Server only) .TP .B \(dqencrypt\-indicate\(dq (Server only) .TP .B \(dqencrypt\-authenticated\-read\(dq .TP .B \(dqencrypt\-authenticated\-write\(dq .TP .B \(dqencrypt\-authenticated\-notify\(dq (Server only) .TP .B \(dqencrypt\-authenticated\-indicate\(dq (Server only) .TP .B \(dqsecure\-read\(dq (Server only) .TP .B \(dqsecure\-write\(dq (Server only) .TP .B \(dqsecure\-notify\(dq (Server only) .TP .B \(dqsecure\-indicate\(dq (Server only) .TP .B \(dqauthorize\(dq .UNINDENT .SS uint16 Handle [read\-only] (Client Only) .sp Characteristic handle. .SS uint16 Handle [read\-write, optional] (Server Only) .sp Characteristic handle. When available in the server it would attempt to use to allocate into the database which may fail, to auto allocate the value 0x0000 shall be used which will cause the allocated handle to be set once registered. .SS uint16 MTU [read\-only] .sp Characteristic MTU, this is valid both for \fBReadValue()\fP and \fBWriteValue()\fP but either method can use long procedures when supported. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.MediaItem.50000644000000000000000000000005015221755332016257 xustar0020 atime=1783093978 20 ctime=1783094112 bluez-5.87/doc/org.bluez.MediaItem.50000644000000000000000000000621715221755332015746 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.MediaItem" "5" "September 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.MediaItem \- BlueZ D-Bus MediaItem API documentation .SH Interface .INDENT 0.0 .TP .B Service unique name (Target role) org.bluez (Controller role) .TP .B Interface org.bluez.MediaItem1 .TP .B Object path freely definable (Target role) [variable prefix]/{hci0,hci1,...}/dev_{BDRADDR}/player#/item# (Controller role) .TP .B Used by \fBbluetoothctl\-player(1)\fP .UNINDENT .SS Methods .SS void Play() .sp Play item .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl [player] > play [item] .UNINDENT .SS void AddtoNowPlaying() .sp Add item to now playing list .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl [player] > queue .UNINDENT .SS Properties .SS object Player [readonly] .sp Player object path the item belongs to .SS string Name [readonly] .sp Item displayable name .SS string Type [readonly] .sp Item type .sp Possible values: .INDENT 0.0 .TP .B \(dqvideo\(dq .TP .B \(dqaudio\(dq .TP .B \(dqfolder\(dq .UNINDENT .SS string FolderType [readonly, optional] .sp Folder type. .sp Available if property Type is \(dqFolder\(dq .sp Possible values: .INDENT 0.0 .TP .B \(dqmixed\(dq .TP .B \(dqtitles\(dq .TP .B \(dqalbums\(dq .TP .B \(dqartists\(dq .UNINDENT .SS boolean Playable [readonly, optional] .sp Indicates if the item can be played .sp Available if property Type is \(dqfolder\(dq .SS dict Metadata [readonly] .sp Item metadata. .sp Possible values: .INDENT 0.0 .TP .B string Title Item title name .sp Available if property Type is \(dqaudio\(dq or \(dqvideo\(dq .TP .B string Artist Item artist name .sp Available if property Type is \(dqaudio\(dq or \(dqvideo\(dq .TP .B string Album Item album name .sp Available if property Type is \(dqaudio\(dq or \(dqvideo\(dq .TP .B string Genre Item genre name .sp Available if property Type is \(dqaudio\(dq or \(dqvideo\(dq .TP .B uint32 NumberOfTracks Item album number of tracks in total .sp Available if property Type is \(dqaudio\(dq or \(dqvideo\(dq .TP .B uint32 Number Item album number .sp Available if property Type is \(dqaudio\(dq or \(dqvideo\(dq .TP .B uint32 Duration Item duration in milliseconds .sp Available if property Type is \(dqaudio\(dq or \(dqvideo\(dq .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.GattService.rst0000644000000000000000000000005015142177620017305 xustar0020 atime=1783093990 20 ctime=1783094112 bluez-5.87/doc/org.bluez.GattService.rst0000644000000000000000000000355415142177620016775 0ustar00rootroot===================== org.bluez.GattService ===================== ------------------------------------------------- BlueZ D-Bus GattService API documentation ------------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Description =========== GATT local/server and remote/client services share the same high-level D-Bus API. Local/Server refers to GATT based service exported by a plugin or an external application. Remote/Client refers to GATT services exported by the peer. Interface ========= Client ------ :Service: org.bluez :Interface: org.bluez.GattService1 :Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/service# :Used by: **bluetoothctl-gatt(1)** Server ------ :Service: unique name :Interface: org.bluez.GattService1 :Object path: freely definable Properties ---------- string UUID [read-only] ``````````````````````` 128-bit service UUID. boolean Primary [read-only] ``````````````````````````` Indicates whether or not this GATT service is a primary service. If false, the service is secondary. object Device [read-only, optional] ``````````````````````````````````` Object path of the Bluetooth device the service belongs to. Only present on services from remote devices. array{object} Includes [read-only, optional] ```````````````````````````````````````````` Array of object paths representing the included services of this service. uint16 Handle [read-only] (client only) ``````````````````````````````````````` Service handle. uint16 Handle [read-write, optional] (Server Only) `````````````````````````````````````````````````` Service handle. When available in the server it would attempt to use to allocate into the database which may fail, to auto allocate the value 0x0000 shall be used which will cause the allocated handle to be set once registered. bluez-5.87/doc/PaxHeaders/org.bluez.MediaFolder.50000644000000000000000000000005015221755331016573 xustar0020 atime=1783093977 20 ctime=1783094112 bluez-5.87/doc/org.bluez.MediaFolder.50000644000000000000000000000652615221755331016265 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.MediaFolder" "5" "September 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.MediaFolder \- BlueZ D-Bus MediaFolder API documentation .SH Interface .INDENT 0.0 .TP .B Service unique name (Target role) org.bluez (Controller role) .TP .B Interface org.bluez.MediaFolder1 .TP .B Object path freely definable (Target role) [variable prefix]/{hci0,hci1,...}/dev_{BDRADDR}/player# (Controller role) .TP .B Used by \fBbluetoothctl\-player(1)\fP .UNINDENT .SS Methods .SS object Search(string value, dict filter) .sp Return a folder object containing the search result. .sp To list the items found use the folder object returned and pass to \fBChangeFolder\fP\&. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl [player] > search .UNINDENT .SS array{objects, properties} ListItems(dict filter) .sp Return a list of items found .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl [player] > list\-items [start] [end] .UNINDENT .SS void ChangeFolder(object folder) .sp Change current folder. .sp Note: By changing folder the items of previous folder might be destroyed and have to be listed again, the exception is NowPlaying folder which should be always present while the player is active. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl [player] > change\-folder .UNINDENT .SS Properties .SS uint32 NumberOfItems [readonly] .sp Number of items in the folder .SS string Name [readonly] .sp Folder name: .sp Possible values: .INDENT 0.0 .TP .B \(dq/Filesystem/...\(dq Filesystem scope .TP .B \(dq/NowPlaying/...\(dq NowPlaying scope .UNINDENT .sp Note: /NowPlaying folder might not be listed if player is stopped, folders created by Search are virtual so once another Search is perform or the folder is changed using ChangeFolder it will no longer be listed. .SS Filters .INDENT 0.0 .TP .B uint32 Start Offset of the first item. .sp Default value: 0 .TP .B uint32 End Offset of the last item. .sp Default value: NumbeOfItems .TP .B array{string} Attributes Item properties that should be included in the list. .sp Possible Values: .INDENT 7.0 .INDENT 3.5 \(dqtitle\(dq, \(dqartist\(dq, \(dqalbum\(dq, \(dqgenre\(dq, \(dqnumber\-of\-tracks\(dq, \(dqnumber\(dq, \(dqduration\(dq .sp Default Value: All .UNINDENT .UNINDENT .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.GattProfile.rst0000644000000000000000000000005015063045254017304 xustar0020 atime=1783093988 20 ctime=1783094112 bluez-5.87/doc/org.bluez.GattProfile.rst0000644000000000000000000000207615063045254016772 0ustar00rootroot===================== org.bluez.GattProfile ===================== ----------------------------------------- BlueZ D-Bus GattProfile API documentation ----------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Description =========== Local profile (GATT client) instance. By registering this type of object an application effectively indicates support for a specific GATT profile and requests automatic connections to be established to devices supporting it. Interface ========= :Service: :Interface: org.bluez.GattProfile1 :Object path: Methods ------- void Release() `````````````` This method gets called when **bluetoothd(8)** unregisters the profile. The profile can use it to do cleanup tasks. There is no need to unregister the profile, because when this method gets called it has already been unregistered. Properties ---------- array{string} UUIDs [read-only] ``````````````````````````````` 128-bit GATT service UUIDs to auto connect. bluez-5.87/doc/PaxHeaders/org.bluez.obex.ObjectPush.50000644000000000000000000000005015221755371017426 xustar0020 atime=1783094009 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.ObjectPush.50000644000000000000000000000614515221755371017115 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.obex.ObjectPush" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.obex.ObjectPush \- BlueZ D-Bus OBEX ObjectPush API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez.obex .TP .B Interface org.bluez.obex.ObjectPush1 .TP .B Object path [Session object path] .UNINDENT .SS Methods .SS object, dict SendFile(string sourcefile) .sp Sends local file to the remote device. .sp The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. .sp The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see \fBorg.bluez.obex.Transfer(5)\fP for the possible list of properties. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS object, dict PullBusinessCard(string targetfile) .sp Request the business card from a remote device and store it in the local file. .sp If an empty target file is given, a name will be automatically generated for the temporary file. .sp The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. .sp The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see \fBorg.bluez.obex.Transfer(5)\fP for the possible list of properties. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS object, dict ExchangeBusinessCards(string clientfile, string targetfile) .sp Push the client\(aqs business card to the remote device and then retrieve the remote business card and store it in a local file. .sp If an empty target file is given, a name will be automatically generated for the temporary file. .sp The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. .sp The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see \fBorg.bluez.obex.Transfer(5)\fP for the possible list of properties. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.Bearer.LE.rst0000644000000000000000000000005015063045254016563 xustar0020 atime=1783093942 20 ctime=1783094111 bluez-5.87/doc/org.bluez.Bearer.LE.rst0000644000000000000000000000725715063045254016257 0ustar00rootroot=================== org.bluez.Bearer.LE =================== --------------------------------------- BlueZ D-Bus Bearer LE API documentation --------------------------------------- :Version: BlueZ :Date: July 2025 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.Bearer.LE1 :Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} Methods ------- void Connect() [experimental] ````````````````````````````` Connects all LE profiles the remote device supports that can be connected to and have been flagged as auto-connectable. If only subset of profiles is already connected it will try to connect currently disconnected ones. If at least one profile was connected successfully this method will indicate success. Possible errors: :org.bluez.Error.NotReady: :org.bluez.Error.Failed: :org.bluez.Error.InProgress: :org.bluez.Error.AlreadyConnected: void Disconnect() [experimental] ```````````````````````````````` Disconnects all connected profiles and then terminates low-level ACL-LE connection. ACL-LE connection will be terminated even if some profiles were not disconnected properly e.g. due to misbehaving device. This method can be also used to cancel a preceding Connect call before a reply to it has been received. For non-trusted devices connected calling this method will disable incoming connections until Connect method is called again. Possible errors: :org.bluez.Error.NotConnected: Signals ------- void Disconnected(string reason, string message) [experimental] ``````````````````````````````````````````````````````````````` This signal is launched when a device is disconnected, with the reason of the disconnection. This could be used by client application, depending on internal policy, to try to reconnect to the device in case of timeout or unknown disconnection, or to try to connect to another device. Possible reasons: :org.bluez.Reason.Unknown: :org.bluez.Reason.Timeout: Connection timeout. The link supervision timeout has expired for a connection or the synchronization timeout has expired for a broadcast. :org.bluez.Reason.Local: Connection terminated by local host. The local device terminated the connection, terminated synchronization with a broadcaster, or terminated broadcasting packets. :org.bluez.Reason.Remote: Connection terminated by remote host. This disconnection can be due to: - the user on the remote device either terminated the connection or stopped broadcasting packets, - the remote device terminated the connection because of low resources, - the remote device terminated the connection because the device is about to power off. :org.bluez.Reason.Authentication: Connection terminated due to an authentication failure. :org.bluez.Reason.Suspend: Connection terminated by local host for suspend. Properties ---------- object Adapter [readonly, experimental] ``````````````````````````````````````` The object path of the adapter the set belongs to. boolean Paired [readonly, experimental] ``````````````````````````````````````` Indicates if the remote device is paired to LE bearer. Paired means the pairing process where devices exchange the information to establish an encrypted connection has been completed. boolean Bonded [readonly, experimental] ``````````````````````````````````````` Indicates if the remote device is bonded to LE bearer. Bonded means the information exchanged on pairing process has been stored and will be persisted. boolean Connected [readonly, experimental] `````````````````````````````````````````` Indicates if the remote device is currently connected to LE bearer. A PropertiesChanged signal indicate changes to this status. bluez-5.87/doc/PaxHeaders/bluetoothctl-advertise.rst0000644000000000000000000000005015142177620017653 xustar0020 atime=1783094059 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-advertise.rst0000644000000000000000000004615415142177620017346 0ustar00rootroot====================== bluetoothctl-advertise ====================== ----------------- Advertise Submenu ----------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: November 2022 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **bluetoothctl** [--options] [advertise.commands] This submenu configures LE advertising data and parameters using the **org.bluez.LEAdvertisement(5)** and **org.bluez.LEAdvertisingManager(5)** interfaces. Advertise Options Commands ========================== uuids ----- Set/Get advertise uuids. :Usage: **> uuids [uuid1 uuid2 ...]** :Uses: **org.bluez.LEAdvertisement(5)** property **ServiceUUIDs** :[uuid1 uuid2 ...]: List of UUIDs to advertise (default: none) :Example Show current advertised UUIDs: | **> uuids** :Example Advertise custom 16-bit service UUID: | **> uuids 0x1234** :Example Advertise custom 32-bit service UUID: | **> uuids 0x12345678** :Example Advertise custom 128-bit service UUID: | **> uuids 90f95193-35de-4306-a6e9-699328f15059** :Example Advertise Battery and Generic Attribute services: | **> uuids 0x180F 0x1801** :Example Advertise Device Information, Battery, and HID services: | **> uuids 0x180A 0x180F 0x1812** :Example Advertise Generic Access service: | **> uuids 0x1800** :Example Advertise Environmental Sensing service: | **> uuids 0x181A** :Example Advertise Pulse Oximeter service: | **> uuids 0x183B** :Example Mix short and full UUID formats: | **> uuids 0x180F 12345678-1234-5678-9abc-123456789abc** solicit ------- Set/Get advertise solicit uuids. :Usage: **> solicit [uuid1 uuid2 ...]** :Uses: **org.bluez.LEAdvertisement(5)** property **SolicitUUIDs** :[uuid1 uuid2 ...]: List of UUIDs to advertise (default: none) :Example Show current solicited service UUIDs: | **> solicit** :Example Solicit Generic Access service: | **> solicit 0x1800** :Example Solicit Device Information service: | **> solicit 0x180A** :Example Solicit User Data service: | **> solicit 0x181C** :Example Solicit Battery and Generic Attribute services: | **> solicit 0x180F 0x1801** :Example Solicit Device Info, Environmental Sensing, and Pulse Oximeter: | **> solicit 0x180A 0x181A 0x183B** service ------- Set/Get advertise service data. :Usage: **> service [uuid] [data=xx xx ...]** :Uses: **org.bluez.LEAdvertisement(5)** property **ServiceData** :[uuid]: Service UUID :[data=xx xx ...]: Service data :Example Show current service data: | **> service** :Example Set battery level to 100 (0x64): | **> service 0x180F 64** :Example Set battery level to 50 (0x32): | **> service 0x180F 32** :Example Set battery level to 0 (0x00): | **> service 0x180F 00** :Example Set environmental data (temperature, humidity, pressure): | **> service 0x181A 20 15 C8 07** :Example Set device information data: | **> service 0x180A 01 02 03 04** :Example Set custom service data with 128-bit UUID: | **> service 12345678-1234-5678-9abc-123456789abc FF EE DD CC** manufacturer ------------ Set/Get advertise manufacturer data. Updating is in real time while advertising. This is currently limited to 25 bytes and will return an error message of "Too much data" if that maximum has been exceeded. However, this does not check if the advertising payload length maximum has been exceeded so you may receive an error from bluetoothd that it "Failed to register advertisement" which means you need to reduce your manufacturer data length. :Usage: **> manufacturer [id] [data=xx xx ...]** :Uses: **org.bluez.LEAdvertisement(5)** property **ManufacturerData** :[id]: Manufacturer ID (default: 0x004C for Apple Inc.) :[data=xx xx ...]: Manufacturer data :Example Show current manufacturer data: | **> manufacturer** :Example Set Apple Inc. manufacturer data: | **> manufacturer 0x004C 01 02 03 04** :Example Set Apple manufacturer data with 8 bytes: | **> manufacturer 0x004C FF FE FD FC FB FA F9 F8** :Example Set Microsoft Corp. manufacturer data: | **> manufacturer 0x0006 10 11 12 13** :Example Set Samsung Electronics manufacturer data: | **> manufacturer 0x0075 AA BB CC DD EE** :Example Set Google LLC manufacturer data: | **> manufacturer 0x00E0 01 23 45 67 89 AB CD EF** :Example Set custom manufacturer ID with test data: | **> manufacturer 0xFFFF DE AD BE EF** data ---- Set/Get advertise data. This allows you to advertise data with a given type. You cannot use a registered data type value {1} with this command. For LE the advertising shows up in the primary advertisements. If you set only the type of the data without any data (data 0x0c) this will cause a parse error when turning advertise on. You can modify the advertising data while it is advertising. To get the currently set data use the command data without any arguments. :Usage: **> data [type] [data=xx xx ...]** :Uses: **org.bluez.LEAdvertisement(5)** property **Data** :[type]: Advertising data type :[data=xx xx ...]: Advertising data (default: none) :Example Show current advertising data: | **> data** :Example Set slave connection interval range data: | **> data 0x0C 01 0x0F 13** :Example Set complete local name to "Hello": | **> data 0x09 48 65 6C 6C 6F** :Example Set shortened local name to "Test": | **> data 0x08 54 65 73 74** :Example Set flags (LE General Discoverable + BR/EDR not supported): | **> data 0x01 06** :Example Set appearance (keyboard, 0x0341): | **> data 0x19 41 03** :Example Set TX power level to +4 dBm: | **> data 0x0A 04** :Example Set TX power level to -4 dBm: | **> data 0x0A FC** :Example Set URI "//google.com": | **> data 0x24 17 2F 2F 67 6F 6F 67 6C 65 2E 63 6F 6D** :Example Set service data for Battery Service (UUID 0x180F, level 100): | **> data 0x16 0F 18 64** sr-uuids -------- Set/Get scan response uuids. :Usage: **> sr-uuids [uuid1 uuid2 ...]** :[uuid1 uuid2 ...]: List of UUIDs to advertise in scan response :Example Show current scan response UUIDs: | **> sr-uuids** :Example Set Generic Access in scan response: | **> sr-uuids 0x1800** :Example Set Device Info and Battery services in scan response: | **> sr-uuids 0x180A 0x180F** :Example Set custom UUID in scan response: | **> sr-uuids 12345678-1234-5678-9abc-123456789abc** sr-solicit ---------- Set/Get scan response solicit uuids. :Usage: **> sr-solicit [uuid1 uuid2 ...]** :[uuid1 uuid2 ...]: List of UUIDs to advertise in scan response :Example Show current scan response solicited UUIDs: | **> sr-solicit** :Example Solicit Generic Attribute service in scan response: | **> sr-solicit 0x1801** :Example Solicit Device Info and Environmental Sensing in scan response: | **> sr-solicit 0x180A 0x181A** sr-service ---------- Set/Get scan response service data. :Usage: **> sr-service [uuid] [data=xx xx ...]** :[uuid]: Service UUID :[data=xx xx ...]: Service data :Example Show current scan response service data: | **> sr-service** :Example Set battery level to 128 in scan response: | **> sr-service 0x180F 80** :Example Set environmental data in scan response: | **> sr-service 0x181A 22 18 C0 05** :Example Set device info "Hello" in scan response: | **> sr-service 0x180A 48 65 6C 6C 6F** sr-manufacturer --------------- Set/Get scan response manufacturer data. :Usage: **> sr-manufacturer [id] [data=xx xx ...]** :[id]: Manufacturer ID (default: 0x004C for Apple Inc.) :[data=xx xx ...]: Manufacturer data :Example Show current scan response manufacturer data: | **> sr-manufacturer** :Example Set Apple manufacturer data in scan response: | **> sr-manufacturer 0x004C 05 06 07 08** :Example Set Microsoft manufacturer data in scan response: | **> sr-manufacturer 0x0006 20 21 22 23** :Example Set custom manufacturer data in scan response: | **> sr-manufacturer 0xFFFF CA FE BA BE** sr-data ------- Set/Get scan response data. :Usage: **> sr-data [type] [data=xx xx ...]** :[type]: Scan Response data type :[data=xx xx ...]: Scan Response data :Example Show current scan response data: | **> sr-data** :Example Set complete local name "ScanResp" in scan response: | **> sr-data 0x09 53 63 61 6E 52 65 73 70** :Example Set TX power level +8 dBm in scan response: | **> sr-data 0x0A 08** :Example Set Battery service data (level 50) in scan response: | **> sr-data 0x16 0F 18 32** discoverable ------------ Set/Get advertise discoverable. For LE discoverable on will set the LE General Discoverable Mode flag to true in the primary advertisement if on. This feature can be changed during advertising, but will only trigger LE General Discoverable Mode even if you had previously selected discoverable-timeout this will be ignored. Entering the command by itself will show the status of the setting :Usage: **> discoverable [on/off]** :Uses: **org.bluez.LEAdvertisement(5)** property **Discoverable** :[on/off]: Enable/Disable LE General Discoverable Mode :Example Show current discoverable setting: | **> discoverable** :Example Enable LE General Discoverable Mode: | **> discoverable on** :Example Disable discoverable mode: | **> discoverable off** discoverable-timeout -------------------- Set/Get advertise discoverable timeout. Using this feature in LE will cause the LE Limited Discoverable Mode flag to be set in the primary advertisement and The LE General Discoverable Mode flag will not be set. The LE Limited Discoverable Mode flag will automatically turn off after [seconds] discoverable [on] must be set to use this feature. Entering the command by itself will show the current value set. :Usage: **> discoverable-timeout [seconds]** :Uses: **org.bluez.LEAdvertisement(5)** property **DiscoverableTimeout** :[seconds]: Timeout duration in seconds for LE Limited Discoverable Mode :Example Show current discoverable timeout: | **> discoverable-timeout** :Example Set LE Limited Discoverable for 30 seconds: | **> discoverable-timeout 30** :Example Set LE Limited Discoverable for 2 minutes: | **> discoverable-timeout 120** :Example Set LE Limited Discoverable for 5 minutes: | **> discoverable-timeout 300** :Example Disable discoverable timeout (unlimited): | **> discoverable-timeout 0** tx-power -------- Show/Enable/Disable TX power to be advertised. This sets the TX Power Level field in the advertising packet. The value is in dBm and can be between -127 and 127. When this feature is turned on the LE device will advertise its transmit power in the primary advertisement. This feature can be modified while advertising. Entering the command by itself will show the current value set. :Usage: **> tx-power [on/off]** :Uses: **org.bluez.LEAdvertisement(5)** property **Includes** :[on/off]: Enable or disable TX power advertisement (optional, shows current if omitted) :Example Show current TX power advertisement setting: | **> tx-power** :Example Enable TX power advertisement with default power: | **> tx-power on** :Example Disable TX power advertisement: | **> tx-power off** name ---- Configure local name to be advertised. Local name to be used in the advertising report. If the string is too big to fit into the packet it will be truncated. It will either advertise as a complete local name or if it has to be truncated then a shortened local name. :Usage: **> name [on/off/name]** :Uses: **org.bluez.LEAdvertisement(5)** property **LocalName** :[name]: Local name (optional, shows current if omitted) :Example Show current advertised name setting: | **> name** :Example Enable advertising the system hostname: | **> name on** :Example Disable name advertisement: | **> name off** :Example Advertise "My Device" as local name: | **> name "My Device"** :Example Advertise "Smart Sensor" as local name: | **> name "Smart Sensor"** :Example Advertise device with ID suffix: | **> name "BLE-Peripheral-001"** :Example Advertise long name (will be truncated to fit): | **> name "0123456789abcdef0123456789abcdef"** :Example Long descriptive name (automatically truncated): | **> name "This is a very long device name that will be truncated"** :Example Name with special characters: | **> name "Device™"** :Example Name with accented characters: | **> name "Tëst-Dëvicë"** appearance ---------- Configure custom appearance to be advertised. :Usage: **> appearance [on/off/value]** :Uses: **org.bluez.LEAdvertisement(5)** property **Appearance** :[value]: Appearance value (optional, shows current if omitted) :Example Show current appearance setting: | **> appearance** :Example Enable appearance advertisement with default value: | **> appearance on** :Example Disable appearance advertisement: | **> appearance off** :Example Set appearance to keyboard (0x03C1): | **> appearance 961** :Example Set appearance to mouse (0x03C2): | **> appearance 962** :Example Set appearance to joystick (0x03C3): | **> appearance 963** :Example Set appearance to gamepad (0x03C4): | **> appearance 964** :Example Set appearance to generic audio sink (0x0340): | **> appearance 832** :Example Set appearance to speaker (0x0341): | **> appearance 833** :Example Set appearance to microphone (0x0342): | **> appearance 834** :Example Set appearance to headset (0x0343): | **> appearance 835** :Example Set appearance to headphones (0x0344): | **> appearance 836** :Example Set appearance to heart rate sensor (0x0540): | **> appearance 1344** :Example Set appearance to thermometer (0x05C0): | **> appearance 1472** :Example Set appearance to glucose meter (0x0640): | **> appearance 1600** :Example Set appearance to generic phone (0x0040): | **> appearance 64** :Example Set appearance to generic computer (0x0080): | **> appearance 128** :Example Set appearance to generic watch (0x00C0): | **> appearance 192** duration -------- Set/Get advertise duration. The Duration parameter configures the length of an Instance. The value is in seconds. A value of 0 indicates a default value is chosen for the Duration. The default is 2 seconds. If only one advertising Instance has been added, then the Duration value will be ignored. If multiple advertising Instances have been added, then the Duration value will be used to determine the length of time each Instance is advertised for. The Duration value is used to calculate the number of advertising events that will be used to advertise each Instance. The number of advertising events is calculated by dividing the Duration value by the advertising interval. The advertising interval is determined by the advertising parameters that are set for each Instance. The advertising interval is the maximum of the advertising intervals set for each Instance. :Usage: **> duration [seconds]** :Uses: **org.bluez.LEAdvertisement(5)** property **Duration** :[seconds]: Duration in seconds (optional, shows current if omitted) :Example Show current advertising duration setting: | **> duration** :Example Use default duration (2 seconds): | **> duration 0** :Example Set duration to 1 second: | **> duration 1** :Example Set duration to 5 seconds: | **> duration 5** :Example Set duration to 10 seconds: | **> duration 10** :Example Set duration to 30 seconds: | **> duration 30** :Example Set duration to 1 minute: | **> duration 60** :Example Set duration to 5 minutes: | **> duration 300** timeout ------- Set/Get advertise timeout. :Usage: **> timeout [seconds]** :Uses: **org.bluez.LEAdvertisement(5)** property **Timeout** :[seconds]: Timeout in seconds (optional, shows current if omitted) :Example Show current advertising timeout setting: | **> timeout** :Example Unlimited advertising (no timeout): | **> timeout 0** :Example Stop advertising after 30 seconds: | **> timeout 30** :Example Stop advertising after 2 minutes: | **> timeout 120** :Example Stop advertising after 1 hour: | **> timeout 3600** secondary --------- Set/Get advertise secondary channel. :Usage: **> secondary [1M/2M/Coded]** :Uses: **org.bluez.LEAdvertisement(5)** property **SecondaryChannel** :[1M/2M/Coded]: Secondary channel (optional, shows current if omitted) :Example Show current secondary channel setting: | **> secondary** :Example Use LE 1M PHY for secondary advertising channel: | **> secondary 1M** :Example Use LE 2M PHY for secondary advertising channel: | **> secondary 2M** :Example Use LE Coded PHY for secondary advertising channel: | **> secondary Coded** rsi --- Show/Enable/Disable RSI to be advertised. RSI (Resolvable Set Identifier) is used to advertise a resolvable identifier for Coordinated Set Identification. :Usage: **> rsi [on/off]** :[on/off]: Enable or disable RSI advertisement (optional, shows current if omitted) :Example Show current RSI setting: | **> rsi** :Example Enable RSI advertisement: | **> rsi on** :Example Disable RSI advertisement: | **> rsi off** interval -------- Set/Get advertise interval. The Interval parameter configures the advertising interval of an Instance. The value is in milliseconds. A value of 0 indicates a default value is chosen for the Interval. The default is 100 milliseconds. The Interval value is used to calculate the number of advertising events that will be used to advertise each Instance. The number of advertising events is calculated by dividing the Duration value by the advertising interval. The advertising interval is determined by the advertising parameters that are set for each Instance. The advertising interval is the maximum of the advertising intervals set for each Instance. :Usage: **> interval [min] [max]** :[min]: Minimum advertising interval in milliseconds (optional, shows current if omitted) :[max]: Maximum advertising interval in milliseconds (optional, defaults to min value) :Example Show current advertising interval setting: | **> interval** :Example Set minimum advertising interval (20ms): | **> interval 20** :Example Set advertising interval range (50-100ms): | **> interval 50 100** :Example Set normal advertising interval (100ms): | **> interval 100** :Example Set moderate advertising interval (200ms): | **> interval 200** :Example Set slow advertising interval (500ms): | **> interval 500** :Example Set very slow advertising interval (1 second): | **> interval 1000** :Example Set maximum advertising interval (10.24 seconds): | **> interval 10240** clear ----- Clear advertise config. This will stop advertising if it is currently advertising. If you want to change the advertise configuration while advertising you must first clear the advertise configuration and then set the new advertise configuration. :Usage: **> clear [uuids/service/manufacturer/config-name...]** :[uuids/service/manufacturer/config-name...]: List of configuration to clear :Example Clear all advertising configuration: | **> clear** :Example Clear only advertised UUIDs: | **> clear uuids** :Example Clear only service data: | **> clear service** :Example Clear only manufacturer data: | **> clear manufacturer** :Example Clear only advertised name: | **> clear name** :Example Clear only appearance data: | **> clear appearance** :Example Clear only TX power advertisement: | **> clear tx-power** :Example Clear UUIDs and service data: | **> clear uuids service** :Example Clear manufacturer data and name: | **> clear manufacturer name** :Example Clear UUIDs, service data, and manufacturer data: | **> clear uuids service manufacturer** :Example Clear discoverable mode and TX power: | **> clear discoverable tx-power** RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.obex.PhonebookAccess.rst0000644000000000000000000000005015063045254021066 xustar0020 atime=1783094015 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.PhonebookAccess.rst0000644000000000000000000001607715063045254020562 0ustar00rootroot============================== org.bluez.obex.PhonebookAccess ============================== -------------------------------------------------- BlueZ D-Bus OBEX PhonebookAccess API documentation -------------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez.obex :Interface: org.bluez.obex.PhonebookAccess1 :Object path: [Session object path] Methods ------- void Select(string location, string phonebook) `````````````````````````````````````````````` Selects the phonebook object for other operations. Should be call before all the other operations. Possible location values: :"int", "internal" (default): Store in the Internal memory. :"sim{#}": Store in the sim number. Possible phonebook values: :"pb": Store as contact. :"ich": Store as incoming call. :"och": Store as outgoing call. :"mch": Store as missing call. :"cch": Store as a combination of incoming, outgoing and missing call. "spd": Store as speed dials entry ( only for "internal" ) "fav": Store as favorites entry ( only for "internal" ) Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: object, dict PullAll(string targetfile, dict filters) ````````````````````````````````````````````````````` Returns the entire phonebook object from the PSE server in plain string with vcard format, and store it in a local file. If an empty target file is given, a name will be automatically generated for the temporary file. The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see **org.bluez.obex.Transfer(5)** for the possible list of properties. Possible filters: :string Format: Items vcard format. Possible values: :"vcard21" (default): :"vcard30": :string Order: Items order. Possible values: :"": :"indexed": :"alphanumeric": :"phonetic": :uint16 Offset (default 0): Offset of the first item. :uint16 MaxCount (default 65535): Maximum number of items. :array{string} Fields (default all fields): Item vcard fields. See **ListFilterFields()** for possible values. :array{string} FilterAll: Filter items by fields using AND logic, cannot be used together with **FilterAny**. See **ListFilterFields()** for possible values. :array{string} FilterAny: Filter items by fields using OR logic, cannot be used together with **FilterAll**. See **ListFilterFields()** for possible values. :bool ResetNewMissedCalls: Reset new the missed calls items, shall only be used for folders mch and cch. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Forbidden: array{string vcard, string name} List(dict filters) ``````````````````````````````````````````````````` Returns array of vcard-listing data where every entry consists of a pair of strings containing the vcard handle and the contact name. For example: :"1.vcf": "John" Possible filters: :string Order: Contact order. Possible values: :"": :"indexed": :"alphanumeric": :"phonetic": :uint16 Offset: Start offset. :uint16 MaxCount: Maximum number of contacts. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Forbidden: object, dict Pull(string vcard, string targetfile, dict filters) ```````````````````````````````````````````````````````````````` Retrieves the vcard in the current phonebook object and store it in a local file. If an empty target file is given, a name will be automatically generated for the temporary file. The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see **org.bluez.obex.Transfer(5)** for the possible list of properties. Possible filters: :string Format: Contact data format. Possible values: :"": :"vcard21": :"vcard30": :array{string} Fields: See **ListFilterFields()** for possible values. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Forbidden: :org.bluez.obex.Error.Failed: array{string vcard, string name} Search(string field, string value, dict filters) ````````````````````````````````````````````````````````````````````````````````` Searches for entries matching the given condition and return an array of vcard-listing data where every entry consists of a pair of strings containing the vcard handle and the contact name. Possible field values: :"name" (default): Search by name. :"number": Search by number. :"sound": Search by sound. value: the string value to search for Possible filters: :string Order: Contact order. Possible values: :"": :"indexed": :"alphanumeric": :"phonetic": :uint16 Offset: Start offset. :uint16 MaxCount: Maximum number of contacts. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Forbidden: :org.bluez.obex.Error.Failed: uint16 GetSize() ```````````````` Returns the number of entries in the selected phonebook object that are actually used (i.e. indexes that correspond to non-NULL entries). Possible errors: :org.bluez.obex.Error.Forbidden: :org.bluez.obex.Error.Failed: void UpdateVersion() ```````````````````` Attempts to update PrimaryCounter and SecondaryCounter. Possible errors: :org.bluez.obex.Error.NotSupported: :org.bluez.obex.Error.Forbidden: :org.bluez.obex.Error.Failed: array{string} ListFilterFields() ```````````````````````````````` Returns all Available fields that can be used in Fields filter. Possible return: :"VERSION": :"FN": :"N": :"PHOTO": :"BDAY": :"ADR": :"LABEL": :"TEL": :"EMAIL": :"MAILER": :"TZ": :"GEO": :"TITLE": :"ROLE": :"LOGO": :"AGENT": :"ORG": :"NOTE": :"REV": :"SOUND": :"URL": :"UID": :"KEY": :"NICKNAME": :"CATEGORIES": :"PROID": :"CLASS": :"SORT-STRING": :"X-IRMC-CALL-DATETIME": :"X-BT-SPEEDDIALKEY": :"X-BT-UCI": :"X-BT-UID": :"BIT-{#}": Possible errors: None Properties ---------- string Folder [readonly] ```````````````````````` Current folder. string DatabaseIdentifier [readonly, optional] `````````````````````````````````````````````` 128 bits persistent database identifier. Possible values: 32-character hexadecimal such as A1A2A3A4B1B2C1C2D1D2E1E2E3E4E5E6 string PrimaryCounter [readonly, optional] `````````````````````````````````````````` 128 bits primary version counter. Possible values: 32-character hexadecimal such as A1A2A3A4B1B2C1C2D1D2E1E2E3E4E5E6 string SecondaryCounter [readonly, optional] ```````````````````````````````````````````` 128 bits secondary version counter. Possible values: 32-character hexadecimal such as A1A2A3A4B1B2C1C2D1D2E1E2E3E4E5E6 bool FixedImageSize [readonly, optional] ```````````````````````````````````````` Indicate support for fixed image size. Possible values: True if image is JPEG 300x300 pixels otherwise False. bluez-5.87/doc/PaxHeaders/bluetoothctl-admin.rst0000644000000000000000000000005015142177620016755 xustar0020 atime=1783094057 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-admin.rst0000644000000000000000000000353315142177620016442 0ustar00rootroot================== bluetoothctl-admin ================== -------------------- Admin Policy Submenu -------------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: November 2022 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **bluetoothctl** [--options] [admin.commands] This submenu configures administrative policies using the **org.bluez.AdminPolicySet(5)** and **org.bluez.AdminPolicyStatus(5)** interfaces. Admin Policy Commands ===================== allow ----- Allow service UUIDs and block rest of them. :Usage: **> allow [clear/uuid1 uuid2 ...]** :Uses: **org.bluez.AdminPolicySet(5)** property **ServiceAllowList** :[clear/uuid1 uuid2 ...]: List of service UUIDs to allow, or "clear" to remove all restrictions :Example Get current allowed UUIDs list: | **> allow** :Example Allow Serial Port Profile only: | **> allow 0x1101** :Example Allow Serial Port Profile and LAN Access Profile: | **> allow 0x1101 0x1102** :Example Allow Serial Port Profile, LAN Access Profile, and Dialup Networking Profile: | **> allow 0x1101 0x1102 0x1103** :Example Allow Advanced Audio Distribution Profile only: | **> allow 0x110e** :Example Allow A2DP Source and Sink profiles: | **> allow 0x110e 0x110f** :Example Allow Serial Port Profile using full UUID: | **> allow 00001101-0000-1000-8000-00805f9b34fb** :Example Allow SPP and LAP using full UUIDs: | **> allow 00001101-0000-1000-8000-00805f9b34fb 00001102-0000-1000-8000-00805f9b34fb** :Example Remove all UUID restrictions: | **> allow clear** :Example Allow SPP, LAP, and DUN using mixed UUID formats: | **> allow 0x1101 00001102-0000-1000-8000-00805f9b34fb 0x1103** RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.BatteryProviderManager.50000644000000000000000000000005015221755311021036 xustar0020 atime=1783093961 20 ctime=1783094112 bluez-5.87/doc/org.bluez.BatteryProviderManager.50000644000000000000000000000422215221755311020517 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.BatteryProviderManager" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.BatteryProviderManager \- BlueZ D-Bus BatteryProviderManager API documentation .SH Description .sp A battery provider starts by registering itself as a battery provider with the \fBRegisterBatteryProvider()\fP method passing an object path as the provider ID. Then, it can start exposing \fBorg.bluez.BatteryProvider(5)\fP objects having the path starting with the given provider ID. It can also remove objects at any time. The objects and their properties exposed by battery providers will be reflected on \fBorg.bluez.Battery(5)\fP interface. .sp \fBbluetoothd(8)\fP will stop monitoring these exposed and removed objects after UnregisterBatteryProvider is called for that provider ID. .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.BatteryProviderManager1 .TP .B Object path /org/bluez/{hci0,hci1,...} .UNINDENT .SS Methods .SS void RegisterBatteryProvider(object provider) .sp Registers a battery provider. A registered battery provider can then expose objects with \fBorg.bluez.BatteryProvider(5)\fP interface. .SS void UnregisterBatteryProvider(object provider) .sp Unregisters a battery provider previously registered with \fBRegisterBatteryProvider()\fP\&. After unregistration, the \fBorg.bluez.BatteryProvider(5)\fP objects provided by this client are ignored by \fBbluetoothd(8)\fP\&. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/hid2hci.rst0000644000000000000000000000005015142177620014471 xustar0020 atime=1783094097 20 ctime=1783094113 bluez-5.87/doc/hid2hci.rst0000644000000000000000000000224215142177620014152 0ustar00rootroot======== hid2hci ======== ------------------------------------------- Bluetooth HID to HCI mode switching utility ------------------------------------------- :Author: Marcel Holtmann :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: May 15, 2009 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **hid2hci** [*OPTIONS*] DESCRIPTION =========== **hid2hci(1)** is used to set up switch supported Bluetooth devices into the HCI mode and back. OPTIONS ======= --mode=[*mode*] Sets the mode to the device into. The possible values for *mode* are **hid**, **hci**. --method=[*method*] Which vendor method to use for switching the device. The possible values for *method* are **csr**, **csr2**, **logitech-hdi**, **dell**. --devpath Specifies the device path in /sys --help Gives a list of possible options. RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/hciattach.rst0000644000000000000000000000005015142177620015107 xustar0020 atime=1783094080 20 ctime=1783094113 bluez-5.87/doc/hciattach.rst0000644000000000000000000001011015142177620014561 0ustar00rootroot========= hciattach ========= ------------------------------------------------- attach serial devices via UART HCI to BlueZ stack ------------------------------------------------- :Authors: - Maxim Krasnyansky - Nils Faerber :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: Jan 22, 2002 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **hciattach** [*OPTIONS*] <*tty*> <*type|id*> [*speed*] [*flow*] [*sleep*] [*bdaddr*] **hciattach** -1 DESCRIPTION =========== **hciattach(1)** is used to attach a serial UART to the Bluetooth stack as HCI transport interface. OPTIONS ======= -i Send break -n Don't detach from controlling terminal. -p Print the PID when detaching. -t timeout Specify an initialization timeout. Default is 5 seconds. -s speed Specify an initial speed instead of the hardware default. -l List all available configurations. -r Set the HCI device into raw mode. The kernel and bluetooth daemon will ignore it. -h, --help Show help options ARGUMENTS ========= *show* This specifies the serial device to attach. A leading /dev can be omitted. Examples: **/dev/ttyS1 ttyS2** *type|id* The *type* or *id* of the Bluetooth device that is to be attached, i.e. vendor or other device specific identifier. Currently supported types are .. list-table:: :header-rows: 1 :widths: auto * - *type* - Description * - any - Unspecified HCI_UART interface, no vendor specific options * - ericsson - Ericsson based modules * - digi - Digianswer based cards * - xircom - Xircom PCMCIA cards: Credit Card Adapter and Real Port Adapter * - csr - CSR Casira serial adapter or BrainBoxes serial dongle (BL642) * - bboxes - BrainBoxes PCMCIA card (BL620) * - swave - Silicon Wave kits * - bcsp - Serial adapters using CSR chips with BCSP serial protocol * - ath3k - Atheros AR300x based serial Bluetooth device * - intel - Intel Bluetooth device .. list-table:: :header-rows: 1 :widths: auto * - | Supported ID | (manufacturer id, product id) - Description * - 0x0105, 0x080a - Xircom PCMCIA cards: Credit Card Adapter and Real Port Adapter * - 0x0160, 0x0002 - BrainBoxes PCMCIA card (BL620) *speed* The *speed* specifies the UART speed to use. Baudrates higher than 115200bps require vendor specific initializations that are not implemented for all types of devices. In general the following speeds are supported: Supported vendor devices are automatically initialised to their respective best settings. .. list-table:: :header-rows: 0 :widths: auto * - 9600 * - 19200 * - 38400 * - 57600 * - 115200 * - 230400 * - 460800 * - 921600 *flow* If the *flow* is appended to the list of options then hardware flow control is forced on the serial link (**CRTSCTS**). All above mentioned device types have flow set by default. To force no flow control use *noflow* instead. *sleep|nosleep* Enables hardware specific power management feature. If *sleep* is appended to the list of options then this feature is enabled. To disable this feature use *nosleep* instead. All above mentioned device types have *nosleep* set by default. Note: This option will only be valid for hardware which support hardware specific power management enable option from host. *bdaddr* The bdaddr specifies the Bluetooth Address to use. Some devices (like the STLC2500) do not store the Bluetooth address in hardware memory. Instead it must be uploaded during the initialization process. If this argument is specified, then the address will be used to initialize the device. Otherwise, a default address will be used. RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.MediaItem.rst0000644000000000000000000000005015142177620016723 xustar0020 atime=1783093978 20 ctime=1783094111 bluez-5.87/doc/org.bluez.MediaItem.rst0000644000000000000000000000442415142177620016410 0ustar00rootroot=================== org.bluez.MediaItem =================== --------------------------------------- BlueZ D-Bus MediaItem API documentation --------------------------------------- :Version: BlueZ :Date: September 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: unique name (Target role) org.bluez (Controller role) :Interface: org.bluez.MediaItem1 :Object path: freely definable (Target role) [variable prefix]/{hci0,hci1,...}/dev_{BDRADDR}/player#/item# (Controller role) :Used by: **bluetoothctl-player(1)** Methods ------- void Play() ``````````` Play item Possible Errors: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: Examples: :bluetoothctl: [player] > play [item] void AddtoNowPlaying() `````````````````````` Add item to now playing list Possible Errors: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: Examples: :bluetoothctl: [player] > queue Properties ---------- object Player [readonly] ```````````````````````` Player object path the item belongs to string Name [readonly] `````````````````````` Item displayable name string Type [readonly] `````````````````````` Item type Possible values: :"video": :"audio": :"folder": string FolderType [readonly, optional] `````````````````````````````````````` Folder type. Available if property Type is "Folder" Possible values: :"mixed": :"titles": :"albums": :"artists": boolean Playable [readonly, optional] ````````````````````````````````````` Indicates if the item can be played Available if property Type is "folder" dict Metadata [readonly] ```````````````````````` Item metadata. Possible values: :string Title: Item title name Available if property Type is "audio" or "video" :string Artist: Item artist name Available if property Type is "audio" or "video" :string Album: Item album name Available if property Type is "audio" or "video" :string Genre: Item genre name Available if property Type is "audio" or "video" :uint32 NumberOfTracks: Item album number of tracks in total Available if property Type is "audio" or "video" :uint32 Number: Item album number Available if property Type is "audio" or "video" :uint32 Duration: Item duration in milliseconds Available if property Type is "audio" or "video" bluez-5.87/doc/PaxHeaders/hci.70000644000000000000000000000005015221755236013263 xustar0020 atime=1783093918 20 ctime=1783094112 bluez-5.87/doc/hci.70000644000000000000000000001013715221755236012746 0ustar00rootroot'\" t .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "hci" "7" "October 2024" "BlueZ" "Linux System Administration" .SH Name hci \- Bluetooth HCI protocol .SH SYNOPSIS .INDENT 0.0 .INDENT 3.5 .sp .EX #include #include #include hci_socket = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); .EE .UNINDENT .UNINDENT .SH DESCRIPTION .sp Bluetooth Host Controller Interface (HCI) is the standard protocol to communicate with Bluetooth adapters. HCI protocol provides a uniform command method for the Host to access Controller capabilities and to control connections to other Controllers. .SH SOCKET ADDRESS .INDENT 0.0 .INDENT 3.5 .sp .EX struct sockaddr_hci { sa_family_t hci_family; unsigned short hci_dev; unsigned short hci_channel; }; .EE .UNINDENT .UNINDENT .sp Possible values for hci_channel: .TS box center; l|l|l. T{ Define T} T{ Value T} T{ Description T} _ T{ \fBHCI_CHANNEL_RAW\fP T} T{ 0x00 T} T{ Raw channel \- Used for raw HCI communication T} _ T{ \fBHCI_CHANNEL_USER\fP T} T{ 0x01 T} T{ User channel \- Used for userspace HCI communication (disables kernel processing) T} _ T{ \fBHCI_CHANNEL_MONITOR\fP T} T{ 0x02 T} T{ Monitor channel \- Used for monitoring HCI traffic (btmon(1)) T} _ T{ \fBHCI_CHANNEL_CONTROL\fP T} T{ 0x03 T} T{ Control channel \- Used to manage local adapters (bluetoothd(7)) T} _ T{ \fBHCI_CHANNEL_LOGGING\fP T} T{ 0x04 T} T{ Logging channel \- Used to inject logging messages (bluetoothd(7)) T} .TE .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX struct sockaddr_hci addr; memset(&addr, 0, sizeof(addr)); addr.hci_family = AF_BLUETOOTH; addr.hci_dev = HCI_DEV_NONE; addr.hci_channel = HCI_CHANNEL_CONTROL; .EE .UNINDENT .UNINDENT .SH SOCKET OPTIONS .sp The socket options listed below can be set by using \fBsetsockopt(2)\fP and read with \fBgetsockopt(2)\fP with the socket level set to SOL_BLUETOOTH or SOL_HCI (HCI_FILTER). .SS HCI_FILTER (since Linux 2.6) .sp Filter by HCI events, requires hci_channel to be set to HCI_CHANNEL_RAW, possible values: .INDENT 0.0 .INDENT 3.5 .sp .EX struct hci_filter { uint32_t type_mask; uint32_t event_mask[2]; uint16_t opcode; }; .EE .UNINDENT .UNINDENT .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX struct hci_filter flt; memset(&flt, 0, sizeof(flt)); flt.type_mask = 1 << BT_H4_EVT_PKT; flt.event_mask[0] = 0xffffffff; flt.event_mask[1] = 0xffffffff; setsockopt(fd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)); .EE .UNINDENT .UNINDENT .SS BT_SNDBUF (since Linux 5.16) .sp Set send buffer size, requires hci_channel to be set to HCI_CHANNEL_MONITOR, HCI_CHANNEL_CONTROL or HCI_CHANNEL_LOGGING. .sp Default value is 1028. .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX uint16_t mtu = UINT16_MAX; int err; err = setsockopt(fd, SOL_BLUETOOTH, BT_SNDMTU, &mtu, sizeof(mtu)); .EE .UNINDENT .UNINDENT .SS BT_RCVBUF (since Linux 5.16) .sp Set receive buffer size, requires hci_channel to be set to HCI_CHANNEL_MONITOR, HCI_CHANNEL_CONTROL or HCI_CHANNEL_LOGGING. .sp Default value is 1028. .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX uint16_t mtu; socklen_t len; int err; len = sizeof(mtu); err = getsockopt(sock, SOL_BLUETOOTH, BT_RCVMTU, mtu, &len); .EE .UNINDENT .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH SEE ALSO .sp socket(7) .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.obex.MessageAccess.50000644000000000000000000000005015221755401020060 xustar0020 atime=1783094017 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.MessageAccess.50000644000000000000000000001253715221755401017551 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.obex.MessageAccess" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.obex.MessageAccess \- BlueZ D-Bus OBEX MessageAccess API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez.obex .TP .B Interface org.bluez.obex.MessageAccess1 .TP .B Object path [Session object path] .UNINDENT .SS Methods .SS void SetFolder(string name) .sp Set working directory for current session. .sp Possible name: .INDENT 0.0 .INDENT 3.5 Directory name or \(aq..[/dir]\(aq. .UNINDENT .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS array{dict} ListFolders(dict filter) .sp Returns a dictionary containing information about the current folder content. .sp Possible filter: .INDENT 0.0 .TP .B uint16 Offset (default 0) Offset of the first item. .TP .B uint16 MaxCount (default 1024) Maximum number of items. .UNINDENT .sp Possible return: .INDENT 0.0 .TP .B string Name Folder name .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS array{string} ListFilterFields() .sp Return all available fields that can be used in \fBFields\fP filter. .sp Possible values: .INDENT 0.0 .TP .B \(dqsubject\(dq .TP .B \(dqtimestamp\(dq .TP .B \(dqsender\(dq .TP .B \(dqsender\-address\(dq .TP .B \(dqrecipient\(dq .TP .B \(dqrecipient\-address\(dq .TP .B \(dqtype\(dq .TP .B \(dqsize\(dq .TP .B \(dqstatus\(dq .TP .B \(dqtext\(dq .TP .B \(dqattachment\(dq .TP .B \(dqpriority\(dq .TP .B \(dqread\(dq .TP .B \(dqsent\(dq .TP .B \(dqprotected\(dq .TP .B \(dqreplyto\(dq .UNINDENT .sp Possible errors: None .SS array{object, dict} ListMessages(string folder, dict filter) .sp Returns an array containing the messages objects found in the given subfolder of the current folder, or in the current folder if folder is empty. .sp Possible Filters: .INDENT 0.0 .TP .B uint16 Offset (default 0) Offset of the first item. .UNINDENT .sp uint16 MaxCount (default 1024): .INDENT 0.0 .INDENT 3.5 Maximum number of items. .UNINDENT .UNINDENT .INDENT 0.0 .TP .B byte SubjectLength (default 256) Maximum length of the Subject property in the message. .TP .B array{string} Fields Message fields, default is all values. .sp See \fBListFilterFields()\fP for possible values. .TP .B array{string} Types Filter messages by type. .sp Possible values: .INDENT 7.0 .TP .B \(dqsms\(dq .TP .B \(dqemail\(dq .TP .B \(dqmms\(dq .UNINDENT .TP .B string PeriodBegin Filter messages by starting period. .sp Possible values: .INDENT 7.0 .INDENT 3.5 Date in \(dqYYYYMMDDTHHMMSS\(dq format. .UNINDENT .UNINDENT .TP .B string PeriodEnd Filter messages by ending period. .sp Possible values: .INDENT 7.0 .INDENT 3.5 Date in \(dqYYYYMMDDTHHMMSS\(dq format. .UNINDENT .UNINDENT .TP .B boolean Read Filter messages by read flag. .sp Possible values: .INDENT 7.0 .INDENT 3.5 True for read or False for unread .UNINDENT .UNINDENT .TP .B string Recipient Filter messages by recipient address. .TP .B string Sender Filter messages by sender address. .TP .B boolean Priority Filter messages by priority flag. .sp Possible values: .INDENT 7.0 .INDENT 3.5 True for high priority or False for non\-high priority. .UNINDENT .UNINDENT .UNINDENT .sp Each message is represented by an object path, which implements \fBorg.bluez.obex.Message(5)\fP interface, followed by a dictionary of its properties. .SS void UpdateInbox(void) .sp Requests remote to update its inbox. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS object, dict PushMessage(string sourcefile, string folder, dict args) .sp Transfers a message (in bMessage format) to the remote device. .sp The message is transferred either to the given subfolder of the current folder, or to the current folder if folder is empty. .sp Possible args: Transparent, Retry, Charset .sp The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. .sp The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see \fBorg.bluez.obex.Transfer(5)\fP for the possible list of properties. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS Properties .SS array{string} SupportedTypes [readonly] .sp List of supported message types. .sp Possible values: .INDENT 0.0 .TP .B \(dqEMAIL\(dq Email messages. .TP .B \(dqSMS_GSM\(dq GSM short messages. .TP .B \(dqSMS_CDMA\(dq CDMA short messages. .TP .B \(dqMMS\(dq MMS messages. .TP .B \(dqIM\(dq Instant messaging. .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/l2ping.10000644000000000000000000000005015221755434013705 xustar0020 atime=1783094044 20 ctime=1783094113 bluez-5.87/doc/l2ping.10000644000000000000000000000464015221755434013372 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "l2ping" "1" "Jan 22, 2002" "BlueZ" "Linux System Administration" .SH Name l2ping \- Send L2CAP echo request and receive answer .SH SYNOPSIS .sp \fBl2ping\fP [\fIOPTIONS\fP] \fIbd_addr\fP .SH DESCRIPTION .sp \fBl2ping(1)\fP sends a L2CAP echo request to the Bluetooth MAC address bd_addr given in dotted hex notation. .SH OPTIONS .INDENT 0.0 .TP .BI \-i \ The command is applied to device \fIhciX\fP, which must be the name of an installed Bluetooth device (X = 0, 1, 2, ...) If not specified, the command will be sent to the first available Bluetooth device. .TP .BI \-s \ size The size of the data packets to be sent. .TP .BI \-c \ count Send count number of packets then exit. .TP .BI \-t \ timeout Wait timeout seconds for the response. .TP .BI \-d \ delay Wait delay seconds between pings. .TP .B \-f Kind of flood ping. Use with care! It reduces the delay time between packets to 0. .TP .B \-r Reverse ping (gnip?). Send echo response instead of echo request. .TP .B \-v Verify response payload is identical to request payload. It is not required for remote stacks to return the request payload, but most stacks do (including Bluez). .UNINDENT .INDENT 0.0 .TP .B bd_addr The Bluetooth MAC address to be pinged in dotted hex notation like \fB01:02:03:ab:cd:ef\fP or \fB01:EF:cd:aB:02:03\fP .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Author Maxim Krasnyansky , Marcel Holtmann , Nils Faerber , Adam Laurie . .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/bluetoothctl-transport.rst0000644000000000000000000000005015142177620017721 xustar0020 atime=1783094069 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-transport.rst0000644000000000000000000001607015142177620017406 0ustar00rootroot====================== bluetoothctl-transport ====================== ----------------------- Media Transport Submenu ----------------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: November 2022 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **bluetoothctl** [--options] [transport.commands] This submenu manages media transports for audio streaming using the **org.bluez.MediaTransport(5)** interface. Media Transport Commands ========================= list ---- List available transports. :Usage: **> list** :Example Display all available media transports (A2DP, LE Audio, etc.): | **> list** show ---- Show transport information. :Usage: **> show [transport]** :Uses: **org.bluez.MediaTransport(5)** properties :[transport]: Specific transport object path (optional, shows current if omitted) :Example Show information for currently selected transport: | **> show** :Example Show transport information: | **> show /org/bluez/hci0/dev_00_11_22_33_44_55/fd0** :Example Show Broadcast Isochronous Stream transport: | **> show /org/bluez/hci0/dev_11_22_33_44_55_66/bis1** acquire ------- Acquire transport. :Usage: **> acquire [transport1...]** :Uses: **org.bluez.MediaTransport(5)** method **Acquire** :: Media transport object path to acquire for audio streaming :[transport1...]: Additional transport paths for multi-stream acquisition (optional) :Example Acquire transport: | **> acquire /org/bluez/hci0/dev_00_11_22_33_44_55/fd0** :Example Acquire multiple transports: | **> acquire /org/bluez/hci0/dev_00_11_22_33_44_55/fd0 /org/bluez/hci0/dev_00_11_22_33_44_55/fd1** Note: If running the setup with an audio server that has LE Audio support (such as PipeWire) it will automatically acquire transports according to the configured roles. select ------- Select transport. For transports created on a Broadcast Sink device only. This moves the transport to the "broadcasting" state, pending acquire. :Usage: **> select [transport1...]** :Uses: **org.bluez.MediaTransport(5)** method **Select** :: Broadcast sink transport path to move to broadcasting state :[transport1...]: Additional transport paths for multi-stream selection (optional) :Example Select single Broadcast Isochronous Stream: | **> select /org/bluez/hci0/dev_00_11_22_33_44_55/bis1** :Example Select stereo broadcast streams: | **> select /org/bluez/hci0/dev_00_11_22_33_44_55/bis1 /org/bluez/hci0/dev_00_11_22_33_44_55/bis2** Note: If the select command receives a list of transports, they will first be linked using the "Links" MediaTransport property. They will then be selected one by one, by calling the "Select" MediaTransport method. After the first transport is acquired, the Broadcast Sink will create fds for the associated stream and all its links. Each link can then be acquired one by one, setting the fd for the transport and starting to receive audio. The select command does not require a local endpoint to be registered beforehand. This is because if the setup runs with an audio server that has LE Audio support (such as PipeWire), the audio server is the one to register endpoints and the transports are created as a result. Once a transport is selected, the audio server will automatically acquire. unselect -------- Unselect transport. For transports created on a Broadcast Sink device only. This moves the transport to the "idle" state, pending release by the audio server. If the transport was acquired by bluetoothctl it can be released straight away, without having to be unselected. :Usage: **> unselect [transport1...]** :: Broadcast sink transport path to move to idle state :[transport1...]: Additional transport paths for multi-stream unselection (optional) :Example Unselect broadcast stream transport: | **> unselect /org/bluez/hci0/dev_00_11_22_33_44_55/bis1** :Example Unselect multiple broadcast streams: | **> unselect /org/bluez/hci0/dev_00_11_22_33_44_55/bis1 /org/bluez/hci0/dev_00_11_22_33_44_55/bis2** Note: If running the setup with an audio server that has LE Audio support (such as PipeWire), it will prompt it to automatically release the transport. release ------- Release transport. :Usage: **> release [transport1...]** :Uses: **org.bluez.MediaTransport(5)** method **Release** :: Media transport object path to release from audio streaming :[transport1...]: Additional transport paths for multi-stream release (optional) :Example Release transport: | **> release /org/bluez/hci0/dev_00_11_22_33_44_55/fd0** :Example Release multiple transports: | **> release /org/bluez/hci0/dev_00_11_22_33_44_55/fd0 /org/bluez/hci0/dev_00_11_22_33_44_55/fd1** Note: Transports acquired by an audio server, can only be released by said audio server. send ---- Send contents of a file. :Usage: **> send [transport1...]** :: Media transport object path to send audio data through :: Path to audio file to transmit (supports WAV, MP3, PCM formats) :[transport1...]: Additional transport paths for multi-stream sending (optional) :Example Send encoded audio file via transport: | **> send /org/bluez/hci0/dev_00_11_22_33_44_55/fd0 /home/user/music.** :Example Send to multiple transports simultaneously: | **> send /org/bluez/hci0/dev_00_11_22_33_44_55/fd0 /home/user/stereo-left. /org/bluez/hci0/dev_00_11_22_33_44_55/fd1 /home/user/stereo-rigth.** receive ------- Get/Set file to receive. :Usage: **> receive [filename]** :: Media transport object path to receive audio data from :[filename]: Path to save received audio data (optional, shows current if omitted) :Example Show current receive file for transport: | **> receive /org/bluez/hci0/dev_00_11_22_33_44_55/fd0** :Example Set file to receive audio data: | **> receive /org/bluez/hci0/dev_00_11_22_33_44_55/fd0 /tmp/recorded_audio.wav** :Example Set file for broadcast audio capture (note quotes): | **> receive /org/bluez/hci0/dev_11_22_33_44_55_66/bis1 "/home/user/My Recordings/broadcast.wav"** volume ------ Get/Set transport volume. :Usage: **> volume [value]** :Uses: **org.bluez.MediaTransport(5)** property **Volume** :: Media transport object path to control volume for :[value]: Volume level (0-127, optional, shows current if omitted) :Example Show current volume level: | **> volume /org/bluez/hci0/dev_00_11_22_33_44_55/fd0** :Example Set volume to 100: | **> volume /org/bluez/hci0/dev_00_11_22_33_44_55/fd0 100** metadata -------- Get/Set Transport Metadata. :Usage: **> metadata [value...]** :: Media transport object path :[value...]: Metadata value as hex string (optional, shows current if omitted) :Example Show current metadata for transport: | **> metadata /org/bluez/hci0/dev_00_11_22_33_44_55/fd0** :Example Set metadata value: | **> metadata /org/bluez/hci0/dev_00_11_22_33_44_55/fd0 0x03020100** RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/bluetoothctl-endpoint.10000644000000000000000000000005015221755455017043 xustar0020 atime=1783094061 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-endpoint.10000644000000000000000000002436015221755455016531 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "bluetoothctl-endpoint" "1" "November 2022" "BlueZ" "Linux System Administration" .SH Name bluetoothctl-endpoint \- Endpoint Submenu .SH SYNOPSIS .sp \fBbluetoothctl\fP [\-\-options] [endpoint.commands] .sp This submenu manages media endpoints for audio streaming using the \fBorg.bluez.MediaEndpoint(5)\fP and \fBorg.bluez.Media(5)\fP interfaces. .SH Endpoint Commands .SS list .sp List available endpoints. .INDENT 0.0 .TP .B Usage \fB> list [local]\fP .TP .B [local] Only show locally registered endpoints (optional, shows all if omitted) .TP .B Example List all available endpoints (local and remote) .nf \fB> list\fP .fi .sp .TP .B Example List only locally registered endpoints .nf \fB> list local\fP .fi .sp .UNINDENT .SS show .sp Endpoint information. .INDENT 0.0 .TP .B Usage \fB> show [endpoint]\fP .TP .B Uses \fBorg.bluez.MediaEndpoint(5)\fP properties .TP .B [endpoint] Path to specific endpoint to display (optional, shows current selection if omitted) .TP .B Example Show information for currently selected endpoint .nf \fB> show\fP .fi .sp .TP .B Example Show local endpoint information .nf \fB> show /local/endpoint/ep0\fP .fi .sp .TP .B Example Show remote endpoint information .nf \fB> show /org/bluez/hci0/dev_00_11_22_33_44_55/ep1\fP .fi .sp .TP .B Example Show another local endpoint .nf \fB> show /local/endpoint/ep1\fP .fi .sp .UNINDENT .SS register .sp Register Endpoint. .INDENT 0.0 .TP .B Usage \fB> register [capabilities...]\fP .TP .B Uses \fBorg.bluez.Media(5)\fP method \fBRegisterEndpoint\fP .TP .B Bluetooth service UUID for the endpoint type (required) .TP .B Codec ID in hex format, optionally with company ID (required) .TP .B [capabilities...] Optional codec\-specific capability data in hex format .TP .B Example LC3 BAP source .nf \fB>endpoint.register 00002bcb\-0000\-1000\-8000\-00805f9b34fb 0x06\fP \fB>Auto Accept (yes/no):\fP y \fB>Max Transports (auto/value):\fP a \fB>Locations:\fP a \fB>Supported Context (value):\fP 3 \fB>Context (value):\fP 3 \fB>CIG (auto/value):\fP a \fB>CIS (auto/value):\fP a .fi .sp .TP .B Example LC3 BAP sink with extra capabilities .nf \fB>endpoint.register 00002bc9\-0000\-1000\-8000\-00805f9b34fb 0x06 \(dq0x03 0xe5 0x03 0x00 0x02 0xe6 0x07\(dq\fP \fB>Enter Metadata (value/no):\fP n \fB>Auto Accept (yes/no):\fP y \fB>Max Transports (auto/value):\fP a \fB>Locations:\fP a \fB>Supported Context (value):\fP 3 \fB>Context (value):\fP 3 \fB>CIG (auto/value):\fP a \fB>CIS (auto/value):\fP a .fi .sp .TP .B Example LC3 BAP Broadcast source .nf \fB>endpoint.register 00001852\-0000\-1000\-8000\-00805f9b34fb 0x06\fP \fB>Auto Accept (yes/no):\fP y \fB>Max Transports (auto/value):\fP a \fB>Locations:\fP 3 \fB>Supported Context (value):\fP 1 .fi .sp .TP .B Example LC3 BAP Broadcast sink .nf \fB>endpoint.register 00001851\-0000\-1000\-8000\-00805f9b34fb 0x06\fP \fB>Auto Accept (yes/no):\fP y \fB>Max Transports (auto/value):\fP a \fB>Locations:\fP 3 \fB>Supported Context (value):\fP 1 .fi .sp .UNINDENT .sp Note: .sp If running the setup with an audio server that has LE Audio support (such as PipeWire) it will automatically register endpoints according to the configured roles. For more details about configuring a Broadcast Source with PipeWire check: \% .SS unregister .sp Unregister Endpoint. .INDENT 0.0 .TP .B Usage \fB> unregister \fP .TP .B Uses \fBorg.bluez.Media(5)\fP method \fBUnregisterEndpoint\fP .TP .B Either the service UUID or the object path of the endpoint to unregister .TP .B Example Unregister LC3 source endpoint by UUID .nf \fB> unregister 00002bcb\-0000\-1000\-8000\-00805f9b34fb\fP .fi .sp .TP .B Example Unregister LC3 sink endpoint by UUID .nf \fB> unregister 00002bc9\-0000\-1000\-8000\-00805f9b34fb\fP .fi .sp .TP .B Example Unregister broadcast source endpoint by UUID .nf \fB> unregister 00001852\-0000\-1000\-8000\-00805f9b34fb\fP .fi .sp .TP .B Example Unregister broadcast sink endpoint by UUID .nf \fB> unregister 00001851\-0000\-1000\-8000\-00805f9b34fb\fP .fi .sp .TP .B Example Unregister endpoint by object path .nf \fB> unregister /local/endpoint/ep0\fP .fi .sp .TP .B Example Unregister another endpoint by object path .nf \fB> unregister /local/endpoint/ep1\fP .fi .sp .TP .B Example Unregister source endpoint by path .nf \fB> unregister /local/endpoint/source0\fP .fi .sp .TP .B Example Unregister sink endpoint by path .nf \fB> unregister /local/endpoint/sink0\fP .fi .sp .UNINDENT .sp Note: .sp If the endpoint was registered by an audio server, it can\(aqt be unregistered from bluetoothctl. This must be done by the audio server as well. .SS config .sp Configure Endpoint. .INDENT 0.0 .TP .B Usage \fB> config [preset]\fP .TP .B Uses \fBorg.bluez.MediaEndpoint(5)\fP method \fBSetConfiguration\fP .TP .B Path to the remote endpoint to configure .TP .B Path to the local endpoint to use for the configuration .TP .B [preset] Audio quality preset name (optional, auto\-detected if omitted) .TP .B Example Configure remote endpoint with local endpoint (auto preset) .nf \fB> config /org/bluez/hci0/dev_00_11_22_33_44_55/ep0 /local/endpoint/ep0\fP .fi .sp .TP .B Example Configure LE Audio Unicast with BAP setting 32_1_1 .nf \fB> config /org/bluez/hci0/dev_00_11_22_33_44_55/ep0 /local/endpoint/ep0 32_1_1\fP .fi .sp .TP .B Example Configure LE Audio Broadcast Source .nf \fB> config /org/bluez/hci0/dev_00_11_22_33_44_55/bis0 /local/endpoint/broadcast0\fP .fi .sp .UNINDENT .sp Note: .sp If the endpoint was registered by an audio server, it can\(aqt be configured from bluetoothctl. This must be done by the audio server as well. .SS presets .sp List available presets. .INDENT 0.0 .TP .B Usage \fB> presets / [codec[:company]] [preset] [codec config] [metadata]\fP .TP .B / Either endpoint path or service UUID to work with presets .TP .B [codec[:company]] Codec ID with optional company identifier .TP .B [preset] Preset name to display details or create custom preset .TP .B [codec config] Custom codec configuration data in hex format .TP .B [metadata] Additional metadata for the preset .TP .B Example using endpoint .nf \fB>presets /local/endpoint/ep0 32_1_1\fP \fB>presets /local/endpoint/ep0\fP Preset 32_1_1 Configuration.**>0: len 0x02 type 0x01 Configuration.Sampling Frequency: 32 Khz (0x06) Configuration.**>1: len 0x02 type 0x02 Configuration.Frame Duration: 7.5 ms (0x00) Configuration.**>2: len 0x03 type 0x04 Configuration.Frame Length: 60 (0x003c) .fi .sp .TP .B Example List all available presets for local endpoint .nf \fB>presets /local/endpoint/ep0\fP .fi .sp .TP .B Example List all presets for another endpoint .nf \fB>presets /local/endpoint/ep1\fP .fi .sp .TP .B Example using UUID .nf \fB>presets 00002bc9\-0000\-1000\-8000\-00805f9b34fb 0x06 32_1_1\fP \fB>presets 00002bc9\-0000\-1000\-8000\-00805f9b34fb 0x06\fP \&... \fB*32_1_1\fP .fi .sp .TP .B Example List all LC3 sink presets .nf \fB>presets 00002bc9\-0000\-1000\-8000\-00805f9b34fb 0x06\fP .fi .sp .TP .B Example List all LC3 source presets .nf \fB>presets 00002bcb\-0000\-1000\-8000\-00805f9b34fb 0x06\fP .fi .sp .TP .B Example List all broadcast source presets .nf \fB>presets 00001852\-0000\-1000\-8000\-00805f9b34fb 0x06\fP .fi .sp .TP .B Example List all broadcast sink presets .nf \fB>presets 00001851\-0000\-1000\-8000\-00805f9b34fb 0x06\fP .fi .sp .TP .B Example Show details for 48kHz stereo preset .nf \fB>presets 00002bc9\-0000\-1000\-8000\-00805f9b34fb 0x06 48_2_1\fP .fi .sp .TP .B Example Show details for 24kHz mono high\-quality preset .nf \fB>presets 00002bc9\-0000\-1000\-8000\-00805f9b34fb 0x06 24_1_2\fP .fi .sp .TP .B Example Show details for 16kHz mono preset .nf \fB>presets 00002bc9\-0000\-1000\-8000\-00805f9b34fb 0x06 16_1_1\fP .fi .sp .TP .B Example setting up LC3 custom preset .nf \fB>presets 00002bc9\-0000\-1000\-8000\-00805f9b34fb 0x06 custom\fP \fB>[Codec] Enter frequency (Khz):\fP 48 \fB>[Codec] Enter frame duration (ms):\fP 10 \fB>[Codec] Enter channel allocation:\fP 3 \fB>[Codec] Enter frame length:\fP 100 \fB>[QoS] Enter Target Latency (Low, Balance, High):\fP Low \fB>[QoS] Enter SDU Interval (us):\fP 1000 \fB>[QoS] Enter Framing (Unframed, Framed):\fP Unframed \fB>[QoS] Enter PHY (1M, 2M):\fP 2M \fB>[QoS] Enter Max SDU:\fP 200 \fB>[QoS] Enter RTN:\fP 3 \fB>[QoS] Enter Max Transport Latency (ms):\fP 10 \fB>[QoS] Enter Presentation Delay (us):\fP 20000 \fB>presets 00002bc9\-0000\-1000\-8000\-00805f9b34fb 0x06\fP \&... \fB*custom\fP .fi .sp .TP .B Example setting up LC3 custom preset with extra configuration .nf \fB>presets 00002bc9\-0000\-1000\-8000\-00805f9b34fb 0x06 custom \(dq0x03 0xe8 0x00 0x00 0x02 0xe9 0x00\(dq\fP \fB>[Codec] Enter frequency (Khz):\fP 48 \fB>[Codec] Enter frame duration (ms):\fP 10 \fB>[Codec] Enter channel allocation:\fP 3 \fB>[Codec] Enter frame length:\fP 100 \fB>[QoS] Enter Target Latency (Low, Balance, High):\fP Low \fB>[QoS] Enter SDU Interval (us):\fP 1000 \fB>[QoS] Enter Framing (Unframed, Framed):\fP Unframed \fB>[QoS] Enter PHY (1M, 2M):\fP 2M \fB>[QoS] Enter Max SDU:\fP 200 \fB>[QoS] Enter RTN:\fP 3 \fB>[QoS] Enter Max Transport Latency (ms):\fP 10 \fB>[QoS] Enter Presentation Delay (us):\fP 20000 \fB>presets 00002bc9\-0000\-1000\-8000\-00805f9b34fb 0x06\fP \&... \fB*custom\fP .fi .sp .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.AdvertisementMonitorManager.rst0000644000000000000000000000005015142177620022542 xustar0020 atime=1783094000 20 ctime=1783094112 bluez-5.87/doc/org.bluez.AdvertisementMonitorManager.rst0000644000000000000000000000547015142177620022231 0ustar00rootroot===================================== org.bluez.AdvertisementMonitorManager ===================================== --------------------------------------------------------- BlueZ D-Bus AdvertisementMonitorManager API documentation --------------------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.AdvertisementMonitorManager1 [experimental] :Object path: /org/bluez/{hci0,hci1,...} :Used by: **bluetoothctl-monitor(1)** Methods ------- void RegisterMonitor(object application) ```````````````````````````````````````` Registers the root path of a hierarchy of advertisement monitors implementing **org.bluez.AdvertisementMonitor(5)**. The application object path together with the D-Bus ystem bus connection ID define the identification of the application registering advertisement monitors. Once a root path is registered by a client via this method, the client can freely expose/unexpose advertisement monitors without re-registering the root path again. After use, the client should call **UnregisterMonitor()** method to invalidate the advertisement monitors. Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.AlreadyExists: :org.bluez.Error.Failed: Examples: :bluetoothctl: [monitor] > add-or-pattern void UnregisterMonitor(object application) `````````````````````````````````````````` Unregisters a hierarchy of advertisement monitors that has been previously registered with **RegisterMonitor()**. The object path parameter must match the same value that has been used on registration. Upon unregistration, the advertisement monitor(s) should expect to receive **Release()** method as the signal that the advertisement monitor(s) has been deactivated. Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.DoesNotExist: Examples: :bluetoothctl: [monitor] > remove-pattern Properties ---------- array{string} SupportedMonitorTypes [read-only] ``````````````````````````````````````````````` This lists the supported types of advertisement monitors. An application should check this before instantiate and expose an object of **org.bluez.AdvertisementMonitor(5)**. Possible values: :"or_patterns": Patterns with logic OR applied. With this type, property **Patterns** must exist and has at least one pattern. array{string} SupportedFeatures [read-only] ``````````````````````````````````````````` This lists the features of advertisement monitoring supported by **bluetoothd(8)**. Possible values: :"controller-patterns": If the controller is capable of performing advertisement monitoring by patterns, **bluetoothd(8)** would offload the patterns to the controller to reduce power consumption. bluez-5.87/doc/PaxHeaders/org.bluez.obex.Session.50000644000000000000000000000005015221755366017007 xustar0020 atime=1783094006 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.Session.50000644000000000000000000000313715221755366016474 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.obex.Session" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.obex.Session \- BlueZ D-Bus OBEX Client API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez.obex .TP .B Interface org.bluez.obex.Session1 .TP .B Object path /org/bluez/obex/server/session{#} or /org/bluez/obex/client/session{#} .UNINDENT .SS Methods .SS string GetCapabilities() .sp Get remote device capabilities. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.NotSupported .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS Properties .SS string Source [readonly] .sp Bluetooth adapter address .SS string Destination [readonly] .sp Bluetooth device address .SS byte Channel [readonly] .sp Bluetooth channel .SS uint16 PSM [readonly] .sp Bluetooth L2CAP PSM .SS string Target [readonly] .sp Target UUID .SS string Root [readonly] .sp Root path .\" End of generated man page. bluez-5.87/doc/PaxHeaders/bluetooth-meshd.rst.in0000644000000000000000000000005015142177620016667 xustar0020 atime=1783092767 20 ctime=1783094098 bluez-5.87/doc/bluetooth-meshd.rst.in0000644000000000000000000000334115142177620016351 0ustar00rootroot=============== bluetooth-meshd =============== --------------------- Bluetooth Mesh daemon --------------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: March 2021 :Manual section: 8 :Manual group: Linux Connectivity SYNOPSIS ======== **bluetooth-meshd** [*options* ...] DESCRIPTION =========== Daemon for managing Bluetooth Mesh connections on Linux. OPTIONS ======= -h, --help Print bluetooth-meshd options and exit. -n, --nodetach Enable logging in foreground. Directs log output to the controlling terminal in addition to syslog. -i , --io Specifies I/O interface type: *auto* - Use first available controller: via MGMT interface if kernel supports it, otherwise, via raw HCI socket. *generic:[hci]* - Use generic HCI io on interface hci. *unit:*- Specifies open file descriptor for daemon testing. By default, if no type is specified, uses auto I/O on the first available controller. -c , --config Specifies an explicit config file path instead of relying on the default path(*@CONFIGDIR@/mesh-main.conf*) for the config file. -s , --storage Specifies an explicit storage directory path instead of the default path(*@MESH_STORAGEDIR@*) for storing mesh node(s) configuration. -d, --debug Enable debug output. -b, --dbus-debug Enable D-Bus debug output. FILES ===== *@CONFIGDIR@/mesh-main.conf* Location of the global configuration file containing mesh daemon settings. RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/rfcomm.10000644000000000000000000000005015221755507013776 xustar0020 atime=1783094087 20 ctime=1783094112 bluez-5.87/doc/rfcomm.10000644000000000000000000000745415221755507013471 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "rfcomm" "1" "April 28, 2002" "BlueZ" "Linux System Administration" .SH Name rfcomm \- RFCOMM configuration utility .SH SYNOPSIS .sp \fBrfcomm\fP [\fIOPTIONS\fP] <\fICOMMAND\fP> <\fIdev\fP> .SH DESCRIPTION .sp \fBrfcomm(1)\fP is used to set up, maintain, and inspect the RFCOMM configuration of the Bluetooth subsystem in the Linux kernel. If no \fBcommand\fP is given, or if the option \fB\-a\fP is used, \fBrfcomm\fP prints information about the configured RFCOMM devices. .SH OPTIONS .INDENT 0.0 .TP .B \-h Gives a list of possible commands. .TP .B \-a Prints information about all configured RFCOMM devices. .TP .B \-r Switch TTY into raw mode (doesn\(aqt work with \(dqbind\(dq). .UNINDENT .INDENT 0.0 .TP .B \-i <\fIhciX\fP> | <\fIbdaddr\fP> The command is applied to device \fIhciX\fP, which must be the name or the address of an installed Bluetooth device. If not specified, the command will be use the first available Bluetooth device. .UNINDENT .INDENT 0.0 .TP .B \-A Enable authentication .TP .B \-E Enable encryption .TP .B \-S Secure connection .TP .B \-C Become the central of a piconet .TP .BI \-L \ Set linger timeout .UNINDENT .SH COMMANDS .INDENT 0.0 .TP .B show <\fIdev\fP> Display the information about the specified device. .TP .B connect <\fIdev\fP> [\fIbdaddr\fP] [\fIchannel\fP] Connect the RFCOMM device to the remote Bluetooth device on the specified channel. If no channel is specified, it will use the channel number \fB1\fP\&. This command can be terminated with the key sequence CTRL\-C. .TP .B listen <\fIdev\fP> [\fIchannel\fP] [\fIcmd\fP] Listen on a specified RFCOMM channel for incoming connections. If no channel is specified, it will use the channel number \fB1\fP, but a channel must be specified before cmd. If cmd is given, it will be executed as soon as a client connects. When the child process terminates or the client disconnect, the command will terminate. Occurrences of {} in cmd will be replaced by the name of the device used by the connection. This command can be terminated with the key sequence CTRL\-C. .TP .B watch <\fIdev\fP> [\fIchannel\fP] [\fIcmd\fP] Watch is identical to listen except that when the child process terminates or the client disconnect, the command will restart listening with the same parameters. .TP .B bind <\fIdev\fP> [\fIbdaddr\fP] [\fIchannel\fP] This binds the RFCOMM device to a remote Bluetooth device. The command does not establish a connection to the remote device, it only creates the binding. The connection will be established right after an application tries to open the RFCOMM device. If no channel number is specified, it uses the channel number \fB1\fP\&. .TP .B release <\fIdev\fP> This command releases a defined RFCOMM binding. .sp If \fBall\fP is specified for the RFCOMM device, then all bindings will be removed. .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Author Marcel Holtmann .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/rfcomm.rst0000644000000000000000000000005015142177620014442 xustar0020 atime=1783094087 20 ctime=1783094113 bluez-5.87/doc/rfcomm.rst0000644000000000000000000000576715142177620014142 0ustar00rootroot====== rfcomm ====== ---------------------------- RFCOMM configuration utility ---------------------------- :Author: Marcel Holtmann :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: April 28, 2002 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **rfcomm** [*OPTIONS*] <*COMMAND*> <*dev*> DESCRIPTION =========== **rfcomm(1)** is used to set up, maintain, and inspect the RFCOMM configuration of the Bluetooth subsystem in the Linux kernel. If no **command** is given, or if the option **-a** is used, **rfcomm** prints information about the configured RFCOMM devices. OPTIONS ======= -h Gives a list of possible commands. -a Prints information about all configured RFCOMM devices. -r Switch TTY into raw mode (doesn't work with "bind"). -i <*hciX*> | <*bdaddr*> The command is applied to device *hciX*, which must be the name or the address of an installed Bluetooth device. If not specified, the command will be use the first available Bluetooth device. -A Enable authentication -E Enable encryption -S Secure connection -C Become the central of a piconet -L Set linger timeout COMMANDS ======== show <*dev*> Display the information about the specified device. connect <*dev*> [*bdaddr*] [*channel*] Connect the RFCOMM device to the remote Bluetooth device on the specified channel. If no channel is specified, it will use the channel number **1**. This command can be terminated with the key sequence CTRL-C. listen <*dev*> [*channel*] [*cmd*] Listen on a specified RFCOMM channel for incoming connections. If no channel is specified, it will use the channel number **1**, but a channel must be specified before cmd. If cmd is given, it will be executed as soon as a client connects. When the child process terminates or the client disconnect, the command will terminate. Occurrences of {} in cmd will be replaced by the name of the device used by the connection. This command can be terminated with the key sequence CTRL-C. watch <*dev*> [*channel*] [*cmd*] Watch is identical to listen except that when the child process terminates or the client disconnect, the command will restart listening with the same parameters. bind <*dev*> [*bdaddr*] [*channel*] This binds the RFCOMM device to a remote Bluetooth device. The command does not establish a connection to the remote device, it only creates the binding. The connection will be established right after an application tries to open the RFCOMM device. If no channel number is specified, it uses the channel number **1**. release <*dev*> This command releases a defined RFCOMM binding. If **all** is specified for the RFCOMM device, then all bindings will be removed. RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/bluetoothctl-monitor.rst0000644000000000000000000000005015142177620017354 xustar0020 atime=1783094055 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-monitor.rst0000644000000000000000000001225215142177620017037 0ustar00rootroot==================== bluetoothctl-monitor ==================== --------------- Monitor Submenu --------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: July 2023 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **bluetoothctl** [--options] [monitor.commands] This submenu configures advertisement monitors using the **org.bluez.AdvertisementMonitor(5)** and **org.bluez.AdvertisementMonitorManager(5)** interfaces. Monitor Commands ================ set-rssi-threshold ------------------ Set RSSI threshold parameter :Usage: **> set-rssi-threshold ** :: Lower RSSI threshold value in dBm for monitoring :: Higher RSSI threshold value in dBm for monitoring :Example Set low threshold to -80 dBm and high threshold to -40 dBm: | **> set-rssi-threshold -80 -40** :Example Set low threshold to -70 dBm and high threshold to -30 dBm: | **> set-rssi-threshold -70 -30** :Example Set very sensitive low threshold and moderate high threshold: | **> set-rssi-threshold -90 -50** :Example Set less sensitive thresholds for close range monitoring: | **> set-rssi-threshold -60 -20** set-rssi-timeout ---------------- Set RSSI timeout parameter :Usage: **> set-rssi-timeout ** :: Timeout value in seconds for low RSSI threshold :: Timeout value in seconds for high RSSI threshold :Example Set low timeout to 5 seconds, high timeout to 10 seconds: | **> set-rssi-timeout 5 10** :Example Set quick timeout response for both thresholds: | **> set-rssi-timeout 1 5** :Example Set longer timeout periods for stable monitoring: | **> set-rssi-timeout 10 30** :Example Set asymmetric timeout values: | **> set-rssi-timeout 3 15** set-rssi-sampling-period ------------------------- Set RSSI sampling period parameter :Usage: **> set-rssi-sampling-period ** :: Sampling period in 100ms units (0-255, where 0 means report all, 255 means report only first) :Example Set sampling period to 100ms (1 unit): | **> set-rssi-sampling-period 1** :Example Set sampling period to 500ms (5 units): | **> set-rssi-sampling-period 5** :Example Set sampling period to 1 second (10 units): | **> set-rssi-sampling-period 10** :Example Report all advertisements (no sampling): | **> set-rssi-sampling-period 0** :Example Report only first advertisement: | **> set-rssi-sampling-period 255** add-or-pattern -------------- Register 'or pattern' type monitor with the specified RSSI parameters Each pattern requires 3 arguments: Multiple patterns can be specified to create an OR condition. :Usage: **> add-or-pattern [start_pos ad_type content ...]** :Uses: **org.bluez.AdvertisementMonitorManager(5)** method **RegisterMonitor** :: Byte position in advertisement data where pattern matching starts (0-based) :: Advertisement data type (e.g., 9 for Complete Local Name, 1 for Flags) :: Hexadecimal pattern content to match :Example Monitor for "Samsung" in Complete Local Name (type 9): | **> add-or-pattern 0 9 53616d73756e67** :Example Monitor for devices with Flags type (type 1) containing 0x06: | **> add-or-pattern 0 1 06** :Example Monitor for two different patterns (OR condition): | **> add-or-pattern 0 9 53616d73756e67 0 9 4170706c65** :Example Monitor for specific manufacturer data (type 255): | **> add-or-pattern 0 255 4c000215** get-pattern ----------- Get advertisement monitor :Usage: **> get-pattern ** :: Monitor ID number to retrieve details for, or "all" for all active monitors :Example Get details of monitor ID 0: | **> get-pattern 0** :Example Get details of monitor ID 1: | **> get-pattern 1** :Example Get details of monitor ID 5: | **> get-pattern 5** :Example Get details of all active monitors: | **> get-pattern all** remove-pattern -------------- Remove advertisement monitor :Usage: **> remove-pattern ** :Uses: **org.bluez.AdvertisementMonitorManager(5)** method **UnregisterMonitor** :: Monitor ID number to remove, or "all" to remove all active monitors :Example Remove monitor with ID 0: | **> remove-pattern 0** :Example Remove monitor with ID 1: | **> remove-pattern 1** :Example Remove monitor with ID 3: | **> remove-pattern 3** :Example Remove all active advertisement monitors: | **> remove-pattern all** get-supported-info ------------------ Get advertisement manager supported features and supported monitor types :Usage: **> get-supported-info** :Uses: **org.bluez.AdvertisementMonitorManager(5)** properties :Example Display supported monitor features and types: | **> get-supported-info** print-usage ----------- Print the command usage :Usage: **> print-usage ** :: Command name to display detailed usage information for :Example Show detailed usage for add-or-pattern command: | **> print-usage add-or-pattern** RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/hciconfig.10000644000000000000000000000005015221755503014440 xustar0020 atime=1783094083 20 ctime=1783094112 bluez-5.87/doc/hciconfig.10000644000000000000000000001672215221755503014131 0ustar00rootroot'\" t .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "hciconfig" "1" "Nov 11, 2002" "BlueZ" "Linux System Administration" .SH Name hciconfig \- Configure Bluetooth devices .SH SYNOPSIS .sp \fBhciconfig\fP \-h .sp \fBhciconfig\fP [\-a] .sp \fBhciconfig\fP [\-a] \fIhciX\fP [\fICOMMAND\fP [\fIPARAMETERS\fP]] .SH DESCRIPTION .sp \fBhciconfig(1)\fP is used to configure Bluetooth devices. \fIhciX\fP is the name of a Bluetooth device installed in the system. If hciX is not given, hciconfig prints name and basic information about all the Bluetooth devices installed in the system. .sp If \fIhciX\fP is given but no command is given, it prints basic information on device \fIhciX\fP only. Basic information is interface type, BD address, ACL MTU, SCO MTU, flags (up, init, running, raw, page scan enabled, inquiry scan enabled, inquiry, authentication enabled, encryption enabled). .SH OPTIONS .INDENT 0.0 .TP .B \-a\fP,\fB \-\-all Print features, packet type, link policy, link mode, class, Version other than the basic info. .TP .B \-h\fP,\fB \-\-help Show help options .UNINDENT .SH COMMANDS .INDENT 0.0 .TP .B up Open and initialize HCI device. .TP .B down Close HCI device. .TP .B reset Reset HCI device. .TP .B rstat Reset statistic counters. .TP .B auth Enable authentication (sets device to security mode 3). .TP .B noauth Disable authentication. .TP .B encrypt Enable encryption (sets device to security mode 3). .TP .B noencrypt Disable encryption. .TP .B secmgr Enable security manager (current kernel support is limited). .TP .B nosecmgr Disable security manager. .TP .B piscan Enable page and inquiry scan. .TP .B noscan Disable page and inquiry scan. .TP .B iscan Enable inquiry scan, disable page scan. .TP .B pscan Enable page scan, disable inquiry scan. .TP .B ptype [\fItype\fP] With no \fItype\fP , displays the current packet types. Otherwise, all the packet types specified by \fItype\fP are set. \fItype\fP is a comma\-separated list of packet types, where the possible packet types are \fBDM1\fP, \fBDM3\fP, \fBDM5\fP, \fBDH1\fP, \fBDH3\fP, \fBDH5\fP, \fBHV1\fP, \fBHV2\fP, \fBHV3\fP\&. .TP .B name [\fIname\fP] With no \fIname\fP, prints local name. Otherwise, sets local name to \fIname\fP\&. .TP .B class [\fIclass\fP] With no \fIclass\fP, prints class of device. Otherwise, sets class of device to \fIclass\fP\&. \fIclass\fP is a 24\-bit hex number describing the class of device, as specified in section 1.2 of the Bluetooth Assigned Numbers document. .TP .B voice [\fIvoice\fP] With no \fIvoice\fP, prints voice setting. Otherwise, sets voice setting to \fIvoice\fP\&. \fIvoice\fP is a 16\-bit hex number describing the voice setting. .TP .B iac [\fIiac\fP] With no \fIiac\fP, prints the current IAC setting. Otherwise, sets the IAC to \fIiac\fP\&. .TP .B inqtpl [\fIlevel\fP] With no \fIlevel\fP, prints out the current inquiry transmit power level. Otherwise, sets inquiry transmit power level to \fIlevel\fP\&. .TP .B inqmode [\fImode\fP] .INDENT 7.0 .INDENT 3.5 With no \fImode\fP, prints out the current inquiry mode. Otherwise, sets inquiry mode to \fImode\fP\&. .UNINDENT .UNINDENT .TS box center; l|l. T{ \fImode\fP T} T{ Description T} _ T{ 0 T} T{ Standard Inquiry T} _ T{ 1 T} T{ Inquiry with RSSI T} _ T{ 2 T} T{ Inquiry with RSSI or Extended Inquiry T} .TE .TP .B inqdata [\fIdata\fP] With no \fIdata\fP, prints out the current inquiry data. Otherwise, sets inquiry data to \fIdata\fP\&. .TP .B inqtype [\fItype\fP] With no \fItype\fP, prints out the current inquiry scan type. Otherwise, sets inquiry scan type to \fItype\fP\&. .TP .B inqparams [\fIwin:int\fP] With no \fIwin:int\fP, prints inquiry scan window and interval. Otherwise, sets inquiry scan window to \fIwin\fP slots and inquiry scan interval to \fIint\fP slots. .TP .B pageparms [\fIwin:int\fP] With no \fIwin:int\fP, prints page scan window and interval. Otherwise, sets page scan window to \fIwin\fP slots and page scan interval to \fIint\fP slots. .TP .B pageto [\fIto\fP] With no \fIto\fP, prints page timeout. Otherwise, sets page timeout \fIto\fP to slots. .TP .B afhmode [\fImode\fP] With no \fImode\fP, prints out the current AFH mode. Otherwise, sets AFH mode to \fImode\fP\&. .UNINDENT .TS box center; l|l. T{ \fImode\fP T} T{ Description T} _ T{ 0 T} T{ Enable T} _ T{ 1 T} T{ Disable T} .TE .INDENT 0.0 .TP .B sspmode [\fImode\fP] With no \fImode\fP, prints out the current Simple Pairing mode. Otherwise, sets Simple Pairing mode to \fImode\fP\&. .UNINDENT .TS box center; l|l. T{ \fImode\fP T} T{ Description T} _ T{ 0 T} T{ Enable T} _ T{ 1 T} T{ Disable T} .TE .INDENT 0.0 .TP .B aclmtu \fImtu:pkt\fP Sets ACL MTU to \fImtu\fP bytes and ACL buffer size to \fIpkt\fP packets. .TP .B scomtu \fImtu:pkt\fP Sets SCO MTU to \fImtu\fP bytes and SCO buffer size to \fIpkt\fP packets. .TP .B delkey <\fIbdaddr\fP> This command deletes the stored link key for \fIbdaddr\fP from the device. .TP .B oobdata Get local OOB data (invalidates previously read data). .TP .B commands Display supported commands. .TP .B features Display device features. .TP .B version Display version information. .TP .B revision Display revision information. .TP .B lm [\fImode\fP] With no \fImode\fP, prints link mode. \fBCENTRAL\fP or \fBPERIPHERAL\fP mean, respectively, to ask to become central or to remain peripheral when a connection request comes in. The additional keyword \fBACCEPT\fP means that baseband connections will be accepted even if there are no listening \fIAF_BLUETOOTH\fP sockets. \fImode\fP is \fBNONE\fP or a comma\-separated list of keywords, where possible keywords are \fBCENTRAL\fP and \fBACCEPT\fP\&. \fBNONE\fP sets link policy to the default behaviour of remaining peripheral and not accepting baseband connections when there are no listening \fIAF_BLUETOOTH\fP sockets. If \fBCENTRAL\fP is present, the device will ask to become central if a connection request comes in. If \fBACCEPT\fP is present, the device will accept baseband connections even when there are no listening \fIAF_BLUETOOTH\fP sockets. .TP .B block <\fIbdaddr\fP> Add a device to the reject list .TP .B unblock <\fIbdaddr\fP> Remove a device from the reject list .TP .B lerandaddr <\fIbdaddr\fP> Set LE Random Address .TP .B leadv [\fItype\fP] Enable LE Advertising. .UNINDENT .TS box center; l|l. T{ \fItype\fP T} T{ Description T} _ T{ 0 T} T{ Connectable undirected advertising (default) T} _ T{ 3 T} T{ Non connectable undirected advertising T} .TE .INDENT 0.0 .TP .B noleadv Disable LE Advertising .TP .B lestates Display the supported LE states .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Author Maxim Krasnyansky , Marcel Holtmann , Fabrizio Gennari .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.LEAdvertisement.50000644000000000000000000000005015221755356017462 xustar0020 atime=1783093998 20 ctime=1783094112 bluez-5.87/doc/org.bluez.LEAdvertisement.50000644000000000000000000001601115221755356017142 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.LEAdvertisement" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.LEAdvertisement \- BlueZ D-Bus LEAdvertisement API documentation .SH Description .sp Advertising packets are structured data which is broadcast on the LE Advertising channels and available for all devices in range. Because of the limited space available in LE Advertising packets, each packet\(aqs contents must be carefully controlled. .sp \fBbluetoothd(8)\fP acts as a store for the Advertisement Data which is meant to be sent. It constructs the correct Advertisement Data from the structured data and configured the kernel to send the correct advertisement. .SH Interface .sp Specifies the Advertisement Data to be broadcast and some advertising parameters. Properties which are not present will not be included in the data. Required advertisement data types will always be included. All UUIDs are 128\-bit versions in the API, and 16 or 32\-bit versions of the same UUID will be used in the advertising data as appropriate. .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.LEAdvertisement1 .TP .B Object path freely definable .TP .B Used by \fBbluetoothctl\-advertise(1)\fP .UNINDENT .SS Methods .SS void Release() [noreply] .sp This method gets called when the \fBbluetoothd(8)\fP removes the Advertisement. .sp A client can use it to do cleanup tasks. There is no need to call \fBUnregisterAdvertisement()\fP because when this method gets called it has already been unregistered. .SS Properties .SS string Type [readonly] .sp Determines the type of advertising packet requested. .sp Possible values: .INDENT 0.0 .TP .B \(dqbroadcast\(dq .TP .B \(dqperipheral\(dq .UNINDENT .SS array{string} ServiceUUIDs [readonly, optional] .sp List of UUIDs to include in the \(dqService UUID\(dq field of the Advertising Data. .SS dict ManufacturerData [readonly, optional] .sp Manufacturer Data fields to include in the Advertising Data. .sp Keys are the Manufacturer ID to associate with the data. .SS array{string} SolicitUUIDs [readonly, optional] .sp List of UUIDs to include in the \(dqService Solicitation\(dq field of the Advertising Data. .SS dict ServiceData [readonly, optional] .sp Service Data elements to include in the Advertising Data. .sp The keys are the UUID to associate with the data. .SS dict Data [readonly, optional] .sp Advertising Data to include. .sp Key is the advertising type and value is the data as byte array. .sp Note: Types already handled by other properties shall not be used. .sp Possible values: .INDENT 0.0 .TP .B .UNINDENT .INDENT 0.0 .TP .B Example: 0x26 0x01 0x01... .UNINDENT .SS array{string} ScanResponseServiceUUIDs [readonly, optional, experimental] .sp List of UUIDs to include in the \(dqService UUID\(dq field of the Scan Response Data. .SS dict ScanResponseManufacturerData [readonly, optional, experimental] .sp Manufacturer Data fields to include in the Scan Response Data. .sp Keys are the Manufacturer ID to associate with the data. .SS array{string} ScanResponseSolicitUUIDs [readonly, optional, experimental] .sp List of UUIDs to include in the \(dqService Solicitation\(dq field of the Scan Response Data. .SS dict ScanResponseServiceData [readonly, optional, experimental] .sp Service Data elements to include in the Scan Response Data. .sp The keys are the UUID to associate with the data. .SS dict ScanResponseData [readonly, optional, experimental] .sp Scan Response Data to include. .sp Key is the advertising type and value is the data as byte array. .SS bool Discoverable [readonly, optional] .sp Advertise as general discoverable. When present this will override adapter Discoverable property. .sp Note: This property shall not be set when \fBType\fP is set to \(dqbroadcast\(dq. .SS uint16 DiscoverableTimeout [readonly, optional] .sp The discoverable timeout in seconds. A value of zero means that the timeout is disabled and it will stay in discoverable/limited mode forever. .sp Note: This property shall not be set when \fBType\fP is set to \(dqbroadcast\(dq. .SS array{string} Includes [readonly, optional] .sp List of features to be included in the advertising packet. .sp Possible values: .sp See \fBorg.bluez.LEAdvertisingManager(5)\fP \fBSupportedIncludes\fP property. .SS string LocalName [readonly, optional] .sp Local name to be used in the advertising report. If the string is too big to fit into the packet it will be truncated. .sp If this property is available \(aqlocal\-name\(aq cannot be present in the \fBIncludes\fP\&. .SS uint16 Appearance [readonly, optional] .sp Appearance to be used in the advertising report. .sp Possible values: as found on GAP Service. .SS uint16 Duration [readonly, optional] .sp Rotation duration of the advertisement in seconds. .sp If there are other applications advertising no duration is set the default is 2 seconds. .SS uint16 Timeout [readonly, optional] .sp Timeout of the advertisement in seconds. This defines the lifetime of the advertisement. .SS string SecondaryChannel [readonly, optional] .sp Secondary channel to be used. .sp Primary channel is always set to \(dq1M\(dq except when \(dqCoded\(dq is set. .sp Possible value: .INDENT 0.0 .TP .B \(dq1M\(dq (default) .TP .B \(dq2M\(dq .TP .B \(dqCoded\(dq .UNINDENT .SS uint32 MinInterval [readonly, optional] .sp Minimum advertising interval to be used by the advertising set, in milliseconds. .sp Acceptable values are in the range [20ms, 10,485s]. .sp If the provided MinInterval is larger than the provided MaxInterval, the registration will return failure. .SS uint32 MaxInterval [readonly, optional] .sp Maximum advertising interval to be used by the advertising set, in milliseconds. .sp Acceptable values are in the range [20ms, 10,485s]. .sp If the provided MinInterval is larger than the provided MaxInterval, the registration will return failure. .SS int16 TxPower [readonly, optional] .sp Requested transmission power of this advertising set. .sp The provided value is used only if the \(dqCanSetTxPower\(dq feature is enabled on the \fBorg.bluez.LEAdvertisingManager(5)\fP\&. .sp Values must be in range [\-127 to +20], where units are in dBm. .SS byte Instance [read\-write, optional] .sp Instance of the advertisement. Set by bluetoothd after advertisement is registered with \fBorg.bluez.LEAdvertisingManager(5)\fP\&. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/btmon-cs.rst0000644000000000000000000000005015200607060014670 xustar0020 atime=1783092683 20 ctime=1783094112 bluez-5.87/doc/btmon-cs.rst0000644000000000000000000014730215200607060014360 0ustar00rootroot.. This file is included by btmon.rst. CHANNEL SOUNDING PROTOCOL FLOW =============================== Channel Sounding (CS) enables precise distance measurement between two Bluetooth LE devices. It uses dedicated HCI commands and events to configure and execute measurement procedures that exchange tones and timing packets on multiple channels. btmon fully decodes all CS operations including step-level result data. CS uses two roles: the **Initiator** sends CS packets first in each step, and the **Reflector** responds. Both devices must support CS (feature bits 46--48 in the LE feature set). Capability Exchange -------------------- Before any CS activity, both devices must learn each other's CS capabilities. The local controller's capabilities are read once, and the remote device's capabilities are fetched over the air. **Read local CS capabilities**:: < HCI Command: LE CS Read Local Supported Capabilities (0x08|0x0089) plen 0 #100 [hci0] > HCI Event: Command Complete (0x0e) plen 42 #101 [hci0] LE CS Read Local Supported Capabilities (0x08|0x0089) ncmd 1 Status: Success (0x00) Num Config Supported: 4 Max Consecutive Procedures Supported: 255 Num Antennas Supported: 2 Max Antenna Paths Supported: 4 Roles Supported: 0x03 Initiator Reflector Modes Supported: 0x03 RTT Capability: 0x03 RTT AA Only N: 10 RTT Sounding N: 10 RTT Random Payload N: 10 CS Sync PHYs Supported: 0x02 LE 2M T_IP1 Times Supported: 0x0005 10 us 30 us T_IP2 Times Supported: 0x0005 10 us 30 us T_FCS Times Supported: 0x0009 15 us 50 us T_PM Times Supported: 0x0003 10 us 20 us Key capability fields: - **Roles Supported** -- Bitmask: bit 0 = Initiator, bit 1 = Reflector. A device must support at least one role. - **Modes Supported** -- Bitmask of CS measurement modes the controller can perform (Mode 1 = RTT, Mode 2 = PBR tone, Mode 3 = both). - **CS Sync PHYs** -- Physical layers available for CS sync exchange. ``LE 2M`` is the most common. - **Num Antennas / Max Antenna Paths** -- Determines multi-antenna Phase-Based Ranging capability. - **T_IP / T_FCS / T_PM times** -- Timing parameters the controller supports for inter-step and tone durations. **Read remote CS capabilities** (over the air via LL):: < HCI Command: LE CS Read Remote Supported Capabilities (0x08|0x008a) plen 2 #110 [hci0] Handle: 2048 > HCI Event: Command Status (0x0f) plen 4 #111 [hci0] LE CS Read Remote Supported Capabilities (0x08|0x008a) ncmd 1 Status: Success (0x00) > HCI Event: LE Meta Event (0x3e) plen 42 #115 [hci0] LE CS Read Remote Supported Capabilities Complete (0x2c) Status: Success (0x00) Handle: 2048 Num Config Supported: 4 Max Consecutive Procedures Supported: 128 Num Antennas Supported: 1 Max Antenna Paths Supported: 1 Roles Supported: 0x03 Initiator Reflector Modes Supported: 0x03 ... This is an asynchronous command -- the controller sends Command Status immediately, then the completion event arrives after the LL exchange. If this fails with a non-zero status, the remote device may not support CS or the connection may have dropped. Cached capabilities can be written directly instead of reading over the air, using ``LE CS Write Cached Remote Supported Capabilities``. Security Enable ---------------- CS security must be enabled on the connection before creating configurations. This performs a CS security start procedure over the LL to exchange nonces:: < HCI Command: LE CS Security Enable (0x08|0x008c) plen 2 #120 [hci0] Handle: 2048 > HCI Event: Command Status (0x0f) plen 4 #121 [hci0] LE CS Security Enable (0x08|0x008c) ncmd 1 Status: Success (0x00) > HCI Event: LE Meta Event (0x3e) plen 3 #125 [hci0] LE CS Security Enable Complete (0x2e) Status: Success (0x00) Handle: 2048 Security Enable must complete successfully before configuration or procedure commands. If this fails, the link may lack encryption or the remote does not support CS security. Default Settings ----------------- Default settings configure which roles the local device is willing to accept and the antenna/power preferences for this connection:: < HCI Command: LE CS Set Default Settings (0x08|0x008d) plen 5 #130 [hci0] Handle: 2048 Role Enable: 0x03 Initiator Reflector CS Sync Antenna Selection: 0x01 Max TX Power: 20 > HCI Event: Command Complete (0x0e) plen 5 #131 [hci0] LE CS Set Default Settings (0x08|0x008d) ncmd 1 Status: Success (0x00) Handle: 2048 - **Role Enable** -- Bitmask of roles the device is willing to perform. Both sides typically enable both roles for flexibility. - **CS Sync Antenna Selection** -- Preferred antenna for CS sync exchange. - **Max TX Power** -- Upper bound on transmit power for CS procedures (dBm, signed). FAE Table Exchange ------------------- The Frequency Actuation Error (FAE) table contains per-channel calibration data that improves distance accuracy. Like capabilities, it can be read from the remote or written from a cache. **Read remote FAE table**:: < HCI Command: LE CS Read Remote FAE Table (0x08|0x008e) plen 2 #135 [hci0] Handle: 2048 > HCI Event: Command Status (0x0f) plen 4 #136 [hci0] LE CS Read Remote FAE Table (0x08|0x008e) ncmd 1 Status: Success (0x00) > HCI Event: LE Meta Event (0x3e) plen 75 #140 [hci0] LE CS Read Remote FAE Table Complete (0x2d) Status: Success (0x00) Handle: 2048 The FAE table is 72 bytes (one byte per channel). Values are signed offsets in 0.5 ppm units. A value of ``0x7f`` means the channel is unused or not measured. Configuration -------------- A CS configuration defines the measurement parameters: mode, role, channel map, and step counts. Up to 4 configurations can exist simultaneously per connection (``config_id`` 0--3). **Create a CS configuration**:: < HCI Command: LE CS Create Config (0x08|0x0090) plen 20 #150 [hci0] Handle: 2048 Config ID: 0 Create Context: 0x00 Main Mode Type: 0x01 Sub Mode Type: 0xff Min Main Mode Steps: 2 Max Main Mode Steps: 5 Main Mode Repetition: 0 Mode 0 Steps: 3 Role: Initiator (0x00) RTT Type: 0x01 CS Sync PHY: LE 2M (0x01) Channel Map: ffffffffff7f0000000000000000 Channel Map Repetition: 1 Channel Selection Type: 0x00 Ch3c Shape: 0x00 Ch3c Jump: 0x00 > HCI Event: Command Status (0x0f) plen 4 #151 [hci0] LE CS Create Config (0x08|0x0090) ncmd 1 Status: Success (0x00) > HCI Event: LE Meta Event (0x3e) plen 30 #155 [hci0] LE CS Config Complete (0x2f) Status: Success (0x00) Handle: 2048 Config ID: 0 Action: 0x00 Main Mode Type: 0x01 Sub Mode Type: 0xff Min Main Mode Steps: 2 Max Main Mode Steps: 5 Main Mode Repetition: 0 Mode 0 Steps: 3 Role: Initiator (0x00) RTT Type: 0x01 CS Sync PHY: LE 2M (0x01) Channel Map: ffffffffff7f0000000000000000 Channel Map Repetition: 1 Channel Selection Type: 0x00 Ch3c Shape: 0x00 Ch3c Jump: 0x00 T_IP1 Time: 30 us T_IP2 Time: 30 us T_FCS Time: 50 us T_PM Time: 10 us Key configuration fields: - **Main Mode Type** -- The primary CS measurement mode: ``0x01`` = Mode 1 (RTT only), ``0x02`` = Mode 2 (PBR/tone only), ``0x03`` = Mode 3 (RTT + PBR combined). - **Sub Mode Type** -- Secondary mode interleaved with main mode. ``0xff`` = no sub mode. - **Mode 0 Steps** -- Number of frequency calibration (sync) steps per subevent. Mode 0 is always present for frequency offset compensation. - **Main/Min/Max Mode Steps** -- Controls how many measurement steps are performed per subevent. - **Role** -- ``0x00`` = Initiator, ``0x01`` = Reflector. This is the local device's role in this configuration. - **RTT Type** -- Round-trip time measurement variant (AA only, sounding sequence, random sequence). - **Channel Map** -- 10-byte bitmask of channels available for CS. Must have at least 15 channels enabled. - **T_IP1/T_IP2/T_FCS/T_PM** -- Timing parameters selected by the controller from the intersection of both devices' capabilities. Reported in the Config Complete event. The Config Complete event confirms the negotiated parameters. The controller may adjust timing values based on both devices' capabilities. **Remove a CS configuration**:: < HCI Command: LE CS Remove Config (0x08|0x0091) plen 3 #160 [hci0] Handle: 2048 Config ID: 0 > HCI Event: Command Status (0x0f) plen 4 #161 [hci0] LE CS Remove Config (0x08|0x0091) ncmd 1 Status: Success (0x00) Procedure Parameters --------------------- Before enabling a procedure, its scheduling and antenna parameters are configured:: < HCI Command: LE CS Set Procedure Parameters (0x08|0x0093) plen 16 #170 [hci0] Handle: 2048 Config ID: 0 Max Procedure Len: 200 Min Procedure Interval: 10 Max Procedure Interval: 20 Max Procedure Count: 0 Min Subevent Len: 5000 us Max Subevent Len: 10000 us Tone Antenna Config Selection: 0x01 PHY: LE 2M (0x02) TX Power Delta: 0 Preferred Peer Antenna: 0x01 SNR Control Initiator: 0x00 SNR Control Reflector: 0x00 > HCI Event: Command Complete (0x0e) plen 5 #171 [hci0] LE CS Set Procedure Parameters (0x08|0x0093) ncmd 1 Status: Success (0x00) Handle: 2048 - **Max Procedure Len** -- Maximum duration of a single CS procedure in units of 0.625 ms. - **Procedure Interval** -- Spacing between repeated procedures (in connection events). - **Max Procedure Count** -- ``0`` means indefinite repetition until explicitly disabled. - **Subevent Len** -- Duration bounds for each subevent within a procedure (in microseconds, 24-bit). - **Tone Antenna Config Selection** -- Which antenna pattern to use for tone exchange. - **SNR Control** -- Signal-to-noise ratio requirements for Initiator and Reflector. Procedure Enable / Disable ---------------------------- Once configuration and parameters are set, the CS procedure is started and stopped with enable/disable commands. **Enable a CS procedure**:: < HCI Command: LE CS Procedure Enable (0x08|0x0094) plen 4 #180 [hci0] Handle: 2048 Config ID: 0 Enable: 0x01 > HCI Event: Command Status (0x0f) plen 4 #181 [hci0] LE CS Procedure Enable (0x08|0x0094) ncmd 1 Status: Success (0x00) > HCI Event: LE Meta Event (0x3e) plen 20 #185 [hci0] LE CS Procedure Enable Complete (0x30) Status: Success (0x00) Handle: 2048 Config ID: 0 State: 0x01 Tone Antenna Config Selection: 0x01 Selected TX Power: 12 Subevent Len: 5000 us Subevents Per Event: 2 Subevent Interval: 3750 Event Interval: 10 Procedure Interval: 10 Procedure Count: 100 Max Procedure Len: 200 The Procedure Enable Complete event confirms the actual scheduling parameters chosen by the controller. Important fields: - **State** -- ``0x01`` = procedure enabled, ``0x00`` = disabled. - **Subevents Per Event** -- How many subevents fit in each connection event. - **Procedure Count** -- Actual number of procedures that will be executed (may differ from the requested maximum). **Disable a CS procedure**:: < HCI Command: LE CS Procedure Enable (0x08|0x0094) plen 4 #300 [hci0] Handle: 2048 Config ID: 0 Enable: 0x00 > HCI Event: Command Status (0x0f) plen 4 #301 [hci0] LE CS Procedure Enable (0x08|0x0094) ncmd 1 Status: Success (0x00) > HCI Event: LE Meta Event (0x3e) plen 20 #305 [hci0] LE CS Procedure Enable Complete (0x30) Status: Success (0x00) Handle: 2048 Config ID: 0 State: 0x00 ... Channel Classification ----------------------- The host can restrict which channels CS uses by marking noisy channels as unused:: < HCI Command: LE CS Set Channel Classification (0x08|0x0092) plen 10 #145 [hci0] Channel Map: ffffffffff7f0000000000 > HCI Event: Command Complete (0x0e) plen 1 #146 [hci0] LE CS Set Channel Classification (0x08|0x0092) ncmd 1 Status: Success (0x00) The channel map is a 10-byte (80-bit) bitmask. Bit N = 1 means channel N is available for CS. At least 15 channels must remain enabled or the procedure will abort. Subevent Results ----------------- While a CS procedure is running, the controller reports measurement results via subevent result events. Each subevent contains multiple steps, and each step contains mode-specific measurement data. **Subevent Result**:: > HCI Event: LE Meta Event (0x3e) plen 50 #200 [hci0] LE CS Subevent Result (0x31) Handle: 2048 Config ID: 0 Start ACL Conn Event Counter: 1200 Procedure Counter: 0 Frequency Compensation: 0x0000 Reference Power Level: -20 Procedure Done Status: Partial results (0x01) Subevent Done Status: All results complete (0x00) Abort Reason: 0x00 Num Antenna Paths: 1 Num Steps Reported: 8 Step Data: Mode: 0 Channel: 10 Length: 5 Packet Quality: 0x00 Packet RSSI: -45 Packet Antenna: 0 Measured Freq Offset: 150 Mode: 1 Channel: 20 Length: 6 Packet Quality: 0x00 Packet NADM: Attack is extremely unlikely (0x00) Packet RSSI: -42 ToA/ToD: 1234 Packet Antenna: 0 Mode: 2 Channel: 30 Length: 5 Antenna Permutation Index: 0 PCT[0]: I=1024, Q=-512 Tone Quality: High (0x00) ... Result events can be split across multiple events when data is large: **Subevent Result Continue** (continuation fragment):: > HCI Event: LE Meta Event (0x3e) plen 40 #202 [hci0] LE CS Subevent Result Continue (0x32) Handle: 2048 Config ID: 0 Procedure Done Status: Partial results (0x01) Subevent Done Status: All results complete (0x00) Abort Reason: 0x00 Num Antenna Paths: 1 Num Steps Reported: 4 Step Data: ... CS Step Modes -------------- Each CS subevent consists of steps executed on different channels. Each step operates in one of four modes: **Mode 0 -- Frequency Calibration (CS Sync)** Mode 0 steps are always present at the start of each subevent. They exchange a known CS sync sequence to calibrate frequency offsets between the two devices. Fields reported: - **Packet Quality** -- ``0x00`` = CS Access Address matched, ``0x01`` = bit errors detected, ``0x02`` = not found. - **Packet RSSI** -- Received signal strength (signed, dBm). - **Packet Antenna** -- Antenna index used. - **Measured Freq Offset** -- Frequency offset in 0.01 ppm units (15-bit value). Only present when data length is 5. **Mode 1 -- Round-Trip Time (RTT)** Mode 1 measures the time-of-arrival and time-of-departure to compute round-trip delay for distance estimation. Fields reported: - **Packet Quality** -- Same as Mode 0. - **Packet NADM** -- Normalized Attack Detector Metric, indicates likelihood that the RTT measurement has been tampered with: - ``0x00`` = attack extremely unlikely - ``0x01`` = attack very unlikely - ``0x02`` = attack unlikely - ``0x03`` = attack possible - ``0x04`` = attack likely - ``0x05`` = attack very likely - ``0x06`` = attack extremely likely - ``0xff`` = unknown - **ToA/ToD** -- Time-of-Arrival minus Time-of-Departure difference (signed 16-bit). ``0x8000`` = measurement not available. - **PCT1/PCT2** -- Phase Correction Terms (I/Q, 12 bits each). Present only with certain RTT types. **Mode 2 -- Phase-Based Ranging (PBR / Tone Exchange)** Mode 2 exchanges tones for high-precision phase measurement. Multiple antenna paths can be measured simultaneously. Fields reported: - **Antenna Permutation Index** -- Identifies the antenna switching pattern used. - **PCT** (per antenna path) -- Phase Correction Term with I component (bits 0--11) and Q component (bits 12--23). - **Tone Quality Indicator** -- Low nibble: - ``0x00`` = high quality - ``0x01`` = medium quality - ``0x02`` = low quality - ``0x03`` = not available High nibble (tone extension slot): - ``0x00`` = not a tone extension slot - ``0x01`` = tone extension slot, tone not expected - ``0x02`` = tone extension slot, tone expected **Mode 3 -- Combined RTT + Tone** Mode 3 combines Mode 1 and Mode 2 in a single step. The initial fields match Mode 1 (quality, NADM, RSSI, ToA/ToD, antenna), followed by Mode 2 tone data. Result Status Fields --------------------- Each subevent result event contains status fields that indicate whether the procedure and subevent completed normally. **Procedure Done Status**: - ``0x00`` -- All results complete for this CS procedure - ``0x01`` -- Partial results, more events will follow - ``0x0f`` -- All subsequent procedures aborted **Subevent Done Status**: - ``0x00`` -- All results complete for this subevent - ``0x01`` -- Partial results, continuation event will follow - ``0x0f`` -- Current subevent aborted **Abort Reason** (packed byte): Low nibble (procedure abort reason): - ``0x00`` -- No abort - ``0x01`` -- Aborted by local Host or remote request - ``0x02`` -- Channel map has fewer than 15 channels - ``0x03`` -- Channel map update instant has passed - ``0x0f`` -- Unspecified reason High nibble (subevent abort reason): - ``0x00`` -- No abort - ``0x01`` -- Aborted by local Host or remote request - ``0x02`` -- No CS_SYNC (Mode 0) received - ``0x03`` -- Scheduling conflict or limited resources - ``0x0f`` -- Unspecified reason CS State Machine ----------------- A CS configuration transitions through these states on each device. The state is not reported explicitly in a single field -- it is inferred from the sequence of HCI commands and events:: ┌──────────┐ │ IDLE │ └────┬─────┘ │ LE CS Read Local/Remote Supported Capabilities │ LE CS Security Enable Complete │ LE CS Set Default Settings ▼ ┌──────────────────┐ │ CAPABILITIES │ Both devices' CS parameters are known. │ EXCHANGED │ Security is established. └────┬─────────────┘ │ LE CS Create Config │ ──► LE CS Config Complete (status=0x00) ▼ ┌──────────────────┐ │ CONFIGURED │ Config ID N exists with negotiated params. │ (config_id=N) │ Can create up to 4 configs simultaneously. └────┬─────────────┘ │ LE CS Set Procedure Parameters ▼ ┌──────────────────┐ │ PARAMETERS SET │ Scheduling and antenna params are locked. │ (config_id=N) │ └────┬─────────────┘ │ LE CS Procedure Enable (enable=1) │ ──► LE CS Procedure Enable Complete (state=1) ▼ ┌──────────────────┐ │ PROCEDURE │ Controller is actively measuring. │ RUNNING │ Subevent Result events stream in. │ (config_id=N) │ └────┬─────────────┘ │ LE CS Procedure Enable (enable=0) │ ──► LE CS Procedure Enable Complete (state=0) ▼ ┌──────────────────┐ │ CONFIGURED │ Config still exists, can re-enable. │ (config_id=N) │ └────┬─────────────┘ │ LE CS Remove Config ▼ ┌──────────┐ │ IDLE │ └──────────┘ Multiple configurations can coexist. Removing one config does not affect others. Typical CS Setup Sequence -------------------------- A complete HCI-only CS distance measurement session follows this flow. The left column shows the Host ──► Controller commands, and the right column shows the resulting state:: Host ──► Controller State ═══════════════════════════════════════════════════════════════ LE CS Read Local Supported Capabilities IDLE ◄── Command Complete (capabilities) │ LE CS Read Remote Supported Capabilities │ ◄── Command Status │ ◄── LE CS Read Remote Supp. Cap. Complete │ │ LE CS Security Enable │ ◄── Command Status │ ◄── LE CS Security Enable Complete │ │ LE CS Set Default Settings │ ◄── Command Complete ▼ CAPABILITIES EXCHANGED LE CS Read Remote FAE Table (optional) │ ◄── LE CS Read Remote FAE Complete │ │ LE CS Set Channel Classification (optional) │ ◄── Command Complete │ │ LE CS Create Config (config_id=0) │ ◄── Command Status │ ◄── LE CS Config Complete ▼ CONFIGURED (id=0) LE CS Set Procedure Parameters (config_id=0) │ ◄── Command Complete ▼ PARAMETERS SET (id=0) LE CS Procedure Enable (id=0, enable=1) │ ◄── Command Status │ ◄── LE CS Procedure Enable Complete (state=1)▼ PROCEDURE RUNNING (id=0) ◄── LE CS Subevent Result ┐ │ ◄── LE CS Subevent Result Cont. │(repeated) │ ◄── LE CS Subevent Result ┘ │ │ LE CS Procedure Enable (id=0, enable=0) │ ◄── Command Status │ ◄── LE CS Procedure Enable Complete (state=0)▼ CONFIGURED (id=0) LE CS Remove Config (id=0) (optional) │ ◄── Command Status ▼ IDLE Steps above the first ``LE CS Create Config`` are one-time setup per connection. The config/params/enable/disable cycle can be repeated. CS Test Mode ------------- CS Test mode allows the controller to run CS procedures without a remote device, for manufacturing and validation purposes:: < HCI Command: LE CS Test (0x08|0x0095) plen 34 #400 [hci0] Main Mode Type: 0x01 Sub Mode Type: 0xff Main Mode Repetition: 0 Mode 0 Steps: 3 Role: Initiator (0x00) RTT Type: 0x01 CS Sync PHY: LE 2M (0x01) CS Sync Antenna Selection: 0x01 Subevent Len: 5000 us Subevent Interval: 0 Max Num Subevents: 1 Transmit Power Level: 10 T_IP1 Time: 30 us T_IP2 Time: 30 us T_FCS Time: 50 us T_PM Time: 10 us T_SW Time: 0 us Tone Antenna Config Selection: 0x01 SNR Control Initiator: 0x00 SNR Control Reflector: 0x00 DRBG Nonce: 0x0000 Channel Map Repetition: 1 Override Config: 0x0000 > HCI Event: Command Complete (0x0e) plen 5 #401 [hci0] LE CS Test (0x08|0x0095) ncmd 1 Status: Success (0x00) Results arrive as normal ``LE CS Subevent Result`` events. The test is terminated with:: < HCI Command: LE CS Test End (0x08|0x0096) plen 0 #450 [hci0] > HCI Event: LE Meta Event (0x3e) plen 1 #451 [hci0] LE CS Test End Complete (0x33) Status: Success (0x00) Common Issues -------------- **Security Enable fails** The connection must be encrypted before CS Security Enable. Check that SMP pairing and LE Encrypt have completed. Also verify both devices advertise CS support in their LE feature set. **Config Complete with error** The controller may reject a configuration if the requested parameters are incompatible with the intersection of both devices' capabilities. Check that main mode, PHY, and timing parameters fall within both devices' supported ranges. **Channel map too small** CS requires at least 15 channels. If channel classification or interference eliminates too many channels, the procedure aborts with abort reason ``0x02`` (low nibble). **No Mode 0 sync received** Subevent abort reason ``0x02`` (high nibble) means the Initiator's Mode 0 sync packet was not received by the Reflector (or vice versa). This indicates an RF issue, scheduling miss, or device out of range. **Procedure aborted by host** Abort reason ``0x01`` in either nibble means the local host or remote device requested termination, typically via Procedure Enable with enable=0. **ToA/ToD not available (0x8000)** The controller could not compute a valid time measurement for this step. Common with weak signals or when the CS Access Address was not detected (packet quality = 0x02). RANGING SERVICE (RAS) / RANGING PROFILE (RAP) ============================================== The Ranging Service (RAS) is a GATT service that transfers CS measurement data between devices at the application layer. It complements the HCI-level CS procedure by providing a standardized way to exchange, acknowledge, and retrieve ranging results over ATT. The Ranging Profile (RAP) defines how a device discovers and interacts with RAS on a remote device. btmon decodes all RAS characteristics automatically when it sees ATT operations on the RAS UUIDs. RAS Service UUID: ``0x185B`` RAS Characteristics -------------------- .. list-table:: :header-rows: 1 :widths: 30 10 20 40 * - Characteristic - UUID - Properties - Description * - RAS Features - 0x2C14 - Read - Bitmask of supported RAS capabilities * - RAS Real-time Ranging Data - 0x2C15 - Notify, Indicate - Streaming ranging results as they are produced * - RAS On-demand Ranging Data - 0x2C16 - Notify, Indicate - Ranging results retrieved on request * - RAS Control Point - 0x2C17 - Write Without Response, Indicate - Command interface for data retrieval and filtering * - RAS Ranging Data Ready - 0x2C18 - Read, Notify, Indicate - Signals that a new ranging dataset is available * - RAS Ranging Data Overwritten - 0x2C19 - Read, Notify, Indicate - Signals that a stored dataset was overwritten All characteristics except RAS Features use Client Characteristic Configuration (CCC) descriptors to enable notifications/indications. The typical GATT attribute layout uses 18 handles total. RAS Features ------------- The RAS Features characteristic is a 4-byte bitmask read by the client to discover the server's capabilities:: > ACL Data RX: Handle 2048 flags 0x02 dlen 11 #200 [hci0] ATT: Read Response (0x0b) len 4 Handle: 0x0003 Features: 0x0000000f Real-time Ranging Data (0x00000001) Retrieve Lost Ranging Data Segments (0x00000002) Abort Operation (0x00000004) Filter Ranging Data (0x00000008) Feature bits: - **Bit 0** -- Real-time Ranging Data: the server can stream results via the Real-time Ranging Data characteristic as CS procedures run. - **Bit 1** -- Retrieve Lost Ranging Data Segments: the client can request retransmission of missing data segments. - **Bit 2** -- Abort Operation: the client can cancel an in-progress data transfer. - **Bit 3** -- Filter Ranging Data: the client can configure which fields are included in ranging data notifications. RAS Ranging Data Ready ----------------------- When a CS procedure completes and results are stored, the server notifies the client that data is available for retrieval:: > ACL Data RX: Handle 2048 flags 0x02 dlen 9 #250 [hci0] ATT: Handle Value Notification (0x1b) len 2 Handle: 0x000e Counter: 42 The counter value identifies the specific ranging dataset. The client uses this counter in subsequent Control Point commands to request or acknowledge the data. RAS Ranging Data Overwritten ------------------------------ If the server's buffer is full and older results are discarded, this characteristic notifies the client:: > ACL Data RX: Handle 2048 flags 0x02 dlen 9 #260 [hci0] ATT: Handle Value Notification (0x1b) len 2 Handle: 0x0011 Overwritten Count: 38 This tells the client that ranging data with counter value 38 (and possibly earlier) has been overwritten and can no longer be retrieved. RAS Control Point ------------------ The RAS Control Point is the command interface for managing ranging data transfers. The client writes commands, and the server responds via indications on the same characteristic. **Opcodes:** .. list-table:: :header-rows: 1 :widths: 10 35 55 * - Opcode - Command - Description * - 0x00 - Get Ranging Data - Request on-demand transfer of a specific dataset * - 0x01 - ACK Ranging Data - Acknowledge receipt of a dataset * - 0x02 - Retrieve Lost Ranging Data Segments - Re-request specific missing segments * - 0x03 - Abort Operation - Cancel an in-progress data transfer * - 0x04 - Set Filter - Configure which data fields are included **Get Ranging Data** -- requests transfer of a stored dataset:: < ACL Data TX: Handle 2048 flags 0x00 dlen 10 #270 [hci0] ATT: Write Command (0x52) len 3 Handle: 0x000b Opcode: Get Ranging Data (0x00) Ranging Counter: 0x002a After this command, the server sends the data as a sequence of notifications on the On-demand Ranging Data characteristic (0x2C16). **ACK Ranging Data** -- confirms the client received a complete dataset, allowing the server to free the buffer:: < ACL Data TX: Handle 2048 flags 0x00 dlen 10 #290 [hci0] ATT: Write Command (0x52) len 3 Handle: 0x000b Opcode: ACK Ranging Data (0x01) Ranging Counter: 0x002a **Retrieve Lost Ranging Data Segments** -- requests retransmission of specific segments that were missed or corrupted:: < ACL Data TX: Handle 2048 flags 0x00 dlen 12 #295 [hci0] ATT: Write Command (0x52) len 5 Handle: 0x000b Opcode: Retrieve Lost Ranging Data Segments (0x02) Ranging Counter: 0x002a First Segment Index: 3 Last Segment Index: 5 A Last Segment Index of ``0xFF`` means "all remaining segments from the first index onward." **Abort Operation** -- cancels any in-progress data transfer:: < ACL Data TX: Handle 2048 flags 0x00 dlen 8 #298 [hci0] ATT: Write Command (0x52) len 1 Handle: 0x000b Opcode: Abort Operation (0x03) **Set Filter** -- configures which fields to include in ranging data notifications:: < ACL Data TX: Handle 2048 flags 0x00 dlen 10 #265 [hci0] ATT: Write Command (0x52) len 3 Handle: 0x000b Opcode: Set Filter (0x04) Filter Configuration: 0x0001 Mode: 1 Filter Bit Mask: 0x0000 The filter configuration is a 16-bit value: bits [1:0] select the filter mode, and bits [15:2] provide a field bitmask. Ranging Data Format -------------------- Both Real-time (0x2C15) and On-demand (0x2C16) Ranging Data characteristics use the same segmented data format. Large datasets are split across multiple ATT notifications. **Segmentation Header** (1 byte, present in every notification):: > ACL Data RX: Handle 2048 flags 0x02 dlen 30 #275 [hci0] ATT: Handle Value Notification (0x1b) len 25 Handle: 0x0005 Segmentation Header: 0x01 First Segment: True Last Segment: False Segment Index: 0 Ranging Data Body: Ranging Counter: 0x02a Configuration ID: 0 Selected TX Power: 12 dBm Antenna Paths Mask: 0x03 Antenna Path 1 (0x01) Antenna Path 2 (0x02) Subevent #0: Start ACL Connection Event: 1200 Frequency Compensation: 150 (0.01 ppm) Ranging Done Status: Partial results (0x1) Subevent Done Status: All results complete (0x0) Ranging Abort Reason: No abort (0x0) Subevent Abort Reason: No abort (0x0) Reference Power Level: -20 dBm Number of Steps Reported: 8 Remaining Ranging Data Segment: ... Segmentation header fields: - **First Segment** (bit 0) -- ``True`` for the first notification of a dataset. Only the first segment contains the Ranging Header. - **Last Segment** (bit 1) -- ``True`` for the final notification. When both bits are set, the entire dataset fits in one notification. - **Segment Index** (bits [7:2]) -- Sequential index for reassembly. **Ranging Header** (4 bytes, only in first segment): - **Ranging Counter** (bits [11:0]) -- Matches the counter from the Data Ready notification; identifies the CS procedure instance. - **Configuration ID** (bits [15:12]) -- The CS configuration ID (0--3) used for this measurement. - **Selected TX Power** (1 byte, signed) -- Actual transmit power used, in dBm. - **Antenna Paths Mask** (1 byte) -- Bitmask indicating which antenna paths have data: bit 0 = path 1, bit 1 = path 2, etc. **Subevent Header** (per subevent within the ranging data): - **Start ACL Connection Event** -- The ACL connection event counter at which this CS subevent started. - **Frequency Compensation** -- Measured frequency offset in 0.01 ppm units (signed 16-bit). - **Ranging Done Status** -- Same codes as HCI subevent results: ``0x0`` = complete, ``0x1`` = partial, ``0xF`` = aborted. - **Subevent Done Status** -- ``0x0`` = complete, ``0xF`` = aborted. - **Ranging/Subevent Abort Reason** -- Same abort reason codes as HCI: ``0x0`` = no abort, ``0x1`` = host/remote request, ``0x2`` = channel map / no sync, ``0x3`` = scheduling, ``0xF`` = unspecified. - **Reference Power Level** -- RSSI reference in dBm (signed). - **Number of Steps Reported** -- Count of CS step results that follow. The step data following the subevent header uses the same mode-specific format as HCI CS Subevent Result events (Mode 0--3), described in the `CS Step Modes`_ section above. Continuation segments contain only the Segmentation Header followed by raw ranging data bytes that are reassembled with the previous segments. RAS Data Transfer State Machine --------------------------------- The RAS data transfer for a single ranging dataset transitions through these states:: ┌───────────────┐ │ IDLE │ No active data transfer. └─────┬─────────┘ │ Server: CS procedure completes (HCI level) │ Server: notifies Ranging Data Ready (counter=N) ▼ ┌───────────────┐ │ DATA READY │ Dataset N is buffered on server. │ (counter=N) │ Client has been notified. └─────┬─────────┘ │ Client writes: Get Ranging Data (counter=N) │ ── OR ── (real-time mode: automatic push) ▼ ┌───────────────┐ │ TRANSFERRING │ Server sends segmented notifications. │ (counter=N) │ Segment Index increments: 0, 1, 2, ... └─────┬──────┬──┘ │ │ Segment lost? Client writes: │ │ Retrieve Lost Segments (counter=N, first, last) │ │ Server re-sends missing segments. │ │ (loops back to TRANSFERRING) │ │ │ │ Client writes: Abort Operation │ └──────────────────────┐ │ Last Segment received │ ▼ ▼ ┌───────────────┐ ┌───────────────┐ │ COMPLETE │ │ ABORTED │ │ (counter=N) │ └───────┬───────┘ └─────┬─────────┘ │ │ Client writes: │ │ ACK Ranging Data │ │ (counter=N) │ ▼ ▼ ┌───────────────┐ │ IDLE │ Server may free buffer for counter=N. └───────────────┘ If the server's buffer fills before the client retrieves data, a Ranging Data Overwritten notification is sent and the dataset transitions directly from DATA READY to overwritten (lost). Typical RAS Data Flow ---------------------- A complete RAS session for on-demand data retrieval:: 1. Client discovers RAS (UUID 0x185B) via GATT primary service discovery 2. Client reads RAS Features (0x2C14) to learn capabilities 3. Client enables CCC notifications on: - RAS Ranging Data Ready (0x2C18) - RAS Ranging Data Overwritten (0x2C19) - RAS On-demand Ranging Data (0x2C16) or RAS Real-time Ranging Data (0x2C15) 4. CS procedure runs (HCI level) 5. Server notifies Ranging Data Ready with counter=N 6. Client writes Control Point: Get Ranging Data (counter=N) 7. Server sends On-demand Ranging Data notifications (segmented) 8. Client writes Control Point: ACK Ranging Data (counter=N) For real-time streaming, step 6 is skipped -- the server pushes data on the Real-time Ranging Data characteristic (0x2C15) as each CS subevent completes, using the same segmented format. If segments are lost (e.g., due to ATT MTU limitations or missed notifications), the client uses Retrieve Lost Ranging Data Segments to request retransmission of specific segment indices. Combined HCI + GATT Flow -------------------------- The following chart shows the full interleaved timeline of HCI CS operations and GATT RAS operations as they appear in a btmon trace. The left column is HCI traffic (``< HCI`` / ``> HCI``), the center shows GATT ATT traffic (``< ACL`` / ``> ACL``), and the right column shows the combined state:: HCI (CS) GATT (RAS) State ══════════════════════════════════════════════════════════════════════════ --- One-time connection setup --- < LE CS Read Local Supp. Cap. IDLE > Command Complete │ < LE CS Read Remote Supp. Cap. │ > Command Status │ > LE CS Read Remote Supp. │ Cap. Complete │ < LE CS Security Enable │ > Command Status │ > LE CS Security Enable │ Complete │ < LE CS Set Default Settings │ > Command Complete ▼ CAPS EXCHANGED < ATT: Find By Type Value │ (RAS UUID 0x185B) │ > ATT: Find By Type Value │ Response │ < ATT: Read Request │ (RAS Features 0x2C14) │ > ATT: Read Response │ Features: 0x0000000f │ < ATT: Write Request │ (CCC: Ready 0x2C18) │ > ATT: Write Response │ < ATT: Write Request │ (CCC: On-demand 0x2C16) │ > ATT: Write Response ▼ RAS READY --- Per-measurement cycle (repeatable) --- < LE CS Create Config (id=0) │ > Command Status │ > LE CS Config Complete ▼ CONFIGURED (id=0) < LE CS Set Proc. Params (id=0) │ > Command Complete ▼ PARAMS SET (id=0) < LE CS Proc. Enable │ (id=0, enable=1) │ > Command Status │ > LE CS Proc. Enable │ Complete (state=1) ▼ PROCEDURE RUNNING > LE CS Subevent Result ─┐ │ > LE CS Subevent Result │ (measurement data │ Continue │ streams from │ > LE CS Subevent Result │ controller) │ > LE CS Subevent Result ─┘ │ │ > LE CS Subevent Result │ (Procedure Done=0x00) ▼ PROCEDURE COMPLETE > ATT: Notification │ (Data Ready 0x2C18) │ Counter: N ▼ DATA READY (N) < ATT: Write Command │ (Control Point 0x2C17) │ Get Ranging Data │ Counter: N ▼ TRANSFERRING (N) > ATT: Notification │ (On-demand 0x2C16) │ Seg[0]: First=T Last=F │ Ranging Header + data │ > ATT: Notification │ (On-demand 0x2C16) │ Seg[1]: First=F Last=F │ > ATT: Notification │ (On-demand 0x2C16) │ Seg[2]: First=F Last=T ▼ TRANSFER COMPLETE (N) < ATT: Write Command │ (Control Point 0x2C17) │ ACK Ranging Data │ Counter: N ▼ IDLE (buffer freed) --- Cleanup (optional) --- < LE CS Remove Config (id=0) > Command Status IDLE Real-time Streaming Flow -------------------------- When the server supports real-time ranging data (Features bit 0), results are pushed immediately as CS subevents complete, without waiting for a Get Ranging Data request:: HCI (CS) GATT (RAS) State ══════════════════════════════════════════════════════════════════════════ < LE CS Proc. Enable PARAMS SET (id=0, enable=1) > LE CS Proc. Enable Complete ▼ PROCEDURE RUNNING > LE CS Subevent Result ──┐ │ > ATT: Notification │ (Real-time 0x2C15) │ Seg[0]: First=T Last=T │ Ranging Header + │ subevent data > LE CS Subevent Result ──┤ │ > ATT: Notification │ (Real-time 0x2C15) │ Seg[0]: First=T Last=F │ > ATT: Notification │ (Real-time 0x2C15) │ Seg[1]: First=F Last=T > LE CS Subevent Result ──┘ > ATT: Notification (Real-time 0x2C15) ... > LE CS Subevent Result │ (Procedure Done=0x00) ▼ PROCEDURE COMPLETE < ATT: Write Command ACK Ranging Data Counter: N ▼ IDLE In real-time mode, each HCI Subevent Result triggers one or more GATT notifications immediately. The Ranging Counter in the data header increments with each procedure. If a notification segment is lost, the client can use Retrieve Lost Segments after the procedure completes (if Features bit 1 is set). Lost Segment Recovery Flow ---------------------------- When segments are missed during transfer, the client requests retransmission:: GATT (RAS) State ══════════════════════════════════════════════════════════════ > ATT: Notification (On-demand 0x2C16) TRANSFERRING Seg[0]: First=T Last=F ── received > ATT: Notification (On-demand 0x2C16) Seg[1]: First=F Last=F ── received │ Seg[2] ── LOST (not received) │ │ > ATT: Notification (On-demand 0x2C16) │ Seg[3]: First=F Last=T ── received │ │ (Client detects gap: Seg[2] missing) ▼ INCOMPLETE < ATT: Write Command (Control Point 0x2C17) Retrieve Lost Ranging Data Segments Counter: N First Segment Index: 2 Last Segment Index: 2 ▼ RECOVERING > ATT: Notification (On-demand 0x2C16) Seg[2]: First=F Last=F ── re-sent ▼ TRANSFER COMPLETE < ATT: Write Command (Control Point 0x2C17) ACK Ranging Data Counter: N ▼ IDLE RAS Common Issues ------------------ **Features read returns 0x00000000** The server does not support any optional RAS features. Only basic on-demand data retrieval via Control Point is available. **Ranging Data Ready not received** Check that CCC notifications are enabled on handle 0x2C18. Also verify the CS procedure actually completed -- if the procedure was aborted, no Data Ready notification is sent. **Segmentation reassembly errors** Verify that Segment Index values are sequential and that the First Segment flag is set on the initial notification. A missing segment triggers the Retrieve Lost Segments mechanism if the server supports it (Features bit 1). **Data Overwritten before retrieval** The server has limited buffer space. If the client does not retrieve and ACK data promptly, older datasets are overwritten. The Data Overwritten notification indicates the counter value of the lost data. **Control Point write rejected** The connection must be encrypted. RAS Control Point requires write permission with encryption (``WRITE_ENCRYPT``). Verify that SMP pairing has completed. bluez-5.87/doc/PaxHeaders/btmon-mgmt.rst0000644000000000000000000000005015200607060015227 xustar0020 atime=1783092683 20 ctime=1783094112 bluez-5.87/doc/btmon-mgmt.rst0000644000000000000000000004501215200607060014712 0ustar00rootroot.. This file is included by btmon.rst. MGMT PROTOCOL FLOWS ===================== The Management Interface (MGMT) is the structured command/event protocol between userspace (typically ``bluetoothd``) and the kernel Bluetooth subsystem. In btmon output, MGMT traffic is prefixed with ``@`` and provides insight into adapter configuration, device discovery, pairing, and connection management at a higher level than raw HCI. This section covers common MGMT protocol flows as seen through btmon. For MGMT output format basics (line anatomy, Open/Close, parameters, events), see the "Management Traffic" subsection in READING THE OUTPUT above. Initialization and Version Handshake -------------------------------------- When ``bluetoothd`` starts, it opens a management socket and queries the kernel for protocol version and supported features. This handshake must succeed before any controller operations:: @ MGMT Open: bluetoothd (privileged) version 1.23 {0x0001} 12:34:49.881936 @ MGMT Command: Read Management Ver.. (0x0001) plen 0 {0x0001} 12:34:49.882003 @ MGMT Event: Command Complete (0x0001) plen 6 {0x0001} 12:34:49.882010 Read Management Version Information (0x0001) plen 3 Status: Success (0x00) Version: 1.23 @ MGMT Command: Read Management Sup.. (0x0002) plen 0 {0x0001} 12:34:49.882050 @ MGMT Event: Command Complete (0x0001) plen 58 {0x0001} 12:34:49.882055 Read Supported Commands (0x0002) plen 55 Status: Success (0x00) Num of commands: 120 Num of events: 38 After the version check, ``bluetoothd`` reads the controller list and queries each controller's information:: @ MGMT Command: Read Controller Index List (0x0003) plen 0 {0x0001} 12:34:49.882100 @ MGMT Event: Command Complete (0x0001) plen 7 {0x0001} 12:34:49.882105 Read Controller Index List (0x0003) plen 4 Status: Success (0x00) Num controllers: 1 Controller: hci0 @ MGMT Command: Read Controller Inf.. (0x0004) plen 0 {0x0001} [hci0] 12:34:49.882200 @ MGMT Event: Command Complete (0x0001) plen 283 {0x0001} [hci0] 12:34:49.882210 Read Controller Information (0x0004) plen 280 Status: Success (0x00) Address: 00:11:22:33:44:55 Bluetooth version: 5.4 Manufacturer: Intel (2) Supported settings: 0x003effff Current settings: 0x00000080 Key things to check in the initialization flow: - **MGMT Open** -- Confirms ``bluetoothd`` connected. If missing, the daemon did not start or crashed before reaching MGMT setup. - **Version mismatch** -- If ``bluetoothd`` expects a newer MGMT version than the kernel provides, some features may be unavailable. - **Num controllers: 0** -- No Bluetooth hardware detected. Check ``dmesg`` for driver issues. - **Current settings** -- The bitmask shows what is currently enabled on the controller (Powered, LE, BR/EDR, SSP, etc.). Adapter Configuration ---------------------- After reading controller info, ``bluetoothd`` configures the adapter with a series of MGMT commands before powering it on. The exact sequence depends on ``main.conf`` settings and adapter capabilities. **Loading stored keys**:: @ MGMT Command: Load Link Keys (0x0012) plen 3 {0x0001} [hci0] 12:34:50.001200 Debug keys: Disabled (0x00) Key count: 0 @ MGMT Event: Command Complete (0x0001) plen 4 {0x0001} [hci0] 12:34:50.001220 Load Link Keys (0x0012) plen 1 Status: Success (0x00) @ MGMT Command: Load Long Term Keys (0x0013) plen 2 {0x0001} [hci0] 12:34:50.001300 Key count: 0 @ MGMT Event: Command Complete (0x0001) plen 4 {0x0001} [hci0] 12:34:50.001315 Load Long Term Keys (0x0013) plen 1 Status: Success (0x00) @ MGMT Command: Load Identity Resolving Keys (0x0030) plen 2 {0x0001} [hci0] 12:34:50.001400 Key count: 0 @ MGMT Event: Command Complete (0x0001) plen 4 {0x0001} [hci0] 12:34:50.001415 Load Identity Resolving Keys (0x0030) plen 1 Status: Success (0x00) When bonded devices exist, the Key count will be non-zero and btmon will list each key entry. A large number of keys can indicate many paired devices; this is normal. **Setting adapter properties**:: @ MGMT Command: Set Secure Connections (0x002d) plen 1 {0x0001} [hci0] 12:34:50.002100 Secure connections: Enabled (0x01) @ MGMT Event: Command Complete (0x0001) plen 7 {0x0001} [hci0] 12:34:50.002120 Set Secure Connections (0x002d) plen 4 Status: Success (0x00) Current settings: 0x004e0a81 @ MGMT Command: Set Bondable (0x0009) plen 1 {0x0001} [hci0] 12:34:50.002200 Bondable: Enabled (0x01) @ MGMT Event: Command Complete (0x0001) plen 7 {0x0001} [hci0] 12:34:50.002220 Set Bondable (0x0009) plen 4 Status: Success (0x00) Current settings: 0x004e0a91 **Powering on**:: @ MGMT Command: Set Powered (0x0005) plen 1 {0x0001} [hci0] 12:35:04.033564 Powered: Enabled (0x01) @ MGMT Event: Command Complete (0x0001) plen 7 {0x0001} [hci0] 12:35:04.114789 Set Powered (0x0005) plen 4 Status: Success (0x00) Current settings: 0x004e0ac1 Between the ``Set Powered`` command and its response, btmon will show the HCI commands the kernel sends to initialize the radio (see `HCI INITIALIZATION SEQUENCE`_). Discovery ---------- Device discovery is initiated through MGMT rather than raw HCI commands. ``bluetoothd`` (or ``btmgmt``) sends a Start Discovery command specifying which transport types to scan. **Starting discovery**:: @ MGMT Command: Start Discovery (0x0023) plen 1 {0x0001} [hci0] 12:36:00.100200 Address type: 0x07 BR/EDR LE Public LE Random @ MGMT Event: Command Complete (0x0001) plen 5 {0x0001} [hci0] 12:36:00.100500 Start Discovery (0x0023) plen 2 Status: Success (0x00) Address type: 0x07 After this, btmon shows the HCI-level scan commands the kernel issues (``LE Set Scan Parameters``, ``LE Set Scan Enable``, and/or ``Inquiry`` for BR/EDR). Discovered devices appear as MGMT events:: @ MGMT Event: Device Found (0x0012) plen 38 {0x0001} [hci0] 12:36:00.250003 LE Address: AA:BB:CC:DD:EE:FF (Random) RSSI: -62 Flags: 0x0000 EIR Data: Name (complete): My Device TX power: 0 **Service discovery** uses a filtered variant:: @ MGMT Command: Start Service Discovery (0x003a) plen 19 {0x0001} [hci0] 12:36:10.100200 Address type: 0x06 LE Public LE Random RSSI threshold: -127 UUIDs: 1 UUID: Heart Rate (0x180d) @ MGMT Event: Command Complete (0x0001) plen 5 {0x0001} [hci0] 12:36:10.100500 Start Service Discovery (0x003a) plen 2 Status: Success (0x00) **Stopping discovery**:: @ MGMT Command: Stop Discovery (0x0024) plen 1 {0x0001} [hci0] 12:36:15.200100 Address type: 0x07 @ MGMT Event: Command Complete (0x0001) plen 5 {0x0001} [hci0] 12:36:15.200400 Stop Discovery (0x0024) plen 2 Status: Success (0x00) @ MGMT Event: Discovering (0x0013) plen 2 {0x0001} [hci0] 12:36:15.200500 Address type: 0x07 Discovery: Disabled (0x00) Discovery problems to look for: - **Status: Busy (0x0a)** on Start Discovery -- Another discovery session is already active. - **Status: Not Powered (0x0f)** -- The adapter is not powered on. - **Status: RFKilled (0x12)** -- The radio is blocked by rfkill. - **No Device Found events** -- The remote device may not be advertising, or address type filtering may be wrong. Pairing and Bonding via MGMT ------------------------------ MGMT provides a high-level pairing interface. The host sends a ``Pair Device`` command; the kernel coordinates SMP or SSP as appropriate and reports the result. **Initiating pairing**:: @ MGMT Command: Pair Device (0x0019) plen 8 {0x0001} [hci0] 12:37:00.500200 LE Address: AA:BB:CC:DD:EE:FF (Random) Capability: KeyboardDisplay (0x04) After this command, btmon shows the underlying SMP or SSP exchange (see `SMP PAIRING FLOW`_ and `HCI INITIALIZATION SEQUENCE`_ for details). User interaction events may appear:: @ MGMT Event: User Confirmation Request (0x000f) plen 12 {0x0001} [hci0] 12:37:01.200100 LE Address: AA:BB:CC:DD:EE:FF (Random) Value: 123456 @ MGMT Command: User Confirmation Reply (0x001e) plen 6 {0x0001} [hci0] 12:37:03.800200 LE Address: AA:BB:CC:DD:EE:FF (Random) @ MGMT Event: Command Complete (0x0001) plen 10 {0x0001} [hci0] 12:37:03.800350 User Confirmation Reply (0x001e) plen 7 Status: Success (0x00) Or for passkey entry:: @ MGMT Event: User Passkey Request (0x0010) plen 6 {0x0001} [hci0] 12:37:01.200100 LE Address: AA:BB:CC:DD:EE:FF (Random) @ MGMT Command: User Passkey Reply (0x0020) plen 10 {0x0001} [hci0] 12:37:05.100200 LE Address: AA:BB:CC:DD:EE:FF (Random) Passkey: 123456 **Pairing success**:: @ MGMT Event: New Long Term Key (0x000a) plen 37 {0x0001} [hci0] 12:37:06.100200 Store hint: Yes (0x01) LE Address: AA:BB:CC:DD:EE:FF (Random) Key type: Authenticated P-256 (0x03) Central: 0x00 Encryption size: 16 @ MGMT Event: New Identity Resolving Key (0x0018) plen 30 {0x0001} [hci0] 12:37:06.100300 Store hint: Yes (0x01) Random address: AA:BB:CC:DD:EE:FF LE Address: 11:22:33:44:55:66 (Public) Key: 00112233445566778899aabbccddeeff @ MGMT Event: Command Complete (0x0001) plen 10 {0x0001} [hci0] 12:37:06.100500 Pair Device (0x0019) plen 7 Status: Success (0x00) LE Address: AA:BB:CC:DD:EE:FF (Random) Key events to check after pairing: - **New Long Term Key** with ``Store hint: Yes`` -- The kernel is telling userspace to persist this key for future reconnections. - **New Identity Resolving Key** -- Reveals the device's real address behind an RPA. This is essential for address resolution on reconnect. - **Key type** -- ``Authenticated P-256`` indicates Secure Connections with MITM protection. ``Unauthenticated P-256`` means Just Works SC. ``Authenticated`` (no P-256) means Legacy with MITM. **Pairing failure**:: @ MGMT Event: Command Complete (0x0001) plen 10 {0x0001} [hci0] 12:37:06.100500 Pair Device (0x0019) plen 7 Status: Authentication Failed (0x05) LE Address: AA:BB:CC:DD:EE:FF (Random) MGMT pairing error codes: .. list-table:: :header-rows: 1 :widths: 8 30 62 * - Code - Status - Diagnostic Meaning * - 0x03 - Failed - Generic failure; check SMP or HCI errors preceding this * - 0x04 - Connect Failed - Could not establish connection before pairing * - 0x05 - Authentication Failed - SMP or SSP authentication did not succeed; check SMP Pairing Failed reason or HCI Authentication Failure event * - 0x08 - Timeout - Pairing timed out; remote device may have gone out of range * - 0x0b - Rejected - Remote device rejected the pairing request * - 0x0d - Invalid Parameters - Bad address type or capability value in the Pair Device command Device Connection and Disconnection -------------------------------------- MGMT reports connection lifecycle events that complement the HCI-level events shown elsewhere in the trace. **Device connected**:: @ MGMT Event: Device Connected (0x000b) plen 13 {0x0001} [hci0] 12:36:18.974319 LE Address: AA:BB:CC:DD:EE:FF (Random) Flags: 0x0000 EIR Data Length: 0 This event follows the HCI ``LE (Enhanced) Connection Complete`` event. It tells userspace that a new connection is ready. **Device disconnected**:: @ MGMT Event: Device Disconnected (0x000c) plen 8 {0x0001} [hci0] 12:38:20.500200 LE Address: AA:BB:CC:DD:EE:FF (Random) Reason: Connection timeout (0x01) Disconnect reasons reported by MGMT: .. list-table:: :header-rows: 1 :widths: 8 30 62 * - Code - Reason - Diagnostic Meaning * - 0x00 - Unspecified - No reason provided; typically a local disconnect * - 0x01 - Connection timeout - Link supervision timeout expired; device may be out of range * - 0x02 - Connection terminated by local host - Local side initiated the disconnection * - 0x03 - Connection terminated by remote host - Remote device initiated the disconnection **Unpair Device** removes stored keys and optionally disconnects:: @ MGMT Command: Unpair Device (0x001a) plen 7 {0x0001} [hci0] 12:39:00.100200 LE Address: AA:BB:CC:DD:EE:FF (Random) Disconnect: Enabled (0x01) @ MGMT Event: Command Complete (0x0001) plen 10 {0x0001} [hci0] 12:39:00.100500 Unpair Device (0x001a) plen 7 Status: Success (0x00) Advertising via MGMT --------------------- Modern BlueZ configures advertising through MGMT commands rather than sending HCI LE advertising commands directly. This provides a multi-client advertising infrastructure. **Adding an advertisement**:: @ MGMT Command: Add Advertising (0x003e) plen 22 {0x0001} [hci0] 12:40:00.100200 Instance: 1 Flags: 0x0006 The connectable flag will be managed The limited discoverable flag will be managed Duration: 0 Timeout: 0 Advertising data length: 6 Scan response length: 0 @ MGMT Event: Command Complete (0x0001) plen 5 {0x0001} [hci0] 12:40:00.100500 Add Advertising (0x003e) plen 2 Status: Success (0x00) Instance: 1 **Removing an advertisement**:: @ MGMT Command: Remove Advertising (0x003f) plen 1 {0x0001} [hci0] 12:41:00.100200 Instance: 1 @ MGMT Event: Command Complete (0x0001) plen 5 {0x0001} [hci0] 12:41:00.100500 Remove Advertising (0x003f) plen 2 Status: Success (0x00) Instance: 1 For the HCI-level advertising commands that result from these MGMT operations, see `ADVERTISING AND SCANNING`_. Error Diagnosis ---------------- MGMT Command Status and Command Complete events carry error codes that are distinct from HCI error codes. When diagnosing failures, check both layers. **MGMT error in Command Complete**:: @ MGMT Event: Command Complete (0x0001) plen 4 {0x0001} [hci0] 12:42:00.100200 Set Powered (0x0005) plen 1 Status: RFKilled (0x12) **MGMT Command Status** (asynchronous command accepted or rejected):: @ MGMT Event: Command Status (0x0002) plen 3 {0x0001} [hci0] 12:42:01.100200 Start Discovery (0x0023) plen 0 Status: Busy (0x0a) MGMT error codes: .. list-table:: :header-rows: 1 :widths: 8 30 62 * - Code - Status - Diagnostic Meaning * - 0x00 - Success - Command completed successfully * - 0x01 - Unknown Command - Kernel does not recognize the command; MGMT version may be too old * - 0x02 - Not Connected - Operation requires an active connection that does not exist * - 0x03 - Failed - Generic failure; check HCI events for root cause * - 0x04 - Connect Failed - Connection attempt failed at HCI level * - 0x05 - Authentication Failed - Pairing or authentication procedure failed * - 0x06 - Not Paired - Operation requires an existing bond; device not paired * - 0x07 - No Resources - Kernel ran out of resources (memory, handles, etc.) * - 0x08 - Timeout - Operation timed out * - 0x09 - Already Connected - Connection already exists to target device * - 0x0a - Busy - Another operation is in progress (e.g., discovery running) * - 0x0b - Rejected - Operation rejected by remote device or policy * - 0x0c - Not Supported - Feature not supported by this controller or kernel version * - 0x0d - Invalid Parameters - Bad parameters in the command * - 0x0e - Disconnected - Connection was lost during the operation * - 0x0f - Not Powered - Controller is not powered on; call Set Powered first * - 0x10 - Cancelled - Operation was cancelled by the user or another command * - 0x11 - Invalid Index - Controller index does not exist * - 0x12 - RFKilled - Radio is disabled by rfkill; unblock with ``rfkill unblock bluetooth`` * - 0x13 - Already Paired - Device is already paired; unpair first if re-pairing is needed * - 0x14 - Permission Denied - Process lacks required privileges (CAP_NET_ADMIN) **Correlating MGMT and HCI errors**: When MGMT reports ``Failed`` (0x03) or ``Authentication Failed`` (0x05), look at the HCI events immediately preceding the MGMT response. The HCI-level error (e.g., ``Authentication Failure (0x05)``, ``Connection Timeout (0x08)``) provides the specific hardware-level cause. Automating MGMT Analysis -------------------------- **List all MGMT commands and events**:: grep -n "@ MGMT" output.txt **Find MGMT errors (non-zero status)**:: grep -n "@ MGMT" output.txt | grep -v "Status: Success" **Track adapter power state changes**:: grep -n "Set Powered\|RFKilled\|Not Powered" output.txt **Find all discovery activity**:: grep -n "Start Discovery\|Stop Discovery\|Device Found\|Discovering" output.txt **Find pairing activity via MGMT**:: grep -n "Pair Device\|User Confirmation\|User Passkey\|New Long Term Key\|New Identity Resolving Key\|Authentication Failed" output.txt **Find connection lifecycle events**:: grep -n "Device Connected\|Device Disconnected\|Unpair Device" output.txt **Identify which MGMT clients are active**:: grep -n "@ MGMT Open\|@ MGMT Close" output.txt **Full MGMT diagnosis pattern**: 1. Find ``MGMT Open`` -- confirm ``bluetoothd`` connected and note socket ID 2. Check ``Read Controller Information`` -- verify address, version, and current settings 3. Find ``Set Powered`` -- confirm the adapter was powered on successfully 4. Search for non-Success status codes -- these indicate failures 5. For pairing issues, find ``Pair Device`` and trace forward to its ``Command Complete`` response 6. For connection issues, find ``Device Connected``/``Device Disconnected`` and check the disconnect reason bluez-5.87/doc/PaxHeaders/sco-protocol.rst0000644000000000000000000000005015142177620015602 xustar0020 atime=1783093931 20 ctime=1783094111 bluez-5.87/doc/sco-protocol.rst0000644000000000000000000002634715142177620015277 0ustar00rootroot=== sco === ------------- SCO transport ------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: March 2025 :Manual section: 7 :Manual group: Linux System Administration SYNOPSIS ======== .. code-block:: #include #include #include sco_socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); DESCRIPTION =========== The SCO logical transport, is a symmetric, point-to-point transport between the Central and a specific Peripheral. The SCO logical transport reserves slots and can therefore be considered as a circuit-switched connection between the Central and the Peripheral. In addition to the reserved slots, when eSCO is supported, a retransmission window follows immediately after. Together, the reserved slots and the retransmission window form the complete eSCO window. SOCKET ADDRESS ============== .. code-block:: struct sockaddr_sco { sa_family_t sco_family; bdaddr_t sco_bdaddr; }; Example: .. code-block:: struct sockaddr_sco addr; memset(&addr, 0, sizeof(addr)); addr.sco_family = AF_BLUETOOTH; bacpy(&addr.sco_bdaddr, bdaddr); SOCKET OPTIONS (SOL_BLUETOOTH) ============================== The socket options listed below can be set by using **setsockopt(2)** and read with **getsockopt(2)** with the socket level set to SOL_BLUETOOTH. BT_SECURITY (since Linux 2.6.30) -------------------------------- Channel security level, possible values: .. csv-table:: :header: "Value", "Security Level", "Link Key Type", "Encryption" :widths: auto **BT_SECURITY_SDP**, 0 (SDP Only), None, Not required **BT_SECURITY_LOW**, 1 (Low), Unauthenticated, Not required **BT_SECURITY_MEDIUM**, 2 (Medium - default), Unauthenticated, Desired **BT_SECURITY_HIGH**, 3 (High), Authenticated, Required **BT_SECURITY_FIPS** (since Linux 3.15), 4 (Secure Only), Authenticated (P-256 based Secure Simple Pairing and Secure Authentication), Required Example: .. code-block:: int level = BT_SECURITY_HIGH; int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_SECURITY, &level, sizeof(level)); if (err == -1) { perror("setsockopt"); return 1; } BT_DEFER_SETUP (since Linux 2.6.30) ----------------------------------- Channel defer connection setup, this control if the connection procedure needs to be authorized by userspace before responding which allows authorization at profile level, possible values: .. csv-table:: :header: "Value", "Description", "Authorization" :widths: auto **0**, Disable (default), Not required **1**, Enable, Required Example: .. code-block:: int defer_setup = 1; int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_DEFER_SETUP, &defer_setup, sizeof(defer_setup)); if (err == -1) { perror("setsockopt"); return err; } err = listen(sco_socket, 5); if (err) { perror("listen"); return err; } struct sockaddr_sco remote_addr = {0}; socklen_t addr_len = sizeof(remote_addr); int new_socket = accept(sco_socket, (struct sockaddr*)&remote_addr, &addr_len); if (new_socket < 0) { perror("accept"); return new_socket; } /* To complete the connection setup of new_socket read 1 byte */ char c; struct pollfd pfd; memset(&pfd, 0, sizeof(pfd)); pfd.fd = new_socket; pfd.events = POLLOUT; err = poll(&pfd, 1, 0); if (err) { perror("poll"); return err; } if (!(pfd.revents & POLLOUT)) { err = read(sk, &c, 1); if (err < 0) { perror("read"); return err; } } BT_VOICE (since Linux 3.11) ----------------------------- Transport voice settings, possible values: .. code-block:: struct bt_voice { uint16_t setting; }; .. csv-table:: :header: "Define", "Value", "Description" :widths: auto **BT_VOICE_TRANSPARENT**, 0x0003, Transparent output **BT_VOICE_CVSD_16BIT**, 0x0060, C-VSD output PCM 16-bit input **BT_VOICE_TRANSPARENT_16BIT**, 0x0063, Transparent output PCM 16-bit input Example: .. code-block:: struct bt_voice voice; memset(&voice, 0, sizeof(voice)); voice.setting = BT_VOICE_TRANSPARENT; int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_VOICE, &voice, sizeof(voice)); if (err == -1) { perror("setsockopt"); return 1; } BT_PKT_STATUS (since Linux 5.9) ------------------------------- Enable reporting packet status via `BT_SCM_PKT_STATUS` CMSG on received packets. Possible values: .. csv-table:: :header: "Value", "Description" :widths: auto **0**, Disable (default) **1**, Enable :BT_SCM_PKT_STATUS: Level ``SOL_BLUETOOTH`` CMSG with data:: uint8_t pkt_status; The values are equal to the "Packet_Status_Flag" defined in Core Specification v6.0 Sec. 5.4.3 pp. 1877: .. csv-table:: :header: "pkt_status", "Description" :widths: auto **0x0**, Correctly received data **0x1**, Possibly invalid data **0x2**, No data received **0x3**, Data partially lost BT_PHY (since Linux 5.10) ------------------------- Transport supported PHY(s), read-only (no setsockopt support). Possible values: .. csv-table:: :header: "Define", "Value", "Description" :widths: auto **BT_PHY_BR_1M_1SLOT**, BIT 0, BR 1Mbps 1SLOT **BT_PHY_BR_1M_3SLOT**, BIT 1, BR 1Mbps 3SLOT **BT_PHY_BR_2M_1SLOT**, BIT 3, EDR 2Mbps 1SLOT **BT_PHY_BR_2M_3SLOT**, BIT 4, EDR 2Mbps 3SLOT **BT_PHY_BR_3M_1SLOT**, BIT 6, EDR 3Mbps 1SLOT **BT_PHY_BR_3M_3SLOT**, BIT 7, EDR 3Mbps 3SLOT BT_CODEC (since Linux 5.14) --------------------------- Transport codec offload, possible values: .. code-block:: struct bt_codec { uint8_t id; uint16_t cid; uint16_t vid; uint8_t data_path_id; uint8_t num_caps; struct codec_caps { uint8_t len; uint8_t data[]; } caps[]; } __attribute__((packed)); struct bt_codecs { uint8_t num_codecs; struct bt_codec codecs[]; } __attribute__((packed)); Example: .. code-block:: char buffer[sizeof(struct bt_codecs) + sizeof(struct bt_codec)]; struct bt_codec *codecs = (void *)buffer; memset(codecs, 0, sizeof(codecs)); codec->num_codecs = 1; codecs->codecs[0].id = 0x05; codecs->codecs[0].data_path_id = 1; int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_CODEC, codecs, sizeof(buffer)); if (err == -1) { perror("setsockopt"); return 1; } SOCKET OPTIONS (SOL_SOCKET) =========================== ``SOL_SOCKET`` level socket options that modify generic socket features (``SO_SNDBUF``, ``SO_RCVBUF``, etc.) have their usual meaning, see **socket(7)**. The ``SOL_SOCKET`` level SCO socket options that have Bluetooth-specific handling in kernel are listed below. SO_TIMESTAMPING, SO_TIMESTAMP, SO_TIMESTAMPNS --------------------------------------------- See https://docs.kernel.org/networking/timestamping.html For SCO sockets, software RX timestamps are supported. Software TX timestamps (SOF_TIMESTAMPING_TX_SOFTWARE) are supported since Linux 6.15. The software RX timestamp is the time when the kernel received the packet from the controller driver. The ``SCM_TSTAMP_SND`` timestamp is emitted when packet is sent to the controller driver. The ``SCM_TSTAMP_COMPLETION`` timestamp is emitted when controller reports the packet completed. Completion timestamps are only supported on controllers that have SCO flow control. Other TX timestamp types are not supported. You can use ``SIOCETHTOOL`` to query supported flags. The timestamps are in ``CLOCK_REALTIME`` time. Example (Enable RX timestamping): .. code-block:: int flags = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE; setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)); Example (Read packet and its RX timestamp): .. code-block:: char data_buf[256]; union { char buf[CMSG_SPACE(sizeof(struct scm_timestamping))]; struct cmsghdr align; } control; struct iovec data = { .iov_base = data_buf, .iov_len = sizeof(data_buf), }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct scm_timestamping tss; res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT); if (res < 0) goto error; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); } tstamp_clock_realtime = tss.ts[0]; Example (Enable TX timestamping): .. code-block:: int flags = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_OPT_ID; setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)); Example (Read TX timestamps): .. code-block:: union { char buf[CMSG_SPACE(sizeof(struct scm_timestamping))]; struct cmsghdr align; } control; struct iovec data = { .iov_base = NULL, .iov_len = 0 }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct cmsghdr *cmsg; struct scm_timestamping tss; struct sock_extended_err serr; int res; res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT); if (res < 0) goto error; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); else if (cmsg->cmsg_level == SOL_BLUETOOTH && cmsg->cmsg_type == BT_SCM_ERROR) memcpy(&serr, CMSG_DATA(cmsg), sizeof(serr)); } tstamp_clock_realtime = tss.ts[0]; tstamp_type = serr->ee_info; /* SCM_TSTAMP_SND or SCM_TSTAMP_COMPLETION */ tstamp_seqnum = serr->ee_data; IOCTLS ====== The following ioctls with operation specific for SCO sockets are available. SIOCETHTOOL (since Linux 6.16-rc1) ---------------------------------- Supports only command `ETHTOOL_GET_TS_INFO`, which may be used to query supported `SOF_TIMESTAMPING_*` flags. The `SOF_TIMESTAMPING_OPT_*` flags are always available as applicable. Example: .. code-block:: #include #include #include #include #include ... struct ifreq ifr = {}; struct ethtool_ts_info cmd = {}; int sk; snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "hci0"); ifr.ifr_data = (void *)&cmd; cmd.cmd = ETHTOOL_GET_TS_INFO; sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); if (sk < 0) goto error; if (ioctl(sk, SIOCETHTOOL, &ifr)) goto error; sof_available = cmd.so_timestamping; RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org SEE ALSO ======== socket(7), scotest(1) bluez-5.87/doc/PaxHeaders/org.bluez.obex.Client.50000644000000000000000000000005015221755364016600 xustar0020 atime=1783094004 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.Client.50000644000000000000000000000415315221755364016264 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.obex.Client" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.obex.Client \- BlueZ D-Bus OBEX Client API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez.obex .TP .B Interface org.bluez.obex.Client1 .TP .B Object path /org/bluez/obex .UNINDENT .SS Methods .SS object CreateSession(string destination, dict args) .sp Connects to the destination address and then proceed to create an OBEX session object which implements \fBorg.bluez.obex.Session(5)\fP interface. .sp The last parameter is a dictionary to hold optional or type\-specific parameters. .sp Possible args values: .INDENT 0.0 .TP .B string Target Type of session to be created. .sp Possible values: .INDENT 7.0 .TP .B \(dqftp\(dq .TP .B \(dqmap\(dq .TP .B \(dqopp\(dq .TP .B \(dqpbap\(dq .TP .B \(dqsync\(dq .TP .B \(dqbip\-avrcp\(dq .UNINDENT .TP .B string Source Local address to be used. .TP .B byte Channel Channel to be used. .TP .B uint16 PSM L2CAP PSM to be used. .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS void RemoveSession(object session) .sp Disconnects and removes session previously created by \fBCreateSession()\fP aborting any pending transfers. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.NotAuthorized .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.MediaControl.50000644000000000000000000000005015221755325017003 xustar0020 atime=1783093973 20 ctime=1783094112 bluez-5.87/doc/org.bluez.MediaControl.50000644000000000000000000000352415221755325016470 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.MediaControl" "5" "September 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.MediaControl \- BlueZ D-Bus MediaControl API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.MediaControl1 .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} .UNINDENT .SS Methods .SS void Play() [Deprecated] .sp Resume playback. .SS void Pause() [Deprecated] .sp Pause playback. .SS void Stop() [Deprecated] .sp Stop playback. .SS void Next() [Deprecated] .sp Next item. .SS void Previous() [Deprecated] .sp Previous item. .SS void VolumeUp() [Deprecated] .sp Adjust remote volume one step up .SS void VolumeDown() [Deprecated] .sp Adjust remote volume one step down .SS void FastForward() [Deprecated] .sp Fast forward playback, this action is only stopped when another method in this interface is called. .SS void Rewind() [Deprecated] .sp Rewind playback, this action is only stopped when another method in this interface is called. .SS Properties .SS boolean Connected [readonly] .SS object Player [readonly, optional] .sp Addressed Player object path. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.Telephony.rst0000644000000000000000000000005015142177620017034 xustar0020 atime=1783094026 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Telephony.rst0000644000000000000000000001355615142177620016527 0ustar00rootroot==================== org.bluez.Telephony1 ==================== ----------------------------------------------------- BlueZ D-Bus Telephony Audio Gateway API documentation ----------------------------------------------------- :Version: BlueZ :Date: May 2025 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.Telephony1 [experimental] :Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/telephony# :Used by: **bluetoothctl-telephony(1)** Methods ------- object Dial(string uri) `````````````````````````` The uri is comprised of the URI scheme followed by the Caller ID (this could be a telephone number or username), separated by a colon. Examples of common URI schemes can be found in Internet Assigned Numbers Authority (IANA) URI Schemes: https://iana.org/assignments/uri-schemes/uri-schemes.xhtml This initiates a new outgoing call. Returns the object path to the newly created call. For HFP the URI is "tel:" followed by the telephone number. The telephone number must be a string containing the following characters: `[0-9+*#,ABCD]{1,80}` The character set can contain numbers, `+`, `*`, `#`, `,` and the letters `A` to `D`. Besides this sanity checking no further number validation is performed. It is assumed that the gateway and/or the network will perform further validation. If telephone number is an empty string, it will try to call last dialed number. NOTE: If an active call (single or multiparty) exists, then it is automatically put on hold if the dial procedure is successful. Possible Errors: :org.bluez.Error.InvalidState: :org.bluez.Error.InvalidArguments: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: Examples: :bluetoothctl: > telephony.dial [telephony] void SwapCalls() ```````````````` Swaps Active and Held calls. The effect of this is that all calls (0 or more including calls in a multi-party conversation) that were Active are now Held, and all calls (0 or more) that were Held are now Active. GSM specification does not allow calls to be swapped in the case where Held, Active and Waiting calls exist. Some modems implement this anyway, thus it is manufacturer specific whether this method will succeed in the case of Held, Active and Waiting calls. Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed void ReleaseAndAnswer() ``````````````````````` Releases currently active call (0 or more) and answers the currently waiting call. Please note that if the current call is a multiparty call, then all parties in the multi-party call will be released. Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed void ReleaseAndSwap() ````````````````````` Releases currently active call (0 or more) and activates any currently held calls. Please note that if the current call is a multiparty call, then all parties in the multi-party call will be released. Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed void HoldAndAnswer() ```````````````````` Puts the current call (including multi-party calls) on hold and answers the currently waiting call. Calling this function when a user already has a both Active and Held calls is invalid, since in GSM a user can have only a single Held call at a time. Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed void HangupAll() ```````````````` Releases all calls except waiting calls. This includes multiparty calls. Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed Examples: :bluetoothctl: > telephony.hangup-all [telephony] void HangupActive() ``````````````````` Releases active calls. This includes multiparty active calls. Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed void HangupHeld() ````````````````` Releases held calls except waiting calls. This includes multiparty held calls. Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed array{object} CreateMultiparty() ```````````````````````````````` Joins active and held calls together into a multi-party call. If one of the calls is already a multi-party call, then the other call is added to the multiparty conversation. Returns the new list of calls participating in the multiparty call. There can only be one subscriber controlled multi-party call according to the GSM specification. Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed void SendTones(string tones) ```````````````````````````` Sends the DTMF tones to the network. The tones have a fixed duration. Tones can be one of: '0' - '9', '*', '#', 'A', 'B', 'C', 'D'. The last four are typically not used in normal circumstances. Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.InvalidArgs :org.bluez.Error.Failed Properties ---------- string UUID [readonly] `````````````````````` UUID of the profile which the Telephony Audio Gateway is for. array{string} SupportedURISchemes [readonly] ```````````````````````````````````````````` Contains the list of supported URI schemes. string State [readonly] ``````````````````````` Contains the state of the current connection. Possible values: :"connecting": RFComm connection in progress :"slc_connecting": Service Level Connection in progress :"connected": RFComm and Service Level Connection are connected :"disconnecting": No further use of this object is allowed, it will be destroyed shortly boolean Service [readonly] `````````````````````````` Network service availability. byte Signal [readonly] `````````````````````` Network level signal from 0 to 5. boolean Roaming [readonly] `````````````````````````` Network roaming usage. byte BattChg [readonly] ``````````````````````` Battery level from 0 to 5. string OperatorName [readonly, optional] ```````````````````````````````````````` Operator name boolean InbandRingtone [readonly] ````````````````````````````````` In-band Ringtone availability. bluez-5.87/doc/PaxHeaders/ciptool.10000644000000000000000000000005015221755513014161 xustar0020 atime=1783094091 20 ctime=1783094113 bluez-5.87/doc/ciptool.10000644000000000000000000000466215221755513013652 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "ciptool" "1" "June 3, 2003" "BlueZ" "Linux System Administration" .SH Name ciptool \- Bluetooth Common ISDN Access Profile (CIP) .SH SYNOPSIS .sp \fBciptool\fP [\fIOPTIONS\fP] \fICOMMANDS\fP .SH DESCRIPTION .sp \fBciptool(1)\fP is used to set up, maintain, and inspect the CIP configuration of the Bluetooth subsystem in the Linux kernel. .SH OPTIONS .INDENT 0.0 .TP .BI \-i \ The command is applied to device \fIhciX\fP , which must be the name or the address of an installed Bluetooth device. .sp If not specified, the command will be use the first available Bluetooth device. .TP .B \-h\fP,\fB \-\-help Show help options .UNINDENT .SH COMMANDS .INDENT 0.0 .TP .B show Display information about the connected devices. .TP .B search Search for Bluetooth devices and connect to first one that offers CIP support. .TP .B connect <\fIBDADDR\fP> [\fIPSM\fP] Connect the local device to the remote Bluetooth device on the specified \fIPSM\fP number. If no \fIPSM\fP is specified, it will use the SDP to retrieve it from the remote device. .TP .B release [\fIBDADDR\fP] Release a connection to the specific device. If no \fIBDADDR\fP is given and only one device is connected this will be released. .TP .B loopback <\fIBDADDR\fP> [\fIPSM\fP] Create a connection to the remote device for Bluetooth testing. This command will not provide a CAPI controller, because it is only for testing the CAPI Message Transport Protocol. .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Author Marcel Holtmann .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/sco.70000644000000000000000000000005015221755254013304 xustar0020 atime=1783093932 20 ctime=1783094112 bluez-5.87/doc/sco.70000644000000000000000000003003715221755254012770 0ustar00rootroot'\" t .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "sco" "7" "March 2025" "BlueZ" "Linux System Administration" .SH Name sco \- SCO transport .SH SYNOPSIS .INDENT 0.0 .INDENT 3.5 .sp .EX #include #include #include sco_socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); .EE .UNINDENT .UNINDENT .SH DESCRIPTION .sp The SCO logical transport, is a symmetric, point\-to\-point transport between the Central and a specific Peripheral. The SCO logical transport reserves slots and can therefore be considered as a circuit\-switched connection between the Central and the Peripheral. .sp In addition to the reserved slots, when eSCO is supported, a retransmission window follows immediately after. Together, the reserved slots and the retransmission window form the complete eSCO window. .SH SOCKET ADDRESS .INDENT 0.0 .INDENT 3.5 .sp .EX struct sockaddr_sco { sa_family_t sco_family; bdaddr_t sco_bdaddr; }; .EE .UNINDENT .UNINDENT .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX struct sockaddr_sco addr; memset(&addr, 0, sizeof(addr)); addr.sco_family = AF_BLUETOOTH; bacpy(&addr.sco_bdaddr, bdaddr); .EE .UNINDENT .UNINDENT .SH SOCKET OPTIONS (SOL_BLUETOOTH) .sp The socket options listed below can be set by using \fBsetsockopt(2)\fP and read with \fBgetsockopt(2)\fP with the socket level set to SOL_BLUETOOTH. .SS BT_SECURITY (since Linux 2.6.30) .sp Channel security level, possible values: .TS box center; l|l|l|l. T{ Value T} T{ Security Level T} T{ Link Key Type T} T{ Encryption T} _ T{ \fBBT_SECURITY_SDP\fP T} T{ 0 (SDP Only) T} T{ None T} T{ Not required T} _ T{ \fBBT_SECURITY_LOW\fP T} T{ 1 (Low) T} T{ Unauthenticated T} T{ Not required T} _ T{ \fBBT_SECURITY_MEDIUM\fP T} T{ 2 (Medium \- default) T} T{ Unauthenticated T} T{ Desired T} _ T{ \fBBT_SECURITY_HIGH\fP T} T{ 3 (High) T} T{ Authenticated T} T{ Required T} _ T{ \fBBT_SECURITY_FIPS\fP (since Linux 3.15) T} T{ 4 (Secure Only) T} T{ Authenticated (P\-256 based Secure Simple Pairing and Secure Authentication) T} T{ Required T} .TE .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX int level = BT_SECURITY_HIGH; int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_SECURITY, &level, sizeof(level)); if (err == \-1) { perror(\(dqsetsockopt\(dq); return 1; } .EE .UNINDENT .UNINDENT .SS BT_DEFER_SETUP (since Linux 2.6.30) .sp Channel defer connection setup, this control if the connection procedure needs to be authorized by userspace before responding which allows authorization at profile level, possible values: .TS box center; l|l|l. T{ Value T} T{ Description T} T{ Authorization T} _ T{ \fB0\fP T} T{ Disable (default) T} T{ Not required T} _ T{ \fB1\fP T} T{ Enable T} T{ Required T} .TE .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX int defer_setup = 1; int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_DEFER_SETUP, &defer_setup, sizeof(defer_setup)); if (err == \-1) { perror(\(dqsetsockopt\(dq); return err; } err = listen(sco_socket, 5); if (err) { perror(\(dqlisten\(dq); return err; } struct sockaddr_sco remote_addr = {0}; socklen_t addr_len = sizeof(remote_addr); int new_socket = accept(sco_socket, (struct sockaddr*)&remote_addr, &addr_len); if (new_socket < 0) { perror(\(dqaccept\(dq); return new_socket; } /* To complete the connection setup of new_socket read 1 byte */ char c; struct pollfd pfd; memset(&pfd, 0, sizeof(pfd)); pfd.fd = new_socket; pfd.events = POLLOUT; err = poll(&pfd, 1, 0); if (err) { perror(\(dqpoll\(dq); return err; } if (!(pfd.revents & POLLOUT)) { err = read(sk, &c, 1); if (err < 0) { perror(\(dqread\(dq); return err; } } .EE .UNINDENT .UNINDENT .SS BT_VOICE (since Linux 3.11) .sp Transport voice settings, possible values: .INDENT 0.0 .INDENT 3.5 .sp .EX struct bt_voice { uint16_t setting; }; .EE .UNINDENT .UNINDENT .TS box center; l|l|l. T{ Define T} T{ Value T} T{ Description T} _ T{ \fBBT_VOICE_TRANSPARENT\fP T} T{ 0x0003 T} T{ Transparent output T} _ T{ \fBBT_VOICE_CVSD_16BIT\fP T} T{ 0x0060 T} T{ C\-VSD output PCM 16\-bit input T} _ T{ \fBBT_VOICE_TRANSPARENT_16BIT\fP T} T{ 0x0063 T} T{ Transparent output PCM 16\-bit input T} .TE .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX struct bt_voice voice; memset(&voice, 0, sizeof(voice)); voice.setting = BT_VOICE_TRANSPARENT; int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_VOICE, &voice, sizeof(voice)); if (err == \-1) { perror(\(dqsetsockopt\(dq); return 1; } .EE .UNINDENT .UNINDENT .SS BT_PKT_STATUS (since Linux 5.9) .sp Enable reporting packet status via \fIBT_SCM_PKT_STATUS\fP CMSG on received packets. Possible values: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ \fB0\fP T} T{ Disable (default) T} _ T{ \fB1\fP T} T{ Enable T} .TE .INDENT 0.0 .TP .B BT_SCM_PKT_STATUS Level \fBSOL_BLUETOOTH\fP CMSG with data: .INDENT 7.0 .INDENT 3.5 .sp .EX uint8_t pkt_status; .EE .UNINDENT .UNINDENT .sp The values are equal to the \(dqPacket_Status_Flag\(dq defined in Core Specification v6.0 Sec. 5.4.3 pp. 1877: .TS box center; l|l. T{ pkt_status T} T{ Description T} _ T{ \fB0x0\fP T} T{ Correctly received data T} _ T{ \fB0x1\fP T} T{ Possibly invalid data T} _ T{ \fB0x2\fP T} T{ No data received T} _ T{ \fB0x3\fP T} T{ Data partially lost T} .TE .UNINDENT .SS BT_PHY (since Linux 5.10) .sp Transport supported PHY(s), read\-only (no setsockopt support). Possible values: .TS box center; l|l|l. T{ Define T} T{ Value T} T{ Description T} _ T{ \fBBT_PHY_BR_1M_1SLOT\fP T} T{ BIT 0 T} T{ BR 1Mbps 1SLOT T} _ T{ \fBBT_PHY_BR_1M_3SLOT\fP T} T{ BIT 1 T} T{ BR 1Mbps 3SLOT T} _ T{ \fBBT_PHY_BR_2M_1SLOT\fP T} T{ BIT 3 T} T{ EDR 2Mbps 1SLOT T} _ T{ \fBBT_PHY_BR_2M_3SLOT\fP T} T{ BIT 4 T} T{ EDR 2Mbps 3SLOT T} _ T{ \fBBT_PHY_BR_3M_1SLOT\fP T} T{ BIT 6 T} T{ EDR 3Mbps 1SLOT T} _ T{ \fBBT_PHY_BR_3M_3SLOT\fP T} T{ BIT 7 T} T{ EDR 3Mbps 3SLOT T} .TE .SS BT_CODEC (since Linux 5.14) .sp Transport codec offload, possible values: .INDENT 0.0 .INDENT 3.5 .sp .EX struct bt_codec { uint8_t id; uint16_t cid; uint16_t vid; uint8_t data_path_id; uint8_t num_caps; struct codec_caps { uint8_t len; uint8_t data[]; } caps[]; } __attribute__((packed)); struct bt_codecs { uint8_t num_codecs; struct bt_codec codecs[]; } __attribute__((packed)); .EE .UNINDENT .UNINDENT .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX char buffer[sizeof(struct bt_codecs) + sizeof(struct bt_codec)]; struct bt_codec *codecs = (void *)buffer; memset(codecs, 0, sizeof(codecs)); codec\->num_codecs = 1; codecs\->codecs[0].id = 0x05; codecs\->codecs[0].data_path_id = 1; int err = setsockopt(sco_socket, SOL_BLUETOOTH, BT_CODEC, codecs, sizeof(buffer)); if (err == \-1) { perror(\(dqsetsockopt\(dq); return 1; } .EE .UNINDENT .UNINDENT .SH SOCKET OPTIONS (SOL_SOCKET) .sp \fBSOL_SOCKET\fP level socket options that modify generic socket features (\fBSO_SNDBUF\fP, \fBSO_RCVBUF\fP, etc.) have their usual meaning, see \fBsocket(7)\fP\&. .sp The \fBSOL_SOCKET\fP level SCO socket options that have Bluetooth\-specific handling in kernel are listed below. .SS SO_TIMESTAMPING, SO_TIMESTAMP, SO_TIMESTAMPNS .sp See \% .sp For SCO sockets, software RX timestamps are supported. Software TX timestamps (SOF_TIMESTAMPING_TX_SOFTWARE) are supported since Linux 6.15. .sp The software RX timestamp is the time when the kernel received the packet from the controller driver. .sp The \fBSCM_TSTAMP_SND\fP timestamp is emitted when packet is sent to the controller driver. .sp The \fBSCM_TSTAMP_COMPLETION\fP timestamp is emitted when controller reports the packet completed. Completion timestamps are only supported on controllers that have SCO flow control. Other TX timestamp types are not supported. .sp You can use \fBSIOCETHTOOL\fP to query supported flags. .sp The timestamps are in \fBCLOCK_REALTIME\fP time. .sp Example (Enable RX timestamping): .INDENT 0.0 .INDENT 3.5 .sp .EX int flags = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE; setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)); .EE .UNINDENT .UNINDENT .sp Example (Read packet and its RX timestamp): .INDENT 0.0 .INDENT 3.5 .sp .EX char data_buf[256]; union { char buf[CMSG_SPACE(sizeof(struct scm_timestamping))]; struct cmsghdr align; } control; struct iovec data = { .iov_base = data_buf, .iov_len = sizeof(data_buf), }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct scm_timestamping tss; res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT); if (res < 0) goto error; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg\->cmsg_level == SOL_SOCKET && cmsg\->cmsg_type == SCM_TIMESTAMPING) memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); } tstamp_clock_realtime = tss.ts[0]; .EE .UNINDENT .UNINDENT .sp Example (Enable TX timestamping): .INDENT 0.0 .INDENT 3.5 .sp .EX int flags = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_OPT_ID; setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)); .EE .UNINDENT .UNINDENT .sp Example (Read TX timestamps): .INDENT 0.0 .INDENT 3.5 .sp .EX union { char buf[CMSG_SPACE(sizeof(struct scm_timestamping))]; struct cmsghdr align; } control; struct iovec data = { .iov_base = NULL, .iov_len = 0 }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct cmsghdr *cmsg; struct scm_timestamping tss; struct sock_extended_err serr; int res; res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT); if (res < 0) goto error; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg\->cmsg_level == SOL_SOCKET && cmsg\->cmsg_type == SCM_TIMESTAMPING) memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); else if (cmsg\->cmsg_level == SOL_BLUETOOTH && cmsg\->cmsg_type == BT_SCM_ERROR) memcpy(&serr, CMSG_DATA(cmsg), sizeof(serr)); } tstamp_clock_realtime = tss.ts[0]; tstamp_type = serr\->ee_info; /* SCM_TSTAMP_SND or SCM_TSTAMP_COMPLETION */ tstamp_seqnum = serr\->ee_data; .EE .UNINDENT .UNINDENT .SH IOCTLS .sp The following ioctls with operation specific for SCO sockets are available. .SS SIOCETHTOOL (since Linux 6.16\-rc1) .sp Supports only command \fIETHTOOL_GET_TS_INFO\fP, which may be used to query supported \fISOF_TIMESTAMPING_*\fP flags. The \fISOF_TIMESTAMPING_OPT_*\fP flags are always available as applicable. .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX #include #include #include #include #include \&... struct ifreq ifr = {}; struct ethtool_ts_info cmd = {}; int sk; snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), \(dqhci0\(dq); ifr.ifr_data = (void *)&cmd; cmd.cmd = ETHTOOL_GET_TS_INFO; sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); if (sk < 0) goto error; if (ioctl(sk, SIOCETHTOOL, &ifr)) goto error; sof_available = cmd.so_timestamping; .EE .UNINDENT .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH SEE ALSO .sp socket(7), scotest(1) .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.AdminPolicySet.rst0000644000000000000000000000005015142177620017751 xustar0020 atime=1783093967 20 ctime=1783094111 bluez-5.87/doc/org.bluez.AdminPolicySet.rst0000644000000000000000000000275715142177620017445 0ustar00rootroot======================== org.bluez.AdminPolicySet ======================== -------------------------------------------- BlueZ D-Bus AdminPolicySet API documentation -------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Description ============ This API provides methods to control the behavior of **bluetoothd(8)** as an administrator. Interface AdminPolicySet1 provides methods to set policies. Once the policy is set successfully, it will affect all clients and stay persistently even after restarting **bluetoothd(8)**. The only way to clear it is to overwrite the policy with the same method. Interface ========= :Service: org.bluez :Interface: org.bluez.AdminPolicySet1 [experimental] :Object path: [variable prefix]/{hci0,hci1,...} :Used by: **bluetoothctl-admin(1)** Methods ------- void SetServiceAllowList(array{string} UUIDs) ````````````````````````````````````````````` Sets the service allowlist by specifying service UUIDs. When called, **bluetoothd(8)** will block incoming and outgoing connections to the service not in UUIDs for all of the clients. Any subsequent calls to this method will supersede any previously set allowlist values. Calling this method with an empty array will allow any service UUIDs to be used. The default value is an empty array. Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.Failed: Examples: :bluetoothctl: > admin.allow [clear/uuid1 uuid2 ...] bluez-5.87/doc/PaxHeaders/org.bluez.AdvertisementMonitorManager.50000644000000000000000000000005015221755360022077 xustar0020 atime=1783094000 20 ctime=1783094112 bluez-5.87/doc/org.bluez.AdvertisementMonitorManager.50000644000000000000000000000674415221755360021573 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.AdvertisementMonitorManager" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.AdvertisementMonitorManager \- BlueZ D-Bus AdvertisementMonitorManager API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.AdvertisementMonitorManager1 [experimental] .TP .B Object path /org/bluez/{hci0,hci1,...} .TP .B Used by \fBbluetoothctl\-monitor(1)\fP .UNINDENT .SS Methods .SS void RegisterMonitor(object application) .sp Registers the root path of a hierarchy of advertisement monitors implementing \fBorg.bluez.AdvertisementMonitor(5)\fP\&. .sp The application object path together with the D\-Bus ystem bus connection ID define the identification of the application registering advertisement monitors. .sp Once a root path is registered by a client via this method, the client can freely expose/unexpose advertisement monitors without re\-registering the root path again. .sp After use, the client should call \fBUnregisterMonitor()\fP method to invalidate the advertisement monitors. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.AlreadyExists .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl [monitor] > add\-or\-pattern .UNINDENT .SS void UnregisterMonitor(object application) .sp Unregisters a hierarchy of advertisement monitors that has been previously registered with \fBRegisterMonitor()\fP\&. The object path parameter must match the same value that has been used on registration. .sp Upon unregistration, the advertisement monitor(s) should expect to receive \fBRelease()\fP method as the signal that the advertisement monitor(s) has been deactivated. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.DoesNotExist .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl [monitor] > remove\-pattern .UNINDENT .SS Properties .SS array{string} SupportedMonitorTypes [read\-only] .sp This lists the supported types of advertisement monitors. An application should check this before instantiate and expose an object of \fBorg.bluez.AdvertisementMonitor(5)\fP\&. .sp Possible values: .INDENT 0.0 .TP .B \(dqor_patterns\(dq Patterns with logic OR applied. With this type, property \fBPatterns\fP must exist and has at least one pattern. .UNINDENT .SS array{string} SupportedFeatures [read\-only] .sp This lists the features of advertisement monitoring supported by \fBbluetoothd(8)\fP\&. .sp Possible values: .INDENT 0.0 .TP .B \(dqcontroller\-patterns\(dq If the controller is capable of performing advertisement monitoring by patterns, \fBbluetoothd(8)\fP would offload the patterns to the controller to reduce power consumption. .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.GattProfile.50000644000000000000000000000005015221755344016644 xustar0020 atime=1783093988 20 ctime=1783094112 bluez-5.87/doc/org.bluez.GattProfile.50000644000000000000000000000323115221755344016324 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.GattProfile" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.GattProfile \- BlueZ D-Bus GattProfile API documentation .SH Description .sp Local profile (GATT client) instance. By registering this type of object an application effectively indicates support for a specific GATT profile and requests automatic connections to be established to devices supporting it. .SH Interface .INDENT 0.0 .TP .B Service .TP .B Interface org.bluez.GattProfile1 .TP .B Object path .UNINDENT .SS Methods .SS void Release() .sp This method gets called when \fBbluetoothd(8)\fP unregisters the profile. The profile can use it to do cleanup tasks. .sp There is no need to unregister the profile, because when this method gets called it has already been unregistered. .SS Properties .SS array{string} UUIDs [read\-only] .sp 128\-bit GATT service UUIDs to auto connect. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/btmgmt.10000644000000000000000000000005015221755517014006 xustar0020 atime=1783094095 20 ctime=1783094113 bluez-5.87/doc/btmgmt.10000644000000000000000000000457115221755517013476 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "btmgmt" "1" "July 2023" "BlueZ" "Linux System Administration" .SH Name btmgmt \- interactive bluetooth management tool .SH SYNOPSIS .sp \fBbtmgmt\fP [\-\-options] [commands] .SH DESCRIPTION .sp \fBbtmgmt(1)\fP interactive bluetooth management tool. The tool issues commands to the Kernel using the Bluetooth Management socket, some commands may require net\-admin capability in order to work since the Bluetooth Management interface is considered a low\-level interface meant for the likes of \fBbluetoothd(8)\fP, it is not recommended for applications to use it directly as it may result in unexpected behavior. .SH OPTIONS .INDENT 0.0 .TP .B \-i/\-\-index Specify adapter index .TP .B \-m\-/\-monitor Enable monitor output .TP .B \-t/\-\-timeout Timeout in seconds for non\-interactive mode .TP .B \-v/\-\-version Display version .TP .B \-i/\-\-init\-script Init script file .TP .B \-h/\-\-help Display help .UNINDENT .SH COMMANDS .INDENT 0.0 .TP .B main See \fBbluetoothctl\-mgmt(1)\fP .TP .B monitor See \fBbluetoothctl\-monitor(1)\fP .UNINDENT .SH AUTOMATION .sp Two common ways to automate the tool are to pass the commands directly like in the follow example: .INDENT 0.0 .INDENT 3.5 .sp .EX btmgmt < .SH REPORTING BUGS .sp \% .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/btmgmt.rst0000644000000000000000000000005015142177620014451 xustar0020 atime=1783094095 20 ctime=1783094113 bluez-5.87/doc/btmgmt.rst0000644000000000000000000000315615142177620014137 0ustar00rootroot====== btmgmt ====== ------------------------------------- interactive bluetooth management tool ------------------------------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: July 2023 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **btmgmt** [--options] [commands] DESCRIPTION =========== **btmgmt(1)** interactive bluetooth management tool. The tool issues commands to the Kernel using the Bluetooth Management socket, some commands may require net-admin capability in order to work since the Bluetooth Management interface is considered a low-level interface meant for the likes of **bluetoothd(8)**, it is not recommended for applications to use it directly as it may result in unexpected behavior. OPTIONS ======= :-i/--index: Specify adapter index :-m-/-monitor: Enable monitor output :-t/--timeout: Timeout in seconds for non-interactive mode :-v/--version: Display version :-i/--init-script: Init script file :-h/--help: Display help COMMANDS ======== :main: See **bluetoothctl-mgmt(1)** :monitor: See **bluetoothctl-monitor(1)** AUTOMATION ========== Two common ways to automate the tool are to pass the commands directly like in the follow example: .. code-block:: btmgmt < - Tedd Ho-Jeong An :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Version: BlueZ :Date: April 2021 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **btmon** [*OPTIONS* ...] DESCRIPTION =========== The btmon(1) command provides access to the Bluetooth subsystem monitor infrastructure for reading HCI traces. OPTIONS ======= -r FILE, --read FILE Read traces in btsnoop format from *FILE*. -w FILE, --write FILE Save traces in btsnoop format to *FILE*. -a FILE, --analyze FILE Analyze traces in btsnoop format from *FILE*. It displays the devices found in the *FILE* with its packets by type. If gnuplot is installed on the system it also attempts to plot packet latency graph. -s SOCKET, --server SOCKET Start monitor server socket. -p PRIORITY, --priority PRIORITY Show only priority or lower for user log. .. list-table:: :header-rows: 1 :widths: auto :stub-columns: 1 * - *PRIORITY* - NAME * - **3** - Error * - **4** - Warning * - **6** - Information (Default) * - **7** - Debug. **debug** can be used. -i NUM, --index NUM Show only specified controller. *hciNUM* is also acceptable. This is useful to capture the traces from the specific controller when the multiple controllers are presented. -d TTY, --tty TTY Read data from *TTY*. -B SPEED, --rate SPEED Set TTY speed. The default *SPEED* is 115300 -V COMPID, --vendor COMPID Set the default company identifier. The *COMPID* is a unique number assigned by the Bluetooth SIG to a member company and can be found/searched from the Bluetooth SIG webpage. For example, Intel is 2 and Realtek is 93. -M, --mgmt Open channel for mgmt events. -K, --kernel Open kmsg for kernel messages. -t, --time Show a time instead of time offset. -T, --date Show a time and date information instead of time offset. -N, --no-time Suppress the time offset display entirely. -S, --sco Dump SCO traffic in raw hex format. -A, --a2dp Dump A2DP stream traffic in a raw hex format. -I, --iso Dump ISO stream traffic in raw hex format. Required to see LE Audio isochronous data in the output. -E IP, --ellisys IP Send Ellisys HCI Injection. -P, --no-pager Disable pager usage while reading the log file. -J OPTIONS, --jlink OPTIONS Read data from RTT. Each options are comma(,) separated without spaces. .. list-table:: :header-rows: 1 :widths: auto :stub-columns: 1 * - *OPTIONS* - Description * - **DEVICE** - Required. Set the target device. * - **SERIALNO** - (Optional) Set the USB serial number. Default is **0**. * - **INTERFACE** - (Optional) Target interface. Default is **swd**. * - **SPEED** - (Optional) Set target interface speed in kHz. Default is **1000**. -R OPTIONS, --rtt OPTIONS RTT control block parameters. Each options are comma(,) separated without spaces. .. list-table:: :header-rows: 1 :widths: auto :stub-columns: 1 * - *OPTIONS* - Description * - **ADDRESS** - (Optional) Address of RTT buffer. Default is **0x00** * - **AREA** - (Optional) Size of range to search in RTT buffer. Default is **0** * - **NAME** - (Optional) Buffer name. Default is **btmonitor** -C WIDTH, --columns WIDTH Output width if not a terminal -c MODE, --color MODE Set output color. The possible *MODE* values are: **auto|always|never**. Default value is **auto** -v, --version Show version -h, --help Show help options READING THE OUTPUT ================== btmon output is organized as a stream of frames, each representing a single event in the Bluetooth subsystem. Understanding the output format is essential for debugging Bluetooth issues. Line Prefixes ------------- Every frame begins with a single-character prefix that identifies its source and type: .. list-table:: :header-rows: 1 :widths: 5 20 75 * - Prefix - Meaning - Description * - ``<`` - **HCI Command / Data TX** - Sent from host to controller (outgoing). HCI commands, ACL/SCO/ISO data transmitted to the controller. * - ``>`` - **HCI Event / Data RX** - Received from controller to host (incoming). HCI events, ACL/SCO/ISO data received from the controller. * - ``@`` - **Management traffic** - Management interface (MGMT) commands and events between bluetoothd and the kernel management layer. * - ``=`` - **System notes** - System-level annotations: kernel information, index changes, process log messages, and D-Bus signals. HCI Traffic (``<`` and ``>``) ----------------------------- HCI frames represent the actual communication between the host software and the Bluetooth controller hardware. **Anatomy of an HCI command line**:: < HCI Command: Reset (0x03|0x0003) plen 0 #5 [hci0] 12:35:01.843185 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ Timestamp │ │ │ │ │ │ └─ Controller │ │ │ │ │ └─ Frame number │ │ │ │ └─ Parameter length (bytes) │ │ │ └─ Full opcode (16-bit) │ │ └─ OGF|OCF (Opcode Group / Command Field) │ └─ Command name (human-readable) └─ Direction: < = Host to Controller (outgoing) **Anatomy of an HCI event line**:: > HCI Event: Command Complete (0x0e) plen 4 #6 [hci0] 12:35:01.864922 │ │ │ │ │ │ │ │ │ │ │ └─ Timestamp │ │ │ │ └─ Controller │ │ │ └─ Frame number │ │ └─ Parameter length │ └─ Event code │ └─ Direction: > = Controller to Host (incoming) The ``<`` direction means the host is **sending** to the controller (commands and data). The ``>`` direction means the controller is **sending** to the host (events and data). Think of it from the controller's perspective: ``<`` is input going into the controller, ``>`` is output coming from it. **HCI commands with parameters** are followed by indented detail lines:: < HCI Command: LE Set Extende.. (0x08|0x0039) plen 2 #1 [hci0] 12:35:01.738352 Extended advertising: Disabled (0x00) Number of sets: Disable all sets (0x00) **HCI event responses** reference the command they complete:: > HCI Event: Command Complete (0x0e) plen 4 #6 [hci0] 12:35:01.864922 Reset (0x03|0x0003) ncmd 2 Status: Success (0x00) Here ``ncmd 2`` indicates the controller can accept 2 more commands (HCI flow control). The indented body shows the command this event completes and the result status. **LE Meta Events** contain a subevent type:: > HCI Event: LE Meta Event (0x3e) plen 31 #487 [hci0] 12:36:18.974201 LE Enhanced Connection Complete (0x0a) Status: Success (0x00) Handle: 2048 Role: Peripheral (0x01) Peer address type: Public (0x00) Peer address: AA:BB:CC:DD:EE:FF (OUI Company) Connection interval: 60.00 msec (0x0030) Connection latency: 0 (0x0000) Supervision timeout: 9600 msec (0x03c0) **ACL Data** shows data plane traffic with handle and protocol decoding:: < LE-ACL: Handle 2048 [66:B0:26:F1:D3:BC] [1/6] flags 0x00 dlen 16 #493 [hci0] 12:36:18.977915 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ Timestamp │ │ │ │ │ │ │ │ └─ Controller │ │ │ │ │ │ │ └─ Frame number │ │ │ │ │ │ └─ Data length │ │ │ │ │ └─ flags │ │ │ │ └─ Buffer tracking (optional) │ │ │ └─ Peer address (optional) │ │ └─ Handle number │ └─ Connection-type-aware label (e.g. BR-ACL, LE-ACL, BR-SCO, LE-ISO) └─ Direction marker: '<' = host->controller (TX), '>' = controller->host (RX) ACL data is automatically decoded into higher-layer protocols:: < LE-ACL: Handle 2048 [2/6] flags 0x00 dlen 7 #494 [hci0] 12:36:18.978488 ATT: Exchange MTU Request (0x02) len 2 Client RX MTU: 517 > LE-ACL: Handle 2048 flags 0x02 dlen 11 #497 [hci0] 12:36:19.000048 SMP: Pairing Request (0x01) len 6 IO capability: NoInputNoOutput (0x03) OOB data: Authentication data not present (0x00) Authentication requirement: Bonding, MITM, SC, No Keypresses, CT2 (0x2d) Max encryption key size: 16 Management Traffic (``@``) -------------------------- Lines starting with ``@`` show management interface traffic -- the structured command/event protocol between ``bluetoothd`` and the kernel (see ``doc/mgmt-protocol.rst``). **Anatomy of a management line**:: @ MGMT Command: Set Powered (0x0005) plen 1 {0x0001} [hci0] 12:35:04.033564 │ │ │ │ │ │ │ │ │ │ │ └─ Timestamp │ │ │ │ └─ Controller │ │ │ └─ MGMT socket ID │ │ └─ Parameter length │ └─ MGMT opcode └─ @ = Management channel The ``{0x0001}`` is the management socket identifier -- it distinguishes between multiple management clients (e.g. bluetoothd and btmgmt running simultaneously). **MGMT Open/Close** track when processes connect to the management channel:: @ MGMT Open: bluetoothd (privileged) version 1.23 {0x0001} 12:34:49.881936 @ MGMT Close: bluetoothd {0x0001} 12:35:01.866256 These show the process name, privilege level, and protocol version. **MGMT commands with parameters**:: @ MGMT Command: Set Powered (0x0005) plen 1 {0x0001} [hci0] 12:35:04.033564 Powered: Enabled (0x01) **MGMT events** (responses and notifications):: @ MGMT Event: Command Complete (0x0001) plen 7 {0x0001} [hci0] 12:35:04.114789 Set Powered (0x0005) plen 4 Status: Success (0x00) Current settings: 0x004e0ac1 Powered Secure Simple Pairing **MGMT without controller index** (global operations):: @ MGMT Command: Read Management Ver.. (0x0001) plen 0 {0x0001} 12:35:04.027771 @ MGMT Event: Command Complete (0x0001) plen 6 {0x0001} 12:35:04.027776 Notice there is no ``[hci0]`` -- these operate at the system level, not on a specific controller. System Notes (``=``) -------------------- Lines starting with ``=`` are system-level annotations injected by the kernel or by processes via the monitor channel. They are **not** HCI or MGMT protocol traffic. **Kernel information** (shown at startup):: = Note: Linux version 6.16.0-rc6-0903 (x86_64) 12:34:49.881926 = Note: Bluetooth subsystem version 2.22 12:34:49.881930 **Index lifecycle** (controller added/removed/opened/closed):: = New Index: 00:11:22:33:44:55 (Primary,USB,hci0) [hci0] 12:34:49.881932 = Open Index: 00:11:22:33:44:55 [hci0] 12:34:49.881933 = Index Info: 00:11:22:33:44:55 (OUI Company) [hci0] 12:34:49.881934 = Close Index: 00:11:22:33:44:55 [hci0] 12:35:01.865125 - ``New Index`` -- a controller was registered with the kernel - ``Open Index`` -- a controller was activated - ``Index Info`` -- controller vendor information - ``Close Index`` -- a controller was deactivated **Process log messages** (debug output from bluetoothd and other daemons):: = bluetoothd: src/adapter.c:connected_callback() hci0 devic.. 12:36:18.975307 │ │ │ │ │ └─ Timestamp │ └─ Source file, function, and message (may be truncated) └─ Process name These appear when ``bluetoothd`` is running with debug enabled (``-d``) or when a process writes to the kernel logging channel. They show the source file path, function name, and a log message -- invaluable for correlating daemon-internal decisions with the HCI traffic around them. **D-Bus activity** (signals and method calls):: = bluetoothd: [:1.21220:method_call] > org.freedesktop.DBus.. 12:34:53.912508 = bluetoothd: [:1.21220:method_return] < [#5] 12:34:53.912546 = bluetoothd: [signal] org.freedesktop.DBus.ObjectManager.I.. 12:36:18.975691 The format is ``[bus_name:message_type]`` followed by ``>`` (outgoing) or ``<`` (incoming). Note that ``>`` and ``<`` within D-Bus system notes indicate D-Bus message direction, not HCI direction. Right-Side Metadata ------------------- Every line has metadata right-aligned at the end. The exact fields depend on the line type:: ┌─ Main content (left-aligned, variable width) │ ┌─ Frame # ─┐ ┌Controller┐ ┌─ Timestamp ─┐ │ │ │ │ │ │ │ < HCI Command: Reset (0x03|0x0003) plen 0 #5 [hci0] 12:35:01.843185 > HCI Event: Command Complete (0x0e) plen 4 #6 [hci0] 12:35:01.864922 @ MGMT Command: Set Powered (0x0005) plen 1 {0x0001} [hci0] 12:35:04.033564 = Note: Linux version 6.16.0-rc6-0903 (x86_64) 12:34:49.881926 = Open Index: 00:11:22:33:44:55 [hci0] 12:34:49.881933 **Frame number** (``#N``): Sequential counter for HCI frames only. Useful for identifying specific packets in a trace. Only HCI traffic (``<`` and ``>``) gets frame numbers -- MGMT (``@``) and system notes (``=``) do not. **Controller** (``[hciN]``): Identifies which Bluetooth controller the frame belongs to. Absent for global operations (kernel notes, MGMT commands without a controller index). **MGMT socket ID** (``{0xNNNN}``): Shown on ``@`` lines instead of frame numbers. Identifies which management socket (process) sent the command. **Timestamp**: Always the rightmost field. The format depends on the command-line option used: .. list-table:: :header-rows: 1 :widths: 15 25 30 * - Option - Format - Example * - *(default)* - Seconds since trace start - ``0.881932`` * - ``-t`` - Time of day (HH:MM:SS.usec) - ``12:35:01.843185`` * - ``-T`` - Full date and time - ``2026-01-13 12:34:49.881926`` Indented Detail Lines --------------------- Lines indented below a frame header contain the decoded payload of that frame. The indentation level indicates the protocol layer: - **First level** (6 spaces): direct payload of the HCI/MGMT frame - **Second level** (8 spaces): decoded fields within the payload - **Third level** (10+ spaces): nested protocol data (e.g. L2CAP inside ACL, ATT inside L2CAP) Example of protocol layering in ACL data:: > ACL: Handle 2048 flags 0x02 dlen 11 #497 [hci0] 12:36:19.000048 SMP: Pairing Request (0x01) len 6 ← L2CAP/SMP layer IO capability: NoInputNoOutput (0x03) ← SMP fields OOB data: Authentication data not present (0x00) Authentication requirement: Bonding, MITM, SC (0x2d) Max encryption key size: 16 Timestamp Notes --------------- When reading btsnoop files with ``-t`` or ``-T``, timestamps reflect the wall-clock time recorded in the btsnoop file. The precision depends on the source: - **Live capture** (``btmon`` monitor channel): Microsecond precision from the kernel. - **btsnoop files**: The btsnoop format stores timestamps as microseconds since epoch, so full microsecond precision is preserved. Trailing zeros in the display (e.g., ``14:38:46.589000``) indicate the original capture source had millisecond granularity. The default timestamp mode shows seconds elapsed since the first packet in the trace, which is useful for measuring intervals between events without needing to know the absolute time. Frame Numbers vs Line Numbers ----------------------------- btmon assigns sequential **frame numbers** (``#N``) to HCI packets. These are stable identifiers for specific packets regardless of output formatting. However, when processing btmon text output with tools like ``grep`` or ``sed``, the relevant unit is **line numbers** in the output file. The two are unrelated: - A single frame may produce many output lines (header + decoded fields). - Frame numbers only apply to HCI traffic (``<`` and ``>``). MGMT (``@``) and system notes (``=``) do not have frame numbers. - When referencing specific packets, prefer frame numbers (``#487``) over line numbers, as frame numbers are stable across different terminal widths and formatting options. Practical Reading Guide ----------------------- **Typical command-response pair**:: < HCI Command: Read BD ADDR (0x04|0x0009) plen 0 #13 [hci0] 12:35:04.057866 > HCI Event: Command Complete (0x0e) plen 10 #14 [hci0] 12:35:04.058750 Read BD ADDR (0x04|0x0009) ncmd 1 Status: Success (0x00) Address: 00:11:22:33:44:55 (OUI Company) Read this as: Frame #13, the host asked the controller for its Bluetooth address. Frame #14, the controller replied with success and the address ``00:11:22:33:44:55``. The response arrived ~0.9ms later. **Typical MGMT flow showing relation to HCI**:: @ MGMT Command: Set Powered (0x0005) plen 1 {0x0001} [hci0] 12:35:04.033564 Powered: Enabled (0x01) < HCI Command: Reset (0x03|0x0003) plen 0 #7 [hci0] 12:35:04.033907 > HCI Event: Command Complete (0x0e) plen 4 #8 [hci0] 12:35:04.055753 Reset (0x03|0x0003) ncmd 2 Status: Success (0x00) ... (more HCI commands to configure the controller) ... @ MGMT Event: Command Complete (0x0001) plen 7 {0x0001} [hci0] 12:35:04.114789 Set Powered (0x0005) plen 4 Status: Success (0x00) Read this as: bluetoothd sent ``Set Powered`` via MGMT. The kernel translated this into a sequence of HCI commands (Reset, then configuration). After all HCI commands completed, the kernel sent the MGMT Command Complete event back to bluetoothd. **Connection establishment flow**:: > HCI Event: LE Meta Event (0x3e) plen 31 #487 [hci0] 12:36:18.974201 LE Enhanced Connection Complete (0x0a) Status: Success (0x00) Handle: 2048 Role: Peripheral (0x01) Peer address: AA:BB:CC:DD:EE:FF (OUI Company) @ MGMT Event: Device Connec.. (0x000b) plen 13 {0x0001} [hci0] 12:36:18.974319 = bluetoothd: src/adapter.c:connected_callback() hci0 devic.. 12:36:18.975307 < ACL: Handle 2048 [1.. flags 0x00 dlen 16 #493 [hci0] 12:36:18.977915 LE L2CAP: Connection Parameter Update Request (0x12) ident 1 len 8 < ACL: Handle 2048 [2/6] flags 0x00 dlen 7 #494 [hci0] 12:36:18.978488 ATT: Exchange MTU Request (0x02) len 2 Client RX MTU: 517 Read this as: The controller reported a new LE connection (HCI event). The kernel forwarded this as a MGMT Device Connected event. bluetoothd logged its ``connected_callback()``. Then data exchange began -- an L2CAP parameter update and ATT MTU negotiation over the new ACL connection. ANALYZE MODE ============ The ``-a`` (``--analyze``) option reads a btsnoop file and produces a statistical summary instead of the full decoded trace. Usage ----- .. code-block:: $ btmon -a hcidump.log Output Contents --------------- Analyze mode reports, for each controller found in the trace: - **Packet counts**: Total HCI packets broken down by type (commands, events, ACL, SCO, ISO, vendor diagnostics, system notes, user logs, control messages). - **Per-connection statistics**: For each connection handle found: - Connection type (BR-ACL, LE-ACL, BR-SCO, BR-ESCO, LE-ISO) - Device address - TX and RX packet counts and completion counts - Latency statistics (min, max, median) in milliseconds - Packet size statistics (min, max, average) in octets - Throughput estimate in Kb/s - **Per-channel statistics**: For each L2CAP channel within a connection, the same packet/latency/size statistics. - **Latency plots**: If ``gnuplot`` is installed, ASCII-art latency distribution plots are rendered in the terminal. PROTOCOL ERROR CODES ===================== btmon automatically decodes error codes from multiple protocol layers. This section provides a reference for interpreting errors seen across ATT, SMP, and L2CAP layers. ATT Error Codes ---------------- ATT errors appear in ``Error Response (0x01)`` PDUs. Beyond the GATT discovery context (where ``Attribute Not Found`` is normal), these errors indicate real problems: .. list-table:: :header-rows: 1 :widths: 8 35 57 * - Code - Error - Diagnostic Meaning * - 0x01 - Invalid Handle - Client used a handle that does not exist * - 0x02 - Read Not Permitted - Characteristic does not allow reads * - 0x03 - Write Not Permitted - Characteristic does not allow writes * - 0x05 - Authentication Insufficient - Operation requires an authenticated (MITM-protected) bond. Triggers SMP pairing if not yet bonded. * - 0x06 - Request Not Supported - Server does not support this ATT operation * - 0x07 - Invalid Offset - Read/write blob offset exceeds attribute length * - 0x08 - Authorization Insufficient - Server requires additional authorization * - 0x09 - Prepare Queue Full - Too many prepared writes queued * - 0x0a - Attribute Not Found - No attributes in requested range. Normal termination for GATT discovery procedures. * - 0x0b - Attribute Not Long - Attribute cannot be read with Read Blob * - 0x0c - Insufficient Encryption Key Size - Encryption key is too short * - 0x0d - Invalid Attribute Value Length - Write value length is incorrect for this attribute * - 0x0e - Unlikely Error - Generic unlikely error * - 0x0f - Insufficient Encryption - Link is not encrypted. Triggers encryption setup. * - 0x10 - Unsupported Group Type - Attribute type is not a valid grouping type * - 0x11 - Insufficient Resources - Server out of resources * - 0x12 - Value Not Allowed - Value is not within permitted range * - 0x80-0x9f - Application Error - Application-specific error; meaning depends on the profile/service. ASCS uses these for ASE-specific errors. * - 0xfc - Write Request Rejected - Write rejected (CSIP, ASCS) * - 0xfd - CCC Descriptor Improperly Configured - CCC must be enabled before certain operations * - 0xfe - Procedure Already in Progress - Another procedure is already running * - 0xff - Out of Range - Value is outside valid range L2CAP Connection Response Results ---------------------------------- L2CAP Connection Response and LE Connection Response include a result code: .. list-table:: :header-rows: 1 :widths: 8 35 57 * - Code - Result - Diagnostic Meaning * - 0x0000 - Connection successful - Channel established normally * - 0x0001 - Connection pending - Connection in progress (BR/EDR only) * - 0x0002 - Connection refused - PSM not supported - Remote does not have a server for this protocol * - 0x0003 - Connection refused - security block - Security requirements not met * - 0x0004 - Connection refused - no resources - Remote ran out of channel resources * - 0x0005 - Connection refused - invalid Source CID - Source CID is invalid or already in use * - 0x0006 - Connection refused - Source CID already allocated - CID collision * - 0x0007 - Connection refused - unacceptable parameters - LE credit-based: MTU, MPS, or credits unacceptable * - 0x0008 - Connection refused - invalid parameters - Parameter values are invalid * - 0x0009 - Connection refused - insufficient authentication - Not authenticated * - 0x000a - Connection refused - insufficient authorization - Not authorized * - 0x000b - Connection refused - insufficient encryption key size - Encryption key too short * - 0x000c - Connection refused - insufficient encryption - Link not encrypted Automating Error Detection --------------------------- **Find all ATT errors** (excluding normal discovery termination):: grep -n "Error Response" output.txt Then check whether each error is ``Attribute Not Found (0x0a)`` within a discovery sequence (normal) or a different error code (problem). **Find all authentication/encryption related errors**:: grep -n "Authentication Insufficient\|Insufficient Encryption\|Insufficient Security\|security block" output.txt These indicate the link needs pairing or encryption. Check whether SMP pairing follows. **Find all L2CAP channel rejections**:: grep -n "Connection refused" output.txt **Cross-layer error correlation**: Errors often cascade across layers. Common patterns: 1. ATT ``Insufficient Encryption`` (0x0f) → triggers HCI ``LE Start Encryption`` → ``Encryption Change`` success → ATT operation retried 2. ATT ``Authentication Insufficient`` (0x05) → triggers SMP ``Pairing Request`` → pairing completes → ATT operation retried 3. SMP ``Pairing Failed`` → ``Disconnect Complete`` with reason ``Authentication Failure (0x05)`` 4. L2CAP ``Connection refused - security block`` → triggers SMP pairing PROTOCOL FLOWS =============== .. include:: btmon-hci-init.rst .. include:: btmon-connections.rst .. include:: btmon-gatt.rst .. include:: btmon-smp.rst .. include:: btmon-l2cap.rst .. include:: btmon-le-audio.rst .. include:: btmon-cs.rst .. include:: btmon-classic-audio.rst .. include:: btmon-advertising.rst .. include:: btmon-mgmt.rst EXAMPLES ======== Capture the traces from hci0 to hcidump.log file ------------------------------------------------ .. code-block:: $ btmon -i hci0 -w hcidump.log Open the trace file ------------------- .. code-block:: $ btmon -r hcidump.log Open the trace file with wall-clock timestamps ----------------------------------------------- .. code-block:: $ btmon -t -r hcidump.log Open the trace file with full date and time -------------------------------------------- .. code-block:: $ btmon -T -r hcidump.log AUTOMATED TRACE ANALYSIS ========================= This section provides guidance for analyzing btmon traces programmatically or with AI assistance. Each topic references the detailed protocol section earlier in this document. Recommended Workflow -------------------- 1. **Get an overview**: Start with ``btmon -a `` to see packet counts, connection handles, device addresses, and traffic volumes. 2. **Decode with timestamps**: Use ``btmon -t -r > output.txt`` to produce a text file with wall-clock timestamps for analysis. 3. **Identify connections**: Search for connection establishment events to build a handle-to-address mapping:: grep -n "Connection Complete\|Enhanced Connection Complete\|CIS Established" output.txt 4. **Track disconnections**: Search for disconnect events and their reasons:: grep -n "Disconnect Complete" output.txt Then examine the lines following each match for the ``Reason:`` field. See `HCI ERROR AND DISCONNECT REASON CODES`_ for interpretation. 5. **Check pairing/security**: Search for SMP activity (see `SMP PAIRING FLOW`_):: grep -n "Pairing Request\|Pairing Response\|Pairing Failed\|Encryption Change" output.txt 6. **Identify LE Audio**: Search for ASCS and CIS activity (see `LE AUDIO PROTOCOL FLOW`_):: grep -n "ASE Control Point\|CIG Parameters\|Create Connected Isochronous\|CIS Established\|Setup ISO Data Path" output.txt 7. **Check for errors**: Search across all protocol layers (see `PROTOCOL ERROR CODES`_):: # HCI-level errors grep -n "Status:" output.txt | grep -v "Success" # ATT-level errors grep -n "Error Response" output.txt # SMP failures grep -n "Pairing Failed" output.txt # L2CAP rejections grep -n "Connection refused" output.txt 8. **Extract GATT discovery**: Filter the GATT service/characteristic discovery traffic (see `RECONSTRUCTING A GATT DATABASE FROM SNOOP TRACES`_):: # Find all service discovery responses grep -n "Read By Group Type Response\|Attribute group list\|Handle range.*UUID" output.txt # Find all characteristic discovery responses grep -n "Read By Type Response\|Properties:\|Value Handle:\|Value UUID:" output.txt # Find all descriptor discovery responses grep -n "Find Information Response\|Format:\|Handle:.*UUID:" output.txt # Find targeted service searches grep -n "Find By Type Value" output.txt 9. **Check L2CAP channels**: Identify protocol usage and channel issues (see `L2CAP CHANNEL TRACKING`_):: grep -n "PSM:\|Connection Request\|Connection Response\|Parameter Update" output.txt 10. **Check advertising**: See what devices are visible and what they advertise (see `ADVERTISING AND SCANNING`_):: grep -n "Advertising Report\|Name (complete):\|Appearance:" output.txt Key Patterns for Connection Lifecycle ------------------------------------- A complete connection lifecycle for an LE ACL connection follows this pattern in the trace: 1. ``LE Enhanced Connection Complete`` -- connection established, note the Handle and Peer address 2. ``LE Connection Update Complete`` -- connection parameters changed (may occur zero or more times) 3. ``Encryption Change`` -- link encrypted (may show encryption algorithm). See `SMP PAIRING FLOW`_ for the SMP exchange that precedes this. 4. ACL Data with ATT/SMP/L2CAP -- service discovery and data exchange. See `RECONSTRUCTING A GATT DATABASE FROM SNOOP TRACES`_ for GATT, `L2CAP CHANNEL TRACKING`_ for channel setup, and `PROTOCOL ERROR CODES`_ for error interpretation. 5. ``Disconnect Complete`` -- connection ended, check Reason field. See `HCI ERROR AND DISCONNECT REASON CODES`_ for reason codes. For LE Audio connections, additional steps appear between 3 and 5 (see `LE AUDIO PROTOCOL FLOW`_ for full details): - ATT operations on PACS/ASCS characteristics (codec negotiation) - ``LE Set CIG Parameters`` command and response - ``LE Create CIS`` command - ``LE CIS Established`` event (note the CIS handle) - ``LE Setup ISO Data Path`` command - ISO Data TX/RX (audio streaming) - ``Disconnect Complete`` on CIS handle (stream ended) - ``LE Remove CIG`` (group removed) Common Debugging Scenarios --------------------------- **Pairing failure diagnosis**: 1. Find ``Pairing Request`` -- note IO capabilities and auth requirements 2. Find ``Pairing Response`` -- compare to determine association model 3. If ``Pairing Failed`` appears, the reason code identifies the failure (see `SMP PAIRING FLOW`_) 4. If ``Encryption Change`` shows ``Status: Success``, pairing succeeded 5. If no SMP traffic on reconnect but ``Encryption Change`` fails, the bond was lost on one side **Audio streaming failure diagnosis** (see `LE AUDIO PROTOCOL FLOW`_): 1. Check PACS reads -- do both devices support compatible codecs? 2. Check ASE Control Point Config Codec -- was it accepted? 3. Check ASE state notifications -- did the ASE reach Streaming? 4. Check ``CIS Established`` -- was Status Success? 5. Check ``Setup ISO Data Path`` -- was it configured? 6. Check for ISO Data packets -- is audio actually flowing? **GATT operation rejected** (see `PROTOCOL ERROR CODES`_): 1. Find ``Error Response`` -- note the error code 2. ``Insufficient Encryption`` (0x0f) → expect ``LE Start Encryption`` to follow 3. ``Authentication Insufficient`` (0x05) → expect SMP pairing to follow 4. After security is established, the ATT operation should be retried **Connection parameter negotiation failure**: 1. Find ``Connection Parameter Update Request`` (L2CAP level) 2. Check the Response -- ``rejected`` means the central denied it 3. Alternatively, find ``LE Connection Update Complete`` (HCI level) 4. Check Status -- non-zero means the controller rejected the update Vendor-Specific Events ---------------------- Vendor-specific HCI events (event code 0xFF) contain controller-manufacturer diagnostic data. btmon decodes some vendor events for known manufacturers (Intel, Broadcom, etc.) but many sub-events show as ``Unknown`` with raw hex data. These are expected and generally not actionable without vendor documentation. Intel controllers emit extended telemetry events (subevent 0x8780) that include connection quality metrics, error counters, and firmware state. Partial decoding is available in ``monitor/intel.c``. RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org SEE ALSO ======== btsnoop(7) bluez-5.87/doc/PaxHeaders/org.bluez.AgentManager.rst0000644000000000000000000000005015142177620017416 xustar0020 atime=1783093948 20 ctime=1783094111 bluez-5.87/doc/org.bluez.AgentManager.rst0000644000000000000000000000412315142177620017077 0ustar00rootroot====================== org.bluez.AgentManager ====================== ------------------------------------------ BlueZ D-Bus AgentManager API documentation ------------------------------------------ :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.AgentManager1 :Object path: /org/bluez :Used by: **bluetoothctl(1)** Methods ------- void RegisterAgent(object agent, string capability) ``````````````````````````````````````````````````` Registers pairing agent. The object path defines the path of the agent that will be called when user input is needed and must implement **org.bluez.Agent(5)** interface. Every application can register its own agent and for all actions triggered by that application its agent is used. It is not required by an application to register an agent. If an application does chooses to not register an agent, the default agent is used. This is on most cases a good idea. Only application like a pairing wizard should register their own agent. An application can only register one agent. Multiple agents per application is not supported. Possible capability values: :"": Fallback to "KeyboardDisplay". :"DisplayOnly": :"DisplayYesNo": :"KeyboardOnly": :"NoInputNoOutput": :"KeyboardDisplay": Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.AlreadyExists: Examples: :bluetoothctl: > agent void UnregisterAgent(object agent) `````````````````````````````````` Unregisters an agent that has been previously registered using **RegisterAgent**. The object path parameter must match the same value that has been used on registration. Possible errors: :org.bluez.Error.DoesNotExist: void RequestDefaultAgent(object agent) `````````````````````````````````````` Requests to make the application agent the default agent. The application is required to register an agent. Special permission might be required to become the default agent. Possible errors: :org.bluez.Error.DoesNotExist: Examples: :bluetoothctl: > default-agent bluez-5.87/doc/PaxHeaders/org.bluez.MediaTransport.50000644000000000000000000000005015221755336017361 xustar0020 atime=1783093982 20 ctime=1783094112 bluez-5.87/doc/org.bluez.MediaTransport.50000644000000000000000000002043615221755336017047 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.MediaTransport" "5" "July 2024" "BlueZ" "Linux System Administration" .SH Name org.bluez.MediaTransport \- BlueZ D-Bus MediaTransport API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.MediaTransport1 .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/fd# .TP .B Used by \fBbluetoothctl\-transport(1)\fP .UNINDENT .SS Methods .SS fd, uint16, uint16 Acquire() .sp Acquire transport file descriptor and the MTU for read and write respectively. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.NotAuthorized .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > transport.acquire [transport1...] .UNINDENT .SS fd, uint16, uint16 TryAcquire() .sp Acquire transport file descriptor only if the transport is in \(dqpending\(dq state at the time the message is received by BlueZ. Otherwise no request will be sent to the remote device and the function will just fail with \fBorg.bluez.Error.NotAvailable\fP\&. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.NotAuthorized .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.NotAvailable .UNINDENT .SS void Release() .sp Releases file descriptor. .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > transport.release [transport1...] .UNINDENT .SS void Select() .sp Applicable only for transports created by a broadcast sink. This moves the transport from \(dqidle\(dq to \(dqbroadcasting\(dq. .sp This allows callers to select which BISes he wishes to sync to via a 2 step process: .INDENT 0.0 .IP 1. 3 the user calls the method, changing the transport\(aqs state to broadcasting .IP 2. 3 the audio server detects that the transport is in the \(aqbroadcasting\(aq state and automatically acquires it .UNINDENT .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.NotAuthorized .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > transport.select [transport1...] .UNINDENT .SS void Unselect() .sp Applicable only for transports created by a broadcast sink. This moves the transport from \(dqbroadcasting\(dq or \(dqactive\(dq to \(dqidle\(dq. .sp This allows callers to terminate the sync to a BIS to via a 2 step process: .INDENT 0.0 .IP 1. 3 the user calls this method, changing the transport\(aqs state to idle .IP 2. 3 the audio server detects this event and releases the transport .UNINDENT .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.NotAuthorized .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > transport.unselect [transport1...] .UNINDENT .SS Properties .SS object Device [readonly] .sp Device object which the transport is connected to. .SS string UUID [readonly] .sp UUID of the profile which the transport is for. .SS byte Codec [readonly] .sp Assigned number of codec that the transport support. .sp The values should match the profile specification which is indicated by the UUID. .SS array{byte} Configuration [readonly] .sp Configuration blob, it is used as it is so the size and byte order must match. .SS string State [readonly] .sp Indicates the state of the transport. Possible values are: .INDENT 0.0 .TP .B \(dqidle\(dq not streaming .TP .B \(dqpending\(dq streaming but not acquired .TP .B \(dqbroadcasting\(dq streaming but not acquired, applicable only for transports created by a broadcast sink .TP .B \(dqactive\(dq streaming and acquired .UNINDENT .SS uint16 Delay [readwrite, optional] .sp Transport delay in 1/10 of millisecond, this property is only writeable when the transport corresponds to a sink endpoint and it was acquired by the sender. .SS uint16 Volume [readwrite, optional] .sp Indicates volume level of the transport, this property is only writeable when the transport was acquired by the sender. .sp Possible Values: .INDENT 0.0 .TP .B 0\-127 (A2DP) .TP .B 0\-255 (BAP) .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > transport.volume [value] .UNINDENT .SS object Endpoint [readonly, optional, experimental] .sp Endpoint object which the transport is associated with. .SS uint32 Location [readonly, ISO only, experimental] .sp Indicates transport Audio Location. .SS array{byte} Metadata [readwrite, ISO Only, experimental] .sp Indicates transport Metadata. .SS array{object} Links [readonly, optional, CIS only, experimental] .sp Linked transport objects which the transport is associated with. .SS array{object} Links [readwrite, BIS only, experimental] .sp For a Broadcast Sink, the BIG sync procedure requires all desired streams to be enumerated from the start and it cannot be later reconfigured by adding or removing BISes. .sp To avoid terminating and recreating the BIG sync every time a new transport is selected for acquire, all transports selected via \fBSelect\fP need to be linked together. .sp When the first transport is acquired via Transport.Acquire, all links are included in the BIG sync command. .sp An acquired transport will create and set fds for all its links. Then, each link needs to be acquired separately, to get the fd and start receiving audio. .SS dict QoS [readwrite, optional, ISO only, experimental] .sp Only present when QoS is configured. .sp Possible values for Unicast: .INDENT 0.0 .TP .B byte CIG Indicates configured CIG. .sp Possible values: .INDENT 7.0 .TP .B 0x00 \- 0xef Valid ID range. .TP .B 0xff Auto allocate. .UNINDENT .TP .B byte CIS Indicates configured CIS. .sp Possible values: .INDENT 7.0 .TP .B 0x00 \- 0xef .UNINDENT .sp Valid ID range. .INDENT 7.0 .TP .B 0xff Auto allocate. .UNINDENT .TP .B byte Framing Indicates configured framing. .sp Possible values: .INDENT 7.0 .TP .B 0x00 Unframed. .TP .B 0x01 Framed. .UNINDENT .TP .B uint32 PresentationDelay Indicates configured transport presentation delay (us). .TP .B byte TargetLatency Indicates the requested target latency. .sp Possible values: .INDENT 7.0 .TP .B 0x01 Low Latency. .TP .B 0x02 Balanced Latency/Reliability. .TP .B 0x03 High Reliability. .UNINDENT .UNINDENT .sp Possible values for Broadcast: .INDENT 0.0 .TP .B byte BIG Indicates configured QoS BIG. .TP .B byte BIS Indicates configured BIS. .TP .B byte SyncFactor Indicates configured broadcast sync factor. .TP .B byte Packing Indicates configured packing. .TP .B byte Framing Indicates configured framing. .TP .B array{byte} BCode Indicates the string used for encryption/decryption. .TP .B byte encryption Indicates if the stream is encrypted. .TP .B byte Options Indicates configured broadcast options. .TP .B uint16 Skip Indicates configured broadcast skip. .TP .B byte SyncTimeout Indicates configured broadcast sync timeout. .TP .B byte SyncType Indicates configured broadcast sync CTE type. .TP .B byte MSE Indicates configured broadcast MSE. .TP .B uint16 Timeout Indicates configured broadcast timeout. .UNINDENT .sp Possible values for both Unicast and Broadcast: .INDENT 0.0 .TP .B uint32 Interval Indicates configured ISO interval (us). .TP .B uint16 Latency Indicates configured transport latency (ms). .TP .B uint16 SDU Indicates configured maximum SDU. .TP .B byte PHY Indicates configured PHY. .sp Possible values: .INDENT 7.0 .TP .B bit 0 LE 1M .TP .B bit 1 LE 2M .TP .B bit 2 LE Coded .UNINDENT .TP .B byte Retransmissions Indicates configured retransmissions. .UNINDENT .SS boolean Desynchronized [readwrite, optional, CIS only, testing, experimental] .sp Available only when D\-Bus testing interfaces are enabled and the transport has the \fBLinks\fP property. .sp Controls whether linked transports are acquired together. .sp When set to False (default), acquiring one linked transport automatically acquires all links. .sp When set to True, links are not acquired automatically and each link must be acquired separately. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/rctest.10000644000000000000000000000005015221755432014014 xustar0020 atime=1783094042 20 ctime=1783094113 bluez-5.87/doc/rctest.10000644000000000000000000000454315221755432013503 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "rctest" "1" "Jul 6, 2009" "BlueZ" "Linux System Administration" .SH Name rctest \- RFCOMM testing .SH SYNOPSIS .sp \fBrctest\fP <\fIMODE\fP> [\fIOPTIONS\fP] [\fIbdaddr\fP] .SH DESCRIPTION .sp \fBrctest(1)\fP is used to test RFCOMM communications on the BlueZ stack .SH MODES .INDENT 0.0 .TP .B \-r listen and receive .TP .B \-w listen and send .TP .B \-d listen and dump incoming data .TP .B \-s connect and send .TP .B \-u connect and receive .TP .B \-n connect and be silent .TP .B \-c connect, disconnect, connect, ... .TP .B \-m multiple connects .UNINDENT .SH OPTIONS .INDENT 0.0 .TP .BI \-b \ bytes send/receive bytes .TP .BI \-i \ device select the specified device .TP .BI \-P \ channel select the specified channel .TP .BI \-U \ uuid select the specified uuid .TP .BI \-L \ seconds enable SO_LINGER options for seconds .TP .BI \-W \ seconds enable deferred setup for seconds .TP .BI \-B \ filename use data packets from filename .TP .BI \-N \ num send num frames .TP .BI \-C \ num send num frames before delay (default: 1) .TP .BI \-D \ milliseconds delay milliseconds after sending num frames (default: 0) .TP .B \-A request authentication .TP .B \-E request encryption .TP .B \-S secure connection .TP .B \-M become central .TP .B \-T enable timestamps .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Author Maxim Krasnyansky , Marcel Holtmann , Filippo Giunchedi .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.obex.Agent.50000644000000000000000000000005015221755407016416 xustar0020 atime=1783094023 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.Agent.50000644000000000000000000000374515221755407016110 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.obex.Agent" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.obex.Agent \- BlueZ D-Bus OBEX Agent API documentation .SH Interface .INDENT 0.0 .TP .B Service unique name .TP .B Interface org.bluez.obex.Agent1 .TP .B Object path freely definable .UNINDENT .SS Methods .SS void Release() .sp This method gets called when \fBobexd(8)\fP daemon unregisters the agent. .sp An agent can use it to do cleanup tasks. There is no need to unregister the agent, because when this method gets called it has already been unregistered. .SS string AuthorizePush(object transfer) .sp This method gets called when the \fBobexd(8)\fP needs to accept/reject a Bluetooth object push request. .sp Returns the full path (including the filename) or the folder name suffixed with \(aq/\(aq where the object shall be stored. .sp The transfer object, see \fBorg.bluez.obex.Transfer(5)\fP will contain a Filename property that contains the default location and name that can be returned. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.Rejected .TP .B org.bluez.obex.Error.Canceled .UNINDENT .SS void Cancel() .sp This method gets called to indicate that the agent request failed before a reply was returned. It cancels the previous request. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/btmon-l2cap.rst0000644000000000000000000000005015200607060015264 xustar0020 atime=1783092683 20 ctime=1783094112 bluez-5.87/doc/btmon-l2cap.rst0000644000000000000000000001726115200607060014754 0ustar00rootroot.. This file is included by btmon.rst. L2CAP CHANNEL TRACKING ======================= L2CAP (Logical Link Control and Adaptation Protocol) multiplexes multiple logical channels over a single ACL connection. btmon decodes L2CAP signaling automatically and routes data to higher-layer protocol decoders based on the channel. Fixed Channels -------------- Fixed channels have pre-assigned Channel Identifiers (CIDs) and do not require signaling to establish: .. list-table:: :header-rows: 1 :widths: 10 30 60 * - CID - Protocol - Description * - 0x0001 - L2CAP Signaling (BR/EDR) - Channel management for classic connections * - 0x0002 - Connectionless Reception - Connectionless L2CAP data * - 0x0003 - AMP Manager - AMP (Alternate MAC/PHY) control * - 0x0004 - ATT - Attribute Protocol (GATT operations) * - 0x0005 - L2CAP Signaling (LE) - Channel management for LE connections * - 0x0006 - SMP (LE) - Security Manager Protocol * - 0x0007 - SMP (BR/EDR) - Security Manager over classic transport In btmon output, fixed channel traffic is decoded directly without any L2CAP signaling preamble. For example, ATT on CID 0x0004 appears as:: < ACL Data TX: Handle 2048 flags 0x00 dlen 7 #494 [hci0] 0.004488 ATT: Exchange MTU Request (0x02) len 2 Client RX MTU: 517 Dynamic Channels (BR/EDR) -------------------------- Classic Bluetooth uses L2CAP signaling on CID 0x0001 to establish dynamic channels. Each channel is identified by a PSM (Protocol/Service Multiplexer) that determines which protocol runs on it. **Channel establishment**:: > ACL Data RX: Handle 256 flags 0x02 dlen 16 #142 [hci0] 2.034556 L2CAP: Connection Request (0x02) ident 3 len 4 PSM: 25 (0x0019) Source CID: 0x0040 < ACL Data TX: Handle 256 flags 0x00 dlen 20 #144 [hci0] 2.035002 L2CAP: Connection Response (0x03) ident 3 len 8 Destination CID: 0x0041 Source CID: 0x0040 Result: Connection successful (0x0000) Status: No further information available (0x0000) After connection, configuration is exchanged:: > ACL Data RX: Handle 256 flags 0x02 dlen 20 #146 [hci0] 2.035556 L2CAP: Configure Request (0x04) ident 4 len 8 Destination CID: 0x0041 Flags: 0x0000 Option: MTU (0x01) [2] MTU: 1024 < ACL Data TX: Handle 256 flags 0x00 dlen 18 #148 [hci0] 2.036003 L2CAP: Configure Response (0x05) ident 4 len 6 Source CID: 0x0040 Flags: 0x0000 Result: Success (0x0000) Common PSM-to-protocol mappings: .. list-table:: :header-rows: 1 :widths: 12 25 63 * - PSM - Protocol - Description * - 0x0001 - SDP - Service Discovery Protocol * - 0x0003 - RFCOMM - Serial port emulation (SPP, HFP, etc.) * - 0x000f - BNEP - Bluetooth Network Encapsulation Protocol * - 0x0017 - AVCTP - Audio/Video Control Transport (AVRCP) * - 0x0019 - AVDTP - Audio/Video Distribution Transport (A2DP) * - 0x001b - AVCTP Browsing - AVRCP browsing channel * - 0x001f - ATT (BR/EDR) - Attribute Protocol over classic transport * - 0x0027 - EATT - Enhanced Attribute Protocol LE Credit-Based Channels -------------------------- LE connections use L2CAP signaling on CID 0x0005 for dynamic channels. The LE Credit Based Connection mechanism provides flow control:: < ACL Data TX: Handle 2048 flags 0x00 dlen 18 #600 [hci0] 1.824003 LE L2CAP: LE Connection Request (0x14) ident 1 len 10 PSM: 39 (0x0027) Source CID: 0x0040 MTU: 517 MPS: 251 Credits: 10 > ACL Data RX: Handle 2048 flags 0x02 dlen 18 #602 [hci0] 1.886556 LE L2CAP: LE Connection Response (0x15) ident 1 len 10 Destination CID: 0x0041 MTU: 517 MPS: 251 Credits: 10 Result: Connection successful (0x0000) EATT (Enhanced ATT) uses PSM 0x0027 over LE Credit-Based channels to provide multiple parallel ATT bearers. Connection Parameter Updates ----------------------------- LE peripherals frequently request connection parameter changes via L2CAP signaling:: < ACL Data TX: Handle 2048 flags 0x00 dlen 16 #493 [hci0] 0.003915 LE L2CAP: Connection Parameter Update Request (0x12) ident 1 len 8 Min interval: 24 Max interval: 40 Peripheral latency: 0 Timeout multiplier: 256 > ACL Data RX: Handle 2048 flags 0x02 dlen 10 #495 [hci0] 0.066003 LE L2CAP: Connection Parameter Update Response (0x13) ident 1 len 2 Result: Connection Parameters accepted (0x0000) A result of ``Connection Parameters rejected (0x0001)`` means the central denied the request. Automating L2CAP Analysis -------------------------- **Find all L2CAP channel establishments**:: grep -n "Connection Request\|Connection Response\|LE Connection Request\|LE Connection Response" output.txt **Track PSM usage** (identifies which protocols are active):: grep -n "PSM:" output.txt **Find connection parameter update issues**:: grep -n "Parameter Update Request\|Parameter Update Response\|Parameters rejected" output.txt **Find EATT channel setup**:: grep -n "PSM: 39\|Enhanced Credit" output.txt **Trace a specific L2CAP channel**: To follow traffic on a dynamic channel, note the Source CID and Destination CID from the Connection Request/Response pair. Then search for those CIDs in subsequent data frames. Throughput Estimation ---------------------- btmon's ``--analyze`` (``-a``) mode computes per-channel statistics including throughput. For each L2CAP channel it reports: - **Speed**: Computed as ``bytes * 8 / latency_sum_ms`` where ``latency_sum_ms`` is the sum of inter-packet deltas (not wall-clock duration). This means idle gaps are excluded, so the figure represents the *active sending rate* rather than application-level throughput. - **Min/Avg/Max latency**: Per-packet inter-arrival time range. Example output:: Found TX L2CAP channel with CID 64 PSM 128 (0x0080) Mode: LE Credit MTU: 672 MPS: 490 TX packets: 29120/29114 TX Latency: 1-79 msec (~29 msec) TX size: 494-494 octets (~494 octets) TX speed: ~571 Kb/s **Channel details shown in analyze mode:** - **Fixed channels** (CID <= 7) display their protocol name (e.g., ``ATT``, ``L2CAP Signaling (LE)``). - **PSM** is shown in both decimal and hexadecimal. - **Mode** is decoded from Configure Request options (BR/EDR) or LE signaling (``Basic``, ``ERTM``, ``LE Credit``, ``Enhanced Credit``, etc.). - **MTU** and **MPS** are extracted from signaling exchanges. **Throughput caveats:** 1. The speed value uses inter-packet latency sums as the denominator, not wall-clock elapsed time. If the sender pauses (e.g., waiting for credits), the idle period is not counted, which inflates the reported speed relative to overall throughput. 2. TX latency is measured from command submission to completion event. RX latency is the inter-arrival time between consecutive packets. These measure different things, so TX and RX speeds for the same channel are not directly comparable. 3. For windowed throughput (min/avg/max), btsnoop-analyzer uses 1-second sampling windows over wall-clock time, which provides a more realistic view of application-level bandwidth variation. **Automated throughput extraction** from btmon analyze output:: btmon -a trace.btsnoop 2>/dev/null | grep -E "speed:|Mode:|MTU:|MPS:|PSM" bluez-5.87/doc/PaxHeaders/org.bluez.obex.Transfer.50000644000000000000000000000005015221755367017151 xustar0020 atime=1783094007 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.Transfer.50000644000000000000000000000625415221755367016641 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.obex.Transfer" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.obex.Transfer \- BlueZ D-Bus OBEX Transfer API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez.obex .TP .B Interface org.bluez.obex.Transfer1 .TP .B Object path [Session object path]/transfer{#} .UNINDENT .SS Methods .SS void Cancel() .sp Cancels the current transference. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.NotAuthorized .TP .B org.bluez.obex.Error.InProgress .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS void Suspend() .sp Suspends transference. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.NotAuthorized .TP .B org.bluez.obex.Error.NotInProgress If transfer is still in with \fBStatus\fP \fB\(dqqueued\(dq\fP\&. .UNINDENT .SS void Resume() .sp Resumes transference previously suspended with use of \fBSuspend()\fP method. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.NotAuthorized .TP .B org.bluez.obex.Error.NotInProgress If transfer is still in with \fBStatus\fP \fB\(dqqueued\(dq\fP\&. .UNINDENT .SS Properties .SS string Status [readonly] .sp Indicates the current status of the transfer. .sp Possible values: .INDENT 0.0 .INDENT 3.5 .INDENT 0.0 .TP .B \(dqqueued\(dq .TP .B \(dqactive\(dq .TP .B \(dqsuspended\(dq .TP .B \(dqcomplete\(dq .TP .B \(dqerror\(dq .UNINDENT .UNINDENT .UNINDENT .SS object Session [readonly] .sp The object path of the session the transfer belongs to. .SS string Name [readonly, optional] .sp Name of the object being transferred. .sp Either Name or Type or both will be present. .SS string Type [readonly, optional] .sp Type of the object transferred being transferred. .sp Either Name or Type or both will be present. .SS uint64 Time [readonly, optional] .sp Time of the object being transferred if this is provided by the remote party. .SS uint64 Size [readonly, optional] .sp Size of the object being transferred. .sp If the size is unknown, then this property will not be present. .SS uint64 Transferred [readonly, optional] .sp Number of bytes transferred. .sp For transfers with \fBStatus\fP set to \fB\(dqqueued\(dq\fP, this value will not be present. .SS string Filename [readonly, optional] .sp Complete name of the file being received or sent. .sp For incoming object push transaction, this will be the proposed default location and name. It can be overwritten by the \fBAuthorizePush()\fP in \fBorg.bluez.obex.Agent(5)\fP and will be then updated accordingly. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/test-runner.rst0000644000000000000000000000005015221752714015446 xustar0020 atime=1783092684 20 ctime=1783094111 bluez-5.87/doc/test-runner.rst0000644000000000000000000001412715221752714015134 0ustar00rootroot=========== test-runner =========== **test-runner** [*OPTIONS*] -- DESCRIPTION =========== **test-runner(1)** is used to test Kernel changes to the Bluetooth subsystem, it lunches a virtual machine using qemu(1) and mounts the local filesystem using virtio (9p). OPTIONS ======= :-a:--auto: Find tests and run them :-b/--dbus: Start D-Bus system daemon :-s/--dbus-session: Start D-Bus session daemon :-d/--daemon: Start bluetoothd :-m/--monitor: Start btmon :-l/--emulator[=num]: Start btvirt :-A/-audio[=path]: Start audio server :-u/--unix[=path]: Provide serial device :-U/--usb=: Provide USB device :-q/--qemu=: QEMU binary :-k/--kernel=: Kernel image (bzImage) :-h/--help: Show help options Kernel ====== The test-runner tool requires a kernel that is at least build with these minimal options for a successful boot. These options should be installed as .config in the kernel source directory followed by: .. code-block:: make olddefconfig After that a default kernel with the required options can be built. More option (like the Bluetooth subsystem) can be enabled on top of this. .. code-block:: CONFIG_VIRTIO=y CONFIG_VIRTIO_PCI=y CONFIG_VIRTIO_CONSOLE=y CONFIG_NET=y CONFIG_INET=y CONFIG_NET_9P=y CONFIG_NET_9P_VIRTIO=y CONFIG_9P_FS=y CONFIG_9P_FS_POSIX_ACL=y CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_PCI=yCONFIG_DEBUG_KERNEL=y CONFIG_SERIAL_8250_NR_UARTS=4 CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y CONFIG_TMPFS_XATTR=y CONFIG_DEVTMPFS=y CONFIG_DEBUG_FS=y Bluetooth --------- .. code-block:: CONFIG_BT=y CONFIG_BT_BREDR=y CONFIG_BT_RFCOMM=y CONFIG_BT_BNEP=y CONFIG_BT_HIDP=y CONFIG_BT_LE=y CONFIG_BT_HCIUART=y CONFIG_BT_HCIUART_H4=y CONFIG_BT_HCIVHCI=y CONFIG_CRYPTO_AES=y CONFIG_CRYPTO_CMAC=y CONFIG_CRYPTO_USER_API=y CONFIG_CRYPTO_USER_API_HASH=y CONFIG_CRYPTO_USER_API_SKCIPHER=y CONFIG_UNIX=y CONFIG_UHID=y For 6lowpan-tester, the following are required: .. code-block:: CONFIG_6LOWPAN=y CONFIG_6LOWPAN_DEBUGFS=y CONFIG_BT_6LOWPAN=y CONFIG_PACKET=y Lock debugging -------------- To catch locking related issues the following set of kernel config options may be useful: .. code-block:: CONFIG_DEBUG_KERNEL=y CONFIG_LOCKDEP_SUPPORT=y CONFIG_DEBUG_SPINLOCK=y CONFIG_DEBUG_LOCK_ALLOC=y CONFIG_DEBUG_ATOMIC_SLEEP=y CONFIG_PROVE_LOCKING=y CONFIG_PROVE_RCU=y CONFIG_LOCKDEP=y CONFIG_DEBUG_MUTEXES=y CONFIG_KASAN=y Other ----- For tests requiring accurate time inside the VM, possible with KVM: .. code-block:: CONFIG_HYPERVISOR_GUEST=y CONFIG_PARAVIRT=y CONFIG_KVM_GUEST=y CONFIG_PTP_1588_CLOCK=y CONFIG_PTP_1588_CLOCK_KVM=y CONFIG_PTP_1588_CLOCK_VMCLOCK=y EXAMPLES ======== Running mgmt-tester ------------------- .. code-block:: $ tools/test-runner -k /pathto/bzImage -- tools/mgmt-tester Running a specific test of mgmt-tester -------------------------------------- .. code-block:: $ tools/test-runner -k /pathto/bzImage -- tools/mgmt-tester -s "" Running bluetoothctl with emulated controller --------------------------------------------- .. code-block:: $ tools/test-runner -l -d -k /pathto/bzImage -- client/bluetoothctl [CHG] Controller 00:AA:01:00:00:00 Pairable: yes [bluetooth]# Running bluetoothctl with 2 emulated controller ------------------------------------------------ .. code-block:: $ tools/test-runner -l2 -d -k /pathto/bzImage -- client/bluetoothctl [CHG] Controller 00:AA:01:01:00:01 Pairable: yes [CHG] Controller 00:AA:01:00:00:00 Pairable: yes [bluetooth]# Running bluetoothctl with emulated controller and audio support --------------------------------------------------------------- .. code-block:: $ tools/test-runner -l -d -A -k /pathto/bzImage -- client/bluetoothctl [CHG] Controller 00:AA:01:00:00:00 Pairable: yes [bluetooth]# [CHG] Controller 00:AA:01:00:00:00 Pairable: yes [CHG] Controller 00:AA:01:00:00:00 Class: 0x00600000 (6291456) [CHG] Controller 00:AA:01:00:00:00 UUIDs: 0000110e-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 0000111f-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 00001200-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 0000110b-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 0000110a-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 0000110c-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 00001800-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 00001801-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 0000180a-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 0000111e-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 Class: 0x006c0000 (7077888) [CHG] Controller 00:AA:01:00:00:00 UUIDs: 0000110e-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 0000111f-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 00001200-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 0000110b-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 0000110a-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 0000110c-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 00001800-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 00001801-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 0000180a-0000-1000-8000-00805f9b34fb [CHG] Controller 00:AA:01:00:00:00 UUIDs: 0000111e-0000-1000-8000-00805f9b34fb Running shell with host controller using btproxy ------------------------------------------------ .. code-block:: $ tools/btproxy -u [1] $ tools/test-runner -u -d -k /pathto/bzImage -- /bin/bash [2] Running shell with host controller USB-passthrough -------------------------------------------------- In addition the above kernel config option the following is required: .. code-block:: CONFIG_USB=y CONFIG_USB_XHCI_HCD=y CONFIG_USB_XHCI_PLATFORM=y .. code-block:: $ tools/test-runner -U "usb-host,vendorid=<0xxxxx>,productid=<0xxxxx>" \ -d -k /pathto/bzImage -- /bin/bash bluez-5.87/doc/PaxHeaders/org.bluez.obex.Synchronization.50000644000000000000000000000005015221755375020565 xustar0020 atime=1783094013 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.Synchronization.50000644000000000000000000000545215221755375020254 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.obex.Synchronization" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.obex.Synchronization \- BlueZ D-Bus OBEX Synchronization API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez.obex .TP .B Interface org.bluez.obex.Synchronization1 .TP .B Object path [Session object path] .UNINDENT .SS Methods .SS void SetLocation(string location) .sp Sets the phonebook object store location for other operations. Should be called before all the other operations. .sp Possible location: .INDENT 0.0 .TP .B \(dqint\(dq ( \(dqinternal\(dq which is default ) Store in the interval memory. .TP .B \(dqsim{#}\(dq Store in sim card number #. .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .UNINDENT .SS object, dict GetPhonebook(string targetfile) .sp Retrieves an entire Phonebook Object store from remote device, and stores it in a local file. .sp If an empty target file is given, a name will be automatically calculated for the temporary file. .sp The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. .sp The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see \fBorg.bluez.obex.Transfer(5)\fP for the possible list of properties. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS object, dict PutPhonebook(string sourcefile) .sp Sends an entire Phonebook Object store to remote device. .sp The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. .sp The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see \fBorg.bluez.obex.Transfer(5)\fP for the possible list of properties. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.AdvertisementMonitor.50000644000000000000000000000005015221755362020606 xustar0020 atime=1783094002 20 ctime=1783094112 bluez-5.87/doc/org.bluez.AdvertisementMonitor.50000644000000000000000000001236415221755362020275 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.AdvertisementMonitor" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.AdvertisementMonitor \- BlueZ D-Bus AdvertisementMonitor API documentation .SH Description .sp This API allows an client to specify a job of monitoring advertisements by registering the root of hierarchy and then exposing advertisement monitors under the root with filtering conditions, thresholds of RSSI and timers of RSSI thresholds. .sp Once a monitoring job is activated by \fBbluetoothd(8)\fP, the client can expect to get notified on the targeted advertisements no matter if there is an ongoing discovery session (see \fBStartDiscovery()\fP in \fBorg.bluez.Adapter(5)\fP). .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.AdvertisementMonitor1 [experimental] .TP .B Object path freely definable .TP .B Used by \fBbluetoothctl\-monitor(1)\fP .UNINDENT .SS Methods .SS void Release() [noreply] .sp This gets called as a signal for a client to perform clean\-up when: .INDENT 0.0 .IP \(bu 2 Monitor cannot be activated after it was exposed .IP \(bu 2 Monitor has been deactivated. .UNINDENT .SS void Activate() [noreply] .sp After a monitor was exposed, this gets called as a signal for client to get acknowledged when a monitor has been activated, so the client can expect to receive calls on \fBDeviceFound()\fP or \fBDeviceLost()\fP\&. .SS void DeviceFound(object device) [noreply] .sp This gets called to notify the client of finding the targeted device. .sp Once receiving the call, the client should start to monitor the corresponding device to retrieve the changes on RSSI and advertisement content. .SS void DeviceLost(object device) [noreply] .sp This gets called to notify the client of losing the targeted device. .sp Once receiving this call, the client should stop monitoring the corresponding device. .SS Properties .SS string Type [read\-only] .sp The type of the monitor. See \fBSupportedMonitorTypes\fP in \fBorg.bluez.AdvertisementMonitorManager(5)\fP for the available options. .SS int16 RSSILowThreshold [read\-only, optional] .sp Used in conjunction with \fBRSSILowTimeout\fP to determine whether a device becomes out\-of\-range. .sp Valid range is \-127 to 20 (dBm), while 127 indicates unset. .SS int16 RSSIHighThreshold [read\-only, optional] .sp Used in conjunction with RSSIHighTimeout to determine whether a device becomes in\-range. .sp Valid range is \-127 to 20 (dBm), while 127 indicates unset. .SS uint16 RSSILowTimeout [read\-only, optional] .sp The time it takes to consider a device as out\-of\-range. If this many seconds elapses without receiving any signal at least as strong as \fBRSSILowThreshold\fP, a currently in\-range device will be considered as out\-of\-range (lost). Valid range is 1 to 300 (seconds), while 0 indicates unset. .SS uint16 RSSIHighTimeout [read\-only, optional] .sp The time it takes to consider a device as in\-range. If this many seconds elapses while we continuously receive signals at least as strong as \fBRSSIHighThreshold\fP, a currently out\-of\-range device will be considered as in\-range (found). .sp Valid range is 1 to 300 (seconds), while 0 indicates unset. .SS uint16 RSSISamplingPeriod [read\-only, optional] .sp Grouping rules on how to propagate the received advertisement packets to the client. .sp Possible values: .INDENT 0.0 .TP .B 0 All advertisement packets from in\-range devices would be propagated. .TP .B 255 Only the first advertisement packet of in\-range devices would be propagated. If the device becomes lost, then the first packet when it is found again will also be propagated. .TP .B 1 to 254 Advertisement packets would be grouped into 100ms * N time period. Packets in the same group will only be reported once, with the RSSI value being averaged out. .sp Currently this is unimplemented in user space, so the value is only used to be forwarded to the kernel. .UNINDENT .SS array{(uint8, uint8, array{byte})} Patterns [read\-only, optional] .sp If the \fBType\fP property is set to \fB\(dqor_patterns\(dq\fP, then this property must exist and have at least one entry in the array. .sp The structure of a pattern contains the following: .INDENT 0.0 .TP .B uint8 start_position The index in an AD data field where the search should start. The beginning of an AD data field is index 0. .TP .B uint8 AD_data_type See \% generic\-access\-profile/ for the possible allowed value. .TP .B array{byte} content_of_pattern This is the value of the pattern. The maximum length of the bytes is 31. .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.GattService.50000644000000000000000000000005015221755346016646 xustar0020 atime=1783093990 20 ctime=1783094112 bluez-5.87/doc/org.bluez.GattService.50000644000000000000000000000451715221755346016336 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.GattService" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.GattService \- BlueZ D-Bus GattService API documentation .SH Description .sp GATT local/server and remote/client services share the same high\-level D\-Bus API. .sp Local/Server refers to GATT based service exported by a plugin or an external application. .sp Remote/Client refers to GATT services exported by the peer. .SH Interface .SS Client .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.GattService1 .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/service# .TP .B Used by \fBbluetoothctl\-gatt(1)\fP .UNINDENT .SS Server .INDENT 0.0 .TP .B Service unique name .TP .B Interface org.bluez.GattService1 .TP .B Object path freely definable .UNINDENT .SS Properties .SS string UUID [read\-only] .sp 128\-bit service UUID. .SS boolean Primary [read\-only] .sp Indicates whether or not this GATT service is a primary service. If false, the service is secondary. .SS object Device [read\-only, optional] .sp Object path of the Bluetooth device the service belongs to. Only present on services from remote devices. .SS array{object} Includes [read\-only, optional] .sp Array of object paths representing the included services of this service. .SS uint16 Handle [read\-only] (client only) .sp Service handle. .SS uint16 Handle [read\-write, optional] (Server Only) .sp Service handle. When available in the server it would attempt to use to allocate into the database which may fail, to auto allocate the value 0x0000 shall be used which will cause the allocated handle to be set once registered. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.MediaEndpoint.rst0000644000000000000000000000005015142177620017605 xustar0020 atime=1783093980 20 ctime=1783094111 bluez-5.87/doc/org.bluez.MediaEndpoint.rst0000644000000000000000000002252015142177620017267 0ustar00rootroot======================= org.bluez.MediaEndpoint ======================= ------------------------------------------- BlueZ D-Bus MediaEndpoint API documentation ------------------------------------------- :Version: BlueZ :Date: September 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: unique name (Server role) org.bluez (Client role) :Interface: org.bluez.MediaEndpoint1 :Object path: freely definable (Server role) [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/sep# (Client role) :Used by: **bluetoothctl-endpoint(1)** Methods ------- .. _SetConfiguration: void SetConfiguration(object transport, dict properties) ```````````````````````````````````````````````````````` Set configuration for the transport. :object transport: Configured transport object. :dict properties: Configured **org.bluez.MediaTransport(5)** properties. For client role transport must be set with a server endpoint object which will be configured and the properties must contain the following properties: :array{byte} Capabilities [Mandatory]: See Capabilities property. :array{byte} Metadata [ISO only]: See Metadata property. :dict QoS [ISO only]: See **org.bluez.MediaTransport(5)** QoS property. Possible errors for A2DP endpoints: :org.bluez.Error.A2DP.InvalidCodecType: :org.bluez.Error.A2DP.NotSupportedCodecType: :org.bluez.Error.A2DP.InvalidSamplingFrequency: :org.bluez.Error.A2DP.NotSupportedSamplingFrequency: :org.bluez.Error.A2DP.InvalidChannelMode: :org.bluez.Error.A2DP.NotSupportedChannelMode: :org.bluez.Error.A2DP.InvalidSubbands: :org.bluez.Error.A2DP.NotSupportedSubbands: :org.bluez.Error.A2DP.InvalidAllocationMethod: :org.bluez.Error.A2DP.NotSupportedAllocationMethod: :org.bluez.Error.A2DP.InvalidMinimumBitpoolValue: :org.bluez.Error.A2DP.NotSupportedMinimumBitpoolValue: :org.bluez.Error.A2DP.InvalidMaximumBitpoolValue: :org.bluez.Error.A2DP.NotSupportedMaximumBitpoolValue: :org.bluez.Error.A2DP.InvalidLayer: :org.bluez.Error.A2DP.NotSupportedLayer: :org.bluez.Error.A2DP.NotSupportedCRC: :org.bluez.Error.A2DP.NotSupportedMPF: :org.bluez.Error.A2DP.NotSupportedVBR: :org.bluez.Error.A2DP.InvalidBitRate: :org.bluez.Error.A2DP.NotSupportedBitRate: :org.bluez.Error.A2DP.InvalidObjectType: :org.bluez.Error.A2DP.NotSupportedObjectType: :org.bluez.Error.A2DP.InvalidChannels: :org.bluez.Error.A2DP.NotSupportedChannels: :org.bluez.Error.A2DP.InvalidVersion: :org.bluez.Error.A2DP.NotSupportedVersion: :org.bluez.Error.A2DP.NotSupportedMaximumSUL: :org.bluez.Error.A2DP.InvalidBlockLength: :org.bluez.Error.A2DP.InvalidCPType: :org.bluez.Error.A2DP.InvalidCPFormat: :org.bluez.Error.A2DP.InvalidCodecParameter: :org.bluez.Error.A2DP.NotSupportedCodecParameter: :org.bluez.Error.A2DP.InvalidDRC: :org.bluez.Error.A2DP.NotSupportedDRC: Examples: :bluetoothctl: > endpoint.config [preset] array{byte} SelectConfiguration(array{byte} capabilities) ````````````````````````````````````````````````````````` Select preferable configuration from the supported capabilities. Returns a configuration which can be used to setup a transport, see **org.bluez.MediaTransport(5)** for possible values. Note: There is no need to cache the selected configuration since on success the configuration is send back as parameter of SetConfiguration. dict SelectProperties(dict capabilities) ```````````````````````````````````````` Select BAP unicast configuration from the supported capabilities: :object Endpoint: :array{byte} Capabilities: :array{byte} Metadata: :uint32 Locations: :uint32_t ChannelAllocation: :dict QoS: :byte Framing: :byte PHY: :uint16 MaximumLatency: :uint32 MinimumDelay: :uint32 MaximumDelay: :uint32 PreferredMinimumDelay: :uint32 PreferredMaximumDelay: See `MediaEndpoint Properties`_ for their possible values. Returns a configuration which can be used to setup a transport: :array{byte} Capabilities: :array{byte} Metadata [optional]: :dict QoS: See `SetConfiguration`_ for their possible values. Note: There is no need to cache the selected properties since on success the configuration is send back as parameter of SetConfiguration. void ClearConfiguration(object transport) ````````````````````````````````````````` Clear transport configuration. **Server role:** [ISO only] Close the stream associated with the given transport. If the path given is the path of this endpoint, all its streams are closed. void Reconfigure(dict properties) ````````````````````````````````` [ISO only] Reconfigure a BAP unicast endpoint. This closes all existing streams of the endpoint, and restarts the configuration selection flow which e.g. triggers calls to *SelectProperties* allowing the sound server to modify the configuration. The following arguments are taken in *properties*: :boolean Defer [optional]: If True, mark endpoint for reconfiguration, but postpone it until a non-deferred *Reconfigure()* operation is made on an endpoint of the same device. This is necessary to use when reconfiguring source and sink streams with the intention that they be combined into the same CIG, possibly forming bidirectional CIS. void Release() `````````````` This method gets called when **bluetoothd(8)** unregisters the endpoint. An endpoint can use it to do cleanup tasks. There is no need to unregister the endpoint, because when this method gets called it has already been unregistered. MediaEndpoint Properties ------------------------ string UUID [readonly, optional] ```````````````````````````````` UUID of the profile which the endpoint is for. byte Codec [readonly, optional] ``````````````````````````````` Assigned number of codec that the endpoint implements. The values should match the profile specification which is indicated by the UUID. uint32_t Vendor [readonly, Optional] ```````````````````````````````````` Vendor-specific Company ID, Codec ID tuple that the endpoint implements. It shall be set to appropriate value when Vendor Specific Codec (0xff) is used. array{byte} Capabilities [readonly, optional] ````````````````````````````````````````````` Capabilities blob, it is used as it is so the size and byte order must match. array{byte} Metadata [readonly, Optional] ````````````````````````````````````````` Metadata blob, it is used as it is so the size and byte order must match. object Device [readonly, optional] `````````````````````````````````` Device object which the endpoint is belongs to. bool DelayReporting [readonly, optional] ```````````````````````````````````````` Indicates if endpoint supports Delay Reporting. uint32 Locations [readonly, optional, ISO only, experimental] ````````````````````````````````````````````````````````````` Indicates endpoint supported locations. uint16 SupportedContext [readonly, optional, ISO only, experimental] ```````````````````````````````````````````````````````````````````` Indicates endpoint supported audio context. uint16 Context [readonly, optional, ISO only, experimental] ``````````````````````````````````````````````````````````` Indicates endpoint available audio context. dict QoS [readonly, optional, ISO only, experimental] ````````````````````````````````````````````````````` Indicates QoS capabilities. :byte Framing: Indicates endpoint support framing. Possible Values: :0x00: Unframed PDUs supported. :0x01: Unframed PDUs not supported. :byte PHY: Indicates endpoint preferred PHY. Possible values: :bit 0: LE 1M preferred. :bit 1: LE 2M preferred. :bit 2: LE Coded preferred. :byte Retransmissions: Indicates endpoint preferred number of retransmissions. :uint16 MaximumLatency: Indicates endpoint maximum latency. :uint32 MinimumDelay: Indicates endpoint minimum presentation delay. :uint32 MaximumDelay: Indicates endpoint maximum presentation delay. :uint32 PreferredMinimumDelay: Indicates endpoint preferred minimum presentation delay. :uint32 PreferredMaximumDelay: Indicates endpoint preferred maximum presentation delay. dict SupportedFeatures [readonly, ISO only, experimental] ````````````````````````````````````````````````````````` Dictionary representing supported features for different services. Keys are service UUIDs, values arrays of strings. Services: :array{string} 00001855-0000-1000-8000-00805f9b34fb: TMAP Features. Possible items: :"cg": Call Gateway :"ct": Call Terminal :"ums": Unicast Media Sender :"umr": Unicast Media Receiver :"bms": Broadcast Media Sender :"bmr": Broadcast Media Receiver :array{string} 00001858-0000-1000-8000-00805f9b34fb: GMAP Features. Possible items: :"ugg": Unicast Game Gateway :"ugt": Unicast Game Terminal :"bgs": Broadcast Game Sender :"bgr": Broadcast Game Receiver :"ugg-multiplex": UGG Multiplex feature support :"ugg-96kbps-source": UGG 96 kbps Source feature support :"ugg-multisink": UGG Multisink feature support :"ugt-source": UGT Source feature support :"ugt-80kbps-source": UGT 80 kbps Source feature support :"ugt-sink": UGT Sink feature support :"ugt-64kbps-sink": UGT 64 kbps Sink feature support :"ugt-multiplex": UGT Multiplex feature support :"ugt-multisink": UGT Multisink feature support :"ugt-multisource": UGT Multisource feature support :"bgs-96kbps": BGS 96 kbps feature support :"bgr-multisink": BGR Multisink feature support :"bgr-multiplex": BGR Multiplex feature support bluez-5.87/doc/PaxHeaders/org.bluez.LEAdvertisingManager.50000644000000000000000000000005015221755354020420 xustar0020 atime=1783093996 20 ctime=1783094112 bluez-5.87/doc/org.bluez.LEAdvertisingManager.50000644000000000000000000000764515221755354020115 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.LEAdvertisingManager" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.LEAdvertisingManager \- BlueZ D-Bus LEAvertisingManager API documentation .SH Interface .sp The Advertising Manager allows external applications to register Advertisement Data which should be broadcast to devices. Advertisement Data elements must follow the API for LE Advertisement Data described above. .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.LEAdvertisingManager1 .TP .B Object path /org/bluez/{hci0,hci1,...} .TP .B Used by \fBbluetoothctl(1)\fP .UNINDENT .SS Methods .SS void RegisterAdvertisement(object advertisement, dict options) .sp Registers an advertisement object to be sent over the LE Advertising channel. .sp The service must implement \fBorg.bluez.LEAdvertisement(5)\fP interface. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments Indicates that the object has invalid or conflicting properties. .TP .B org.bluez.Error.AlreadyExists Indicates the object is already registered. .TP .B org.bluez.Error.InvalidLength Indicates that the data provided generates a data packet which is too long. .TP .B org.bluez.Error.NotPermitted Indicates the maximum number of advertisement instances has been reached. .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > advertise .UNINDENT .SS void UnregisterAdvertisement(object advertisement) .sp Unregisters an advertisement that has been previously registered using \fBRegisterAdvertisement()\fP\&. .sp The object path parameter must match the same value that has been used on registration. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.DoesNotExist .UNINDENT .SS Properties .SS byte ActiveInstances [readonly] .sp Number of active advertising instances. .SS byte SupportedInstances [readonly] .sp Number of available advertising instances. .SS array{string} SupportedIncludes [readonly] .sp List of supported system includes. .sp Possible values: .INDENT 0.0 .TP .B \(dqtx\-power\(dq .TP .B \(dqappearance\(dq .TP .B \(dqlocal\-name\(dq .TP .B \(dqrsi\(dq .UNINDENT .SS array{string} SupportedSecondaryChannels [readonly] .sp List of supported Secondary channels. Secondary channels can be used to advertise with the corresponding PHY. .sp Possible values: .INDENT 0.0 .TP .B \(dq1M\(dq .TP .B \(dq2M\(dq .TP .B \(dqCoded\(dq .UNINDENT .SS dict SupportedCapabilities [readonly] .sp Enumerates Advertising\-related controller capabilities useful to the client. .sp Possible Values: .INDENT 0.0 .TP .B byte MaxAdvLen Max advertising data length .TP .B byte MaxScnRspLen Max advertising scan response length .TP .B int16 MinTxPower Min advertising tx power (dBm) .TP .B int16 MaxTxPower Max advertising tx power (dBm) .UNINDENT .SS array{string} SupportedFeatures [readonly,optional] .sp List of supported platform features. If no features are available on the platform, the SupportedFeatures array will be empty. .sp Possible values: .INDENT 0.0 .TP .B \(dqCanSetTxPower\(dq Indicates whether platform can specify tx power on each advertising instance. .TP .B \(dqHardwareOffload\(dq Indicates whether multiple advertising will be offloaded to the controller. .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.obex.Session.rst0000644000000000000000000000005015063045254017443 xustar0020 atime=1783094005 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.Session.rst0000644000000000000000000000211115063045254017117 0ustar00rootroot====================== org.bluez.obex.Session ====================== ----------------------------------------- BlueZ D-Bus OBEX Client API documentation ----------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez.obex :Interface: org.bluez.obex.Session1 :Object path: /org/bluez/obex/server/session{#} or /org/bluez/obex/client/session{#} Methods ------- string GetCapabilities() ```````````````````````` Get remote device capabilities. Possible errors: :org.bluez.obex.Error.NotSupported: :org.bluez.obex.Error.Failed: Properties ---------- string Source [readonly] ```````````````````````` Bluetooth adapter address string Destination [readonly] ````````````````````````````` Bluetooth device address byte Channel [readonly] ``````````````````````` Bluetooth channel uint16 PSM [readonly] ``````````````````````` Bluetooth L2CAP PSM string Target [readonly] ```````````````````````` Target UUID string Root [readonly] `````````````````````` Root path bluez-5.87/doc/PaxHeaders/org.bluez.Device.50000644000000000000000000000005015221755264015624 xustar0020 atime=1783093940 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Device.50000644000000000000000000003150315221755264015307 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.Device" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.Device \- BlueZ D-Bus Device API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.Device1 .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} .TP .B Used by \fBbluetoothctl(1)\fP .UNINDENT .SS Methods .SS void Connect() .sp Connects all profiles the remote device supports that can be connected to and have been flagged as auto\-connectable. If only subset of profiles is already connected it will try to connect currently disconnected ones. .sp If at least one profile was connected successfully this method will indicate success. .sp For dual\-mode devices only one bearer is connected at time, the conditions are in the following order: .INDENT 0.0 .IP 1. 3 Connect the disconnected bearer if already connected. .IP 2. 3 Connect first the bonded bearer. If no bearers are bonded or both are skip and check latest seen bearer. .IP 3. 3 Connect last used bearer, in case the timestamps are the same BR/EDR takes precedence, or in case \fBPreferredBearer\fP has been set to a specific bearer then that is used instead. .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.NotReady .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.InProgress .TP .B org.bluez.Error.AlreadyConnected .TP .B org.bluez.Error.BREDR.ProfileUnavailable .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > connect [uuid] .UNINDENT .SS void Disconnect() .sp Disconnects all connected profiles and then terminates low\-level ACL connection. .sp ACL connection will be terminated even if some profiles were not disconnected properly e.g. due to misbehaving device. .sp This method can be also used to cancel a preceding Connect call before a reply to it has been received. .sp For non\-trusted devices connected over LE bearer calling this method will disable incoming connections until Connect method is called again. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.NotConnected .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > disconnect [dev] [uuid] .UNINDENT .SS void ConnectProfile(string uuid) .sp Connects a specific profile of this device. The UUID provided is the remote service UUID for the profile. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.InProgress .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.NotAvailable .TP .B org.bluez.Error.NotReady .UNINDENT .SS void DisconnectProfile(string uuid) .sp Disconnects a specific profile of this device. The profile needs to be registered client profile. .sp There is no connection tracking for a profile, so as long as the profile is registered this will always succeed. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.InProgress .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.NotSupported .UNINDENT .SS void Pair() .sp Connects to the remote device and initiate pairing procedure then proceed with service discovery. .sp If the application has registered its own agent, then that specific agent will be used. Otherwise it will use the default agent. .sp Only for applications like a pairing wizard it would make sense to have its own agent. In almost all other cases the default agent will handle this just fine. .sp In case there is no application agent and also no default agent present, this method will fail. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.AlreadyExists .TP .B org.bluez.Error.AuthenticationCanceled .TP .B org.bluez.Error.AuthenticationFailed .TP .B org.bluez.Error.AuthenticationRejected .TP .B org.bluez.Error.AuthenticationTimeout .TP .B org.bluez.Error.ConnectionAttemptFailed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > pair [dev] .UNINDENT .SS void CancelPairing() .sp Cancels a pairing operation initiated by the \fBPair\fP method. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.DoesNotExist .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > cancel\-pairing [dev] .UNINDENT .SS array{array{byte}} GetServiceRecords() [experimental] .sp Returns all currently known BR/EDR service records for the device. Each individual byte array represents a raw SDP record, as defined by the Bluetooth Service Discovery Protocol specification. .sp This method is intended to be only used by compatibility layers like Wine, that need to provide access to raw SDP records to support foreign Bluetooth APIs. .sp General applications should instead use the Profile API for services\-related functionality. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.NotReady .TP .B org.bluez.Error.NotConnected .TP .B org.bluez.Error.DoesNotExist .UNINDENT .SS Signals .SS void Disconnected(string reason, string message) .sp This signal is launched when a device is disconnected, with the reason of the disconnection. .sp This could be used by client application, depending on internal policy, to try to reconnect to the device in case of timeout or unknown disconnection, or to try to connect to another device. .sp Possible reasons: .INDENT 0.0 .TP .B org.bluez.Reason.Unknown .TP .B org.bluez.Reason.Timeout Connection timeout. .sp The link supervision timeout has expired for a connection or the synchronization timeout has expired for a broadcast. .TP .B org.bluez.Reason.Local Connection terminated by local host. .sp The local device terminated the connection, terminated synchronization with a broadcaster, or terminated broadcasting packets. .TP .B org.bluez.Reason.Remote Connection terminated by remote host. .sp This disconnection can be due to: .INDENT 7.0 .IP \(bu 2 the user on the remote device either terminated the connection or stopped broadcasting packets, .IP \(bu 2 the remote device terminated the connection because of low resources, .IP \(bu 2 the remote device terminated the connection because the device is about to power off. .UNINDENT .TP .B org.bluez.Reason.Authentication Connection terminated due to an authentication failure. .TP .B org.bluez.Reason.Suspend Connection terminated by local host for suspend. .UNINDENT .SS Properties .SS string Address [readonly] .sp The Bluetooth device address of the remote device. .SS string AddressType [readonly] .sp The Bluetooth device Address Type. For dual\-mode and BR/EDR only devices this defaults to \(dqpublic\(dq. Single mode LE devices may have either value. .sp If remote device uses privacy than before pairing this represents address type used for connection and Identity Address after pairing. .sp Possible values: .INDENT 0.0 .TP .B \(dqpublic\(dq Public address .TP .B \(dqrandom\(dq Random address .UNINDENT .SS string Name [readonly, optional] .sp The Bluetooth remote name. .sp This value is only present for completeness. It is better to always use the \fBAlias\fP property when displaying the devices name. .sp If the \fBAlias\fP property is unset, it will reflect this value which makes it more convenient. .SS string Icon [readonly, optional] .sp Proposed icon name according to the freedesktop.org icon naming specification. .SS uint32 Class [readonly, optional] .sp The Bluetooth class of device of the remote device. .SS uint16 Appearance [readonly, optional] .sp External appearance of device, as found on GAP service. .SS array{string} UUIDs [readonly, optional] .sp List of 128\-bit UUIDs that represents the available remote services. .SS boolean Paired [readonly] .sp Indicates if the remote device is paired. Paired means the pairing process where devices exchange the information to establish an encrypted connection has been completed. .SS boolean Bonded [readonly] .sp Indicates if the remote device is bonded. Bonded means the information exchanged on pairing process has been stored and will be persisted. .SS boolean Connected [readonly] .sp Indicates if the remote device is currently connected. .sp A PropertiesChanged signal indicate changes to this status. .SS boolean Trusted [readwrite] .sp Indicates if the remote is seen as trusted. .sp This setting can be changed by the application. .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > trust [dev] .TP .B bluetoothctl > untrust [dev] .UNINDENT .SS boolean Blocked [readwrite] .sp If set to true any incoming connections from the device will be immediately rejected. .sp Any device drivers will also be removed and no new ones will be probed as long as the device is blocked. .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > block [dev] .TP .B bluetoothctl > unblock [dev] .UNINDENT .SS boolean WakeAllowed [readwrite] .sp If set to true this device will be allowed to wake the host from system suspend. .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > wake [dev] [on/off] .UNINDENT .SS string Alias [readwrite] .sp The name alias for the remote device. The alias can be used to have a different friendly name for the remote device. .sp In case no alias is set, it will return the remote device name. Setting an empty string as alias will convert it back to the remote device name. .sp When resetting the alias with an empty string, the property will default back to the remote name. .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > set\-alias .UNINDENT .SS object Adapter [readonly] .sp The object path of the adapter the device belongs to. .SS boolean LegacyPairing [readonly] .sp Set to true if the device only supports the pre\-2.1 pairing mechanism. .sp This property is useful during device discovery to anticipate whether legacy or simple pairing will occur if pairing is initiated. .sp Note that this property can exhibit false\-positives in the case of Bluetooth 2.1 (or newer) devices that have disabled Extended Inquiry Response support. .SS boolean CablePairing [readonly] .sp Set to true if the device was cable paired and it doesn\(aqt support the canonical bonding with encryption, e.g. the Sixaxis gamepad. .sp If true, BlueZ will establish a connection without enforcing encryption. .SS string Modalias [readonly, optional] .sp Remote Device ID information in modalias format used by the kernel and udev. .SS int16 RSSI [readonly, optional] .sp Received Signal Strength Indicator of the remote device (inquiry or advertising). .SS int16 TxPower [readonly, optional] .sp Advertised transmitted power level (inquiry or advertising). .SS dict ManufacturerData [readonly, optional] .sp Manufacturer specific advertisement data. Keys are 16 bits Manufacturer ID followed by its byte array value. .SS dict ServiceData [readonly, optional] .sp Service advertisement data. Keys are the UUIDs in string format followed by its byte array value. .SS bool ServicesResolved [readonly] .sp Indicate whether or not service discovery has been resolved. .SS array{byte} AdvertisingFlags [readonly] .sp The Advertising Data Flags of the remote device. .SS dict AdvertisingData [readonly] .sp The Advertising Data of the remote device. Keys are 1 byte AD Type followed by data as byte array. .sp Note: Only types considered safe to be handled by application are exposed. .sp Possible values: .INDENT 0.0 .TP .B .UNINDENT .sp Example: .INDENT 0.0 .INDENT 3.5 0x26 0x01 0x01... .UNINDENT .UNINDENT .SS array{object, dict} Sets [readonly, experimental] .sp The object paths of the sets the device belongs to followed by a dictionary which can contain the following: .INDENT 0.0 .TP .B byte Rank Rank of the device in the Set. .UNINDENT .SS string PreferredBearer [readwrite, optional, experimental] .sp Indicate the preferred bearer when initiating a connection, only available for dual\-mode devices. .sp When changing from \(dqbredr\(dq to \(dqle\(dq the device will be removed from the \(aqauto\-connect\(aq list so it won\(aqt automatically be connected when advertising. .sp Note: Changes only take effect when the device is disconnected. .sp Possible values: .INDENT 0.0 .TP .B \(dqlast\-used\(dq Connect to last used bearer first. Default. .TP .B \(dqbredr\(dq Connect to BR/EDR first. .TP .B \(dqle\(dq Connect to LE first. .TP .B \(dqlast\-seen\(dq Connect to last seen bearer first. .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > bearer [last\-seen/bredr/le] .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/ciptool.rst0000644000000000000000000000005015142177620014630 xustar0020 atime=1783094091 20 ctime=1783094113 bluez-5.87/doc/ciptool.rst0000644000000000000000000000345415142177620014317 0ustar00rootroot======== ciptool ======== ------------------------------------------ Bluetooth Common ISDN Access Profile (CIP) ------------------------------------------ :Author: Marcel Holtmann :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: June 3, 2003 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **ciptool** [*OPTIONS*] *COMMANDS* DESCRIPTION =========== **ciptool(1)** is used to set up, maintain, and inspect the CIP configuration of the Bluetooth subsystem in the Linux kernel. OPTIONS ======= -i The command is applied to device *hciX* , which must be the name or the address of an installed Bluetooth device. If not specified, the command will be use the first available Bluetooth device. -h, --help Show help options COMMANDS ======== show Display information about the connected devices. search Search for Bluetooth devices and connect to first one that offers CIP support. connect <*BDADDR*> [*PSM*] Connect the local device to the remote Bluetooth device on the specified *PSM* number. If no *PSM* is specified, it will use the SDP to retrieve it from the remote device. release [*BDADDR*] Release a connection to the specific device. If no *BDADDR* is given and only one device is connected this will be released. loopback <*BDADDR*> [*PSM*] Create a connection to the remote device for Bluetooth testing. This command will not provide a CAPI controller, because it is only for testing the CAPI Message Transport Protocol. RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.BatteryProvider.rst0000644000000000000000000000005015063045254020211 xustar0020 atime=1783093963 20 ctime=1783094111 bluez-5.87/doc/org.bluez.BatteryProvider.rst0000644000000000000000000000147315063045254017677 0ustar00rootroot========================= org.bluez.BatteryProvider ========================= --------------------------------------------- BlueZ D-Bus BatteryProvider API documentation --------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: :Interface: org.bluez.BatteryProvider1 :Object path: {provider_root}/{unique battery object path} Properties ---------- Objects provided on this interface contain the same properties as **org.bluez.Battery(5)** interface. Additionally, this interface needs to have the Device property indicating the object path of the device this battery provides. object Device [readonly] ```````````````````````` The object path of the device that has this battery. bluez-5.87/doc/PaxHeaders/org.bluez.Profile.rst0000644000000000000000000000005015063045254016464 xustar0020 atime=1783093954 20 ctime=1783094111 bluez-5.87/doc/org.bluez.Profile.rst0000644000000000000000000000207415063045254016150 0ustar00rootroot================= org.bluez.Profile ================= ------------------------------------- BlueZ D-Bus Profile API documentation ------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: unique name :Interface: org.bluez.Profile1 :Object path: freely definable Methods ------- void Release() [noreply] ```````````````````````` This method gets called when **bluetoothd(8)** unregisters the profile. A profile can use it to do cleanup tasks. There is no need to unregister the profile, because when this method gets called it has already been unregistered. void NewConnection(object device, fd, dict fd_properties) ````````````````````````````````````````````````````````` This method gets called when a new service level connection has been made and authorized. Possible fd_properties values: :uint16 Version [optional]: Profile version. :uint16 Features [optional]: Profile features. Possible errors: :org.bluez.Error.Rejected: :org.bluez.Error.Canceled: bluez-5.87/doc/PaxHeaders/org.bluez.NetworkServer.50000644000000000000000000000005015221755304017240 xustar0020 atime=1783093956 20 ctime=1783094112 bluez-5.87/doc/org.bluez.NetworkServer.50000644000000000000000000000427215221755304016726 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.NetworkServer" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.NetworkServer \- BlueZ D-Bus NetworkServer API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.NetworkServer1 .TP .B Object path /org/bluez/{hci0,hci1,...} .UNINDENT .SS Methods .SS void Register(string uuid, string bridge) .sp Registers server for the provided UUID. .sp Every new connection to this server will be added the bridge interface. .sp Possible uuid values: .INDENT 0.0 .TP .B \(dqpanu\(dq, \(dq00001115\-0000\-1000\-8000\-00805f9b34fb\(dq Personal Network User role. .TP .B \(dqnap\(dq, \(dq00001116\-0000\-1000\-8000\-00805f9b34fb\(dq Network Access Point role. .TP .B \(dqgn\(dq, \(dq00001117\-0000\-1000\-8000\-00805f9b34fb\(dq Group Network role. .UNINDENT .sp Initially no network server SDP is provided. Only after this method a SDP record will be available and the BNEP server will be ready for incoming connections. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.AlreadyExists .TP .B org.bluez.Error.Failed .UNINDENT .SS void Unregister(string uuid) .sp Unregisters the server for provided UUID which was previously registered with \fBRegister()\fP method. .sp All servers will be automatically unregistered when the calling application terminates. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.Failed .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/bluetoothctl-player.rst0000644000000000000000000000005015142177620017161 xustar0020 atime=1783094065 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-player.rst0000644000000000000000000001517115142177620016647 0ustar00rootroot=================== bluetoothctl-player =================== -------------------- Media Player Submenu -------------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: November 2022 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **bluetoothctl** [--options] [player.commands] This submenu controls media playback using the **org.bluez.MediaPlayer(5)** interface. Media Player Commands ===================== list ---- List available players. :Usage: **> list** :Example Display all available media players (local and remote): | **> list** show ---- Show player information. :Usage: **> show [player]** :Uses: **org.bluez.MediaPlayer(5)** properties :[player]: Media player path or identifier (optional, shows currently selected player if omitted) :Example Show information for currently selected player: | **> show** :Example Show remote player information: | **> show /org/bluez/hci0/dev_00_11_22_33_44_55/player0** select ------ Select default player. :Usage: **> select ** :: Media player path or identifier to select as default :Example Select remote Bluetooth media player: | **> select /org/bluez/hci0/dev_00_11_22_33_44_55/player0** play ---- Start playback. :Usage: **> play [item]** :Uses: **org.bluez.MediaPlayer(5)** method **Play** :[item]: Media item path to play (optional, starts playback of current item if omitted) :Example Start playback on current player: | **> play** :Example Play specific item from media browser: | **> play /org/bluez/item/1** :Example Play named track item: | **> play /org/bluez/item/track_001** :Example Play track from specific album folder: | **> play /org/bluez/item/album_rock/track_05** pause ----- Pause playback. :Usage: **> pause** :Uses: **org.bluez.MediaPlayer(5)** method **Pause** :Example Pause current playback: | **> pause** stop ---- Stop playback. :Usage: **> stop** :Uses: **org.bluez.MediaPlayer(5)** method **Stop** :Example Stop current playback: | **> stop** next ---- Jump to next item. :Usage: **> next** :Uses: **org.bluez.MediaPlayer(5)** method **Next** :Example Skip to next track: | **> next** previous -------- Jump to previous item. :Usage: **> previous** :Uses: **org.bluez.MediaPlayer(5)** method **Previous** :Example Go back to previous track: | **> previous** fast-forward ------------ Fast forward playback. :Usage: **> fast-forward** :Uses: **org.bluez.MediaPlayer(5)** method **FastForward** :Example Enable fast forward mode: | **> fast-forward** rewind ------ Rewind playback. :Usage: **> rewind** :Uses: **org.bluez.MediaPlayer(5)** method **Rewind** :Example Enable rewind mode: | **> rewind** equalizer --------- Enable/Disable equalizer. :Usage: **> equalizer ** :Uses: **org.bluez.MediaPlayer(5)** property **Equalizer** :: Enable (on) or disable (off) the equalizer :Example Enable equalizer: | **> equalizer on** :Example Disable equalizer: | **> equalizer off** repeat ------ Set repeat mode. :Usage: **> repeat ** :Uses: **org.bluez.MediaPlayer(5)** property **Repeat** :: Set repeat mode - singletrack (current track), alltrack (all tracks), group (current group/album), or off (no repeat) :Example Disable repeat mode: | **> repeat off** :Example Repeat current track: | **> repeat singletrack** :Example Repeat entire playlist/queue: | **> repeat alltrack** :Example Repeat current group/album: | **> repeat group** shuffle ------- Set shuffle mode. :Usage: **> shuffle ** :Uses: **org.bluez.MediaPlayer(5)** property **Shuffle** :: Set shuffle mode - alltracks (shuffle all tracks), group (shuffle within current group), or off (no shuffle) :Example Disable shuffle mode: | **> shuffle off** :Example Shuffle all tracks in playlist: | **> shuffle alltracks** :Example Shuffle tracks within current group: | **> shuffle group** scan ---- Set scan mode. :Usage: **> scan ** :Uses: **org.bluez.MediaPlayer(5)** property **Scan** :: Set scan mode - alltracks (scan through all tracks), group (scan within current group), or off (no scan) :Example Disable scan mode: | **> scan off** :Example Scan through all tracks: | **> scan alltracks** :Example Scan through current group only: | **> scan group** change-folder ------------- Change current folder. :Usage: **> change-folder ** :: Folder path to navigate to, or ".." to go up one directory level :Example Navigate to Albums folder: | **> change-folder /org/bluez/item/Albums** :Example Navigate to Artists folder: | **> change-folder /org/bluez/item/Artists** :Example Navigate to Playlists folder: | **> change-folder /org/bluez/item/Playlists** :Example Go up one directory level: | **> change-folder ..** list-items ---------- List items of current folder. :Usage: **> list-items [start] [end]** :[start]: Starting index for item list (optional, defaults to 0) :[end]: Ending index for item list (optional, lists all items from start if omitted) :Example List all items in current folder: | **> list-items** :Example List first 10 items (0-10): | **> list-items 0 10** :Example List items 5 through 15: | **> list-items 5 15** :Example List items 10 through 20: | **> list-items 10 20** :Example List first 50 items: | **> list-items 0 50** search ------ Search items containing string. :Usage: **> search ** :: Search term to find matching items (songs, albums, artists, etc.) :Example Search for Beatles songs/albums: | **> search "The Beatles"** :Example Search for items containing "rock": | **> search "rock"** :Example Search for items from 2023: | **> search "2023"** :Example Search for playlists: | **> search "playlist"** queue ----- Add item to playlist queue. :Usage: **> queue ** :: Media item path to add to the playback queue :Example Add specific track to queue: | **> queue /org/bluez/item/track_001** :Example Add entire album to queue: | **> queue /org/bluez/item/album_rock** :Example Add playlist to queue: | **> queue /org/bluez/item/playlist_favorites** show-item --------- Show item information. :Usage: **> show-item ** :: Media item path to display detailed information for :Example Show details of specific track: | **> show-item /org/bluez/item/track_001** :Example Show details of album: | **> show-item /org/bluez/item/album_rock** :Example Show details of artist: | **> show-item /org/bluez/item/artist_beatles** RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.ThermometerManager.50000644000000000000000000000005015221755416020212 xustar0020 atime=1783094030 20 ctime=1783094112 bluez-5.87/doc/org.bluez.ThermometerManager.50000644000000000000000000000410515221755416017673 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.ThermometerManager" "5" "July 2011" "BlueZ" "Linux System Administration" .SH Name org.bluez.ThermometerManager \- BlueZ D-Bus Health ThermometerManager API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.ThermometerManager1 .TP .B Object path [variable prefix]/{hci0,hci1,...} .UNINDENT .SS Methods .SS void RegisterWatcher(object agent) .sp Registers a watcher to monitor scanned measurements. This agent will be notified about final temperature measurements. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .UNINDENT .SS void UnregisterWatcher(object agent) .sp Unregisters a watcher. .SS void EnableIntermediateMeasurement(object agent) .sp Enables intermediate measurement notifications for this agent. Intermediate measurements will be enabled only for thermometers which support it. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .UNINDENT .SS void DisableIntermediateMeasurement(object agent) .sp Disables intermediate measurement notifications for this agent. It will disable notifications in thermometers when the last agent removes the watcher for intermediate measurements. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.NotFound .UNINDENT .SH Author Santiago Carot-Nemesio .\" End of generated man page. bluez-5.87/doc/PaxHeaders/hcitool.10000644000000000000000000000005015221755505014152 xustar0020 atime=1783094085 20 ctime=1783094112 bluez-5.87/doc/hcitool.10000644000000000000000000001612715221755505013642 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "hcitool" "1" "Nov 12, 2002" "BlueZ" "Linux System Administration" .SH Name hcitool \- Configure Bluetooth connections .SH SYNOPSIS .sp \fBhcitool\fP \-h .sp \fBhcitool\fP \fICOMMAND\fP \-\-help .sp \fBhcitool\fP [\-i \fIhciX\fP] [\fICOMMAND\fP [\fIPARAMETERS\fP]] .SH DESCRIPTION .sp \fBhcitool(1)\fP is used to configure Bluetooth connections and send some special command to Bluetooth devices. If no \fBcommand\fP is given, or if the option \fB\-h\fP is used, \fIhcitool\fP prints some usage information and exits. .SH OPTIONS .INDENT 0.0 .TP .BI \-i \ The command is applied to device \fIhciX\fP, which must be the name of an installed Bluetooth device. If not specified, the command will be sent to the first available Bluetooth device. .TP .B \-h Gives a list of possible commands .UNINDENT .SH COMMANDS .INDENT 0.0 .TP .B dev Display local devices .TP .B inq Inquire remote devices. For each discovered device, Bluetooth device address, clock offset and class are printed. .TP .B scan Inquire remote devices. For each discovered device, device name are printed. .TP .B name <\fIbdaddr\fP> Print device name of remote device with Bluetooth address \fIbdaddr\fP\&. .TP .B info <\fIbdaddr\fP> Print device name, version and supported features of remote device with Bluetooth address \fIbdaddr\fP\&. .TP .B spinq Start periodic inquiry process. No inquiry results are printed. .TP .B epinq Exit periodic inquiry process. .TP .B cmd <\fIogf\fP> <\fIocf\fP> [\fIparameters\fP] Submit an arbitrary HCI command to local device. \fIogf\fP, \fIocf\fP and parameters are hexadecimal bytes. .TP .B con Display active baseband connections .TP .B cc [\-\-\fIrole\fP=c|p] [\-\-\fIpkt\-type\fP=<\fIptype\fP>] <\fIbdaddr\fP> Create baseband connection to remote device with Bluetooth address \fIbdaddr\fP\&. .sp Option \fB\-\-pkt\-type\fP specifies a list of allowed packet types. <\fIptype\fP> is a comma\-separated list of packet types, where the possible packet types are \fBDM1\fP, \fBDM3\fP, \fBDM5\fP, \fBDH1\fP, \fBDH3\fP, \fBDH5\fP, \fBHV1\fP, \fBHV2\fP, \fBHV3\fP\&. Default is to allow all packet types. .sp Option \fB\-\-role\fP can have value \fBc\fP (do not allow role switch, stay central) or \fBp\fP (allow role switch, become peripheral if the peer asks to become central). Default is \fBc\fP\&. .TP .B dc <\fIbdaddr\fP> [\fIreason\fP] Delete baseband connection from remote device with Bluetooth address \fIbdaddr\fP\&. .sp The reason can be one of the Bluetooth HCI error codes. Default is \fB19\fP for user ended connections. The value must be given in decimal. .TP .B sr <\fIbdaddr\fP> <\fIrole\fP> Switch role for the baseband connection from the remote device to \fBcentral\fP or \fBperipheral\fP\&. .TP .B cpt <\fIbdaddr\fP> <\fIptypes\fP> Change packet types for baseband connection to device with Bluetooth address \fIbdaddr\fP\&. \fIptypes\fP is a comma\-separated list of packet types, where the possible packet types are \fBDM1\fP, \fBDM3\fP, \fBDM5\fP, \fBDH1\fP, \fBDH3\fP, \fBDH5\fP, \fBHV1\fP, \fBHV2\fP, \fBHV3\fP\&. .TP .B rssi <\fIbdaddr\fP> Display received signal strength information for the connection to the device with Bluetooth address \fIbdaddr\fP\&. .TP .B lq <\fIbdaddr\fP> Display link quality for the connection to the device with Bluetooth address \fIbdaddr\fP\&. .TP .B tpl <\fIbdaddr\fP> [\fItype\fP] Display transmit power level for the connection to the device with Bluetooth address \fIbdaddr\fP\&. .sp The \fItype\fP can be \fB0\fP for the current transmit power level (which is default) or \fB1\fP for the maximum transmit power level. .TP .B afh <\fIbdaddr\fP> Display AFH channel map for the connection to the device with Bluetooth address \fIbdaddr\fP\&. .TP .B lp <\fIbdaddr\fP> [\fIvalue\fP] With no value, displays link policy settings for the connection to the device with Bluetooth address \fIbdaddr\fP\&. .sp If \fIvalue\fP is given, sets the link policy settings for that connection to \fIvalue\fP\&. Possible values are \fBRSWITCH\fP, \fBHOLD\fP, \fBSNIFF\fP and \fBPARK\fP\&. .TP .B lst <\fIbdaddr\fP> [\fIvalue\fP] With no value, displays link supervision timeout for the connection to the device with Bluetooth address \fIbdaddr\fP\&. .sp If \fIvalue\fP is given, sets the link supervision timeout for that connection to \fIvalue\fP slots, or to infinite if value is 0. .TP .B auth <\fIbdaddr\fP> Request authentication for the device with Bluetooth address \fIbdaddr\fP\&. .TP .B enc <\fIbdaddr\fP> [\fIencrypt\fP] \fBenable\fP or \fBdisable\fP the encryption for the device with Bluetooth address \fIbdaddr\fP\&. .TP .B key <\fIbdaddr\fP> Change the connection link key for the device with Bluetooth address \fIbdaddr\fP\&. .TP .B clkoff <\fIbdaddr\fP> Read the clock offset for the device with Bluetooth address \fIbdaddr\fP\&. .TP .B clock [\fIbdaddr\fP] [\fIclock\fP] Read the clock for the device with Bluetooth address \fIbdaddr\fP\&. .sp The \fIclock\fP can be \fB0\fP for the local clock or \fB1\fP for the piconet clock (which is default). .TP .B lescan [\-\-\fIprivacy\fP] [\-\-\fIpassive\fP] [\-\-\fIacceptlist\fP] [\-\-\fIdiscovery\fP=g|l] [\-\-\fIduplicates\fP] Start LE scan .TP .B leinfo [\-\-\fIstatic\fP] [\-\-\fIrandom\fP] <\fIbdaddr\fP> Get LE remote information .TP .B lealadd [\-\-\fIrandom\fP] <\fIbdaddr\fP> Add device to LE Accept List .TP .B lealrm <\fIbdaddr\fP> Remove device from LE Accept List .TP .B lealsz Read size of LE Accept List .TP .B lealclr Clear LE Accept List .TP .B lerladd [\-\-\fIlocal_irk\fP] [\-\-\fIpeer_irk\fP] [\-\-\fIrandom\fP] <\fIbdaddr\fP> Add device to LE Resolving List .TP .B lerlrm <\fIbdaddr\fP> Remove device from LE Resolving List .TP .B lerlclr Clear LE Resolving List .TP .B lerlsz Read size of LE Resolving List .TP .B lerlon Enable LE Address Resolution .TP .B lerloff Disable LE Address Resolution .TP .B lecc [\-\-\fIstatic\fP] [\-\-\fIrandom\fP] <\fIbdaddr\fP> | [\-\-\fIacceptlist\fP] Create a LE Connection .TP .B ledc <\fIhandle\fP> [\fIreason\fP] Disconnect a LE Connection .TP .B lecup <\fIhandle\fP> <\fImin\fP> <\fImax\fP> <\fIlatency\fP> <\fItimeout\fP> LE Connection Update .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Author Maxim Krasnyansky , Marcel Holtmann , Fabrizio Gennari .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.Network.50000644000000000000000000000005015221755306016053 xustar0020 atime=1783093958 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Network.50000644000000000000000000000454615221755306015545 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.Network" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.Network \- BlueZ D-Bus Network API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.Network1 .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} .UNINDENT .SS Methods .SS string Connect(string uuid) .sp Connects to the network device and return the network interface name. .sp Possible uuid values: .INDENT 0.0 .TP .B \(dqpanu\(dq, \(dq00001115\-0000\-1000\-8000\-00805f9b34fb\(dq Personal Network User role. .TP .B \(dqnap\(dq, \(dq00001116\-0000\-1000\-8000\-00805f9b34fb\(dq Network Access Point role. .TP .B \(dqgn\(dq, \(dq00001117\-0000\-1000\-8000\-00805f9b34fb\(dq Group Network role. .UNINDENT .sp The connection will be closed and network device released either upon calling \fBDisconnect()\fP or when the client disappears from the message bus. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.InProgress .TP .B org.bluez.Error.Failed .UNINDENT .SS void Disconnect() .sp Disconnects from the network device. .sp To abort a connection attempt in case of errors or timeouts in the client it is fine to call this method. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.NotConnected .UNINDENT .SS Properties .SS boolean Connected [readonly] .sp Indicates if the device is connected. .SS string Interface [readonly, optional] .sp Indicates the network interface name when available. .SS string UUID [readonly, optional] .sp Indicates the connection role when available. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/btsnoop.70000644000000000000000000000005015221755260014201 xustar0020 atime=1783093936 20 ctime=1783094112 bluez-5.87/doc/btsnoop.70000644000000000000000000001227015221755260013664 0ustar00rootroot'\" t .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "btsnoop" "7" "February 2026" "BlueZ" "Linux System Administration" .SH Name btsnoop \- BTSnoop/Monitor protocol documentation .SH SYNOPSIS .sp This document describes the BTSnoop/Monitor formats used to record and transport HCI/PHY traffic. The formats are used by tools such as btmon and the BlueZ btsnoop implementation. .SH DESCRIPTION .SS Opcode definitions .sp The following opcodes are used by the BTSnoop/Monitor formats. The numeric values match the definitions in \fBsrc/shared/btsnoop.h\fP\&. .SS BTSnoop opcodes .TS box center; l|l|l|l. T{ Name T} T{ Code (dec) T} T{ Code (hex) T} T{ Meaning T} _ T{ BTSNOOP_OPCODE_NEW_INDEX T} T{ 0 T} T{ 0x0000 T} T{ New controller index (parameters: type, bus, bdaddr, name) T} _ T{ BTSNOOP_OPCODE_DEL_INDEX T} T{ 1 T} T{ 0x0001 T} T{ Deleted controller index T} _ T{ BTSNOOP_OPCODE_COMMAND_PKT T} T{ 2 T} T{ 0x0002 T} T{ HCI Command packet T} _ T{ BTSNOOP_OPCODE_EVENT_PKT T} T{ 3 T} T{ 0x0003 T} T{ HCI Event packet T} _ T{ BTSNOOP_OPCODE_ACL_TX_PKT T} T{ 4 T} T{ 0x0004 T} T{ Outgoing ACL packet T} _ T{ BTSNOOP_OPCODE_ACL_RX_PKT T} T{ 5 T} T{ 0x0005 T} T{ Incoming ACL packet T} _ T{ BTSNOOP_OPCODE_SCO_TX_PKT T} T{ 6 T} T{ 0x0006 T} T{ Outgoing SCO packet T} _ T{ BTSNOOP_OPCODE_SCO_RX_PKT T} T{ 7 T} T{ 0x0007 T} T{ Incoming SCO packet T} _ T{ BTSNOOP_OPCODE_OPEN_INDEX T} T{ 8 T} T{ 0x0008 T} T{ HCI transport for the specified controller opened T} _ T{ BTSNOOP_OPCODE_CLOSE_INDEX T} T{ 9 T} T{ 0x0009 T} T{ HCI transport for the specified controller closed T} _ T{ BTSNOOP_OPCODE_INDEX_INFO T} T{ 10 T} T{ 0x000a T} T{ Index information (parameters: bdaddr, manufacturer) T} _ T{ BTSNOOP_OPCODE_VENDOR_DIAG T} T{ 11 T} T{ 0x000b T} T{ Vendor diagnostic information T} _ T{ BTSNOOP_OPCODE_SYSTEM_NOTE T} T{ 12 T} T{ 0x000c T} T{ System note T} _ T{ BTSNOOP_OPCODE_USER_LOGGING T} T{ 13 T} T{ 0x000d T} T{ User logging (parameters: priority, ident_len, ident) T} _ T{ BTSNOOP_OPCODE_CTRL_OPEN T} T{ 14 T} T{ 0x000e T} T{ Control channel opened T} _ T{ BTSNOOP_OPCODE_CTRL_CLOSE T} T{ 15 T} T{ 0x000f T} T{ Control channel closed T} _ T{ BTSNOOP_OPCODE_CTRL_COMMAND T} T{ 16 T} T{ 0x0010 T} T{ Control command packet T} _ T{ BTSNOOP_OPCODE_CTRL_EVENT T} T{ 17 T} T{ 0x0011 T} T{ Control event packet T} _ T{ BTSNOOP_OPCODE_ISO_TX_PKT T} T{ 18 T} T{ 0x0012 T} T{ Outgoing ISO packet T} _ T{ BTSNOOP_OPCODE_ISO_RX_PKT T} T{ 19 T} T{ 0x0013 T} T{ Incoming ISO packet T} .TE .SS New Index .sp Code: 0x0000 .sp Parameters: .INDENT 0.0 .IP \(bu 2 Type (1 octet) .IP \(bu 2 Bus (1 octet) .IP \(bu 2 BD_Addr (6 octets) .IP \(bu 2 Name (8 octets) .UNINDENT .sp This opcode indicates that a new controller instance with a given index was added. With some transports (for example a single TTY device) the index is implicitly 0. .SS Deleted Index .sp Code: 0x0001 .sp Indicates that the controller with a specific index was removed. .SS TTY\-based protocol .sp The TTY protocol used by btmon with the \fB\-\-tty\fP option is a little\-endian packet format. Each packet uses this header: .INDENT 0.0 .INDENT 3.5 .sp .EX struct tty_hdr { uint16_t data_len; uint16_t opcode; uint8_t flags; uint8_t hdr_len; uint8_t ext_hdr[0]; } __attribute__ ((packed)); .EE .UNINDENT .UNINDENT .sp The payload starts at \fBext_hdr + hdr_len\fP and has length \fBdata_len \- 4 \- hdr_len\fP\&. .SS Extended header format .sp Each extension field is encoded as: .INDENT 0.0 .INDENT 3.5 .sp .EX struct { uint8_t type; uint8_t value[length]; }; .EE .UNINDENT .UNINDENT .sp Defined types: .SS Extended header types .TS box center; l|l|l. T{ Type T} T{ Length T} T{ Meaning T} _ T{ 1 T} T{ 1 byte T} T{ Command drops (dropped HCI command packets) T} _ T{ 2 T} T{ 1 byte T} T{ Event drops (dropped HCI event packets) T} _ T{ 3 T} T{ 1 byte T} T{ ACL TX drops T} _ T{ 4 T} T{ 1 byte T} T{ ACL RX drops T} _ T{ 5 T} T{ 1 byte T} T{ SCO TX drops T} _ T{ 6 T} T{ 1 byte T} T{ SCO RX drops T} _ T{ 7 T} T{ 1 byte T} T{ Other drops T} _ T{ 8 T} T{ 4 bytes T} T{ 32\-bit timestamp (1/10th ms) T} .TE .sp The drops fields contain the number of packets the implementation had to drop since the last reported drop count. Extension fields must be sorted by increasing \fBtype\fP so unknown types can be skipped and the payload location discovered. .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH SEE ALSO .sp btmon(1) .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.AdminPolicyStatus.rst0000644000000000000000000000005015142177620020501 xustar0020 atime=1783093969 20 ctime=1783094111 bluez-5.87/doc/org.bluez.AdminPolicyStatus.rst0000644000000000000000000000230215142177620020157 0ustar00rootroot=========================== org.bluez.AdminPolicyStatus =========================== ----------------------------------------------- BlueZ D-Bus AdminPolicyStatus API documentation ----------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Description =========== Interface AdminPolicyStatus1 provides readonly properties to indicate the current values of admin policy affecting the Adapter and Device objects. Interface ========= Adapter ------- :Service: org.bluez :Interface: org.bluez.AdminPolicyStatus1 [experimental] :Object path: [variable prefix]/{hci0,hci1,...} :Used by: **bluetoothctl-admin(1)** Device ------ :Service: org.bluez :Interface: org.bluez.AdminPolicyStatus1 [experimental] :Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} Properties ---------- array{string} ServiceAllowList [readonly, adapter-only] ``````````````````````````````````````````````````````` Current value of service allow list. bool IsAffectedByPolicy [readonly, device-only] ``````````````````````````````````````````````` Indicate if there is any auto-connect profile in this device is not allowed by admin policy. bluez-5.87/doc/PaxHeaders/l2cap-protocol.rst0000644000000000000000000000005015142177620016017 xustar0020 atime=1783093926 20 ctime=1783094111 bluez-5.87/doc/l2cap-protocol.rst0000644000000000000000000002616015142177620015505 0ustar00rootroot===== l2cap ===== -------------- L2CAP protocol -------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: May 2024 :Manual section: 7 :Manual group: Linux System Administration SYNOPSIS ======== .. code-block:: #include #include #include l2cap_socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); DESCRIPTION =========== L2CAP is a protocol that provides an interface for higher-level protocols to send and receive data over a Bluetooth connection. L2CAP sits on top of the Bluetooth Host Controller Interface (HCI) and provides a set of channels that can be used by higher-level protocols to transmit data. L2CAP provides a number of services to higher-level protocols, including segmentation and reassembly of large data packets and flow control to prevent overloading of the receiver. L2CAP also supports multiple channels per connection, allowing for concurrent data transmission using different protocols. SOCKET ADDRESS ============== .. code-block:: struct sockaddr_l2 { sa_family_t l2_family; unsigned short l2_psm; bdaddr_t l2_bdaddr; unsigned short l2_cid; uint8_t l2_bdaddr_type; }; Example: .. code-block:: struct sockaddr_l2 addr; memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; bacpy(&addr.l2_bdaddr, bdaddr); if (cid) addr.l2_cid = htobs(cid); else addr.l2_psm = htobs(psm); addr.l2_bdaddr_type = bdaddr_type; SOCKET OPTIONS (SOL_BLUETOOTH) ============================== The socket options listed below can be set by using **setsockopt(2)** and read with **getsockopt(2)** with the socket level set to SOL_BLUETOOTH. BT_SECURITY (since Linux 2.6.30) -------------------------------- Channel security level, possible values: .. csv-table:: :header: "Value", "Security Level", "Link Key Type", "Encryption" :widths: auto **BT_SECURITY_SDP**, 0 (SDP Only), None, Not required **BT_SECURITY_LOW**, 1 (Low), Unauthenticated, Not required **BT_SECURITY_MEDIUM**, 2 (Medium - default), Unauthenticated, Desired **BT_SECURITY_HIGH**, 3 (High), Authenticated, Required **BT_SECURITY_FIPS** (since Linux 3.15), 4 (Secure Only), Authenticated (P-256 based Secure Simple Pairing and Secure Authentication), Required Example: .. code-block:: int level = BT_SECURITY_HIGH; int err = setsockopt(l2cap_socket, SOL_BLUETOOTH, BT_SECURITY, &level, sizeof(level)); if (err == -1) { perror("setsockopt"); return 1; } BT_DEFER_SETUP (since Linux 2.6.30) ----------------------------------- Channel defer connection setup, this control if the connection procedure needs to be authorized by userspace before responding which allows authorization at profile level, possible values: .. csv-table:: :header: "Value", "Description", "Authorization" :widths: auto **0**, Disable (default), Not required **1**, Enable, Required Example: .. code-block:: int defer_setup = 1; int err = setsockopt(l2cap_socket, SOL_BLUETOOTH, BT_DEFER_SETUP, &defer_setup, sizeof(defer_setup)); if (err == -1) { perror("setsockopt"); return err; } err = listen(l2cap_socket, 5); if (err) { perror("listen"); return err; } struct sockaddr_l2 remote_addr = {0}; socklen_t addr_len = sizeof(remote_addr); int new_socket = accept(l2cap_socket, (struct sockaddr*)&remote_addr, &addr_len); if (new_socket < 0) { perror("accept"); return new_socket; } /* To complete the connection setup of new_socket read 1 byte */ char c; struct pollfd pfd; memset(&pfd, 0, sizeof(pfd)); pfd.fd = new_socket; pfd.events = POLLOUT; err = poll(&pfd, 1, 0); if (err) { perror("poll"); return err; } if (!(pfd.revents & POLLOUT)) { err = read(sk, &c, 1); if (err < 0) { perror("read"); return err; } } BT_FLUSHABLE (since Linux 2.6.39) --------------------------------- Channel flushable flag, this control if the channel data can be flushed or not, possible values: .. csv-table:: :header: "Define", "Value", "Description" :widths: auto **BT_FLUSHABLE_OFF**, 0x00 (default), Do not flush data **BT_FLUSHABLE_ON**, 0x01, Flush data BT_POWER (since Linux 3.1) -------------------------- Channel power policy, this control if the channel shall force exit of sniff mode or not, possible values: .. csv-table:: :header: "Define", "Value", "Description" :widths: auto **BT_POWER_FORCE_ACTIVE_OFF**, 0x00, Don't force exit of sniff mode **BT_POWER_FORCE_ACTIVE_ON**, 0x01 (default), Force exit of sniff mode BT_CHANNEL_POLICY (since Linux 3.10) ------------------------------------ High-speed (AMP) channel policy, possible values: .. csv-table:: :header: "Define", "Value", "Description" :widths: auto **BT_CHANNEL_POLICY_BREDR_ONLY**, 0 (default), BR/EDR only **BT_CHANNEL_POLICY_BREDR_PREFERRED**, 1, BR/EDR Preferred **BT_CHANNEL_POLICY_BREDR_PREFERRED**, 2, AMP Preferred BT_PHY (since Linux 5.10) ------------------------- Channel supported PHY(s), possible values: .. csv-table:: :header: "Define", "Value", "Description" :widths: auto **BT_PHY_BR_1M_1SLOT**, BIT 0, BR 1Mbps 1SLOT **BT_PHY_BR_1M_3SLOT**, BIT 1, BR 1Mbps 3SLOT **BT_PHY_BR_1M_5SLOT**, BIT 2, BR 1Mbps 5SLOT **BT_PHY_BR_2M_1SLOT**, BIT 3, EDR 2Mbps 1SLOT **BT_PHY_BR_2M_3SLOT**, BIT 4, EDR 2Mbps 3SLOT **BT_PHY_BR_2M_5SLOT**, BIT 5, EDR 2Mbps 5SLOT **BT_PHY_BR_3M_1SLOT**, BIT 6, EDR 3Mbps 1SLOT **BT_PHY_BR_3M_3SLOT**, BIT 7, EDR 3Mbps 3SLOT **BT_PHY_BR_3M_5SLOT**, BIT 8, EDR 3Mbps 5SLOT **BT_PHY_LE_1M_TX**, BIT 9, LE 1Mbps TX **BT_PHY_LE_1M_RX**, BIT 10, LE 1Mbps RX **BT_PHY_LE_2M_TX**, BIT 11, LE 2Mbps TX **BT_PHY_LE_2M_RX**, BIT 12, LE 2Mbps RX **BT_PHY_LE_CODED_TX**, BIT 13, LE Coded TX **BT_PHY_LE_CODED_RX**, BIT 14, LE Coded RX BT_MODE (since Linux 5.10) -------------------------- Channel Mode, possible values: .. csv-table:: :header: "Define", "Value", "Description", "Link" :widths: auto **BT_MODE_BASIC**, 0x00 (default), Basic mode, Any **BT_MODE_ERTM**, 0x01, Enhanced Retransmission mode, BR/EDR **BT_MODE_STREAM**, 0x02, Stream mode, BR/EDR **BT_MODE_LE_FLOWCTL**, 0x03, Credit based flow control mode, LE **BT_MODE_EXT_FLOWCTL**, 0x04, Extended Credit based flow control mode, Any SOCKET OPTIONS (SOL_SOCKET) =========================== ``SOL_SOCKET`` level socket options that modify generic socket features (``SO_SNDBUF``, ``SO_RCVBUF``, etc.) have their usual meaning, see **socket(7)**. The ``SOL_SOCKET`` level L2CAP socket options that have Bluetooth-specific handling in kernel are listed below. SO_TIMESTAMPING, SO_TIMESTAMP, SO_TIMESTAMPNS --------------------------------------------- See https://docs.kernel.org/networking/timestamping.html For L2CAP sockets, software RX timestamps are supported. Software TX timestamps (SOF_TIMESTAMPING_TX_SOFTWARE, SOF_TIMESTAMPING_TX_COMPLETION) are supported since Linux 6.15. The software RX timestamp is the time when the kernel received the packet from the controller driver. The ``SCM_TSTAMP_SND`` timestamp is emitted when packet is sent to the controller driver. The ``SCM_TSTAMP_COMPLETION`` timestamp is emitted when controller reports the packet completed. Other TX timestamp types are not supported. You can use ``SIOCETHTOOL`` to query supported flags. The timestamps are in ``CLOCK_REALTIME`` time. Example (Enable RX timestamping): .. code-block:: int flags = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE; setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)); Example (Read packet and its RX timestamp): .. code-block:: char data_buf[256]; union { char buf[CMSG_SPACE(sizeof(struct scm_timestamping))]; struct cmsghdr align; } control; struct iovec data = { .iov_base = data_buf, .iov_len = sizeof(data_buf), }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct scm_timestamping tss; res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT); if (res < 0) goto error; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); } tstamp_clock_realtime = tss.ts[0]; Example (Enable TX timestamping): .. code-block:: int flags = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_TX_COMPLETION | SOF_TIMESTAMPING_OPT_ID; setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)); Example (Read TX timestamps): .. code-block:: union { char buf[2 * CMSG_SPACE(sizeof(struct scm_timestamping))]; struct cmsghdr align; } control; struct iovec data = { .iov_base = NULL, .iov_len = 0 }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct cmsghdr *cmsg; struct scm_timestamping tss; struct sock_extended_err serr; int res; res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT); if (res < 0) goto error; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); else if (cmsg->cmsg_level == SOL_BLUETOOTH && cmsg->cmsg_type == BT_SCM_ERROR) memcpy(&serr, CMSG_DATA(cmsg), sizeof(serr)); } tstamp_clock_realtime = tss.ts[0]; tstamp_type = serr->ee_info; /* SCM_TSTAMP_SND or SCM_TSTAMP_COMPLETION */ tstamp_seqnum = serr->ee_data; IOCTLS ====== The following ioctls with operation specific for L2CAP sockets are available. SIOCETHTOOL (since Linux 6.16-rc1) ---------------------------------- Supports only command `ETHTOOL_GET_TS_INFO`, which may be used to query supported `SOF_TIMESTAMPING_*` flags. The `SOF_TIMESTAMPING_OPT_*` flags are always available as applicable. Example: .. code-block:: #include #include #include #include #include ... struct ifreq ifr = {}; struct ethtool_ts_info cmd = {}; int sk; snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "hci0"); ifr.ifr_data = (void *)&cmd; cmd.cmd = ETHTOOL_GET_TS_INFO; sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (sk < 0) goto error; if (ioctl(sk, SIOCETHTOOL, &ifr)) goto error; sof_available = cmd.so_timestamping; RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org SEE ALSO ======== socket(7), l2test(1) bluez-5.87/doc/PaxHeaders/org.bluez.AdminPolicySet.50000644000000000000000000000005015221755317017310 xustar0020 atime=1783093967 20 ctime=1783094112 bluez-5.87/doc/org.bluez.AdminPolicySet.50000644000000000000000000000422715221755317016776 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.AdminPolicySet" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.AdminPolicySet \- BlueZ D-Bus AdminPolicySet API documentation .SH Description .sp This API provides methods to control the behavior of \fBbluetoothd(8)\fP as an administrator. .sp Interface AdminPolicySet1 provides methods to set policies. Once the policy is set successfully, it will affect all clients and stay persistently even after restarting \fBbluetoothd(8)\fP\&. The only way to clear it is to overwrite the policy with the same method. .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.AdminPolicySet1 [experimental] .TP .B Object path [variable prefix]/{hci0,hci1,...} .TP .B Used by \fBbluetoothctl\-admin(1)\fP .UNINDENT .SS Methods .SS void SetServiceAllowList(array{string} UUIDs) .sp Sets the service allowlist by specifying service UUIDs. .sp When called, \fBbluetoothd(8)\fP will block incoming and outgoing connections to the service not in UUIDs for all of the clients. .sp Any subsequent calls to this method will supersede any previously set allowlist values. Calling this method with an empty array will allow any service UUIDs to be used. .sp The default value is an empty array. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > admin.allow [clear/uuid1 uuid2 ...] .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.obex.FileTransfer.50000644000000000000000000000005015221755373017746 xustar0020 atime=1783094011 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.FileTransfer.50000644000000000000000000001015315221755373017427 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.obex.FileTransfer" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.obex.FileTransfer \- BlueZ D-Bus OBEX FileTransfer API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez.obex .TP .B Interface org.bluez.obex.FileTransfer1 .TP .B Object path [Session object path] .UNINDENT .SS Methods .SS void ChangeFolder(string folder) .sp Changes the current folder of the remote device. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS void CreateFolder(string folder) .sp Creates a new folder in the remote device. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS array{dict} ListFolder() .sp Returns a dictionary containing information about the current folder content. .sp Possible return values: .INDENT 0.0 .TP .B string Name Object name in UTF\-8 format. .TP .B string Type Either \(dqfolder\(dq or \(dqfile\(dq. .TP .B uint64 Size Object size or number of items in folder. .TP .B string Permission Group, owner and other permission. .TP .B uint64 Modified Last change. .TP .B uint64 Accessed Last access. .TP .B uint64 Created Creation date. .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS object, dict GetFile(string targetfile, string sourcefile) .sp Copies the contents of the source file (from remote device) to the target file (on local filesystem). .sp If an empty target file is given, a name will be automatically generated for the temporary file. .sp The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. .sp The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see \fBorg.bluez.obex.Transfer(5)\fP for the possible list of properties. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS object, dict PutFile(string sourcefile, string targetfile) .sp Copies the contents of the source file (from local filesystem) to the target file (on remote device). .sp The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. .sp The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see \fBorg.bluez.obex.Transfer(5)\fP for the possible list of properties. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS void CopyFile(string sourcefile, string targetfile) .sp Copies the contents from source file to target file on the remote device. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS void MoveFile(string sourcefile, string targetfile) .sp Moves a file within the remote device from source file to the target file. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS void Delete(string file) .sp Deletes the specified file/folder. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.MediaFolder.rst0000644000000000000000000000005015142177620017240 xustar0020 atime=1783093976 20 ctime=1783094111 bluez-5.87/doc/org.bluez.MediaFolder.rst0000644000000000000000000000477215142177620016733 0ustar00rootroot===================== org.bluez.MediaFolder ===================== ----------------------------------------- BlueZ D-Bus MediaFolder API documentation ----------------------------------------- :Version: BlueZ :Date: September 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: unique name (Target role) org.bluez (Controller role) :Interface: org.bluez.MediaFolder1 :Object path: freely definable (Target role) [variable prefix]/{hci0,hci1,...}/dev_{BDRADDR}/player# (Controller role) :Used by: **bluetoothctl-player(1)** Methods ------- object Search(string value, dict filter) ```````````````````````````````````````` Return a folder object containing the search result. To list the items found use the folder object returned and pass to **ChangeFolder**. Possible Errors: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: Examples: :bluetoothctl: [player] > search array{objects, properties} ListItems(dict filter) ````````````````````````````````````````````````` Return a list of items found Possible Errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: Examples: :bluetoothctl: [player] > list-items [start] [end] void ChangeFolder(object folder) ```````````````````````````````` Change current folder. Note: By changing folder the items of previous folder might be destroyed and have to be listed again, the exception is NowPlaying folder which should be always present while the player is active. Possible Errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.NotSupported: :org.bluez.Error.Failed: Examples: :bluetoothctl: [player] > change-folder Properties ---------- uint32 NumberOfItems [readonly] ``````````````````````````````` Number of items in the folder string Name [readonly] `````````````````````` Folder name: Possible values: :"/Filesystem/...": Filesystem scope :"/NowPlaying/...": NowPlaying scope Note: /NowPlaying folder might not be listed if player is stopped, folders created by Search are virtual so once another Search is perform or the folder is changed using ChangeFolder it will no longer be listed. Filters ------- :uint32 Start: Offset of the first item. Default value: 0 :uint32 End: Offset of the last item. Default value: NumbeOfItems :array{string} Attributes: Item properties that should be included in the list. Possible Values: "title", "artist", "album", "genre", "number-of-tracks", "number", "duration" Default Value: All bluez-5.87/doc/PaxHeaders/btmon-hfp.rst0000644000000000000000000000005015200607060015040 xustar0020 atime=1783092683 20 ctime=1783094112 bluez-5.87/doc/btmon-hfp.rst0000644000000000000000000003513115200607060014524 0ustar00rootroot.. This file is included by btmon-classic-audio.rst. HFP: Hands-Free Profile ------------------------- HFP carries voice audio over SCO/eSCO connections with call control over RFCOMM. btmon decodes RFCOMM framing and SCO/eSCO connection setup. AT commands (the HFP control protocol) appear as raw hex dumps within RFCOMM data frames -- btmon does not parse AT command syntax. SDP Discovery ~~~~~~~~~~~~~~ HFP uses SDP to discover the remote device's Hands-Free or Audio Gateway service record and its RFCOMM channel number:: < ACL Data TX: Handle 1 flags 0x00 dlen 25 Channel: 64 len 21 [PSM 1 mode Basic (0x00)] {chan 0} SDP: Service Search Attribute Request (0x06) tid 1 len 16 Search pattern: [len 5] Sequence (6) with 3 byte(s) [8 extra bits] len 5 UUID (3) with 2 byte(s) [0 extra bits] len 3 Handsfree Audio Gateway (0x111f) Max record count: 65535 Attribute list: [len 5] Sequence (6) with 3 byte(s) [8 extra bits] len 5 Unsigned Integer (1) with 4 byte(s) [0 extra bits] len 5 0x0000ffff Continuation state: 0 The response contains the service record with the RFCOMM channel:: > ACL Data RX: Handle 1 flags 0x02 dlen 89 Channel: 64 len 85 [PSM 1 mode Basic (0x00)] {chan 0} SDP: Service Search Attribute Response (0x07) tid 1 len 80 Attribute bytes: 77 Attribute list: [len 75] {position 0} Attribute: Service Class ID List (0x0001) [len 2] Handsfree Audio Gateway (0x111f) Attribute: Protocol Descriptor List (0x0004) [len 2] L2CAP (0x0100) RFCOMM (0x0003) Channel: 1 Attribute: Bluetooth Profile Descriptor List (0x0009) [len 2] Handsfree (0x111e) Version: 0x0108 Continuation state: 0 Key fields to extract: - **Service Class** -- ``Handsfree (0x111e)`` for HF role, ``Handsfree Audio Gateway (0x111f)`` for AG role - **RFCOMM Channel** -- The channel number under ``RFCOMM (0x0003)`` in the Protocol Descriptor List (e.g., channel 1) - **Profile Version** -- Under the Bluetooth Profile Descriptor List (e.g., ``0x0108`` = HFP 1.8, ``0x0109`` = HFP 1.9) RFCOMM Connection Setup ~~~~~~~~~~~~~~~~~~~~~~~~ RFCOMM runs over L2CAP PSM 3. The connection proceeds in stages: multiplexer session on DLCI 0, parameter negotiation, then the data channel on the target DLCI. **L2CAP connection for RFCOMM**:: < ACL Data TX: Handle 1 flags 0x00 dlen 12 L2CAP: Connection Request (0x02) ident 2 len 4 PSM: 3 (0x0003) Source CID: 65 > ACL Data RX: Handle 1 flags 0x02 dlen 16 L2CAP: Connection Response (0x03) ident 2 len 8 Destination CID: 65 Source CID: 65 Result: Connection successful (0x0000) Status: No further information available (0x0000) **SABM/UA on DLCI 0** (multiplexer session):: < ACL Data TX: Handle 1 flags 0x00 dlen 12 Channel: 65 len 4 [PSM 3 mode Basic (0x00)] {chan 1} RFCOMM: Set Async Balance Mode (SABM) (0x2f) Address: 0x03 cr 1 dlci 0x00 Control: 0x3f poll/final 1 Length: 0 FCS: 0x1c > ACL Data RX: Handle 1 flags 0x02 dlen 12 Channel: 65 len 4 [PSM 3 mode Basic (0x00)] {chan 1} RFCOMM: Unnumbered Ack (UA) (0x63) Address: 0x03 cr 1 dlci 0x00 Control: 0x73 poll/final 1 Length: 0 FCS: 0xd7 **Parameter Negotiation** (MCC on DLCI 0):: Channel: 65 len 14 [PSM 3 mode Basic (0x00)] {chan 1} RFCOMM: Unnumbered Info with Header Check (UIH) (0xef) Address: 0x03 cr 1 dlci 0x00 Control: 0xef poll/final 0 Length: 10 FCS: 0x70 MCC Message type: DLC Parameter Negotiation CMD (0x20) Length: 8 dlci 2 frame_type 0 credit_flow 15 pri 7 ack_timer 0 frame_size 127 max_retrans 0 credits 7 The DLCI in the PN command identifies the target channel. For RFCOMM channel N, DLCI = N * 2 (or N * 2 + 1 depending on the initiator role). **SABM/UA on target DLCI** (data channel):: Channel: 65 len 4 [PSM 3 mode Basic (0x00)] {chan 1} RFCOMM: Set Async Balance Mode (SABM) (0x2f) Address: 0x0b cr 1 dlci 0x02 Control: 0x3f poll/final 1 Length: 0 FCS: 0x59 Channel: 65 len 4 [PSM 3 mode Basic (0x00)] {chan 1} RFCOMM: Unnumbered Ack (UA) (0x63) Address: 0x0b cr 1 dlci 0x02 Control: 0x73 poll/final 1 Length: 0 FCS: 0x92 **Modem Status Command** (signals readiness):: Channel: 65 len 8 [PSM 3 mode Basic (0x00)] {chan 1} RFCOMM: Unnumbered Info with Header Check (UIH) (0xef) Address: 0x03 cr 1 dlci 0x00 Control: 0xef poll/final 0 Length: 4 FCS: 0x70 MCC Message type: Modem Status Command CMD (0x38) Length: 2 dlci 2 fc 0 rtc 1 rtr 1 ic 0 dv 1 AT Command Exchange (SLC Setup) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HFP uses AT commands over RFCOMM for call control and feature negotiation. btmon shows these as RFCOMM UIH frames with the AT command text visible in the hex dump. **AT command in RFCOMM UIH frame**:: Channel: 65 len 21 [PSM 3 mode Basic (0x00)] {chan 1} RFCOMM: Unnumbered Info with Header Check (UIH) (0xef) Address: 0x09 cr 0 dlci 0x02 Control: 0xff poll/final 1 Length: 14 FCS: 0x86 Credits: 1 41 54 2b 42 52 53 46 3d 32 30 35 35 0d AT+BRSF=2055. The AT command text is readable in the ASCII column of the hex dump (right side). The Service Level Connection (SLC) setup sequence exchanges device features and capabilities: 1. ``AT+BRSF=`` / ``+BRSF:`` -- Supported features bitmask exchange 2. ``AT+BAC=1,2`` -- Available codecs (if codec negotiation supported). Codec IDs: 1 = CVSD, 2 = mSBC, 3 = LC3-SWB 3. ``AT+CIND=?`` / ``+CIND:(...)`` -- Indicator mapping query 4. ``AT+CIND?`` / ``+CIND:values`` -- Current indicator values 5. ``AT+CMER=3,0,0,1`` -- Enable indicator status reporting 6. ``AT+CHLD=?`` / ``+CHLD:(0,1,2,3,4)`` -- Three-way calling support Key HFP feature bits (from ``AT+BRSF``): .. list-table:: :header-rows: 1 :widths: 10 25 25 * - Bit - HF Feature - AG Feature * - 0 - EC/NR - Three-way calling * - 1 - Three-way calling - EC/NR * - 2 - CLI presentation - Voice recognition * - 3 - Voice recognition - In-band ring tone * - 4 - Remote volume - Voice tag * - 5 - Enhanced call status - Reject call * - 6 - Enhanced call control - Enhanced call status * - 7 - Codec negotiation - Enhanced call control * - 8 - HF indicators - Extended error codes * - 9 - eSCO S4 (T2) - Codec negotiation * - 11 - - eSCO S4 (T2) Codec Connection Setup ~~~~~~~~~~~~~~~~~~~~~~~ When both sides support codec negotiation (feature bit 7 on HF, bit 9 on AG), the AG selects a codec before establishing the audio link. This appears as AT commands in RFCOMM UIH frames:: AG -> HF: +BCS:2 (select mSBC) HF -> AG: AT+BCS=2 (confirm mSBC) AG -> HF: OK HFP codec IDs (used in ``AT+BAC`` and ``AT+BCS``): .. list-table:: :header-rows: 1 :widths: 10 20 30 * - ID - Codec - Description * - 1 - CVSD - Narrow band (8 kHz), mandatory * - 2 - mSBC - Wide band speech (16 kHz) * - 3 - LC3-SWB - Super wide band (32 kHz), HFP 1.9+ These HFP-level codec IDs differ from HCI codec IDs. Voice Setting ~~~~~~~~~~~~~~ Before SCO/eSCO setup, the host configures the voice setting. For CVSD, the air coding format is CVSD; for mSBC or LC3-SWB, it must be set to Transparent Data:: < HCI Command: Write Voice Setting (0x0c|0x0026) plen 2 Setting: 0x0063 Input Coding: Linear Input Data Format: 2's complement Input Sample Size: 16-bit # of bits padding at MSB: 0 Air Coding Format: Transparent Data For CVSD:: < HCI Command: Write Voice Setting (0x0c|0x0026) plen 2 Setting: 0x0060 Input Coding: Linear Input Data Format: 2's complement Input Sample Size: 16-bit # of bits padding at MSB: 0 Air Coding Format: CVSD SCO/eSCO Connection Setup ~~~~~~~~~~~~~~~~~~~~~~~~~~ After codec negotiation, the host establishes a synchronous connection for voice audio. **Setup Synchronous Connection** (basic):: < HCI Command: Setup Synchronous Connection (0x01|0x0028) plen 17 Handle: 1 Transmit bandwidth: 8000 Receive bandwidth: 8000 Max latency: 13 Setting: 0x0063 Input Coding: Linear Input Data Format: 2's complement Input Sample Size: 16-bit # of bits padding at MSB: 0 Air Coding Format: Transparent Data Retransmission effort: Optimize for link quality (0x02) Packet type: 0x0008 EV3 may be used **Enhanced Setup Synchronous Connection** (codec-aware):: < HCI Command: Enhanced Setup Synchronous Connection (0x01|0x003d) plen 59 Handle: 1 Transmit bandwidth: 8000 Receive bandwidth: 8000 Transmit Coding Format: Codec: mSBC (0x05) Receive Coding Format: Codec: mSBC (0x05) Transmit Codec Frame Size: 60 Receive Codec Frame Size: 60 Input Coding Format: Codec: mSBC (0x05) Output Coding Format: Codec: mSBC (0x05) Input Coded Data Size: 16 Output Coded Data Size: 16 Input PCM Data Format: 2's complement Output PCM Data Format: 2's complement Input PCM Sample Payload MSB Position: 0 Output PCM Sample Payload MSB Position: 0 Input Data Path: HCI Output Data Path: HCI Input Transport Unit Size: 60 Output Transport Unit Size: 60 Max latency: 13 Packet type: 0x0008 EV3 may be used Retransmission effort: Optimize for link quality (0x02) HCI codec IDs displayed by btmon: .. list-table:: :header-rows: 1 :widths: 10 20 30 * - ID - btmon Name - Used For * - 0x02 - CVSD - Narrow band voice * - 0x03 - Transparent - Transparent data mode * - 0x04 - Linear PCM - Uncompressed PCM input/output * - 0x05 - mSBC - Wide band speech * - 0x06 - LC3 - Super wide band (LC3-SWB) **Synchronous Connection Complete** (result):: > HCI Event: Synchronous Connection Complete (0x2c) plen 17 Status: Success (0x00) Handle: 257 Address: 11:22:33:44:55:66 (OUI 11-22-33) Link type: eSCO (0x02) Transmission interval: 0x0c Retransmission window: 0x06 RX packet length: 60 TX packet length: 60 Air mode: Transparent (0x03) Key fields: - **Link type** -- ``SCO (0x00)`` for legacy, ``eSCO (0x02)`` for enhanced (used by mSBC and LC3-SWB) - **Air mode** -- ``CVSD (0x02)`` for narrow band, ``Transparent (0x03)`` for mSBC or LC3-SWB - **RX/TX packet length** -- 60 bytes typical for mSBC T2 settings SCO Data Packets ~~~~~~~~~~~~~~~~~ After the synchronous connection is established, voice data flows as SCO/eSCO data packets:: > BR-ESCO: Handle 257 flags 0x00 dlen 60 < BR-ESCO: Handle 257 flags 0x00 dlen 60 btmon labels packets based on the connection type established in Synchronous Connection Complete: - ``BR-SCO`` -- Legacy SCO connection - ``BR-ESCO`` -- Enhanced SCO connection (mSBC, LC3-SWB, or eSCO CVSD) SCO data payload is **not displayed by default**. It requires the ``--show-sco-data`` filter to see the hex dump of voice data. Codec-Specific Connection Summary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **CVSD (narrow band)**: - HFP codec ID: 1 - HCI codec: CVSD (0x02) - Voice setting: 0x0060 (Air Coding Format: CVSD) - Air mode: CVSD (0x02) - Link type: SCO or eSCO - Typical packet size: 48 bytes (HV3) or 60 bytes (EV3) **mSBC (wide band speech)**: - HFP codec ID: 2 - HCI codec: mSBC (0x05) - Voice setting: 0x0063 (Air Coding Format: Transparent Data) - Air mode: Transparent (0x03) - Link type: eSCO - Typical packet size: 60 bytes (EV3, T2 settings) **LC3-SWB (super wide band)**: - HFP codec ID: 3 - HCI codec: LC3 (0x06) - Voice setting: 0x0063 (Air Coding Format: Transparent Data) - Air mode: Transparent (0x03) - Link type: eSCO - Note: btmon displays ``LC3``, not ``LC3-SWB`` Automating HFP Analysis ~~~~~~~~~~~~~~~~~~~~~~~~~ **Identify HFP activity** -- look for RFCOMM on PSM 3:: grep -n "PSM: 3\|RFCOMM:" output.txt **Read AT commands** -- search hex dump ASCII for AT command patterns:: grep -n "AT+B\|AT+C\|+BRSF\|+CIND\|+CHLD\|+BCS" output.txt **Check codec negotiation** -- look for BCS (Bluetooth Codec Selection):: grep -n "+BCS\|AT+BAC\|AT+BCS" output.txt **Verify SCO/eSCO setup**:: grep -n "Setup Synchronous\|Enhanced Setup Synchronous\|Synchronous Connection Complete\|Write Voice Setting" output.txt **Check voice codec** -- confirm air mode and coding format:: grep -n "Air mode:\|Air Coding Format:\|Codec:" output.txt **Detect SCO failures** -- check Synchronous Connection Complete status:: grep -n "Synchronous Connection Complete" output.txt Then examine the next line for ``Status:``. Common failures: - ``Connection Rejected due to Limited Resources (0x0d)`` -- controller cannot allocate bandwidth - ``SCO Offset Rejected (0x2b)`` -- timing parameters rejected - ``SCO Interval Rejected (0x2c)`` -- interval parameters rejected **Track call state** -- look for CIEV indicator updates:: grep -n "+CIEV\|AT+CHUP\|ATD\|ATA\|AT+CLCC\|RING" output.txt **Full HFP diagnosis pattern**: 1. Find SDP query for UUID 0x111e/0x111f -- confirms HFP discovery 2. Find L2CAP Connection Request for PSM 3 -- RFCOMM channel setup 3. Find RFCOMM SABM/UA on DLCI 0 then target DLCI -- multiplexer and data channel 4. Find AT+BRSF in hex dumps -- feature exchange, check codec negotiation bit 5. Find AT+BAC in hex dumps -- available codecs reported 6. Find +BCS/AT+BCS in hex dumps -- codec selected for audio 7. Find Write Voice Setting -- verify air coding format matches codec 8. Find Setup Synchronous Connection or Enhanced variant -- SCO setup 9. Find Synchronous Connection Complete -- check Status, Link type, Air mode 10. Find BR-SCO or BR-ESCO packets -- voice data flowing bluez-5.87/doc/PaxHeaders/org.bluez.Media.rst0000644000000000000000000000005015142177620016104 xustar0020 atime=1783093971 20 ctime=1783094111 bluez-5.87/doc/org.bluez.Media.rst0000644000000000000000000000725015142177620015571 0ustar00rootroot=============== org.bluez.Media =============== ----------------------------------- BlueZ D-Bus Media API documentation ----------------------------------- :Version: BlueZ :Date: April 2025 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.Media1 :Object path: [variable prefix]/{hci0,hci1,...} :Used by: **bluetoothctl-endpoint(1)** Methods ------- void RegisterEndpoint(object endpoint, dict properties) ``````````````````````````````````````````````````````` Register a local end point to sender, the sender can register as many end points as it likes. Note: If the sender disconnects the end points are automatically unregistered. possible properties: :string UUID: UUID of the profile which the endpoint is for. UUID must be in the list of SupportedUUIDS. :byte Codec: Assigned number of codec that the endpoint implements. The values should match the profile specification which is indicated by the UUID. :uint32_t Vendor [Optional]: Vendor-specific Company ID, Codec ID tuple that the endpoint implements. It shall be set to appropriate value when Vendor Specific Codec (0xff) is used. :array{byte} Capabilities: Capabilities blob, it is used as it is so the size and byte order must match. :array{byte} Metadata [Optional]: Metadata blob, it is used as it is so the size and byte order must match. Possible Errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.NotSupported: emitted when interface for the end-point is disabled Examples: :bluetoothctl: > endpoint.register [capabilities...] void UnregisterEndpoint(object endpoint) ```````````````````````````````````````` Unregister sender end point. Examples: :bluetoothctl: > endpoint.unregister void RegisterPlayer(object player, dict properties) ``````````````````````````````````````````````````` Register a media player object to sender, the sender can register as many objects as it likes. Object must implement at least **org.mpris.MediaPlayer2.Player** as defined in MPRIS 2.2 spec: http://specifications.freedesktop.org/mpris-spec/latest/ Note: If the sender disconnects its objects are automatically unregistered. Possible Errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.NotSupported: void UnregisterPlayer(object player) ```````````````````````````````````` Unregister sender media player. void RegisterApplication(object root, dict options) ``````````````````````````````````````````````````` Register endpoints an player objects within root object which must implement **org.freedesktop.DBus.ObjectManager**. The application object path together with the D-Bus system bus connection ID define the identification of the application. Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.AlreadyExists: void UnregisterApplication(object application) `````````````````````````````````````````````` This unregisters the services that has been previously registered. The object path parameter must match the same value that has been used on registration. Possible errors: :org.bluez.Error.InvalidArguments: :org.bluez.Error.DoesNotExist: Properties ---------- array{string} SupportedUUIDs [readonly] ``````````````````````````````````````` List of 128-bit UUIDs that represents the supported Endpoint registration. array{string} SupportedFeatures [readonly] `````````````````````````````````````````` List of strings that represent supported special features. Possible values: :"tx-timestamping": Bluetooth TX timestamping in media stream sockets is supported by BlueZ and kernel. Applications may check kernel support for specific timestamp types via SIOCETHTOOL. bluez-5.87/doc/PaxHeaders/hci-protocol.rst0000644000000000000000000000005015142177620015561 xustar0020 atime=1783093918 20 ctime=1783094111 bluez-5.87/doc/hci-protocol.rst0000644000000000000000000000646715142177620015257 0ustar00rootroot=== hci === ---------------------- Bluetooth HCI protocol ---------------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: October 2024 :Manual section: 7 :Manual group: Linux System Administration SYNOPSIS ======== .. code-block:: #include #include #include hci_socket = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); DESCRIPTION =========== Bluetooth Host Controller Interface (HCI) is the standard protocol to communicate with Bluetooth adapters. HCI protocol provides a uniform command method for the Host to access Controller capabilities and to control connections to other Controllers. SOCKET ADDRESS ============== .. code-block:: struct sockaddr_hci { sa_family_t hci_family; unsigned short hci_dev; unsigned short hci_channel; }; Possible values for hci_channel: .. csv-table:: :header: "Define", "Value", "Description" :widths: auto **HCI_CHANNEL_RAW**, 0x00, Raw channel - Used for raw HCI communication **HCI_CHANNEL_USER**, 0x01, User channel - Used for userspace HCI communication (disables kernel processing) **HCI_CHANNEL_MONITOR**, 0x02, Monitor channel - Used for monitoring HCI traffic (btmon(1)) **HCI_CHANNEL_CONTROL**, 0x03, Control channel - Used to manage local adapters (bluetoothd(7)) **HCI_CHANNEL_LOGGING**, 0x04, Logging channel - Used to inject logging messages (bluetoothd(7)) Example: .. code-block:: struct sockaddr_hci addr; memset(&addr, 0, sizeof(addr)); addr.hci_family = AF_BLUETOOTH; addr.hci_dev = HCI_DEV_NONE; addr.hci_channel = HCI_CHANNEL_CONTROL; SOCKET OPTIONS ============== The socket options listed below can be set by using **setsockopt(2)** and read with **getsockopt(2)** with the socket level set to SOL_BLUETOOTH or SOL_HCI (HCI_FILTER). HCI_FILTER (since Linux 2.6) ---------------------------- Filter by HCI events, requires hci_channel to be set to HCI_CHANNEL_RAW, possible values: .. code-block:: struct hci_filter { uint32_t type_mask; uint32_t event_mask[2]; uint16_t opcode; }; Example: .. code-block:: struct hci_filter flt; memset(&flt, 0, sizeof(flt)); flt.type_mask = 1 << BT_H4_EVT_PKT; flt.event_mask[0] = 0xffffffff; flt.event_mask[1] = 0xffffffff; setsockopt(fd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)); BT_SNDBUF (since Linux 5.16) ---------------------------- Set send buffer size, requires hci_channel to be set to HCI_CHANNEL_MONITOR, HCI_CHANNEL_CONTROL or HCI_CHANNEL_LOGGING. Default value is 1028. Example: .. code-block:: uint16_t mtu = UINT16_MAX; int err; err = setsockopt(fd, SOL_BLUETOOTH, BT_SNDMTU, &mtu, sizeof(mtu)); BT_RCVBUF (since Linux 5.16) ---------------------------- Set receive buffer size, requires hci_channel to be set to HCI_CHANNEL_MONITOR, HCI_CHANNEL_CONTROL or HCI_CHANNEL_LOGGING. Default value is 1028. Example: .. code-block:: uint16_t mtu; socklen_t len; int err; len = sizeof(mtu); err = getsockopt(sock, SOL_BLUETOOTH, BT_RCVMTU, mtu, &len); RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org SEE ALSO ======== socket(7) bluez-5.87/doc/PaxHeaders/bdaddr.10000644000000000000000000000005015221755515013732 xustar0020 atime=1783094093 20 ctime=1783094113 bluez-5.87/doc/bdaddr.10000644000000000000000000000577715221755515013433 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "bdaddr" "1" "Sep 27, 2005" "BlueZ" "Linux System Administration" .SH Name bdaddr \- Utility for changing the Bluetooth device address .SH SYNOPSIS .sp \fBbdaddr\fP .sp \fBbdaddr\fP \-h .sp \fBbdaddr\fP [\-i <\fIdev\fP>] [\-r] [\-t] [\fInew_bdaddr\fP] .SH DESCRIPTION .sp \fBbdaddr(1)\fP is used to query or set the local Bluetooth device address (BD_ADDR). If run with no arguments, \fBbdaddr\fP prints the chip manufacturer\(aqs name, and the current BD_ADDR. If the IEEE OUI index file \(dqoui.txt\(dq is installed on the system, the BD_ADDR owner will be displayed. If the optional [\fInew_bdaddr\fP] argument is given, the device will be reprogrammed with that address. This can either be permanent or temporary, as specified by the \-t flag. In both cases, the device must be reset before the new address will become active. This can be done with a \(aqsoft\(aq reset by specifying the \-r flag, or a \(aqhard\(aq reset by removing and replugging the device. A \(aqhard\(aq reset will cause the address to revert to the current non\-volatile value. .sp \fBbdaddr\fP uses manufacturer specific commands to set the address, and is therefore device specific. For this reason, not all devices are supported, and not all options are supported on all devices. Current supported manufacturers are: \fBEricsson\fP, \fBCambridge Silicon Radio (CSR)\fP, \fBTexas Instruments (TI)\fP, \fBZeevo\fP and \fBST Microelectronics (ST)\fP\&. .SH OPTIONS .INDENT 0.0 .TP .B \-h Gives a list of possible commands. .TP .BI \-i \ Specify a particular device to operate on. If not specified, default is the first available device. .TP .B \-r Reset device and make new BD_ADDR active. CSR devices only. .TP .B \-t Temporary change. Do not write to non\-volatile memory. CSR devices only. .UNINDENT .SH FILES .INDENT 0.0 .TP .B /usr/share/misc/oui.txt IEEE Organizationally Unique Identifier consolidated file. Manually update from: \% .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Author Marcel Holtmann , Adam Laurie .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.AdminPolicyStatus.50000644000000000000000000000005015221755321020033 xustar0020 atime=1783093969 20 ctime=1783094112 bluez-5.87/doc/org.bluez.AdminPolicyStatus.50000644000000000000000000000336615221755321017524 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.AdminPolicyStatus" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.AdminPolicyStatus \- BlueZ D-Bus AdminPolicyStatus API documentation .SH Description .sp Interface AdminPolicyStatus1 provides readonly properties to indicate the current values of admin policy affecting the Adapter and Device objects. .SH Interface .SS Adapter .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.AdminPolicyStatus1 [experimental] .TP .B Object path [variable prefix]/{hci0,hci1,...} .TP .B Used by \fBbluetoothctl\-admin(1)\fP .UNINDENT .SS Device .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.AdminPolicyStatus1 [experimental] .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} .UNINDENT .SS Properties .SS array{string} ServiceAllowList [readonly, adapter\-only] .sp Current value of service allow list. .SS bool IsAffectedByPolicy [readonly, device\-only] .sp Indicate if there is any auto\-connect profile in this device is not allowed by admin policy. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.obex.Synchronization.rst0000644000000000000000000000005015063045254021221 xustar0020 atime=1783094013 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.Synchronization.rst0000644000000000000000000000420715063045254020705 0ustar00rootroot============================== org.bluez.obex.Synchronization ============================== -------------------------------------------------- BlueZ D-Bus OBEX Synchronization API documentation -------------------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez.obex :Interface: org.bluez.obex.Synchronization1 :Object path: [Session object path] Methods ------- void SetLocation(string location) ````````````````````````````````` Sets the phonebook object store location for other operations. Should be called before all the other operations. Possible location: :"int" ( "internal" which is default ): Store in the interval memory. :"sim{#}": Store in sim card number #. Possible errors: :org.bluez.obex.Error.InvalidArguments: object, dict GetPhonebook(string targetfile) ```````````````````````````````````````````` Retrieves an entire Phonebook Object store from remote device, and stores it in a local file. If an empty target file is given, a name will be automatically calculated for the temporary file. The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see **org.bluez.obex.Transfer(5)** for the possible list of properties. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: object, dict PutPhonebook(string sourcefile) ```````````````````````````````````````````` Sends an entire Phonebook Object store to remote device. The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see **org.bluez.obex.Transfer(5)** for the possible list of properties. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: bluez-5.87/doc/PaxHeaders/btattach.10000644000000000000000000000005015221755436014306 xustar0020 atime=1783094046 20 ctime=1783094113 bluez-5.87/doc/btattach.10000644000000000000000000000404715221755436013774 0ustar00rootroot'\" t .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "btattach" "1" "November 2015" "BlueZ" "Linux System Administration" .SH Name btattach \- Attach serial devices to BlueZ stack .SH SYNOPSIS .sp \fBbtattach\fP [\fB\-B\fP \fIdevice\fP] [\fB\-A\fP \fIdevice\fP] [\fB\-P\fP \fIprotocol\fP] [\fB\-R\fP] .SH DESCRIPTION .sp \fBbtattach(1)\fP is used to attach a serial UART to the Bluetooth stack as a transport interface. .SH OPTIONS .INDENT 0.0 .TP .BI \-B \ device\fR,\fB \ \-\-brder \ device Attach a BR/EDR controller .TP .BI \-A \ device\fR,\fB \ \-\-amp \ device Attach an AMP controller .TP .BI \-P \ protocol\fR,\fB \ \-\-protocol \ protocol Specify the protocol type for talking to the device. .sp Supported values are: .UNINDENT .TS box center; l. T{ \fIprotocol\fP T} _ T{ h4 T} _ T{ bcsp T} _ T{ 3wire T} _ T{ h4ds T} _ T{ ll T} _ T{ ath3k T} _ T{ intel T} _ T{ bcm T} _ T{ qca T} .TE .INDENT 0.0 .TP .BI \-S \ baudrate\fR,\fB \ \-\-speed \ baudrate Specify which baudrate to use .TP .B \-N\fP,\fB \-\-noflowctl Disable flow control .TP .B \-v\fP,\fB \-\-version Show version .TP .B \-h\fP,\fB \-\-help Show help options .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/rctest.rst0000644000000000000000000000005015142177620014463 xustar0020 atime=1783094042 20 ctime=1783094113 bluez-5.87/doc/rctest.rst0000644000000000000000000000316315142177620014147 0ustar00rootroot====== rctest ====== -------------- RFCOMM testing -------------- :Authors: - Maxim Krasnyansky - Marcel Holtmann - Filippo Giunchedi :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: Jul 6, 2009 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **rctest** <*MODE*> [*OPTIONS*] [*bdaddr*] DESCRIPTION =========== **rctest(1)** is used to test RFCOMM communications on the BlueZ stack MODES ===== -r listen and receive -w listen and send -d listen and dump incoming data -s connect and send -u connect and receive -n connect and be silent -c connect, disconnect, connect, ... -m multiple connects OPTIONS ======= -b bytes send/receive bytes -i device select the specified device -P channel select the specified channel -U uuid select the specified uuid -L seconds enable SO_LINGER options for seconds -W seconds enable deferred setup for seconds -B filename use data packets from filename -N num send num frames -C num send num frames before delay (default: 1) -D milliseconds delay milliseconds after sending num frames (default: 0) -A request authentication -E request encryption -S secure connection -M become central -T enable timestamps RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.obex.MessageAccess.rst0000644000000000000000000000005015063045254020526 xustar0020 atime=1783094017 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.MessageAccess.rst0000644000000000000000000001017615063045254020214 0ustar00rootroot============================ org.bluez.obex.MessageAccess ============================ ------------------------------------------------ BlueZ D-Bus OBEX MessageAccess API documentation ------------------------------------------------ :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez.obex :Interface: org.bluez.obex.MessageAccess1 :Object path: [Session object path] Methods ------- void SetFolder(string name) ``````````````````````````` Set working directory for current session. Possible name: Directory name or '..[/dir]'. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: array{dict} ListFolders(dict filter) ```````````````````````````````````` Returns a dictionary containing information about the current folder content. Possible filter: :uint16 Offset (default 0): Offset of the first item. :uint16 MaxCount (default 1024): Maximum number of items. Possible return: :string Name: Folder name Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: array{string} ListFilterFields() ```````````````````````````````` Return all available fields that can be used in **Fields** filter. Possible values: :"subject": :"timestamp": :"sender": :"sender-address": :"recipient": :"recipient-address": :"type": :"size": :"status": :"text": :"attachment": :"priority": :"read": :"sent": :"protected": :"replyto": Possible errors: None array{object, dict} ListMessages(string folder, dict filter) ```````````````````````````````````````````````````````````` Returns an array containing the messages objects found in the given subfolder of the current folder, or in the current folder if folder is empty. Possible Filters: :uint16 Offset (default 0): Offset of the first item. uint16 MaxCount (default 1024): Maximum number of items. :byte SubjectLength (default 256): Maximum length of the Subject property in the message. :array{string} Fields: Message fields, default is all values. See **ListFilterFields()** for possible values. :array{string} Types: Filter messages by type. Possible values: :"sms": :"email": :"mms": :string PeriodBegin: Filter messages by starting period. Possible values: Date in "YYYYMMDDTHHMMSS" format. :string PeriodEnd: Filter messages by ending period. Possible values: Date in "YYYYMMDDTHHMMSS" format. :boolean Read: Filter messages by read flag. Possible values: True for read or False for unread :string Recipient: Filter messages by recipient address. :string Sender: Filter messages by sender address. :boolean Priority: Filter messages by priority flag. Possible values: True for high priority or False for non-high priority. Each message is represented by an object path, which implements **org.bluez.obex.Message(5)** interface, followed by a dictionary of its properties. void UpdateInbox(void) `````````````````````` Requests remote to update its inbox. Possible errors: :org.bluez.obex.Error.Failed: object, dict PushMessage(string sourcefile, string folder, dict args) ````````````````````````````````````````````````````````````````````` Transfers a message (in bMessage format) to the remote device. The message is transferred either to the given subfolder of the current folder, or to the current folder if folder is empty. Possible args: Transparent, Retry, Charset The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see **org.bluez.obex.Transfer(5)** for the possible list of properties. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: Properties ---------- array{string} SupportedTypes [readonly] ``````````````````````````````````````` List of supported message types. Possible values: :"EMAIL": Email messages. :"SMS_GSM": GSM short messages. :"SMS_CDMA": CDMA short messages. :"MMS": MMS messages. :"IM": Instant messaging. bluez-5.87/doc/PaxHeaders/bluetoothctl-mgmt.rst0000644000000000000000000000005015200607060016620 xustar0020 atime=1783092683 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-mgmt.rst0000644000000000000000000011434415200607060016310 0ustar00rootroot================= bluetoothctl-mgmt ================= ------------------ Management Submenu ------------------ :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: July 2023 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **bluetoothctl** [--options] [mgmt.commands] .. note:: This submenu communicates directly with the kernel via the Bluetooth Management socket (mgmt), bypassing the BlueZ D-Bus API. Commands use the Bluetooth Management API defined in the kernel documentation. Mgmt Commands ============= select ------ Select a different index :Usage: **> select ** :: Controller index number (0-based) to select :Example Select controller index 0 (hci0): | **> select 0** :Example Select controller index 1 (hci1): | **> select 1** :Example Select controller index 2 (hci2): | **> select 2** revision -------- Get the MGMT Revision :Usage: **> revision** :Example Display MGMT API revision information: | **> revision** commands -------- List supported commands :Usage: **> commands** :Example List all supported management commands: | **> commands** config ------ Show configuration info :Usage: **> config** :Example Show current controller configuration: | **> config** info ---- Show controller info :Usage: **> info** :Example Show detailed controller information: | **> info** extinfo ------- Show extended controller info :Usage: **> extinfo** :Example Show extended controller information: | **> extinfo** auto-power ---------- Power all available features :Usage: **> auto-power** :Example Automatically power on all available controller features: | **> auto-power** power ----- Toggle powered state :Usage: **> power ** :: Power state - "on" to enable controller, "off" to disable :Example Power on the controller: | **> power on** :Example Power off the controller: | **> power off** discov ------ Toggle discoverable state :Usage: **> discov [timeout]** :: Discoverable mode - "yes" for general, "no" to disable, "limited" for limited discoverable :[timeout]: Optional timeout in seconds for discoverable mode duration :Example Make controller discoverable indefinitely: | **> discov yes** :Example Make controller non-discoverable: | **> discov no** :Example Enable limited discoverable mode: | **> discov limited** :Example Make discoverable for 30 seconds: | **> discov yes 30** :Example Make discoverable for 2 minutes: | **> discov yes 120** :Example Make discoverable for 5 minutes: | **> discov yes 300** :Example Limited discoverable for 60 seconds: | **> discov limited 60** connectable ----------- Toggle connectable state :Usage: **> connectable ** :: Connectable state - "on" to allow connections, "off" to reject :Example Make controller connectable: | **> connectable on** :Example Make controller non-connectable: | **> connectable off** fast-conn --------- Toggle fast connectable state :Usage: **> fast-conn ** :: Fast connectable state - "on" to enable fast connection mode, "off" to disable :Example Enable fast connectable mode: | **> fast-conn on** :Example Disable fast connectable mode: | **> fast-conn off** bondable -------- Toggle bondable state :Usage: **> bondable ** :: Bondable state - "on" to enable bonding capability, "off" to disable :Example Enable bonding capability: | **> bondable on** :Example Disable bonding capability: | **> bondable off** pairable -------- Toggle bondable state :Usage: **> pairable ** :: Pairable state - "on" to enable pairing capability, "off" to disable :Example Enable pairing capability: | **> pairable on** :Example Disable pairing capability: | **> pairable off** linksec ------- Toggle link level security :Usage: **> linksec ** :: Link level security - "on" to enable, "off" to disable :Example Enable link level security: | **> linksec on** :Example Disable link level security: | **> linksec off** ssp --- Toggle SSP mode :Usage: **> ssp ** :: Secure Simple Pairing mode - "on" to enable SSP, "off" to disable :Example Enable Secure Simple Pairing: | **> ssp on** :Example Disable Secure Simple Pairing: | **> ssp off** sc -- Toggle SC support :Usage: **> sc ** :: Secure Connections support - "on" to enable, "off" to disable, "only" for exclusive use :Example Enable Secure Connections support: | **> sc on** :Example Disable Secure Connections support: | **> sc off** :Example Use Secure Connections exclusively: | **> sc only** hs -- Toggle HS support :Usage: **> hs ** :: High Speed support - "on" to enable 802.11 High Speed, "off" to disable :Example Enable High Speed support: | **> hs on** :Example Disable High Speed support: | **> hs off** le -- Toggle LE support :Usage: **> le ** :: Low Energy support - "on" to enable LE functionality, "off" to disable :Example Enable Low Energy support: | **> le on** :Example Disable Low Energy support: | **> le off** advertising ----------- Toggle LE advertising :Usage: **> advertising ** :: LE advertising state - "on" to enable advertising, "off" to disable :Example Enable LE advertising: | **> advertising on** :Example Disable LE advertising: | **> advertising off** bredr ----- Toggle BR/EDR support :Usage: **> bredr ** :: BR/EDR support - "on" to enable Classic Bluetooth, "off" to disable :Example Enable BR/EDR (Classic Bluetooth) support: | **> bredr on** :Example Disable BR/EDR support: | **> bredr off** privacy ------- Toggle privacy support :Usage: **> privacy [irk]** :: Privacy support - "on" to enable privacy features, "off" to disable :[irk]: Optional 32-character hexadecimal Identity Resolving Key :Example Enable privacy with auto-generated IRK: | **> privacy on** :Example Disable privacy: | **> privacy off** :Example Enable privacy with specific IRK: | **> privacy on 0123456789abcdef0123456789abcdef** :Example Enable privacy with different IRK: | **> privacy on 1234567890abcdef1234567890abcdef** class ----- Set device major/minor class :Usage: **> class ** :: Major device class code (hexadecimal) :: Minor device class code (hexadecimal) :Example Set class to Audio/Video - Wearable Headset: | **> class 0x04 0x01** :Example Set class to Audio/Video - Hands-free: | **> class 0x04 0x02** :Example Set class to Audio/Video - Headphones: | **> class 0x04 0x06** :Example Set class to Audio/Video - VCR: | **> class 0x04 0x0B** :Example Set class to Computer - Desktop workstation: | **> class 0x01 0x01** :Example Set class to Computer - Server: | **> class 0x01 0x02** :Example Set class to Computer - Laptop: | **> class 0x01 0x03** :Example Set class to Computer - Handheld PC/PDA: | **> class 0x01 0x04** :Example Set class to Phone - Cellular: | **> class 0x02 0x01** :Example Set class to Phone - Cordless: | **> class 0x02 0x02** :Example Set class to Phone - Smart phone: | **> class 0x02 0x03** disconnect ---------- Disconnect device :Usage: **> disconnect [-t type] ** :[-t type]: Optional address type (public, random, le_public, le_random) :: Bluetooth address of device to disconnect :Example Disconnect device using public address: | **> disconnect 00:11:22:33:44:55** :Example Disconnect another device: | **> disconnect AA:BB:CC:DD:EE:FF** :Example Disconnect device with public address type: | **> disconnect -t public 00:11:22:33:44:55** :Example Disconnect device with random address type: | **> disconnect -t random AA:BB:CC:DD:EE:FF** :Example Disconnect LE device with public address: | **> disconnect -t le_public 11:22:33:44:55:66** :Example Disconnect LE device with random address: | **> disconnect -t le_random CC:DD:EE:FF:00:11** con --- List connections :Usage: **> con** :Example List all active connections: | **> con** find ---- Discover nearby devices :Usage: **> find [-l|-b] [-L]** :[-l|-b]: Discovery type - "-l" for LE only, "-b" for BR/EDR only (default: both) :[-L]: Limited discovery mode flag :Example Discover both LE and BR/EDR devices: | **> find** :Example Discover LE devices only: | **> find -l** :Example Discover BR/EDR devices only: | **> find -b** :Example Discover devices in limited mode: | **> find -L** :Example Discover LE devices in limited mode: | **> find -l -L** :Example Discover BR/EDR devices in limited mode: | **> find -b -L** find-service ------------ Discover nearby service :Usage: **> find-service [-u UUID] [-r RSSI_Threshold] [-l|-b]** :[-u UUID]: Service UUID to search for (16-bit, 32-bit, or 128-bit) :[-r RSSI_Threshold]: Minimum RSSI value in dBm for device filtering :[-l|-b]: Discovery type - "-l" for LE only, "-b" for BR/EDR only :Example Find devices with Generic Access service: | **> find-service -u 0x1800** :Example Find devices with Battery Service: | **> find-service -u 0x180F** :Example Find devices with custom service: | **> find-service -u 12345678-1234-5678-9abc-123456789abc** :Example Find devices with RSSI ≥ -60 dBm: | **> find-service -r -60** :Example Find devices with RSSI ≥ -80 dBm: | **> find-service -r -80** :Example Find Battery Service devices with RSSI ≥ -70 dBm: | **> find-service -u 0x180F -r -70** :Example Find Generic Access on LE devices with strong signal: | **> find-service -u 0x1800 -r -50 -l** :Example Find Serial Port Profile on BR/EDR devices: | **> find-service -u 0x1101 -b** :Example Find Battery Service on LE devices: | **> find-service -u 0x180F -l** stop-find --------- Stop discovery :Usage: **> stop-find [-l|-b]** :[-l|-b]: Discovery type to stop - "-l" for LE only, "-b" for BR/EDR only (default: all) :Example Stop all discovery: | **> stop-find** :Example Stop LE discovery only: | **> stop-find -l** :Example Stop BR/EDR discovery only: | **> stop-find -b** name ---- Set local name :Usage: **> name [shortname]** :: Complete local name for the controller :[shortname]: Optional shortened local name :Example Set complete local name: | **> name "My Bluetooth Device"** :Example Set name without spaces: | **> name MyComputer** :Example Set name with hyphens: | **> name "Home-Office-PC"** :Example Set both complete and shortened names: | **> name "My Long Device Name" MyDevice** :Example Set descriptive and short names: | **> name "Professional Workstation" ProWork** :Example Set audio device names: | **> name "Bluetooth Audio Headset" BT-Audio** pair ---- Pair with a remote device :Usage: **> pair [-c cap] [-t type] ** :[-c cap]: IO capability (DisplayOnly, DisplayYesNo, KeyboardOnly, NoInputNoOutput, KeyboardDisplay) :[-t type]: Address type (public, random, le_public, le_random) :: Bluetooth address of device to pair with :Example Pair with device using default settings: | **> pair 00:11:22:33:44:55** :Example Pair with another device: | **> pair AA:BB:CC:DD:EE:FF** :Example Pair with display-only IO capability: | **> pair -c DisplayOnly 00:11:22:33:44:55** :Example Pair with display and yes/no capability: | **> pair -c DisplayYesNo AA:BB:CC:DD:EE:FF** :Example Pair with keyboard-only capability: | **> pair -c KeyboardOnly 11:22:33:44:55:66** :Example Pair with no I/O capability: | **> pair -c NoInputNoOutput CC:DD:EE:FF:00:11** :Example Pair with keyboard and display capability: | **> pair -c KeyboardDisplay 22:33:44:55:66:77** :Example Pair with public address device: | **> pair -t public 00:11:22:33:44:55** :Example Pair with random address device: | **> pair -t random AA:BB:CC:DD:EE:FF** :Example Pair with LE public address device: | **> pair -t le_public 11:22:33:44:55:66** :Example Pair with LE random address device: | **> pair -t le_random CC:DD:EE:FF:00:11** :Example Pair LE device with keyboard/display capability: | **> pair -c KeyboardDisplay -t le_public 00:11:22:33:44:55** :Example Pair random address device with display/yes-no capability: | **> pair -c DisplayYesNo -t random AA:BB:CC:DD:EE:FF** cancelpair ---------- Cancel pairing :Usage: **> cancelpair [-t type] ** :[-t type]: Address type (public, random, le_public, le_random) :: Bluetooth address of device to cancel pairing with :Example Cancel ongoing pairing with device: | **> cancelpair 00:11:22:33:44:55** :Example Cancel pairing with another device: | **> cancelpair AA:BB:CC:DD:EE:FF** :Example Cancel pairing with public address device: | **> cancelpair -t public 00:11:22:33:44:55** :Example Cancel pairing with LE random address device: | **> cancelpair -t le_random AA:BB:CC:DD:EE:FF** unpair ------ Unpair device :Usage: **> unpair [-t type] ** :[-t type]: Address type (public, random, le_public, le_random) :: Bluetooth address of device to unpair :Example Remove pairing with device: | **> unpair 00:11:22:33:44:55** :Example Remove pairing with another device: | **> unpair AA:BB:CC:DD:EE:FF** :Example Unpair public address device: | **> unpair -t public 00:11:22:33:44:55** :Example Unpair LE public address device: | **> unpair -t le_public 11:22:33:44:55:66** :Example Unpair LE random address device: | **> unpair -t le_random CC:DD:EE:FF:00:11** keys ---- Load Link Keys :Usage: **> keys** :Example Load stored link keys: | **> keys** ltks ---- Load Long Term Keys :Usage: **> ltks** :Example Load stored LE long term keys: | **> ltks** irks ---- Load Identity Resolving Keys :Usage: **> irks [--local index] [--file file path]** :[--local index]: Local controller index to use :[--file file path]: Custom IRK configuration file path :Example Load IRKs with default local index and file: | **> irks** :Example Load IRKs for controller index 0: | **> irks --local 0** :Example Load IRKs for controller index 1: | **> irks --local 1** :Example Load IRKs from custom configuration file: | **> irks --file /etc/bluetooth/irks.conf** :Example Load IRKs from user file: | **> irks --file /home/user/bluetooth_irks.txt** :Example Load IRKs for hci0 from specific file: | **> irks --local 0 --file /etc/bluetooth/hci0_irks.conf** block ----- Block Device :Usage: **> block [-t type] ** :[-t type]: Address type (public, random, le_public, le_random) :: Bluetooth address of device to block :Example Block device using default address type: | **> block 00:11:22:33:44:55** :Example Block another device: | **> block AA:BB:CC:DD:EE:FF** :Example Block device with public address: | **> block -t public 00:11:22:33:44:55** :Example Block device with random address: | **> block -t random AA:BB:CC:DD:EE:FF** :Example Block LE device with public address: | **> block -t le_public 11:22:33:44:55:66** :Example Block LE device with random address: | **> block -t le_random CC:DD:EE:FF:00:11** unblock ------- Unblock Device :Usage: **> unblock [-t type] ** :[-t type]: Address type (public, random, le_public, le_random) :: Bluetooth address of device to unblock :Example Unblock device using default address type: | **> unblock 00:11:22:33:44:55** :Example Unblock another device: | **> unblock AA:BB:CC:DD:EE:FF** :Example Unblock device with public address: | **> unblock -t public 00:11:22:33:44:55** :Example Unblock device with random address: | **> unblock -t random AA:BB:CC:DD:EE:FF** :Example Unblock LE device with public address: | **> unblock -t le_public 11:22:33:44:55:66** :Example Unblock LE device with random address: | **> unblock -t le_random CC:DD:EE:FF:00:11** add-uuid -------- Add UUID :Usage: **> add-uuid ** :: Service UUID (16-bit, 32-bit, or 128-bit format) :: Service class hint bitmask (hexadecimal) :Example Add Serial Port Profile with object transfer hint: | **> add-uuid 0x1101 0x100000** :Example Add A2DP with audio hint: | **> add-uuid 0x110E 0x200000** :Example Add HFP with audio hint: | **> add-uuid 0x111E 0x200000** :Example Add custom service UUID with no hint: | **> add-uuid 12345678-1234-5678-9abc-123456789abc 0x000000** :Example Add custom UUID with object transfer hint: | **> add-uuid ABCD1234-ABCD-1234-ABCD-123456789ABC 0x100000** :Example Add Generic Access with no specific hint: | **> add-uuid 0x1800 0x000000** :Example Add Battery Service with no hint: | **> add-uuid 0x180F 0x000000** rm-uuid ------- Remove UUID :Usage: **> rm-uuid ** :: Service UUID to remove (16-bit, 32-bit, or 128-bit format) :Example Remove Serial Port Profile UUID: | **> rm-uuid 0x1101** :Example Remove A2DP UUID: | **> rm-uuid 0x110E** :Example Remove custom service UUID: | **> rm-uuid 12345678-1234-5678-9abc-123456789abc** clr-uuids --------- Clear UUIDs :Usage: **> clr-uuids** :Example Clear all registered UUIDs: | **> clr-uuids** local-oob --------- Local OOB data :Usage: **> local-oob** :Example Generate and display local OOB authentication data: | **> local-oob** remote-oob ---------- Remote OOB data :Usage: **> remote-oob [-t ] [-r ] [-h ] [-R ] [-H ] ** :[-t ]: Address type (public, random, le_public, le_random) :[-r ]: P-192 random value (32 hexadecimal characters) :[-h ]: P-192 hash value (32 hexadecimal characters) :[-R ]: P-256 random value (64 hexadecimal characters) :[-H ]: P-256 hash value (64 hexadecimal characters) :: Remote device Bluetooth address :Example Set remote OOB data for device (minimal): | **> remote-oob 00:11:22:33:44:55** :Example Set P-192 random and hash values: | **> remote-oob -r 0123456789abcdef0123456789abcdef -h fedcba9876543210fedcba9876543210 00:11:22:33:44:55** :Example Set P-256 random and hash values: | **> remote-oob -R 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef -H fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210 AA:BB:CC:DD:EE:FF** :Example Set OOB data for LE public address device: | **> remote-oob -t le_public 11:22:33:44:55:66** :Example Set OOB data for LE random address device: | **> remote-oob -t le_random CC:DD:EE:FF:00:11** :Example Set complete OOB data with both P-192 and P-256 values: | **> remote-oob -t public -r 0123456789abcdef0123456789abcdef -h fedcba9876543210fedcba9876543210 -R 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef -H fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210 00:11:22:33:44:55** did --- Set Device ID :Usage: **> did :::** ::::: Device ID in format source:vendor:product:version (all hexadecimal) :Example Set USB source with Linux Foundation vendor ID: | **> did 0x0002:0x1D6B:0x0001:0x0100** :Example Set USB device with Cypress vendor ID: | **> did 0x0002:0x04B4:0x1234:0x0200** :Example Set Bluetooth SIG source with Broadcom vendor: | **> did 0x0001:0x000F:0x0001:0x0100** :Example Set Bluetooth SIG source with Apple vendor: | **> did 0x0001:0x004C:0x0001:0x0100** :Example Set custom USB device ID: | **> did 0x0002:0xFFFF:0x1234:0x5678** :Example Set Qualcomm Bluetooth device: | **> did 0x0001:0x05F1:0xABCD:0x0001** static-addr ----------- Set static address :Usage: **> static-addr
** :
: Static random Bluetooth address (must have bits 47-46 set to 11) :Example Set static random address (bit 47=1, bit 46=1): | **> static-addr C0:00:00:00:00:01** :Example Set another static random address: | **> static-addr D0:11:22:33:44:55** :Example Set different static address: | **> static-addr E0:AA:BB:CC:DD:EE** public-addr ----------- Set public address :Usage: **> public-addr
** :
: Public Bluetooth address to set for the controller :Example Set public Bluetooth address: | **> public-addr 00:11:22:33:44:55** :Example Set different public address: | **> public-addr AA:BB:CC:DD:EE:FF** :Example Set custom public address: | **> public-addr 12:34:56:78:9A:BC** ext-config ---------- External configuration :Usage: **> ext-config ** :: External configuration - "on" to enable external config, "off" to disable :Example Enable external configuration: | **> ext-config on** :Example Disable external configuration: | **> ext-config off** debug-keys ---------- Toggle debug keys :Usage: **> debug-keys ** :: Debug keys support - "on" to enable debug keys, "off" to disable :Example Enable debug key support: | **> debug-keys on** :Example Disable debug key support: | **> debug-keys off** conn-info --------- Get connection information :Usage: **> conn-info [-t type] ** :[-t type]: Address type (public, random, le_public, le_random) :: Bluetooth address of connected device :Example Get connection information for device: | **> conn-info 00:11:22:33:44:55** :Example Get info for another device: | **> conn-info AA:BB:CC:DD:EE:FF** :Example Get info for public address device: | **> conn-info -t public 00:11:22:33:44:55** :Example Get info for LE public address device: | **> conn-info -t le_public 11:22:33:44:55:66** :Example Get info for LE random address device: | **> conn-info -t le_random CC:DD:EE:FF:00:11** io-cap ------ Set IO Capability :Usage: **> io-cap ** :: IO capability (DisplayOnly, DisplayYesNo, KeyboardOnly, NoInputNoOutput, KeyboardDisplay) :Example Set IO capability to display only: | **> io-cap DisplayOnly** :Example Set IO capability to display with yes/no: | **> io-cap DisplayYesNo** :Example Set IO capability to keyboard only: | **> io-cap KeyboardOnly** :Example Set IO capability to no input/output: | **> io-cap NoInputNoOutput** :Example Set IO capability to keyboard and display: | **> io-cap KeyboardDisplay** scan-params ----------- Set Scan Parameters :Usage: **> scan-params ** :: Scan interval in hexadecimal (0x0004 to 0x4000, units of 0.625ms) :: Scan window in hexadecimal (0x0004 to 0x4000, units of 0.625ms) :Example Set fast scan (16ms interval, 16ms window): | **> scan-params 0x0010 0x0010** :Example Set moderate fast scan (32ms interval, 32ms window): | **> scan-params 0x0020 0x0020** :Example Set balanced scan (96ms interval, 48ms window): | **> scan-params 0x0060 0x0030** :Example Set slow scan (256ms interval, 80ms window): | **> scan-params 0x0100 0x0050** :Example Set background scan (2.56s interval, 18ms window): | **> scan-params 0x0800 0x0012** :Example Set very slow background scan: | **> scan-params 0x1000 0x0020** get-clock --------- Get Clock Information :Usage: **> get-clock [address]** :[address]: Optional remote device Bluetooth address (omit for local clock) :Example Get local Bluetooth clock information: | **> get-clock** :Example Get clock information for remote device: | **> get-clock 00:11:22:33:44:55** :Example Get clock for another remote device: | **> get-clock AA:BB:CC:DD:EE:FF** add-device ---------- Add Device :Usage: **> add-device [-a action] [-t type]
** :[-a action]: Device action (auto, allow, deny) - "auto" for auto-connect, "allow" for allow-only, "deny" to block :[-t type]: Address type (public, random, le_public, le_random) :
: Bluetooth address of device to add :Example Add device with auto-connect action and default type: | **> add-device 00:11:22:33:44:55** :Example Add another device with defaults: | **> add-device AA:BB:CC:DD:EE:FF** :Example Add device with auto-connect action: | **> add-device -a auto 00:11:22:33:44:55** :Example Add device with allow-only action (no auto-connect): | **> add-device -a allow 11:22:33:44:55:66** :Example Add device with deny action (blocked): | **> add-device -a deny CC:DD:EE:FF:00:11** :Example Add device with public address type: | **> add-device -t public 00:11:22:33:44:55** :Example Add device with random address type: | **> add-device -t random AA:BB:CC:DD:EE:FF** :Example Add LE device with public address: | **> add-device -t le_public 11:22:33:44:55:66** :Example Add LE device with random address: | **> add-device -t le_random CC:DD:EE:FF:00:11** :Example Add LE public device with auto-connect: | **> add-device -a auto -t le_public 00:11:22:33:44:55** :Example Add random address device with allow action: | **> add-device -a allow -t random AA:BB:CC:DD:EE:FF** :Example Add LE random device with deny action: | **> add-device -a deny -t le_random CC:DD:EE:FF:00:11** del-device ---------- Remove Device :Usage: **> del-device [-t type]
** :[-t type]: Address type (public, random, le_public, le_random) :
: Bluetooth address of device to remove :Example Remove device using default address type: | **> del-device 00:11:22:33:44:55** :Example Remove another device: | **> del-device AA:BB:CC:DD:EE:FF** :Example Remove device with public address type: | **> del-device -t public 00:11:22:33:44:55** :Example Remove device with random address type: | **> del-device -t random AA:BB:CC:DD:EE:FF** :Example Remove LE device with public address: | **> del-device -t le_public 11:22:33:44:55:66** :Example Remove LE device with random address: | **> del-device -t le_random CC:DD:EE:FF:00:11** clr-devices ----------- Clear Devices :Usage: **> clr-devices** :Example Clear all devices from the device list: | **> clr-devices** bredr-oob --------- Local OOB data (BR/EDR) :Usage: **> bredr-oob** :Example Generate and display local BR/EDR OOB authentication data: | **> bredr-oob** le-oob ------ Local OOB data (LE) :Usage: **> le-oob** :Example Generate and display local LE OOB authentication data: | **> le-oob** advinfo ------- Show advertising features :Usage: **> advinfo** :Example Display advertising capabilities and supported features: | **> advinfo** advsize ------- Show advertising size info :Usage: **> advsize [options] ** :[options]: Advertising options flags :: Advertising instance identifier (0-based) :Example Show advertising data size information for instance 0: | **> advsize 0** :Example Show size information for advertising instance 1: | **> advsize 1** :Example Show size information for advertising instance 5: | **> advsize 5** add-adv ------- Add advertising instance :Usage: **> add-adv [options] ** :[options]: Advertising options (-c connectable, -s scannable, -g general discoverable, -l limited discoverable, -m managed flags, -p TX power) :: Advertising instance identifier (0-based) :Example Add advertising instance 0 with default settings: | **> add-adv 0** :Example Add advertising instance 1: | **> add-adv 1** :Example Add advertising instance 5: | **> add-adv 5** :Example Add connectable advertising instance 0: | **> add-adv -c 0** :Example Add scannable advertising instance 1: | **> add-adv -s 1** :Example Add general discoverable advertising instance 2: | **> add-adv -g 2** :Example Add limited discoverable advertising instance 3: | **> add-adv -l 3** :Example Add managed flags advertising instance 4: | **> add-adv -m 4** :Example Add TX power advertising instance 5: | **> add-adv -p 5** :Example Add connectable and scannable advertising instance: | **> add-adv -c -s 0** :Example Add general discoverable with TX power: | **> add-adv -g -p 1** :Example Add connectable, general discoverable, and scannable: | **> add-adv -c -g -s 2** rm-adv ------ Remove advertising instance :Usage: **> rm-adv ** :: Advertising instance identifier (0-based) to remove :Example Remove advertising instance 0: | **> rm-adv 0** :Example Remove advertising instance 1: | **> rm-adv 1** :Example Remove advertising instance 5: | **> rm-adv 5** clr-adv ------- Clear advertising instances :Usage: **> clr-adv** :Example Clear all advertising instances: | **> clr-adv** add-ext-adv-params ------------------ Add extended advertising params :Usage: **> add-ext-adv-params [options] ** :[options]: Extended advertising options (-c connectable, -s scannable, -d directed, -h high duty cycle, -l legacy PDU, -a anonymous, -i include TX power) :: Extended advertising instance identifier (0-based) :Example Add extended advertising parameters for instance 0: | **> add-ext-adv-params 0** :Example Add extended advertising parameters for instance 1: | **> add-ext-adv-params 1** :Example Add connectable extended advertising for instance 0: | **> add-ext-adv-params -c 0** :Example Add scannable extended advertising for instance 1: | **> add-ext-adv-params -s 1** :Example Add directed extended advertising for instance 2: | **> add-ext-adv-params -d 2** :Example Add high duty cycle extended advertising for instance 3: | **> add-ext-adv-params -h 3** :Example Add legacy PDU extended advertising for instance 4: | **> add-ext-adv-params -l 4** :Example Add anonymous extended advertising for instance 5: | **> add-ext-adv-params -a 5** :Example Add include TX power extended advertising for instance 6: | **> add-ext-adv-params -i 6** :Example Add connectable and scannable extended advertising: | **> add-ext-adv-params -c -s 0** :Example Add connectable extended advertising with TX power: | **> add-ext-adv-params -c -i 1** :Example Add scannable legacy PDU extended advertising: | **> add-ext-adv-params -s -l 2** add-ext-adv-data ---------------- Add extended advertising data :Usage: **> add-ext-adv-data [options] ** :[options]: Data options (-s for scan response data, -c for complete data) :: Extended advertising instance identifier (0-based) :Example Add advertising data to extended instance 0: | **> add-ext-adv-data 0** :Example Add advertising data to extended instance 1: | **> add-ext-adv-data 1** :Example Add scan response data to extended instance 0: | **> add-ext-adv-data -s 0** :Example Add scan response data to extended instance 1: | **> add-ext-adv-data -s 1** :Example Add complete advertising data to instance 0: | **> add-ext-adv-data -c 0** :Example Add complete scan response data to instance 1: | **> add-ext-adv-data -c -s 1** appearance ---------- Set appearance :Usage: **> appearance ** :: Appearance value (16-bit integer) representing device type :Example Set appearance to Unknown: | **> appearance 0** :Example Set appearance to Generic Phone: | **> appearance 64** :Example Set appearance to Generic Computer: | **> appearance 128** :Example Set appearance to Generic Audio/Video device: | **> appearance 832** :Example Set appearance to Speaker: | **> appearance 833** :Example Set appearance to Microphone: | **> appearance 834** :Example Set appearance to Headset: | **> appearance 835** :Example Set appearance to Headphones: | **> appearance 836** :Example Set appearance to Generic HID: | **> appearance 960** :Example Set appearance to Keyboard: | **> appearance 961** :Example Set appearance to Mouse: | **> appearance 962** :Example Set appearance to Joystick: | **> appearance 963** :Example Set appearance to Generic Health Sensor: | **> appearance 1344** :Example Set appearance to Heart Rate Sensor: | **> appearance 1345** :Example Set appearance to Blood Pressure Monitor: | **> appearance 1346** :Example Set appearance to Generic Sports and Fitness: | **> appearance 1472** :Example Set appearance to Location Display: | **> appearance 1473** :Example Set appearance to Location Navigation Display: | **> appearance 1474** phy --- Get/Set PHY Configuration :Usage: **> phy [LE1MTX] [LE1MRX] [LE2MTX] [LE2MRX] [LECODEDTX] [LECODEDRX] [BR1M1SLOT] [BR1M3SLOT] [BR1M5SLOT][EDR2M1SLOT] [EDR2M3SLOT] [EDR2M5SLOT][EDR3M1SLOT] [EDR3M3SLOT] [EDR3M5SLOT]** :[PHY flags]: PHY configuration flags to enable (LE1MTX, LE1MRX, LE2MTX, LE2MRX, LECODEDTX, LECODEDRX, BR1M1SLOT, etc.) :Example Display current PHY configuration: | **> phy** :Example Enable LE 1M TX PHY: | **> phy LE1MTX** :Example Enable LE 1M RX PHY: | **> phy LE1MRX** :Example Enable LE 2M TX PHY: | **> phy LE2MTX** :Example Enable LE 2M RX PHY: | **> phy LE2MRX** :Example Enable LE Coded TX PHY: | **> phy LECODEDTX** :Example Enable LE Coded RX PHY: | **> phy LECODEDRX** :Example Enable BR 1M 1-slot packets: | **> phy BR1M1SLOT** :Example Enable BR 1M 3-slot packets: | **> phy BR1M3SLOT** :Example Enable BR 1M 5-slot packets: | **> phy BR1M5SLOT** :Example Enable EDR 2M 1-slot packets: | **> phy EDR2M1SLOT** :Example Enable EDR 2M 3-slot packets: | **> phy EDR2M3SLOT** :Example Enable EDR 2M 5-slot packets: | **> phy EDR2M5SLOT** :Example Enable EDR 3M 1-slot packets: | **> phy EDR3M1SLOT** :Example Enable EDR 3M 3-slot packets: | **> phy EDR3M3SLOT** :Example Enable EDR 3M 5-slot packets: | **> phy EDR3M5SLOT** :Example Enable LE 1M TX and RX PHYs: | **> phy LE1MTX LE1MRX** :Example Enable LE 1M and 2M TX/RX PHYs: | **> phy LE1MTX LE1MRX LE2MTX LE2MRX** :Example Enable 1-slot packets for all BR/EDR PHYs: | **> phy BR1M1SLOT EDR2M1SLOT EDR3M1SLOT** wbs --- Toggle Wideband-Speech support :Usage: **> wbs ** :: Wideband Speech support - "on" to enable for audio profiles, "off" to disable :Example Enable Wideband Speech support for audio profiles: | **> wbs on** :Example Disable Wideband Speech support: | **> wbs off** secinfo ------- Show security information :Usage: **> secinfo** :Example Display security features and capabilities: | **> secinfo** expinfo ------- Show experimental features :Usage: **> expinfo** :Example Display available experimental features and their status: | **> expinfo** exp-debug --------- Set debug feature :Usage: **> exp-debug ** :: Experimental debug features - "on" to enable, "off" to disable :Example Enable experimental debug features: | **> exp-debug on** :Example Disable experimental debug features: | **> exp-debug off** exp-privacy ----------- Set LL privacy feature :Usage: **> exp-privacy ** :: Experimental Link Layer privacy - "on" to enable LL privacy features, "off" to disable :Example Enable experimental Link Layer privacy features: | **> exp-privacy on** :Example Disable experimental Link Layer privacy features: | **> exp-privacy off** exp-quality ----------- Set bluetooth quality report feature :Usage: **> exp-quality ** :: Experimental quality reporting - "on" to enable Bluetooth quality reports, "off" to disable :Example Enable experimental Bluetooth quality reporting: | **> exp-quality on** :Example Disable experimental Bluetooth quality reporting: | **> exp-quality off** exp-offload ----------- Toggle codec support :Usage: **> exp-offload ** :: Experimental codec offload - "on" to enable codec offload support, "off" to disable :Example Enable experimental codec offload support: | **> exp-offload on** :Example Disable experimental codec offload support: | **> exp-offload off** exp-iso ------- Toggle ISO Socket support :Usage: **> exp-iso ** :: Experimental ISO Socket support - "on" to enable ISO Socket support, "off" to disable :Example Enable experimental ISO Socket support: | **> exp-offload on** :Example Disable experimental ISO Socket support: | **> exp-offload off** read-sysconfig -------------- Read System Configuration :Usage: **> read-sysconfig** :Example Read current system configuration parameters: | **> read-sysconfig** set-sysconfig ------------- Set System Configuration :Usage: **> set-sysconfig <-v|-h> [options...]** :<-v|-h>: Verbose output or help flag :[options...]: System configuration options (--br-page-scan-type, --br-page-scan-interval, etc.) :Example Show help for system configuration options: | **> set-sysconfig -h** :Example Set system configuration with verbose output: | **> set-sysconfig -v** :Example Set BR/EDR page scan type with verbose output: | **> set-sysconfig -v --br-page-scan-type=0** :Example Set BR/EDR page scan interval: | **> set-sysconfig -v --br-page-scan-interval=0x800** :Example Set BR/EDR page scan window: | **> set-sysconfig -v --br-page-scan-window=0x12** :Example Set BR/EDR inquiry scan type: | **> set-sysconfig -v --br-inquiry-scan-type=0** :Example Set BR/EDR link supervision timeout: | **> set-sysconfig -v --br-link-supervision-timeout=0x2000** :Example Set LE minimum connection interval: | **> set-sysconfig -v --le-min-connection-interval=0x18** :Example Set LE maximum connection interval: | **> set-sysconfig -v --le-max-connection-interval=0x28** :Example Set multiple BR/EDR scan parameters: | **> set-sysconfig -v --br-page-scan-type=0 --br-page-scan-interval=0x800 --br-page-scan-window=0x12** :Example Set multiple LE connection parameters: | **> set-sysconfig -v --le-min-connection-interval=0x18 --le-max-connection-interval=0x28 --le-connection-latency=0** get-flags --------- Get device flags :Usage: **> get-flags [-t type]
** :[-t type]: Address type (public, random, le_public, le_random) :
: Bluetooth address of device to query flags for :Example Get device flags for device using default type: | **> get-flags 00:11:22:33:44:55** :Example Get flags for another device: | **> get-flags AA:BB:CC:DD:EE:FF** :Example Get flags for public address device: | **> get-flags -t public 00:11:22:33:44:55** :Example Get flags for random address device: | **> get-flags -t random AA:BB:CC:DD:EE:FF** :Example Get flags for LE public address device: | **> get-flags -t le_public 11:22:33:44:55:66** :Example Get flags for LE random address device: | **> get-flags -t le_random CC:DD:EE:FF:00:11** set-flags --------- Set device flags :Usage: **> set-flags [-f flags] [-t type]
** :[-f flags]: Device flags bitmask in hexadecimal (0x01=remote wake, 0x02=privacy) :[-t type]: Address type (public, random, le_public, le_random) :
: Bluetooth address of device to set flags for :Example Set default flags for device: | **> set-flags 00:11:22:33:44:55** :Example Set default flags for another device: | **> set-flags AA:BB:CC:DD:EE:FF** :Example Set remote wake flag for device: | **> set-flags -f 0x01 00:11:22:33:44:55** :Example Set device privacy flag: | **> set-flags -f 0x02 AA:BB:CC:DD:EE:FF** :Example Set both remote wake and privacy flags: | **> set-flags -f 0x03 11:22:33:44:55:66** :Example Set remote wake for public address device: | **> set-flags -f 0x01 -t public 00:11:22:33:44:55** :Example Set privacy flag for LE public device: | **> set-flags -f 0x02 -t le_public 11:22:33:44:55:66** :Example Set multiple flags for LE random device: | **> set-flags -f 0x03 -t le_random CC:DD:EE:FF:00:11** :Example Clear all flags for device: | **> set-flags -f 0x00 00:11:22:33:44:55** :Example Clear all flags for LE public device: | **> set-flags -f 0x00 -t le_public 11:22:33:44:55:66** RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/bluetoothctl-admin.10000644000000000000000000000005015221755451016307 xustar0020 atime=1783094057 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-admin.10000644000000000000000000000530715221755451015775 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "bluetoothctl-admin" "1" "November 2022" "BlueZ" "Linux System Administration" .SH Name bluetoothctl-admin \- Admin Policy Submenu .SH SYNOPSIS .sp \fBbluetoothctl\fP [\-\-options] [admin.commands] .sp This submenu configures administrative policies using the \fBorg.bluez.AdminPolicySet(5)\fP and \fBorg.bluez.AdminPolicyStatus(5)\fP interfaces. .SH Admin Policy Commands .SS allow .sp Allow service UUIDs and block rest of them. .INDENT 0.0 .TP .B Usage \fB> allow [clear/uuid1 uuid2 ...]\fP .TP .B Uses \fBorg.bluez.AdminPolicySet(5)\fP property \fBServiceAllowList\fP .TP .B [clear/uuid1 uuid2 ...] List of service UUIDs to allow, or \(dqclear\(dq to remove all restrictions .TP .B Example Get current allowed UUIDs list .nf \fB> allow\fP .fi .sp .TP .B Example Allow Serial Port Profile only .nf \fB> allow 0x1101\fP .fi .sp .TP .B Example Allow Serial Port Profile and LAN Access Profile .nf \fB> allow 0x1101 0x1102\fP .fi .sp .TP .B Example Allow Serial Port Profile, LAN Access Profile, and Dialup Networking Profile .nf \fB> allow 0x1101 0x1102 0x1103\fP .fi .sp .TP .B Example Allow Advanced Audio Distribution Profile only .nf \fB> allow 0x110e\fP .fi .sp .TP .B Example Allow A2DP Source and Sink profiles .nf \fB> allow 0x110e 0x110f\fP .fi .sp .TP .B Example Allow Serial Port Profile using full UUID .nf \fB> allow 00001101\-0000\-1000\-8000\-00805f9b34fb\fP .fi .sp .TP .B Example Allow SPP and LAP using full UUIDs .nf \fB> allow 00001101\-0000\-1000\-8000\-00805f9b34fb 00001102\-0000\-1000\-8000\-00805f9b34fb\fP .fi .sp .TP .B Example Remove all UUID restrictions .nf \fB> allow clear\fP .fi .sp .TP .B Example Allow SPP, LAP, and DUN using mixed UUID formats .nf \fB> allow 0x1101 00001102\-0000\-1000\-8000\-00805f9b34fb 0x1103\fP .fi .sp .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/btmon-le-audio.rst0000644000000000000000000000005015221752714015774 xustar0020 atime=1783094038 20 ctime=1783094112 bluez-5.87/doc/btmon-le-audio.rst0000644000000000000000000006470715221752714015473 0ustar00rootroot.. This file is included by btmon.rst. LE AUDIO PROTOCOL FLOW ======================= LE Audio uses a multi-layer protocol stack visible in btmon traces. The setup sequence involves ATT operations on specific GATT characteristics (PACS, ASCS) followed by HCI-level CIS/BIG management. btmon fully decodes all layers. PACS: Published Audio Capabilities ------------------------------------ Before audio streaming begins, devices exchange codec capabilities via the Published Audio Capabilities Service (PACS). The client reads PACS characteristics to learn what the remote device supports. **Sink PAC read** (remote device's receive capabilities):: < ACL Data TX: Handle 2048 flags 0x00 dlen 7 #550 [hci0] 0.824003 ATT: Read Request (0x0a) len 2 Handle: 0x0075 > ACL Data RX: Handle 2048 flags 0x02 dlen 30 #552 [hci0] 0.886556 ATT: Read Response (0x0b) len 25 Handle: 0x0075 Number of PAC(s): 1 Codec: LC3 (0x06) Codec Specific Capabilities: #0 Sampling Frequency: 8000 Hz 16000 Hz 24000 Hz 32000 Hz 48000 Hz Frame Duration: 7.5 ms 10 ms Audio Channel Counts: 1 Frame Length: 26 - 240 The PAC record shows codec capabilities using LTV (Length-Type-Value) encoding. Key fields: - **Codec** -- ``LC3 (0x06)`` is the mandatory LE Audio codec - **Sampling Frequency** -- Supported sample rates (bitmask) - **Frame Duration** -- Supported frame durations (7.5 ms and/or 10 ms) - **Audio Channel Counts** -- Supported channel counts - **Frame Length** -- Min and max octets per codec frame **Audio Locations** (channel assignment):: > ACL Data RX: Handle 2048 flags 0x02 dlen 9 #554 [hci0] 0.948003 ATT: Read Response (0x0b) len 4 Handle: 0x0077 Location: Front Left **Available Audio Contexts** (current use cases):: > ACL Data RX: Handle 2048 flags 0x02 dlen 9 #558 [hci0] 1.012556 ATT: Read Response (0x0b) len 4 Handle: 0x007b Sink Context: Media Conversational Source Context: Unspecified ASCS: Audio Stream Control ---------------------------- The Audio Stream Control Service (ASCS) manages the ASE (Audio Stream Endpoint) state machine. Each ASE transitions through a defined set of states as streaming is set up and torn down. **ASE State Machine**:: Idle ──► Codec Configured ──► QoS Configured ──► Enabling ──► Streaming │ Idle ◄── Releasing ◄──────── Disabling ◄─────────────────────────┘ **ASE Status notification** (state change):: > ACL Data RX: Handle 2048 flags 0x02 dlen 20 #580 [hci0] 1.456003 ATT: Handle Value Notification (0x1b) len 15 Handle: 0x0088 ASE ID: 0x01 State: Codec Configured (0x01) Framing: Unframed PDUs supported (0x00) PHY: 0x02 LE 2M PHY (0x02) RTN: 2 Max Transport Latency: 10 Presentation Delay Min: 20000 us Presentation Delay Max: 40000 us Preferred Presentation Delay Min: 20000 us Preferred Presentation Delay Max: 40000 us Codec: LC3 (0x06) Sampling Frequency: 48000 Hz Frame Duration: 10 ms Audio Channel Allocation: Front Left Frame Length: 120 **ASE Control Point operations** drive state transitions. The client writes to the ASE Control Point characteristic to issue commands:: < ACL Data TX: Handle 2048 flags 0x00 dlen 25 #582 [hci0] 1.518003 ATT: Write Request (0x12) len 20 Handle: 0x008b ASE Control Point: Config Codec (0x01) ASE ID: 0x01 Target Latency: Low Latency (0x01) PHY: LE 2M PHY Codec: LC3 (0x06) Sampling Frequency: 48000 Hz Frame Duration: 10 ms Audio Channel Allocation: Front Left Frame Length: 120 ASE Control Point commands: .. list-table:: :header-rows: 1 :widths: 10 25 65 * - Opcode - Command - Purpose * - 0x01 - Config Codec - Select codec and parameters (Idle → Codec Configured) * - 0x02 - Config QoS - Set CIG/CIS IDs and QoS params (Codec Configured → QoS Configured) * - 0x03 - Enable - Start ASE with metadata (QoS Configured → Enabling) * - 0x04 - Receiver Start Ready - Signal receiver readiness (Enabling → Streaming, server-side) * - 0x05 - Disable - Stop streaming (Streaming → Disabling) * - 0x06 - Receiver Stop Ready - Signal receiver stopped * - 0x07 - Update Metadata - Change metadata during streaming * - 0x08 - Release - Tear down ASE (any → Releasing → Idle) Bidirectional Streams (Source + Sink) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A device may expose multiple ASEs with different directions. In the conversational (telephony) use case, the remote device typically has at least one **Sink** ASE (receives audio, e.g., speaker) and one **Source** ASE (sends audio, e.g., microphone). Both ASEs share the same CIG and often the same CIS, using the bidirectional capability of Connected Isochronous Streams. **Direction indicators in btmon traces:** - The ``Receiver Start Ready`` command is only sent for **Source** ASEs (ASEs that send audio toward the local device). The server issues this command to indicate it is ready to transmit. - The ``Receiver Stop Ready`` command likewise applies to Source ASEs. - **Sink** ASEs transition directly from Enabling to Streaming without ``Receiver Start Ready``. **Typical bidirectional setup sequence** (two ASEs on one CIS):: Config Codec ASE ID=1 (Sink) → Codec Configured Config Codec ASE ID=3 (Source) → Codec Configured Config QoS ASE ID=1 → QoS Configured (CIG=X, CIS=Y) Config QoS ASE ID=3 → QoS Configured (CIG=X, CIS=Y) Enable ASE ID=1 → Enabling → Streaming (immediate) Enable ASE ID=3 → Enabling CIS Established (Success) Setup ISO Data Path Input (Host→Controller, for Sink) Setup ISO Data Path Output (Controller→Host, for Source) Receiver Start Ready ASE ID=3 → Streaming Both ASEs may reach Streaming at different times. The Sink ASE can start receiving audio as soon as CIS is established, while the Source ASE waits for the ``Receiver Start Ready`` handshake. .. note:: **This is normal behavior.** Seeing one Source ASE and one Sink ASE on the same connection is the standard bidirectional (conversational) configuration. It is **not** an error or misconfiguration. Both directions sharing a single CIS is efficient and expected. **Multiple ASEs per direction** are also valid. For example, a stereo headset may expose two Sink ASEs (left and right channels) and one Source ASE (mono microphone), each with its own GATT handle for ASE state notifications. CIS Setup and Teardown ------------------------ After ASE QoS Configuration, the host creates Connected Isochronous Streams (CIS) at the HCI level. **CIG Parameters** (configure the CIS group):: < HCI Command: LE Set CIG Parameters (0x08|0x0062) plen 26 #590 [hci0] 1.624003 CIG ID: 0x00 Central to Peripheral SDU Interval: 10000 us Peripheral to Central SDU Interval: 10000 us SCA: 0x00 Packing: Sequential (0x00) Framing: Unframed (0x00) Central to Peripheral Max Latency: 10 ms Peripheral to Central Max Latency: 10 ms Number of CIS: 1 CIS ID: 0x00 Central to Peripheral Max SDU: 120 Peripheral to Central Max SDU: 0 Central to Peripheral PHY: LE 2M PHY Peripheral to Central PHY: LE 2M PHY Central to Peripheral RTN: 2 Peripheral to Central RTN: 2 > HCI Event: Command Complete (0x0e) plen 8 #592 [hci0] 1.624556 LE Set CIG Parameters (0x08|0x0062) ncmd 1 Status: Success (0x00) CIG ID: 0x00 Number of Handles: 1 Connection Handle: 2064 **CIS Creation**:: < HCI Command: LE Create CIS (0x08|0x0064) plen 9 #594 [hci0] 1.688003 Number of CIS: 1 CIS Handle: 2064 ACL Handle: 2048 > HCI Event: LE Meta Event (0x3e) plen 29 #596 [hci0] 1.756556 LE CIS Established (0x19) Status: Success (0x00) Connection Handle: 2064 CIG Sync Delay: 5000 us CIS Sync Delay: 5000 us Central to Peripheral Latency: 10000 us Peripheral to Central Latency: 10000 us Central to Peripheral PHY: LE 2M PHY Peripheral to Central PHY: LE 2M PHY NSE: 3 Central to Peripheral BN: 1 Peripheral to Central BN: 0 Central to Peripheral FT: 2 Peripheral to Central FT: 2 Max PDU C to P: 120 Max PDU P to C: 0 ISO Interval: 10.00 msec (0x0008) Note that the CIS Handle (2064) is different from the ACL Handle (2048). CIS data packets use the CIS handle. **ISO Data Path Setup**:: < HCI Command: LE Setup ISO Data Path (0x08|0x006e) plen 13 #598 [hci0] 1.820003 Handle: 2064 Data Path Direction: Input (Host to Controller) (0x00) Data Path ID: HCI (0x00) Coding Format: LC3 (0x06) Company ID: 0x0000 Vendor Codec ID: 0x0000 Controller Delay: 0 us After this, ISO data packets flow on the CIS handle:: < ISO Data TS: Handle 2064 flags 0x02 dlen 124 #600 [hci0] 1.884003 Broadcast Audio (BIG) ---------------------- Broadcast Isochronous Streams use BIG (Broadcast Isochronous Group) instead of CIS. The setup involves periodic advertising with BASE (Broadcast Audio Source Endpoint) announcements. **BASE announcement** (in periodic advertising data):: > HCI Event: LE Meta Event (0x3e) plen 80 #200 [hci0] 0.500003 LE Periodic Advertising Report (0x0f) ... Service Data: Basic Audio Announcement (0x1851) Presentation Delay: 40000 us Number of Subgroups: 1 Number of BIS: 2 Codec: LC3 (0x06) Sampling Frequency: 48000 Hz Frame Duration: 10 ms Frame Length: 120 BIS #1 Audio Channel Allocation: Front Left BIS #2 Audio Channel Allocation: Front Right **BIG creation** (source side):: < HCI Command: LE Create BIG (0x08|0x0068) plen 31 #210 [hci0] 0.600003 BIG Handle: 0x00 Advertising Handle: 0x01 Number of BIS: 2 SDU Interval: 10000 us Max SDU: 120 Max Latency: 10 ms RTN: 2 PHY: LE 2M PHY Packing: Sequential (0x00) Framing: Unframed (0x00) Encryption: Unencrypted (0x00) **BIG sync** (receiver side):: < HCI Command: LE BIG Create Sync (0x08|0x006b) plen 15 #220 [hci0] 0.700003 BIG Handle: 0x00 Sync Handle: 0x0001 Encryption: Unencrypted (0x00) Number of BIS: 2 BIS: 0x01 BIS: 0x02 BIG Sync Receiver Flow ~~~~~~~~~~~~~~~~~~~~~~ A receiver must complete a specific sequence of steps before it can receive broadcast audio. The critical prerequisite chain is: 1. **Synchronize to periodic advertising** -- the receiver must first discover and sync to the broadcaster's periodic advertising train. 2. **Receive PA Reports with BASE** -- the periodic advertising data contains the BASE (Broadcast Audio Source Endpoint) structure describing the broadcast's codec configuration. 3. **Receive BIG Info Advertising Report** -- this event tells the receiver that a BIG exists on the periodic advertising train and provides its parameters (number of BIS, encryption, SDU interval, etc.). **This is the critical gate**: without receiving BIG Info, the receiver cannot issue ``LE BIG Create Sync``. 4. **Issue LE BIG Create Sync** -- using the sync handle from step 1 and parameters from step 3. 5. **Receive BIG Sync Established** -- the controller confirms sync with BIS connection handles. 6. **Setup ISO Data Path** -- configure the data path for each BIS. 7. **ISO Data flows** -- broadcast audio packets arrive. If any step fails or is missing, the subsequent steps cannot proceed. The most common failure pattern is that **BIG Info is never received** (e.g., the periodic advertising data does not contain a BIG, or PA sync was lost before BIG Info arrived), which means ``LE BIG Create Sync`` is never sent. Without PAST (Direct PA Sync) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When the receiver scans for and syncs to periodic advertising directly (without assistance from a Broadcast Assistant): **Step 1 -- Create PA sync**:: < HCI Command: LE Periodic Advertising Create Sync (0x08|0x0044) plen 14 #100 [hci0] 0.100003 Options: 0x0000 SID: 0x01 Adv Address Type: Public (0x00) Adv Address: XX:XX:XX:XX:XX:XX Skip: 0x0000 Sync Timeout: 2000 msec (0x00c8) Sync CTE Type: 0x0000 **Step 2 -- PA Sync Established** (sync handle assigned):: > HCI Event: LE Meta Event (0x3e) plen 16 #105 [hci0] 0.150003 LE Periodic Advertising Sync Established (0x0e) Status: Success (0x00) Sync Handle: 0x0001 Advertising SID: 0x01 Advertiser Address Type: Public (0x00) Advertiser Address: XX:XX:XX:XX:XX:XX Advertiser PHY: LE 2M PHY (0x02) Periodic Advertising Interval: 10.000 msec (0x0008) Advertiser Clock Accuracy: 0x05 **Step 3 -- PA Reports** (contain BASE data):: > HCI Event: LE Meta Event (0x3e) plen 80 #110 [hci0] 0.200003 LE Periodic Advertising Report (0x0f) Sync Handle: 0x0001 ... Service Data: Basic Audio Announcement (0x1851) **Step 4 -- BIG Info Advertising Report** (critical gate):: > HCI Event: LE Meta Event (0x3e) plen 24 #120 [hci0] 0.300003 LE BIG Info Advertising Report (0x22) Sync Handle: 0x0001 Number BIS: 2 NSE: 4 ISO Interval: 10.000 msec (0x0008) BN: 2 PTO: 1 IRC: 2 Maximum PDU: 120 SDU Interval: 10000 us Maximum SDU: 120 PHY: LE 2M PHY (0x02) Framing: Unframed (0x00) Encryption: 0x00 This event is generated by the controller each time it receives a periodic advertising packet that contains BIG Info. It provides all parameters the receiver needs to decide whether to sync to the BIG. Key fields: - ``Number BIS`` -- how many BIS streams are available. - ``SDU Interval`` and ``Maximum SDU`` -- audio frame timing and size. - ``Encryption`` -- whether a Broadcast Code is required (0x01) or not (0x00). If encrypted, the receiver must supply the correct Broadcast Code in ``LE BIG Create Sync``. - ``Sync Handle`` -- must match a currently active PA sync. **Step 5 -- BIG Create Sync** (using sync handle + BIG Info):: < HCI Command: LE BIG Create Sync (0x08|0x006b) plen 15 #130 [hci0] 0.400003 BIG Handle: 0x00 BIG Sync Handle: 0x0001 Encryption: Unencrypted (0x00) Broadcast Code: 00000000000000000000000000000000 Maximum Number Subevents: 0x00 Timeout: 2000 ms (0x00c8) Number of BIS: 2 BIS: 0x01 BIS: 0x02 **Step 6 -- BIG Sync Established**:: > HCI Event: LE Meta Event (0x3e) plen 20 #135 [hci0] 0.450003 LE BIG Sync Established (0x1d) Status: Success (0x00) BIG Handle: 0x00 Transport Latency: 10000 us NSE: 4 BN: 2 PTO: 1 IRC: 2 Maximum PDU: 120 ISO Interval: 10.000 msec (0x0008) Connection Handle: 0x0010 Connection Handle: 0x0011 On success, the controller assigns BIS connection handles (0x0010, 0x0011 above). A non-zero Status indicates failure -- common errors: - ``0x3e`` (Connection Failed to be Established) -- BIG parameters do not match or the BIG is no longer present. - ``0x3f`` (Limit Reached) -- controller resources exhausted. **Step 7 -- Setup ISO Data Path** (for each BIS):: < HCI Command: LE Setup ISO Data Path (0x08|0x006e) plen 13 #140 [hci0] 0.500003 Connection Handle: 0x0010 Data Path Direction: Output (Controller to Host) (0x01) Data Path ID: HCI (0x00) < HCI Command: LE Setup ISO Data Path (0x08|0x006e) plen 13 #145 [hci0] 0.550003 Connection Handle: 0x0011 Data Path Direction: Output (Controller to Host) (0x01) Data Path ID: HCI (0x00) **Step 8 -- ISO Data flows** on BIS handles:: > ISO Data: Handle 0x0010 flags 0x02 dlen 124 #150 [hci0] 0.600003 > ISO Data: Handle 0x0011 flags 0x02 dlen 124 #151 [hci0] 0.600003 With PAST (Periodic Advertising Sync Transfer) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When a Broadcast Assistant (e.g., a phone) helps a Scan Delegator (e.g., a hearing aid) sync to a broadcast, it can transfer the PA sync via PAST over an existing ACL connection. This avoids the delegator having to scan for and sync to the PA train itself. The BASS (Broadcast Audio Scan Service) protocol coordinates this: 1. The assistant writes an **Add Source** operation to the delegator's BASS control point with ``PA Sync`` set to ``0x01`` (Synchronize via PAST). 2. The delegator prepares to receive the transfer by setting PAST parameters. 3. The assistant transfers its PA sync to the delegator. 4. The delegator receives the PAST event with a sync handle. 5. From here, the flow continues as in the non-PAST case (PA Reports → BIG Info → BIG Create Sync → etc.). **BASS Add Source** (assistant writes to delegator, seen in ATT):: < ACL Data TX: Handle 64 flags 0x00 dlen 27 #300 [hci0] 1.000003 ATT: Write Command (0x52) len 22 Handle: 0x0025 Data: 04... Opcode: Add Source (0x04) Advertiser Address Type: Public (0x00) Advertiser Address: XX:XX:XX:XX:XX:XX Advertising SID: 0x01 PA Sync: Synchronize to PA - PAST (0x01) PA Interval: 0x0008 Number of Subgroups: 1 BIS Sync: 0x00000003 Metadata Length: 0 PA Sync values in the Add Source operation: - ``0x00`` -- Do not synchronize to PA - ``0x01`` -- Synchronize to PA, PAST available - ``0x02`` -- Synchronize to PA, PAST not available (delegator must scan and sync directly) **PAST Parameters** (delegator prepares to receive transfer):: < HCI Command: LE Periodic Advertising Sync Transfer Parameters (0x08|0x005c) plen 8 #310 [hci0] 1.100003 Connection handle: 64 Mode: Enabled with report events enabled (0x02) Skip: 0x00 Sync timeout: 2000 msec (0x00c8) Sync CTE Type: 0x0000 **PAST Transfer** (assistant sends its PA sync):: < HCI Command: LE Periodic Advertising Sync Transfer (0x08|0x005a) plen 6 #320 [hci0] 1.200003 Connection handle: 64 Service data: 0x0001 Sync handle: 1 **PAST Received** (delegator gets the sync handle):: > HCI Event: LE Meta Event (0x3e) plen 19 #325 [hci0] 1.250003 LE Periodic Advertising Sync Transfer Received (0x18) Status: Success (0x00) Handle: 64 Connection handle: 64 Service data: 0x0001 Sync handle: 1 SID: 0x01 Address type: Public (0x00) Address: XX:XX:XX:XX:XX:XX PHY: LE 2M PHY (0x02) Periodic advertising Interval: 10.000 Clock Accuracy: 0x05 On success, the delegator now has a PA sync (``Sync handle: 1``) and will begin receiving PA Reports and BIG Info events, continuing from step 3 of the non-PAST flow above. .. note:: **Race condition**: The PAST Parameters command must be sent **before** the assistant sends the PAST Transfer. In BlueZ, the PA sync state in BASS is set after probing completes to avoid the remote sending PAST before the kernel has enabled PAST Parameters on the HCI level. BIG Sync Teardown ^^^^^^^^^^^^^^^^^ **Receiver-initiated teardown** -- the receiver terminates its BIG sync:: < HCI Command: LE BIG Terminate Sync (0x08|0x006c) plen 1 #500 [hci0] 5.000003 BIG Handle: 0x00 > HCI Event: Command Complete (0x0e) plen 5 #501 [hci0] 5.001003 LE BIG Terminate Sync (0x08|0x006c) ncmd 1 Status: Success (0x00) BIG Handle: 0x00 **Broadcaster-initiated teardown** -- the broadcaster terminates its BIG, and the receiver gets a BIG Sync Lost event:: > HCI Event: LE Meta Event (0x3e) plen 2 #510 [hci0] 6.000003 LE BIG Sync Lost (0x1e) BIG Handle: 0x00 Reason: Connection Terminated By Local Host (0x16) The ``Reason`` field indicates why sync was lost: - ``0x08`` (Connection Timeout) -- BIG packets not received within the sync timeout. - ``0x13`` (Remote User Terminated Connection) -- broadcaster stopped the BIG intentionally. - ``0x16`` (Connection Terminated By Local Host) -- local controller terminated. - ``0x3e`` (Connection Failed to be Established) -- could not establish sync initially. **Source-side BIG termination** (broadcaster tears down):: < HCI Command: LE Terminate BIG (0x08|0x006a) plen 2 #520 [hci0] 7.000003 BIG Handle: 0x00 Reason: Connection Terminated By Local Host (0x16) > HCI Event: LE Meta Event (0x3e) plen 2 #521 [hci0] 7.001003 LE BIG Terminate (0x1c) BIG Handle: 0x00 Reason: Connection Terminated By Local Host (0x16) BIG Sync Failure Diagnosis ^^^^^^^^^^^^^^^^^^^^^^^^^^ When analyzing a trace where BIG sync fails, check the following in order: 1. **Is PA sync established?** -- Look for ``LE Periodic Advertising Sync Established`` with ``Status: Success``. If missing, the receiver never synced to the PA train. 2. **Are PA Reports arriving?** -- Look for ``LE Periodic Advertising Report`` events. If absent after PA sync, the PA train may have been lost. 3. **Is BIG Info received?** -- Look for ``LE BIG Info Advertising Report``. **If this event never appears, the BIG does not exist on this PA train**, or the broadcaster has not yet started it. Without BIG Info, ``LE BIG Create Sync`` cannot be sent. 4. **Is BIG Create Sync sent?** -- If BIG Info was received but ``LE BIG Create Sync`` was never sent, the host-side logic failed to act on the BIG Info (e.g., mismatched codec, encryption mismatch, application-level issue). 5. **Does BIG Sync Established succeed?** -- Check the ``Status`` field. A non-zero status means the controller could not sync to the BIG. 6. **Is ISO Data Path set up?** -- Look for ``LE Setup ISO Data Path`` for each BIS handle from BIG Sync Established. 7. **Is ISO Data flowing?** -- Look for ``ISO Data`` packets on the BIS handles. Automating LE Audio Analysis ------------------------------ **Identify LE Audio activity**:: grep -n "ASE Control Point\|ASE ID\|State:.*Codec Configured\|State:.*QoS Configured\|State:.*Enabling\|State:.*Streaming\|State:.*Releasing" output.txt **Track ASE state transitions** for a specific ASE:: grep -n "ASE ID:" output.txt Then examine the ``State:`` line following each ASE ID match. **Check codec configuration**:: grep -n "Codec: LC3\|Sampling Frequency:\|Frame Duration:\|Frame Length:\|Audio Channel" output.txt **Verify CIS establishment**:: grep -n "Set CIG Parameters\|Create CIS\|CIS Established\|Setup ISO Data Path" output.txt **Detect CIS failures** -- check the Status field after ``CIS Established``:: grep -n "CIS Established" output.txt Then examine the following line for ``Status:``. **Detect broadcast audio**:: grep -n "Basic Audio Announcement\|Create BIG\|BIG Complete\|BIG Create Sync\|BIG Sync\|BIG Info\|BIG Terminate\|BIG Sync Lost" output.txt **Trace BIG Sync receiver flow** -- verify each prerequisite step:: grep -n "Periodic Advertising Create Sync\|Periodic Advertising Sync Established\|BIG Info Advertising Report\|BIG Create Sync\|BIG Sync Established\|BIG Sync Lost\|BIG Terminate" output.txt **Detect PAST-based sync** -- check for Periodic Advertising Sync Transfer:: grep -n "Sync Transfer Parameters\|Sync Transfer (0x08\|PAST Received\|PA Sync:.*PAST\|Add Source" output.txt **Check BIG Info arrival** -- the critical gate for BIG sync. If this is absent, the receiver has no BIG to sync to:: grep -n "BIG Info Advertising Report" output.txt **Full LE Audio diagnosis pattern**: *Unicast (CIS) flow:* 1. Find PACS reads -- verify codec compatibility between devices 2. Find ASE Control Point writes -- trace the Config Codec → Config QoS → Enable sequence 3. Find ASE state notifications -- verify each transition succeeds 4. Find CIG Parameters and CIS creation -- verify HCI-level setup 5. Find ``CIS Established`` -- check Status for success 6. Find ``Setup ISO Data Path`` -- verify data path configuration 7. Find ISO Data packets -- confirm audio is flowing 8. On failure, check ASE Control Point notification responses for error codes (Response Code and Response Reason fields) *Broadcast (BIG) receiver flow:* 1. Find ``Periodic Advertising Create Sync`` or ``PAST Received`` -- how did PA sync start? 2. Find ``Periodic Advertising Sync Established`` or ``PAST Received`` with ``Status: Success`` -- is PA synced? 3. Find ``Periodic Advertising Report`` with ``Basic Audio Announcement`` -- is BASE data arriving? 4. Find ``BIG Info Advertising Report`` -- **critical**: does the BIG exist? If missing, the BIG cannot be synced. 5. Find ``BIG Create Sync`` -- did the host request BIG sync? 6. Find ``BIG Sync Established`` -- check ``Status`` for success. 7. Find ``Setup ISO Data Path`` for each BIS handle. 8. Find ``ISO Data`` on BIS handles -- confirm audio is flowing. 9. On failure, check for ``BIG Sync Lost`` and examine ``Reason``. bluez-5.87/doc/PaxHeaders/org.bluez.Input.50000644000000000000000000000005015221755307015522 xustar0020 atime=1783093959 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Input.50000644000000000000000000000340015221755307015200 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.Input" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.Input \- BlueZ D-Bus Input API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.Input1 .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} .UNINDENT .SS Properties .SS string ReconnectMode [readonly] .sp Indicates the Connectability mode of the HID device as defined by the HID Profile specification, Section 5.4.2. .sp This mode is based in the two properties HIDReconnectInitiate (see Section 5.3.4.6) and HIDNormallyConnectable (see Section 5.3.4.14) which define the following four possible values: .INDENT 0.0 .TP .B \(dqnone\(dq Device and host are not required to automatically restore the connection. .TP .B \(dqhost\(dq Bluetooth HID host restores connection. .TP .B \(dqdevice\(dq Bluetooth HID device restores connection. .TP .B \(dqany\(dq Bluetooth HID device shall attempt to restore the lost connection, but Bluetooth HID Host may also restore the connection. .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.obex.Message.rst0000644000000000000000000000005015063045254017404 xustar0020 atime=1783094019 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.Message.rst0000644000000000000000000000642315063045254017072 0ustar00rootroot====================== org.bluez.obex.Message ====================== ------------------------------------------ BlueZ D-Bus OBEX Message API documentation ------------------------------------------ :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez.obex :Interface: org.bluez.obex.Message1 :Object path: [Session object path]/message{#} Methods ------- object, dict Get(string targetfile, boolean attachment) ``````````````````````````````````````````````````````` Download message and store it in the target file. If an empty target file is given, a temporary file will be automatically generated. The returned path represents the newly created transfer, which should be used to find out if the content has been successfully transferred or if the operation fails. The properties of this transfer are also returned along with the object path, to avoid a call to GetProperties, see **org.bluez.obex.Transfer(5)** for the possible list of properties. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: Properties ---------- string Folder [readonly] ```````````````````````` Folder which the message belongs to string Subject [readonly] ````````````````````````` Message subject string Timestamp [readonly] ``````````````````````````` Message timestamp string Sender [readonly] ```````````````````````` Message sender name string SenderAddress [readonly] ``````````````````````````````` Message sender address string ReplyTo [readonly] ````````````````````````` Message Reply-To address string Recipient [readonly] ``````````````````````````` Message recipient name string RecipientAddress [readonly] `````````````````````````````````` Message recipient address string Type [readonly] `````````````````````` Message type Possible values: :"email": :"sms-gsm": :"sms-cdma": :"mms": uint64 Size [readonly] `````````````````````` Message size in bytes string Status [readonly] ```````````````````````` Message reception status Possible values: :"complete": :"fractioned": :"notification": boolean Priority [readonly] ``````````````````````````` Message priority flag boolean Read [read/write] ````````````````````````` Message read flag boolean Deleted [writeonly] ``````````````````````````` Message deleted flag boolean Sent [readonly] ``````````````````````` Message sent flag boolean Protected [readonly] ```````````````````````````` Message protected flag string DeliveryStatus [readonly] [optional] ``````````````````````````````````````````` Message delivery status Possible values: :"delivered": :"sent": :"unknown": uint64 ConversationId [readonly] [required] ``````````````````````````````````````````` Message conversation id sent by Server which servers as Unique identification of the conversation. string ConversationName [readonly] [optional] ````````````````````````````````````````````` Human readable name of the conversation string Direction [readonly] [required] `````````````````````````````````````` Indicate the direction of the message Possible values: :"incoming": :"outgoing": :"outgoingdraft": :"outgoingpending": string AttachmentMimeTypes [readonly] [optional] ```````````````````````````````````````````````` MIME type of the attachment bluez-5.87/doc/PaxHeaders/org.bluez.MediaAssistant.50000644000000000000000000000005015221755340017331 xustar0020 atime=1783093984 20 ctime=1783094112 bluez-5.87/doc/org.bluez.MediaAssistant.50000644000000000000000000000541115221755340017013 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.MediaAssistant" "5" "June 2024" "BlueZ" "Linux System Administration" .SH Name org.bluez.MediaAssistant \- BlueZ D-Bus MediaAssistant API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.MediaAssistant1 .TP .B Object path /org/bluez/{hci0,hci1,...}/dev_{BDADDR}/src_{BDADDR}/sid#/bis# .TP .B Used by \fBbluetoothctl\-assistant(1)\fP .UNINDENT .SS Methods .SS void Push(dict properties) .sp Send stream information to the remote device. .sp If the assistant is in the \(dqactive\(dq state, this method uses BASS_Modify_Source to update the existing source on the remote device instead of adding a new one with BASS_Add_Source. .INDENT 0.0 .TP .B dict properties .UNINDENT .sp Indicate stream properties that will be sent to the peer. .sp Values: .INDENT 0.0 .INDENT 3.5 .INDENT 0.0 .TP .B array{byte} Metadata [ISO only] See Metadata property. .TP .B dict QoS [ISO only] See QoS property. .TP .B object Device [ISO only, State=local only] Push to a specific device. Device must be connected and with an active BASS session. .UNINDENT .UNINDENT .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > assistant.push .UNINDENT .SS Properties .SS string State [readonly] .sp Indicates the state of the assistant object. Possible values are: .INDENT 0.0 .TP .B \(dqidle\(dq assistant object was created for the stream .TP .B \(dqpending\(dq assistant object was pushed (stream information was sent to the peer) .TP .B \(dqrequesting\(dq remote device requires Broadcast_Code .TP .B \(dqactive\(dq remote device started receiving stream .TP .B \(dqlocal\(dq assistant object was created for a local stream .UNINDENT .SS array{byte} Metadata [readwrite, ISO Only, experimental] .sp Indicates stream Metadata. .SS dict QoS [readwrite, ISO only, experimental] .sp Indicates stream QoS capabilities. .sp Values: .INDENT 0.0 .TP .B byte Encryption Indicates whether the stream is encrypted. .UNINDENT .sp :array{byte} BCode .INDENT 0.0 .INDENT 3.5 Indicates Broadcast_Code to decrypt stream. .UNINDENT .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/bluetoothctl-scan.10000644000000000000000000000005015221755464016147 xustar0020 atime=1783094068 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-scan.10000644000000000000000000002460615221755464015640 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "bluetoothctl-scan" "1" "July 2023" "BlueZ" "Linux System Administration" .SH Name bluetoothctl-scan \- Scan Submenu .SH SYNOPSIS .sp \fBbluetoothctl\fP [\-\-options] [scan.commands] .sp This submenu configures discovery filters using \fBorg.bluez.Adapter(5)\fP method \fBSetDiscoveryFilter\fP\&. .SH Scan Commands .SS uuids .sp Set/Get UUIDs filter. .INDENT 0.0 .TP .B Usage \fB> uuids [all/uuid1 uuid2 ...]\fP .TP .B Uses \fBorg.bluez.Adapter(5)\fP method \fBSetDiscoveryFilter\fP property \fBUUIDs\fP .TP .B [all/uuid1 uuid2 ...] List of UUIDs to filter for during scanning (default: none, use \(dqall\(dq to scan for any UUID) .TP .B Example Show current UUID filter settings .nf \fB> uuids\fP .fi .sp .TP .B Example Filter for Generic Access service only .nf \fB> uuids 0x1800\fP .fi .sp .TP .B Example Filter for Battery service only .nf \fB> uuids 0x180F\fP .fi .sp .TP .B Example Filter for Generic Access and Battery services .nf \fB> uuids 0x1800 0x180F\fP .fi .sp .TP .B Example Filter for Device Info, Battery, and Environmental Sensing .nf \fB> uuids 0x180A 0x180F 0x181A\fP .fi .sp .TP .B Example Filter for Generic Access using full UUID .nf \fB> uuids 00001800\-0000\-1000\-8000\-00805f9b34fb\fP .fi .sp .TP .B Example Filter using mixed short and long forms .nf \fB> uuids 00001800\-0000\-1000\-8000\-00805f9b34fb 0000180f\-0000\-1000\-8000\-00805f9b34fb\fP .fi .sp .TP .B Example Remove UUID filtering (scan for all devices) .nf \fB> uuids all\fP .fi .sp .UNINDENT .SS rssi .sp Set/Get RSSI filter, and clears pathloss. .sp This sets the minimum rssi value for reporting device advertisements. .sp The value is in dBm. .sp If one or more discovery filters have been set, the RSSI delta\-threshold imposed by starting discovery by default will not be applied. .INDENT 0.0 .TP .B Usage \fB> rssi [rssi]\fP .TP .B Uses \fBorg.bluez.Adapter(5)\fP method \fBSetDiscoveryFilter\fP property \fBRSSI\fP .TP .B [rssi] Minimum RSSI threshold value in dBm (optional, shows current if omitted) .TP .B Example Show current RSSI filter setting .nf \fB> rssi\fP .fi .sp .TP .B Example Only report devices with RSSI ≥ \-60 dBm (close range) .nf \fB> rssi \-60\fP .fi .sp .TP .B Example Report devices with RSSI ≥ \-80 dBm (medium range) .nf \fB> rssi \-80\fP .fi .sp .TP .B Example Report devices with RSSI ≥ \-90 dBm (extended range) .nf \fB> rssi \-90\fP .fi .sp .TP .B Example Report only very close devices (RSSI ≥ \-40 dBm) .nf \fB> rssi \-40\fP .fi .sp .TP .B Example Report devices with very weak signals (maximum sensitivity) .nf \fB> rssi \-100\fP .fi .sp .UNINDENT .SS pathloss .sp Set/Get Pathloss filter, and clears RSSI. .sp This sets the maximum pathloss value for reporting device advertisements. .sp The value is in dB. .sp If one or more discovery filters have been set, the RSSI delta\-threshold imposed by starting discovery by default will not be applied. .INDENT 0.0 .TP .B Usage \fB> pathloss [pathloss]\fP .TP .B Uses \fBorg.bluez.Adapter(5)\fP method \fBSetDiscoveryFilter\fP property \fBPathloss\fP .TP .B [pathloss] Maximum pathloss threshold value in dB (optional, shows current if omitted) .TP .B Example Show current pathloss filter setting .nf \fB> pathloss\fP .fi .sp .TP .B Example Report devices with maximum 4 dB pathloss (very close) .nf \fB> pathloss 4\fP .fi .sp .TP .B Example Report devices with maximum 10 dB pathloss (close range) .nf \fB> pathloss 10\fP .fi .sp .TP .B Example Report devices with maximum 20 dB pathloss (medium range) .nf \fB> pathloss 20\fP .fi .sp .TP .B Example Report devices with maximum 30 dB pathloss (extended range) .nf \fB> pathloss 30\fP .fi .sp .TP .B Example Report devices with maximum 50 dB pathloss (maximum range) .nf \fB> pathloss 50\fP .fi .sp .UNINDENT .SS transport .sp Set/Get transport filter. .sp Transport parameter determines the type of scan. .sp The default is auto. .sp Possible values: .INDENT 0.0 .IP \(bu 2 \(dqauto\(dq: interleaved scan .IP \(bu 2 \(dqbredr\(dq: BR/EDR inquiry .IP \(bu 2 \(dqle\(dq: LE scan only .UNINDENT .sp If \(dqle\(dq or \(dqbredr\(dq Transport is requested and the controller doesn\(aqt support it, an org.bluez.Error.Failed error will be returned. .sp If \(dqauto\(dq transport is requested, the scan will use LE, BREDR, or both, depending on what\(aqs currently enabled on the controller. .INDENT 0.0 .TP .B Usage \fB> transport [auto/bredr/le]\fP .TP .B [auto/bredr/le] Transport type for scanning (optional, shows current if omitted) .TP .B Example Show current transport filter setting .nf \fB> transport\fP .fi .sp .TP .B Example Use automatic transport selection (LE + BR/EDR) .nf \fB> transport auto\fP .fi .sp .TP .B Example Scan only Low Energy devices .nf \fB> transport le\fP .fi .sp .TP .B Example Scan only BR/EDR (Classic Bluetooth) devices .nf \fB> transport bredr\fP .fi .sp .UNINDENT .SS duplicate\-data .sp Set/Get duplicate data filter. .sp Disables duplicate detection of advertisement data. .sp When enabled, PropertiesChanged signals will be generated for ManufacturerData and ServiceData every time they are discovered. .INDENT 0.0 .TP .B Usage \fB> duplicate\-data [on/off]\fP .TP .B [on/off] Enable or disable duplicate advertisement data reporting (optional, shows current if omitted) .TP .B Example Show current duplicate data filter setting .nf \fB> duplicate\-data\fP .fi .sp .TP .B Example Enable reporting of duplicate advertisement data .nf \fB> duplicate\-data on\fP .fi .sp .TP .B Example Disable duplicate data reporting (filter duplicates) .nf \fB> duplicate\-data off\fP .fi .sp .UNINDENT .SS discoverable .sp Set/Get discoverable filter. .sp Makes the adapter discoverable while discovering. .sp If the adapter is already discoverable, setting this filter won\(aqt have any effect. .INDENT 0.0 .TP .B Usage \fB> discoverable [on/off]\fP .TP .B [on/off] Make adapter discoverable during scanning (optional, shows current if omitted) .TP .B Example Show current discoverable filter setting .nf \fB> discoverable\fP .fi .sp .TP .B Example Make adapter discoverable during scanning .nf \fB> discoverable on\fP .fi .sp .TP .B Example Keep adapter non\-discoverable during scanning .nf \fB> discoverable off\fP .fi .sp .UNINDENT .SS pattern .sp Set/Get pattern filter. .sp Discover devices where the pattern matches either the prefix of the address or the device name, which is a convenient way to limit the number of device objects created during a discovery. .sp When set, it disregards device discoverable flags. .INDENT 0.0 .TP .B Note The pattern matching is ignored if there are other clients that don\(aqt set any pattern, as it works as a logical OR. Also, setting an empty string \(dq\(dq pattern will match any device found. .TP .B Usage \fB> pattern [value]\fP .TP .B [value] Pattern to match device address prefix or name (optional, shows current if omitted) .TP .B Example Show current pattern filter setting .nf \fB> pattern\fP .fi .sp .TP .B Example Discover devices with \(dqSamsung\(dq in the name .nf \fB> pattern Samsung\fP .fi .sp .TP .B Example Discover devices with \(dqiPhone\(dq in the name .nf \fB> pattern iPhone\fP .fi .sp .TP .B Example Discover devices with \(dqHeadphones\(dq in the name .nf \fB> pattern Headphones\fP .fi .sp .TP .B Example Discover devices with addresses starting with 00:11:22 .nf \fB> pattern 00:11:22\fP .fi .sp .TP .B Example Discover devices with addresses starting with AA:BB:CC .nf \fB> pattern AA:BB:CC\fP .fi .sp .TP .B Example Clear pattern filter (match any device) .nf \fB> pattern \(dq\(dq\fP .fi .sp .UNINDENT .SS auto\-connect .sp Set/Get auto\-connect filter. .sp Connect to discovered devices automatically if pattern filter has been set and it matches the device address or name and the device is connectable. .INDENT 0.0 .TP .B Usage \fB> auto\-connect [on/off]\fP .TP .B [on/off] Automatically connect to discovered devices matching pattern filter (optional, shows current if omitted) .TP .B Example Show current auto\-connect filter setting .nf \fB> auto\-connect\fP .fi .sp .TP .B Example Enable automatic connection to matching devices .nf \fB> auto\-connect on\fP .fi .sp .TP .B Example Disable automatic connection (manual connection required) .nf \fB> auto\-connect off\fP .fi .sp .UNINDENT .SS clear .sp Clears discovery filter. .INDENT 0.0 .TP .B Usage \fB> clear [uuids/rssi/pathloss/transport/duplicate\-data/discoverable/pattern/auto\-connect]\fP .TP .B [uuids/rssi/pathloss/transport/duplicate\-data/discoverable/pattern/auto\-connect] Specific filter(s) to clear (optional, clears all if omitted) .TP .B Example Clear all discovery filters .nf \fB> clear\fP .fi .sp .TP .B Example Clear only UUID filter .nf \fB> clear uuids\fP .fi .sp .TP .B Example Clear only RSSI filter .nf \fB> clear rssi\fP .fi .sp .TP .B Example Clear only pathloss filter .nf \fB> clear pathloss\fP .fi .sp .TP .B Example Clear only transport filter .nf \fB> clear transport\fP .fi .sp .TP .B Example Clear only duplicate data filter .nf \fB> clear duplicate\-data\fP .fi .sp .TP .B Example Clear only discoverable filter .nf \fB> clear discoverable\fP .fi .sp .TP .B Example Clear only pattern filter .nf \fB> clear pattern\fP .fi .sp .TP .B Example Clear only auto\-connect filter .nf \fB> clear auto\-connect\fP .fi .sp .TP .B Example Clear UUID and RSSI filters .nf \fB> clear uuids rssi\fP .fi .sp .TP .B Example Clear transport and pattern filters .nf \fB> clear transport pattern\fP .fi .sp .TP .B Example Clear pattern and auto\-connect filters .nf \fB> clear pattern auto\-connect\fP .fi .sp .TP .B Example Clear RSSI, pathloss, and discoverable filters .nf \fB> clear rssi pathloss discoverable\fP .fi .sp .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/rfcomm-protocol.rst0000644000000000000000000000005015142177620016301 xustar0020 atime=1783093929 20 ctime=1783094111 bluez-5.87/doc/rfcomm-protocol.rst0000644000000000000000000001222615142177620015765 0ustar00rootroot====== rfcomm ====== --------------- RFCOMM protocol --------------- :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: May 2024 :Manual section: 7 :Manual group: Linux System Administration SYNOPSIS ======== .. code-block:: #include #include #include rfcomm_socket = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); DESCRIPTION =========== The RFCOMM protocol provides emulation of serial ports over the L2CAP(7) protocol. The protocol is based on the ETSI standard TS 07.10. RFCOMM is a simple transport protocol, with additional provisions for emulating the 9 circuits of RS-232 (EIATIA-232-E) serial ports. SOCKET ADDRESS ============== .. code-block:: struct sockaddr_rc { sa_family_t rc_family; unsigned short rc_bdaddr; unsigned char rc_channel; }; Example: .. code-block:: struct sockaddr_rc addr; memset(&addr, 0, sizeof(addr)); addr.rc_family = AF_BLUETOOTH; bacpy(&addr.rc_bdaddr, bdaddr); addr.rc_channel = channel; SOCKET OPTIONS ============== The socket options listed below can be set by using **setsockopt(2)** and read with **getsockopt(2)** with the socket level set to SOL_BLUETOOTH. BT_SECURITY (since Linux 2.6.30) -------------------------------- Channel security level, possible values: .. csv-table:: :header: "Value", "Security Level", "Link Key Type", "Encryption" :widths: auto **BT_SECURITY_SDP**, 0 (SDP Only), None, Not required **BT_SECURITY_LOW**, 1 (Low), Unauthenticated, Not required **BT_SECURITY_MEDIUM**, 2 (Medium - default), Unauthenticated, Desired **BT_SECURITY_HIGH**, 3 (High), Authenticated, Required **BT_SECURITY_FIPS** (since Linux 3.15), 4 (Secure Only), Authenticated (P-256 based Secure Simple Pairing and Secure Authentication), Required Example: .. code-block:: int level = BT_SECURITY_HIGH; int err = setsockopt(rfcomm_socket, SOL_BLUETOOTH, BT_SECURITY, &level, sizeof(level)); if (err == -1) { perror("setsockopt"); return 1; } BT_DEFER_SETUP (since Linux 2.6.30) ----------------------------------- Channel defer connection setup, this control if the connection procedure needs to be authorized by userspace before responding which allows authorization at profile level, possible values: .. csv-table:: :header: "Value", "Description", "Authorization" :widths: auto **0**, Disable (default), Not required **1**, Enable, Required Example: .. code-block:: int defer_setup = 1; int err = setsockopt(rfcomm_socket, SOL_BLUETOOTH, BT_DEFER_SETUP, &defer_setup, sizeof(defer_setup)); if (err == -1) { perror("setsockopt"); return err; } err = listen(rfcomm_socket, 5); if (err) { perror("listen"); return err; } struct sockaddr_rc remote_addr = {0}; socklen_t addr_len = sizeof(remote_addr); int new_socket = accept(rfcomm_socket, (struct sockaddr*)&remote_addr, &addr_len); if (new_socket < 0) { perror("accept"); return new_socket; } /* To complete the connection setup of new_socket read 1 byte */ char c; struct pollfd pfd; memset(&pfd, 0, sizeof(pfd)); pfd.fd = new_socket; pfd.events = POLLOUT; err = poll(&pfd, 1, 0); if (err) { perror("poll"); return err; } if (!(pfd.revents & POLLOUT)) { err = read(sk, &c, 1); if (err < 0) { perror("read"); return err; } } BT_FLUSHABLE (since Linux 2.6.39) --------------------------------- Channel flushable flag, this control if the channel data can be flushed or not, possible values: .. csv-table:: :header: "Define", "Value", "Description" :widths: auto **BT_FLUSHABLE_OFF**, 0x00 (default), Do not flush data **BT_FLUSHABLE_ON**, 0x01, Flush data BT_CHANNEL_POLICY (since Linux 3.10) ------------------------------------ High-speed (AMP) channel policy, possible values: .. csv-table:: :header: "Define", "Value", "Description" :widths: auto **BT_CHANNEL_POLICY_BREDR_ONLY**, 0 (default), BR/EDR only **BT_CHANNEL_POLICY_BREDR_PREFERRED**, 1, BR/EDR Preferred **BT_CHANNEL_POLICY_BREDR_PREFERRED**, 2, AMP Preferred BT_PHY (since Linux 5.10) ------------------------- Channel supported PHY(s), possible values: .. csv-table:: :header: "Define", "Value", "Description" :widths: auto **BT_PHY_BR_1M_1SLOT**, BIT 0, BR 1Mbps 1SLOT **BT_PHY_BR_1M_3SLOT**, BIT 1, BR 1Mbps 3SLOT **BT_PHY_BR_1M_5SLOT**, BIT 2, BR 1Mbps 5SLOT **BT_PHY_BR_2M_1SLOT**, BIT 3, EDR 2Mbps 1SLOT **BT_PHY_BR_2M_3SLOT**, BIT 4, EDR 2Mbps 3SLOT **BT_PHY_BR_2M_5SLOT**, BIT 5, EDR 2Mbps 5SLOT **BT_PHY_BR_3M_1SLOT**, BIT 6, EDR 3Mbps 1SLOT **BT_PHY_BR_3M_3SLOT**, BIT 7, EDR 3Mbps 3SLOT **BT_PHY_BR_3M_5SLOT**, BIT 8, EDR 3Mbps 5SLOT RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org SEE ALSO ======== socket(7), rctest(1) bluez-5.87/doc/PaxHeaders/iso.70000644000000000000000000000005015221755256013314 xustar0020 atime=1783093934 20 ctime=1783094112 bluez-5.87/doc/iso.70000644000000000000000000003461415221755256013005 0ustar00rootroot'\" t .\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "iso" "7" "July 2025" "BlueZ" "Linux System Administration" .SH Name iso \- ISO transport .SH SYNOPSIS (since Linux 6.0 [experimental]) .INDENT 0.0 .INDENT 3.5 .sp .EX #include #include #include iso_socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO); .EE .UNINDENT .UNINDENT .SH DESCRIPTION .sp Bluetooth Isochronous Channels is a feature introduced in Bluetooth 5.2 that allow for the transmission of multiple, synchronized audio streams between devices. .sp For unicast/multi\-stream audio, connected isochronous group (CIG), and connected isochronous stream (CIS) are used. A CIG is created by the central and it can include one or more CISs. A CIS is a point\-to\-point data transportation stream between a central and a certain peripheral, and is a bidirectional communication protocol with acknowledgment. .sp For broadcast audio, broadcast isochronous group (BIG) and broadcast isochronous stream (BIS) are used. There are two types of devices: isochronous broadcaster and synchronized receiver. A BIG is created by an isochronous broadcaster, and it can include one or more BISs. A BIS is a one\-to\-many data transportation stream. .SH SOCKET ADDRESS .INDENT 0.0 .INDENT 3.5 .sp .EX struct sockaddr_iso_bc { bdaddr_t bc_bdaddr; uint8_t bc_bdaddr_type; uint8_t bc_sid; uint8_t bc_num_bis; uint8_t bc_bis[ISO_MAX_NUM_BIS]; }; struct sockaddr_iso { sa_family_t iso_family; bdaddr_t iso_bdaddr; uint8_t iso_bdaddr_type; struct sockaddr_iso_bc iso_bc[]; }; .EE .UNINDENT .UNINDENT .sp Unicast example: .INDENT 0.0 .INDENT 3.5 .sp .EX struct sockaddr_iso addr; memset(&addr, 0, sizeof(addr)); addr.iso_family = AF_BLUETOOTH; bacpy(&addr.iso_bdaddr, bdaddr); addr.iso_bdaddr_type = BDADDR_LE_PUBLIC; .EE .UNINDENT .UNINDENT .sp Broadcast example: .INDENT 0.0 .INDENT 3.5 .sp .EX struct sockaddr_iso *addr; size_t addr_len; addr_len = sizeof(*addr) + sizeof(*addr\->iso_bc); memset(addr, 0, addr_len); addr\->iso_family = AF_BLUETOOTH; bacpy(&addr\->iso_bdaddr, bdaddr); addr\->iso_bdaddr_type = BDADDR_LE_PUBLIC; .EE .UNINDENT .UNINDENT .sp Broadcast Source (Broadcaster) example: .INDENT 0.0 .INDENT 3.5 .sp .EX struct sockaddr_iso *addr; size_t addr_len; addr_len = sizeof(*addr) + sizeof(*addr\->iso_bc); memset(addr, 0, addr_len); addr\->iso_family = AF_BLUETOOTH; /* Set address to BDADR_ANY(00:00:00:00:00:00) */ bacpy(&addr\->iso_bdaddr, BADDR_ANY); addr\->iso_bdaddr_type = BDADDR_LE_PUBLIC; /* Connect to Broadcast address */ connect(iso_socket, (struct sockaddr *)addr, addr_len); .EE .UNINDENT .UNINDENT .sp Broadcast Sink (Receiver) example: .INDENT 0.0 .INDENT 3.5 .sp .EX struct sockaddr_iso *addr; size_t addr_len; addr_len = sizeof(*addr) + sizeof(*addr\->iso_bc); memset(addr, 0, addr_len); addr\->iso_family = AF_BLUETOOTH; /* Set destination to Broadcaster address */ bacpy(&addr\->iso_bdaddr, bdaddr); addr\->iso_bdaddr_type = BDADDR_LE_PUBLIC; /* Bind to Broadcaster address */ bind(iso_socket, (struct sockaddr *)addr, addr_len); .EE .UNINDENT .UNINDENT .sp Broadcast Source (Broadcaster) or Broadcast Sink (Receiver) Periodic Advertising Sync Transfer (PAST): .INDENT 0.0 .INDENT 3.5 .sp .EX struct sockaddr_iso *addr; size_t addr_len; addr_len = sizeof(*addr) + sizeof(*addr\->iso_bc); memset(addr, 0, addr_len); addr\->iso_family = AF_BLUETOOTH; /* Set destination address to PAST destination address */ bacpy(&addr\->iso_bc\->bc_bdaddr, (void *) bdaddr); addr\->iso_bc\->bc_bdaddr_type = bdaddr_type; /* Rebind already connected socket to PAST address */ bind(iso_socket, (struct sockaddr *)addr, addr_len); .EE .UNINDENT .UNINDENT .SH SOCKET OPTIONS (SOL_BLUETOOTH) .sp The socket options listed below can be set by using \fBsetsockopt(2)\fP and read with \fBgetsockopt(2)\fP with the socket level set to SOL_BLUETOOTH. .SS BT_SECURITY .sp Channel security level, possible values: .TS box center; l|l|l|l. T{ Value T} T{ Security Level T} T{ Link Key Type T} T{ Encryption T} _ T{ \fBBT_SECURITY_SDP\fP T} T{ 0 (SDP Only) T} T{ None T} T{ Not required T} _ T{ \fBBT_SECURITY_LOW\fP T} T{ 1 (Low) T} T{ Unauthenticated T} T{ Not required T} _ T{ \fBBT_SECURITY_MEDIUM\fP T} T{ 2 (Medium \- default) T} T{ Unauthenticated T} T{ Desired T} _ T{ \fBBT_SECURITY_HIGH\fP T} T{ 3 (High) T} T{ Authenticated T} T{ Required T} _ T{ \fBBT_SECURITY_FIPS\fP (since Linux 3.15) T} T{ 4 (Secure Only) T} T{ Authenticated (P\-256 based Secure Simple Pairing and Secure Authentication) T} T{ Required T} .TE .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX int level = BT_SECURITY_HIGH; int err = setsockopt(iso_socket, SOL_BLUETOOTH, BT_SECURITY, &level, sizeof(level)); if (err == \-1) { perror(\(dqsetsockopt\(dq); return 1; } .EE .UNINDENT .UNINDENT .SS BT_DEFER_SETUP .sp Channel defer connection setup, this control if the connection procedure needs to be authorized by userspace before responding which allows authorization at profile level, possible values: .TS box center; l|l|l. T{ Value T} T{ Description T} T{ Authorization T} _ T{ \fB0\fP T} T{ Disable (default) T} T{ Not required T} _ T{ \fB1\fP T} T{ Enable T} T{ Required T} .TE .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX int defer_setup = 1; int err = setsockopt(iso_socket, SOL_BLUETOOTH, BT_DEFER_SETUP, &defer_setup, sizeof(defer_setup)); if (err == \-1) { perror(\(dqsetsockopt\(dq); return err; } err = listen(iso_socket, 5); if (err) { perror(\(dqlisten\(dq); return err; } struct sockaddr_iso remote_addr = {0}; socklen_t addr_len = sizeof(remote_addr); int new_socket = accept(iso_socket, (struct sockaddr*)&remote_addr, &addr_len); if (new_socket < 0) { perror(\(dqaccept\(dq); return new_socket; } /* To complete the connection setup of new_socket read 1 byte */ char c; struct pollfd pfd; memset(&pfd, 0, sizeof(pfd)); pfd.fd = new_socket; pfd.events = POLLOUT; err = poll(&pfd, 1, 0); if (err) { perror(\(dqpoll\(dq); return err; } if (!(pfd.revents & POLLOUT)) { err = read(sk, &c, 1); if (err < 0) { perror(\(dqread\(dq); return err; } } .EE .UNINDENT .UNINDENT .SS BT_PKT_STATUS .sp Enable reporting packet status via \fIBT_SCM_PKT_STATUS\fP CMSG on received packets. Possible values: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ \fB0\fP T} T{ Disable (default) T} _ T{ \fB1\fP T} T{ Enable T} .TE .INDENT 0.0 .TP .B BT_SCM_PKT_STATUS Level \fBSOL_BLUETOOTH\fP CMSG with data: .INDENT 7.0 .INDENT 3.5 .sp .EX uint8_t pkt_status; .EE .UNINDENT .UNINDENT .sp The values are equal to the \(dqPacket_Status_Flag\(dq defined in Core Specification v6.1, 5.4.5. HCI ISO Data packets: .sp \% .TS box center; l|l. T{ pkt_status T} T{ Description T} _ T{ \fB0x0\fP T} T{ Valid data. The complete SDU was received correctly. T} _ T{ \fB0x1\fP T} T{ Possibly invalid data. The contents of the ISO_SDU_Fragment, may contain errors or part of the SDU may be missing. This is reported as \(aqdata with possible errors\(aq. T} _ T{ \fB0x2\fP T} T{ Part(s) of the SDU were not received correctly. This is reported as \(aqlost data\(aq. T} .TE .UNINDENT .SS BT_PKT_SEQNUM (since Linux 6.17\-rc1) .sp Enable reporting packet ISO sequence number via \fIBT_SCM_PKT_SEQNUM\fP CMSG on received packets. Possible values: .TS box center; l|l. T{ Value T} T{ Description T} _ T{ \fB0\fP T} T{ Disable (default) T} _ T{ \fB1\fP T} T{ Enable T} .TE .INDENT 0.0 .TP .B BT_SCM_PKT_SEQNUM Level \fBSOL_BLUETOOTH\fP CMSG with data: .INDENT 7.0 .INDENT 3.5 .sp .EX uint16_t pkt_seqnum; .EE .UNINDENT .UNINDENT .sp The values are equal to the \(dqPacket_Sequence_Number\(dq defined in Core Specification v6.1, 5.4.5. HCI ISO Data packets: .sp \% .UNINDENT .sp Example (Enable sequence numbers): .INDENT 0.0 .INDENT 3.5 .sp .EX uint32_t opt = 1; if (setsockopt(fd, SOL_BLUETOOTH, BT_PKT_SEQNUM, &opt, sizeof(opt)) < 0) goto error; .EE .UNINDENT .UNINDENT .sp Example (Read packet and its sequence number): .INDENT 0.0 .INDENT 3.5 .sp .EX char data_buf[256]; uint16_t seqnum; union { char buf[CMSG_SPACE(sizeof(uint16_t))]; struct cmsghdr align; } control; struct iovec data = { .iov_base = data_buf, .iov_len = sizeof(data_buf), }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct scm_timestamping tss; res = recvmsg(fd, &msg, 0); if (res < 0) goto error; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg\->cmsg_level == SOL_BLUETOOTH && cmsg\->cmsg_type == BT_PKT_SEQNUM) memcpy(&seqnum, CMSG_DATA(cmsg), sizeof(seqnum)); } .EE .UNINDENT .UNINDENT .SH SOCKET OPTIONS (SOL_SOCKET) .sp \fBSOL_SOCKET\fP level socket options that modify generic socket features (\fBSO_SNDBUF\fP, \fBSO_RCVBUF\fP, etc.) have their usual meaning, see \fBsocket(7)\fP\&. .sp The \fBSOL_SOCKET\fP level ISO socket options that have Bluetooth\-specific handling in kernel are listed below. .SS SO_TIMESTAMPING, SO_TIMESTAMP, SO_TIMESTAMPNS .sp See \% .sp For ISO sockets, software RX timestamps are supported. Software TX timestamps (SOF_TIMESTAMPING_TX_SOFTWARE) are supported since Linux 6.15. .sp The software RX timestamp is the time when the kernel received the packet from the controller driver. .sp The \fBSCM_TSTAMP_SND\fP timestamp is emitted when packet is sent to the controller driver. .sp The \fBSCM_TSTAMP_COMPLETION\fP timestamp is emitted when controller reports the packet completed. Completion timestamps are only supported on controllers that have ISO flow control. Other TX timestamp types are not supported. .sp You can use \fBSIOCETHTOOL\fP to query supported flags. .sp The timestamps are in \fBCLOCK_REALTIME\fP time. .sp Example (Enable RX timestamping): .INDENT 0.0 .INDENT 3.5 .sp .EX int flags = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE; setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)); .EE .UNINDENT .UNINDENT .sp Example (Read packet and its RX timestamp): .INDENT 0.0 .INDENT 3.5 .sp .EX char data_buf[256]; union { char buf[CMSG_SPACE(sizeof(struct scm_timestamping))]; struct cmsghdr align; } control; struct iovec data = { .iov_base = data_buf, .iov_len = sizeof(data_buf), }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct scm_timestamping tss; res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT); if (res < 0) goto error; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg\->cmsg_level == SOL_SOCKET && cmsg\->cmsg_type == SCM_TIMESTAMPING) memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); } tstamp_clock_realtime = tss.ts[0]; .EE .UNINDENT .UNINDENT .sp Example (Enable TX timestamping): .INDENT 0.0 .INDENT 3.5 .sp .EX int flags = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_OPT_ID; setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)); .EE .UNINDENT .UNINDENT .sp Example (Read TX timestamps): .INDENT 0.0 .INDENT 3.5 .sp .EX union { char buf[CMSG_SPACE(sizeof(struct scm_timestamping))]; struct cmsghdr align; } control; struct iovec data = { .iov_base = NULL, .iov_len = 0 }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct cmsghdr *cmsg; struct scm_timestamping tss; struct sock_extended_err serr; int res; res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT); if (res < 0) goto error; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg\->cmsg_level == SOL_SOCKET && cmsg\->cmsg_type == SCM_TIMESTAMPING) memcpy(&tss, CMSG_DATA(cmsg), sizeof(tss)); else if (cmsg\->cmsg_level == SOL_BLUETOOTH && cmsg\->cmsg_type == BT_SCM_ERROR) memcpy(&serr, CMSG_DATA(cmsg), sizeof(serr)); } tstamp_clock_realtime = tss.ts[0]; tstamp_type = serr\->ee_info; /* SCM_TSTAMP_SND or SCM_TSTAMP_COMPLETION */ tstamp_seqnum = serr\->ee_data; .EE .UNINDENT .UNINDENT .SH IOCTLS .sp The following ioctls with operation specific for ISO sockets are available. .SS SIOCETHTOOL (since Linux 6.16\-rc1) .sp Supports only command \fIETHTOOL_GET_TS_INFO\fP, which may be used to query supported \fISOF_TIMESTAMPING_*\fP flags. The \fISOF_TIMESTAMPING_OPT_*\fP flags are always available as applicable. .sp Example: .INDENT 0.0 .INDENT 3.5 .sp .EX #include #include #include #include #include \&... struct ifreq ifr = {}; struct ethtool_ts_info cmd = {}; int sk; snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), \(dqhci0\(dq); ifr.ifr_data = (void *)&cmd; cmd.cmd = ETHTOOL_GET_TS_INFO; sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO); if (sk < 0) goto error; if (ioctl(sk, SIOCETHTOOL, &ifr)) goto error; sof_available = cmd.so_timestamping; .EE .UNINDENT .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH SEE ALSO .sp socket(7), isotest(1) .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/hciconfig.rst0000644000000000000000000000005015142177620015110 xustar0020 atime=1783094083 20 ctime=1783094113 bluez-5.87/doc/hciconfig.rst0000644000000000000000000001475115142177620014601 0ustar00rootroot========= hciconfig ========= --------------------------- Configure Bluetooth devices --------------------------- :Authors: - Maxim Krasnyansky - Marcel Holtmann - Fabrizio Gennari :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: Nov 11, 2002 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **hciconfig** -h **hciconfig** [-a] **hciconfig** [-a] *hciX* [*COMMAND* [*PARAMETERS*]] DESCRIPTION =========== **hciconfig(1)** is used to configure Bluetooth devices. *hciX* is the name of a Bluetooth device installed in the system. If hciX is not given, hciconfig prints name and basic information about all the Bluetooth devices installed in the system. If *hciX* is given but no command is given, it prints basic information on device *hciX* only. Basic information is interface type, BD address, ACL MTU, SCO MTU, flags (up, init, running, raw, page scan enabled, inquiry scan enabled, inquiry, authentication enabled, encryption enabled). OPTIONS ======= -a, --all Print features, packet type, link policy, link mode, class, Version other than the basic info. -h, --help Show help options COMMANDS ======== up Open and initialize HCI device. down Close HCI device. reset Reset HCI device. rstat Reset statistic counters. auth Enable authentication (sets device to security mode 3). noauth Disable authentication. encrypt Enable encryption (sets device to security mode 3). noencrypt Disable encryption. secmgr Enable security manager (current kernel support is limited). nosecmgr Disable security manager. piscan Enable page and inquiry scan. noscan Disable page and inquiry scan. iscan Enable inquiry scan, disable page scan. pscan Enable page scan, disable inquiry scan. ptype [*type*] With no *type* , displays the current packet types. Otherwise, all the packet types specified by *type* are set. *type* is a comma-separated list of packet types, where the possible packet types are **DM1**, **DM3**, **DM5**, **DH1**, **DH3**, **DH5**, **HV1**, **HV2**, **HV3**. name [*name*] With no *name*, prints local name. Otherwise, sets local name to *name*. class [*class*] With no *class*, prints class of device. Otherwise, sets class of device to *class*. *class* is a 24-bit hex number describing the class of device, as specified in section 1.2 of the Bluetooth Assigned Numbers document. voice [*voice*] With no *voice*, prints voice setting. Otherwise, sets voice setting to *voice*. *voice* is a 16-bit hex number describing the voice setting. iac [*iac*] With no *iac*, prints the current IAC setting. Otherwise, sets the IAC to *iac*. inqtpl [*level*] With no *level*, prints out the current inquiry transmit power level. Otherwise, sets inquiry transmit power level to *level*. inqmode [*mode*] With no *mode*, prints out the current inquiry mode. Otherwise, sets inquiry mode to *mode*. .. list-table:: :header-rows: 1 :widths: auto * - *mode* - Description * - 0 - Standard Inquiry * - 1 - Inquiry with RSSI * - 2 - Inquiry with RSSI or Extended Inquiry inqdata [*data*] With no *data*, prints out the current inquiry data. Otherwise, sets inquiry data to *data*. inqtype [*type*] With no *type*, prints out the current inquiry scan type. Otherwise, sets inquiry scan type to *type*. inqparams [*win:int*] With no *win:int*, prints inquiry scan window and interval. Otherwise, sets inquiry scan window to *win* slots and inquiry scan interval to *int* slots. pageparms [*win:int*] With no *win:int*, prints page scan window and interval. Otherwise, sets page scan window to *win* slots and page scan interval to *int* slots. pageto [*to*] With no *to*, prints page timeout. Otherwise, sets page timeout *to* to slots. afhmode [*mode*] With no *mode*, prints out the current AFH mode. Otherwise, sets AFH mode to *mode*. .. list-table:: :header-rows: 1 :widths: auto * - *mode* - Description * - 0 - Enable * - 1 - Disable sspmode [*mode*] With no *mode*, prints out the current Simple Pairing mode. Otherwise, sets Simple Pairing mode to *mode*. .. list-table:: :header-rows: 1 :widths: auto * - *mode* - Description * - 0 - Enable * - 1 - Disable aclmtu *mtu:pkt* Sets ACL MTU to *mtu* bytes and ACL buffer size to *pkt* packets. scomtu *mtu:pkt* Sets SCO MTU to *mtu* bytes and SCO buffer size to *pkt* packets. delkey <*bdaddr*> This command deletes the stored link key for *bdaddr* from the device. oobdata Get local OOB data (invalidates previously read data). commands Display supported commands. features Display device features. version Display version information. revision Display revision information. lm [*mode*] With no *mode*, prints link mode. **CENTRAL** or **PERIPHERAL** mean, respectively, to ask to become central or to remain peripheral when a connection request comes in. The additional keyword **ACCEPT** means that baseband connections will be accepted even if there are no listening *AF_BLUETOOTH* sockets. *mode* is **NONE** or a comma-separated list of keywords, where possible keywords are **CENTRAL** and **ACCEPT**. **NONE** sets link policy to the default behaviour of remaining peripheral and not accepting baseband connections when there are no listening *AF_BLUETOOTH* sockets. If **CENTRAL** is present, the device will ask to become central if a connection request comes in. If **ACCEPT** is present, the device will accept baseband connections even when there are no listening *AF_BLUETOOTH* sockets. block <*bdaddr*> Add a device to the reject list unblock <*bdaddr*> Remove a device from the reject list lerandaddr <*bdaddr*> Set LE Random Address leadv [*type*] Enable LE Advertising. .. list-table:: :header-rows: 1 :widths: auto * - *type* - Description * - 0 - Connectable undirected advertising (default) * - 3 - Non connectable undirected advertising noleadv Disable LE Advertising lestates Display the supported LE states RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.obex.Image.rst0000644000000000000000000000005015063045254017042 xustar0020 atime=1783094025 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.Image.rst0000644000000000000000000000505115063045254016524 0ustar00rootroot==================== org.bluez.obex.Image ==================== -------------------------------------------------- BlueZ D-Bus OBEX Image API documentation -------------------------------------------------- :Version: BlueZ :Date: August 2024 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez.obex :Interface: org.bluez.obex.Image1 [experimental] :Object path: [Session object path] Methods ------- object, dict Get(string targetfile, string handle, dict description) ```````````````````````````````````````````````````````````````````` Retrieves the image corresponding to the handle and the description, as one of the descriptions retrieved by GetImageProperties, and store it in a local file. If the "transform" property description exists it should be set to one of the value listed by GetImageProperties for this description. If description is an empty dict, the native image will be retrieved. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: array{dict} Properties(string handle) ````````````````````````````````````` Retrieves the image properties corresponding to the handle. The first dict entry is mandatory and correspond to 'handle' and 'name' of the image. The second dict entry is mandatory and correspond to the native description ('type':'native') of the image. The following dict entries are optional and correspond to variant descriptions of the image. If the 'transform' entry exists in the description, it lists the available possible image transformations and should be set to one of them before using the description as parameter to **GetImage**. Possible property values: :string type: Type of dict properties. Mandatory for each dict. Possible values: :"native": :"variant": :string encoding: File encoding format. Possible values: :"BMP": :"GIF": :"JPEG": :"JPEG2000": :"PNG": :"WBMP": :string pixel: File encoding format size of form "*". :uint64 size: File size. :uint64 maxsize: File maximum size. :string transformation: List of available transformations separated by space. Possible values: :"crop": :"fill": :"stretch": Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: object, dict GetThumbnail(string targetfile, string handle) ``````````````````````````````````````````````````````````` Retrieves the image thumbnail corresponding to the handle and store it in a local file. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: bluez-5.87/doc/PaxHeaders/btmon-a2dp.rst0000644000000000000000000000005015200607060015111 xustar0020 atime=1783092683 20 ctime=1783094112 bluez-5.87/doc/btmon-a2dp.rst0000644000000000000000000003226015200607060014575 0ustar00rootroot.. This file is included by btmon-classic-audio.rst. A2DP: Advanced Audio Distribution ----------------------------------- A2DP uses AVDTP (Audio/Video Distribution Transport Protocol) over L2CAP to negotiate codec parameters and stream high-quality audio. btmon fully decodes AVDTP signaling, codec capabilities, and AVRCP remote control messages. AVDTP Signaling Channel ~~~~~~~~~~~~~~~~~~~~~~~~ A2DP begins with an L2CAP connection on PSM 25 (0x0019). The first L2CAP connection on this PSM carries AVDTP signaling; a second connection on the same PSM carries media transport data:: < ACL Data TX: Handle 1 flags 0x00 dlen 12 L2CAP: Connection Request (0x02) ident 1 len 4 PSM: 25 (0x0019) Source CID: 64 > ACL Data RX: Handle 1 flags 0x02 dlen 16 L2CAP: Connection Response (0x03) ident 1 len 8 Destination CID: 64 Source CID: 64 Result: Connection successful (0x0000) Status: No further information available (0x0000) After L2CAP configuration completes, AVDTP signaling begins on this channel. Stream Endpoint Discovery ~~~~~~~~~~~~~~~~~~~~~~~~~~ The initiator discovers available Stream Endpoints (SEPs) on the remote device:: < ACL Data TX: Handle 1 flags 0x00 dlen 6 Channel: 64 len 2 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Discover (0x01) Command (0x00) type 0x00 label 0 nosp 0 > ACL Data RX: Handle 1 flags 0x02 dlen 10 Channel: 64 len 6 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Discover (0x01) Response Accept (0x02) type 0x00 label 0 nosp 0 ACP SEID: 1 Media Type: Audio (0x00) SEP Type: SNK (0x01) In use: No Each SEP has a SEID (Stream Endpoint Identifier), media type, and role (Source or Sink). ``In use: Yes`` means the endpoint is already streaming. Codec Capability Discovery ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ After discovering SEIDs, the initiator queries each endpoint's capabilities:: < ACL Data TX: Handle 1 flags 0x00 dlen 7 Channel: 64 len 3 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Get All Capabilities (0x0c) Command (0x00) type 0x00 label 1 nosp 0 ACP SEID: 1 > ACL Data RX: Handle 1 flags 0x02 dlen 30 Channel: 64 len 26 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Get All Capabilities (0x0c) Response Accept (0x02) type 0x00 label 1 nosp 0 Service Category: Media Transport (0x01) Service Category: Media Codec (0x07) Media Type: Audio (0x00) Media Codec: SBC (0x00) Frequency: 0xf0 16000 32000 44100 48000 Channel Mode: 0x0f Mono Dual Channel Stereo Joint Stereo Block Length: 0xf0 4 8 12 16 Subbands: 0x0c 4 8 Allocation Method: 0x03 SNR Loudness Minimum Bitpool: 2 Maximum Bitpool: 53 Service Category: Content Protection (0x04) Content Protection Type: SCMS-T (0x0002) Service Category: Delay Reporting (0x08) Capability responses list all supported values as bitmasks. Key service categories: - **Media Transport** (0x01) -- Always present, indicates the endpoint supports a media transport channel - **Media Codec** (0x07) -- Codec type and supported parameters - **Content Protection** (0x04) -- Copy protection (SCMS-T or DTCP) - **Delay Reporting** (0x08) -- Endpoint supports delay reporting Supported Codecs ~~~~~~~~~~~~~~~~~ btmon decodes several A2DP codecs. Each has a different parameter format. **SBC** (mandatory A2DP codec):: Media Codec: SBC (0x00) Frequency: 44100 (0x20) Channel Mode: Joint Stereo (0x01) Block Length: 16 (0x10) Subbands: 8 (0x04) Allocation Method: Loudness (0x01) Minimum Bitpool: 2 Maximum Bitpool: 53 **AAC** (MPEG-2,4):: Media Codec: MPEG-2,4 AAC (0x02) Object Type: MPEG-4 AAC LC (0x40) Frequency: 44100 (0x0100) Channels: 2 (0x04) Bitrate: 256000bps VBR: No **aptX** (Qualcomm, vendor-specific):: Media Codec: Non-A2DP (0xff) Vendor ID: Qualcomm Technologies International, Ltd. (APT) (0x0000004f) Vendor Specific Codec ID: aptX (0x0001) Frequency: 44100 (0x20) Channel Mode: Stereo (0x02) **aptX HD** (Qualcomm, vendor-specific):: Media Codec: Non-A2DP (0xff) Vendor ID: Qualcomm Technologies, Inc. (0x000000d7) Vendor Specific Codec ID: aptX HD (0x0024) Frequency: 44100 (0x20) Channel Mode: Stereo (0x02) **LDAC** (Sony, vendor-specific):: Media Codec: Non-A2DP (0xff) Vendor ID: Sony Corporation (0x0000012d) Vendor Specific Codec ID: LDAC (0x00aa) Vendor codecs appear as ``Non-A2DP (0xff)`` with decoded Vendor ID and Codec ID. Capability responses list all supported values as bitmasks; configuration responses show the single selected value. Stream Configuration ~~~~~~~~~~~~~~~~~~~~~ After selecting a codec and parameters, the initiator sends Set Configuration:: < ACL Data TX: Handle 1 flags 0x00 dlen 20 Channel: 64 len 16 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Set Configuration (0x03) Command (0x00) type 0x00 label 2 nosp 0 ACP SEID: 1 INT SEID: 1 Service Category: Media Transport (0x01) Service Category: Media Codec (0x07) Media Type: Audio (0x00) Media Codec: SBC (0x00) Frequency: 44100 (0x20) Channel Mode: Joint Stereo (0x01) Block Length: 16 (0x10) Subbands: 8 (0x04) Allocation Method: Loudness (0x01) Minimum Bitpool: 2 Maximum Bitpool: 53 > ACL Data RX: Handle 1 flags 0x02 dlen 6 Channel: 64 len 2 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Set Configuration (0x03) Response Accept (0x02) type 0x00 label 2 nosp 0 Configuration responses show single selected values instead of bitmask lists. ``ACP SEID`` is the remote endpoint, ``INT SEID`` is the local endpoint. If configuration is rejected:: > ACL Data RX: Handle 1 flags 0x02 dlen 8 Channel: 64 len 4 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Set Configuration (0x03) Response Reject (0x03) type 0x00 label 2 nosp 0 Service Category: Media Codec (0x07) Error code: Unsupported Configuration (0x29) Common AVDTP error codes: .. list-table:: :header-rows: 1 :widths: 10 30 60 * - Code - Name - Meaning * - 0x01 - Bad Header Format - Malformed AVDTP header * - 0x11 - Bad ACP SEID - Unknown endpoint ID * - 0x12 - SEP In Use - Endpoint already streaming * - 0x13 - SEP Not In Use - Endpoint not configured * - 0x29 - Unsupported Configuration - Requested parameters not supported * - 0x31 - Bad State - Command not valid in current state Open and Start ~~~~~~~~~~~~~~~ After configuration, the stream is opened and then started:: < ACL Data TX: Handle 1 flags 0x00 dlen 7 Channel: 64 len 3 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Open (0x06) Command (0x00) type 0x00 label 3 nosp 0 ACP SEID: 1 > ACL Data RX: Handle 1 flags 0x02 dlen 6 Channel: 64 len 2 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Open (0x06) Response Accept (0x02) type 0x00 label 3 nosp 0 After Open succeeds, a **second L2CAP connection** on PSM 25 is established for the media transport channel. Then Start begins streaming:: < ACL Data TX: Handle 1 flags 0x00 dlen 7 Channel: 64 len 3 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Start (0x07) Command (0x00) type 0x00 label 4 nosp 0 ACP SEID: 1 > ACL Data RX: Handle 1 flags 0x02 dlen 6 Channel: 64 len 2 [PSM 25 mode Basic (0x00)] {chan 0} AVDTP: Start (0x07) Response Accept (0x02) type 0x00 label 4 nosp 0 Media Data ~~~~~~~~~~~ After Start, encoded audio flows on the media transport channel (second L2CAP connection on PSM 25). btmon does **not** decode media packet contents by default -- media data is suppressed. Only the L2CAP channel header is visible when the ``--show-a2dp-stream`` filter is enabled, with the payload shown as a raw hex dump. The AVDTP state machine visible in the trace: .. list-table:: :header-rows: 1 :widths: 20 30 50 * - State - Triggered By - Description * - Idle - Initial / Abort response - No stream configured * - Configured - Set Configuration accept - Codec and parameters selected * - Open - Open accept / Suspend accept - Transport channel ready, not streaming * - Streaming - Start accept - Audio data flowing * - Closing - Close command sent - Tearing down stream * - Aborting - Abort command sent - Forced teardown Suspend, Close, and Abort ~~~~~~~~~~~~~~~~~~~~~~~~~~~ **Suspend** pauses streaming without tearing down the transport:: AVDTP: Suspend (0x09) Command (0x00) type 0x00 label 5 nosp 0 ACP SEID: 1 AVDTP: Suspend (0x09) Response Accept (0x02) type 0x00 label 5 nosp 0 **Close** tears down the stream (returns to Idle):: AVDTP: Close (0x08) Command (0x00) type 0x00 label 6 nosp 0 ACP SEID: 1 AVDTP: Close (0x08) Response Accept (0x02) type 0x00 label 6 nosp 0 **Abort** forces immediate teardown:: AVDTP: Abort (0x0a) Command (0x00) type 0x00 label 7 nosp 0 ACP SEID: 1 AVDTP: Abort (0x0a) Response Accept (0x02) type 0x00 label 7 nosp 0 Delay Reporting ~~~~~~~~~~~~~~~~ When the sink supports delay reporting, it sends a Delay Report to inform the source of its rendering delay:: AVDTP: Delay Report (0x0d) Command (0x00) type 0x00 label 8 nosp 0 ACP SEID: 1 Delay: 15.0ms AVDTP: Delay Report (0x0d) Response Accept (0x02) type 0x00 label 8 nosp 0 AVRCP Remote Control ~~~~~~~~~~~~~~~~~~~~~ AVRCP (Audio/Video Remote Control Profile) uses L2CAP PSM 23 (control) and PSM 27 (browsing). btmon decodes AVCTP framing and AVRCP PDUs. **Volume control**:: < ACL Data TX: Handle 1 flags 0x00 dlen 17 Channel: 66 len 13 [PSM 23 mode Basic (0x00)] {chan 2} AVCTP Control: Response: type 0x00 label 0 PID 0x110e AV/C: Accepted: address 0x48 opcode 0x00 Subunit: Panel Opcode: Vendor Dependent Company ID: 0x001958 AVRCP: SetAbsoluteVolume pt Single len 0x0001 Volume: 50.39% (64/127) **Playback status**:: AVRCP: GetPlayStatus pt Single len 0x0009 SongLength: 0x00038270 (230000 milliseconds) SongPosition: 0x00000000 (0 milliseconds) PlayStatus: 0x01 (PLAYING) **Track metadata** (GetElementAttributes response):: AVRCP: GetElementAttributes pt Single len 0x0050 AttributeCount: 0x02 Attribute: 0x00000001 (Title) CharsetID: 0x006a (UTF-8) AttributeValueLength: 0x000c AttributeValue: My Song Name Attribute: 0x00000002 (Artist) CharsetID: 0x006a (UTF-8) AttributeValueLength: 0x000b AttributeValue: The Artist **Passthrough commands** (play, pause, skip):: AVCTP Control: Command: type 0x00 label 1 PID 0x110e AV/C: Control: address 0x48 opcode 0x7c Subunit: Panel Opcode: Passthrough Operation: 0x44 (PLAY Pressed) Length: 0x00 **Event notifications** (volume change, track change):: AVRCP: RegisterNotification pt Single len 0x0005 EventID: 0x0d (EVENT_VOLUME_CHANGED) Volume: 50.39% (64/127) Automating A2DP Analysis ~~~~~~~~~~~~~~~~~~~~~~~~~~ **Identify A2DP activity**:: grep -n "AVDTP:\|Media Codec:" output.txt **Track AVDTP state transitions**:: grep -n "AVDTP:.*Command\|AVDTP:.*Response" output.txt **Check codec negotiation** (what was selected):: grep -n "Set Configuration\|Media Codec:" output.txt **Verify stream is flowing** -- look for Start accept followed by media data:: grep -n "AVDTP: Start\|PSM 25.*chan 1" output.txt **Detect codec mismatch** -- look for Set Configuration reject:: grep -n "Response Reject\|Error code:" output.txt **Track AVRCP volume and playback**:: grep -n "SetAbsoluteVolume\|Volume:\|GetPlayStatus\|PlayStatus:" output.txt **Full A2DP diagnosis pattern**: 1. Find L2CAP Connection Request for PSM 25 -- confirms AVDTP channel setup 2. Find Discover response -- shows available endpoints and whether any are already in use 3. Find Get All Capabilities response -- verify codec support overlap 4. Find Set Configuration -- verify agreed codec and parameters 5. Find Open and Start -- confirm stream setup succeeded 6. Find second L2CAP connection on PSM 25 -- media transport channel 7. On failure, check for Response Reject with error code 8. If audio cuts out, look for Suspend or Close, or check for ACL disconnection bluez-5.87/doc/PaxHeaders/org.bluez.AgentManager.50000644000000000000000000000005015221755274016757 xustar0020 atime=1783093948 20 ctime=1783094112 bluez-5.87/doc/org.bluez.AgentManager.50000644000000000000000000000557115221755274016450 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.AgentManager" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.AgentManager \- BlueZ D-Bus AgentManager API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.AgentManager1 .TP .B Object path /org/bluez .TP .B Used by \fBbluetoothctl(1)\fP .UNINDENT .SS Methods .SS void RegisterAgent(object agent, string capability) .sp Registers pairing agent. .sp The object path defines the path of the agent that will be called when user input is needed and must implement \fBorg.bluez.Agent(5)\fP interface. .sp Every application can register its own agent and for all actions triggered by that application its agent is used. .sp It is not required by an application to register an agent. If an application does chooses to not register an agent, the default agent is used. This is on most cases a good idea. Only application like a pairing wizard should register their own agent. .sp An application can only register one agent. Multiple agents per application is not supported. .sp Possible capability values: .INDENT 0.0 .TP .B \(dq\(dq Fallback to \(dqKeyboardDisplay\(dq. .TP .B \(dqDisplayOnly\(dq .TP .B \(dqDisplayYesNo\(dq .TP .B \(dqKeyboardOnly\(dq .TP .B \(dqNoInputNoOutput\(dq .TP .B \(dqKeyboardDisplay\(dq .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.AlreadyExists .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > agent .UNINDENT .SS void UnregisterAgent(object agent) .sp Unregisters an agent that has been previously registered using \fBRegisterAgent\fP\&. The object path parameter must match the same value that has been used on registration. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.DoesNotExist .UNINDENT .SS void RequestDefaultAgent(object agent) .sp Requests to make the application agent the default agent. The application is required to register an agent. .sp Special permission might be required to become the default agent. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.DoesNotExist .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > default\-agent .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/bluetoothctl-advertise.10000644000000000000000000000005015221755453017207 xustar0020 atime=1783094059 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-advertise.10000644000000000000000000005564415221755453016706 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "bluetoothctl-advertise" "1" "November 2022" "BlueZ" "Linux System Administration" .SH Name bluetoothctl-advertise \- Advertise Submenu .SH SYNOPSIS .sp \fBbluetoothctl\fP [\-\-options] [advertise.commands] .sp This submenu configures LE advertising data and parameters using the \fBorg.bluez.LEAdvertisement(5)\fP and \fBorg.bluez.LEAdvertisingManager(5)\fP interfaces. .SH Advertise Options Commands .SS uuids .sp Set/Get advertise uuids. .INDENT 0.0 .TP .B Usage \fB> uuids [uuid1 uuid2 ...]\fP .TP .B Uses \fBorg.bluez.LEAdvertisement(5)\fP property \fBServiceUUIDs\fP .TP .B [uuid1 uuid2 ...] List of UUIDs to advertise (default: none) .TP .B Example Show current advertised UUIDs .nf \fB> uuids\fP .fi .sp .TP .B Example Advertise custom 16\-bit service UUID .nf \fB> uuids 0x1234\fP .fi .sp .TP .B Example Advertise custom 32\-bit service UUID .nf \fB> uuids 0x12345678\fP .fi .sp .TP .B Example Advertise custom 128\-bit service UUID .nf \fB> uuids 90f95193\-35de\-4306\-a6e9\-699328f15059\fP .fi .sp .TP .B Example Advertise Battery and Generic Attribute services .nf \fB> uuids 0x180F 0x1801\fP .fi .sp .TP .B Example Advertise Device Information, Battery, and HID services .nf \fB> uuids 0x180A 0x180F 0x1812\fP .fi .sp .TP .B Example Advertise Generic Access service .nf \fB> uuids 0x1800\fP .fi .sp .TP .B Example Advertise Environmental Sensing service .nf \fB> uuids 0x181A\fP .fi .sp .TP .B Example Advertise Pulse Oximeter service .nf \fB> uuids 0x183B\fP .fi .sp .TP .B Example Mix short and full UUID formats .nf \fB> uuids 0x180F 12345678\-1234\-5678\-9abc\-123456789abc\fP .fi .sp .UNINDENT .SS solicit .sp Set/Get advertise solicit uuids. .INDENT 0.0 .TP .B Usage \fB> solicit [uuid1 uuid2 ...]\fP .TP .B Uses \fBorg.bluez.LEAdvertisement(5)\fP property \fBSolicitUUIDs\fP .TP .B [uuid1 uuid2 ...] List of UUIDs to advertise (default: none) .TP .B Example Show current solicited service UUIDs .nf \fB> solicit\fP .fi .sp .TP .B Example Solicit Generic Access service .nf \fB> solicit 0x1800\fP .fi .sp .TP .B Example Solicit Device Information service .nf \fB> solicit 0x180A\fP .fi .sp .TP .B Example Solicit User Data service .nf \fB> solicit 0x181C\fP .fi .sp .TP .B Example Solicit Battery and Generic Attribute services .nf \fB> solicit 0x180F 0x1801\fP .fi .sp .TP .B Example Solicit Device Info, Environmental Sensing, and Pulse Oximeter .nf \fB> solicit 0x180A 0x181A 0x183B\fP .fi .sp .UNINDENT .SS service .sp Set/Get advertise service data. .INDENT 0.0 .TP .B Usage \fB> service [uuid] [data=xx xx ...]\fP .TP .B Uses \fBorg.bluez.LEAdvertisement(5)\fP property \fBServiceData\fP .TP .B [uuid] Service UUID .TP .B [data=xx xx ...] Service data .TP .B Example Show current service data .nf \fB> service\fP .fi .sp .TP .B Example Set battery level to 100 (0x64) .nf \fB> service 0x180F 64\fP .fi .sp .TP .B Example Set battery level to 50 (0x32) .nf \fB> service 0x180F 32\fP .fi .sp .TP .B Example Set battery level to 0 (0x00) .nf \fB> service 0x180F 00\fP .fi .sp .TP .B Example Set environmental data (temperature, humidity, pressure) .nf \fB> service 0x181A 20 15 C8 07\fP .fi .sp .TP .B Example Set device information data .nf \fB> service 0x180A 01 02 03 04\fP .fi .sp .TP .B Example Set custom service data with 128\-bit UUID .nf \fB> service 12345678\-1234\-5678\-9abc\-123456789abc FF EE DD CC\fP .fi .sp .UNINDENT .SS manufacturer .sp Set/Get advertise manufacturer data. .sp Updating is in real time while advertising. This is currently limited to 25 bytes and will return an error message of \(dqToo much data\(dq if that maximum has been exceeded. However, this does not check if the advertising payload length maximum has been exceeded so you may receive an error from bluetoothd that it \(dqFailed to register advertisement\(dq which means you need to reduce your manufacturer data length. .INDENT 0.0 .TP .B Usage \fB> manufacturer [id] [data=xx xx ...]\fP .TP .B Uses \fBorg.bluez.LEAdvertisement(5)\fP property \fBManufacturerData\fP .TP .B [id] Manufacturer ID (default: 0x004C for Apple Inc.) .TP .B [data=xx xx ...] Manufacturer data .TP .B Example Show current manufacturer data .nf \fB> manufacturer\fP .fi .sp .TP .B Example Set Apple Inc. manufacturer data .nf \fB> manufacturer 0x004C 01 02 03 04\fP .fi .sp .TP .B Example Set Apple manufacturer data with 8 bytes .nf \fB> manufacturer 0x004C FF FE FD FC FB FA F9 F8\fP .fi .sp .TP .B Example Set Microsoft Corp. manufacturer data .nf \fB> manufacturer 0x0006 10 11 12 13\fP .fi .sp .TP .B Example Set Samsung Electronics manufacturer data .nf \fB> manufacturer 0x0075 AA BB CC DD EE\fP .fi .sp .TP .B Example Set Google LLC manufacturer data .nf \fB> manufacturer 0x00E0 01 23 45 67 89 AB CD EF\fP .fi .sp .TP .B Example Set custom manufacturer ID with test data .nf \fB> manufacturer 0xFFFF DE AD BE EF\fP .fi .sp .UNINDENT .SS data .sp Set/Get advertise data. .sp This allows you to advertise data with a given type. You cannot use a registered data type value {1} with this command. For LE the advertising shows up in the primary advertisements. .sp If you set only the type of the data without any data (data 0x0c) this will cause a parse error when turning advertise on. .sp You can modify the advertising data while it is advertising. .sp To get the currently set data use the command data without any arguments. .INDENT 0.0 .TP .B Usage \fB> data [type] [data=xx xx ...]\fP .TP .B Uses \fBorg.bluez.LEAdvertisement(5)\fP property \fBData\fP .TP .B [type] Advertising data type .TP .B [data=xx xx ...] Advertising data (default: none) .TP .B Example Show current advertising data .nf \fB> data\fP .fi .sp .TP .B Example Set slave connection interval range data .nf \fB> data 0x0C 01 0x0F 13\fP .fi .sp .TP .B Example Set complete local name to \(dqHello\(dq .nf \fB> data 0x09 48 65 6C 6C 6F\fP .fi .sp .TP .B Example Set shortened local name to \(dqTest\(dq .nf \fB> data 0x08 54 65 73 74\fP .fi .sp .TP .B Example Set flags (LE General Discoverable + BR/EDR not supported) .nf \fB> data 0x01 06\fP .fi .sp .TP .B Example Set appearance (keyboard, 0x0341) .nf \fB> data 0x19 41 03\fP .fi .sp .TP .B Example Set TX power level to +4 dBm .nf \fB> data 0x0A 04\fP .fi .sp .TP .B Example Set TX power level to \-4 dBm .nf \fB> data 0x0A FC\fP .fi .sp .TP .B Example Set URI \(dq//google.com\(dq .nf \fB> data 0x24 17 2F 2F 67 6F 6F 67 6C 65 2E 63 6F 6D\fP .fi .sp .TP .B Example Set service data for Battery Service (UUID 0x180F, level 100) .nf \fB> data 0x16 0F 18 64\fP .fi .sp .UNINDENT .SS sr\-uuids .sp Set/Get scan response uuids. .INDENT 0.0 .TP .B Usage \fB> sr\-uuids [uuid1 uuid2 ...]\fP .TP .B [uuid1 uuid2 ...] List of UUIDs to advertise in scan response .TP .B Example Show current scan response UUIDs .nf \fB> sr\-uuids\fP .fi .sp .TP .B Example Set Generic Access in scan response .nf \fB> sr\-uuids 0x1800\fP .fi .sp .TP .B Example Set Device Info and Battery services in scan response .nf \fB> sr\-uuids 0x180A 0x180F\fP .fi .sp .TP .B Example Set custom UUID in scan response .nf \fB> sr\-uuids 12345678\-1234\-5678\-9abc\-123456789abc\fP .fi .sp .UNINDENT .SS sr\-solicit .sp Set/Get scan response solicit uuids. .INDENT 0.0 .TP .B Usage \fB> sr\-solicit [uuid1 uuid2 ...]\fP .TP .B [uuid1 uuid2 ...] List of UUIDs to advertise in scan response .TP .B Example Show current scan response solicited UUIDs .nf \fB> sr\-solicit\fP .fi .sp .TP .B Example Solicit Generic Attribute service in scan response .nf \fB> sr\-solicit 0x1801\fP .fi .sp .TP .B Example Solicit Device Info and Environmental Sensing in scan response .nf \fB> sr\-solicit 0x180A 0x181A\fP .fi .sp .UNINDENT .SS sr\-service .sp Set/Get scan response service data. .INDENT 0.0 .TP .B Usage \fB> sr\-service [uuid] [data=xx xx ...]\fP .TP .B [uuid] Service UUID .TP .B [data=xx xx ...] Service data .TP .B Example Show current scan response service data .nf \fB> sr\-service\fP .fi .sp .TP .B Example Set battery level to 128 in scan response .nf \fB> sr\-service 0x180F 80\fP .fi .sp .TP .B Example Set environmental data in scan response .nf \fB> sr\-service 0x181A 22 18 C0 05\fP .fi .sp .TP .B Example Set device info \(dqHello\(dq in scan response .nf \fB> sr\-service 0x180A 48 65 6C 6C 6F\fP .fi .sp .UNINDENT .SS sr\-manufacturer .sp Set/Get scan response manufacturer data. .INDENT 0.0 .TP .B Usage \fB> sr\-manufacturer [id] [data=xx xx ...]\fP .TP .B [id] Manufacturer ID (default: 0x004C for Apple Inc.) .TP .B [data=xx xx ...] Manufacturer data .TP .B Example Show current scan response manufacturer data .nf \fB> sr\-manufacturer\fP .fi .sp .TP .B Example Set Apple manufacturer data in scan response .nf \fB> sr\-manufacturer 0x004C 05 06 07 08\fP .fi .sp .TP .B Example Set Microsoft manufacturer data in scan response .nf \fB> sr\-manufacturer 0x0006 20 21 22 23\fP .fi .sp .TP .B Example Set custom manufacturer data in scan response .nf \fB> sr\-manufacturer 0xFFFF CA FE BA BE\fP .fi .sp .UNINDENT .SS sr\-data .sp Set/Get scan response data. .INDENT 0.0 .TP .B Usage \fB> sr\-data [type] [data=xx xx ...]\fP .TP .B [type] Scan Response data type .TP .B [data=xx xx ...] Scan Response data .TP .B Example Show current scan response data .nf \fB> sr\-data\fP .fi .sp .TP .B Example Set complete local name \(dqScanResp\(dq in scan response .nf \fB> sr\-data 0x09 53 63 61 6E 52 65 73 70\fP .fi .sp .TP .B Example Set TX power level +8 dBm in scan response .nf \fB> sr\-data 0x0A 08\fP .fi .sp .TP .B Example Set Battery service data (level 50) in scan response .nf \fB> sr\-data 0x16 0F 18 32\fP .fi .sp .UNINDENT .SS discoverable .sp Set/Get advertise discoverable. .sp For LE discoverable on will set the LE General Discoverable Mode flag to true in the primary advertisement if on. .sp This feature can be changed during advertising, but will only trigger LE General Discoverable Mode even if you had previously selected discoverable\-timeout this will be ignored. .sp Entering the command by itself will show the status of the setting .INDENT 0.0 .TP .B Usage \fB> discoverable [on/off]\fP .TP .B Uses \fBorg.bluez.LEAdvertisement(5)\fP property \fBDiscoverable\fP .TP .B [on/off] Enable/Disable LE General Discoverable Mode .TP .B Example Show current discoverable setting .nf \fB> discoverable\fP .fi .sp .TP .B Example Enable LE General Discoverable Mode .nf \fB> discoverable on\fP .fi .sp .TP .B Example Disable discoverable mode .nf \fB> discoverable off\fP .fi .sp .UNINDENT .SS discoverable\-timeout .sp Set/Get advertise discoverable timeout. .sp Using this feature in LE will cause the LE Limited Discoverable Mode flag to be set in the primary advertisement and The LE General Discoverable Mode flag will not be set. .sp The LE Limited Discoverable Mode flag will automatically turn off after [seconds] discoverable [on] must be set to use this feature. .sp Entering the command by itself will show the current value set. .INDENT 0.0 .TP .B Usage \fB> discoverable\-timeout [seconds]\fP .TP .B Uses \fBorg.bluez.LEAdvertisement(5)\fP property \fBDiscoverableTimeout\fP .TP .B [seconds] Timeout duration in seconds for LE Limited Discoverable Mode .TP .B Example Show current discoverable timeout .nf \fB> discoverable\-timeout\fP .fi .sp .TP .B Example Set LE Limited Discoverable for 30 seconds .nf \fB> discoverable\-timeout 30\fP .fi .sp .TP .B Example Set LE Limited Discoverable for 2 minutes .nf \fB> discoverable\-timeout 120\fP .fi .sp .TP .B Example Set LE Limited Discoverable for 5 minutes .nf \fB> discoverable\-timeout 300\fP .fi .sp .TP .B Example Disable discoverable timeout (unlimited) .nf \fB> discoverable\-timeout 0\fP .fi .sp .UNINDENT .SS tx\-power .sp Show/Enable/Disable TX power to be advertised. .sp This sets the TX Power Level field in the advertising packet. .sp The value is in dBm and can be between \-127 and 127. .sp When this feature is turned on the LE device will advertise its transmit power in the primary advertisement. .sp This feature can be modified while advertising. .sp Entering the command by itself will show the current value set. .INDENT 0.0 .TP .B Usage \fB> tx\-power [on/off]\fP .TP .B Uses \fBorg.bluez.LEAdvertisement(5)\fP property \fBIncludes\fP .TP .B [on/off] Enable or disable TX power advertisement (optional, shows current if omitted) .TP .B Example Show current TX power advertisement setting .nf \fB> tx\-power\fP .fi .sp .TP .B Example Enable TX power advertisement with default power .nf \fB> tx\-power on\fP .fi .sp .TP .B Example Disable TX power advertisement .nf \fB> tx\-power off\fP .fi .sp .UNINDENT .SS name .sp Configure local name to be advertised. .sp Local name to be used in the advertising report. .sp If the string is too big to fit into the packet it will be truncated. .sp It will either advertise as a complete local name or if it has to be truncated then a shortened local name. .INDENT 0.0 .TP .B Usage \fB> name [on/off/name]\fP .TP .B Uses \fBorg.bluez.LEAdvertisement(5)\fP property \fBLocalName\fP .TP .B [name] Local name (optional, shows current if omitted) .TP .B Example Show current advertised name setting .nf \fB> name\fP .fi .sp .TP .B Example Enable advertising the system hostname .nf \fB> name on\fP .fi .sp .TP .B Example Disable name advertisement .nf \fB> name off\fP .fi .sp .TP .B Example Advertise \(dqMy Device\(dq as local name .nf \fB> name \(dqMy Device\(dq\fP .fi .sp .TP .B Example Advertise \(dqSmart Sensor\(dq as local name .nf \fB> name \(dqSmart Sensor\(dq\fP .fi .sp .TP .B Example Advertise device with ID suffix .nf \fB> name \(dqBLE\-Peripheral\-001\(dq\fP .fi .sp .TP .B Example Advertise long name (will be truncated to fit) .nf \fB> name \(dq0123456789abcdef0123456789abcdef\(dq\fP .fi .sp .TP .B Example Long descriptive name (automatically truncated) .nf \fB> name \(dqThis is a very long device name that will be truncated\(dq\fP .fi .sp .TP .B Example Name with special characters .nf \fB> name \(dqDevice™\(dq\fP .fi .sp .TP .B Example Name with accented characters .nf \fB> name \(dqTëst\-Dëvicë\(dq\fP .fi .sp .UNINDENT .SS appearance .sp Configure custom appearance to be advertised. .INDENT 0.0 .TP .B Usage \fB> appearance [on/off/value]\fP .TP .B Uses \fBorg.bluez.LEAdvertisement(5)\fP property \fBAppearance\fP .TP .B [value] Appearance value (optional, shows current if omitted) .TP .B Example Show current appearance setting .nf \fB> appearance\fP .fi .sp .TP .B Example Enable appearance advertisement with default value .nf \fB> appearance on\fP .fi .sp .TP .B Example Disable appearance advertisement .nf \fB> appearance off\fP .fi .sp .TP .B Example Set appearance to keyboard (0x03C1) .nf \fB> appearance 961\fP .fi .sp .TP .B Example Set appearance to mouse (0x03C2) .nf \fB> appearance 962\fP .fi .sp .TP .B Example Set appearance to joystick (0x03C3) .nf \fB> appearance 963\fP .fi .sp .TP .B Example Set appearance to gamepad (0x03C4) .nf \fB> appearance 964\fP .fi .sp .TP .B Example Set appearance to generic audio sink (0x0340) .nf \fB> appearance 832\fP .fi .sp .TP .B Example Set appearance to speaker (0x0341) .nf \fB> appearance 833\fP .fi .sp .TP .B Example Set appearance to microphone (0x0342) .nf \fB> appearance 834\fP .fi .sp .TP .B Example Set appearance to headset (0x0343) .nf \fB> appearance 835\fP .fi .sp .TP .B Example Set appearance to headphones (0x0344) .nf \fB> appearance 836\fP .fi .sp .TP .B Example Set appearance to heart rate sensor (0x0540) .nf \fB> appearance 1344\fP .fi .sp .TP .B Example Set appearance to thermometer (0x05C0) .nf \fB> appearance 1472\fP .fi .sp .TP .B Example Set appearance to glucose meter (0x0640) .nf \fB> appearance 1600\fP .fi .sp .TP .B Example Set appearance to generic phone (0x0040) .nf \fB> appearance 64\fP .fi .sp .TP .B Example Set appearance to generic computer (0x0080) .nf \fB> appearance 128\fP .fi .sp .TP .B Example Set appearance to generic watch (0x00C0) .nf \fB> appearance 192\fP .fi .sp .UNINDENT .SS duration .sp Set/Get advertise duration. .sp The Duration parameter configures the length of an Instance. .sp The value is in seconds. .sp A value of 0 indicates a default value is chosen for the Duration. .sp The default is 2 seconds. .sp If only one advertising Instance has been added, then the Duration value will be ignored. .sp If multiple advertising Instances have been added, then the Duration value will be used to determine the length of time each Instance is advertised for. .sp The Duration value is used to calculate the number of advertising events that will be used to advertise each Instance. .sp The number of advertising events is calculated by dividing the Duration value by the advertising interval. .sp The advertising interval is determined by the advertising parameters that are set for each Instance. The advertising interval is the maximum of the advertising intervals set for each Instance. .INDENT 0.0 .TP .B Usage \fB> duration [seconds]\fP .TP .B Uses \fBorg.bluez.LEAdvertisement(5)\fP property \fBDuration\fP .TP .B [seconds] Duration in seconds (optional, shows current if omitted) .TP .B Example Show current advertising duration setting .nf \fB> duration\fP .fi .sp .TP .B Example Use default duration (2 seconds) .nf \fB> duration 0\fP .fi .sp .TP .B Example Set duration to 1 second .nf \fB> duration 1\fP .fi .sp .TP .B Example Set duration to 5 seconds .nf \fB> duration 5\fP .fi .sp .TP .B Example Set duration to 10 seconds .nf \fB> duration 10\fP .fi .sp .TP .B Example Set duration to 30 seconds .nf \fB> duration 30\fP .fi .sp .TP .B Example Set duration to 1 minute .nf \fB> duration 60\fP .fi .sp .TP .B Example Set duration to 5 minutes .nf \fB> duration 300\fP .fi .sp .UNINDENT .SS timeout .sp Set/Get advertise timeout. .INDENT 0.0 .TP .B Usage \fB> timeout [seconds]\fP .TP .B Uses \fBorg.bluez.LEAdvertisement(5)\fP property \fBTimeout\fP .TP .B [seconds] Timeout in seconds (optional, shows current if omitted) .TP .B Example Show current advertising timeout setting .nf \fB> timeout\fP .fi .sp .TP .B Example Unlimited advertising (no timeout) .nf \fB> timeout 0\fP .fi .sp .TP .B Example Stop advertising after 30 seconds .nf \fB> timeout 30\fP .fi .sp .TP .B Example Stop advertising after 2 minutes .nf \fB> timeout 120\fP .fi .sp .TP .B Example Stop advertising after 1 hour .nf \fB> timeout 3600\fP .fi .sp .UNINDENT .SS secondary .sp Set/Get advertise secondary channel. .INDENT 0.0 .TP .B Usage \fB> secondary [1M/2M/Coded]\fP .TP .B Uses \fBorg.bluez.LEAdvertisement(5)\fP property \fBSecondaryChannel\fP .TP .B [1M/2M/Coded] Secondary channel (optional, shows current if omitted) .TP .B Example Show current secondary channel setting .nf \fB> secondary\fP .fi .sp .TP .B Example Use LE 1M PHY for secondary advertising channel .nf \fB> secondary 1M\fP .fi .sp .TP .B Example Use LE 2M PHY for secondary advertising channel .nf \fB> secondary 2M\fP .fi .sp .TP .B Example Use LE Coded PHY for secondary advertising channel .nf \fB> secondary Coded\fP .fi .sp .UNINDENT .SS rsi .sp Show/Enable/Disable RSI to be advertised. .sp RSI (Resolvable Set Identifier) is used to advertise a resolvable identifier for Coordinated Set Identification. .INDENT 0.0 .TP .B Usage \fB> rsi [on/off]\fP .TP .B [on/off] Enable or disable RSI advertisement (optional, shows current if omitted) .TP .B Example Show current RSI setting .nf \fB> rsi\fP .fi .sp .TP .B Example Enable RSI advertisement .nf \fB> rsi on\fP .fi .sp .TP .B Example Disable RSI advertisement .nf \fB> rsi off\fP .fi .sp .UNINDENT .SS interval .sp Set/Get advertise interval. .sp The Interval parameter configures the advertising interval of an Instance. .sp The value is in milliseconds. .sp A value of 0 indicates a default value is chosen for the Interval. .sp The default is 100 milliseconds. .sp The Interval value is used to calculate the number of advertising events that will be used to advertise each Instance. .sp The number of advertising events is calculated by dividing the Duration value by the advertising interval. .sp The advertising interval is determined by the advertising parameters that are set for each Instance. .sp The advertising interval is the maximum of the advertising intervals set for each Instance. .INDENT 0.0 .TP .B Usage \fB> interval [min] [max]\fP .TP .B [min] Minimum advertising interval in milliseconds (optional, shows current if omitted) .TP .B [max] Maximum advertising interval in milliseconds (optional, defaults to min value) .TP .B Example Show current advertising interval setting .nf \fB> interval\fP .fi .sp .TP .B Example Set minimum advertising interval (20ms) .nf \fB> interval 20\fP .fi .sp .TP .B Example Set advertising interval range (50\-100ms) .nf \fB> interval 50 100\fP .fi .sp .TP .B Example Set normal advertising interval (100ms) .nf \fB> interval 100\fP .fi .sp .TP .B Example Set moderate advertising interval (200ms) .nf \fB> interval 200\fP .fi .sp .TP .B Example Set slow advertising interval (500ms) .nf \fB> interval 500\fP .fi .sp .TP .B Example Set very slow advertising interval (1 second) .nf \fB> interval 1000\fP .fi .sp .TP .B Example Set maximum advertising interval (10.24 seconds) .nf \fB> interval 10240\fP .fi .sp .UNINDENT .SS clear .sp Clear advertise config. .sp This will stop advertising if it is currently advertising. .sp If you want to change the advertise configuration while advertising you must first clear the advertise configuration and then set the new advertise configuration. .INDENT 0.0 .TP .B Usage \fB> clear [uuids/service/manufacturer/config\-name...]\fP .TP .B [uuids/service/manufacturer/config\-name...] List of configuration to clear .TP .B Example Clear all advertising configuration .nf \fB> clear\fP .fi .sp .TP .B Example Clear only advertised UUIDs .nf \fB> clear uuids\fP .fi .sp .TP .B Example Clear only service data .nf \fB> clear service\fP .fi .sp .TP .B Example Clear only manufacturer data .nf \fB> clear manufacturer\fP .fi .sp .TP .B Example Clear only advertised name .nf \fB> clear name\fP .fi .sp .TP .B Example Clear only appearance data .nf \fB> clear appearance\fP .fi .sp .TP .B Example Clear only TX power advertisement .nf \fB> clear tx\-power\fP .fi .sp .TP .B Example Clear UUIDs and service data .nf \fB> clear uuids service\fP .fi .sp .TP .B Example Clear manufacturer data and name .nf \fB> clear manufacturer name\fP .fi .sp .TP .B Example Clear UUIDs, service data, and manufacturer data .nf \fB> clear uuids service manufacturer\fP .fi .sp .TP .B Example Clear discoverable mode and TX power .nf \fB> clear discoverable tx\-power\fP .fi .sp .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/bluetoothctl-assistant.10000644000000000000000000000005015221755470017231 xustar0020 atime=1783094072 20 ctime=1783094113 bluez-5.87/doc/bluetoothctl-assistant.10000644000000000000000000000746115221755470016722 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "bluetoothctl-assistant" "1" "August 2024" "BlueZ" "Linux System Administration" .SH Name bluetoothctl-assistant \- Assistant Submenu .SH SYNOPSIS .sp \fBbluetoothctl\fP [\-\-options] [assistant.commands] .sp This submenu manages BAP Broadcast Assistants using the \fBorg.bluez.MediaAssistant(5)\fP interface. .SH Assistant Commands .SS list .sp List available assistants. .INDENT 0.0 .TP .B Usage \fB> list\fP .TP .B Example Display all available BAP Broadcast Assistants .nf \fB> list\fP .fi .sp .UNINDENT .SS show .sp Show assistant information. .INDENT 0.0 .TP .B Usage \fB> show [assistant]\fP .TP .B Uses \fBorg.bluez.MediaAssistant(5)\fP properties .TP .B [assistant] BAP Broadcast Assistant path (optional, shows current if omitted) .TP .B Example Show information for currently selected assistant .nf \fB> show\fP .fi .sp .TP .B Example Show specific BAP Broadcast Assistant information .nf \fB> show /org/bluez/hci0/src_05_1F_EE_F3_F8_7D/dev_00_60_37_31_7E_3F/bis1\fP .fi .sp .UNINDENT .SS push .sp Send stream information to peer. .sp This command is used by a BAP Broadcast Assistant to send information about a broadcast stream to a peer BAP Scan Delegator. .sp The information is sent via a GATT Write Command for the BASS Broadcast Audio Scan Control Point characteristic. .sp After issuing the command, the user is prompted to enter stream metadata LTVs to send to the peer. If the auto option is chosen, the Broadcast Assistant will send the default metadata discovered about the stream. Otherwise, the default metadata will be overwritten by the LTVs entered by the user. .sp If the stream is encrypted, the user will also be prompted to enter the Broadcast Code. This is the key to decrypt the stream. On the UI level, the Broadcast Code shall be represented as a string of at least 4 octets, and no more than 16 octets when represented in UTF\-8. The string will be sent to the peer via GATT as an array of 16 octets. .sp If the auto value is chosen when prompted for the Broadcast Code, a zero filled array will be sent to the peer. Otherwise, the string entered by the user will be sent as an array of bytes. .INDENT 0.0 .TP .B Usage \fB> push \fP .TP .B Uses \fBorg.bluez.MediaAssistant(5)\fP method \fBPush\fP .TP .B BAP Broadcast Assistant path to send stream information to .TP .B Example Push stream info with automatic metadata and broadcast code .nf \fB> push /org/bluez/hci0/src_05_1F_EE_F3_F8_7D/dev_00_60_37_31_7E_3F/bis1\fP \fB[Assistant] Enter Metadata (auto/value): auto\fP \fB[Assistant] Enter Broadcast Code (auto/value): auto\fP .fi .sp .TP .B Example Push stream info with custom metadata and broadcast code .nf \fB> push /org/bluez/hci0/src_05_1F_EE_F3_F8_7D/dev_00_60_37_31_7E_3F/bis1\fP \fB[Assistant] Enter Metadata (auto/value): 0x03 0x02 0x04 0x00\fP \fB[Assistant] Enter Broadcast Code (auto/value): Borne House\fP .fi .sp .UNINDENT .SH RESOURCES .sp \% .SH REPORTING BUGS .sp \% .SH Copyright Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.Thermometer.50000644000000000000000000000005015221755420016712 xustar0020 atime=1783094032 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Thermometer.50000644000000000000000000000344115221755420016375 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.Thermometer" "5" "July 2011" "BlueZ" "Linux System Administration" .SH Name org.bluez.Thermometer \- BlueZ D-Bus Health Thermometer API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.Thermometer1 .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX .UNINDENT .SS Properties .SS boolean Intermediate [readonly] .sp True if the thermometer supports intermediate measurement notifications. .SS uint16 Interval (optional) [readwrite] .sp The Measurement Interval defines the time (in seconds) between measurements. This interval is not related to the intermediate measurements and must be defined into a valid range. Setting it to zero means that no periodic measurements will be taken. .SS uint16 Maximum (optional) [readonly] .sp Defines the maximum value allowed for the interval between periodic measurements. .SS uint16 Minimum (optional) [readonly] .sp Defines the minimum value allowed for the interval between periodic measurements. .SH Author Santiago Carot-Nemesio .\" End of generated man page. bluez-5.87/doc/PaxHeaders/isotest.rst0000644000000000000000000000005015142177620014651 xustar0020 atime=1783094048 20 ctime=1783094113 bluez-5.87/doc/isotest.rst0000644000000000000000000001032315142177620014331 0ustar00rootroot======= isotest ======= ----------- ISO testing ----------- :Authors: - Luiz Augusto Von Dentz :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: May 4, 2022 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **isotest** <*MODE*> [*OPTIONS*] [*bdaddr*] [*bdaddr1*]... DESCRIPTION =========== **isotest(1)** is used to test Isochronous (CIS/BIS) communications on the BlueZ stack MODES ===== -d, --dump=[FILE] Listen and dump incoming data (CIS server/BIS broadcaster) and optionally save the contents to *FILE*. -c, --reconnect Reconnect (CIS client). -m, --multiple Multiple connects (CIS client). -r, --receive=[FILE] Receive (CIS server/BIS broadcast receiver) and optionally save the contents to *FILE*. -s, --send=[FILE] Connect and send (CIS client/BIS broadcaster), can optionally use contents from *FILE*. -n, --silent Connect and be silent (CIS client/BIS broadcaster). OPTIONS ======= -b, --bytes= Send or Receive packet size -i, --index= Select the specified HCI device index. *hciNUM* is also acceptable. -j, --jitter= Socket jitter buffer. -h, --help -q, --quiet Disables packet logging. -t, --timeout= Socket send timeout. -C, --continue Continuously send packets starting over in case of a file. -W, --defer= Enable deferred setup. -M, --mtu= Socket QoS SDU. -S, --sca/adv-interval= Socket QoS CIS SCA/BIS advertising interval. -P, --packing= Socket QoS Packing. .. list-table:: :header-rows: 1 :widths: auto :stub-columns: 1 :align: left * - *PACKING* - Description * - **0x00** - Sequential * - **0x01** - Interleaved -F, --framing= Socket QoS Framing. .. list-table:: :header-rows: 1 :widths: auto :stub-columns: 1 :align: left * - *FRAMING* - Description * - **0x00** - Unframed * - **0x01** - Framed -I, --interval= Socket QoS Interval. -L, --latency= Socket QoS Latency. -Y, --phy= Socket QoS PHY. .. list-table:: :header-rows: 1 :widths: auto :stub-columns: 1 :align: left * - *PHY* - Description * - **0x01** - LE 1M * - **0x02** - LE 2M * - **0x03** - LE Coded -R, --rtn= Socket QoS retransmissions. -B, --preset= Socket QoS preset. -G, --CIG/BIG= Socket QoS CIG/BIG ID. -T, --CIS/BIS= Socket QoS CIS/BIS ID. -V, --type= Socket destination address type: .. list-table:: :header-rows: 1 :widths: auto :stub-columns: 1 :align: left * - *TYPE* - Description * - **le_public** - LE Public Address * - **le_random** - LE Random Address -e, --enc= Socket QoS BIG Encryption .. list-table:: :header-rows: 1 :widths: auto :stub-columns: 1 :align: left * - *ENCRYPTION* - Description * - **0x00** - BIG unencrypted * - **0x01** - BIG encrypted -k, --bcode= Socket QoS Broadcast Code -N, --nbis= Number of BISes to create as part of a BIG (BIS broadcaster) or to synchronize to (BIS broadcast receiver) EXAMPLES ======== Unicast Central --------------- .. code-block:: $ tools/isotest -s XX:XX:XX:XX:XX:XX Unicast Central connecting to 2 peers using CIG 0x01 ---------------------------------------------------- .. code-block:: $ tools/isotest -G 0x01 -s XX:XX:XX:XX:XX:XX YY:YY:YY:YY:YY:YY Unicast Peripheral ------------------ .. code-block:: $ tools/isotest -d Broadcaster ----------- .. code-block:: $ tools/isotest -s 00:00:00:00:00:00 Broadcast Receiver using hci1 ----------------------------- .. code-block:: $ tools/isotest -i hci1 -d XX:XX:XX:XX:XX:XX RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.Adapter.50000644000000000000000000000005015221755262016003 xustar0020 atime=1783093938 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Adapter.50000644000000000000000000003172415221755262015473 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.Adapter" "5" "October 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.Adapter \- BlueZ D-Bus Adapter API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.Adapter1 .TP .B Object path [variable prefix]/{hci0,hci1,...} .TP .B Used by \fBbluetoothctl(1)\fP, \fBbluetoothctl\-scan(1)\fP .UNINDENT .SS Methods .SS void StartDiscovery() .sp Starts device discovery session which may include starting an inquiry and/or scanning procedures and remote device name resolving. .sp Use \fBStopDiscovery\fP to release the sessions acquired. .sp This process will start creating Device objects as new devices are discovered. .sp During discovery RSSI delta\-threshold is imposed. .sp Each client can request a single device discovery session per adapter. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.NotReady .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.InProgress .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > scan .UNINDENT .SS void StopDiscovery() .sp Stops device discovery session started by \fBStartDiscovery\fP\&. .sp Note that a discovery procedure is shared between all discovery sessions thus calling StopDiscovery will only release a single session and discovery will stop when all sessions from all clients have finished. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.NotReady .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.NotAuthorized .UNINDENT .SS void RemoveDevice(object device) .sp Removes the remote device object at the given path including cached information such as bonding information. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > remove .UNINDENT .SS void SetDiscoveryFilter(dict filter) .sp Sets the device discovery filter for the caller. When this method is called with no filter parameter, filter is removed. .sp Possible filter values: .INDENT 0.0 .TP .B array{string} UUIDs Filter by service UUIDs, empty means match \fIany\fP UUID. .sp When a remote device is found that advertises any UUID from UUIDs, it will be reported if: .INDENT 7.0 .IP \(bu 2 \fBPathloss\fP and \fBRSSI\fP are both empty. .IP \(bu 2 only \fBPathloss\fP param is set, device advertise TX power, and computed pathloss is less than Pathloss param. .IP \(bu 2 only \fBRSSI\fP param is set, and received RSSI is higher than RSSI param. .UNINDENT .TP .B int16 RSSI RSSI threshold value. .sp PropertiesChanged signals will be emitted for already existing Device objects, with updated RSSI value. If one or more discovery filters have been set, the RSSI delta\-threshold, that is imposed by StartDiscovery by default, will not be applied. .TP .B uint16 Pathloss Pathloss threshold value. .sp PropertiesChanged signals will be emitted for already existing Device objects, with updated Pathloss value. .TP .B string Transport (Default \(dqauto\(dq) Transport parameter determines the type of scan. .sp Possible values: .INDENT 7.0 .TP .B \(dqauto\(dq Interleaved scan, use LE, BREDR, or both, depending on what\(aqs currently enabled. .TP .B \(dqbredr\(dq BR/EDR inquiry only. .TP .B \(dqle\(dq LE scan only. .UNINDENT .TP .B bool DuplicateData (Default false) Disables duplicate detection of advertisement data. .sp When enabled PropertiesChanged signals will be generated for either ManufacturerData and ServiceData every time they are discovered. .TP .B bool Discoverable (Default false) Make adapter discoverable while discovering, if the adapter is already discoverable setting this filter won\(aqt do anything. .TP .B string Pattern (Default none) Discover devices where the pattern matches either the prefix of the address or device name which is convenient way to limited the number of device objects created during a discovery. .sp When set disregards device discoverable flags. .sp Note: The pattern matching is ignored if there are other client that don\(aqt set any pattern as it work as a logical OR, also setting empty string \(dq\(dq pattern will match any device found. .TP .B bool AutoConnect (Default false) Connect to discovered devices automatically if a Pattern has been set and it matches the device address or name and it is connectable. .UNINDENT .sp When discovery filter is set, Device objects will be created as new devices with matching criteria are discovered regardless of they are connectable or discoverable which enables listening to non\-connectable and non\-discoverable devices. .sp When multiple clients call SetDiscoveryFilter, their filters are internally merged, and notifications about new devices are sent to all clients. Therefore, each client must check that device updates actually match its filter. .sp When SetDiscoveryFilter is called multiple times by the same client, last filter passed will be active for given client. .sp SetDiscoveryFilter can be called before StartDiscovery. It is useful when client will create first discovery session, to ensure that proper scan will be started right after call to StartDiscovery. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.NotReady .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl [scan] > uuids [all/uuid1 uuid2 ...] .TP .B bluetoothctl [scan] > rssi [rssi] .TP .B bluetoothctl [scan] > pathloss [pathloss] .TP .B bluetoothctl [scan] > transport [auto/bredr/le] .TP .B bluetoothctl [scan] > duplicate\-data [on/off] .TP .B bluetoothctl [scan] > discoverable [on/off] .TP .B bluetoothctl [scan] > pattern [value] .TP .B bluetoothctl [scan] > clear [filter] .UNINDENT .SS array{string} GetDiscoveryFilters() .sp Returns available filters that can be given to \fBSetDiscoveryFilter\fP\&. .sp Possible errors: None .SS object ConnectDevice(dict properties) [experimental] .sp Connects to device without need of performing General Discovery. .sp Connection mechanism is similar to Connect method on \fBorg.bluez.Device1(5)\fP interface with exception that this method returns success when physical connection is established and you can specify bearer to connect with parameter. .sp After this method returns, services discovery will continue and any supported profile will be connected. There is no need for calling Connect on Device1 after this call. If connection was successful this method returns object path to created device object or device that already exist. .sp Possible properties values: .INDENT 0.0 .TP .B string Address (Mandatory) The Bluetooth device address of the remote device. .TP .B string AddressType (Default \(dqBR/EDR\(dq) The Bluetooth device Address Type. This is address type that should be used for initial connection. .sp Possible values: .INDENT 7.0 .TP .B \(dqpublic\(dq Public address .TP .B \(dqrandom\(dq Random address .UNINDENT .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.AlreadyExists .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.NotReady .TP .B org.bluez.Error.Failed .UNINDENT .SS Properties .SS string Address [readonly] .sp The Bluetooth device address. .SS string AddressType [readonly] .sp The Bluetooth Address Type. For dual\-mode and BR/EDR only adapter this defaults to \(dqpublic\(dq. Single mode LE adapters may have either value. With privacy enabled this contains type of Identity Address and not type of address used for connection. .sp Possible values: .INDENT 0.0 .TP .B \(dqpublic\(dq Public address. .TP .B \(dqrandom\(dq Random address. .UNINDENT .SS string Name [readonly] .sp The Bluetooth system name (pretty hostname). .sp This property is either a static system default or controlled by an external daemon providing access to the pretty hostname configuration. .SS string Alias [readwrite] .sp The Bluetooth friendly name. This value can be changed. .sp In case no alias is set, it will return the system provided name. Setting an empty string as alias will convert it back to the system provided name. .sp When resetting the alias with an empty string, the property will default back to system name. .sp On a well configured system, this property never needs to be changed since it defaults to the system name and provides the pretty hostname. .sp Only if the local name needs to be different from the pretty hostname, this property should be used as last resort. .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > system\-alias .TP .B bluetoothctl > reset\-alias .UNINDENT .SS uint32 Class [readonly] .sp The Bluetooth class of device. .sp This property represents the value that is either automatically configured by DMI/ACPI information or provided as static configuration. .SS boolean Connectable [readwrite] .sp Set an adapter to connectable or non\-connectable. This is a global setting and should only be used by the settings application. .sp Setting this property to false will set the Discoverable property of the adapter to false as well, which will not be reverted if Connectable is set back to true. .sp If required, the application will need to manually set Discoverable to true. .sp Note that this property only affects incoming connections. .SS boolean Powered [readwrite] .sp Switch an adapter on or off. This will also set the appropriate connectable state of the controller. .sp The value of this property is not persistent. After restart or unplugging of the adapter it will reset back to false. .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > power .UNINDENT .SS string PowerState [readonly, experimental] .sp The power state of an adapter. .sp The power state will show whether the adapter is turning off, or turning on, as well as being on or off. .sp Possible values: .INDENT 0.0 .TP .B \(dqon\(dq Powered on. .TP .B \(dqoff\(dq Powered off .TP .B \(dqoff\-enabling\(dq Transitioning from \(dqoff\(dq to \(dqon\(dq. .TP .B \(dqon\-disabling\(dq Transitioning from \(dqon\(dq to \(dqoff\(dq. .TP .B \(dqoff\-blocked\(dq Blocked by rfkill. .UNINDENT .SS boolean Discoverable [readwrite] (Default: false) .sp Switch an adapter to discoverable or non\-discoverable to either make it visible or hide it. This is a global setting and should only be used by the settings application. .sp If the DiscoverableTimeout is set to a non\-zero value then the system will set this value back to false after the timer expired. .sp In case the adapter is switched off, setting this value will fail. .sp When changing the Powered property the new state of this property will be updated via a PropertiesChanged signal. .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > discoverable .UNINDENT .SS boolean Pairable [readwrite] (Default: true) .sp Switch an adapter to pairable or non\-pairable. This is a global setting and should only be used by the settings application. .sp Note that this property only affects incoming pairing requests. .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > pairable .UNINDENT .SS uint32 PairableTimeout [readwrite] (Default: 0) .sp The pairable timeout in seconds. A value of zero means that the timeout is disabled and it will stay in pairable mode forever. .SS uint32 DiscoverableTimeout [readwrite] (Default: 180) .sp The discoverable timeout in seconds. A value of zero means that the timeout is disabled and it will stay in discoverable/limited mode forever. .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > discoverable\-timeout [value] .UNINDENT .SS boolean Discovering [readonly] .sp Indicates that a device discovery procedure is active. .SS array{string} UUIDs [readonly] .sp List of 128\-bit UUIDs that represents the available local services. .SS string Modalias [readonly, optional] .sp Local Device ID information in modalias format used by the kernel and udev. .SS array{string} Roles [readonly] .sp List of supported roles. .sp Possible values: .INDENT 0.0 .TP .B \(dqcentral\(dq Supports the central role. .TP .B \(dqperipheral\(dq Supports the peripheral role. .TP .B \(dqcentral\-peripheral\(dq Supports both roles concurrently. .UNINDENT .SS array{string} ExperimentalFeatures [readonly, optional] .sp List of 128\-bit UUIDs that represents the experimental features currently enabled. .SS uint16 Manufacturer [readonly] .sp The manufacturer of the device, as a uint16 company identifier defined by the Core Bluetooth Specification. .SS byte Version [readonly] .sp The Bluetooth version supported by the device, as a core version code defined by the Core Bluetooth Specification. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.obex.Image.50000644000000000000000000000005015221755411016375 xustar0020 atime=1783094025 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.Image.50000644000000000000000000000665015221755411016065 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.obex.Image" "5" "August 2024" "BlueZ" "Linux System Administration" .SH Name org.bluez.obex.Image \- BlueZ D-Bus OBEX Image API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez.obex .TP .B Interface org.bluez.obex.Image1 [experimental] .TP .B Object path [Session object path] .UNINDENT .SS Methods .SS object, dict Get(string targetfile, string handle, dict description) .sp Retrieves the image corresponding to the handle and the description, as one of the descriptions retrieved by GetImageProperties, and store it in a local file. .sp If the \(dqtransform\(dq property description exists it should be set to one of the value listed by GetImageProperties for this description. .sp If description is an empty dict, the native image will be retrieved. .INDENT 0.0 .INDENT 3.5 Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .UNINDENT .UNINDENT .SS array{dict} Properties(string handle) .sp Retrieves the image properties corresponding to the handle. .sp The first dict entry is mandatory and correspond to \(aqhandle\(aq and \(aqname\(aq of the image. .sp The second dict entry is mandatory and correspond to the native description (\(aqtype\(aq:\(aqnative\(aq) of the image. .sp The following dict entries are optional and correspond to variant descriptions of the image. If the \(aqtransform\(aq entry exists in the description, it lists the available possible image transformations and should be set to one of them before using the description as parameter to \fBGetImage\fP\&. .sp Possible property values: .INDENT 0.0 .TP .B string type Type of dict properties. Mandatory for each dict. .sp Possible values: .INDENT 7.0 .TP .B \(dqnative\(dq .TP .B \(dqvariant\(dq .UNINDENT .TP .B string encoding File encoding format. .sp Possible values: .INDENT 7.0 .TP .B \(dqBMP\(dq .TP .B \(dqGIF\(dq .TP .B \(dqJPEG\(dq .TP .B \(dqJPEG2000\(dq .TP .B \(dqPNG\(dq .TP .B \(dqWBMP\(dq .UNINDENT .TP .B string pixel File encoding format size of form \(dq*\(dq. .TP .B uint64 size File size. .TP .B uint64 maxsize File maximum size. .TP .B string transformation List of available transformations separated by space. .sp Possible values: .INDENT 7.0 .TP .B \(dqcrop\(dq .TP .B \(dqfill\(dq .TP .B \(dqstretch\(dq .UNINDENT .UNINDENT .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .SS object, dict GetThumbnail(string targetfile, string handle) .sp Retrieves the image thumbnail corresponding to the handle and store it in a local file. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.obex.Error.InvalidArguments .TP .B org.bluez.obex.Error.Failed .UNINDENT .\" End of generated man page. bluez-5.87/doc/PaxHeaders/l2ping.rst0000644000000000000000000000005015142177620014352 xustar0020 atime=1783094043 20 ctime=1783094113 bluez-5.87/doc/l2ping.rst0000644000000000000000000000361115142177620014034 0ustar00rootroot====== l2ping ====== ------------------------------------------ Send L2CAP echo request and receive answer ------------------------------------------ :Authors: - Maxim Krasnyansky - Marcel Holtmann - Nils Faerber - Adam Laurie . :Version: BlueZ :Copyright: Free use of this software is granted under the terms of the GNU Lesser General Public Licenses (LGPL). :Date: Jan 22, 2002 :Manual section: 1 :Manual group: Linux System Administration SYNOPSIS ======== **l2ping** [*OPTIONS*] *bd_addr* DESCRIPTION =========== **l2ping(1)** sends a L2CAP echo request to the Bluetooth MAC address bd_addr given in dotted hex notation. OPTIONS ======= -i The command is applied to device *hciX*, which must be the name of an installed Bluetooth device (X = 0, 1, 2, ...) If not specified, the command will be sent to the first available Bluetooth device. -s size The size of the data packets to be sent. -c count Send count number of packets then exit. -t timeout Wait timeout seconds for the response. -d delay Wait delay seconds between pings. -f Kind of flood ping. Use with care! It reduces the delay time between packets to 0. -r Reverse ping (gnip?). Send echo response instead of echo request. -v Verify response payload is identical to request payload. It is not required for remote stacks to return the request payload, but most stacks do (including Bluez). bd_addr The Bluetooth MAC address to be pinged in dotted hex notation like **01:02:03:ab:cd:ef** or **01:EF:cd:aB:02:03** RESOURCES ========= http://www.bluez.org REPORTING BUGS ============== linux-bluetooth@vger.kernel.org bluez-5.87/doc/PaxHeaders/org.bluez.Telephony.50000644000000000000000000000005015221755413016370 xustar0020 atime=1783094027 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Telephony.50000644000000000000000000001463715221755413016064 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.Telephony1" "5" "May 2025" "BlueZ" "Linux System Administration" .SH Name org.bluez.Telephony1 \- BlueZ D-Bus Telephony Audio Gateway API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.Telephony1 [experimental] .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/telephony# .TP .B Used by \fBbluetoothctl\-telephony(1)\fP .UNINDENT .SS Methods .SS object Dial(string uri) .sp The uri is comprised of the URI scheme followed by the Caller ID (this could be a telephone number or username), separated by a colon. .sp Examples of common URI schemes can be found in Internet Assigned Numbers Authority (IANA) URI Schemes: \% .sp This initiates a new outgoing call. Returns the object path to the newly created call. .sp For HFP the URI is \(dqtel:\(dq followed by the telephone number. .sp The telephone number must be a string containing the following characters: \fI[0\-9+*#,ABCD]{1,80}\fP The character set can contain numbers, \fI+\fP, \fI*\fP, \fI#\fP, \fI,\fP and the letters \fIA\fP to \fID\fP\&. Besides this sanity checking no further number validation is performed. It is assumed that the gateway and/or the network will perform further validation. .sp If telephone number is an empty string, it will try to call last dialed number. .sp NOTE: If an active call (single or multiparty) exists, then it is automatically put on hold if the dial procedure is successful. .sp Possible Errors: .INDENT 0.0 .TP .B org.bluez.Error.InvalidState .TP .B org.bluez.Error.InvalidArguments .TP .B org.bluez.Error.NotSupported .TP .B org.bluez.Error.Failed .UNINDENT .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > telephony.dial [telephony] .UNINDENT .SS void SwapCalls() .sp Swaps Active and Held calls. The effect of this is that all calls (0 or more including calls in a multi\-party conversation) that were Active are now Held, and all calls (0 or more) that were Held are now Active. .sp GSM specification does not allow calls to be swapped in the case where Held, Active and Waiting calls exist. Some modems implement this anyway, thus it is manufacturer specific whether this method will succeed in the case of Held, Active and Waiting calls. .sp Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed .SS void ReleaseAndAnswer() .sp Releases currently active call (0 or more) and answers the currently waiting call. Please note that if the current call is a multiparty call, then all parties in the multi\-party call will be released. .sp Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed .SS void ReleaseAndSwap() .sp Releases currently active call (0 or more) and activates any currently held calls. Please note that if the current call is a multiparty call, then all parties in the multi\-party call will be released. .sp Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed .SS void HoldAndAnswer() .sp Puts the current call (including multi\-party calls) on hold and answers the currently waiting call. Calling this function when a user already has a both Active and Held calls is invalid, since in GSM a user can have only a single Held call at a time. .sp Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed .SS void HangupAll() .sp Releases all calls except waiting calls. This includes multiparty calls. .sp Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed .sp Examples: .INDENT 0.0 .TP .B bluetoothctl > telephony.hangup\-all [telephony] .UNINDENT .SS void HangupActive() .sp Releases active calls. This includes multiparty active calls. .sp Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed .SS void HangupHeld() .sp Releases held calls except waiting calls. This includes multiparty held calls. .sp Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed .SS array{object} CreateMultiparty() .sp Joins active and held calls together into a multi\-party call. If one of the calls is already a multi\-party call, then the other call is added to the multiparty conversation. Returns the new list of calls participating in the multiparty call. .sp There can only be one subscriber controlled multi\-party call according to the GSM specification. .sp Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.Failed .SS void SendTones(string tones) .sp Sends the DTMF tones to the network. The tones have a fixed duration. Tones can be one of: \(aq0\(aq \- \(aq9\(aq, \(aq*\(aq, \(aq#\(aq, \(aqA\(aq, \(aqB\(aq, \(aqC\(aq, \(aqD\(aq. The last four are typically not used in normal circumstances. .sp Possible Errors: :org.bluez.Error.InvalidState :org.bluez.Error.InvalidArgs :org.bluez.Error.Failed .SS Properties .SS string UUID [readonly] .sp UUID of the profile which the Telephony Audio Gateway is for. .SS array{string} SupportedURISchemes [readonly] .sp Contains the list of supported URI schemes. .SS string State [readonly] .sp Contains the state of the current connection. .sp Possible values: .INDENT 0.0 .TP .B \(dqconnecting\(dq RFComm connection in progress .TP .B \(dqslc_connecting\(dq Service Level Connection in progress .TP .B \(dqconnected\(dq RFComm and Service Level Connection are connected .TP .B \(dqdisconnecting\(dq No further use of this object is allowed, it will be destroyed shortly .UNINDENT .SS boolean Service [readonly] .sp Network service availability. .SS byte Signal [readonly] .sp Network level signal from 0 to 5. .SS boolean Roaming [readonly] .sp Network roaming usage. .SS byte BattChg [readonly] .sp Battery level from 0 to 5. .SS string OperatorName [readonly, optional] .sp Operator name .SS boolean InbandRingtone [readonly] .sp In\-band Ringtone availability. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.obex.Client.rst0000644000000000000000000000005015063045254017236 xustar0020 atime=1783094004 20 ctime=1783094112 bluez-5.87/doc/org.bluez.obex.Client.rst0000644000000000000000000000256015063045254016722 0ustar00rootroot===================== org.bluez.obex.Client ===================== ----------------------------------------- BlueZ D-Bus OBEX Client API documentation ----------------------------------------- :Version: BlueZ :Date: October 2023 :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez.obex :Interface: org.bluez.obex.Client1 :Object path: /org/bluez/obex Methods ------- object CreateSession(string destination, dict args) ``````````````````````````````````````````````````` Connects to the destination address and then proceed to create an OBEX session object which implements **org.bluez.obex.Session(5)** interface. The last parameter is a dictionary to hold optional or type-specific parameters. Possible args values: :string Target: Type of session to be created. Possible values: :"ftp": :"map": :"opp": :"pbap": :"sync": :"bip-avrcp": :string Source: Local address to be used. :byte Channel: Channel to be used. :uint16 PSM: L2CAP PSM to be used. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.Failed: void RemoveSession(object session) `````````````````````````````````` Disconnects and removes session previously created by **CreateSession()** aborting any pending transfers. Possible errors: :org.bluez.obex.Error.InvalidArguments: :org.bluez.obex.Error.NotAuthorized: bluez-5.87/doc/PaxHeaders/org.bluez.Bearer.LE.50000644000000000000000000000005015221755266016126 xustar0020 atime=1783093942 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Bearer.LE.50000644000000000000000000001035015221755266015606 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.Bearer.LE" "5" "July 2025" "BlueZ" "Linux System Administration" .SH Name org.bluez.Bearer.LE \- BlueZ D-Bus Bearer LE API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.Bearer.LE1 .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} .UNINDENT .SS Methods .SS void Connect() [experimental] .sp Connects all LE profiles the remote device supports that can be connected to and have been flagged as auto\-connectable. If only subset of profiles is already connected it will try to connect currently disconnected ones. .sp If at least one profile was connected successfully this method will indicate success. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.NotReady .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.InProgress .TP .B org.bluez.Error.AlreadyConnected .UNINDENT .SS void Disconnect() [experimental] .sp Disconnects all connected profiles and then terminates low\-level ACL\-LE connection. .sp ACL\-LE connection will be terminated even if some profiles were not disconnected properly e.g. due to misbehaving device. .sp This method can be also used to cancel a preceding Connect call before a reply to it has been received. .sp For non\-trusted devices connected calling this method will disable incoming connections until Connect method is called again. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.NotConnected .UNINDENT .SS Signals .SS void Disconnected(string reason, string message) [experimental] .sp This signal is launched when a device is disconnected, with the reason of the disconnection. .sp This could be used by client application, depending on internal policy, to try to reconnect to the device in case of timeout or unknown disconnection, or to try to connect to another device. .sp Possible reasons: .INDENT 0.0 .TP .B org.bluez.Reason.Unknown .TP .B org.bluez.Reason.Timeout Connection timeout. .sp The link supervision timeout has expired for a connection or the synchronization timeout has expired for a broadcast. .TP .B org.bluez.Reason.Local Connection terminated by local host. .sp The local device terminated the connection, terminated synchronization with a broadcaster, or terminated broadcasting packets. .TP .B org.bluez.Reason.Remote Connection terminated by remote host. .sp This disconnection can be due to: .INDENT 7.0 .IP \(bu 2 the user on the remote device either terminated the connection or stopped broadcasting packets, .IP \(bu 2 the remote device terminated the connection because of low resources, .IP \(bu 2 the remote device terminated the connection because the device is about to power off. .UNINDENT .TP .B org.bluez.Reason.Authentication Connection terminated due to an authentication failure. .TP .B org.bluez.Reason.Suspend Connection terminated by local host for suspend. .UNINDENT .SS Properties .SS object Adapter [readonly, experimental] .sp The object path of the adapter the set belongs to. .SS boolean Paired [readonly, experimental] .sp Indicates if the remote device is paired to LE bearer. .sp Paired means the pairing process where devices exchange the information to establish an encrypted connection has been completed. .SS boolean Bonded [readonly, experimental] .sp Indicates if the remote device is bonded to LE bearer. .sp Bonded means the information exchanged on pairing process has been stored and will be persisted. .SS boolean Connected [readonly, experimental] .sp Indicates if the remote device is currently connected to LE bearer. .sp A PropertiesChanged signal indicate changes to this status. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.Thermometer.rst0000644000000000000000000000005015142177620017360 xustar0020 atime=1783094032 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Thermometer.rst0000644000000000000000000000244415142177620017045 0ustar00rootroot===================== org.bluez.Thermometer ===================== ------------------------------------------------ BlueZ D-Bus Health Thermometer API documentation ------------------------------------------------ :Version: BlueZ :Date: July 2011 :Author: Santiago Carot-Nemesio :Manual section: 5 :Manual group: Linux System Administration Interface ========= :Service: org.bluez :Interface: org.bluez.Thermometer1 :Object path: [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX Properties ---------- boolean Intermediate [readonly] ``````````````````````````````` True if the thermometer supports intermediate measurement notifications. uint16 Interval (optional) [readwrite] `````````````````````````````````````` The Measurement Interval defines the time (in seconds) between measurements. This interval is not related to the intermediate measurements and must be defined into a valid range. Setting it to zero means that no periodic measurements will be taken. uint16 Maximum (optional) [readonly] ```````````````````````````````````` Defines the maximum value allowed for the interval between periodic measurements. uint16 Minimum (optional) [readonly] ```````````````````````````````````` Defines the minimum value allowed for the interval between periodic measurements. bluez-5.87/doc/PaxHeaders/org.bluez.DeviceSet.50000644000000000000000000000005015221755272016277 xustar0020 atime=1783093946 20 ctime=1783094112 bluez-5.87/doc/org.bluez.DeviceSet.50000644000000000000000000000431115221755272015757 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.DeviceSet" "5" "September 2023" "BlueZ" "Linux System Administration" .SH Name org.bluez.DeviceSet \- BlueZ D-Bus DeviceSet API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.DeviceSet1 .TP .B Object path [variable prefix]/{hci0,hci1,...}/set_{sirk} .UNINDENT .SS Methods .SS void Connect() [experimental] .sp Connects all \fBdevices\fP members of the set, each member is connected in sequence as they were added/loaded following the same procedure as described in \fBDevice1.Connect\fP\&. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.NotReady .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.InProgress .TP .B org.bluez.Error.AlreadyConnected .UNINDENT .SS void Disconnect() [experimental] .sp Disconnects all \fBdevices\fP members of the set, each member is disconnected in sequence as they were connected following the same procedure as described in \fBDevice1.Disconnect\fP\&. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.NotConnected .UNINDENT .SS Properties .SS object Adapter [readonly, experimental] .sp The object path of the adapter the set belongs to. .SS bool AutoConnect [read\-write, experimental] .sp Indicates if the \fBdevices\fP members of the set shall be automatically connected once any of its members is connected. .SS array(object) Devices [ready\-only, experimental] .sp List of devices objects that are members of the set. .SS byte Size [read\-only, experimental] .sp Set members size. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/org.bluez.Bearer.BREDR.50000644000000000000000000000005015221755270016457 xustar0020 atime=1783093944 20 ctime=1783094112 bluez-5.87/doc/org.bluez.Bearer.BREDR.50000644000000000000000000001037015221755270016141 0ustar00rootroot.\" Man page generated from reStructuredText .\" by the Docutils 0.22.4 manpage writer. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "org.bluez.Bearer.BREDR" "5" "July 2025" "BlueZ" "Linux System Administration" .SH Name org.bluez.Bearer.BREDR \- BlueZ D-Bus Bearer BREDR API documentation .SH Interface .INDENT 0.0 .TP .B Service org.bluez .TP .B Interface org.bluez.Bearer.BREDR1 .TP .B Object path [variable prefix]/{hci0,hci1,...}/dev_{BDADDR} .UNINDENT .SS Methods .SS void Connect() [experimental] .sp Connects all BREDR profiles the remote device supports that can be connected to and have been flagged as auto\-connectable. If only subset of profiles is already connected it will try to connect currently disconnected ones. .sp If at least one profile was connected successfully this method will indicate success. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.NotReady .TP .B org.bluez.Error.Failed .TP .B org.bluez.Error.InProgress .TP .B org.bluez.Error.AlreadyConnected .UNINDENT .SS void Disconnect() [experimental] .sp Disconnects all connected profiles and then terminates low\-level ACL connection. .sp ACL connection will be terminated even if some profiles were not disconnected properly e.g. due to misbehaving device. .sp This method can be also used to cancel a preceding Connect call before a reply to it has been received. .sp For non\-trusted devices connected calling this method will disable incoming connections until Connect method is called again. .sp Possible errors: .INDENT 0.0 .TP .B org.bluez.Error.NotConnected .UNINDENT .SS Signals .SS void Disconnected(string reason, string message) [experimental] .sp This signal is launched when a device is disconnected, with the reason of the disconnection. .sp This could be used by client application, depending on internal policy, to try to reconnect to the device in case of timeout or unknown disconnection, or to try to connect to another device. .sp Possible reasons: .INDENT 0.0 .TP .B org.bluez.Reason.Unknown .TP .B org.bluez.Reason.Timeout Connection timeout. .sp The link supervision timeout has expired for a connection or the synchronization timeout has expired for a broadcast. .TP .B org.bluez.Reason.Local Connection terminated by local host. .sp The local device terminated the connection, terminated synchronization with a broadcaster, or terminated broadcasting packets. .TP .B org.bluez.Reason.Remote Connection terminated by remote host. .sp This disconnection can be due to: .INDENT 7.0 .IP \(bu 2 the user on the remote device either terminated the connection or stopped broadcasting packets, .IP \(bu 2 the remote device terminated the connection because of low resources, .IP \(bu 2 the remote device terminated the connection because the device is about to power off. .UNINDENT .TP .B org.bluez.Reason.Authentication Connection terminated due to an authentication failure. .TP .B org.bluez.Reason.Suspend Connection terminated by local host for suspend. .UNINDENT .SS Properties .SS object Adapter [readonly, experimental] .sp The object path of the adapter the set belongs to. .SS boolean Paired [readonly, experimental] .sp Indicates if the remote device is paired to BREDR bearer. .sp Paired means the pairing process where devices exchange the information to establish an encrypted connection has been completed. .SS boolean Bonded [readonly, experimental] .sp Indicates if the remote device is bonded to BREDR bearer. .sp Bonded means the information exchanged on pairing process has been stored and will be persisted. .SS boolean Connected [readonly, experimental] .sp Indicates if the remote device is currently connected to BREDR bearer. .sp A PropertiesChanged signal indicate changes to this status. .\" End of generated man page. bluez-5.87/doc/PaxHeaders/btmon-hci-init.rst0000644000000000000000000000005015200607060015767 xustar0020 atime=1783092683 20 ctime=1783094112 bluez-5.87/doc/btmon-hci-init.rst0000644000000000000000000003106615200607060015456 0ustar00rootroot.. This file is included by btmon.rst. HCI INITIALIZATION SEQUENCE ============================ Every btsnoop trace that captures controller startup begins with a dense block of HCI commands and events. This is the kernel's Bluetooth subsystem initializing the controller through a multi-stage sequence defined in ``net/bluetooth/hci_sync.c``. Understanding this sequence helps distinguish normal initialization traffic from application-level issues. Overview -------- The kernel initializes a Bluetooth controller in four stages after opening the HCI device. Each stage sends a batch of HCI commands and waits for their completion before proceeding to the next. The full call chain is:: hci_power_on_sync └─ hci_dev_open_sync └─ hci_dev_init_sync ├─ hci_dev_setup_sync (driver setup + quirks) └─ hci_init_sync ├─ Stage 1: Reset + identity ├─ Stage 2: Capabilities + buffer sizes ├─ Stage 3: Event masks + policy └─ Stage 4: Final configuration After all four stages complete, a post-init phase (``hci_powered_update_sync``) configures runtime parameters like SSP, advertising, and scan settings. For unconfigured devices (e.g. controllers that need firmware or a BD address programmed), only a minimal **Stage 0** runs to identify the hardware. Stage 0: Reset and Basic Identity (Unconfigured Only) ----------------------------------------------------- This stage runs only for unconfigured controllers that need setup before full initialization. **Commands sent:** .. list-table:: :header-rows: 1 :widths: 40 60 * - HCI Command - Purpose * - ``HCI_Reset`` - Reset the controller (skipped if ``RESET_ON_CLOSE`` quirk) * - ``HCI_Read_Local_Version_Information`` - Read hardware/firmware version * - ``HCI_Read_BD_ADDR`` - Read the controller's Bluetooth address Stage 1: Reset and Read Local Features --------------------------------------- Resets the controller and reads core identity and capability information. **Commands sent:** .. list-table:: :header-rows: 1 :widths: 40 60 * - HCI Command - Purpose * - ``HCI_Reset`` - Reset the controller * - ``HCI_Read_Local_Supported_Features`` - Read LMP feature bitmask (BR/EDR, LE, SSP, etc.) * - ``HCI_Read_Local_Version_Information`` - Read HCI version, LMP version, manufacturer * - ``HCI_Read_BD_ADDR`` - Read the public Bluetooth address Stage 2: Read Capabilities and Setup ------------------------------------- Reads detailed capabilities, enables core features, and reads buffer sizes. This stage has three phases: common commands, BR/EDR-specific commands, and LE-specific commands. Common Commands ~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 40 60 * - HCI Command - Purpose * - ``HCI_Read_Local_Supported_Commands`` - Read the supported command bitmask (HCI 1.2+) * - ``HCI_Write_Simple_Pairing_Mode`` (enable) - Enable SSP if supported and configured * - ``HCI_Write_Extended_Inquiry_Response`` (clear) - Clear EIR data when SSP is disabled * - ``HCI_Write_Inquiry_Mode`` - Set inquiry mode (RSSI or Extended, based on features) * - ``HCI_Read_Inquiry_Response_Transmit_Power_Level`` - Read inquiry TX power if supported * - ``HCI_Read_Local_Extended_Features`` (page 1) - Read extended feature page 1 (SSP host, LE host, etc.) * - ``HCI_Write_Authentication_Enable`` - Sync authentication state with ``LINK_SECURITY`` flag BR/EDR Commands (if BR/EDR capable) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 40 60 * - HCI Command - Purpose * - ``HCI_Read_Buffer_Size`` - Read ACL/SCO buffer sizes and count * - ``HCI_Read_Class_of_Device`` - Read current device class * - ``HCI_Read_Local_Name`` - Read the stored local name * - ``HCI_Read_Voice_Setting`` - Read SCO voice setting (if supported) * - ``HCI_Read_Number_of_Supported_IAC`` - Read number of supported inquiry access codes * - ``HCI_Read_Current_IAC_LAP`` - Read current IAC LAP values * - ``HCI_Set_Event_Filter`` (clear all) - Clear any stored event filters * - ``HCI_Write_Connection_Accept_Timeout`` - Set connection accept timeout (~20 seconds) * - ``HCI_Write_Synchronous_Flow_Control_Enable`` - Enable SCO flow control if supported LE Commands (if LE capable) ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 40 60 * - HCI Command - Purpose * - ``LE_Read_Local_Supported_Features`` - Read LE feature bitmask * - ``LE_Read_All_Local_Supported_Features`` - Read extended LE features (if supported) * - ``LE_Read_Buffer_Size`` [v2] or [v1] - Read LE ACL (and ISO) buffer sizes; v2 used when ISO capable * - ``LE_Read_Supported_States`` - Read the LE state combination table Stage 3: Event Masks, Link Policy, and Features ------------------------------------------------ Configures which events the controller should report, sets link policy, and reads extended feature pages. This is the longest stage. Event Masks and Link Policy ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 40 60 * - HCI Command - Purpose * - ``HCI_Set_Event_Mask`` - Configure the main event mask based on controller capabilities * - ``HCI_Read_Stored_Link_Key`` - Read all stored link keys * - ``HCI_Write_Default_Link_Policy_Settings`` - Enable role switch, hold, sniff, park based on LMP features * - ``HCI_Read_Page_Scan_Activity`` - Read page scan interval and window * - ``HCI_Read_Default_Erroneous_Data_Reporting`` - Read error data reporting state (for wideband speech) * - ``HCI_Read_Page_Scan_Type`` - Read page scan type (standard or interlaced) * - ``HCI_Read_Local_Extended_Features`` (pages 2..N) - Read all remaining extended feature pages **Event mask details:** For dual-mode controllers the kernel enables events for inquiry results (RSSI and extended), SSP (IO capability, user confirmation, passkey), synchronous connections, sniff subrating, encryption refresh, link supervision, and LE meta-events. For LE-only controllers a minimal mask covers only command completion, hardware errors, disconnection, and encryption changes. LE Event Mask and Capabilities ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 40 60 * - HCI Command - Purpose * - ``LE_Set_Event_Mask`` - Configure which LE sub-events are reported * - ``LE_Read_Advertising_Channel_Tx_Power`` - Read advertising TX power (legacy advertising only) * - ``LE_Read_Transmit_Power`` - Read min/max transmit power range * - ``LE_Read_Accept_List_Size`` - Read filter accept list capacity * - ``LE_Clear_Accept_List`` - Clear the filter accept list * - ``LE_Read_Resolving_List_Size`` - Read resolving list capacity (LL Privacy) * - ``LE_Clear_Resolving_List`` - Clear the resolving list * - ``LE_Set_Resolvable_Private_Address_Timeout`` - Set RPA rotation timeout * - ``LE_Read_Maximum_Data_Length`` - Read max TX/RX octets and time (Data Length Extension) * - ``LE_Read_Suggested_Default_Data_Length`` - Read current default data length * - ``LE_Read_Number_of_Supported_Advertising_Sets`` - Read extended advertising set capacity * - ``HCI_Write_LE_Host_Supported`` - Notify controller of host LE support (dual-mode only) * - ``LE_Set_Host_Feature`` - Enable CIS Central (bit 32) and/or Channel Sounding (bit 47) **LE event mask details:** The kernel enables LE sub-events based on features: connection complete (enhanced if available), advertising reports (extended if available), long term key request, connection parameter request, data length change, PHY update, channel selection algorithm, periodic advertising events, CIS established/request (if CIS capable), BIG create/sync/info (if BIS capable), and channel sounding events (if CS capable). Stage 4: Final Configuration ----------------------------- Performs final setup: deletes stale keys, sets event mask page 2, reads codec information, enables Secure Connections, and configures LE data length and PHY defaults. Keys, Codecs, and Secure Connections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 40 60 * - HCI Command - Purpose * - ``HCI_Delete_Stored_Link_Key`` (all) - Delete all stored link keys from controller * - ``HCI_Set_Event_Mask_Page_2`` - Enable page 2 events (authenticated payload timeout, etc.) * - ``HCI_Read_Local_Supported_Codecs`` [v2] or [v1] - Read supported codec IDs; v2 includes transport type info * - ``HCI_Read_Local_Pairing_Options`` - Read default pairing options (max encryption key size) * - ``HCI_Get_MWS_Transport_Layer_Configuration`` - Read MWS coexistence config if supported * - ``HCI_Read_Synchronization_Train_Parameters`` - Read sync train params (Connectionless Peripheral Broadcast) * - ``HCI_Write_Secure_Connections_Support`` (enable) - Enable Secure Connections if SSP active * - ``HCI_Write_Default_Erroneous_Data_Reporting`` - Enable/disable based on wideband speech setting LE Data Length and PHY Defaults ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 40 60 * - HCI Command - Purpose * - ``LE_Write_Suggested_Default_Data_Length`` - Set default TX octets/time for new connections * - ``LE_Set_Default_PHY`` - Set preferred PHY (1M always; 2M and Coded if supported) Post-Initialization ------------------- After the four stages complete, ``hci_powered_update_sync`` runs to apply runtime configuration: .. list-table:: :header-rows: 1 :widths: 40 60 * - Action - Purpose * - ``HCI_Write_Simple_Pairing_Mode`` - Re-enable SSP + Secure Connections if configured * - ``HCI_Write_LE_Host_Supported`` - Sync LE host support state * - LE advertising setup - Configure advertising parameters and data * - ``HCI_Write_Authentication_Enable`` - Sync authentication enable state * - Scan/class/name/EIR updates - Configure page scan, device class, local name, EIR data * - ``LE_Set_Random_Address`` - Set static random address if no public address Reading the Init Sequence in a Trace ------------------------------------- When examining a btsnoop trace, the initialization block is the first thing after the controller is opened. A typical dual-mode controller trace starts with:: < HCI Command: Reset > HCI Event: Command Complete (Reset) < HCI Command: Read Local Supported Features > HCI Event: Command Complete (Read Local Supported Features) < HCI Command: Read Local Version Information > HCI Event: Command Complete (Read Local Version Information) < HCI Command: Read BD ADDR > HCI Event: Command Complete (Read BD ADDR) ... [Stage 2-4 commands follow] **Key things to look for:** - **Missing commands**: If expected commands are absent, the controller may not support the corresponding feature. For example, no ``LE_Read_Buffer_Size`` means the controller is BR/EDR only. - **Command failures**: A ``Status`` other than ``0x00`` in a Command Complete event during init usually indicates a broken controller or unsupported feature. The kernel handles most gracefully, but persistent errors may prevent the adapter from functioning. - **Buffer sizes**: The values returned by ``Read_Buffer_Size`` and ``LE_Read_Buffer_Size`` determine how many in-flight packets the controller can hold. Small buffer counts can cause throughput issues. - **Feature bits**: The ``Read_Local_Supported_Features`` response reveals what the controller supports (LE, SSP, eSCO, etc.). Cross reference with the commands that follow — the kernel only sends commands for features the controller reports supporting. - **Event mask**: The ``Set_Event_Mask`` command shows exactly which events the host wants to receive. If an expected event never appears in the trace, check whether it was enabled in the mask. - **LE-only controllers**: These skip all BR/EDR commands (``Read_Buffer_Size``, ``Read_Local_Name``, link policy, etc.) and use a minimal event mask. The trace will be noticeably shorter. - **Vendor commands**: Some controllers (Intel, Broadcom, Qualcomm, Realtek, MediaTek) insert vendor-specific HCI commands between stages for firmware download, configuration, or patch application. These appear as opcode groups ``0x3F`` (vendor) and are driver-specific. bluez-5.87/PaxHeaders/obexd0000644000000000000000000000005015221755522012705 xustar0020 atime=1783094113 20 ctime=1783094098 bluez-5.87/obexd/0000755000000000000000000000000015221755522012443 5ustar00rootrootbluez-5.87/obexd/PaxHeaders/src0000644000000000000000000000005015221755537013502 xustar0020 atime=1783094113 20 ctime=1783094111 bluez-5.87/obexd/src/0000755000000000000000000000000015221755537013240 5ustar00rootrootbluez-5.87/obexd/src/PaxHeaders/org.bluez.obex.service.in0000644000000000000000000000005014572354773020416 xustar0020 atime=1783092767 20 ctime=1783094098 bluez-5.87/obexd/src/org.bluez.obex.service.in0000644000000000000000000000015214572354773020075 0ustar00rootroot[D-BUS Service] Name=org.bluez.obex Exec=@PKGLIBEXECDIR@/obexd SystemdService=dbus-org.bluez.obex.service bluez-5.87/obexd/src/PaxHeaders/obex.c0000644000000000000000000000005014711225434014647 xustar0020 atime=1783093879 20 ctime=1783094104 bluez-5.87/obexd/src/obex.c0000644000000000000000000005347214711225434014343 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2007-2010 Nokia Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gobex/gobex.h" #include "btio/btio.h" #include "obexd.h" #include "log.h" #include "obex.h" #include "obex-priv.h" #include "server.h" #include "manager.h" #include "mimetype.h" #include "service.h" #include "transport.h" #include "src/shared/util.h" typedef struct { uint8_t version; uint8_t flags; uint16_t mtu; } __attribute__ ((packed)) obex_connect_hdr_t; struct auth_header { uint8_t tag; uint8_t len; uint8_t val[0]; } __attribute__ ((packed)); /* Possible commands */ static const struct { int cmd; const char *name; } obex_command[] = { { G_OBEX_OP_CONNECT, "CONNECT" }, { G_OBEX_OP_DISCONNECT, "DISCONNECT" }, { G_OBEX_OP_PUT, "PUT" }, { G_OBEX_OP_GET, "GET" }, { G_OBEX_OP_SETPATH, "SETPATH" }, { G_OBEX_OP_SESSION, "SESSION" }, { G_OBEX_OP_ABORT, "ABORT" }, { G_OBEX_OP_ACTION, "ACTION" }, { 0xFF, NULL }, }; static gboolean handle_async_io(void *object, int flags, int err, void *user_data); static void print_event(int cmd, uint8_t rsp) { const char *cmdstr = NULL; int i; static int lastcmd; if (cmd < 0) cmd = lastcmd; else lastcmd = cmd; for (i = 0; obex_command[i].cmd != 0xFF; i++) { if (obex_command[i].cmd != cmd) continue; cmdstr = obex_command[i].name; } obex_debug("%s(0x%x), %s(0x%x)", cmdstr, cmd, g_obex_strerror(rsp), rsp); } static void os_set_response(struct obex_session *os, int err) { uint8_t rsp; rsp = g_obex_errno_to_rsp(err); print_event(-1, rsp); g_obex_send_rsp(os->obex, rsp, NULL, G_OBEX_HDR_INVALID); } static void os_session_mark_aborted(struct obex_session *os) { /* the session was already cancelled/aborted or size in unknown */ if (os->aborted || os->size == OBJECT_SIZE_UNKNOWN) return; os->aborted = (os->size != os->offset); } static void os_reset_session(struct obex_session *os) { os_session_mark_aborted(os); if (os->object) { obex_object_reset_io_watch(os->object); os->driver->close(os->object); if (os->aborted && os->cmd == G_OBEX_OP_PUT && os->path && os->driver->remove) os->driver->remove(os->path); } if (os->service && os->service->reset) os->service->reset(os, os->service_data); if (os->name) { g_free(os->name); os->name = NULL; } if (os->type) { g_free(os->type); os->type = NULL; } if (os->buf) { g_free(os->buf); os->buf = NULL; } if (os->path) { g_free(os->path); os->path = NULL; } if (os->apparam) { free(os->apparam); os->apparam = NULL; os->apparam_len = 0; } if (os->get_rsp > 0) { g_obex_remove_request_function(os->obex, os->get_rsp); os->get_rsp = 0; } os->object = NULL; os->driver = NULL; os->aborted = FALSE; os->pending = 0; os->offset = 0; os->size = OBJECT_SIZE_DELETE; os->headers_sent = FALSE; os->checked = FALSE; } static void obex_session_free(struct obex_session *os) { if (os->io) { g_io_channel_shutdown(os->io, TRUE, NULL); g_io_channel_unref(os->io); } if (os->obex) g_obex_unref(os->obex); g_free(os->src); g_free(os->dst); g_free(os); } /* From Imendio's GnomeVFS OBEX module (om-utils.c) */ static time_t parse_iso8610(const char *val, int size) { time_t time, tz_offset = 0; struct tm tm; char *date; char tz; int nr; memset(&tm, 0, sizeof(tm)); /* According to spec the time doesn't have to be null terminated */ date = g_strndup(val, size); nr = sscanf(date, "%04u%02u%02uT%02u%02u%02u%c", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &tz); g_free(date); if (nr < 6) { /* Invalid time format */ return -1; } tm.tm_year -= 1900; /* Year since 1900 */ tm.tm_mon--; /* Months since January, values 0-11 */ tm.tm_isdst = -1; /* Daylight savings information not avail */ #if defined(HAVE_TM_GMTOFF) tz_offset = tm.tm_gmtoff; #elif defined(HAVE_TIMEZONE) tz_offset = -timezone; if (tm.tm_isdst > 0) tz_offset += 3600; #endif time = mktime(&tm); if (nr == 7) { /* * Date/Time was in localtime (to remote device) * already. Since we don't know anything about the * timezone on that one we won't try to apply UTC offset */ time += tz_offset; } return time; } static void parse_service(struct obex_session *os, GObexPacket *req) { GObexHeader *hdr; const guint8 *target = NULL, *who = NULL; gsize target_size = 0, who_size = 0; hdr = g_obex_packet_get_header(req, G_OBEX_HDR_WHO); if (hdr == NULL) goto target; g_obex_header_get_bytes(hdr, &who, &who_size); target: hdr = g_obex_packet_get_header(req, G_OBEX_HDR_TARGET); if (hdr == NULL) goto probe; g_obex_header_get_bytes(hdr, &target, &target_size); probe: os->service = obex_service_driver_find(os->server->drivers, target, target_size, who, who_size); } static void cmd_connect(GObex *obex, GObexPacket *req, void *user_data) { struct obex_session *os = user_data; GObexPacket *rsp; GObexHeader *hdr; int err; DBG(""); print_event(G_OBEX_OP_CONNECT, -1); parse_service(os, req); if (os->service == NULL || os->service->connect == NULL) { error("Connect attempt to a non-supported target"); os_set_response(os, -EPERM); return; } DBG("Selected driver: %s", os->service->name); os->service_data = os->service->connect(os, &err); if (err < 0) { os_set_response(os, err); return; } os->cmd = G_OBEX_OP_CONNECT; rsp = g_obex_packet_new(G_OBEX_RSP_SUCCESS, TRUE, G_OBEX_HDR_INVALID); if (os->service->target) { hdr = g_obex_header_new_bytes(G_OBEX_HDR_WHO, os->service->target, os->service->target_size); g_obex_packet_add_header(rsp, hdr); } g_obex_send(obex, rsp, NULL); print_event(-1, 0); } static void cmd_disconnect(GObex *obex, GObexPacket *req, void *user_data) { struct obex_session *os = user_data; DBG("session %p", os); print_event(G_OBEX_OP_DISCONNECT, -1); os->cmd = G_OBEX_OP_DISCONNECT; os_set_response(os, 0); } static ssize_t driver_write(struct obex_session *os) { ssize_t len = 0; while (os->pending > 0) { ssize_t w; w = os->driver->write(os->object, os->buf + len, os->pending); if (w < 0) { error("write(): %s (%zd)", strerror(-w), -w); if (w == -EINTR) continue; else if (w == -EINVAL) memmove(os->buf, os->buf + len, os->pending); return w; } len += w; os->offset += w; os->pending -= w; } DBG("%zd written", len); if (os->service->progress != NULL) os->service->progress(os, os->service_data); return len; } static gssize driver_read(struct obex_session *os, void *buf, gsize size) { gssize len; if (os->object == NULL) return -EIO; if (os->service->progress != NULL) os->service->progress(os, os->service_data); len = os->driver->read(os->object, buf, size); if (len < 0) { error("read(): %s (%zd)", strerror(-len), -len); if (len == -ENOSTR) return 0; if (len == -EAGAIN) obex_object_set_io_watch(os->object, handle_async_io, os); } os->offset += len; DBG("%zd read", len); return len; } static gssize send_data(void *buf, gsize size, gpointer user_data) { struct obex_session *os = user_data; DBG("name=%s type=%s file=%p size=%zu", os->name, os->type, os->object, size); if (os->aborted) return os->err < 0 ? os->err : -EPERM; return driver_read(os, buf, size); } static void transfer_complete(GObex *obex, GError *err, gpointer user_data) { struct obex_session *os = user_data; DBG(""); if (err != NULL) { error("transfer failed: %s\n", err->message); goto reset; } if (os->object && os->driver && os->driver->flush) { if (os->driver->flush(os->object) == -EAGAIN) { g_obex_suspend(os->obex); obex_object_set_io_watch(os->object, handle_async_io, os); return; } } reset: os_reset_session(os); } static int driver_get_headers(struct obex_session *os) { GObexPacket *rsp; gssize len; guint8 data[255]; guint8 id; GObexHeader *hdr; DBG("name=%s type=%s object=%p", os->name, os->type, os->object); if (os->aborted) return os->err < 0 ? os->err : -EPERM; if (os->object == NULL) return -EIO; if (os->headers_sent) return 0; rsp = g_obex_packet_new(G_OBEX_RSP_CONTINUE, TRUE, G_OBEX_HDR_INVALID); if (os->driver->get_next_header == NULL) goto done; while ((len = os->driver->get_next_header(os->object, &data, sizeof(data), &id))) { if (len < 0) { error("get_next_header(): %s (%zd)", strerror(-len), -len); g_obex_packet_free(rsp); if (len == -EAGAIN) return len; g_free(os->buf); os->buf = NULL; return len; } hdr = g_obex_header_new_bytes(id, data, len); g_obex_packet_add_header(rsp, hdr); } done: if (os->size != OBJECT_SIZE_UNKNOWN && os->size < UINT32_MAX) { hdr = g_obex_header_new_uint32(G_OBEX_HDR_LENGTH, os->size); g_obex_packet_add_header(rsp, hdr); } g_obex_get_rsp_pkt(os->obex, rsp, send_data, transfer_complete, os, NULL); os->headers_sent = TRUE; print_event(-1, G_OBEX_RSP_CONTINUE); return 0; } static gboolean handle_async_io(void *object, int flags, int err, void *user_data) { struct obex_session *os = user_data; if (err < 0) goto done; if (flags & G_IO_OUT) err = driver_write(os); if ((flags & G_IO_IN) && !os->headers_sent) err = driver_get_headers(os); if (err == -EAGAIN) return TRUE; done: if (err < 0) { os->err = err; os->aborted = TRUE; } g_obex_resume(os->obex); return FALSE; } static gboolean recv_data(const void *buf, gsize size, gpointer user_data) { struct obex_session *os = user_data; ssize_t ret; DBG("name=%s type=%s file=%p size=%zu", os->name, os->type, os->object, size); if (os->aborted) return FALSE; /* workaround: client didn't send the object length */ if (os->size == OBJECT_SIZE_DELETE) os->size = OBJECT_SIZE_UNKNOWN; os->buf = g_realloc(os->buf, os->pending + size); memcpy(os->buf + os->pending, buf, size); os->pending += size; /* only write if both object and driver are valid */ if (os->object == NULL || os->driver == NULL) { DBG("Stored %" PRIu64 " bytes into temporary buffer", os->pending); return TRUE; } ret = driver_write(os); if (ret >= 0) return TRUE; if (ret == -EAGAIN) { g_obex_suspend(os->obex); obex_object_set_io_watch(os->object, handle_async_io, os); return TRUE; } return FALSE; } static void parse_type(struct obex_session *os, GObexPacket *req) { GObexHeader *hdr; const guint8 *type; gsize len; g_free(os->type); os->type = NULL; hdr = g_obex_packet_get_header(req, G_OBEX_HDR_TYPE); if (hdr == NULL) goto probe; if (!g_obex_header_get_bytes(hdr, &type, &len)) goto probe; /* Ensure null termination */ if (type[len - 1] != '\0') goto probe; os->type = g_strndup((const char *) type, len); DBG("TYPE: %s", os->type); probe: os->driver = obex_mime_type_driver_find(os->service->target, os->service->target_size, os->type, os->service->who, os->service->who_size); } static void parse_name(struct obex_session *os, GObexPacket *req) { GObexHeader *hdr; const char *name; g_free(os->name); os->name = NULL; hdr = g_obex_packet_get_header(req, G_OBEX_HDR_NAME); if (hdr == NULL) return; if (!g_obex_header_get_unicode(hdr, &name)) return; os->name = g_strdup(name); DBG("NAME: %s", os->name); } static void parse_apparam(struct obex_session *os, GObexPacket *req) { GObexHeader *hdr; const guint8 *apparam; gsize len; hdr = g_obex_packet_get_header(req, G_OBEX_HDR_APPARAM); if (hdr == NULL) return; if (!g_obex_header_get_bytes(hdr, &apparam, &len)) return; os->apparam = util_memdup(apparam, len); os->apparam_len = len; DBG("APPARAM"); } static void cmd_get(GObex *obex, GObexPacket *req, gpointer user_data) { struct obex_session *os = user_data; int err; DBG("session %p", os); print_event(G_OBEX_OP_GET, -1); if (os->service == NULL) { os_set_response(os, -EPERM); return; } if (os->service->get == NULL) { os_set_response(os, -ENOSYS); return; } os->headers_sent = FALSE; if (os->type) { g_free(os->type); os->type = NULL; } parse_type(os, req); if (!os->driver) { error("No driver found"); os_set_response(os, -ENOSYS); return; } os->cmd = G_OBEX_OP_GET; parse_name(os, req); parse_apparam(os, req); err = os->service->get(os, os->service_data); if (err == 0) return; os_set_response(os, err); } static void cmd_setpath(GObex *obex, GObexPacket *req, gpointer user_data) { struct obex_session *os = user_data; int err; DBG(""); print_event(G_OBEX_OP_SETPATH, -1); if (os->service == NULL) { err = -EPERM; goto done; } if (os->service->setpath == NULL) { err = -ENOSYS; goto done; } os->cmd = G_OBEX_OP_SETPATH; parse_name(os, req); os->nonhdr = g_obex_packet_get_data(req, &os->nonhdr_len); err = os->service->setpath(os, os->service_data); done: os_set_response(os, err); } int obex_get_stream_start(struct obex_session *os, const char *filename) { int err; void *object; size_t size = OBJECT_SIZE_UNKNOWN; object = os->driver->open(filename, O_RDONLY, 0, os->service_data, &size, &err); if (object == NULL) { error("open(%s): %s (%d)", filename, strerror(-err), -err); return err; } os->object = object; os->offset = 0; os->size = size; err = driver_get_headers(os); if (err != -EAGAIN) return err; g_obex_suspend(os->obex); obex_object_set_io_watch(os->object, handle_async_io, os); return 0; } int obex_put_stream_start(struct obex_session *os, const char *filename) { int err; os->object = os->driver->open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600, os->service_data, os->size != OBJECT_SIZE_UNKNOWN ? (size_t *) &os->size : NULL, &err); if (os->object == NULL) { error("open(%s): %s (%d)", filename, strerror(-err), -err); return err; } os->path = g_strdup(filename); return 0; } static void parse_length(struct obex_session *os, GObexPacket *req) { GObexHeader *hdr; guint32 size; hdr = g_obex_packet_get_header(req, G_OBEX_HDR_LENGTH); if (hdr == NULL) return; if (!g_obex_header_get_uint32(hdr, &size)) return; os->size = size; DBG("LENGTH: %" PRIu64, os->size); } static void parse_time(struct obex_session *os, GObexPacket *req) { GObexHeader *hdr; const guint8 *time; gsize len; hdr = g_obex_packet_get_header(req, G_OBEX_HDR_TIME); if (hdr == NULL) return; if (!g_obex_header_get_bytes(hdr, &time, &len)) return; os->time = parse_iso8610((const char *) time, len); DBG("TIME: %s", ctime(&os->time)); } static gboolean check_put(GObex *obex, GObexPacket *req, void *user_data) { struct obex_session *os = user_data; int ret; if (os->service->chkput == NULL) goto done; ret = os->service->chkput(os, os->service_data); switch (ret) { case 0: break; case -EAGAIN: g_obex_suspend(os->obex); obex_object_set_io_watch(os->object, handle_async_io, os); return TRUE; default: os_set_response(os, ret); return FALSE; } if (os->size == OBJECT_SIZE_DELETE || os->size == OBJECT_SIZE_UNKNOWN) DBG("Got a PUT without a Length"); done: os->checked = TRUE; return TRUE; } static void cmd_put(GObex *obex, GObexPacket *req, gpointer user_data) { struct obex_session *os = user_data; int err; DBG(""); print_event(G_OBEX_OP_PUT, -1); if (os->service == NULL) { os_set_response(os, -EPERM); return; } /* OPP session don't require CONNECT, in which case just call connect * callback to register the transfer. */ if (!os->service_data && os->service->service == OBEX_OPP) { os->service_data = os->service->connect(os, &err); if (err < 0) { os_set_response(os, err); return; } } parse_type(os, req); if (os->driver == NULL) { error("No driver found"); os_set_response(os, -ENOSYS); return; } os->cmd = G_OBEX_OP_PUT; /* Set size to unknown if a body header exists */ if (g_obex_packet_get_body(req)) os->size = OBJECT_SIZE_UNKNOWN; parse_name(os, req); parse_length(os, req); parse_time(os, req); parse_apparam(os, req); if (!os->checked) { if (!check_put(obex, req, user_data)) return; } if (os->service->put == NULL) { os_set_response(os, -ENOSYS); return; } err = os->service->put(os, os->service_data); if (err == 0) { g_obex_put_rsp(obex, req, recv_data, transfer_complete, os, NULL, G_OBEX_HDR_INVALID); print_event(G_OBEX_OP_PUT, G_OBEX_RSP_CONTINUE); return; } os_set_response(os, err); } static void parse_destname(struct obex_session *os, GObexPacket *req) { GObexHeader *hdr; const char *destname; g_free(os->destname); os->destname = NULL; hdr = g_obex_packet_get_header(req, G_OBEX_HDR_DESTNAME); if (hdr == NULL) return; if (!g_obex_header_get_unicode(hdr, &destname)) return; os->destname = g_strdup(destname); DBG("DESTNAME: %s", os->destname); } static void parse_action(struct obex_session *os, GObexPacket *req) { GObexHeader *hdr; guint8 id; hdr = g_obex_packet_get_header(req, G_OBEX_HDR_ACTION); if (hdr == NULL) return; if (!g_obex_header_get_uint8(hdr, &id)) return; os->action_id = id; DBG("ACTION: 0x%02x", os->action_id); } static void cmd_action(GObex *obex, GObexPacket *req, gpointer user_data) { struct obex_session *os = user_data; int err; DBG(""); print_event(G_OBEX_OP_ACTION, -1); if (os->service == NULL) { err = -EPERM; goto done; } if (os->service->action == NULL) { err = -ENOSYS; goto done; } os->cmd = G_OBEX_OP_ACTION; parse_name(os, req); parse_destname(os, req); parse_action(os, req); os->driver = obex_mime_type_driver_find(os->service->target, os->service->target_size, NULL, os->service->who, os->service->who_size); if (os->driver == NULL) { err = -ENOSYS; goto done; } err = os->service->action(os, os->service_data); done: os_set_response(os, err); } static void cmd_abort(GObex *obex, GObexPacket *req, gpointer user_data) { struct obex_session *os = user_data; DBG(""); print_event(G_OBEX_OP_ABORT, -1); os_reset_session(os); os_set_response(os, 0); } static void obex_session_destroy(struct obex_session *os) { DBG(""); os_reset_session(os); if (os->service && os->service->disconnect) os->service->disconnect(os, os->service_data); obex_session_free(os); } static void disconn_func(GObex *obex, GError *err, gpointer user_data) { struct obex_session *os = user_data; error("disconnected: %s\n", err ? err->message : ""); obex_session_destroy(os); } int obex_session_start(GIOChannel *io, uint16_t tx_mtu, uint16_t rx_mtu, gboolean stream, struct obex_server *server) { struct obex_session *os; GObex *obex; GObexTransportType type; static uint32_t id = 0; DBG(""); os = g_new0(struct obex_session, 1); os->id = ++id; os->service = obex_service_driver_find(server->drivers, NULL, 0, NULL, 0); os->server = server; os->size = OBJECT_SIZE_DELETE; type = stream ? G_OBEX_TRANSPORT_STREAM : G_OBEX_TRANSPORT_PACKET; obex = g_obex_new(io, type, rx_mtu, tx_mtu); if (!obex) { obex_session_free(os); return -EIO; } g_obex_set_disconnect_function(obex, disconn_func, os); g_obex_add_request_function(obex, G_OBEX_OP_CONNECT, cmd_connect, os); g_obex_add_request_function(obex, G_OBEX_OP_DISCONNECT, cmd_disconnect, os); g_obex_add_request_function(obex, G_OBEX_OP_PUT, cmd_put, os); g_obex_add_request_function(obex, G_OBEX_OP_GET, cmd_get, os); g_obex_add_request_function(obex, G_OBEX_OP_SETPATH, cmd_setpath, os); g_obex_add_request_function(obex, G_OBEX_OP_ACTION, cmd_action, os); g_obex_add_request_function(obex, G_OBEX_OP_ABORT, cmd_abort, os); os->obex = obex; os->io = g_io_channel_ref(io); obex_getsockname(os, &os->src); obex_getpeername(os, &os->dst); return 0; } const char *obex_get_name(struct obex_session *os) { return os->name; } const char *obex_get_destname(struct obex_session *os) { return os->destname; } void obex_set_name(struct obex_session *os, const char *name) { g_free(os->name); os->name = g_strdup(name); DBG("Name changed: %s", os->name); } ssize_t obex_get_size(struct obex_session *os) { return os->size; } const char *obex_get_type(struct obex_session *os) { return os->type; } int obex_remove(struct obex_session *os, const char *path) { if (os->driver == NULL) return -ENOSYS; return os->driver->remove(path); } int obex_copy(struct obex_session *os, const char *source, const char *destination) { if (os->driver == NULL || os->driver->copy == NULL) return -ENOSYS; DBG("%s %s", source, destination); return os->driver->copy(source, destination); } int obex_move(struct obex_session *os, const char *source, const char *destination) { if (os->driver == NULL || os->driver->move == NULL) return -ENOSYS; DBG("%s %s", source, destination); return os->driver->move(source, destination); } uint8_t obex_get_action_id(struct obex_session *os) { return os->action_id; } ssize_t obex_get_apparam(struct obex_session *os, const uint8_t **buffer) { *buffer = os->apparam; return os->apparam_len; } ssize_t obex_get_non_header_data(struct obex_session *os, const uint8_t **data) { *data = os->nonhdr; return os->nonhdr_len; } int obex_getpeername(struct obex_session *os, char **name) { const struct obex_transport_driver *transport = os->server->transport; if (transport == NULL || transport->getpeername == NULL) return -ENOTSUP; return transport->getpeername(os->io, name); } int obex_getsockname(struct obex_session *os, char **name) { const struct obex_transport_driver *transport = os->server->transport; if (transport == NULL || transport->getsockname == NULL) return -ENOTSUP; return transport->getsockname(os->io, name); } int memncmp0(const void *a, size_t na, const void *b, size_t nb) { if (na != nb) return na - nb; if (a == NULL) return -(a != b); if (b == NULL) return a != b; return memcmp(a, b, na); } bluez-5.87/obexd/src/PaxHeaders/transport.c0000644000000000000000000000005014572354773015764 xustar0020 atime=1783093883 20 ctime=1783094104 bluez-5.87/obexd/src/transport.c0000644000000000000000000000277314572354773015456 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "obex.h" #include "server.h" #include "transport.h" #include "log.h" static GSList *drivers = NULL; static const struct obex_transport_driver * obex_transport_driver_find(const char *name) { const GSList *l; for (l = drivers; l; l = l->next) { const struct obex_transport_driver *driver = l->data; if (g_strcmp0(name, driver->name) == 0) return driver; } return NULL; } const GSList *obex_transport_driver_list(void) { return drivers; } int obex_transport_driver_register(const struct obex_transport_driver *driver) { if (!driver) { error("Invalid driver"); return -EINVAL; } if (obex_transport_driver_find(driver->name) != NULL) { error("Permission denied: transport %s already registered", driver->name); return -EPERM; } DBG("driver %p transport %s registered", driver, driver->name); drivers = g_slist_prepend(drivers, (gpointer)driver); return 0; } void obex_transport_driver_unregister(const struct obex_transport_driver *driver) { if (!g_slist_find(drivers, driver)) { error("Unable to unregister: No such driver %p", driver); return; } DBG("driver %p transport %s unregistered", driver, driver->name); drivers = g_slist_remove(drivers, driver); } bluez-5.87/obexd/src/PaxHeaders/obex.conf0000644000000000000000000000005014766002272015355 xustar0020 atime=1770587295 20 ctime=1783094111 bluez-5.87/obexd/src/obex.conf0000644000000000000000000000207014766002272015035 0ustar00rootroot bluez-5.87/obexd/src/PaxHeaders/mimetype.c0000644000000000000000000000005014572354773015561 xustar0020 atime=1783093881 20 ctime=1783094104 bluez-5.87/obexd/src/mimetype.c0000644000000000000000000000761214572354773015250 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include "log.h" #include "obex.h" #include "mimetype.h" static GSList *drivers = NULL; static GSList *watches = NULL; struct io_watch { void *object; obex_object_io_func func; void *user_data; }; void obex_object_set_io_flags(void *object, int flags, int err) { GSList *l; for (l = watches; l;) { struct io_watch *watch = l->data; l = l->next; if (watch->object != object) continue; if (watch->func(object, flags, err, watch->user_data) == TRUE) continue; if (g_slist_find(watches, watch) == NULL) continue; watches = g_slist_remove(watches, watch); g_free(watch); } } static struct io_watch *find_io_watch(void *object) { GSList *l; for (l = watches; l; l = l->next) { struct io_watch *watch = l->data; if (watch->object == object) return watch; } return NULL; } void obex_object_reset_io_watch(void *object) { struct io_watch *watch; watch = find_io_watch(object); if (watch == NULL) return; watches = g_slist_remove(watches, watch); g_free(watch); } int obex_object_set_io_watch(void *object, obex_object_io_func func, void *user_data) { struct io_watch *watch; watch = find_io_watch(object); if (watch) return -EPERM; watch = g_new0(struct io_watch, 1); watch->object = object; watch->func = func; watch->user_data = user_data; watches = g_slist_append(watches, watch); return 0; } static const struct obex_mime_type_driver *find_driver(const uint8_t *target, unsigned int target_size, const char *mimetype, const uint8_t *who, unsigned int who_size) { GSList *l; for (l = drivers; l; l = l->next) { const struct obex_mime_type_driver *driver = l->data; if (memncmp0(target, target_size, driver->target, driver->target_size)) continue; if (memncmp0(who, who_size, driver->who, driver->who_size)) continue; if (mimetype == NULL || driver->mimetype == NULL) { if (mimetype == driver->mimetype) return driver; else continue; } if (g_ascii_strcasecmp(mimetype, driver->mimetype) == 0) return driver; } return NULL; } const struct obex_mime_type_driver * obex_mime_type_driver_find(const uint8_t *target, unsigned int target_size, const char *mimetype, const uint8_t *who, unsigned int who_size) { const struct obex_mime_type_driver *driver; driver = find_driver(target, target_size, mimetype, who, who_size); if (driver == NULL) { if (who != NULL) { /* Fallback to non-who specific */ driver = find_driver(target, target_size, mimetype, NULL, 0); if (driver != NULL) return driver; } if (mimetype != NULL) /* Fallback to target default */ driver = find_driver(target, target_size, NULL, NULL, 0); if (driver == NULL) /* Fallback to general default */ driver = find_driver(NULL, 0, NULL, NULL, 0); } return driver; } int obex_mime_type_driver_register(const struct obex_mime_type_driver *driver) { if (!driver) { error("Invalid driver"); return -EINVAL; } if (find_driver(driver->target, driver->target_size, driver->mimetype, driver->who, driver->who_size)) { error("Permission denied: %s could not be registered", driver->mimetype); return -EPERM; } DBG("driver %p mimetype %s registered", driver, driver->mimetype); drivers = g_slist_append(drivers, (gpointer)driver); return 0; } void obex_mime_type_driver_unregister(const struct obex_mime_type_driver *driver) { if (!g_slist_find(drivers, driver)) { error("Unable to unregister: No such driver %p", driver); return; } DBG("driver %p mimetype %s unregistered", driver, driver->mimetype); drivers = g_slist_remove(drivers, driver); } bluez-5.87/obexd/src/PaxHeaders/service.h0000644000000000000000000000005014572354773015375 xustar0020 atime=1783093855 20 ctime=1783094104 bluez-5.87/obexd/src/service.h0000644000000000000000000000247514572354773015066 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ #define OBEX_PORT_RANDOM UINT16_MAX struct obex_service_driver { const char *name; uint16_t service; uint8_t channel; uint16_t port; gboolean secure; const uint8_t *target; unsigned int target_size; const uint8_t *who; unsigned int who_size; const char *record; void *(*connect) (struct obex_session *os, int *err); void (*progress) (struct obex_session *os, void *user_data); int (*get) (struct obex_session *os, void *user_data); int (*put) (struct obex_session *os, void *user_data); int (*chkput) (struct obex_session *os, void *user_data); int (*setpath) (struct obex_session *os, void *user_data); int (*action) (struct obex_session *os, void *user_data); void (*disconnect) (struct obex_session *os, void *user_data); void (*reset) (struct obex_session *os, void *user_data); }; int obex_service_driver_register(const struct obex_service_driver *driver); void obex_service_driver_unregister(const struct obex_service_driver *driver); GSList *obex_service_driver_list(uint16_t services); const struct obex_service_driver *obex_service_driver_find(GSList *drivers, const uint8_t *target, unsigned int target_size, const uint8_t *who, unsigned int who_size); bluez-5.87/obexd/src/PaxHeaders/log.h0000644000000000000000000000005014015011623014466 xustar0020 atime=1783093852 20 ctime=1783094104 bluez-5.87/obexd/src/log.h0000644000000000000000000000225014015011623014146 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ void info(const char *format, ...) __attribute__((format(printf, 1, 2))); void error(const char *format, ...) __attribute__((format(printf, 1, 2))); void obex_debug(const char *format, ...) __attribute__((format(printf, 1, 2))); void __obex_log_init(const char *debug, int detach); void __obex_log_cleanup(void); void __obex_log_enable_debug(void); struct obex_debug_desc { const char *name; const char *file; #define OBEX_DEBUG_FLAG_DEFAULT (0) #define OBEX_DEBUG_FLAG_PRINT (1 << 0) unsigned int flags; } __attribute__((aligned(8))); /** * DBG: * @fmt: format string * @arg...: list of arguments * * Simple macro around debug() which also include the function * name it is called in. */ #define DBG(fmt, arg...) do { \ static struct obex_debug_desc __obex_debug_desc \ __attribute__((used, section("__debug"), aligned(8))) = { \ .file = __FILE__, .flags = OBEX_DEBUG_FLAG_DEFAULT, \ }; \ if (__obex_debug_desc.flags & OBEX_DEBUG_FLAG_PRINT) \ obex_debug("%s:%s() " fmt, __FILE__, __func__ , ## arg); \ } while (0) bluez-5.87/obexd/src/PaxHeaders/plugin.h0000644000000000000000000000005014572354773015233 xustar0020 atime=1783093852 20 ctime=1783094104 bluez-5.87/obexd/src/plugin.h0000644000000000000000000000136414572354773014720 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ struct obex_plugin_desc { const char *name; int (*init) (void); void (*exit) (void); }; #ifdef OBEX_PLUGIN_BUILTIN #define OBEX_PLUGIN_DEFINE(name, init, exit) \ const struct obex_plugin_desc __obex_builtin_ ## name = { \ #name, init, exit \ }; #else #if EXTERNAL_PLUGINS #define OBEX_PLUGIN_DEFINE(name,init,exit) \ extern struct obex_plugin_desc obex_plugin_desc \ __attribute__ ((visibility("default"))); \ const struct obex_plugin_desc obex_plugin_desc = { \ #name, init, exit \ }; #else #error "Requested non built-in plugin, while external plugins is disabled" #endif #endif bluez-5.87/obexd/src/PaxHeaders/logind.h0000644000000000000000000000005015063045254015174 xustar0020 atime=1783093855 20 ctime=1783094104 bluez-5.87/obexd/src/logind.h0000644000000000000000000000143715063045254014662 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * Enable functionality only when the user is active * * Copyright (C) 2007-2010 Marcel Holtmann * * */ typedef int (*logind_init_cb)(gboolean at_register); typedef void (*logind_exit_cb)(gboolean at_unregister); #ifdef SYSTEMD int logind_register(logind_init_cb init_cb, logind_exit_cb exit_cb); void logind_unregister(logind_init_cb init_cb, logind_exit_cb exit_cb); int logind_set(gboolean enabled); #else static inline int logind_register(logind_init_cb init_cb, logind_exit_cb exit_cb) { return init_cb(TRUE); } static inline void logind_unregister(logind_init_cb init_cb, logind_exit_cb exit_cb) { return exit_cb(TRUE); } static inline int logind_set(gboolean enabled) { return 0; } #endif bluez-5.87/obexd/src/PaxHeaders/logind.c0000644000000000000000000000005015063045254015167 xustar0020 atime=1783093876 20 ctime=1783094104 bluez-5.87/obexd/src/logind.c0000644000000000000000000001226715063045254014660 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * Enable functionality only when the user is active * * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef SYSTEMD #include #include #include #include #include #include #include #include #include #include #include "obexd/src/log.h" #include "obexd/src/logind.h" static sd_login_monitor * monitor; static int uid; static gboolean active = FALSE; static gboolean monitoring_enabled = TRUE; static guint event_source; static guint timeout_source; struct callback_pair { logind_init_cb init_cb; logind_exit_cb exit_cb; }; GSList *callbacks; static void call_init_cb(gpointer data, gpointer user_data) { int res; res = ((struct callback_pair *)data)->init_cb(FALSE); if (res) *(int *)user_data = res; } static void call_exit_cb(gpointer data, gpointer user_data) { ((struct callback_pair *)data)->exit_cb(FALSE); } static int update(void) { char *state = NULL; gboolean state_is_active; int res; res = sd_login_monitor_flush(monitor); if (res < 0) return res; res = sd_uid_get_state(uid, &state); state_is_active = g_strcmp0(state, "active"); free(state); if (res < 0) return res; if (state_is_active) { if (!active) return 0; } else { res = sd_uid_get_seats(uid, 1, NULL); if (res < 0) return res; if (active == !!res) return 0; } active ^= TRUE; res = 0; g_slist_foreach(callbacks, active ? call_init_cb : call_exit_cb, &res); return res; } static gboolean timeout_handler(gpointer user_data); static int check_event(void) { uint64_t timeout_usec; int res; res = sd_login_monitor_flush(monitor); if (res < 0) return res; if (!monitoring_enabled) return 0; res = update(); if (res < 0) return res; res = sd_login_monitor_get_timeout(monitor, &timeout_usec); if (res < 0) return res; if (timeout_usec != (uint64_t)-1) { uint64_t time_usec; struct timespec ts; guint interval; res = clock_gettime(CLOCK_MONOTONIC, &ts); if (res < 0) return -errno; time_usec = (uint64_t) ts.tv_sec * 1000000 + ts.tv_nsec / 1000; if (time_usec > timeout_usec) return check_event(); interval = (timeout_usec - time_usec + 999) / 1000; timeout_source = g_timeout_add(interval, timeout_handler, NULL); } return 0; } static gboolean event_handler(GIOChannel *source, GIOCondition condition, gpointer data) { int res; if (timeout_source) { g_source_remove(timeout_source); timeout_source = 0; } res = check_event(); if (res) { error("%s: %s", __func__, strerror(-res)); return FALSE; } return TRUE; } static gboolean timeout_handler(gpointer user_data) { int res; res = check_event(); if (res) error("%s: %s", __func__, strerror(-res)); return FALSE; } static int logind_init(void) { GIOChannel *channel; int events; int fd; int res; monitor = NULL; DBG(""); if (!monitoring_enabled) return 0; uid = getuid(); res = sd_login_monitor_new("uid", &monitor); if (res < 0) { monitor = NULL; goto FAIL; } // Check this after creating the monitor, in case of race conditions: res = update(); if (res < 0) goto FAIL; events = res = sd_login_monitor_get_events(monitor); if (res < 0) goto FAIL; fd = res = sd_login_monitor_get_fd(monitor); if (res < 0) goto FAIL; channel = g_io_channel_unix_new(fd); g_io_channel_set_close_on_unref(channel, TRUE); g_io_channel_set_encoding(channel, NULL, NULL); g_io_channel_set_buffered(channel, FALSE); event_source = g_io_add_watch(channel, events, event_handler, NULL); g_io_channel_unref(channel); return check_event(); FAIL: sd_login_monitor_unref(monitor); monitoring_enabled = FALSE; active = TRUE; return res; } static void logind_exit(void) { if (event_source) { g_source_remove(event_source); event_source = 0; } if (timeout_source) { g_source_remove(timeout_source); timeout_source = 0; } sd_login_monitor_unref(monitor); } static gint find_cb(gconstpointer a, gconstpointer b) { return ((struct callback_pair *)a)->init_cb - (logind_init_cb)b; } int logind_register(logind_init_cb init_cb, logind_exit_cb exit_cb) { struct callback_pair *cbs; if (!monitoring_enabled) return init_cb(TRUE); if (callbacks == NULL) { int res; res = logind_init(); if (res) { error("logind_init(): %s - login detection disabled", strerror(-res)); return init_cb(TRUE); } } cbs = g_new(struct callback_pair, 1); cbs->init_cb = init_cb; cbs->exit_cb = exit_cb; callbacks = g_slist_prepend(callbacks, cbs); return active ? init_cb(TRUE) : 0; } void logind_unregister(logind_init_cb init_cb, logind_exit_cb exit_cb) { GSList *cb_node; if (!monitoring_enabled) return exit_cb(TRUE); if (active) exit_cb(TRUE); cb_node = g_slist_find_custom(callbacks, init_cb, find_cb); if (cb_node != NULL) callbacks = g_slist_delete_link(callbacks, cb_node); if (callbacks == NULL) logind_exit(); } int logind_set(gboolean enabled) { int res = 0; if (monitoring_enabled == enabled) return 0; monitoring_enabled = enabled; if (enabled) { active = FALSE; return update(); } active = TRUE; g_slist_foreach(callbacks, call_exit_cb, &res); return res; } #endif bluez-5.87/obexd/src/PaxHeaders/map_ap.h0000644000000000000000000000005014015011623015142 xustar0020 atime=1783093867 20 ctime=1783094104 bluez-5.87/obexd/src/map_ap.h0000644000000000000000000000254414015011623014630 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2010-2011 Nokia Corporation * * */ /* List of OBEX application parameters tags as per MAP specification. */ enum map_ap_tag { MAP_AP_MAXLISTCOUNT = 0x01, /* uint16_t */ MAP_AP_STARTOFFSET = 0x02, /* uint16_t */ MAP_AP_FILTERMESSAGETYPE = 0x03, /* uint8_t */ MAP_AP_FILTERPERIODBEGIN = 0x04, /* char * */ MAP_AP_FILTERPERIODEND = 0x05, /* char * */ MAP_AP_FILTERREADSTATUS = 0x06, /* uint8_t */ MAP_AP_FILTERRECIPIENT = 0x07, /* char * */ MAP_AP_FILTERORIGINATOR = 0x08, /* char * */ MAP_AP_FILTERPRIORITY = 0x09, /* uint8_t */ MAP_AP_ATTACHMENT = 0x0A, /* uint8_t */ MAP_AP_TRANSPARENT = 0x0B, /* uint8_t */ MAP_AP_RETRY = 0x0C, /* uint8_t */ MAP_AP_NEWMESSAGE = 0x0D, /* uint8_t */ MAP_AP_NOTIFICATIONSTATUS = 0x0E, /* uint8_t */ MAP_AP_MASINSTANCEID = 0x0F, /* uint8_t */ MAP_AP_PARAMETERMASK = 0x10, /* uint32_t */ MAP_AP_FOLDERLISTINGSIZE = 0x11, /* uint16_t */ MAP_AP_MESSAGESLISTINGSIZE = 0x12, /* uint16_t */ MAP_AP_SUBJECTLENGTH = 0x13, /* uint8_t */ MAP_AP_CHARSET = 0x14, /* uint8_t */ MAP_AP_FRACTIONREQUEST = 0x15, /* uint8_t */ MAP_AP_FRACTIONDELIVER = 0x16, /* uint8_t */ MAP_AP_STATUSINDICATOR = 0x17, /* uint8_t */ MAP_AP_STATUSVALUE = 0x18, /* uint8_t */ MAP_AP_MSETIME = 0x19, /* char * */ }; bluez-5.87/obexd/src/PaxHeaders/server.c0000644000000000000000000000005014572354773015236 xustar0020 atime=1783093884 20 ctime=1783094104 bluez-5.87/obexd/src/server.c0000644000000000000000000000435414572354773014725 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2007-2010 Nokia Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "gobex/gobex.h" #include "log.h" #include "obex.h" #include "obex-priv.h" #include "server.h" #include "service.h" #include "transport.h" static GSList *servers = NULL; static void init_server(uint16_t service, const GSList *transports) { const GSList *l; for (l = transports; l; l = l->next) { const struct obex_transport_driver *transport = l->data; struct obex_server *server; int err; if (transport->service != 0 && (transport->service & service) == FALSE) continue; server = g_new0(struct obex_server, 1); server->transport = transport; server->drivers = obex_service_driver_list(service); server->transport_data = transport->start(server, &err); if (server->transport_data == NULL) { DBG("Unable to start %s transport: %s (%d)", transport->name, strerror(err), err); g_free(server); continue; } servers = g_slist_prepend(servers, server); } } int obex_server_init(void) { GSList *drivers; const GSList *transports; GSList *l; drivers = obex_service_driver_list(0); if (drivers == NULL) { DBG("No service driver registered"); return -EINVAL; } transports = obex_transport_driver_list(); if (transports == NULL) { DBG("No transport driver registered"); return -EINVAL; } for (l = drivers; l; l = l->next) { const struct obex_service_driver *driver = l->data; init_server(driver->service, transports); } return 0; } void obex_server_exit(void) { GSList *l; for (l = servers; l; l = l->next) { struct obex_server *server = l->data; server->transport->stop(server->transport_data); g_slist_free(server->drivers); g_free(server); } g_slist_free(servers); return; } int obex_server_new_connection(struct obex_server *server, GIOChannel *io, uint16_t tx_mtu, uint16_t rx_mtu, gboolean stream) { return obex_session_start(io, tx_mtu, rx_mtu, stream, server); } bluez-5.87/obexd/src/PaxHeaders/transport.h0000644000000000000000000000005014572354773015771 xustar0020 atime=1783093855 20 ctime=1783094104 bluez-5.87/obexd/src/transport.h0000644000000000000000000000115514572354773015454 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ struct obex_transport_driver { const char *name; uint16_t service; void *(*start) (struct obex_server *server, int *err); int (*getpeername) (GIOChannel *io, char **name); int (*getsockname) (GIOChannel *io, char **name); void (*stop) (void *data); }; int obex_transport_driver_register(const struct obex_transport_driver *driver); void obex_transport_driver_unregister(const struct obex_transport_driver *driver); const GSList *obex_transport_driver_list(void); bluez-5.87/obexd/src/PaxHeaders/obex.h0000644000000000000000000000005014015011623014642 xustar0020 atime=1783093855 20 ctime=1783094104 bluez-5.87/obexd/src/obex.h0000644000000000000000000000266014015011623014327 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2007-2010 Nokia Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ #include #define OBJECT_SIZE_UNKNOWN -1 #define OBJECT_SIZE_DELETE -2 #define TARGET_SIZE 16 struct obex_session; int obex_get_stream_start(struct obex_session *os, const char *filename); int obex_put_stream_start(struct obex_session *os, const char *filename); const char *obex_get_name(struct obex_session *os); const char *obex_get_destname(struct obex_session *os); void obex_set_name(struct obex_session *os, const char *name); ssize_t obex_get_size(struct obex_session *os); const char *obex_get_type(struct obex_session *os); int obex_remove(struct obex_session *os, const char *path); int obex_copy(struct obex_session *os, const char *source, const char *destination); int obex_move(struct obex_session *os, const char *source, const char *destination); uint8_t obex_get_action_id(struct obex_session *os); ssize_t obex_get_apparam(struct obex_session *os, const uint8_t **buffer); ssize_t obex_get_non_header_data(struct obex_session *os, const uint8_t **data); int obex_getpeername(struct obex_session *os, char **name); int obex_getsockname(struct obex_session *os, char **name); /* Just a thin wrapper around memcmp to deal with NULL values */ int memncmp0(const void *a, size_t na, const void *b, size_t nb); bluez-5.87/obexd/src/PaxHeaders/plugin.c0000644000000000000000000000005015063045254015211 xustar0020 atime=1783093874 20 ctime=1783094104 bluez-5.87/obexd/src/plugin.c0000644000000000000000000001016315063045254014673 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "obexd.h" #include "plugin.h" #include "log.h" /* * Plugins that are using libraries with threads and their own mainloop * will crash on exit. This is a bug inside these libraries, but there is * nothing much that can be done about it. One bad example is libebook. */ #define PLUGINFLAG (RTLD_NOW | RTLD_NODELETE) #define IS_ENABLED(x) (x) static GSList *plugins = NULL; struct obex_plugin { void *handle; const struct obex_plugin_desc *desc; }; static gboolean add_external_plugin(void *handle, const struct obex_plugin_desc *desc) { struct obex_plugin *plugin; if (desc->init == NULL) return FALSE; plugin = g_try_new0(struct obex_plugin, 1); if (plugin == NULL) return FALSE; plugin->handle = handle; plugin->desc = desc; if (desc->init() < 0) { g_free(plugin); return FALSE; } plugins = g_slist_append(plugins, plugin); DBG("Plugin %s loaded", desc->name); return TRUE; } static void add_plugin(const struct obex_plugin_desc *desc) { struct obex_plugin *plugin; plugin = g_try_new0(struct obex_plugin, 1); if (plugin == NULL) return; plugin->desc = desc; if (desc->init() < 0) { g_free(plugin); return; } plugins = g_slist_append(plugins, plugin); DBG("Plugin %s loaded", desc->name); } static gboolean check_plugin(const struct obex_plugin_desc *desc, char **patterns, char **excludes) { if (excludes) { for (; *excludes; excludes++) if (g_pattern_match_simple(*excludes, desc->name)) break; if (*excludes) { info("Excluding %s", desc->name); return FALSE; } } if (patterns) { for (; *patterns; patterns++) if (g_pattern_match_simple(*patterns, desc->name)) break; if (*patterns == NULL) { info("Ignoring %s", desc->name); return FALSE; } } return TRUE; } static void external_plugin_init(char **patterns, char **excludes) { GDir *dir; const char *file; info("Using external plugins is not officially supported.\n"); info("Consider upstreaming your plugins into the BlueZ project."); if (strlen(PLUGINDIR) == 0) return; DBG("Loading plugins %s", PLUGINDIR); dir = g_dir_open(PLUGINDIR, 0, NULL); if (!dir) { return; } while ((file = g_dir_read_name(dir)) != NULL) { const struct obex_plugin_desc *desc; void *handle; char *filename; if (g_str_has_prefix(file, "lib") == TRUE || g_str_has_suffix(file, ".so") == FALSE) continue; filename = g_build_filename(PLUGINDIR, file, NULL); handle = dlopen(filename, PLUGINFLAG); if (handle == NULL) { error("Can't load plugin %s: %s", filename, dlerror()); g_free(filename); continue; } g_free(filename); desc = dlsym(handle, "obex_plugin_desc"); if (desc == NULL) { error("Can't load plugin description: %s", dlerror()); dlclose(handle); continue; } if (check_plugin(desc, patterns, excludes) == FALSE) { dlclose(handle); continue; } if (add_external_plugin(handle, desc) == FALSE) dlclose(handle); } g_dir_close(dir); } #include "builtin.h" void plugin_init(const char *pattern, const char *exclude) { char **patterns = NULL; char **excludes = NULL; unsigned int i; if (pattern) patterns = g_strsplit_set(pattern, ":, ", -1); if (exclude) excludes = g_strsplit_set(exclude, ":, ", -1); DBG("Loading builtin plugins"); for (i = 0; __obex_builtin[i]; i++) { if (check_plugin(__obex_builtin[i], patterns, excludes) == FALSE) continue; add_plugin(__obex_builtin[i]); } if IS_ENABLED(EXTERNAL_PLUGINS) external_plugin_init(patterns, excludes); g_strfreev(patterns); g_strfreev(excludes); } void plugin_cleanup(void) { GSList *list; DBG("Cleanup plugins"); for (list = plugins; list; list = list->next) { struct obex_plugin *plugin = list->data; if (plugin->desc->exit) plugin->desc->exit(); if (plugin->handle != NULL) dlclose(plugin->handle); g_free(plugin); } g_slist_free(plugins); } bluez-5.87/obexd/src/PaxHeaders/obex-priv.h0000644000000000000000000000005014572354773015650 xustar0020 atime=1783093877 20 ctime=1783094104 bluez-5.87/obexd/src/obex-priv.h0000644000000000000000000000161414572354773015333 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2007-2010 Nokia Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ struct obex_session { GIOChannel *io; uint32_t id; uint8_t cmd; uint8_t action_id; char *src; char *dst; char *name; char *destname; char *type; char *path; time_t time; uint8_t *apparam; size_t apparam_len; const void *nonhdr; size_t nonhdr_len; guint get_rsp; uint8_t *buf; int64_t pending; int64_t offset; int64_t size; void *object; gboolean aborted; int err; const struct obex_service_driver *service; void *service_data; struct obex_server *server; gboolean checked; GObex *obex; const struct obex_mime_type_driver *driver; gboolean headers_sent; }; int obex_session_start(GIOChannel *io, uint16_t tx_mtu, uint16_t rx_mtu, gboolean stream, struct obex_server *server); bluez-5.87/obexd/src/PaxHeaders/main.c0000644000000000000000000000005015063045254014637 xustar0020 atime=1783093873 20 ctime=1783094104 bluez-5.87/obexd/src/main.c0000644000000000000000000001657315063045254014334 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "../client/manager.h" #include "log.h" #include "logind.h" #include "obexd.h" #include "server.h" #define DEFAULT_CAP_FILE CONFIGDIR "/capability.xml" static GMainLoop *main_loop = NULL; static DBusConnection *connection; static gboolean signal_handler(GIOChannel *channel, GIOCondition cond, gpointer user_data) { static unsigned int __terminated = 0; struct signalfd_siginfo si; ssize_t result; int fd; if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) return FALSE; fd = g_io_channel_unix_get_fd(channel); result = read(fd, &si, sizeof(si)); if (result != sizeof(si)) return FALSE; switch (si.ssi_signo) { case SIGINT: case SIGTERM: if (__terminated == 0) { info("Terminating"); g_main_loop_quit(main_loop); } __terminated = 1; break; case SIGUSR2: __obex_log_enable_debug(); break; } return TRUE; } static guint setup_signalfd(void) { GIOChannel *channel; guint source; sigset_t mask; int fd; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGUSR2); if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { perror("Failed to set signal mask"); return 0; } fd = signalfd(-1, &mask, 0); if (fd < 0) { perror("Failed to create signal descriptor"); return 0; } channel = g_io_channel_unix_new(fd); g_io_channel_set_close_on_unref(channel, TRUE); g_io_channel_set_encoding(channel, NULL, NULL); g_io_channel_set_buffered(channel, FALSE); source = g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, signal_handler, NULL); g_io_channel_unref(channel); return source; } static gboolean option_detach = TRUE; static char *option_debug = NULL; static char *option_root = NULL; static char *option_root_setup = NULL; static char *option_capability = NULL; static char *option_plugin = NULL; static char *option_noplugin = NULL; static gboolean option_autoaccept = FALSE; static gboolean option_symlinks = FALSE; static gboolean option_system_bus = FALSE; static gboolean parse_debug(const char *key, const char *value, gpointer user_data, GError **error) { if (value) option_debug = g_strdup(value); else option_debug = g_strdup("*"); return TRUE; } static const GOptionEntry options[] = { { "debug", 'd', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, parse_debug, "Enable debug information output", "DEBUG" }, { "plugin", 'p', 0, G_OPTION_ARG_STRING, &option_plugin, "Specify plugins to load", "NAME,..." }, { "noplugin", 'P', 0, G_OPTION_ARG_STRING, &option_noplugin, "Specify plugins not to load", "NAME,..." }, { "nodetach", 'n', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &option_detach, "Run with logging in foreground" }, { "root", 'r', 0, G_OPTION_ARG_STRING, &option_root, "Specify root folder location. Both absolute " "and relative can be used, but relative paths " "are assumed to be relative to user $HOME " "folder. Default $XDG_CACHE_HOME", "PATH" }, { "root-setup", 'S', 0, G_OPTION_ARG_STRING, &option_root_setup, "Root folder setup script", "SCRIPT" }, { "symlinks", 'l', 0, G_OPTION_ARG_NONE, &option_symlinks, "Allow symlinks leading outside of the root " "folder" }, { "capability", 'c', 0, G_OPTION_ARG_STRING, &option_capability, "Specify capability file, use '!' mark for " "scripts", "FILE" }, { "auto-accept", 'a', 0, G_OPTION_ARG_NONE, &option_autoaccept, "Automatically accept push requests" }, { "system-bus", 's', 0, G_OPTION_ARG_NONE, &option_system_bus, "Use System bus "}, { NULL }, }; gboolean obex_option_auto_accept(void) { return option_autoaccept; } const char *obex_option_root_folder(void) { return option_root; } gboolean obex_option_symlinks(void) { return option_symlinks; } const char *obex_option_capability(void) { return option_capability; } static gboolean is_dir(const char *dir) { struct stat st; if (stat(dir, &st) < 0) { error("stat(%s): %s (%d)", dir, strerror(errno), errno); return FALSE; } return S_ISDIR(st.st_mode); } static gboolean root_folder_setup(char *root, char *root_setup) { int status; char *argv[3] = { root_setup, root, NULL }; if (is_dir(root)) return TRUE; if (root_setup == NULL) return FALSE; DBG("Setting up %s using %s", root, root_setup); if (!g_spawn_sync(NULL, argv, NULL, 0, NULL, NULL, NULL, NULL, &status, NULL)) { error("Unable to execute %s", root_setup); return FALSE; } if (WEXITSTATUS(status) != EXIT_SUCCESS) { error("%s exited with status %d", root_setup, WEXITSTATUS(status)); return FALSE; } return is_dir(root); } DBusConnection *obex_get_dbus_connection(void) { if (connection) return dbus_connection_ref(connection); connection = dbus_bus_get(option_system_bus ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL); return connection; } DBusConnection *obex_setup_dbus_connection(const char *name, DBusError *error) { connection = g_dbus_setup_bus(option_system_bus ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, name, error); return connection; } int main(int argc, char *argv[]) { GOptionContext *context; GError *err = NULL; guint signal; context = g_option_context_new(NULL); g_option_context_add_main_entries(context, options, NULL); if (g_option_context_parse(context, &argc, &argv, &err) == FALSE) { if (err != NULL) { g_printerr("%s\n", err->message); g_error_free(err); } else g_printerr("An unknown error occurred\n"); exit(EXIT_FAILURE); } g_option_context_free(context); __obex_log_init(option_debug, option_detach); if (option_system_bus) logind_set(FALSE); DBG("Entering main loop"); main_loop = g_main_loop_new(NULL, FALSE); signal = setup_signalfd(); if (manager_init() == FALSE) { error("manager_init failed"); exit(EXIT_FAILURE); } if (option_root == NULL) { option_root = g_build_filename(g_get_user_cache_dir(), "obexd", NULL); if (g_mkdir_with_parents(option_root, 0700) < 0) error("Failed to create dir(%d): %s", errno, option_root); } if (option_root[0] != '/') { const char *home = getenv("HOME"); if (home) { char *old_root = option_root; option_root = g_strdup_printf("%s/%s", home, old_root); g_free(old_root); } } if (option_capability == NULL) option_capability = g_strdup(DEFAULT_CAP_FILE); plugin_init(option_plugin, option_noplugin); if (obex_server_init() < 0) { error("obex_server_init failed"); exit(EXIT_FAILURE); } if (!root_folder_setup(option_root, option_root_setup)) { error("Unable to setup root folder %s", option_root); exit(EXIT_FAILURE); } if (client_manager_init() < 0) { error("client_manager_init failed"); exit(EXIT_FAILURE); } g_main_loop_run(main_loop); g_source_remove(signal); client_manager_exit(); obex_server_exit(); plugin_cleanup(); manager_cleanup(); g_main_loop_unref(main_loop); g_free(option_capability); g_free(option_root); __obex_log_cleanup(); return 0; } bluez-5.87/obexd/src/PaxHeaders/obexd.h0000644000000000000000000000005015063045254015021 xustar0020 atime=1783093852 20 ctime=1783094104 bluez-5.87/obexd/src/obexd.h0000644000000000000000000000154315063045254014505 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ #include #define OBEX_OPP (1 << 1) #define OBEX_FTP (1 << 2) #define OBEX_BIP (1 << 3) #define OBEX_PBAP (1 << 4) #define OBEX_IRMC (1 << 5) #define OBEX_PCSUITE (1 << 6) #define OBEX_SYNCEVOLUTION (1 << 7) #define OBEX_MAS (1 << 8) #define OBEX_MNS (1 << 9) void plugin_init(const char *pattern, const char *exclude); void plugin_cleanup(void); gboolean manager_init(void); void manager_cleanup(void); gboolean obex_option_auto_accept(void); const char *obex_option_root_folder(void); gboolean obex_option_symlinks(void); const char *obex_option_capability(void); DBusConnection *obex_get_dbus_connection(void); DBusConnection *obex_setup_dbus_connection(const char *name, DBusError *error); bluez-5.87/obexd/src/PaxHeaders/manager.h0000644000000000000000000000005015142177620015333 xustar0020 atime=1783093856 20 ctime=1783094104 bluez-5.87/obexd/src/manager.h0000644000000000000000000000177215142177620015023 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2007-2010 Nokia Corporation * * */ #include #define OBEXD_SERVICE "org.bluez.obex" struct obex_session; struct obex_transfer; void manager_register_session(struct obex_session *os); void manager_unregister_session(struct obex_session *os); struct obex_transfer *manager_register_transfer(struct obex_session *os); void manager_unregister_transfer(struct obex_transfer *transfer); void manager_emit_transfer_property(struct obex_transfer *transfer, char *name); void manager_emit_transfer_queued(struct obex_transfer *transfer); void manager_emit_transfer_started(struct obex_transfer *transfer); void manager_emit_transfer_progress(struct obex_transfer *transfer); void manager_emit_transfer_completed(struct obex_transfer *transfer); int manager_request_authorization(struct obex_transfer *transfer, char **new_folder, char **new_name); DBusConnection *manager_dbus_get_connection(void); bluez-5.87/obexd/src/PaxHeaders/service.c0000644000000000000000000000005014572354773015370 xustar0020 atime=1783093882 20 ctime=1783094104 bluez-5.87/obexd/src/service.c0000644000000000000000000000460214572354773015053 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "obex.h" #include "service.h" #include "log.h" static GSList *drivers = NULL; const struct obex_service_driver *obex_service_driver_find(GSList *drivers, const uint8_t *target, unsigned int target_size, const uint8_t *who, unsigned int who_size) { GSList *l; for (l = drivers; l; l = l->next) { const struct obex_service_driver *driver = l->data; /* who is optional, so only check for it if not NULL */ if (who != NULL && memncmp0(who, who_size, driver->who, driver->who_size)) continue; if (memncmp0(target, target_size, driver->target, driver->target_size) == 0) return driver; } return NULL; } GSList *obex_service_driver_list(uint16_t services) { GSList *l; GSList *list = NULL; if (services == 0) return drivers; for (l = drivers; l && services; l = l->next) { const struct obex_service_driver *driver = l->data; if (driver->service & services) { list = g_slist_append(list, (gpointer)driver); services &= ~driver->service; } } return list; } static const struct obex_service_driver *find_driver(uint16_t service) { GSList *l; for (l = drivers; l; l = l->next) { const struct obex_service_driver *driver = l->data; if (driver->service == service) return driver; } return NULL; } int obex_service_driver_register(const struct obex_service_driver *driver) { if (!driver) { error("Invalid driver"); return -EINVAL; } if (find_driver(driver->service)) { error("Permission denied: service %s already registered", driver->name); return -EPERM; } DBG("driver %p service %s registered", driver, driver->name); /* Drivers that support who has priority */ if (driver->who) drivers = g_slist_prepend(drivers, (gpointer)driver); else drivers = g_slist_append(drivers, (gpointer)driver); return 0; } void obex_service_driver_unregister(const struct obex_service_driver *driver) { if (!g_slist_find(drivers, driver)) { error("Unable to unregister: No such driver %p", driver); return; } DBG("driver %p service %s unregistered", driver, driver->name); drivers = g_slist_remove(drivers, driver); } bluez-5.87/obexd/src/PaxHeaders/mimetype.h0000644000000000000000000000005014572354773015566 xustar0020 atime=1783093853 20 ctime=1783094104 bluez-5.87/obexd/src/mimetype.h0000644000000000000000000000271514572354773015254 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ typedef gboolean (*obex_object_io_func) (void *object, int flags, int err, void *user_data); struct obex_mime_type_driver { const uint8_t *target; unsigned int target_size; const char *mimetype; const uint8_t *who; unsigned int who_size; void *(*open) (const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err); int (*close) (void *object); ssize_t (*get_next_header)(void *object, void *buf, size_t mtu, uint8_t *hi); ssize_t (*read) (void *object, void *buf, size_t count); ssize_t (*write) (void *object, const void *buf, size_t count); int (*flush) (void *object); int (*copy) (const char *name, const char *destname); int (*move) (const char *name, const char *destname); int (*remove) (const char *name); }; int obex_mime_type_driver_register(const struct obex_mime_type_driver *driver); void obex_mime_type_driver_unregister(const struct obex_mime_type_driver *driver); const struct obex_mime_type_driver * obex_mime_type_driver_find(const uint8_t *target, unsigned int target_size, const char *mimetype, const uint8_t *who, unsigned int who_size); void obex_object_set_io_flags(void *object, int flags, int err); void obex_object_reset_io_watch(void *object); int obex_object_set_io_watch(void *object, obex_object_io_func func, void *user_data); bluez-5.87/obexd/src/PaxHeaders/obex.service.in0000644000000000000000000000005014572354773016510 xustar0020 atime=1783092767 20 ctime=1783094098 bluez-5.87/obexd/src/obex.service.in0000644000000000000000000000024314572354773016170 0ustar00rootroot[Unit] Description=Bluetooth OBEX service [Service] Type=dbus BusName=org.bluez.obex ExecStart=@PKGLIBEXECDIR@/obexd [Install] Alias=dbus-org.bluez.obex.service bluez-5.87/obexd/src/PaxHeaders/manager.c0000644000000000000000000000005015142177620015326 xustar0020 atime=1783093877 20 ctime=1783094104 bluez-5.87/obexd/src/manager.c0000644000000000000000000004445215142177620015020 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "gobex/gobex.h" #include "btio/btio.h" #include "obexd.h" #include "obex.h" #include "obex-priv.h" #include "server.h" #include "manager.h" #include "log.h" #include "service.h" #define OBEX_BASE_PATH "/org/bluez/obex" #define SESSION_BASE_PATH OBEX_BASE_PATH "/server" #define OBEX_MANAGER_INTERFACE OBEXD_SERVICE ".AgentManager1" #define ERROR_INTERFACE OBEXD_SERVICE ".Error" #define TRANSFER_INTERFACE OBEXD_SERVICE ".Transfer1" #define SESSION_INTERFACE OBEXD_SERVICE ".Session1" #define AGENT_INTERFACE OBEXD_SERVICE ".Agent1" #define OBEX_ERROR_REJECT "org.bluez.obex.Error.Rejected" #define TIMEOUT 60*1000 /* Timeout for user response (milliseconds) */ struct agent { char *bus_name; char *path; gboolean auth_pending; gboolean auth_reject; char *new_name; char *new_folder; unsigned int watch_id; }; enum { TRANSFER_STATUS_QUEUED = 0, TRANSFER_STATUS_ACTIVE, TRANSFER_STATUS_COMPLETE, TRANSFER_STATUS_ERROR }; struct obex_transfer { uint8_t status; char *path; struct obex_session *session; }; static struct agent *agent = NULL; static DBusConnection *connection = NULL; static void agent_free(struct agent *agent) { if (!agent) return; g_free(agent->new_folder); g_free(agent->new_name); g_free(agent->bus_name); g_free(agent->path); g_free(agent); } static inline DBusMessage *invalid_args(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); } static inline DBusMessage *not_supported(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NotSupported", "Operation is not supported"); } static inline DBusMessage *agent_already_exists(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyExists", "Agent already exists"); } static inline DBusMessage *agent_does_not_exist(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExist", "Agent does not exist"); } static inline DBusMessage *not_authorized(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAuthorized", "Not authorized"); } static void agent_disconnected(DBusConnection *conn, void *user_data) { DBG("Agent exited"); agent_free(agent); agent = NULL; } static DBusMessage *register_agent(DBusConnection *conn, DBusMessage *msg, void *data) { const char *path, *sender; if (agent) return agent_already_exists(msg); if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return invalid_args(msg); sender = dbus_message_get_sender(msg); agent = g_new0(struct agent, 1); agent->bus_name = g_strdup(sender); agent->path = g_strdup(path); agent->watch_id = g_dbus_add_disconnect_watch(conn, sender, agent_disconnected, NULL, NULL); DBG("Agent registered"); return dbus_message_new_method_return(msg); } static DBusMessage *unregister_agent(DBusConnection *conn, DBusMessage *msg, void *data) { const char *path, *sender; if (!agent) return agent_does_not_exist(msg); if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return invalid_args(msg); if (strcmp(agent->path, path) != 0) return agent_does_not_exist(msg); sender = dbus_message_get_sender(msg); if (strcmp(agent->bus_name, sender) != 0) return not_authorized(msg); g_dbus_remove_watch(conn, agent->watch_id); agent_free(agent); agent = NULL; DBG("Agent unregistered"); return dbus_message_new_method_return(msg); } static gboolean get_source(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obex_session *os = data; char *s; s = os->src; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &s); return TRUE; } static gboolean get_destination(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obex_session *os = data; char *s; s = os->dst; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &s); return TRUE; } static gboolean session_target_exists(const GDBusPropertyTable *property, void *data) { struct obex_session *os = data; return os->service->target ? TRUE : FALSE; } static char *target2str(const uint8_t *t) { if (!t) return NULL; return g_strdup_printf("%02X%02X%02X%02X-%02X%02X-%02X%02X-" "%02X%02X-%02X%02X%02X%02X%02X%02X", t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8], t[9], t[10], t[11], t[12], t[13], t[14], t[15]); } static gboolean get_target(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obex_session *os = data; char *uuid; uuid = target2str(os->service->target); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); g_free(uuid); return TRUE; } static gboolean get_root(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { const char *root; root = obex_option_root_folder(); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &root); return TRUE; } static DBusMessage *transfer_cancel(DBusConnection *connection, DBusMessage *msg, void *user_data) { struct obex_transfer *transfer = user_data; struct obex_session *os = transfer->session; const char *sender; if (!agent) return agent_does_not_exist(msg); if (!os) return invalid_args(msg); sender = dbus_message_get_sender(msg); if (strcmp(agent->bus_name, sender) != 0) return not_authorized(msg); os->aborted = TRUE; return dbus_message_new_method_return(msg); } static const char *status2str(uint8_t status) { switch (status) { case TRANSFER_STATUS_QUEUED: return "queued"; case TRANSFER_STATUS_ACTIVE: return "active"; case TRANSFER_STATUS_COMPLETE: return "complete"; case TRANSFER_STATUS_ERROR: default: return "error"; } } static gboolean transfer_get_status(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obex_transfer *transfer = data; const char *status = status2str(transfer->status); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &status); return TRUE; } static gboolean transfer_get_session(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obex_transfer *transfer = data; struct obex_session *session = transfer->session; char *path; if (session == NULL) return FALSE; path = g_strdup_printf("%s/session%u", SESSION_BASE_PATH, session->id); dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); g_free(path); return TRUE; } static gboolean transfer_name_exists(const GDBusPropertyTable *property, void *data) { struct obex_transfer *transfer = data; struct obex_session *session = transfer->session; return session->name != NULL; } static gboolean transfer_get_name(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obex_transfer *transfer = data; struct obex_session *session = transfer->session; if (session->name == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &session->name); return TRUE; } static gboolean transfer_type_exists(const GDBusPropertyTable *property, void *data) { struct obex_transfer *transfer = data; struct obex_session *session = transfer->session; return session->type != NULL; } static gboolean transfer_get_type(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obex_transfer *transfer = data; struct obex_session *session = transfer->session; if (session->type == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &session->type); return TRUE; } static gboolean transfer_size_exists(const GDBusPropertyTable *property, void *data) { struct obex_transfer *transfer = data; struct obex_session *session = transfer->session; return (session->size != OBJECT_SIZE_UNKNOWN && session->size != OBJECT_SIZE_DELETE); } static gboolean transfer_get_size(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obex_transfer *transfer = data; struct obex_session *session = transfer->session; if (session->size == OBJECT_SIZE_UNKNOWN || session->size == OBJECT_SIZE_DELETE) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &session->size); return TRUE; } static gboolean transfer_time_exists(const GDBusPropertyTable *property, void *data) { struct obex_transfer *transfer = data; struct obex_session *session = transfer->session; return session->time != 0; } static gboolean transfer_get_time(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obex_transfer *transfer = data; struct obex_session *session = transfer->session; dbus_uint64_t time_u64; if (session->size == 0) return FALSE; time_u64 = session->time; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &time_u64); return TRUE; } static gboolean transfer_filename_exists(const GDBusPropertyTable *property, void *data) { struct obex_transfer *transfer = data; struct obex_session *session = transfer->session; return session->path != NULL; } static gboolean transfer_get_filename(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obex_transfer *transfer = data; struct obex_session *session = transfer->session; if (session->path == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &session->path); return TRUE; } static gboolean transfer_get_transferred(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obex_transfer *transfer = data; struct obex_session *session = transfer->session; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &session->offset); return TRUE; } static const GDBusMethodTable manager_methods[] = { { GDBUS_METHOD("RegisterAgent", GDBUS_ARGS({ "agent", "o" }), NULL, register_agent) }, { GDBUS_METHOD("UnregisterAgent", GDBUS_ARGS({ "agent", "o" }), NULL, unregister_agent) }, { } }; static const GDBusMethodTable transfer_methods[] = { { GDBUS_METHOD("Cancel", NULL, NULL, transfer_cancel) }, { } }; static const GDBusPropertyTable transfer_properties[] = { { "Status", "s", transfer_get_status }, { "Session", "o", transfer_get_session }, { "Name", "s", transfer_get_name, NULL, transfer_name_exists }, { "Type", "s", transfer_get_type, NULL, transfer_type_exists }, { "Size", "t", transfer_get_size, NULL, transfer_size_exists }, { "Time", "t", transfer_get_time, NULL, transfer_time_exists }, { "Filename", "s", transfer_get_filename, NULL, transfer_filename_exists }, { "Transferred", "t", transfer_get_transferred }, { } }; static const GDBusPropertyTable session_properties[] = { { "Source", "s", get_source }, { "Destination", "s", get_destination }, { "Target", "s", get_target, NULL, session_target_exists }, { "Root", "s", get_root }, { } }; gboolean manager_init(void) { DBusError err; DBG(""); dbus_error_init(&err); connection = obex_setup_dbus_connection(OBEXD_SERVICE, &err); if (connection == NULL) { if (dbus_error_is_set(&err) == TRUE) { fprintf(stderr, "%s\n", err.message); dbus_error_free(&err); } else fprintf(stderr, "Can't register with session bus\n"); return FALSE; } g_dbus_attach_object_manager(connection); return g_dbus_register_interface(connection, OBEX_BASE_PATH, OBEX_MANAGER_INTERFACE, manager_methods, NULL, NULL, NULL, NULL); } void manager_cleanup(void) { DBG(""); g_dbus_unregister_interface(connection, OBEX_BASE_PATH, OBEX_MANAGER_INTERFACE); /* FIXME: Release agent? */ agent_free(agent); g_dbus_detach_object_manager(connection); dbus_connection_unref(connection); } void manager_emit_transfer_property(struct obex_transfer *transfer, char *name) { if (transfer->path == NULL) return; if (strcasecmp("Size", name) == 0 || strcasecmp("Status", name) == 0) g_dbus_emit_property_changed_full(connection, transfer->path, TRANSFER_INTERFACE, name, G_DBUS_PROPERTY_CHANGED_FLAG_FLUSH); else g_dbus_emit_property_changed(connection, transfer->path, TRANSFER_INTERFACE, name); } void manager_emit_transfer_queued(struct obex_transfer *transfer) { transfer->status = TRANSFER_STATUS_QUEUED; manager_emit_transfer_property(transfer, "Status"); } void manager_emit_transfer_started(struct obex_transfer *transfer) { transfer->status = TRANSFER_STATUS_ACTIVE; manager_emit_transfer_property(transfer, "Status"); } static void emit_transfer_completed(struct obex_transfer *transfer, gboolean success) { transfer->status = success ? TRANSFER_STATUS_COMPLETE : TRANSFER_STATUS_ERROR; manager_emit_transfer_property(transfer, "Status"); } static void transfer_free(struct obex_transfer *transfer) { g_free(transfer->path); g_free(transfer); } struct obex_transfer *manager_register_transfer(struct obex_session *os) { struct obex_transfer *transfer; static unsigned int id = 0; transfer = g_new0(struct obex_transfer, 1); transfer->path = g_strdup_printf("%s/session%u/transfer%u", SESSION_BASE_PATH, os->id, id++); transfer->session = os; if (!g_dbus_register_interface(connection, transfer->path, TRANSFER_INTERFACE, transfer_methods, NULL, transfer_properties, transfer, NULL)) { error("Cannot register Transfer interface."); transfer_free(transfer); return NULL; } return transfer; } void manager_unregister_transfer(struct obex_transfer *transfer) { struct obex_session *os; if (transfer == NULL) return; os = transfer->session; if (transfer->status == TRANSFER_STATUS_ACTIVE) emit_transfer_completed(transfer, os->offset == os->size); g_dbus_unregister_interface(connection, transfer->path, TRANSFER_INTERFACE); transfer_free(transfer); } static void agent_cancel(void) { DBusMessage *msg; if (agent == NULL) return; msg = dbus_message_new_method_call(agent->bus_name, agent->path, AGENT_INTERFACE, "Cancel"); g_dbus_send_message(connection, msg); } static void agent_reply(DBusPendingCall *call, void *user_data) { DBusMessage *reply = dbus_pending_call_steal_reply(call); const char *name; DBusError derr; gboolean *got_reply = user_data; *got_reply = TRUE; /* Received a reply after the agent exited */ if (!agent) return; agent->auth_pending = FALSE; dbus_error_init(&derr); if (dbus_set_error_from_message(&derr, reply)) { error("Agent replied with an error: %s, %s", derr.name, derr.message); if (dbus_error_has_name(&derr, DBUS_ERROR_NO_REPLY)) agent_cancel(); else if (dbus_error_has_name(&derr, OBEX_ERROR_REJECT)) agent->auth_reject = TRUE; dbus_error_free(&derr); dbus_message_unref(reply); return; } if (dbus_message_get_args(reply, NULL, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)) { /* Splits folder and name */ const char *slash = strrchr(name, '/'); DBG("Agent replied with %s", name); if (!slash) { agent->new_name = g_strdup(name); agent->new_folder = NULL; } else { if (strlen(slash) == 1) agent->new_name = NULL; else agent->new_name = g_strdup(slash + 1); agent->new_folder = g_strndup(name, slash - name); } } dbus_message_unref(reply); } static gboolean auth_error(GIOChannel *io, GIOCondition cond, void *user_data) { agent->auth_pending = FALSE; return FALSE; } int manager_request_authorization(struct obex_transfer *transfer, char **new_folder, char **new_name) { struct obex_session *os = transfer->session; DBusMessage *msg; DBusPendingCall *call; unsigned int watch; gboolean got_reply; if (!agent) return -1; if (agent->auth_pending) return -EPERM; if (!new_folder || !new_name) return -EINVAL; msg = dbus_message_new_method_call(agent->bus_name, agent->path, AGENT_INTERFACE, "AuthorizePush"); dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &transfer->path, DBUS_TYPE_INVALID); if (!g_dbus_send_message_with_reply(connection, msg, &call, TIMEOUT)) { dbus_message_unref(msg); return -EPERM; } dbus_message_unref(msg); agent->auth_pending = TRUE; agent->auth_reject = FALSE; got_reply = FALSE; /* Catches errors before authorization response comes */ watch = g_io_add_watch_full(os->io, G_PRIORITY_DEFAULT, G_IO_HUP | G_IO_ERR | G_IO_NVAL, auth_error, NULL, NULL); dbus_pending_call_set_notify(call, agent_reply, &got_reply, NULL); /* Workaround: process events while agent doesn't reply */ while (agent && agent->auth_pending) g_main_context_iteration(NULL, TRUE); g_source_remove(watch); if (!got_reply) { dbus_pending_call_cancel(call); agent_cancel(); } dbus_pending_call_unref(call); if (!agent || agent->auth_reject) return -EPERM; *new_folder = agent->new_folder; *new_name = agent->new_name; agent->new_folder = NULL; agent->new_name = NULL; return 0; } static DBusMessage *session_get_capabilities(DBusConnection *connection, DBusMessage *message, void *user_data) { return not_supported(message); } static const GDBusMethodTable session_methods[] = { { GDBUS_ASYNC_METHOD("GetCapabilities", NULL, GDBUS_ARGS({ "capabilities", "s" }), session_get_capabilities) }, { } }; void manager_register_session(struct obex_session *os) { char *path; path = g_strdup_printf("%s/session%u", SESSION_BASE_PATH, os->id); if (!g_dbus_register_interface(connection, path, SESSION_INTERFACE, session_methods, NULL, session_properties, os, NULL)) error("Cannot register Session interface."); g_free(path); } void manager_unregister_session(struct obex_session *os) { char *path; path = g_strdup_printf("%s/session%u", SESSION_BASE_PATH, os->id); g_dbus_unregister_interface(connection, path, SESSION_INTERFACE); g_free(path); } void manager_emit_transfer_progress(struct obex_transfer *transfer) { manager_emit_transfer_property(transfer, "Transferred"); } void manager_emit_transfer_completed(struct obex_transfer *transfer) { struct obex_session *session; if (transfer == NULL) return; session = transfer->session; if (session == NULL || session->object == NULL) return; emit_transfer_completed(transfer, !session->aborted); } DBusConnection *manager_dbus_get_connection(void) { if (connection == NULL) return NULL; return dbus_connection_ref(connection); } bluez-5.87/obexd/src/PaxHeaders/server.h0000644000000000000000000000005014572354773015243 xustar0020 atime=1783093855 20 ctime=1783094104 bluez-5.87/obexd/src/server.h0000644000000000000000000000077414572354773014734 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2007-2010 Nokia Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ struct obex_server { const struct obex_transport_driver *transport; void *transport_data; GSList *drivers; }; int obex_server_init(void); void obex_server_exit(void); int obex_server_new_connection(struct obex_server *server, GIOChannel *io, uint16_t tx_mtu, uint16_t rx_mtu, gboolean stream); bluez-5.87/obexd/src/PaxHeaders/genbuiltin0000644000000000000000000000005014572354773015647 xustar0020 atime=1783092780 20 ctime=1783094111 bluez-5.87/obexd/src/genbuiltin0000755000000000000000000000036314572354773015335 0ustar00rootroot#!/bin/sh for i in $* do echo "extern const struct obex_plugin_desc __obex_builtin_$i;" done echo echo "static const struct obex_plugin_desc *__obex_builtin[] = {" for i in $* do echo " &__obex_builtin_$i," done echo " NULL" echo "};" bluez-5.87/obexd/src/PaxHeaders/log.c0000644000000000000000000000005014015011623014461 xustar0020 atime=1783093875 20 ctime=1783094104 bluez-5.87/obexd/src/log.c0000644000000000000000000000403314015011623014142 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include "log.h" void info(const char *format, ...) { va_list ap; va_start(ap, format); vsyslog(LOG_INFO, format, ap); va_end(ap); } void error(const char *format, ...) { va_list ap; va_start(ap, format); vsyslog(LOG_ERR, format, ap); va_end(ap); } void obex_debug(const char *format, ...) { va_list ap; va_start(ap, format); vsyslog(LOG_DEBUG, format, ap); va_end(ap); } extern struct obex_debug_desc __start___debug[]; extern struct obex_debug_desc __stop___debug[]; static char **enabled = NULL; static gboolean is_enabled(struct obex_debug_desc *desc) { int i; if (enabled == NULL) return 0; for (i = 0; enabled[i] != NULL; i++) { if (desc->name != NULL && g_pattern_match_simple(enabled[i], desc->name) == TRUE) return 1; if (desc->file != NULL && g_pattern_match_simple(enabled[i], desc->file) == TRUE) return 1; } return 0; } void __obex_log_enable_debug(void) { struct obex_debug_desc *desc; for (desc = __start___debug; desc < __stop___debug; desc++) desc->flags |= OBEX_DEBUG_FLAG_PRINT; } void __obex_log_init(const char *debug, int detach) { int option = LOG_NDELAY | LOG_PID; struct obex_debug_desc *desc; const char *name = NULL, *file = NULL; if (debug != NULL) enabled = g_strsplit_set(debug, ":, ", 0); for (desc = __start___debug; desc < __stop___debug; desc++) { if (file != NULL || name != NULL) { if (g_strcmp0(desc->file, file) == 0) { if (desc->name == NULL) desc->name = name; } else file = NULL; } if (is_enabled(desc)) desc->flags |= OBEX_DEBUG_FLAG_PRINT; } if (!detach) option |= LOG_PERROR; openlog("obexd", option, LOG_DAEMON); info("OBEX daemon %s", VERSION); } void __obex_log_cleanup(void) { closelog(); g_strfreev(enabled); } bluez-5.87/obexd/PaxHeaders/plugins0000644000000000000000000000005015221755537014374 xustar0020 atime=1783094113 20 ctime=1783094111 bluez-5.87/obexd/plugins/0000755000000000000000000000000015221755537014132 5ustar00rootrootbluez-5.87/obexd/plugins/PaxHeaders/messages-dummy.c0000644000000000000000000000005014766002272017547 xustar0020 atime=1783093870 20 ctime=1783094104 bluez-5.87/obexd/plugins/messages-dummy.c0000644000000000000000000003037214766002272017235 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2010-2011 Nokia Corporation * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "obexd/src/log.h" #include "messages.h" #define MSG_LIST_XML "mlisting.xml" static char *root_folder = NULL; struct session { char *cwd; char *cwd_absolute; void *request; }; struct folder_listing_data { struct session *session; const char *name; uint16_t max; uint16_t offset; messages_folder_listing_cb callback; void *user_data; }; struct message_listing_data { struct session *session; const char *name; uint16_t max; uint16_t offset; uint8_t subject_len; uint16_t size; char *path; FILE *fp; const struct messages_filter *filter; messages_get_messages_listing_cb callback; void *user_data; }; /* NOTE: Neither IrOBEX nor MAP specs says that folder listing needs to * be sorted (in IrOBEX examples it is not). However existing implementations * seem to follow the fig. 3-2 from MAP specification v1.0, and I've seen a * test suite requiring folder listing to be in that order. */ static int folder_names_cmp(gconstpointer a, gconstpointer b, gpointer user_data) { static const char *order[] = { "inbox", "outbox", "sent", "deleted", "draft", NULL }; struct session *session = user_data; int ia, ib; if (g_strcmp0(session->cwd, "telecom/msg") == 0) { for (ia = 0; order[ia]; ia++) { if (g_strcmp0(a, order[ia]) == 0) break; } for (ib = 0; order[ib]; ib++) { if (g_strcmp0(b, order[ib]) == 0) break; } if (ia != ib) return ia - ib; } return g_strcmp0(a, b); } static char *get_next_subdir(DIR *dp, char *path) { struct dirent *ep; char *abs, *name; for (;;) { if ((ep = readdir(dp)) == NULL) return NULL; if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0) continue; abs = g_build_filename(path, ep->d_name, NULL); if (g_file_test(abs, G_FILE_TEST_IS_DIR)) { g_free(abs); break; } g_free(abs); } name = g_filename_to_utf8(ep->d_name, -1, NULL, NULL, NULL); if (name == NULL) { DBG("g_filename_to_utf8(): invalid filename"); return NULL; } return name; } static ssize_t get_subdirs(struct folder_listing_data *fld, GSList **list) { DIR *dp; char *path, *name; size_t n; path = g_build_filename(fld->session->cwd_absolute, fld->name, NULL); dp = opendir(path); if (dp == NULL) { int err = -errno; DBG("opendir(): %d, %s", -err, strerror(-err)); g_free(path); return err; } n = 0; while ((name = get_next_subdir(dp, path)) != NULL) { n++; if (fld->max > 0) *list = g_slist_prepend(*list, name); } closedir(dp); g_free(path); *list = g_slist_sort_with_data(*list, folder_names_cmp, fld->session); return n; } static void return_folder_listing(struct folder_listing_data *fld, GSList *list) { struct session *session = fld->session; GSList *cur; uint16_t num = 0; uint16_t offs = 0; for (cur = list; offs < fld->offset; offs++) { cur = cur->next; if (cur == NULL) break; } for (; cur != NULL && num < fld->max; cur = cur->next, num++) fld->callback(session, -EAGAIN, 0, cur->data, fld->user_data); fld->callback(session, 0, 0, NULL, fld->user_data); } static gboolean get_folder_listing(void *d) { struct folder_listing_data *fld = d; ssize_t n; GSList *list = NULL; n = get_subdirs(fld, &list); if (n < 0) { fld->callback(fld->session, n, 0, NULL, fld->user_data); return FALSE; } if (fld->max == 0) { fld->callback(fld->session, 0, n, NULL, fld->user_data); return FALSE; } return_folder_listing(fld, list); g_slist_free_full(list, g_free); return FALSE; } int messages_init(void) { char *tmp; if (root_folder) return 0; tmp = getenv("MAP_ROOT"); if (tmp) { root_folder = g_strdup(tmp); return 0; } tmp = getenv("HOME"); if (!tmp) return -ENOENT; root_folder = g_build_filename(tmp, "map-messages", NULL); return 0; } void messages_exit(void) { g_free(root_folder); root_folder = NULL; } int messages_connect(void **s) { struct session *session; session = g_new0(struct session, 1); session->cwd = g_strdup(""); session->cwd_absolute = g_strdup(root_folder); *s = session; return 0; } void messages_disconnect(void *s) { struct session *session = s; g_free(session->cwd); g_free(session->cwd_absolute); g_free(session); } int messages_set_notification_registration(void *session, void (*send_event)(void *session, const struct messages_event *event, void *user_data), void *user_data) { return -ENOSYS; } int messages_set_folder(void *s, const char *name, gboolean cdup) { struct session *session = s; char *newrel = NULL; char *newabs; char *tmp; if (name && (strchr(name, '/') || strcmp(name, "..") == 0)) return -EBADR; if (cdup) { if (session->cwd[0] == 0) return -ENOENT; newrel = g_path_get_dirname(session->cwd); /* We use empty string for indication of the root directory */ if (newrel[0] == '.' && newrel[1] == 0) newrel[0] = 0; } tmp = newrel; if (!cdup && (!name || name[0] == 0)) newrel = g_strdup(""); else newrel = g_build_filename(newrel ? newrel : session->cwd, name, NULL); g_free(tmp); newabs = g_build_filename(root_folder, newrel, NULL); if (!g_file_test(newabs, G_FILE_TEST_IS_DIR)) { g_free(newrel); g_free(newabs); return -ENOENT; } g_free(session->cwd); session->cwd = newrel; g_free(session->cwd_absolute); session->cwd_absolute = newabs; return 0; } int messages_get_folder_listing(void *s, const char *name, uint16_t max, uint16_t offset, messages_folder_listing_cb callback, void *user_data) { struct session *session = s; struct folder_listing_data *fld; fld = g_new0(struct folder_listing_data, 1); fld->session = session; fld->name = name; fld->max = max; fld->offset = offset; fld->callback = callback; fld->user_data = user_data; session->request = fld; g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, get_folder_listing, fld, g_free); return 0; } static void max_msg_element(GMarkupParseContext *ctxt, const char *element, const char **names, const char **values, gpointer user_data, GError **gerr) { struct message_listing_data *mld = user_data; const char *key; int i; for (i = 0, key = names[i]; key; key = names[++i]) { if (g_strcmp0(names[i], "handle") == 0) { mld->size++; break; } } } static void msg_element(GMarkupParseContext *ctxt, const char *element, const char **names, const char **values, gpointer user_data, GError **gerr) { struct message_listing_data *mld = user_data; struct messages_message *entry = NULL; int i; entry = g_new0(struct messages_message, 1); if (mld->filter->parameter_mask == 0) { entry->mask = (entry->mask | PMASK_SUBJECT \ | PMASK_DATETIME | PMASK_RECIPIENT_ADDRESSING \ | PMASK_SENDER_ADDRESSING \ | PMASK_ATTACHMENT_SIZE | PMASK_TYPE \ | PMASK_RECEPTION_STATUS); } else entry->mask = mld->filter->parameter_mask; for (i = 0 ; names[i]; ++i) { if (g_strcmp0(names[i], "handle") == 0) { g_free(entry->handle); entry->handle = g_strdup(values[i]); mld->size++; continue; } if (g_strcmp0(names[i], "attachment_size") == 0) { g_free(entry->attachment_size); entry->attachment_size = g_strdup(values[i]); continue; } if (g_strcmp0(names[i], "datetime") == 0) { g_free(entry->datetime); entry->datetime = g_strdup(values[i]); continue; } if (g_strcmp0(names[i], "subject") == 0) { g_free(entry->subject); entry->subject = g_strdup(values[i]); continue; } if (g_strcmp0(names[i], "recipient_addressing") == 0) { g_free(entry->recipient_addressing); entry->recipient_addressing = g_strdup(values[i]); continue; } if (g_strcmp0(names[i], "sender_addressing") == 0) { g_free(entry->sender_addressing); entry->sender_addressing = g_strdup(values[i]); continue; } if (g_strcmp0(names[i], "type") == 0) { g_free(entry->type); entry->type = g_strdup(values[i]); continue; } if (g_strcmp0(names[i], "reception_status") == 0) { g_free(entry->reception_status); entry->reception_status = g_strdup(values[i]); } } if (mld->size > mld->offset) mld->callback(mld->session, -EAGAIN, mld->size, 0, entry, mld->user_data); g_free(entry->reception_status); g_free(entry->type); g_free(entry->sender_addressing); g_free(entry->recipient_addressing); g_free(entry->subject); g_free(entry->datetime); g_free(entry->attachment_size); g_free(entry->handle); g_free(entry); } static const GMarkupParser msg_parser = { msg_element, NULL, NULL, NULL, NULL }; static const GMarkupParser max_msg_parser = { max_msg_element, NULL, NULL, NULL, NULL }; static gboolean get_messages_listing(void *d) { struct message_listing_data *mld = d; /* 1024 is the maximum size of the line which is calculated to be more * sufficient*/ char buffer[1024]; GMarkupParseContext *ctxt; size_t len; while (fgets(buffer, 1024, mld->fp)) { len = strlen(buffer); if (mld->max == 0) { ctxt = g_markup_parse_context_new(&max_msg_parser, 0, mld, NULL); g_markup_parse_context_parse(ctxt, buffer, len, NULL); g_markup_parse_context_free(ctxt); } else { ctxt = g_markup_parse_context_new(&msg_parser, 0, mld, NULL); g_markup_parse_context_parse(ctxt, buffer, len, NULL); g_markup_parse_context_free(ctxt); } } if (mld->max == 0) { mld->callback(mld->session, 0, mld->size, 0, NULL, mld->user_data); goto done; } mld->callback(mld->session, 0, mld->size, 0, NULL, mld->user_data); done: fclose(mld->fp); return FALSE; } int messages_get_messages_listing(void *session, const char *name, uint16_t max, uint16_t offset, uint8_t subject_len, const struct messages_filter *filter, messages_get_messages_listing_cb callback, void *user_data) { struct message_listing_data *mld; struct session *s = session; char *path; mld = g_new0(struct message_listing_data, 1); mld->session = s; mld->name = name; mld->max = max; mld->offset = offset; mld->subject_len = subject_len; mld->callback = callback; mld->filter = filter; mld->user_data = user_data; path = g_build_filename(s->cwd_absolute, MSG_LIST_XML, NULL); mld->fp = fopen(path, "r"); if (mld->fp == NULL) { g_free(path); messages_set_folder(s, mld->name, 0); path = g_build_filename(s->cwd_absolute, MSG_LIST_XML, NULL); mld->fp = fopen(path, "r"); if (mld->fp == NULL) { int err = -errno; DBG("fopen(): %d, %s", -err, strerror(-err)); g_free(path); g_free(mld); return -EBADR; } } g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, get_messages_listing, mld, g_free); g_free(path); return 0; } int messages_get_message(void *session, const char *handle, unsigned long flags, messages_get_message_cb callback, void *user_data) { struct session *s = session; FILE *fp; char *path; char *msg, *buffer; int file_size, err = 0; struct stat file_info; DBG(" "); path = g_build_filename(s->cwd_absolute, handle, NULL); fp = fopen(path, "r"); if (!fp) { DBG("fopen() failed"); err = -EBADR; goto file_open_err; } if (fstat(fileno(fp), &file_info) < 0) { DBG("Error getting file size"); err = -EBADR; goto mmap_err; } file_size = file_info.st_size; msg = mmap(0, file_size, PROT_READ, MAP_PRIVATE, fileno(fp), 0); if (msg == MAP_FAILED) { DBG("Error mapping file"); err = -EBADR; goto mmap_err; } buffer = strndup(msg, file_size); if (callback) callback(session, 0, 0, buffer, user_data); if (munmap(msg, file_size) == -1) { DBG("Error unmapping"); err = -EBADR; } free(buffer); mmap_err: fclose(fp); file_open_err: g_free(path); return err; } int messages_update_inbox(void *session, messages_status_cb callback, void *user_data) { return -ENOSYS; } int messages_set_read(void *session, const char *handle, uint8_t value, messages_status_cb callback, void *user_data) { return -ENOSYS; } int messages_set_delete(void *session, const char *handle, uint8_t value, messages_status_cb callback, void *user_data) { return -ENOSYS; } void messages_abort(void *s) { struct session *session = s; if (session->request) { g_idle_remove_by_data(session->request); session->request = NULL; } } bluez-5.87/obexd/plugins/PaxHeaders/bluetooth.c0000644000000000000000000000005015063045254016612 xustar0020 atime=1783093854 20 ctime=1783094104 bluez-5.87/obexd/plugins/bluetooth.c0000644000000000000000000002463615063045254016306 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2007-2010 Nokia Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "gdbus/gdbus.h" #include "btio/btio.h" #include "obexd/src/obexd.h" #include "obexd/src/plugin.h" #include "obexd/src/server.h" #include "obexd/src/obex.h" #include "obexd/src/transport.h" #include "obexd/src/service.h" #include "obexd/src/log.h" #include "obexd/src/logind.h" #define BT_RX_MTU 32767 #define BT_TX_MTU 32767 struct bluetooth_profile { struct obex_server *server; const struct obex_service_driver *driver; char *uuid; char *path; }; static GSList *profiles = NULL; static DBusConnection *connection = NULL; static DBusMessage *profile_release(DBusConnection *conn, DBusMessage *msg, void *data) { return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static void connect_event(GIOChannel *io, GError *err, void *user_data) { int sk = g_io_channel_unix_get_fd(io); struct bluetooth_profile *profile = user_data; struct obex_server *server = profile->server; int type; uint16_t omtu = BT_TX_MTU; uint16_t imtu = BT_RX_MTU; gboolean stream = TRUE; socklen_t len = sizeof(int); if (err) goto drop; if (getsockopt(sk, SOL_SOCKET, SO_TYPE, &type, &len) < 0) goto done; if (type != SOCK_SEQPACKET) goto done; stream = FALSE; /* Read MTU if io is an L2CAP socket */ bt_io_get(io, NULL, BT_IO_OPT_OMTU, &omtu, BT_IO_OPT_IMTU, &imtu, BT_IO_OPT_INVALID); done: if (obex_server_new_connection(server, io, omtu, imtu, stream) < 0) g_io_channel_shutdown(io, TRUE, NULL); return; drop: error("%s", err->message); g_io_channel_shutdown(io, TRUE, NULL); return; } static DBusMessage *invalid_args(DBusMessage *msg) { return g_dbus_create_error(msg, "org.bluez.Error.InvalidArguments", "Invalid arguments in method call"); } static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *msg, void *data) { DBusMessageIter args; const char *device; int fd; GIOChannel *io; dbus_message_iter_init(msg, &args); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) return invalid_args(msg); dbus_message_iter_get_basic(&args, &device); dbus_message_iter_next(&args); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_UNIX_FD) return invalid_args(msg); dbus_message_iter_get_basic(&args, &fd); if (fd < 0) { error("bluetooth: NewConnection invalid fd"); return invalid_args(msg); } /* Read fd flags to make sure it can be used */ if (fcntl(fd, F_GETFD) < 0) { error("bluetooth: fcntl(%d, F_GETFD): %s (%d)", fd, strerror(errno), errno); close(fd); return invalid_args(msg); } io = g_io_channel_unix_new(fd); if (io == NULL) { close(fd); return invalid_args(msg); } DBG("device %s", device); connect_event(io, NULL, data); g_io_channel_unref(io); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *profile_request_disconnection(DBusConnection *conn, DBusMessage *msg, void *data) { return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *profile_cancel(DBusConnection *conn, DBusMessage *msg, void *data) { return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static const GDBusMethodTable profile_methods[] = { { GDBUS_METHOD("Release", NULL, NULL, profile_release) }, { GDBUS_METHOD("NewConnection", GDBUS_ARGS({ "device", "o" }, { "fd", "h" }, { "options", "a{sv}" }), NULL, profile_new_connection) }, { GDBUS_METHOD("RequestDisconnection", GDBUS_ARGS({ "device", "o" }), NULL, profile_request_disconnection) }, { GDBUS_METHOD("Cancel", NULL, NULL, profile_cancel) }, { } }; static void unregister_profile(struct bluetooth_profile *profile) { g_dbus_unregister_interface(connection, profile->path, "org.bluez.Profile1"); g_free(profile->path); profile->path = NULL; } static void register_profile_reply(DBusPendingCall *call, void *user_data) { struct bluetooth_profile *profile = user_data; DBusMessage *reply = dbus_pending_call_steal_reply(call); DBusError derr; dbus_error_init(&derr); if (!dbus_set_error_from_message(&derr, reply)) { DBG("Profile %s registered", profile->path); goto done; } unregister_profile(profile); error("bluetooth: RequestProfile error: %s, %s", derr.name, derr.message); dbus_error_free(&derr); done: dbus_message_unref(reply); } static void profile_free(void *data) { struct bluetooth_profile *profile = data; if (profile->path != NULL) unregister_profile(profile); g_free(profile->uuid); g_free(profile); } static int register_profile(struct bluetooth_profile *profile) { DBusMessage *msg; DBusMessageIter iter, opt; DBusPendingCall *call; dbus_bool_t auto_connect = FALSE; char *xml; int ret = 0; profile->path = g_strconcat("/org/bluez/obex/", profile->uuid, NULL); g_strdelimit(profile->path, "-", '_'); if (!g_dbus_register_interface(connection, profile->path, "org.bluez.Profile1", profile_methods, NULL, NULL, profile, NULL)) { error("D-Bus failed to register %s", profile->path); g_free(profile->path); profile->path = NULL; return -1; } msg = dbus_message_new_method_call("org.bluez", "/org/bluez", "org.bluez.ProfileManager1", "RegisterProfile"); dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &profile->path); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &profile->uuid); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &opt); g_dbus_dict_append_entry(&opt, "AutoConnect", DBUS_TYPE_BOOLEAN, &auto_connect); if (profile->driver->record) { if (profile->driver->port != 0) xml = g_markup_printf_escaped(profile->driver->record, profile->driver->channel, profile->driver->name, profile->driver->port); else xml = g_markup_printf_escaped(profile->driver->record, profile->driver->channel, profile->driver->name); g_dbus_dict_append_entry(&opt, "ServiceRecord", DBUS_TYPE_STRING, &xml); g_free(xml); } dbus_message_iter_close_container(&iter, &opt); if (!g_dbus_send_message_with_reply(connection, msg, &call, -1)) { ret = -1; unregister_profile(profile); goto failed; } dbus_pending_call_set_notify(call, register_profile_reply, profile, NULL); dbus_pending_call_unref(call); failed: dbus_message_unref(msg); return ret; } static const char *service2uuid(uint16_t service) { switch (service) { case OBEX_OPP: return OBEX_OPP_UUID; case OBEX_FTP: return OBEX_FTP_UUID; case OBEX_PBAP: return OBEX_PSE_UUID; case OBEX_IRMC: return OBEX_SYNC_UUID; case OBEX_PCSUITE: return "00005005-0000-1000-8000-0002ee000001"; case OBEX_SYNCEVOLUTION: return "00000002-0000-1000-8000-0002ee000002"; case OBEX_MAS: return OBEX_MAS_UUID; case OBEX_MNS: return OBEX_MNS_UUID; } return NULL; } static void name_acquired(DBusConnection *conn, void *user_data) { GSList *l; DBG("org.bluez appeared"); for (l = profiles; l; l = l->next) { struct bluetooth_profile *profile = l->data; if (profile->path != NULL) continue; if (register_profile(profile) < 0) { error("bluetooth: Failed to register profile %s", profile->path); g_free(profile->path); profile->path = NULL; } } } static void name_released(DBusConnection *conn, void *user_data) { GSList *l; DBG("org.bluez disappeared"); for (l = profiles; l; l = l->next) { struct bluetooth_profile *profile = l->data; if (profile->path == NULL) continue; unregister_profile(profile); } } static void *bluetooth_start(struct obex_server *server, int *err) { const GSList *l; for (l = server->drivers; l; l = l->next) { const struct obex_service_driver *driver = l->data; struct bluetooth_profile *profile; const char *uuid; uuid = service2uuid(driver->service); if (uuid == NULL) continue; profile = g_new0(struct bluetooth_profile, 1); profile->driver = driver; profile->server = server; profile->uuid = g_strdup(uuid); profiles = g_slist_prepend(profiles, profile); } return profiles; } static void bluetooth_stop(void *data) { g_slist_free_full(profiles, profile_free); profiles = NULL; } static int bluetooth_getpeername(GIOChannel *io, char **name) { GError *gerr = NULL; char address[18]; bt_io_get(io, &gerr, BT_IO_OPT_DEST, address, BT_IO_OPT_INVALID); if (gerr) { error("%s", gerr->message); g_error_free(gerr); return -EINVAL; } *name = g_strdup(address); return 0; } static int bluetooth_getsockname(GIOChannel *io, char **name) { GError *gerr = NULL; char address[18]; bt_io_get(io, &gerr, BT_IO_OPT_SOURCE, address, BT_IO_OPT_INVALID); if (gerr) { error("%s", gerr->message); g_error_free(gerr); return -EINVAL; } *name = g_strdup(address); return 0; } static const struct obex_transport_driver driver = { .name = "bluetooth", .start = bluetooth_start, .getpeername = bluetooth_getpeername, .getsockname = bluetooth_getsockname, .stop = bluetooth_stop }; static unsigned int listener_id = 0; static int bluetooth_init_cb(gboolean at_register) { (void)at_register; connection = g_dbus_setup_private(DBUS_BUS_SYSTEM, NULL, NULL); if (connection == NULL) return -EPERM; listener_id = g_dbus_add_service_watch(connection, "org.bluez", name_acquired, name_released, NULL, NULL); return obex_transport_driver_register(&driver); } static void bluetooth_exit_cb(gboolean at_unregister) { GSList *l; (void)at_unregister; g_dbus_remove_watch(connection, listener_id); for (l = profiles; l; l = l->next) { struct bluetooth_profile *profile = l->data; if (profile->path == NULL) continue; unregister_profile(profile); } if (connection) { dbus_connection_close(connection); dbus_connection_unref(connection); connection = NULL; } obex_transport_driver_unregister(&driver); } static int bluetooth_init(void) { return logind_register(bluetooth_init_cb, bluetooth_exit_cb); } static void bluetooth_exit(void) { return logind_unregister(bluetooth_init_cb, bluetooth_exit_cb); } OBEX_PLUGIN_DEFINE(bluetooth, bluetooth_init, bluetooth_exit) bluez-5.87/obexd/plugins/PaxHeaders/ftp.h0000644000000000000000000000005014015011623015370 xustar0020 atime=1783093857 20 ctime=1783094104 bluez-5.87/obexd/plugins/ftp.h0000644000000000000000000000105114015011623015046 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ void *ftp_connect(struct obex_session *os, int *err); int ftp_get(struct obex_session *os, void *user_data); int ftp_chkput(struct obex_session *os, void *user_data); int ftp_put(struct obex_session *os, void *user_data); int ftp_setpath(struct obex_session *os, void *user_data); void ftp_disconnect(struct obex_session *os, void *user_data); int ftp_action(struct obex_session *os, void *user_data); bluez-5.87/obexd/plugins/PaxHeaders/phonebook-dummy.c0000644000000000000000000000005015063045254017722 xustar0020 atime=1783093865 20 ctime=1783094104 bluez-5.87/obexd/plugins/phonebook-dummy.c0000644000000000000000000002715115063045254017411 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2009-2010 Intel Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "obexd/src/log.h" #include "phonebook.h" typedef void (*vcard_func_t) (const char *file, VObject *vo, void *user_data); struct dummy_data { phonebook_cb cb; void *user_data; const struct apparam_field *apparams; char *folder; int fd; guint id; }; struct cache_query { phonebook_entry_cb entry_cb; phonebook_cache_ready_cb ready_cb; void *user_data; DIR *dp; }; static char *root_folder = NULL; static void dummy_free(void *user_data) { struct dummy_data *dummy = user_data; if (dummy->fd >= 0) close(dummy->fd); g_free(dummy->folder); g_free(dummy); } static void query_free(void *user_data) { struct cache_query *query = user_data; if (query->dp) closedir(query->dp); g_free(query); } int phonebook_init(void) { if (root_folder) return 0; /* FIXME: It should NOT be hard-coded */ root_folder = g_build_filename(getenv("HOME"), "phonebook", NULL); return 0; } void phonebook_exit(void) { g_free(root_folder); root_folder = NULL; } static int handle_cmp(gconstpointer a, gconstpointer b) { const char *f1 = a; const char *f2 = b; unsigned int i1, i2; if (sscanf(f1, "%u.vcf", &i1) != 1) return -1; if (sscanf(f2, "%u.vcf", &i2) != 1) return -1; return (i1 - i2); } static int foreach_vcard(DIR *dp, vcard_func_t func, uint16_t offset, uint16_t maxlistcount, void *user_data, uint16_t *count) { struct dirent *ep; GSList *sorted = NULL, *l; VObject *v; FILE *fp; int err, fd, folderfd; uint16_t n = 0; folderfd = dirfd(dp); if (folderfd < 0) { err = errno; error("dirfd(): %s(%d)", strerror(err), err); return -err; } /* * Sorting vcards by file name. versionsort is a GNU extension. * The simple sorting function implemented on handle_cmp address * vcards handle only(handle is always a number). This sort function * doesn't address filename started by "0". */ while ((ep = readdir(dp))) { char *filename; if (ep->d_name[0] == '.') continue; filename = g_filename_to_utf8(ep->d_name, -1, NULL, NULL, NULL); if (filename == NULL) { error("g_filename_to_utf8: invalid filename"); continue; } if (!g_str_has_suffix(filename, ".vcf")) { g_free(filename); continue; } sorted = g_slist_insert_sorted(sorted, filename, handle_cmp); } /* * Filtering only the requested vCards attributes. Offset * shall be based on the first entry of the phonebook. */ for (l = g_slist_nth(sorted, offset); l && n < maxlistcount; l = l->next) { const char *filename = l->data; fd = openat(folderfd, filename, O_RDONLY); if (fd < 0) { err = errno; error("openat(%s): %s(%d)", filename, strerror(err), err); continue; } fp = fdopen(fd, "r"); v = Parse_MIME_FromFile(fp); if (v != NULL) { func(filename, v, user_data); deleteVObject(v); n++; } close(fd); } g_slist_free_full(sorted, g_free); if (count) *count = n; return 0; } static void entry_concat(const char *filename, VObject *v, void *user_data) { GString *buffer = user_data; char tmp[1024]; int len; /* * VObject API uses len for IN and OUT * Written bytes is also returned in the len variable */ len = sizeof(tmp); memset(tmp, 0, len); writeMemVObject(tmp, &len, v); /* FIXME: only the requested fields must be added */ g_string_append_len(buffer, tmp, len); } static gboolean read_dir(void *user_data) { struct dummy_data *dummy = user_data; GString *buffer; DIR *dp; uint16_t count = 0, max, offset; buffer = g_string_new(""); dp = opendir(dummy->folder); if (dp == NULL) { int err = errno; DBG("opendir(): %s(%d)", strerror(err), err); goto done; } /* * For PullPhoneBook function, the decision of returning the size * or contacts is made in the PBAP core. When MaxListCount is ZERO, * PCE wants to know the size of a given folder, PSE shall ignore all * other applicattion parameters that may be present in the request. */ if (dummy->apparams->maxlistcount == 0) { max = 0xffff; offset = 0; } else { max = dummy->apparams->maxlistcount; offset = dummy->apparams->liststartoffset; } foreach_vcard(dp, entry_concat, offset, max, buffer, &count); closedir(dp); done: /* FIXME: Missing vCards fields filtering */ dummy->cb(buffer->str, buffer->len, count, 0, TRUE, dummy->user_data); g_string_free(buffer, TRUE); return FALSE; } static void entry_notify(const char *filename, VObject *v, void *user_data) { struct cache_query *query = user_data; VObject *property, *subproperty; GString *name; const char *tel; long unsigned int handle; property = isAPropertyOf(v, VCNameProp); if (!property) return; if (sscanf(filename, "%lu.vcf", &handle) != 1) return; if (handle > UINT32_MAX) return; /* LastName; FirstName; MiddleName; Prefix; Suffix */ name = g_string_new(""); subproperty = isAPropertyOf(property, VCFamilyNameProp); if (subproperty) { g_string_append(name, fakeCString(vObjectUStringZValue(subproperty))); } subproperty = isAPropertyOf(property, VCGivenNameProp); if (subproperty) g_string_append_printf(name, ";%s", fakeCString(vObjectUStringZValue(subproperty))); subproperty = isAPropertyOf(property, VCAdditionalNamesProp); if (subproperty) g_string_append_printf(name, ";%s", fakeCString(vObjectUStringZValue(subproperty))); subproperty = isAPropertyOf(property, VCNamePrefixesProp); if (subproperty) g_string_append_printf(name, ";%s", fakeCString(vObjectUStringZValue(subproperty))); subproperty = isAPropertyOf(property, VCNameSuffixesProp); if (subproperty) g_string_append_printf(name, ";%s", fakeCString(vObjectUStringZValue(subproperty))); property = isAPropertyOf(v, VCTelephoneProp); tel = property ? fakeCString(vObjectUStringZValue(property)) : NULL; query->entry_cb(filename, handle, name->str, NULL, tel, query->user_data); g_string_free(name, TRUE); } static gboolean create_cache(void *user_data) { struct cache_query *query = user_data; /* * MaxListCount and ListStartOffset shall not be used * when creating the cache. All entries shall be fetched. * PBAP core is responsible for consider these application * parameters before reply the entries. */ foreach_vcard(query->dp, entry_notify, 0, 0xffff, query, NULL); query->ready_cb(query->user_data); return FALSE; } static gboolean read_entry(void *user_data) { struct dummy_data *dummy = user_data; char buffer[1024]; ssize_t count; memset(buffer, 0, sizeof(buffer)); count = read(dummy->fd, buffer, sizeof(buffer)); if (count < 0) { int err = errno; error("read(): %s(%d)", strerror(err), err); count = 0; } /* FIXME: Missing vCards fields filtering */ dummy->cb(buffer, count, 1, 0, TRUE, dummy->user_data); return FALSE; } static gboolean is_dir(const char *dir) { struct stat st; if (stat(dir, &st) < 0) { int err = errno; error("stat(%s): %s (%d)", dir, strerror(err), err); return FALSE; } return S_ISDIR(st.st_mode); } char *phonebook_set_folder(const char *current_folder, const char *new_folder, uint8_t flags, int *err) { gboolean root, child; char *tmp1, *tmp2, *base, *absolute, *relative = NULL; int len, ret = 0; root = (g_strcmp0("/", current_folder) == 0); child = (new_folder && strlen(new_folder) != 0); switch (flags) { case 0x02: /* Go back to root */ if (!child) { relative = g_strdup("/"); goto done; } relative = g_build_filename(current_folder, new_folder, NULL); break; case 0x03: /* Go up 1 level */ if (root) { /* Already root */ ret = -EBADR; goto done; } /* * Removing one level of the current folder. Current folder * contains AT LEAST one level since it is not at root folder. * Use glib utility functions to handle invalid chars in the * folder path properly. */ tmp1 = g_path_get_basename(current_folder); tmp2 = g_strrstr(current_folder, tmp1); len = tmp2 - (current_folder + 1); g_free(tmp1); if (len == 0) base = g_strdup("/"); else base = g_strndup(current_folder, len); /* Return: one level only */ if (!child) { relative = base; goto done; } relative = g_build_filename(base, new_folder, NULL); g_free(base); break; default: ret = -EBADR; break; } done: if (!relative) { if (err) *err = ret; return NULL; } absolute = g_build_filename(root_folder, relative, NULL); if (!is_dir(absolute)) { g_free(relative); relative = NULL; ret = -ENOENT; } g_free(absolute); if (err) *err = ret; return relative; } void phonebook_req_finalize(void *request) { struct dummy_data *dummy = request; /* dummy_data will be cleaned when request will be finished via * g_source_remove */ if (dummy && dummy->id) g_source_remove(dummy->id); } void *phonebook_pull(const char *name, const struct apparam_field *params, phonebook_cb cb, void *user_data, int *err) { struct dummy_data *dummy; char *filename, *folder; /* * Main phonebook objects will be created dynamically based on the * folder content. All vcards inside the given folder will be appended * in the "virtual" main phonebook object. */ filename = g_build_filename(root_folder, name, NULL); if (!g_str_has_suffix(filename, ".vcf")) { g_free(filename); if (err) *err = -EBADR; return NULL; } folder = g_strndup(filename, strlen(filename) - 4); g_free(filename); if (!is_dir(folder)) { g_free(folder); if (err) *err = -ENOENT; return NULL; } dummy = g_new0(struct dummy_data, 1); dummy->cb = cb; dummy->user_data = user_data; dummy->apparams = params; dummy->folder = folder; dummy->fd = -1; if (err) *err = 0; return dummy; } int phonebook_pull_read(void *request) { struct dummy_data *dummy = request; if (!dummy) return -ENOENT; dummy->id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, read_dir, dummy, dummy_free); return 0; } void *phonebook_get_entry(const char *folder, const char *id, const struct apparam_field *params, phonebook_cb cb, void *user_data, int *err) { struct dummy_data *dummy; char *filename; int fd; filename = g_build_filename(root_folder, folder, id, NULL); fd = open(filename, O_RDONLY); g_free(filename); if (fd < 0) { DBG("open(): %s(%d)", strerror(errno), errno); if (err) *err = -ENOENT; return NULL; } dummy = g_new0(struct dummy_data, 1); dummy->cb = cb; dummy->user_data = user_data; dummy->apparams = params; dummy->fd = fd; dummy->id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, read_entry, dummy, dummy_free); if (err) *err = 0; return dummy; } void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb, phonebook_cache_ready_cb ready_cb, void *user_data, int *err) { struct cache_query *query; char *foldername; DIR *dp; struct dummy_data *dummy; foldername = g_build_filename(root_folder, name, NULL); dp = opendir(foldername); g_free(foldername); if (dp == NULL) { DBG("opendir(): %s(%d)", strerror(errno), errno); if (err) *err = -ENOENT; return NULL; } query = g_new0(struct cache_query, 1); query->entry_cb = entry_cb; query->ready_cb = ready_cb; query->user_data = user_data; query->dp = dp; dummy = g_new0(struct dummy_data, 1); dummy->id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, create_cache, query, query_free); if (err) *err = 0; return dummy; } bluez-5.87/obexd/plugins/PaxHeaders/filesystem.c0000644000000000000000000000005014643061455016775 xustar0020 atime=1783093852 20 ctime=1783094104 bluez-5.87/obexd/plugins/filesystem.c0000644000000000000000000003413414643061455016463 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2009-2010 Intel Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "obexd/src/obexd.h" #include "obexd/src/plugin.h" #include "obexd/src/log.h" #include "obexd/src/mimetype.h" #include "filesystem.h" #define EOL_CHARS "\n" #define FL_VERSION "" EOL_CHARS #define FL_TYPE "" EOL_CHARS #define FL_TYPE_PCSUITE " ]>" EOL_CHARS #define FL_BODY_BEGIN "" EOL_CHARS #define FL_BODY_END "" EOL_CHARS #define FL_PARENT_FOLDER_ELEMENT "" EOL_CHARS #define FL_FILE_ELEMENT "" EOL_CHARS #define FL_FOLDER_ELEMENT "" EOL_CHARS #define FL_FOLDER_ELEMENT_PCSUITE "" EOL_CHARS #define FTP_TARGET_SIZE 16 static const uint8_t FTP_TARGET[FTP_TARGET_SIZE] = { 0xF9, 0xEC, 0x7B, 0xC4, 0x95, 0x3C, 0x11, 0xD2, 0x98, 0x4E, 0x52, 0x54, 0x00, 0xDC, 0x9E, 0x09 }; #define PCSUITE_WHO_SIZE 8 static const uint8_t PCSUITE_WHO[PCSUITE_WHO_SIZE] = { 'P', 'C', ' ', 'S', 'u', 'i', 't', 'e' }; gboolean is_filename(const char *name) { if (strchr(name, '/')) return FALSE; if (strcmp(name, ".") == 0) return FALSE; if (strcmp(name, "..") == 0) return FALSE; return TRUE; } int verify_path(const char *path) { char *t; int ret = 0; if (obex_option_symlinks()) return 0; t = realpath(path, NULL); if (t == NULL) return -errno; if (!g_str_has_prefix(t, obex_option_root_folder())) ret = -EPERM; free(t); return ret; } static char *file_stat_line(char *filename, struct stat *fstat, struct stat *dstat, gboolean root, gboolean pcsuite) { char perm[51], atime[18], ctime[18], mtime[18]; char *escaped, *ret = NULL; struct tm a_gmtime, c_gmtime, m_gmtime; snprintf(perm, 50, "user-perm=\"%s%s%s\" group-perm=\"%s%s%s\" " "other-perm=\"%s%s%s\"", (fstat->st_mode & 0400 ? "R" : ""), (fstat->st_mode & 0200 ? "W" : ""), (dstat->st_mode & 0200 ? "D" : ""), (fstat->st_mode & 0040 ? "R" : ""), (fstat->st_mode & 0020 ? "W" : ""), (dstat->st_mode & 0020 ? "D" : ""), (fstat->st_mode & 0004 ? "R" : ""), (fstat->st_mode & 0002 ? "W" : ""), (dstat->st_mode & 0002 ? "D" : "")); if (!gmtime_r(&fstat->st_atime, &a_gmtime) || !gmtime_r(&fstat->st_ctime, &c_gmtime) || !gmtime_r(&fstat->st_mtime, &m_gmtime)) { error("gmtime_r() returned NULL"); return ret; } strftime(atime, 17, "%Y%m%dT%H%M%SZ", &a_gmtime); strftime(ctime, 17, "%Y%m%dT%H%M%SZ", &c_gmtime); strftime(mtime, 17, "%Y%m%dT%H%M%SZ", &m_gmtime); escaped = g_markup_escape_text(filename, -1); if (S_ISDIR(fstat->st_mode)) { if (pcsuite && root && g_str_equal(filename, "Data")) ret = g_strdup_printf(FL_FOLDER_ELEMENT_PCSUITE, escaped, perm, atime, mtime, ctime); else ret = g_strdup_printf(FL_FOLDER_ELEMENT, escaped, perm, atime, mtime, ctime); } else if (S_ISREG(fstat->st_mode)) ret = g_strdup_printf(FL_FILE_ELEMENT, escaped, (uint64_t) fstat->st_size, perm, atime, mtime, ctime); g_free(escaped); return ret; } static void *filesystem_open(const char *name, int oflag, mode_t mode, void *context, size_t *size, int *err) { struct stat stats; struct statvfs buf; int fd, ret; uint64_t avail; fd = open(name, oflag, mode); if (fd < 0) { if (err) *err = -errno; return NULL; } if (fstat(fd, &stats) < 0) { if (err) *err = -errno; goto failed; } ret = verify_path(name); if (ret < 0) { if (err) *err = ret; goto failed; } if (oflag == O_RDONLY) { if (size) *size = stats.st_size; goto done; } if (fstatvfs(fd, &buf) < 0) { if (err) *err = -errno; goto failed; } if (size == NULL) goto done; avail = (uint64_t) buf.f_bsize * buf.f_bavail; if (avail < *size) { if (err) *err = -ENOSPC; goto failed; } done: if (err) *err = 0; return GINT_TO_POINTER(fd); failed: close(fd); return NULL; } static int filesystem_close(void *object) { if (close(GPOINTER_TO_INT(object)) < 0) return -errno; return 0; } static ssize_t filesystem_read(void *object, void *buf, size_t count) { ssize_t ret; ret = read(GPOINTER_TO_INT(object), buf, count); if (ret < 0) return -errno; return ret; } static ssize_t filesystem_write(void *object, const void *buf, size_t count) { ssize_t ret; ret = write(GPOINTER_TO_INT(object), buf, count); if (ret < 0) return -errno; return ret; } static int filesystem_rename(const char *name, const char *destname) { int ret; ret = rename(name, destname); if (ret < 0) { error("rename(%s, %s): %s (%d)", name, destname, strerror(errno), errno); return -errno; } return ret; } static int sendfile_async(int out_fd, int in_fd, off_t *offset, size_t count) { int pid; /* Run sendfile on child process */ pid = fork(); switch (pid) { case 0: break; case -1: error("fork() %s (%d)", strerror(errno), errno); return -errno; default: DBG("child %d forked", pid); return pid; } /* At child */ if (sendfile(out_fd, in_fd, offset, count) < 0) error("sendfile(): %s (%d)", strerror(errno), errno); close(in_fd); close(out_fd); exit(errno); } static int filesystem_copy(const char *name, const char *destname) { void *in, *out; ssize_t ret; size_t size; struct stat st; int in_fd, out_fd, err; in = filesystem_open(name, O_RDONLY, 0, NULL, &size, &err); if (in == NULL) { error("open(%s): %s (%d)", name, strerror(-err), -err); return -err; } in_fd = GPOINTER_TO_INT(in); ret = fstat(in_fd, &st); if (ret < 0) { error("stat(%s): %s (%d)", name, strerror(errno), errno); return -errno; } out = filesystem_open(destname, O_WRONLY | O_CREAT | O_TRUNC, st.st_mode, NULL, &size, &err); if (out == NULL) { error("open(%s): %s (%d)", destname, strerror(-err), -err); filesystem_close(in); return -errno; } out_fd = GPOINTER_TO_INT(out); /* Check if sendfile is supported */ ret = sendfile(out_fd, in_fd, NULL, 0); if (ret < 0) { ret = -errno; error("sendfile: %s (%zd)", strerror(-ret), -ret); goto done; } ret = sendfile_async(out_fd, in_fd, NULL, st.st_size); if (ret < 0) goto done; return 0; done: filesystem_close(in); filesystem_close(out); return ret; } struct capability_object { int pid; int output; int err; gboolean aborted; GString *buffer; }; static void script_exited(GPid pid, int status, void *data) { struct capability_object *object = data; char buf[128]; object->pid = -1; DBG("pid: %d status: %d", pid, status); g_spawn_close_pid(pid); /* free the object if aborted */ if (object->aborted) { if (object->buffer != NULL) g_string_free(object->buffer, TRUE); g_free(object); return; } if (WEXITSTATUS(status) != EXIT_SUCCESS) { memset(buf, 0, sizeof(buf)); if (read(object->err, buf, sizeof(buf)) > 0) error("%s", buf); obex_object_set_io_flags(data, G_IO_ERR, -EPERM); } else obex_object_set_io_flags(data, G_IO_IN, 0); } static int capability_exec(const char **argv, int *output, int *err) { GError *gerr = NULL; int pid; GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH; if (!g_spawn_async_with_pipes(NULL, (char **) argv, NULL, flags, NULL, NULL, &pid, NULL, output, err, &gerr)) { error("%s", gerr->message); g_error_free(gerr); return -EPERM; } DBG("executing %s pid %d", argv[0], pid); return pid; } static void *capability_open(const char *name, int oflag, mode_t mode, void *context, size_t *size, int *err) { struct capability_object *object = NULL; char *buf; const char *argv[2]; if (oflag != O_RDONLY) goto fail; object = g_new0(struct capability_object, 1); object->pid = -1; object->output = -1; object->err = -1; if (name[0] != '!') { GError *gerr = NULL; gboolean ret; ret = g_file_get_contents(name, &buf, NULL, &gerr); if (ret == FALSE) { error("%s", gerr->message); g_error_free(gerr); goto fail; } object->buffer = g_string_new(buf); g_free(buf); if (size) *size = object->buffer->len; goto done; } argv[0] = &name[1]; argv[1] = NULL; object->pid = capability_exec(argv, &object->output, &object->err); if (object->pid < 0) goto fail; /* Watch cannot be removed while the process is still running */ g_child_watch_add(object->pid, script_exited, object); done: if (err) *err = 0; return object; fail: if (err) *err = -EPERM; g_free(object); return NULL; } static GString *append_pcsuite_preamble(GString *object) { return g_string_append(object, FL_TYPE_PCSUITE); } static GString *append_folder_preamble(GString *object) { return g_string_append(object, FL_TYPE); } static GString *append_listing(GString *object, const char *name, gboolean pcsuite, size_t *size, int *err) { struct stat fstat, dstat; struct dirent *ep; DIR *dp; gboolean root; int ret; root = g_str_equal(name, obex_option_root_folder()); dp = opendir(name); if (dp == NULL) { if (err) *err = -ENOENT; goto failed; } if (!root) object = g_string_append(object, FL_PARENT_FOLDER_ELEMENT); ret = verify_path(name); if (ret < 0) { *err = ret; goto failed; } ret = stat(name, &dstat); if (ret < 0) { if (err) *err = -errno; goto failed; } while ((ep = readdir(dp))) { char *filename; char *fullname; char *line; if (ep->d_name[0] == '.') continue; filename = g_filename_to_utf8(ep->d_name, -1, NULL, NULL, NULL); if (filename == NULL) { error("g_filename_to_utf8: invalid filename"); continue; } fullname = g_build_filename(name, ep->d_name, NULL); ret = stat(fullname, &fstat); if (ret < 0) { DBG("stat: %s(%d)", strerror(errno), errno); g_free(filename); g_free(fullname); continue; } g_free(fullname); line = file_stat_line(filename, &fstat, &dstat, root, FALSE); if (line == NULL) { g_free(filename); continue; } g_free(filename); object = g_string_append(object, line); g_free(line); } closedir(dp); object = g_string_append(object, FL_BODY_END); if (size) *size = object->len; if (err) *err = 0; return object; failed: if (dp) closedir(dp); g_string_free(object, TRUE); return NULL; } static void *folder_open(const char *name, int oflag, mode_t mode, void *context, size_t *size, int *err) { GString *object; object = g_string_new(FL_VERSION); object = append_folder_preamble(object); object = g_string_append(object, FL_BODY_BEGIN); return append_listing(object, name, FALSE, size, err); } static void *pcsuite_open(const char *name, int oflag, mode_t mode, void *context, size_t *size, int *err) { GString *object; object = g_string_new(FL_VERSION); object = append_pcsuite_preamble(object); object = g_string_append(object, FL_BODY_BEGIN); return append_listing(object, name, TRUE, size, err); } static int string_free(void *object) { GString *string = object; g_string_free(string, TRUE); return 0; } ssize_t string_read(void *object, void *buf, size_t count) { GString *string = object; ssize_t len; if (string->len == 0) return 0; len = MIN(string->len, count); memcpy(buf, string->str, len); g_string_erase(string, 0, len); return len; } static ssize_t folder_read(void *object, void *buf, size_t count) { return string_read(object, buf, count); } static ssize_t capability_read(void *object, void *buf, size_t count) { struct capability_object *obj = object; if (obj->buffer) return string_read(obj->buffer, buf, count); if (obj->pid >= 0) return -EAGAIN; return read(obj->output, buf, count); } static int capability_close(void *object) { struct capability_object *obj = object; int err = 0; if (obj->pid < 0) goto done; DBG("kill: pid %d", obj->pid); err = kill(obj->pid, SIGTERM); if (err < 0) { err = -errno; error("kill: %s (%d)", strerror(-err), -err); goto done; } obj->aborted = TRUE; return 0; done: if (obj->buffer != NULL) g_string_free(obj->buffer, TRUE); g_free(obj); return err; } static const struct obex_mime_type_driver file = { .open = filesystem_open, .close = filesystem_close, .read = filesystem_read, .write = filesystem_write, .remove = remove, .move = filesystem_rename, .copy = filesystem_copy, }; static const struct obex_mime_type_driver capability = { .target = FTP_TARGET, .target_size = FTP_TARGET_SIZE, .mimetype = "x-obex/capability", .open = capability_open, .close = capability_close, .read = capability_read, }; static const struct obex_mime_type_driver folder = { .target = FTP_TARGET, .target_size = FTP_TARGET_SIZE, .mimetype = "x-obex/folder-listing", .open = folder_open, .close = string_free, .read = folder_read, }; static const struct obex_mime_type_driver pcsuite = { .target = FTP_TARGET, .target_size = FTP_TARGET_SIZE, .who = PCSUITE_WHO, .who_size = PCSUITE_WHO_SIZE, .mimetype = "x-obex/folder-listing", .open = pcsuite_open, .close = string_free, .read = folder_read, }; static int filesystem_init(void) { int err; err = obex_mime_type_driver_register(&folder); if (err < 0) return err; err = obex_mime_type_driver_register(&capability); if (err < 0) return err; err = obex_mime_type_driver_register(&pcsuite); if (err < 0) return err; return obex_mime_type_driver_register(&file); } static void filesystem_exit(void) { obex_mime_type_driver_unregister(&folder); obex_mime_type_driver_unregister(&capability); obex_mime_type_driver_unregister(&file); } OBEX_PLUGIN_DEFINE(filesystem, filesystem_init, filesystem_exit) bluez-5.87/obexd/plugins/PaxHeaders/mas.c0000644000000000000000000000005015063045254015365 xustar0020 atime=1783093867 20 ctime=1783094104 bluez-5.87/obexd/plugins/mas.c0000644000000000000000000005062715063045254015060 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2010-2011 Nokia Corporation * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "gobex/gobex.h" #include "gobex/gobex-apparam.h" #include "obexd/src/obexd.h" #include "obexd/src/plugin.h" #include "obexd/src/log.h" #include "obexd/src/obex.h" #include "obexd/src/service.h" #include "obexd/src/mimetype.h" #include "obexd/src/manager.h" #include "obexd/src/map_ap.h" #include "filesystem.h" #include "messages.h" #define READ_STATUS_REQ 0 #define DELETE_STATUS_REQ 1 #define XML_DECL "" /* Building blocks for x-obex/folder-listing */ #define FL_DTD "" #define FL_BODY_BEGIN "" #define FL_BODY_EMPTY "" #define FL_PARENT_FOLDER_ELEMENT "" #define FL_FOLDER_ELEMENT "" #define FL_BODY_END "" #define ML_BODY_BEGIN "" #define ML_BODY_END "" struct mas_session { struct mas_request *request; void *backend_data; gboolean finished; gboolean nth_call; GString *buffer; GObexApparam *inparams; GObexApparam *outparams; gboolean ap_sent; uint8_t notification_status; }; static const uint8_t MAS_TARGET[TARGET_SIZE] = { 0xbb, 0x58, 0x2b, 0x40, 0x42, 0x0c, 0x11, 0xdb, 0xb0, 0xde, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 }; static int get_params(struct obex_session *os, struct mas_session *mas) { const uint8_t *buffer; ssize_t size; size = obex_get_apparam(os, &buffer); if (size <= 0) return 0; mas->inparams = g_obex_apparam_decode(buffer, size); if (mas->inparams == NULL) { DBG("Error when parsing parameters!"); return -EBADR; } return 0; } static void reset_request(struct mas_session *mas) { if (mas->buffer) { g_string_free(mas->buffer, TRUE); mas->buffer = NULL; } if (mas->inparams) { g_obex_apparam_free(mas->inparams); mas->inparams = NULL; } if (mas->outparams) { g_obex_apparam_free(mas->outparams); mas->outparams = NULL; } mas->nth_call = FALSE; mas->finished = FALSE; mas->ap_sent = FALSE; } static void mas_clean(struct mas_session *mas) { reset_request(mas); g_free(mas); } static void *mas_connect(struct obex_session *os, int *err) { struct mas_session *mas; DBG(""); mas = g_new0(struct mas_session, 1); *err = messages_connect(&mas->backend_data); if (*err < 0) goto failed; manager_register_session(os); return mas; failed: g_free(mas); return NULL; } static void mas_disconnect(struct obex_session *os, void *user_data) { struct mas_session *mas = user_data; DBG(""); manager_unregister_session(os); messages_disconnect(mas->backend_data); mas_clean(mas); } static int mas_get(struct obex_session *os, void *user_data) { struct mas_session *mas = user_data; const char *type = obex_get_type(os); const char *name = obex_get_name(os); int ret; DBG("GET: name %s type %s mas %p", name, type, mas); if (type == NULL) return -EBADR; ret = get_params(os, mas); if (ret < 0) goto failed; ret = obex_get_stream_start(os, name); if (ret < 0) goto failed; return 0; failed: reset_request(mas); return ret; } static int mas_put(struct obex_session *os, void *user_data) { struct mas_session *mas = user_data; const char *type = obex_get_type(os); const char *name = obex_get_name(os); int ret; DBG("PUT: name %s type %s mas %p", name, type, mas); if (type == NULL) return -EBADR; ret = get_params(os, mas); if (ret < 0) goto failed; ret = obex_put_stream_start(os, name); if (ret < 0) goto failed; return 0; failed: reset_request(mas); return ret; } /* FIXME: Preserve whitespaces */ static void g_string_append_escaped_printf(GString *string, const char *format, ...) { va_list ap; char *escaped; va_start(ap, format); escaped = g_markup_vprintf_escaped(format, ap); g_string_append(string, escaped); g_free(escaped); va_end(ap); } static gchar *get_mse_timestamp(void) { struct timeval time_val; struct tm ltime; gchar *local_ts; char sign; if (gettimeofday(&time_val, NULL) < 0) return NULL; if (!localtime_r(&time_val.tv_sec, <ime)) return NULL; if (difftime(mktime(localtime(&time_val.tv_sec)), mktime(gmtime(&time_val.tv_sec))) < 0) sign = '+'; else sign = '-'; local_ts = g_strdup_printf("%04d%02d%02dT%02d%02d%02d%c%2ld%2ld", ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday, ltime.tm_hour, ltime.tm_min, ltime.tm_sec, sign, ltime.tm_gmtoff/3600, (ltime.tm_gmtoff%3600)/60); return local_ts; } static const char *yesorno(gboolean a) { if (a) return "yes"; return "no"; } static void get_messages_listing_cb(void *session, int err, uint16_t size, gboolean newmsg, const struct messages_message *entry, void *user_data) { struct mas_session *mas = user_data; uint16_t max = 1024; gchar *mse_time; if (err < 0 && err != -EAGAIN) { obex_object_set_io_flags(mas, G_IO_ERR, err); return; } if (mas->inparams) g_obex_apparam_get_uint16(mas->inparams, MAP_AP_MAXLISTCOUNT, &max); if (max == 0) { if (!entry) mas->finished = TRUE; goto proceed; } if (!mas->nth_call) { g_string_append(mas->buffer, ML_BODY_BEGIN); mas->nth_call = TRUE; } if (!entry) { g_string_append(mas->buffer, ML_BODY_END); mas->finished = TRUE; goto proceed; } g_string_append(mas->buffer, "buffer, " handle=\"%s\"", entry->handle); if (entry->mask & PMASK_SUBJECT) g_string_append_escaped_printf(mas->buffer, " subject=\"%s\"", entry->subject); if (entry->mask & PMASK_DATETIME) g_string_append_escaped_printf(mas->buffer, " datetime=\"%s\"", entry->datetime); if (entry->mask & PMASK_SENDER_NAME) g_string_append_escaped_printf(mas->buffer, " sender_name=\"%s\"", entry->sender_name); if (entry->mask & PMASK_SENDER_ADDRESSING) g_string_append_escaped_printf(mas->buffer, " sender_addressing=\"%s\"", entry->sender_addressing); if (entry->mask & PMASK_REPLYTO_ADDRESSING) g_string_append_escaped_printf(mas->buffer, " replyto_addressing=\"%s\"", entry->replyto_addressing); if (entry->mask & PMASK_RECIPIENT_NAME) g_string_append_escaped_printf(mas->buffer, " recipient_name=\"%s\"", entry->recipient_name); if (entry->mask & PMASK_RECIPIENT_ADDRESSING) g_string_append_escaped_printf(mas->buffer, " recipient_addressing=\"%s\"", entry->recipient_addressing); if (entry->mask & PMASK_TYPE) g_string_append_escaped_printf(mas->buffer, " type=\"%s\"", entry->type); if (entry->mask & PMASK_RECEPTION_STATUS) g_string_append_escaped_printf(mas->buffer, " reception_status=\"%s\"", entry->reception_status); if (entry->mask & PMASK_SIZE) g_string_append_escaped_printf(mas->buffer, " size=\"%s\"", entry->size); if (entry->mask & PMASK_ATTACHMENT_SIZE) g_string_append_escaped_printf(mas->buffer, " attachment_size=\"%s\"", entry->attachment_size); if (entry->mask & PMASK_TEXT) g_string_append_escaped_printf(mas->buffer, " text=\"%s\"", yesorno(entry->text)); if (entry->mask & PMASK_READ) g_string_append_escaped_printf(mas->buffer, " read=\"%s\"", yesorno(entry->read)); if (entry->mask & PMASK_SENT) g_string_append_escaped_printf(mas->buffer, " sent=\"%s\"", yesorno(entry->sent)); if (entry->mask & PMASK_PROTECTED) g_string_append_escaped_printf(mas->buffer, " protected=\"%s\"", yesorno(entry->protect)); if (entry->mask & PMASK_PRIORITY) g_string_append_escaped_printf(mas->buffer, " priority=\"%s\"", yesorno(entry->priority)); g_string_append(mas->buffer, "/>\n"); proceed: if (!entry) { mas->outparams = g_obex_apparam_set_uint16(mas->outparams, MAP_AP_MESSAGESLISTINGSIZE, size); mas->outparams = g_obex_apparam_set_uint8(mas->outparams, MAP_AP_NEWMESSAGE, newmsg ? 1 : 0); /* Response to report the local time of MSE */ mse_time = get_mse_timestamp(); if (mse_time) { g_obex_apparam_set_string(mas->outparams, MAP_AP_MSETIME, mse_time); g_free(mse_time); } } if (err != -EAGAIN) obex_object_set_io_flags(mas, G_IO_IN, 0); } static void get_message_cb(void *session, int err, gboolean fmore, const char *chunk, void *user_data) { struct mas_session *mas = user_data; DBG(""); if (err < 0 && err != -EAGAIN) { obex_object_set_io_flags(mas, G_IO_ERR, err); return; } if (!chunk) { mas->finished = TRUE; goto proceed; } g_string_append(mas->buffer, chunk); mas->finished = TRUE; proceed: if (err != -EAGAIN) obex_object_set_io_flags(mas, G_IO_IN, 0); } static void get_folder_listing_cb(void *session, int err, uint16_t size, const char *name, void *user_data) { struct mas_session *mas = user_data; uint16_t max = 1024; if (err < 0 && err != -EAGAIN) { obex_object_set_io_flags(mas, G_IO_ERR, err); return; } if (mas->inparams) g_obex_apparam_get_uint16(mas->inparams, MAP_AP_MAXLISTCOUNT, &max); if (max == 0) { if (err != -EAGAIN) mas->outparams = g_obex_apparam_set_uint16( mas->outparams, MAP_AP_FOLDERLISTINGSIZE, size); if (!name) mas->finished = TRUE; goto proceed; } if (!mas->nth_call) { g_string_append(mas->buffer, XML_DECL); g_string_append(mas->buffer, FL_DTD); if (!name) { g_string_append(mas->buffer, FL_BODY_EMPTY); mas->finished = TRUE; goto proceed; } g_string_append(mas->buffer, FL_BODY_BEGIN); mas->nth_call = TRUE; } if (!name) { g_string_append(mas->buffer, FL_BODY_END); mas->finished = TRUE; goto proceed; } if (g_strcmp0(name, "..") == 0) g_string_append(mas->buffer, FL_PARENT_FOLDER_ELEMENT); else g_string_append_escaped_printf(mas->buffer, FL_FOLDER_ELEMENT, name); proceed: if (err != -EAGAIN) obex_object_set_io_flags(mas, G_IO_IN, err); } static void set_status_cb(void *session, int err, void *user_data) { struct mas_session *mas = user_data; DBG(""); mas->finished = TRUE; if (err < 0) obex_object_set_io_flags(mas, G_IO_ERR, err); else obex_object_set_io_flags(mas, G_IO_OUT, 0); } static int mas_setpath(struct obex_session *os, void *user_data) { const char *name; const uint8_t *nonhdr; struct mas_session *mas = user_data; if (obex_get_non_header_data(os, &nonhdr) != 2) { error("Set path failed: flag and constants not found!"); return -EBADR; } name = obex_get_name(os); DBG("SETPATH: name %s nonhdr 0x%x%x", name, nonhdr[0], nonhdr[1]); if ((nonhdr[0] & 0x02) != 0x02) { DBG("Error: requested directory creation"); return -EBADR; } return messages_set_folder(mas->backend_data, name, nonhdr[0] & 0x01); } static void *folder_listing_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { struct mas_session *mas = driver_data; /* 1024 is the default when there was no MaxListCount sent */ uint16_t max = 1024; uint16_t offset = 0; if (oflag != O_RDONLY) { *err = -EBADR; return NULL; } DBG("name = %s", name); if (mas->inparams) { g_obex_apparam_get_uint16(mas->inparams, MAP_AP_MAXLISTCOUNT, &max); g_obex_apparam_get_uint16(mas->inparams, MAP_AP_STARTOFFSET, &offset); } *err = messages_get_folder_listing(mas->backend_data, name, max, offset, get_folder_listing_cb, mas); mas->buffer = g_string_new(""); if (*err < 0) return NULL; else return mas; } static void *msg_listing_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { struct mas_session *mas = driver_data; struct messages_filter filter = { 0, }; /* 1024 is the default when there was no MaxListCount sent */ uint16_t max = 1024; uint16_t offset = 0; /* If MAP client does not specify the subject length, then subject_len = 0 and subject should be sent unaltered. */ uint8_t subject_len = 0; DBG(""); if (oflag != O_RDONLY) { *err = -EBADR; return NULL; } if (!mas->inparams) goto done; g_obex_apparam_get_uint16(mas->inparams, MAP_AP_MAXLISTCOUNT, &max); g_obex_apparam_get_uint16(mas->inparams, MAP_AP_STARTOFFSET, &offset); g_obex_apparam_get_uint8(mas->inparams, MAP_AP_SUBJECTLENGTH, &subject_len); g_obex_apparam_get_uint32(mas->inparams, MAP_AP_PARAMETERMASK, &filter.parameter_mask); g_obex_apparam_get_uint8(mas->inparams, MAP_AP_FILTERMESSAGETYPE, &filter.type); filter.period_begin = g_obex_apparam_get_string(mas->inparams, MAP_AP_FILTERPERIODBEGIN); filter.period_end = g_obex_apparam_get_string(mas->inparams, MAP_AP_FILTERPERIODEND); g_obex_apparam_get_uint8(mas->inparams, MAP_AP_FILTERREADSTATUS, &filter.read_status); filter.recipient = g_obex_apparam_get_string(mas->inparams, MAP_AP_FILTERRECIPIENT); filter.originator = g_obex_apparam_get_string(mas->inparams, MAP_AP_FILTERORIGINATOR); g_obex_apparam_get_uint8(mas->inparams, MAP_AP_FILTERPRIORITY, &filter.priority); done: *err = messages_get_messages_listing(mas->backend_data, name, max, offset, subject_len, &filter, get_messages_listing_cb, mas); mas->buffer = g_string_new(""); if (*err < 0) return NULL; else return mas; } static void *message_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { struct mas_session *mas = driver_data; DBG(""); if (oflag != O_RDONLY) { DBG("Message pushing unsupported"); *err = -ENOSYS; return NULL; } mas->buffer = g_string_new(""); *err = messages_get_message(mas->backend_data, name, 0, get_message_cb, mas); if (*err < 0) return NULL; else return mas; } static void *message_update_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { struct mas_session *mas = driver_data; DBG(""); if (oflag == O_RDONLY) { *err = -EBADR; return NULL; } *err = messages_update_inbox(mas->backend_data, set_status_cb, mas); if (*err < 0) return NULL; else return mas; } static void *message_set_status_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { struct mas_session *mas = driver_data; uint8_t indicator; uint8_t value; DBG(""); if (oflag == O_RDONLY) { *err = -EBADR; return NULL; } if (!g_obex_apparam_get_uint8(mas->inparams, MAP_AP_STATUSINDICATOR, &indicator)) { *err = -EBADR; return NULL; } if (!g_obex_apparam_get_uint8(mas->inparams, MAP_AP_STATUSVALUE, &value)) { *err = -EBADR; return NULL; } if (indicator == READ_STATUS_REQ) *err = messages_set_read(mas->backend_data, name, value, set_status_cb, mas); else if (indicator == DELETE_STATUS_REQ) *err = messages_set_delete(mas->backend_data, name, value, set_status_cb, mas); else *err = -EBADR; if (*err < 0) return NULL; return mas; } static ssize_t any_get_next_header(void *object, void *buf, size_t mtu, uint8_t *hi) { struct mas_session *mas = object; DBG(""); if (mas->buffer->len == 0 && !mas->finished) return -EAGAIN; *hi = G_OBEX_HDR_APPARAM; if (mas->ap_sent) return 0; mas->ap_sent = TRUE; if (!mas->outparams) return 0; return g_obex_apparam_encode(mas->outparams, buf, mtu); } static void *any_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { DBG(""); *err = -ENOSYS; return NULL; } static ssize_t any_write(void *object, const void *buf, size_t count) { DBG(""); return count; } static ssize_t any_read(void *obj, void *buf, size_t count) { struct mas_session *mas = obj; ssize_t len; DBG(""); len = string_read(mas->buffer, buf, count); if (len == 0 && !mas->finished) return -EAGAIN; return len; } static int any_close(void *obj) { struct mas_session *mas = obj; DBG(""); if (!mas->finished) messages_abort(mas->backend_data); reset_request(mas); return 0; } static void *notification_registration_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { struct mas_session *mas = driver_data; uint8_t status; DBG(""); if (O_RDONLY) { *err = -EBADR; return NULL; } if (!g_obex_apparam_get_uint8(mas->inparams, MAP_AP_NOTIFICATIONSTATUS, &status)) { *err = -EBADR; return NULL; } mas->notification_status = status; mas->finished = TRUE; *err = 0; return mas; } static void *message_get_instance_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { struct mas_session *mas = driver_data; DBG(""); mas->buffer = g_string_new("Mas Instance 0"); mas->finished = TRUE; *err = 0; return mas; } static void *message_notification_filter_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { struct mas_session *mas = driver_data; DBG(""); /* TODO notification filter add */ mas->finished = TRUE; *err = 0; return mas; } static const struct obex_service_driver mas = { .name = "Message Access server", .service = OBEX_MAS, .target = MAS_TARGET, .target_size = TARGET_SIZE, .connect = mas_connect, .get = mas_get, .put = mas_put, .setpath = mas_setpath, .disconnect = mas_disconnect, }; static const struct obex_mime_type_driver mime_map = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = NULL, .open = any_open, .close = any_close, .read = any_read, .write = any_write, }; static const struct obex_mime_type_driver mime_message = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/message", .open = message_open, .close = any_close, .read = any_read, .write = any_write, }; static const struct obex_mime_type_driver mime_folder_listing = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-obex/folder-listing", .get_next_header = any_get_next_header, .open = folder_listing_open, .close = any_close, .read = any_read, .write = any_write, }; static const struct obex_mime_type_driver mime_msg_listing = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/MAP-msg-listing", .get_next_header = any_get_next_header, .open = msg_listing_open, .close = any_close, .read = any_read, .write = any_write, }; static const struct obex_mime_type_driver mime_notification_registration = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/MAP-NotificationRegistration", .open = notification_registration_open, .close = any_close, .read = any_read, .write = any_write, }; static const struct obex_mime_type_driver mime_message_status = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/messageStatus", .open = message_set_status_open, .close = any_close, .read = any_read, .write = any_write, }; static const struct obex_mime_type_driver mime_message_update = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/MAP-messageUpdate", .open = message_update_open, .close = any_close, .read = any_read, .write = any_write, }; static struct obex_mime_type_driver mime_message_instance = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/MASInstanceInformation", .open = message_get_instance_open, .close = any_close, .read = any_read, .write = any_write, }; static struct obex_mime_type_driver mime_message_notification_filter = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/MAP-notification-filter", .open = message_notification_filter_open, .close = any_close, .read = any_read, .write = any_write, }; static const struct obex_mime_type_driver *map_drivers[] = { &mime_map, &mime_message, &mime_folder_listing, &mime_msg_listing, &mime_notification_registration, &mime_message_status, &mime_message_update, &mime_message_instance, &mime_message_notification_filter, NULL }; static int mas_init(void) { int err; int i; err = messages_init(); if (err < 0) return err; for (i = 0; map_drivers[i] != NULL; ++i) { err = obex_mime_type_driver_register(map_drivers[i]); if (err < 0) goto failed; } err = obex_service_driver_register(&mas); if (err < 0) goto failed; return 0; failed: for (--i; i >= 0; --i) obex_mime_type_driver_unregister(map_drivers[i]); messages_exit(); return err; } static void mas_exit(void) { int i; obex_service_driver_unregister(&mas); for (i = 0; map_drivers[i] != NULL; ++i) obex_mime_type_driver_unregister(map_drivers[i]); messages_exit(); } OBEX_PLUGIN_DEFINE(mas, mas_init, mas_exit) bluez-5.87/obexd/plugins/PaxHeaders/opp.c0000644000000000000000000000005015142177620015404 xustar0020 atime=1783093856 20 ctime=1783094104 bluez-5.87/obexd/plugins/opp.c0000644000000000000000000000676515142177620015103 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2007-2010 Nokia Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "obexd/src/obexd.h" #include "obexd/src/plugin.h" #include "obexd/src/obex.h" #include "obexd/src/service.h" #include "obexd/src/log.h" #include "obexd/src/manager.h" #include "filesystem.h" #define VCARD_TYPE "text/x-vcard" static void *opp_connect(struct obex_session *os, int *err) { manager_register_session(os); if (err) *err = 0; return manager_register_transfer(os); } static void opp_progress(struct obex_session *os, void *user_data) { manager_emit_transfer_progress(user_data); } static int opp_chkput(struct obex_session *os, void *user_data) { char *folder, *name, *path; const char *t; int err; if (obex_get_size(os) == OBJECT_SIZE_DELETE) return -ENOSYS; t = obex_get_name(os); if (t != NULL && !is_filename(t)) return -EBADR; manager_emit_transfer_queued(user_data); if (obex_option_auto_accept()) { folder = g_strdup(obex_option_root_folder()); name = g_strdup(obex_get_name(os)); goto skip_auth; } err = manager_request_authorization(user_data, &folder, &name); if (err < 0) return -EPERM; if (folder == NULL) folder = g_strdup(obex_option_root_folder()); if (name == NULL) name = g_strdup(obex_get_name(os)); skip_auth: if (name == NULL || strlen(name) == 0) { err = -EBADR; goto failed; } if (g_strcmp0(name, obex_get_name(os)) != 0) obex_set_name(os, name); path = g_build_filename(folder, name, NULL); err = obex_put_stream_start(os, path); if (err == 0 && obex_get_size(os) != OBJECT_SIZE_DELETE && obex_get_size(os) != OBJECT_SIZE_UNKNOWN) { manager_emit_transfer_property(user_data, "Size"); } g_free(path); if (err < 0) goto failed; manager_emit_transfer_started(user_data); failed: g_free(folder); g_free(name); return err; } static int opp_put(struct obex_session *os, void *user_data) { const char *name = obex_get_name(os); const char *folder = obex_option_root_folder(); if (folder == NULL) return -EPERM; if (name == NULL) return -EBADR; return 0; } static int opp_get(struct obex_session *os, void *user_data) { const char *type; char *folder, *path; int err = 0; if (obex_get_name(os)) return -EPERM; type = obex_get_type(os); if (type == NULL) return -EPERM; folder = g_strdup(obex_option_root_folder()); path = g_build_filename(folder, "/vcard.vcf", NULL); if (g_ascii_strcasecmp(type, VCARD_TYPE) == 0) { if (obex_get_stream_start(os, path) < 0) err = -ENOENT; } else err = -EPERM; g_free(folder); g_free(path); return err; } static void opp_disconnect(struct obex_session *os, void *user_data) { manager_unregister_transfer(user_data); manager_unregister_session(os); } static void opp_reset(struct obex_session *os, void *user_data) { manager_emit_transfer_completed(user_data); } static const struct obex_service_driver driver = { .name = "Object Push server", .service = OBEX_OPP, .connect = opp_connect, .progress = opp_progress, .disconnect = opp_disconnect, .get = opp_get, .put = opp_put, .chkput = opp_chkput, .reset = opp_reset }; static int opp_init(void) { return obex_service_driver_register(&driver); } static void opp_exit(void) { obex_service_driver_unregister(&driver); } OBEX_PLUGIN_DEFINE(opp, opp_init, opp_exit) bluez-5.87/obexd/plugins/PaxHeaders/messages.h0000644000000000000000000000005015063045254016421 xustar0020 atime=1783093867 20 ctime=1783094104 bluez-5.87/obexd/plugins/messages.h0000644000000000000000000002250415063045254016105 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2010-2011 Nokia Corporation * * */ #include #include /* Those are used by backend to notify transport plugin which properties did it * send. */ #define PMASK_SUBJECT 0x0001 #define PMASK_DATETIME 0x0002 #define PMASK_SENDER_NAME 0x0004 #define PMASK_SENDER_ADDRESSING 0x0008 #define PMASK_RECIPIENT_NAME 0x0010 #define PMASK_RECIPIENT_ADDRESSING 0x0020 #define PMASK_TYPE 0x0040 #define PMASK_SIZE 0x0080 #define PMASK_RECEPTION_STATUS 0x0100 #define PMASK_TEXT 0x0200 #define PMASK_ATTACHMENT_SIZE 0x0400 #define PMASK_PRIORITY 0x0800 #define PMASK_READ 0x1000 #define PMASK_SENT 0x2000 #define PMASK_PROTECTED 0x4000 #define PMASK_REPLYTO_ADDRESSING 0x8000 /* This one is used in a response to GetMessagesListing. Use PMASK_* values to * notify the plugin which members are actually set. Backend shall not omit * properties required by MAP specification (subject, datetime, * recipient_addressing, type, size, reception_status, attachment_size) unless * ordered by PARAMETERMASK. Boolean values should be probably * always sent (need checking). Handle is mandatory. Plugin will filter out any * properties that were not wanted by MCE. * * Handle shall be set to hexadecimal representation with upper-case letters. No * prefix shall be appended and without no zeros. This corresponds to PTS * behaviour described in comments to the MAP specification. * * The rest of char * fields shall be set according to the MAP specification * rules. */ struct messages_message { uint32_t mask; char *handle; char *subject; char *datetime; char *sender_name; char *sender_addressing; char *replyto_addressing; char *recipient_name; char *recipient_addressing; char *type; char *reception_status; char *size; char *attachment_size; gboolean text; gboolean read; gboolean sent; gboolean protect; gboolean priority; }; /* Type of message event to be delivered to MNS server */ enum messages_event_type { MET_NEW_MESSAGE, MET_DELIVERY_SUCCESS, MET_SENDING_SUCCESS, MET_DELIVERY_FAILURE, MET_SENDING_FAILURE, MET_MEMORY_FULL, MET_MEMORY_AVAILABLE, MET_MESSAGE_DELETED, MET_MESSAGE_SHIFT }; /* Data for sending MNS notification. Handle shall be formatted as described in * messages_message. */ struct messages_event { enum messages_event_type type; uint8_t instance_id; char *handle; char *folder; char *old_folder; char *msg_type; }; /* parameter_mask: |-ed PMASK_* values * See MAP specification for the rest. */ struct messages_filter { uint32_t parameter_mask; uint8_t type; const char *period_begin; const char *period_end; uint8_t read_status; const char *recipient; const char *originator; uint8_t priority; }; /* This is called once after server starts. * * Returns value less than zero if error. This will prevent MAP plugin from * starting. */ int messages_init(void); /* This gets called right before server finishes */ void messages_exit(void); /* Starts a new MAP session. * * session: variable to store pointer to backend session data. This one shall be * passed to all in-session calls. * * If session start succeeded, backend shall return 0. Otherwise the error value * will be sent as a response to OBEX connect. */ int messages_connect(void **session); /* Closes a MAP session. * * This call should free buffer reserved by messages_connect. */ void messages_disconnect(void *session); /****************************************************************************** * NOTE on callbacks. * * All functions requiring callbacks have to call them asynchronously. * 'user_data' is for passing arbitrary user data. * * Functions for GetMessagesListing, GetFolder listing and GetMessage call their * callbacks multiple times - one for each listing entry or message body chunk. * To indicate the end of operation backend must call callback with the data * pointer parameter set to NULL. * * If err == -EAGAIN the transport * plugin does not wake IO. * * Keep in mind that application parameters has to be send first. Therefore the * first time err == 0 and thus sending is started, callback will use provided * parameters (e.g. size in case of folder listing) to build applications * parameters header used in response. In any other case those parameters will * be ignored. * * If err != 0 && err != -EAGAIN, the operation is finished immediately and err * value is used to set the error code in OBEX response. ******************************************************************************/ /* Registers for messaging events notifications. * * session: Backend session. * send_event: Function that will be called to indicate a new event. * * To unregister currently registered notifications, call this with send_event * set to NULL. */ int messages_set_notification_registration(void *session, void (*send_event)(void *session, const struct messages_event *event, void *user_data), void *user_data); /* Changes current directory. * * session: Backend session. * name: Subdirectory to go to. If empty or null and cdup is false, go to the * root directory. * cdup: If true, go up one level first. */ int messages_set_folder(void *session, const char *name, gboolean cdup); /* Retrieves subdirectories listing from a current directory. * * session: Backend session. * name: Optional subdirectory name (not strictly required by MAP). * max: Maximum number of entries to retrieve. * offset: Offset of the first entry. * size: Total size of listing to be returned. * * Callback shall be called for every entry of the listing. 'name' is the * subdirectory name. */ typedef void (*messages_folder_listing_cb)(void *session, int err, uint16_t size, const char *name, void *user_data); int messages_get_folder_listing(void *session, const char *name, uint16_t max, uint16_t offset, messages_folder_listing_cb callback, void *user_data); /* Retrieves messages listing from a current directory. * * session: Backend session. * name: Optional subdirectory name. * max: Maximum number of entries to retrieve. * offset: Offset of the first entry. * subject_len: Maximum string length of the "subject" parameter in the entries. * filter: Filter to apply on returned message listing. * size: Total size of listing to be returned. * newmsg: Indicates presence of unread messages. * * Callback shall be called for every entry of the listing, giving message data * in 'message'. */ typedef void (*messages_get_messages_listing_cb)(void *session, int err, uint16_t size, gboolean newmsg, const struct messages_message *message, void *user_data); int messages_get_messages_listing(void *session, const char *name, uint16_t max, uint16_t offset, uint8_t subject_len, const struct messages_filter *filter, messages_get_messages_listing_cb callback, void *user_data); #define MESSAGES_ATTACHMENT (1 << 0) #define MESSAGES_UTF8 (1 << 1) #define MESSAGES_FRACTION (1 << 2) #define MESSAGES_NEXT (1 << 3) /* Retrieves bMessage object (see MAP specification, ch. 3.1.3) of a given * message. * * session: Backend session. * handle: Handle of the message to retrieve. * flags: or-ed mask of following: * MESSAGES_ATTACHMENT: Selects whether or not attachments (if any) are to * be included. * MESSAGES_UTF8: If true, convert message to utf-8. Otherwise use native * encoding. * MESSAGES_FRACTION: If true, deliver fractioned message. * MESSAGES_NEXT: If fraction is true this indicates whether to retrieve * first fraction * or the next one. * fmore: Indicates whether next fraction is available. * chunk: chunk of bMessage body * * Callback allows for returning bMessage in chunks. */ typedef void (*messages_get_message_cb)(void *session, int err, gboolean fmore, const char *chunk, void *user_data); int messages_get_message(void *session, const char *handle, unsigned long flags, messages_get_message_cb callback, void *user_data); typedef void (*messages_status_cb)(void *session, int err, void *user_data); /* Informs Message Server to Update Inbox via network. * * session: Backend session. * user_data: User data if any to be sent. * Callback shall be called for every update inbox request received from MCE. */ int messages_update_inbox(void *session, messages_status_cb callback, void *user_data); /* Informs Message Server to modify read status of a given message. * * session: Backend session. * handle: Unique identifier to the message. * value: Indicates the new value of the read status for a given message. * Callback shall be called for every read status update request * received from MCE. * user_data: User data if any to be sent. */ int messages_set_read(void *session, const char *handle, uint8_t value, messages_status_cb callback, void *user_data); /* Informs Message Server to modify delete status of a given message. * * session: Backend session. * handle: Unique identifier to the message. * value: Indicates the new value of the delete status for a given message. * Callback shall be called for every delete status update request * received from MCE. * user_data: User data if any to be sent. */ int messages_set_delete(void *session, const char *handle, uint8_t value, messages_status_cb callback, void *user_data); /* Aborts currently pending request. * * session: Backend session. */ void messages_abort(void *session); bluez-5.87/obexd/plugins/PaxHeaders/phonebook.h0000644000000000000000000000005014015011623016563 xustar0020 atime=1783093859 20 ctime=1783094104 bluez-5.87/obexd/plugins/phonebook.h0000644000000000000000000001254714015011623016255 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ #define EOL "\r\n" #define VCARD_LISTING_BEGIN \ "" EOL\ "" EOL\ "" EOL #define VCARD_LISTING_ELEMENT "" EOL #define VCARD_LISTING_END "" #define PB_TELECOM_FOLDER "/telecom" #define PB_CONTACTS_FOLDER "/telecom/pb" #define PB_CALENDAR_FOLDER "/telecom/cal" #define PB_NOTES_FOLDER "/telecom/nt" #define PB_CALLS_COMBINED_FOLDER "/telecom/cch" #define PB_CALLS_INCOMING_FOLDER "/telecom/ich" #define PB_CALLS_MISSED_FOLDER "/telecom/mch" #define PB_CALLS_OUTGOING_FOLDER "/telecom/och" #define PB_CALLS_SPEEDDIAL_FOLDER "/telecom/spd" #define PB_CALLS_FAVORITE_FOLDER "/telecom/fav" #define PB_LUID_FOLDER "/telecom/pb/luid" #define PB_CONTACTS "/telecom/pb.vcf" #define PB_CALLS_COMBINED "/telecom/cch.vcf" #define PB_CALLS_INCOMING "/telecom/ich.vcf" #define PB_CALLS_MISSED "/telecom/mch.vcf" #define PB_CALLS_OUTGOING "/telecom/och.vcf" #define PB_CALLS_SPEEDDIAL "/telecom/spd.vcf" #define PB_CALLS_FAVORITE "/telecom/fav.vcf" #define PB_DEVINFO "/telecom/devinfo.txt" #define PB_INFO_LOG "/telecom/pb/info.log" #define PB_CC_LOG "/telecom/pb/luid/cc.log" struct apparam_field { /* list and pull attributes */ uint16_t maxlistcount; uint16_t liststartoffset; /* pull and vcard attributes */ uint64_t filter; uint8_t format; /* list attributes only */ uint8_t order; uint8_t searchattrib; char *searchval; }; /* * Interface between the PBAP core and backends to retrieve * all contacts that match the application parameters rules. * Contacts will be returned in the vcard format. */ typedef void (*phonebook_cb) (const char *buffer, size_t bufsize, int vcards, int missed, gboolean lastpart, void *user_data); /* * Interface between the PBAP core and backends to * append a new entry in the PBAP folder cache. */ #define PHONEBOOK_INVALID_HANDLE 0xffffffff typedef void (*phonebook_entry_cb) (const char *id, uint32_t handle, const char *name, const char *sound, const char *tel, void *user_data); /* * After notify all entries to PBAP core, the backend * needs to notify that the operation has finished. */ typedef void (*phonebook_cache_ready_cb) (void *user_data); int phonebook_init(void); void phonebook_exit(void); /* * Changes the current folder in the phonebook back-end. The PBAP core * doesn't validate or restrict the possible values for the folders, * allowing non-standard backends implementation which doesn't follow * the PBAP virtual folder architecture. Validate the folder's name * is responsibility of the back-ends. */ char *phonebook_set_folder(const char *current_folder, const char *new_folder, uint8_t flags, int *err); /* * phonebook_pull should be used only to prepare pull request - prepared * request data is returned by this function. Start of fetching data from * back-end will be done only after calling phonebook_pull_read with this * returned request given as a parameter. * * phonebook_req_finalize MUST always be used to free associated resources. */ void *phonebook_pull(const char *name, const struct apparam_field *params, phonebook_cb cb, void *user_data, int *err); /* * phonebook_pull_read should be used to start getting results from back-end. * The back-end can return data as one response or can return it many parts. * After obtaining one part, PBAP core need to call phonebook_pull_read with * the same request again to get more results from back-end. * The back-end MUST return only the content based on the application * parameters requested by the client. * * Returns error code or 0 in case of success */ int phonebook_pull_read(void *request); /* * Function used to retrieve a contact from the backend. Only contacts * found in the cache are requested to the back-ends. The back-end MUST * return only the content based on the application parameters requested * by the client. * * Return value is a pointer to asynchronous request to phonebook back-end. * phonebook_req_finalize MUST always be used to free associated resources. */ void *phonebook_get_entry(const char *folder, const char *id, const struct apparam_field *params, phonebook_cb cb, void *user_data, int *err); /* * PBAP core will keep the contacts cache per folder. SetPhoneBook or * PullvCardListing can invalidate the cache if the current folder changes. * Cache will store only the necessary information required to reply to * PullvCardListing request and verify if a given contact belongs to the * source. * * Return value is a pointer to asynchronous request to phonebook back-end. * phonebook_req_finalize MUST always be used to free associated resources. */ void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb, phonebook_cache_ready_cb ready_cb, void *user_data, int *err); /* * Finalizes request to phonebook back-end and deallocates associated * resources. Operation is canceled if not completed. This function MUST * always be used after any of phonebook_pull, phonebook_get_entry, and * phonebook_create_cache invoked. * * request is a pointer to asynchronous operation returned by phonebook_pull, * phonebook_get_entry, and phonebook_create_cache. */ void phonebook_req_finalize(void *request); bluez-5.87/obexd/plugins/PaxHeaders/vcard.c0000644000000000000000000000005014015011623015671 xustar0020 atime=1783093862 20 ctime=1783094104 bluez-5.87/obexd/plugins/vcard.c0000644000000000000000000005275114015011623015364 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * OBEX Server * * Copyright (C) 2008-2010 Intel Corporation. All rights reserved. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "vcard.h" #define ADDR_FIELD_AMOUNT 7 #define LEN_MAX 128 #define TYPE_INTERNATIONAL 145 #define PHONEBOOK_FLAG_CACHED 0x1 #define FILTER_VERSION (1 << 0) #define FILTER_FN (1 << 1) #define FILTER_N (1 << 2) #define FILTER_PHOTO (1 << 3) #define FILTER_BDAY (1 << 4) #define FILTER_ADR (1 << 5) #define FILTER_LABEL (1 << 6) #define FILTER_TEL (1 << 7) #define FILTER_EMAIL (1 << 8) #define FILTER_MAILER (1 << 9) #define FILTER_TZ (1 << 10) #define FILTER_GEO (1 << 11) #define FILTER_TITLE (1 << 12) #define FILTER_ROLE (1 << 13) #define FILTER_LOGO (1 << 14) #define FILTER_AGENT (1 << 15) #define FILTER_ORG (1 << 16) #define FILTER_NOTE (1 << 17) #define FILTER_REV (1 << 18) #define FILTER_SOUND (1 << 19) #define FILTER_URL (1 << 20) #define FILTER_UID (1 << 21) #define FILTER_KEY (1 << 22) #define FILTER_NICKNAME (1 << 23) #define FILTER_CATEGORIES (1 << 24) #define FILTER_PROID (1 << 25) #define FILTER_CLASS (1 << 26) #define FILTER_SORT_STRING (1 << 27) #define FILTER_X_IRMC_CALL_DATETIME (1 << 28) #define FORMAT_VCARD21 0x00 #define FORMAT_VCARD30 0x01 #define QP_LINE_LEN 75 #define QP_CHAR_LEN 3 #define QP_CR 0x0D #define QP_LF 0x0A #define QP_ESC 0x5C #define QP_SOFT_LINE_BREAK "=" #define QP_SELECT "\n!\"#$=@[\\]^`{|}~" #define ASCII_LIMIT 0x7F /* according to RFC 2425, the output string may need folding */ static void vcard_printf(GString *str, const char *fmt, ...) { char buf[1024]; va_list ap; int len_temp, line_number, i; unsigned int line_delimit = 75; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); line_number = strlen(buf) / line_delimit + 1; for (i = 0; i < line_number; i++) { len_temp = MIN(line_delimit, strlen(buf) - line_delimit * i); g_string_append_len(str, buf + line_delimit * i, len_temp); if (i != line_number - 1) g_string_append(str, "\r\n "); } g_string_append(str, "\r\n"); } /* According to RFC 2426, we need escape following characters: * '\n', '\r', ';', ',', '\'. */ static void add_slash(char *dest, const char *src, int len_max, int len) { int i, j; for (i = 0, j = 0; i < len && j + 1 < len_max; i++, j++) { /* filling dest buffer - last field need to be reserved * for '\0'*/ switch (src[i]) { case '\n': if (j + 2 >= len_max) /* not enough space in the buffer to put char * preceded with escaping sequence (and '\0' in * the end) */ goto done; dest[j++] = '\\'; dest[j] = 'n'; break; case '\r': if (j + 2 >= len_max) goto done; dest[j++] = '\\'; dest[j] = 'r'; break; case '\\': case ';': case ',': if (j + 2 >= len_max) goto done; dest[j++] = '\\'; /* fall through */ default: dest[j] = src[i]; break; } } done: dest[j] = 0; } static void escape_semicolon(char *dest, const char *src, int len_max, int len) { int i, j; for (i = 0, j = 0; i < len && j + 1 < len_max; i++, j++) { if (src[i] == ';') { if (j + 2 >= len_max) break; dest[j++] = '\\'; } dest[j] = src[i]; } dest[j] = 0; } static void set_escape(uint8_t format, char *dest, const char *src, int len_max, int len) { if (format == FORMAT_VCARD30) add_slash(dest, src, len_max, len); else if (format == FORMAT_VCARD21) escape_semicolon(dest, src, len_max, len); } static void get_escaped_fields(uint8_t format, char **fields, ...) { va_list ap; GString *line; char *field; char escaped[LEN_MAX]; va_start(ap, fields); line = g_string_new(""); for (field = va_arg(ap, char *); field; ) { set_escape(format, escaped, field, LEN_MAX, strlen(field)); g_string_append(line, escaped); field = va_arg(ap, char *); if (field) g_string_append(line, ";"); } va_end(ap); *fields = g_string_free(line, FALSE); } static gboolean set_qp_encoding(char c) { unsigned char q = c; if (strchr(QP_SELECT, q) != NULL) return TRUE; if (q < '!' || q > '~') return TRUE; return FALSE; } static void append_qp_break_line(GString *vcards, size_t *limit) { /* Quoted Printable lines of text must be limited to less than 76 * characters and terminated by Quoted Printable softline break * sequence of "=" (if some more characters left) */ g_string_append(vcards, QP_SOFT_LINE_BREAK); g_string_append(vcards, "\r\n "); *limit = QP_LINE_LEN - 1; } static void append_qp_ascii(GString *vcards, size_t *limit, char c) { if (*limit == 0) append_qp_break_line(vcards, limit); g_string_append_c(vcards, c); --*limit; } static void append_qp_hex(GString *vcards, size_t *limit, char c) { if (*limit < QP_CHAR_LEN) append_qp_break_line(vcards, limit); g_string_append_printf(vcards, "=%2.2X", (unsigned char) c); *limit -= QP_CHAR_LEN; } static void append_qp_new_line(GString *vcards, size_t *limit) { /* Multiple lines of text are separated with a Quoted Printable CRLF * sequence of "=0D" followed by "=0A" followed by a Quoted Printable * softline break sequence of "=" */ append_qp_hex(vcards, limit, QP_CR); append_qp_hex(vcards, limit, QP_LF); append_qp_break_line(vcards, limit); } static gboolean utf8_select(const char *field) { const char *pos; if (g_utf8_validate(field, -1, NULL) == FALSE) return FALSE; for (pos = field; *pos != '\0'; pos = g_utf8_next_char(pos)) { /* Test for non-standard UTF-8 character (out of range * standard ASCII set), composed of more than single byte * and represented by 32-bit value greater than 0x7F */ if (g_utf8_get_char(pos) > ASCII_LIMIT) return TRUE; } return FALSE; } static void vcard_qp_print_encoded(GString *vcards, const char *desc, ...) { const char *field, *charset = ""; const char *encoding = ";ENCODING=QUOTED-PRINTABLE"; size_t limit, param_len; va_list ap; va_start(ap, desc); for (field = va_arg(ap, char *); field; field = va_arg(ap, char *)) { if (utf8_select(field) == TRUE) { charset = ";CHARSET=UTF-8"; break; } } va_end(ap); vcard_printf(vcards, "%s%s%s:", desc, encoding, charset); g_string_truncate(vcards, vcards->len - 2); param_len = strlen(desc) + strlen(encoding) + strlen(charset) + 1; limit = QP_LINE_LEN - param_len; va_start(ap, desc); for (field = va_arg(ap, char *); field != NULL; ) { size_t i, size = strlen(field); for (i = 0; i < size; ++i) { if (set_qp_encoding(field[i])) { if (field[i] == '\n') { append_qp_new_line(vcards, &limit); continue; } append_qp_hex(vcards, &limit, field[i]); } else { /* According to vCard 2.1 spec. semicolons in * property parameter value must be escaped */ if (field[i] == ';') append_qp_hex(vcards, &limit, QP_ESC); append_qp_ascii(vcards, &limit, field[i]); } } field = va_arg(ap, char *); if (field) append_qp_ascii(vcards, &limit, ';'); } va_end(ap); g_string_append(vcards, "\r\n"); } static gboolean select_qp_encoding(uint8_t format, ...) { char *field; va_list ap; if (format != FORMAT_VCARD21) return FALSE; va_start(ap, format); for (field = va_arg(ap, char *); field; field = va_arg(ap, char *)) { int i; unsigned char c; if (strpbrk(field, QP_SELECT)) { va_end(ap); return TRUE; } /* Quoted Printable encoding is selected if there is * a character, which value is out of range standard * ASCII set, since it may be a part of some * non-standard character such as specified by UTF-8 */ for (i = 0; (c = field[i]) != '\0'; ++i) { if (c > ASCII_LIMIT) { va_end(ap); return TRUE; } } } va_end(ap); return FALSE; } static void vcard_printf_begin(GString *vcards, uint8_t format) { vcard_printf(vcards, "BEGIN:VCARD"); if (format == FORMAT_VCARD30) vcard_printf(vcards, "VERSION:3.0"); else if (format == FORMAT_VCARD21) vcard_printf(vcards, "VERSION:2.1"); } /* check if there is at least one contact field with personal data present */ static gboolean contact_fields_present(struct phonebook_contact * contact) { if (contact->family && strlen(contact->family) > 0) return TRUE; if (contact->given && strlen(contact->given) > 0) return TRUE; if (contact->additional && strlen(contact->additional) > 0) return TRUE; if (contact->prefix && strlen(contact->prefix) > 0) return TRUE; if (contact->suffix && strlen(contact->suffix) > 0) return TRUE; /* none of the personal data fields are present*/ return FALSE; } static void vcard_printf_name(GString *vcards, uint8_t format, struct phonebook_contact *contact) { char *fields; if (contact_fields_present(contact) == FALSE) { /* If fields are empty, add only 'N:' as parameter. * This is crucial for some devices (Nokia BH-903) which * have problems with history listings and can't determine * that a parameter is really empty if there are unnecessary * characters after 'N:' (e.g. 'N:;;;;'). * We need to add only'N:' param - without semicolons. */ vcard_printf(vcards, "N:"); return; } if (select_qp_encoding(format, contact->family, contact->given, contact->additional, contact->prefix, contact->suffix, NULL)) { vcard_qp_print_encoded(vcards, "N", contact->family, contact->given, contact->additional, contact->prefix, contact->suffix, NULL); return; } get_escaped_fields(format, &fields, contact->family, contact->given, contact->additional, contact->prefix, contact->suffix, NULL); vcard_printf(vcards, "N:%s", fields); g_free(fields); } static void vcard_printf_fullname(GString *vcards, uint8_t format, const char *text) { char field[LEN_MAX]; if (!text || strlen(text) == 0) { vcard_printf(vcards, "FN:"); return; } if (select_qp_encoding(format, text, NULL)) { vcard_qp_print_encoded(vcards, "FN", text, NULL); return; } set_escape(format, field, text, LEN_MAX, strlen(text)); vcard_printf(vcards, "FN:%s", field); } static void vcard_printf_number(GString *vcards, uint8_t format, const char *number, int type, enum phonebook_number_type category) { const char *intl = "", *category_string = ""; char buf[LEN_MAX], field[LEN_MAX]; /* TEL is a mandatory field, include even if empty */ if (!number || !strlen(number) || !type) { vcard_printf(vcards, "TEL:"); return; } switch (category) { case TEL_TYPE_HOME: if (format == FORMAT_VCARD21) category_string = "HOME;VOICE"; else if (format == FORMAT_VCARD30) category_string = "TYPE=HOME;TYPE=VOICE"; break; case TEL_TYPE_MOBILE: if (format == FORMAT_VCARD21) category_string = "CELL;VOICE"; else if (format == FORMAT_VCARD30) category_string = "TYPE=CELL;TYPE=VOICE"; break; case TEL_TYPE_FAX: if (format == FORMAT_VCARD21) category_string = "FAX"; else if (format == FORMAT_VCARD30) category_string = "TYPE=FAX"; break; case TEL_TYPE_WORK: if (format == FORMAT_VCARD21) category_string = "WORK;VOICE"; else if (format == FORMAT_VCARD30) category_string = "TYPE=WORK;TYPE=VOICE"; break; case TEL_TYPE_OTHER: if (format == FORMAT_VCARD21) category_string = "OTHER;VOICE"; else if (format == FORMAT_VCARD30) category_string = "TYPE=OTHER;TYPE=VOICE"; break; } if ((type == TYPE_INTERNATIONAL) && (number[0] != '+')) intl = "+"; snprintf(field, sizeof(field), "%s%s", intl, number); if (select_qp_encoding(format, number, NULL)) { snprintf(buf, sizeof(buf), "TEL;%s", category_string); vcard_qp_print_encoded(vcards, buf, field, NULL); return; } vcard_printf(vcards, "TEL;%s:%s", category_string, field); } static void vcard_printf_tag(GString *vcards, uint8_t format, const char *tag, const char *category, const char *fld) { int len; char *separator = "", *type = ""; char buf[LEN_MAX], field[LEN_MAX]; if (tag == NULL || strlen(tag) == 0) return; if (fld == NULL || (len = strlen(fld)) == 0) { vcard_printf(vcards, "%s:", tag); return; } if (category && strlen(category)) { separator = ";"; if (format == FORMAT_VCARD30) type = "TYPE="; } else { category = ""; } snprintf(buf, LEN_MAX, "%s%s%s%s", tag, separator, type, category); if (select_qp_encoding(format, fld, NULL)) { vcard_qp_print_encoded(vcards, buf, fld, NULL); return; } set_escape(format, field, fld, LEN_MAX, len); vcard_printf(vcards, "%s:%s", buf, field); } static void vcard_printf_email(GString *vcards, uint8_t format, const char *address, enum phonebook_field_type category) { const char *category_string = ""; char buf[LEN_MAX], field[LEN_MAX]; int len = 0; if (!address || !(len = strlen(address))) { vcard_printf(vcards, "EMAIL:"); return; } switch (category) { case FIELD_TYPE_HOME: if (format == FORMAT_VCARD21) category_string = "INTERNET;HOME"; else if (format == FORMAT_VCARD30) category_string = "TYPE=INTERNET;TYPE=HOME"; break; case FIELD_TYPE_WORK: if (format == FORMAT_VCARD21) category_string = "INTERNET;WORK"; else if (format == FORMAT_VCARD30) category_string = "TYPE=INTERNET;TYPE=WORK"; break; case FIELD_TYPE_OTHER: default: if (format == FORMAT_VCARD21) category_string = "INTERNET"; else if (format == FORMAT_VCARD30) category_string = "TYPE=INTERNET;TYPE=OTHER"; } if (select_qp_encoding(format, address, NULL)) { snprintf(buf, sizeof(buf), "EMAIL;%s", category_string); vcard_qp_print_encoded(vcards, buf, address, NULL); return; } set_escape(format, field, address, LEN_MAX, len); vcard_printf(vcards, "EMAIL;%s:%s", category_string, field); } static void vcard_printf_url(GString *vcards, uint8_t format, const char *url, enum phonebook_field_type category) { const char *category_string = ""; char buf[LEN_MAX], field[LEN_MAX]; if (!url || strlen(url) == 0) { vcard_printf(vcards, "URL:"); return; } switch (category) { case FIELD_TYPE_HOME: if (format == FORMAT_VCARD21) category_string = "INTERNET;HOME"; else if (format == FORMAT_VCARD30) category_string = "TYPE=INTERNET;TYPE=HOME"; break; case FIELD_TYPE_WORK: if (format == FORMAT_VCARD21) category_string = "INTERNET;WORK"; else if (format == FORMAT_VCARD30) category_string = "TYPE=INTERNET;TYPE=WORK"; break; case FIELD_TYPE_OTHER: default: if (format == FORMAT_VCARD21) category_string = "INTERNET"; else if (format == FORMAT_VCARD30) category_string = "TYPE=INTERNET"; break; } if (select_qp_encoding(format, url, NULL)) { snprintf(buf, sizeof(buf), "URL;%s", category_string); vcard_qp_print_encoded(vcards, buf, url, NULL); return; } set_escape(format, field, url, LEN_MAX, strlen(url)); vcard_printf(vcards, "URL;%s:%s", category_string, field); } static gboolean org_fields_present(struct phonebook_contact *contact) { if (contact->company && strlen(contact->company)) return TRUE; if (contact->department && strlen(contact->department)) return TRUE; return FALSE; } static void vcard_printf_org(GString *vcards, uint8_t format, struct phonebook_contact *contact) { char *fields; if (org_fields_present(contact) == FALSE) return; if (select_qp_encoding(format, contact->company, contact->department, NULL)) { vcard_qp_print_encoded(vcards, "ORG", contact->company, contact->department, NULL); return; } get_escaped_fields(format, &fields, contact->company, contact->department, NULL); vcard_printf(vcards, "ORG:%s", fields); g_free(fields); } static void vcard_printf_address(GString *vcards, uint8_t format, struct phonebook_addr *address) { char *fields, field_esc[LEN_MAX]; const char *category_string = ""; char buf[LEN_MAX], *address_fields[ADDR_FIELD_AMOUNT]; int i; size_t len; GSList *l; if (!address) { vcard_printf(vcards, "ADR:"); return; } switch (address->type) { case FIELD_TYPE_HOME: if (format == FORMAT_VCARD21) category_string = "HOME"; else if (format == FORMAT_VCARD30) category_string = "TYPE=HOME"; break; case FIELD_TYPE_WORK: if (format == FORMAT_VCARD21) category_string = "WORK"; else if (format == FORMAT_VCARD30) category_string = "TYPE=WORK"; break; default: if (format == FORMAT_VCARD21) category_string = "OTHER"; else if (format == FORMAT_VCARD30) category_string = "TYPE=OTHER"; break; } for (i = 0, l = address->fields; l; l = l->next) address_fields[i++] = l->data; if (select_qp_encoding(format, address_fields[0], address_fields[1], address_fields[2], address_fields[3], address_fields[4], address_fields[5], address_fields[6], NULL)) { snprintf(buf, sizeof(buf), "ADR;%s", category_string); vcard_qp_print_encoded(vcards, buf, address_fields[0], address_fields[1], address_fields[2], address_fields[3], address_fields[4], address_fields[5], address_fields[6], NULL); return; } /* allocate enough memory to insert address fields separated by ';' * and terminated by '\0' */ len = ADDR_FIELD_AMOUNT * LEN_MAX; fields = g_malloc0(len); for (l = address->fields; l; l = l->next) { char *field = l->data; if (field) { set_escape(format, field_esc, field, LEN_MAX, strlen(field)); g_strlcat(fields, field_esc, len); } if (l->next) /* not adding ';' after last addr field */ g_strlcat(fields, ";", len); } vcard_printf(vcards,"ADR;%s:%s", category_string, fields); g_free(fields); } static void vcard_printf_datetime(GString *vcards, uint8_t format, struct phonebook_contact *contact) { const char *type; char buf[LEN_MAX]; switch (contact->calltype) { case CALL_TYPE_MISSED: type = "MISSED"; break; case CALL_TYPE_INCOMING: type = "RECEIVED"; break; case CALL_TYPE_OUTGOING: type = "DIALED"; break; case CALL_TYPE_NOT_A_CALL: default: return; } if (select_qp_encoding(format, contact->datetime, NULL)) { snprintf(buf, sizeof(buf), "X-IRMC-CALL-DATETIME;%s", type); vcard_qp_print_encoded(vcards, buf, contact->datetime, NULL); return; } vcard_printf(vcards, "X-IRMC-CALL-DATETIME;%s:%s", type, contact->datetime); } static void vcard_printf_end(GString *vcards) { vcard_printf(vcards, "END:VCARD"); } void phonebook_add_contact(GString *vcards, struct phonebook_contact *contact, uint64_t filter, uint8_t format) { if (format == FORMAT_VCARD30 && filter) filter |= (FILTER_VERSION | FILTER_FN | FILTER_N | FILTER_TEL); else if (format == FORMAT_VCARD21 && filter) filter |= (FILTER_VERSION | FILTER_N | FILTER_TEL); else filter = (FILTER_VERSION | FILTER_UID | FILTER_N | FILTER_FN | FILTER_TEL | FILTER_EMAIL | FILTER_ADR | FILTER_BDAY | FILTER_NICKNAME | FILTER_URL | FILTER_PHOTO | FILTER_ORG | FILTER_ROLE | FILTER_TITLE | FILTER_X_IRMC_CALL_DATETIME); vcard_printf_begin(vcards, format); if (filter & FILTER_UID && *contact->uid) vcard_printf_tag(vcards, format, "UID", NULL, contact->uid); if (filter & FILTER_N) vcard_printf_name(vcards, format, contact); if (filter & FILTER_FN && (*contact->fullname || format == FORMAT_VCARD30)) vcard_printf_fullname(vcards, format, contact->fullname); if (filter & FILTER_TEL) { GSList *l = contact->numbers; if (g_slist_length(l) == 0) vcard_printf_number(vcards, format, NULL, 1, TEL_TYPE_OTHER); for (; l; l = l->next) { struct phonebook_field *number = l->data; vcard_printf_number(vcards, format, number->text, 1, number->type); } } if (filter & FILTER_EMAIL) { GSList *l = contact->emails; for (; l; l = l->next) { struct phonebook_field *email = l->data; vcard_printf_email(vcards, format, email->text, email->type); } } if (filter & FILTER_ADR) { GSList *l = contact->addresses; for (; l; l = l->next) { struct phonebook_addr *addr = l->data; vcard_printf_address(vcards, format, addr); } } if (filter & FILTER_BDAY && *contact->birthday) vcard_printf_tag(vcards, format, "BDAY", NULL, contact->birthday); if (filter & FILTER_NICKNAME && *contact->nickname) vcard_printf_tag(vcards, format, "NICKNAME", NULL, contact->nickname); if (filter & FILTER_URL) { GSList *l = contact->urls; for (; l; l = l->next) { struct phonebook_field *url = l->data; vcard_printf_url(vcards, format, url->text, url->type); } } if (filter & FILTER_PHOTO && *contact->photo) vcard_printf_tag(vcards, format, "PHOTO", NULL, contact->photo); if (filter & FILTER_ORG) vcard_printf_org(vcards, format, contact); if (filter & FILTER_ROLE && *contact->role) vcard_printf_tag(vcards, format, "ROLE", NULL, contact->role); if (filter & FILTER_TITLE && *contact->title) vcard_printf_tag(vcards, format, "TITLE", NULL, contact->title); if (filter & FILTER_X_IRMC_CALL_DATETIME) vcard_printf_datetime(vcards, format, contact); vcard_printf_end(vcards); } static void field_free(gpointer data) { struct phonebook_field *field = data; g_free(field->text); g_free(field); } void phonebook_addr_free(gpointer addr) { struct phonebook_addr *address = addr; g_slist_free_full(address->fields, g_free); g_free(address); } void phonebook_contact_free(struct phonebook_contact *contact) { if (contact == NULL) return; g_slist_free_full(contact->numbers, field_free); g_slist_free_full(contact->emails, field_free); g_slist_free_full(contact->addresses, phonebook_addr_free); g_slist_free_full(contact->urls, field_free); g_free(contact->uid); g_free(contact->fullname); g_free(contact->given); g_free(contact->family); g_free(contact->additional); g_free(contact->prefix); g_free(contact->suffix); g_free(contact->birthday); g_free(contact->nickname); g_free(contact->photo); g_free(contact->company); g_free(contact->department); g_free(contact->role); g_free(contact->title); g_free(contact->datetime); g_free(contact); } bluez-5.87/obexd/plugins/PaxHeaders/vcard.h0000644000000000000000000000005014015011623015676 xustar0020 atime=1783093863 20 ctime=1783094104 bluez-5.87/obexd/plugins/vcard.h0000644000000000000000000000217214015011623015361 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * OBEX Server * * Copyright (C) 2008-2010 Intel Corporation. All rights reserved. * */ enum phonebook_number_type { TEL_TYPE_HOME, TEL_TYPE_MOBILE, TEL_TYPE_FAX, TEL_TYPE_WORK, TEL_TYPE_OTHER, }; enum phonebook_field_type { FIELD_TYPE_HOME, FIELD_TYPE_WORK, FIELD_TYPE_OTHER, }; enum phonebook_call_type { CALL_TYPE_NOT_A_CALL, CALL_TYPE_MISSED, CALL_TYPE_INCOMING, CALL_TYPE_OUTGOING, }; struct phonebook_field { char *text; int type; }; struct phonebook_addr { GSList *fields; int type; }; struct phonebook_contact { char *uid; char *fullname; char *given; char *family; char *additional; GSList *numbers; GSList *emails; char *prefix; char *suffix; GSList *addresses; char *birthday; char *nickname; GSList *urls; char *photo; char *company; char *department; char *role; char *title; char *datetime; int calltype; }; void phonebook_add_contact(GString *vcards, struct phonebook_contact *contact, uint64_t filter, uint8_t format); void phonebook_contact_free(struct phonebook_contact *contact); void phonebook_addr_free(gpointer addr); bluez-5.87/obexd/plugins/PaxHeaders/pcsuite.c0000644000000000000000000000005015063045254016261 xustar0020 atime=1770587290 20 ctime=1783094104 bluez-5.87/obexd/plugins/pcsuite.c0000644000000000000000000002523015063045254015744 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2007-2010 Nokia Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "obexd/src/obexd.h" #include "obexd/src/plugin.h" #include "obexd/src/log.h" #include "obexd/src/obex.h" #include "obexd/src/mimetype.h" #include "obexd/src/service.h" #include "ftp.h" #define PCSUITE_CHANNEL 24 #define PCSUITE_WHO_SIZE 8 #define PCSUITE_RECORD " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " #define BACKUP_BUS_NAME "com.nokia.backup.plugin" #define BACKUP_PATH "/com/nokia/backup" #define BACKUP_PLUGIN_INTERFACE "com.nokia.backup.plugin" #define BACKUP_DBUS_TIMEOUT (1000 * 60 * 15) static const uint8_t FTP_TARGET[TARGET_SIZE] = { 0xF9, 0xEC, 0x7B, 0xC4, 0x95, 0x3C, 0x11, 0xD2, 0x98, 0x4E, 0x52, 0x54, 0x00, 0xDC, 0x9E, 0x09 }; static const uint8_t PCSUITE_WHO[PCSUITE_WHO_SIZE] = { 'P', 'C', ' ', 'S', 'u', 'i', 't', 'e' }; struct pcsuite_session { struct ftp_session *ftp; char *lock_file; int fd; }; static void *pcsuite_connect(struct obex_session *os, int *err) { struct pcsuite_session *pcsuite; struct ftp_session *ftp; int fd; char *filename; DBG(""); ftp = ftp_connect(os, err); if (ftp == NULL) return NULL; filename = g_build_filename(g_get_home_dir(), ".pcsuite", NULL); fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0644); if (fd < 0 && errno != EEXIST) { error("open(%s): %s(%d)", filename, strerror(errno), errno); goto fail; } /* Try to remove the file before retrying since it could be that some process left/crash without removing it */ if (fd < 0) { if (remove(filename) < 0) { error("remove(%s): %s(%d)", filename, strerror(errno), errno); goto fail; } fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0644); if (fd < 0) { error("open(%s): %s(%d)", filename, strerror(errno), errno); goto fail; } } DBG("%s created", filename); pcsuite = g_new0(struct pcsuite_session, 1); pcsuite->ftp = ftp; pcsuite->lock_file = filename; pcsuite->fd = fd; DBG("session %p created", pcsuite); if (err) *err = 0; return pcsuite; fail: if (ftp) ftp_disconnect(os, ftp); if (err) *err = -errno; g_free(filename); return NULL; } static int pcsuite_get(struct obex_session *os, void *user_data) { struct pcsuite_session *pcsuite = user_data; DBG("%p", pcsuite); return ftp_get(os, pcsuite->ftp); } static int pcsuite_chkput(struct obex_session *os, void *user_data) { struct pcsuite_session *pcsuite = user_data; DBG("%p", pcsuite); return ftp_chkput(os, pcsuite->ftp); } static int pcsuite_put(struct obex_session *os, void *user_data) { struct pcsuite_session *pcsuite = user_data; DBG("%p", pcsuite); return ftp_put(os, pcsuite->ftp); } static int pcsuite_setpath(struct obex_session *os, void *user_data) { struct pcsuite_session *pcsuite = user_data; DBG("%p", pcsuite); return ftp_setpath(os, pcsuite->ftp); } static int pcsuite_action(struct obex_session *os, void *user_data) { struct pcsuite_session *pcsuite = user_data; DBG("%p", pcsuite); return ftp_action(os, pcsuite->ftp); } static void pcsuite_disconnect(struct obex_session *os, void *user_data) { struct pcsuite_session *pcsuite = user_data; DBG("%p", pcsuite); if (pcsuite->fd >= 0) close(pcsuite->fd); if (pcsuite->lock_file) { if (remove(pcsuite->lock_file) < 0) error("remove(%s): %s(%d)", pcsuite->lock_file, strerror(errno), errno); g_free(pcsuite->lock_file); } if (pcsuite->ftp) ftp_disconnect(os, pcsuite->ftp); g_free(pcsuite); } static const struct obex_service_driver pcsuite = { .name = "Nokia OBEX PC Suite Services", .service = OBEX_PCSUITE, .channel = PCSUITE_CHANNEL, .secure = TRUE, .record = PCSUITE_RECORD, .target = FTP_TARGET, .target_size = TARGET_SIZE, .who = PCSUITE_WHO, .who_size = PCSUITE_WHO_SIZE, .connect = pcsuite_connect, .get = pcsuite_get, .put = pcsuite_put, .chkput = pcsuite_chkput, .setpath = pcsuite_setpath, .action = pcsuite_action, .disconnect = pcsuite_disconnect }; struct backup_object { char *cmd; int fd; int oflag; int error_code; mode_t mode; DBusPendingCall *pending_call; DBusConnection *conn; }; static void on_backup_dbus_notify(DBusPendingCall *pending_call, void *user_data) { struct backup_object *obj = user_data; DBusMessage *reply; const char *filename; int error_code; DBG("Notification received for pending call - %s", obj->cmd); reply = dbus_pending_call_steal_reply(pending_call); if (reply && dbus_message_get_args(reply, NULL, DBUS_TYPE_INT32, &error_code, DBUS_TYPE_STRING, &filename, DBUS_TYPE_INVALID)) { obj->error_code = error_code; if (filename) { DBG("Notification - file path = %s, error_code = %d", filename, error_code); if (error_code == 0) obj->fd = open(filename,obj->oflag,obj->mode); } } else DBG("Notification timed out or connection got closed"); if (reply) dbus_message_unref(reply); dbus_pending_call_unref(pending_call); obj->pending_call = NULL; dbus_connection_unref(obj->conn); obj->conn = NULL; if (obj->fd >= 0) { DBG("File opened, setting io flags, cmd = %s", obj->cmd); if (obj->oflag == O_RDONLY) obex_object_set_io_flags(user_data, G_IO_IN, 0); else obex_object_set_io_flags(user_data, G_IO_OUT, 0); } else { DBG("File open error, setting io error, cmd = %s", obj->cmd); obex_object_set_io_flags(user_data, G_IO_ERR, -EPERM); } } static gboolean send_backup_dbus_message(const char *oper, struct backup_object *obj, size_t *size) { DBusConnection *conn; DBusMessage *msg; DBusPendingCall *pending_call; gboolean ret = FALSE; dbus_uint32_t file_size; file_size = size ? *size : 0; conn = obex_setup_dbus_connection(NULL, NULL); if (conn == NULL) return FALSE; msg = dbus_message_new_method_call(BACKUP_BUS_NAME, BACKUP_PATH, BACKUP_PLUGIN_INTERFACE, "request"); if (msg == NULL) { dbus_connection_unref(conn); return FALSE; } dbus_message_append_args(msg, DBUS_TYPE_STRING, &oper, DBUS_TYPE_STRING, &obj->cmd, DBUS_TYPE_INT32, &file_size, DBUS_TYPE_INVALID); if (strcmp(oper, "open") == 0) { ret = g_dbus_send_message_with_reply(conn, msg, &pending_call, BACKUP_DBUS_TIMEOUT); dbus_message_unref(msg); if (ret) { obj->conn = conn; obj->pending_call = pending_call; ret = dbus_pending_call_set_notify(pending_call, on_backup_dbus_notify, obj, NULL); } else dbus_connection_unref(conn); } else { g_dbus_send_message(conn, msg); dbus_connection_unref(conn); } return ret; } static void *backup_open(const char *name, int oflag, mode_t mode, void *context, size_t *size, int *err) { struct backup_object *obj = g_new0(struct backup_object, 1); DBG("cmd = %s", name); obj->cmd = g_path_get_basename(name); obj->oflag = oflag; obj->mode = mode; obj->fd = -1; obj->pending_call = NULL; obj->conn = NULL; obj->error_code = 0; if (send_backup_dbus_message("open", obj, size) == FALSE) { g_free(obj->cmd); g_free(obj); obj = NULL; } if (err) *err = 0; return obj; } static int backup_close(void *object) { struct backup_object *obj = object; size_t size = 0; DBG("cmd = %s", obj->cmd); if (obj->fd != -1) close(obj->fd); if (obj->pending_call) { dbus_pending_call_cancel(obj->pending_call); dbus_pending_call_unref(obj->pending_call); dbus_connection_unref(obj->conn); } send_backup_dbus_message("close", obj, &size); g_free(obj->cmd); g_free(obj); return 0; } static ssize_t backup_read(void *object, void *buf, size_t count) { struct backup_object *obj = object; ssize_t ret = 0; if (obj->pending_call) { DBG("cmd = %s, IN WAITING STAGE", obj->cmd); return -EAGAIN; } if (obj->fd != -1) { DBG("cmd = %s, READING DATA", obj->cmd); ret = read(obj->fd, buf, count); if (ret < 0) ret = -errno; } else { DBG("cmd = %s, PERMANENT FAILURE", obj->cmd); ret = obj->error_code ? -obj->error_code : -ENOENT; } return ret; } static ssize_t backup_write(void *object, const void *buf, size_t count) { struct backup_object *obj = object; ssize_t ret = 0; if (obj->pending_call) { DBG("cmd = %s, IN WAITING STAGE", obj->cmd); return -EAGAIN; } if (obj->fd != -1) { ret = write(obj->fd, buf, count); DBG("cmd = %s, WRITING", obj->cmd); if (ret < 0) { error("backup: cmd = %s", obj->cmd); ret = -errno; } } else { error("backup: cmd = %s", obj->cmd); ret = obj->error_code ? -obj->error_code : -ENOENT; } return ret; } static int backup_flush(void *object) { DBG("%p", object); return 0; } static const struct obex_mime_type_driver backup = { .target = FTP_TARGET, .target_size = TARGET_SIZE, .mimetype = "application/vnd.nokia-backup", .open = backup_open, .close = backup_close, .read = backup_read, .write = backup_write, .flush = backup_flush, }; static int pcsuite_init(void) { int err; err = obex_service_driver_register(&pcsuite); if (err < 0) return err; err = obex_mime_type_driver_register(&backup); if (err < 0) obex_service_driver_unregister(&pcsuite); return err; } static void pcsuite_exit(void) { obex_mime_type_driver_unregister(&backup); obex_service_driver_unregister(&pcsuite); } OBEX_PLUGIN_DEFINE(pcsuite, pcsuite_init, pcsuite_exit) bluez-5.87/obexd/plugins/PaxHeaders/ftp.c0000644000000000000000000000005014711225434015375 xustar0020 atime=1783093857 20 ctime=1783094104 bluez-5.87/obexd/plugins/ftp.c0000644000000000000000000002346014711225434015063 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2007-2010 Nokia Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "obexd/src/obexd.h" #include "obexd/src/plugin.h" #include "obexd/src/log.h" #include "obexd/src/obex.h" #include "obexd/src/manager.h" #include "obexd/src/mimetype.h" #include "obexd/src/service.h" #include "ftp.h" #include "filesystem.h" #define LST_TYPE "x-obex/folder-listing" #define CAP_TYPE "x-obex/capability" static const uint8_t FTP_TARGET[TARGET_SIZE] = { 0xF9, 0xEC, 0x7B, 0xC4, 0x95, 0x3C, 0x11, 0xD2, 0x98, 0x4E, 0x52, 0x54, 0x00, 0xDC, 0x9E, 0x09 }; struct ftp_session { struct obex_session *os; struct obex_transfer *transfer; char *folder; }; static void set_folder(struct ftp_session *ftp, const char *new_folder) { DBG("%p folder %s", ftp, new_folder); g_free(ftp->folder); ftp->folder = new_folder ? g_strdup(new_folder) : NULL; } static int get_by_type(struct ftp_session *ftp, const char *type) { struct obex_session *os = ftp->os; const char *capability = obex_option_capability(); const char *name = obex_get_name(os); char *path; int err; DBG("%p name %s type %s", ftp, name, type); if (type == NULL && name == NULL) return -EBADR; if (type != NULL && g_ascii_strcasecmp(type, CAP_TYPE) == 0) return obex_get_stream_start(os, capability); if (name != NULL && !is_filename(name)) return -EBADR; path = g_build_filename(ftp->folder, name, NULL); err = obex_get_stream_start(os, path); g_free(path); return err; } void *ftp_connect(struct obex_session *os, int *err) { struct ftp_session *ftp; const char *root_folder; DBG(""); root_folder = obex_option_root_folder(); manager_register_session(os); ftp = g_new0(struct ftp_session, 1); set_folder(ftp, root_folder); ftp->os = os; if (err) *err = 0; ftp->transfer = manager_register_transfer(os); DBG("session %p created", ftp); return ftp; } int ftp_get(struct obex_session *os, void *user_data) { struct ftp_session *ftp = user_data; const char *type = obex_get_type(os); int ret; DBG("%p", ftp); if (ftp->folder == NULL) return -ENOENT; ret = get_by_type(ftp, type); if (ret < 0) return ret; /* Only track progress of file transfer */ if (type == NULL) manager_emit_transfer_started(ftp->transfer); return 0; } static int ftp_delete(struct ftp_session *ftp, const char *name) { char *path; int ret = 0; DBG("%p name %s", ftp, name); if (!(ftp->folder && name)) return -EINVAL; path = g_build_filename(ftp->folder, name, NULL); if (obex_remove(ftp->os, path) < 0) ret = -errno; g_free(path); return ret; } int ftp_chkput(struct obex_session *os, void *user_data) { struct ftp_session *ftp = user_data; const char *name = obex_get_name(os); char *path; int ret; DBG("%p name %s", ftp, name); if (name == NULL) return -EBADR; if (!is_filename(name)) return -EBADR; if (obex_get_size(os) == OBJECT_SIZE_DELETE) return 0; path = g_build_filename(ftp->folder, name, NULL); ret = obex_put_stream_start(os, path); if (ret == 0 && obex_get_size(os) != OBJECT_SIZE_DELETE && obex_get_size(os) != OBJECT_SIZE_UNKNOWN) { manager_emit_transfer_property(ftp->transfer, "Size"); } if (ret == 0) manager_emit_transfer_started(ftp->transfer); g_free(path); return ret; } int ftp_put(struct obex_session *os, void *user_data) { struct ftp_session *ftp = user_data; const char *name = obex_get_name(os); ssize_t size = obex_get_size(os); DBG("%p name %s size %zd", ftp, name, size); if (ftp->folder == NULL) return -EPERM; if (name == NULL) return -EBADR; if (!is_filename(name)) return -EBADR; if (size == OBJECT_SIZE_DELETE) return ftp_delete(ftp, name); return 0; } int ftp_setpath(struct obex_session *os, void *user_data) { struct ftp_session *ftp = user_data; const char *root_folder, *name; const uint8_t *nonhdr; char *fullname; struct stat dstat; gboolean root; int err; if (obex_get_non_header_data(os, &nonhdr) != 2) { error("Set path failed: flag and constants not found!"); return -EBADMSG; } name = obex_get_name(os); root_folder = obex_option_root_folder(); root = g_str_equal(root_folder, ftp->folder); DBG("%p name %s", ftp, name); /* Check flag "Backup" */ if ((nonhdr[0] & 0x01) == 0x01) { DBG("Set to parent path"); if (root) return -EPERM; fullname = g_path_get_dirname(ftp->folder); set_folder(ftp, fullname); g_free(fullname); DBG("Set to parent path: %s", ftp->folder); return 0; } if (!name) { DBG("Set path failed: name missing!"); return -EINVAL; } if (strlen(name) == 0) { DBG("Set to root"); set_folder(ftp, root_folder); return 0; } /* Check and set to name path */ if (!is_filename(name)) { error("Set path failed: name incorrect!"); return -EPERM; } fullname = g_build_filename(ftp->folder, name, NULL); DBG("Fullname: %s", fullname); err = verify_path(fullname); if (err == -ENOENT) goto not_found; if (err < 0) goto done; err = stat(fullname, &dstat); if (err < 0) { err = -errno; if (err == -ENOENT) goto not_found; DBG("stat: %s(%d)", strerror(-err), -err); goto done; } if (S_ISDIR(dstat.st_mode) && (dstat.st_mode & 0400) && (dstat.st_mode & 0100)) { set_folder(ftp, fullname); goto done; } err = -EPERM; goto done; not_found: if (nonhdr[0] != 0) { err = -ENOENT; goto done; } if (mkdir(fullname, 0755) < 0) { err = -errno; DBG("mkdir: %s(%d)", strerror(-err), -err); goto done; } err = 0; set_folder(ftp, fullname); done: g_free(fullname); return err; } static gboolean is_valid_path(const char *path) { char **elements, **cur; int depth = 0; elements = g_strsplit(path, "/", 0); for (cur = elements; *cur != NULL; cur++) { if (**cur == '\0' || strcmp(*cur, ".") == 0) continue; if (strcmp(*cur, "..") == 0) { depth--; if (depth < 0) break; continue; } depth++; } g_strfreev(elements); if (depth < 0) return FALSE; return TRUE; } static char *ftp_build_filename(struct ftp_session *ftp, const char *destname) { char *filename; /* DestName can either be relative or absolute (FTP style) */ if (destname[0] == '/') filename = g_build_filename(obex_option_root_folder(), destname, NULL); else filename = g_build_filename(ftp->folder, destname, NULL); if (is_valid_path(filename + strlen(obex_option_root_folder()))) return filename; g_free(filename); return NULL; } static int ftp_copy(struct ftp_session *ftp, const char *name, const char *destname) { char *source, *destination, *destdir; int ret; DBG("%p name %s destination %s", ftp, name, destname); if (ftp->folder == NULL) { error("No folder set"); return -ENOENT; } if (name == NULL || destname == NULL) return -EINVAL; destination = ftp_build_filename(ftp, destname); if (destination == NULL) return -EBADR; destdir = g_path_get_dirname(destination); ret = verify_path(destdir); g_free(destdir); if (ret < 0) { g_free(destination); return ret; } source = g_build_filename(ftp->folder, name, NULL); ret = obex_copy(ftp->os, source, destination); g_free(source); g_free(destination); return ret; } static int ftp_move(struct ftp_session *ftp, const char *name, const char *destname) { char *source, *destination, *destdir; int ret; DBG("%p name %s destname %s", ftp, name, destname); if (ftp->folder == NULL) { error("No folder set"); return -ENOENT; } if (name == NULL || destname == NULL) return -EINVAL; destination = ftp_build_filename(ftp, destname); if (destination == NULL) return -EBADR; destdir = g_path_get_dirname(destination); ret = verify_path(destdir); g_free(destdir); if (ret < 0) { g_free(destination); return ret; } source = g_build_filename(ftp->folder, name, NULL); ret = obex_move(ftp->os, source, destination); g_free(source); g_free(destination); return ret; } int ftp_action(struct obex_session *os, void *user_data) { struct ftp_session *ftp = user_data; const char *name, *destname; uint8_t action_id; name = obex_get_name(os); if (name == NULL || !is_filename(name)) return -EBADR; destname = obex_get_destname(os); action_id = obex_get_action_id(os); DBG("%p action 0x%x", ftp, action_id); switch (action_id) { case 0x00: /* Copy Object */ return ftp_copy(ftp, name, destname); case 0x01: /* Move/Rename Object */ return ftp_move(ftp, name, destname); default: return -ENOSYS; } } void ftp_disconnect(struct obex_session *os, void *user_data) { struct ftp_session *ftp = user_data; DBG("%p", ftp); manager_unregister_session(os); manager_unregister_transfer(ftp->transfer); g_free(ftp->folder); g_free(ftp); } static void ftp_progress(struct obex_session *os, void *user_data) { struct ftp_session *ftp = user_data; manager_emit_transfer_progress(ftp->transfer); } static void ftp_reset(struct obex_session *os, void *user_data) { struct ftp_session *ftp = user_data; manager_emit_transfer_completed(ftp->transfer); } static const struct obex_service_driver ftp = { .name = "File Transfer server", .service = OBEX_FTP, .target = FTP_TARGET, .target_size = TARGET_SIZE, .connect = ftp_connect, .progress = ftp_progress, .get = ftp_get, .put = ftp_put, .chkput = ftp_chkput, .setpath = ftp_setpath, .action = ftp_action, .disconnect = ftp_disconnect, .reset = ftp_reset }; static int ftp_init(void) { return obex_service_driver_register(&ftp); } static void ftp_exit(void) { obex_service_driver_unregister(&ftp); } OBEX_PLUGIN_DEFINE(ftp, ftp_init, ftp_exit) bluez-5.87/obexd/plugins/PaxHeaders/filesystem.h0000644000000000000000000000005014015011623016763 xustar0020 atime=1783093853 20 ctime=1783094104 bluez-5.87/obexd/plugins/filesystem.h0000644000000000000000000000043614015011623016447 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ ssize_t string_read(void *object, void *buf, size_t count); gboolean is_filename(const char *name); int verify_path(const char *path); bluez-5.87/obexd/plugins/PaxHeaders/pbap.c0000644000000000000000000000005015221752714015531 xustar0020 atime=1783093860 20 ctime=1783094104 bluez-5.87/obexd/plugins/pbap.c0000644000000000000000000005174315221752714015224 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2009-2010 Intel Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "gobex/gobex.h" #include "gobex/gobex-apparam.h" #include "obexd/src/obexd.h" #include "obexd/src/plugin.h" #include "obexd/src/log.h" #include "obexd/src/obex.h" #include "obexd/src/service.h" #include "obexd/src/manager.h" #include "obexd/src/mimetype.h" #include "phonebook.h" #include "filesystem.h" #define PHONEBOOK_TYPE "x-bt/phonebook" #define VCARDLISTING_TYPE "x-bt/vcard-listing" #define VCARDENTRY_TYPE "x-bt/vcard" #define ORDER_TAG 0x01 #define SEARCHVALUE_TAG 0x02 #define SEARCHATTRIB_TAG 0x03 #define MAXLISTCOUNT_TAG 0x04 #define LISTSTARTOFFSET_TAG 0x05 #define FILTER_TAG 0x06 #define FORMAT_TAG 0X07 #define PHONEBOOKSIZE_TAG 0X08 #define NEWMISSEDCALLS_TAG 0X09 struct cache { gboolean valid; uint32_t index; GSList *entries; }; struct cache_entry { uint32_t handle; char *id; char *name; char *sound; char *tel; }; struct pbap_session { struct apparam_field *params; char *folder; uint32_t find_handle; struct cache cache; struct pbap_object *obj; }; struct pbap_object { GString *buffer; GObexApparam *apparam; gboolean firstpacket; gboolean lastpart; struct pbap_session *session; void *request; }; static const uint8_t PBAP_TARGET[TARGET_SIZE] = { 0x79, 0x61, 0x35, 0xF0, 0xF0, 0xC5, 0x11, 0xD8, 0x09, 0x66, 0x08, 0x00, 0x20, 0x0C, 0x9A, 0x66 }; typedef int (*cache_entry_find_f) (const struct cache_entry *entry, const char *value); static void cache_entry_free(void *data) { struct cache_entry *entry = data; g_free(entry->id); g_free(entry->name); g_free(entry->sound); g_free(entry->tel); g_free(entry); } static gboolean entry_name_find(const struct cache_entry *entry, const char *value) { char *name; gboolean ret; if (!entry->name) return FALSE; if (strlen(value) == 0) return TRUE; name = g_utf8_strdown(entry->name, -1); ret = (g_strstr_len(name, -1, value) ? TRUE : FALSE); g_free(name); return ret; } static gboolean entry_sound_find(const struct cache_entry *entry, const char *value) { if (!entry->sound) return FALSE; return (g_strstr_len(entry->sound, -1, value) ? TRUE : FALSE); } static gboolean entry_tel_find(const struct cache_entry *entry, const char *value) { if (!entry->tel) return FALSE; return (g_strstr_len(entry->tel, -1, value) ? TRUE : FALSE); } static const char *cache_find(struct cache *cache, uint32_t handle) { GSList *l; for (l = cache->entries; l; l = l->next) { struct cache_entry *entry = l->data; if (entry->handle == handle) return entry->id; } return NULL; } static void cache_clear(struct cache *cache) { g_slist_free_full(cache->entries, cache_entry_free); cache->entries = NULL; } static void phonebook_size_result(const char *buffer, size_t bufsize, int vcards, int missed, gboolean lastpart, void *user_data) { struct pbap_session *pbap = user_data; uint16_t phonebooksize; if (pbap->obj->request) { phonebook_req_finalize(pbap->obj->request); pbap->obj->request = NULL; } if (vcards < 0) vcards = 0; DBG("vcards %d", vcards); phonebooksize = vcards; pbap->obj->apparam = g_obex_apparam_set_uint16(NULL, PHONEBOOKSIZE_TAG, phonebooksize); pbap->obj->firstpacket = TRUE; if (missed > 0) { DBG("missed %d", missed); } pbap->obj->apparam = g_obex_apparam_set_uint16(pbap->obj->apparam, NEWMISSEDCALLS_TAG, missed); obex_object_set_io_flags(pbap->obj, G_IO_IN, 0); } static void query_result(const char *buffer, size_t bufsize, int vcards, int missed, gboolean lastpart, void *user_data) { struct pbap_session *pbap = user_data; DBG(""); if (pbap->obj->request && lastpart) { phonebook_req_finalize(pbap->obj->request); pbap->obj->request = NULL; } pbap->obj->lastpart = lastpart; if (vcards < 0) { obex_object_set_io_flags(pbap->obj, G_IO_ERR, -ENOENT); return; } if (!pbap->obj->buffer) pbap->obj->buffer = g_string_new_len(buffer, bufsize); else pbap->obj->buffer = g_string_append_len(pbap->obj->buffer, buffer, bufsize); if (missed > 0) { DBG("missed %d", missed); pbap->obj->firstpacket = TRUE; } pbap->obj->apparam = g_obex_apparam_set_uint16(pbap->obj->apparam, NEWMISSEDCALLS_TAG, missed); obex_object_set_io_flags(pbap->obj, G_IO_IN, 0); } static void cache_entry_notify(const char *id, uint32_t handle, const char *name, const char *sound, const char *tel, void *user_data) { struct pbap_session *pbap = user_data; struct cache_entry *entry = g_new0(struct cache_entry, 1); struct cache *cache = &pbap->cache; if (handle != PHONEBOOK_INVALID_HANDLE) entry->handle = handle; else entry->handle = ++pbap->cache.index; entry->id = g_strdup(id); entry->name = g_strdup(name); entry->sound = g_strdup(sound); entry->tel = g_strdup(tel); cache->entries = g_slist_append(cache->entries, entry); } static int alpha_sort(gconstpointer a, gconstpointer b) { const struct cache_entry *e1 = a; const struct cache_entry *e2 = b; return g_strcmp0(e1->name, e2->name); } static int indexed_sort(gconstpointer a, gconstpointer b) { const struct cache_entry *e1 = a; const struct cache_entry *e2 = b; return (e1->handle - e2->handle); } static int phonetical_sort(gconstpointer a, gconstpointer b) { const struct cache_entry *e1 = a; const struct cache_entry *e2 = b; /* SOUND attribute is optional. Use Indexed sort if not present. */ if (!e1->sound || !e2->sound) return indexed_sort(a, b); return g_strcmp0(e1->sound, e2->sound); } static GSList *sort_entries(GSList *l, uint8_t order, uint8_t search_attrib, const char *value) { GSList *sorted = NULL; cache_entry_find_f find; GCompareFunc sort; char *searchval; /* * Default sorter is "Indexed". Some backends doesn't inform the index, * for this case a sequential internal index is assigned. * 0x00 = indexed * 0x01 = alphanumeric * 0x02 = phonetic */ switch (order) { case 0x01: sort = alpha_sort; break; case 0x02: sort = phonetical_sort; break; default: sort = indexed_sort; break; } /* * This implementation checks if the given field CONTAINS the * search value(case insensitive). Name is the default field * when the attribute is not provided. */ switch (search_attrib) { /* Number */ case 1: find = entry_tel_find; break; /* Sound */ case 2: find = entry_sound_find; break; default: find = entry_name_find; break; } searchval = value ? g_utf8_strdown(value, -1) : NULL; for (; l; l = l->next) { struct cache_entry *entry = l->data; if (searchval && !find(entry, (const char *) searchval)) continue; sorted = g_slist_insert_sorted(sorted, entry, sort); } g_free(searchval); return sorted; } static int generate_response(void *user_data) { struct pbap_session *pbap = user_data; GSList *sorted; GSList *l; uint16_t max = pbap->params->maxlistcount; DBG(""); if (max == 0) { /* Ignore all other parameter and return PhoneBookSize */ uint16_t size = g_slist_length(pbap->cache.entries); pbap->obj->firstpacket = TRUE; pbap->obj->apparam = g_obex_apparam_set_uint16( pbap->obj->apparam, PHONEBOOKSIZE_TAG, size); return 0; } /* * Don't free the sorted list content: this list contains * only the reference for the "real" cache entry. */ sorted = sort_entries(pbap->cache.entries, pbap->params->order, pbap->params->searchattrib, (const char *) pbap->params->searchval); /* Computing offset considering first entry of the phonebook */ l = g_slist_nth(sorted, pbap->params->liststartoffset); pbap->obj->buffer = g_string_new(VCARD_LISTING_BEGIN); for (; l && max; l = l->next, max--) { const struct cache_entry *entry = l->data; char *escaped_name = g_markup_escape_text(entry->name, -1); g_string_append_printf(pbap->obj->buffer, VCARD_LISTING_ELEMENT, entry->handle, escaped_name); g_free(escaped_name); } pbap->obj->buffer = g_string_append(pbap->obj->buffer, VCARD_LISTING_END); g_slist_free(sorted); return 0; } static void cache_ready_notify(void *user_data) { struct pbap_session *pbap = user_data; DBG(""); phonebook_req_finalize(pbap->obj->request); pbap->obj->request = NULL; pbap->cache.valid = TRUE; generate_response(pbap); obex_object_set_io_flags(pbap->obj, G_IO_IN, 0); } static void cache_entry_done(void *user_data) { struct pbap_session *pbap = user_data; const char *id; int ret; DBG(""); pbap->cache.valid = TRUE; id = cache_find(&pbap->cache, pbap->find_handle); if (id == NULL) { DBG("Entry %d not found on cache", pbap->find_handle); obex_object_set_io_flags(pbap->obj, G_IO_ERR, -ENOENT); return; } phonebook_req_finalize(pbap->obj->request); pbap->obj->request = phonebook_get_entry(pbap->folder, id, pbap->params, query_result, pbap, &ret); if (ret < 0) obex_object_set_io_flags(pbap->obj, G_IO_ERR, ret); } static struct apparam_field *parse_aparam(const uint8_t *buffer, uint32_t hlen) { GObexApparam *apparam; struct apparam_field *param; param = g_new0(struct apparam_field, 1); /* * As per spec when client doesn't include MAXLISTCOUNT_TAG then it * should be assume as Maximum value in vcardlisting 65535 */ param->maxlistcount = UINT16_MAX; apparam = g_obex_apparam_decode(buffer, hlen); if (apparam) { g_obex_apparam_get_uint8(apparam, ORDER_TAG, ¶m->order); g_obex_apparam_get_uint8(apparam, SEARCHATTRIB_TAG, ¶m->searchattrib); g_obex_apparam_get_uint8(apparam, FORMAT_TAG, ¶m->format); g_obex_apparam_get_uint16(apparam, MAXLISTCOUNT_TAG, ¶m->maxlistcount); g_obex_apparam_get_uint16(apparam, LISTSTARTOFFSET_TAG, ¶m->liststartoffset); g_obex_apparam_get_uint64(apparam, FILTER_TAG, ¶m->filter); param->searchval = g_obex_apparam_get_string(apparam, SEARCHVALUE_TAG); g_obex_apparam_free(apparam); } DBG("o %x sa %x sv %s fil %" G_GINT64_MODIFIER "x for %x max %x off %x", param->order, param->searchattrib, param->searchval, param->filter, param->format, param->maxlistcount, param->liststartoffset); return param; } static void *pbap_connect(struct obex_session *os, int *err) { struct pbap_session *pbap; manager_register_session(os); pbap = g_new0(struct pbap_session, 1); pbap->folder = g_strdup("/"); pbap->find_handle = PHONEBOOK_INVALID_HANDLE; if (err) *err = 0; return pbap; } static int pbap_get(struct obex_session *os, void *user_data) { struct pbap_session *pbap = user_data; const char *type = obex_get_type(os); const char *name = obex_get_name(os); struct apparam_field *params; const uint8_t *buffer; char *path; ssize_t rsize; int ret; DBG("name %s type %s pbap %p", name, type, pbap); if (type == NULL) return -EBADR; rsize = obex_get_apparam(os, &buffer); if (rsize < 0) { if (g_ascii_strcasecmp(type, VCARDENTRY_TYPE) != 0) return -EBADR; rsize = 0; } /* Workaround for PTS client not sending mandatory apparams */ if (!rsize && g_ascii_strcasecmp(type, VCARDLISTING_TYPE) == 0) { static const uint8_t default_apparams[] = { 0x04, 0x02, 0xff, 0xff }; buffer = default_apparams; rsize = sizeof(default_apparams); } else if (!rsize && g_ascii_strcasecmp(type, VCARDENTRY_TYPE) == 0) { static const uint8_t default_apparams[] = { 0x07, 0x01, 0x00 }; buffer = default_apparams; rsize = sizeof(default_apparams); } params = parse_aparam(buffer, rsize); if (params == NULL) return -EBADR; if (pbap->params) { g_free(pbap->params->searchval); g_free(pbap->params); } pbap->params = params; if (g_ascii_strcasecmp(type, PHONEBOOK_TYPE) == 0) { /* Always contains the absolute path */ if (g_path_is_absolute(name)) path = g_strdup(name); else path = g_build_filename("/", name, NULL); } else if (g_ascii_strcasecmp(type, VCARDLISTING_TYPE) == 0) { /* Always relative */ if (!name || strlen(name) == 0) { /* Current folder */ path = g_strdup(pbap->folder); } else { /* Current folder + relative path */ path = g_build_filename(pbap->folder, name, NULL); /* clear cache */ pbap->cache.valid = FALSE; pbap->cache.index = 0; cache_clear(&pbap->cache); } } else if (g_ascii_strcasecmp(type, VCARDENTRY_TYPE) == 0) { /* File name only */ path = g_strdup(name); } else return -EBADR; if (path == NULL) return -EBADR; ret = obex_get_stream_start(os, path); g_free(path); return ret; } static int pbap_setpath(struct obex_session *os, void *user_data) { struct pbap_session *pbap = user_data; const char *name; const uint8_t *nonhdr; char *fullname; int err; if (obex_get_non_header_data(os, &nonhdr) != 2) { error("Set path failed: flag and constants not found!"); return -EBADMSG; } name = obex_get_name(os); DBG("name %s folder %s nonhdr 0x%x%x", name, pbap->folder, nonhdr[0], nonhdr[1]); fullname = phonebook_set_folder(pbap->folder, name, nonhdr[0], &err); if (err < 0) return err; g_free(pbap->folder); pbap->folder = fullname; /* * FIXME: Define a criteria to mark the cache as invalid */ pbap->cache.valid = FALSE; pbap->cache.index = 0; cache_clear(&pbap->cache); return 0; } static void pbap_disconnect(struct obex_session *os, void *user_data) { struct pbap_session *pbap = user_data; manager_unregister_session(os); if (pbap->obj) pbap->obj->session = NULL; if (pbap->params) { g_free(pbap->params->searchval); g_free(pbap->params); } cache_clear(&pbap->cache); g_free(pbap->folder); g_free(pbap); } static int pbap_chkput(struct obex_session *os, void *user_data) { /* Rejects all PUTs */ return -EBADR; } static const struct obex_service_driver pbap = { .name = "Phonebook Access server", .service = OBEX_PBAP, .target = PBAP_TARGET, .target_size = TARGET_SIZE, .connect = pbap_connect, .get = pbap_get, .setpath = pbap_setpath, .disconnect = pbap_disconnect, .chkput = pbap_chkput }; static struct pbap_object *vobject_create(struct pbap_session *pbap, void *request) { struct pbap_object *obj; obj = g_new0(struct pbap_object, 1); obj->session = pbap; pbap->obj = obj; obj->request = request; return obj; } static void *vobject_pull_open(const char *name, int oflag, mode_t mode, void *context, size_t *size, int *err) { struct pbap_session *pbap = context; phonebook_cb cb; int ret = 0; void *request; DBG("name %s context %p maxlistcount %d", name, context, pbap->params->maxlistcount); if (oflag != O_RDONLY) { ret = -EPERM; goto fail; } if (name == NULL) { ret = -EBADR; goto fail; } if (pbap->params->maxlistcount == 0) cb = phonebook_size_result; else cb = query_result; request = phonebook_pull(name, pbap->params, cb, pbap, &ret); if (ret < 0) goto fail; /* reading first part of results from backend */ ret = phonebook_pull_read(request); if (ret < 0) goto fail; if (err) *err = 0; return vobject_create(pbap, request); fail: if (err) *err = ret; return NULL; } static int vobject_close(void *object) { struct pbap_object *obj = object; DBG(""); if (obj->session) obj->session->obj = NULL; if (obj->buffer) g_string_free(obj->buffer, TRUE); if (obj->apparam) g_obex_apparam_free(obj->apparam); if (obj->request) phonebook_req_finalize(obj->request); g_free(obj); return 0; } static void *vobject_list_open(const char *name, int oflag, mode_t mode, void *context, size_t *size, int *err) { struct pbap_session *pbap = context; struct pbap_object *obj = NULL; int ret; void *request; if (name == NULL) { ret = -EBADR; goto fail; } DBG("name %s context %p valid %d", name, context, pbap->cache.valid); if (oflag != O_RDONLY) { ret = -EPERM; goto fail; } /* PullvCardListing always get the contacts from the cache */ if (pbap->cache.valid) { obj = vobject_create(pbap, NULL); ret = generate_response(pbap); } else { request = phonebook_create_cache(name, cache_entry_notify, cache_ready_notify, pbap, &ret); if (ret == 0) obj = vobject_create(pbap, request); } if (ret < 0) goto fail; if (err) *err = 0; return obj; fail: if (obj) vobject_close(obj); if (err) *err = ret; return NULL; } static void *vobject_vcard_open(const char *name, int oflag, mode_t mode, void *context, size_t *size, int *err) { struct pbap_session *pbap = context; const char *id; uint32_t handle; int ret; void *request; DBG("name %s context %p valid %d", name, context, pbap->cache.valid); if (oflag != O_RDONLY) { ret = -EPERM; goto fail; } if (name == NULL || sscanf(name, "%u.vcf", &handle) != 1) { ret = -EBADR; goto fail; } if (pbap->cache.valid == FALSE) { pbap->find_handle = handle; request = phonebook_create_cache(pbap->folder, cache_entry_notify, cache_entry_done, pbap, &ret); goto done; } id = cache_find(&pbap->cache, handle); if (!id) { ret = -ENOENT; goto fail; } request = phonebook_get_entry(pbap->folder, id, pbap->params, query_result, pbap, &ret); done: if (ret < 0) goto fail; if (err) *err = 0; return vobject_create(pbap, request); fail: if (err) *err = ret; return NULL; } static ssize_t vobject_pull_get_next_header(void *object, void *buf, size_t mtu, uint8_t *hi) { struct pbap_object *obj = object; if (!obj->buffer && !obj->apparam) return -EAGAIN; *hi = G_OBEX_HDR_APPARAM; if (obj->firstpacket) { obj->firstpacket = FALSE; return g_obex_apparam_encode(obj->apparam, buf, mtu); } return 0; } static ssize_t vobject_pull_read(void *object, void *buf, size_t count) { struct pbap_object *obj = object; struct pbap_session *pbap = obj->session; int len, ret; DBG("buffer %p maxlistcount %d", obj->buffer, pbap->params->maxlistcount); if (!obj->buffer) { if (pbap->params->maxlistcount == 0) return -ENOSTR; return -EAGAIN; } len = string_read(obj->buffer, buf, count); if (len == 0 && !obj->lastpart) { /* in case when buffer is empty and we know that more * data is still available in backend, requesting new * data part via phonebook_pull_read and returning * -EAGAIN to suspend request for now */ ret = phonebook_pull_read(obj->request); if (ret) return -EPERM; return -EAGAIN; } return len; } static ssize_t vobject_list_get_next_header(void *object, void *buf, size_t mtu, uint8_t *hi) { struct pbap_object *obj = object; struct pbap_session *pbap = obj->session; /* Backend still busy reading contacts */ if (!pbap->cache.valid) return -EAGAIN; *hi = G_OBEX_HDR_APPARAM; if (obj->firstpacket) { obj->firstpacket = FALSE; return g_obex_apparam_encode(obj->apparam, buf, mtu); } return 0; } static ssize_t vobject_list_read(void *object, void *buf, size_t count) { struct pbap_object *obj = object; struct pbap_session *pbap = obj->session; DBG("valid %d maxlistcount %d", pbap->cache.valid, pbap->params->maxlistcount); if (pbap->params->maxlistcount == 0) return -ENOSTR; return string_read(obj->buffer, buf, count); } static ssize_t vobject_vcard_read(void *object, void *buf, size_t count) { struct pbap_object *obj = object; DBG("buffer %p", obj->buffer); if (!obj->buffer) return -EAGAIN; return string_read(obj->buffer, buf, count); } static const struct obex_mime_type_driver mime_pull = { .target = PBAP_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/phonebook", .open = vobject_pull_open, .close = vobject_close, .read = vobject_pull_read, .get_next_header = vobject_pull_get_next_header, }; static const struct obex_mime_type_driver mime_list = { .target = PBAP_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/vcard-listing", .open = vobject_list_open, .close = vobject_close, .read = vobject_list_read, .get_next_header = vobject_list_get_next_header, }; static const struct obex_mime_type_driver mime_vcard = { .target = PBAP_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/vcard", .open = vobject_vcard_open, .close = vobject_close, .read = vobject_vcard_read, }; static int pbap_init(void) { int err; err = phonebook_init(); if (err < 0) return err; err = obex_mime_type_driver_register(&mime_pull); if (err < 0) goto fail_mime_pull; err = obex_mime_type_driver_register(&mime_list); if (err < 0) goto fail_mime_list; err = obex_mime_type_driver_register(&mime_vcard); if (err < 0) goto fail_mime_vcard; err = obex_service_driver_register(&pbap); if (err < 0) goto fail_pbap_reg; return 0; fail_pbap_reg: obex_mime_type_driver_unregister(&mime_vcard); fail_mime_vcard: obex_mime_type_driver_unregister(&mime_list); fail_mime_list: obex_mime_type_driver_unregister(&mime_pull); fail_mime_pull: phonebook_exit(); return err; } static void pbap_exit(void) { obex_service_driver_unregister(&pbap); obex_mime_type_driver_unregister(&mime_pull); obex_mime_type_driver_unregister(&mime_list); obex_mime_type_driver_unregister(&mime_vcard); phonebook_exit(); } OBEX_PLUGIN_DEFINE(pbap, pbap_init, pbap_exit) bluez-5.87/obexd/plugins/PaxHeaders/phonebook-ebook.c0000644000000000000000000000005015221752714017670 xustar0020 atime=1783092684 20 ctime=1783094111 bluez-5.87/obexd/plugins/phonebook-ebook.c0000644000000000000000000003454015221752714017357 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2009-2021 Intel Corporation * Copyright (C) 2007-2021 Marcel Holtmann * Copyright (C) 2021 Dylan Van Assche * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "obexd/src/log.h" #include "phonebook.h" #define CONNECTION_TIMEOUT 30 // seconds #define PB_FORMAT_VCARD21 0 #define PB_FORMAT_VCARD30 1 #define PB_FORMAT_NONE 2 ESourceRegistry *registry; ESource *address_book; EBookClient *book_client; struct query_context { const struct apparam_field *params; phonebook_cb contacts_cb; phonebook_entry_cb entry_cb; phonebook_cache_ready_cb ready_cb; gchar *query; unsigned int count; GString *buf; char *uid; unsigned queued_calls; void *user_data; gboolean canceled; }; static const char *attribute_mask[] = { /* 0 */ "VERSION", "FN", "N", "PHOTO", "BDAY", "ADR", "LABEL", "TEL", /* 8 */ "EMAIL", "MAILER", "TZ", "GEO", "TITLE", "ROLE", "LOGO", "AGENT", /* 16 */ "ORG", "NOTE", "REV", "SOUND", "URL", "UID", "KEY", "NICKNAME", /* 24 */ "CATEGORIES", "PROID", "CLASS", "SORT-STRING", /* 28 */ "X-IRMC-CALL-DATETIME", NULL }; static void free_query_context(struct query_context *data) { g_free(data->uid); if (data->buf != NULL) g_string_free(data->buf, TRUE); if (data->query != NULL) g_free(data->query); g_free(data); } static char *evcard_to_string(EVCard *evcard, unsigned int format, uint64_t filter) { EVCard *evcard2; GList *l; char *vcard; if (!filter) return e_vcard_to_string(evcard, format); /* * Mandatory attributes for vCard 2.1 are VERSION ,N and TEL. * Mandatory attributes for vCard 3.0 are VERSION, N, FN and TEL */ filter = format == EVC_FORMAT_VCARD_30 ? filter | 0x87: filter | 0x85; l = e_vcard_get_attributes(evcard); evcard2 = e_vcard_new(); for (; l; l = g_list_next(l)) { EVCardAttribute *attrib = l->data; const char *name; int i; if (!attrib) continue; name = e_vcard_attribute_get_name(attrib); for (i = 0; attribute_mask[i] != NULL; i++) { if (!(filter & (1 << i))) continue; if (g_strcmp0(name, attribute_mask[i]) != 0) continue; e_vcard_add_attribute(evcard2, e_vcard_attribute_copy(attrib)); } } vcard = e_vcard_to_string(evcard2, format); g_object_unref(evcard2); return vcard; } char *phonebook_set_folder(const char *current_folder, const char *new_folder, uint8_t flags, int *err) { gboolean root, child; char *fullname = NULL, *tmp1, *tmp2, *base; int ret = 0, len; root = (g_strcmp0("/", current_folder) == 0); child = (new_folder && strlen(new_folder) != 0); /* Evolution back-end will support /telecom/pb folder only */ switch (flags) { case 0x02: /* Go back to root */ if (!child) { fullname = g_strdup("/"); goto done; } /* Go down 1 level */ fullname = g_build_filename(current_folder, new_folder, NULL); if (strcmp(PB_TELECOM_FOLDER, fullname) != 0 && strcmp(PB_CONTACTS_FOLDER, fullname) != 0) { g_free(fullname); fullname = NULL; ret = -ENOENT; } break; case 0x03: /* Go up 1 level */ if (root) { /* Already root */ ret = -EBADR; goto done; } /* * Removing one level of the current folder. Current folder * contains AT LEAST one level since it is not at root folder. * Use glib utility functions to handle invalid chars in the * folder path properly. */ tmp1 = g_path_get_basename(current_folder); tmp2 = g_strrstr(current_folder, tmp1); len = tmp2 - (current_folder + 1); g_free(tmp1); if (len == 0) base = g_strdup("/"); else base = g_strndup(current_folder, len); /* Return one level only */ if (!child) { fullname = base; goto done; } fullname = g_build_filename(base, new_folder, NULL); if (strcmp(fullname, PB_TELECOM_FOLDER) != 0 && strcmp(fullname, PB_CONTACTS_FOLDER) != 0) { g_free(fullname); fullname = NULL; ret = -ENOENT; } g_free(base); break; default: ret = -EBADR; break; } done: if (err) *err = ret; return fullname; } void phonebook_req_finalize(void *request) { /* Free resources after pull request */ struct query_context *data = request; if (data->queued_calls == 0) free_query_context(data); else data->canceled = TRUE; } void *phonebook_pull(const char *name, const struct apparam_field *params, phonebook_cb cb, void *user_data, int *err) { struct query_context *data; EBookQuery *query; /* Request should be for '/telecom/pb.vcf', reject others */ if (g_strcmp0(PB_CONTACTS, name) != 0) { if (err) *err = -ENOENT; return NULL; } data = g_new0(struct query_context, 1); data->contacts_cb = cb; data->params = params; data->user_data = user_data; data->buf = g_string_new(""); query = e_book_query_any_field_contains(""); // all contacts data->query = e_book_query_to_string(query); e_book_query_unref(query); if (err) *err = 0; return data; } static void phonebook_pull_read_ready(GObject *source_object, GAsyncResult *result, gpointer user_data) { struct query_context *data = user_data; GSList *l = NULL; GSList *contacts = NULL; GError *gerr = NULL; unsigned int count, maxcount; /* Finish async call to retrieve contacts */ data->queued_calls--; if (data->canceled) goto canceled; e_book_client_get_contacts_finish(E_BOOK_CLIENT(source_object), result, &contacts, &gerr); if (gerr != NULL) { error("Failed to retrieve contacts, invalid query"); g_error_free(gerr); goto done; } /* * When MaxListCount is zero, PCE wants to know the number of used * indexes in the phonebook of interest. All other parameters that * may be present in the request shall be ignored. */ maxcount = data->params->maxlistcount; if (maxcount == 0) { data->count += g_slist_length(contacts); goto done; } /* * Convert each contact to a vCard and append the card to * the buffer string. */ l = g_slist_nth(contacts, data->params->liststartoffset); for (count = 0; l && count + data->count < maxcount; l = g_slist_next(l), count++) { EContact *contact = E_CONTACT(l->data); EVCard *evcard = E_VCARD(contact); char *vcard; if (data->params->format == PB_FORMAT_VCARD30) vcard = evcard_to_string(evcard, EVC_FORMAT_VCARD_30, data->params->filter); else if (data->params->format == PB_FORMAT_VCARD21) vcard = evcard_to_string(evcard, EVC_FORMAT_VCARD_21, data->params->filter); else error("unknown format: %d", data->params->format); data->buf = g_string_append(data->buf, vcard); data->buf = g_string_append(data->buf, "\r\n"); g_free(vcard); } DBG("collected %d contacts", count); data->count += count; g_slist_free_full(contacts, (GDestroyNotify) g_object_unref); done: if (data->queued_calls == 0) { GString *buf = data->buf; data->buf = NULL; data->contacts_cb(buf->str, buf->len, data->count, 0, TRUE, data->user_data); g_string_free(buf, TRUE); } return; canceled: if (data->queued_calls == 0) free_query_context(data); } int phonebook_pull_read(void *request) { struct query_context *data = request; GError *gerr = NULL; if (!data) { error("Request data is empty"); return -ENOENT; } DBG("retrieving all contacts"); /* Fetch async contacts from default address book */ e_book_client_get_contacts(book_client, data->query, NULL, (GAsyncReadyCallback) phonebook_pull_read_ready, data); data->queued_calls++; return 0; } static void phonebook_get_entry_ready(GObject *source_object, GAsyncResult *result, gpointer user_data) { GError *gerr = NULL; EContact *contact = NULL; struct query_context *data = user_data; EVCard *evcard; char *vcard; size_t len; data->queued_calls--; e_book_client_get_contact_finish(E_BOOK_CLIENT(source_object), result, &contact, &gerr); if (data->canceled) goto done; if (gerr != NULL) { error("Getting contact failed: %s", gerr->message); g_error_free(gerr); goto done; } evcard = E_VCARD(contact); if (data->params->format == PB_FORMAT_VCARD30) vcard = evcard_to_string(evcard, EVC_FORMAT_VCARD_30, data->params->filter); else if (data->params->format == PB_FORMAT_VCARD21) vcard = evcard_to_string(evcard, EVC_FORMAT_VCARD_21, data->params->filter); else error("Unknown vCard format: %d", data->params->format); len = vcard ? strlen(vcard) : 0; data->count++; data->contacts_cb(vcard, len, 1, 0, TRUE, data->user_data); g_free(vcard); DBG("retrieving entry successful"); done: if (data->queued_calls == 0) { if (data->count == 0) data->contacts_cb(NULL, 0, 1, 0, TRUE, data->user_data); else if (data->canceled) free_query_context(data); } g_object_unref(contact); } void *phonebook_get_entry(const char *folder, const char *id, const struct apparam_field *params, phonebook_cb cb, void *user_data, int *err) { struct query_context *data; GSList *l; DBG("retrieving entry: %s", id); data = g_new0(struct query_context, 1); data->contacts_cb = cb; data->params = params; data->user_data = user_data; data->uid = g_strdup(id); /* Fetch async contacts from default address book */ e_book_client_get_contact(book_client, data->uid, NULL, (GAsyncReadyCallback) phonebook_get_entry_ready, data); data->queued_calls++; if (err) *err = (data->queued_calls == 0 ? -ENOENT : 0); return data; } static char *evcard_name_attribute_to_string(EVCard *evcard) { EVCardAttribute *attrib; GList *l; GString *name = NULL; attrib = e_vcard_get_attribute(evcard, EVC_N); if (!attrib) return NULL; for (l = e_vcard_attribute_get_values(attrib); l; l = l->next) { const char *value = l->data; if (!strlen(value)) continue; if (!name) name = g_string_new(value); else { name = g_string_append(name, ";"); name = g_string_append(name, l->data); } } if (!name) return NULL; return g_string_free(name, FALSE); } static void phonebook_create_cache_ready(GObject *source_object, GAsyncResult *result, gpointer user_data) { struct query_context *data = user_data; GSList *l = NULL; GSList *contacts = NULL; GError *gerr = NULL; data->queued_calls--; if (data->canceled) goto canceled; e_book_client_get_contacts_finish(E_BOOK_CLIENT(source_object), result, &contacts, &gerr); if (gerr != NULL) { error("Getting contacts failed: %s", gerr->message); goto done; } for (l = contacts; l; l = g_slist_next(l)) { EContact *contact = E_CONTACT(l->data); EVCard *evcard = E_VCARD(contact); EVCardAttribute *attrib; char *uid, *tel, *name; name = evcard_name_attribute_to_string(evcard); if (!name) continue; attrib = e_vcard_get_attribute(evcard, EVC_UID); if (!attrib) continue; uid = e_vcard_attribute_get_value(attrib); if (!uid) continue; attrib = e_vcard_get_attribute(evcard, EVC_TEL); if (attrib) tel = e_vcard_attribute_get_value(attrib); else tel = g_strdup(""); data->entry_cb(uid, PHONEBOOK_INVALID_HANDLE, name, NULL, tel, data->user_data); g_free(name); g_free(uid); g_free(tel); } DBG("caching successful"); g_slist_free_full(contacts, (GDestroyNotify) g_object_unref); done: if (data->queued_calls == 0) data->ready_cb(data->user_data); return; canceled: if (data->queued_calls == 0) free_query_context(data); } void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb, phonebook_cache_ready_cb ready_cb, void *user_data, int *err) { /* Build a cache of contacts */ struct query_context *data; EBookQuery *query; EContact *me; EBookClient *me_client; EVCard *evcard; GError *gerr = NULL; EVCardAttribute *attrib; char *uid, *tel, *cname; if (g_strcmp0(PB_CONTACTS_FOLDER, name) != 0) { if (err) *err = -ENOENT; return NULL; } DBG("creating cache"); data = g_new0(struct query_context, 1); data->entry_cb = entry_cb; data->ready_cb = ready_cb; data->user_data = user_data; query = e_book_query_any_field_contains(""); // all contacts data->query = e_book_query_to_string(query); e_book_query_unref(query); /* Myself as contact should always be 0.vcf if found in address book */ if (!e_book_client_get_self(registry, &me, &me_client, &gerr)) { DBG("owner is not in address book: %s", gerr->message); g_error_free(gerr); goto next; } DBG("caching address book owner"); evcard = E_VCARD(me); cname = evcard_name_attribute_to_string(evcard); if (!cname) cname = g_strdup(""); attrib = e_vcard_get_attribute(evcard, EVC_UID); uid = e_vcard_attribute_get_value(attrib); if (!uid) uid = g_strdup(""); attrib = e_vcard_get_attribute(evcard, EVC_TEL); if (attrib) tel = e_vcard_attribute_get_value(attrib); else tel = g_strdup(""); data->entry_cb(uid, 0, cname, NULL, tel, data->user_data); data->count++; g_free(cname); g_free(uid); g_free(tel); next: /* Fetch async contacts from default address book */ DBG("caching contacts"); e_book_client_get_contacts(book_client, data->query, NULL, (GAsyncReadyCallback) phonebook_create_cache_ready, data); data->queued_calls++; if (err) *err = (data->queued_calls == 0 ? -ENOENT : 0); return data; } int phonebook_init(void) { EClient *client; GError *gerr = NULL; /* Acquire ESource Registry */ registry = e_source_registry_new_sync(NULL, &gerr); if (gerr != NULL) { error("Unable to acquire registry: %s\n", gerr->message); g_error_free(gerr); return -1; } /* Get ref to default address book */ address_book = e_source_registry_ref_default_address_book(registry); if (address_book == NULL) { error("Unable to get reference to default address book"); return -2; } /* Allocate e-book client for address book */ gerr = NULL; client = e_book_client_connect_sync(address_book, CONNECTION_TIMEOUT, NULL, &gerr); if (gerr != NULL || client == NULL) { error("Cannot connect ebook client to EDS: %s", gerr != NULL ? gerr->message : "NULL"); g_error_free(gerr); return -3; } book_client = E_BOOK_CLIENT(client); DBG("created address book client"); return 0; } void phonebook_exit(void) { g_object_unref(book_client); g_object_unref(address_book); g_object_unref(registry); } bluez-5.87/obexd/plugins/PaxHeaders/irmc.c0000644000000000000000000000005015063045254015537 xustar0020 atime=1783093858 20 ctime=1783094104 bluez-5.87/obexd/plugins/irmc.c0000644000000000000000000002367115063045254015231 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX IrMC Sync Server * * Copyright (C) 2010 Marcel Mol * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "obexd/src/obexd.h" #include "obexd/src/plugin.h" #include "obexd/src/log.h" #include "obexd/src/obex.h" #include "obexd/src/service.h" #include "obexd/src/manager.h" #include "obexd/src/mimetype.h" #include "phonebook.h" #include "filesystem.h" struct aparam_header { uint8_t tag; uint8_t len; uint8_t val[0]; } __attribute__ ((packed)); #define DID_LEN 18 struct irmc_session { struct obex_session *os; struct apparam_field *params; uint16_t entries; GString *buffer; char sn[DID_LEN]; char did[DID_LEN]; char manu[DID_LEN]; char model[DID_LEN]; void *request; }; #define IRMC_TARGET_SIZE 9 static const guint8 IRMC_TARGET[IRMC_TARGET_SIZE] = { 0x49, 0x52, 0x4d, 0x43, 0x2d, 0x53, 0x59, 0x4e, 0x43 }; /* FIXME: * the IrMC specs state the first vcard should be the owner * vcard. As there is no simple way to collect ownerdetails * just create an empty vcard (which is allowed according to the * specs). */ static const char *owner_vcard = "BEGIN:VCARD\r\n" "VERSION:2.1\r\n" "N:\r\n" "TEL:\r\n" "X-IRMX-LUID:0\r\n" "END:VCARD\r\n"; static void phonebook_size_result(const char *buffer, size_t bufsize, int vcards, int missed, gboolean lastpart, void *user_data) { struct irmc_session *irmc = user_data; DBG("vcards %d", vcards); irmc->params->maxlistcount = vcards; if (irmc->request) { phonebook_req_finalize(irmc->request); irmc->request = NULL; } } static void query_result(const char *buffer, size_t bufsize, int vcards, int missed, gboolean lastpart, void *user_data) { struct irmc_session *irmc = user_data; const char *s, *t; DBG("bufsize %zu vcards %d missed %d", bufsize, vcards, missed); if (irmc->request) { phonebook_req_finalize(irmc->request); irmc->request = NULL; } /* first add a 'owner' vcard */ if (!irmc->buffer) irmc->buffer = g_string_new(owner_vcard); else irmc->buffer = g_string_append(irmc->buffer, owner_vcard); if (buffer == NULL) goto done; /* loop around buffer and add X-IRMC-LUID attribs */ s = buffer; while ((t = strstr(s, "UID:")) != NULL) { /* add up to UID: into buffer */ irmc->buffer = g_string_append_len(irmc->buffer, s, t-s); /* * add UID: line into buffer * Not sure if UID is still needed if X-IRMC-LUID is there */ s = t; t = strstr(s, "\r\n"); t += 2; irmc->buffer = g_string_append_len(irmc->buffer, s, t-s); /* add X-IRMC-LUID with same number as UID */ irmc->buffer = g_string_append_len(irmc->buffer, "X-IRMC-LUID:", 12); s += 4; /* point to uid number */ irmc->buffer = g_string_append_len(irmc->buffer, s, t-s); s = t; } /* add remaining bit of buffer */ irmc->buffer = g_string_append(irmc->buffer, s); done: obex_object_set_io_flags(irmc, G_IO_IN, 0); } static void *irmc_connect(struct obex_session *os, int *err) { struct irmc_session *irmc; struct apparam_field *param; int ret; DBG(""); manager_register_session(os); irmc = g_new0(struct irmc_session, 1); irmc->os = os; /* FIXME: * Ideally get capabilities info here and use that to define * IrMC DID and SN etc parameters. * For now lets used hostname and some 'random' value */ gethostname(irmc->did, DID_LEN); strncpy(irmc->sn, "12345", sizeof(irmc->sn) - 1); strncpy(irmc->manu, "obex", sizeof(irmc->manu) - 1); strncpy(irmc->model, "mymodel", sizeof(irmc->model) - 1); /* We need to know the number of contact/cal/nt entries * somewhere so why not do it now. */ param = g_new0(struct apparam_field, 1); param->maxlistcount = 0; /* to count the number of vcards... */ param->filter = 0x200085; /* UID TEL N VERSION */ irmc->params = param; irmc->request = phonebook_pull(PB_CONTACTS, irmc->params, phonebook_size_result, irmc, err); ret = phonebook_pull_read(irmc->request); if (err) *err = ret; return irmc; } static int irmc_get(struct obex_session *os, void *user_data) { struct irmc_session *irmc = user_data; const char *type = obex_get_type(os); const char *name = obex_get_name(os); char *path; int ret; DBG("name %s type %s irmc %p", name, type ? type : "NA", irmc); path = g_strdup(name); ret = obex_get_stream_start(os, path); g_free(path); return ret; } static void irmc_disconnect(struct obex_session *os, void *user_data) { struct irmc_session *irmc = user_data; DBG(""); manager_unregister_session(os); if (irmc->params) { if (irmc->params->searchval) g_free(irmc->params->searchval); g_free(irmc->params); } if (irmc->buffer) g_string_free(irmc->buffer, TRUE); g_free(irmc); } static int irmc_chkput(struct obex_session *os, void *user_data) { DBG(""); /* Reject all PUTs */ return -EBADR; } static int irmc_open_devinfo(struct irmc_session *irmc) { if (!irmc->buffer) irmc->buffer = g_string_new(""); g_string_append_printf(irmc->buffer, "MANU:%s\r\n" "MOD:%s\r\n" "SN:%s\r\n" "IRMC-VERSION:1.1\r\n" "PB-TYPE-TX:VCARD2.1\r\n" "PB-TYPE-RX:NONE\r\n" "CAL-TYPE-TX:NONE\r\n" "CAL-TYPE-RX:NONE\r\n" "MSG-TYPE-TX:NONE\r\n" "MSG-TYPE-RX:NONE\r\n" "NOTE-TYPE-TX:NONE\r\n" "NOTE-TYPE-RX:NONE\r\n", irmc->manu, irmc->model, irmc->sn); return 0; } static int irmc_open_pb(struct irmc_session *irmc) { int ret; /* how can we tell if the vcard count call already finished? */ irmc->request = phonebook_pull(PB_CONTACTS, irmc->params, query_result, irmc, &ret); if (ret < 0) { DBG("phonebook_pull failed..."); return ret; } ret = phonebook_pull_read(irmc->request); if (ret < 0) { DBG("phonebook_pull_read failed..."); return ret; } return 0; } static int irmc_open_info(struct irmc_session *irmc) { if (irmc->buffer == NULL) irmc->buffer = g_string_new(""); g_string_printf(irmc->buffer, "Total-Records:%d\r\n" "Maximum-Records:%d\r\n" "IEL:2\r\n" "DID:%s\r\n", irmc->params->maxlistcount, irmc->params->maxlistcount, irmc->did); return 0; } static int irmc_open_cc(struct irmc_session *irmc) { if (irmc->buffer == NULL) irmc->buffer = g_string_new(""); g_string_printf(irmc->buffer, "%d\r\n", irmc->params->maxlistcount); return 0; } static int irmc_open_cal(struct irmc_session *irmc) { /* no support yet. Just return an empty buffer. cal.vcs */ DBG("unsupported, returning empty buffer"); if (!irmc->buffer) irmc->buffer = g_string_new(""); return 0; } static int irmc_open_nt(struct irmc_session *irmc) { /* no support yet. Just return an empty buffer. nt.vnt */ DBG("unsupported, returning empty buffer"); if (!irmc->buffer) irmc->buffer = g_string_new(""); return 0; } static int irmc_open_luid(struct irmc_session *irmc) { if (irmc->buffer == NULL) irmc->buffer = g_string_new(""); DBG("changelog request, force whole book"); g_string_printf(irmc->buffer, "SN:%s\r\n" "DID:%s\r\n" "Total-Records:%d\r\n" "Maximum-Records:%d\r\n" "*\r\n", irmc->sn, irmc->did, irmc->params->maxlistcount, irmc->params->maxlistcount); return 0; } static void *irmc_open(const char *name, int oflag, mode_t mode, void *context, size_t *size, int *err) { struct irmc_session *irmc = context; int ret = 0; char *path; DBG("name %s context %p", name, context); if (oflag != O_RDONLY) { ret = -EPERM; goto fail; } if (name == NULL) { ret = -EBADR; goto fail; } /* Always contains the absolute path */ if (g_path_is_absolute(name)) path = g_strdup(name); else path = g_build_filename("/", name, NULL); if (g_str_equal(path, PB_DEVINFO)) ret = irmc_open_devinfo(irmc); else if (g_str_equal(path, PB_CONTACTS)) ret = irmc_open_pb(irmc); else if (g_str_equal(path, PB_INFO_LOG)) ret = irmc_open_info(irmc); else if (g_str_equal(path, PB_CC_LOG)) ret = irmc_open_cc(irmc); else if (g_str_has_prefix(path, PB_CALENDAR_FOLDER)) ret = irmc_open_cal(irmc); else if (g_str_has_prefix(path, PB_NOTES_FOLDER)) ret = irmc_open_nt(irmc); else if (g_str_has_prefix(path, PB_LUID_FOLDER)) ret = irmc_open_luid(irmc); else ret = -EBADR; g_free(path); if (ret == 0) return irmc; fail: if (err) *err = ret; return NULL; } static int irmc_close(void *object) { struct irmc_session *irmc = object; DBG(""); if (irmc->buffer) { g_string_free(irmc->buffer, TRUE); irmc->buffer = NULL; } if (irmc->request) { phonebook_req_finalize(irmc->request); irmc->request = NULL; } return 0; } static ssize_t irmc_read(void *object, void *buf, size_t count) { struct irmc_session *irmc = object; int len; DBG("buffer %p count %zu", irmc->buffer, count); if (!irmc->buffer) return -EAGAIN; len = string_read(irmc->buffer, buf, count); DBG("returning %d bytes", len); return len; } static const struct obex_mime_type_driver irmc_driver = { .target = IRMC_TARGET, .target_size = IRMC_TARGET_SIZE, .open = irmc_open, .close = irmc_close, .read = irmc_read, }; static const struct obex_service_driver irmc = { .name = "IRMC Sync server", .service = OBEX_IRMC, .target = IRMC_TARGET, .target_size = IRMC_TARGET_SIZE, .connect = irmc_connect, .get = irmc_get, .disconnect = irmc_disconnect, .chkput = irmc_chkput }; static int irmc_init(void) { int err; DBG(""); err = phonebook_init(); if (err < 0) return err; err = obex_mime_type_driver_register(&irmc_driver); if (err < 0) goto fail_mime_irmc; err = obex_service_driver_register(&irmc); if (err < 0) goto fail_irmc_reg; return 0; fail_irmc_reg: obex_mime_type_driver_unregister(&irmc_driver); fail_mime_irmc: phonebook_exit(); return err; } static void irmc_exit(void) { DBG(""); obex_service_driver_unregister(&irmc); obex_mime_type_driver_unregister(&irmc_driver); phonebook_exit(); } OBEX_PLUGIN_DEFINE(irmc, irmc_init, irmc_exit) bluez-5.87/obexd/plugins/PaxHeaders/phonebook-tracker.c0000644000000000000000000000005015063045254020222 xustar0020 atime=1770587295 20 ctime=1783094111 bluez-5.87/obexd/plugins/phonebook-tracker.c0000644000000000000000000013044115063045254017706 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * Phonebook access through D-Bus vCard and call history service * * Copyright (C) 2010 Nokia Corporation * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "obexd/src/log.h" #include "obexd/src/obex.h" #include "obexd/src/service.h" #include "obexd/src/mimetype.h" #include "phonebook.h" #include "vcard.h" #define TRACKER_SERVICE "org.freedesktop.Tracker1" #define TRACKER_RESOURCES_PATH "/org/freedesktop/Tracker1/Resources" #define TRACKER_RESOURCES_INTERFACE "org.freedesktop.Tracker1.Resources" #define TRACKER_DEFAULT_CONTACT_ME "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#default-contact-me" #define AFFILIATION_HOME "Home" #define AFFILIATION_WORK "Work" #define ADDR_FIELD_AMOUNT 7 #define PULL_QUERY_COL_AMOUNT 23 #define COUNT_QUERY_COL_AMOUNT 1 #define COL_PHONE_AFF 0 /* work/home phone numbers */ #define COL_FULL_NAME 1 #define COL_FAMILY_NAME 2 #define COL_GIVEN_NAME 3 #define COL_ADDITIONAL_NAME 4 #define COL_NAME_PREFIX 5 #define COL_NAME_SUFFIX 6 #define COL_ADDR_AFF 7 /* addresses from affiliation */ #define COL_BIRTH_DATE 8 #define COL_NICKNAME 9 #define COL_URL 10 #define COL_PHOTO 11 #define COL_ORG_ROLE 12 #define COL_UID 13 #define COL_TITLE 14 #define COL_AFF_TYPE 15 #define COL_ORG_NAME 16 #define COL_ORG_DEPARTMENT 17 #define COL_EMAIL_AFF 18 /* email's from affiliation (work/home) */ #define COL_DATE 19 #define COL_SENT 20 #define COL_ANSWERED 21 #define CONTACTS_ID_COL 22 #define CONTACT_ID_PREFIX "urn:uuid:" #define CALL_ID_PREFIX "message:" #define FAX_NUM_TYPE "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#FaxNumber" #define MOBILE_NUM_TYPE "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#CellPhoneNumber" #define MAIN_DELIM "\30" /* Main delimiter between phones, addresses, emails*/ #define SUB_DELIM "\31" /* Delimiter used in telephone number strings*/ #define ADDR_DELIM "\37" /* Delimiter used for address data fields */ #define MAX_FIELDS 100 /* Max amount of fields to be concatenated at once*/ #define VCARDS_PART_COUNT 50 /* amount of vcards sent at once to PBAP core */ #define QUERY_OFFSET_FORMAT "%s OFFSET %d" #define CONTACTS_QUERY_ALL \ "SELECT " \ "(SELECT GROUP_CONCAT(fn:concat(rdf:type(?aff_number)," \ "\"\31\", nco:phoneNumber(?aff_number)), \"\30\")" \ "WHERE {" \ " ?_role nco:hasPhoneNumber ?aff_number" \ "}) " \ "nco:fullname(?_contact) " \ "nco:nameFamily(?_contact) " \ "nco:nameGiven(?_contact) " \ "nco:nameAdditional(?_contact) " \ "nco:nameHonorificPrefix(?_contact) " \ "nco:nameHonorificSuffix(?_contact) " \ "(SELECT GROUP_CONCAT(fn:concat(" \ "tracker:coalesce(nco:pobox(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:locality(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:region(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:country(?aff_addr), \"\"), " \ "\"\31\", rdfs:label(?_role) ), " \ "\"\30\") " \ "WHERE {" \ "?_role nco:hasPostalAddress ?aff_addr" \ "}) " \ "nco:birthDate(?_contact) " \ "(SELECT " \ " ?nick " \ " WHERE { " \ " { " \ " ?_contact nco:nickname ?nick " \ " } UNION { " \ " ?_contact nco:hasAffiliation ?role . " \ " ?role nco:hasIMAddress ?im . " \ " ?im nco:imNickname ?nick " \ " } " \ " } " \ ") " \ "(SELECT GROUP_CONCAT(fn:concat( " \ "?url_val, \"\31\", tracker:coalesce(rdfs:label(?_role), \"\") "\ "), \"\30\") " \ "WHERE {" \ "?_role nco:url ?url_val . " \ "})" \ "nie:url(nco:photo(?_contact)) " \ "nco:role(?_role) " \ "nco:contactUID(?_contact) " \ "nco:title(?_role) " \ "rdfs:label(?_role) " \ "nco:fullname(nco:org(?_role))" \ "nco:department(?_role) " \ "(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\"," \ "tracker:coalesce(rdfs:label(?_role), \"\"))," \ "\"\30\") " \ "WHERE { " \ "?_role nco:hasEmailAddress " \ " [ nco:emailAddress ?emailaddress ] " \ "}) " \ "\"NOTACALL\" \"false\" \"false\" " \ "?_contact " \ "WHERE {" \ " ?_contact a nco:PersonContact ." \ " OPTIONAL {?_contact nco:hasAffiliation ?_role .}" \ "}" \ "ORDER BY tracker:id(?_contact)" #define CONTACTS_QUERY_ALL_LIST \ "SELECT ?c nco:nameFamily(?c) " \ "nco:nameGiven(?c) nco:nameAdditional(?c) " \ "nco:nameHonorificPrefix(?c) nco:nameHonorificSuffix(?c) " \ "(SELECT " \ "?nick " \ "WHERE { " \ "{ " \ "?c nco:nickname ?nick " \ "} UNION { " \ "?c nco:hasAffiliation ?role . " \ "?role nco:hasIMAddress ?im . " \ "?im nco:imNickname ?nick " \ "} " \ "} " \ ") " \ "nco:phoneNumber(?h) " \ "WHERE { " \ "?c a nco:PersonContact . " \ "OPTIONAL { ?c nco:hasPhoneNumber ?h . } " \ "OPTIONAL { " \ "?c nco:hasAffiliation ?a . " \ "?a nco:hasPhoneNumber ?h . " \ "} " \ "} GROUP BY ?c" #define CALLS_CONSTRAINTS(CONSTRAINT) \ " WHERE { " \ "?_call a nmo:Call . " \ "?_unb_contact a nco:Contact . " \ "?_unb_contact nco:hasPhoneNumber ?_cpn . " \ CONSTRAINT \ "OPTIONAL { " \ "{ SELECT ?_contact ?_no ?_role ?_number " \ "count(?_contact) as ?cnt " \ "WHERE { " \ "?_contact a nco:PersonContact . " \ "{ " \ "?_contact nco:hasAffiliation ?_role . "\ "?_role nco:hasPhoneNumber ?_number . " \ "} UNION { " \ "?_contact nco:hasPhoneNumber ?_number" \ "} " \ "?_number maemo:localPhoneNumber ?_no . " \ "} GROUP BY ?_no } " \ "FILTER(?cnt = 1) " \ "?_cpn maemo:localPhoneNumber ?_no . " \ "} " \ "} " #define CALLS_LIST(CONSTRAINT) \ "SELECT ?_call nco:nameFamily(?_contact) " \ "nco:nameGiven(?_contact) nco:nameAdditional(?_contact) " \ "nco:nameHonorificPrefix(?_contact) " \ "nco:nameHonorificSuffix(?_contact) " \ "(SELECT " \ "?nick " \ "WHERE { " \ "{ " \ "?_contact nco:nickname ?nick " \ "} UNION { " \ "?_contact nco:hasAffiliation ?role . " \ "?role nco:hasIMAddress ?im . " \ "?im nco:imNickname ?nick " \ "} " \ "} " \ ") " \ "nco:phoneNumber(?_cpn) " \ CALLS_CONSTRAINTS(CONSTRAINT) \ "ORDER BY DESC(nmo:sentDate(?_call)) " #define CALLS_QUERY(CONSTRAINT) \ "SELECT " \ "(SELECT fn:concat(rdf:type(?role_number)," \ "\"\31\", nco:phoneNumber(?role_number))" \ "WHERE {" \ "{" \ " ?_role nco:hasPhoneNumber ?role_number " \ " FILTER (?role_number = ?_number)" \ "} UNION { " \ "?_unb_contact nco:hasPhoneNumber ?role_number . " \ " FILTER (!bound(?_role)) " \ "}" \ "} GROUP BY nco:phoneNumber(?role_number) ) " \ "nco:fullname(?_contact) " \ "nco:nameFamily(?_contact) " \ "nco:nameGiven(?_contact) " \ "nco:nameAdditional(?_contact) " \ "nco:nameHonorificPrefix(?_contact) " \ "nco:nameHonorificSuffix(?_contact) " \ "(SELECT GROUP_CONCAT(fn:concat(" \ "tracker:coalesce(nco:pobox(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \"\37\","\ "tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \"\37\","\ "tracker:coalesce(nco:locality(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:region(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:country(?aff_addr), \"\"), " \ "\"\31\", rdfs:label(?c_role) ), " \ "\"\30\") " \ "WHERE {" \ "?_contact nco:hasAffiliation ?c_role . " \ "?c_role nco:hasPostalAddress ?aff_addr" \ "}) " \ "nco:birthDate(?_contact) " \ "(SELECT " \ "?nick " \ "WHERE { " \ " { " \ " ?_contact nco:nickname ?nick " \ " } UNION { " \ " ?_contact nco:hasAffiliation ?role . " \ " ?role nco:hasIMAddress ?im . " \ " ?im nco:imNickname ?nick " \ " } " \ " } " \ ") " \ "(SELECT GROUP_CONCAT(fn:concat(?url_value, \"\31\", " \ "tracker:coalesce(rdfs:label(?c_role), \"\")), \"\30\") " \ "WHERE {" \ "?_contact nco:hasAffiliation ?c_role . " \ "?c_role nco:url ?url_value . " \ "})" \ "nie:url(nco:photo(?_contact)) " \ "nco:role(?_role) " \ "nco:contactUID(?_contact) " \ "nco:title(?_role) " \ "rdfs:label(?_role) " \ "nco:fullname(nco:org(?_role)) " \ "nco:department(?_role) " \ "(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\"," \ "tracker:coalesce(rdfs:label(?c_role), \"\"))," \ "\"\30\") " \ "WHERE { " \ "?_contact nco:hasAffiliation ?c_role . " \ "?c_role nco:hasEmailAddress " \ " [ nco:emailAddress ?emailaddress ] " \ "}) " \ "nmo:receivedDate(?_call) " \ "nmo:isSent(?_call) " \ "nmo:isAnswered(?_call) " \ "?_call " \ CALLS_CONSTRAINTS(CONSTRAINT) \ "ORDER BY DESC(nmo:sentDate(?_call)) " #define MISSED_CONSTRAINT \ "?_call nmo:from ?_unb_contact . " \ "?_call nmo:isSent false . " \ "?_call nmo:isAnswered false . " #define INCOMING_CONSTRAINT \ "?_call nmo:from ?_unb_contact . " \ "?_call nmo:isSent false . " \ "?_call nmo:isAnswered true . " #define OUTGOING_CONSTRAINT \ "?_call nmo:to ?_unb_contact . " \ "?_call nmo:isSent true . " #define COMBINED_CONSTRAINT \ "{ " \ " ?_call nmo:from ?_unb_contact . " \ " ?_call nmo:isSent false " \ "} UNION { " \ " ?_call nmo:to ?_unb_contact . " \ " ?_call nmo:isSent true " \ "} " #define CALL_URI_CONSTRAINT \ COMBINED_CONSTRAINT \ "FILTER (?_call = <%s>) " #define MISSED_CALLS_QUERY CALLS_QUERY(MISSED_CONSTRAINT) #define MISSED_CALLS_LIST CALLS_LIST(MISSED_CONSTRAINT) #define INCOMING_CALLS_QUERY CALLS_QUERY(INCOMING_CONSTRAINT) #define INCOMING_CALLS_LIST CALLS_LIST(INCOMING_CONSTRAINT) #define OUTGOING_CALLS_QUERY CALLS_QUERY(OUTGOING_CONSTRAINT) #define OUTGOING_CALLS_LIST CALLS_LIST(OUTGOING_CONSTRAINT) #define COMBINED_CALLS_QUERY CALLS_QUERY(COMBINED_CONSTRAINT) #define COMBINED_CALLS_LIST CALLS_LIST(COMBINED_CONSTRAINT) #define CONTACT_FROM_CALL_QUERY CALLS_QUERY(CALL_URI_CONSTRAINT) #define CONTACTS_QUERY_FROM_URI \ "SELECT " \ "(SELECT GROUP_CONCAT(fn:concat(rdf:type(?aff_number)," \ "\"\31\", nco:phoneNumber(?aff_number)), \"\30\")" \ "WHERE {" \ " ?_role nco:hasPhoneNumber ?aff_number" \ "}) " \ "nco:fullname(<%s>) " \ "nco:nameFamily(<%s>) " \ "nco:nameGiven(<%s>) " \ "nco:nameAdditional(<%s>) " \ "nco:nameHonorificPrefix(<%s>) " \ "nco:nameHonorificSuffix(<%s>) " \ "(SELECT GROUP_CONCAT(fn:concat(" \ "tracker:coalesce(nco:pobox(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:locality(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:region(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \"\37\"," \ "tracker:coalesce(nco:country(?aff_addr), \"\"), " \ "\"\31\", rdfs:label(?_role) ), " \ "\"\30\") " \ "WHERE {" \ "?_role nco:hasPostalAddress ?aff_addr" \ "}) " \ "nco:birthDate(<%s>) " \ "(SELECT " \ " ?nick " \ " WHERE { " \ " { " \ " ?_contact nco:nickname ?nick " \ " } UNION { " \ " ?_contact nco:hasAffiliation ?role . " \ " ?role nco:hasIMAddress ?im . " \ " ?im nco:imNickname ?nick " \ " } " \ " FILTER (?_contact = <%s>)" \ " } " \ ") " \ "(SELECT GROUP_CONCAT(fn:concat( " \ "?url_val, \"\31\", tracker:coalesce(rdfs:label(?_role), \"\") "\ "), \"\30\") " \ "WHERE {" \ "?_role nco:url ?url_val . " \ "})" \ "nie:url(nco:photo(<%s>)) " \ "nco:role(?_role) " \ "nco:contactUID(<%s>) " \ "nco:title(?_role) " \ "rdfs:label(?_role) " \ "nco:fullname(nco:org(?_role))" \ "nco:department(?_role) " \ "(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\"," \ "tracker:coalesce(rdfs:label(?_role), \"\"))," \ "\"\30\") " \ "WHERE { " \ "?_role nco:hasEmailAddress " \ " [ nco:emailAddress ?emailaddress ] " \ "}) " \ "\"NOTACALL\" \"false\" \"false\" " \ "<%s> " \ "WHERE {" \ " <%s> a nco:PersonContact ." \ " OPTIONAL {<%s> nco:hasAffiliation ?_role .}" \ "}" #define CONTACTS_OTHER_QUERY_FROM_URI \ "SELECT fn:concat(\"TYPE_OTHER\", \"\31\", nco:phoneNumber(?t))"\ "\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" " \ "\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" " \ " \"NOTACALL\" \"false\" \"false\" <%s> " \ "WHERE { " \ "<%s> a nco:Contact . " \ "OPTIONAL { <%s> nco:hasPhoneNumber ?t . } " \ "} " #define CONTACTS_COUNT_QUERY \ "SELECT COUNT(?c) " \ "WHERE {" \ "?c a nco:PersonContact ." \ "}" #define MISSED_CALLS_COUNT_QUERY \ "SELECT COUNT(?call) WHERE {" \ "?c a nco:Contact ;" \ "nco:hasPhoneNumber ?h ." \ "?call a nmo:Call ;" \ "nmo:isSent false ;" \ "nmo:from ?c ;" \ "nmo:isAnswered false ." \ "}" #define INCOMING_CALLS_COUNT_QUERY \ "SELECT COUNT(?call) WHERE {" \ "?c a nco:Contact ;" \ "nco:hasPhoneNumber ?h ." \ "?call a nmo:Call ;" \ "nmo:isSent false ;" \ "nmo:from ?c ;" \ "nmo:isAnswered true ." \ "}" #define OUTGOING_CALLS_COUNT_QUERY \ "SELECT COUNT(?call) WHERE {" \ "?c a nco:Contact ;" \ "nco:hasPhoneNumber ?h ." \ "?call a nmo:Call ;" \ "nmo:isSent true ;" \ "nmo:to ?c ." \ "}" #define COMBINED_CALLS_COUNT_QUERY \ "SELECT COUNT(?call) WHERE {" \ "{" \ "?c a nco:Contact ;" \ "nco:hasPhoneNumber ?h ." \ "?call a nmo:Call ;" \ "nmo:isSent true ;" \ "nmo:to ?c ." \ "}UNION {" \ "?c a nco:Contact ;" \ "nco:hasPhoneNumber ?h ." \ "?call a nmo:Call ;" \ "nmo:from ?c ." \ "}" \ "}" #define NEW_MISSED_CALLS_COUNT_QUERY \ "SELECT COUNT(?call) WHERE {" \ "?c a nco:Contact ;" \ "nco:hasPhoneNumber ?h ." \ "?call a nmo:Call ;" \ "nmo:isSent false ;" \ "nmo:from ?c ;" \ "nmo:isAnswered false ;" \ "nmo:isRead false ." \ "}" typedef int (*reply_list_foreach_t) (const char **reply, int num_fields, void *user_data); typedef void (*add_field_t) (struct phonebook_contact *contact, const char *value, int type); struct pending_reply { reply_list_foreach_t callback; void *user_data; int num_fields; }; struct contact_data { char *id; struct phonebook_contact *contact; }; struct phonebook_data { phonebook_cb cb; void *user_data; int index; gboolean vcardentry; const struct apparam_field *params; GSList *contacts; phonebook_cache_ready_cb ready_cb; phonebook_entry_cb entry_cb; int newmissedcalls; GCancellable *query_canc; char *req_name; int vcard_part_count; int tracker_index; }; struct phonebook_index { GArray *phonebook; int index; }; static TrackerSparqlConnection *connection = NULL; static const char *name2query(const char *name) { if (g_str_equal(name, PB_CONTACTS)) return CONTACTS_QUERY_ALL; else if (g_str_equal(name, PB_CALLS_INCOMING)) return INCOMING_CALLS_QUERY; else if (g_str_equal(name, PB_CALLS_OUTGOING)) return OUTGOING_CALLS_QUERY; else if (g_str_equal(name, PB_CALLS_MISSED)) return MISSED_CALLS_QUERY; else if (g_str_equal(name, PB_CALLS_COMBINED)) return COMBINED_CALLS_QUERY; return NULL; } static const char *name2count_query(const char *name) { if (g_str_equal(name, PB_CONTACTS)) return CONTACTS_COUNT_QUERY; else if (g_str_equal(name, PB_CALLS_INCOMING)) return INCOMING_CALLS_COUNT_QUERY; else if (g_str_equal(name, PB_CALLS_OUTGOING)) return OUTGOING_CALLS_COUNT_QUERY; else if (g_str_equal(name, PB_CALLS_MISSED)) return MISSED_CALLS_COUNT_QUERY; else if (g_str_equal(name, PB_CALLS_COMBINED)) return COMBINED_CALLS_COUNT_QUERY; return NULL; } static gboolean folder_is_valid(const char *folder) { if (folder == NULL) return FALSE; if (g_str_equal(folder, "/")) return TRUE; else if (g_str_equal(folder, PB_TELECOM_FOLDER)) return TRUE; else if (g_str_equal(folder, PB_CONTACTS_FOLDER)) return TRUE; else if (g_str_equal(folder, PB_CALLS_INCOMING_FOLDER)) return TRUE; else if (g_str_equal(folder, PB_CALLS_OUTGOING_FOLDER)) return TRUE; else if (g_str_equal(folder, PB_CALLS_MISSED_FOLDER)) return TRUE; else if (g_str_equal(folder, PB_CALLS_COMBINED_FOLDER)) return TRUE; return FALSE; } static const char *folder2query(const char *folder) { if (g_str_equal(folder, PB_CONTACTS_FOLDER)) return CONTACTS_QUERY_ALL_LIST; else if (g_str_equal(folder, PB_CALLS_INCOMING_FOLDER)) return INCOMING_CALLS_LIST; else if (g_str_equal(folder, PB_CALLS_OUTGOING_FOLDER)) return OUTGOING_CALLS_LIST; else if (g_str_equal(folder, PB_CALLS_MISSED_FOLDER)) return MISSED_CALLS_LIST; else if (g_str_equal(folder, PB_CALLS_COMBINED_FOLDER)) return COMBINED_CALLS_LIST; return NULL; } static const char **string_array_from_cursor(TrackerSparqlCursor *cursor, int array_len) { const char **result; int i; result = g_new0(const char *, array_len); for (i = 0; i < array_len; ++i) { TrackerSparqlValueType type; type = tracker_sparql_cursor_get_value_type(cursor, i); if (type == TRACKER_SPARQL_VALUE_TYPE_BLANK_NODE || type == TRACKER_SPARQL_VALUE_TYPE_UNBOUND) /* For null/unbound type filling result part with ""*/ result[i] = ""; else /* Filling with string representation of content*/ result[i] = tracker_sparql_cursor_get_string(cursor, i, NULL); } return result; } static void update_cancellable(struct phonebook_data *pdata, GCancellable *canc) { if (pdata->query_canc) g_object_unref(pdata->query_canc); pdata->query_canc = canc; } static void async_query_cursor_next_cb(GObject *source, GAsyncResult *result, gpointer user_data) { struct pending_reply *pending = user_data; TrackerSparqlCursor *cursor = TRACKER_SPARQL_CURSOR(source); GCancellable *cancellable; GError *error = NULL; gboolean success; const char **node; int err; success = tracker_sparql_cursor_next_finish( TRACKER_SPARQL_CURSOR(source), result, &error); if (!success) { if (error) { DBG("cursor_next error: %s", error->message); g_error_free(error); } else /* When tracker_sparql_cursor_next_finish ends with * failure and no error is set, that means end of * results returned by query */ pending->callback(NULL, 0, pending->user_data); goto failed; } node = string_array_from_cursor(cursor, pending->num_fields); err = pending->callback(node, pending->num_fields, pending->user_data); g_free(node); /* Fetch next result only if processing current chunk ended with * success. Sometimes during processing data, we are able to determine * if there is no need to get more data from tracker - by example * stored amount of data parts is big enough for sending and we might * want to suspend processing or just some error occurred. */ if (!err) { cancellable = g_cancellable_new(); update_cancellable(pending->user_data, cancellable); tracker_sparql_cursor_next_async(cursor, cancellable, async_query_cursor_next_cb, pending); return; } failed: g_object_unref(cursor); g_free(pending); } static int query_tracker(const char *query, int num_fields, reply_list_foreach_t callback, void *user_data) { struct pending_reply *pending; GCancellable *cancellable; TrackerSparqlCursor *cursor; GError *error = NULL; DBG(""); if (connection == NULL) connection = tracker_sparql_connection_get_direct( NULL, &error); if (!connection) { if (error) { DBG("direct-connection error: %s", error->message); g_error_free(error); } return -EINTR; } cancellable = g_cancellable_new(); update_cancellable(user_data, cancellable); cursor = tracker_sparql_connection_query(connection, query, cancellable, &error); if (cursor == NULL) { if (error) { DBG("connection_query error: %s", error->message); g_error_free(error); } g_object_unref(cancellable); return -EINTR; } pending = g_new0(struct pending_reply, 1); pending->callback = callback; pending->user_data = user_data; pending->num_fields = num_fields; /* Now asynchronously going through each row of results - callback * async_query_cursor_next_cb will be called ALWAYS, even if async * request was canceled */ tracker_sparql_cursor_next_async(cursor, cancellable, async_query_cursor_next_cb, pending); return 0; } static char *iso8601_utc_to_localtime(const char *datetime) { time_t time; struct tm tm, *local; char localdate[32]; int nr; memset(&tm, 0, sizeof(tm)); nr = sscanf(datetime, "%04u-%02u-%02uT%02u:%02u:%02u", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec); if (nr < 6) { /* Invalid time format */ error("sscanf(): %s (%d)", strerror(errno), errno); return g_strdup(""); } /* Time already in localtime */ if (!g_str_has_suffix(datetime, "Z")) { strftime(localdate, sizeof(localdate), "%Y%m%dT%H%M%S", &tm); return g_strdup(localdate); } tm.tm_year -= 1900; /* Year since 1900 */ tm.tm_mon--; /* Months since January, values 0-11 */ time = mktime(&tm); time -= timezone; local = localtime(&time); strftime(localdate, sizeof(localdate), "%Y%m%dT%H%M%S", local); return g_strdup(localdate); } static void set_call_type(struct phonebook_contact *contact, const char *datetime, const char *is_sent, const char *is_answered) { gboolean sent, answered; if (g_strcmp0(datetime, "NOTACALL") == 0) { contact->calltype = CALL_TYPE_NOT_A_CALL; return; } sent = g_str_equal(is_sent, "true"); answered = g_str_equal(is_answered, "true"); if (sent == FALSE) { if (answered == FALSE) contact->calltype = CALL_TYPE_MISSED; else contact->calltype = CALL_TYPE_INCOMING; } else contact->calltype = CALL_TYPE_OUTGOING; /* Tracker gives time in the ISO 8601 format, UTC time */ contact->datetime = iso8601_utc_to_localtime(datetime); } static gboolean contact_matches(struct contact_data *c_data, const char *id, const char *datetime) { char *localtime; int cmp_ret; if (g_strcmp0(c_data->id, id) != 0) return FALSE; /* id is equal and not call history entry => contact matches */ if (c_data->contact->calltype == CALL_TYPE_NOT_A_CALL) return TRUE; /* for call history entries have to compare also timestamps of calls */ localtime = iso8601_utc_to_localtime(datetime); cmp_ret = g_strcmp0(c_data->contact->datetime, localtime); g_free(localtime); return (cmp_ret == 0) ? TRUE : FALSE; } static struct phonebook_contact *find_contact(GSList *contacts, const char *id, const char *datetime) { GSList *l; for (l = contacts; l; l = l->next) { struct contact_data *c_data = l->data; if (contact_matches(c_data, id, datetime)) return c_data->contact; } return NULL; } static struct phonebook_field *find_field(GSList *fields, const char *value, int type) { GSList *l; for (l = fields; l; l = l->next) { struct phonebook_field *field = l->data; /* Returning phonebook number if phone values and type values * are equal */ if (g_strcmp0(field->text, value) == 0 && field->type == type) return field; } return NULL; } static void add_phone_number(struct phonebook_contact *contact, const char *phone, int type) { struct phonebook_field *number; if (phone == NULL || strlen(phone) == 0) return; /* Not adding number if there is already added with the same value */ if (find_field(contact->numbers, phone, type)) return; number = g_new0(struct phonebook_field, 1); number->text = g_strdup(phone); number->type = type; contact->numbers = g_slist_append(contact->numbers, number); } static void add_email(struct phonebook_contact *contact, const char *address, int type) { struct phonebook_field *email; if (address == NULL || strlen(address) == 0) return; /* Not adding email if there is already added with the same value */ if (find_field(contact->emails, address, type)) return; email = g_new0(struct phonebook_field, 1); email->text = g_strdup(address); email->type = type; contact->emails = g_slist_append(contact->emails, email); } static gboolean addr_matches(struct phonebook_addr *a, struct phonebook_addr *b) { GSList *la, *lb; if (a->type != b->type) return FALSE; for (la = a->fields, lb = b->fields; la && lb; la = la->next, lb = lb->next) { char *field_a = la->data; char *field_b = lb->data; if (g_strcmp0(field_a, field_b) != 0) return FALSE; } return TRUE; } /* generates phonebook_addr struct from tracker address data string. */ static struct phonebook_addr *gen_addr(const char *address, int type) { struct phonebook_addr *addr; GSList *fields = NULL; char **addr_parts; int i; /* This test handles cases when address points to empty string * (or address is NULL pointer) or string containing only six * separators. It indicates that none of address fields is present * and there is no sense to create dummy phonebook_addr struct */ if (address == NULL || strlen(address) < ADDR_FIELD_AMOUNT) return NULL; addr_parts = g_strsplit(address, ADDR_DELIM, ADDR_FIELD_AMOUNT); for (i = 0; i < ADDR_FIELD_AMOUNT; ++i) fields = g_slist_append(fields, g_strdup(addr_parts[i])); g_strfreev(addr_parts); addr = g_new0(struct phonebook_addr, 1); addr->fields = fields; addr->type = type; return addr; } static void add_address(struct phonebook_contact *contact, const char *address, int type) { struct phonebook_addr *addr; GSList *l; addr = gen_addr(address, type); if (addr == NULL) return; /* Not adding address if there is already added with the same value. * These type of checks have to be done because sometimes tracker * returns results for contact data in more than 1 row - then the same * address may be returned more than once in query results */ for (l = contact->addresses; l; l = l->next) { struct phonebook_addr *tmp = l->data; if (addr_matches(tmp, addr)) { phonebook_addr_free(addr); return; } } contact->addresses = g_slist_append(contact->addresses, addr); } static void add_url(struct phonebook_contact *contact, const char *url_val, int type) { struct phonebook_field *url; if (url_val == NULL || strlen(url_val) == 0) return; /* Not adding url if there is already added with the same value */ if (find_field(contact->urls, url_val, type)) return; url = g_new0(struct phonebook_field, 1); url->text = g_strdup(url_val); url->type = type; contact->urls = g_slist_append(contact->urls, url); } static GString *gen_vcards(GSList *contacts, const struct apparam_field *params) { GSList *l; GString *vcards; vcards = g_string_new(NULL); /* Generating VCARD string from contacts and freeing used contacts */ for (l = contacts; l; l = l->next) { struct contact_data *c_data = l->data; phonebook_add_contact(vcards, c_data->contact, params->filter, params->format); } return vcards; } static int pull_contacts_size(const char **reply, int num_fields, void *user_data) { struct phonebook_data *data = user_data; if (num_fields < 0) { data->cb(NULL, 0, num_fields, 0, TRUE, data->user_data); return -EINTR; } if (reply != NULL) { data->index = atoi(reply[0]); return 0; } data->cb(NULL, 0, data->index, data->newmissedcalls, TRUE, data->user_data); return 0; /* * phonebook_data is freed in phonebook_req_finalize. Useful in * cases when call is terminated. */ } static void add_affiliation(char **field, const char *value) { if (strlen(*field) > 0 || value == NULL || strlen(value) == 0) return; g_free(*field); *field = g_strdup(value); } static void contact_init(struct phonebook_contact *contact, const char **reply) { if (reply[COL_FAMILY_NAME][0] == '\0' && reply[COL_GIVEN_NAME][0] == '\0' && reply[COL_ADDITIONAL_NAME][0] == '\0' && reply[COL_NAME_PREFIX][0] == '\0' && reply[COL_NAME_SUFFIX][0] == '\0') { if (reply[COL_FULL_NAME][0] != '\0') contact->family = g_strdup(reply[COL_FULL_NAME]); else contact->family = g_strdup(reply[COL_NICKNAME]); } else { contact->family = g_strdup(reply[COL_FAMILY_NAME]); contact->given = g_strdup(reply[COL_GIVEN_NAME]); contact->additional = g_strdup(reply[COL_ADDITIONAL_NAME]); contact->prefix = g_strdup(reply[COL_NAME_PREFIX]); contact->suffix = g_strdup(reply[COL_NAME_SUFFIX]); } contact->fullname = g_strdup(reply[COL_FULL_NAME]); contact->birthday = g_strdup(reply[COL_BIRTH_DATE]); contact->nickname = g_strdup(reply[COL_NICKNAME]); contact->photo = g_strdup(reply[COL_PHOTO]); contact->company = g_strdup(reply[COL_ORG_NAME]); contact->department = g_strdup(reply[COL_ORG_DEPARTMENT]); contact->role = g_strdup(reply[COL_ORG_ROLE]); contact->uid = g_strdup(reply[COL_UID]); contact->title = g_strdup(reply[COL_TITLE]); set_call_type(contact, reply[COL_DATE], reply[COL_SENT], reply[COL_ANSWERED]); } static enum phonebook_number_type get_phone_type(const char *affiliation) { if (g_strcmp0(AFFILIATION_HOME, affiliation) == 0) return TEL_TYPE_HOME; else if (g_strcmp0(AFFILIATION_WORK, affiliation) == 0) return TEL_TYPE_WORK; return TEL_TYPE_OTHER; } static void add_aff_number(struct phonebook_contact *contact, const char *pnumber, const char *aff_type) { char **num_parts; char *type, *number; /* For phone taken directly from contacts data, phone number string * is represented as number type and number string - those strings are * separated by SUB_DELIM string */ num_parts = g_strsplit(pnumber, SUB_DELIM, 2); if (!num_parts) return; if (num_parts[0]) type = num_parts[0]; else goto failed; if (num_parts[1]) number = num_parts[1]; else goto failed; if (g_strrstr(type, FAX_NUM_TYPE)) add_phone_number(contact, number, TEL_TYPE_FAX); else if (g_strrstr(type, MOBILE_NUM_TYPE)) add_phone_number(contact, number, TEL_TYPE_MOBILE); else /* if this is no fax/mobile phone, then adding phone number * type based on type of the affiliation field */ add_phone_number(contact, number, get_phone_type(aff_type)); failed: g_strfreev(num_parts); } static void contact_add_numbers(struct phonebook_contact *contact, const char **reply) { char **aff_numbers; int i; /* Filling phone numbers from contact's affiliation */ aff_numbers = g_strsplit(reply[COL_PHONE_AFF], MAIN_DELIM, MAX_FIELDS); if (aff_numbers) for (i = 0; aff_numbers[i]; ++i) add_aff_number(contact, aff_numbers[i], reply[COL_AFF_TYPE]); g_strfreev(aff_numbers); } static enum phonebook_field_type get_field_type(const char *affiliation) { if (g_strcmp0(AFFILIATION_HOME, affiliation) == 0) return FIELD_TYPE_HOME; else if (g_strcmp0(AFFILIATION_WORK, affiliation) == 0) return FIELD_TYPE_WORK; return FIELD_TYPE_OTHER; } static void add_aff_field(struct phonebook_contact *contact, const char *aff_email, add_field_t add_field_cb) { char **email_parts; char *type, *email; /* Emails from affiliation data, are represented as real email * string and affiliation type - those strings are separated by * SUB_DELIM string */ email_parts = g_strsplit(aff_email, SUB_DELIM, 2); if (!email_parts) return; if (email_parts[0]) email = email_parts[0]; else goto failed; if (email_parts[1]) type = email_parts[1]; else goto failed; add_field_cb(contact, email, get_field_type(type)); failed: g_strfreev(email_parts); } static void contact_add_emails(struct phonebook_contact *contact, const char **reply) { char **aff_emails; int i; /* Emails from affiliation */ aff_emails = g_strsplit(reply[COL_EMAIL_AFF], MAIN_DELIM, MAX_FIELDS); if (aff_emails) for (i = 0; aff_emails[i] != NULL; ++i) add_aff_field(contact, aff_emails[i], add_email); g_strfreev(aff_emails); } static void contact_add_addresses(struct phonebook_contact *contact, const char **reply) { char **aff_addr; int i; /* Addresses from affiliation */ aff_addr = g_strsplit(reply[COL_ADDR_AFF], MAIN_DELIM, MAX_FIELDS); if (aff_addr) for (i = 0; aff_addr[i] != NULL; ++i) add_aff_field(contact, aff_addr[i], add_address); g_strfreev(aff_addr); } static void contact_add_urls(struct phonebook_contact *contact, const char **reply) { char **aff_url; int i; /* Addresses from affiliation */ aff_url = g_strsplit(reply[COL_URL], MAIN_DELIM, MAX_FIELDS); if (aff_url) for (i = 0; aff_url[i] != NULL; ++i) add_aff_field(contact, aff_url[i], add_url); g_strfreev(aff_url); } static void contact_add_organization(struct phonebook_contact *contact, const char **reply) { /* Adding fields connected by nco:hasAffiliation - they may be in * separate replies */ add_affiliation(&contact->title, reply[COL_TITLE]); add_affiliation(&contact->company, reply[COL_ORG_NAME]); add_affiliation(&contact->department, reply[COL_ORG_DEPARTMENT]); add_affiliation(&contact->role, reply[COL_ORG_ROLE]); } static void free_data_contacts(struct phonebook_data *data) { GSList *l; /* freeing contacts */ for (l = data->contacts; l; l = l->next) { struct contact_data *c_data = l->data; g_free(c_data->id); phonebook_contact_free(c_data->contact); g_free(c_data); } g_slist_free(data->contacts); data->contacts = NULL; } static void send_pull_part(struct phonebook_data *data, const struct apparam_field *params, gboolean lastpart) { GString *vcards; DBG(""); vcards = gen_vcards(data->contacts, params); data->cb(vcards->str, vcards->len, g_slist_length(data->contacts), data->newmissedcalls, lastpart, data->user_data); if (!lastpart) free_data_contacts(data); g_string_free(vcards, TRUE); } static int pull_contacts(const char **reply, int num_fields, void *user_data) { struct phonebook_data *data = user_data; const struct apparam_field *params = data->params; struct phonebook_contact *contact; struct contact_data *contact_data; int last_index, i; gboolean cdata_present = FALSE, part_sent = FALSE; static char *temp_id = NULL; if (num_fields < 0) { data->cb(NULL, 0, num_fields, 0, TRUE, data->user_data); goto fail; } DBG("reply %p", reply); data->tracker_index++; if (reply == NULL) goto done; /* Trying to find contact in recently added contacts. It is needed for * contacts that have more than one telephone number filled */ contact = find_contact(data->contacts, reply[CONTACTS_ID_COL], reply[COL_DATE]); /* If contact is already created then adding only new phone numbers */ if (contact) { cdata_present = TRUE; goto add_numbers; } /* We are doing a PullvCardEntry, no need for those checks */ if (data->vcardentry) goto add_entry; /* Last four fields are always present, ignoring them */ for (i = 0; i < num_fields - 4; i++) { if (reply[i][0] != '\0') break; } if (i == num_fields - 4 && !g_str_equal(reply[CONTACTS_ID_COL], TRACKER_DEFAULT_CONTACT_ME)) return 0; if (g_strcmp0(temp_id, reply[CONTACTS_ID_COL])) { data->index++; g_free(temp_id); temp_id = g_strdup(reply[CONTACTS_ID_COL]); /* Incrementing counter for vcards in current part of data, * but only if liststartoffset has been already reached */ if (data->index > params->liststartoffset) data->vcard_part_count++; } if (data->vcard_part_count > VCARDS_PART_COUNT) { DBG("Part of vcard data ready for sending..."); data->vcard_part_count = 0; /* Sending part of data to PBAP core - more data can be still * fetched, so marking lastpart as FALSE */ send_pull_part(data, params, FALSE); /* Later, after adding contact data, need to return -EINTR to * stop fetching more data for this request. Data will be * downloaded again from this point, when phonebook_pull_read * will be called again with current request as a parameter*/ part_sent = TRUE; } last_index = params->liststartoffset + params->maxlistcount; if (data->index <= params->liststartoffset) return 0; /* max number of results achieved - need send vcards data that was * already collected and stop further data processing (these operations * will be invoked in "done" section) */ if (data->index > last_index && params->maxlistcount > 0) { DBG("Maxlistcount achieved"); goto done; } add_entry: contact = g_new0(struct phonebook_contact, 1); contact_init(contact, reply); add_numbers: contact_add_numbers(contact, reply); contact_add_emails(contact, reply); contact_add_addresses(contact, reply); contact_add_urls(contact, reply); contact_add_organization(contact, reply); DBG("contact %p", contact); /* Adding contacts data to wrapper struct - this data will be used to * generate vcard list */ if (!cdata_present) { contact_data = g_new0(struct contact_data, 1); contact_data->contact = contact; contact_data->id = g_strdup(reply[CONTACTS_ID_COL]); data->contacts = g_slist_append(data->contacts, contact_data); } if (part_sent) return -EINTR; return 0; done: /* Processing is end, this is definitely last part of transmission * (marking lastpart as TRUE) */ send_pull_part(data, params, TRUE); fail: g_free(temp_id); temp_id = NULL; return -EINTR; /* * phonebook_data is freed in phonebook_req_finalize. Useful in * cases when call is terminated. */ } static int add_to_cache(const char **reply, int num_fields, void *user_data) { struct phonebook_data *data = user_data; char *formatted; int i; if (reply == NULL || num_fields < 0) goto done; /* the first element is the URI, always not empty */ for (i = 1; i < num_fields; i++) { if (reply[i][0] != '\0') break; } if (i == num_fields && !g_str_equal(reply[0], TRACKER_DEFAULT_CONTACT_ME)) return 0; if (i == 7) formatted = g_strdup(reply[7]); else if (i == 6) formatted = g_strdup(reply[6]); else formatted = g_strdup_printf("%s;%s;%s;%s;%s", reply[1], reply[2], reply[3], reply[4], reply[5]); /* The owner vCard must have the 0 handle */ if (strcmp(reply[0], TRACKER_DEFAULT_CONTACT_ME) == 0) data->entry_cb(reply[0], 0, formatted, "", reply[6], data->user_data); else data->entry_cb(reply[0], PHONEBOOK_INVALID_HANDLE, formatted, "", reply[6], data->user_data); g_free(formatted); return 0; done: if (num_fields <= 0) data->ready_cb(data->user_data); return -EINTR; /* * phonebook_data is freed in phonebook_req_finalize. Useful in * cases when call is terminated. */ } int phonebook_init(void) { g_type_init(); return 0; } void phonebook_exit(void) { } char *phonebook_set_folder(const char *current_folder, const char *new_folder, uint8_t flags, int *err) { char *tmp1, *tmp2, *base, *path = NULL; gboolean root, child; int ret = 0; int len; root = (g_strcmp0("/", current_folder) == 0); child = (new_folder && strlen(new_folder) != 0); switch (flags) { case 0x02: /* Go back to root */ if (!child) { path = g_strdup("/"); goto done; } path = g_build_filename(current_folder, new_folder, NULL); break; case 0x03: /* Go up 1 level */ if (root) { /* Already root */ path = g_strdup("/"); goto done; } /* * Removing one level of the current folder. Current folder * contains AT LEAST one level since it is not at root folder. * Use glib utility functions to handle invalid chars in the * folder path properly. */ tmp1 = g_path_get_basename(current_folder); tmp2 = g_strrstr(current_folder, tmp1); len = tmp2 - (current_folder + 1); g_free(tmp1); if (len == 0) base = g_strdup("/"); else base = g_strndup(current_folder, len); /* Return: one level only */ if (!child) { path = base; goto done; } path = g_build_filename(base, new_folder, NULL); g_free(base); break; default: ret = -EBADR; break; } done: if (path && !folder_is_valid(path)) ret = -ENOENT; if (ret < 0) { g_free(path); path = NULL; } if (err) *err = ret; return path; } static int pull_newmissedcalls(const char **reply, int num_fields, void *user_data) { struct phonebook_data *data = user_data; reply_list_foreach_t pull_cb; int col_amount, err; const char *query; int nmissed; if (num_fields < 0) { data->cb(NULL, 0, num_fields, 0, TRUE, data->user_data); return -EINTR; } if (reply != NULL) { nmissed = atoi(reply[0]); data->newmissedcalls = nmissed <= UINT8_MAX ? nmissed : UINT8_MAX; DBG("newmissedcalls %d", data->newmissedcalls); return 0; } if (data->params->maxlistcount == 0) { query = name2count_query(PB_CALLS_MISSED); col_amount = COUNT_QUERY_COL_AMOUNT; pull_cb = pull_contacts_size; } else { query = name2query(PB_CALLS_MISSED); col_amount = PULL_QUERY_COL_AMOUNT; pull_cb = pull_contacts; } err = query_tracker(query, col_amount, pull_cb, data); if (err < 0) { data->cb(NULL, 0, err, 0, TRUE, data->user_data); return -EINTR; } return 0; } void phonebook_req_finalize(void *request) { struct phonebook_data *data = request; DBG(""); if (!data) return; /* canceling asynchronous operation on tracker if any is active */ if (data->query_canc) { g_cancellable_cancel(data->query_canc); g_object_unref(data->query_canc); } free_data_contacts(data); g_free(data->req_name); g_free(data); } void *phonebook_pull(const char *name, const struct apparam_field *params, phonebook_cb cb, void *user_data, int *err) { struct phonebook_data *data; DBG("name %s", name); data = g_new0(struct phonebook_data, 1); data->params = params; data->user_data = user_data; data->cb = cb; data->req_name = g_strdup(name); if (err) *err = 0; return data; } int phonebook_pull_read(void *request) { struct phonebook_data *data = request; reply_list_foreach_t pull_cb; const char *query; char *offset_query; int col_amount; int ret; if (!data) return -ENOENT; data->newmissedcalls = 0; if (g_strcmp0(data->req_name, PB_CALLS_MISSED) == 0 && data->tracker_index == 0) { /* new missed calls amount should be counted only once - it * will be done during generating first part of results of * missed calls history */ query = NEW_MISSED_CALLS_COUNT_QUERY; col_amount = COUNT_QUERY_COL_AMOUNT; pull_cb = pull_newmissedcalls; } else if (data->params->maxlistcount == 0) { query = name2count_query(data->req_name); col_amount = COUNT_QUERY_COL_AMOUNT; pull_cb = pull_contacts_size; } else { query = name2query(data->req_name); col_amount = PULL_QUERY_COL_AMOUNT; pull_cb = pull_contacts; } if (query == NULL) return -ENOENT; if (pull_cb == pull_contacts && data->tracker_index > 0) { /* Adding offset to pull query to download next parts of data * from tracker (phonebook_pull_read may be called many times * from PBAP core to fetch data partially) */ offset_query = g_strdup_printf(QUERY_OFFSET_FORMAT, query, data->tracker_index); ret = query_tracker(offset_query, col_amount, pull_cb, data); g_free(offset_query); return ret; } return query_tracker(query, col_amount, pull_cb, data); } void *phonebook_get_entry(const char *folder, const char *id, const struct apparam_field *params, phonebook_cb cb, void *user_data, int *err) { struct phonebook_data *data; char *query; int ret; DBG("folder %s id %s", folder, id); data = g_new0(struct phonebook_data, 1); data->user_data = user_data; data->params = params; data->cb = cb; data->vcardentry = TRUE; if (g_str_has_prefix(id, CONTACT_ID_PREFIX) == TRUE || g_strcmp0(id, TRACKER_DEFAULT_CONTACT_ME) == 0) query = g_strdup_printf(CONTACTS_QUERY_FROM_URI, id, id, id, id, id, id, id, id, id, id, id, id, id); else if (g_str_has_prefix(id, CALL_ID_PREFIX) == TRUE) query = g_strdup_printf(CONTACT_FROM_CALL_QUERY, id); else query = g_strdup_printf(CONTACTS_OTHER_QUERY_FROM_URI, id, id, id); ret = query_tracker(query, PULL_QUERY_COL_AMOUNT, pull_contacts, data); if (err) *err = ret; g_free(query); return data; } void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb, phonebook_cache_ready_cb ready_cb, void *user_data, int *err) { struct phonebook_data *data; const char *query; int ret; DBG("name %s", name); query = folder2query(name); if (query == NULL) { if (err) *err = -ENOENT; return NULL; } data = g_new0(struct phonebook_data, 1); data->entry_cb = entry_cb; data->ready_cb = ready_cb; data->user_data = user_data; ret = query_tracker(query, 8, add_to_cache, data); if (err) *err = ret; return data; } bluez-5.87/obexd/PaxHeaders/client0000644000000000000000000000005015221755531014163 xustar0020 atime=1783094113 20 ctime=1783094105 bluez-5.87/obexd/client/0000755000000000000000000000000015221755531013721 5ustar00rootrootbluez-5.87/obexd/client/PaxHeaders/map-event.h0000644000000000000000000000005014015011623016270 xustar0020 atime=1783093872 20 ctime=1783094104 bluez-5.87/obexd/client/map-event.h0000644000000000000000000000247614015011623015762 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX * * Copyright (C) 2013 BMW Car IT GmbH. All rights reserved. * * */ struct obc_session; enum map_event_type { MAP_ET_NEW_MESSAGE, MAP_ET_DELIVERY_SUCCESS, MAP_ET_SENDING_SUCCESS, MAP_ET_DELIVERY_FAILURE, MAP_ET_SENDING_FAILURE, MAP_ET_MEMORY_FULL, MAP_ET_MEMORY_AVAILABLE, MAP_ET_MESSAGE_DELETED, MAP_ET_MESSAGE_SHIFT }; struct map_event { enum map_event_type type; uint64_t handle; char *folder; char *old_folder; char *msg_type; char *datetime; char *subject; char *sender_name; char *priority; }; /* Handle notification in map client. * * event: Event report. * * Callback shall be called for every received event. */ typedef void (*map_event_cb) (struct map_event *event, void *user_data); /* Registers client notification handler callback for events that are * addressed to the given mas instance id for the given device. */ bool map_register_event_handler(struct obc_session *session, int mas_id, map_event_cb cb, void *user_data); /* Unregisters client notification handler callback. */ void map_unregister_event_handler(struct obc_session *session, int mas_id); /* Dispatch notification to a registered notification handler callback. */ void map_dispatch_event(int mas_id, const char *device, struct map_event *event); bluez-5.87/obexd/client/PaxHeaders/bluetooth.c0000644000000000000000000000005015063045254016407 xustar0020 atime=1783093889 20 ctime=1783094104 bluez-5.87/obexd/client/bluetooth.c0000644000000000000000000002475715063045254016107 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Client * * Copyright (C) 2012 Intel Corporation * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/rfcomm.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "gdbus/gdbus.h" #include "btio/btio.h" #include "obexd/src/log.h" #include "transport.h" #include "bluetooth.h" #define BT_RX_MTU 32767 #define BT_TX_MTU 32767 #define OBC_BT_ERROR obc_bt_error_quark() struct bluetooth_session { guint id; bdaddr_t src; bdaddr_t dst; uint16_t port; sdp_session_t *sdp; sdp_record_t *sdp_record; GIOChannel *io; char *service; obc_transport_func func; void *user_data; }; static GSList *sessions = NULL; static GQuark obc_bt_error_quark(void) { return g_quark_from_static_string("obc-bluetooth-error-quark"); } static void session_destroy(struct bluetooth_session *session) { DBG("%p", session); if (g_slist_find(sessions, session) == NULL) return; sessions = g_slist_remove(sessions, session); if (session->io != NULL) { g_io_channel_shutdown(session->io, TRUE, NULL); g_io_channel_unref(session->io); } if (session->sdp) sdp_close(session->sdp); if (session->sdp_record) sdp_record_free(session->sdp_record); g_free(session->service); g_free(session); } static void transport_callback(GIOChannel *io, GError *err, gpointer user_data) { struct bluetooth_session *session = user_data; DBG(""); if (session->func) session->func(io, err, session->user_data); if (err != NULL) session_destroy(session); } static GIOChannel *transport_connect(const bdaddr_t *src, const bdaddr_t *dst, uint16_t port, BtIOConnect function, gpointer user_data) { GIOChannel *io; GError *err = NULL; DBG("port %u", port); if (port > 31) { io = bt_io_connect(function, user_data, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_DEST_BDADDR, dst, BT_IO_OPT_PSM, port, BT_IO_OPT_MODE, BT_IO_MODE_ERTM, BT_IO_OPT_OMTU, BT_TX_MTU, BT_IO_OPT_IMTU, BT_RX_MTU, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_INVALID); } else { io = bt_io_connect(function, user_data, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_DEST_BDADDR, dst, BT_IO_OPT_CHANNEL, port, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_INVALID); } if (io != NULL) return io; error("%s", err->message); g_error_free(err); return NULL; } static void search_callback(uint8_t type, uint16_t status, uint8_t *rsp, size_t size, void *user_data) { struct bluetooth_session *session = user_data; unsigned int scanned, bytesleft = size; int seqlen = 0; uint8_t dataType; uint16_t port = 0; GError *gerr = NULL; if (status || type != SDP_SVC_SEARCH_ATTR_RSP) goto failed; scanned = sdp_extract_seqtype(rsp, bytesleft, &dataType, &seqlen); if (!scanned || !seqlen) goto failed; rsp += scanned; bytesleft -= scanned; do { sdp_record_t *rec; sdp_list_t *protos; sdp_data_t *data; int recsize, ch = -1; recsize = 0; rec = sdp_extract_pdu(rsp, bytesleft, &recsize); if (!rec) break; if (!recsize) { sdp_record_free(rec); break; } if (!sdp_get_access_protos(rec, &protos)) { ch = sdp_get_proto_port(protos, RFCOMM_UUID); sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); sdp_list_free(protos, NULL); protos = NULL; } data = sdp_data_get(rec, 0x0200); /* PSM must be odd and lsb of upper byte must be 0 */ if (data != NULL && (data->val.uint16 & 0x0101) == 0x0001) ch = data->val.uint16; /* Cache the sdp record associated with the service that we * attempt to connect. This allows reading its application * specific service attributes. */ if (ch > 0) { port = ch; session->sdp_record = rec; break; } sdp_record_free(rec); scanned += recsize; rsp += recsize; bytesleft -= recsize; } while (scanned < size && bytesleft > 0); if (port == 0) goto failed; session->port = port; g_io_channel_set_close_on_unref(session->io, FALSE); g_io_channel_unref(session->io); session->io = transport_connect(&session->src, &session->dst, port, transport_callback, session); if (session->io != NULL) { sdp_close(session->sdp); session->sdp = NULL; return; } failed: if (session->io != NULL) { g_io_channel_shutdown(session->io, TRUE, NULL); g_io_channel_unref(session->io); session->io = NULL; } g_set_error(&gerr, OBC_BT_ERROR, -EIO, "Unable to find service record"); if (session->func) session->func(session->io, gerr, session->user_data); g_clear_error(&gerr); session_destroy(session); } static gboolean process_callback(GIOChannel *io, GIOCondition cond, gpointer user_data) { struct bluetooth_session *session = user_data; if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) return FALSE; if (sdp_process(session->sdp) < 0) return FALSE; return TRUE; } static int bt_string2uuid(uuid_t *uuid, const char *string) { uint32_t data0, data4; uint16_t data1, data2, data3, data5; if (sscanf(string, "%08x-%04hx-%04hx-%04hx-%08x%04hx", &data0, &data1, &data2, &data3, &data4, &data5) == 6) { uint8_t val[16]; data0 = g_htonl(data0); data1 = g_htons(data1); data2 = g_htons(data2); data3 = g_htons(data3); data4 = g_htonl(data4); data5 = g_htons(data5); memcpy(&val[0], &data0, 4); memcpy(&val[4], &data1, 2); memcpy(&val[6], &data2, 2); memcpy(&val[8], &data3, 2); memcpy(&val[10], &data4, 4); memcpy(&val[14], &data5, 2); sdp_uuid128_create(uuid, val); return 0; } return -EINVAL; } static gboolean service_callback(GIOChannel *io, GIOCondition cond, gpointer user_data) { struct bluetooth_session *session = user_data; sdp_list_t *search, *attrid; uint32_t range = 0x0000ffff; GError *gerr = NULL; uuid_t uuid; if (cond & G_IO_NVAL) return FALSE; if (cond & G_IO_ERR) goto failed; if (sdp_set_notify(session->sdp, search_callback, session) < 0) goto failed; if (bt_string2uuid(&uuid, session->service) < 0) goto failed; sdp_uuid128_to_uuid(&uuid); search = sdp_list_append(NULL, &uuid); attrid = sdp_list_append(NULL, &range); if (sdp_service_search_attr_async(session->sdp, search, SDP_ATTR_REQ_RANGE, attrid) < 0) { sdp_list_free(attrid, NULL); sdp_list_free(search, NULL); goto failed; } sdp_list_free(attrid, NULL); sdp_list_free(search, NULL); g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, process_callback, session); return FALSE; failed: g_io_channel_shutdown(session->io, TRUE, NULL); g_io_channel_unref(session->io); session->io = NULL; g_set_error(&gerr, OBC_BT_ERROR, -EIO, "Unable to find service record"); if (session->func) session->func(session->io, gerr, session->user_data); g_clear_error(&gerr); session_destroy(session); return FALSE; } static sdp_session_t *service_connect(const bdaddr_t *src, const bdaddr_t *dst, GIOFunc function, gpointer user_data) { struct bluetooth_session *session = user_data; sdp_session_t *sdp; GIOChannel *io; DBG(""); sdp = sdp_connect(src, dst, SDP_NON_BLOCKING); if (sdp == NULL) return NULL; io = g_io_channel_unix_new(sdp_get_socket(sdp)); if (io == NULL) { sdp_close(sdp); return NULL; } g_io_add_watch(io, G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL, function, user_data); session->io = io; return sdp; } static int session_connect(struct bluetooth_session *session) { int err; DBG("session %p", session); if (session->port > 0) { session->io = transport_connect(&session->src, &session->dst, session->port, transport_callback, session); err = (session->io == NULL) ? -EINVAL : 0; } else { session->sdp = service_connect(&session->src, &session->dst, service_callback, session); err = (session->sdp == NULL) ? -ENOMEM : 0; } return err; } static guint bluetooth_connect(const char *source, const char *destination, const char *service, uint16_t port, obc_transport_func func, void *user_data) { struct bluetooth_session *session; static guint id = 0; DBG("src %s dest %s service %s port %u", source, destination, service, port); if (destination == NULL) return 0; session = g_try_malloc0(sizeof(*session)); if (session == NULL) return 0; session->id = ++id; session->func = func; session->port = port; session->user_data = user_data; str2ba(destination, &session->dst); str2ba(source, &session->src); if (session_connect(session) < 0) { g_free(session); return 0; } session->service = g_strdup(service); sessions = g_slist_prepend(sessions, session); return session->id; } static void bluetooth_disconnect(guint id) { GSList *l; DBG(""); for (l = sessions; l; l = l->next) { struct bluetooth_session *session = l->data; if (session->id == id) { session_destroy(session); return; } } } static int bluetooth_getpacketopt(GIOChannel *io, int *tx_mtu, int *rx_mtu) { int sk = g_io_channel_unix_get_fd(io); int type; uint16_t omtu = BT_TX_MTU; uint16_t imtu = BT_RX_MTU; socklen_t len = sizeof(int); DBG(""); if (getsockopt(sk, SOL_SOCKET, SO_TYPE, &type, &len) < 0) return -errno; if (type != SOCK_SEQPACKET) return -EINVAL; if (!bt_io_get(io, NULL, BT_IO_OPT_OMTU, &omtu, BT_IO_OPT_IMTU, &imtu, BT_IO_OPT_INVALID)) return -EINVAL; if (tx_mtu) *tx_mtu = omtu; if (rx_mtu) *rx_mtu = imtu; return 0; } static const void *bluetooth_getattribute(guint id, int attribute_id) { GSList *l; sdp_data_t *data; for (l = sessions; l; l = l->next) { struct bluetooth_session *session = l->data; if (session->id != id) continue; if (session->sdp_record == NULL) break; /* Read version since UUID is already known */ if (attribute_id == SDP_ATTR_PFILE_DESC_LIST) { sdp_list_t *descs; void *ret = NULL; if (sdp_get_profile_descs(session->sdp_record, &descs) < 0) return NULL; if (descs && descs->data) { sdp_profile_desc_t *desc = descs->data; ret = GINT_TO_POINTER(desc->version); } sdp_list_free(descs, free); return ret; } data = sdp_data_get(session->sdp_record, attribute_id); if (!data) break; return &data->val; } return NULL; } static struct obc_transport bluetooth = { .name = "Bluetooth", .connect = bluetooth_connect, .getpacketopt = bluetooth_getpacketopt, .disconnect = bluetooth_disconnect, .getattribute = bluetooth_getattribute, }; int bluetooth_init(void) { DBG(""); return obc_transport_register(&bluetooth); } void bluetooth_exit(void) { DBG(""); obc_transport_unregister(&bluetooth); } bluez-5.87/obexd/client/PaxHeaders/pbap.h0000644000000000000000000000005014015011623015316 xustar0020 atime=1783093885 20 ctime=1783094104 bluez-5.87/obexd/client/pbap.h0000644000000000000000000000036114015011623014777 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Client * * Copyright (C) 2007-2010 Intel Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ int pbap_init(void); void pbap_exit(void); bluez-5.87/obexd/client/PaxHeaders/transfer.h0000644000000000000000000000005014711225434016232 xustar0020 atime=1783093885 20 ctime=1783094105 bluez-5.87/obexd/client/transfer.h0000644000000000000000000000321114711225434015710 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Client * * Copyright (C) 2007-2010 Marcel Holtmann * Copyright (C) 2011-2012 BMW Car IT GmbH. All rights reserved. * * */ struct obc_transfer; typedef void (*transfer_callback_t) (struct obc_transfer *transfer, GError *err, void *user_data); struct obc_transfer *obc_transfer_get(const char *type, const char *name, const char *filename, GError **err); struct obc_transfer *obc_transfer_put(const char *type, const char *name, const char *filename, const void *contents, size_t size, GError **err); gboolean obc_transfer_register(struct obc_transfer *transfer, DBusConnection *conn, const char *session, const char *owner, GError **err); void obc_transfer_unregister(struct obc_transfer *transfer); gboolean obc_transfer_set_callback(struct obc_transfer *transfer, transfer_callback_t func, void *user_data); gboolean obc_transfer_start(struct obc_transfer *transfer, void *obex, GError **err); guint8 obc_transfer_get_operation(struct obc_transfer *transfer); void obc_transfer_set_apparam(struct obc_transfer *transfer, void *data); void *obc_transfer_get_apparam(struct obc_transfer *transfer); int obc_transfer_get_contents(struct obc_transfer *transfer, char **contents, size_t *size); const char *obc_transfer_get_path(struct obc_transfer *transfer); gint64 obc_transfer_get_size(struct obc_transfer *transfer); DBusMessage *obc_transfer_create_dbus_reply(struct obc_transfer *transfer, DBusMessage *message); void obc_transfer_add_header(struct obc_transfer *transfer, void *data); bluez-5.87/obexd/client/PaxHeaders/ftp.h0000644000000000000000000000005014015011623015165 xustar0020 atime=1783093885 20 ctime=1783094105 bluez-5.87/obexd/client/ftp.h0000644000000000000000000000030014015011623014637 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Client * * Copyright (C) 2007-2010 Marcel Holtmann * * */ int ftp_init(void); void ftp_exit(void); bluez-5.87/obexd/client/PaxHeaders/transport.c0000644000000000000000000000005014015011623016423 xustar0020 atime=1783093908 20 ctime=1783094105 bluez-5.87/obexd/client/transport.c0000644000000000000000000000250114015011623016102 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2012 Intel Corporation * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include "obexd/src/log.h" #include "transport.h" static GSList *transports = NULL; struct obc_transport *obc_transport_find(const char *name) { GSList *l; for (l = transports; l; l = l->next) { struct obc_transport *transport = l->data; if (strcasecmp(name, transport->name) == 0) return transport; } return NULL; } int obc_transport_register(struct obc_transport *transport) { if (!transport) { error("Invalid transport"); return -EINVAL; } if (obc_transport_find(transport->name)) { error("Permission denied: transport %s already registered", transport->name); return -EPERM; } DBG("transport %p name %s registered", transport, transport->name); transports = g_slist_append(transports, transport); return 0; } void obc_transport_unregister(struct obc_transport *transport) { if (!g_slist_find(transports, transport)) { error("Unable to unregister: No such transport %p", transport); return; } DBG("transport %p name %s unregistered", transport, transport->name); transports = g_slist_remove(transports, transport); } bluez-5.87/obexd/client/PaxHeaders/opp.h0000644000000000000000000000005014015011623015172 xustar0020 atime=1783093885 20 ctime=1783094105 bluez-5.87/obexd/client/opp.h0000644000000000000000000000024614015011623014655 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Client * * Copyright (C) 2011 Intel Corporation * * */ int opp_init(void); void opp_exit(void); bluez-5.87/obexd/client/PaxHeaders/bip.h0000644000000000000000000000005014711225434015160 xustar0020 atime=1783093885 20 ctime=1783094105 bluez-5.87/obexd/client/bip.h0000644000000000000000000000024414711225434014641 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Client * * Copyright (C) 2024 Collabora Ltd. * * */ int bip_init(void); void bip_exit(void); bluez-5.87/obexd/client/PaxHeaders/mns.c0000644000000000000000000000005014572354773015214 xustar0020 atime=1783093871 20 ctime=1783094104 bluez-5.87/obexd/client/mns.c0000644000000000000000000002076314572354773014705 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2013 BMW Car IT GmbH. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include "gobex/gobex.h" #include "gobex/gobex-apparam.h" #include "obexd/src/obexd.h" #include "obexd/src/plugin.h" #include "obexd/src/log.h" #include "obexd/src/obex.h" #include "obexd/src/service.h" #include "obexd/src/mimetype.h" #include "obexd/src/map_ap.h" #include "map-event.h" #include "obexd/src/manager.h" struct mns_session { GString *buffer; GObexApparam *inparams; char *remote_address; uint8_t mas_instance_id; }; static const uint8_t MNS_TARGET[TARGET_SIZE] = { 0xbb, 0x58, 0x2b, 0x41, 0x42, 0x0c, 0x11, 0xdb, 0xb0, 0xde, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 }; static int get_params(struct obex_session *os, struct mns_session *mns) { const uint8_t *buffer; ssize_t size; size = obex_get_apparam(os, &buffer); if (size < 0) size = 0; mns->inparams = g_obex_apparam_decode(buffer, size); if (mns->inparams == NULL) { DBG("Error when parsing parameters!"); return -EBADR; } return 0; } static void reset_request(struct mns_session *mns) { if (mns->buffer) { g_string_free(mns->buffer, TRUE); mns->buffer = NULL; } if (mns->inparams) { g_obex_apparam_free(mns->inparams); mns->inparams = NULL; } } static void mns_session_free(struct mns_session *mns) { reset_request(mns); if (mns->remote_address) g_free(mns->remote_address); g_free(mns); } static void *mns_connect(struct obex_session *os, int *err) { struct mns_session *mns; char *address; manager_register_session(os); mns = g_new0(struct mns_session, 1); if (obex_getpeername(os, &address) == 0) { mns->remote_address = g_strdup(address); g_free(address); } DBG("MNS connected to %s", mns->remote_address); if (err) *err = 0; return mns; } static void mns_disconnect(struct obex_session *os, void *user_data) { struct mns_session *mns = user_data; DBG("MNS disconnected from %s", mns->remote_address); manager_unregister_session(os); mns_session_free(mns); } static int mns_put(struct obex_session *os, void *user_data) { struct mns_session *mns = user_data; const char *type = obex_get_type(os); const char *name = obex_get_name(os); int ret; DBG("PUT: name %s type %s mns %p", name, type, mns); if (type == NULL) return -EBADR; ret = get_params(os, mns); if (ret < 0) goto failed; ret = obex_put_stream_start(os, name); if (ret < 0) goto failed; return 0; failed: reset_request(mns); return ret; } static void parse_event_report_type(struct map_event *event, const char *value) { if (!g_ascii_strcasecmp(value, "NewMessage")) event->type = MAP_ET_NEW_MESSAGE; else if (!g_ascii_strcasecmp(value, "DeliverySuccess")) event->type = MAP_ET_DELIVERY_SUCCESS; else if (!g_ascii_strcasecmp(value, "SendingSuccess")) event->type = MAP_ET_SENDING_SUCCESS; else if (!g_ascii_strcasecmp(value, "DeliveryFailure")) event->type = MAP_ET_DELIVERY_FAILURE; else if (!g_ascii_strcasecmp(value, "SendingFailure")) event->type = MAP_ET_SENDING_FAILURE; else if (!g_ascii_strcasecmp(value, "MemoryFull")) event->type = MAP_ET_MEMORY_FULL; else if (!g_ascii_strcasecmp(value, "MemoryAvailable")) event->type = MAP_ET_MEMORY_AVAILABLE; else if (!g_ascii_strcasecmp(value, "MessageDeleted")) event->type = MAP_ET_MESSAGE_DELETED; else if (!g_ascii_strcasecmp(value, "MessageShift")) event->type = MAP_ET_MESSAGE_SHIFT; } static void parse_event_report_handle(struct map_event *event, const char *value) { event->handle = strtoull(value, NULL, 16); } static void parse_event_report_folder(struct map_event *event, const char *value) { g_free(event->folder); if (g_str_has_prefix(value, "/")) event->folder = g_strdup(value); else event->folder = g_strconcat("/", value, NULL); } static void parse_event_report_old_folder(struct map_event *event, const char *value) { g_free(event->old_folder); if (g_str_has_prefix(value, "/")) event->old_folder = g_strdup(value); else event->old_folder = g_strconcat("/", value, NULL); } static void parse_event_report_msg_type(struct map_event *event, const char *value) { g_free(event->msg_type); event->msg_type = g_strdup(value); } static void parse_event_report_date_time(struct map_event *event, const char *value) { g_free(event->datetime); event->datetime = g_strdup(value); } static void parse_event_report_subject(struct map_event *event, const char *value) { g_free(event->subject); event->subject = g_strdup(value); } static void parse_event_report_sender_name(struct map_event *event, const char *value) { g_free(event->sender_name); event->sender_name = g_strdup(value); } static void parse_event_report_priority(struct map_event *event, const char *value) { g_free(event->priority); event->priority = g_strdup(value); } static const struct map_event_report_parser { const char *name; void (*func) (struct map_event *event, const char *value); } event_report_parsers[] = { { "type", parse_event_report_type }, { "handle", parse_event_report_handle }, { "folder", parse_event_report_folder }, { "old_folder", parse_event_report_old_folder }, { "msg_type", parse_event_report_msg_type }, { "datetime", parse_event_report_date_time }, { "subject", parse_event_report_subject }, { "sender_name", parse_event_report_sender_name }, { "priority", parse_event_report_priority }, { } }; static void event_report_element(GMarkupParseContext *ctxt, const char *element, const char **names, const char **values, gpointer user_data, GError **gerr) { struct map_event *event = user_data; const char *key; int i; if (strcasecmp("event", element) != 0) return; for (i = 0, key = names[i]; key; key = names[++i]) { const struct map_event_report_parser *parser; for (parser = event_report_parsers; parser && parser->name; parser++) { if (strcasecmp(key, parser->name) == 0) { if (values[i]) parser->func(event, values[i]); break; } } } } static const GMarkupParser event_report_parser = { event_report_element, NULL, NULL, NULL, NULL }; static void map_event_free(struct map_event *event) { g_free(event->folder); g_free(event->old_folder); g_free(event->msg_type); g_free(event->datetime); g_free(event->subject); g_free(event->sender_name); g_free(event->priority); g_free(event); } static void *event_report_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { struct mns_session *mns = driver_data; DBG(""); g_obex_apparam_get_uint8(mns->inparams, MAP_AP_MASINSTANCEID, &mns->mas_instance_id); mns->buffer = g_string_new(""); if (err != NULL) *err = 0; return mns; } static int event_report_close(void *obj) { struct mns_session *mns = obj; GMarkupParseContext *ctxt; struct map_event *event; DBG(""); event = g_new0(struct map_event, 1); ctxt = g_markup_parse_context_new(&event_report_parser, 0, event, NULL); g_markup_parse_context_parse(ctxt, mns->buffer->str, mns->buffer->len, NULL); g_markup_parse_context_free(ctxt); map_dispatch_event(mns->mas_instance_id, mns->remote_address, event); map_event_free(event); reset_request(mns); return 0; } static ssize_t event_report_write(void *obj, const void *buf, size_t count) { struct mns_session *mns = obj; DBG(""); g_string_append_len(mns->buffer, buf, count); return count; } static const struct obex_service_driver mns = { .name = "Message Notification server", .service = OBEX_MNS, .target = MNS_TARGET, .target_size = TARGET_SIZE, .connect = mns_connect, .put = mns_put, .disconnect = mns_disconnect, }; static const struct obex_mime_type_driver mime_event_report = { .target = MNS_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/MAP-event-report", .open = event_report_open, .close = event_report_close, .write = event_report_write, }; static int mns_init(void) { int err; err = obex_mime_type_driver_register(&mime_event_report); if (err < 0) goto fail_mime_event; err = obex_service_driver_register(&mns); if (err < 0) goto fail_mns_reg; return 0; fail_mns_reg: obex_mime_type_driver_unregister(&mime_event_report); fail_mime_event: return err; } static void mns_exit(void) { obex_service_driver_unregister(&mns); obex_mime_type_driver_unregister(&mime_event_report); } OBEX_PLUGIN_DEFINE(mns, mns_init, mns_exit) bluez-5.87/obexd/client/PaxHeaders/opp.c0000644000000000000000000000005014766002272015202 xustar0020 atime=1783093896 20 ctime=1783094105 bluez-5.87/obexd/client/opp.c0000644000000000000000000001027114766002272014664 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Client * * Copyright (C) 2011 Intel Corporation * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include "gdbus/gdbus.h" #include "obexd/src/log.h" #include "obexd/src/obexd.h" #include "transfer.h" #include "session.h" #include "driver.h" #include "opp.h" #define OPP_UUID "00001105-0000-1000-8000-00805f9b34fb" #define OPP_INTERFACE "org.bluez.obex.ObjectPush1" #define ERROR_INTERFACE "org.bluez.obex.Error" struct opp_data { struct obc_session *session; }; static DBusConnection *conn = NULL; static DBusMessage *opp_send_file(DBusConnection *connection, DBusMessage *message, void *user_data) { struct opp_data *opp = user_data; struct obc_transfer *transfer; DBusMessage *reply; char *filename; char *basename; GError *err = NULL; if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &filename, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); basename = g_path_get_basename(filename); transfer = obc_transfer_put(NULL, basename, filename, NULL, 0, &err); g_free(basename); if (transfer == NULL) goto fail; if (!obc_session_queue(opp->session, transfer, NULL, NULL, &err)) goto fail; return obc_transfer_create_dbus_reply(transfer, message); fail: reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } static DBusMessage *opp_pull_business_card(DBusConnection *connection, DBusMessage *message, void *user_data) { struct opp_data *opp = user_data; struct obc_transfer *pull; DBusMessage *reply; const char *filename = NULL; GError *err = NULL; if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &filename, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); pull = obc_transfer_get("text/x-vcard", NULL, filename, &err); if (pull == NULL) goto fail; if (!obc_session_queue(opp->session, pull, NULL, NULL, &err)) goto fail; return obc_transfer_create_dbus_reply(pull, message); fail: reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } static DBusMessage *opp_exchange_business_cards(DBusConnection *connection, DBusMessage *message, void *user_data) { return g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "Not Implemented"); } static const GDBusMethodTable opp_methods[] = { { GDBUS_METHOD("SendFile", GDBUS_ARGS({ "sourcefile", "s" }), GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), opp_send_file) }, { GDBUS_METHOD("PullBusinessCard", GDBUS_ARGS({ "targetfile", "s" }), GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), opp_pull_business_card) }, { GDBUS_METHOD("ExchangeBusinessCards", GDBUS_ARGS({ "clientfile", "s" }, { "targetfile", "s" }), GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), opp_exchange_business_cards) }, { } }; static void opp_free(void *data) { struct opp_data *opp = data; obc_session_unref(opp->session); g_free(opp); } static int opp_probe(struct obc_session *session) { struct opp_data *opp; const char *path; path = obc_session_get_path(session); DBG("%s", path); opp = g_try_new0(struct opp_data, 1); if (!opp) return -ENOMEM; opp->session = obc_session_ref(session); if (!g_dbus_register_interface(conn, path, OPP_INTERFACE, opp_methods, NULL, NULL, opp, opp_free)) { opp_free(opp); return -ENOMEM; } return 0; } static void opp_remove(struct obc_session *session) { const char *path = obc_session_get_path(session); DBG("%s", path); g_dbus_unregister_interface(conn, path, OPP_INTERFACE); } static struct obc_driver opp = { .service = "OPP", .uuid = OPP_UUID, .probe = opp_probe, .remove = opp_remove }; int opp_init(void) { int err; DBG(""); conn = obex_get_dbus_connection(); if (!conn) return -EIO; err = obc_driver_register(&opp); if (err < 0) { dbus_connection_unref(conn); conn = NULL; return err; } return 0; } void opp_exit(void) { DBG(""); dbus_connection_unref(conn); conn = NULL; obc_driver_unregister(&opp); } bluez-5.87/obexd/client/PaxHeaders/transport.h0000644000000000000000000000005014015011623016430 xustar0020 atime=1783093886 20 ctime=1783094105 bluez-5.87/obexd/client/transport.h0000644000000000000000000000136414015011623016115 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2012 Intel Corporation * * */ typedef void (*obc_transport_func)(GIOChannel *io, GError *err, gpointer user_data); struct obc_transport { const char *name; guint (*connect) (const char *source, const char *destination, const char *service, uint16_t port, obc_transport_func func, void *user_data); int (*getpacketopt) (GIOChannel *io, int *tx_mtu, int *rx_mtu); void (*disconnect) (guint id); const void *(*getattribute) (guint id, int attribute_id); }; int obc_transport_register(struct obc_transport *transport); void obc_transport_unregister(struct obc_transport *transport); struct obc_transport *obc_transport_find(const char *name); bluez-5.87/obexd/client/PaxHeaders/sync.h0000644000000000000000000000005014015011623015350 xustar0020 atime=1783093885 20 ctime=1783094104 bluez-5.87/obexd/client/sync.h0000644000000000000000000000036114015011623015031 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Client * * Copyright (C) 2007-2010 Intel Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ int sync_init(void); void sync_exit(void); bluez-5.87/obexd/client/PaxHeaders/ftp.c0000644000000000000000000000005014766002272015175 xustar0020 atime=1783093894 20 ctime=1783094105 bluez-5.87/obexd/client/ftp.c0000644000000000000000000002753014766002272014665 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Client * * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include "gdbus/gdbus.h" #include "obexd/src/log.h" #include "obexd/src/obexd.h" #include "transfer.h" #include "session.h" #include "driver.h" #include "ftp.h" #define OBEX_FTP_UUID \ "\xF9\xEC\x7B\xC4\x95\x3C\x11\xD2\x98\x4E\x52\x54\x00\xDC\x9E\x09" #define OBEX_FTP_UUID_LEN 16 #define FTP_INTERFACE "org.bluez.obex.FileTransfer1" #define ERROR_INTERFACE "org.bluez.obex.Error" #define FTP_UUID "00001106-0000-1000-8000-00805f9b34fb" #define PCSUITE_UUID "00005005-0000-1000-8000-0002ee000001" static DBusConnection *conn = NULL; struct ftp_data { struct obc_session *session; }; static void async_cb(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { DBusMessage *reply, *msg = user_data; if (err != NULL) reply = g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", "%s", err->message); else reply = dbus_message_new_method_return(msg); g_dbus_send_message(conn, reply); dbus_message_unref(msg); } static DBusMessage *change_folder(DBusConnection *connection, DBusMessage *message, void *user_data) { struct ftp_data *ftp = user_data; struct obc_session *session = ftp->session; const char *folder; GError *err = NULL; if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &folder, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); obc_session_setpath(session, folder, async_cb, message, &err); if (err != NULL) { DBusMessage *reply; reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } dbus_message_ref(message); return NULL; } static void xml_element(GMarkupParseContext *ctxt, const char *element, const char **names, const char **values, gpointer user_data, GError **gerr) { DBusMessageIter dict, *iter = user_data; char *key; int i; if (strcasecmp("folder", element) != 0 && strcasecmp("file", element) != 0) return; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); g_dbus_dict_append_entry(&dict, "Type", DBUS_TYPE_STRING, &element); /* FIXME: User, Group, Other permission must be reviewed */ i = 0; for (key = (char *) names[i]; key; key = (char *) names[++i]) { key[0] = g_ascii_toupper(key[0]); if (g_str_equal("Size", key) == TRUE) { guint64 size; size = g_ascii_strtoll(values[i], NULL, 10); g_dbus_dict_append_entry(&dict, key, DBUS_TYPE_UINT64, &size); } else g_dbus_dict_append_entry(&dict, key, DBUS_TYPE_STRING, &values[i]); } dbus_message_iter_close_container(iter, &dict); } static const GMarkupParser parser = { xml_element, NULL, NULL, NULL, NULL }; static void list_folder_callback(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { DBusMessage *msg = user_data; GMarkupParseContext *ctxt; DBusMessage *reply; DBusMessageIter iter, array; char *contents; size_t size; reply = dbus_message_new_method_return(msg); if (obc_transfer_get_contents(transfer, &contents, &size) < 0) goto done; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &array); ctxt = g_markup_parse_context_new(&parser, 0, &array, NULL); g_markup_parse_context_parse(ctxt, contents, size, NULL); g_markup_parse_context_free(ctxt); dbus_message_iter_close_container(&iter, &array); g_free(contents); done: g_dbus_send_message(conn, reply); dbus_message_unref(msg); } static DBusMessage *create_folder(DBusConnection *connection, DBusMessage *message, void *user_data) { struct ftp_data *ftp = user_data; struct obc_session *session = ftp->session; const char *folder; GError *err = NULL; if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &folder, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); obc_session_mkdir(session, folder, async_cb, message, &err); if (err != NULL) { DBusMessage *reply; reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } dbus_message_ref(message); return NULL; } static DBusMessage *list_folder(DBusConnection *connection, DBusMessage *message, void *user_data) { struct ftp_data *ftp = user_data; struct obc_session *session = ftp->session; struct obc_transfer *transfer; GError *err = NULL; DBusMessage *reply; transfer = obc_transfer_get("x-obex/folder-listing", NULL, NULL, &err); if (transfer == NULL) goto fail; if (obc_session_queue(session, transfer, list_folder_callback, message, &err)) { dbus_message_ref(message); return NULL; } fail: reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } static DBusMessage *get_file(DBusConnection *connection, DBusMessage *message, void *user_data) { struct ftp_data *ftp = user_data; struct obc_session *session = ftp->session; struct obc_transfer *transfer; const char *target_file, *source_file; GError *err = NULL; DBusMessage *reply; if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &target_file, DBUS_TYPE_STRING, &source_file, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); transfer = obc_transfer_get(NULL, source_file, target_file, &err); if (transfer == NULL) goto fail; if (!obc_session_queue(session, transfer, NULL, NULL, &err)) goto fail; return obc_transfer_create_dbus_reply(transfer, message); fail: reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } static DBusMessage *put_file(DBusConnection *connection, DBusMessage *message, void *user_data) { struct ftp_data *ftp = user_data; struct obc_session *session = ftp->session; struct obc_transfer *transfer; char *sourcefile, *targetfile; GError *err = NULL; DBusMessage *reply; if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &sourcefile, DBUS_TYPE_STRING, &targetfile, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); transfer = obc_transfer_put(NULL, targetfile, sourcefile, NULL, 0, &err); if (transfer == NULL) goto fail; if (!obc_session_queue(session, transfer, NULL, NULL, &err)) goto fail; return obc_transfer_create_dbus_reply(transfer, message); fail: reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } static DBusMessage *copy_file(DBusConnection *connection, DBusMessage *message, void *user_data) { struct ftp_data *ftp = user_data; struct obc_session *session = ftp->session; const char *filename, *destname; GError *err = NULL; if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &filename, DBUS_TYPE_STRING, &destname, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); obc_session_copy(session, filename, destname, async_cb, message, &err); if (err != NULL) { DBusMessage *reply; reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } dbus_message_ref(message); return NULL; } static DBusMessage *move_file(DBusConnection *connection, DBusMessage *message, void *user_data) { struct ftp_data *ftp = user_data; struct obc_session *session = ftp->session; const char *filename, *destname; GError *err = NULL; if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &filename, DBUS_TYPE_STRING, &destname, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); obc_session_move(session, filename, destname, async_cb, message, &err); if (err != NULL) { DBusMessage *reply; reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } dbus_message_ref(message); return NULL; } static DBusMessage *delete(DBusConnection *connection, DBusMessage *message, void *user_data) { struct ftp_data *ftp = user_data; struct obc_session *session = ftp->session; const char *file; GError *err = NULL; if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &file, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); obc_session_delete(session, file, async_cb, message, &err); if (err != NULL) { DBusMessage *reply; reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } dbus_message_ref(message); return NULL; } static const GDBusMethodTable ftp_methods[] = { { GDBUS_ASYNC_METHOD("ChangeFolder", GDBUS_ARGS({ "folder", "s" }), NULL, change_folder) }, { GDBUS_ASYNC_METHOD("CreateFolder", GDBUS_ARGS({ "folder", "s" }), NULL, create_folder) }, { GDBUS_ASYNC_METHOD("ListFolder", NULL, GDBUS_ARGS({ "folderinfo", "aa{sv}" }), list_folder) }, { GDBUS_METHOD("GetFile", GDBUS_ARGS({ "targetfile", "s" }, { "sourcefile", "s" }), GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), get_file) }, { GDBUS_METHOD("PutFile", GDBUS_ARGS({ "sourcefile", "s" }, { "targetfile", "s" }), GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), put_file) }, { GDBUS_ASYNC_METHOD("CopyFile", GDBUS_ARGS({ "sourcefile", "s" }, { "targetfile", "s" }), NULL, copy_file) }, { GDBUS_ASYNC_METHOD("MoveFile", GDBUS_ARGS({ "sourcefile", "s" }, { "targetfile", "s" }), NULL, move_file) }, { GDBUS_ASYNC_METHOD("Delete", GDBUS_ARGS({ "file", "s" }), NULL, delete) }, { } }; static void ftp_free(void *data) { struct ftp_data *ftp = data; obc_session_unref(ftp->session); g_free(ftp); } static int ftp_probe(struct obc_session *session) { struct ftp_data *ftp; const char *path; path = obc_session_get_path(session); DBG("%s", path); ftp = g_try_new0(struct ftp_data, 1); if (!ftp) return -ENOMEM; ftp->session = obc_session_ref(session); if (!g_dbus_register_interface(conn, path, FTP_INTERFACE, ftp_methods, NULL, NULL, ftp, ftp_free)) { ftp_free(ftp); return -ENOMEM; } return 0; } static void ftp_remove(struct obc_session *session) { const char *path = obc_session_get_path(session); DBG("%s", path); g_dbus_unregister_interface(conn, path, FTP_INTERFACE); } static struct obc_driver ftp = { .service = "FTP", .uuid = FTP_UUID, .target = OBEX_FTP_UUID, .target_len = OBEX_FTP_UUID_LEN, .probe = ftp_probe, .remove = ftp_remove }; static struct obc_driver pcsuite = { .service = "PCSUITE", .uuid = PCSUITE_UUID, .target = OBEX_FTP_UUID, .target_len = OBEX_FTP_UUID_LEN, .probe = ftp_probe, .remove = ftp_remove }; int ftp_init(void) { int err; DBG(""); conn = obex_get_dbus_connection(); if (!conn) return -EIO; err = obc_driver_register(&ftp); if (err < 0) goto failed; err = obc_driver_register(&pcsuite); if (err < 0) { obc_driver_unregister(&ftp); goto failed; } return 0; failed: dbus_connection_unref(conn); conn = NULL; return err; } void ftp_exit(void) { DBG(""); dbus_connection_unref(conn); conn = NULL; obc_driver_unregister(&ftp); obc_driver_unregister(&pcsuite); } bluez-5.87/obexd/client/PaxHeaders/map.c0000644000000000000000000000005015063045254015157 xustar0020 atime=1783093896 20 ctime=1783094105 bluez-5.87/obexd/client/map.c0000644000000000000000000015707615063045254014660 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Client * * Copyright (C) 2011 Bartosz Szatkowski for Comarch * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include "bluetooth/sdp.h" #include "gobex/gobex-apparam.h" #include "gdbus/gdbus.h" #include "obexd/src/log.h" #include "obexd/src/obexd.h" #include "obexd/src/map_ap.h" #include "map-event.h" #include "map.h" #include "transfer.h" #include "session.h" #include "driver.h" #define OBEX_MAS_UUID \ "\xBB\x58\x2B\x40\x42\x0C\x11\xDB\xB0\xDE\x08\x00\x20\x0C\x9A\x66" #define OBEX_MAS_UUID_LEN 16 #define SUPPORTED_FEATURES_TAG 0x29 #define NOTIFICATION_REGISTRATION_FEATURE 0x00000001 #define NOTIFICATION_FEATURE 0x00000002 #define BROWSING_FEATURE 0x00000004 #define UPLOADING_FEATURE 0x00000008 #define DELETE_FEATURE 0x00000010 #define INSTANCE_INFORMATION_FEATURE 0x00000020 #define EXTENDED_EVENT_REPORT_1_1 0x00000040 #define MESSAGES_LISTING_FORMAT_VERSION_1_1 0x00000200 #define MAPSUPPORTEDFEATURES_IN_CONNECT_REQUEST 0x00080000 #define MAP_INTERFACE "org.bluez.obex.MessageAccess1" #define MAP_MSG_INTERFACE "org.bluez.obex.Message1" #define ERROR_INTERFACE "org.bluez.obex.Error" #define MAS_UUID "00001132-0000-1000-8000-00805f9b34fb" #define DEFAULT_COUNT 1024 #define DEFAULT_OFFSET 0 #define CHARSET_NATIVE 0 #define CHARSET_UTF8 1 #define SDP_MESSAGE_TYPE_EMAIL 0x01 #define SDP_MESSAGE_TYPE_SMS_GSM 0x02 #define SDP_MESSAGE_TYPE_SMS_CDMA 0x04 #define SDP_MESSAGE_TYPE_MMS 0x08 #define SDP_MESSAGE_TYPE_IM 0x10 static const char * const filter_list[] = { "subject", "timestamp", "sender", "sender-address", "recipient", "recipient-address", "type", "size", "status", "text", "attachment", "priority", "read", "sent", "protected", "replyto", NULL }; #define FILTER_BIT_MAX 15 #define FILTER_ALL 0x0000FFFF #define FILTER_READ_STATUS_NONE 0x00 #define FILTER_READ_STATUS_ONLY_UNREAD 0x01 #define FILTER_READ_STATUS_ONLY_READ 0x02 #define FILTER_PRIORITY_NONE 0x00 #define FILTER_PRIORITY_ONLY_HIGH 0x01 #define FILTER_PRIORITY_ONLY_NONHIGH 0x02 #define STATUS_READ 0 #define STATUS_DELETE 1 #define FILLER_BYTE 0x30 struct map_data { struct obc_session *session; GHashTable *messages; int16_t mas_instance_id; uint8_t supported_message_types; uint32_t supported_features; }; struct pending_request { struct map_data *map; DBusMessage *msg; char *folder; }; #define MAP_MSG_FLAG_PRIORITY 0x01 #define MAP_MSG_FLAG_READ 0x02 #define MAP_MSG_FLAG_SENT 0x04 #define MAP_MSG_FLAG_PROTECTED 0x08 #define MAP_MSG_FLAG_TEXT 0x10 struct map_msg { struct map_data *data; char *path; uint64_t handle; char *subject; char *timestamp; char *sender; char *sender_address; char *replyto; char *recipient; char *recipient_address; char *type; uint64_t size; char *status; uint64_t attachment_size; uint8_t flags; char *folder; char *delivery_status; uint64_t conversation_id; char *conversation_name; char *direction; char *attachment_mime_types; GDBusPendingPropertySet pending; }; struct map_parser { struct pending_request *request; DBusMessageIter *iter; }; static DBusConnection *conn = NULL; static struct pending_request *pending_request_new(struct map_data *map, DBusMessage *message) { struct pending_request *p; p = g_new0(struct pending_request, 1); p->map = map; p->msg = dbus_message_ref(message); return p; } static void pending_request_free(struct pending_request *p) { dbus_message_unref(p->msg); g_free(p->folder); g_free(p); } static void simple_cb(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { struct pending_request *request = user_data; DBusMessage *reply; if (err != NULL) reply = g_dbus_create_error(request->msg, ERROR_INTERFACE ".Failed", "%s", err->message); else reply = dbus_message_new_method_return(request->msg); g_dbus_send_message(conn, reply); pending_request_free(request); } static DBusMessage *map_setpath(DBusConnection *connection, DBusMessage *message, void *user_data) { struct map_data *map = user_data; const char *folder; struct pending_request *request; GError *err = NULL; if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &folder, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); request = pending_request_new(map, message); obc_session_setpath(map->session, folder, simple_cb, request, &err); if (err != NULL) { DBusMessage *reply; reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); pending_request_free(request); return reply; } return NULL; } static void folder_element(GMarkupParseContext *ctxt, const char *element, const char **names, const char **values, gpointer user_data, GError **gerr) { DBusMessageIter dict, *iter = user_data; const char *key; int i; if (strcasecmp("folder", element) != 0) return; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); for (i = 0, key = names[i]; key; key = names[++i]) { if (strcasecmp("name", key) == 0) g_dbus_dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &values[i]); } dbus_message_iter_close_container(iter, &dict); } static const GMarkupParser folder_parser = { folder_element, NULL, NULL, NULL, NULL }; static void folder_listing_cb(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { struct pending_request *request = user_data; GMarkupParseContext *ctxt; DBusMessage *reply; DBusMessageIter iter, array; char *contents; size_t size; int perr; if (err != NULL) { reply = g_dbus_create_error(request->msg, ERROR_INTERFACE ".Failed", "%s", err->message); goto done; } perr = obc_transfer_get_contents(transfer, &contents, &size); if (perr < 0) { reply = g_dbus_create_error(request->msg, ERROR_INTERFACE ".Failed", "Error reading contents: %s", strerror(-perr)); goto done; } reply = dbus_message_new_method_return(request->msg); if (reply == NULL) { g_free(contents); goto clean; } dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &array); ctxt = g_markup_parse_context_new(&folder_parser, 0, &array, NULL); g_markup_parse_context_parse(ctxt, contents, size, NULL); g_markup_parse_context_free(ctxt); dbus_message_iter_close_container(&iter, &array); g_free(contents); done: g_dbus_send_message(conn, reply); clean: pending_request_free(request); } static DBusMessage *get_folder_listing(struct map_data *map, DBusMessage *message, GObexApparam *apparam) { struct pending_request *request; struct obc_transfer *transfer; GError *err = NULL; DBusMessage *reply; transfer = obc_transfer_get("x-obex/folder-listing", NULL, NULL, &err); if (transfer == NULL) { g_obex_apparam_free(apparam); goto fail; } obc_transfer_set_apparam(transfer, apparam); request = pending_request_new(map, message); if (!obc_session_queue(map->session, transfer, folder_listing_cb, request, &err)) { pending_request_free(request); goto fail; } return NULL; fail: reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } static GObexApparam *parse_offset(GObexApparam *apparam, DBusMessageIter *iter) { guint16 num; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) return NULL; dbus_message_iter_get_basic(iter, &num); return g_obex_apparam_set_uint16(apparam, MAP_AP_STARTOFFSET, num); } static GObexApparam *parse_max_count(GObexApparam *apparam, DBusMessageIter *iter) { guint16 num; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) return NULL; dbus_message_iter_get_basic(iter, &num); return g_obex_apparam_set_uint16(apparam, MAP_AP_MAXLISTCOUNT, num); } static GObexApparam *parse_folder_filters(GObexApparam *apparam, DBusMessageIter *iter) { DBusMessageIter array; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return NULL; dbus_message_iter_recurse(iter, &array); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { const char *key; DBusMessageIter value, entry; dbus_message_iter_recurse(&array, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); if (strcasecmp(key, "Offset") == 0) { if (parse_offset(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "MaxCount") == 0) { if (parse_max_count(apparam, &value) == NULL) return NULL; } dbus_message_iter_next(&array); } return apparam; } static DBusMessage *map_list_folders(DBusConnection *connection, DBusMessage *message, void *user_data) { struct map_data *map = user_data; GObexApparam *apparam; DBusMessageIter args; dbus_message_iter_init(message, &args); apparam = g_obex_apparam_set_uint16(NULL, MAP_AP_MAXLISTCOUNT, DEFAULT_COUNT); apparam = g_obex_apparam_set_uint16(apparam, MAP_AP_STARTOFFSET, DEFAULT_OFFSET); if (parse_folder_filters(apparam, &args) == NULL) { g_obex_apparam_free(apparam); return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); } return get_folder_listing(map, message, apparam); } static void map_msg_free(void *data) { struct map_msg *msg = data; g_free(msg->path); g_free(msg->subject); g_free(msg->folder); g_free(msg->timestamp); g_free(msg->sender); g_free(msg->sender_address); g_free(msg->replyto); g_free(msg->recipient); g_free(msg->recipient_address); g_free(msg->type); g_free(msg->status); g_free(msg->delivery_status); g_free(msg->conversation_name); g_free(msg->direction); g_free(msg->attachment_mime_types); g_free(msg); } static DBusMessage *map_msg_get(DBusConnection *connection, DBusMessage *message, void *user_data) { struct map_msg *msg = user_data; struct obc_transfer *transfer; const char *target_file; gboolean attachment; GError *err = NULL; DBusMessage *reply; GObexApparam *apparam; char handle[17]; if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &target_file, DBUS_TYPE_BOOLEAN, &attachment, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); snprintf(handle, sizeof(handle), "%" PRIx64, msg->handle); transfer = obc_transfer_get("x-bt/message", handle, target_file, &err); if (transfer == NULL) goto fail; apparam = g_obex_apparam_set_uint8(NULL, MAP_AP_ATTACHMENT, attachment); apparam = g_obex_apparam_set_uint8(apparam, MAP_AP_CHARSET, CHARSET_UTF8); obc_transfer_set_apparam(transfer, apparam); if (!obc_session_queue(msg->data->session, transfer, NULL, NULL, &err)) goto fail; return obc_transfer_create_dbus_reply(transfer, message); fail: reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } static void set_message_status_cb(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { struct map_msg *msg = user_data; if (err != NULL) { g_dbus_pending_property_error(msg->pending, ERROR_INTERFACE ".Failed", "%s", err->message); goto done; } g_dbus_pending_property_success(msg->pending); done: msg->pending = 0; } static gboolean get_folder(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->folder); return TRUE; } static gboolean subject_exists(const GDBusPropertyTable *property, void *data) { struct map_msg *msg = data; return msg->subject != NULL; } static gboolean get_subject(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->subject); return TRUE; } static gboolean timestamp_exists(const GDBusPropertyTable *property, void *data) { struct map_msg *msg = data; return msg->timestamp != NULL; } static gboolean get_timestamp(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->timestamp); return TRUE; } static gboolean sender_exists(const GDBusPropertyTable *property, void *data) { struct map_msg *msg = data; return msg->sender != NULL; } static gboolean get_sender(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->sender); return TRUE; } static gboolean sender_address_exists(const GDBusPropertyTable *property, void *data) { struct map_msg *msg = data; return msg->sender_address != NULL; } static gboolean get_sender_address(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->sender_address); return TRUE; } static gboolean replyto_exists(const GDBusPropertyTable *property, void *data) { struct map_msg *msg = data; return msg->replyto != NULL; } static gboolean get_replyto(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->replyto); return TRUE; } static gboolean recipient_exists(const GDBusPropertyTable *property, void *data) { struct map_msg *msg = data; return msg->recipient != NULL; } static gboolean get_recipient(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->recipient); return TRUE; } static gboolean recipient_address_exists(const GDBusPropertyTable *property, void *data) { struct map_msg *msg = data; return msg->recipient_address != NULL; } static gboolean get_recipient_address(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->recipient_address); return TRUE; } static gboolean type_exists(const GDBusPropertyTable *property, void *data) { struct map_msg *msg = data; return msg->type != NULL; } static gboolean get_type(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->type); return TRUE; } static gboolean get_size(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &msg->size); return TRUE; } static gboolean reception_status_exists(const GDBusPropertyTable *property, void *data) { struct map_msg *msg = data; return msg->status != NULL; } static gboolean get_reception_status(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->status); return TRUE; } static gboolean get_attachment_size(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &msg->attachment_size); return TRUE; } static gboolean get_flag(const GDBusPropertyTable *property, DBusMessageIter *iter, uint8_t flag, void *data) { struct map_msg *msg = data; dbus_bool_t value = (msg->flags & flag) != 0; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); return TRUE; } static gboolean get_priority(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { return get_flag(property, iter, MAP_MSG_FLAG_PRIORITY, data); } static gboolean get_read(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { return get_flag(property, iter, MAP_MSG_FLAG_READ, data); } static gboolean get_sent(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { return get_flag(property, iter, MAP_MSG_FLAG_SENT, data); } static gboolean get_protected(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { return get_flag(property, iter, MAP_MSG_FLAG_PROTECTED, data); } static gboolean get_text(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { return get_flag(property, iter, MAP_MSG_FLAG_TEXT, data); } static void set_status(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, uint8_t status, void *data) { struct map_msg *msg = data; struct obc_transfer *transfer; gboolean value; GError *err = NULL; GObexApparam *apparam; char contents[1]; char handle[17]; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } dbus_message_iter_get_basic(iter, &value); contents[0] = FILLER_BYTE; snprintf(handle, sizeof(handle), "%" PRIx64, msg->handle); transfer = obc_transfer_put("x-bt/messageStatus", handle, NULL, contents, sizeof(contents), &err); if (transfer == NULL) goto fail; apparam = g_obex_apparam_set_uint8(NULL, MAP_AP_STATUSINDICATOR, status); apparam = g_obex_apparam_set_uint8(apparam, MAP_AP_STATUSVALUE, value); obc_transfer_set_apparam(transfer, apparam); if (!obc_session_queue(msg->data->session, transfer, set_message_status_cb, msg, &err)) goto fail; msg->pending = id; return; fail: g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); } static void set_read(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *data) { set_status(property, iter, id, STATUS_READ, data); } static void set_deleted(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *data) { set_status(property, iter, id, STATUS_DELETE, data); } static gboolean delivery_status_exists(const GDBusPropertyTable *property, void *data) { struct map_msg *msg = data; return msg->delivery_status != NULL; } static gboolean get_delivery_status(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->delivery_status); return TRUE; } static gboolean get_conversation_id(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &msg->conversation_id); return TRUE; } static gboolean conversation_name_exists(const GDBusPropertyTable *property, void *data) { struct map_msg *msg = data; return msg->conversation_name != NULL; } static gboolean get_conversation_name(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->conversation_name); return TRUE; } static gboolean direction_exists(const GDBusPropertyTable *property, void *data) { struct map_msg *msg = data; return msg->direction != NULL; } static gboolean get_direction(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->direction); return TRUE; } static gboolean attachment_mime_exists(const GDBusPropertyTable *property, void *data) { struct map_msg *msg = data; return msg->attachment_mime_types != NULL; } static gboolean get_attachment_mime_types(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct map_msg *msg = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->attachment_mime_types); return TRUE; } static const GDBusMethodTable map_msg_methods[] = { { GDBUS_METHOD("Get", GDBUS_ARGS({ "targetfile", "s" }, { "attachment", "b" }), GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), map_msg_get) }, { } }; static const GDBusPropertyTable map_msg_properties[] = { { "Folder", "s", get_folder }, { "Subject", "s", get_subject, NULL, subject_exists }, { "Timestamp", "s", get_timestamp, NULL, timestamp_exists }, { "Sender", "s", get_sender, NULL, sender_exists }, { "SenderAddress", "s", get_sender_address, NULL, sender_address_exists }, { "ReplyTo", "s", get_replyto, NULL, replyto_exists }, { "Recipient", "s", get_recipient, NULL, recipient_exists }, { "RecipientAddress", "s", get_recipient_address, NULL, recipient_address_exists }, { "Type", "s", get_type, NULL, type_exists }, { "Size", "t", get_size }, { "Text", "b", get_text }, { "Status", "s", get_reception_status, NULL, reception_status_exists }, { "AttachmentSize", "t", get_attachment_size }, { "Priority", "b", get_priority }, { "Read", "b", get_read, set_read }, { "Sent", "b", get_sent }, { "Protected", "b", get_protected }, { "Deleted", "b", NULL, set_deleted }, { "DeliveryStatus", "s", get_delivery_status, NULL, delivery_status_exists }, { "ConversationId", "t", get_conversation_id }, { "ConversationName", "s", get_conversation_name, NULL, conversation_name_exists }, { "Direction", "s", get_direction, NULL, direction_exists }, { "AttachmentMimeTypes", "s", get_attachment_mime_types, NULL, attachment_mime_exists }, { } }; static void parse_type(struct map_msg *msg, const char *value) { const char *type = NULL; if (strcasecmp(value, "SMS_GSM") == 0) type = "sms-gsm"; else if (strcasecmp(value, "SMS_CDMA") == 0) type = "sms-cdma"; else if (strcasecmp(value, "EMAIL") == 0) type = "email"; else if (strcasecmp(value, "MMS") == 0) type = "mms"; if (g_strcmp0(msg->type, type) == 0) return; g_free(msg->type); msg->type = g_strdup(type); g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "Type"); } static struct map_msg *map_msg_create(struct map_data *data, uint64_t handle, const char *folder, const char *type) { struct map_msg *msg; msg = g_new0(struct map_msg, 1); msg->data = data; msg->handle = handle; msg->path = g_strdup_printf("%s/message%" PRIu64, obc_session_get_path(data->session), msg->handle); msg->folder = g_strdup(folder); if (!g_dbus_register_interface(conn, msg->path, MAP_MSG_INTERFACE, map_msg_methods, NULL, map_msg_properties, msg, map_msg_free)) { map_msg_free(msg); return NULL; } g_hash_table_insert(data->messages, &msg->handle, msg); if (type) parse_type(msg, type); return msg; } static void parse_subject(struct map_msg *msg, const char *value) { if (g_strcmp0(msg->subject, value) == 0) return; g_free(msg->subject); msg->subject = g_strdup(value); g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "Subject"); } static void parse_datetime(struct map_msg *msg, const char *value) { if (g_strcmp0(msg->timestamp, value) == 0) return; g_free(msg->timestamp); msg->timestamp = g_strdup(value); g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "Timestamp"); } static void parse_sender(struct map_msg *msg, const char *value) { if (g_strcmp0(msg->sender, value) == 0) return; g_free(msg->sender); msg->sender = g_strdup(value); g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "Sender"); } static void parse_sender_address(struct map_msg *msg, const char *value) { if (g_strcmp0(msg->sender_address, value) == 0) return; g_free(msg->sender_address); msg->sender_address = g_strdup(value); g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "SenderAddress"); } static void parse_replyto(struct map_msg *msg, const char *value) { if (g_strcmp0(msg->replyto, value) == 0) return; g_free(msg->replyto); msg->replyto = g_strdup(value); g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "ReplyTo"); } static void parse_recipient(struct map_msg *msg, const char *value) { if (g_strcmp0(msg->recipient, value) == 0) return; g_free(msg->recipient); msg->recipient = g_strdup(value); g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "Recipient"); } static void parse_recipient_address(struct map_msg *msg, const char *value) { if (g_strcmp0(msg->recipient_address, value) == 0) return; g_free(msg->recipient_address); msg->recipient_address = g_strdup(value); g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "RecipientAddress"); } static void parse_size(struct map_msg *msg, const char *value) { uint64_t size = g_ascii_strtoll(value, NULL, 10); if (msg->size == size) return; msg->size = size; g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "Size"); } static void parse_text(struct map_msg *msg, const char *value) { gboolean flag = strcasecmp(value, "no") != 0; uint8_t oldflags = msg->flags; if (flag) msg->flags |= MAP_MSG_FLAG_TEXT; else msg->flags &= ~MAP_MSG_FLAG_TEXT; if (msg->flags != oldflags) g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "Text"); } static void parse_status(struct map_msg *msg, const char *value) { if (g_strcmp0(msg->status, value) == 0) return; g_free(msg->status); msg->status = g_strdup(value); g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "Status"); } static void parse_attachment_size(struct map_msg *msg, const char *value) { uint64_t attachment_size = g_ascii_strtoll(value, NULL, 10); if (msg->attachment_size == attachment_size) return; msg->attachment_size = attachment_size; g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "AttachmentSize"); } static void parse_priority(struct map_msg *msg, const char *value) { gboolean flag = strcasecmp(value, "no") != 0; uint8_t oldflags = msg->flags; if (flag) msg->flags |= MAP_MSG_FLAG_PRIORITY; else msg->flags &= ~MAP_MSG_FLAG_PRIORITY; if (msg->flags != oldflags) g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "Priority"); } static void parse_read(struct map_msg *msg, const char *value) { gboolean flag = strcasecmp(value, "no") != 0; uint8_t oldflags = msg->flags; if (flag) msg->flags |= MAP_MSG_FLAG_READ; else msg->flags &= ~MAP_MSG_FLAG_READ; if (msg->flags != oldflags) g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "Read"); } static void parse_sent(struct map_msg *msg, const char *value) { gboolean flag = strcasecmp(value, "no") != 0; uint8_t oldflags = msg->flags; if (flag) msg->flags |= MAP_MSG_FLAG_SENT; else msg->flags &= ~MAP_MSG_FLAG_SENT; if (msg->flags != oldflags) g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "Sent"); } static void parse_protected(struct map_msg *msg, const char *value) { gboolean flag = strcasecmp(value, "no") != 0; uint8_t oldflags = msg->flags; if (flag) msg->flags |= MAP_MSG_FLAG_PROTECTED; else msg->flags &= ~MAP_MSG_FLAG_PROTECTED; if (msg->flags != oldflags) g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "Protected"); } static void parse_delivery_status(struct map_msg *msg, const char *value) { if (g_strcmp0(msg->delivery_status, value) == 0) return; g_free(msg->delivery_status); msg->delivery_status = g_strdup(value); g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "DeliveryStatus"); } static void parse_conversation_id(struct map_msg *msg, const char *value) { uint64_t conversation_id = strtoull(value, NULL, 16); if (msg->conversation_id == conversation_id) return; msg->conversation_id = conversation_id; g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "ConversationId"); } static void parse_conversation_name(struct map_msg *msg, const char *value) { if (g_strcmp0(msg->conversation_name, value) == 0) return; g_free(msg->conversation_name); msg->conversation_name = g_strdup(value); g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "ConversationName"); } static void parse_direction(struct map_msg *msg, const char *value) { if (g_strcmp0(msg->direction, value) == 0) return; g_free(msg->direction); msg->direction = g_strdup(value); g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "Direction"); } static void parse_mime_types(struct map_msg *msg, const char *value) { if (g_strcmp0(msg->attachment_mime_types, value) == 0) return; g_free(msg->attachment_mime_types); msg->attachment_mime_types = g_strdup(value); g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "AttachmentMimeTypes"); } static const struct map_msg_parser { const char *name; void (*func) (struct map_msg *msg, const char *value); } msg_parsers[] = { { "subject", parse_subject }, { "datetime", parse_datetime }, { "sender_name", parse_sender }, { "sender_addressing", parse_sender_address }, { "replyto_addressing", parse_replyto }, { "recipient_name", parse_recipient }, { "recipient_addressing", parse_recipient_address }, { "type", parse_type }, { "size", parse_size }, { "text", parse_text }, { "reception_status", parse_status }, { "attachment_size", parse_attachment_size }, { "priority", parse_priority }, { "read", parse_read }, { "sent", parse_sent }, { "protected", parse_protected }, { "delivery_status", parse_delivery_status}, { "conversation_id", parse_conversation_id}, { "conversation_name", parse_conversation_name}, { "direction", parse_direction}, { "attachment_mime_types", parse_mime_types}, { } }; static void msg_element(GMarkupParseContext *ctxt, const char *element, const char **names, const char **values, gpointer user_data, GError **gerr) { struct map_parser *parser = user_data; struct map_data *data = parser->request->map; DBusMessageIter entry, *iter = parser->iter; struct map_msg *msg; const char *key; int i; uint64_t handle; if (strcasecmp("msg", element) != 0) return; for (i = 0, key = names[i]; key; key = names[++i]) { if (strcasecmp(key, "handle") == 0) break; } handle = strtoull(values[i], NULL, 16); msg = g_hash_table_lookup(data->messages, &handle); if (msg == NULL) { msg = map_msg_create(data, handle, parser->request->folder, NULL); if (msg == NULL) return; } dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, &msg->path); for (i = 0, key = names[i]; key; key = names[++i]) { const struct map_msg_parser *parser; for (parser = msg_parsers; parser && parser->name; parser++) { if (strcasecmp(key, parser->name) == 0) { if (values[i]) parser->func(msg, values[i]); break; } } } g_dbus_get_properties(conn, msg->path, MAP_MSG_INTERFACE, &entry); dbus_message_iter_close_container(iter, &entry); } static const GMarkupParser msg_parser = { msg_element, NULL, NULL, NULL, NULL }; static void message_listing_cb(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { struct pending_request *request = user_data; struct map_parser *parser; GMarkupParseContext *ctxt; DBusMessage *reply; DBusMessageIter iter, array; char *contents; size_t size; int perr; if (err != NULL) { reply = g_dbus_create_error(request->msg, ERROR_INTERFACE ".Failed", "%s", err->message); goto done; } perr = obc_transfer_get_contents(transfer, &contents, &size); if (perr < 0) { reply = g_dbus_create_error(request->msg, ERROR_INTERFACE ".Failed", "Error reading contents: %s", strerror(-perr)); goto done; } reply = dbus_message_new_method_return(request->msg); if (reply == NULL) { g_free(contents); goto clean; } dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_OBJECT_PATH_AS_STRING DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &array); parser = g_new(struct map_parser, 1); parser->request = request; parser->iter = &array; ctxt = g_markup_parse_context_new(&msg_parser, 0, parser, NULL); g_markup_parse_context_parse(ctxt, contents, size, NULL); g_markup_parse_context_free(ctxt); dbus_message_iter_close_container(&iter, &array); g_free(contents); g_free(parser); done: g_dbus_send_message(conn, reply); clean: pending_request_free(request); } static char *get_absolute_folder(struct map_data *map, const char *subfolder) { const char *root = obc_session_get_folder(map->session); if (!subfolder || strlen(subfolder) == 0) return g_strdup(root); else if (g_str_has_suffix(root, "/")) return g_strconcat(root, subfolder, NULL); else return g_strconcat(root, "/", subfolder, NULL); } static DBusMessage *get_message_listing(struct map_data *map, DBusMessage *message, const char *folder, GObexApparam *apparam) { struct pending_request *request; struct obc_transfer *transfer; GError *err = NULL; DBusMessage *reply; transfer = obc_transfer_get("x-bt/MAP-msg-listing", folder, NULL, &err); if (transfer == NULL) { g_obex_apparam_free(apparam); goto fail; } obc_transfer_set_apparam(transfer, apparam); request = pending_request_new(map, message); request->folder = get_absolute_folder(map, folder); if (!obc_session_queue(map->session, transfer, message_listing_cb, request, &err)) { pending_request_free(request); goto fail; } return NULL; fail: reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } static GObexApparam *parse_subject_length(GObexApparam *apparam, DBusMessageIter *iter) { guint8 num; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BYTE) return NULL; dbus_message_iter_get_basic(iter, &num); return g_obex_apparam_set_uint8(apparam, MAP_AP_SUBJECTLENGTH, num); } static uint64_t get_filter_mask(const char *filterstr) { int i; if (!filterstr) return 0; if (!g_ascii_strcasecmp(filterstr, "ALL")) return FILTER_ALL; for (i = 0; filter_list[i] != NULL; i++) if (!g_ascii_strcasecmp(filterstr, filter_list[i])) return 1ULL << i; return 0; } static int set_field(guint32 *filter, const char *filterstr) { guint64 mask; mask = get_filter_mask(filterstr); if (mask == 0) return -EINVAL; *filter |= mask; return 0; } static GObexApparam *parse_fields(GObexApparam *apparam, DBusMessageIter *iter) { DBusMessageIter array; guint32 filter = 0; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return NULL; dbus_message_iter_recurse(iter, &array); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) { const char *string; dbus_message_iter_get_basic(&array, &string); if (set_field(&filter, string) < 0) return NULL; dbus_message_iter_next(&array); } return g_obex_apparam_set_uint32(apparam, MAP_AP_PARAMETERMASK, filter); } static GObexApparam *parse_filter_type(GObexApparam *apparam, DBusMessageIter *iter) { DBusMessageIter array; guint8 types = 0; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return NULL; dbus_message_iter_recurse(iter, &array); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) { const char *string; dbus_message_iter_get_basic(&array, &string); if (!g_ascii_strcasecmp(string, "sms")) types |= 0x03; /* sms-gsm and sms-cdma */ else if (!g_ascii_strcasecmp(string, "email")) types |= 0x04; /* email */ else if (!g_ascii_strcasecmp(string, "mms")) types |= 0x08; /* mms */ else return NULL; dbus_message_iter_next(&array); } return g_obex_apparam_set_uint8(apparam, MAP_AP_FILTERMESSAGETYPE, types); } static GObexApparam *parse_period_begin(GObexApparam *apparam, DBusMessageIter *iter) { const char *string; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return NULL; dbus_message_iter_get_basic(iter, &string); return g_obex_apparam_set_string(apparam, MAP_AP_FILTERPERIODBEGIN, string); } static GObexApparam *parse_period_end(GObexApparam *apparam, DBusMessageIter *iter) { const char *string; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return NULL; dbus_message_iter_get_basic(iter, &string); return g_obex_apparam_set_string(apparam, MAP_AP_FILTERPERIODEND, string); } static GObexApparam *parse_filter_read(GObexApparam *apparam, DBusMessageIter *iter) { guint8 status = FILTER_READ_STATUS_NONE; dbus_bool_t dbus_status = FALSE; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) return NULL; dbus_message_iter_get_basic(iter, &dbus_status); if (dbus_status) status = FILTER_READ_STATUS_ONLY_READ; else status = FILTER_READ_STATUS_ONLY_UNREAD; return g_obex_apparam_set_uint8(apparam, MAP_AP_FILTERREADSTATUS, status); } static GObexApparam *parse_filter_recipient(GObexApparam *apparam, DBusMessageIter *iter) { const char *string; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return NULL; dbus_message_iter_get_basic(iter, &string); return g_obex_apparam_set_string(apparam, MAP_AP_FILTERRECIPIENT, string); } static GObexApparam *parse_filter_sender(GObexApparam *apparam, DBusMessageIter *iter) { const char *string; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return NULL; dbus_message_iter_get_basic(iter, &string); return g_obex_apparam_set_string(apparam, MAP_AP_FILTERORIGINATOR, string); } static GObexApparam *parse_filter_priority(GObexApparam *apparam, DBusMessageIter *iter) { guint8 priority = FILTER_PRIORITY_NONE; dbus_bool_t dbus_priority = FALSE; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) return NULL; dbus_message_iter_get_basic(iter, &dbus_priority); if (dbus_priority) priority = FILTER_PRIORITY_ONLY_HIGH; else priority = FILTER_PRIORITY_ONLY_NONHIGH; return g_obex_apparam_set_uint8(apparam, MAP_AP_FILTERPRIORITY, priority); } static GObexApparam *parse_message_filters(GObexApparam *apparam, DBusMessageIter *iter) { DBusMessageIter array; DBG(""); if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return NULL; dbus_message_iter_recurse(iter, &array); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { const char *key; DBusMessageIter value, entry; dbus_message_iter_recurse(&array, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); if (strcasecmp(key, "Offset") == 0) { if (parse_offset(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "MaxCount") == 0) { if (parse_max_count(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "SubjectLength") == 0) { if (parse_subject_length(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "Fields") == 0) { if (parse_fields(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "Types") == 0) { if (parse_filter_type(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "PeriodBegin") == 0) { if (parse_period_begin(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "PeriodEnd") == 0) { if (parse_period_end(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "Read") == 0) { if (parse_filter_read(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "Recipient") == 0) { if (parse_filter_recipient(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "Sender") == 0) { if (parse_filter_sender(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "Priority") == 0) { if (parse_filter_priority(apparam, &value) == NULL) return NULL; } dbus_message_iter_next(&array); } return apparam; } static DBusMessage *map_list_messages(DBusConnection *connection, DBusMessage *message, void *user_data) { struct map_data *map = user_data; const char *folder; GObexApparam *apparam; DBusMessageIter args; dbus_message_iter_init(message, &args); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); dbus_message_iter_get_basic(&args, &folder); apparam = g_obex_apparam_set_uint16(NULL, MAP_AP_MAXLISTCOUNT, DEFAULT_COUNT); apparam = g_obex_apparam_set_uint16(apparam, MAP_AP_STARTOFFSET, DEFAULT_OFFSET); dbus_message_iter_next(&args); if (parse_message_filters(apparam, &args) == NULL) { g_obex_apparam_free(apparam); return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); } return get_message_listing(map, message, folder, apparam); } static char **get_filter_strs(uint64_t filter, int *size) { char **list, **item; int i; list = g_malloc0(sizeof(char **) * (FILTER_BIT_MAX + 2)); item = list; for (i = 0; filter_list[i] != NULL; i++) if (filter & (1ULL << i)) *(item++) = g_strdup(filter_list[i]); *item = NULL; *size = item - list; return list; } static DBusMessage *map_list_filter_fields(DBusConnection *connection, DBusMessage *message, void *user_data) { char **filters = NULL; int size; DBusMessage *reply; filters = get_filter_strs(FILTER_ALL, &size); reply = dbus_message_new_method_return(message); dbus_message_append_args(reply, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &filters, size, DBUS_TYPE_INVALID); g_strfreev(filters); return reply; } static void update_inbox_cb(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { struct pending_request *request = user_data; DBusMessage *reply; if (err != NULL) { reply = g_dbus_create_error(request->msg, ERROR_INTERFACE ".Failed", "%s", err->message); goto done; } reply = dbus_message_new_method_return(request->msg); done: g_dbus_send_message(conn, reply); pending_request_free(request); } static DBusMessage *map_update_inbox(DBusConnection *connection, DBusMessage *message, void *user_data) { struct map_data *map = user_data; DBusMessage *reply; char contents[1]; struct obc_transfer *transfer; GError *err = NULL; struct pending_request *request; contents[0] = FILLER_BYTE; transfer = obc_transfer_put("x-bt/MAP-messageUpdate", NULL, NULL, contents, sizeof(contents), &err); if (transfer == NULL) goto fail; request = pending_request_new(map, message); if (!obc_session_queue(map->session, transfer, update_inbox_cb, request, &err)) { pending_request_free(request); goto fail; } return NULL; fail: reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } static DBusMessage *push_message(struct map_data *map, DBusMessage *message, const char *filename, const char *folder, GObexApparam *apparam) { struct obc_transfer *transfer; GError *err = NULL; DBusMessage *reply; transfer = obc_transfer_put("x-bt/message", folder, filename, NULL, 0, &err); if (transfer == NULL) { g_obex_apparam_free(apparam); goto fail; } obc_transfer_set_apparam(transfer, apparam); if (!obc_session_queue(map->session, transfer, NULL, NULL, &err)) goto fail; return obc_transfer_create_dbus_reply(transfer, message); fail: reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } static GObexApparam *parse_transparent(GObexApparam *apparam, DBusMessageIter *iter) { dbus_bool_t transparent; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) return NULL; dbus_message_iter_get_basic(iter, &transparent); return g_obex_apparam_set_uint8(apparam, MAP_AP_TRANSPARENT, transparent ? TRUE : FALSE); } static GObexApparam *parse_retry(GObexApparam *apparam, DBusMessageIter *iter) { dbus_bool_t retry; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) return NULL; dbus_message_iter_get_basic(iter, &retry); return g_obex_apparam_set_uint8(apparam, MAP_AP_RETRY, retry ? TRUE : FALSE); } static GObexApparam *parse_charset(GObexApparam *apparam, DBusMessageIter *iter) { guint8 charset = 0; const char *string; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return NULL; dbus_message_iter_get_basic(iter, &string); if (strcasecmp(string, "native") == 0) charset = CHARSET_NATIVE; else if (strcasecmp(string, "utf8") == 0) charset = CHARSET_UTF8; else return NULL; return g_obex_apparam_set_uint8(apparam, MAP_AP_CHARSET, charset); } static GObexApparam *parse_push_options(GObexApparam *apparam, DBusMessageIter *iter) { DBusMessageIter array; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return NULL; dbus_message_iter_recurse(iter, &array); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { const char *key; DBusMessageIter value, entry; dbus_message_iter_recurse(&array, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); if (strcasecmp(key, "Transparent") == 0) { if (parse_transparent(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "Retry") == 0) { if (parse_retry(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "Charset") == 0) { if (parse_charset(apparam, &value) == NULL) return NULL; } dbus_message_iter_next(&array); } return apparam; } static DBusMessage *map_push_message(DBusConnection *connection, DBusMessage *message, void *user_data) { struct map_data *map = user_data; char *filename; char *folder; GObexApparam *apparam; DBusMessageIter args; dbus_message_iter_init(message, &args); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); dbus_message_iter_get_basic(&args, &filename); dbus_message_iter_next(&args); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) { return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); } dbus_message_iter_get_basic(&args, &folder); dbus_message_iter_next(&args); apparam = g_obex_apparam_set_uint8(NULL, MAP_AP_CHARSET, CHARSET_UTF8); if (parse_push_options(apparam, &args) == NULL) { g_obex_apparam_free(apparam); return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); } return push_message(map, message, filename, folder, apparam); } static const GDBusMethodTable map_methods[] = { { GDBUS_ASYNC_METHOD("SetFolder", GDBUS_ARGS({ "name", "s" }), NULL, map_setpath) }, { GDBUS_ASYNC_METHOD("ListFolders", GDBUS_ARGS({ "filters", "a{sv}" }), GDBUS_ARGS({ "content", "aa{sv}" }), map_list_folders) }, { GDBUS_ASYNC_METHOD("ListMessages", GDBUS_ARGS({ "folder", "s" }, { "filter", "a{sv}" }), GDBUS_ARGS({ "messages", "a{oa{sv}}" }), map_list_messages) }, { GDBUS_METHOD("ListFilterFields", NULL, GDBUS_ARGS({ "fields", "as" }), map_list_filter_fields) }, { GDBUS_ASYNC_METHOD("UpdateInbox", NULL, NULL, map_update_inbox) }, { GDBUS_ASYNC_METHOD("PushMessage", GDBUS_ARGS({ "file", "s" }, { "folder", "s" }, { "args", "a{sv}" }), GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), map_push_message) }, { } }; static gboolean get_supported_types(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct map_data *map = user_data; DBusMessageIter entry; const char *str; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); if (map->supported_message_types & SDP_MESSAGE_TYPE_EMAIL) { str = "EMAIL"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str); } if (map->supported_message_types & SDP_MESSAGE_TYPE_SMS_GSM) { str = "SMS_GSM"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str); } if (map->supported_message_types & SDP_MESSAGE_TYPE_SMS_CDMA) { str = "SMS_CDMA"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str); } if (map->supported_message_types & SDP_MESSAGE_TYPE_MMS) { str = "MMS"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str); } if (map->supported_message_types & SDP_MESSAGE_TYPE_IM) { str = "IM"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str); } dbus_message_iter_close_container(iter, &entry); return TRUE; } static const GDBusPropertyTable map_properties[] = { { "SupportedTypes", "as", get_supported_types }, { } }; static void map_msg_remove(void *data) { struct map_msg *msg = data; char *path; path = msg->path; msg->path = NULL; g_dbus_unregister_interface(conn, path, MAP_MSG_INTERFACE); g_free(path); } static void map_handle_new_message(struct map_data *map, struct map_event *event) { struct map_msg *msg; msg = g_hash_table_lookup(map->messages, &event->handle); /* New message event can be used if a new message replaces an old one */ if (msg) g_hash_table_remove(map->messages, &event->handle); map_msg_create(map, event->handle, event->folder, event->msg_type); } static void map_handle_status_changed(struct map_data *map, struct map_event *event, const char *status) { struct map_msg *msg; msg = g_hash_table_lookup(map->messages, &event->handle); if (msg == NULL) return; if (g_strcmp0(msg->status, status) == 0) return; g_free(msg->status); msg->status = g_strdup(status); g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "Status"); } static void map_handle_folder_changed(struct map_data *map, struct map_event *event, const char *folder) { struct map_msg *msg; if (!folder) return; msg = g_hash_table_lookup(map->messages, &event->handle); if (!msg) return; if (g_strcmp0(msg->folder, folder) == 0) return; g_free(msg->folder); msg->folder = g_strdup(folder); g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, "Folder"); } static void map_handle_notification(struct map_event *event, void *user_data) { struct map_data *map = user_data; DBG("Event report for %s:%d", obc_session_get_destination(map->session), map->mas_instance_id); DBG("type=%x handle=%" PRIx64 " folder=%s old_folder=%s msg_type=%s", event->type, event->handle, event->folder, event->old_folder, event->msg_type); switch (event->type) { case MAP_ET_NEW_MESSAGE: map_handle_new_message(map, event); map_handle_status_changed(map, event, "notification"); break; case MAP_ET_DELIVERY_SUCCESS: map_handle_status_changed(map, event, "delivery-success"); break; case MAP_ET_SENDING_SUCCESS: map_handle_status_changed(map, event, "sending-success"); break; case MAP_ET_DELIVERY_FAILURE: map_handle_status_changed(map, event, "delivery-failure"); break; case MAP_ET_SENDING_FAILURE: map_handle_status_changed(map, event, "sending-failure"); break; case MAP_ET_MESSAGE_DELETED: map_handle_folder_changed(map, event, "/telecom/msg/deleted"); break; case MAP_ET_MESSAGE_SHIFT: map_handle_folder_changed(map, event, event->folder); break; case MAP_ET_MEMORY_FULL: case MAP_ET_MEMORY_AVAILABLE: default: break; } } static bool set_notification_registration(struct map_data *map, bool status) { struct obc_transfer *transfer; GError *err = NULL; GObexApparam *apparam; char contents[1]; const char *address; address = obc_session_get_destination(map->session); if (!address || map->mas_instance_id < 0) return FALSE; if (status) { map_register_event_handler(map->session, map->mas_instance_id, &map_handle_notification, map); } else { map_unregister_event_handler(map->session, map->mas_instance_id); } contents[0] = FILLER_BYTE; transfer = obc_transfer_put("x-bt/MAP-NotificationRegistration", NULL, NULL, contents, sizeof(contents), &err); if (transfer == NULL) return false; apparam = g_obex_apparam_set_uint8(NULL, MAP_AP_NOTIFICATIONSTATUS, status); obc_transfer_set_apparam(transfer, apparam); if (obc_session_queue(map->session, transfer, NULL, map, &err)) return true; return false; } static void map_free(void *data) { struct map_data *map = data; set_notification_registration(map, false); obc_session_unref(map->session); g_hash_table_unref(map->messages); g_free(map); } static void parse_service_record(struct map_data *map) { const void *data; /* MAS instance id */ map->mas_instance_id = -1; data = obc_session_get_attribute(map->session, SDP_ATTR_MAS_INSTANCE_ID); if (data != NULL) map->mas_instance_id = *(uint8_t *)data; else DBG("Failed to read MAS instance id"); /* Supported Message Types */ data = obc_session_get_attribute(map->session, SDP_ATTR_SUPPORTED_MESSAGE_TYPES); if (data != NULL) map->supported_message_types = *(uint8_t *)data; else DBG("Failed to read supported message types"); /* Supported Feature Bits */ data = obc_session_get_attribute(map->session, SDP_ATTR_MAP_SUPPORTED_FEATURES); if(data != NULL) map->supported_features = *(uint32_t *) data; else map->supported_features = 0x0000001f; } static void *map_supported_features(struct obc_session *session) { const void *data; uint32_t supported_features; /* Supported Feature Bits */ data = obc_session_get_attribute(session, SDP_ATTR_MAP_SUPPORTED_FEATURES); if (!data) return NULL; supported_features = *(uint32_t *) data; if (!supported_features) return NULL; if (supported_features & MAPSUPPORTEDFEATURES_IN_CONNECT_REQUEST) return g_obex_apparam_set_uint32(NULL, SUPPORTED_FEATURES_TAG, NOTIFICATION_REGISTRATION_FEATURE | NOTIFICATION_FEATURE | BROWSING_FEATURE | UPLOADING_FEATURE | DELETE_FEATURE | INSTANCE_INFORMATION_FEATURE | EXTENDED_EVENT_REPORT_1_1 | MESSAGES_LISTING_FORMAT_VERSION_1_1); return NULL; } static int map_probe(struct obc_session *session) { struct map_data *map; const char *path; path = obc_session_get_path(session); map = g_try_new0(struct map_data, 1); if (!map) return -ENOMEM; map->session = obc_session_ref(session); map->messages = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, map_msg_remove); parse_service_record(map); DBG("%s, instance id %d", path, map->mas_instance_id); set_notification_registration(map, true); if (!g_dbus_register_interface(conn, path, MAP_INTERFACE, map_methods, NULL, map_properties, map, map_free)) { map_free(map); return -ENOMEM; } return 0; } static void map_remove(struct obc_session *session) { const char *path = obc_session_get_path(session); DBG("%s", path); g_dbus_unregister_interface(conn, path, MAP_INTERFACE); } static struct obc_driver map = { .service = "MAP", .uuid = MAS_UUID, .target = OBEX_MAS_UUID, .target_len = OBEX_MAS_UUID_LEN, .supported_features = map_supported_features, .probe = map_probe, .remove = map_remove }; int map_init(void) { int err; DBG(""); conn = obex_get_dbus_connection(); if (!conn) return -EIO; err = obc_driver_register(&map); if (err < 0) { dbus_connection_unref(conn); conn = NULL; return err; } return 0; } void map_exit(void) { DBG(""); dbus_connection_unref(conn); conn = NULL; obc_driver_unregister(&map); } bluez-5.87/obexd/client/PaxHeaders/transfer.c0000644000000000000000000000005015063045254016226 xustar0020 atime=1783093905 20 ctime=1783094105 bluez-5.87/obexd/client/transfer.c0000644000000000000000000005525715063045254015725 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Client * * Copyright (C) 2007-2010 Marcel Holtmann * Copyright (C) 2011-2012 BMW Car IT GmbH. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "gobex/gobex.h" #include "obexd/src/log.h" #include "transfer.h" #define TRANSFER_INTERFACE "org.bluez.obex.Transfer1" #define ERROR_INTERFACE "org.bluez.obex.Error" #define OBC_TRANSFER_ERROR obc_transfer_error_quark() #define FIRST_PACKET_TIMEOUT 60 static guint64 counter = 0; struct transfer_callback { transfer_callback_t func; void *data; }; enum { TRANSFER_STATUS_QUEUED = 0, TRANSFER_STATUS_ACTIVE, TRANSFER_STATUS_SUSPENDED, TRANSFER_STATUS_COMPLETE, TRANSFER_STATUS_SUSPENDED_QUEUED, TRANSFER_STATUS_ERROR }; struct obc_transfer { GObex *obex; uint8_t status; GObexApparam *apparam; GSList *headers; guint8 op; struct transfer_callback *callback; DBusConnection *conn; DBusMessage *msg; char *session; /* Session path */ char *owner; /* Transfer initiator */ char *path; /* Transfer path */ char *filename; /* Transfer file location */ char *name; /* Transfer object name */ char *type; /* Transfer object type */ int fd; guint req; guint xfer; gint64 size; gint64 transferred; gint64 progress; guint progress_id; }; static GQuark obc_transfer_error_quark(void) { return g_quark_from_static_string("obc-transfer-error-quark"); } DBusMessage *obc_transfer_create_dbus_reply(struct obc_transfer *transfer, DBusMessage *message) { DBusMessage *reply; DBusMessageIter iter; reply = dbus_message_new_method_return(message); if (reply == NULL) return NULL; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &transfer->path); g_dbus_get_properties(transfer->conn, transfer->path, TRANSFER_INTERFACE, &iter); return reply; } static void abort_complete(GObex *obex, GError *err, gpointer user_data) { struct obc_transfer *transfer = user_data; struct transfer_callback *callback = transfer->callback; DBusMessage *reply; transfer->xfer = 0; reply = dbus_message_new_method_return(transfer->msg); if (reply) g_dbus_send_message(transfer->conn, reply); dbus_message_unref(transfer->msg); transfer->msg = NULL; if (callback == NULL) return; if (err) { callback->func(transfer, err, callback->data); } else { GError *abort_err; abort_err = g_error_new(OBC_TRANSFER_ERROR, -ECANCELED, "%s", "Transfer cancelled by user"); callback->func(transfer, abort_err, callback->data); g_error_free(abort_err); } } static DBusMessage *obc_transfer_cancel(DBusConnection *connection, DBusMessage *message, void *user_data) { struct obc_transfer *transfer = user_data; const char *sender; sender = dbus_message_get_sender(message); if (g_strcmp0(transfer->owner, sender) != 0) return g_dbus_create_error(message, ERROR_INTERFACE ".NotAuthorized", "Not Authorized"); if (transfer->msg != NULL) return g_dbus_create_error(message, ERROR_INTERFACE ".InProgress", "Cancellation already in progress"); if (transfer->status == TRANSFER_STATUS_SUSPENDED) g_obex_resume(transfer->obex); if (transfer->req > 0) { if (!g_obex_cancel_req(transfer->obex, transfer->req, TRUE)) return g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "Failed"); transfer->req = 0; } if (transfer->xfer == 0) { struct transfer_callback *callback = transfer->callback; if (callback != NULL) { GError *err; err = g_error_new(OBC_TRANSFER_ERROR, -ECANCELED, "%s", "Transfer cancelled by user"); callback->func(transfer, err, callback->data); g_error_free(err); } return dbus_message_new_method_return(message); } if (transfer->progress_id != 0) { g_source_remove(transfer->progress_id); transfer->progress_id = 0; } if (!g_obex_cancel_transfer(transfer->xfer, abort_complete, transfer)) return g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "Failed"); transfer->msg = dbus_message_ref(message); return NULL; } static void transfer_set_status(struct obc_transfer *transfer, uint8_t status) { if (transfer->status == status) return; transfer->status = status; g_dbus_emit_property_changed(transfer->conn, transfer->path, TRANSFER_INTERFACE, "Status"); } static DBusMessage *obc_transfer_suspend(DBusConnection *connection, DBusMessage *message, void *user_data) { struct obc_transfer *transfer = user_data; const char *sender; uint8_t status; sender = dbus_message_get_sender(message); if (g_strcmp0(transfer->owner, sender) != 0) return g_dbus_create_error(message, ERROR_INTERFACE ".NotAuthorized", "Not Authorized"); switch (transfer->status) { case TRANSFER_STATUS_QUEUED: status = TRANSFER_STATUS_SUSPENDED_QUEUED; break; case TRANSFER_STATUS_ACTIVE: if (transfer->xfer) g_obex_suspend(transfer->obex); status = TRANSFER_STATUS_SUSPENDED; break; default: return g_dbus_create_error(message, ERROR_INTERFACE ".NotInProgress", "Not in progress"); } transfer_set_status(transfer, status); return g_dbus_create_reply(message, DBUS_TYPE_INVALID); } static DBusMessage *obc_transfer_resume(DBusConnection *connection, DBusMessage *message, void *user_data) { struct obc_transfer *transfer = user_data; const char *sender; uint8_t status; sender = dbus_message_get_sender(message); if (g_strcmp0(transfer->owner, sender) != 0) return g_dbus_create_error(message, ERROR_INTERFACE ".NotAuthorized", "Not Authorized"); switch (transfer->status) { case TRANSFER_STATUS_SUSPENDED_QUEUED: status = TRANSFER_STATUS_QUEUED; break; case TRANSFER_STATUS_SUSPENDED: if (transfer->xfer) g_obex_resume(transfer->obex); else obc_transfer_start(transfer, NULL, NULL); status = TRANSFER_STATUS_ACTIVE; break; default: return g_dbus_create_error(message, ERROR_INTERFACE ".NotInProgress", "Not in progress"); } transfer_set_status(transfer, status); return g_dbus_create_reply(message, DBUS_TYPE_INVALID); } static gboolean name_exists(const GDBusPropertyTable *property, void *data) { struct obc_transfer *transfer = data; return transfer->name != NULL; } static gboolean get_name(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obc_transfer *transfer = data; if (transfer->name == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &transfer->name); return TRUE; } static gboolean get_size(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obc_transfer *transfer = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &transfer->size); return TRUE; } static gboolean filename_exists(const GDBusPropertyTable *property, void *data) { struct obc_transfer *transfer = data; return transfer->filename != NULL; } static gboolean get_filename(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obc_transfer *transfer = data; if (transfer->filename == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &transfer->filename); return TRUE; } static gboolean transferred_exists(const GDBusPropertyTable *property, void *data) { struct obc_transfer *transfer = data; return transfer->obex != NULL; } static gboolean get_transferred(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obc_transfer *transfer = data; if (transfer->obex == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &transfer->progress); return TRUE; } static const char *status2str(uint8_t status) { switch (status) { case TRANSFER_STATUS_QUEUED: return "queued"; case TRANSFER_STATUS_ACTIVE: return "active"; case TRANSFER_STATUS_SUSPENDED_QUEUED: case TRANSFER_STATUS_SUSPENDED: return "suspended"; case TRANSFER_STATUS_COMPLETE: return "complete"; case TRANSFER_STATUS_ERROR: default: return "error"; } } static gboolean get_status(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obc_transfer *transfer = data; const char *status = status2str(transfer->status); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &status); return TRUE; } static gboolean get_session(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obc_transfer *transfer = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &transfer->session); return TRUE; } static const GDBusMethodTable obc_transfer_methods[] = { { GDBUS_METHOD("Suspend", NULL, NULL, obc_transfer_suspend) }, { GDBUS_METHOD("Resume", NULL, NULL, obc_transfer_resume) }, { GDBUS_ASYNC_METHOD("Cancel", NULL, NULL, obc_transfer_cancel) }, { } }; static const GDBusPropertyTable obc_transfer_properties[] = { { "Status", "s", get_status }, { "Name", "s", get_name, NULL, name_exists }, { "Size", "t", get_size }, { "Filename", "s", get_filename, NULL, filename_exists }, { "Transferred", "t", get_transferred, NULL, transferred_exists }, { "Session", "o", get_session }, { } }; static void header_free(void *data, void *user_data) { g_obex_header_free(data); } static void obc_transfer_free(struct obc_transfer *transfer) { DBG("%p", transfer); if (transfer->status == TRANSFER_STATUS_SUSPENDED) g_obex_resume(transfer->obex); if (transfer->req > 0) g_obex_cancel_req(transfer->obex, transfer->req, TRUE); if (transfer->xfer) g_obex_cancel_transfer(transfer->xfer, NULL, NULL); if (transfer->progress_id != 0) { g_source_remove(transfer->progress_id); transfer->progress_id = 0; } if (transfer->op == G_OBEX_OP_GET && transfer->status != TRANSFER_STATUS_COMPLETE && transfer->filename) { if (remove(transfer->filename) < 0) error("remove(%s): %s(%d)", transfer->filename, strerror(errno), errno); } if (transfer->fd > 0) close(transfer->fd); if (transfer->apparam != NULL) g_obex_apparam_free(transfer->apparam); if (transfer->conn) dbus_connection_unref(transfer->conn); if (transfer->msg) dbus_message_unref(transfer->msg); if (transfer->obex) g_obex_unref(transfer->obex); g_slist_foreach(transfer->headers, header_free, NULL); g_slist_free(transfer->headers); g_free(transfer->callback); g_free(transfer->owner); g_free(transfer->filename); g_free(transfer->name); g_free(transfer->type); g_free(transfer->session); g_free(transfer->path); g_free(transfer); } static struct obc_transfer *obc_transfer_create(guint8 op, const char *filename, const char *name, const char *type) { struct obc_transfer *transfer; transfer = g_new0(struct obc_transfer, 1); transfer->op = op; transfer->filename = g_strdup(filename); transfer->name = g_strdup(name); transfer->type = g_strdup(type); return transfer; } gboolean obc_transfer_register(struct obc_transfer *transfer, DBusConnection *conn, const char *session, const char *owner, GError **err) { transfer->owner = g_strdup(owner); transfer->session = g_strdup(session); transfer->path = g_strdup_printf("%s/transfer%ju", session, counter++); transfer->conn = dbus_connection_ref(conn); if (transfer->conn == NULL) { g_set_error(err, OBC_TRANSFER_ERROR, -EFAULT, "Unable to connect to D-Bus"); return FALSE; } if (g_dbus_register_interface(transfer->conn, transfer->path, TRANSFER_INTERFACE, obc_transfer_methods, NULL, obc_transfer_properties, transfer, NULL) == FALSE) { g_set_error(err, OBC_TRANSFER_ERROR, -EFAULT, "Unable to register to D-Bus"); return FALSE; } DBG("%p registered %s", transfer, transfer->path); return TRUE; } static gboolean transfer_open(struct obc_transfer *transfer, int flags, mode_t mode, GError **err) { int fd; char *filename; if (transfer->filename != NULL && strcmp(transfer->filename, "") != 0) { fd = open(transfer->filename, flags, mode); if (fd < 0) { error("open(): %s(%d)", strerror(errno), errno); g_set_error(err, OBC_TRANSFER_ERROR, -errno, "Unable to open file"); return FALSE; } goto done; } fd = g_file_open_tmp("obex-clientXXXXXX", &filename, err); if (fd < 0) { error("g_file_open_tmp(): %s", (*err)->message); return FALSE; } if (transfer->filename == NULL) { /* remove always only if NULL was given */ if (remove(filename) < 0) error("remove(%s): %s(%d)", filename, strerror(errno), errno); g_free(filename); } else { g_free(transfer->filename); transfer->filename = filename; } done: transfer->fd = fd; return TRUE; } struct obc_transfer *obc_transfer_get(const char *type, const char *name, const char *filename, GError **err) { struct obc_transfer *transfer; int perr; transfer = obc_transfer_create(G_OBEX_OP_GET, filename, name, type); perr = transfer_open(transfer, O_WRONLY | O_CREAT | O_TRUNC, 0600, err); if (!perr) { obc_transfer_free(transfer); return NULL; } return transfer; } struct obc_transfer *obc_transfer_put(const char *type, const char *name, const char *filename, const void *contents, size_t size, GError **err) { struct obc_transfer *transfer; struct stat st; int perr; if ((filename == NULL || strcmp(filename, "") == 0) && contents == NULL) { g_set_error(err, OBC_TRANSFER_ERROR, -EINVAL, "Invalid filename given"); return NULL; } transfer = obc_transfer_create(G_OBEX_OP_PUT, filename, name, type); if (contents != NULL) { ssize_t w; if (!transfer_open(transfer, O_RDWR, 0, err)) goto fail; w = write(transfer->fd, contents, size); if (w < 0) { perr = errno; error("write(): %s(%d)", strerror(perr), perr); g_set_error(err, OBC_TRANSFER_ERROR, -perr, "Writing to file failed"); goto fail; } else if ((size_t) w != size) { error("Unable to write all contents to file"); g_set_error(err, OBC_TRANSFER_ERROR, -EFAULT, "Writing all contents to file failed"); goto fail; } lseek(transfer->fd, 0, SEEK_SET); } else { if (!transfer_open(transfer, O_RDONLY, 0, err)) goto fail; } if (fstat(transfer->fd, &st) < 0) { perr = errno; error("fstat(): %s(%d)", strerror(perr), perr); g_set_error(err, OBC_TRANSFER_ERROR, -perr, "Unable to get file status"); goto fail; } transfer->size = st.st_size; return transfer; fail: obc_transfer_free(transfer); return NULL; } void obc_transfer_unregister(struct obc_transfer *transfer) { if (transfer->path) { g_dbus_unregister_interface(transfer->conn, transfer->path, TRANSFER_INTERFACE); } DBG("%p unregistered %s", transfer, transfer->path); obc_transfer_free(transfer); } static gboolean get_xfer_progress(const void *buf, gsize len, gpointer user_data) { struct obc_transfer *transfer = user_data; if (transfer->fd > 0) { int w; w = write(transfer->fd, buf, len); if (w < 0) return FALSE; transfer->transferred += w; } return TRUE; } static void xfer_complete(GObex *obex, GError *err, gpointer user_data) { struct obc_transfer *transfer = user_data; struct transfer_callback *callback = transfer->callback; transfer->xfer = 0; transfer->progress = transfer->transferred; g_dbus_emit_property_changed(transfer->conn, transfer->path, TRANSFER_INTERFACE, "Transferred"); if (transfer->progress_id != 0) { g_source_remove(transfer->progress_id); transfer->progress_id = 0; } if (transfer->status == TRANSFER_STATUS_SUSPENDED) g_obex_resume(transfer->obex); if (err) transfer_set_status(transfer, TRANSFER_STATUS_ERROR); else transfer_set_status(transfer, TRANSFER_STATUS_COMPLETE); if (callback == NULL) return; if (callback->func) callback->func(transfer, err, callback->data); } static void get_xfer_progress_first(GObex *obex, GError *err, GObexPacket *rsp, gpointer user_data) { struct obc_transfer *transfer = user_data; GObexPacket *req; GObexHeader *hdr; GObexApparam *apparam; const guint8 *buf; gsize len; guint8 rspcode; gboolean final; if (err != NULL) { xfer_complete(obex, err, transfer); return; } rspcode = g_obex_packet_get_operation(rsp, &final); if (rspcode != G_OBEX_RSP_SUCCESS && rspcode != G_OBEX_RSP_CONTINUE) { err = g_error_new(OBC_TRANSFER_ERROR, rspcode, "%s", g_obex_strerror(rspcode)); xfer_complete(obex, err, transfer); g_error_free(err); return; } hdr = g_obex_packet_get_header(rsp, G_OBEX_HDR_LENGTH); if (hdr) { uint32_t len; if (g_obex_header_get_uint32(hdr, &len)) { transfer->size = len; g_dbus_emit_property_changed(transfer->conn, transfer->path, TRANSFER_INTERFACE, "Size"); } } hdr = g_obex_packet_get_header(rsp, G_OBEX_HDR_APPARAM); if (hdr) { apparam = g_obex_header_get_apparam(hdr); if (apparam != NULL) obc_transfer_set_apparam(transfer, apparam); } hdr = g_obex_packet_get_body(rsp); if (hdr) { g_obex_header_get_bytes(hdr, &buf, &len); if (len != 0) get_xfer_progress(buf, len, transfer); } if (rspcode == G_OBEX_RSP_SUCCESS) { transfer->req = 0; xfer_complete(obex, err, transfer); return; } if (g_obex_srm_active(obex) || transfer->status == TRANSFER_STATUS_SUSPENDED) return; transfer->req = 0; req = g_obex_packet_new(G_OBEX_OP_GET, TRUE, G_OBEX_HDR_INVALID); transfer->xfer = g_obex_get_req_pkt(obex, req, get_xfer_progress, xfer_complete, transfer, &err); } static gssize put_xfer_progress(void *buf, gsize len, gpointer user_data) { struct obc_transfer *transfer = user_data; gssize size; size = read(transfer->fd, buf, len); if (size <= 0) return size; transfer->transferred += size; return size; } gboolean obc_transfer_set_callback(struct obc_transfer *transfer, transfer_callback_t func, void *user_data) { struct transfer_callback *callback; if (transfer->callback != NULL) return FALSE; callback = g_new0(struct transfer_callback, 1); callback->func = func; callback->data = user_data; transfer->callback = callback; return TRUE; } static gboolean report_progress(gpointer data) { struct obc_transfer *transfer = data; if (transfer->transferred == transfer->progress) return TRUE; transfer->progress = transfer->transferred; if (transfer->transferred == transfer->size) { transfer->progress_id = 0; return FALSE; } if (transfer->status != TRANSFER_STATUS_ACTIVE && transfer->status != TRANSFER_STATUS_SUSPENDED) transfer_set_status(transfer, TRANSFER_STATUS_ACTIVE); g_dbus_emit_property_changed(transfer->conn, transfer->path, TRANSFER_INTERFACE, "Transferred"); return TRUE; } static gboolean transfer_start_get(struct obc_transfer *transfer, GError **err) { GObexPacket *req; GObexHeader *hdr; if (transfer->xfer > 0) { g_set_error(err, OBC_TRANSFER_ERROR, -EALREADY, "Transfer already started"); return FALSE; } req = g_obex_packet_new(G_OBEX_OP_GET, TRUE, G_OBEX_HDR_INVALID); if (transfer->name != NULL) g_obex_packet_add_unicode(req, G_OBEX_HDR_NAME, transfer->name); if (transfer->type != NULL) g_obex_packet_add_bytes(req, G_OBEX_HDR_TYPE, transfer->type, strlen(transfer->type) + 1); while (transfer->headers) { hdr = transfer->headers->data; g_obex_packet_add_header(req, hdr); transfer->headers = g_slist_remove(transfer->headers, hdr); } if (transfer->apparam != NULL) { hdr = g_obex_header_new_apparam(transfer->apparam); g_obex_packet_add_header(req, hdr); } transfer->req = g_obex_send_req(transfer->obex, req, FIRST_PACKET_TIMEOUT, get_xfer_progress_first, transfer, err); if (transfer->req == 0) return FALSE; if (transfer->path == NULL) return TRUE; transfer->progress_id = g_timeout_add_seconds(1, report_progress, transfer); return TRUE; } static gboolean transfer_start_put(struct obc_transfer *transfer, GError **err) { GObexPacket *req; GObexHeader *hdr; if (transfer->xfer > 0) { g_set_error(err, OBC_TRANSFER_ERROR, -EALREADY, "Transfer already started"); return FALSE; } req = g_obex_packet_new(G_OBEX_OP_PUT, FALSE, G_OBEX_HDR_INVALID); if (transfer->name != NULL) g_obex_packet_add_unicode(req, G_OBEX_HDR_NAME, transfer->name); if (transfer->type != NULL) g_obex_packet_add_bytes(req, G_OBEX_HDR_TYPE, transfer->type, strlen(transfer->type) + 1); if (transfer->size < UINT32_MAX) g_obex_packet_add_uint32(req, G_OBEX_HDR_LENGTH, transfer->size); if (transfer->apparam != NULL) { hdr = g_obex_header_new_apparam(transfer->apparam); g_obex_packet_add_header(req, hdr); } transfer->xfer = g_obex_put_req_pkt(transfer->obex, req, put_xfer_progress, xfer_complete, transfer, err); if (transfer->xfer == 0) return FALSE; if (transfer->path == NULL) return TRUE; transfer->progress_id = g_timeout_add_seconds(1, report_progress, transfer); return TRUE; } gboolean obc_transfer_start(struct obc_transfer *transfer, void *obex, GError **err) { if (!transfer->obex) transfer->obex = g_obex_ref(obex); if (transfer->status == TRANSFER_STATUS_SUSPENDED_QUEUED) { /* Reset status so the transfer can be resumed */ transfer->status = TRANSFER_STATUS_SUSPENDED; return TRUE; } switch (transfer->op) { case G_OBEX_OP_GET: return transfer_start_get(transfer, err); case G_OBEX_OP_PUT: return transfer_start_put(transfer, err); } g_set_error(err, OBC_TRANSFER_ERROR, -ENOTSUP, "Not supported"); return FALSE; } guint8 obc_transfer_get_operation(struct obc_transfer *transfer) { return transfer->op; } void obc_transfer_set_apparam(struct obc_transfer *transfer, void *data) { if (transfer->apparam != NULL) g_obex_apparam_free(transfer->apparam); if (data == NULL) return; transfer->apparam = data; } void *obc_transfer_get_apparam(struct obc_transfer *transfer) { return transfer->apparam; } int obc_transfer_get_contents(struct obc_transfer *transfer, char **contents, size_t *size) { struct stat st; ssize_t ret; if (contents == NULL) return -EINVAL; if (fstat(transfer->fd, &st) < 0) { error("fstat(): %s(%d)", strerror(errno), errno); return -errno; } if (lseek(transfer->fd, 0, SEEK_SET) < 0) { error("lseek(): %s(%d)", strerror(errno), errno); return -errno; } *contents = g_malloc(st.st_size + 1); ret = read(transfer->fd, *contents, st.st_size); if (ret < 0) { error("read(): %s(%d)", strerror(errno), errno); g_free(*contents); *contents = NULL; return -errno; } (*contents)[ret] = '\0'; if (size) *size = ret; return 0; } const char *obc_transfer_get_path(struct obc_transfer *transfer) { return transfer->path; } gint64 obc_transfer_get_size(struct obc_transfer *transfer) { return transfer->size; } void obc_transfer_add_header(struct obc_transfer *transfer, void *data) { transfer->headers = g_slist_append(transfer->headers, data); } bluez-5.87/obexd/client/PaxHeaders/pbap.c0000644000000000000000000000005015221752714015326 xustar0020 atime=1783093891 20 ctime=1783094105 bluez-5.87/obexd/client/pbap.c0000644000000000000000000011031215221752714015005 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Client * * Copyright (C) 2007-2010 Intel Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "gobex/gobex-apparam.h" #include "gdbus/gdbus.h" #include "obexd/src/log.h" #include "obexd/src/logind.h" #include "obexd/src/obexd.h" #include "transfer.h" #include "session.h" #include "driver.h" #include "pbap.h" #define OBEX_PBAP_UUID \ "\x79\x61\x35\xF0\xF0\xC5\x11\xD8\x09\x66\x08\x00\x20\x0C\x9A\x66" #define OBEX_PBAP_UUID_LEN 16 #define FORMAT_VCARD21 0x0 #define FORMAT_VCARD30 0x1 #define ORDER_INDEXED 0x0 #define ORDER_ALPHANUMERIC 0x1 #define ORDER_PHONETIC 0x2 #define ATTRIB_NAME 0x0 #define ATTRIB_NUMBER 0x1 #define ATTRIB_SOUND 0x2 #define DEFAULT_COUNT 65535 #define DEFAULT_OFFSET 0 #define PULLPHONEBOOK 0x1 #define GETPHONEBOOKSIZE 0x2 #define ORDER_TAG 0x01 #define SEARCHVALUE_TAG 0x02 #define SEARCHATTRIB_TAG 0x03 #define MAXLISTCOUNT_TAG 0x04 #define LISTSTARTOFFSET_TAG 0x05 #define FILTER_TAG 0x06 #define FORMAT_TAG 0X07 #define PHONEBOOKSIZE_TAG 0X08 #define NEWMISSEDCALLS_TAG 0X09 #define PRIMARY_COUNTER_TAG 0X0A #define SECONDARY_COUNTER_TAG 0X0B #define DATABASEID_TAG 0X0D #define SUPPORTED_FEATURES_TAG 0x10 #define DOWNLOAD_FEATURE 0x00000001 #define BROWSE_FEATURE 0x00000002 #define DATABASEID_FEATURE 0x00000004 #define FOLDER_VERSION_FEATURE 0x00000008 #define VCARD_SELECTING_FEATURE 0x00000010 #define ENHANCED_CALLS_FEATURE 0x00000020 #define UCI_FEATURE 0x00000040 #define UID_FEATURE 0x00000080 #define REFERENCING_FEATURE 0x00000100 #define DEFAULT_IMAGE_FEATURE 0x00000200 static const char *filter_list[] = { "VERSION", "FN", "N", "PHOTO", "BDAY", "ADR", "LABEL", "TEL", "EMAIL", "MAILER", "TZ", "GEO", "TITLE", "ROLE", "LOGO", "AGENT", "ORG", "NOTE", "REV", "SOUND", "URL", "UID", "KEY", "NICKNAME", "CATEGORIES", "PROID", "CLASS", "SORT-STRING", "X-IRMC-CALL-DATETIME", "X-BT-SPEEDDIALKEY", "X-BT-UCI", "X-BT-UID", NULL }; #define FILTER_BIT_MAX 63 #define FILTER_ALL 0xFFFFFFFFFFFFFFFFULL #define PBAP_INTERFACE "org.bluez.obex.PhonebookAccess1" #define ERROR_INTERFACE "org.bluez.obex.Error" #define PBAP_CLIENT_UUID "0000112e-0000-1000-8000-00805f9b34fb" #define PBAP_UUID "0000112f-0000-1000-8000-00805f9b34fb" struct pbap_data { struct obc_session *session; char *path; uint16_t version; uint32_t supported_features; uint8_t databaseid[16]; uint8_t primary[16]; uint8_t secondary[16]; }; struct pending_request { struct pbap_data *pbap; DBusMessage *msg; }; static DBusConnection *conn = NULL; static DBusConnection *system_conn; static unsigned int listener_id; static char *client_path; static struct pending_request *pending_request_new(struct pbap_data *pbap, DBusMessage *message) { struct pending_request *p; p = g_new0(struct pending_request, 1); p->pbap = pbap; p->msg = dbus_message_ref(message); return p; } static void pending_request_free(struct pending_request *p) { dbus_message_unref(p->msg); g_free(p); } static void listing_element(GMarkupParseContext *ctxt, const char *element, const char **names, const char **values, gpointer user_data, GError **gerr) { DBusMessageIter *item = user_data, entry; char **key; const char *handle = NULL, *vcardname = NULL; if (g_str_equal(element, "card") != TRUE) return; for (key = (char **) names; *key; key++, values++) { if (g_str_equal(*key, "handle") == TRUE) handle = *values; else if (g_str_equal(*key, "name") == TRUE) vcardname = *values; } if (!handle || !vcardname) return; dbus_message_iter_open_container(item, DBUS_TYPE_STRUCT, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &handle); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &vcardname); dbus_message_iter_close_container(item, &entry); } static const GMarkupParser listing_parser = { listing_element, NULL, NULL, NULL, NULL }; static char *build_phonebook_path(const char *location, const char *item) { char *path = NULL, *tmp, *tmp1; gboolean internal = FALSE; if (!g_ascii_strcasecmp(location, "int") || !g_ascii_strcasecmp(location, "internal")) { path = g_strdup("/telecom"); internal = TRUE; } else if (!g_ascii_strncasecmp(location, "sim", 3)) { if (strlen(location) == 3) tmp = g_strdup("sim1"); else tmp = g_ascii_strup(location, 4); path = g_build_filename("/", tmp, "telecom", NULL); g_free(tmp); } else return NULL; if (!g_ascii_strcasecmp(item, "pb") || !g_ascii_strcasecmp(item, "ich") || !g_ascii_strcasecmp(item, "och") || !g_ascii_strcasecmp(item, "mch") || !g_ascii_strcasecmp(item, "cch") || (internal && !g_ascii_strcasecmp(item, "spd")) || (internal && !g_ascii_strcasecmp(item, "fav"))) { tmp = path; tmp1 = g_ascii_strdown(item, -1); path = g_build_filename(tmp, tmp1, NULL); g_free(tmp); g_free(tmp1); } else { g_free(path); return NULL; } return path; } /* should only be called inside pbap_set_path */ static void pbap_reset_path(struct pbap_data *pbap) { if (!pbap->path) return; obc_session_setpath(pbap->session, pbap->path, NULL, NULL, NULL); } static void pbap_setpath_cb(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { struct pending_request *request = user_data; struct pbap_data *pbap = request->pbap; if (err != NULL) pbap_reset_path(pbap); else g_dbus_emit_property_changed(conn, obc_session_get_path(pbap->session), PBAP_INTERFACE, "Folder"); if (err) { DBusMessage *reply = g_dbus_create_error(request->msg, ERROR_INTERFACE ".Failed", "%s", err->message); g_dbus_send_message(conn, reply); } else g_dbus_send_reply(conn, request->msg, DBUS_TYPE_INVALID); pending_request_free(request); } static void read_version(struct pbap_data *pbap, GObexApparam *apparam) { const guint8 *data; uint8_t value[16]; gsize len; if (!(pbap->supported_features & FOLDER_VERSION_FEATURE)) return; if (!g_obex_apparam_get_bytes(apparam, PRIMARY_COUNTER_TAG, &data, &len)) { len = sizeof(value); memset(value, 0, len); data = value; } if (len == sizeof(pbap->primary) && memcmp(pbap->primary, data, len)) { memcpy(pbap->primary, data, len); g_dbus_emit_property_changed(conn, obc_session_get_path(pbap->session), PBAP_INTERFACE, "PrimaryCounter"); } if (!g_obex_apparam_get_bytes(apparam, SECONDARY_COUNTER_TAG, &data, &len)) { len = sizeof(value); memset(value, 0, len); data = value; } if (len == sizeof(pbap->secondary) && memcmp(pbap->secondary, data, len)) { memcpy(pbap->secondary, data, len); g_dbus_emit_property_changed(conn, obc_session_get_path(pbap->session), PBAP_INTERFACE, "SecondaryCounter"); } } static void read_databaseid(struct pbap_data *pbap, GObexApparam *apparam) { const guint8 *data; guint8 value[16]; gsize len; if (!(pbap->supported_features & DATABASEID_FEATURE)) return; if (!g_obex_apparam_get_bytes(apparam, DATABASEID_TAG, &data, &len)) { len = sizeof(value); memset(value, 0, len); data = value; } if (len == sizeof(pbap->databaseid) && memcmp(data, pbap->databaseid, len)) { memcpy(pbap->databaseid, data, len); g_dbus_emit_property_changed(conn, obc_session_get_path(pbap->session), PBAP_INTERFACE, "DatabaseIdentifier"); } } static void read_return_apparam(struct obc_transfer *transfer, struct pbap_data *pbap, guint16 *phone_book_size, guint8 *new_missed_calls) { GObexApparam *apparam; *phone_book_size = 0; *new_missed_calls = 0; apparam = obc_transfer_get_apparam(transfer); if (apparam == NULL) return; g_obex_apparam_get_uint16(apparam, PHONEBOOKSIZE_TAG, phone_book_size); g_obex_apparam_get_uint8(apparam, NEWMISSEDCALLS_TAG, new_missed_calls); read_version(pbap, apparam); read_databaseid(pbap, apparam); } static void phonebook_size_callback(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { struct pending_request *request = user_data; DBusMessage *reply; guint16 phone_book_size; guint8 new_missed_calls; if (err) { reply = g_dbus_create_error(request->msg, ERROR_INTERFACE ".Failed", "%s", err->message); goto send; } reply = dbus_message_new_method_return(request->msg); read_return_apparam(transfer, request->pbap, &phone_book_size, &new_missed_calls); if (dbus_message_is_method_call(request->msg, PBAP_INTERFACE, "GetSize")) dbus_message_append_args(reply, DBUS_TYPE_UINT16, &phone_book_size, DBUS_TYPE_INVALID); send: g_dbus_send_message(conn, reply); pending_request_free(request); } static void pull_vcard_listing_callback(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { struct pending_request *request = user_data; GMarkupParseContext *ctxt; DBusMessage *reply; DBusMessageIter iter, array; char *contents; size_t size; int perr; if (err) { reply = g_dbus_create_error(request->msg, ERROR_INTERFACE ".Failed", "%s", err->message); goto send; } perr = obc_transfer_get_contents(transfer, &contents, &size); if (perr < 0) { reply = g_dbus_create_error(request->msg, ERROR_INTERFACE ".Failed", "Error reading contents: %s", strerror(-perr)); goto send; } reply = dbus_message_new_method_return(request->msg); dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_STRUCT_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_STRUCT_END_CHAR_AS_STRING, &array); ctxt = g_markup_parse_context_new(&listing_parser, 0, &array, NULL); g_markup_parse_context_parse(ctxt, contents, size, NULL); g_markup_parse_context_free(ctxt); dbus_message_iter_close_container(&iter, &array); g_free(contents); send: g_dbus_send_message(conn, reply); pending_request_free(request); } static GObexApparam *parse_format(GObexApparam *apparam, DBusMessageIter *iter) { const char *string; guint8 format; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return NULL; dbus_message_iter_get_basic(iter, &string); if (!string || g_str_equal(string, "")) format = FORMAT_VCARD21; else if (!g_ascii_strcasecmp(string, "vcard21")) format = FORMAT_VCARD21; else if (!g_ascii_strcasecmp(string, "vcard30")) format = FORMAT_VCARD30; else return NULL; return g_obex_apparam_set_uint8(apparam, FORMAT_TAG, format); } static GObexApparam *parse_order(GObexApparam *apparam, DBusMessageIter *iter) { const char *string; guint8 order; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return NULL; dbus_message_iter_get_basic(iter, &string); if (!string || g_str_equal(string, "")) order = ORDER_INDEXED; else if (!g_ascii_strcasecmp(string, "indexed")) order = ORDER_INDEXED; else if (!g_ascii_strcasecmp(string, "alphanumeric")) order = ORDER_ALPHANUMERIC; else if (!g_ascii_strcasecmp(string, "phonetic")) order = ORDER_PHONETIC; else return NULL; return g_obex_apparam_set_uint8(apparam, ORDER_TAG, order); } static GObexApparam *parse_offset(GObexApparam *apparam, DBusMessageIter *iter) { guint16 num; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) return NULL; dbus_message_iter_get_basic(iter, &num); return g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, num); } static GObexApparam *parse_max_count(GObexApparam *apparam, DBusMessageIter *iter) { guint16 num; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) return NULL; dbus_message_iter_get_basic(iter, &num); return g_obex_apparam_set_uint16(apparam, MAXLISTCOUNT_TAG, num); } static uint64_t get_filter_mask(const char *filterstr) { int i, bit = -1; if (!filterstr) return 0; if (!g_ascii_strcasecmp(filterstr, "ALL")) return FILTER_ALL; for (i = 0; filter_list[i] != NULL; i++) if (!g_ascii_strcasecmp(filterstr, filter_list[i])) return 1ULL << i; if (strlen(filterstr) < 4 || strlen(filterstr) > 5 || g_ascii_strncasecmp(filterstr, "bit", 3) != 0) return 0; sscanf(&filterstr[3], "%d", &bit); if (bit >= 0 && bit <= FILTER_BIT_MAX) return 1ULL << bit; else return 0; } static int set_field(guint64 *filter, const char *filterstr) { guint64 mask; mask = get_filter_mask(filterstr); if (mask == 0) return -EINVAL; *filter |= mask; return 0; } static GObexApparam *parse_fields(GObexApparam *apparam, DBusMessageIter *iter) { DBusMessageIter array; guint64 filter = 0; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return NULL; dbus_message_iter_recurse(iter, &array); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) { const char *string; dbus_message_iter_get_basic(&array, &string); if (set_field(&filter, string) < 0) return NULL; dbus_message_iter_next(&array); } return g_obex_apparam_set_uint64(apparam, FILTER_TAG, filter); } static GObexApparam *parse_filters(GObexApparam *apparam, DBusMessageIter *iter) { DBusMessageIter array; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return NULL; dbus_message_iter_recurse(iter, &array); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { const char *key; DBusMessageIter value, entry; dbus_message_iter_recurse(&array, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); if (strcasecmp(key, "Format") == 0) { if (parse_format(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "Order") == 0) { if (parse_order(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "Offset") == 0) { if (parse_offset(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "MaxCount") == 0) { if (parse_max_count(apparam, &value) == NULL) return NULL; } else if (strcasecmp(key, "Fields") == 0) { if (parse_fields(apparam, &value) == NULL) return NULL; } dbus_message_iter_next(&array); } return apparam; } static DBusMessage *pull_phonebook(struct pbap_data *pbap, DBusMessage *message, guint8 type, const char *targetfile, GObexApparam *apparam) { struct pending_request *request; struct obc_transfer *transfer; char *name; session_callback_t func; DBusMessage *reply; GError *err = NULL; name = g_strconcat(g_path_skip_root(pbap->path), ".vcf", NULL); transfer = obc_transfer_get("x-bt/phonebook", name, targetfile, &err); if (transfer == NULL) { g_obex_apparam_free(apparam); goto fail; } switch (type) { case PULLPHONEBOOK: func = NULL; request = NULL; break; case GETPHONEBOOKSIZE: func = phonebook_size_callback; request = pending_request_new(pbap, message); break; default: error("Unexpected type : 0x%2x", type); return NULL; } obc_transfer_set_apparam(transfer, apparam); if (!obc_session_queue(pbap->session, transfer, func, request, &err)) { if (request != NULL) pending_request_free(request); goto fail; } g_free(name); if (targetfile == NULL) return NULL; return obc_transfer_create_dbus_reply(transfer, message); fail: g_free(name); reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } static DBusMessage *pull_vcard_listing(struct pbap_data *pbap, DBusMessage *message, const char *name, GObexApparam *apparam) { struct pending_request *request; struct obc_transfer *transfer; GError *err = NULL; DBusMessage *reply; transfer = obc_transfer_get("x-bt/vcard-listing", name, NULL, &err); if (transfer == NULL) { g_obex_apparam_free(apparam); goto fail; } obc_transfer_set_apparam(transfer, apparam); request = pending_request_new(pbap, message); if (obc_session_queue(pbap->session, transfer, pull_vcard_listing_callback, request, &err)) return NULL; pending_request_free(request); fail: reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } static DBusMessage *pbap_select(DBusConnection *connection, DBusMessage *message, void *user_data) { struct pbap_data *pbap = user_data; const char *item, *location; char *path; struct pending_request *request; GError *err = NULL; if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &location, DBUS_TYPE_STRING, &item, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); path = build_phonebook_path(location, item); if (path == NULL) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", "Invalid path"); if (pbap->path != NULL && g_str_equal(pbap->path, path)) { g_free(path); return dbus_message_new_method_return(message); } request = pending_request_new(pbap, message); obc_session_setpath(pbap->session, path, pbap_setpath_cb, request, &err); if (err != NULL) { DBusMessage *reply; reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); g_free(path); pending_request_free(request); return reply; } g_free(pbap->path); pbap->path = path; return NULL; } static DBusMessage *pbap_pull_all(DBusConnection *connection, DBusMessage *message, void *user_data) { struct pbap_data *pbap = user_data; const char *targetfile; GObexApparam *apparam; DBusMessageIter args; if (!pbap->path) return g_dbus_create_error(message, ERROR_INTERFACE ".Forbidden", "Call Select first of all"); dbus_message_iter_init(message, &args); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); dbus_message_iter_get_basic(&args, &targetfile); dbus_message_iter_next(&args); apparam = g_obex_apparam_set_uint16(NULL, MAXLISTCOUNT_TAG, DEFAULT_COUNT); apparam = g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, DEFAULT_OFFSET); if (parse_filters(apparam, &args) == NULL) { g_obex_apparam_free(apparam); return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); } return pull_phonebook(pbap, message, PULLPHONEBOOK, targetfile, apparam); } static DBusMessage *pull_vcard(struct pbap_data *pbap, DBusMessage *message, const char *name, const char *targetfile, GObexApparam *apparam) { struct obc_transfer *transfer; DBusMessage *reply; GError *err = NULL; transfer = obc_transfer_get("x-bt/vcard", name, targetfile, &err); if (transfer == NULL) { g_obex_apparam_free(apparam); goto fail; } obc_transfer_set_apparam(transfer, apparam); if (!obc_session_queue(pbap->session, transfer, NULL, NULL, &err)) goto fail; return obc_transfer_create_dbus_reply(transfer, message); fail: reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } static DBusMessage *pbap_pull_vcard(DBusConnection *connection, DBusMessage *message, void *user_data) { struct pbap_data *pbap = user_data; GObexApparam *apparam; const char *name, *targetfile; DBusMessageIter args; if (!pbap->path) return g_dbus_create_error(message, ERROR_INTERFACE ".Forbidden", "Call Select first of all"); dbus_message_iter_init(message, &args); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); dbus_message_iter_get_basic(&args, &name); dbus_message_iter_next(&args); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); dbus_message_iter_get_basic(&args, &targetfile); dbus_message_iter_next(&args); apparam = g_obex_apparam_set_uint16(NULL, MAXLISTCOUNT_TAG, DEFAULT_COUNT); apparam = g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, DEFAULT_OFFSET); if (parse_filters(apparam, &args) == NULL) { g_obex_apparam_free(apparam); return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); } return pull_vcard(pbap, message, name, targetfile, apparam); } static DBusMessage *pbap_list(DBusConnection *connection, DBusMessage *message, void *user_data) { struct pbap_data *pbap = user_data; GObexApparam *apparam; DBusMessageIter args; if (!pbap->path) return g_dbus_create_error(message, ERROR_INTERFACE ".Forbidden", "Call Select first of all"); dbus_message_iter_init(message, &args); apparam = g_obex_apparam_set_uint16(NULL, MAXLISTCOUNT_TAG, DEFAULT_COUNT); apparam = g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, DEFAULT_OFFSET); if (parse_filters(apparam, &args) == NULL) { g_obex_apparam_free(apparam); return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); } return pull_vcard_listing(pbap, message, "", apparam); } static GObexApparam *parse_attribute(GObexApparam *apparam, const char *field) { guint8 attrib; if (!field || g_str_equal(field, "")) attrib = ATTRIB_NAME; else if (!g_ascii_strcasecmp(field, "name")) attrib = ATTRIB_NAME; else if (!g_ascii_strcasecmp(field, "number")) attrib = ATTRIB_NUMBER; else if (!g_ascii_strcasecmp(field, "sound")) attrib = ATTRIB_SOUND; else return NULL; return g_obex_apparam_set_uint8(apparam, SEARCHATTRIB_TAG, attrib); } static DBusMessage *pbap_search(DBusConnection *connection, DBusMessage *message, void *user_data) { struct pbap_data *pbap = user_data; char *field, *value; GObexApparam *apparam; DBusMessageIter args; if (!pbap->path) return g_dbus_create_error(message, ERROR_INTERFACE ".Forbidden", "Call Select first of all"); dbus_message_iter_init(message, &args); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); dbus_message_iter_get_basic(&args, &field); dbus_message_iter_next(&args); apparam = parse_attribute(NULL, field); if (apparam == NULL) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) { g_obex_apparam_free(apparam); return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); } dbus_message_iter_get_basic(&args, &value); dbus_message_iter_next(&args); apparam = g_obex_apparam_set_uint16(apparam, MAXLISTCOUNT_TAG, DEFAULT_COUNT); apparam = g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, DEFAULT_OFFSET); apparam = g_obex_apparam_set_string(apparam, SEARCHVALUE_TAG, value); if (parse_filters(apparam, &args) == NULL) { g_obex_apparam_free(apparam); return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); } return pull_vcard_listing(pbap, message, "", apparam); } static DBusMessage *pbap_get_size(DBusConnection *connection, DBusMessage *message, void *user_data) { struct pbap_data *pbap = user_data; GObexApparam *apparam; DBusMessageIter args; if (!pbap->path) return g_dbus_create_error(message, ERROR_INTERFACE ".Forbidden", "Call Select first of all"); dbus_message_iter_init(message, &args); apparam = g_obex_apparam_set_uint16(NULL, MAXLISTCOUNT_TAG, 0); apparam = g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, DEFAULT_OFFSET); return pull_phonebook(pbap, message, GETPHONEBOOKSIZE, NULL, apparam); } static char **get_filter_strs(uint64_t filter, int *size) { char **list, **item; int i; list = g_malloc0(sizeof(char **) * (FILTER_BIT_MAX + 2)); item = list; for (i = 0; filter_list[i] != NULL; i++) if (filter & (1ULL << i)) *(item++) = g_strdup(filter_list[i]); for (; i <= FILTER_BIT_MAX; i++) if (filter & (1ULL << i)) *(item++) = g_strdup_printf("%s%d", "BIT", i); *item = NULL; *size = item - list; return list; } static DBusMessage *pbap_list_filter_fields(DBusConnection *connection, DBusMessage *message, void *user_data) { char **filters = NULL; int size; DBusMessage *reply; filters = get_filter_strs(FILTER_ALL, &size); reply = dbus_message_new_method_return(message); dbus_message_append_args(reply, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &filters, size, DBUS_TYPE_INVALID); g_strfreev(filters); return reply; } static DBusMessage *pbap_update_version(DBusConnection *connection, DBusMessage *message, void *user_data) { struct pbap_data *pbap = user_data; if (!(pbap->supported_features & FOLDER_VERSION_FEATURE)) return g_dbus_create_error(message, ERROR_INTERFACE ".NotSupported", "Operation is not supported"); return pbap_get_size(connection, message, user_data); } static const GDBusMethodTable pbap_methods[] = { { GDBUS_ASYNC_METHOD("Select", GDBUS_ARGS({ "location", "s" }, { "phonebook", "s" }), NULL, pbap_select) }, { GDBUS_METHOD("PullAll", GDBUS_ARGS({ "targetfile", "s" }, { "filters", "a{sv}" }), GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), pbap_pull_all) }, { GDBUS_METHOD("Pull", GDBUS_ARGS({ "vcard", "s" }, { "targetfile", "s" }, { "filters", "a{sv}" }), GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), pbap_pull_vcard) }, { GDBUS_ASYNC_METHOD("List", GDBUS_ARGS({ "filters", "a{sv}" }), GDBUS_ARGS({ "vcard_listing", "a(ss)" }), pbap_list) }, { GDBUS_ASYNC_METHOD("Search", GDBUS_ARGS({ "field", "s" }, { "value", "s" }, { "filters", "a{sv}" }), GDBUS_ARGS({ "vcard_listing", "a(ss)" }), pbap_search) }, { GDBUS_ASYNC_METHOD("GetSize", NULL, GDBUS_ARGS({ "size", "q" }), pbap_get_size) }, { GDBUS_METHOD("ListFilterFields", NULL, GDBUS_ARGS({ "fields", "as" }), pbap_list_filter_fields) }, { GDBUS_ASYNC_METHOD("UpdateVersion", NULL, NULL, pbap_update_version) }, { } }; static gboolean folder_exists(const GDBusPropertyTable *property, void *data) { struct pbap_data *pbap = data; return pbap->path != NULL; } static gboolean get_folder(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct pbap_data *pbap = data; if (!pbap->path) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &pbap->path); return TRUE; } static gboolean databaseid_exists(const GDBusPropertyTable *property, void *data) { struct pbap_data *pbap = data; return pbap->supported_features & DATABASEID_FEATURE; } static int u128_to_string(uint8_t *data, char *str, size_t len) { return snprintf(str, len, "%02X%02X%02X%02X%02X%02X%02X%02X" "%02X%02X%02X%02X%02X%02X%02X%02X", data[0], data[1], data[2], data[3], data[3], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]); } static gboolean get_databaseid(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct pbap_data *pbap = data; char value[33]; const char *pvalue = value; if (u128_to_string(pbap->databaseid, value, sizeof(value)) < 0) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &pvalue); return TRUE; } static gboolean version_exists(const GDBusPropertyTable *property, void *data) { struct pbap_data *pbap = data; return pbap->supported_features & FOLDER_VERSION_FEATURE; } static gboolean get_primary(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct pbap_data *pbap = data; char value[33]; const char *pvalue = value; if (u128_to_string(pbap->primary, value, sizeof(value)) < 0) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &pvalue); return TRUE; } static gboolean get_secondary(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct pbap_data *pbap = data; char value[33]; const char *pvalue = value; if (u128_to_string(pbap->secondary, value, sizeof(value)) < 0) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &pvalue); return TRUE; } static gboolean image_size_exists(const GDBusPropertyTable *property, void *data) { struct pbap_data *pbap = data; return pbap->supported_features & DEFAULT_IMAGE_FEATURE; } static gboolean get_image_size(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { dbus_bool_t value = TRUE; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); return TRUE; } static const GDBusPropertyTable pbap_properties[] = { { "Folder", "s", get_folder, NULL, folder_exists }, { "DatabaseIdentifier", "s", get_databaseid, NULL, databaseid_exists }, { "PrimaryCounter", "s", get_primary, NULL, version_exists }, { "SecondaryCounter", "s", get_secondary, NULL, version_exists }, { "FixedImageSize", "b", get_image_size, NULL, image_size_exists }, { } }; static void pbap_free(void *data) { struct pbap_data *pbap = data; obc_session_unref(pbap->session); g_free(pbap->path); g_free(pbap); } static void parse_service_record(struct pbap_data *pbap) { const void *data; /* Version */ data = obc_session_get_attribute(pbap->session, SDP_ATTR_PFILE_DESC_LIST); if (!data) return; pbap->version = GPOINTER_TO_UINT(data); /* * If the PbapSupportedFeatures attribute is not present * 0x00000003 shall be assumed for a remote PSE. */ pbap->supported_features = 0x00000003; if (pbap->version < 0x0102) return; /* Supported Feature Bits */ data = obc_session_get_attribute(pbap->session, SDP_ATTR_PBAP_SUPPORTED_FEATURES); if (data) pbap->supported_features = *(uint32_t *) data; } static void *pbap_supported_features(struct obc_session *session) { const void *data; uint16_t version; uint32_t features; /* Version */ data = obc_session_get_attribute(session, SDP_ATTR_PFILE_DESC_LIST); if (!data) return NULL; version = GPOINTER_TO_UINT(data); if (version < 0x0102) return NULL; /* Supported Feature Bits */ data = obc_session_get_attribute(session, SDP_ATTR_PBAP_SUPPORTED_FEATURES); if (!data) return NULL; features = *(uint32_t *) data; if (!features) return NULL; return g_obex_apparam_set_uint32(NULL, SUPPORTED_FEATURES_TAG, DOWNLOAD_FEATURE | BROWSE_FEATURE | DATABASEID_FEATURE | FOLDER_VERSION_FEATURE | VCARD_SELECTING_FEATURE | ENHANCED_CALLS_FEATURE | UCI_FEATURE | UID_FEATURE | REFERENCING_FEATURE | DEFAULT_IMAGE_FEATURE); } static int pbap_probe(struct obc_session *session) { struct pbap_data *pbap; const char *path; path = obc_session_get_path(session); DBG("%s", path); pbap = g_try_new0(struct pbap_data, 1); if (!pbap) return -ENOMEM; pbap->session = obc_session_ref(session); parse_service_record(pbap); DBG("%s, version 0x%04x supported features 0x%08x", path, pbap->version, pbap->supported_features); if (!g_dbus_register_interface(conn, path, PBAP_INTERFACE, pbap_methods, NULL, pbap_properties, pbap, pbap_free)) { pbap_free(pbap); return -ENOMEM; } return 0; } static void pbap_remove(struct obc_session *session) { const char *path = obc_session_get_path(session); DBG("%s", path); g_dbus_unregister_interface(conn, path, PBAP_INTERFACE); } static DBusMessage *pbap_release(DBusConnection *conn, DBusMessage *msg, void *data) { return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *pbap_new_connection(DBusConnection *conn, DBusMessage *msg, void *data) { return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *pbap_request_disconnection(DBusConnection *conn, DBusMessage *msg, void *data) { return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *pbap_cancel(DBusConnection *conn, DBusMessage *msg, void *data) { return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static const GDBusMethodTable profile_methods[] = { { GDBUS_METHOD("Release", NULL, NULL, pbap_release) }, { GDBUS_METHOD("NewConnection", GDBUS_ARGS({ "device", "o" }, { "fd", "h" }, { "options", "a{sv}" }), NULL, pbap_new_connection) }, { GDBUS_METHOD("RequestDisconnection", GDBUS_ARGS({ "device", "o" }), NULL, pbap_request_disconnection) }, { GDBUS_METHOD("Cancel", NULL, NULL, pbap_cancel) }, { } }; static void unregister_profile(void) { g_dbus_unregister_interface(system_conn, client_path, "org.bluez.Profile1"); g_free(client_path); client_path = NULL; } static void register_profile_reply(DBusPendingCall *call, void *user_data) { DBusMessage *reply = dbus_pending_call_steal_reply(call); DBusError derr; dbus_error_init(&derr); if (!dbus_set_error_from_message(&derr, reply)) { DBG("Profile %s registered", client_path); goto done; } unregister_profile(); error("bluetooth: RequestProfile error: %s, %s", derr.name, derr.message); dbus_error_free(&derr); done: dbus_message_unref(reply); } static int register_profile(void) { DBusMessage *msg; DBusMessageIter iter, opt; DBusPendingCall *call; char *uuid = PBAP_CLIENT_UUID; dbus_bool_t auto_connect = FALSE; int ret = 0; client_path = g_strconcat("/org/bluez/obex/", uuid, NULL); g_strdelimit(client_path, "-", '_'); if (!g_dbus_register_interface(system_conn, client_path, "org.bluez.Profile1", profile_methods, NULL, NULL, NULL, NULL)) { error("D-Bus failed to register %s", client_path); g_free(client_path); client_path = NULL; return -1; } msg = dbus_message_new_method_call("org.bluez", "/org/bluez", "org.bluez.ProfileManager1", "RegisterProfile"); dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &client_path); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &uuid); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &opt); g_dbus_dict_append_entry(&opt, "AutoConnect", DBUS_TYPE_BOOLEAN, &auto_connect); dbus_message_iter_close_container(&iter, &opt); if (!g_dbus_send_message_with_reply(system_conn, msg, &call, -1)) { ret = -1; unregister_profile(); goto failed; } dbus_pending_call_set_notify(call, register_profile_reply, NULL, NULL); dbus_pending_call_unref(call); failed: dbus_message_unref(msg); return ret; } static void name_acquired(DBusConnection *conn, void *user_data) { DBG("org.bluez appeared"); if (register_profile() < 0) { error("bluetooth: Failed to register profile %s", client_path); g_free(client_path); client_path = NULL; } } static void name_released(DBusConnection *conn, void *user_data) { DBG("org.bluez disappeared"); unregister_profile(); } static struct obc_driver pbap = { .service = "PBAP", .uuid = PBAP_UUID, .target = OBEX_PBAP_UUID, .target_len = OBEX_PBAP_UUID_LEN, .supported_features = pbap_supported_features, .probe = pbap_probe, .remove = pbap_remove }; static int pbap_init_cb(gboolean at_register) { int err; (void)at_register; DBG(""); conn = obex_get_dbus_connection(); if (!conn) return -EIO; system_conn = g_dbus_setup_private(DBUS_BUS_SYSTEM, NULL, NULL); if (system_conn == NULL) return -EIO; err = obc_driver_register(&pbap); if (err < 0) { dbus_connection_unref(conn); conn = NULL; return err; } listener_id = g_dbus_add_service_watch(system_conn, "org.bluez", name_acquired, name_released, NULL, NULL); return 0; } static void pbap_exit_cb(gboolean at_unregister) { DBusMessage *msg; DBusMessageIter iter; char *uuid = PBAP_CLIENT_UUID; DBG(""); if (!at_unregister) { client_path = g_strconcat("/org/bluez/obex/", uuid, NULL); g_strdelimit(client_path, "-", '_'); msg = dbus_message_new_method_call("org.bluez", "/org/bluez", "org.bluez.ProfileManager1", "UnregisterProfile"); dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &client_path); g_dbus_send_message(system_conn, msg); } g_dbus_remove_watch(system_conn, listener_id); unregister_profile(); if (system_conn) { dbus_connection_close(system_conn); dbus_connection_unref(system_conn); system_conn = NULL; } if (conn) { dbus_connection_unref(conn); conn = NULL; } obc_driver_unregister(&pbap); } int pbap_init(void) { return logind_register(pbap_init_cb, pbap_exit_cb); } void pbap_exit(void) { return logind_unregister(pbap_init_cb, pbap_exit_cb); } bluez-5.87/obexd/client/PaxHeaders/sync.c0000644000000000000000000000005014766002272015360 xustar0020 atime=1783093890 20 ctime=1783094104 bluez-5.87/obexd/client/sync.c0000644000000000000000000001303414766002272015042 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Client * * Copyright (C) 2007-2010 Intel Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "gdbus/gdbus.h" #include "obexd/src/log.h" #include "obexd/src/obexd.h" #include "transfer.h" #include "session.h" #include "driver.h" #include "sync.h" #define OBEX_SYNC_UUID "IRMC-SYNC" #define OBEX_SYNC_UUID_LEN 9 #define SYNC_INTERFACE "org.bluez.obex.Synchronization1" #define ERROR_INF SYNC_INTERFACE ".Error" #define SYNC_UUID "00001104-0000-1000-8000-00805f9b34fb" struct sync_data { struct obc_session *session; char *phonebook_path; DBusMessage *msg; }; static DBusConnection *conn = NULL; static DBusMessage *sync_setlocation(DBusConnection *connection, DBusMessage *message, void *user_data) { struct sync_data *sync = user_data; const char *location; char *path = NULL, *tmp; if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &location, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INF ".InvalidArguments", NULL); if (!g_ascii_strcasecmp(location, "int") || !g_ascii_strcasecmp(location, "internal")) path = g_strdup("telecom/pb.vcf"); else if (!g_ascii_strncasecmp(location, "sim", 3)) { tmp = g_ascii_strup(location, 4); path = g_build_filename(tmp, "telecom/pb.vcf", NULL); g_free(tmp); } else return g_dbus_create_error(message, ERROR_INF ".InvalidArguments", "InvalidPhonebook"); g_free(sync->phonebook_path); sync->phonebook_path = path; return dbus_message_new_method_return(message); } static DBusMessage *sync_getphonebook(DBusConnection *connection, DBusMessage *message, void *user_data) { struct sync_data *sync = user_data; struct obc_transfer *transfer; const char *target_file; GError *err = NULL; DBusMessage *reply; if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &target_file, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INF ".InvalidArguments", "Invalid arguments in method call"); if (sync->msg) return g_dbus_create_error(message, ERROR_INF ".InProgress", "Transfer in progress"); /* set default phonebook_path to memory internal phonebook */ if (!sync->phonebook_path) sync->phonebook_path = g_strdup("telecom/pb.vcf"); transfer = obc_transfer_get("phonebook", sync->phonebook_path, target_file, &err); if (transfer == NULL) goto fail; if (!obc_session_queue(sync->session, transfer, NULL, NULL, &err)) goto fail; return obc_transfer_create_dbus_reply(transfer, message); fail: reply = g_dbus_create_error(message, ERROR_INF ".Failed", "%s", err->message); g_error_free(err); return reply; } static DBusMessage *sync_putphonebook(DBusConnection *connection, DBusMessage *message, void *user_data) { struct sync_data *sync = user_data; struct obc_transfer *transfer; const char *source_file; GError *err = NULL; DBusMessage *reply; if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &source_file, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INF ".InvalidArguments", "Invalid arguments in method call"); /* set default phonebook_path to memory internal phonebook */ if (!sync->phonebook_path) sync->phonebook_path = g_strdup("telecom/pb.vcf"); transfer = obc_transfer_put(NULL, sync->phonebook_path, source_file, NULL, 0, &err); if (transfer == NULL) goto fail; if (!obc_session_queue(sync->session, transfer, NULL, NULL, &err)) goto fail; return obc_transfer_create_dbus_reply(transfer, message); fail: reply = g_dbus_create_error(message, ERROR_INF ".Failed", "%s", err->message); g_error_free(err); return reply; } static const GDBusMethodTable sync_methods[] = { { GDBUS_METHOD("SetLocation", GDBUS_ARGS({ "location", "s" }), NULL, sync_setlocation) }, { GDBUS_METHOD("GetPhonebook", GDBUS_ARGS({ "targetfile", "s" }), GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), sync_getphonebook) }, { GDBUS_METHOD("PutPhonebook", GDBUS_ARGS({ "sourcefile", "s" }), GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), sync_putphonebook) }, { } }; static void sync_free(void *data) { struct sync_data *sync = data; obc_session_unref(sync->session); g_free(sync->phonebook_path); g_free(sync); } static int sync_probe(struct obc_session *session) { struct sync_data *sync; const char *path; path = obc_session_get_path(session); DBG("%s", path); sync = g_try_new0(struct sync_data, 1); if (!sync) return -ENOMEM; sync->session = obc_session_ref(session); if (!g_dbus_register_interface(conn, path, SYNC_INTERFACE, sync_methods, NULL, NULL, sync, sync_free)) { sync_free(sync); return -ENOMEM; } return 0; } static void sync_remove(struct obc_session *session) { const char *path = obc_session_get_path(session); DBG("%s", path); g_dbus_unregister_interface(conn, path, SYNC_INTERFACE); } static struct obc_driver sync = { .service = "SYNC", .uuid = SYNC_UUID, .target = OBEX_SYNC_UUID, .target_len = OBEX_SYNC_UUID_LEN, .probe = sync_probe, .remove = sync_remove }; int sync_init(void) { int err; DBG(""); conn = obex_get_dbus_connection(); if (!conn) return -EIO; err = obc_driver_register(&sync); if (err < 0) { dbus_connection_unref(conn); conn = NULL; return err; } return 0; } void sync_exit(void) { DBG(""); dbus_connection_unref(conn); conn = NULL; obc_driver_unregister(&sync); } bluez-5.87/obexd/client/PaxHeaders/bip.c0000644000000000000000000000005014766002272015156 xustar0020 atime=1783093901 20 ctime=1783094105 bluez-5.87/obexd/client/bip.c0000644000000000000000000002463314766002272014647 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Client * * Copyright (C) 2024 Collabora Ltd. * * */ #define _GNU_SOURCE #include #include #include #include "gdbus/gdbus.h" #include "gobex/gobex.h" #include "obexd/src/log.h" #include "obexd/src/obexd.h" #include "transfer.h" #include "session.h" #include "driver.h" #include "bip-common.h" #include "bip.h" #define OBEX_BIP_AVRCP_UUID \ "\x71\x63\xDD\x54\x4A\x7E\x11\xE2\xB4\x7C\x00\x50\xC2\x49\x00\x48" #define OBEX_BIP_AVRCP_UUID_LEN 16 #define IMAGE_INTERFACE "org.bluez.obex.Image1" #define ERROR_INTERFACE "org.bluez.obex.Error" #define IMAGE_UUID "0000111A-0000-1000-8000-00805f9b34fb" #define IMG_HANDLE_TAG 0x30 #define IMG_DESC_TAG 0x71 #define EOL_CHARS "\n" #define IMG_DESC_BEGIN "" EOL_CHARS #define IMG_BEGIN "" EOL_CHARS #define IMG_DESC_END "" EOL_CHARS static DBusConnection *conn; struct bip_avrcp_data { struct obc_session *session; }; static void image_properties_complete_cb(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { DBusMessage *message = user_data; DBusMessage *reply = NULL; DBusMessageIter iter; char *contents = NULL; size_t size; int perr; struct prop_object *prop = NULL; if (err != NULL) { reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); goto done; } perr = obc_transfer_get_contents(transfer, &contents, &size); if (perr < 0) { reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "Error reading contents: %s", strerror(-perr)); goto done; } prop = parse_properties(contents, size, &perr); if (prop == NULL) { reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "Error parsing contents: %s", strerror(-perr)); goto done; } if (!verify_properties(prop)) { reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "Error verifying contents"); goto done; } reply = dbus_message_new_method_return(message); dbus_message_iter_init_append(reply, &iter); append_properties(&iter, prop); done: g_dbus_send_message(conn, reply); g_free(contents); dbus_message_unref(message); } static DBusMessage *get_image_properties(DBusConnection *connection, DBusMessage *message, void *user_data) { struct bip_avrcp_data *bip_avrcp = user_data; const char *handle = NULL; struct obc_transfer *transfer; GObexHeader *header; DBusMessage *reply = NULL; GError *err = NULL; DBG(""); if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &handle, DBUS_TYPE_INVALID) == FALSE) { reply = g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); return reply; } transfer = obc_transfer_get("x-bt/img-properties", NULL, NULL, &err); if (transfer == NULL) goto fail; header = g_obex_header_new_unicode(IMG_HANDLE_TAG, handle); obc_transfer_add_header(transfer, header); if (!obc_session_queue(bip_avrcp->session, transfer, image_properties_complete_cb, message, &err)) goto fail; dbus_message_ref(message); return NULL; fail: reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } static DBusMessage *get_thumbnail(DBusConnection *connection, DBusMessage *message, void *user_data) { struct bip_avrcp_data *bip_avrcp = user_data; const char *handle = NULL, *image_path = NULL; struct obc_transfer *transfer; GObexHeader *header; DBusMessage *reply = NULL; GError *err = NULL; DBG(""); if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &image_path, DBUS_TYPE_STRING, &handle, DBUS_TYPE_INVALID) == FALSE) { reply = g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); return reply; } transfer = obc_transfer_get("x-bt/img-thm", NULL, image_path, &err); if (transfer == NULL) goto fail; header = g_obex_header_new_unicode(IMG_HANDLE_TAG, handle); obc_transfer_add_header(transfer, header); if (!obc_session_queue(bip_avrcp->session, transfer, NULL, NULL, &err)) goto fail; return obc_transfer_create_dbus_reply(transfer, message); fail: reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); return reply; } static gboolean parse_get_image_dict(DBusMessage *msg, char **path, char **handle, char **pixel, char **encoding, uint64_t *maxsize, char **transform) { DBusMessageIter iter, array; DBG(""); *path = NULL; *handle = NULL; *pixel = NULL; *encoding = NULL; *transform = NULL; dbus_message_iter_init(msg, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) goto failed; dbus_message_iter_get_basic(&iter, path); *path = g_strdup(*path); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) goto failed; dbus_message_iter_next(&iter); dbus_message_iter_get_basic(&iter, handle); *handle = g_strdup(*handle); dbus_message_iter_next(&iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) goto failed; dbus_message_iter_recurse(&iter, &array); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter entry, value; const char *key, *val; dbus_message_iter_recurse(&array, &entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); switch (dbus_message_iter_get_arg_type(&value)) { case DBUS_TYPE_STRING: dbus_message_iter_get_basic(&value, &val); if (g_str_equal(key, "pixel")) { if (!parse_pixel_range(val, NULL, NULL, NULL)) goto failed; *pixel = g_strdup(val); } else if (g_str_equal(key, "encoding")) { if (!verify_encoding(val)) goto failed; *encoding = g_strdup(val); if (*encoding == NULL) goto failed; } else if (g_str_equal(key, "transformation")) { *transform = parse_transform(val); if (*transform == NULL) goto failed; } break; case DBUS_TYPE_UINT64: if (g_str_equal(key, "maxsize") == TRUE) { dbus_message_iter_get_basic(&value, maxsize); if (*maxsize == 0) goto failed; } break; } dbus_message_iter_next(&array); } if (*pixel == NULL) *pixel = strdup(""); if (*encoding == NULL) *encoding = strdup(""); DBG("pixel: '%s' encoding: '%s' maxsize: '%lu' transform: '%s'", *pixel, *encoding, *maxsize, *transform ); return TRUE; failed: g_free(*path); g_free(*handle); g_free(*pixel); g_free(*encoding); g_free(*transform); return FALSE; } static DBusMessage *get_image(DBusConnection *connection, DBusMessage *message, void *user_data) { struct bip_avrcp_data *bip_avrcp = user_data; char *handle = NULL, *image_path = NULL, *transform = NULL, *encoding = NULL, *pixel = NULL; uint64_t maxsize; struct obc_transfer *transfer; GObexHeader *header; DBusMessage *reply = NULL; GString *descriptor = NULL; GError *err = NULL; DBG(""); if (!parse_get_image_dict(message, &image_path, &handle, &pixel, &encoding, &maxsize, &transform)) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); transfer = obc_transfer_get("x-bt/img-img", NULL, image_path, &err); if (transfer == NULL) { reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); goto fail; } header = g_obex_header_new_unicode(IMG_HANDLE_TAG, handle); obc_transfer_add_header(transfer, header); descriptor = g_string_new(IMG_DESC_BEGIN); g_string_append_printf(descriptor, IMG_BEGIN, encoding, pixel); if (transform != NULL) g_string_append_printf(descriptor, IMG_TRANSFORM, transform); g_string_append(descriptor, IMG_END); descriptor = g_string_append(descriptor, IMG_DESC_END); header = g_obex_header_new_bytes(IMG_DESC_TAG, descriptor->str, descriptor->len); obc_transfer_add_header(transfer, header); g_string_free(descriptor, TRUE); if (!obc_session_queue(bip_avrcp->session, transfer, NULL, NULL, &err)) { reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_error_free(err); goto fail; } reply = obc_transfer_create_dbus_reply(transfer, message); fail: g_free(handle); g_free(image_path); g_free(transform); g_free(encoding); g_free(pixel); return reply; } static const GDBusMethodTable bip_avrcp_methods[] = { { GDBUS_ASYNC_METHOD("Properties", GDBUS_ARGS({ "handle", "s"}), GDBUS_ARGS({ "properties", "aa{sv}" }), get_image_properties) }, { GDBUS_ASYNC_METHOD("Get", GDBUS_ARGS({ "file", "s" }, { "handle", "s"}, {"properties", "a{sv}"}), GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), get_image) }, { GDBUS_ASYNC_METHOD("GetThumbnail", GDBUS_ARGS({ "file", "s" }, { "handle", "s"}), GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), get_thumbnail) }, { } }; static void bip_avrcp_free(void *data) { struct bip_avrcp_data *bip_avrcp = data; obc_session_unref(bip_avrcp->session); g_free(bip_avrcp); } static int bip_avrcp_probe(struct obc_session *session) { struct bip_avrcp_data *bip_avrcp; const char *path; path = obc_session_get_path(session); DBG("%s", path); bip_avrcp = g_try_new0(struct bip_avrcp_data, 1); if (!bip_avrcp) return -ENOMEM; bip_avrcp->session = obc_session_ref(session); if (!g_dbus_register_interface(conn, path, IMAGE_INTERFACE, bip_avrcp_methods, NULL, NULL, bip_avrcp, bip_avrcp_free)) { bip_avrcp_free(bip_avrcp); return -ENOMEM; } return 0; } static void bip_avrcp_remove(struct obc_session *session) { const char *path = obc_session_get_path(session); DBG("%s", path); g_dbus_unregister_interface(conn, path, IMAGE_INTERFACE); } static struct obc_driver bip_avrcp = { .service = "BIP-AVRCP", .uuid = IMAGE_UUID, .target = OBEX_BIP_AVRCP_UUID, .target_len = OBEX_BIP_AVRCP_UUID_LEN, .probe = bip_avrcp_probe, .remove = bip_avrcp_remove }; int bip_init(void) { int err; DBG(""); conn = obex_get_dbus_connection(); if (!conn) return -EIO; err = obc_driver_register(&bip_avrcp); if (err < 0) goto failed; return 0; failed: dbus_connection_unref(conn); conn = NULL; return err; } void bip_exit(void) { DBG(""); dbus_connection_unref(conn); conn = NULL; obc_driver_unregister(&bip_avrcp); } bluez-5.87/obexd/client/PaxHeaders/bip-common.h0000644000000000000000000000005014711225434016446 xustar0020 atime=1783093901 20 ctime=1783094105 bluez-5.87/obexd/client/bip-common.h0000644000000000000000000000116614711225434016133 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Client * * Copyright (C) 2024 Collabora Ltd. * * */ #include #include "gdbus/gdbus.h" struct prop_object; gboolean parse_pixel_range(const gchar *dim, unsigned int *lower_ret, unsigned int *upper_ret, gboolean *fixed_ratio_ret); gboolean verify_encoding(const char *encoding); char *parse_transform(const char *transform); struct prop_object *parse_properties(char *data, unsigned int length, int *err); gboolean verify_properties(struct prop_object *obj); void append_properties(DBusMessageIter *args, struct prop_object *obj); bluez-5.87/obexd/client/PaxHeaders/manager.h0000644000000000000000000000005014015011623016006 xustar0020 atime=1783093873 20 ctime=1783094104 bluez-5.87/obexd/client/manager.h0000644000000000000000000000032614015011623015470 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ int client_manager_init(void); void client_manager_exit(void); bluez-5.87/obexd/client/PaxHeaders/session.c0000644000000000000000000000005015063045254016065 xustar0020 atime=1783093886 20 ctime=1783094104 bluez-5.87/obexd/client/session.c0000644000000000000000000007475615063045254015571 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Client * * Copyright (C) 2007-2010 Marcel Holtmann * Copyright (C) 2011-2012 BMW Car IT GmbH. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "gobex/gobex.h" #include "obexd/src/log.h" #include "obexd/src/obexd.h" #include "transfer.h" #include "session.h" #include "driver.h" #include "transport.h" #define SESSION_INTERFACE "org.bluez.obex.Session1" #define ERROR_INTERFACE "org.bluez.obex.Error" #define SESSION_BASEPATH "/org/bluez/obex/client" #define OBEX_IO_ERROR obex_io_error_quark() #define OBEX_IO_ERROR_FIRST (0xff + 1) enum { OBEX_IO_DISCONNECTED = OBEX_IO_ERROR_FIRST, OBEX_IO_BUSY, }; static guint64 counter = 0; struct callback_data { struct obc_session *session; guint id; session_callback_t func; void *data; }; struct pending_request; typedef int (*session_process_t) (struct pending_request *p, GError **err); typedef void (*destroy_t) (void *data); struct pending_request { guint id; guint req_id; struct obc_session *session; session_process_t process; struct obc_transfer *transfer; session_callback_t func; void *data; destroy_t destroy; }; struct setpath_data { char **remaining; int index; session_callback_t func; void *user_data; }; struct file_data { char *srcname; char *destname; session_callback_t func; void *user_data; }; struct obc_session { guint id; int refcount; char *source; char *destination; uint8_t channel; uint16_t psm; struct obc_transport *transport; struct obc_driver *driver; char *path; /* Session path */ DBusConnection *conn; GObex *obex; struct pending_request *p; char *owner; /* Session owner */ guint watch; GQueue *queue; guint process_id; char *folder; struct callback_data *callback; }; static GSList *sessions = NULL; static void session_process_queue(struct obc_session *session); static void session_terminate_transfer(struct obc_session *session, struct obc_transfer *transfer, GError *gerr); static void transfer_complete(struct obc_transfer *transfer, GError *err, void *user_data); static GQuark obex_io_error_quark(void) { return g_quark_from_static_string("obex-io-error-quark"); } struct obc_session *obc_session_ref(struct obc_session *session) { int refs = __sync_add_and_fetch(&session->refcount, 1); DBG("%p: ref=%d", session, refs); return session; } static void session_unregistered(struct obc_session *session) { char *path; if (session->driver && session->driver->remove) session->driver->remove(session); path = session->path; session->path = NULL; g_dbus_unregister_interface(session->conn, path, SESSION_INTERFACE); DBG("Session(%p) unregistered %s", session, path); g_free(path); } static struct pending_request *pending_request_new(struct obc_session *session, session_process_t process, struct obc_transfer *transfer, session_callback_t func, void *data, destroy_t destroy) { struct pending_request *p; static guint id = 0; p = g_new0(struct pending_request, 1); p->id = ++id; p->session = obc_session_ref(session); p->process = process; p->destroy = destroy; p->transfer = transfer; p->func = func; p->data = data; return p; } static void pending_request_free(struct pending_request *p) { if (p->req_id > 0) g_obex_cancel_req(p->session->obex, p->req_id, TRUE); if (p->destroy) p->destroy(p->data); if (p->transfer) obc_transfer_unregister(p->transfer); if (p->session) obc_session_unref(p->session); g_free(p); } static void setpath_data_free(void *process_data) { struct setpath_data *data = process_data; g_strfreev(data->remaining); g_free(data); } static void file_data_free(void *process_data) { struct file_data *data = process_data; g_free(data->srcname); g_free(data->destname); g_free(data); } static void request_free(void *data, void *user_data) { pending_request_free(data); } static void session_free(struct obc_session *session) { DBG("%p", session); if (session->process_id != 0) g_source_remove(session->process_id); if (session->queue) { g_queue_foreach(session->queue, request_free, NULL); g_queue_free(session->queue); } if (session->watch) g_dbus_remove_watch(session->conn, session->watch); if (session->obex) { g_obex_set_disconnect_function(session->obex, NULL, NULL); g_obex_unref(session->obex); } if (session->id > 0 && session->transport != NULL) session->transport->disconnect(session->id); if (session->path) session_unregistered(session); if (session->conn) dbus_connection_unref(session->conn); if (session->p) pending_request_free(session->p); g_free(session->path); g_free(session->owner); g_free(session->source); g_free(session->destination); g_free(session->folder); g_free(session); } static void disconnect_complete(GObex *obex, GError *err, GObexPacket *rsp, void *user_data) { struct obc_session *session = user_data; DBG(""); if (err) error("%s", err->message); /* Disconnect transport */ if (session->id > 0 && session->transport != NULL) { session->transport->disconnect(session->id); session->id = 0; } session_free(session); } void obc_session_unref(struct obc_session *session) { int refs; refs = __sync_sub_and_fetch(&session->refcount, 1); DBG("%p: ref=%d", session, refs); if (refs > 0) return; sessions = g_slist_remove(sessions, session); if (!session->obex) goto disconnect; /* Wait OBEX Disconnect to complete if command succeed otherwise * proceed with transport disconnection since there is nothing else to * be done */ if (g_obex_disconnect(session->obex, disconnect_complete, session, NULL)) return; disconnect: /* Disconnect transport */ if (session->id > 0 && session->transport != NULL) { session->transport->disconnect(session->id); session->id = 0; } session_free(session); } static void callback_destroy(struct callback_data *callback, GError *err) { struct obc_session *session = callback->session; if (callback->id > 0) g_obex_cancel_req(session->obex, callback->id, TRUE); callback->func(session, NULL, err, callback->data); g_free(callback); session->callback = NULL; obc_session_unref(session); } static void connect_cb(GObex *obex, GError *err, GObexPacket *rsp, gpointer user_data) { struct callback_data *callback = user_data; GError *gerr = NULL; uint8_t rsp_code; callback->id = 0; if (err != NULL) { error("connect_cb: %s", err->message); gerr = g_error_copy(err); goto done; } rsp_code = g_obex_packet_get_operation(rsp, NULL); if (rsp_code != G_OBEX_RSP_SUCCESS) gerr = g_error_new(OBEX_IO_ERROR, -EIO, "OBEX Connect failed with 0x%02x", rsp_code); done: callback_destroy(callback, gerr); if (gerr != NULL) g_error_free(gerr); } static void session_disconnected(GObex *obex, GError *err, gpointer user_data) { struct obc_session *session = user_data; if (err) error("%s", err->message); obc_session_shutdown(session); } static void transport_func(GIOChannel *io, GError *err, gpointer user_data) { struct callback_data *callback = user_data; struct obc_session *session = callback->session; struct obc_driver *driver = session->driver; struct obc_transport *transport = session->transport; GObex *obex; GObexApparam *apparam; GObexTransportType type; int tx_mtu = -1; int rx_mtu = -1; DBG(""); if (err != NULL) { error("%s", err->message); goto done; } g_io_channel_set_close_on_unref(io, FALSE); if (transport->getpacketopt && transport->getpacketopt(io, &tx_mtu, &rx_mtu) == 0) type = G_OBEX_TRANSPORT_PACKET; else type = G_OBEX_TRANSPORT_STREAM; obex = g_obex_new(io, type, rx_mtu, tx_mtu); if (obex == NULL) goto done; g_io_channel_set_close_on_unref(io, TRUE); apparam = NULL; if (driver->supported_features) apparam = driver->supported_features(session); if (apparam) { uint8_t buf[1024]; ssize_t len; len = g_obex_apparam_encode(apparam, buf, sizeof(buf)); if (driver->target) callback->id = g_obex_connect(obex, connect_cb, callback, &err, G_OBEX_HDR_TARGET, driver->target, driver->target_len, G_OBEX_HDR_APPARAM, buf, len, G_OBEX_HDR_INVALID); else callback->id = g_obex_connect(obex, connect_cb, callback, &err, G_OBEX_HDR_APPARAM, buf, len, G_OBEX_HDR_INVALID); g_obex_apparam_free(apparam); } else if (driver->target) callback->id = g_obex_connect(obex, connect_cb, callback, &err, G_OBEX_HDR_TARGET, driver->target, driver->target_len, G_OBEX_HDR_INVALID); else callback->id = g_obex_connect(obex, connect_cb, callback, &err, G_OBEX_HDR_INVALID); if (err != NULL) { error("%s", err->message); g_obex_unref(obex); goto done; } session->obex = obex; sessions = g_slist_prepend(sessions, session); g_obex_set_disconnect_function(obex, session_disconnected, session); return; done: callback_destroy(callback, err); } static void owner_disconnected(DBusConnection *connection, void *user_data) { struct obc_session *session = user_data; GError *err; DBG(""); /* * If connection still connecting notify the callback to destroy the * session. */ if (session->callback) { err = g_error_new(OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, "Session closed by user"); callback_destroy(session->callback, err); g_error_free(err); return; } obc_session_shutdown(session); } int obc_session_set_owner(struct obc_session *session, const char *name, GDBusWatchFunction func) { if (session == NULL) return -EINVAL; if (session->watch) g_dbus_remove_watch(session->conn, session->watch); session->watch = g_dbus_add_disconnect_watch(session->conn, name, func, session, NULL); if (session->watch == 0) return -EINVAL; session->owner = g_strdup(name); return 0; } static struct obc_session *session_find(const char *source, const char *destination, const char *service, uint8_t channel, uint16_t psm, const char *owner) { GSList *l; for (l = sessions; l; l = l->next) { struct obc_session *session = l->data; if (g_strcmp0(session->destination, destination)) continue; if (g_strcmp0(service, session->driver->service)) continue; if (source && g_strcmp0(session->source, source)) continue; if (channel && session->channel != channel) continue; if (psm && session->psm != psm) continue; if (g_strcmp0(owner, session->owner)) continue; return session; } return NULL; } static gboolean connection_complete(gpointer data) { struct callback_data *cb = data; cb->func(cb->session, NULL, NULL, cb->data); obc_session_unref(cb->session); g_free(cb); return FALSE; } static int session_connect(struct obc_session *session, session_callback_t function, void *user_data) { struct callback_data *callback; struct obc_transport *transport = session->transport; struct obc_driver *driver = session->driver; callback = g_try_malloc0(sizeof(*callback)); if (callback == NULL) return -ENOMEM; callback->func = function; callback->data = user_data; callback->session = obc_session_ref(session); /* Connection completed */ if (session->obex) { g_idle_add(connection_complete, callback); return 0; } /* Ongoing connection */ if (session->id > 0) { obc_session_unref(callback->session); g_free(callback); return 0; } session->id = transport->connect(session->source, session->destination, driver->uuid, session->channel ? session->channel : session->psm, transport_func, callback); if (session->id == 0) { obc_session_unref(callback->session); g_free(callback); return -EINVAL; } session->callback = callback; return 0; } struct obc_session *obc_session_create(const char *source, const char *destination, const char *service, uint8_t channel, uint16_t psm, const char *owner, session_callback_t function, void *user_data) { DBusConnection *conn; struct obc_session *session; struct obc_transport *transport; struct obc_driver *driver; if (destination == NULL) return NULL; session = session_find(source, destination, service, channel, psm, owner); if (session != NULL) goto proceed; /* FIXME: Do proper transport lookup when the API supports it */ transport = obc_transport_find("Bluetooth"); if (transport == NULL) return NULL; driver = obc_driver_find(service); if (driver == NULL) return NULL; conn = obex_get_dbus_connection(); if (conn == NULL) return NULL; session = g_try_malloc0(sizeof(*session)); if (session == NULL) return NULL; session->refcount = 1; session->transport = transport; session->driver = driver; session->conn = conn; session->source = g_strdup(source); session->destination = g_strdup(destination); session->channel = channel; session->psm = psm; session->queue = g_queue_new(); session->folder = g_strdup("/"); if (owner) obc_session_set_owner(session, owner, owner_disconnected); proceed: if (session_connect(session, function, user_data) < 0) { obc_session_unref(session); return NULL; } DBG("session %p transport %s driver %s", session, session->transport->name, session->driver->service); return session; } void obc_session_shutdown(struct obc_session *session) { struct pending_request *p; GError *err; DBG("%p", session); obc_session_ref(session); /* Unregister any pending transfer */ err = g_error_new(OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, "Session closed by user"); if (session->p != NULL && session->p->id != 0) { p = session->p; session->p = NULL; if (p->func) p->func(session, p->transfer, err, p->data); pending_request_free(p); } while ((p = g_queue_pop_head(session->queue))) { if (p->func) p->func(session, p->transfer, err, p->data); pending_request_free(p); } g_error_free(err); /* Unregister interfaces */ if (session->path) session_unregistered(session); obc_session_unref(session); } static void capabilities_complete_callback(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { DBusMessage *message = user_data; char *contents; size_t size; int perr; if (err != NULL) { DBusMessage *error = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", err->message); g_dbus_send_message(session->conn, error); goto done; } perr = obc_transfer_get_contents(transfer, &contents, &size); if (perr < 0) { DBusMessage *error = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "Error reading contents: %s", strerror(-perr)); g_dbus_send_message(session->conn, error); goto done; } g_dbus_send_reply(session->conn, message, DBUS_TYPE_STRING, &contents, DBUS_TYPE_INVALID); g_free(contents); done: dbus_message_unref(message); } static DBusMessage *get_capabilities(DBusConnection *connection, DBusMessage *message, void *user_data) { struct obc_session *session = user_data; struct obc_transfer *pull; DBusMessage *reply; GError *gerr = NULL; pull = obc_transfer_get("x-obex/capability", NULL, NULL, &gerr); if (pull == NULL) goto fail; if (!obc_session_queue(session, pull, capabilities_complete_callback, message, &gerr)) goto fail; dbus_message_ref(message); return NULL; fail: reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", gerr->message); g_error_free(gerr); return reply; } static gboolean get_source(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obc_session *session = data; if (session->source == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &session->source); return TRUE; } static gboolean source_exists(const GDBusPropertyTable *property, void *data) { struct obc_session *session = data; return session->source != NULL; } static gboolean get_destination(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obc_session *session = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &session->destination); return TRUE; } static gboolean get_channel(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obc_session *session = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &session->channel); return TRUE; } static gboolean get_psm(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obc_session *session = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &session->psm); return TRUE; } static const GDBusMethodTable session_methods[] = { { GDBUS_ASYNC_METHOD("GetCapabilities", NULL, GDBUS_ARGS({ "capabilities", "s" }), get_capabilities) }, { } }; static gboolean get_target(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct obc_session *session = data; if (session->driver->uuid == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &session->driver->uuid); return TRUE; } static gboolean target_exists(const GDBusPropertyTable *property, void *data) { struct obc_session *session = data; return session->driver->uuid != NULL; } static const GDBusPropertyTable session_properties[] = { { "Source", "s", get_source, NULL, source_exists }, { "Destination", "s", get_destination }, { "Channel", "y", get_channel }, { "PSM", "q", get_psm }, { "Target", "s", get_target, NULL, target_exists }, { } }; static gboolean session_process(gpointer data) { struct obc_session *session = data; session->process_id = 0; session_process_queue(session); return FALSE; } static void session_queue(struct pending_request *p) { g_queue_push_tail(p->session->queue, p); if (p->session->process_id == 0) p->session->process_id = g_idle_add(session_process, p->session); } static int session_process_transfer(struct pending_request *p, GError **err) { if (!obc_transfer_start(p->transfer, p->session->obex, err)) return -1; DBG("Transfer(%p) started", p->transfer); p->session->p = p; return 0; } guint obc_session_queue(struct obc_session *session, struct obc_transfer *transfer, session_callback_t func, void *user_data, GError **err) { struct pending_request *p; if (session->obex == NULL) { obc_transfer_unregister(transfer); g_set_error(err, OBEX_IO_ERROR, -ENOTCONN, "Session not connected"); return 0; } if (!obc_transfer_register(transfer, session->conn, session->path, session->owner, err)) { obc_transfer_unregister(transfer); return 0; } obc_transfer_set_callback(transfer, transfer_complete, session); p = pending_request_new(session, session_process_transfer, transfer, func, user_data, NULL); session_queue(p); return p->id; } static void session_process_queue(struct obc_session *session) { struct pending_request *p; if (session->p != NULL) return; if (session->queue == NULL || g_queue_is_empty(session->queue)) return; obc_session_ref(session); while ((p = g_queue_pop_head(session->queue))) { GError *gerr = NULL; if (p->process(p, &gerr) == 0) break; if (p->func) p->func(session, p->transfer, gerr, p->data); g_clear_error(&gerr); pending_request_free(p); } obc_session_unref(session); } static int pending_transfer_cmptransfer(gconstpointer a, gconstpointer b) { const struct pending_request *p = a; const struct obc_transfer *transfer = b; if (p->transfer == transfer) return 0; return -1; } static void session_terminate_transfer(struct obc_session *session, struct obc_transfer *transfer, GError *gerr) { struct pending_request *p = session->p; if (p == NULL || p->transfer != transfer) { GList *match; match = g_list_find_custom(session->queue->head, transfer, pending_transfer_cmptransfer); if (match == NULL) return; p = match->data; g_queue_delete_link(session->queue, match); } else session->p = NULL; obc_session_ref(session); if (p->func) p->func(session, p->transfer, gerr, p->data); pending_request_free(p); if (session->p == NULL) session_process_queue(session); obc_session_unref(session); } static void session_notify_complete(struct obc_session *session, struct obc_transfer *transfer) { DBG("Transfer(%p) complete", transfer); session_terminate_transfer(session, transfer, NULL); } static void session_notify_error(struct obc_session *session, struct obc_transfer *transfer, GError *err) { error("Transfer(%p) Error: %s", transfer, err->message); session_terminate_transfer(session, transfer, err); } static void transfer_complete(struct obc_transfer *transfer, GError *err, void *user_data) { struct obc_session *session = user_data; if (err) goto fail; session_notify_complete(session, transfer); return; fail: session_notify_error(session, transfer, err); } const char *obc_session_register(struct obc_session *session, GDBusDestroyFunction destroy) { if (session->path) return session->path; session->path = g_strdup_printf("%s/session%ju", SESSION_BASEPATH, counter++); if (g_dbus_register_interface(session->conn, session->path, SESSION_INTERFACE, session_methods, NULL, session_properties, session, destroy) == FALSE) goto fail; if (session->driver->probe && session->driver->probe(session) < 0) { g_dbus_unregister_interface(session->conn, session->path, SESSION_INTERFACE); goto fail; } DBG("Session(%p) registered %s", session, session->path); return session->path; fail: g_free(session->path); session->path = NULL; return NULL; } const void *obc_session_get_attribute(struct obc_session *session, int attribute_id) { if (session == NULL || session->id == 0) return NULL; return session->transport->getattribute(session->id, attribute_id); } const char *obc_session_get_owner(struct obc_session *session) { if (session == NULL) return NULL; return session->owner; } const char *obc_session_get_destination(struct obc_session *session) { return session->destination; } const char *obc_session_get_path(struct obc_session *session) { return session->path; } const char *obc_session_get_target(struct obc_session *session) { return session->driver->target; } const char *obc_session_get_folder(struct obc_session *session) { return session->folder; } static void setpath_complete(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { struct pending_request *p = user_data; if (p->func) p->func(session, NULL, err, p->data); if (session->p == p) session->p = NULL; pending_request_free(p); session_process_queue(session); } static void setpath_op_complete(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { struct setpath_data *data = user_data; if (data->func) data->func(session, NULL, err, data->user_data); } static void setpath_set_folder(struct obc_session *session, const char *cur) { char *folder = NULL; const char *delim; delim = strrchr(session->folder, '/'); if (strlen(cur) == 0 || delim == NULL || (strcmp(cur, "..") == 0 && delim == session->folder)) { folder = g_strdup("/"); } else { if (strcmp(cur, "..") == 0) { folder = g_strndup(session->folder, delim - session->folder); } else { if (g_str_has_suffix(session->folder, "/")) folder = g_strconcat(session->folder, cur, NULL); else folder = g_strconcat(session->folder, "/", cur, NULL); } } g_free(session->folder); session->folder = folder; } static void setpath_cb(GObex *obex, GError *err, GObexPacket *rsp, gpointer user_data) { struct pending_request *p = user_data; struct setpath_data *data = p->data; char *next; char *current; guint8 code; p->req_id = 0; if (err != NULL) { setpath_complete(p->session, NULL, err, user_data); return; } code = g_obex_packet_get_operation(rsp, NULL); if (code != G_OBEX_RSP_SUCCESS) { GError *gerr = NULL; g_set_error(&gerr, OBEX_IO_ERROR, code, "%s", g_obex_strerror(code)); setpath_complete(p->session, NULL, gerr, user_data); g_clear_error(&gerr); return; } current = data->remaining[data->index - 1]; setpath_set_folder(p->session, current); /* Ignore empty folder names to avoid resetting the current path */ while ((next = data->remaining[data->index]) && strlen(next) == 0) data->index++; if (next == NULL) { setpath_complete(p->session, NULL, NULL, user_data); return; } data->index++; p->req_id = g_obex_setpath(obex, next, setpath_cb, p, &err); if (err != NULL) { setpath_complete(p->session, NULL, err, user_data); g_error_free(err); } } static int session_process_setpath(struct pending_request *p, GError **err) { struct setpath_data *req = p->data; const char *first = ""; /* Relative path */ if (req->remaining[0][0] != '/') first = req->remaining[req->index]; req->index++; p->req_id = g_obex_setpath(p->session->obex, first, setpath_cb, p, err); if (*err != NULL) return (*err)->code; p->session->p = p; return 0; } guint obc_session_setpath(struct obc_session *session, const char *path, session_callback_t func, void *user_data, GError **err) { struct setpath_data *data; struct pending_request *p; if (session->obex == NULL) { g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, "Session disconnected"); return 0; } data = g_new0(struct setpath_data, 1); data->func = func; data->user_data = user_data; data->remaining = g_strsplit(strlen(path) ? path : "/", "/", 0); if (!data->remaining || !data->remaining[0]) { error("obc_session_setpath: invalid path %s", path); g_set_error(err, OBEX_IO_ERROR, -EINVAL, "Invalid argument"); setpath_data_free(data); return 0; } p = pending_request_new(session, session_process_setpath, NULL, setpath_op_complete, data, setpath_data_free); session_queue(p); return p->id; } static void async_cb(GObex *obex, GError *err, GObexPacket *rsp, gpointer user_data) { struct pending_request *p = user_data; struct obc_session *session = p->session; GError *gerr = NULL; uint8_t code; p->req_id = 0; if (err != NULL) { if (p->func) p->func(p->session, NULL, err, p->data); goto done; } code = g_obex_packet_get_operation(rsp, NULL); if (code != G_OBEX_RSP_SUCCESS) g_set_error(&gerr, OBEX_IO_ERROR, code, "%s", g_obex_strerror(code)); if (p->func) p->func(p->session, NULL, gerr, p->data); if (gerr != NULL) g_clear_error(&gerr); done: pending_request_free(p); session->p = NULL; session_process_queue(session); } static void file_op_complete(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { struct file_data *data = user_data; if (data->func) data->func(session, NULL, err, data->user_data); } static int session_process_mkdir(struct pending_request *p, GError **err) { struct file_data *req = p->data; p->req_id = g_obex_mkdir(p->session->obex, req->srcname, async_cb, p, err); if (*err != NULL) return (*err)->code; p->session->p = p; return 0; } guint obc_session_mkdir(struct obc_session *session, const char *folder, session_callback_t func, void *user_data, GError **err) { struct file_data *data; struct pending_request *p; if (session->obex == NULL) { g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, "Session disconnected"); return 0; } data = g_new0(struct file_data, 1); data->srcname = g_strdup(folder); data->func = func; data->user_data = user_data; p = pending_request_new(session, session_process_mkdir, NULL, file_op_complete, data, file_data_free); session_queue(p); return p->id; } static int session_process_copy(struct pending_request *p, GError **err) { struct file_data *req = p->data; p->req_id = g_obex_copy(p->session->obex, req->srcname, req->destname, async_cb, p, err); if (*err != NULL) return (*err)->code; p->session->p = p; return 0; } guint obc_session_copy(struct obc_session *session, const char *srcname, const char *destname, session_callback_t func, void *user_data, GError **err) { struct file_data *data; struct pending_request *p; if (session->obex == NULL) { g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, "Session disconnected"); return 0; } data = g_new0(struct file_data, 1); data->srcname = g_strdup(srcname); data->destname = g_strdup(destname); data->func = func; data->user_data = user_data; p = pending_request_new(session, session_process_copy, NULL, file_op_complete, data, file_data_free); session_queue(p); return p->id; } static int session_process_move(struct pending_request *p, GError **err) { struct file_data *req = p->data; p->req_id = g_obex_move(p->session->obex, req->srcname, req->destname, async_cb, p, err); if (*err != NULL) return (*err)->code; p->session->p = p; return 0; } guint obc_session_move(struct obc_session *session, const char *srcname, const char *destname, session_callback_t func, void *user_data, GError **err) { struct file_data *data; struct pending_request *p; if (session->obex == NULL) { g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, "Session disconnected"); return 0; } data = g_new0(struct file_data, 1); data->srcname = g_strdup(srcname); data->destname = g_strdup(destname); data->func = func; data->user_data = user_data; p = pending_request_new(session, session_process_move, NULL, file_op_complete, data, file_data_free); session_queue(p); return p->id; } static int session_process_delete(struct pending_request *p, GError **err) { struct file_data *req = p->data; p->req_id = g_obex_delete(p->session->obex, req->srcname, async_cb, p, err); if (*err != NULL) return (*err)->code; p->session->p = p; return 0; } guint obc_session_delete(struct obc_session *session, const char *file, session_callback_t func, void *user_data, GError **err) { struct file_data *data; struct pending_request *p; if (session->obex == NULL) { g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, "Session disconnected"); return 0; } data = g_new0(struct file_data, 1); data->srcname = g_strdup(file); data->func = func; data->user_data = user_data; p = pending_request_new(session, session_process_delete, NULL, file_op_complete, data, file_data_free); session_queue(p); return p->id; } void obc_session_cancel(struct obc_session *session, guint id, gboolean remove) { struct pending_request *p = session->p; if (p == NULL || p->id != id) return; if (p->req_id == 0) return; g_obex_cancel_req(session->obex, p->req_id, remove); p->req_id = 0; if (!remove) return; pending_request_free(p); session->p = NULL; session_process_queue(session); } bluez-5.87/obexd/client/PaxHeaders/bluetooth.h0000644000000000000000000000005014015011623016401 xustar0020 atime=1783093885 20 ctime=1783094104 bluez-5.87/obexd/client/bluetooth.h0000644000000000000000000000026214015011623016062 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Client * * Copyright (C) 2011 Intel Corporation * * */ int bluetooth_init(void); void bluetooth_exit(void); bluez-5.87/obexd/client/PaxHeaders/session.h0000644000000000000000000000005014711225434016071 xustar0020 atime=1783093885 20 ctime=1783094104 bluez-5.87/obexd/client/session.h0000644000000000000000000000460514711225434015557 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Client * * Copyright (C) 2007-2010 Marcel Holtmann * Copyright (C) 2011-2012 BMW Car IT GmbH. All rights reserved. * * */ #include #include struct obc_session; typedef void (*session_callback_t) (struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data); struct obc_session *obc_session_create(const char *source, const char *destination, const char *service, uint8_t channel, uint16_t psm, const char *owner, session_callback_t function, void *user_data); struct obc_session *obc_session_ref(struct obc_session *session); void obc_session_unref(struct obc_session *session); void obc_session_shutdown(struct obc_session *session); int obc_session_set_owner(struct obc_session *session, const char *name, GDBusWatchFunction func); const char *obc_session_get_owner(struct obc_session *session); const char *obc_session_get_destination(struct obc_session *session); const char *obc_session_get_path(struct obc_session *session); const char *obc_session_get_target(struct obc_session *session); const char *obc_session_register(struct obc_session *session, GDBusDestroyFunction destroy); const void *obc_session_get_attribute(struct obc_session *session, int attribute_id); const char *obc_session_get_folder(struct obc_session *session); guint obc_session_queue(struct obc_session *session, struct obc_transfer *transfer, session_callback_t func, void *user_data, GError **err); guint obc_session_setpath(struct obc_session *session, const char *path, session_callback_t func, void *user_data, GError **err); guint obc_session_mkdir(struct obc_session *session, const char *folder, session_callback_t func, void *user_data, GError **err); guint obc_session_copy(struct obc_session *session, const char *srcname, const char *destname, session_callback_t func, void *user_data, GError **err); guint obc_session_move(struct obc_session *session, const char *srcname, const char *destname, session_callback_t func, void *user_data, GError **err); guint obc_session_delete(struct obc_session *session, const char *file, session_callback_t func, void *user_data, GError **err); void obc_session_cancel(struct obc_session *session, guint id, gboolean remove); bluez-5.87/obexd/client/PaxHeaders/map-event.c0000644000000000000000000000005014015011623016263 xustar0020 atime=1783093905 20 ctime=1783094105 bluez-5.87/obexd/client/map-event.c0000644000000000000000000000377414015011623015757 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX * * Copyright (C) 2013 BMW Car IT GmbH. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "gdbus/gdbus.h" #include "obexd/src/log.h" #include "map-event.h" #include "transfer.h" #include "session.h" static GSList *handlers; struct mns_handler { int mas_id; struct obc_session *session; map_event_cb cb; void *user_data; }; static struct mns_handler *find_handler(int mas_id, const char *device) { GSList *list; for (list = handlers; list; list = list->next) { struct mns_handler *handler = list->data; if (mas_id != handler->mas_id) continue; if (g_str_equal(device, obc_session_get_destination(handler->session))) return handler; } return NULL; } bool map_register_event_handler(struct obc_session *session, int mas_id, map_event_cb cb, void *user_data) { struct mns_handler *handler; handler = find_handler(mas_id, obc_session_get_destination(session)); if (handler != NULL) return FALSE; handler = g_new0(struct mns_handler, 1); handler->mas_id = mas_id; handler->session = session; handler->cb = cb; handler->user_data = user_data; handlers = g_slist_prepend(handlers, handler); DBG("Handler %p for %s:%d registered", handler, obc_session_get_destination(session), mas_id); return TRUE; } void map_unregister_event_handler(struct obc_session *session, int mas_id) { struct mns_handler *handler; handler = find_handler(mas_id, obc_session_get_destination(session)); if (handler == NULL) return; handlers = g_slist_remove(handlers, handler); DBG("Handler %p for %s:%d unregistered", handler, obc_session_get_destination(session), mas_id); g_free(handler); } void map_dispatch_event(int mas_id, const char *device, struct map_event *event) { struct mns_handler *handler; handler = find_handler(mas_id, device); if (handler) handler->cb(event, handler->user_data); } bluez-5.87/obexd/client/PaxHeaders/driver.c0000644000000000000000000000005014015011623015662 xustar0020 atime=1783093908 20 ctime=1783094105 bluez-5.87/obexd/client/driver.c0000644000000000000000000000256414015011623015352 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include "gdbus/gdbus.h" #include "obexd/src/log.h" #include "transfer.h" #include "session.h" #include "driver.h" static GSList *drivers = NULL; struct obc_driver *obc_driver_find(const char *pattern) { GSList *l; for (l = drivers; l; l = l->next) { struct obc_driver *driver = l->data; if (strcasecmp(pattern, driver->service) == 0) return driver; if (strcasecmp(pattern, driver->uuid) == 0) return driver; } return NULL; } int obc_driver_register(struct obc_driver *driver) { if (!driver) { error("Invalid driver"); return -EINVAL; } if (obc_driver_find(driver->service)) { error("Permission denied: service %s already registered", driver->service); return -EPERM; } DBG("driver %p service %s registered", driver, driver->service); drivers = g_slist_append(drivers, driver); return 0; } void obc_driver_unregister(struct obc_driver *driver) { if (!g_slist_find(drivers, driver)) { error("Unable to unregister: No such driver %p", driver); return; } DBG("driver %p service %s unregistered", driver, driver->service); drivers = g_slist_remove(drivers, driver); } bluez-5.87/obexd/client/PaxHeaders/manager.c0000644000000000000000000000005014711225434016013 xustar0020 atime=1783093884 20 ctime=1783094104 bluez-5.87/obexd/client/manager.c0000644000000000000000000001650414711225434015502 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Client * * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "obexd/src/log.h" #include "obexd/src/manager.h" #include "transfer.h" #include "session.h" #include "bluetooth.h" #include "opp.h" #include "ftp.h" #include "pbap.h" #include "sync.h" #include "map.h" #include "bip.h" #include "manager.h" #define CLIENT_INTERFACE "org.bluez.obex.Client1" #define ERROR_INTERFACE "org.bluez.obex.Error" #define CLIENT_PATH "/org/bluez/obex" struct send_data { DBusConnection *connection; DBusMessage *message; }; static GSList *sessions = NULL; static void shutdown_session(struct obc_session *session) { obc_session_shutdown(session); obc_session_unref(session); } static void release_session(struct obc_session *session) { sessions = g_slist_remove(sessions, session); shutdown_session(session); } static void unregister_session(void *data) { struct obc_session *session = data; if (g_slist_find(sessions, session) == NULL) return; sessions = g_slist_remove(sessions, session); obc_session_unref(session); } static void create_callback(struct obc_session *session, struct obc_transfer *transfer, GError *err, void *user_data) { struct send_data *data = user_data; const char *path; if (err != NULL) { DBusMessage *error = g_dbus_create_error(data->message, ERROR_INTERFACE ".Failed", "%s", err->message); g_dbus_send_message(data->connection, error); shutdown_session(session); goto done; } path = obc_session_register(session, unregister_session); if (path == NULL) { DBusMessage *error = g_dbus_create_error(data->message, ERROR_INTERFACE ".Failed", NULL); g_dbus_send_message(data->connection, error); shutdown_session(session); goto done; } sessions = g_slist_append(sessions, session); g_dbus_send_reply(data->connection, data->message, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); done: dbus_message_unref(data->message); dbus_connection_unref(data->connection); g_free(data); } static int parse_device_dict(DBusMessageIter *iter, const char **source, const char **target, uint8_t *channel, uint16_t *psm) { while (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter entry, value; const char *key; dbus_message_iter_recurse(iter, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); switch (dbus_message_iter_get_arg_type(&value)) { case DBUS_TYPE_STRING: if (g_str_equal(key, "Source") == TRUE) dbus_message_iter_get_basic(&value, source); else if (g_str_equal(key, "Target") == TRUE) dbus_message_iter_get_basic(&value, target); break; case DBUS_TYPE_BYTE: if (g_str_equal(key, "Channel") == TRUE) dbus_message_iter_get_basic(&value, channel); break; case DBUS_TYPE_UINT16: if (g_str_equal(key, "PSM") == TRUE) dbus_message_iter_get_basic(&value, psm); break; } dbus_message_iter_next(iter); } return 0; } static struct obc_session *find_session(const char *path) { GSList *l; for (l = sessions; l; l = l->next) { struct obc_session *session = l->data; if (g_strcmp0(obc_session_get_path(session), path) == 0) return session; } return NULL; } static DBusMessage *create_session(DBusConnection *connection, DBusMessage *message, void *user_data) { DBusMessageIter iter, dict; struct obc_session *session; struct send_data *data; const char *source = NULL, *dest = NULL, *target = NULL; uint8_t channel = 0; uint16_t psm = 0; dbus_message_iter_init(message, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); dbus_message_iter_get_basic(&iter, &dest); dbus_message_iter_next(&iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); dbus_message_iter_recurse(&iter, &dict); parse_device_dict(&dict, &source, &target, &channel, &psm); if (dest == NULL || target == NULL || (channel && psm)) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); data = g_try_malloc0(sizeof(*data)); if (data == NULL) return g_dbus_create_error(message, ERROR_INTERFACE ".Error.NoMemory", NULL); data->connection = dbus_connection_ref(connection); data->message = dbus_message_ref(message); session = obc_session_create(source, dest, target, channel, psm, dbus_message_get_sender(message), create_callback, data); if (session != NULL) { return NULL; } dbus_message_unref(data->message); dbus_connection_unref(data->connection); g_free(data); return g_dbus_create_error(message, ERROR_INTERFACE ".Failed", NULL); } static DBusMessage *remove_session(DBusConnection *connection, DBusMessage *message, void *user_data) { struct obc_session *session; const char *sender, *path; if (dbus_message_get_args(message, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID) == FALSE) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); session = find_session(path); if (session == NULL) return g_dbus_create_error(message, ERROR_INTERFACE ".InvalidArguments", NULL); sender = dbus_message_get_sender(message); if (g_str_equal(sender, obc_session_get_owner(session)) == FALSE) return g_dbus_create_error(message, ERROR_INTERFACE ".NotAuthorized", "Not Authorized"); release_session(session); return dbus_message_new_method_return(message); } static const GDBusMethodTable client_methods[] = { { GDBUS_ASYNC_METHOD("CreateSession", GDBUS_ARGS({ "destination", "s" }, { "args", "a{sv}" }), GDBUS_ARGS({ "session", "o" }), create_session) }, { GDBUS_ASYNC_METHOD("RemoveSession", GDBUS_ARGS({ "session", "o" }), NULL, remove_session) }, { } }; static DBusConnection *conn = NULL; static const struct obc_module { const char *name; int (*init) (void); void (*exit) (void); } modules[] = { { "bluetooth", bluetooth_init, bluetooth_exit }, { "opp", opp_init, opp_exit }, { "ftp", ftp_init, ftp_exit }, { "pbap", pbap_init, pbap_exit }, { "sync", sync_init, sync_exit }, { "map", map_init, map_exit }, { "bip", bip_init, bip_exit }, { } }; int client_manager_init(void) { DBusError derr; const struct obc_module *module; dbus_error_init(&derr); conn = manager_dbus_get_connection(); if (conn == NULL) { error("Can't get client D-Bus connection"); return -1; } if (g_dbus_register_interface(conn, CLIENT_PATH, CLIENT_INTERFACE, client_methods, NULL, NULL, NULL, NULL) == FALSE) { error("Can't register client interface"); dbus_connection_unref(conn); conn = NULL; return -1; } for (module = modules; module && module->init; module++) { if (module->init() < 0) continue; DBG("Module %s loaded", module->name); } return 0; } void client_manager_exit(void) { const struct obc_module *module; if (conn == NULL) return; for (module = modules; module && module->exit; module++) module->exit(); g_dbus_unregister_interface(conn, CLIENT_PATH, CLIENT_INTERFACE); dbus_connection_unref(conn); } bluez-5.87/obexd/client/PaxHeaders/map.h0000644000000000000000000000005014015011623015151 xustar0020 atime=1783093885 20 ctime=1783094105 bluez-5.87/obexd/client/map.h0000644000000000000000000000030614015011623014631 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Client * * Copyright (C) 2011 Bartosz Szatkowski for Comarch * */ int map_init(void); void map_exit(void); bluez-5.87/obexd/client/PaxHeaders/bip-common.c0000644000000000000000000000005014711225434016441 xustar0020 atime=1783093902 20 ctime=1783094105 bluez-5.87/obexd/client/bip-common.c0000644000000000000000000004425614711225434016135 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Client * * Copyright (C) 2024 Collabora Ltd. * Based on previous work done by Jakub Adamek for GSoC 2011 * */ #define _GNU_SOURCE #include #include #include #include #include #include "gobex/gobex.h" #include "obexd/src/log.h" #include "bip-common.h" #define HANDLE_LEN 7 #define HANDLE_LIMIT 10000000 struct encconv_pair { gchar *bip, *im; }; struct encconv_pair encconv_table[] = { { "JPEG", "JPEG" }, { "GIF", "GIF" }, { "WBMP", "WBMP" }, { "PNG", "PNG" }, { "JPEG2000", "JP2" }, { "BMP", "BMP" }, { } }; static const gchar *convBIP2IM(const gchar *encoding) { struct encconv_pair *et = encconv_table; while (et->bip) { if (g_strcmp0(encoding, et->bip) == 0) return et->im; et++; } return NULL; } gboolean parse_pixel_range(const gchar *dim, unsigned int *lower_ret, unsigned int *upper_ret, gboolean *fixed_ratio_ret) { static regex_t no_range; static regex_t range; static regex_t range_fixed; static int regex_initialized; unsigned int lower[2], upper[2]; gboolean fixed_ratio = FALSE; if (!regex_initialized) { regcomp(&no_range, "^([[:digit:]]{1,5})\\*([[:digit:]]{1,5})$", REG_EXTENDED); regcomp(&range, "^([[:digit:]]{1,5})\\*([[:digit:]]{1,5})" "-([[:digit:]]{1,5})\\*([[:digit:]]{1,5})$", REG_EXTENDED); regcomp(&range_fixed, "^([[:digit:]]{1,5})\\*\\*" "-([[:digit:]]{1,5})\\*([[:digit:]]{1,5})$", REG_EXTENDED); regex_initialized = 1; } if (dim == NULL) return FALSE; if (regexec(&no_range, dim, 0, NULL, 0) == 0) { if (sscanf(dim, "%u*%u", &lower[0], &lower[1]) != 2) return FALSE; upper[0] = lower[0]; upper[1] = lower[1]; fixed_ratio = FALSE; } else if (regexec(&range, dim, 0, NULL, 0) == 0) { if (sscanf(dim, "%u*%u-%u*%u", &lower[0], &lower[1], &upper[0], &upper[1]) != 4) return FALSE; fixed_ratio = FALSE; } else if (regexec(&range_fixed, dim, 0, NULL, 0) == 0) { if (sscanf(dim, "%u**-%u*%u", &lower[0], &upper[0], &upper[1]) != 3) return FALSE; lower[1] = 0; fixed_ratio = TRUE; } else { return FALSE; } if (lower[0] > 65535 || lower[1] > 65535 || upper[0] > 65535 || upper[1] > 65535) return FALSE; if (lower_ret == NULL || upper_ret == NULL || fixed_ratio_ret == NULL) return TRUE; if (upper[0] < lower[0] || upper[1] < lower[1]) return FALSE; lower_ret[0] = lower[0]; lower_ret[1] = lower[1]; upper_ret[0] = upper[0]; upper_ret[1] = upper[1]; *fixed_ratio_ret = fixed_ratio; return TRUE; } static gboolean verify_unsignednumber(const char *size) { static regex_t unumber; static int regex_initialized; if (!regex_initialized) { regcomp(&unumber, "^[[:digit:]]+$", REG_EXTENDED); regex_initialized = 1; } if (regexec(&unumber, size, 0, NULL, 0) != 0) return FALSE; return TRUE; } static uint64_t parse_unsignednumber(const char *size) { if (!verify_unsignednumber(size)) return 0; return g_ascii_strtoll(size, NULL, 10); } char *transforms[] = { "crop", "stretch", "fill", NULL }; gboolean verify_encoding(const char *encoding) { struct encconv_pair *et = encconv_table; while (et->bip) { if (g_strcmp0(encoding, et->bip) == 0) return TRUE; et++; } return FALSE; } static gboolean verify_transform(const char *transform) { char **str = transforms; while (*str != NULL) { if (g_str_equal(transform, *str)) return TRUE; str++; } return FALSE; } char *parse_transform(const char *transform) { if (!verify_transform(transform)) return NULL; return g_strdup(transform); } static char *parse_transform_list(const char *transform) { char **args = NULL, **arg = NULL; gboolean used[3] = { FALSE, FALSE, FALSE }; if (transform == NULL) return NULL; if (strlen(transform) == 0) return NULL; args = g_strsplit(transform, " ", 0); for (arg = args; *arg != NULL; arg++) { char *t = *arg; if (!verify_transform(t)) { g_strfreev(args); return NULL; } switch (t[0]) { case 's': if (used[0]) goto failure; used[0] = TRUE; break; case 'c': if (used[1]) goto failure; used[1] = TRUE; break; case 'f': if (used[2]) goto failure; used[2] = TRUE; break; } } g_strfreev(args); return g_strdup(transform); failure: g_strfreev(args); return NULL; } static time_t parse_iso8601_bip(const gchar *str, int len) { gchar *tstr; struct tm tm; gint nr; gchar tz; time_t time; time_t tz_offset = 0; if (str == NULL) return -1; memset(&tm, 0, sizeof(struct tm)); /* According to spec the time doesn't have to be null terminated */ if (str[len - 1] != '\0') { tstr = g_malloc(len + 1); strncpy(tstr, str, len); tstr[len] = '\0'; } else tstr = g_strdup(str); nr = sscanf(tstr, "%04u%02u%02uT%02u%02u%02u%c", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &tz); g_free(tstr); /* Fixup the tm values */ tm.tm_year -= 1900; /* Year since 1900 */ tm.tm_mon--; /* Months since January, values 0-11 */ tm.tm_isdst = -1; /* Daylight savings information not avail */ if (nr < 6) { /* Invalid time format */ return -1; } time = mktime(&tm); #if defined(HAVE_TM_GMTOFF) tz_offset = tm.tm_gmtoff; #elif defined(HAVE_TIMEZONE) tz_offset = -timezone; if (tm.tm_isdst > 0) tz_offset += 3600; #endif if (nr == 7) { /* Date/Time was in localtime (to remote device) * already. Since we don't know anything about the * timezone on that one we won't try to apply UTC offset */ time += tz_offset; } return time; } static int parse_handle(const char *data) { int handle; char *ptr; if (data == NULL) return -1; if (strlen(data) != HANDLE_LEN) return -1; handle = strtol(data, &ptr, 10); if (ptr != data + HANDLE_LEN) return -1; if (handle < 0 || handle >= HANDLE_LIMIT) return -1; return handle; } struct native_prop { char *encoding, *pixel; uint64_t size; }; struct variant_prop { char *encoding, *pixel, *transform; uint64_t maxsize; }; struct att_prop { char *content_type, *charset, *name; uint64_t size; time_t ctime, mtime; }; struct prop_object { char *handle, *name; GSList *native, *variant, *att; }; static void free_native_prop(struct native_prop *prop) { DBG(""); if (prop == NULL) return; g_free(prop->encoding); g_free(prop->pixel); g_free(prop); } static void free_variant_prop(struct variant_prop *prop) { DBG(""); if (prop == NULL) return; g_free(prop->encoding); g_free(prop->pixel); g_free(prop->transform); g_free(prop); } static void free_att_prop(struct att_prop *prop) { DBG(""); if (prop == NULL) return; g_free(prop->content_type); g_free(prop->charset); g_free(prop->name); g_free(prop); } static void free_prop_object(struct prop_object *object) { GSList *list; DBG(""); if (object == NULL) return; for (list = object->native; list != NULL; list = g_slist_next(list)) free_native_prop(list->data); for (list = object->variant; list != NULL; list = g_slist_next(list)) free_variant_prop(list->data); for (list = object->att; list != NULL; list = g_slist_next(list)) free_att_prop(list->data); g_slist_free(object->native); g_slist_free(object->variant); g_slist_free(object->att); g_free(object->handle); g_free(object->name); g_free(object); } static gboolean parse_attrib_native(struct native_prop *prop, const gchar *key, const gchar *value, GError **gerr) { DBG(""); if (g_str_equal(key, "encoding")) { if (convBIP2IM(value) == NULL) goto invalid; prop->encoding = g_strdup(value); } else if (g_str_equal(key, "pixel")) { if (!parse_pixel_range(value, NULL, NULL, NULL)) goto invalid; prop->pixel = g_strdup(value); } else if (g_str_equal(key, "size")) { prop->size = parse_unsignednumber(value); if (prop->size == 0) goto invalid; } else { g_set_error(gerr, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, NULL); return FALSE; } return TRUE; invalid: g_set_error(gerr, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, NULL); return FALSE; } static gboolean parse_attrib_variant(struct variant_prop *prop, const gchar *key, const gchar *value, GError **gerr) { DBG(""); if (g_str_equal(key, "encoding")) { if (convBIP2IM(value) == NULL) goto invalid; prop->encoding = g_strdup(value); } else if (g_str_equal(key, "pixel")) { if (!parse_pixel_range(value, NULL, NULL, NULL)) goto invalid; prop->pixel = g_strdup(value); } else if (g_str_equal(key, "maxsize")) { prop->maxsize = parse_unsignednumber(value); if (prop->maxsize == 0) goto invalid; } else if (g_str_equal(key, "transform")) { prop->transform = parse_transform_list(value); if (prop->transform == NULL) goto invalid; } else { g_set_error(gerr, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, NULL); return FALSE; } return TRUE; invalid: g_set_error(gerr, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, NULL); return FALSE; } static gboolean parse_attrib_att(struct att_prop *prop, const gchar *key, const gchar *value, GError **gerr) { DBG(""); if (g_str_equal(key, "content-type")) { prop->content_type = g_strdup(value); } else if (g_str_equal(key, "charset")) { prop->charset = g_strdup(value); } else if (g_str_equal(key, "name")) { prop->name = g_strdup(value); } else if (g_str_equal(key, "size")) { prop->size = parse_unsignednumber(value); if (prop->size == 0) goto invalid; } else if (g_str_equal(key, "created")) { prop->ctime = parse_iso8601_bip(value, strlen(value)); if (prop->ctime == -1) goto invalid; } else if (g_str_equal(key, "modified")) { prop->mtime = parse_iso8601_bip(value, strlen(value)); if (prop->mtime == -1) goto invalid; } else { g_set_error(gerr, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, NULL); return FALSE; } return TRUE; invalid: g_set_error(gerr, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, NULL); return FALSE; } static struct att_prop *parse_elem_att(const gchar **names, const gchar **values, GError **gerr) { gchar **key; struct att_prop *prop = g_new0(struct att_prop, 1); DBG(""); for (key = (gchar **) names; *key; key++, values++) { if (!parse_attrib_att(prop, *key, *values, gerr)) { free_att_prop(prop); return NULL; } } return prop; } static struct variant_prop *parse_elem_variant(const gchar **names, const gchar **values, GError **gerr) { gchar **key; struct variant_prop *prop = g_new0(struct variant_prop, 1); DBG(""); for (key = (gchar **) names; *key; key++, values++) { if (!parse_attrib_variant(prop, *key, *values, gerr)) { free_variant_prop(prop); return NULL; } } if (prop->transform == NULL) prop->transform = g_strdup("stretch crop fill"); return prop; } static struct native_prop *parse_elem_native(const gchar **names, const gchar **values, GError **gerr) { gchar **key; struct native_prop *prop = g_new0(struct native_prop, 1); DBG(""); for (key = (gchar **) names; *key; key++, values++) { if (!parse_attrib_native(prop, *key, *values, gerr)) { free_native_prop(prop); return NULL; } } return prop; } static gboolean parse_attrib_prop(struct prop_object *prop, const gchar *key, const gchar *value, GError **gerr) { DBG(""); if (g_str_equal(key, "handle")) { if (parse_handle(value) < 0) goto invalid; prop->handle = g_strdup(value); } else if (g_str_equal(key, "friendly-name")) { prop->name = g_strdup(value); } else if (g_str_equal(key, "version")) { // pass; } else { g_set_error(gerr, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE, NULL); return FALSE; } return TRUE; invalid: g_set_error(gerr, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, NULL); return FALSE; } static struct prop_object *parse_elem_prop(const gchar **names, const gchar **values, GError **gerr) { gchar **key; struct prop_object *prop = g_new0(struct prop_object, 1); DBG(""); for (key = (gchar **) names; *key; key++, values++) { if (!parse_attrib_prop(prop, *key, *values, gerr)) { free_prop_object(prop); return NULL; } } return prop; } static void prop_element(GMarkupParseContext *ctxt, const gchar *element, const gchar **names, const gchar **values, gpointer user_data, GError **gerr) { struct prop_object **obj = user_data; DBG(""); if (g_str_equal(element, "image-properties")) { if (*obj != NULL) { free_prop_object(*obj); *obj = NULL; goto invalid; } *obj = parse_elem_prop(names, values, gerr); } else if (g_str_equal(element, "native")) { struct native_prop *prop; if (*obj == NULL) goto invalid; prop = parse_elem_native(names, values, gerr); (*obj)->native = g_slist_append((*obj)->native, prop); } else if (g_str_equal(element, "variant")) { struct variant_prop *prop; if (*obj == NULL) goto invalid; prop = parse_elem_variant(names, values, gerr); (*obj)->variant = g_slist_append((*obj)->variant, prop); } else if (g_str_equal(element, "attachment")) { struct att_prop *prop; if (*obj == NULL) goto invalid; prop = parse_elem_att(names, values, gerr); (*obj)->att = g_slist_append((*obj)->att, prop); } else { if (*obj != NULL) { free_prop_object(*obj); *obj = NULL; } goto invalid; } return; invalid: g_set_error(gerr, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, NULL); } static const GMarkupParser properties_parser = { prop_element, NULL, NULL, NULL, NULL }; struct prop_object *parse_properties(char *data, unsigned int length, int *err) { struct prop_object *prop = NULL; gboolean status; GError *gerr = NULL; GMarkupParseContext *ctxt = g_markup_parse_context_new( &properties_parser, 0, &prop, NULL); DBG(""); if (err != NULL) *err = 0; status = g_markup_parse_context_parse(ctxt, data, length, &gerr); g_markup_parse_context_free(ctxt); if (!status) { if (err != NULL) *err = -EINVAL; free_prop_object(prop); prop = NULL; } return prop; } gboolean verify_properties(struct prop_object *obj) { GSList *list; if (obj->handle == NULL) return FALSE; for (list = obj->native; list != NULL; list = g_slist_next(list)) { struct native_prop *prop = list->data; if (prop->encoding == NULL || prop->pixel == NULL) return FALSE; } for (list = obj->variant; list != NULL; list = g_slist_next(list)) { struct variant_prop *prop = list->data; if (prop->encoding == NULL || prop->pixel == NULL) return FALSE; } for (list = obj->att; list != NULL; list = g_slist_next(list)) { struct att_prop *prop = list->data; if (prop->content_type == NULL || prop->name == NULL) return FALSE; } return TRUE; } void append_properties(DBusMessageIter *args, struct prop_object *obj) { DBusMessageIter dict, iter; GSList *list; dbus_message_iter_open_container(args, DBUS_TYPE_ARRAY, DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); dbus_message_iter_open_container(&dict, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &iter); g_dbus_dict_append_entry(&iter, "handle", DBUS_TYPE_STRING, &obj->handle); g_dbus_dict_append_entry(&iter, "name", DBUS_TYPE_STRING, &obj->name); dbus_message_iter_close_container(&dict, &iter); for (list = obj->native; list != NULL; list = g_slist_next(list)) { struct native_prop *prop = list->data; static char *native_str = "native"; dbus_message_iter_open_container(&dict, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &iter); g_dbus_dict_append_entry(&iter, "type", DBUS_TYPE_STRING, &native_str); if (prop->encoding) g_dbus_dict_append_entry(&iter, "encoding", DBUS_TYPE_STRING, &prop->encoding); if (prop->pixel) g_dbus_dict_append_entry(&iter, "pixel", DBUS_TYPE_STRING, &prop->pixel); if (prop->size) g_dbus_dict_append_entry(&iter, "size", DBUS_TYPE_UINT64, &prop->size); dbus_message_iter_close_container(&dict, &iter); } for (list = obj->variant; list != NULL; list = g_slist_next(list)) { struct variant_prop *prop = list->data; static char *variant_str = "variant"; dbus_message_iter_open_container(&dict, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &iter); g_dbus_dict_append_entry(&iter, "type", DBUS_TYPE_STRING, &variant_str); if (prop->encoding) g_dbus_dict_append_entry(&iter, "encoding", DBUS_TYPE_STRING, &prop->encoding); if (prop->pixel) g_dbus_dict_append_entry(&iter, "pixel", DBUS_TYPE_STRING, &prop->pixel); if (prop->maxsize) g_dbus_dict_append_entry(&iter, "maxsize", DBUS_TYPE_UINT64, &prop->maxsize); if (prop->transform) g_dbus_dict_append_entry(&iter, "transformation", DBUS_TYPE_STRING, &prop->transform); dbus_message_iter_close_container(&dict, &iter); } for (list = obj->att; list != NULL; list = g_slist_next(list)) { struct att_prop *prop = list->data; static char *attachment_str = "attachment"; dbus_message_iter_open_container(&dict, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &iter); g_dbus_dict_append_entry(&iter, "type", DBUS_TYPE_STRING, &attachment_str); if (prop->content_type) g_dbus_dict_append_entry(&iter, "content-type", DBUS_TYPE_STRING, &prop->content_type); if (prop->charset) g_dbus_dict_append_entry(&iter, "charset", DBUS_TYPE_STRING, &prop->charset); if (prop->name) g_dbus_dict_append_entry(&iter, "name", DBUS_TYPE_STRING, &prop->name); if (prop->size) g_dbus_dict_append_entry(&iter, "size", DBUS_TYPE_UINT64, &prop->size); if (prop->ctime) g_dbus_dict_append_entry(&iter, "ctime", DBUS_TYPE_UINT64, &prop->ctime); if (prop->mtime) g_dbus_dict_append_entry(&iter, "mtime", DBUS_TYPE_UINT64, &prop->mtime); dbus_message_iter_close_container(&dict, &iter); } dbus_message_iter_close_container(args, &dict); } bluez-5.87/obexd/client/PaxHeaders/driver.h0000644000000000000000000000005014015011623015667 xustar0020 atime=1783093886 20 ctime=1783094105 bluez-5.87/obexd/client/driver.h0000644000000000000000000000106614015011623015353 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * */ struct obc_driver { const char *service; const char *uuid; void *target; gsize target_len; void *(*supported_features) (struct obc_session *session); int (*probe) (struct obc_session *session); void (*remove) (struct obc_session *session); }; int obc_driver_register(struct obc_driver *driver); void obc_driver_unregister(struct obc_driver *driver); struct obc_driver *obc_driver_find(const char *pattern); bluez-5.87/PaxHeaders/src0000644000000000000000000000005015221755536012400 xustar0020 atime=1783094113 20 ctime=1783094110 bluez-5.87/src/0000755000000000000000000000000015221755536012136 5ustar00rootrootbluez-5.87/src/PaxHeaders/sdp-client.c0000644000000000000000000000005015063045254014654 xustar0020 atime=1783093754 20 ctime=1783094106 bluez-5.87/src/sdp-client.c0000644000000000000000000002263615063045254014346 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2011 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "btio/btio.h" #include "shared/timeout.h" #include "log.h" #include "sdp-client.h" /* Number of seconds to keep a sdp_session_t in the cache */ #define CACHE_TIMEOUT 2 struct cached_sdp_session { bdaddr_t src; bdaddr_t dst; sdp_session_t *session; unsigned int timer; guint io_id; }; static GSList *cached_sdp_sessions = NULL; static void cleanup_cached_session(struct cached_sdp_session *cached) { cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, cached); sdp_close(cached->session); g_free(cached); } static bool cached_session_expired(gpointer user_data) { struct cached_sdp_session *cached = user_data; g_source_remove(cached->io_id); cleanup_cached_session(cached); return FALSE; } static sdp_session_t *get_cached_sdp_session(const bdaddr_t *src, const bdaddr_t *dst) { GSList *l; for (l = cached_sdp_sessions; l != NULL; l = l->next) { struct cached_sdp_session *c = l->data; sdp_session_t *session; if (bacmp(&c->src, src) || bacmp(&c->dst, dst)) continue; timeout_remove(c->timer); g_source_remove(c->io_id); session = c->session; cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, c); g_free(c); return session; } return NULL; } static gboolean disconnect_watch(GIOChannel *chan, GIOCondition cond, gpointer user_data) { struct cached_sdp_session *cached = user_data; timeout_remove(cached->timer); cleanup_cached_session(cached); return FALSE; } static void cache_sdp_session(bdaddr_t *src, bdaddr_t *dst, sdp_session_t *session) { struct cached_sdp_session *cached; int sk; GIOChannel *chan; cached = g_new0(struct cached_sdp_session, 1); bacpy(&cached->src, src); bacpy(&cached->dst, dst); cached->session = session; cached_sdp_sessions = g_slist_append(cached_sdp_sessions, cached); cached->timer = timeout_add_seconds(CACHE_TIMEOUT, cached_session_expired, cached, NULL); /* Watch the connection state during cache timeout */ sk = sdp_get_socket(session); chan = g_io_channel_unix_new(sk); cached->io_id = g_io_add_watch(chan, G_IO_HUP | G_IO_ERR | G_IO_NVAL, disconnect_watch, cached); g_io_channel_unref(chan); } struct search_context { bdaddr_t src; bdaddr_t dst; sdp_session_t *session; bt_callback_t cb; bt_destroy_t destroy; gpointer user_data; uuid_t uuid; guint io_id; gboolean filter_svc_class; }; static GSList *context_list = NULL; static void search_context_cleanup(struct search_context *ctxt) { context_list = g_slist_remove(context_list, ctxt); if (ctxt->destroy) ctxt->destroy(ctxt->user_data); g_free(ctxt); } static void search_completed_cb(uint8_t type, uint16_t status, uint8_t *rsp, size_t size, void *user_data) { struct search_context *ctxt = user_data; sdp_list_t *recs = NULL; int scanned, seqlen = 0, bytesleft = size; uint8_t dataType; int err = 0; if (status || type != SDP_SVC_SEARCH_ATTR_RSP) { err = -EPROTO; goto done; } scanned = sdp_extract_seqtype(rsp, bytesleft, &dataType, &seqlen); if (!scanned || !seqlen) goto done; rsp += scanned; bytesleft -= scanned; do { sdp_record_t *rec; int recsize; recsize = 0; rec = sdp_extract_pdu(rsp, bytesleft, &recsize); if (!rec) break; if (!recsize) { sdp_record_free(rec); break; } scanned += recsize; rsp += recsize; bytesleft -= recsize; /* Check whether service class ID matches some specified uuid. * If the record is missing service class ID, svclass will be * all zero, and thus will be unequal to the requested uuid. */ if (ctxt->filter_svc_class && sdp_uuid_cmp(&ctxt->uuid, &rec->svclass) != 0) { sdp_record_free(rec); continue; } recs = sdp_list_append(recs, rec); } while (scanned < (ssize_t) size && bytesleft > 0); done: cache_sdp_session(&ctxt->src, &ctxt->dst, ctxt->session); if (ctxt->cb) ctxt->cb(recs, err, ctxt->user_data); if (recs) sdp_list_free(recs, (sdp_free_func_t) sdp_record_free); search_context_cleanup(ctxt); } static gboolean search_process_cb(GIOChannel *chan, GIOCondition cond, gpointer user_data) { struct search_context *ctxt = user_data; if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { sdp_close(ctxt->session); ctxt->session = NULL; if (ctxt->cb) ctxt->cb(NULL, -EIO, ctxt->user_data); search_context_cleanup(ctxt); return FALSE; } /* If sdp_process fails it calls search_completed_cb */ if (sdp_process(ctxt->session) < 0) return FALSE; return TRUE; } static gboolean connect_watch(GIOChannel *chan, GIOCondition cond, gpointer user_data) { struct search_context *ctxt = user_data; sdp_list_t *search, *attrids; uint32_t range = 0x0000ffff; socklen_t len; int sk, err, sk_err = 0; sk = g_io_channel_unix_get_fd(chan); ctxt->io_id = 0; len = sizeof(sk_err); if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &sk_err, &len) < 0) err = -errno; else err = -sk_err; if (err != 0) goto failed; if (sdp_set_notify(ctxt->session, search_completed_cb, ctxt) < 0) { err = -EIO; goto failed; } search = sdp_list_append(NULL, &ctxt->uuid); attrids = sdp_list_append(NULL, &range); if (sdp_service_search_attr_async(ctxt->session, search, SDP_ATTR_REQ_RANGE, attrids) < 0) { sdp_list_free(attrids, NULL); sdp_list_free(search, NULL); err = -EIO; goto failed; } sdp_list_free(attrids, NULL); sdp_list_free(search, NULL); /* Set callback responsible for update the internal SDP transaction */ ctxt->io_id = g_io_add_watch(chan, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, search_process_cb, ctxt); return FALSE; failed: sdp_close(ctxt->session); ctxt->session = NULL; if (ctxt->cb) ctxt->cb(NULL, err, ctxt->user_data); search_context_cleanup(ctxt); return FALSE; } static int create_search_context(struct search_context **ctxt, const bdaddr_t *src, const bdaddr_t *dst, uuid_t *uuid, uint16_t flags) { sdp_session_t *s; GIOChannel *chan; uint32_t prio = 1; int sk; if (!ctxt) return -EINVAL; s = get_cached_sdp_session(src, dst); if (!s) s = sdp_connect(src, dst, SDP_NON_BLOCKING | flags); if (!s) return -errno; *ctxt = g_try_malloc0(sizeof(struct search_context)); if (!*ctxt) { sdp_close(s); return -ENOMEM; } bacpy(&(*ctxt)->src, src); bacpy(&(*ctxt)->dst, dst); (*ctxt)->session = s; (*ctxt)->uuid = *uuid; sk = sdp_get_socket(s); /* Set low priority for the SDP connection not to interfere with * other potential traffic. */ if (setsockopt(sk, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio)) < 0) warn("Setting SDP priority failed: %s (%d)", strerror(errno), errno); chan = g_io_channel_unix_new(sk); (*ctxt)->io_id = g_io_add_watch(chan, G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL, connect_watch, *ctxt); g_io_channel_unref(chan); return 0; } static int create_search_context_full(struct search_context **ctxt, const bdaddr_t *src, const bdaddr_t *dst, uuid_t *uuid, uint16_t flags, void *user_data, bt_callback_t cb, bt_destroy_t destroy, gboolean filter_svc_class) { int err = create_search_context(ctxt, src, dst, uuid, flags); if (err < 0) return err; (*ctxt)->cb = cb; (*ctxt)->destroy = destroy; (*ctxt)->user_data = user_data; (*ctxt)->filter_svc_class = filter_svc_class; return 0; } int bt_search(const bdaddr_t *src, const bdaddr_t *dst, uuid_t *uuid, bt_callback_t cb, void *user_data, bt_destroy_t destroy, uint16_t flags) { struct search_context *ctxt = NULL; int err; if (!cb) return -EINVAL; /* The resulting service class ID doesn't have to match uuid */ err = create_search_context_full(&ctxt, src, dst, uuid, flags, user_data, cb, destroy, FALSE); if (err < 0) return err; context_list = g_slist_append(context_list, ctxt); return 0; } int bt_search_service(const bdaddr_t *src, const bdaddr_t *dst, uuid_t *uuid, bt_callback_t cb, void *user_data, bt_destroy_t destroy, uint16_t flags) { struct search_context *ctxt = NULL; int err; if (!cb) return -EINVAL; /* The resulting service class ID need to match uuid */ err = create_search_context_full(&ctxt, src, dst, uuid, flags, user_data, cb, destroy, TRUE); if (err < 0) return err; context_list = g_slist_append(context_list, ctxt); return 0; } static int find_by_bdaddr(gconstpointer data, gconstpointer user_data) { const struct search_context *ctxt = data, *search = user_data; int ret; ret = bacmp(&ctxt->src, &search->src); if (ret != 0) return ret; return bacmp(&ctxt->dst, &search->dst); } int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst) { struct search_context match, *ctxt; GSList *l; memset(&match, 0, sizeof(match)); bacpy(&match.src, src); bacpy(&match.dst, dst); /* Ongoing SDP Discovery */ l = g_slist_find_custom(context_list, &match, find_by_bdaddr); if (l == NULL) return -ENOENT; ctxt = l->data; if (!ctxt->session) return -ENOTCONN; if (ctxt->io_id) g_source_remove(ctxt->io_id); if (ctxt->session) sdp_close(ctxt->session); search_context_cleanup(ctxt); return 0; } void bt_clear_cached_session(const bdaddr_t *src, const bdaddr_t *dst) { sdp_session_t *session; session = get_cached_sdp_session(src, dst); if (session) sdp_close(session); } bluez-5.87/src/PaxHeaders/battery.c0000644000000000000000000000005015110075565014265 xustar0020 atime=1783093822 20 ctime=1783094106 bluez-5.87/src/battery.c0000644000000000000000000003570015110075565013753 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2020 Google LLC * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include "gdbus/gdbus.h" #include "bluetooth/bluetooth.h" #include "src/shared/battery.h" #include "src/shared/queue.h" #include "src/shared/util.h" #include "battery.h" #include "dbus-common.h" #include "adapter.h" #include "device.h" #include "log.h" #include "error.h" #define BATTERY_INTERFACE "org.bluez.Battery1" #define BATTERY_PROVIDER_INTERFACE "org.bluez.BatteryProvider1" #define BATTERY_PROVIDER_MANAGER_INTERFACE "org.bluez.BatteryProviderManager1" #define BATTERY_MAX_PERCENTAGE 100 struct btd_battery { char *path; /* D-Bus object path */ uint8_t percentage; /* valid between 0 to 100 inclusively */ char *source; /* Descriptive source of the battery info */ char *provider_path; /* The provider root path, if any */ struct bt_battery *filter; }; struct btd_battery_provider_manager { struct btd_adapter *adapter; /* Does not own pointer */ struct queue *battery_providers; }; struct battery_provider { struct btd_battery_provider_manager *manager; /* Does not own pointer */ char *owner; /* Owner D-Bus address */ char *path; /* D-Bus object path */ GDBusClient *client; }; static struct queue *batteries = NULL; static void provider_disconnect_cb(DBusConnection *conn, void *user_data); static void battery_add(struct btd_battery *battery) { if (!batteries) batteries = queue_new(); queue_push_head(batteries, battery); } static void battery_remove(struct btd_battery *battery) { queue_remove(batteries, battery); if (queue_isempty(batteries)) { queue_destroy(batteries, NULL); batteries = NULL; } } static bool match_path(const void *data, const void *user_data) { const struct btd_battery *battery = data; const char *path = user_data; return g_strcmp0(battery->path, path) == 0; } static struct btd_battery *battery_new(const char *path, const char *source, const char *provider_path) { struct btd_battery *battery; battery = new0(struct btd_battery, 1); battery->path = g_strdup(path); battery->percentage = UINT8_MAX; if (source) battery->source = g_strdup(source); if (provider_path) battery->provider_path = g_strdup(provider_path); battery->filter = bt_battery_new(); return battery; } static void battery_free(struct btd_battery *battery) { if (battery->path) g_free(battery->path); if (battery->source) g_free(battery->source); if (battery->filter) { bt_battery_free(battery->filter); free(battery->filter); } free(battery); } static gboolean property_percentage_get(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_battery *battery = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &battery->percentage); return TRUE; } static gboolean property_percentage_exists(const GDBusPropertyTable *property, void *data) { struct btd_battery *battery = data; return battery->percentage <= BATTERY_MAX_PERCENTAGE; } static gboolean property_source_get(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_battery *battery = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &battery->source); return TRUE; } static gboolean property_source_exists(const GDBusPropertyTable *property, void *data) { struct btd_battery *battery = data; return battery->source != NULL; } static const GDBusPropertyTable battery_properties[] = { { "Percentage", "y", property_percentage_get, NULL, property_percentage_exists }, { "Source", "s", property_source_get, NULL, property_source_exists }, {} }; struct btd_battery *btd_battery_register(const char *path, const char *source, const char *provider_path) { struct btd_battery *battery; DBG("path = %s", path); if (queue_find(batteries, match_path, path)) { error("error registering battery: path exists"); return NULL; } if (!g_str_has_prefix(path, "/")) { error("error registering battery: invalid D-Bus object path"); return NULL; } battery = battery_new(path, source, provider_path); battery_add(battery); if (!g_dbus_register_interface(btd_get_dbus_connection(), battery->path, BATTERY_INTERFACE, NULL, NULL, battery_properties, battery, NULL)) { error("error registering D-Bus interface for %s", battery->path); battery_remove(battery); battery_free(battery); return NULL; } DBG("registered Battery object: %s", battery->path); return battery; } bool btd_battery_unregister(struct btd_battery *battery) { DBG("path = %s", battery->path); if (!queue_find(batteries, NULL, battery)) { error("error unregistering battery: " "battery %s is not registered", battery->path); return false; } if (!g_dbus_unregister_interface(btd_get_dbus_connection(), battery->path, BATTERY_INTERFACE)) { error("error unregistering battery %s from D-Bus interface", battery->path); return false; } battery_remove(battery); battery_free(battery); return true; } bool btd_battery_update(struct btd_battery *battery, uint8_t percentage) { DBG("path = %s", battery->path); if (!queue_find(batteries, NULL, battery)) { error("error updating battery: battery is not registered"); return false; } if (percentage > BATTERY_MAX_PERCENTAGE) { error("error updating battery: percentage is not valid"); return false; } if (battery->percentage == percentage) return true; battery->percentage = bt_battery_charge(battery->filter, percentage); g_dbus_emit_property_changed(btd_get_dbus_connection(), battery->path, BATTERY_INTERFACE, "Percentage"); return true; } static struct btd_battery *find_battery_by_path(const char *path) { return queue_find(batteries, match_path, path); } static void provided_battery_property_changed_cb(GDBusProxy *proxy, const char *name, DBusMessageIter *iter, void *user_data) { uint8_t percentage = 0; const char *export_path; DBusMessageIter dev_iter; if (g_dbus_proxy_get_property(proxy, "Device", &dev_iter) == FALSE) return; dbus_message_iter_get_basic(&dev_iter, &export_path); if (strcmp(name, "Percentage") != 0) return; if (iter) { if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BYTE) return; dbus_message_iter_get_basic(iter, &percentage); } DBG("battery percentage changed on %s, percentage = %d", g_dbus_proxy_get_path(proxy), percentage); btd_battery_update(find_battery_by_path(export_path), percentage); } static void provided_battery_added_cb(GDBusProxy *proxy, void *user_data) { struct battery_provider *provider = user_data; struct btd_battery *battery; struct btd_device *device; const char *path = g_dbus_proxy_get_path(proxy); const char *export_path; const char *source = NULL; uint8_t percentage; DBusMessageIter iter; if (strcmp(g_dbus_proxy_get_interface(proxy), BATTERY_PROVIDER_INTERFACE) != 0) return; if (g_dbus_proxy_get_property(proxy, "Device", &iter) == FALSE) { warn("Battery object %s does not specify device path", path); return; } dbus_message_iter_get_basic(&iter, &export_path); device = btd_adapter_find_device_by_path(provider->manager->adapter, export_path); if (!device || device_is_temporary(device)) { warn("Ignoring non-existent device path for battery %s", export_path); return; } if (find_battery_by_path(export_path)) { DBG("Battery for %s is already provided, ignoring the new one", export_path); return; } g_dbus_proxy_set_property_watch( proxy, provided_battery_property_changed_cb, provider); if (g_dbus_proxy_get_property(proxy, "Source", &iter) == TRUE) dbus_message_iter_get_basic(&iter, &source); battery = btd_battery_register(export_path, source, provider->path); DBG("provided battery added %s", path); /* Percentage property may not be immediately available, that's okay * since we monitor changes to this property. */ if (g_dbus_proxy_get_property(proxy, "Percentage", &iter) == FALSE) return; dbus_message_iter_get_basic(&iter, &percentage); btd_battery_update(battery, percentage); } static void provided_battery_removed_cb(GDBusProxy *proxy, void *user_data) { struct battery_provider *provider = user_data; struct btd_battery *battery; const char *export_path; DBusMessageIter iter; if (strcmp(g_dbus_proxy_get_interface(proxy), BATTERY_PROVIDER_INTERFACE) != 0) return; if (g_dbus_proxy_get_property(proxy, "Device", &iter) == FALSE) return; dbus_message_iter_get_basic(&iter, &export_path); DBG("provided battery removed %s", g_dbus_proxy_get_path(proxy)); battery = find_battery_by_path(export_path); if (!battery) return; if (g_strcmp0(battery->provider_path, provider->path) != 0) return; g_dbus_proxy_set_property_watch(proxy, NULL, NULL); btd_battery_unregister(battery); } static bool match_provider_path(const void *data, const void *user_data) { const struct battery_provider *provider = data; const char *path = user_data; return strcmp(provider->path, path) == 0; } static void unregister_if_path_has_prefix(void *data, void *user_data) { struct btd_battery *battery = data; struct battery_provider *provider = user_data; if (g_strcmp0(battery->provider_path, provider->path) == 0) btd_battery_unregister(battery); } static void battery_provider_free(gpointer data) { struct battery_provider *provider = data; /* Unregister batteries under the root path of provider->path */ queue_foreach(batteries, unregister_if_path_has_prefix, provider); if (provider->owner) g_free(provider->owner); if (provider->path) g_free(provider->path); if (provider->client) { g_dbus_client_set_disconnect_watch(provider->client, NULL, NULL); g_dbus_client_set_proxy_handlers(provider->client, NULL, NULL, NULL, NULL); g_dbus_client_unref(provider->client); } free(provider); } static struct battery_provider * battery_provider_new(DBusConnection *conn, struct btd_battery_provider_manager *manager, const char *path, const char *sender) { struct battery_provider *provider; provider = new0(struct battery_provider, 1); provider->manager = manager; provider->owner = g_strdup(sender); provider->path = g_strdup(path); provider->client = g_dbus_client_new_full(conn, sender, path, path); if (!provider->client) { error("error creating D-Bus client %s", path); battery_provider_free(provider); return NULL; } g_dbus_client_set_disconnect_watch(provider->client, provider_disconnect_cb, provider); g_dbus_client_set_proxy_handlers(provider->client, provided_battery_added_cb, provided_battery_removed_cb, NULL, provider); return provider; } static void provider_disconnect_cb(DBusConnection *conn, void *user_data) { struct battery_provider *provider = user_data; struct btd_battery_provider_manager *manager = provider->manager; DBG("battery provider client disconnected %s root path %s", provider->owner, provider->path); if (!queue_find(manager->battery_providers, NULL, provider)) { warn("Disconnection on a non-existing provider %s", provider->path); return; } queue_remove(manager->battery_providers, provider); battery_provider_free(provider); } static DBusMessage *register_battery_provider(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_battery_provider_manager *manager = user_data; const char *sender = dbus_message_get_sender(msg); DBusMessageIter args; const char *path; struct battery_provider *provider; if (!dbus_message_iter_init(msg, &args)) return btd_error_invalid_args(msg); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) return btd_error_invalid_args(msg); dbus_message_iter_get_basic(&args, &path); DBG("register battery provider path = %s", path); if (!g_str_has_prefix(path, "/")) return btd_error_invalid_args(msg); if (queue_find(manager->battery_providers, match_provider_path, path)) { return dbus_message_new_error(msg, ERROR_INTERFACE ".AlreadyExists", "Provider already exists"); } provider = battery_provider_new(conn, manager, path, sender); queue_push_head(manager->battery_providers, provider); return dbus_message_new_method_return(msg); } static DBusMessage *unregister_battery_provider(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_battery_provider_manager *manager = user_data; const char *sender = dbus_message_get_sender(msg); DBusMessageIter args; const char *path; struct battery_provider *provider; if (!dbus_message_iter_init(msg, &args)) return btd_error_invalid_args(msg); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) return btd_error_invalid_args(msg); dbus_message_iter_get_basic(&args, &path); DBG("unregister battery provider path = %s", path); provider = queue_find(manager->battery_providers, match_provider_path, path); if (!provider || strcmp(provider->owner, sender) != 0) { return dbus_message_new_error(msg, ERROR_INTERFACE ".DoesNotExist", "Provider does not exist"); } queue_remove(manager->battery_providers, provider); battery_provider_free(provider); return dbus_message_new_method_return(msg); } static const GDBusMethodTable methods[] = { { GDBUS_METHOD("RegisterBatteryProvider", GDBUS_ARGS({ "provider", "o" }), NULL, register_battery_provider) }, { GDBUS_METHOD("UnregisterBatteryProvider", GDBUS_ARGS({ "provider", "o" }), NULL, unregister_battery_provider) }, {} }; static struct btd_battery_provider_manager * manager_new(struct btd_adapter *adapter) { struct btd_battery_provider_manager *manager; DBG(""); manager = new0(struct btd_battery_provider_manager, 1); manager->adapter = adapter; manager->battery_providers = queue_new(); return manager; } static void manager_free(struct btd_battery_provider_manager *manager) { if (!manager) return; DBG(""); queue_destroy(manager->battery_providers, battery_provider_free); free(manager); } struct btd_battery_provider_manager * btd_battery_provider_manager_create(struct btd_adapter *adapter) { struct btd_battery_provider_manager *manager; if (!adapter) return NULL; manager = manager_new(adapter); if (!manager) return NULL; if (!g_dbus_register_interface(btd_get_dbus_connection(), adapter_get_path(manager->adapter), BATTERY_PROVIDER_MANAGER_INTERFACE, methods, NULL, NULL, manager, NULL)) { error("error registering " BATTERY_PROVIDER_MANAGER_INTERFACE " interface"); manager_free(manager); return NULL; } info("Battery Provider Manager created"); return manager; } void btd_battery_provider_manager_destroy( struct btd_battery_provider_manager *manager) { if (!manager) return; g_dbus_unregister_interface(btd_get_dbus_connection(), adapter_get_path(manager->adapter), BATTERY_PROVIDER_MANAGER_INTERFACE); info("Battery Provider Manager destroyed"); manager_free(manager); } bluez-5.87/src/PaxHeaders/backtrace.c0000644000000000000000000000005014015011623014516 xustar0020 atime=1783093736 20 ctime=1783094106 bluez-5.87/src/backtrace.c0000644000000000000000000000506514015011623014205 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_BACKTRACE_SUPPORT #include #include #endif #include "src/log.h" #include "src/backtrace.h" void btd_backtrace_init(void) { #ifdef HAVE_BACKTRACE_SUPPORT void *frames[1]; /* * initialize the backtracer, since the ctor calls dlopen(), which * calls malloc(), which isn't signal-safe. */ backtrace(frames, 1); #endif } void btd_backtrace(uint16_t index) { #ifdef HAVE_BACKTRACE_SUPPORT char *debuginfo_path = NULL; const Dwfl_Callbacks callbacks = { .find_debuginfo = dwfl_standard_find_debuginfo, .find_elf = dwfl_linux_proc_find_elf, .debuginfo_path = &debuginfo_path, }; Dwfl *dwfl; void *frames[48]; int n, n_ptrs; dwfl = dwfl_begin(&callbacks); if (dwfl_linux_proc_report(dwfl, getpid())) goto done; dwfl_report_end(dwfl, NULL, NULL); n_ptrs = backtrace(frames, 48); if (n_ptrs < 1) goto done; btd_error(index, "++++++++ backtrace ++++++++"); for (n = 1; n < n_ptrs; n++) { GElf_Addr addr = (uintptr_t) frames[n]; GElf_Sym sym; GElf_Word shndx; Dwfl_Module *module = dwfl_addrmodule(dwfl, addr); Dwfl_Line *line; const char *name, *modname; if (!module) { btd_error(index, "#%-2u ?? [%#" PRIx64 "]", n, addr); continue; } name = dwfl_module_addrsym(module, addr, &sym, &shndx); if (!name) { modname = dwfl_module_info(module, NULL, NULL, NULL, NULL, NULL, NULL, NULL); btd_error(index, "#%-2u ?? (%s) [%#" PRIx64 "]", n, modname, addr); continue; } line = dwfl_module_getsrc(module, addr); if (line) { int lineno; const char *src = dwfl_lineinfo(line, NULL, &lineno, NULL, NULL, NULL); if (src) { btd_error(index, "#%-2u %s+%#" PRIx64 " " "(%s:%d) [%#" PRIx64 "]", n, name, addr - sym.st_value, src, lineno, addr); continue; } } modname = dwfl_module_info(module, NULL, NULL, NULL, NULL, NULL, NULL, NULL); btd_error(index, "#%-2u %s+%#" PRIx64 " (%s) [%#" PRIx64 "]", n, name, addr - sym.st_value, modname, addr); } btd_error(index, "+++++++++++++++++++++++++++"); done: dwfl_end(dwfl); #endif } void btd_assertion_message_expr(const char *file, int line, const char *func, const char *expr) { btd_error(0xffff, "Assertion failed: (%s) %s:%d in %s", expr, file, line, func); btd_backtrace(0xffff); } bluez-5.87/src/PaxHeaders/sdpd-database.c0000644000000000000000000000005015063045254015306 xustar0020 atime=1783093742 20 ctime=1783094106 bluez-5.87/src/sdpd-database.c0000644000000000000000000001242215063045254014770 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2001-2002 Nokia Corporation * Copyright (C) 2002-2003 Maxim Krasnyansky * Copyright (C) 2002-2010 Marcel Holtmann * Copyright (C) 2002-2003 Stephen Crane * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "sdpd.h" #include "log.h" static sdp_list_t *service_db; static sdp_list_t *access_db; typedef struct { uint32_t handle; bdaddr_t device; } sdp_access_t; /* * Ordering function called when inserting a service record. * The service repository is a linked list in sorted order * and the service record handle is the sort key */ int record_sort(const void *r1, const void *r2) { const sdp_record_t *rec1 = r1; const sdp_record_t *rec2 = r2; if (!rec1 || !rec2) { error("NULL RECORD LIST FATAL"); return -1; } return rec1->handle - rec2->handle; } static int access_sort(const void *r1, const void *r2) { const sdp_access_t *rec1 = r1; const sdp_access_t *rec2 = r2; if (!rec1 || !rec2) { error("NULL RECORD LIST FATAL"); return -1; } return rec1->handle - rec2->handle; } static void access_free(void *p) { free(p); } /* * Reset the service repository by deleting its contents */ void sdp_svcdb_reset(void) { sdp_list_free(service_db, (sdp_free_func_t) sdp_record_free); service_db = NULL; sdp_list_free(access_db, access_free); access_db = NULL; } typedef struct _indexed { int sock; sdp_record_t *record; } sdp_indexed_t; static sdp_list_t *socket_index; /* * collect all services registered over this socket */ void sdp_svcdb_collect_all(int sock) { sdp_list_t *p, *q; for (p = socket_index, q = 0; p; ) { sdp_indexed_t *item = p->data; if (item->sock == sock) { sdp_list_t *next = p->next; sdp_record_remove(item->record->handle); sdp_record_free(item->record); free(item); if (q) q->next = next; else socket_index = next; free(p); p = next; } else if (item->sock > sock) return; else { q = p; p = p->next; } } } void sdp_svcdb_collect(sdp_record_t *rec) { sdp_list_t *p, *q; for (p = socket_index, q = 0; p; q = p, p = p->next) { sdp_indexed_t *item = p->data; if (rec == item->record) { free(item); if (q) q->next = p->next; else socket_index = p->next; free(p); return; } } } static int compare_indices(const void *i1, const void *i2) { const sdp_indexed_t *s1 = i1; const sdp_indexed_t *s2 = i2; return s1->sock - s2->sock; } void sdp_svcdb_set_collectable(sdp_record_t *record, int sock) { sdp_indexed_t *item = malloc(sizeof(sdp_indexed_t)); if (!item) { SDPDBG("No memory"); return; } item->sock = sock; item->record = record; socket_index = sdp_list_insert_sorted(socket_index, item, compare_indices); } /* * Add a service record to the repository */ void sdp_record_add(const bdaddr_t *device, sdp_record_t *rec) { sdp_access_t *dev; SDPDBG("Adding rec : 0x%lx", (long) rec); SDPDBG("with handle : 0x%x", rec->handle); service_db = sdp_list_insert_sorted(service_db, rec, record_sort); dev = malloc(sizeof(*dev)); if (!dev) return; bacpy(&dev->device, device); dev->handle = rec->handle; access_db = sdp_list_insert_sorted(access_db, dev, access_sort); } static sdp_list_t *record_locate(uint32_t handle) { if (service_db) { sdp_list_t *p; sdp_record_t r; r.handle = handle; p = sdp_list_find(service_db, &r, record_sort); return p; } SDPDBG("Could not find svcRec for : 0x%x", handle); return NULL; } static sdp_list_t *access_locate(uint32_t handle) { if (access_db) { sdp_list_t *p; sdp_access_t a; a.handle = handle; p = sdp_list_find(access_db, &a, access_sort); return p; } SDPDBG("Could not find access data for : 0x%x", handle); return NULL; } /* * Given a service record handle, find the record associated with it. */ sdp_record_t *sdp_record_find(uint32_t handle) { sdp_list_t *p = record_locate(handle); if (!p) { SDPDBG("Couldn't find record for : 0x%x", handle); return 0; } return p->data; } /* * Given a service record handle, remove its record from the repository */ int sdp_record_remove(uint32_t handle) { sdp_list_t *p = record_locate(handle); sdp_record_t *r; sdp_access_t *a; if (!p) { error("Remove : Couldn't find record for : 0x%x", handle); return -1; } r = p->data; if (r) service_db = sdp_list_remove(service_db, r); p = access_locate(handle); if (p == NULL || p->data == NULL) return 0; a = p->data; access_db = sdp_list_remove(access_db, a); access_free(a); return 0; } /* * Return a pointer to the linked list containing the records in sorted order */ sdp_list_t *sdp_get_record_list(void) { return service_db; } int sdp_check_access(uint32_t handle, bdaddr_t *device) { sdp_list_t *p = access_locate(handle); sdp_access_t *a; if (!p) return 1; a = p->data; if (!a) return 1; if (bacmp(&a->device, device) && bacmp(&a->device, BDADDR_ANY) && bacmp(device, BDADDR_ANY)) return 0; return 1; } uint32_t sdp_next_handle(void) { uint32_t handle = 0x10000; while (sdp_record_find(handle)) handle++; return handle; } bluez-5.87/src/PaxHeaders/service.h0000644000000000000000000000005014267331527014265 xustar0020 atime=1783093608 20 ctime=1783094106 bluez-5.87/src/service.h0000644000000000000000000000433514267331527013753 0ustar00rootroot/* 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. * * */ typedef enum { BTD_SERVICE_STATE_UNAVAILABLE, /* Not probed */ BTD_SERVICE_STATE_DISCONNECTED, BTD_SERVICE_STATE_CONNECTING, BTD_SERVICE_STATE_CONNECTED, BTD_SERVICE_STATE_DISCONNECTING, } btd_service_state_t; struct btd_service; struct btd_device; struct btd_profile; typedef void (*btd_service_state_cb) (struct btd_service *service, btd_service_state_t old_state, btd_service_state_t new_state, void *user_data); struct btd_service *btd_service_ref(struct btd_service *service); void btd_service_unref(struct btd_service *service); /* Service management functions used by the core */ struct btd_service *service_create(struct btd_device *device, struct btd_profile *profile); int service_probe(struct btd_service *service); void service_remove(struct btd_service *service); int service_accept(struct btd_service *service, bool initiator); int service_set_connecting(struct btd_service *service); /* Connection control API */ int btd_service_connect(struct btd_service *service); int btd_service_disconnect(struct btd_service *service); /* Public member access */ struct btd_device *btd_service_get_device(const struct btd_service *service); struct btd_profile *btd_service_get_profile(const struct btd_service *service); btd_service_state_t btd_service_get_state(const struct btd_service *service); int btd_service_get_error(const struct btd_service *service); bool btd_service_is_initiator(const struct btd_service *service); unsigned int btd_service_add_state_cb(btd_service_state_cb cb, void *user_data); bool btd_service_remove_state_cb(unsigned int id); void btd_service_set_allowed(struct btd_service *service, bool allowed); bool btd_service_is_allowed(struct btd_service *service); /* Functions used by profile implementation */ void btd_service_connecting_complete(struct btd_service *service, int err); void btd_service_disconnecting_complete(struct btd_service *service, int err); void btd_service_set_user_data(struct btd_service *service, void *user_data); void *btd_service_get_user_data(const struct btd_service *service); bluez-5.87/src/PaxHeaders/adv_monitor.c0000644000000000000000000000005015063045254015133 xustar0020 atime=1783093817 20 ctime=1783094106 bluez-5.87/src/adv_monitor.c0000644000000000000000000017373015063045254014627 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2020 Google LLC * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/mgmt.h" #include "adapter.h" #include "btd.h" #include "dbus-common.h" #include "device.h" #include "log.h" #include "src/error.h" #include "src/shared/mgmt.h" #include "src/shared/queue.h" #include "src/shared/timeout.h" #include "src/shared/util.h" #include "adv_monitor.h" #define ADV_MONITOR_INTERFACE "org.bluez.AdvertisementMonitor1" #define ADV_MONITOR_MGR_INTERFACE "org.bluez.AdvertisementMonitorManager1" #define ADV_MONITOR_UNSET_RSSI 127 /* dBm */ #define ADV_MONITOR_MAX_RSSI 20 /* dBm */ #define ADV_MONITOR_MIN_RSSI -127 /* dBm */ #define ADV_MONITOR_UNSET_TIMEOUT 0 /* second */ #define ADV_MONITOR_MIN_TIMEOUT 1 /* second */ #define ADV_MONITOR_MAX_TIMEOUT 300 /* second */ #define ADV_MONITOR_DEFAULT_LOW_TIMEOUT 5 /* second */ #define ADV_MONITOR_DEFAULT_HIGH_TIMEOUT 10 /* second */ #define ADV_MONITOR_UNSET_SAMPLING_PERIOD 256 /* 100 ms */ #define ADV_MONITOR_MAX_SAMPLING_PERIOD 255 /* 100 ms */ struct btd_adv_monitor_manager { struct btd_adapter *adapter; struct mgmt *mgmt; uint16_t adapter_id; uint32_t supported_features; /* MGMT_ADV_MONITOR_FEATURE_MASK_* */ uint32_t enabled_features; /* MGMT_ADV_MONITOR_FEATURE_MASK_* */ uint16_t max_num_monitors; uint8_t max_num_patterns; struct queue *apps; /* apps who registered for Adv monitoring */ struct queue *merged_patterns; }; struct adv_monitor_app { struct btd_adv_monitor_manager *manager; char *owner; char *path; DBusMessage *reg; GDBusClient *client; struct queue *monitors; }; enum monitor_type { MONITOR_TYPE_NONE, MONITOR_TYPE_OR_PATTERNS, }; enum monitor_state { MONITOR_STATE_NEW, /* New but not yet init'ed with actual values */ MONITOR_STATE_FAILED, /* Failed to be init'ed */ MONITOR_STATE_INITED, /* Init'ed but not yet sent to kernel */ MONITOR_STATE_ACTIVE, /* Accepted by kernel */ MONITOR_STATE_REMOVED, /* Removed from kernel */ MONITOR_STATE_RELEASED, /* Dbus Object removed by app */ }; enum merged_pattern_state { MERGED_PATTERN_STATE_ADDING, /* Adding pattern to kernel */ MERGED_PATTERN_STATE_REMOVING, /* Removing pattern from kernel */ MERGED_PATTERN_STATE_STABLE, /* Idle */ }; struct rssi_parameters { int8_t high_rssi; /* High RSSI threshold */ uint16_t high_rssi_timeout; /* High RSSI threshold timeout */ int8_t low_rssi; /* Low RSSI threshold */ uint16_t low_rssi_timeout; /* Low RSSI threshold timeout */ uint16_t sampling_period; /* Merge packets in the same timeslot. * Currently unimplemented in user * space. * Used only to pass data to kernel. */ }; struct adv_monitor { struct adv_monitor_app *app; GDBusProxy *proxy; char *path; enum monitor_state state; /* MONITOR_STATE_* */ struct rssi_parameters rssi; /* RSSI parameter for this monitor */ struct adv_monitor_merged_pattern *merged_pattern; struct queue *devices; /* List of adv_monitor_device objects */ }; /* Some chipsets doesn't support multiple monitors with the same pattern. * To solve that and to generally ease their task, we merge monitors with the * same pattern, so those monitors will only be sent once to the kernel. */ struct adv_monitor_merged_pattern { struct btd_adv_monitor_manager *manager; uint16_t monitor_handle; /* Kernel Monitor Handle */ struct rssi_parameters rssi; /* Merged RSSI parameter for |monitors|, * this will be sent to the kernel. */ struct queue *monitors; /* List of adv_monitor objects which * have this pattern */ enum monitor_type type; /* MONITOR_TYPE_* */ struct queue *patterns; /* List of bt_ad_pattern objects */ enum merged_pattern_state current_state; /* MERGED_PATTERN_STATE_* */ enum merged_pattern_state next_state; /* MERGED_PATTERN_STATE_* */ }; /* Some data like last_seen, timer/timeout values need to be maintained * per device. struct adv_monitor_device maintains such data. */ struct adv_monitor_device { struct adv_monitor *monitor; struct btd_device *device; time_t high_rssi_first_seen; /* Start time when RSSI climbs above * the high RSSI threshold */ time_t low_rssi_first_seen; /* Start time when RSSI drops below * the low RSSI threshold */ time_t last_seen; /* Time when last Adv was received */ bool found; /* State of the device - lost/found */ unsigned int lost_timer; /* Timer to track if the device goes * offline/out-of-range */ }; struct app_match_data { const char *owner; const char *path; }; struct adv_content_filter_info { struct bt_ad *ad; struct queue *matched_monitors; /* List of matched monitors */ }; struct adv_rssi_filter_info { struct btd_device *device; int8_t rssi; }; struct monitored_device_info { uint16_t monitor_handle; /* Kernel Monitor Handle */ struct btd_device *device; }; static void monitor_device_free(void *data); static void adv_monitor_filter_rssi(struct adv_monitor *monitor, struct btd_device *device, int8_t rssi); static void merged_pattern_send_add( struct adv_monitor_merged_pattern *merged_pattern); static void merged_pattern_send_remove( struct adv_monitor_merged_pattern *merged_pattern); const struct adv_monitor_type { enum monitor_type type; const char *name; } supported_types[] = { { MONITOR_TYPE_OR_PATTERNS, "or_patterns" }, { }, }; static void rssi_unset(struct rssi_parameters *rssi) { rssi->high_rssi = ADV_MONITOR_UNSET_RSSI; rssi->high_rssi_timeout = ADV_MONITOR_UNSET_TIMEOUT; rssi->low_rssi = ADV_MONITOR_UNSET_RSSI; rssi->low_rssi_timeout = ADV_MONITOR_UNSET_TIMEOUT; rssi->sampling_period = ADV_MONITOR_UNSET_SAMPLING_PERIOD; } static bool rssi_is_unset(const struct rssi_parameters *rssi) { return rssi->high_rssi == ADV_MONITOR_UNSET_RSSI && rssi->low_rssi == ADV_MONITOR_UNSET_RSSI && rssi->high_rssi_timeout == ADV_MONITOR_UNSET_TIMEOUT && rssi->low_rssi_timeout == ADV_MONITOR_UNSET_TIMEOUT && rssi->sampling_period == ADV_MONITOR_UNSET_SAMPLING_PERIOD; } /* Replies to an app's D-Bus message and unref it */ static void app_reply_msg(struct adv_monitor_app *app, DBusMessage *reply) { if (!app || !app->reg || !reply) return; g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(app->reg); app->reg = NULL; } /* Frees a pattern */ static void pattern_free(void *data) { struct bt_ad_pattern *pattern = data; free(pattern); } static void merged_pattern_free(void *data) { struct adv_monitor_merged_pattern *merged_pattern = data; queue_destroy(merged_pattern->patterns, pattern_free); queue_destroy(merged_pattern->monitors, NULL); if (merged_pattern->manager) queue_remove(merged_pattern->manager->merged_patterns, merged_pattern); free(merged_pattern); } /* Returns the smaller of the two integers |a| and |b| which is not equal to the * |unset| value. If both are unset, return unset. */ static int get_smaller_not_unset(int a, int b, int unset) { if (a == unset) return b; if (b == unset) return a; return a < b ? a : b; } /* Merges two RSSI parameters, return the result. The result is chosen to be * whichever is more lenient of the two inputs, so we can pass that to the * kernel and still do additional filtering in the user space without loss of * information while still receiving benefit from offloading some filtering to * the hardware. * It is allowed for |a|, |b|, and |merged| to point to the same object. */ static void merge_rssi(const struct rssi_parameters *a, const struct rssi_parameters *b, struct rssi_parameters *merged) { /* For low rssi, low_timeout, and high_rssi, choose the minimum of the * two values. Filtering the higher values is done on userspace. */ merged->low_rssi = get_smaller_not_unset(a->low_rssi, b->low_rssi, ADV_MONITOR_UNSET_RSSI); merged->high_rssi = get_smaller_not_unset(a->high_rssi, b->high_rssi, ADV_MONITOR_UNSET_RSSI); merged->low_rssi_timeout = get_smaller_not_unset(a->low_rssi_timeout, b->low_rssi_timeout, ADV_MONITOR_UNSET_TIMEOUT); /* High timeout doesn't matter for now, it will be zeroed when it is * forwarded to kernel anyway. */ merged->high_rssi_timeout = 0; /* Sampling period is not implemented yet in userspace. There is no * good value if the two values are different, so just choose 0 for * always reporting, to avoid missing packets. */ if (a->sampling_period != b->sampling_period) merged->sampling_period = 0; else merged->sampling_period = a->sampling_period; } /* Two merged_pattern are considered equal if all the following are true: * (1) both has the same monitor_type * (2) both has exactly the same pattern in the same order * Therefore, patterns A+B and B+A are considered different, as well as patterns * A and A+A. This shouldn't cause any issue, but solving this issue is a * potential improvement. */ static bool merged_pattern_is_equal(const void *data, const void *match_data) { const struct adv_monitor_merged_pattern *a = data; const struct adv_monitor_merged_pattern *b = match_data; const struct queue_entry *a_entry, *b_entry; struct bt_ad_pattern *a_data, *b_data; if (a->type != b->type) return false; if (queue_length(a->patterns) != queue_length(b->patterns)) return false; a_entry = queue_get_entries(a->patterns); b_entry = queue_get_entries(b->patterns); while (a_entry) { a_data = a_entry->data; b_data = b_entry->data; if (a_data->type != b_data->type || a_data->offset != b_data->offset || a_data->len != b_data->len || memcmp(a_data->data, b_data->data, a_data->len) != 0) return false; a_entry = a_entry->next; b_entry = b_entry->next; } return true; } static char *get_merged_pattern_state_name(enum merged_pattern_state state) { switch (state) { case MERGED_PATTERN_STATE_ADDING: return "Adding"; case MERGED_PATTERN_STATE_REMOVING: return "Removing"; case MERGED_PATTERN_STATE_STABLE: return "Stable"; } return NULL; } /* Adds a new merged pattern */ static void merged_pattern_add( struct adv_monitor_merged_pattern *merged_pattern) { /* This is only called when no merged_pattern found. Therefore, the * state must be stable. */ if (merged_pattern->current_state != MERGED_PATTERN_STATE_STABLE) { btd_error(merged_pattern->manager->adapter_id, "Add merged_pattern request when state is not stable"); return; } merged_pattern->current_state = MERGED_PATTERN_STATE_ADDING; merged_pattern_send_add(merged_pattern); DBG("Monitor state: %s -> %s", get_merged_pattern_state_name(merged_pattern->current_state), get_merged_pattern_state_name(merged_pattern->next_state)); } /* Removes merged pattern, or queues for removal if busy */ static void merged_pattern_remove( struct adv_monitor_merged_pattern *merged_pattern) { rssi_unset(&merged_pattern->rssi); /* If we currently are removing, cancel subsequent ADD command if any */ if (merged_pattern->current_state == MERGED_PATTERN_STATE_REMOVING) { merged_pattern->next_state = MERGED_PATTERN_STATE_STABLE; goto print_state; } /* If stable, we can proceed with removal right away */ if (merged_pattern->current_state == MERGED_PATTERN_STATE_STABLE) { merged_pattern->current_state = MERGED_PATTERN_STATE_REMOVING; merged_pattern_send_remove(merged_pattern); } else { /* otherwise queue the removal */ merged_pattern->next_state = MERGED_PATTERN_STATE_REMOVING; } print_state: DBG("Monitor state: %s -> %s", get_merged_pattern_state_name(merged_pattern->current_state), get_merged_pattern_state_name(merged_pattern->next_state)); } /* Replaces (removes and re-adds) merged pattern, or queues it if busy */ static void merged_pattern_replace( struct adv_monitor_merged_pattern *merged_pattern, const struct rssi_parameters *rssi) { /* If the RSSI are the same then nothing needs to be done, except on * the case where pattern is being removed. In that case, we need to * re-add the pattern. * high_rssi_timeout is purposely left out in the comparison since * the value is ignored upon submission to kernel. */ if (merged_pattern->rssi.high_rssi == rssi->high_rssi && merged_pattern->rssi.low_rssi == rssi->low_rssi && merged_pattern->rssi.low_rssi_timeout == rssi->low_rssi_timeout && merged_pattern->rssi.sampling_period == rssi->sampling_period && merged_pattern->current_state != MERGED_PATTERN_STATE_REMOVING && merged_pattern->next_state != MERGED_PATTERN_STATE_REMOVING) return; merged_pattern->rssi = *rssi; /* If stable, we can proceed with replacement. */ if (merged_pattern->current_state == MERGED_PATTERN_STATE_STABLE) { /* Replacement is done by first removing, then re-adding */ merged_pattern->current_state = MERGED_PATTERN_STATE_REMOVING; merged_pattern->next_state = MERGED_PATTERN_STATE_ADDING; merged_pattern_send_remove(merged_pattern); } else { /* otherwise queue the replacement */ merged_pattern->next_state = MERGED_PATTERN_STATE_ADDING; } DBG("Monitor state: %s -> %s", get_merged_pattern_state_name(merged_pattern->current_state), get_merged_pattern_state_name(merged_pattern->next_state)); } /* Current_state of merged_pattern is done, proceed to the next_state */ static void merged_pattern_process_next_step( struct adv_monitor_merged_pattern *mp) { if (mp->current_state == MERGED_PATTERN_STATE_STABLE) { btd_error(mp->manager->adapter_id, "Merged pattern invalid current state"); return; } if (mp->current_state == MERGED_PATTERN_STATE_REMOVING) { /* We might need to follow-up with re-adding the pattern */ if (mp->next_state == MERGED_PATTERN_STATE_ADDING) { mp->current_state = MERGED_PATTERN_STATE_ADDING; mp->next_state = MERGED_PATTERN_STATE_STABLE; merged_pattern_send_add(mp); goto print_state; } /* We should never end up with remove-remove sequence */ if (mp->next_state == MERGED_PATTERN_STATE_REMOVING) btd_error(mp->manager->adapter_id, "Merged pattern can't be removed again"); /* No more operations */ mp->current_state = MERGED_PATTERN_STATE_STABLE; mp->next_state = MERGED_PATTERN_STATE_STABLE; goto print_state; } /* current_state == MERGED_PATTERN_STATE_ADDING */ if (mp->next_state == MERGED_PATTERN_STATE_REMOVING) { mp->current_state = MERGED_PATTERN_STATE_REMOVING; mp->next_state = MERGED_PATTERN_STATE_STABLE; merged_pattern_send_remove(mp); goto print_state; } else if (mp->next_state == MERGED_PATTERN_STATE_ADDING) { /* To re-add a just added pattern, we need to remove it first */ mp->current_state = MERGED_PATTERN_STATE_REMOVING; mp->next_state = MERGED_PATTERN_STATE_ADDING; merged_pattern_send_remove(mp); goto print_state; } /* No more operations */ mp->current_state = MERGED_PATTERN_STATE_STABLE; mp->next_state = MERGED_PATTERN_STATE_STABLE; print_state: DBG("Monitor state: %s -> %s", get_merged_pattern_state_name(mp->current_state), get_merged_pattern_state_name(mp->next_state)); } /* Frees a monitor object */ static void monitor_free(struct adv_monitor *monitor) { g_dbus_proxy_unref(monitor->proxy); g_free(monitor->path); queue_destroy(monitor->devices, monitor_device_free); monitor->devices = NULL; free(monitor); } /* Calls Release() method of the remote Adv Monitor */ static void monitor_release(struct adv_monitor *monitor) { /* Release() method on a monitor can be called when - * 1. monitor initialization failed * 2. app calls UnregisterMonitor and monitors held by app are released, * it may or may not be activated at this point * 3. monitor is removed by kernel */ if (monitor->state != MONITOR_STATE_FAILED && monitor->state != MONITOR_STATE_INITED && monitor->state != MONITOR_STATE_ACTIVE && monitor->state != MONITOR_STATE_REMOVED) { return; } DBG("Calling Release() on Adv Monitor of owner %s at path %s", monitor->app->owner, monitor->path); g_dbus_proxy_method_call(monitor->proxy, "Release", NULL, NULL, NULL, NULL); } /* Removes monitor from the merged_pattern. This would result in removing it * from the kernel if there is only one such monitor with that pattern. */ static void monitor_remove(struct adv_monitor *monitor) { struct adv_monitor_app *app = monitor->app; uint16_t adapter_id = app->manager->adapter_id; struct adv_monitor_merged_pattern *merged_pattern; const struct queue_entry *e; struct rssi_parameters rssi; /* Monitor from kernel can be removed when - * 1. monitor object is deleted by app - may or may not be activated * 2. app is destroyed and monitors held by app are marked as released */ if (monitor->state != MONITOR_STATE_INITED && monitor->state != MONITOR_STATE_ACTIVE && monitor->state != MONITOR_STATE_RELEASED) { return; } monitor->state = MONITOR_STATE_REMOVED; if (!monitor->merged_pattern) { btd_error(adapter_id, "Merged_pattern not found when removing monitor"); return; } merged_pattern = monitor->merged_pattern; monitor->merged_pattern = NULL; queue_remove(merged_pattern->monitors, monitor); /* No more monitors - just remove the pattern entirely */ if (queue_length(merged_pattern->monitors) == 0) { merged_pattern_remove(merged_pattern); return; } /* Calculate the merge result of the RSSIs of the monitors with the * same pattern, minus the monitor being removed. */ rssi_unset(&rssi); for (e = queue_get_entries(merged_pattern->monitors); e; e = e->next) { struct adv_monitor *m = e->data; merge_rssi(&rssi, &m->rssi, &rssi); } merged_pattern_replace(merged_pattern, &rssi); } /* Destroys monitor object */ static void monitor_destroy(void *data) { struct adv_monitor *monitor = data; if (!monitor) return; queue_remove(monitor->app->monitors, monitor); monitor_release(monitor); monitor_remove(monitor); monitor_free(monitor); } /* Destroys an app object along with related D-Bus handlers */ static void app_destroy(void *data) { struct adv_monitor_app *app = data; if (!app) return; DBG("Destroy Adv Monitor app %s at path %s", app->owner, app->path); queue_destroy(app->monitors, monitor_destroy); if (app->reg) { app_reply_msg(app, btd_error_failed(app->reg, "Adv Monitor app destroyed")); } if (app->client) { g_dbus_client_set_disconnect_watch(app->client, NULL, NULL); g_dbus_client_set_proxy_handlers(app->client, NULL, NULL, NULL, NULL); g_dbus_client_set_ready_watch(app->client, NULL, NULL); g_dbus_client_unref(app->client); } g_free(app->owner); g_free(app->path); free(app); } /* Updates monitor state to 'released' */ static void monitor_state_released(void *data, void *user_data) { struct adv_monitor *monitor = data; if (!monitor || (monitor->state != MONITOR_STATE_INITED && monitor->state != MONITOR_STATE_ACTIVE)) return; monitor->state = MONITOR_STATE_RELEASED; } /* Updates monitor state to 'active' */ static void monitor_state_active(void *data, void *user_data) { struct adv_monitor *monitor = data; if (!monitor || monitor->state != MONITOR_STATE_INITED) return; monitor->state = MONITOR_STATE_ACTIVE; DBG("Calling Activate() on Adv Monitor of owner %s at path %s", monitor->app->owner, monitor->path); g_dbus_proxy_method_call(monitor->proxy, "Activate", NULL, NULL, NULL, NULL); } /* Handles a D-Bus disconnection event of an app */ static void app_disconnect_cb(DBusConnection *conn, void *user_data) { struct adv_monitor_app *app = user_data; if (!app) { error("Unexpected NULL app object upon app disconnect"); return; } btd_info(app->manager->adapter_id, "Adv Monitor app %s disconnected from D-Bus", app->owner); if (queue_remove(app->manager->apps, app)) { queue_foreach(app->monitors, monitor_state_released, NULL); app_destroy(app); } } /* Handles the ready signal of Adv Monitor app */ static void app_ready_cb(GDBusClient *client, void *user_data) { struct adv_monitor_app *app = user_data; uint16_t adapter_id = app->manager->adapter_id; btd_info(adapter_id, "Path %s reserved for Adv Monitor app %s", app->path, app->owner); app_reply_msg(app, dbus_message_new_method_return(app->reg)); } /* Allocates an Adv Monitor */ static struct adv_monitor *monitor_new(struct adv_monitor_app *app, GDBusProxy *proxy) { struct adv_monitor *monitor; if (!app || !proxy) return NULL; monitor = new0(struct adv_monitor, 1); if (!monitor) return NULL; monitor->app = app; monitor->proxy = g_dbus_proxy_ref(proxy); monitor->path = g_strdup(g_dbus_proxy_get_path(proxy)); monitor->state = MONITOR_STATE_NEW; rssi_unset(&monitor->rssi); monitor->devices = queue_new(); return monitor; } /* Matches a monitor based on its D-Bus path */ static bool monitor_match(const void *a, const void *b) { const GDBusProxy *proxy = b; const struct adv_monitor *monitor = a; if (!proxy || !monitor) return false; if (g_strcmp0(g_dbus_proxy_get_path(proxy), monitor->path) != 0) return false; return true; } /* Retrieves Type from the remote Adv Monitor object, verifies the value and * update the local Adv Monitor */ static bool parse_monitor_type(struct adv_monitor *monitor, const char *path) { DBusMessageIter iter; const struct adv_monitor_type *t; const char *type_str; uint16_t adapter_id = monitor->app->manager->adapter_id; if (!g_dbus_proxy_get_property(monitor->proxy, "Type", &iter)) { btd_error(adapter_id, "Failed to retrieve property Type from the " "Adv Monitor at path %s", path); return false; } if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) goto failed; dbus_message_iter_get_basic(&iter, &type_str); for (t = supported_types; t->name; t++) { if (strcmp(t->name, type_str) == 0) { monitor->merged_pattern->type = t->type; return true; } } failed: btd_error(adapter_id, "Invalid argument of property Type of the Adv Monitor " "at path %s", path); return false; } /* Retrieves RSSI thresholds and timeouts from the remote Adv Monitor object, * verifies the values and update the local Adv Monitor */ static bool parse_rssi_and_timeout(struct adv_monitor *monitor, const char *path) { DBusMessageIter iter; GDBusProxy *proxy = monitor->proxy; int16_t h_rssi = ADV_MONITOR_UNSET_RSSI; int16_t l_rssi = ADV_MONITOR_UNSET_RSSI; uint16_t h_rssi_timeout = ADV_MONITOR_UNSET_TIMEOUT; uint16_t l_rssi_timeout = ADV_MONITOR_UNSET_TIMEOUT; uint16_t sampling_period = ADV_MONITOR_UNSET_SAMPLING_PERIOD; uint16_t adapter_id = monitor->app->manager->adapter_id; /* Extract RSSIHighThreshold */ if (g_dbus_proxy_get_property(proxy, "RSSIHighThreshold", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT16) goto failed; dbus_message_iter_get_basic(&iter, &h_rssi); } /* Extract RSSIHighTimeout */ if (g_dbus_proxy_get_property(proxy, "RSSIHighTimeout", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT16) goto failed; dbus_message_iter_get_basic(&iter, &h_rssi_timeout); } /* Extract RSSILowThreshold */ if (g_dbus_proxy_get_property(proxy, "RSSILowThreshold", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT16) goto failed; dbus_message_iter_get_basic(&iter, &l_rssi); } /* Extract RSSILowTimeout */ if (g_dbus_proxy_get_property(proxy, "RSSILowTimeout", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT16) goto failed; dbus_message_iter_get_basic(&iter, &l_rssi_timeout); } /* Extract RSSISamplingPeriod */ if (g_dbus_proxy_get_property(proxy, "RSSISamplingPeriod", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT16) goto failed; dbus_message_iter_get_basic(&iter, &sampling_period); } /* Verify the values of RSSIs and their timeouts. All fields should be * either set to the unset values or are set within valid ranges. * If the fields are only partially set, we would try our best to fill * in with some sane values. */ if (h_rssi == ADV_MONITOR_UNSET_RSSI && l_rssi == ADV_MONITOR_UNSET_RSSI && h_rssi_timeout == ADV_MONITOR_UNSET_TIMEOUT && l_rssi_timeout == ADV_MONITOR_UNSET_TIMEOUT && sampling_period == ADV_MONITOR_UNSET_SAMPLING_PERIOD) { goto done; } if (l_rssi == ADV_MONITOR_UNSET_RSSI) l_rssi = ADV_MONITOR_MIN_RSSI; if (h_rssi == ADV_MONITOR_UNSET_RSSI) h_rssi = l_rssi; if (l_rssi_timeout == ADV_MONITOR_UNSET_TIMEOUT) l_rssi_timeout = ADV_MONITOR_DEFAULT_LOW_TIMEOUT; if (h_rssi_timeout == ADV_MONITOR_UNSET_TIMEOUT) h_rssi_timeout = ADV_MONITOR_DEFAULT_HIGH_TIMEOUT; if (sampling_period == ADV_MONITOR_UNSET_SAMPLING_PERIOD) sampling_period = btd_opts.advmon.rssi_sampling_period; if (h_rssi < ADV_MONITOR_MIN_RSSI || h_rssi > ADV_MONITOR_MAX_RSSI || l_rssi < ADV_MONITOR_MIN_RSSI || l_rssi > ADV_MONITOR_MAX_RSSI || h_rssi < l_rssi) { goto failed; } if (h_rssi_timeout < ADV_MONITOR_MIN_TIMEOUT || h_rssi_timeout > ADV_MONITOR_MAX_TIMEOUT || l_rssi_timeout < ADV_MONITOR_MIN_TIMEOUT || l_rssi_timeout > ADV_MONITOR_MAX_TIMEOUT) { goto failed; } if (sampling_period > ADV_MONITOR_MAX_SAMPLING_PERIOD) goto failed; monitor->rssi.high_rssi = h_rssi; monitor->rssi.low_rssi = l_rssi; monitor->rssi.high_rssi_timeout = h_rssi_timeout; monitor->rssi.low_rssi_timeout = l_rssi_timeout; monitor->rssi.sampling_period = sampling_period; done: DBG("Adv Monitor at %s initiated with high RSSI threshold %d, high " "RSSI threshold timeout %d, low RSSI threshold %d, low RSSI " "threshold timeout %d, sampling period %d", path, monitor->rssi.high_rssi, monitor->rssi.high_rssi_timeout, monitor->rssi.low_rssi, monitor->rssi.low_rssi_timeout, monitor->rssi.sampling_period); monitor->merged_pattern->rssi = monitor->rssi; return true; failed: btd_error(adapter_id, "Invalid argument of RSSI thresholds and timeouts " "of the Adv Monitor at path %s", path); return false; } /* Retrieves Patterns from the remote Adv Monitor object, verifies the values * and update the local Adv Monitor */ static bool parse_patterns(struct adv_monitor *monitor, const char *path) { DBusMessageIter array, array_iter; uint16_t adapter_id = monitor->app->manager->adapter_id; if (!g_dbus_proxy_get_property(monitor->proxy, "Patterns", &array)) { btd_error(adapter_id, "Failed to retrieve property Patterns from the " "Adv Monitor at path %s", path); return false; } if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY || dbus_message_iter_get_element_type(&array) != DBUS_TYPE_STRUCT) { goto failed; } monitor->merged_pattern->patterns = queue_new(); dbus_message_iter_recurse(&array, &array_iter); while (dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRUCT) { int value_len; uint8_t *value; uint8_t offset, ad_type; struct bt_ad_pattern *pattern; DBusMessageIter struct_iter, value_iter; dbus_message_iter_recurse(&array_iter, &struct_iter); // Extract start position if (dbus_message_iter_get_arg_type(&struct_iter) != DBUS_TYPE_BYTE) { goto failed; } dbus_message_iter_get_basic(&struct_iter, &offset); if (!dbus_message_iter_next(&struct_iter)) goto failed; // Extract AD data type if (dbus_message_iter_get_arg_type(&struct_iter) != DBUS_TYPE_BYTE) { goto failed; } dbus_message_iter_get_basic(&struct_iter, &ad_type); if (!dbus_message_iter_next(&struct_iter)) goto failed; // Extract value of a pattern if (dbus_message_iter_get_arg_type(&struct_iter) != DBUS_TYPE_ARRAY) { goto failed; } dbus_message_iter_recurse(&struct_iter, &value_iter); dbus_message_iter_get_fixed_array(&value_iter, &value, &value_len); pattern = bt_ad_pattern_new(ad_type, offset, value_len, value); if (!pattern) goto failed; queue_push_tail(monitor->merged_pattern->patterns, pattern); dbus_message_iter_next(&array_iter); } /* There must be at least one pattern. */ if (queue_isempty(monitor->merged_pattern->patterns)) goto failed; return true; failed: btd_error(adapter_id, "Invalid argument of property Patterns of the " "Adv Monitor at path %s", path); return false; } /* Processes the content of the remote Adv Monitor */ static bool monitor_process(struct adv_monitor *monitor) { const char *path = g_dbus_proxy_get_path(monitor->proxy); monitor->state = MONITOR_STATE_FAILED; monitor->merged_pattern = malloc0(sizeof(*monitor->merged_pattern)); monitor->merged_pattern->current_state = MERGED_PATTERN_STATE_STABLE; monitor->merged_pattern->next_state = MERGED_PATTERN_STATE_STABLE; if (!parse_monitor_type(monitor, path)) goto fail; if (!parse_rssi_and_timeout(monitor, path)) goto fail; if (monitor->merged_pattern->type != MONITOR_TYPE_OR_PATTERNS || !parse_patterns(monitor, path)) goto fail; monitor->state = MONITOR_STATE_INITED; monitor->merged_pattern->monitors = queue_new(); queue_push_tail(monitor->merged_pattern->monitors, monitor); return true; fail: merged_pattern_free(monitor->merged_pattern); monitor->merged_pattern = NULL; return false; } static void merged_pattern_destroy_monitors( struct adv_monitor_merged_pattern *merged_pattern) { const struct queue_entry *e; for (e = queue_get_entries(merged_pattern->monitors); e; e = e->next) { struct adv_monitor *monitor = e->data; monitor->merged_pattern = NULL; monitor_destroy(monitor); } } /* Handles the callback of Remove Adv Monitor command */ static void remove_adv_monitor_cb(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_remove_adv_monitor *rp = param; struct adv_monitor_merged_pattern *merged_pattern = user_data; if (status != MGMT_STATUS_SUCCESS || !param) { error("Failed to Remove Adv Monitor with status 0x%02x", status); goto fail; } if (length < sizeof(*rp)) { error("Wrong size of Remove Adv Monitor response"); goto fail; } DBG("Adv monitor with handle:0x%04x removed from kernel", le16_to_cpu(rp->monitor_handle)); merged_pattern_process_next_step(merged_pattern); if (merged_pattern->current_state == MERGED_PATTERN_STATE_STABLE) merged_pattern_free(merged_pattern); return; fail: merged_pattern_destroy_monitors(merged_pattern); merged_pattern_free(merged_pattern); } /* sends MGMT_OP_REMOVE_ADV_MONITOR */ static void merged_pattern_send_remove( struct adv_monitor_merged_pattern *merged_pattern) { struct mgmt_cp_remove_adv_monitor cp; struct btd_adv_monitor_manager *manager = merged_pattern->manager; cp.monitor_handle = cpu_to_le16(merged_pattern->monitor_handle); if (!mgmt_send(manager->mgmt, MGMT_OP_REMOVE_ADV_MONITOR, manager->adapter_id, sizeof(cp), &cp, remove_adv_monitor_cb, merged_pattern, NULL)) { btd_error(merged_pattern->manager->adapter_id, "Unable to send Remove Advt Monitor command"); } } /* Handles the callback of Add Adv Patterns Monitor command */ static void add_adv_patterns_monitor_cb(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_add_adv_patterns_monitor *rp = param; struct adv_monitor_merged_pattern *merged_pattern = user_data; uint16_t adapter_id = merged_pattern->manager->adapter_id; if (status != MGMT_STATUS_SUCCESS || !param) { btd_error(adapter_id, "Failed to Add Adv Patterns Monitor with status" " 0x%02x", status); goto fail; } if (length < sizeof(*rp)) { btd_error(adapter_id, "Wrong size of Add Adv Patterns Monitor " "response"); goto fail; } merged_pattern->monitor_handle = le16_to_cpu(rp->monitor_handle); DBG("Adv monitor with handle:0x%04x added", merged_pattern->monitor_handle); merged_pattern_process_next_step(merged_pattern); if (merged_pattern->current_state != MERGED_PATTERN_STATE_STABLE) return; queue_foreach(merged_pattern->monitors, monitor_state_active, NULL); return; fail: merged_pattern_destroy_monitors(merged_pattern); merged_pattern_free(merged_pattern); } /* sends MGMT_OP_ADD_ADV_PATTERNS_MONITOR */ static bool merged_pattern_send_add_pattern( struct adv_monitor_merged_pattern *merged_pattern) { struct mgmt_cp_add_adv_monitor *cp = NULL; uint8_t pattern_count, cp_len; const struct queue_entry *e; bool success = true; pattern_count = queue_length(merged_pattern->patterns); cp_len = sizeof(*cp) + pattern_count * sizeof(struct mgmt_adv_pattern); cp = malloc0(cp_len); if (!cp) return false; for (e = queue_get_entries(merged_pattern->patterns); e; e = e->next) { struct bt_ad_pattern *pattern = e->data; memcpy(&cp->patterns[cp->pattern_count++], pattern, sizeof(*pattern)); } if (!mgmt_send(merged_pattern->manager->mgmt, MGMT_OP_ADD_ADV_PATTERNS_MONITOR, merged_pattern->manager->adapter_id, cp_len, cp, add_adv_patterns_monitor_cb, merged_pattern, NULL)) { error("Unable to send Add Adv Patterns Monitor command"); success = false; } free(cp); return success; } /* sends MGMT_OP_ADD_ADV_PATTERNS_MONITOR_RSSI */ static bool merged_pattern_send_add_pattern_rssi( struct adv_monitor_merged_pattern *merged_pattern) { struct mgmt_cp_add_adv_patterns_monitor_rssi *cp = NULL; uint8_t pattern_count, cp_len; const struct queue_entry *e; bool success = true; pattern_count = queue_length(merged_pattern->patterns); cp_len = sizeof(*cp) + pattern_count * sizeof(struct mgmt_adv_pattern); cp = malloc0(cp_len); if (!cp) return false; cp->rssi.high_threshold = merged_pattern->rssi.high_rssi; /* High threshold timeout is unsupported in kernel. Value must be 0. */ cp->rssi.high_threshold_timeout = 0; cp->rssi.low_threshold = merged_pattern->rssi.low_rssi; cp->rssi.low_threshold_timeout = htobs(merged_pattern->rssi.low_rssi_timeout); cp->rssi.sampling_period = merged_pattern->rssi.sampling_period; for (e = queue_get_entries(merged_pattern->patterns); e; e = e->next) { struct bt_ad_pattern *pattern = e->data; memcpy(&cp->patterns[cp->pattern_count++], pattern, sizeof(*pattern)); } if (!mgmt_send(merged_pattern->manager->mgmt, MGMT_OP_ADD_ADV_PATTERNS_MONITOR_RSSI, merged_pattern->manager->adapter_id, cp_len, cp, add_adv_patterns_monitor_cb, merged_pattern, NULL)) { error("Unable to send Add Adv Patterns Monitor RSSI command"); success = false; } free(cp); return success; } /* Sends mgmt command to kernel for adding monitor */ static void merged_pattern_send_add( struct adv_monitor_merged_pattern *merged_pattern) { if (rssi_is_unset(&merged_pattern->rssi)) merged_pattern_send_add_pattern(merged_pattern); else merged_pattern_send_add_pattern_rssi(merged_pattern); } /* Handles an Adv Monitor D-Bus proxy added event */ static void monitor_proxy_added_cb(GDBusProxy *proxy, void *user_data) { struct adv_monitor *monitor; struct adv_monitor_app *app = user_data; struct adv_monitor_merged_pattern *existing_pattern; uint16_t adapter_id = app->manager->adapter_id; const char *path = g_dbus_proxy_get_path(proxy); const char *iface = g_dbus_proxy_get_interface(proxy); struct rssi_parameters rssi; if (strcmp(iface, ADV_MONITOR_INTERFACE) != 0 || !g_str_has_prefix(path, app->path)) { return; } if (queue_find(app->monitors, monitor_match, proxy)) { btd_error(adapter_id, "Adv Monitor proxy already exists with path %s", path); return; } monitor = monitor_new(app, proxy); if (!monitor) { btd_error(adapter_id, "Failed to allocate an Adv Monitor for the " "object at %s", path); return; } if (!monitor_process(monitor)) { monitor_destroy(monitor); DBG("Adv Monitor at path %s released due to invalid content", path); return; } queue_push_tail(app->monitors, monitor); existing_pattern = queue_find(monitor->app->manager->merged_patterns, merged_pattern_is_equal, monitor->merged_pattern); if (!existing_pattern) { monitor->merged_pattern->manager = monitor->app->manager; queue_push_tail(monitor->app->manager->merged_patterns, monitor->merged_pattern); merged_pattern_add(monitor->merged_pattern); } else { /* Since there is a matching pattern, abandon the one we have */ merged_pattern_free(monitor->merged_pattern); monitor->merged_pattern = existing_pattern; queue_push_tail(existing_pattern->monitors, monitor); merge_rssi(&existing_pattern->rssi, &monitor->rssi, &rssi); merged_pattern_replace(existing_pattern, &rssi); /* Stable means request is not forwarded to kernel */ if (existing_pattern->current_state == MERGED_PATTERN_STATE_STABLE) monitor_state_active(monitor, NULL); } DBG("Adv Monitor allocated for the object at path %s", path); } /* Handles the removal of an Adv Monitor D-Bus proxy */ static void monitor_proxy_removed_cb(GDBusProxy *proxy, void *user_data) { struct adv_monitor *monitor; struct adv_monitor_app *app = user_data; monitor = queue_find(app->monitors, monitor_match, proxy); if (!monitor) return; DBG("Adv Monitor removed in state %02x with path %s", monitor->state, monitor->path); monitor_state_released(monitor, NULL); monitor_destroy(monitor); } /* Creates an app object, initiates it and sets D-Bus event handlers */ static struct adv_monitor_app *app_create(DBusConnection *conn, DBusMessage *msg, const char *sender, const char *path, struct btd_adv_monitor_manager *manager) { struct adv_monitor_app *app; if (!path || !sender || !manager) return NULL; app = new0(struct adv_monitor_app, 1); if (!app) return NULL; app->owner = g_strdup(sender); app->path = g_strdup(path); app->manager = manager; app->reg = NULL; app->client = g_dbus_client_new_full(conn, sender, path, path); if (!app->client) { app_destroy(app); return NULL; } app->monitors = queue_new(); app->reg = dbus_message_ref(msg); g_dbus_client_set_disconnect_watch(app->client, app_disconnect_cb, app); /* Note that any property changes on a monitor object would not affect * the content of the corresponding monitor. */ g_dbus_client_set_proxy_handlers(app->client, monitor_proxy_added_cb, monitor_proxy_removed_cb, NULL, app); g_dbus_client_set_ready_watch(app->client, app_ready_cb, app); return app; } /* Matches an app based on its owner and path */ static bool app_match(const void *a, const void *b) { const struct adv_monitor_app *app = a; const struct app_match_data *match = b; if (match->owner && strcmp(app->owner, match->owner)) return false; if (match->path && strcmp(app->path, match->path)) return false; return true; } /* Handles a RegisterMonitor D-Bus call */ static DBusMessage *register_monitor(DBusConnection *conn, DBusMessage *msg, void *user_data) { DBusMessageIter args; struct app_match_data match; struct adv_monitor_app *app; struct btd_adv_monitor_manager *manager = user_data; if (!dbus_message_iter_init(msg, &args)) return btd_error_invalid_args(msg); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) return btd_error_invalid_args(msg); dbus_message_iter_get_basic(&args, &match.path); if (!strlen(match.path) || !g_str_has_prefix(match.path, "/")) return btd_error_invalid_args(msg); match.owner = dbus_message_get_sender(msg); if (queue_find(manager->apps, app_match, &match)) return btd_error_already_exists(msg); app = app_create(conn, msg, match.owner, match.path, manager); if (!app) { btd_error(manager->adapter_id, "Failed to reserve %s for Adv Monitor app %s", match.path, match.owner); return btd_error_failed(msg, "Failed to create Adv Monitor app"); } queue_push_tail(manager->apps, app); return NULL; } /* Handles UnregisterMonitor D-Bus call */ static DBusMessage *unregister_monitor(DBusConnection *conn, DBusMessage *msg, void *user_data) { DBusMessageIter args; struct app_match_data match; struct adv_monitor_app *app; struct btd_adv_monitor_manager *manager = user_data; if (!dbus_message_iter_init(msg, &args)) return btd_error_invalid_args(msg); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) return btd_error_invalid_args(msg); dbus_message_iter_get_basic(&args, &match.path); if (!strlen(match.path) || !g_str_has_prefix(match.path, "/")) return btd_error_invalid_args(msg); match.owner = dbus_message_get_sender(msg); app = queue_find(manager->apps, app_match, &match); if (!app) return btd_error_does_not_exist(msg); queue_remove(manager->apps, app); app_destroy(app); btd_info(manager->adapter_id, "Path %s removed along with Adv Monitor app %s", match.path, match.owner); return dbus_message_new_method_return(msg); } static const GDBusMethodTable adv_monitor_methods[] = { { GDBUS_EXPERIMENTAL_ASYNC_METHOD("RegisterMonitor", GDBUS_ARGS({ "application", "o" }), NULL, register_monitor) }, { GDBUS_EXPERIMENTAL_ASYNC_METHOD("UnregisterMonitor", GDBUS_ARGS({ "application", "o" }), NULL, unregister_monitor) }, { } }; /* Gets SupportedMonitorTypes property */ static gboolean get_supported_monitor_types(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { DBusMessageIter entry; const struct adv_monitor_type *t; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); for (t = supported_types; t->name; t++) { dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &t->name); } dbus_message_iter_close_container(iter, &entry); return TRUE; } const struct adv_monitor_feature { uint32_t mask; const char *name; } supported_features[] = { { MGMT_ADV_MONITOR_FEATURE_MASK_OR_PATTERNS, "controller-patterns" }, { } }; /* Gets SupportedFeatures property */ static gboolean get_supported_features(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { DBusMessageIter entry; const struct adv_monitor_feature *f; struct btd_adv_monitor_manager *manager = data; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); for (f = supported_features; f->name; f++) { if (manager->supported_features & f->mask) { dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &f->name); } } dbus_message_iter_close_container(iter, &entry); return TRUE; } static const GDBusPropertyTable adv_monitor_properties[] = { {"SupportedMonitorTypes", "as", get_supported_monitor_types, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL}, {"SupportedFeatures", "as", get_supported_features, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL}, { } }; /* Updates monitor state to 'removed' */ static void monitor_state_removed(void *data, void *user_data) { struct adv_monitor *monitor = data; if (!monitor || (monitor->state != MONITOR_STATE_INITED && monitor->state != MONITOR_STATE_ACTIVE)) return; monitor->state = MONITOR_STATE_REMOVED; monitor->merged_pattern = NULL; } /* Remove the matched merged_pattern and remove the monitors */ static void remove_merged_pattern(void *data, void *user_data) { struct adv_monitor_merged_pattern *merged_pattern = data; uint16_t *handle = user_data; if (!handle) return; /* handle = 0 indicates kernel has removed all monitors */ if (handle != 0 && *handle != merged_pattern->monitor_handle) return; DBG("Adv monitor with handle:0x%04x removed by kernel", merged_pattern->monitor_handle); queue_foreach(merged_pattern->monitors, monitor_state_removed, NULL); queue_destroy(merged_pattern->monitors, monitor_destroy); merged_pattern_free(merged_pattern); } /* Processes Adv Monitor removed event from kernel */ static void adv_monitor_removed_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { struct btd_adv_monitor_manager *manager = user_data; const struct mgmt_ev_adv_monitor_removed *ev = param; uint16_t handle = ev->monitor_handle; const uint16_t adapter_id = manager->adapter_id; if (length < sizeof(*ev)) { btd_error(adapter_id, "Wrong size of Adv Monitor Removed event"); return; } /* Traverse the merged_patterns to find matching pattern */ queue_foreach(manager->merged_patterns, remove_merged_pattern, &handle); DBG("Adv Monitor removed event with handle 0x%04x processed", ev->monitor_handle); } /* Includes found/lost device's object path into the dbus message */ static void report_device_state_setup(DBusMessageIter *iter, void *user_data) { const char *path = device_get_path(user_data); dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); } /* Invokes DeviceFound on the matched monitor */ static void notify_device_found_per_monitor(void *data, void *user_data) { struct adv_monitor *monitor = data; struct monitored_device_info *info = user_data; if (monitor->merged_pattern->monitor_handle == info->monitor_handle) { DBG("Calling DeviceFound() on Adv Monitor of owner %s " "at path %s", monitor->app->owner, monitor->path); g_dbus_proxy_method_call(monitor->proxy, "DeviceFound", report_device_state_setup, NULL, info->device, NULL); } } /* Checks all monitors for match in the app to invoke DeviceFound */ static void notify_device_found_per_app(void *data, void *user_data) { struct adv_monitor_app *app = data; queue_foreach(app->monitors, notify_device_found_per_monitor, user_data); } /* Processes Adv Monitor Device Found event from kernel */ static void adv_monitor_device_found_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_adv_monitor_device_found *ev = param; struct btd_adv_monitor_manager *manager = user_data; const uint16_t adapter_id = manager->adapter_id; struct btd_adapter *adapter = manager->adapter; uint16_t handle = le16_to_cpu(ev->monitor_handle); struct monitored_device_info info; const uint8_t *ad_data = NULL; uint16_t ad_data_len; uint32_t flags; char addr[18]; if (length < sizeof(*ev)) { btd_error(adapter_id, "Too short Adv Monitor Device Found event"); return; } ad_data_len = btohs(ev->ad_data_len); if (length != sizeof(*ev) + ad_data_len) { btd_error(adapter_id, "Wrong size of Adv Monitor Device Found event"); return; } if (ad_data_len > 0) ad_data = ev->ad_data; flags = le32_to_cpu(ev->flags); ba2str(&ev->addr.bdaddr, addr); DBG("hci%u addr %s, rssi %d flags 0x%04x ad_data_len %u", index, addr, ev->rssi, flags, ad_data_len); btd_adapter_device_found(adapter, &ev->addr.bdaddr, ev->addr.type, ev->rssi, flags, ad_data, ad_data_len, true); if (handle) { DBG("Adv Monitor with handle 0x%04x started tracking " "the device %s", handle, addr); info.device = btd_adapter_find_device(adapter, &ev->addr.bdaddr, ev->addr.type); if (!info.device) { btd_error(adapter_id, "Device object not found for %s", addr); return; } /* Check for matched monitor in all apps */ info.monitor_handle = handle; queue_foreach(manager->apps, notify_device_found_per_app, &info); } } /* Invokes DeviceLost on the matched monitor */ static void notify_device_lost_per_monitor(void *data, void *user_data) { struct adv_monitor *monitor = data; struct monitored_device_info *info = user_data; if (monitor->merged_pattern->monitor_handle == info->monitor_handle) { DBG("Calling DeviceLost() on Adv Monitor of owner %s " "at path %s", monitor->app->owner, monitor->path); g_dbus_proxy_method_call(monitor->proxy, "DeviceLost", report_device_state_setup, NULL, info->device, NULL); } } /* Checks all monitors for match in the app to invoke DeviceLost */ static void notify_device_lost_per_app(void *data, void *user_data) { struct adv_monitor_app *app = data; queue_foreach(app->monitors, notify_device_lost_per_monitor, user_data); } /* Processes Adv Monitor Device Lost event from kernel */ static void adv_monitor_device_lost_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { struct btd_adv_monitor_manager *manager = user_data; const struct mgmt_ev_adv_monitor_device_lost *ev = param; uint16_t handle = le16_to_cpu(ev->monitor_handle); const uint16_t adapter_id = manager->adapter_id; struct btd_adapter *adapter = manager->adapter; struct monitored_device_info info; char addr[18]; if (length < sizeof(*ev)) { btd_error(adapter_id, "Wrong size of Adv Monitor Device Lost event"); return; } ba2str(&ev->addr.bdaddr, addr); DBG("Adv Monitor with handle 0x%04x stopped tracking the device %s", handle, addr); info.device = btd_adapter_find_device(adapter, &ev->addr.bdaddr, ev->addr.type); if (!info.device) { btd_error(adapter_id, "Device object not found for %s", addr); return; } /* Check for matched monitor in all apps */ info.monitor_handle = handle; queue_foreach(manager->apps, notify_device_lost_per_app, &info); } /* Allocates a manager object */ static struct btd_adv_monitor_manager *manager_new( struct btd_adapter *adapter, struct mgmt *mgmt) { struct btd_adv_monitor_manager *manager; if (!adapter || !mgmt) return NULL; manager = new0(struct btd_adv_monitor_manager, 1); if (!manager) return NULL; manager->adapter = adapter; manager->mgmt = mgmt_ref(mgmt); manager->adapter_id = btd_adapter_get_index(adapter); manager->apps = queue_new(); manager->merged_patterns = queue_new(); mgmt_register(manager->mgmt, MGMT_EV_ADV_MONITOR_REMOVED, manager->adapter_id, adv_monitor_removed_callback, manager, NULL); mgmt_register(manager->mgmt, MGMT_EV_ADV_MONITOR_DEVICE_FOUND, manager->adapter_id, adv_monitor_device_found_callback, manager, NULL); mgmt_register(manager->mgmt, MGMT_EV_ADV_MONITOR_DEVICE_LOST, manager->adapter_id, adv_monitor_device_lost_callback, manager, NULL); return manager; } /* Frees a manager object */ static void manager_free(struct btd_adv_monitor_manager *manager) { mgmt_unref(manager->mgmt); queue_destroy(manager->apps, app_destroy); queue_destroy(manager->merged_patterns, merged_pattern_free); free(manager); } /* Destroys a manager object and unregisters its D-Bus interface */ static void manager_destroy(struct btd_adv_monitor_manager *manager) { if (!manager) return; g_dbus_unregister_interface(btd_get_dbus_connection(), adapter_get_path(manager->adapter), ADV_MONITOR_MGR_INTERFACE); manager_free(manager); } /* Initiates manager's members based on the return of * MGMT_OP_READ_ADV_MONITOR_FEATURES */ static void read_adv_monitor_features_cb(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_read_adv_monitor_features *rp = param; struct btd_adv_monitor_manager *manager = user_data; if (status != MGMT_STATUS_SUCCESS || !param) { btd_error(manager->adapter_id, "Failed to Read Adv Monitor Features with " "status 0x%02x", status); return; } if (length < sizeof(*rp)) { btd_error(manager->adapter_id, "Wrong size of Read Adv Monitor Features " "response"); return; } manager->supported_features = le32_to_cpu(rp->supported_features); manager->enabled_features = le32_to_cpu(rp->enabled_features); manager->max_num_monitors = le16_to_cpu(rp->max_num_handles); manager->max_num_patterns = rp->max_num_patterns; btd_info(manager->adapter_id, "Adv Monitor Manager created with " "supported features:0x%08x, enabled features:0x%08x, " "max number of supported monitors:%d, " "max number of supported patterns:%d", manager->supported_features, manager->enabled_features, manager->max_num_monitors, manager->max_num_patterns); } /* Creates a manager and registers its D-Bus interface */ struct btd_adv_monitor_manager *btd_adv_monitor_manager_create( struct btd_adapter *adapter, struct mgmt *mgmt) { struct btd_adv_monitor_manager *manager; manager = manager_new(adapter, mgmt); if (!manager) return NULL; if (!g_dbus_register_interface(btd_get_dbus_connection(), adapter_get_path(manager->adapter), ADV_MONITOR_MGR_INTERFACE, adv_monitor_methods, NULL, adv_monitor_properties, manager, NULL)) { btd_error(manager->adapter_id, "Failed to register " ADV_MONITOR_MGR_INTERFACE); manager_free(manager); return NULL; } if (!mgmt_send(manager->mgmt, MGMT_OP_READ_ADV_MONITOR_FEATURES, manager->adapter_id, 0, NULL, read_adv_monitor_features_cb, manager, NULL)) { btd_error(manager->adapter_id, "Failed to send Read Adv Monitor Features"); manager_destroy(manager); return NULL; } return manager; } /* Destroys a manager and unregisters its D-Bus interface */ void btd_adv_monitor_manager_destroy(struct btd_adv_monitor_manager *manager) { if (!manager) return; btd_info(manager->adapter_id, "Destroy Adv Monitor Manager"); manager_destroy(manager); } bool btd_adv_monitor_offload_enabled(struct btd_adv_monitor_manager *manager) { if (!manager) return false; return !!(manager->enabled_features & MGMT_ADV_MONITOR_FEATURE_MASK_OR_PATTERNS); } /* Processes the content matching based pattern(s) of a monitor */ static void adv_match_per_monitor(void *data, void *user_data) { struct adv_monitor *monitor = data; struct adv_content_filter_info *info = user_data; struct queue *patterns; if (!monitor) { error("Unexpected NULL adv_monitor object upon match"); return; } if (monitor->state != MONITOR_STATE_ACTIVE) return; if (!monitor->merged_pattern) return; patterns = monitor->merged_pattern->patterns; if (monitor->merged_pattern->type == MONITOR_TYPE_OR_PATTERNS && bt_ad_pattern_match(info->ad, patterns)) { goto matched; } return; matched: if (!info->matched_monitors) info->matched_monitors = queue_new(); queue_push_tail(info->matched_monitors, monitor); } /* Processes the content matching for the monitor(s) of an app */ static void adv_match_per_app(void *data, void *user_data) { struct adv_monitor_app *app = data; if (!app) { error("Unexpected NULL adv_monitor_app object upon match"); return; } queue_foreach(app->monitors, adv_match_per_monitor, user_data); } /* Processes the content matching for every app without RSSI filtering and * notifying monitors. The caller is responsible of releasing the memory of the * list but not the ad data. * Returns the list of monitors whose content match the ad data. */ struct queue *btd_adv_monitor_content_filter( struct btd_adv_monitor_manager *manager, struct bt_ad *ad) { struct adv_content_filter_info info; if (!manager || !ad) return NULL; info.ad = ad; info.matched_monitors = NULL; queue_foreach(manager->apps, adv_match_per_app, &info); return info.matched_monitors; } /* Wraps adv_monitor_filter_rssi() to processes the content-matched monitor with * RSSI filtering and notifies it on device found/lost event */ static void monitor_filter_rssi(void *data, void *user_data) { struct adv_monitor *monitor = data; struct adv_rssi_filter_info *info = user_data; if (!monitor || !info) return; adv_monitor_filter_rssi(monitor, info->device, info->rssi); } /* Processes every content-matched monitor with RSSI filtering and notifies on * device found/lost event. The caller is responsible of releasing the memory * of matched_monitors list but not its data. */ void btd_adv_monitor_notify_monitors(struct btd_adv_monitor_manager *manager, struct btd_device *device, int8_t rssi, struct queue *matched_monitors) { struct adv_rssi_filter_info info; if (!manager || !device || !matched_monitors || queue_isempty(matched_monitors)) { return; } info.device = device; info.rssi = rssi; queue_foreach(matched_monitors, monitor_filter_rssi, &info); } /* Matches a device based on btd_device object */ static bool monitor_device_match(const void *a, const void *b) { const struct adv_monitor_device *dev = a; const struct btd_device *device = b; if (!dev) { error("Unexpected NULL adv_monitor_device object upon match"); return false; } if (dev->device != device) return false; return true; } /* Frees a monitor device object */ static void monitor_device_free(void *data) { struct adv_monitor_device *dev = data; if (!dev) { error("Unexpected NULL adv_monitor_device object upon free"); return; } if (dev->lost_timer) { timeout_remove(dev->lost_timer); dev->lost_timer = 0; } dev->monitor = NULL; dev->device = NULL; free(dev); } /* Removes a device from monitor->devices list */ static void remove_device_from_monitor(void *data, void *user_data) { struct adv_monitor *monitor = data; struct btd_device *device = user_data; struct adv_monitor_device *dev = NULL; if (!monitor) { error("Unexpected NULL adv_monitor object upon device remove"); return; } dev = queue_remove_if(monitor->devices, monitor_device_match, device); if (dev) { DBG("Device removed from the Adv Monitor at path %s", monitor->path); monitor_device_free(dev); } } /* Removes a device from every monitor in an app */ static void remove_device_from_app(void *data, void *user_data) { struct adv_monitor_app *app = data; struct btd_device *device = user_data; if (!app) { error("Unexpected NULL adv_monitor_app object upon device " "remove"); return; } queue_foreach(app->monitors, remove_device_from_monitor, device); } /* Removes a device from every monitor in all apps */ void btd_adv_monitor_device_remove(struct btd_adv_monitor_manager *manager, struct btd_device *device) { if (!manager || !device) return; queue_foreach(manager->apps, remove_device_from_app, device); } /* Creates a device object to track the per-device information */ static struct adv_monitor_device *monitor_device_create( struct adv_monitor *monitor, struct btd_device *device) { struct adv_monitor_device *dev = NULL; dev = new0(struct adv_monitor_device, 1); if (!dev) return NULL; dev->monitor = monitor; dev->device = device; queue_push_tail(monitor->devices, dev); return dev; } /* Handles a situation where the device goes offline/out-of-range */ static bool handle_device_lost_timeout(gpointer user_data) { struct adv_monitor_device *dev = user_data; struct adv_monitor *monitor = dev->monitor; DBG("Device Lost timeout triggered for device %p. Calling DeviceLost() " "on Adv Monitor of owner %s at path %s", dev->device, monitor->app->owner, monitor->path); g_dbus_proxy_method_call(monitor->proxy, "DeviceLost", report_device_state_setup, NULL, dev->device, NULL); dev->lost_timer = 0; dev->found = false; return FALSE; } /* Filters an Adv based on its RSSI value */ static void adv_monitor_filter_rssi(struct adv_monitor *monitor, struct btd_device *device, int8_t rssi) { struct adv_monitor_device *dev = NULL; time_t curr_time = time(NULL); uint16_t adapter_id = monitor->app->manager->adapter_id; /* If the RSSI thresholds and timeouts are not specified, report the * DeviceFound() event without tracking for the RSSI as the Adv has * already matched the pattern filter. */ if (rssi_is_unset(&monitor->rssi)) { DBG("Calling DeviceFound() on Adv Monitor of owner %s " "at path %s", monitor->app->owner, monitor->path); g_dbus_proxy_method_call(monitor->proxy, "DeviceFound", report_device_state_setup, NULL, device, NULL); return; } dev = queue_find(monitor->devices, monitor_device_match, device); if (!dev) { dev = monitor_device_create(monitor, device); if (!dev) { btd_error(adapter_id, "Failed to create Adv Monitor device object."); return; } } if (dev->lost_timer) { timeout_remove(dev->lost_timer); dev->lost_timer = 0; } /* Reset the timings of found/lost if a device has been offline for * longer than the high/low timeouts. */ if (dev->last_seen) { if (difftime(curr_time, dev->last_seen) > monitor->rssi.high_rssi_timeout) { dev->high_rssi_first_seen = 0; } if (difftime(curr_time, dev->last_seen) > monitor->rssi.low_rssi_timeout) { dev->low_rssi_first_seen = 0; } } dev->last_seen = curr_time; /* Check for the found devices (if the device is not already found) */ if (!dev->found && rssi > monitor->rssi.high_rssi) { if (dev->high_rssi_first_seen) { if (difftime(curr_time, dev->high_rssi_first_seen) >= monitor->rssi.high_rssi_timeout) { dev->found = true; DBG("Calling DeviceFound() on Adv Monitor " "of owner %s at path %s", monitor->app->owner, monitor->path); g_dbus_proxy_method_call( monitor->proxy, "DeviceFound", report_device_state_setup, NULL, dev->device, NULL); } } else { dev->high_rssi_first_seen = curr_time; } } else { dev->high_rssi_first_seen = 0; } /* Check for the lost devices (only if the device is already found, as * it doesn't make any sense to report the Device Lost event if the * device is not found yet) */ if (dev->found && rssi < monitor->rssi.low_rssi) { if (dev->low_rssi_first_seen) { if (difftime(curr_time, dev->low_rssi_first_seen) >= monitor->rssi.low_rssi_timeout) { dev->found = false; DBG("Calling DeviceLost() on Adv Monitor " "of owner %s at path %s", monitor->app->owner, monitor->path); g_dbus_proxy_method_call( monitor->proxy, "DeviceLost", report_device_state_setup, NULL, dev->device, NULL); } } else { dev->low_rssi_first_seen = curr_time; } } else { dev->low_rssi_first_seen = 0; } /* Setup a timer to track if the device goes offline/out-of-range, only * if we are tracking for the Low RSSI Threshold. If we are tracking * the High RSSI Threshold, nothing needs to be done. */ if (dev->found) { dev->lost_timer = timeout_add_seconds(monitor->rssi.low_rssi_timeout, handle_device_lost_timeout, dev, NULL); } } /* Clears running DeviceLost timer for a given device */ static void clear_device_lost_timer(void *data, void *user_data) { struct adv_monitor_device *dev = data; struct adv_monitor *monitor = NULL; if (dev->lost_timer) { timeout_remove(dev->lost_timer); dev->lost_timer = 0; monitor = dev->monitor; DBG("Calling DeviceLost() for device %p on Adv Monitor " "of owner %s at path %s", dev->device, monitor->app->owner, monitor->path); g_dbus_proxy_method_call(monitor->proxy, "DeviceLost", report_device_state_setup, NULL, dev->device, NULL); } } /* Clears running DeviceLost timers from each monitor */ static void clear_lost_timers_from_monitor(void *data, void *user_data) { struct adv_monitor *monitor = data; queue_foreach(monitor->devices, clear_device_lost_timer, NULL); } /* Clears running DeviceLost timers from each app */ static void clear_lost_timers_from_app(void *data, void *user_data) { struct adv_monitor_app *app = data; queue_foreach(app->monitors, clear_lost_timers_from_monitor, NULL); } /* Handles bt power down scenario */ void btd_adv_monitor_power_down(struct btd_adv_monitor_manager *manager) { if (!manager) { error("Unexpected NULL btd_adv_monitor_manager object upon " "power down"); return; } /* Clear any running DeviceLost timers in case of power down */ queue_foreach(manager->apps, clear_lost_timers_from_app, NULL); } bluez-5.87/src/PaxHeaders/gatt-database.c0000644000000000000000000000005015221752714015315 xustar0020 atime=1783092685 20 ctime=1783094106 bluez-5.87/src/gatt-database.c0000644000000000000000000031163015221752714015002 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Google Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "bluetooth/uuid.h" #include "bluetooth/mgmt.h" #include "btio/btio.h" #include "gdbus/gdbus.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/io.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-server.h" #include "log.h" #include "error.h" #include "btd.h" #include "adapter.h" #include "device.h" #include "gatt-database.h" #include "dbus-common.h" #include "profile.h" #include "service.h" #include "textfile.h" #include "settings.h" #define GATT_MANAGER_IFACE "org.bluez.GattManager1" #define GATT_PROFILE_IFACE "org.bluez.GattProfile1" #define GATT_SERVICE_IFACE "org.bluez.GattService1" #define GATT_CHRC_IFACE "org.bluez.GattCharacteristic1" #define GATT_DESC_IFACE "org.bluez.GattDescriptor1" #define ERROR_FAILED ERROR_INTERFACE ".Failed" #define UUID_GAP 0x1800 #define UUID_GATT 0x1801 #define UUID_DIS 0x180a struct gatt_record { struct btd_gatt_database *database; uint32_t handle; struct gatt_db_attribute *attr; }; struct btd_gatt_database { struct btd_adapter *adapter; struct gatt_db *db; unsigned int db_id; GIOChannel *le_io; GIOChannel *eatt_io; GIOChannel *bredr_io; struct queue *records; struct queue *device_states; struct queue *ccc_callbacks; struct gatt_db_attribute *svc_chngd; struct gatt_db_attribute *svc_chngd_ccc; struct gatt_db_attribute *cli_feat; struct gatt_db_attribute *db_hash; struct gatt_db_attribute *eatt; struct queue *apps; struct queue *profiles; }; struct gatt_app { struct btd_gatt_database *database; char *owner; char *path; DBusMessage *reg; GDBusClient *client; bool failed; struct queue *profiles; struct queue *services; struct queue *proxies; }; struct external_service { struct gatt_app *app; char *path; /* Path to GattService1 */ GDBusProxy *proxy; struct gatt_db_attribute *attrib; uint16_t attr_cnt; struct queue *chrcs; struct queue *descs; struct queue *includes; }; struct external_profile { struct gatt_app *app; GDBusProxy *proxy; struct queue *profiles; /* btd_profile list */ }; struct client_io { struct bt_att *att; struct external_chrc *chrc; unsigned int disconn_id; struct io *io; }; struct external_chrc { struct external_service *service; char *path; GDBusProxy *proxy; uint8_t props; uint8_t ext_props; uint32_t perm; uint32_t ccc_perm; uint16_t mtu; struct queue *write_ios; struct queue *notify_ios; struct gatt_db_attribute *attrib; struct gatt_db_attribute *ccc; struct queue *pending_reads; struct queue *pending_writes; unsigned int ntfy_cnt; bool prep_authorized; bool req_prep_authorization; }; struct external_desc { struct external_service *service; char *chrc_path; GDBusProxy *proxy; uint32_t perm; struct gatt_db_attribute *attrib; bool handled; struct queue *pending_reads; struct queue *pending_writes; bool prep_authorized; bool req_prep_authorization; }; struct pending_op { struct bt_att *att; unsigned int id; unsigned int disconn_id; uint16_t offset; uint8_t link_type; struct gatt_db_attribute *attrib; struct queue *owner_queue; struct iovec data; bool is_characteristic; bool prep_authorize; }; struct notify { struct btd_gatt_database *database; uint16_t handle, ccc_handle; uint8_t *value; uint16_t len; bt_gatt_server_conf_func_t conf; void *user_data; }; #define CLI_FEAT_SIZE 1 struct device_state { struct btd_gatt_database *db; bdaddr_t bdaddr; uint8_t bdaddr_type; unsigned int disc_id; uint8_t cli_feat[CLI_FEAT_SIZE]; bool change_aware; bool out_of_sync; struct queue *ccc_states; struct notify *pending; }; typedef uint8_t (*btd_gatt_database_ccc_write_t) (struct pending_op *op, void *user_data); typedef void (*btd_gatt_database_destroy_t) (void *data); struct ccc_state { uint16_t handle; uint16_t value; }; struct ccc_cb_data { uint16_t handle; btd_gatt_database_ccc_write_t callback; btd_gatt_database_destroy_t destroy; void *user_data; }; struct device_info { bdaddr_t bdaddr; uint8_t bdaddr_type; }; static struct queue *dbs = NULL; static void ccc_cb_free(void *data) { struct ccc_cb_data *ccc_cb = data; if (ccc_cb->destroy) ccc_cb->destroy(ccc_cb->user_data); free(ccc_cb); } static bool ccc_cb_match_service(const void *data, const void *match_data) { const struct ccc_cb_data *ccc_cb = data; const struct gatt_db_attribute *attrib = match_data; uint16_t start, end; if (!gatt_db_attribute_get_service_handles(attrib, &start, &end)) return false; return ccc_cb->handle >= start && ccc_cb->handle <= end; } static bool ccc_cb_match_handle(const void *data, const void *match_data) { const struct ccc_cb_data *ccc_cb = data; uint16_t handle = PTR_TO_UINT(match_data); return ccc_cb->handle == handle; } static bool dev_state_match(const void *a, const void *b) { const struct device_state *dev_state = a; const struct device_info *dev_info = b; return bacmp(&dev_state->bdaddr, &dev_info->bdaddr) == 0 && dev_state->bdaddr_type == dev_info->bdaddr_type; } static struct device_state * find_device_state(struct btd_gatt_database *database, const bdaddr_t *bdaddr, uint8_t bdaddr_type) { struct device_info dev_info; memset(&dev_info, 0, sizeof(dev_info)); bacpy(&dev_info.bdaddr, bdaddr); dev_info.bdaddr_type = bdaddr_type; return queue_find(database->device_states, dev_state_match, &dev_info); } static bool ccc_state_match(const void *a, const void *b) { const struct ccc_state *ccc = a; uint16_t handle = PTR_TO_UINT(b); return ccc->handle == handle; } static struct ccc_state *find_ccc_state(struct device_state *dev_state, uint16_t handle) { return queue_find(dev_state->ccc_states, ccc_state_match, UINT_TO_PTR(handle)); } static struct device_state *device_state_create(struct btd_gatt_database *db, const bdaddr_t *bdaddr, uint8_t bdaddr_type) { struct device_state *dev_state; dev_state = new0(struct device_state, 1); dev_state->db = db; dev_state->ccc_states = queue_new(); bacpy(&dev_state->bdaddr, bdaddr); dev_state->bdaddr_type = bdaddr_type; return dev_state; } static void device_state_free(void *data) { struct device_state *state = data; queue_destroy(state->ccc_states, free); if (state->pending) { free(state->pending->value); free(state->pending); } free(state); } static void clear_ccc_state(void *data, void *user_data) { struct ccc_state *ccc = data; struct btd_gatt_database *db = user_data; struct ccc_cb_data *ccc_cb; if (!ccc->value) return; ccc_cb = queue_find(db->ccc_callbacks, ccc_cb_match_handle, UINT_TO_PTR(ccc->handle)); if (!ccc_cb) return; if (ccc_cb->callback) ccc_cb->callback(NULL, ccc_cb->user_data); } static void att_disconnected(int err, void *user_data) { struct device_state *state = user_data; struct btd_device *device; DBG(""); state->disc_id = 0; state->out_of_sync = false; device = btd_adapter_find_device(state->db->adapter, &state->bdaddr, state->bdaddr_type); if (!device) goto remove; if (device_is_bonded(device, state->bdaddr_type)) { struct ccc_state *ccc; uint16_t handle; handle = gatt_db_attribute_get_handle(state->db->svc_chngd_ccc); ccc = find_ccc_state(state, handle); if (ccc && ccc->value) device_store_svc_chng_ccc(device, state->bdaddr_type, ccc->value); return; } remove: /* Remove device state if device no longer exists or is not paired */ if (queue_remove(state->db->device_states, state)) { queue_foreach(state->ccc_states, clear_ccc_state, state->db); device_state_free(state); } } static bool get_dst_info(struct bt_att *att, bdaddr_t *dst, uint8_t *dst_type) { GIOChannel *io = NULL; GError *gerr = NULL; int fd; fd = bt_att_get_fd(att); if (fd < 0) return false; io = g_io_channel_unix_new(fd); if (!io) return false; bt_io_get(io, &gerr, BT_IO_OPT_DEST_BDADDR, dst, BT_IO_OPT_DEST_TYPE, dst_type, BT_IO_OPT_INVALID); if (gerr) { error("gatt: bt_io_get: %s", gerr->message); g_error_free(gerr); g_io_channel_unref(io); return false; } g_io_channel_unref(io); return true; } static struct device_state * find_device_state_by_att(struct btd_gatt_database *database, struct bt_att *att) { bdaddr_t bdaddr; uint8_t bdaddr_type; if (!get_dst_info(att, &bdaddr, &bdaddr_type)) return NULL; return find_device_state(database, &bdaddr, bdaddr_type); } static struct device_state *get_device_state(struct btd_gatt_database *database, struct bt_att *att) { struct device_state *dev_state; bdaddr_t bdaddr; uint8_t bdaddr_type; if (!get_dst_info(att, &bdaddr, &bdaddr_type)) return NULL; /* * Find and return a device state. If a matching state doesn't exist, * then create a new one. */ dev_state = find_device_state(database, &bdaddr, bdaddr_type); if (dev_state) goto done; dev_state = device_state_create(database, &bdaddr, bdaddr_type); queue_push_tail(database->device_states, dev_state); done: if (!dev_state->disc_id) dev_state->disc_id = bt_att_register_disconnect(att, att_disconnected, dev_state, NULL); return dev_state; } static struct ccc_state *get_ccc_state(struct btd_gatt_database *database, struct bt_att *att, uint16_t handle) { struct device_state *dev_state; struct ccc_state *ccc; dev_state = get_device_state(database, att); if (!dev_state) return NULL; ccc = find_ccc_state(dev_state, handle); if (ccc) return ccc; ccc = new0(struct ccc_state, 1); ccc->handle = handle; queue_push_tail(dev_state->ccc_states, ccc); return ccc; } static void cancel_pending_read(void *data) { struct pending_op *op = data; gatt_db_attribute_read_result(op->attrib, op->id, BT_ATT_ERROR_REQUEST_NOT_SUPPORTED, NULL, 0); op->owner_queue = NULL; } static void cancel_pending_write(void *data) { struct pending_op *op = data; gatt_db_attribute_write_result(op->attrib, op->id, BT_ATT_ERROR_REQUEST_NOT_SUPPORTED); op->owner_queue = NULL; } static void client_io_free(void *data) { struct client_io *client = data; bt_att_unregister_disconnect(client->att, client->disconn_id); bt_att_unref(client->att); io_destroy(client->io); free(client); } static void chrc_free(void *data) { struct external_chrc *chrc = data; queue_destroy(chrc->write_ios, client_io_free); queue_destroy(chrc->notify_ios, client_io_free); queue_destroy(chrc->pending_reads, cancel_pending_read); queue_destroy(chrc->pending_writes, cancel_pending_write); g_free(chrc->path); g_dbus_proxy_set_property_watch(chrc->proxy, NULL, NULL); g_dbus_proxy_unref(chrc->proxy); free(chrc); } static void desc_free(void *data) { struct external_desc *desc = data; queue_destroy(desc->pending_reads, cancel_pending_read); queue_destroy(desc->pending_writes, cancel_pending_write); g_dbus_proxy_unref(desc->proxy); g_free(desc->chrc_path); free(desc); } static void inc_free(void *data) { struct external_desc *inc = data; free(inc); } static void service_free(void *data) { struct external_service *service = data; queue_destroy(service->chrcs, chrc_free); queue_destroy(service->descs, desc_free); queue_destroy(service->includes, inc_free); if (service->attrib) gatt_db_remove_service(service->app->database->db, service->attrib); if (service->app->client) g_dbus_proxy_unref(service->proxy); g_free(service->path); free(service); } static void profile_remove(void *data) { struct btd_profile *p = data; DBG("Removed \"%s\"", p->name); btd_adapter_foreach(adapter_remove_profile, p); btd_profile_unregister(p); g_free((void *) p->name); g_free((void *) p->remote_uuid); free(p); } static void profile_release(struct external_profile *profile) { DBG("Releasing \"%s\"", profile->app->owner); g_dbus_proxy_method_call(profile->proxy, "Release", NULL, NULL, NULL, NULL); } static void profile_free(void *data) { struct external_profile *profile = data; queue_destroy(profile->profiles, profile_remove); profile_release(profile); g_dbus_proxy_unref(profile->proxy); free(profile); } static void app_free(void *data) { struct gatt_app *app = data; queue_destroy(app->profiles, profile_free); queue_destroy(app->services, service_free); queue_destroy(app->proxies, NULL); if (app->client) { g_dbus_client_set_disconnect_watch(app->client, NULL, NULL); g_dbus_client_set_proxy_handlers(app->client, NULL, NULL, NULL, NULL); g_dbus_client_set_ready_watch(app->client, NULL, NULL); g_dbus_client_unref(app->client); } if (app->reg) dbus_message_unref(app->reg); g_free(app->owner); g_free(app->path); free(app); } static void gatt_record_free(void *data) { struct gatt_record *rec = data; adapter_service_remove(rec->database->adapter, rec->handle); free(rec); } static void gatt_database_free(void *data) { struct btd_gatt_database *database = data; if (database->le_io) { g_io_channel_shutdown(database->le_io, FALSE, NULL); g_io_channel_unref(database->le_io); } if (database->eatt_io) { g_io_channel_shutdown(database->eatt_io, FALSE, NULL); g_io_channel_unref(database->eatt_io); } if (database->bredr_io) { g_io_channel_shutdown(database->bredr_io, FALSE, NULL); g_io_channel_unref(database->bredr_io); } /* TODO: Persistently store CCC states before freeing them */ gatt_db_unregister(database->db, database->db_id); queue_destroy(database->records, gatt_record_free); queue_destroy(database->device_states, device_state_free); queue_destroy(database->apps, app_free); queue_destroy(database->profiles, profile_free); queue_destroy(database->ccc_callbacks, ccc_cb_free); database->device_states = NULL; database->ccc_callbacks = NULL; gatt_db_unref(database->db); btd_adapter_unref(database->adapter); free(database); } static void connect_cb(GIOChannel *io, GError *gerr, gpointer user_data) { struct btd_adapter *adapter; struct btd_device *device; uint8_t dst_type; bdaddr_t src, dst; if (gerr) { error("%s", gerr->message); return; } bt_io_get(io, &gerr, BT_IO_OPT_SOURCE_BDADDR, &src, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_DEST_TYPE, &dst_type, BT_IO_OPT_INVALID); if (gerr) { error("bt_io_get: %s", gerr->message); g_error_free(gerr); return; } DBG("New incoming %s ATT connection", dst_type == BDADDR_BREDR ? "BR/EDR" : "LE"); adapter = adapter_find(&src); if (!adapter) return; device = btd_adapter_get_device(adapter, &dst, dst_type); if (!device) return; device_attach_att(device, io); } static void gap_device_name_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct btd_gatt_database *database = user_data; uint8_t error = 0; size_t len = 0; const uint8_t *value = NULL; const char *device_name; DBG("GAP Device Name read request\n"); device_name = btd_adapter_get_name(database->adapter); len = strlen(device_name); if (offset > len) { error = BT_ATT_ERROR_INVALID_OFFSET; goto done; } len -= offset; value = len ? (const uint8_t *) &device_name[offset] : NULL; done: gatt_db_attribute_read_result(attrib, id, error, value, len); } static void gap_appearance_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct btd_gatt_database *database = user_data; uint8_t error = 0; size_t len = 2; const uint8_t *value = NULL; uint8_t appearance[2]; uint32_t dev_class; DBG("GAP Appearance read request\n"); dev_class = btd_adapter_get_class(database->adapter); appearance[0] = dev_class & 0x00ff; appearance[1] = (dev_class >> 8) & 0x001f; len -= offset; value = len ? &appearance[offset] : NULL; gatt_db_attribute_read_result(attrib, id, error, value, len); } static void gap_car_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { uint8_t value = 0x00; DBG("GAP Central Address Resolution read request\n"); if (btd_opts.defaults.le.addr_resolution) { struct btd_device *device; device = btd_adapter_find_device_by_fd(bt_att_get_fd(att)); if (device) value = btd_device_flags_enabled(device, DEVICE_FLAG_ADDRESS_RESOLUTION); } gatt_db_attribute_read_result(attrib, id, 0, &value, sizeof(value)); } static sdp_record_t *record_new(uuid_t *uuid, uint16_t start, uint16_t end) { sdp_list_t *svclass_id, *apseq, *proto[2], *root, *aproto; uuid_t root_uuid, proto_uuid, l2cap; sdp_record_t *record; sdp_data_t *psm, *sh, *eh; uint16_t lp = BT_ATT_PSM; if (uuid == NULL) return NULL; if (start > end) return NULL; record = sdp_record_alloc(); if (record == NULL) return NULL; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(NULL, &root_uuid); sdp_set_browse_groups(record, root); sdp_list_free(root, NULL); svclass_id = sdp_list_append(NULL, uuid); sdp_set_service_classes(record, svclass_id); sdp_list_free(svclass_id, NULL); sdp_uuid16_create(&l2cap, L2CAP_UUID); proto[0] = sdp_list_append(NULL, &l2cap); psm = sdp_data_alloc(SDP_UINT16, &lp); proto[0] = sdp_list_append(proto[0], psm); apseq = sdp_list_append(NULL, proto[0]); sdp_uuid16_create(&proto_uuid, ATT_UUID); proto[1] = sdp_list_append(NULL, &proto_uuid); sh = sdp_data_alloc(SDP_UINT16, &start); proto[1] = sdp_list_append(proto[1], sh); eh = sdp_data_alloc(SDP_UINT16, &end); proto[1] = sdp_list_append(proto[1], eh); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(NULL, apseq); sdp_set_access_protos(record, aproto); sdp_data_free(psm); sdp_data_free(sh); sdp_data_free(eh); sdp_list_free(proto[0], NULL); sdp_list_free(proto[1], NULL); sdp_list_free(apseq, NULL); sdp_list_free(aproto, NULL); return record; } static void database_add_record(struct btd_gatt_database *database, struct gatt_db_attribute *attr) { struct gatt_record *rec; sdp_record_t *record; uint16_t start, end; uuid_t svc, gap_uuid; bt_uuid_t uuid; const char *name = NULL; char uuidstr[MAX_LEN_UUID_STR]; gatt_db_attribute_get_service_uuid(attr, &uuid); switch (uuid.type) { case BT_UUID16: name = bt_uuid16_to_str(uuid.value.u16); sdp_uuid16_create(&svc, uuid.value.u16); break; case BT_UUID32: name = bt_uuid32_to_str(uuid.value.u32); sdp_uuid32_create(&svc, uuid.value.u32); break; case BT_UUID128: bt_uuid_to_string(&uuid, uuidstr, sizeof(uuidstr)); name = bt_uuidstr_to_str(uuidstr); sdp_uuid128_create(&svc, (void *) &uuid.value.u128); break; case BT_UUID_UNSPEC: return; } gatt_db_attribute_get_service_handles(attr, &start, &end); record = record_new(&svc, start, end); if (!record) return; if (name != NULL) sdp_set_info_attr(record, name, "BlueZ", NULL); sdp_uuid16_create(&gap_uuid, UUID_GAP); if (sdp_uuid_cmp(&svc, &gap_uuid) == 0) { sdp_set_url_attr(record, "http://www.bluez.org/", "http://www.bluez.org/", "http://www.bluez.org/"); } if (adapter_service_add(database->adapter, record) < 0) { sdp_record_free(record); return; } rec = new0(struct gatt_record, 1); rec->database = database; rec->handle = record->handle; rec->attr = attr; queue_push_tail(database->records, rec); } static void populate_gap_service(struct btd_gatt_database *database) { bt_uuid_t uuid; struct gatt_db_attribute *service, *attrib; bool ll_privacy = btd_adapter_has_settings(database->adapter, MGMT_SETTING_LL_PRIVACY); /* Add the GAP service */ bt_uuid16_create(&uuid, UUID_GAP); service = gatt_db_add_service(database->db, &uuid, true, ll_privacy ? 7 : 5); /* * Device Name characteristic. */ bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME); gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, gap_device_name_read_cb, NULL, database); /* * Device Appearance characteristic. */ bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE); attrib = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, gap_appearance_read_cb, NULL, database); gatt_db_attribute_set_fixed_length(attrib, 2); /* Only enable Central Address Resolution if LL Privacy is supported */ if (ll_privacy) { /* * Central Address Resolution characteristic. */ bt_uuid16_create(&uuid, GATT_CHARAC_CAR); attrib = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, gap_car_read_cb, NULL, database); } gatt_db_attribute_set_fixed_length(attrib, 1); gatt_db_service_set_active(service, true); database_add_record(database, service); } static void gatt_ccc_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct btd_gatt_database *database = user_data; struct ccc_state *ccc; uint16_t handle; uint8_t ecode = 0; const uint8_t *value = NULL; size_t len = 0; handle = gatt_db_attribute_get_handle(attrib); DBG("CCC read called for handle: 0x%04x", handle); ccc = get_ccc_state(database, att, handle); if (!ccc) { ecode = BT_ATT_ERROR_UNLIKELY; goto done; } len = sizeof(ccc->value); value = (void *) &ccc->value; done: gatt_db_attribute_read_result(attrib, id, ecode, value, len); } static struct btd_device *att_get_device(struct bt_att *att) { GIOChannel *io = NULL; GError *gerr = NULL; bdaddr_t src, dst; uint8_t dst_type; struct btd_adapter *adapter; io = g_io_channel_unix_new(bt_att_get_fd(att)); if (!io) return NULL; bt_io_get(io, &gerr, BT_IO_OPT_SOURCE_BDADDR, &src, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_DEST_TYPE, &dst_type, BT_IO_OPT_INVALID); if (gerr) { error("bt_io_get: %s", gerr->message); g_error_free(gerr); g_io_channel_unref(io); return NULL; } g_io_channel_unref(io); adapter = adapter_find(&src); if (!adapter) { error("Unable to find adapter object"); return NULL; } return btd_adapter_find_device(adapter, &dst, dst_type); } static void pending_op_free(void *data) { struct pending_op *op = data; if (op->owner_queue) queue_remove(op->owner_queue, op); bt_att_unregister_disconnect(op->att, op->disconn_id); bt_att_unref(op->att); free(op); } static void pending_disconnect_cb(int err, void *user_data) { struct pending_op *op = user_data; op->owner_queue = NULL; } static struct pending_op *pending_ccc_new(struct bt_att *att, struct gatt_db_attribute *attrib, uint16_t value, uint8_t link_type) { struct pending_op *op; struct btd_device *device; device = att_get_device(att); if (!device) { error("Unable to find device object"); return NULL; } op = new0(struct pending_op, 1); op->data.iov_base = UINT_TO_PTR(value); op->data.iov_len = sizeof(value); op->att = bt_att_ref(att); op->attrib = attrib; op->link_type = link_type; op->disconn_id = bt_att_register_disconnect(att, pending_disconnect_cb, op, NULL); return op; } static void gatt_ccc_write_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { struct btd_gatt_database *database = user_data; struct ccc_state *ccc; struct ccc_cb_data *ccc_cb; uint16_t handle, val; uint8_t ecode = 0; handle = gatt_db_attribute_get_handle(attrib); DBG("CCC write called for handle: 0x%04x", handle); if (!value || len > 2) { ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; goto done; } if (offset > 2) { ecode = BT_ATT_ERROR_INVALID_OFFSET; goto done; } ccc = get_ccc_state(database, att, handle); if (!ccc) { ecode = BT_ATT_ERROR_UNLIKELY; goto done; } if (len == 1) val = *value; else val = get_le16(value); /* If value is identical, then just succeed */ if (val == ccc->value) goto done; ccc_cb = queue_find(database->ccc_callbacks, ccc_cb_match_handle, UINT_TO_PTR(gatt_db_attribute_get_handle(attrib))); if (ccc_cb) { struct pending_op *op; op = pending_ccc_new(att, attrib, val, bt_att_get_link_type(att)); if (!op) { ecode = BT_ATT_ERROR_UNLIKELY; goto done; } ecode = ccc_cb->callback(op, ccc_cb->user_data); if (ecode) pending_op_free(op); } if (!ecode) ccc->value = val; done: gatt_db_attribute_write_result(attrib, id, ecode); } static void ccc_add_cb(struct btd_gatt_database *database, struct gatt_db_attribute *ccc, btd_gatt_database_ccc_write_t callback, void *user_data, btd_gatt_database_destroy_t destroy) { struct ccc_cb_data *ccc_cb; ccc_cb = new0(struct ccc_cb_data, 1); ccc_cb->handle = gatt_db_attribute_get_handle(ccc); ccc_cb->callback = callback; ccc_cb->destroy = destroy; ccc_cb->user_data = user_data; queue_push_tail(database->ccc_callbacks, ccc_cb); } static struct gatt_db_attribute * service_add_ccc(struct gatt_db_attribute *service, struct btd_gatt_database *database, btd_gatt_database_ccc_write_t write_callback, void *user_data, uint32_t perm, btd_gatt_database_destroy_t destroy) { struct gatt_db_attribute *ccc; ccc = gatt_db_service_add_ccc(service, perm); if (!ccc) return ccc; /* Only add ccc_cb if callback is set */ if (write_callback) ccc_add_cb(database, ccc, write_callback, user_data, destroy); return ccc; } static void cli_feat_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct btd_gatt_database *database = user_data; struct device_state *state; uint8_t ecode = 0; const uint8_t *value = NULL; size_t len = 0; DBG("Client Features read"); state = get_device_state(database, att); if (!state) { ecode = BT_ATT_ERROR_UNLIKELY; goto done; } len = sizeof(state->cli_feat) - offset; value = len ? &state->cli_feat[offset] : NULL; done: gatt_db_attribute_read_result(attrib, id, ecode, value, len); } static void cli_feat_write_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { struct btd_gatt_database *database = user_data; struct device_state *state; uint8_t bits[] = { BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING, BT_GATT_CHRC_CLI_FEAT_EATT, BT_GATT_CHRC_CLI_FEAT_NFY_MULTI }; uint8_t ecode = 0; unsigned int i; DBG("Client Features write"); state = get_device_state(database, att); if (!state) { ecode = BT_ATT_ERROR_UNLIKELY; goto done; } if (!value || !len) { ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; goto done; } for (i = 0; i < sizeof(bits); i++) { /* A client shall never clear a bit it has set */ if (state->cli_feat[0] & (1 << i) && !(value[0] & (1 << i))) { ecode = BT_ATT_ERROR_VALUE_NOT_ALLOWED; goto done; } } /* Shall we reallocate the feat array if bigger? */ len = MIN(sizeof(state->cli_feat), len); while (len) { state->cli_feat[len - 1] |= value[len - 1]; len--; } state->cli_feat[0] &= ((1 << sizeof(bits)) - 1); state->change_aware = true; done: gatt_db_attribute_write_result(attrib, id, ecode); } static void db_hash_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct btd_gatt_database *database = user_data; const uint8_t *hash; struct device_state *state; bdaddr_t bdaddr; uint8_t bdaddr_type; DBG("Database Hash read"); hash = gatt_db_get_hash(database->db); gatt_db_attribute_read_result(attrib, id, 0, hash, 16); if (!get_dst_info(att, &bdaddr, &bdaddr_type)) return; state = find_device_state(database, &bdaddr, bdaddr_type); if (state) state->change_aware = true; } static void server_feat_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct btd_gatt_database *database = user_data; struct device_state *state; uint8_t ecode = 0; uint8_t value = 0; state = get_device_state(database, att); if (!state) { ecode = BT_ATT_ERROR_UNLIKELY; goto done; } if (btd_opts.gatt_channels > 1) value |= BT_GATT_CHRC_SERVER_FEAT_EATT; done: gatt_db_attribute_read_result(attrib, id, ecode, &value, sizeof(value)); } static void populate_gatt_service(struct btd_gatt_database *database) { bt_uuid_t uuid; struct gatt_db_attribute *service; /* Add the GATT service */ bt_uuid16_create(&uuid, UUID_GATT); service = gatt_db_add_service(database->db, &uuid, true, 10); bt_uuid16_create(&uuid, GATT_CHARAC_SERVICE_CHANGED); database->svc_chngd = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_NONE, BT_GATT_CHRC_PROP_INDICATE, NULL, NULL, database); database->svc_chngd_ccc = service_add_ccc(service, database, NULL, NULL, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, NULL); bt_uuid16_create(&uuid, GATT_CHARAC_CLI_FEAT); database->cli_feat = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_WRITE, cli_feat_read_cb, cli_feat_write_cb, database); gatt_db_attribute_set_fixed_length(database->cli_feat, CLI_FEAT_SIZE); /* Only expose database hash chrc if supported */ if (gatt_db_hash_support(database->db)) { bt_uuid16_create(&uuid, GATT_CHARAC_DB_HASH); database->db_hash = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, db_hash_read_cb, NULL, database); gatt_db_attribute_set_fixed_length(database->db_hash, 16); } /* Only enable EATT if there is a socket listening */ if (database->eatt_io) { bt_uuid16_create(&uuid, GATT_CHARAC_SERVER_FEAT); database->eatt = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, server_feat_read_cb, NULL, database); gatt_db_attribute_set_fixed_length(database->eatt, 1); } gatt_db_service_set_active(service, true); database_add_record(database, service); } static void device_info_read_pnp_id_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { uint8_t pdu[7]; pdu[0] = btd_opts.did_source; put_le16(btd_opts.did_vendor, &pdu[1]); put_le16(btd_opts.did_product, &pdu[3]); put_le16(btd_opts.did_version, &pdu[5]); gatt_db_attribute_read_result(attrib, id, 0, pdu, sizeof(pdu)); } static void populate_devinfo_service(struct btd_gatt_database *database) { struct gatt_db_attribute *service; struct gatt_db_attribute *attrib; bt_uuid_t uuid; if (!btd_opts.did_source) return; bt_uuid16_create(&uuid, UUID_DIS); service = gatt_db_add_service(database->db, &uuid, true, 3); bt_uuid16_create(&uuid, GATT_CHARAC_PNP_ID); attrib = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, device_info_read_pnp_id_cb, NULL, database); gatt_db_attribute_set_fixed_length(attrib, 7); gatt_db_service_set_active(service, true); database_add_record(database, service); } static void conf_cb(void *user_data) { GDBusProxy *proxy = user_data; DBG("GATT server received confirmation"); if (proxy != NULL) { g_dbus_proxy_method_call(proxy, "Confirm", NULL, NULL, NULL, NULL); } } static void service_changed_conf(void *user_data) { struct device_state *state = user_data; DBG(""); if (!state) return; state->change_aware = true; } static void state_set_pending(struct device_state *state, struct notify *notify) { uint16_t start, end, old_start, old_end; /* Cache this only for Service Changed */ if (notify->conf != service_changed_conf) return; if (state->pending) { old_start = get_le16(state->pending->value); old_end = get_le16(state->pending->value + 2); start = get_le16(notify->value); end = get_le16(notify->value + 2); if (start < old_start) put_le16(start, state->pending->value); if (end > old_end) put_le16(end, state->pending->value + 2); return; } /* Copy notify contents to pending */ state->pending = new0(struct notify, 1); memcpy(state->pending, notify, sizeof(*notify)); state->pending->value = malloc(notify->len); memcpy(state->pending->value, notify->value, notify->len); } static void send_notification_to_device(void *data, void *user_data) { struct device_state *device_state = data; struct notify *notify = user_data; struct ccc_state *ccc; struct btd_device *device; struct bt_gatt_server *server; if (notify->conf == service_changed_conf) { if (device_state->cli_feat[0] & BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING) { device_state->change_aware = false; notify->user_data = device_state; } } ccc = find_ccc_state(device_state, notify->ccc_handle); if (!ccc || !(ccc->value & 0x0003)) return; device = btd_adapter_find_device(notify->database->adapter, &device_state->bdaddr, device_state->bdaddr_type); if (!device) { /* If ATT has not disconnect yet don't remove the state as it * will eventually be removed when att_disconnected is called. */ if (device_state->disc_id) return; goto remove; } server = btd_device_get_gatt_server(device); if (!server) { if (!device_is_bonded(device, device_state->bdaddr_type)) goto remove; state_set_pending(device_state, notify); return; } /* * TODO: If the device is not connected but bonded, send the * notification/indication when it becomes connected. */ if (ccc->value & 0x0001) { DBG("GATT server sending notification"); bt_gatt_server_send_notification(server, notify->handle, notify->value, notify->len, device_state->cli_feat[0] & BT_GATT_CHRC_CLI_FEAT_NFY_MULTI); return; } DBG("GATT server sending indication"); bt_gatt_server_send_indication(server, notify->handle, notify->value, notify->len, notify->conf, notify->user_data, NULL); return; remove: /* Remove device state if device no longer exists or is not paired */ if (queue_remove(notify->database->device_states, device_state)) { queue_foreach(device_state->ccc_states, clear_ccc_state, notify->database); device_state_free(device_state); } } static void gatt_notify_cb(struct gatt_db_attribute *attrib, struct gatt_db_attribute *ccc, const uint8_t *value, size_t len, struct bt_att *att, void *user_data) { struct btd_gatt_database *database = user_data; struct notify notify; memset(¬ify, 0, sizeof(notify)); notify.database = database; notify.handle = gatt_db_attribute_get_handle(attrib); notify.ccc_handle = gatt_db_attribute_get_handle(ccc); notify.value = (void *) value; notify.len = len; if (attrib == database->svc_chngd) notify.conf = service_changed_conf; /* If a specific att is provided notify only to that device */ if (att) { struct device_state *state; state = find_device_state_by_att(database, att); if (!state) return; send_notification_to_device(state, ¬ify); } else queue_foreach(database->device_states, send_notification_to_device, ¬ify); } static void register_core_services(struct btd_gatt_database *database) { gatt_db_ccc_register(database->db, gatt_ccc_read_cb, gatt_ccc_write_cb, gatt_notify_cb, database); populate_gap_service(database); populate_gatt_service(database); populate_devinfo_service(database); } static void send_notification_to_devices(struct btd_gatt_database *database, uint16_t handle, uint8_t *value, uint16_t len, uint16_t ccc_handle, bt_gatt_server_conf_func_t conf, void *user_data) { struct notify notify; memset(¬ify, 0, sizeof(notify)); notify.database = database; notify.handle = handle; notify.ccc_handle = ccc_handle; notify.value = value; notify.len = len; notify.conf = conf; notify.user_data = user_data; queue_foreach(database->device_states, send_notification_to_device, ¬ify); } static void send_service_changed(struct btd_gatt_database *database, struct gatt_db_attribute *attrib) { uint16_t start, end; uint8_t value[4]; uint16_t handle, ccc_handle; if (!gatt_db_attribute_get_service_handles(attrib, &start, &end)) { error("Failed to obtain changed service handles"); return; } handle = gatt_db_attribute_get_handle(database->svc_chngd); ccc_handle = gatt_db_attribute_get_handle(database->svc_chngd_ccc); if (!handle || !ccc_handle) { error("Failed to obtain handles for \"Service Changed\"" " characteristic"); return; } put_le16(start, value); put_le16(end, value + 2); if (!gatt_db_attribute_notify(database->svc_chngd, value, sizeof(value), NULL)) error("Failed to notify Service Changed"); } static void database_store(struct btd_gatt_database *database) { char filename[PATH_MAX]; create_filename(filename, PATH_MAX, "/%s/attributes", btd_adapter_get_storage_dir(database->adapter)); create_file(filename, 0600); btd_settings_gatt_db_store(database->db, filename); } static void gatt_db_service_added(struct gatt_db_attribute *attrib, void *user_data) { struct btd_gatt_database *database = user_data; DBG("GATT Service added to local database"); database_add_record(database, attrib); send_service_changed(database, attrib); database_store(database); } static bool ccc_match_service(const void *data, const void *match_data) { const struct ccc_state *ccc = data; const struct gatt_db_attribute *attrib = match_data; uint16_t start, end; if (!gatt_db_attribute_get_service_handles(attrib, &start, &end)) return false; return ccc->handle >= start && ccc->handle <= end; } static void remove_device_ccc(void *data, void *user_data) { struct device_state *state = data; queue_remove_all(state->ccc_states, ccc_match_service, user_data, free); } static bool match_gatt_record(const void *data, const void *user_data) { const struct gatt_record *rec = data; const struct gatt_db_attribute *attr = user_data; return (rec->attr == attr); } static void gatt_db_service_removed(struct gatt_db_attribute *attrib, void *user_data) { struct btd_gatt_database *database = user_data; struct gatt_record *rec; DBG("Local GATT service removed"); rec = queue_remove_if(database->records, match_gatt_record, attrib); if (rec) gatt_record_free(rec); send_service_changed(database, attrib); queue_foreach(database->device_states, remove_device_ccc, attrib); queue_remove_all(database->ccc_callbacks, ccc_cb_match_service, attrib, ccc_cb_free); } struct svc_match_data { const char *path; const char *sender; }; static bool match_app(const void *a, const void *b) { const struct gatt_app *app = a; const struct svc_match_data *data = b; return g_strcmp0(app->path, data->path) == 0 && g_strcmp0(app->owner, data->sender) == 0; } static gboolean app_free_idle_cb(void *data) { app_free(data); return FALSE; } static void client_disconnect_cb(DBusConnection *conn, void *user_data) { struct gatt_app *app = user_data; struct btd_gatt_database *database = app->database; DBG("Client disconnected"); if (queue_remove(database->apps, app)) app_free(app); } static void remove_app(void *data) { struct gatt_app *app = data; /* * Set callback to NULL to avoid potential race condition * when calling remove_app and GDBusClient unref. */ g_dbus_client_set_disconnect_watch(app->client, NULL, NULL); /* * Set proxy handlers to NULL, so that this gets called only once when * the first proxy that belongs to this service gets removed. */ g_dbus_client_set_proxy_handlers(app->client, NULL, NULL, NULL, NULL); queue_remove(app->database->apps, app); /* * Do not run in the same loop, this may be a disconnect * watch call and GDBusClient should not be destroyed. */ g_idle_add(app_free_idle_cb, app); } static bool match_service_by_path(const void *a, const void *b) { const struct external_service *service = a; const char *path = b; return strcmp(service->path, path) == 0; } static bool parse_path(GDBusProxy *proxy, const char *name, const char **path) { DBusMessageIter iter; if (!g_dbus_proxy_get_property(proxy, name, &iter)) return false; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) return false; dbus_message_iter_get_basic(&iter, path); return true; } static bool incr_attr_count(struct external_service *service, uint16_t incr) { if (service->attr_cnt > UINT16_MAX - incr) return false; service->attr_cnt += incr; return true; } static bool parse_chrc_flags(DBusMessageIter *array, uint8_t *props, uint8_t *ext_props, uint32_t *perm, uint32_t *ccc_perm, bool *req_prep_authorization) { const char *flag; if (!props || !ext_props || !perm || !ccc_perm) return false; *props = 0; *ext_props = 0; *perm = 0; *ccc_perm = 0; do { if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_STRING) return false; dbus_message_iter_get_basic(array, &flag); if (!strcmp("broadcast", flag)) *props |= BT_GATT_CHRC_PROP_BROADCAST; else if (!strcmp("read", flag)) { *props |= BT_GATT_CHRC_PROP_READ; *perm |= BT_ATT_PERM_READ; } else if (!strcmp("write-without-response", flag)) { *props |= BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP; *perm |= BT_ATT_PERM_WRITE; } else if (!strcmp("write", flag)) { *props |= BT_GATT_CHRC_PROP_WRITE; *perm |= BT_ATT_PERM_WRITE; } else if (!strcmp("notify", flag)) { *props |= BT_GATT_CHRC_PROP_NOTIFY; *ccc_perm |= BT_ATT_PERM_WRITE; } else if (!strcmp("indicate", flag)) { *props |= BT_GATT_CHRC_PROP_INDICATE; *ccc_perm |= BT_ATT_PERM_WRITE; } else if (!strcmp("authenticated-signed-writes", flag)) { *props |= BT_GATT_CHRC_PROP_AUTH; *perm |= BT_ATT_PERM_WRITE; } else if (!strcmp("extended-properties", flag)) { *props |= BT_GATT_CHRC_PROP_EXT_PROP; } else if (!strcmp("reliable-write", flag)) { *ext_props |= BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE; *perm |= BT_ATT_PERM_WRITE; } else if (!strcmp("writable-auxiliaries", flag)) { *ext_props |= BT_GATT_CHRC_EXT_PROP_WRITABLE_AUX; } else if (!strcmp("encrypt-read", flag)) { *props |= BT_GATT_CHRC_PROP_READ; *perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT; } else if (!strcmp("encrypt-write", flag)) { *props |= BT_GATT_CHRC_PROP_WRITE; *perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_ENCRYPT; } else if (!strcmp("encrypt-authenticated-read", flag)) { *props |= BT_GATT_CHRC_PROP_READ; *perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_AUTHEN; } else if (!strcmp("encrypt-authenticated-write", flag)) { *props |= BT_GATT_CHRC_PROP_WRITE; *perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_AUTHEN; } else if (!strcmp("secure-read", flag)) { *props |= BT_GATT_CHRC_PROP_READ; *perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_SECURE; } else if (!strcmp("secure-write", flag)) { *props |= BT_GATT_CHRC_PROP_WRITE; *perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_SECURE; } else if (!strcmp("authorize", flag)) { *req_prep_authorization = true; } else if (!strcmp("encrypt-notify", flag)) { *ccc_perm |= BT_ATT_PERM_WRITE_ENCRYPT; *props |= BT_GATT_CHRC_PROP_NOTIFY; } else if (!strcmp("encrypt-authenticated-notify", flag)) { *ccc_perm |= BT_ATT_PERM_WRITE_AUTHEN; *props |= BT_GATT_CHRC_PROP_NOTIFY; } else if (!strcmp("secure-notify", flag)) { *ccc_perm |= BT_ATT_PERM_WRITE_SECURE; *props |= BT_GATT_CHRC_PROP_NOTIFY; } else if (!strcmp("encrypt-indicate", flag)) { *ccc_perm |= BT_ATT_PERM_WRITE_ENCRYPT; *props |= BT_GATT_CHRC_PROP_INDICATE; } else if (!strcmp("encrypt-authenticated-indicate", flag)) { *ccc_perm |= BT_ATT_PERM_WRITE_AUTHEN; *props |= BT_GATT_CHRC_PROP_INDICATE; } else if (!strcmp("secure-indicate", flag)) { *ccc_perm |= BT_ATT_PERM_WRITE_SECURE; *props |= BT_GATT_CHRC_PROP_INDICATE; } else { error("Invalid characteristic flag: %s", flag); return false; } } while (dbus_message_iter_next(array)); if (*ext_props) *props |= BT_GATT_CHRC_PROP_EXT_PROP; return true; } static bool parse_desc_flags(DBusMessageIter *array, uint32_t *perm, bool *req_prep_authorization) { const char *flag; *perm = 0; do { if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_STRING) return false; dbus_message_iter_get_basic(array, &flag); if (!strcmp("read", flag)) *perm |= BT_ATT_PERM_READ; else if (!strcmp("write", flag)) *perm |= BT_ATT_PERM_WRITE; else if (!strcmp("encrypt-read", flag)) *perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT; else if (!strcmp("encrypt-write", flag)) *perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_ENCRYPT; else if (!strcmp("encrypt-authenticated-read", flag)) *perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_AUTHEN; else if (!strcmp("encrypt-authenticated-write", flag)) *perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_AUTHEN; else if (!strcmp("secure-read", flag)) *perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_SECURE; else if (!strcmp("secure-write", flag)) *perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_SECURE; else if (!strcmp("authorize", flag)) *req_prep_authorization = true; else { error("Invalid descriptor flag: %s", flag); return false; } } while (dbus_message_iter_next(array)); return true; } static bool parse_flags(GDBusProxy *proxy, uint8_t *props, uint8_t *ext_props, uint32_t *perm, uint32_t *ccc_perm, bool *req_prep_authorization) { DBusMessageIter iter, array; const char *iface; if (!g_dbus_proxy_get_property(proxy, "Flags", &iter)) return false; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) return false; dbus_message_iter_recurse(&iter, &array); iface = g_dbus_proxy_get_interface(proxy); if (!strcmp(iface, GATT_DESC_IFACE)) return parse_desc_flags(&array, perm, req_prep_authorization); return parse_chrc_flags(&array, props, ext_props, perm, ccc_perm, req_prep_authorization); } static struct external_chrc *chrc_create(struct gatt_app *app, GDBusProxy *proxy, const char *path) { struct external_service *service; struct external_chrc *chrc; const char *service_path; if (!parse_path(proxy, "Service", &service_path)) { error("Failed to obtain service path for characteristic"); return NULL; } service = queue_find(app->services, match_service_by_path, service_path); if (!service) { error("Unable to find service for characteristic: %s", path); return NULL; } chrc = new0(struct external_chrc, 1); chrc->pending_reads = queue_new(); chrc->pending_writes = queue_new(); chrc->path = g_strdup(path); if (!chrc->path) goto fail; chrc->service = service; chrc->proxy = g_dbus_proxy_ref(proxy); /* * Add 2 for the characteristic declaration and the value * attribute. */ if (!incr_attr_count(chrc->service, 2)) { error("Failed to increment attribute count"); goto fail; } /* * Parse characteristic flags (i.e. properties) here since they * are used to determine if any special descriptors should be * created. */ if (!parse_flags(proxy, &chrc->props, &chrc->ext_props, &chrc->perm, &chrc->ccc_perm, &chrc->req_prep_authorization)) { error("Failed to parse characteristic properties"); goto fail; } if ((chrc->props & BT_GATT_CHRC_PROP_NOTIFY || chrc->props & BT_GATT_CHRC_PROP_INDICATE) && !incr_attr_count(chrc->service, 1)) { error("Failed to increment attribute count for CCC"); goto fail; } if (chrc->ext_props && !incr_attr_count(chrc->service, 1)) { error("Failed to increment attribute count for CEP"); goto fail; } queue_push_tail(chrc->service->chrcs, chrc); return chrc; fail: chrc_free(chrc); return NULL; } static bool match_chrc(const void *a, const void *b) { const struct external_chrc *chrc = a; const char *path = b; return strcmp(chrc->path, path) == 0; } static bool match_service_by_chrc(const void *a, const void *b) { const struct external_service *service = a; const char *path = b; return queue_find(service->chrcs, match_chrc, path); } static struct external_desc *desc_create(struct gatt_app *app, GDBusProxy *proxy) { struct external_service *service; struct external_desc *desc; const char *chrc_path; if (!parse_path(proxy, "Characteristic", &chrc_path)) { error("Failed to obtain characteristic path for descriptor"); return NULL; } service = queue_find(app->services, match_service_by_chrc, chrc_path); if (!service) { error("Unable to find service for characteristic: %s", chrc_path); return NULL; } desc = new0(struct external_desc, 1); desc->pending_reads = queue_new(); desc->pending_writes = queue_new(); desc->chrc_path = g_strdup(chrc_path); if (!desc->chrc_path) goto fail; desc->service = service; desc->proxy = g_dbus_proxy_ref(proxy); /* Add 1 for the descriptor attribute */ if (!incr_attr_count(desc->service, 1)) { error("Failed to increment attribute count"); goto fail; } /* * Parse descriptors flags here since they are used to * determine the permission the descriptor should have */ if (!parse_flags(proxy, NULL, NULL, &desc->perm, NULL, &desc->req_prep_authorization)) { error("Failed to parse characteristic properties"); goto fail; } queue_push_tail(desc->service->descs, desc); return desc; fail: desc_free(desc); return NULL; } static bool check_service_path(GDBusProxy *proxy, struct external_service *service) { const char *service_path; if (!parse_path(proxy, "Service", &service_path)) return false; return g_strcmp0(service_path, service->path) == 0; } static struct external_service *create_service(struct gatt_app *app, GDBusProxy *proxy, const char *path) { struct external_service *service; if (!path || !g_str_has_prefix(path, "/")) return NULL; service = queue_find(app->services, match_service_by_path, path); if (service) { error("Duplicated service: %s", path); return NULL; } service = new0(struct external_service, 1); service->app = app; service->path = g_strdup(path); if (!service->path) goto fail; service->proxy = g_dbus_proxy_ref(proxy); service->chrcs = queue_new(); service->descs = queue_new(); service->includes = queue_new(); /* Add 1 for the service declaration */ if (!incr_attr_count(service, 1)) { error("Failed to increment attribute count"); goto fail; } queue_push_tail(app->services, service); return service; fail: service_free(service); return NULL; } static void proxy_added_cb(GDBusProxy *proxy, void *user_data) { struct gatt_app *app = user_data; const char *iface, *path; if (app->failed) return; queue_push_tail(app->proxies, proxy); iface = g_dbus_proxy_get_interface(proxy); path = g_dbus_proxy_get_path(proxy); DBG("Object received: %s, iface: %s", path, iface); } static void proxy_removed_cb(GDBusProxy *proxy, void *user_data) { struct gatt_app *app = user_data; struct external_service *service; const char *path; path = g_dbus_proxy_get_path(proxy); service = queue_remove_if(app->services, match_service_by_path, (void *) path); if (!service) return; DBG("Proxy removed - removing service: %s", service->path); service_free(service); } static bool parse_uuid(GDBusProxy *proxy, bt_uuid_t *uuid) { DBusMessageIter iter; bt_uuid_t tmp; const char *uuidstr; if (!g_dbus_proxy_get_property(proxy, "UUID", &iter)) return false; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) return false; dbus_message_iter_get_basic(&iter, &uuidstr); if (bt_string_to_uuid(uuid, uuidstr) < 0) return false; /* GAP & GATT services are created and managed by BlueZ */ bt_uuid16_create(&tmp, UUID_GAP); if (!bt_uuid_cmp(&tmp, uuid)) { error("GAP service must be handled by BlueZ"); return false; } bt_uuid16_create(&tmp, UUID_GATT); if (!bt_uuid_cmp(&tmp, uuid)) { error("GATT service must be handled by BlueZ"); return false; } return true; } static bool parse_includes(GDBusProxy *proxy, struct external_service *service) { DBusMessageIter iter; DBusMessageIter array; char *obj; char *includes; int type; /* Includes property is optional */ if (!g_dbus_proxy_get_property(proxy, "Includes", &iter)) return true; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) return false; dbus_message_iter_recurse(&iter, &array); while ((type = dbus_message_iter_get_arg_type(&array)) != DBUS_TYPE_INVALID) { if (type != DBUS_TYPE_OBJECT_PATH) return false; dbus_message_iter_get_basic(&array, &obj); includes = g_strdup(obj); if (!includes) return false; if (!queue_push_tail(service->includes, includes)) { error("Failed to add Includes path in queue\n"); return false; } incr_attr_count(service, 1); dbus_message_iter_next(&array); } return true; } static bool parse_primary(GDBusProxy *proxy, bool *primary) { DBusMessageIter iter; dbus_bool_t val; if (!g_dbus_proxy_get_property(proxy, "Primary", &iter)) return false; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) return false; dbus_message_iter_get_basic(&iter, &val); *primary = val; return true; } static bool parse_handle(GDBusProxy *proxy, uint16_t *handle) { DBusMessageIter iter; *handle = 0; /* Handle property is optional */ if (!g_dbus_proxy_get_property(proxy, "Handle", &iter)) return true; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT16) return false; dbus_message_iter_get_basic(&iter, handle); return true; } static uint8_t dbus_error_to_att_ecode(const char *name, const char *msg, uint8_t perm_err) { if (strcmp(name, ERROR_INTERFACE ".Failed") == 0) { char *endptr = NULL; uint32_t ecode; ecode = strtol(msg, &endptr, 0); /* If message doesn't set an error code just use 0x80 */ if (!endptr || *endptr != '\0') return 0x80; if (ecode < 0x80 || ecode > 0x9f) { error("Invalid error code: %s", msg); return BT_ATT_ERROR_UNLIKELY; } return ecode; } if (strcmp(name, ERROR_INTERFACE ".NotSupported") == 0) return BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; if (strcmp(name, ERROR_INTERFACE ".NotAuthorized") == 0) return BT_ATT_ERROR_AUTHORIZATION; if (strcmp(name, ERROR_INTERFACE ".InvalidValueLength") == 0) return BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; if (strcmp(name, ERROR_INTERFACE ".InvalidOffset") == 0) return BT_ATT_ERROR_INVALID_OFFSET; if (strcmp(name, ERROR_INTERFACE ".InProgress") == 0) return BT_ERROR_ALREADY_IN_PROGRESS; if (!strcmp(name, ERROR_INTERFACE ".ImproperlyConfigured")) return BT_ERROR_CCC_IMPROPERLY_CONFIGURED; if (strcmp(name, ERROR_INTERFACE ".NotPermitted") == 0) return perm_err; return BT_ATT_ERROR_UNLIKELY; } static void read_reply_cb(DBusMessage *message, void *user_data) { struct pending_op *op = user_data; DBusError err; DBusMessageIter iter, array; uint8_t ecode = 0; uint8_t *value = NULL; int len = 0; if (!op->owner_queue) { DBG("Pending read was canceled when object got removed"); return; } dbus_error_init(&err); if (dbus_set_error_from_message(&err, message) == TRUE) { DBG("Failed to read value: %s: %s", err.name, err.message); ecode = dbus_error_to_att_ecode(err.name, err.message, BT_ATT_ERROR_READ_NOT_PERMITTED); dbus_error_free(&err); goto done; } dbus_message_iter_init(message, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { /* * Return not supported for this, as the external app basically * doesn't properly support reading from this characteristic. */ ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; error("Invalid return value received for \"ReadValue\""); goto done; } dbus_message_iter_recurse(&iter, &array); dbus_message_iter_get_fixed_array(&array, &value, &len); if (len < 0) { ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; value = NULL; len = 0; goto done; } /* Truncate the value if it's too large */ len = MIN(BT_ATT_MAX_VALUE_LEN, len); value = len ? value : NULL; done: gatt_db_attribute_read_result(op->attrib, op->id, ecode, value, len); } static struct pending_op *pending_read_new(struct bt_att *att, struct queue *owner_queue, struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset) { struct pending_op *op; op = new0(struct pending_op, 1); op->owner_queue = owner_queue; op->att = bt_att_ref(att); op->attrib = attrib; op->id = id; op->offset = offset; op->link_type = bt_att_get_link_type(att); queue_push_tail(owner_queue, op); op->disconn_id = bt_att_register_disconnect(att, pending_disconnect_cb, op, NULL); return op; } static void append_options(DBusMessageIter *iter, void *user_data) { struct pending_op *op = user_data; struct btd_device *device = att_get_device(op->att); const char *path = device_get_path(device); struct bt_gatt_server *server; const char *link; uint16_t mtu; switch (op->link_type) { case BT_ATT_BREDR: link = "BR/EDR"; break; case BT_ATT_LE: link = "LE"; break; default: link = NULL; break; } dict_append_entry(iter, "device", DBUS_TYPE_OBJECT_PATH, &path); if (op->offset) dict_append_entry(iter, "offset", DBUS_TYPE_UINT16, &op->offset); if (link) dict_append_entry(iter, "link", DBUS_TYPE_STRING, &link); if (op->prep_authorize) dict_append_entry(iter, "prepare-authorize", DBUS_TYPE_BOOLEAN, &op->prep_authorize); server = btd_device_get_gatt_server(device); mtu = bt_gatt_server_get_mtu(server); dict_append_entry(iter, "mtu", DBUS_TYPE_UINT16, &mtu); } static void read_setup_cb(DBusMessageIter *iter, void *user_data) { struct pending_op *op = user_data; DBusMessageIter dict; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); append_options(&dict, op); dbus_message_iter_close_container(iter, &dict); } static struct pending_op *send_read(struct bt_att *att, struct gatt_db_attribute *attrib, GDBusProxy *proxy, struct queue *owner_queue, unsigned int id, uint16_t offset) { struct pending_op *op; op = pending_read_new(att, owner_queue, attrib, id, offset); if (g_dbus_proxy_method_call(proxy, "ReadValue", read_setup_cb, read_reply_cb, op, pending_op_free) == TRUE) return op; pending_op_free(op); return NULL; } static void write_setup_cb(DBusMessageIter *iter, void *user_data) { struct pending_op *op = user_data; DBusMessageIter array, dict; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "y", &array); dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &op->data.iov_base, op->data.iov_len); dbus_message_iter_close_container(iter, &array); dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); append_options(&dict, op); dbus_message_iter_close_container(iter, &dict); if (!op->owner_queue) { gatt_db_attribute_write_result(op->attrib, op->id, 0); pending_op_free(op); } } static void write_reply_cb(DBusMessage *message, void *user_data) { struct pending_op *op = user_data; struct external_chrc *chrc; struct external_desc *desc; DBusError err; DBusMessageIter iter; uint8_t ecode = 0; if (!op->owner_queue) { DBG("Pending write was canceled when object got removed"); return; } dbus_error_init(&err); if (dbus_set_error_from_message(&err, message) == TRUE) { DBG("Failed to write value: %s: %s", err.name, err.message); ecode = dbus_error_to_att_ecode(err.name, err.message, BT_ATT_ERROR_WRITE_NOT_PERMITTED); dbus_error_free(&err); goto done; } if (op->prep_authorize) { if (op->is_characteristic) { chrc = gatt_db_attribute_get_user_data(op->attrib); chrc->prep_authorized = true; } else { desc = gatt_db_attribute_get_user_data(op->attrib); desc->prep_authorized = true; } } dbus_message_iter_init(message, &iter); if (dbus_message_iter_has_next(&iter)) { /* * Return not supported for this, as the external app basically * doesn't properly support the "WriteValue" API. */ ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; error("Invalid return value received for \"WriteValue\""); } done: gatt_db_attribute_write_result(op->attrib, op->id, ecode); } static struct pending_op *pending_write_new(struct bt_att *att, struct queue *owner_queue, struct gatt_db_attribute *attrib, unsigned int id, const uint8_t *value, size_t len, uint16_t offset, bool is_characteristic, bool prep_authorize) { struct pending_op *op; op = new0(struct pending_op, 1); op->data.iov_base = (uint8_t *) value; op->data.iov_len = len; op->att = bt_att_ref(att); op->owner_queue = owner_queue; op->attrib = attrib; op->id = id; op->offset = offset; op->link_type = bt_att_get_link_type(att); op->is_characteristic = is_characteristic; op->prep_authorize = prep_authorize; queue_push_tail(owner_queue, op); op->disconn_id = bt_att_register_disconnect(att, pending_disconnect_cb, op, NULL); return op; } static struct pending_op *send_write(struct bt_att *att, struct gatt_db_attribute *attrib, GDBusProxy *proxy, struct queue *owner_queue, unsigned int id, const uint8_t *value, size_t len, uint16_t offset, bool is_characteristic, bool prep_authorize) { struct pending_op *op; op = pending_write_new(att, owner_queue, attrib, id, value, len, offset, is_characteristic, prep_authorize); if (g_dbus_proxy_method_call(proxy, "WriteValue", write_setup_cb, owner_queue ? write_reply_cb : NULL, op, pending_op_free) == TRUE) return op; pending_op_free(op); return NULL; } static void flush_pending_write(void *data, void *user_data) { GDBusProxy *proxy = user_data; struct pending_op *op = data; if (g_dbus_proxy_method_call(proxy, "WriteValue", write_setup_cb, write_reply_cb, op, pending_op_free) == TRUE) return; pending_op_free(op); } static void flush_pending_writes(GDBusProxy *proxy, struct queue *owner_queue) { queue_foreach(owner_queue, flush_pending_write, proxy); queue_remove_all(owner_queue, NULL, NULL, NULL); } static bool match_client_io(const void *data, const void *user_data) { const struct client_io *client = data; const struct io *io = user_data; return client->io == io; } static bool sock_hup(struct io *io, void *user_data) { struct external_chrc *chrc = user_data; struct client_io *client; DBG("%p closed\n", io); client = queue_remove_if(chrc->write_ios, match_client_io, io); if (!client) { client = queue_remove_if(chrc->notify_ios, match_client_io, io); if (!client) return false; } client_io_free(client); return false; } static bool sock_io_write(struct io *io, void *user_data) { uint8_t buf[] = { 1 }; struct iovec iov = { buf, sizeof(buf) }; /* Send a 1 to the server as confirmation */ io_send(io, &iov, 1); return false; } static void sock_io_conf(void *user_data) { struct io *io = user_data; io_set_write_handler(io, sock_io_write, NULL, NULL); } static bool sock_io_read(struct io *io, void *user_data) { struct client_io *client = user_data; struct external_chrc *chrc = client->chrc; uint8_t buf[512]; int fd = io_get_fd(io); ssize_t bytes_read; struct notify notify; struct device_state *state; if (fd < 0) { error("io_get_fd() returned %d\n", fd); return false; } bytes_read = read(fd, buf, sizeof(buf)); if (bytes_read <= 0) return false; memset(¬ify, 0, sizeof(notify)); notify.database = client->chrc->service->app->database; notify.handle = gatt_db_attribute_get_handle(chrc->attrib); notify.ccc_handle = gatt_db_attribute_get_handle(chrc->ccc); notify.value = (void *) buf; notify.len = bytes_read; notify.conf = sock_io_conf; notify.user_data = io; state = find_device_state_by_att(notify.database, client->att); if (!state) return false; send_notification_to_device(state, ¬ify); return true; } static struct io *sock_io_new(int fd, void *user_data) { struct io *io; io = io_new(fd); io_set_close_on_destroy(io, true); io_set_disconnect_handler(io, sock_hup, user_data, NULL); return io; } static int sock_io_send(struct io *io, const void *data, size_t len) { struct msghdr msg; struct iovec iov; int fd; iov.iov_base = (void *) data; iov.iov_len = len; memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; fd = io_get_fd(io); if (fd < 0) { error("io_get_fd() returned %d\n", fd); return fd; } return sendmsg(fd, &msg, MSG_NOSIGNAL); } static void att_disconnect_cb(int err, void *user_data) { struct client_io *client = user_data; /* If ATT is disconnected shutdown correspondent client IO so sock_hup * is triggered and the server socket is closed. */ io_shutdown(client->io); } static struct client_io * client_io_new(struct external_chrc *chrc, int fd, struct bt_att *att) { struct client_io *client; client = new0(struct client_io, 1); client->att = bt_att_ref(att); client->chrc = chrc; client->disconn_id = bt_att_register_disconnect(att, att_disconnect_cb, client, NULL); client->io = sock_io_new(fd, chrc); return client; } static bool match_client_att(const void *data, const void *user_data) { const struct client_io *client = data; const struct bt_att *att = user_data; /* Always match if ATT instance is not set since that is used by * clear_cc_state to clear all instances. */ if (!att) return true; return client->att == att; } static struct client_io * client_write_io_get(struct external_chrc *chrc, int fd, struct bt_att *att) { struct client_io *client; client = queue_find(chrc->write_ios, match_client_att, att); if (client) return client; client = client_io_new(chrc, fd, att); if (!chrc->write_ios) chrc->write_ios = queue_new(); queue_push_tail(chrc->write_ios, client); return client; } static void acquire_write_reply(DBusMessage *message, void *user_data) { struct pending_op *op = user_data; struct external_chrc *chrc; struct client_io *client; DBusError err; int fd; uint16_t mtu; if (!op->owner_queue) { DBG("Pending write was canceled when object got removed"); return; } chrc = gatt_db_attribute_get_user_data(op->attrib); dbus_error_init(&err); if (dbus_set_error_from_message(&err, message) == TRUE) { uint8_t ecode; error("Failed to acquire write: %s\n", err.name); ecode = dbus_error_to_att_ecode(err.name, err.message, BT_ATT_ERROR_WRITE_NOT_PERMITTED); dbus_error_free(&err); if (ecode != BT_ATT_ERROR_UNLIKELY) { gatt_db_attribute_write_result(op->attrib, op->id, ecode); pending_op_free(op); return; } goto retry; } if ((dbus_message_get_args(message, NULL, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_UINT16, &mtu, DBUS_TYPE_INVALID) == false)) { error("Invalid AcquireWrite response\n"); goto retry; } DBG("AcquireWrite success: fd %d MTU %u\n", fd, mtu); client = client_write_io_get(chrc, fd, op->att); if (!client) goto retry; while ((op = queue_peek_head(chrc->pending_writes)) != NULL) { if (sock_io_send(client->io, op->data.iov_base, op->data.iov_len) < 0) goto retry; gatt_db_attribute_write_result(op->attrib, op->id, 0); pending_op_free(op); } return; retry: flush_pending_writes(chrc->proxy, chrc->pending_writes); } static void acquire_write_setup(DBusMessageIter *iter, void *user_data) { struct pending_op *op = user_data; DBusMessageIter dict; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); append_options(&dict, op); dbus_message_iter_close_container(iter, &dict); } static struct pending_op *acquire_write(struct external_chrc *chrc, struct bt_att *att, struct gatt_db_attribute *attrib, unsigned int id, const uint8_t *value, size_t len) { struct pending_op *op; bool acquiring = !queue_isempty(chrc->pending_writes); op = pending_write_new(att, chrc->pending_writes, attrib, id, value, len, 0, false, false); if (acquiring) return op; if (g_dbus_proxy_method_call(chrc->proxy, "AcquireWrite", acquire_write_setup, acquire_write_reply, op, NULL)) return op; pending_op_free(op); return NULL; } static struct client_io * client_notify_io_get(struct external_chrc *chrc, int fd, struct bt_att *att) { struct client_io *client; client = queue_find(chrc->notify_ios, match_client_att, att); if (client) return client; client = client_io_new(chrc, fd, att); io_set_read_handler(client->io, sock_io_read, client, NULL); if (!chrc->notify_ios) chrc->notify_ios = queue_new(); queue_push_tail(chrc->notify_ios, client); return client; } static void acquire_notify_reply(DBusMessage *message, void *user_data) { struct pending_op *op = user_data; struct external_chrc *chrc = (void *) op->data.iov_base; struct client_io *client; DBusError err; int fd; uint16_t mtu; if (!op->owner_queue) { DBG("Pending notify was canceled when object got removed"); return; } dbus_error_init(&err); if (dbus_set_error_from_message(&err, message) == TRUE) { error("Failed to acquire notify: %s\n", err.name); if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY) || dbus_error_has_name(&err, ERROR_FAILED)) { dbus_error_free(&err); return; } dbus_error_free(&err); goto retry; } if ((dbus_message_get_args(message, NULL, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_UINT16, &mtu, DBUS_TYPE_INVALID) == false)) { error("Invalid AcquirNotify response\n"); goto retry; } DBG("AcquireNotify success: fd %d MTU %u\n", fd, mtu); client = client_notify_io_get(chrc, fd, op->att); if (!client) goto retry; __sync_fetch_and_add(&chrc->ntfy_cnt, 1); return; retry: g_dbus_proxy_method_call(chrc->proxy, "StartNotify", NULL, NULL, NULL, NULL); __sync_fetch_and_add(&chrc->ntfy_cnt, 1); } static void acquire_notify_setup(DBusMessageIter *iter, void *user_data) { DBusMessageIter dict; struct pending_op *op = user_data; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); append_options(&dict, op); dbus_message_iter_close_container(iter, &dict); } static uint8_t ccc_write_cb(struct pending_op *op, void *user_data) { struct external_chrc *chrc = user_data; struct client_io *client; DBusMessageIter iter; uint16_t value; value = op ? PTR_TO_UINT(op->data.iov_base) : 0; DBG("External CCC write received with value: 0x%04x", value); /* Notifications/indications disabled */ if (!value) { if (!chrc->ntfy_cnt) goto done; client = queue_remove_if(chrc->notify_ios, match_client_att, op ? op->att : NULL); if (client) { client_io_free(client); __sync_sub_and_fetch(&chrc->ntfy_cnt, 1); goto done; } if (__sync_sub_and_fetch(&chrc->ntfy_cnt, 1)) goto done; /* * Send request to stop notifying. This is best-effort * operation, so simply ignore the return the value. */ g_dbus_proxy_method_call(chrc->proxy, "StopNotify", NULL, NULL, NULL, NULL); goto done; } if (chrc->ntfy_cnt == UINT_MAX) /* Maximum number of per-device CCC descriptors configured */ return BT_ATT_ERROR_INSUFFICIENT_RESOURCES; /* Don't support undefined CCC values yet */ if (value > 2 || (value == 1 && !(chrc->props & BT_GATT_CHRC_PROP_NOTIFY)) || (value == 2 && !(chrc->props & BT_GATT_CHRC_PROP_INDICATE))) return BT_ERROR_CCC_IMPROPERLY_CONFIGURED; client = queue_find(chrc->notify_ios, match_client_att, op->att); if (client) { __sync_fetch_and_add(&chrc->ntfy_cnt, 1); goto done; } /* Make use of AcquireNotify if supported */ if (g_dbus_proxy_get_property(chrc->proxy, "NotifyAcquired", &iter)) { op->data.iov_base = (void *) chrc; op->data.iov_len = sizeof(chrc); op->owner_queue = chrc->pending_writes; if (g_dbus_proxy_method_call(chrc->proxy, "AcquireNotify", acquire_notify_setup, acquire_notify_reply, op, pending_op_free)) return 0; } /* * Always call StartNotify for an incoming enable and ignore the return * value for now. */ if (g_dbus_proxy_method_call(chrc->proxy, "StartNotify", NULL, NULL, NULL, NULL) == FALSE) return BT_ATT_ERROR_UNLIKELY; __sync_fetch_and_add(&chrc->ntfy_cnt, 1); done: if (op) pending_op_free(op); return 0; } static void property_changed_cb(GDBusProxy *proxy, const char *name, DBusMessageIter *iter, void *user_data) { struct external_chrc *chrc = user_data; DBusMessageIter array; uint8_t *value = NULL; int len = 0; if (strcmp(name, "Value")) return; if (iter) { if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) { DBG("Malformed \"Value\" property received"); return; } dbus_message_iter_recurse(iter, &array); dbus_message_iter_get_fixed_array(&array, &value, &len); if (len < 0) { DBG("Malformed \"Value\" property received"); return; } } /* Truncate the value if it's too large */ len = MIN(BT_ATT_MAX_VALUE_LEN, len); value = len ? value : NULL; send_notification_to_devices(chrc->service->app->database, gatt_db_attribute_get_handle(chrc->attrib), value, len, gatt_db_attribute_get_handle(chrc->ccc), conf_cb, proxy); } static bool database_add_ccc(struct external_service *service, struct external_chrc *chrc) { if (!(chrc->props & BT_GATT_CHRC_PROP_NOTIFY) && !(chrc->props & BT_GATT_CHRC_PROP_INDICATE)) return true; /* Always set read/write permissions */ chrc->ccc_perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_READ; chrc->ccc = service_add_ccc(service->attrib, service->app->database, ccc_write_cb, chrc, chrc->ccc_perm, NULL); if (!chrc->ccc) { error("Failed to create CCC entry for characteristic"); return false; } if (g_dbus_proxy_set_property_watch(chrc->proxy, property_changed_cb, chrc) == FALSE) { error("Failed to set up property watch for characteristic"); return false; } DBG("Created CCC entry for characteristic"); return true; } static void cep_write_cb(struct gatt_db_attribute *attrib, int err, void *user_data) { if (err) DBG("Failed to store CEP value in the database"); else DBG("Stored CEP value in the database"); } static bool database_add_cep(struct external_service *service, struct external_chrc *chrc) { struct gatt_db_attribute *cep; bt_uuid_t uuid; uint8_t value[2]; if (!chrc->ext_props) return true; bt_uuid16_create(&uuid, GATT_CHARAC_EXT_PROPER_UUID); cep = gatt_db_service_add_descriptor(service->attrib, &uuid, BT_ATT_PERM_READ, NULL, NULL, NULL); if (!cep) { error("Failed to create CEP entry for characteristic"); return false; } memset(value, 0, sizeof(value)); value[0] = chrc->ext_props; if (!gatt_db_attribute_write(cep, 0, value, sizeof(value), 0, NULL, cep_write_cb, NULL)) { DBG("Failed to store CEP value in the database"); return false; } DBG("Created CEP entry for characteristic"); return true; } static void desc_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct external_desc *desc = user_data; struct btd_device *device; if (desc->attrib != attrib) { error("Read callback called with incorrect attribute"); goto fail; } device = att_get_device(att); if (!device) { error("Unable to find device object"); goto fail; } if (send_read(att, attrib, desc->proxy, desc->pending_reads, id, offset)) return; fail: gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_UNLIKELY, NULL, 0); } static void desc_write_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { struct external_desc *desc = user_data; struct btd_device *device; if (desc->attrib != attrib) { error("Read callback called with incorrect attribute"); goto fail; } device = att_get_device(att); if (!device) { error("Unable to find device object"); goto fail; } if (opcode == BT_ATT_OP_PREP_WRITE_REQ) { if (!btd_device_is_trusted(device) && !desc->prep_authorized && desc->req_prep_authorization) send_write(att, attrib, desc->proxy, desc->pending_writes, id, value, len, offset, false, true); else gatt_db_attribute_write_result(attrib, id, 0); return; } if (opcode == BT_ATT_OP_EXEC_WRITE_REQ) desc->prep_authorized = false; if (send_write(att, attrib, desc->proxy, desc->pending_writes, id, value, len, offset, false, false)) return; fail: gatt_db_attribute_write_result(attrib, id, BT_ATT_ERROR_UNLIKELY); } static void write_handle(struct GDBusProxy *proxy, uint16_t handle) { DBusMessageIter iter; /* Check if the attribute has the Handle property */ if (!g_dbus_proxy_get_property(proxy, "Handle", &iter)) return; g_dbus_proxy_set_property_basic(proxy, "Handle", DBUS_TYPE_UINT16, &handle, NULL, NULL, NULL); } static bool database_add_desc(struct external_service *service, struct external_desc *desc) { uint16_t handle; bt_uuid_t uuid; char str[MAX_LEN_UUID_STR]; if (!parse_handle(desc->proxy, &handle)) { error("Failed to read \"Handle\" property of descriptor"); return false; } if (!parse_uuid(desc->proxy, &uuid)) { error("Failed to read \"UUID\" property of descriptor"); return false; } desc->attrib = gatt_db_service_insert_descriptor(service->attrib, handle, &uuid, desc->perm, desc_read_cb, desc_write_cb, desc); if (!desc->attrib) { error("Failed to create descriptor entry in database"); return false; } desc->handled = true; if (!handle) { handle = gatt_db_attribute_get_handle(desc->attrib); write_handle(desc->proxy, handle); } bt_uuid_to_string(&uuid, str, sizeof(str)); DBG("handle 0x%04x UUID %s", handle, str); return true; } static void chrc_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct external_chrc *chrc = user_data; struct btd_device *device; if (chrc->attrib != attrib) { error("Read callback called with incorrect attribute"); goto fail; } device = att_get_device(att); if (!device) { error("Unable to find device object"); goto fail; } if (send_read(att, attrib, chrc->proxy, chrc->pending_reads, id, offset)) return; fail: gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_UNLIKELY, NULL, 0); } static void chrc_write_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { struct external_chrc *chrc = user_data; struct client_io *client; struct btd_device *device; struct queue *queue; DBusMessageIter iter; if (chrc->attrib != attrib) { error("Write callback called with incorrect attribute"); goto fail; } device = att_get_device(att); if (!device) { error("Unable to find device object"); goto fail; } if (!(chrc->props & BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP)) queue = chrc->pending_writes; else queue = NULL; if (opcode == BT_ATT_OP_PREP_WRITE_REQ) { if (!btd_device_is_trusted(device) && !chrc->prep_authorized && chrc->req_prep_authorization) send_write(att, attrib, chrc->proxy, queue, id, value, len, offset, true, true); else gatt_db_attribute_write_result(attrib, id, 0); return; } if (opcode == BT_ATT_OP_EXEC_WRITE_REQ) chrc->prep_authorized = false; client = queue_find(chrc->write_ios, match_client_att, att); if (client) { if (sock_io_send(client->io, value, len) < 0) { error("Unable to write: %s", strerror(errno)); goto fail; } gatt_db_attribute_write_result(attrib, id, 0); return; } if (g_dbus_proxy_get_property(chrc->proxy, "WriteAcquired", &iter)) { if (acquire_write(chrc, att, attrib, id, value, len)) return; } if (send_write(att, attrib, chrc->proxy, queue, id, value, len, offset, false, false)) return; fail: gatt_db_attribute_write_result(attrib, id, BT_ATT_ERROR_UNLIKELY); } static void include_services(void *data ,void *userdata) { char *obj = data; struct external_service *service = userdata; struct gatt_db_attribute *attrib; struct external_service *service_inc; DBG("path %s", obj); service_inc = queue_find(service->app->services, match_service_by_path, obj); if (!service_inc) { error("include service not found\n"); return; } attrib = gatt_db_service_add_included(service->attrib, service_inc->attrib); if (!attrib) { error("include service attributes failed\n"); return; } service->attrib = attrib; } static void database_add_includes(struct external_service *service) { queue_foreach(service->includes, include_services, service); } static bool database_add_chrc(struct external_service *service, struct external_chrc *chrc) { uint16_t handle = 0, value_handle; bt_uuid_t uuid; char str[MAX_LEN_UUID_STR]; const struct queue_entry *entry; if (!parse_handle(chrc->proxy, &value_handle)) { error("Failed to read \"Handle\" property of characteristic"); return false; } if (!parse_uuid(chrc->proxy, &uuid)) { error("Failed to read \"UUID\" property of characteristic"); return false; } if (!check_service_path(chrc->proxy, service)) { error("Invalid service path for characteristic"); return false; } if (value_handle) handle = value_handle - 1; chrc->attrib = gatt_db_service_insert_characteristic(service->attrib, handle, value_handle, &uuid, chrc->perm, chrc->props, chrc_read_cb, chrc_write_cb, chrc); if (!chrc->attrib) { error("Failed to create characteristic entry in database"); return false; } if (!database_add_ccc(service, chrc)) return false; if (!database_add_cep(service, chrc)) return false; if (!handle) { handle = gatt_db_attribute_get_handle(chrc->attrib); write_handle(chrc->proxy, handle); } bt_uuid_to_string(&uuid, str, sizeof(str)); DBG("handle 0x%04x UUID %s", handle, str); /* Handle the descriptors that belong to this characteristic. */ for (entry = queue_get_entries(service->descs); entry; entry = entry->next) { struct external_desc *desc = entry->data; if (desc->handled || g_strcmp0(desc->chrc_path, chrc->path)) continue; if (!database_add_desc(service, desc)) { chrc->attrib = NULL; error("Failed to create descriptor entry"); return false; } } return true; } static bool match_desc_unhandled(const void *a, const void *b) { const struct external_desc *desc = a; return !desc->handled; } static bool database_add_service(struct external_service *service) { bt_uuid_t uuid; bool primary; uint16_t handle; const struct queue_entry *entry; char str[MAX_LEN_UUID_STR]; if (!parse_uuid(service->proxy, &uuid)) { error("Failed to read \"UUID\" property of service"); return false; } if (!parse_primary(service->proxy, &primary)) { error("Failed to read \"Primary\" property of service"); return false; } if (!parse_includes(service->proxy, service)) { error("Failed to read \"Includes\" property of service"); return false; } if (!parse_handle(service->proxy, &handle)) { error("Failed to read \"Handle\" property of service"); return false; } service->attrib = gatt_db_insert_service(service->app->database->db, handle, &uuid, primary, service->attr_cnt); if (!service->attrib) return false; if (!handle) { handle = gatt_db_attribute_get_handle(service->attrib); write_handle(service->proxy, handle); } bt_uuid_to_string(&uuid, str, sizeof(str)); DBG("handle 0x%04x UUID %s", handle, str); database_add_includes(service); entry = queue_get_entries(service->chrcs); while (entry) { struct external_chrc *chrc = entry->data; if (!database_add_chrc(service, chrc)) { error("Failed to add characteristic"); goto fail; } entry = entry->next; } /* If there are any unhandled descriptors, return an error */ if (queue_find(service->descs, match_desc_unhandled, NULL)) { error("Found descriptor with no matching characteristic!"); goto fail; } gatt_db_service_set_active(service->attrib, true); return true; fail: gatt_db_remove_service(service->app->database->db, service->attrib); service->attrib = NULL; return false; } static bool database_add_app(struct gatt_app *app) { const struct queue_entry *entry; entry = queue_get_entries(app->services); while (entry) { if (!database_add_service(entry->data)) { error("Failed to add service"); return false; } entry = entry->next; } return true; } static int profile_device_probe(struct btd_service *service) { struct btd_profile *p = btd_service_get_profile(service); DBG("%s probed", p->name); return 0; } static void profile_device_remove(struct btd_service *service) { struct btd_profile *p = btd_service_get_profile(service); DBG("%s removed", p->name); } static int profile_device_accept(struct btd_service *service) { struct btd_profile *p = btd_service_get_profile(service); DBG("%s accept", p->name); return 0; } static int profile_add(struct external_profile *profile, const char *uuid) { struct btd_profile *p; p = new0(struct btd_profile, 1); /* Assign directly to avoid having extra fields */ p->name = (const void *) g_strdup_printf("%s%s/%s", profile->app->owner, g_dbus_proxy_get_path(profile->proxy), uuid); if (!p->name) { free(p); return -ENOMEM; } p->remote_uuid = (const void *) g_strdup(uuid); if (!p->remote_uuid) { g_free((void *) p->name); free(p); return -ENOMEM; } p->device_probe = profile_device_probe; p->device_remove = profile_device_remove; p->accept = profile_device_accept; p->auto_connect = true; p->external = true; queue_push_tail(profile->profiles, p); DBG("Added \"%s\"", p->name); return 0; } static void add_profile(void *data, void *user_data) { struct btd_adapter *adapter = user_data; if (btd_profile_register(data)) return; adapter_add_profile(adapter, data); } static struct external_profile *create_profile(struct gatt_app *app, GDBusProxy *proxy, const char *path) { struct external_profile *profile; DBusMessageIter iter, array; if (!path || !g_str_has_prefix(path, "/")) return NULL; profile = new0(struct external_profile, 1); profile->app = app; profile->proxy = g_dbus_proxy_ref(proxy); profile->profiles = queue_new(); if (!g_dbus_proxy_get_property(proxy, "UUIDs", &iter)) { DBG("UUIDs property not found"); goto fail; } if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { DBG("UUIDs wrongly formatted"); goto fail; } dbus_message_iter_recurse(&iter, &array); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) { const char *uuid; dbus_message_iter_get_basic(&array, &uuid); if (profile_add(profile, uuid) < 0) goto fail; dbus_message_iter_next(&array); } if (queue_isempty(profile->profiles)) goto fail; queue_foreach(profile->profiles, add_profile, app->database->adapter); queue_push_tail(app->profiles, profile); return profile; fail: profile_free(profile); return NULL; } static void register_profile(void *data, void *user_data) { struct gatt_app *app = user_data; GDBusProxy *proxy = data; const char *iface = g_dbus_proxy_get_interface(proxy); const char *path = g_dbus_proxy_get_path(proxy); if (app->failed) return; if (g_strcmp0(iface, GATT_PROFILE_IFACE) == 0) { struct external_profile *profile; profile = create_profile(app, proxy, path); if (!profile) { app->failed = true; return; } } } static void register_service(void *data, void *user_data) { struct gatt_app *app = user_data; GDBusProxy *proxy = data; const char *iface = g_dbus_proxy_get_interface(proxy); const char *path = g_dbus_proxy_get_path(proxy); if (app->failed) return; if (g_strcmp0(iface, GATT_SERVICE_IFACE) == 0) { struct external_service *service; service = create_service(app, proxy, path); if (!service) { app->failed = true; return; } } } static void register_characteristic(void *data, void *user_data) { struct gatt_app *app = user_data; GDBusProxy *proxy = data; const char *iface; const char *path; if (app->failed) return; iface = g_dbus_proxy_get_interface(proxy); path = g_dbus_proxy_get_path(proxy); if (g_strcmp0(iface, GATT_CHRC_IFACE) == 0) { struct external_chrc *chrc; chrc = chrc_create(app, proxy, path); if (!chrc) { app->failed = true; return; } } } static void register_descriptor(void *data, void *user_data) { struct gatt_app *app = user_data; GDBusProxy *proxy = data; const char *iface = g_dbus_proxy_get_interface(proxy); if (app->failed) return; if (g_strcmp0(iface, GATT_DESC_IFACE) == 0) { struct external_desc *desc; desc = desc_create(app, proxy); if (!desc) { app->failed = true; return; } } } static void client_ready_cb(GDBusClient *client, void *user_data) { struct gatt_app *app = user_data; DBusMessage *reply; bool fail = false; /* * Process received objects */ if (queue_isempty(app->proxies)) { error("No object received"); fail = true; reply = btd_error_failed(app->reg, "No object received"); goto reply; } queue_foreach(app->proxies, register_profile, app); queue_foreach(app->proxies, register_service, app); queue_foreach(app->proxies, register_characteristic, app); queue_foreach(app->proxies, register_descriptor, app); if ((queue_isempty(app->services) && queue_isempty(app->profiles)) || app->failed) { error("No valid external GATT objects found"); fail = true; reply = btd_error_failed(app->reg, "No valid service object found"); goto reply; } if (!database_add_app(app)) { error("Failed to create GATT service entry in local database"); fail = true; reply = btd_error_failed(app->reg, "Failed to create entry in database"); goto reply; } DBG("GATT application registered: %s:%s", app->owner, app->path); reply = dbus_message_new_method_return(app->reg); reply: g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(app->reg); app->reg = NULL; if (fail) remove_app(app); } static struct gatt_app *create_app(DBusConnection *conn, DBusMessage *msg, const char *path) { struct gatt_app *app; const char *sender = dbus_message_get_sender(msg); if (!path || !g_str_has_prefix(path, "/")) return NULL; app = new0(struct gatt_app, 1); app->client = g_dbus_client_new_full(conn, sender, path, path); if (!app->client) goto fail; app->owner = g_strdup(sender); if (!app->owner) goto fail; app->path = g_strdup(path); if (!app->path) goto fail; app->services = queue_new(); app->profiles = queue_new(); app->proxies = queue_new(); app->reg = dbus_message_ref(msg); g_dbus_client_set_disconnect_watch(app->client, client_disconnect_cb, app); g_dbus_client_set_proxy_handlers(app->client, proxy_added_cb, proxy_removed_cb, NULL, app); g_dbus_client_set_ready_watch(app->client, client_ready_cb, app); return app; fail: app_free(app); return NULL; } static DBusMessage *manager_register_app(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_gatt_database *database = user_data; const char *sender = dbus_message_get_sender(msg); DBusMessageIter args; const char *path; struct gatt_app *app; struct svc_match_data match_data; if (!dbus_message_iter_init(msg, &args)) return btd_error_invalid_args(msg); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) return btd_error_invalid_args(msg); dbus_message_iter_get_basic(&args, &path); match_data.path = path; match_data.sender = sender; if (queue_find(database->apps, match_app, &match_data)) return btd_error_already_exists(msg); dbus_message_iter_next(&args); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) return btd_error_invalid_args(msg); app = create_app(conn, msg, path); if (!app) return btd_error_failed(msg, "Failed to register application"); DBG("Registering application: %s:%s", sender, path); app->database = database; queue_push_tail(database->apps, app); return NULL; } static DBusMessage *manager_unregister_app(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_gatt_database *database = user_data; const char *sender = dbus_message_get_sender(msg); const char *path; DBusMessageIter args; struct gatt_app *app; struct svc_match_data match_data; if (!dbus_message_iter_init(msg, &args)) return btd_error_invalid_args(msg); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) return btd_error_invalid_args(msg); dbus_message_iter_get_basic(&args, &path); match_data.path = path; match_data.sender = sender; app = queue_remove_if(database->apps, match_app, &match_data); if (!app) return btd_error_does_not_exist(msg); app_free(app); return dbus_message_new_method_return(msg); } static const GDBusMethodTable manager_methods[] = { { GDBUS_ASYNC_METHOD("RegisterApplication", GDBUS_ARGS({ "application", "o" }, { "options", "a{sv}" }), NULL, manager_register_app) }, { GDBUS_ASYNC_METHOD("UnregisterApplication", GDBUS_ARGS({ "application", "o" }), NULL, manager_unregister_app) }, { } }; static uint8_t server_authorize(struct bt_att *att, uint8_t opcode, uint16_t handle, void *user_data) { struct btd_gatt_database *database = user_data; struct device_state *state; bdaddr_t bdaddr; uint8_t bdaddr_type; if (!get_dst_info(att, &bdaddr, &bdaddr_type)) return 0; /* Skip if there is no device state */ state = find_device_state(database, &bdaddr, bdaddr_type); if (!state) return 0; /* Skip if client doesn't support Robust Caching */ if (!(state->cli_feat[0] & BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING)) return 0; if (state->change_aware) return 0; if (state->out_of_sync) { state->out_of_sync = false; state->change_aware = true; return 0; } state->out_of_sync = true; return BT_ATT_ERROR_DB_OUT_OF_SYNC; } static void eatt_confirm_cb(GIOChannel *io, gpointer data) { char address[18]; uint8_t dst_type; bdaddr_t src, dst; GError *gerr = NULL; struct btd_device *device; struct bt_gatt_server *server; struct bt_att *att; bt_io_get(io, &gerr, BT_IO_OPT_SOURCE_BDADDR, &src, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_DEST_TYPE, &dst_type, BT_IO_OPT_DEST, address, BT_IO_OPT_INVALID); if (gerr) { error("bt_io_get: %s", gerr->message); g_error_free(gerr); goto drop; } DBG("New incoming EATT connection"); /* Confirm the device exists before accepting the connection, if the * device is using an RPA it could be that the MGMT event has not been * processed yet which would lead to create a second copy of the same * device using its identity address. */ device = btd_adapter_find_device(adapter_find(&src), &dst, dst_type); if (!device) { error("Unable to find device: %s", address); goto drop; } /* Only allow EATT connection from central */ if (btd_device_is_initiator(device)) { warn("EATT connection from peripheral may cause collisions"); goto drop; } server = btd_device_get_gatt_server(device); if (!server) { error("Unable to resolve bt_server"); goto drop; } att = bt_gatt_server_get_att(server); if (bt_att_get_channels(att) == btd_opts.gatt_channels) { DBG("EATT channel limit reached"); goto drop; } if (!bt_io_accept(io, connect_cb, NULL, NULL, &gerr)) { error("bt_io_accept: %s", gerr->message); g_error_free(gerr); goto drop; } return; drop: g_io_channel_shutdown(io, TRUE, NULL); } struct btd_gatt_database *btd_gatt_database_new(struct btd_adapter *adapter) { struct btd_gatt_database *database; GError *gerr = NULL; const bdaddr_t *addr; if (!adapter) return NULL; database = new0(struct btd_gatt_database, 1); database->adapter = btd_adapter_ref(adapter); database->db = gatt_db_new(); database->records = queue_new(); database->device_states = queue_new(); database->apps = queue_new(); database->profiles = queue_new(); database->ccc_callbacks = queue_new(); addr = btd_adapter_get_address(adapter); database->le_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, addr, BT_IO_OPT_SOURCE_TYPE, btd_adapter_get_address_type(adapter), BT_IO_OPT_CID, BT_ATT_CID, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_INVALID); if (!database->le_io) { error("Failed to start listening: %s", gerr->message); g_error_free(gerr); goto fail; } /* If just just 1 channel is enabled EATT is not required */ if (btd_opts.gatt_channels == 1) goto bredr; /* EATT socket, encryption is required */ database->eatt_io = bt_io_listen(NULL, eatt_confirm_cb, NULL, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, addr, BT_IO_OPT_SOURCE_TYPE, btd_adapter_get_address_type(adapter), BT_IO_OPT_PSM, BT_ATT_EATT_PSM, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_MTU, btd_opts.gatt_mtu, BT_IO_OPT_INVALID); if (!database->eatt_io) { g_error_free(gerr); goto fail; } bredr: /* BR/EDR socket */ if (btd_adapter_get_bredr(adapter)) { database->bredr_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, addr, BT_IO_OPT_PSM, BT_ATT_PSM, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_MTU, btd_opts.gatt_mtu, BT_IO_OPT_INVALID); if (database->bredr_io == NULL) { error("Failed to start listening: %s", gerr->message); g_error_free(gerr); goto fail; } } if (g_dbus_register_interface(btd_get_dbus_connection(), adapter_get_path(adapter), GATT_MANAGER_IFACE, manager_methods, NULL, NULL, database, NULL)) DBG("GATT Manager registered for adapter: %s", adapter_get_path(adapter)); register_core_services(database); database->db_id = gatt_db_register(database->db, gatt_db_service_added, gatt_db_service_removed, database, NULL); if (!database->db_id) goto fail; if (!dbs) dbs = queue_new(); queue_push_tail(dbs, database); return database; fail: gatt_database_free(database); return NULL; } void btd_gatt_database_destroy(struct btd_gatt_database *database) { if (!database) return; g_dbus_unregister_interface(btd_get_dbus_connection(), adapter_get_path(database->adapter), GATT_MANAGER_IFACE); queue_remove(dbs, database); gatt_database_free(database); } static bool match_db(const void *data, const void *user_data) { const struct btd_gatt_database *database = data; const struct gatt_db *db = user_data; return database->db == db; } struct btd_gatt_database *btd_gatt_database_get(struct gatt_db *db) { struct btd_gatt_database *database; database = queue_find(dbs, match_db, db); if (!database) return NULL; return database; } struct btd_adapter * btd_gatt_database_get_adapter(struct btd_gatt_database *database) { if (!database) return NULL; return database->adapter; } struct gatt_db *btd_gatt_database_get_db(struct btd_gatt_database *database) { if (!database) return NULL; return database->db; } void btd_gatt_database_server_connected(struct btd_gatt_database *database, struct bt_gatt_server *server) { struct bt_att *att = bt_gatt_server_get_att(server); struct device_state *state; bdaddr_t bdaddr; uint8_t bdaddr_type; if (!get_dst_info(att, &bdaddr, &bdaddr_type)) return; bt_gatt_server_set_authorize(server, server_authorize, database); state = find_device_state(database, &bdaddr, bdaddr_type); if (!state || !state->pending) return; send_notification_to_device(state, state->pending); state = find_device_state(database, &bdaddr, bdaddr_type); if (!state || !state->pending) return; free(state->pending->value); free(state->pending); state->pending = NULL; } void btd_gatt_database_att_disconnected(struct btd_gatt_database *database, struct btd_device *device) { struct bt_gatt_server *server = btd_device_get_gatt_server(device); struct bt_att *att = bt_gatt_server_get_att(server); struct device_state *state; const bdaddr_t *addr; uint8_t type; DBG(""); addr = device_get_address(device); type = btd_device_get_bdaddr_type(device); state = find_device_state(database, addr, type); if (!state) return; if (state->disc_id) bt_att_unregister_disconnect(att, state->disc_id); att_disconnected(0, state); } static void restore_ccc(struct btd_gatt_database *database, const bdaddr_t *addr, uint8_t addr_type, uint16_t value) { struct device_state *dev_state; struct ccc_state *ccc; dev_state = device_state_create(database, addr, addr_type); queue_push_tail(database->device_states, dev_state); ccc = new0(struct ccc_state, 1); ccc->handle = gatt_db_attribute_get_handle(database->svc_chngd_ccc); ccc->value = value; queue_push_tail(dev_state->ccc_states, ccc); } static void restore_state(struct btd_device *device, void *data) { struct btd_gatt_database *database = data; uint16_t ccc_le, ccc_bredr; device_load_svc_chng_ccc(device, &ccc_le, &ccc_bredr); if (ccc_le) { restore_ccc(database, device_get_address(device), device_get_le_address_type(device), ccc_le); DBG("%s LE", device_get_path(device)); } if (ccc_bredr) { restore_ccc(database, device_get_address(device), BDADDR_BREDR, ccc_bredr); DBG("%s BR/EDR", device_get_path(device)); } } void btd_gatt_database_restore_svc_chng_ccc(struct btd_gatt_database *database) { uint8_t value[4]; uint16_t handle, ccc_handle; if (!database) return; handle = gatt_db_attribute_get_handle(database->svc_chngd); ccc_handle = gatt_db_attribute_get_handle(database->svc_chngd_ccc); if (!handle || !ccc_handle) { error("Failed to obtain handles for \"Service Changed\"" " characteristic"); return; } /* restore states for bonded device that registered for Service Changed * indication */ btd_adapter_for_each_device(database->adapter, restore_state, database); put_le16(0x0001, value); put_le16(0xffff, value + 2); if (!gatt_db_attribute_notify(database->svc_chngd, value, sizeof(value), NULL)) error("Failed to notify Service Changed"); } bluez-5.87/src/PaxHeaders/sdpd-server.c0000644000000000000000000000005015063045254015050 xustar0020 atime=1783093737 20 ctime=1783094106 bluez-5.87/src/sdpd-server.c0000644000000000000000000001335115063045254014534 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2001-2002 Nokia Corporation * Copyright (C) 2002-2003 Maxim Krasnyansky * Copyright (C) 2002-2010 Marcel Holtmann * Copyright (C) 2002-2003 Stephen Crane * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/l2cap.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "log.h" #include "sdpd.h" static guint l2cap_id = 0, unix_id = 0; static int l2cap_sock = -1, unix_sock = -1; /* * SDP server initialization on startup includes creating the * l2cap and unix sockets over which discovery and registration clients * access us respectively */ static int init_server(uint16_t mtu, int central, int compat) { struct l2cap_options opts; struct sockaddr_l2 l2addr; struct sockaddr_un unaddr; socklen_t optlen; /* Register the public browse group root */ register_public_browse_group(); /* Register the SDP server's service record */ register_server_service(); /* Create L2CAP socket */ l2cap_sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (l2cap_sock < 0) { error("opening L2CAP socket: %s", strerror(errno)); return -1; } memset(&l2addr, 0, sizeof(l2addr)); l2addr.l2_family = AF_BLUETOOTH; bacpy(&l2addr.l2_bdaddr, BDADDR_ANY); l2addr.l2_psm = htobs(SDP_PSM); if (bind(l2cap_sock, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) { error("binding L2CAP socket: %s", strerror(errno)); return -1; } if (central) { int opt = L2CAP_LM_MASTER; if (setsockopt(l2cap_sock, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0) { error("setsockopt: %s", strerror(errno)); return -1; } } if (mtu > 0) { memset(&opts, 0, sizeof(opts)); optlen = sizeof(opts); if (getsockopt(l2cap_sock, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen) < 0) { error("getsockopt: %s", strerror(errno)); return -1; } opts.omtu = mtu; opts.imtu = mtu; if (setsockopt(l2cap_sock, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0) { error("setsockopt: %s", strerror(errno)); return -1; } } if (listen(l2cap_sock, 5) < 0) { error("listen: %s", strerror(errno)); return -1; } if (!compat) { unix_sock = -1; return 0; } /* Create local Unix socket */ unix_sock = socket(PF_UNIX, SOCK_STREAM, 0); if (unix_sock < 0) { error("opening UNIX socket: %s", strerror(errno)); return -1; } memset(&unaddr, 0, sizeof(unaddr)); unaddr.sun_family = AF_UNIX; strcpy(unaddr.sun_path, SDP_UNIX_PATH); unlink(unaddr.sun_path); if (bind(unix_sock, (struct sockaddr *) &unaddr, sizeof(unaddr)) < 0) { error("binding UNIX socket: %s", strerror(errno)); return -1; } if (listen(unix_sock, 5) < 0) { error("listen UNIX socket: %s", strerror(errno)); return -1; } chmod(SDP_UNIX_PATH, 0660); return 0; } static gboolean io_session_event(GIOChannel *chan, GIOCondition cond, gpointer data) { sdp_pdu_hdr_t hdr; uint8_t *buf; int sk, len, size; if (cond & G_IO_NVAL) return FALSE; sk = g_io_channel_unix_get_fd(chan); if (cond & (G_IO_HUP | G_IO_ERR)) goto cleanup; len = recv(sk, &hdr, sizeof(sdp_pdu_hdr_t), MSG_PEEK); if (len < 0 || (unsigned int) len < sizeof(sdp_pdu_hdr_t)) goto cleanup; size = sizeof(sdp_pdu_hdr_t) + ntohs(hdr.plen); buf = malloc(size); if (!buf) return TRUE; len = recv(sk, buf, size, 0); /* Check here only that the received message is not empty. * Incorrect length of message should be processed later * inside handle_request() in order to produce ErrorResponse. */ if (len <= 0) { free(buf); goto cleanup; } handle_request(sk, buf, len); return TRUE; cleanup: sdp_svcdb_collect_all(sk); sdp_cstate_cleanup(sk); return FALSE; } static gboolean io_accept_event(GIOChannel *chan, GIOCondition cond, gpointer data) { GIOChannel *io; int nsk; if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) return FALSE; if (data == &l2cap_sock) { struct sockaddr_l2 addr; socklen_t len = sizeof(addr); nsk = accept(l2cap_sock, (struct sockaddr *) &addr, &len); } else if (data == &unix_sock) { struct sockaddr_un addr; socklen_t len = sizeof(addr); nsk = accept(unix_sock, (struct sockaddr *) &addr, &len); } else return FALSE; if (nsk < 0) { error("Can't accept connection: %s", strerror(errno)); return TRUE; } io = g_io_channel_unix_new(nsk); g_io_channel_set_close_on_unref(io, TRUE); g_io_add_watch(io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, io_session_event, data); g_io_channel_unref(io); return TRUE; } int start_sdp_server(uint16_t mtu, uint32_t flags) { int compat = flags & SDP_SERVER_COMPAT; int central = flags & SDP_SERVER_CENTRAL; GIOChannel *io; info("Starting SDP server"); if (init_server(mtu, central, compat) < 0) { error("Server initialization failed"); return -1; } io = g_io_channel_unix_new(l2cap_sock); g_io_channel_set_close_on_unref(io, TRUE); l2cap_id = g_io_add_watch(io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, io_accept_event, &l2cap_sock); g_io_channel_unref(io); if (compat && unix_sock > fileno(stderr)) { io = g_io_channel_unix_new(unix_sock); g_io_channel_set_close_on_unref(io, TRUE); unix_id = g_io_add_watch(io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, io_accept_event, &unix_sock); g_io_channel_unref(io); } return 0; } void stop_sdp_server(void) { info("Stopping SDP server"); sdp_svcdb_reset(); if (unix_id > 0) g_source_remove(unix_id); if (l2cap_id > 0) g_source_remove(l2cap_id); l2cap_id = unix_id = 0; l2cap_sock = unix_sock = -1; } bluez-5.87/src/PaxHeaders/gatt-client.h0000644000000000000000000000005014131623652015031 xustar0020 atime=1783093794 20 ctime=1783094106 bluez-5.87/src/gatt-client.h0000644000000000000000000000177214131623652014521 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Google Inc. * * */ struct btd_gatt_client; struct btd_gatt_client *btd_gatt_client_new(struct btd_device *device); void btd_gatt_client_destroy(struct btd_gatt_client *client); void btd_gatt_client_ready(struct btd_gatt_client *client); void btd_gatt_client_connected(struct btd_gatt_client *client); void btd_gatt_client_service_added(struct btd_gatt_client *client, struct gatt_db_attribute *attrib); void btd_gatt_client_service_removed(struct btd_gatt_client *client, struct gatt_db_attribute *attrib); void btd_gatt_client_disconnected(struct btd_gatt_client *client); void btd_gatt_client_eatt_connect(struct btd_gatt_client *client); typedef void (*btd_gatt_client_service_path_t)(const char *service_path, void *user_data); void btd_gatt_client_foreach_service(struct btd_gatt_client *client, btd_gatt_client_service_path_t func, void *user_data); bluez-5.87/src/PaxHeaders/log.h0000644000000000000000000000005014267331527013406 xustar0020 atime=1783092968 20 ctime=1783094104 bluez-5.87/src/log.h0000644000000000000000000000361414267331527013073 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #include #include void info(const char *format, ...) __attribute__((format(printf, 1, 2))); void btd_log(uint16_t index, int priority, const char *format, ...) __attribute__((format(printf, 3, 4))); void btd_error(uint16_t index, const char *format, ...) __attribute__((format(printf, 2, 3))); void btd_warn(uint16_t index, const char *format, ...) __attribute__((format(printf, 2, 3))); void btd_info(uint16_t index, const char *format, ...) __attribute__((format(printf, 2, 3))); void btd_debug(uint16_t index, const char *format, ...) __attribute__((format(printf, 2, 3))); void __btd_log_init(const char *debug, int detach); void __btd_log_cleanup(void); void __btd_toggle_debug(void); struct btd_debug_desc { const char *file; #define BTD_DEBUG_FLAG_DEFAULT (0) #define BTD_DEBUG_FLAG_PRINT (1 << 0) unsigned int flags; } __attribute__((aligned(8))); void __btd_enable_debug(struct btd_debug_desc *start, struct btd_debug_desc *stop); /** * DBG: * @fmt: format string * @arg...: list of arguments * * Simple macro around btd_debug() which also include the function * name it is called in. */ #define DBG_IDX(idx, fmt, arg...) do { \ static struct btd_debug_desc __btd_debug_desc \ __attribute__((used, section("__debug"), aligned(8))) = { \ .file = __FILE__, .flags = BTD_DEBUG_FLAG_DEFAULT, \ }; \ if (__btd_debug_desc.flags & BTD_DEBUG_FLAG_PRINT) \ btd_debug(idx, fmt, ## arg); \ } while (0) #define DBG(fmt, arg...) \ DBG_IDX(0xffff, "%s:%s() " fmt, __FILE__, __func__, ## arg) #define error(fmt, arg...) \ btd_error(0xffff, "%s:%s() " fmt, __FILE__, __func__, ## arg) #define warn(fmt, arg...) \ btd_warn(0xffff, "%s:%s() " fmt, __FILE__, __func__, ## arg) bluez-5.87/src/PaxHeaders/plugin.h0000644000000000000000000000005015142177620014116 xustar0020 atime=1783093605 20 ctime=1783094106 bluez-5.87/src/plugin.h0000644000000000000000000000260315142177620013600 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ enum bluetooth_plugin_priority { BLUETOOTH_PLUGIN_PRIORITY_LOW = -100, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT = 0, BLUETOOTH_PLUGIN_PRIORITY_HIGH = 100 }; struct bluetooth_plugin_desc { const char *name; const char *version; int priority; int (*init) (void); void (*exit) (void); void *debug_start; void *debug_stop; }; #ifdef BLUETOOTH_PLUGIN_BUILTIN #define BLUETOOTH_PLUGIN_DEFINE(name, version, priority, init, exit) \ const struct bluetooth_plugin_desc \ __bluetooth_builtin_ ## name = { \ #name, version, priority, init, exit \ }; #else #if EXTERNAL_PLUGINS #define BLUETOOTH_PLUGIN_DEFINE(name, version, priority, init, exit) \ extern struct btd_debug_desc __start___debug[] \ __attribute__ ((weak, visibility("hidden"))); \ extern struct btd_debug_desc __stop___debug[] \ __attribute__ ((weak, visibility("hidden"))); \ extern const struct bluetooth_plugin_desc \ bluetooth_plugin_desc \ __attribute__ ((visibility("default"))); \ const struct bluetooth_plugin_desc bluetooth_plugin_desc = { \ #name, version, priority, init, exit, \ __start___debug, __stop___debug \ }; #else #error "Requested non built-in plugin, while external plugins is disabled" #endif #endif bluez-5.87/src/PaxHeaders/sdp-client.h0000644000000000000000000000005014015011623014646 xustar0020 atime=1783093671 20 ctime=1783094106 bluez-5.87/src/sdp-client.h0000644000000000000000000000137014015011623014330 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ typedef void (*bt_callback_t) (sdp_list_t *recs, int err, gpointer user_data); typedef void (*bt_destroy_t) (gpointer user_data); int bt_search(const bdaddr_t *src, const bdaddr_t *dst, uuid_t *uuid, bt_callback_t cb, void *user_data, bt_destroy_t destroy, uint16_t flags); int bt_search_service(const bdaddr_t *src, const bdaddr_t *dst, uuid_t *uuid, bt_callback_t cb, void *user_data, bt_destroy_t destroy, uint16_t flags); int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst); void bt_clear_cached_session(const bdaddr_t *src, const bdaddr_t *dst); bluez-5.87/src/PaxHeaders/set.h0000644000000000000000000000005014621503015013403 xustar0020 atime=1783093799 20 ctime=1783094106 bluez-5.87/src/set.h0000644000000000000000000000102314621503015013060 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2023 Intel Corporation * * */ #define BTD_DEVICE_SET_INTERFACE "org.bluez.DeviceSet1" struct btd_device_set; struct btd_device_set *btd_set_add_device(struct btd_device *device, const uint8_t *ltk, const uint8_t sirk[16], uint8_t size); bool btd_set_remove_device(struct btd_device_set *set, struct btd_device *device); const char *btd_set_get_path(struct btd_device_set *set); bluez-5.87/src/PaxHeaders/settings.h0000644000000000000000000000005014267331527014465 xustar0020 atime=1783093123 20 ctime=1783094104 bluez-5.87/src/settings.h0000644000000000000000000000046014267331527014146 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. * */ int btd_settings_gatt_db_load(struct gatt_db *db, const char *filename); void btd_settings_gatt_db_store(struct gatt_db *db, const char *filename); bluez-5.87/src/PaxHeaders/shared0000644000000000000000000000005015221755526013645 xustar0020 atime=1783094113 20 ctime=1783094102 bluez-5.87/src/shared/0000755000000000000000000000000015221755526013403 5ustar00rootrootbluez-5.87/src/shared/PaxHeaders/queue.c0000644000000000000000000000005014015011623015171 xustar0020 atime=1783092877 20 ctime=1783094099 bluez-5.87/src/shared/queue.c0000644000000000000000000001320414015011623014652 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include "src/shared/util.h" #include "src/shared/queue.h" struct queue { int ref_count; struct queue_entry *head; struct queue_entry *tail; unsigned int entries; }; static struct queue *queue_ref(struct queue *queue) { if (!queue) return NULL; __sync_fetch_and_add(&queue->ref_count, 1); return queue; } static void queue_unref(struct queue *queue) { if (__sync_sub_and_fetch(&queue->ref_count, 1)) return; free(queue); } struct queue *queue_new(void) { struct queue *queue; queue = new0(struct queue, 1); queue->head = NULL; queue->tail = NULL; queue->entries = 0; return queue_ref(queue); } void queue_destroy(struct queue *queue, queue_destroy_func_t destroy) { if (!queue) return; queue_remove_all(queue, NULL, NULL, destroy); queue_unref(queue); } static struct queue_entry *queue_entry_new(void *data) { struct queue_entry *entry; entry = new0(struct queue_entry, 1); entry->data = data; return entry; } bool queue_push_tail(struct queue *queue, void *data) { struct queue_entry *entry; if (!queue) return false; entry = queue_entry_new(data); if (queue->tail) queue->tail->next = entry; queue->tail = entry; if (!queue->head) queue->head = entry; queue->entries++; return true; } bool queue_push_head(struct queue *queue, void *data) { struct queue_entry *entry; if (!queue) return false; entry = queue_entry_new(data); entry->next = queue->head; queue->head = entry; if (!queue->tail) queue->tail = entry; queue->entries++; return true; } bool queue_push_after(struct queue *queue, void *entry, void *data) { struct queue_entry *qentry, *tmp, *new_entry; qentry = NULL; if (!queue) return false; for (tmp = queue->head; tmp; tmp = tmp->next) { if (tmp->data == entry) { qentry = tmp; break; } } if (!qentry) return false; new_entry = queue_entry_new(data); new_entry->next = qentry->next; if (!qentry->next) queue->tail = new_entry; qentry->next = new_entry; queue->entries++; return true; } void *queue_pop_head(struct queue *queue) { struct queue_entry *entry; void *data; if (!queue || !queue->head) return NULL; entry = queue->head; if (!queue->head->next) { queue->head = NULL; queue->tail = NULL; } else queue->head = queue->head->next; data = entry->data; free(entry); queue->entries--; return data; } void *queue_peek_head(struct queue *queue) { if (!queue || !queue->head) return NULL; return queue->head->data; } void *queue_peek_tail(struct queue *queue) { if (!queue || !queue->tail) return NULL; return queue->tail->data; } void queue_foreach(struct queue *queue, queue_foreach_func_t function, void *user_data) { struct queue_entry *entry; if (!queue || !function) return; entry = queue->head; if (!entry) return; queue_ref(queue); while (entry && queue->head && queue->ref_count > 1) { struct queue_entry *next; next = entry->next; function(entry->data, user_data); entry = next; } queue_unref(queue); } static bool direct_match(const void *a, const void *b) { return a == b; } void *queue_find(struct queue *queue, queue_match_func_t function, const void *match_data) { struct queue_entry *entry; if (!queue) return NULL; if (!function) function = direct_match; for (entry = queue->head; entry; entry = entry->next) if (function(entry->data, match_data)) return entry->data; return NULL; } bool queue_remove(struct queue *queue, void *data) { struct queue_entry *entry, *prev; if (!queue) return false; for (entry = queue->head, prev = NULL; entry; prev = entry, entry = entry->next) { if (entry->data != data) continue; if (prev) prev->next = entry->next; else queue->head = entry->next; if (!entry->next) queue->tail = prev; free(entry); queue->entries--; return true; } return false; } void *queue_remove_if(struct queue *queue, queue_match_func_t function, void *user_data) { struct queue_entry *entry, *prev = NULL; if (!queue) return NULL; if (!function) function = direct_match; entry = queue->head; while (entry) { if (function(entry->data, user_data)) { void *data; if (prev) prev->next = entry->next; else queue->head = entry->next; if (!entry->next) queue->tail = prev; data = entry->data; free(entry); queue->entries--; return data; } else { prev = entry; entry = entry->next; } } return NULL; } unsigned int queue_remove_all(struct queue *queue, queue_match_func_t function, void *user_data, queue_destroy_func_t destroy) { struct queue_entry *entry; unsigned int count = 0; if (!queue) return 0; entry = queue->head; if (function) { while (entry) { void *data; unsigned int entries = queue->entries; data = queue_remove_if(queue, function, user_data); if (entries == queue->entries) break; if (destroy) destroy(data); count++; } } else { queue->head = NULL; queue->tail = NULL; queue->entries = 0; while (entry) { struct queue_entry *tmp = entry; entry = entry->next; if (destroy) destroy(tmp->data); free(tmp); count++; } } return count; } const struct queue_entry *queue_get_entries(struct queue *queue) { if (!queue) return NULL; return queue->head; } unsigned int queue_length(struct queue *queue) { if (!queue) return 0; return queue->entries; } bool queue_isempty(struct queue *queue) { if (!queue) return true; return queue->entries == 0; } bluez-5.87/src/shared/PaxHeaders/tester.h0000644000000000000000000000005015063045254015373 xustar0020 atime=1783092985 20 ctime=1783094099 bluez-5.87/src/shared/tester.h0000644000000000000000000000451215063045254015056 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #include #include #include #include #define data(args...) ((const unsigned char[]) { args }) #define IOV_DATA(args...) \ { \ .iov_base = (void *)data(args), \ .iov_len = sizeof(data(args)), \ } #define IOV_NULL {} void tester_init(int *argc, char ***argv); int tester_run(void); bool tester_use_quiet(void); bool tester_use_debug(void); void tester_print(const char *format, ...) __attribute__((format(printf, 1, 2))); void tester_warn(const char *format, ...) __attribute__((format(printf, 1, 2))); void tester_debug(const char *format, ...) __attribute__((format(printf, 1, 2))); void tester_monitor(char dir, uint16_t cid, uint16_t psm, const void *data, size_t len); typedef void (*tester_destroy_func_t)(void *user_data); typedef void (*tester_data_func_t)(const void *test_data); void tester_add_full(const char *name, const void *test_data, tester_data_func_t pre_setup_func, tester_data_func_t setup_func, tester_data_func_t test_func, tester_data_func_t teardown_func, tester_data_func_t post_teardown_func, unsigned int timeout, void *user_data, tester_destroy_func_t destroy); void tester_add(const char *name, const void *test_data, tester_data_func_t setup_func, tester_data_func_t test_func, tester_data_func_t teardown_func); void *tester_get_data(void); void tester_pre_setup_complete(void); void tester_pre_setup_failed(void); void tester_pre_setup_abort(void); bool tester_pre_setup_skip_by_default(void); void tester_setup_complete(void); void tester_setup_failed(void); void tester_test_passed(void); void tester_test_failed(void); void tester_test_abort(void); void tester_teardown_complete(void); void tester_teardown_failed(void); void tester_post_teardown_complete(void); void tester_post_teardown_failed(void); typedef void (*tester_wait_func_t)(void *user_data); void tester_wait(unsigned int seconds, tester_wait_func_t func, void *user_data); struct io *tester_setup_io(const struct iovec *iov, int iovcnt); void tester_shutdown_io(void); void tester_io_send(void); void tester_io_set_complete_func(tester_data_func_t func); bluez-5.87/src/shared/PaxHeaders/vcp.h0000644000000000000000000000005015200607060014645 xustar0020 atime=1783092684 20 ctime=1783094100 bluez-5.87/src/shared/vcp.h0000644000000000000000000000343715200607060014335 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2020 Intel Corporation. All rights reserved. * */ #include #include #include "src/shared/io.h" #define BT_VCP_RENDERER 0x01 #define BT_VCP_CONTROLLER 0x02 #define BT_VCP_RELATIVE_VOL_DOWN 0x00 #define BT_VCP_RELATIVE_VOL_UP 0x01 #define BT_VCP_UNMUTE_RELATIVE_VOL_DOWN 0x02 #define BT_VCP_UNMUTE_RELATIVE_VOL_UP 0x03 #define BT_VCP_SET_ABOSULTE_VOL 0x04 #define BT_VCP_UNMUTE 0x05 #define BT_VCP_MUTE 0x06 struct bt_vcp; typedef void (*bt_vcp_destroy_func_t)(void *user_data); typedef void (*bt_vcp_debug_func_t)(const char *str, void *user_data); typedef void (*bt_vcp_func_t)(struct bt_vcp *vcp, void *user_data); typedef void (*bt_vcp_volume_func_t)(struct bt_vcp *vcp, uint8_t volume); struct bt_vcp *bt_vcp_ref(struct bt_vcp *vcp); void bt_vcp_unref(struct bt_vcp *vcp); void bt_vcp_add_db(struct gatt_db *db); bool bt_vcp_attach(struct bt_vcp *vcp, struct bt_gatt_client *client, bt_vcp_func_t ready, void *ready_user_data); void bt_vcp_detach(struct bt_vcp *vcp); uint8_t bt_vcp_get_volume(struct bt_vcp *vcp); bool bt_vcp_set_volume(struct bt_vcp *vcp, uint8_t volume); bool bt_vcp_set_debug(struct bt_vcp *vcp, bt_vcp_debug_func_t cb, void *user_data, bt_vcp_destroy_func_t destroy); bool bt_vcp_set_volume_callback(struct bt_vcp *vcp, bt_vcp_volume_func_t volume_changed); struct bt_att *bt_vcp_get_att(struct bt_vcp *vcp); bool bt_vcp_set_user_data(struct bt_vcp *vcp, void *user_data); /* Session related function */ unsigned int bt_vcp_register(bt_vcp_func_t added, bt_vcp_func_t removed, void *user_data); bool bt_vcp_unregister(unsigned int id); struct bt_vcp *bt_vcp_new(struct gatt_db *ldb, struct gatt_db *rdb); bluez-5.87/src/shared/PaxHeaders/micp.c0000644000000000000000000000005015063045254015010 xustar0020 atime=1783092955 20 ctime=1783094100 bluez-5.87/src/shared/micp.c0000644000000000000000000004415415063045254014501 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2023 NXP Semiconductors. All rights reserved. * */ #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "src/shared/queue.h" #include "src/shared/util.h" #include "src/shared/timeout.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-server.h" #include "src/shared/gatt-helpers.h" #include "src/shared/micp.h" #define DBG(_micp, fmt, arg...) \ micp_debug(_micp, "%s:%s() " fmt, __FILE__, __func__, ##arg) /* Application error codes */ #define MICP_ERROR_MUTE_DISABLED 0x80 #define MICP_ERROR_VALUE_NOT_ALLOWED 0x13 #define BT_ATT_ERROR_OPCODE_NOT_SUPPORTED 0x81 /* Mute char values */ #define MICS_NOT_MUTED 0x00 #define MICS_MUTED 0x01 #define MICS_DISABLED 0x02 static struct queue *micp_db; static struct queue *micp_cbs; static struct queue *sessions; struct bt_micp_cb { unsigned int id; bt_micp_func_t attached; bt_micp_func_t detached; void *user_data; }; typedef void (*micp_func_t)(struct bt_micp *micp, bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data); struct bt_micp_pending { unsigned int id; struct bt_micp *micp; micp_func_t func; void *userdata; }; struct bt_micp_ready { unsigned int id; bt_micp_ready_func_t func; bt_micp_destroy_func_t destroy; void *data; }; typedef void (*micp_notify_t)(struct bt_micp *micp, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data); struct bt_micp_notify { unsigned int id; struct bt_micp *micp; micp_notify_t func; void *user_data; }; static void *iov_pull_mem(struct iovec *iov, size_t len) { void *data = iov->iov_base; if (iov->iov_len < len) return NULL; iov->iov_base += len; iov->iov_len -= len; return data; } static struct bt_micp_db *micp_get_mdb(struct bt_micp *micp) { if (!micp) return NULL; if (micp->ldb) return micp->ldb; return NULL; } static uint8_t *mdb_get_mute_state(struct bt_micp_db *vdb) { if (!vdb->mics) return NULL; return &(vdb->mics->mute_stat); } struct bt_mics *micp_get_mics(struct bt_micp *micp) { if (!micp) return NULL; if (micp->rdb->mics) return micp->rdb->mics; micp->rdb->mics = new0(struct bt_mics, 1); micp->rdb->mics->mdb = micp->rdb; return micp->rdb->mics; } static void micp_detached(void *data, void *user_data) { struct bt_micp_cb *cb = data; struct bt_micp *micp = user_data; cb->detached(micp, cb->user_data); } void bt_micp_detach(struct bt_micp *micp) { if (!queue_remove(sessions, micp)) return; bt_gatt_client_idle_unregister(micp->client, micp->idle_id); bt_gatt_client_unref(micp->client); micp->client = NULL; queue_foreach(micp_cbs, micp_detached, micp); } static void micp_db_free(void *data) { struct bt_micp_db *mdb = data; if (!mdb) return; gatt_db_unref(mdb->db); free(mdb->mics); free(mdb); } static void micp_ready_free(void *data) { struct bt_micp_ready *ready = data; if (ready->destroy) ready->destroy(ready->data); free(ready); } static void micp_free(void *data) { struct bt_micp *micp = data; bt_micp_detach(micp); micp_db_free(micp->rdb); queue_destroy(micp->notify, free); queue_destroy(micp->pending, NULL); queue_destroy(micp->ready_cbs, micp_ready_free); free(micp); } bool bt_micp_set_user_data(struct bt_micp *micp, void *user_data) { if (!micp) return false; micp->user_data = user_data; return true; } static bool micp_db_match(const void *data, const void *match_data) { const struct bt_micp_db *mdb = data; const struct gatt_db *db = match_data; return (mdb->db == db); } struct bt_att *bt_micp_get_att(struct bt_micp *micp) { if (!micp) return NULL; if (micp->att) return micp->att; return bt_gatt_client_get_att(micp->client); } struct bt_micp *bt_micp_ref(struct bt_micp *micp) { if (!micp) return NULL; __sync_fetch_and_add(&micp->ref_count, 1); return micp; } void bt_micp_unref(struct bt_micp *micp) { if (!micp) return; if (__sync_sub_and_fetch(&micp->ref_count, 1)) return; micp_free(micp); } static void micp_debug(struct bt_micp *micp, const char *format, ...) { va_list ap; if (!micp || !format || !micp->debug_func) return; va_start(ap, format); util_debug_va(micp->debug_func, micp->debug_data, format, ap); va_end(ap); } static void micp_disconnected(int err, void *user_data) { struct bt_micp *micp = user_data; DBG(micp, "micp %p disconnected err %d", micp, err); bt_micp_detach(micp); } static struct bt_micp *micp_get_session(struct bt_att *att, struct gatt_db *db) { const struct queue_entry *entry; struct bt_micp *micp; for (entry = queue_get_entries(sessions); entry; entry = entry->next) { struct bt_micp *micp = entry->data; if (att == bt_micp_get_att(micp)) return micp; } micp = bt_micp_new(db, NULL); micp->att = att; bt_att_register_disconnect(att, micp_disconnected, micp, NULL); bt_micp_attach(micp, NULL); return micp; } static void mics_mute_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_mics *mics = user_data; struct iovec iov; iov.iov_base = &mics->mute_stat; iov.iov_len = sizeof(mics->mute_stat); gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, iov.iov_len); } static uint8_t mics_not_muted(struct bt_mics *mics, struct bt_micp *micp, struct iovec *iov) { struct bt_micp_db *mdb; uint8_t *mute_state; DBG(micp, "Mute state OP: Not Muted"); mdb = micp_get_mdb(micp); if (!mdb) { DBG(micp, "error: MDB not available"); return 0; } mute_state = mdb_get_mute_state(mdb); if (!mute_state) { DBG(micp, "Error : Mute State not available"); return 0; } *mute_state = MICS_NOT_MUTED; gatt_db_attribute_notify(mdb->mics->ms, (void *)mute_state, sizeof(uint8_t), bt_micp_get_att(micp)); return 0; } static uint8_t mics_muted(struct bt_mics *mics, struct bt_micp *micp, struct iovec *iov) { struct bt_micp_db *mdb; uint8_t *mute_state; DBG(micp, "Mute state OP: Muted"); mdb = micp_get_mdb(micp); if (!mdb) { DBG(micp, "error: MDB not available"); return 0; } mute_state = mdb_get_mute_state(mdb); *mute_state = MICS_MUTED; gatt_db_attribute_notify(mdb->mics->ms, (void *)mute_state, sizeof(uint8_t), bt_micp_get_att(micp)); return 0; } #define MICS_OP(_str, _op, _size, _func) \ { \ .str = _str, \ .op = _op, \ .size = _size, \ .func = _func, \ } struct mics_op_handler { const char *str; uint8_t op; size_t size; uint8_t (*func)(struct bt_mics *mics, struct bt_micp *micp, struct iovec *iov); } micp_handlers[] = { MICS_OP("Not Muted", MICS_NOT_MUTED, sizeof(uint8_t), mics_not_muted), MICS_OP("Muted", MICS_MUTED, sizeof(uint8_t), mics_muted), {}}; static void mics_mute_write(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_mics *mics = user_data; struct bt_micp *micp = micp_get_session(att, mics->mdb->db); struct iovec iov = { .iov_base = (void *)value, .iov_len = len, }; uint8_t *micp_op, *mute_state; struct mics_op_handler *handler; uint8_t ret = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; struct bt_micp_db *mdb; DBG(micp, "MICS Mute Char write: len: %ld: %ld", len, iov.iov_len); if (offset) { DBG(micp, "invalid offset: %d", offset); ret = BT_ATT_ERROR_INVALID_OFFSET; goto respond; } if (len < sizeof(*micp_op)) { DBG(micp, "invalid length: %ld < %ld sizeof(param)", len, sizeof(*micp_op)); ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; goto respond; } micp_op = iov_pull_mem(&iov, sizeof(*micp_op)); if (!micp_op) { DBG(micp, "iov_pull_mem() returned NULL"); goto respond; } if ((*micp_op == MICS_DISABLED) || (*micp_op != MICS_NOT_MUTED && *micp_op != MICS_MUTED)) { DBG(micp, "Invalid operation - MICS DISABLED/RFU mics op:%d", micp_op); ret = MICP_ERROR_VALUE_NOT_ALLOWED; goto respond; } mdb = micp_get_mdb(micp); if (!mdb) { DBG(micp, "error: MDB not available"); goto respond; } mute_state = mdb_get_mute_state(mdb); if (*mute_state == MICS_DISABLED) { DBG(micp, "state: MICS DISABLED , can not write value: %d", *micp_op); ret = MICP_ERROR_MUTE_DISABLED; goto respond; } for (handler = micp_handlers; handler && handler->str; handler++) { DBG(micp, "handler->op: %d micp_op: %d iov.iov_len: %ld", handler->op, *micp_op, iov.iov_len); if (handler->op != *micp_op) continue; if (len < handler->size) { DBG(micp, "invalid len %ld : %ld < %ld handler->size", len, iov.iov_len, handler->size); ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED; goto respond; } break; } if (handler && handler->str) { DBG(micp, "%s", handler->str); ret = handler->func(mics, micp, &iov); } else { DBG(micp, "unknown opcode 0x%02x", *micp_op); ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED; } respond: gatt_db_attribute_write_result(attrib, id, ret); } static struct bt_mics *mics_new(struct gatt_db *db) { struct bt_mics *mics; bt_uuid_t uuid; if (!db) return NULL; mics = new0(struct bt_mics, 1); mics->mute_stat = MICS_MUTED; /* Populate DB with MICS attributes */ bt_uuid16_create(&uuid, MICS_UUID); mics->service = gatt_db_add_service(db, &uuid, true, 4); bt_uuid16_create(&uuid, MUTE_CHRC_UUID); mics->ms = gatt_db_service_add_characteristic(mics->service, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_WRITE | BT_GATT_CHRC_PROP_NOTIFY, mics_mute_read, mics_mute_write, mics); mics->ms_ccc = gatt_db_service_add_ccc(mics->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); gatt_db_service_set_active(mics->service, true); return mics; } static struct bt_micp_db *micp_db_new(struct gatt_db *db) { struct bt_micp_db *mdb; if (!db) return NULL; mdb = new0(struct bt_micp_db, 1); mdb->db = gatt_db_ref(db); if (!micp_db) micp_db = queue_new(); mdb->mics = mics_new(db); mdb->mics->mdb = mdb; queue_push_tail(micp_db, mdb); return mdb; } static struct bt_micp_db *micp_get_db(struct gatt_db *db) { struct bt_micp_db *mdb; mdb = queue_find(micp_db, micp_db_match, db); if (mdb) return mdb; return micp_db_new(db); } void bt_micp_add_db(struct gatt_db *db) { micp_db_new(db); } bool bt_micp_set_debug(struct bt_micp *micp, bt_micp_debug_func_t func, void *user_data, bt_micp_destroy_func_t destroy) { if (!micp) return false; if (micp->debug_destroy) micp->debug_destroy(micp->debug_data); micp->debug_func = func; micp->debug_destroy = destroy; micp->debug_data = user_data; return true; } unsigned int bt_micp_register(bt_micp_func_t attached, bt_micp_func_t detached, void *user_data) { struct bt_micp_cb *cb; static unsigned int id; if (!attached && !detached) return 0; if (!micp_cbs) micp_cbs = queue_new(); cb = new0(struct bt_micp_cb, 1); cb->id = ++id ? id : ++id; cb->attached = attached; cb->detached = detached; cb->user_data = user_data; queue_push_tail(micp_cbs, cb); return cb->id; } static bool match_id(const void *data, const void *match_data) { const struct bt_micp_cb *cb = data; unsigned int id = PTR_TO_UINT(match_data); return (cb->id == id); } bool bt_micp_unregister(unsigned int id) { struct bt_micp_cb *cb; cb = queue_remove_if(micp_cbs, match_id, UINT_TO_PTR(id)); if (!cb) return false; free(cb); return true; } struct bt_micp *bt_micp_new(struct gatt_db *ldb, struct gatt_db *rdb) { struct bt_micp *micp; struct bt_micp_db *mdb; if (!ldb) return NULL; mdb = micp_get_db(ldb); if (!mdb) return NULL; micp = new0(struct bt_micp, 1); micp->ldb = mdb; micp->pending = queue_new(); micp->ready_cbs = queue_new(); micp->notify = queue_new(); if (!rdb) goto done; mdb = new0(struct bt_micp_db, 1); mdb->db = gatt_db_ref(rdb); micp->rdb = mdb; done: bt_micp_ref(micp); return micp; } static void micp_pending_destroy(void *data) { struct bt_micp_pending *pending = data; struct bt_micp *micp = pending->micp; if (queue_remove_if(micp->pending, NULL, pending)) free(pending); } static void micp_pending_complete(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_micp_pending *pending = user_data; if (pending->func) pending->func(pending->micp, success, att_ecode, value, length, pending->userdata); } static void micp_read_value(struct bt_micp *micp, uint16_t value_handle, micp_func_t func, void *user_data) { struct bt_micp_pending *pending; pending = new0(struct bt_micp_pending, 1); pending->micp = micp; pending->func = func; pending->userdata = user_data; pending->id = bt_gatt_client_read_value(micp->client, value_handle, micp_pending_complete, pending, micp_pending_destroy); if (!pending->id) { DBG(micp, "unable to send read request"); free(pending); return; } queue_push_tail(micp->pending, pending); } static void micp_register(uint16_t att_ecode, void *user_data) { struct bt_micp_notify *notify = user_data; if (att_ecode) DBG(notify->micp, "MICP register failed 0x%04x", att_ecode); } static void micp_notify(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct bt_micp_notify *notify = user_data; if (notify->func) notify->func(notify->micp, value_handle, value, length, notify->user_data); } static void micp_notify_destroy(void *data) { struct bt_micp_notify *notify = data; struct bt_micp *micp = notify->micp; if (queue_remove_if(micp->notify, NULL, notify)) free(notify); } static unsigned int micp_register_notify(struct bt_micp *micp, uint16_t value_handle, micp_notify_t func, void *user_data) { struct bt_micp_notify *notify; notify = new0(struct bt_micp_notify, 1); notify->micp = micp; notify->func = func; notify->user_data = user_data; notify->id = bt_gatt_client_register_notify(micp->client, value_handle, micp_register, micp_notify, notify, micp_notify_destroy); if (!notify->id) { DBG(micp, "Unable to register for notifications"); free(notify); return 0; } queue_push_tail(micp->notify, notify); return notify->id; } static void micp_mute_state_notify(struct bt_micp *micp, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { uint8_t mute_state; memcpy(&mute_state, value, sizeof(mute_state)); DBG(micp, "Mute state: 0x%x", mute_state); } static void read_mute_state(struct bt_micp *micp, bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { uint8_t *mute_state; struct iovec iov = { .iov_base = (void *)value, .iov_len = length, }; if (!success) { DBG(micp, "Unable to read Mute state: error 0x%02x", att_ecode); return; } mute_state = iov_pull_mem(&iov, sizeof(uint8_t)); if (mute_state == NULL) { DBG(micp, "Unable to get Mute state"); return; } DBG(micp, "Mute state: %x", *mute_state); } static void foreach_mics_char(struct gatt_db_attribute *attr, void *user_data) { struct bt_micp *micp = user_data; uint16_t value_handle; bt_uuid_t uuid, uuid_mute; struct bt_mics *mics; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) return; bt_uuid16_create(&uuid_mute, MUTE_CHRC_UUID); if (!bt_uuid_cmp(&uuid, &uuid_mute)) { DBG(micp, "MICS Mute characteristic found: handle 0x%04x", value_handle); mics = micp_get_mics(micp); if (!mics || mics->ms) return; mics->ms = attr; micp_read_value(micp, value_handle, read_mute_state, micp); micp->mute_id = micp_register_notify(micp, value_handle, micp_mute_state_notify, NULL); } } static void foreach_mics_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_micp *micp = user_data; struct bt_mics *mics = micp_get_mics(micp); mics->service = attr; gatt_db_service_set_claimed(attr, true); gatt_db_service_foreach_char(attr, foreach_mics_char, micp); } unsigned int bt_micp_ready_register(struct bt_micp *micp, bt_micp_ready_func_t func, void *user_data, bt_micp_destroy_func_t destroy) { struct bt_micp_ready *ready; static unsigned int id; DBG(micp, "bt_micp_ready_register_Entry\n"); if (!micp) return 0; ready = new0(struct bt_micp_ready, 1); ready->id = ++id ? id : ++id; ready->func = func; ready->destroy = destroy; ready->data = user_data; queue_push_tail(micp->ready_cbs, ready); return ready->id; } static bool match_ready_id(const void *data, const void *match_data) { const struct bt_micp_ready *ready = data; unsigned int id = PTR_TO_UINT(match_data); return (ready->id == id); } bool bt_micp_ready_unregister(struct bt_micp *micp, unsigned int id) { struct bt_micp_ready *ready; ready = queue_remove_if(micp->ready_cbs, match_ready_id, UINT_TO_PTR(id)); if (!ready) return false; micp_ready_free(ready); return true; } static struct bt_micp *bt_micp_ref_safe(struct bt_micp *micp) { if (!micp || !micp->ref_count) return NULL; return bt_micp_ref(micp); } static void micp_notify_ready(struct bt_micp *micp) { const struct queue_entry *entry; if (!bt_micp_ref_safe(micp)) return; for (entry = queue_get_entries(micp->ready_cbs); entry; entry = entry->next) { struct bt_micp_ready *ready = entry->data; ready->func(micp, ready->data); } bt_micp_unref(micp); } static void micp_idle(void *data) { struct bt_micp *micp = data; micp->idle_id = 0; micp_notify_ready(micp); } bool bt_micp_attach(struct bt_micp *micp, struct bt_gatt_client *client) { bt_uuid_t uuid; if (!sessions) sessions = queue_new(); queue_push_tail(sessions, micp); if (!client) return true; if (micp->client) return false; micp->client = bt_gatt_client_clone(client); if (!micp->client) return false; bt_gatt_client_idle_register(micp->client, micp_idle, micp, NULL); bt_uuid16_create(&uuid, MICS_UUID); gatt_db_foreach_service(micp->ldb->db, &uuid, foreach_mics_service, micp); return true; } bluez-5.87/src/shared/PaxHeaders/mainloop-notify.h0000644000000000000000000000005014015011623017176 xustar0020 atime=1783092983 20 ctime=1783094100 bluez-5.87/src/shared/mainloop-notify.h0000644000000000000000000000036614015011623016664 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ void mainloop_notify_init(void); void mainloop_notify_exit(void); bluez-5.87/src/shared/PaxHeaders/rap.c0000644000000000000000000000005015221752714014644 xustar0020 atime=1783092685 20 ctime=1783094100 bluez-5.87/src/shared/rap.c0000644000000000000000000021321215221752714014326 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * BlueZ - Bluetooth protocol stack for Linux * * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/uuid.h" #include "src/shared/queue.h" #include "src/shared/util.h" #include "src/shared/timeout.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-server.h" #include "src/shared/gatt-client.h" #include "src/shared/rap.h" #define DBG(_rap, fmt, ...) \ rap_debug(_rap, "%s:%s() " fmt, __FILE__, __func__, ##__VA_ARGS__) #define SIGN_EXTEND_TO_16(val, bits) \ ((int16_t)(((val) ^ (1U << ((bits)-1))) - (1U << ((bits)-1)))) #define RAS_UUID16 0x185B /* Total number of attribute handles reserved for the RAS service */ #define RAS_TOTAL_NUM_HANDLES 18 /* 2(rc+cfg) + 1(tx_pwr) + 1(4 bits antenna_mask, 2 bits reserved, * 2 bits pct_format) */ #define RAS_RANGING_HEADER_SIZE 4 #define TOTAL_RAS_RANGING_HEADER_SIZE 5 #define ATT_OVERHEAD 3 /* 1(opcode) + 2(char handle) */ #define RAS_STEP_ABORTED_BIT 0x80/* set step aborted */ #define RAS_SUBEVENT_HEADER_SIZE 8 #define CS_MODE_ZERO_WIRE_INIT_SIZE 5 #define CS_MODE_ZERO_WIRE_REF_SIZE 3 #define CS_MODE_ONE_WIRE_SIZE_MIN 6 #define CS_MODE_ONE_WIRE_SIZE_MAX 12 enum pct_format { IQ = 0, PHASE = 1, }; enum ranging_done_status { RANGING_DONE_ALL_RESULTS_COMPLETE = 0x0, RANGING_DONE_PARTIAL_RESULTS = 0x1, RANGING_DONE_ABORTED = 0xF, }; enum subevent_done_status { SUBEVENT_DONE_ALL_RESULTS_COMPLETE = 0x0, SUBEVENT_DONE_PARTIAL_RESULTS = 0x1, SUBEVENT_DONE_ABORTED = 0xF, }; enum ranging_abort_reason { RANGING_ABORT_NO_ABORT = 0x0, RANGING_ABORT_LOCAL_HOST_OR_REMOTE = 0x1, RANGING_ABORT_INSUFFICIENT_FILTERED_CHANNELS = 0x2, RANGING_ABORT_INSTANT_HAS_PASSED = 0x3, RANGING_ABORT_UNSPECIFIED = 0xF, }; enum subevent_abort_reason { SUBEVENT_ABORT_NO_ABORT = 0x0, SUBEVENT_ABORT_LOCAL_HOST_OR_REMOTE = 0x1, SUBEVENT_ABORT_NO_CS_SYNC_RECEIVED = 0x2, SUBEVENT_ABORT_SCHEDULING_CONFLICTS_OR_LIMITED_RESOURCES = 0x3, SUBEVENT_ABORT_UNSPECIFIED = 0xF, }; /* Segmentation header: 1 byte * bit 0: first_segment * bit 1: last_segment * bits 2-7: rolling_segment_counter (6 bits) */ struct segmentation_header { uint8_t first_segment; uint8_t last_segment; uint8_t rolling_segment_counter; }; /* Macros to pack/unpack segmentation header */ #define SEG_HDR_PACK(first, last, counter) \ ((uint8_t)(((first) ? 0x01 : 0x00) | \ ((last) ? 0x02 : 0x00) | \ (((counter) & 0x3F) << 2))) struct ranging_header { /* Byte 0-1: 12-bit counter + 4-bit config_id */ uint8_t counter_config[2]; int8_t selected_tx_power; /* Byte 2: selected TX power */ /* Byte 3: 4-bit antenna_mask + 2-bit reserved + 2-bit pct_format */ uint8_t antenna_pct; } __packed; static inline void ranging_header_set_counter(struct ranging_header *hdr, uint16_t counter) { /* Counter is 12 bits, stored in lower 12 bits of first 2 bytes */ hdr->counter_config[0] = counter & 0xFF; hdr->counter_config[1] = (hdr->counter_config[1] & 0xF0) | ((counter >> 8) & 0x0F); } static inline void ranging_header_set_config_id(struct ranging_header *hdr, uint8_t config_id) { /* Config ID is 4 bits, stored in upper 4 bits of byte 1 */ hdr->counter_config[1] = (hdr->counter_config[1] & 0x0F) | ((config_id & 0x0F) << 4); } static inline void ranging_header_set_antenna_mask( struct ranging_header *hdr, uint8_t mask) { /* Antenna mask is 4 bits, stored in lower 4 bits of byte 3 */ hdr->antenna_pct = (hdr->antenna_pct & 0xF0) | (mask & 0x0F); } static inline void ranging_header_set_pct_format(struct ranging_header *hdr, uint8_t format) { /* PCT format is 2 bits, stored in bits 6-7 of byte 3 */ hdr->antenna_pct = (hdr->antenna_pct & 0x3F) | ((format & 0x03) << 6); } static inline uint8_t ranging_header_get_antenna_mask( const struct ranging_header *hdr) { if (!hdr) return 0; return hdr->antenna_pct & 0x0F; } static inline uint8_t antenna_mask_count_paths(uint8_t antenna_mask) { uint8_t count = 0; uint8_t i; for (i = 0; i < 4; i++) { if (antenna_mask & (1u << i)) count++; } return count; } struct ras_subevent_header { uint16_t start_acl_conn_event; uint16_t frequency_compensation; uint8_t ranging_done_status; uint8_t subevent_done_status; uint8_t ranging_abort_reason; uint8_t subevent_abort_reason; int8_t reference_power_level; uint8_t num_steps_reported; }; /* Macros to pack/unpack RAS subevent header status fields */ #define RAS_DONE_STATUS_PACK(ranging, subevent) \ ((uint8_t)(((ranging) & 0x0F) | (((subevent) & 0x0F) << 4))) #define RAS_DONE_STATUS_UNPACK_RANGING(packed) \ ((uint8_t)((packed) & 0x0F)) #define RAS_DONE_STATUS_UNPACK_SUBEVENT(packed) \ ((uint8_t)(((packed) >> 4) & 0x0F)) #define RAS_ABORT_REASON_PACK(ranging, subevent) \ ((uint8_t)(((ranging) & 0x0F) | (((subevent) & 0x0F) << 4))) #define RAS_ABORT_REASON_UNPACK_RANGING(packed) \ ((uint8_t)((packed) & 0x0F)) #define RAS_ABORT_REASON_UNPACK_SUBEVENT(packed) \ ((uint8_t)(((packed) >> 4) & 0x0F)) struct ras_subevent { struct ras_subevent_header subevent_header; uint8_t subevent_data[]; }; /* Role maps to Core CS roles (initiator/reflector) */ enum cs_role { CS_ROLE_INITIATOR = 0x00, CS_ROLE_REFLECTOR = 0x01, }; #define CS_INVALID_CONFIG_ID 0xFF /* Minimal enums (align to controller values if needed) */ enum cs_procedure_done_status { CS_PROC_ALL_RESULTS_COMPLETE = 0x00, CS_PROC_PARTIAL_RESULTS = 0x01, CS_PROC_ABORTED = 0x02 }; /* Main cs_procedure_data */ struct cs_procedure_data { /* Identity and counters */ uint16_t counter; uint8_t num_antenna_paths; /* Flags and status */ enum cs_procedure_done_status local_status; enum cs_procedure_done_status remote_status; bool contains_complete_subevent_; /* RAS aggregation */ struct segmentation_header segmentation_header_; struct ranging_header ranging_header_; struct iovec ras_raw_data_; /* raw concatenated */ uint16_t ras_raw_data_index_; struct ras_subevent_header ras_subevent_header_; struct iovec ras_subevent_data_; /* buffer per subevent */ uint8_t ras_subevent_counter_; /* Reference power levels */ int8_t initiator_reference_power_level; int8_t reflector_reference_power_level; bool ranging_header_prepended_; bool ras_subevent_header_emitted; }; struct cstracker { enum cs_role role; /* INITIATOR/REFLECTOR */ uint8_t config_id; /* CS_INVALID_CONFIG_ID */ int8_t selected_tx_power; /* PROC_ENABLE_COMPLETE */ uint8_t rtt_type; /* RTT type */ struct cs_procedure_data *current_proc; /* Cached header values for CONT events (per-connection state) */ uint16_t last_proc_counter; uint16_t last_start_acl_conn_evt_counter; uint16_t last_freq_comp; int8_t last_ref_pwr_lvl; /* Client - first segment carries this */ struct ranging_header ranging_header_; /* Client - subsequent segments appended using iovec */ struct iovec segment_data; }; /* Ranging Service context */ struct ras { struct bt_rap_db *rapdb; /* Service and characteristic attributes */ struct gatt_db_attribute *svc; struct gatt_db_attribute *feat_chrc; struct gatt_db_attribute *realtime_chrc; struct gatt_db_attribute *realtime_chrc_ccc; struct gatt_db_attribute *ondemand_chrc; struct gatt_db_attribute *ondemand_ccc; struct gatt_db_attribute *cp_chrc; struct gatt_db_attribute *cp_ccc; struct gatt_db_attribute *ready_chrc; struct gatt_db_attribute *ready_ccc; struct gatt_db_attribute *overwritten_chrc; struct gatt_db_attribute *overwritten_ccc; /* CCC state tracking for mutual exclusivity */ uint16_t realtime_ccc_value; uint16_t ondemand_ccc_value; }; struct bt_rap_db { struct gatt_db *db; struct ras *ras; }; struct bt_rap { int ref_count; struct bt_rap_db *lrapdb; struct bt_rap_db *rrapdb; struct bt_gatt_client *client; struct bt_att *att; unsigned int idle_id; struct queue *notify; struct queue *pending; struct queue *ready_cbs; bt_rap_debug_func_t debug_func; bt_rap_destroy_func_t debug_destroy; void *debug_data; void *user_data; struct cstracker *resptracker; struct cstracker *reqtracker; }; static struct queue *rap_db; static struct queue *bt_rap_cbs; static struct queue *sessions; struct bt_rap_cb { unsigned int id; bt_rap_func_t attached; bt_rap_func_t detached; void *user_data; }; struct bt_rap_ready { unsigned int id; bt_rap_ready_func_t func; bt_rap_destroy_func_t destroy; void *data; }; typedef void (*rap_notify_t)(struct bt_rap *rap, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data); struct bt_rap_notify { unsigned int id; struct bt_rap *rap; rap_notify_t func; void *user_data; }; typedef void (*rap_func_t)(struct bt_rap *rap, bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data); struct bt_rap_pending { unsigned int id; struct bt_rap *rap; rap_func_t func; void *userdata; }; uint16_t default_ras_mtu = 247; /*Section 3.1.2 of RAP 1.0*/ uint8_t ras_segment_header_size = 1; static struct cs_procedure_data *cs_procedure_data_create( uint16_t procedure_counter, uint8_t num_antenna_paths, uint8_t configuration_id, int8_t selected_tx_power) { struct cs_procedure_data *d; uint8_t i; uint8_t antenna_mask = 0; d = calloc(1, sizeof(struct cs_procedure_data)); if (!d) return NULL; d->counter = procedure_counter; d->num_antenna_paths = num_antenna_paths; d->local_status = CS_PROC_PARTIAL_RESULTS; d->remote_status = CS_PROC_PARTIAL_RESULTS; d->contains_complete_subevent_ = false; d->segmentation_header_.first_segment = 1; d->segmentation_header_.last_segment = 0; d->segmentation_header_.rolling_segment_counter = 0; /* Initialize ranging header using helper functions */ memset(&d->ranging_header_, 0, sizeof(d->ranging_header_)); ranging_header_set_counter(&d->ranging_header_, procedure_counter); ranging_header_set_config_id(&d->ranging_header_, configuration_id); d->ranging_header_.selected_tx_power = selected_tx_power; /* Build antenna mask */ for (i = 0; i < num_antenna_paths; i++) antenna_mask |= (1u << i); ranging_header_set_antenna_mask(&d->ranging_header_, antenna_mask); ranging_header_set_pct_format(&d->ranging_header_, IQ); memset(&d->ras_raw_data_, 0, sizeof(d->ras_raw_data_)); d->ras_raw_data_index_ = 0; memset(&d->ras_subevent_data_, 0, sizeof(d->ras_subevent_data_)); d->ras_subevent_counter_ = 0; d->initiator_reference_power_level = 0; d->reflector_reference_power_level = 0; d->ranging_header_prepended_ = false; d->ras_subevent_header_emitted = false; return d; } static void cs_procedure_data_destroy(struct cs_procedure_data *d) { if (!d) return; free(d->ras_raw_data_.iov_base); free(d->ras_subevent_data_.iov_base); free(d); } static void cs_pd_set_local_status(struct cs_procedure_data *d, enum cs_procedure_done_status s) { if (d) d->local_status = s; } static void cs_pd_set_remote_status(struct cs_procedure_data *d, enum cs_procedure_done_status s) { if (d) d->remote_status = s; } static void cs_pd_set_reference_power_levels(struct cs_procedure_data *d, int8_t init_lvl, int8_t ref_lvl) { if (!d) return; d->initiator_reference_power_level = init_lvl; d->reflector_reference_power_level = ref_lvl; } static void cs_pd_ras_begin_subevent(struct cs_procedure_data *d, uint16_t start_acl_conn_event, uint16_t frequency_compensation, int8_t reference_power_level) { if (!d) return; d->ras_subevent_counter_++; d->ras_subevent_header_.start_acl_conn_event = start_acl_conn_event; d->ras_subevent_header_.frequency_compensation = frequency_compensation; d->ras_subevent_header_.reference_power_level = reference_power_level; d->ras_subevent_header_.num_steps_reported = 0; d->ras_subevent_header_emitted = false; d->ras_subevent_data_.iov_len = 0; } static bool cs_pd_ras_append_subevent_bytes(struct cs_procedure_data *d, const uint8_t *bytes, size_t len) { if (!d || !bytes || len == 0) return false; return util_iov_append(&d->ras_subevent_data_, bytes, len) != NULL; } static inline size_t serialize_ras_subevent_header( const struct ras_subevent_header *h, uint8_t *out, size_t out_len) { if (!h || !out || out_len < RAS_SUBEVENT_HEADER_SIZE) return 0; put_le16(h->start_acl_conn_event, out + 0); put_le16(h->frequency_compensation, out + 2); out[4] = RAS_DONE_STATUS_PACK(h->ranging_done_status, h->subevent_done_status); out[5] = RAS_ABORT_REASON_PACK(h->ranging_abort_reason, h->subevent_abort_reason); out[6] = h->reference_power_level; out[7] = h->num_steps_reported; return RAS_SUBEVENT_HEADER_SIZE; } static bool cs_pd_ras_commit_subevent(struct cs_procedure_data *d, uint8_t num_steps_reported, uint8_t ranging_done_status, uint8_t subevent_done_status, uint8_t ranging_abort_reason, uint8_t subevent_abort_reason) { size_t hdr_sz; size_t payload_sz; size_t total; uint8_t *buf; size_t w; bool ok; if (!d) return false; d->ras_subevent_header_.num_steps_reported = (uint8_t)(d->ras_subevent_header_.num_steps_reported + num_steps_reported); d->ras_subevent_header_.ranging_done_status = ranging_done_status; d->ras_subevent_header_.subevent_done_status = subevent_done_status; d->ras_subevent_header_.ranging_abort_reason = ranging_abort_reason; d->ras_subevent_header_.subevent_abort_reason = subevent_abort_reason; if (subevent_done_status == SUBEVENT_DONE_ALL_RESULTS_COMPLETE) d->contains_complete_subevent_ = true; if (subevent_done_status == SUBEVENT_DONE_PARTIAL_RESULTS) return true; if (!d->ras_subevent_header_emitted) { hdr_sz = RAS_SUBEVENT_HEADER_SIZE; payload_sz = d->ras_subevent_data_.iov_len; total = hdr_sz + payload_sz; buf = (uint8_t *)malloc(total); if (!buf) return false; w = serialize_ras_subevent_header(&d->ras_subevent_header_, buf, total); if (w != hdr_sz) { free(buf); return false; } if (payload_sz > 0) memcpy(buf + hdr_sz, (const uint8_t *)d->ras_subevent_data_.iov_base, payload_sz); ok = util_iov_append(&d->ras_raw_data_, buf, total) != NULL; free(buf); if (!ok) return false; d->ras_subevent_data_.iov_len = 0; d->ras_subevent_header_emitted = true; } return true; } static struct ras *rap_get_ras(struct bt_rap *rap) { if (!rap) return NULL; if (rap->rrapdb->ras) return rap->rrapdb->ras; rap->rrapdb->ras = new0(struct ras, 1); rap->rrapdb->ras->rapdb = rap->rrapdb; return rap->rrapdb->ras; } static void rap_detached(void *data, void *user_data) { struct bt_rap_cb *cb = data; struct bt_rap *rap = user_data; cb->detached(rap, cb->user_data); } void bt_rap_detach(struct bt_rap *rap) { if (!queue_remove(sessions, rap)) return; bt_gatt_client_idle_unregister(rap->client, rap->idle_id); bt_gatt_client_unref(rap->client); rap->client = NULL; queue_foreach(bt_rap_cbs, rap_detached, rap); } static void rap_db_free(void *data) { struct bt_rap_db *rapdb = data; if (!rapdb) return; gatt_db_unref(rapdb->db); free(rapdb->ras); free(rapdb); } static void rap_ready_free(void *data) { struct bt_rap_ready *ready = data; if (ready->destroy) ready->destroy(ready->data); free(ready); } static void rap_free(void *data) { struct bt_rap *rap = data; bt_rap_detach(rap); rap_db_free(rap->rrapdb); if (rap->resptracker) { free(rap->resptracker); rap->resptracker = NULL; } if (rap->reqtracker) { free(rap->reqtracker); rap->reqtracker = NULL; } queue_destroy(rap->notify, free); queue_destroy(rap->pending, NULL); queue_destroy(rap->ready_cbs, rap_ready_free); free(rap); } bool bt_rap_set_user_data(struct bt_rap *rap, void *user_data) { if (!rap) return false; rap->user_data = user_data; return true; } static bool rap_db_match(const void *data, const void *match_data) { const struct bt_rap_db *rapdb = data; const struct gatt_db *db = match_data; return rapdb->db == db; } struct bt_att *bt_rap_get_att(struct bt_rap *rap) { if (!rap) return NULL; if (rap->att) return rap->att; return bt_gatt_client_get_att(rap->client); } struct bt_rap *bt_rap_ref(struct bt_rap *rap) { if (!rap) return NULL; __sync_fetch_and_add(&rap->ref_count, 1); return rap; } void bt_rap_unref(struct bt_rap *rap) { if (!rap) return; if (__sync_sub_and_fetch(&rap->ref_count, 1)) return; rap_free(rap); } static void rap_debug(struct bt_rap *rap, const char *format, ...) { va_list ap; if (!rap || !format || !rap->debug_func) return; va_start(ap, format); util_debug_va(rap->debug_func, rap->debug_data, format, ap); va_end(ap); } bool bt_rap_set_debug(struct bt_rap *rap, bt_rap_debug_func_t func, void *user_data, bt_rap_destroy_func_t destroy) { if (!rap) return false; if (rap->debug_destroy) rap->debug_destroy(rap->debug_data); rap->debug_func = func; rap->debug_destroy = destroy; rap->debug_data = user_data; return true; } static void cs_tracker_init(struct cstracker *t) { if (!t) return; memset(t, 0, sizeof(*t)); t->role = CS_ROLE_REFLECTOR; t->config_id = CS_INVALID_CONFIG_ID; t->rtt_type = 0; t->selected_tx_power = 0; t->last_proc_counter = 0; t->last_start_acl_conn_evt_counter = 0; t->last_freq_comp = 0; t->last_ref_pwr_lvl = 0; /* Initialize ranging header using helper functions */ memset(&t->ranging_header_, 0, sizeof(t->ranging_header_)); /* Initialize segment accumulator */ t->segment_data.iov_base = NULL; t->segment_data.iov_len = 0; } static void ras_features_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { /* * Feature mask: bits 0-2 set: * - Real-time ranging * - Retrieve stored results * - Abort operation */ uint8_t value[4] = { 0x01, 0x00, 0x00, 0x00 }; gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value)); } static void ras_ondemand_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { /* No static read data – on‑demand data is pushed via * notifications */ gatt_db_attribute_read_result(attrib, id, 0, NULL, 0); } /* * Control point handler. * Parses the opcode and acts on queued data (implementation TBD). */ static void ras_control_point_write_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { /* Control point handler - implementation TBD */ } /* Data Ready – returns the latest ranging counter. */ static void ras_data_ready_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { uint16_t counter = 0; uint8_t value[2]; put_le16(counter, value); gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value)); } /* Data Overwritten – indicates how many results were overwritten. */ static void ras_data_overwritten_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { uint8_t value[2] = { 0x00, 0x00 }; gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value)); } static void ras_ranging_data_ccc_write_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { struct ras *ras = user_data; uint16_t ccc_value; bool is_realtime; uint16_t *this_ccc; uint16_t *other_ccc; if (!ras) { gatt_db_attribute_write_result(attrib, id, BT_ATT_ERROR_UNLIKELY); return; } if (offset) { gatt_db_attribute_write_result(attrib, id, BT_ATT_ERROR_INVALID_OFFSET); return; } if (len != 2) { gatt_db_attribute_write_result(attrib, id, BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN); return; } ccc_value = get_le16(value); if (ccc_value != 0x0000 && ccc_value != 0x0001 && ccc_value != 0x0002 && ccc_value != 0x0003) { gatt_db_attribute_write_result(attrib, id, BT_ERROR_WRITE_REQUEST_REJECTED); return; } /* Determine which CCC this is */ is_realtime = (attrib == ras->realtime_chrc_ccc); this_ccc = is_realtime ? &ras->realtime_ccc_value : &ras->ondemand_ccc_value; other_ccc = is_realtime ? &ras->ondemand_ccc_value : &ras->realtime_ccc_value; /* Check mutual exclusivity: reject if trying to enable realtime * while ondemand is already enabled. * Test case: RAS/SR/SPE/BI-11-C [Client enables both Real-time * Ranging Data and On-demand Ranging Data notifications or * indications] */ if (ccc_value != 0x0000 && *other_ccc != 0x0000) { gatt_db_attribute_write_result(attrib, id, BT_ERROR_CCC_IMPROPERLY_CONFIGURED); return; } /* Update state */ *this_ccc = ccc_value; gatt_db_attribute_write_result(attrib, id, 0); } /* Service registration – store attribute pointers */ static struct ras *register_ras_service(struct gatt_db *db) { struct ras *ras; struct gatt_db_attribute *service; bt_uuid_t uuid; if (!db) return NULL; ras = new0(struct ras, 1); if (!ras) return NULL; /* Primary RAS service */ bt_uuid16_create(&uuid, RAS_UUID16); service = gatt_db_add_service(db, &uuid, true, RAS_TOTAL_NUM_HANDLES); if (!service) { free(ras); return NULL; } ras->svc = service; /* RAS Features */ bt_uuid16_create(&uuid, RAS_FEATURES_UUID); ras->feat_chrc = gatt_db_service_add_characteristic(ras->svc, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT, BT_GATT_CHRC_PROP_READ, ras_features_read_cb, NULL, ras); /* Real-time Ranging Data */ bt_uuid16_create(&uuid, RAS_REALTIME_DATA_UUID); ras->realtime_chrc = gatt_db_service_add_characteristic(ras->svc, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT, BT_GATT_CHRC_PROP_NOTIFY | BT_GATT_CHRC_PROP_INDICATE, NULL, NULL, ras); ras->realtime_chrc_ccc = gatt_db_service_add_ccc_custom(ras->svc, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, ras_ranging_data_ccc_write_cb, ras); /* On-demand Ranging Data */ bt_uuid16_create(&uuid, RAS_ONDEMAND_DATA_UUID); ras->ondemand_chrc = gatt_db_service_add_characteristic(ras->svc, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT, BT_GATT_CHRC_PROP_NOTIFY | BT_GATT_CHRC_PROP_INDICATE, ras_ondemand_read_cb, NULL, ras); ras->ondemand_ccc = gatt_db_service_add_ccc_custom(ras->svc, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, ras_ranging_data_ccc_write_cb, ras); /* RAS Control Point */ bt_uuid16_create(&uuid, RAS_CONTROL_POINT_UUID); ras->cp_chrc = gatt_db_service_add_characteristic(ras->svc, &uuid, BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_ENCRYPT, BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP | BT_GATT_CHRC_PROP_INDICATE, NULL, ras_control_point_write_cb, ras); ras->cp_ccc = gatt_db_service_add_ccc(ras->svc, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); /* RAS Data Ready */ bt_uuid16_create(&uuid, RAS_DATA_READY_UUID); ras->ready_chrc = gatt_db_service_add_characteristic(ras->svc, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY | BT_GATT_CHRC_PROP_INDICATE, ras_data_ready_read_cb, NULL, ras); ras->ready_ccc = gatt_db_service_add_ccc(ras->svc, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); /* RAS Data Overwritten */ bt_uuid16_create(&uuid, RAS_DATA_OVERWRITTEN_UUID); ras->overwritten_chrc = gatt_db_service_add_characteristic(ras->svc, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY | BT_GATT_CHRC_PROP_INDICATE, ras_data_overwritten_read_cb, NULL, ras); ras->overwritten_ccc = gatt_db_service_add_ccc(ras->svc, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); /* Activate the service */ gatt_db_service_set_active(ras->svc, true); return ras; } static struct bt_rap_db *rap_db_new(struct gatt_db *db) { struct bt_rap_db *rapdb; if (!db) return NULL; rapdb = new0(struct bt_rap_db, 1); if (!rapdb) return NULL; rapdb->db = gatt_db_ref(db); if (!rap_db) rap_db = queue_new(); rapdb->ras = register_ras_service(db); if (rapdb->ras) rapdb->ras->rapdb = rapdb; queue_push_tail(rap_db, rapdb); return rapdb; } static struct bt_rap_db *rap_get_db(struct gatt_db *db) { struct bt_rap_db *rapdb; rapdb = queue_find(rap_db, rap_db_match, db); if (rapdb) return rapdb; return rap_db_new(db); } void bt_rap_add_db(struct gatt_db *db) { rap_db_new(db); } unsigned int bt_rap_register(bt_rap_func_t attached, bt_rap_func_t detached, void *user_data) { struct bt_rap_cb *cb; static unsigned int id; if (!attached && !detached) return 0; if (!bt_rap_cbs) bt_rap_cbs = queue_new(); cb = new0(struct bt_rap_cb, 1); cb->id = ++id ? id : ++id; cb->attached = attached; cb->detached = detached; cb->user_data = user_data; queue_push_tail(bt_rap_cbs, cb); return cb->id; } static bool match_id(const void *data, const void *match_data) { const struct bt_rap_cb *cb = data; unsigned int id = PTR_TO_UINT(match_data); return cb->id == id; } bool bt_rap_unregister(unsigned int id) { struct bt_rap_cb *cb; cb = queue_remove_if(bt_rap_cbs, match_id, UINT_TO_PTR(id)); if (!cb) return false; free(cb); return true; } static inline size_t serialize_segmentation_header( const struct segmentation_header *s, uint8_t *out, size_t out_len) { if (!s || !out || out_len < 1) return 0; out[0] = SEG_HDR_PACK(s->first_segment, s->last_segment, s->rolling_segment_counter); return 1; } static inline bool serialize_ranging_header_iov(const struct ranging_header *r, struct iovec *iov) { if (!r || !iov) return false; /* Serialize the per-byte packed fields using util_iov_push functions */ if (!util_iov_push_le16(iov, get_le16(r->counter_config))) return false; if (!util_iov_push_u8(iov, r->selected_tx_power)) return false; if (!util_iov_push_u8(iov, r->antenna_pct)) return false; return true; } static inline uint16_t ras_att_value_payload_max(struct bt_rap *rap) { struct bt_att *att = bt_rap_get_att(rap); uint16_t mtu = att ? bt_att_get_mtu(att) : default_ras_mtu; return (uint16_t)(mtu > ATT_OVERHEAD ? (mtu - ATT_OVERHEAD - TOTAL_RAS_RANGING_HEADER_SIZE - ras_segment_header_size) : 0); } /* Prepend data to an iovec - optimized to avoid unnecessary malloc/copy * by using realloc and memmove instead of malloc/memcpy/free pattern. * This reduces memory allocations and is more cache-friendly. */ static bool iov_prepend_bytes(struct iovec *iov, const uint8_t *bytes, size_t len) { size_t new_len; void *new_base; if (!iov || !bytes || len == 0) return false; new_len = iov->iov_len + len; /* Use realloc to potentially expand in-place */ new_base = realloc(iov->iov_base, new_len); if (!new_base) return false; /* Move existing data forward to make room at the beginning */ if (iov->iov_len > 0) memmove((uint8_t *)new_base + len, new_base, iov->iov_len); /* Copy new data to the beginning */ memcpy(new_base, bytes, len); iov->iov_base = new_base; iov->iov_len = new_len; return true; } /* Append the 4-byte RangingHeader to ras_raw_data_ on first segment */ static bool ras_maybe_prepend_ranging_header(struct cs_procedure_data *d) { struct iovec temp_iov = { 0 }; bool ok; if (!d) return false; if (d->ranging_header_prepended_) return false; if (!d->segmentation_header_.first_segment) return false; if (d->ras_raw_data_index_ != 0) return false; temp_iov.iov_base = malloc(4); if (!temp_iov.iov_base) return false; temp_iov.iov_len = 0; /* Serialize ranging header into temporary iovec */ if (!serialize_ranging_header_iov(&d->ranging_header_, &temp_iov)) { free(temp_iov.iov_base); return false; } /* Prepend the serialized header to ras_raw_data_ */ ok = iov_prepend_bytes(&d->ras_raw_data_, temp_iov.iov_base, temp_iov.iov_len); /* Free temporary iovec buffer */ free(temp_iov.iov_base); if (ok) d->ranging_header_prepended_ = true; return ok; } static void send_ras_segment_data(struct bt_rap *rap, struct cs_procedure_data *proc) { struct ras *ras; uint16_t value_max; const uint16_t header_len = ras_segment_header_size; uint16_t raw_payload_size; if (!rap || !proc) return; if (!rap->lrapdb || !rap->lrapdb->ras) return; ras = rap->lrapdb->ras; value_max = ras_att_value_payload_max(rap); if (value_max == 0) { DBG(rap, "value_max=0 (MTU not available?)"); return; } if (value_max <= header_len) { DBG(rap, "value_max(%u) too small for header", value_max); return; } raw_payload_size = (uint16_t)(value_max - header_len); /* Convert tail recursion to loop */ while (true) { size_t total_len = proc->ras_raw_data_.iov_len; size_t index = proc->ras_raw_data_index_; size_t unsent_data_size; uint16_t copy_size; uint16_t seg_len; uint8_t *seg; uint16_t wr = 0; bool ok = true; if (index > total_len) index = total_len; unsent_data_size = total_len - index; if (unsent_data_size == 0) return; /* Set last_segment if procedure complete or fits in segment */ if ((proc->local_status != CS_PROC_PARTIAL_RESULTS && unsent_data_size <= raw_payload_size) || (proc->contains_complete_subevent_ && unsent_data_size <= raw_payload_size)) { proc->segmentation_header_.last_segment = 1; } else { proc->segmentation_header_.last_segment = 0; } /* Wait for more data if needed and not last segment */ if (unsent_data_size < raw_payload_size && proc->segmentation_header_.last_segment == 0) { DBG(rap, "waiting for more data (unsent=%zu < " "payload=%u)", unsent_data_size, raw_payload_size); return; } copy_size = (uint16_t)((unsent_data_size < raw_payload_size) ? unsent_data_size : raw_payload_size); seg_len = (uint16_t)(header_len + copy_size); seg = (uint8_t *)malloc(seg_len); if (!seg) { DBG(rap, "OOM (%u)", seg_len); return; } wr += (uint16_t)serialize_segmentation_header( &proc->segmentation_header_, seg + wr, seg_len - wr); memcpy(seg + wr, (const uint8_t *)proc->ras_raw_data_.iov_base + index, copy_size); wr += copy_size; /* Try sending to real-time characteristic */ if (ras->realtime_chrc) ok = gatt_db_attribute_notify(ras->realtime_chrc, seg, wr, bt_rap_get_att(rap)); /* Try sending to on-demand characteristic */ if (ras->ondemand_chrc && ok) ok = gatt_db_attribute_notify(ras->ondemand_chrc, seg, wr, bt_rap_get_att(rap)); free(seg); if (!ok) { DBG(rap, "Failed to send RAS notification"); return; } /* Advance read cursor and update segmentation state */ proc->ras_raw_data_index_ += copy_size; proc->segmentation_header_.first_segment = 0; proc->segmentation_header_.rolling_segment_counter = (uint8_t)((proc->segmentation_header_ .rolling_segment_counter + 1) & 0x3F); if (proc->segmentation_header_.last_segment || proc->ras_raw_data_index_ >= proc->ras_raw_data_.iov_len) { DBG(rap, "RAS clear ras buffers"); proc->ras_raw_data_.iov_len = 0; proc->ras_raw_data_index_ = 0; proc->ranging_header_prepended_ = false; return; } } } static inline void resptracker_reset_current_proc(struct cstracker *t) { if (!t) return; if (t->current_proc) { cs_procedure_data_destroy(t->current_proc); t->current_proc = NULL; } } static void process_cs_mode_zero(struct bt_rap *rap, struct cs_procedure_data *proc, const struct cs_step_data *step, uint8_t idx, uint8_t mode_byte) { const struct cs_mode_zero_data *m0 = &step->step_mode_data.mode_zero_data; struct cstracker *resptracker = rap->resptracker; enum cs_role cs_role = resptracker->role; struct iovec temp_iov = { 0 }; temp_iov.iov_base = malloc(8); if (!temp_iov.iov_base) { DBG(rap, "Mode0 ERROR: malloc failed!"); return; } temp_iov.iov_len = 0; if (!util_iov_push_u8(&temp_iov, m0->packet_quality) || !util_iov_push_u8(&temp_iov, m0->packet_rssi_dbm) || !util_iov_push_u8(&temp_iov, m0->packet_ant)) goto done; if (cs_role == CS_ROLE_INITIATOR) { if (!util_iov_push_le16(&temp_iov, m0->init_measured_freq_offset)) goto done; } cs_pd_ras_append_subevent_bytes(proc, temp_iov.iov_base, temp_iov.iov_len); done: free(temp_iov.iov_base); DBG(rap, "step[%u]: mode=0x%02x Mode0 serialized payload_len=%zu " "role=%s", idx, mode_byte, temp_iov.iov_len, cs_role == CS_ROLE_INITIATOR ? "INIT" : "REFL"); } static void process_cs_mode_one(struct bt_rap *rap, struct cs_procedure_data *proc, const struct cs_step_data *step, uint8_t idx, uint8_t mode_byte) { const struct cs_mode_one_data *m1 = &step->step_mode_data.mode_one_data; struct cstracker *resptracker = rap->resptracker; struct iovec temp_iov = { 0 }; uint16_t time_val; uint32_t pct1; uint32_t pct2; enum cs_role cs_role = resptracker->role; uint8_t cs_rtt_type = resptracker->rtt_type; bool include_pct; temp_iov.iov_base = malloc(64); if (!temp_iov.iov_base) { DBG(rap, "Mode1 ERROR: malloc failed!"); return; } temp_iov.iov_len = 0; include_pct = (cs_rtt_type == 0x01 || cs_rtt_type == 0x02); if (!util_iov_push_u8(&temp_iov, m1->packet_quality) || !util_iov_push_u8(&temp_iov, m1->packet_nadm) || !util_iov_push_u8(&temp_iov, m1->packet_rssi_dbm)) goto done; /* Time value (2 bytes LE) - use the appropriate field based on role */ if (cs_role == CS_ROLE_REFLECTOR) time_val = m1->tod_toa_refl; else time_val = m1->toa_tod_init; if (!util_iov_push_le16(&temp_iov, time_val) || !util_iov_push_u8(&temp_iov, m1->packet_ant)) goto done; if (include_pct) { /* PCT1 (3 bytes LE) - 12-bit I + 12-bit Q */ pct1 = ((uint32_t)(m1->packet_pct1.i_sample & 0x0FFF)) | (((uint32_t)(m1->packet_pct1.q_sample & 0x0FFF)) << 12); if (!util_iov_push_le24(&temp_iov, pct1)) goto done; /* PCT2 (3 bytes LE) */ pct2 = ((uint32_t)(m1->packet_pct2.i_sample & 0x0FFF)) | (((uint32_t)(m1->packet_pct2.q_sample & 0x0FFF)) << 12); if (!util_iov_push_le24(&temp_iov, pct2)) goto done; } cs_pd_ras_append_subevent_bytes(proc, temp_iov.iov_base, temp_iov.iov_len); done: free(temp_iov.iov_base); DBG(rap, "step[%u]: mode=0x%02x Mode1 serialized payload_len=%zu " "role=%s rtt_type=0x%02x pct=%s", idx, mode_byte, temp_iov.iov_len, cs_role == CS_ROLE_INITIATOR ? "INIT" : "REFL", cs_rtt_type, include_pct ? "YES" : "NO"); } static void process_cs_mode_two(struct bt_rap *rap, struct cs_procedure_data *proc, const struct cs_step_data *step, uint8_t num_ant_paths, uint8_t idx, uint8_t mode_byte) { const struct cs_mode_two_data *m2 = &step->step_mode_data.mode_two_data; struct iovec temp_iov = { 0 }; uint8_t k; uint8_t num_paths = (num_ant_paths + 1) < 5 ? (num_ant_paths + 1) : 5; temp_iov.iov_base = malloc(128); if (!temp_iov.iov_base) { DBG(rap, "Mode2 ERROR: malloc failed!"); return; } temp_iov.iov_len = 0; if (!util_iov_push_u8(&temp_iov, m2->ant_perm_index)) goto done; /* Serialize each path: PCT (3 bytes LE) + quality (1 byte) */ for (k = 0; k < num_paths; k++) { /* Convert 4-byte structure PCT to 3-byte wire format */ uint32_t pct = ((uint32_t)(m2->tone_pct[k].i_sample & 0x0FFF)) | (((uint32_t)(m2->tone_pct[k].q_sample & 0x0FFF)) << 12); if (!util_iov_push_le24(&temp_iov, pct) || !util_iov_push_u8(&temp_iov, m2->tone_quality_indicator[k])) goto done; } cs_pd_ras_append_subevent_bytes(proc, temp_iov.iov_base, temp_iov.iov_len); done: free(temp_iov.iov_base); DBG(rap, "step[%u]: mode=0x%02x Mode2 serialized payload_len=%zu " "paths=%u", idx, mode_byte, temp_iov.iov_len, num_paths); } static void process_cs_mode_three(struct bt_rap *rap, struct cs_procedure_data *proc, const struct cs_step_data *step, uint8_t num_ant_paths, uint8_t idx, uint8_t mode_byte) { const struct cs_mode_three_data *m3 = &step->step_mode_data.mode_three_data; const struct cs_mode_one_data *m1 = &m3->mode_one_data; const struct cs_mode_two_data *m2 = &m3->mode_two_data; struct cstracker *resptracker = rap->resptracker; struct iovec temp_iov = { 0 }; uint16_t time_val; uint32_t pct1; uint32_t pct2; enum cs_role cs_role = resptracker->role; uint8_t cs_rtt_type = resptracker->rtt_type; uint8_t k; uint8_t num_paths = (num_ant_paths + 1) < 5 ? (num_ant_paths + 1) : 5; bool include_pct; temp_iov.iov_base = malloc(128); if (!temp_iov.iov_base) { DBG(rap, "Mode3 ERROR: malloc failed!"); return; } temp_iov.iov_len = 0; /* Determine if PCT samples should be included */ include_pct = (cs_rtt_type == 0x01 || cs_rtt_type == 0x02); if (!util_iov_push_u8(&temp_iov, m1->packet_quality) || !util_iov_push_u8(&temp_iov, m1->packet_nadm) || !util_iov_push_u8(&temp_iov, m1->packet_rssi_dbm)) goto done; /* Time value (2 bytes LE) - use the appropriate field based on role */ if (cs_role == CS_ROLE_REFLECTOR) time_val = m1->tod_toa_refl; else time_val = m1->toa_tod_init; if (!util_iov_push_le16(&temp_iov, time_val) || !util_iov_push_u8(&temp_iov, m1->packet_ant)) goto done; /* PCT samples if RTT type contains sounding sequence */ if (include_pct) { /* PCT1 (3 bytes LE) - 12-bit I + 12-bit Q */ pct1 = ((uint32_t)(m1->packet_pct1.i_sample & 0x0FFF)) | (((uint32_t)(m1->packet_pct1.q_sample & 0x0FFF)) << 12); if (!util_iov_push_le24(&temp_iov, pct1)) goto done; /* PCT2 (3 bytes LE) */ pct2 = ((uint32_t)(m1->packet_pct2.i_sample & 0x0FFF)) | (((uint32_t)(m1->packet_pct2.q_sample & 0x0FFF)) << 12); if (!util_iov_push_le24(&temp_iov, pct2)) goto done; } if (!util_iov_push_u8(&temp_iov, m2->ant_perm_index)) goto done; for (k = 0; k < num_paths; k++) { /* Convert 4-byte structure PCT to 3-byte wire format */ uint32_t pct = ((uint32_t)(m2->tone_pct[k].i_sample & 0x0FFF)) | (((uint32_t)(m2->tone_pct[k].q_sample & 0x0FFF)) << 12); if (!util_iov_push_le24(&temp_iov, pct) || !util_iov_push_u8(&temp_iov, m2->tone_quality_indicator[k])) goto done; } cs_pd_ras_append_subevent_bytes(proc, temp_iov.iov_base, temp_iov.iov_len); done: free(temp_iov.iov_base); DBG(rap, "=== Mode3 END: step[%u] payload_len=%zu paths=%u role=%s " "rtt_type=0x%02x pct=%s ===", idx, temp_iov.iov_len, num_paths, cs_role == CS_ROLE_INITIATOR ? "INIT" : "REFL", cs_rtt_type, include_pct ? "YES" : "NO"); } static void process_cs_mode_step(struct bt_rap *rap, struct cs_procedure_data *proc, const struct cs_step_data *step, uint8_t num_ant_paths, uint8_t idx) { const uint8_t mode = step->step_mode; const uint8_t payload_len = step->step_data_length; uint8_t mode_byte; uint8_t mode_type; const uint8_t *payload; uint8_t plen; bool step_aborted; /* Check if step is aborted: bit 7 of step_mode or 0 payload len */ step_aborted = (mode & RAS_STEP_ABORTED_BIT) || (payload_len == 0); DBG(rap, "step[%u]: mode=0x%02x channel=%u payload_len=%u " "aborted=%s", idx, mode, step->step_chnl, payload_len, step_aborted ? "YES" : "NO"); mode_byte = step->step_mode; if (step_aborted) { /* Ensure abort bit is set */ mode_byte |= RAS_STEP_ABORTED_BIT; cs_pd_ras_append_subevent_bytes(proc, &mode_byte, 1); /* No payload when aborted - per RAS spec Table 3.8 */ DBG(rap, "step[%u]: mode=0x%02x aborted, no payload sent", idx, mode_byte); return; } mode_type = mode & 0x03; /* Mode byte first (without abort bit) */ cs_pd_ras_append_subevent_bytes(proc, &mode_byte, 1); switch (mode_type) { case CS_MODE_ZERO: process_cs_mode_zero(rap, proc, step, idx, mode_byte); break; case CS_MODE_ONE: process_cs_mode_one(rap, proc, step, idx, mode_byte); break; case CS_MODE_TWO: process_cs_mode_two(rap, proc, step, num_ant_paths, idx, mode_byte); break; case CS_MODE_THREE: process_cs_mode_three(rap, proc, step, num_ant_paths, idx, mode_byte); break; default: /* Unknown mode: use raw structure bytes */ payload = (const uint8_t *)&step->step_mode_data; plen = step->step_data_length; cs_pd_ras_append_subevent_bytes(proc, payload, plen); DBG(rap, "step[%u]: mode=0x%02x unknown mode, " "payload_len=%u sent", idx, mode_byte, (unsigned int)plen); break; } } /* Unified local subevent handler */ static void handle_local_subevent_result(struct bt_rap *rap, bool has_header_fields, uint8_t config_id, uint8_t num_ant_paths, uint16_t proc_counter, uint16_t start_acl_conn_evt_counter, uint16_t freq_comp, int8_t ref_pwr_lvl, uint8_t proc_done_status, uint8_t subevt_done_status, uint8_t abort_reason, uint8_t num_steps_reported, const void *step_bytes) { struct cstracker *resptracker; struct cs_procedure_data *proc; const struct cs_step_data *steps; uint8_t idx; if (!rap || !rap->resptracker || !step_bytes) return; resptracker = rap->resptracker; if (resptracker->current_proc) { struct cs_procedure_data *cur = resptracker->current_proc; if (has_header_fields && cur->counter != proc_counter) { /* Safety: a new procedure; destroy the previous one */ resptracker_reset_current_proc(resptracker); } } proc = resptracker->current_proc; /* Cache header info from a RESULT event for later CONT usage */ if (has_header_fields) { resptracker->last_proc_counter = proc_counter; resptracker->last_start_acl_conn_evt_counter = start_acl_conn_evt_counter; resptracker->last_freq_comp = freq_comp; resptracker->last_ref_pwr_lvl = ref_pwr_lvl; } /* Create the procedure on first use */ if (!proc) { uint16_t create_counter = has_header_fields ? proc_counter : resptracker->last_proc_counter; proc = cs_procedure_data_create(create_counter, num_ant_paths, config_id, resptracker->selected_tx_power); if (!proc) return; resptracker->current_proc = proc; /* Reference power levels and status defaults */ cs_pd_set_reference_power_levels(proc, has_header_fields ? ref_pwr_lvl : resptracker->last_ref_pwr_lvl, has_header_fields ? ref_pwr_lvl : resptracker->last_ref_pwr_lvl); cs_pd_set_local_status(proc, (enum cs_procedure_done_status)proc_done_status); cs_pd_set_remote_status(proc, (enum cs_procedure_done_status)subevt_done_status); } /* Begin a new RAS subevent only when we have header fields */ if (has_header_fields) { cs_pd_ras_begin_subevent(proc, start_acl_conn_evt_counter, freq_comp, ref_pwr_lvl); } /* step_bytes points to an array of struct cs_step_data */ steps = (const struct cs_step_data *)step_bytes; /* Process each step using helper function */ for (idx = 0; idx < num_steps_reported; idx++) process_cs_mode_step(rap, proc, &steps[idx], num_ant_paths, idx); /* Update status for this chunk */ cs_pd_set_local_status(proc, (enum cs_procedure_done_status)proc_done_status); cs_pd_set_remote_status(proc, (enum cs_procedure_done_status)subevt_done_status); /* Commit subevent chunk (RESULT or CONT) */ cs_pd_ras_commit_subevent(proc, num_steps_reported, proc_done_status, subevt_done_status, abort_reason & 0x0F, (abort_reason >> 4) & 0x0F); /* Ensure first segment body starts with the 4-byte RangingHeader */ ras_maybe_prepend_ranging_header(proc); if (subevt_done_status != SUBEVENT_DONE_PARTIAL_RESULTS) /* Send RAS raw segment data */ send_ras_segment_data(rap, proc); /* Procedure complete? Clean up */ if (proc_done_status == CS_PROC_ALL_RESULTS_COMPLETE) { DBG(rap, "Destroying CsProcedureData counter=%u and " "clearing current_proc", proc->counter); resptracker_reset_current_proc(resptracker); /* Reset cached header values for next procedure */ resptracker->last_proc_counter = 0; resptracker->last_start_acl_conn_evt_counter = 0; resptracker->last_freq_comp = 0; resptracker->last_ref_pwr_lvl = 0; } } static void form_ras_data_with_cs_subevent_result(struct bt_rap *rap, const struct rap_ev_cs_subevent_result *data, uint16_t length) { size_t base_len = offsetof(struct rap_ev_cs_subevent_result, step_data); if (!rap || !rap->resptracker || !data) return; /* Defensive check: base header must be present */ if (length < base_len) return; DBG(rap, "Received CS subevent result subevent: len=%u", length); handle_local_subevent_result(rap, true, /* has header fields */ data->config_id, data->num_ant_paths, data->proc_counter, data->start_acl_conn_evt_counter, data->freq_comp, data->ref_pwr_lvl, data->proc_done_status, data->subevt_done_status, data->abort_reason, data->num_steps_reported, data->step_data); /* start of steps */ } static void form_ras_data_with_cs_subevent_result_cont(struct bt_rap *rap, const struct rap_ev_cs_subevent_result_cont *cont, uint16_t length) { size_t base_len = offsetof(struct rap_ev_cs_subevent_result_cont, step_data); struct cstracker *resptracker; if (!rap || !rap->resptracker || !cont) return; if (length < base_len) return; DBG(rap, "Received CS subevent result continue subevent: len=%u", length); resptracker = rap->resptracker; /* Use cached header values captured from the last RESULT event */ handle_local_subevent_result(rap, false, /* CONT has no header fields */ cont->config_id, cont->num_ant_paths, resptracker->last_proc_counter, resptracker->last_start_acl_conn_evt_counter, resptracker->last_freq_comp, resptracker->last_ref_pwr_lvl, cont->proc_done_status, cont->subevt_done_status, cont->abort_reason, cont->num_steps_reported, cont->step_data); } static void fill_initiator_data_from_cs_subevent_result_cont(struct bt_rap *rap, const struct rap_ev_cs_subevent_result_cont *cont, uint16_t length) { size_t base_len = offsetof(struct rap_ev_cs_subevent_result_cont, step_data); if (!rap || !rap->reqtracker || !cont) return; if (length < base_len) return; DBG(rap, "Received CS subevent result continue subevent: len=%u", length); } static void fill_initiator_data_from_cs_subevent_result(struct bt_rap *rap, const struct rap_ev_cs_subevent_result *data, uint16_t length) { size_t base_len = offsetof(struct rap_ev_cs_subevent_result, step_data); if (!rap || !rap->reqtracker || !data) return; /* Defensive check: base header must be present */ if (length < base_len) return; DBG(rap, "Received CS subevent result subevent: len=%u", length); /* TODO: Store initiator subevent result data */ } void bt_rap_hci_cs_subevent_result_cont_callback(uint16_t length, const void *param, void *user_data) { const struct rap_ev_cs_subevent_result_cont *cont = param; struct bt_rap *rap = user_data; DBG(rap, "Received CS subevent CONT: len=%u", length); if (rap->resptracker && rap->resptracker->role == CS_REFLECTOR) form_ras_data_with_cs_subevent_result_cont(rap, cont, length); if (rap->reqtracker && rap->reqtracker->role == CS_INITIATOR) fill_initiator_data_from_cs_subevent_result_cont(rap, cont, length); } void bt_rap_hci_cs_subevent_result_callback(uint16_t length, const void *param, void *user_data) { const struct rap_ev_cs_subevent_result *data = param; struct bt_rap *rap = user_data; DBG(rap, "Received CS subevent: len=%u", length); /* Populate CsProcedureData and send RAS payload */ if (rap->resptracker && rap->resptracker->role == CS_REFLECTOR) form_ras_data_with_cs_subevent_result(rap, data, length); if (rap->reqtracker && rap->reqtracker->role == CS_INITIATOR) fill_initiator_data_from_cs_subevent_result(rap, data, length); } void bt_rap_hci_cs_procedure_enable_complete_callback(uint16_t length, const void *param, void *user_data) { const struct rap_ev_cs_proc_enable_cmplt *data = param; struct bt_rap *rap = user_data; struct cstracker *resptracker; DBG(rap, "Received CS procedure enable complete subevent: len=%u", length); if (!rap->resptracker) { resptracker = new0(struct cstracker, 1); cs_tracker_init(resptracker); rap->resptracker = resptracker; } resptracker = rap->resptracker; /* Populate responder tracker */ resptracker->config_id = data->config_id; resptracker->selected_tx_power = data->sel_tx_pwr; } void bt_rap_hci_cs_sec_enable_complete_callback(uint16_t length, const void *param, void *user_data) { struct bt_rap *rap = user_data; DBG(rap, "Received CS security enable subevent: len=%u", length); } void bt_rap_hci_cs_config_complete_callback(uint16_t length, const void *param, void *user_data) { const struct rap_ev_cs_config_cmplt *data = param; struct bt_rap *rap = user_data; struct cstracker *resptracker; struct cstracker *reqtracker; if (!rap) return; DBG(rap, "Received CS config complete subevent: len=%u", length); if (!rap->resptracker) { resptracker = new0(struct cstracker, 1); cs_tracker_init(resptracker); rap->resptracker = resptracker; } resptracker = rap->resptracker; /* Basic fields */ resptracker->config_id = data->config_id; resptracker->role = data->role; resptracker->rtt_type = data->rtt_type; if (!rap->reqtracker) { reqtracker = new0(struct cstracker, 1); cs_tracker_init(reqtracker); rap->reqtracker = reqtracker; } reqtracker = rap->reqtracker; /* Basic fields */ reqtracker->config_id = data->config_id; reqtracker->role = data->role; reqtracker->rtt_type = data->rtt_type; } struct bt_rap *bt_rap_new(struct gatt_db *ldb, struct gatt_db *rdb) { struct bt_rap *rap; struct bt_rap_db *rapdb; if (!ldb) return NULL; rapdb = rap_get_db(ldb); if (!rapdb) return NULL; rap = new0(struct bt_rap, 1); rap->lrapdb = rapdb; rap->pending = queue_new(); rap->ready_cbs = queue_new(); rap->notify = queue_new(); if (!rdb) goto done; rapdb = new0(struct bt_rap_db, 1); rapdb->db = gatt_db_ref(rdb); rap->rrapdb = rapdb; done: bt_rap_ref(rap); return rap; } static void ras_pending_destroy(void *data) { struct bt_rap_pending *pending = data; struct bt_rap *rap = pending->rap; if (queue_remove_if(rap->pending, NULL, pending)) free(pending); } static void ras_pending_complete(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_rap_pending *pending = user_data; if (pending->func) pending->func(pending->rap, success, att_ecode, value, length, pending->userdata); } static void rap_read_value(struct bt_rap *rap, uint16_t value_handle, rap_func_t func, void *user_data) { struct bt_rap_pending *pending; pending = new0(struct bt_rap_pending, 1); pending->rap = rap; pending->func = func; pending->userdata = user_data; pending->id = bt_gatt_client_read_value(rap->client, value_handle, ras_pending_complete, pending, ras_pending_destroy); if (!pending->id) { DBG(rap, "Unable to send Read request"); free(pending); return; } queue_push_tail(rap->pending, pending); } static void ras_register(uint16_t att_ecode, void *user_data) { struct bt_rap_notify *notify = user_data; if (att_ecode) DBG(notify->rap, "RAS register failed 0x%04x", att_ecode); (void)notify; } static void rap_notify(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct bt_rap_notify *notify = user_data; if (notify->func) notify->func(notify->rap, value_handle, value, length, notify->user_data); } static void rap_notify_destroy(void *data) { struct bt_rap_notify *notify = data; struct bt_rap *rap = notify->rap; if (queue_remove_if(rap->notify, NULL, notify)) free(notify); } static unsigned int bt_rap_register_notify(struct bt_rap *rap, uint16_t value_handle, rap_notify_t func, void *user_data) { struct bt_rap_notify *notify; notify = new0(struct bt_rap_notify, 1); notify->rap = rap; notify->func = func; notify->user_data = user_data; DBG(rap, "register for notifications"); notify->id = bt_gatt_client_register_notify(rap->client, value_handle, ras_register, rap_notify, notify, rap_notify_destroy); if (!notify->id) { DBG(rap, "Unable to register for notifications"); free(notify); return 0; } queue_push_tail(rap->notify, notify); return notify->id; } static inline bool parse_segmentation_header(struct iovec *iov, struct segmentation_header *s) { uint8_t byte; if (!util_iov_pull_u8(iov, &byte)) return false; s->first_segment = (byte & 0x01) ? 1 : 0; s->last_segment = (byte & 0x02) ? 1 : 0; s->rolling_segment_counter = (byte >> 2) & 0x3F; return true; } static void parse_i_q_sample(struct iovec *iov, int16_t *i_sample, int16_t *q_sample) { uint32_t buffer; uint32_t i12; uint32_t q12; if (!util_iov_pull_le24(iov, &buffer)) { *i_sample = 0; *q_sample = 0; return; } i12 = buffer & 0x0FFFU; /* bits 0..11 */ q12 = (buffer >> 12) & 0x0FFFU; /* bits 12..23 */ *i_sample = SIGN_EXTEND_TO_16(i12, 12); *q_sample = SIGN_EXTEND_TO_16(q12, 12); } static size_t get_mode_zero_length(enum cs_role remote_role) { return (remote_role == CS_ROLE_INITIATOR) ? CS_MODE_ZERO_WIRE_INIT_SIZE : CS_MODE_ZERO_WIRE_REF_SIZE; } static void parse_mode_zero(struct bt_rap *rap, struct iovec *mode_iov, enum cs_role remote_role) { uint8_t packet_quality; int8_t packet_rssi_dbm; uint8_t packet_ant; uint16_t init_measured_freq_offset = 0; if (!util_iov_pull_u8(mode_iov, &packet_quality) || !util_iov_pull_u8(mode_iov, (uint8_t *)&packet_rssi_dbm) || !util_iov_pull_u8(mode_iov, &packet_ant)) { DBG(rap, "Mode 0: failed to parse common fields"); return; } if (remote_role == CS_ROLE_INITIATOR) { if (!util_iov_pull_le16(mode_iov, &init_measured_freq_offset)) { DBG(rap, "Mode 0: failed to parse freq offset"); return; } } /* TODO: Store this data as reflector data */ } static size_t get_mode_one_length(bool include_pct) { if (include_pct) return CS_MODE_ONE_WIRE_SIZE_MAX; return CS_MODE_ONE_WIRE_SIZE_MIN; } static void parse_mode_one(struct bt_rap *rap, struct iovec *mode_iov, enum cs_role remote_role, bool include_pct) { uint8_t packet_quality; uint8_t packet_nadm; int8_t packet_rssi_dbm; int16_t time_value; uint8_t packet_ant; int16_t pct1_i = 0, pct1_q = 0; int16_t pct2_i = 0, pct2_q = 0; if (!util_iov_pull_u8(mode_iov, &packet_quality) || !util_iov_pull_u8(mode_iov, &packet_nadm) || !util_iov_pull_u8(mode_iov, (uint8_t *)&packet_rssi_dbm) || !util_iov_pull_le16(mode_iov, (uint16_t *)&time_value) || !util_iov_pull_u8(mode_iov, &packet_ant)) { DBG(rap, "Mode 1: failed to parse fixed fields"); return; } if (include_pct) { parse_i_q_sample(mode_iov, &pct1_i, &pct1_q); parse_i_q_sample(mode_iov, &pct2_i, &pct2_q); } /* TODO: Store this data as reflector data */ } static size_t get_mode_two_length(uint8_t num_antenna_paths) { uint8_t num_tone_data = num_antenna_paths + 1; return 1 + (4 * num_tone_data); } static void parse_mode_two(struct bt_rap *rap, struct iovec *mode_iov, uint8_t num_antenna_paths) { uint8_t ant_perm_index; int16_t tone_pct_i[5]; int16_t tone_pct_q[5]; uint8_t tone_quality[5]; uint8_t k; uint8_t num_paths = (num_antenna_paths + 1) < 5 ? (num_antenna_paths + 1) : 5; if (!util_iov_pull_u8(mode_iov, &ant_perm_index)) { DBG(rap, "Mode 2: failed to parse ant_perm_index"); return; } for (k = 0; k < num_paths; k++) { int16_t i_val, q_val; if (mode_iov->iov_len < 4) { DBG(rap, "Mode 2: insufficient PCT for " "path %u (rem=%zu)", k, mode_iov->iov_len); break; } parse_i_q_sample(mode_iov, &i_val, &q_val); tone_pct_i[k] = i_val; tone_pct_q[k] = q_val; util_iov_pull_u8(mode_iov, &tone_quality[k]); DBG(rap, "tone_quality_indicator : %d", tone_quality[k]); DBG(rap, "[i, q] : %d, %d", tone_pct_i[k], tone_pct_q[k]); } DBG(rap, " cs_mode_two_data: ant_perm_idx=%u", ant_perm_index); /* TODO: Store this data as reflector data */ } static size_t get_mode_three_length(uint8_t num_antenna_paths, bool include_pct) { return get_mode_one_length(include_pct) + get_mode_two_length(num_antenna_paths); } static void parse_mode_three(struct bt_rap *rap, struct iovec *mode_iov, enum cs_role remote_role, bool include_pct, uint8_t num_antenna_paths) { /* Mode 3 = Mode 1 + Mode 2 */ parse_mode_one(rap, mode_iov, remote_role, include_pct); if (mode_iov->iov_len > 0) parse_mode_two(rap, mode_iov, num_antenna_paths); } static bool parse_subevent_header(struct iovec *iov, struct ras_subevent_header *hdr) { uint16_t start_acl, freq_comp; uint8_t done_byte, abort_byte, ref_pwr, num_steps; if (!util_iov_pull_le16(iov, &start_acl) || !util_iov_pull_le16(iov, &freq_comp) || !util_iov_pull_u8(iov, &done_byte) || !util_iov_pull_u8(iov, &abort_byte) || !util_iov_pull_u8(iov, &ref_pwr) || !util_iov_pull_u8(iov, &num_steps)) return false; hdr->start_acl_conn_event = start_acl; hdr->frequency_compensation = freq_comp; hdr->ranging_done_status = RAS_DONE_STATUS_UNPACK_RANGING(done_byte); hdr->subevent_done_status = RAS_DONE_STATUS_UNPACK_SUBEVENT(done_byte); hdr->ranging_abort_reason = RAS_ABORT_REASON_UNPACK_RANGING(abort_byte); hdr->subevent_abort_reason = RAS_ABORT_REASON_UNPACK_SUBEVENT(abort_byte); hdr->reference_power_level = (int8_t)ref_pwr; hdr->num_steps_reported = num_steps; return true; } static bool parse_step(struct bt_rap *rap, struct iovec *iov, struct cstracker *reqtracker, uint8_t num_antenna_paths, uint8_t step_idx) { uint8_t mode_byte, step_mode; bool include_pct; enum cs_role remote_role; size_t step_payload_len; struct iovec mode_iov; void *payload; if (!util_iov_pull_u8(iov, &mode_byte)) { DBG(rap, "Insufficient data for step %u", step_idx); return false; } if (mode_byte & RAS_STEP_ABORTED_BIT) { DBG(rap, " Step %u: mode=%u (aborted)", step_idx, mode_byte & 0x03); return true; } step_mode = mode_byte & 0x03; include_pct = (reqtracker->rtt_type == 0x01 || reqtracker->rtt_type == 0x02); remote_role = (reqtracker->role == CS_ROLE_INITIATOR) ? CS_ROLE_REFLECTOR : CS_ROLE_INITIATOR; switch (step_mode) { case CS_MODE_ZERO: step_payload_len = get_mode_zero_length(remote_role); break; case CS_MODE_ONE: step_payload_len = get_mode_one_length(include_pct); break; case CS_MODE_TWO: step_payload_len = get_mode_two_length(num_antenna_paths); break; case CS_MODE_THREE: step_payload_len = get_mode_three_length(num_antenna_paths, include_pct); break; default: DBG(rap, " Step %u: unknown mode=%u", step_idx, step_mode); return true; } DBG(rap, " Step %u: mode=%u payload_len=%zu", step_idx, step_mode, step_payload_len); payload = util_iov_pull_mem(iov, step_payload_len); if (!payload) { DBG(rap, "Insufficient data for step %u payload " "(need %zu, have %zu)", step_idx, step_payload_len, iov->iov_len); return false; } mode_iov.iov_base = payload; mode_iov.iov_len = step_payload_len; switch (step_mode) { case CS_MODE_ZERO: parse_mode_zero(rap, &mode_iov, remote_role); break; case CS_MODE_ONE: parse_mode_one(rap, &mode_iov, remote_role, include_pct); break; case CS_MODE_TWO: parse_mode_two(rap, &mode_iov, num_antenna_paths); break; case CS_MODE_THREE: parse_mode_three(rap, &mode_iov, remote_role, include_pct, num_antenna_paths); break; default: break; } return true; } static void parse_subevent_steps(struct bt_rap *rap, struct iovec *iov, struct cstracker *reqtracker, uint8_t num_antenna_paths, uint8_t num_steps) { uint8_t i; for (i = 0; i < num_steps; i++) { if (!parse_step(rap, iov, reqtracker, num_antenna_paths, i)) break; } } static void parse_ras_data_segments(struct bt_rap *rap, struct cstracker *reqtracker) { struct iovec iov; uint8_t antenna_mask; uint8_t num_antenna_paths; if (!rap || !reqtracker) return; DBG(rap, "Complete RAS data received: %zu bytes", reqtracker->segment_data.iov_len); antenna_mask = ranging_header_get_antenna_mask(&reqtracker->ranging_header_); num_antenna_paths = antenna_mask_count_paths(antenna_mask); iov = reqtracker->segment_data; while (iov.iov_len >= RAS_SUBEVENT_HEADER_SIZE) { struct ras_subevent_header hdr; if (!parse_subevent_header(&iov, &hdr)) break; DBG(rap, "Parsed subevent: start_acl=%u " "freq_comp=%d ref_pwr=%d steps=%u", hdr.start_acl_conn_event, hdr.frequency_compensation, hdr.reference_power_level, hdr.num_steps_reported); parse_subevent_steps(rap, &iov, reqtracker, num_antenna_paths, hdr.num_steps_reported); if (hdr.subevent_done_status == SUBEVENT_DONE_ALL_RESULTS_COMPLETE || hdr.ranging_done_status == RANGING_DONE_ALL_RESULTS_COMPLETE) { DBG(rap, "Ranging procedure complete"); break; } } free(reqtracker->segment_data.iov_base); reqtracker->segment_data.iov_base = NULL; reqtracker->segment_data.iov_len = 0; } static bool process_first_segment(struct bt_rap *rap, struct cstracker *reqtracker, struct iovec *iov, bool last_segment) { uint16_t counter_config_val; int8_t selected_tx_power; uint8_t antenna_pct; if (!util_iov_pull_le16(iov, &counter_config_val) || !util_iov_pull_u8(iov, (uint8_t *)&selected_tx_power) || !util_iov_pull_u8(iov, &antenna_pct)) { DBG(rap, "First segment too short for ranging header"); return false; } ranging_header_set_counter(&reqtracker->ranging_header_, counter_config_val & 0x0FFF); ranging_header_set_config_id(&reqtracker->ranging_header_, (counter_config_val >> 12) & 0x0F); reqtracker->ranging_header_.selected_tx_power = selected_tx_power; reqtracker->ranging_header_.antenna_pct = antenna_pct; DBG(rap, "First segment: parsed ranging header " "(counter=%u, config_id=%u, tx_pwr=%d)", counter_config_val & 0x0FFF, (counter_config_val >> 12) & 0x0F, selected_tx_power); if (reqtracker->segment_data.iov_base) { free(reqtracker->segment_data.iov_base); reqtracker->segment_data.iov_base = NULL; reqtracker->segment_data.iov_len = 0; } if (iov->iov_len > 0) { if (!util_iov_append(&reqtracker->segment_data, iov->iov_base, iov->iov_len)) { DBG(rap, "Failed to initialize segment accumulator"); return false; } DBG(rap, "First segment: initialized accumulator " "with %zu bytes", iov->iov_len); } if (!last_segment) return false; DBG(rap, "Single-segment: first=1, last=1"); return true; } static void ras_realtime_notify_cb(struct bt_rap *rap, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; struct segmentation_header seg_hdr; struct cstracker *reqtracker; if (!rap || !value || length == 0) { DBG(rap, "Invalid notification data"); return; } DBG(rap, "Received real-time notification: handle=0x%04x len=%u", value_handle, length); if (!parse_segmentation_header(&iov, &seg_hdr)) { DBG(rap, "Failed to parse segmentation header"); return; } DBG(rap, "Segment: first=%u last=%u counter=%u", seg_hdr.first_segment, seg_hdr.last_segment, seg_hdr.rolling_segment_counter); if (!rap->reqtracker) { DBG(rap, "reqtracker is not initialised"); return; } reqtracker = rap->reqtracker; if (seg_hdr.first_segment) { if (!process_first_segment(rap, reqtracker, &iov, seg_hdr.last_segment)) return; } else { if (iov.iov_len > 0) { if (!util_iov_append(&reqtracker->segment_data, iov.iov_base, iov.iov_len)) { DBG(rap, "Failed to append segment data"); return; } DBG(rap, "Continuation segment: appended " "%zu bytes (total=%zu)", iov.iov_len, reqtracker->segment_data.iov_len); } } /* Last segment: parse complete RAS data */ if (seg_hdr.last_segment) parse_ras_data_segments(rap, reqtracker); } static void read_ras_features(struct bt_rap *rap, bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; uint32_t features = 0; struct ras *ras; bool supports_realtime; bool retrieve_lost; bool abort_operation; if (!success) { DBG(rap, "Unable to read RAS Features: error 0x%02x", att_ecode); return; } ras = rap_get_ras(rap); if (length == 0 || length > 4) { DBG(rap, "Invalid RAS Features length: %u (expected 1-4)", length); return; } if (length < 4) { uint8_t padded[4] = { 0 }; memcpy(padded, value, length); features = get_le32(padded); DBG(rap, "RAS Features: short read (%u bytes), zero-pad to 4", length); } else if (!util_iov_pull_le32(&iov, &features)) { DBG(rap, "Unable to parse RAS Features value"); return; } DBG(rap, "RAS Features: 0x%08x", features); supports_realtime = (features & 0x01) != 0; retrieve_lost = (features & 0x02) != 0; abort_operation = (features & 0x04) != 0; DBG(rap, "RAS Features - Real-time: %s, Retrieve Lost: %s, Abort: %s", supports_realtime ? "Yes" : "No", retrieve_lost ? "Yes" : "No", abort_operation ? "Yes" : "No"); DBG(rap, "RAS features read successfully, capabilities determined"); /* Register for real-time characteristic notifications if supported */ if (supports_realtime && ras && ras->realtime_chrc && rap->client) { uint16_t value_handle; bt_uuid_t uuid; if (gatt_db_attribute_get_char_data(ras->realtime_chrc, NULL, &value_handle, NULL, NULL, &uuid)) { unsigned int notify_id; notify_id = bt_rap_register_notify(rap, value_handle, ras_realtime_notify_cb, NULL); if (!notify_id) DBG(rap, "Failed to register for " "real-time notifications"); else DBG(rap, "Registered for real-time " "features: id=%u", notify_id); } } else { DBG(rap, "On demand ranging " "remote device - skipping notification " "registration"); } } static void foreach_rap_char(struct gatt_db_attribute *attr, void *user_data) { struct bt_rap *rap = user_data; uint16_t value_handle; bt_uuid_t uuid; bt_uuid_t uuid_features; bt_uuid_t uuid_realtime; bt_uuid_t uuid_ondemand; bt_uuid_t uuid_cp; bt_uuid_t uuid_dataready; bt_uuid_t uuid_overwritten; struct ras *ras; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) return; bt_uuid16_create(&uuid_features, RAS_FEATURES_UUID); bt_uuid16_create(&uuid_realtime, RAS_REALTIME_DATA_UUID); bt_uuid16_create(&uuid_ondemand, RAS_ONDEMAND_DATA_UUID); bt_uuid16_create(&uuid_cp, RAS_CONTROL_POINT_UUID); bt_uuid16_create(&uuid_dataready, RAS_DATA_READY_UUID); bt_uuid16_create(&uuid_overwritten, RAS_DATA_OVERWRITTEN_UUID); if (!bt_uuid_cmp(&uuid, &uuid_features)) { DBG(rap, "Features characteristic found: handle 0x%04x", value_handle); ras = rap_get_ras(rap); if (!ras || ras->feat_chrc) return; ras->feat_chrc = attr; if (rap->client) { rap_read_value(rap, value_handle, read_ras_features, rap); } } if (!bt_uuid_cmp(&uuid, &uuid_realtime)) { DBG(rap, "Real Time Data characteristic found: handle 0x%04x", value_handle); ras = rap_get_ras(rap); if (!ras || ras->realtime_chrc) return; ras->realtime_chrc = attr; } if (!bt_uuid_cmp(&uuid, &uuid_ondemand)) { DBG(rap, "On-demand Data characteristic found: handle 0x%04x", value_handle); ras = rap_get_ras(rap); if (!ras || ras->ondemand_chrc) return; ras->ondemand_chrc = attr; } if (!bt_uuid_cmp(&uuid, &uuid_cp)) { DBG(rap, "Control Point characteristic found: handle 0x%04x", value_handle); ras = rap_get_ras(rap); if (!ras || ras->cp_chrc) return; ras->cp_chrc = attr; } if (!bt_uuid_cmp(&uuid, &uuid_dataready)) { DBG(rap, "Data Ready characteristic found: handle 0x%04x", value_handle); ras = rap_get_ras(rap); if (!ras || ras->ready_chrc) return; ras->ready_chrc = attr; } if (!bt_uuid_cmp(&uuid, &uuid_overwritten)) { DBG(rap, "Overwritten characteristic found: handle 0x%04x", value_handle); ras = rap_get_ras(rap); if (!ras || ras->overwritten_chrc) return; ras->overwritten_chrc = attr; } } static void foreach_rap_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_rap *rap = user_data; struct ras *ras = rap_get_ras(rap); ras->svc = attr; gatt_db_service_set_claimed(attr, true); gatt_db_service_foreach_char(attr, foreach_rap_char, rap); } unsigned int bt_rap_ready_register(struct bt_rap *rap, bt_rap_ready_func_t func, void *user_data, bt_rap_destroy_func_t destroy) { struct bt_rap_ready *ready; static unsigned int id; if (!rap) return 0; DBG(rap, "bt_rap_ready_register"); ready = new0(struct bt_rap_ready, 1); ready->id = ++id ? id : ++id; ready->func = func; ready->destroy = destroy; ready->data = user_data; queue_push_tail(rap->ready_cbs, ready); return ready->id; } static bool match_ready_id(const void *data, const void *match_data) { const struct bt_rap_ready *ready = data; unsigned int id = PTR_TO_UINT(match_data); return ready->id == id; } bool bt_rap_ready_unregister(struct bt_rap *rap, unsigned int id) { struct bt_rap_ready *ready; ready = queue_remove_if(rap->ready_cbs, match_ready_id, UINT_TO_PTR(id)); if (!ready) return false; rap_ready_free(ready); return true; } static struct bt_rap *bt_rap_ref_safe(struct bt_rap *rap) { if (!rap || !rap->ref_count) return NULL; return bt_rap_ref(rap); } static void rap_notify_ready(struct bt_rap *rap) { const struct queue_entry *entry; if (!bt_rap_ref_safe(rap)) return; for (entry = queue_get_entries(rap->ready_cbs); entry; entry = entry->next) { struct bt_rap_ready *ready = entry->data; ready->func(rap, ready->data); } bt_rap_unref(rap); } static void rap_idle(void *data) { struct bt_rap *rap = data; rap->idle_id = 0; rap_notify_ready(rap); } bool bt_rap_attach(struct bt_rap *rap, struct bt_gatt_client *client) { bt_uuid_t uuid; if (!sessions) sessions = queue_new(); queue_push_tail(sessions, rap); if (!client) return true; if (rap->client) return false; rap->client = bt_gatt_client_clone(client); if (!rap->client) return false; bt_gatt_client_idle_register(rap->client, rap_idle, rap, NULL); bt_uuid16_create(&uuid, RAS_UUID16); gatt_db_foreach_service(rap->rrapdb->db, &uuid, foreach_rap_service, rap); return true; } bluez-5.87/src/shared/PaxHeaders/bap-debug.c0000644000000000000000000000005014536422313015705 xustar0020 atime=1783092944 20 ctime=1783094100 bluez-5.87/src/shared/bap-debug.c0000644000000000000000000003474214536422313015400 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2023 Intel Corporation. */ #define _GNU_SOURCE #include #include #include #include #include #include #include "src/shared/util.h" #include "src/shared/bap-debug.h" static const struct util_bit_debugger pac_freq_table[] = { UTIL_BIT_DEBUG(0, "8 Khz (0x0001)"), UTIL_BIT_DEBUG(1, "11.25 Khz (0x0002)"), UTIL_BIT_DEBUG(2, "16 Khz (0x0004)"), UTIL_BIT_DEBUG(3, "22.05 Khz (0x0008)"), UTIL_BIT_DEBUG(4, "24 Khz (0x0010)"), UTIL_BIT_DEBUG(5, "32 Khz (0x0020)"), UTIL_BIT_DEBUG(6, "44.1 Khz (0x0040)"), UTIL_BIT_DEBUG(7, "48 Khz (0x0080)"), UTIL_BIT_DEBUG(8, "88.2 Khz (0x0100)"), UTIL_BIT_DEBUG(9, "96 Khz (0x0200)"), UTIL_BIT_DEBUG(10, "176.4 Khz (0x0400)"), UTIL_BIT_DEBUG(11, "192 Khz (0x0800)"), UTIL_BIT_DEBUG(12, "384 Khz (0x1000)"), UTIL_BIT_DEBUG(13, "RFU (0x2000)"), UTIL_BIT_DEBUG(14, "RFU (0x4000)"), UTIL_BIT_DEBUG(15, "RFU (0x8000)"), { } }; static void pac_debug_freq(const uint8_t *data, uint8_t len, util_debug_func_t func, void *user_data) { struct iovec frame = { (void *)data, len }; uint16_t value; uint16_t mask; if (!util_iov_pull_le16(&frame, &value)) { util_debug(func, user_data, "value: invalid size"); goto done; } util_debug(func, user_data, "Sampling Frequencies: 0x%4.4x", value); mask = util_debug_bit("Sampling Frequency: ", value, pac_freq_table, func, user_data); if (mask) util_debug(func, user_data, "Unknown fields (0x%4.4x)", mask); done: if (frame.iov_len) util_hexdump(' ', frame.iov_base, frame.iov_len, func, user_data); } static const struct util_bit_debugger pac_duration_table[] = { UTIL_BIT_DEBUG(0, "7.5 ms (0x01)"), UTIL_BIT_DEBUG(1, "10 ms (0x02)"), UTIL_BIT_DEBUG(2, "RFU (0x04)"), UTIL_BIT_DEBUG(3, "RFU (0x08)"), UTIL_BIT_DEBUG(4, "7.5 ms preferred (0x10)"), UTIL_BIT_DEBUG(5, "10 ms preferred (0x20)"), UTIL_BIT_DEBUG(6, "RFU (0x40)"), UTIL_BIT_DEBUG(7, "RFU (0x80)"), { } }; static void pac_debug_duration(const uint8_t *data, uint8_t len, util_debug_func_t func, void *user_data) { struct iovec frame = { (void *)data, len }; uint8_t value; uint8_t mask; if (!util_iov_pull_u8(&frame, &value)) { util_debug(func, user_data, "value: invalid size"); goto done; } util_debug(func, user_data, "Frame Duration: 0x%2.2x", value); mask = util_debug_bit("Frame Duration: ", value, pac_duration_table, func, user_data); if (mask) util_debug(func, user_data, "Unknown fields (0x%2.2x)", mask); done: if (frame.iov_len) util_hexdump(' ', frame.iov_base, frame.iov_len, func, user_data); } static const struct util_bit_debugger pac_channel_table[] = { UTIL_BIT_DEBUG(0, "1 channel (0x01)"), UTIL_BIT_DEBUG(1, "2 channel (0x02)"), UTIL_BIT_DEBUG(2, "3 channel (0x04)"), UTIL_BIT_DEBUG(3, "4 channel (0x08)"), UTIL_BIT_DEBUG(4, "5 channel (0x10)"), UTIL_BIT_DEBUG(5, "6 channel (0x20)"), UTIL_BIT_DEBUG(6, "7 channel (0x40)"), UTIL_BIT_DEBUG(7, "8 channel (0x80)"), { } }; static void pac_debug_channels(const uint8_t *data, uint8_t len, util_debug_func_t func, void *user_data) { struct iovec frame = { (void *)data, len }; uint8_t value; uint8_t mask; if (!util_iov_pull_u8(&frame, &value)) { util_debug(func, user_data, "value: invalid size"); goto done; } util_debug(func, user_data, "Audio Channel Count: 0x%2.2x", value); mask = util_debug_bit("Audio Channel Count: ", value, pac_channel_table, func, user_data); if (mask) util_debug(func, user_data, "Unknown fields (0x%2.2x)", mask); done: if (frame.iov_len) util_hexdump(' ', frame.iov_base, frame.iov_len, func, user_data); } static void pac_debug_frame_length(const uint8_t *data, uint8_t len, util_debug_func_t func, void *user_data) { struct iovec frame = { (void *)data, len }; uint16_t min, max; if (!util_iov_pull_le16(&frame, &min)) { util_debug(func, user_data, "min: invalid size"); goto done; } if (!util_iov_pull_le16(&frame, &max)) { util_debug(func, user_data, "max: invalid size"); goto done; } util_debug(func, user_data, "Frame Length: %u (0x%4.4x) - %u (0x%4.4x)", min, min, max, max); done: if (frame.iov_len) util_hexdump(' ', frame.iov_base, frame.iov_len, func, user_data); } static void pac_debug_sdu(const uint8_t *data, uint8_t len, util_debug_func_t func, void *user_data) { struct iovec frame = { (void *)data, len }; uint8_t value; if (!util_iov_pull_u8(&frame, &value)) { util_debug(func, user_data, "value: invalid size"); goto done; } util_debug(func, user_data, "Max SDU: %u (0x%2.2x)", value, value); done: if (frame.iov_len) util_hexdump(' ', frame.iov_base, frame.iov_len, func, user_data); } static const struct util_ltv_debugger pac_cap_table[] = { UTIL_LTV_DEBUG(0x01, pac_debug_freq), UTIL_LTV_DEBUG(0x02, pac_debug_duration), UTIL_LTV_DEBUG(0x03, pac_debug_channels), UTIL_LTV_DEBUG(0x04, pac_debug_frame_length), UTIL_LTV_DEBUG(0x05, pac_debug_sdu) }; bool bt_bap_debug_caps(void *data, size_t len, util_debug_func_t func, void *user_data) { return util_debug_ltv(data, len, pac_cap_table, ARRAY_SIZE(pac_cap_table), func, user_data); } static void ase_debug_freq(const uint8_t *data, uint8_t len, util_debug_func_t func, void *user_data) { struct iovec frame = { (void *)data, len }; uint8_t value; if (!util_iov_pull_u8(&frame, &value)) { util_debug(func, user_data, "value: invalid size"); goto done; } switch (value) { case 0x01: util_debug(func, user_data, "Sampling Frequency: 8 Khz (0x01)"); break; case 0x02: util_debug(func, user_data, "Sampling Frequency: 11.25 Khz (0x02)"); break; case 0x03: util_debug(func, user_data, "Sampling Frequency: 16 Khz (0x03)"); break; case 0x04: util_debug(func, user_data, "Sampling Frequency: 22.05 Khz (0x04)"); break; case 0x05: util_debug(func, user_data, "Sampling Frequency: 24 Khz (0x05)"); break; case 0x06: util_debug(func, user_data, "Sampling Frequency: 32 Khz (0x06)"); break; case 0x07: util_debug(func, user_data, "Sampling Frequency: 44.1 Khz (0x07)"); break; case 0x08: util_debug(func, user_data, "Sampling Frequency: 48 Khz (0x08)"); break; case 0x09: util_debug(func, user_data, "Sampling Frequency: 88.2 Khz (0x09)"); break; case 0x0a: util_debug(func, user_data, "Sampling Frequency: 96 Khz (0x0a)"); break; case 0x0b: util_debug(func, user_data, "Sampling Frequency: 176.4 Khz (0x0b)"); break; case 0x0c: util_debug(func, user_data, "Sampling Frequency: 192 Khz (0x0c)"); break; case 0x0d: util_debug(func, user_data, "Sampling Frequency: 384 Khz (0x0d)"); break; default: util_debug(func, user_data, "Sampling Frequency: RFU (0x%2.2x)", value); break; } done: if (frame.iov_len) util_hexdump(' ', frame.iov_base, frame.iov_len, func, user_data); } static void ase_debug_duration(const uint8_t *data, uint8_t len, util_debug_func_t func, void *user_data) { struct iovec frame = { (void *)data, len }; uint8_t value; if (!util_iov_pull_u8(&frame, &value)) { util_debug(func, user_data, "\tvalue: invalid size\n"); goto done; } switch (value) { case 0x00: util_debug(func, user_data, "Frame Duration: 7.5 ms (0x00)"); break; case 0x01: util_debug(func, user_data, "Frame Duration: 10 ms (0x01)"); break; default: util_debug(func, user_data, "Frame Duration: RFU (0x%2.2x)", value); break; } done: if (frame.iov_len) util_hexdump(' ', frame.iov_base, frame.iov_len, func, user_data); } static const struct util_bit_debugger channel_location_table[] = { UTIL_BIT_DEBUG(0, "Front Left (0x00000001)"), UTIL_BIT_DEBUG(1, "Front Right (0x00000002)"), UTIL_BIT_DEBUG(2, "Front Center (0x00000004)"), UTIL_BIT_DEBUG(3, "Low Frequency Effects 1 (0x00000008)"), UTIL_BIT_DEBUG(4, "Back Left (0x00000010)"), UTIL_BIT_DEBUG(5, "Back Right (0x00000020)"), UTIL_BIT_DEBUG(6, "Front Left of Center (0x00000040)"), UTIL_BIT_DEBUG(7, "Front Right of Center (0x00000080)"), UTIL_BIT_DEBUG(8, "Back Center (0x00000100)"), UTIL_BIT_DEBUG(9, "Low Frequency Effects 2 (0x00000200)"), UTIL_BIT_DEBUG(10, "Side Left (0x00000400)"), UTIL_BIT_DEBUG(11, "Side Right (0x00000800)"), UTIL_BIT_DEBUG(12, "Top Front Left (0x00001000)"), UTIL_BIT_DEBUG(13, "Top Front Right (0x00002000)"), UTIL_BIT_DEBUG(14, "Top Front Center (0x00004000)"), UTIL_BIT_DEBUG(15, "Top Center (0x00008000)"), UTIL_BIT_DEBUG(16, "Top Back Left (0x00010000)"), UTIL_BIT_DEBUG(17, "Top Back Right (0x00020000)"), UTIL_BIT_DEBUG(18, "Top Side Left (0x00040000)"), UTIL_BIT_DEBUG(19, "Top Side Right (0x00080000)"), UTIL_BIT_DEBUG(20, "Top Back Center (0x00100000)"), UTIL_BIT_DEBUG(21, "Bottom Front Center (0x00200000)"), UTIL_BIT_DEBUG(22, "Bottom Front Left (0x00400000)"), UTIL_BIT_DEBUG(23, "Bottom Front Right (0x00800000)"), UTIL_BIT_DEBUG(24, "Front Left Wide (0x01000000)"), UTIL_BIT_DEBUG(25, "Front Right Wide (0x02000000)"), UTIL_BIT_DEBUG(26, "Left Surround (0x04000000)"), UTIL_BIT_DEBUG(27, "Right Surround (0x08000000)"), UTIL_BIT_DEBUG(28, "RFU (0x10000000)"), UTIL_BIT_DEBUG(29, "RFU (0x20000000)"), UTIL_BIT_DEBUG(30, "RFU (0x40000000)"), UTIL_BIT_DEBUG(31, "RFU (0x80000000)"), { } }; static void debug_location(const struct iovec *frame, util_debug_func_t func, void *user_data) { uint32_t value; uint32_t mask; if (!util_iov_pull_le32((void *)frame, &value)) { util_debug(func, user_data, "value: invalid size"); goto done; } util_debug(func, user_data, "Location: 0x%8.8x", value); mask = util_debug_bit("Location: ", value, channel_location_table, func, user_data); if (mask) util_debug(func, user_data, "Unknown fields (0x%8.8x)", mask); done: if (frame->iov_len) util_hexdump(' ', frame->iov_base, frame->iov_len, func, user_data); } static void ase_debug_location(const uint8_t *data, uint8_t len, util_debug_func_t func, void *user_data) { struct iovec frame = { (void *)data, len }; debug_location(&frame, func, user_data); } static void ase_debug_frame_length(const uint8_t *data, uint8_t len, util_debug_func_t func, void *user_data) { struct iovec frame = { (void *)data, len }; uint16_t value; if (!util_iov_pull_le16(&frame, &value)) { util_debug(func, user_data, "\tvalue: invalid size\n"); goto done; } util_debug(func, user_data, "Frame Length: %u (0x%4.4x)", value, value); done: if (frame.iov_len) util_hexdump(' ', frame.iov_base, frame.iov_len, func, user_data); } static void ase_debug_blocks(const uint8_t *data, uint8_t len, util_debug_func_t func, void *user_data) { struct iovec frame = { (void *)data, len }; uint8_t value; if (!util_iov_pull_u8(&frame, &value)) { util_debug(func, user_data, "value: invalid size"); goto done; } util_debug(func, user_data, "Frame Blocks per SDU: %u (0x%2.2x)", value, value); done: if (frame.iov_len) util_hexdump(' ', frame.iov_base, frame.iov_len, func, user_data); } static const struct util_ltv_debugger ase_cc_table[] = { UTIL_LTV_DEBUG(0x01, ase_debug_freq), UTIL_LTV_DEBUG(0x02, ase_debug_duration), UTIL_LTV_DEBUG(0x03, ase_debug_location), UTIL_LTV_DEBUG(0x04, ase_debug_frame_length), UTIL_LTV_DEBUG(0x05, ase_debug_blocks) }; bool bt_bap_debug_config(void *data, size_t len, util_debug_func_t func, void *user_data) { return util_debug_ltv(data, len, ase_cc_table, ARRAY_SIZE(ase_cc_table), func, user_data); } static const struct util_bit_debugger pac_context_table[] = { UTIL_BIT_DEBUG(0, "\tUnspecified (0x0001)"), UTIL_BIT_DEBUG(1, "\tConversational (0x0002)"), UTIL_BIT_DEBUG(2, "\tMedia (0x0004)"), UTIL_BIT_DEBUG(3, "\tGame (0x0008)"), UTIL_BIT_DEBUG(4, "\tInstructional (0x0010)"), UTIL_BIT_DEBUG(5, "\tVoice Assistants (0x0020)"), UTIL_BIT_DEBUG(6, "\tLive (0x0040)"), UTIL_BIT_DEBUG(7, "\tSound Effects (0x0080)"), UTIL_BIT_DEBUG(8, "\tNotifications (0x0100)"), UTIL_BIT_DEBUG(9, "\tRingtone (0x0200)"), UTIL_BIT_DEBUG(10, "\tAlerts (0x0400)"), UTIL_BIT_DEBUG(11, "\tEmergency alarm (0x0800)"), UTIL_BIT_DEBUG(12, "\tRFU (0x1000)"), UTIL_BIT_DEBUG(13, "\tRFU (0x2000)"), UTIL_BIT_DEBUG(14, "\tRFU (0x4000)"), UTIL_BIT_DEBUG(15, "\tRFU (0x8000)"), { } }; static void debug_context(const struct iovec *frame, const char *label, util_debug_func_t func, void *user_data) { uint16_t value; uint16_t mask; if (!util_iov_pull_le16((void *)frame, &value)) { util_debug(func, user_data, "value: invalid size"); goto done; } util_debug(func, user_data, "%s: 0x%4.4x", label, value); mask = util_debug_bit(label, value, pac_context_table, func, user_data); if (mask) util_debug(func, user_data, "Unknown fields (0x%4.4x)", mask); done: if (frame->iov_len) util_hexdump(' ', frame->iov_base, frame->iov_len, func, user_data); } static void ase_debug_preferred_context(const uint8_t *data, uint8_t len, util_debug_func_t func, void *user_data) { struct iovec frame = { (void *)data, len }; debug_context(&frame, "Preferred Context", func, user_data); } static void ase_debug_context(const uint8_t *data, uint8_t len, util_debug_func_t func, void *user_data) { struct iovec frame = { (void *)data, len }; debug_context(&frame, "Context", func, user_data); } static void ase_debug_program_info(const uint8_t *data, uint8_t len, util_debug_func_t func, void *user_data) { struct iovec frame = { (void *)data, len }; const char *str; str = util_iov_pull_mem(&frame, len); if (!str) { util_debug(func, user_data, "value: invalid size"); goto done; } util_debug(func, user_data, "Program Info: %*s", len, str); done: if (frame.iov_len) util_hexdump(' ', frame.iov_base, frame.iov_len, func, user_data); } static void ase_debug_language(const uint8_t *data, uint8_t len, util_debug_func_t func, void *user_data) { struct iovec frame = { (void *)data, len }; uint32_t value; if (!util_iov_pull_le24(&frame, &value)) { util_debug(func, user_data, "value: invalid size"); goto done; } util_debug(func, user_data, "Language: 0x%6.6x\n", value); done: if (frame.iov_len) util_hexdump(' ', frame.iov_base, frame.iov_len, func, user_data); } static const struct util_ltv_debugger ase_metadata_table[] = { UTIL_LTV_DEBUG(0x01, ase_debug_preferred_context), UTIL_LTV_DEBUG(0x02, ase_debug_context), UTIL_LTV_DEBUG(0x03, ase_debug_program_info), UTIL_LTV_DEBUG(0x04, ase_debug_language) }; bool bt_bap_debug_metadata(void *data, size_t len, util_debug_func_t func, void *user_data) { return util_debug_ltv(data, len, ase_metadata_table, ARRAY_SIZE(ase_metadata_table), func, user_data); } bluez-5.87/src/shared/PaxHeaders/battery.c0000644000000000000000000000005015110075565015533 xustar0020 atime=1783092969 20 ctime=1783094100 bluez-5.87/src/shared/battery.c0000644000000000000000000000440615110075565015220 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2025 Open Mobile Platform LLC * * */ #include #include #include "src/shared/battery.h" #include "src/shared/queue.h" #include "src/shared/util.h" struct bt_battery { struct queue *last_charges; /* last charges received */ uint8_t avg_charge; /* average battery charge */ bool is_fluctuating; /* true, if the battery sensor fluctuates */ }; struct bt_battery *bt_battery_new(void) { struct bt_battery *battery; battery = new0(struct bt_battery, 1); battery->last_charges = queue_new(); battery->avg_charge = 0; battery->is_fluctuating = false; return battery; } void bt_battery_free(struct bt_battery *battery) { if (battery->last_charges) queue_destroy(battery->last_charges, NULL); } static void bt_battery_check_fluctuations(struct bt_battery *battery) { const struct queue_entry *entry; uint8_t spikes = 0; int8_t step; int8_t direction = 0; int8_t prev_direction; uintptr_t prev_charge; uintptr_t next_charge = 0; uint16_t sum_charge = 0; for (entry = queue_get_entries(battery->last_charges); entry->next; entry = entry->next) { prev_direction = direction; prev_charge = PTR_TO_UINT(entry->data); next_charge = PTR_TO_UINT(entry->next->data); step = next_charge - prev_charge; sum_charge += prev_charge; /* * The battery charge fluctuates too much, * which may indicate a battery problem, so * the actual value should be displayed. */ if (abs(step) >= MAX_CHARGE_STEP) { battery->is_fluctuating = false; return; } if (step > 0) direction = 1; else if (step < 0) direction = -1; if (direction != prev_direction && prev_direction) spikes++; } sum_charge += next_charge; battery->avg_charge = sum_charge / LAST_CHARGES_SIZE; battery->is_fluctuating = (spikes > 1) ? true : false; } uint8_t bt_battery_charge(struct bt_battery *battery, uint8_t percentage) { queue_push_tail(battery->last_charges, UINT_TO_PTR(percentage)); if (queue_length(battery->last_charges) == LAST_CHARGES_SIZE) { bt_battery_check_fluctuations(battery); queue_pop_head(battery->last_charges); } return (battery->is_fluctuating) ? battery->avg_charge : percentage; } bluez-5.87/src/shared/PaxHeaders/hfp.c0000644000000000000000000000005015142177620014636 xustar0020 atime=1783092894 20 ctime=1783094099 bluez-5.87/src/shared/hfp.c0000644000000000000000000017067715142177620014341 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "src/shared/util.h" #include "src/shared/ringbuf.h" #include "src/shared/queue.h" #include "src/shared/io.h" #include "src/shared/hfp.h" #define DBG(_hfp, fmt, arg...) \ hfp_debug(_hfp->debug_callback, _hfp->debug_data, "%s:%s() " fmt, \ __FILE__, __func__, ## arg) #define HFP_HF_FEATURES ( \ HFP_HF_FEAT_ECNR | \ HFP_HF_FEAT_3WAY | \ HFP_HF_FEAT_CLIP | \ HFP_HF_FEAT_ENHANCED_CALL_STATUS | \ HFP_HF_FEAT_ESCO_S4_T2 \ ) struct hfp_gw { int ref_count; int fd; bool close_on_unref; struct io *io; struct ringbuf *read_buf; struct ringbuf *write_buf; struct queue *cmd_handlers; bool writer_active; bool result_pending; hfp_command_func_t command_callback; hfp_destroy_func_t command_destroy; void *command_data; hfp_debug_func_t debug_callback; hfp_destroy_func_t debug_destroy; void *debug_data; hfp_disconnect_func_t disconnect_callback; hfp_destroy_func_t disconnect_destroy; void *disconnect_data; bool in_disconnect; bool destroyed; }; typedef void (*ciev_func_t)(uint8_t val, void *user_data); struct indicator { uint8_t index; uint32_t min; uint32_t max; uint32_t val; ciev_func_t cb; }; struct hfp_hf { int ref_count; int fd; bool close_on_unref; struct io *io; struct ringbuf *read_buf; struct ringbuf *write_buf; bool writer_active; struct queue *cmd_queue; struct queue *event_handlers; hfp_debug_func_t debug_callback; hfp_destroy_func_t debug_destroy; void *debug_data; hfp_disconnect_func_t disconnect_callback; hfp_destroy_func_t disconnect_destroy; void *disconnect_data; bool in_disconnect; bool destroyed; struct hfp_hf_callbacks *callbacks; void *callbacks_data; uint32_t features; struct indicator ag_ind[HFP_INDICATOR_LAST]; bool service; uint8_t signal; bool roaming; uint8_t battchg; uint8_t chlds; bool session; bool clcc_in_progress; struct queue *calls; struct queue *updated_calls; char *dialing_number; }; struct cmd_handler { char *prefix; void *user_data; hfp_destroy_func_t destroy; hfp_result_func_t callback; }; struct hfp_context { const char *data; unsigned int offset; }; struct cmd_response { hfp_response_func_t resp_cb; struct hfp_context *response; char *resp_data; void *user_data; }; struct event_handler { char *prefix; void *user_data; hfp_destroy_func_t destroy; hfp_hf_result_func_t callback; }; struct hf_call { uint id; enum hfp_call_status status; char *line_id; uint type; bool mpty; struct hfp_hf *hfp; }; static void hfp_debug(hfp_debug_func_t debug_func, void *debug_data, const char *format, ...) { va_list ap; if (!debug_func || !format) return; va_start(ap, format); util_debug_va(debug_func, debug_data, format, ap); va_end(ap); } static void destroy_cmd_handler(void *data) { struct cmd_handler *handler = data; if (handler->destroy) handler->destroy(handler->user_data); free(handler->prefix); free(handler); } static bool match_handler_prefix(const void *a, const void *b) { const struct cmd_handler *handler = a; const char *prefix = b; if (strcmp(handler->prefix, prefix) != 0) return false; return true; } static void write_watch_destroy(void *user_data) { struct hfp_gw *hfp = user_data; hfp->writer_active = false; } static bool can_write_data(struct io *io, void *user_data) { struct hfp_gw *hfp = user_data; ssize_t bytes_written; bytes_written = ringbuf_write(hfp->write_buf, hfp->fd); if (bytes_written < 0) return false; if (ringbuf_len(hfp->write_buf) > 0) return true; return false; } static void wakeup_writer(struct hfp_gw *hfp) { if (hfp->writer_active) return; if (!ringbuf_len(hfp->write_buf)) return; if (!io_set_write_handler(hfp->io, can_write_data, hfp, write_watch_destroy)) return; hfp->writer_active = true; } static void skip_whitespace(struct hfp_context *context) { while (context->data[context->offset] == ' ') context->offset++; } static void handle_unknown_at_command(struct hfp_gw *hfp, const char *data) { if (hfp->command_callback) { hfp->result_pending = true; hfp->command_callback(data, hfp->command_data); } else { hfp_gw_send_result(hfp, HFP_RESULT_ERROR); } } static bool handle_at_command(struct hfp_gw *hfp, const char *data) { struct cmd_handler *handler; const char *separators = ";?=\0"; struct hfp_context context; enum hfp_gw_cmd_type type; char lookup_prefix[18]; uint8_t pref_len = 0; const char *prefix; int i; context.offset = 0; context.data = data; skip_whitespace(&context); if (strlen(data + context.offset) < 3) return false; if (strncmp(data + context.offset, "AT", 2)) if (strncmp(data + context.offset, "at", 2)) return false; context.offset += 2; prefix = data + context.offset; if (isalpha(prefix[0])) { lookup_prefix[pref_len++] = toupper(prefix[0]); } else { pref_len = strcspn(prefix, separators); if (pref_len > 17 || pref_len < 2) return false; for (i = 0; i < pref_len; i++) lookup_prefix[i] = toupper(prefix[i]); } lookup_prefix[pref_len] = '\0'; context.offset += pref_len; if (lookup_prefix[0] == 'D') { type = HFP_GW_CMD_TYPE_SET; goto done; } if (data[context.offset] == '=') { context.offset++; if (data[context.offset] == '?') { context.offset++; type = HFP_GW_CMD_TYPE_TEST; } else { type = HFP_GW_CMD_TYPE_SET; } goto done; } if (data[context.offset] == '?') { context.offset++; type = HFP_GW_CMD_TYPE_READ; goto done; } type = HFP_GW_CMD_TYPE_COMMAND; done: handler = queue_find(hfp->cmd_handlers, match_handler_prefix, lookup_prefix); if (!handler) { handle_unknown_at_command(hfp, data); return true; } hfp->result_pending = true; handler->callback(&context, type, handler->user_data); return true; } static void next_field(struct hfp_context *context) { if (context->data[context->offset] == ',') context->offset++; } bool hfp_context_get_number_default(struct hfp_context *context, unsigned int *val, unsigned int default_val) { skip_whitespace(context); if (context->data[context->offset] == ',') { if (val) *val = default_val; context->offset++; return true; } return hfp_context_get_number(context, val); } bool hfp_context_get_number(struct hfp_context *context, unsigned int *val) { unsigned int i; int tmp = 0; skip_whitespace(context); i = context->offset; while (context->data[i] >= '0' && context->data[i] <= '9') tmp = tmp * 10 + context->data[i++] - '0'; if (i == context->offset) return false; if (val) *val = tmp; context->offset = i; skip_whitespace(context); next_field(context); return true; } bool hfp_context_open_container(struct hfp_context *context) { skip_whitespace(context); /* The list shall be preceded by a left parenthesis "(") */ if (context->data[context->offset] != '(') return false; context->offset++; return true; } bool hfp_context_close_container(struct hfp_context *context) { skip_whitespace(context); /* The list shall be followed by a right parenthesis (")" V250 5.7.3.1*/ if (context->data[context->offset] != ')') return false; context->offset++; next_field(context); return true; } bool hfp_context_is_container_close(struct hfp_context *context) { return context->data[context->offset] == ')'; } bool hfp_context_get_string(struct hfp_context *context, char *buf, uint8_t len) { int i = 0; const char *data = context->data; unsigned int offset; skip_whitespace(context); if (data[context->offset] != '"') return false; offset = context->offset; offset++; while (data[offset] != '\0' && data[offset] != '"') { if (i == len) return false; buf[i++] = data[offset]; offset++; } if (i == len) return false; buf[i] = '\0'; if (data[offset] == '"') offset++; else return false; context->offset = offset; skip_whitespace(context); next_field(context); return true; } bool hfp_context_get_unquoted_string(struct hfp_context *context, char *buf, uint8_t len) { const char *data = context->data; unsigned int offset; int i = 0; char c; skip_whitespace(context); c = data[context->offset]; if (c == '"' || c == ')' || c == '(') return false; offset = context->offset; while (data[offset] != '\0' && data[offset] != ',' && data[offset] != ')') { if (i == len) return false; buf[i++] = data[offset]; offset++; } if (i == len) return false; buf[i] = '\0'; context->offset = offset; next_field(context); return true; } bool hfp_context_has_next(struct hfp_context *context) { return context->data[context->offset] != '\0'; } void hfp_context_skip_field(struct hfp_context *context) { const char *data = context->data; unsigned int offset = context->offset; while (data[offset] != '\0' && data[offset] != ',') offset++; context->offset = offset; next_field(context); } bool hfp_context_get_range(struct hfp_context *context, uint32_t *min, uint32_t *max) { uint32_t l, h; uint32_t start; start = context->offset; if (!hfp_context_get_number(context, &l)) goto failed; if (context->data[context->offset] != '-') goto failed; context->offset++; if (!hfp_context_get_number(context, &h)) goto failed; *min = l; *max = h; next_field(context); return true; failed: context->offset = start; return false; } static void process_input(struct hfp_gw *hfp) { char *str, *ptr; size_t len, count; bool free_ptr = false; bool read_again; do { str = ringbuf_peek(hfp->read_buf, 0, &len); if (!str) return; ptr = memchr(str, '\r', len); if (!ptr) { char *str2; size_t len2; /* * If there is no more data in ringbuffer, * it's just an incomplete command. */ if (len == ringbuf_len(hfp->read_buf)) return; str2 = ringbuf_peek(hfp->read_buf, len, &len2); if (!str2) return; ptr = memchr(str2, '\r', len2); if (!ptr) return; *ptr = '\0'; count = len2 + len; ptr = malloc(count); if (!ptr) return; memcpy(ptr, str, len); memcpy(ptr + len, str2, len2); free_ptr = true; str = ptr; } else { count = ptr - str; *ptr = '\0'; } if (!handle_at_command(hfp, str)) /* * Command is not handled that means that was some * trash. Let's skip that and keep reading from ring * buffer. */ read_again = true; else /* * Command has been handled. If we are waiting for a * result from upper layer, we can stop reading. If we * already reply i.e. ERROR on unknown command, then we * can keep reading ring buffer. Actually ring buffer * should be empty but lets just look there. */ read_again = !hfp->result_pending; ringbuf_drain(hfp->read_buf, count + 1); if (free_ptr) free(ptr); } while (read_again); } static void read_watch_destroy(void *user_data) { } static bool can_read_data(struct io *io, void *user_data) { struct hfp_gw *hfp = user_data; ssize_t bytes_read; bytes_read = ringbuf_read(hfp->read_buf, hfp->fd); if (bytes_read < 0) return false; if (hfp->result_pending) return true; process_input(hfp); return true; } struct hfp_gw *hfp_gw_new(int fd) { struct hfp_gw *hfp; if (fd < 0) return NULL; hfp = new0(struct hfp_gw, 1); hfp->fd = fd; hfp->close_on_unref = false; hfp->read_buf = ringbuf_new(4096); if (!hfp->read_buf) { free(hfp); return NULL; } hfp->write_buf = ringbuf_new(4096); if (!hfp->write_buf) { ringbuf_free(hfp->read_buf); free(hfp); return NULL; } hfp->io = io_new(fd); if (!hfp->io) { ringbuf_free(hfp->write_buf); ringbuf_free(hfp->read_buf); free(hfp); return NULL; } hfp->cmd_handlers = queue_new(); if (!io_set_read_handler(hfp->io, can_read_data, hfp, read_watch_destroy)) { queue_destroy(hfp->cmd_handlers, destroy_cmd_handler); io_destroy(hfp->io); ringbuf_free(hfp->write_buf); ringbuf_free(hfp->read_buf); free(hfp); return NULL; } hfp->writer_active = false; hfp->result_pending = false; return hfp_gw_ref(hfp); } struct hfp_gw *hfp_gw_ref(struct hfp_gw *hfp) { if (!hfp) return NULL; __sync_fetch_and_add(&hfp->ref_count, 1); return hfp; } void hfp_gw_unref(struct hfp_gw *hfp) { if (!hfp) return; if (__sync_sub_and_fetch(&hfp->ref_count, 1)) return; hfp_gw_set_command_handler(hfp, NULL, NULL, NULL); io_set_write_handler(hfp->io, NULL, NULL, NULL); io_set_read_handler(hfp->io, NULL, NULL, NULL); io_set_disconnect_handler(hfp->io, NULL, NULL, NULL); io_destroy(hfp->io); hfp->io = NULL; if (hfp->close_on_unref) close(hfp->fd); hfp_gw_set_debug(hfp, NULL, NULL, NULL); ringbuf_free(hfp->read_buf); hfp->read_buf = NULL; ringbuf_free(hfp->write_buf); hfp->write_buf = NULL; queue_destroy(hfp->cmd_handlers, destroy_cmd_handler); hfp->cmd_handlers = NULL; if (!hfp->in_disconnect) { free(hfp); return; } hfp->destroyed = true; } static void read_tracing(const void *buf, size_t count, void *user_data) { struct hfp_gw *hfp = user_data; util_hexdump('>', buf, count, hfp->debug_callback, hfp->debug_data); } static void write_tracing(const void *buf, size_t count, void *user_data) { struct hfp_gw *hfp = user_data; util_hexdump('<', buf, count, hfp->debug_callback, hfp->debug_data); } bool hfp_gw_set_debug(struct hfp_gw *hfp, hfp_debug_func_t callback, void *user_data, hfp_destroy_func_t destroy) { if (!hfp) return false; if (hfp->debug_destroy) hfp->debug_destroy(hfp->debug_data); hfp->debug_callback = callback; hfp->debug_destroy = destroy; hfp->debug_data = user_data; if (hfp->debug_callback) { ringbuf_set_input_tracing(hfp->read_buf, read_tracing, hfp); ringbuf_set_input_tracing(hfp->write_buf, write_tracing, hfp); } else { ringbuf_set_input_tracing(hfp->read_buf, NULL, NULL); ringbuf_set_input_tracing(hfp->write_buf, NULL, NULL); } return true; } bool hfp_gw_set_close_on_unref(struct hfp_gw *hfp, bool do_close) { if (!hfp) return false; hfp->close_on_unref = do_close; return true; } bool hfp_gw_send_result(struct hfp_gw *hfp, enum hfp_result result) { const char *str; if (!hfp) return false; switch (result) { case HFP_RESULT_OK: str = "OK"; break; case HFP_RESULT_ERROR: str = "ERROR"; break; case HFP_RESULT_RING: case HFP_RESULT_NO_CARRIER: case HFP_RESULT_BUSY: case HFP_RESULT_NO_ANSWER: case HFP_RESULT_DELAYED: case HFP_RESULT_REJECTED: case HFP_RESULT_CME_ERROR: case HFP_RESULT_NO_DIALTONE: case HFP_RESULT_CONNECT: default: return false; } if (ringbuf_printf(hfp->write_buf, "\r\n%s\r\n", str) < 0) return false; wakeup_writer(hfp); /* * There might be already something to read in the ring buffer. * If so, let's read it. */ if (hfp->result_pending) { hfp->result_pending = false; process_input(hfp); } return true; } bool hfp_gw_send_error(struct hfp_gw *hfp, enum hfp_error error) { if (!hfp) return false; if (ringbuf_printf(hfp->write_buf, "\r\n+CME ERROR: %u\r\n", error) < 0) return false; wakeup_writer(hfp); /* * There might be already something to read in the ring buffer. * If so, let's read it. */ if (hfp->result_pending) { hfp->result_pending = false; process_input(hfp); } return true; } bool hfp_gw_send_info(struct hfp_gw *hfp, const char *format, ...) { va_list ap; char *fmt; int len; if (!hfp || !format) return false; if (asprintf(&fmt, "\r\n%s\r\n", format) < 0) return false; va_start(ap, format); len = ringbuf_vprintf(hfp->write_buf, fmt, ap); va_end(ap); free(fmt); if (len < 0) return false; if (hfp->result_pending) return true; wakeup_writer(hfp); return true; } bool hfp_gw_set_command_handler(struct hfp_gw *hfp, hfp_command_func_t callback, void *user_data, hfp_destroy_func_t destroy) { if (!hfp) return false; if (hfp->command_destroy) hfp->command_destroy(hfp->command_data); hfp->command_callback = callback; hfp->command_destroy = destroy; hfp->command_data = user_data; return true; } bool hfp_gw_register(struct hfp_gw *hfp, hfp_result_func_t callback, const char *prefix, void *user_data, hfp_destroy_func_t destroy) { struct cmd_handler *handler; handler = new0(struct cmd_handler, 1); handler->callback = callback; handler->user_data = user_data; handler->prefix = strdup(prefix); if (!handler->prefix) { free(handler); return false; } if (queue_find(hfp->cmd_handlers, match_handler_prefix, handler->prefix)) { destroy_cmd_handler(handler); return false; } handler->destroy = destroy; return queue_push_tail(hfp->cmd_handlers, handler); } bool hfp_gw_unregister(struct hfp_gw *hfp, const char *prefix) { struct cmd_handler *handler; char *lookup_prefix; lookup_prefix = strdup(prefix); if (!lookup_prefix) return false; handler = queue_remove_if(hfp->cmd_handlers, match_handler_prefix, lookup_prefix); free(lookup_prefix); if (!handler) return false; destroy_cmd_handler(handler); return true; } static void disconnect_watch_destroy(void *user_data) { struct hfp_gw *hfp = user_data; if (hfp->disconnect_destroy) hfp->disconnect_destroy(hfp->disconnect_data); if (hfp->destroyed) free(hfp); } static bool io_disconnected(struct io *io, void *user_data) { struct hfp_gw *hfp = user_data; hfp->in_disconnect = true; if (hfp->disconnect_callback) hfp->disconnect_callback(hfp->disconnect_data); hfp->in_disconnect = false; return false; } bool hfp_gw_set_disconnect_handler(struct hfp_gw *hfp, hfp_disconnect_func_t callback, void *user_data, hfp_destroy_func_t destroy) { if (!hfp) return false; if (hfp->disconnect_destroy) hfp->disconnect_destroy(hfp->disconnect_data); if (!io_set_disconnect_handler(hfp->io, io_disconnected, hfp, disconnect_watch_destroy)) { hfp->disconnect_callback = NULL; hfp->disconnect_destroy = NULL; hfp->disconnect_data = NULL; return false; } hfp->disconnect_callback = callback; hfp->disconnect_destroy = destroy; hfp->disconnect_data = user_data; return true; } bool hfp_gw_disconnect(struct hfp_gw *hfp) { if (!hfp) return false; return io_shutdown(hfp->io); } static bool match_handler_event_prefix(const void *a, const void *b) { const struct event_handler *handler = a; const char *prefix = b; if (strcmp(handler->prefix, prefix) != 0) return false; return true; } static void destroy_event_handler(void *data) { struct event_handler *handler = data; if (handler->destroy) handler->destroy(handler->user_data); free(handler->prefix); free(handler); } static bool hf_can_write_data(struct io *io, void *user_data) { struct hfp_hf *hfp = user_data; ssize_t bytes_written; bytes_written = ringbuf_write(hfp->write_buf, hfp->fd); if (bytes_written < 0) return false; if (ringbuf_len(hfp->write_buf) > 0) return true; return false; } static void hf_write_watch_destroy(void *user_data) { struct hfp_hf *hfp = user_data; hfp->writer_active = false; } static void hf_skip_whitespace(struct hfp_context *context) { while (context->data[context->offset] == ' ') context->offset++; } static bool is_response(const char *prefix, enum hfp_result *result, enum hfp_error *cme_err, struct hfp_context *context) { if (strcmp(prefix, "OK") == 0) { *result = HFP_RESULT_OK; /* * Set cme_err to 0 as this is not valid when result is not * CME ERROR */ *cme_err = 0; return true; } if (strcmp(prefix, "ERROR") == 0) { *result = HFP_RESULT_ERROR; *cme_err = 0; return true; } if (strcmp(prefix, "NO CARRIER") == 0) { *result = HFP_RESULT_NO_CARRIER; *cme_err = 0; return true; } if (strcmp(prefix, "NO ANSWER") == 0) { *result = HFP_RESULT_NO_ANSWER; *cme_err = 0; return true; } if (strcmp(prefix, "BUSY") == 0) { *result = HFP_RESULT_BUSY; *cme_err = 0; return true; } if (strcmp(prefix, "DELAYED") == 0) { *result = HFP_RESULT_DELAYED; *cme_err = 0; return true; } if (strcmp(prefix, "BLACKLISTED") == 0) { *result = HFP_RESULT_REJECTED; *cme_err = 0; return true; } if (strcmp(prefix, "+CME ERROR") == 0) { uint32_t val; *result = HFP_RESULT_CME_ERROR; if (hfp_context_get_number(context, &val) && val <= HFP_ERROR_NETWORK_NOT_ALLOWED) *cme_err = val; else *cme_err = HFP_ERROR_AG_FAILURE; return true; } return false; } static void hf_wakeup_writer(struct hfp_hf *hfp) { if (hfp->writer_active) return; if (!ringbuf_len(hfp->write_buf)) return; if (!io_set_write_handler(hfp->io, hf_can_write_data, hfp, hf_write_watch_destroy)) return; hfp->writer_active = true; } static void hf_call_prefix_handler(struct hfp_hf *hfp, const char *data) { struct event_handler *handler; const char *separators = ";:\0"; struct hfp_context context; enum hfp_result result; enum hfp_error cme_err; char lookup_prefix[18] = {}; uint8_t pref_len = 0; const char *prefix; int i; context.offset = 0; context.data = data; hf_skip_whitespace(&context); if (strlen(data + context.offset) < 2) return; prefix = data + context.offset; pref_len = strcspn(prefix, separators); if (pref_len > 17 || pref_len < 2) return; for (i = 0; i < pref_len; i++) lookup_prefix[i] = toupper(prefix[i]); lookup_prefix[pref_len] = '\0'; context.offset += pref_len + 1; if (is_response(lookup_prefix, &result, &cme_err, &context)) { struct cmd_response *cmd; cmd = queue_peek_head(hfp->cmd_queue); if (!cmd) return; cmd->resp_cb(result, cme_err, cmd->user_data); queue_remove(hfp->cmd_queue, cmd); free(cmd); hf_wakeup_writer(hfp); return; } handler = queue_find(hfp->event_handlers, match_handler_event_prefix, lookup_prefix); if (!handler) return; handler->callback(&context, handler->user_data); } static char *find_cr_lf(char *str, size_t len) { char *ptr; size_t count, offset; offset = 0; ptr = memchr(str, '\r', len); while (ptr) { /* * Check if there is more data after '\r'. If so check for * '\n' */ count = ptr - str; if ((count < (len - 1)) && *(ptr + 1) == '\n') return ptr; /* There is only '\r'? Let's try to find next one */ offset += count + 1; if (offset >= len) return NULL; ptr = memchr(str + offset, '\r', len - offset); } return NULL; } static void hf_process_input(struct hfp_hf *hfp) { char *str, *ptr, *str2, *tmp; size_t len, count, offset, len2; bool free_tmp = false; str = ringbuf_peek(hfp->read_buf, 0, &len); if (!str) return; offset = 0; ptr = find_cr_lf(str, len); while (ptr) { count = ptr - (str + offset); if (count == 0) { /* 2 is for */ offset += 2; } else { *ptr = '\0'; hf_call_prefix_handler(hfp, str + offset); offset += count + 2; } ptr = find_cr_lf(str + offset, len - offset); } /* * Just check if there is no wrapped data in ring buffer. * Should not happen too often */ if (len == ringbuf_len(hfp->read_buf)) goto done; str2 = ringbuf_peek(hfp->read_buf, len, &len2); if (!str2) goto done; ptr = find_cr_lf(str2, len2); if (!ptr) { /* Might happen that we wrap between \r and \n */ ptr = memchr(str2, '\n', len2); if (!ptr) goto done; } count = ptr - str2; if (count) { *ptr = '\0'; tmp = malloc(len + count); if (!tmp) goto done; /* "str" here is not a string so we need to use memcpy */ memcpy(tmp, str, len); memcpy(tmp + len, str2, count); free_tmp = true; } else { str[len-1] = '\0'; tmp = str; } hf_call_prefix_handler(hfp, tmp); offset += count; done: ringbuf_drain(hfp->read_buf, offset); if (free_tmp) free(tmp); } static bool hf_can_read_data(struct io *io, void *user_data) { struct hfp_hf *hfp = user_data; ssize_t bytes_read; bytes_read = ringbuf_read(hfp->read_buf, hfp->fd); if (bytes_read < 0) return false; hf_process_input(hfp); return true; } struct hfp_hf *hfp_hf_new(int fd) { struct hfp_hf *hfp; if (fd < 0) return NULL; hfp = new0(struct hfp_hf, 1); hfp->fd = fd; hfp->close_on_unref = false; hfp->read_buf = ringbuf_new(4096); if (!hfp->read_buf) { free(hfp); return NULL; } hfp->write_buf = ringbuf_new(4096); if (!hfp->write_buf) { ringbuf_free(hfp->read_buf); free(hfp); return NULL; } hfp->io = io_new(fd); if (!hfp->io) { ringbuf_free(hfp->write_buf); ringbuf_free(hfp->read_buf); free(hfp); return NULL; } hfp->event_handlers = queue_new(); hfp->cmd_queue = queue_new(); hfp->calls = queue_new(); hfp->updated_calls = queue_new(); hfp->writer_active = false; if (!io_set_read_handler(hfp->io, hf_can_read_data, hfp, read_watch_destroy)) { queue_destroy(hfp->event_handlers, destroy_event_handler); io_destroy(hfp->io); ringbuf_free(hfp->write_buf); ringbuf_free(hfp->read_buf); free(hfp); return NULL; } return hfp_hf_ref(hfp); } struct hfp_hf *hfp_hf_ref(struct hfp_hf *hfp) { if (!hfp) return NULL; __sync_fetch_and_add(&hfp->ref_count, 1); return hfp; } static void remove_call_cb(void *user_data) { struct hf_call *call = user_data; struct hfp_hf *hfp = call->hfp; if (hfp->callbacks && hfp->callbacks->call_removed) hfp->callbacks->call_removed(call->id, hfp->callbacks_data); free(call->line_id); free(call); } void hfp_hf_unref(struct hfp_hf *hfp) { if (!hfp) return; if (__sync_sub_and_fetch(&hfp->ref_count, 1)) return; io_set_write_handler(hfp->io, NULL, NULL, NULL); io_set_read_handler(hfp->io, NULL, NULL, NULL); io_set_disconnect_handler(hfp->io, NULL, NULL, NULL); io_destroy(hfp->io); hfp->io = NULL; if (hfp->close_on_unref) close(hfp->fd); hfp_hf_set_debug(hfp, NULL, NULL, NULL); ringbuf_free(hfp->read_buf); hfp->read_buf = NULL; ringbuf_free(hfp->write_buf); hfp->write_buf = NULL; queue_destroy(hfp->event_handlers, destroy_event_handler); hfp->event_handlers = NULL; queue_destroy(hfp->cmd_queue, free); hfp->cmd_queue = NULL; queue_destroy(hfp->calls, remove_call_cb); hfp->calls = NULL; queue_destroy(hfp->updated_calls, NULL); hfp->updated_calls = NULL; if (hfp->dialing_number) { free(hfp->dialing_number); hfp->dialing_number = NULL; } if (!hfp->in_disconnect) { free(hfp); return; } hfp->destroyed = true; } static void hf_read_tracing(const void *buf, size_t count, void *user_data) { struct hfp_hf *hfp = user_data; util_hexdump('>', buf, count, hfp->debug_callback, hfp->debug_data); } static void hf_write_tracing(const void *buf, size_t count, void *user_data) { struct hfp_hf *hfp = user_data; util_hexdump('<', buf, count, hfp->debug_callback, hfp->debug_data); } bool hfp_hf_set_debug(struct hfp_hf *hfp, hfp_debug_func_t callback, void *user_data, hfp_destroy_func_t destroy) { if (!hfp) return false; if (hfp->debug_destroy) hfp->debug_destroy(hfp->debug_data); hfp->debug_callback = callback; hfp->debug_destroy = destroy; hfp->debug_data = user_data; if (hfp->debug_callback) { ringbuf_set_input_tracing(hfp->read_buf, hf_read_tracing, hfp); ringbuf_set_input_tracing(hfp->write_buf, hf_write_tracing, hfp); } else { ringbuf_set_input_tracing(hfp->read_buf, NULL, NULL); ringbuf_set_input_tracing(hfp->write_buf, NULL, NULL); } return true; } bool hfp_hf_set_close_on_unref(struct hfp_hf *hfp, bool do_close) { if (!hfp) return false; hfp->close_on_unref = do_close; return true; } bool hfp_hf_send_command(struct hfp_hf *hfp, hfp_response_func_t resp_cb, void *user_data, const char *format, ...) { va_list ap; char *fmt; int len; struct cmd_response *cmd; if (!hfp || !format || !resp_cb) return false; if (asprintf(&fmt, "%s\r", format) < 0) return false; cmd = new0(struct cmd_response, 1); va_start(ap, format); len = ringbuf_vprintf(hfp->write_buf, fmt, ap); va_end(ap); free(fmt); if (len < 0) { free(cmd); return false; } cmd->resp_cb = resp_cb; cmd->user_data = user_data; if (!queue_push_tail(hfp->cmd_queue, cmd)) { ringbuf_drain(hfp->write_buf, len); free(cmd); return false; } hf_wakeup_writer(hfp); return true; } bool hfp_hf_register(struct hfp_hf *hfp, hfp_hf_result_func_t callback, const char *prefix, void *user_data, hfp_destroy_func_t destroy) { struct event_handler *handler; if (!callback) return false; handler = new0(struct event_handler, 1); handler->callback = callback; handler->user_data = user_data; handler->prefix = strdup(prefix); if (!handler->prefix) { free(handler); return false; } if (queue_find(hfp->event_handlers, match_handler_event_prefix, handler->prefix)) { destroy_event_handler(handler); return false; } handler->destroy = destroy; return queue_push_tail(hfp->event_handlers, handler); } bool hfp_hf_unregister(struct hfp_hf *hfp, const char *prefix) { struct cmd_handler *handler; /* Cast to void as queue_remove needs that */ handler = queue_remove_if(hfp->event_handlers, match_handler_event_prefix, (void *) prefix); if (!handler) return false; destroy_event_handler(handler); return true; } static void hf_disconnect_watch_destroy(void *user_data) { struct hfp_hf *hfp = user_data; if (hfp->disconnect_destroy) hfp->disconnect_destroy(hfp->disconnect_data); if (hfp->destroyed) free(hfp); } static bool hf_io_disconnected(struct io *io, void *user_data) { struct hfp_hf *hfp = user_data; hfp->in_disconnect = true; if (hfp->disconnect_callback) hfp->disconnect_callback(hfp->disconnect_data); hfp->in_disconnect = false; return false; } bool hfp_hf_set_disconnect_handler(struct hfp_hf *hfp, hfp_disconnect_func_t callback, void *user_data, hfp_destroy_func_t destroy) { if (!hfp) return false; if (hfp->disconnect_destroy) hfp->disconnect_destroy(hfp->disconnect_data); if (!io_set_disconnect_handler(hfp->io, hf_io_disconnected, hfp, hf_disconnect_watch_destroy)) { hfp->disconnect_callback = NULL; hfp->disconnect_destroy = NULL; hfp->disconnect_data = NULL; return false; } hfp->disconnect_callback = callback; hfp->disconnect_destroy = destroy; hfp->disconnect_data = user_data; return true; } bool hfp_hf_disconnect(struct hfp_hf *hfp) { if (!hfp) return false; return io_shutdown(hfp->io); } static bool call_id_match(const void *data, const void *match_data) { const struct hf_call *call = data; uint id = PTR_TO_UINT(match_data); return (call->id == id); } static uint next_call_index(struct hfp_hf *hfp) { for (uint i = 1; i < UINT_MAX; i++) { if (!queue_find(hfp->calls, call_id_match, UINT_TO_PTR(i))) return i; } return 0; } static struct hf_call *call_new(struct hfp_hf *hfp, unsigned int id, enum hfp_call_status status, char *number, unsigned int type, bool mpty) { struct hf_call *call; call = new0(struct hf_call, 1); call->id = id; call->status = status; if (number) call->line_id = strdup(number); call->type = type; call->mpty = mpty; call->hfp = hfp; queue_push_tail(hfp->calls, call); if (hfp->callbacks && hfp->callbacks->call_added) hfp->callbacks->call_added(call->id, call->status, hfp->callbacks_data); return call; } static void ciev_service_cb(uint8_t val, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, "%u", val); if (val < hfp->ag_ind[HFP_INDICATOR_SERVICE].min || val > hfp->ag_ind[HFP_INDICATOR_SERVICE].max) { DBG(hfp, "hf: Incorrect state: %u", val); return; } hfp->service = val; if (hfp->callbacks && hfp->callbacks->update_indicator) hfp->callbacks->update_indicator(HFP_INDICATOR_SERVICE, val, hfp->callbacks_data); } static void clcc_resp(enum hfp_result result, enum hfp_error cme_err, void *user_data) { struct hfp_hf *hfp = user_data; const struct queue_entry *call_entry, *id_entry; struct hf_call *call; uint id; bool found; struct queue *to_remove; DBG(hfp, ""); hfp->clcc_in_progress = false; if (result != HFP_RESULT_OK) { DBG(hfp, "hf: CLCC error: %d", result); goto failed; } /* Removed disconnected calls */ to_remove = queue_new(); for (call_entry = queue_get_entries(hfp->calls); call_entry; call_entry = call_entry->next) { call = call_entry->data; found = false; for (id_entry = queue_get_entries(hfp->updated_calls); id_entry; id_entry = id_entry->next) { id = PTR_TO_UINT(id_entry->data); if (call->id == id) { found = true; break; } } DBG(hfp, "hf: call %d -> %s", call->id, found ? "updated" : "disconnected"); if (!found) queue_push_tail(to_remove, UINT_TO_PTR(call->id)); } for (id_entry = queue_get_entries(to_remove); id_entry; id_entry = id_entry->next) { id = PTR_TO_UINT(id_entry->data); call = queue_find(hfp->calls, call_id_match, UINT_TO_PTR(id)); if (!call) { DBG(hfp, "hf: Unknown call to remove: %u", id); continue; } queue_remove(hfp->calls, call); remove_call_cb(call); } queue_remove_all(hfp->updated_calls, NULL, NULL, NULL); queue_destroy(to_remove, NULL); return; failed: if (!hfp->session && hfp->callbacks->session_ready) hfp->callbacks->session_ready(result, cme_err, hfp->callbacks_data); } static bool send_clcc(struct hfp_hf *hfp) { if (!hfp->session || hfp->clcc_in_progress) return true; if (!hfp_hf_send_command(hfp, clcc_resp, hfp, "AT+CLCC")) { DBG(hfp, "hf: Could not send AT+CLCC"); return false; } hfp->clcc_in_progress = true; return true; } static bool update_call_to_active(struct hfp_hf *hfp) { const struct queue_entry *entry; struct hf_call *call; for (entry = queue_get_entries(hfp->calls); entry; entry = entry->next) { call = entry->data; if (call->status == CALL_STATUS_DIALING || call->status == CALL_STATUS_ALERTING || call->status == CALL_STATUS_INCOMING) { call->status = CALL_STATUS_ACTIVE; if (hfp->callbacks && hfp->callbacks->call_status_updated) hfp->callbacks->call_status_updated( call->id, call->status, hfp->callbacks_data); return true; } } return false; } static void ciev_call_cb(uint8_t val, void *user_data) { struct hfp_hf *hfp = user_data; uint id; DBG(hfp, "%u", val); if (hfp->features & HFP_AG_FEAT_ENHANCED_CALL_STATUS) { send_clcc(hfp); return; } if (val < hfp->ag_ind[HFP_INDICATOR_CALL].min || val > hfp->ag_ind[HFP_INDICATOR_CALL].max) { DBG(hfp, "hf: Incorrect call state: %u", val); return; } switch (val) { case CIND_CALL_NONE: /* Remove all calls */ queue_remove_all(hfp->calls, NULL, hfp, remove_call_cb); break; case CIND_CALL_IN_PROGRESS: { /* Find incoming, dialing or alerting call to change * it to active */ if (update_call_to_active(hfp)) return; /* else create new already active call */ id = next_call_index(hfp); if (id == 0) { DBG(hfp, "hf: No new call index available"); return; } call_new(hfp, id, CALL_STATUS_ACTIVE, NULL, 0, false); } break; default: DBG(hfp, "hf: Unsupported call state: %u", val); } } static bool call_outgoing_match(const void *data, const void *match_data) { const struct hf_call *call = data; return (call->status == CALL_STATUS_DIALING || call->status == CALL_STATUS_ALERTING); } static bool call_incoming_match(const void *data, const void *match_data) { const struct hf_call *call = data; return (call->status == CALL_STATUS_INCOMING); } static bool call_setup_match(const void *data, const void *match_data) { return (call_outgoing_match(data, match_data) || call_incoming_match(data, match_data)); } static bool call_active_match(const void *data, const void *match_data) { const struct hf_call *call = data; return (call->status == CALL_STATUS_ACTIVE); } static bool call_waiting_match(const void *data, const void *match_data) { const struct hf_call *call = data; return (call->status == CALL_STATUS_WAITING); } static bool call_held_match(const void *data, const void *match_data) { const struct hf_call *call = data; return (call->status == CALL_STATUS_HELD); } static void bsir_cb(struct hfp_context *context, void *user_data) { struct hfp_hf *hfp = user_data; unsigned int val; DBG(hfp, ""); if (!hfp_context_get_number(context, &val)) return; if (hfp->callbacks && hfp->callbacks->update_inband_ring) hfp->callbacks->update_inband_ring(!!val, hfp->callbacks_data); } static void ccwa_cb(struct hfp_context *context, void *user_data) { struct hfp_hf *hfp = user_data; char number[255]; unsigned int type; struct hf_call *call; uint id; DBG(hfp, ""); if (hfp->features & HFP_AG_FEAT_ENHANCED_CALL_STATUS) { send_clcc(hfp); return; } if (!hfp_context_get_string(context, number, sizeof(number))) { DBG(hfp, "hf: Could not get string"); return; } if (!hfp_context_get_number(context, &type)) return; call = queue_find(hfp->calls, call_waiting_match, NULL); if (call) { DBG(hfp, "hf: waiting call already in progress"); return; } id = next_call_index(hfp); if (id == 0) { DBG(hfp, "hf: No new call index available"); return; } call_new(hfp, id, CALL_STATUS_WAITING, number, type, false); } static void ciev_callsetup_cb(uint8_t val, void *user_data) { struct hfp_hf *hfp = user_data; struct hf_call *call; uint id; enum hfp_call_status status; DBG(hfp, "%u", val); if (hfp->features & HFP_AG_FEAT_ENHANCED_CALL_STATUS) { send_clcc(hfp); return; } if (val < hfp->ag_ind[HFP_INDICATOR_CALLSETUP].min || val > hfp->ag_ind[HFP_INDICATOR_CALLSETUP].max) { DBG(hfp, "hf: Incorrect call setup state: %u", val); return; } switch (val) { case CIND_CALLSETUP_NONE: /* remove call in setup phase */ queue_remove_all(hfp->calls, call_setup_match, hfp, remove_call_cb); break; case CIND_CALLSETUP_INCOMING: if (queue_length(hfp->calls) != 0) { DBG(hfp, "hf: Call already exists"); return; } id = next_call_index(hfp); if (id == 0) { DBG(hfp, "hf: No new call index available"); return; } call_new(hfp, id, CALL_STATUS_INCOMING, NULL, 0, false); break; case CIND_CALLSETUP_DIALING: case CIND_CALLSETUP_ALERTING: if (val == CIND_CALLSETUP_DIALING) status = CALL_STATUS_DIALING; else status = CALL_STATUS_ALERTING; if (queue_find(hfp->calls, call_active_match, NULL)) { DBG(hfp, "hf: Error: active call"); return; } call = queue_find(hfp->calls, call_outgoing_match, NULL); if (call && call->status != status) { call->status = status; if (hfp->callbacks && hfp->callbacks->call_status_updated) hfp->callbacks->call_status_updated(call->id, call->status, hfp->callbacks_data); return; } id = next_call_index(hfp); if (id == 0) { DBG(hfp, "hf: No new call index available"); return; } call_new(hfp, id, status, hfp->dialing_number, 0, false); if (hfp->dialing_number) { free(hfp->dialing_number); hfp->dialing_number = NULL; } break; } } static void ciev_callheld_cb(uint8_t val, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, "%u", val); if (hfp->features & HFP_AG_FEAT_ENHANCED_CALL_STATUS) { send_clcc(hfp); return; } if (val < hfp->ag_ind[HFP_INDICATOR_CALLHELD].min || val > hfp->ag_ind[HFP_INDICATOR_CALLHELD].max) { DBG(hfp, "hf: Incorrect call held state: %u", val); return; } } static void ciev_signal_cb(uint8_t val, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, "%u", val); if (val < hfp->ag_ind[HFP_INDICATOR_SIGNAL].min || val > hfp->ag_ind[HFP_INDICATOR_SIGNAL].max) { DBG(hfp, "hf: Incorrect signal value: %u", val); return; } hfp->signal = val; if (hfp->callbacks && hfp->callbacks->update_indicator) hfp->callbacks->update_indicator(HFP_INDICATOR_SIGNAL, val, hfp->callbacks_data); } static void ciev_roam_cb(uint8_t val, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, "%u", val); if (val < hfp->ag_ind[HFP_INDICATOR_ROAM].min || val > hfp->ag_ind[HFP_INDICATOR_ROAM].max) { DBG(hfp, "hf: Incorrect roaming state: %u", val); return; } hfp->roaming = val; if (hfp->callbacks && hfp->callbacks->update_indicator) hfp->callbacks->update_indicator(HFP_INDICATOR_ROAM, val, hfp->callbacks_data); } static void ciev_battchg_cb(uint8_t val, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, "%u", val); if (val < hfp->ag_ind[HFP_INDICATOR_BATTCHG].min || val > hfp->ag_ind[HFP_INDICATOR_BATTCHG].max) { DBG(hfp, "hf: Incorrect battery charge value: %u", val); return; } hfp->battchg = val; if (hfp->callbacks && hfp->callbacks->update_indicator) hfp->callbacks->update_indicator(HFP_INDICATOR_BATTCHG, val, hfp->callbacks_data); } static void set_indicator_value(uint8_t index, unsigned int val, struct indicator *ag_ind, struct hfp_hf *hfp) { int i; for (i = 0; i < HFP_INDICATOR_LAST; i++) { if (index != ag_ind[i].index) continue; ag_ind[i].val = val; ag_ind[i].cb(val, hfp); return; } } static void ciev_cb(struct hfp_context *context, void *user_data) { struct hfp_hf *hfp = user_data; unsigned int index, val; DBG(hfp, ""); if (!hfp_context_get_number(context, &index)) return; if (!hfp_context_get_number(context, &val)) return; set_indicator_value(index, val, hfp->ag_ind, hfp); } static void cops_cb(struct hfp_context *context, void *user_data) { struct hfp_hf *hfp = user_data; unsigned int mode, val; char name[255]; DBG(hfp, ""); if (!hfp_context_get_number(context, &mode)) return; if (!hfp_context_get_number(context, &val)) return; if (!hfp_context_get_string(context, name, sizeof(name))) { DBG(hfp, "hf: Could not get string"); return; } if (hfp->callbacks && hfp->callbacks->update_operator) hfp->callbacks->update_operator(name, hfp->callbacks_data); } static void clcc_cb(struct hfp_context *context, void *user_data) { struct hfp_hf *hfp = user_data; unsigned int id, status, mpty, type; char number[255]; struct hf_call *call; DBG(hfp, ""); if (!hfp_context_get_number(context, &id)) return; /* Skip direction */ hfp_context_skip_field(context); if (!hfp_context_get_number(context, &status)) return; /* Skip mode */ hfp_context_skip_field(context); if (!hfp_context_get_number(context, &mpty)) return; if (!hfp_context_get_string(context, number, sizeof(number))) { DBG(hfp, "hf: Could not get string"); return; } if (!hfp_context_get_number(context, &type)) return; queue_push_tail(hfp->updated_calls, UINT_TO_PTR(id)); call = queue_find(hfp->calls, call_id_match, UINT_TO_PTR(id)); if (!call) { call_new(hfp, id, status, number, type, !!mpty); return; } if (call->status != status) { call->status = status; if (hfp->callbacks && hfp->callbacks->call_status_updated) hfp->callbacks->call_status_updated(call->id, call->status, hfp->callbacks_data); } if (call->mpty != mpty) { call->mpty = mpty; if (hfp->callbacks && hfp->callbacks->call_mpty_updated) hfp->callbacks->call_mpty_updated(call->id, call->mpty, hfp->callbacks_data); } if (call->line_id && strcmp(call->line_id, number) == 0 && call->type == type) return; if (call->line_id) free(call->line_id); call->line_id = strdup(number); call->type = type; if (hfp->callbacks && hfp->callbacks->call_line_id_updated) hfp->callbacks->call_line_id_updated(call->id, call->line_id, call->type, hfp->callbacks_data); } static void clip_cb(struct hfp_context *context, void *user_data) { struct hfp_hf *hfp = user_data; char number[255]; unsigned int type; struct hf_call *call; DBG(hfp, ""); if (!hfp_context_get_string(context, number, sizeof(number))) { DBG(hfp, "hf: Could not get string"); return; } if (!hfp_context_get_number(context, &type)) return; call = queue_find(hfp->calls, call_incoming_match, NULL); if (!call) { DBG(hfp, "hf: no incoming call"); return; } if (call->line_id && strcmp(call->line_id, number) == 0 && call->type == type) return; if (call->line_id) free(call->line_id); call->line_id = strdup(number); call->type = type; if (hfp->callbacks && hfp->callbacks->call_line_id_updated) hfp->callbacks->call_line_id_updated(call->id, call->line_id, call->type, hfp->callbacks_data); } static void nrec_resp(enum hfp_result result, enum hfp_error cme_err, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, ""); if (result != HFP_RESULT_OK) { DBG(hfp, "hf: NREC error: %d", result); goto failed; } hfp->session = true; if (hfp->callbacks->session_ready) hfp->callbacks->session_ready(HFP_RESULT_OK, 0, hfp->callbacks_data); if (hfp->features & HFP_AG_FEAT_ENHANCED_CALL_STATUS) { if (!send_clcc(hfp)) { result = HFP_RESULT_ERROR; goto failed; } } return; failed: if (hfp->callbacks->session_ready) hfp->callbacks->session_ready(result, cme_err, hfp->callbacks_data); } static void cmee_resp(enum hfp_result result, enum hfp_error cme_err, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, ""); if (result != HFP_RESULT_OK) { DBG(hfp, "hf: CMEE error: %d", result); goto failed; } if (!(hfp->features & HFP_AG_FEAT_ECNR)) { /* Jump to next setup state */ nrec_resp(HFP_RESULT_OK, cme_err, user_data); return; } if (!hfp_hf_send_command(hfp, nrec_resp, hfp, "AT+NREC=0")) { DBG(hfp, "hf: Could not send AT+NREC=0"); result = HFP_RESULT_ERROR; goto failed; } return; failed: if (hfp->callbacks->session_ready) hfp->callbacks->session_ready(result, cme_err, hfp->callbacks_data); } static void ccwa_resp(enum hfp_result result, enum hfp_error cme_err, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, ""); if (result != HFP_RESULT_OK) { DBG(hfp, "hf: CCWA error: %d", result); goto failed; } if (!(hfp->features & HFP_AG_FEAT_EXTENDED_RES_CODE)) { /* Jump to next setup state */ cmee_resp(HFP_RESULT_OK, cme_err, user_data); return; } if (!hfp_hf_send_command(hfp, cmee_resp, hfp, "AT+CMEE=1")) { DBG(hfp, "hf: Could not send AT+CMEE=1"); result = HFP_RESULT_ERROR; goto failed; } return; failed: if (hfp->callbacks->session_ready) hfp->callbacks->session_ready(result, cme_err, hfp->callbacks_data); } static void clip_resp(enum hfp_result result, enum hfp_error cme_err, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, ""); if (result != HFP_RESULT_OK) { DBG(hfp, "hf: CLIP error: %d", result); goto failed; } if (!(hfp->features & HFP_AG_FEAT_3WAY)) { /* Jump to next setup state */ ccwa_resp(HFP_RESULT_OK, cme_err, user_data); return; } if (!hfp_hf_send_command(hfp, ccwa_resp, hfp, "AT+CCWA=1")) { DBG(hfp, "hf: Could not send AT+CCWA=1"); result = HFP_RESULT_ERROR; goto failed; } return; failed: if (hfp->callbacks->session_ready) hfp->callbacks->session_ready(result, cme_err, hfp->callbacks_data); } static void cops_resp(enum hfp_result result, enum hfp_error cme_err, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, ""); if (result != HFP_RESULT_OK) { DBG(hfp, "hf: COPS? error: %d", result); goto failed; } /* SLC creation done, continue with default setup */ if (!hfp_hf_send_command(hfp, clip_resp, hfp, "AT+CLIP=1")) { DBG(hfp, "hf: Could not send AT+CLIP=1"); result = HFP_RESULT_ERROR; goto failed; } return; failed: if (hfp->callbacks->session_ready) hfp->callbacks->session_ready(result, cme_err, hfp->callbacks_data); } static void cops_conf_resp(enum hfp_result result, enum hfp_error cme_err, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, ""); if (result != HFP_RESULT_OK) { DBG(hfp, "hf: COPS= error: %d", result); goto failed; } /* SLC creation done, continue with default setup */ if (!hfp_hf_send_command(hfp, cops_resp, hfp, "AT+COPS?")) { DBG(hfp, "hf: Could not send AT+COPS?"); result = HFP_RESULT_ERROR; goto failed; } return; failed: if (hfp->callbacks->session_ready) hfp->callbacks->session_ready(result, cme_err, hfp->callbacks_data); } static void slc_chld_cb(struct hfp_context *context, void *user_data) { struct hfp_hf *hfp = user_data; if (!hfp_context_open_container(context)) { DBG(hfp, "hf: Could not open container for CHLD"); return; } while (hfp_context_has_next(context) && !hfp_context_is_container_close(context)) { char val[3]; if (!hfp_context_get_unquoted_string(context, val, sizeof(val))) { DBG(hfp, "hf: Could not get string"); goto failed; } if (strcmp(val, "0") == 0) hfp->chlds |= HFP_CHLD_0; else if (strcmp(val, "1") == 0) hfp->chlds |= HFP_CHLD_1; else if (strcmp(val, "2") == 0) hfp->chlds |= HFP_CHLD_2; else DBG(hfp, "CHLD not supported: %s", val); } if (!hfp_context_close_container(context)) { DBG(hfp, "hf: Could not close container"); goto failed; } return; failed: DBG(hfp, "hf: Error on CHLD response"); } static void slc_chld_resp(enum hfp_result result, enum hfp_error cme_err, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, ""); hfp_hf_unregister(hfp, "+CHLD"); if (result != HFP_RESULT_OK) { DBG(hfp, "hf: CHLD=? error: %d", result); goto failed; } /* SLC creation done, continue with default setup */ if (!hfp_hf_send_command(hfp, cops_conf_resp, hfp, "AT+COPS=3,0")) { DBG(hfp, "hf: Could not send AT+COPS=3,0"); result = HFP_RESULT_ERROR; goto failed; } /* Register unsolicited results handlers */ if (hfp->features & HFP_AG_FEAT_IN_BAND_RING_TONE) hfp_hf_register(hfp, bsir_cb, "+BSIR", hfp, NULL); if (hfp->features & HFP_AG_FEAT_3WAY) hfp_hf_register(hfp, ccwa_cb, "+CCWA", hfp, NULL); hfp_hf_register(hfp, ciev_cb, "+CIEV", hfp, NULL); hfp_hf_register(hfp, clcc_cb, "+CLCC", hfp, NULL); hfp_hf_register(hfp, clip_cb, "+CLIP", hfp, NULL); hfp_hf_register(hfp, cops_cb, "+COPS", hfp, NULL); return; failed: if (hfp->callbacks->session_ready) hfp->callbacks->session_ready(result, cme_err, hfp->callbacks_data); } static void slc_cmer_resp(enum hfp_result result, enum hfp_error cme_err, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, ""); if (result != HFP_RESULT_OK) { DBG(hfp, "hf: CMER error: %d", result); goto failed; } if (!(hfp->features & HFP_AG_FEAT_3WAY)) { /* Jump to next setup state */ slc_chld_resp(HFP_RESULT_OK, cme_err, user_data); return; } if (!hfp_hf_register(hfp, slc_chld_cb, "+CHLD", hfp, NULL)) { DBG(hfp, "hf: Could not register +CHLD"); result = HFP_RESULT_ERROR; goto failed; } if (!hfp_hf_send_command(hfp, slc_chld_resp, hfp, "AT+CHLD=?")) { DBG(hfp, "hf: Could not send AT+CHLD=?"); result = HFP_RESULT_ERROR; goto failed; } return; failed: if (hfp->callbacks->session_ready) hfp->callbacks->session_ready(result, cme_err, hfp->callbacks_data); } static void slc_cind_status_cb(struct hfp_context *context, void *user_data) { struct hfp_hf *hfp = user_data; uint8_t index = 1; while (hfp_context_has_next(context)) { uint32_t val; if (!hfp_context_get_number(context, &val)) { DBG(hfp, "hf: Error on CIND status response"); return; } set_indicator_value(index++, val, hfp->ag_ind, hfp); } } static void slc_cind_status_resp(enum hfp_result result, enum hfp_error cme_err, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, ""); hfp_hf_unregister(hfp, "+CIND"); if (result != HFP_RESULT_OK) { DBG(hfp, "hf: CIND error: %d", result); goto failed; } /* Continue with SLC creation */ if (!hfp_hf_send_command(hfp, slc_cmer_resp, hfp, "AT+CMER=3,0,0,1")) { DBG(hfp, "hf: Could not send AT+CMER"); result = HFP_RESULT_ERROR; goto failed; } return; failed: if (hfp->callbacks->session_ready) hfp->callbacks->session_ready(result, cme_err, hfp->callbacks_data); } static void set_indicator_parameters(struct hfp_hf *hfp, uint8_t index, const char *indicator, unsigned int min, unsigned int max) { struct indicator *ag_ind = hfp->ag_ind; DBG(hfp, "%s, %i", indicator, index); if (strcmp("service", indicator) == 0) { if (min != 0 || max != 1) { DBG(hfp, "hf: Invalid min/max values for service," " expected (0,1) got (%u,%u)", min, max); return; } ag_ind[HFP_INDICATOR_SERVICE].index = index; ag_ind[HFP_INDICATOR_SERVICE].min = min; ag_ind[HFP_INDICATOR_SERVICE].max = max; ag_ind[HFP_INDICATOR_SERVICE].cb = ciev_service_cb; return; } if (strcmp("call", indicator) == 0) { if (min != 0 || max != 1) { DBG(hfp, "hf: Invalid min/max values for call," " expected (0,1) got (%u,%u)", min, max); return; } ag_ind[HFP_INDICATOR_CALL].index = index; ag_ind[HFP_INDICATOR_CALL].min = min; ag_ind[HFP_INDICATOR_CALL].max = max; ag_ind[HFP_INDICATOR_CALL].cb = ciev_call_cb; return; } if (strcmp("callsetup", indicator) == 0) { if (min != 0 || max != 3) { DBG(hfp, "hf: Invalid min/max values for callsetup," " expected (0,3) got (%u,%u)", min, max); return; } ag_ind[HFP_INDICATOR_CALLSETUP].index = index; ag_ind[HFP_INDICATOR_CALLSETUP].min = min; ag_ind[HFP_INDICATOR_CALLSETUP].max = max; ag_ind[HFP_INDICATOR_CALLSETUP].cb = ciev_callsetup_cb; return; } if (strcmp("callheld", indicator) == 0) { if (min != 0 || max != 2) { DBG(hfp, "hf: Invalid min/max values for callheld," " expected (0,2) got (%u,%u)", min, max); return; } ag_ind[HFP_INDICATOR_CALLHELD].index = index; ag_ind[HFP_INDICATOR_CALLHELD].min = min; ag_ind[HFP_INDICATOR_CALLHELD].max = max; ag_ind[HFP_INDICATOR_CALLHELD].cb = ciev_callheld_cb; return; } if (strcmp("signal", indicator) == 0) { if (min != 0 || max != 5) { DBG(hfp, "hf: Invalid min/max values for signal," " expected (0,5) got (%u,%u)", min, max); return; } ag_ind[HFP_INDICATOR_SIGNAL].index = index; ag_ind[HFP_INDICATOR_SIGNAL].min = min; ag_ind[HFP_INDICATOR_SIGNAL].max = max; ag_ind[HFP_INDICATOR_SIGNAL].cb = ciev_signal_cb; return; } if (strcmp("roam", indicator) == 0) { if (min != 0 || max != 1) { DBG(hfp, "hf: Invalid min/max values for roam," " expected (0,1) got (%u,%u)", min, max); return; } ag_ind[HFP_INDICATOR_ROAM].index = index; ag_ind[HFP_INDICATOR_ROAM].min = min; ag_ind[HFP_INDICATOR_ROAM].max = max; ag_ind[HFP_INDICATOR_ROAM].cb = ciev_roam_cb; return; } if (strcmp("battchg", indicator) == 0) { if (min != 0 || max != 5) { DBG(hfp, "hf: Invalid min/max values for battchg," " expected (0,5) got (%u,%u)", min, max); return; } ag_ind[HFP_INDICATOR_BATTCHG].index = index; ag_ind[HFP_INDICATOR_BATTCHG].min = min; ag_ind[HFP_INDICATOR_BATTCHG].max = max; ag_ind[HFP_INDICATOR_BATTCHG].cb = ciev_battchg_cb; return; } DBG(hfp, "hf: Unknown indicator: %s", indicator); } static void slc_cind_cb(struct hfp_context *context, void *user_data) { struct hfp_hf *hfp = user_data; int index = 1; DBG(hfp, ""); while (hfp_context_has_next(context)) { char name[255]; unsigned int min, max; /* e.g ("callsetup",(0-3)) */ if (!hfp_context_open_container(context)) break; if (!hfp_context_get_string(context, name, sizeof(name))) { DBG(hfp, "hf: Could not get string"); goto failed; } if (!hfp_context_open_container(context)) { DBG(hfp, "hf: Could not open container"); goto failed; } if (!hfp_context_get_range(context, &min, &max)) { if (!hfp_context_get_number(context, &min)) { DBG(hfp, "hf: Could not get number"); goto failed; } if (!hfp_context_get_number(context, &max)) { DBG(hfp, "hf: Could not get number"); goto failed; } } if (!hfp_context_close_container(context)) { DBG(hfp, "hf: Could not close container"); goto failed; } if (!hfp_context_close_container(context)) { DBG(hfp, "hf: Could not close container"); goto failed; } set_indicator_parameters(hfp, index, name, min, max); index++; } return; failed: DBG(hfp, "hf: Error on CIND response"); } static void slc_cind_resp(enum hfp_result result, enum hfp_error cme_err, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, ""); hfp_hf_unregister(hfp, "+CIND"); if (result != HFP_RESULT_OK) { DBG(hfp, "hf: CIND error: %d", result); goto failed; } /* Continue with SLC creation */ if (!hfp_hf_register(hfp, slc_cind_status_cb, "+CIND", hfp, NULL)) { DBG(hfp, "hf: Could not register +CIND"); result = HFP_RESULT_ERROR; goto failed; } if (!hfp_hf_send_command(hfp, slc_cind_status_resp, hfp, "AT+CIND?")) { DBG(hfp, "hf: Could not send AT+CIND?"); result = HFP_RESULT_ERROR; goto failed; } return; failed: if (hfp->callbacks->session_ready) hfp->callbacks->session_ready(result, cme_err, hfp->callbacks_data); } static void slc_brsf_cb(struct hfp_context *context, void *user_data) { struct hfp_hf *hfp = user_data; unsigned int feat; DBG(hfp, ""); if (hfp_context_get_number(context, &feat)) hfp->features = feat; } static void slc_brsf_resp(enum hfp_result result, enum hfp_error cme_err, void *user_data) { struct hfp_hf *hfp = user_data; DBG(hfp, ""); hfp_hf_unregister(hfp, "+BRSF"); if (result != HFP_RESULT_OK) { DBG(hfp, "BRSF error: %d", result); goto failed; } /* Continue with SLC creation */ if (!hfp_hf_register(hfp, slc_cind_cb, "+CIND", hfp, NULL)) { DBG(hfp, "hf: Could not register for +CIND"); result = HFP_RESULT_ERROR; goto failed; } if (!hfp_hf_send_command(hfp, slc_cind_resp, hfp, "AT+CIND=?")) { DBG(hfp, "hf: Could not send AT+CIND command"); result = HFP_RESULT_ERROR; goto failed; } return; failed: if (hfp->callbacks->session_ready) hfp->callbacks->session_ready(result, cme_err, hfp->callbacks_data); } bool hfp_hf_session_register(struct hfp_hf *hfp, struct hfp_hf_callbacks *callbacks, void *callbacks_data) { if (!hfp) return false; hfp->callbacks = callbacks; hfp->callbacks_data = callbacks_data; return true; } bool hfp_hf_session(struct hfp_hf *hfp) { if (!hfp) return false; DBG(hfp, ""); if (!hfp_hf_register(hfp, slc_brsf_cb, "+BRSF", hfp, NULL)) return false; return hfp_hf_send_command(hfp, slc_brsf_resp, hfp, "AT+BRSF=%u", HFP_HF_FEATURES); } const char *hfp_hf_call_get_number(struct hfp_hf *hfp, uint id) { struct hf_call *call; if (!hfp) return NULL; DBG(hfp, ""); call = queue_find(hfp->calls, call_id_match, UINT_TO_PTR(id)); if (!call) { DBG(hfp, "hf: no call with id: %u", id); return NULL; } return call->line_id; } bool hfp_hf_call_get_multiparty(struct hfp_hf *hfp, uint id, bool *mpty) { struct hf_call *call; if (!hfp) return false; DBG(hfp, ""); call = queue_find(hfp->calls, call_id_match, UINT_TO_PTR(id)); if (!call) { DBG(hfp, "hf: no call with id: %u", id); return false; } *mpty = call->mpty; return true; } bool hfp_hf_dial(struct hfp_hf *hfp, const char *number, hfp_response_func_t resp_cb, void *user_data) { const char *c; int count = 0; if (!hfp) return false; DBG(hfp, ""); if (number == NULL || strlen(number) == 0) return hfp_hf_send_command(hfp, resp_cb, user_data, "AT+BLDN"); if (number[0] == '>') { for (c = number + 1; *c != '\0'; c++) { if (!(*c >= '0' && *c <= '9')) return false; count++; } if (count < 1 || count > 10) return false; } else { for (c = number; *c != '\0'; c++) { if (!(*c >= '0' && *c <= '9') && !(*c >= 'A' && *c <= 'D') && *c != '#' && *c != '*' && *c != '+' && *c != ',') return false; count++; } if (count < 1 || count > 80) return false; } if (hfp->dialing_number) free(hfp->dialing_number); hfp->dialing_number = strdup(number); return hfp_hf_send_command(hfp, resp_cb, user_data, "ATD%s;", number); } bool hfp_hf_release_and_accept(struct hfp_hf *hfp, hfp_response_func_t resp_cb, void *user_data) { if (!hfp) return false; DBG(hfp, ""); if (!(hfp->chlds & HFP_CHLD_1) || (!queue_find(hfp->calls, call_waiting_match, NULL) && !queue_find(hfp->calls, call_held_match, NULL))) return false; return hfp_hf_send_command(hfp, resp_cb, user_data, "AT+CHLD=1"); } bool hfp_hf_swap_calls(struct hfp_hf *hfp, hfp_response_func_t resp_cb, void *user_data) { if (!hfp) return false; DBG(hfp, ""); if (!(hfp->chlds & HFP_CHLD_2)) return false; return hfp_hf_send_command(hfp, resp_cb, user_data, "AT+CHLD=2"); } bool hfp_hf_call_answer(struct hfp_hf *hfp, uint id, hfp_response_func_t resp_cb, void *user_data) { struct hf_call *call; if (!hfp) return false; DBG(hfp, ""); call = queue_find(hfp->calls, call_id_match, UINT_TO_PTR(id)); if (!call) { DBG(hfp, "hf: no call with id: %u", id); return false; } if (call->status != CALL_STATUS_INCOMING) { DBG(hfp, "hf: %d not in incoming call state: %u", id, call->status); return false; } return hfp_hf_send_command(hfp, resp_cb, user_data, "ATA"); } bool hfp_hf_call_hangup(struct hfp_hf *hfp, uint id, hfp_response_func_t resp_cb, void *user_data) { struct hf_call *call; if (!hfp) return false; DBG(hfp, ""); call = queue_find(hfp->calls, call_id_match, UINT_TO_PTR(id)); if (!call) { DBG(hfp, "hf: no call with id: %u", id); return false; } if (call_setup_match(call, NULL) || call_active_match(call, NULL)) { return hfp_hf_send_command(hfp, resp_cb, user_data, "AT+CHUP"); } else if ((call_waiting_match(call, NULL) || call_held_match(call, NULL)) && (hfp->chlds & HFP_CHLD_0)) { return hfp_hf_send_command(hfp, resp_cb, user_data, "AT+CHLD=0"); } return false; } bluez-5.87/src/shared/PaxHeaders/att-types.h0000644000000000000000000000005014447506754016033 xustar0020 atime=1783092907 20 ctime=1783094099 bluez-5.87/src/shared/att-types.h0000644000000000000000000001275714447506754015530 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Google Inc. * Copyright 2023 NXP * * */ #include #ifndef __packed #define __packed __attribute__((packed)) #endif #define BT_ATT_CID 4 #define BT_ATT_PSM 31 #define BT_ATT_EATT_PSM 0x27 #define BT_ATT_SECURITY_AUTO 0 #define BT_ATT_SECURITY_LOW 1 #define BT_ATT_SECURITY_MEDIUM 2 #define BT_ATT_SECURITY_HIGH 3 #define BT_ATT_SECURITY_FIPS 4 #define BT_ATT_DEFAULT_LE_MTU 23 #define BT_ATT_MAX_LE_MTU 517 #define BT_ATT_MAX_VALUE_LEN 512 #define BT_ATT_BREDR 0x00 #define BT_ATT_LE 0x01 #define BT_ATT_EATT 0x02 #define BT_ATT_LOCAL 0xff /* ATT protocol opcodes */ #define BT_ATT_OP_ERROR_RSP 0x01 #define BT_ATT_OP_MTU_REQ 0x02 #define BT_ATT_OP_MTU_RSP 0x03 #define BT_ATT_OP_FIND_INFO_REQ 0x04 #define BT_ATT_OP_FIND_INFO_RSP 0x05 #define BT_ATT_OP_FIND_BY_TYPE_REQ 0x06 #define BT_ATT_OP_FIND_BY_TYPE_RSP 0x07 #define BT_ATT_OP_READ_BY_TYPE_REQ 0x08 #define BT_ATT_OP_READ_BY_TYPE_RSP 0x09 #define BT_ATT_OP_READ_REQ 0x0a #define BT_ATT_OP_READ_RSP 0x0b #define BT_ATT_OP_READ_BLOB_REQ 0x0c #define BT_ATT_OP_READ_BLOB_RSP 0x0d #define BT_ATT_OP_READ_MULT_REQ 0x0e #define BT_ATT_OP_READ_MULT_RSP 0x0f #define BT_ATT_OP_READ_BY_GRP_TYPE_REQ 0x10 #define BT_ATT_OP_READ_BY_GRP_TYPE_RSP 0x11 #define BT_ATT_OP_WRITE_REQ 0x12 #define BT_ATT_OP_WRITE_RSP 0x13 #define BT_ATT_OP_WRITE_CMD 0x52 #define BT_ATT_OP_SIGNED_WRITE_CMD 0xD2 #define BT_ATT_OP_PREP_WRITE_REQ 0x16 #define BT_ATT_OP_PREP_WRITE_RSP 0x17 #define BT_ATT_OP_EXEC_WRITE_REQ 0x18 #define BT_ATT_OP_EXEC_WRITE_RSP 0x19 #define BT_ATT_OP_HANDLE_NFY 0x1B #define BT_ATT_OP_HANDLE_IND 0x1D #define BT_ATT_OP_HANDLE_CONF 0x1E #define BT_ATT_OP_READ_MULT_VL_REQ 0x20 #define BT_ATT_OP_READ_MULT_VL_RSP 0x21 #define BT_ATT_OP_HANDLE_NFY_MULT 0x23 /* Packed struct definitions for ATT protocol PDUs */ /* TODO: Complete these definitions for all opcodes */ struct bt_att_pdu_error_rsp { uint8_t opcode; uint16_t handle; uint8_t ecode; } __packed; /* Special opcode to receive all requests (legacy servers) */ #define BT_ATT_ALL_REQUESTS 0x00 /* Error codes for Error response PDU */ #define BT_ATT_ERROR_INVALID_HANDLE 0x01 #define BT_ATT_ERROR_READ_NOT_PERMITTED 0x02 #define BT_ATT_ERROR_WRITE_NOT_PERMITTED 0x03 #define BT_ATT_ERROR_INVALID_PDU 0x04 #define BT_ATT_ERROR_AUTHENTICATION 0x05 #define BT_ATT_ERROR_REQUEST_NOT_SUPPORTED 0x06 #define BT_ATT_ERROR_INVALID_OFFSET 0x07 #define BT_ATT_ERROR_AUTHORIZATION 0x08 #define BT_ATT_ERROR_PREPARE_QUEUE_FULL 0x09 #define BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND 0x0A #define BT_ATT_ERROR_ATTRIBUTE_NOT_LONG 0x0B #define BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE 0x0C #define BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN 0x0D #define BT_ATT_ERROR_UNLIKELY 0x0E #define BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION 0x0F #define BT_ATT_ERROR_UNSUPPORTED_GROUP_TYPE 0x10 #define BT_ATT_ERROR_INSUFFICIENT_RESOURCES 0x11 #define BT_ATT_ERROR_DB_OUT_OF_SYNC 0x12 #define BT_ATT_ERROR_VALUE_NOT_ALLOWED 0x13 /* * Common Profile and Service Error Code descriptions (see Supplement to the * Bluetooth Core Specification, sections 1.2 and 2). The error codes within * 0xE0-0xFB are reserved for future use. The remaining 4 are defined as the * following: */ #define BT_ERROR_WRITE_REQUEST_REJECTED 0xfc #define BT_ERROR_CCC_IMPROPERLY_CONFIGURED 0xfd #define BT_ERROR_ALREADY_IN_PROGRESS 0xfe #define BT_ERROR_OUT_OF_RANGE 0xff /* * ATT attribute permission bitfield values. Permissions are grouped as * "Access", "Encryption", "Authentication", and "Authorization". A bitmask of * permissions is a byte that encodes a combination of these. */ #define BT_ATT_PERM_READ 0x01 #define BT_ATT_PERM_WRITE 0x02 #define BT_ATT_PERM_READ_ENCRYPT 0x04 #define BT_ATT_PERM_WRITE_ENCRYPT 0x08 #define BT_ATT_PERM_ENCRYPT (BT_ATT_PERM_READ_ENCRYPT | \ BT_ATT_PERM_WRITE_ENCRYPT) #define BT_ATT_PERM_READ_AUTHEN 0x10 #define BT_ATT_PERM_WRITE_AUTHEN 0x20 #define BT_ATT_PERM_AUTHEN (BT_ATT_PERM_READ_AUTHEN | \ BT_ATT_PERM_WRITE_AUTHEN) #define BT_ATT_PERM_AUTHOR 0x40 #define BT_ATT_PERM_NONE 0x80 #define BT_ATT_PERM_READ_SECURE 0x0100 #define BT_ATT_PERM_WRITE_SECURE 0x0200 #define BT_ATT_PERM_SECURE (BT_ATT_PERM_READ_SECURE | \ BT_ATT_PERM_WRITE_SECURE) #define BT_ATT_PERM_READ_MASK (BT_ATT_PERM_READ | \ BT_ATT_PERM_READ_AUTHEN | \ BT_ATT_PERM_READ_ENCRYPT | \ BT_ATT_PERM_READ_SECURE) #define BT_ATT_PERM_WRITE_MASK (BT_ATT_PERM_WRITE | \ BT_ATT_PERM_WRITE_AUTHEN | \ BT_ATT_PERM_WRITE_ENCRYPT | \ BT_ATT_PERM_WRITE_SECURE) /* GATT Characteristic Properties Bitfield values */ #define BT_GATT_CHRC_PROP_BROADCAST 0x01 #define BT_GATT_CHRC_PROP_READ 0x02 #define BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP 0x04 #define BT_GATT_CHRC_PROP_WRITE 0x08 #define BT_GATT_CHRC_PROP_NOTIFY 0x10 #define BT_GATT_CHRC_PROP_INDICATE 0x20 #define BT_GATT_CHRC_PROP_AUTH 0x40 #define BT_GATT_CHRC_PROP_EXT_PROP 0x80 /* GATT Characteristic Extended Properties Bitfield values */ #define BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE 0x01 #define BT_GATT_CHRC_EXT_PROP_WRITABLE_AUX 0x02 /* GATT Characteristic Client Features Bitfield values */ #define BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING 0x01 #define BT_GATT_CHRC_CLI_FEAT_EATT 0x02 #define BT_GATT_CHRC_CLI_FEAT_NFY_MULTI 0x04 /* GATT Characteristic Server Features Bitfield values */ #define BT_GATT_CHRC_SERVER_FEAT_EATT 0x01 bluez-5.87/src/shared/PaxHeaders/gatt-server.c0000644000000000000000000000005015221752714016325 xustar0020 atime=1783092685 20 ctime=1783094100 bluez-5.87/src/shared/gatt-server.c0000644000000000000000000012374715221752714016024 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Google Inc. * Copyright 2023 NXP * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "src/shared/att.h" #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-server.h" #include "src/shared/gatt-helpers.h" #include "src/shared/util.h" #include "src/shared/timeout.h" /* * TODO: This is an arbitrary limit. Come up with something reasonable or * perhaps an API to set this value if there is a use case for it. */ #define DEFAULT_MAX_PREP_QUEUE_LEN 30 #define NFY_MULT_TIMEOUT 10 #define DBG(_server, _format, arg...) \ gatt_log(_server, "%s:%s() " _format, __FILE__, __func__, ## arg) struct async_read_op { struct bt_att_chan *chan; struct bt_gatt_server *server; uint8_t opcode; bool done; uint16_t mtu; uint8_t *pdu; size_t pdu_len; size_t value_len; struct queue *db_data; }; struct async_write_op { struct bt_att_chan *chan; struct bt_gatt_server *server; uint8_t opcode; }; struct prep_write_data { struct bt_gatt_server *server; uint8_t *value; uint16_t handle; uint16_t offset; uint16_t length; bool reliable_supported; }; static void prep_write_data_destroy(void *user_data) { struct prep_write_data *data = user_data; free(data->value); free(data); } struct nfy_mult_data { unsigned int id; uint8_t *pdu; uint16_t offset; uint16_t len; }; struct bt_gatt_server { struct gatt_db *db; struct bt_att *att; int ref_count; uint16_t mtu; bool perms; unsigned int mtu_id; unsigned int read_by_grp_type_id; unsigned int read_by_type_id; unsigned int find_info_id; unsigned int find_by_type_value_id; unsigned int write_id; unsigned int write_cmd_id; unsigned int read_id; unsigned int read_blob_id; unsigned int read_multiple_id; unsigned int read_multiple_vl_id; unsigned int prep_write_id; unsigned int exec_write_id; unsigned int signed_write_cmd_id; uint8_t min_enc_size; struct queue *prep_queue; unsigned int max_prep_queue_len; bt_gatt_server_debug_func_t debug_callback; bt_gatt_server_destroy_func_t debug_destroy; void *debug_data; bt_gatt_server_authorize_cb_t authorize; void *authorize_data; struct nfy_mult_data *nfy_mult; }; static void notify_multiple_free(struct bt_gatt_server *server) { if (!server->nfy_mult) return; if (server->nfy_mult->id) timeout_remove(server->nfy_mult->id); free(server->nfy_mult->pdu); free(server->nfy_mult); server->nfy_mult = NULL; } static void bt_gatt_server_free(struct bt_gatt_server *server) { if (server->debug_destroy) server->debug_destroy(server->debug_data); notify_multiple_free(server); bt_att_unregister(server->att, server->mtu_id); bt_att_unregister(server->att, server->read_by_grp_type_id); bt_att_unregister(server->att, server->read_by_type_id); bt_att_unregister(server->att, server->find_info_id); bt_att_unregister(server->att, server->find_by_type_value_id); bt_att_unregister(server->att, server->write_id); bt_att_unregister(server->att, server->write_cmd_id); bt_att_unregister(server->att, server->read_id); bt_att_unregister(server->att, server->read_blob_id); bt_att_unregister(server->att, server->read_multiple_id); bt_att_unregister(server->att, server->read_multiple_vl_id); bt_att_unregister(server->att, server->prep_write_id); bt_att_unregister(server->att, server->exec_write_id); bt_att_unregister(server->att, server->signed_write_cmd_id); queue_destroy(server->prep_queue, prep_write_data_destroy); gatt_db_unref(server->db); bt_att_unref(server->att); free(server); } static bool get_uuid_le(const uint8_t *uuid, size_t len, bt_uuid_t *out_uuid) { uint128_t u128; switch (len) { case 2: bt_uuid16_create(out_uuid, get_le16(uuid)); return true; case 16: bswap_128(uuid, &u128.data); bt_uuid128_create(out_uuid, u128); return true; default: return false; } return false; } static void attribute_read_cb(struct gatt_db_attribute *attrib, int err, const uint8_t *value, size_t length, void *user_data) { struct iovec *iov = user_data; iov->iov_base = (void *) value; iov->iov_len = length; } static bool encode_read_by_grp_type_rsp(struct gatt_db *db, struct queue *q, struct bt_att *att, uint16_t mtu, uint8_t *pdu, uint16_t *len) { int iter = 0; uint16_t start_handle, end_handle; struct iovec value; uint8_t data_val_len; *len = 0; while (queue_peek_head(q)) { struct gatt_db_attribute *attrib = queue_pop_head(q); value.iov_base = NULL; value.iov_len = 0; /* * This should never be deferred to the read callback for * primary/secondary service declarations. */ if (!gatt_db_attribute_read(attrib, 0, BT_ATT_OP_READ_BY_GRP_TYPE_REQ, att, attribute_read_cb, &value) || !value.iov_len) return false; /* * Use the first attribute to determine the length of each * attribute data unit. Stop the list when a different attribute * value is seen. */ if (iter == 0) { data_val_len = MIN(MIN((unsigned int)mtu - 6, 251), value.iov_len); pdu[0] = data_val_len + 4; iter++; } else if (value.iov_len != data_val_len) break; /* Stop if this unit would surpass the MTU */ if (iter + data_val_len + 4 > mtu - 1) break; gatt_db_attribute_get_service_handles(attrib, &start_handle, &end_handle); put_le16(start_handle, pdu + iter); put_le16(end_handle, pdu + iter + 2); memcpy(pdu + iter + 4, value.iov_base, data_val_len); iter += data_val_len + 4; } *len = iter; return true; } static void gatt_log(struct bt_gatt_server *server, const char *format, ...) { va_list ap; if (!server || !format || !server->debug_callback) return; va_start(ap, format); util_debug_va(server->debug_callback, server->debug_data, format, ap); va_end(ap); } static void read_by_grp_type_cb(struct bt_att_chan *chan, uint16_t mtu, uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; uint16_t start, end; bt_uuid_t type; bt_uuid_t prim, snd; uint8_t rsp_pdu[mtu]; uint16_t rsp_len; uint8_t ecode = 0; uint16_t ehandle = 0; struct queue *q = NULL; if (length != 6 && length != 20) { ecode = BT_ATT_ERROR_INVALID_PDU; goto error; } q = queue_new(); start = get_le16(pdu); end = get_le16(pdu + 2); get_uuid_le(pdu + 4, length - 4, &type); DBG(server, "Read By Grp Type - start: 0x%04x end: 0x%04x", start, end); if (!start || !end) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } ehandle = start; if (start > end) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } /* * GATT defines that only the <> and * <> group types can be used for the * "Read By Group Type" request (Core v4.1, Vol 3, sec 2.5.3). Return an * error if any other group type is given. */ bt_uuid16_create(&prim, GATT_PRIM_SVC_UUID); bt_uuid16_create(&snd, GATT_SND_SVC_UUID); if (bt_uuid_cmp(&type, &prim) && bt_uuid_cmp(&type, &snd)) { ecode = BT_ATT_ERROR_UNSUPPORTED_GROUP_TYPE; goto error; } gatt_db_read_by_group_type(server->db, start, end, type, q); if (queue_isempty(q)) { ecode = BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND; goto error; } if (!encode_read_by_grp_type_rsp(server->db, q, server->att, mtu, rsp_pdu, &rsp_len)) { ecode = BT_ATT_ERROR_UNLIKELY; goto error; } queue_destroy(q, NULL); bt_att_chan_send_rsp(chan, BT_ATT_OP_READ_BY_GRP_TYPE_RSP, rsp_pdu, rsp_len); return; error: queue_destroy(q, NULL); bt_att_chan_send_error_rsp(chan, opcode, ehandle, ecode); } static void async_read_op_destroy(struct async_read_op *op) { bt_gatt_server_unref(op->server); queue_destroy(op->db_data, NULL); free(op->pdu); free(op); } static void process_read_by_type(struct async_read_op *op); static void read_by_type_read_complete_cb(struct gatt_db_attribute *attr, int err, const uint8_t *value, size_t len, void *user_data) { struct async_read_op *op = user_data; uint16_t handle; handle = gatt_db_attribute_get_handle(attr); /* Terminate the operation if there was an error */ if (err) { bt_att_chan_send_error_rsp(op->chan, BT_ATT_OP_READ_BY_TYPE_REQ, handle, err); async_read_op_destroy(op); return; } if (op->pdu_len == 0) { op->value_len = MIN(MIN((unsigned int)op->mtu - 4, 253), len); op->pdu[0] = op->value_len + 2; op->pdu_len++; } else if (len != op->value_len) { op->done = true; goto done; } /* Stop if this would surpass the MTU */ if (op->pdu_len + op->value_len + 2 > (unsigned int) op->mtu - 1) { op->done = true; goto done; } /* Encode the current value */ put_le16(handle, op->pdu + op->pdu_len); memcpy(op->pdu + op->pdu_len + 2, value, op->value_len); op->pdu_len += op->value_len + 2; if (op->pdu_len == (unsigned int) op->mtu - 1) op->done = true; done: process_read_by_type(op); } static bool check_min_key_size(uint8_t min_size, uint8_t size) { if (!min_size || !size) return true; return min_size <= size; } static uint8_t check_permissions(struct bt_gatt_server *server, struct gatt_db_attribute *attr, uint32_t mask) { uint8_t enc_size; uint32_t perm; int security; if (!server->perms) return 0; perm = gatt_db_attribute_get_permissions(attr); if (perm && mask & BT_ATT_PERM_READ && !(perm & BT_ATT_PERM_READ)) return BT_ATT_ERROR_READ_NOT_PERMITTED; if (perm && mask & BT_ATT_PERM_WRITE && !(perm & BT_ATT_PERM_WRITE)) return BT_ATT_ERROR_WRITE_NOT_PERMITTED; perm &= mask; if (!perm) return 0; security = bt_att_get_security(server->att, &enc_size); if (security < 0) return BT_ATT_ERROR_UNLIKELY; if (perm & BT_ATT_PERM_SECURE) { if (security < BT_ATT_SECURITY_FIPS) return BT_ATT_ERROR_AUTHENTICATION; if (!check_min_key_size(server->min_enc_size, enc_size)) return BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE; } if (perm & BT_ATT_PERM_AUTHEN) { if (security < BT_ATT_SECURITY_HIGH) return BT_ATT_ERROR_AUTHENTICATION; if (!check_min_key_size(server->min_enc_size, enc_size)) return BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE; } if (perm & BT_ATT_PERM_ENCRYPT) { if (security < BT_ATT_SECURITY_MEDIUM) return BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION; if (!check_min_key_size(server->min_enc_size, enc_size)) return BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE; } return 0; } static void process_read_by_type(struct async_read_op *op) { struct bt_gatt_server *server = op->server; uint8_t ecode; struct gatt_db_attribute *attr; attr = queue_pop_head(op->db_data); if (op->done || !attr) { bt_att_chan_send_rsp(op->chan, BT_ATT_OP_READ_BY_TYPE_RSP, op->pdu, op->pdu_len); async_read_op_destroy(op); return; } ecode = check_permissions(server, attr, BT_ATT_PERM_READ_MASK); if (ecode) goto error; if (gatt_db_attribute_read(attr, 0, op->opcode, server->att, read_by_type_read_complete_cb, op)) return; ecode = BT_ATT_ERROR_UNLIKELY; error: bt_att_chan_send_error_rsp(op->chan, BT_ATT_OP_READ_BY_TYPE_REQ, gatt_db_attribute_get_handle(attr), ecode); async_read_op_destroy(op); } static void read_by_type_cb(struct bt_att_chan *chan, uint16_t mtu, uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; uint16_t start, end; bt_uuid_t type; uint16_t ehandle = 0; uint8_t ecode; struct queue *q = NULL; struct async_read_op *op; if (length != 6 && length != 20) { ecode = BT_ATT_ERROR_INVALID_PDU; goto error; } q = queue_new(); start = get_le16(pdu); end = get_le16(pdu + 2); get_uuid_le(pdu + 4, length - 4, &type); DBG(server, "Read By Type - start: 0x%04x end: 0x%04x", start, end); if (!start || !end) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } ehandle = start; if (start > end) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } gatt_db_read_by_type(server->db, start, end, type, q); if (queue_isempty(q)) { ecode = BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND; goto error; } op = new0(struct async_read_op, 1); op->mtu = mtu; op->pdu = malloc(mtu); if (!op->pdu) { free(op); ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES; goto error; } op->chan = chan; op->opcode = opcode; op->server = bt_gatt_server_ref(server); op->db_data = q; process_read_by_type(op); return; error: bt_att_chan_send_error_rsp(chan, opcode, ehandle, ecode); queue_destroy(q, NULL); } static bool encode_find_info_rsp(struct gatt_db *db, struct queue *q, uint16_t mtu, uint8_t *pdu, uint16_t *len) { uint16_t handle; struct gatt_db_attribute *attr; const bt_uuid_t *type; int uuid_len, cur_uuid_len; int iter = 0; *len = 0; while (queue_peek_head(q)) { attr = queue_pop_head(q); handle = gatt_db_attribute_get_handle(attr); type = gatt_db_attribute_get_type(attr); if (!handle || !type) return false; cur_uuid_len = bt_uuid_len(type); if (iter == 0) { switch (cur_uuid_len) { case 2: uuid_len = 2; pdu[0] = 0x01; break; case 4: case 16: uuid_len = 16; pdu[0] = 0x02; break; default: return false; } iter++; } else if (cur_uuid_len != uuid_len) break; if (iter + uuid_len + 2 > mtu - 1) break; put_le16(handle, pdu + iter); bt_uuid_to_le(type, pdu + iter + 2); iter += uuid_len + 2; } *len = iter; return true; } static void find_info_cb(struct bt_att_chan *chan, uint16_t mtu, uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; uint16_t start, end; uint8_t rsp_pdu[mtu]; uint16_t rsp_len; uint8_t ecode = 0; uint16_t ehandle = 0; struct queue *q = NULL; if (length != 4) { ecode = BT_ATT_ERROR_INVALID_PDU; goto error; } q = queue_new(); start = get_le16(pdu); end = get_le16(pdu + 2); DBG(server, "Find Info - start: 0x%04x end: 0x%04x", start, end); if (!start || !end) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } ehandle = start; if (start > end) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } gatt_db_find_information(server->db, start, end, q); if (queue_isempty(q)) { ecode = BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND; goto error; } if (!encode_find_info_rsp(server->db, q, mtu, rsp_pdu, &rsp_len)) { ecode = BT_ATT_ERROR_UNLIKELY; goto error; } bt_att_chan_send_rsp(chan, BT_ATT_OP_FIND_INFO_RSP, rsp_pdu, rsp_len); queue_destroy(q, NULL); return; error: bt_att_chan_send_error_rsp(chan, opcode, ehandle, ecode); queue_destroy(q, NULL); } struct find_by_type_val_data { uint8_t *pdu; uint16_t len; uint16_t mtu; uint8_t ecode; }; static void find_by_type_val_att_cb(struct gatt_db_attribute *attrib, void *user_data) { uint16_t handle, end_handle; struct find_by_type_val_data *data = user_data; if (data->ecode) return; if (data->len + 4 > data->mtu - 1) return; /* * This OP is only valid for Primary Service per the spec * page 562, so this should work. */ gatt_db_attribute_get_service_data(attrib, &handle, &end_handle, NULL, NULL); if (!handle || !end_handle) { data->ecode = BT_ATT_ERROR_UNLIKELY; return; } put_le16(handle, data->pdu + data->len); put_le16(end_handle, data->pdu + data->len + 2); data->len += 4; } static void find_by_type_val_cb(struct bt_att_chan *chan, uint16_t mtu, uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; uint16_t start, end, uuid16; struct find_by_type_val_data data; uint8_t rsp_pdu[mtu]; uint16_t ehandle = 0; bt_uuid_t uuid; if (length < 6) { data.ecode = BT_ATT_ERROR_INVALID_PDU; goto error; } data.pdu = rsp_pdu; data.len = 0; data.mtu = mtu; data.ecode = 0; start = get_le16(pdu); end = get_le16(pdu + 2); uuid16 = get_le16(pdu + 4); DBG(server, "Find By Type Value - start: 0x%04x end: 0x%04x uuid: 0x%04x", start, end, uuid16); ehandle = start; if (start > end) { data.ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } bt_uuid16_create(&uuid, uuid16); gatt_db_find_by_type_value(server->db, start, end, &uuid, pdu + 6, length - 6, find_by_type_val_att_cb, &data); if (!data.len) data.ecode = BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND; if (data.ecode) goto error; bt_att_chan_send_rsp(chan, BT_ATT_OP_FIND_BY_TYPE_RSP, data.pdu, data.len); return; error: bt_att_chan_send_error_rsp(chan, opcode, ehandle, data.ecode); } static void async_write_op_destroy(struct async_write_op *op) { bt_gatt_server_unref(op->server); free(op); } static void write_complete_cb(struct gatt_db_attribute *attr, int err, void *user_data) { struct async_write_op *op = user_data; struct bt_gatt_server *server = op->server; uint16_t handle; if (op->opcode == BT_ATT_OP_WRITE_CMD || op->opcode == BT_ATT_OP_SIGNED_WRITE_CMD) { async_write_op_destroy(op); return; } DBG(server, "Write Complete: err %d", err); handle = gatt_db_attribute_get_handle(attr); if (err) bt_att_chan_send_error_rsp(op->chan, op->opcode, handle, err); else bt_att_chan_send_rsp(op->chan, BT_ATT_OP_WRITE_RSP, NULL, 0); async_write_op_destroy(op); } static uint8_t authorize_req(struct bt_gatt_server *server, uint8_t opcode, uint16_t handle) { if (!server->authorize) return 0; return server->authorize(server->att, opcode, handle, server->authorize_data); } static uint8_t check_length(uint16_t length, uint16_t offset) { if (length > BT_ATT_MAX_VALUE_LEN) return BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; if (offset > BT_ATT_MAX_VALUE_LEN) return BT_ATT_ERROR_INVALID_OFFSET; if (length + offset > BT_ATT_MAX_VALUE_LEN) return BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; return 0; } static void write_cb(struct bt_att_chan *chan, uint16_t mtu, uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; struct gatt_db_attribute *attr; uint16_t handle = 0; struct async_write_op *op = NULL; uint8_t ecode; if (length < 2) { ecode = BT_ATT_ERROR_INVALID_PDU; goto error; } ecode = authorize_req(server, opcode, handle); if (ecode) goto error; handle = get_le16(pdu); attr = gatt_db_get_attribute(server->db, handle); if (!attr) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } DBG(server, "Write %s - handle: 0x%04x", (opcode == BT_ATT_OP_WRITE_REQ) ? "Req" : "Cmd", handle); ecode = check_length(length - 2, 0); if (ecode) goto error; ecode = check_permissions(server, attr, BT_ATT_PERM_WRITE_MASK); if (ecode) goto error; op = new0(struct async_write_op, 1); op->chan = chan; op->server = bt_gatt_server_ref(server); op->opcode = opcode; if (gatt_db_attribute_write(attr, 0, pdu + 2, length - 2, opcode, server->att, write_complete_cb, op)) return; async_write_op_destroy(op); ecode = BT_ATT_ERROR_UNLIKELY; error: if (opcode == BT_ATT_OP_WRITE_CMD) return; bt_att_chan_send_error_rsp(chan, opcode, handle, ecode); } static uint8_t get_read_rsp_opcode(uint8_t opcode) { switch (opcode) { case BT_ATT_OP_READ_REQ: return BT_ATT_OP_READ_RSP; case BT_ATT_OP_READ_BLOB_REQ: return BT_ATT_OP_READ_BLOB_RSP; default: /* * Should never happen * * TODO: It would be nice to have a debug-mode assert macro * for development builds. This way bugs could be easily caught * during development and there would be self documenting code * that wouldn't be crash release builds. */ return 0; } return 0; } static void read_complete_cb(struct gatt_db_attribute *attr, int err, const uint8_t *value, size_t len, void *user_data) { struct async_read_op *op = user_data; struct bt_gatt_server *server = op->server; uint8_t rsp_opcode; uint16_t handle; DBG(server, "Read Complete: err %d", err); handle = gatt_db_attribute_get_handle(attr); if (err) { bt_att_chan_send_error_rsp(op->chan, op->opcode, handle, err); async_read_op_destroy(op); return; } rsp_opcode = get_read_rsp_opcode(op->opcode); bt_att_chan_send_rsp(op->chan, rsp_opcode, len ? value : NULL, MIN((unsigned int) op->mtu - 1, len)); async_read_op_destroy(op); } static void handle_read_req(struct bt_att_chan *chan, struct bt_gatt_server *server, uint16_t mtu, uint8_t opcode, uint16_t handle, uint16_t offset) { struct gatt_db_attribute *attr; uint8_t ecode; struct async_read_op *op = NULL; ecode = authorize_req(server, opcode, handle); if (ecode) goto error; attr = gatt_db_get_attribute(server->db, handle); if (!attr) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } DBG(server, "Read %sReq - handle: 0x%04x", opcode == BT_ATT_OP_READ_BLOB_REQ ? "Blob " : "", handle); ecode = check_permissions(server, attr, BT_ATT_PERM_READ_MASK); if (ecode) goto error; op = new0(struct async_read_op, 1); op->chan = chan; op->opcode = opcode; op->server = bt_gatt_server_ref(server); op->mtu = mtu; if (gatt_db_attribute_read(attr, offset, opcode, server->att, read_complete_cb, op)) return; ecode = BT_ATT_ERROR_UNLIKELY; error: if (op) async_read_op_destroy(op); bt_att_chan_send_error_rsp(chan, opcode, handle, ecode); } static void read_cb(struct bt_att_chan *chan, uint16_t mtu, uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; uint16_t handle; if (length != 2) { bt_att_chan_send_error_rsp(chan, opcode, 0, BT_ATT_ERROR_INVALID_PDU); return; } handle = get_le16(pdu); handle_read_req(chan, server, mtu, opcode, handle, 0); } static void read_blob_cb(struct bt_att_chan *chan, uint16_t mtu, uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; uint16_t handle, offset; if (length != 4) { bt_att_chan_send_error_rsp(chan, opcode, 0, BT_ATT_ERROR_INVALID_PDU); return; } handle = get_le16(pdu); offset = get_le16(pdu + 2); handle_read_req(chan, server, mtu, opcode, handle, offset); } struct read_mult_data { struct bt_att_chan *chan; struct bt_gatt_server *server; uint8_t opcode; uint16_t *handles; size_t cur_handle; size_t num_handles; uint8_t *rsp_data; size_t length; size_t mtu; }; static void read_mult_data_free(struct read_mult_data *data) { free(data->handles); free(data->rsp_data); free(data); } static void read_multiple_complete_cb(struct gatt_db_attribute *attr, int err, const uint8_t *value, size_t len, void *user_data) { struct read_mult_data *data = user_data; struct gatt_db_attribute *next_attr; uint16_t handle = gatt_db_attribute_get_handle(attr); uint8_t ecode; uint16_t length; if (err != 0) { ecode = err; goto error; } length = data->opcode == BT_ATT_OP_READ_MULT_VL_REQ ? MIN(len, MAX(data->mtu - data->length, 3) - 3) : MIN(len, data->mtu - data->length - 1); if (data->opcode == BT_ATT_OP_READ_MULT_VL_REQ) { /* The Length Value Tuple List may be truncated within the first * two octets of a tuple due to the size limits of the current * ATT_MTU, but the first two octets cannot be separated. */ if (data->mtu - data->length >= 3) { put_le16(len, data->rsp_data + data->length); data->length += 2; } } memcpy(data->rsp_data + data->length, value, length); data->length += length; data->cur_handle++; if (data->cur_handle == data->num_handles) { bt_att_chan_send_rsp(data->chan, data->opcode + 1, data->rsp_data, data->length); read_mult_data_free(data); return; } handle = data->handles[data->cur_handle]; util_debug(data->server->debug_callback, data->server->debug_data, "%s Req - #%zu of %zu: 0x%04x", data->opcode == BT_ATT_OP_READ_MULT_REQ ? "Read Multiple" : "Read Multiple Variable Length", data->cur_handle + 1, data->num_handles, handle); next_attr = gatt_db_get_attribute(data->server->db, handle); if (!next_attr) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } ecode = check_permissions(data->server, next_attr, BT_ATT_PERM_READ_MASK); if (ecode) goto error; if (gatt_db_attribute_read(next_attr, 0, data->opcode, data->server->att, read_multiple_complete_cb, data)) return; ecode = BT_ATT_ERROR_UNLIKELY; error: bt_att_chan_send_error_rsp(data->chan, data->opcode, handle, ecode); read_mult_data_free(data); } static struct read_mult_data *read_mult_data_new(struct bt_gatt_server *server, struct bt_att_chan *chan, uint16_t mtu, uint8_t opcode, uint16_t num_handles) { struct read_mult_data *data; data = new0(struct read_mult_data, 1); data->chan = chan; data->opcode = opcode; data->handles = new0(uint16_t, num_handles); data->rsp_data = NULL; data->server = server; data->num_handles = num_handles; data->cur_handle = 0; data->mtu = mtu; data->length = 0; data->rsp_data = new0(uint8_t, data->mtu - 1); return data; } static void read_multiple_cb(struct bt_att_chan *chan, uint16_t mtu, uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; struct gatt_db_attribute *attr; struct read_mult_data *data = NULL; uint8_t ecode; size_t i = 0; uint16_t handle = 0; if (length < 4) { ecode = BT_ATT_ERROR_INVALID_PDU; goto error; } data = read_mult_data_new(server, chan, mtu, opcode, length / 2); for (i = 0; i < data->num_handles; i++) data->handles[i] = get_le16(pdu + i * 2); handle = data->handles[0]; DBG(server, "%s Req - %zu handles, 1st: 0x%04x", data->opcode == BT_ATT_OP_READ_MULT_REQ ? "Read Multiple" : "Read Multiple Variable Length", data->num_handles, handle); attr = gatt_db_get_attribute(server->db, handle); if (!attr) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } ecode = check_permissions(data->server, attr, BT_ATT_PERM_READ_MASK); if (ecode) goto error; if (gatt_db_attribute_read(attr, 0, opcode, server->att, read_multiple_complete_cb, data)) return; ecode = BT_ATT_ERROR_UNLIKELY; error: if (data) read_mult_data_free(data); bt_att_chan_send_error_rsp(chan, opcode, handle, ecode); } static bool append_prep_data(struct prep_write_data *prep_data, uint16_t handle, uint16_t length, uint8_t *value) { uint8_t *val; uint16_t len; if (!length) return true; len = prep_data->length + length; val = realloc(prep_data->value, len); if (!val) return false; memcpy(val + prep_data->length, value, length); prep_data->value = val; prep_data->length = len; return true; } static bool is_reliable_write_supported(const struct bt_gatt_server *server, uint16_t handle) { struct gatt_db_attribute *attr; uint16_t ext_prop; attr = gatt_db_get_attribute(server->db, handle); if (!attr) return false; if (!gatt_db_attribute_get_char_data(attr, NULL, NULL, NULL, &ext_prop, NULL)) return false; return (ext_prop & BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE); } static bool prep_data_new(struct bt_gatt_server *server, uint16_t handle, uint16_t offset, uint16_t length, uint8_t *value) { struct prep_write_data *prep_data; prep_data = new0(struct prep_write_data, 1); if (!append_prep_data(prep_data, handle, length, value)) { prep_write_data_destroy(prep_data); return false; } prep_data->server = server; prep_data->handle = handle; prep_data->offset = offset; /* * Handle is the value handle. We need characteristic declaration * handle which in BlueZ is handle_value -1 */ prep_data->reliable_supported = is_reliable_write_supported(server, handle - 1); queue_push_tail(server->prep_queue, prep_data); return true; } static bool store_prep_data(struct bt_gatt_server *server, uint16_t handle, uint16_t offset, uint16_t length, uint8_t *value) { struct prep_write_data *prep_data = NULL; /* * Now lets check if prep write is a continuation of long write * If so do aggregation of data */ prep_data = queue_peek_tail(server->prep_queue); if (prep_data && (prep_data->handle == handle) && (offset == (prep_data->length + prep_data->offset))) return append_prep_data(prep_data, handle, length, value); return prep_data_new(server, handle, offset, length, value); } struct prep_write_complete_data { struct bt_att_chan *chan; void *pdu; uint16_t length; struct bt_gatt_server *server; }; static void prep_write_complete_cb(struct gatt_db_attribute *attr, int err, void *user_data) { struct prep_write_complete_data *pwcd = user_data; uint16_t handle = 0; uint16_t offset; handle = get_le16(pwcd->pdu); if (err) { bt_att_chan_send_error_rsp(pwcd->chan, BT_ATT_OP_PREP_WRITE_REQ, handle, err); free(pwcd->pdu); free(pwcd); return; } offset = get_le16(pwcd->pdu + 2); if (!store_prep_data(pwcd->server, handle, offset, pwcd->length - 4, &((uint8_t *) pwcd->pdu)[4])) bt_att_chan_send_error_rsp(pwcd->chan, BT_ATT_OP_PREP_WRITE_RSP, handle, BT_ATT_ERROR_INSUFFICIENT_RESOURCES); bt_att_chan_send_rsp(pwcd->chan, BT_ATT_OP_PREP_WRITE_RSP, pwcd->pdu, pwcd->length); free(pwcd->pdu); free(pwcd); } static void prep_write_cb(struct bt_att_chan *chan, uint16_t mtu, uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; uint16_t handle = 0; uint16_t offset; struct gatt_db_attribute *attr; struct prep_write_complete_data *pwcd; uint8_t ecode, status; if (length < 4) { ecode = BT_ATT_ERROR_INVALID_PDU; goto error; } if (queue_length(server->prep_queue) >= server->max_prep_queue_len) { ecode = BT_ATT_ERROR_PREPARE_QUEUE_FULL; goto error; } handle = get_le16(pdu); offset = get_le16(pdu + 2); attr = gatt_db_get_attribute(server->db, handle); if (!attr) { ecode = BT_ATT_ERROR_INVALID_HANDLE; goto error; } DBG(server, "Prep Write Req - handle: 0x%04x", handle); ecode = check_length(length - 4, offset); if (ecode) goto error; ecode = check_permissions(server, attr, BT_ATT_PERM_WRITE_MASK); if (ecode) goto error; pwcd = new0(struct prep_write_complete_data, 1); pwcd->chan = chan; pwcd->pdu = malloc(length); memcpy(pwcd->pdu, pdu, length); pwcd->length = length; pwcd->server = server; status = gatt_db_attribute_write(attr, offset, NULL, 0, BT_ATT_OP_PREP_WRITE_REQ, server->att, prep_write_complete_cb, pwcd); if (status) return; ecode = BT_ATT_ERROR_UNLIKELY; error: bt_att_chan_send_error_rsp(chan, opcode, handle, ecode); } struct exec_data { struct bt_att_chan *chan; struct bt_gatt_server *server; }; static void exec_next_prep_write(struct exec_data *data, uint16_t ehandle, int err); static void exec_write_complete_cb(struct gatt_db_attribute *attr, int err, void *user_data) { struct exec_data *data = user_data; uint16_t handle = gatt_db_attribute_get_handle(attr); exec_next_prep_write(data, handle, err); } static void exec_next_prep_write(struct exec_data *data, uint16_t ehandle, int err) { struct prep_write_data *next = NULL; struct gatt_db_attribute *attr; bool status; if (err) goto error; next = queue_pop_head(data->server->prep_queue); if (!next) { bt_att_chan_send_rsp(data->chan, BT_ATT_OP_EXEC_WRITE_RSP, NULL, 0); free(data); return; } attr = gatt_db_get_attribute(data->server->db, next->handle); if (!attr) { err = BT_ATT_ERROR_UNLIKELY; goto error; } status = gatt_db_attribute_write(attr, next->offset, next->value, next->length, BT_ATT_OP_EXEC_WRITE_REQ, data->server->att, exec_write_complete_cb, data); prep_write_data_destroy(next); if (status) return; err = BT_ATT_ERROR_UNLIKELY; error: queue_remove_all(data->server->prep_queue, NULL, NULL, prep_write_data_destroy); bt_att_chan_send_error_rsp(data->chan, BT_ATT_OP_EXEC_WRITE_REQ, ehandle, err); free(data); } static bool find_no_reliable_characteristic(const void *data, const void *match_data) { const struct prep_write_data *prep_data = data; return !prep_data->reliable_supported; } static void exec_write_cb(struct bt_att_chan *chan, uint16_t mtu, uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; struct exec_data *data; uint8_t flags; uint8_t ecode; bool write; uint16_t ehandle = 0; if (length != 1) { ecode = BT_ATT_ERROR_INVALID_PDU; goto error; } flags = ((uint8_t *) pdu)[0]; DBG(server, "Exec Write Req - flags: 0x%02x", flags); if (flags == 0x00) write = false; else if (flags == 0x01) write = true; else { ecode = BT_ATT_ERROR_INVALID_PDU; goto error; } if (!write) { queue_remove_all(server->prep_queue, NULL, NULL, prep_write_data_destroy); bt_att_chan_send_rsp(chan, BT_ATT_OP_EXEC_WRITE_RSP, NULL, 0); return; } /* If there is more than one prep request, we are in reliable session */ if (queue_length(server->prep_queue) > 1) { struct prep_write_data *prep_data; prep_data = queue_find(server->prep_queue, find_no_reliable_characteristic, NULL); if (prep_data) { ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; ehandle = prep_data->handle; goto error; } } data = new0(struct exec_data, 1); data->chan = chan; data->server = server; exec_next_prep_write(data, 0, 0); return; error: queue_remove_all(server->prep_queue, NULL, NULL, prep_write_data_destroy); bt_att_chan_send_error_rsp(chan, opcode, ehandle, ecode); } static void exchange_mtu_cb(struct bt_att_chan *chan, uint16_t mtu, uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_server *server = user_data; uint16_t client_rx_mtu; uint16_t final_mtu; uint8_t rsp_pdu[2]; if (length != 2) { bt_att_chan_send_error_rsp(chan, opcode, 0, BT_ATT_ERROR_INVALID_PDU); return; } client_rx_mtu = get_le16(pdu); final_mtu = MAX(MIN(client_rx_mtu, server->mtu), BT_ATT_DEFAULT_LE_MTU); /* Respond with the server MTU */ put_le16(server->mtu, rsp_pdu); bt_att_chan_send_rsp(chan, BT_ATT_OP_MTU_RSP, rsp_pdu, 2); /* Set MTU to be the minimum */ server->mtu = final_mtu; bt_att_set_mtu(server->att, final_mtu); DBG(server, "MTU exchange complete, with MTU: %u", final_mtu); } static bool gatt_server_register_att_handlers(struct bt_gatt_server *server) { /* Exchange MTU */ server->mtu_id = bt_att_register(server->att, BT_ATT_OP_MTU_REQ, exchange_mtu_cb, server, NULL); if (!server->mtu_id) return false; /* Read By Group Type */ server->read_by_grp_type_id = bt_att_register(server->att, BT_ATT_OP_READ_BY_GRP_TYPE_REQ, read_by_grp_type_cb, server, NULL); if (!server->read_by_grp_type_id) return false; /* Read By Type */ server->read_by_type_id = bt_att_register(server->att, BT_ATT_OP_READ_BY_TYPE_REQ, read_by_type_cb, server, NULL); if (!server->read_by_type_id) return false; /* Find Information */ server->find_info_id = bt_att_register(server->att, BT_ATT_OP_FIND_INFO_REQ, find_info_cb, server, NULL); if (!server->find_info_id) return false; /* Find By Type Value */ server->find_by_type_value_id = bt_att_register(server->att, BT_ATT_OP_FIND_BY_TYPE_REQ, find_by_type_val_cb, server, NULL); if (!server->find_by_type_value_id) return false; /* Write Request */ server->write_id = bt_att_register(server->att, BT_ATT_OP_WRITE_REQ, write_cb, server, NULL); if (!server->write_id) return false; /* Write Command */ server->write_cmd_id = bt_att_register(server->att, BT_ATT_OP_WRITE_CMD, write_cb, server, NULL); if (!server->write_cmd_id) return false; /* Read Request */ server->read_id = bt_att_register(server->att, BT_ATT_OP_READ_REQ, read_cb, server, NULL); if (!server->read_id) return false; /* Read Blob Request */ server->read_blob_id = bt_att_register(server->att, BT_ATT_OP_READ_BLOB_REQ, read_blob_cb, server, NULL); if (!server->read_blob_id) return false; /* Read Multiple Request */ server->read_multiple_id = bt_att_register(server->att, BT_ATT_OP_READ_MULT_REQ, read_multiple_cb, server, NULL); if (!server->read_multiple_id) return false; /* Read Multiple Variable Length Request */ server->read_multiple_vl_id = bt_att_register(server->att, BT_ATT_OP_READ_MULT_VL_REQ, read_multiple_cb, server, NULL); if (!server->read_multiple_vl_id) return false; /* Prepare Write Request */ server->prep_write_id = bt_att_register(server->att, BT_ATT_OP_PREP_WRITE_REQ, prep_write_cb, server, NULL); if (!server->prep_write_id) return false; /* Execute Write Request */ server->exec_write_id = bt_att_register(server->att, BT_ATT_OP_EXEC_WRITE_REQ, exec_write_cb, server, NULL); if (!server->exec_write_id) return NULL; /* Signed Write Command */ server->signed_write_cmd_id = bt_att_register(server->att, BT_ATT_OP_SIGNED_WRITE_CMD, write_cb, server, NULL); if (!server->signed_write_cmd_id) return false; return true; } struct bt_gatt_server *bt_gatt_server_new(struct gatt_db *db, struct bt_att *att, uint16_t mtu, uint8_t min_enc_size) { struct bt_gatt_server *server; if (!att || !db) return NULL; server = new0(struct bt_gatt_server, 1); server->db = gatt_db_ref(db); server->att = bt_att_ref(att); server->mtu = MAX(mtu, BT_ATT_DEFAULT_LE_MTU); server->perms = true; server->max_prep_queue_len = DEFAULT_MAX_PREP_QUEUE_LEN; server->prep_queue = queue_new(); server->min_enc_size = min_enc_size; if (!gatt_server_register_att_handlers(server)) { bt_gatt_server_free(server); return NULL; } return bt_gatt_server_ref(server); } uint16_t bt_gatt_server_get_mtu(struct bt_gatt_server *server) { if (!server || !server->att) return 0; return bt_att_get_mtu(server->att); } struct bt_att *bt_gatt_server_get_att(struct bt_gatt_server *server) { if (!server) return NULL; return server->att; } void bt_gatt_server_set_permissions(struct bt_gatt_server *server, bool value) { if (!server) return; server->perms = value; } struct bt_gatt_server *bt_gatt_server_ref(struct bt_gatt_server *server) { if (!server) return NULL; __sync_fetch_and_add(&server->ref_count, 1); return server; } void bt_gatt_server_unref(struct bt_gatt_server *server) { if (!server) return; if (__sync_sub_and_fetch(&server->ref_count, 1)) return; bt_gatt_server_free(server); } bool bt_gatt_server_set_debug(struct bt_gatt_server *server, bt_gatt_server_debug_func_t callback, void *user_data, bt_gatt_server_destroy_func_t destroy) { if (!server) return false; if (server->debug_destroy) server->debug_destroy(server->debug_data); server->debug_callback = callback; server->debug_destroy = destroy; server->debug_data = user_data; return true; } static void notify_multiple_timeout_remove(struct bt_gatt_server *server) { if (!server->nfy_mult->id) return; timeout_remove(server->nfy_mult->id); server->nfy_mult->id = 0; } static bool notify_multiple(void *user_data) { struct bt_gatt_server *server = user_data; server->nfy_mult->id = 0; bt_att_send(server->att, BT_ATT_OP_HANDLE_NFY_MULT, server->nfy_mult->pdu, server->nfy_mult->offset, NULL, NULL, NULL); notify_multiple_free(server); return false; } static bool notify_append_le16(struct nfy_mult_data *data, uint16_t value) { if (data->offset + sizeof(value) > data->len) return false; put_le16(value, data->pdu + data->offset); data->offset += sizeof(value); return true; } bool bt_gatt_server_send_notification(struct bt_gatt_server *server, uint16_t handle, const uint8_t *value, uint16_t length, bool multiple) { struct nfy_mult_data *data = NULL; bool result; if (!server || (length && !value)) return false; if (multiple) { data = server->nfy_mult; /* flush buffered data if this request hits buffer size limit */ if (data && data->offset > 0 && data->len - data->offset < 4 + length) { notify_multiple_timeout_remove(server); notify_multiple(server); /* data has been freed by notify_multiple */ data = NULL; } } if (!data) { data = new0(struct nfy_mult_data, 1); data->len = bt_att_get_mtu(server->att) - 1; data->pdu = malloc(data->len); } if (!notify_append_le16(data, handle)) goto error; if (multiple) { length = MIN(data->len - data->offset - 2, length); if (!notify_append_le16(data, length)) goto error; } else { length = MIN(data->len - data->offset, length); } if (value) memcpy(data->pdu + data->offset, value, length); data->offset += length; if (multiple) { if (!server->nfy_mult) server->nfy_mult = data; if (!server->nfy_mult->id) server->nfy_mult->id = timeout_add(NFY_MULT_TIMEOUT, notify_multiple, server, NULL); return true; } result = !!bt_att_send(server->att, BT_ATT_OP_HANDLE_NFY, data->pdu, data->offset, NULL, NULL, NULL); free(data->pdu); free(data); return result; error: if (data) { free(data->pdu); free(data); } return false; } struct ind_data { bt_gatt_server_conf_func_t callback; bt_gatt_server_destroy_func_t destroy; void *user_data; }; static void destroy_ind_data(void *user_data) { struct ind_data *data = user_data; if (data->destroy) data->destroy(data->user_data); free(data); } static void conf_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct ind_data *data = user_data; if (data->callback) data->callback(data->user_data); } bool bt_gatt_server_send_indication(struct bt_gatt_server *server, uint16_t handle, const uint8_t *value, uint16_t length, bt_gatt_server_conf_func_t callback, void *user_data, bt_gatt_server_destroy_func_t destroy) { uint16_t pdu_len; uint8_t *pdu; struct ind_data *data; bool result; if (!server || (length && !value)) return false; pdu_len = MIN(bt_att_get_mtu(server->att) - 1, length + 2); pdu = malloc(pdu_len); if (!pdu) return false; data = new0(struct ind_data, 1); data->callback = callback; data->destroy = destroy; data->user_data = user_data; put_le16(handle, pdu); memcpy(pdu + 2, value, pdu_len - 2); result = !!bt_att_send(server->att, BT_ATT_OP_HANDLE_IND, pdu, pdu_len, conf_cb, data, destroy_ind_data); if (!result) destroy_ind_data(data); free(pdu); return result; } bool bt_gatt_server_set_authorize(struct bt_gatt_server *server, bt_gatt_server_authorize_cb_t cb, void *user_data) { if (!server) return false; server->authorize = cb; server->authorize_data = user_data; return true; } bluez-5.87/src/shared/PaxHeaders/vcp.c0000644000000000000000000000005015142177620014651 xustar0020 atime=1783092950 20 ctime=1783094100 bluez-5.87/src/shared/vcp.c0000644000000000000000000021315515142177620014341 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. * */ #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "src/shared/queue.h" #include "src/shared/util.h" #include "src/shared/timeout.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-server.h" #include "src/shared/gatt-client.h" #include "src/shared/vcp.h" #define DBG(_vcp, fmt, arg...) \ vcp_debug(_vcp, "%s:%s() " fmt, __FILE__, __func__, ## arg) #define VCP_STEP_SIZE 1 #define VCP_CLIENT_OP_TIMEOUT 2000 #define VOCS_VOL_OFFSET_UPPER_LIMIT 255 #define VOCS_VOL_OFFSET_LOWER_LIMIT -255 /* Application Error Code */ #define BT_ATT_ERROR_INVALID_CHANGE_COUNTER 0x80 #define BT_ATT_ERROR_OPCODE_NOT_SUPPORTED 0x81 #define BT_ATT_ERROR_VALUE_OUT_OF_RANGE 0x82 #define BT_ATT_AICS_ERROR_VALUE_OUT_OF_RANGE 0x83 #define BT_ATT_AICS_ERROR_MUTE_DISABLED 0x82 #define BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED 0x84 #define BT_VCP_NA BIT(0) #define BT_VCP_FRONT_LEFT BIT(1) #define BT_VCP_FRONT_RIGHT BIT(2) #define BT_VCP_FRONT_CENTER BIT(3) #define BT_VCP_LOW_FRQ_EFF_1 BIT(4) #define BT_VCP_BACK_LEFT BIT(5) #define BT_VCP_BACK_RIGHT BIT(6) #define BT_VCP_FRONT_LEFT_CENTER BIT(7) #define BT_VCP_FRONT_RIGHT_CENTER BIT(8) #define BT_VCP_BACK_CENTER BIT(9) #define BT_VCP_LOW_FRQ_EFF_2 BIT(10) #define BT_VCP_SIDE_LEFT BIT(11) #define BT_VCP_SIDE_RIGHT BIT(12) #define BT_VCP_TOP_FRONT_LEFT BIT(13) #define BT_VCP_TOP_FRONT_RIGHT BIT(14) #define BT_VCP_TOP_FRONT_CENTER BIT(15) #define BT_VCP_TOP_CENTER BIT(16) #define BT_VCP_TOP_BACK_LEFT BIT(17) #define BT_VCP_TOP_BACK_RIGHT BIT(18) #define BT_VCP_TOP_SIDE_LEFT BIT(19) #define BT_VCP_TOP_SIDE_RIGHT BIT(20) #define BT_VCP_TOP_BACK_CENTER BIT(21) #define BT_VCP_BOTTOM_FRONT_CENTER BIT(22) #define BT_VCP_BOTTOM_FRONT_LEFT BIT(23) #define BT_VCP_BOTTOM_FRONT_RIGHT BIT(24) #define BT_VCP_FRONT_LEFT_WIDE BIT(25) #define BT_VCP_FRONT_RIGHT_WIDE BIT(26) #define BT_VCP_LEFT_SURROUND BIT(27) #define BT_VCP_RIGHT_SURROUND BIT(28) #define VCS_TOTAL_NUM_HANDLES 11 #define AICS_TOTAL_NUM_HANDLES 16 /* AICS Audio Input Type Values */ #define AICS_AUD_IP_TYPE_UNSPECIFIED 0x00 #define AICS_AUD_IP_TYPE_BLUETOOTH 0x01 #define AICS_AUD_IP_TYPE_MICROPHONE 0x02 #define AICS_AUD_IP_TYPE_ANALOG 0x03 #define AICS_AUD_IP_TYPE_DIGITAL 0x04 #define AICS_AUD_IP_TYPE_RADIO 0x05 #define AICS_AUD_IP_TYPE_STREAMING 0x06 #define AICS_AUD_IP_TYPE_AMBIENT 0x07 /* AICS Audio Input Status Values */ #define AICS_AUD_IP_STATUS_INACTIVE 0x00 #define AICS_AUD_IP_STATUS_ACTIVE 0x01 /* AICS Audio Input Control Point Opcodes */ #define BT_AICS_SET_GAIN_SETTING 0x01 #define BT_AICS_UNMUTE 0x02 #define BT_AICS_MUTE 0x03 #define BT_AICS_SET_MANUAL_GAIN_MODE 0x04 #define BT_AICS_SET_AUTO_GAIN_MODE 0x05 /* AICS Gain Mode Field Value */ #define AICS_GAIN_MODE_MANUAL_ONLY 0x00 #define AICS_GAIN_MODE_AUTO_ONLY 0x01 #define AICS_GAIN_MODE_MANUAL 0x02 #define AICS_GAIN_MODE_AUTO 0x03 /* AICS Mute Field Values */ #define AICS_NOT_MUTED 0x00 #define AICS_MUTED 0x01 #define AICS_DISABLED 0x02 #define AICS_GAIN_SETTING_UNITS 1 #define AICS_GAIN_SETTING_MAX_VALUE 127 #define AICS_GAIN_SETTING_MIN_VALUE -128 #define AICS_GAIN_SETTING_DEFAULT_VALUE 88 struct bt_vcp_db { struct gatt_db *db; struct bt_vcs *vcs; struct bt_vocs *vocs; struct bt_aics *aics; }; typedef void (*vcp_func_t)(struct bt_vcp *vcp, bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data); struct bt_vcp_pending { unsigned int id; struct bt_vcp *vcp; vcp_func_t func; void *user_data; }; struct bt_vcs_param { uint8_t op; uint8_t change_counter; } __packed; struct bt_vocs_param { uint8_t op; uint8_t change_counter; } __packed; struct bt_vcs_ab_vol { uint8_t change_counter; uint8_t vol_set; } __packed; struct bt_vcs_client_ab_vol { uint8_t op; uint8_t change_counter; uint8_t vol_set; } __packed; struct bt_vocs_set_vol_off { uint8_t change_counter; int16_t set_vol_offset; } __packed; struct bt_vcp_cb { unsigned int id; bt_vcp_func_t attached; bt_vcp_func_t detached; void *user_data; }; typedef void (*vcp_notify_t)(struct bt_vcp *vcp, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data); struct bt_vcp_notify { unsigned int id; struct bt_vcp *vcp; vcp_notify_t func; void *user_data; }; struct bt_vcp_client_op { uint8_t volume; bool resend; bool wait_reply; bool wait_notify; unsigned int timeout_id; }; struct bt_vcp { int ref_count; struct bt_vcp_db *ldb; struct bt_vcp_db *rdb; struct bt_gatt_client *client; struct bt_att *att; unsigned int vstate_id; unsigned int vflag_id; unsigned int state_id; unsigned int audio_loc_id; unsigned int ao_dec_id; unsigned int aics_ip_state_id; unsigned int aics_ip_status_id; unsigned int aics_ip_descr_id; unsigned int idle_id; bt_vcp_func_t ready_func; void *ready_data; struct queue *notify; struct queue *pending; bt_vcp_debug_func_t debug_func; bt_vcp_destroy_func_t debug_destroy; bt_vcp_volume_func_t volume_changed; uint8_t volume; uint8_t volume_counter; struct bt_vcp_client_op pending_op; void *debug_data; void *user_data; }; #define RESET_VOLUME_SETTING 0x00 #define USERSET_VOLUME_SETTING 0x01 /* Contains local bt_vcp_db */ struct vol_state { uint8_t vol_set; uint8_t mute; uint8_t counter; } __packed; struct bt_vcs { struct bt_vcp_db *vdb; struct vol_state *vstate; uint8_t vol_flag; struct gatt_db_attribute *service; struct gatt_db_attribute *vs; struct gatt_db_attribute *vs_ccc; struct gatt_db_attribute *vol_cp; struct gatt_db_attribute *vf; struct gatt_db_attribute *vf_ccc; }; /* Contains local bt_vcp_db */ struct vol_offset_state { int16_t vol_offset; uint8_t counter; } __packed; struct bt_vocs { struct bt_vcp_db *vdb; struct vol_offset_state *vostate; uint32_t vocs_audio_loc; char *vocs_ao_dec; struct gatt_db_attribute *service; struct gatt_db_attribute *vos; struct gatt_db_attribute *vos_ccc; struct gatt_db_attribute *voal; struct gatt_db_attribute *voal_ccc; struct gatt_db_attribute *vo_cp; struct gatt_db_attribute *voaodec; struct gatt_db_attribute *voaodec_ccc; }; struct aud_ip_st { int8_t gain_setting; uint8_t mute; uint8_t gain_mode; uint8_t chg_counter; } __packed; struct gain_setting_prop { uint8_t gain_setting_units; int8_t gain_setting_min; int8_t gain_setting_max; } __packed; struct bt_aics_set_gain_setting { uint8_t change_counter; int8_t gain_setting; } __packed; struct bt_aics { struct bt_vcp_db *vdb; struct aud_ip_st *aud_ipst; struct gain_setting_prop *gain_settingprop; uint8_t aud_input_type; uint8_t aud_input_status; char *aud_input_descr; struct gatt_db_attribute *service; struct gatt_db_attribute *aud_ip_state; struct gatt_db_attribute *aud_ip_state_ccc; struct gatt_db_attribute *gain_stting_prop; struct gatt_db_attribute *aud_ip_type; struct gatt_db_attribute *aud_ip_status; struct gatt_db_attribute *aud_ip_status_ccc; struct gatt_db_attribute *aud_ip_cp; struct gatt_db_attribute *aud_ip_dscrptn; struct gatt_db_attribute *aud_ip_dscrptn_ccc; }; static struct queue *vcp_db; static struct queue *vcp_cbs; static struct queue *sessions; static char *iov_pull_string(struct iovec *iov) { char *res; if (!iov) return NULL; res = malloc(iov->iov_len + 1); if (!res) return NULL; if (iov->iov_len) memcpy(res, iov->iov_base, iov->iov_len); res[iov->iov_len] = 0; util_iov_pull(iov, iov->iov_len); return res; } static struct bt_vcp_db *vcp_get_vdb(struct bt_vcp *vcp) { if (!vcp) return NULL; if (vcp->ldb) return vcp->ldb; return NULL; } static struct vol_state *vdb_get_vstate(struct bt_vcp_db *vdb) { if (!vdb->vcs) return NULL; if (vdb->vcs->vstate) return vdb->vcs->vstate; return NULL; } static struct vol_offset_state *vdb_get_vostate(struct bt_vcp_db *vdb) { if (!vdb->vocs) return NULL; if (vdb->vocs->vostate) return vdb->vocs->vostate; return NULL; } static struct bt_vcs *vcp_get_vcs(struct bt_vcp *vcp) { if (!vcp) return NULL; if (vcp->rdb->vcs) return vcp->rdb->vcs; vcp->rdb->vcs = new0(struct bt_vcs, 1); vcp->rdb->vcs->vdb = vcp->rdb; return vcp->rdb->vcs; } static struct bt_vocs *vcp_get_vocs(struct bt_vcp *vcp) { if (!vcp) return NULL; if (vcp->rdb->vocs) return vcp->rdb->vocs; vcp->rdb->vocs = new0(struct bt_vocs, 1); vcp->rdb->vocs->vdb = vcp->rdb; return vcp->rdb->vocs; } static struct bt_aics *vcp_get_aics(struct bt_vcp *vcp) { if (!vcp) return NULL; if (vcp->rdb->aics) return vcp->rdb->aics; vcp->rdb->aics = new0(struct bt_aics, 1); vcp->rdb->aics->vdb = vcp->rdb; return vcp->rdb->aics; } static void vcp_remote_client_attached(void *data, void *user_data) { struct bt_vcp_cb *cb = data; struct bt_vcp *vcp = user_data; cb->attached(vcp, cb->user_data); } static void vcp_remote_client_detached(void *data, void *user_data) { struct bt_vcp_cb *cb = data; struct bt_vcp *vcp = user_data; cb->detached(vcp, cb->user_data); } static void vcp_client_op_clear(struct bt_vcp_client_op *op) { if (op->timeout_id) timeout_remove(op->timeout_id); memset(op, 0, sizeof(*op)); } void bt_vcp_detach(struct bt_vcp *vcp) { if (!queue_remove(sessions, vcp)) return; if (vcp->idle_id) { bt_gatt_client_idle_unregister(vcp->client, vcp->idle_id); vcp->idle_id = 0; } if (vcp->client) { bt_gatt_client_unref(vcp->client); vcp->client = NULL; } vcp_client_op_clear(&vcp->pending_op); } static void vcp_db_free(void *data) { struct bt_vcp_db *vdb = data; if (!vdb) return; gatt_db_unref(vdb->db); free(vdb->vcs); free(vdb->vocs); free(vdb->aics); free(vdb); } static void vcp_free(void *data) { struct bt_vcp *vcp = data; bt_vcp_detach(vcp); vcp_db_free(vcp->rdb); queue_destroy(vcp->pending, NULL); free(vcp); } bool bt_vcp_set_user_data(struct bt_vcp *vcp, void *user_data) { if (!vcp) return false; vcp->user_data = user_data; return true; } static bool vcp_db_match(const void *data, const void *match_data) { const struct bt_vcp_db *vdb = data; const struct gatt_db *db = match_data; return (vdb->db == db); } struct bt_att *bt_vcp_get_att(struct bt_vcp *vcp) { if (!vcp) return NULL; if (vcp->att) return vcp->att; return bt_gatt_client_get_att(vcp->client); } struct bt_vcp *bt_vcp_ref(struct bt_vcp *vcp) { if (!vcp) return NULL; __sync_fetch_and_add(&vcp->ref_count, 1); return vcp; } void bt_vcp_unref(struct bt_vcp *vcp) { if (!vcp) return; if (__sync_sub_and_fetch(&vcp->ref_count, 1)) return; vcp_free(vcp); } static void vcp_debug(struct bt_vcp *vcp, const char *format, ...) { va_list ap; if (!vcp || !format || !vcp->debug_func) return; va_start(ap, format); util_debug_va(vcp->debug_func, vcp->debug_data, format, ap); va_end(ap); } static void vcp_disconnected(int err, void *user_data) { /* called only when this device is acting a a server */ struct bt_vcp *vcp = user_data; DBG(vcp, "vcp %p disconnected err %d", vcp, err); bt_vcp_detach(vcp); queue_foreach(vcp_cbs, vcp_remote_client_detached, vcp); } static struct bt_vcp *vcp_get_session(struct bt_att *att, struct gatt_db *db) { const struct queue_entry *entry; struct bt_vcp *vcp; for (entry = queue_get_entries(sessions); entry; entry = entry->next) { struct bt_vcp *vcp = entry->data; if (att == bt_vcp_get_att(vcp)) return vcp; } /* called only when this device is acting a a server */ vcp = bt_vcp_new(db, NULL); vcp->att = att; queue_foreach(vcp_cbs, vcp_remote_client_attached, vcp); bt_att_register_disconnect(att, vcp_disconnected, vcp, NULL); if (!sessions) sessions = queue_new(); queue_push_tail(sessions, vcp); return vcp; } static uint8_t vcs_rel_vol_down(struct bt_vcs *vcs, struct bt_vcp *vcp, struct iovec *iov) { struct bt_vcp_db *vdb; struct vol_state *vstate; uint8_t *change_counter; DBG(vcp, "Volume Down"); vdb = vcp_get_vdb(vcp); if (!vdb) { DBG(vcp, "error: VDB not available"); return 0; } vstate = vdb_get_vstate(vdb); if (!vstate) { DBG(vcp, "error: VSTATE not available"); return 0; } change_counter = util_iov_pull_mem(iov, sizeof(*change_counter)); if (!change_counter) return 0; if (*change_counter != vstate->counter) { DBG(vcp, "Change Counter Mismatch Volume not decremented!"); return BT_ATT_ERROR_INVALID_CHANGE_COUNTER; } vstate->vol_set = MAX((vstate->vol_set - VCP_STEP_SIZE), 0); vstate->counter = -~vstate->counter; /*Increment Change Counter*/ vcp->volume = vstate->vol_set; if (vcp->volume_changed) vcp->volume_changed(vcp, vcp->volume); gatt_db_attribute_notify(vdb->vcs->vs, (void *)vstate, sizeof(struct vol_state), bt_vcp_get_att(vcp)); return 0; } static uint8_t vcs_rel_vol_up(struct bt_vcs *vcs, struct bt_vcp *vcp, struct iovec *iov) { struct bt_vcp_db *vdb; struct vol_state *vstate; uint8_t *change_counter; DBG(vcp, "Volume Up"); vdb = vcp_get_vdb(vcp); if (!vdb) { DBG(vcp, "error: VDB not available"); return 0; } vstate = vdb_get_vstate(vdb); if (!vstate) { DBG(vcp, "error: VCP database not available"); return 0; } change_counter = util_iov_pull_mem(iov, sizeof(*change_counter)); if (!change_counter) return 0; if (*change_counter != vstate->counter) { DBG(vcp, "Change Counter Mismatch Volume not decremented!"); return BT_ATT_ERROR_INVALID_CHANGE_COUNTER; } vstate->vol_set = MIN((vstate->vol_set + VCP_STEP_SIZE), 255); vstate->counter = -~vstate->counter; /*Increment Change Counter*/ vcp->volume = vstate->vol_set; if (vcp->volume_changed) vcp->volume_changed(vcp, vcp->volume); gatt_db_attribute_notify(vdb->vcs->vs, (void *)vstate, sizeof(struct vol_state), bt_vcp_get_att(vcp)); return 0; } static uint8_t vcs_unmute_rel_vol_down(struct bt_vcs *vcs, struct bt_vcp *vcp, struct iovec *iov) { struct bt_vcp_db *vdb; struct vol_state *vstate; uint8_t *change_counter; DBG(vcp, "Un Mute and Volume Down"); vdb = vcp_get_vdb(vcp); if (!vdb) { DBG(vcp, "error: VDB not available"); return 0; } vstate = vdb_get_vstate(vdb); if (!vstate) { DBG(vcp, "error: VCP database not available"); return 0; } change_counter = util_iov_pull_mem(iov, sizeof(*change_counter)); if (!change_counter) return 0; if (*change_counter != vstate->counter) { DBG(vcp, "Change Counter Mismatch Volume not decremented!"); return BT_ATT_ERROR_INVALID_CHANGE_COUNTER; } vstate->mute = 0x00; vstate->vol_set = MAX((vstate->vol_set - VCP_STEP_SIZE), 0); vstate->counter = -~vstate->counter; /*Increment Change Counter*/ vcp->volume = vstate->vol_set; if (vcp->volume_changed) vcp->volume_changed(vcp, vcp->volume); gatt_db_attribute_notify(vdb->vcs->vs, (void *)vstate, sizeof(struct vol_state), bt_vcp_get_att(vcp)); return 0; } static uint8_t vcs_unmute_rel_vol_up(struct bt_vcs *vcs, struct bt_vcp *vcp, struct iovec *iov) { struct bt_vcp_db *vdb; struct vol_state *vstate; uint8_t *change_counter; DBG(vcp, "UN Mute and Volume Up"); vdb = vcp_get_vdb(vcp); if (!vdb) { DBG(vcp, "error: VDB not available"); return 0; } vstate = vdb_get_vstate(vdb); if (!vstate) { DBG(vcp, "error: VSTATE not available"); return 0; } change_counter = util_iov_pull_mem(iov, sizeof(*change_counter)); if (!change_counter) return 0; if (*change_counter != vstate->counter) { DBG(vcp, "Change Counter Mismatch Volume not decremented!"); return BT_ATT_ERROR_INVALID_CHANGE_COUNTER; } vstate->mute = 0x00; vstate->vol_set = MIN((vstate->vol_set + VCP_STEP_SIZE), 255); vstate->counter = -~vstate->counter; /*Increment Change Counter*/ vcp->volume = vstate->vol_set; if (vcp->volume_changed) vcp->volume_changed(vcp, vcp->volume); gatt_db_attribute_notify(vdb->vcs->vs, (void *)vstate, sizeof(struct vol_state), bt_vcp_get_att(vcp)); return 0; } static uint8_t vcs_set_absolute_vol(struct bt_vcs *vcs, struct bt_vcp *vcp, struct iovec *iov) { struct bt_vcp_db *vdb; struct vol_state *vstate; struct bt_vcs_ab_vol *req; DBG(vcp, "Set Absolute Volume"); vdb = vcp_get_vdb(vcp); if (!vdb) { DBG(vcp, "error: VDB not available"); return 0; } vstate = vdb_get_vstate(vdb); if (!vstate) { DBG(vcp, "error: VSTATE not available"); return 0; } req = util_iov_pull_mem(iov, sizeof(*req)); if (!req) return 0; if (req->change_counter != vstate->counter) { DBG(vcp, "Change Counter Mismatch Volume not decremented!"); return BT_ATT_ERROR_INVALID_CHANGE_COUNTER; } vstate->vol_set = req->vol_set; vstate->counter = -~vstate->counter; /*Increment Change Counter*/ vcp->volume = vstate->vol_set; if (vcp->volume_changed) vcp->volume_changed(vcp, vcp->volume); gatt_db_attribute_notify(vdb->vcs->vs, (void *)vstate, sizeof(struct vol_state), bt_vcp_get_att(vcp)); return 0; } static uint8_t vcs_unmute(struct bt_vcs *vcs, struct bt_vcp *vcp, struct iovec *iov) { struct bt_vcp_db *vdb; struct vol_state *vstate; uint8_t *change_counter; DBG(vcp, "Un Mute"); vdb = vcp_get_vdb(vcp); if (!vdb) { DBG(vcp, "error: VDB not available"); return 0; } vstate = vdb_get_vstate(vdb); if (!vstate) { DBG(vcp, "error: VSTATE not available"); return 0; } change_counter = util_iov_pull_mem(iov, sizeof(*change_counter)); if (!change_counter) return 0; if (*change_counter != vstate->counter) { DBG(vcp, "Change Counter Mismatch Volume not decremented!"); return BT_ATT_ERROR_INVALID_CHANGE_COUNTER; } vstate->mute = 0x00; vstate->counter = -~vstate->counter; /*Increment Change Counter*/ gatt_db_attribute_notify(vdb->vcs->vs, (void *)vstate, sizeof(struct vol_state), bt_vcp_get_att(vcp)); return 0; } static uint8_t vcs_mute(struct bt_vcs *vcs, struct bt_vcp *vcp, struct iovec *iov) { struct bt_vcp_db *vdb; struct vol_state *vstate; uint8_t *change_counter; DBG(vcp, "MUTE"); vdb = vcp_get_vdb(vcp); if (!vdb) { DBG(vcp, "error: VDB not available"); return 0; } vstate = vdb_get_vstate(vdb); if (!vstate) { DBG(vcp, "error: VSTATE not available"); return 0; } change_counter = util_iov_pull_mem(iov, sizeof(*change_counter)); if (!change_counter) return 0; if (*change_counter != vstate->counter) { DBG(vcp, "Change Counter Mismatch Volume not decremented!"); return BT_ATT_ERROR_INVALID_CHANGE_COUNTER; } vstate->mute = 0x01; vstate->counter = -~vstate->counter; /*Increment Change Counter*/ return 0; } static uint8_t vocs_set_vol_offset(struct bt_vocs *vocs, struct bt_vcp *vcp, struct iovec *iov) { struct bt_vcp_db *vdb; struct vol_offset_state *vstate, state; struct bt_vocs_set_vol_off *req; DBG(vcp, "Set Volume Offset"); vdb = vcp_get_vdb(vcp); if (!vdb) { DBG(vcp, "error: VDB not available"); return 0; } vstate = vdb_get_vostate(vdb); if (!vstate) { DBG(vcp, "error: VSTATE not available"); return 0; } req = util_iov_pull_mem(iov, sizeof(*req)); if (!req) return 0; if (req->change_counter != vstate->counter) { DBG(vcp, "Change Counter Mismatch Volume not decremented!"); return BT_ATT_ERROR_INVALID_CHANGE_COUNTER; } vstate->vol_offset = le16_to_cpu(req->set_vol_offset); if (vstate->vol_offset > VOCS_VOL_OFFSET_UPPER_LIMIT || vstate->vol_offset < VOCS_VOL_OFFSET_LOWER_LIMIT) { DBG(vcp, "error: Value Out of Range"); return BT_ATT_ERROR_VALUE_OUT_OF_RANGE; } /* Increment Change Counter */ vstate->counter = -~vstate->counter; /* Notify change */ state.vol_offset = req->set_vol_offset; state.counter = vstate->counter; gatt_db_attribute_notify(vdb->vocs->vos, (void *)&state, sizeof(state), bt_vcp_get_att(vcp)); return 0; } #define BT_VCS_REL_VOL_DOWN 0x00 #define BT_VCS_REL_VOL_UP 0x01 #define BT_VCS_UNMUTE_REL_VOL_DOWN 0x02 #define BT_VCS_UNMUTE_REL_VOL_UP 0x03 #define BT_VCS_SET_ABSOLUTE_VOL 0x04 #define BT_VCS_UNMUTE 0x05 #define BT_VCS_MUTE 0x06 #define BT_VOCS_SET_VOL_OFFSET 0x01 #define VCS_OP(_str, _op, _size, _func) \ { \ .str = _str, \ .op = _op, \ .size = _size, \ .func = _func, \ } struct vcs_op_handler { const char *str; uint8_t op; size_t size; uint8_t (*func)(struct bt_vcs *vcs, struct bt_vcp *vcp, struct iovec *iov); } vcp_handlers[] = { VCS_OP("Relative Volume Down", BT_VCS_REL_VOL_DOWN, sizeof(uint8_t), vcs_rel_vol_down), VCS_OP("Relative Volume Up", BT_VCS_REL_VOL_UP, sizeof(uint8_t), vcs_rel_vol_up), VCS_OP("Unmute - Relative Volume Down", BT_VCS_UNMUTE_REL_VOL_DOWN, sizeof(uint8_t), vcs_unmute_rel_vol_down), VCS_OP("Unmute - Relative Volume Up", BT_VCS_UNMUTE_REL_VOL_UP, sizeof(uint8_t), vcs_unmute_rel_vol_up), VCS_OP("Set Absolute Volume", BT_VCS_SET_ABSOLUTE_VOL, sizeof(struct bt_vcs_ab_vol), vcs_set_absolute_vol), VCS_OP("UnMute", BT_VCS_UNMUTE, sizeof(uint8_t), vcs_unmute), VCS_OP("Mute", BT_VCS_MUTE, sizeof(uint8_t), vcs_mute), {} }; #define VOCS_OP(_str, _op, _size, _func) \ { \ .str = _str, \ .op = _op, \ .size = _size, \ .func = _func, \ } struct vocs_op_handler { const char *str; uint8_t op; size_t size; uint8_t (*func)(struct bt_vocs *vocs, struct bt_vcp *vcp, struct iovec *iov); } vocp_handlers[] = { VOCS_OP("Set Volume Offset", BT_VOCS_SET_VOL_OFFSET, sizeof(uint8_t), vocs_set_vol_offset), {} }; static void vcs_cp_write(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_vcs *vcs = user_data; struct bt_vcp *vcp = vcp_get_session(att, vcs->vdb->db); struct iovec iov = { .iov_base = (void *) value, .iov_len = len, }; uint8_t *vcp_op; struct vcs_op_handler *handler; uint8_t ret = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; DBG(vcp, "VCP Control Point Write"); if (offset) { DBG(vcp, "invalid offset %d", offset); ret = BT_ATT_ERROR_INVALID_OFFSET; goto respond; } if (len < sizeof(*vcp_op)) { DBG(vcp, "invalid len %ld < %ld sizeof(*param)", len, sizeof(*vcp_op)); ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; goto respond; } vcp_op = util_iov_pull_mem(&iov, sizeof(*vcp_op)); if (!vcp_op) { DBG(vcp, "util_iov_pull_mem() returned NULL"); goto respond; } for (handler = vcp_handlers; handler && handler->str; handler++) { if (handler->op != *vcp_op) continue; if (iov.iov_len < handler->size) { DBG(vcp, "invalid len %ld < %ld handler->size", len, handler->size); ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED; goto respond; } break; } if (handler && handler->str) { DBG(vcp, "%s", handler->str); ret = handler->func(vcs, vcp, &iov); } else { DBG(vcp, "Unknown opcode 0x%02x", *vcp_op); ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED; } respond: gatt_db_attribute_write_result(attrib, id, ret); } static void vocs_cp_write(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_vocs *vocs = user_data; struct bt_vcp *vcp = vcp_get_session(att, vocs->vdb->db); struct iovec iov = { .iov_base = (void *) value, .iov_len = len, }; uint8_t *vcp_op; struct vocs_op_handler *handler; uint8_t ret = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; DBG(vcp, "VOCP Control Point Write"); if (offset) { DBG(vcp, "invalid offset %d", offset); ret = BT_ATT_ERROR_INVALID_OFFSET; goto respond; } if (len < sizeof(*vcp_op)) { DBG(vcp, "invalid len %ld < %ld sizeof(*param)", len, sizeof(*vcp_op)); ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; goto respond; } vcp_op = util_iov_pull_mem(&iov, sizeof(*vcp_op)); if (!vcp_op) { DBG(vcp, "util_iov_pull_mem() returned NULL"); goto respond; } for (handler = vocp_handlers; handler && handler->str; handler++) { if (handler->op != *vcp_op) continue; if (iov.iov_len < handler->size) { DBG(vcp, "invalid len %ld < %ld handler->size", len, handler->size); ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED; goto respond; } break; } if (handler && handler->str) { DBG(vcp, "%s", handler->str); ret = handler->func(vocs, vcp, &iov); } else { DBG(vcp, "Unknown opcode 0x%02x", *vcp_op); ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED; } respond: gatt_db_attribute_write_result(attrib, id, ret); } static void vcs_state_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_vcs *vcs = user_data; struct iovec iov; iov.iov_base = vcs->vstate; iov.iov_len = sizeof(*vcs->vstate); gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, iov.iov_len); } static void vocs_state_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_vocs *vocs = user_data; struct vol_offset_state state; state.vol_offset = cpu_to_le16(vocs->vostate->vol_offset); state.counter = vocs->vostate->counter; gatt_db_attribute_read_result(attrib, id, 0, (void *)&state, sizeof(state)); } static void vcs_flag_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_vcs *vcs = user_data; struct iovec iov; iov.iov_base = &vcs->vol_flag; iov.iov_len = sizeof(vcs->vol_flag); gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, iov.iov_len); } static void vocs_voal_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_vocs *vocs = user_data; uint32_t loc; loc = cpu_to_le32(vocs->vocs_audio_loc); gatt_db_attribute_read_result(attrib, id, 0, (void *)&loc, sizeof(loc)); } static void vocs_voaodec_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_vocs *vocs = user_data; struct iovec iov; iov.iov_base = vocs->vocs_ao_dec; iov.iov_len = strlen(vocs->vocs_ao_dec); gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, iov.iov_len); } static void aics_input_state_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_aics *aics = user_data; struct iovec iov; iov.iov_base = aics->aud_ipst; iov.iov_len = sizeof(*aics->aud_ipst); gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, iov.iov_len); } static void aics_gain_setting_prop_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_aics *aics = user_data; struct iovec iov; iov.iov_base = aics->gain_settingprop; iov.iov_len = sizeof(*aics->gain_settingprop); gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, iov.iov_len); } static void aics_audio_input_type_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_aics *aics = user_data; struct iovec iov; iov.iov_base = &aics->aud_input_type; iov.iov_len = sizeof(aics->aud_input_type); gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, iov.iov_len); } static void aics_input_status_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_aics *aics = user_data; struct iovec iov; iov.iov_base = &aics->aud_input_status; iov.iov_len = sizeof(aics->aud_input_status); gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, iov.iov_len); } static struct aud_ip_st *vdb_get_audipst(struct bt_vcp_db *vdb) { if (!vdb->aics) return NULL; if (vdb->aics->aud_ipst) return vdb->aics->aud_ipst; return NULL; } static struct gain_setting_prop *vdb_get_gainsettingprop( struct bt_vcp_db *vdb) { if (!vdb->aics) return NULL; if (vdb->aics->gain_settingprop) return vdb->aics->gain_settingprop; return NULL; } static uint8_t aics_set_gain_setting(struct bt_aics *aics, struct bt_vcp *vcp, struct iovec *iov) { struct bt_vcp_db *vdb; struct aud_ip_st *audipst; struct bt_aics_set_gain_setting *req; struct gain_setting_prop *gainsettngprop; uint8_t ret = 1; vdb = vcp_get_vdb(vcp); if (!vdb) { DBG(vcp, "error: VDB not available"); ret = 0; goto respond; } audipst = vdb_get_audipst(vdb); if (!audipst) { DBG(vcp, "error: Audio Input State value is not available"); ret = 0; goto respond; } req = util_iov_pull_mem(iov, sizeof(*req)); if (!req) { ret = 0; goto respond; } if (req->change_counter != audipst->chg_counter) { DBG(vcp, "Change Counter Mismatch Audio Input State!"); ret = BT_ATT_ERROR_INVALID_CHANGE_COUNTER; goto respond; } if (audipst->gain_mode != AICS_GAIN_MODE_MANUAL_ONLY && audipst->gain_mode != AICS_GAIN_MODE_MANUAL) { DBG(vcp, "Gain Mode is not Manual only or Manual"); ret = BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED; goto respond; } gainsettngprop = vdb_get_gainsettingprop(vdb); if (req->gain_setting > gainsettngprop->gain_setting_max || req->gain_setting < gainsettngprop->gain_setting_min) { DBG(vcp, "error: Value Out of Range"); ret = BT_ATT_AICS_ERROR_VALUE_OUT_OF_RANGE; goto respond; } audipst->gain_setting = req->gain_setting; /*Increment Change Counter*/ audipst->chg_counter = -~audipst->chg_counter; gatt_db_attribute_notify(vdb->aics->aud_ip_state, (void *)audipst, sizeof(struct aud_ip_st), bt_vcp_get_att(vcp)); ret = 0; respond: return ret; } static uint8_t aics_unmute(struct bt_aics *aics, struct bt_vcp *vcp, struct iovec *iov) { struct bt_vcp_db *vdb; struct aud_ip_st *audipst; uint8_t *change_counter; uint8_t ret = 1; vdb = vcp_get_vdb(vcp); if (!vdb) { DBG(vcp, "error: VDB not available"); ret = 0; goto respond; } audipst = vdb_get_audipst(vdb); if (!audipst) { DBG(vcp, "error: Audio Input State value is not available"); ret = 0; goto respond; } change_counter = util_iov_pull_mem(iov, sizeof(*change_counter)); if (!change_counter) { ret = 0; goto respond; } if (*change_counter != audipst->chg_counter) { DBG(vcp, "Change Counter Mismatch Audio Input State!"); ret = BT_ATT_ERROR_INVALID_CHANGE_COUNTER; goto respond; } if (audipst->mute == AICS_DISABLED) { DBG(vcp, "Mute state is Disabled!"); ret = BT_ATT_AICS_ERROR_MUTE_DISABLED; goto respond; } audipst->mute = AICS_NOT_MUTED; /*Increment Change Counter*/ audipst->chg_counter = -~audipst->chg_counter; gatt_db_attribute_notify(vdb->aics->aud_ip_state, (void *)audipst, sizeof(struct aud_ip_st), bt_vcp_get_att(vcp)); ret = 0; respond: return ret; } static uint8_t aics_mute(struct bt_aics *aics, struct bt_vcp *vcp, struct iovec *iov) { struct bt_vcp_db *vdb; struct aud_ip_st *audipst; uint8_t *change_counter; uint8_t ret = 1; vdb = vcp_get_vdb(vcp); if (!vdb) { DBG(vcp, "error: VDB not available"); ret = 0; goto respond; } audipst = vdb_get_audipst(vdb); if (!audipst) { DBG(vcp, "error: Audio Input State value is not available"); ret = 0; goto respond; } change_counter = util_iov_pull_mem(iov, sizeof(*change_counter)); if (!change_counter) { ret = 0; goto respond; } if (*change_counter != audipst->chg_counter) { DBG(vcp, "Change Counter Mismatch Audio Input State!"); ret = BT_ATT_ERROR_INVALID_CHANGE_COUNTER; goto respond; } if (audipst->mute == AICS_DISABLED) { DBG(vcp, "Mute state is Disabled!"); ret = BT_ATT_AICS_ERROR_MUTE_DISABLED; goto respond; } audipst->mute = AICS_MUTED; /*Increment Change Counter*/ audipst->chg_counter = -~audipst->chg_counter; gatt_db_attribute_notify(vdb->aics->aud_ip_state, (void *)audipst, sizeof(struct aud_ip_st), bt_vcp_get_att(vcp)); ret = 0; respond: return ret; } static uint8_t aics_set_manual_gain_mode(struct bt_aics *aics, struct bt_vcp *vcp, struct iovec *iov) { struct bt_vcp_db *vdb; struct aud_ip_st *audipst; uint8_t *change_counter; uint8_t ret = 1; vdb = vcp_get_vdb(vcp); if (!vdb) { DBG(vcp, "error: VDB not available"); ret = 0; goto respond; } audipst = vdb_get_audipst(vdb); if (!audipst) { DBG(vcp, "error: Audio Input State value is not available"); ret = 0; goto respond; } change_counter = util_iov_pull_mem(iov, sizeof(*change_counter)); if (!change_counter) { ret = 0; goto respond; } if (*change_counter != audipst->chg_counter) { DBG(vcp, "Change Counter Mismatch Audio Input State!"); ret = BT_ATT_ERROR_INVALID_CHANGE_COUNTER; goto respond; } if (audipst->gain_mode == AICS_GAIN_MODE_AUTO_ONLY || audipst->gain_mode == AICS_GAIN_MODE_MANUAL_ONLY) { DBG(vcp, "error!! gain mode is Automatic only or Manual only"); ret = BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED; goto respond; } if (audipst->gain_mode == AICS_GAIN_MODE_AUTO) { audipst->gain_mode = AICS_GAIN_MODE_MANUAL; /*Increment Change Counter*/ audipst->chg_counter = -~audipst->chg_counter; gatt_db_attribute_notify(vdb->aics->aud_ip_state, (void *)audipst, sizeof(struct aud_ip_st), bt_vcp_get_att(vcp)); ret = 0; } else { DBG(vcp, "error!! Gain mode field value not Automatic"); ret = BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED; } respond: return ret; } static uint8_t aics_set_auto_gain_mode(struct bt_aics *aics, struct bt_vcp *vcp, struct iovec *iov) { struct bt_vcp_db *vdb; struct aud_ip_st *audipst; uint8_t *change_counter; uint8_t ret = 1; vdb = vcp_get_vdb(vcp); if (!vdb) { DBG(vcp, "error: VDB not available"); ret = 0; goto respond; } audipst = vdb_get_audipst(vdb); if (!audipst) { DBG(vcp, "error: Audio Input State value is not available"); ret = 0; goto respond; } change_counter = util_iov_pull_mem(iov, sizeof(*change_counter)); if (!change_counter) { ret = 0; goto respond; } if (*change_counter != audipst->chg_counter) { DBG(vcp, "Change Counter Mismatch Audio Input State!"); ret = BT_ATT_ERROR_INVALID_CHANGE_COUNTER; goto respond; } if (audipst->gain_mode == AICS_GAIN_MODE_AUTO_ONLY || audipst->gain_mode == AICS_GAIN_MODE_MANUAL_ONLY) { DBG(vcp, "error!! gain mode is Automatic only or Manual only"); ret = BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED; goto respond; } if (audipst->gain_mode == AICS_GAIN_MODE_MANUAL) { audipst->gain_mode = AICS_GAIN_MODE_AUTO; /*Increment Change Counter*/ audipst->chg_counter = -~audipst->chg_counter; gatt_db_attribute_notify(vdb->aics->aud_ip_state, (void *)audipst, sizeof(struct aud_ip_st), bt_vcp_get_att(vcp)); ret = 0; } else { DBG(vcp, "error!! Gain mode field value is not Manual"); ret = BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED; } respond: return ret; } #define AICS_OP(_str, _op, _size, _func) \ { \ .str = _str, \ .op = _op, \ .size = _size, \ .func = _func, \ } struct aics_op_handler { const char *str; uint8_t op; size_t size; uint8_t (*func)(struct bt_aics *aics, struct bt_vcp *vcp, struct iovec *iov); } aics_handlers[] = { AICS_OP("Set Gain Setting", BT_AICS_SET_GAIN_SETTING, sizeof(struct bt_aics_set_gain_setting), aics_set_gain_setting), AICS_OP("Unmute", BT_AICS_UNMUTE, sizeof(uint8_t), aics_unmute), AICS_OP("Mute", BT_AICS_MUTE, sizeof(uint8_t), aics_mute), AICS_OP("Set Manual Gain Mode", BT_AICS_SET_MANUAL_GAIN_MODE, sizeof(uint8_t), aics_set_manual_gain_mode), AICS_OP("Set Automatic Gain Mode", BT_AICS_SET_AUTO_GAIN_MODE, sizeof(uint8_t), aics_set_auto_gain_mode), {} }; static void aics_ip_cp_write(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_aics *aics = user_data; struct bt_vcp *vcp = vcp_get_session(att, aics->vdb->db); struct iovec iov = { .iov_base = (void *) value, .iov_len = len, }; uint8_t *aics_op; struct aics_op_handler *handler; uint8_t ret = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; DBG(vcp, "AICS Control Point Write"); if (offset) { DBG(vcp, "invalid offset %d", offset); ret = BT_ATT_ERROR_INVALID_OFFSET; goto respond; } if (len < sizeof(*aics_op)) { DBG(vcp, "invalid len %ld < %ld sizeof(*param)", len, sizeof(*aics_op)); ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; goto respond; } aics_op = util_iov_pull_mem(&iov, sizeof(*aics_op)); if (!aics_op) { DBG(vcp, "util_iov_pull_mem() returned NULL"); goto respond; } for (handler = aics_handlers; handler && handler->str; handler++) { if (handler->op != *aics_op) continue; if (iov.iov_len < handler->size) { DBG(vcp, "invalid len %ld < %ld handler->size", len, handler->size); ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED; goto respond; } break; } if (handler && handler->str) { DBG(vcp, "%s", handler->str); ret = handler->func(aics, vcp, &iov); } else { DBG(vcp, "Unknown opcode 0x%02x", *aics_op); ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED; } respond: gatt_db_attribute_write_result(attrib, id, ret); } static void aics_input_descr_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_aics *aics = user_data; struct iovec iov; iov.iov_base = aics->aud_input_descr; iov.iov_len = strlen(aics->aud_input_descr); gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, iov.iov_len); } static void aics_input_descr_write(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { /* TODO : AICS optional feature */ } static struct bt_vcs *vcs_new(struct gatt_db *db, struct bt_vcp_db *vdb) { struct bt_vcs *vcs; struct vol_state *vstate; bt_uuid_t uuid; if (!db) return NULL; vcs = new0(struct bt_vcs, 1); vstate = new0(struct vol_state, 1); vcs->vstate = vstate; vcs->vol_flag = USERSET_VOLUME_SETTING; /* Populate DB with VCS attributes */ bt_uuid16_create(&uuid, VCS_UUID); vcs->service = gatt_db_add_service(db, &uuid, true, VCS_TOTAL_NUM_HANDLES); gatt_db_service_add_included(vcs->service, vdb->vocs->service); gatt_db_service_set_active(vdb->vocs->service, true); gatt_db_service_add_included(vcs->service, vdb->aics->service); gatt_db_service_set_active(vdb->aics->service, true); bt_uuid16_create(&uuid, VOL_STATE_CHRC_UUID); vcs->vs = gatt_db_service_add_characteristic(vcs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, vcs_state_read, NULL, vcs); vcs->vs_ccc = gatt_db_service_add_ccc(vcs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, VOL_CP_CHRC_UUID); vcs->vol_cp = gatt_db_service_add_characteristic(vcs->service, &uuid, BT_ATT_PERM_WRITE, BT_GATT_CHRC_PROP_WRITE, NULL, vcs_cp_write, vcs); bt_uuid16_create(&uuid, VOL_FLAG_CHRC_UUID); vcs->vf = gatt_db_service_add_characteristic(vcs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, vcs_flag_read, NULL, vcs); vcs->vf_ccc = gatt_db_service_add_ccc(vcs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); gatt_db_service_set_active(vcs->service, true); return vcs; } static struct bt_vocs *vocs_new(struct gatt_db *db) { struct bt_vocs *vocs; struct vol_offset_state *vostate; bt_uuid_t uuid; if (!db) return NULL; vocs = new0(struct bt_vocs, 1); vostate = new0(struct vol_offset_state, 1); vocs->vostate = vostate; vocs->vocs_audio_loc = BT_VCP_FRONT_LEFT; vocs->vocs_ao_dec = "Left Speaker"; /* Populate DB with VOCS attributes */ bt_uuid16_create(&uuid, VOL_OFFSET_CS_UUID); vocs->service = gatt_db_add_service(db, &uuid, false, 12); bt_uuid16_create(&uuid, VOCS_STATE_CHAR_UUID); vocs->vos = gatt_db_service_add_characteristic(vocs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, vocs_state_read, NULL, vocs); vocs->vos_ccc = gatt_db_service_add_ccc(vocs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, VOCS_AUDIO_LOC_CHRC_UUID); vocs->voal = gatt_db_service_add_characteristic(vocs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, vocs_voal_read, NULL, vocs); vocs->voal_ccc = gatt_db_service_add_ccc(vocs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, VOCS_CP_CHRC_UUID); vocs->vo_cp = gatt_db_service_add_characteristic(vocs->service, &uuid, BT_ATT_PERM_WRITE, BT_GATT_CHRC_PROP_WRITE, NULL, vocs_cp_write, vocs); bt_uuid16_create(&uuid, VOCS_AUDIO_OP_DESC_CHAR_UUID); vocs->voaodec = gatt_db_service_add_characteristic(vocs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, vocs_voaodec_read, NULL, vocs); vocs->voaodec_ccc = gatt_db_service_add_ccc(vocs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); return vocs; } static struct bt_aics *aics_new(struct gatt_db *db) { struct bt_aics *aics; struct aud_ip_st *aics_aud_ip_st; struct gain_setting_prop *aics_gain_settng_prop; char *ip_descr; char ip_descr_str[] = "Blueooth"; bt_uuid_t uuid; if (!db) return NULL; aics = new0(struct bt_aics, 1); aics_aud_ip_st = new0(struct aud_ip_st, 1); aics_gain_settng_prop = new0(struct gain_setting_prop, 1); ip_descr = malloc(256); memset(ip_descr, 0, 256); aics_aud_ip_st->mute = AICS_NOT_MUTED; aics_aud_ip_st->gain_mode = AICS_GAIN_MODE_MANUAL; aics_aud_ip_st->gain_setting = AICS_GAIN_SETTING_DEFAULT_VALUE; aics->aud_ipst = aics_aud_ip_st; aics_gain_settng_prop->gain_setting_units = AICS_GAIN_SETTING_UNITS; aics_gain_settng_prop->gain_setting_max = AICS_GAIN_SETTING_MAX_VALUE; aics_gain_settng_prop->gain_setting_min = AICS_GAIN_SETTING_MIN_VALUE; aics->gain_settingprop = aics_gain_settng_prop; aics->aud_input_type = AICS_AUD_IP_TYPE_BLUETOOTH; aics->aud_input_status = AICS_AUD_IP_STATUS_ACTIVE; memcpy(ip_descr, ip_descr_str, strlen(ip_descr_str)); aics->aud_input_descr = ip_descr; /* Populate DB with AICS attributes */ bt_uuid16_create(&uuid, AUDIO_INPUT_CS_UUID); aics->service = gatt_db_add_service(db, &uuid, false, AICS_TOTAL_NUM_HANDLES); bt_uuid16_create(&uuid, AICS_INPUT_STATE_CHAR_UUID); aics->aud_ip_state = gatt_db_service_add_characteristic(aics->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, aics_input_state_read, NULL, aics); aics->aud_ip_state_ccc = gatt_db_service_add_ccc(aics->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, AICS_GAIN_SETTING_PROP_CHAR_UUID); aics->gain_stting_prop = gatt_db_service_add_characteristic( aics->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, aics_gain_setting_prop_read, NULL, aics); bt_uuid16_create(&uuid, AICS_AUDIO_INPUT_TYPE_CHAR_UUID); aics->aud_ip_type = gatt_db_service_add_characteristic(aics->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, aics_audio_input_type_read, NULL, aics); bt_uuid16_create(&uuid, AICS_INPUT_STATUS_CHAR_UUID); aics->aud_ip_status = gatt_db_service_add_characteristic(aics->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, aics_input_status_read, NULL, aics); aics->aud_ip_status_ccc = gatt_db_service_add_ccc(aics->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, AICS_AUDIO_INPUT_CP_CHRC_UUID); aics->aud_ip_cp = gatt_db_service_add_characteristic(aics->service, &uuid, BT_ATT_PERM_WRITE, BT_GATT_CHRC_PROP_WRITE, NULL, aics_ip_cp_write, aics); bt_uuid16_create(&uuid, AICS_INPUT_DESCR_CHAR_UUID); aics->aud_ip_dscrptn = gatt_db_service_add_characteristic(aics->service, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP | BT_GATT_CHRC_PROP_NOTIFY, aics_input_descr_read, aics_input_descr_write, aics); aics->aud_ip_dscrptn_ccc = gatt_db_service_add_ccc(aics->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); return aics; } static struct bt_vcp_db *vcp_db_new(struct gatt_db *db) { struct bt_vcp_db *vdb; if (!db) return NULL; vdb = new0(struct bt_vcp_db, 1); vdb->db = gatt_db_ref(db); if (!vcp_db) vcp_db = queue_new(); vdb->vocs = vocs_new(db); vdb->vocs->vdb = vdb; vdb->aics = aics_new(db); vdb->aics->vdb = vdb; vdb->vcs = vcs_new(db, vdb); vdb->vcs->vdb = vdb; queue_push_tail(vcp_db, vdb); return vdb; } static struct bt_vcp_db *vcp_get_db(struct gatt_db *db) { struct bt_vcp_db *vdb; vdb = queue_find(vcp_db, vcp_db_match, db); if (vdb) return vdb; return vcp_db_new(db); } void bt_vcp_add_db(struct gatt_db *db) { vcp_db_new(db); } bool bt_vcp_set_debug(struct bt_vcp *vcp, bt_vcp_debug_func_t func, void *user_data, bt_vcp_destroy_func_t destroy) { if (!vcp) return false; if (vcp->debug_destroy) vcp->debug_destroy(vcp->debug_data); vcp->debug_func = func; vcp->debug_destroy = destroy; vcp->debug_data = user_data; return true; } bool bt_vcp_set_volume_callback(struct bt_vcp *vcp, bt_vcp_volume_func_t volume_changed) { if (!vcp) return false; vcp->volume_changed = volume_changed; return true; } unsigned int bt_vcp_register(bt_vcp_func_t attached, bt_vcp_func_t detached, void *user_data) { struct bt_vcp_cb *cb; static unsigned int id; if (!attached && !detached) return 0; if (!vcp_cbs) vcp_cbs = queue_new(); cb = new0(struct bt_vcp_cb, 1); cb->id = ++id ? id : ++id; cb->attached = attached; cb->detached = detached; cb->user_data = user_data; queue_push_tail(vcp_cbs, cb); return cb->id; } static bool match_id(const void *data, const void *match_data) { const struct bt_vcp_cb *cb = data; unsigned int id = PTR_TO_UINT(match_data); return (cb->id == id); } bool bt_vcp_unregister(unsigned int id) { struct bt_vcp_cb *cb; cb = queue_remove_if(vcp_cbs, match_id, UINT_TO_PTR(id)); if (!cb) return false; free(cb); return true; } struct bt_vcp *bt_vcp_new(struct gatt_db *ldb, struct gatt_db *rdb) { struct bt_vcp *vcp; struct bt_vcp_db *vdb; if (!ldb) return NULL; vdb = vcp_get_db(ldb); if (!vdb) return NULL; vcp = new0(struct bt_vcp, 1); vcp->ldb = vdb; vcp->pending = queue_new(); if (!rdb) goto done; vdb = new0(struct bt_vcp_db, 1); vdb->db = gatt_db_ref(rdb); vcp->rdb = vdb; done: bt_vcp_ref(vcp); return vcp; } static void vcp_set_volume_complete(struct bt_vcp *vcp) { bool resend = vcp->pending_op.resend; uint8_t volume = vcp->pending_op.volume; vcp_client_op_clear(&vcp->pending_op); /* If there were more volume set ops while waiting for the one that * completes, send request to set volume to the latest pending value. */ if (resend) { DBG(vcp, "set pending volume 0x%x", volume); bt_vcp_set_volume(vcp, volume); } } static void vcp_vstate_notify(struct bt_vcp *vcp, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; struct vol_state *vstate; vstate = util_iov_pull_mem(&iov, sizeof(*vstate)); if (!vstate) { DBG(vcp, "Invalid Vol State"); return; } DBG(vcp, "Vol Settings 0x%x", vstate->vol_set); DBG(vcp, "Mute Status 0x%x", vstate->mute); DBG(vcp, "Vol Counter 0x%x", vstate->counter); vcp->volume = vstate->vol_set; vcp->volume_counter = vstate->counter; if (vcp->volume_changed) vcp->volume_changed(vcp, vcp->volume); vcp->pending_op.wait_notify = false; if (!vcp->pending_op.wait_reply) vcp_set_volume_complete(vcp); } static void vcp_volume_cp_sent(bool success, uint8_t err, void *user_data) { struct bt_vcp *vcp = user_data; if (!success) { if (err == BT_ATT_ERROR_INVALID_CHANGE_COUNTER) DBG(vcp, "setting volume failed: invalid counter"); else DBG(vcp, "setting volume failed: error 0x%x", err); vcp_set_volume_complete(vcp); } else { vcp->pending_op.wait_reply = false; if (!vcp->pending_op.wait_notify) vcp_set_volume_complete(vcp); } } static bool vcp_set_volume_timeout(void *data) { struct bt_vcp *vcp = data; DBG(vcp, "setting volume: timeout"); vcp->pending_op.timeout_id = 0; vcp_set_volume_complete(vcp); return false; } static bool vcp_set_volume_client(struct bt_vcp *vcp, uint8_t volume) { struct bt_vcs_client_ab_vol req; uint16_t value_handle; struct bt_vcs *vcs = vcp_get_vcs(vcp); if (!vcs) { DBG(vcp, "error: vcs not available"); return false; } if (!vcs->vol_cp) { DBG(vcp, "error: vol_cp characteristics not available"); return false; } if (!gatt_db_attribute_get_char_data(vcs->vol_cp, NULL, &value_handle, NULL, NULL, NULL)) { DBG(vcp, "error: vol_cp characteristics not available"); return false; } /* If there is another set volume op in flight, just update the wanted * pending volume value. Req with the latest volume value is sent after * the current one completes. This may skip over some volume changes, * as it only sends a request for the final value. */ if (vcp->pending_op.timeout_id) { vcp->pending_op.volume = volume; vcp->pending_op.resend = true; return true; } else if (vcp->volume == volume) { /* Do not set to current value, as that doesn't generate * a notification */ return true; } req.op = BT_VCS_SET_ABSOLUTE_VOL; req.vol_set = volume; req.change_counter = vcp->volume_counter; if (!bt_gatt_client_write_value(vcp->client, value_handle, (void *)&req, sizeof(req), vcp_volume_cp_sent, vcp, NULL)) { DBG(vcp, "error writing volume"); return false; } vcp->pending_op.timeout_id = timeout_add(VCP_CLIENT_OP_TIMEOUT, vcp_set_volume_timeout, vcp, NULL); vcp->pending_op.wait_notify = true; vcp->pending_op.wait_reply = true; return true; } static bool vcp_set_volume_server(struct bt_vcp *vcp, uint8_t volume) { struct bt_vcp_db *vdb = vcp_get_vdb(vcp); struct vol_state *vstate; vcp->volume = volume; if (!vdb) { DBG(vcp, "error: VDB not available"); return false; } vstate = vdb_get_vstate(vdb); if (!vstate) { DBG(vcp, "error: VSTATE not available"); return false; } vstate->vol_set = vcp->volume; vstate->counter = -~vstate->counter; /*Increment Change Counter*/ gatt_db_attribute_notify(vdb->vcs->vs, (void *) vstate, sizeof(struct vol_state), bt_vcp_get_att(vcp)); return true; } bool bt_vcp_set_volume(struct bt_vcp *vcp, uint8_t volume) { if (vcp->client) return vcp_set_volume_client(vcp, volume); else return vcp_set_volume_server(vcp, volume); } uint8_t bt_vcp_get_volume(struct bt_vcp *vcp) { return vcp->volume; } static void vcp_voffset_state_notify(struct bt_vcp *vcp, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; struct vol_offset_state *vostate; vostate = util_iov_pull_mem(&iov, sizeof(*vostate)); if (!vostate) { DBG(vcp, "Invalid Vol Offset State"); return; } DBG(vcp, "Vol Offset 0x%x", vostate->vol_offset); DBG(vcp, "Vol Offset Counter 0x%x", vostate->counter); } static void vcp_audio_loc_notify(struct bt_vcp *vcp, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; uint32_t audio_loc; if (!util_iov_pull_le32(&iov, &audio_loc)) { DBG(vcp, "Invalid VOCS Audio Location"); return; } DBG(vcp, "VOCS Audio Location 0x%x", audio_loc); } static void vcp_audio_descriptor_notify(struct bt_vcp *vcp, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; char *vocs_audio_dec; vocs_audio_dec = iov_pull_string(&iov); if (!vocs_audio_dec) return; DBG(vcp, "VOCS Audio Descriptor 0x%s", vocs_audio_dec); free(vocs_audio_dec); } static void vcp_vflag_notify(struct bt_vcp *vcp, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; uint8_t vflag; if (!util_iov_pull_u8(&iov, &vflag)) { DBG(vcp, "Invalid Vol Flag"); return; } DBG(vcp, "Vol Flag 0x%x", vflag); } static void read_vol_flag(struct bt_vcp *vcp, bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *) value, .iov_len = length }; uint8_t vol_flag; if (!success) { DBG(vcp, "Unable to read Vol Flag: error 0x%02x", att_ecode); return; } if (!util_iov_pull_u8(&iov, &vol_flag)) { DBG(vcp, "Unable to get Vol Flag"); return; } DBG(vcp, "Vol Flag:%x", vol_flag); } static void read_vol_state(struct bt_vcp *vcp, bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct vol_state *vs; struct iovec iov = { .iov_base = (void *) value, .iov_len = length, }; if (!success) { DBG(vcp, "Unable to read Vol State: error 0x%02x", att_ecode); return; } vs = util_iov_pull_mem(&iov, sizeof(*vs)); if (!vs) { DBG(vcp, "Unable to get Vol State"); return; } DBG(vcp, "Vol Set:%x", vs->vol_set); DBG(vcp, "Vol Mute:%x", vs->mute); DBG(vcp, "Vol Counter:%x", vs->counter); vcp->volume = vs->vol_set; vcp->volume_counter = vs->counter; } static void read_vol_offset_state(struct bt_vcp *vcp, bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct vol_offset_state *vos; struct iovec iov = { .iov_base = (void *) value, .iov_len = length, }; if (!success) { DBG(vcp, "Unable to read Vol Offset State: error 0x%02x", att_ecode); return; } vos = util_iov_pull_mem(&iov, sizeof(*vos)); if (!vos) { DBG(vcp, "Unable to get Vol Offset State"); return; } DBG(vcp, "Vol Offset: 0x%04x", le16_to_cpu(vos->vol_offset)); DBG(vcp, "Vol Counter: 0x%02x", vos->counter); } static void read_vocs_audio_location(struct bt_vcp *vcp, bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; uint32_t vocs_audio_loc; if (!success) { DBG(vcp, "Unable to read VOCS Audio Location: error 0x%02x", att_ecode); return; } if (!util_iov_pull_le32(&iov, &vocs_audio_loc)) { DBG(vcp, "Invalid size for VOCS Audio Location"); return; } DBG(vcp, "VOCS Audio Loc: 0x%8x", vocs_audio_loc); } static void read_vocs_audio_descriptor(struct bt_vcp *vcp, bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; char *vocs_ao_dec; if (!success) { DBG(vcp, "Unable to read VOCS Audio Descriptor: error 0x%02x", att_ecode); return; } vocs_ao_dec = iov_pull_string(&iov); if (!vocs_ao_dec) return; DBG(vcp, "VOCS Audio Descriptor: %s", vocs_ao_dec); free(vocs_ao_dec); } static void vcp_pending_destroy(void *data) { struct bt_vcp_pending *pending = data; struct bt_vcp *vcp = pending->vcp; if (queue_remove_if(vcp->pending, NULL, pending)) free(pending); } static void vcp_pending_complete(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_vcp_pending *pending = user_data; if (pending->func) pending->func(pending->vcp, success, att_ecode, value, length, pending->user_data); } static void vcp_read_value(struct bt_vcp *vcp, uint16_t value_handle, vcp_func_t func, void *user_data) { struct bt_vcp_pending *pending; pending = new0(struct bt_vcp_pending, 1); pending->vcp = vcp; pending->func = func; pending->user_data = user_data; pending->id = bt_gatt_client_read_value(vcp->client, value_handle, vcp_pending_complete, pending, vcp_pending_destroy); if (!pending->id) { DBG(vcp, "Unable to send Read request"); free(pending); return; } queue_push_tail(vcp->pending, pending); } static void vcp_register(uint16_t att_ecode, void *user_data) { struct bt_vcp_notify *notify = user_data; if (att_ecode) DBG(notify->vcp, "VCP register failed: 0x%04x", att_ecode); } static void vcp_notify(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct bt_vcp_notify *notify = user_data; if (notify->func) notify->func(notify->vcp, value_handle, value, length, notify->user_data); } static void vcp_notify_destroy(void *data) { struct bt_vcp_notify *notify = data; struct bt_vcp *vcp = notify->vcp; if (queue_remove_if(vcp->notify, NULL, notify)) free(notify); } static unsigned int vcp_register_notify(struct bt_vcp *vcp, uint16_t value_handle, vcp_notify_t func, void *user_data) { struct bt_vcp_notify *notify; notify = new0(struct bt_vcp_notify, 1); notify->vcp = vcp; notify->func = func; notify->user_data = user_data; notify->id = bt_gatt_client_register_notify(vcp->client, value_handle, vcp_register, vcp_notify, notify, vcp_notify_destroy); if (!notify->id) { DBG(vcp, "Unable to register for notifications"); free(notify); return 0; } queue_push_tail(vcp->notify, notify); return notify->id; } static void foreach_vcs_char(struct gatt_db_attribute *attr, void *user_data) { struct bt_vcp *vcp = user_data; uint16_t value_handle; bt_uuid_t uuid, uuid_vstate, uuid_cp, uuid_vflag; struct bt_vcs *vcs; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) return; bt_uuid16_create(&uuid_vstate, VOL_STATE_CHRC_UUID); bt_uuid16_create(&uuid_cp, VOL_CP_CHRC_UUID); bt_uuid16_create(&uuid_vflag, VOL_FLAG_CHRC_UUID); if (!bt_uuid_cmp(&uuid, &uuid_vstate)) { DBG(vcp, "VCS Vol state found: handle 0x%04x", value_handle); vcs = vcp_get_vcs(vcp); if (!vcs) return; vcs->vs = attr; vcp_read_value(vcp, value_handle, read_vol_state, vcp); vcp->vstate_id = vcp_register_notify(vcp, value_handle, vcp_vstate_notify, NULL); return; } if (!bt_uuid_cmp(&uuid, &uuid_cp)) { DBG(vcp, "VCS Volume CP found: handle 0x%04x", value_handle); vcs = vcp_get_vcs(vcp); if (!vcs) return; vcs->vol_cp = attr; return; } if (!bt_uuid_cmp(&uuid, &uuid_vflag)) { DBG(vcp, "VCS Vol Flag found: handle 0x%04x", value_handle); vcs = vcp_get_vcs(vcp); if (!vcs) return; vcs->vf = attr; vcp_read_value(vcp, value_handle, read_vol_flag, vcp); vcp->vflag_id = vcp_register_notify(vcp, value_handle, vcp_vflag_notify, NULL); } } static void foreach_vocs_char(struct gatt_db_attribute *attr, void *user_data) { struct bt_vcp *vcp = user_data; uint16_t value_handle; bt_uuid_t uuid, uuid_vostate, uuid_audio_loc, uuid_vo_cp, uuid_audio_op_decs; struct bt_vocs *vocs; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) return; bt_uuid16_create(&uuid_vostate, VOCS_STATE_CHAR_UUID); bt_uuid16_create(&uuid_audio_loc, VOCS_AUDIO_LOC_CHRC_UUID); bt_uuid16_create(&uuid_vo_cp, VOCS_CP_CHRC_UUID); bt_uuid16_create(&uuid_audio_op_decs, VOCS_AUDIO_OP_DESC_CHAR_UUID); if (!bt_uuid_cmp(&uuid, &uuid_vostate)) { DBG(vcp, "VOCS Vol state found: handle 0x%04x", value_handle); vocs = vcp_get_vocs(vcp); if (!vocs || vocs->vos) return; vocs->vos = attr; vcp_read_value(vcp, value_handle, read_vol_offset_state, vcp); vcp->state_id = vcp_register_notify(vcp, value_handle, vcp_voffset_state_notify, NULL); return; } if (!bt_uuid_cmp(&uuid, &uuid_audio_loc)) { DBG(vcp, "VOCS Volume Audio Location found: handle 0x%04x", value_handle); vocs = vcp_get_vocs(vcp); if (!vocs || vocs->voal) return; vocs->voal = attr; vcp_read_value(vcp, value_handle, read_vocs_audio_location, vcp); vcp->audio_loc_id = vcp_register_notify(vcp, value_handle, vcp_audio_loc_notify, NULL); return; } if (!bt_uuid_cmp(&uuid, &uuid_vo_cp)) { DBG(vcp, "VOCS Volume CP found: handle 0x%04x", value_handle); vocs = vcp_get_vocs(vcp); if (!vocs || vocs->vo_cp) return; vocs->vo_cp = attr; return; } if (!bt_uuid_cmp(&uuid, &uuid_audio_op_decs)) { DBG(vcp, "VOCS Vol Audio Descriptor found: handle 0x%04x", value_handle); vocs = vcp_get_vocs(vcp); if (!vocs || vocs->voaodec) return; vocs->voaodec = attr; vcp_read_value(vcp, value_handle, read_vocs_audio_descriptor, vcp); vcp->ao_dec_id = vcp_register_notify(vcp, value_handle, vcp_audio_descriptor_notify, NULL); } } static void read_aics_audio_ip_state(struct bt_vcp *vcp, bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct aud_ip_st *ip_st; struct iovec iov = { .iov_base = (void *) value, .iov_len = length, }; if (!success) { DBG(vcp, "Unable to read Audio Input State: error 0x%02x", att_ecode); return; } ip_st = util_iov_pull_mem(&iov, sizeof(*ip_st)); if (!ip_st) { DBG(vcp, "Invalid Audio Input State"); return; } DBG(vcp, "Audio Input State, Gain Setting:%d", ip_st->gain_setting); DBG(vcp, "Audio Input State, Mute:%x", ip_st->mute); DBG(vcp, "Audio Input State, Gain Mode:%x", ip_st->gain_mode); DBG(vcp, "Audio Input State, Change Counter:%x", ip_st->chg_counter); } static void aics_ip_state_notify(struct bt_vcp *vcp, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *) value, .iov_len = length }; struct aud_ip_st *ip_st; ip_st = util_iov_pull_mem(&iov, sizeof(*ip_st)); if (!ip_st) { DBG(vcp, "Invalid Audio Input State"); return; } DBG(vcp, "Audio Input State, Gain Setting:%d", ip_st->gain_setting); DBG(vcp, "Audio Input State, Mute:%x", ip_st->mute); DBG(vcp, "Audio Input State, Gain Mode:%x", ip_st->gain_mode); DBG(vcp, "Audio Input State, Change Counter:%x", ip_st->chg_counter); } static void read_aics_gain_setting_prop(struct bt_vcp *vcp, bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *) value, .iov_len = length }; struct gain_setting_prop *aics_gain_setting_prop; if (!success) { DBG(vcp, "Unable to read Gain Setting Properties Char: 0x%02x", att_ecode); return; } aics_gain_setting_prop = util_iov_pull_mem(&iov, sizeof(*aics_gain_setting_prop)); if (!aics_gain_setting_prop) { DBG(vcp, "Unable to get Gain Setting Properties Char"); return; } DBG(vcp, "Gain Setting Properties, Units: %x", aics_gain_setting_prop->gain_setting_units); DBG(vcp, "Gain Setting Properties, Min Value: %d", aics_gain_setting_prop->gain_setting_min); DBG(vcp, "Gain Setting Properties, Max Value: %d", aics_gain_setting_prop->gain_setting_max); } static void read_aics_aud_ip_type(struct bt_vcp *vcp, bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *) value, .iov_len = length }; uint8_t ip_type; if (!success) { DBG(vcp, "Unable to read Audio Input Type Char: error 0x%02x", att_ecode); return; } if (!util_iov_pull_u8(&iov, &ip_type)) { DBG(vcp, "Invalid Audio Input Type Char"); return; } DBG(vcp, "Audio Input Type : %x", ip_type); } static void read_aics_audio_ip_status(struct bt_vcp *vcp, bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *) value, .iov_len = length }; uint8_t ip_status; if (!success) { DBG(vcp, "Unable to read Audio Input Status Char: 0x%02x", att_ecode); return; } if (!util_iov_pull_u8(&iov, &ip_status)) { DBG(vcp, "Invalid Audio Input Status Char"); return; } DBG(vcp, "Audio Input Status : %x", ip_status); } static void aics_ip_status_notify(struct bt_vcp *vcp, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *) value, .iov_len = length }; uint8_t ip_status; if (!util_iov_pull_u8(&iov, &ip_status)) { DBG(vcp, "Invalid Audio Input Status Char"); return; } DBG(vcp, "Audio Input Status, %x", ip_status); } static void read_aics_audio_ip_description(struct bt_vcp *vcp, bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *) value, .iov_len = length }; char *ip_descrptn; if (!success) { DBG(vcp, "Unable to read Audio Input Description Char: error 0x%02x", att_ecode); return; } ip_descrptn = iov_pull_string(&iov); if (!ip_descrptn) return; DBG(vcp, "Audio Input Description: %s", ip_descrptn); free(ip_descrptn); } static void aics_audio_ip_desr_notify(struct bt_vcp *vcp, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct iovec iov = { .iov_base = (void *) value, .iov_len = length }; char *aud_ip_desr; aud_ip_desr = iov_pull_string(&iov); if (!aud_ip_desr) return; DBG(vcp, "Audio Input Description Notify, %s", aud_ip_desr); free(aud_ip_desr); } static void foreach_aics_char(struct gatt_db_attribute *attr, void *user_data) { struct bt_vcp *vcp = user_data; uint16_t value_handle; bt_uuid_t uuid, uuid_ipstate, uuid_gain_setting_prop, uuid_ip_type, uuid_ip_status, uuid_ip_cp, uuid_ip_decs; struct bt_aics *aics; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) return; bt_uuid16_create(&uuid_ipstate, AICS_INPUT_STATE_CHAR_UUID); bt_uuid16_create(&uuid_gain_setting_prop, AICS_GAIN_SETTING_PROP_CHAR_UUID); bt_uuid16_create(&uuid_ip_type, AICS_AUDIO_INPUT_TYPE_CHAR_UUID); bt_uuid16_create(&uuid_ip_status, AICS_INPUT_STATUS_CHAR_UUID); bt_uuid16_create(&uuid_ip_cp, AICS_AUDIO_INPUT_CP_CHRC_UUID); bt_uuid16_create(&uuid_ip_decs, AICS_INPUT_DESCR_CHAR_UUID); if (!bt_uuid_cmp(&uuid, &uuid_ipstate)) { DBG(vcp, "AICS Audio Input State Char found: handle 0x%04x", value_handle); aics = vcp_get_aics(vcp); if (!aics || aics->aud_ip_state) return; aics->aud_ip_state = attr; vcp_read_value(vcp, value_handle, read_aics_audio_ip_state, vcp); vcp->aics_ip_state_id = vcp_register_notify(vcp, value_handle, aics_ip_state_notify, NULL); return; } if (!bt_uuid_cmp(&uuid, &uuid_gain_setting_prop)) { DBG(vcp, "AICS Gain Setting Properties Char found: handle 0x%04x", value_handle); aics = vcp_get_aics(vcp); if (!aics || aics->gain_stting_prop) return; aics->gain_stting_prop = attr; vcp_read_value(vcp, value_handle, read_aics_gain_setting_prop, vcp); return; } if (!bt_uuid_cmp(&uuid, &uuid_ip_type)) { DBG(vcp, "AICS Audio Input Type Char found: handle 0x%04x", value_handle); aics = vcp_get_aics(vcp); if (!aics || aics->aud_ip_type) return; aics->aud_ip_type = attr; vcp_read_value(vcp, value_handle, read_aics_aud_ip_type, vcp); return; } if (!bt_uuid_cmp(&uuid, &uuid_ip_status)) { DBG(vcp, "AICS Audio Input Status Char found: handle 0x%04x", value_handle); aics = vcp_get_aics(vcp); if (!aics || aics->aud_ip_status) return; aics->aud_ip_status = attr; vcp_read_value(vcp, value_handle, read_aics_audio_ip_status, vcp); vcp->aics_ip_status_id = vcp_register_notify(vcp, value_handle, aics_ip_status_notify, NULL); return; } if (!bt_uuid_cmp(&uuid, &uuid_ip_cp)) { DBG(vcp, "AICS Input CP found: handle 0x%04x", value_handle); aics = vcp_get_aics(vcp); if (!aics || aics->aud_ip_cp) return; aics->aud_ip_cp = attr; return; } if (!bt_uuid_cmp(&uuid, &uuid_ip_decs)) { DBG(vcp, "AICS Audio Input Description Char found: handle 0x%04x", value_handle); aics = vcp_get_aics(vcp); if (!aics || aics->aud_ip_dscrptn) return; aics->aud_ip_dscrptn = attr; vcp_read_value(vcp, value_handle, read_aics_audio_ip_description, vcp); vcp->aics_ip_descr_id = vcp_register_notify(vcp, value_handle, aics_audio_ip_desr_notify, NULL); } } static void foreach_vcs_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_vcp *vcp = user_data; struct bt_vcs *vcs = vcp_get_vcs(vcp); if (!vcs) return; vcs->service = attr; gatt_db_service_set_claimed(attr, true); gatt_db_service_foreach_char(attr, foreach_vcs_char, vcp); } static void foreach_vocs_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_vcp *vcp = user_data; struct bt_vocs *vocs = vcp_get_vocs(vcp); if (!vocs || !attr) return; vocs->service = attr; gatt_db_service_set_claimed(attr, true); gatt_db_service_foreach_char(attr, foreach_vocs_char, vcp); } static void foreach_aics_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_vcp *vcp = user_data; struct bt_aics *aics = vcp_get_aics(vcp); if (!aics || !attr) return; aics->service = attr; gatt_db_service_set_claimed(attr, true); gatt_db_service_foreach_char(attr, foreach_aics_char, vcp); } static void vcp_idle(void *data) { struct bt_vcp *vcp = data; vcp->idle_id = 0; if (vcp->ready_func) vcp->ready_func(vcp, vcp->ready_data); } bool bt_vcp_attach(struct bt_vcp *vcp, struct bt_gatt_client *client, bt_vcp_func_t ready, void *ready_user_data) { bt_uuid_t uuid; if (!sessions) sessions = queue_new(); queue_push_tail(sessions, vcp); if (!client) return true; if (vcp->client) return false; vcp->client = bt_gatt_client_clone(client); if (!vcp->client) return false; bt_uuid16_create(&uuid, VCS_UUID); gatt_db_foreach_service(vcp->rdb->db, &uuid, foreach_vcs_service, vcp); bt_uuid16_create(&uuid, VOL_OFFSET_CS_UUID); gatt_db_foreach_service(vcp->rdb->db, &uuid, foreach_vocs_service, vcp); bt_uuid16_create(&uuid, AUDIO_INPUT_CS_UUID); gatt_db_foreach_service(vcp->rdb->db, &uuid, foreach_aics_service, vcp); vcp->ready_func = ready; vcp->ready_data = ready_user_data; if (!vcp->idle_id) vcp->idle_id = bt_gatt_client_idle_register(vcp->client, vcp_idle, vcp, NULL); return true; } bluez-5.87/src/shared/PaxHeaders/timeout.h0000644000000000000000000000005014031556405015552 xustar0020 atime=1783092780 20 ctime=1783094099 bluez-5.87/src/shared/timeout.h0000644000000000000000000000112614031556405015233 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * */ #include typedef bool (*timeout_func_t)(void *user_data); typedef void (*timeout_destroy_func_t)(void *user_data); unsigned int timeout_add(unsigned int timeout, timeout_func_t func, void *user_data, timeout_destroy_func_t destroy); void timeout_remove(unsigned int id); unsigned int timeout_add_seconds(unsigned int timeout, timeout_func_t func, void *user_data, timeout_destroy_func_t destroy); bluez-5.87/src/shared/PaxHeaders/lc3.h0000644000000000000000000000005015142177620014547 xustar0020 atime=1783092807 20 ctime=1783094100 bluez-5.87/src/shared/lc3.h0000644000000000000000000011004015142177620014224 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. * Copyright 2024 NXP * */ #include "src/shared/bap-defs.h" #define LC3_ID 0x06 #define LC3_TYPE_BASE 0x01 #define LC3_FREQ LC3_TYPE_BASE #define LC3_FREQ_8KHZ BIT(0) #define LC3_FREQ_11KHZ BIT(1) #define LC3_FREQ_16KHZ BIT(2) #define LC3_FREQ_22KHZ BIT(3) #define LC3_FREQ_24KHZ BIT(4) #define LC3_FREQ_32KHZ BIT(5) #define LC3_FREQ_44KHZ BIT(6) #define LC3_FREQ_48KHZ BIT(7) #define LC3_FREQ_ANY (LC3_FREQ_8KHZ | \ LC3_FREQ_11KHZ | \ LC3_FREQ_16KHZ | \ LC3_FREQ_22KHZ | \ LC3_FREQ_24KHZ | \ LC3_FREQ_32KHZ | \ LC3_FREQ_44KHZ | \ LC3_FREQ_48KHZ) #define LC3_DURATION (LC3_TYPE_BASE + 1) #define LC3_DURATION_7_5 BIT(0) #define LC3_DURATION_10 BIT(1) #define LC3_DURATION_ANY (LC3_DURATION_7_5 | LC3_DURATION_10) #define LC3_DURATION_PREFER_7_5 BIT(4) #define LC3_DURATION_PREFER_10 BIT(5) #define LC3_CHAN_COUNT (LC3_TYPE_BASE + 2) #define LC3_CHAN_COUNT_SUPPORT BIT(0) #define LC3_FRAME_LEN (LC3_TYPE_BASE + 3) #define LC3_FRAME_COUNT (LC3_TYPE_BASE + 4) #define LC3_CAPABILITIES(_freq, _duration, _chan_count, _len_min, _len_max) \ UTIL_IOV_INIT(0x03, LC3_FREQ, _freq, _freq >> 8, \ 0x02, LC3_DURATION, _duration, \ 0x02, LC3_CHAN_COUNT, _chan_count, \ 0x05, LC3_FRAME_LEN, _len_min, _len_min >> 8, \ _len_max, _len_max >> 8) #define LC3_CONFIG_BASE 0x01 #define LC3_CONFIG_FREQ (LC3_CONFIG_BASE) #define LC3_CONFIG_FREQ_8KHZ 0x01 #define LC3_CONFIG_FREQ_11KHZ 0x02 #define LC3_CONFIG_FREQ_16KHZ 0x03 #define LC3_CONFIG_FREQ_22KHZ 0x04 #define LC3_CONFIG_FREQ_24KHZ 0x05 #define LC3_CONFIG_FREQ_32KHZ 0x06 #define LC3_CONFIG_FREQ_44KHZ 0x07 #define LC3_CONFIG_FREQ_48KHZ 0x08 #define LC3_CONFIG_DURATION (LC3_CONFIG_BASE + 1) #define LC3_CONFIG_DURATION_7_5 0x00 #define LC3_CONFIG_DURATION_10 0x01 #define LC3_CONFIG_CHAN_ALLOC (LC3_CONFIG_BASE + 2) #define LC3_CONFIG_CHAN_ALLOC_LEN 0x05 #define LC3_CONFIG_FRAME_LEN (LC3_CONFIG_BASE + 3) #define LC3_CONFIG(_freq, _duration, _len) \ UTIL_IOV_INIT(0x02, LC3_CONFIG_FREQ, _freq, \ 0x02, LC3_CONFIG_DURATION, _duration, \ 0x03, LC3_CONFIG_FRAME_LEN, _len, _len >> 8) #define LC3_AC_BITS(_ac) (BIT(_ac) - 1) #define LC3_CONFIG_AC(_freq, _duration, _len, _ac) \ UTIL_IOV_INIT(0x02, LC3_CONFIG_FREQ, _freq, \ 0x02, LC3_CONFIG_DURATION, _duration, \ 0x03, LC3_CONFIG_FRAME_LEN, _len, _len >> 8, \ 0x05, LC3_CONFIG_CHAN_ALLOC, LC3_AC_BITS(_ac), \ LC3_AC_BITS(_ac) >> 8 & 0xff, \ LC3_AC_BITS(_ac) >> 16 & 0xff, \ LC3_AC_BITS(_ac) >> 24 & 0xff) #define LC3_CONFIG_8(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_8KHZ, _duration, _len) #define LC3_CONFIG_8_AC(_duration, _len, _ac) \ LC3_CONFIG_AC(LC3_CONFIG_FREQ_8KHZ, _duration, _len, _ac) #define LC3_CONFIG_11(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_11KHZ, _duration, _len) #define LC3_CONFIG_11_AC(_duration, _len, _ac) \ LC3_CONFIG_AC(LC3_CONFIG_FREQ_11KHZ, _duration, _len, _ac) #define LC3_CONFIG_16(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_16KHZ, _duration, _len) #define LC3_CONFIG_16_AC(_duration, _len, _ac) \ LC3_CONFIG_AC(LC3_CONFIG_FREQ_16KHZ, _duration, _len, _ac) #define LC3_CONFIG_22(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_22KHZ, _duration, _len) #define LC3_CONFIG_22_AC(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_22KHZ, _duration, _len, _ac) #define LC3_CONFIG_24(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_24KHZ, _duration, _len) #define LC3_CONFIG_24_AC(_duration, _len, _ac) \ LC3_CONFIG_AC(LC3_CONFIG_FREQ_24KHZ, _duration, _len, _ac) #define LC3_CONFIG_32(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_32KHZ, _duration, _len) #define LC3_CONFIG_32_AC(_duration, _len, _ac) \ LC3_CONFIG_AC(LC3_CONFIG_FREQ_32KHZ, _duration, _len, _ac) #define LC3_CONFIG_44(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_44KHZ, _duration, _len) #define LC3_CONFIG_44_AC(_duration, _len, _ac) \ LC3_CONFIG_AC(LC3_CONFIG_FREQ_44KHZ, _duration, _len, _ac) #define LC3_CONFIG_48(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_48KHZ, _duration, _len) #define LC3_CONFIG_48_AC(_duration, _len, _ac) \ LC3_CONFIG_AC(LC3_CONFIG_FREQ_48KHZ, _duration, _len, _ac) #define LC3_CONFIG_FRAME_LEN_8_1 26u #define LC3_CONFIG_8_1 \ LC3_CONFIG_8(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_8_1) #define LC3_CONFIG_8_1_AC(_ac) \ LC3_CONFIG_8_AC(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_8_1, _ac) #define LC3_CONFIG_FRAME_LEN_8_2 30u #define LC3_CONFIG_8_2 \ LC3_CONFIG_8(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_8_2 ) #define LC3_CONFIG_8_2_AC(_ac) \ LC3_CONFIG_8_AC(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_8_2, _ac) #define LC3_CONFIG_FRAME_LEN_16_1 30u #define LC3_CONFIG_16_1 \ LC3_CONFIG_16(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_16_1) #define LC3_CONFIG_16_1_AC(_ac) \ LC3_CONFIG_16_AC(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_16_1, \ _ac) #define LC3_CONFIG_FRAME_LEN_16_2 40u #define LC3_CONFIG_16_2 \ LC3_CONFIG_16(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_16_2) #define LC3_CONFIG_16_2_AC(_ac) \ LC3_CONFIG_16_AC(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_16_2, _ac) #define LC3_CONFIG_FRAME_LEN_24_1 45u #define LC3_CONFIG_24_1 \ LC3_CONFIG_24(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_24_1) #define LC3_CONFIG_24_1_AC(_ac) \ LC3_CONFIG_24_AC(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_24_1, \ _ac) #define LC3_CONFIG_FRAME_LEN_24_2 60u #define LC3_CONFIG_24_2 \ LC3_CONFIG_24(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_24_2) #define LC3_CONFIG_24_2_AC(_ac) \ LC3_CONFIG_24_AC(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_24_2, _ac) #define LC3_CONFIG_FRAME_LEN_32_1 60u #define LC3_CONFIG_32_1 \ LC3_CONFIG_32(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_32_1) #define LC3_CONFIG_32_1_AC(_ac) \ LC3_CONFIG_32_AC(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_32_1, \ _ac) #define LC3_CONFIG_FRAME_LEN_32_2 80u #define LC3_CONFIG_32_2 \ LC3_CONFIG_32(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_32_2) #define LC3_CONFIG_32_2_AC(_ac) \ LC3_CONFIG_32_AC(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_32_2, _ac) #define LC3_CONFIG_FRAME_LEN_44_1 97u #define LC3_CONFIG_44_1 \ LC3_CONFIG_44(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_44_1) #define LC3_CONFIG_44_1_AC(_ac) \ LC3_CONFIG_44_AC(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_44_1, \ _ac) #define LC3_CONFIG_FRAME_LEN_44_2 130u #define LC3_CONFIG_44_2 \ LC3_CONFIG_44(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_44_2) #define LC3_CONFIG_44_2_AC(_ac) \ LC3_CONFIG_44_AC(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_44_2, _ac) #define LC3_CONFIG_FRAME_LEN_48_1 75u #define LC3_CONFIG_48_1 \ LC3_CONFIG_48(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_48_1) #define LC3_CONFIG_48_1_AC(_ac) \ LC3_CONFIG_48_AC(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_48_1, \ _ac) #define LC3_CONFIG_FRAME_LEN_48_2 100u #define LC3_CONFIG_48_2 \ LC3_CONFIG_48(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_48_2) #define LC3_CONFIG_48_2_AC(_ac) \ LC3_CONFIG_48_AC(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_48_2, _ac) #define LC3_CONFIG_FRAME_LEN_48_3 90u #define LC3_CONFIG_48_3 \ LC3_CONFIG_48(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_48_3) #define LC3_CONFIG_48_3_AC(_ac) \ LC3_CONFIG_48_AC(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_48_3, \ _ac) #define LC3_CONFIG_FRAME_LEN_48_4 120u #define LC3_CONFIG_48_4 \ LC3_CONFIG_48(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_48_4) #define LC3_CONFIG_48_4_AC(_ac) \ LC3_CONFIG_48_AC(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_48_4, _ac) #define LC3_CONFIG_FRAME_LEN_48_5 117u #define LC3_CONFIG_48_5 \ LC3_CONFIG_48(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_48_5) #define LC3_CONFIG_48_5_AC(_ac) \ LC3_CONFIG_48_AC(LC3_CONFIG_DURATION_7_5, LC3_CONFIG_FRAME_LEN_48_5, \ _ac) #define LC3_CONFIG_FRAME_LEN_48_6 155u #define LC3_CONFIG_48_6 \ LC3_CONFIG_48(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_48_6) #define LC3_CONFIG_48_6_AC(_ac) \ LC3_CONFIG_48_AC(LC3_CONFIG_DURATION_10, LC3_CONFIG_FRAME_LEN_48_6, \ _ac) #define BASE(_pd, _sgrp, _nbis, _cfg...) \ _pd & 0xff, _pd >> 8, _pd >> 16, \ _sgrp, \ _nbis, \ _cfg #define BASE_LC3(_pd, _sgrp, _nbis, _cc...) \ BASE(_pd, _sgrp, _nbis, \ 0x06, 0x00, 0x00, 0x00, 0x00, \ _cc) #define LC3_CFG(_freq, _dur, _len) \ 0x0a, \ 0x02, LC3_CONFIG_FREQ, _freq, \ 0x02, LC3_CONFIG_DURATION, _dur, \ 0x03, LC3_CONFIG_FRAME_LEN, _len, _len >> 8 #define LC3_CFG_8_1 \ LC3_CFG(LC3_CONFIG_FREQ_8KHZ, \ LC3_CONFIG_DURATION_7_5, \ LC3_CONFIG_FRAME_LEN_8_1) #define BASE_LC3_8_1 \ BASE_LC3(40000, 1, 1, LC3_CFG_8_1, 0x00, 0x01, 0x00) #define LC3_CFG_8_2 \ LC3_CFG(LC3_CONFIG_FREQ_8KHZ, \ LC3_CONFIG_DURATION_10, \ LC3_CONFIG_FRAME_LEN_8_2) #define BASE_LC3_8_2 \ BASE_LC3(40000, 1, 1, LC3_CFG_8_2, 0x00, 0x01, 0x00) #define LC3_CFG_16_1 \ LC3_CFG(LC3_CONFIG_FREQ_16KHZ, \ LC3_CONFIG_DURATION_7_5, \ LC3_CONFIG_FRAME_LEN_16_1) #define BASE_LC3_16_1 \ BASE_LC3(40000, 1, 1, LC3_CFG_16_1, 0x00, 0x01, 0x00) #define LC3_CFG_16_2 \ LC3_CFG(LC3_CONFIG_FREQ_16KHZ, \ LC3_CONFIG_DURATION_10, \ LC3_CONFIG_FRAME_LEN_16_2) #define BASE_LC3_16_2 \ BASE_LC3(40000, 1, 1, LC3_CFG_16_2, 0x00, 0x01, 0x00) #define LC3_CFG_24_1 \ LC3_CFG(LC3_CONFIG_FREQ_24KHZ, \ LC3_CONFIG_DURATION_7_5, \ LC3_CONFIG_FRAME_LEN_24_1) #define BASE_LC3_24_1 \ BASE_LC3(40000, 1, 1, LC3_CFG_24_1, 0x00, 0x01, 0x00) #define LC3_CFG_24_2 \ LC3_CFG(LC3_CONFIG_FREQ_24KHZ, \ LC3_CONFIG_DURATION_10, \ LC3_CONFIG_FRAME_LEN_24_2) #define BASE_LC3_24_2 \ BASE_LC3(40000, 1, 1, LC3_CFG_24_2, 0x00, 0x01, 0x00) #define LC3_CFG_32_1 \ LC3_CFG(LC3_CONFIG_FREQ_32KHZ, \ LC3_CONFIG_DURATION_7_5, \ LC3_CONFIG_FRAME_LEN_32_1) #define BASE_LC3_32_1 \ BASE_LC3(40000, 1, 1, LC3_CFG_32_1, 0x00, 0x01, 0x00) #define LC3_CFG_32_2 \ LC3_CFG(LC3_CONFIG_FREQ_32KHZ, \ LC3_CONFIG_DURATION_10, \ LC3_CONFIG_FRAME_LEN_32_2) #define BASE_LC3_32_2 \ BASE_LC3(40000, 1, 1, LC3_CFG_32_2, 0x00, 0x01, 0x00) #define LC3_CFG_44_1 \ LC3_CFG(LC3_CONFIG_FREQ_44KHZ, \ LC3_CONFIG_DURATION_7_5, \ LC3_CONFIG_FRAME_LEN_44_1) #define BASE_LC3_44_1 \ BASE_LC3(40000, 1, 1, LC3_CFG_44_1, 0x00, 0x01, 0x00) #define LC3_CFG_44_2 \ LC3_CFG(LC3_CONFIG_FREQ_44KHZ, \ LC3_CONFIG_DURATION_10, \ LC3_CONFIG_FRAME_LEN_44_2) #define BASE_LC3_44_2 \ BASE_LC3(40000, 1, 1, LC3_CFG_44_2, 0x00, 0x01, 0x00) #define LC3_CFG_48_1 \ LC3_CFG(LC3_CONFIG_FREQ_48KHZ, \ LC3_CONFIG_DURATION_7_5, \ LC3_CONFIG_FRAME_LEN_48_1) #define BASE_LC3_48_1 \ BASE_LC3(40000, 1, 1, LC3_CFG_48_1, 0x00, 0x01, 0x00) #define LC3_CFG_48_2 \ LC3_CFG(LC3_CONFIG_FREQ_48KHZ, \ LC3_CONFIG_DURATION_10, \ LC3_CONFIG_FRAME_LEN_48_2) #define BASE_LC3_48_2 \ BASE_LC3(40000, 1, 1, LC3_CFG_48_2, 0x00, 0x01, 0x00) #define LC3_CFG_48_3 \ LC3_CFG(LC3_CONFIG_FREQ_48KHZ, \ LC3_CONFIG_DURATION_7_5, \ LC3_CONFIG_FRAME_LEN_48_3) #define BASE_LC3_48_3 \ BASE_LC3(40000, 1, 1, LC3_CFG_48_3, 0x00, 0x01, 0x00) #define LC3_CFG_48_4 \ LC3_CFG(LC3_CONFIG_FREQ_48KHZ, \ LC3_CONFIG_DURATION_10, \ LC3_CONFIG_FRAME_LEN_48_4) #define BASE_LC3_48_4 \ BASE_LC3(40000, 1, 1, LC3_CFG_48_4, 0x00, 0x01, 0x00) #define LC3_CFG_48_5 \ LC3_CFG(LC3_CONFIG_FREQ_48KHZ, \ LC3_CONFIG_DURATION_7_5, \ LC3_CONFIG_FRAME_LEN_48_5) #define BASE_LC3_48_5 \ BASE_LC3(40000, 1, 1, LC3_CFG_48_5, 0x00, 0x01, 0x00) #define LC3_CFG_48_6 \ LC3_CFG(LC3_CONFIG_FREQ_48KHZ, \ LC3_CONFIG_DURATION_10, \ LC3_CONFIG_FRAME_LEN_48_6) #define BASE_LC3_48_6 \ BASE_LC3(40000, 1, 1, LC3_CFG_48_6, 0x00, 0x01, 0x00) #define BASE_VS(_cc) \ BASE(40000, 1, 1, \ 0xFF, 0x00, 0x00, 0x00, 0x00, \ _cc, 0x00, 0x01, 0x00) #define BASE_LC3_8_1_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_8_1, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_LC3_8_2_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_8_2, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_LC3_16_1_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_16_1, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_LC3_16_2_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_16_2, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_LC3_24_1_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_24_1, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_LC3_24_2_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_24_2, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_LC3_32_1_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_32_1, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_LC3_32_2_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_32_2, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_LC3_44_1_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_44_1, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_LC3_44_2_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_44_2, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_LC3_48_1_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_48_1, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_LC3_48_2_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_48_2, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_LC3_48_3_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_48_3, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_LC3_48_4_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_48_4, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_LC3_48_5_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_48_5, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_LC3_48_6_MBIS \ BASE_LC3(40000, 1, 2, LC3_CFG_48_6, 0x00, 0x01, 0x00, 0x02, 0x00) #define BASE_VS_MBIS(_cc) \ BASE(40000, 1, 2, \ 0xFF, 0x00, 0x00, 0x00, 0x00, \ _cc, 0x00, 0x01, 0x00, 0x02, 0x00) #define LC3_QOS_UNFRAMED 0x00 #define LC3_QOS_FRAMED 0x01 #define LC3_QOS_UCAST(_frame, _pd, _t_lat, _interval, _lat, _sdu, _rtn) \ { \ .ucast.cig_id = 0x00, \ .ucast.cis_id = 0x00, \ .ucast.framing = _frame, \ .ucast.delay = _pd, \ .ucast.target_latency = _t_lat, \ .ucast.io_qos.interval = _interval, \ .ucast.io_qos.latency = _lat, \ .ucast.io_qos.sdu = _sdu, \ .ucast.io_qos.phys = BT_BAP_CONFIG_PHY_2M, \ .ucast.io_qos.rtn = _rtn, \ } #define LC3_QOS_UCAST_7_5_UNFRAMED(_pd, _t_lat, _lat, _sdu, _rtn) \ LC3_QOS_UCAST(LC3_QOS_UNFRAMED, _pd, _t_lat, 7500u, _lat, _sdu, _rtn) #define LC3_QOS_UCAST_10_UNFRAMED(_pd, _t_lat, _lat, _sdu, _rtn) \ LC3_QOS_UCAST(LC3_QOS_UNFRAMED, _pd, _t_lat, 10000u, _lat, _sdu, _rtn) #define LC3_QOS_UCAST_FRAMED(_pd, _t_lat, _interval, _lat, _sdu, _rtn) \ LC3_QOS_UCAST(LC3_QOS_FRAMED, _pd, _t_lat, _interval, _lat, _sdu, _rtn) #define LC3_QOS_8_1_1_LATENCY 8u #define LC3_QOS_8_1_1_RTN 2u #define LC3_QOS_8_1_1_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_8_1_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_8_1, \ LC3_QOS_8_1_1_RTN) #define LC3_QOS_8_1_1 LC3_QOS_8_1_1_AC(1) #define LC3_QOS_8_1_2_LATENCY 75u #define LC3_QOS_8_1_2_RTN 13u #define LC3_QOS_8_1_2_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_8_1_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_8_1, \ LC3_QOS_8_1_2_RTN) #define LC3_QOS_8_1_2 LC3_QOS_8_1_2_AC(1) #define LC3_QOS_8_2_1_LATENCY 10u #define LC3_QOS_8_2_1_RTN 2u #define LC3_QOS_8_2_1_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_8_2_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_8_2, \ LC3_QOS_8_2_1_RTN) #define LC3_QOS_8_2_1 LC3_QOS_8_2_1_AC(1) #define LC3_QOS_8_2_2_LATENCY 95u #define LC3_QOS_8_2_2_RTN 13u #define LC3_QOS_8_2_2_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_8_2_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_8_2, \ LC3_QOS_8_2_2_RTN) #define LC3_QOS_8_2_2 LC3_QOS_8_2_2_AC(1) #define LC3_QOS_16_1_1_LATENCY 8u #define LC3_QOS_16_1_1_RTN 2u #define LC3_QOS_16_1_1_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_16_1_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_16_1, \ LC3_QOS_16_1_1_RTN) #define LC3_QOS_16_1_1 LC3_QOS_16_1_1_AC(1) #define LC3_QOS_16_1_2_LATENCY 75u #define LC3_QOS_16_1_2_RTN 13u #define LC3_QOS_16_1_2_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_16_1_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_16_1, \ LC3_QOS_16_1_2_RTN) #define LC3_QOS_16_1_2 LC3_QOS_16_1_2_AC(1) #define LC3_QOS_16_1_GS_LATENCY 15u #define LC3_QOS_16_1_GS_RTN 1u #define LC3_QOS_16_1_GS \ LC3_QOS_UCAST_7_5_UNFRAMED(60000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_16_1_GS_LATENCY, \ LC3_CONFIG_FRAME_LEN_16_1, \ LC3_QOS_16_1_GS_RTN) #define LC3_QOS_16_2_1_LATENCY 10u #define LC3_QOS_16_2_1_RTN 2u #define LC3_QOS_16_2_1_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_16_2_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_16_2, \ LC3_QOS_16_2_1_RTN) #define LC3_QOS_16_2_1 LC3_QOS_16_2_1_AC(1) #define LC3_QOS_16_2_2_LATENCY 95u #define LC3_QOS_16_2_2_RTN 13u #define LC3_QOS_16_2_2_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_16_2_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_16_2, \ LC3_QOS_16_2_2_RTN) #define LC3_QOS_16_2_2 LC3_QOS_16_2_2_AC(1) #define LC3_QOS_16_2_GS_LATENCY 20u #define LC3_QOS_16_2_GS_RTN 1u #define LC3_QOS_16_2_GS \ LC3_QOS_UCAST_10_UNFRAMED(60000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_16_2_GS_LATENCY, \ LC3_CONFIG_FRAME_LEN_16_2, \ LC3_QOS_16_2_GS_RTN) #define LC3_QOS_24_1_1_LATENCY 8u #define LC3_QOS_24_1_1_RTN 2u #define LC3_QOS_24_1_1_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_24_1_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_24_1, \ LC3_QOS_24_1_1_RTN) #define LC3_QOS_24_1_1 LC3_QOS_24_1_1_AC(1) #define LC3_QOS_24_1_2_LATENCY 75u #define LC3_QOS_24_1_2_RTN 13u #define LC3_QOS_24_1_2_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_24_1_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_24_1, \ LC3_QOS_24_1_2_RTN) #define LC3_QOS_24_1_2 LC3_QOS_24_1_2_AC(1) #define LC3_QOS_24_2_1_LATENCY 10u #define LC3_QOS_24_2_1_RTN 2u #define LC3_QOS_24_2_1_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_24_2_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_24_2, \ LC3_QOS_24_2_1_RTN) #define LC3_QOS_24_2_1 LC3_QOS_24_2_1_AC(1) #define LC3_QOS_24_2_2_LATENCY 95u #define LC3_QOS_24_2_2_RTN 13u #define LC3_QOS_24_2_2_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_24_2_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_24_2, \ LC3_QOS_24_2_2_RTN) #define LC3_QOS_24_2_2 LC3_QOS_24_2_2_AC(1) #define LC3_QOS_32_1_1_LATENCY 8u #define LC3_QOS_32_1_1_RTN 2u #define LC3_QOS_32_1_1_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_32_1_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_32_1, \ LC3_QOS_32_1_1_RTN) #define LC3_QOS_32_1_1 LC3_QOS_32_1_1_AC(1) #define LC3_QOS_32_1_2_LATENCY 75u #define LC3_QOS_32_1_2_RTN 13u #define LC3_QOS_32_1_2_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_32_1_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_32_1, \ LC3_QOS_32_1_2_RTN) #define LC3_QOS_32_1_2 LC3_QOS_32_1_2_AC(1) #define LC3_QOS_32_1_GS_LATENCY 15u #define LC3_QOS_32_1_GS_RTN 1u #define LC3_QOS_32_1_GS \ LC3_QOS_UCAST_7_5_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_32_1_GS_LATENCY, \ LC3_CONFIG_FRAME_LEN_32_1, \ LC3_QOS_32_1_GS_RTN) #define LC3_QOS_32_1_GR_LATENCY 15u #define LC3_QOS_32_1_GR_RTN 1u #define LC3_QOS_32_1_GR_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(10000u, BT_BAP_CONFIG_LATENCY_LOW, \ LC3_QOS_32_1_GS_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_32_1, \ LC3_QOS_32_1_GS_RTN) #define LC3_QOS_32_1_GR LC3_QOS_32_1_GR_AC(1) #define LC3_QOS_32_2_1_LATENCY 10u #define LC3_QOS_32_2_1_RTN 2u #define LC3_QOS_32_2_1_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_32_2_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_32_2, \ LC3_QOS_32_2_1_RTN) #define LC3_QOS_32_2_1 LC3_QOS_32_2_1_AC(1) #define LC3_QOS_32_2_2_LATENCY 95u #define LC3_QOS_32_2_2_RTN 13u #define LC3_QOS_32_2_2_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_32_2_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_32_2, \ LC3_QOS_32_2_2_RTN) #define LC3_QOS_32_2_2 LC3_QOS_32_2_2_AC(1) #define LC3_QOS_32_2_GS_LATENCY 20u #define LC3_QOS_32_2_GS_RTN 1u #define LC3_QOS_32_2_GS \ LC3_QOS_UCAST_10_UNFRAMED(60000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_32_2_GS_LATENCY, \ LC3_CONFIG_FRAME_LEN_32_2, \ LC3_QOS_32_2_GS_RTN) #define LC3_QOS_32_2_GR_LATENCY 20u #define LC3_QOS_32_2_GR_RTN 1u #define LC3_QOS_32_2_GR_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(10000u, BT_BAP_CONFIG_LATENCY_LOW, \ LC3_QOS_32_2_GR_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_32_2, \ LC3_QOS_32_2_GR_RTN) #define LC3_QOS_32_2_GR LC3_QOS_32_2_GR_AC(1) #define LC3_QOS_44_1_INTERVAL 8163u #define LC3_QOS_44_1_1_LATENCY 24u #define LC3_QOS_44_1_1_RTN 5u #define LC3_QOS_44_1_1_AC(_ac) \ LC3_QOS_UCAST_FRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_44_1_INTERVAL, \ LC3_QOS_44_1_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_44_1, \ LC3_QOS_44_1_1_RTN) #define LC3_QOS_44_1_1 LC3_QOS_44_1_1_AC(1) #define LC3_QOS_44_1_2_LATENCY 80u #define LC3_QOS_44_1_2_RTN 13u #define LC3_QOS_44_1_2_AC(_ac) \ LC3_QOS_UCAST_FRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_44_1_INTERVAL, \ LC3_QOS_44_1_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_44_1, \ LC3_QOS_44_1_2_RTN) #define LC3_QOS_44_1_2 LC3_QOS_44_1_2_AC(1) #define LC3_QOS_44_2_INTERVAL 10884u #define LC3_QOS_44_2_1_LATENCY 31u #define LC3_QOS_44_2_1_RTN 5u #define LC3_QOS_44_2_1_AC(_ac) \ LC3_QOS_UCAST_FRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_44_2_INTERVAL, \ LC3_QOS_44_2_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_44_2, \ LC3_QOS_44_2_1_RTN) #define LC3_QOS_44_2_1 LC3_QOS_44_2_1_AC(1) #define LC3_QOS_44_2_2_LATENCY 85u #define LC3_QOS_44_2_2_RTN 13u #define LC3_QOS_44_2_2_AC(_ac) \ LC3_QOS_UCAST_FRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_44_2_INTERVAL, \ LC3_QOS_44_2_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_44_2, \ LC3_QOS_44_2_2_RTN) #define LC3_QOS_44_2_2 LC3_QOS_44_2_2_AC(1) #define LC3_QOS_48_1_1_LATENCY 15u #define LC3_QOS_48_1_1_RTN 5u #define LC3_QOS_48_1_1_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_48_1_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_1,\ LC3_QOS_48_1_1_RTN) #define LC3_QOS_48_1_1 LC3_QOS_48_1_1_AC(1) #define LC3_QOS_48_1_2_LATENCY 75u #define LC3_QOS_48_1_2_RTN 13u #define LC3_QOS_48_1_2_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_48_1_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_1, \ LC3_QOS_48_1_2_RTN) #define LC3_QOS_48_1_2 LC3_QOS_48_1_2_AC(1) #define LC3_QOS_48_1_GS_LATENCY 15u #define LC3_QOS_48_1_GS_RTN 1u #define LC3_QOS_48_1_GS \ LC3_QOS_UCAST_7_5_UNFRAMED(60000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_48_1_GS_LATENCY, \ LC3_CONFIG_FRAME_LEN_48_1, \ LC3_QOS_48_1_GS_RTN) #define LC3_QOS_48_1_GR_LATENCY 15u #define LC3_QOS_48_1_GR_RTN 1u #define LC3_QOS_48_1_GR_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(10000u, BT_BAP_CONFIG_LATENCY_LOW, \ LC3_QOS_48_1_GR_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_1, \ LC3_QOS_48_1_GR_RTN) #define LC3_QOS_48_1_GR LC3_QOS_48_1_GR_AC(1) #define LC3_QOS_48_2_1_LATENCY 20u #define LC3_QOS_48_2_1_RTN 5u #define LC3_QOS_48_2_1_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_48_2_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_2, \ LC3_QOS_48_2_1_RTN) #define LC3_QOS_48_2_1 LC3_QOS_48_2_1_AC(1) #define LC3_QOS_48_2_2_LATENCY 95u #define LC3_QOS_48_2_2_RTN 13u #define LC3_QOS_48_2_2_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_48_2_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_2, \ LC3_QOS_48_2_2_RTN) #define LC3_QOS_48_2_2 LC3_QOS_48_2_2_AC(1) #define LC3_QOS_48_2_GS_LATENCY 20u #define LC3_QOS_48_2_GS_RTN 1u #define LC3_QOS_48_2_GS \ LC3_QOS_UCAST_10_UNFRAMED(60000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_48_2_GS_LATENCY, \ LC3_CONFIG_FRAME_LEN_48_2, \ LC3_QOS_48_2_GS_RTN) #define LC3_QOS_48_2_GR_LATENCY 20u #define LC3_QOS_48_2_GR_RTN 1u #define LC3_QOS_48_2_GR_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(10000u, BT_BAP_CONFIG_LATENCY_LOW, \ LC3_QOS_48_2_GR_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_2, \ LC3_QOS_48_2_GR_RTN) #define LC3_QOS_48_2_GR LC3_QOS_48_2_GR_AC(1) #define LC3_QOS_48_3_1_LATENCY 15u #define LC3_QOS_48_3_1_RTN 5u #define LC3_QOS_48_3_1_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_48_3_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_3, \ LC3_QOS_48_3_1_RTN) #define LC3_QOS_48_3_1 LC3_QOS_48_3_1_AC(1) #define LC3_QOS_48_3_2_LATENCY 75u #define LC3_QOS_48_3_2_RTN 13u #define LC3_QOS_48_3_2_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_48_3_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_3, \ LC3_QOS_48_3_2_RTN) #define LC3_QOS_48_3_2 LC3_QOS_48_3_2_AC(1) #define LC3_QOS_48_3_GR_LATENCY 15u #define LC3_QOS_48_3_GR_RTN 1u #define LC3_QOS_48_3_GR_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(10000u, BT_BAP_CONFIG_LATENCY_LOW, \ LC3_QOS_48_3_GR_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_3, \ LC3_QOS_48_3_GR_RTN) #define LC3_QOS_48_3_GR LC3_QOS_48_3_GR_AC(1) #define LC3_QOS_48_4_1_LATENCY 20u #define LC3_QOS_48_4_1_RTN 5u #define LC3_QOS_48_4_1_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_48_4_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_4, \ LC3_QOS_48_4_1_RTN) #define LC3_QOS_48_4_1 LC3_QOS_48_4_1_AC(1) #define LC3_QOS_48_4_2_LATENCY 100u #define LC3_QOS_48_4_2_RTN 13u #define LC3_QOS_48_4_2_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_48_4_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_4, \ LC3_QOS_48_4_2_RTN) #define LC3_QOS_48_4_2 LC3_QOS_48_4_2_AC(1) #define LC3_QOS_48_4_GR_LATENCY 20u #define LC3_QOS_48_4_GR_RTN 1u #define LC3_QOS_48_4_GR_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(10000u, BT_BAP_CONFIG_LATENCY_LOW, \ LC3_QOS_48_4_GR_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_4, \ LC3_QOS_48_4_GR_RTN) #define LC3_QOS_48_4_GR LC3_QOS_48_4_GR_AC(1) #define LC3_QOS_48_5_1_LATENCY 15u #define LC3_QOS_48_5_1_RTN 5u #define LC3_QOS_48_5_1_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_48_5_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_5, \ LC3_QOS_48_5_1_RTN) #define LC3_QOS_48_5_1 LC3_QOS_48_5_1_AC(1) #define LC3_QOS_48_5_2_LATENCY 75u #define LC3_QOS_48_5_2_RTN 13u #define LC3_QOS_48_5_2_AC(_ac) \ LC3_QOS_UCAST_7_5_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_48_5_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_5, \ LC3_QOS_48_5_2_RTN) #define LC3_QOS_48_5_2 LC3_QOS_48_5_2_AC(1) #define LC3_QOS_48_6_1_LATENCY 20u #define LC3_QOS_48_6_1_RTN 5u #define LC3_QOS_48_6_1_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_48_6_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_6, \ LC3_QOS_48_6_1_RTN) #define LC3_QOS_48_6_1 LC3_QOS_48_6_1_AC(1) #define LC3_QOS_48_6_2_LATENCY 100u #define LC3_QOS_48_6_2_RTN 13u #define LC3_QOS_48_6_2_AC(_ac) \ LC3_QOS_UCAST_10_UNFRAMED(40000u, BT_BAP_CONFIG_LATENCY_BALANCED, \ LC3_QOS_48_6_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_6, \ LC3_QOS_48_6_2_RTN) #define LC3_QOS_48_6_2 LC3_QOS_48_6_2_AC(1) #define LC3_QOS_BCAST(_frame, _pd, _interval, _lat, _sdu, _rtn) \ { \ .bcast.big = 0x00, \ .bcast.bis = 0x00, \ .bcast.framing = _frame, \ .bcast.delay = _pd, \ .bcast.io_qos.interval = _interval, \ .bcast.io_qos.latency = _lat, \ .bcast.io_qos.sdu = _sdu, \ .bcast.io_qos.phys = BT_BAP_CONFIG_PHY_2M, \ .bcast.io_qos.rtn = _rtn, \ } #define LC3_QOS_BCAST_7_5_UNFRAMED(_pd, _lat, _sdu, _rtn) \ LC3_QOS_BCAST(LC3_QOS_UNFRAMED, _pd, 7500u, _lat, _sdu, _rtn) #define LC3_QOS_BCAST_10_UNFRAMED(_pd, _lat, _sdu, _rtn) \ LC3_QOS_BCAST(LC3_QOS_UNFRAMED, _pd, 10000u, _lat, _sdu, _rtn) #define LC3_QOS_BCAST_FRAMED(_pd, _interval, _lat, _sdu, _rtn) \ LC3_QOS_BCAST(LC3_QOS_FRAMED, _pd, _interval, _lat, _sdu, _rtn) #define LC3_QOS_8_1_1_B_AC(_ac) \ LC3_QOS_BCAST_7_5_UNFRAMED(40000u, LC3_QOS_8_1_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_8_1, \ LC3_QOS_8_1_1_RTN) #define LC3_QOS_8_1_1_B LC3_QOS_8_1_1_B_AC(1) #define LC3_QOS_8_1_2_B_LATENCY 75u #define LC3_QOS_8_1_2_B_RTN 4u #define LC3_QOS_8_1_2_B_AC(_ac) \ LC3_QOS_BCAST_7_5_UNFRAMED(40000u, LC3_QOS_8_1_2_B_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_8_1, \ LC3_QOS_8_1_2_B_RTN) #define LC3_QOS_8_1_2_B LC3_QOS_8_1_2_B_AC(1) #define LC3_QOS_8_2_1_B_AC(_ac) \ LC3_QOS_BCAST_10_UNFRAMED(40000u, LC3_QOS_8_2_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_8_2, \ LC3_QOS_8_2_1_RTN) #define LC3_QOS_8_2_1_B LC3_QOS_8_2_1_B_AC(1) #define LC3_QOS_8_2_2_B_LATENCY 60u #define LC3_QOS_8_2_2_B_RTN 4u #define LC3_QOS_8_2_2_B_AC(_ac) \ LC3_QOS_BCAST_10_UNFRAMED(40000u, LC3_QOS_8_2_2_B_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_8_2, \ LC3_QOS_8_2_2_B_RTN) #define LC3_QOS_8_2_2_B LC3_QOS_8_2_2_B_AC(1) #define LC3_QOS_16_1_1_B_AC(_ac) \ LC3_QOS_BCAST_7_5_UNFRAMED(40000u, LC3_QOS_16_1_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_16_1, \ LC3_QOS_16_1_1_RTN) #define LC3_QOS_16_1_1_B LC3_QOS_16_1_1_B_AC(1) #define LC3_QOS_16_1_2_B_LATENCY 45u #define LC3_QOS_16_1_2_B_RTN 4u #define LC3_QOS_16_1_2_B_AC(_ac) \ LC3_QOS_BCAST_7_5_UNFRAMED(40000u, LC3_QOS_16_1_2_B_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_16_1, \ LC3_QOS_16_1_2_B_RTN) #define LC3_QOS_16_1_2_B LC3_QOS_16_1_2_B_AC(1) #define LC3_QOS_16_2_1_B_AC(_ac) \ LC3_QOS_BCAST_10_UNFRAMED(40000u, LC3_QOS_16_2_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_16_2, \ LC3_QOS_16_2_1_RTN) #define LC3_QOS_16_2_1_B LC3_QOS_16_2_1_B_AC(1) #define LC3_QOS_16_2_2_B_LATENCY 60u #define LC3_QOS_16_2_2_B_RTN 4u #define LC3_QOS_16_2_2_B_AC(_ac) \ LC3_QOS_BCAST_10_UNFRAMED(40000u, LC3_QOS_16_2_2_B_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_16_2, \ LC3_QOS_16_2_2_B_RTN) #define LC3_QOS_16_2_2_B LC3_QOS_16_2_2_B_AC(1) #define LC3_QOS_24_1_1_B_AC(_ac) \ LC3_QOS_BCAST_7_5_UNFRAMED(40000u, LC3_QOS_24_1_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_24_1, \ LC3_QOS_24_1_1_RTN) #define LC3_QOS_24_1_1_B LC3_QOS_24_1_1_B_AC(1) #define LC3_QOS_24_1_2_B_LATENCY 45u #define LC3_QOS_24_1_2_B_RTN 4u #define LC3_QOS_24_1_2_B_AC(_ac) \ LC3_QOS_BCAST_7_5_UNFRAMED(40000u, LC3_QOS_24_1_2_B_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_24_1, \ LC3_QOS_24_1_2_B_RTN) #define LC3_QOS_24_1_2_B LC3_QOS_24_1_2_B_AC(1) #define LC3_QOS_24_2_1_B_AC(_ac) \ LC3_QOS_BCAST_10_UNFRAMED(40000u, LC3_QOS_24_2_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_24_2, \ LC3_QOS_24_2_1_RTN) #define LC3_QOS_24_2_1_B LC3_QOS_24_2_1_B_AC(1) #define LC3_QOS_24_2_2_B_LATENCY 60u #define LC3_QOS_24_2_2_B_RTN 4u #define LC3_QOS_24_2_2_B_AC(_ac) \ LC3_QOS_BCAST_10_UNFRAMED(40000u, LC3_QOS_24_2_2_B_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_24_2, \ LC3_QOS_24_2_2_B_RTN) #define LC3_QOS_24_2_2_B LC3_QOS_24_2_2_B_AC(1) #define LC3_QOS_32_1_1_B_AC(_ac) \ LC3_QOS_BCAST_7_5_UNFRAMED(40000u, LC3_QOS_32_1_1_LATENCY, \ LC3_CONFIG_FRAME_LEN_32_1, \ LC3_QOS_32_1_1_RTN) #define LC3_QOS_32_1_1_B LC3_QOS_32_1_1_B_AC(1) #define LC3_QOS_32_1_2_B_LATENCY 45u #define LC3_QOS_32_1_2_B_RTN 4u #define LC3_QOS_32_1_2_B_AC(_ac) \ LC3_QOS_BCAST_7_5_UNFRAMED(40000u, LC3_QOS_32_1_2_B_LATENCY, \ LC3_CONFIG_FRAME_LEN_32_1, \ LC3_QOS_32_1_2_B_RTN) #define LC3_QOS_32_1_2_B LC3_QOS_32_1_2_B_AC(1) #define LC3_QOS_32_2_1_B_AC(_ac) \ LC3_QOS_BCAST_10_UNFRAMED(40000u, LC3_QOS_32_2_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_32_2, \ LC3_QOS_32_2_1_RTN) #define LC3_QOS_32_2_1_B LC3_QOS_32_2_1_B_AC(1) #define LC3_QOS_32_2_2_B_LATENCY 60u #define LC3_QOS_32_2_2_B_RTN 4u #define LC3_QOS_32_2_2_B_AC(_ac) \ LC3_QOS_BCAST_10_UNFRAMED(40000u, LC3_QOS_32_2_2_B_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_32_2, \ LC3_QOS_32_2_2_B_RTN) #define LC3_QOS_32_2_2_B LC3_QOS_32_2_2_B_AC(1) #define LC3_QOS_44_1_1_B_RTN 4u #define LC3_QOS_44_1_1_B_AC(_ac) \ LC3_QOS_BCAST_FRAMED(40000u, LC3_QOS_44_1_INTERVAL, \ LC3_QOS_44_1_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_44_1, \ LC3_QOS_44_1_1_B_RTN) #define LC3_QOS_44_1_1_B LC3_QOS_44_1_1_B_AC(1) #define LC3_QOS_44_1_2_B_LATENCY 54u #define LC3_QOS_44_1_2_B_RTN 4u #define LC3_QOS_44_1_2_B_AC(_ac) \ LC3_QOS_BCAST_FRAMED(40000u, LC3_QOS_44_1_INTERVAL, \ LC3_QOS_44_1_2_B_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_44_1, \ LC3_QOS_44_1_2_B_RTN) #define LC3_QOS_44_1_2_B LC3_QOS_44_1_2_B_AC(1) #define LC3_QOS_44_2_1_B_RTN 4u #define LC3_QOS_44_2_1_B_AC(_ac) \ LC3_QOS_BCAST_FRAMED(40000u, LC3_QOS_44_2_INTERVAL, \ LC3_QOS_44_2_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_44_2, \ LC3_QOS_44_2_1_B_RTN) #define LC3_QOS_44_2_1_B LC3_QOS_44_2_1_B_AC(1) #define LC3_QOS_44_2_2_B_LATENCY 60u #define LC3_QOS_44_2_2_B_RTN 4u #define LC3_QOS_44_2_2_B_AC(_ac) \ LC3_QOS_BCAST_FRAMED(40000u, LC3_QOS_44_2_INTERVAL, \ LC3_QOS_44_2_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_44_2, \ LC3_QOS_44_2_2_RTN) #define LC3_QOS_44_2_2_B LC3_QOS_44_2_2_B_AC(1) #define LC3_QOS_48_1_1_B_RTN 4u #define LC3_QOS_48_1_1_B_AC(_ac) \ LC3_QOS_BCAST_7_5_UNFRAMED(40000u, LC3_QOS_48_1_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_1,\ LC3_QOS_48_1_1_B_RTN) #define LC3_QOS_48_1_1_B LC3_QOS_48_1_1_B_AC(1) #define LC3_QOS_48_1_2_B_LATENCY 50u #define LC3_QOS_48_1_2_B_RTN 4u #define LC3_QOS_48_1_2_B_AC(_ac) \ LC3_QOS_BCAST_7_5_UNFRAMED(40000u, LC3_QOS_48_1_2_B_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_1, \ LC3_QOS_48_1_2_B_RTN) #define LC3_QOS_48_1_2_B LC3_QOS_48_1_2_B_AC(1) #define LC3_QOS_48_2_1_B_RTN 4u #define LC3_QOS_48_2_1_B_AC(_ac) \ LC3_QOS_BCAST_10_UNFRAMED(40000u, LC3_QOS_48_2_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_2, \ LC3_QOS_48_2_1_B_RTN) #define LC3_QOS_48_2_1_B LC3_QOS_48_2_1_B_AC(1) #define LC3_QOS_48_2_2_B_LATENCY 65u #define LC3_QOS_48_2_2_B_RTN 4u #define LC3_QOS_48_2_2_B_AC(_ac) \ LC3_QOS_BCAST_10_UNFRAMED(40000u, LC3_QOS_48_2_2_B_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_2, \ LC3_QOS_48_2_2_B_RTN) #define LC3_QOS_48_2_2_B LC3_QOS_48_2_2_B_AC(1) #define LC3_QOS_48_3_1_B_RTN 4u #define LC3_QOS_48_3_1_B_AC(_ac) \ LC3_QOS_BCAST_7_5_UNFRAMED(40000u, LC3_QOS_48_3_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_3, \ LC3_QOS_48_3_1_B_RTN) #define LC3_QOS_48_3_1_B LC3_QOS_48_3_1_B_AC(1) #define LC3_QOS_48_3_2_B_LATENCY 50u #define LC3_QOS_48_3_2_B_RTN 4u #define LC3_QOS_48_3_2_B_AC(_ac) \ LC3_QOS_BCAST_7_5_UNFRAMED(40000u, LC3_QOS_48_3_2_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_3, \ LC3_QOS_48_3_2_RTN) #define LC3_QOS_48_3_2_B LC3_QOS_48_3_2_B_AC(1) #define LC3_QOS_48_4_1_B_RTN 4u #define LC3_QOS_48_4_1_B_AC(_ac) \ LC3_QOS_BCAST_10_UNFRAMED(40000u, LC3_QOS_48_4_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_4, \ LC3_QOS_48_4_1_B_RTN) #define LC3_QOS_48_4_1_B LC3_QOS_48_4_1_B_AC(1) #define LC3_QOS_48_4_2_B_LATENCY 65u #define LC3_QOS_48_4_2_B_RTN 4u #define LC3_QOS_48_4_2_B_AC(_ac) \ LC3_QOS_BCAST_10_UNFRAMED(40000u, LC3_QOS_48_4_2_B_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_4, \ LC3_QOS_48_4_2_B_RTN) #define LC3_QOS_48_4_2_B LC3_QOS_48_4_2_B_AC(1) #define LC3_QOS_48_5_1_B_RTN 4u #define LC3_QOS_48_5_1_B_AC(_ac) \ LC3_QOS_BCAST_7_5_UNFRAMED(40000u, LC3_QOS_48_5_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_5, \ LC3_QOS_48_5_1_B_RTN) #define LC3_QOS_48_5_1_B LC3_QOS_48_5_1_B_AC(1) #define LC3_QOS_48_5_2_B_LATENCY 50u #define LC3_QOS_48_5_2_B_RTN 4u #define LC3_QOS_48_5_2_B_AC(_ac) \ LC3_QOS_BCAST_7_5_UNFRAMED(40000u, LC3_QOS_48_5_2_B_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_5, \ LC3_QOS_48_5_2_B_RTN) #define LC3_QOS_48_5_2_B LC3_QOS_48_5_2_B_AC(1) #define LC3_QOS_48_6_1_B_RTN 5u #define LC3_QOS_48_6_1_B_AC(_ac) \ LC3_QOS_BCAST_10_UNFRAMED(40000u, LC3_QOS_48_6_1_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_6, \ LC3_QOS_48_6_1_B_RTN) #define LC3_QOS_48_6_1_B LC3_QOS_48_6_1_B_AC(1) #define LC3_QOS_48_6_2_B_LATENCY 65u #define LC3_QOS_48_6_2_B_RTN 4u #define LC3_QOS_48_6_2_B_AC(_ac) \ LC3_QOS_BCAST_10_UNFRAMED(40000u, LC3_QOS_48_6_2_B_LATENCY, \ (_ac) * LC3_CONFIG_FRAME_LEN_48_6, \ LC3_QOS_48_6_2_B_RTN) #define LC3_QOS_48_6_2_B LC3_QOS_48_6_2_B_AC(1) bluez-5.87/src/shared/PaxHeaders/gatt-client.h0000644000000000000000000000005014572354773016315 xustar0020 atime=1783092914 20 ctime=1783094100 bluez-5.87/src/shared/gatt-client.h0000644000000000000000000001254214572354773016002 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Google Inc. * * */ #include #include #include #define BT_GATT_UUID_SIZE 16 struct bt_gatt_client; struct bt_gatt_client *bt_gatt_client_new(struct gatt_db *db, struct bt_att *att, uint16_t mtu, uint8_t features); struct bt_gatt_client *bt_gatt_client_clone(struct bt_gatt_client *client); struct bt_gatt_client *bt_gatt_client_ref(struct bt_gatt_client *client); void bt_gatt_client_unref(struct bt_gatt_client *client); typedef void (*bt_gatt_client_destroy_func_t)(void *user_data); typedef void (*bt_gatt_client_idle_callback_t)(void *user_data); typedef void (*bt_gatt_client_callback_t)(bool success, uint8_t att_ecode, void *user_data); typedef void (*bt_gatt_client_debug_func_t)(const char *str, void *user_data); typedef void (*bt_gatt_client_read_callback_t)(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data); typedef void (*bt_gatt_client_write_long_callback_t)(bool success, bool reliable_error, uint8_t att_ecode, void *user_data); typedef void (*bt_gatt_client_notify_callback_t)(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data); typedef void (*bt_gatt_client_register_callback_t)(uint16_t att_ecode, void *user_data); typedef void (*bt_gatt_client_service_changed_callback_t)(uint16_t start_handle, uint16_t end_handle, void *user_data); bool bt_gatt_client_is_ready(struct bt_gatt_client *client); unsigned int bt_gatt_client_ready_register(struct bt_gatt_client *client, bt_gatt_client_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy); bool bt_gatt_client_ready_unregister(struct bt_gatt_client *client, unsigned int id); bool bt_gatt_client_set_service_changed(struct bt_gatt_client *client, bt_gatt_client_service_changed_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy); bool bt_gatt_client_set_debug(struct bt_gatt_client *client, bt_gatt_client_debug_func_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy); uint16_t bt_gatt_client_get_mtu(struct bt_gatt_client *client); struct bt_att *bt_gatt_client_get_att(struct bt_gatt_client *client); struct gatt_db *bt_gatt_client_get_db(struct bt_gatt_client *client); uint8_t bt_gatt_client_get_features(struct bt_gatt_client *client); bool bt_gatt_client_cancel(struct bt_gatt_client *client, unsigned int id); bool bt_gatt_client_cancel_all(struct bt_gatt_client *client); unsigned int bt_gatt_client_read_value(struct bt_gatt_client *client, uint16_t value_handle, bt_gatt_client_read_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy); unsigned int bt_gatt_client_read_long_value(struct bt_gatt_client *client, uint16_t value_handle, uint16_t offset, bt_gatt_client_read_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy); unsigned int bt_gatt_client_read_multiple(struct bt_gatt_client *client, uint16_t *handles, uint8_t num_handles, bt_gatt_client_read_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy); unsigned int bt_gatt_client_write_without_response( struct bt_gatt_client *client, uint16_t value_handle, bool signed_write, const uint8_t *value, uint16_t length); unsigned int bt_gatt_client_write_value(struct bt_gatt_client *client, uint16_t value_handle, const uint8_t *value, uint16_t length, bt_gatt_client_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy); unsigned int bt_gatt_client_write_long_value(struct bt_gatt_client *client, bool reliable, uint16_t value_handle, uint16_t offset, const uint8_t *value, uint16_t length, bt_gatt_client_write_long_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy); unsigned int bt_gatt_client_prepare_write(struct bt_gatt_client *client, unsigned int id, uint16_t value_handle, uint16_t offset, const uint8_t *value, uint16_t length, bt_gatt_client_write_long_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy); unsigned int bt_gatt_client_write_execute(struct bt_gatt_client *client, unsigned int id, bt_gatt_client_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy); unsigned int bt_gatt_client_register_notify(struct bt_gatt_client *client, uint16_t chrc_value_handle, bt_gatt_client_register_callback_t callback, bt_gatt_client_notify_callback_t notify, void *user_data, bt_gatt_client_destroy_func_t destroy); bool bt_gatt_client_unregister_notify(struct bt_gatt_client *client, unsigned int id); bool bt_gatt_client_set_security(struct bt_gatt_client *client, int level); int bt_gatt_client_get_security(struct bt_gatt_client *client); unsigned int bt_gatt_client_idle_register(struct bt_gatt_client *client, bt_gatt_client_idle_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy); bool bt_gatt_client_idle_unregister(struct bt_gatt_client *client, unsigned int id); bool bt_gatt_client_set_retry(struct bt_gatt_client *client, unsigned int id, bool retry); bluez-5.87/src/shared/PaxHeaders/gatt-server.h0000644000000000000000000000005015200607060016320 xustar0020 atime=1783092683 20 ctime=1783094100 bluez-5.87/src/shared/gatt-server.h0000644000000000000000000000325415200607060016005 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Google Inc. * * */ #include struct bt_gatt_server; struct bt_gatt_server *bt_gatt_server_new(struct gatt_db *db, struct bt_att *att, uint16_t mtu, uint8_t min_enc_size); uint16_t bt_gatt_server_get_mtu(struct bt_gatt_server *server); struct bt_att *bt_gatt_server_get_att(struct bt_gatt_server *server); void bt_gatt_server_set_permissions(struct bt_gatt_server *server, bool value); struct bt_gatt_server *bt_gatt_server_ref(struct bt_gatt_server *server); void bt_gatt_server_unref(struct bt_gatt_server *server); typedef void (*bt_gatt_server_destroy_func_t)(void *user_data); typedef void (*bt_gatt_server_debug_func_t)(const char *str, void *user_data); typedef void (*bt_gatt_server_conf_func_t)(void *user_data); bool bt_gatt_server_set_debug(struct bt_gatt_server *server, bt_gatt_server_debug_func_t callback, void *user_data, bt_gatt_server_destroy_func_t destroy); typedef uint8_t (*bt_gatt_server_authorize_cb_t)(struct bt_att *att, uint8_t opcode, uint16_t handle, void *user_data); bool bt_gatt_server_set_authorize(struct bt_gatt_server *server, bt_gatt_server_authorize_cb_t cb, void *user_data); bool bt_gatt_server_send_notification(struct bt_gatt_server *server, uint16_t handle, const uint8_t *value, uint16_t length, bool multiple); bool bt_gatt_server_send_indication(struct bt_gatt_server *server, uint16_t handle, const uint8_t *value, uint16_t length, bt_gatt_server_conf_func_t callback, void *user_data, bt_gatt_server_destroy_func_t destroy); bluez-5.87/src/shared/PaxHeaders/tmap.h0000644000000000000000000000005015142177620015027 xustar0020 atime=1783092965 20 ctime=1783094100 bluez-5.87/src/shared/tmap.h0000644000000000000000000000312615142177620014512 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2025 Pauli Virtanen. All rights reserved. * */ #include #include #define BT_TMAP_ROLE_CG BIT(0) #define BT_TMAP_ROLE_CT BIT(1) #define BT_TMAP_ROLE_UMS BIT(2) #define BT_TMAP_ROLE_UMR BIT(3) #define BT_TMAP_ROLE_BMS BIT(4) #define BT_TMAP_ROLE_BMR BIT(5) #define BT_TMAP_ROLE_MASK (BIT(6) - 1) #define BT_TMAP_ROLE_CG_STR "cg" #define BT_TMAP_ROLE_CT_STR "ct" #define BT_TMAP_ROLE_UMS_STR "ums" #define BT_TMAP_ROLE_UMR_STR "umr" #define BT_TMAP_ROLE_BMS_STR "bms" #define BT_TMAP_ROLE_BMR_STR "bmr" #define BT_TMAP_ROLE_LIST(role) \ role(BT_TMAP_ROLE_CG) \ role(BT_TMAP_ROLE_CT) \ role(BT_TMAP_ROLE_UMS) \ role(BT_TMAP_ROLE_UMR) \ role(BT_TMAP_ROLE_BMS) \ role(BT_TMAP_ROLE_BMR) struct bt_tmap; typedef void (*bt_tmap_ready_func_t)(struct bt_tmap *tmap, void *user_data); typedef void (*bt_tmap_destroy_func_t)(void *user_data); typedef void (*bt_tmap_debug_func_t)(const char *str, void *user_data); struct bt_tmap *bt_tmap_ref(struct bt_tmap *tmap); void bt_tmap_unref(struct bt_tmap *tmap); struct bt_tmap *bt_tmap_attach(struct bt_gatt_client *client, bt_tmap_ready_func_t ready, void *user_data); struct bt_tmap *bt_tmap_find(struct gatt_db *db); struct bt_tmap *bt_tmap_add_db(struct gatt_db *db); uint16_t bt_tmap_get_role(struct bt_tmap *tmap); void bt_tmap_set_role(struct bt_tmap *tmas, uint16_t role); bool bt_tmap_set_debug(struct bt_tmap *tmap, bt_tmap_debug_func_t cb, void *user_data, bt_tmap_destroy_func_t destroy); bluez-5.87/src/shared/PaxHeaders/log.h0000644000000000000000000000005014015011623014633 xustar0020 atime=1783092930 20 ctime=1783094100 bluez-5.87/src/shared/log.h0000644000000000000000000000102214015011623014307 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ int bt_log_open(void); int bt_log_sendmsg(uint16_t index, const char *label, int level, struct iovec *io, size_t io_len); int bt_log_vprintf(uint16_t index, const char *label, int level, const char *format, va_list ap); int bt_log_printf(uint16_t index, const char *label, int level, const char *format, ...); void bt_log_close(void); bluez-5.87/src/shared/PaxHeaders/ccp.h0000644000000000000000000000005014572354773014647 xustar0020 atime=1783092963 20 ctime=1783094100 bluez-5.87/src/shared/ccp.h0000644000000000000000000000236114572354773014332 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2020 Intel Corporation. All rights reserved. * */ #include #include #ifndef __packed #define __packed __attribute__((packed)) #endif struct bt_ccp; struct bt_ccp_db; struct bt_ccp_session_info; typedef void (*bt_ccp_debug_func_t)(const char *str, void *user_data); typedef void (*bt_ccp_destroy_func_t)(void *user_data); struct bt_ccp_event_callback { void (*call_state)(struct bt_ccp *ccp, const uint8_t *value, uint16_t length); }; void bt_ccp_set_event_callbacks(struct bt_ccp *ccp, const struct bt_ccp_event_callback *cbs, void *user_data); bool bt_ccp_set_debug(struct bt_ccp *ccp, bt_ccp_debug_func_t cb, void *user_data, bt_ccp_destroy_func_t destroy); void bt_ccp_register(struct gatt_db *db); bool bt_ccp_attach(struct bt_ccp *ccp, struct bt_gatt_client *client); void bt_ccp_detach(struct bt_ccp *ccp); struct bt_ccp *bt_ccp_new(struct gatt_db *ldb, struct gatt_db *rdb); struct bt_ccp *bt_ccp_ref(struct bt_ccp *ccp); void bt_ccp_unref(struct bt_ccp *ccp); bool bt_ccp_set_user_data(struct bt_ccp *ccp, void *user_data); void *bt_ccp_get_user_data(struct bt_ccp *ccp); bluez-5.87/src/shared/PaxHeaders/ad.h0000644000000000000000000000005015142177620014452 xustar0020 atime=1783092780 20 ctime=1783094099 bluez-5.87/src/shared/ad.h0000644000000000000000000001214015142177620014131 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Google Inc. * * */ #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #define BT_AD_MAX_DATA_LEN 31 #define BT_EA_MAX_DATA_LEN 251 #define BT_PA_MAX_DATA_LEN 252 #define BT_AD_FLAGS 0x01 #define BT_AD_UUID16_SOME 0x02 #define BT_AD_UUID16_ALL 0x03 #define BT_AD_UUID32_SOME 0x04 #define BT_AD_UUID32_ALL 0x05 #define BT_AD_UUID128_SOME 0x06 #define BT_AD_UUID128_ALL 0x07 #define BT_AD_NAME_SHORT 0x08 #define BT_AD_NAME_COMPLETE 0x09 #define BT_AD_TX_POWER 0x0a #define BT_AD_CLASS_OF_DEV 0x0d #define BT_AD_SSP_HASH 0x0e #define BT_AD_SSP_RANDOMIZER 0x0f #define BT_AD_DEVICE_ID 0x10 #define BT_AD_SMP_TK 0x10 #define BT_AD_SMP_OOB_FLAGS 0x11 #define BT_AD_PERIPHERAL_CONN_INTERVAL 0x12 #define BT_AD_SOLICIT16 0x14 #define BT_AD_SOLICIT128 0x15 #define BT_AD_SERVICE_DATA16 0x16 #define BT_AD_PUBLIC_ADDRESS 0x17 #define BT_AD_RANDOM_ADDRESS 0x18 #define BT_AD_GAP_APPEARANCE 0x19 #define BT_AD_ADVERTISING_INTERVAL 0x1a #define BT_AD_LE_DEVICE_ADDRESS 0x1b #define BT_AD_LE_ROLE 0x1c #define BT_AD_SSP_HASH_P256 0x1d #define BT_AD_SSP_RANDOMIZER_P256 0x1e #define BT_AD_SOLICIT32 0x1f #define BT_AD_SERVICE_DATA32 0x20 #define BT_AD_SERVICE_DATA128 0x21 #define BT_AD_LE_SC_CONFIRM_VALUE 0x22 #define BT_AD_LE_SC_RANDOM_VALUE 0x23 #define BT_AD_URI 0x24 #define BT_AD_INDOOR_POSITIONING 0x25 #define BT_AD_TRANSPORT_DISCOVERY 0x26 #define BT_AD_LE_SUPPORTED_FEATURES 0x27 #define BT_AD_CHANNEL_MAP_UPDATE_IND 0x28 #define BT_AD_MESH_PROV 0x29 #define BT_AD_MESH_DATA 0x2a #define BT_AD_MESH_BEACON 0x2b #define BT_AD_CSIP_RSI 0x2e #define BT_AD_3D_INFO_DATA 0x3d #define BT_AD_MANUFACTURER_DATA 0xff /* Low Energy Advertising Flags */ #define BT_AD_FLAG_LIMITED 0x01 /* Limited Discoverable */ #define BT_AD_FLAG_GENERAL 0x02 /* General Discoverable */ #define BT_AD_FLAG_NO_BREDR 0x04 /* BR/EDR not supported */ typedef void (*bt_ad_func_t)(void *data, void *user_data); struct bt_ad; struct queue; struct bt_ad_manufacturer_data { uint16_t manufacturer_id; uint8_t *data; size_t len; }; struct bt_ad_service_data { bt_uuid_t uuid; size_t len; void *data; }; struct bt_ad_data { uint8_t type; uint8_t *data; size_t len; }; struct bt_ad_pattern { uint8_t type; uint8_t offset; uint8_t len; uint8_t data[BT_AD_MAX_DATA_LEN]; }; struct bt_ad *bt_ad_new(void); bool bt_ad_set_max_len(struct bt_ad *ad, uint8_t len); struct bt_ad *bt_ad_new_with_data(size_t len, const uint8_t *data); struct bt_ad *bt_ad_ref(struct bt_ad *ad); void bt_ad_unref(struct bt_ad *ad); size_t bt_ad_length(struct bt_ad *ad); uint8_t *bt_ad_generate(struct bt_ad *ad, size_t *length); bool bt_ad_is_empty(struct bt_ad *ad); bool bt_ad_add_service_uuid(struct bt_ad *ad, const bt_uuid_t *uuid); bool bt_ad_has_service_uuid(struct bt_ad *ad, const bt_uuid_t *uuid); bool bt_ad_remove_service_uuid(struct bt_ad *ad, bt_uuid_t *uuid); void bt_ad_clear_service_uuid(struct bt_ad *ad); bool bt_ad_add_manufacturer_data(struct bt_ad *ad, uint16_t id, void *data, size_t len); bool bt_ad_has_manufacturer_data(struct bt_ad *ad, const struct bt_ad_manufacturer_data *data); void bt_ad_foreach_manufacturer_data(struct bt_ad *ad, bt_ad_func_t func, void *user_data); bool bt_ad_remove_manufacturer_data(struct bt_ad *ad, uint16_t manufacturer_id); void bt_ad_clear_manufacturer_data(struct bt_ad *ad); bool bt_ad_add_solicit_uuid(struct bt_ad *ad, const bt_uuid_t *uuid); bool bt_ad_remove_solicit_uuid(struct bt_ad *ad, bt_uuid_t *uuid); void bt_ad_clear_solicit_uuid(struct bt_ad *ad); bool bt_ad_add_service_data(struct bt_ad *ad, const bt_uuid_t *uuid, void *data, size_t len); bool bt_ad_has_service_data(struct bt_ad *ad, const struct bt_ad_service_data *data); void bt_ad_foreach_service_data(struct bt_ad *ad, bt_ad_func_t func, void *user_data); bool bt_ad_remove_service_data(struct bt_ad *ad, bt_uuid_t *uuid); void bt_ad_clear_service_data(struct bt_ad *ad); bool bt_ad_add_name(struct bt_ad *ad, const char *name); const char *bt_ad_get_name(struct bt_ad *ad); void bt_ad_clear_name(struct bt_ad *ad); bool bt_ad_add_appearance(struct bt_ad *ad, uint16_t appearance); void bt_ad_clear_appearance(struct bt_ad *ad); bool bt_ad_add_flags(struct bt_ad *ad, uint8_t *flags, size_t len); bool bt_ad_has_flags(struct bt_ad *ad); uint8_t bt_ad_get_flags(struct bt_ad *ad); void bt_ad_clear_flags(struct bt_ad *ad); bool bt_ad_add_data(struct bt_ad *ad, uint8_t type, void *data, size_t len); struct bt_ad_data *bt_ad_has_data(struct bt_ad *ad, const struct bt_ad_data *data); void bt_ad_foreach_data(struct bt_ad *ad, bt_ad_func_t func, void *user_data); bool bt_ad_remove_data(struct bt_ad *ad, uint8_t type); void bt_ad_clear_data(struct bt_ad *ad); int8_t bt_ad_get_tx_power(struct bt_ad *ad); struct bt_ad_pattern *bt_ad_pattern_new(uint8_t type, size_t offset, size_t len, const uint8_t *data); struct bt_ad_pattern *bt_ad_pattern_match(struct bt_ad *ad, struct queue *patterns); bluez-5.87/src/shared/PaxHeaders/bap.h0000644000000000000000000000005015200607060014617 xustar0020 atime=1783092683 20 ctime=1783094100 bluez-5.87/src/shared/bap.h0000644000000000000000000002554515200607060014313 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. * Copyright 2023-2025 NXP * */ #include #include #include "src/shared/bap-defs.h" struct bt_bap; struct bt_bap_pac; struct bt_bap_stream; typedef void (*bt_bap_ready_func_t)(struct bt_bap *bap, void *user_data); typedef void (*bt_bap_destroy_func_t)(void *user_data); typedef void (*bt_bap_debug_func_t)(const char *str, void *user_data); typedef void (*bt_bap_pac_func_t)(struct bt_bap_pac *pac, void *user_data); typedef bool (*bt_bap_pac_foreach_t)(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, void *user_data); typedef void (*bt_bap_pac_select_t)(struct bt_bap_pac *pac, int err, struct iovec *caps, struct iovec *metadata, struct bt_bap_qos *qos, void *user_data); typedef void (*bt_bap_pac_config_t)(struct bt_bap_stream *stream, int err); typedef void (*bt_bap_state_func_t)(struct bt_bap_stream *stream, uint8_t old_state, uint8_t new_state, void *user_data); typedef void (*bt_bap_connecting_func_t)(struct bt_bap_stream *stream, bool state, int fd, void *user_data); typedef void (*bt_bap_stream_func_t)(struct bt_bap_stream *stream, uint8_t code, uint8_t reason, void *user_data); typedef void (*bt_bap_func_t)(struct bt_bap *bap, void *user_data); typedef void (*bt_bap_bis_func_t)(uint8_t sid, uint8_t bis, uint8_t sgrp, struct iovec *caps, struct iovec *meta, struct bt_bap_qos *qos, void *user_data); typedef void (*bt_bap_bcode_reply_t)(void *user_data, int err); typedef void (*bt_bap_bcode_func_t)(struct bt_bap_stream *stream, bt_bap_bcode_reply_t reply, void *reply_data, void *user_data); extern struct bt_iso_qos bap_sink_pa_qos; /* Local PAC related functions */ struct bt_bap_pac_qos { uint8_t framing; uint8_t phys; uint8_t rtn; uint16_t latency; uint32_t pd_min; uint32_t pd_max; uint32_t ppd_min; uint32_t ppd_max; uint32_t location; uint16_t supported_context; uint16_t context; }; struct bt_bap_pac_ops { int (*select)(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, uint32_t chan_alloc, struct bt_bap_pac_qos *qos, bt_bap_pac_select_t cb, void *cb_data, void *user_data); void (*cancel_select)(struct bt_bap_pac *lpac, bt_bap_pac_select_t cb, void *cb_data, void *user_data); int (*config)(struct bt_bap_stream *stream, struct iovec *cfg, struct bt_bap_qos *qos, bt_bap_pac_config_t cb, void *user_data); void (*clear)(struct bt_bap_stream *stream, void *user_data); }; struct bt_bap_pac *bt_bap_add_vendor_pac_full(struct gatt_db *db, const char *name, uint8_t type, uint8_t id, uint16_t cid, uint16_t vid, struct bt_bap_pac_qos *qos, struct iovec *data, struct iovec *metadata, struct bt_bap_pac_ops *ops, void *ops_user_data); struct bt_bap_pac *bt_bap_add_vendor_pac(struct gatt_db *db, const char *name, uint8_t type, uint8_t id, uint16_t cid, uint16_t vid, struct bt_bap_pac_qos *qos, struct iovec *data, struct iovec *metadata); struct bt_bap_pac *bt_bap_add_pac(struct gatt_db *db, const char *name, uint8_t type, uint8_t id, struct bt_bap_pac_qos *qos, struct iovec *data, struct iovec *metadata); bool bt_bap_pac_set_ops(struct bt_bap_pac *pac, struct bt_bap_pac_ops *ops, void *user_data); bool bt_bap_remove_pac(struct bt_bap_pac *pac); uint8_t bt_bap_pac_get_type(struct bt_bap_pac *pac); uint32_t bt_bap_pac_get_locations(struct bt_bap_pac *pac); uint16_t bt_bap_pac_get_supported_context(struct bt_bap_pac *pac); uint16_t bt_bap_pac_get_context(struct bt_bap_pac *pac); struct bt_bap_pac_qos *bt_bap_pac_get_qos(struct bt_bap_pac *pac); struct iovec *bt_bap_pac_get_data(struct bt_bap_pac *pac); struct iovec *bt_bap_pac_get_metadata(struct bt_bap_pac *pac); uint8_t bt_bap_stream_get_type(struct bt_bap_stream *stream); struct bt_bap_stream *bt_bap_pac_get_stream(struct bt_bap_pac *pac); /* Session related function */ unsigned int bt_bap_register(bt_bap_func_t added, bt_bap_func_t removed, void *user_data); bool bt_bap_unregister(unsigned int id); struct bt_bap *bt_bap_new(struct gatt_db *ldb, struct gatt_db *rdb); bool bt_bap_set_user_data(struct bt_bap *bap, void *user_data); void *bt_bap_get_user_data(struct bt_bap *bap); struct bt_att *bt_bap_get_att(struct bt_bap *bap); struct gatt_db *bt_bap_get_db(struct bt_bap *bap, bool remote); struct bt_bap *bt_bap_ref(struct bt_bap *bap); void bt_bap_unref(struct bt_bap *bap); bool bt_bap_attach(struct bt_bap *bap, struct bt_gatt_client *client); bool bt_bap_attach_broadcast(struct bt_bap *bap); void bt_bap_detach(struct bt_bap *bap); bool bt_bap_set_debug(struct bt_bap *bap, bt_bap_debug_func_t cb, void *user_data, bt_bap_destroy_func_t destroy); unsigned int bt_bap_pac_register(struct bt_bap *bap, bt_bap_pac_func_t added, bt_bap_pac_func_t removed, void *user_data, bt_bap_destroy_func_t destroy); bool bt_bap_pac_unregister(struct bt_bap *bap, unsigned int id); unsigned int bt_bap_ready_register(struct bt_bap *bap, bt_bap_ready_func_t func, void *user_data, bt_bap_destroy_func_t destroy); bool bt_bap_ready_unregister(struct bt_bap *bap, unsigned int id); unsigned int bt_bap_state_register(struct bt_bap *bap, bt_bap_state_func_t func, bt_bap_connecting_func_t connecting, void *user_data, bt_bap_destroy_func_t destroy); bool bt_bap_state_unregister(struct bt_bap *bap, unsigned int id); const char *bt_bap_stream_statestr(uint8_t state); void bt_bap_foreach_pac(struct bt_bap *bap, uint8_t type, bt_bap_pac_foreach_t func, void *user_data); int bt_bap_pac_get_vendor_codec(struct bt_bap_pac *pac, uint8_t *id, uint16_t *cid, uint16_t *vid, struct iovec **data, struct iovec **metadata); int bt_bap_pac_get_codec(struct bt_bap_pac *pac, uint8_t *id, struct iovec **data, struct iovec **metadata); void bt_bap_pac_set_user_data(struct bt_bap_pac *pac, void *user_data); void *bt_bap_pac_get_user_data(struct bt_bap_pac *pac); /* Stream related functions */ int bt_bap_select(struct bt_bap *bap, struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, unsigned int max_channels, int *count, bt_bap_pac_select_t func, void *user_data); void bt_bap_cancel_select(struct bt_bap_pac *lpac, bt_bap_pac_select_t func, void *user_data); struct bt_bap_stream *bt_bap_stream_new(struct bt_bap *bap, struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, struct bt_bap_qos *pqos, struct iovec *data); void bt_bap_stream_lock(struct bt_bap_stream *stream); void bt_bap_stream_unlock(struct bt_bap_stream *stream); struct bt_bap *bt_bap_stream_get_session(struct bt_bap_stream *stream); uint8_t bt_bap_stream_get_state(struct bt_bap_stream *stream); bool bt_bap_stream_set_user_data(struct bt_bap_stream *stream, void *user_data); void *bt_bap_stream_get_user_data(struct bt_bap_stream *stream); unsigned int bt_bap_stream_config(struct bt_bap_stream *stream, struct bt_bap_qos *pqos, struct iovec *data, bt_bap_stream_func_t func, void *user_data); unsigned int bt_bap_stream_qos(struct bt_bap_stream *stream, struct bt_bap_qos *qos, bt_bap_stream_func_t func, void *user_data); unsigned int bt_bap_stream_enable(struct bt_bap_stream *stream, bool enable_links, struct iovec *metadata, bt_bap_stream_func_t func, void *user_data); unsigned int bt_bap_stream_start(struct bt_bap_stream *stream, bt_bap_stream_func_t func, void *user_data); unsigned int bt_bap_stream_disable(struct bt_bap_stream *stream, bool disable_links, bt_bap_stream_func_t func, void *user_data); unsigned int bt_bap_stream_stop(struct bt_bap_stream *stream, bt_bap_stream_func_t func, void *user_data); unsigned int bt_bap_stream_metadata(struct bt_bap_stream *stream, struct iovec *metadata, bt_bap_stream_func_t func, void *user_data); unsigned int bt_bap_stream_release(struct bt_bap_stream *stream, bt_bap_stream_func_t func, void *user_data); uint8_t bt_bap_stream_get_dir(struct bt_bap_stream *stream); uint32_t bt_bap_stream_get_location(struct bt_bap_stream *stream); struct iovec *bt_bap_stream_get_config(struct bt_bap_stream *stream); struct bt_bap_qos *bt_bap_stream_get_qos(struct bt_bap_stream *stream); struct iovec *bt_bap_stream_get_metadata(struct bt_bap_stream *stream); struct io *bt_bap_stream_get_io(struct bt_bap_stream *stream); bool bt_bap_match_bcast_sink_stream(const void *data, const void *user_data); bool bt_bap_stream_set_io(struct bt_bap_stream *stream, int fd); int bt_bap_stream_cancel(struct bt_bap_stream *stream, unsigned int id); int bt_bap_stream_io_link(struct bt_bap_stream *stream, struct bt_bap_stream *link); int bt_bap_stream_io_unlink(struct bt_bap_stream *stream, struct bt_bap_stream *link); struct queue *bt_bap_stream_io_get_links(struct bt_bap_stream *stream); bool bt_bap_stream_io_get_qos(struct bt_bap_stream *stream, struct bt_bap_qos **in, struct bt_bap_qos **out); uint8_t bt_bap_stream_io_dir(struct bt_bap_stream *stream); int bt_bap_stream_io_connecting(struct bt_bap_stream *stream, int fd); bool bt_bap_stream_io_is_connecting(struct bt_bap_stream *stream, int *fd); bool bt_bap_new_bcast_source(struct bt_bap *bap, const char *name); void bt_bap_update_bcast_source(struct bt_bap_pac *pac, struct bt_bap_codec *codec, struct iovec *data, struct iovec *metadata); bool bt_bap_pac_bcast_is_local(struct bt_bap *bap, struct bt_bap_pac *pac); struct iovec *bt_bap_stream_get_base(struct bt_bap_stream *stream); struct iovec *bt_bap_merge_caps(struct iovec *l2_caps, struct iovec *l3_caps); void bt_bap_verify_bis(struct bt_bap *bap, uint8_t bis_index, struct iovec *caps, struct bt_bap_pac **lpac); bool bt_bap_parse_base(uint8_t sid, struct iovec *base, struct bt_bap_qos *qos, util_debug_func_t func, bt_bap_bis_func_t handler, void *user_data); unsigned int bt_bap_bis_cb_register(struct bt_bap *bap, bt_bap_bis_func_t probe, bt_bap_func_t remove, void *user_data, bt_bap_destroy_func_t destroy); bool bt_bap_bis_cb_unregister(struct bt_bap *bap, unsigned int id); void bt_bap_bis_probe(struct bt_bap *bap, uint8_t sid, uint8_t bis, uint8_t sgrp, struct iovec *caps, struct iovec *meta, struct bt_bap_qos *qos); void bt_bap_bis_remove(struct bt_bap *bap); void bt_bap_req_bcode(struct bt_bap_stream *stream, bt_bap_bcode_reply_t reply, void *reply_data); unsigned int bt_bap_bcode_cb_register(struct bt_bap *bap, bt_bap_bcode_func_t func, void *user_data, bt_bap_destroy_func_t destroy); bool bt_bap_bcode_cb_unregister(struct bt_bap *bap, unsigned int id); struct bt_bap *bt_bap_get_session(struct bt_att *att, struct gatt_db *db); void bt_bap_iso_qos_to_bap_qos(struct bt_iso_qos *iso_qos, struct bt_bap_qos *bap_qos); void bt_bap_qos_to_iso_qos(struct bt_bap_qos *bap_qos, struct bt_iso_qos *iso_qos); bluez-5.87/src/shared/PaxHeaders/gatt-db.h0000644000000000000000000000005015200607060015377 xustar0020 atime=1783092683 20 ctime=1783094100 bluez-5.87/src/shared/gatt-db.h0000644000000000000000000002374315200607060015071 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * */ struct gatt_db; struct gatt_db_attribute; struct gatt_db *gatt_db_new(void); struct gatt_db *gatt_db_clone(struct gatt_db *db); struct gatt_db *gatt_db_ref(struct gatt_db *db); void gatt_db_unref(struct gatt_db *db); bool gatt_db_isempty(struct gatt_db *db); struct gatt_db_attribute *gatt_db_add_service(struct gatt_db *db, const bt_uuid_t *uuid, bool primary, uint16_t num_handles); bool gatt_db_remove_service(struct gatt_db *db, struct gatt_db_attribute *attrib); bool gatt_db_clear(struct gatt_db *db); bool gatt_db_clear_range(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle); bool gatt_db_hash_support(struct gatt_db *db); uint8_t *gatt_db_get_hash(struct gatt_db *db); struct gatt_db_attribute *gatt_db_insert_service(struct gatt_db *db, uint16_t handle, const bt_uuid_t *uuid, bool primary, uint16_t num_handles); typedef void (*gatt_db_read_t) (struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data); typedef void (*gatt_db_write_t) (struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data); typedef void (*gatt_db_notify_t) (struct gatt_db_attribute *attrib, struct gatt_db_attribute *ccc, const uint8_t *value, size_t len, struct bt_att *att, void *user_data); struct gatt_db_attribute * gatt_db_service_add_characteristic(struct gatt_db_attribute *attrib, const bt_uuid_t *uuid, uint32_t permissions, uint8_t properties, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data); struct gatt_db_attribute * gatt_db_service_insert_characteristic(struct gatt_db_attribute *attrib, uint16_t handle, uint16_t value_handle, const bt_uuid_t *uuid, uint32_t permissions, uint8_t properties, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data); struct gatt_db_attribute * gatt_db_insert_characteristic(struct gatt_db *db, uint16_t handle, uint16_t value_handle, const bt_uuid_t *uuid, uint32_t permissions, uint8_t properties, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data); struct gatt_db_attribute * gatt_db_insert_descriptor(struct gatt_db *db, uint16_t handle, const bt_uuid_t *uuid, uint32_t permissions, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data); struct gatt_db_attribute * gatt_db_service_add_descriptor(struct gatt_db_attribute *attrib, const bt_uuid_t *uuid, uint32_t permissions, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data); struct gatt_db_attribute * gatt_db_service_insert_descriptor(struct gatt_db_attribute *attrib, uint16_t handle, const bt_uuid_t *uuid, uint32_t permissions, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data); struct gatt_db_attribute * gatt_db_service_add_ccc(struct gatt_db_attribute *attrib, uint32_t permissions); struct gatt_db_attribute * gatt_db_service_add_ccc_custom(struct gatt_db_attribute *attrib, uint32_t permissions, gatt_db_write_t write_func, void *user_data); struct gatt_db_attribute * gatt_db_insert_included(struct gatt_db *db, uint16_t handle, struct gatt_db_attribute *include); struct gatt_db_attribute * gatt_db_service_add_included(struct gatt_db_attribute *attrib, struct gatt_db_attribute *include); struct gatt_db_attribute * gatt_db_service_insert_included(struct gatt_db_attribute *attrib, uint16_t handle, struct gatt_db_attribute *include); bool gatt_db_service_set_active(struct gatt_db_attribute *attrib, bool active); bool gatt_db_service_get_active(struct gatt_db_attribute *attrib); bool gatt_db_service_set_claimed(struct gatt_db_attribute *attrib, bool claimed); bool gatt_db_service_get_claimed(struct gatt_db_attribute *attrib); typedef void (*gatt_db_attribute_cb_t)(struct gatt_db_attribute *attrib, void *user_data); void gatt_db_read_by_group_type(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle, const bt_uuid_t type, struct queue *queue); unsigned int gatt_db_find_by_type(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle, const bt_uuid_t *type, gatt_db_attribute_cb_t func, void *user_data); unsigned int gatt_db_find_by_type_value(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle, const bt_uuid_t *type, const void *value, size_t value_len, gatt_db_attribute_cb_t func, void *user_data); void gatt_db_read_by_type(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle, const bt_uuid_t type, struct queue *queue); void gatt_db_find_information(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle, struct queue *queue); void gatt_db_foreach_service(struct gatt_db *db, const bt_uuid_t *uuid, gatt_db_attribute_cb_t func, void *user_data); void gatt_db_foreach_in_range(struct gatt_db *db, const bt_uuid_t *uuid, gatt_db_attribute_cb_t func, void *user_data, uint16_t start_handle, uint16_t end_handle); void gatt_db_foreach_service_in_range(struct gatt_db *db, const bt_uuid_t *uuid, gatt_db_attribute_cb_t func, void *user_data, uint16_t start_handle, uint16_t end_handle); void gatt_db_service_foreach(struct gatt_db_attribute *attrib, const bt_uuid_t *uuid, gatt_db_attribute_cb_t func, void *user_data); void gatt_db_service_foreach_char(struct gatt_db_attribute *attrib, gatt_db_attribute_cb_t func, void *user_data); void gatt_db_service_foreach_desc(struct gatt_db_attribute *attrib, gatt_db_attribute_cb_t func, void *user_data); void gatt_db_service_foreach_incl(struct gatt_db_attribute *attrib, gatt_db_attribute_cb_t func, void *user_data); typedef void (*gatt_db_destroy_func_t)(void *user_data); unsigned int gatt_db_register(struct gatt_db *db, gatt_db_attribute_cb_t service_added, gatt_db_attribute_cb_t service_removed, void *user_data, gatt_db_destroy_func_t destroy); bool gatt_db_unregister(struct gatt_db *db, unsigned int id); void gatt_db_ccc_register(struct gatt_db *db, gatt_db_read_t read_func, gatt_db_write_t write_func, gatt_db_notify_t notify_func, void *user_data); typedef uint8_t (*gatt_db_authorize_cb_t)(struct gatt_db_attribute *attrib, uint8_t opcode, struct bt_att *att, void *user_data); bool gatt_db_set_authorize(struct gatt_db *db, gatt_db_authorize_cb_t cb, void *user_data); struct gatt_db_attribute *gatt_db_get_service(struct gatt_db *db, uint16_t handle); struct gatt_db_attribute *gatt_db_get_attribute(struct gatt_db *db, uint16_t handle); struct gatt_db_attribute *gatt_db_get_service_with_uuid(struct gatt_db *db, const bt_uuid_t *uuid); const bt_uuid_t *gatt_db_attribute_get_type( const struct gatt_db_attribute *attrib); uint16_t gatt_db_attribute_get_handle(const struct gatt_db_attribute *attrib); struct gatt_db_attribute * gatt_db_attribute_get_service(const struct gatt_db_attribute *attrib); bool gatt_db_attribute_get_service_uuid(const struct gatt_db_attribute *attrib, bt_uuid_t *uuid); bool gatt_db_attribute_get_service_handles( const struct gatt_db_attribute *attrib, uint16_t *start_handle, uint16_t *end_handle); bool gatt_db_attribute_get_service_data(const struct gatt_db_attribute *attrib, uint16_t *start_handle, uint16_t *end_handle, bool *primary, bt_uuid_t *uuid); bool gatt_db_attribute_get_char_data(const struct gatt_db_attribute *attrib, uint16_t *handle, uint16_t *value_handle, uint8_t *properties, uint16_t *ext_prop, bt_uuid_t *uuid); bool gatt_db_attribute_get_incl_data(const struct gatt_db_attribute *attrib, uint16_t *handle, uint16_t *start_handle, uint16_t *end_handle); uint32_t gatt_db_attribute_get_permissions(const struct gatt_db_attribute *attrib); bool gatt_db_attribute_set_fixed_length(struct gatt_db_attribute *attrib, uint16_t len); typedef void (*gatt_db_attribute_read_t) (struct gatt_db_attribute *attrib, int err, const uint8_t *value, size_t length, void *user_data); bool gatt_db_attribute_read(struct gatt_db_attribute *attrib, uint16_t offset, uint8_t opcode, struct bt_att *att, gatt_db_attribute_read_t func, void *user_data); bool gatt_db_attribute_read_result(struct gatt_db_attribute *attrib, unsigned int id, int err, const uint8_t *value, size_t length); typedef void (*gatt_db_attribute_write_t) (struct gatt_db_attribute *attrib, int err, void *user_data); bool gatt_db_attribute_write(struct gatt_db_attribute *attrib, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, gatt_db_attribute_write_t func, void *user_data); bool gatt_db_attribute_write_result(struct gatt_db_attribute *attrib, unsigned int id, int err); struct gatt_db_attribute * gatt_db_attribute_get_value(struct gatt_db_attribute *attrib); struct gatt_db_attribute * gatt_db_attribute_get_ccc(struct gatt_db_attribute *attrib); bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib, const uint8_t *value, size_t len, struct bt_att *att); bool gatt_db_attribute_reset(struct gatt_db_attribute *attrib); void *gatt_db_attribute_get_user_data(struct gatt_db_attribute *attrib); unsigned int gatt_db_attribute_register(struct gatt_db_attribute *attrib, gatt_db_attribute_cb_t removed, void *user_data, gatt_db_destroy_func_t destroy); bool gatt_db_attribute_unregister(struct gatt_db_attribute *attrib, unsigned int id); bluez-5.87/src/shared/PaxHeaders/util.h0000644000000000000000000000005015221752714015044 xustar0020 atime=1783092685 20 ctime=1783094099 bluez-5.87/src/shared/util.h0000644000000000000000000002234115221752714014527 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * Copyright 2023-2024 NXP * * */ #include #include #include #include #include #include #include #include #include #include #include #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) #define BIT(n) (1 << (n)) #undef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) #undef MAX #define MAX(a, b) ((a) > (b) ? (a) : (b)) #if __BYTE_ORDER == __LITTLE_ENDIAN #define le16_to_cpu(val) (val) #define le32_to_cpu(val) (val) #define le64_to_cpu(val) (val) #define cpu_to_le16(val) (val) #define cpu_to_le32(val) (val) #define cpu_to_le64(val) (val) #define be16_to_cpu(val) bswap_16(val) #define be32_to_cpu(val) bswap_32(val) #define be64_to_cpu(val) bswap_64(val) #define cpu_to_be16(val) bswap_16(val) #define cpu_to_be32(val) bswap_32(val) #define cpu_to_be64(val) bswap_64(val) #elif __BYTE_ORDER == __BIG_ENDIAN #define le16_to_cpu(val) bswap_16(val) #define le32_to_cpu(val) bswap_32(val) #define le64_to_cpu(val) bswap_64(val) #define cpu_to_le16(val) bswap_16(val) #define cpu_to_le32(val) bswap_32(val) #define cpu_to_le64(val) bswap_64(val) #define be16_to_cpu(val) (val) #define be32_to_cpu(val) (val) #define be64_to_cpu(val) (val) #define cpu_to_be16(val) (val) #define cpu_to_be32(val) (val) #define cpu_to_be64(val) (val) #else #error "Unknown byte order" #endif #define get_unaligned(ptr) \ __extension__ ({ \ struct __attribute__((packed)) { \ __typeof__(*(ptr)) __v; \ } *__p = (__typeof__(__p)) (ptr); \ __p->__v; \ }) #define put_unaligned(val, ptr) \ do { \ struct __attribute__((packed)) { \ __typeof__(*(ptr)) __v; \ } *__p = (__typeof__(__p)) (ptr); \ __p->__v = (val); \ } while (0) #define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) #define UINT_TO_PTR(u) ((void *) ((uintptr_t) (u))) #define PTR_TO_INT(p) ((int) ((intptr_t) (p))) #define INT_TO_PTR(u) ((void *) ((intptr_t) (u))) #define new0(type, count) \ (type *) (__extension__ ({ \ size_t __n = (size_t) (count); \ size_t __s = sizeof(type); \ void *__p; \ __p = util_malloc(__n * __s); \ memset(__p, 0, __n * __s); \ __p; \ })) #define newa(t, n) ((t*) alloca(sizeof(t)*(n))) #define malloc0(n) (calloc(1, (n))) static inline void freep(void *p) { free(*(void **) p); } static inline void *_steal_(void *p) { void **orig = (void **) p; void *ret = *orig; *orig = NULL; return ret; } static inline int _steal_fd_(int *fdp) { int fd = *fdp; *fdp = -1; return fd; } static inline void closefd(int *fdp) { int errno_save = errno; int fd = *fdp; *fdp = -1; if (fd < 0) return; close(fd); errno = errno_save; } #define CLEANUP_FREEFUNC(type, func) \ static inline void cleanup_##type (type **_ptr) { (func) (*_ptr); } #define _cleanup_(f) __attribute__((cleanup(f))) #define _cleanup_type_(type) __attribute__((cleanup(cleanup_##type))) #define _cleanup_free_ _cleanup_(freep) #define _cleanup_fd_ _cleanup_(closefd) char *strdelimit(char *str, char *del, char c); int strsuffix(const char *str, const char *suffix); char *strstrip(char *str); size_t strnlenutf8(const char *str, size_t len); bool strisutf8(const char *str, size_t length); bool argsisutf8(int argc, char *argv[]); char *strtoutf8(char *str, size_t len); void *util_malloc(size_t size); void *util_memdup(const void *src, size_t size); typedef void (*util_debug_func_t)(const char *str, void *user_data); void util_debug_va(util_debug_func_t function, void *user_data, const char *format, va_list va); void util_debug(util_debug_func_t function, void *user_data, const char *format, ...) __attribute__((format(printf, 3, 4))); void util_hexdump(const char dir, const unsigned char *buf, size_t len, util_debug_func_t function, void *user_data); #define UTIL_BIT_DEBUG(_bit, _str) \ { \ .bit = _bit, \ .str = _str, \ } struct util_bit_debugger { uint64_t bit; const char *str; }; uint64_t util_debug_bit(const char *label, uint64_t val, const struct util_bit_debugger *table, util_debug_func_t func, void *user_data); #define UTIL_LTV_DEBUG(_type, _func) \ { \ .type = _type, \ .func = _func, \ } struct util_ltv_debugger { uint8_t type; void (*func)(const uint8_t *data, uint8_t len, util_debug_func_t func, void *user_data); }; void util_ltv_push(struct iovec *output, uint8_t l, uint8_t t, void *v); bool util_debug_ltv(const uint8_t *data, uint8_t len, const struct util_ltv_debugger *debugger, size_t num, util_debug_func_t function, void *user_data); typedef void (*util_ltv_func_t)(size_t i, uint8_t l, uint8_t t, uint8_t *v, void *user_data); bool util_ltv_foreach(const uint8_t *data, uint8_t len, uint8_t *type, util_ltv_func_t func, void *user_data); unsigned char util_get_dt(const char *parent, const char *name); ssize_t util_getrandom(void *buf, size_t buflen, unsigned int flags); uint8_t util_get_uid(uint64_t *bitmap, uint8_t max); void util_clear_uid(uint64_t *bitmap, uint8_t id); #define util_data(args...) ((const unsigned char[]) { args }) #define UTIL_IOV_INIT(args...) \ { \ .iov_base = (void *)util_data(args), \ .iov_len = sizeof(util_data(args)), \ } struct iovec *util_iov_dup(const struct iovec *iov, size_t cnt); int util_iov_memcmp(const struct iovec *iov1, const struct iovec *iov2); void util_iov_memcpy(struct iovec *iov, void *src, size_t len); void *util_iov_push(struct iovec *iov, size_t len); void *util_iov_push_mem(struct iovec *iov, size_t len, const void *data); void *util_iov_push_le64(struct iovec *iov, uint64_t val); void *util_iov_push_be64(struct iovec *iov, uint64_t val); void *util_iov_push_le32(struct iovec *iov, uint32_t val); void *util_iov_push_be32(struct iovec *iov, uint32_t val); void *util_iov_push_le24(struct iovec *iov, uint32_t val); void *util_iov_push_be24(struct iovec *iov, uint32_t val); void *util_iov_push_le16(struct iovec *iov, uint16_t val); void *util_iov_push_be16(struct iovec *iov, uint16_t val); void *util_iov_push_u8(struct iovec *iov, uint8_t val); void *util_iov_append(struct iovec *iov, const void *data, size_t len); struct iovec *util_iov_new(void *data, size_t len); void *util_iov_pull(struct iovec *iov, size_t len); void *util_iov_pull_mem(struct iovec *iov, size_t len); void *util_iov_pull_le64(struct iovec *iov, uint64_t *val); void *util_iov_pull_be64(struct iovec *iov, uint64_t *val); void *util_iov_pull_le32(struct iovec *iov, uint32_t *val); void *util_iov_pull_be32(struct iovec *iov, uint32_t *val); void *util_iov_pull_le24(struct iovec *iov, uint32_t *val); void *util_iov_pull_be24(struct iovec *iov, uint32_t *val); void *util_iov_pull_le16(struct iovec *iov, uint16_t *val); void *util_iov_pull_be16(struct iovec *iov, uint16_t *val); void *util_iov_pull_u8(struct iovec *iov, uint8_t *val); void util_iov_free(struct iovec *iov, size_t cnt); const char *bt_uuid16_to_str(uint16_t uuid); const char *bt_uuid32_to_str(uint32_t uuid); const char *bt_uuid128_to_str(const uint8_t uuid[16]); const char *bt_uuidstr_to_str(const char *uuid); const char *bt_appear_to_str(uint16_t appearance); static inline int8_t get_s8(const void *ptr) { return *((int8_t *) ptr); } static inline uint8_t get_u8(const void *ptr) { return *((uint8_t *) ptr); } static inline uint16_t get_le16(const void *ptr) { return le16_to_cpu(get_unaligned((const uint16_t *) ptr)); } static inline uint16_t get_be16(const void *ptr) { return be16_to_cpu(get_unaligned((const uint16_t *) ptr)); } static inline uint32_t get_le24(const void *ptr) { const uint8_t *src = ptr; return ((uint32_t)src[2] << 16) | get_le16(ptr); } static inline uint32_t get_be24(const void *ptr) { const uint8_t *src = ptr; return ((uint32_t)src[0] << 16) | get_be16(&src[1]); } static inline uint32_t get_le32(const void *ptr) { return le32_to_cpu(get_unaligned((const uint32_t *) ptr)); } static inline uint32_t get_be32(const void *ptr) { return be32_to_cpu(get_unaligned((const uint32_t *) ptr)); } static inline uint64_t get_le64(const void *ptr) { return le64_to_cpu(get_unaligned((const uint64_t *) ptr)); } static inline uint64_t get_be64(const void *ptr) { return be64_to_cpu(get_unaligned((const uint64_t *) ptr)); } static inline void put_u8(uint8_t val, void *dst) { put_unaligned(val, (uint8_t *) dst); } static inline void put_le16(uint16_t val, void *dst) { put_unaligned(cpu_to_le16(val), (uint16_t *) dst); } static inline void put_be16(uint16_t val, const void *ptr) { put_unaligned(cpu_to_be16(val), (uint16_t *) ptr); } static inline void put_le24(uint32_t val, void *ptr) { put_le16(val, ptr); put_unaligned(val >> 16, (uint8_t *) ptr + 2); } static inline void put_be24(uint32_t val, void *ptr) { put_unaligned(val >> 16, (uint8_t *) ptr + 2); put_be16(val, ptr + 1); } static inline void put_le32(uint32_t val, void *dst) { put_unaligned(cpu_to_le32(val), (uint32_t *) dst); } static inline void put_be32(uint32_t val, void *dst) { put_unaligned(cpu_to_be32(val), (uint32_t *) dst); } static inline void put_le64(uint64_t val, void *dst) { put_unaligned(cpu_to_le64(val), (uint64_t *) dst); } static inline void put_be64(uint64_t val, void *dst) { put_unaligned(cpu_to_be64(val), (uint64_t *) dst); } bluez-5.87/src/shared/PaxHeaders/bass.c0000644000000000000000000000005015221752714015012 xustar0020 atime=1783092685 20 ctime=1783094100 bluez-5.87/src/shared/bass.c0000644000000000000000000012524315221752714014502 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright 2023-2024 NXP * */ #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "src/shared/queue.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/shared/bass.h" #define DBG(_bass, fmt, arg...) \ bass_debug(_bass, "%s:%s() " fmt, __FILE__, __func__, ## arg) struct bt_bass_db; struct bt_bass_cb { unsigned int id; bt_bass_func_t attached; bt_bass_func_t detached; void *user_data; }; struct bt_bcast_recv_state { struct bt_bass_db *bdb; struct gatt_db_attribute *attr; struct gatt_db_attribute *ccc; }; struct bt_bass_db { struct gatt_db *db; bdaddr_t adapter_bdaddr; struct queue *bcast_srcs; struct gatt_db_attribute *service; struct gatt_db_attribute *bcast_audio_scan_cp; struct bt_bcast_recv_state *bcast_recv_states[NUM_BCAST_RECV_STATES]; }; struct bt_bass { int ref_count; struct bt_bass_db *ldb; struct bt_bass_db *rdb; struct bt_gatt_client *client; struct bt_att *att; struct queue *notify; bt_bass_debug_func_t debug_func; bt_bass_destroy_func_t debug_destroy; void *debug_data; struct queue *src_cbs; struct queue *cp_handlers; unsigned int disconn_id; void *user_data; }; struct bt_bass_cp_handler { unsigned int id; bt_bass_cp_handler_func_t handler; bt_bass_destroy_func_t destroy; void *data; }; /* BASS subgroup field of the Broadcast * Receive State characteristic */ struct bt_bass_subgroup_data { uint32_t bis_sync; uint32_t pending_bis_sync; uint8_t meta_len; uint8_t *meta; }; /* BASS Broadcast Source structure */ struct bt_bcast_src { struct bt_bass *bass; struct gatt_db_attribute *attr; uint8_t id; uint8_t addr_type; bdaddr_t addr; uint8_t sid; uint32_t bid; uint8_t sync_state; uint8_t enc; uint8_t bad_code[BT_BASS_BCAST_CODE_SIZE]; uint8_t num_subgroups; struct bt_bass_subgroup_data *subgroup_data; }; typedef void (*bass_notify_t)(struct bt_bass *bass, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data); struct bt_bass_notify { unsigned int id; struct bt_bass *bass; bass_notify_t func; void *user_data; }; static struct queue *bass_db; static struct queue *bass_cbs; static struct queue *sessions; struct bt_bass_src_changed { unsigned int id; bt_bass_src_func_t cb; bt_bass_destroy_func_t destroy; void *data; }; static void bass_bcast_src_free(void *data); static void bass_debug(struct bt_bass *bass, const char *format, ...) { va_list ap; if (!bass || !format || !bass->debug_func) return; va_start(ap, format); util_debug_va(bass->debug_func, bass->debug_data, format, ap); va_end(ap); } unsigned int bt_bass_cp_handler_register(struct bt_bass *bass, bt_bass_cp_handler_func_t handler, bt_bass_destroy_func_t destroy, void *user_data) { struct bt_bass_cp_handler *cb; static unsigned int id; if (!bass) return 0; cb = new0(struct bt_bass_cp_handler, 1); cb->id = ++id ? id : ++id; cb->handler = handler; cb->destroy = destroy; cb->data = user_data; queue_push_tail(bass->cp_handlers, cb); return cb->id; } static void bass_cp_handler_free(void *data) { struct bt_bass_cp_handler *cb = data; if (cb->destroy) cb->destroy(cb->data); free(cb); } static bool match_cb_id(const void *data, const void *match_data) { const struct bt_bass_cp_handler *cb = data; unsigned int id = PTR_TO_UINT(match_data); return (cb->id == id); } bool bt_bass_cp_handler_unregister(struct bt_bass *bass, unsigned int id) { struct bt_bass_cp_handler *cb; if (!bass) return false; cb = queue_remove_if(bass->cp_handlers, match_cb_id, UINT_TO_PTR(id)); if (!cb) return false; bass_cp_handler_free(cb); return true; } unsigned int bt_bass_src_register(struct bt_bass *bass, bt_bass_src_func_t cb, void *user_data, bt_bass_destroy_func_t destroy) { struct bt_bass_src_changed *changed; static unsigned int id; if (!bass) return 0; changed = new0(struct bt_bass_src_changed, 1); if (!changed) return 0; changed->id = ++id ? id : ++id; changed->cb = cb; changed->destroy = destroy; changed->data = user_data; queue_push_tail(bass->src_cbs, changed); return changed->id; } static void bass_src_changed_free(void *data) { struct bt_bass_src_changed *changed = data; if (changed->destroy) changed->destroy(changed->data); free(changed); } static bool match_src_changed_id(const void *data, const void *match_data) { const struct bt_bass_src_changed *changed = data; unsigned int id = PTR_TO_UINT(match_data); return (changed->id == id); } bool bt_bass_src_unregister(struct bt_bass *bass, unsigned int id) { struct bt_bass_src_changed *changed; if (!bass) return false; changed = queue_remove_if(bass->src_cbs, match_src_changed_id, UINT_TO_PTR(id)); if (!changed) return false; bass_src_changed_free(changed); return true; } static int bass_build_bcast_src(struct bt_bcast_src *bcast_src, const uint8_t *value, uint16_t length) { struct bt_bass_subgroup_data *subgroup_data = NULL; uint8_t id; uint8_t addr_type; uint8_t *addr; uint8_t sid; uint32_t bid; uint8_t pa_sync_state; uint8_t enc; uint8_t *bad_code = NULL; uint8_t num_subgroups; uint32_t bis_sync_state; uint8_t meta_len; uint8_t *meta; struct iovec iov = { .iov_base = (void *) value, .iov_len = length, }; /* Extract all fields from notification */ if (!util_iov_pull_u8(&iov, &id)) { DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); return -1; } if (!util_iov_pull_u8(&iov, &addr_type)) { DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); return -1; } addr = util_iov_pull_mem(&iov, sizeof(bdaddr_t)); if (!addr) { DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); return -1; } if (!util_iov_pull_u8(&iov, &sid)) { DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); return -1; } if (!util_iov_pull_le24(&iov, &bid)) { DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); return -1; } if (!util_iov_pull_u8(&iov, &pa_sync_state)) { DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); return -1; } if (!util_iov_pull_u8(&iov, &enc)) { DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); return -1; } if (enc == BT_BASS_BIG_ENC_STATE_BAD_CODE) { bad_code = util_iov_pull_mem(&iov, BT_BASS_BCAST_CODE_SIZE); if (!bad_code) { DBG(bcast_src->bass, "Unable to parse " "Broadcast Receive State"); return -1; } } if (!util_iov_pull_u8(&iov, &num_subgroups)) { DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); return -1; } if (num_subgroups == 0) goto done; subgroup_data = new0(struct bt_bass_subgroup_data, 1); if (!subgroup_data) { DBG(bcast_src->bass, "Unable to allocate memory"); return -1; } for (int i = 0; i < num_subgroups; i++) { if (!util_iov_pull_le32(&iov, &bis_sync_state)) { DBG(bcast_src->bass, "Unable to parse " "Broadcast Receive State"); for (int j = 0; j < i; j++) free(subgroup_data[j].meta); free(subgroup_data); return -1; } subgroup_data[i].bis_sync = bis_sync_state; if (!util_iov_pull_u8(&iov, &meta_len)) { DBG(bcast_src->bass, "Unable to parse " "Broadcast Receive State"); for (int j = 0; j < i; j++) free(subgroup_data[j].meta); free(subgroup_data); return -1; } subgroup_data[i].meta_len = meta_len; if (meta_len == 0) continue; subgroup_data[i].meta = malloc0(meta_len); if (!subgroup_data[i].meta) { DBG(bcast_src->bass, "Unable to allocate memory"); for (int j = 0; j < i; j++) free(subgroup_data[j].meta); free(subgroup_data); return -1; } meta = util_iov_pull_mem(&iov, meta_len); if (!meta) { DBG(bcast_src->bass, "Unable to parse " "Broadcast Receive State"); for (int j = 0; j < i; j++) free(subgroup_data[j].meta); free(subgroup_data); return -1; } memcpy(subgroup_data[i].meta, meta, meta_len); } done: /* * If no errors occurred, copy extracted fields into * the broadcast source structure */ if (bcast_src->subgroup_data) { for (int i = 0; i < bcast_src->num_subgroups; i++) free(bcast_src->subgroup_data[i].meta); free(bcast_src->subgroup_data); } bcast_src->id = id; bcast_src->addr_type = addr_type; memcpy(&bcast_src->addr, addr, sizeof(bdaddr_t)); bcast_src->sid = sid; bcast_src->bid = bid; bcast_src->sync_state = pa_sync_state; bcast_src->enc = enc; if (enc == BT_BASS_BIG_ENC_STATE_BAD_CODE) memcpy(bcast_src->bad_code, bad_code, BT_BASS_BCAST_CODE_SIZE); else memset(bcast_src->bad_code, 0, BT_BASS_BCAST_CODE_SIZE); bcast_src->num_subgroups = num_subgroups; bcast_src->subgroup_data = subgroup_data; return 0; } static struct iovec *bass_parse_bcast_src(struct bt_bcast_src *bcast_src) { size_t len = 0; uint8_t *notif = NULL; struct iovec *iov; if (!bcast_src) return NULL; len = BT_BASS_BCAST_SRC_LEN + bcast_src->num_subgroups * BT_BASS_BCAST_SRC_SUBGROUP_LEN; if (bcast_src->enc == BT_BASS_BIG_ENC_STATE_BAD_CODE) len += BT_BASS_BCAST_CODE_SIZE; for (size_t i = 0; i < bcast_src->num_subgroups; i++) { /* Add length for subgroup metadata */ len += bcast_src->subgroup_data[i].meta_len; } notif = malloc0(len); if (!notif) return NULL; iov = new0(struct iovec, 1); if (!iov) { free(notif); return NULL; } iov->iov_base = notif; iov->iov_len = 0; util_iov_push_u8(iov, bcast_src->id); util_iov_push_u8(iov, bcast_src->addr_type); util_iov_push_mem(iov, sizeof(bcast_src->addr), &bcast_src->addr); util_iov_push_u8(iov, bcast_src->sid); util_iov_push_le24(iov, bcast_src->bid); util_iov_push_u8(iov, bcast_src->sync_state); util_iov_push_u8(iov, bcast_src->enc); if (bcast_src->enc == BT_BASS_BIG_ENC_STATE_BAD_CODE) util_iov_push_mem(iov, sizeof(bcast_src->bad_code), bcast_src->bad_code); util_iov_push_u8(iov, bcast_src->num_subgroups); for (size_t i = 0; i < bcast_src->num_subgroups; i++) { /* Add subgroup bis_sync */ util_iov_push_le32(iov, bcast_src->subgroup_data[i].bis_sync); /* Add subgroup meta_len */ util_iov_push_u8(iov, bcast_src->subgroup_data[i].meta_len); /* Add subgroup metadata */ if (bcast_src->subgroup_data[i].meta_len > 0) util_iov_push_mem(iov, bcast_src->subgroup_data[i].meta_len, bcast_src->subgroup_data[i].meta); } return iov; } static bool bass_check_cp_command_subgroup_data_len(uint8_t num_subgroups, struct iovec *iov) { uint32_t bis_sync_state; uint8_t *meta_len; uint8_t *meta; for (int i = 0; i < num_subgroups; i++) { if (!util_iov_pull_le32(iov, &bis_sync_state)) return false; meta_len = util_iov_pull_mem(iov, sizeof(*meta_len)); if (!meta_len) return false; meta = util_iov_pull_mem(iov, *meta_len); if (!meta) return false; } return true; } static bool bass_check_cp_command_len(const uint8_t *value, size_t len) { struct bt_bass_bcast_audio_scan_cp_hdr *hdr; union { struct bt_bass_add_src_params *add_src_params; struct bt_bass_mod_src_params *mod_src_params; struct bt_bass_set_bcast_code_params *set_bcast_code_params; struct bt_bass_remove_src_params *remove_src_params; } params; struct iovec iov = { .iov_base = (void *)value, .iov_len = len, }; /* Get command header */ hdr = util_iov_pull_mem(&iov, sizeof(*hdr)); if (!hdr) return false; /* Check command parameters */ switch (hdr->op) { case BT_BASS_ADD_SRC: params.add_src_params = util_iov_pull_mem(&iov, sizeof(*params.add_src_params)); if (!params.add_src_params) return false; if (!bass_check_cp_command_subgroup_data_len( params.add_src_params->num_subgroups, &iov)) return false; break; case BT_BASS_MOD_SRC: params.mod_src_params = util_iov_pull_mem(&iov, sizeof(*params.mod_src_params)); if (!params.mod_src_params) return false; if (!bass_check_cp_command_subgroup_data_len( params.mod_src_params->num_subgroups, &iov)) return false; break; case BT_BASS_SET_BCAST_CODE: params.set_bcast_code_params = util_iov_pull_mem(&iov, sizeof(*params.set_bcast_code_params)); if (!params.set_bcast_code_params) return false; break; case BT_BASS_REMOVE_SRC: params.remove_src_params = util_iov_pull_mem(&iov, sizeof(*params.remove_src_params)); if (!params.remove_src_params) return false; break; case BT_BASS_REMOTE_SCAN_STOPPED: case BT_BASS_REMOTE_SCAN_STARTED: break; default: return true; } if (iov.iov_len > 0) return false; return true; } static void bass_handle_remote_scan_stopped_op(struct bt_bass *bass, struct gatt_db_attribute *attrib, uint8_t opcode, unsigned int id, struct iovec *iov, struct bt_att *att) { gatt_db_attribute_write_result(attrib, id, 0x00); } static void bass_handle_remote_scan_started_op(struct bt_bass *bass, struct gatt_db_attribute *attrib, uint8_t opcode, unsigned int id, struct iovec *iov, struct bt_att *att) { gatt_db_attribute_write_result(attrib, id, 0x00); } static bool bass_src_id_match(const void *data, const void *match_data) { const struct bt_bcast_src *bcast_src = data; const uint8_t *id = match_data; return (bcast_src->id == *id); } static void bass_handle_remove_src_op(struct bt_bass *bass, struct gatt_db_attribute *attrib, uint8_t opcode, unsigned int id, struct iovec *iov, struct bt_att *att) { struct bt_bass_remove_src_params *params; struct bt_bcast_src *bcast_src; int att_err = 0; /* Get Remove Source command parameters */ params = util_iov_pull_mem(iov, sizeof(*params)); bcast_src = queue_find(bass->ldb->bcast_srcs, bass_src_id_match, ¶ms->id); if (!bcast_src) { /* No source matches the written source id */ att_err = BT_BASS_ERROR_INVALID_SOURCE_ID; goto done; } /* Ignore if server is synchronized to the PA * of the source */ if (bcast_src->sync_state == BT_BASS_SYNCHRONIZED_TO_PA) goto done; /* Ignore if server is synchronized to any BIS * of the source */ for (int i = 0; i < bcast_src->num_subgroups; i++) if (bcast_src->subgroup_data[i].bis_sync) goto done; /* Accept the operation and remove source */ queue_remove(bass->ldb->bcast_srcs, bcast_src); gatt_db_attribute_notify(bcast_src->attr, NULL, 0, att); bass_bcast_src_free(bcast_src); done: gatt_db_attribute_write_result(attrib, id, att_err); } static bool bass_src_attr_match(const void *data, const void *match_data) { const struct bt_bcast_src *bcast_src = data; const struct gatt_db_attribute *attr = match_data; return (bcast_src->attr == attr); } static bool bass_trigger_big_sync(struct bt_bcast_src *bcast_src) { for (int i = 0; i < bcast_src->num_subgroups; i++) { struct bt_bass_subgroup_data *data = &bcast_src->subgroup_data[i]; if (data->pending_bis_sync && data->pending_bis_sync != BIS_SYNC_NO_PREF) return true; } return false; } static struct bt_bass *bass_get_session(struct bt_att *att, struct gatt_db *db, const bdaddr_t *adapter_bdaddr) { const struct queue_entry *entry; struct bt_bass *bass; for (entry = queue_get_entries(sessions); entry; entry = entry->next) { struct bt_bass *bass = entry->data; if (att == bt_bass_get_att(bass)) return bass; } bass = bt_bass_new(db, NULL, adapter_bdaddr); bass->att = att; bt_bass_attach(bass, NULL); return bass; } static bool bass_validate_bis_sync(uint8_t num_subgroups, struct iovec *iov) { uint32_t bis_sync_state; uint32_t bitmask = 0U; uint8_t *meta_len; for (int i = 0; i < num_subgroups; i++) { util_iov_pull_le32(iov, &bis_sync_state); if (bis_sync_state != BIS_SYNC_NO_PREF) for (int bis_idx = 0; bis_idx < 31; bis_idx++) { if (bis_sync_state & (1 << bis_idx)) { if (bitmask & (1 << bis_idx)) return false; bitmask |= (1 << bis_idx); } } meta_len = util_iov_pull_mem(iov, sizeof(*meta_len)); util_iov_pull_mem(iov, *meta_len); } return true; } static bool bass_validate_add_src_params(uint8_t *value, size_t len) { struct bt_bass_add_src_params *params; struct iovec iov = { .iov_base = (void *)value, .iov_len = len, }; params = util_iov_pull_mem(&iov, sizeof(*params)); if (params->pa_sync > PA_SYNC_NO_PAST) return false; if (params->addr_type > 0x01) return false; if (params->sid > 0x0F) return false; if (!bass_validate_bis_sync(params->num_subgroups, &iov)) return false; return true; } static void bass_handle_add_src_op(struct bt_bass *bass, struct gatt_db_attribute *attrib, uint8_t opcode, unsigned int id, struct iovec *iov, struct bt_att *att) { struct bt_bcast_src *bcast_src, *src; uint8_t src_id = 0; struct gatt_db_attribute *attr; uint8_t pa_sync; struct iovec *notif; int ret; const struct queue_entry *entry; struct bt_bass_add_src_params *params; gatt_db_attribute_write_result(attrib, id, 0x00); /* Ignore operation if parameters are invalid */ if (!bass_validate_add_src_params(iov->iov_base, iov->iov_len)) return; /* Allocate a new broadcast source */ bcast_src = new0(struct bt_bcast_src, 1); if (!bcast_src) { DBG(bass, "Unable to allocate broadcast source"); return; } queue_push_tail(bass->ldb->bcast_srcs, bcast_src); bcast_src->bass = bass; /* Map the source to a Broadcast Receive State characteristic */ for (int i = 0; i < NUM_BCAST_RECV_STATES; i++) { src = queue_find(bass->ldb->bcast_srcs, bass_src_attr_match, bass->ldb->bcast_recv_states[i]->attr); if (!src) { /* Found and empty characteristic */ bcast_src->attr = bass->ldb->bcast_recv_states[i]->attr; break; } } if (!bcast_src->attr) { /* If no empty characteristic has been found, * overwrite an existing one */ attr = bass->ldb->bcast_recv_states[0]->attr; src = queue_find(bass->ldb->bcast_srcs, bass_src_attr_match, attr); queue_remove(bass->ldb->bcast_srcs, src); bass_bcast_src_free(src); bcast_src->attr = attr; } /* Allocate source id */ while (true) { src = queue_find(bass->ldb->bcast_srcs, bass_src_id_match, &src_id); if (!src) break; if (src_id == 0xFF) { DBG(bass, "Unable to allocate broadcast source id"); return; } src_id++; } bcast_src->id = src_id; params = util_iov_pull_mem(iov, sizeof(*params)); /* Populate broadcast source fields from command parameters */ bcast_src->addr_type = params->addr_type; /* Convert to three-value type */ if (bcast_src->addr_type) params->addr_type = BDADDR_LE_RANDOM; else params->addr_type = BDADDR_LE_PUBLIC; bacpy(&bcast_src->addr, ¶ms->addr); bcast_src->sid = params->sid; memcpy(&bcast_src->bid, params->bid, sizeof(params->bid)); pa_sync = params->pa_sync; bcast_src->sync_state = BT_BASS_NOT_SYNCHRONIZED_TO_PA; bcast_src->num_subgroups = params->num_subgroups; if (!bcast_src->num_subgroups) return; bcast_src->subgroup_data = new0(struct bt_bass_subgroup_data, bcast_src->num_subgroups); if (!bcast_src->subgroup_data) { DBG(bass, "Unable to allocate subgroup data"); goto err; } for (int i = 0; i < bcast_src->num_subgroups; i++) { struct bt_bass_subgroup_data *data = &bcast_src->subgroup_data[i]; util_iov_pull_le32(iov, &data->pending_bis_sync); data->meta_len = *(uint8_t *)util_iov_pull_mem(iov, sizeof(data->meta_len)); if (!data->meta_len) continue; data->meta = malloc0(data->meta_len); if (!data->meta) goto err; memcpy(data->meta, (uint8_t *)util_iov_pull_mem(iov, data->meta_len), data->meta_len); } if (pa_sync != PA_SYNC_NO_SYNC) { for (entry = queue_get_entries(bass->cp_handlers); entry; entry = entry->next) { struct bt_bass_cp_handler *cb = entry->data; if (cb->handler) { ret = cb->handler(bcast_src, BT_BASS_ADD_SRC, params, cb->data); if (ret) goto err; } } } else { for (int i = 0; i < bcast_src->num_subgroups; i++) bcast_src->subgroup_data[i].bis_sync = bcast_src->subgroup_data[i].pending_bis_sync; notif = bass_parse_bcast_src(bcast_src); if (!notif) return; gatt_db_attribute_notify(bcast_src->attr, notif->iov_base, notif->iov_len, bt_bass_get_att(bcast_src->bass)); free(notif->iov_base); free(notif); } return; err: if (bcast_src->subgroup_data) { for (int i = 0; i < bcast_src->num_subgroups; i++) free(bcast_src->subgroup_data[i].meta); free(bcast_src->subgroup_data); } free(bcast_src); } static void bass_handle_set_bcast_code_op(struct bt_bass *bass, struct gatt_db_attribute *attrib, uint8_t opcode, unsigned int id, struct iovec *iov, struct bt_att *att) { struct bt_bass_set_bcast_code_params *params; struct bt_bcast_src *bcast_src; struct iovec *notif; const struct queue_entry *entry; int ret; /* Get Set Broadcast Code command parameters */ params = util_iov_pull_mem(iov, sizeof(*params)); bcast_src = queue_find(bass->ldb->bcast_srcs, bass_src_id_match, ¶ms->id); if (!bcast_src) { /* No source matches the written source id */ gatt_db_attribute_write_result(attrib, id, BT_BASS_ERROR_INVALID_SOURCE_ID); return; } gatt_db_attribute_write_result(attrib, id, 0x00); for (entry = queue_get_entries(bass->cp_handlers); entry; entry = entry->next) { struct bt_bass_cp_handler *cb = entry->data; if (cb->handler) { ret = cb->handler(bcast_src, BT_BASS_SET_BCAST_CODE, params, cb->data); if (ret) DBG(bass, "Unable to handle Set " "Broadcast Code operation"); } } if (!bass_trigger_big_sync(bcast_src)) { bcast_src->enc = BT_BASS_BIG_ENC_STATE_DEC; notif = bass_parse_bcast_src(bcast_src); if (!notif) return; gatt_db_attribute_notify(bcast_src->attr, notif->iov_base, notif->iov_len, bt_bass_get_att(bcast_src->bass)); free(notif->iov_base); free(notif); } } static void bass_handle_mod_src_op(struct bt_bass *bass, struct gatt_db_attribute *attrib, uint8_t opcode, unsigned int id, struct iovec *iov, struct bt_att *att) { struct bt_bcast_src *bcast_src; struct bt_bass_mod_src_params *params; const struct queue_entry *entry; struct iovec *notif; bool updated = false; int err = 0; /* Get Modify Source command parameters */ params = util_iov_pull_mem(iov, sizeof(*params)); bcast_src = queue_find(bass->ldb->bcast_srcs, bass_src_id_match, ¶ms->id); if (!bcast_src) { /* No source matches the written source id */ gatt_db_attribute_write_result(attrib, id, BT_BASS_ERROR_INVALID_SOURCE_ID); return; } gatt_db_attribute_write_result(attrib, id, 0x00); for (int i = 0; i < bcast_src->num_subgroups; i++) { struct bt_bass_subgroup_data *data = &bcast_src->subgroup_data[i]; uint8_t meta_len; uint8_t *meta; if (!util_iov_pull_le32(iov, &data->pending_bis_sync)) return; if (!util_iov_pull_u8(iov, &meta_len)) return; /* Check for metadata updates and notify peers */ if (meta_len != data->meta_len) { updated = true; data->meta_len = meta_len; free(data->meta); data->meta = malloc0(data->meta_len); if (!data->meta) return; } if (!data->meta_len) continue; meta = (uint8_t *)util_iov_pull_mem(iov, meta_len); if (!meta) return; if (memcmp(meta, data->meta, data->meta_len)) { updated = true; memcpy(data->meta, meta, data->meta_len); } } for (entry = queue_get_entries(bass->cp_handlers); entry; entry = entry->next) { struct bt_bass_cp_handler *cb = entry->data; if (cb->handler) { err = cb->handler(bcast_src, BT_BASS_MOD_SRC, params, cb->data); if (err) DBG(bass, "Unable to handle Modify Source " "operation"); } } if (!updated) return; notif = bass_parse_bcast_src(bcast_src); if (!notif) return; gatt_db_attribute_notify(bcast_src->attr, notif->iov_base, notif->iov_len, bt_bass_get_att(bcast_src->bass)); free(notif->iov_base); free(notif); } #define BASS_OP(_str, _op, _size, _func) \ { \ .str = _str, \ .op = _op, \ .size = _size, \ .func = _func, \ } struct bass_op_handler { const char *str; uint8_t op; size_t size; void (*func)(struct bt_bass *bass, struct gatt_db_attribute *attrib, uint8_t opcode, unsigned int id, struct iovec *iov, struct bt_att *att); } bass_handlers[] = { BASS_OP("Remote Scan Stopped", BT_BASS_REMOTE_SCAN_STOPPED, 0, bass_handle_remote_scan_stopped_op), BASS_OP("Remote Scan Started", BT_BASS_REMOTE_SCAN_STARTED, 0, bass_handle_remote_scan_started_op), BASS_OP("Remove Source", BT_BASS_REMOVE_SRC, 0, bass_handle_remove_src_op), BASS_OP("Add Source", BT_BASS_ADD_SRC, 0, bass_handle_add_src_op), BASS_OP("Set Broadcast Code", BT_BASS_SET_BCAST_CODE, 0, bass_handle_set_bcast_code_op), BASS_OP("Modify Source", BT_BASS_MOD_SRC, 0, bass_handle_mod_src_op), {} }; static void bass_bcast_audio_scan_cp_write(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_bass_db *bdb = user_data; struct bt_bass_bcast_audio_scan_cp_hdr *hdr; struct bass_op_handler *handler; struct bt_bass *bass = bass_get_session(att, bdb->db, &bdb->adapter_bdaddr); struct iovec iov = { .iov_base = (void *)value, .iov_len = len, }; /* Validate written command length */ if (!bass_check_cp_command_len(value, len)) { gatt_db_attribute_write_result(attrib, id, BT_ERROR_WRITE_REQUEST_REJECTED); return; } /* Get command header */ hdr = util_iov_pull_mem(&iov, sizeof(*hdr)); /* Call the appropriate opcode handler */ for (handler = bass_handlers; handler && handler->str; handler++) { if (handler->op == hdr->op) { handler->func(bass, attrib, opcode, id, &iov, att); return; } } /* Send error response if unsupported opcode was written */ gatt_db_attribute_write_result(attrib, id, BT_BASS_ERROR_OPCODE_NOT_SUPPORTED); } static bool bass_src_match_attrib(const void *data, const void *match_data) { const struct bt_bcast_src *bcast_src = data; const struct gatt_db_attribute *attr = match_data; return (bcast_src->attr == attr); } static void bass_bcast_recv_state_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_bass_db *bdb = user_data; struct iovec *rsp; struct bt_bcast_src *bcast_src; struct bt_bass *bass = bass_get_session(att, bdb->db, &bdb->adapter_bdaddr); bcast_src = queue_find(bass->ldb->bcast_srcs, bass_src_match_attrib, attrib); if (!bcast_src) { gatt_db_attribute_read_result(attrib, id, 0, NULL, 0); return; } /* Build read response */ rsp = bass_parse_bcast_src(bcast_src); if (!rsp) { gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_UNLIKELY, NULL, 0); return; } gatt_db_attribute_read_result(attrib, id, 0, rsp->iov_base, rsp->iov_len); free(rsp->iov_base); free(rsp); } static void bcast_recv_new(struct bt_bass_db *bdb, int i) { struct bt_bcast_recv_state *bcast_recv_state; bt_uuid_t uuid; if (!bdb) return; bcast_recv_state = new0(struct bt_bcast_recv_state, 1); bcast_recv_state->bdb = bdb; bt_uuid16_create(&uuid, BCAST_RECV_STATE_UUID); bcast_recv_state->attr = gatt_db_service_add_characteristic(bdb->service, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, bass_bcast_recv_state_read, NULL, bdb); bcast_recv_state->ccc = gatt_db_service_add_ccc(bdb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bdb->bcast_recv_states[i] = bcast_recv_state; } static void bass_new(struct bt_bass_db *bdb) { bt_uuid_t uuid; int i; /* Populate DB with BASS attributes */ bt_uuid16_create(&uuid, BASS_UUID); bdb->service = gatt_db_add_service(bdb->db, &uuid, true, 3 + (NUM_BCAST_RECV_STATES * 3)); for (i = 0; i < NUM_BCAST_RECV_STATES; i++) bcast_recv_new(bdb, i); bt_uuid16_create(&uuid, BCAST_AUDIO_SCAN_CP_UUID); bdb->bcast_audio_scan_cp = gatt_db_service_add_characteristic(bdb->service, &uuid, BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_ENCRYPT, BT_GATT_CHRC_PROP_WRITE | BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP, NULL, bass_bcast_audio_scan_cp_write, bdb); gatt_db_service_set_active(bdb->service, true); } static void bass_bcast_src_free(void *data) { struct bt_bcast_src *bcast_src = data; if (!bcast_src) return; for (int i = 0; i < bcast_src->num_subgroups; i++) free(bcast_src->subgroup_data[i].meta); free(bcast_src->subgroup_data); free(bcast_src); } static void read_bcast_recv_state(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_bcast_src *bcast_src = user_data; if (!success) { DBG(bcast_src->bass, "Unable to read " "Broadcast Receive State: error 0x%02x", att_ecode); return; } if (length == 0) { queue_remove(bcast_src->bass->rdb->bcast_srcs, bcast_src); bass_bcast_src_free(bcast_src); return; } if (bass_build_bcast_src(bcast_src, value, length)) { queue_remove(bcast_src->bass->rdb->bcast_srcs, bcast_src); bass_bcast_src_free(bcast_src); return; } } static void notify_src_changed(void *data, void *user_data) { struct bt_bass_src_changed *changed = data; struct bt_bcast_src *bcast_src = user_data; uint32_t bis_sync = 0; for (uint8_t i = 0; i < bcast_src->num_subgroups; i++) { struct bt_bass_subgroup_data *sgrp = &bcast_src->subgroup_data[i]; /* Create a bitmask of all BIS indices that the peer has * synchronized with. */ bis_sync |= sgrp->bis_sync; } if (changed->cb) changed->cb(bcast_src->id, bcast_src->bid, bcast_src->sync_state, bcast_src->enc, bis_sync, changed->data); } static void bcast_recv_state_notify(struct bt_bass *bass, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct gatt_db_attribute *attr = user_data; struct bt_bcast_src *bcast_src; bool new_src = false; bcast_src = queue_find(bass->rdb->bcast_srcs, bass_src_match_attrib, attr); if (!bcast_src) { new_src = true; bcast_src = new0(struct bt_bcast_src, 1); if (!bcast_src) { DBG(bass, "Failed to allocate " "memory for broadcast source"); return; } bcast_src->bass = bass; bcast_src->attr = attr; } if (bass_build_bcast_src(bcast_src, value, length) && new_src) { bass_bcast_src_free(bcast_src); return; } if (new_src) queue_push_tail(bass->rdb->bcast_srcs, bcast_src); /* Notify the update in the Broadcast Receive State characteristic * to all drivers that registered a callback. */ queue_foreach(bass->src_cbs, notify_src_changed, bcast_src); } static void bass_register(uint16_t att_ecode, void *user_data) { struct bt_bass_notify *notify = user_data; if (att_ecode) DBG(notify->bass, "BASS register notify failed: 0x%04x", att_ecode); } static void bass_notify(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct bt_bass_notify *notify = user_data; if (notify->func) notify->func(notify->bass, value_handle, value, length, notify->user_data); } static void bass_notify_destroy(void *data) { struct bt_bass_notify *notify = data; struct bt_bass *bass = notify->bass; if (queue_remove_if(bass->notify, NULL, notify)) free(notify); } static unsigned int bass_register_notify(struct bt_bass *bass, uint16_t value_handle, bass_notify_t func, void *user_data) { struct bt_bass_notify *notify; notify = new0(struct bt_bass_notify, 1); notify->bass = bass; notify->func = func; notify->user_data = user_data; notify->id = bt_gatt_client_register_notify(bass->client, value_handle, bass_register, bass_notify, notify, bass_notify_destroy); if (!notify->id) { DBG(bass, "Unable to register for notifications"); free(notify); return 0; } queue_push_tail(bass->notify, notify); return notify->id; } static void foreach_bass_char(struct gatt_db_attribute *attr, void *user_data) { struct bt_bass *bass = user_data; uint16_t value_handle; bt_uuid_t uuid, uuid_bcast_audio_scan_cp, uuid_bcast_recv_state; /* Get attribute value handle and uuid */ if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) return; bt_uuid16_create(&uuid_bcast_audio_scan_cp, BCAST_AUDIO_SCAN_CP_UUID); bt_uuid16_create(&uuid_bcast_recv_state, BCAST_RECV_STATE_UUID); if (!bt_uuid_cmp(&uuid, &uuid_bcast_audio_scan_cp)) { /* Found Broadcast Audio Scan Control Point characteristic */ bass->rdb->bcast_audio_scan_cp = attr; DBG(bass, "Broadcast Audio Scan Control Point " "found: handle 0x%04x", value_handle); } if (!bt_uuid_cmp(&uuid, &uuid_bcast_recv_state)) { /* Found Broadcast Receive State characteristic */ struct bt_bcast_src *bcast_src = queue_find(bass->rdb->bcast_srcs, bass_src_match_attrib, attr); if (!bcast_src) { bcast_src = new0(struct bt_bcast_src, 1); if (bcast_src == NULL) { DBG(bass, "Failed to allocate " "memory for broadcast source"); return; } bcast_src->bass = bass; bcast_src->attr = attr; queue_push_tail(bass->rdb->bcast_srcs, bcast_src); } bt_gatt_client_read_value(bass->client, value_handle, read_bcast_recv_state, bcast_src, NULL); (void)bass_register_notify(bass, value_handle, bcast_recv_state_notify, attr); DBG(bass, "Broadcast Receive State found: handle 0x%04x", value_handle); } } static void foreach_bass_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_bass *bass = user_data; /* Store BASS service reference */ bass->rdb->service = attr; /* Handle BASS characteristics */ gatt_db_service_foreach_char(attr, foreach_bass_char, bass); } static void bass_attached(void *data, void *user_data) { struct bt_bass_cb *cb = data; struct bt_bass *bass = user_data; cb->attached(bass, cb->user_data); } static void bass_disconnected(int err, void *user_data) { struct bt_bass *bass = user_data; bass->disconn_id = 0; DBG(bass, "bass %p disconnected err %d", bass, err); bt_bass_detach(bass); } static void bass_attach_att(struct bt_bass *bass, struct bt_att *att) { if (bass->disconn_id) { if (att == bt_bass_get_att(bass)) return; bt_att_unregister_disconnect(bt_bass_get_att(bass), bass->disconn_id); } bass->disconn_id = bt_att_register_disconnect(att, bass_disconnected, bass, NULL); } bool bt_bass_attach(struct bt_bass *bass, struct bt_gatt_client *client) { bt_uuid_t uuid; if (!sessions) sessions = queue_new(); queue_push_tail(sessions, bass); queue_foreach(bass_cbs, bass_attached, bass); if (!client) { if (bass->att) bass_attach_att(bass, bass->att); return true; } if (bass->client) return false; bass->client = bt_gatt_client_clone(client); if (!bass->client) return false; bass_attach_att(bass, bt_gatt_client_get_att(client)); bt_uuid16_create(&uuid, BASS_UUID); gatt_db_foreach_service(bass->rdb->db, &uuid, foreach_bass_service, bass); return true; } bool bt_bass_set_att(struct bt_bass *bass, struct bt_att *att) { if (!bass) return false; bass->att = att; return true; } static void bass_detached(void *data, void *user_data) { struct bt_bass_cb *cb = data; struct bt_bass *bass = user_data; cb->detached(bass, cb->user_data); } void bt_bass_detach(struct bt_bass *bass) { struct bt_att *att; if (!queue_remove(sessions, bass)) return; if (bass->client) att = bt_gatt_client_get_att(bass->client); else att = bass->att; bt_att_unregister_disconnect(att, bass->disconn_id); bt_gatt_client_unref(bass->client); bass->client = NULL; bass->att = NULL; queue_foreach(bass_cbs, bass_detached, bass); } static void bass_db_free(void *data) { struct bt_bass_db *bdb = data; if (!bdb) return; gatt_db_unref(bdb->db); queue_destroy(bdb->bcast_srcs, bass_bcast_src_free); free(bdb); } static void bass_free(void *data) { struct bt_bass *bass = data; bt_bass_detach(bass); bass_db_free(bass->rdb); queue_destroy(bass->notify, NULL); queue_destroy(bass->src_cbs, bass_src_changed_free); queue_destroy(bass->cp_handlers, bass_cp_handler_free); free(bass); } void bt_bass_unref(struct bt_bass *bass) { if (!bass) return; if (__sync_sub_and_fetch(&bass->ref_count, 1)) return; bass_free(bass); } bool bt_bass_set_user_data(struct bt_bass *bass, void *user_data) { if (!bass) return false; bass->user_data = user_data; return true; } static struct bt_bass_db *bass_db_new(struct gatt_db *db, const bdaddr_t *adapter_bdaddr) { struct bt_bass_db *bdb; if (!db) return NULL; bdb = new0(struct bt_bass_db, 1); bdb->db = gatt_db_ref(db); bacpy(&bdb->adapter_bdaddr, adapter_bdaddr); bdb->bcast_srcs = queue_new(); if (!bass_db) bass_db = queue_new(); bass_new(bdb); queue_push_tail(bass_db, bdb); return bdb; } static bool bass_db_match(const void *data, const void *match_data) { const struct bt_bass_db *bdb = data; const struct gatt_db *db = match_data; return (bdb->db == db); } static struct bt_bass_db *bass_get_db(struct gatt_db *db, const bdaddr_t *adapter_bdaddr) { struct bt_bass_db *bdb; bdb = queue_find(bass_db, bass_db_match, db); if (bdb) return bdb; return bass_db_new(db, adapter_bdaddr); } struct bt_bass *bt_bass_ref(struct bt_bass *bass) { if (!bass) return NULL; __sync_fetch_and_add(&bass->ref_count, 1); return bass; } struct bt_bass *bt_bass_new(struct gatt_db *ldb, struct gatt_db *rdb, const bdaddr_t *adapter_bdaddr) { struct bt_bass *bass; struct bt_bass_db *db; if (!ldb) return NULL; db = bass_get_db(ldb, adapter_bdaddr); if (!db) return NULL; bass = new0(struct bt_bass, 1); bass->ldb = db; bass->notify = queue_new(); bass->src_cbs = queue_new(); bass->cp_handlers = queue_new(); if (!rdb) goto done; db = new0(struct bt_bass_db, 1); db->db = gatt_db_ref(rdb); db->bcast_srcs = queue_new(); bass->rdb = db; done: bt_bass_ref(bass); return bass; } struct bt_att *bt_bass_get_att(struct bt_bass *bass) { if (!bass) return NULL; if (bass->att) return bass->att; return bt_gatt_client_get_att(bass->client); } struct bt_gatt_client *bt_bass_get_client(struct bt_bass *bass) { if (!bass) return NULL; return bass->client; } bool bt_bass_set_debug(struct bt_bass *bass, bt_bass_debug_func_t func, void *user_data, bt_bass_destroy_func_t destroy) { if (!bass) return false; if (bass->debug_destroy) bass->debug_destroy(bass->debug_data); bass->debug_func = func; bass->debug_destroy = destroy; bass->debug_data = user_data; return true; } unsigned int bt_bass_register(bt_bass_func_t attached, bt_bass_func_t detached, void *user_data) { struct bt_bass_cb *cb; static unsigned int id; if (!attached && !detached) return 0; if (!bass_cbs) bass_cbs = queue_new(); cb = new0(struct bt_bass_cb, 1); cb->id = ++id ? id : ++id; cb->attached = attached; cb->detached = detached; cb->user_data = user_data; queue_push_tail(bass_cbs, cb); return cb->id; } static bool match_id(const void *data, const void *match_data) { const struct bt_bass_cb *cb = data; unsigned int id = PTR_TO_UINT(match_data); return (cb->id == id); } bool bt_bass_unregister(unsigned int id) { struct bt_bass_cb *cb; cb = queue_remove_if(bass_cbs, match_id, UINT_TO_PTR(id)); if (!cb) return false; free(cb); return true; } void bt_bass_add_db(struct gatt_db *db, const bdaddr_t *adapter_bdaddr) { bass_db_new(db, adapter_bdaddr); } int bt_bass_send(struct bt_bass *bass, struct bt_bass_bcast_audio_scan_cp_hdr *hdr, struct iovec *params) { struct iovec req = {0}; uint16_t handle; int err = 0; if (!bass || !bass->client || !bass->rdb) return -EINVAL; DBG(bass, "bass %p", bass); req.iov_base = malloc0(sizeof(*hdr) + params->iov_len); if (!req.iov_base) return -EINVAL; util_iov_push_mem(&req, sizeof(*hdr), hdr); util_iov_push_mem(&req, params->iov_len, params->iov_base); if (!gatt_db_attribute_get_char_data(bass->rdb->bcast_audio_scan_cp, NULL, &handle, NULL, NULL, NULL)) { err = -EINVAL; goto done; } if (!bt_gatt_client_write_without_response(bass->client, handle, false, req.iov_base, req.iov_len)) err = -EINVAL; done: free(req.iov_base); return err; } static void bt_bass_notify_all(struct gatt_db_attribute *attr, struct iovec *iov) { const struct queue_entry *entry; for (entry = queue_get_entries(sessions); entry; entry = entry->next) { struct bt_bass *bass = entry->data; gatt_db_attribute_notify(attr, iov->iov_base, iov->iov_len, bt_bass_get_att(bass)); } } int bt_bass_set_pa_sync(struct bt_bcast_src *bcast_src, uint8_t sync_state) { struct iovec *iov; if (!bcast_src) return -EINVAL; bcast_src->sync_state = sync_state; iov = bass_parse_bcast_src(bcast_src); if (!iov) return -ENOMEM; bt_bass_notify_all(bcast_src->attr, iov); free(iov->iov_base); free(iov); return 0; } int bt_bass_get_pa_sync(struct bt_bcast_src *bcast_src, uint8_t *sync_state) { if (!bcast_src) return -EINVAL; *sync_state = bcast_src->sync_state; return 0; } int bt_bass_set_bis_sync(struct bt_bcast_src *bcast_src, uint8_t bis) { struct iovec *iov; for (uint8_t i = 0; i < bcast_src->num_subgroups; i++) { struct bt_bass_subgroup_data *sgrp = &bcast_src->subgroup_data[i]; uint32_t bitmask = 1 << (bis - 1); if (sgrp->pending_bis_sync & bitmask) { sgrp->bis_sync |= bitmask; if (bcast_src->enc == BT_BASS_BIG_ENC_STATE_BCODE_REQ) bcast_src->enc = BT_BASS_BIG_ENC_STATE_DEC; iov = bass_parse_bcast_src(bcast_src); if (!iov) return -ENOMEM; bt_bass_notify_all(bcast_src->attr, iov); free(iov->iov_base); free(iov); } } return 0; } int bt_bass_clear_bis_sync(struct bt_bcast_src *bcast_src, uint8_t bis) { struct iovec *iov; for (uint8_t i = 0; i < bcast_src->num_subgroups; i++) { struct bt_bass_subgroup_data *sgrp = &bcast_src->subgroup_data[i]; uint32_t bitmask = 1 << (bis - 1); if (sgrp->bis_sync & bitmask) { sgrp->bis_sync &= ~bitmask; /* If there are no BIS index left then reset encryption * state to no encryption. */ if (!sgrp->bis_sync) bcast_src->enc = BT_BASS_BIG_ENC_STATE_NO_ENC; iov = bass_parse_bcast_src(bcast_src); if (!iov) return -ENOMEM; bt_bass_notify_all(bcast_src->attr, iov); free(iov->iov_base); free(iov); } } return 0; } bool bt_bass_check_bis(struct bt_bcast_src *bcast_src, uint8_t bis) { for (uint8_t i = 0; i < bcast_src->num_subgroups; i++) { struct bt_bass_subgroup_data *sgrp = &bcast_src->subgroup_data[i]; uint32_t bitmask = 1 << (bis - 1); if (sgrp->pending_bis_sync & bitmask) return true; } return false; } int bt_bass_set_enc(struct bt_bcast_src *bcast_src, uint8_t enc) { struct iovec *iov; if (!bcast_src) return -EINVAL; if (bcast_src->enc == enc) return 0; bcast_src->enc = enc; iov = bass_parse_bcast_src(bcast_src); if (!iov) return -ENOMEM; bt_bass_notify_all(bcast_src->attr, iov); free(iov->iov_base); free(iov); return 0; } bluez-5.87/src/shared/PaxHeaders/asha.c0000644000000000000000000000005015063045254014774 xustar0020 atime=1783092968 20 ctime=1783094100 bluez-5.87/src/shared/asha.c0000644000000000000000000003116515063045254014463 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2024 Asymptotic Inc. * * Author: Arun Raghavan * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/log.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "asha.h" /* We use strings instead of uint128_t to maintain readability */ #define ASHA_CHRC_READ_ONLY_PROPERTIES_UUID "6333651e-c481-4a3e-9169-7c902aad37bb" #define ASHA_CHRC_AUDIO_CONTROL_POINT_UUID "f0d4de7e-4a88-476c-9d9f-1937b0996cc0" #define ASHA_CHRC_AUDIO_STATUS_UUID "38663f1a-e711-4cac-b641-326b56404837" #define ASHA_CHRC_VOLUME_UUID "00e4ca9e-ab14-41e4-8823-f9e70c7e91df" #define ASHA_CHRC_LE_PSM_OUT_UUID "2d410339-82b6-42aa-b34e-e2e01df8cc1a" static struct queue *asha_devices; static unsigned int bt_asha_status(struct bt_asha *asha, bool connected); static bool match_hisyncid(const void *data, const void *user_data) { const struct bt_asha_set *set = data; const struct bt_asha *asha = user_data; return (memcmp(set->hisyncid, asha->hisyncid, 8) == 0); } static struct bt_asha_set *find_asha_set(struct bt_asha *asha) { return queue_find(asha_devices, match_hisyncid, asha); } static uint8_t is_other_connected(struct bt_asha *asha) { struct bt_asha_set *set = find_asha_set(asha); if (set) { if (asha->right_side && set->left) { DBG("ASHA right and left side connected"); return 1; } if (!asha->right_side && set->right) { DBG("ASHA left and right side connected"); return 1; } } if (asha->right_side) DBG("ASHA right side connected"); else DBG("ASHA left side connected"); return 0; } static void update_asha_set(struct bt_asha *asha, bool connected) { struct bt_asha_set *set; set = queue_find(asha_devices, match_hisyncid, asha); if (connected) { if (!set) { set = new0(struct bt_asha_set, 1); memcpy(set->hisyncid, asha->hisyncid, 8); queue_push_tail(asha_devices, set); DBG("Created ASHA set"); } if (asha->right_side) { set->right = asha; DBG("Right side registered for ASHA set"); } else { set->left = asha; DBG("Left side registered for ASHA set"); } } else { if (!set) { error("Missing ASHA set"); return; } if (asha->right_side && set->right) { set->right = NULL; DBG("Right side unregistered for ASHA set"); } else if (!asha->right_side && set->left) { set->left = NULL; DBG("Left side unregistered for ASHA set"); } if (!set->right && !set->left) { if (queue_remove(asha_devices, set)) { free(set); DBG("Freeing ASHA set"); } if (!queue_peek_tail(asha_devices)) { queue_destroy(asha_devices, NULL); asha_devices = NULL; } } } } static int asha_set_send_status(struct bt_asha *asha, bool other_connected) { struct bt_asha_set *set; int ret = 0; set = queue_find(asha_devices, match_hisyncid, asha); if (set) { if (asha->right_side && set->left) { ret = bt_asha_status(set->left, other_connected); DBG("ASHA left side update: %d, ret: %d", other_connected, ret); } if (!asha->right_side && set->right) { ret = bt_asha_status(set->right, other_connected); DBG("ASHA right side update: %d, ret: %d", other_connected, ret); } } return ret; } struct bt_asha *bt_asha_new(void) { struct bt_asha *asha; asha = new0(struct bt_asha, 1); return asha; } void bt_asha_reset(struct bt_asha *asha) { if (asha->status_notify_id) { bt_gatt_client_unregister_notify(asha->client, asha->status_notify_id); } gatt_db_unref(asha->db); asha->db = NULL; bt_gatt_client_unref(asha->client); asha->client = NULL; bt_asha_state_reset(asha); asha->psm = 0; memset(asha->hisyncid, 0, sizeof(asha->hisyncid)); asha->attach_cb = NULL; asha->attach_cb_data = NULL; update_asha_set(asha, false); } void bt_asha_state_reset(struct bt_asha *asha) { asha->state = ASHA_STOPPED; asha->state_cb = NULL; asha->state_cb_data = NULL; } void bt_asha_free(struct bt_asha *asha) { update_asha_set(asha, false); gatt_db_unref(asha->db); bt_gatt_client_unref(asha->client); free(asha); } static void asha_acp_sent(bool success, uint8_t err, void *user_data) { struct bt_asha *asha = user_data; if (success) { DBG("AudioControlPoint command successfully sent"); } else { error("Failed to send AudioControlPoint command: %d", err); if (asha->state_cb) asha->state_cb(-1, asha->state_cb_data); bt_asha_state_reset(asha); } } static int asha_send_acp(struct bt_asha *asha, uint8_t *cmd, unsigned int len, bt_asha_cb_t cb, void *user_data) { if (!bt_gatt_client_write_value(asha->client, asha->acp_handle, cmd, len, asha_acp_sent, asha, NULL)) { error("Error writing ACP command"); return -1; } asha->state_cb = cb; asha->state_cb_data = user_data; return 0; } static int asha_send_acp_without_response(struct bt_asha *asha, uint8_t *cmd, unsigned int len) { if (!bt_gatt_client_write_without_response(asha->client, asha->acp_handle, false, cmd, len)) { error("Error writing ACP command"); return -1; } return 0; } unsigned int bt_asha_start(struct bt_asha *asha, bt_asha_cb_t cb, void *user_data) { uint8_t other_connected = is_other_connected(asha); uint8_t acp_start_cmd[] = { 0x01, /* START */ 0x01, /* G.722, 16 kHz */ 0, /* Unknown media type */ asha->volume, /* Volume */ other_connected, }; int ret; if (asha->state != ASHA_STOPPED) { error("ASHA device start failed. Bad state %d", asha->state); return 0; } ret = asha_send_acp(asha, acp_start_cmd, sizeof(acp_start_cmd), cb, user_data); if (ret < 0) return ret; asha->state = ASHA_STARTING; return 0; } unsigned int bt_asha_stop(struct bt_asha *asha) { uint8_t acp_stop_cmd[] = { 0x02, /* STOP */ }; int ret; if (asha->state != ASHA_STARTED) return 0; asha->state = ASHA_STOPPED; ret = asha_send_acp(asha, acp_stop_cmd, sizeof(acp_stop_cmd), NULL, NULL); asha_set_send_status(asha, false); /* We reset our state without waiting for a response */ bt_asha_state_reset(asha); DBG("ASHA stop done"); return ret; } static unsigned int bt_asha_status(struct bt_asha *asha, bool other_connected) { uint8_t status = other_connected ? 1 : 0; uint8_t acp_status_cmd[] = { 0x03, /* STATUS */ status, }; int ret; if (asha->state != ASHA_STARTED) { const char *side = asha->right_side ? "right" : "left"; DBG("ASHA %s device not started for status update", side); return 0; } ret = asha_send_acp_without_response(asha, acp_status_cmd, sizeof(acp_status_cmd)); if (ret < 0) return ret; return 0; } bool bt_asha_set_volume(struct bt_asha *asha, int8_t volume) { if (!bt_gatt_client_write_without_response(asha->client, asha->volume_handle, false, (const uint8_t *)&volume, 1)) { error("Error writing volume"); return false; } asha->volume = volume; return true; } static bool uuid_cmp(const char *uuid1, const bt_uuid_t *uuid2) { bt_uuid_t lhs; bt_string_to_uuid(&lhs, uuid1); return bt_uuid_cmp(&lhs, uuid2) == 0; } static void check_probe_done(struct bt_asha *asha) { uint8_t zeroes[8] = { 0, }; /* Once we have ROPs & PSM, we should be good to go */ if (asha->psm == 0 || memcmp(asha->hisyncid, zeroes, sizeof(zeroes)) == 0) return; if (asha->attach_cb) { asha->attach_cb(asha->attach_cb_data); asha->attach_cb = NULL; asha->attach_cb_data = NULL; } } static void read_psm(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_asha *asha = user_data; if (!success) { DBG("Reading PSM failed with ATT error: %u", att_ecode); return; } if (length != 2) { DBG("Reading PSM failed: unexpected length %u", length); return; } asha->psm = get_le16(value); DBG("Got PSM: %u", asha->psm); check_probe_done(asha); } static void read_rops(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_asha *asha = user_data; if (!success) { DBG("Reading ROPs failed with ATT error: %u", att_ecode); return; } if (length != 17) { DBG("Reading ROPs failed: unexpected length %u", length); return; } if (value[0] != 0x01) { DBG("Unexpected ASHA version: %u", value[0]); return; } /* Device Capabilities */ asha->right_side = (value[1] & 0x1) != 0; asha->binaural = (value[1] & 0x2) != 0; asha->csis_supported = (value[1] & 0x4) != 0; /* HiSyncId: 2 byte company id, 6 byte ID shared by left and right */ memcpy(asha->hisyncid, &value[2], 8); /* FeatureMap */ asha->coc_streaming_supported = (value[10] & 0x1) != 0; /* RenderDelay */ asha->render_delay = get_le16(&value[11]); /* byte 13 & 14 are reserved */ /* Codec IDs */ asha->codec_ids = get_le16(&value[15]); DBG("Got ROPS: side %u, binaural %u, csis: %u, delay %u, codecs: %u", asha->right_side, asha->binaural, asha->csis_supported, asha->render_delay, asha->codec_ids); check_probe_done(asha); } static void audio_status_register(uint16_t att_ecode, void *user_data) { if (att_ecode) DBG("AudioStatusPoint register failed 0x%04x", att_ecode); else DBG("AudioStatusPoint register succeeded"); } static void audio_status_notify(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct bt_asha *asha = user_data; uint8_t status = *value; /* Back these up to survive the reset paths */ bt_asha_cb_t state_cb = asha->state_cb; bt_asha_cb_t state_cb_data = asha->state_cb_data; DBG("ASHA status %u", status); if (asha->state == ASHA_STARTING) { if (status == 0) { asha->state = ASHA_STARTED; DBG("ASHA start complete"); update_asha_set(asha, true); asha_set_send_status(asha, true); } else { bt_asha_state_reset(asha); DBG("ASHA start failed"); } } if (state_cb) { state_cb(status, state_cb_data); asha->state_cb = NULL; asha->state_cb_data = NULL; } } static void handle_characteristic(struct gatt_db_attribute *attr, void *user_data) { struct bt_asha *asha = user_data; uint16_t value_handle; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) { error("Failed to obtain characteristic data"); return; } bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); if (uuid_cmp(ASHA_CHRC_LE_PSM_OUT_UUID, &uuid)) { DBG("Got chrc %s/0x%x: LE_PSM_ID", uuid_str, value_handle); if (!bt_gatt_client_read_value(asha->client, value_handle, read_psm, asha, NULL)) DBG("Failed to send request to read battery level"); } else if (uuid_cmp(ASHA_CHRC_READ_ONLY_PROPERTIES_UUID, &uuid)) { DBG("Got chrc %s/0x%x: READ_ONLY_PROPERTIES", uuid_str, value_handle); if (!bt_gatt_client_read_value(asha->client, value_handle, read_rops, asha, NULL)) DBG("Failed to send request for readonly properties"); } else if (uuid_cmp(ASHA_CHRC_AUDIO_CONTROL_POINT_UUID, &uuid)) { DBG("Got chrc %s/0x%x: AUDIO_CONTROL_POINT", uuid_str, value_handle); /* Store this for later writes */ asha->acp_handle = value_handle; } else if (uuid_cmp(ASHA_CHRC_VOLUME_UUID, &uuid)) { DBG("Got chrc %s/0x%x: VOLUME", uuid_str, value_handle); /* Store this for later writes */ asha->volume_handle = value_handle; } else if (uuid_cmp(ASHA_CHRC_AUDIO_STATUS_UUID, &uuid)) { DBG("Got chrc %s/0x%x: AUDIO_STATUS", uuid_str, value_handle); asha->status_notify_id = bt_gatt_client_register_notify(asha->client, value_handle, audio_status_register, audio_status_notify, asha, NULL); if (!asha->status_notify_id) DBG("Failed to send request to notify AudioStatus"); } else { DBG("Unsupported characteristic: %s", uuid_str); } } static void foreach_asha_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_asha *asha = user_data; DBG("Found ASHA GATT service"); asha->attr = attr; gatt_db_service_set_claimed(attr, true); gatt_db_service_foreach_char(asha->attr, handle_characteristic, asha); } bool bt_asha_attach(struct bt_asha *asha, struct gatt_db *db, struct bt_gatt_client *client, bt_asha_attach_cb_t attach_cb, void *cb_user_data) { bt_uuid_t asha_uuid; asha->db = gatt_db_ref(db); asha->client = bt_gatt_client_clone(client); asha->attach_cb = attach_cb; asha->attach_cb_data = cb_user_data; bt_uuid16_create(&asha_uuid, ASHA_SERVICE); gatt_db_foreach_service(db, &asha_uuid, foreach_asha_service, asha); if (!asha->attr) { error("ASHA attribute not found"); bt_asha_reset(asha); return false; } if (!asha_devices) asha_devices = queue_new(); return true; } bluez-5.87/src/shared/PaxHeaders/mainloop.c0000644000000000000000000000005015017252326015675 xustar0020 atime=1783093258 20 ctime=1783094100 bluez-5.87/src/shared/mainloop.c0000644000000000000000000001324615017252326015364 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2002-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include "mainloop.h" #include "mainloop-notify.h" #define MAX_EPOLL_EVENTS 10 static int epoll_fd; static int epoll_terminate; static int exit_status = EXIT_SUCCESS; struct mainloop_data { int fd; uint32_t events; mainloop_event_func callback; mainloop_destroy_func destroy; void *user_data; }; #define MAX_MAINLOOP_ENTRIES 128 static struct mainloop_data *mainloop_list[MAX_MAINLOOP_ENTRIES]; struct timeout_data { int fd; mainloop_timeout_func callback; mainloop_destroy_func destroy; void *user_data; }; void mainloop_init(void) { unsigned int i; epoll_fd = epoll_create1(EPOLL_CLOEXEC); for (i = 0; i < MAX_MAINLOOP_ENTRIES; i++) mainloop_list[i] = NULL; epoll_terminate = 0; } void mainloop_quit(void) { epoll_terminate = 1; mainloop_sd_notify("STOPPING=1"); } void mainloop_exit_success(void) { exit_status = EXIT_SUCCESS; epoll_terminate = 1; } void mainloop_exit_failure(void) { exit_status = EXIT_FAILURE; epoll_terminate = 1; } int mainloop_run(void) { unsigned int i; while (!epoll_terminate) { struct epoll_event events[MAX_EPOLL_EVENTS]; int n, nfds; nfds = epoll_wait(epoll_fd, events, MAX_EPOLL_EVENTS, -1); if (nfds < 0) continue; for (n = 0; n < nfds; n++) { struct mainloop_data *data = events[n].data.ptr; data->callback(data->fd, events[n].events, data->user_data); } } for (i = 0; i < MAX_MAINLOOP_ENTRIES; i++) { struct mainloop_data *data = mainloop_list[i]; mainloop_list[i] = NULL; if (data) { epoll_ctl(epoll_fd, EPOLL_CTL_DEL, data->fd, NULL); if (data->destroy) data->destroy(data->user_data); free(data); } } close(epoll_fd); epoll_fd = 0; mainloop_notify_exit(); return exit_status; } int mainloop_add_fd(int fd, uint32_t events, mainloop_event_func callback, void *user_data, mainloop_destroy_func destroy) { struct mainloop_data *data; struct epoll_event ev; int err; if (fd < 0 || fd > MAX_MAINLOOP_ENTRIES - 1 || !callback) return -EINVAL; data = malloc(sizeof(*data)); if (!data) return -ENOMEM; memset(data, 0, sizeof(*data)); data->fd = fd; data->events = events; data->callback = callback; data->destroy = destroy; data->user_data = user_data; memset(&ev, 0, sizeof(ev)); ev.events = events; ev.data.ptr = data; err = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, data->fd, &ev); if (err < 0) { free(data); return err; } mainloop_list[fd] = data; return 0; } int mainloop_modify_fd(int fd, uint32_t events) { struct mainloop_data *data; struct epoll_event ev; int err; if (fd < 0 || fd > MAX_MAINLOOP_ENTRIES - 1) return -EINVAL; data = mainloop_list[fd]; if (!data) return -ENXIO; memset(&ev, 0, sizeof(ev)); ev.events = events; ev.data.ptr = data; err = epoll_ctl(epoll_fd, EPOLL_CTL_MOD, data->fd, &ev); if (err < 0) return err; data->events = events; return 0; } int mainloop_remove_fd(int fd) { struct mainloop_data *data; int err; if (fd < 0 || fd > MAX_MAINLOOP_ENTRIES - 1) return -EINVAL; data = mainloop_list[fd]; if (!data) return -ENXIO; mainloop_list[fd] = NULL; err = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, data->fd, NULL); if (data->destroy) data->destroy(data->user_data); free(data); return err; } static void timeout_destroy(void *user_data) { struct timeout_data *data = user_data; close(data->fd); data->fd = -1; if (data->destroy) data->destroy(data->user_data); free(data); } static void timeout_callback(int fd, uint32_t events, void *user_data) { struct timeout_data *data = user_data; uint64_t expired; ssize_t result; if (events & (EPOLLERR | EPOLLHUP)) return; result = read(data->fd, &expired, sizeof(expired)); if (result != sizeof(expired)) return; if (data->callback) data->callback(data->fd, data->user_data); } static inline int timeout_set(int fd, unsigned int msec) { struct itimerspec itimer; unsigned int sec = msec / 1000; memset(&itimer, 0, sizeof(itimer)); itimer.it_interval.tv_sec = 0; itimer.it_interval.tv_nsec = 0; itimer.it_value.tv_sec = sec; itimer.it_value.tv_nsec = (msec - (sec * 1000)) * 1000 * 1000; return timerfd_settime(fd, 0, &itimer, NULL); } int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback, void *user_data, mainloop_destroy_func destroy) { struct timeout_data *data; if (!callback) return -EINVAL; data = malloc(sizeof(*data)); if (!data) return -ENOMEM; memset(data, 0, sizeof(*data)); data->callback = callback; data->destroy = destroy; data->user_data = user_data; data->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); if (data->fd < 0) { free(data); return -EIO; } if (msec > 0) { if (timeout_set(data->fd, msec) < 0) { close(data->fd); free(data); return -EIO; } } if (mainloop_add_fd(data->fd, EPOLLIN | EPOLLONESHOT, timeout_callback, data, timeout_destroy) < 0) { close(data->fd); free(data); return -EIO; } return data->fd; } int mainloop_modify_timeout(int id, unsigned int msec) { if (msec > 0) { if (timeout_set(id, msec) < 0) return -EIO; } if (mainloop_modify_fd(id, EPOLLIN | EPOLLONESHOT) < 0) return -EIO; return 0; } int mainloop_remove_timeout(int id) { return mainloop_remove_fd(id); } bluez-5.87/src/shared/PaxHeaders/timeout-mainloop.c0000644000000000000000000000005014031556405017361 xustar0020 atime=1783093257 20 ctime=1783094100 bluez-5.87/src/shared/timeout-mainloop.c0000644000000000000000000000301714031556405017043 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * */ #include #include "mainloop.h" #include "util.h" #include "timeout.h" struct timeout_data { int id; timeout_func_t func; timeout_destroy_func_t destroy; unsigned int timeout; void *user_data; }; static void timeout_callback(int id, void *user_data) { struct timeout_data *data = user_data; if (data->func(data->user_data) && !mainloop_modify_timeout(data->id, data->timeout)) return; mainloop_remove_timeout(data->id); } static void timeout_destroy(void *user_data) { struct timeout_data *data = user_data; if (data->destroy) data->destroy(data->user_data); free(data); } unsigned int timeout_add(unsigned int timeout, timeout_func_t func, void *user_data, timeout_destroy_func_t destroy) { struct timeout_data *data; data = new0(struct timeout_data, 1); data->func = func; data->user_data = user_data; data->timeout = timeout; data->destroy = destroy; data->id = mainloop_add_timeout(timeout, timeout_callback, data, timeout_destroy); if (data->id < 0) { free(data); return 0; } return (unsigned int) data->id; } void timeout_remove(unsigned int id) { if (!id) return; mainloop_remove_timeout((int) id); } unsigned int timeout_add_seconds(unsigned int timeout, timeout_func_t func, void *user_data, timeout_destroy_func_t destroy) { return timeout_add(timeout * 1000, func, user_data, destroy); } bluez-5.87/src/shared/PaxHeaders/hci.c0000644000000000000000000000005015221752714014625 xustar0020 atime=1783092685 20 ctime=1783094099 bluez-5.87/src/shared/hci.c0000644000000000000000000004756315221752714014325 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "bluetooth/hci.h" #include "monitor/bt.h" #include "src/shared/mainloop.h" #include "src/shared/io.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/timeout.h" #include "src/shared/hci.h" struct bt_hci { int ref_count; struct io *io; bool is_stream; bool writer_active; uint8_t num_cmds; unsigned int next_cmd_id; unsigned int next_evt_id; unsigned int filter_id; struct queue *cmd_queue; struct queue *rsp_queue; struct queue *evt_list; struct queue *subevt_list; struct queue *data_queue; }; struct cmd { unsigned int id; uint16_t opcode; void *data; uint8_t size; bt_hci_callback_func_t callback; bt_hci_destroy_func_t destroy; void *user_data; }; struct evt { unsigned int id; uint8_t event; bt_hci_callback_func_t callback; bt_hci_destroy_func_t destroy; void *user_data; }; struct data { uint8_t type; uint16_t handle; void *data; uint8_t size; }; static void cmd_free(void *data) { struct cmd *cmd = data; if (cmd->destroy) cmd->destroy(cmd->user_data); free(cmd->data); free(cmd); } static void evt_free(void *data) { struct evt *evt = data; if (evt->destroy) evt->destroy(evt->user_data); free(evt); } static void data_free(void *data) { struct data *d = data; free(d->data); free(d); } static void send_command(struct bt_hci *hci, uint16_t opcode, void *data, uint8_t size) { uint8_t type = BT_H4_CMD_PKT; struct bt_hci_cmd_hdr hdr; struct iovec iov[3]; int iovcnt; if (hci->num_cmds < 1) return; hdr.opcode = cpu_to_le16(opcode); hdr.plen = size; iov[0].iov_base = &type; iov[0].iov_len = 1; iov[1].iov_base = &hdr; iov[1].iov_len = sizeof(hdr); if (size > 0) { iov[2].iov_base = data; iov[2].iov_len = size; iovcnt = 3; } else iovcnt = 2; if (io_send(hci->io, iov, iovcnt) < 0) return; hci->num_cmds--; } static void send_data(struct bt_hci *hci, uint8_t type, uint16_t handle, void *data, uint16_t size) { struct iovec iov[3]; struct bt_hci_acl_hdr hdr; hdr.handle = cpu_to_le16(handle); hdr.dlen = cpu_to_le16(size); iov[0].iov_base = &type; iov[0].iov_len = 1; iov[1].iov_base = &hdr; iov[1].iov_len = sizeof(hdr); iov[2].iov_base = data; iov[2].iov_len = size; io_send(hci->io, iov, 3); } static bool io_write_callback(struct io *io, void *user_data) { struct bt_hci *hci = user_data; struct cmd *cmd; struct data *data; if (hci->num_cmds) { cmd = queue_pop_head(hci->cmd_queue); if (cmd) { send_command(hci, cmd->opcode, cmd->data, cmd->size); queue_push_tail(hci->rsp_queue, cmd); } } data = queue_pop_head(hci->data_queue); if (data) send_data(hci, data->type, data->handle, data->data, data->size); hci->writer_active = false; return false; } static void wakeup_writer(struct bt_hci *hci) { if (hci->writer_active) return; if (queue_isempty(hci->cmd_queue) && queue_isempty(hci->data_queue)) return; if (!io_set_write_handler(hci->io, io_write_callback, hci, NULL)) return; hci->writer_active = true; } static bool match_cmd_opcode(const void *a, const void *b) { const struct cmd *cmd = a; uint16_t opcode = PTR_TO_UINT(b); return cmd->opcode == opcode; } static void process_response(struct bt_hci *hci, uint16_t opcode, const void *data, size_t size) { struct cmd *cmd; if (opcode == BT_HCI_CMD_NOP) { wakeup_writer(hci); return; } cmd = queue_remove_if(hci->rsp_queue, match_cmd_opcode, UINT_TO_PTR(opcode)); if (!cmd) return; /* Take a reference before calling the callback since that can unref * its reference destroying the instance. */ bt_hci_ref(hci); if (cmd->callback) cmd->callback(data, size, cmd->user_data); cmd_free(cmd); wakeup_writer(hci); bt_hci_unref(hci); } static void process_notify(void *data, void *user_data) { struct bt_hci_evt_hdr *hdr = user_data; struct evt *evt = data; if (evt->event == hdr->evt) evt->callback(user_data + sizeof(struct bt_hci_evt_hdr), hdr->plen, evt->user_data); } struct subevt_data { uint8_t subevent; const void *data; uint8_t size; }; static void process_subevt_notify(void *data, void *user_data) { struct subevt_data *sd = user_data; struct evt *evt = data; if (evt->event == sd->subevent) evt->callback(sd->data, sd->size, evt->user_data); } static void process_event(struct bt_hci *hci, const void *data, size_t size) { const struct bt_hci_evt_hdr *hdr = data; const struct bt_hci_evt_cmd_complete *cc; const struct bt_hci_evt_cmd_status *cs; if (size < sizeof(struct bt_hci_evt_hdr)) return; data += sizeof(struct bt_hci_evt_hdr); size -= sizeof(struct bt_hci_evt_hdr); if (hdr->plen != size) return; switch (hdr->evt) { case BT_HCI_EVT_CMD_COMPLETE: if (size < sizeof(*cc)) return; cc = data; hci->num_cmds = cc->ncmd; process_response(hci, le16_to_cpu(cc->opcode), data + sizeof(*cc), size - sizeof(*cc)); break; case BT_HCI_EVT_CMD_STATUS: if (size < sizeof(*cs)) return; cs = data; hci->num_cmds = cs->ncmd; process_response(hci, le16_to_cpu(cs->opcode), &cs->status, 1); break; default: queue_foreach(hci->evt_list, process_notify, (void *) hdr); if (hdr->evt == BT_HCI_EVT_LE_META_EVENT && size > 0) { const uint8_t *params = data; struct subevt_data sd; sd.subevent = params[0]; sd.data = data + 1; sd.size = size - 1; queue_foreach(hci->subevt_list, process_subevt_notify, &sd); } break; } } static bool io_read_callback(struct io *io, void *user_data) { struct bt_hci *hci = user_data; uint8_t buf[512]; ssize_t len; int fd; fd = io_get_fd(hci->io); if (fd < 0) return false; if (hci->is_stream) return false; len = read(fd, buf, sizeof(buf)); if (len < 0) return false; if (len < 1) return true; switch (buf[0]) { case BT_H4_EVT_PKT: process_event(hci, buf + 1, len - 1); break; } return true; } static struct bt_hci *create_hci(int fd) { struct bt_hci *hci; if (fd < 0) return NULL; hci = new0(struct bt_hci, 1); hci->io = io_new(fd); if (!hci->io) { free(hci); return NULL; } hci->is_stream = true; hci->writer_active = false; hci->num_cmds = 1; hci->next_cmd_id = 1; hci->next_evt_id = 1; hci->cmd_queue = queue_new(); hci->rsp_queue = queue_new(); hci->evt_list = queue_new(); hci->subevt_list = queue_new(); hci->data_queue = queue_new(); if (!io_set_read_handler(hci->io, io_read_callback, hci, NULL)) { queue_destroy(hci->evt_list, NULL); queue_destroy(hci->subevt_list, NULL); queue_destroy(hci->rsp_queue, NULL); queue_destroy(hci->cmd_queue, NULL); queue_destroy(hci->data_queue, NULL); io_destroy(hci->io); free(hci); return NULL; } return bt_hci_ref(hci); } struct bt_hci *bt_hci_new(int fd) { struct bt_hci *hci; hci = create_hci(fd); if (!hci) return NULL; return hci; } static int create_socket(uint16_t index, uint16_t channel) { struct sockaddr_hci addr; int fd; fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, BTPROTO_HCI); if (fd < 0) return -1; memset(&addr, 0, sizeof(addr)); addr.hci_family = AF_BLUETOOTH; addr.hci_dev = index; addr.hci_channel = channel; if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { close(fd); return -1; } return fd; } struct bt_hci *bt_hci_new_user_channel(uint16_t index) { struct bt_hci *hci; int fd; fd = create_socket(index, HCI_CHANNEL_USER); if (fd < 0) { printf("Unable to create user channel socket: %s(%d)\n", strerror(errno), -errno); return NULL; } hci = create_hci(fd); if (!hci) { close(fd); return NULL; } hci->is_stream = false; bt_hci_set_close_on_unref(hci, true); return hci; } struct bt_hci *bt_hci_new_raw_device(uint16_t index) { struct bt_hci *hci; struct hci_filter flt; int fd; fd = create_socket(index, HCI_CHANNEL_RAW); if (fd < 0) return NULL; memset(&flt, 0, sizeof(flt)); flt.type_mask = 1 << BT_H4_EVT_PKT; flt.event_mask[0] = 0xffffffff; flt.event_mask[1] = 0xffffffff; if (setsockopt(fd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) { close(fd); return NULL; } hci = create_hci(fd); if (!hci) { close(fd); return NULL; } hci->is_stream = false; bt_hci_set_close_on_unref(hci, true); return hci; } struct bt_hci *bt_hci_ref(struct bt_hci *hci) { if (!hci) return NULL; __sync_fetch_and_add(&hci->ref_count, 1); return hci; } void bt_hci_unref(struct bt_hci *hci) { if (!hci) return; if (__sync_sub_and_fetch(&hci->ref_count, 1)) return; if (hci->filter_id) timeout_remove(hci->filter_id); queue_destroy(hci->evt_list, evt_free); queue_destroy(hci->subevt_list, evt_free); queue_destroy(hci->cmd_queue, cmd_free); queue_destroy(hci->rsp_queue, cmd_free); queue_destroy(hci->data_queue, data_free); io_destroy(hci->io); free(hci); } bool bt_hci_set_close_on_unref(struct bt_hci *hci, bool do_close) { if (!hci) return false; return io_set_close_on_destroy(hci->io, do_close); } unsigned int bt_hci_send(struct bt_hci *hci, uint16_t opcode, const void *data, uint8_t size, bt_hci_callback_func_t callback, void *user_data, bt_hci_destroy_func_t destroy) { struct cmd *cmd; if (!hci) return 0; cmd = new0(struct cmd, 1); cmd->opcode = opcode; cmd->size = size; if (cmd->size > 0) { cmd->data = malloc(cmd->size); if (!cmd->data) { free(cmd); return 0; } memcpy(cmd->data, data, cmd->size); } if (hci->next_cmd_id < 1) hci->next_cmd_id = 1; cmd->id = hci->next_cmd_id++; cmd->callback = callback; cmd->destroy = destroy; cmd->user_data = user_data; if (!queue_push_tail(hci->cmd_queue, cmd)) { free(cmd->data); free(cmd); return 0; } wakeup_writer(hci); return cmd->id; } static bool match_cmd_id(const void *a, const void *b) { const struct cmd *cmd = a; unsigned int id = PTR_TO_UINT(b); return cmd->id == id; } bool bt_hci_cancel(struct bt_hci *hci, unsigned int id) { struct cmd *cmd; if (!hci || !id) return false; cmd = queue_remove_if(hci->cmd_queue, match_cmd_id, UINT_TO_PTR(id)); if (!cmd) { cmd = queue_remove_if(hci->rsp_queue, match_cmd_id, UINT_TO_PTR(id)); if (!cmd) return false; } cmd_free(cmd); wakeup_writer(hci); return true; } bool bt_hci_flush(struct bt_hci *hci) { if (!hci) return false; if (hci->writer_active) { io_set_write_handler(hci->io, NULL, NULL, NULL); hci->writer_active = false; } queue_remove_all(hci->cmd_queue, NULL, NULL, cmd_free); queue_remove_all(hci->rsp_queue, NULL, NULL, cmd_free); queue_remove_all(hci->data_queue, NULL, NULL, data_free); return true; } static void update_evt_filter(struct bt_hci *hci) { const struct queue_entry *entry; struct sock_filter *filters; struct sock_fprog fprog; unsigned int evt_count, subevt_count, count, i; int fd; fd = io_get_fd(hci->io); if (fd < 0) return; /* If stream-based (not a raw socket), no BPF filtering needed */ if (hci->is_stream) return; evt_count = queue_length(hci->evt_list); subevt_count = queue_length(hci->subevt_list); /* Filter structure: * Packet layout: [H4 type(1)][evt code(1)][plen(1)][params...] * For LE Meta: params[0] is the subevent code (offset 3 from start) * * [0] Load byte at offset 1 (event code) * [1] JEQ CMD_COMPLETE -> accept * [2] JEQ CMD_STATUS -> accept * [3] JEQ LE_META -> subevent_check (if subevts registered) * [4..4+evt_count-1] JEQ registered_event -> accept * [4+evt_count] reject * -- subevent section (if subevt_count > 0) -- * [5+evt_count] Load byte at offset 3 (subevent code) * [6+evt_count..6+evt_count+subevt_count-1] JEQ subevent -> accept * [6+evt_count+subevt_count] reject * -- shared accept -- * [last] accept */ /* Without subevents: 3 (defaults) + evt_count + reject + accept = * evt_count + 5 * With subevents: 4 (defaults+LE_META) + evt_count + reject + * 1 (load subevent) + subevt_count + reject + accept */ if (subevt_count) count = 4 + evt_count + 1 + 1 + subevt_count + 1 + 1; else count = 3 + evt_count + 1 + 1; filters = malloc(sizeof(*filters) * count); if (!filters) return; i = 0; /* Load event code byte */ filters[i++] = (struct sock_filter) BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 1); if (subevt_count) { /* accept is at index: count - 1 * From instruction at index i, jump_true = (count-1) - (i+1) */ /* Check BT_HCI_EVT_CMD_COMPLETE -> accept */ filters[i] = (struct sock_filter) BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BT_HCI_EVT_CMD_COMPLETE, count - 1 - (i + 1), 0); i++; /* Check BT_HCI_EVT_CMD_STATUS -> accept */ filters[i] = (struct sock_filter) BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BT_HCI_EVT_CMD_STATUS, count - 1 - (i + 1), 0); i++; /* Check LE_META -> subevent section * subevent section starts at: 4 + evt_count + 1 * (after the evt reject instruction) */ filters[i] = (struct sock_filter) BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BT_HCI_EVT_LE_META_EVENT, 4 + evt_count + 1 - (i + 1), 0); i++; /* Check each registered event -> accept */ entry = queue_get_entries(hci->evt_list); while (entry) { const struct evt *evt = entry->data; filters[i] = (struct sock_filter) BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, evt->event, count - 1 - (i + 1), 0); i++; entry = entry->next; } /* Reject (for non-matching events) */ filters[i++] = (struct sock_filter) BPF_STMT(BPF_RET | BPF_K, 0); /* Subevent section: load subevent byte at offset 3 */ filters[i++] = (struct sock_filter) BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 3); /* Check each registered subevent -> accept */ entry = queue_get_entries(hci->subevt_list); while (entry) { const struct evt *evt = entry->data; filters[i] = (struct sock_filter) BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, evt->event, count - 1 - (i + 1), 0); i++; entry = entry->next; } /* Reject (for non-matching subevents) */ filters[i++] = (struct sock_filter) BPF_STMT(BPF_RET | BPF_K, 0); } else { /* No subevents - simple filter */ /* Check BT_HCI_EVT_CMD_COMPLETE -> accept */ filters[i] = (struct sock_filter) BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BT_HCI_EVT_CMD_COMPLETE, count - 1 - (i + 1), 0); i++; /* Check BT_HCI_EVT_CMD_STATUS -> accept */ filters[i] = (struct sock_filter) BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BT_HCI_EVT_CMD_STATUS, count - 1 - (i + 1), 0); i++; /* Check each registered event -> accept */ entry = queue_get_entries(hci->evt_list); while (entry) { const struct evt *evt = entry->data; filters[i] = (struct sock_filter) BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, evt->event, count - 1 - (i + 1), 0); i++; entry = entry->next; } /* Reject */ filters[i++] = (struct sock_filter) BPF_STMT(BPF_RET | BPF_K, 0); } /* Accept */ filters[i++] = (struct sock_filter) BPF_STMT(BPF_RET | BPF_K, 0x0fffffff); fprog.len = i; fprog.filter = filters; setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); free(filters); } static bool filter_timeout(void *user_data) { struct bt_hci *hci = user_data; hci->filter_id = 0; update_evt_filter(hci); return false; } static void schedule_evt_filter(struct bt_hci *hci) { /* Coalesce multiple filter updates into a single SO_ATTACH_FILTER call * by deferring the update to the next event loop iteration. */ if (hci->filter_id) return; hci->filter_id = timeout_add(0, filter_timeout, hci, NULL); } static bool match_evt_event(const void *a, const void *b) { const struct evt *evt = a; uint8_t event = PTR_TO_UINT(b); return evt->event == event; } unsigned int bt_hci_register(struct bt_hci *hci, uint8_t event, bt_hci_callback_func_t callback, void *user_data, bt_hci_destroy_func_t destroy) { struct evt *evt; bool update_filter; if (!hci) return 0; /* Check if event already has a handler registered */ update_filter = !queue_find(hci->evt_list, match_evt_event, UINT_TO_PTR(event)); evt = new0(struct evt, 1); evt->event = event; if (hci->next_evt_id < 1) hci->next_evt_id = 1; evt->id = hci->next_evt_id++; evt->callback = callback; evt->destroy = destroy; evt->user_data = user_data; if (!queue_push_tail(hci->evt_list, evt)) { free(evt); return 0; } if (update_filter) schedule_evt_filter(hci); return evt->id; } bool bt_hci_send_data(struct bt_hci *hci, uint8_t type, uint16_t handle, const void *data, uint8_t size) { struct data *d; if (!hci) return false; /* Check if type really reflects to a data packet */ switch (type) { case BT_H4_ACL_PKT: case BT_H4_SCO_PKT: case BT_H4_ISO_PKT: break; default: return false; } d = new0(struct data, 1); d->type = type; d->handle = handle; d->size = size; if (d->size > 0) { d->data = util_memdup(data, d->size); if (!d->data) { free(d); return false; } } if (!queue_push_tail(hci->data_queue, d)) { free(d->data); free(d); return false; } wakeup_writer(hci); return true; } static bool match_evt_id(const void *a, const void *b) { const struct evt *evt = a; unsigned int id = PTR_TO_UINT(b); return evt->id == id; } bool bt_hci_unregister(struct bt_hci *hci, unsigned int id) { struct evt *evt; uint8_t event; if (!hci || !id) return false; evt = queue_remove_if(hci->evt_list, match_evt_id, UINT_TO_PTR(id)); if (!evt) return false; event = evt->event; evt_free(evt); /* Only update filter if no other handler for this event remains */ if (!queue_find(hci->evt_list, match_evt_event, UINT_TO_PTR(event))) schedule_evt_filter(hci); return true; } unsigned int bt_hci_register_subevent(struct bt_hci *hci, uint8_t subevent, bt_hci_callback_func_t callback, void *user_data, bt_hci_destroy_func_t destroy) { struct evt *evt; bool update_filter; if (!hci) return 0; /* Check if subevent already has a handler registered */ update_filter = !queue_find(hci->subevt_list, match_evt_event, UINT_TO_PTR(subevent)); evt = new0(struct evt, 1); evt->event = subevent; if (hci->next_evt_id < 1) hci->next_evt_id = 1; evt->id = hci->next_evt_id++; evt->callback = callback; evt->destroy = destroy; evt->user_data = user_data; if (!queue_push_tail(hci->subevt_list, evt)) { free(evt); return 0; } if (update_filter) schedule_evt_filter(hci); return evt->id; } bool bt_hci_unregister_subevent(struct bt_hci *hci, unsigned int id) { struct evt *evt; uint8_t event; if (!hci || !id) return false; evt = queue_remove_if(hci->subevt_list, match_evt_id, UINT_TO_PTR(id)); if (!evt) return false; event = evt->event; evt_free(evt); /* Only update filter if no other handler for this subevent remains */ if (!queue_find(hci->subevt_list, match_evt_event, UINT_TO_PTR(event))) schedule_evt_filter(hci); return true; } bool bt_hci_get_conn_handle(struct bt_hci *hci, const uint8_t *bdaddr, uint16_t *handle) { struct hci_conn_list_req *cl; struct hci_conn_info *ci; int fd, i; bool found = false; if (!hci || !bdaddr || !handle) return false; fd = io_get_fd(hci->io); if (fd < 0) return false; /* Allocate buffer for connection list request */ cl = malloc(10 * sizeof(*ci) + sizeof(*cl)); if (!cl) return false; memset(cl, 0, 10 * sizeof(*ci) + sizeof(*cl)); cl->dev_id = 0; /* Will be filled by ioctl */ cl->conn_num = 10; /* Get connection list via ioctl */ if (ioctl(fd, HCIGETCONNLIST, (void *) cl) < 0) { free(cl); return false; } /* Search for the connection with matching bdaddr */ ci = cl->conn_info; for (i = 0; i < cl->conn_num; i++, ci++) { if (memcmp(&ci->bdaddr, bdaddr, 6) == 0) { *handle = ci->handle; found = true; break; } } free(cl); return found; } bluez-5.87/src/shared/PaxHeaders/bap.c0000644000000000000000000000005015221752714014624 xustar0020 atime=1783092685 20 ctime=1783094100 bluez-5.87/src/shared/bap.c0000644000000000000000000054336215221752714014322 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. * Copyright 2023-2025 NXP * */ #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "src/shared/io.h" #include "src/shared/queue.h" #include "src/shared/util.h" #include "src/shared/timeout.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-server.h" #include "src/shared/gatt-client.h" #include "src/shared/bap.h" #include "src/shared/ascs.h" #include "src/shared/bap-debug.h" /* Maximum number of ASE(s) */ #define NUM_SINKS 2 #define NUM_SOURCE 2 #define NUM_ASES (NUM_SINKS + NUM_SOURCE) #define ASE_UUID(_id) (_id < NUM_SINKS ? ASE_SINK_UUID : ASE_SOURCE_UUID) #define DBG(_bap, fmt, arg...) \ bap_debug(_bap, "%s:%s() " fmt, __FILE__, __func__, ## arg) #define LTV(_type, _bytes...) \ { \ .len = 1 + sizeof((uint8_t []) { _bytes }), \ .type = _type, \ .data = { _bytes }, \ } #define BAP_PROCESS_TIMEOUT 10 #define BAP_FREQ_LTV_TYPE 1 #define BAP_DURATION_LTV_TYPE 2 #define BAP_CHANNEL_ALLOCATION_LTV_TYPE 3 #define BAP_FRAME_LEN_LTV_TYPE 4 #define CODEC_SPECIFIC_CONFIGURATION_MASK (\ (1<db == db); } unsigned int bt_bap_pac_register(struct bt_bap *bap, bt_bap_pac_func_t added, bt_bap_pac_func_t removed, void *user_data, bt_bap_destroy_func_t destroy) { struct bt_bap_pac_changed *changed; static unsigned int id; if (!bap) return 0; changed = new0(struct bt_bap_pac_changed, 1); changed->id = ++id ? id : ++id; changed->added = added; changed->removed = removed; changed->destroy = destroy; changed->data = user_data; queue_push_tail(bap->pac_cbs, changed); return changed->id; } static void pac_changed_free(void *data) { struct bt_bap_pac_changed *changed = data; if (changed->destroy) changed->destroy(changed->data); free(changed); } static bool match_pac_changed_id(const void *data, const void *match_data) { const struct bt_bap_pac_changed *changed = data; unsigned int id = PTR_TO_UINT(match_data); return (changed->id == id); } bool bt_bap_pac_unregister(struct bt_bap *bap, unsigned int id) { struct bt_bap_pac_changed *changed; if (!bap) return false; changed = queue_remove_if(bap->pac_cbs, match_pac_changed_id, UINT_TO_PTR(id)); if (!changed) return false; pac_changed_free(changed); return true; } static void pac_foreach(void *data, void *user_data) { struct bt_bap_pac *pac = data; struct iovec *iov = user_data; struct bt_pacs_read_rsp *rsp; struct bt_pac *p; struct bt_pac_metadata *meta; if (!iov->iov_len) { rsp = util_iov_push(iov, sizeof(*rsp)); rsp->num_pac = 0; } else rsp = iov->iov_base; rsp->num_pac++; p = util_iov_push(iov, sizeof(*p)); p->codec.id = pac->codec.id; p->codec.cid = cpu_to_le16(pac->codec.cid); p->codec.vid = cpu_to_le16(pac->codec.vid); if (pac->data) { p->cc_len = pac->data->iov_len; util_iov_push_mem(iov, p->cc_len, pac->data->iov_base); } else p->cc_len = 0; meta = util_iov_push(iov, sizeof(*meta)); if (pac->metadata) { meta->len = pac->metadata->iov_len; util_iov_push_mem(iov, meta->len, pac->metadata->iov_base); } else meta->len = 0; } static void pacs_sink_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_pacs *pacs = user_data; struct bt_bap_db *bdb = pacs->bdb; struct iovec iov; uint8_t value[512]; memset(value, 0, sizeof(value)); iov.iov_base = value; iov.iov_len = 0; queue_foreach(bdb->sinks, pac_foreach, &iov); queue_foreach(bdb->broadcast_sinks, pac_foreach, &iov); if (offset > iov.iov_len) { gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_INVALID_OFFSET, NULL, 0); return; } gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base + offset, iov.iov_len - offset); } static void pacs_sink_loc_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_pacs *pacs = user_data; uint32_t value = cpu_to_le32(pacs->sink_loc_value); gatt_db_attribute_read_result(attrib, id, 0, (void *) &value, sizeof(value)); } static void pacs_source_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_pacs *pacs = user_data; struct bt_bap_db *bdb = pacs->bdb; struct iovec iov; uint8_t value[512]; memset(value, 0, sizeof(value)); iov.iov_base = value; iov.iov_len = 0; queue_foreach(bdb->sources, pac_foreach, &iov); if (offset > iov.iov_len) { gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_INVALID_OFFSET, NULL, 0); return; } gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base + offset, iov.iov_len - offset); } static void pacs_source_loc_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_pacs *pacs = user_data; uint32_t value = cpu_to_le32(pacs->source_loc_value); gatt_db_attribute_read_result(attrib, id, 0, (void *) &value, sizeof(value)); } static void pacs_context_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_pacs *pacs = user_data; struct bt_pacs_context ctx = { .snk = cpu_to_le16(pacs->sink_context_value), .src = cpu_to_le16(pacs->source_context_value) }; gatt_db_attribute_read_result(attrib, id, 0, (void *) &ctx, sizeof(ctx)); } static void pacs_supported_context_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_pacs *pacs = user_data; struct bt_pacs_context ctx = { .snk = cpu_to_le16(pacs->supported_sink_context_value), .src = cpu_to_le16(pacs->supported_source_context_value) }; gatt_db_attribute_read_result(attrib, id, 0, (void *) &ctx, sizeof(ctx)); } static struct bt_pacs *pacs_new(struct gatt_db *db) { struct bt_pacs *pacs; bt_uuid_t uuid; if (!db) return NULL; pacs = new0(struct bt_pacs, 1); /* Populate DB with PACS attributes */ bt_uuid16_create(&uuid, PACS_UUID); pacs->service = gatt_db_add_service(db, &uuid, true, 19); bt_uuid16_create(&uuid, PAC_SINK_CHRC_UUID); pacs->sink = gatt_db_service_add_characteristic(pacs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, pacs_sink_read, NULL, pacs); pacs->sink_ccc = gatt_db_service_add_ccc(pacs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, PAC_SINK_LOC_CHRC_UUID); pacs->sink_loc = gatt_db_service_add_characteristic(pacs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, pacs_sink_loc_read, NULL, pacs); gatt_db_attribute_set_fixed_length(pacs->sink_loc, sizeof(uint32_t)); pacs->sink_loc_ccc = gatt_db_service_add_ccc(pacs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, PAC_SOURCE_CHRC_UUID); pacs->source = gatt_db_service_add_characteristic(pacs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, pacs_source_read, NULL, pacs); pacs->source_ccc = gatt_db_service_add_ccc(pacs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, PAC_SOURCE_LOC_CHRC_UUID); pacs->source_loc = gatt_db_service_add_characteristic(pacs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, pacs_source_loc_read, NULL, pacs); gatt_db_attribute_set_fixed_length(pacs->source_loc, sizeof(uint32_t)); pacs->source_loc_ccc = gatt_db_service_add_ccc(pacs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, PAC_CONTEXT); pacs->context = gatt_db_service_add_characteristic(pacs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, pacs_context_read, NULL, pacs); gatt_db_attribute_set_fixed_length(pacs->context, sizeof(struct bt_pacs_context)); pacs->context_ccc = gatt_db_service_add_ccc(pacs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, PAC_SUPPORTED_CONTEXT); pacs->supported_context = gatt_db_service_add_characteristic(pacs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, pacs_supported_context_read, NULL, pacs); gatt_db_attribute_set_fixed_length(pacs->supported_context, sizeof(struct bt_pacs_context)); pacs->supported_context_ccc = gatt_db_service_add_ccc(pacs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); gatt_db_service_set_active(pacs->service, true); return pacs; } static void bap_debug(struct bt_bap *bap, const char *format, ...) { va_list ap; if (!bap || !format || !bap->debug_func) return; va_start(ap, format); util_debug_va(bap->debug_func, bap->debug_data, format, ap); va_end(ap); } static void bap_disconnected(int err, void *user_data) { struct bt_bap *bap = user_data; bap->disconn_id = 0; DBG(bap, "bap %p disconnected err %d", bap, err); bt_bap_detach(bap); } struct bt_bap *bt_bap_get_session(struct bt_att *att, struct gatt_db *db) { const struct queue_entry *entry; struct bt_bap *bap; for (entry = queue_get_entries(sessions); entry; entry = entry->next) { struct bt_bap *bap = entry->data; if (att == bt_bap_get_att(bap)) return bap; } bap = bt_bap_new(db, NULL); if (!bap) return NULL; bap->att = att; bt_bap_attach(bap, NULL); return bap; } static bool bap_endpoint_match(const void *data, const void *match_data) { const struct bt_bap_endpoint *ep = data; const struct gatt_db_attribute *attr = match_data; return (ep->attr == attr); } static struct bt_bap_endpoint *bap_endpoint_new(struct bt_bap_db *bdb, struct gatt_db_attribute *attr) { struct bt_bap_endpoint *ep; bt_uuid_t uuid, source, sink; if (!gatt_db_attribute_get_char_data(attr, NULL, NULL, NULL, NULL, &uuid)) return NULL; ep = new0(struct bt_bap_endpoint, 1); ep->bdb = bdb; ep->attr = attr; bt_uuid16_create(&source, ASE_SOURCE_UUID); bt_uuid16_create(&sink, ASE_SINK_UUID); if (!bt_uuid_cmp(&source, &uuid)) ep->dir = BT_BAP_SOURCE; else if (!bt_uuid_cmp(&sink, &uuid)) ep->dir = BT_BAP_SINK; return ep; } static struct bt_bap_endpoint *bap_endpoint_new_broadcast(struct bt_bap_db *bdb, uint8_t type) { struct bt_bap_endpoint *ep; ep = new0(struct bt_bap_endpoint, 1); ep->bdb = bdb; ep->attr = NULL; if (type == BT_BAP_BCAST_SINK) ep->dir = BT_BAP_BCAST_SOURCE; else ep->dir = BT_BAP_BCAST_SINK; return ep; } static struct bt_bap_endpoint *bap_get_endpoint(struct queue *endpoints, struct bt_bap_db *db, struct gatt_db_attribute *attr) { struct bt_bap_endpoint *ep; if (!db || !attr) return NULL; ep = queue_find(endpoints, bap_endpoint_match, attr); if (ep) return ep; ep = bap_endpoint_new(db, attr); if (!ep) return NULL; queue_push_tail(endpoints, ep); return ep; } static bool match_ep_type(const void *data, const void *match_data) { const struct bt_bap_endpoint *ep = data; const uint8_t type = PTR_TO_INT(match_data); return (ep->dir == type); } static struct bt_bap_endpoint *bap_get_endpoint_bcast(struct queue *endpoints, struct bt_bap_db *db, uint8_t type) { struct bt_bap_endpoint *ep; if (!db) return NULL; ep = queue_find(endpoints, match_ep_type, INT_TO_PTR(type)); if (ep) return ep; ep = bap_endpoint_new_broadcast(db, type); if (!ep) return NULL; queue_push_tail(endpoints, ep); return ep; } static bool bap_endpoint_match_id(const void *data, const void *match_data) { const struct bt_bap_endpoint *ep = data; uint8_t id = PTR_TO_UINT(match_data); return (ep->id == id); } static struct bt_bap_endpoint *bap_get_local_endpoint_id(struct bt_bap *bap, uint8_t id) { struct bt_bap_endpoint *ep; struct gatt_db_attribute *attr = NULL; size_t i; if (!bap) return NULL; ep = queue_find(bap->local_eps, bap_endpoint_match_id, UINT_TO_PTR(id)); if (ep) return ep; for (i = 0; i < ARRAY_SIZE(bap->ldb->ascs->ase); i++) { struct bt_ase *ase = bap->ldb->ascs->ase[i]; if (id) { if (ase->id != id) continue; attr = ase->attr; break; } ep = queue_find(bap->local_eps, bap_endpoint_match, ase->attr); if (!ep) { attr = ase->attr; break; } } if (!attr) return NULL; ep = bap_endpoint_new(bap->ldb, attr); if (!ep) return NULL; ep->id = id; queue_push_tail(bap->local_eps, ep); return ep; } static void ascs_ase_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_ase *ase = user_data; struct bt_bap *bap = NULL; struct bt_bap_endpoint *ep = NULL; struct bt_ascs_ase_status rsp; if (ase) bap = bt_bap_get_session(att, ase->ascs->bdb->db); if (bap) ep = bap_get_endpoint(bap->local_eps, bap->ldb, attrib); if (!ep) { gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_UNLIKELY, NULL, 0); return; } memset(&rsp, 0, sizeof(rsp)); /* Initialize Endpoint ID with ASE ID */ if (ase->id != ep->id) ep->id = ase->id; rsp.id = ep->id; rsp.state = ep->state; gatt_db_attribute_read_result(attrib, id, 0, (void *) &rsp, sizeof(rsp)); } static void ase_new(struct bt_ascs *ascs, int i) { struct bt_ase *ase; bt_uuid_t uuid; if (!ascs) return; ase = new0(struct bt_ase, 1); ase->ascs = ascs; ase->id = i + 1; bt_uuid16_create(&uuid, ASE_UUID(i)); ase->attr = gatt_db_service_add_characteristic(ascs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, ascs_ase_read, NULL, ase); ase->ccc = gatt_db_service_add_ccc(ascs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); ascs->ase[i] = ase; } static bool bap_codec_equal(const struct bt_bap_codec *c1, const struct bt_bap_codec *c2) { /* Compare CID and VID if id is 0xff */ if (c1->id == 0xff) return !memcmp(c1, c2, sizeof(*c1)); return c1->id == c2->id; } static void ascs_ase_rsp_add(struct iovec *iov, uint8_t id, uint8_t code, uint8_t reason) { struct bt_ascs_cp_rsp *cp; struct bt_ascs_ase_rsp *rsp; if (!iov) return; cp = iov->iov_base; if (cp->num_ase == 0xff) return; switch (code) { /* If the Response_Code value is 0x01 or 0x02, Number_of_ASEs shall be * set to 0xFF. */ case BT_ASCS_RSP_NOT_SUPPORTED: case BT_ASCS_RSP_TRUNCATED: cp->num_ase = 0xff; break; default: cp->num_ase++; break; } iov->iov_len += sizeof(*rsp); iov->iov_base = realloc(iov->iov_base, iov->iov_len); rsp = iov->iov_base + (iov->iov_len - sizeof(*rsp)); rsp->ase = id; rsp->code = code; rsp->reason = reason; } static void ascs_ase_rsp_success(struct iovec *iov, uint8_t id) { return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_SUCCESS, BT_ASCS_REASON_NONE); } static void stream_notify_config(struct bt_bap_stream *stream) { struct bt_bap_endpoint *ep = stream->ep; struct bt_bap_pac *lpac = stream->lpac; struct bt_ascs_ase_status *status; struct bt_ascs_ase_status_config *config; size_t len; DBG(stream->bap, "stream %p", stream); if (!lpac) return; len = sizeof(*status) + sizeof(*config) + stream->cc->iov_len; status = malloc(len); memset(status, 0, len); status->id = ep->id; status->state = ep->state; /* Initialize preferred settings if not set */ if (!lpac->qos.phys) lpac->qos.phys = BIT(1); if (!lpac->qos.rtn) lpac->qos.rtn = 0x05; if (!lpac->qos.latency) lpac->qos.latency = 10; if (!lpac->qos.pd_min) lpac->qos.pd_min = 20000; if (!lpac->qos.pd_max) lpac->qos.pd_max = 40000; if (!lpac->qos.ppd_min) lpac->qos.ppd_min = lpac->qos.pd_min; if (!lpac->qos.ppd_max) lpac->qos.ppd_max = lpac->qos.pd_max; /* TODO:Add support for setting preferred settings on bt_bap_pac */ config = (void *)status->params; config->framing = lpac->qos.framing; config->phys = lpac->qos.phys; config->rtn = lpac->qos.rtn; config->latency = cpu_to_le16(lpac->qos.latency); put_le24(lpac->qos.pd_min, config->pd_min); put_le24(lpac->qos.pd_max, config->pd_max); put_le24(lpac->qos.ppd_min, config->ppd_min); put_le24(lpac->qos.ppd_max, config->ppd_max); config->codec = lpac->codec; if (config->codec.id == 0x0ff) { config->codec.vid = cpu_to_le16(config->codec.vid); config->codec.cid = cpu_to_le16(config->codec.cid); } config->cc_len = stream->cc->iov_len; memcpy(config->cc, stream->cc->iov_base, stream->cc->iov_len); gatt_db_attribute_notify(ep->attr, (void *) status, len, bt_bap_get_att(stream->bap)); free(status); } static void stream_notify_qos(struct bt_bap_stream *stream) { struct bt_bap_endpoint *ep = stream->ep; struct bt_ascs_ase_status *status; struct bt_ascs_ase_status_qos *qos; size_t len; DBG(stream->bap, "stream %p", stream); len = sizeof(*status) + sizeof(*qos); status = malloc(len); memset(status, 0, len); status->id = ep->id; status->state = ep->state; qos = (void *)status->params; qos->cis_id = stream->qos.ucast.cis_id; qos->cig_id = stream->qos.ucast.cig_id; put_le24(stream->qos.ucast.io_qos.interval, qos->interval); qos->framing = stream->qos.ucast.framing; qos->phys = stream->qos.ucast.io_qos.phys; qos->sdu = cpu_to_le16(stream->qos.ucast.io_qos.sdu); qos->rtn = stream->qos.ucast.io_qos.rtn; qos->latency = cpu_to_le16(stream->qos.ucast.io_qos.latency); put_le24(stream->qos.ucast.delay, qos->pd); gatt_db_attribute_notify(ep->attr, (void *) status, len, bt_bap_get_att(stream->bap)); free(status); } static void stream_notify_metadata(struct bt_bap_stream *stream, uint8_t state) { struct bt_bap_endpoint *ep = stream->ep; struct bt_ascs_ase_status *status; struct bt_ascs_ase_status_metadata *meta; size_t len; size_t meta_len = 0; DBG(stream->bap, "stream %p", stream); if (stream->meta) meta_len = stream->meta->iov_len; len = sizeof(*status) + sizeof(*meta) + meta_len; status = malloc(len); memset(status, 0, len); status->id = ep->id; status->state = state; meta = (void *)status->params; meta->cis_id = stream->qos.ucast.cis_id; meta->cig_id = stream->qos.ucast.cig_id; if (stream->meta) { meta->len = stream->meta->iov_len; memcpy(meta->data, stream->meta->iov_base, meta->len); } gatt_db_attribute_notify(ep->attr, (void *) status, len, bt_bap_get_att(stream->bap)); free(status); } static void stream_notify_release(struct bt_bap_stream *stream) { struct bt_bap_endpoint *ep = stream->ep; struct bt_ascs_ase_status status; DBG(stream->bap, "stream %p", stream); memset(&status, 0, sizeof(status)); status.id = ep->id; status.state = BT_ASCS_ASE_STATE_RELEASING; gatt_db_attribute_notify(ep->attr, (void *)&status, sizeof(status), bt_bap_get_att(stream->bap)); } static void stream_notify_idle(struct bt_bap_stream *stream) { struct bt_bap_endpoint *ep = stream->ep; struct bt_ascs_ase_status status; DBG(stream->bap, "stream %p", stream); memset(&status, 0, sizeof(status)); status.id = ep->id; status.state = BT_ASCS_ASE_STATE_IDLE; gatt_db_attribute_notify(ep->attr, (void *)&status, sizeof(status), bt_bap_get_att(stream->bap)); } static struct bt_bap *bt_bap_ref_safe(struct bt_bap *bap) { if (!bap || !bap->ref_count || !queue_find(sessions, NULL, bap)) return NULL; return bt_bap_ref(bap); } static void bap_stream_clear_cfm(struct bt_bap_stream *stream) { if (!stream->lpac || !stream->lpac->ops || !stream->lpac->ops->clear) return; stream->lpac->ops->clear(stream, stream->lpac->user_data); } static int stream_io_get_fd(struct bt_bap_stream_io *io) { if (!io) return -1; return io_get_fd(io->io); } static void stream_io_free(void *data) { struct bt_bap_stream_io *io = data; int fd; fd = stream_io_get_fd(io); DBG(io->bap, "fd %d", fd); io_destroy(io->io); free(io); /* Shutdown using SHUT_WR as SHUT_RDWR cause the socket to HUP * immediately instead of waiting for Disconnect Complete event. */ shutdown(fd, SHUT_WR); } static void stream_io_unref(struct bt_bap_stream_io *io) { if (!io) return; if (__sync_sub_and_fetch(&io->ref_count, 1)) return; stream_io_free(io); } static void bap_stream_unlink(void *data, void *user_data) { struct bt_bap_stream *stream = data; struct bt_bap_stream *link = user_data; queue_remove(stream->links, link); } static void bap_stream_free(void *data) { struct bt_bap_stream *stream = data; timeout_remove(stream->state_id); queue_destroy(stream->pending_states, NULL); if (stream->ep) stream->ep->stream = NULL; queue_foreach(stream->links, bap_stream_unlink, stream); queue_destroy(stream->links, NULL); stream_io_unref(stream->io); util_iov_free(stream->cc, 1); util_iov_free(stream->meta, 1); free(stream); } static void bap_req_free(void *data) { struct bt_bap_req *req = data; size_t i; queue_destroy(req->group, bap_req_free); for (i = 0; i < req->len; i++) free(req->iov[i].iov_base); free(req->iov); free(req); } static void bap_req_complete(struct bt_bap_req *req, const struct bt_ascs_ase_rsp *rsp) { struct queue *group; if (!req->func) goto done; if (rsp) req->func(req->stream, rsp->code, rsp->reason, req->user_data); else req->func(req->stream, BT_ASCS_RSP_UNSPECIFIED, 0x00, req->user_data); done: /* Detach from request so it can be freed separately */ group = req->group; req->group = NULL; queue_foreach(group, (queue_foreach_func_t)bap_req_complete, (void *)rsp); queue_destroy(group, NULL); bap_req_free(req); } static bool match_req_stream(const void *data, const void *match_data) { const struct bt_bap_req *req = data; return req->stream == match_data; } static void bap_req_abort(void *data) { struct bt_bap_req *req = data; struct bt_bap *bap = req->stream->bap; DBG(bap, "req %p", req); bap_req_complete(req, NULL); } static void bap_abort_stream_req(struct bt_bap *bap, struct bt_bap_stream *stream) { queue_remove_all(bap->reqs, match_req_stream, stream, bap_req_abort); if (bap->req && bap->req->stream == stream) { struct bt_bap_req *req = bap->req; bap->req = NULL; bap_req_complete(req, NULL); } } static void bt_bap_stream_unref(void *data) { struct bt_bap_stream *stream = data; if (!stream) return; if (__sync_sub_and_fetch(&stream->ref_count, 1)) return; bap_stream_free(stream); } static void bap_ucast_detach(struct bt_bap_stream *stream) { struct bt_bap_endpoint *ep = stream->ep; if (!ep) return; DBG(stream->bap, "stream %p ep %p", stream, ep); bap_abort_stream_req(stream->bap, stream); queue_remove(stream->bap->streams, stream); bap_stream_clear_cfm(stream); ep->stream = NULL; if (!stream->client) { ep->state = BT_ASCS_ASE_STATE_IDLE; ep->old_state = BT_ASCS_ASE_STATE_IDLE; } bt_bap_stream_unref(stream); } static void bap_bcast_src_detach(struct bt_bap_stream *stream) { struct bt_bap_endpoint *ep = stream->ep; if (!ep) return; DBG(stream->bap, "stream %p ep %p", stream, ep); queue_remove(stream->bap->streams, stream); bap_stream_clear_cfm(stream); stream->ep = NULL; ep->stream = NULL; bt_bap_stream_unref(stream); } static void bap_bcast_sink_detach(struct bt_bap_stream *stream) { DBG(stream->bap, "stream %p", stream); queue_remove(stream->bap->streams, stream); bap_stream_clear_cfm(stream); bt_bap_stream_unref(stream); } static bool bap_stream_io_link(const void *data, const void *user_data) { struct bt_bap_stream *stream = (void *)data; struct bt_bap_stream *link = (void *)user_data; return !bt_bap_stream_io_link(stream, link); } static void bap_stream_update_io_links(struct bt_bap_stream *stream) { struct bt_bap *bap = stream->bap; DBG(bap, "stream %p", stream); queue_find(bap->streams, bap_stream_io_link, stream); } static bool match_stream_io(const void *data, const void *user_data) { const struct bt_bap_stream *stream = data; const struct bt_bap_stream_io *io = user_data; if (!stream->io) return false; return stream->io == io; } static bool bap_stream_io_detach(struct bt_bap_stream *stream) { struct bt_bap_stream *link; struct bt_bap_stream_io *io; if (!stream->io) return false; DBG(stream->bap, "stream %p", stream); io = stream->io; stream->io = NULL; link = queue_find(stream->links, match_stream_io, io); if (link) { /* Detach link if in QoS state */ if (link->ep->state == BT_ASCS_ASE_STATE_QOS) bap_stream_io_detach(link); } stream_io_unref(io); return true; } static void stream_state_changed(void *data, void *user_data) { const struct bt_bap_state *state = data; struct bt_bap_stream *stream = user_data; if (state->func) state->func(stream, stream->old_state, stream->state, state->data); } static void bap_stream_state_changed(struct bt_bap_stream *stream) { struct bt_bap *bap = stream->bap; /* Pre notification updates */ switch (stream->ep->state) { case BT_ASCS_ASE_STATE_IDLE: break; case BT_ASCS_ASE_STATE_CONFIG: bap_stream_update_io_links(stream); break; case BT_ASCS_ASE_STATE_DISABLING: /* As client, we detach after Receiver Stop Ready */ if (!stream->client) bap_stream_io_detach(stream); break; case BT_ASCS_ASE_STATE_QOS: if (stream->io && !stream->io->connecting) bap_stream_io_detach(stream); else bap_stream_update_io_links(stream); break; case BT_ASCS_ASE_STATE_ENABLING: case BT_ASCS_ASE_STATE_STREAMING: break; } stream->old_state = stream->ep->old_state; stream->state = stream->ep->state; /* Notify callbacks using queue_foreach since it does attempt to * protect against concurrent modifications to the list. */ queue_foreach(bap->state_cbs, stream_state_changed, stream); /* Post notification updates */ switch (stream->ep->state) { case BT_ASCS_ASE_STATE_IDLE: if (bap->req && bap->req->stream == stream) { bap_req_complete(bap->req, NULL); bap->req = NULL; } if (stream->ops && stream->ops->detach) stream->ops->detach(stream); break; case BT_ASCS_ASE_STATE_QOS: break; case BT_ASCS_ASE_STATE_ENABLING: if (bt_bap_stream_get_io(stream)) bt_bap_stream_start(stream, NULL, NULL); break; case BT_ASCS_ASE_STATE_DISABLING: /* Client may terminate CIS after Receiver Stop Ready completes * successfully (BAP v1.0.2, 5.6.5.1). Do it when back to QOS. * Ensure IO is detached also if CIS was not yet established. */ if (stream->client) { bt_bap_stream_stop(stream, NULL, NULL); if (stream->io) stream->io->connecting = false; } break; case BT_ASCS_ASE_STATE_RELEASING: if (stream->client) { bap_stream_clear_cfm(stream); bap_stream_io_detach(stream); bt_bap_stream_io_unlink(stream, NULL); } break; } } /* Return false if the stream is being detached */ static bool stream_set_state(struct bt_bap_stream *stream, uint8_t state) { struct bt_bap *bap = stream->bap; /* Check if ref_count is already 0 which means detaching is in * progress. */ bap = bt_bap_ref_safe(bap); if (!bap) { if (stream->ops && stream->ops->detach) stream->ops->detach(stream); return false; } if (stream->ops && stream->ops->set_state) stream->ops->set_state(stream, state); bt_bap_unref(bap); return true; } static void ep_config_cb(struct bt_bap_stream *stream, int err) { if (err) return; stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG); } static uint8_t stream_config(struct bt_bap_stream *stream, struct iovec *cc, struct iovec *rsp) { struct bt_bap_pac *pac = stream->lpac; DBG(stream->bap, "stream %p", stream); if (!pac) { ascs_ase_rsp_add(rsp, stream->ep->id, BT_ASCS_RSP_CONF_REJECTED, BT_ASCS_REASON_CODEC); return 0; } /* TODO: Wait for pac->ops response */ ascs_ase_rsp_success(rsp, stream->ep->id); if (!util_iov_memcmp(stream->cc, cc)) { stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG); return 0; } util_iov_free(stream->cc, 1); stream->cc = util_iov_dup(cc, 1); if (pac->ops && pac->ops->config) pac->ops->config(stream, cc, NULL, ep_config_cb, pac->user_data); return 0; } static struct bt_bap_req *bap_req_new(struct bt_bap_stream *stream, uint8_t op, struct iovec *iov, size_t len, bt_bap_stream_func_t func, void *user_data) { struct bt_bap_req *req; static unsigned int id; req = new0(struct bt_bap_req, 1); req->id = ++id ? id : ++id; req->stream = stream; req->op = op; req->iov = util_iov_dup(iov, len); req->len = len; req->func = func; req->user_data = user_data; return req; } static uint16_t bap_req_len(struct bt_bap_req *req) { uint16_t len = 0; size_t i; const struct queue_entry *e; for (i = 0; i < req->len; i++) len += req->iov[i].iov_len; e = queue_get_entries(req->group); for (; e; e = e->next) len += bap_req_len(e->data); return len; } static bool match_req(const void *data, const void *match_data) { const struct bt_bap_req *pend = data; const struct bt_bap_req *req = match_data; return pend->op == req->op; } static struct bt_ascs *bap_get_ascs(struct bt_bap *bap) { if (!bap || !bap->rdb) return NULL; if (bap->rdb->ascs) return bap->rdb->ascs; bap->rdb->ascs = new0(struct bt_ascs, 1); bap->rdb->ascs->bdb = bap->rdb; return bap->rdb->ascs; } static void append_group(void *data, void *user_data) { struct bt_bap_req *req = data; struct iovec *iov = user_data; size_t i; for (i = 0; i < req->len; i++) util_iov_push_mem(iov, req->iov[i].iov_len, req->iov[i].iov_base); } static bool bap_send(struct bt_bap *bap, struct bt_bap_req *req) { struct bt_ascs *ascs = bap_get_ascs(bap); int ret; uint16_t handle; struct bt_ascs_ase_hdr hdr; struct iovec iov; size_t i; iov.iov_len = sizeof(hdr) + bap_req_len(req); DBG(bap, "req %p len %u", req, iov.iov_len); if (req->stream && !queue_find(bap->streams, NULL, req->stream)) { DBG(bap, "stream %p detached, aborting op 0x%02x", req->stream, req->op); return false; } if (!gatt_db_attribute_get_char_data(ascs->ase_cp, NULL, &handle, NULL, NULL, NULL)) { DBG(bap, "Unable to find Control Point"); return false; } iov.iov_base = alloca(iov.iov_len); iov.iov_len = 0; hdr.op = req->op; hdr.num = 1 + queue_length(req->group); util_iov_push_mem(&iov, sizeof(hdr), &hdr); for (i = 0; i < req->len; i++) util_iov_push_mem(&iov, req->iov[i].iov_len, req->iov[i].iov_base); /* Append the request group with the same opcode */ queue_foreach(req->group, append_group, &iov); ret = bt_gatt_client_write_without_response(bap->client, handle, false, iov.iov_base, iov.iov_len); if (!ret) { DBG(bap, "Unable to Write to Control Point"); return false; } bap->req = req; return true; } static bool bap_process_queue(void *data) { struct bt_bap *bap = data; struct bt_bap_req *req; DBG(bap, ""); if (bap->process_id) { timeout_remove(bap->process_id); bap->process_id = 0; } while ((req = queue_pop_head(bap->reqs))) { if (bap_send(bap, req)) break; bap_req_complete(req, NULL); } return false; } static bool bap_queue_req(struct bt_bap *bap, struct bt_bap_req *req) { struct bt_bap_req *pend; struct queue *queue; struct bt_att *att = bt_bap_get_att(bap); uint16_t mtu = bt_att_get_mtu(att); uint16_t len = 3 + 2 + bap_req_len(req); if (len > mtu) { DBG(bap, "Unable to queue request: req len %u > %u mtu", len, mtu); return false; } pend = queue_find(bap->reqs, match_req, req); /* Check if req can be grouped together and it fits in the MTU */ if (pend && (bap_req_len(pend) + len < mtu)) { if (!pend->group) pend->group = queue_new(); /* Group requests with the same opcode */ queue = pend->group; } else { queue = bap->reqs; } DBG(bap, "req %p (op 0x%2.2x) queue %p", req, req->op, queue); if (!queue_push_tail(queue, req)) { DBG(bap, "Unable to queue request"); return false; } /* Only attempot to process queue if there is no outstanding request * and it has not been scheduled. */ if (!bap->req && !bap->process_id) bap->process_id = timeout_add(BAP_PROCESS_TIMEOUT, bap_process_queue, bap, NULL); return true; } static void stream_notify(struct bt_bap_stream *stream, uint8_t state) { DBG(stream->bap, "stream %p state %d", stream, state); switch (state) { case BT_ASCS_ASE_STATE_IDLE: stream_notify_idle(stream); break; case BT_ASCS_ASE_STATE_CONFIG: stream_notify_config(stream); break; case BT_ASCS_ASE_STATE_QOS: stream_notify_qos(stream); break; case BT_ASCS_ASE_STATE_ENABLING: case BT_ASCS_ASE_STATE_STREAMING: case BT_ASCS_ASE_STATE_DISABLING: stream_notify_metadata(stream, state); break; case BT_ASCS_ASE_STATE_RELEASING: stream_notify_release(stream); break; } } static bool stream_notify_state(void *data) { struct bt_bap_stream *stream = data; struct bt_bap_endpoint *ep = stream->ep; uint8_t state; if (stream->state_id) { timeout_remove(stream->state_id); stream->state_id = 0; } /* Notify any pending states before notifying ep->state */ while ((state = PTR_TO_UINT(queue_pop_head(stream->pending_states)))) stream_notify(stream, state); stream_notify(stream, ep->state); return false; } static struct bt_bap_stream *bt_bap_stream_ref(struct bt_bap_stream *stream) { if (!stream) return NULL; __sync_fetch_and_add(&stream->ref_count, 1); return stream; } static void bap_ucast_set_state(struct bt_bap_stream *stream, uint8_t state) { struct bt_bap_endpoint *ep = stream->ep; ep->old_state = ep->state; ep->state = state; DBG(stream->bap, "stream %p dir 0x%02x: %s -> %s", stream, bt_bap_stream_get_dir(stream), bt_bap_stream_statestr(stream->ep->old_state), bt_bap_stream_statestr(stream->ep->state)); if (stream->client) goto done; if (!stream->bap->in_cp_write) stream_notify_state(stream); else if (!stream->state_id) stream->state_id = timeout_add(BAP_PROCESS_TIMEOUT, stream_notify_state, bt_bap_stream_ref(stream), bt_bap_stream_unref); else /* If a state_id is already pending then queue the old one */ queue_push_tail(stream->pending_states, UINT_TO_PTR(ep->old_state)); done: bap_stream_state_changed(stream); } static unsigned int bap_ucast_get_state(struct bt_bap_stream *stream) { return stream->ep->state; } static uint8_t bits_to_phy(uint8_t bits) { uint8_t phy = 0x00; /* Convert PHY bits to PHY values on a ascending order. */ if (bits & BIT(0)) phy = 0x01; /* LE 1M */ if (bits & BIT(1)) phy = 0x02; /* LE 2M */ if (bits & BIT(2)) phy = 0x03; /* LE Coded */ return phy; } static unsigned int bap_ucast_config(struct bt_bap_stream *stream, struct bt_bap_qos *qos, struct iovec *data, bt_bap_stream_func_t func, void *user_data) { struct iovec iov[2]; struct bt_ascs_config config; uint8_t iovlen = 1; struct bt_bap_req *req; if (!stream->client) { stream_config(stream, data, NULL); return -EINVAL; } memset(&config, 0, sizeof(config)); config.ase = stream->ep->id; config.latency = qos->ucast.target_latency; config.phy = bits_to_phy(qos->ucast.io_qos.phys); config.codec = stream->rpac->codec; if (config.codec.id == 0xff) { config.codec.cid = cpu_to_le16(config.codec.cid); config.codec.vid = cpu_to_le16(config.codec.vid); } iov[0].iov_base = &config; iov[0].iov_len = sizeof(config); if (data) { if (!bt_bap_debug_config(data->iov_base, data->iov_len, stream->bap->debug_func, stream->bap->debug_data)) return 0; config.cc_len = data->iov_len; iov[1] = *data; iovlen++; } req = bap_req_new(stream, BT_ASCS_CONFIG, iov, iovlen, func, user_data); if (!bap_queue_req(stream->bap, req)) { bap_req_free(req); return 0; } stream->qos = *qos; return req->id; } static unsigned int bap_ucast_qos(struct bt_bap_stream *stream, struct bt_bap_qos *data, bt_bap_stream_func_t func, void *user_data) { struct iovec iov; struct bt_ascs_qos qos; struct bt_bap_req *req; /* Table 3.2: ASE state machine transition * Initiating device - client Only */ if (!stream->client) return 0; if (stream->need_reconfig) return 0; memset(&qos, 0, sizeof(qos)); /* TODO: Figure out how to pass these values around */ qos.ase = stream->ep->id; qos.cig = data->ucast.cig_id; qos.cis = data->ucast.cis_id; put_le24(data->ucast.io_qos.interval, qos.interval); qos.framing = data->ucast.framing; qos.phys = data->ucast.io_qos.phys; qos.sdu = cpu_to_le16(data->ucast.io_qos.sdu); qos.rtn = data->ucast.io_qos.rtn; qos.latency = cpu_to_le16(data->ucast.io_qos.latency); put_le24(data->ucast.delay, qos.pd); iov.iov_base = &qos; iov.iov_len = sizeof(qos); req = bap_req_new(stream, BT_ASCS_QOS, &iov, 1, func, user_data); if (!bap_queue_req(stream->bap, req)) { bap_req_free(req); return 0; } stream->qos = *data; return req->id; } static void bap_stream_get_context(size_t i, uint8_t l, uint8_t t, uint8_t *v, void *user_data) { bool *found = user_data; if (!v) return; *found = true; } static unsigned int bap_stream_metadata(struct bt_bap_stream *stream, uint8_t op, struct iovec *data, bt_bap_stream_func_t func, void *user_data) { struct iovec iov[2]; struct bt_ascs_metadata meta; struct bt_bap_req *req; uint16_t value = cpu_to_le16(0x0001); /* Context = Unspecified */ memset(&meta, 0, sizeof(meta)); meta.ase = stream->ep->id; iov[0].iov_base = &meta; iov[0].iov_len = sizeof(meta); if (data) { util_iov_free(stream->meta, 1); stream->meta = util_iov_dup(data, 1); } /* Check if metadata contains an Audio Context */ if (stream->meta) { uint8_t type = 0x02; bool found = false; util_ltv_foreach(stream->meta->iov_base, stream->meta->iov_len, &type, bap_stream_get_context, &found); if (!found) util_ltv_push(stream->meta, sizeof(value), type, &value); } /* If metadata doesn't contain an Audio Context, add one */ if (!stream->meta) { stream->meta = new0(struct iovec, 1); util_ltv_push(stream->meta, sizeof(value), 0x02, &value); } iov[1].iov_base = stream->meta->iov_base; iov[1].iov_len = stream->meta->iov_len; meta.len = iov[1].iov_len; req = bap_req_new(stream, op, iov, 2, func, user_data); if (!bap_queue_req(stream->bap, req)) { bap_req_free(req); return 0; } return req->id; } static unsigned int bap_bcast_qos(struct bt_bap_stream *stream, struct bt_bap_qos *data, bt_bap_stream_func_t func, void *user_data) { stream->qos = *data; return 1; } static unsigned int bap_bcast_config(struct bt_bap_stream *stream, struct bt_bap_qos *qos, struct iovec *data, bt_bap_stream_func_t func, void *user_data) { if (!stream->lpac) return 0; stream->qos = *qos; stream->lpac->ops->config(stream, stream->cc, &stream->qos, ep_config_cb, stream->lpac->user_data); return 1; } static void bap_stream_enable_link(void *data, void *user_data) { struct bt_bap_stream *stream = data; struct iovec *metadata = user_data; bap_stream_metadata(stream, BT_ASCS_ENABLE, metadata, NULL, NULL); } static unsigned int bap_ucast_enable(struct bt_bap_stream *stream, bool enable_links, struct iovec *data, bt_bap_stream_func_t func, void *user_data) { int ret; /* Table 3.2: ASE state machine transition * Initiating device - client Only */ if (!stream->client) return 0; ret = bap_stream_metadata(stream, BT_ASCS_ENABLE, data, func, user_data); if (!ret || !enable_links) return ret; queue_foreach(stream->links, bap_stream_enable_link, data); return ret; } static uint8_t stream_start(struct bt_bap_stream *stream, struct iovec *rsp) { DBG(stream->bap, "stream %p", stream); ascs_ase_rsp_success(rsp, stream->ep->id); stream_set_state(stream, BT_BAP_STREAM_STATE_STREAMING); return 0; } static unsigned int bap_ucast_start(struct bt_bap_stream *stream, bt_bap_stream_func_t func, void *user_data) { struct iovec iov; struct bt_ascs_start start; struct bt_bap_req *req; if (!stream->client) { if (stream->ep->dir == BT_BAP_SINK) stream_start(stream, NULL); return 0; } if (stream->ep->dir == BT_BAP_SINK) return 0; memset(&start, 0, sizeof(start)); start.ase = stream->ep->id; iov.iov_base = &start; iov.iov_len = sizeof(start); req = bap_req_new(stream, BT_ASCS_START, &iov, 1, func, user_data); if (!bap_queue_req(stream->bap, req)) { bap_req_free(req); return 0; } return req->id; } static uint8_t stream_disable(struct bt_bap_stream *stream, struct iovec *rsp) { if (!stream) return 0; switch (stream->ep->state) { case BT_BAP_STREAM_STATE_ENABLING: case BT_BAP_STREAM_STATE_STREAMING: break; default: return 0; } DBG(stream->bap, "stream %p", stream); ascs_ase_rsp_success(rsp, stream->ep->id); /* Sink can autonomously transit to QOS while source needs to go to * Disabling until BT_ASCS_STOP is received. */ if (stream->ep->dir == BT_BAP_SINK) stream_set_state(stream, BT_BAP_STREAM_STATE_QOS); if (stream->ep->dir == BT_BAP_SOURCE) stream_set_state(stream, BT_BAP_STREAM_STATE_DISABLING); return 0; } static void bap_stream_disable_link(void *data, void *user_data) { struct bt_bap_stream *stream = data; bt_bap_stream_disable(stream, false, NULL, NULL); } static unsigned int bap_ucast_disable(struct bt_bap_stream *stream, bool disable_links, bt_bap_stream_func_t func, void *user_data) { struct iovec iov; struct bt_ascs_disable disable; struct bt_bap_req *req; if (!stream->client) return stream_disable(stream, NULL); memset(&disable, 0, sizeof(disable)); disable.ase = stream->ep->id; iov.iov_base = &disable; iov.iov_len = sizeof(disable); req = bap_req_new(stream, BT_ASCS_DISABLE, &iov, 1, func, user_data); if (!bap_queue_req(stream->bap, req)) { bap_req_free(req); return 0; } if (disable_links) queue_foreach(stream->links, bap_stream_disable_link, NULL); return req->id; } static uint8_t stream_stop(struct bt_bap_stream *stream, struct iovec *rsp) { if (!stream) return 0; DBG(stream->bap, "stream %p", stream); ascs_ase_rsp_success(rsp, stream->ep->id); stream_set_state(stream, BT_BAP_STREAM_STATE_QOS); return 0; } static unsigned int bap_ucast_stop(struct bt_bap_stream *stream, bt_bap_stream_func_t func, void *user_data) { struct iovec iov; struct bt_ascs_stop stop; struct bt_bap_req *req; if (!stream->client) { if (stream->ep->dir == BT_BAP_SINK) stream_stop(stream, NULL); return 0; } if (stream->ep->dir == BT_BAP_SINK) return 0; memset(&stop, 0, sizeof(stop)); stop.ase = stream->ep->id; iov.iov_base = &stop; iov.iov_len = sizeof(stop); req = bap_req_new(stream, BT_ASCS_STOP, &iov, 1, func, user_data); if (!bap_queue_req(stream->bap, req)) { bap_req_free(req); return 0; } return req->id; } static uint8_t stream_metadata(struct bt_bap_stream *stream, struct iovec *meta, struct iovec *rsp) { DBG(stream->bap, "stream %p", stream); ascs_ase_rsp_success(rsp, stream->ep->id); util_iov_free(stream->meta, 1); stream->meta = util_iov_dup(meta, 1); switch (bt_bap_stream_get_state(stream)) { case BT_BAP_STREAM_STATE_IDLE: /* Initial metadata */ break; default: /* Force state change to the same state to update metadata */ stream_set_state(stream, bt_bap_stream_get_state(stream)); } return 0; } static unsigned int bap_ucast_metadata(struct bt_bap_stream *stream, struct iovec *data, bt_bap_stream_func_t func, void *user_data) { if (!stream->client) { stream_metadata(stream, data, NULL); return 0; } switch (bt_bap_stream_get_state(stream)) { /* Valid only if ASE_State field = 0x03 (Enabling) */ case BT_BAP_STREAM_STATE_ENABLING: /* or 0x04 (Streaming) */ case BT_BAP_STREAM_STATE_STREAMING: return bap_stream_metadata(stream, BT_ASCS_METADATA, data, func, user_data); } stream_metadata(stream, data, NULL); return 0; } static uint8_t stream_release(struct bt_bap_stream *stream, struct iovec *rsp) { DBG(stream->bap, "stream %p", stream); ascs_ase_rsp_success(rsp, stream->ep->id); /* In case the stream IO is already down the released transition needs * to take action immeditely. */ if (!stream->io) { bool cache_config = !stream->no_cache_config; switch (bt_bap_stream_get_state(stream)) { case BT_BAP_STREAM_STATE_CONFIG: /* Released (no caching) */ cache_config = false; break; default: /* Released (caching) */ break; } stream_set_state(stream, BT_BAP_STREAM_STATE_RELEASING); if (cache_config) stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG); else stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE); } else stream_set_state(stream, BT_BAP_STREAM_STATE_RELEASING); return 0; } static bool bap_stream_valid(struct bt_bap_stream *stream) { if (!stream || !stream->bap) return false; return queue_find(stream->bap->streams, NULL, stream); } static unsigned int bap_ucast_get_dir(struct bt_bap_stream *stream) { return stream->ep->dir; } static unsigned int bap_ucast_get_location(struct bt_bap_stream *stream) { struct bt_pacs *pacs; if (!stream) return 0x00000000; pacs = stream->client ? stream->bap->rdb->pacs : stream->bap->ldb->pacs; if (stream->ep->dir == BT_BAP_SOURCE) return pacs->source_loc_value; else if (stream->ep->dir == BT_BAP_SINK) return pacs->sink_loc_value; return 0x00000000; } static unsigned int bap_ucast_release(struct bt_bap_stream *stream, bt_bap_stream_func_t func, void *user_data) { struct iovec iov; struct bt_ascs_release rel; struct bt_bap_req *req; struct bt_bap *bap; if (!stream->client) { stream_release(stream, NULL); return 0; } memset(&req, 0, sizeof(req)); rel.ase = stream->ep->id; iov.iov_base = &rel; iov.iov_len = sizeof(rel); bap = stream->bap; /* If stream does not belong to a client session, clean it up now */ if (!bap_stream_valid(stream)) { stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE); return 0; } req = bap_req_new(stream, BT_ASCS_RELEASE, &iov, 1, func, user_data); if (!bap_queue_req(bap, req)) { bap_req_free(req); return 0; } return req->id; } static void bap_bcast_set_state(struct bt_bap_stream *stream, uint8_t state) { struct bt_bap *bap = stream->bap; stream->old_state = stream->state; stream->state = state; bt_bap_stream_ref(stream); DBG(bap, "stream %p dir 0x%02x: %s -> %s", stream, bt_bap_stream_get_dir(stream), bt_bap_stream_statestr(stream->old_state), bt_bap_stream_statestr(stream->state)); /* Notify callbacks using queue_foreach since it does attempt to * protect against concurrent modifications to the list. */ queue_foreach(bap->state_cbs, stream_state_changed, stream); /* Post notification updates */ switch (stream->state) { case BT_ASCS_ASE_STATE_IDLE: if (stream->ops && stream->ops->detach) stream->ops->detach(stream); break; case BT_ASCS_ASE_STATE_RELEASING: bap_stream_io_detach(stream); break; case BT_ASCS_ASE_STATE_ENABLING: if (bt_bap_stream_get_io(stream)) /* Start stream if fd has already been set */ bt_bap_stream_start(stream, NULL, NULL); break; } bt_bap_stream_unref(stream); } static unsigned int bap_bcast_get_state(struct bt_bap_stream *stream) { return stream->state; } static bool bcast_sink_stream_enabled(const void *data, const void *match_data) { struct bt_bap_stream *stream = (struct bt_bap_stream *)data; struct bt_bap_stream *match = (struct bt_bap_stream *)match_data; uint8_t state = bt_bap_stream_get_state(stream); if (stream == match) return false; if (queue_find(stream->links, NULL, match)) return false; /* Ignore streams that are not Broadcast Sink */ if (bt_bap_pac_get_type(stream->lpac) != BT_BAP_BCAST_SINK) return false; return ((state == BT_BAP_STREAM_STATE_ENABLING) || bt_bap_stream_get_io(stream)); } static unsigned int bap_bcast_sink_enable(struct bt_bap_stream *stream, bool enable_links, struct iovec *data, bt_bap_stream_func_t func, void *user_data) { struct bt_bap *bap = stream->bap; /* The stream cannot be enabled if there is any other * unlinked stream for the same source that is in the * process of enabling or that has already been started. */ if (queue_find(bap->streams, bcast_sink_stream_enabled, stream)) return 0; stream_set_state(stream, BT_BAP_STREAM_STATE_ENABLING); return 1; } static unsigned int bap_bcast_src_enable(struct bt_bap_stream *stream, bool enable_links, struct iovec *data, bt_bap_stream_func_t func, void *user_data) { stream_set_state(stream, BT_BAP_STREAM_STATE_ENABLING); return 1; } static unsigned int bap_bcast_start(struct bt_bap_stream *stream, bt_bap_stream_func_t func, void *user_data) { stream_set_state(stream, BT_BAP_STREAM_STATE_STREAMING); return 1; } static unsigned int bap_bcast_disable(struct bt_bap_stream *stream, bool disable_links, bt_bap_stream_func_t func, void *user_data) { bap_stream_io_detach(stream); stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG); return 1; } static unsigned int bap_bcast_metadata(struct bt_bap_stream *stream, struct iovec *data, bt_bap_stream_func_t func, void *user_data) { util_iov_free(stream->meta, 1); stream->meta = util_iov_dup(data, 1); if (func) func(stream, BT_ASCS_RSP_SUCCESS, 0x00, user_data); return 1; } static unsigned int bap_bcast_src_get_dir(struct bt_bap_stream *stream) { return BT_BAP_BCAST_SINK; } static unsigned int bap_bcast_sink_get_dir(struct bt_bap_stream *stream) { return BT_BAP_BCAST_SOURCE; } static void bap_sink_get_allocation(size_t i, uint8_t l, uint8_t t, uint8_t *v, void *user_data) { uint32_t location32; if (!v) return; memcpy(&location32, v, l); *((uint32_t *)user_data) = le32_to_cpu(location32); } static unsigned int bap_bcast_get_location(struct bt_bap_stream *stream) { uint8_t type = BAP_CHANNEL_ALLOCATION_LTV_TYPE; uint32_t allocation = 0; struct iovec *caps; caps = bt_bap_stream_get_config(stream); /* Get stream allocation from capabilities */ util_ltv_foreach(caps->iov_base, caps->iov_len, &type, bap_sink_get_allocation, &allocation); return allocation; } static unsigned int bap_bcast_release(struct bt_bap_stream *stream, bt_bap_stream_func_t func, void *user_data) { stream_set_state(stream, BT_BAP_STREAM_STATE_RELEASING); stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE); return 1; } static bool bap_ucast_set_io(struct bt_bap_stream *stream, int fd) { if (!stream || (fd >= 0 && stream->io && !stream->io->connecting)) return false; bap_stream_set_io(stream, INT_TO_PTR(fd)); queue_foreach(stream->links, bap_stream_set_io, INT_TO_PTR(fd)); return true; } static bool bap_bcast_set_io(struct bt_bap_stream *stream, int fd) { if (!stream || (fd >= 0 && stream->io && !stream->io->connecting)) return false; bap_stream_set_io(stream, INT_TO_PTR(fd)); return true; } static struct bt_bap_stream_io *bap_ucast_get_io(struct bt_bap_stream *stream) { struct bt_bap_stream_io *io = NULL; if (!stream) return NULL; if (stream->io) return stream->io; queue_foreach(stream->links, stream_find_io, &io); return io; } static struct bt_bap_stream_io *bap_bcast_get_io(struct bt_bap_stream *stream) { if (!stream) return NULL; return stream->io; } static uint8_t bap_ucast_io_dir(struct bt_bap_stream *stream) { uint8_t dir; if (!stream) return 0x00; dir = stream->ep->dir; queue_foreach(stream->links, bap_stream_get_dir, &dir); return dir; } static uint8_t bap_bcast_io_dir(struct bt_bap_stream *stream) { uint8_t dir; uint8_t pac_type; if (!stream) return 0x00; pac_type = bt_bap_pac_get_type(stream->lpac); if (pac_type == BT_BAP_BCAST_SINK) dir = BT_BAP_BCAST_SOURCE; else dir = BT_BAP_BCAST_SINK; return dir; } static int bap_ucast_io_link(struct bt_bap_stream *stream, struct bt_bap_stream *link) { struct bt_bap *bap; if (!stream || !link || stream == link) return -EINVAL; bap = stream->bap; if (!queue_isempty(stream->links) || !queue_isempty(link->links)) return -EALREADY; if (stream->client != link->client || stream->qos.ucast.cig_id != link->qos.ucast.cig_id || stream->qos.ucast.cis_id != link->qos.ucast.cis_id || stream->ep->dir == link->ep->dir) return -EINVAL; /* The server pairs Sink and Source by matching CIG/CIS, but those are * unset until QoS Configured, so it would link unrelated ASEs (CIS 0 * is otherwise a valid ID). Clients gate linking on the lock instead. */ if (!stream->client && (stream->qos.ucast.cis_id == BT_ISO_QOS_CIS_UNSET || link->qos.ucast.cis_id == BT_ISO_QOS_CIS_UNSET)) return -EINVAL; if (stream->client && !(stream->locked && link->locked)) return -EINVAL; if (!stream->links) stream->links = queue_new(); if (!link->links) link->links = queue_new(); queue_push_tail(stream->links, link); queue_push_tail(link->links, stream); /* Link IOs if already set on stream/link */ if (stream->io && !link->io) link->io = stream_io_ref(stream->io); else if (link->io && !stream->io) stream->io = stream_io_ref(link->io); DBG(bap, "stream %p link %p", stream, link); return 0; } static void stream_unlink_ucast(void *data) { struct bt_bap_stream *link = data; DBG(link->bap, "stream %p unlink", link); queue_destroy(link->links, NULL); link->links = NULL; } static int bap_ucast_io_unlink(struct bt_bap_stream *stream, struct bt_bap_stream *link) { if (!stream) return -EINVAL; queue_destroy(stream->links, stream_unlink_ucast); stream->links = NULL; DBG(stream->bap, "stream %p unlink", stream); return 0; } static void stream_link(void *data, void *user_data) { struct bt_bap_stream *stream = (void *)data; struct bt_bap_stream *link = (void *)user_data; bt_bap_stream_io_link(stream, link); } static int bap_bcast_io_link(struct bt_bap_stream *stream, struct bt_bap_stream *link) { struct bt_bap *bap; if (!stream || !link || stream == link) return -EINVAL; bap = stream->bap; if (queue_find(stream->links, NULL, link) || queue_find(link->links, NULL, stream)) return -EALREADY; if (!stream->links) stream->links = queue_new(); if (!link->links) link->links = queue_new(); queue_push_tail(stream->links, link); queue_push_tail(link->links, stream); DBG(bap, "stream %p link %p", stream, link); queue_foreach(stream->links, stream_link, link); return 0; } static void stream_unlink(void *data, void *user_data) { struct bt_bap_stream *stream = (void *)data; struct bt_bap_stream *link = (void *)user_data; bap_bcast_io_unlink(stream, link); } static int bap_bcast_io_unlink(struct bt_bap_stream *stream, struct bt_bap_stream *link) { struct bt_bap *bap; if (!stream || !link || stream == link) return -EINVAL; bap = stream->bap; if (!queue_find(stream->links, NULL, link) || !queue_find(link->links, NULL, stream)) return -EALREADY; queue_remove(stream->links, link); queue_remove(link->links, stream); DBG(bap, "stream %p unlink %p", stream, link); queue_foreach(stream->links, stream_unlink, link); return 0; } #define STREAM_OPS(_type, _set_state, _get_state, _config, _qos, _enable, \ _start, _disable, _stop, _metadata, _get_dir, _get_loc, _release, \ _detach, _set_io, _get_io, _io_dir, _io_link, _io_unlink) \ { \ .type = _type, \ .set_state = _set_state, \ .get_state = _get_state, \ .config = _config, \ .qos = _qos, \ .enable = _enable, \ .start = _start, \ .disable = _disable, \ .stop = _stop, \ .metadata = _metadata, \ .get_dir = _get_dir,\ .get_loc = _get_loc, \ .release = _release, \ .detach = _detach, \ .set_io = _set_io, \ .get_io = _get_io, \ .io_dir = _io_dir, \ .io_link = _io_link, \ .io_unlink = _io_unlink, \ } static const struct bt_bap_stream_ops stream_ops[] = { STREAM_OPS(BT_BAP_SINK, bap_ucast_set_state, bap_ucast_get_state, bap_ucast_config, bap_ucast_qos, bap_ucast_enable, bap_ucast_start, bap_ucast_disable, bap_ucast_stop, bap_ucast_metadata, bap_ucast_get_dir, bap_ucast_get_location, bap_ucast_release, bap_ucast_detach, bap_ucast_set_io, bap_ucast_get_io, bap_ucast_io_dir, bap_ucast_io_link, bap_ucast_io_unlink), STREAM_OPS(BT_BAP_SOURCE, bap_ucast_set_state, bap_ucast_get_state, bap_ucast_config, bap_ucast_qos, bap_ucast_enable, bap_ucast_start, bap_ucast_disable, bap_ucast_stop, bap_ucast_metadata, bap_ucast_get_dir, bap_ucast_get_location, bap_ucast_release, bap_ucast_detach, bap_ucast_set_io, bap_ucast_get_io, bap_ucast_io_dir, bap_ucast_io_link, bap_ucast_io_unlink), STREAM_OPS(BT_BAP_BCAST_SINK, bap_bcast_set_state, bap_bcast_get_state, bap_bcast_config, bap_bcast_qos, bap_bcast_sink_enable, bap_bcast_start, bap_bcast_disable, NULL, bap_bcast_metadata, bap_bcast_sink_get_dir, bap_bcast_get_location, bap_bcast_release, bap_bcast_sink_detach, bap_bcast_set_io, bap_bcast_get_io, bap_bcast_io_dir, bap_bcast_io_link, bap_bcast_io_unlink), STREAM_OPS(BT_BAP_BCAST_SOURCE, bap_bcast_set_state, bap_bcast_get_state, bap_bcast_config, bap_bcast_qos, bap_bcast_src_enable, bap_bcast_start, bap_bcast_disable, NULL, bap_bcast_metadata, bap_bcast_src_get_dir, bap_bcast_get_location, bap_bcast_release, bap_bcast_src_detach, bap_bcast_set_io, bap_bcast_get_io, bap_bcast_io_dir, bap_bcast_io_link, bap_bcast_io_unlink), }; static const struct bt_bap_stream_ops * bap_stream_new_ops(struct bt_bap_stream *stream) { const struct bt_bap_stream_ops *ops; uint8_t type = bt_bap_pac_get_type(stream->lpac); size_t i; for (i = 0; i < ARRAY_SIZE(stream_ops); i++) { ops = &stream_ops[i]; if (ops->type == type) return ops; } return NULL; } static struct bt_bap_stream *bap_stream_new(struct bt_bap *bap, struct bt_bap_endpoint *ep, struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, struct iovec *data, bool client) { struct bt_bap_stream *stream; stream = new0(struct bt_bap_stream, 1); stream->bap = bap; stream->ep = ep; if (ep != NULL) ep->stream = stream; stream->lpac = lpac; stream->rpac = rpac; stream->cc = util_iov_dup(data, 1); stream->client = client; stream->ops = bap_stream_new_ops(stream); stream->pending_states = queue_new(); switch (bt_bap_pac_get_type(lpac)) { case BT_BAP_SINK: case BT_BAP_SOURCE: stream->qos.ucast.cig_id = BT_ISO_QOS_CIG_UNSET; stream->qos.ucast.cis_id = BT_ISO_QOS_CIS_UNSET; break; case BT_BAP_BCAST_SOURCE: case BT_BAP_BCAST_SINK: stream->qos.bcast.big = BT_ISO_QOS_BIG_UNSET; stream->qos.bcast.bis = BT_ISO_QOS_BIS_UNSET; break; } queue_push_tail(bap->streams, stream); return bt_bap_stream_ref(stream); } static struct bt_bap_stream_io *stream_io_ref(struct bt_bap_stream_io *io) { if (!io) return NULL; __sync_fetch_and_add(&io->ref_count, 1); return io; } static struct bt_bap_stream_io *stream_io_new(struct bt_bap *bap, int fd) { struct io *io; struct bt_bap_stream_io *sio; io = io_new(fd); if (!io) return NULL; DBG(bap, "fd %d", fd); io_set_ignore_errqueue(io, true); sio = new0(struct bt_bap_stream_io, 1); sio->bap = bap; sio->io = io; return stream_io_ref(sio); } static void stream_find_io(void *data, void *user_data) { struct bt_bap_stream *stream = data; struct bt_bap_stream_io **io = user_data; if (*io) return; *io = stream->io; } static struct bt_bap_stream_io *stream_get_io(struct bt_bap_stream *stream) { struct bt_bap_stream_io *io; struct bt_bap *bap; if (!bap_stream_valid(stream)) return NULL; if (!stream->ops || !stream->ops->get_io) return NULL; if (!bt_bap_ref_safe(stream->bap)) return NULL; bap = stream->bap; io = stream->ops->get_io(stream); bt_bap_unref(bap); return io; } static bool stream_io_disconnected(struct io *io, void *user_data); static bool bap_stream_io_attach(struct bt_bap_stream *stream, int fd, bool connecting) { struct bt_bap_stream_io *io; io = stream_get_io(stream); if (io) { if (fd == stream_io_get_fd(io)) { if (!stream->io) stream->io = stream_io_ref(io); io->connecting = connecting; return true; } DBG(stream->bap, "stream %p io already set", stream); return false; } DBG(stream->bap, "stream %p connecting %s", stream, connecting ? "true" : "false"); io = stream_io_new(stream->bap, fd); if (!io) return false; io->connecting = connecting; stream->io = io; io_set_disconnect_handler(io->io, stream_io_disconnected, stream, NULL); return true; } static void bap_stream_notify_connecting(struct bt_bap_stream *stream, bool connecting, int fd) { const struct queue_entry *entry; for (entry = queue_get_entries(stream->bap->state_cbs); entry;) { struct bt_bap_state *state = entry->data; /* Prefetch next entry since the callback may remove the current * entry. */ entry = entry->next; if (state->connecting) state->connecting(stream, connecting, fd, state->data); } } static void bap_stream_set_io(void *data, void *user_data) { struct bt_bap_stream *stream = data; int fd = PTR_TO_INT(user_data); bool ret; uint8_t state; if (fd >= 0) ret = bap_stream_io_attach(stream, fd, false); else ret = bap_stream_io_detach(stream); if (!ret) return; if (bt_bap_stream_get_type(stream) == BT_BAP_STREAM_TYPE_BCAST) state = stream->state; else state = stream->ep->state; switch (state) { case BT_BAP_STREAM_STATE_ENABLING: if (fd < 0) bt_bap_stream_disable(stream, false, NULL, NULL); else bt_bap_stream_start(stream, NULL, NULL); break; case BT_BAP_STREAM_STATE_STREAMING: if (fd < 0) stream_set_state(stream, BT_BAP_STREAM_STATE_QOS); break; case BT_BAP_STREAM_STATE_DISABLING: if (fd < 0) bt_bap_stream_stop(stream, NULL, NULL); break; } /* Notify IO connected so transports can complete pending requests. */ if (fd >= 0) bap_stream_notify_connecting(stream, false, fd); } static void ascs_ase_rsp_add_errno(struct iovec *iov, uint8_t id, int err) { struct bt_ascs_cp_rsp *rsp = iov->iov_base; switch (err) { case -ENOBUFS: case -ENOMEM: return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_NO_MEM, BT_ASCS_REASON_NONE); case -EINVAL: switch (rsp->op) { case BT_ASCS_CONFIG: /* Fallthrough */ case BT_ASCS_QOS: return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_CONF_INVALID, BT_ASCS_REASON_NONE); case BT_ASCS_ENABLE: /* Fallthrough */ case BT_ASCS_METADATA: return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_METADATA_INVALID, BT_ASCS_REASON_NONE); default: return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_UNSPECIFIED, BT_ASCS_REASON_NONE); } case -ENOTSUP: switch (rsp->op) { case BT_ASCS_CONFIG: /* Fallthrough */ case BT_ASCS_QOS: return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_CONF_UNSUPPORTED, BT_ASCS_REASON_NONE); case BT_ASCS_ENABLE: /* Fallthrough */ case BT_ASCS_METADATA: return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_METADATA_UNSUPPORTED, BT_ASCS_REASON_NONE); default: return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_NOT_SUPPORTED, BT_ASCS_REASON_NONE); } case -EBADMSG: return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE); case -ENOMSG: return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_TRUNCATED, BT_ASCS_REASON_NONE); default: return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_UNSPECIFIED, BT_ASCS_REASON_NONE); } } static uint8_t ep_config(struct bt_bap_endpoint *ep, struct bt_bap *bap, struct bt_ascs_config *req, struct iovec *iov, struct iovec *rsp) { struct iovec cc; const struct queue_entry *e; struct bt_bap_codec codec; DBG(bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir); switch (ep->state) { /* Valid only if ASE_State field = 0x00 (Idle) */ case BT_ASCS_ASE_STATE_IDLE: /* or 0x01 (Codec Configured) */ case BT_ASCS_ASE_STATE_CONFIG: /* or 0x02 (QoS Configured) */ case BT_ASCS_ASE_STATE_QOS: break; default: DBG(bap, "Invalid state %s", bt_bap_stream_statestr(ep->state)); ascs_ase_rsp_add(rsp, req->ase, BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE); return 0; } if (iov->iov_len < req->cc_len) return BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; cc.iov_base = util_iov_pull_mem(iov, req->cc_len); cc.iov_len = req->cc_len; if (!bt_bap_debug_config(cc.iov_base, cc.iov_len, bap->debug_func, bap->debug_data)) { ascs_ase_rsp_add(rsp, req->ase, BT_ASCS_RSP_CONF_INVALID, BT_ASCS_REASON_CODEC_DATA); return 0; } switch (ep->dir) { case BT_BAP_SINK: e = queue_get_entries(bap->ldb->sinks); break; case BT_BAP_SOURCE: e = queue_get_entries(bap->ldb->sources); break; default: e = NULL; } /* Convert to native endianness before comparing */ memset(&codec, 0, sizeof(codec)); codec.id = req->codec.id; codec.cid = le16_to_cpu(req->codec.cid); codec.vid = le16_to_cpu(req->codec.vid); for (; e; e = e->next) { struct bt_bap_pac *pac = e->data; if (!bap_codec_equal(&codec, &pac->codec)) continue; if (!ep->stream) ep->stream = bap_stream_new(bap, ep, pac, NULL, NULL, false); break; } if (!e) { ascs_ase_rsp_add(rsp, req->ase, BT_ASCS_RSP_CONF_INVALID, BT_ASCS_REASON_CODEC); return 0; } return stream_config(ep->stream, &cc, rsp); } static uint8_t ascs_config(struct bt_ascs *ascs, struct bt_bap *bap, struct iovec *iov, struct iovec *rsp) { struct bt_bap_endpoint *ep; struct bt_ascs_config *req; req = util_iov_pull_mem(iov, sizeof(*req)); DBG(bap, "codec 0x%02x phy 0x%02x latency %u", req->codec.id, req->phy, req->latency); ep = bap_get_local_endpoint_id(bap, req->ase); if (!ep) { DBG(bap, "Invalid ASE ID 0x%02x", req->ase); ascs_ase_rsp_add(rsp, req->ase, BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); return 0; } return ep_config(ep, bap, req, iov, rsp); } static uint8_t stream_qos(struct bt_bap_stream *stream, struct bt_bap_qos *qos, struct iovec *rsp) { DBG(stream->bap, "stream %p", stream); ascs_ase_rsp_success(rsp, stream->ep->id); if (memcmp(&stream->qos, qos, sizeof(*qos))) stream->qos = *qos; stream_set_state(stream, BT_BAP_STREAM_STATE_QOS); return 0; } static uint8_t ep_qos(struct bt_bap_endpoint *ep, struct bt_bap *bap, struct bt_bap_qos *qos, struct iovec *rsp) { DBG(bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir); switch (ep->state) { /* Valid only if ASE_State field = 0x01 (Codec Configured) */ case BT_ASCS_ASE_STATE_CONFIG: /* or 0x02 (QoS Configured) */ case BT_ASCS_ASE_STATE_QOS: break; default: DBG(bap, "Invalid state %s", bt_bap_stream_statestr(ep->state)); ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE); return 0; } if (!ep->stream) { DBG(bap, "No stream found"); ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE); return 0; } return stream_qos(ep->stream, qos, rsp); } static uint8_t ascs_qos(struct bt_ascs *ascs, struct bt_bap *bap, struct iovec *iov, struct iovec *rsp) { struct bt_bap_endpoint *ep; struct bt_ascs_qos *req; struct bt_bap_qos qos; req = util_iov_pull_mem(iov, sizeof(*req)); memset(&qos, 0, sizeof(qos)); qos.ucast.cig_id = req->cig; qos.ucast.cis_id = req->cis; qos.ucast.io_qos.interval = get_le24(req->interval); qos.ucast.framing = req->framing; qos.ucast.io_qos.phys = req->phys; qos.ucast.io_qos.sdu = le16_to_cpu(req->sdu); qos.ucast.io_qos.rtn = req->rtn; qos.ucast.io_qos.latency = le16_to_cpu(req->latency); qos.ucast.delay = get_le24(req->pd); DBG(bap, "CIG 0x%02x CIS 0x%02x interval %u framing 0x%02x " "phys 0x%02x SDU %u rtn %u latency %u pd %u", req->cig, req->cis, qos.ucast.io_qos.interval, qos.ucast.framing, qos.ucast.io_qos.phys, qos.ucast.io_qos.sdu, qos.ucast.io_qos.rtn, qos.ucast.io_qos.latency, qos.ucast.delay); ep = bap_get_local_endpoint_id(bap, req->ase); if (!ep) { DBG(bap, "Invalid ASE ID 0x%02x", req->ase); ascs_ase_rsp_add(rsp, req->ase, BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); return 0; } return ep_qos(ep, bap, &qos, rsp); } static uint8_t stream_enable(struct bt_bap_stream *stream, struct iovec *meta, struct iovec *rsp) { DBG(stream->bap, "stream %p", stream); ascs_ase_rsp_success(rsp, stream->ep->id); util_iov_free(stream->meta, 1); stream->meta = util_iov_dup(meta, 1); if (!stream_set_state(stream, BT_BAP_STREAM_STATE_ENABLING)) return 1; /* Sink can autonomously for to Streaming state if io already exits */ if (stream->io && stream->ep->dir == BT_BAP_SINK) stream_set_state(stream, BT_BAP_STREAM_STATE_STREAMING); return 0; } static bool ascs_metadata_rsp(struct bt_bap_endpoint *ep, struct iovec *meta, struct iovec *rsp) { struct bt_ltv *ltv; uint16_t supported_context = 0; uint16_t context; if (ep->stream && ep->stream->lpac) supported_context = ep->stream->lpac->qos.supported_context; ltv = meta->iov_base; if (meta->iov_len >= sizeof(*ltv) && (ltv->type < BAP_METADATA_PREF_CONTEXT_LTV_TYPE || ltv->type > BAP_METADATA_LANGUAGE_LTV_TYPE)) { ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_METADATA_UNSUPPORTED, ltv->type); return true; } if (meta->iov_len >= sizeof(*ltv) + sizeof(context) && ltv->type == BAP_METADATA_CONTEXT_LTV_TYPE && ltv->len == sizeof(context) + 1) { context = get_le16(ltv->value); if (!context || (context & ~supported_context)) { ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_METADATA_INVALID, ltv->type); return true; } } return false; } static uint8_t ep_enable(struct bt_bap_endpoint *ep, struct bt_bap *bap, struct bt_ascs_enable *req, struct iovec *iov, struct iovec *rsp) { struct iovec meta; DBG(bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir); switch (ep->state) { /* Valid only if ASE_State field = 0x02 (QoS Configured) */ case BT_ASCS_ASE_STATE_QOS: break; default: DBG(bap, "Invalid state %s", bt_bap_stream_statestr(ep->state)); ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE); return 0; } meta.iov_base = util_iov_pull_mem(iov, req->meta.len); meta.iov_len = req->meta.len; if (!bt_bap_debug_metadata(meta.iov_base, meta.iov_len, bap->debug_func, bap->debug_data)) { ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_METADATA_INVALID, BT_ASCS_REASON_NONE); return 0; } if (ascs_metadata_rsp(ep, &meta, rsp)) return 0; if (!ep->stream) { DBG(bap, "No stream found"); ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE); return 0; } return stream_enable(ep->stream, &meta, rsp); } static uint8_t ascs_enable(struct bt_ascs *ascs, struct bt_bap *bap, struct iovec *iov, struct iovec *rsp) { struct bt_bap_endpoint *ep; struct bt_ascs_enable *req; req = util_iov_pull_mem(iov, sizeof(*req)); ep = bap_get_local_endpoint_id(bap, req->meta.ase); if (!ep) { DBG(bap, "Invalid ASE ID 0x%02x", req->meta.ase); ascs_ase_rsp_add(rsp, req->meta.ase, BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); return 0; } return ep_enable(ep, bap, req, iov, rsp); } static uint8_t ep_start(struct bt_bap_endpoint *ep, struct iovec *rsp) { struct bt_bap_stream *stream = ep->stream; DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir); switch (ep->state) { /* Valid only if ASE_State field = 0x03 (Enabling) */ case BT_ASCS_ASE_STATE_ENABLING: break; default: DBG(ep->stream->bap, "Invalid state %s", bt_bap_stream_statestr(ep->state)); ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE); return 0; } /* If the ASE_ID written by the client represents a Sink ASE, the * server shall not accept the Receiver Start Ready operation for that * ASE. The server shall send a notification of the ASE Control Point * characteristic to the client, and the server shall set the * Response_Code value for that ASE to 0x05 (Invalid ASE direction). */ if (ep->dir == BT_BAP_SINK) { ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_INVALID_DIR, BT_ASCS_REASON_NONE); return 0; } return stream_start(ep->stream, rsp); } static uint8_t ascs_start(struct bt_ascs *ascs, struct bt_bap *bap, struct iovec *iov, struct iovec *rsp) { struct bt_bap_endpoint *ep; struct bt_ascs_start *req; req = util_iov_pull_mem(iov, sizeof(*req)); ep = bap_get_local_endpoint_id(bap, req->ase); if (!ep) { DBG(bap, "Invalid ASE ID 0x%02x", req->ase); ascs_ase_rsp_add(rsp, req->ase, BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); return 0; } if (!ep->stream) { DBG(bap, "No stream found for %p", ep); ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE); return 0; } return ep_start(ep, rsp); } static uint8_t ep_disable(struct bt_bap_endpoint *ep, struct iovec *rsp) { struct bt_bap_stream *stream = ep->stream; DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir); switch (ep->state) { /* Valid only if ASE_State field = 0x03 (Enabling) */ case BT_ASCS_ASE_STATE_ENABLING: /* or 0x04 (Streaming) */ case BT_ASCS_ASE_STATE_STREAMING: break; default: DBG(stream->bap, "Invalid state %s", bt_bap_stream_statestr(ep->state)); ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE); return 0; } return stream_disable(ep->stream, rsp); } static uint8_t ascs_disable(struct bt_ascs *ascs, struct bt_bap *bap, struct iovec *iov, struct iovec *rsp) { struct bt_bap_endpoint *ep; struct bt_ascs_disable *req; req = util_iov_pull_mem(iov, sizeof(*req)); ep = bap_get_local_endpoint_id(bap, req->ase); if (!ep) { DBG(bap, "Invalid ASE ID 0x%02x", req->ase); ascs_ase_rsp_add(rsp, req->ase, BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); return 0; } if (!ep->stream) { DBG(bap, "No stream found"); ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE); return 0; } return ep_disable(ep, rsp); } static uint8_t ep_stop(struct bt_bap_endpoint *ep, struct iovec *rsp) { struct bt_bap_stream *stream = ep->stream; DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir); switch (ep->state) { /* Valid only if ASE_State field = 0x05 (Disabling) */ case BT_ASCS_ASE_STATE_DISABLING: break; default: DBG(stream->bap, "Invalid state %s", bt_bap_stream_statestr(ep->state)); ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE); return 0; } /* If the ASE_ID written by the client represents a Sink ASE, the * server shall not accept the Receiver Stop Ready operation for that * ASE. The server shall send a notification of the ASE Control Point * characteristic to the client, and the server shall set the * Response_Code value for that ASE to 0x05 (Invalid ASE direction). */ if (ep->dir == BT_BAP_SINK) { ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_INVALID_DIR, BT_ASCS_REASON_NONE); return 0; } return stream_stop(ep->stream, rsp); } static uint8_t ascs_stop(struct bt_ascs *ascs, struct bt_bap *bap, struct iovec *iov, struct iovec *rsp) { struct bt_bap_endpoint *ep; struct bt_ascs_stop *req; req = util_iov_pull_mem(iov, sizeof(*req)); ep = bap_get_local_endpoint_id(bap, req->ase); if (!ep) { DBG(bap, "Invalid ASE ID 0x%02x", req->ase); ascs_ase_rsp_add(rsp, req->ase, BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); return 0; } if (!ep->stream) { DBG(bap, "No stream found"); ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE); return 0; } return ep_stop(ep, rsp); } static uint8_t ep_metadata(struct bt_bap_endpoint *ep, struct bt_ascs_metadata *req, struct iovec *iov, struct iovec *rsp) { struct bt_bap_stream *stream = ep->stream; struct iovec meta; DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir); switch (ep->state) { /* Valid only if ASE_State field = 0x03 (Enabling) */ case BT_ASCS_ASE_STATE_ENABLING: /* or 0x04 (Streaming) */ case BT_ASCS_ASE_STATE_STREAMING: break; default: DBG(stream->bap, "Invalid state %s", bt_bap_stream_statestr(ep->state)); ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE); return 0; } if (iov->iov_len < req->len) return BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; meta.iov_base = util_iov_pull_mem(iov, req->len); meta.iov_len = req->len; if (ascs_metadata_rsp(ep, &meta, rsp)) return 0; return stream_metadata(ep->stream, &meta, rsp); } static uint8_t ascs_metadata(struct bt_ascs *ascs, struct bt_bap *bap, struct iovec *iov, struct iovec *rsp) { struct bt_bap_endpoint *ep; struct bt_ascs_metadata *req; req = util_iov_pull_mem(iov, sizeof(*req)); ep = bap_get_local_endpoint_id(bap, req->ase); if (!ep) { DBG(bap, "Invalid ASE ID 0x%02x", req->ase); ascs_ase_rsp_add(rsp, req->ase, BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); return 0; } if (!ep->stream) { DBG(bap, "No stream found"); ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE); return 0; } return ep_metadata(ep, req, iov, rsp); } static uint8_t ascs_release(struct bt_ascs *ascs, struct bt_bap *bap, struct iovec *iov, struct iovec *rsp) { struct bt_bap_endpoint *ep; struct bt_ascs_release *req; req = util_iov_pull_mem(iov, sizeof(*req)); ep = bap_get_local_endpoint_id(bap, req->ase); if (!ep) { DBG(bap, "Invalid ASE ID 0x%02x", req->ase); ascs_ase_rsp_add(rsp, req->ase, BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); return 0; } if (!ep->stream) { DBG(bap, "No stream found"); ascs_ase_rsp_add(rsp, ep->id, BT_ASCS_RSP_INVALID_ASE_STATE, BT_ASCS_REASON_NONE); return 0; } return stream_release(ep->stream, rsp); } #define ASCS_OP(_str, _op, _size, _func) \ { \ .str = _str, \ .op = _op, \ .size = _size, \ .func = _func, \ } struct ascs_op_handler { const char *str; uint8_t op; size_t size; uint8_t (*func)(struct bt_ascs *ascs, struct bt_bap *bap, struct iovec *iov, struct iovec *rsp); } handlers[] = { ASCS_OP("Codec Config", BT_ASCS_CONFIG, sizeof(struct bt_ascs_config), ascs_config), ASCS_OP("QoS Config", BT_ASCS_QOS, sizeof(struct bt_ascs_qos), ascs_qos), ASCS_OP("Enable", BT_ASCS_ENABLE, sizeof(struct bt_ascs_enable), ascs_enable), ASCS_OP("Receiver Start Ready", BT_ASCS_START, sizeof(struct bt_ascs_start), ascs_start), ASCS_OP("Disable", BT_ASCS_DISABLE, sizeof(struct bt_ascs_disable), ascs_disable), ASCS_OP("Receiver Stop Ready", BT_ASCS_STOP, sizeof(struct bt_ascs_stop), ascs_stop), ASCS_OP("Update Metadata", BT_ASCS_METADATA, sizeof(struct bt_ascs_metadata), ascs_metadata), ASCS_OP("Release", BT_ASCS_RELEASE, sizeof(struct bt_ascs_release), ascs_release), {} }; static struct iovec *ascs_ase_cp_rsp_new(uint8_t op) { struct bt_ascs_cp_rsp *rsp; struct iovec *iov; iov = new0(struct iovec, 1); rsp = new0(struct bt_ascs_cp_rsp, 1); rsp->op = op; iov->iov_base = rsp; iov->iov_len = sizeof(*rsp); return iov; } static void ascs_ase_cp_rsp_add_truncated(struct iovec *rsp) { ascs_ase_rsp_add_errno(rsp, 0x00, -ENOMSG); } static bool ascs_ase_cp_rsp_invalid_len(uint8_t op, size_t len, uint8_t num) { switch (op) { case BT_ASCS_METADATA: return len == num; case BT_ASCS_RELEASE: return true; default: return false; } } static void ascs_ase_cp_write(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_ascs *ascs = user_data; struct bt_bap *bap = bt_bap_get_session(att, ascs->bdb->db); struct iovec iov = { .iov_base = (void *) value, .iov_len = len, }; struct bt_ascs_ase_hdr *hdr; struct ascs_op_handler *handler; uint8_t ret = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; struct iovec *rsp; if (offset) { DBG(bap, "invalid offset %u", offset); gatt_db_attribute_write_result(attrib, id, BT_ATT_ERROR_INVALID_OFFSET); return; } if (!len) { DBG(bap, "invalid len %u < %u sizeof(*hdr)", len, sizeof(*hdr)); rsp = ascs_ase_cp_rsp_new(len > 0 ? value[0] : 0x00); ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; goto respond; } if (len < sizeof(*hdr)) { DBG(bap, "invalid len %u < %u sizeof(*hdr)", len, sizeof(*hdr)); rsp = ascs_ase_cp_rsp_new(value[0]); ascs_ase_cp_rsp_add_truncated(rsp); ret = 0; goto respond; } hdr = util_iov_pull_mem(&iov, sizeof(*hdr)); rsp = ascs_ase_cp_rsp_new(hdr->op); if (!hdr->num) { DBG(bap, "invalid Number_of_ASEs 0"); ascs_ase_cp_rsp_add_truncated(rsp); ret = 0; goto respond; } for (handler = handlers; handler && handler->str; handler++) { if (handler->op != hdr->op) continue; if (iov.iov_len < hdr->num * handler->size) { DBG(bap, "invalid len %u < %u " "hdr->num * handler->size", len, hdr->num * handler->size); if (ascs_ase_cp_rsp_invalid_len(hdr->op, iov.iov_len, hdr->num)) { ascs_ase_cp_rsp_add_truncated(rsp); ret = 0; } else ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; goto respond; } break; } if (handler && handler->str) { int i; DBG(bap, "%s", handler->str); /* Set in_cp_write so ASE notification are not sent ahead of * CP notification. */ bap->in_cp_write = true; for (i = 0; i < hdr->num; i++) ret = handler->func(ascs, bap, &iov, rsp); bap->in_cp_write = false; } else { DBG(bap, "Unknown opcode 0x%02x", hdr->op); ascs_ase_rsp_add_errno(rsp, 0x00, -ENOTSUP); } respond: if (ret == BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN) { ascs_ase_rsp_add_errno(rsp, 0x00, -ENOMSG); ret = 0; } gatt_db_attribute_notify(attrib, rsp->iov_base, rsp->iov_len, att); gatt_db_attribute_write_result(attrib, id, ret); util_iov_free(rsp, 1); } static struct bt_ascs *ascs_new(struct gatt_db *db) { struct bt_ascs *ascs; bt_uuid_t uuid; int i; if (!db) return NULL; ascs = new0(struct bt_ascs, 1); /* Populate DB with ASCS attributes */ bt_uuid16_create(&uuid, ASCS_UUID); ascs->service = gatt_db_add_service(db, &uuid, true, 4 + (NUM_ASES * 3)); for (i = 0; i < NUM_ASES; i++) ase_new(ascs, i); bt_uuid16_create(&uuid, ASE_CP_UUID); ascs->ase_cp = gatt_db_service_add_characteristic(ascs->service, &uuid, BT_ATT_PERM_WRITE, BT_GATT_CHRC_PROP_WRITE | BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP | BT_GATT_CHRC_PROP_NOTIFY, NULL, ascs_ase_cp_write, ascs); ascs->ase_cp_ccc = gatt_db_service_add_ccc(ascs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); gatt_db_service_set_active(ascs->service, true); return ascs; } static struct bt_bap_db *bap_db_new(struct gatt_db *db) { struct bt_bap_db *bdb; if (!db) return NULL; bdb = new0(struct bt_bap_db, 1); bdb->db = gatt_db_ref(db); bdb->sinks = queue_new(); bdb->sources = queue_new(); bdb->broadcast_sources = queue_new(); bdb->broadcast_sinks = queue_new(); if (!bap_db) bap_db = queue_new(); bdb->pacs = pacs_new(db); bdb->pacs->bdb = bdb; bdb->ascs = ascs_new(db); bdb->ascs->bdb = bdb; queue_push_tail(bap_db, bdb); return bdb; } static struct bt_bap_db *bap_get_db(struct gatt_db *db) { struct bt_bap_db *bdb; bdb = queue_find(bap_db, bap_db_match, db); if (bdb) return bdb; return bap_db_new(db); } static struct bt_pacs *bap_get_pacs(struct bt_bap *bap) { if (!bap) return NULL; if (bap->rdb->pacs) return bap->rdb->pacs; bap->rdb->pacs = new0(struct bt_pacs, 1); bap->rdb->pacs->bdb = bap->rdb; return bap->rdb->pacs; } static bool match_codec(const void *data, const void *user_data) { const struct bt_bap_pac *pac = data; const struct bt_bap_codec *codec = user_data; return bap_codec_equal(&pac->codec, codec); } static struct bt_bap_pac *bap_pac_find(struct bt_bap_db *bdb, uint8_t type, struct bt_bap_codec *codec) { switch (type) { case BT_BAP_SOURCE: return queue_find(bdb->sources, match_codec, codec); case BT_BAP_SINK: return queue_find(bdb->sinks, match_codec, codec); case BT_BAP_BCAST_SOURCE: return queue_find(bdb->broadcast_sources, match_codec, codec); case BT_BAP_BCAST_SINK: return queue_find(bdb->broadcast_sinks, match_codec, codec); } return NULL; } static void *ltv_merge(struct iovec *data, struct iovec *cont) { uint8_t delimiter = 0; if (!data) return NULL; util_iov_append(data, &delimiter, sizeof(delimiter)); if (!cont || !cont->iov_len || !cont->iov_base) return data->iov_base; return util_iov_append(data, cont->iov_base, cont->iov_len); } static void bap_pac_merge(struct bt_bap_pac *pac, struct iovec *data, struct iovec *metadata) { /* Merge data into existing record */ if (pac->data) ltv_merge(pac->data, data); else pac->data = util_iov_dup(data, 1); /* Merge metadata into existing record */ if (pac->metadata) ltv_merge(pac->metadata, metadata); else pac->metadata = util_iov_dup(metadata, 1); } static struct bt_bap_pac *bap_pac_new(struct bt_bap_db *bdb, const char *name, uint8_t type, struct bt_bap_codec *codec, struct bt_bap_pac_qos *qos, struct iovec *data, struct iovec *metadata) { struct bt_bap_pac *pac; pac = new0(struct bt_bap_pac, 1); pac->bdb = bdb; pac->name = name ? strdup(name) : NULL; pac->type = type; if (codec) pac->codec = *codec; if (qos) pac->qos = *qos; bap_pac_merge(pac, data, metadata); return pac; } static void bap_pac_free(void *data) { struct bt_bap_pac *pac = data; free(pac->name); util_iov_free(pac->metadata, 1); util_iov_free(pac->data, 1); queue_destroy(pac->channels, free); free(pac); } static void pacs_sink_location_changed(struct bt_pacs *pacs) { uint32_t location = cpu_to_le32(pacs->sink_loc_value); gatt_db_attribute_notify(pacs->sink_loc, (void *)&location, sizeof(location), NULL); } static void pacs_add_sink_location(struct bt_pacs *pacs, uint32_t location) { /* Check if location value needs updating */ if (location == pacs->sink_loc_value) return; pacs->sink_loc_value |= location; pacs_sink_location_changed(pacs); } static void pacs_supported_context_changed(struct bt_pacs *pacs) { struct bt_pacs_context ctx; memset(&ctx, 0, sizeof(ctx)); ctx.snk = cpu_to_le16(pacs->supported_sink_context_value); ctx.src = cpu_to_le16(pacs->supported_source_context_value); gatt_db_attribute_notify(pacs->supported_context, (void *)&ctx, sizeof(ctx), NULL); } static void pacs_add_sink_supported_context(struct bt_pacs *pacs, uint16_t context) { context |= pacs->supported_sink_context_value; /* Check if context value needs updating */ if (context == pacs->supported_sink_context_value) return; pacs->supported_sink_context_value = context; pacs_supported_context_changed(pacs); } static void pacs_context_changed(struct bt_pacs *pacs) { struct bt_pacs_context ctx; memset(&ctx, 0, sizeof(ctx)); ctx.snk = cpu_to_le16(pacs->sink_context_value); ctx.src = cpu_to_le16(pacs->source_context_value); gatt_db_attribute_notify(pacs->context, (void *)&ctx, sizeof(ctx), NULL); } static void pacs_add_sink_context(struct bt_pacs *pacs, uint16_t context) { context |= pacs->supported_sink_context_value; /* Check if context value needs updating */ if (context == pacs->sink_context_value) return; pacs->sink_context_value = context; pacs_context_changed(pacs); } static void bap_add_sink(struct bt_bap_pac *pac) { struct iovec iov; uint8_t value[512]; queue_push_tail(pac->bdb->sinks, pac); memset(value, 0, sizeof(value)); iov.iov_base = value; iov.iov_len = 0; queue_foreach(pac->bdb->sinks, pac_foreach, &iov); pacs_add_sink_location(pac->bdb->pacs, pac->qos.location); pacs_add_sink_supported_context(pac->bdb->pacs, pac->qos.supported_context); pacs_add_sink_context(pac->bdb->pacs, pac->qos.context); gatt_db_attribute_notify(pac->bdb->pacs->sink, iov.iov_base, iov.iov_len, NULL); } static void pacs_source_location_changed(struct bt_pacs *pacs) { uint32_t location = cpu_to_le32(pacs->source_loc_value); gatt_db_attribute_notify(pacs->source_loc, (void *)&location, sizeof(location), NULL); } static void pacs_add_source_location(struct bt_pacs *pacs, uint32_t location) { location |= pacs->source_loc_value; /* Check if location value needs updating */ if (location == pacs->source_loc_value) return; pacs->source_loc_value = location; pacs_source_location_changed(pacs); } static void pacs_add_source_supported_context(struct bt_pacs *pacs, uint16_t context) { context |= pacs->supported_source_context_value; /* Check if context value needs updating */ if (context == pacs->supported_source_context_value) return; pacs->supported_source_context_value = context; pacs_supported_context_changed(pacs); } static void pacs_add_source_context(struct bt_pacs *pacs, uint16_t context) { context |= pacs->source_context_value; /* Check if context value needs updating */ if (context == pacs->source_context_value) return; pacs->source_context_value = context; pacs_context_changed(pacs); } static void bap_add_source(struct bt_bap_pac *pac) { struct iovec iov; uint8_t value[512]; queue_push_tail(pac->bdb->sources, pac); memset(value, 0, sizeof(value)); iov.iov_base = value; iov.iov_len = 0; queue_foreach(pac->bdb->sources, pac_foreach, &iov); pacs_add_source_location(pac->bdb->pacs, pac->qos.location); pacs_add_source_supported_context(pac->bdb->pacs, pac->qos.supported_context); pacs_add_source_context(pac->bdb->pacs, pac->qos.context); gatt_db_attribute_notify(pac->bdb->pacs->source, iov.iov_base, iov.iov_len, NULL); } static void bap_add_broadcast_source(struct bt_bap_pac *pac) { queue_push_tail(pac->bdb->broadcast_sources, pac); } static void bap_add_broadcast_sink(struct bt_bap_pac *pac) { queue_push_tail(pac->bdb->broadcast_sinks, pac); /* Update local PACS for broadcast sink also, when registering an * endpoint */ pacs_add_sink_location(pac->bdb->pacs, pac->qos.location); pacs_add_sink_supported_context(pac->bdb->pacs, pac->qos.supported_context); } static void notify_pac_added(void *data, void *user_data) { struct bt_bap_pac_changed *changed = data; struct bt_bap_pac *pac = user_data; if (changed->added) changed->added(pac, changed->data); } static void notify_session_pac_added(void *data, void *user_data) { struct bt_bap *bap = data; queue_foreach(bap->pac_cbs, notify_pac_added, user_data); } struct bt_bap_pac *bt_bap_add_vendor_pac_full(struct gatt_db *db, const char *name, uint8_t type, uint8_t id, uint16_t cid, uint16_t vid, struct bt_bap_pac_qos *qos, struct iovec *data, struct iovec *metadata, struct bt_bap_pac_ops *ops, void *ops_user_data) { struct bt_bap_db *bdb; struct bt_bap_pac *pac; struct bt_bap_codec codec; if (!db) return NULL; bdb = bap_get_db(db); if (!bdb) return NULL; if ((id != 0xff) && ((cid != 0U) || (vid != 0U))) return NULL; codec.id = id; codec.cid = cid; codec.vid = vid; pac = bap_pac_new(bdb, name, type, &codec, qos, data, metadata); if (ops) bt_bap_pac_set_ops(pac, ops, ops_user_data); switch (type) { case BT_BAP_SINK: bap_add_sink(pac); break; case BT_BAP_SOURCE: bap_add_source(pac); break; case BT_BAP_BCAST_SOURCE: bap_add_broadcast_source(pac); break; case BT_BAP_BCAST_SINK: bap_add_broadcast_sink(pac); break; default: bap_pac_free(pac); return NULL; } queue_foreach(sessions, notify_session_pac_added, pac); return pac; } struct bt_bap_pac *bt_bap_add_vendor_pac(struct gatt_db *db, const char *name, uint8_t type, uint8_t id, uint16_t cid, uint16_t vid, struct bt_bap_pac_qos *qos, struct iovec *data, struct iovec *metadata) { return bt_bap_add_vendor_pac_full(db, name, type, id, cid, vid, qos, data, metadata, NULL, NULL); } struct bt_bap_pac *bt_bap_add_pac(struct gatt_db *db, const char *name, uint8_t type, uint8_t id, struct bt_bap_pac_qos *qos, struct iovec *data, struct iovec *metadata) { return bt_bap_add_vendor_pac(db, name, type, id, 0x0000, 0x0000, qos, data, metadata); } uint8_t bt_bap_pac_get_type(struct bt_bap_pac *pac) { if (!pac) return 0x00; return pac->type; } uint32_t bt_bap_pac_get_locations(struct bt_bap_pac *pac) { struct bt_pacs *pacs; if (!pac) return 0; if (pac->qos.location) return pac->qos.location; pacs = pac->bdb->pacs; switch (pac->type) { case BT_BAP_SOURCE: return pacs->source_loc_value; case BT_BAP_SINK: return pacs->sink_loc_value; default: return 0; } } uint16_t bt_bap_pac_get_supported_context(struct bt_bap_pac *pac) { struct bt_pacs *pacs; if (!pac) return 0; pacs = pac->bdb->pacs; switch (pac->type) { case BT_BAP_SOURCE: return pacs->supported_source_context_value; case BT_BAP_SINK: return pacs->supported_sink_context_value; default: return 0; } } uint16_t bt_bap_pac_get_context(struct bt_bap_pac *pac) { struct bt_pacs *pacs; if (!pac) return 0; pacs = pac->bdb->pacs; switch (pac->type) { case BT_BAP_SOURCE: return pacs->source_context_value; case BT_BAP_SINK: return pacs->sink_context_value; default: return 0; } } struct bt_bap_pac_qos *bt_bap_pac_get_qos(struct bt_bap_pac *pac) { if (!pac || !pac->qos.phys) return NULL; return &pac->qos; } struct iovec *bt_bap_pac_get_data(struct bt_bap_pac *pac) { return pac->data; } struct iovec *bt_bap_pac_get_metadata(struct bt_bap_pac *pac) { return pac->metadata; } uint8_t bt_bap_stream_get_type(struct bt_bap_stream *stream) { if (!stream) return 0x00; switch (bt_bap_pac_get_type(stream->lpac)) { case BT_BAP_SINK: case BT_BAP_SOURCE: return BT_BAP_STREAM_TYPE_UCAST; case BT_BAP_BCAST_SOURCE: case BT_BAP_BCAST_SINK: return BT_BAP_STREAM_TYPE_BCAST; } return 0x00; } static void notify_pac_removed(void *data, void *user_data) { struct bt_bap_pac_changed *changed = data; struct bt_bap_pac *pac = user_data; if (changed->removed) changed->removed(pac, changed->data); } static void notify_session_pac_removed(void *data, void *user_data) { struct bt_bap *bap = data; queue_foreach(bap->pac_cbs, notify_pac_removed, user_data); } bool bt_bap_pac_set_ops(struct bt_bap_pac *pac, struct bt_bap_pac_ops *ops, void *user_data) { if (!pac) return false; pac->ops = ops; pac->user_data = user_data; return true; } static bool match_stream_lpac(const void *data, const void *user_data) { const struct bt_bap_stream *stream = data; const struct bt_bap_pac *pac = user_data; return stream->lpac == pac; } static void remove_lpac_streams(void *data, void *user_data) { struct bt_bap *bap = data; struct bt_bap_pac *pac = user_data; struct bt_bap_stream *stream; while (1) { stream = queue_remove_if(bap->streams, match_stream_lpac, pac); if (!stream) break; bt_bap_stream_ref(stream); stream->no_cache_config = true; bt_bap_stream_release(stream, NULL, NULL); stream->lpac = NULL; bt_bap_stream_unref(stream); } } static void bap_pac_sink_removed(void *data, void *user_data) { struct bt_bap_pac *pac = data; struct bt_bap_pac_qos *qos = user_data; qos->location |= pac->qos.location; qos->supported_context |= pac->qos.supported_context; qos->context |= pac->qos.context; } bool bt_bap_remove_pac(struct bt_bap_pac *pac) { if (!pac) return false; if (queue_remove_if(pac->bdb->sinks, NULL, pac)) { struct bt_pacs *pacs = pac->bdb->pacs; struct bt_bap_pac_qos qos; memset(&qos, 0, sizeof(qos)); queue_foreach(pac->bdb->sinks, bap_pac_sink_removed, &qos); if (pacs->sink_loc_value != qos.location) { pacs->sink_loc_value = qos.location; pacs_sink_location_changed(pacs); } if (pacs->supported_sink_context_value != qos.supported_context) { pacs->supported_sink_context_value = qos.supported_context; pacs_supported_context_changed(pacs); } if (pacs->sink_context_value != qos.context) { pacs->sink_context_value = qos.context; pacs_context_changed(pacs); } goto found; } if (queue_remove_if(pac->bdb->sources, NULL, pac)) goto found; if (queue_remove_if(pac->bdb->broadcast_sources, NULL, pac)) goto found; return false; found: queue_foreach(sessions, remove_lpac_streams, pac); queue_foreach(sessions, notify_session_pac_removed, pac); bap_pac_free(pac); return true; } static void bap_db_free(void *data) { struct bt_bap_db *bdb = data; if (!bdb) return; queue_destroy(bdb->sinks, bap_pac_free); queue_destroy(bdb->sources, bap_pac_free); gatt_db_unref(bdb->db); free(bdb->pacs); free(bdb->ascs); free(bdb); } static void bap_ready_free(void *data) { struct bt_bap_ready *ready = data; if (ready->destroy) ready->destroy(ready->data); free(ready); } static void bap_state_free(void *data) { struct bt_bap_state *state = data; if (state->destroy) state->destroy(state->data); free(state); } static void bap_bis_cb_free(void *data) { struct bt_bap_bis_cb *bis_cb = data; if (bis_cb->destroy) bis_cb->destroy(bis_cb->data); free(bis_cb); } static void bap_bcode_cb_free(void *data) { struct bt_bap_bcode_cb *cb = data; if (cb->destroy) cb->destroy(cb->data); free(cb); } static void bap_ep_free(void *data) { struct bt_bap_endpoint *ep = data; if (ep && ep->stream) ep->stream->ep = NULL; free(ep); } static void bap_detached(void *data, void *user_data) { struct bt_bap_cb *cb = data; struct bt_bap *bap = user_data; if (!cb->detached) return; cb->detached(bap, cb->user_data); } static void bap_stream_unref(void *data) { struct bt_bap_stream *stream = data; bt_bap_stream_unref(stream); } static void bap_free(void *data) { struct bt_bap *bap = data; timeout_remove(bap->process_id); bt_bap_detach(bap); bap_db_free(bap->rdb); queue_destroy(bap->pac_cbs, pac_changed_free); queue_destroy(bap->ready_cbs, bap_ready_free); queue_destroy(bap->state_cbs, bap_state_free); queue_destroy(bap->bis_cbs, bap_bis_cb_free); queue_destroy(bap->bcode_cbs, bap_bcode_cb_free); queue_destroy(bap->local_eps, free); queue_destroy(bap->remote_eps, bap_ep_free); queue_destroy(bap->reqs, bap_req_free); queue_destroy(bap->notify, NULL); queue_destroy(bap->streams, bap_stream_unref); free(bap); } unsigned int bt_bap_register(bt_bap_func_t attached, bt_bap_func_t detached, void *user_data) { struct bt_bap_cb *cb; static unsigned int id; if (!attached && !detached) return 0; if (!bap_cbs) bap_cbs = queue_new(); cb = new0(struct bt_bap_cb, 1); cb->id = ++id ? id : ++id; cb->attached = attached; cb->detached = detached; cb->user_data = user_data; queue_push_tail(bap_cbs, cb); return cb->id; } static bool match_id(const void *data, const void *match_data) { const struct bt_bap_cb *cb = data; unsigned int id = PTR_TO_UINT(match_data); return (cb->id == id); } bool bt_bap_unregister(unsigned int id) { struct bt_bap_cb *cb; cb = queue_remove_if(bap_cbs, match_id, UINT_TO_PTR(id)); if (!cb) return false; free(cb); return true; } static void bap_attached(void *data, void *user_data) { struct bt_bap_cb *cb = data; struct bt_bap *bap = user_data; if (!cb->attached) return; cb->attached(bap, cb->user_data); } struct bt_bap *bt_bap_new(struct gatt_db *ldb, struct gatt_db *rdb) { struct bt_bap *bap; struct bt_bap_db *bdb; if (!ldb) return NULL; bdb = bap_get_db(ldb); if (!bdb) return NULL; bap = new0(struct bt_bap, 1); bap->ldb = bdb; bap->reqs = queue_new(); bap->notify = queue_new(); bap->pac_cbs = queue_new(); bap->ready_cbs = queue_new(); bap->streams = queue_new(); bap->state_cbs = queue_new(); bap->bis_cbs = queue_new(); bap->bcode_cbs = queue_new(); bap->local_eps = queue_new(); if (!rdb) goto done; bdb = new0(struct bt_bap_db, 1); bdb->db = gatt_db_ref(rdb); bdb->sinks = queue_new(); bdb->sources = queue_new(); bap->rdb = bdb; bap->remote_eps = queue_new(); done: return bt_bap_ref(bap); } bool bt_bap_set_user_data(struct bt_bap *bap, void *user_data) { if (!bap) return false; bap->user_data = user_data; return true; } void *bt_bap_get_user_data(struct bt_bap *bap) { if (!bap) return NULL; return bap->user_data; } struct bt_att *bt_bap_get_att(struct bt_bap *bap) { if (!bap) return NULL; if (bap->att) return bap->att; return bt_gatt_client_get_att(bap->client); } struct gatt_db *bt_bap_get_db(struct bt_bap *bap, bool remote) { if (!bap) return NULL; if (remote) return bap->rdb ? bap->rdb->db : NULL; return bap->ldb ? bap->ldb->db : NULL; } struct bt_bap *bt_bap_ref(struct bt_bap *bap) { if (!bap) return NULL; __sync_fetch_and_add(&bap->ref_count, 1); return bap; } void bt_bap_unref(struct bt_bap *bap) { if (!bap) return; if (__sync_sub_and_fetch(&bap->ref_count, 1)) return; bap_free(bap); } static void bap_notify_ready(struct bt_bap *bap) { const struct queue_entry *entry; if (!bt_bap_ref_safe(bap)) return; for (entry = queue_get_entries(bap->ready_cbs); entry; entry = entry->next) { struct bt_bap_ready *ready = entry->data; ready->func(bap, ready->data); } bt_bap_unref(bap); } static void bap_parse_pacs(struct bt_bap *bap, uint8_t type, struct queue *queue, const uint8_t *value, uint16_t len) { struct bt_pacs_read_rsp *rsp; struct iovec iov = { .iov_base = (void *) value, .iov_len = len, }; int i; rsp = util_iov_pull_mem(&iov, sizeof(*rsp)); if (!rsp) { DBG(bap, "Unable to parse PAC"); return; } DBG(bap, "PAC(s) %u", rsp->num_pac); for (i = 0; i < rsp->num_pac; i++) { struct bt_bap_pac *pac; struct bt_pac *p; struct bt_ltv *cc, *m; struct bt_pac_metadata *meta; struct iovec data, metadata; p = util_iov_pull_mem(&iov, sizeof(*p)); if (!p) { DBG(bap, "Unable to parse PAC"); return; } if (p->codec.id == 0xff) { p->codec.cid = le16_to_cpu(p->codec.cid); p->codec.vid = le16_to_cpu(p->codec.vid); } pac = NULL; cc = util_iov_pull_mem(&iov, p->cc_len); if (!cc) { DBG(bap, "Unable to parse PAC codec capabilities"); return; } if (!bt_bap_debug_caps(cc, p->cc_len, bap->debug_func, bap->debug_data)) { DBG(bap, "Invalid PAC codec capabilities LTV"); return; } meta = util_iov_pull_mem(&iov, sizeof(*meta)); if (!meta) { DBG(bap, "Unable to parse PAC metadata"); return; } m = util_iov_pull_mem(&iov, meta->len); if (!m) { DBG(bap, "Unable to parse PAC metadata"); return; } if (!bt_bap_debug_metadata(meta->data, meta->len, bap->debug_func, bap->debug_data)) { DBG(bap, "Invalid PAC metadata LTV"); return; } data.iov_len = p->cc_len; data.iov_base = cc; metadata.iov_len = meta->len; metadata.iov_base = m; DBG(bap, "PAC #%u: type %u codec 0x%02x cc_len %u meta_len %u", i, type, p->codec.id, p->cc_len, meta->len); /* Check if there is already a PAC record for the codec */ pac = bap_pac_find(bap->rdb, type, &p->codec); if (pac) { bap_pac_merge(pac, &data, &metadata); continue; } pac = bap_pac_new(bap->rdb, NULL, type, &p->codec, NULL, &data, &metadata); if (!pac) continue; queue_push_tail(queue, pac); } } static void read_source_pac(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_bap *bap = user_data; if (!success) { DBG(bap, "Unable to read Source PAC: error 0x%02x", att_ecode); return; } bap_parse_pacs(bap, BT_BAP_SOURCE, bap->rdb->sources, value, length); } static void read_sink_pac(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_bap *bap = user_data; if (!success) { DBG(bap, "Unable to read Sink PAC: error 0x%02x", att_ecode); return; } bap_parse_pacs(bap, BT_BAP_SINK, bap->rdb->sinks, value, length); } static void read_source_pac_loc(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_bap *bap = user_data; struct bt_pacs *pacs = bap_get_pacs(bap); if (!success) { DBG(bap, "Unable to read Source PAC Location: error 0x%02x", att_ecode); return; } if (length != sizeof(uint32_t)) { DBG(bap, "Invalid Source PAC Location size: %d", length); return; } pacs->source_loc_value = get_le32(value); DBG(bap, "PACS Source Locations: 0x%08x", pacs->source_loc_value); /* Resume reading sinks if supported but for some reason is empty */ if (pacs->source && queue_isempty(bap->rdb->sources)) { uint16_t value_handle; if (gatt_db_attribute_get_char_data(pacs->source, NULL, &value_handle, NULL, NULL, NULL)) bt_gatt_client_read_value(bap->client, value_handle, read_source_pac, bap, NULL); } } static void read_sink_pac_loc(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_bap *bap = user_data; struct bt_pacs *pacs = bap_get_pacs(bap); if (!success) { DBG(bap, "Unable to read Sink PAC Location: error 0x%02x", att_ecode); return; } if (length != sizeof(uint32_t)) { DBG(bap, "Invalid Sink PAC Location size: %d", length); return; } pacs->sink_loc_value = get_le32(value); DBG(bap, "PACS Sink Locations: 0x%08x", pacs->sink_loc_value); /* Resume reading sinks if supported but for some reason is empty */ if (pacs->sink && queue_isempty(bap->rdb->sinks)) { uint16_t value_handle; if (gatt_db_attribute_get_char_data(pacs->sink, NULL, &value_handle, NULL, NULL, NULL)) bt_gatt_client_read_value(bap->client, value_handle, read_sink_pac, bap, NULL); } } static void read_pac_context(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_bap *bap = user_data; struct bt_pacs *pacs = bap_get_pacs(bap); const struct bt_pacs_context *ctx = (void *)value; if (!success) { DBG(bap, "Unable to read PAC Context: error 0x%02x", att_ecode); return; } if (length != sizeof(*ctx)) { DBG(bap, "Invalid PAC Context size: %d", length); return; } pacs->sink_context_value = le16_to_cpu(ctx->snk); pacs->source_context_value = le16_to_cpu(ctx->src); DBG(bap, "PACS Sink Context: 0x%04x", pacs->sink_context_value); DBG(bap, "PACS Source Context: 0x%04x", pacs->source_context_value); } static void read_pac_supported_context(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_bap *bap = user_data; struct bt_pacs *pacs = bap_get_pacs(bap); const struct bt_pacs_context *ctx = (void *)value; if (!success) { DBG(bap, "Unable to read PAC Supported Context: error 0x%02x", att_ecode); return; } if (length != sizeof(*ctx)) { DBG(bap, "Invalid PAC Supported Context size: %d", length); return; } pacs->supported_sink_context_value = le16_to_cpu(ctx->snk); pacs->supported_source_context_value = le16_to_cpu(ctx->src); DBG(bap, "PACS Supported Sink Context: 0x%04x", pacs->supported_sink_context_value); DBG(bap, "PACS Supported Source Context: 0x%04x", pacs->supported_source_context_value); } static void foreach_pacs_char(struct gatt_db_attribute *attr, void *user_data) { struct bt_bap *bap = user_data; uint16_t value_handle; bt_uuid_t uuid, uuid_sink, uuid_source, uuid_sink_loc, uuid_source_loc; bt_uuid_t uuid_context, uuid_supported_context; struct bt_pacs *pacs; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) return; bt_uuid16_create(&uuid_sink, PAC_SINK_CHRC_UUID); bt_uuid16_create(&uuid_source, PAC_SOURCE_CHRC_UUID); bt_uuid16_create(&uuid_sink_loc, PAC_SINK_LOC_CHRC_UUID); bt_uuid16_create(&uuid_source_loc, PAC_SOURCE_LOC_CHRC_UUID); bt_uuid16_create(&uuid_context, PAC_CONTEXT); bt_uuid16_create(&uuid_supported_context, PAC_SUPPORTED_CONTEXT); if (!bt_uuid_cmp(&uuid, &uuid_sink)) { DBG(bap, "Sink PAC found: handle 0x%04x", value_handle); pacs = bap_get_pacs(bap); if (!pacs) return; if (!pacs->sink) pacs->sink = attr; bt_gatt_client_read_value(bap->client, value_handle, read_sink_pac, bap, NULL); } if (!bt_uuid_cmp(&uuid, &uuid_source)) { DBG(bap, "Source PAC found: handle 0x%04x", value_handle); pacs = bap_get_pacs(bap); if (!pacs) return; if (!pacs->source) pacs->source = attr; bt_gatt_client_read_value(bap->client, value_handle, read_source_pac, bap, NULL); } if (!bt_uuid_cmp(&uuid, &uuid_sink_loc)) { DBG(bap, "Sink PAC Location found: handle 0x%04x", value_handle); pacs = bap_get_pacs(bap); if (!pacs || pacs->sink_loc) return; pacs->sink_loc = attr; bt_gatt_client_read_value(bap->client, value_handle, read_sink_pac_loc, bap, NULL); } if (!bt_uuid_cmp(&uuid, &uuid_source_loc)) { DBG(bap, "Source PAC Location found: handle 0x%04x", value_handle); pacs = bap_get_pacs(bap); if (!pacs || pacs->source_loc) return; pacs->source_loc = attr; bt_gatt_client_read_value(bap->client, value_handle, read_source_pac_loc, bap, NULL); } if (!bt_uuid_cmp(&uuid, &uuid_context)) { DBG(bap, "PAC Context found: handle 0x%04x", value_handle); pacs = bap_get_pacs(bap); if (!pacs || pacs->context) return; pacs->context = attr; bt_gatt_client_read_value(bap->client, value_handle, read_pac_context, bap, NULL); } if (!bt_uuid_cmp(&uuid, &uuid_supported_context)) { DBG(bap, "PAC Supported Context found: handle 0x%04x", value_handle); pacs = bap_get_pacs(bap); if (!pacs || pacs->supported_context) return; pacs->supported_context = attr; bt_gatt_client_read_value(bap->client, value_handle, read_pac_supported_context, bap, NULL); } } static void foreach_pacs_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_bap *bap = user_data; struct bt_pacs *pacs = bap_get_pacs(bap); pacs->service = attr; gatt_db_service_foreach_char(attr, foreach_pacs_char, bap); } struct match_pac { struct bt_bap_codec codec; struct bt_bap_pac *lpac; struct bt_bap_pac *rpac; struct bt_bap_endpoint *ep; }; static bool match_stream_pac(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, void *user_data) { struct match_pac *match = user_data; if (!bap_codec_equal(&match->codec, &lpac->codec)) return true; match->lpac = lpac; match->rpac = rpac; return false; } static void ep_status_config(struct bt_bap *bap, struct bt_bap_endpoint *ep, struct iovec *iov) { struct bt_ascs_ase_status_config *cfg; struct bt_ltv *cc; uint32_t pd_min, pd_max, ppd_min, ppd_max; int i; cfg = util_iov_pull_mem(iov, sizeof(*cfg)); if (!cfg) { DBG(bap, "Unable to parse Config Status"); return; } pd_min = get_le24(cfg->pd_min); pd_max = get_le24(cfg->pd_max); ppd_min = get_le24(cfg->ppd_min); ppd_max = get_le24(cfg->ppd_max); DBG(bap, "codec 0x%02x framing 0x%02x.phys 0x%02x rtn %u " "latency %u pd %u - %u ppd %u - %u", cfg->codec.id, cfg->framing, cfg->phys, cfg->rtn, le16_to_cpu(cfg->latency), pd_min, pd_max, ppd_min, ppd_max); if (iov->iov_len < cfg->cc_len) { DBG(bap, "Unable to parse Config Status: len %zu < %u cc_len", iov->iov_len, cfg->cc_len); return; } for (i = 0; iov->iov_len >= sizeof(*cc); i++) { cc = util_iov_pull_mem(iov, sizeof(*cc)); if (!cc) break; DBG(bap, "Codec Config #%u: type 0x%02x len %u", i, cc->type, cc->len); util_iov_pull_mem(iov, cc->len - 1); } /* Any previously applied codec configuration may be cached by the * server. */ if (!ep->stream) { struct match_pac match; match.lpac = NULL; match.rpac = NULL; match.codec.id = cfg->codec.id; match.codec.cid = le16_to_cpu(cfg->codec.cid); match.codec.vid = le16_to_cpu(cfg->codec.vid); bt_bap_foreach_pac(bap, ep->dir, match_stream_pac, &match); if (!match.lpac || !match.rpac) return; bap_stream_new(bap, ep, match.lpac, match.rpac, NULL, true); } if (!ep->stream) return; /* Set preferred settings */ if (ep->stream->rpac) { ep->stream->rpac->qos.framing = cfg->framing; ep->stream->rpac->qos.phys = cfg->phys; ep->stream->rpac->qos.rtn = cfg->rtn; ep->stream->rpac->qos.latency = le16_to_cpu(cfg->latency); ep->stream->rpac->qos.pd_min = pd_min; ep->stream->rpac->qos.pd_max = pd_max; ep->stream->rpac->qos.ppd_min = ppd_min; ep->stream->rpac->qos.ppd_max = ppd_max; } if (!ep->stream->cc) ep->stream->cc = new0(struct iovec, 1); util_iov_memcpy(ep->stream->cc, cfg->cc, cfg->cc_len); ep->stream->need_reconfig = false; } static void bap_stream_config_cfm_cb(struct bt_bap_stream *stream, int err) { struct bt_bap *bap = stream->bap; if (err) { DBG(bap, "Config Confirmation failed: %d", err); bt_bap_stream_release(stream, NULL, NULL); return; } } static void bap_stream_config_cfm(struct bt_bap_stream *stream) { int err; if (!stream->lpac || !stream->lpac->ops || !stream->lpac->ops->config) return; err = stream->lpac->ops->config(stream, stream->cc, &stream->qos, bap_stream_config_cfm_cb, stream->lpac->user_data); if (err < 0) { DBG(stream->bap, "Unable to send Config Confirmation: %d", err); bt_bap_stream_release(stream, NULL, NULL); } } static void ep_status_qos(struct bt_bap *bap, struct bt_bap_endpoint *ep, struct iovec *iov) { struct bt_ascs_ase_status_qos *qos; uint32_t interval; uint32_t pd; uint16_t sdu; uint16_t latency; qos = util_iov_pull_mem(iov, sizeof(*qos)); if (!qos) { DBG(bap, "Unable to parse QoS Status"); return; } interval = get_le24(qos->interval); pd = get_le24(qos->pd); sdu = le16_to_cpu(qos->sdu); latency = le16_to_cpu(qos->latency); DBG(bap, "CIG 0x%02x CIS 0x%02x interval %u framing 0x%02x " "phys 0x%02x rtn %u latency %u pd %u", qos->cig_id, qos->cis_id, interval, qos->framing, qos->phys, qos->rtn, latency, pd); if (!ep->stream) return; ep->stream->qos.ucast.io_qos.interval = interval; ep->stream->qos.ucast.framing = qos->framing; ep->stream->qos.ucast.io_qos.phys = qos->phys; ep->stream->qos.ucast.io_qos.sdu = sdu; ep->stream->qos.ucast.io_qos.rtn = qos->rtn; ep->stream->qos.ucast.io_qos.latency = latency; ep->stream->qos.ucast.delay = pd; if (ep->old_state == BT_ASCS_ASE_STATE_CONFIG) bap_stream_config_cfm(ep->stream); } static void ep_status_metadata(struct bt_bap *bap, struct bt_bap_endpoint *ep, struct iovec *iov) { struct bt_ascs_ase_status_metadata *meta; meta = util_iov_pull_mem(iov, sizeof(*meta)); if (!meta) { DBG(bap, "Unable to parse Metadata Status"); return; } DBG(bap, "CIS 0x%02x CIG 0x%02x metadata len %u", meta->cis_id, meta->cig_id, meta->len); } static void bap_ep_set_status(struct bt_bap *bap, struct bt_bap_endpoint *ep, const uint8_t *value, uint16_t length) { struct bt_ascs_ase_status *rsp; struct iovec iov = { .iov_base = (void *) value, .iov_len = length, }; rsp = util_iov_pull_mem(&iov, sizeof(*rsp)); if (!rsp) return; ep->id = rsp->id; ep->old_state = ep->state; ep->state = rsp->state; DBG(bap, "ASE status: ep %p id 0x%02x handle 0x%04x state %s " "len %zu", ep, ep->id, gatt_db_attribute_get_handle(ep->attr), bt_bap_stream_statestr(ep->state), iov.iov_len); switch (ep->state) { case BT_ASCS_ASE_STATE_IDLE: break; case BT_ASCS_ASE_STATE_CONFIG: ep_status_config(bap, ep, &iov); break; case BT_ASCS_ASE_STATE_QOS: ep_status_qos(bap, ep, &iov); break; case BT_ASCS_ASE_STATE_ENABLING: case BT_ASCS_ASE_STATE_STREAMING: case BT_ASCS_ASE_STATE_DISABLING: ep_status_metadata(bap, ep, &iov); break; case BT_ASCS_ASE_STATE_RELEASING: break; } /* Only notifify if there is a stream */ if (!ep->stream) return; bap_stream_state_changed(ep->stream); } static void read_ase_status(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_bap_endpoint *ep = user_data; struct bt_bap *bap = ep->bap; if (!success) { DBG(bap, "ASE read status failed: 0x%04x", att_ecode); return; } bap_ep_set_status(bap, ep, value, length); } static void bap_register(uint16_t att_ecode, void *user_data) { struct bt_bap_notify *notify = user_data; if (att_ecode) DBG(notify->bap, "ASE register failed: 0x%04x", att_ecode); } static void bap_endpoint_notify(struct bt_bap *bap, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct bt_bap_endpoint *ep = user_data; bap_ep_set_status(bap, ep, value, length); } static void bap_notify(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct bt_bap_notify *notify = user_data; if (notify->func) notify->func(notify->bap, value_handle, value, length, notify->user_data); } static void bap_notify_destroy(void *data) { struct bt_bap_notify *notify = data; struct bt_bap *bap = notify->bap; if (queue_remove_if(bap->notify, NULL, notify)) free(notify); } static unsigned int bap_register_notify(struct bt_bap *bap, uint16_t value_handle, bap_notify_t func, void *user_data) { struct bt_bap_notify *notify; notify = new0(struct bt_bap_notify, 1); notify->bap = bap; notify->func = func; notify->user_data = user_data; notify->id = bt_gatt_client_register_notify(bap->client, value_handle, bap_register, bap_notify, notify, bap_notify_destroy); if (!notify->id) { DBG(bap, "Unable to register for notifications"); free(notify); return 0; } queue_push_tail(bap->notify, notify); return notify->id; } static void bap_endpoint_attach(struct bt_bap *bap, struct bt_bap_endpoint *ep) { uint16_t value_handle; if (!gatt_db_attribute_get_char_data(ep->attr, NULL, &value_handle, NULL, NULL, NULL)) return; DBG(bap, "ASE handle 0x%04x", value_handle); ep->bap = bap; bt_gatt_client_read_value(bap->client, value_handle, read_ase_status, ep, NULL); ep->state_id = bap_register_notify(bap, value_handle, bap_endpoint_notify, ep); } static void bap_cp_notify(struct bt_bap *bap, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { const struct bt_ascs_cp_rsp *rsp = (void *)value; const struct bt_ascs_ase_rsp *ase_rsp = NULL; struct bt_bap_req *req; int i; if (!bap->req) return; req = bap->req; bap->req = NULL; if (length < sizeof(*rsp)) { DBG(bap, "Invalid ASE CP notification: length %u < %zu", length, sizeof(*rsp)); goto done; } if (rsp->op != req->op) { DBG(bap, "Invalid ASE CP notification: op 0x%02x != 0x%02x", rsp->op, req->op); goto done; } length -= sizeof(*rsp); if (rsp->num_ase == 0xff) { ase_rsp = rsp->rsp; goto done; } for (i = 0; i < rsp->num_ase; i++) { if (length < sizeof(*ase_rsp)) { DBG(bap, "Invalid ASE CP notification: length %u < %zu", length, sizeof(*ase_rsp)); goto done; } ase_rsp = &rsp->rsp[i]; } done: bap_req_complete(req, ase_rsp); bap_process_queue(bap); } static void bap_cp_attach(struct bt_bap *bap) { uint16_t value_handle; struct bt_ascs *ascs = bap_get_ascs(bap); if (!gatt_db_attribute_get_char_data(ascs->ase_cp, NULL, &value_handle, NULL, NULL, NULL)) return; DBG(bap, "ASE CP handle 0x%04x", value_handle); bap->cp_id = bap_register_notify(bap, value_handle, bap_cp_notify, NULL); } static void foreach_ascs_char(struct gatt_db_attribute *attr, void *user_data) { struct bt_bap *bap = user_data; uint16_t value_handle; bt_uuid_t uuid, uuid_sink, uuid_source, uuid_cp; struct bt_ascs *ascs; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) return; bt_uuid16_create(&uuid_sink, ASE_SINK_UUID); bt_uuid16_create(&uuid_source, ASE_SOURCE_UUID); bt_uuid16_create(&uuid_cp, ASE_CP_UUID); if (!bt_uuid_cmp(&uuid, &uuid_sink) || !bt_uuid_cmp(&uuid, &uuid_source)) { struct bt_bap_endpoint *ep; ep = bap_get_endpoint(bap->remote_eps, bap->rdb, attr); if (!ep) return; if (!bt_uuid_cmp(&uuid, &uuid_sink)) DBG(bap, "ASE Sink found: handle 0x%04x", value_handle); else DBG(bap, "ASE Source found: handle 0x%04x", value_handle); bap_endpoint_attach(bap, ep); return; } if (!bt_uuid_cmp(&uuid, &uuid_cp)) { ascs = bap_get_ascs(bap); if (!ascs || ascs->ase_cp) return; ascs->ase_cp = attr; DBG(bap, "ASE Control Point found: handle 0x%04x", value_handle); bap_cp_attach(bap); } } static void foreach_ascs_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_bap *bap = user_data; struct bt_ascs *ascs = bap_get_ascs(bap); ascs->service = attr; gatt_db_service_set_claimed(attr, true); gatt_db_service_foreach_char(attr, foreach_ascs_char, bap); } static void bap_endpoint_foreach(void *data, void *user_data) { struct bt_bap_endpoint *ep = data; struct bt_bap *bap = user_data; bap_endpoint_attach(bap, ep); } static void bap_attach_att(struct bt_bap *bap, struct bt_att *att) { if (bap->disconn_id) { if (att == bt_bap_get_att(bap)) return; bt_att_unregister_disconnect(bap->att, bap->disconn_id); } bap->disconn_id = bt_att_register_disconnect(bap->att, bap_disconnected, bap, NULL); } static void bap_idle(void *data) { struct bt_bap *bap = data; bap->idle_id = 0; bap_notify_ready(bap); } bool bt_bap_attach(struct bt_bap *bap, struct bt_gatt_client *client) { bt_uuid_t uuid; if (queue_find(sessions, NULL, bap)) { /* If instance already been set but there is no client proceed * to clone it otherwise considered it already attached. */ if (client && !bap->client) goto clone; return true; } if (!sessions) sessions = queue_new(); queue_push_tail(sessions, bap); queue_foreach(bap_cbs, bap_attached, bap); if (!client) { if (bap->att) bap_attach_att(bap, bap->att); return true; } if (bap->client) return false; clone: bap->client = bt_gatt_client_clone(client); if (!bap->client) return false; bap_attach_att(bap, bt_gatt_client_get_att(client)); bap->idle_id = bt_gatt_client_idle_register(bap->client, bap_idle, bap, NULL); if (bap->rdb->pacs) { uint16_t value_handle; struct bt_pacs *pacs = bap->rdb->pacs; /* Resume reading sinks if supported */ if (pacs->sink && queue_isempty(bap->rdb->sinks)) { if (gatt_db_attribute_get_char_data(pacs->sink, NULL, &value_handle, NULL, NULL, NULL)) { bt_gatt_client_read_value(bap->client, value_handle, read_sink_pac, bap, NULL); } } /* Resume reading sink locations if supported */ if (pacs->sink && pacs->sink_loc && !pacs->sink_loc_value) { if (gatt_db_attribute_get_char_data(pacs->sink_loc, NULL, &value_handle, NULL, NULL, NULL)) { bt_gatt_client_read_value(bap->client, value_handle, read_sink_pac_loc, bap, NULL); } } /* Resume reading sources if supported */ if (pacs->source && queue_isempty(bap->rdb->sources)) { if (gatt_db_attribute_get_char_data(pacs->source, NULL, &value_handle, NULL, NULL, NULL)) { bt_gatt_client_read_value(bap->client, value_handle, read_source_pac, bap, NULL); } } /* Resume reading source locations if supported */ if (pacs->source && pacs->source_loc && !pacs->source_loc_value) { if (gatt_db_attribute_get_char_data(pacs->source_loc, NULL, &value_handle, NULL, NULL, NULL)) { bt_gatt_client_read_value(bap->client, value_handle, read_source_pac_loc, bap, NULL); } } /* Resume reading supported contexts if supported */ if (pacs->sink && pacs->supported_context && !pacs->supported_sink_context_value && !pacs->supported_source_context_value) { if (gatt_db_attribute_get_char_data( pacs->supported_context, NULL, &value_handle, NULL, NULL, NULL)) { bt_gatt_client_read_value(bap->client, value_handle, read_pac_supported_context, bap, NULL); } } /* Resume reading contexts if supported */ if (pacs->sink && pacs->context && !pacs->sink_context_value && !pacs->source_context_value) { if (gatt_db_attribute_get_char_data(pacs->context, NULL, &value_handle, NULL, NULL, NULL)) { bt_gatt_client_read_value(bap->client, value_handle, read_pac_context, bap, NULL); } } queue_foreach(bap->remote_eps, bap_endpoint_foreach, bap); bap_cp_attach(bap); return true; } bt_uuid16_create(&uuid, PACS_UUID); gatt_db_foreach_service(bap->rdb->db, &uuid, foreach_pacs_service, bap); bt_uuid16_create(&uuid, ASCS_UUID); gatt_db_foreach_service(bap->rdb->db, &uuid, foreach_ascs_service, bap); return true; } bool bt_bap_attach_broadcast(struct bt_bap *bap) { struct bt_bap_endpoint *ep; if (queue_find(sessions, NULL, bap)) return true; if (!sessions) sessions = queue_new(); queue_push_tail(sessions, bap); queue_foreach(bap_cbs, bap_attached, bap); ep = bap_get_endpoint_bcast(bap->remote_eps, bap->ldb, BT_BAP_BCAST_SOURCE); if (ep) ep->bap = bap; return true; } static void stream_foreach_detach(void *data, void *user_data) { struct bt_bap_stream *stream = data; stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE); } static void bap_req_detach(void *data) { struct bt_bap_req *req = data; bap_req_complete(req, NULL); } void bt_bap_detach(struct bt_bap *bap) { DBG(bap, "%p", bap); if (!queue_remove(sessions, bap)) return; /* Cancel ongoing request */ if (bap->req) { bap_req_detach(bap->req); bap->req = NULL; } bt_gatt_client_idle_unregister(bap->client, bap->idle_id); /* Cancel queued requests */ queue_remove_all(bap->reqs, NULL, NULL, bap_req_detach); bt_gatt_client_unref(bap->client); bap->client = NULL; bt_att_unregister_disconnect(bap->att, bap->disconn_id); bap->att = NULL; queue_foreach(bap->streams, stream_foreach_detach, bap); queue_foreach(bap_cbs, bap_detached, bap); } bool bt_bap_set_debug(struct bt_bap *bap, bt_bap_debug_func_t func, void *user_data, bt_bap_destroy_func_t destroy) { if (!bap) return false; if (bap->debug_destroy) bap->debug_destroy(bap->debug_data); bap->debug_func = func; bap->debug_destroy = destroy; bap->debug_data = user_data; return true; } unsigned int bt_bap_ready_register(struct bt_bap *bap, bt_bap_ready_func_t func, void *user_data, bt_bap_destroy_func_t destroy) { struct bt_bap_ready *ready; static unsigned int id; if (!bap) return 0; ready = new0(struct bt_bap_ready, 1); ready->id = ++id ? id : ++id; ready->func = func; ready->destroy = destroy; ready->data = user_data; queue_push_tail(bap->ready_cbs, ready); return ready->id; } static bool match_ready_id(const void *data, const void *match_data) { const struct bt_bap_ready *ready = data; unsigned int id = PTR_TO_UINT(match_data); return (ready->id == id); } bool bt_bap_ready_unregister(struct bt_bap *bap, unsigned int id) { struct bt_bap_ready *ready; ready = queue_remove_if(bap->ready_cbs, match_ready_id, UINT_TO_PTR(id)); if (!ready) return false; bap_ready_free(ready); return true; } unsigned int bt_bap_state_register(struct bt_bap *bap, bt_bap_state_func_t func, bt_bap_connecting_func_t connecting, void *user_data, bt_bap_destroy_func_t destroy) { struct bt_bap_state *state; static unsigned int id; if (!bap) return 0; state = new0(struct bt_bap_state, 1); state->id = ++id ? id : ++id; state->func = func; state->connecting = connecting; state->destroy = destroy; state->data = user_data; queue_push_tail(bap->state_cbs, state); return state->id; } static bool match_state_id(const void *data, const void *match_data) { const struct bt_bap_state *state = data; unsigned int id = PTR_TO_UINT(match_data); return (state->id == id); } bool bt_bap_state_unregister(struct bt_bap *bap, unsigned int id) { struct bt_bap_state *state; if (!bap) return false; state = queue_remove_if(bap->state_cbs, match_state_id, UINT_TO_PTR(id)); if (!state) return false; bap_state_free(state); return false; } unsigned int bt_bap_bis_cb_register(struct bt_bap *bap, bt_bap_bis_func_t probe, bt_bap_func_t remove, void *user_data, bt_bap_destroy_func_t destroy) { struct bt_bap_bis_cb *bis_cb; static unsigned int id; if (!bap) return 0; bis_cb = new0(struct bt_bap_bis_cb, 1); bis_cb->id = ++id ? id : ++id; bis_cb->probe = probe; bis_cb->remove = remove; bis_cb->destroy = destroy; bis_cb->data = user_data; queue_push_tail(bap->bis_cbs, bis_cb); return bis_cb->id; } static bool match_bis_cb_id(const void *data, const void *match_data) { const struct bt_bap_bis_cb *bis_cb = data; unsigned int id = PTR_TO_UINT(match_data); return (bis_cb->id == id); } bool bt_bap_bis_cb_unregister(struct bt_bap *bap, unsigned int id) { struct bt_bap_bis_cb *bis_cb; if (!bap) return false; bis_cb = queue_remove_if(bap->bis_cbs, match_bis_cb_id, UINT_TO_PTR(id)); if (!bis_cb) return false; bap_bis_cb_free(bis_cb); return false; } void bt_bap_bis_probe(struct bt_bap *bap, uint8_t sid, uint8_t bis, uint8_t sgrp, struct iovec *caps, struct iovec *meta, struct bt_bap_qos *qos) { const struct queue_entry *entry; if (!bt_bap_ref_safe(bap)) return; entry = queue_get_entries(bap->bis_cbs); while (entry) { struct bt_bap_bis_cb *cb = entry->data; entry = entry->next; if (cb->probe) cb->probe(sid, bis, sgrp, caps, meta, qos, cb->data); } bt_bap_unref(bap); } void bt_bap_bis_remove(struct bt_bap *bap) { const struct queue_entry *entry; if (!bt_bap_ref_safe(bap)) return; entry = queue_get_entries(bap->bis_cbs); while (entry) { struct bt_bap_bis_cb *cb = entry->data; entry = entry->next; if (cb->remove) cb->remove(bap, cb->data); } bt_bap_unref(bap); } const char *bt_bap_stream_statestr(uint8_t state) { switch (state) { case BT_BAP_STREAM_STATE_IDLE: return "idle"; case BT_BAP_STREAM_STATE_CONFIG: return "config"; case BT_BAP_STREAM_STATE_QOS: return "qos"; case BT_BAP_STREAM_STATE_ENABLING: return "enabling"; case BT_BAP_STREAM_STATE_STREAMING: return "streaming"; case BT_BAP_STREAM_STATE_DISABLING: return "disabling"; case BT_BAP_STREAM_STATE_RELEASING: return "releasing"; } return "unknown"; } static void bap_foreach_pac(struct queue *l, struct queue *r, bt_bap_pac_foreach_t func, void *user_data) { const struct queue_entry *el; for (el = queue_get_entries(l); el; el = el->next) { struct bt_bap_pac *lpac = el->data; const struct queue_entry *er; for (er = queue_get_entries(r); er; er = er->next) { struct bt_bap_pac *rpac = er->data; /* Skip checking codec for bcast source, * it will be checked when BASE info are received */ if ((rpac->type != BT_BAP_BCAST_SOURCE) && (!bap_codec_equal(&lpac->codec, &rpac->codec))) continue; if (!func(lpac, rpac, user_data)) return; } } } void bt_bap_foreach_pac(struct bt_bap *bap, uint8_t type, bt_bap_pac_foreach_t func, void *user_data) { if (!bap || !func || !bap->rdb || queue_isempty(bap_db)) return; switch (type) { case BT_BAP_SINK: return bap_foreach_pac(bap->ldb->sources, bap->rdb->sinks, func, user_data); case BT_BAP_SOURCE: return bap_foreach_pac(bap->ldb->sinks, bap->rdb->sources, func, user_data); case BT_BAP_BCAST_SOURCE: case BT_BAP_BCAST_SINK: return bap_foreach_pac(bap->ldb->broadcast_sinks, bap->rdb->broadcast_sources, func, user_data); } } int bt_bap_pac_get_vendor_codec(struct bt_bap_pac *pac, uint8_t *id, uint16_t *cid, uint16_t *vid, struct iovec **data, struct iovec **metadata) { if (!pac) return -EINVAL; if (id) *id = pac->codec.id; if (cid) *cid = pac->codec.cid; if (vid) *vid = pac->codec.cid; if (data && pac->data) *data = pac->data; if (metadata && pac->metadata) *metadata = pac->metadata; return 0; } int bt_bap_pac_get_codec(struct bt_bap_pac *pac, uint8_t *id, struct iovec **data, struct iovec **metadata) { return bt_bap_pac_get_vendor_codec(pac, id, NULL, NULL, data, metadata); } void bt_bap_pac_set_user_data(struct bt_bap_pac *pac, void *user_data) { pac->user_data = user_data; } void *bt_bap_pac_get_user_data(struct bt_bap_pac *pac) { return pac->user_data; } bool bt_bap_pac_bcast_is_local(struct bt_bap *bap, struct bt_bap_pac *pac) { if (!bap->ldb) return false; if (queue_find(bap->ldb->broadcast_sinks, NULL, pac)) return true; if (queue_find(bap->ldb->broadcast_sources, NULL, pac)) return true; return false; } static bool find_ep_source(const void *data, const void *user_data) { const struct bt_bap_endpoint *ep = data; if (ep->dir == BT_BAP_BCAST_SINK) return true; else return false; } unsigned int bt_bap_stream_config(struct bt_bap_stream *stream, struct bt_bap_qos *qos, struct iovec *data, bt_bap_stream_func_t func, void *user_data) { unsigned int id; struct bt_bap *bap; if (!bap_stream_valid(stream)) return 0; if (!stream->ops || !stream->ops->config) return 0; if (!bt_bap_ref_safe(stream->bap)) return 0; bap = stream->bap; id = stream->ops->config(stream, qos, data, func, user_data); bt_bap_unref(bap); return id; } static bool match_pac(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, void *user_data) { struct match_pac *match = user_data; if (match->lpac && match->lpac != lpac) return true; if (match->rpac && match->rpac != rpac) return true; match->lpac = lpac; match->rpac = rpac; return false; } static void bap_pac_ltv_ch_counts(size_t i, uint8_t l, uint8_t t, uint8_t *v, void *user_data) { uint8_t *mask = user_data; if (v) *mask |= *v; } static uint8_t bap_pac_ch_counts(struct bt_bap_pac *pac) { uint8_t type = 0x03; uint8_t mask = 0; if (!pac->data) return 0; util_ltv_foreach(pac->data->iov_base, pac->data->iov_len, &type, bap_pac_ltv_ch_counts, &mask); if (!mask) mask = 0x01; /* default (BAP v1.0.1 Sec 4.3.1) */ return mask; } static unsigned int bap_count_eps(struct queue *eps, uint8_t dir) { const struct queue_entry *entry; unsigned int count = 0; for (entry = queue_get_entries(eps); entry; entry = entry->next) { struct bt_bap_endpoint *ep = entry->data; if (ep->dir == dir) count++; } return count; } int bt_bap_select(struct bt_bap *bap, struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, unsigned int max_channels, int *count, bt_bap_pac_select_t func, void *user_data) { uint32_t locations; uint8_t ch_counts; unsigned int num_ase; if (!lpac || !rpac || !func) return -EINVAL; if (!lpac->ops || !lpac->ops->select) return -EOPNOTSUPP; if (!max_channels) max_channels = 2; /* By default: don't go beyond BAP AC */ ch_counts = bap_pac_ch_counts(lpac) & bap_pac_ch_counts(rpac); locations = bt_bap_pac_get_locations(rpac); num_ase = bap_count_eps(bap->remote_eps, rpac->type); /* Fallback to unspecified/mono allocation if nothing is matching */ if (!locations || !ch_counts) { lpac->ops->select(lpac, rpac, 0, &rpac->qos, func, user_data, lpac->user_data); if (count) (*count)++; return 0; } /* Allocate all locations to streams */ while (num_ase) { uint32_t allocation = 0, alloc = 0; unsigned int i, n; /* Put max number of channels per stream */ for (i = 0, n = 0; i < 32 && n < 8; ++i) { uint32_t loc = (1LL << i); if (!(locations & loc)) continue; alloc |= loc; if ((BIT(n) & ch_counts) && n < max_channels) allocation = alloc; ++n; } if (!allocation) break; /* Configure stream */ lpac->ops->select(lpac, rpac, allocation, &rpac->qos, func, user_data, lpac->user_data); if (count) (*count)++; locations &= ~allocation; max_channels -= __builtin_popcount(allocation); num_ase--; } return 0; } void bt_bap_cancel_select(struct bt_bap_pac *lpac, bt_bap_pac_select_t func, void *user_data) { if (!lpac || !func) return; if (!lpac->ops || !lpac->ops->cancel_select) return; lpac->ops->cancel_select(lpac, func, user_data, lpac->user_data); } static struct bt_bap_stream *bap_bcast_stream_new(struct bt_bap *bap, struct bt_bap_pac *lpac, struct bt_bap_qos *pqos, struct iovec *data) { struct bt_bap_stream *stream = NULL; struct bt_bap_endpoint *ep = NULL; struct match_pac match; if (!bap || !lpac) return NULL; if (lpac->type == BT_BAP_BCAST_SOURCE) { match.lpac = lpac; match.rpac = NULL; memset(&match.codec, 0, sizeof(match.codec)); bt_bap_foreach_pac(bap, BT_BAP_BCAST_SINK, match_pac, &match); if (!match.lpac) return NULL; lpac = match.lpac; ep = queue_find(bap->remote_eps, find_ep_source, NULL); if (!ep) return NULL; } else if (lpac->type != BT_BAP_BCAST_SINK) { return NULL; } if (!stream) stream = bap_stream_new(bap, ep, lpac, NULL, data, true); return stream; } static bool find_ep_ucast(const void *data, const void *user_data) { const struct bt_bap_endpoint *ep = data; const struct match_pac *match = user_data; if (ep->stream) { if (!ep->stream->client) return false; if (ep->stream->locked) return false; if (!queue_isempty(ep->stream->pending_states)) return false; switch (ep->stream->state) { case BT_BAP_STREAM_STATE_IDLE: case BT_BAP_STREAM_STATE_CONFIG: case BT_BAP_STREAM_STATE_QOS: break; default: return false; } } switch (ep->state) { case BT_ASCS_ASE_STATE_IDLE: case BT_ASCS_ASE_STATE_CONFIG: case BT_ASCS_ASE_STATE_QOS: break; default: return false; } if (ep->dir != match->rpac->type) return false; switch (match->lpac->type) { case BT_BAP_SOURCE: if (ep->dir != BT_BAP_SINK) return false; break; case BT_BAP_SINK: if (ep->dir != BT_BAP_SOURCE) return false; break; default: return false; } return true; } static struct bt_bap_stream *bap_ucast_stream_new(struct bt_bap *bap, struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, struct bt_bap_qos *pqos, struct iovec *data) { struct bt_bap_stream *stream = NULL; struct bt_bap_endpoint *ep = NULL; struct match_pac match; if (!lpac || !rpac || !bap_codec_equal(&lpac->codec, &rpac->codec)) return NULL; memset(&match, 0, sizeof(match)); match.lpac = lpac; match.rpac = rpac; /* Get free ASE */ ep = queue_find(bap->remote_eps, find_ep_ucast, &match); if (!ep) { DBG(bap, "Unable to find usable ASE"); return NULL; } stream = ep->stream; if (stream) { /* Replace lpac: the stream generally needs to be reconfigured * after this, otherwise things like codec config not match. */ bap_stream_clear_cfm(stream); stream->lpac = lpac; util_iov_free(stream->cc, 1); stream->cc = util_iov_dup(data, 1); stream->need_reconfig = true; } else { stream = bap_stream_new(bap, ep, lpac, rpac, data, true); } return stream; } struct bt_bap_stream *bt_bap_stream_new(struct bt_bap *bap, struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, struct bt_bap_qos *pqos, struct iovec *data) { if (!bap) return NULL; /* Check if ATT is attached then it must be a unicast stream */ if (bt_bap_get_att(bap)) return bap_ucast_stream_new(bap, lpac, rpac, pqos, data); return bap_bcast_stream_new(bap, lpac, pqos, data); } void bt_bap_stream_lock(struct bt_bap_stream *stream) { if (!stream || !stream->client) return; /* Reserve stream ASE for use by upper level, so it won't get * reallocated */ stream->locked = true; } void bt_bap_stream_unlock(struct bt_bap_stream *stream) { if (!stream || !stream->client) return; stream->locked = false; } struct bt_bap *bt_bap_stream_get_session(struct bt_bap_stream *stream) { if (!stream) return NULL; return stream->bap; } uint8_t bt_bap_stream_get_state(struct bt_bap_stream *stream) { if (!stream) return BT_BAP_STREAM_STATE_IDLE; return stream->ops->get_state(stream); } bool bt_bap_stream_set_user_data(struct bt_bap_stream *stream, void *user_data) { if (!stream) return false; stream->user_data = user_data; return true; } void *bt_bap_stream_get_user_data(struct bt_bap_stream *stream) { if (!stream) return NULL; return stream->user_data; } unsigned int bt_bap_stream_qos(struct bt_bap_stream *stream, struct bt_bap_qos *data, bt_bap_stream_func_t func, void *user_data) { unsigned int id; if (!bap_stream_valid(stream)) return 0; if (!stream->ops || !stream->ops->qos) return 0; if (!bt_bap_ref_safe(stream->bap)) return 0; id = stream->ops->qos(stream, data, func, user_data); bt_bap_unref(stream->bap); return id; } unsigned int bt_bap_stream_enable(struct bt_bap_stream *stream, bool enable_links, struct iovec *metadata, bt_bap_stream_func_t func, void *user_data) { unsigned int id; struct bt_bap *bap; if (!bap_stream_valid(stream)) return 0; if (!stream->ops || !stream->ops->enable) return 0; if (!bt_bap_ref_safe(stream->bap)) return 0; bap = stream->bap; id = stream->ops->enable(stream, enable_links, metadata, func, user_data); bt_bap_unref(bap); return id; } unsigned int bt_bap_stream_start(struct bt_bap_stream *stream, bt_bap_stream_func_t func, void *user_data) { unsigned int id; struct bt_bap *bap; if (!bap_stream_valid(stream)) return 0; if (!stream->ops || !stream->ops->start) return 0; if (!bt_bap_ref_safe(stream->bap)) return 0; bap = stream->bap; id = stream->ops->start(stream, func, user_data); bt_bap_unref(bap); return id; } unsigned int bt_bap_stream_disable(struct bt_bap_stream *stream, bool disable_links, bt_bap_stream_func_t func, void *user_data) { unsigned int id; struct bt_bap *bap; if (!bap_stream_valid(stream)) return 0; if (!stream->ops || !stream->ops->disable) return 0; if (!bt_bap_ref_safe(stream->bap)) return 0; bap = stream->bap; id = stream->ops->disable(stream, disable_links, func, user_data); bt_bap_unref(bap); return id; } unsigned int bt_bap_stream_stop(struct bt_bap_stream *stream, bt_bap_stream_func_t func, void *user_data) { unsigned int id; if (!bap_stream_valid(stream)) return 0; if (!stream->ops || !stream->ops->stop) return 0; if (!bt_bap_ref_safe(stream->bap)) return 0; id = stream->ops->stop(stream, func, user_data); bt_bap_unref(stream->bap); return id; } unsigned int bt_bap_stream_metadata(struct bt_bap_stream *stream, struct iovec *metadata, bt_bap_stream_func_t func, void *user_data) { unsigned int id; if (!bap_stream_valid(stream)) return 0; if (!stream->ops || !stream->ops->metadata) return 0; if (!bt_bap_ref_safe(stream->bap)) return 0; id = stream->ops->metadata(stream, metadata, func, user_data); bt_bap_unref(stream->bap); return id; } unsigned int bt_bap_stream_release(struct bt_bap_stream *stream, bt_bap_stream_func_t func, void *user_data) { unsigned int id; struct bt_bap *bap; if (!bap_stream_valid(stream)) return 0; if (!stream || !stream->ops || !stream->ops->release) return 0; bap = stream->bap; if (!bt_bap_ref_safe(bap)) return 0; id = stream->ops->release(stream, func, user_data); bt_bap_unref(bap); return id; } uint8_t bt_bap_stream_get_dir(struct bt_bap_stream *stream) { if (!stream) return 0x00; return stream->ops->get_dir(stream); } uint32_t bt_bap_stream_get_location(struct bt_bap_stream *stream) { if (!stream) return 0x00000000; return stream->ops->get_loc(stream); } struct iovec *bt_bap_stream_get_config(struct bt_bap_stream *stream) { if (!stream) return NULL; return stream->cc; } struct bt_bap_qos *bt_bap_stream_get_qos(struct bt_bap_stream *stream) { if (!stream) return NULL; return &stream->qos; } struct iovec *bt_bap_stream_get_metadata(struct bt_bap_stream *stream) { if (!stream) return NULL; return stream->meta; } struct io *bt_bap_stream_get_io(struct bt_bap_stream *stream) { struct bt_bap_stream_io *io; io = stream_get_io(stream); if (!io || io->connecting) return NULL; return io->io; } bool bt_bap_match_bcast_sink_stream(const void *data, const void *user_data) { const struct bt_bap_stream *stream = data; if (!stream->lpac) return false; return stream->lpac->type == BT_BAP_BCAST_SINK; } static bool stream_io_disconnected(struct io *io, void *user_data) { struct bt_bap_stream *stream = user_data; DBG(stream->bap, "stream %p io disconnected", stream); /* If the IO is for a broadcast sink has been disconnected both BIG Sync * and PA Sync have been lost so switch to idle state to cleanup the * stream. */ if (stream->lpac->type == BT_BAP_BCAST_SINK) { stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE); return false; } switch (stream->ep->state) { case BT_ASCS_ASE_STATE_RELEASING: stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG); break; case BT_ASCS_ASE_STATE_ENABLING: case BT_ASCS_ASE_STATE_STREAMING: case BT_ASCS_ASE_STATE_DISABLING: stream_set_state(stream, BT_BAP_STREAM_STATE_QOS); break; } bt_bap_stream_set_io(stream, -1); return false; } bool bt_bap_stream_set_io(struct bt_bap_stream *stream, int fd) { bool ret; struct bt_bap *bap; if (!bap_stream_valid(stream)) return false; if (!stream->ops || !stream->ops->set_io) return false; if (!bt_bap_ref_safe(stream->bap)) return false; bap = stream->bap; ret = stream->ops->set_io(stream, fd); bt_bap_unref(bap); return ret; } static bool match_req_id(const void *data, const void *match_data) { const struct bt_bap_req *req = data; unsigned int id = PTR_TO_UINT(match_data); return (req->id == id); } static bool match_name(const void *data, const void *match_data) { const struct bt_bap_pac *pac = data; const char *name = match_data; return (!strcmp(pac->name, name)); } int bt_bap_stream_cancel(struct bt_bap_stream *stream, unsigned int id) { struct bt_bap_req *req; if (!stream) return -EINVAL; if (stream->bap->req && stream->bap->req->id == id) { req = stream->bap->req; stream->bap->req = NULL; bap_req_free(req); return 0; } req = queue_remove_if(stream->bap->reqs, match_req_id, UINT_TO_PTR(id)); if (!req) return 0; bap_req_free(req); return 0; } int bt_bap_stream_io_link(struct bt_bap_stream *stream, struct bt_bap_stream *link) { int ret; struct bt_bap *bap; if (!bap_stream_valid(stream)) return -EINVAL; if (!stream->ops || !stream->ops->io_link) return -EINVAL; if (!bt_bap_ref_safe(stream->bap)) return -EINVAL; bap = stream->bap; ret = stream->ops->io_link(stream, link); bt_bap_unref(bap); return ret; } int bt_bap_stream_io_unlink(struct bt_bap_stream *stream, struct bt_bap_stream *link) { int ret; struct bt_bap *bap; if (!bap_stream_valid(stream)) return -EINVAL; if (!stream->ops || !stream->ops->io_unlink) return -EINVAL; if (!bt_bap_ref_safe(stream->bap)) return -EINVAL; bap = stream->bap; ret = stream->ops->io_unlink(stream, link); bt_bap_unref(bap); return ret; } struct queue *bt_bap_stream_io_get_links(struct bt_bap_stream *stream) { if (!stream) return NULL; return stream->links; } static void bap_stream_get_in_qos(void *data, void *user_data) { struct bt_bap_stream *stream = data; struct bt_bap_qos **qos = user_data; if (!stream) return; if (!qos || *qos || stream->ep->dir != BT_BAP_SOURCE || !stream->qos.ucast.io_qos.sdu) return; *qos = &stream->qos; } static void bap_stream_get_out_qos(void *data, void *user_data) { struct bt_bap_stream *stream = data; struct bt_bap_qos **qos = user_data; if (!stream) return; if (!qos || *qos || stream->ep->dir != BT_BAP_SINK || !stream->qos.ucast.io_qos.sdu) return; *qos = &stream->qos; } static void bap_stream_bcast_get_out_qos(void *data, void *user_data) { struct bt_bap_stream *stream = data; struct bt_bap_qos **qos = user_data; if (!stream) return; if (!qos || *qos || stream->ep->dir != BT_BAP_BCAST_SINK || !stream->qos.bcast.io_qos.sdu) return; *qos = &stream->qos; } static void bap_stream_bcast_get_in_qos(void *data, void *user_data) { struct bt_bap_stream *stream = data; struct bt_bap_qos **qos = user_data; if (!stream) return; if (!qos || *qos || stream->ep->dir != BT_BAP_BCAST_SOURCE || !stream->qos.bcast.io_qos.sdu) return; *qos = &stream->qos; } bool bt_bap_stream_io_get_qos(struct bt_bap_stream *stream, struct bt_bap_qos **in, struct bt_bap_qos **out) { if (!stream || (!in && !out)) return false; switch (stream->ep->dir) { case BT_BAP_SOURCE: bap_stream_get_in_qos(stream, in); queue_foreach(stream->links, bap_stream_get_out_qos, out); break; case BT_BAP_SINK: bap_stream_get_out_qos(stream, out); queue_foreach(stream->links, bap_stream_get_in_qos, in); break; case BT_BAP_BCAST_SOURCE: bap_stream_bcast_get_in_qos(stream, in); break; case BT_BAP_BCAST_SINK: bap_stream_bcast_get_out_qos(stream, out); break; default: return false; } DBG(stream->bap, "in %p out %p", in ? *in : NULL, out ? *out : NULL); return (in && *in) || (out && *out); } static void bap_stream_get_dir(void *data, void *user_data) { struct bt_bap_stream *stream = data; uint8_t *dir = user_data; *dir |= stream->ep->dir; } uint8_t bt_bap_stream_io_dir(struct bt_bap_stream *stream) { uint8_t dir; struct bt_bap *bap; if (!bap_stream_valid(stream)) return 0; if (!stream->ops || !stream->ops->set_io) return 0; if (!bt_bap_ref_safe(stream->bap)) return 00; bap = stream->bap; dir = stream->ops->io_dir(stream); bt_bap_unref(bap); return dir; } static void bap_stream_io_connecting(void *data, void *user_data) { struct bt_bap_stream *stream = data; int fd = PTR_TO_INT(user_data); if (!stream) return; if (fd >= 0) bap_stream_io_attach(stream, fd, true); else bap_stream_io_detach(stream); bap_stream_notify_connecting(stream, stream->io ? true : false, fd); } int bt_bap_stream_io_connecting(struct bt_bap_stream *stream, int fd) { if (!stream) return -EINVAL; bap_stream_io_connecting(stream, INT_TO_PTR(fd)); queue_foreach(stream->links, bap_stream_io_connecting, INT_TO_PTR(fd)); return 0; } bool bt_bap_stream_io_is_connecting(struct bt_bap_stream *stream, int *fd) { struct bt_bap_stream_io *io; if (!stream) return false; io = stream_get_io(stream); if (!io) return false; if (fd) *fd = stream_io_get_fd(io); return io->connecting; } bool bt_bap_new_bcast_source(struct bt_bap *bap, const char *name) { struct bt_bap_endpoint *ep; struct bt_bap_pac *pac_broadcast_source; /* Add the remote source only if a local sink endpoint was registered */ if (queue_isempty(bap->ldb->broadcast_sinks)) return false; /* Add remote source endpoint */ if (!bap->rdb->broadcast_sources) bap->rdb->broadcast_sources = queue_new(); if (queue_find(bap->rdb->broadcast_sources, match_name, name)) return true; pac_broadcast_source = bap_pac_new(bap->rdb, name, BT_BAP_BCAST_SOURCE, NULL, NULL, NULL, NULL); queue_push_tail(bap->rdb->broadcast_sources, pac_broadcast_source); if (!pac_broadcast_source) return false; queue_foreach(bap->pac_cbs, notify_pac_added, pac_broadcast_source); /* Push remote endpoint with direction sink */ ep = bap_endpoint_new_broadcast(bap->rdb, BT_BAP_BCAST_SINK); if (ep) queue_push_tail(bap->remote_eps, ep); return true; } void bt_bap_update_bcast_source(struct bt_bap_pac *pac, struct bt_bap_codec *codec, struct iovec *data, struct iovec *metadata) { bap_pac_merge(pac, data, metadata); pac->codec = *codec; } static void destroy_base_bis(void *data) { struct bt_bis *bis = data; if (!bis) return; if (bis->caps) util_iov_free(bis->caps, 1); free(bis); } static void generate_bis_base(void *data, void *user_data) { struct bt_bis *bis = data; struct iovec *base_iov = user_data; uint8_t cc_length = bis->caps->iov_len; if (!util_iov_push_u8(base_iov, bis->index)) return; if (!util_iov_push_u8(base_iov, cc_length)) return; if (cc_length) util_iov_push_mem(base_iov, bis->caps->iov_len, bis->caps->iov_base); } static void generate_subgroup_base(void *data, void *user_data) { struct bt_subgroup *sgrp = data; struct iovec *base_iov = user_data; if (!util_iov_push_u8(base_iov, queue_length(sgrp->bises))) return; if (!util_iov_push_u8(base_iov, sgrp->codec.id)) return; if (!util_iov_push_le16(base_iov, sgrp->codec.cid)) return; if (!util_iov_push_le16(base_iov, sgrp->codec.vid)) return; if (sgrp->caps) { if (!util_iov_push_u8(base_iov, sgrp->caps->iov_len)) return; if (sgrp->caps->iov_len) util_iov_push_mem(base_iov, sgrp->caps->iov_len, sgrp->caps->iov_base); } else if (!util_iov_push_u8(base_iov, 0)) return; if (sgrp->meta) { if (!util_iov_push_u8(base_iov, sgrp->meta->iov_len)) return; if (sgrp->meta->iov_len) util_iov_push_mem(base_iov, sgrp->meta->iov_len, sgrp->meta->iov_base); } else if (!util_iov_push_u8(base_iov, 0)) return; queue_foreach(sgrp->bises, generate_bis_base, base_iov); } static struct iovec *generate_base(struct bt_base *base) { struct iovec *base_iov = new0(struct iovec, 0x1); base_iov->iov_base = util_malloc(BASE_MAX_LENGTH); if (!util_iov_push_le24(base_iov, base->pres_delay)) { free(base_iov->iov_base); free(base_iov); return NULL; } if (!util_iov_push_u8(base_iov, queue_length(base->subgroups))) { free(base_iov->iov_base); free(base_iov); return NULL; } queue_foreach(base->subgroups, generate_subgroup_base, base_iov); return base_iov; } static void add_new_bis(struct bt_subgroup *subgroup, uint8_t bis_index, struct iovec *caps) { struct bt_bis *bis = new0(struct bt_bis, 1); bis->index = bis_index; if (caps) bis->caps = caps; else bis->caps = new0(struct iovec, 1); queue_push_tail(subgroup->bises, bis); } static void add_new_subgroup(struct bt_base *base, struct bt_bap_stream *stream) { struct bt_bap_pac *lpac = stream->lpac; struct bt_subgroup *sgrp; uint16_t cid = 0; uint16_t vid = 0; if (!lpac) return; sgrp = new0(struct bt_subgroup, 1); bt_bap_pac_get_vendor_codec(lpac, &sgrp->codec.id, &cid, &vid, NULL, NULL); sgrp->codec.cid = cid; sgrp->codec.vid = vid; sgrp->caps = util_iov_dup(stream->cc, 1); sgrp->meta = util_iov_dup(stream->meta, 1); sgrp->bises = queue_new(); stream->qos.bcast.bis = base->next_bis_index++; add_new_bis(sgrp, stream->qos.bcast.bis, NULL); queue_push_tail(base->subgroups, sgrp); } struct bt_ltv_match { uint8_t l; void *data; bool found; uint32_t data32; }; struct bt_ltv_search { struct iovec *iov; bool found; }; static void match_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v, void *user_data) { struct bt_ltv_match *ltv_match = user_data; if (ltv_match->found == true) return; if (ltv_match->l != l) return; if (!memcmp(v, ltv_match->data, l)) ltv_match->found = true; } static void search_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v, void *user_data) { struct bt_ltv_search *ltv_search = user_data; struct bt_ltv_match ltv_match; ltv_match.found = false; ltv_match.l = l; ltv_match.data = v; util_ltv_foreach(ltv_search->iov->iov_base, ltv_search->iov->iov_len, &t, match_ltv, <v_match); /* Once "found" has been updated to "false", * do not overwrite it anymore. * It means that an ltv was not found in the search list, * and this should be detected back in the parent function. */ if (ltv_search->found) ltv_search->found = ltv_match.found; } static bool compare_ltv(struct iovec *iov1, struct iovec *iov2) { struct bt_ltv_search ltv_search; if ((!iov1) && (!iov2)) return true; if ((!iov1) || (!iov2)) return false; /* Compare metadata length */ if (iov1->iov_len != iov2->iov_len) return false; ltv_search.found = true; ltv_search.iov = iov2; util_ltv_foreach(iov1->iov_base, iov1->iov_len, NULL, search_ltv, <v_search); return ltv_search.found; } struct bt_ltv_extract { struct iovec *src; void *value; uint8_t len; struct iovec *result; }; static void extract_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v, void *user_data) { struct bt_ltv_extract *ext_data = user_data; struct bt_ltv_match ltv_match; uint8_t ltv_len = 0; ltv_match.found = false; ltv_match.l = l; ltv_match.data = v; /* Search each BIS caps ltv in subgroup caps * to extract the one that are BIS specific */ util_ltv_foreach(ext_data->src->iov_base, ext_data->src->iov_len, &t, match_ltv, <v_match); if (!ltv_match.found) { ltv_len = l + 1; util_iov_append(ext_data->result, <v_len, 1); util_iov_append(ext_data->result, &t, 1); util_iov_append(ext_data->result, v, l); } } static struct iovec *extract_diff_caps( struct iovec *subgroup_caps, struct iovec *bis_caps) { struct bt_ltv_extract ext_data; ext_data.src = subgroup_caps; ext_data.result = new0(struct iovec, 1); util_ltv_foreach(bis_caps->iov_base, bis_caps->iov_len, NULL, extract_ltv, &ext_data); return ext_data.result; } static void set_base_subgroup(void *data, void *user_data) { struct bt_bap_stream *stream = data; struct bt_base *base = user_data; /* BIS specific codec capabilities */ struct iovec *bis_caps; if (bt_bap_pac_get_type(stream->lpac) != BT_BAP_BCAST_SOURCE) return; if (stream->qos.bcast.big != base->big_id) return; if (base->pres_delay < stream->qos.bcast.delay) base->pres_delay = stream->qos.bcast.delay; if (queue_isempty(base->subgroups)) { add_new_subgroup(base, stream); } else { /* Verify if a subgroup has the same metadata */ const struct queue_entry *entry; struct bt_subgroup *subgroup = NULL; bool same_meta = false; for (entry = queue_get_entries(base->subgroups); entry; entry = entry->next) { subgroup = entry->data; same_meta = compare_ltv(subgroup->meta, stream->meta); if (same_meta) break; } if (!same_meta) { /* No subgroup with the same metadata found. * Create a new one. */ add_new_subgroup(base, stream); } else { /* Subgroup found with the same metadata. * Extract different codec capabilities. */ bis_caps = extract_diff_caps( subgroup->caps, stream->cc); stream->qos.bcast.bis = base->next_bis_index++; add_new_bis(subgroup, stream->qos.bcast.bis, bis_caps); } } } static void destroy_base_subgroup(void *data) { struct bt_subgroup *subgroup = data; if (!subgroup) return; if (subgroup->caps) util_iov_free(subgroup->caps, 1); if (subgroup->meta) util_iov_free(subgroup->meta, 1); queue_destroy(subgroup->bises, destroy_base_bis); free(subgroup); } /* * Function to update the BASE using configuration data * from each BIS belonging to the same BIG */ struct iovec *bt_bap_stream_get_base(struct bt_bap_stream *stream) { struct bt_base base; struct iovec *base_iov; base.subgroups = queue_new(); base.next_bis_index = 1; base.big_id = stream->qos.bcast.big; base.pres_delay = stream->qos.bcast.delay; /* If the BIG ID was explicitly set, create a BASE with information * from all streams belonging to this BIG. Otherwise, create a BASE * with only this BIS. */ if (stream->qos.bcast.big != 0xFF) queue_foreach(stream->bap->streams, set_base_subgroup, &base); else { base.pres_delay = stream->qos.bcast.delay; set_base_subgroup(stream, &base); } base_iov = generate_base(&base); queue_destroy(base.subgroups, destroy_base_subgroup); return base_iov; } /* * This function compares PAC Codec Specific Capabilities, with the Codec * Specific Configuration LTVs received in the BASE of the BAP Source. The * result is accumulated in data32 which is a bitmask of types. */ static void check_pac_caps_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v, void *user_data) { struct bt_ltv_match *compare_data = user_data; uint8_t *bis_v = compare_data->data; uint16_t mask; uint16_t min; uint16_t max; uint16_t frame_len; switch (t) { case BAP_FREQ_LTV_TYPE: mask = get_le16(v); if (mask & (1 << (bis_v[0] - 1))) compare_data->data32 |= 1<data32 |= 1<= min) && (frame_len <= max)) compare_data->data32 |= 1<data; struct bt_ltv_match compare_data; compare_data.data = v; /* Search inside local PAC's caps for LTV of type t */ util_ltv_foreach(pac_caps->iov_base, pac_caps->iov_len, &t, check_pac_caps_ltv, &compare_data); local_data->data32 |= compare_data.data32; } static void bap_sink_check_level3_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v, void *user_data) { struct bt_ltv_extract *merge_data = user_data; merge_data->value = v; merge_data->len = l; } static void bap_sink_check_level2_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v, void *user_data) { struct bt_ltv_extract *merge_data = user_data; merge_data->value = NULL; util_ltv_foreach(merge_data->src->iov_base, merge_data->src->iov_len, &t, bap_sink_check_level3_ltv, merge_data); /* If the LTV at level 2 was found at level 3 add the one from level 3, * otherwise add the one at level 2 */ if (merge_data->value) util_ltv_push(merge_data->result, merge_data->len, t, merge_data->value); else util_ltv_push(merge_data->result, l, t, v); } static void bap_sink_append_level3_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v, void *user_data) { struct bt_ltv_extract *merge_data = user_data; merge_data->value = NULL; util_ltv_foreach(merge_data->result->iov_base, merge_data->result->iov_len, &t, bap_sink_check_level3_ltv, merge_data); /* If the LTV at level 3 was not found in merged configuration, * append value */ if (!merge_data->value) util_ltv_push(merge_data->result, l, t, v); } static void check_local_pac(void *data, void *user_data) { struct bt_ltv_match *compare_data = user_data; struct iovec *bis_data = (struct iovec *)compare_data->data; const struct bt_bap_pac *pac = data; /* Keep searching for a matching PAC if one wasn't found * in previous PAC element */ if (compare_data->found == false) { struct bt_ltv_match bis_compare_data = { .data = pac->data, .data32 = 0, /* LTVs bitmask result */ .found = false }; /* loop each BIS LTV */ util_ltv_foreach(bis_data->iov_base, bis_data->iov_len, NULL, check_source_ltv, &bis_compare_data); /* We have a match if all selected LTVs have a match */ if ((bis_compare_data.data32 & CODEC_SPECIFIC_CONFIGURATION_MASK) == CODEC_SPECIFIC_CONFIGURATION_MASK) { compare_data->found = true; compare_data->data = data; } } } static void bap_sink_match_allocation(size_t i, uint8_t l, uint8_t t, uint8_t *v, void *user_data) { struct bt_ltv_match *data = user_data; uint32_t location32; if (!v) return; memcpy(&location32, v, l); location32 = le32_to_cpu(location32); /* If all the bits in the received bitmask are found in * the local bitmask then we have a match */ if ((location32 & data->data32) == location32) data->found = true; else data->found = false; } static struct bt_ltv_match bap_check_bis(uint32_t sink_loc, struct queue *pacs, struct iovec *bis_data) { struct bt_ltv_match compare_data = {}; /* Check channel allocation against the PACS location. * If we don't have a location set we can accept any BIS location. * If the BIS doesn't have a location set we also accept it */ compare_data.found = true; if (sink_loc) { uint8_t type = BAP_CHANNEL_ALLOCATION_LTV_TYPE; compare_data.data32 = sink_loc; util_ltv_foreach(bis_data->iov_base, bis_data->iov_len, &type, bap_sink_match_allocation, &compare_data); } /* Check remaining LTVs against the PACs list */ if (compare_data.found) { compare_data.data = bis_data; compare_data.found = false; queue_foreach(pacs, check_local_pac, &compare_data); } return compare_data; } struct iovec *bt_bap_merge_caps(struct iovec *l2_caps, struct iovec *l3_caps) { struct bt_ltv_extract merge_data = {0}; if (!l2_caps) /* Codec_Specific_Configuration parameters shall * be present at Level 2. */ return NULL; if (!l3_caps) /* Codec_Specific_Configuration parameters may * be present at Level 3. */ return util_iov_dup(l2_caps, 1); merge_data.src = l3_caps; merge_data.result = new0(struct iovec, 1); /* Create a Codec Specific Configuration with LTVs at level 2 (subgroup) * overwritten by LTVs at level 3 (BIS) */ util_ltv_foreach(l2_caps->iov_base, l2_caps->iov_len, NULL, bap_sink_check_level2_ltv, &merge_data); /* Append LTVs at level 3 (BIS) that were not found at * level 2 (subgroup) */ util_ltv_foreach(l3_caps->iov_base, l3_caps->iov_len, NULL, bap_sink_append_level3_ltv, &merge_data); return merge_data.result; } void bt_bap_verify_bis(struct bt_bap *bap, uint8_t bis_index, struct iovec *caps, struct bt_bap_pac **lpac) { struct bt_ltv_match match_data; uint32_t sink_loc; struct queue *pacs; if (!caps) return; /* If the bap session corresponds to a client connection with * a BAP Server, bis caps should be checked against peer caps. * If the bap session corresponds to a scanned broadcast source, * bis caps should be checked against local broadcast sink caps. */ if (bap->client) { sink_loc = bap->rdb->pacs->sink_loc_value; pacs = bap->rdb->sinks; } else { sink_loc = bap->ldb->pacs->sink_loc_value; pacs = bap->ldb->broadcast_sinks; } /* Check each BIS Codec Specific Configuration LTVs against our Codec * Specific Capabilities and if the BIS matches create a PAC with it */ match_data = bap_check_bis(sink_loc, pacs, caps); if (match_data.found == true) { *lpac = match_data.data; DBG(bap, "Matching BIS %i", bis_index); } else { *lpac = NULL; } } bool bt_bap_parse_base(uint8_t sid, struct iovec *iov, struct bt_bap_qos *qos, util_debug_func_t func, bt_bap_bis_func_t handler, void *user_data) { uint8_t sgrps; bool ret = true; util_debug(func, NULL, "BASE len: %zd", iov->iov_len); if (!util_iov_pull_le24(iov, &qos->bcast.delay)) return false; util_debug(func, NULL, "PresentationDelay: %d", qos->bcast.delay); if (!util_iov_pull_u8(iov, &sgrps)) return false; util_debug(func, NULL, "Number of Subgroups: %d", sgrps); /* Loop subgroups */ for (int idx = 0; idx < sgrps; idx++) { uint8_t num_bis; struct bt_bap_codec *codec; struct iovec l2_cc; uint8_t l2_cc_len; struct iovec meta; uint8_t meta_len; util_debug(func, NULL, "Subgroup #%d", idx); if (!util_iov_pull_u8(iov, &num_bis)) { ret = false; goto done; } util_debug(func, NULL, "Number of BISes: %d", num_bis); codec = util_iov_pull_mem(iov, sizeof(*codec)); if (!codec) { ret = false; goto done; } util_debug(func, NULL, "Codec: ID %d CID 0x%2.2x VID 0x%2.2x", codec->id, codec->cid, codec->vid); /* Level 2 */ /* Read Codec Specific Configuration */ if (!util_iov_pull_u8(iov, &l2_cc_len)) { ret = false; goto done; } l2_cc.iov_base = util_iov_pull_mem(iov, l2_cc_len); if (!l2_cc.iov_base) { ret = false; goto done; } l2_cc.iov_len = l2_cc_len; /* Print Codec Specific Configuration */ util_debug(func, NULL, "CC len: %zd", l2_cc.iov_len); bt_bap_debug_config(l2_cc.iov_base, l2_cc.iov_len, func, NULL); /* Read Metadata */ if (!util_iov_pull_u8(iov, &meta_len)) { ret = false; goto done; } meta.iov_base = util_iov_pull_mem(iov, meta_len); if (!meta.iov_base) { ret = false; goto done; } meta.iov_len = meta_len; /* Print Metadata */ util_debug(func, NULL, "Metadata len: %i", (uint8_t)meta.iov_len); bt_bap_debug_metadata(meta.iov_base, meta.iov_len, func, NULL); /* Level 3 */ for (; num_bis; num_bis--) { uint8_t bis_index; struct iovec l3_cc; uint8_t l3_cc_len; struct iovec *bis_cc; if (!util_iov_pull_u8(iov, &bis_index)) { ret = false; goto done; } util_debug(func, NULL, "BIS #%d", bis_index); /* Read Codec Specific Configuration */ if (!util_iov_pull_u8(iov, &l3_cc_len)) { ret = false; goto done; } l3_cc.iov_base = util_iov_pull_mem(iov, l3_cc_len); if (!l3_cc.iov_base) { ret = false; goto done; } l3_cc.iov_len = l3_cc_len; /* Print Codec Specific Configuration */ util_debug(func, NULL, "CC Len: %d", (uint8_t)l3_cc.iov_len); bt_bap_debug_config(l3_cc.iov_base, l3_cc.iov_len, func, NULL); bis_cc = bt_bap_merge_caps(&l2_cc, &l3_cc); if (!bis_cc) continue; handler(sid, bis_index, idx, bis_cc, &meta, qos, user_data); util_iov_free(bis_cc, 1); } } done: if (!ret) util_debug(func, NULL, "Unable to parse Base"); return ret; } void bt_bap_req_bcode(struct bt_bap_stream *stream, bt_bap_bcode_reply_t reply, void *reply_data) { const struct queue_entry *entry; if (!bap_stream_valid(stream)) return; bt_bap_stream_ref(stream); if (!bt_bap_ref_safe(stream->bap)) goto done; entry = queue_get_entries(stream->bap->bcode_cbs); while (entry) { struct bt_bap_bcode_cb *cb = entry->data; entry = entry->next; if (cb->func) cb->func(stream, reply, reply_data, cb->data); } bt_bap_unref(stream->bap); done: bt_bap_stream_unref(stream); } unsigned int bt_bap_bcode_cb_register(struct bt_bap *bap, bt_bap_bcode_func_t func, void *user_data, bt_bap_destroy_func_t destroy) { struct bt_bap_bcode_cb *cb; static unsigned int id; if (!bap) return 0; cb = new0(struct bt_bap_bcode_cb, 1); cb->id = ++id ? id : ++id; cb->func = func; cb->destroy = destroy; cb->data = user_data; queue_push_tail(bap->bcode_cbs, cb); return cb->id; } static bool match_bcode_cb_id(const void *data, const void *match_data) { const struct bt_bap_bcode_cb *cb = data; unsigned int id = PTR_TO_UINT(match_data); return (cb->id == id); } bool bt_bap_bcode_cb_unregister(struct bt_bap *bap, unsigned int id) { struct bt_bap_bcode_cb *cb; if (!bap) return false; cb = queue_remove_if(bap->bcode_cbs, match_bcode_cb_id, UINT_TO_PTR(id)); if (!cb) return false; bap_bcode_cb_free(cb); return false; } void bt_bap_iso_qos_to_bap_qos(struct bt_iso_qos *iso_qos, struct bt_bap_qos *bap_qos) { bap_qos->bcast.big = iso_qos->bcast.big; bap_qos->bcast.bis = iso_qos->bcast.bis; bap_qos->bcast.sync_factor = iso_qos->bcast.sync_factor; bap_qos->bcast.packing = iso_qos->bcast.packing; bap_qos->bcast.framing = iso_qos->bcast.framing; bap_qos->bcast.encryption = iso_qos->bcast.encryption; if (bap_qos->bcast.encryption) bap_qos->bcast.bcode = util_iov_new(iso_qos->bcast.bcode, sizeof(iso_qos->bcast.bcode)); bap_qos->bcast.options = iso_qos->bcast.options; bap_qos->bcast.skip = iso_qos->bcast.skip; bap_qos->bcast.sync_timeout = iso_qos->bcast.sync_timeout; bap_qos->bcast.sync_cte_type = iso_qos->bcast.sync_cte_type; bap_qos->bcast.mse = iso_qos->bcast.mse; bap_qos->bcast.timeout = iso_qos->bcast.timeout; bap_qos->bcast.io_qos.interval = iso_qos->bcast.in.interval; bap_qos->bcast.io_qos.latency = iso_qos->bcast.in.latency; bap_qos->bcast.io_qos.phys = iso_qos->bcast.in.phys; bap_qos->bcast.io_qos.rtn = iso_qos->bcast.in.rtn; bap_qos->bcast.io_qos.sdu = iso_qos->bcast.in.sdu; } void bt_bap_qos_to_iso_qos(struct bt_bap_qos *bap_qos, struct bt_iso_qos *iso_qos) { memset(iso_qos, 0, sizeof(*iso_qos)); iso_qos->bcast.big = bap_qos->bcast.big; iso_qos->bcast.bis = bap_qos->bcast.bis; iso_qos->bcast.sync_factor = bap_qos->bcast.sync_factor; iso_qos->bcast.packing = bap_qos->bcast.packing; iso_qos->bcast.framing = bap_qos->bcast.framing; iso_qos->bcast.encryption = bap_qos->bcast.encryption; if (bap_qos->bcast.bcode && bap_qos->bcast.bcode->iov_base) memcpy(iso_qos->bcast.bcode, bap_qos->bcast.bcode->iov_base, bap_qos->bcast.bcode->iov_len); iso_qos->bcast.options = bap_qos->bcast.options; iso_qos->bcast.skip = bap_qos->bcast.skip; iso_qos->bcast.sync_timeout = bap_qos->bcast.sync_timeout; iso_qos->bcast.sync_cte_type = bap_qos->bcast.sync_cte_type; iso_qos->bcast.mse = bap_qos->bcast.mse; iso_qos->bcast.timeout = bap_qos->bcast.timeout; memcpy(&iso_qos->bcast.out, &bap_qos->bcast.io_qos, sizeof(struct bt_iso_io_qos)); } bluez-5.87/src/shared/PaxHeaders/ad.c0000644000000000000000000000005015200607060014434 xustar0020 atime=1783092683 20 ctime=1783094099 bluez-5.87/src/shared/ad.c0000644000000000000000000007020115200607060014115 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Google Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "src/shared/ad.h" #include "src/eir.h" #include "src/shared/queue.h" #include "src/shared/util.h" struct bt_ad { int ref_count; uint8_t max_len; char *name; uint16_t appearance; struct queue *service_uuids; struct queue *manufacturer_data; struct queue *solicit_uuids; struct queue *service_data; struct queue *data; }; struct pattern_match_info { struct bt_ad *ad; struct bt_ad_pattern *current_pattern; struct bt_ad_pattern *matched_pattern; }; struct bt_ad *bt_ad_new(void) { struct bt_ad *ad; ad = new0(struct bt_ad, 1); ad->max_len = BT_EA_MAX_DATA_LEN; ad->service_uuids = queue_new(); ad->manufacturer_data = queue_new(); ad->solicit_uuids = queue_new(); ad->service_data = queue_new(); ad->data = queue_new(); ad->appearance = UINT16_MAX; return bt_ad_ref(ad); } bool bt_ad_set_max_len(struct bt_ad *ad, uint8_t len) { if (!ad || len < BT_AD_MAX_DATA_LEN) return false; ad->max_len = len; return true; } static bool ad_replace_data(struct bt_ad *ad, uint8_t type, const void *data, size_t len); static bool ad_is_type_valid(uint8_t type) { if (type > BT_AD_3D_INFO_DATA && type != BT_AD_MANUFACTURER_DATA) return false; if (type < BT_AD_FLAGS) return false; return true; } struct bt_ad *bt_ad_new_with_data(size_t len, const uint8_t *data) { struct bt_ad *ad; struct iovec iov = { .iov_base = (void *)data, .iov_len = len, }; uint8_t elen; if (data == NULL || !len) return NULL; ad = bt_ad_new(); if (!ad) return NULL; bt_ad_set_max_len(ad, len); while (util_iov_pull_u8(&iov, &elen)) { uint8_t type; void *data; if (elen == 0 || elen > iov.iov_len) break; if (!util_iov_pull_u8(&iov, &type)) goto failed; elen--; if (!ad_is_type_valid(type)) goto failed; data = util_iov_pull_mem(&iov, elen); if (!data) goto failed; if (!ad_replace_data(ad, type, data, elen)) goto failed; } return ad; failed: bt_ad_unref(ad); return NULL; } struct bt_ad *bt_ad_ref(struct bt_ad *ad) { if (!ad) return NULL; ad->ref_count++; return ad; } static void uuid_destroy(void *data) { struct bt_ad_service_data *uuid_data = data; free(uuid_data->data); free(uuid_data); } static bool uuid_data_match(const void *data, const void *elem) { const struct bt_ad_service_data *uuid_data = elem; const bt_uuid_t *uuid = data; return !bt_uuid_cmp(&uuid_data->uuid, uuid); } static void manuf_destroy(void *data) { struct bt_ad_manufacturer_data *manuf = data; free(manuf->data); free(manuf); } static bool manuf_match(const void *data, const void *elem) { const struct bt_ad_manufacturer_data *manuf = elem; uint16_t manuf_id = PTR_TO_UINT(elem); return manuf->manufacturer_id == manuf_id; } static void data_destroy(void *data) { struct bt_ad_data *ad = data; free(ad->data); free(ad); } void bt_ad_unref(struct bt_ad *ad) { if (!ad) return; if (__sync_sub_and_fetch(&ad->ref_count, 1)) return; queue_destroy(ad->service_uuids, free); queue_destroy(ad->manufacturer_data, manuf_destroy); queue_destroy(ad->solicit_uuids, free); queue_destroy(ad->service_data, uuid_destroy); queue_destroy(ad->data, data_destroy); free(ad->name); free(ad); } static bool data_type_match(const void *data, const void *user_data) { const struct bt_ad_data *a = data; const uint8_t type = PTR_TO_UINT(user_data); return a->type == type; } static bool ad_replace_uuid16(struct bt_ad *ad, struct iovec *iov) { uint16_t value; while ((util_iov_pull_le16(iov, &value))) { bt_uuid_t uuid; if (bt_uuid16_create(&uuid, value)) return false; if (bt_ad_has_service_uuid(ad, &uuid)) continue; if (!bt_ad_add_service_uuid(ad, &uuid)) return false; } return true; } static bool ad_replace_uuid32(struct bt_ad *ad, struct iovec *iov) { uint32_t value; while ((util_iov_pull_le32(iov, &value))) { bt_uuid_t uuid; if (bt_uuid32_create(&uuid, value)) return false; if (bt_ad_has_service_uuid(ad, &uuid)) continue; if (!bt_ad_add_service_uuid(ad, &uuid)) return false; } return true; } static bool ad_replace_uuid128(struct bt_ad *ad, struct iovec *iov) { void *data; while ((data = util_iov_pull_mem(iov, 16))) { uint128_t value; bt_uuid_t uuid; bswap_128(data, &value); if (bt_uuid128_create(&uuid, value)) return false; if (bt_ad_has_service_uuid(ad, &uuid)) continue; if (!bt_ad_add_service_uuid(ad, &uuid)) return false; } return true; } static bool ad_replace_name(struct bt_ad *ad, struct iovec *iov) { char utf8_name[HCI_MAX_NAME_LENGTH + 2]; memset(utf8_name, 0, sizeof(utf8_name)); strncpy(utf8_name, (const char *)iov->iov_base, MIN(iov->iov_len, HCI_MAX_NAME_LENGTH)); if (strisutf8(utf8_name, iov->iov_len)) goto done; strtoutf8(utf8_name, iov->iov_len); /* Remove leading and trailing whitespace characters */ strstrip(utf8_name); done: return bt_ad_add_name(ad, utf8_name); } static bool ad_replace_uuid16_data(struct bt_ad *ad, struct iovec *iov) { uint16_t value; bt_uuid_t uuid; if (!util_iov_pull_le16(iov, &value)) return false; if (bt_uuid16_create(&uuid, value)) return false; return bt_ad_add_service_data(ad, &uuid, iov->iov_base, iov->iov_len); } static bool ad_replace_uuid32_data(struct bt_ad *ad, struct iovec *iov) { uint32_t value; bt_uuid_t uuid; if (!util_iov_pull_le32(iov, &value)) return false; if (bt_uuid32_create(&uuid, value)) return false; return bt_ad_add_service_data(ad, &uuid, iov->iov_base, iov->iov_len); } static bool ad_replace_uuid128_data(struct bt_ad *ad, struct iovec *iov) { void *data; uint128_t value; bt_uuid_t uuid; data = util_iov_pull_mem(iov, 16); if (!data) return false; bswap_128(data, &value); if (bt_uuid128_create(&uuid, value)) return false; return bt_ad_add_service_data(ad, &uuid, iov->iov_base, iov->iov_len); } static bool ad_replace_manufacturer_data(struct bt_ad *ad, struct iovec *iov) { uint16_t value; if (!util_iov_pull_le16(iov, &value)) return false; return bt_ad_add_manufacturer_data(ad, value, iov->iov_base, iov->iov_len); } static bool ad_replace_data(struct bt_ad *ad, uint8_t type, const void *data, size_t len) { struct bt_ad_data *new_data; struct iovec iov = { .iov_base = (void *)data, .iov_len = len, }; switch (type) { case BT_AD_UUID16_SOME: case BT_AD_UUID16_ALL: return ad_replace_uuid16(ad, &iov); case BT_AD_UUID32_SOME: case BT_AD_UUID32_ALL: return ad_replace_uuid32(ad, &iov); case BT_AD_UUID128_SOME: case BT_AD_UUID128_ALL: return ad_replace_uuid128(ad, &iov); case BT_AD_NAME_SHORT: case BT_AD_NAME_COMPLETE: return ad_replace_name(ad, &iov); case BT_AD_SERVICE_DATA16: return ad_replace_uuid16_data(ad, &iov); case BT_AD_SERVICE_DATA32: return ad_replace_uuid32_data(ad, &iov); case BT_AD_SERVICE_DATA128: return ad_replace_uuid128_data(ad, &iov); case BT_AD_MANUFACTURER_DATA: return ad_replace_manufacturer_data(ad, &iov); } new_data = queue_find(ad->data, data_type_match, UINT_TO_PTR(type)); if (new_data) { if (new_data->len == len && !memcmp(new_data->data, data, len)) return false; new_data->data = realloc(new_data->data, len); memcpy(new_data->data, data, len); new_data->len = len; return true; } new_data = new0(struct bt_ad_data, 1); new_data->type = type; new_data->data = util_memdup(data, len); if (!new_data->data) { free(new_data); return false; } new_data->len = len; if (queue_push_tail(ad->data, new_data)) return true; data_destroy(new_data); return false; } static size_t uuid_list_length(struct queue *uuid_queue) { bool uuid16_included = false; bool uuid32_included = false; bool uuid128_included = false; size_t length = 0; const struct queue_entry *entry; entry = queue_get_entries(uuid_queue); while (entry) { bt_uuid_t *uuid = entry->data; length += bt_uuid_len(uuid); if (uuid->type == BT_UUID16) uuid16_included = true; else if (uuid->type == BT_UUID32) uuid32_included = true; else uuid128_included = true; entry = entry->next; } if (uuid16_included) length += 2; if (uuid32_included) length += 2; if (uuid128_included) length += 2; return length; } static size_t mfg_data_length(struct queue *manuf_data) { size_t length = 0; const struct queue_entry *entry; entry = queue_get_entries(manuf_data); while (entry) { struct bt_ad_manufacturer_data *data = entry->data; length += 2 + sizeof(uint16_t) + data->len; entry = entry->next; } return length; } static size_t uuid_data_length(struct queue *uuid_data) { size_t length = 0; const struct queue_entry *entry; entry = queue_get_entries(uuid_data); while (entry) { struct bt_ad_service_data *data = entry->data; length += 2 + bt_uuid_len(&data->uuid) + data->len; entry = entry->next; } return length; } static size_t name_length(struct bt_ad *ad, size_t *pos) { size_t len; if (!ad->name) return 0; len = 2 + strlen(ad->name); if (len > ad->max_len - (*pos)) len = ad->max_len - (*pos); return len; } static size_t data_length(struct queue *queue) { size_t length = 0; const struct queue_entry *entry; entry = queue_get_entries(queue); while (entry) { struct bt_ad_data *data = entry->data; length += 1 + 1 + data->len; entry = entry->next; } return length; } size_t bt_ad_length(struct bt_ad *ad) { size_t length; if (!ad) return 0; length = 0; length += uuid_list_length(ad->service_uuids); length += uuid_list_length(ad->solicit_uuids); length += mfg_data_length(ad->manufacturer_data); length += uuid_data_length(ad->service_data); length += name_length(ad, &length); length += ad->appearance != UINT16_MAX ? 4 : 0; length += data_length(ad->data); return length; } static void serialize_uuids(struct queue *uuids, uint8_t uuid_type, uint8_t ad_type, struct iovec *iov) { const struct queue_entry *entry = queue_get_entries(uuids); uint8_t *len = NULL; while (entry) { bt_uuid_t *uuid = entry->data; if (uuid->type == uuid_type) { if (!len) { len = iov->iov_base + iov->iov_len; util_iov_push_u8(iov, 1); util_iov_push_u8(iov, ad_type); } switch (uuid->type) { case BT_UUID16: util_iov_push_le16(iov, uuid->value.u16); *len += 2; break; case BT_UUID32: util_iov_push_le32(iov, uuid->value.u32); *len += 4; break; case BT_UUID128: bt_uuid_to_le(uuid, util_iov_push(iov, 16)); *len += 16; break; case BT_UUID_UNSPEC: break; } } entry = entry->next; } } static void serialize_service_uuids(struct queue *uuids, struct iovec *iov) { serialize_uuids(uuids, BT_UUID16, BT_AD_UUID16_ALL, iov); serialize_uuids(uuids, BT_UUID32, BT_AD_UUID32_ALL, iov); serialize_uuids(uuids, BT_UUID128, BT_AD_UUID128_ALL, iov); } static void serialize_solicit_uuids(struct queue *uuids, struct iovec *iov) { serialize_uuids(uuids, BT_UUID16, BT_AD_SOLICIT16, iov); serialize_uuids(uuids, BT_UUID32, BT_AD_SOLICIT32, iov); serialize_uuids(uuids, BT_UUID128, BT_AD_SOLICIT128, iov); } static void serialize_manuf_data(struct queue *manuf_data, struct iovec *iov) { const struct queue_entry *entry = queue_get_entries(manuf_data); while (entry) { struct bt_ad_manufacturer_data *data = entry->data; util_iov_push_u8(iov, data->len + 2 + 1); util_iov_push_u8(iov, BT_AD_MANUFACTURER_DATA); util_iov_push_le16(iov, data->manufacturer_id); util_iov_push_mem(iov, data->len, data->data); entry = entry->next; } } static void serialize_service_data(struct queue *service_data, struct iovec *iov) { const struct queue_entry *entry = queue_get_entries(service_data); while (entry) { struct bt_ad_service_data *data = entry->data; int uuid_len = bt_uuid_len(&data->uuid); util_iov_push_u8(iov, data->len + uuid_len + 1); switch (uuid_len) { case 2: util_iov_push_u8(iov, BT_AD_SERVICE_DATA16); util_iov_push_le16(iov, data->uuid.value.u16); break; case 4: util_iov_push_u8(iov, BT_AD_SERVICE_DATA32); util_iov_push_le32(iov, data->uuid.value.u32); break; case 16: util_iov_push_u8(iov, BT_AD_SERVICE_DATA128); bt_uuid_to_le(&data->uuid, util_iov_push(iov, uuid_len)); break; } util_iov_push_mem(iov, data->len, data->data); entry = entry->next; } } static void serialize_name(struct bt_ad *ad, struct iovec *iov) { size_t len; uint8_t type = BT_AD_NAME_COMPLETE; if (!ad->name) return; len = strlen(ad->name); if (len > ad->max_len - (iov->iov_len + 2)) { type = BT_AD_NAME_SHORT; len = ad->max_len - (iov->iov_len + 2); } util_iov_push_u8(iov, len + 1); util_iov_push_u8(iov, type); util_iov_push_mem(iov, len, ad->name); } static void serialize_appearance(struct bt_ad *ad, struct iovec *iov) { if (ad->appearance == UINT16_MAX) return; util_iov_push_u8(iov, sizeof(ad->appearance) + 1); util_iov_push_u8(iov, BT_AD_GAP_APPEARANCE); util_iov_push_le16(iov, ad->appearance); } static void serialize_data(struct queue *queue, struct iovec *iov) { const struct queue_entry *entry = queue_get_entries(queue); while (entry) { struct bt_ad_data *data = entry->data; util_iov_push_u8(iov, data->len + 1); util_iov_push_u8(iov, data->type); util_iov_push_mem(iov, data->len, data->data); entry = entry->next; } } uint8_t *bt_ad_generate(struct bt_ad *ad, size_t *length) { struct iovec iov; if (!ad) return NULL; *length = bt_ad_length(ad); if (*length > ad->max_len) return NULL; iov.iov_base = malloc0(*length); if (!iov.iov_base) return NULL; iov.iov_len = 0; serialize_service_uuids(ad->service_uuids, &iov); serialize_solicit_uuids(ad->solicit_uuids, &iov); serialize_manuf_data(ad->manufacturer_data, &iov); serialize_service_data(ad->service_data, &iov); serialize_name(ad, &iov); serialize_appearance(ad, &iov); serialize_data(ad->data, &iov); return iov.iov_base; } bool bt_ad_is_empty(struct bt_ad *ad) { /* If any of the bt_ad fields are non-empty or don't have the default * value, then bt_ad_generate will return a non-empty buffer */ if (!ad->name && ad->appearance == UINT16_MAX && queue_isempty(ad->service_uuids) && queue_isempty(ad->manufacturer_data) && queue_isempty(ad->solicit_uuids) && queue_isempty(ad->service_data) && queue_isempty(ad->data)) { return true; } return false; } static bool queue_add_uuid(struct queue *queue, const bt_uuid_t *uuid) { bt_uuid_t *new_uuid; if (!queue) return false; new_uuid = new0(bt_uuid_t, 1); *new_uuid = *uuid; if (queue_push_tail(queue, new_uuid)) return true; free(new_uuid); return false; } static bool uuid_match(const void *data, const void *elem) { const bt_uuid_t *match_uuid = data; const bt_uuid_t *uuid = elem; return !bt_uuid_cmp(match_uuid, uuid); } static bool queue_remove_uuid(struct queue *queue, bt_uuid_t *uuid) { bt_uuid_t *removed; if (!queue || !uuid) return false; removed = queue_remove_if(queue, uuid_match, uuid); if (removed) { free(removed); return true; } return false; } bool bt_ad_add_service_uuid(struct bt_ad *ad, const bt_uuid_t *uuid) { if (!ad) return false; return queue_add_uuid(ad->service_uuids, uuid); } bool bt_ad_has_service_uuid(struct bt_ad *ad, const bt_uuid_t *uuid) { if (!ad) return false; return queue_find(ad->service_uuids, uuid_match, uuid); } bool bt_ad_remove_service_uuid(struct bt_ad *ad, bt_uuid_t *uuid) { if (!ad) return false; return queue_remove_uuid(ad->service_uuids, uuid); } void bt_ad_clear_service_uuid(struct bt_ad *ad) { if (!ad) return; queue_remove_all(ad->service_uuids, NULL, NULL, free); } static bool manufacturer_id_data_match(const void *data, const void *user_data) { const struct bt_ad_manufacturer_data *m = data; uint16_t id = PTR_TO_UINT(user_data); return m->manufacturer_id == id; } bool bt_ad_add_manufacturer_data(struct bt_ad *ad, uint16_t manufacturer_id, void *data, size_t len) { struct bt_ad_manufacturer_data *new_data; if (!ad) return false; if (len > (ad->max_len - 2 - sizeof(uint16_t))) return false; new_data = queue_find(ad->manufacturer_data, manufacturer_id_data_match, UINT_TO_PTR(manufacturer_id)); if (new_data) { if (new_data->len == len && !memcmp(new_data->data, data, len)) return false; new_data->data = realloc(new_data->data, len); memcpy(new_data->data, data, len); new_data->len = len; return true; } new_data = new0(struct bt_ad_manufacturer_data, 1); new_data->manufacturer_id = manufacturer_id; new_data->data = malloc(len); if (!new_data->data) { free(new_data); return false; } memcpy(new_data->data, data, len); new_data->len = len; if (queue_push_tail(ad->manufacturer_data, new_data)) return true; manuf_destroy(new_data); return false; } static bool manufacturer_data_match(const void *data, const void *user_data) { const struct bt_ad_manufacturer_data *m1 = data; const struct bt_ad_manufacturer_data *m2 = user_data; if (m1->manufacturer_id != m2->manufacturer_id) return false; if (m1->len != m2->len) return false; return !memcmp(m1->data, m2->data, m1->len); } bool bt_ad_has_manufacturer_data(struct bt_ad *ad, const struct bt_ad_manufacturer_data *data) { if (!ad) return false; if (!data) return !queue_isempty(ad->manufacturer_data); return queue_find(ad->manufacturer_data, manufacturer_data_match, data); } void bt_ad_foreach_manufacturer_data(struct bt_ad *ad, bt_ad_func_t func, void *user_data) { if (!ad) return; queue_foreach(ad->manufacturer_data, func, user_data); } bool bt_ad_remove_manufacturer_data(struct bt_ad *ad, uint16_t manufacturer_id) { struct bt_ad_manufacturer_data *data; if (!ad) return false; data = queue_remove_if(ad->manufacturer_data, manuf_match, UINT_TO_PTR(manufacturer_id)); if (!data) return false; manuf_destroy(data); return true; } void bt_ad_clear_manufacturer_data(struct bt_ad *ad) { if (!ad) return; queue_remove_all(ad->manufacturer_data, NULL, NULL, manuf_destroy); } bool bt_ad_add_solicit_uuid(struct bt_ad *ad, const bt_uuid_t *uuid) { if (!ad) return false; return queue_add_uuid(ad->solicit_uuids, uuid); } bool bt_ad_remove_solicit_uuid(struct bt_ad *ad, bt_uuid_t *uuid) { if (!ad) return false; return queue_remove_uuid(ad->solicit_uuids, uuid); } void bt_ad_clear_solicit_uuid(struct bt_ad *ad) { if (!ad) return; queue_remove_all(ad->solicit_uuids, NULL, NULL, free); } static bool service_uuid_match(const void *data, const void *user_data) { const struct bt_ad_service_data *s = data; const bt_uuid_t *uuid = user_data; return !bt_uuid_cmp(&s->uuid, uuid); } bool bt_ad_add_service_data(struct bt_ad *ad, const bt_uuid_t *uuid, void *data, size_t len) { struct bt_ad_service_data *new_data; if (!ad) return false; if (len > (ad->max_len - 2 - (size_t)bt_uuid_len(uuid))) return false; new_data = queue_find(ad->service_data, service_uuid_match, uuid); if (new_data) { if (new_data->len == len && !memcmp(new_data->data, data, len)) return false; new_data->data = realloc(new_data->data, len); memcpy(new_data->data, data, len); new_data->len = len; return true; } new_data = new0(struct bt_ad_service_data, 1); new_data->uuid = *uuid; new_data->data = malloc(len); if (!new_data->data) { free(new_data); return false; } memcpy(new_data->data, data, len); new_data->len = len; if (queue_push_tail(ad->service_data, new_data)) return true; uuid_destroy(new_data); return false; } static bool service_data_match(const void *data, const void *user_data) { const struct bt_ad_service_data *s1 = data; const struct bt_ad_service_data *s2 = user_data; if (bt_uuid_cmp(&s1->uuid, &s2->uuid)) return false; if (s1->len != s2->len) return false; return !memcmp(s1->data, s2->data, s1->len); } bool bt_ad_has_service_data(struct bt_ad *ad, const struct bt_ad_service_data *data) { if (!ad) return false; if (!data) return !queue_isempty(ad->service_data); return queue_find(ad->service_data, service_data_match, data); } void bt_ad_foreach_service_data(struct bt_ad *ad, bt_ad_func_t func, void *user_data) { if (!ad) return; queue_foreach(ad->service_data, func, user_data); } bool bt_ad_remove_service_data(struct bt_ad *ad, bt_uuid_t *uuid) { struct bt_ad_service_data *data; if (!ad) return false; data = queue_remove_if(ad->service_data, uuid_data_match, uuid); if (!data) return false; uuid_destroy(data); return true; } void bt_ad_clear_service_data(struct bt_ad *ad) { if (!ad) return; queue_remove_all(ad->service_data, NULL, NULL, uuid_destroy); } bool bt_ad_add_name(struct bt_ad *ad, const char *name) { if (!ad) return false; free(ad->name); ad->name = strdup(name); return true; } const char *bt_ad_get_name(struct bt_ad *ad) { if (!ad) return NULL; return ad->name; } void bt_ad_clear_name(struct bt_ad *ad) { if (!ad) return; free(ad->name); ad->name = NULL; } bool bt_ad_add_appearance(struct bt_ad *ad, uint16_t appearance) { if (!ad) return false; ad->appearance = appearance; return true; } void bt_ad_clear_appearance(struct bt_ad *ad) { if (!ad) return; ad->appearance = UINT16_MAX; } bool bt_ad_add_flags(struct bt_ad *ad, uint8_t *flags, size_t len) { if (!ad) return false; /* TODO: Add table to check other flags */ if (len > 1 || flags[0] & 0xe0) return false; return ad_replace_data(ad, BT_AD_FLAGS, flags, len); } uint8_t bt_ad_get_flags(struct bt_ad *ad) { struct bt_ad_data *data; if (!ad) return 0; data = queue_find(ad->data, data_type_match, UINT_TO_PTR(BT_AD_FLAGS)); if (!data || data->len != 1) return 0; return data->data[0]; } bool bt_ad_has_flags(struct bt_ad *ad) { struct bt_ad_data *data; if (!ad) return false; data = queue_find(ad->data, data_type_match, UINT_TO_PTR(BT_AD_FLAGS)); if (!data) return false; return true; } void bt_ad_clear_flags(struct bt_ad *ad) { if (!ad) return; queue_remove_all(ad->data, data_type_match, UINT_TO_PTR(BT_AD_FLAGS), data_destroy); } static uint8_t type_reject_list[] = { BT_AD_FLAGS, BT_AD_UUID16_SOME, BT_AD_UUID16_ALL, BT_AD_UUID32_SOME, BT_AD_UUID32_ALL, BT_AD_UUID128_SOME, BT_AD_UUID128_ALL, BT_AD_NAME_SHORT, BT_AD_NAME_COMPLETE, BT_AD_TX_POWER, BT_AD_CLASS_OF_DEV, BT_AD_SSP_HASH, BT_AD_SSP_RANDOMIZER, BT_AD_DEVICE_ID, BT_AD_SMP_TK, BT_AD_SMP_OOB_FLAGS, BT_AD_PERIPHERAL_CONN_INTERVAL, BT_AD_SOLICIT16, BT_AD_SOLICIT128, BT_AD_SERVICE_DATA16, BT_AD_PUBLIC_ADDRESS, BT_AD_RANDOM_ADDRESS, BT_AD_GAP_APPEARANCE, BT_AD_ADVERTISING_INTERVAL, BT_AD_LE_DEVICE_ADDRESS, BT_AD_LE_ROLE, BT_AD_SSP_HASH_P256, BT_AD_SSP_RANDOMIZER_P256, BT_AD_SOLICIT32, BT_AD_SERVICE_DATA32, BT_AD_SERVICE_DATA128, BT_AD_LE_SC_CONFIRM_VALUE, BT_AD_LE_SC_RANDOM_VALUE, BT_AD_LE_SUPPORTED_FEATURES, BT_AD_CHANNEL_MAP_UPDATE_IND, BT_AD_MESH_PROV, BT_AD_MESH_DATA, BT_AD_MESH_BEACON, BT_AD_3D_INFO_DATA, BT_AD_MANUFACTURER_DATA, }; bool bt_ad_add_data(struct bt_ad *ad, uint8_t type, void *data, size_t len) { size_t i; if (!ad) return false; if (len > (size_t)(ad->max_len - 2)) return false; for (i = 0; i < sizeof(type_reject_list); i++) { if (type == type_reject_list[i]) return false; } return ad_replace_data(ad, type, data, len); } static bool data_match(const void *data, const void *user_data) { const struct bt_ad_data *d1 = data; const struct bt_ad_data *d2 = user_data; if (!d2) return true; if (d1->type != d2->type) return false; if (!d2->len && !d2->data) return true; if (d1->len != d2->len) return false; return !memcmp(d1->data, d2->data, d1->len); } struct bt_ad_data *bt_ad_has_data(struct bt_ad *ad, const struct bt_ad_data *data) { if (!ad) return false; return queue_find(ad->data, data_match, data); } void bt_ad_foreach_data(struct bt_ad *ad, bt_ad_func_t func, void *user_data) { if (!ad) return; queue_foreach(ad->data, func, user_data); } bool bt_ad_remove_data(struct bt_ad *ad, uint8_t type) { struct bt_ad_data *data; if (!ad) return false; data = queue_remove_if(ad->data, data_type_match, UINT_TO_PTR(type)); if (!data) return false; data_destroy(data); return true; } void bt_ad_clear_data(struct bt_ad *ad) { if (!ad) return; queue_remove_all(ad->data, NULL, NULL, data_destroy); } int8_t bt_ad_get_tx_power(struct bt_ad *ad) { struct bt_ad_data *data; if (!ad) return 0; data = queue_find(ad->data, data_type_match, UINT_TO_PTR(BT_AD_TX_POWER)); if (!data || data->len != 1) return 127; return data->data[0]; } struct bt_ad_pattern *bt_ad_pattern_new(uint8_t type, size_t offset, size_t len, const uint8_t *data) { struct bt_ad_pattern *pattern; if (!data || !len || offset >= BT_AD_MAX_DATA_LEN || len > BT_AD_MAX_DATA_LEN || offset + len > BT_AD_MAX_DATA_LEN) { return NULL; } if (!ad_is_type_valid(type)) return NULL; pattern = new0(struct bt_ad_pattern, 1); if (!pattern) return NULL; pattern->len = len; pattern->type = type; pattern->offset = offset; memcpy(pattern->data, data, len); return pattern; } static bool match_manufacturer(const void *data, const void *user_data) { const struct bt_ad_manufacturer_data *manufacturer_data = data; const struct pattern_match_info *info = user_data; const struct bt_ad_pattern *pattern; uint8_t all_data[BT_EA_MAX_DATA_LEN]; if (!manufacturer_data || !info) return false; if (info->matched_pattern) return false; pattern = info->current_pattern; if (!pattern || pattern->type != BT_AD_MANUFACTURER_DATA) return false; /* Take the manufacturer ID into account */ if (manufacturer_data->len + 2 < pattern->offset + pattern->len) return false; memcpy(&all_data[0], &manufacturer_data->manufacturer_id, 2); memcpy(&all_data[2], manufacturer_data->data, manufacturer_data->len); if (!memcmp(all_data + pattern->offset, pattern->data, pattern->len)) { return true; } return false; } static bool match_service(const void *data, const void *user_data) { const struct bt_ad_service_data *service_data = data; const struct pattern_match_info *info = user_data; const struct bt_ad_pattern *pattern; if (!service_data || !info) return false; if (info->matched_pattern) return false; pattern = info->current_pattern; if (!pattern) return false; switch (pattern->type) { case BT_AD_SERVICE_DATA16: case BT_AD_SERVICE_DATA32: case BT_AD_SERVICE_DATA128: break; default: return false; } if (service_data->len < pattern->offset + pattern->len) return false; if (!memcmp(service_data->data + pattern->offset, pattern->data, pattern->len)) { return true; } return false; } static bool match_ad_data(const void *data, const void *user_data) { const struct bt_ad_data *ad_data = data; const struct pattern_match_info *info = user_data; const struct bt_ad_pattern *pattern; if (!ad_data || !info) return false; if (info->matched_pattern) return false; pattern = info->current_pattern; if (!pattern || ad_data->type != pattern->type) return false; if (ad_data->len < pattern->offset + pattern->len) return false; if (!memcmp(ad_data->data + pattern->offset, pattern->data, pattern->len)) { return true; } return false; } static void pattern_match(void *data, void *user_data) { struct bt_ad_pattern *pattern = data; struct pattern_match_info *info = user_data; struct bt_ad *ad; void *matched = NULL; if (!pattern || !info) return; if (info->matched_pattern) return; info->current_pattern = pattern; ad = info->ad; if (!ad) return; switch (pattern->type) { case BT_AD_MANUFACTURER_DATA: matched = queue_find(ad->manufacturer_data, match_manufacturer, user_data); break; case BT_AD_SERVICE_DATA16: case BT_AD_SERVICE_DATA32: case BT_AD_SERVICE_DATA128: matched = queue_find(ad->service_data, match_service, user_data); break; default: matched = queue_find(ad->data, match_ad_data, user_data); break; } if (matched) info->matched_pattern = info->current_pattern; } struct bt_ad_pattern *bt_ad_pattern_match(struct bt_ad *ad, struct queue *patterns) { struct pattern_match_info info; if (!ad || queue_isempty(patterns)) return NULL; info.ad = ad; info.matched_pattern = NULL; info.current_pattern = NULL; queue_foreach(patterns, pattern_match, &info); return info.matched_pattern; } bluez-5.87/src/shared/PaxHeaders/pcap.c0000644000000000000000000000005014015011623014770 xustar0020 atime=1783092901 20 ctime=1783094099 bluez-5.87/src/shared/pcap.c0000644000000000000000000001006214015011623014450 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include "src/shared/util.h" #include "src/shared/pcap.h" struct pcap_hdr { uint32_t magic_number; /* magic number */ uint16_t version_major; /* major version number */ uint16_t version_minor; /* minor version number */ int32_t thiszone; /* GMT to local correction */ uint32_t sigfigs; /* accuracy of timestamps */ uint32_t snaplen; /* max length of captured packets, in octets */ uint32_t network; /* data link type */ } __attribute__ ((packed)); #define PCAP_HDR_SIZE (sizeof(struct pcap_hdr)) struct pcap_pkt { uint32_t ts_sec; /* timestamp seconds */ uint32_t ts_usec; /* timestamp microseconds */ uint32_t incl_len; /* number of octets of packet saved in file */ uint32_t orig_len; /* actual length of packet */ } __attribute__ ((packed)); #define PCAP_PKT_SIZE (sizeof(struct pcap_pkt)) struct pcap_ppi { uint8_t version; /* version, currently 0 */ uint8_t flags; /* flags */ uint16_t len; /* length of entire message */ uint32_t dlt; /* data link type */ } __attribute__ ((packed)); #define PCAP_PPI_SIZE (sizeof(struct pcap_ppi)) struct pcap { int ref_count; int fd; uint32_t type; uint32_t snaplen; }; struct pcap *pcap_open(const char *path) { struct pcap *pcap; struct pcap_hdr hdr; ssize_t len; pcap = calloc(1, sizeof(*pcap)); if (!pcap) return NULL; pcap->fd = open(path, O_RDONLY | O_CLOEXEC); if (pcap->fd < 0) { free(pcap); return NULL; } len = read(pcap->fd, &hdr, PCAP_HDR_SIZE); if (len < 0 || len != PCAP_HDR_SIZE) goto failed; if (hdr.magic_number != 0xa1b2c3d4) goto failed; if (hdr.version_major != 2 || hdr.version_minor != 4) goto failed; pcap->snaplen = hdr.snaplen; pcap->type = hdr.network; return pcap_ref(pcap); failed: close(pcap->fd); free(pcap); return NULL; } struct pcap *pcap_ref(struct pcap *pcap) { if (!pcap) return NULL; __sync_fetch_and_add(&pcap->ref_count, 1); return pcap; } void pcap_unref(struct pcap *pcap) { if (!pcap) return; if (__sync_sub_and_fetch(&pcap->ref_count, 1)) return; if (pcap->fd >= 0) close(pcap->fd); free(pcap); } uint32_t pcap_get_type(struct pcap *pcap) { if (!pcap) return PCAP_TYPE_INVALID; return pcap->type; } uint32_t pcap_get_snaplen(struct pcap *pcap) { if (!pcap) return 0; return pcap->snaplen; } bool pcap_read(struct pcap *pcap, struct timeval *tv, void *data, uint32_t size, uint32_t *len) { struct pcap_pkt pkt; uint32_t toread; ssize_t bytes_read; if (!pcap) return false; bytes_read = read(pcap->fd, &pkt, PCAP_PKT_SIZE); if (bytes_read != PCAP_PKT_SIZE) return false; if (pkt.incl_len > size) toread = size; else toread = pkt.incl_len; bytes_read = read(pcap->fd, data, toread); if (bytes_read < 0) return false; if (tv) { tv->tv_sec = pkt.ts_sec; tv->tv_usec = pkt.ts_usec; } if (len) *len = toread; return true; } bool pcap_read_ppi(struct pcap *pcap, struct timeval *tv, uint32_t *type, void *data, uint32_t size, uint32_t *offset, uint32_t *len) { struct pcap_pkt pkt; struct pcap_ppi ppi; uint16_t pph_len; uint32_t toread; ssize_t bytes_read; if (!pcap) return false; bytes_read = read(pcap->fd, &pkt, PCAP_PKT_SIZE); if (bytes_read != PCAP_PKT_SIZE) return false; if (pkt.incl_len > size) toread = size; else toread = pkt.incl_len; bytes_read = read(pcap->fd, &ppi, PCAP_PPI_SIZE); if (bytes_read != PCAP_PPI_SIZE) return false; if (ppi.flags) return false; pph_len = le16_to_cpu(ppi.len); if (pph_len < PCAP_PPI_SIZE) return false; bytes_read = read(pcap->fd, data, toread - PCAP_PPI_SIZE); if (bytes_read < 0) return false; if (tv) { tv->tv_sec = pkt.ts_sec; tv->tv_usec = pkt.ts_usec; } if (type) *type = le32_to_cpu(ppi.dlt); if (offset) *offset = pph_len - PCAP_PPI_SIZE; if (len) *len = toread - pph_len; return true; } bluez-5.87/src/shared/PaxHeaders/ascs.h0000644000000000000000000000005015142177620015017 xustar0020 atime=1783092931 20 ctime=1783094100 bluez-5.87/src/shared/ascs.h0000644000000000000000000001123615142177620014503 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. * */ /* Response Status Code */ #define BT_ASCS_RSP_SUCCESS 0x00 #define BT_ASCS_RSP_NOT_SUPPORTED 0x01 #define BT_ASCS_RSP_TRUNCATED 0x02 #define BT_ASCS_RSP_INVALID_ASE 0x03 #define BT_ASCS_RSP_INVALID_ASE_STATE 0x04 #define BT_ASCS_RSP_INVALID_DIR 0x05 #define BT_ASCS_RSP_CAP_UNSUPPORTED 0x06 #define BT_ASCS_RSP_CONF_UNSUPPORTED 0x07 #define BT_ASCS_RSP_CONF_REJECTED 0x08 #define BT_ASCS_RSP_CONF_INVALID 0x09 #define BT_ASCS_RSP_METADATA_UNSUPPORTED 0x0a #define BT_ASCS_RSP_METADATA_REJECTED 0x0b #define BT_ASCS_RSP_METADATA_INVALID 0x0c #define BT_ASCS_RSP_NO_MEM 0x0d #define BT_ASCS_RSP_UNSPECIFIED 0x0e /* Response Reasons */ #define BT_ASCS_REASON_NONE 0x00 #define BT_ASCS_REASON_CODEC 0x01 #define BT_ASCS_REASON_CODEC_DATA 0x02 #define BT_ASCS_REASON_INTERVAL 0x03 #define BT_ASCS_REASON_FRAMING 0x04 #define BT_ASCS_REASON_PHY 0x05 #define BT_ASCS_REASON_SDU 0x06 #define BT_ASCS_REASON_RTN 0x07 #define BT_ASCS_REASON_LATENCY 0x08 #define BT_ASCS_REASON_PD 0x09 #define BT_ASCS_REASON_CIS 0x0a /* Transport QoS Packing */ #define BT_ASCS_QOS_PACKING_SEQ 0x00 #define BT_ASCS_QOS_PACKING_INT 0x01 /* Transport QoS Framing */ #define BT_ASCS_QOS_FRAMING_UNFRAMED 0x00 #define BT_ASCS_QOS_FRAMING_FRAMED 0x01 /* ASE characteristic states */ #define BT_ASCS_ASE_STATE_IDLE 0x00 #define BT_ASCS_ASE_STATE_CONFIG 0x01 #define BT_ASCS_ASE_STATE_QOS 0x02 #define BT_ASCS_ASE_STATE_ENABLING 0x03 #define BT_ASCS_ASE_STATE_STREAMING 0x04 #define BT_ASCS_ASE_STATE_DISABLING 0x05 #define BT_ASCS_ASE_STATE_RELEASING 0x06 struct bt_ascs_ase_rsp { uint8_t ase; uint8_t code; uint8_t reason; } __packed; struct bt_ascs_cp_rsp { uint8_t op; uint8_t num_ase; struct bt_ascs_ase_rsp rsp[0]; } __packed; struct bt_ascs_ase_status { uint8_t id; uint8_t state; uint8_t params[0]; } __packed; /* ASE_State = 0x01 (Codec Configured), defined in Table 4.7. */ struct bt_ascs_ase_status_config { uint8_t framing; uint8_t phys; uint8_t rtn; uint16_t latency; uint8_t pd_min[3]; uint8_t pd_max[3]; uint8_t ppd_min[3]; uint8_t ppd_max[3]; struct bt_bap_codec codec; uint8_t cc_len; /* LTV-formatted Codec-Specific Configuration */ struct bt_ltv cc[0]; } __packed; /* ASE_State = 0x02 (QoS Configured), defined in Table 4.8. */ struct bt_ascs_ase_status_qos { uint8_t cig_id; uint8_t cis_id; uint8_t interval[3]; uint8_t framing; uint8_t phys; uint16_t sdu; uint8_t rtn; uint16_t latency; uint8_t pd[3]; } __packed; /* ASE_Status = 0x03 (Enabling), 0x04 (Streaming), or 0x05 (Disabling) * defined in Table 4.9. */ struct bt_ascs_ase_status_metadata { uint8_t cig_id; uint8_t cis_id; uint8_t len; uint8_t data[0]; } __packed; struct bt_ascs_ase_hdr { uint8_t op; uint8_t num; } __packed; #define BT_ASCS_CONFIG 0x01 #define BT_ASCS_CONFIG_LATENCY_LOW 0x01 #define BT_ASCS_CONFIG_LATENCY_MEDIUM 0x02 #define BT_ASCS_CONFIG_LATENCY_HIGH 0x03 #define BT_ASCS_CONFIG_PHY_LE_1M 0x01 #define BT_ASCS_CONFIG_PHY_LE_2M 0x02 #define BT_ASCS_CONFIG_PHY_LE_CODED 0x03 struct bt_ascs_codec_config { uint8_t len; uint8_t type; uint8_t data[0]; } __packed; struct bt_ascs_config { uint8_t ase; /* ASE ID */ uint8_t latency; /* Target Latency */ uint8_t phy; /* Target PHY */ struct bt_bap_codec codec; /* Codec ID */ uint8_t cc_len; /* Codec Specific Config Length */ /* LTV-formatted Codec-Specific Configuration */ struct bt_ascs_codec_config cc[0]; } __packed; #define BT_ASCS_QOS 0x02 struct bt_ascs_qos { uint8_t ase; /* ASE ID */ uint8_t cig; /* CIG ID*/ uint8_t cis; /* CIG ID*/ uint8_t interval[3]; /* Frame interval */ uint8_t framing; /* Frame framing */ uint8_t phys; /* PHY */ uint16_t sdu; /* Maximum SDU Size */ uint8_t rtn; /* Retransmission Effort */ uint16_t latency; /* Transport Latency */ uint8_t pd[3]; /* Presentation Delay */ } __packed; #define BT_ASCS_ENABLE 0x03 struct bt_ascs_metadata { uint8_t ase; /* ASE ID */ uint8_t len; /* Metadata length */ uint8_t data[0]; /* LTV-formatted Metadata */ } __packed; struct bt_ascs_enable { struct bt_ascs_metadata meta; /* Metadata */ } __packed; #define BT_ASCS_START 0x04 struct bt_ascs_start { uint8_t ase; /* ASE ID */ } __packed; #define BT_ASCS_DISABLE 0x05 struct bt_ascs_disable { uint8_t ase; /* ASE ID */ } __packed; #define BT_ASCS_STOP 0x06 struct bt_ascs_stop { uint8_t ase; /* ASE ID */ } __packed; #define BT_ASCS_METADATA 0x07 #define BT_ASCS_RELEASE 0x08 struct bt_ascs_release { uint8_t ase; /* ASE ID */ } __packed; bluez-5.87/src/shared/PaxHeaders/micp.h0000644000000000000000000000005014505354670015022 xustar0020 atime=1783092955 20 ctime=1783094100 bluez-5.87/src/shared/micp.h0000644000000000000000000000427514505354670014513 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2023 NXP Semiconductors. All rights reserved. * */ #include #include #include "src/shared/io.h" #include "src/shared/gatt-client.h" struct bt_mics; struct bt_micp; typedef void (*bt_micp_ready_func_t)(struct bt_micp *micp, void *user_data); typedef void (*bt_micp_destroy_func_t)(void *user_data); typedef void (*bt_micp_debug_func_t)(const char *str, void *user_data); typedef void (*bt_micp_func_t)(struct bt_micp *micp, void *user_data); struct bt_micp_db { struct gatt_db *db; struct bt_mics *mics; }; struct bt_mics { struct bt_micp_db *mdb; uint8_t mute_stat; struct gatt_db_attribute *service; struct gatt_db_attribute *ms; struct gatt_db_attribute *ms_ccc; }; struct bt_micp { int ref_count; struct bt_micp_db *ldb; struct bt_micp_db *rdb; struct bt_gatt_client *client; struct bt_att *att; unsigned int mute_id; unsigned int idle_id; uint8_t mute; struct queue *notify; struct queue *pending; struct queue *ready_cbs; bt_micp_debug_func_t debug_func; bt_micp_destroy_func_t debug_destroy; void *debug_data; void *user_data; }; struct bt_micp *bt_micp_ref(struct bt_micp *micp); void bt_micp_unref(struct bt_micp *micp); void bt_micp_add_db(struct gatt_db *db); bool bt_micp_attach(struct bt_micp *micp, struct bt_gatt_client *client); void bt_micp_detach(struct bt_micp *micp); bool bt_micp_set_debug(struct bt_micp *micp, bt_micp_debug_func_t func, void *user_data, bt_micp_destroy_func_t destroy); struct bt_att *bt_micp_get_att(struct bt_micp *micp); bool bt_micp_set_user_data(struct bt_micp *micp, void *user_data); /* session related functions */ unsigned int bt_micp_register(bt_micp_func_t attached, bt_micp_func_t detached, void *user_data); unsigned int bt_micp_ready_register(struct bt_micp *micp, bt_micp_ready_func_t func, void *user_data, bt_micp_destroy_func_t destroy); bool bt_micp_ready_unregister(struct bt_micp *micp, unsigned int id); bool bt_micp_unregister(unsigned int id); struct bt_micp *bt_micp_new(struct gatt_db *ldb, struct gatt_db *rdb); struct bt_mics *micp_get_mics(struct bt_micp *micp); bluez-5.87/src/shared/PaxHeaders/ecc.h0000644000000000000000000000005014015011623014604 xustar0020 atime=1783092885 20 ctime=1783094099 bluez-5.87/src/shared/ecc.h0000644000000000000000000000340314015011623014265 0ustar00rootroot/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (c) 2013, Kenneth MacKay * All rights reserved. * */ #include #include /* Create a public key from a private key. * * Inputs: * private_key - Your private key. * * Outputs: * public_key - Will be filled in with the public key. * * Returns true if the public key was generated successfully, false * if an error occurred. The keys are with the LSB first. */ bool ecc_make_public_key(const uint8_t private_key[32], uint8_t public_key[64]); /* Create a public/private key pair. * * Outputs: * public_key - Will be filled in with the public key. * private_key - Will be filled in with the private key. * * Returns true if the key pair was generated successfully, false * if an error occurred. The keys are with the LSB first. */ bool ecc_make_key(uint8_t public_key[64], uint8_t private_key[32]); /* Check to see if a public key is valid. * * Inputs: * public_key - The public key to check. * * Returns true if the public key is valid, false if it is invalid. */ bool ecc_valid_public_key(const uint8_t public_key[64]); /* Compute a shared secret given your secret key and someone else's * public key. * Note: It is recommended that you hash the result of ecdh_shared_secret * before using it for symmetric encryption or HMAC. * * Inputs: * public_key - The public key of the remote party. * private_key - Your private key. * * Outputs: * secret - Will be filled in with the shared secret value. * * Returns true if the shared secret was generated successfully, false * if an error occurred. Both input and output parameters are with the * LSB first. */ bool ecdh_shared_secret(const uint8_t public_key[64], const uint8_t private_key[32], uint8_t secret[32]); bluez-5.87/src/shared/PaxHeaders/mcp.h0000644000000000000000000000005015142177620014645 xustar0020 atime=1783092945 20 ctime=1783094100 bluez-5.87/src/shared/mcp.h0000644000000000000000000001326715142177620014337 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2020 Intel Corporation. All rights reserved. * */ #include #include struct bt_mcp; struct bt_mcs; /* * Media Control Client */ struct bt_mcp_callback { /* New player seen */ void (*ccid)(void *data, uint8_t ccid, bool gmcs); /* Client command complete */ void (*complete)(void *data, unsigned int id, uint8_t status); /* Attach complete */ void (*ready)(void *data); /* Debug message */ void (*debug)(void *data, const char *str); /* mcp destroyed (no further callbacks) */ void (*destroy)(void *data); }; struct bt_mcp_listener_callback { /* Value notification */ void (*media_player_name)(void *data, const uint8_t *value, uint16_t length); void (*track_changed)(void *data); void (*track_title)(void *data, const uint8_t *value, uint16_t length); void (*track_duration)(void *data, int32_t duration_centisecond); void (*track_position)(void *data, int32_t position_centisecond); void (*playback_speed)(void *data, int8_t log2_speed); void (*seeking_speed)(void *data, int8_t log2_speed); void (*playing_order)(void *data, uint8_t order); void (*media_state)(void *data, uint8_t state); /* TODO: OTS */ /* Listener destroyed (no further callbacks) */ void (*destroy)(void *data); }; unsigned int bt_mcp_play(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_pause(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_fast_rewind(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_fast_forward(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_stop(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_move_relative(struct bt_mcp *mcp, uint8_t ccid, int32_t offset); unsigned int bt_mcp_previous_segment(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_next_segment(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_first_segment(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_last_segment(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_goto_segment(struct bt_mcp *mcp, uint8_t ccid, int32_t n); unsigned int bt_mcp_previous_track(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_next_track(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_first_track(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_last_track(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_goto_track(struct bt_mcp *mcp, uint8_t ccid, int32_t n); unsigned int bt_mcp_previous_group(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_next_group(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_first_group(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_last_group(struct bt_mcp *mcp, uint8_t ccid); unsigned int bt_mcp_goto_group(struct bt_mcp *mcp, uint8_t ccid, int32_t n); unsigned int bt_mcp_set_track_position(struct bt_mcp *mcp, uint8_t ccid, int32_t position); unsigned int bt_mcp_set_playback_speed(struct bt_mcp *mcp, uint8_t ccid, int8_t speed); unsigned int bt_mcp_set_playing_order(struct bt_mcp *mcp, uint8_t ccid, uint8_t order); uint16_t bt_mcp_get_supported_playing_order(struct bt_mcp *mcp, uint8_t ccid); uint32_t bt_mcp_get_supported_commands(struct bt_mcp *mcp, uint8_t ccid); bool bt_mcp_add_listener(struct bt_mcp *mcp, uint8_t ccid, const struct bt_mcp_listener_callback *cb, void *user_data); struct bt_mcp *bt_mcp_attach(struct bt_gatt_client *client, bool gmcs, const struct bt_mcp_callback *cb, void *user_data); void bt_mcp_detach(struct bt_mcp *mcp); /* * Media Control Server */ struct bt_mcs_callback { /* Value requests */ void (*media_player_name)(void *data, struct iovec *buf, size_t size); void (*track_title)(void *data, struct iovec *buf, size_t size); int32_t (*track_duration)(void *data); int32_t (*track_position)(void *data); int8_t (*playback_speed)(void *data); int8_t (*seeking_speed)(void *data); uint8_t (*playing_order)(void *data); uint16_t (*playing_order_supported)(void *data); uint32_t (*media_cp_op_supported)(void *data); /* TODO: OTS */ /* Set value notification */ bool (*set_track_position)(void *data, int32_t value); bool (*set_playback_speed)(void *data, int8_t value); bool (*set_playing_order)(void *data, uint8_t value); /* Command notification */ bool (*play)(void *data); bool (*pause)(void *data); bool (*fast_rewind)(void *data); bool (*fast_forward)(void *data); bool (*stop)(void *data); bool (*move_relative)(void *data, int32_t offset); bool (*previous_segment)(void *data); bool (*next_segment)(void *data); bool (*first_segment)(void *data); bool (*last_segment)(void *data); bool (*goto_segment)(void *data, int32_t n); bool (*previous_track)(void *data); bool (*next_track)(void *data); bool (*first_track)(void *data); bool (*last_track)(void *data); bool (*goto_track)(void *data, int32_t n); bool (*previous_group)(void *data); bool (*next_group)(void *data); bool (*first_group)(void *data); bool (*last_group)(void *data); bool (*goto_group)(void *data, int32_t n); /* Debug message */ void (*debug)(void *data, const char *str); /* Player destroyed (no further callbacks) */ void (*destroy)(void *data); }; void bt_mcs_set_media_state(struct bt_mcs *mcs, uint8_t state); uint8_t bt_mcs_get_media_state(struct bt_mcs *mcs); void bt_mcs_changed(struct bt_mcs *mcs, uint16_t chrc_uuid); uint8_t bt_mcs_get_ccid(struct bt_mcs *mcs); struct bt_mcs *bt_mcs_register(struct gatt_db *db, bool is_gmcs, const struct bt_mcs_callback *cb, void *user_data); void bt_mcs_unregister(struct bt_mcs *mcs); void bt_mcs_unregister_all(struct gatt_db *db); /* For tests: */ void bt_mcs_test_util_reset_ccid(void); struct bt_gatt_client *bt_mcp_test_util_get_client(struct bt_mcp *mcp); bluez-5.87/src/shared/PaxHeaders/battery.h0000644000000000000000000000005015110075565015540 xustar0020 atime=1783092969 20 ctime=1783094100 bluez-5.87/src/shared/battery.h0000644000000000000000000000067315110075565015227 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2025 Open Mobile Platform LLC * * */ #include #define LAST_CHARGES_SIZE 8 #define MAX_CHARGE_STEP 5 struct bt_battery; struct bt_battery *bt_battery_new(void); void bt_battery_free(struct bt_battery *battery); uint8_t bt_battery_charge(struct bt_battery *battery, uint8_t percentage); bluez-5.87/src/shared/PaxHeaders/uhid.c0000644000000000000000000000005015200607060015001 xustar0020 atime=1783092683 20 ctime=1783094099 bluez-5.87/src/shared/uhid.c0000644000000000000000000002764415200607060014477 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include "src/shared/io.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/uhid.h" #define UHID_DEVICE_FILE "/dev/uhid" struct uhid_replay { bool active; struct queue *out; struct queue *in; struct queue *rout; struct queue *rin; }; struct bt_uhid { int ref_count; struct io *io; unsigned int notify_id; bool notifying; struct queue *notify_list; struct queue *input; uint8_t type; bool created; unsigned int start_id; bool started; struct uhid_replay *replay; }; struct uhid_notify { unsigned int id; uint32_t event; bt_uhid_callback_t func; void *user_data; bool removed; }; static void uhid_replay_free(struct uhid_replay *replay) { if (!replay) return; queue_destroy(replay->rin, NULL); queue_destroy(replay->in, free); queue_destroy(replay->rout, NULL); queue_destroy(replay->out, free); free(replay); } static void uhid_free(struct bt_uhid *uhid) { if (uhid->io) io_destroy(uhid->io); if (uhid->notify_list) queue_destroy(uhid->notify_list, free); if (uhid->input) queue_destroy(uhid->input, free); uhid_replay_free(uhid->replay); free(uhid); } static void notify_handler(void *data, void *user_data) { struct uhid_notify *notify = data; struct uhid_event *ev = user_data; if (notify->event != ev->type) return; if (notify->func) notify->func(ev, notify->user_data); } static struct uhid_replay *uhid_replay_new(void) { struct uhid_replay *replay = new0(struct uhid_replay, 1); replay->out = queue_new(); replay->in = queue_new(); return replay; } static int bt_uhid_record(struct bt_uhid *uhid, bool input, struct uhid_event *ev) { if (!uhid) return -EINVAL; /* Capture input events in replay mode and send the next replay event */ if (uhid->replay && uhid->replay->active && input) { queue_pop_head(uhid->replay->rin); bt_uhid_replay(uhid); return -EALREADY; } if (!uhid->replay) uhid->replay = uhid_replay_new(); if (input) queue_push_tail(uhid->replay->in, util_memdup(ev, sizeof(*ev))); else queue_push_tail(uhid->replay->out, util_memdup(ev, sizeof(*ev))); return 0; } static bool match_removed(const void *a, const void *b) { const struct uhid_notify *notify = a; return notify->removed; } static void uhid_notify(struct bt_uhid *uhid, struct uhid_event *ev) { /* Add a reference to the uhid to ensure it doesn't get freed while at * notify_handler. */ bt_uhid_ref(uhid); uhid->notifying = true; queue_foreach(uhid->notify_list, notify_handler, ev); uhid->notifying = false; queue_remove_all(uhid->notify_list, match_removed, NULL, free); bt_uhid_unref(uhid); } static bool uhid_read_handler(struct io *io, void *user_data) { struct bt_uhid *uhid = user_data; int fd; ssize_t len; struct uhid_event ev; fd = io_get_fd(io); if (fd < 0) return false; memset(&ev, 0, sizeof(ev)); len = read(fd, &ev, sizeof(ev)); if (len < 0) return false; if ((size_t) len < sizeof(ev.type)) return false; switch (ev.type) { case UHID_GET_REPORT: case UHID_SET_REPORT: bt_uhid_record(uhid, false, &ev); break; } uhid_notify(uhid, &ev); return true; } struct bt_uhid *bt_uhid_new_default(void) { struct bt_uhid *uhid; int fd; fd = open(UHID_DEVICE_FILE, O_RDWR | O_CLOEXEC); if (fd < 0) return NULL; uhid = bt_uhid_new(fd); if (!uhid) { close(fd); return NULL; } io_set_close_on_destroy(uhid->io, true); return uhid; } struct bt_uhid *bt_uhid_new(int fd) { struct bt_uhid *uhid; uhid = new0(struct bt_uhid, 1); uhid->io = io_new(fd); if (!uhid->io) goto failed; uhid->notify_list = queue_new(); if (!io_set_read_handler(uhid->io, uhid_read_handler, uhid, NULL)) goto failed; return bt_uhid_ref(uhid); failed: uhid_free(uhid); return NULL; } struct bt_uhid *bt_uhid_ref(struct bt_uhid *uhid) { if (!uhid) return NULL; __sync_fetch_and_add(&uhid->ref_count, 1); return uhid; } void bt_uhid_unref(struct bt_uhid *uhid) { if (!uhid) return; if (__sync_sub_and_fetch(&uhid->ref_count, 1)) return; uhid_free(uhid); } bool bt_uhid_set_close_on_unref(struct bt_uhid *uhid, bool do_close) { if (!uhid || !uhid->io) return false; io_set_close_on_destroy(uhid->io, do_close); return true; } unsigned int bt_uhid_register(struct bt_uhid *uhid, uint32_t event, bt_uhid_callback_t func, void *user_data) { struct uhid_notify *notify; if (!uhid) return 0; notify = new0(struct uhid_notify, 1); notify->id = ++uhid->notify_id ? uhid->notify_id : ++uhid->notify_id; notify->event = event; notify->func = func; notify->user_data = user_data; if (!queue_push_tail(uhid->notify_list, notify)) { free(notify); return 0; } return notify->id; } static bool match_notify_id(const void *a, const void *b) { const struct uhid_notify *notify = a; unsigned int id = PTR_TO_UINT(b); return notify->id == id; } bool bt_uhid_unregister(struct bt_uhid *uhid, unsigned int id) { struct uhid_notify *notify; if (!uhid || !id) return false; notify = queue_remove_if(uhid->notify_list, match_notify_id, UINT_TO_PTR(id)); if (!notify) return false; free(notify); return true; } static bool match_not_id(const void *a, const void *b) { const struct uhid_notify *notify = a; unsigned int id = PTR_TO_UINT(b); return notify->id != id; } static void uhid_notify_removed(void *data, void *user_data) { struct uhid_notify *notify = data; struct bt_uhid *uhid = user_data; /* Skip marking start_id as removed since that is not removed with * unregister all. */ if (notify->id == uhid->start_id) return; notify->removed = true; } bool bt_uhid_unregister_all(struct bt_uhid *uhid) { if (!uhid) return false; if (!uhid->notifying) queue_remove_all(uhid->notify_list, match_not_id, UINT_TO_PTR(uhid->start_id), free); else queue_foreach(uhid->notify_list, uhid_notify_removed, uhid); return true; } static int uhid_send(struct bt_uhid *uhid, const struct uhid_event *ev) { ssize_t len; struct iovec iov; iov.iov_base = (void *) ev; iov.iov_len = sizeof(*ev); len = io_send(uhid->io, &iov, 1); if (len < 0) return -errno; /* uHID kernel driver does not handle partial writes */ return len != sizeof(*ev) ? -EIO : 0; } int bt_uhid_send(struct bt_uhid *uhid, const struct uhid_event *ev) { if (!uhid || !ev) return -EINVAL; if (!uhid->io) return -ENOTCONN; return uhid_send(uhid, ev); } static bool input_dequeue(const void *data, const void *match_data) { struct uhid_event *ev = (void *)data; struct bt_uhid *uhid = (void *)match_data; return bt_uhid_send(uhid, ev) == 0; } static void uhid_start(struct uhid_event *ev, void *user_data) { struct bt_uhid *uhid = user_data; uhid->started = true; /* dequeue input events send while UHID_CREATE2 was in progress */ queue_remove_all(uhid->input, input_dequeue, uhid, free); } int bt_uhid_create(struct bt_uhid *uhid, const char *name, bdaddr_t *src, bdaddr_t *dst, uint32_t vendor, uint32_t product, uint32_t version, uint32_t country, uint8_t type, void *rd_data, size_t rd_size) { struct uhid_event ev; int err; if (!uhid || !name || rd_size > sizeof(ev.u.create2.rd_data)) return -EINVAL; if (uhid->created) return 0; /* Register callback for UHID_START if not registered yet */ if (!uhid->start_id) { uhid->start_id = bt_uhid_register(uhid, UHID_START, uhid_start, uhid); if (!uhid->start_id) return -ENOMEM; } memset(&ev, 0, sizeof(ev)); ev.type = UHID_CREATE2; strncpy((char *) ev.u.create2.name, name, sizeof(ev.u.create2.name) - 1); if (src) sprintf((char *)ev.u.create2.phys, "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x", src->b[5], src->b[4], src->b[3], src->b[2], src->b[1], src->b[0]); if (dst) sprintf((char *)ev.u.create2.uniq, "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x", dst->b[5], dst->b[4], dst->b[3], dst->b[2], dst->b[1], dst->b[0]); ev.u.create2.vendor = vendor; ev.u.create2.product = product; ev.u.create2.version = version; ev.u.create2.country = country; ev.u.create2.bus = BUS_BLUETOOTH; if (rd_size) memcpy(ev.u.create2.rd_data, rd_data, rd_size); ev.u.create2.rd_size = rd_size; err = bt_uhid_send(uhid, &ev); if (err) return err; uhid->created = true; uhid->started = false; uhid->type = type; return 0; } bool bt_uhid_created(struct bt_uhid *uhid) { if (!uhid) return false; return uhid->created; } bool bt_uhid_started(struct bt_uhid *uhid) { if (!uhid) return false; return uhid->started; } int bt_uhid_input(struct bt_uhid *uhid, uint8_t number, const void *data, size_t size) { struct uhid_event ev; struct uhid_input2_req *req = &ev.u.input2; size_t len = 0; if (!uhid) return -EINVAL; memset(&ev, 0, sizeof(ev)); ev.type = UHID_INPUT2; if (number) { req->data[len++] = number; req->size = 1 + MIN(size, sizeof(req->data) - 1); } else req->size = MIN(size, sizeof(req->data)); if (data && size) memcpy(&req->data[len], data, req->size - len); /* Queue events if UHID_START has not been received yet */ if (!uhid->started) { if (!uhid->input) uhid->input = queue_new(); queue_push_tail(uhid->input, util_memdup(&ev, sizeof(ev))); return 0; } return bt_uhid_send(uhid, &ev); } int bt_uhid_set_report_reply(struct bt_uhid *uhid, uint32_t id, uint8_t status) { struct uhid_event ev; struct uhid_set_report_reply_req *rsp = &ev.u.set_report_reply; if (!uhid) return false; memset(&ev, 0, sizeof(ev)); ev.type = UHID_SET_REPORT_REPLY; rsp->id = id; rsp->err = status; if (bt_uhid_record(uhid, true, &ev) == -EALREADY) return 0; return bt_uhid_send(uhid, &ev); } int bt_uhid_get_report_reply(struct bt_uhid *uhid, uint32_t id, uint8_t number, uint8_t status, const void *data, size_t size) { struct uhid_event ev; struct uhid_get_report_reply_req *rsp = &ev.u.get_report_reply; size_t len = 0; if (!uhid) return false; memset(&ev, 0, sizeof(ev)); ev.type = UHID_GET_REPORT_REPLY; rsp->id = id; rsp->err = status; if (!data || !size) goto done; if (number) { rsp->data[len++] = number; rsp->size += MIN(size, sizeof(rsp->data) - 1); } else rsp->size = MIN(size, sizeof(ev.u.input.data)); memcpy(&rsp->data[len], data, rsp->size - len); done: if (bt_uhid_record(uhid, true, &ev) == -EALREADY) return 0; return bt_uhid_send(uhid, &ev); } int bt_uhid_destroy(struct bt_uhid *uhid, bool force) { struct uhid_event ev; int err; if (!uhid) return -EINVAL; /* Cleanup input queue */ queue_destroy(uhid->input, free); uhid->input = NULL; /* Force destroy for non-keyboard devices - keyboards are not destroyed * on disconnect since they can glitch on reconnection losing * keypresses. */ if (!force && uhid->type != BT_UHID_KEYBOARD) force = true; if (!uhid->created || !force) return 0; memset(&ev, 0, sizeof(ev)); ev.type = UHID_DESTROY; err = bt_uhid_send(uhid, &ev); if (err < 0) return err; uhid->created = false; uhid_replay_free(uhid->replay); uhid->replay = NULL; return err; } static void queue_append(void *data, void *user_data) { queue_push_tail(user_data, data); } static struct queue *queue_dup(struct queue *q) { struct queue *dup; if (!q || queue_isempty(q)) return NULL; dup = queue_new(); queue_foreach(q, queue_append, dup); return dup; } int bt_uhid_replay(struct bt_uhid *uhid) { struct uhid_event *ev; if (!uhid || !uhid->started) return -EINVAL; if (!uhid->replay) return 0; if (uhid->replay->active) goto resend; uhid->replay->active = true; queue_destroy(uhid->replay->rin, NULL); uhid->replay->rin = queue_dup(uhid->replay->in); queue_destroy(uhid->replay->rout, NULL); uhid->replay->rout = queue_dup(uhid->replay->out); resend: ev = queue_pop_head(uhid->replay->rout); if (!ev) { uhid->replay->active = false; return 0; } uhid_notify(uhid, ev); return 0; } bluez-5.87/src/shared/PaxHeaders/tester.c0000644000000000000000000000005015142177620015367 xustar0020 atime=1783092984 20 ctime=1783094100 bluez-5.87/src/shared/tester.c0000644000000000000000000005216015142177620015054 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #ifdef HAVE_VALGRIND_MEMCHECK_H #include #endif #include "src/shared/mainloop.h" #include "src/shared/util.h" #include "src/shared/io.h" #include "src/shared/tester.h" #include "src/shared/log.h" #include "src/shared/timeout.h" #define COLOR_OFF "\x1B[0m" #define COLOR_BLACK "\x1B[0;30m" #define COLOR_RED "\x1B[0;31m" #define COLOR_GREEN "\x1B[0;32m" #define COLOR_YELLOW "\x1B[0;33m" #define COLOR_BLUE "\x1B[0;34m" #define COLOR_MAGENTA "\x1B[0;35m" #define COLOR_CYAN "\x1B[0;36m" #define COLOR_WHITE "\x1B[0;37m" #define COLOR_HIGHLIGHT "\x1B[1;39m" #define print_text(color, fmt, args...) \ tester_log(color fmt COLOR_OFF, ## args) #define print_summary(label, color, value, fmt, args...) \ tester_log("%-52s " color "%-10s" COLOR_OFF fmt, \ label, value, ## args) #define print_progress(name, color, fmt, args...) \ tester_log(COLOR_HIGHLIGHT "%s" COLOR_OFF " - " \ color fmt COLOR_OFF, name, ## args) enum test_result { TEST_RESULT_NOT_RUN, TEST_RESULT_PASSED, TEST_RESULT_FAILED, TEST_RESULT_TIMED_OUT, }; enum test_stage { TEST_STAGE_INVALID, TEST_STAGE_PRE_SETUP, TEST_STAGE_SETUP, TEST_STAGE_RUN, TEST_STAGE_TEARDOWN, TEST_STAGE_POST_TEARDOWN, }; struct test_case { char *name; enum test_result result; enum test_stage stage; const void *test_data; const struct iovec *iov; size_t iovcnt; tester_data_func_t pre_setup_func; tester_data_func_t setup_func; tester_data_func_t test_func; tester_data_func_t teardown_func; tester_data_func_t post_teardown_func; tester_data_func_t io_complete_func; gdouble start_time; gdouble end_time; unsigned int timeout; unsigned int timeout_id; unsigned int teardown_id; tester_destroy_func_t destroy; void *user_data; }; static char *tester_name; static GList *test_list; static GList *test_current; static GTimer *test_timer; static gboolean option_version = FALSE; static gboolean option_quiet = FALSE; static gboolean option_debug = FALSE; static gboolean option_monitor = FALSE; static gboolean option_list = FALSE; static const char *option_prefix = NULL; static const char *option_string = NULL; struct monitor_hdr { uint16_t opcode; uint16_t index; uint16_t len; uint8_t priority; uint8_t ident_len; } __attribute__((packed)); struct monitor_l2cap_hdr { uint16_t cid; uint16_t psm; } __attribute__((packed)); static void test_destroy(gpointer data) { struct test_case *test = data; if (test->timeout_id > 0) timeout_remove(test->timeout_id); if (test->teardown_id > 0) g_source_remove(test->teardown_id); if (test->destroy) test->destroy(test->user_data); free(test->name); free(test); } static void tester_vprintf(const char *format, va_list ap) { if (tester_use_quiet()) return; printf(" %s", COLOR_WHITE); vprintf(format, ap); printf("%s\n", COLOR_OFF); } static void tester_log(const char *format, ...) { va_list ap; va_start(ap, format); vprintf(format, ap); printf("\n"); va_end(ap); va_start(ap, format); bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_INFO, format, ap); va_end(ap); } void tester_print(const char *format, ...) { va_list ap; va_start(ap, format); tester_vprintf(format, ap); va_end(ap); va_start(ap, format); bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_INFO, format, ap); va_end(ap); } void tester_debug(const char *format, ...) { va_list ap; va_start(ap, format); tester_vprintf(format, ap); va_end(ap); va_start(ap, format); bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_DEBUG, format, ap); va_end(ap); } void tester_warn(const char *format, ...) { va_list ap; va_start(ap, format); tester_vprintf(format, ap); va_end(ap); va_start(ap, format); bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_WARNING, format, ap); va_end(ap); } static void monitor_debug(const char *str, void *user_data) { const char *label = user_data; tester_debug("%s: %s", label, str); } static void monitor_log(char dir, uint16_t cid, uint16_t psm, const void *data, size_t len) { struct iovec iov[3]; struct monitor_l2cap_hdr hdr; uint8_t term = 0x00; char label[16]; if (snprintf(label, sizeof(label), "%c %s", dir, tester_name) < 0) return; hdr.cid = cpu_to_le16(cid); hdr.psm = cpu_to_le16(psm); iov[0].iov_base = &hdr; iov[0].iov_len = sizeof(hdr); iov[1].iov_base = (void *) data; iov[1].iov_len = len; /* Kernel won't forward if data is no NULL terminated */ iov[2].iov_base = &term; iov[2].iov_len = sizeof(term); bt_log_sendmsg(HCI_DEV_NONE, label, LOG_INFO, iov, 3); } void tester_monitor(char dir, uint16_t cid, uint16_t psm, const void *data, size_t len) { monitor_log(dir, cid, psm, data, len); if (!tester_use_debug()) return; util_hexdump(dir, data, len, monitor_debug, (void *) tester_name); } static void default_pre_setup(const void *test_data) { tester_pre_setup_complete(); } static void default_setup(const void *test_data) { tester_setup_complete(); } static void default_teardown(const void *test_data) { tester_teardown_complete(); } static void default_post_teardown(const void *test_data) { tester_post_teardown_complete(); } void tester_add_full(const char *name, const void *test_data, tester_data_func_t pre_setup_func, tester_data_func_t setup_func, tester_data_func_t test_func, tester_data_func_t teardown_func, tester_data_func_t post_teardown_func, unsigned int timeout, void *user_data, tester_destroy_func_t destroy) { struct test_case *test; if (!test_func) return; if (option_prefix && !g_str_has_prefix(name, option_prefix)) { if (destroy) destroy(user_data); return; } if (option_string && !strstr(name, option_string)) { if (destroy) destroy(user_data); return; } if (option_list) { tester_log("%s", name); if (destroy) destroy(user_data); return; } test = new0(struct test_case, 1); test->name = strdup(name); test->result = TEST_RESULT_NOT_RUN; test->stage = TEST_STAGE_INVALID; test->test_data = test_data; if (pre_setup_func) test->pre_setup_func = pre_setup_func; else test->pre_setup_func = default_pre_setup; if (setup_func) test->setup_func = setup_func; else test->setup_func = default_setup; test->test_func = test_func; if (teardown_func) test->teardown_func = teardown_func; else test->teardown_func = default_teardown; if (post_teardown_func) test->post_teardown_func = post_teardown_func; else test->post_teardown_func = default_post_teardown; test->timeout = timeout; test->destroy = destroy; test->user_data = user_data; test_list = g_list_append(test_list, test); } void tester_add(const char *name, const void *test_data, tester_data_func_t setup_func, tester_data_func_t test_func, tester_data_func_t teardown_func) { tester_add_full(name, test_data, NULL, setup_func, test_func, teardown_func, NULL, 0, NULL, NULL); } static struct test_case *tester_get_test(void) { if (!test_current) return NULL; return test_current->data; } void *tester_get_data(void) { struct test_case *test = tester_get_test(); if (!test) return NULL; return test->user_data; } static int tester_summarize(void) { unsigned int not_run = 0, passed = 0, failed = 0; gdouble execution_time; GList *list; tester_log(""); print_text(COLOR_HIGHLIGHT, ""); print_text(COLOR_HIGHLIGHT, "Test Summary"); print_text(COLOR_HIGHLIGHT, "------------"); for (list = g_list_first(test_list); list; list = g_list_next(list)) { struct test_case *test = list->data; gdouble exec_time; exec_time = test->end_time - test->start_time; switch (test->result) { case TEST_RESULT_NOT_RUN: print_summary(test->name, COLOR_YELLOW, "Not Run", ""); not_run++; break; case TEST_RESULT_PASSED: print_summary(test->name, COLOR_GREEN, "Passed", "%8.3f seconds", exec_time); passed++; break; case TEST_RESULT_FAILED: print_summary(test->name, COLOR_RED, "Failed", "%8.3f seconds", exec_time); failed++; break; case TEST_RESULT_TIMED_OUT: print_summary(test->name, COLOR_RED, "Timed out", "%8.3f seconds", exec_time); failed++; break; } } tester_log("Total: %d, " COLOR_GREEN "Passed: %d (%.1f%%)" COLOR_OFF ", " COLOR_RED "Failed: %d" COLOR_OFF ", " COLOR_YELLOW "Not Run: %d" COLOR_OFF, not_run + passed + failed, passed, (not_run + passed + failed) ? (float) passed * 100 / (not_run + passed + failed) : 0, failed, not_run); execution_time = g_timer_elapsed(test_timer, NULL); tester_log("Overall execution time: %.3g seconds", execution_time); return failed; } static gboolean teardown_callback(gpointer user_data) { struct test_case *test = user_data; test->teardown_id = 0; test->stage = TEST_STAGE_TEARDOWN; print_progress(test->name, COLOR_MAGENTA, "teardown"); test->teardown_func(test->test_data); #ifdef HAVE_VALGRIND_MEMCHECK_H VALGRIND_DO_ADDED_LEAK_CHECK; #endif return FALSE; } static bool test_timeout(gpointer user_data) { struct test_case *test = user_data; test->timeout_id = 0; if (!test_current) return FALSE; test->result = TEST_RESULT_TIMED_OUT; print_progress(test->name, COLOR_RED, "test timed out"); g_idle_add(teardown_callback, test); return FALSE; } static void next_test_case(void) { struct test_case *test; if (test_current) test_current = g_list_next(test_current); else test_current = test_list; if (!test_current) { g_timer_stop(test_timer); mainloop_quit(); return; } test = test_current->data; tester_log(""); print_progress(test->name, COLOR_BLACK, "init"); test->start_time = g_timer_elapsed(test_timer, NULL); if (test->timeout > 0) test->timeout_id = timeout_add_seconds(test->timeout, test_timeout, test, NULL); test->stage = TEST_STAGE_PRE_SETUP; test->pre_setup_func(test->test_data); } static gboolean setup_callback(gpointer user_data) { struct test_case *test = user_data; test->stage = TEST_STAGE_SETUP; print_progress(test->name, COLOR_BLUE, "setup"); test->setup_func(test->test_data); return FALSE; } static gboolean run_callback(gpointer user_data) { struct test_case *test = user_data; test->stage = TEST_STAGE_RUN; print_progress(test->name, COLOR_BLACK, "run"); test->test_func(test->test_data); return FALSE; } static gboolean done_callback(gpointer user_data) { struct test_case *test = user_data; test->end_time = g_timer_elapsed(test_timer, NULL); print_progress(test->name, COLOR_BLACK, "done"); next_test_case(); return FALSE; } void tester_pre_setup_complete(void) { struct test_case *test; if (!test_current) return; test = test_current->data; if (test->stage != TEST_STAGE_PRE_SETUP) return; g_idle_add(setup_callback, test); } void tester_pre_setup_failed(void) { struct test_case *test; if (!test_current) return; test = test_current->data; if (test->stage != TEST_STAGE_PRE_SETUP) return; if (test->timeout_id > 0) { timeout_remove(test->timeout_id); test->timeout_id = 0; } print_progress(test->name, COLOR_RED, "pre setup failed"); g_idle_add(done_callback, test); } void tester_pre_setup_abort(void) { struct test_case *test; if (!test_current) return; test = test_current->data; if (test->stage != TEST_STAGE_PRE_SETUP) return; if (test->timeout_id > 0) { timeout_remove(test->timeout_id); test->timeout_id = 0; } print_progress(test->name, COLOR_YELLOW, "not run"); g_idle_add(done_callback, test); } bool tester_pre_setup_skip_by_default(void) { if (!option_prefix && !option_string) { tester_pre_setup_abort(); return true; } return false; } void tester_setup_complete(void) { struct test_case *test; if (!test_current) return; test = test_current->data; if (test->stage != TEST_STAGE_SETUP) return; print_progress(test->name, COLOR_BLUE, "setup complete"); g_idle_add(run_callback, test); } void tester_setup_failed(void) { struct test_case *test; if (!test_current) return; test = test_current->data; if (test->stage != TEST_STAGE_SETUP) return; test->stage = TEST_STAGE_POST_TEARDOWN; if (test->timeout_id > 0) { timeout_remove(test->timeout_id); test->timeout_id = 0; } print_progress(test->name, COLOR_RED, "setup failed"); print_progress(test->name, COLOR_MAGENTA, "teardown"); test->post_teardown_func(test->test_data); } static void test_result(enum test_result result) { struct test_case *test; if (!test_current) return; test = test_current->data; if (test->stage != TEST_STAGE_RUN) return; if (test->timeout_id > 0) { timeout_remove(test->timeout_id); test->timeout_id = 0; } tester_shutdown_io(); if (test->result == TEST_RESULT_FAILED) result = TEST_RESULT_FAILED; test->result = result; switch (result) { case TEST_RESULT_PASSED: print_progress(test->name, COLOR_GREEN, "test passed"); break; case TEST_RESULT_FAILED: print_progress(test->name, COLOR_RED, "test failed"); break; case TEST_RESULT_NOT_RUN: print_progress(test->name, COLOR_YELLOW, "test not run"); break; case TEST_RESULT_TIMED_OUT: print_progress(test->name, COLOR_RED, "test timed out"); break; } if (test->teardown_id > 0) return; test->teardown_id = g_idle_add(teardown_callback, test); } void tester_test_passed(void) { test_result(TEST_RESULT_PASSED); } void tester_test_failed(void) { test_result(TEST_RESULT_FAILED); } void tester_test_abort(void) { test_result(TEST_RESULT_NOT_RUN); } void tester_teardown_complete(void) { struct test_case *test; if (!test_current) return; test = test_current->data; if (test->stage != TEST_STAGE_TEARDOWN) return; test->stage = TEST_STAGE_POST_TEARDOWN; test->post_teardown_func(test->test_data); } void tester_teardown_failed(void) { struct test_case *test; if (!test_current) return; test = test_current->data; if (test->stage != TEST_STAGE_TEARDOWN) return; test->stage = TEST_STAGE_POST_TEARDOWN; tester_post_teardown_failed(); } void tester_post_teardown_complete(void) { struct test_case *test; if (!test_current) return; test = test_current->data; if (test->stage != TEST_STAGE_POST_TEARDOWN) return; print_progress(test->name, COLOR_MAGENTA, "teardown complete"); g_idle_add(done_callback, test); } void tester_post_teardown_failed(void) { struct test_case *test; if (!test_current) return; test = test_current->data; if (test->stage != TEST_STAGE_POST_TEARDOWN) return; print_progress(test->name, COLOR_RED, "teardown failed"); g_idle_add(done_callback, test); } static gboolean start_tester(gpointer user_data) { test_timer = g_timer_new(); next_test_case(); return FALSE; } struct wait_data { unsigned int seconds; struct test_case *test; tester_wait_func_t func; void *user_data; }; static gboolean wait_callback(gpointer user_data) { struct wait_data *wait = user_data; struct test_case *test = wait->test; wait->seconds--; if (wait->seconds > 0) { print_progress(test->name, COLOR_BLACK, "%u seconds left", wait->seconds); return TRUE; } print_progress(test->name, COLOR_BLACK, "waiting done"); wait->func(wait->user_data); free(wait); return FALSE; } void tester_wait(unsigned int seconds, tester_wait_func_t func, void *user_data) { struct test_case *test; struct wait_data *wait; if (!func || seconds < 1) return; if (!test_current) return; test = test_current->data; wait = new0(struct wait_data, 1); wait->seconds = seconds; wait->test = test; wait->func = func; wait->user_data = user_data; g_timeout_add(1000, wait_callback, wait); print_progress(test->name, COLOR_BLACK, "waiting %u seconds", seconds); } static void signal_callback(int signum, void *user_data) { static bool terminated = false; switch (signum) { case SIGINT: case SIGTERM: if (!terminated) mainloop_quit(); terminated = true; break; } } bool tester_use_quiet(void) { return option_quiet == TRUE ? true : false; } bool tester_use_debug(void) { return option_debug == TRUE ? true : false; } static GOptionEntry options[] = { { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, "Show version information and exit" }, { "quiet", 'q', 0, G_OPTION_ARG_NONE, &option_quiet, "Run tests without logging" }, { "debug", 'd', 0, G_OPTION_ARG_NONE, &option_debug, "Run tests with debug output" }, { "monitor", 'm', 0, G_OPTION_ARG_NONE, &option_monitor, "Enable monitor output" }, { "list", 'l', 0, G_OPTION_ARG_NONE, &option_list, "Only list the tests to be run" }, { "prefix", 'p', 0, G_OPTION_ARG_STRING, &option_prefix, "Run tests matching provided prefix" }, { "string", 's', 0, G_OPTION_ARG_STRING, &option_string, "Run tests matching provided string" }, { NULL }, }; void tester_init(int *argc, char ***argv) { GOptionContext *context; GError *error = NULL; context = g_option_context_new(NULL); g_option_context_add_main_entries(context, options, NULL); if (g_option_context_parse(context, argc, argv, &error) == FALSE) { if (error != NULL) { g_printerr("%s\n", error->message); g_error_free(error); } else g_printerr("An unknown error occurred\n"); exit(1); } g_option_context_free(context); if (option_version == TRUE) { g_print("%s\n", VERSION); exit(EXIT_SUCCESS); } mainloop_init(); tester_name = strrchr(*argv[0], '/'); if (!tester_name) tester_name = strdup(*argv[0]); else tester_name = strdup(++tester_name); test_list = NULL; test_current = NULL; } static struct io *ios[2]; static bool io_disconnected(struct io *io, void *user_data) { if (io == ios[0]) { io_destroy(ios[0]); ios[0] = NULL; } else if (io == ios[1]) { io_destroy(ios[1]); ios[1] = NULL; } return false; } static const struct iovec *test_get_iov(struct test_case *test) { const struct iovec *iov; if (!test || !test->iov || !test->iovcnt) return NULL; iov = test->iov; test->iov++; test->iovcnt--; return iov; } static bool test_io_send(struct io *io, void *user_data) { struct test_case *test = tester_get_test(); const struct iovec *iov = test_get_iov(test); ssize_t len; if (!iov) return false; len = io_send(io, iov, 1); tester_monitor('<', 0x0004, 0x0000, iov->iov_base, len); g_assert_cmpint(len, ==, iov->iov_len); if (!test->iovcnt && test->io_complete_func) { test->io_complete_func(test->test_data); } else if (test->iovcnt && !test->iov->iov_base) { test_get_iov(test); return test_io_send(io, user_data); } return false; } static bool test_io_recv(struct io *io, void *user_data) { struct test_case *test = tester_get_test(); const struct iovec *iov = test_get_iov(test); unsigned char buf[512]; int fd; ssize_t len; fd = io_get_fd(io); len = read(fd, buf, sizeof(buf)); g_assert(len > 0); tester_monitor('>', 0x0004, 0x0000, buf, len); if (!iov) return true; if (test->iovcnt && !iov->iov_base) iov = test_get_iov(test); if ((size_t)len != iov->iov_len || memcmp(buf, iov->iov_base, len)) tester_monitor('!', 0x0004, 0x0000, iov->iov_base, iov->iov_len); g_assert_cmpint(len, ==, iov->iov_len); g_assert(memcmp(buf, iov->iov_base, len) == 0); if (test->iovcnt) io_set_write_handler(io, test_io_send, NULL, NULL); else if (test->io_complete_func) test->io_complete_func(test->test_data); return true; } static void setup_io(void) { int fd[2], err; io_destroy(ios[0]); io_destroy(ios[1]); err = socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, fd); if (err < 0) { tester_warn("socketpair: %s (%d)", strerror(errno), errno); return; } ios[0] = io_new(fd[0]); if (!ios[0]) { tester_warn("io_new: %p", ios[0]); return; } io_set_close_on_destroy(ios[0], true); io_set_disconnect_handler(ios[0], io_disconnected, NULL, NULL); ios[1] = io_new(fd[1]); if (!ios[1]) { tester_warn("io_new: %p", ios[1]); return; } io_set_close_on_destroy(ios[1], true); io_set_disconnect_handler(ios[1], io_disconnected, NULL, NULL); io_set_read_handler(ios[1], test_io_recv, NULL, NULL); } struct io *tester_setup_io(const struct iovec *iov, int iovcnt) { struct test_case *test = tester_get_test(); if (!ios[0] || !ios[1]) { setup_io(); if (!ios[0] || !ios[1]) { tester_warn("Unable to setup IO"); return NULL; } } test->iov = iov; test->iovcnt = iovcnt; return ios[0]; } void tester_shutdown_io(void) { io_shutdown(ios[0]); io_shutdown(ios[1]); } void tester_io_send(void) { struct test_case *test = tester_get_test(); if (test->iovcnt) io_set_write_handler(ios[1], test_io_send, NULL, NULL); } void tester_io_set_complete_func(tester_data_func_t func) { struct test_case *test = tester_get_test(); test->io_complete_func = func; } int tester_run(void) { int ret; if (option_list) { mainloop_quit(); return EXIT_SUCCESS; } g_idle_add(start_tester, NULL); mainloop_run_with_signal(signal_callback, NULL); ret = tester_summarize(); g_list_free_full(test_list, test_destroy); if (option_monitor) bt_log_close(); io_destroy(ios[0]); io_destroy(ios[1]); return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } bluez-5.87/src/shared/PaxHeaders/gatt-helpers.c0000644000000000000000000000005015200607060016447 xustar0020 atime=1783092683 20 ctime=1783094100 bluez-5.87/src/shared/gatt-helpers.c0000644000000000000000000010113715200607060016133 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Google Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #include "src/shared/queue.h" #include "src/shared/att.h" #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "src/shared/gatt-helpers.h" #include "src/shared/util.h" struct bt_gatt_result { uint8_t opcode; void *pdu; uint16_t pdu_len; uint16_t data_len; void *op; /* Discovery operation data */ struct bt_gatt_result *next; }; static struct bt_gatt_result *result_create(uint8_t opcode, const void *pdu, uint16_t pdu_len, uint16_t data_len, void *op) { struct bt_gatt_result *result; result = new0(struct bt_gatt_result, 1); result->pdu = malloc(pdu_len); if (!result->pdu) { free(result); return NULL; } result->opcode = opcode; result->pdu_len = pdu_len; result->data_len = data_len; result->op = op; memcpy(result->pdu, pdu, pdu_len); return result; } static void result_destroy(struct bt_gatt_result *result) { struct bt_gatt_result *next; while (result) { next = result->next; free(result->pdu); free(result); result = next; } } static unsigned int result_element_count(struct bt_gatt_result *result) { unsigned int count = 0; struct bt_gatt_result *cur; cur = result; while (cur) { count += cur->pdu_len / cur->data_len; cur = cur->next; } return count; } unsigned int bt_gatt_result_service_count(struct bt_gatt_result *result) { if (!result) return 0; if (result->opcode != BT_ATT_OP_READ_BY_GRP_TYPE_RSP && result->opcode != BT_ATT_OP_FIND_BY_TYPE_RSP) return 0; return result_element_count(result); } unsigned int bt_gatt_result_characteristic_count(struct bt_gatt_result *result) { if (!result) return 0; if (result->opcode != BT_ATT_OP_READ_BY_TYPE_RSP) return 0; /* * Data length contains 7 or 21 octets: * 2 octets: Attribute handle * 1 octet: Characteristic properties * 2 octets: Characteristic value handle * 2 or 16 octets: characteristic UUID */ if (result->data_len != 21 && result->data_len != 7) return 0; return result_element_count(result); } unsigned int bt_gatt_result_descriptor_count(struct bt_gatt_result *result) { if (!result) return 0; if (result->opcode != BT_ATT_OP_FIND_INFO_RSP) return 0; return result_element_count(result); } unsigned int bt_gatt_result_included_count(struct bt_gatt_result *result) { struct bt_gatt_result *cur; unsigned int count = 0; if (!result) return 0; if (result->opcode != BT_ATT_OP_READ_BY_TYPE_RSP) return 0; /* * Data length can be of length 6 or 8 octets: * 2 octets - include service handle * 2 octets - start handle of included service * 2 octets - end handle of included service * 2 octets (optionally) - 16 bit Bluetooth UUID */ if (result->data_len != 6 && result->data_len != 8) return 0; for (cur = result; cur; cur = cur->next) if (cur->opcode == BT_ATT_OP_READ_BY_TYPE_RSP) count += cur->pdu_len / cur->data_len; return count; } bool bt_gatt_iter_init(struct bt_gatt_iter *iter, struct bt_gatt_result *result) { if (!iter || !result) return false; iter->result = result; iter->pos = 0; return true; } static const uint8_t bt_base_uuid[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB }; static bool convert_uuid_le(const uint8_t *src, size_t len, uint8_t dst[16]) { if (len == 16) { bswap_128(src, dst); return true; } if (len != 2) return false; memcpy(dst, bt_base_uuid, sizeof(bt_base_uuid)); dst[2] = src[1]; dst[3] = src[0]; return true; } struct bt_gatt_request { struct bt_att *att; unsigned int id; uint16_t start_handle; uint16_t end_handle; int ref_count; bt_uuid_t uuid; uint16_t service_type; struct bt_gatt_result *result_head; struct bt_gatt_result *result_tail; bt_gatt_request_callback_t callback; void *user_data; bt_gatt_destroy_func_t destroy; }; static struct bt_gatt_result *result_append(uint8_t opcode, const void *pdu, uint16_t pdu_len, uint16_t data_len, struct bt_gatt_request *op) { struct bt_gatt_result *result; result = result_create(opcode, pdu, pdu_len, data_len, op); if (!result) return NULL; if (!op->result_head) op->result_head = op->result_tail = result; else { op->result_tail->next = result; op->result_tail = result; } return result; } bool bt_gatt_iter_next_included_service(struct bt_gatt_iter *iter, uint16_t *handle, uint16_t *start_handle, uint16_t *end_handle, uint8_t uuid[16]) { struct bt_gatt_result *read_result; struct bt_gatt_request *op; const void *pdu_ptr; int i = 0; if (!iter || !iter->result || !handle || !start_handle || !end_handle || !uuid) return false; if (iter->result->opcode != BT_ATT_OP_READ_BY_TYPE_RSP) return false; /* UUID in discovery_op is set in read_by_type and service_discovery */ op = iter->result->op; if (op->uuid.type != BT_UUID_UNSPEC) return false; /* * iter->result points to READ_BY_TYPE_RSP with data length containing: * 2 octets - include service handle * 2 octets - start handle of included service * 2 octets - end handle of included service * optional 2 octets - Bluetooth UUID */ if (iter->result->data_len != 8 && iter->result->data_len != 6) return false; pdu_ptr = iter->result->pdu + iter->pos; /* This result contains 16 bit UUID */ if (iter->result->data_len == 8) { *handle = get_le16(pdu_ptr); *start_handle = get_le16(pdu_ptr + 2); *end_handle = get_le16(pdu_ptr + 4); convert_uuid_le(pdu_ptr + 6, 2, uuid); iter->pos += iter->result->data_len; if (iter->pos == iter->result->pdu_len) { iter->result = iter->result->next; iter->pos = 0; } return true; } *handle = get_le16(pdu_ptr); *start_handle = get_le16(pdu_ptr + 2); *end_handle = get_le16(pdu_ptr + 4); read_result = iter->result; /* * Find READ_RSP with include service UUID. * If number of current data set in READ_BY_TYPE_RSP is n, then we must * go to n'th PDU next to current item->result */ for (read_result = read_result->next; read_result; i++) { if (i >= (iter->pos / iter->result->data_len)) break; read_result = read_result->next; } if (!read_result) return false; convert_uuid_le(read_result->pdu, read_result->data_len, uuid); iter->pos += iter->result->data_len; if (iter->pos == iter->result->pdu_len) { iter->result = read_result->next; iter->pos = 0; } return true; } bool bt_gatt_iter_next_service(struct bt_gatt_iter *iter, uint16_t *start_handle, uint16_t *end_handle, uint8_t uuid[16]) { struct bt_gatt_request *op; const void *pdu_ptr; bt_uuid_t tmp; if (!iter || !iter->result || !start_handle || !end_handle || !uuid) return false; op = iter->result->op; pdu_ptr = iter->result->pdu + iter->pos; switch (iter->result->opcode) { case BT_ATT_OP_READ_BY_GRP_TYPE_RSP: *start_handle = get_le16(pdu_ptr); *end_handle = get_le16(pdu_ptr + 2); convert_uuid_le(pdu_ptr + 4, iter->result->data_len - 4, uuid); break; case BT_ATT_OP_FIND_BY_TYPE_RSP: *start_handle = get_le16(pdu_ptr); *end_handle = get_le16(pdu_ptr + 2); bt_uuid_to_uuid128(&op->uuid, &tmp); memcpy(uuid, tmp.value.u128.data, 16); break; default: return false; } iter->pos += iter->result->data_len; if (iter->pos == iter->result->pdu_len) { iter->result = iter->result->next; iter->pos = 0; } return true; } bool bt_gatt_iter_next_characteristic(struct bt_gatt_iter *iter, uint16_t *start_handle, uint16_t *end_handle, uint16_t *value_handle, uint8_t *properties, uint8_t uuid[16]) { struct bt_gatt_request *op; const void *pdu_ptr; if (!iter || !iter->result || !start_handle || !end_handle || !value_handle || !properties || !uuid) return false; if (iter->result->opcode != BT_ATT_OP_READ_BY_TYPE_RSP) return false; /* UUID in discovery_op is set in read_by_type and service_discovery */ op = iter->result->op; if (op->uuid.type != BT_UUID_UNSPEC) return false; /* * Data length contains 7 or 21 octets: * 2 octets: Attribute handle * 1 octet: Characteristic properties * 2 octets: Characteristic value handle * 2 or 16 octets: characteristic UUID */ if (iter->result->data_len != 21 && iter->result->data_len != 7) return false; pdu_ptr = iter->result->pdu + iter->pos; *start_handle = get_le16(pdu_ptr); *properties = ((uint8_t *) pdu_ptr)[2]; *value_handle = get_le16(pdu_ptr + 3); convert_uuid_le(pdu_ptr + 5, iter->result->data_len - 5, uuid); iter->pos += iter->result->data_len; if (iter->pos == iter->result->pdu_len) { iter->result = iter->result->next; iter->pos = 0; } if (!iter->result) { *end_handle = op->end_handle; return true; } *end_handle = get_le16(iter->result->pdu + iter->pos) - 1; return true; } bool bt_gatt_iter_next_descriptor(struct bt_gatt_iter *iter, uint16_t *handle, uint8_t uuid[16]) { const void *pdu_ptr; if (!iter || !iter->result || !handle || !uuid) return false; if (iter->result->opcode != BT_ATT_OP_FIND_INFO_RSP) return false; pdu_ptr = iter->result->pdu + iter->pos; *handle = get_le16(pdu_ptr); convert_uuid_le(pdu_ptr + 2, iter->result->data_len - 2, uuid); iter->pos += iter->result->data_len; if (iter->pos == iter->result->pdu_len) { iter->result = iter->result->next; iter->pos = 0; } return true; } bool bt_gatt_iter_next_read_by_type(struct bt_gatt_iter *iter, uint16_t *handle, uint16_t *length, const uint8_t **value) { struct bt_gatt_request *op; const void *pdu_ptr; if (!iter || !iter->result || !handle || !length || !value) return false; if (iter->result->opcode != BT_ATT_OP_READ_BY_TYPE_RSP) return false; /* * Check if UUID is set, otherwise results can contain characteristic * discovery service or included service discovery results */ op = iter->result->op; if (op->uuid.type == BT_UUID_UNSPEC) return false; pdu_ptr = iter->result->pdu + iter->pos; *handle = get_le16(pdu_ptr); *length = iter->result->data_len - 2; *value = pdu_ptr + 2; iter->pos += iter->result->data_len; if (iter->pos == iter->result->pdu_len) { iter->result = iter->result->next; iter->pos = 0; } return true; } struct mtu_op { struct bt_att *att; uint16_t client_rx_mtu; bt_gatt_result_callback_t callback; void *user_data; bt_gatt_destroy_func_t destroy; }; static void destroy_mtu_op(void *user_data) { struct mtu_op *op = user_data; if (op->destroy) op->destroy(op->user_data); free(op); } static uint8_t process_error(const void *pdu, uint16_t length) { const struct bt_att_pdu_error_rsp *error_pdu; if (!pdu || length != sizeof(struct bt_att_pdu_error_rsp)) return 0; error_pdu = pdu; return error_pdu->ecode; } static void mtu_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct mtu_op *op = user_data; bool success = true; uint8_t att_ecode = 0; uint16_t server_rx_mtu; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); goto done; } if (opcode != BT_ATT_OP_MTU_RSP || !pdu || length != 2) { success = false; goto done; } server_rx_mtu = get_le16(pdu); bt_att_set_mtu(op->att, MIN(op->client_rx_mtu, server_rx_mtu)); done: if (op->callback) op->callback(success, att_ecode, op->user_data); } unsigned int bt_gatt_exchange_mtu(struct bt_att *att, uint16_t client_rx_mtu, bt_gatt_result_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy) { struct mtu_op *op; uint8_t pdu[2]; unsigned int id; if (!att || !client_rx_mtu) return false; op = new0(struct mtu_op, 1); op->att = att; op->client_rx_mtu = client_rx_mtu; op->callback = callback; op->user_data = user_data; op->destroy = destroy; put_le16(client_rx_mtu, pdu); id = bt_att_send(att, BT_ATT_OP_MTU_REQ, pdu, sizeof(pdu), mtu_cb, op, destroy_mtu_op); if (!id) free(op); return id; } static inline int get_uuid_len(const bt_uuid_t *uuid) { if (!uuid) return 0; return (uuid->type == BT_UUID16) ? 2 : 16; } struct bt_gatt_request *bt_gatt_request_ref(struct bt_gatt_request *req) { if (!req) return NULL; __sync_fetch_and_add(&req->ref_count, 1); return req; } void bt_gatt_request_unref(struct bt_gatt_request *req) { if (!req) return; if (__sync_sub_and_fetch(&req->ref_count, 1)) return; bt_gatt_request_cancel(req); if (req->destroy) req->destroy(req->user_data); result_destroy(req->result_head); free(req); } void bt_gatt_request_cancel(struct bt_gatt_request *req) { if (!req) return; if (!req->id) return; bt_att_cancel(req->att, req->id); req->id = 0; } static void async_req_unref(void *data) { struct bt_gatt_request *req = data; bt_gatt_request_unref(req); } static void discovery_op_complete(struct bt_gatt_request *op, bool success, uint8_t ecode) { /* Reset success if there is some result to report */ if (ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND && op->result_head) success = true; if (op->callback) op->callback(success, ecode, success ? op->result_head : NULL, op->user_data); if (!op->id) async_req_unref(op); else op->id = 0; } static void read_by_grp_type_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_request *op = user_data; bool success; uint8_t att_ecode = 0; struct bt_gatt_result *cur_result; size_t data_length; size_t list_length; uint16_t last_end; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); goto done; } /* PDU must contain at least the following (sans opcode): * - Attr Data Length (1 octet) * - Attr Data List (at least 6 octets): * -- 2 octets: Attribute handle * -- 2 octets: End group handle * -- 2 or 16 octets: service UUID */ if (opcode != BT_ATT_OP_READ_BY_GRP_TYPE_RSP || !pdu || length < 7) { success = false; goto done; } data_length = ((uint8_t *) pdu)[0]; list_length = length - 1; if ((data_length != 6 && data_length != 20) || (list_length % data_length)) { success = false; goto done; } /* PDU is correctly formatted. Get the last end handle to process the * next request and store the PDU. */ cur_result = result_append(opcode, pdu + 1, list_length, data_length, op); if (!cur_result) { success = false; goto done; } last_end = get_le16(pdu + length - data_length + 2); /* * If last handle is lower from previous start handle then it is smth * wrong. Let's stop search, otherwise we might enter infinite loop. */ if (last_end < op->start_handle) { success = false; goto done; } op->start_handle = last_end + 1; if (last_end < op->end_handle) { uint8_t pdu[6]; put_le16(op->start_handle, pdu); put_le16(op->end_handle, pdu + 2); put_le16(op->service_type, pdu + 4); op->id = bt_att_send(op->att, BT_ATT_OP_READ_BY_GRP_TYPE_REQ, pdu, sizeof(pdu), read_by_grp_type_cb, bt_gatt_request_ref(op), async_req_unref); if (op->id) return; success = false; goto done; } /* Some devices incorrectly return 0xffff as the end group handle when * the read-by-group-type request is performed within a smaller range. * Manually set the end group handle that we report in the result to the * end handle in the original request. */ if (last_end == 0xffff && last_end != op->end_handle) put_le16(op->end_handle, cur_result->pdu + length - data_length + 1); success = true; done: discovery_op_complete(op, success, att_ecode); } static void find_by_type_val_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_request *op = user_data; bool success; uint8_t att_ecode = 0; uint16_t last_end; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); goto done; } /* PDU must contain 4 bytes and it must be a multiple of 4, where each * 4 bytes contain the 16-bit attribute and group end handles. */ if (opcode != BT_ATT_OP_FIND_BY_TYPE_RSP || !pdu || !length || length % 4) { success = false; goto done; } if (!result_append(opcode, pdu, length, 4, op)) { success = false; goto done; } /* * Each data set contains: * 2 octets with start handle * 2 octets with end handle * last_end is end handle of last data set */ last_end = get_le16(pdu + length - 2); /* * If last handle is lower from previous start handle then it is smth * wrong. Let's stop search, otherwise we might enter infinite loop. */ if (last_end < op->start_handle) { success = false; goto done; } op->start_handle = last_end + 1; if (last_end < op->end_handle) { uint8_t pdu[6 + get_uuid_len(&op->uuid)]; put_le16(op->start_handle, pdu); put_le16(op->end_handle, pdu + 2); put_le16(op->service_type, pdu + 4); bt_uuid_to_le(&op->uuid, pdu + 6); op->id = bt_att_send(op->att, BT_ATT_OP_FIND_BY_TYPE_REQ, pdu, sizeof(pdu), find_by_type_val_cb, bt_gatt_request_ref(op), async_req_unref); if (op->id) return; success = false; goto done; } success = true; done: discovery_op_complete(op, success, att_ecode); } uint16_t bt_gatt_req_get_start_handle(struct bt_gatt_request *req) { if (!req) return 0; return req->start_handle; } uint16_t bt_gatt_req_get_end_handle(struct bt_gatt_request *req) { if (!req) return 0; return req->end_handle; } static struct bt_gatt_request *discover_services(struct bt_att *att, bt_uuid_t *uuid, uint16_t start, uint16_t end, bt_gatt_request_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy, bool primary) { struct bt_gatt_request *op; if (!att) return NULL; op = new0(struct bt_gatt_request, 1); op->att = att; op->start_handle = start; op->end_handle = end; op->callback = callback; op->user_data = user_data; op->destroy = destroy; /* set service uuid to primary or secondary */ op->service_type = primary ? GATT_PRIM_SVC_UUID : GATT_SND_SVC_UUID; /* If UUID is NULL, then discover all primary services */ if (!uuid) { uint8_t pdu[6]; put_le16(start, pdu); put_le16(end, pdu + 2); put_le16(op->service_type, pdu + 4); op->id = bt_att_send(att, BT_ATT_OP_READ_BY_GRP_TYPE_REQ, pdu, sizeof(pdu), read_by_grp_type_cb, bt_gatt_request_ref(op), async_req_unref); } else { uint8_t pdu[6 + get_uuid_len(uuid)]; if (uuid->type == BT_UUID_UNSPEC) { free(op); return NULL; } /* Discover by UUID */ op->uuid = *uuid; put_le16(start, pdu); put_le16(end, pdu + 2); put_le16(op->service_type, pdu + 4); bt_uuid_to_le(&op->uuid, pdu + 6); op->id = bt_att_send(att, BT_ATT_OP_FIND_BY_TYPE_REQ, pdu, sizeof(pdu), find_by_type_val_cb, bt_gatt_request_ref(op), async_req_unref); } if (!op->id) { free(op); return NULL; } return bt_gatt_request_ref(op); } struct bt_gatt_request *bt_gatt_discover_all_primary_services( struct bt_att *att, bt_uuid_t *uuid, bt_gatt_request_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy) { return bt_gatt_discover_primary_services(att, uuid, 0x0001, 0xffff, callback, user_data, destroy); } struct bt_gatt_request *bt_gatt_discover_primary_services( struct bt_att *att, bt_uuid_t *uuid, uint16_t start, uint16_t end, bt_gatt_request_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy) { return discover_services(att, uuid, start, end, callback, user_data, destroy, true); } struct bt_gatt_request *bt_gatt_discover_secondary_services( struct bt_att *att, bt_uuid_t *uuid, uint16_t start, uint16_t end, bt_gatt_request_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy) { return discover_services(att, uuid, start, end, callback, user_data, destroy, false); } struct read_incl_data { struct bt_gatt_request *op; struct bt_gatt_result *result; int pos; int ref_count; }; static struct read_incl_data *new_read_included(struct bt_gatt_result *res) { struct read_incl_data *data; data = new0(struct read_incl_data, 1); data->op = bt_gatt_request_ref(res->op); data->result = res; return data; }; static struct read_incl_data *read_included_ref(struct read_incl_data *data) { __sync_fetch_and_add(&data->ref_count, 1); return data; } static void read_included_unref(void *data) { struct read_incl_data *read_data = data; if (__sync_sub_and_fetch(&read_data->ref_count, 1)) return; async_req_unref(read_data->op); free(read_data); } static void discover_included_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data); static void read_included_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct read_incl_data *data = user_data; struct bt_gatt_request *op = data->op; uint8_t att_ecode = 0; uint8_t read_pdu[2]; bool success; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); goto done; } if (opcode != BT_ATT_OP_READ_RSP || (!pdu && length)) { success = false; goto done; } /* * UUID should be in 128 bit format, as it couldn't be read in * READ_BY_TYPE request */ if (length != 16) { success = false; goto done; } if (!result_append(opcode, pdu, length, length, op)) { success = false; goto done; } if (data->pos == data->result->pdu_len) { uint16_t last_handle; uint8_t pdu[6]; last_handle = get_le16(data->result->pdu + data->pos - data->result->data_len); if (last_handle == op->end_handle) { success = true; goto done; } put_le16(last_handle + 1, pdu); put_le16(op->end_handle, pdu + 2); put_le16(GATT_INCLUDE_UUID, pdu + 4); op->id = bt_att_send(op->att, BT_ATT_OP_READ_BY_TYPE_REQ, pdu, sizeof(pdu), discover_included_cb, bt_gatt_request_ref(op), async_req_unref); if (op->id) return; success = false; goto done; } memcpy(read_pdu, data->result->pdu + data->pos + 2, sizeof(uint16_t)); data->pos += data->result->data_len; if (bt_att_send(op->att, BT_ATT_OP_READ_REQ, read_pdu, sizeof(read_pdu), read_included_cb, read_included_ref(data), read_included_unref)) return; read_included_unref(data); success = false; done: discovery_op_complete(op, success, att_ecode); } static void read_included(struct read_incl_data *data) { struct bt_gatt_request *op = data->op; uint8_t pdu[2]; memcpy(pdu, data->result->pdu + 2, sizeof(uint16_t)); data->pos += data->result->data_len; if (bt_att_send(op->att, BT_ATT_OP_READ_REQ, pdu, sizeof(pdu), read_included_cb, read_included_ref(data), read_included_unref)) return; if (op->callback) op->callback(false, 0, NULL, data->op->user_data); read_included_unref(data); } static void discover_included_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_request *op = user_data; struct bt_gatt_result *cur_result; uint8_t att_ecode = 0; uint16_t last_handle; size_t data_length; bool success; if (opcode == BT_ATT_OP_ERROR_RSP) { att_ecode = process_error(pdu, length); success = false; goto failed; } if (opcode != BT_ATT_OP_READ_BY_TYPE_RSP || !pdu || length < 6) { success = false; goto failed; } data_length = ((const uint8_t *) pdu)[0]; /* * Check if PDU contains data sets with length declared in the beginning * of frame and if this length is correct. * Data set length may be 6 or 8 octets: * 2 octets - include service handle * 2 octets - start handle of included service * 2 octets - end handle of included service * optional 2 octets - Bluetooth UUID of included service */ if ((data_length != 8 && data_length != 6) || (length - 1) % data_length) { success = false; goto failed; } cur_result = result_append(opcode, pdu + 1, length - 1, data_length, op); if (!cur_result) { success = false; goto failed; } if (data_length == 6) { struct read_incl_data *data; data = new_read_included(cur_result); if (!data) { success = false; goto failed; } read_included(data); return; } last_handle = get_le16(pdu + length - data_length); /* * If last handle is lower from previous start handle then it is smth * wrong. Let's stop search, otherwise we might enter infinite loop. */ if (last_handle < op->start_handle) { success = false; goto failed; } op->start_handle = last_handle + 1; if (last_handle != op->end_handle) { uint8_t pdu[6]; put_le16(op->start_handle, pdu); put_le16(op->end_handle, pdu + 2); put_le16(GATT_INCLUDE_UUID, pdu + 4); op->id = bt_att_send(op->att, BT_ATT_OP_READ_BY_TYPE_REQ, pdu, sizeof(pdu), discover_included_cb, bt_gatt_request_ref(op), async_req_unref); if (op->id) return; success = false; goto failed; } success = true; failed: discovery_op_complete(op, success, att_ecode); } struct bt_gatt_request *bt_gatt_discover_included_services(struct bt_att *att, uint16_t start, uint16_t end, bt_gatt_request_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy) { struct bt_gatt_request *op; uint8_t pdu[6]; if (!att) return NULL; op = new0(struct bt_gatt_request, 1); op->att = att; op->callback = callback; op->user_data = user_data; op->destroy = destroy; op->start_handle = start; op->end_handle = end; put_le16(start, pdu); put_le16(end, pdu + 2); put_le16(GATT_INCLUDE_UUID, pdu + 4); op->id = bt_att_send(att, BT_ATT_OP_READ_BY_TYPE_REQ, pdu, sizeof(pdu), discover_included_cb, bt_gatt_request_ref(op), async_req_unref); if (!op->id) { free(op); return NULL; } return bt_gatt_request_ref(op); } static void discover_chrcs_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_request *op = user_data; bool success; uint8_t att_ecode = 0; size_t data_length; uint16_t last_handle; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); goto done; } /* PDU must contain at least the following (sans opcode): * - Attr Data Length (1 octet) * - Attr Data List (at least 7 octets): * -- 2 octets: Attribute handle * -- 1 octet: Characteristic properties * -- 2 octets: Characteristic value handle * -- 2 or 16 octets: characteristic UUID */ if (opcode != BT_ATT_OP_READ_BY_TYPE_RSP || !pdu || length < 8) { success = false; goto done; } data_length = ((uint8_t *) pdu)[0]; if ((data_length != 7 && data_length != 21) || ((length - 1) % data_length)) { success = false; goto done; } if (!result_append(opcode, pdu + 1, length - 1, data_length, op)) { success = false; goto done; } last_handle = get_le16(pdu + length - data_length); /* * If last handle is lower from previous start handle then it is smth * wrong. Let's stop search, otherwise we might enter infinite loop. */ if (last_handle < op->start_handle) { success = false; goto done; } op->start_handle = last_handle + 1; if (last_handle != op->end_handle) { uint8_t pdu[6]; put_le16(op->start_handle, pdu); put_le16(op->end_handle, pdu + 2); put_le16(GATT_CHARAC_UUID, pdu + 4); op->id = bt_att_send(op->att, BT_ATT_OP_READ_BY_TYPE_REQ, pdu, sizeof(pdu), discover_chrcs_cb, bt_gatt_request_ref(op), async_req_unref); if (op->id) return; success = false; goto done; } success = true; done: discovery_op_complete(op, success, att_ecode); } struct bt_gatt_request *bt_gatt_discover_characteristics(struct bt_att *att, uint16_t start, uint16_t end, bt_gatt_request_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy) { struct bt_gatt_request *op; uint8_t pdu[6]; if (!att) return NULL; op = new0(struct bt_gatt_request, 1); op->att = att; op->callback = callback; op->user_data = user_data; op->destroy = destroy; op->start_handle = start; op->end_handle = end; put_le16(start, pdu); put_le16(end, pdu + 2); put_le16(GATT_CHARAC_UUID, pdu + 4); op->id = bt_att_send(att, BT_ATT_OP_READ_BY_TYPE_REQ, pdu, sizeof(pdu), discover_chrcs_cb, bt_gatt_request_ref(op), async_req_unref); if (!op->id) { free(op); return NULL; } return bt_gatt_request_ref(op); } static void read_by_type_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_request *op = user_data; bool success; uint8_t att_ecode = 0; size_t data_length; uint16_t last_handle; if (opcode == BT_ATT_OP_ERROR_RSP) { att_ecode = process_error(pdu, length); success = false; goto done; } if (opcode != BT_ATT_OP_READ_BY_TYPE_RSP || !pdu) { success = false; att_ecode = 0; goto done; } data_length = ((uint8_t *) pdu)[0]; if (((length - 1) % data_length)) { success = false; att_ecode = 0; goto done; } if (!result_append(opcode, pdu + 1, length - 1, data_length, op)) { success = false; att_ecode = 0; goto done; } last_handle = get_le16(pdu + length - data_length); /* * If last handle is lower from previous start handle then it is smth * wrong. Let's stop search, otherwise we might enter infinite loop. */ if (last_handle < op->start_handle) { success = false; goto done; } op->start_handle = last_handle + 1; if (last_handle != op->end_handle) { uint8_t pdu[4 + get_uuid_len(&op->uuid)]; put_le16(op->start_handle, pdu); put_le16(op->end_handle, pdu + 2); bt_uuid_to_le(&op->uuid, pdu + 4); op->id = bt_att_send(op->att, BT_ATT_OP_READ_BY_TYPE_REQ, pdu, sizeof(pdu), read_by_type_cb, bt_gatt_request_ref(op), async_req_unref); if (op->id) return; success = false; goto done; } success = true; done: discovery_op_complete(op, success, att_ecode); } bool bt_gatt_read_by_type(struct bt_att *att, uint16_t start, uint16_t end, const bt_uuid_t *uuid, bt_gatt_request_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy) { struct bt_gatt_request *op; uint8_t pdu[4 + get_uuid_len(uuid)]; if (!att || !uuid || uuid->type == BT_UUID_UNSPEC) return false; op = new0(struct bt_gatt_request, 1); op->att = att; op->callback = callback; op->user_data = user_data; op->destroy = destroy; op->start_handle = start; op->end_handle = end; op->uuid = *uuid; put_le16(start, pdu); put_le16(end, pdu + 2); bt_uuid_to_le(uuid, pdu + 4); op->id = bt_att_send(att, BT_ATT_OP_READ_BY_TYPE_REQ, pdu, sizeof(pdu), read_by_type_cb, bt_gatt_request_ref(op), async_req_unref); if (op->id) return true; free(op); return false; } static void discover_descs_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_request *op = user_data; bool success; uint8_t att_ecode = 0; uint8_t format; uint16_t last_handle; size_t data_length; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); goto done; } /* The PDU should contain the following data (sans opcode): * - Format (1 octet) * - Attr Data List (at least 4 octets): * -- 2 octets: Attribute handle * -- 2 or 16 octets: UUID. */ if (opcode != BT_ATT_OP_FIND_INFO_RSP || !pdu || length < 5) { success = false; goto done; } format = ((uint8_t *) pdu)[0]; if (format == 0x01) data_length = 4; else if (format == 0x02) data_length = 18; else { success = false; goto done; } if ((length - 1) % data_length) { success = false; goto done; } if (!result_append(opcode, pdu + 1, length - 1, data_length, op)) { success = false; goto done; } last_handle = get_le16(pdu + length - data_length); /* * If last handle is lower from previous start handle then it is smth * wrong. Let's stop search, otherwise we might enter infinite loop. */ if (last_handle < op->start_handle) { success = false; goto done; } op->start_handle = last_handle + 1; if (last_handle != op->end_handle) { uint8_t pdu[4]; put_le16(op->start_handle, pdu); put_le16(op->end_handle, pdu + 2); op->id = bt_att_send(op->att, BT_ATT_OP_FIND_INFO_REQ, pdu, sizeof(pdu), discover_descs_cb, bt_gatt_request_ref(op), async_req_unref); if (op->id) return; success = false; goto done; } success = true; done: discovery_op_complete(op, success, att_ecode); } struct bt_gatt_request *bt_gatt_discover_descriptors(struct bt_att *att, uint16_t start, uint16_t end, bt_gatt_request_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy) { struct bt_gatt_request *op; uint8_t pdu[4]; if (!att) return NULL; op = new0(struct bt_gatt_request, 1); op->att = att; op->callback = callback; op->user_data = user_data; op->destroy = destroy; op->start_handle = start; op->end_handle = end; put_le16(start, pdu); put_le16(end, pdu + 2); op->id = bt_att_send(att, BT_ATT_OP_FIND_INFO_REQ, pdu, sizeof(pdu), discover_descs_cb, bt_gatt_request_ref(op), async_req_unref); if (!op->id) { free(op); return NULL; } return bt_gatt_request_ref(op); } bluez-5.87/src/shared/PaxHeaders/uhid.h0000644000000000000000000000005015110075565015017 xustar0020 atime=1783092900 20 ctime=1783094099 bluez-5.87/src/shared/uhid.h0000644000000000000000000000410015110075565014473 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * */ #include #include #include #include struct bt_uhid; enum { BT_UHID_NONE = 0, BT_UHID_KEYBOARD, BT_UHID_MOUSE, BT_UHID_GAMING, BT_UHID_TABLET }; static inline uint8_t bt_uhid_icon_to_type(const char *icon) { if (!icon) return BT_UHID_NONE; if (!strcmp(icon, "input-keyboard")) return BT_UHID_KEYBOARD; else if (!strcmp(icon, "input-mouse")) return BT_UHID_MOUSE; else if (!strcmp(icon, "input-gaming")) return BT_UHID_GAMING; else if (!strcmp(icon, "input-tablet")) return BT_UHID_TABLET; else return BT_UHID_NONE; } struct bt_uhid *bt_uhid_new_default(void); struct bt_uhid *bt_uhid_new(int fd); struct bt_uhid *bt_uhid_ref(struct bt_uhid *uhid); void bt_uhid_unref(struct bt_uhid *uhid); bool bt_uhid_set_close_on_unref(struct bt_uhid *uhid, bool do_close); typedef void (*bt_uhid_callback_t)(struct uhid_event *ev, void *user_data); unsigned int bt_uhid_register(struct bt_uhid *uhid, uint32_t event, bt_uhid_callback_t func, void *user_data); bool bt_uhid_unregister(struct bt_uhid *uhid, unsigned int id); bool bt_uhid_unregister_all(struct bt_uhid *uhid); int bt_uhid_send(struct bt_uhid *uhid, const struct uhid_event *ev); int bt_uhid_create(struct bt_uhid *uhid, const char *name, bdaddr_t *src, bdaddr_t *dst, uint32_t vendor, uint32_t product, uint32_t version, uint32_t country, uint8_t type, void *rd_data, size_t rd_size); bool bt_uhid_created(struct bt_uhid *uhid); bool bt_uhid_started(struct bt_uhid *uhid); int bt_uhid_input(struct bt_uhid *uhid, uint8_t number, const void *data, size_t size); int bt_uhid_set_report_reply(struct bt_uhid *uhid, uint32_t id, uint8_t status); int bt_uhid_get_report_reply(struct bt_uhid *uhid, uint32_t id, uint8_t number, uint8_t status, const void *data, size_t size); int bt_uhid_destroy(struct bt_uhid *uhid, bool force); int bt_uhid_replay(struct bt_uhid *uhid); bluez-5.87/src/shared/PaxHeaders/crypto.h0000644000000000000000000000005015142177620015406 xustar0020 atime=1783092883 20 ctime=1783094099 bluez-5.87/src/shared/crypto.h0000644000000000000000000000514415142177620015073 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #include #include #include struct bt_crypto; struct bt_crypto *bt_crypto_new(void); struct bt_crypto *bt_crypto_ref(struct bt_crypto *crypto); void bt_crypto_unref(struct bt_crypto *crypto); bool bt_crypto_random_bytes(struct bt_crypto *crypto, void *buf, uint8_t num_bytes); bool bt_crypto_e(struct bt_crypto *crypto, const uint8_t key[16], const uint8_t plaintext[16], uint8_t encrypted[16]); bool bt_crypto_ah(struct bt_crypto *crypto, const uint8_t k[16], const uint8_t r[3], uint8_t hash[3]); bool bt_crypto_c1(struct bt_crypto *crypto, const uint8_t k[16], const uint8_t r[16], const uint8_t pres[7], const uint8_t preq[7], uint8_t iat, const uint8_t ia[6], uint8_t rat, const uint8_t ra[6], uint8_t res[16]); bool bt_crypto_s1(struct bt_crypto *crypto, const uint8_t k[16], const uint8_t r1[16], const uint8_t r2[16], uint8_t res[16]); bool bt_crypto_f4(struct bt_crypto *crypto, uint8_t u[32], uint8_t v[32], uint8_t x[16], uint8_t z, uint8_t res[16]); bool bt_crypto_f5(struct bt_crypto *crypto, uint8_t w[32], uint8_t n1[16], uint8_t n2[16], uint8_t a1[7], uint8_t a2[7], uint8_t mackey[16], uint8_t ltk[16]); bool bt_crypto_f6(struct bt_crypto *crypto, uint8_t w[16], uint8_t n1[16], uint8_t n2[16], uint8_t r[16], uint8_t io_cap[3], uint8_t a1[7], uint8_t a2[7], uint8_t res[16]); bool bt_crypto_g2(struct bt_crypto *crypto, uint8_t u[32], uint8_t v[32], uint8_t x[16], uint8_t y[16], uint32_t *val); bool bt_crypto_h6(struct bt_crypto *crypto, const uint8_t w[16], const uint8_t keyid[4], uint8_t res[16]); bool bt_crypto_sign_att(struct bt_crypto *crypto, const uint8_t key[16], const uint8_t *m, uint16_t m_len, uint32_t sign_cnt, uint8_t signature[12]); bool bt_crypto_verify_att_sign(struct bt_crypto *crypto, const uint8_t key[16], const uint8_t *pdu, uint16_t pdu_len); bool bt_crypto_gatt_hash(struct bt_crypto *crypto, struct iovec *iov, size_t iov_len, uint8_t res[16]); bool bt_crypto_sef(struct bt_crypto *crypto, const uint8_t k[16], const uint8_t sirk[16], uint8_t out[16]); bool bt_crypto_sih(struct bt_crypto *crypto, const uint8_t k[16], const uint8_t r[3], uint8_t hash[3]); bool bt_crypto_sirk(struct bt_crypto *crypto, const char *str, uint16_t vendor, uint16_t product, uint16_t version, uint16_t source, uint8_t sirk[16]); bool bt_crypto_rsi(struct bt_crypto *crypto, const uint8_t sirk[16], uint8_t rsi[6]); bluez-5.87/src/shared/PaxHeaders/gap.h0000644000000000000000000000005014015011623014621 xustar0020 atime=1783092929 20 ctime=1783094100 bluez-5.87/src/shared/gap.h0000644000000000000000000000201314015011623014276 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #include #include #define BT_GAP_ADDR_TYPE_BREDR 0x00 #define BT_GAP_ADDR_TYPE_LE_PUBLIC 0x01 #define BT_GAP_ADDR_TYPE_LE_RANDOM 0x02 struct bt_gap; struct bt_gap *bt_gap_new_default(void); struct bt_gap *bt_gap_new_index(uint16_t index); struct bt_gap *bt_gap_ref(struct bt_gap *gap); void bt_gap_unref(struct bt_gap *gap); typedef void (*bt_gap_destroy_func_t)(void *user_data); typedef void (*bt_gap_ready_func_t)(bool success, void *user_data); bool bt_gap_set_ready_handler(struct bt_gap *gap, bt_gap_ready_func_t handler, void *user_data, bt_gap_destroy_func_t destroy); bool bt_gap_set_static_addr(struct bt_gap *gap, uint8_t addr[6]); bool bt_gap_set_local_irk(struct bt_gap *gap, uint8_t key[16]); bool bt_gap_add_peer_irk(struct bt_gap *gap, uint8_t addr_type, uint8_t addr[6], uint8_t key[16]); bluez-5.87/src/shared/PaxHeaders/ringbuf.h0000644000000000000000000000005014015011623015506 xustar0020 atime=1783092890 20 ctime=1783094099 bluez-5.87/src/shared/ringbuf.h0000644000000000000000000000217114015011623015170 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #include #include #include typedef void (*ringbuf_tracing_func_t)(const void *buf, size_t count, void *user_data); struct ringbuf; struct ringbuf *ringbuf_new(size_t size); void ringbuf_free(struct ringbuf *ringbuf); bool ringbuf_set_input_tracing(struct ringbuf *ringbuf, ringbuf_tracing_func_t callback, void *user_data); size_t ringbuf_capacity(struct ringbuf *ringbuf); size_t ringbuf_len(struct ringbuf *ringbuf); size_t ringbuf_drain(struct ringbuf *ringbuf, size_t count); void *ringbuf_peek(struct ringbuf *ringbuf, size_t offset, size_t *len_nowrap); ssize_t ringbuf_write(struct ringbuf *ringbuf, int fd); size_t ringbuf_avail(struct ringbuf *ringbuf); int ringbuf_printf(struct ringbuf *ringbuf, const char *format, ...) __attribute__((format(printf, 2, 3))); int ringbuf_vprintf(struct ringbuf *ringbuf, const char *format, va_list ap); ssize_t ringbuf_read(struct ringbuf *ringbuf, int fd); bluez-5.87/src/shared/PaxHeaders/tmap.c0000644000000000000000000000005015142177620015022 xustar0020 atime=1783092965 20 ctime=1783094100 bluez-5.87/src/shared/tmap.c0000644000000000000000000001440015142177620014502 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2025 Pauli Virtanen. All rights reserved. * */ #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "src/shared/queue.h" #include "src/shared/util.h" #include "src/shared/timeout.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-server.h" #include "src/shared/gatt-client.h" #include "src/shared/tmap.h" #include "src/shared/bap.h" #define DBG(_tmap, fmt, arg...) \ tmap_debug(_tmap, "%s:%s() " fmt, __FILE__, __func__, ## arg) struct bt_tmas_db { struct gatt_db *db; struct gatt_db_attribute *service; struct gatt_db_attribute *role; uint16_t role_value; }; struct bt_tmap { int ref_count; struct bt_gatt_client *client; struct bt_tmas_db db; int idle_id; bt_tmap_ready_func_t ready_func; void *ready_data; bt_tmap_debug_func_t debug_func; bt_tmap_destroy_func_t debug_destroy; void *debug_data; }; static struct queue *instances; static void tmap_free(void *data) { struct bt_tmap *tmap = data; if (tmap->client) { bt_gatt_client_idle_unregister(tmap->client, tmap->idle_id); bt_gatt_client_unref(tmap->client); } else { gatt_db_remove_service(tmap->db.db, tmap->db.service); gatt_db_unref(tmap->db.db); } queue_remove(instances, tmap); if (queue_isempty(instances)) { queue_destroy(instances, NULL); instances = NULL; } free(tmap); } struct bt_tmap *bt_tmap_ref(struct bt_tmap *tmap) { if (!tmap) return NULL; __sync_fetch_and_add(&tmap->ref_count, 1); return tmap; } void bt_tmap_unref(struct bt_tmap *tmap) { if (!tmap) return; if (__sync_sub_and_fetch(&tmap->ref_count, 1)) return; tmap_free(tmap); } static void tmap_debug(struct bt_tmap *tmap, const char *format, ...) { va_list ap; if (!tmap || !format || !tmap->debug_func) return; va_start(ap, format); util_debug_va(tmap->debug_func, tmap->debug_data, format, ap); va_end(ap); } bool bt_tmap_set_debug(struct bt_tmap *tmap, bt_tmap_debug_func_t cb, void *user_data, bt_tmap_destroy_func_t destroy) { if (!tmap) return false; if (tmap->debug_destroy) tmap->debug_destroy(tmap->debug_data); tmap->debug_func = cb; tmap->debug_destroy = destroy; tmap->debug_data = user_data; return true; } uint16_t bt_tmap_get_role(struct bt_tmap *tmap) { if (!tmap) return 0; return tmap->db.role_value; } /* * TMA Client */ static void tmap_role_read(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_tmap *tmap = user_data; struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; uint16_t role; if (!success) { DBG(tmap, "Unable to read Role: error 0x%02x", att_ecode); return; } if (!util_iov_pull_le16(&iov, &role)) { DBG(tmap, "Invalid Role"); return; } DBG(tmap, "Role 0x%x", role); tmap->db.role_value = role & BT_TMAP_ROLE_MASK; } static void foreach_tmap_char(struct gatt_db_attribute *attr, void *user_data) { struct bt_tmap *tmap = user_data; uint16_t value_handle; bt_uuid_t uuid, uuid_role; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) return; bt_uuid16_create(&uuid_role, TMAP_ROLE_CHRC_UUID); if (!bt_uuid_cmp(&uuid, &uuid_role)) { DBG(tmap, "TMAS Role Char found: handle 0x%04x", value_handle); bt_gatt_client_read_value(tmap->client, value_handle, tmap_role_read, tmap, NULL); return; } } static void foreach_tmap_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_tmap *tmap = user_data; gatt_db_service_set_claimed(attr, true); gatt_db_service_foreach_char(attr, foreach_tmap_char, tmap); } static void tmap_idle(void *data) { struct bt_tmap *tmap = data; tmap->idle_id = 0; if (!instances) instances = queue_new(); queue_push_tail(instances, tmap); if (tmap->ready_func) tmap->ready_func(tmap, tmap->ready_data); } struct bt_tmap *bt_tmap_attach(struct bt_gatt_client *client, bt_tmap_ready_func_t ready, void *user_data) { struct bt_tmap *tmap; bt_uuid_t uuid; if (!client) return NULL; client = bt_gatt_client_clone(client); if (!client) return NULL; tmap = new0(struct bt_tmap, 1); tmap->client = client; tmap->ready_func = ready; tmap->ready_data = user_data; tmap->db.db = bt_gatt_client_get_db(tmap->client); bt_uuid16_create(&uuid, TMAS_UUID); gatt_db_foreach_service(tmap->db.db, &uuid, foreach_tmap_service, tmap); tmap->idle_id = bt_gatt_client_idle_register(tmap->client, tmap_idle, tmap, NULL); return bt_tmap_ref(tmap); } /* * TMAS Service */ static void tmas_role_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_tmas_db *db = user_data; uint8_t role[2]; put_le16(db->role_value, role); gatt_db_attribute_read_result(attrib, id, 0, role, sizeof(role)); } static bool match_db(const void *data, const void *match_data) { const struct bt_tmap *tmap = data; return tmap->db.db == match_data; } struct bt_tmap *bt_tmap_find(struct gatt_db *db) { return db ? queue_find(instances, match_db, db) : NULL; } struct bt_tmap *bt_tmap_add_db(struct gatt_db *db) { struct bt_tmap *tmap; bt_uuid_t uuid; if (!db || queue_find(instances, match_db, db)) return NULL; tmap = new0(struct bt_tmap, 1); tmap->db.db = gatt_db_ref(db); bt_uuid16_create(&uuid, TMAS_UUID); tmap->db.service = gatt_db_add_service(db, &uuid, true, 3); bt_uuid16_create(&uuid, TMAP_ROLE_CHRC_UUID); tmap->db.role = gatt_db_service_add_characteristic( tmap->db.service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, tmas_role_read, NULL, &tmap->db); if (!instances) instances = queue_new(); queue_push_tail(instances, tmap); return bt_tmap_ref(tmap); } void bt_tmap_set_role(struct bt_tmap *tmap, uint16_t role) { if (!tmap || tmap->client) return; role &= BT_TMAP_ROLE_MASK; if (role == tmap->db.role_value) return; DBG(tmap, "set role 0x%02x", role); tmap->db.role_value = role; /* Expose when first set and present. Role does not have Notify. */ gatt_db_service_set_active(tmap->db.service, role != 0); } bluez-5.87/src/shared/PaxHeaders/io-mainloop.c0000644000000000000000000000005015017252326016302 xustar0020 atime=1783093256 20 ctime=1783094100 bluez-5.87/src/shared/io-mainloop.c0000644000000000000000000001310615017252326015764 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "src/shared/mainloop.h" #include "src/shared/util.h" #include "src/shared/io.h" struct io { int ref_count; int fd; uint32_t events; bool close_on_destroy; io_callback_func_t read_callback; io_destroy_func_t read_destroy; void *read_data; io_callback_func_t write_callback; io_destroy_func_t write_destroy; void *write_data; io_callback_func_t disconnect_callback; io_destroy_func_t disconnect_destroy; void *disconnect_data; }; static struct io *io_ref(struct io *io) { if (!io) return NULL; __sync_fetch_and_add(&io->ref_count, 1); return io; } static void io_unref(struct io *io) { if (!io) return; if (__sync_sub_and_fetch(&io->ref_count, 1)) return; free(io); } static void io_cleanup(void *user_data) { struct io *io = user_data; if (io->write_destroy) io->write_destroy(io->write_data); if (io->read_destroy) io->read_destroy(io->read_data); if (io->disconnect_destroy) io->disconnect_destroy(io->disconnect_data); if (io->close_on_destroy) close(io->fd); io->fd = -1; } static void io_callback(int fd, uint32_t events, void *user_data) { struct io *io = user_data; io_ref(io); if ((events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR))) { io->read_callback = NULL; io->write_callback = NULL; if (!io->disconnect_callback) { mainloop_remove_fd(io->fd); io_unref(io); return; } if (!io->disconnect_callback(io, io->disconnect_data)) { if (io->disconnect_destroy) io->disconnect_destroy(io->disconnect_data); io->disconnect_callback = NULL; io->disconnect_destroy = NULL; io->disconnect_data = NULL; io->events &= ~EPOLLRDHUP; mainloop_modify_fd(io->fd, io->events); } } if ((events & EPOLLIN) && io->read_callback) { if (!io->read_callback(io, io->read_data)) { if (io->read_destroy) io->read_destroy(io->read_data); io->read_callback = NULL; io->read_destroy = NULL; io->read_data = NULL; io->events &= ~EPOLLIN; mainloop_modify_fd(io->fd, io->events); } } if ((events & EPOLLOUT) && io->write_callback) { if (!io->write_callback(io, io->write_data)) { if (io->write_destroy) io->write_destroy(io->write_data); io->write_callback = NULL; io->write_destroy = NULL; io->write_data = NULL; io->events &= ~EPOLLOUT; mainloop_modify_fd(io->fd, io->events); } } io_unref(io); } struct io *io_new(int fd) { struct io *io; if (fd < 0) return NULL; io = new0(struct io, 1); io->fd = fd; io->events = 0; io->close_on_destroy = false; if (mainloop_add_fd(io->fd, io->events, io_callback, io, io_cleanup) < 0) { free(io); return NULL; } return io_ref(io); } void io_destroy(struct io *io) { if (!io) return; io->read_callback = NULL; io->write_callback = NULL; io->disconnect_callback = NULL; mainloop_remove_fd(io->fd); io_unref(io); } int io_get_fd(struct io *io) { if (!io) return -ENOTCONN; return io->fd; } bool io_set_close_on_destroy(struct io *io, bool do_close) { if (!io) return false; io->close_on_destroy = do_close; return true; } bool io_set_ignore_errqueue(struct io *io, bool do_ignore) { /* TODO: unimplemented */ return false; } bool io_set_read_handler(struct io *io, io_callback_func_t callback, void *user_data, io_destroy_func_t destroy) { uint32_t events; if (!io || io->fd < 0) return false; if (io->read_destroy) io->read_destroy(io->read_data); if (callback) events = io->events | EPOLLIN; else events = io->events & ~EPOLLIN; io->read_callback = callback; io->read_destroy = destroy; io->read_data = user_data; if (events == io->events) return true; if (mainloop_modify_fd(io->fd, events) < 0) return false; io->events = events; return true; } bool io_set_write_handler(struct io *io, io_callback_func_t callback, void *user_data, io_destroy_func_t destroy) { uint32_t events; if (!io || io->fd < 0) return false; if (io->write_destroy) io->write_destroy(io->write_data); if (callback) events = io->events | EPOLLOUT; else events = io->events & ~EPOLLOUT; io->write_callback = callback; io->write_destroy = destroy; io->write_data = user_data; if (events == io->events) return true; if (mainloop_modify_fd(io->fd, events) < 0) return false; io->events = events; return true; } bool io_set_disconnect_handler(struct io *io, io_callback_func_t callback, void *user_data, io_destroy_func_t destroy) { uint32_t events; if (!io || io->fd < 0) return false; if (io->disconnect_destroy) io->disconnect_destroy(io->disconnect_data); if (callback) events = io->events | EPOLLRDHUP; else events = io->events & ~EPOLLRDHUP; io->disconnect_callback = callback; io->disconnect_destroy = destroy; io->disconnect_data = user_data; if (events == io->events) return true; if (mainloop_modify_fd(io->fd, events) < 0) return false; io->events = events; return true; } ssize_t io_send(struct io *io, const struct iovec *iov, int iovcnt) { ssize_t ret; if (!io || io->fd < 0) return -ENOTCONN; do { ret = writev(io->fd, iov, iovcnt); } while (ret < 0 && errno == EINTR); if (ret < 0) return -errno; return ret; } bool io_shutdown(struct io *io) { if (!io || io->fd < 0) return false; return shutdown(io->fd, SHUT_RDWR) == 0; } unsigned int io_glib_add_err_watch(void *giochannel, io_glib_err_func_t func, void *user_data) { return 0; } bluez-5.87/src/shared/PaxHeaders/btsnoop.c0000644000000000000000000000005015200607060015534 xustar0020 atime=1783092683 20 ctime=1783094099 bluez-5.87/src/shared/btsnoop.c0000644000000000000000000003003515200607060015216 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "src/shared/btsnoop.h" struct btsnoop_hdr { uint8_t id[8]; /* Identification Pattern */ uint32_t version; /* Version Number = 1 */ uint32_t type; /* Datalink Type */ } __attribute__ ((packed)); #define BTSNOOP_HDR_SIZE (sizeof(struct btsnoop_hdr)) struct btsnoop_pkt { uint32_t size; /* Original Length */ uint32_t len; /* Included Length */ uint32_t flags; /* Packet Flags */ uint32_t drops; /* Cumulative Drops */ uint64_t ts; /* Timestamp microseconds */ uint8_t data[0]; /* Packet Data */ } __attribute__ ((packed)); #define BTSNOOP_PKT_SIZE (sizeof(struct btsnoop_pkt)) static const uint8_t btsnoop_id[] = { 0x62, 0x74, 0x73, 0x6e, 0x6f, 0x6f, 0x70, 0x00 }; static const uint32_t btsnoop_version = 1; struct pklg_pkt { uint32_t len; uint64_t ts; uint8_t type; } __attribute__ ((packed)); #define PKLG_PKT_SIZE (sizeof(struct pklg_pkt)) struct btsnoop { int ref_count; int fd; unsigned long flags; uint32_t format; uint16_t index; bool aborted; bool pklg_format; bool pklg_v2; const char *path; size_t max_size; size_t cur_size; unsigned int max_count; unsigned int cur_count; }; struct btsnoop *btsnoop_open(const char *path, unsigned long flags) { struct btsnoop *btsnoop; struct btsnoop_hdr hdr; ssize_t len; btsnoop = calloc(1, sizeof(*btsnoop)); if (!btsnoop) return NULL; btsnoop->fd = open(path, O_RDONLY | O_CLOEXEC); if (btsnoop->fd < 0) { free(btsnoop); return NULL; } btsnoop->flags = flags; len = read(btsnoop->fd, &hdr, BTSNOOP_HDR_SIZE); if (len < 0 || len != BTSNOOP_HDR_SIZE) goto failed; if (!memcmp(hdr.id, btsnoop_id, sizeof(btsnoop_id))) { /* Check for BTSnoop version 1 format */ if (be32toh(hdr.version) != btsnoop_version) goto failed; btsnoop->format = be32toh(hdr.type); btsnoop->index = 0xffff; } else { if (!(btsnoop->flags & BTSNOOP_FLAG_PKLG_SUPPORT)) goto failed; if (hdr.id[0] == 0x00 && (hdr.id[1] == 0x00 || hdr.id[1] == 0x01)) { /* Apple Packet Logger format (big-endian) */ btsnoop->format = BTSNOOP_FORMAT_MONITOR; btsnoop->index = 0xffff; btsnoop->pklg_format = true; btsnoop->pklg_v2 = false; } else if (hdr.id[3] == 0x00 && (hdr.id[2] == 0x00 || hdr.id[2] == 0x01)) { /* Apple Packet Logger format (little-endian) */ btsnoop->format = BTSNOOP_FORMAT_MONITOR; btsnoop->index = 0xffff; btsnoop->pklg_format = true; btsnoop->pklg_v2 = true; } else { goto failed; } /* Apple Packet Logger format has no header */ lseek(btsnoop->fd, 0, SEEK_SET); } return btsnoop_ref(btsnoop); failed: close(btsnoop->fd); free(btsnoop); return NULL; } struct btsnoop *btsnoop_create(const char *path, size_t max_size, unsigned int max_count, uint32_t format) { struct btsnoop *btsnoop; struct btsnoop_hdr hdr; const char *real_path; char tmp[PATH_MAX]; ssize_t written; if (!max_size && max_count) return NULL; btsnoop = calloc(1, sizeof(*btsnoop)); if (!btsnoop) return NULL; /* If max file size is specified, always add counter to file path */ if (max_size) { snprintf(tmp, PATH_MAX, "%s.0", path); real_path = tmp; } else { real_path = path; } btsnoop->fd = open(real_path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); if (btsnoop->fd < 0) { free(btsnoop); return NULL; } btsnoop->format = format; btsnoop->index = 0xffff; btsnoop->path = path; btsnoop->max_count = max_count; btsnoop->max_size = max_size; memcpy(hdr.id, btsnoop_id, sizeof(btsnoop_id)); hdr.version = htobe32(btsnoop_version); hdr.type = htobe32(btsnoop->format); written = write(btsnoop->fd, &hdr, BTSNOOP_HDR_SIZE); if (written < 0) { close(btsnoop->fd); free(btsnoop); return NULL; } btsnoop->cur_size = BTSNOOP_HDR_SIZE; return btsnoop_ref(btsnoop); } struct btsnoop *btsnoop_ref(struct btsnoop *btsnoop) { if (!btsnoop) return NULL; __sync_fetch_and_add(&btsnoop->ref_count, 1); return btsnoop; } void btsnoop_unref(struct btsnoop *btsnoop) { if (!btsnoop) return; if (__sync_sub_and_fetch(&btsnoop->ref_count, 1)) return; if (btsnoop->fd >= 0) close(btsnoop->fd); free(btsnoop); } uint32_t btsnoop_get_format(struct btsnoop *btsnoop) { if (!btsnoop) return BTSNOOP_FORMAT_INVALID; return btsnoop->format; } static bool btsnoop_rotate(struct btsnoop *btsnoop) { struct btsnoop_hdr hdr; char path[PATH_MAX]; ssize_t written; close(btsnoop->fd); /* Check if max number of log files has been reached */ if (btsnoop->max_count && btsnoop->cur_count >= btsnoop->max_count) { snprintf(path, PATH_MAX, "%s.%u", btsnoop->path, btsnoop->cur_count - btsnoop->max_count); unlink(path); } snprintf(path, PATH_MAX,"%s.%u", btsnoop->path, btsnoop->cur_count); btsnoop->cur_count++; btsnoop->fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); if (btsnoop->fd < 0) return false; memcpy(hdr.id, btsnoop_id, sizeof(btsnoop_id)); hdr.version = htobe32(btsnoop_version); hdr.type = htobe32(btsnoop->format); written = write(btsnoop->fd, &hdr, BTSNOOP_HDR_SIZE); if (written < 0) return false; btsnoop->cur_size = BTSNOOP_HDR_SIZE; return true; } bool btsnoop_write(struct btsnoop *btsnoop, struct timeval *tv, uint32_t flags, uint32_t drops, const void *data, uint16_t size) { struct btsnoop_pkt pkt; uint64_t ts; ssize_t written; if (!btsnoop || !tv) return false; if (btsnoop->max_size && btsnoop->max_size <= btsnoop->cur_size + size + BTSNOOP_PKT_SIZE) if (!btsnoop_rotate(btsnoop)) return false; ts = (tv->tv_sec - 946684800ll) * 1000000ll + tv->tv_usec; pkt.size = htobe32(size); pkt.len = htobe32(size); pkt.flags = htobe32(flags); pkt.drops = htobe32(drops); pkt.ts = htobe64(ts + 0x00E03AB44A676000ll); written = write(btsnoop->fd, &pkt, BTSNOOP_PKT_SIZE); if (written < 0) return false; btsnoop->cur_size += BTSNOOP_PKT_SIZE; if (data && size > 0) { written = write(btsnoop->fd, data, size); if (written < 0) return false; } btsnoop->cur_size += size; return true; } static uint32_t get_flags_from_opcode(uint16_t opcode) { switch (opcode) { case BTSNOOP_OPCODE_NEW_INDEX: case BTSNOOP_OPCODE_DEL_INDEX: break; case BTSNOOP_OPCODE_COMMAND_PKT: return 0x02; case BTSNOOP_OPCODE_EVENT_PKT: return 0x03; case BTSNOOP_OPCODE_ACL_TX_PKT: return 0x00; case BTSNOOP_OPCODE_ACL_RX_PKT: return 0x01; case BTSNOOP_OPCODE_SCO_TX_PKT: case BTSNOOP_OPCODE_SCO_RX_PKT: break; case BTSNOOP_OPCODE_ISO_TX_PKT: case BTSNOOP_OPCODE_ISO_RX_PKT: break; case BTSNOOP_OPCODE_OPEN_INDEX: case BTSNOOP_OPCODE_CLOSE_INDEX: break; } return 0xff; } bool btsnoop_write_hci(struct btsnoop *btsnoop, struct timeval *tv, uint16_t index, uint16_t opcode, uint32_t drops, const void *data, uint16_t size) { uint32_t flags; if (!btsnoop) return false; switch (btsnoop->format) { case BTSNOOP_FORMAT_HCI: if (btsnoop->index == 0xffff) btsnoop->index = index; if (index != btsnoop->index) return false; flags = get_flags_from_opcode(opcode); if (flags == 0xff) return false; break; case BTSNOOP_FORMAT_MONITOR: flags = ((uint32_t)index << 16) | opcode; break; default: return false; } return btsnoop_write(btsnoop, tv, flags, drops, data, size); } bool btsnoop_write_phy(struct btsnoop *btsnoop, struct timeval *tv, uint16_t frequency, const void *data, uint16_t size) { uint32_t flags; if (!btsnoop) return false; switch (btsnoop->format) { case BTSNOOP_FORMAT_SIMULATOR: flags = (1 << 16) | frequency; break; default: return false; } return btsnoop_write(btsnoop, tv, flags, 0, data, size); } static bool pklg_read_hci(struct btsnoop *btsnoop, struct timeval *tv, uint16_t *index, uint16_t *opcode, void *data, uint16_t *size) { struct pklg_pkt pkt; uint32_t toread; uint64_t ts; ssize_t len; len = read(btsnoop->fd, &pkt, PKLG_PKT_SIZE); if (len == 0) return false; if (len < 0 || len != PKLG_PKT_SIZE) { btsnoop->aborted = true; return false; } if (btsnoop->pklg_v2) { toread = le32toh(pkt.len) - (PKLG_PKT_SIZE - 4); ts = le64toh(pkt.ts); tv->tv_sec = ts & 0xffffffff; tv->tv_usec = ts >> 32; } else { toread = be32toh(pkt.len) - (PKLG_PKT_SIZE - 4); ts = be64toh(pkt.ts); tv->tv_sec = ts >> 32; tv->tv_usec = ts & 0xffffffff; } if (toread > BTSNOOP_MAX_PACKET_SIZE) { btsnoop->aborted = true; return false; } switch (pkt.type) { case 0x00: *index = 0x0000; *opcode = BTSNOOP_OPCODE_COMMAND_PKT; break; case 0x01: *index = 0x0000; *opcode = BTSNOOP_OPCODE_EVENT_PKT; break; case 0x02: *index = 0x0000; *opcode = BTSNOOP_OPCODE_ACL_TX_PKT; break; case 0x03: *index = 0x0000; *opcode = BTSNOOP_OPCODE_ACL_RX_PKT; break; case 0x08: *index = 0x0000; *opcode = BTSNOOP_OPCODE_SCO_TX_PKT; break; case 0x09: *index = 0x0000; *opcode = BTSNOOP_OPCODE_SCO_RX_PKT; break; case 0x12: *index = 0x0000; *opcode = BTSNOOP_OPCODE_ISO_TX_PKT; break; case 0x13: *index = 0x0000; *opcode = BTSNOOP_OPCODE_ISO_RX_PKT; break; case 0x0b: *index = 0x0000; *opcode = BTSNOOP_OPCODE_VENDOR_DIAG; break; case 0xfc: *index = 0xffff; *opcode = BTSNOOP_OPCODE_SYSTEM_NOTE; break; default: *index = 0xffff; *opcode = 0xffff; break; } len = read(btsnoop->fd, data, toread); if (len < 0) { btsnoop->aborted = true; return false; } *size = toread; return true; } static uint16_t get_opcode_from_flags(uint8_t type, uint32_t flags) { switch (type) { case 0x01: return BTSNOOP_OPCODE_COMMAND_PKT; case 0x02: if (flags & 0x01) return BTSNOOP_OPCODE_ACL_RX_PKT; else return BTSNOOP_OPCODE_ACL_TX_PKT; case 0x03: if (flags & 0x01) return BTSNOOP_OPCODE_SCO_RX_PKT; else return BTSNOOP_OPCODE_SCO_TX_PKT; case 0x04: return BTSNOOP_OPCODE_EVENT_PKT; case 0x05: if (flags & 0x01) return BTSNOOP_OPCODE_ISO_RX_PKT; else return BTSNOOP_OPCODE_ISO_TX_PKT; case 0xff: if (flags & 0x02) { if (flags & 0x01) return BTSNOOP_OPCODE_EVENT_PKT; else return BTSNOOP_OPCODE_COMMAND_PKT; } else { if (flags & 0x01) return BTSNOOP_OPCODE_ACL_RX_PKT; else return BTSNOOP_OPCODE_ACL_TX_PKT; } break; } return 0xffff; } bool btsnoop_read_hci(struct btsnoop *btsnoop, struct timeval *tv, uint16_t *index, uint16_t *opcode, void *data, uint16_t *size) { struct btsnoop_pkt pkt; uint32_t toread, flags; uint64_t ts; uint8_t pkt_type; ssize_t len; if (!btsnoop || btsnoop->aborted) return false; if (btsnoop->pklg_format) return pklg_read_hci(btsnoop, tv, index, opcode, data, size); len = read(btsnoop->fd, &pkt, BTSNOOP_PKT_SIZE); if (len == 0) return false; if (len < 0 || len != BTSNOOP_PKT_SIZE) { btsnoop->aborted = true; return false; } toread = be32toh(pkt.len); if (toread > BTSNOOP_MAX_PACKET_SIZE) { btsnoop->aborted = true; return false; } flags = be32toh(pkt.flags); ts = be64toh(pkt.ts) - 0x00E03AB44A676000ll; tv->tv_sec = (ts / 1000000ll) + 946684800ll; tv->tv_usec = ts % 1000000ll; switch (btsnoop->format) { case BTSNOOP_FORMAT_HCI: *index = 0; *opcode = get_opcode_from_flags(0xff, flags); break; case BTSNOOP_FORMAT_UART: len = read(btsnoop->fd, &pkt_type, 1); if (len < 0) { btsnoop->aborted = true; return false; } toread--; *index = 0; *opcode = get_opcode_from_flags(pkt_type, flags); break; case BTSNOOP_FORMAT_MONITOR: *index = flags >> 16; *opcode = flags & 0xffff; break; default: btsnoop->aborted = true; return false; } len = read(btsnoop->fd, data, toread); if (len < 0) { btsnoop->aborted = true; return false; } *size = toread; return true; } bool btsnoop_read_phy(struct btsnoop *btsnoop, struct timeval *tv, uint16_t *frequency, void *data, uint16_t *size) { return false; } bluez-5.87/src/shared/PaxHeaders/tty.h0000644000000000000000000000005014015011623014672 xustar0020 atime=1783092991 20 ctime=1783094100 bluez-5.87/src/shared/tty.h0000644000000000000000000000177614015011623014366 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2016 Intel Corporation. All rights reserved. * * */ #include static inline unsigned int tty_get_speed(int speed) { switch (speed) { case 9600: return B9600; case 19200: return B19200; case 38400: return B38400; case 57600: return B57600; case 115200: return B115200; case 230400: return B230400; case 460800: return B460800; case 500000: return B500000; case 576000: return B576000; case 921600: return B921600; case 1000000: return B1000000; case 1152000: return B1152000; case 1500000: return B1500000; case 2000000: return B2000000; #ifdef B2500000 case 2500000: return B2500000; #endif #ifdef B3000000 case 3000000: return B3000000; #endif #ifdef B3500000 case 3500000: return B3500000; #endif #ifdef B3710000 case 3710000: return B3710000; #endif #ifdef B4000000 case 4000000: return B4000000; #endif } return 0; } bluez-5.87/src/shared/PaxHeaders/timeout-glib.c0000644000000000000000000000005014031556405016460 xustar0020 atime=1783092981 20 ctime=1783094100 bluez-5.87/src/shared/timeout-glib.c0000644000000000000000000000351514031556405016145 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * */ #include "timeout.h" #include struct timeout_data { timeout_func_t func; timeout_destroy_func_t destroy; void *user_data; }; static gboolean timeout_callback(gpointer user_data) { struct timeout_data *data = user_data; if (data->func(data->user_data)) return TRUE; return FALSE; } static void timeout_destroy(gpointer user_data) { struct timeout_data *data = user_data; if (data->destroy) data->destroy(data->user_data); g_free(data); } unsigned int timeout_add(unsigned int timeout, timeout_func_t func, void *user_data, timeout_destroy_func_t destroy) { struct timeout_data *data; guint id; data = g_try_new0(struct timeout_data, 1); if (!data) return 0; data->func = func; data->destroy = destroy; data->user_data = user_data; id = g_timeout_add_full(G_PRIORITY_DEFAULT, timeout, timeout_callback, data, timeout_destroy); if (!id) g_free(data); return id; } void timeout_remove(unsigned int id) { GSource *source; if (!id) return; source = g_main_context_find_source_by_id(NULL, id); if (source) g_source_destroy(source); } unsigned int timeout_add_seconds(unsigned int timeout, timeout_func_t func, void *user_data, timeout_destroy_func_t destroy) { struct timeout_data *data; guint id; data = g_try_new0(struct timeout_data, 1); if (!data) return 0; data->func = func; data->destroy = destroy; data->user_data = user_data; if (!timeout) id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, timeout_callback, data, timeout_destroy); else id = g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, timeout, timeout_callback, data, timeout_destroy); if (!id) g_free(data); return id; } bluez-5.87/src/shared/PaxHeaders/csip.c0000644000000000000000000000005015200607060015006 xustar0020 atime=1783092683 20 ctime=1783094100 bluez-5.87/src/shared/csip.c0000644000000000000000000004106215200607060014472 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. * */ #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "src/shared/queue.h" #include "src/shared/util.h" #include "src/shared/timeout.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-server.h" #include "src/shared/gatt-client.h" #include "src/shared/crypto.h" #include "src/shared/csip.h" #define DBG(_csip, fmt, arg...) \ csip_debug(_csip, "%s:%s() " fmt, __FILE__, __func__, ## arg) /* SIRK is now hardcoded in the code. This can be moved * to a configuration file. Since the code is to validate * the CSIP use case of set member */ #define SIRK "761FAE703ED681F0C50B34155B6434FB" #define CSIS_SIZE 0x02 #define CSIS_LOCK 0x01 #define CSIS_RANK 0x01 #define CSIS_PLAINTEXT 0x01 #define CSIS_ENC 0x02 struct bt_csip_db { struct gatt_db *db; struct bt_csis *csis; }; struct csis_sirk { uint8_t type; uint8_t val[16]; } __packed; struct bt_csis { struct bt_csip_db *cdb; struct csis_sirk *sirk_val; uint8_t size_val; uint8_t lock_val; uint8_t rank_val; struct gatt_db_attribute *service; struct gatt_db_attribute *sirk; struct gatt_db_attribute *size; struct gatt_db_attribute *lock; struct gatt_db_attribute *lock_ccc; struct gatt_db_attribute *rank; bt_csip_encrypt_func_t encrypt; }; struct bt_csip_cb { unsigned int id; bt_csip_func_t attached; bt_csip_func_t detached; void *user_data; }; struct bt_csip_ready { unsigned int id; bt_csip_ready_func_t func; bt_csip_destroy_func_t destroy; void *data; }; struct bt_csip { int ref_count; struct bt_csip_db *ldb; struct bt_csip_db *rdb; struct bt_gatt_client *client; struct bt_att *att; unsigned int idle_id; struct queue *ready_cbs; bt_csip_debug_func_t debug_func; bt_csip_destroy_func_t debug_destroy; void *debug_data; bt_csip_sirk_func_t sirk_func; void *sirk_data; void *user_data; }; static struct queue *csip_db; static struct queue *csip_cbs; static struct queue *sessions; static void csip_detached(void *data, void *user_data) { struct bt_csip_cb *cb = data; struct bt_csip *csip = user_data; cb->detached(csip, cb->user_data); } void bt_csip_detach(struct bt_csip *csip) { if (!queue_remove(sessions, csip)) return; bt_gatt_client_idle_unregister(csip->client, csip->idle_id); bt_gatt_client_unref(csip->client); csip->client = NULL; queue_foreach(csip_cbs, csip_detached, csip); } static void csis_free(struct bt_csis *csis) { if (!csis) return; free(csis->sirk_val); free(csis); } static void csip_db_free(void *data) { struct bt_csip_db *cdb = data; if (!cdb) return; gatt_db_unref(cdb->db); csis_free(cdb->csis); free(cdb); } static void csip_ready_free(void *data) { struct bt_csip_ready *ready = data; if (ready->destroy) ready->destroy(ready->data); free(ready); } static void csip_free(void *data) { struct bt_csip *csip = data; bt_csip_detach(csip); csip_db_free(csip->rdb); queue_destroy(csip->ready_cbs, csip_ready_free); free(csip); } struct bt_att *bt_csip_get_att(struct bt_csip *csip) { if (!csip) return NULL; if (csip->att) return csip->att; return bt_gatt_client_get_att(csip->client); } struct bt_csip *bt_csip_ref(struct bt_csip *csip) { if (!csip) return NULL; __sync_fetch_and_add(&csip->ref_count, 1); return csip; } static struct bt_csip *bt_csip_ref_safe(struct bt_csip *csip) { if (!csip || !csip->ref_count) return NULL; return bt_csip_ref(csip); } void bt_csip_unref(struct bt_csip *csip) { if (!csip) return; if (__sync_sub_and_fetch(&csip->ref_count, 1)) return; csip_free(csip); } static void csip_debug(struct bt_csip *csip, const char *format, ...) { va_list ap; if (!csip || !format || !csip->debug_func) return; va_start(ap, format); util_debug_va(csip->debug_func, csip->debug_data, format, ap); va_end(ap); } static void csis_sirk_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_csis *csis = user_data; struct csis_sirk sirk; struct iovec iov; memcpy(&sirk, csis->sirk_val, sizeof(sirk)); if (sirk.type == BT_CSIP_SIRK_ENCRYPT && !csis->encrypt(att, sirk.val)) { gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_UNLIKELY, NULL, 0); return; } iov.iov_base = &sirk; iov.iov_len = sizeof(sirk); gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, iov.iov_len); } static void csis_size_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_csis *csis = user_data; struct iovec iov; iov.iov_base = &csis->size_val; iov.iov_len = sizeof(csis->size_val); gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, iov.iov_len); } static void csis_lock_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { uint8_t value = CSIS_LOCK; gatt_db_attribute_read_result(attrib, id, 0, &value, sizeof(value)); } static void csis_lock_write_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { gatt_db_attribute_write_result(attrib, id, 0); } static void csis_rank_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_csis *csis = user_data; struct iovec iov; iov.iov_base = &csis->rank_val; iov.iov_len = sizeof(csis->rank_val); gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, iov.iov_len); } static struct bt_csis *csis_new(struct gatt_db *db) { struct bt_csis *csis; if (!db) return NULL; csis = new0(struct bt_csis, 1); return csis; } static struct bt_csip_db *csip_db_new(struct gatt_db *db) { struct bt_csip_db *cdb; if (!db) return NULL; cdb = new0(struct bt_csip_db, 1); cdb->db = gatt_db_ref(db); if (!csip_db) csip_db = queue_new(); cdb->csis = csis_new(db); cdb->csis->cdb = cdb; queue_push_tail(csip_db, cdb); return cdb; } bool bt_csip_set_user_data(struct bt_csip *csip, void *user_data) { if (!csip) return false; csip->user_data = user_data; return true; } static bool csip_db_match(const void *data, const void *match_data) { const struct bt_csip_db *cdb = data; const struct gatt_db *db = match_data; return (cdb->db == db); } static struct bt_csip_db *csip_get_db(struct gatt_db *db) { struct bt_csip_db *cdb; cdb = queue_find(csip_db, csip_db_match, db); if (cdb) return cdb; return csip_db_new(db); } bool bt_csip_set_debug(struct bt_csip *csip, bt_csip_debug_func_t func, void *user_data, bt_csip_destroy_func_t destroy) { if (!csip) return false; if (csip->debug_destroy) csip->debug_destroy(csip->debug_data); csip->debug_func = func; csip->debug_destroy = destroy; csip->debug_data = user_data; return true; } unsigned int bt_csip_register(bt_csip_func_t attached, bt_csip_func_t detached, void *user_data) { struct bt_csip_cb *cb; static unsigned int id; if (!attached && !detached) return 0; if (!csip_cbs) csip_cbs = queue_new(); cb = new0(struct bt_csip_cb, 1); cb->id = ++id ? id : ++id; cb->attached = attached; cb->detached = detached; cb->user_data = user_data; queue_push_tail(csip_cbs, cb); return cb->id; } static bool match_id(const void *data, const void *match_data) { const struct bt_csip_cb *cb = data; unsigned int id = PTR_TO_UINT(match_data); return (cb->id == id); } bool bt_csip_unregister(unsigned int id) { struct bt_csip_cb *cb; cb = queue_remove_if(csip_cbs, match_id, UINT_TO_PTR(id)); if (!cb) return false; free(cb); return true; } struct bt_csip *bt_csip_new(struct gatt_db *ldb, struct gatt_db *rdb) { struct bt_csip *csip; struct bt_csip_db *db; if (!ldb) return NULL; db = csip_get_db(ldb); if (!db) return NULL; csip = new0(struct bt_csip, 1); csip->ldb = db; csip->ready_cbs = queue_new(); if (!rdb) goto done; db = new0(struct bt_csip_db, 1); db->db = gatt_db_ref(rdb); csip->rdb = db; done: bt_csip_ref(csip); return csip; } static struct bt_csis *csip_get_csis(struct bt_csip *csip) { if (!csip) return NULL; if (csip->rdb->csis) return csip->rdb->csis; csip->rdb->csis = new0(struct bt_csis, 1); csip->rdb->csis->cdb = csip->rdb; return csip->rdb->csis; } static void read_sirk(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_csip *csip = user_data; struct bt_csis *csis; struct csis_sirk *sirk; struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; if (!success) { DBG(csip, "Unable to read SIRK: error 0x%02x", att_ecode); return; } csis = csip_get_csis(csip); if (!csis) return; sirk = util_iov_pull_mem(&iov, sizeof(*sirk)); if (!sirk) { DBG(csip, "Invalid size for SIRK: len %u", length); return; } if (!csis->sirk_val) csis->sirk_val = new0(struct csis_sirk, 1); memcpy(csis->sirk_val, sirk, sizeof(*sirk)); } static void read_size(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_csip *csip = user_data; struct bt_csis *csis; if (!success) { DBG(csip, "Unable to read Size: error 0x%02x", att_ecode); return; } csis = csip_get_csis(csip); if (!csis) return; csis->size_val = *value; } static void read_rank(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_csip *csip = user_data; struct bt_csis *csis; if (!success) { DBG(csip, "Unable to read Rank: error 0x%02x", att_ecode); return; } csis = csip_get_csis(csip); if (!csis) return; csis->rank_val = *value; } static void csip_notify_ready(struct bt_csip *csip) { const struct queue_entry *entry; if (!bt_csip_ref_safe(csip)) return; for (entry = queue_get_entries(csip->ready_cbs); entry; entry = entry->next) { struct bt_csip_ready *ready = entry->data; ready->func(csip, ready->data); } bt_csip_unref(csip); } static void foreach_csis_char(struct gatt_db_attribute *attr, void *user_data) { struct bt_csip *csip = user_data; uint16_t value_handle; bt_uuid_t uuid, uuid_sirk, uuid_size, uuid_rank; struct bt_csis *csis; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) return; bt_uuid16_create(&uuid_sirk, CS_SIRK); bt_uuid16_create(&uuid_size, CS_SIZE); bt_uuid16_create(&uuid_rank, CS_RANK); if (!bt_uuid_cmp(&uuid, &uuid_sirk)) { DBG(csip, "SIRK found: handle 0x%04x", value_handle); csis = csip_get_csis(csip); if (!csis) return; csis->sirk = attr; bt_gatt_client_read_value(csip->client, value_handle, read_sirk, csip, NULL); return; } if (!bt_uuid_cmp(&uuid, &uuid_size)) { DBG(csip, "Size found: handle 0x%04x", value_handle); csis = csip_get_csis(csip); if (!csis) return; csis->size = attr; bt_gatt_client_read_value(csip->client, value_handle, read_size, csip, NULL); } if (!bt_uuid_cmp(&uuid, &uuid_rank)) { DBG(csip, "Rank found: handle 0x%04x", value_handle); csis = csip_get_csis(csip); if (!csis) return; csis->rank = attr; bt_gatt_client_read_value(csip->client, value_handle, read_rank, csip, NULL); } } static void foreach_csis_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_csip *csip = user_data; struct bt_csis *csis = csip_get_csis(csip); if (!csis) return; csis->service = attr; gatt_db_service_set_claimed(attr, true); gatt_db_service_foreach_char(attr, foreach_csis_char, csip); } static void csip_idle(void *data) { struct bt_csip *csip = data; csip->idle_id = 0; csip_notify_ready(csip); } bool bt_csip_attach(struct bt_csip *csip, struct bt_gatt_client *client) { bt_uuid_t uuid; if (!sessions) sessions = queue_new(); queue_push_tail(sessions, csip); if (!client) return true; if (csip->client) return false; csip->client = bt_gatt_client_clone(client); if (!csip->client) return false; csip->idle_id = bt_gatt_client_idle_register(csip->client, csip_idle, csip, NULL); bt_uuid16_create(&uuid, CSIS_UUID); gatt_db_foreach_service(csip->rdb->db, &uuid, foreach_csis_service, csip); return true; } static struct csis_sirk *sirk_new(struct bt_csis *csis, struct gatt_db *db, uint8_t type, uint8_t k[16], uint8_t size, uint8_t rank) { struct csis_sirk *sirk; bt_uuid_t uuid; struct gatt_db_attribute *cas; if (!csis) return NULL; if (csis->sirk) sirk = csis->sirk_val; else sirk = new0(struct csis_sirk, 1); sirk->type = type; memcpy(sirk->val, k, sizeof(sirk->val)); csis->sirk_val = sirk; csis->size_val = size; csis->lock_val = 1; csis->rank_val = rank; /* Check if service already active as that means the attributes have * already been registered. */ if (gatt_db_service_get_active(csis->service)) return sirk; /* Populate DB with CSIS attributes */ bt_uuid16_create(&uuid, CSIS_UUID); csis->service = gatt_db_add_service(db, &uuid, true, 10); bt_uuid16_create(&uuid, CS_SIRK); csis->sirk = gatt_db_service_add_characteristic(csis->service, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT, BT_GATT_CHRC_PROP_READ, csis_sirk_read, NULL, csis); bt_uuid16_create(&uuid, CS_SIZE); csis->size = gatt_db_service_add_characteristic(csis->service, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT, BT_GATT_CHRC_PROP_READ, csis_size_read, NULL, csis); /* Lock */ bt_uuid16_create(&uuid, CS_LOCK); csis->lock = gatt_db_service_add_characteristic(csis->service, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT | BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_ENCRYPT, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_WRITE | BT_GATT_CHRC_PROP_NOTIFY, csis_lock_read_cb, csis_lock_write_cb, csis); csis->lock_ccc = gatt_db_service_add_ccc(csis->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); /* Rank */ bt_uuid16_create(&uuid, CS_RANK); csis->rank = gatt_db_service_add_characteristic(csis->service, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT, BT_GATT_CHRC_PROP_READ, csis_rank_read_cb, NULL, csis); /* Add the CAS service */ bt_uuid16_create(&uuid, 0x1853); cas = gatt_db_add_service(db, &uuid, true, 2); gatt_db_service_add_included(cas, csis->service); gatt_db_service_set_active(cas, true); gatt_db_service_add_included(cas, csis->service); gatt_db_service_set_active(csis->service, true); return sirk; } bool bt_csip_set_sirk(struct bt_csip *csip, bool encrypt, uint8_t k[16], uint8_t size, uint8_t rank, bt_csip_encrypt_func_t func) { uint8_t zero[16] = {}; uint8_t type; if (!csip || !csip->ldb || !memcmp(k, zero, sizeof(zero))) return false; type = encrypt ? BT_CSIP_SIRK_ENCRYPT : BT_CSIP_SIRK_CLEARTEXT; /* In case of encrypted type requires sef key function */ if (type == BT_CSIP_SIRK_ENCRYPT && !func) return false; if (!sirk_new(csip->ldb->csis, csip->ldb->db, type, k, size, rank)) return false; csip->ldb->csis->encrypt = func; return true; } bool bt_csip_get_sirk(struct bt_csip *csip, uint8_t *type, uint8_t k[16], uint8_t *size, uint8_t *rank) { struct bt_csis *csis; if (!csip) return false; csis = csip_get_csis(csip); if (!csis) return false; if (!csis->sirk_val) return false; if (type) *type = csis->sirk_val->type; memcpy(k, csis->sirk_val->val, sizeof(csis->sirk_val->val)); if (size) *size = csis->size_val; if (rank) *rank = csis->rank_val; return true; } unsigned int bt_csip_ready_register(struct bt_csip *csip, bt_csip_ready_func_t func, void *user_data, bt_csip_destroy_func_t destroy) { struct bt_csip_ready *ready; static unsigned int id; if (!csip) return 0; ready = new0(struct bt_csip_ready, 1); ready->id = ++id ? id : ++id; ready->func = func; ready->destroy = destroy; ready->data = user_data; queue_push_tail(csip->ready_cbs, ready); return ready->id; } static bool match_ready_id(const void *data, const void *match_data) { const struct bt_csip_ready *ready = data; unsigned int id = PTR_TO_UINT(match_data); return (ready->id == id); } bool bt_csip_ready_unregister(struct bt_csip *csip, unsigned int id) { struct bt_csip_ready *ready; ready = queue_remove_if(csip->ready_cbs, match_ready_id, UINT_TO_PTR(id)); if (!ready) return false; csip_ready_free(ready); return true; } bluez-5.87/src/shared/PaxHeaders/gatt-helpers.h0000644000000000000000000000005015200607060016454 xustar0020 atime=1783092683 20 ctime=1783094100 bluez-5.87/src/shared/gatt-helpers.h0000644000000000000000000000743515200607060016146 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Google Inc. * * */ /* This file defines helpers for performing client-side procedures defined by * the Generic Attribute Profile. */ #include #include struct bt_gatt_result; struct bt_gatt_iter { struct bt_gatt_result *result; uint16_t pos; }; unsigned int bt_gatt_result_service_count(struct bt_gatt_result *result); unsigned int bt_gatt_result_characteristic_count(struct bt_gatt_result *result); unsigned int bt_gatt_result_descriptor_count(struct bt_gatt_result *result); unsigned int bt_gatt_result_included_count(struct bt_gatt_result *result); bool bt_gatt_iter_init(struct bt_gatt_iter *iter, struct bt_gatt_result *result); bool bt_gatt_iter_next_service(struct bt_gatt_iter *iter, uint16_t *start_handle, uint16_t *end_handle, uint8_t uuid[16]); bool bt_gatt_iter_next_characteristic(struct bt_gatt_iter *iter, uint16_t *start_handle, uint16_t *end_handle, uint16_t *value_handle, uint8_t *properties, uint8_t uuid[16]); bool bt_gatt_iter_next_descriptor(struct bt_gatt_iter *iter, uint16_t *handle, uint8_t uuid[16]); bool bt_gatt_iter_next_included_service(struct bt_gatt_iter *iter, uint16_t *handle, uint16_t *start_handle, uint16_t *end_handle, uint8_t uuid[16]); bool bt_gatt_iter_next_read_by_type(struct bt_gatt_iter *iter, uint16_t *handle, uint16_t *length, const uint8_t **value); typedef void (*bt_gatt_destroy_func_t)(void *user_data); typedef void (*bt_gatt_result_callback_t)(bool success, uint8_t att_ecode, void *user_data); typedef void (*bt_gatt_request_callback_t)(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data); struct bt_gatt_request; struct bt_gatt_request *bt_gatt_request_ref(struct bt_gatt_request *req); void bt_gatt_request_unref(struct bt_gatt_request *req); void bt_gatt_request_cancel(struct bt_gatt_request *req); unsigned int bt_gatt_exchange_mtu(struct bt_att *att, uint16_t client_rx_mtu, bt_gatt_result_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy); struct bt_gatt_request *bt_gatt_discover_all_primary_services( struct bt_att *att, bt_uuid_t *uuid, bt_gatt_request_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy); struct bt_gatt_request *bt_gatt_discover_primary_services( struct bt_att *att, bt_uuid_t *uuid, uint16_t start, uint16_t end, bt_gatt_request_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy); struct bt_gatt_request *bt_gatt_discover_secondary_services( struct bt_att *att, bt_uuid_t *uuid, uint16_t start, uint16_t end, bt_gatt_request_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy); struct bt_gatt_request *bt_gatt_discover_included_services(struct bt_att *att, uint16_t start, uint16_t end, bt_gatt_request_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy); struct bt_gatt_request *bt_gatt_discover_characteristics(struct bt_att *att, uint16_t start, uint16_t end, bt_gatt_request_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy); struct bt_gatt_request *bt_gatt_discover_descriptors(struct bt_att *att, uint16_t start, uint16_t end, bt_gatt_request_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy); bool bt_gatt_read_by_type(struct bt_att *att, uint16_t start, uint16_t end, const bt_uuid_t *uuid, bt_gatt_request_callback_t callback, void *user_data, bt_gatt_destroy_func_t destroy); uint16_t bt_gatt_req_get_end_handle(struct bt_gatt_request *req); uint16_t bt_gatt_req_get_start_handle(struct bt_gatt_request *req); bluez-5.87/src/shared/PaxHeaders/gmap.h0000644000000000000000000000005015142177620015012 xustar0020 atime=1783092966 20 ctime=1783094100 bluez-5.87/src/shared/gmap.h0000644000000000000000000000734315142177620014502 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2025 Pauli Virtanen. All rights reserved. * */ #include #include #define BT_GMAP_ROLE_UGG BIT(0) #define BT_GMAP_ROLE_UGT BIT(1) #define BT_GMAP_ROLE_BGS BIT(2) #define BT_GMAP_ROLE_BGR BIT(3) #define BT_GMAP_ROLE_MASK (BIT(4) - 1) #define BT_GMAP_UGG_MULTIPLEX (BIT(0) << 0) #define BT_GMAP_UGG_96KBPS_SOURCE (BIT(1) << 0) #define BT_GMAP_UGG_MULTISINK (BIT(2) << 0) #define BT_GMAP_UGG_FEATURE_MASK ((BIT(3) - 1) << 0) #define BT_GMAP_UGG_FEATURE_SHIFT 0 #define BT_GMAP_UGT_SOURCE (BIT(0) << 8) #define BT_GMAP_UGT_80KBPS_SOURCE (BIT(1) << 8) #define BT_GMAP_UGT_SINK (BIT(2) << 8) #define BT_GMAP_UGT_64KBPS_SINK (BIT(3) << 8) #define BT_GMAP_UGT_MULTIPLEX (BIT(4) << 8) #define BT_GMAP_UGT_MULTISINK (BIT(5) << 8) #define BT_GMAP_UGT_MULTISOURCE (BIT(6) << 8) #define BT_GMAP_UGT_FEATURE_MASK ((BIT(7) - 1) << 8) #define BT_GMAP_UGT_FEATURE_SHIFT 8 #define BT_GMAP_BGS_96KBPS (BIT(0) << 16) #define BT_GMAP_BGS_FEATURE_MASK ((BIT(1) - 1) << 16) #define BT_GMAP_BGS_FEATURE_SHIFT 16 #define BT_GMAP_BGR_MULTISINK (BIT(0) << 24) #define BT_GMAP_BGR_MULTIPLEX (BIT(1) << 24) #define BT_GMAP_BGR_FEATURE_MASK ((BIT(2) - 1) << 24) #define BT_GMAP_BGR_FEATURE_SHIFT 24 #define BT_GMAP_FEATURE_MASK (BT_GMAP_UGG_FEATURE_MASK | \ BT_GMAP_UGT_FEATURE_MASK | \ BT_GMAP_BGS_FEATURE_MASK | \ BT_GMAP_BGR_FEATURE_MASK) #define BT_GMAP_ROLE_UGG_STR "ugg" #define BT_GMAP_ROLE_UGT_STR "ugt" #define BT_GMAP_ROLE_BGS_STR "bgs" #define BT_GMAP_ROLE_BGR_STR "bgr" #define BT_GMAP_UGG_MULTIPLEX_STR "ugg-multiplex" #define BT_GMAP_UGG_96KBPS_SOURCE_STR "ugg-96kbps-source" #define BT_GMAP_UGG_MULTISINK_STR "ugg-multisink" #define BT_GMAP_UGT_SOURCE_STR "ugt-source" #define BT_GMAP_UGT_80KBPS_SOURCE_STR "ugt-80kbps-source" #define BT_GMAP_UGT_SINK_STR "ugt-sink" #define BT_GMAP_UGT_64KBPS_SINK_STR "ugt-64kbps-sink" #define BT_GMAP_UGT_MULTIPLEX_STR "ugt-multiplex" #define BT_GMAP_UGT_MULTISINK_STR "ugt-multisink" #define BT_GMAP_UGT_MULTISOURCE_STR "ugt-multisource" #define BT_GMAP_BGS_96KBPS_STR "bgs-96kbps" #define BT_GMAP_BGR_MULTISINK_STR "bgr-multisink" #define BT_GMAP_BGR_MULTIPLEX_STR "bgr-multiplex" #define BT_GMAP_ROLE_LIST(role) \ role(BT_GMAP_ROLE_UGG) \ role(BT_GMAP_ROLE_UGT) \ role(BT_GMAP_ROLE_BGS) \ role(BT_GMAP_ROLE_BGR) #define BT_GMAP_FEATURE_LIST(feature) \ feature(BT_GMAP_UGG_MULTIPLEX) \ feature(BT_GMAP_UGG_96KBPS_SOURCE) \ feature(BT_GMAP_UGG_MULTISINK) \ feature(BT_GMAP_UGT_SOURCE) \ feature(BT_GMAP_UGT_80KBPS_SOURCE) \ feature(BT_GMAP_UGT_SINK) \ feature(BT_GMAP_UGT_64KBPS_SINK) \ feature(BT_GMAP_UGT_MULTIPLEX) \ feature(BT_GMAP_UGT_MULTISINK) \ feature(BT_GMAP_UGT_MULTISOURCE) \ feature(BT_GMAP_BGS_96KBPS) \ feature(BT_GMAP_BGR_MULTISINK) \ feature(BT_GMAP_BGR_MULTIPLEX) struct bt_gmap; typedef void (*bt_gmap_ready_func_t)(struct bt_gmap *gmap, void *user_data); typedef void (*bt_gmap_destroy_func_t)(void *user_data); typedef void (*bt_gmap_debug_func_t)(const char *str, void *user_data); struct bt_gmap *bt_gmap_ref(struct bt_gmap *gmap); void bt_gmap_unref(struct bt_gmap *gmap); struct bt_gmap *bt_gmap_attach(struct bt_gatt_client *client, bt_gmap_ready_func_t ready, void *user_data); struct bt_gmap *bt_gmap_find(struct gatt_db *db); struct bt_gmap *bt_gmap_add_db(struct gatt_db *db); uint8_t bt_gmap_get_role(struct bt_gmap *gmap); uint32_t bt_gmap_get_features(struct bt_gmap *gmap); void bt_gmap_set_role(struct bt_gmap *gmas, uint8_t role); void bt_gmap_set_features(struct bt_gmap *gmas, uint32_t features); bool bt_gmap_set_debug(struct bt_gmap *gmap, bt_gmap_debug_func_t cb, void *user_data, bt_gmap_destroy_func_t destroy); bluez-5.87/src/shared/PaxHeaders/uinput.h0000644000000000000000000000005015142177620015412 xustar0020 atime=1783092970 20 ctime=1783094100 bluez-5.87/src/shared/uinput.h0000644000000000000000000000144315142177620015075 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2025 Pauli Virtanen */ struct bt_uinput; struct bt_uinput_key_map { const char *name; unsigned int code; uint16_t uinput; }; typedef void (*bt_uinput_debug_func_t)(const char *str, void *user_data); struct bt_uinput *bt_uinput_new(const char *name, const char *suffix, const bdaddr_t *addr, const struct input_id *dev_id); void bt_uinput_set_debug(struct bt_uinput *uinput, bt_uinput_debug_func_t debug_func, void *user_data); int bt_uinput_create(struct bt_uinput *uinput, const struct bt_uinput_key_map *key_map); void bt_uinput_destroy(struct bt_uinput *uinput); void bt_uinput_send_key(struct bt_uinput *uinput, uint16_t key, bool pressed); bluez-5.87/src/shared/PaxHeaders/ringbuf.c0000644000000000000000000000005015200607060015504 xustar0020 atime=1783092683 20 ctime=1783094099 bluez-5.87/src/shared/ringbuf.c0000644000000000000000000001302215200607060015163 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include "src/shared/util.h" #include "src/shared/ringbuf.h" struct ringbuf { void *buffer; size_t size; size_t in; size_t out; ringbuf_tracing_func_t in_tracing; void *in_data; }; #define RINGBUF_RESET 0 /* Find last (most significant) set bit */ static inline unsigned int fls(unsigned int x) { return x ? sizeof(x) * 8 - __builtin_clz(x) : 0; } /* Round up to nearest power of two */ static inline unsigned int align_power2(unsigned int u) { return 1 << fls(u - 1); } struct ringbuf *ringbuf_new(size_t size) { struct ringbuf *ringbuf; size_t real_size; if (size < 2 || size > UINT_MAX) return NULL; /* Find the next power of two for size */ real_size = align_power2(size); ringbuf = new0(struct ringbuf, 1); ringbuf->buffer = malloc(real_size); if (!ringbuf->buffer) { free(ringbuf); return NULL; } ringbuf->size = real_size; ringbuf->in = RINGBUF_RESET; ringbuf->out = RINGBUF_RESET; return ringbuf; } void ringbuf_free(struct ringbuf *ringbuf) { if (!ringbuf) return; free(ringbuf->buffer); free(ringbuf); } bool ringbuf_set_input_tracing(struct ringbuf *ringbuf, ringbuf_tracing_func_t callback, void *user_data) { if (!ringbuf) return false; ringbuf->in_tracing = callback; ringbuf->in_data = user_data; return true; } size_t ringbuf_capacity(struct ringbuf *ringbuf) { if (!ringbuf) return 0; return ringbuf->size; } size_t ringbuf_len(struct ringbuf *ringbuf) { if (!ringbuf) return 0; return ringbuf->in - ringbuf->out; } size_t ringbuf_drain(struct ringbuf *ringbuf, size_t count) { size_t len; if (!ringbuf) return 0; len = MIN(count, ringbuf->in - ringbuf->out); if (!len) return 0; ringbuf->out += len; if (ringbuf->out == ringbuf->in) { ringbuf->in = RINGBUF_RESET; ringbuf->out = RINGBUF_RESET; } return len; } void *ringbuf_peek(struct ringbuf *ringbuf, size_t offset, size_t *len_nowrap) { if (!ringbuf) return NULL; offset = (ringbuf->out + offset) & (ringbuf->size - 1); if (len_nowrap) { size_t len = ringbuf->in - ringbuf->out; *len_nowrap = MIN(len, ringbuf->size - offset); } return ringbuf->buffer + offset; } ssize_t ringbuf_write(struct ringbuf *ringbuf, int fd) { size_t len, offset, end; struct iovec iov[2]; ssize_t consumed; if (!ringbuf || fd < 0) return -1; /* Determine how much data is available */ len = ringbuf->in - ringbuf->out; if (!len) return 0; /* Grab data from buffer starting at offset until the end */ offset = ringbuf->out & (ringbuf->size - 1); end = MIN(len, ringbuf->size - offset); iov[0].iov_base = ringbuf->buffer + offset; iov[0].iov_len = end; /* Use second vector for remainder from the beginning */ iov[1].iov_base = ringbuf->buffer; iov[1].iov_len = len - end; consumed = writev(fd, iov, 2); if (consumed < 0) return -1; ringbuf->out += consumed; if (ringbuf->out == ringbuf->in) { ringbuf->in = RINGBUF_RESET; ringbuf->out = RINGBUF_RESET; } return consumed; } size_t ringbuf_avail(struct ringbuf *ringbuf) { if (!ringbuf) return 0; return ringbuf->size - ringbuf->in + ringbuf->out; } int ringbuf_printf(struct ringbuf *ringbuf, const char *format, ...) { va_list ap; int len; va_start(ap, format); len = ringbuf_vprintf(ringbuf, format, ap); va_end(ap); return len; } int ringbuf_vprintf(struct ringbuf *ringbuf, const char *format, va_list ap) { size_t avail, offset, end; char *str; int len; if (!ringbuf || !format) return -1; /* Determine maximum length available for string */ avail = ringbuf->size - ringbuf->in + ringbuf->out; if (!avail) return -1; len = vasprintf(&str, format, ap); if (len < 0) return -1; if ((size_t) len > avail) { free(str); return -1; } /* Determine possible length of string before wrapping */ offset = ringbuf->in & (ringbuf->size - 1); end = MIN((size_t) len, ringbuf->size - offset); memcpy(ringbuf->buffer + offset, str, end); if (ringbuf->in_tracing) ringbuf->in_tracing(ringbuf->buffer + offset, end, ringbuf->in_data); if ((size_t) len > end) { /* Put the remainder of string at the beginning */ memcpy(ringbuf->buffer, str + end, len - end); if (ringbuf->in_tracing) ringbuf->in_tracing(ringbuf->buffer, len - end, ringbuf->in_data); } free(str); ringbuf->in += len; return len; } ssize_t ringbuf_read(struct ringbuf *ringbuf, int fd) { size_t avail, offset, end; struct iovec iov[2]; ssize_t consumed; if (!ringbuf || fd < 0) return -1; /* Determine how much can actually be consumed */ avail = ringbuf->size - ringbuf->in + ringbuf->out; if (!avail) return -1; /* Determine how much to consume before wrapping */ offset = ringbuf->in & (ringbuf->size - 1); end = MIN(avail, ringbuf->size - offset); iov[0].iov_base = ringbuf->buffer + offset; iov[0].iov_len = end; /* Now put the remainder into the second vector */ iov[1].iov_base = ringbuf->buffer; iov[1].iov_len = avail - end; consumed = readv(fd, iov, 2); if (consumed < 0) return -1; if (ringbuf->in_tracing) { size_t len = MIN((size_t) consumed, end); ringbuf->in_tracing(ringbuf->buffer + offset, len, ringbuf->in_data); if (consumed - len > 0) ringbuf->in_tracing(ringbuf->buffer, consumed - len, ringbuf->in_data); } ringbuf->in += consumed; return consumed; } bluez-5.87/src/shared/PaxHeaders/bap-defs.h0000644000000000000000000000005015142177620015547 xustar0020 atime=1783092807 20 ctime=1783094100 bluez-5.87/src/shared/bap-defs.h0000644000000000000000000000421615142177620015233 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. * Copyright 2023-2024 NXP * */ #ifndef SRC_SHARED_BAP_DEFS_H_ #define SRC_SHARED_BAP_DEFS_H_ #ifndef __packed #define __packed __attribute__((packed)) #endif #define BT_BAP_SINK BIT(0) #define BT_BAP_SOURCE BIT(1) #define BT_BAP_BCAST_SOURCE BIT(2) #define BT_BAP_BCAST_SINK BIT(3) #define BT_BAP_STREAM_TYPE_UCAST 0x01 #define BT_BAP_STREAM_TYPE_BCAST 0x02 #define BT_BAP_STREAM_STATE_IDLE 0x00 #define BT_BAP_STREAM_STATE_CONFIG 0x01 #define BT_BAP_STREAM_STATE_QOS 0x02 #define BT_BAP_STREAM_STATE_ENABLING 0x03 #define BT_BAP_STREAM_STATE_STREAMING 0x04 #define BT_BAP_STREAM_STATE_DISABLING 0x05 #define BT_BAP_STREAM_STATE_RELEASING 0x06 #define BT_BAP_CONFIG_LATENCY_LOW 0x01 #define BT_BAP_CONFIG_LATENCY_BALANCED 0x02 #define BT_BAP_CONFIG_LATENCY_HIGH 0x03 #define BT_BAP_CONFIG_PHY_1M BIT(0) #define BT_BAP_CONFIG_PHY_2M BIT(1) #define BT_BAP_CONFIG_PHY_CODEC BIT(2) struct bt_bap_codec { uint8_t id; uint16_t cid; uint16_t vid; } __packed; struct bt_ltv { uint8_t len; uint8_t type; uint8_t value[]; } __packed; struct bt_bap_io_qos { uint32_t interval; /* Frame interval */ uint16_t latency; /* Transport Latency */ uint16_t sdu; /* Maximum SDU Size */ uint8_t phys; /* PHY */ uint8_t rtn; /* Retransmission Effort */ }; struct bt_bap_ucast_qos { uint8_t cig_id; uint8_t cis_id; uint8_t framing; /* Frame framing */ uint32_t delay; /* Presentation Delay */ uint8_t target_latency; /* Target Latency */ struct bt_bap_io_qos io_qos; }; struct bt_bap_bcast_qos { uint8_t big; uint8_t bis; uint8_t sync_factor; uint8_t packing; uint8_t framing; uint8_t encryption; struct iovec *bcode; uint8_t options; uint16_t skip; uint16_t sync_timeout; uint8_t sync_cte_type; uint8_t mse; uint16_t timeout; uint8_t pa_sync; struct bt_bap_io_qos io_qos; uint32_t delay; /* Presentation Delay */ }; struct bt_bap_qos { union { struct bt_bap_ucast_qos ucast; struct bt_bap_bcast_qos bcast; }; }; #endif /* SRC_SHARED_BAP_DEFS_H_ */ bluez-5.87/src/shared/PaxHeaders/hci-crypto.c0000644000000000000000000000005014015011623016126 xustar0020 atime=1783092893 20 ctime=1783094099 bluez-5.87/src/shared/hci-crypto.c0000644000000000000000000000661714015011623015621 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2013-2014 Intel Corporation * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include "monitor/bt.h" #include "src/shared/util.h" #include "src/shared/hci.h" #include "src/shared/hci-crypto.h" struct crypto_data { uint8_t size; bt_hci_crypto_func_t callback; void *user_data; }; static void le_encrypt_callback(const void *response, uint8_t size, void *user_data) { struct crypto_data *data = user_data; const struct bt_hci_rsp_le_encrypt *rsp = response; if (rsp->status) { data->callback(NULL, 0, data->user_data); return; } data->callback(rsp->data, data->size, data->user_data); } static bool le_encrypt(struct bt_hci *hci, uint8_t size, const uint8_t key[16], const uint8_t plaintext[16], bt_hci_crypto_func_t callback, void *user_data) { struct crypto_data *data; struct bt_hci_cmd_le_encrypt cmd; if (!callback || !size || size > 16) return false; memcpy(cmd.key, key, 16); memcpy(cmd.plaintext, plaintext, 16); data = new0(struct crypto_data, 1); data->size = size; data->callback = callback; data->user_data = user_data; if (!bt_hci_send(hci, BT_HCI_CMD_LE_ENCRYPT, &cmd, sizeof(cmd), le_encrypt_callback, data, free)) { free(data); return false; } return true; } static void prand_callback(const void *response, uint8_t size, void *user_data) { struct crypto_data *data = user_data; const struct bt_hci_rsp_le_rand *rsp = response; uint8_t prand[3]; if (rsp->status) { data->callback(NULL, 0, data->user_data); return; } prand[0] = (rsp->number & 0xff0000) >> 16; prand[1] = (rsp->number & 0x00ff00) >> 8; prand[2] = (rsp->number & 0x00003f) | 0x40; data->callback(prand, 3, data->user_data); } bool bt_hci_crypto_prand(struct bt_hci *hci, bt_hci_crypto_func_t callback, void *user_data) { struct crypto_data *data; if (!callback) return false; data = new0(struct crypto_data, 1); data->callback = callback; data->user_data = user_data; if (!bt_hci_send(hci, BT_HCI_CMD_LE_RAND, NULL, 0, prand_callback, data, free)) { free(data); return false; } return true; } bool bt_hci_crypto_e(struct bt_hci *hci, const uint8_t key[16], const uint8_t plaintext[16], bt_hci_crypto_func_t callback, void *user_data) { return le_encrypt(hci, 16, key, plaintext, callback, user_data); } bool bt_hci_crypto_d1(struct bt_hci *hci, const uint8_t k[16], uint16_t d, uint16_t r, bt_hci_crypto_func_t callback, void *user_data) { uint8_t dp[16]; /* d' = padding || r || d */ dp[0] = d & 0xff; dp[1] = d >> 8; dp[2] = r & 0xff; dp[3] = r >> 8; memset(dp + 4, 0, 12); /* d1(k, d, r) = e(k, d') */ return le_encrypt(hci, 16, k, dp, callback, user_data); } bool bt_hci_crypto_dm(struct bt_hci *hci, const uint8_t k[16], const uint8_t r[8], bt_hci_crypto_func_t callback, void *user_data) { uint8_t rp[16]; /* r' = padding || r */ memcpy(rp, r, 8); memset(rp + 8, 0, 8); /* dm(k, r) = e(k, r') mod 2^16 */ return le_encrypt(hci, 8, k, rp, callback, user_data); } bool bt_hci_crypto_ah(struct bt_hci *hci, const uint8_t k[16], const uint8_t r[3], bt_hci_crypto_func_t callback, void *user_data) { uint8_t rp[16]; /* r' = padding || r */ memcpy(rp, r, 3); memset(rp + 3, 0, 13); /* ah(k, r) = e(k, r') mod 2^24 */ return le_encrypt(hci, 3, k, rp, callback, user_data); } bluez-5.87/src/shared/PaxHeaders/btsnoop.h0000644000000000000000000000005014766002272015553 xustar0020 atime=1783092902 20 ctime=1783094099 bluez-5.87/src/shared/btsnoop.h0000644000000000000000000000644214766002272015242 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #include #include #include #define BTSNOOP_FORMAT_INVALID 0 #define BTSNOOP_FORMAT_HCI 1001 #define BTSNOOP_FORMAT_UART 1002 #define BTSNOOP_FORMAT_BCSP 1003 #define BTSNOOP_FORMAT_3WIRE 1004 #define BTSNOOP_FORMAT_MONITOR 2001 #define BTSNOOP_FORMAT_SIMULATOR 2002 #define BTSNOOP_FLAG_PKLG_SUPPORT (1 << 0) #define BTSNOOP_OPCODE_NEW_INDEX 0 #define BTSNOOP_OPCODE_DEL_INDEX 1 #define BTSNOOP_OPCODE_COMMAND_PKT 2 #define BTSNOOP_OPCODE_EVENT_PKT 3 #define BTSNOOP_OPCODE_ACL_TX_PKT 4 #define BTSNOOP_OPCODE_ACL_RX_PKT 5 #define BTSNOOP_OPCODE_SCO_TX_PKT 6 #define BTSNOOP_OPCODE_SCO_RX_PKT 7 #define BTSNOOP_OPCODE_OPEN_INDEX 8 #define BTSNOOP_OPCODE_CLOSE_INDEX 9 #define BTSNOOP_OPCODE_INDEX_INFO 10 #define BTSNOOP_OPCODE_VENDOR_DIAG 11 #define BTSNOOP_OPCODE_SYSTEM_NOTE 12 #define BTSNOOP_OPCODE_USER_LOGGING 13 #define BTSNOOP_OPCODE_CTRL_OPEN 14 #define BTSNOOP_OPCODE_CTRL_CLOSE 15 #define BTSNOOP_OPCODE_CTRL_COMMAND 16 #define BTSNOOP_OPCODE_CTRL_EVENT 17 #define BTSNOOP_OPCODE_ISO_TX_PKT 18 #define BTSNOOP_OPCODE_ISO_RX_PKT 19 #define BTSNOOP_MAX_PACKET_SIZE (1486 + 4) #define BTSNOOP_TYPE_PRIMARY 0 #define BTSNOOP_TYPE_AMP 1 #define BTSNOOP_BUS_VIRTUAL 0 #define BTSNOOP_BUS_USB 1 #define BTSNOOP_BUS_PCCARD 2 #define BTSNOOP_BUS_UART 3 #define BTSNOOP_BUS_RS232 4 #define BTSNOOP_BUS_PCI 5 #define BTSNOOP_BUS_SDIO 6 #define BTSNOOP_BUS_SPI 7 #define BTSNOOP_BUS_I2C 8 #define BTSNOOP_BUS_SMD 9 #define BTSNOOP_BUS_VIRTIO 10 #define BTSNOOP_BUS_IPC 11 struct btsnoop_opcode_new_index { uint8_t type; uint8_t bus; uint8_t bdaddr[6]; char name[8]; } __attribute__((packed)); struct btsnoop_opcode_index_info { uint8_t bdaddr[6]; uint16_t manufacturer; } __attribute__((packed)); #define BTSNOOP_PRIORITY_EMERG 0 #define BTSNOOP_PRIORITY_ALERT 1 #define BTSNOOP_PRIORITY_CRIT 2 #define BTSNOOP_PRIORITY_ERR 3 #define BTSNOOP_PRIORITY_WARNING 4 #define BTSNOOP_PRIORITY_NOTICE 5 #define BTSNOOP_PRIORITY_INFO 6 #define BTSNOOP_PRIORITY_DEBUG 7 struct btsnoop_opcode_user_logging { uint8_t priority; uint8_t ident_len; } __attribute__((packed)); struct btsnoop; struct btsnoop *btsnoop_open(const char *path, unsigned long flags); struct btsnoop *btsnoop_create(const char *path, size_t max_size, unsigned int max_count, uint32_t format); struct btsnoop *btsnoop_ref(struct btsnoop *btsnoop); void btsnoop_unref(struct btsnoop *btsnoop); uint32_t btsnoop_get_format(struct btsnoop *btsnoop); bool btsnoop_write(struct btsnoop *btsnoop, struct timeval *tv, uint32_t flags, uint32_t drops, const void *data, uint16_t size); bool btsnoop_write_hci(struct btsnoop *btsnoop, struct timeval *tv, uint16_t index, uint16_t opcode, uint32_t drops, const void *data, uint16_t size); bool btsnoop_write_phy(struct btsnoop *btsnoop, struct timeval *tv, uint16_t frequency, const void *data, uint16_t size); bool btsnoop_read_hci(struct btsnoop *btsnoop, struct timeval *tv, uint16_t *index, uint16_t *opcode, void *data, uint16_t *size); bool btsnoop_read_phy(struct btsnoop *btsnoop, struct timeval *tv, uint16_t *frequency, void *data, uint16_t *size); bluez-5.87/src/shared/PaxHeaders/mgmt.h0000644000000000000000000000005015200607060015021 xustar0020 atime=1783092683 20 ctime=1783094099 bluez-5.87/src/shared/mgmt.h0000644000000000000000000000743515200607060014513 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #pragma once #include #include #define MGMT_VERSION(v, r) (((v) << 16) + (r)) typedef void (*mgmt_destroy_func_t)(void *user_data); struct mgmt; struct mgmt_tlv_list; struct mgmt *mgmt_new(int fd); struct mgmt *mgmt_new_default(void); struct mgmt *mgmt_ref(struct mgmt *mgmt); void mgmt_unref(struct mgmt *mgmt); typedef void (*mgmt_debug_func_t)(const char *str, void *user_data); bool mgmt_set_debug(struct mgmt *mgmt, mgmt_debug_func_t callback, void *user_data, mgmt_destroy_func_t destroy); bool mgmt_set_close_on_unref(struct mgmt *mgmt, bool do_close); typedef void (*mgmt_request_func_t)(uint8_t status, uint16_t length, const void *param, void *user_data); struct mgmt_tlv_list *mgmt_tlv_list_new(void); void mgmt_tlv_list_free(struct mgmt_tlv_list *tlv_list); uint16_t mgmt_tlv_list_size(struct mgmt_tlv_list *tlv_list); bool mgmt_tlv_add(struct mgmt_tlv_list *tlv_list, uint16_t type, uint8_t length, void *value); #define mgmt_tlv_add_fixed(_list, _type, _value) \ mgmt_tlv_add(_list, _type, sizeof(*(_value)), _value) struct mgmt_tlv_list *mgmt_tlv_list_load_from_buf(const uint8_t *buf, uint16_t len); typedef void (*mgmt_tlv_list_foreach_func_t)(void *data, void *user_data); void mgmt_tlv_list_foreach(struct mgmt_tlv_list *tlv_list, mgmt_tlv_list_foreach_func_t callback, void *user_data); unsigned int mgmt_send_tlv(struct mgmt *mgmt, uint16_t opcode, uint16_t index, struct mgmt_tlv_list *tlv_list, mgmt_request_func_t callback, void *user_data, mgmt_destroy_func_t destroy); unsigned int mgmt_send(struct mgmt *mgmt, uint16_t opcode, uint16_t index, uint16_t length, const void *param, mgmt_request_func_t callback, void *user_data, mgmt_destroy_func_t destroy); unsigned int mgmt_send_timeout(struct mgmt *mgmt, uint16_t opcode, uint16_t index, uint16_t length, const void *param, mgmt_request_func_t callback, void *user_data, mgmt_destroy_func_t destroy, int timeout); unsigned int mgmt_send_nowait(struct mgmt *mgmt, uint16_t opcode, uint16_t index, uint16_t length, const void *param, mgmt_request_func_t callback, void *user_data, mgmt_destroy_func_t destroy); unsigned int mgmt_reply(struct mgmt *mgmt, uint16_t opcode, uint16_t index, uint16_t length, const void *param, mgmt_request_func_t callback, void *user_data, mgmt_destroy_func_t destroy); unsigned int mgmt_reply_timeout(struct mgmt *mgmt, uint16_t opcode, uint16_t index, uint16_t length, const void *param, mgmt_request_func_t callback, void *user_data, mgmt_destroy_func_t destroy, int timeout); bool mgmt_cancel(struct mgmt *mgmt, unsigned int id); bool mgmt_cancel_index(struct mgmt *mgmt, uint16_t index); bool mgmt_cancel_all(struct mgmt *mgmt); typedef void (*mgmt_notify_func_t)(uint16_t index, uint16_t length, const void *param, void *user_data); unsigned int mgmt_register(struct mgmt *mgmt, uint16_t event, uint16_t index, mgmt_notify_func_t callback, void *user_data, mgmt_destroy_func_t destroy); bool mgmt_unregister(struct mgmt *mgmt, unsigned int id); bool mgmt_unregister_index(struct mgmt *mgmt, uint16_t index); bool mgmt_unregister_all(struct mgmt *mgmt); uint16_t mgmt_get_mtu(struct mgmt *mgmt); enum mgmt_io_capability { MGMT_IO_CAPABILITY_DISPLAYONLY = 0x00, MGMT_IO_CAPABILITY_DISPLAYYESNO = 0x01, MGMT_IO_CAPABILITY_KEYBOARDONLY = 0x02, MGMT_IO_CAPABILITY_NOINPUTNOOUTPUT = 0x03, MGMT_IO_CAPABILITY_KEYBOARDDISPLAY = 0x04, MGMT_IO_CAPABILITY_INVALID = 0xFF, }; char *mgmt_iocap_generator(const char *text, int state); enum mgmt_io_capability mgmt_parse_io_capability(const char *capability); bluez-5.87/src/shared/PaxHeaders/bap-debug.h0000644000000000000000000000005014536422313015712 xustar0020 atime=1783092807 20 ctime=1783094100 bluez-5.87/src/shared/bap-debug.h0000644000000000000000000000067414536422313015402 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2023 Intel Corporation. */ bool bt_bap_debug_caps(void *data, size_t len, util_debug_func_t func, void *user_data); bool bt_bap_debug_config(void *data, size_t len, util_debug_func_t func, void *user_data); bool bt_bap_debug_metadata(void *data, size_t len, util_debug_func_t func, void *user_data); bluez-5.87/src/shared/PaxHeaders/pcap.h0000644000000000000000000000005014015011623014775 xustar0020 atime=1783092901 20 ctime=1783094099 bluez-5.87/src/shared/pcap.h0000644000000000000000000000151714015011623014462 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #include #include #include #define PCAP_TYPE_INVALID 0 #define PCAP_TYPE_USER0 147 #define PCAP_TYPE_PPI 192 #define PCAP_TYPE_BLUETOOTH_LE_LL 251 struct pcap; struct pcap *pcap_open(const char *path); struct pcap *pcap_ref(struct pcap *pcap); void pcap_unref(struct pcap *pcap); uint32_t pcap_get_type(struct pcap *pcap); uint32_t pcap_get_snaplen(struct pcap *pcap); bool pcap_read(struct pcap *pcap, struct timeval *tv, void *data, uint32_t size, uint32_t *len); bool pcap_read_ppi(struct pcap *pcap, struct timeval *tv, uint32_t *type, void *data, uint32_t size, uint32_t *offset, uint32_t *len); bluez-5.87/src/shared/PaxHeaders/mainloop-glib.c0000644000000000000000000000005015017252326016610 xustar0020 atime=1783092982 20 ctime=1783094100 bluez-5.87/src/shared/mainloop-glib.c0000644000000000000000000000335315017252326016275 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "mainloop.h" #include "mainloop-notify.h" #include "io.h" static GMainLoop *main_loop; static int exit_status; void mainloop_init(void) { main_loop = g_main_loop_new(NULL, FALSE); } void mainloop_quit(void) { if (!main_loop) return; g_main_loop_quit(main_loop); mainloop_sd_notify("STOPPING=1"); } void mainloop_exit_success(void) { exit_status = EXIT_SUCCESS; mainloop_quit(); } void mainloop_exit_failure(void) { exit_status = EXIT_FAILURE; mainloop_quit(); } int mainloop_run(void) { if (!main_loop) return -EINVAL; g_main_loop_run(main_loop); g_main_loop_unref(main_loop); main_loop = NULL; mainloop_notify_exit(); return exit_status; } int mainloop_add_fd(int fd, uint32_t events, mainloop_event_func callback, void *user_data, mainloop_destroy_func destroy) { return -ENOSYS; } int mainloop_modify_fd(int fd, uint32_t events) { return -ENOSYS; } int mainloop_remove_fd(int fd) { return -ENOSYS; } int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback, void *user_data, mainloop_destroy_func destroy) { return -ENOSYS; } int mainloop_modify_timeout(int fd, unsigned int msec) { return -ENOSYS; } int mainloop_remove_timeout(int id) { return -ENOSYS; } int mainloop_set_signal(sigset_t *mask, mainloop_signal_func callback, void *user_data, mainloop_destroy_func destroy) { return -ENOSYS; } bluez-5.87/src/shared/PaxHeaders/gap.c0000644000000000000000000000005015063045254014627 xustar0020 atime=1783092928 20 ctime=1783094100 bluez-5.87/src/shared/gap.c0000644000000000000000000001123315063045254014310 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/mgmt.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/mgmt.h" #include "src/shared/gap.h" #define FLAG_MGMT_CONN_CONTROL (0 << 1) struct bt_gap { int ref_count; uint16_t index; struct mgmt *mgmt; uint8_t mgmt_version; uint16_t mgmt_revision; bool mgmt_ready; unsigned long flags; bt_gap_ready_func_t ready_handler; bt_gap_destroy_func_t ready_destroy; void *ready_data; uint8_t static_addr[6]; uint8_t local_irk[16]; struct queue *irk_list; }; struct irk_entry { uint8_t addr_type; uint8_t addr[6]; uint8_t key[16]; }; static void irk_entry_free(void *data) { struct irk_entry *irk = data; free(irk); } static void ready_status(struct bt_gap *gap, bool status) { gap->mgmt_ready = status; if (gap->ready_handler) gap->ready_handler(status, gap->ready_data); } static void read_commands_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct bt_gap *gap = user_data; const struct mgmt_rp_read_commands *rp = param; uint16_t num_commands, num_events; size_t expected_len; int i; if (status != MGMT_STATUS_SUCCESS) { ready_status(gap, false); return; } if (length < sizeof(*rp)) { ready_status(gap, false); return; } num_commands = le16_to_cpu(rp->num_commands); num_events = le16_to_cpu(rp->num_events); expected_len = sizeof(*rp) + num_commands * sizeof(uint16_t) + num_events * sizeof(uint16_t); if (length < expected_len) { ready_status(gap, false); return; } for (i = 0; i < num_commands; i++) { uint16_t op = get_le16(rp->opcodes + i); if (op == MGMT_OP_ADD_DEVICE) gap->flags |= FLAG_MGMT_CONN_CONTROL; } ready_status(gap, true); } static void read_version_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct bt_gap *gap = user_data; const struct mgmt_rp_read_version *rp = param; if (status != MGMT_STATUS_SUCCESS) { ready_status(gap, false); return; } if (length < sizeof(*rp)) { ready_status(gap, false); return; } gap->mgmt_version = rp->version; gap->mgmt_revision = le16_to_cpu(rp->revision); if (!mgmt_send(gap->mgmt, MGMT_OP_READ_COMMANDS, MGMT_INDEX_NONE, 0, NULL, read_commands_complete, gap, NULL)) { ready_status(gap, false); return; } } struct bt_gap *bt_gap_new_default(void) { return bt_gap_new_index(0x0000); } struct bt_gap *bt_gap_new_index(uint16_t index) { struct bt_gap *gap; if (index == MGMT_INDEX_NONE) return NULL; gap = new0(struct bt_gap, 1); gap->index = index; gap->mgmt = mgmt_new_default(); if (!gap->mgmt) { free(gap); return NULL; } gap->irk_list = queue_new(); gap->mgmt_ready = false; if (!mgmt_send(gap->mgmt, MGMT_OP_READ_VERSION, MGMT_INDEX_NONE, 0, NULL, read_version_complete, gap, NULL)) { mgmt_unref(gap->mgmt); return NULL; } return bt_gap_ref(gap); } struct bt_gap *bt_gap_ref(struct bt_gap *gap) { if (!gap) return NULL; __sync_fetch_and_add(&gap->ref_count, 1); return gap; } void bt_gap_unref(struct bt_gap *gap) { if (!gap) return; if (__sync_sub_and_fetch(&gap->ref_count, 1)) return; gap->mgmt_ready = false; mgmt_cancel_all(gap->mgmt); mgmt_unregister_all(gap->mgmt); if (gap->ready_destroy) gap->ready_destroy(gap->ready_data); queue_destroy(gap->irk_list, irk_entry_free); mgmt_unref(gap->mgmt); free(gap); } bool bt_gap_set_ready_handler(struct bt_gap *gap, bt_gap_ready_func_t handler, void *user_data, bt_gap_destroy_func_t destroy) { if (!gap) return false; if (gap->ready_destroy) gap->ready_destroy(gap->ready_data); gap->ready_handler = handler; gap->ready_destroy = destroy; gap->ready_data = user_data; return true; } bool bt_gap_set_static_addr(struct bt_gap *gap, uint8_t addr[6]) { if (!gap) return false; memcpy(gap->static_addr, addr, 6); return true; } bool bt_gap_set_local_irk(struct bt_gap *gap, uint8_t key[16]) { if (!gap) return false; memcpy(gap->local_irk, key, 16); return true; } bool bt_gap_add_peer_irk(struct bt_gap *gap, uint8_t addr_type, uint8_t addr[6], uint8_t key[16]) { struct irk_entry *irk; if (!gap) return false; if (addr_type > BT_GAP_ADDR_TYPE_LE_RANDOM) return false; irk = new0(struct irk_entry, 1); irk->addr_type = addr_type; memcpy(irk->addr, addr, 6); memcpy(irk->key, key, 16); if (!queue_push_tail(gap->irk_list, irk)) { free(irk); return false; } return true; } bluez-5.87/src/shared/PaxHeaders/hfp.h0000644000000000000000000000005015142177620014643 xustar0020 atime=1783092894 20 ctime=1783094099 bluez-5.87/src/shared/hfp.h0000644000000000000000000002073015142177620014326 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #include #define HFP_HF_FEAT_ECNR 0x00000001 #define HFP_HF_FEAT_3WAY 0x00000002 #define HFP_HF_FEAT_CLIP 0x00000004 #define HFP_HF_FEAT_VOICE_RECOGNITION 0x00000008 #define HFP_HF_FEAT_REMOTE_VOLUME_CONTROL 0x00000010 #define HFP_HF_FEAT_ENHANCED_CALL_STATUS 0x00000020 #define HFP_HF_FEAT_ENHANCED_CALL_CONTROL 0x00000040 #define HFP_HF_FEAT_CODEC_NEGOTIATION 0x00000080 #define HFP_HF_FEAT_HF_INDICATORS 0x00000100 #define HFP_HF_FEAT_ESCO_S4_T2 0x00000200 #define HFP_HF_FEAT_ENHANCED_VOICE_RECOGNITION_STATUS 0x00000400 #define HFP_HF_FEAT_VOICE_RECOGNITION_TEXT 0x00000800 #define HFP_AG_FEAT_3WAY 0x00000001 #define HFP_AG_FEAT_ECNR 0x00000002 #define HFP_AG_FEAT_VOICE_RECOGNITION 0x00000004 #define HFP_AG_FEAT_IN_BAND_RING_TONE 0x00000008 #define HFP_AG_FEAT_ATTACH_VOICE_TAG 0x00000010 #define HFP_AG_FEAT_REJECT_CALL 0x00000020 #define HFP_AG_FEAT_ENHANCED_CALL_STATUS 0x00000040 #define HFP_AG_FEAT_ENHANCED_CALL_CONTROL 0x00000080 #define HFP_AG_FEAT_EXTENDED_RES_CODE 0x00000100 #define HFP_AG_FEAT_CODEC_NEGOTIATION 0x00000200 #define HFP_AG_FEAT_HF_INDICATORS 0x00000400 #define HFP_AG_FEAT_ESCO_S4_T2 0x00000800 #define HFP_AG_FEAT_ENHANCED_VOICE_RECOGNITION_STATUS 0x00001000 #define HFP_AG_FEAT_VOICE_RECOGNITION_TEXT 0x00002000 #define HFP_CHLD_0 1 << 0 #define HFP_CHLD_1 1 << 1 #define HFP_CHLD_2 1 << 2 #define HFP_CHLD_3 1 << 3 #define HFP_CHLD_4 1 << 4 #define HFP_CHLD_1x 1 << 5 #define HFP_CHLD_2x 1 << 6 enum hfp_result { HFP_RESULT_OK = 0, HFP_RESULT_CONNECT = 1, HFP_RESULT_RING = 2, HFP_RESULT_NO_CARRIER = 3, HFP_RESULT_ERROR = 4, HFP_RESULT_NO_DIALTONE = 6, HFP_RESULT_BUSY = 7, HFP_RESULT_NO_ANSWER = 8, HFP_RESULT_DELAYED = 9, HFP_RESULT_REJECTED = 10, HFP_RESULT_CME_ERROR = 11, }; enum hfp_error { HFP_ERROR_AG_FAILURE = 0, HFP_ERROR_NO_CONNECTION_TO_PHONE = 1, HFP_ERROR_OPERATION_NOT_ALLOWED = 3, HFP_ERROR_OPERATION_NOT_SUPPORTED = 4, HFP_ERROR_PH_SIM_PIN_REQUIRED = 5, HFP_ERROR_SIM_NOT_INSERTED = 10, HFP_ERROR_SIM_PIN_REQUIRED = 11, HFP_ERROR_SIM_PUK_REQUIRED = 12, HFP_ERROR_SIM_FAILURE = 13, HFP_ERROR_SIM_BUSY = 14, HFP_ERROR_INCORRECT_PASSWORD = 16, HFP_ERROR_SIM_PIN2_REQUIRED = 17, HFP_ERROR_SIM_PUK2_REQUIRED = 18, HFP_ERROR_MEMORY_FULL = 20, HFP_ERROR_INVALID_INDEX = 21, HFP_ERROR_MEMORY_FAILURE = 23, HFP_ERROR_TEXT_STRING_TOO_LONG = 24, HFP_ERROR_INVALID_CHARS_IN_TEXT_STRING = 25, HFP_ERROR_DIAL_STRING_TO_LONG = 26, HFP_ERROR_INVALID_CHARS_IN_DIAL_STRING = 27, HFP_ERROR_NO_NETWORK_SERVICE = 30, HFP_ERROR_NETWORK_TIMEOUT = 31, HFP_ERROR_NETWORK_NOT_ALLOWED = 32, }; enum hfp_gw_cmd_type { HFP_GW_CMD_TYPE_READ, HFP_GW_CMD_TYPE_SET, HFP_GW_CMD_TYPE_TEST, HFP_GW_CMD_TYPE_COMMAND }; enum hfp_indicator { HFP_INDICATOR_SERVICE = 0, HFP_INDICATOR_CALL, HFP_INDICATOR_CALLSETUP, HFP_INDICATOR_CALLHELD, HFP_INDICATOR_SIGNAL, HFP_INDICATOR_ROAM, HFP_INDICATOR_BATTCHG, HFP_INDICATOR_LAST }; enum hfp_call { CIND_CALL_NONE = 0, CIND_CALL_IN_PROGRESS }; enum hfp_call_setup { CIND_CALLSETUP_NONE = 0, CIND_CALLSETUP_INCOMING, CIND_CALLSETUP_DIALING, CIND_CALLSETUP_ALERTING }; enum hfp_call_held { CIND_CALLHELD_NONE = 0, CIND_CALLHELD_HOLD_AND_ACTIVE, CIND_CALLHELD_HOLD }; enum hfp_call_status { CALL_STATUS_ACTIVE = 0, CALL_STATUS_HELD, CALL_STATUS_DIALING, CALL_STATUS_ALERTING, CALL_STATUS_INCOMING, CALL_STATUS_WAITING, CALL_STATUS_RESPONSE_AND_HOLD }; struct hfp_context; typedef void (*hfp_result_func_t)(struct hfp_context *context, enum hfp_gw_cmd_type type, void *user_data); typedef void (*hfp_destroy_func_t)(void *user_data); typedef void (*hfp_debug_func_t)(const char *str, void *user_data); typedef void (*hfp_command_func_t)(const char *command, void *user_data); typedef void (*hfp_disconnect_func_t)(void *user_data); struct hfp_gw; struct hfp_gw *hfp_gw_new(int fd); struct hfp_gw *hfp_gw_ref(struct hfp_gw *hfp); void hfp_gw_unref(struct hfp_gw *hfp); bool hfp_gw_set_debug(struct hfp_gw *hfp, hfp_debug_func_t callback, void *user_data, hfp_destroy_func_t destroy); bool hfp_gw_set_close_on_unref(struct hfp_gw *hfp, bool do_close); bool hfp_gw_set_permissive_syntax(struct hfp_gw *hfp, bool permissive); bool hfp_gw_send_result(struct hfp_gw *hfp, enum hfp_result result); bool hfp_gw_send_error(struct hfp_gw *hfp, enum hfp_error error); bool hfp_gw_send_info(struct hfp_gw *hfp, const char *format, ...) __attribute__((format(printf, 2, 3))); bool hfp_gw_set_command_handler(struct hfp_gw *hfp, hfp_command_func_t callback, void *user_data, hfp_destroy_func_t destroy); bool hfp_gw_set_disconnect_handler(struct hfp_gw *hfp, hfp_disconnect_func_t callback, void *user_data, hfp_destroy_func_t destroy); bool hfp_gw_disconnect(struct hfp_gw *hfp); bool hfp_gw_register(struct hfp_gw *hfp, hfp_result_func_t callback, const char *prefix, void *user_data, hfp_destroy_func_t destroy); bool hfp_gw_unregister(struct hfp_gw *hfp, const char *prefix); bool hfp_context_get_number(struct hfp_context *context, unsigned int *val); bool hfp_context_get_number_default(struct hfp_context *context, unsigned int *val, unsigned int default_val); bool hfp_context_open_container(struct hfp_context *context); bool hfp_context_close_container(struct hfp_context *context); bool hfp_context_is_container_close(struct hfp_context *context); bool hfp_context_get_string(struct hfp_context *context, char *buf, uint8_t len); bool hfp_context_get_unquoted_string(struct hfp_context *context, char *buf, uint8_t len); bool hfp_context_get_range(struct hfp_context *context, unsigned int *min, unsigned int *max); bool hfp_context_has_next(struct hfp_context *context); void hfp_context_skip_field(struct hfp_context *context); typedef void (*hfp_hf_result_func_t)(struct hfp_context *context, void *user_data); typedef void (*hfp_response_func_t)(enum hfp_result result, enum hfp_error cme_err, void *user_data); struct hfp_hf; struct hfp_hf_callbacks { void (*session_ready)(enum hfp_result result, enum hfp_error cme_err, void *user_data); void (*update_indicator)(enum hfp_indicator indicator, uint32_t val, void *user_data); void (*update_operator)(const char *operator_name, void *user_data); void (*update_inband_ring)(bool enabled, void *user_data); void (*call_added)(uint id, enum hfp_call_status status, void *user_data); void (*call_removed)(uint id, void *user_data); void (*call_status_updated)(uint id, enum hfp_call_status status, void *user_data); void (*call_line_id_updated)(uint id, const char *number, uint type, void *user_data); void (*call_mpty_updated)(uint id, bool mpty, void *user_data); }; struct hfp_hf *hfp_hf_new(int fd); struct hfp_hf *hfp_hf_ref(struct hfp_hf *hfp); void hfp_hf_unref(struct hfp_hf *hfp); bool hfp_hf_set_debug(struct hfp_hf *hfp, hfp_debug_func_t callback, void *user_data, hfp_destroy_func_t destroy); bool hfp_hf_set_close_on_unref(struct hfp_hf *hfp, bool do_close); bool hfp_hf_set_disconnect_handler(struct hfp_hf *hfp, hfp_disconnect_func_t callback, void *user_data, hfp_destroy_func_t destroy); bool hfp_hf_disconnect(struct hfp_hf *hfp); bool hfp_hf_register(struct hfp_hf *hfp, hfp_hf_result_func_t callback, const char *prefix, void *user_data, hfp_destroy_func_t destroy); bool hfp_hf_unregister(struct hfp_hf *hfp, const char *prefix); bool hfp_hf_send_command(struct hfp_hf *hfp, hfp_response_func_t resp_cb, void *user_data, const char *format, ...); bool hfp_hf_session_register(struct hfp_hf *hfp, struct hfp_hf_callbacks *callbacks, void *callbacks_data); bool hfp_hf_session(struct hfp_hf *hfp); const char *hfp_hf_call_get_number(struct hfp_hf *hfp, uint id); bool hfp_hf_call_get_multiparty(struct hfp_hf *hfp, uint id, bool *mpty); bool hfp_hf_dial(struct hfp_hf *hfp, const char *number, hfp_response_func_t resp_cb, void *user_data); bool hfp_hf_release_and_accept(struct hfp_hf *hfp, hfp_response_func_t resp_cb, void *user_data); bool hfp_hf_swap_calls(struct hfp_hf *hfp, hfp_response_func_t resp_cb, void *user_data); bool hfp_hf_call_answer(struct hfp_hf *hfp, uint id, hfp_response_func_t resp_cb, void *user_data); bool hfp_hf_call_hangup(struct hfp_hf *hfp, uint id, hfp_response_func_t resp_cb, void *user_data); bluez-5.87/src/shared/PaxHeaders/io-ell.c0000644000000000000000000000005015017252326015240 xustar0020 atime=1783093491 20 ctime=1783094100 bluez-5.87/src/shared/io-ell.c0000644000000000000000000001261415017252326014725 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "src/shared/io.h" struct io_watch { struct io *io; io_callback_func_t cb; io_destroy_func_t destroy; void *user_data; }; struct io { int ref_count; struct l_io *l_io; struct io_watch *read_watch; struct io_watch *write_watch; struct io_watch *disc_watch; }; static struct io *io_ref(struct io *io) { if (!io) return NULL; __sync_fetch_and_add(&io->ref_count, 1); return io; } static void io_unref(struct io *io) { if (!io) return; if (__sync_sub_and_fetch(&io->ref_count, 1)) return; l_free(io); } static void watch_destroy(void *user_data) { struct io_watch *watch = user_data; struct io *io; if (!watch) return; io = watch->io; if (watch == io->read_watch) io->read_watch = NULL; else if (watch == io->write_watch) io->write_watch = NULL; else if (watch == io->disc_watch) io->disc_watch = NULL; if (watch->destroy) watch->destroy(watch->user_data); io_unref(watch->io); l_free(watch); } static struct io_watch *watch_new(struct io *io, io_callback_func_t cb, void *user_data, io_destroy_func_t destroy) { struct io_watch *watch; watch = l_new(struct io_watch, 1); watch->io = io_ref(io); watch->cb = cb; watch->user_data = user_data; watch->destroy = destroy; return watch; } static bool watch_callback(struct l_io *l_io, void *user_data) { struct io_watch *watch = user_data; if (!watch->cb) return false; return watch->cb(watch->io, watch->user_data); } static void disc_callback(struct l_io *l_io, void *user_data) { struct io_watch *watch = user_data; if (watch->cb) watch->cb(watch->io, watch->user_data); } struct io *io_new(int fd) { struct io *io; struct l_io *l_io; if (fd < 0) return NULL; io = l_new(struct io, 1); if (!io) return NULL; l_io = l_io_new(fd); if (!l_io) { l_free(io); return NULL; } io->l_io = l_io; return io_ref(io); } void io_destroy(struct io *io) { if (!io) return; l_io_set_read_handler(io->l_io, NULL, NULL, NULL); watch_destroy(io->read_watch); io->read_watch = NULL; l_io_set_write_handler(io->l_io, NULL, NULL, NULL); watch_destroy(io->write_watch); io->write_watch = NULL; l_io_set_disconnect_handler(io->l_io, NULL, NULL, NULL); watch_destroy(io->disc_watch); io->disc_watch = NULL; l_io_destroy(io->l_io); io->l_io = NULL; io_unref(io); } int io_get_fd(struct io *io) { if (!io || !io->l_io) return -ENOTCONN; return l_io_get_fd(io->l_io); } bool io_set_close_on_destroy(struct io *io, bool do_close) { if (!io || !io->l_io) return false; return l_io_set_close_on_destroy(io->l_io, do_close); } bool io_set_ignore_errqueue(struct io *io, bool do_ignore) { /* TODO: unimplemented */ return false; } bool io_set_read_handler(struct io *io, io_callback_func_t callback, void *user_data, io_destroy_func_t destroy) { bool result; if (!io || !io->l_io) return false; if (io->read_watch) { l_io_set_read_handler(io->l_io, NULL, NULL, NULL); if (!callback) { watch_destroy(io->read_watch); io->read_watch = NULL; return true; } } io->read_watch = watch_new(io, callback, user_data, destroy); result = l_io_set_read_handler(io->l_io, watch_callback, io->read_watch, watch_destroy); if (!result) { watch_destroy(io->read_watch); io->read_watch = NULL; } return result; } bool io_set_write_handler(struct io *io, io_callback_func_t callback, void *user_data, io_destroy_func_t destroy) { bool result; if (!io || !io->l_io) return false; if (io->write_watch) { l_io_set_write_handler(io->l_io, NULL, NULL, NULL); if (!callback) { watch_destroy(io->write_watch); io->write_watch = NULL; return true; } } io->write_watch = watch_new(io, callback, user_data, destroy); result = l_io_set_write_handler(io->l_io, watch_callback, io->write_watch, watch_destroy); if (!result) { watch_destroy(io->write_watch); io->write_watch = NULL; } return result; } bool io_set_disconnect_handler(struct io *io, io_callback_func_t callback, void *user_data, io_destroy_func_t destroy) { bool result; if (!io || !io->l_io) return false; if (io->disc_watch) { l_io_set_disconnect_handler(io->l_io, NULL, NULL, NULL); if (!callback) { watch_destroy(io->disc_watch); io->disc_watch = NULL; return true; } } io->disc_watch = watch_new(io, callback, user_data, destroy); result = l_io_set_disconnect_handler(io->l_io, disc_callback, io->disc_watch, watch_destroy); if (!result) { watch_destroy(io->disc_watch); io->disc_watch = NULL; } return result; } ssize_t io_send(struct io *io, const struct iovec *iov, int iovcnt) { ssize_t ret; int fd; if (!io || !io->l_io) return -ENOTCONN; fd = l_io_get_fd(io->l_io); if (fd < 0) return -ENOTCONN; do { ret = writev(fd, iov, iovcnt); } while (ret < 0 && errno == EINTR); if (ret < 0) return -errno; return ret; } bool io_shutdown(struct io *io) { int fd; if (!io || !io->l_io) return false; fd = l_io_get_fd(io->l_io); if (fd < 0) return false; return shutdown(fd, SHUT_RDWR) == 0; } unsigned int io_glib_add_err_watch(void *giochannel, io_glib_err_func_t func, void *user_data) { return 0; } bluez-5.87/src/shared/PaxHeaders/bass.h0000644000000000000000000000005015110075565015016 xustar0020 atime=1783092959 20 ctime=1783094100 bluez-5.87/src/shared/bass.h0000644000000000000000000001062715110075565014505 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright 2023-2024 NXP * */ struct bt_bass; struct bt_bcast_src; #define NUM_BCAST_RECV_STATES 2 #define BT_BASS_BCAST_CODE_SIZE 16 #define BT_BASS_BIG_SYNC_FAILED_BITMASK 0xFFFFFFFF #define BT_BASS_BCAST_SRC_LEN 15 #define BT_BASS_BCAST_SRC_SUBGROUP_LEN 5 /* Application error codes */ #define BT_BASS_ERROR_OPCODE_NOT_SUPPORTED 0x80 #define BT_BASS_ERROR_INVALID_SOURCE_ID 0x81 /* PA_Sync_State values */ #define BT_BASS_NOT_SYNCHRONIZED_TO_PA 0x00 #define BT_BASS_SYNC_INFO_RE 0x01 #define BT_BASS_SYNCHRONIZED_TO_PA 0x02 #define BT_BASS_FAILED_TO_SYNCHRONIZE_TO_PA 0x03 #define BT_BASS_NO_PAST 0x04 /* BIG_Encryption values */ #define BT_BASS_BIG_ENC_STATE_NO_ENC 0x00 #define BT_BASS_BIG_ENC_STATE_BCODE_REQ 0x01 #define BT_BASS_BIG_ENC_STATE_DEC 0x02 #define BT_BASS_BIG_ENC_STATE_BAD_CODE 0x03 /* Broadcast Audio Scan Control Point * header structure */ struct bt_bass_bcast_audio_scan_cp_hdr { uint8_t op; } __packed; #define BT_BASS_REMOTE_SCAN_STOPPED 0x00 #define BT_BASS_REMOTE_SCAN_STARTED 0x01 #define BT_BASS_ADD_SRC 0x02 #define BT_BASS_ADDR_PUBLIC 0x00 #define BT_BASS_ADDR_RANDOM 0x01 /* PA_Sync values */ #define PA_SYNC_NO_SYNC 0x00 #define PA_SYNC_PAST 0x01 #define PA_SYNC_NO_PAST 0x02 /* BIS_Sync no preference bitmask */ #define BIS_SYNC_NO_PREF 0xFFFFFFFF #define PA_INTERVAL_UNKNOWN 0xFFFF struct bt_bass_add_src_params { uint8_t addr_type; bdaddr_t addr; uint8_t sid; uint8_t bid[3]; uint8_t pa_sync; uint16_t pa_interval; uint8_t num_subgroups; uint8_t subgroup_data[]; } __packed; #define BT_BASS_MOD_SRC 0x03 struct bt_bass_mod_src_params { uint8_t id; uint8_t pa_sync; uint16_t pa_interval; uint8_t num_subgroups; uint8_t subgroup_data[]; } __packed; #define BT_BASS_SET_BCAST_CODE 0x04 struct bt_bass_set_bcast_code_params { uint8_t id; uint8_t bcast_code[BT_BASS_BCAST_CODE_SIZE]; } __packed; #define BT_BASS_REMOVE_SRC 0x05 struct bt_bass_remove_src_params { uint8_t id; } __packed; typedef void (*bt_bass_func_t)(struct bt_bass *bass, void *user_data); typedef void (*bt_bass_destroy_func_t)(void *user_data); typedef void (*bt_bass_debug_func_t)(const char *str, void *user_data); typedef void (*bt_bass_src_func_t)(uint8_t id, uint32_t bid, uint8_t enc, uint8_t state, uint32_t bis_sync, void *user_data); typedef int (*bt_bass_cp_handler_func_t)(struct bt_bcast_src *bcast_src, uint8_t op, void *params, void *user_data); struct bt_att *bt_bass_get_att(struct bt_bass *bass); struct bt_gatt_client *bt_bass_get_client(struct bt_bass *bass); unsigned int bt_bass_register(bt_bass_func_t attached, bt_bass_func_t detached, void *user_data); bool bt_bass_unregister(unsigned int id); bool bt_bass_set_debug(struct bt_bass *bass, bt_bass_debug_func_t func, void *user_data, bt_bass_destroy_func_t destroy); struct bt_bass *bt_bass_new(struct gatt_db *ldb, struct gatt_db *rdb, const bdaddr_t *adapter_bdaddr); bool bt_bass_set_user_data(struct bt_bass *bass, void *user_data); struct bt_bass *bt_bass_ref(struct bt_bass *bass); void bt_bass_unref(struct bt_bass *bass); bool bt_bass_attach(struct bt_bass *bass, struct bt_gatt_client *client); bool bt_bass_set_att(struct bt_bass *bass, struct bt_att *att); void bt_bass_detach(struct bt_bass *bass); void bt_bass_add_db(struct gatt_db *db, const bdaddr_t *adapter_bdaddr); int bt_bass_send(struct bt_bass *bass, struct bt_bass_bcast_audio_scan_cp_hdr *hdr, struct iovec *params); unsigned int bt_bass_src_register(struct bt_bass *bass, bt_bass_src_func_t cb, void *user_data, bt_bass_destroy_func_t destroy); bool bt_bass_src_unregister(struct bt_bass *bass, unsigned int id); unsigned int bt_bass_cp_handler_register(struct bt_bass *bass, bt_bass_cp_handler_func_t handler, bt_bass_destroy_func_t destroy, void *user_data); bool bt_bass_cp_handler_unregister(struct bt_bass *bass, unsigned int id); int bt_bass_set_pa_sync(struct bt_bcast_src *bcast_src, uint8_t sync_state); int bt_bass_get_pa_sync(struct bt_bcast_src *bcast_src, uint8_t *sync_state); int bt_bass_set_bis_sync(struct bt_bcast_src *bcast_src, uint8_t bis); int bt_bass_clear_bis_sync(struct bt_bcast_src *bcast_src, uint8_t bis); bool bt_bass_check_bis(struct bt_bcast_src *bcast_src, uint8_t bis); int bt_bass_set_enc(struct bt_bcast_src *bcast_src, uint8_t enc); bluez-5.87/src/shared/PaxHeaders/util.c0000644000000000000000000000005015200607060015025 xustar0020 atime=1783092683 20 ctime=1783094099 bluez-5.87/src/shared/util.c0000644000000000000000000020674315200607060014522 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * Copyright 2023-2024 NXP * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_RANDOM_H #include #endif #include /* define MAX_INPUT for musl */ #ifndef MAX_INPUT #define MAX_INPUT _POSIX_MAX_INPUT #endif #include "src/shared/util.h" void *util_malloc(size_t size) { if (__builtin_expect(!!size, 1)) { void *ptr; ptr = malloc(size); if (ptr) return ptr; fprintf(stderr, "failed to allocate %zu bytes\n", size); abort(); } return NULL; } void *util_memdup(const void *src, size_t size) { void *cpy; if (!src || !size) return NULL; cpy = util_malloc(size); if (!cpy) return NULL; memcpy(cpy, src, size); return cpy; } void util_debug_va(util_debug_func_t function, void *user_data, const char *format, va_list va) { char str[MAX_INPUT]; if (!function || !format) return; vsnprintf(str, sizeof(str), format, va); function(str, user_data); } void util_debug(util_debug_func_t function, void *user_data, const char *format, ...) { va_list ap; if (!function || !format) return; va_start(ap, format); util_debug_va(function, user_data, format, ap); va_end(ap); } void util_hexdump(const char dir, const unsigned char *buf, size_t len, util_debug_func_t function, void *user_data) { static const char hexdigits[] = "0123456789abcdef"; char str[68]; size_t i; if (!function || !len) return; str[0] = dir; for (i = 0; i < len; i++) { str[((i % 16) * 3) + 1] = ' '; str[((i % 16) * 3) + 2] = hexdigits[buf[i] >> 4]; str[((i % 16) * 3) + 3] = hexdigits[buf[i] & 0xf]; str[(i % 16) + 51] = isprint(buf[i]) ? buf[i] : '.'; if ((i + 1) % 16 == 0) { str[49] = ' '; str[50] = ' '; str[67] = '\0'; function(str, user_data); str[0] = ' '; } } if (i % 16 > 0) { size_t j; for (j = (i % 16); j < 16; j++) { str[(j * 3) + 1] = ' '; str[(j * 3) + 2] = ' '; str[(j * 3) + 3] = ' '; str[j + 51] = ' '; } str[49] = ' '; str[50] = ' '; str[67] = '\0'; function(str, user_data); } } /* Helper to print debug information of bitfields */ uint64_t util_debug_bit(const char *label, uint64_t val, const struct util_bit_debugger *table, util_debug_func_t function, void *user_data) { uint64_t mask = val; int i; for (i = 0; table[i].str; i++) { if (val & (((uint64_t) 1) << table[i].bit)) { util_debug(function, user_data, "%s%s", label, table[i].str); mask &= ~(((uint64_t) 1) << table[i].bit); } } return mask; } static const struct util_ltv_debugger* ltv_debugger(const struct util_ltv_debugger *debugger, size_t num, uint8_t type) { size_t i; if (!debugger || !num) return NULL; for (i = 0; i < num; i++) { const struct util_ltv_debugger *debug = &debugger[i]; if (debug->type == type) return debug; } return NULL; } /* Helper to itertate over LTV entries */ bool util_ltv_foreach(const uint8_t *data, uint8_t len, uint8_t *type, util_ltv_func_t func, void *user_data) { struct iovec iov; int i; if (!func || !data) return false; iov.iov_base = (void *) data; iov.iov_len = len; for (i = 0; iov.iov_len; i++) { uint8_t l, t, *v; if (!util_iov_pull_u8(&iov, &l)) return false; if (!l) { func(i, l, 0, NULL, user_data); continue; } if (!util_iov_pull_u8(&iov, &t)) return false; l--; if (l) { v = util_iov_pull_mem(&iov, l); if (!v) return false; } else v = NULL; if (!type || *type == t) func(i, l, t, v, user_data); } return true; } /* Helper to add l,t,v data in an iovec struct */ void util_ltv_push(struct iovec *output, uint8_t l, uint8_t t, void *v) { output->iov_base = realloc(output->iov_base, output->iov_len + l + 2); util_iov_push_u8(output, l + 1); util_iov_push_u8(output, t); util_iov_push_mem(output, l, v); } /* Helper to print debug information of LTV entries */ bool util_debug_ltv(const uint8_t *data, uint8_t len, const struct util_ltv_debugger *debugger, size_t num, util_debug_func_t function, void *user_data) { struct iovec iov; int i; iov.iov_base = (void *) data; iov.iov_len = len; for (i = 0; iov.iov_len; i++) { uint8_t l, t, *v; const struct util_ltv_debugger *debug; if (!util_iov_pull_u8(&iov, &l)) { util_debug(function, user_data, "Unable to pull length"); return false; } if (!l) { util_debug(function, user_data, "#%d: len 0x%02x", i, l); continue; } if (!util_iov_pull_u8(&iov, &t)) { util_debug(function, user_data, "Unable to pull type"); return false; } util_debug(function, user_data, "#%d: len 0x%02x type 0x%02x", i, l, t); l--; v = util_iov_pull_mem(&iov, l); if (!v) { util_debug(function, user_data, "Unable to pull value"); return false; } debug = ltv_debugger(debugger, num, t); if (debug) debug->func(v, l, function, user_data); else util_hexdump(' ', (void *)v, l, function, user_data); } return true; } /* Helper for getting the dirent type in case readdir returns DT_UNKNOWN */ unsigned char util_get_dt(const char *parent, const char *name) { char filename[PATH_MAX]; struct stat st; snprintf(filename, PATH_MAX, "%s/%s", parent, name); if (lstat(filename, &st) == 0 && S_ISDIR(st.st_mode)) return DT_DIR; return DT_UNKNOWN; } /* Helper for getting a random in case getrandom unavailable (glibc < 2.25) */ ssize_t util_getrandom(void *buf, size_t buflen, unsigned int flags) { #ifdef HAVE_GETRANDOM return getrandom(buf, buflen, flags); #else int fd; ssize_t bytes; fd = open("/dev/urandom", O_CLOEXEC); if (fd < 0) return -1; bytes = read(fd, buf, buflen); close(fd); return bytes; #endif } /* Helpers for bitfield operations */ /* Find unique id in range from 1 to max but no bigger than 64. */ uint8_t util_get_uid(uint64_t *bitmap, uint8_t max) { uint8_t id; id = ffsll(~*bitmap); if (!id || id > max) return 0; *bitmap |= ((uint64_t)1) << (id - 1); return id; } /* Clear id bit in bitmap */ void util_clear_uid(uint64_t *bitmap, uint8_t id) { if (!id || id > 64) return; *bitmap &= ~(((uint64_t)1) << (id - 1)); } struct iovec *util_iov_dup(const struct iovec *iov, size_t cnt) { struct iovec *dup; size_t i; if (!iov) return NULL; dup = new0(struct iovec, cnt); for (i = 0; i < cnt; i++) util_iov_memcpy(&dup[i], iov[i].iov_base, iov[i].iov_len); return dup; } int util_iov_memcmp(const struct iovec *iov1, const struct iovec *iov2) { if (!iov1) return 1; if (!iov2) return -1; if (iov1->iov_len != iov2->iov_len) return iov1->iov_len - iov2->iov_len; return memcmp(iov1->iov_base, iov2->iov_base, iov1->iov_len); } void util_iov_memcpy(struct iovec *iov, void *src, size_t len) { if (!iov || !src || !len) return; iov->iov_base = realloc(iov->iov_base, len); iov->iov_len = len; memcpy(iov->iov_base, src, len); } void util_iov_free(struct iovec *iov, size_t cnt) { size_t i; if (!iov) return; for (i = 0; i < cnt; i++) free(iov[i].iov_base); free(iov); } void *util_iov_push(struct iovec *iov, size_t len) { void *data; if (!iov) return NULL; data = iov->iov_base + iov->iov_len; iov->iov_len += len; return data; } void *util_iov_push_mem(struct iovec *iov, size_t len, const void *data) { void *p; p = util_iov_push(iov, len); if (!p) return NULL; if (data) memcpy(p, data, len); return p; } void *util_iov_push_le64(struct iovec *iov, uint64_t val) { void *p; p = util_iov_push(iov, sizeof(val)); if (!p) return NULL; put_le64(val, p); return p; } void *util_iov_push_be64(struct iovec *iov, uint64_t val) { void *p; p = util_iov_push(iov, sizeof(val)); if (!p) return NULL; put_be64(val, p); return p; } void *util_iov_push_le32(struct iovec *iov, uint32_t val) { void *p; p = util_iov_push(iov, sizeof(val)); if (!p) return NULL; put_le32(val, p); return p; } void *util_iov_push_be32(struct iovec *iov, uint32_t val) { void *p; p = util_iov_push(iov, sizeof(val)); if (!p) return NULL; put_be32(val, p); return p; } void *util_iov_push_le24(struct iovec *iov, uint32_t val) { void *p; p = util_iov_push(iov, sizeof(uint24_t)); if (!p) return NULL; put_le24(val, p); return p; } void *util_iov_push_be24(struct iovec *iov, uint32_t val) { void *p; p = util_iov_push(iov, sizeof(uint24_t)); if (!p) return NULL; put_le24(val, p); return p; } void *util_iov_push_le16(struct iovec *iov, uint16_t val) { void *p; p = util_iov_push(iov, sizeof(val)); if (!p) return NULL; put_le16(val, p); return p; } void *util_iov_push_be16(struct iovec *iov, uint16_t val) { void *p; p = util_iov_push(iov, sizeof(val)); if (!p) return NULL; put_be16(val, p); return p; } void *util_iov_push_u8(struct iovec *iov, uint8_t val) { void *p; p = util_iov_push(iov, sizeof(val)); if (!p) return NULL; put_u8(val, p); return p; } void *util_iov_append(struct iovec *iov, const void *data, size_t len) { iov->iov_base = realloc(iov->iov_base, iov->iov_len + len); return util_iov_push_mem(iov, len, data); } struct iovec *util_iov_new(void *data, size_t len) { struct iovec *iov; iov = new0(struct iovec, 1); util_iov_append(iov, data, len); return iov; } void *util_iov_pull(struct iovec *iov, size_t len) { if (!iov) return NULL; if (iov->iov_len < len) return NULL; iov->iov_base += len; iov->iov_len -= len; return iov->iov_base; } void *util_iov_pull_mem(struct iovec *iov, size_t len) { void *data = iov->iov_base; if (util_iov_pull(iov, len)) return data; return NULL; } void *util_iov_pull_le64(struct iovec *iov, uint64_t *val) { void *data = iov->iov_base; if (util_iov_pull(iov, sizeof(*val))) { *val = get_le64(data); return data; } return NULL; } void *util_iov_pull_be64(struct iovec *iov, uint64_t *val) { void *data = iov->iov_base; if (util_iov_pull(iov, sizeof(*val))) { *val = get_be64(data); return data; } return NULL; } void *util_iov_pull_le32(struct iovec *iov, uint32_t *val) { void *data = iov->iov_base; if (util_iov_pull(iov, sizeof(*val))) { *val = get_le32(data); return data; } return NULL; } void *util_iov_pull_be32(struct iovec *iov, uint32_t *val) { void *data = iov->iov_base; if (util_iov_pull(iov, sizeof(*val))) { *val = get_be32(data); return data; } return NULL; } void *util_iov_pull_le24(struct iovec *iov, uint32_t *val) { void *data = iov->iov_base; if (util_iov_pull(iov, sizeof(uint24_t))) { *val = get_le24(data); return data; } return NULL; } void *util_iov_pull_be24(struct iovec *iov, uint32_t *val) { void *data = iov->iov_base; if (util_iov_pull(iov, sizeof(uint24_t))) { *val = get_be24(data); return data; } return NULL; } void *util_iov_pull_le16(struct iovec *iov, uint16_t *val) { void *data = iov->iov_base; if (util_iov_pull(iov, sizeof(*val))) { *val = get_le16(data); return data; } return NULL; } void *util_iov_pull_be16(struct iovec *iov, uint16_t *val) { void *data = iov->iov_base; if (util_iov_pull(iov, sizeof(*val))) { *val = get_be16(data); return data; } return NULL; } void *util_iov_pull_u8(struct iovec *iov, uint8_t *val) { void *data = iov->iov_base; if (util_iov_pull(iov, sizeof(*val))) { *val = get_u8(data); return data; } return NULL; } static const struct { uint16_t uuid; const char *str; } uuid16_table[] = { { 0x0001, "SDP" }, { 0x0003, "RFCOMM" }, { 0x0005, "TCS-BIN" }, { 0x0007, "ATT" }, { 0x0008, "OBEX" }, { 0x000f, "BNEP" }, { 0x0010, "UPNP" }, { 0x0011, "HIDP" }, { 0x0012, "Hardcopy Control Channel" }, { 0x0014, "Hardcopy Data Channel" }, { 0x0016, "Hardcopy Notification" }, { 0x0017, "AVCTP" }, { 0x0019, "AVDTP" }, { 0x001b, "CMTP" }, { 0x001e, "MCAP Control Channel" }, { 0x001f, "MCAP Data Channel" }, { 0x0100, "L2CAP" }, /* 0x0101 to 0x0fff undefined */ { 0x1000, "Service Discovery Server Service Class" }, { 0x1001, "Browse Group Descriptor Service Class" }, { 0x1002, "Public Browse Root" }, /* 0x1003 to 0x1100 undefined */ { 0x1101, "Serial Port" }, { 0x1102, "LAN Access Using PPP" }, { 0x1103, "Dialup Networking" }, { 0x1104, "IrMC Sync" }, { 0x1105, "OBEX Object Push" }, { 0x1106, "OBEX File Transfer" }, { 0x1107, "IrMC Sync Command" }, { 0x1108, "Headset" }, { 0x1109, "Cordless Telephony" }, { 0x110a, "Audio Source" }, { 0x110b, "Audio Sink" }, { 0x110c, "A/V Remote Control Target" }, { 0x110d, "Advanced Audio Distribution" }, { 0x110e, "A/V Remote Control" }, { 0x110f, "A/V Remote Control Controller" }, { 0x1110, "Intercom" }, { 0x1111, "Fax" }, { 0x1112, "Headset AG" }, { 0x1113, "WAP" }, { 0x1114, "WAP Client" }, { 0x1115, "PANU" }, { 0x1116, "NAP" }, { 0x1117, "GN" }, { 0x1118, "Direct Printing" }, { 0x1119, "Reference Printing" }, { 0x111a, "Basic Imaging Profile" }, { 0x111b, "Imaging Responder" }, { 0x111c, "Imaging Automatic Archive" }, { 0x111d, "Imaging Referenced Objects" }, { 0x111e, "Handsfree" }, { 0x111f, "Handsfree Audio Gateway" }, { 0x1120, "Direct Printing Reference Objects Service" }, { 0x1121, "Reflected UI" }, { 0x1122, "Basic Printing" }, { 0x1123, "Printing Status" }, { 0x1124, "Human Interface Device Service" }, { 0x1125, "Hardcopy Cable Replacement" }, { 0x1126, "HCR Print" }, { 0x1127, "HCR Scan" }, { 0x1128, "Common ISDN Access" }, /* 0x1129 and 0x112a undefined */ { 0x112d, "SIM Access" }, { 0x112e, "Phonebook Access Client" }, { 0x112f, "Phonebook Access Server" }, { 0x1130, "Phonebook Access" }, { 0x1131, "Headset HS" }, { 0x1132, "Message Access Server" }, { 0x1133, "Message Notification Server" }, { 0x1134, "Message Access Profile" }, { 0x1135, "GNSS" }, { 0x1136, "GNSS Server" }, { 0x1137, "3D Display" }, { 0x1138, "3D Glasses" }, { 0x1139, "3D Synchronization" }, { 0x113a, "MPS Profile" }, { 0x113b, "MPS Service" }, /* 0x113c to 0x11ff undefined */ { 0x1200, "PnP Information" }, { 0x1201, "Generic Networking" }, { 0x1202, "Generic File Transfer" }, { 0x1203, "Generic Audio" }, { 0x1204, "Generic Telephony" }, { 0x1205, "UPNP Service" }, { 0x1206, "UPNP IP Service" }, { 0x1300, "UPNP IP PAN" }, { 0x1301, "UPNP IP LAP" }, { 0x1302, "UPNP IP L2CAP" }, { 0x1303, "Video Source" }, { 0x1304, "Video Sink" }, { 0x1305, "Video Distribution" }, /* 0x1306 to 0x13ff undefined */ { 0x1400, "HDP" }, { 0x1401, "HDP Source" }, { 0x1402, "HDP Sink" }, /* 0x1403 to 0x17ff undefined */ { 0x1800, "Generic Access Profile" }, { 0x1801, "Generic Attribute Profile" }, { 0x1802, "Immediate Alert" }, { 0x1803, "Link Loss" }, { 0x1804, "Tx Power" }, { 0x1805, "Current Time Service" }, { 0x1806, "Reference Time Update Service" }, { 0x1807, "Next DST Change Service" }, { 0x1808, "Glucose" }, { 0x1809, "Health Thermometer" }, { 0x180a, "Device Information" }, /* 0x180b and 0x180c undefined */ { 0x180d, "Heart Rate" }, { 0x180e, "Phone Alert Status Service" }, { 0x180f, "Battery Service" }, { 0x1810, "Blood Pressure" }, { 0x1811, "Alert Notification Service" }, { 0x1812, "Human Interface Device" }, { 0x1813, "Scan Parameters" }, { 0x1814, "Running Speed and Cadence" }, { 0x1815, "Automation IO" }, { 0x1816, "Cycling Speed and Cadence" }, /* 0x1817 undefined */ { 0x1818, "Cycling Power" }, { 0x1819, "Location and Navigation" }, { 0x181a, "Environmental Sensing" }, { 0x181b, "Body Composition" }, { 0x181c, "User Data" }, { 0x181d, "Weight Scale" }, { 0x181e, "Bond Management" }, { 0x181f, "Continuous Glucose Monitoring" }, { 0x1820, "Internet Protocol Support" }, { 0x1821, "Indoor Positioning" }, { 0x1822, "Pulse Oximeter" }, { 0x1823, "HTTP Proxy" }, { 0x1824, "Transport Discovery" }, { 0x1825, "Object Transfer" }, { 0x1826, "Fitness Machine" }, { 0x1827, "Mesh Provisioning" }, { 0x1828, "Mesh Proxy" }, { 0x1843, "Audio Input Control" }, { 0x1844, "Volume Control" }, { 0x1845, "Volume Offset Control" }, { 0x1846, "Coordinated Set Identification" }, { 0x1848, "Media Control" }, { 0x1849, "Generic Media Control" }, { 0x184b, "Telephony Bearer" }, { 0x184c, "Generic Telephony Bearer" }, { 0x184d, "Microphone Control" }, { 0x184e, "Audio Stream Control" }, { 0x184f, "Broadcast Audio Scan" }, { 0x1850, "Published Audio Capabilities" }, { 0x1851, "Basic Audio Announcement" }, { 0x1852, "Broadcast Audio Announcement" }, { 0x1853, "Common Audio" }, { 0x1854, "Hearing Aid" }, { 0x1855, "Telephony and Media Audio" }, { 0x1856, "Public Broadcast Announcement" }, { 0x1858, "Gaming Audio" }, { 0x185b, "Ranging Service" }, /* 0x185c to 0x27ff undefined */ { 0x2800, "Primary Service" }, { 0x2801, "Secondary Service" }, { 0x2802, "Include" }, { 0x2803, "Characteristic" }, /* 0x2804 to 0x28ff undefined */ { 0x2900, "Characteristic Extended Properties" }, { 0x2901, "Characteristic User Description" }, { 0x2902, "Client Characteristic Configuration" }, { 0x2903, "Server Characteristic Configuration" }, { 0x2904, "Characteristic Format" }, { 0x2905, "Characteristic Aggregate Format" }, { 0x2906, "Valid Range" }, { 0x2907, "External Report Reference" }, { 0x2908, "Report Reference" }, { 0x2909, "Number of Digitals" }, { 0x290a, "Value Trigger Setting" }, { 0x290b, "Environmental Sensing Configuration" }, { 0x290c, "Environmental Sensing Measurement" }, { 0x290d, "Environmental Sensing Trigger Setting" }, { 0x290e, "Time Trigger Setting" }, /* 0x290f to 0x29ff undefined */ { 0x2a00, "Device Name" }, { 0x2a01, "Appearance" }, { 0x2a02, "Peripheral Privacy Flag" }, { 0x2a03, "Reconnection Address" }, { 0x2a04, "Peripheral Preferred Connection Parameters" }, { 0x2a05, "Service Changed" }, { 0x2a06, "Alert Level" }, { 0x2a07, "Tx Power Level" }, { 0x2a08, "Date Time" }, { 0x2a09, "Day of Week" }, { 0x2a0a, "Day Date Time" }, /* 0x2a0b undefined */ { 0x2a0c, "Exact Time 256" }, { 0x2a0d, "DST Offset" }, { 0x2a0e, "Time Zone" }, { 0x2a0f, "Local Time Information" }, /* 0x2a10 undefined */ { 0x2a11, "Time with DST" }, { 0x2a12, "Time Accuracy" }, { 0x2a13, "Time Source" }, { 0x2a14, "Reference Time Information" }, /* 0x2a15 undefined */ { 0x2a16, "Time Update Control Point" }, { 0x2a17, "Time Update State" }, { 0x2a18, "Glucose Measurement" }, { 0x2a19, "Battery Level" }, /* 0x2a1a and 0x2a1b undefined */ { 0x2a1c, "Temperature Measurement" }, { 0x2a1d, "Temperature Type" }, { 0x2a1e, "Intermediate Temperature" }, /* 0x2a1f and 0x2a20 undefined */ { 0x2a21, "Measurement Interval" }, { 0x2a22, "Boot Keyboard Input Report" }, { 0x2a23, "System ID" }, { 0x2a24, "Model Number String" }, { 0x2a25, "Serial Number String" }, { 0x2a26, "Firmware Revision String" }, { 0x2a27, "Hardware Revision String" }, { 0x2a28, "Software Revision String" }, { 0x2a29, "Manufacturer Name String" }, { 0x2a2a, "IEEE 11073-20601 Regulatory Cert. Data List" }, { 0x2a2b, "Current Time" }, { 0x2a2c, "Magnetic Declination" }, /* 0x2a2d to 0x2a30 undefined */ { 0x2a31, "Scan Refresh" }, { 0x2a32, "Boot Keyboard Output Report" }, { 0x2a33, "Boot Mouse Input Report" }, { 0x2a34, "Glucose Measurement Context" }, { 0x2a35, "Blood Pressure Measurement" }, { 0x2a36, "Intermediate Cuff Pressure" }, { 0x2a37, "Heart Rate Measurement" }, { 0x2a38, "Body Sensor Location" }, { 0x2a39, "Heart Rate Control Point" }, /* 0x2a3a to 0x2a3e undefined */ { 0x2a3f, "Alert Status" }, { 0x2a40, "Ringer Control Point" }, { 0x2a41, "Ringer Setting" }, { 0x2a42, "Alert Category ID Bit Mask" }, { 0x2a43, "Alert Category ID" }, { 0x2a44, "Alert Notification Control Point" }, { 0x2a45, "Unread Alert Status" }, { 0x2a46, "New Alert" }, { 0x2a47, "Supported New Alert Category" }, { 0x2a48, "Supported Unread Alert Category" }, { 0x2a49, "Blood Pressure Feature" }, { 0x2a4a, "HID Information" }, { 0x2a4b, "Report Map" }, { 0x2a4c, "HID Control Point" }, { 0x2a4d, "Report" }, { 0x2a4e, "Protocol Mode" }, { 0x2a4f, "Scan Interval Window" }, { 0x2a50, "PnP ID" }, { 0x2a51, "Glucose Feature" }, { 0x2a52, "Record Access Control Point" }, { 0x2a53, "RSC Measurement" }, { 0x2a54, "RSC Feature" }, { 0x2a55, "SC Control Point" }, { 0x2a56, "Digital" }, /* 0x2a57 undefined */ { 0x2a58, "Analog" }, /* 0x2a59 undefined */ { 0x2a5a, "Aggregate" }, { 0x2a5b, "CSC Measurement" }, { 0x2a5c, "CSC Feature" }, { 0x2a5d, "Sensor Location" }, /* 0x2a5e to 0x2a62 undefined */ { 0x2a63, "Cycling Power Measurement" }, { 0x2a64, "Cycling Power Vector" }, { 0x2a65, "Cycling Power Feature" }, { 0x2a66, "Cycling Power Control Point" }, { 0x2a67, "Location and Speed" }, { 0x2a68, "Navigation" }, { 0x2a69, "Position Quality" }, { 0x2a6a, "LN Feature" }, { 0x2a6b, "LN Control Point" }, { 0x2a6c, "Elevation" }, { 0x2a6d, "Pressure" }, { 0x2a6e, "Temperature" }, { 0x2a6f, "Humidity" }, { 0x2a70, "True Wind Speed" }, { 0x2a71, "True Wind Direction" }, { 0x2a72, "Apparent Wind Speed" }, { 0x2a73, "Apparent Wind Direction" }, { 0x2a74, "Gust Factor" }, { 0x2a75, "Pollen Concentration" }, { 0x2a76, "UV Index" }, { 0x2a77, "Irradiance" }, { 0x2a78, "Rainfall" }, { 0x2a79, "Wind Chill" }, { 0x2a7a, "Heat Index" }, { 0x2a7b, "Dew Point" }, { 0x2a7c, "Trend" }, { 0x2a7d, "Descriptor Value Changed" }, { 0x2a7e, "Aerobic Heart Rate Lower Limit" }, { 0x2a7f, "Aerobic Threshold" }, { 0x2a80, "Age" }, { 0x2a81, "Anaerobic Heart Rate Lower Limit" }, { 0x2a82, "Anaerobic Heart Rate Upper Limit" }, { 0x2a83, "Anaerobic Threshold" }, { 0x2a84, "Aerobic Heart Rate Upper Limit" }, { 0x2a85, "Date of Birth" }, { 0x2a86, "Date of Threshold Assessment" }, { 0x2a87, "Email Address" }, { 0x2a88, "Fat Burn Heart Rate Lower Limit" }, { 0x2a89, "Fat Burn Heart Rate Upper Limit" }, { 0x2a8a, "First Name" }, { 0x2a8b, "Five Zone Heart Rate Limits" }, { 0x2a8c, "Gender" }, { 0x2a8d, "Heart Rate Max" }, { 0x2a8e, "Height" }, { 0x2a8f, "Hip Circumference" }, { 0x2a90, "Last Name" }, { 0x2a91, "Maximum Recommended Heart Rate" }, { 0x2a92, "Resting Heart Rate" }, { 0x2a93, "Sport Type for Aerobic/Anaerobic Thresholds" }, { 0x2a94, "Three Zone Heart Rate Limits" }, { 0x2a95, "Two Zone Heart Rate Limit" }, { 0x2a96, "VO2 Max" }, { 0x2a97, "Waist Circumference" }, { 0x2a98, "Weight" }, { 0x2a99, "Database Change Increment" }, { 0x2a9a, "User Index" }, { 0x2a9b, "Body Composition Feature" }, { 0x2a9c, "Body Composition Measurement" }, { 0x2a9d, "Weight Measurement" }, { 0x2a9e, "Weight Scale Feature" }, { 0x2a9f, "User Control Point" }, { 0x2aa0, "Magnetic Flux Density - 2D" }, { 0x2aa1, "Magnetic Flux Density - 3D" }, { 0x2aa2, "Language" }, { 0x2aa3, "Barometric Pressure Trend" }, { 0x2aa4, "Bond Management Control Point" }, { 0x2aa5, "Bond Management Feature" }, { 0x2aa6, "Central Address Resolution" }, { 0x2aa7, "CGM Measurement" }, { 0x2aa8, "CGM Feature" }, { 0x2aa9, "CGM Status" }, { 0x2aaa, "CGM Session Start Time" }, { 0x2aab, "CGM Session Run Time" }, { 0x2aac, "CGM Specific Ops Control Point" }, { 0x2aad, "Indoor Positioning Configuration" }, { 0x2aae, "Latitude" }, { 0x2aaf, "Longitude" }, { 0x2ab0, "Local North Coordinate" }, { 0x2ab1, "Local East Coordinate" }, { 0x2ab2, "Floor Number" }, { 0x2ab3, "Altitude" }, { 0x2ab4, "Uncertainty" }, { 0x2ab5, "Location Name" }, { 0x2ab6, "URI" }, { 0x2ab7, "HTTP Headers" }, { 0x2ab8, "HTTP Status Code" }, { 0x2ab9, "HTTP Entity Body" }, { 0x2aba, "HTTP Control Point" }, { 0x2abb, "HTTPS Security" }, { 0x2abc, "TDS Control Point" }, { 0x2abd, "OTS Feature" }, { 0x2abe, "Object Name" }, { 0x2abf, "Object Type" }, { 0x2ac0, "Object Size" }, { 0x2ac1, "Object First-Created" }, { 0x2ac2, "Object Last-Modified" }, { 0x2ac3, "Object ID" }, { 0x2ac4, "Object Properties" }, { 0x2ac5, "Object Action Control Point" }, { 0x2ac6, "Object List Control Point" }, { 0x2ac7, "Object List Filter" }, { 0x2ac8, "Object Changed" }, { 0x2ac9, "Resolvable Private Address Only" }, /* 0x2aca and 0x2acb undefined */ { 0x2acc, "Fitness Machine Feature" }, { 0x2acd, "Treadmill Data" }, { 0x2ace, "Cross Trainer Data" }, { 0x2acf, "Step Climber Data" }, { 0x2ad0, "Stair Climber Data" }, { 0x2ad1, "Rower Data" }, { 0x2ad2, "Indoor Bike Data" }, { 0x2ad3, "Training Status" }, { 0x2ad4, "Supported Speed Range" }, { 0x2ad5, "Supported Inclination Range" }, { 0x2ad6, "Supported Resistance Level Range" }, { 0x2ad7, "Supported Heart Rate Range" }, { 0x2ad8, "Supported Power Range" }, { 0x2ad9, "Fitness Machine Control Point" }, { 0x2ada, "Fitness Machine Status" }, { 0x2adb, "Mesh Provisioning Data In" }, { 0x2adc, "Mesh Provisioning Data Out" }, { 0x2add, "Mesh Proxy Data In" }, { 0x2ade, "Mesh Proxy Data Out" }, { 0x2b29, "Client Supported Features" }, { 0x2b2A, "Database Hash" }, { 0x2b3a, "Server Supported Features" }, { 0x2b51, "Telephony and Media Audio Profile Role" }, { 0x2b77, "Audio Input State" }, { 0x2b78, "Gain Settings Attribute" }, { 0x2b79, "Audio Input Type" }, { 0x2b7a, "Audio Input Status" }, { 0x2b7b, "Audio Input Control Point" }, { 0x2b7c, "Audio Input Description" }, { 0x2b7d, "Volume State" }, { 0x2b7e, "Volume Control Point" }, { 0x2b7f, "Volume Flags" }, { 0x2b80, "Offset State" }, { 0x2b81, "Audio Location" }, { 0x2b82, "Volume Offset Control Point" }, { 0x2b83, "Audio Output Description" }, { 0x2b84, "Set Identity Resolving Key" }, { 0x2b85, "Coordinated Set Size" }, { 0x2b86, "Set Member Lock" }, { 0x2b87, "Set Member Rank" }, { 0x2b93, "Media Player Name" }, { 0x2b94, "Media Player Icon Object ID" }, { 0x2b95, "Media Player Icon URL" }, { 0x2b96, "Track Changed" }, { 0x2b97, "Track Title" }, { 0x2b98, "Track Duration" }, { 0x2b99, "Track Position" }, { 0x2b9a, "Playback Speed" }, { 0x2b9b, "Seeking Speed" }, { 0x2b9c, "Current Track Segments Object ID" }, { 0x2b9d, "Current Track Object ID" }, { 0x2b9e, "Next Track Object ID" }, { 0x2b9f, "Parent Group Object ID" }, { 0x2ba0, "Current Group Object ID" }, { 0x2ba1, "Playing Order" }, { 0x2ba2, "Playing Orders Supported" }, { 0x2ba3, "Media State" }, { 0x2ba4, "Media Control Point" }, { 0x2ba5, "Media Control Point Opcodes Supported" }, { 0x2ba6, "Search Results Object ID" }, { 0x2ba7, "Search Control Point" }, { 0x2ba9, "Media Player Icon Object Type" }, { 0x2baa, "Track Segments Object Type" }, { 0x2bab, "Track Object Type" }, { 0x2bac, "Group Object Type" }, { 0x2bb3, "Bearer Provider Name" }, { 0x2bb4, "Bearer UCI" }, { 0x2bb5, "Bearer Technology" }, { 0x2bb6, "Bearer URI Schemes Supported List" }, { 0x2bb7, "Bearer Signal Strength" }, { 0x2bb8, "Bearer Signal Strength Reporting Interval" }, { 0x2bb9, "Bearer List Current Calls" }, { 0x2bba, "Content Control ID" }, { 0x2bbb, "Status Flags" }, { 0x2bbc, "Incoming Call Target Bearer URI" }, { 0x2bbd, "Call State" }, { 0x2bbe, "Call Control Point" }, { 0x2bbf, "Call Control Point Optional Opcodes" }, { 0x2bc0, "Termination Reason" }, { 0x2bc1, "Incoming Call" }, { 0x2bc2, "Call Friendly Name" }, { 0x2bc3, "Mute" }, { 0x2bc4, "Sink ASE" }, { 0x2bc5, "Source ASE" }, { 0x2bc6, "ASE Control Point" }, { 0x2bc7, "Broadcast Audio Scan Control Point" }, { 0x2bc8, "Broadcast Receive State" }, { 0x2bc9, "Sink PAC" }, { 0x2bca, "Sink Audio Locations" }, { 0x2bcb, "Source PAC" }, { 0x2bcc, "Source Audio Locations" }, { 0x2bcd, "Available Audio Contexts" }, { 0x2bce, "Supported Audio Contexts" }, { 0x2bda, "Hearing Aid Features" }, { 0x2bdb, "Hearing Aid Preset Control Point" }, { 0x2bdc, "Active Preset Index" }, { 0x2c00, "GMAP Role" }, { 0x2c01, "UGG Features" }, { 0x2c02, "UGT Features" }, { 0x2c03, "BGS Features" }, { 0x2c03, "BGR Features" }, { 0x2c14, "RAS Features" }, { 0x2c15, "RAS Real-time Ranging Data" }, { 0x2c16, "RAS On-demand Ranging Data" }, { 0x2c17, "RAS Control Point" }, { 0x2c18, "RAS Ranging Data Ready" }, { 0x2c19, "RAS Ranging Data Overwritten" }, { 0x2c39, "HID SCI Mode" }, { 0x2c3a, "HID SCI Information" }, /* vendor defined */ { 0xfeff, "GN Netcom" }, { 0xfefe, "GN ReSound A/S" }, { 0xfefd, "Gimbal, Inc." }, { 0xfefc, "Gimbal, Inc." }, { 0xfefb, "Telit Wireless Solutions (Formerly Stollmann E+V GmbH)" }, { 0xfefa, "PayPal, Inc." }, { 0xfef9, "PayPal, Inc." }, { 0xfef8, "Aplix Corporation" }, { 0xfef7, "Aplix Corporation" }, { 0xfef6, "Wicentric, Inc." }, { 0xfef5, "Dialog Semiconductor GmbH" }, { 0xfef4, "Google" }, { 0xfef3, "Google" }, { 0xfef2, "CSR" }, { 0xfef1, "CSR" }, { 0xfef0, "Intel" }, { 0xfeef, "Polar Electro Oy " }, { 0xfeee, "Polar Electro Oy " }, { 0xfeed, "Tile, Inc." }, { 0xfeec, "Tile, Inc." }, { 0xfeeb, "Swirl Networks, Inc." }, { 0xfeea, "Swirl Networks, Inc." }, { 0xfee9, "Quintic Corp." }, { 0xfee8, "Quintic Corp." }, { 0xfee7, "Tencent Holdings Limited." }, { 0xfee6, "Silvair, Inc." }, { 0xfee5, "Nordic Semiconductor ASA" }, { 0xfee4, "Nordic Semiconductor ASA" }, { 0xfee3, "Anki, Inc." }, { 0xfee2, "Anki, Inc." }, { 0xfee1, "Anhui Huami Information Technology Co., Ltd. " }, { 0xfee0, "Anhui Huami Information Technology Co., Ltd. " }, { 0xfedf, "Design SHIFT" }, { 0xfede, "Coin, Inc." }, { 0xfedd, "Jawbone" }, { 0xfedc, "Jawbone" }, { 0xfedb, "Perka, Inc." }, { 0xfeda, "ISSC Technologies Corp. " }, { 0xfed9, "Pebble Technology Corporation" }, { 0xfed8, "Google" }, { 0xfed7, "Broadcom" }, { 0xfed6, "Broadcom" }, { 0xfed5, "Plantronics Inc." }, { 0xfed4, "Apple, Inc." }, { 0xfed3, "Apple, Inc." }, { 0xfed2, "Apple, Inc." }, { 0xfed1, "Apple, Inc." }, { 0xfed0, "Apple, Inc." }, { 0xfecf, "Apple, Inc." }, { 0xfece, "Apple, Inc." }, { 0xfecd, "Apple, Inc." }, { 0xfecc, "Apple, Inc." }, { 0xfecb, "Apple, Inc." }, { 0xfeca, "Apple, Inc." }, { 0xfec9, "Apple, Inc." }, { 0xfec8, "Apple, Inc." }, { 0xfec7, "Apple, Inc." }, { 0xfec6, "Kocomojo, LLC" }, { 0xfec5, "Realtek Semiconductor Corp." }, { 0xfec4, "PLUS Location Systems" }, { 0xfec3, "360fly, Inc." }, { 0xfec2, "Blue Spark Technologies, Inc." }, { 0xfec1, "KDDI Corporation" }, { 0xfec0, "KDDI Corporation" }, { 0xfebf, "Nod, Inc." }, { 0xfebe, "Bose Corporation" }, { 0xfebd, "Clover Network, Inc" }, { 0xfebc, "Dexcom Inc" }, { 0xfebb, "adafruit industries" }, { 0xfeba, "Tencent Holdings Limited" }, { 0xfeb9, "LG Electronics" }, { 0xfeb8, "Facebook, Inc." }, { 0xfeb7, "Facebook, Inc." }, { 0xfeb6, "Vencer Co., Ltd" }, { 0xfeb5, "WiSilica Inc." }, { 0xfeb4, "WiSilica Inc." }, { 0xfeb3, "Taobao" }, { 0xfeb2, "Microsoft Corporation" }, { 0xfeb1, "Electronics Tomorrow Limited" }, { 0xfeb0, "Nest Labs Inc" }, { 0xfeaf, "Nest Labs Inc" }, { 0xfeae, "Nokia" }, { 0xfead, "Nokia" }, { 0xfeac, "Nokia" }, { 0xfeab, "Nokia" }, { 0xfeaa, "Google" }, { 0xfea9, "Savant Systems LLC" }, { 0xfea8, "Savant Systems LLC" }, { 0xfea7, "UTC Fire and Security" }, { 0xfea6, "GoPro, Inc." }, { 0xfea5, "GoPro, Inc." }, { 0xfea4, "Paxton Access Ltd" }, { 0xfea3, "ITT Industries" }, { 0xfea2, "Intrepid Control Systems, Inc." }, { 0xfea1, "Intrepid Control Systems, Inc." }, { 0xfea0, "Google" }, { 0xfe9f, "Google" }, { 0xfe9e, "Dialog Semiconductor B.V." }, { 0xfe9d, "Mobiquity Networks Inc" }, { 0xfe9c, "GSI Laboratories, Inc." }, { 0xfe9b, "Samsara Networks, Inc" }, { 0xfe9a, "Estimote" }, { 0xfe99, "Currant Inc" }, { 0xfe98, "Currant Inc" }, { 0xfe97, "Tesla Motors Inc." }, { 0xfe96, "Tesla Motors Inc." }, { 0xfe95, "Xiaomi Inc." }, { 0xfe94, "OttoQ In" }, { 0xfe93, "OttoQ In" }, { 0xfe92, "Jarden Safety & Security" }, { 0xfe91, "Shanghai Imilab Technology Co.,Ltd" }, { 0xfe90, "JUMA" }, { 0xfe8f, "CSR" }, { 0xfe8e, "ARM Ltd" }, { 0xfe8d, "Interaxon Inc." }, { 0xfe8c, "TRON Forum" }, { 0xfe8b, "Apple, Inc." }, { 0xfe8a, "Apple, Inc." }, { 0xfe89, "B&O Play A/S" }, { 0xfe88, "SALTO SYSTEMS S.L." }, { 0xfe87, "Qingdao Yeelink Information Technology Co., Ltd. ( 青岛亿联客信息技术有限公司 )" }, { 0xfe86, "HUAWEI Technologies Co., Ltd. ( 华为技术有限公司 )" }, { 0xfe85, "RF Digital Corp" }, { 0xfe84, "RF Digital Corp" }, { 0xfe83, "Blue Bite" }, { 0xfe82, "Medtronic Inc." }, { 0xfe81, "Medtronic Inc." }, { 0xfe80, "Doppler Lab" }, { 0xfe7f, "Doppler Lab" }, { 0xfe7e, "Awear Solutions Ltd" }, { 0xfe7d, "Aterica Health Inc." }, { 0xfe7c, "Telit Wireless Solutions (Formerly Stollmann E+V GmbH)" }, { 0xfe7b, "Orion Labs, Inc." }, { 0xfe7a, "Bragi GmbH" }, { 0xfe79, "Zebra Technologies" }, { 0xfe78, "Hewlett-Packard Company" }, { 0xfe77, "Hewlett-Packard Company" }, { 0xfe76, "TangoMe" }, { 0xfe75, "TangoMe" }, { 0xfe74, "unwire" }, { 0xfe73, "Abbott (formerly St. Jude Medical, Inc.)" }, { 0xfe72, "Abbott (formerly St. Jude Medical, Inc.)" }, { 0xfe71, "Plume Design Inc" }, { 0xfe70, "Beijing Jingdong Century Trading Co., Ltd." }, { 0xfe6f, "LINE Corporation" }, { 0xfe6e, "The University of Tokyo " }, { 0xfe6d, "The University of Tokyo " }, { 0xfe6c, "TASER International, Inc." }, { 0xfe6b, "TASER International, Inc." }, { 0xfe6a, "Kontakt Micro-Location Sp. z o.o." }, { 0xfe69, "Capsule Technologies Inc." }, { 0xfe68, "Capsule Technologies Inc." }, { 0xfe67, "Lab Sensor Solutions" }, { 0xfe66, "Intel Corporation " }, { 0xfe65, "CHIPOLO d.o.o. " }, { 0xfe64, "Siemens AG" }, { 0xfe63, "Connected Yard, Inc. " }, { 0xfe62, "Indagem Tech LLC " }, { 0xfe61, "Logitech International SA " }, { 0xfe60, "Lierda Science & Technology Group Co., Ltd." }, { 0xfe5f, "Eyefi, Inc." }, { 0xfe5e, "Plastc Corporation " }, { 0xfe5d, "Grundfos A/S " }, { 0xfe5c, "million hunters GmbH " }, { 0xfe5b, "GT-tronics HK Ltd" }, { 0xfe5a, "Cronologics Corporation" }, { 0xfe59, "Nordic Semiconductor ASA " }, { 0xfe58, "Nordic Semiconductor ASA " }, { 0xfe57, "Dotted Labs " }, { 0xfe56, "Google Inc. " }, { 0xfe55, "Google Inc. " }, { 0xfe54, "Motiv, Inc. " }, { 0xfe53, "3M" }, { 0xfe52, "SetPoint Medical " }, { 0xfe51, "SRAM " }, { 0xfe50, "Google Inc." }, { 0xfe4f, "Molekule, Inc." }, { 0xfe4e, "NTT docomo " }, { 0xfe4d, "Casambi Technologies Oy" }, { 0xfe4c, "Volkswagen AG " }, { 0xfe4b, "Signify Netherlands B.V. (formerly Philips Lighting B.V.)" }, { 0xfe4a, "OMRON HEALTHCARE Co., Ltd." }, { 0xfe49, "SenionLab AB" }, { 0xfe48, "General Motors " }, { 0xfe47, "General Motors " }, { 0xfe46, "B&O Play A/S " }, { 0xfe45, "Snapchat Inc" }, { 0xfe44, "SK Telecom " }, { 0xfe43, "Andreas Stihl AG & Co. KG" }, { 0xfe42, "Nets A/S " }, { 0xfe41, "Inugo Systems Limited" }, { 0xfe40, "Inugo Systems Limited" }, { 0xfe3f, "Friday Labs Limited" }, { 0xfe3e, "BD Medical" }, { 0xfe3d, "BD Medical" }, { 0xfe3c, "alibaba" }, { 0xfe3b, "Dobly Laboratories" }, { 0xfe3a, "TTS Tooltechnic Systems AG & Co. KG" }, { 0xfe39, "TTS Tooltechnic Systems AG & Co. KG" }, { 0xfe38, "Spaceek LTD" }, { 0xfe37, "Spaceek LTD" }, { 0xfe36, "HUAWEI Technologies Co., Ltd" }, { 0xfe35, "HUAWEI Technologies Co., Ltd" }, { 0xfe34, "SmallLoop LLC" }, { 0xfe33, "CHIPOLO d.o.o." }, { 0xfe32, "Pro-Mark, Inc." }, { 0xfe31, "Volkswagen AG" }, { 0xfe30, "Volkswagen AG" }, { 0xfe2f, "CRESCO Wireless, Inc" }, { 0xfe2e, "ERi,Inc." }, { 0xfe2d, "SMART INNOVATION Co.,Ltd" }, { 0xfe2c, "Google" }, { 0xfe2b, "ITT Industries" }, { 0xfe2a, "DaisyWorks, Inc." }, { 0xfe29, "Gibson Innovations" }, { 0xfe28, "Ayla Networks" }, { 0xfe27, "Google" }, { 0xfe26, "Google" }, { 0xfe25, "Apple, Inc. " }, { 0xfe24, "August Home Inc" }, { 0xfe23, "Zoll Medical Corporation" }, { 0xfe22, "Zoll Medical Corporation" }, { 0xfe21, "Bose Corporation" }, { 0xfe20, "Emerson" }, { 0xfe1f, "Garmin International, Inc." }, { 0xfe1e, "Smart Innovations Co., Ltd" }, { 0xfe1d, "Illuminati Instrument Corporation" }, { 0xfe1c, "NetMedia, Inc." }, { 0xfe1b, "Tyto Life LLC" }, { 0xfe1a, "Tyto Life LLC" }, { 0xfe19, "Google, Inc" }, { 0xfe18, "Runtime, Inc." }, { 0xfe17, "Telit Wireless Solutions GmbH" }, { 0xfe16, "Footmarks, Inc." }, { 0xfe15, "Amazon.com Services, Inc.." }, { 0xfe14, "Flextronics International USA Inc." }, { 0xfe13, "Apple Inc." }, { 0xfe12, "M-Way Solutions GmbH" }, { 0xfe11, "GMC-I Messtechnik GmbH" }, { 0xfe10, "Lapis Semiconductor Co., Ltd." }, { 0xfe0f, "Signify Netherlands B.V. (formerly Philips Lighting B.V.)" }, { 0xfe0e, "Setec Pty Ltd" }, { 0xfe0d, "Procter & Gamble" }, { 0xfe0c, "Procter & Gamble" }, { 0xfe0b, "ruwido austria gmbh" }, { 0xfe0a, "ruwido austria gmbh" }, { 0xfe09, "Pillsy, Inc." }, { 0xfe08, "Microsoft" }, { 0xfe07, "Sonos, Inc." }, { 0xfe06, "Qualcomm Technologies, Inc." }, { 0xfe05, "CORE Transport Technologies NZ Limited " }, { 0xfe04, "OpenPath Security Inc" }, { 0xfe03, "Amazon.com Services, Inc." }, { 0xfe02, "Robert Bosch GmbH" }, { 0xfe01, "Duracell U.S. Operations Inc." }, { 0xfe00, "Amazon.com Services, Inc." }, { 0xfdff, "OSRAM GmbH" }, { 0xfdfe, "ADHERIUM(NZ) LIMITED" }, { 0xfdfd, "RecursiveSoft Inc." }, { 0xfdfc, "Optrel AG" }, { 0xfdfb, "Tandem Diabetes Care" }, { 0xfdfa, "Tandem Diabetes Care" }, { 0xfdf9, "INIA" }, { 0xfdf8, "Onvocal" }, { 0xfdf7, "HP Inc." }, { 0xfdf6, "AIAIAI ApS" }, { 0xfdf5, "Milwaukee Electric Tools" }, { 0xfdf4, "O. E. M. Controls, Inc." }, { 0xfdf3, "Amersports" }, { 0xfdf2, "AMICCOM Electronics Corporation" }, { 0xfdf1, "LAMPLIGHT Co.,Ltd" }, { 0xfdf0, "Google Inc." }, { 0xfdef, "ART AND PROGRAM, INC." }, { 0xfdee, "Huawei Technologies Co., Ltd." }, { 0xfded, "Pole Star" }, { 0xfdec, "Mannkind Corporation" }, { 0xfdeb, "Syntronix Corporation" }, { 0xfdea, "SeeScan, Inc" }, { 0xfde9, "Spacesaver Corporation" }, { 0xfde8, "Robert Bosch GmbH" }, { 0xfde7, "SECOM Co., LTD" }, { 0xfde6, "Intelletto Technologies Inc" }, { 0xfde5, "SMK Corporation " }, { 0xfde4, "JUUL Labs, Inc." }, { 0xfde3, "Abbott Diabetes Care" }, { 0xfde2, "Google Inc." }, { 0xfde1, "Fortin Electronic Systems " }, { 0xfde0, "John Deere" }, { 0xfddf, "Harman International" }, { 0xfdde, "Noodle Technology Inc. " }, { 0xfddd, "Arch Systems Inc" }, { 0xfddc, "4iiii Innovations Inc." }, { 0xfddb, "Samsung Electronics Co., Ltd. " }, { 0xfdda, "MHCS" }, { 0xfdd9, "Jiangsu Teranovo Tech Co., Ltd." }, { 0xfdd8, "Jiangsu Teranovo Tech Co., Ltd." }, { 0xfdd7, "Emerson" }, { 0xfdd6, "Ministry of Supply " }, { 0xfdd5, "Brompton Bicycle Ltd" }, { 0xfdd4, "LX Solutions Pty Limited" }, { 0xfdd3, "FUBA Automotive Electronics GmbH" }, { 0xfdd2, "Bose Corporation" }, { 0xfdd1, "Huawei Technologies Co., Ltd " }, { 0xfdd0, "Huawei Technologies Co., Ltd " }, { 0xfdcf, "Nalu Medical, Inc" }, { 0xfdce, "SENNHEISER electronic GmbH & Co. KG" }, { 0xfdcd, "Qingping Technology (Beijing) Co., Ltd." }, { 0xfdcc, "Shoof Technologies" }, { 0xfdcb, "Meggitt SA" }, { 0xfdca, "Fortin Electronic Systems " }, { 0xfdc9, "Busch-Jaeger Elektro GmbH" }, { 0xfdc8, "Hach – Danaher" }, { 0xfdc7, "Eli Lilly and Company" }, { 0xfdc6, "Eli Lilly and Company" }, { 0xfdc5, "Automatic Labs" }, { 0xfdc4, "Simavita (Aust) Pty Ltd" }, { 0xfdc3, "Baidu Online Network Technology (Beijing) Co., Ltd" }, { 0xfdc2, "Baidu Online Network Technology (Beijing) Co., Ltd" }, { 0xfdc1, "Hunter Douglas" }, { 0xfdc0, "Hunter Douglas" }, { 0xfdbf, "California Things Inc. " }, { 0xfdbe, "California Things Inc. " }, { 0xfdbd, "Clover Network, Inc." }, { 0xfdbc, "Emerson" }, { 0xfdbb, "Profoto" }, { 0xfdba, "Comcast Cable Corporation" }, { 0xfdb9, "Comcast Cable Corporation" }, { 0xfdb8, "LivaNova USA Inc." }, { 0xfdb7, "LivaNova USA Inc." }, { 0xfdb6, "GWA Hygiene GmbH" }, { 0xfdb5, "ECSG" }, { 0xfdb4, "HP Inc" }, { 0xfdb3, "Audiodo AB" }, { 0xfdb2, "Portable Multimedia Ltd " }, { 0xfdb1, "Proxy Technologies, Inc." }, { 0xfdb0, "Proxy Technologies, Inc." }, { 0xfdaf, "Wiliot LTD" }, { 0xfdae, "Houwa System Design, k.k." }, { 0xfdad, "Houwa System Design, k.k." }, { 0xfdac, "Tentacle Sync GmbH" }, { 0xfdab, "Xiaomi Inc." }, { 0xfdaa, "Xiaomi Inc." }, { 0xfda9, "Rhombus Systems, Inc." }, { 0xfda8, "PSA Peugeot Citroën" }, { 0xfda7, "WWZN Information Technology Company Limited" }, { 0xfda6, "WWZN Information Technology Company Limited" }, { 0xfda5, "Neurostim OAB, Inc." }, { 0xfda4, "Inseego Corp." }, { 0xfda3, "Inseego Corp." }, { 0xfda2, "Groove X, Inc" }, { 0xfda1, "Groove X, Inc" }, { 0xfda0, "Secugen Corporation" }, { 0xfd9f, "VitalTech Affiliates LLC" }, { 0xfd9e, "The Coca-Cola Company" }, { 0xfd9d, "Gastec Corporation" }, { 0xfd9c, "Huawei Technologies Co., Ltd." }, { 0xfd9b, "Huawei Technologies Co., Ltd." }, { 0xfd9a, "Huawei Technologies Co., Ltd." }, { 0xfd99, "ABB Oy" }, { 0xfd98, "Disney Worldwide Services, Inc." }, { 0xfd97, "June Life, Inc." }, { 0xfd96, "Google LLC" }, { 0xfd95, "Rigado" }, { 0xfd94, "Hewlett Packard Enterprise" }, { 0xfd93, "Bayerische Motoren Werke AG" }, { 0xfd92, "Qualcomm Technologies International, Ltd. (QTIL)" }, { 0xfd91, "Groove X, Inc." }, { 0xfd90, "Guangzhou SuperSound Information Technology Co.,Ltd" }, { 0xfd8f, "Matrix ComSec Pvt. Ltd." }, { 0xfd8e, "Motorola Solutions" }, { 0xfd8d, "quip NYC Inc." }, { 0xfd8c, "Google LLC" }, { 0xfd8b, "Jigowatts Inc." }, { 0xfd8a, "Signify Netherlands B.V." }, { 0xfd89, "Urbanminded LTD" }, { 0xfd88, "Urbanminded LTD" }, { 0xfd87, "Google LLC" }, { 0xfd86, "Abbott" }, { 0xfd85, "Husqvarna AB" }, { 0xfd84, "Tile, Inc." }, { 0xfd83, "iNFORM Technology GmbH" }, { 0xfd82, "Sony Corporation" }, { 0xfd81, "CANDY HOUSE, Inc." }, { 0xfd80, "Phindex Technologies, Inc" }, { 0xfd7f, "Husqvarna AB" }, { 0xfd7e, "Samsung Electronics Co., Ltd." }, { 0xfd7d, "Center for Advanced Research Wernher Von Braun" }, { 0xfd7c, "Toshiba Information Systems(Japan) Corporation" }, { 0xfd7b, "WYZE LABS, INC." }, { 0xfd7a, "Withings" }, { 0xfd79, "Withings" }, { 0xfd78, "Withings" }, { 0xfd77, "Withings" }, { 0xfd76, "Insulet Corporation" }, { 0xfd75, "Insulet Corporation" }, { 0xfd74, "BRControls Products BV" }, { 0xfd73, "BRControls Products BV" }, { 0xfd72, "Logitech International SA" }, { 0xfd71, "GN Hearing A/S" }, { 0xfd70, "GuangDong Oppo Mobile Telecommunications Corp., Ltd." }, { 0xfd6f, "Apple, Inc." }, { 0xfd6e, "Polidea sp. z o.o." }, { 0xfd6d, "Sigma Elektro GmbH" }, { 0xfd6c, "Samsung Electronics Co., Ltd." }, { 0xfd6b, " rapitag GmbH" }, { 0xfd6a, "Emerson" }, { 0xfd69, "Samsung Electronics Co., Ltd." }, { 0xfd68, "Ubique Innovation AG" }, { 0xfd67, "Montblanc Simplo GmbH" }, { 0xfd66, "Zebra Technologies Corporation" }, { 0xfd65, "Razer Inc." }, { 0xfd64, "INRIA" }, { 0xfd63, "Fitbit, Inc." }, { 0xfd62, "Fitbit, Inc." }, { 0xfd61, "Arendi AG" }, { 0xfd60, "Sercomm Corporation" }, { 0xfd5f, "Oculus VR, LLC" }, /* SDO defined */ { 0xfccc, "Wi-Fi Easy Connect Specification" }, { 0xffef, "Wi-Fi Direct Specification" }, { 0xfff0, "Public Key Open Credential (PKOC)" }, { 0xfff1, "ICCE Digital Key" }, { 0xfff2, "Aliro" }, { 0xfff3, "FiRa Consortium" }, { 0xfff4, "FiRa Consortium" }, { 0xfff5, "Car Connectivity Consortium, LLC" }, { 0xfff6, "Matter Profile ID" }, { 0xfff7, "Zigbee Direct" }, { 0xfff8, "Mopria Alliance BLE" }, { 0xfff9, "FIDO2 Secure Client-To-Authenticator Transport" }, { 0xfffa, "ASTM Remote ID" }, { 0xfffb, "Direct Thread Commissioning" }, { 0xfffc, "Wireless Power Transfer (WPT)" }, { 0xfffd, "Universal Second Factor Authenticator" }, { 0xfffe, "Wireless Power Transfer" }, { } }; static const struct { const char *uuid; const char *str; } uuid128_table[] = { { "a3c87500-8ed3-4bdf-8a39-a01bebede295", "Eddystone Configuration Service" }, { "a3c87501-8ed3-4bdf-8a39-a01bebede295", "Capabilities" }, { "a3c87502-8ed3-4bdf-8a39-a01bebede295", "Active Slot" }, { "a3c87503-8ed3-4bdf-8a39-a01bebede295", "Advertising Interval" }, { "a3c87504-8ed3-4bdf-8a39-a01bebede295", "Radio Tx Power" }, { "a3c87505-8ed3-4bdf-8a39-a01bebede295", "(Advanced) Advertised Tx Power" }, { "a3c87506-8ed3-4bdf-8a39-a01bebede295", "Lock State" }, { "a3c87507-8ed3-4bdf-8a39-a01bebede295", "Unlock" }, { "a3c87508-8ed3-4bdf-8a39-a01bebede295", "Public ECDH Key" }, { "a3c87509-8ed3-4bdf-8a39-a01bebede295", "EID Identity Key" }, { "a3c8750a-8ed3-4bdf-8a39-a01bebede295", "ADV Slot Data" }, { "a3c8750b-8ed3-4bdf-8a39-a01bebede295", "(Advanced) Factory reset" }, { "a3c8750c-8ed3-4bdf-8a39-a01bebede295", "(Advanced) Remain Connectable" }, /* BBC micro:bit Bluetooth Profiles */ { "e95d0753-251d-470a-a062-fa1922dfa9a8", "MicroBit Accelerometer Service" }, { "e95dca4b-251d-470a-a062-fa1922dfa9a8", "MicroBit Accelerometer Data" }, { "e95dfb24-251d-470a-a062-fa1922dfa9a8", "MicroBit Accelerometer Period" }, { "e95df2d8-251d-470a-a062-fa1922dfa9a8", "MicroBit Magnetometer Service" }, { "e95dfb11-251d-470a-a062-fa1922dfa9a8", "MicroBit Magnetometer Data" }, { "e95d386c-251d-470a-a062-fa1922dfa9a8", "MicroBit Magnetometer Period" }, { "e95d9715-251d-470a-a062-fa1922dfa9a8", "MicroBit Magnetometer Bearing" }, { "e95d9882-251d-470a-a062-fa1922dfa9a8", "MicroBit Button Service" }, { "e95dda90-251d-470a-a062-fa1922dfa9a8", "MicroBit Button A State" }, { "e95dda91-251d-470a-a062-fa1922dfa9a8", "MicroBit Button B State" }, { "e95d127b-251d-470a-a062-fa1922dfa9a8", "MicroBit IO PIN Service" }, { "e95d8d00-251d-470a-a062-fa1922dfa9a8", "MicroBit PIN Data" }, { "e95d5899-251d-470a-a062-fa1922dfa9a8", "MicroBit PIN AD Configuration" }, { "e95dd822-251d-470a-a062-fa1922dfa9a8", "MicroBit PWM Control" }, { "e95dd91d-251d-470a-a062-fa1922dfa9a8", "MicroBit LED Service" }, { "e95d7b77-251d-470a-a062-fa1922dfa9a8", "MicroBit LED Matrix state" }, { "e95d93ee-251d-470a-a062-fa1922dfa9a8", "MicroBit LED Text" }, { "e95d0d2d-251d-470a-a062-fa1922dfa9a8", "MicroBit Scrolling Delay" }, { "e95d93af-251d-470a-a062-fa1922dfa9a8", "MicroBit Event Service" }, { "e95db84c-251d-470a-a062-fa1922dfa9a8", "MicroBit Requirements" }, { "e95d9775-251d-470a-a062-fa1922dfa9a8", "MicroBit Event Data" }, { "e95d23c4-251d-470a-a062-fa1922dfa9a8", "MicroBit Client Requirements" }, { "e95d5404-251d-470a-a062-fa1922dfa9a8", "MicroBit Client Events" }, { "e95d93b0-251d-470a-a062-fa1922dfa9a8", "MicroBit DFU Control Service" }, { "e95d93b1-251d-470a-a062-fa1922dfa9a8", "MicroBit DFU Control" }, { "e95d6100-251d-470a-a062-fa1922dfa9a8", "MicroBit Temperature Service" }, { "e95d1b25-251d-470a-a062-fa1922dfa9a8", "MicroBit Temperature Period" }, /* Nordic UART Port Emulation */ { "6e400001-b5a3-f393-e0a9-e50e24dcca9e", "Nordic UART Service" }, { "6e400002-b5a3-f393-e0a9-e50e24dcca9e", "Nordic UART TX" }, { "6e400003-b5a3-f393-e0a9-e50e24dcca9e", "Nordic UART RX" }, /* BlueZ Experimental Features */ { "d4992530-b9ec-469f-ab01-6c481c47da1c", "BlueZ Experimental Debug" }, { "671b10b5-42c0-4696-9227-eb28d1b049d6", "BlueZ Experimental Simultaneous Central and Peripheral" }, { "15c0a148-c273-11ea-b3de-0242ac130004", "BlueZ Experimental LL privacy" }, { "330859bc-7506-492d-9370-9a6f0614037f", "BlueZ Experimental Bluetooth Quality Report" }, { "a6695ace-ee7f-4fb9-881a-5fac66c629af", "BlueZ Offload Codecs"}, { "6fbaf188-05e0-496a-9885-d6ddfdb4e03e", "BlueZ Experimental ISO Socket"}, { } }; const char *bt_uuid16_to_str(uint16_t uuid) { int i; for (i = 0; uuid16_table[i].str; i++) { if (uuid16_table[i].uuid == uuid) return uuid16_table[i].str; } return "Unknown"; } const char *bt_uuid32_to_str(uint32_t uuid) { if ((uuid & 0xffff0000) == 0x0000) return bt_uuid16_to_str(uuid & 0x0000ffff); return "Unknown"; } const char *bt_uuid128_to_str(const uint8_t uuid[16]) { char uuidstr[37]; sprintf(uuidstr, "%8.8x-%4.4x-%4.4x-%4.4x-%8.8x%4.4x", get_le32(&uuid[12]), get_le16(&uuid[10]), get_le16(&uuid[8]), get_le16(&uuid[6]), get_le32(&uuid[2]), get_le16(&uuid[0])); return bt_uuidstr_to_str(uuidstr); } const char *bt_uuidstr_to_str(const char *uuid) { uint32_t val; size_t len; int i; if (!uuid) return NULL; len = strlen(uuid); if (len < 36) { char *endptr = NULL; val = strtol(uuid, &endptr, 0); if (!endptr || *endptr != '\0') return NULL; if (val > UINT16_MAX) return bt_uuid32_to_str(val); return bt_uuid16_to_str(val); } if (len != 36) return NULL; for (i = 0; uuid128_table[i].str; i++) { if (strcasecmp(uuid128_table[i].uuid, uuid) == 0) return uuid128_table[i].str; } if (strncasecmp(uuid + 8, "-0000-1000-8000-00805f9b34fb", 28)) return "Vendor specific"; if (sscanf(uuid, "%08x-0000-1000-8000-00805f9b34fb", &val) != 1) return NULL; return bt_uuid32_to_str(val); } #define BT_APPEAR(_cat, _subcat, _str) \ { \ .val = (((_cat) << 6) | ((_subcat) & 0x3F)), \ .generic = ((_subcat) == 0), \ .str = (_str) \ } static const struct { uint16_t val; bool generic; const char *str; } appearance_table[] = { BT_APPEAR(0x0000, 0x00, "Unknown"), BT_APPEAR(0x0001, 0x00, "Phone"), BT_APPEAR(0x0002, 0x00, "Computer"), BT_APPEAR(0x0002, 0x01, "Desktop Workstation"), BT_APPEAR(0x0002, 0x02, "Server-class Computer"), BT_APPEAR(0x0002, 0x03, "Laptop"), BT_APPEAR(0x0002, 0x04, "Handheld PC/PDA (clamshell)"), BT_APPEAR(0x0002, 0x05, "Palm-size PC/PDA"), BT_APPEAR(0x0002, 0x06, "Wearable computer (watch size)"), BT_APPEAR(0x0002, 0x07, "Tablet"), BT_APPEAR(0x0002, 0x08, "Docking Station"), BT_APPEAR(0x0002, 0x09, "All in One"), BT_APPEAR(0x0002, 0x0A, "Blade Server"), BT_APPEAR(0x0002, 0x0B, "Convertible"), BT_APPEAR(0x0002, 0x0C, "Detachable"), BT_APPEAR(0x0002, 0x0D, "IoT Gateway"), BT_APPEAR(0x0002, 0x0E, "Mini PC"), BT_APPEAR(0x0002, 0x0F, "Stick PC"), BT_APPEAR(0x0003, 0x00, "Watch"), BT_APPEAR(0x0003, 0x01, "Sports Watch"), BT_APPEAR(0x0003, 0x02, "Smartwatch"), BT_APPEAR(0x0004, 0x00, "Clock"), BT_APPEAR(0x0005, 0x00, "Display"), BT_APPEAR(0x0006, 0x00, "Remote Control"), BT_APPEAR(0x0007, 0x00, "Eye-glasses"), BT_APPEAR(0x0008, 0x00, "Tag"), BT_APPEAR(0x0009, 0x00, "Keyring"), BT_APPEAR(0x000A, 0x00, "Media Player"), BT_APPEAR(0x000B, 0x00, "Barcode Scanner"), BT_APPEAR(0x000C, 0x00, "Thermometer"), BT_APPEAR(0x000C, 0x01, "Ear Thermometer"), BT_APPEAR(0x000D, 0x00, "Heart Rate Sensor"), BT_APPEAR(0x000D, 0x01, "Heart Rate Belt"), BT_APPEAR(0x000E, 0x00, "Blood Pressure"), BT_APPEAR(0x000E, 0x01, "Arm Blood Pressure"), BT_APPEAR(0x000E, 0x02, "Wrist Blood Pressure"), BT_APPEAR(0x000F, 0x00, "Human Interface Device"), BT_APPEAR(0x000F, 0x01, "Keyboard"), BT_APPEAR(0x000F, 0x02, "Mouse"), BT_APPEAR(0x000F, 0x03, "Joystick"), BT_APPEAR(0x000F, 0x04, "Gamepad"), BT_APPEAR(0x000F, 0x05, "Digitizer Tablet"), BT_APPEAR(0x000F, 0x06, "Card Reader"), BT_APPEAR(0x000F, 0x07, "Digital Pen"), BT_APPEAR(0x000F, 0x08, "Barcode Scanner"), BT_APPEAR(0x000F, 0x09, "Touchpad"), BT_APPEAR(0x000F, 0x0A, "Presentation Remote"), BT_APPEAR(0x0010, 0x00, "Glucose Meter"), BT_APPEAR(0x0011, 0x00, "Running Walking Sensor"), BT_APPEAR(0x0011, 0x01, "In-Shoe Running Walking Sensor"), BT_APPEAR(0x0011, 0x02, "On-Shoe Running Walking Sensor"), BT_APPEAR(0x0011, 0x03, "On-Hip Running Walking Sensor"), BT_APPEAR(0x0012, 0x00, "Cycling"), BT_APPEAR(0x0012, 0x01, "Cycling Computer"), BT_APPEAR(0x0012, 0x02, "Speed Sensor"), BT_APPEAR(0x0012, 0x03, "Cadence Sensor"), BT_APPEAR(0x0012, 0x04, "Power Sensor"), BT_APPEAR(0x0012, 0x05, "Speed and Cadence Sensor"), BT_APPEAR(0x0013, 0x00, "Control Device"), BT_APPEAR(0x0013, 0x01, "Switch"), BT_APPEAR(0x0013, 0x02, "Multi-switch"), BT_APPEAR(0x0013, 0x03, "Button"), BT_APPEAR(0x0013, 0x04, "Slider"), BT_APPEAR(0x0013, 0x05, "Rotary Switch"), BT_APPEAR(0x0013, 0x06, "Touch Panel"), BT_APPEAR(0x0013, 0x07, "Single Switch"), BT_APPEAR(0x0013, 0x08, "Double Switch"), BT_APPEAR(0x0013, 0x09, "Triple Switch"), BT_APPEAR(0x0013, 0x0A, "Battery Switch"), BT_APPEAR(0x0013, 0x0B, "Energy Harvesting Switch"), BT_APPEAR(0x0013, 0x0C, "Push Button"), BT_APPEAR(0x0013, 0x0D, "Dial"), BT_APPEAR(0x0014, 0x00, "Network Device"), BT_APPEAR(0x0014, 0x01, "Access Point"), BT_APPEAR(0x0014, 0x02, "Mesh Device"), BT_APPEAR(0x0014, 0x03, "Mesh Network Proxy"), BT_APPEAR(0x0015, 0x00, "Sensor"), BT_APPEAR(0x0015, 0x01, "Motion Sensor"), BT_APPEAR(0x0015, 0x02, "Air quality Sensor"), BT_APPEAR(0x0015, 0x03, "Temperature Sensor"), BT_APPEAR(0x0015, 0x04, "Humidity Sensor"), BT_APPEAR(0x0015, 0x05, "Leak Sensor"), BT_APPEAR(0x0015, 0x06, "Smoke Sensor"), BT_APPEAR(0x0015, 0x07, "Occupancy Sensor"), BT_APPEAR(0x0015, 0x08, "Contact Sensor"), BT_APPEAR(0x0015, 0x09, "Carbon Monoxide Sensor"), BT_APPEAR(0x0015, 0x0A, "Carbon Dioxide Sensor"), BT_APPEAR(0x0015, 0x0B, "Ambient Light Sensor"), BT_APPEAR(0x0015, 0x0C, "Energy Sensor"), BT_APPEAR(0x0015, 0x0D, "Color Light Sensor"), BT_APPEAR(0x0015, 0x0E, "Rain Sensor"), BT_APPEAR(0x0015, 0x0F, "Fire Sensor"), BT_APPEAR(0x0015, 0x10, "Wind Sensor"), BT_APPEAR(0x0015, 0x11, "Proximity Sensor"), BT_APPEAR(0x0015, 0x12, "Multi-Sensor"), BT_APPEAR(0x0015, 0x13, "Flush Mounted Sensor"), BT_APPEAR(0x0015, 0x14, "Ceiling Mounted Sensor"), BT_APPEAR(0x0015, 0x15, "Wall Mounted Sensor"), BT_APPEAR(0x0015, 0x16, "Multisensor"), BT_APPEAR(0x0015, 0x17, "Energy Meter"), BT_APPEAR(0x0015, 0x18, "Flame Detector"), BT_APPEAR(0x0015, 0x19, "Vehicle Tire Pressure Sensor"), BT_APPEAR(0x0016, 0x00, "Light Fixtures"), BT_APPEAR(0x0016, 0x01, "Wall Light"), BT_APPEAR(0x0016, 0x02, "Ceiling Light"), BT_APPEAR(0x0016, 0x03, "Floor Light"), BT_APPEAR(0x0016, 0x04, "Cabinet Light"), BT_APPEAR(0x0016, 0x05, "Desk Light"), BT_APPEAR(0x0016, 0x06, "Troffer Light"), BT_APPEAR(0x0016, 0x07, "Pendant Light"), BT_APPEAR(0x0016, 0x08, "In-ground Light"), BT_APPEAR(0x0016, 0x09, "Flood Light"), BT_APPEAR(0x0016, 0x0A, "Underwater Light"), BT_APPEAR(0x0016, 0x0B, "Bollard with Light"), BT_APPEAR(0x0016, 0x0C, "Pathway Light"), BT_APPEAR(0x0016, 0x0D, "Garden Light"), BT_APPEAR(0x0016, 0x0E, "Pole-top Light"), BT_APPEAR(0x0016, 0x0F, "Spotlight"), BT_APPEAR(0x0016, 0x10, "Linear Light"), BT_APPEAR(0x0016, 0x11, "Street Light"), BT_APPEAR(0x0016, 0x12, "Shelves Light"), BT_APPEAR(0x0016, 0x13, "Bay Light"), BT_APPEAR(0x0016, 0x14, "Emergency Exit Light"), BT_APPEAR(0x0016, 0x15, "Light Controller"), BT_APPEAR(0x0016, 0x16, "Light Driver"), BT_APPEAR(0x0016, 0x17, "Bulb"), BT_APPEAR(0x0016, 0x18, "Low-bay Light"), BT_APPEAR(0x0016, 0x19, "High-bay Light"), BT_APPEAR(0x0017, 0x00, "Fan"), BT_APPEAR(0x0017, 0x01, "Ceiling Fan"), BT_APPEAR(0x0017, 0x02, "Axial Fan"), BT_APPEAR(0x0017, 0x03, "Exhaust Fan"), BT_APPEAR(0x0017, 0x04, "Pedestal Fan"), BT_APPEAR(0x0017, 0x05, "Desk Fan"), BT_APPEAR(0x0017, 0x06, "Wall Fan"), BT_APPEAR(0x0018, 0x00, "HVAC"), BT_APPEAR(0x0018, 0x01, "Thermostat"), BT_APPEAR(0x0018, 0x02, "Humidifier"), BT_APPEAR(0x0018, 0x03, "De-humidifier"), BT_APPEAR(0x0018, 0x04, "Heater"), BT_APPEAR(0x0018, 0x05, "Radiator"), BT_APPEAR(0x0018, 0x06, "Boiler"), BT_APPEAR(0x0018, 0x07, "Heat Pump"), BT_APPEAR(0x0018, 0x08, "Infrared Heater"), BT_APPEAR(0x0018, 0x09, "Radiant Panel Heater"), BT_APPEAR(0x0018, 0x0A, "Fan Heater"), BT_APPEAR(0x0018, 0x0B, "Air Curtain"), BT_APPEAR(0x0019, 0x00, "Air Conditioning"), BT_APPEAR(0x001A, 0x00, "Humidifier"), BT_APPEAR(0x001B, 0x00, "Heating"), BT_APPEAR(0x001B, 0x01, "Radiator"), BT_APPEAR(0x001B, 0x02, "Boiler"), BT_APPEAR(0x001B, 0x03, "Heat Pump"), BT_APPEAR(0x001B, 0x04, "Infrared Heater"), BT_APPEAR(0x001B, 0x05, "Radiant Panel Heater"), BT_APPEAR(0x001B, 0x06, "Fan Heater"), BT_APPEAR(0x001B, 0x07, "Air Curtain"), BT_APPEAR(0x001C, 0x00, "Access Control"), BT_APPEAR(0x001C, 0x01, "Access Door"), BT_APPEAR(0x001C, 0x02, "Garage Door"), BT_APPEAR(0x001C, 0x03, "Emergency Exit Door"), BT_APPEAR(0x001C, 0x04, "Access Lock"), BT_APPEAR(0x001C, 0x05, "Elevator"), BT_APPEAR(0x001C, 0x06, "Window"), BT_APPEAR(0x001C, 0x07, "Entrance Gate"), BT_APPEAR(0x001C, 0x08, "Door Lock"), BT_APPEAR(0x001C, 0x09, "Locker"), BT_APPEAR(0x001D, 0x00, "Motorized Device"), BT_APPEAR(0x001D, 0x01, "Motorized Gate"), BT_APPEAR(0x001D, 0x02, "Awning"), BT_APPEAR(0x001D, 0x03, "Blinds or Shades"), BT_APPEAR(0x001D, 0x04, "Curtains"), BT_APPEAR(0x001D, 0x05, "Screen"), BT_APPEAR(0x001E, 0x00, "Power Device"), BT_APPEAR(0x001E, 0x01, "Power Outlet"), BT_APPEAR(0x001E, 0x02, "Power Strip"), BT_APPEAR(0x001E, 0x03, "Plug"), BT_APPEAR(0x001E, 0x04, "Power Supply"), BT_APPEAR(0x001E, 0x05, "LED Driver"), BT_APPEAR(0x001E, 0x06, "Fluorescent Lamp Gear"), BT_APPEAR(0x001E, 0x07, "HID Lamp Gear"), BT_APPEAR(0x001E, 0x08, "Charge Case"), BT_APPEAR(0x001E, 0x09, "Power Bank"), BT_APPEAR(0x001F, 0x00, "Light Source"), BT_APPEAR(0x001F, 0x01, "Incandescent Light Bulb"), BT_APPEAR(0x001F, 0x02, "LED Lamp"), BT_APPEAR(0x001F, 0x03, "HID Lamp"), BT_APPEAR(0x001F, 0x04, "Fluorescent Lamp"), BT_APPEAR(0x001F, 0x05, "LED Array"), BT_APPEAR(0x001F, 0x06, "Multi-Color LED Array"), BT_APPEAR(0x001F, 0x07, "Low voltage halogen"), BT_APPEAR(0x001F, 0x08, "Organic light emitting diode (OLED)"), BT_APPEAR(0x0020, 0x00, "Window Covering"), BT_APPEAR(0x0020, 0x01, "Window Shades"), BT_APPEAR(0x0020, 0x02, "Window Blinds"), BT_APPEAR(0x0020, 0x03, "Window Awning"), BT_APPEAR(0x0020, 0x04, "Window Curtain"), BT_APPEAR(0x0020, 0x05, "Exterior Shutter"), BT_APPEAR(0x0020, 0x06, "Exterior Screen"), BT_APPEAR(0x0021, 0x00, "Audio Sink"), BT_APPEAR(0x0021, 0x01, "Standalone Speaker"), BT_APPEAR(0x0021, 0x02, "Soundbar"), BT_APPEAR(0x0021, 0x03, "Bookshelf Speaker"), BT_APPEAR(0x0021, 0x04, "Standmounted Speaker"), BT_APPEAR(0x0021, 0x05, "Speakerphone"), BT_APPEAR(0x0022, 0x00, "Audio Source"), BT_APPEAR(0x0022, 0x01, "Microphone"), BT_APPEAR(0x0022, 0x02, "Alarm"), BT_APPEAR(0x0022, 0x03, "Bell"), BT_APPEAR(0x0022, 0x04, "Horn"), BT_APPEAR(0x0022, 0x05, "Broadcasting Device"), BT_APPEAR(0x0022, 0x06, "Service Desk"), BT_APPEAR(0x0022, 0x07, "Kiosk"), BT_APPEAR(0x0022, 0x08, "Broadcasting Room"), BT_APPEAR(0x0022, 0x09, "Auditorium"), BT_APPEAR(0x0023, 0x00, "Motorized Vehicle"), BT_APPEAR(0x0023, 0x01, "Car"), BT_APPEAR(0x0023, 0x02, "Large Goods Vehicle"), BT_APPEAR(0x0023, 0x03, "2-Wheeled Vehicle"), BT_APPEAR(0x0023, 0x04, "Motorbike"), BT_APPEAR(0x0023, 0x05, "Scooter"), BT_APPEAR(0x0023, 0x06, "Moped"), BT_APPEAR(0x0023, 0x07, "3-Wheeled Vehicle"), BT_APPEAR(0x0023, 0x08, "Light Vehicle"), BT_APPEAR(0x0023, 0x09, "Quad Bike"), BT_APPEAR(0x0023, 0x0A, "Minibus"), BT_APPEAR(0x0023, 0x0B, "Bus"), BT_APPEAR(0x0023, 0x0C, "Trolley"), BT_APPEAR(0x0023, 0x0D, "Agricultural Vehicle"), BT_APPEAR(0x0023, 0x0E, "Camper / Caravan"), BT_APPEAR(0x0023, 0x0F, "Recreational Vehicle / Motor Home"), BT_APPEAR(0x0024, 0x00, "Domestic Appliance"), BT_APPEAR(0x0024, 0x01, "Refrigerator"), BT_APPEAR(0x0024, 0x02, "Freezer"), BT_APPEAR(0x0024, 0x03, "Oven"), BT_APPEAR(0x0024, 0x04, "Microwave"), BT_APPEAR(0x0024, 0x05, "Toaster"), BT_APPEAR(0x0024, 0x06, "Washing Machine"), BT_APPEAR(0x0024, 0x07, "Dryer"), BT_APPEAR(0x0024, 0x08, "Coffee maker"), BT_APPEAR(0x0024, 0x09, "Clothes iron"), BT_APPEAR(0x0024, 0x0A, "Curling iron"), BT_APPEAR(0x0024, 0x0B, "Hair dryer"), BT_APPEAR(0x0024, 0x0C, "Vacuum cleaner"), BT_APPEAR(0x0024, 0x0D, "Robotic vacuum cleaner"), BT_APPEAR(0x0024, 0x0E, "Rice cooker"), BT_APPEAR(0x0024, 0x0F, "Clothes steamer"), BT_APPEAR(0x0025, 0x00, "Wearable Audio Device"), BT_APPEAR(0x0025, 0x01, "Earbud"), BT_APPEAR(0x0025, 0x02, "Headset"), BT_APPEAR(0x0025, 0x03, "Headphones"), BT_APPEAR(0x0025, 0x04, "Neck Band"), BT_APPEAR(0x0025, 0x05, "Left Earbud"), BT_APPEAR(0x0025, 0x06, "Right Earbud"), BT_APPEAR(0x0026, 0x00, "Aircraft"), BT_APPEAR(0x0026, 0x01, "Light Aircraft"), BT_APPEAR(0x0026, 0x02, "Microlight"), BT_APPEAR(0x0026, 0x03, "Paraglider"), BT_APPEAR(0x0026, 0x04, "Large Passenger Aircraft"), BT_APPEAR(0x0027, 0x00, "AV Equipment"), BT_APPEAR(0x0027, 0x01, "Amplifier"), BT_APPEAR(0x0027, 0x02, "Receiver"), BT_APPEAR(0x0027, 0x03, "Radio"), BT_APPEAR(0x0027, 0x04, "Tuner"), BT_APPEAR(0x0027, 0x05, "Turntable"), BT_APPEAR(0x0027, 0x06, "CD Player"), BT_APPEAR(0x0027, 0x07, "DVD Player"), BT_APPEAR(0x0027, 0x08, "Bluray Player"), BT_APPEAR(0x0027, 0x09, "Optical Disc Player"), BT_APPEAR(0x0027, 0x0A, "Set-Top Box"), BT_APPEAR(0x0028, 0x00, "Display Equipment"), BT_APPEAR(0x0028, 0x01, "Television"), BT_APPEAR(0x0028, 0x02, "Monitor"), BT_APPEAR(0x0028, 0x03, "Projector"), BT_APPEAR(0x0029, 0x00, "Hearing aid"), BT_APPEAR(0x0029, 0x01, "In-ear hearing aid"), BT_APPEAR(0x0029, 0x02, "Behind-ear hearing aid"), BT_APPEAR(0x0029, 0x03, "Cochlear Implant"), BT_APPEAR(0x002A, 0x00, "Gaming"), BT_APPEAR(0x002A, 0x01, "Home Video Game Console"), BT_APPEAR(0x002A, 0x02, "Portable handheld console"), BT_APPEAR(0x002B, 0x00, "Signage"), BT_APPEAR(0x002B, 0x01, "Digital Signage"), BT_APPEAR(0x002B, 0x02, "Electronic Label"), BT_APPEAR(0x0031, 0x00, "Pulse Oximeter"), BT_APPEAR(0x0031, 0x01, "Fingertip Pulse Oximeter"), BT_APPEAR(0x0031, 0x02, "Wrist Worn Pulse Oximeter"), BT_APPEAR(0x0032, 0x00, "Weight Scale"), BT_APPEAR(0x0033, 0x00, "Personal Mobility Device"), BT_APPEAR(0x0033, 0x01, "Powered Wheelchair"), BT_APPEAR(0x0033, 0x02, "Mobility Scooter"), BT_APPEAR(0x0034, 0x00, "Continuous Glucose Monitor"), BT_APPEAR(0x0035, 0x00, "Insulin Pump"), BT_APPEAR(0x0035, 0x01, "Insulin Pump, durable pump"), BT_APPEAR(0x0035, 0x04, "Insulin Pump, patch pump"), BT_APPEAR(0x0035, 0x08, "Insulin Pen"), BT_APPEAR(0x0036, 0x00, "Medication Delivery"), BT_APPEAR(0x0037, 0x00, "Spirometer"), BT_APPEAR(0x0037, 0x01, "Handheld Spirometer"), BT_APPEAR(0x0051, 0x00, "Outdoor Sports Activity"), BT_APPEAR(0x0051, 0x01, "Location Display"), BT_APPEAR(0x0051, 0x02, "Location and Navigation Display"), BT_APPEAR(0x0051, 0x03, "Location Pod"), BT_APPEAR(0x0051, 0x04, "Location and Navigation Pod"), BT_APPEAR(0x0052, 0x00, "Industrial Measurement Device"), BT_APPEAR(0x0052, 0x01, "Torque Testing Device"), BT_APPEAR(0x0052, 0x02, "Caliper"), BT_APPEAR(0x0052, 0x03, "Dial Indicator"), BT_APPEAR(0x0052, 0x04, "Micrometer"), BT_APPEAR(0x0052, 0x05, "Height Gauge"), BT_APPEAR(0x0052, 0x06, "Force Gauge"), BT_APPEAR(0x0053, 0x00, "Industrial Tools"), BT_APPEAR(0x0053, 0x01, "Machine Tool Holder"), BT_APPEAR(0x0053, 0x02, "Generic Clamping Device"), BT_APPEAR(0x0053, 0x03, "Clamping Jaws/Jaw Chuck"), BT_APPEAR(0x0053, 0x04, "Clamping (Collet) Chuck"), BT_APPEAR(0x0053, 0x05, "Clamping Mandrel"), BT_APPEAR(0x0053, 0x06, "Vise"), BT_APPEAR(0x0053, 0x07, "Zero-Point Clamping System"), BT_APPEAR(0x0053, 0x08, "Torque Wrench"), BT_APPEAR(0x0053, 0x09, "Torque Screwdriver"), BT_APPEAR(0x0054, 0x00, "Cookware Device"), BT_APPEAR(0x0054, 0x01, "Pot and Jugs"), BT_APPEAR(0x0054, 0x02, "Pressure Cooker"), BT_APPEAR(0x0054, 0x03, "Slow Cooker"), BT_APPEAR(0x0054, 0x04, "Steam Cooker"), BT_APPEAR(0x0054, 0x05, "Saucepan"), BT_APPEAR(0x0054, 0x06, "Frying Pan"), BT_APPEAR(0x0054, 0x07, "Casserole"), BT_APPEAR(0x0054, 0x08, "Dutch Oven"), BT_APPEAR(0x0054, 0x09, "Grill Pan/Raclette Grill/Griddle Pan"), BT_APPEAR(0x0054, 0x0A, "Braising Pan"), BT_APPEAR(0x0054, 0x0B, "Wok Pan"), BT_APPEAR(0x0054, 0x0C, "Paella Pan"), BT_APPEAR(0x0054, 0x0D, "Crepe Pan"), BT_APPEAR(0x0054, 0x0E, "Tagine"), BT_APPEAR(0x0054, 0x0F, "Fondue"), BT_APPEAR(0x0054, 0x10, "Lid"), BT_APPEAR(0x0054, 0x11, "Wired Probe"), BT_APPEAR(0x0054, 0x12, "Wireless Probe"), BT_APPEAR(0x0054, 0x13, "Baking Molds"), BT_APPEAR(0x0054, 0x14, "Baking Tray"), { } }; const char *bt_appear_to_str(uint16_t appearance) { const char *str = NULL; int i, type = 0; for (i = 0; appearance_table[i].str; i++) { if (appearance_table[i].generic) { if (appearance < appearance_table[i].val) break; type = i; } if (appearance_table[i].val == appearance) { str = appearance_table[i].str; break; } } if (!str) str = appearance_table[type].str; return str; } char *strdelimit(char *str, char *del, char c) { char *dup; if (!str) return NULL; dup = strdup(str); if (dup[0] == '\0') return dup; while (del[0] != '\0') { char *rep = dup; while ((rep = strchr(rep, del[0]))) rep[0] = c; del++; } return dup; } int strsuffix(const char *str, const char *suffix) { int len; int suffix_len; if (!str || !suffix) return -1; if (str[0] == '\0' && suffix[0] != '\0') return -1; if (suffix[0] == '\0' && str[0] != '\0') return -1; len = strlen(str); suffix_len = strlen(suffix); if (len < suffix_len) return -1; return strncmp(str + len - suffix_len, suffix, suffix_len); } char *strstrip(char *str) { size_t size; char *end; if (!str) return NULL; size = strlen(str); if (!size) return str; end = str + size - 1; while (end >= str && isspace(*end)) end--; *(end + 1) = '\0'; while (*str && isspace(*str)) str++; return str; } size_t strnlenutf8(const char *str, size_t len) { size_t i = 0; while (i < len) { unsigned char c = str[i]; size_t size = 0; /* Check the first byte to determine the number of bytes in the * UTF-8 character. */ if ((c & 0x80) == 0x00) size = 1; else if ((c & 0xE0) == 0xC0) size = 2; else if ((c & 0xF0) == 0xE0) size = 3; else if ((c & 0xF8) == 0xF0) size = 4; else /* Invalid UTF-8 sequence */ goto done; /* Check the following bytes to ensure they have the correct * format. */ for (size_t j = 1; j < size; ++j) { if (i + j >= len || (str[i + j] & 0xC0) != 0x80) /* Invalid UTF-8 sequence */ goto done; } /* Move to the next character */ i += size; } done: return i; } bool strisutf8(const char *str, size_t len) { return strnlenutf8(str, len) == len; } bool argsisutf8(int argc, char *argv[]) { for (int i = 0; i < argc; i++) { if (!strisutf8(argv[i], strlen(argv[i]))) { printf("Invalid character in string: %s\n", argv[i]); return false; } } return true; } char *strtoutf8(char *str, size_t len) { size_t i = 0; i = strnlenutf8(str, len); if (i == len) return str; /* Truncate to the longest valid UTF-8 string */ memset(str + i, 0, len - i); return str; } bluez-5.87/src/shared/PaxHeaders/ecc.c0000644000000000000000000000005014621503015014603 xustar0020 atime=1783092885 20 ctime=1783094099 bluez-5.87/src/shared/ecc.c0000644000000000000000000005413314621503015014272 0ustar00rootroot// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2013, Kenneth MacKay * All rights reserved. * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include "ecc.h" /* 256-bit curve */ #define ECC_BYTES 32 /* Number of uint64_t's needed */ #define NUM_ECC_DIGITS (ECC_BYTES / 8) struct ecc_point { uint64_t x[NUM_ECC_DIGITS]; uint64_t y[NUM_ECC_DIGITS]; }; #define MAX_TRIES 16 typedef struct { uint64_t m_low; uint64_t m_high; } uint128_t; #define CURVE_P_32 { 0xFFFFFFFFFFFFFFFFull, 0x00000000FFFFFFFFull, \ 0x0000000000000000ull, 0xFFFFFFFF00000001ull } #define CURVE_G_32 { \ { 0xF4A13945D898C296ull, 0x77037D812DEB33A0ull, \ 0xF8BCE6E563A440F2ull, 0x6B17D1F2E12C4247ull }, \ { 0xCBB6406837BF51F5ull, 0x2BCE33576B315ECEull, \ 0x8EE7EB4A7C0F9E16ull, 0x4FE342E2FE1A7F9Bull } \ } #define CURVE_N_32 { 0xF3B9CAC2FC632551ull, 0xBCE6FAADA7179E84ull, \ 0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFF00000000ull } #define CURVE_B_32 { 0x3BCE3C3E27D2604Bull, 0x651D06B0CC53B0F6ull, \ 0xB3EBBD55769886BCull, 0x5AC635D8AA3A93E7ull } static uint64_t curve_p[NUM_ECC_DIGITS] = CURVE_P_32; static struct ecc_point curve_g = CURVE_G_32; static uint64_t curve_n[NUM_ECC_DIGITS] = CURVE_N_32; static uint64_t curve_b[NUM_ECC_DIGITS] = CURVE_B_32; static bool get_random_number(uint64_t *vli) { char *ptr = (char *) vli; size_t left = ECC_BYTES; int fd; fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); if (fd < 0) { fd = open("/dev/random", O_RDONLY | O_CLOEXEC); if (fd < 0) return false; } while (left > 0) { ssize_t ret; ret = read(fd, ptr, left); if (ret <= 0) { close(fd); return false; } left -= ret; ptr += ret; } close(fd); return true; } static void vli_clear(uint64_t *vli) { int i; for (i = 0; i < NUM_ECC_DIGITS; i++) vli[i] = 0; } /* Returns true if vli == 0, false otherwise. */ static bool vli_is_zero(const uint64_t *vli) { int i; for (i = 0; i < NUM_ECC_DIGITS; i++) { if (vli[i]) return false; } return true; } /* Returns nonzero if bit bit of vli is set. */ static uint64_t vli_test_bit(const uint64_t *vli, unsigned int bit) { return (vli[bit / 64] & ((uint64_t) 1 << (bit % 64))); } /* Counts the number of 64-bit "digits" in vli. */ static unsigned int vli_num_digits(const uint64_t *vli) { int i; /* Search from the end until we find a non-zero digit. * We do it in reverse because we expect that most digits will * be nonzero. */ for (i = NUM_ECC_DIGITS - 1; i >= 0 && vli[i] == 0; i--); return (i + 1); } /* Counts the number of bits required for vli. */ static unsigned int vli_num_bits(const uint64_t *vli) { unsigned int i, num_digits; uint64_t digit; num_digits = vli_num_digits(vli); if (num_digits == 0) return 0; digit = vli[num_digits - 1]; for (i = 0; digit; i++) digit >>= 1; return ((num_digits - 1) * 64 + i); } /* Sets dest = src. */ static void vli_set(uint64_t *dest, const uint64_t *src) { int i; for (i = 0; i < NUM_ECC_DIGITS; i++) dest[i] = src[i]; } /* Returns sign of left - right. */ static int vli_cmp(const uint64_t *left, const uint64_t *right) { int i; for (i = NUM_ECC_DIGITS - 1; i >= 0; i--) { if (left[i] > right[i]) return 1; else if (left[i] < right[i]) return -1; } return 0; } /* Constant-time comparison function - secure way to compare long integers */ /* Returns one if left == right, zero otherwise. */ static bool vli_equal(const uint64_t *left, const uint64_t *right) { uint64_t diff = 0; int i; for (i = NUM_ECC_DIGITS - 1; i >= 0; --i) diff |= (left[i] ^ right[i]); return (diff == 0); } /* Computes result = in << c, returning carry. Can modify in place * (if result == in). 0 < shift < 64. */ static uint64_t vli_lshift(uint64_t *result, const uint64_t *in, unsigned int shift) { uint64_t carry = 0; int i; for (i = 0; i < NUM_ECC_DIGITS; i++) { uint64_t temp = in[i]; result[i] = (temp << shift) | carry; carry = temp >> (64 - shift); } return carry; } /* Computes vli = vli >> 1. */ static void vli_rshift1(uint64_t *vli) { uint64_t *end = vli; uint64_t carry = 0; vli += NUM_ECC_DIGITS; while (vli-- > end) { uint64_t temp = *vli; *vli = (temp >> 1) | carry; carry = temp << 63; } } /* Computes result = left + right, returning carry. Can modify in place. */ static uint64_t vli_add(uint64_t *result, const uint64_t *left, const uint64_t *right) { uint64_t carry = 0; int i; for (i = 0; i < NUM_ECC_DIGITS; i++) { uint64_t sum; sum = left[i] + right[i] + carry; if (sum != left[i]) carry = (sum < left[i]); result[i] = sum; } return carry; } /* Computes result = left - right, returning borrow. Can modify in place. */ static uint64_t vli_sub(uint64_t *result, const uint64_t *left, const uint64_t *right) { uint64_t borrow = 0; int i; for (i = 0; i < NUM_ECC_DIGITS; i++) { uint64_t diff; diff = left[i] - right[i] - borrow; if (diff != left[i]) borrow = (diff > left[i]); result[i] = diff; } return borrow; } static uint128_t mul_64_64(uint64_t left, uint64_t right) { uint64_t a0 = left & 0xffffffffull; uint64_t a1 = left >> 32; uint64_t b0 = right & 0xffffffffull; uint64_t b1 = right >> 32; uint64_t m0 = a0 * b0; uint64_t m1 = a0 * b1; uint64_t m2 = a1 * b0; uint64_t m3 = a1 * b1; uint128_t result; m2 += (m0 >> 32); m2 += m1; /* Overflow */ if (m2 < m1) m3 += 0x100000000ull; result.m_low = (m0 & 0xffffffffull) | (m2 << 32); result.m_high = m3 + (m2 >> 32); return result; } static uint128_t add_128_128(uint128_t a, uint128_t b) { uint128_t result; result.m_low = a.m_low + b.m_low; result.m_high = a.m_high + b.m_high + (result.m_low < a.m_low); return result; } static void vli_mult(uint64_t *result, const uint64_t *left, const uint64_t *right) { uint128_t r01 = { 0, 0 }; uint64_t r2 = 0; unsigned int i, k; /* Compute each digit of result in sequence, maintaining the * carries. */ for (k = 0; k < NUM_ECC_DIGITS * 2 - 1; k++) { unsigned int min; if (k < NUM_ECC_DIGITS) min = 0; else min = (k + 1) - NUM_ECC_DIGITS; for (i = min; i <= k && i < NUM_ECC_DIGITS; i++) { uint128_t product; product = mul_64_64(left[i], right[k - i]); r01 = add_128_128(r01, product); r2 += (r01.m_high < product.m_high); } result[k] = r01.m_low; r01.m_low = r01.m_high; r01.m_high = r2; r2 = 0; } result[NUM_ECC_DIGITS * 2 - 1] = r01.m_low; } static void vli_square(uint64_t *result, const uint64_t *left) { uint128_t r01 = { 0, 0 }; uint64_t r2 = 0; int i, k; for (k = 0; k < NUM_ECC_DIGITS * 2 - 1; k++) { unsigned int min; if (k < NUM_ECC_DIGITS) min = 0; else min = (k + 1) - NUM_ECC_DIGITS; for (i = min; i <= k && i <= k - i; i++) { uint128_t product; product = mul_64_64(left[i], left[k - i]); if (i < k - i) { r2 += product.m_high >> 63; product.m_high = (product.m_high << 1) | (product.m_low >> 63); product.m_low <<= 1; } r01 = add_128_128(r01, product); r2 += (r01.m_high < product.m_high); } result[k] = r01.m_low; r01.m_low = r01.m_high; r01.m_high = r2; r2 = 0; } result[NUM_ECC_DIGITS * 2 - 1] = r01.m_low; } /* Computes result = (left + right) % mod. * Assumes that left < mod and right < mod, result != mod. */ static void vli_mod_add(uint64_t *result, const uint64_t *left, const uint64_t *right, const uint64_t *mod) { uint64_t carry; carry = vli_add(result, left, right); /* result > mod (result = mod + remainder), so subtract mod to * get remainder. */ if (carry || vli_cmp(result, mod) >= 0) vli_sub(result, result, mod); } /* Computes result = (left - right) % mod. * Assumes that left < mod and right < mod, result != mod. */ static void vli_mod_sub(uint64_t *result, const uint64_t *left, const uint64_t *right, const uint64_t *mod) { uint64_t borrow = vli_sub(result, left, right); /* In this case, p_result == -diff == (max int) - diff. * Since -x % d == d - x, we can get the correct result from * result + mod (with overflow). */ if (borrow) vli_add(result, result, mod); } /* Computes result = product % curve_p from http://www.nsa.gov/ia/_files/nist-routines.pdf */ static void vli_mmod_fast(uint64_t *result, const uint64_t *product) { uint64_t tmp[NUM_ECC_DIGITS]; int carry; /* t */ vli_set(result, product); /* s1 */ tmp[0] = 0; tmp[1] = product[5] & 0xffffffff00000000ull; tmp[2] = product[6]; tmp[3] = product[7]; carry = vli_lshift(tmp, tmp, 1); carry += vli_add(result, result, tmp); /* s2 */ tmp[1] = product[6] << 32; tmp[2] = (product[6] >> 32) | (product[7] << 32); tmp[3] = product[7] >> 32; carry += vli_lshift(tmp, tmp, 1); carry += vli_add(result, result, tmp); /* s3 */ tmp[0] = product[4]; tmp[1] = product[5] & 0xffffffff; tmp[2] = 0; tmp[3] = product[7]; carry += vli_add(result, result, tmp); /* s4 */ tmp[0] = (product[4] >> 32) | (product[5] << 32); tmp[1] = (product[5] >> 32) | (product[6] & 0xffffffff00000000ull); tmp[2] = product[7]; tmp[3] = (product[6] >> 32) | (product[4] << 32); carry += vli_add(result, result, tmp); /* d1 */ tmp[0] = (product[5] >> 32) | (product[6] << 32); tmp[1] = (product[6] >> 32); tmp[2] = 0; tmp[3] = (product[4] & 0xffffffff) | (product[5] << 32); carry -= vli_sub(result, result, tmp); /* d2 */ tmp[0] = product[6]; tmp[1] = product[7]; tmp[2] = 0; tmp[3] = (product[4] >> 32) | (product[5] & 0xffffffff00000000ull); carry -= vli_sub(result, result, tmp); /* d3 */ tmp[0] = (product[6] >> 32) | (product[7] << 32); tmp[1] = (product[7] >> 32) | (product[4] << 32); tmp[2] = (product[4] >> 32) | (product[5] << 32); tmp[3] = (product[6] << 32); carry -= vli_sub(result, result, tmp); /* d4 */ tmp[0] = product[7]; tmp[1] = product[4] & 0xffffffff00000000ull; tmp[2] = product[5]; tmp[3] = product[6] & 0xffffffff00000000ull; carry -= vli_sub(result, result, tmp); if (carry < 0) { do { carry += vli_add(result, result, curve_p); } while (carry < 0); } else { while (carry || vli_cmp(curve_p, result) != 1) carry -= vli_sub(result, result, curve_p); } } /* Computes result = (left * right) % curve_p. */ static void vli_mod_mult_fast(uint64_t *result, const uint64_t *left, const uint64_t *right) { uint64_t product[2 * NUM_ECC_DIGITS]; vli_mult(product, left, right); vli_mmod_fast(result, product); } /* Computes result = left^2 % curve_p. */ static void vli_mod_square_fast(uint64_t *result, const uint64_t *left) { uint64_t product[2 * NUM_ECC_DIGITS]; vli_square(product, left); vli_mmod_fast(result, product); } #define EVEN(vli) (!(vli[0] & 1)) /* Computes result = (1 / p_input) % mod. All VLIs are the same size. * See "From Euclid's GCD to Montgomery Multiplication to the Great Divide" * https://labs.oracle.com/techrep/2001/smli_tr-2001-95.pdf */ static void vli_mod_inv(uint64_t *result, const uint64_t *input, const uint64_t *mod) { uint64_t a[NUM_ECC_DIGITS], b[NUM_ECC_DIGITS]; uint64_t u[NUM_ECC_DIGITS], v[NUM_ECC_DIGITS]; uint64_t carry; int cmp_result; if (vli_is_zero(input)) { vli_clear(result); return; } vli_set(a, input); vli_set(b, mod); vli_clear(u); u[0] = 1; vli_clear(v); while ((cmp_result = vli_cmp(a, b)) != 0) { carry = 0; if (EVEN(a)) { vli_rshift1(a); if (!EVEN(u)) carry = vli_add(u, u, mod); vli_rshift1(u); if (carry) u[NUM_ECC_DIGITS - 1] |= 0x8000000000000000ull; } else if (EVEN(b)) { vli_rshift1(b); if (!EVEN(v)) carry = vli_add(v, v, mod); vli_rshift1(v); if (carry) v[NUM_ECC_DIGITS - 1] |= 0x8000000000000000ull; } else if (cmp_result > 0) { vli_sub(a, a, b); vli_rshift1(a); if (vli_cmp(u, v) < 0) vli_add(u, u, mod); vli_sub(u, u, v); if (!EVEN(u)) carry = vli_add(u, u, mod); vli_rshift1(u); if (carry) u[NUM_ECC_DIGITS - 1] |= 0x8000000000000000ull; } else { vli_sub(b, b, a); vli_rshift1(b); if (vli_cmp(v, u) < 0) vli_add(v, v, mod); vli_sub(v, v, u); if (!EVEN(v)) carry = vli_add(v, v, mod); vli_rshift1(v); if (carry) v[NUM_ECC_DIGITS - 1] |= 0x8000000000000000ull; } } vli_set(result, u); } /* ------ Point operations ------ */ /* Returns true if p_point is the point at infinity, false otherwise. */ static bool ecc_point_is_zero(const struct ecc_point *point) { return (vli_is_zero(point->x) && vli_is_zero(point->y)); } /* Point multiplication algorithm using Montgomery's ladder with co-Z * coordinates. From http://eprint.iacr.org/2011/338.pdf */ /* Double in place */ static void ecc_point_double_jacobian(uint64_t *x1, uint64_t *y1, uint64_t *z1) { /* t1 = x, t2 = y, t3 = z */ uint64_t t4[NUM_ECC_DIGITS]; uint64_t t5[NUM_ECC_DIGITS]; if (vli_is_zero(z1)) return; vli_mod_square_fast(t4, y1); /* t4 = y1^2 */ vli_mod_mult_fast(t5, x1, t4); /* t5 = x1*y1^2 = A */ vli_mod_square_fast(t4, t4); /* t4 = y1^4 */ vli_mod_mult_fast(y1, y1, z1); /* t2 = y1*z1 = z3 */ vli_mod_square_fast(z1, z1); /* t3 = z1^2 */ vli_mod_add(x1, x1, z1, curve_p); /* t1 = x1 + z1^2 */ vli_mod_add(z1, z1, z1, curve_p); /* t3 = 2*z1^2 */ vli_mod_sub(z1, x1, z1, curve_p); /* t3 = x1 - z1^2 */ vli_mod_mult_fast(x1, x1, z1); /* t1 = x1^2 - z1^4 */ vli_mod_add(z1, x1, x1, curve_p); /* t3 = 2*(x1^2 - z1^4) */ vli_mod_add(x1, x1, z1, curve_p); /* t1 = 3*(x1^2 - z1^4) */ if (vli_test_bit(x1, 0)) { uint64_t carry = vli_add(x1, x1, curve_p); vli_rshift1(x1); x1[NUM_ECC_DIGITS - 1] |= carry << 63; } else { vli_rshift1(x1); } /* t1 = 3/2*(x1^2 - z1^4) = B */ vli_mod_square_fast(z1, x1); /* t3 = B^2 */ vli_mod_sub(z1, z1, t5, curve_p); /* t3 = B^2 - A */ vli_mod_sub(z1, z1, t5, curve_p); /* t3 = B^2 - 2A = x3 */ vli_mod_sub(t5, t5, z1, curve_p); /* t5 = A - x3 */ vli_mod_mult_fast(x1, x1, t5); /* t1 = B * (A - x3) */ vli_mod_sub(t4, x1, t4, curve_p); /* t4 = B * (A - x3) - y1^4 = y3 */ vli_set(x1, z1); vli_set(z1, y1); vli_set(y1, t4); } /* Modify (x1, y1) => (x1 * z^2, y1 * z^3) */ static void apply_z(uint64_t *x1, uint64_t *y1, uint64_t *z) { uint64_t t1[NUM_ECC_DIGITS]; vli_mod_square_fast(t1, z); /* z^2 */ vli_mod_mult_fast(x1, x1, t1); /* x1 * z^2 */ vli_mod_mult_fast(t1, t1, z); /* z^3 */ vli_mod_mult_fast(y1, y1, t1); /* y1 * z^3 */ } /* P = (x1, y1) => 2P, (x2, y2) => P' */ static void xycz_initial_double(uint64_t *x1, uint64_t *y1, uint64_t *x2, uint64_t *y2, uint64_t *p_initial_z) { uint64_t z[NUM_ECC_DIGITS]; vli_set(x2, x1); vli_set(y2, y1); vli_clear(z); z[0] = 1; if (p_initial_z) vli_set(z, p_initial_z); apply_z(x1, y1, z); ecc_point_double_jacobian(x1, y1, z); apply_z(x2, y2, z); } /* Input P = (x1, y1, Z), Q = (x2, y2, Z) * Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3) * or P => P', Q => P + Q */ static void xycz_add(uint64_t *x1, uint64_t *y1, uint64_t *x2, uint64_t *y2) { /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ uint64_t t5[NUM_ECC_DIGITS]; vli_mod_sub(t5, x2, x1, curve_p); /* t5 = x2 - x1 */ vli_mod_square_fast(t5, t5); /* t5 = (x2 - x1)^2 = A */ vli_mod_mult_fast(x1, x1, t5); /* t1 = x1*A = B */ vli_mod_mult_fast(x2, x2, t5); /* t3 = x2*A = C */ vli_mod_sub(y2, y2, y1, curve_p); /* t4 = y2 - y1 */ vli_mod_square_fast(t5, y2); /* t5 = (y2 - y1)^2 = D */ vli_mod_sub(t5, t5, x1, curve_p); /* t5 = D - B */ vli_mod_sub(t5, t5, x2, curve_p); /* t5 = D - B - C = x3 */ vli_mod_sub(x2, x2, x1, curve_p); /* t3 = C - B */ vli_mod_mult_fast(y1, y1, x2); /* t2 = y1*(C - B) */ vli_mod_sub(x2, x1, t5, curve_p); /* t3 = B - x3 */ vli_mod_mult_fast(y2, y2, x2); /* t4 = (y2 - y1)*(B - x3) */ vli_mod_sub(y2, y2, y1, curve_p); /* t4 = y3 */ vli_set(x2, t5); } /* Input P = (x1, y1, Z), Q = (x2, y2, Z) * Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3) * or P => P - Q, Q => P + Q */ static void xycz_add_c(uint64_t *x1, uint64_t *y1, uint64_t *x2, uint64_t *y2) { /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ uint64_t t5[NUM_ECC_DIGITS]; uint64_t t6[NUM_ECC_DIGITS]; uint64_t t7[NUM_ECC_DIGITS]; vli_mod_sub(t5, x2, x1, curve_p); /* t5 = x2 - x1 */ vli_mod_square_fast(t5, t5); /* t5 = (x2 - x1)^2 = A */ vli_mod_mult_fast(x1, x1, t5); /* t1 = x1*A = B */ vli_mod_mult_fast(x2, x2, t5); /* t3 = x2*A = C */ vli_mod_add(t5, y2, y1, curve_p); /* t4 = y2 + y1 */ vli_mod_sub(y2, y2, y1, curve_p); /* t4 = y2 - y1 */ vli_mod_sub(t6, x2, x1, curve_p); /* t6 = C - B */ vli_mod_mult_fast(y1, y1, t6); /* t2 = y1 * (C - B) */ vli_mod_add(t6, x1, x2, curve_p); /* t6 = B + C */ vli_mod_square_fast(x2, y2); /* t3 = (y2 - y1)^2 */ vli_mod_sub(x2, x2, t6, curve_p); /* t3 = x3 */ vli_mod_sub(t7, x1, x2, curve_p); /* t7 = B - x3 */ vli_mod_mult_fast(y2, y2, t7); /* t4 = (y2 - y1)*(B - x3) */ vli_mod_sub(y2, y2, y1, curve_p); /* t4 = y3 */ vli_mod_square_fast(t7, t5); /* t7 = (y2 + y1)^2 = F */ vli_mod_sub(t7, t7, t6, curve_p); /* t7 = x3' */ vli_mod_sub(t6, t7, x1, curve_p); /* t6 = x3' - B */ vli_mod_mult_fast(t6, t6, t5); /* t6 = (y2 + y1)*(x3' - B) */ vli_mod_sub(y1, t6, y1, curve_p); /* t2 = y3' */ vli_set(x1, t7); } static void ecc_point_mult(struct ecc_point *result, const struct ecc_point *point, uint64_t *scalar, uint64_t *initial_z, int num_bits) { /* R0 and R1 */ uint64_t rx[2][NUM_ECC_DIGITS]; uint64_t ry[2][NUM_ECC_DIGITS]; uint64_t z[NUM_ECC_DIGITS]; int i, nb; vli_set(rx[1], point->x); vli_set(ry[1], point->y); xycz_initial_double(rx[1], ry[1], rx[0], ry[0], initial_z); for (i = num_bits - 2; i > 0; i--) { nb = !vli_test_bit(scalar, i); xycz_add_c(rx[1 - nb], ry[1 - nb], rx[nb], ry[nb]); xycz_add(rx[nb], ry[nb], rx[1 - nb], ry[1 - nb]); } nb = !vli_test_bit(scalar, 0); xycz_add_c(rx[1 - nb], ry[1 - nb], rx[nb], ry[nb]); /* Find final 1/Z value. */ vli_mod_sub(z, rx[1], rx[0], curve_p); /* X1 - X0 */ vli_mod_mult_fast(z, z, ry[1 - nb]); /* Yb * (X1 - X0) */ vli_mod_mult_fast(z, z, point->x); /* xP * Yb * (X1 - X0) */ vli_mod_inv(z, z, curve_p); /* 1 / (xP * Yb * (X1 - X0)) */ vli_mod_mult_fast(z, z, point->y); /* yP / (xP * Yb * (X1 - X0)) */ vli_mod_mult_fast(z, z, rx[1 - nb]); /* Xb * yP / (xP * Yb * (X1 - X0)) */ /* End 1/Z calculation */ xycz_add(rx[nb], ry[nb], rx[1 - nb], ry[1 - nb]); apply_z(rx[0], ry[0], z); vli_set(result->x, rx[0]); vli_set(result->y, ry[0]); } static bool ecc_valid_point(const struct ecc_point *point) { uint64_t tmp1[NUM_ECC_DIGITS]; uint64_t tmp2[NUM_ECC_DIGITS]; uint64_t _3[NUM_ECC_DIGITS] = { 3 }; /* -a = 3 */ /* The point at infinity is invalid. */ if (ecc_point_is_zero(point)) return false; /* x and y must be smaller than p. */ if (vli_cmp(curve_p, point->x) != 1 || vli_cmp(curve_p, point->y) != 1) return false; /* Computes result = y^2. */ vli_mod_square_fast(tmp1, point->y); /* Computes result = x^3 + ax + b. result must not overlap x. */ vli_mod_square_fast(tmp2, point->x); /* r = x^2 */ vli_mod_sub(tmp2, tmp2, _3, curve_p); /* r = x^2 - 3 */ vli_mod_mult_fast(tmp2, tmp2, point->x); /* r = x^3 - 3x */ vli_mod_add(tmp2, tmp2, curve_b, curve_p); /* r = x^3 - 3x + b */ /* Make sure that y^2 == x^3 + ax + b */ return vli_equal(tmp1, tmp2); } /* Little endian byte-array to native conversion */ static void ecc_bytes2native(const uint8_t bytes[ECC_BYTES], uint64_t native[NUM_ECC_DIGITS]) { int i; for (i = 0; i < NUM_ECC_DIGITS; i++) { const uint8_t *digit = bytes + 8 * (NUM_ECC_DIGITS - 1 - i); native[NUM_ECC_DIGITS - 1 - i] = ((uint64_t) digit[0] << 0) | ((uint64_t) digit[1] << 8) | ((uint64_t) digit[2] << 16) | ((uint64_t) digit[3] << 24) | ((uint64_t) digit[4] << 32) | ((uint64_t) digit[5] << 40) | ((uint64_t) digit[6] << 48) | ((uint64_t) digit[7] << 56); } } /* Native to little endian byte-array conversion */ static void ecc_native2bytes(const uint64_t native[NUM_ECC_DIGITS], uint8_t bytes[ECC_BYTES]) { int i; for (i = 0; i < NUM_ECC_DIGITS; i++) { uint8_t *digit = bytes + 8 * (NUM_ECC_DIGITS - 1 - i); digit[0] = native[NUM_ECC_DIGITS - 1 - i] >> 0; digit[1] = native[NUM_ECC_DIGITS - 1 - i] >> 8; digit[2] = native[NUM_ECC_DIGITS - 1 - i] >> 16; digit[3] = native[NUM_ECC_DIGITS - 1 - i] >> 24; digit[4] = native[NUM_ECC_DIGITS - 1 - i] >> 32; digit[5] = native[NUM_ECC_DIGITS - 1 - i] >> 40; digit[6] = native[NUM_ECC_DIGITS - 1 - i] >> 48; digit[7] = native[NUM_ECC_DIGITS - 1 - i] >> 56; } } bool ecc_make_public_key(const uint8_t private_key[32], uint8_t public_key[64]) { struct ecc_point pk; uint64_t priv[NUM_ECC_DIGITS]; ecc_bytes2native(private_key, priv); if (vli_is_zero(priv)) return false; /* Make sure the private key is in the range [1, n-1]. */ if (vli_cmp(curve_n, priv) != 1) return false; ecc_point_mult(&pk, &curve_g, priv, NULL, vli_num_bits(priv)); if (ecc_point_is_zero(&pk)) return false; ecc_native2bytes(pk.x, public_key); ecc_native2bytes(pk.y, &public_key[32]); return true; } bool ecc_make_key(uint8_t public_key[64], uint8_t private_key[32]) { struct ecc_point pk; uint64_t priv[NUM_ECC_DIGITS]; unsigned int tries = 0; memset(&pk, 0, sizeof(pk)); do { if (!get_random_number(priv) || (tries++ >= MAX_TRIES)) return false; if (vli_is_zero(priv)) continue; /* Make sure the private key is in the range [1, n-1]. */ if (vli_cmp(curve_n, priv) != 1) continue; ecc_point_mult(&pk, &curve_g, priv, NULL, vli_num_bits(priv)); } while (ecc_point_is_zero(&pk)); ecc_native2bytes(priv, private_key); ecc_native2bytes(pk.x, public_key); ecc_native2bytes(pk.y, &public_key[32]); return true; } bool ecc_valid_public_key(const uint8_t public_key[64]) { struct ecc_point pk; ecc_bytes2native(public_key, pk.x); ecc_bytes2native(&public_key[32], pk.y); return ecc_valid_point(&pk); } bool ecdh_shared_secret(const uint8_t public_key[64], const uint8_t private_key[32], uint8_t secret[32]) { uint64_t priv[NUM_ECC_DIGITS]; uint64_t rand[NUM_ECC_DIGITS]; struct ecc_point product, pk; if (!get_random_number(rand)) return false; ecc_bytes2native(public_key, pk.x); ecc_bytes2native(&public_key[32], pk.y); if (!ecc_valid_point(&pk)) return false; ecc_bytes2native(private_key, priv); ecc_point_mult(&product, &pk, priv, rand, vli_num_bits(priv)); ecc_native2bytes(product.x, secret); return !ecc_point_is_zero(&product); } bluez-5.87/src/shared/PaxHeaders/btp.h0000644000000000000000000000005015221752714014654 xustar0020 atime=1783092685 20 ctime=1783094102 bluez-5.87/src/shared/btp.h0000644000000000000000000002450015221752714014336 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * */ #include #ifndef __packed #define __packed __attribute__((packed)) #endif #define BTP_INDEX_NON_CONTROLLER 0xff #define BTP_ERROR_FAIL 0x01 #define BTP_ERROR_UNKNOWN_CMD 0x02 #define BTP_ERROR_NOT_READY 0x03 #define BTP_ERROR_INVALID_INDEX 0x04 #define BTP_CORE_SERVICE 0 #define BTP_GAP_SERVICE 1 #define BTP_GATT_SERVICE 2 #define BTP_L2CAP_SERVICE 3 #define BTP_MESH_NODE_SERVICE 4 #define BTP_BAP_SERVICE 14 struct btp_hdr { uint8_t service; uint8_t opcode; uint8_t index; uint16_t data_len; uint8_t data[0]; } __packed; struct btp_error { uint8_t status; } __packed; #define BTP_OP_ERROR 0x00 #define BTP_OP_CORE_READ_SUPPORTED_COMMANDS 0x01 #define BTP_OP_CORE_READ_SUPPORTED_SERVICES 0x02 #define BTP_OP_CORE_REGISTER 0x03 struct btp_core_register_cp { uint8_t service_id; } __packed; #define BTP_OP_CORE_UNREGISTER 0x04 struct btp_core_unregister_cp { uint8_t service_id; } __packed; #define BTP_EV_CORE_READY 0x80 #define BTP_OP_GAP_READ_SUPPORTED_COMMANDS 0x01 #define BTP_OP_GAP_READ_CONTROLLER_INDEX_LIST 0x02 struct btp_gap_read_index_rp { uint8_t num; uint8_t indexes[0]; } __packed; #define BTP_GAP_SETTING_POWERED 0x00000001 #define BTP_GAP_SETTING_CONNECTABLE 0x00000002 #define BTP_GAP_SETTING_FAST_CONNECTABLE 0x00000004 #define BTP_GAP_SETTING_DISCOVERABLE 0x00000008 #define BTP_GAP_SETTING_BONDABLE 0x00000010 #define BTP_GAP_SETTING_LL_SECURITY 0x00000020 #define BTP_GAP_SETTING_SSP 0x00000040 #define BTP_GAP_SETTING_BREDR 0x00000080 #define BTP_GAP_SETTING_HS 0x00000100 #define BTP_GAP_SETTING_LE 0x00000200 #define BTP_GAP_SETTING_ADVERTISING 0x00000400 #define BTP_GAP_SETTING_SC 0x00000800 #define BTP_GAP_SETTING_DEBUG_KEYS 0x00001000 #define BTP_GAP_SETTING_PRIVACY 0x00002000 #define BTP_GAP_SETTING_CONTROLLER_CONF 0x00004000 #define BTP_GAP_SETTING_STATIC_ADDRESS 0x00008000 #define BTP_GAP_SETTINGS_SC_ONLY 0x00010000 #define BTP_GAP_SETTINGS_EXTENDED_ADVERTISING 0x00020000 #define BTP_OP_GAP_READ_CONTROLLER_INFO 0x03 struct btp_gap_read_info_rp { bdaddr_t address; uint32_t supported_settings; uint32_t current_settings; uint8_t cod[3]; uint8_t name[249]; uint8_t short_name[11]; } __packed; #define BTP_OP_GAP_RESET 0x04 struct btp_gap_reset_rp { uint32_t current_settings; } __packed; #define BTP_OP_GAP_SET_POWERED 0x05 struct btp_gap_set_powered_cp { uint8_t powered; } __packed; struct btp_gap_set_powered_rp { uint32_t current_settings; } __packed; #define BTP_OP_GAP_SET_CONNECTABLE 0x06 struct btp_gap_set_connectable_cp { uint8_t connectable; } __packed; struct btp_gap_set_connectable_rp { uint32_t current_settings; } __packed; #define BTP_OP_GAP_SET_FAST_CONNECTABLE 0x07 struct btp_gap_set_fast_connectable_cp { uint8_t fast_connectable; } __packed; struct btp_gap_set_fast_connectable_rp { uint32_t current_settings; } __packed; #define BTP_OP_GAP_SET_DISCOVERABLE 0x08 struct btp_gap_set_discoverable_cp { uint8_t discoverable; } __packed; struct btp_gap_set_discoverable_rp { uint32_t current_settings; } __packed; #define BTP_OP_GAP_SET_BONDABLE 0x09 struct btp_gap_set_bondable_cp { uint8_t bondable; } __packed; struct btp_gap_set_bondable_rp { uint32_t current_settings; } __packed; #define BTP_OP_GAP_START_ADVERTISING 0x0a struct btp_gap_start_adv_cp { uint8_t adv_data_len; uint8_t scan_rsp_len; uint8_t data[0]; } __packed; struct btp_gap_start_adv_rp { uint32_t current_settings; } __packed; #define BTP_OP_GAP_STOP_ADVERTISING 0x0b struct btp_gap_stop_adv_rp { uint32_t current_settings; } __packed; #define BTP_GAP_DISCOVERY_FLAG_LE 0x01 #define BTP_GAP_DISCOVERY_FLAG_BREDR 0x02 #define BTP_GAP_DISCOVERY_FLAG_LIMITED 0x04 #define BTP_GAP_DISCOVERY_FLAG_ACTIVE 0x08 #define BTP_GAP_DISCOVERY_FLAG_OBSERVATION 0x10 #define BTP_OP_GAP_START_DISCOVERY 0x0c struct btp_gap_start_discovery_cp { uint8_t flags; } __packed; #define BTP_OP_GAP_STOP_DISCOVERY 0x0d #define BTP_GAP_ADDR_PUBLIC 0x00 #define BTP_GAP_ADDR_RANDOM 0x01 #define BTP_OP_GAP_CONNECT 0x0e struct btp_gap_connect_cp { uint8_t address_type; bdaddr_t address; } __packed; #define BTP_OP_GAP_DISCONNECT 0x0f struct btp_gap_disconnect_cp { uint8_t address_type; bdaddr_t address; } __packed; #define BTP_GAP_IOCAPA_DISPLAY_ONLY 0x00 #define BTP_GAP_IOCAPA_DISPLAY_YESNO 0x01 #define BTP_GAP_IOCAPA_KEYBOARD_ONLY 0x02 #define BTP_GAP_IOCAPA_NO_INPUT_NO_OUTPUT 0x03 #define BTP_GAP_IOCAPA_KEYBOARD_DISPLAY 0x04 #define BTP_OP_GAP_SET_IO_CAPA 0x10 struct btp_gap_set_io_capa_cp { uint8_t capa; } __packed; #define BTP_OP_GAP_PAIR 0x11 struct btp_gap_pair_cp { uint8_t address_type; bdaddr_t address; } __packed; #define BTP_OP_GAP_UNPAIR 0x12 struct btp_gap_unpair_cp { uint8_t address_type; bdaddr_t address; } __packed; #define BTP_OP_GAP_PASSKEY_ENTRY_RSP 0x13 struct btp_gap_passkey_entry_rsp_cp { uint8_t address_type; bdaddr_t address; uint32_t passkey; } __packed; #define BTP_OP_GAP_PASSKEY_CONFIRM_RSP 0x14 struct btp_gap_passkey_confirm_rsp_cp { uint8_t address_type; bdaddr_t address; uint8_t match; } __packed; #define BTP_OP_GAP_SET_EXTENDED_ADVERTISING 0x21 struct btp_gap_set_extended_advertising_cp { uint8_t settings; } __packed; #define BTP_EV_GAP_NEW_SETTINGS 0x80 struct btp_new_settings_ev { uint32_t current_settings; } __packed; #define BTP_EV_GAP_DEVICE_FOUND_FLAG_RSSI 0x01 #define BTP_EV_GAP_DEVICE_FOUND_FLAG_AD 0x02 #define BTP_EV_GAP_DEVICE_FOUND_FLAG_SR 0x04 #define BTP_EV_GAP_DEVICE_FOUND 0x81 struct btp_device_found_ev { uint8_t address_type; bdaddr_t address; int8_t rssi; uint8_t flags; uint16_t eir_len; uint8_t eir[0]; } __packed; #define BTP_EV_GAP_DEVICE_CONNECTED 0x82 struct btp_gap_device_connected_ev { uint8_t address_type; bdaddr_t address; uint16_t connection_interval; uint16_t connection_latency; uint16_t supervision_timeout; } __packed; #define BTP_EV_GAP_DEVICE_DISCONNECTED 0x83 struct btp_gap_device_disconnected_ev { uint8_t address_type; bdaddr_t address; } __packed; #define BTP_EV_GAP_PASSKEY_DISPLAY 0x84 struct btp_gap_passkey_display_ev { uint8_t address_type; bdaddr_t address; uint32_t passkey; } __packed; #define BTP_EV_GAP_PASSKEY_REQUEST 0x85 struct btp_gap_passkey_req_ev { uint8_t address_type; bdaddr_t address; } __packed; #define BTP_EV_GAP_PASSKEY_CONFIRM 0x86 struct btp_gap_passkey_confirm_ev { uint8_t address_type; bdaddr_t address; uint32_t passkey; } __packed; #define BTP_EV_GAP_IDENTITY_RESOLVED 0x87 struct btp_gap_identity_resolved_ev { uint8_t address_type; bdaddr_t address; uint8_t identity_address_type; bdaddr_t identity_address; } __packed; struct btp_gatt_service { uint16_t start_handle; uint16_t end_handle; uint8_t uuid_length; uint8_t uuid[]; } __packed; struct btp_gatt_characteristic { uint16_t characteristic_handle; uint16_t value_handle; uint8_t properties; uint8_t uuid_length; uint8_t uuid[]; } __packed; struct btp_gatt_descriptor { uint16_t descriptor_handle; uint8_t uuid_length; uint8_t uuid[]; } __packed; struct btp_gatt_char_value { uint16_t handle; uint8_t data_len; uint8_t data[]; } __packed; #define BTP_OP_GATT_READ_SUPPORTED_COMMANDS 0x01 #define BTP_OP_GATT_DISC_PRIM_UUID 0x0c struct btp_gatt_disc_prim_uuid_cp { uint8_t address_type; bdaddr_t address; uint8_t uuid_length; uint8_t uuid[]; } __packed; struct btp_gatt_disc_prim_rp { uint8_t services_count; struct btp_gatt_service services[]; } __packed; struct btp_gatt_disc_chrc_rp { uint8_t characteristics_count; struct btp_gatt_characteristic characteristics[]; } __packed; #define BTP_OP_GATT_DISC_CHRC_UUID 0x0f struct btp_gatt_disc_chrc_uuid_cp { uint8_t address_type; bdaddr_t address; uint16_t start_handle; uint16_t end_handle; uint8_t uuid_length; uint8_t uuid[]; } __packed; #define BTP_OP_GATT_DISC_ALL_DESC 0x10 struct btp_gatt_disc_all_desc_cp { uint8_t address_type; bdaddr_t address; uint16_t start_handle; uint16_t end_handle; } __packed; struct btp_gatt_disc_all_desc_rp { uint8_t descriptors_count; struct btp_gatt_descriptor descriptors[]; } __packed; #define BTP_OP_GATT_READ 0x11 struct btp_gatt_read_cp { uint8_t address_type; bdaddr_t address; uint16_t handle; } __packed; struct btp_gatt_read_rp { uint8_t response; uint16_t len; uint8_t data[]; } __packed; #define BTP_OP_GATT_READ_UUID 0x12 struct btp_gatt_read_uuid_cp { uint8_t address_type; bdaddr_t address; uint16_t start_handle; uint16_t end_handle; uint8_t uuid_len; uint8_t uuid[]; } __packed; struct btp_gatt_read_uuid_rp { uint8_t att_response; uint8_t values_count; struct btp_gatt_char_value values[]; } __packed; #define BTP_OP_GATT_WRITE_WITHOUT_RSP 0x15 struct btp_gatt_write_without_rsp_cp { uint8_t address_type; bdaddr_t address; uint16_t handle; uint16_t len; uint8_t data[]; } __packed; #define BTP_OP_GATT_WRITE 0x17 struct btp_gatt_write_cp { uint8_t address_type; bdaddr_t address; uint16_t handle; uint16_t len; uint8_t data[]; } __packed; struct btp_gatt_write_rp { uint8_t att_response; } __packed; #define BTP_OP_BAP_READ_SUPPORTED_COMMANDS 0x01 #define BTP_OP_BAP_DISCOVER 0x02 struct btp_bap_discover_cp { uint8_t address_type; bdaddr_t address; } __packed; #define BTP_EV_BAP_DISCOVERY_COMPLETED 0x80 struct btp_bap_discovery_completed_ev { uint8_t address_type; bdaddr_t address; uint8_t status; } __packed; struct btp; typedef void (*btp_destroy_func_t)(void *user_data); typedef void (*btp_disconnect_func_t)(struct btp *btp, void *user_data); typedef void (*btp_cmd_func_t)(uint8_t index, const void *param, uint16_t length, void *user_data); struct btp *btp_new(const char *path); void btp_cleanup(struct btp *btp); bool btp_set_disconnect_handler(struct btp *btp, btp_disconnect_func_t callback, void *user_data, btp_destroy_func_t destroy); bool btp_send_error(struct btp *btp, uint8_t service, uint8_t index, uint8_t status); bool btp_send(struct btp *btp, uint8_t service, uint8_t opcode, uint8_t index, uint16_t length, const void *param); unsigned int btp_register(struct btp *btp, uint8_t service, uint8_t opcode, btp_cmd_func_t callback, void *user_data, btp_destroy_func_t destroy); bool btp_unregister(struct btp *btp, unsigned int id); void btp_unregister_service(struct btp *btp, uint8_t service); bluez-5.87/src/shared/PaxHeaders/gmap.c0000644000000000000000000000005015142177620015005 xustar0020 atime=1783092966 20 ctime=1783094100 bluez-5.87/src/shared/gmap.c0000644000000000000000000002304615142177620014473 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2025 Pauli Virtanen. All rights reserved. * */ #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "src/shared/queue.h" #include "src/shared/util.h" #include "src/shared/timeout.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-server.h" #include "src/shared/gatt-client.h" #include "src/shared/gmap.h" #define DBG(_gmap, fmt, arg...) \ gmap_debug(_gmap, "%s:%s() " fmt, __FILE__, __func__, ## arg) struct bt_gmas_attr { struct bt_gmap *gmap; const char *name; struct gatt_db_attribute *attr; uint8_t value; }; struct bt_gmas_db { struct gatt_db *db; struct gatt_db_attribute *service; struct bt_gmas_attr role; struct bt_gmas_attr ugg; struct bt_gmas_attr ugt; struct bt_gmas_attr bgs; struct bt_gmas_attr bgr; }; struct bt_gmap { int ref_count; struct bt_gatt_client *client; struct bt_gmas_db db; int idle_id; bt_gmap_ready_func_t ready_func; void *ready_data; bt_gmap_debug_func_t debug_func; bt_gmap_destroy_func_t debug_destroy; void *debug_data; }; static struct queue *instances; static void gmap_free(void *data) { struct bt_gmap *gmap = data; if (gmap->client) { bt_gatt_client_idle_unregister(gmap->client, gmap->idle_id); bt_gatt_client_unref(gmap->client); } else { gatt_db_remove_service(gmap->db.db, gmap->db.service); gatt_db_unref(gmap->db.db); } queue_remove(instances, gmap); if (queue_isempty(instances)) { queue_destroy(instances, NULL); instances = NULL; } free(gmap); } struct bt_gmap *bt_gmap_ref(struct bt_gmap *gmap) { if (!gmap) return NULL; __sync_fetch_and_add(&gmap->ref_count, 1); return gmap; } void bt_gmap_unref(struct bt_gmap *gmap) { if (!gmap) return; if (__sync_sub_and_fetch(&gmap->ref_count, 1)) return; gmap_free(gmap); } static void gmap_debug(struct bt_gmap *gmap, const char *format, ...) { va_list ap; if (!gmap || !format || !gmap->debug_func) return; va_start(ap, format); util_debug_va(gmap->debug_func, gmap->debug_data, format, ap); va_end(ap); } bool bt_gmap_set_debug(struct bt_gmap *gmap, bt_gmap_debug_func_t cb, void *user_data, bt_gmap_destroy_func_t destroy) { if (!gmap) return false; if (gmap->debug_destroy) gmap->debug_destroy(gmap->debug_data); gmap->debug_func = cb; gmap->debug_destroy = destroy; gmap->debug_data = user_data; return true; } uint8_t bt_gmap_get_role(struct bt_gmap *gmap) { if (!gmap) return 0; return gmap->db.role.value & BT_GMAP_ROLE_MASK; } uint32_t bt_gmap_get_features(struct bt_gmap *gmap) { if (!gmap) return 0; return (((uint32_t)gmap->db.ugg.value << BT_GMAP_UGG_FEATURE_SHIFT) | ((uint32_t)gmap->db.ugt.value << BT_GMAP_UGT_FEATURE_SHIFT) | ((uint32_t)gmap->db.bgs.value << BT_GMAP_BGS_FEATURE_SHIFT) | ((uint32_t)gmap->db.bgr.value << BT_GMAP_BGR_FEATURE_SHIFT)) & BT_GMAP_FEATURE_MASK; } /* * GMA Client */ static void gmap_attr_read(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_gmas_attr *attr = user_data; struct bt_gmap *gmap = attr->gmap; struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; uint8_t v; if (!success) { DBG(gmap, "Unable to read %s: error 0x%02x", attr->name, att_ecode); return; } if (!util_iov_pull_u8(&iov, &v)) { DBG(gmap, "Invalid %s", attr->name); return; } DBG(gmap, "%s Value 0x%x", attr->name, v); attr->value = v; } static void foreach_gmap_char(struct gatt_db_attribute *attr, void *user_data) { struct bt_gmap *gmap = user_data; uint16_t value_handle; bt_uuid_t uuid, uuid_attr; struct { const uint32_t uuid; struct bt_gmas_attr *attr; const char *name; } attrs[] = { { GMAP_ROLE_CHRC_UUID, &gmap->db.role, "Role" }, { GMAP_UGG_CHRC_UUID, &gmap->db.ugg, "UGG Features" }, { GMAP_UGT_CHRC_UUID, &gmap->db.ugt, "UGT Features" }, { GMAP_BGS_CHRC_UUID, &gmap->db.bgs, "BGS Features" }, { GMAP_BGR_CHRC_UUID, &gmap->db.bgr, "BGR Features" }, }; unsigned int i; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) return; for (i = 0; i < ARRAY_SIZE(attrs); ++i) { bt_uuid16_create(&uuid_attr, attrs[i].uuid); if (bt_uuid_cmp(&uuid, &uuid_attr)) continue; attrs[i].attr->gmap = gmap; attrs[i].attr->name = attrs[i].name; DBG(gmap, "GMAS %s Char found: handle 0x%04x", attrs[i].name, value_handle); bt_gatt_client_read_value(gmap->client, value_handle, gmap_attr_read, attrs[i].attr, NULL); return; } } static void foreach_gmap_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_gmap *gmap = user_data; gatt_db_service_set_claimed(attr, true); gatt_db_service_foreach_char(attr, foreach_gmap_char, gmap); } static void gmap_idle(void *data) { struct bt_gmap *gmap = data; gmap->idle_id = 0; if (!instances) instances = queue_new(); queue_push_tail(instances, gmap); if (gmap->ready_func) gmap->ready_func(gmap, gmap->ready_data); } struct bt_gmap *bt_gmap_attach(struct bt_gatt_client *client, bt_gmap_ready_func_t ready, void *user_data) { struct bt_gmap *gmap; bt_uuid_t uuid; if (!client) return NULL; client = bt_gatt_client_clone(client); if (!client) return NULL; gmap = new0(struct bt_gmap, 1); gmap->client = client; gmap->ready_func = ready; gmap->ready_data = user_data; gmap->db.db = bt_gatt_client_get_db(gmap->client); bt_uuid16_create(&uuid, GMAS_UUID); gatt_db_foreach_service(gmap->db.db, &uuid, foreach_gmap_service, gmap); gmap->idle_id = bt_gatt_client_idle_register(gmap->client, gmap_idle, gmap, NULL); return bt_gmap_ref(gmap); } /* * GMAS Service */ static void gmas_attr_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_gmas_attr *attr = user_data; struct iovec iov = { .iov_base = &attr->value, .iov_len = sizeof(attr->value) }; gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, iov.iov_len); } static bool match_db(const void *data, const void *match_data) { const struct bt_gmap *gmap = data; return gmap->db.db == match_data; } struct bt_gmap *bt_gmap_find(struct gatt_db *db) { return db ? queue_find(instances, match_db, db) : NULL; } static void gmap_update_chrc(struct bt_gmap *gmap) { struct { const uint32_t uuid; uint8_t role; struct bt_gmas_attr *attr; const char *name; } attrs[] = { { GMAP_ROLE_CHRC_UUID, 0, &gmap->db.role, "Role" }, { GMAP_UGG_CHRC_UUID, BT_GMAP_ROLE_UGG, &gmap->db.ugg, "UGG Features" }, { GMAP_UGT_CHRC_UUID, BT_GMAP_ROLE_UGT, &gmap->db.ugt, "UGT Features" }, { GMAP_BGS_CHRC_UUID, BT_GMAP_ROLE_BGS, &gmap->db.bgs, "BGS Features" }, { GMAP_BGR_CHRC_UUID, BT_GMAP_ROLE_BGR, &gmap->db.bgr, "BGR Features" }, }; unsigned int i; bt_uuid_t uuid; for (i = 0; i < ARRAY_SIZE(attrs); ++i) { if (attrs[i].attr->attr) continue; attrs[i].attr->gmap = gmap; attrs[i].attr->name = attrs[i].name; if (attrs[i].role && !(gmap->db.role.value & attrs[i].role)) continue; bt_uuid16_create(&uuid, attrs[i].uuid); attrs[i].attr->attr = gatt_db_service_add_characteristic( gmap->db.service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, gmas_attr_read, NULL, attrs[i].attr); gatt_db_attribute_set_fixed_length(attrs[i].attr->attr, 1); } } static void gmap_init_service(struct bt_gmas_db *db) { bt_uuid_t uuid; if (db->service) { gatt_db_remove_service(db->db, db->service); db->service = NULL; db->role.attr = NULL; db->ugg.attr = NULL; db->ugt.attr = NULL; db->bgs.attr = NULL; db->bgr.attr = NULL; } bt_uuid16_create(&uuid, GMAS_UUID); db->service = gatt_db_add_service(db->db, &uuid, true, 5*2 + 1); } struct bt_gmap *bt_gmap_add_db(struct gatt_db *db) { struct bt_gmap *gmap; if (!db || queue_find(instances, match_db, db)) return NULL; gmap = new0(struct bt_gmap, 1); gmap->db.db = gatt_db_ref(db); gmap_init_service(&gmap->db); if (!instances) instances = queue_new(); queue_push_tail(instances, gmap); return bt_gmap_ref(gmap); } void bt_gmap_set_role(struct bt_gmap *gmap, uint8_t role) { if (!gmap || gmap->client) return; role &= BT_GMAP_ROLE_MASK; if (role == gmap->db.role.value) return; DBG(gmap, "set role 0x%02x", role); gmap->db.role.value = role; /* Removing role must remove feature chrc too; reset svc if needed */ if (role && ((!(role & BT_GMAP_ROLE_UGG) && gmap->db.ugg.attr) || (!(role & BT_GMAP_ROLE_UGT) && gmap->db.ugt.attr) || (!(role & BT_GMAP_ROLE_BGS) && gmap->db.bgs.attr) || (!(role & BT_GMAP_ROLE_BGR) && gmap->db.bgr.attr))) gmap_init_service(&gmap->db); gmap_update_chrc(gmap); /* Expose values only when first set and active */ gatt_db_service_set_active(gmap->db.service, role != 0); } void bt_gmap_set_features(struct bt_gmap *gmap, uint32_t features) { if (!gmap || gmap->client) return; features &= BT_GMAP_FEATURE_MASK; if (features == bt_gmap_get_features(gmap)) return; DBG(gmap, "set features 0x%08x", features); gmap->db.ugg.value = (features & BT_GMAP_UGG_FEATURE_MASK) >> BT_GMAP_UGG_FEATURE_SHIFT; gmap->db.ugt.value = (features & BT_GMAP_UGT_FEATURE_MASK) >> BT_GMAP_UGT_FEATURE_SHIFT; gmap->db.bgs.value = (features & BT_GMAP_BGS_FEATURE_MASK) >> BT_GMAP_BGS_FEATURE_SHIFT; gmap->db.bgr.value = (features & BT_GMAP_BGR_FEATURE_MASK) >> BT_GMAP_BGR_FEATURE_SHIFT; } bluez-5.87/src/shared/PaxHeaders/asha.h0000644000000000000000000000005015017252326015000 xustar0020 atime=1783092968 20 ctime=1783094100 bluez-5.87/src/shared/asha.h0000644000000000000000000000301715017252326014462 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2024 Asymptotic Inc. * * Author: Arun Raghavan * * */ # #include #include enum bt_asha_state_t { ASHA_STOPPED = 0, ASHA_STARTING, ASHA_STARTED, }; typedef void (*bt_asha_cb_t)(int status, void *data); typedef void (*bt_asha_attach_cb_t)(void *data); struct bt_asha { struct bt_gatt_client *client; struct gatt_db *db; struct gatt_db_attribute *attr; uint16_t acp_handle; uint16_t volume_handle; unsigned int status_notify_id; uint16_t psm; bool right_side; bool binaural; bool csis_supported; bool coc_streaming_supported; uint8_t hisyncid[8]; uint16_t render_delay; uint16_t codec_ids; int8_t volume; enum bt_asha_state_t state; bt_asha_cb_t state_cb; void *state_cb_data; bt_asha_attach_cb_t attach_cb; void *attach_cb_data; }; struct bt_asha_set { uint8_t hisyncid[8]; struct bt_asha *left; struct bt_asha *right; }; struct bt_asha *bt_asha_new(void); void bt_asha_reset(struct bt_asha *asha); void bt_asha_state_reset(struct bt_asha *asha); void bt_asha_free(struct bt_asha *asha); unsigned int bt_asha_start(struct bt_asha *asha, bt_asha_cb_t cb, void *user_data); unsigned int bt_asha_stop(struct bt_asha *asha); bool bt_asha_set_volume(struct bt_asha *asha, int8_t volume); bool bt_asha_attach(struct bt_asha *asha, struct gatt_db *db, struct bt_gatt_client *client, bt_asha_attach_cb_t probe_cb, void *cb_user_data); bluez-5.87/src/shared/PaxHeaders/uinput.c0000644000000000000000000000005015142177620015405 xustar0020 atime=1783092970 20 ctime=1783094100 bluez-5.87/src/shared/uinput.c0000644000000000000000000001003315142177620015063 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2025 Pauli Virtanen * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "src/shared/util.h" #include "src/shared/uinput.h" #define DBG(uinput, fmt, arg...) \ uinput_debug(uinput->debug_func, uinput->debug_data, "%s:%s() " fmt, \ __FILE__, __func__, ## arg) struct bt_uinput { struct input_id dev_id; char name[UINPUT_MAX_NAME_SIZE]; bdaddr_t addr; int fd; bt_uinput_debug_func_t debug_func; void *debug_data; }; static void uinput_debug(bt_uinput_debug_func_t debug_func, void *debug_data, const char *format, ...) { va_list ap; if (!debug_func || !format) return; va_start(ap, format); util_debug_va(debug_func, debug_data, format, ap); va_end(ap); } static int uinput_emit(struct bt_uinput *uinput, uint16_t type, uint16_t code, int32_t val) { struct input_event ie; memset(&ie, 0, sizeof(ie)); ie.type = type; ie.code = code; ie.value = val; return write(uinput->fd, &ie, sizeof(ie)); } void bt_uinput_send_key(struct bt_uinput *uinput, uint16_t key, bool pressed) { if (!uinput) return; DBG(uinput, "%d", key); uinput_emit(uinput, EV_KEY, key, pressed ? 1 : 0); uinput_emit(uinput, EV_SYN, SYN_REPORT, 0); } struct bt_uinput *bt_uinput_new(const char *name, const char *suffix, const bdaddr_t *addr, const struct input_id *dev_id) { struct bt_uinput *uinput; const size_t name_max = sizeof(uinput->name); uinput = new0(struct bt_uinput, 1); uinput->fd = -1; if (name) snprintf(uinput->name, name_max, "%s", name); if (suffix) { size_t name_len = strlen(uinput->name); size_t suffix_len = strlen(suffix); size_t pos = name_len; if (suffix_len > name_max - 1) suffix_len = name_max - 1; if (pos + suffix_len > name_max - 1) pos = name_max - 1 - suffix_len; snprintf(uinput->name + pos, name_max - pos, "%s", suffix); } if (addr) bacpy(&uinput->addr, addr); if (dev_id) { uinput->dev_id.bustype = dev_id->bustype; uinput->dev_id.product = dev_id->product; uinput->dev_id.vendor = dev_id->vendor; uinput->dev_id.version = dev_id->version; } else { uinput->dev_id.bustype = BUS_BLUETOOTH; } return uinput; } void bt_uinput_set_debug(struct bt_uinput *uinput, bt_uinput_debug_func_t debug_func, void *user_data) { if (!uinput) return; uinput->debug_func = debug_func; uinput->debug_data = user_data; } int bt_uinput_create(struct bt_uinput *uinput, const struct bt_uinput_key_map *key_map) { int fd = -1; struct uinput_user_dev dev; size_t i; int err; char addr[18]; if (!uinput || uinput->fd >= 0) return -EINVAL; fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); if (fd < 0) fd = open("/dev/input/uinput", O_WRONLY | O_NONBLOCK); if (fd < 0) fd = open("/dev/misc/uinput", O_WRONLY | O_NONBLOCK); if (fd < 0) { err = -errno; DBG(uinput, "Failed to open /dev/uinput: %s", strerror(-err)); goto fail; } ioctl(fd, UI_SET_EVBIT, EV_KEY); ioctl(fd, UI_SET_EVBIT, EV_SYN); for (i = 0; key_map[i].name; ++i) ioctl(fd, UI_SET_KEYBIT, key_map[i].uinput); ba2strlc(&uinput->addr, addr); ioctl(fd, UI_SET_PHYS, addr); memset(&dev, 0, sizeof(dev)); dev.id = uinput->dev_id; snprintf(dev.name, sizeof(dev.name), "%s", uinput->name); if (write(fd, &dev, sizeof(dev)) < 0) { err = -errno; DBG(uinput, "Failed to write setup: %s", strerror(-err)); goto fail; } if (ioctl(fd, UI_DEV_CREATE) < 0) { err = -errno; DBG(uinput, "Failed to create device: %s", strerror(-err)); goto fail; } DBG(uinput, "%p", uinput); uinput->fd = fd; return 0; fail: if (fd >= 0) close(fd); return err; } void bt_uinput_destroy(struct bt_uinput *uinput) { if (!uinput) return; DBG(uinput, "%p", uinput); if (uinput->fd >= 0) { ioctl(uinput->fd, UI_DEV_DESTROY); close(uinput->fd); } free(uinput); } bluez-5.87/src/shared/PaxHeaders/io.h0000644000000000000000000000005015017252326014473 xustar0020 atime=1783092799 20 ctime=1783094099 bluez-5.87/src/shared/io.h0000644000000000000000000000225715017252326014162 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #include #include typedef void (*io_destroy_func_t)(void *data); struct io; struct io *io_new(int fd); void io_destroy(struct io *io); int io_get_fd(struct io *io); bool io_set_close_on_destroy(struct io *io, bool do_close); bool io_set_ignore_errqueue(struct io *io, bool do_ignore); ssize_t io_send(struct io *io, const struct iovec *iov, int iovcnt); bool io_shutdown(struct io *io); typedef bool (*io_callback_func_t)(struct io *io, void *user_data); bool io_set_read_handler(struct io *io, io_callback_func_t callback, void *user_data, io_destroy_func_t destroy); bool io_set_write_handler(struct io *io, io_callback_func_t callback, void *user_data, io_destroy_func_t destroy); bool io_set_disconnect_handler(struct io *io, io_callback_func_t callback, void *user_data, io_destroy_func_t destroy); typedef void (*io_glib_err_func_t)(int cond, void *user_data); unsigned int io_glib_add_err_watch(void *giochannel, io_glib_err_func_t func, void *user_data); bluez-5.87/src/shared/PaxHeaders/btp.c0000644000000000000000000000005015063045254014645 xustar0020 atime=1783093498 20 ctime=1783094102 bluez-5.87/src/shared/btp.c0000644000000000000000000001647315063045254014341 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2017 Intel Corporation. All rights reserved. * * */ #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "src/shared/btp.h" #define BTP_MTU 512 struct btp_handler { unsigned int id; uint8_t service; uint8_t opcode; btp_cmd_func_t callback; void *user_data; btp_destroy_func_t destroy; }; struct btp { struct l_io *io; struct l_queue *pending; bool writer_active; bool reader_active; struct l_queue *handlers; unsigned int next_handler; uint8_t buf[BTP_MTU]; btp_disconnect_func_t disconnect_cb; void *disconnect_cb_data; btp_destroy_func_t disconnect_cb_data_destroy; }; static struct l_io *btp_connect(const char *path) { struct sockaddr_un addr; struct l_io *io; int sk; sk = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); if (sk < 0) return NULL; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { close(sk); return NULL; } io = l_io_new(sk); if (!io) { close(sk); return NULL; } l_io_set_close_on_destroy(io, true); return io; } static void disconnect_handler(struct l_io *io, void *user_data) { struct btp *btp = user_data; btp->disconnect_cb(btp, btp->disconnect_cb_data); } static void disconnect_handler_destroy(void *user_data) { struct btp *btp = user_data; if (btp->disconnect_cb_data_destroy) btp->disconnect_cb_data_destroy(btp->disconnect_cb_data); } struct handler_match_data { uint8_t service; uint8_t opcode; }; static bool handler_match(const void *a, const void *b) { const struct btp_handler *handler = a; const struct handler_match_data *match = b; return (handler->service == match->service) && (handler->opcode == match->opcode); } static bool can_read_data(struct l_io *io, void *user_data) { struct handler_match_data match; struct btp *btp = user_data; struct btp_handler *handler; struct btp_hdr *hdr; ssize_t bytes_read; uint16_t data_len; bytes_read = read(l_io_get_fd(btp->io), btp->buf, sizeof(btp->buf)); if (bytes_read < 0) return false; if ((size_t) bytes_read < sizeof(*hdr)) return false; hdr = (void *)btp->buf; data_len = L_LE16_TO_CPU(hdr->data_len); if ((size_t) bytes_read < sizeof(*hdr) + data_len) return false; match.service = hdr->service; match.opcode = hdr->opcode; handler = l_queue_find(btp->handlers, handler_match, &match); if (handler) { handler->callback(hdr->index, hdr->data, data_len, handler->user_data); return false; } /* keep reader active if we sent error reply */ btp_send_error(btp, match.service, hdr->index, BTP_ERROR_UNKNOWN_CMD); return true; } static void read_watch_destroy(void *user_data) { struct btp *btp = user_data; btp->reader_active = false; } static void wakeup_reader(struct btp *btp) { if (btp->reader_active) return; btp->reader_active = l_io_set_read_handler(btp->io, can_read_data, btp, read_watch_destroy); } struct btp *btp_new(const char *path) { struct btp *btp; struct l_io *io; io = btp_connect(path); if (!io) return NULL; btp = l_new(struct btp, 1); btp->pending = l_queue_new(); btp->handlers = l_queue_new(); btp->io = io; btp->next_handler = 1; wakeup_reader(btp); return btp; } struct pending_message { size_t len; void *data; bool wakeup_read; }; static void destroy_message(struct pending_message *msg) { l_free(msg->data); l_free(msg); } void btp_cleanup(struct btp *btp) { if (!btp) return; l_io_destroy(btp->io); l_queue_destroy(btp->pending, (l_queue_destroy_func_t)destroy_message); l_queue_destroy(btp->handlers, (l_queue_destroy_func_t)l_free); l_free(btp); } bool btp_set_disconnect_handler(struct btp *btp, btp_disconnect_func_t callback, void *user_data, btp_destroy_func_t destroy) { if (callback) { if (!l_io_set_disconnect_handler(btp->io, disconnect_handler, btp, disconnect_handler_destroy)) return false; } else { if (!l_io_set_disconnect_handler(btp->io, NULL, NULL, NULL)) return false; } btp->disconnect_cb = callback; btp->disconnect_cb_data = user_data; btp->disconnect_cb_data_destroy = destroy; return true; } static bool can_write_data(struct l_io *io, void *user_data) { struct btp *btp = user_data; struct pending_message *msg; msg = l_queue_pop_head(btp->pending); if (!msg) return false; if (msg->wakeup_read) wakeup_reader(btp); if (write(l_io_get_fd(btp->io), msg->data, msg->len) < 0) { l_error("Failed to send BTP message"); destroy_message(msg); return false; } destroy_message(msg); return !l_queue_isempty(btp->pending); } static void write_watch_destroy(void *user_data) { struct btp *btp = user_data; btp->writer_active = false; } static void wakeup_writer(struct btp *btp) { if (l_queue_isempty(btp->pending)) return; if (btp->writer_active) return; btp->writer_active = l_io_set_write_handler(btp->io, can_write_data, btp, write_watch_destroy); } bool btp_send_error(struct btp *btp, uint8_t service, uint8_t index, uint8_t status) { struct btp_error rsp; rsp.status = status; return btp_send(btp, service, BTP_OP_ERROR, index, sizeof(rsp), &rsp); } bool btp_send(struct btp *btp, uint8_t service, uint8_t opcode, uint8_t index, uint16_t length, const void *param) { struct btp_hdr *hdr; struct pending_message *msg; size_t len; if (!btp) return false; len = sizeof(*hdr) + length; hdr = l_malloc(len); if (!hdr) return false; hdr->service = service; hdr->opcode = opcode; hdr->index = index; hdr->data_len = L_CPU_TO_LE16(length); if (length) memcpy(hdr->data, param, length); msg = l_new(struct pending_message, 1); msg->len = len; msg->data = hdr; msg->wakeup_read = opcode < 0x80; l_queue_push_tail(btp->pending, msg); wakeup_writer(btp); return true; } unsigned int btp_register(struct btp *btp, uint8_t service, uint8_t opcode, btp_cmd_func_t callback, void *user_data, btp_destroy_func_t destroy) { struct btp_handler *handler; handler = l_new(struct btp_handler, 1); handler->id = btp->next_handler++; handler->service = service; handler->opcode = opcode; handler->callback = callback; handler->user_data = user_data; handler->destroy = destroy; l_queue_push_tail(btp->handlers, handler); return handler->id; } static bool handler_match_by_id(const void *a, const void *b) { const struct btp_handler *handler = a; unsigned int id = L_PTR_TO_UINT(b); return handler->id == id; } bool btp_unregister(struct btp *btp, unsigned int id) { struct btp_handler *handler; handler = l_queue_remove_if(btp->handlers, handler_match_by_id, L_UINT_TO_PTR(id)); if (!handler) return false; if (handler->destroy) handler->destroy(handler->user_data); l_free(handler); return true; } static bool handler_remove_by_service(void *a, void *b) { struct btp_handler *handler = a; uint8_t service = L_PTR_TO_UINT(b); if (handler->service != service) return false; if (handler->destroy) handler->destroy(handler->user_data); l_free(handler); return true; } void btp_unregister_service(struct btp *btp, uint8_t service) { l_queue_foreach_remove(btp->handlers, handler_remove_by_service, L_UINT_TO_PTR(service)); } bluez-5.87/src/shared/PaxHeaders/hci-crypto.h0000644000000000000000000000005014015011623016133 xustar0020 atime=1783092893 20 ctime=1783094099 bluez-5.87/src/shared/hci-crypto.h0000644000000000000000000000172314015011623015617 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2013-2014 Intel Corporation * * */ #include #include struct bt_hci; typedef void (*bt_hci_crypto_func_t)(const void *data, uint8_t size, void *user_data); bool bt_hci_crypto_prand(struct bt_hci *hci, bt_hci_crypto_func_t callback, void *user_data); bool bt_hci_crypto_e(struct bt_hci *hci, const uint8_t key[16], const uint8_t plaintext[16], bt_hci_crypto_func_t callback, void *user_data); bool bt_hci_crypto_d1(struct bt_hci *hci, const uint8_t k[16], uint16_t d, uint16_t r, bt_hci_crypto_func_t callback, void *user_data); bool bt_hci_crypto_dm(struct bt_hci *hci, const uint8_t k[16], const uint8_t r[8], bt_hci_crypto_func_t callback, void *user_data); bool bt_hci_crypto_ah(struct bt_hci *hci, const uint8_t k[16], const uint8_t r[3], bt_hci_crypto_func_t callback, void *user_data); bluez-5.87/src/shared/PaxHeaders/timeout-ell.c0000644000000000000000000000005014772767672016345 xustar0020 atime=1783093492 20 ctime=1783094100 bluez-5.87/src/shared/timeout-ell.c0000644000000000000000000000402714772767672016031 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2019 Intel Corporation. All rights reserved. * * */ #include #include #include "timeout.h" static struct l_queue *timeout_q; struct timeout_data { timeout_func_t func; timeout_destroy_func_t destroy; void *user_data; unsigned int timeout; }; static bool match_id(const void *a, const void *b) { unsigned int to_id = L_PTR_TO_UINT(a); unsigned int id = L_PTR_TO_UINT(b); return (to_id == id); } static void timeout_callback(struct l_timeout *timeout, void *user_data) { struct timeout_data *data = user_data; if (data->func) data->func(data->user_data); l_timeout_modify(timeout, data->timeout); } static void timeout_destroy(void *user_data) { struct timeout_data *data = user_data; if (data->destroy) data->destroy(data->user_data); l_free(data); } unsigned int timeout_add(unsigned int timeout, timeout_func_t func, void *user_data, timeout_destroy_func_t destroy) { struct timeout_data *data; unsigned int id = 0; struct l_timeout *to; int tries = 0; if (!timeout_q) timeout_q = l_queue_new(); data = l_new(struct timeout_data, 1); data->func = func; data->destroy = destroy; data->user_data = user_data; data->timeout = timeout; while (id == 0 && tries < 3) { to = l_timeout_create_ms(timeout, timeout_callback, data, timeout_destroy); if (!to) break; tries++; id = L_PTR_TO_UINT(to); if (id == 0 || l_queue_find(timeout_q, match_id, L_UINT_TO_PTR(id))) { l_timeout_remove(to); continue; } l_queue_push_tail(timeout_q, to); } if (id == 0) l_free(data); return id; } void timeout_remove(unsigned int id) { struct l_timeout *to; to = l_queue_remove_if(timeout_q, match_id, L_UINT_TO_PTR(id)); if (to) l_timeout_remove(to); } unsigned int timeout_add_seconds(unsigned int timeout, timeout_func_t func, void *user_data, timeout_destroy_func_t destroy) { return timeout_add(timeout * 1000, func, user_data, destroy); } bluez-5.87/src/shared/PaxHeaders/queue.h0000644000000000000000000000005015200607060015201 xustar0020 atime=1783092683 20 ctime=1783094099 bluez-5.87/src/shared/queue.h0000644000000000000000000000301415200607060014660 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #pragma once #include typedef void (*queue_destroy_func_t)(void *data); struct queue; struct queue_entry { void *data; struct queue_entry *next; }; struct queue *queue_new(void); void queue_destroy(struct queue *queue, queue_destroy_func_t destroy); bool queue_push_tail(struct queue *queue, void *data); bool queue_push_head(struct queue *queue, void *data); bool queue_push_after(struct queue *queue, void *entry, void *data); void *queue_pop_head(struct queue *queue); void *queue_peek_head(struct queue *queue); void *queue_peek_tail(struct queue *queue); typedef void (*queue_foreach_func_t)(void *data, void *user_data); void queue_foreach(struct queue *queue, queue_foreach_func_t function, void *user_data); typedef bool (*queue_match_func_t)(const void *data, const void *match_data); void *queue_find(struct queue *queue, queue_match_func_t function, const void *match_data); bool queue_remove(struct queue *queue, void *data); void *queue_remove_if(struct queue *queue, queue_match_func_t function, void *user_data); unsigned int queue_remove_all(struct queue *queue, queue_match_func_t function, void *user_data, queue_destroy_func_t destroy); const struct queue_entry *queue_get_entries(struct queue *queue); unsigned int queue_length(struct queue *queue); bool queue_isempty(struct queue *queue); bluez-5.87/src/shared/PaxHeaders/io-glib.c0000644000000000000000000000005015017252326015401 xustar0020 atime=1783092980 20 ctime=1783094100 bluez-5.87/src/shared/io-glib.c0000644000000000000000000002255415017252326015072 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "src/shared/io.h" #define IO_ERR_WATCH_RATELIMIT (500 * G_TIME_SPAN_MILLISECOND) struct io_watch { struct io *io; guint id; io_callback_func_t callback; io_destroy_func_t destroy; void *user_data; }; struct io { int ref_count; GIOChannel *channel; bool err_watch; struct io_watch *read_watch; struct io_watch *write_watch; struct io_watch *disconnect_watch; }; struct io_err_watch { GSource source; GIOChannel *io; GIOCondition events; gpointer tag; }; static guint io_glib_add_err_watch_full(GIOChannel *io, gint priority, GIOCondition events, GIOFunc func, gpointer user_data, GDestroyNotify notify); static struct io *io_ref(struct io *io) { if (!io) return NULL; __sync_fetch_and_add(&io->ref_count, 1); return io; } static void io_unref(struct io *io) { if (!io) return; if (__sync_sub_and_fetch(&io->ref_count, 1)) return; g_free(io); } struct io *io_new(int fd) { struct io *io; if (fd < 0) return NULL; io = g_try_new0(struct io, 1); if (!io) return NULL; io->channel = g_io_channel_unix_new(fd); g_io_channel_set_encoding(io->channel, NULL, NULL); g_io_channel_set_buffered(io->channel, FALSE); g_io_channel_set_close_on_unref(io->channel, FALSE); return io_ref(io); } static void watch_destroy(void *user_data) { struct io_watch *watch = user_data; struct io *io = watch->io; if (watch == io->read_watch) io->read_watch = NULL; else if (watch == io->write_watch) io->write_watch = NULL; else if (watch == io->disconnect_watch) io->disconnect_watch = NULL; if (watch->destroy) watch->destroy(watch->user_data); io_unref(watch->io); g_free(watch); } void io_destroy(struct io *io) { if (!io) return; if (io->read_watch) { g_source_remove(io->read_watch->id); io->read_watch = NULL; } if (io->write_watch) { g_source_remove(io->write_watch->id); io->write_watch = NULL; } if (io->disconnect_watch) { g_source_remove(io->disconnect_watch->id); io->disconnect_watch = NULL; } g_io_channel_unref(io->channel); io->channel = NULL; io_unref(io); } int io_get_fd(struct io *io) { if (!io) return -ENOTCONN; return g_io_channel_unix_get_fd(io->channel); } bool io_set_close_on_destroy(struct io *io, bool do_close) { if (!io) return false; if (do_close) g_io_channel_set_close_on_unref(io->channel, TRUE); else g_io_channel_set_close_on_unref(io->channel, FALSE); return true; } static gboolean watch_callback(GIOChannel *channel, GIOCondition cond, gpointer user_data) { struct io_watch *watch = user_data; bool result, destroy; destroy = watch == watch->io->disconnect_watch; if (!destroy && (cond & (G_IO_ERR | G_IO_NVAL))) return FALSE; if (watch->callback) result = watch->callback(watch->io, watch->user_data); else result = false; return result ? TRUE : FALSE; } static struct io_watch *watch_new(struct io *io, GIOCondition cond, io_callback_func_t callback, void *user_data, io_destroy_func_t destroy) { struct io_watch *watch; int prio; watch = g_try_new0(struct io_watch, 1); if (!watch) return NULL; watch->io = io_ref(io); watch->callback = callback; watch->destroy = destroy; watch->user_data = user_data; prio = cond == G_IO_HUP ? G_PRIORITY_DEFAULT_IDLE : G_PRIORITY_DEFAULT; if (!io->err_watch) watch->id = g_io_add_watch_full(io->channel, prio, cond | G_IO_ERR | G_IO_NVAL, watch_callback, watch, watch_destroy); else watch->id = io_glib_add_err_watch_full(io->channel, prio, cond | G_IO_ERR | G_IO_NVAL, watch_callback, watch, watch_destroy); if (watch->id == 0) { watch_destroy(watch); return NULL; } return watch; } static bool io_set_handler(struct io *io, GIOCondition cond, io_callback_func_t callback, void *user_data, io_destroy_func_t destroy) { struct io_watch **watch; if (!io) return false; switch (cond) { case G_IO_IN: watch = &io->read_watch; break; case G_IO_OUT: watch = &io->write_watch; break; case G_IO_HUP: watch = &io->disconnect_watch; break; case G_IO_PRI: case G_IO_ERR: case G_IO_NVAL: default: return false; } if (*watch) { g_source_remove((*watch)->id); *watch = NULL; } if (!callback) return true; *watch = watch_new(io, cond, callback, user_data, destroy); if (!*watch) return false; return true; } bool io_set_read_handler(struct io *io, io_callback_func_t callback, void *user_data, io_destroy_func_t destroy) { return io_set_handler(io, G_IO_IN, callback, user_data, destroy); } bool io_set_write_handler(struct io *io, io_callback_func_t callback, void *user_data, io_destroy_func_t destroy) { return io_set_handler(io, G_IO_OUT, callback, user_data, destroy); } bool io_set_disconnect_handler(struct io *io, io_callback_func_t callback, void *user_data, io_destroy_func_t destroy) { return io_set_handler(io, G_IO_HUP, callback, user_data, destroy); } bool io_set_ignore_errqueue(struct io *io, bool do_ignore) { if (!io) return false; io->err_watch = do_ignore; return true; } ssize_t io_send(struct io *io, const struct iovec *iov, int iovcnt) { int fd; ssize_t ret; if (!io || !io->channel) return -ENOTCONN; fd = io_get_fd(io); do { ret = writev(fd, iov, iovcnt); } while (ret < 0 && errno == EINTR); if (ret < 0) return -errno; return ret; } bool io_shutdown(struct io *io) { if (!io || !io->channel) return false; return g_io_channel_shutdown(io->channel, TRUE, NULL) == G_IO_STATUS_NORMAL; } /* * GSource implementation that tolerates non-empty MSG_ERRQUEUE, without * attempting to flush it. This is intended for use with TX timestamping in * cases where someone else is reading the timestamps and we are only interested * in POLLHUP or socket errors. */ static gint64 io_err_watch_wakeup; static gboolean io_err_watch_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { struct io_err_watch *watch = (void *)source; const GIOFunc func = (void *)callback; const gint64 timeout = IO_ERR_WATCH_RATELIMIT; GIOCondition cond; int fd; if (!func) return FALSE; fd = g_io_channel_unix_get_fd(watch->io); /* * If woken up by POLLERR only, and SO_ERROR is not set, ignore this * event. Also disable polling for some time so that we don't consume * too much CPU on events we are not interested in, or busy loop if * nobody is flushing the errqueue. */ if (watch->tag) cond = g_source_query_unix_fd(&watch->source, watch->tag); else cond = 0; if (cond == G_IO_ERR) { int err, ret; socklen_t len = sizeof(err); ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len); if (ret == 0 && err == 0) { g_source_remove_unix_fd(&watch->source, watch->tag); watch->tag = NULL; /* io_err watches all wake up at the same time */ if (!io_err_watch_wakeup) io_err_watch_wakeup = g_get_monotonic_time() + timeout; g_source_set_ready_time(&watch->source, io_err_watch_wakeup); return TRUE; } } if (g_source_get_ready_time(&watch->source) != -1) { g_assert(!watch->tag); io_err_watch_wakeup = 0; watch->tag = g_source_add_unix_fd(&watch->source, fd, watch->events); g_source_set_ready_time(&watch->source, -1); } cond &= watch->events; if (cond) return func(watch->io, cond, user_data); else return TRUE; } static void io_err_watch_finalize(GSource *source) { struct io_err_watch *watch = (void *)source; if (watch->tag) g_source_remove_unix_fd(&watch->source, watch->tag); g_io_channel_unref(watch->io); } static guint io_glib_add_err_watch_full(GIOChannel *io, gint priority, GIOCondition events, GIOFunc func, gpointer user_data, GDestroyNotify notify) { static GSourceFuncs source_funcs = { .dispatch = io_err_watch_dispatch, .finalize = io_err_watch_finalize, }; GSourceFunc callback = (void *)func; struct io_err_watch *watch; gint fd; guint id; g_return_val_if_fail(!(events & (G_IO_IN | G_IO_OUT)), 0); g_return_val_if_fail(events, 0); g_return_val_if_fail(func, 0); fd = g_io_channel_unix_get_fd(io); watch = (void *)g_source_new(&source_funcs, sizeof(struct io_err_watch)); watch->io = g_io_channel_ref(io); watch->events = events; watch->tag = g_source_add_unix_fd(&watch->source, fd, events); g_source_set_name((void *)watch, "io_glib_err_watch"); g_source_set_callback(&watch->source, callback, user_data, notify); if (priority != G_PRIORITY_DEFAULT) g_source_set_priority(&watch->source, priority); id = g_source_attach(&watch->source, NULL); g_source_unref(&watch->source); return id; } struct err_watch_cb_data { io_glib_err_func_t func; void *data; }; static gboolean err_watch_callback(GIOChannel *channel, GIOCondition cond, gpointer user_data) { struct err_watch_cb_data *data = user_data; data->func(cond, data->data); return FALSE; } unsigned int io_glib_add_err_watch(void *giochannel, io_glib_err_func_t func, void *user_data) { struct err_watch_cb_data *data; data = g_try_new0(struct err_watch_cb_data, 1); if (!data) return 0; data->func = func; data->data = user_data; return io_glib_add_err_watch_full(giochannel, G_PRIORITY_DEFAULT, G_IO_ERR | G_IO_HUP | G_IO_NVAL, err_watch_callback, data, g_free); } bluez-5.87/src/shared/PaxHeaders/crypto.c0000644000000000000000000000005015142177620015401 xustar0020 atime=1783092883 20 ctime=1783094099 bluez-5.87/src/shared/crypto.c0000644000000000000000000005553215142177620015074 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "src/shared/util.h" #include "src/shared/crypto.h" #ifndef HAVE_LINUX_IF_ALG_H #ifndef HAVE_LINUX_TYPES_H typedef uint8_t __u8; typedef uint16_t __u16; typedef uint32_t __u32; #else #include #endif struct sockaddr_alg { __u16 salg_family; __u8 salg_type[14]; __u32 salg_feat; __u32 salg_mask; __u8 salg_name[64]; }; struct af_alg_iv { __u32 ivlen; __u8 iv[0]; }; #define ALG_SET_KEY 1 #define ALG_SET_IV 2 #define ALG_SET_OP 3 #define ALG_OP_DECRYPT 0 #define ALG_OP_ENCRYPT 1 #define PF_ALG 38 /* Algorithm sockets. */ #define AF_ALG PF_ALG #else #include #endif #ifndef SOL_ALG #define SOL_ALG 279 #endif /* Maximum message length that can be passed to aes_cmac */ #define CMAC_MSG_MAX 80 #define ATT_SIGN_LEN 12 struct bt_crypto { int ref_count; int ecb_aes; int urandom; int cmac_aes; }; static int urandom_setup(void) { int fd; fd = open("/dev/urandom", O_RDONLY); if (fd < 0) return -1; return fd; } static int ecb_aes_setup(void) { struct sockaddr_alg salg; int fd; fd = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); if (fd < 0) return -1; memset(&salg, 0, sizeof(salg)); salg.salg_family = AF_ALG; strcpy((char *) salg.salg_type, "skcipher"); strcpy((char *) salg.salg_name, "ecb(aes)"); if (bind(fd, (struct sockaddr *) &salg, sizeof(salg)) < 0) { close(fd); return -1; } return fd; } static int cmac_aes_setup(void) { struct sockaddr_alg salg; int fd; fd = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); if (fd < 0) return -1; memset(&salg, 0, sizeof(salg)); salg.salg_family = AF_ALG; strcpy((char *) salg.salg_type, "hash"); strcpy((char *) salg.salg_name, "cmac(aes)"); if (bind(fd, (struct sockaddr *) &salg, sizeof(salg)) < 0) { close(fd); return -1; } return fd; } static struct bt_crypto *singleton; struct bt_crypto *bt_crypto_new(void) { if (singleton) return bt_crypto_ref(singleton); singleton = new0(struct bt_crypto, 1); singleton->ecb_aes = ecb_aes_setup(); if (singleton->ecb_aes < 0) { free(singleton); singleton = NULL; return NULL; } singleton->urandom = urandom_setup(); if (singleton->urandom < 0) { close(singleton->ecb_aes); free(singleton); singleton = NULL; return NULL; } singleton->cmac_aes = cmac_aes_setup(); if (singleton->cmac_aes < 0) { close(singleton->urandom); close(singleton->ecb_aes); free(singleton); singleton = NULL; return NULL; } return bt_crypto_ref(singleton); } struct bt_crypto *bt_crypto_ref(struct bt_crypto *crypto) { if (!crypto) return NULL; __sync_fetch_and_add(&crypto->ref_count, 1); return crypto; } void bt_crypto_unref(struct bt_crypto *crypto) { if (!crypto) return; if (__sync_sub_and_fetch(&crypto->ref_count, 1)) return; close(crypto->urandom); close(crypto->ecb_aes); close(crypto->cmac_aes); free(crypto); singleton = NULL; } bool bt_crypto_random_bytes(struct bt_crypto *crypto, void *buf, uint8_t num_bytes) { ssize_t len; if (!crypto) return false; len = read(crypto->urandom, buf, num_bytes); if (len < num_bytes) return false; return true; } static int alg_new(int fd, const void *keyval, socklen_t keylen) { if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, keyval, keylen) < 0) return -1; /* FIXME: This should use accept4() with SOCK_CLOEXEC */ return accept(fd, NULL, 0); } static bool alg_encrypt(int fd, const void *inbuf, size_t inlen, void *outbuf, size_t outlen) { __u32 alg_op = ALG_OP_ENCRYPT; char cbuf[CMSG_SPACE(sizeof(alg_op))]; struct cmsghdr *cmsg; struct msghdr msg; struct iovec iov; ssize_t len; memset(cbuf, 0, sizeof(cbuf)); memset(&msg, 0, sizeof(msg)); msg.msg_control = cbuf; msg.msg_controllen = sizeof(cbuf); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_OP; cmsg->cmsg_len = CMSG_LEN(sizeof(alg_op)); memcpy(CMSG_DATA(cmsg), &alg_op, sizeof(alg_op)); iov.iov_base = (void *) inbuf; iov.iov_len = inlen; msg.msg_iov = &iov; msg.msg_iovlen = 1; len = sendmsg(fd, &msg, 0); if (len < 0) return false; len = read(fd, outbuf, outlen); if (len < 0) return false; return true; } static inline void swap_buf(const uint8_t *src, uint8_t *dst, uint16_t len) { int i; for (i = 0; i < len; i++) dst[len - 1 - i] = src[i]; } bool bt_crypto_sign_att(struct bt_crypto *crypto, const uint8_t key[16], const uint8_t *m, uint16_t m_len, uint32_t sign_cnt, uint8_t signature[ATT_SIGN_LEN]) { int fd; int len; uint8_t tmp[16], out[16]; uint16_t msg_len = m_len + sizeof(uint32_t); uint8_t msg[msg_len]; uint8_t msg_s[msg_len]; if (!crypto) return false; memset(msg, 0, msg_len); memcpy(msg, m, m_len); /* Add sign_counter to the message */ put_le32(sign_cnt, msg + m_len); /* The most significant octet of key corresponds to key[0] */ swap_buf(key, tmp, 16); fd = alg_new(crypto->cmac_aes, tmp, 16); if (fd < 0) return false; /* Swap msg before signing */ swap_buf(msg, msg_s, msg_len); len = send(fd, msg_s, msg_len, 0); if (len < 0) { close(fd); return false; } len = read(fd, out, 16); if (len < 0) { close(fd); return false; } close(fd); /* * As to BT spec. 4.1 Vol[3], Part C, chapter 10.4.1 sign counter should * be placed in the signature */ put_be32(sign_cnt, out + 8); /* * The most significant octet of hash corresponds to out[0] - swap it. * Then truncate in most significant bit first order to a length of * 12 octets */ swap_buf(out, tmp, 16); memcpy(signature, tmp + 4, ATT_SIGN_LEN); return true; } bool bt_crypto_verify_att_sign(struct bt_crypto *crypto, const uint8_t key[16], const uint8_t *pdu, uint16_t pdu_len) { uint8_t generated_sign[ATT_SIGN_LEN]; const uint8_t *sign; uint32_t sign_cnt; if (pdu_len < ATT_SIGN_LEN) return false; sign = pdu + pdu_len - ATT_SIGN_LEN; sign_cnt = get_le32(sign); if (!bt_crypto_sign_att(crypto, key, pdu, pdu_len - ATT_SIGN_LEN, sign_cnt, generated_sign)) return false; return memcmp(generated_sign, sign, ATT_SIGN_LEN) == 0; } /* * Security function e * * Security function e generates 128-bit encryptedData from a 128-bit key * and 128-bit plaintextData using the AES-128-bit block cypher: * * encryptedData = e(key, plaintextData) * * The most significant octet of key corresponds to key[0], the most * significant octet of plaintextData corresponds to in[0] and the * most significant octet of encryptedData corresponds to out[0]. * */ bool bt_crypto_e(struct bt_crypto *crypto, const uint8_t key[16], const uint8_t plaintext[16], uint8_t encrypted[16]) { uint8_t tmp[16], in[16], out[16]; int fd; if (!crypto) return false; /* The most significant octet of key corresponds to key[0] */ swap_buf(key, tmp, 16); fd = alg_new(crypto->ecb_aes, tmp, 16); if (fd < 0) return false; /* Most significant octet of plaintextData corresponds to in[0] */ swap_buf(plaintext, in, 16); if (!alg_encrypt(fd, in, 16, out, 16)) { close(fd); return false; } /* Most significant octet of encryptedData corresponds to out[0] */ swap_buf(out, encrypted, 16); close(fd); return true; } /* * Random Address Hash function ah * * The random address hash function ah is used to generate a hash value * that is used in resolvable private addresses. * * The following are inputs to the random address hash function ah: * * k is 128 bits * r is 24 bits * padding is 104 bits * * r is concatenated with padding to generate r' which is used as the * 128-bit input parameter plaintextData to security function e: * * r' = padding || r * * The least significant octet of r becomes the least significant octet * of r’ and the most significant octet of padding becomes the most * significant octet of r'. * * For example, if the 24-bit value r is 0x423456 then r' is * 0x00000000000000000000000000423456. * * The output of the random address function ah is: * * ah(k, r) = e(k, r') mod 2^24 * * The output of the security function e is then truncated to 24 bits by * taking the least significant 24 bits of the output of e as the result * of ah. */ bool bt_crypto_ah(struct bt_crypto *crypto, const uint8_t k[16], const uint8_t r[3], uint8_t hash[3]) { uint8_t rp[16]; uint8_t encrypted[16]; if (!crypto) return false; /* r' = padding || r */ memcpy(rp, r, 3); memset(rp + 3, 0, 13); /* e(k, r') */ if (!bt_crypto_e(crypto, k, rp, encrypted)) return false; /* ah(k, r) = e(k, r') mod 2^24 */ memcpy(hash, encrypted, 3); return true; } typedef struct { uint64_t a, b; } u128; static inline void u128_xor(const uint8_t p[16], const uint8_t q[16], uint8_t r[16]) { u128 pp, qq, rr; memcpy(&pp, p, 16); memcpy(&qq, q, 16); rr.a = pp.a ^ qq.a; rr.b = pp.b ^ qq.b; memcpy(r, &rr, 16); } /* * Confirm value generation function c1 * * During the pairing process confirm values are exchanged. This confirm * value generation function c1 is used to generate the confirm values. * * The following are inputs to the confirm value generation function c1: * * k is 128 bits * r is 128 bits * pres is 56 bits * preq is 56 bits * iat is 1 bit * ia is 48 bits * rat is 1 bit * ra is 48 bits * padding is 32 bits of 0 * * iat is concatenated with 7-bits of 0 to create iat' which is 8 bits * in length. iat is the least significant bit of iat' * * rat is concatenated with 7-bits of 0 to create rat' which is 8 bits * in length. rat is the least significant bit of rat' * * pres, preq, rat' and iat' are concatenated to generate p1 which is * XORed with r and used as 128-bit input parameter plaintextData to * security function e: * * p1 = pres || preq || rat' || iat' * * The octet of iat' becomes the least significant octet of p1 and the * most significant octet of pres becomes the most significant octet of * p1. * * ra is concatenated with ia and padding to generate p2 which is XORed * with the result of the security function e using p1 as the input * parameter plaintextData and is then used as the 128-bit input * parameter plaintextData to security function e: * * p2 = padding || ia || ra * * The least significant octet of ra becomes the least significant octet * of p2 and the most significant octet of padding becomes the most * significant octet of p2. * * The output of the confirm value generation function c1 is: * * c1(k, r, preq, pres, iat, rat, ia, ra) = e(k, e(k, r XOR p1) XOR p2) * * The 128-bit output of the security function e is used as the result * of confirm value generation function c1. */ bool bt_crypto_c1(struct bt_crypto *crypto, const uint8_t k[16], const uint8_t r[16], const uint8_t pres[7], const uint8_t preq[7], uint8_t iat, const uint8_t ia[6], uint8_t rat, const uint8_t ra[6], uint8_t res[16]) { uint8_t p1[16], p2[16]; /* p1 = pres || preq || _rat || _iat */ p1[0] = iat; p1[1] = rat; memcpy(p1 + 2, preq, 7); memcpy(p1 + 9, pres, 7); /* p2 = padding || ia || ra */ memcpy(p2, ra, 6); memcpy(p2 + 6, ia, 6); memset(p2 + 12, 0, 4); /* res = r XOR p1 */ u128_xor(r, p1, res); /* res = e(k, res) */ if (!bt_crypto_e(crypto, k, res, res)) return false; /* res = res XOR p2 */ u128_xor(res, p2, res); /* res = e(k, res) */ return bt_crypto_e(crypto, k, res, res); } /* * Key generation function s1 * * The key generation function s1 is used to generate the STK during the * pairing process. * * The following are inputs to the key generation function s1: * * k is 128 bits * r1 is 128 bits * r2 is 128 bits * * The most significant 64-bits of r1 are discarded to generate r1' and * the most significant 64-bits of r2 are discarded to generate r2'. * * r1' is concatenated with r2' to generate r' which is used as the * 128-bit input parameter plaintextData to security function e: * * r' = r1' || r2' * * The least significant octet of r2' becomes the least significant * octet of r' and the most significant octet of r1' becomes the most * significant octet of r'. * * The output of the key generation function s1 is: * * s1(k, r1, r2) = e(k, r') * * The 128-bit output of the security function e is used as the result * of key generation function s1. */ bool bt_crypto_s1(struct bt_crypto *crypto, const uint8_t k[16], const uint8_t r1[16], const uint8_t r2[16], uint8_t res[16]) { memcpy(res, r2, 8); memcpy(res + 8, r1, 8); return bt_crypto_e(crypto, k, res, res); } static bool aes_cmac_be(struct bt_crypto *crypto, const uint8_t key[16], const uint8_t *msg, size_t msg_len, uint8_t res[16]) { ssize_t len; int fd; if (msg_len > CMAC_MSG_MAX) return false; fd = alg_new(crypto->cmac_aes, key, 16); if (fd < 0) return false; len = send(fd, msg, msg_len, 0); if (len < 0) { close(fd); return false; } len = read(fd, res, 16); if (len < 0) { close(fd); return false; } close(fd); return true; } static bool aes_cmac(struct bt_crypto *crypto, const uint8_t key[16], const uint8_t *msg, size_t msg_len, uint8_t res[16]) { uint8_t key_msb[16], out[16], msg_msb[CMAC_MSG_MAX]; if (msg_len > CMAC_MSG_MAX) return false; swap_buf(key, key_msb, 16); swap_buf(msg, msg_msb, msg_len); if (!aes_cmac_be(crypto, key_msb, msg_msb, msg_len, out)) return false; swap_buf(out, res, 16); return true; } bool bt_crypto_f4(struct bt_crypto *crypto, uint8_t u[32], uint8_t v[32], uint8_t x[16], uint8_t z, uint8_t res[16]) { uint8_t m[65]; if (!crypto) return false; m[0] = z; memcpy(&m[1], v, 32); memcpy(&m[33], u, 32); return aes_cmac(crypto, x, m, sizeof(m), res); } bool bt_crypto_f5(struct bt_crypto *crypto, uint8_t w[32], uint8_t n1[16], uint8_t n2[16], uint8_t a1[7], uint8_t a2[7], uint8_t mackey[16], uint8_t ltk[16]) { uint8_t btle[4] = { 0x65, 0x6c, 0x74, 0x62 }; uint8_t salt[16] = { 0xbe, 0x83, 0x60, 0x5a, 0xdb, 0x0b, 0x37, 0x60, 0x38, 0xa5, 0xf5, 0xaa, 0x91, 0x83, 0x88, 0x6c }; uint8_t length[2] = { 0x00, 0x01 }; uint8_t m[53], t[16]; if (!aes_cmac(crypto, salt, w, 32, t)) return false; memcpy(&m[0], length, 2); memcpy(&m[2], a2, 7); memcpy(&m[9], a1, 7); memcpy(&m[16], n2, 16); memcpy(&m[32], n1, 16); memcpy(&m[48], btle, 4); m[52] = 0; /* Counter */ if (!aes_cmac(crypto, t, m, sizeof(m), mackey)) return false; m[52] = 1; /* Counter */ return aes_cmac(crypto, t, m, sizeof(m), ltk); } bool bt_crypto_f6(struct bt_crypto *crypto, uint8_t w[16], uint8_t n1[16], uint8_t n2[16], uint8_t r[16], uint8_t io_cap[3], uint8_t a1[7], uint8_t a2[7], uint8_t res[16]) { uint8_t m[65]; memcpy(&m[0], a2, 7); memcpy(&m[7], a1, 7); memcpy(&m[14], io_cap, 3); memcpy(&m[17], r, 16); memcpy(&m[33], n2, 16); memcpy(&m[49], n1, 16); return aes_cmac(crypto, w, m, sizeof(m), res); } bool bt_crypto_g2(struct bt_crypto *crypto, uint8_t u[32], uint8_t v[32], uint8_t x[16], uint8_t y[16], uint32_t *val) { uint8_t m[80], tmp[16]; memcpy(&m[0], y, 16); memcpy(&m[16], v, 32); memcpy(&m[48], u, 32); if (!aes_cmac(crypto, x, m, sizeof(m), tmp)) return false; *val = get_le32(tmp); *val %= 1000000; return true; } bool bt_crypto_h6(struct bt_crypto *crypto, const uint8_t w[16], const uint8_t keyid[4], uint8_t res[16]) { if (!aes_cmac(crypto, w, keyid, 4, res)) return false; return true; } bool bt_crypto_gatt_hash(struct bt_crypto *crypto, struct iovec *iov, size_t iov_len, uint8_t res[16]) { const uint8_t key[16] = {}; ssize_t len; int fd; if (!crypto) return false; fd = alg_new(crypto->cmac_aes, key, 16); if (fd < 0) return false; len = writev(fd, iov, iov_len); if (len < 0) { close(fd); return false; } len = read(fd, res, 16); if (len < 0) { close(fd); return false; } close(fd); return true; } /* * Resolvable Set Identifier hash function sih * * The RSI hash function sih is used to generate a hash value that is used in * RSIs. * * The following variables are the inputs to the RSI hash function sih: * * k is 128 bits * r is 24 bits * padding is 104 bits, all set to 0 * * r is concatenated with padding to generate r', which is used as the 128-bit * input parameter plaintextData to security function e: * * r'=padding||r * * The LSO of r becomes the LSO of r', and the MSO of padding becomes the MSO * of r'. * * For example, if the 24-bit value r is 0x3A98B5, then r' is * 0x000000000000000000000000003A98B5. * * The output of the Resolvable Set Identifier function sih is: * * sih(k, r)=e(k, r') mod 2^24 * * The output of the security function e is truncated to 24 bits by taking the * least significant 24 bits of the output of e as the result of sih. */ bool bt_crypto_sih(struct bt_crypto *crypto, const uint8_t k[16], const uint8_t r[3], uint8_t hash[3]) { return bt_crypto_ah(crypto, k, r, hash); } /* * The hash is generated by using the RSI hash function sih, with the input * parameter k set to the device’s SIRK, and the input parameter r set to * prand: * * hash = sih(SIRK, prand) * * The prand and hash are concatenated to generate the RSI * resolvableSetIdentifier in the following manner: * * resolvableSetIdentifier = hash || prand */ bool bt_crypto_rsi(struct bt_crypto *crypto, const uint8_t sirk[16], uint8_t rsi[6]) { uint8_t prand[3]; uint8_t hash[3]; /* The random number prand shall meet the following requirements: * * - The two most significant bits (MSBs) of prand shall be equal to 0 * - At least one bit of the random part of prand shall be 0. * - At least one bit of the random part of prand shall be 1. */ if (!bt_crypto_random_bytes(crypto, prand, 3)) return false; prand[2] &= 0x3f; prand[2] |= 0x40; if (!bt_crypto_sih(crypto, sirk, prand, hash)) return false; memcpy(rsi, hash, 3); memcpy(rsi + 3, prand, 3); return true; } static bool aes_cmac_zero(struct bt_crypto *crypto, const uint8_t *msg, size_t msg_len, uint8_t res[16]) { const uint8_t zero[16] = {}; return aes_cmac_be(crypto, zero, msg, msg_len, res); } /* The inputs to function s1 are: * * M is a non-zero length octet array or ASCII encoded string * * If M is an ASCII encoded string, M shall be converted into an integer number * by replacing each string character with its ASCII code preserving the order. * For example, if M is the string “CSIS”, M is converted into the integer * number: 0x4353 4953. * * ZERO is the 128-bit value: * * 0x0000 0000 0000 0000 0000 0000 0000 0000 * * The output of the salt generation function s1 shall be calculated as follows: * * s1(M)=AES‐CMACZERO(M) * * Where AES-CMACZERO is the CMAC function defined in Section 4.2. */ static bool sef_s1(struct bt_crypto *crypto, const uint8_t *m, size_t m_len, uint8_t res[16]) { /* s1(M)=AES‐CMACZERO(M) */ return aes_cmac_zero(crypto, m, m_len, res); } /* The key derivation function k1 is used to derive a key. The derived key is * used to encrypt and decrypt the value of the Set Identity Resolving Key * characteristic (see Section 5.1). * * The definition of this key generation function uses the MAC function * AES-CMACT with a 128-bit key T. * * The inputs to function k1 are: * * N is 0 or more octets * * SALT is 128 bits * * P is 0 or more octets * * The key (T) shall be computed as follows: * * T=AES‐CMACSALT(N) * * Where AES-CMACSALT is the CMAC function defined in Section 4.2. * * The output of the key generation function k1 shall be calculated as follows: * * k1(N, SALT, P)=AES‐CMACT(P) * * Where AES-CMACT is the CMAC function defined in Section 4.2. */ static bool sef_k1(struct bt_crypto *crypto, const uint8_t n[16], uint8_t salt[16], const uint8_t *p, size_t p_len, uint8_t res[16]) { uint8_t res1[16]; /* T=AES‐CMACSALT(N) */ if (!aes_cmac_be(crypto, salt, n, 16, res1)) return false; /* k1(N, SALT, P)=AES‐CMACT(P) */ return aes_cmac_be(crypto, res1, p, p_len, res); } /* * SIRK encryption function sef * * The SIRK encryption function sef shall be used by the server to encrypt the * SIRK with a key K. The value of K depends on the transport on which the Set * Identity Resolving Key characteristic is read or notified. * * If the Set Identity Resolving Key characteristic is read or notified on the * Basic Rate/Enhanced Data Rate (BR/EDR) transport, K shall be equal to the * Link Key shared by the server and the client. * * K=LinkKey * * If the Set Identity Resolving Key characteristic is read or notified on the * Bluetooth Low Energy (LE) transport, K shall be equal to the LTK shared by * the server and client. That is, * * K=LTK * * The inputs to the function sef are: * * K is the key defined above in this section * * SIRK is the value of the SIRK to be encrypted * * The output of the SIRK encryption function sef is as follows: * * sef(K, SIRK)=k1(K, s1(“SIRKenc”), “csis”)^SIRK * * Where ^ is the bitwise exclusive or operation. */ bool bt_crypto_sef(struct bt_crypto *crypto, const uint8_t k[16], const uint8_t sirk[16], uint8_t out[16]) { const uint8_t m[] = {'S', 'I', 'R', 'K', 'e', 'n', 'c'}; const uint8_t p[] = {'c', 's', 'i', 's'}; uint8_t k_msb[16]; uint8_t salt[16]; uint8_t res_msb[16]; uint8_t res[16]; if (!crypto) return false; /* salt = s1(“SIRKenc”) */ if (!sef_s1(crypto, m, sizeof(m), salt)) return false; /* Convert K to MSB/BE format */ swap_buf(k, k_msb, 16); /* res_msb = k1(K, salt, “csis”) */ if (!sef_k1(crypto, k_msb, salt, p, sizeof(p), res_msb)) return false; /* Convert back to LSB/LE format */ swap_buf(res_msb, res, 16); /* res^SIRK */ u128_xor(res, sirk, out); return true; } /* Generates a SIRK from a string using the following steps: * - Generate a hash (k) using the str as input * - Generate a hash (sirk) using vendor, product, version and source as input * - Encrypt sirk using k as LTK with sef function. */ bool bt_crypto_sirk(struct bt_crypto *crypto, const char *str, uint16_t vendor, uint16_t product, uint16_t version, uint16_t source, uint8_t sirk[16]) { struct iovec iov[4]; uint8_t k[16]; uint8_t sirk_plaintext[16]; if (!crypto) return false; iov[0].iov_base = (void *)str; iov[0].iov_len = strlen(str); /* Generate a k using the str as input */ if (!bt_crypto_gatt_hash(crypto, iov, 1, k)) return false; iov[0].iov_base = &vendor; iov[0].iov_len = sizeof(vendor); iov[1].iov_base = &product; iov[1].iov_len = sizeof(product); iov[2].iov_base = &version; iov[2].iov_len = sizeof(version); iov[3].iov_base = &source; iov[3].iov_len = sizeof(source); /* Generate a sirk using vendor, product, version and source as input */ if (!bt_crypto_gatt_hash(crypto, iov, 4, sirk_plaintext)) return false; /* Encrypt sirk using k as LTK with sef function */ return bt_crypto_sef(crypto, k, sirk_plaintext, sirk); } bluez-5.87/src/shared/PaxHeaders/mgmt.c0000644000000000000000000000005015200607060015014 xustar0020 atime=1783092683 20 ctime=1783094099 bluez-5.87/src/shared/mgmt.c0000644000000000000000000005433115200607060014503 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/mgmt.h" #include "bluetooth/hci.h" #include "src/shared/io.h" #include "src/shared/queue.h" #include "src/shared/util.h" #include "src/shared/mgmt.h" #include "src/shared/timeout.h" #define DBG(_mgmt, _format, arg...) \ mgmt_log(_mgmt, "%s:%s() " _format, __FILE__, __func__, ## arg) struct mgmt { int ref_count; int fd; bool close_on_unref; struct io *io; bool writer_active; struct queue *request_queue; struct queue *reply_queue; struct queue *pending_list; struct queue *notify_list; unsigned int next_request_id; unsigned int next_notify_id; bool need_notify_cleanup; bool in_notify; void *buf; uint16_t len; uint16_t mtu; mgmt_debug_func_t debug_callback; mgmt_destroy_func_t debug_destroy; void *debug_data; }; struct mgmt_request { struct mgmt *mgmt; unsigned int id; uint16_t opcode; uint16_t index; void *buf; uint16_t len; mgmt_request_func_t callback; mgmt_destroy_func_t destroy; void *user_data; int timeout; unsigned int timeout_id; }; struct mgmt_notify { unsigned int id; uint16_t event; uint16_t index; bool removed; mgmt_notify_func_t callback; mgmt_destroy_func_t destroy; void *user_data; }; struct mgmt_tlv_list { struct queue *tlv_queue; uint16_t size; }; struct arg_table { const char *name; enum mgmt_io_capability value; }; static const struct arg_table iocap_arguments[] = { { "DisplayOnly", MGMT_IO_CAPABILITY_DISPLAYONLY }, { "DisplayYesNo", MGMT_IO_CAPABILITY_DISPLAYYESNO }, { "KeyboardOnly", MGMT_IO_CAPABILITY_KEYBOARDONLY }, { "NoInputNoOutput", MGMT_IO_CAPABILITY_NOINPUTNOOUTPUT }, { "KeyboardDisplay", MGMT_IO_CAPABILITY_KEYBOARDDISPLAY }, { NULL, 0} }; static void destroy_request(void *data) { struct mgmt_request *request = data; if (request->destroy) request->destroy(request->user_data); if (request->timeout_id) timeout_remove(request->timeout_id); free(request->buf); free(request); } static bool match_request_id(const void *a, const void *b) { const struct mgmt_request *request = a; unsigned int id = PTR_TO_UINT(b); return request->id == id; } static bool match_request_index(const void *a, const void *b) { const struct mgmt_request *request = a; uint16_t index = PTR_TO_UINT(b); return request->index == index; } static void destroy_notify(void *data) { struct mgmt_notify *notify = data; if (notify->destroy) notify->destroy(notify->user_data); free(notify); } static bool match_notify_id(const void *a, const void *b) { const struct mgmt_notify *notify = a; unsigned int id = PTR_TO_UINT(b); return notify->id == id; } static bool match_notify_index(const void *a, const void *b) { const struct mgmt_notify *notify = a; uint16_t index = PTR_TO_UINT(b); return notify->index == index; } static bool match_notify_removed(const void *a, const void *b) { const struct mgmt_notify *notify = a; return notify->removed; } static void mark_notify_removed(void *data , void *user_data) { struct mgmt_notify *notify = data; uint16_t index = PTR_TO_UINT(user_data); if (notify->index == index || index == MGMT_INDEX_NONE) notify->removed = true; } static void write_watch_destroy(void *user_data) { struct mgmt *mgmt = user_data; mgmt->writer_active = false; } static bool request_timeout(void *data) { struct mgmt_request *request = data; if (!request) return false; request->timeout_id = 0; queue_remove_if(request->mgmt->pending_list, NULL, request); if (request->callback) request->callback(MGMT_STATUS_TIMEOUT, 0, NULL, request->user_data); destroy_request(request); return false; } static void mgmt_log(struct mgmt *mgmt, const char *format, ...) { va_list ap; if (!mgmt || !format || !mgmt->debug_callback) return; va_start(ap, format); util_debug_va(mgmt->debug_callback, mgmt->debug_data, format, ap); va_end(ap); } static bool send_request(struct mgmt *mgmt, struct mgmt_request *request) { struct iovec iov; ssize_t ret; iov.iov_base = request->buf; iov.iov_len = request->len; ret = io_send(mgmt->io, &iov, 1); if (ret < 0) { DBG(mgmt, "write failed: %s", strerror(-ret)); if (request->callback) request->callback(MGMT_STATUS_FAILED, 0, NULL, request->user_data); destroy_request(request); return false; } if (request->timeout) request->timeout_id = timeout_add_seconds(request->timeout, request_timeout, request, NULL); DBG(mgmt, "[0x%04x] command 0x%04x", request->index, request->opcode); queue_push_tail(mgmt->pending_list, request); return true; } static bool can_write_data(struct io *io, void *user_data) { struct mgmt *mgmt = user_data; struct mgmt_request *request; bool can_write; request = queue_pop_head(mgmt->reply_queue); if (!request) { /* only reply commands can jump the queue */ if (!queue_isempty(mgmt->pending_list)) return false; request = queue_pop_head(mgmt->request_queue); if (!request) return false; can_write = false; } else { /* allow multiple replies to jump the queue */ can_write = !queue_isempty(mgmt->reply_queue); } if (!send_request(mgmt, request)) return true; return can_write; } static void wakeup_writer(struct mgmt *mgmt) { if (!queue_isempty(mgmt->pending_list)) { /* only queued reply commands trigger wakeup */ if (queue_isempty(mgmt->reply_queue)) return; } if (mgmt->writer_active) return; mgmt->writer_active = true; io_set_write_handler(mgmt->io, can_write_data, mgmt, write_watch_destroy); } struct opcode_index { uint16_t opcode; uint16_t index; }; static bool match_request_opcode_index(const void *a, const void *b) { const struct mgmt_request *request = a; const struct opcode_index *match = b; return request->opcode == match->opcode && request->index == match->index; } static void request_complete(struct mgmt *mgmt, uint8_t status, uint16_t opcode, uint16_t index, uint16_t length, const void *param) { struct opcode_index match = { .opcode = opcode, .index = index }; struct mgmt_request *request; request = queue_remove_if(mgmt->pending_list, match_request_opcode_index, &match); if (!request) { DBG(mgmt, "Unable to find request for opcode 0x%04x", opcode); /* Attempt to remove with no opcode */ request = queue_remove_if(mgmt->pending_list, match_request_index, UINT_TO_PTR(index)); } if (request) { if (request->callback) request->callback(status, length, param, request->user_data); destroy_request(request); } wakeup_writer(mgmt); } struct event_index { uint16_t event; uint16_t index; uint16_t length; const void *param; }; static void notify_handler(void *data, void *user_data) { struct mgmt_notify *notify = data; struct event_index *match = user_data; if (notify->removed) return; if (notify->event != match->event) return; if (notify->index != match->index && notify->index != MGMT_INDEX_NONE) return; if (notify->callback) notify->callback(match->index, match->length, match->param, notify->user_data); } static void process_notify(struct mgmt *mgmt, uint16_t event, uint16_t index, uint16_t length, const void *param) { struct event_index match = { .event = event, .index = index, .length = length, .param = param }; mgmt->in_notify = true; queue_foreach(mgmt->notify_list, notify_handler, &match); mgmt->in_notify = false; if (mgmt->need_notify_cleanup) { queue_remove_all(mgmt->notify_list, match_notify_removed, NULL, destroy_notify); mgmt->need_notify_cleanup = false; } } static bool can_read_data(struct io *io, void *user_data) { struct mgmt *mgmt = user_data; struct mgmt_hdr *hdr; struct mgmt_ev_cmd_complete *cc; struct mgmt_ev_cmd_status *cs; ssize_t bytes_read; uint16_t opcode, event, index, length; bytes_read = read(mgmt->fd, mgmt->buf, mgmt->len); if (bytes_read < 0) return false; if (bytes_read < MGMT_HDR_SIZE) return true; hdr = mgmt->buf; event = btohs(hdr->opcode); index = btohs(hdr->index); length = btohs(hdr->len); if (bytes_read < length + MGMT_HDR_SIZE) return true; mgmt_ref(mgmt); switch (event) { case MGMT_EV_CMD_COMPLETE: cc = mgmt->buf + MGMT_HDR_SIZE; opcode = btohs(cc->opcode); DBG(mgmt, "[0x%04x] command 0x%04x complete: 0x%02x", index, opcode, cc->status); request_complete(mgmt, cc->status, opcode, index, length - 3, mgmt->buf + MGMT_HDR_SIZE + 3); break; case MGMT_EV_CMD_STATUS: cs = mgmt->buf + MGMT_HDR_SIZE; opcode = btohs(cs->opcode); DBG(mgmt, "[0x%04x] command 0x%02x status: 0x%02x", index, opcode, cs->status); request_complete(mgmt, cs->status, opcode, index, 0, NULL); break; default: DBG(mgmt, "[0x%04x] event 0x%04x", index, event); process_notify(mgmt, event, index, length, mgmt->buf + MGMT_HDR_SIZE); break; } mgmt_unref(mgmt); return true; } static void mgmt_set_mtu(struct mgmt *mgmt) { socklen_t len = 0; /* Check if kernel support BT_SNDMTU to read the current MTU set */ if (getsockopt(mgmt->fd, SOL_BLUETOOTH, BT_SNDMTU, &mgmt->mtu, &len) < 0) { /* If BT_SNDMTU is not supported then MTU cannot be changed and * MTU is fixed to HCI_MAX_ACL_SIZE. */ mgmt->mtu = HCI_MAX_ACL_SIZE; return; } if (mgmt->mtu < UINT16_MAX) { uint16_t mtu = UINT16_MAX; /* Try increasing the MTU since some commands may go * over HCI_MAX_ACL_SIZE (1024) */ if (!setsockopt(mgmt->fd, SOL_BLUETOOTH, BT_SNDMTU, &mtu, sizeof(mtu))) mgmt->mtu = mtu; } } struct mgmt *mgmt_new(int fd) { struct mgmt *mgmt; if (fd < 0) return NULL; mgmt = new0(struct mgmt, 1); mgmt->fd = fd; mgmt->close_on_unref = false; mgmt->len = 512; mgmt->buf = malloc(mgmt->len); if (!mgmt->buf) { free(mgmt); return NULL; } mgmt->io = io_new(fd); if (!mgmt->io) { free(mgmt->buf); free(mgmt); return NULL; } mgmt->request_queue = queue_new(); mgmt->reply_queue = queue_new(); mgmt->pending_list = queue_new(); mgmt->notify_list = queue_new(); if (!io_set_read_handler(mgmt->io, can_read_data, mgmt, NULL)) { queue_destroy(mgmt->notify_list, NULL); queue_destroy(mgmt->pending_list, NULL); queue_destroy(mgmt->reply_queue, NULL); queue_destroy(mgmt->request_queue, NULL); io_destroy(mgmt->io); free(mgmt->buf); free(mgmt); return NULL; } mgmt->writer_active = false; mgmt_set_mtu(mgmt); return mgmt_ref(mgmt); } struct mgmt *mgmt_new_default(void) { struct mgmt *mgmt; union { struct sockaddr common; struct sockaddr_hci hci; } addr; int fd; fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, BTPROTO_HCI); if (fd < 0) return NULL; memset(&addr, 0, sizeof(addr)); addr.hci.hci_family = AF_BLUETOOTH; addr.hci.hci_dev = HCI_DEV_NONE; addr.hci.hci_channel = HCI_CHANNEL_CONTROL; if (bind(fd, &addr.common, sizeof(addr.hci)) < 0) { close(fd); return NULL; } mgmt = mgmt_new(fd); if (!mgmt) { close(fd); return NULL; } mgmt->close_on_unref = true; return mgmt; } struct mgmt *mgmt_ref(struct mgmt *mgmt) { if (!mgmt) return NULL; __sync_fetch_and_add(&mgmt->ref_count, 1); return mgmt; } void mgmt_unref(struct mgmt *mgmt) { if (!mgmt) return; if (__sync_sub_and_fetch(&mgmt->ref_count, 1)) return; mgmt_unregister_all(mgmt); mgmt_cancel_all(mgmt); queue_destroy(mgmt->reply_queue, NULL); queue_destroy(mgmt->request_queue, NULL); io_set_write_handler(mgmt->io, NULL, NULL, NULL); io_set_read_handler(mgmt->io, NULL, NULL, NULL); io_destroy(mgmt->io); mgmt->io = NULL; if (mgmt->close_on_unref) close(mgmt->fd); if (mgmt->debug_destroy) mgmt->debug_destroy(mgmt->debug_data); free(mgmt->buf); mgmt->buf = NULL; if (!mgmt->in_notify) { queue_destroy(mgmt->notify_list, NULL); queue_destroy(mgmt->pending_list, NULL); free(mgmt); return; } } bool mgmt_set_debug(struct mgmt *mgmt, mgmt_debug_func_t callback, void *user_data, mgmt_destroy_func_t destroy) { if (!mgmt) return false; if (mgmt->debug_destroy) mgmt->debug_destroy(mgmt->debug_data); mgmt->debug_callback = callback; mgmt->debug_destroy = destroy; mgmt->debug_data = user_data; return true; } bool mgmt_set_close_on_unref(struct mgmt *mgmt, bool do_close) { if (!mgmt) return false; mgmt->close_on_unref = do_close; return true; } static struct mgmt_request *create_request(struct mgmt *mgmt, uint16_t opcode, uint16_t index, uint16_t length, const void *param, mgmt_request_func_t callback, void *user_data, mgmt_destroy_func_t destroy, int timeout) { struct mgmt_request *request; struct mgmt_hdr *hdr; if (!opcode) return NULL; if (length > 0 && !param) return NULL; if (length > mgmt->mtu) { printf("length %u > %u mgmt->mtu", length, mgmt->mtu); return NULL; } request = new0(struct mgmt_request, 1); request->len = length + MGMT_HDR_SIZE; request->buf = malloc(request->len); if (!request->buf) { free(request); return NULL; } if (length > 0) memcpy(request->buf + MGMT_HDR_SIZE, param, length); hdr = request->buf; hdr->opcode = htobs(opcode); hdr->index = htobs(index); hdr->len = htobs(length); /* Use a weak reference so requests don't prevent mgmt_unref to * cancel requests and free in case of the last reference is dropped by * the user. */ request->mgmt = mgmt; request->opcode = opcode; request->index = index; request->callback = callback; request->destroy = destroy; request->user_data = user_data; request->timeout = timeout; return request; } struct mgmt_tlv_list *mgmt_tlv_list_new(void) { struct mgmt_tlv_list *tlv_list = new0(struct mgmt_tlv_list, 1); tlv_list->tlv_queue = queue_new(); tlv_list->size = 0; return tlv_list; } static struct mgmt_tlv *mgmt_tlv_new(uint16_t type, uint8_t length, void *value) { struct mgmt_tlv *entry = malloc(sizeof(*entry) + length); if (!entry) return NULL; entry->type = htobs(type); entry->length = length; memcpy(entry->value, value, length); return entry; } static void mgmt_tlv_free(struct mgmt_tlv *entry) { free(entry); } void mgmt_tlv_list_free(struct mgmt_tlv_list *tlv_list) { queue_destroy(tlv_list->tlv_queue, free); free(tlv_list); } uint16_t mgmt_tlv_list_size(struct mgmt_tlv_list *tlv_list) { return tlv_list->size; } bool mgmt_tlv_add(struct mgmt_tlv_list *tlv_list, uint16_t type, uint8_t length, void *value) { struct mgmt_tlv *entry = mgmt_tlv_new(type, length, value); if (!entry) return false; if (!queue_push_tail(tlv_list->tlv_queue, entry)) { mgmt_tlv_free(entry); return false; } tlv_list->size += sizeof(*entry) + entry->length; return true; } static void mgmt_tlv_to_buf(void *data, void *user_data) { struct mgmt_tlv *entry = data; uint8_t **buf_ptr = user_data; size_t entry_size = sizeof(*entry) + entry->length; memcpy(*buf_ptr, entry, entry_size); *buf_ptr += entry_size; } struct mgmt_tlv_list *mgmt_tlv_list_load_from_buf(const uint8_t *buf, uint16_t len) { struct mgmt_tlv_list *tlv_list; const uint8_t *cur = buf; if (!len || !buf) return NULL; tlv_list = mgmt_tlv_list_new(); while (cur < buf + len) { struct mgmt_tlv *entry = (struct mgmt_tlv *)cur; cur += sizeof(*entry) + entry->length; if (cur > buf + len) goto failed; if (!mgmt_tlv_add(tlv_list, entry->type, entry->length, entry->value)) { goto failed; } } return tlv_list; failed: mgmt_tlv_list_free(tlv_list); return NULL; } void mgmt_tlv_list_foreach(struct mgmt_tlv_list *tlv_list, mgmt_tlv_list_foreach_func_t callback, void *user_data) { queue_foreach(tlv_list->tlv_queue, callback, user_data); } unsigned int mgmt_send_tlv(struct mgmt *mgmt, uint16_t opcode, uint16_t index, struct mgmt_tlv_list *tlv_list, mgmt_request_func_t callback, void *user_data, mgmt_destroy_func_t destroy) { uint8_t *buf, *buf_ptr; unsigned int ret; if (!tlv_list) return 0; buf = malloc(tlv_list->size); if (!buf) return 0; buf_ptr = buf; queue_foreach(tlv_list->tlv_queue, mgmt_tlv_to_buf, &buf_ptr); ret = mgmt_send(mgmt, opcode, index, tlv_list->size, (void *)buf, callback, user_data, destroy); free(buf); return ret; } unsigned int mgmt_send_timeout(struct mgmt *mgmt, uint16_t opcode, uint16_t index, uint16_t length, const void *param, mgmt_request_func_t callback, void *user_data, mgmt_destroy_func_t destroy, int timeout) { struct mgmt_request *request; if (!mgmt) return 0; request = create_request(mgmt, opcode, index, length, param, callback, user_data, destroy, timeout); if (!request) return 0; if (mgmt->next_request_id < 1) mgmt->next_request_id = 1; request->id = mgmt->next_request_id++; if (!queue_push_tail(mgmt->request_queue, request)) { free(request->buf); free(request); return 0; } wakeup_writer(mgmt); return request->id; } unsigned int mgmt_send(struct mgmt *mgmt, uint16_t opcode, uint16_t index, uint16_t length, const void *param, mgmt_request_func_t callback, void *user_data, mgmt_destroy_func_t destroy) { return mgmt_send_timeout(mgmt, opcode, index, length, param, callback, user_data, destroy, 0); } unsigned int mgmt_send_nowait(struct mgmt *mgmt, uint16_t opcode, uint16_t index, uint16_t length, const void *param, mgmt_request_func_t callback, void *user_data, mgmt_destroy_func_t destroy) { struct mgmt_request *request; if (!mgmt) return 0; request = create_request(mgmt, opcode, index, length, param, callback, user_data, destroy, 0); if (!request) return 0; if (mgmt->next_request_id < 1) mgmt->next_request_id = 1; request->id = mgmt->next_request_id++; if (!send_request(mgmt, request)) return 0; return request->id; } unsigned int mgmt_reply_timeout(struct mgmt *mgmt, uint16_t opcode, uint16_t index, uint16_t length, const void *param, mgmt_request_func_t callback, void *user_data, mgmt_destroy_func_t destroy, int timeout) { struct mgmt_request *request; if (!mgmt) return 0; request = create_request(mgmt, opcode, index, length, param, callback, user_data, destroy, timeout); if (!request) return 0; if (mgmt->next_request_id < 1) mgmt->next_request_id = 1; request->id = mgmt->next_request_id++; if (!queue_push_tail(mgmt->reply_queue, request)) { free(request->buf); free(request); return 0; } wakeup_writer(mgmt); return request->id; } unsigned int mgmt_reply(struct mgmt *mgmt, uint16_t opcode, uint16_t index, uint16_t length, const void *param, mgmt_request_func_t callback, void *user_data, mgmt_destroy_func_t destroy) { return mgmt_reply_timeout(mgmt, opcode, index, length, param, callback, user_data, destroy, 0); } bool mgmt_cancel(struct mgmt *mgmt, unsigned int id) { struct mgmt_request *request; if (!mgmt || !id) return false; request = queue_remove_if(mgmt->request_queue, match_request_id, UINT_TO_PTR(id)); if (request) goto done; request = queue_remove_if(mgmt->reply_queue, match_request_id, UINT_TO_PTR(id)); if (request) goto done; request = queue_remove_if(mgmt->pending_list, match_request_id, UINT_TO_PTR(id)); if (!request) return false; done: destroy_request(request); wakeup_writer(mgmt); return true; } bool mgmt_cancel_index(struct mgmt *mgmt, uint16_t index) { if (!mgmt) return false; queue_remove_all(mgmt->request_queue, match_request_index, UINT_TO_PTR(index), destroy_request); queue_remove_all(mgmt->reply_queue, match_request_index, UINT_TO_PTR(index), destroy_request); queue_remove_all(mgmt->pending_list, match_request_index, UINT_TO_PTR(index), destroy_request); return true; } bool mgmt_cancel_all(struct mgmt *mgmt) { if (!mgmt) return false; queue_remove_all(mgmt->pending_list, NULL, NULL, destroy_request); queue_remove_all(mgmt->reply_queue, NULL, NULL, destroy_request); queue_remove_all(mgmt->request_queue, NULL, NULL, destroy_request); return true; } unsigned int mgmt_register(struct mgmt *mgmt, uint16_t event, uint16_t index, mgmt_notify_func_t callback, void *user_data, mgmt_destroy_func_t destroy) { struct mgmt_notify *notify; if (!mgmt || !event) return 0; notify = new0(struct mgmt_notify, 1); notify->event = event; notify->index = index; notify->callback = callback; notify->destroy = destroy; notify->user_data = user_data; if (mgmt->next_notify_id < 1) mgmt->next_notify_id = 1; notify->id = mgmt->next_notify_id++; if (!queue_push_tail(mgmt->notify_list, notify)) { free(notify); return 0; } return notify->id; } bool mgmt_unregister(struct mgmt *mgmt, unsigned int id) { struct mgmt_notify *notify; if (!mgmt || !id) return false; notify = queue_remove_if(mgmt->notify_list, match_notify_id, UINT_TO_PTR(id)); if (!notify) return false; if (!mgmt->in_notify) { destroy_notify(notify); return true; } notify->removed = true; mgmt->need_notify_cleanup = true; return true; } bool mgmt_unregister_index(struct mgmt *mgmt, uint16_t index) { if (!mgmt) return false; if (mgmt->in_notify) { queue_foreach(mgmt->notify_list, mark_notify_removed, UINT_TO_PTR(index)); mgmt->need_notify_cleanup = true; } else queue_remove_all(mgmt->notify_list, match_notify_index, UINT_TO_PTR(index), destroy_notify); return true; } bool mgmt_unregister_all(struct mgmt *mgmt) { if (!mgmt) return false; if (mgmt->in_notify) { queue_foreach(mgmt->notify_list, mark_notify_removed, UINT_TO_PTR(MGMT_INDEX_NONE)); mgmt->need_notify_cleanup = true; } else queue_remove_all(mgmt->notify_list, NULL, NULL, destroy_notify); return true; } uint16_t mgmt_get_mtu(struct mgmt *mgmt) { if (!mgmt) return 0; return mgmt->mtu; } char *mgmt_iocap_generator(const char *text, int state) { static int index, len; const char *arg; if (!state) { index = 0; len = strlen(text); } while ((arg = iocap_arguments[index].name)) { index++; if (!strncmp(arg, text, len)) return strdup(arg); } return NULL; } enum mgmt_io_capability mgmt_parse_io_capability(const char *capability) { const char *arg; int index = 0; if (!strcmp(capability, "")) return MGMT_IO_CAPABILITY_KEYBOARDDISPLAY; while ((arg = iocap_arguments[index].name)) { if (!strncmp(arg, capability, strlen(capability))) return iocap_arguments[index].value; index++; } return MGMT_IO_CAPABILITY_INVALID; } bluez-5.87/src/shared/PaxHeaders/mcs.h0000644000000000000000000000005015142177620014650 xustar0020 atime=1783092946 20 ctime=1783094100 bluez-5.87/src/shared/mcs.h0000644000000000000000000000733615142177620014342 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2020 Intel Corporation. All rights reserved. * */ #ifndef __packed #define __packed __attribute__((packed)) #endif struct bt_mcs_cp_rsp { uint8_t op; uint8_t result; } __packed; /* MCP Track Position */ #define BT_MCS_POSITION_UNAVAILABLE ((int32_t)0xffffffffu) #define BT_MCS_DURATION_UNAVAILABLE ((int32_t)0xffffffffu) /* MCP Media State */ #define BT_MCS_STATE_INACTIVE 0x00 #define BT_MCS_STATE_PLAYING 0x01 #define BT_MCS_STATE_PAUSED 0x02 #define BT_MCS_STATE_SEEKING 0x03 /* MCP Playing Order */ #define BT_MCS_ORDER_SINGLE_ONCE 0x01 #define BT_MCS_ORDER_SINGLE_REPEAT 0x02 #define BT_MCS_ORDER_IN_ORDER_ONCE 0x03 #define BT_MCS_ORDER_IN_ORDER_REPEAT 0x04 #define BT_MCS_ORDER_OLDEST_ONCE 0x05 #define BT_MCS_ORDER_OLDEST_REPEAT 0x06 #define BT_MCS_ORDER_NEWEST_ONCE 0x07 #define BT_MCS_ORDER_NEWEST_REPEAT 0x08 #define BT_MCS_ORDER_SHUFFLE_ONCE 0x09 #define BT_MCS_ORDER_SHUFFLE_REPEAT 0x0a /* MCP Playing Order Supported */ #define BT_MCS_ORDER_SUPPORTED_SINGLE_ONCE 0x0001 #define BT_MCS_ORDER_SUPPORTED_SINGLE_REPEAT 0x0002 #define BT_MCS_ORDER_SUPPORTED_IN_ORDER_ONCE 0x0004 #define BT_MCS_ORDER_SUPPORTED_IN_ORDER_REPEAT 0x0008 #define BT_MCS_ORDER_SUPPORTED_OLDEST_ONCE 0x0010 #define BT_MCS_ORDER_SUPPORTED_OLDEST_REPEAT 0x0020 #define BT_MCS_ORDER_SUPPORTED_NEWEST_ONCE 0x0040 #define BT_MCS_ORDER_SUPPORTED_NEWEST_REPEAT 0x0080 #define BT_MCS_ORDER_SUPPORTED_SHUFFLE_ONCE 0x0100 #define BT_MCS_ORDER_SUPPORTED_SHUFFLE_REPEAT 0x0200 /* Control Point result codes */ #define BT_MCS_RESULT_SUCCESS 0x01 #define BT_MCS_RESULT_OP_NOT_SUPPORTED 0x02 #define BT_MCS_RESULT_MEDIA_PLAYER_INACTIVE 0x03 #define BT_MCS_RESULT_COMMAND_CANNOT_COMPLETE 0x04 /* MCP Control Point Opcodes */ #define BT_MCS_CMD_PLAY 0x01 #define BT_MCS_CMD_PAUSE 0x02 #define BT_MCS_CMD_FAST_REWIND 0x03 #define BT_MCS_CMD_FAST_FORWARD 0x04 #define BT_MCS_CMD_STOP 0x05 #define BT_MCS_CMD_MOVE_RELATIVE 0x10 #define BT_MCS_CMD_PREV_SEGMENT 0x20 #define BT_MCS_CMD_NEXT_SEGMENT 0x21 #define BT_MCS_CMD_FIRST_SEGMENT 0x22 #define BT_MCS_CMD_LAST_SEGMENT 0x23 #define BT_MCS_CMD_GOTO_SEGMENT 0x24 #define BT_MCS_CMD_PREV_TRACK 0x30 #define BT_MCS_CMD_NEXT_TRACK 0x31 #define BT_MCS_CMD_FIRST_TRACK 0x32 #define BT_MCS_CMD_LAST_TRACK 0x33 #define BT_MCS_CMD_GOTO_TRACK 0x34 #define BT_MCS_CMD_PREV_GROUP 0x40 #define BT_MCS_CMD_NEXT_GROUP 0x41 #define BT_MCS_CMD_FIRST_GROUP 0x42 #define BT_MCS_CMD_LAST_GROUP 0x43 #define BT_MCS_CMD_GOTO_GROUP 0x44 /* MCP Control Point Opcodes Supported */ #define BT_MCS_CMD_PLAY_SUPPORTED 0x00000001 #define BT_MCS_CMD_PAUSE_SUPPORTED 0x00000002 #define BT_MCS_CMD_FAST_REWIND_SUPPORTED 0x00000004 #define BT_MCS_CMD_FAST_FORWARD_SUPPORTED 0x00000008 #define BT_MCS_CMD_STOP_SUPPORTED 0x00000010 #define BT_MCS_CMD_MOVE_RELATIVE_SUPPORTED 0x00000020 #define BT_MCS_CMD_PREV_SEGMENT_SUPPORTED 0x00000040 #define BT_MCS_CMD_NEXT_SEGMENT_SUPPORTED 0x00000080 #define BT_MCS_CMD_FIRST_SEGMENT_SUPPORTED 0x00000100 #define BT_MCS_CMD_LAST_SEGMENT_SUPPORTED 0x00000200 #define BT_MCS_CMD_GOTO_SEGMENT_SUPPORTED 0x00000400 #define BT_MCS_CMD_PREV_TRACK_SUPPORTED 0x00000800 #define BT_MCS_CMD_NEXT_TRACK_SUPPORTED 0x00001000 #define BT_MCS_CMD_FIRST_TRACK_SUPPORTED 0x00002000 #define BT_MCS_CMD_LAST_TRACK_SUPPORTED 0x00004000 #define BT_MCS_CMD_GOTO_TRACK_SUPPORTED 0x00008000 #define BT_MCS_CMD_PREV_GROUP_SUPPORTED 0x00010000 #define BT_MCS_CMD_NEXT_GROUP_SUPPORTED 0x00020000 #define BT_MCS_CMD_FIRST_GROUP_SUPPORTED 0x00040000 #define BT_MCS_CMD_LAST_GROUP_SUPPORTED 0x00080000 #define BT_MCS_CMD_GOTO_GROUP_SUPPORTED 0x00100000 bluez-5.87/src/shared/PaxHeaders/mainloop.h0000644000000000000000000000005014015011623015670 xustar0020 atime=1783092780 20 ctime=1783094100 bluez-5.87/src/shared/mainloop.h0000644000000000000000000000252514015011623015355 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2002-2010 Marcel Holtmann * * */ #include #include typedef void (*mainloop_destroy_func) (void *user_data); typedef void (*mainloop_event_func) (int fd, uint32_t events, void *user_data); typedef void (*mainloop_timeout_func) (int id, void *user_data); typedef void (*mainloop_signal_func) (int signum, void *user_data); void mainloop_init(void); void mainloop_quit(void); void mainloop_exit_success(void); void mainloop_exit_failure(void); int mainloop_run(void); int mainloop_run_with_signal(mainloop_signal_func func, void *user_data); int mainloop_add_fd(int fd, uint32_t events, mainloop_event_func callback, void *user_data, mainloop_destroy_func destroy); int mainloop_modify_fd(int fd, uint32_t events); int mainloop_remove_fd(int fd); int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback, void *user_data, mainloop_destroy_func destroy); int mainloop_modify_timeout(int fd, unsigned int msec); int mainloop_remove_timeout(int id); int mainloop_set_signal(sigset_t *mask, mainloop_signal_func callback, void *user_data, mainloop_destroy_func destroy); int mainloop_sd_notify(const char *state); bluez-5.87/src/shared/PaxHeaders/hci.h0000644000000000000000000000005015200607060014620 xustar0020 atime=1783092683 20 ctime=1783094099 bluez-5.87/src/shared/hci.h0000644000000000000000000000315515200607060014305 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. * * */ #include #include typedef void (*bt_hci_destroy_func_t)(void *user_data); struct bt_hci; struct bt_hci *bt_hci_new(int fd); struct bt_hci *bt_hci_new_user_channel(uint16_t index); struct bt_hci *bt_hci_new_raw_device(uint16_t index); struct bt_hci *bt_hci_ref(struct bt_hci *hci); void bt_hci_unref(struct bt_hci *hci); bool bt_hci_set_close_on_unref(struct bt_hci *hci, bool do_close); typedef void (*bt_hci_callback_func_t)(const void *data, uint8_t size, void *user_data); unsigned int bt_hci_send(struct bt_hci *hci, uint16_t opcode, const void *data, uint8_t size, bt_hci_callback_func_t callback, void *user_data, bt_hci_destroy_func_t destroy); bool bt_hci_cancel(struct bt_hci *hci, unsigned int id); bool bt_hci_flush(struct bt_hci *hci); bool bt_hci_send_data(struct bt_hci *hci, uint8_t type, uint16_t handle, const void *data, uint8_t size); unsigned int bt_hci_register(struct bt_hci *hci, uint8_t event, bt_hci_callback_func_t callback, void *user_data, bt_hci_destroy_func_t destroy); bool bt_hci_unregister(struct bt_hci *hci, unsigned int id); unsigned int bt_hci_register_subevent(struct bt_hci *hci, uint8_t subevent, bt_hci_callback_func_t callback, void *user_data, bt_hci_destroy_func_t destroy); bool bt_hci_unregister_subevent(struct bt_hci *hci, unsigned int id); bool bt_hci_get_conn_handle(struct bt_hci *hci, const uint8_t *bdaddr, uint16_t *handle); bluez-5.87/src/shared/PaxHeaders/mcp.c0000644000000000000000000000005015200607060014627 xustar0020 atime=1783092683 20 ctime=1783094100 bluez-5.87/src/shared/mcp.c0000644000000000000000000015425615200607060014325 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. * Copyright (C) 2025 Pauli Virtanen. All rights reserved. * */ #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "bluetooth/hci.h" #include "src/shared/queue.h" #include "src/shared/util.h" #include "src/shared/timeout.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-server.h" #include "src/shared/gatt-client.h" #include "src/shared/mcp.h" #include "src/shared/mcs.h" #define BT_MCS_ERROR_VALUE_CHANGED_DURING_READ_LONG 0x80 #define DBG_MCP(mcp, fmt, ...) \ mcp_debug(mcp, "%s:%s() mcp %p | " fmt, __FILE__, __func__, mcp, \ ##__VA_ARGS__) #define DBG_SVC(service, fmt, ...) \ mcp_debug(service->mcp, "%s:%s() svc %p | " fmt, __FILE__, __func__, \ service, ##__VA_ARGS__) #define DBG_MCS(mcs, fmt, ...) \ mcs_debug(mcs, "%s:%s() mcs %p | " fmt, __FILE__, __func__, mcs, \ ##__VA_ARGS__) #define MAX_ATTR 32 #define MAX_PENDING 256 struct bt_mcs_db { bool gmcs; int ccid_value; uint32_t media_cp_op_supported_value; uint16_t playing_order_supported_value; struct gatt_db_attribute *service; struct gatt_db_attribute *media_player_name; struct gatt_db_attribute *media_player_name_ccc; struct gatt_db_attribute *track_changed; struct gatt_db_attribute *track_changed_ccc; struct gatt_db_attribute *track_title; struct gatt_db_attribute *track_title_ccc; struct gatt_db_attribute *track_duration; struct gatt_db_attribute *track_duration_ccc; struct gatt_db_attribute *track_position; struct gatt_db_attribute *track_position_ccc; struct gatt_db_attribute *playback_speed; struct gatt_db_attribute *playback_speed_ccc; struct gatt_db_attribute *seeking_speed; struct gatt_db_attribute *seeking_speed_ccc; struct gatt_db_attribute *playing_order; struct gatt_db_attribute *playing_order_ccc; struct gatt_db_attribute *playing_order_supported; struct gatt_db_attribute *media_state; struct gatt_db_attribute *media_state_ccc; struct gatt_db_attribute *media_cp; struct gatt_db_attribute *media_cp_ccc; struct gatt_db_attribute *media_cp_op_supported; struct gatt_db_attribute *media_cp_op_supported_ccc; struct gatt_db_attribute *ccid; }; struct bt_mcs_session { struct bt_mcs *mcs; struct bt_att *att; unsigned int disconn_id; /* Per-client state */ struct queue *changed; }; struct bt_mcs { struct gatt_db *db; struct bt_mcs_db ldb; struct queue *sessions; uint8_t media_state; const struct bt_mcs_callback *cb; void *user_data; }; struct bt_mcp_listener { const struct bt_mcp_listener_callback *cb; void *user_data; }; struct bt_mcp_service { struct bt_mcp *mcp; struct bt_mcs_db rdb; bool ready; unsigned int notify_id[MAX_ATTR]; unsigned int notify_id_count; unsigned int pending_id; struct queue *pending; struct queue *listeners; }; struct bt_mcp_pending { struct bt_mcp_service *service; unsigned int id; uint8_t op; struct { unsigned int client_id; struct gatt_db_attribute *attrib; uint8_t result; } write; }; struct bt_mcp { bool gmcs; struct bt_gatt_client *client; unsigned int idle_id; unsigned int db_id; bool ready; struct queue *services; const struct bt_mcp_callback *cb; void *user_data; }; #define MCS_COMMAND(name_, op_, arg_, end_state_) \ { \ .name = name_, \ .op = BT_MCS_CMD_ ## op_, \ .support = BT_MCS_CMD_ ## op_ ## _SUPPORTED, \ .int32_arg = arg_, \ .end_state = end_state_, \ } #define ANY_STATE -1 static const struct mcs_command { const char *name; uint8_t op; uint32_t support; bool int32_arg; int end_state; } mcs_command[] = { MCS_COMMAND("Play", PLAY, false, BT_MCS_STATE_PLAYING), MCS_COMMAND("Pause", PAUSE, false, BT_MCS_STATE_PAUSED), MCS_COMMAND("Fast Rewind", FAST_REWIND, false, BT_MCS_STATE_SEEKING), MCS_COMMAND("Fast Forward", FAST_FORWARD, false, BT_MCS_STATE_SEEKING), MCS_COMMAND("Stop", STOP, false, BT_MCS_STATE_PAUSED), MCS_COMMAND("Move Relative", MOVE_RELATIVE, true, ANY_STATE), MCS_COMMAND("Prev Segment", PREV_SEGMENT, false, ANY_STATE), MCS_COMMAND("Next Segment", NEXT_SEGMENT, false, ANY_STATE), MCS_COMMAND("First Segment", FIRST_SEGMENT, false, ANY_STATE), MCS_COMMAND("Last Segment", LAST_SEGMENT, false, ANY_STATE), MCS_COMMAND("Goto Segment", GOTO_SEGMENT, true, ANY_STATE), MCS_COMMAND("Prev Track", PREV_TRACK, false, ANY_STATE), MCS_COMMAND("Next Track", NEXT_TRACK, false, ANY_STATE), MCS_COMMAND("First Track", FIRST_TRACK, false, ANY_STATE), MCS_COMMAND("Last Track", LAST_TRACK, false, ANY_STATE), MCS_COMMAND("Goto Track", GOTO_TRACK, true, ANY_STATE), MCS_COMMAND("Prev Group", PREV_GROUP, false, ANY_STATE), MCS_COMMAND("Next Group", NEXT_GROUP, false, ANY_STATE), MCS_COMMAND("First Group", FIRST_GROUP, false, ANY_STATE), MCS_COMMAND("Last Group", LAST_GROUP, false, ANY_STATE), MCS_COMMAND("Goto Group", GOTO_GROUP, true, ANY_STATE), }; #define MCS_PLAYING_ORDER(name) \ { BT_MCS_ORDER_ ## name, BT_MCS_ORDER_SUPPORTED_ ## name } static const struct { uint8_t order; uint16_t support; } mcs_playing_orders[] = { MCS_PLAYING_ORDER(SINGLE_ONCE), MCS_PLAYING_ORDER(SINGLE_REPEAT), MCS_PLAYING_ORDER(IN_ORDER_ONCE), MCS_PLAYING_ORDER(IN_ORDER_REPEAT), MCS_PLAYING_ORDER(OLDEST_ONCE), MCS_PLAYING_ORDER(OLDEST_REPEAT), MCS_PLAYING_ORDER(NEWEST_ONCE), MCS_PLAYING_ORDER(NEWEST_REPEAT), MCS_PLAYING_ORDER(SHUFFLE_ONCE), MCS_PLAYING_ORDER(SHUFFLE_REPEAT) }; typedef bool (*mcs_command_func_t)(void *data); typedef bool (*mcs_command_func_int32_t)(void *data, int32_t offset); typedef void (*mcs_get_func_t)(struct bt_mcs *mcs, struct iovec *buf, size_t size); typedef bool (*mcs_set_func_t)(struct bt_mcs *mcs, void *data); static struct queue *servers; static uint8_t servers_ccid; /* * MCS Server */ static void mcs_debug_func(const char *str, void *user_data) { struct bt_mcs *mcs = user_data; mcs->cb->debug(mcs->user_data, str); } static void mcs_debug(struct bt_mcs *mcs, const char *format, ...) { va_list ap; if (!mcs || !format || !mcs->cb->debug) return; va_start(ap, format); util_debug_va(mcs_debug_func, mcs, format, ap); va_end(ap); } static const struct mcs_command *mcs_get_command(uint8_t op) { size_t i; for (i = 0; i < ARRAY_SIZE(mcs_command); ++i) if (mcs_command[i].op == op) return &mcs_command[i]; return NULL; } static mcs_command_func_t mcs_get_handler(struct bt_mcs *mcs, uint8_t op) { switch (op) { case BT_MCS_CMD_PLAY: return mcs->cb->play; case BT_MCS_CMD_PAUSE: return mcs->cb->pause; case BT_MCS_CMD_FAST_REWIND: return mcs->cb->fast_rewind; case BT_MCS_CMD_FAST_FORWARD: return mcs->cb->fast_forward; case BT_MCS_CMD_STOP: return mcs->cb->stop; case BT_MCS_CMD_PREV_SEGMENT: return mcs->cb->previous_segment; case BT_MCS_CMD_NEXT_SEGMENT: return mcs->cb->next_segment; case BT_MCS_CMD_FIRST_SEGMENT: return mcs->cb->first_segment; case BT_MCS_CMD_LAST_SEGMENT: return mcs->cb->last_segment; case BT_MCS_CMD_PREV_TRACK: return mcs->cb->previous_track; case BT_MCS_CMD_NEXT_TRACK: return mcs->cb->next_track; case BT_MCS_CMD_FIRST_TRACK: return mcs->cb->first_track; case BT_MCS_CMD_LAST_TRACK: return mcs->cb->last_track; case BT_MCS_CMD_PREV_GROUP: return mcs->cb->previous_group; case BT_MCS_CMD_NEXT_GROUP: return mcs->cb->next_group; case BT_MCS_CMD_FIRST_GROUP: return mcs->cb->first_group; case BT_MCS_CMD_LAST_GROUP: return mcs->cb->last_group; } return NULL; } static mcs_command_func_int32_t mcs_get_handler_int32(struct bt_mcs *mcs, uint8_t op) { switch (op) { case BT_MCS_CMD_MOVE_RELATIVE: return mcs->cb->move_relative; case BT_MCS_CMD_GOTO_SEGMENT: return mcs->cb->goto_segment; case BT_MCS_CMD_GOTO_TRACK: return mcs->cb->goto_track; case BT_MCS_CMD_GOTO_GROUP: return mcs->cb->goto_group; } return NULL; } static uint32_t mcs_get_supported(struct bt_mcs *mcs) { unsigned int i; uint32_t value = 0; for (i = 0; i < ARRAY_SIZE(mcs_command); ++i) value |= mcs_command[i].support; if (mcs->cb->media_cp_op_supported) value = mcs->cb->media_cp_op_supported(mcs->user_data); for (i = 0; i < ARRAY_SIZE(mcs_command); ++i) { void *handler = mcs_get_handler(mcs, mcs_command[i].op); if (!handler) handler = mcs_get_handler_int32(mcs, mcs_command[i].op); if (!handler) value &= ~mcs_command[i].support; } mcs->ldb.media_cp_op_supported_value = value; return value; } static void write_media_cp(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *data, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { struct bt_mcs *mcs = user_data; struct iovec iov = { .iov_base = (void *)data, .iov_len = len }; const struct mcs_command *cmd = NULL; struct bt_mcs_cp_rsp rsp = { .op = 0, .result = BT_MCS_RESULT_COMMAND_CANNOT_COMPLETE }; int ret = 0; int32_t arg = 0; uint8_t op; bool ok = false; if (offset) { ret = BT_ATT_ERROR_INVALID_OFFSET; goto respond; } if (!util_iov_pull_u8(&iov, &op)) { ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; goto respond; } rsp.op = op; cmd = mcs_get_command(op); if (!cmd || !(cmd->support & mcs_get_supported(mcs))) { rsp.result = BT_MCS_RESULT_OP_NOT_SUPPORTED; goto respond; } if (cmd->int32_arg && !util_iov_pull_le32(&iov, (uint32_t *)&arg)) { ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; rsp.op = 0; goto respond; } DBG_MCS(mcs, "Command %s", cmd->name); /* We may attempt to perform the operation also if inactive (MCS v1.0.1 * p. 26), leave decision to upper layer. */ ok = cmd->int32_arg ? mcs_get_handler_int32(mcs, op)(mcs->user_data, arg) : mcs_get_handler(mcs, op)(mcs->user_data); if (ok) rsp.result = BT_MCS_RESULT_SUCCESS; else if (mcs->media_state == BT_MCS_STATE_INACTIVE) rsp.result = BT_MCS_RESULT_MEDIA_PLAYER_INACTIVE; else rsp.result = BT_MCS_RESULT_COMMAND_CANNOT_COMPLETE; respond: DBG_MCS(mcs, "%s ret %u result %u", cmd ? cmd->name : "-", ret, rsp.result); gatt_db_attribute_write_result(attrib, id, ret); if (!rsp.op) return; /* Make state transition immediately if command was successful and has * specified end state. Upper layer shall emit spontaneous transitions * to correct as needed. */ if (ok) { bt_mcs_set_media_state(mcs, cmd->end_state); switch (op) { case BT_MCS_CMD_STOP: case BT_MCS_CMD_PREV_TRACK: case BT_MCS_CMD_NEXT_TRACK: case BT_MCS_CMD_FIRST_TRACK: case BT_MCS_CMD_LAST_TRACK: case BT_MCS_CMD_GOTO_TRACK: case BT_MCS_CMD_PREV_GROUP: case BT_MCS_CMD_NEXT_GROUP: case BT_MCS_CMD_FIRST_GROUP: case BT_MCS_CMD_LAST_GROUP: case BT_MCS_CMD_GOTO_GROUP: if (mcs->cb->set_track_position) mcs->cb->set_track_position(mcs->user_data, 0); bt_mcs_changed(mcs, MCS_TRACK_POSITION_CHRC_UUID); break; } } gatt_db_attribute_notify(attrib, (uint8_t *)&rsp, sizeof(rsp), att); } void bt_mcs_set_media_state(struct bt_mcs *mcs, uint8_t state) { switch (state) { case BT_MCS_STATE_INACTIVE: case BT_MCS_STATE_PLAYING: case BT_MCS_STATE_PAUSED: case BT_MCS_STATE_SEEKING: break; default: return; } if (state == mcs->media_state) return; mcs->media_state = state; bt_mcs_changed(mcs, MCS_MEDIA_STATE_CHRC_UUID); } uint8_t bt_mcs_get_media_state(struct bt_mcs *mcs) { return mcs->media_state; } static void get_media_player_name(struct bt_mcs *mcs, struct iovec *buf, size_t size) { if (mcs->cb->media_player_name) mcs->cb->media_player_name(mcs->user_data, buf, size); } static void get_track_changed(struct bt_mcs *mcs, struct iovec *buf, size_t size) { } static void get_track_title(struct bt_mcs *mcs, struct iovec *buf, size_t size) { if (mcs->cb->track_title) mcs->cb->track_title(mcs->user_data, buf, size); } static void get_track_duration(struct bt_mcs *mcs, struct iovec *buf, size_t size) { int32_t value = BT_MCS_DURATION_UNAVAILABLE; if (mcs->cb->track_duration) value = mcs->cb->track_duration(mcs->user_data); util_iov_push_le32(buf, (uint32_t)value); } static void get_track_position(struct bt_mcs *mcs, struct iovec *buf, size_t size) { int32_t value = BT_MCS_POSITION_UNAVAILABLE; if (mcs->cb->track_position) value = mcs->cb->track_position(mcs->user_data); util_iov_push_le32(buf, (uint32_t)value); } static void get_playback_speed(struct bt_mcs *mcs, struct iovec *buf, size_t size) { int8_t value = 0x00; if (mcs->cb->playback_speed) value = mcs->cb->playback_speed(mcs->user_data); util_iov_push_u8(buf, (uint8_t)value); } static void get_seeking_speed(struct bt_mcs *mcs, struct iovec *buf, size_t size) { int8_t value = 0x00; if (mcs->cb->seeking_speed) value = mcs->cb->seeking_speed(mcs->user_data); util_iov_push_u8(buf, (uint8_t)value); } static void get_playing_order(struct bt_mcs *mcs, struct iovec *buf, size_t size) { uint8_t value = BT_MCS_ORDER_IN_ORDER_REPEAT; if (mcs->cb->playing_order) value = mcs->cb->playing_order(mcs->user_data); util_iov_push_u8(buf, value); } static void get_playing_order_supported(struct bt_mcs *mcs, struct iovec *buf, size_t size) { uint16_t value = BT_MCS_ORDER_SUPPORTED_IN_ORDER_REPEAT; if (mcs->cb->playing_order_supported) value = mcs->cb->playing_order_supported(mcs->user_data); util_iov_push_le16(buf, value); } static void get_media_state(struct bt_mcs *mcs, struct iovec *buf, size_t size) { util_iov_push_u8(buf, mcs->media_state); } static void get_media_cp_op_supported(struct bt_mcs *mcs, struct iovec *buf, size_t size) { util_iov_push_le32(buf, mcs_get_supported(mcs)); } static void get_ccid(struct bt_mcs *mcs, struct iovec *buf, size_t size) { util_iov_push_u8(buf, mcs->ldb.ccid_value); } static bool set_track_position(struct bt_mcs *mcs, void *data) { int32_t value = (int32_t)get_le32(data); DBG_MCS(mcs, "Set Track Position %d", value); if (mcs->cb->set_track_position) return mcs->cb->set_track_position(mcs->user_data, value); return false; } static bool set_playback_speed(struct bt_mcs *mcs, void *data) { int8_t value = (int8_t)get_u8(data); DBG_MCS(mcs, "Set Playback Speed %d", value); if (mcs->cb->set_playback_speed) return mcs->cb->set_playback_speed(mcs->user_data, value); return false; } static bool set_playing_order(struct bt_mcs *mcs, void *data) { uint8_t value = get_u8(data); DBG_MCS(mcs, "Set Playing Order %u", value); if (mcs->cb->set_playing_order) return mcs->cb->set_playing_order(mcs->user_data, value); return false; } static bool match_session_att(const void *data, const void *match_data) { const struct bt_mcs_session *session = data; return session->att == match_data; } static void session_destroy(void *data) { struct bt_mcs_session *session = data; struct bt_mcs *mcs = session->mcs; if (mcs) queue_remove(mcs->sessions, session); queue_destroy(session->changed, NULL); free(session); } static void session_remove(void *user_data) { struct bt_mcs_session *session = user_data; session->mcs = NULL; bt_att_unregister_disconnect(session->att, session->disconn_id); } static struct bt_mcs_session *get_session(struct bt_mcs *mcs, struct bt_att *att) { struct bt_mcs_session *session; session = queue_find(mcs->sessions, match_session_att, att); if (session) return session; session = new0(struct bt_mcs_session, 1); session->disconn_id = bt_att_register_disconnect(att, NULL, session, session_destroy); if (!session->disconn_id) { free(session); return NULL; } session->mcs = mcs; session->att = att; session->changed = queue_new(); queue_push_tail(mcs->sessions, session); return session; } static void session_changed(void *data, void *user_data) { struct bt_mcs_session *session = data; struct gatt_db_attribute *attrib = user_data; if (!queue_find(session->changed, NULL, attrib)) queue_push_tail(session->changed, attrib); } static void read_result(struct bt_mcs *mcs, struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, struct bt_att *att, mcs_get_func_t get) { uint8_t buf[BT_ATT_MAX_VALUE_LEN]; struct iovec iov = { .iov_base = buf, .iov_len = 0 }; struct bt_mcs_session *session = get_session(mcs, att); if (!session) { gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_UNLIKELY, NULL, 0); return; } if (!offset) { queue_remove(session->changed, attrib); } else if (queue_find(session->changed, NULL, attrib)) { gatt_db_attribute_read_result(attrib, id, BT_MCS_ERROR_VALUE_CHANGED_DURING_READ_LONG, NULL, 0); return; } get(mcs, &iov, sizeof(buf)); if (offset > iov.iov_len) { gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_INVALID_OFFSET, NULL, 0); return; } gatt_db_attribute_read_result(attrib, id, 0, buf + offset, iov.iov_len - offset); } #define READ_FUNC(name) \ static void read_ ## name(struct gatt_db_attribute *attrib, \ unsigned int id, uint16_t offset, \ uint8_t opcode, struct bt_att *att, \ void *user_data) \ { \ DBG_MCS(user_data, ""); \ read_result(user_data, attrib, id, offset, att, get_ ##name); \ } READ_FUNC(media_player_name) READ_FUNC(track_title) READ_FUNC(track_duration) READ_FUNC(track_position) READ_FUNC(playback_speed) READ_FUNC(seeking_speed) READ_FUNC(playing_order) READ_FUNC(playing_order_supported) READ_FUNC(media_state) READ_FUNC(media_cp_op_supported) READ_FUNC(ccid) static void write_result(struct bt_mcs *mcs, struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *data, size_t len, mcs_get_func_t get, mcs_set_func_t set) { uint8_t buf[4]; struct iovec iov = { .iov_base = buf, .iov_len = 0 }; bt_uuid_t uuid; uint8_t ret; get(mcs, &iov, sizeof(buf)); if (len > iov.iov_len) { gatt_db_attribute_write_result(attrib, id, BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN); return; } if (offset + len > iov.iov_len) { gatt_db_attribute_write_result(attrib, id, BT_ATT_ERROR_INVALID_OFFSET); return; } memcpy(iov.iov_base + offset, data, len); if (set(mcs, iov.iov_base)) ret = 0; else ret = BT_ATT_ERROR_VALUE_NOT_ALLOWED; gatt_db_attribute_write_result(attrib, id, ret); if (!gatt_db_attribute_get_char_data(attrib, NULL, NULL, NULL, NULL, &uuid)) return; if (!ret) bt_mcs_changed(mcs, uuid.value.u16); } #define WRITE_FUNC(name) \ static void write_ ## name(struct gatt_db_attribute *attrib, \ unsigned int id, uint16_t offset, \ const uint8_t *data, size_t len, \ uint8_t opcode, struct bt_att *att, \ void *user_data) \ { write_result(user_data, attrib, id, offset, data, len, \ get_ ## name, set_ ## name); } WRITE_FUNC(track_position) WRITE_FUNC(playback_speed) WRITE_FUNC(playing_order) void bt_mcs_changed(struct bt_mcs *mcs, uint16_t chrc_uuid) { struct { struct gatt_db_attribute *attr; mcs_get_func_t get; } attrs[] = { { mcs->ldb.media_player_name, get_media_player_name }, { mcs->ldb.track_changed, get_track_changed }, { mcs->ldb.track_title, get_track_title }, { mcs->ldb.track_duration, get_track_duration }, { mcs->ldb.track_position, get_track_position }, { mcs->ldb.playback_speed, get_playback_speed }, { mcs->ldb.seeking_speed, get_seeking_speed }, { mcs->ldb.playing_order, get_playing_order }, { mcs->ldb.media_state, get_media_state }, { mcs->ldb.media_cp_op_supported, get_media_cp_op_supported }, }; uint8_t buf[BT_ATT_MAX_VALUE_LEN]; struct iovec iov = { .iov_base = buf, .iov_len = 0 }; unsigned int i; bt_uuid_t uuid, uuid_attr; uint8_t props; bt_uuid16_create(&uuid, chrc_uuid); for (i = 0; i < ARRAY_SIZE(attrs); ++i) { if (!gatt_db_attribute_get_char_data(attrs[i].attr, NULL, NULL, &props, NULL, &uuid_attr)) continue; if (bt_uuid_cmp(&uuid_attr, &uuid)) continue; queue_foreach(mcs->sessions, session_changed, attrs[i].attr); DBG_MCS(mcs, "Notify 0x%04x", chrc_uuid); attrs[i].get(mcs, &iov, sizeof(buf)); /* No client-specific state: notify everyone */ gatt_db_attribute_notify(attrs[i].attr, iov.iov_base, iov.iov_len, NULL); break; } } static bool mcs_init_db(struct bt_mcs *mcs, bool is_gmcs) { struct gatt_db *db = mcs->db; struct bt_mcs_db *ldb = &mcs->ldb; bt_uuid_t uuid; bt_uuid16_create(&uuid, is_gmcs ? GMCS_UUID : MCS_UUID); ldb->service = gatt_db_add_service(db, &uuid, true, 38); /* Add also optional CCC */ bt_uuid16_create(&uuid, MCS_MEDIA_PLAYER_NAME_CHRC_UUID); ldb->media_player_name = gatt_db_service_add_characteristic( ldb->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, read_media_player_name, NULL, mcs); ldb->media_player_name_ccc = gatt_db_service_add_ccc( ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, MCS_TRACK_CHANGED_CHRC_UUID); ldb->track_changed = gatt_db_service_add_characteristic( ldb->service, &uuid, BT_ATT_PERM_NONE, BT_GATT_CHRC_PROP_NOTIFY, NULL, NULL, mcs); gatt_db_attribute_set_fixed_length(ldb->track_changed, 0); ldb->track_changed_ccc = gatt_db_service_add_ccc( ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, MCS_TRACK_TITLE_CHRC_UUID); ldb->track_title = gatt_db_service_add_characteristic( ldb->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, read_track_title, NULL, mcs); ldb->track_title_ccc = gatt_db_service_add_ccc( ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, MCS_TRACK_DURATION_CHRC_UUID); ldb->track_duration = gatt_db_service_add_characteristic( ldb->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, read_track_duration, NULL, mcs); gatt_db_attribute_set_fixed_length(ldb->track_duration, sizeof(int32_t)); ldb->track_duration_ccc = gatt_db_service_add_ccc( ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, MCS_TRACK_POSITION_CHRC_UUID); ldb->track_position = gatt_db_service_add_characteristic( ldb->service, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY | BT_GATT_CHRC_PROP_WRITE | BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP, read_track_position, write_track_position, mcs); gatt_db_attribute_set_fixed_length(ldb->track_position, sizeof(int32_t)); ldb->track_position_ccc = gatt_db_service_add_ccc( ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, MCS_PLAYBACK_SPEED_CHRC_UUID); ldb->playback_speed = gatt_db_service_add_characteristic( ldb->service, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY | BT_GATT_CHRC_PROP_WRITE | BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP, read_playback_speed, write_playback_speed, mcs); gatt_db_attribute_set_fixed_length(ldb->playback_speed, sizeof(int8_t)); ldb->playback_speed_ccc = gatt_db_service_add_ccc( ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, MCS_SEEKING_SPEED_CHRC_UUID); ldb->seeking_speed = gatt_db_service_add_characteristic( ldb->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, read_seeking_speed, NULL, mcs); gatt_db_attribute_set_fixed_length(ldb->seeking_speed, sizeof(int8_t)); ldb->seeking_speed_ccc = gatt_db_service_add_ccc( ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, MCS_PLAYING_ORDER_CHRC_UUID); ldb->playing_order = gatt_db_service_add_characteristic( ldb->service, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY | BT_GATT_CHRC_PROP_WRITE | BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP, read_playing_order, write_playing_order, mcs); gatt_db_attribute_set_fixed_length(ldb->playing_order, sizeof(uint8_t)); ldb->playing_order_ccc = gatt_db_service_add_ccc( ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, MCS_PLAYING_ORDER_SUPPORTED_CHRC_UUID); ldb->playing_order_supported = gatt_db_service_add_characteristic( ldb->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, read_playing_order_supported, NULL, mcs); gatt_db_attribute_set_fixed_length(ldb->playing_order_supported, sizeof(uint16_t)); bt_uuid16_create(&uuid, MCS_MEDIA_STATE_CHRC_UUID); ldb->media_state = gatt_db_service_add_characteristic( ldb->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, read_media_state, NULL, mcs); gatt_db_attribute_set_fixed_length(ldb->media_state, sizeof(uint8_t)); ldb->media_state_ccc = gatt_db_service_add_ccc( ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, MCS_MEDIA_CP_CHRC_UUID); ldb->media_cp = gatt_db_service_add_characteristic( ldb->service, &uuid, BT_ATT_PERM_WRITE, BT_GATT_CHRC_PROP_WRITE | BT_GATT_CHRC_PROP_NOTIFY | BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP, NULL, write_media_cp, mcs); ldb->media_cp_ccc = gatt_db_service_add_ccc( ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, MCS_MEDIA_CP_OP_SUPPORTED_CHRC_UUID); ldb->media_cp_op_supported = gatt_db_service_add_characteristic( ldb->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, read_media_cp_op_supported, NULL, mcs); gatt_db_attribute_set_fixed_length(ldb->media_cp_op_supported, sizeof(uint32_t)); ldb->media_cp_op_supported_ccc = gatt_db_service_add_ccc( ldb->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, MCS_CCID_CHRC_UUID); ldb->ccid = gatt_db_service_add_characteristic( ldb->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, read_ccid, NULL, mcs); gatt_db_attribute_set_fixed_length(ldb->ccid, sizeof(uint8_t)); return true; } uint8_t bt_mcs_get_ccid(struct bt_mcs *mcs) { return mcs->ldb.ccid_value; } struct match_mcs_data { struct gatt_db *db; bool gmcs; bool any; int ccid; }; static bool match_mcs(const void *data, const void *user_data) { const struct bt_mcs *mcs = data; const struct match_mcs_data *match = user_data; if (match->db != mcs->db) return false; if (match->gmcs) return mcs->ldb.gmcs; if (match->any) return true; return match->ccid == mcs->ldb.ccid_value; } static int mcs_alloc_ccid(struct gatt_db *db) { unsigned int ccid; if (!db) return 0; for (ccid = servers_ccid; ccid < servers_ccid + 0x100u; ccid++) { struct match_mcs_data match = { .db = db, .ccid = ccid & 0xff }; if (!queue_find(servers, match_mcs, &match)) { servers_ccid = ccid + 1; return match.ccid; } } return -ENOENT; } void bt_mcs_test_util_reset_ccid(void) { servers_ccid = 0; } struct bt_mcs *bt_mcs_register(struct gatt_db *db, bool is_gmcs, const struct bt_mcs_callback *cb, void *user_data) { struct bt_mcs *mcs; int ccid; if (!db || !cb) return NULL; if (is_gmcs) { struct match_mcs_data match = { .db = db, .gmcs = true }; /* Only one GMCS possible */ if (queue_find(servers, match_mcs, &match)) return NULL; } ccid = mcs_alloc_ccid(db); if (ccid < 0) return NULL; mcs = new0(struct bt_mcs, 1); mcs->db = db; mcs->ldb.ccid_value = ccid; mcs->cb = cb; mcs->user_data = user_data; mcs->media_state = BT_MCS_STATE_INACTIVE; mcs->sessions = queue_new(); if (!mcs_init_db(mcs, is_gmcs)) { free(mcs); return NULL; } gatt_db_ref(mcs->db); if (!servers) servers = queue_new(); queue_push_tail(servers, mcs); gatt_db_service_set_active(mcs->ldb.service, true); return mcs; } void bt_mcs_unregister(struct bt_mcs *mcs) { if (!mcs) return; if (mcs->cb->destroy) mcs->cb->destroy(mcs->user_data); queue_remove(servers, mcs); gatt_db_remove_service(mcs->db, mcs->ldb.service); gatt_db_unref(mcs->db); if (queue_isempty(servers)) { queue_destroy(servers, NULL); servers = NULL; } queue_destroy(mcs->sessions, session_remove); free(mcs); } void bt_mcs_unregister_all(struct gatt_db *db) { struct bt_mcs *mcs; do { struct match_mcs_data match = { .db = db, .any = true }; mcs = queue_find(servers, match_mcs, &match); bt_mcs_unregister(mcs); } while (mcs); } /* * MCP Client */ static void mcp_service_reread(struct bt_mcp_service *service, struct gatt_db_attribute *attrib, bool skip_notify); static void foreach_mcs_char(struct gatt_db_attribute *attr, void *user_data); static void mcp_debug_func(const char *str, void *user_data) { struct bt_mcp *mcp = user_data; mcp->cb->debug(mcp->user_data, str); } static void mcp_debug(struct bt_mcp *mcp, const char *format, ...) { va_list ap; if (!mcp || !format || !mcp->cb->debug) return; va_start(ap, format); util_debug_va(mcp_debug_func, mcp, format, ap); va_end(ap); } static bool match_ccid(const void *data, const void *user_data) { const struct bt_mcp_service *service = data; return service->rdb.ccid_value == (int)PTR_TO_UINT(user_data); } static struct bt_mcp_service *mcp_service(struct bt_mcp *mcp, uint8_t ccid) { if (!mcp) return NULL; return queue_find(mcp->services, match_ccid, UINT_TO_PTR(ccid)); } static bool match_pending(const void *data, const void *user_data) { const struct bt_mcp_pending *pending = data; return pending->id == PTR_TO_UINT(user_data); } static struct bt_mcp_pending *mcp_pending_new(struct bt_mcp_service *service) { struct bt_mcp_pending *pending; if (queue_length(service->pending) > MAX_PENDING) return NULL; while (!service->pending_id || queue_find(service->pending, match_pending, UINT_TO_PTR(service->pending_id))) service->pending_id++; pending = new0(struct bt_mcp_pending, 1); pending->service = service; pending->id = service->pending_id++; return pending; } static unsigned int mcp_send(struct bt_mcp_service *service, uint8_t *buf, uint16_t length) { struct bt_mcp *mcp = service->mcp; uint16_t handle; struct bt_mcp_pending *pending; int ret; uint8_t op = buf[0]; if (!gatt_db_attribute_get_char_data(service->rdb.media_cp, NULL, &handle, NULL, NULL, NULL)) return 0; pending = mcp_pending_new(service); if (!pending) return 0; ret = bt_gatt_client_write_without_response(mcp->client, handle, false, buf, length); if (!ret) { free(pending); return 0; } pending->op = op; queue_push_tail(service->pending, pending); DBG_SVC(service, "%u", pending->id); return pending->id; } static void mcp_pending_write_cb(bool success, uint8_t att_ecode, void *user_data) { struct bt_mcp_pending *pending = user_data; if (!success) { pending->write.result = BT_MCS_RESULT_COMMAND_CANNOT_COMPLETE; return; } pending->write.result = BT_MCS_RESULT_SUCCESS; /* If the attribute doesn't have notify, reread to get the new value */ mcp_service_reread(pending->service, pending->write.attrib, true); } static void mcp_pending_write_done(void *user_data) { struct bt_mcp_pending *pending = user_data; struct bt_mcp_service *service = pending->service; struct bt_mcp *mcp = service->mcp; DBG_SVC(service, "write %u", pending->id); queue_remove(service->pending, pending); if (mcp->cb->complete) mcp->cb->complete(mcp->user_data, pending->id, pending->write.result); free(pending); } static unsigned int mcp_write_chrc(struct bt_mcp_service *service, struct gatt_db_attribute *attrib, void *data, uint16_t length) { struct bt_mcp *mcp; struct bt_mcp_pending *pending; uint16_t handle; if (!service) return 0; mcp = service->mcp; if (!gatt_db_attribute_get_char_data(attrib, NULL, &handle, NULL, NULL, NULL)) return 0; pending = mcp_pending_new(service); if (!pending) return 0; pending->write.attrib = attrib; pending->write.client_id = bt_gatt_client_write_value(mcp->client, handle, data, length, mcp_pending_write_cb, pending, mcp_pending_write_done); if (!pending->write.client_id) { free(pending); return 0; } queue_push_tail(service->pending, pending); return pending->id; } static bool match_pending_write(const void *data, const void *user_data) { const struct bt_mcp_pending *pending = data; return !pending->op; } static void mcp_cancel_pending_writes(struct bt_mcp_service *service) { struct bt_mcp_pending *pending; struct bt_gatt_client *client = service->mcp->client; do { pending = queue_remove_if(service->pending, match_pending_write, NULL); if (pending) { if (!bt_gatt_client_cancel(client, pending->write.client_id)) free(pending); } } while (pending); } static unsigned int mcp_command(struct bt_mcp *mcp, uint8_t ccid, uint8_t op, int32_t arg) { const struct mcs_command *cmd = mcs_get_command(op); struct bt_mcp_service *service = mcp_service(mcp, ccid); uint8_t buf[5]; struct iovec iov = { .iov_base = buf, .iov_len = 0 }; if (!service || !cmd) return 0; if (!(service->rdb.media_cp_op_supported_value & cmd->support)) return 0; DBG_SVC(service, "%s %d", cmd->name, arg); util_iov_push_u8(&iov, op); if (cmd->int32_arg) util_iov_push_le32(&iov, arg); return mcp_send(service, iov.iov_base, iov.iov_len); } unsigned int bt_mcp_play(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_PLAY, 0); } unsigned int bt_mcp_pause(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_PAUSE, 0); } unsigned int bt_mcp_fast_rewind(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_FAST_REWIND, 0); } unsigned int bt_mcp_fast_forward(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_FAST_FORWARD, 0); } unsigned int bt_mcp_stop(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_STOP, 0); } unsigned int bt_mcp_move_relative(struct bt_mcp *mcp, uint8_t ccid, int32_t offset) { return mcp_command(mcp, ccid, BT_MCS_CMD_MOVE_RELATIVE, offset); } unsigned int bt_mcp_previous_segment(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_PREV_SEGMENT, 0); } unsigned int bt_mcp_next_segment(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_NEXT_SEGMENT, 0); } unsigned int bt_mcp_first_segment(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_FIRST_SEGMENT, 0); } unsigned int bt_mcp_last_segment(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_LAST_SEGMENT, 0); } unsigned int bt_mcp_goto_segment(struct bt_mcp *mcp, uint8_t ccid, int32_t n) { return mcp_command(mcp, ccid, BT_MCS_CMD_GOTO_SEGMENT, n); } unsigned int bt_mcp_previous_track(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_PREV_TRACK, 0); } unsigned int bt_mcp_next_track(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_NEXT_TRACK, 0); } unsigned int bt_mcp_first_track(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_FIRST_TRACK, 0); } unsigned int bt_mcp_last_track(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_LAST_TRACK, 0); } unsigned int bt_mcp_goto_track(struct bt_mcp *mcp, uint8_t ccid, int32_t n) { return mcp_command(mcp, ccid, BT_MCS_CMD_GOTO_TRACK, n); } unsigned int bt_mcp_previous_group(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_PREV_GROUP, 0); } unsigned int bt_mcp_next_group(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_NEXT_GROUP, 0); } unsigned int bt_mcp_first_group(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_FIRST_GROUP, 0); } unsigned int bt_mcp_last_group(struct bt_mcp *mcp, uint8_t ccid) { return mcp_command(mcp, ccid, BT_MCS_CMD_LAST_GROUP, 0); } unsigned int bt_mcp_goto_group(struct bt_mcp *mcp, uint8_t ccid, int32_t n) { return mcp_command(mcp, ccid, BT_MCS_CMD_GOTO_GROUP, n); } unsigned int bt_mcp_set_track_position(struct bt_mcp *mcp, uint8_t ccid, int32_t position) { struct bt_mcp_service *service = mcp_service(mcp, ccid); position = cpu_to_le32(position); return mcp_write_chrc(service, service->rdb.track_position, &position, sizeof(position)); } unsigned int bt_mcp_set_playback_speed(struct bt_mcp *mcp, uint8_t ccid, int8_t value) { struct bt_mcp_service *service = mcp_service(mcp, ccid); return mcp_write_chrc(service, service->rdb.playback_speed, &value, sizeof(value)); } unsigned int bt_mcp_set_playing_order(struct bt_mcp *mcp, uint8_t ccid, uint8_t value) { struct bt_mcp_service *service = mcp_service(mcp, ccid); uint16_t support = 0; unsigned int i; if (!service) return 0; for (i = 0; i < ARRAY_SIZE(mcs_playing_orders); ++i) { if (mcs_playing_orders[i].order == value) { support = mcs_playing_orders[i].support; break; } } if (!(service->rdb.playing_order_supported_value & support)) return 0; return mcp_write_chrc(service, service->rdb.playing_order, &value, sizeof(value)); } uint16_t bt_mcp_get_supported_playing_order(struct bt_mcp *mcp, uint8_t ccid) { struct bt_mcp_service *service = mcp_service(mcp, ccid); if (!service) return 0; return service->rdb.playing_order_supported_value; } uint32_t bt_mcp_get_supported_commands(struct bt_mcp *mcp, uint8_t ccid) { struct bt_mcp_service *service = mcp_service(mcp, ccid); if (!service) return 0; return service->rdb.media_cp_op_supported_value; } #define LISTENER_CB(service, method, ...) \ do { \ const struct queue_entry *entry = \ queue_get_entries((service)->listeners); \ for (; entry; entry = entry->next) { \ struct bt_mcp_listener *listener = entry->data; \ if (listener->cb->method) \ listener->cb->method(listener->user_data, \ ## __VA_ARGS__); \ } \ } while (0) static void update_media_player_name(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_mcp_service *service = user_data; if (!success) { DBG_SVC(service, "Unable to read Media Player Name: " "error 0x%02x", att_ecode); return; } DBG_SVC(service, "Media Player Name"); LISTENER_CB(service, media_player_name, value, length); } static void update_track_changed(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_mcp_service *service = user_data; if (!success) { DBG_SVC(service, "Unable to read Track Changed: " "error 0x%02x", att_ecode); return; } mcp_service_reread(service, NULL, true); DBG_SVC(service, "Track Changed"); LISTENER_CB(service, track_changed); } static void update_track_title(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_mcp_service *service = user_data; if (!success) { DBG_SVC(service, "Unable to read Track Title: error 0x%02x", att_ecode); return; } DBG_SVC(service, "Track Title"); LISTENER_CB(service, track_title, value, length); } static void update_track_duration(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_mcp_service *service = user_data; struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; uint32_t v; if (!success || !util_iov_pull_le32(&iov, &v)) { DBG_SVC(service, "Unable to read Track Duration: " "error 0x%02x", att_ecode); return; } DBG_SVC(service, "Track Duration: %d", (int32_t)v); LISTENER_CB(service, track_duration, (int32_t)v); } static void update_track_position(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_mcp_service *service = user_data; struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; uint32_t v; if (!success || !util_iov_pull_le32(&iov, &v)) { DBG_SVC(service, "Unable to read Track Position: " "error 0x%02x", att_ecode); return; } DBG_SVC(service, "Track Position: %d", (int32_t)v); LISTENER_CB(service, track_position, (int32_t)v); } static void update_playback_speed(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_mcp_service *service = user_data; struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; uint8_t v; if (!success || !util_iov_pull_u8(&iov, &v)) { DBG_SVC(service, "Unable to read Playback Speed: " "error 0x%02x", att_ecode); return; } DBG_SVC(service, "Playback Speed: %d", (int8_t)v); LISTENER_CB(service, playback_speed, (int8_t)v); } static void update_seeking_speed(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_mcp_service *service = user_data; struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; uint8_t v; if (!success || !util_iov_pull_u8(&iov, &v)) { DBG_SVC(service, "Unable to read Seeking Speed: " "error 0x%02x", att_ecode); return; } DBG_SVC(service, "Seeking Speed: %d", (int8_t)v); LISTENER_CB(service, seeking_speed, (int8_t)v); } static void update_playing_order(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_mcp_service *service = user_data; struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; uint8_t v; if (!success || !util_iov_pull_u8(&iov, &v)) { DBG_SVC(service, "Unable to read Playing Order: " "error 0x%02x", att_ecode); return; } DBG_SVC(service, "Playing Order: %u", v); LISTENER_CB(service, playing_order, v); } static void update_playing_order_supported(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_mcp_service *service = user_data; struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; uint16_t v; if (!success || !util_iov_pull_le16(&iov, &v)) { DBG_SVC(service, "Unable to read " "Playing Order Supported: error 0x%02x", att_ecode); return; } DBG_SVC(service, "Playing Order Supported: %u", v); service->rdb.playing_order_supported_value = v; } static void update_media_state(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_mcp_service *service = user_data; struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; uint8_t v; if (!success || !util_iov_pull_u8(&iov, &v)) { DBG_SVC(service, "Unable to read Media State: error 0x%02x", att_ecode); return; } DBG_SVC(service, "Media State: %u", v); LISTENER_CB(service, media_state, v); } static bool match_pending_op(const void *data, const void *user_data) { const struct bt_mcp_pending *pending = data; return pending->op && pending->op == PTR_TO_UINT(user_data); } static void update_media_cp(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_mcp_service *service = user_data; struct bt_mcp *mcp = service->mcp; struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; struct bt_mcp_pending *pending; uint8_t op, result; if (!success || !util_iov_pull_u8(&iov, &op) || !util_iov_pull_u8(&iov, &result)) { DBG_SVC(service, "Unable to read Media CP: error 0x%02x", att_ecode); return; } DBG_SVC(service, "Media CP %u result %u", op, result); pending = queue_remove_if(service->pending, match_pending_op, UINT_TO_PTR(op)); if (!pending) return; if (mcp->cb->complete) mcp->cb->complete(mcp->user_data, pending->id, result); free(pending); } static void update_media_cp_op_supported(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_mcp_service *service = user_data; struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; uint32_t v; if (!success || !util_iov_pull_le32(&iov, &v)) { DBG_SVC(service, "Unable to read " "Media CP Op Supported: error 0x%02x", att_ecode); return; } DBG_SVC(service, "Media CP Op Supported: %d", v); service->rdb.media_cp_op_supported_value = v; } static void update_add_service(void *data, void *user_data) { struct bt_mcp_service *service = data; struct bt_mcp *mcp = user_data; if (service->rdb.ccid_value < 0) return; if (service->ready) return; service->ready = true; if (mcp->cb->ccid) mcp->cb->ccid(mcp->user_data, service->rdb.ccid_value, service->rdb.gmcs); } static void update_ccid(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_mcp_service *service = user_data; struct iovec iov = { .iov_base = (void *)value, .iov_len = length }; uint8_t v; if (!success || !util_iov_pull_u8(&iov, &v)) { DBG_SVC(service, "Unable to read CCID: error 0x%02x", att_ecode); return; } DBG_SVC(service, "CCID: %u", v); service->rdb.ccid_value = v; gatt_db_service_foreach_char(service->rdb.service, foreach_mcs_char, service); update_add_service(service, service->mcp); } static void mcp_service_reread(struct bt_mcp_service *service, struct gatt_db_attribute *attrib, bool skip_notify) { const struct { struct gatt_db_attribute *attr; bt_gatt_client_read_callback_t cb; } attrs[] = { { service->rdb.track_title, update_track_title }, { service->rdb.track_duration, update_track_duration }, { service->rdb.track_position, update_track_position }, { service->rdb.playback_speed, update_playback_speed }, { service->rdb.seeking_speed, update_seeking_speed }, { service->rdb.playing_order, update_playing_order }, { service->rdb.playing_order_supported, update_playing_order_supported }, { service->rdb.media_state, update_media_state }, { service->rdb.media_cp_op_supported, update_media_cp_op_supported }, }; struct bt_gatt_client *client = service->mcp->client; uint16_t value_handle; uint8_t props; unsigned int i; for (i = 0; i < ARRAY_SIZE(attrs); ++i) { if (!attrs[i].attr) continue; if (attrib && attrs[i].attr != attrib) continue; if (!gatt_db_attribute_get_char_data(attrs[i].attr, NULL, &value_handle, &props, NULL, NULL)) continue; if (skip_notify && (props & BT_GATT_CHRC_PROP_NOTIFY)) continue; DBG_SVC(service, "re-read handle 0x%04x", value_handle); bt_gatt_client_read_value(client, value_handle, attrs[i].cb, service, NULL); } } static void notify_media_player_name(struct bt_mcp_service *service) { /* On player name change, re-read all attributes */ mcp_service_reread(service, NULL, false); } static void mcp_idle(void *data) { struct bt_mcp *mcp = data; DBG_MCP(mcp, ""); mcp->idle_id = 0; if (!mcp->ready) { mcp->ready = true; if (mcp->cb->ready) mcp->cb->ready(mcp->user_data); } } struct chrc_notify_data { const char *name; struct bt_mcp_service *service; bt_gatt_client_read_callback_t cb; void (*notify_cb)(struct bt_mcp_service *service); }; static void chrc_register(uint16_t att_ecode, void *user_data) { struct chrc_notify_data *data = user_data; if (att_ecode) DBG_SVC(data->service, "%s notification failed: 0x%04x", data->name, att_ecode); } static void chrc_notify(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct chrc_notify_data *data = user_data; struct bt_mcp_service *service = data->service; struct bt_gatt_client *client = service->mcp->client; uint16_t mtu = bt_gatt_client_get_mtu(client); DBG_SVC(service, "Notify %s", data->name); if (length == mtu - 3) { /* Probably truncated value */ DBG_SVC(service, "Read %s", data->name); bt_gatt_client_read_value(client, value_handle, data->cb, service, NULL); return; } data->cb(true, 0xff, value, length, data->service); if (data->notify_cb) data->notify_cb(service); } static void foreach_mcs_char(struct gatt_db_attribute *attr, void *user_data) { struct bt_mcp_service *service = user_data; struct bt_mcp *mcp = service->mcp; const struct { uint16_t uuid; const char *name; struct gatt_db_attribute **dst; bt_gatt_client_read_callback_t cb; void (*notify_cb)(struct bt_mcp_service *service); bool no_read; bool no_notify; } attrs[] = { { MCS_CCID_CHRC_UUID, "CCID", &service->rdb.ccid, update_ccid, .no_notify = true }, { MCS_MEDIA_PLAYER_NAME_CHRC_UUID, "Media Player Name", &service->rdb.media_player_name, update_media_player_name, .notify_cb = notify_media_player_name }, { MCS_TRACK_CHANGED_CHRC_UUID, "Track Changed", &service->rdb.track_changed, update_track_changed, .no_read = true }, { MCS_TRACK_TITLE_CHRC_UUID, "Track Title", &service->rdb.track_title, update_track_title }, { MCS_TRACK_DURATION_CHRC_UUID, "Track Duration", &service->rdb.track_duration, update_track_duration }, { MCS_TRACK_POSITION_CHRC_UUID, "Track Position", &service->rdb.track_position, update_track_position }, { MCS_PLAYBACK_SPEED_CHRC_UUID, "Playback Speed", &service->rdb.playback_speed, update_playback_speed }, { MCS_SEEKING_SPEED_CHRC_UUID, "Seeking Speed", &service->rdb.seeking_speed, update_seeking_speed }, { MCS_PLAYING_ORDER_CHRC_UUID, "Playing Order", &service->rdb.playing_order, update_playing_order }, { MCS_PLAYING_ORDER_SUPPORTED_CHRC_UUID, "Playing Order Supported", &service->rdb.playing_order_supported, update_playing_order_supported, .no_notify = true }, { MCS_MEDIA_STATE_CHRC_UUID, "Media State", &service->rdb.media_state, update_media_state }, { MCS_MEDIA_CP_CHRC_UUID, "Media Control Point", &service->rdb.media_cp, update_media_cp }, { MCS_MEDIA_CP_OP_SUPPORTED_CHRC_UUID, "Media CP Op Supported", &service->rdb.media_cp_op_supported, update_media_cp_op_supported }, }; struct bt_gatt_client *client = service->mcp->client; bt_uuid_t uuid, uuid_attr; uint16_t value_handle; uint8_t props; unsigned int i; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, &props, NULL, &uuid_attr)) return; for (i = 0; i < ARRAY_SIZE(attrs); ++i) { unsigned int id; struct chrc_notify_data *data; if (*attrs[i].dst) continue; bt_uuid16_create(&uuid, attrs[i].uuid); if (bt_uuid_cmp(&uuid_attr, &uuid)) continue; DBG_SVC(service, "%s found: handle 0x%04x", attrs[i].name, value_handle); *attrs[i].dst = attr; if ((props & BT_GATT_CHRC_PROP_READ) && !attrs[i].no_read) bt_gatt_client_read_value(client, value_handle, attrs[i].cb, service, NULL); if (!(props & BT_GATT_CHRC_PROP_NOTIFY) || attrs[i].no_notify) break; if (service->notify_id_count >= ARRAY_SIZE(service->notify_id)) break; data = new0(struct chrc_notify_data, 1); data->name = attrs[i].name; data->service = service; data->cb = attrs[i].cb; id = bt_gatt_client_register_notify(client, value_handle, chrc_register, chrc_notify, data, free); if (id) service->notify_id[service->notify_id_count++] = id; else free(data); break; } if (!mcp->idle_id && i < ARRAY_SIZE(attrs)) mcp->idle_id = bt_gatt_client_idle_register(mcp->client, mcp_idle, mcp, NULL); } static void foreach_mcs_ccid(struct gatt_db_attribute *attr, void *user_data) { bt_uuid_t uuid, uuid_attr; if (!gatt_db_attribute_get_char_data(attr, NULL, NULL, NULL, NULL, &uuid_attr)) return; bt_uuid16_create(&uuid, MCS_CCID_CHRC_UUID); if (bt_uuid_cmp(&uuid_attr, &uuid)) return; foreach_mcs_char(attr, user_data); } static void listener_destroy(void *data) { struct bt_mcp_listener *listener = data; if (listener->cb->destroy) listener->cb->destroy(listener->user_data); free(listener); } static void mcp_service_destroy(void *data) { struct bt_mcp_service *service = data; struct bt_gatt_client *client = service->mcp->client; unsigned int i; mcp_cancel_pending_writes(service); queue_destroy(service->listeners, listener_destroy); for (i = 0; i < service->notify_id_count; ++i) bt_gatt_client_unregister_notify(client, service->notify_id[i]); queue_destroy(service->pending, free); free(service); } static void foreach_mcs_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_mcp *mcp = user_data; struct bt_mcp_service *service; bt_uuid_t uuid, uuid_attr; bool gmcs, mcs; DBG_MCP(mcp, ""); if (!gatt_db_attribute_get_service_uuid(attr, &uuid_attr)) return; bt_uuid16_create(&uuid, GMCS_UUID); gmcs = !bt_uuid_cmp(&uuid_attr, &uuid); if (gmcs != mcp->gmcs) return; bt_uuid16_create(&uuid, MCS_UUID); mcs = !bt_uuid_cmp(&uuid_attr, &uuid); if (!gmcs && !mcs) return; service = new0(struct bt_mcp_service, 1); service->mcp = mcp; service->rdb.gmcs = gmcs; service->rdb.service = attr; service->rdb.ccid_value = -1; service->pending = queue_new(); service->listeners = queue_new(); /* Find CCID first */ gatt_db_service_foreach_char(attr, foreach_mcs_ccid, service); queue_push_tail(mcp->services, service); } static bool match_service_attr(const void *data, const void *user_data) { const struct bt_mcp_service *service = data; return service->rdb.service == user_data; } static void mcp_service_added(struct gatt_db_attribute *attr, void *user_data) { struct bt_mcp *mcp = user_data; foreach_mcs_service(attr, mcp); } static void mcp_service_removed(struct gatt_db_attribute *attr, void *user_data) { struct bt_mcp *mcp = user_data; queue_remove_all(mcp->services, match_service_attr, attr, mcp_service_destroy); } struct bt_mcp *bt_mcp_attach(struct bt_gatt_client *client, bool gmcs, const struct bt_mcp_callback *cb, void *user_data) { struct bt_mcp *mcp; struct gatt_db *db; bt_uuid_t uuid; if (!cb) return NULL; client = bt_gatt_client_clone(client); if (!client) return NULL; mcp = new0(struct bt_mcp, 1); mcp->gmcs = gmcs; mcp->client = client; mcp->services = queue_new(); mcp->cb = cb; mcp->user_data = user_data; DBG_MCP(mcp, ""); db = bt_gatt_client_get_db(client); bt_uuid16_create(&uuid, GMCS_UUID); gatt_db_foreach_service(db, &uuid, foreach_mcs_service, mcp); bt_uuid16_create(&uuid, MCS_UUID); gatt_db_foreach_service(db, &uuid, foreach_mcs_service, mcp); mcp->db_id = gatt_db_register(db, mcp_service_added, mcp_service_removed, mcp, NULL); if (!mcp->idle_id) mcp_idle(mcp); return mcp; } void bt_mcp_detach(struct bt_mcp *mcp) { struct gatt_db *db; if (!mcp) return; DBG_MCP(mcp, ""); queue_destroy(mcp->services, mcp_service_destroy); if (mcp->cb->destroy) mcp->cb->destroy(mcp->user_data); if (mcp->idle_id) bt_gatt_client_idle_unregister(mcp->client, mcp->idle_id); db = bt_gatt_client_get_db(mcp->client); if (mcp->db_id) gatt_db_unregister(db, mcp->db_id); bt_gatt_client_unref(mcp->client); free(mcp); } bool bt_mcp_add_listener(struct bt_mcp *mcp, uint8_t ccid, const struct bt_mcp_listener_callback *cb, void *user_data) { struct bt_mcp_listener *listener; struct bt_mcp_service *service; if (!cb) return false; service = queue_find(mcp->services, match_ccid, UINT_TO_PTR(ccid)); if (!service) return false; listener = new0(struct bt_mcp_listener, 1); listener->cb = cb; listener->user_data = user_data; queue_push_tail(service->listeners, listener); return true; } struct bt_gatt_client *bt_mcp_test_util_get_client(struct bt_mcp *mcp) { return mcp->client; } bluez-5.87/src/shared/PaxHeaders/mainloop-ell.c0000644000000000000000000000005014772767672016475 xustar0020 atime=1783093493 20 ctime=1783094100 bluez-5.87/src/shared/mainloop-ell.c0000644000000000000000000000361014772767672016156 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2019 Intel Corporation * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include "mainloop.h" static bool is_initialized; static int exit_status; static mainloop_signal_func sig_func; static void l_sig_func(uint32_t signo, void *user_data) { if (sig_func) sig_func(signo, user_data); } void mainloop_init(void) { is_initialized = l_main_init(); } void mainloop_quit(void) { l_main_quit(); } void mainloop_exit_success(void) { exit_status = EXIT_SUCCESS; l_main_quit(); } void mainloop_exit_failure(void) { exit_status = EXIT_FAILURE; l_main_quit(); } int mainloop_run(void) { if (!is_initialized) return -EINVAL; l_main_run(); is_initialized = false; sig_func = NULL; return exit_status; } int mainloop_run_with_signal(mainloop_signal_func func, void *user_data) { if (!is_initialized || !func) return -EINVAL; /* Workaround for sign discrepancy in ell and bluez */ sig_func = func; return l_main_run_with_signal(l_sig_func, user_data); } int mainloop_add_fd(int fd, uint32_t events, mainloop_event_func callback, void *user_data, mainloop_destroy_func destroy) { return -ENOSYS; } int mainloop_modify_fd(int fd, uint32_t events) { return -ENOSYS; } int mainloop_remove_fd(int fd) { return -ENOSYS; } int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback, void *user_data, mainloop_destroy_func destroy) { return -ENOSYS; } int mainloop_modify_timeout(int fd, unsigned int msec) { return -ENOSYS; } int mainloop_remove_timeout(int id) { return -ENOSYS; } int mainloop_set_signal(sigset_t *mask, mainloop_signal_func callback, void *user_data, mainloop_destroy_func destroy) { return -ENOSYS; } bluez-5.87/src/shared/PaxHeaders/shell.c0000644000000000000000000000005015200607060015157 xustar0020 atime=1783092683 20 ctime=1783094100 bluez-5.87/src/shared/shell.c0000644000000000000000000010270715200607060014647 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2017 Intel Corporation. All rights reserved. * Copyright 2024 NXP * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "src/shared/mainloop.h" #include "src/shared/timeout.h" #include "src/shared/io.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/shell.h" #include "src/shared/log.h" #define CMD_LENGTH 48 #define print_text(color, fmt, args...) \ printf(color fmt COLOR_OFF "\n", ## args) #define print_menu(cmd, args, desc) \ printf(COLOR_HIGHLIGHT "%s %-*s " COLOR_OFF "%s\n", \ cmd, (int)(CMD_LENGTH - strlen(cmd)), args, desc) #define print_submenu(cmd, desc) \ printf(COLOR_BLUE "%s %-*s " COLOR_OFF "%s\n", \ cmd, (int)(CMD_LENGTH - strlen(cmd)), "", desc) struct bt_shell_env { char *name; void *value; }; static char *cmplt = "help"; struct bt_shell_prompt_input { char *str; bt_shell_prompt_input_func func; void *user_data; }; struct input { struct io *io; FILE *f; }; typedef enum { MODE_INTERACTIVE = 0, MODE_NON_INTERACTIVE= 1 } mode_type_t; static struct { bool init; char *name; char history[256]; int argc; char **argv; mode_type_t mode; bool zsh; bool monitor; int timeout; int init_fd; struct queue *inputs; char *line; struct queue *queue; bool saved_prompt; bt_shell_prompt_input_func saved_func; void *saved_user_data; struct queue *prompts; const struct bt_shell_menu *menu; const struct bt_shell_menu *main; struct queue *submenus; const struct bt_shell_menu_entry *exec; struct queue *envs; } data; static void shell_print_menu(void); static void shell_print_menu_zsh_complete(void); static void cmd_version(int argc, char *argv[]) { bt_shell_printf("Version %s\n", VERSION); return bt_shell_noninteractive_quit(EXIT_SUCCESS); } static void cmd_quit(int argc, char *argv[]) { mainloop_quit(); } static void print_cmds(void) { const struct bt_shell_menu_entry *entry; const struct queue_entry *submenu; if (!data.menu) return; printf("Commands:\n"); for (entry = data.menu->entries; entry->cmd; entry++) { printf("\t%s%s\t%s\n", entry->cmd, strlen(entry->cmd) < 8 ? "\t" : "", entry->desc); } for (submenu = queue_get_entries(data.submenus); submenu; submenu = submenu->next) { struct bt_shell_menu *menu = submenu->data; printf("\n\t%s.:\n", menu->name); for (entry = menu->entries; entry->cmd; entry++) { printf("\t\t%s%s\t%s\n", entry->cmd, strlen(entry->cmd) < 8 ? "\t" : "", entry->desc); } } } static void cmd_help(int argc, char *argv[]) { if (argv[0] == cmplt) print_cmds(); else shell_print_menu(); return bt_shell_noninteractive_quit(EXIT_SUCCESS); } static const struct bt_shell_menu *find_menu(const char *name, size_t len, int *index) { const struct queue_entry *entry; int i; for (i = 0, entry = queue_get_entries(data.submenus); entry; entry = entry->next, i++) { struct bt_shell_menu *menu = entry->data; if (index) { if (i < *index) continue; (*index)++; } if (!strncmp(menu->name, name, len)) return menu; } return NULL; } static char *menu_generator(const char *text, int state) { static unsigned int len; static struct queue_entry *entry; if (!state) { len = strlen(text); entry = (void *) queue_get_entries(data.submenus); } for (; entry; entry = entry->next) { struct bt_shell_menu *menu = entry->data; if (!strncmp(menu->name, text, len)) { entry = entry->next; return strdup(menu->name); } } return NULL; } static void cmd_menu(int argc, char *argv[]) { const struct bt_shell_menu *menu; if (argc < 2 || !strlen(argv[1])) { bt_shell_printf("Missing name argument\n"); return bt_shell_noninteractive_quit(EXIT_FAILURE); } menu = find_menu(argv[1], strlen(argv[1]), NULL); if (!menu) { bt_shell_printf("Unable find menu with name: %s\n", argv[1]); return bt_shell_noninteractive_quit(EXIT_FAILURE); } bt_shell_set_menu(menu); shell_print_menu(); return bt_shell_noninteractive_quit(EXIT_SUCCESS); } static bool cmd_menu_exists(const struct bt_shell_menu *menu) { /* Skip menu command if not on main menu or if there are no * submenus. */ if (menu != data.main || queue_isempty(data.submenus)) return false; return true; } static void cmd_back(int argc, char *argv[]) { if (data.menu == data.main) { bt_shell_printf("Already on main menu\n"); return; } bt_shell_set_menu(data.main); shell_print_menu(); } static bool cmd_back_exists(const struct bt_shell_menu *menu) { /* Skip back command if on main menu */ if (menu == data.main) return false; return true; } static void cmd_export(int argc, char *argv[]) { const struct queue_entry *entry; for (entry = queue_get_entries(data.envs); entry; entry = entry->next) { struct bt_shell_env *env = entry->data; print_text(COLOR_HIGHLIGHT, "%s=%p", env->name, env->value); } } static int bt_shell_queue_exec(char *line) { /* Ignore comments */ if (line[0] == '#') return 0; /* Queue if already executing */ if (data.line) { /* Check if prompt is being held then release using the line */ if (!bt_shell_release_prompt(line)) { bt_shell_printf("%s\n", line); return 0; } queue_push_tail(data.queue, strdup(line)); return 0; } bt_shell_printf("%s\n", line); data.line = strdup(line); return bt_shell_exec(line); } static bool bt_shell_input_line(struct input *input) { int fd; char *line = NULL; size_t len = 0; ssize_t nread; fd = io_get_fd(input->io); if (fd < 0) { printf("io_get_fd() returned %d\n", fd); return false; } if (fd == STDIN_FILENO) { rl_callback_read_char(); return true; } if (!input->f) { input->f = fdopen(fd, "r"); if (!input->f) { printf("fdopen: %s (%d)\n", strerror(errno), errno); return false; } } nread = getline(&line, &len, input->f); if (nread > 0) { int err; if (line[nread - 1] == '\n') line[nread - 1] = '\0'; err = bt_shell_queue_exec(line); if (err < 0) printf("%s: %s (%d)\n", line, strerror(-err), -err); } else if (input->f) { fclose(input->f); input->f = NULL; } free(line); return input->f ? true : false; } static bool input_read(struct io *io, void *user_data) { return bt_shell_input_line(user_data); } static bool input_hup(struct io *io, void *user_data) { if (queue_remove(data.inputs, user_data)) { if (!queue_isempty(data.inputs)) return false; } mainloop_quit(); return false; } static struct input *input_new(int fd) { struct input *input; struct io *io; io = io_new(fd); if (!io) return NULL; input = new0(struct input, 1); input->io = io; queue_push_tail(data.inputs, input); return input; } static bool bt_shell_input_attach(int fd) { struct input *input; struct queue *queue; input = input_new(fd); if (!input) return false; /* Save executing queue so input lines can be placed in the correct * order. */ queue = data.queue; data.queue = queue_new(); while (bt_shell_input_line(input)); /* Push existing input lines back into the executing queue */ while (!queue_isempty(queue)) queue_push_tail(data.queue, queue_pop_head(queue)); queue_destroy(queue, free); return true; } static void cmd_script(int argc, char *argv[]) { int fd; fd = open(argv[1], O_RDONLY); if (fd < 0) { printf("Unable to open %s: %s (%d)\n", argv[1], strerror(errno), errno); bt_shell_noninteractive_quit(EXIT_FAILURE); return; } printf("Running script %s...\n", argv[1]); if (!bt_shell_input_attach(fd)) return bt_shell_noninteractive_quit(EXIT_FAILURE); return bt_shell_noninteractive_quit(EXIT_SUCCESS); } static const struct bt_shell_menu default_menu = { .entries = { { "back", NULL, cmd_back, "Return to main menu", NULL, NULL, cmd_back_exists }, { "menu", "", cmd_menu, "Select submenu", menu_generator, NULL, cmd_menu_exists}, { "version", NULL, cmd_version, "Display version" }, { "quit", NULL, cmd_quit, "Quit program" }, { "exit", NULL, cmd_quit, "Quit program" }, { "help", NULL, cmd_help, "Display help about this program" }, { "export", NULL, cmd_export, "Print environment variables" }, { "script", "", cmd_script, "Run script" }, {} }, }; static void shell_print_help(void) { print_text(COLOR_HIGHLIGHT, "\n" "Use \"help\" for a list of available commands in a menu.\n" "Use \"menu \" if you want to enter any submenu.\n" "Use \"back\" if you want to return to menu main."); } static void shell_print_menu(void) { const struct bt_shell_menu_entry *entry; const struct queue_entry *submenu; if (!data.menu) return; if (data.zsh) { shell_print_menu_zsh_complete(); return; } print_text(COLOR_HIGHLIGHT, "Menu %s:", data.menu->name); print_text(COLOR_HIGHLIGHT, "Available commands:"); print_text(COLOR_HIGHLIGHT, "-------------------"); if (data.menu == data.main) { for (submenu = queue_get_entries(data.submenus); submenu; submenu = submenu->next) { struct bt_shell_menu *menu = submenu->data; print_submenu(menu->name, menu->desc ? menu->desc : "Submenu"); } } for (entry = data.menu->entries; entry->cmd; entry++) { print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : ""); } for (entry = default_menu.entries; entry->cmd; entry++) { if (entry->exists && !entry->exists(data.menu)) continue; print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : ""); } } static void shell_print_menu_zsh_complete(void) { const struct bt_shell_menu_entry *entry; for (entry = data.menu->entries; entry->cmd; entry++) printf("%s:%s\n", entry->cmd, entry->desc ? : ""); for (entry = default_menu.entries; entry->cmd; entry++) { if (entry->exists && !entry->exists(data.menu)) continue; printf("%s:%s\n", entry->cmd, entry->desc ? : ""); } } static int parse_args(char *arg, wordexp_t *w, char *del, int flags) { char *str; str = strdelimit(arg, del, '"'); if (wordexp(str, w, flags)) { free(str); return -EINVAL; } /* If argument ends with ... set we_offs bypass strict checks */ if (w->we_wordc && !strsuffix(w->we_wordv[w->we_wordc -1], "...")) w->we_offs = 1; free(str); return 0; } static int cmd_exec(const struct bt_shell_menu_entry *entry, int argc, char *argv[]) { wordexp_t w; size_t len; char *man, *opt; const char *man2, *opt2; int flags = WRDE_NOCMD; bool optargs = false; if (argc == 2 && (!memcmp(argv[1], "help", 4) || !memcmp(argv[1], "--help", 6))) { printf("%s\n", entry->desc); printf(COLOR_HIGHLIGHT "Usage:" COLOR_OFF "\n"); printf("\t %s %-*s\n", entry->cmd, (int)(CMD_LENGTH - strlen(entry->cmd)), !entry->arg ? "" : entry->arg); return 0; } if (!entry->arg || entry->arg[0] == '\0') { if (argc > 1) { print_text(COLOR_HIGHLIGHT, "Too many arguments"); return -EINVAL; } goto exec; } /* Find last mandatory arguments */ man2 = strrchr(entry->arg, '>'); if (!man2) { opt = strdup(entry->arg); goto optional; } len = man2 - entry->arg; if (entry->arg[0] == '<') man = strndup(entry->arg, len + 1); else { /* Find where mandatory arguments start */ opt2 = strrchr(entry->arg, '<'); /* Skip if mandatory arguments are not in the right format */ if (!opt2 || opt2 > man2) { opt = strdup(entry->arg); goto optional; } man = strndup(opt2, man2 - opt2 + 1); optargs = true; } if (parse_args(man, &w, "<>", flags) < 0) { print_text(COLOR_HIGHLIGHT, "Unable to parse mandatory command arguments: %s", man ); free(man); return -EINVAL; } free(man); /* Check if there are enough arguments */ if ((unsigned) argc - 1 < w.we_wordc) { print_text(COLOR_HIGHLIGHT, "Missing %s argument", w.we_wordv[argc - 1]); goto fail; } flags |= WRDE_APPEND; opt = strdup(entry->arg + len + 1); optional: if (parse_args(opt, &w, "[]", flags) < 0) { print_text(COLOR_HIGHLIGHT, "Unable to parse optional command arguments: %s", opt); free(opt); return -EINVAL; } free(opt); /* Check if there are too many arguments */ if (!optargs && ((unsigned int) argc - 1 > w.we_wordc && !w.we_offs)) { print_text(COLOR_HIGHLIGHT, "Too many arguments: %d > %zu", argc - 1, w.we_wordc); goto fail; } w.we_offs = 0; wordfree(&w); exec: data.exec = entry; if (entry->func) entry->func(argc, argv); data.exec = NULL; return 0; fail: w.we_offs = 0; wordfree(&w); return -EINVAL; } static int menu_exec(const struct bt_shell_menu *menu, int argc, char *argv[]) { const struct bt_shell_menu_entry *entry = menu->entries; for (; entry->cmd; entry++) { if (strcmp(argv[0], entry->cmd)) continue; /* Skip menu command if not on main menu */ if (data.menu != data.main && !strcmp(entry->cmd, "menu")) continue; /* Skip back command if on main menu */ if (data.menu == data.main && !strcmp(entry->cmd, "back")) continue; if (data.mode == MODE_NON_INTERACTIVE && menu->pre_run) menu->pre_run(menu); return cmd_exec(entry, argc, argv); } return -ENOENT; } static int submenu_exec(int argc, char *argv[]) { char *name; int len, tlen; const struct bt_shell_menu *submenu; if (data.menu != data.main) return -ENOENT; name = strchr(argv[0], '.'); if (!name) return -ENOENT; tlen = strlen(argv[0]); len = name - argv[0]; name[0] = '\0'; submenu = find_menu(argv[0], strlen(argv[0]), NULL); if (!submenu) return -ENOENT; /* Replace submenu.command with command */ memmove(argv[0], argv[0] + len + 1, tlen - len - 1); memset(argv[0] + tlen - len - 1, 0, len + 1); return menu_exec(submenu, argc, argv); } static int shell_exec(int argc, char *argv[]) { int err; if (!data.menu || !argv[0]) return -EINVAL; if (!argsisutf8(argc, argv)) return -EINVAL; err = menu_exec(&default_menu, argc, argv); if (err == -ENOENT) { err = menu_exec(data.menu, argc, argv); if (err == -ENOENT) { err = submenu_exec(argc, argv); if (err == -ENOENT) { print_text(COLOR_HIGHLIGHT, "Invalid command in menu %s: %s", data.menu->name , argv[0]); shell_print_help(); } } } return err; } void bt_shell_printf(const char *fmt, ...) { va_list args; bool save_input; char *saved_line; int saved_point; if (queue_isempty(data.inputs)) return; if (data.mode == MODE_NON_INTERACTIVE) { va_start(args, fmt); vprintf(fmt, args); va_end(args); return; } save_input = !RL_ISSTATE(RL_STATE_DONE); if (save_input) { saved_point = rl_point; saved_line = rl_copy_text(0, rl_end); if (!data.saved_prompt) rl_save_prompt(); rl_clear_visible_line(); rl_reset_line_state(); } va_start(args, fmt); vprintf(fmt, args); va_end(args); if (data.monitor) { va_start(args, fmt); bt_log_vprintf(0xffff, data.name, LOG_INFO, fmt, args); va_end(args); } if (save_input) { if (!data.saved_prompt) rl_restore_prompt(); rl_replace_line(saved_line, 0); rl_point = saved_point; rl_redisplay(); free(saved_line); } } void bt_shell_echo(const char *fmt, ...) { va_list args; char *str; int ret; va_start(args, fmt); ret = vasprintf(&str, fmt, args); va_end(args); if (ret < 0) return; rl_save_prompt(); bt_shell_set_prompt(str, COLOR_HIGHLIGHT); rl_restore_prompt(); } static void print_string(const char *str, void *user_data) { bt_shell_printf("%s\n", str); } void bt_shell_hexdump(const unsigned char *buf, size_t len) { util_hexdump(' ', buf, len, print_string, NULL); } void bt_shell_usage(void) { if (!data.exec) return; bt_shell_printf("Usage: %s %s\n", data.exec->cmd, data.exec->arg ? data.exec->arg : ""); } static void bt_shell_dequeue_exec(void) { int err; if (!data.line) return; free(data.line); data.line = NULL; data.line = queue_pop_head(data.queue); if (!data.line) return; bt_shell_printf("%s\n", data.line); if (!bt_shell_release_prompt(data.line)) { /* If a prompt was released with this line, * try to release all the other prompts, * if any are left. Otherwise, the next * line will be executed on * bt_shell_noninteractive_quit. */ if (data.saved_prompt) bt_shell_dequeue_exec(); return; } err = bt_shell_exec(data.line); if (err) bt_shell_dequeue_exec(); } static void prompt_input(const char *str, bt_shell_prompt_input_func func, void *user_data) { data.saved_prompt = true; data.saved_func = func; data.saved_user_data = user_data; rl_save_prompt(); bt_shell_set_prompt(str, COLOR_HIGHLIGHT); } void bt_shell_prompt_input(const char *label, const char *msg, bt_shell_prompt_input_func func, void *user_data) { char *str; if (!data.init || data.mode == MODE_NON_INTERACTIVE) return; if (data.saved_prompt) { struct bt_shell_prompt_input *prompt; prompt = new0(struct bt_shell_prompt_input, 1); if (asprintf(&str, COLOR_HIGHLIGHT "[%s] %s " COLOR_OFF, label, msg) < 0) { free(prompt); return; } prompt->func = func; prompt->user_data = user_data; queue_push_tail(data.prompts, prompt); free(str); return; } if (asprintf(&str, COLOR_HIGHLIGHT "[%s] %s " COLOR_OFF, label, msg) < 0) return; prompt_input(str, func, user_data); free(str); if (data.line && !queue_isempty(data.queue)) /* If a prompt was set to receive input and * data is already available, try to execute * the line and release the prompt. */ bt_shell_dequeue_exec(); } static void prompt_free(void *data) { struct bt_shell_prompt_input *prompt = data; free(prompt->str); free(prompt); } int bt_shell_release_prompt(const char *input) { struct bt_shell_prompt_input *prompt; bt_shell_prompt_input_func func; void *user_data; if (!data.saved_prompt) return -1; data.saved_prompt = false; rl_restore_prompt(); func = data.saved_func; user_data = data.saved_user_data; prompt = queue_pop_head(data.prompts); if (prompt) data.saved_prompt = true; data.saved_func = NULL; data.saved_user_data = NULL; func(input, user_data); if (prompt) { prompt_input(prompt->str, prompt->func, prompt->user_data); prompt_free(prompt); } return 0; } static void rl_handler(char *input) { if (!input) { rl_insert_text("quit"); rl_redisplay(); rl_crlf(); mainloop_quit(); return; } /* Ignore empty/comment lines */ if (!strlen(input) || input[0] == '#') goto done; if (!bt_shell_release_prompt(input)) goto done; bt_shell_exec(input); done: free(input); } static char *find_cmd(const char *text, const struct bt_shell_menu_entry *entry, int *index) { const struct bt_shell_menu_entry *tmp; int len; len = strlen(text); while ((tmp = &entry[*index])) { (*index)++; if (!tmp->cmd) break; if (tmp->exists && !tmp->exists(data.menu)) continue; if (!strncmp(tmp->cmd, text, len)) return strdup(tmp->cmd); } return NULL; } static char *cmd_generator(const char *text, int state) { static int index; static bool default_menu_enabled, menu_enabled, submenu_enabled; static const struct bt_shell_menu *menu; char *cmd; const char *cmd2; if (!state) { index = 0; menu = NULL; default_menu_enabled = true; submenu_enabled = false; } if (default_menu_enabled) { cmd = find_cmd(text, default_menu.entries, &index); if (cmd) { return cmd; } else { index = 0; menu = data.menu; default_menu_enabled = false; if (data.main == data.menu) menu_enabled = true; } } if (menu_enabled) { menu = find_menu(text, strlen(text), &index); if (menu) return strdup(menu->name); index = 0; menu = data.menu; menu_enabled = false; } if (!submenu_enabled) { cmd = find_cmd(text, menu->entries, &index); if (cmd || menu != data.main) return cmd; cmd2 = strrchr(text, '.'); if (!cmd2) return NULL; menu = find_menu(text, cmd2 - text, NULL); if (!menu) return NULL; index = 0; submenu_enabled = true; } cmd = find_cmd(text + strlen(menu->name) + 1, menu->entries, &index); if (cmd) { int err; char *tmp; err = asprintf(&tmp, "%s.%s", menu->name, cmd); free(cmd); if (err < 0) return NULL; cmd = tmp; } return cmd; } static wordexp_t args; static char *arg_generator(const char *text, int state) { static unsigned int index, len; const char *arg; if (!state) { index = 0; len = strlen(text); } while (index < args.we_wordc) { arg = args.we_wordv[index]; index++; if (!strncmp(arg, text, len)) return strdup(arg); } return NULL; } static char **args_completion(const struct bt_shell_menu_entry *entry, int argc, const char *text) { char **matches = NULL; char *str; int index; index = text[0] == '\0' ? argc - 1 : argc - 2; if (index < 0) return NULL; if (!entry->arg) goto end; str = strdup(entry->arg); if (parse_args(str, &args, "<>[]", WRDE_NOCMD)) goto done; /* Check if argument is valid */ if ((unsigned) index > args.we_wordc - 1) goto done; /* Check if there are multiple values */ if (!strrchr(entry->arg, '/')) goto done; free(str); /* Split values separated by / */ str = strdelimit(args.we_wordv[index], "/", ' '); args.we_offs = 0; wordfree(&args); if (wordexp(str, &args, WRDE_NOCMD)) goto done; rl_completion_display_matches_hook = NULL; matches = rl_completion_matches(text, arg_generator); done: free(str); end: if (!matches && text[0] == '\0') bt_shell_printf("Usage: %s %s\n", entry->cmd, entry->arg ? entry->arg : ""); args.we_offs = 0; wordfree(&args); return matches; } static char **menu_completion(const struct bt_shell_menu_entry *entry, const char *text, int argc, char *input_cmd) { char **matches = NULL; if (argc == 0) return NULL; for (; entry->cmd; entry++) { if (strcmp(entry->cmd, input_cmd)) continue; if (!entry->gen) { matches = args_completion(entry, argc, text); break; } rl_completion_display_matches_hook = entry->disp; matches = rl_completion_matches(text, entry->gen); break; } return matches; } static char **submenu_completion(const char *text, int argc, char *input_cmd) { const struct bt_shell_menu *menu; char *cmd; if (data.main != data.menu) return NULL; if (!input_cmd) return NULL; cmd = strrchr(input_cmd, '.'); if (!cmd) return NULL; menu = find_menu(input_cmd, cmd - input_cmd, NULL); if (!menu) return NULL; return menu_completion(menu->entries, text, argc, input_cmd + strlen(menu->name) + 1); } static char **shell_completion(const char *text, int start, int end) { char **matches = NULL; rl_attempted_completion_over = 1; if (!data.menu) return NULL; if (start > 0) { wordexp_t w; if (wordexp(rl_line_buffer, &w, WRDE_NOCMD)) return NULL; matches = menu_completion(default_menu.entries, text, w.we_wordc, w.we_wordv[0]); if (!matches) { matches = menu_completion(data.menu->entries, text, w.we_wordc, w.we_wordv[0]); if (!matches) matches = submenu_completion(text, w.we_wordc, w.we_wordv[0]); } wordfree(&w); } else { rl_completion_display_matches_hook = NULL; matches = rl_completion_matches(text, cmd_generator); } return matches; } static void signal_callback(int signum, void *user_data) { static bool terminated = false; switch (signum) { case SIGINT: if (!queue_isempty(data.inputs) && data.mode == MODE_INTERACTIVE) { rl_replace_line("", 0); rl_crlf(); rl_on_new_line(); rl_redisplay(); return; } /* * If input was not yet setup up that means signal was received * while daemon was not yet running. Since user is not able * to terminate client by CTRL-D or typing exit treat this as * exit and fall through. */ /* fall through */ case SIGTERM: if (!terminated) { if (data.mode == MODE_INTERACTIVE) { rl_replace_line("", 0); rl_crlf(); } mainloop_quit(); } terminated = true; break; } } static void rl_init_history(void) { const char *name; char *dir; memset(data.history, 0, sizeof(data.history)); name = strrchr(data.name, '/'); if (!name) name = data.name; else name++; dir = getenv("XDG_CACHE_HOME"); if (dir) { snprintf(data.history, sizeof(data.history), "%s/.%s_history", dir, name); goto done; } dir = getenv("HOME"); if (dir) { snprintf(data.history, sizeof(data.history), "%s/.cache/.%s_history", dir, name); goto done; } dir = getenv("PWD"); if (dir) { snprintf(data.history, sizeof(data.history), "%s/.%s_history", dir, name); goto done; } return; done: read_history(data.history); using_history(); bt_shell_set_env("HISTORY", data.history); } static void rl_init(void) { if (data.mode == MODE_NON_INTERACTIVE) return; /* Allow conditional parsing of the ~/.inputrc file. */ rl_readline_name = data.name; rl_attempted_completion_function = shell_completion; rl_erase_empty_line = 1; rl_callback_handler_install(NULL, rl_handler); rl_init_history(); } static const struct option main_options[] = { { "version", no_argument, 0, 'v' }, { "help", no_argument, 0, 'h' }, { "init-script", required_argument, 0, 's' }, { "timeout", required_argument, 0, 't' }, { "monitor", no_argument, 0, 'm' }, { "zsh-complete", no_argument, 0, 'z' }, }; static void usage(int argc, char **argv, const struct bt_shell_opt *opt) { unsigned int i; printf("%s ver %s\n", data.name, VERSION); printf("Usage:\n" "\t%s [--options] [commands]\n", data.name); printf("Options:\n"); for (i = 0; opt && opt->options[i].name; i++) printf("\t--%s \t%s\n", opt->options[i].name, opt->help[i]); printf("\t--monitor \tEnable monitor output\n" "\t--timeout \tTimeout in seconds for non-interactive mode\n" "\t--version \tDisplay version\n" "\t--init-script \tInit script file\n" "\t--help \t\tDisplay help\n"); } void bt_shell_init(int argc, char **argv, const struct bt_shell_opt *opt) { int c, index = -1; struct option options[256]; char optstr[256]; size_t offset; char *endptr = NULL; offset = sizeof(main_options) / sizeof(struct option); memcpy(options, main_options, sizeof(struct option) * offset); if (opt) { memcpy(options + offset, opt->options, sizeof(struct option) * opt->optno); snprintf(optstr, sizeof(optstr), "+mhvs:t:%s", opt->optstr); } else snprintf(optstr, sizeof(optstr), "+mhvs:t:"); data.name = strrchr(argv[0], '/'); if (!data.name) data.name = strdup(argv[0]); else data.name = strdup(++data.name); data.init_fd = -1; while ((c = getopt_long(argc, argv, optstr, options, &index)) != -1) { switch (c) { case 'v': printf("%s: %s\n", data.name, VERSION); exit(EXIT_SUCCESS); return; case 'h': usage(argc, argv, opt); data.argc = 1; data.argv = &cmplt; data.mode = MODE_NON_INTERACTIVE; goto done; case 's': if (optarg && data.init_fd < 0) { data.init_fd = open(optarg, O_RDONLY); if (data.init_fd < 0) printf("Unable to open %s: %s (%d)\n", optarg, strerror(errno), errno); } break; case 't': if (optarg) data.timeout = strtol(optarg, &endptr, 0); if (!endptr || *endptr != '\0') printf("Unable to parse timeout\n"); break; case 'z': data.zsh = 1; break; case 'm': data.monitor = true; if (bt_log_open() < 0) { data.monitor = false; printf("Unable to open logging channel\n"); } break; default: if (index < 0) { for (index = 0; options[index].val; index++) { if (c == options[index].val) break; } } if (opt && index >= 0 && (size_t)index >= offset) { if (c != opt->options[index - offset].val) { usage(argc, argv, opt); exit(EXIT_SUCCESS); return; } *opt->optarg[index - offset] = optarg ? : ""; } } index = -1; } bt_shell_set_env("SHELL", data.name); data.argc = argc - optind; data.argv = argv + optind; optind = 0; data.mode = (data.argc > 0) ? MODE_NON_INTERACTIVE : MODE_INTERACTIVE; done: if (data.mode == MODE_NON_INTERACTIVE) bt_shell_set_env("NON_INTERACTIVE", &data.mode); mainloop_init(); /* Always set stdout as line buffered */ setlinebuf(stdout); rl_init(); data.init = true; data.inputs = queue_new(); data.queue = queue_new(); data.prompts = queue_new(); } static void rl_cleanup(void) { if (data.mode == MODE_NON_INTERACTIVE) return; if (data.history[0] != '\0') write_history(data.history); rl_message("%s", ""); rl_callback_handler_remove(); } static void env_destroy(void *data) { struct bt_shell_env *env = data; free(env->name); free(env); } int bt_shell_run(void) { int status; const struct queue_entry *submenu; /* Check if on non-interactive mode skip pre-run since that is on-demand * by shell_exec() only for the menu in use. */ if (data.mode == MODE_NON_INTERACTIVE) goto done; if (data.menu && data.menu->pre_run) data.menu->pre_run(data.menu); for (submenu = queue_get_entries(data.submenus); submenu; submenu = submenu->next) { struct bt_shell_menu *menu = submenu->data; if (menu->pre_run != NULL) menu->pre_run(menu); } done: status = mainloop_run_with_signal(signal_callback, NULL); bt_shell_cleanup(); return status; } int bt_shell_exec(const char *input) { HIST_ENTRY *last; wordexp_t w; int err; if (!input) return 0; last = history_get(history_length + history_base - 1); /* append only if input is different from previous command */ if (!last || strcmp(input, last->line)) add_history(input); if (data.monitor) bt_log_printf(0xffff, data.name, LOG_INFO, "%s", input); err = wordexp(input, &w, WRDE_NOCMD); switch (err) { case WRDE_BADCHAR: return -EBADMSG; case WRDE_BADVAL: case WRDE_SYNTAX: return -EINVAL; case WRDE_NOSPACE: return -ENOMEM; case WRDE_CMDSUB: if (wordexp(input, &w, 0)) return -ENOEXEC; break; }; if (w.we_wordc == 0) { wordfree(&w); return -ENOEXEC; } err = shell_exec(w.we_wordc, w.we_wordv); wordfree(&w); return err; } static void input_destroy(void *data) { struct input *input = data; if (input->f) fclose(input->f); io_destroy(input->io); free(input); } void bt_shell_cleanup(void) { bt_shell_release_prompt(""); bt_shell_detach(); if (data.envs) { queue_destroy(data.envs, env_destroy); data.envs = NULL; } if (data.monitor) bt_log_close(); rl_cleanup(); queue_destroy(data.inputs, input_destroy); data.inputs = NULL; queue_destroy(data.queue, free); data.queue = NULL; queue_destroy(data.prompts, prompt_free); data.prompts = NULL; data.init = false; free(data.name); } void bt_shell_quit(int status) { if (status == EXIT_SUCCESS) mainloop_exit_success(); else mainloop_exit_failure(); } void bt_shell_noninteractive_quit(int status) { if (data.mode == MODE_INTERACTIVE || data.timeout) { bt_shell_dequeue_exec(); return; } /* Ignore EINPROGRESS as it is meant for commands that need to stay * running. */ if (status == -EINPROGRESS) return; bt_shell_quit(status); } bool bt_shell_set_menu(const struct bt_shell_menu *menu) { if (!menu) return false; data.menu = menu; if (!data.main) data.main = menu; return true; } bool bt_shell_add_submenu(const struct bt_shell_menu *menu) { if (!menu) return false; if (!data.main) return bt_shell_set_menu(menu); if (!data.submenus) data.submenus = queue_new(); queue_push_tail(data.submenus, (void *) menu); return true; } void bt_shell_set_prompt(const char *string, const char *color) { char *prompt; if (!data.init || data.mode == MODE_NON_INTERACTIVE) return; /* Envelope color within RL_PROMPT_START_IGNORE (\001) and * RL_PROMPT_END_IGNORE (\002) so readline can properly calculate the * prompt length. */ if (!color || asprintf(&prompt, "\001%s\002%s\001%s\002", color, string, COLOR_OFF) < 0) { rl_set_prompt(string); } else { rl_set_prompt(prompt); free(prompt); } rl_redisplay(); } static bool shell_quit(void *data) { mainloop_quit(); return false; } bool bt_shell_attach(int fd) { struct input *input; input = input_new(fd); if (!input) return false; if (data.mode == MODE_INTERACTIVE) { io_set_read_handler(input->io, input_read, input, NULL); io_set_disconnect_handler(input->io, input_hup, input, NULL); if (data.init_fd >= 0) { int fd = data.init_fd; data.init_fd = -1; if (!bt_shell_attach(fd)) return false; } } else { if (shell_exec(data.argc, data.argv) < 0) { bt_shell_noninteractive_quit(EXIT_FAILURE); return true; } if (data.timeout) timeout_add(data.timeout * 1000, shell_quit, NULL, NULL); } return true; } bool bt_shell_detach(void) { if (queue_isempty(data.inputs)) return false; queue_remove_all(data.inputs, NULL, NULL, input_destroy); return true; } static bool match_env(const void *data, const void *user_data) { const struct bt_shell_env *env = data; const char *name = user_data; return !strcmp(env->name, name); } void bt_shell_set_env(const char *name, void *value) { struct bt_shell_env *env; if (!data.envs) { if (!value) return; data.envs = queue_new(); goto done; } env = queue_remove_if(data.envs, match_env, (void *) name); if (env) env_destroy(env); /* Don't create an env if value is not set */ if (!value) return; done: env = new0(struct bt_shell_env, 1); env->name = strdup(name); env->value = value; queue_push_tail(data.envs, env); } void *bt_shell_get_env(const char *name) { const struct bt_shell_env *env; if (!data.envs) return NULL; env = queue_find(data.envs, match_env, name); if (!env) return NULL; return env->value; } int bt_shell_get_timeout(void) { return data.timeout; } void bt_shell_handle_non_interactive_help(void) { if (!data.mode) return; if (data.argv[0] != cmplt) return; print_cmds(); exit(EXIT_SUCCESS); } bluez-5.87/src/shared/PaxHeaders/rap.h0000644000000000000000000000005015221752714014651 xustar0020 atime=1783092685 20 ctime=1783094100 bluez-5.87/src/shared/rap.h0000644000000000000000000001275115221752714014340 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * BlueZ - Bluetooth protocol stack for Linux * * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include #include #include "src/shared/io.h" #include "bluetooth/mgmt.h" #include "src/shared/hci.h" struct bt_rap; struct gatt_db; struct bt_gatt_client; /* Channel Sounding Events */ struct bt_rap_hci_cs_options { uint8_t role; uint8_t cs_sync_ant_sel; int8_t max_tx_power; int rtt_type; }; #define CS_MODE_ZERO 0x00 #define CS_MODE_ONE 0x01 #define CS_MODE_TWO 0x02 #define CS_MODE_THREE 0x03 #define CS_REFLECTOR 0x01 #define CS_INITIATOR 0x00 #define CS_MAX_ANT_PATHS 0x05 #define CS_MAX_STEPS 0xA0 #define CS_MAX_STEP_DATA_LEN 0xFF struct rap_ev_cs_config_cmplt { uint8_t status; uint16_t conn_hdl; uint8_t config_id; uint8_t action; uint8_t main_mode_type; uint8_t sub_mode_type; uint8_t min_main_mode_steps; uint8_t max_main_mode_steps; uint8_t main_mode_rep; uint8_t mode_0_steps; uint8_t role; uint8_t rtt_type; uint8_t cs_sync_phy; uint8_t channel_map[10]; uint8_t channel_map_rep; uint8_t channel_sel_type; uint8_t ch3c_shape; uint8_t ch3c_jump; uint8_t reserved; uint8_t t_ip1_time; uint8_t t_ip2_time; uint8_t t_fcs_time; uint8_t t_pm_time; }; struct rap_ev_cs_sec_enable_cmplt { uint8_t status; uint16_t conn_hdl; }; struct rap_ev_cs_proc_enable_cmplt { uint8_t status; uint16_t conn_hdl; uint8_t config_id; uint8_t state; uint8_t tone_ant_config_sel; int8_t sel_tx_pwr; uint8_t sub_evt_len[3]; uint8_t sub_evts_per_evt; uint16_t sub_evt_intrvl; uint16_t evt_intrvl; uint16_t proc_intrvl; uint16_t proc_counter; uint16_t max_proc_len; }; struct pct_iq_sample { int16_t i_sample; int16_t q_sample; }; struct cs_mode_zero_data { uint8_t packet_quality; uint8_t packet_rssi_dbm; uint8_t packet_ant; uint16_t init_measured_freq_offset; }; struct cs_mode_one_data { uint8_t packet_quality; uint8_t packet_nadm; uint8_t packet_rssi_dbm; int16_t toa_tod_init; int16_t tod_toa_refl; uint8_t packet_ant; struct pct_iq_sample packet_pct1; struct pct_iq_sample packet_pct2; }; struct cs_mode_two_data { uint8_t ant_perm_index; struct pct_iq_sample tone_pct[5]; uint8_t tone_quality_indicator[5]; }; struct cs_mode_three_data { struct cs_mode_one_data mode_one_data; struct cs_mode_two_data mode_two_data; }; union cs_mode_data { struct cs_mode_zero_data mode_zero_data; struct cs_mode_one_data mode_one_data; struct cs_mode_two_data mode_two_data; struct cs_mode_three_data mode_three_data; }; struct cs_step_data { uint8_t step_mode; uint8_t step_chnl; uint8_t step_data_length; union cs_mode_data step_mode_data; }; struct rap_ev_cs_subevent_result { uint16_t conn_hdl; uint8_t config_id; uint16_t start_acl_conn_evt_counter; uint16_t proc_counter; uint16_t freq_comp; uint8_t ref_pwr_lvl; uint8_t proc_done_status; uint8_t subevt_done_status; uint8_t abort_reason; uint8_t num_ant_paths; uint8_t num_steps_reported; struct cs_step_data step_data[]; }; struct rap_ev_cs_subevent_result_cont { uint16_t conn_hdl; uint8_t config_id; uint8_t proc_done_status; uint8_t subevt_done_status; uint8_t abort_reason; uint8_t num_ant_paths; uint8_t num_steps_reported; struct cs_step_data step_data[]; }; typedef void (*bt_rap_debug_func_t)(const char *str, void *user_data); typedef void (*bt_rap_ready_func_t)(struct bt_rap *rap, void *user_data); typedef void (*bt_rap_destroy_func_t)(void *user_data); typedef void (*bt_rap_func_t)(struct bt_rap *rap, void *user_data); struct bt_rap *bt_rap_ref(struct bt_rap *rap); void bt_rap_unref(struct bt_rap *rap); void bt_rap_add_db(struct gatt_db *db); bool bt_rap_attach(struct bt_rap *rap, struct bt_gatt_client *client); void bt_rap_detach(struct bt_rap *rap); struct bt_att *bt_rap_get_att(struct bt_rap *rap); bool bt_rap_set_user_data(struct bt_rap *rap, void *user_data); bool bt_rap_set_debug(struct bt_rap *rap, bt_rap_debug_func_t func, void *user_data, bt_rap_destroy_func_t destroy); /* session related functions */ unsigned int bt_rap_register(bt_rap_func_t attached, bt_rap_func_t detached, void *user_data); unsigned int bt_rap_ready_register(struct bt_rap *rap, bt_rap_ready_func_t func, void *user_data, bt_rap_destroy_func_t destroy); bool bt_rap_ready_unregister(struct bt_rap *rap, unsigned int id); bool bt_rap_unregister(unsigned int id); struct bt_rap *bt_rap_new(struct gatt_db *ldb, struct gatt_db *rdb); /* HCI Raw Channel Approach */ void bt_rap_hci_cs_config_complete_callback(uint16_t length, const void *param, void *user_data); void bt_rap_hci_cs_sec_enable_complete_callback(uint16_t length, const void *param, void *user_data); void bt_rap_hci_cs_procedure_enable_complete_callback(uint16_t length, const void *param, void *user_data); void bt_rap_hci_cs_subevent_result_callback(uint16_t length, const void *param, void *user_data); void bt_rap_hci_cs_subevent_result_cont_callback(uint16_t length, const void *param, void *user_data); void *bt_rap_attach_hci(struct bt_rap *rap, struct bt_hci *hci, uint8_t role, uint8_t cs_sync_ant_sel, int8_t max_tx_power); void bt_rap_detach_hci(struct bt_rap *rap, void *hci_sm); /* Connection handle mapping functions */ bool bt_rap_set_conn_hndl(void *hci_sm, struct bt_rap *rap, uint16_t handle, const uint8_t *bdaddr, uint8_t bdaddr_type, bool is_central); void bt_rap_clear_conn_handle(void *hci_sm, uint16_t handle); bluez-5.87/src/shared/PaxHeaders/gatt-client.c0000644000000000000000000000005015200607060016263 xustar0020 atime=1783092683 20 ctime=1783094100 bluez-5.87/src/shared/gatt-client.c0000644000000000000000000025774315200607060015766 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Google Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #include "src/shared/att.h" #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "src/shared/gatt-helpers.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include #include #include #define UUID_BYTES (BT_GATT_UUID_SIZE * sizeof(uint8_t)) #define GATT_SVC_UUID 0x1801 #define SVC_CHNGD_UUID 0x2a05 #define DBG(_client, _format, arg...) \ gatt_log(_client, "[%p] %s:%s() " _format, _client, __FILE__, \ __func__, ## arg) struct ready_cb { bt_gatt_client_callback_t callback; bt_gatt_client_destroy_func_t destroy; void *data; }; struct idle_cb { bt_gatt_client_idle_callback_t callback; bt_gatt_client_destroy_func_t destroy; void *data; }; struct bt_gatt_client { struct bt_att *att; int ref_count; uint8_t features; struct bt_gatt_client *parent; struct queue *clones; struct queue *ready_cbs; struct queue *idle_cbs; bt_gatt_client_service_changed_callback_t svc_chngd_callback; bt_gatt_client_destroy_func_t svc_chngd_destroy; void *svc_chngd_data; bt_gatt_client_debug_func_t debug_callback; bt_gatt_client_destroy_func_t debug_destroy; void *debug_data; struct gatt_db *db; bool in_init; bool ready; /* * Queue of long write requests. An error during "prepare write" * requests can result in a cancel through "execute write". To prevent * cancellation of prepared writes to the wrong attribute and multiple * requests to the same attribute that may result in a corrupted final * value, we avoid interleaving prepared writes. */ struct queue *long_write_queue; bool in_long_write; unsigned int reliable_write_session_id; /* List of registered disconnect/notification/indication callbacks */ struct queue *notify_list; struct queue *notify_chrcs; int next_reg_id; unsigned int disc_id, nfy_id, nfy_mult_id, ind_id; /* * Handles of the GATT Service and the Service Changed characteristic * value handle. These will have the value 0 if they are not present on * the remote peripheral. */ unsigned int svc_chngd_ind_id; bool svc_chngd_registered; struct queue *svc_chngd_queue; /* Queued service changed events */ bool in_svc_chngd; /* * List of pending read/write operations. For operations that span * across multiple PDUs, this list provides a mapping from an operation * id to an ATT request id. */ struct queue *pending_requests; unsigned int next_request_id; struct bt_gatt_request *discovery_req; unsigned int mtu_req_id; /* Pending retry operation for DB out of sync handling */ unsigned int pending_retry_att_id; uint16_t pending_error_handle; }; struct request { struct bt_gatt_client *client; bool long_write; bool prep_write; bool removed; int ref_count; unsigned int id; unsigned int att_id; void *data; void (*destroy)(void *); }; static struct request *request_ref(struct request *req) { __sync_fetch_and_add(&req->ref_count, 1); return req; } static struct request *request_create(struct bt_gatt_client *client) { struct request *req; if (!client->att) return NULL; req = new0(struct request, 1); if (client->next_request_id < 1) client->next_request_id = 1; queue_push_tail(client->pending_requests, req); req->client = client; req->id = client->next_request_id++; return request_ref(req); } static void idle_destroy(void *data) { struct idle_cb *idle = data; if (idle->destroy) idle->destroy(idle->data); free(idle); } static bool idle_notify(const void *data, const void *user_data) { const struct idle_cb *idle = data; idle->callback(idle->data); return true; } static struct bt_gatt_client * bt_gatt_client_ref_safe(struct bt_gatt_client *client) { if (!client || !client->ref_count) return NULL; return bt_gatt_client_ref(client); } static void notify_client_idle(struct bt_gatt_client *client) { client = bt_gatt_client_ref_safe(client); if (!client) return; queue_remove_all(client->idle_cbs, idle_notify, NULL, idle_destroy); bt_gatt_client_unref(client); } static void request_unref(void *data) { struct request *req = data; struct bt_gatt_client *client = req->client; if (__sync_sub_and_fetch(&req->ref_count, 1)) return; if (req->destroy) req->destroy(req->data); if (!req->removed) { queue_remove(client->pending_requests, req); if (queue_isempty(client->pending_requests)) notify_client_idle(client); } free(req); } struct notify_chrc { struct bt_gatt_client *client; struct gatt_db_attribute *attr; uint16_t value_handle; uint16_t ccc_handle; uint16_t properties; unsigned int notify_id; int notify_count; /* Reference count of registered notify callbacks */ /* Pending calls to register_notify are queued here so that they can be * processed after a write that modifies the CCC descriptor. */ struct queue *reg_notify_queue; unsigned int ccc_write_id; }; struct notify_data { struct bt_gatt_client *client; unsigned int id; unsigned int att_id; uint8_t att_ecode; int ref_count; struct notify_chrc *chrc; bt_gatt_client_register_callback_t callback; bt_gatt_client_notify_callback_t notify; void *user_data; bt_gatt_client_destroy_func_t destroy; }; static struct notify_data *notify_data_ref(struct notify_data *notify_data) { __sync_fetch_and_add(¬ify_data->ref_count, 1); return notify_data; } static void notify_data_unref(void *data) { struct notify_data *notify_data = data; if (__sync_sub_and_fetch(¬ify_data->ref_count, 1)) return; if (notify_data->destroy) notify_data->destroy(notify_data->user_data); free(notify_data); } static bool match_notify_chrc(const void *data, const void *user_data) { const struct notify_data *notify_data = data; const struct notify_chrc *chrc = user_data; return notify_data->chrc == chrc; } static void notify_data_cleanup(void *data) { struct notify_data *notify_data = data; if (notify_data->att_id) bt_att_cancel(notify_data->client->att, notify_data->att_id); notify_data_unref(notify_data); } static void notify_chrc_free(void *data) { struct notify_chrc *chrc = data; if (chrc->notify_id) gatt_db_attribute_unregister(chrc->attr, chrc->notify_id); queue_destroy(chrc->reg_notify_queue, notify_data_unref); free(chrc); } static void chrc_removed(struct gatt_db_attribute *attr, void *user_data) { struct notify_chrc *chrc = user_data; struct bt_gatt_client *client = chrc->client; struct notify_data *data; chrc->notify_id = 0; while ((data = queue_remove_if(client->notify_list, match_notify_chrc, chrc))) notify_data_cleanup(data); queue_remove(client->notify_chrcs, chrc); notify_chrc_free(chrc); } static struct notify_chrc *notify_chrc_create(struct bt_gatt_client *client, uint16_t handle) { struct gatt_db_attribute *attr, *ccc; struct notify_chrc *chrc; uint16_t value_handle; uint8_t properties; /* Check that there is an attribute with handle */ attr = gatt_db_get_attribute(client->db, handle); if (!attr) return NULL; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, &properties, NULL, NULL)) return NULL; /* Check that there is an attribute with value_handle */ attr = gatt_db_get_attribute(client->db, value_handle); if (!attr) return NULL; chrc = new0(struct notify_chrc, 1); chrc->reg_notify_queue = queue_new(); if (!chrc->reg_notify_queue) { free(chrc); return NULL; } ccc = gatt_db_attribute_get_ccc(attr); if (ccc) chrc->ccc_handle = gatt_db_attribute_get_handle(ccc); chrc->client = client; chrc->attr = attr; chrc->value_handle = value_handle; chrc->properties = properties; chrc->notify_id = gatt_db_attribute_register(attr, chrc_removed, chrc, NULL); queue_push_tail(client->notify_chrcs, chrc); return chrc; } static bool match_notify_data_id(const void *a, const void *b) { const struct notify_data *notify_data = a; unsigned int id = PTR_TO_UINT(b); return notify_data->id == id; } struct handle_range { uint16_t start; uint16_t end; }; struct discovery_op; typedef void (*discovery_op_complete_func_t)(struct discovery_op *op, bool success, uint8_t att_ecode); typedef void (*discovery_op_fail_func_t)(struct discovery_op *op); struct discovery_op { struct bt_gatt_client *client; struct queue *discov_ranges; struct queue *pending_svcs; struct queue *pending_chrcs; struct queue *ext_prop_desc; struct gatt_db_attribute *cur_svc; struct gatt_db_attribute *hash; uint8_t server_feat; bool success; uint16_t start; uint16_t end; uint16_t last; uint16_t svc_first; uint16_t svc_last; unsigned int db_id; int ref_count; discovery_op_complete_func_t complete_func; discovery_op_fail_func_t failure_func; }; static void discovery_op_free(struct discovery_op *op) { if (op->db_id > 0) gatt_db_unregister(op->client->db, op->db_id); queue_destroy(op->discov_ranges, free); queue_destroy(op->pending_svcs, NULL); queue_destroy(op->pending_chrcs, free); queue_destroy(op->ext_prop_desc, NULL); free(op); } static bool read_db_hash(struct discovery_op *op); static void gatt_log_va(struct bt_gatt_client *client, const char *format, va_list va) { if (!client || !format) return; if (client->debug_callback) util_debug_va(client->debug_callback, client->debug_data, format, va); else gatt_log_va(client->parent, format, va); } static void gatt_log(struct bt_gatt_client *client, const char *format, ...) { va_list ap; if (!client || !format) return; va_start(ap, format); gatt_log_va(client, format, ap); va_end(ap); } static void discovery_op_complete(struct discovery_op *op, bool success, uint8_t err) { const struct queue_entry *svc; op->success = success; /* Read database hash if discovery has been successful */ if (success && read_db_hash(op)) return; /* * Unregister remove callback so it is not called when clearing unused * range. */ gatt_db_unregister(op->client->db, op->db_id); op->db_id = 0; /* Remove services pending */ for (svc = queue_get_entries(op->pending_svcs); svc; svc = svc->next) { struct gatt_db_attribute *attr = svc->data; uint16_t start, end; /* Leave active services if operation was aborted */ if ((!success && err == 0) && gatt_db_service_get_active(attr)) continue; gatt_db_attribute_get_service_data(attr, &start, &end, NULL, NULL); DBG(op->client, "service disappeared: start 0x%04x end 0x%04x", start, end); gatt_db_remove_service(op->client->db, attr); } /* Reset remaining range */ if (op->last != UINT16_MAX) gatt_db_clear_range(op->client->db, op->last + 1, UINT16_MAX); op->complete_func(op, success, err); } static void discovery_load_services(struct gatt_db_attribute *attr, void *user_data) { struct discovery_op *op = user_data; queue_push_tail(op->pending_svcs, attr); } static void discovery_service_changed(struct gatt_db_attribute *attr, void *user_data) { struct discovery_op *op = user_data; queue_remove(op->pending_svcs, attr); } static struct discovery_op *discovery_op_create(struct bt_gatt_client *client, uint16_t start, uint16_t end, discovery_op_complete_func_t complete_func, discovery_op_fail_func_t failure_func) { struct discovery_op *op; struct handle_range *range; op = new0(struct discovery_op, 1); op->discov_ranges = queue_new(); op->pending_svcs = queue_new(); op->pending_chrcs = queue_new(); op->ext_prop_desc = queue_new(); op->client = client; op->complete_func = complete_func; op->failure_func = failure_func; op->start = start; op->end = end; op->last = gatt_db_isempty(client->db) ? 0 : UINT16_MAX; op->svc_first = UINT16_MAX; op->svc_last = 0; /* Load existing services as pending */ gatt_db_foreach_service_in_range(client->db, NULL, discovery_load_services, op, start, end); /* * Services are only added when set active in which case they are no * longer pending so it is safe to remove either way. */ op->db_id = gatt_db_register(client->db, discovery_service_changed, discovery_service_changed, op, NULL); range = new0(struct handle_range, 1); range->start = start; range->end = end; queue_push_tail(op->discov_ranges, range); return op; } static struct discovery_op *discovery_op_ref(struct discovery_op *op) { __sync_fetch_and_add(&op->ref_count, 1); return op; } static void discovery_op_unref(void *data) { struct discovery_op *op = data; if (__sync_sub_and_fetch(&op->ref_count, 1)) return; if (!op->success && op->failure_func) op->failure_func(op); discovery_op_free(op); } static void discovery_req_clear(struct bt_gatt_client *client) { if (!client->discovery_req) return; bt_gatt_request_unref(client->discovery_req); client->discovery_req = NULL; } static void discover_remove_pending(struct discovery_op *op, struct gatt_db_attribute *attr) { struct gatt_db_attribute *svc; svc = gatt_db_attribute_get_service(attr); if (!svc) return; if (!queue_remove(op->pending_svcs, svc)) return; gatt_db_service_set_active(svc, true); if (op->cur_svc == svc) op->cur_svc = NULL; } static void discover_chrcs_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data); static void discover_incl_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; struct bt_gatt_iter iter; struct gatt_db_attribute *attr; uint16_t handle, start, end; uint128_t u128; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; unsigned int includes_count, i; struct handle_range *range; discovery_req_clear(client); if (!success) { if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) goto next; goto failed; } if (!result || !bt_gatt_iter_init(&iter, result)) goto failed; includes_count = bt_gatt_result_included_count(result); if (includes_count == 0) goto failed; DBG(client, "Included services found: %u", includes_count); for (i = 0; i < includes_count; i++) { if (!bt_gatt_iter_next_included_service(&iter, &handle, &start, &end, u128.data)) break; bt_uuid128_create(&uuid, u128); /* Log debug message */ bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); DBG(client, "handle: 0x%04x, start: 0x%04x, end: 0x%04x," "uuid: %s", handle, start, end, uuid_str); attr = gatt_db_get_attribute(client->db, start); if (!attr) { DBG(client, "Unable to find attribute at 0x%04x: skipping", start); continue; } attr = gatt_db_insert_included(client->db, handle, attr); if (!attr) { DBG(client, "Unable to add include attribute at 0x%04x", handle); goto failed; } /* * GATT requires that all include definitions precede * characteristic declarations. Based on the order we're adding * these entries, the correct handle must be assigned to the new * attribute. */ if (gatt_db_attribute_get_handle(attr) != handle) { DBG(client, "Invalid attribute 0x%04x expect it at 0x%04x", gatt_db_attribute_get_handle(attr), handle); goto failed; } if (!gatt_db_attribute_get_service_data(attr, NULL, &end, NULL, NULL)) { DBG(client, "Unable to get service data at 0x%04x", handle); goto failed; } /* Skip if there are no attributes */ if (handle == end) discover_remove_pending(op, attr); } next: range = queue_pop_head(op->discov_ranges); if (!range) { /* Skip if there are no attributes */ discover_remove_pending(op, op->cur_svc); goto failed; } client->discovery_req = bt_gatt_discover_characteristics(client->att, range->start, range->end, discover_chrcs_cb, discovery_op_ref(op), discovery_op_unref); free(range); if (client->discovery_req) return; DBG(client, "Failed to start characteristic discovery"); discovery_op_unref(op); failed: discovery_op_complete(op, false, att_ecode); } struct chrc { uint16_t start_handle; uint16_t end_handle; uint16_t value_handle; uint8_t properties; bt_uuid_t uuid; }; static void discover_descs_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data); static bool discover_descs(struct discovery_op *op, bool *discovering) { struct bt_gatt_client *client = op->client; struct gatt_db_attribute *attr; struct chrc *chrc_data; uint16_t desc_start; *discovering = false; while ((chrc_data = queue_pop_head(op->pending_chrcs))) { struct gatt_db_attribute *svc; uint16_t start, end; /* Adjust current service */ svc = gatt_db_get_service(client->db, chrc_data->value_handle); if (op->cur_svc != svc) { if (op->cur_svc) { queue_remove(op->pending_svcs, op->cur_svc); /* Done with the current service */ gatt_db_service_set_active(op->cur_svc, true); } op->cur_svc = svc; } attr = gatt_db_insert_characteristic(client->db, chrc_data->start_handle, chrc_data->value_handle, &chrc_data->uuid, 0, chrc_data->properties, NULL, NULL, NULL); if (!attr) { DBG(client, "Failed to insert characteristic at 0x%04x", chrc_data->value_handle); /* Some devices have been seen reporting orphaned * characteristics. In order to favor interoperability * we skip over characteristics in error */ free(chrc_data); continue; } if (gatt_db_attribute_get_handle(attr) != chrc_data->value_handle) goto failed; gatt_db_attribute_get_service_handles(svc, &start, &end); /* * Adjust end_handle in case the next chrc is not within the * same service. */ if (chrc_data->end_handle > end) chrc_data->end_handle = end; /* * check for descriptors presence, before initializing the * desc_handle and avoid integer overflow during desc_handle * initialization. */ if (chrc_data->value_handle >= chrc_data->end_handle) { free(chrc_data); continue; } desc_start = chrc_data->value_handle + 1; if (desc_start == chrc_data->end_handle && (chrc_data->properties & BT_GATT_CHRC_PROP_NOTIFY || chrc_data->properties & BT_GATT_CHRC_PROP_INDICATE)) { bt_uuid_t ccc_uuid; /* If there is only one descriptor that must be the CCC * in case either notify or indicate are supported. */ bt_uuid16_create(&ccc_uuid, GATT_CLIENT_CHARAC_CFG_UUID); attr = gatt_db_insert_descriptor(client->db, desc_start, &ccc_uuid, 0, NULL, NULL, NULL); if (attr) { free(chrc_data); continue; } } /* Check if the start range is within characteristic range */ if (desc_start > chrc_data->end_handle) { free(chrc_data); continue; } client->discovery_req = bt_gatt_discover_descriptors( client->att, desc_start, chrc_data->end_handle, discover_descs_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) { *discovering = true; goto done; } DBG(client, "Failed to start descriptor discovery"); discovery_op_unref(op); goto failed; } /* Done with the current service */ discover_remove_pending(op, op->cur_svc); done: free(chrc_data); return true; failed: DBG(client, "Failed to discover descriptors"); free(chrc_data); return false; } static void ext_prop_write_cb(struct gatt_db_attribute *attrib, int err, void *user_data) { struct bt_gatt_client *client = user_data; DBG(client, "Value set status: %d", err); } static void ext_prop_read_cb(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data); static bool read_ext_prop_desc(struct discovery_op *op) { struct bt_gatt_client *client = op->client; uint16_t handle; struct gatt_db_attribute *attr; attr = queue_peek_head(op->ext_prop_desc); if (!attr) return false; handle = gatt_db_attribute_get_handle(attr); if (!bt_gatt_client_read_value(client, handle, ext_prop_read_cb, discovery_op_ref(op), discovery_op_unref)) return false; return true; } static void ext_prop_read_cb(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; bool discovering; struct gatt_db_attribute *desc_attr = NULL; if (!success) goto done; DBG(client, "Ext. prop value: 0x%04x", (uint16_t)value[0]); desc_attr = queue_pop_head(op->ext_prop_desc); if (!desc_attr) goto failed; if (!gatt_db_attribute_write(desc_attr, 0, value, length, 0, NULL, ext_prop_write_cb, client)) goto failed; /* Any other descriptor to read? */ if (read_ext_prop_desc(op)) return; if (!discover_descs(op, &discovering)) goto failed; if (discovering) return; goto done; failed: success = false; done: discovery_op_complete(op, success, att_ecode); } static void discover_descs_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; struct bt_gatt_iter iter; struct gatt_db_attribute *attr; uint16_t handle; uint128_t u128; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; unsigned int desc_count; bool discovering; bt_uuid_t ext_prop_uuid; discovery_req_clear(client); if (!success) { if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) { success = true; goto next; } goto done; } if (!result || !bt_gatt_iter_init(&iter, result)) goto failed; desc_count = bt_gatt_result_descriptor_count(result); if (desc_count == 0) goto failed; DBG(client, "Descriptors found: %u", desc_count); bt_uuid16_create(&ext_prop_uuid, GATT_CHARAC_EXT_PROPER_UUID); while (bt_gatt_iter_next_descriptor(&iter, &handle, u128.data)) { bt_uuid128_create(&uuid, u128); /* Log debug message */ bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); DBG(client, "handle: 0x%04x, uuid: %s", handle, uuid_str); attr = gatt_db_insert_descriptor(client->db, handle, &uuid, 0, NULL, NULL, NULL); if (!attr) { attr = gatt_db_get_attribute(client->db, handle); if (attr && !bt_uuid_cmp(&uuid, gatt_db_attribute_get_type(attr))) continue; DBG(client, "Failed to insert descriptor at 0x%04x", handle); goto failed; } if (gatt_db_attribute_get_handle(attr) != handle) goto failed; if (!bt_uuid_cmp(&ext_prop_uuid, &uuid)) queue_push_tail(op->ext_prop_desc, attr); } /* If we got extended prop descriptor, lets read it right away */ if (read_ext_prop_desc(op)) return; next: if (!discover_descs(op, &discovering)) goto failed; if (discovering) return; goto done; failed: success = false; done: discovery_op_complete(op, success, att_ecode); } static void discover_chrcs_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; struct bt_gatt_iter iter; struct chrc *chrc_data; uint16_t start, end, value; uint8_t properties; uint128_t u128; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; unsigned int chrc_count; bool discovering; discovery_req_clear(client); if (!success) { if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) { success = true; goto next; } goto done; } if (!result || !bt_gatt_iter_init(&iter, result)) goto failed; chrc_count = bt_gatt_result_characteristic_count(result); DBG(client, "Characteristics found: %u", chrc_count); if (chrc_count == 0) goto failed; while (bt_gatt_iter_next_characteristic(&iter, &start, &end, &value, &properties, u128.data)) { bt_uuid128_create(&uuid, u128); /* Log debug message */ bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); DBG(client, "start: 0x%04x, end: 0x%04x, value: 0x%04x, " "props: 0x%02x, uuid: %s", start, end, value, properties, uuid_str); chrc_data = new0(struct chrc, 1); chrc_data->start_handle = start; chrc_data->end_handle = end; chrc_data->value_handle = value; chrc_data->properties = properties; chrc_data->uuid = uuid; queue_push_tail(op->pending_chrcs, chrc_data); } next: /* * Before attempting to process discovered characteristics make sure we * discovered all missing ranges. */ if (queue_length(op->discov_ranges)) { struct handle_range *range; range = queue_peek_head(op->discov_ranges); if (!range) goto failed; client->discovery_req = bt_gatt_discover_included_services(client->att, range->start, range->end, discover_incl_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) return; DBG(client, "Failed to start included services discovery"); discovery_op_unref(op); goto failed; } /* * Sequentially discover descriptors for each characteristic and insert * the characteristics into the database as we proceed. */ if (!discover_descs(op, &discovering)) goto failed; if (discovering) return; goto done; failed: success = false; done: discovery_op_complete(op, success, att_ecode); } static bool match_handle_range(const void *data, const void *match_data) { const struct handle_range *range = data; const struct handle_range *match_range = match_data; return (match_range->start >= range->start) && (match_range->start <= range->end); } static struct handle_range *range_new(uint16_t start, uint16_t end) { struct handle_range *range; if (!start || !end || start > end) return NULL; range = new0(struct handle_range, 1); range->start = start; range->end = end; return range; } static void remove_discov_range(struct discovery_op *op, uint16_t start, uint16_t end) { struct handle_range match_range; struct handle_range *range, *new_range; match_range.start = start; match_range.end = end; range = queue_find(op->discov_ranges, match_handle_range, &match_range); if (!range) return; if ((range->start == start) && (range->end == end)) { queue_remove(op->discov_ranges, range); free(range); } else if (range->start == start) { range->start = end + 1; if (!range->start || range->start > range->end) { queue_remove(op->discov_ranges, range); free(range); } } else if (range->end == end) range->end = start - 1; else { new_range = range_new(end + 1, range->end); if (new_range) queue_push_after(op->discov_ranges, range, new_range); range->end = start - 1; } } static void discovery_found_service(struct discovery_op *op, struct gatt_db_attribute *attr, uint16_t start, uint16_t end) { /* Skip if service already active */ if (!gatt_db_service_get_active(attr)) { /* Skip if there are no attributes */ if (end == start) gatt_db_service_set_active(attr, true); else queue_push_tail(op->pending_svcs, attr); if (start < op->svc_first) op->svc_first = start; if (end > op->svc_last) op->svc_last = end; } else { /* Remove from pending if active */ queue_remove(op->pending_svcs, attr); remove_discov_range(op, start, end); } /* Update last handle */ if (end > op->last) op->last = end; } static bool discovery_parse_services(struct discovery_op *op, bool primary, struct bt_gatt_iter *iter) { struct bt_gatt_client *client = op->client; struct gatt_db_attribute *attr; uint16_t start, end; uint128_t u128; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; while (bt_gatt_iter_next_service(iter, &start, &end, u128.data)) { bt_uuid128_create(&uuid, u128); /* Log debug message */ bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); DBG(client, "start: 0x%04x, end: 0x%04x, uuid: %s", start, end, uuid_str); /* Store the service */ attr = gatt_db_insert_service(client->db, start, &uuid, primary, end - start + 1); if (!attr) { gatt_db_clear_range(client->db, start, end); attr = gatt_db_insert_service(client->db, start, &uuid, false, end - start + 1); if (!attr) { DBG(client, "Failed to store service"); return false; } /* Database has changed adjust last handle */ op->last = end; } /* Update pending list */ discovery_found_service(op, attr, start, end); } return true; } static void discover_secondary_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; struct bt_gatt_iter iter; struct handle_range *range; discovery_req_clear(client); if (!success) { switch (att_ecode) { case BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND: case BT_ATT_ERROR_UNSUPPORTED_GROUP_TYPE: success = true; att_ecode = 0; goto next; default: DBG(client, "Secondary service discovery failed." " ATT ECODE: 0x%02x", att_ecode); goto done; } } if (!result || !bt_gatt_iter_init(&iter, result)) { success = false; goto done; } DBG(client, "Secondary services found: %u", bt_gatt_result_service_count(result)); if (!discovery_parse_services(op, false, &iter)) { success = false; goto done; } next: if (queue_isempty(op->pending_svcs) || queue_isempty(op->discov_ranges)) goto done; if (op->svc_first > 0x0001) remove_discov_range(op, 1, op->svc_first - 1); if (op->svc_last < 0xffff) remove_discov_range(op, op->svc_last + 1, 0xffff); range = queue_peek_head(op->discov_ranges); if (range) client->discovery_req = bt_gatt_discover_included_services( client->att, range->start, range->end, discover_incl_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) return; DBG(client, "Failed to start included services discovery"); discovery_op_unref(op); success = false; done: discovery_op_complete(op, success, att_ecode); } static void discover_primary_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; struct bt_gatt_iter iter; discovery_req_clear(client); if (!success) { /* Reset error in case of not found */ switch (att_ecode) { case BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND: success = true; att_ecode = 0; goto secondary; default: DBG(client, "Primary service discovery failed." " ATT ECODE: 0x%02x", att_ecode); goto done; } } if (!result || !bt_gatt_iter_init(&iter, result)) { success = false; goto done; } DBG(client, "Primary services found: %u", bt_gatt_result_service_count(result)); if (!discovery_parse_services(op, true, &iter)) { success = false; goto done; } secondary: /* * Version 4.2 [Vol 1, Part A] page 101: * A secondary service is a service that provides auxiliary * functionality of a device and is referenced from at least one * primary service on the device. */ if (queue_isempty(op->pending_svcs)) goto done; /* Discover secondary services */ client->discovery_req = bt_gatt_discover_secondary_services(client->att, NULL, op->start, op->end, discover_secondary_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) return; DBG(client, "Failed to start secondary service discovery"); discovery_op_unref(op); success = false; done: discovery_op_complete(op, success, att_ecode); } static void ready_destroy(void *data) { struct ready_cb *ready = data; if (ready->destroy) ready->destroy(ready->data); free(ready); } static void notify_client_ready(struct bt_gatt_client *client, bool success, uint8_t att_ecode) { const struct queue_entry *entry; client = bt_gatt_client_ref_safe(client); if (!client) return; if (client->ready) goto done; client->ready = success; if (client->parent) client->features = client->parent->features; for (entry = queue_get_entries(client->ready_cbs); entry; entry = entry->next) { struct ready_cb *ready = entry->data; ready->callback(success, att_ecode, ready->data); } queue_remove_all(client->ready_cbs, NULL, NULL, ready_destroy); /* Notify clones */ for (entry = queue_get_entries(client->clones); entry; entry = entry->next) { struct bt_gatt_client *clone = entry->data; notify_client_ready(clone, success, att_ecode); } done: bt_gatt_client_unref(client); } static void discover_all(struct discovery_op *op) { struct bt_gatt_client *client = op->client; client->discovery_req = bt_gatt_discover_all_primary_services( client->att, NULL, discover_primary_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) return; DBG(client, "Failed to initiate primary service discovery"); client->in_init = false; notify_client_ready(client, false, BT_ATT_ERROR_UNLIKELY); discovery_op_unref(op); } static void db_hash_write_value_cb(struct gatt_db_attribute *attrib, int err, void *user_data) { struct bt_gatt_client *client = user_data; DBG(client, "Value set status: %d", err); } static void db_hash_read_value_cb(struct gatt_db_attribute *attrib, int err, const uint8_t *value, size_t length, void *user_data) { const uint8_t **hash = user_data; if (err || (length != 16)) return; *hash = value; } static void db_hash_read_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; const uint8_t *hash = NULL, *value; uint16_t len, handle; struct bt_gatt_iter iter; if (!success) goto discover; bt_gatt_iter_init(&iter, result); bt_gatt_iter_next_read_by_type(&iter, &handle, &len, &value); DBG(client, "DB Hash found: handle 0x%04x length 0x%04x", handle, len); if (len != 16) goto discover; /* Read stored value in the db */ gatt_db_attribute_read(op->hash, 0, BT_ATT_OP_READ_REQ, NULL, db_hash_read_value_cb, &hash); /* Check if the has has changed since last time */ if (hash && !memcmp(hash, value, len)) { DBG(client, "DB Hash match: skipping discovery"); queue_remove_all(op->pending_svcs, NULL, NULL, NULL); discovery_op_complete(op, true, 0); return; } DBG(client, "DB Hash value:"); util_hexdump(' ', value, len, client->debug_callback, client->debug_data); /* Store ithe new hash in the db */ gatt_db_attribute_write(op->hash, 0, value, len, 0, NULL, db_hash_write_value_cb, client); discover: if (!op->success) { discover_all(op); return; } discovery_op_complete(op, true, 0); } static void get_first_attribute(struct gatt_db_attribute *attrib, void *user_data) { struct gatt_db_attribute **stored = user_data; if (*stored) return; *stored = attrib; } static bool read_db_hash(struct discovery_op *op) { struct bt_gatt_client *client = op->client; bt_uuid_t uuid; /* Check if hash was already been read or there are more services to * process. */ if (op->hash || !queue_isempty(client->svc_chngd_queue)) return false; bt_uuid16_create(&uuid, GATT_CHARAC_DB_HASH); gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, get_first_attribute, &op->hash); if (!op->hash) return false; if (!bt_gatt_read_by_type(client->att, 0x0001, 0xffff, &uuid, db_hash_read_cb, discovery_op_ref(op), discovery_op_unref)) { discovery_op_unref(op); return false; } return true; } static void db_server_feat_read(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; const uint8_t *value; uint16_t len, handle; struct bt_gatt_iter iter; if (!result) return; bt_gatt_iter_init(&iter, result); bt_gatt_iter_next_read_by_type(&iter, &handle, &len, &value); DBG(client, "Server Features found: handle 0x%04x " "length 0x%04x value 0x%02x", handle, len, value[0]); op->server_feat = value[0]; } static void server_feat_read_value(struct gatt_db_attribute *attrib, int err, const uint8_t *value, size_t length, void *user_data) { const uint8_t **feat = user_data; if (err) return; *feat = value; } static void read_server_feat(struct discovery_op *op) { struct bt_gatt_client *client = op->client; struct gatt_db_attribute *attr = NULL; const uint8_t *feat = NULL; bt_uuid_t uuid; bt_uuid16_create(&uuid, GATT_CHARAC_SERVER_FEAT); gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, get_first_attribute, &attr); if (attr) { /* Read stored value in the db */ gatt_db_attribute_read(attr, 0, BT_ATT_OP_READ_REQ, NULL, server_feat_read_value, &feat); if (feat) return; } if (!bt_gatt_read_by_type(client->att, 0x0001, 0xffff, &uuid, db_server_feat_read, discovery_op_ref(op), discovery_op_unref)) discovery_op_unref(op); } static void exchange_mtu_cb(bool success, uint8_t att_ecode, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; op->success = success; client->mtu_req_id = 0; if (!success) { DBG(client, "MTU Exchange failed. ATT ECODE: 0x%02x", att_ecode); /* * BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part G] page 546 * If the Error Response is sent by the server with the Error * Code set to RequestNot Supported , the Attribute Opcode is * not supported and the default MTU shall be used. */ if (att_ecode == BT_ATT_ERROR_REQUEST_NOT_SUPPORTED) goto discover; client->in_init = false; notify_client_ready(client, success, att_ecode); return; } DBG(client, "MTU exchange complete, with MTU: %u", bt_att_get_mtu(client->att)); discover: read_server_feat(op); if (read_db_hash(op)) { op->success = false; return; } discover_all(op); } struct service_changed_op { struct bt_gatt_client *client; uint16_t start_handle; uint16_t end_handle; }; static void process_service_changed(struct bt_gatt_client *client, uint16_t start_handle, uint16_t end_handle); static void service_changed_cb(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data); static void complete_notify_request(void *data) { struct notify_data *notify_data = data; notify_data->att_id = 0; if (notify_data->callback) notify_data->callback(notify_data->att_ecode, notify_data->user_data); } static bool notify_data_write_ccc(struct notify_data *notify_data, bool enable, bt_gatt_client_callback_t callback) { unsigned int att_id; uint16_t value = 0x0000; uint16_t properties = notify_data->chrc->properties; assert(notify_data->chrc->ccc_handle); if (enable) { /* Try to enable notifications or indications based on * whatever the characteristic supports. */ if (properties & BT_GATT_CHRC_PROP_NOTIFY) value = cpu_to_le16(0x0001); else if (properties & BT_GATT_CHRC_PROP_INDICATE) value = cpu_to_le16(0x0002); else return false; } att_id = bt_gatt_client_write_value(notify_data->client, notify_data->chrc->ccc_handle, (void *)&value, sizeof(value), callback, notify_data_ref(notify_data), notify_data_unref); if (!att_id) notify_data_unref(notify_data); notify_data->chrc->ccc_write_id = notify_data->att_id = att_id; return !!att_id; } static uint8_t process_error(const void *pdu, uint16_t length) { const struct bt_att_pdu_error_rsp *error_pdu; if (!pdu || length != sizeof(struct bt_att_pdu_error_rsp)) return BT_ATT_ERROR_UNLIKELY; error_pdu = pdu; return error_pdu->ecode; } static bool notify_set_ecode(const void *data, const void *match_data) { struct notify_data *notify_data = (void *)data; uint8_t ecode = PTR_TO_UINT(match_data); notify_data->att_ecode = ecode; return true; } static void enable_ccc_callback(bool success, uint8_t att_ecode, void *user_data) { struct notify_data *notify_data = user_data; assert(notify_data->chrc->ccc_write_id); notify_data->chrc->ccc_write_id = 0; bt_gatt_client_ref_safe(notify_data->client); notify_data->att_ecode = att_ecode; /* Notify for all remaining requests. */ complete_notify_request(notify_data); queue_remove_all(notify_data->chrc->reg_notify_queue, notify_set_ecode, UINT_TO_PTR(notify_data->att_ecode), complete_notify_request); bt_gatt_client_unref(notify_data->client); } static bool match_notify_chrc_value_handle(const void *a, const void *b) { const struct notify_chrc *chrc = a; uint16_t value_handle = PTR_TO_UINT(b); return chrc->value_handle == value_handle; } static unsigned int register_notify(struct bt_gatt_client *client, uint16_t handle, bt_gatt_client_register_callback_t callback, bt_gatt_client_notify_callback_t notify, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct notify_data *notify_data; struct notify_chrc *chrc = NULL; /* Check if a characteristic ref count has been started already */ chrc = queue_find(client->notify_chrcs, match_notify_chrc_value_handle, UINT_TO_PTR(handle)); if (!chrc) { /* * Create an entry if the characteristic is known and has a CCC * descriptor. */ chrc = notify_chrc_create(client, handle); if (!chrc) { DBG(client, "Unable to locate characteristic at 0x%04x", handle); return 0; } } /* Fail if we've hit the maximum allowed notify sessions */ if (chrc->notify_count == INT_MAX) return 0; notify_data = new0(struct notify_data, 1); notify_data->client = client; notify_data->ref_count = 1; notify_data->chrc = chrc; notify_data->callback = callback; notify_data->notify = notify; notify_data->user_data = user_data; notify_data->destroy = destroy; /* Add the handler to the bt_gatt_client's general list */ queue_push_tail(client->notify_list, notify_data); /* Assign an ID to the handler. */ if (client->next_reg_id < 1) client->next_reg_id = 1; notify_data->id = client->next_reg_id++; /* Increment the per-characteristic ref count of notify handlers */ __sync_fetch_and_add(¬ify_data->chrc->notify_count, 1); /* * If a write to the CCC descriptor is in progress, then queue this * request. */ if (chrc->ccc_write_id) { queue_push_tail(chrc->reg_notify_queue, notify_data); return notify_data->id; } /* * If the ref count > 1, ccc handle cannot be found or registration * callback is not set consider notifications are already enabled. */ if (chrc->notify_count > 1 || !chrc->ccc_handle || !callback) { complete_notify_request(notify_data); return notify_data->id; } /* Write to the CCC descriptor */ if (!notify_data_write_ccc(notify_data, true, enable_ccc_callback)) { queue_remove(client->notify_list, notify_data); free(notify_data); return 0; } return notify_data->id; } static void service_changed_register_cb(uint16_t att_ecode, void *user_data) { bool success; struct bt_gatt_client *client = user_data; if (att_ecode) { DBG(client, "Failed to register handler for \"Service Changed\""); success = false; client->svc_chngd_ind_id = 0; goto done; } client->svc_chngd_registered = true; success = true; DBG(client, "Registered handler for \"Service Changed\": %u", client->svc_chngd_ind_id); done: notify_client_ready(client, success, att_ecode); } static bool register_service_changed(struct bt_gatt_client *client) { bt_uuid_t uuid; struct gatt_db_attribute *attr = NULL; bt_uuid16_create(&uuid, SVC_CHNGD_UUID); if (client->svc_chngd_ind_id) return true; gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, get_first_attribute, &attr); if (!attr) return true; /* * Register an indication handler for the "Service Changed" * characteristic and report ready only if the handler is registered * successfully. */ client->svc_chngd_ind_id = register_notify(client, gatt_db_attribute_get_handle(attr), service_changed_register_cb, service_changed_cb, client, NULL); return client->svc_chngd_ind_id ? true : false; } static void service_changed_complete(struct discovery_op *op, bool success, uint8_t att_ecode) { struct bt_gatt_client *client = op->client; struct service_changed_op *next_sc_op; uint16_t start_handle = op->start; uint16_t end_handle = op->end; const struct queue_entry *entry; client->in_svc_chngd = false; if (!success && att_ecode != BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) { DBG(client, "Failed to discover services within changed range - " "error: 0x%02x", att_ecode); gatt_db_clear_range(client->db, start_handle, end_handle); } /* Notify the upper layer of changed services */ if (client->svc_chngd_callback) client->svc_chngd_callback(start_handle, end_handle, client->svc_chngd_data); /* Notify clones */ for (entry = queue_get_entries(client->clones); entry; entry = entry->next) { struct bt_gatt_client *clone = entry->data; if (clone->svc_chngd_callback) clone->svc_chngd_callback(start_handle, end_handle, clone->svc_chngd_data); } /* Process any queued events */ next_sc_op = queue_pop_head(client->svc_chngd_queue); if (next_sc_op) { process_service_changed(client, next_sc_op->start_handle, next_sc_op->end_handle); free(next_sc_op); return; } if (register_service_changed(client)) return; DBG(client, "Failed to re-register handler for \"Service Changed\""); } static void service_changed_failure(struct discovery_op *op) { struct bt_gatt_client *client = op->client; gatt_db_clear_range(client->db, op->start, op->end); } static void process_service_changed(struct bt_gatt_client *client, uint16_t start_handle, uint16_t end_handle) { struct discovery_op *op; op = discovery_op_create(client, start_handle, end_handle, service_changed_complete, service_changed_failure); if (!op) goto fail; client->discovery_req = bt_gatt_discover_primary_services(client->att, NULL, start_handle, end_handle, discover_primary_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) { client->in_svc_chngd = true; return; } discovery_op_free(op); fail: DBG(client, "Failed to initiate service discovery after Service Changed"); } static void service_changed_cb(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct bt_gatt_client *client = user_data; struct service_changed_op *op; uint16_t start, end; if (length != 4) return; start = get_le16(value); end = get_le16(value + 2); if (start > end) { DBG(client, "Service Changed received with invalid handles"); return; } DBG(client, "Service Changed received - start: 0x%04x end: 0x%04x", start, end); if (!client->in_svc_chngd) { process_service_changed(client, start, end); return; } op = new0(struct service_changed_op, 1); op->start_handle = start; op->end_handle = end; queue_push_tail(client->svc_chngd_queue, op); } static void server_feat_write_value(struct gatt_db_attribute *attrib, int err, void *user_data) { struct bt_gatt_client *client = user_data; DBG(client, "Server Features Value set status: %d", err); } static void write_server_features(struct bt_gatt_client *client, uint8_t feat) { bt_uuid_t uuid; struct gatt_db_attribute *attr = NULL; bt_uuid16_create(&uuid, GATT_CHARAC_SERVER_FEAT); gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, get_first_attribute, &attr); if (!attr) return; /* Store value in the DB */ if (!gatt_db_attribute_write(attr, 0, &feat, sizeof(feat), 0, NULL, server_feat_write_value, client)) DBG(client, "Unable to store Server Features"); } static void write_client_features(struct bt_gatt_client *client) { bt_uuid_t uuid; struct gatt_db_attribute *attr = NULL; uint16_t handle; const uint8_t *feat = NULL; bt_uuid16_create(&uuid, GATT_CHARAC_CLI_FEAT); gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, get_first_attribute, &attr); if (!attr) return; handle = gatt_db_attribute_get_handle(attr); client->features |= BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING; bt_uuid16_create(&uuid, GATT_CHARAC_SERVER_FEAT); attr = NULL; gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, get_first_attribute, &attr); if (attr) { /* Read stored value in the db */ gatt_db_attribute_read(attr, 0, BT_ATT_OP_READ_REQ, NULL, server_feat_read_value, &feat); if (!(feat && feat[0] & BT_GATT_CHRC_SERVER_FEAT_EATT) || !(client->features & BT_GATT_CHRC_CLI_FEAT_EATT)) client->features &= ~BT_GATT_CHRC_CLI_FEAT_EATT; } client->features |= BT_GATT_CHRC_CLI_FEAT_NFY_MULTI; DBG(client, "Writing Client Features 0x%02x", client->features); bt_gatt_client_write_value(client, handle, &client->features, sizeof(client->features), NULL, NULL, NULL); } static void init_complete(struct discovery_op *op, bool success, uint8_t att_ecode) { struct bt_gatt_client *client = op->client; client->in_init = false; if (!success) goto fail; if (op->server_feat) write_server_features(client, op->server_feat); write_client_features(client); if (register_service_changed(client)) goto done; DBG(client, "Failed to register handler for \"Service Changed\""); success = false; fail: DBG(client, "Failed to initialize gatt-client"); op->success = false; done: notify_client_ready(client, success, att_ecode); } static bool gatt_client_init(struct bt_gatt_client *client, uint16_t mtu) { struct discovery_op *op; if (client->in_init || client->ready) return false; op = discovery_op_create(client, 0x0001, 0xffff, init_complete, NULL); if (!op) return false; /* * BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part G] page 546: * * 4.3.1 Exchange MTU * * This sub-procedure shall not be used on a BR/EDR physical link since * the MTU size is negotiated using L2CAP channel configuration * procedures. */ if (bt_att_get_link_type(client->att) == BT_ATT_BREDR) goto discover; /* Check if MTU needs to be send */ mtu = MAX(BT_ATT_DEFAULT_LE_MTU, mtu); if (mtu == BT_ATT_DEFAULT_LE_MTU) goto discover; /* Configure the MTU */ client->mtu_req_id = bt_gatt_exchange_mtu(client->att, mtu, exchange_mtu_cb, discovery_op_ref(op), discovery_op_unref); if (!client->mtu_req_id) { discovery_op_free(op); return false; } client->in_init = true; return true; discover: read_server_feat(op); if (read_db_hash(op)) { op->success = false; goto done; } client->discovery_req = bt_gatt_discover_all_primary_services( client->att, NULL, discover_primary_cb, discovery_op_ref(op), discovery_op_unref); if (!client->discovery_req) { discovery_op_free(op); return false; } done: client->in_init = true; return true; } struct value_data { uint16_t handle; uint16_t len; const void *data; }; static void disable_ccc_callback(bool success, uint8_t att_ecode, void *user_data) { struct notify_data *notify_data = user_data; struct notify_data *next_data; assert(notify_data->chrc->ccc_write_id); notify_data->chrc->ccc_write_id = 0; /* This is a best effort procedure, so ignore errors and process any * queued requests. */ while (1) { next_data = queue_pop_head(notify_data->chrc->reg_notify_queue); if (!next_data || notify_data_write_ccc(next_data, true, enable_ccc_callback)) return; } } static void complete_unregister_notify(void *data) { struct notify_data *notify_data = data; /* * If a procedure to enable the CCC is still pending, then cancel it and * return. */ if (notify_data->att_id) { bt_att_cancel(notify_data->client->att, notify_data->att_id); notify_data->att_id = 0; goto done; } if (__sync_sub_and_fetch(¬ify_data->chrc->notify_count, 1) || !notify_data->chrc->ccc_handle) goto done; notify_data_write_ccc(notify_data, false, disable_ccc_callback); done: notify_data_unref(notify_data); } static void notify_handler(void *data, void *user_data) { struct notify_data *notify_data = data; struct value_data *value_data = user_data; if (notify_data->chrc->value_handle != value_data->handle) return; /* * Even if the notify data has a pending ATT request to write to the * CCC, there is really no reason not to notify the handlers. */ if (notify_data->notify) notify_data->notify(value_data->handle, value_data->data, value_data->len, notify_data->user_data); } static void notify_cb(struct bt_att_chan *chan, uint16_t mtu, uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_client *client = user_data; struct value_data data; bt_gatt_client_ref(client); if (queue_isempty(client->notify_list)) goto done; memset(&data, 0, sizeof(data)); if (opcode == BT_ATT_OP_HANDLE_NFY_MULT) { while (length >= 4) { data.handle = get_le16(pdu); length -= 2; pdu += 2; data.len = get_le16(pdu); length -= 2; pdu += 2; if (data.len > length) data.len = length; data.data = pdu; queue_foreach(client->notify_list, notify_handler, &data); length -= data.len; pdu += data.len; } } else { data.handle = get_le16(pdu); length -= 2; pdu += 2; data.len = length; data.data = pdu; queue_foreach(client->notify_list, notify_handler, &data); } done: if (opcode == BT_ATT_OP_HANDLE_IND && !client->parent) bt_att_chan_send(chan, BT_ATT_OP_HANDLE_CONF, NULL, 0, NULL, NULL, NULL); bt_gatt_client_unref(client); } static void bt_gatt_client_free(struct bt_gatt_client *client) { bt_gatt_client_cancel_all(client); queue_destroy(client->notify_chrcs, notify_chrc_free); queue_destroy(client->notify_list, notify_data_cleanup); queue_destroy(client->ready_cbs, ready_destroy); queue_destroy(client->idle_cbs, idle_destroy); if (client->debug_destroy) client->debug_destroy(client->debug_data); if (client->att) { bt_att_unregister_disconnect(client->att, client->disc_id); bt_att_unregister(client->att, client->nfy_id); bt_att_unregister(client->att, client->nfy_mult_id); bt_att_unregister(client->att, client->ind_id); bt_att_unref(client->att); } gatt_db_unref(client->db); queue_destroy(client->clones, NULL); queue_destroy(client->svc_chngd_queue, free); queue_destroy(client->long_write_queue, request_unref); queue_destroy(client->pending_requests, request_unref); if (client->parent) { queue_remove(client->parent->clones, client); bt_gatt_client_unref(client->parent); } free(client); } static void att_disconnect_cb(int err, void *user_data) { struct bt_gatt_client *client = user_data; bool in_init = client->in_init; client->disc_id = 0; bt_att_unref(client->att); client->att = NULL; client->in_init = false; client->ready = false; if (in_init) notify_client_ready(client, false, 0); } static bool is_out_of_range(struct bt_gatt_client *client, uint16_t handle) { uint16_t start_handle, end_handle; if (!handle) return false; start_handle = bt_gatt_req_get_start_handle(client->discovery_req); end_handle = bt_gatt_req_get_end_handle(client->discovery_req); if (start_handle != 0 && end_handle != 0 && (handle < start_handle || handle > end_handle)) return true; return false; } static void db_hash_check_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data) { struct bt_gatt_client *client = user_data; struct gatt_db_attribute *hash_attr = NULL; const uint8_t *local_hash = NULL; const uint8_t *remote_hash; uint16_t length, handle; struct bt_gatt_iter iter; bt_uuid_t uuid; unsigned int att_id = client->pending_retry_att_id; uint16_t pending_error_handle = client->pending_error_handle; client->pending_retry_att_id = 0; client->pending_error_handle = 0; /* If a Service Changed indication is received at this stage, the * pending request may be retried once we have verified that the * affected attribute handle is not within the range impacted by * the service change. */ if (client->in_svc_chngd) { if (is_out_of_range(client, pending_error_handle)) { DBG(client, "Error handle not effected, resending"); bt_att_resend(client->att, att_id, 0, NULL, 0, NULL, NULL, NULL); } else { DBG(client, "Error handle is in range of svc chngd"); bt_att_cancel(client->att, att_id); } return; } if (!att_id) { DBG(client, "No pending retry operation"); return; } if (!success) { DBG(client, "Failed to read remote DB Hash, triggering full discovery"); goto trigger_discovery; } /* Extract hash value from result */ if (!result || !bt_gatt_iter_init(&iter, result)) goto trigger_discovery; if (!bt_gatt_iter_next_read_by_type(&iter, &handle, &length, &remote_hash)) goto trigger_discovery; if (length != 16) { DBG(client, "Invalid DB Hash length: %u", length); goto trigger_discovery; } /* Get local hash from database */ bt_uuid16_create(&uuid, GATT_CHARAC_DB_HASH); gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, get_first_attribute, &hash_attr); if (hash_attr) { gatt_db_attribute_read(hash_attr, 0, BT_ATT_OP_READ_REQ, NULL, db_hash_read_value_cb, &local_hash); } /* Compare hashes */ if (local_hash && !memcmp(local_hash, remote_hash, 16)) { /* Hashes match - safe to retry */ DBG(client, "DB Hash matches, approving retry"); bt_att_resend(client->att, att_id, 0, NULL, 0, NULL, NULL, NULL); return; } /* Hashes differ - need service discovery */ DBG(client, "DB Hash differs, proceed with out of sync recovery"); trigger_discovery: bt_att_cancel(client->att, att_id); if (!client->in_svc_chngd) process_service_changed(client, 0x0001, 0xffff); } static void gatt_client_db_sync_cb(const struct bt_att_pdu_error_rsp *error, const void *req_pdu, uint16_t req_len, unsigned int att_id, void *user_data) { struct bt_gatt_client *client = user_data; bt_uuid_t uuid; uint16_t error_handle; assert(client); /* Only handle DB_OUT_OF_SYNC errors */ if (error->ecode != BT_ATT_ERROR_DB_OUT_OF_SYNC) return; error_handle = get_le16(&error->handle); client->pending_error_handle = error_handle; /* If a Service Changed indication is received at this stage, the * pending request may be retried once we have verified that the * affected attribute handle is not within the range impacted by * the service change. */ if (client->in_svc_chngd) { if (is_out_of_range(client, error_handle)) bt_att_resend(client->att, att_id, 0, NULL, 0, NULL, NULL, NULL); else bt_att_cancel(client->att, att_id); return; } /* Store the att_id for later use */ client->pending_retry_att_id = att_id; /* Read remote DB Hash to compare */ bt_uuid16_create(&uuid, GATT_CHARAC_DB_HASH); if (!bt_gatt_read_by_type(client->att, 0x0001, 0xffff, &uuid, db_hash_check_cb, client, NULL)) { DBG(client, "Failed to read DB Hash, aborting out of sync " "recovery"); client->pending_retry_att_id = 0; client->pending_error_handle = 0; bt_att_cancel(client->att, att_id); } } static struct bt_gatt_client *gatt_client_new(struct gatt_db *db, struct bt_att *att, uint8_t features) { struct bt_gatt_client *client; client = new0(struct bt_gatt_client, 1); client->disc_id = bt_att_register_disconnect(att, att_disconnect_cb, client, NULL); if (!client->disc_id) goto fail; client->clones = queue_new(); client->ready_cbs = queue_new(); client->idle_cbs = queue_new(); client->long_write_queue = queue_new(); client->svc_chngd_queue = queue_new(); client->notify_list = queue_new(); client->notify_chrcs = queue_new(); client->pending_requests = queue_new(); client->nfy_id = bt_att_register(att, BT_ATT_OP_HANDLE_NFY, notify_cb, client, NULL); if (!client->nfy_id) goto fail; client->nfy_mult_id = bt_att_register(att, BT_ATT_OP_HANDLE_NFY_MULT, notify_cb, client, NULL); if (!client->nfy_mult_id) goto fail; client->ind_id = bt_att_register(att, BT_ATT_OP_HANDLE_IND, notify_cb, client, NULL); if (!client->ind_id) goto fail; client->att = bt_att_ref(att); client->db = gatt_db_ref(db); client->features = features; /* Register DB sync callback for DB out of sync handling */ bt_att_set_db_sync_cb(att, gatt_client_db_sync_cb, client, NULL); return client; fail: bt_gatt_client_free(client); return NULL; } struct bt_gatt_client *bt_gatt_client_new(struct gatt_db *db, struct bt_att *att, uint16_t mtu, uint8_t features) { struct bt_gatt_client *client; if (!att || !db) return NULL; client = gatt_client_new(db, att, features); if (!client) return NULL; if (!gatt_client_init(client, mtu)) { bt_gatt_client_free(client); return NULL; } return bt_gatt_client_ref(client); } struct bt_gatt_client *bt_gatt_client_clone(struct bt_gatt_client *client) { struct bt_gatt_client *clone; if (!client) return NULL; clone = gatt_client_new(client->db, client->att, client->features); if (!clone) return NULL; queue_push_tail(client->clones, clone); /* * Reference the parent since the clones depend on it to propagate * service changed and ready callbacks. */ clone->parent = bt_gatt_client_ref(client); clone->ready = client->ready; return bt_gatt_client_ref(clone); } struct bt_gatt_client *bt_gatt_client_ref(struct bt_gatt_client *client) { if (!client) return NULL; __sync_fetch_and_add(&client->ref_count, 1); return client; } void bt_gatt_client_unref(struct bt_gatt_client *client) { if (!client) return; if (__sync_sub_and_fetch(&client->ref_count, 1)) return; bt_gatt_client_free(client); } bool bt_gatt_client_is_ready(struct bt_gatt_client *client) { return (client && client->ready); } unsigned int bt_gatt_client_ready_register(struct bt_gatt_client *client, bt_gatt_client_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct ready_cb *ready; if (!client) return 0; ready = new0(struct ready_cb, 1); ready->callback = callback; ready->destroy = destroy; ready->data = user_data; queue_push_tail(client->ready_cbs, ready); return PTR_TO_UINT(ready); } bool bt_gatt_client_ready_unregister(struct bt_gatt_client *client, unsigned int id) { struct ready_cb *ready = UINT_TO_PTR(id); if (queue_remove(client->ready_cbs, ready)) { ready_destroy(ready); return true; } return false; } bool bt_gatt_client_set_service_changed(struct bt_gatt_client *client, bt_gatt_client_service_changed_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { if (!client) return false; if (client->svc_chngd_destroy) client->svc_chngd_destroy(client->svc_chngd_data); client->svc_chngd_callback = callback; client->svc_chngd_destroy = destroy; client->svc_chngd_data = user_data; return true; } bool bt_gatt_client_set_debug(struct bt_gatt_client *client, bt_gatt_client_debug_func_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { if (!client) return false; if (client->debug_destroy) client->debug_destroy(client->debug_data); client->debug_callback = callback; client->debug_destroy = destroy; client->debug_data = user_data; return true; } uint16_t bt_gatt_client_get_mtu(struct bt_gatt_client *client) { if (!client || !client->att) return 0; return bt_att_get_mtu(client->att); } struct bt_att *bt_gatt_client_get_att(struct bt_gatt_client *client) { if (!client) return NULL; return client->att; } struct gatt_db *bt_gatt_client_get_db(struct bt_gatt_client *client) { if (!client || !client->db) return NULL; return client->db; } uint8_t bt_gatt_client_get_features(struct bt_gatt_client *client) { if (!client) return 0; if (client->parent) return client->parent->features; return client->features; } static bool match_req_id(const void *a, const void *b) { const struct request *req = a; unsigned int id = PTR_TO_UINT(b); return req->id == id; } static void cancel_long_write_cb(uint8_t opcode, const void *pdu, uint16_t len, void *user_data) { struct bt_gatt_client *client = user_data; if (queue_isempty(client->long_write_queue)) client->in_long_write = false; } static bool cancel_long_write_req(struct bt_gatt_client *client, struct request *req) { uint8_t pdu = 0x00; /* * att_id == 0 means that request has been queued and no prepare write * has been sent so far.Let's just remove if from the queue. * Otherwise execute write needs to be send. */ if (!req->att_id) return queue_remove(client->long_write_queue, req); return !!bt_att_send(client->att, BT_ATT_OP_EXEC_WRITE_REQ, &pdu, sizeof(pdu), cancel_long_write_cb, client, NULL); } static void cancel_prep_write_cb(uint8_t opcode, const void *pdu, uint16_t len, void *user_data) { struct request *req = user_data; struct bt_gatt_client *client = req->client; client->reliable_write_session_id = 0; } static bool cancel_prep_write_session(struct bt_gatt_client *client, struct request *req) { uint8_t pdu = 0x00; return !!bt_att_send(client->att, BT_ATT_OP_EXEC_WRITE_REQ, &pdu, sizeof(pdu), cancel_prep_write_cb, req, request_unref); } static bool cancel_request(struct request *req) { req->removed = true; if (req->long_write) return cancel_long_write_req(req->client, req); if (req->prep_write) return cancel_prep_write_session(req->client, req); return bt_att_cancel(req->client->att, req->att_id); } bool bt_gatt_client_cancel(struct bt_gatt_client *client, unsigned int id) { struct request *req; if (!client || !id || !client->att) return false; req = queue_remove_if(client->pending_requests, match_req_id, UINT_TO_PTR(id)); if (!req) return false; return cancel_request(req); } static void cancel_pending(void *data) { cancel_request(data); } bool bt_gatt_client_cancel_all(struct bt_gatt_client *client) { if (!client || !client->att) return false; queue_remove_all(client->pending_requests, NULL, NULL, cancel_pending); if (client->discovery_req) { bt_gatt_request_cancel(client->discovery_req); bt_gatt_request_unref(client->discovery_req); client->discovery_req = NULL; } if (client->mtu_req_id) bt_att_cancel(client->att, client->mtu_req_id); return true; } struct read_op { bt_gatt_client_read_callback_t callback; void *user_data; bt_gatt_client_destroy_func_t destroy; }; static void destroy_read_op(void *data) { struct read_op *op = data; if (op->destroy) op->destroy(op->user_data); free(op); } unsigned int bt_gatt_client_read_value(struct bt_gatt_client *client, uint16_t value_handle, bt_gatt_client_read_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { return bt_gatt_client_read_long_value(client, value_handle, 0, callback, user_data, destroy); } static void read_multiple_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct request *req = user_data; struct read_op *op = req->data; uint8_t att_ecode; bool success; if ((opcode != BT_ATT_OP_READ_MULT_RSP && opcode != BT_ATT_OP_READ_MULT_VL_RSP) || (!pdu && length)) { success = false; if (opcode == BT_ATT_OP_ERROR_RSP) att_ecode = process_error(pdu, length); else att_ecode = 0; pdu = NULL; length = 0; } else { success = true; att_ecode = 0; } if (!op->callback) return; if (opcode == BT_ATT_OP_READ_MULT_RSP || att_ecode) { op->callback(success, att_ecode, pdu, length, op->user_data); return; } if (length < 2) { op->callback(success, att_ecode, pdu, length, op->user_data); return; } /* Parse response */ while (length >= 2) { uint16_t len; len = get_le16(pdu); length -= 2; pdu += 2; /* The Length Value Tuple List may be truncated within the * first two octets of a tuple due to the size limits of the * current ATT_MTU. */ if (len > length) length = len; op->callback(success, att_ecode, pdu, len, op->user_data); } } unsigned int bt_gatt_client_read_multiple(struct bt_gatt_client *client, uint16_t *handles, uint8_t num_handles, bt_gatt_client_read_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { uint8_t *pdu = newa(uint8_t, num_handles * 2); struct request *req; struct read_op *op; uint8_t opcode; int i; if (!client) return 0; if (num_handles < 2) return 0; if (num_handles * 2 > bt_att_get_mtu(client->att) - 1) return 0; op = new0(struct read_op, 1); req = request_create(client); if (!req) { free(op); return 0; } op->callback = callback; op->user_data = user_data; op->destroy = destroy; req->data = op; req->destroy = destroy_read_op; for (i = 0; i < num_handles; i++) put_le16(handles[i], pdu + (2 * i)); opcode = bt_gatt_client_get_features(client) & BT_GATT_CHRC_CLI_FEAT_EATT ? BT_ATT_OP_READ_MULT_VL_REQ : BT_ATT_OP_READ_MULT_REQ; req->att_id = bt_att_send(client->att, opcode, pdu, num_handles * 2, read_multiple_cb, req, request_unref); if (!req->att_id) { op->destroy = NULL; request_unref(req); return 0; } return req->id; } struct read_long_op { struct bt_gatt_client *client; int ref_count; uint16_t value_handle; uint16_t offset; struct iovec iov; bt_gatt_client_read_callback_t callback; void *user_data; bt_gatt_client_destroy_func_t destroy; }; static void destroy_read_long_op(void *data) { struct read_long_op *op = data; if (op->destroy) op->destroy(op->user_data); free(op->iov.iov_base); free(op); } static bool append_chunk(struct read_long_op *op, const uint8_t *data, uint16_t len) { void *buf; /* Truncate if the data would exceed maximum length */ if (op->offset + len > BT_ATT_MAX_VALUE_LEN) len = BT_ATT_MAX_VALUE_LEN - op->offset; buf = realloc(op->iov.iov_base, op->iov.iov_len + len); if (!buf) return false; op->iov.iov_base = buf; memcpy(op->iov.iov_base + op->iov.iov_len, data, len); op->iov.iov_len += len; op->offset += len; return true; } static void read_long_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct request *req = user_data; struct read_long_op *op = req->data; bool success; uint8_t att_ecode = 0; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); goto done; } if ((!op->offset && opcode != BT_ATT_OP_READ_RSP) || (op->offset && opcode != BT_ATT_OP_READ_BLOB_RSP) || (!pdu && length)) { success = false; goto done; } if (!length) goto success; if (!append_chunk(op, pdu, length)) { success = false; goto done; } if (op->offset >= BT_ATT_MAX_VALUE_LEN) goto success; if (length >= bt_att_get_mtu(op->client->att) - 1) { uint8_t pdu[4]; int err; put_le16(op->value_handle, pdu); put_le16(op->offset, pdu + 2); err = bt_att_resend(op->client->att, req->att_id, BT_ATT_OP_READ_BLOB_REQ, pdu, sizeof(pdu), read_long_cb, request_ref(req), request_unref); if (!err) return; request_unref(req); success = false; goto done; } success: success = true; done: if (op->callback) op->callback(success, att_ecode, op->iov.iov_base, op->iov.iov_len, op->user_data); } unsigned int bt_gatt_client_read_long_value(struct bt_gatt_client *client, uint16_t value_handle, uint16_t offset, bt_gatt_client_read_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct request *req; struct read_long_op *op; uint8_t att_op; uint8_t pdu[4]; uint16_t pdu_len; if (!client) return 0; op = new0(struct read_long_op, 1); req = request_create(client); if (!req) { free(op); return 0; } op->client = client; op->value_handle = value_handle; op->offset = offset; op->callback = callback; op->user_data = user_data; op->destroy = destroy; req->data = op; req->destroy = destroy_read_long_op; put_le16(value_handle, pdu); pdu_len = sizeof(value_handle); /* * Core v4.2, part F, section 1.3.4.4.5: * If the attribute value has a fixed length that is less than or equal * to (ATT_MTU - 3) octets in length, then an Error Response can be sent * with the error code «Attribute Not Long». * * To remove need for caller to handle "Attribute Not Long" error when * reading characteristics with short values, use Read Request for * reading first part of characteristics value instead of Read Blob * Request. Both are allowed in this case. */ if (op->offset) { att_op = BT_ATT_OP_READ_BLOB_REQ; pdu_len += sizeof(op->offset); put_le16(op->offset, pdu + 2); } else { att_op = BT_ATT_OP_READ_REQ; } req->att_id = bt_att_send(client->att, att_op, pdu, pdu_len, read_long_cb, req, request_unref); if (!req->att_id) { op->destroy = NULL; request_unref(req); return 0; } return req->id; } unsigned int bt_gatt_client_write_without_response( struct bt_gatt_client *client, uint16_t value_handle, bool signed_write, const uint8_t *value, uint16_t length) { uint8_t *pdu = newa(uint8_t, 2 + length); struct request *req; int security; uint8_t op; if (!client) return 0; req = request_create(client); if (!req) return 0; /* Only use signed write if unencrypted */ if (signed_write) { security = bt_att_get_security(client->att, NULL); op = security > BT_SECURITY_LOW ? BT_ATT_OP_WRITE_CMD : BT_ATT_OP_SIGNED_WRITE_CMD; } else op = BT_ATT_OP_WRITE_CMD; put_le16(value_handle, pdu); memcpy(pdu + 2, value, length); req->att_id = bt_att_send(client->att, op, pdu, 2 + length, NULL, req, request_unref); if (!req->att_id) { request_unref(req); return 0; } return req->id; } struct write_op { struct bt_gatt_client *client; bt_gatt_client_callback_t callback; void *user_data; bt_gatt_destroy_func_t destroy; }; static void destroy_write_op(void *data) { struct write_op *op = data; if (op->destroy) op->destroy(op->user_data); free(op); } static void write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct request *req = user_data; struct write_op *op = req->data; bool success = true; uint8_t att_ecode = 0; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); goto done; } if (opcode != BT_ATT_OP_WRITE_RSP || pdu || length) success = false; done: if (op->callback) op->callback(success, att_ecode, op->user_data); } unsigned int bt_gatt_client_write_value(struct bt_gatt_client *client, uint16_t value_handle, const uint8_t *value, uint16_t length, bt_gatt_client_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct request *req; struct write_op *op; uint8_t *pdu = newa(uint8_t, 2 + length); if (!client) return 0; op = new0(struct write_op, 1); req = request_create(client); if (!req) { free(op); return 0; } op->callback = callback; op->user_data = user_data; op->destroy = destroy; req->data = op; req->destroy = destroy_write_op; put_le16(value_handle, pdu); memcpy(pdu + 2, value, length); req->att_id = bt_att_send(client->att, BT_ATT_OP_WRITE_REQ, pdu, 2 + length, write_cb, req, request_unref); if (!req->att_id) { op->destroy = NULL; request_unref(req); return 0; } return req->id; } struct long_write_op { struct bt_gatt_client *client; bool reliable; bool success; uint8_t att_ecode; bool reliable_error; uint16_t value_handle; uint8_t *value; uint16_t length; uint16_t offset; uint16_t index; uint16_t cur_length; bt_gatt_client_write_long_callback_t callback; void *user_data; bt_gatt_client_destroy_func_t destroy; }; static void long_write_op_free(void *data) { struct long_write_op *op = data; if (op->destroy) op->destroy(op->user_data); free(op->value); free(op); } static void prepare_write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data); static void complete_write_long_op(struct request *req, bool success, uint8_t att_ecode, bool reliable_error); static void handle_next_prep_write(struct request *req) { struct long_write_op *op = req->data; bool success = true; uint8_t *pdu; int err; pdu = malloc(op->cur_length + 4); if (!pdu) { success = false; goto done; } put_le16(op->value_handle, pdu); put_le16(op->offset + op->index, pdu + 2); memcpy(pdu + 4, op->value + op->index, op->cur_length); err = bt_att_resend(op->client->att, req->att_id, BT_ATT_OP_PREP_WRITE_REQ, pdu, op->cur_length + 4, prepare_write_cb, request_ref(req), request_unref); if (err) { request_unref(req); success = false; } free(pdu); /* If so far successful, then the operation should continue. * Otherwise, there was an error and the procedure should be * completed. */ if (success) return; done: complete_write_long_op(req, success, 0, false); } static void start_next_long_write(struct bt_gatt_client *client) { struct request *req; if (queue_isempty(client->long_write_queue)) { client->in_long_write = false; return; } req = queue_pop_head(client->long_write_queue); if (!req) return; handle_next_prep_write(req); /* * send_next_prep_write adds an extra ref. Unref here to clean up if * necessary, since we also added a ref before pushing to the queue. */ request_unref(req); } static void execute_write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct request *req = user_data; struct long_write_op *op = req->data; bool success = op->success; uint8_t att_ecode = op->att_ecode; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); } else if (opcode != BT_ATT_OP_EXEC_WRITE_RSP || pdu || length) success = false; bt_gatt_client_ref(op->client); if (op->callback) op->callback(success, op->reliable_error, att_ecode, op->user_data); start_next_long_write(op->client); bt_gatt_client_unref(op->client); } static void complete_write_long_op(struct request *req, bool success, uint8_t att_ecode, bool reliable_error) { struct long_write_op *op = req->data; uint8_t pdu; int err; op->success = success; op->att_ecode = att_ecode; op->reliable_error = reliable_error; if (success) pdu = 0x01; /* Write */ else pdu = 0x00; /* Cancel */ err = bt_att_resend(op->client->att, req->att_id, BT_ATT_OP_EXEC_WRITE_REQ, &pdu, sizeof(pdu), execute_write_cb, request_ref(req), request_unref); if (!err) return; request_unref(req); success = false; bt_gatt_client_ref(op->client); if (op->callback) op->callback(success, reliable_error, att_ecode, op->user_data); start_next_long_write(op->client); bt_gatt_client_unref(op->client); } static void prepare_write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct request *req = user_data; struct long_write_op *op = req->data; bool success = true; bool reliable_error = false; uint8_t att_ecode = 0; uint16_t next_index; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); goto done; } if (opcode != BT_ATT_OP_PREP_WRITE_RSP) { success = false; goto done; } if (op->reliable) { if (!pdu || length != (op->cur_length + 4)) { success = false; reliable_error = true; goto done; } if (get_le16(pdu) != op->value_handle || get_le16(pdu + 2) != (op->offset + op->index)) { success = false; reliable_error = true; goto done; } if (memcmp(pdu + 4, op->value + op->index, op->cur_length)) { success = false; reliable_error = true; goto done; } } next_index = op->index + op->cur_length; if (next_index == op->length) { /* All bytes written */ goto done; } /* If the last written length was greater than or equal to what can fit * inside a PDU, then there is more data to send. */ if (op->cur_length >= bt_att_get_mtu(op->client->att) - 5) { op->index = next_index; op->cur_length = MIN(op->length - op->index, bt_att_get_mtu(op->client->att) - 5); handle_next_prep_write(req); return; } done: complete_write_long_op(req, success, att_ecode, reliable_error); } unsigned int bt_gatt_client_write_long_value(struct bt_gatt_client *client, bool reliable, uint16_t value_handle, uint16_t offset, const uint8_t *value, uint16_t length, bt_gatt_client_write_long_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct request *req; struct long_write_op *op; uint8_t *pdu; if (!client) return 0; if ((size_t)(length + offset) > UINT16_MAX) return 0; /* Don't allow writing a 0-length value using this procedure. The * upper-layer should use bt_gatt_write_value for that instead. */ if (!length || !value) return 0; op = new0(struct long_write_op, 1); op->value = malloc(length); if (!op->value) { free(op); return 0; } req = request_create(client); if (!req) { free(op->value); free(op); return 0; } memcpy(op->value, value, length); op->client = client; op->reliable = reliable; op->value_handle = value_handle; op->length = length; op->offset = offset; op->cur_length = MIN(length, bt_att_get_mtu(client->att) - 5); op->callback = callback; op->user_data = user_data; op->destroy = destroy; req->data = op; req->destroy = long_write_op_free; req->long_write = true; if (client->in_long_write || client->reliable_write_session_id > 0) { queue_push_tail(client->long_write_queue, req); return req->id; } pdu = malloc(op->cur_length + 4); if (!pdu) { free(op->value); free(op); return 0; } put_le16(value_handle, pdu); put_le16(offset, pdu + 2); memcpy(pdu + 4, op->value, op->cur_length); req->att_id = bt_att_send(client->att, BT_ATT_OP_PREP_WRITE_REQ, pdu, op->cur_length + 4, prepare_write_cb, req, request_unref); free(pdu); if (!req->att_id) { op->destroy = NULL; request_unref(req); return 0; } client->in_long_write = true; return req->id; } struct prep_write_op { bt_gatt_client_write_long_callback_t callback; void *user_data; bt_gatt_destroy_func_t destroy; uint8_t *pdu; uint16_t pdu_len; }; static void destroy_prep_write_op(void *data) { struct prep_write_op *op = data; if (op->destroy) op->destroy(op->user_data); free(op->pdu); free(op); } static void prep_write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct request *req = user_data; struct prep_write_op *op = req->data; bool success; uint8_t att_ecode; bool reliable_error; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; reliable_error = false; att_ecode = process_error(pdu, length); goto done; } if (opcode != BT_ATT_OP_PREP_WRITE_RSP) { success = false; reliable_error = false; att_ecode = 0; goto done; } if (!pdu || length != op->pdu_len || memcmp(pdu, op->pdu, op->pdu_len)) { success = false; reliable_error = true; att_ecode = 0; goto done; } success = true; reliable_error = false; att_ecode = 0; done: if (op->callback) op->callback(success, reliable_error, att_ecode, op->user_data); } static struct request *get_reliable_request(struct bt_gatt_client *client, unsigned int id) { struct request *req; struct prep_write_op *op; op = new0(struct prep_write_op, 1); /* Following prepare writes */ if (id != 0) req = queue_find(client->pending_requests, match_req_id, UINT_TO_PTR(id)); else req = request_create(client); if (!req) { free(op); return NULL; } req->data = op; return req; } unsigned int bt_gatt_client_prepare_write(struct bt_gatt_client *client, unsigned int id, uint16_t value_handle, uint16_t offset, const uint8_t *value, uint16_t length, bt_gatt_client_write_long_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct request *req; struct prep_write_op *op; uint8_t *pdu = newa(uint8_t, 4 + length); if (!client) return 0; if (client->in_long_write) return 0; /* * Make sure that client who owns reliable session continues with * prepare writes or this is brand new reliable session (id == 0) */ if (id != client->reliable_write_session_id) { DBG(client, "There is other reliable write session ongoing %u", client->reliable_write_session_id); return 0; } req = get_reliable_request(client, id); if (!req) return 0; op = (struct prep_write_op *)req->data; op->callback = callback; op->user_data = user_data; op->destroy = destroy; req->destroy = destroy_prep_write_op; req->prep_write = true; put_le16(value_handle, pdu); put_le16(offset, pdu + 2); memcpy(pdu + 4, value, length); /* * Before sending command we need to remember pdu as we need to validate * it in the response. Store handle, offset and value. Therefore * increase length by 4 (handle + offset) as we need it in couple places * below */ length += 4; op->pdu = malloc(length); if (!op->pdu) { op->destroy = NULL; request_unref(req); return 0; } memcpy(op->pdu, pdu, length); op->pdu_len = length; /* * Now we are ready to send command * Note that request_unref will be done on write execute */ req->att_id = bt_att_send(client->att, BT_ATT_OP_PREP_WRITE_REQ, pdu, length, prep_write_cb, req, NULL); if (!req->att_id) { op->destroy = NULL; request_unref(req); return 0; } /* * Store first request id for prepare write and treat it as a session id * valid until write execute is done */ if (client->reliable_write_session_id == 0) client->reliable_write_session_id = req->id; return client->reliable_write_session_id; } static void exec_write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct request *req = user_data; struct write_op *op = req->data; bool success; uint8_t att_ecode; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); goto done; } if (opcode != BT_ATT_OP_EXEC_WRITE_RSP || pdu || length) { success = false; att_ecode = 0; goto done; } success = true; att_ecode = 0; done: if (op->callback) op->callback(success, att_ecode, op->user_data); op->client->reliable_write_session_id = 0; start_next_long_write(op->client); } unsigned int bt_gatt_client_write_execute(struct bt_gatt_client *client, unsigned int id, bt_gatt_client_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct request *req; struct write_op *op; uint8_t pdu; if (!client) return 0; if (client->in_long_write) return 0; if (client->reliable_write_session_id != id) return 0; op = new0(struct write_op, 1); req = queue_find(client->pending_requests, match_req_id, UINT_TO_PTR(id)); if (!req) { free(op); return 0; } op->client = client; op->callback = callback; op->user_data = user_data; op->destroy = destroy; pdu = 0x01; req->data = op; req->destroy = destroy_write_op; req->att_id = bt_att_send(client->att, BT_ATT_OP_EXEC_WRITE_REQ, &pdu, sizeof(pdu), exec_write_cb, req, request_unref); if (!req->att_id) { op->destroy = NULL; request_unref(req); return 0; } return id; } unsigned int bt_gatt_client_register_notify(struct bt_gatt_client *client, uint16_t chrc_value_handle, bt_gatt_client_register_callback_t callback, bt_gatt_client_notify_callback_t notify, void *user_data, bt_gatt_client_destroy_func_t destroy) { if (!client || !client->db || !chrc_value_handle || (!callback && !notify)) return 0; if (client->in_svc_chngd) return 0; return register_notify(client, chrc_value_handle, callback, notify, user_data, destroy); } bool bt_gatt_client_unregister_notify(struct bt_gatt_client *client, unsigned int id) { struct notify_data *notify_data; if (!client || !id) return false; notify_data = queue_remove_if(client->notify_list, match_notify_data_id, UINT_TO_PTR(id)); if (!notify_data) return false; /* Remove data if it has been queued */ queue_remove(notify_data->chrc->reg_notify_queue, notify_data); /* Reset callbacks */ notify_data->callback = NULL; notify_data->notify = NULL; complete_unregister_notify(notify_data); return true; } bool bt_gatt_client_set_security(struct bt_gatt_client *client, int level) { if (!client) return false; return bt_att_set_security(client->att, level); } int bt_gatt_client_get_security(struct bt_gatt_client *client) { if (!client) return -1; return bt_att_get_security(client->att, NULL); } unsigned int bt_gatt_client_idle_register(struct bt_gatt_client *client, bt_gatt_client_idle_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct idle_cb *idle; if (!client) return 0; idle = new0(struct idle_cb, 1); idle->callback = callback; idle->destroy = destroy; idle->data = user_data; queue_push_tail(client->idle_cbs, idle); return PTR_TO_UINT(idle); } bool bt_gatt_client_idle_unregister(struct bt_gatt_client *client, unsigned int id) { struct idle_cb *idle = UINT_TO_PTR(id); if (!client || !id) return false; if (queue_remove(client->idle_cbs, idle)) { idle_destroy(idle); return true; } return false; } bool bt_gatt_client_set_retry(struct bt_gatt_client *client, unsigned int id, bool retry) { struct request *req; if (!client || !id) return false; req = queue_find(client->pending_requests, match_req_id, UINT_TO_PTR(id)); if (!req) return false; bt_att_set_retry(client->att, req->att_id, retry); return true; } bluez-5.87/src/shared/PaxHeaders/ccp.c0000644000000000000000000000005015063045254014625 xustar0020 atime=1783092963 20 ctime=1783094100 bluez-5.87/src/shared/ccp.c0000644000000000000000000007066715063045254014326 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. * */ #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "bluetooth/hci.h" #include "src/shared/queue.h" #include "src/shared/util.h" #include "src/shared/timeout.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-server.h" #include "src/shared/gatt-client.h" #include "src/shared/ccp.h" #define DBG(_ccp, fmt, arg...) \ ccp_debug(_ccp, "%s:%s() " fmt, __FILE__, __func__, ## arg) struct bt_ccp_db { struct gatt_db *db; struct bt_ccs *ccs; }; struct bt_ccp_pending { unsigned int id; struct bt_ccp *ccp; bt_gatt_client_read_callback_t func; void *user_data; }; struct event_callback { const struct bt_ccp_event_callback *cbs; void *user_data; }; struct bt_ccp { int ref_count; struct bt_gatt_client *client; struct bt_ccp_db *ldb; struct bt_ccp_db *rdb; unsigned int bearer_name_id; unsigned int bearer_uci_id; unsigned int bearer_technology_id; unsigned int bearer_uri_schemes_list_id; unsigned int signal_strength_id; unsigned int signal_reporting_intrvl_id; unsigned int current_call_list_id; unsigned int ccid_id; unsigned int status_flag_id; unsigned int target_bearer_uri_id; unsigned int call_state_id; unsigned int call_control_pt_id; unsigned int call_control_opt_opcode_id; unsigned int termination_reason_id; unsigned int incoming_call_id; unsigned int friendly_name_id; struct event_callback *cb; struct queue *pending; bt_ccp_debug_func_t debug_func; bt_ccp_destroy_func_t debug_destroy; void *debug_data; void *user_data; }; struct bt_ccs { struct bt_ccp_db *mdb; struct gatt_db_attribute *service; struct gatt_db_attribute *bearer_name; struct gatt_db_attribute *bearer_name_ccc; struct gatt_db_attribute *bearer_uci; struct gatt_db_attribute *bearer_technology; struct gatt_db_attribute *bearer_technology_ccc; struct gatt_db_attribute *bearer_uri_schemes_list; struct gatt_db_attribute *signal_strength; struct gatt_db_attribute *signal_strength_ccc; struct gatt_db_attribute *signal_reporting_intrvl; struct gatt_db_attribute *current_call_list; struct gatt_db_attribute *current_call_list_ccc; struct gatt_db_attribute *ccid; struct gatt_db_attribute *status_flag; struct gatt_db_attribute *status_flag_ccc; struct gatt_db_attribute *target_bearer_uri; struct gatt_db_attribute *call_state; struct gatt_db_attribute *call_state_ccc; struct gatt_db_attribute *call_ctrl_point; struct gatt_db_attribute *call_ctrl_point_ccc; struct gatt_db_attribute *call_ctrl_opt_opcode; struct gatt_db_attribute *termination_reason; struct gatt_db_attribute *termination_reason_ccc; struct gatt_db_attribute *incoming_call; struct gatt_db_attribute *incoming_call_ccc; struct gatt_db_attribute *friendly_name; struct gatt_db_attribute *friendly_name_ccc; }; static struct queue *ccp_db; static void ccp_debug(struct bt_ccp *ccp, const char *format, ...) { va_list ap; if (!ccp || !format || !ccp->debug_func) return; va_start(ap, format); util_debug_va(ccp->debug_func, ccp->debug_data, format, ap); va_end(ap); } static bool ccp_db_match(const void *data, const void *match_data) { const struct bt_ccp_db *mdb = data; const struct gatt_db *db = match_data; return (mdb->db == db); } static void ccp_db_free(void *data) { struct bt_ccp_db *bdb = data; if (!bdb) return; gatt_db_unref(bdb->db); free(bdb->ccs); free(bdb); } static void ccp_free(void *data) { struct bt_ccp *ccp = data; DBG(ccp, ""); bt_ccp_detach(ccp); ccp_db_free(ccp->rdb); queue_destroy(ccp->pending, NULL); free(ccp); } struct bt_ccp *bt_ccp_ref(struct bt_ccp *ccp) { if (!ccp) return NULL; __sync_fetch_and_add(&ccp->ref_count, 1); return ccp; } void bt_ccp_unref(struct bt_ccp *ccp) { if (!ccp) return; if (__sync_sub_and_fetch(&ccp->ref_count, 1)) return; ccp_free(ccp); } bool bt_ccp_set_user_data(struct bt_ccp *ccp, void *user_data) { if (!ccp) return false; ccp->user_data = user_data; return true; } void *bt_ccp_get_user_data(struct bt_ccp *ccp) { if (!ccp) return NULL; return ccp->user_data; } bool bt_ccp_set_debug(struct bt_ccp *ccp, bt_ccp_debug_func_t func, void *user_data, bt_ccp_destroy_func_t destroy) { if (!ccp) return false; if (ccp->debug_destroy) ccp->debug_destroy(ccp->debug_data); ccp->debug_func = func; ccp->debug_destroy = destroy; ccp->debug_data = user_data; return true; } static void ccs_call_state_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { int call_state = 0; struct iovec iov; iov.iov_base = &call_state; iov.iov_len = sizeof(int); gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, iov.iov_len); } static void ccs_call_state_write(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { gatt_db_attribute_write_result(attrib, id, BT_ATT_ERROR_INSUFFICIENT_RESOURCES); } static struct bt_ccs *ccs_new(struct gatt_db *db) { struct bt_ccs *ccs; bt_uuid_t uuid; if (!db) return NULL; ccs = new0(struct bt_ccs, 1); /* Populate DB with ccs attributes */ bt_uuid16_create(&uuid, GTBS_UUID); ccs->service = gatt_db_add_service(db, &uuid, true, 42); bt_uuid16_create(&uuid, BEARER_PROVIDER_NAME_CHRC_UUID); ccs->bearer_name = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, ccs_call_state_read, NULL, ccs); ccs->bearer_name_ccc = gatt_db_service_add_ccc(ccs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, BEARER_UCI_CHRC_UUID); ccs->bearer_uci = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, ccs_call_state_read, NULL, ccs); bt_uuid16_create(&uuid, BEARER_TECH_CHRC_UUID); ccs->bearer_technology = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, ccs_call_state_read, NULL, ccs); ccs->bearer_technology_ccc = gatt_db_service_add_ccc(ccs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, BEARER_URI_SCHEME_CHRC_UUID); ccs->bearer_uri_schemes_list = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, ccs_call_state_read, NULL, ccs); bt_uuid16_create(&uuid, BEARER_SIGNAL_STR_CHRC_UUID); ccs->signal_strength = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, ccs_call_state_read, NULL, ccs); ccs->signal_strength_ccc = gatt_db_service_add_ccc(ccs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, BEARER_SIGNAL_INTRVL_CHRC_UUID); ccs->signal_reporting_intrvl = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_WRITE | BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP, ccs_call_state_read, ccs_call_state_write, ccs); bt_uuid16_create(&uuid, CURR_CALL_LIST_CHRC_UUID); ccs->current_call_list = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, ccs_call_state_read, NULL, ccs); ccs->current_call_list_ccc = gatt_db_service_add_ccc(ccs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, BEARER_CCID_CHRC_UUID); ccs->ccid = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, ccs_call_state_read, NULL, ccs); bt_uuid16_create(&uuid, CALL_STATUS_FLAG_CHRC_UUID); ccs->status_flag = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, ccs_call_state_read, NULL, ccs); ccs->status_flag_ccc = gatt_db_service_add_ccc(ccs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, INCOM_CALL_TARGET_URI_CHRC_UUID); ccs->target_bearer_uri = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, ccs_call_state_read, NULL, ccs); bt_uuid16_create(&uuid, CALL_STATE_CHRC_UUID); ccs->call_ctrl_point = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, ccs_call_state_read, NULL, ccs); ccs->call_ctrl_point_ccc = gatt_db_service_add_ccc(ccs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, CALL_CTRL_POINT_CHRC_UUID); ccs->call_ctrl_opt_opcode = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_WRITE, BT_GATT_CHRC_PROP_WRITE | BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP | BT_GATT_CHRC_PROP_NOTIFY, NULL, ccs_call_state_write, ccs); bt_uuid16_create(&uuid, CALL_CTRL_POINT_OPT_OPCODE_CHRC_UUID); ccs->call_ctrl_opt_opcode = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, ccs_call_state_read, NULL, ccs); bt_uuid16_create(&uuid, TERMINATION_REASON_CHRC_UUID); ccs->termination_reason = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, ccs_call_state_read, NULL, ccs); bt_uuid16_create(&uuid, INCOMING_CALL_CHRC_UUID); ccs->incoming_call = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_NONE, BT_GATT_CHRC_PROP_NOTIFY, NULL, NULL, ccs); ccs->incoming_call_ccc = gatt_db_service_add_ccc(ccs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); bt_uuid16_create(&uuid, CALL_FRIENDLY_NAME_CHRC_UUID); ccs->friendly_name = gatt_db_service_add_characteristic(ccs->service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_NOTIFY, ccs_call_state_read, NULL, ccs); ccs->friendly_name_ccc = gatt_db_service_add_ccc(ccs->service, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); gatt_db_service_set_active(ccs->service, false); return ccs; } static struct bt_ccs *ccp_get_ccs(struct bt_ccp *ccp) { if (!ccp) return NULL; if (ccp->rdb->ccs) return ccp->rdb->ccs; ccp->rdb->ccs = new0(struct bt_ccs, 1); ccp->rdb->ccs->mdb = ccp->rdb; return ccp->rdb->ccs; } static void ccp_pending_destroy(void *data) { struct bt_ccp_pending *pending = data; struct bt_ccp *ccp = pending->ccp; queue_remove_if(ccp->pending, NULL, pending); } static void ccp_pending_complete(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_ccp_pending *pending = user_data; if (pending->func) pending->func(success, att_ecode, value, length, pending->user_data); } static void ccp_read_value(struct bt_ccp *ccp, uint16_t value_handle, bt_gatt_client_read_callback_t func, void *user_data) { struct bt_ccp_pending *pending; pending = new0(struct bt_ccp_pending, 1); pending->ccp = ccp; pending->func = func; pending->user_data = user_data; pending->id = bt_gatt_client_read_value(ccp->client, value_handle, ccp_pending_complete, pending, ccp_pending_destroy); if (!pending->id) { DBG(ccp, "Unable to send Read request"); free(pending); return; } queue_push_tail(ccp->pending, pending); } static void read_call_back(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_ccp *ccp = user_data; DBG(ccp, ""); if (!success) { DBG(ccp, "Unable to read call state: error 0x%02x", att_ecode); return; } } static void ccp_cb_register(uint16_t att_ecode, void *user_data) { struct bt_ccp *ccp = user_data; if (att_ecode) DBG(ccp, "ccp cb notification failed: 0x%04x", att_ecode); /* TODO: generic handler for non-mandatory CCP call backs */ } static void ccp_cb_notify(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { /* TODO: generic handler for non-mandatory CCP notifications */ } static void ccp_cb_status_flag_register(uint16_t att_ecode, void *user_data) { struct bt_ccp *ccp = user_data; if (att_ecode) DBG(ccp, "ccp cb notification failed: 0x%04x", att_ecode); } static void ccp_cb_status_flag_notify(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct bt_ccp *ccp = user_data; DBG(ccp, ""); if (!length) return; } static void ccp_cb_terminate_register(uint16_t att_ecode, void *user_data) { struct bt_ccp *ccp = user_data; if (att_ecode) DBG(ccp, "ccp cb notification failed: 0x%04x", att_ecode); } static void ccp_cb_terminate_notify(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct bt_ccp *ccp = user_data; DBG(ccp, ""); if (!length) return; /* TODO: update call state in Local context */ } static void ccp_cb_bearer_name_register(uint16_t att_ecode, void *user_data) { struct bt_ccp *ccp = user_data; DBG(ccp, ""); if (att_ecode) DBG(ccp, "ccp cb notification failed: 0x%04x", att_ecode); } static void ccp_cb_bearer_name_notify(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct bt_ccp *ccp = user_data; DBG(ccp, ""); if (!length) return; /* TODO: update call details in Local context */ } static void ccp_cb_call_list_register(uint16_t att_ecode, void *user_data) { struct bt_ccp *ccp = user_data; DBG(ccp, ""); if (att_ecode) DBG(ccp, "ccp cb notification failed: 0x%04x", att_ecode); } static void ccp_cb_call_list_notify(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct bt_ccp *ccp = user_data; DBG(ccp, ""); if (!length) return; /* TODO: update call list in Local context */ } static void ccp_cb_call_state_register(uint16_t att_ecode, void *user_data) { struct bt_ccp *ccp = user_data; DBG(ccp, ""); if (att_ecode) DBG(ccp, "ccp cb notification failed: 0x%04x", att_ecode); } static void ccp_cb_call_state_notify(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct bt_ccp *ccp = user_data; DBG(ccp, ""); if (!length) return; /* TODO: update call state in Local context */ } static void ccp_cb_incom_call_register(uint16_t att_ecode, void *user_data) { struct bt_ccp *ccp = user_data; DBG(ccp, ""); if (att_ecode) DBG(ccp, "ccp cb notification failed: 0x%04x", att_ecode); } static void ccp_cb_incom_call_notify(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct bt_ccp *ccp = user_data; DBG(ccp, ""); if (!length) return; /* TODO: Handle incoming call notofiation, Answer/reject etc */ } static void bt_ccp_incom_call_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->incoming_call, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->incoming_call_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_incom_call_register, ccp_cb_incom_call_notify, ccp, NULL); } static void bt_ccp_call_state_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->call_state, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->call_state_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_call_state_register, ccp_cb_call_state_notify, ccp, NULL); } static void bt_ccp_call_list_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->current_call_list, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->current_call_list_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_call_list_register, ccp_cb_call_list_notify, ccp, NULL); } static void bt_ccp_name_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->bearer_name, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->bearer_name_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_bearer_name_register, ccp_cb_bearer_name_notify, ccp, NULL); } static void bt_ccp_term_reason_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->termination_reason, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->termination_reason_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_terminate_register, ccp_cb_terminate_notify, ccp, NULL); } static void bt_ccp_status_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->status_flag, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->status_flag_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_status_flag_register, ccp_cb_status_flag_notify, ccp, NULL); } static void bt_ccp_uci_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->bearer_uci, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->bearer_uci_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_register, ccp_cb_notify, ccp, NULL); } static void bt_ccp_technology_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->bearer_technology, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->bearer_technology_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_register, ccp_cb_notify, ccp, NULL); } static void bt_ccp_strength_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->signal_strength, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->signal_strength_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_register, ccp_cb_notify, ccp, NULL); } static void bt_ccp_ccid_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->ccid, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->ccid_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_register, ccp_cb_notify, ccp, NULL); } static void bt_ccp_tar_uri_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->target_bearer_uri, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->target_bearer_uri_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_register, ccp_cb_notify, ccp, NULL); } static void bt_ccp_ctrl_point_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->call_ctrl_point, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->call_control_pt_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_register, ccp_cb_notify, ccp, NULL); } static void bt_ccp_ctrl_opcode_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->call_ctrl_opt_opcode, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->call_control_opt_opcode_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_register, ccp_cb_notify, ccp, NULL); } static void bt_ccp_friendly_name_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->friendly_name, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->friendly_name_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_register, ccp_cb_notify, ccp, NULL); } static void bt_ccp_signal_intrvl_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->signal_reporting_intrvl, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->signal_reporting_intrvl_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_register, ccp_cb_notify, ccp, NULL); } static void bt_ccp_uri_list_attach(struct bt_ccp *ccp) { uint16_t value_handle; struct bt_ccs *ccs = ccp_get_ccs(ccp); DBG(ccp, ""); if (!gatt_db_attribute_get_char_data(ccs->bearer_uri_schemes_list, NULL, &value_handle, NULL, NULL, NULL)) return; ccp_read_value(ccp, value_handle, read_call_back, ccp); ccp->bearer_uri_schemes_list_id = bt_gatt_client_register_notify(ccp->client, value_handle, ccp_cb_register, ccp_cb_notify, ccp, NULL); } static void foreach_ccs_char(struct gatt_db_attribute *attr, void *user_data) { struct bt_ccp *ccp = user_data; struct bt_ccs *ccs; uint16_t value_handle; bt_uuid_t uuid; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) return; ccs = ccp_get_ccs(ccp); if (!ccs || ccs->call_state) return; if (bt_uuid16_cmp(&uuid, BEARER_PROVIDER_NAME_CHRC_UUID)) { DBG(ccp, "Found Bearer Name, handle 0x%04x", value_handle); ccs->bearer_name = attr; bt_ccp_name_attach(ccp); } if (bt_uuid16_cmp(&uuid, BEARER_UCI_CHRC_UUID)) { DBG(ccp, "Found Bearer Uci, handle 0x%04x", value_handle); ccs->bearer_uci = attr; bt_ccp_uci_attach(ccp); } if (bt_uuid16_cmp(&uuid, BEARER_TECH_CHRC_UUID)) { DBG(ccp, "Found Bearer Technology, handle %x", value_handle); ccs->bearer_technology = attr; bt_ccp_technology_attach(ccp); } if (bt_uuid16_cmp(&uuid, BEARER_SIGNAL_STR_CHRC_UUID)) { DBG(ccp, "Found Signal Strength, handle 0x%04x", value_handle); ccs->signal_strength = attr; bt_ccp_strength_attach(ccp); } if (bt_uuid16_cmp(&uuid, BEARER_SIGNAL_INTRVL_CHRC_UUID)) { DBG(ccp, "Found Signal Interval, handle 0x%04x", value_handle); ccs->signal_reporting_intrvl = attr; bt_ccp_signal_intrvl_attach(ccp); } if (bt_uuid16_cmp(&uuid, CALL_STATUS_FLAG_CHRC_UUID)) { DBG(ccp, "Found Status Flag, handle 0x%04x", value_handle); ccs->status_flag = attr; bt_ccp_status_attach(ccp); } if (bt_uuid16_cmp(&uuid, BEARER_URI_SCHEME_CHRC_UUID)) { DBG(ccp, "Found URI Scheme, handle 0x%04x", value_handle); ccs->bearer_uri_schemes_list = attr; bt_ccp_uri_list_attach(ccp); } if (bt_uuid16_cmp(&uuid, CURR_CALL_LIST_CHRC_UUID)) { DBG(ccp, "Found Call List, handle 0x%04x", value_handle); ccs->current_call_list = attr; bt_ccp_call_list_attach(ccp); } if (bt_uuid16_cmp(&uuid, BEARER_CCID_CHRC_UUID)) { DBG(ccp, "Found CCID, handle 0x%04x", value_handle); ccs->ccid = attr; bt_ccp_ccid_attach(ccp); } if (bt_uuid16_cmp(&uuid, INCOM_CALL_TARGET_URI_CHRC_UUID)) { DBG(ccp, "Found Bearer Uri, handle 0x%04x", value_handle); ccs->target_bearer_uri = attr; bt_ccp_tar_uri_attach(ccp); } if (bt_uuid16_cmp(&uuid, CALL_CTRL_POINT_CHRC_UUID)) { DBG(ccp, "Found Control Point, handle 0x%04x", value_handle); ccs->call_ctrl_point = attr; bt_ccp_ctrl_point_attach(ccp); } if (bt_uuid16_cmp(&uuid, CALL_CTRL_POINT_OPT_OPCODE_CHRC_UUID)) { DBG(ccp, "Found Control opcode, handle 0x%04x", value_handle); ccs->call_ctrl_opt_opcode = attr; bt_ccp_ctrl_opcode_attach(ccp); } if (bt_uuid16_cmp(&uuid, TERMINATION_REASON_CHRC_UUID)) { DBG(ccp, "Found Termination Reason, handle %x", value_handle); ccs->termination_reason = attr; bt_ccp_term_reason_attach(ccp); } if (bt_uuid16_cmp(&uuid, INCOMING_CALL_CHRC_UUID)) { DBG(ccp, "Found Incoming call, handle 0x%04x", value_handle); ccs->incoming_call = attr; bt_ccp_incom_call_attach(ccp); } if (bt_uuid16_cmp(&uuid, CALL_FRIENDLY_NAME_CHRC_UUID)) { DBG(ccp, "Found Friendly name, handle 0x%04x", value_handle); ccs->friendly_name = attr; bt_ccp_friendly_name_attach(ccp); } } void bt_ccp_set_event_callbacks(struct bt_ccp *ccp, const struct bt_ccp_event_callback *cbs, void *user_data) { struct event_callback *cb; if (ccp->cb) free(ccp->cb); cb = new0(struct event_callback, 1); cb->cbs = cbs; cb->user_data = user_data; ccp->cb = cb; } static void foreach_ccs_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_ccp *ccp = user_data; struct bt_ccs *ccs = ccp_get_ccs(ccp); ccs->service = attr; gatt_db_service_foreach_char(attr, foreach_ccs_char, ccp); } static struct bt_ccp_db *ccp_db_new(struct gatt_db *db) { struct bt_ccp_db *mdb; if (!db) return NULL; mdb = new0(struct bt_ccp_db, 1); mdb->db = gatt_db_ref(db); if (!ccp_db) ccp_db = queue_new(); queue_push_tail(ccp_db, mdb); mdb->ccs = ccs_new(db); return mdb; } static struct bt_ccp_db *ccp_get_db(struct gatt_db *db) { struct bt_ccp_db *mdb; mdb = queue_find(ccp_db, ccp_db_match, db); if (mdb) return mdb; return ccp_db_new(db); } struct bt_ccp *bt_ccp_new(struct gatt_db *ldb, struct gatt_db *rdb) { struct bt_ccp *ccp; struct bt_ccp_db *mdb; if (!ldb) return NULL; mdb = ccp_get_db(ldb); if (!mdb) return NULL; ccp = new0(struct bt_ccp, 1); ccp->ldb = mdb; ccp->pending = queue_new(); if (!rdb) goto done; mdb = new0(struct bt_ccp_db, 1); mdb->db = gatt_db_ref(rdb); ccp->rdb = mdb; done: bt_ccp_ref(ccp); return ccp; } void bt_ccp_register(struct gatt_db *db) { ccp_db_new(db); } bool bt_ccp_attach(struct bt_ccp *ccp, struct bt_gatt_client *client) { bt_uuid_t uuid; DBG(ccp, "ccp %p", ccp); ccp->client = bt_gatt_client_clone(client); if (!ccp->client) return false; if (ccp->rdb->ccs) { bt_ccp_call_state_attach(ccp); return true; } bt_uuid16_create(&uuid, GTBS_UUID); gatt_db_foreach_service(ccp->rdb->db, &uuid, foreach_ccs_service, ccp); return true; } void bt_ccp_detach(struct bt_ccp *ccp) { DBG(ccp, "%p", ccp); bt_gatt_client_unref(ccp->client); ccp->client = NULL; } bluez-5.87/src/shared/PaxHeaders/att.c0000644000000000000000000000005015200607060014640 xustar0020 atime=1783092683 20 ctime=1783094099 bluez-5.87/src/shared/att.c0000644000000000000000000013344415200607060014332 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Google Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "src/shared/io.h" #include "src/shared/queue.h" #include "src/shared/util.h" #include "src/shared/timeout.h" #include "bluetooth/bluetooth.h" #include "bluetooth/l2cap.h" #include "bluetooth/uuid.h" #include "src/shared/att.h" #include "src/shared/crypto.h" #define ATT_MIN_PDU_LEN 1 /* At least 1 byte for the opcode. */ #define ATT_OP_CMD_MASK 0x40 #define ATT_OP_SIGNED_MASK 0x80 #define ATT_TIMEOUT_INTERVAL 30000 /* 30000 ms */ /* Length of signature in write signed packet */ #define BT_ATT_SIGNATURE_LEN 12 struct att_send_op; struct bt_att_chan { struct bt_att *att; int fd; struct io *io; uint8_t type; int sec_level; /* Only used for non-L2CAP */ struct queue *queue; /* Channel dedicated queue */ struct att_send_op *pending_req; struct att_send_op *pending_ind; struct att_send_op *pending_db_sync; bool writer_active; bool in_req; /* There's a pending incoming request */ uint8_t *buf; uint16_t mtu; }; struct bt_att { int ref_count; bool close_on_unref; struct queue *chans; uint8_t enc_size; uint16_t mtu; /* Biggest possible MTU */ struct queue *notify_list; /* List of registered callbacks */ struct queue *disconn_list; /* List of disconnect handlers */ struct queue *exchange_list; /* List of MTU changed handlers */ unsigned int next_send_id; /* IDs for "send" ops */ unsigned int next_reg_id; /* IDs for registered callbacks */ struct queue *req_queue; /* Queued ATT protocol requests */ struct queue *ind_queue; /* Queued ATT protocol indications */ struct queue *write_queue; /* Queue of PDUs ready to send */ bool in_disc; /* Cleanup queues on disconnect_cb */ bt_att_timeout_func_t timeout_callback; bt_att_destroy_func_t timeout_destroy; void *timeout_data; bt_att_db_sync_func_t db_sync_callback; bt_att_destroy_func_t db_sync_destroy; void *db_sync_data; uint8_t debug_level; bt_att_debug_func_t debug_callback; bt_att_destroy_func_t debug_destroy; void *debug_data; struct bt_crypto *crypto; struct sign_info *local_sign; struct sign_info *remote_sign; }; struct sign_info { uint8_t key[16]; bt_att_counter_func_t counter; void *user_data; }; enum att_op_type { ATT_OP_TYPE_REQ, ATT_OP_TYPE_RSP, ATT_OP_TYPE_CMD, ATT_OP_TYPE_IND, ATT_OP_TYPE_NFY, ATT_OP_TYPE_CONF, ATT_OP_TYPE_UNKNOWN, }; static const struct { uint8_t opcode; enum att_op_type type; } att_opcode_type_table[] = { { BT_ATT_OP_ERROR_RSP, ATT_OP_TYPE_RSP }, { BT_ATT_OP_MTU_REQ, ATT_OP_TYPE_REQ }, { BT_ATT_OP_MTU_RSP, ATT_OP_TYPE_RSP }, { BT_ATT_OP_FIND_INFO_REQ, ATT_OP_TYPE_REQ }, { BT_ATT_OP_FIND_INFO_RSP, ATT_OP_TYPE_RSP }, { BT_ATT_OP_FIND_BY_TYPE_REQ, ATT_OP_TYPE_REQ }, { BT_ATT_OP_FIND_BY_TYPE_RSP, ATT_OP_TYPE_RSP }, { BT_ATT_OP_READ_BY_TYPE_REQ, ATT_OP_TYPE_REQ }, { BT_ATT_OP_READ_BY_TYPE_RSP, ATT_OP_TYPE_RSP }, { BT_ATT_OP_READ_REQ, ATT_OP_TYPE_REQ }, { BT_ATT_OP_READ_RSP, ATT_OP_TYPE_RSP }, { BT_ATT_OP_READ_BLOB_REQ, ATT_OP_TYPE_REQ }, { BT_ATT_OP_READ_BLOB_RSP, ATT_OP_TYPE_RSP }, { BT_ATT_OP_READ_MULT_REQ, ATT_OP_TYPE_REQ }, { BT_ATT_OP_READ_MULT_RSP, ATT_OP_TYPE_RSP }, { BT_ATT_OP_READ_BY_GRP_TYPE_REQ, ATT_OP_TYPE_REQ }, { BT_ATT_OP_READ_BY_GRP_TYPE_RSP, ATT_OP_TYPE_RSP }, { BT_ATT_OP_WRITE_REQ, ATT_OP_TYPE_REQ }, { BT_ATT_OP_WRITE_RSP, ATT_OP_TYPE_RSP }, { BT_ATT_OP_WRITE_CMD, ATT_OP_TYPE_CMD }, { BT_ATT_OP_SIGNED_WRITE_CMD, ATT_OP_TYPE_CMD }, { BT_ATT_OP_PREP_WRITE_REQ, ATT_OP_TYPE_REQ }, { BT_ATT_OP_PREP_WRITE_RSP, ATT_OP_TYPE_RSP }, { BT_ATT_OP_EXEC_WRITE_REQ, ATT_OP_TYPE_REQ }, { BT_ATT_OP_EXEC_WRITE_RSP, ATT_OP_TYPE_RSP }, { BT_ATT_OP_HANDLE_NFY, ATT_OP_TYPE_NFY }, { BT_ATT_OP_HANDLE_NFY_MULT, ATT_OP_TYPE_NFY }, { BT_ATT_OP_HANDLE_IND, ATT_OP_TYPE_IND }, { BT_ATT_OP_HANDLE_CONF, ATT_OP_TYPE_CONF }, { } }; static enum att_op_type get_op_type(uint8_t opcode) { int i; for (i = 0; att_opcode_type_table[i].opcode; i++) { if (att_opcode_type_table[i].opcode == opcode) return att_opcode_type_table[i].type; } if (opcode & ATT_OP_CMD_MASK) return ATT_OP_TYPE_CMD; return ATT_OP_TYPE_UNKNOWN; } static const struct { uint8_t req_opcode; uint8_t rsp_opcode; } att_req_rsp_mapping_table[] = { { BT_ATT_OP_MTU_REQ, BT_ATT_OP_MTU_RSP }, { BT_ATT_OP_FIND_INFO_REQ, BT_ATT_OP_FIND_INFO_RSP}, { BT_ATT_OP_FIND_BY_TYPE_REQ, BT_ATT_OP_FIND_BY_TYPE_RSP }, { BT_ATT_OP_READ_BY_TYPE_REQ, BT_ATT_OP_READ_BY_TYPE_RSP }, { BT_ATT_OP_READ_REQ, BT_ATT_OP_READ_RSP }, { BT_ATT_OP_READ_BLOB_REQ, BT_ATT_OP_READ_BLOB_RSP }, { BT_ATT_OP_READ_MULT_REQ, BT_ATT_OP_READ_MULT_RSP }, { BT_ATT_OP_READ_BY_GRP_TYPE_REQ, BT_ATT_OP_READ_BY_GRP_TYPE_RSP }, { BT_ATT_OP_WRITE_REQ, BT_ATT_OP_WRITE_RSP }, { BT_ATT_OP_PREP_WRITE_REQ, BT_ATT_OP_PREP_WRITE_RSP }, { BT_ATT_OP_EXEC_WRITE_REQ, BT_ATT_OP_EXEC_WRITE_RSP }, { } }; static uint8_t get_req_opcode(uint8_t rsp_opcode) { int i; for (i = 0; att_req_rsp_mapping_table[i].rsp_opcode; i++) { if (att_req_rsp_mapping_table[i].rsp_opcode == rsp_opcode) return att_req_rsp_mapping_table[i].req_opcode; } return 0; } struct att_send_op { unsigned int id; unsigned int timeout_id; enum att_op_type type; uint8_t opcode; void *pdu; uint16_t len; bool retry; bt_att_response_func_t callback; bt_att_destroy_func_t destroy; void *user_data; }; static void destroy_att_send_op(void *data) { struct att_send_op *op = data; if (op->timeout_id) timeout_remove(op->timeout_id); if (op->destroy) op->destroy(op->user_data); free(op->pdu); free(op); } static void cancel_att_send_op(void *data) { struct att_send_op *op = data; if (op->destroy) op->destroy(op->user_data); op->user_data = NULL; op->callback = NULL; op->destroy = NULL; } struct att_notify { unsigned int id; uint16_t opcode; bt_att_notify_func_t callback; bt_att_destroy_func_t destroy; void *user_data; }; static void destroy_att_notify(void *data) { struct att_notify *notify = data; if (notify->destroy) notify->destroy(notify->user_data); free(notify); } static bool match_notify_id(const void *a, const void *b) { const struct att_notify *notify = a; unsigned int id = PTR_TO_UINT(b); return notify->id == id; } struct att_disconn { unsigned int id; bool removed; bt_att_disconnect_func_t callback; bt_att_destroy_func_t destroy; void *user_data; }; struct att_exchange { unsigned int id; bool removed; bt_att_exchange_func_t callback; bt_att_destroy_func_t destroy; void *user_data; }; static void destroy_att_disconn(void *data) { struct att_disconn *disconn = data; if (disconn->destroy) disconn->destroy(disconn->user_data); free(disconn); } static void destroy_att_exchange(void *data) { struct att_exchange *exchange = data; if (exchange->destroy) exchange->destroy(exchange->user_data); free(exchange); } static bool match_disconn_id(const void *a, const void *b) { const struct att_disconn *disconn = a; unsigned int id = PTR_TO_UINT(b); return disconn->id == id; } static void att_log(struct bt_att *att, uint8_t level, const char *format, ...) { va_list va; if (att->debug_level < level) return; va_start(va, format); util_debug_va(att->debug_callback, att->debug_data, format, va); va_end(va); } #define DBG(_att, _format, _arg...) \ att_log(_att, BT_ATT_DEBUG, "%s:%s() " _format, __FILE__, __func__,\ ## _arg) #define VERBOSE(_att, _format, _arg...) \ att_log(_att, BT_ATT_DEBUG_VERBOSE, "%s:%s() " _format, __FILE__, \ __func__, ## _arg) static void att_hexdump(struct bt_att *att, char dir, const void *data, size_t len) { if (att->debug_level < 2) return; util_hexdump(dir, data, len, att->debug_callback, att->debug_data); } static bool encode_pdu(struct bt_att *att, struct att_send_op *op, const void *pdu, uint16_t length) { uint16_t pdu_len = 1; struct sign_info *sign = att->local_sign; uint32_t sign_cnt; if (sign && (op->opcode & ATT_OP_SIGNED_MASK)) pdu_len += BT_ATT_SIGNATURE_LEN; if (length && pdu) pdu_len += length; if (pdu_len > att->mtu) return false; op->len = pdu_len; op->pdu = malloc(op->len); if (!op->pdu) return false; ((uint8_t *) op->pdu)[0] = op->opcode; if (pdu_len > 1) memcpy(op->pdu + 1, pdu, length); if (!sign || !(op->opcode & ATT_OP_SIGNED_MASK) || !att->crypto) return true; if (!sign->counter(&sign_cnt, sign->user_data)) goto fail; if ((bt_crypto_sign_att(att->crypto, sign->key, op->pdu, 1 + length, sign_cnt, &((uint8_t *) op->pdu)[1 + length]))) return true; DBG(att, "ATT unable to generate signature"); fail: free(op->pdu); return false; } static struct att_send_op *create_att_send_op(struct bt_att *att, uint8_t opcode, const void *pdu, uint16_t length, bt_att_response_func_t callback, void *user_data, bt_att_destroy_func_t destroy) { struct att_send_op *op; enum att_op_type type; if (length && !pdu) return NULL; type = get_op_type(opcode); if (type == ATT_OP_TYPE_UNKNOWN) return NULL; /* If the opcode corresponds to an operation type that does not elicit a * response from the remote end, then no callback should have been * provided, since it will never be called. */ if (callback && type != ATT_OP_TYPE_REQ && type != ATT_OP_TYPE_IND) return NULL; /* Similarly, if the operation does elicit a response then a callback * must be provided. */ if (!callback && (type == ATT_OP_TYPE_REQ || type == ATT_OP_TYPE_IND)) return NULL; op = new0(struct att_send_op, 1); op->type = type; op->opcode = opcode; op->callback = callback; op->destroy = destroy; op->user_data = user_data; if (!encode_pdu(att, op, pdu, length)) { free(op); return NULL; } return op; } static struct att_send_op *pick_next_send_op(struct bt_att_chan *chan) { struct bt_att *att = chan->att; struct att_send_op *op; /* Check if there is anything queued on the channel */ op = queue_pop_head(chan->queue); if (op) return op; /* See if any operations are already in the write queue */ op = queue_peek_head(att->write_queue); if (op && op->len <= chan->mtu) return queue_pop_head(att->write_queue); /* If there is no pending request, pick an operation from the * request queue. */ if (!chan->pending_req) { op = queue_peek_head(att->req_queue); if (op && op->len <= chan->mtu) { /* Don't send Exchange MTU over EATT */ if (op->opcode == BT_ATT_OP_MTU_REQ && chan->type == BT_ATT_EATT) goto indicate; return queue_pop_head(att->req_queue); } } indicate: /* There is either a request pending or no requests queued. If there is * no pending indication, pick an operation from the indication queue. */ if (!chan->pending_ind) { op = queue_peek_head(att->ind_queue); if (op && op->len <= chan->mtu) return queue_pop_head(att->ind_queue); } return NULL; } static void disc_att_send_op(void *data) { struct att_send_op *op = data; if (op->callback) op->callback(BT_ATT_OP_ERROR_RSP, NULL, 0, op->user_data); destroy_att_send_op(op); } struct timeout_data { struct bt_att_chan *chan; unsigned int id; }; static bool timeout_cb(void *user_data) { struct timeout_data *timeout = user_data; struct bt_att_chan *chan = timeout->chan; struct bt_att *att = chan->att; struct att_send_op *op = NULL; if (chan->pending_req && chan->pending_req->id == timeout->id) { op = chan->pending_req; chan->pending_req = NULL; } else if (chan->pending_ind && chan->pending_ind->id == timeout->id) { op = chan->pending_ind; chan->pending_ind = NULL; } if (!op) return false; DBG(att, "(chan %p) Operation timed out: 0x%02x", chan, op->opcode); if (att->timeout_callback) att->timeout_callback(op->id, op->opcode, att->timeout_data); op->timeout_id = 0; disc_att_send_op(op); /* * Directly terminate the connection as required by the ATT protocol. * This should trigger an io disconnect event which will clean up the * io and notify the upper layer. */ io_shutdown(chan->io); return false; } static void write_watch_destroy(void *user_data) { struct bt_att_chan *chan = user_data; chan->writer_active = false; } static ssize_t bt_att_chan_write(struct bt_att_chan *chan, uint8_t opcode, const void *pdu, uint16_t len) { struct bt_att *att = chan->att; ssize_t ret; struct iovec iov; iov.iov_base = (void *) pdu; iov.iov_len = len; VERBOSE(att, "(chan %p) ATT op 0x%02x", chan, opcode); ret = io_send(chan->io, &iov, 1); if (ret < 0) { DBG(att, "(chan %p) write failed: %s", chan, strerror(-ret)); return ret; } if (att->debug_level) util_hexdump('<', pdu, ret, att->debug_callback, att->debug_data); return ret; } static bool can_write_data(struct io *io, void *user_data) { struct bt_att_chan *chan = user_data; struct att_send_op *op; struct timeout_data *timeout; op = pick_next_send_op(chan); if (!op) return false; if (bt_att_chan_write(chan, op->opcode, op->pdu, op->len) < 0) { if (op->callback) op->callback(BT_ATT_OP_ERROR_RSP, NULL, 0, op->user_data); destroy_att_send_op(op); return true; } /* Based on the operation type, set either the pending request or the * pending indication. If it came from the write queue, then there is * no need to keep it around. */ switch (op->type) { case ATT_OP_TYPE_REQ: chan->pending_req = op; break; case ATT_OP_TYPE_IND: chan->pending_ind = op; break; case ATT_OP_TYPE_RSP: /* Set in_req to false to indicate that no request is pending */ chan->in_req = false; /* fall through */ case ATT_OP_TYPE_CMD: case ATT_OP_TYPE_NFY: case ATT_OP_TYPE_CONF: case ATT_OP_TYPE_UNKNOWN: default: destroy_att_send_op(op); return true; } timeout = new0(struct timeout_data, 1); timeout->chan = chan; timeout->id = op->id; op->timeout_id = timeout_add(ATT_TIMEOUT_INTERVAL, timeout_cb, timeout, free); /* Return true as there may be more operations ready to write. */ return true; } static void wakeup_chan_writer(void *data, void *user_data) { struct bt_att_chan *chan = data; struct bt_att *att = chan->att; if (chan->writer_active) return; /* Set the write handler only if there is anything that can be sent * at all. */ if (queue_isempty(chan->queue) && queue_isempty(att->write_queue)) { if ((chan->pending_req || queue_isempty(att->req_queue)) && (chan->pending_ind || queue_isempty(att->ind_queue))) return; } if (!io_set_write_handler(chan->io, can_write_data, chan, write_watch_destroy)) return; chan->writer_active = true; } static void wakeup_writer(struct bt_att *att) { queue_foreach(att->chans, wakeup_chan_writer, NULL); } static void disconn_handler(void *data, void *user_data) { struct att_disconn *disconn = data; int err = PTR_TO_INT(user_data); if (disconn->removed) return; if (disconn->callback) disconn->callback(err, disconn->user_data); } static void bt_att_chan_free(void *data) { struct bt_att_chan *chan = data; if (chan->pending_req) destroy_att_send_op(chan->pending_req); if (chan->pending_ind) destroy_att_send_op(chan->pending_ind); if (chan->pending_db_sync) destroy_att_send_op(chan->pending_db_sync); queue_destroy(chan->queue, destroy_att_send_op); io_destroy(chan->io); free(chan->buf); free(chan); } static bool disconnect_cb(struct io *io, void *user_data) { struct bt_att_chan *chan = user_data; struct bt_att *att = chan->att; int err; socklen_t len; len = sizeof(err); if (getsockopt(chan->fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { DBG(att, "(chan %p) Failed to obtain disconnect " "error: %s", chan, strerror(errno)); err = 0; } DBG(att, "Channel %p disconnected: %s", chan, strerror(err)); /* Detach channel */ queue_remove(att->chans, chan); if (chan->pending_req) { disc_att_send_op(chan->pending_req); chan->pending_req = NULL; } if (chan->pending_ind) { disc_att_send_op(chan->pending_ind); chan->pending_ind = NULL; } if (chan->pending_db_sync) { disc_att_send_op(chan->pending_db_sync); chan->pending_db_sync = NULL; } bt_att_chan_free(chan); /* Don't run disconnect callback if there are channels left */ if (!queue_isempty(att->chans)) return false; bt_att_ref(att); att->in_disc = true; /* Notify request callbacks */ queue_remove_all(att->req_queue, NULL, NULL, disc_att_send_op); queue_remove_all(att->ind_queue, NULL, NULL, disc_att_send_op); queue_remove_all(att->write_queue, NULL, NULL, disc_att_send_op); att->in_disc = false; queue_foreach(att->disconn_list, disconn_handler, INT_TO_PTR(err)); bt_att_unregister_all(att); bt_att_unref(att); return false; } static int bt_att_chan_get_security(struct bt_att_chan *chan) { struct bt_security sec; socklen_t len; if (chan->type == BT_ATT_LOCAL) return chan->sec_level; memset(&sec, 0, sizeof(sec)); len = sizeof(sec); if (getsockopt(chan->fd, SOL_BLUETOOTH, BT_SECURITY, &sec, &len) < 0) return -EIO; return sec.level; } static bool bt_att_chan_set_security(struct bt_att_chan *chan, int level) { struct bt_security sec; if (chan->type == BT_ATT_LOCAL) { chan->sec_level = level; return true; } /* Check if security level has already been set, if the security level * is higher it shall satisfy the request since we never want to * downgrade security. */ if (level <= bt_att_chan_get_security(chan)) return true; memset(&sec, 0, sizeof(sec)); sec.level = level; if (setsockopt(chan->fd, SOL_BLUETOOTH, BT_SECURITY, &sec, sizeof(sec)) < 0) return false; return true; } static bool change_security(struct bt_att_chan *chan, uint8_t ecode) { int security; if (chan->sec_level != BT_ATT_SECURITY_AUTO) return false; security = bt_att_chan_get_security(chan); if (ecode == BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION && security < BT_ATT_SECURITY_MEDIUM) { security = BT_ATT_SECURITY_MEDIUM; } else if (ecode == BT_ATT_ERROR_AUTHENTICATION) { if (security < BT_ATT_SECURITY_MEDIUM) security = BT_ATT_SECURITY_MEDIUM; else if (security < BT_ATT_SECURITY_HIGH) security = BT_ATT_SECURITY_HIGH; else if (security < BT_ATT_SECURITY_FIPS) security = BT_ATT_SECURITY_FIPS; else return false; } else { return false; } return bt_att_chan_set_security(chan, security); } static bool handle_error_rsp(struct bt_att_chan *chan, uint8_t *pdu, ssize_t pdu_len, uint8_t *opcode) { struct bt_att *att = chan->att; const struct bt_att_pdu_error_rsp *rsp; struct att_send_op *op = chan->pending_req; if (pdu_len != sizeof(*rsp)) { *opcode = 0; return false; } rsp = (void *) pdu; *opcode = rsp->opcode; /* If operation has already been marked as retry don't attempt to change * the security again. */ if (op->retry) return false; /* Attempt to change security */ if (change_security(chan, rsp->ecode)) goto retry; /* Check if this is DB_OUT_OF_SYNC and we have a callback */ if (rsp->ecode == BT_ATT_ERROR_DB_OUT_OF_SYNC && att->db_sync_callback) { /* Remove timeout since we're waiting for approval */ if (op->timeout_id) { timeout_remove(op->timeout_id); op->timeout_id = 0; } /* Move to pending_db_sync */ chan->pending_db_sync = op; chan->pending_req = NULL; DBG(att, "(chan %p) DB out of sync for operation %p", chan, op); /* Notify upper layer */ att->db_sync_callback(rsp, op->pdu + 1, op->len - 1, op->id, att->db_sync_data); return true; } return false; retry: /* Remove timeout_id if outstanding */ if (op->timeout_id) { timeout_remove(op->timeout_id); op->timeout_id = 0; } DBG(att, "(chan %p) Retrying operation %p", chan, op); chan->pending_req = NULL; op->retry = true; /* Push operation back to channel queue */ return queue_push_head(chan->queue, op); } static void handle_rsp(struct bt_att_chan *chan, uint8_t opcode, uint8_t *pdu, ssize_t pdu_len) { struct bt_att *att = chan->att; struct att_send_op *op = chan->pending_req; uint8_t req_opcode; uint8_t rsp_opcode; uint8_t *rsp_pdu = NULL; uint16_t rsp_pdu_len = 0; /* * If no request is pending, then the response is unexpected. Disconnect * the bearer. */ if (!op) { DBG(att, "(chan %p) Received unexpected ATT response", chan); io_shutdown(chan->io); return; } /* * If the received response doesn't match the pending request, or if * the request is malformed, end the current request with failure. */ if (opcode == BT_ATT_OP_ERROR_RSP) { /* Return if error response cause a retry */ if (handle_error_rsp(chan, pdu, pdu_len, &req_opcode)) { wakeup_chan_writer(chan, NULL); return; } } else if (!(req_opcode = get_req_opcode(opcode))) goto fail; if (req_opcode != op->opcode) goto fail; rsp_opcode = opcode; if (pdu_len > 0) { rsp_pdu = pdu; rsp_pdu_len = pdu_len; } goto done; fail: DBG(att, "(chan %p) Failed to handle response PDU; opcode: " "0x%02x", chan, opcode); rsp_opcode = BT_ATT_OP_ERROR_RSP; done: if (op->callback) op->callback(rsp_opcode, rsp_pdu, rsp_pdu_len, op->user_data); destroy_att_send_op(op); chan->pending_req = NULL; wakeup_chan_writer(chan, NULL); } static void handle_conf(struct bt_att_chan *chan, uint8_t *pdu, ssize_t pdu_len) { struct bt_att *att = chan->att; struct att_send_op *op = chan->pending_ind; /* * Disconnect the bearer if the confirmation is unexpected or the PDU is * invalid. */ if (!op || pdu_len) { DBG(att, "(chan %p) Received unexpected/invalid ATT " "confirmation", chan); io_shutdown(chan->io); return; } if (op->callback) op->callback(BT_ATT_OP_HANDLE_CONF, NULL, 0, op->user_data); destroy_att_send_op(op); chan->pending_ind = NULL; wakeup_chan_writer(chan, NULL); } struct notify_data { uint8_t opcode; uint8_t *pdu; ssize_t pdu_len; bool handler_found; }; static bool opcode_match(uint8_t opcode, uint8_t test_opcode) { enum att_op_type op_type = get_op_type(test_opcode); if (opcode == BT_ATT_ALL_REQUESTS && (op_type == ATT_OP_TYPE_REQ || op_type == ATT_OP_TYPE_CMD)) return true; return opcode == test_opcode; } static void respond_not_supported(struct bt_att *att, uint8_t opcode) { struct bt_att_pdu_error_rsp pdu; pdu.opcode = opcode; pdu.handle = 0x0000; pdu.ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; bt_att_send(att, BT_ATT_OP_ERROR_RSP, &pdu, sizeof(pdu), NULL, NULL, NULL); } static bool handle_signed(struct bt_att *att, uint8_t *pdu, ssize_t pdu_len) { uint8_t *signature; uint32_t sign_cnt; struct sign_info *sign; uint8_t opcode = pdu[0]; /* Check if there is enough data for a signature */ if (pdu_len < 3 + BT_ATT_SIGNATURE_LEN) goto fail; sign = att->remote_sign; if (!sign) goto fail; signature = pdu + (pdu_len - BT_ATT_SIGNATURE_LEN); sign_cnt = get_le32(signature); /* Validate counter */ if (!sign->counter(&sign_cnt, sign->user_data)) goto fail; /* Verify received signature */ if (!bt_crypto_verify_att_sign(att->crypto, sign->key, pdu, pdu_len)) goto fail; return true; fail: DBG(att, "ATT failed to verify signature: 0x%02x", opcode); return false; } static void handle_notify(struct bt_att_chan *chan, uint8_t *pdu, ssize_t pdu_len) { struct bt_att *att = chan->att; const struct queue_entry *entry; bool found; uint8_t opcode = pdu[0]; bt_att_ref(att); found = false; entry = queue_get_entries(att->notify_list); while (entry) { struct att_notify *notify = entry->data; entry = entry->next; if (!opcode_match(notify->opcode, opcode)) continue; if ((opcode & ATT_OP_SIGNED_MASK) && att->crypto) { if (!handle_signed(att, pdu, pdu_len)) return; pdu_len -= BT_ATT_SIGNATURE_LEN; } /* BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part G * page 2370 * * 4.3.1 Exchange MTU * * This sub-procedure shall not be used on a BR/EDR physical * link since the MTU size is negotiated using L2CAP channel * configuration procedures. */ if (bt_att_get_link_type(att) == BT_ATT_BREDR || chan->type == BT_ATT_EATT) { switch (opcode) { case BT_ATT_OP_MTU_REQ: goto not_supported; } } found = true; if (notify->callback) notify->callback(chan, chan->mtu, opcode, pdu + 1, pdu_len - 1, notify->user_data); /* callback could remove all entries from notify list */ if (queue_isempty(att->notify_list)) break; } not_supported: /* * If this was not a command and no handler was registered for it, * respond with "Not Supported" */ if (!found && get_op_type(opcode) != ATT_OP_TYPE_CMD) respond_not_supported(att, opcode); bt_att_unref(att); } static bool can_read_data(struct io *io, void *user_data) { struct bt_att_chan *chan = user_data; struct bt_att *att = chan->att; uint8_t opcode; uint8_t *pdu; ssize_t bytes_read; bytes_read = read(chan->fd, chan->buf, chan->mtu); if (bytes_read < 0) return false; VERBOSE(att, "(chan %p) ATT received: %zd", chan, bytes_read); att_hexdump(att, '>', chan->buf, bytes_read); if (bytes_read < ATT_MIN_PDU_LEN) return true; pdu = chan->buf; opcode = pdu[0]; bt_att_ref(att); /* Act on the received PDU based on the opcode type */ switch (get_op_type(opcode)) { case ATT_OP_TYPE_RSP: VERBOSE(att, "(chan %p) ATT response received: 0x%02x", chan, opcode); handle_rsp(chan, opcode, pdu + 1, bytes_read - 1); break; case ATT_OP_TYPE_CONF: VERBOSE(att, "(chan %p) ATT confirmation received: 0x%02x", chan, opcode); handle_conf(chan, pdu + 1, bytes_read - 1); break; case ATT_OP_TYPE_REQ: /* * If a request is currently pending, then the sequential * protocol was violated. Disconnect the bearer, which will * promptly notify the upper layer via disconnect handlers. */ if (chan->in_req) { DBG(att, "(chan %p) Received request while " "another is pending: 0x%02x", chan, opcode); io_shutdown(chan->io); bt_att_unref(chan->att); return false; } chan->in_req = true; /* fall through */ case ATT_OP_TYPE_CMD: case ATT_OP_TYPE_NFY: case ATT_OP_TYPE_UNKNOWN: case ATT_OP_TYPE_IND: /* fall through */ default: /* For all other opcodes notify the upper layer of the PDU and * let them act on it. */ DBG(att, "(chan %p) ATT PDU received: 0x%02x", chan, opcode); handle_notify(chan, pdu, bytes_read); break; } bt_att_unref(att); return true; } static bool is_io_l2cap_based(int fd) { int domain; int proto; int err; socklen_t len; domain = 0; len = sizeof(domain); err = getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &domain, &len); if (err < 0) return false; if (domain != AF_BLUETOOTH) return false; proto = 0; len = sizeof(proto); err = getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &proto, &len); if (err < 0) return false; return proto == BTPROTO_L2CAP; } static void bt_att_free(struct bt_att *att) { bt_crypto_unref(att->crypto); if (att->timeout_destroy) att->timeout_destroy(att->timeout_data); if (att->db_sync_destroy) att->db_sync_destroy(att->db_sync_data); if (att->debug_destroy) att->debug_destroy(att->debug_data); free(att->local_sign); free(att->remote_sign); queue_destroy(att->req_queue, NULL); queue_destroy(att->ind_queue, NULL); queue_destroy(att->write_queue, NULL); queue_destroy(att->notify_list, NULL); queue_destroy(att->disconn_list, NULL); queue_destroy(att->exchange_list, NULL); queue_destroy(att->chans, bt_att_chan_free); free(att); } static uint16_t io_get_mtu(int fd) { socklen_t len; struct l2cap_options l2o; len = sizeof(l2o); if (!getsockopt(fd, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len)) return l2o.omtu; if (!getsockopt(fd, SOL_BLUETOOTH, BT_SNDMTU, &l2o.omtu, &len)) return l2o.omtu; return 0; } static uint8_t io_get_type(int fd) { struct sockaddr_l2 src; socklen_t len; if (!is_io_l2cap_based(fd)) return BT_ATT_LOCAL; len = sizeof(src); memset(&src, 0, len); if (getsockname(fd, (void *)&src, &len) < 0) return -errno; if (src.l2_bdaddr_type == BDADDR_BREDR) return BT_ATT_BREDR; return BT_ATT_LE; } static struct bt_att_chan *bt_att_chan_new(int fd, uint8_t type) { struct bt_att_chan *chan; if (fd < 0) return NULL; chan = new0(struct bt_att_chan, 1); chan->fd = fd; chan->io = io_new(fd); if (!chan->io) goto fail; if (!io_set_read_handler(chan->io, can_read_data, chan, NULL)) goto fail; if (!io_set_disconnect_handler(chan->io, disconnect_cb, chan, NULL)) goto fail; chan->type = type; switch (chan->type) { case BT_ATT_LOCAL: chan->sec_level = BT_ATT_SECURITY_LOW; /* fall through */ case BT_ATT_LE: chan->mtu = BT_ATT_DEFAULT_LE_MTU; break; default: chan->mtu = io_get_mtu(chan->fd); } if (chan->mtu < BT_ATT_DEFAULT_LE_MTU) goto fail; chan->buf = malloc(chan->mtu); if (!chan->buf) goto fail; chan->queue = queue_new(); return chan; fail: bt_att_chan_free(chan); return NULL; } static void bt_att_attach_chan(struct bt_att *att, struct bt_att_chan *chan) { /* Push to head as EATT channels have higher priority */ queue_push_head(att->chans, chan); chan->att = att; if (chan->mtu > att->mtu) att->mtu = chan->mtu; io_set_close_on_destroy(chan->io, att->close_on_unref); DBG(att, "Channel %p attached", chan); wakeup_chan_writer(chan, NULL); } struct bt_att *bt_att_new(int fd, bool ext_signed) { struct bt_att *att; struct bt_att_chan *chan; chan = bt_att_chan_new(fd, io_get_type(fd)); if (!chan) return NULL; att = new0(struct bt_att, 1); att->chans = queue_new(); att->mtu = chan->mtu; /* crypto is optional, if not available leave it NULL */ if (!ext_signed) att->crypto = bt_crypto_new(); att->req_queue = queue_new(); att->ind_queue = queue_new(); att->write_queue = queue_new(); att->notify_list = queue_new(); att->disconn_list = queue_new(); att->exchange_list = queue_new(); bt_att_attach_chan(att, chan); return bt_att_ref(att); } struct bt_att *bt_att_ref(struct bt_att *att) { if (!att) return NULL; __sync_fetch_and_add(&att->ref_count, 1); return att; } void bt_att_unref(struct bt_att *att) { if (!att) return; if (__sync_sub_and_fetch(&att->ref_count, 1)) return; bt_att_unregister_all(att); bt_att_cancel_all(att); bt_att_free(att); } bool bt_att_set_close_on_unref(struct bt_att *att, bool do_close) { const struct queue_entry *entry; if (!att) return false; att->close_on_unref = do_close; for (entry = queue_get_entries(att->chans); entry; entry = entry->next) { struct bt_att_chan *chan = entry->data; if (!io_set_close_on_destroy(chan->io, do_close)) return false; } return true; } int bt_att_attach_fd(struct bt_att *att, int fd) { struct bt_att_chan *chan; if (!att || fd < 0) return -EINVAL; chan = bt_att_chan_new(fd, BT_ATT_EATT); if (!chan) return -EINVAL; bt_att_attach_chan(att, chan); return 0; } int bt_att_get_fd(struct bt_att *att) { struct bt_att_chan *chan; if (!att) return -1; if (queue_isempty(att->chans)) return -ENOTCONN; chan = queue_peek_tail(att->chans); return chan->fd; } int bt_att_get_channels(struct bt_att *att) { if (!att) return 0; return queue_length(att->chans); } bool bt_att_set_debug(struct bt_att *att, uint8_t level, bt_att_debug_func_t callback, void *user_data, bt_att_destroy_func_t destroy) { if (!att) return false; if (att->debug_destroy) att->debug_destroy(att->debug_data); att->debug_level = level; att->debug_callback = callback; att->debug_destroy = destroy; att->debug_data = user_data; return true; } uint16_t bt_att_get_mtu(struct bt_att *att) { if (!att) return 0; return att->mtu; } static void exchange_handler(void *data, void *user_data) { struct att_exchange *exchange = data; uint16_t mtu = PTR_TO_INT(user_data); if (exchange->removed) return; if (exchange->callback) exchange->callback(mtu, exchange->user_data); } bool bt_att_set_mtu(struct bt_att *att, uint16_t mtu) { struct bt_att_chan *chan; void *buf; if (!att) return false; if (mtu < BT_ATT_DEFAULT_LE_MTU) return false; /* Original channel is always the last */ chan = queue_peek_tail(att->chans); if (!chan) return -ENOTCONN; buf = malloc(mtu); if (!buf) return false; free(chan->buf); chan->mtu = mtu; chan->buf = buf; if (chan->mtu > att->mtu) { att->mtu = chan->mtu; queue_foreach(att->exchange_list, exchange_handler, INT_TO_PTR(att->mtu)); } return true; } uint8_t bt_att_get_link_type(struct bt_att *att) { struct bt_att_chan *chan; if (!att) return -EINVAL; chan = queue_peek_tail(att->chans); if (!chan) return -ENOTCONN; return chan->type; } bool bt_att_set_timeout_cb(struct bt_att *att, bt_att_timeout_func_t callback, void *user_data, bt_att_destroy_func_t destroy) { if (!att) return false; if (att->timeout_destroy) att->timeout_destroy(att->timeout_data); att->timeout_callback = callback; att->timeout_destroy = destroy; att->timeout_data = user_data; return true; } bool bt_att_set_db_sync_cb(struct bt_att *att, bt_att_db_sync_func_t callback, void *user_data, bt_att_destroy_func_t destroy) { if (!att) return false; if (att->db_sync_destroy) att->db_sync_destroy(att->db_sync_data); att->db_sync_callback = callback; att->db_sync_destroy = destroy; att->db_sync_data = user_data; return true; } unsigned int bt_att_register_disconnect(struct bt_att *att, bt_att_disconnect_func_t callback, void *user_data, bt_att_destroy_func_t destroy) { struct att_disconn *disconn; if (!att || queue_isempty(att->chans)) return 0; disconn = new0(struct att_disconn, 1); disconn->callback = callback; disconn->destroy = destroy; disconn->user_data = user_data; if (att->next_reg_id < 1) att->next_reg_id = 1; disconn->id = att->next_reg_id++; if (!queue_push_tail(att->disconn_list, disconn)) { free(disconn); return 0; } return disconn->id; } bool bt_att_unregister_disconnect(struct bt_att *att, unsigned int id) { struct att_disconn *disconn; if (!att || !id) return false; /* Check if disconnect is running */ if (queue_isempty(att->chans)) { disconn = queue_find(att->disconn_list, match_disconn_id, UINT_TO_PTR(id)); if (!disconn) return false; disconn->removed = true; return true; } disconn = queue_remove_if(att->disconn_list, match_disconn_id, UINT_TO_PTR(id)); if (!disconn) return false; destroy_att_disconn(disconn); return true; } unsigned int bt_att_register_exchange(struct bt_att *att, bt_att_exchange_func_t callback, void *user_data, bt_att_destroy_func_t destroy) { struct att_exchange *mtu; if (!att || queue_isempty(att->chans)) return 0; mtu = new0(struct att_exchange, 1); mtu->callback = callback; mtu->destroy = destroy; mtu->user_data = user_data; if (att->next_reg_id < 1) att->next_reg_id = 1; mtu->id = att->next_reg_id++; if (!queue_push_tail(att->exchange_list, mtu)) { free(att); return 0; } return mtu->id; } bool bt_att_unregister_exchange(struct bt_att *att, unsigned int id) { struct att_exchange *mtu; if (!att || !id) return false; /* Check if disconnect is running */ if (queue_isempty(att->chans)) { mtu = queue_find(att->exchange_list, match_disconn_id, UINT_TO_PTR(id)); if (!mtu) return false; mtu->removed = true; return true; } mtu = queue_remove_if(att->exchange_list, match_disconn_id, UINT_TO_PTR(id)); if (!mtu) return false; destroy_att_exchange(mtu); return true; } unsigned int bt_att_send(struct bt_att *att, uint8_t opcode, const void *pdu, uint16_t length, bt_att_response_func_t callback, void *user_data, bt_att_destroy_func_t destroy) { struct att_send_op *op; bool result; if (!att || queue_isempty(att->chans)) return 0; op = create_att_send_op(att, opcode, pdu, length, callback, user_data, destroy); if (!op) return 0; if (att->next_send_id < 1) att->next_send_id = 1; op->id = att->next_send_id++; /* Always use fixed channel for BT_ATT_OP_MTU_REQ */ if (opcode == BT_ATT_OP_MTU_REQ) { struct bt_att_chan *chan = queue_peek_tail(att->chans); result = queue_push_tail(chan->queue, op); goto done; } /* Add the op to the correct queue based on its type */ switch (op->type) { case ATT_OP_TYPE_REQ: result = queue_push_tail(att->req_queue, op); break; case ATT_OP_TYPE_IND: result = queue_push_tail(att->ind_queue, op); break; case ATT_OP_TYPE_CMD: case ATT_OP_TYPE_NFY: case ATT_OP_TYPE_UNKNOWN: case ATT_OP_TYPE_RSP: case ATT_OP_TYPE_CONF: default: result = queue_push_tail(att->write_queue, op); break; } done: if (!result) { free(op->pdu); free(op); return 0; } wakeup_writer(att); return op->id; } int bt_att_resend(struct bt_att *att, unsigned int id, uint8_t opcode, const void *pdu, uint16_t length, bt_att_response_func_t callback, void *user_data, bt_att_destroy_func_t destroy) { const struct queue_entry *entry; struct att_send_op *op; bool result; if (!att || !id) return -EINVAL; /* Lookup request on each channel */ for (entry = queue_get_entries(att->chans); entry; entry = entry->next) { struct bt_att_chan *chan = entry->data; if (chan->pending_req && chan->pending_req->id == id) break; /* Also check pending_db_sync */ if (chan->pending_db_sync && chan->pending_db_sync->id == id) { op = chan->pending_db_sync; chan->pending_db_sync = NULL; DBG(att, "(chan %p) Resending DB out of sync operation" " %p", chan, op); goto done; } } if (!entry) return -ENOENT; /* Only allow requests to be resend */ if (get_op_type(opcode) != ATT_OP_TYPE_REQ) return -EOPNOTSUPP; op = create_att_send_op(att, opcode, pdu, length, callback, user_data, destroy); if (!op) return -ENOMEM; op->id = id; done: switch (opcode) { /* Only prepend requests that could be a continuation */ case BT_ATT_OP_READ_BLOB_REQ: case BT_ATT_OP_PREP_WRITE_REQ: case BT_ATT_OP_EXEC_WRITE_REQ: result = queue_push_head(att->req_queue, op); break; default: result = queue_push_tail(att->req_queue, op); break; } if (!result) { free(op->pdu); free(op); return -ENOMEM; } wakeup_writer(att); return 0; } unsigned int bt_att_chan_send(struct bt_att_chan *chan, uint8_t opcode, const void *pdu, uint16_t len, bt_att_response_func_t callback, void *user_data, bt_att_destroy_func_t destroy) { struct att_send_op *op; if (!chan || !chan->att) return -EINVAL; op = create_att_send_op(chan->att, opcode, pdu, len, callback, user_data, destroy); if (!op) return -EINVAL; if (!queue_push_tail(chan->queue, op)) { free(op->pdu); free(op); return 0; } wakeup_chan_writer(chan, NULL); return op->id; } static bool match_op_id(const void *a, const void *b) { const struct att_send_op *op = a; unsigned int id = PTR_TO_UINT(b); return op->id == id; } bool bt_att_chan_cancel(struct bt_att_chan *chan, unsigned int id) { struct att_send_op *op; if (chan->pending_req && chan->pending_req->id == id) { /* Don't cancel the pending request; remove it's handlers */ cancel_att_send_op(chan->pending_req); return true; } if (chan->pending_ind && chan->pending_ind->id == id) { /* Don't cancel the pending indication; remove it's handlers. */ cancel_att_send_op(chan->pending_ind); return true; } op = queue_remove_if(chan->queue, match_op_id, UINT_TO_PTR(id)); if (!op) return false; destroy_att_send_op(op); wakeup_chan_writer(chan, NULL); return true; } static bool bt_att_db_sync_cancel(struct bt_att_chan *chan, unsigned int id) { struct att_send_op *op = chan->pending_db_sync; struct bt_att_pdu_error_rsp rsp; if (!op || op->id != id) return false; /* Build error response PDU */ memset(&rsp, 0, sizeof(rsp)); rsp.opcode = op->opcode; rsp.ecode = BT_ATT_ERROR_DB_OUT_OF_SYNC; /* Clear pending state */ chan->pending_db_sync = NULL; /* Notify callback with error */ if (op->callback) op->callback(BT_ATT_OP_ERROR_RSP, &rsp, sizeof(rsp), op->user_data); destroy_att_send_op(op); wakeup_chan_writer(chan, NULL); return true; } static bool bt_att_disc_cancel(struct bt_att *att, unsigned int id) { struct att_send_op *op; op = queue_find(att->req_queue, match_op_id, UINT_TO_PTR(id)); if (op) goto done; op = queue_find(att->ind_queue, match_op_id, UINT_TO_PTR(id)); if (op) goto done; op = queue_find(att->write_queue, match_op_id, UINT_TO_PTR(id)); done: if (!op) return false; /* Just cancel since disconnect_cb will be cleaning up */ cancel_att_send_op(op); return true; } bool bt_att_cancel(struct bt_att *att, unsigned int id) { const struct queue_entry *entry; struct att_send_op *op; if (!att || !id) return false; /* Lookup request on each channel first */ for (entry = queue_get_entries(att->chans); entry; entry = entry->next) { struct bt_att_chan *chan = entry->data; /* Check pending_db_sync first on each channel */ if (bt_att_db_sync_cancel(chan, id)) return true; if (bt_att_chan_cancel(chan, id)) return true; } if (att->in_disc) return bt_att_disc_cancel(att, id); op = queue_remove_if(att->req_queue, match_op_id, UINT_TO_PTR(id)); if (op) goto done; op = queue_remove_if(att->ind_queue, match_op_id, UINT_TO_PTR(id)); if (op) goto done; op = queue_remove_if(att->write_queue, match_op_id, UINT_TO_PTR(id)); if (op) goto done; if (!op) return false; done: destroy_att_send_op(op); wakeup_writer(att); return true; } bool bt_att_cancel_all(struct bt_att *att) { const struct queue_entry *entry; if (!att) return false; queue_remove_all(att->req_queue, NULL, NULL, destroy_att_send_op); queue_remove_all(att->ind_queue, NULL, NULL, destroy_att_send_op); queue_remove_all(att->write_queue, NULL, NULL, destroy_att_send_op); for (entry = queue_get_entries(att->chans); entry; entry = entry->next) { struct bt_att_chan *chan = entry->data; if (chan->pending_req) /* Don't cancel the pending request; remove it's * handlers */ cancel_att_send_op(chan->pending_req); if (chan->pending_ind) /* Don't cancel the pending request; remove it's * handlers */ cancel_att_send_op(chan->pending_ind); } return true; } static uint8_t att_ecode_from_error(int err) { /* * If the error fits in a single byte, treat it as an ATT protocol * error as is. Since "0" is not a valid ATT protocol error code, we map * that to UNLIKELY below. */ if (err > 0 && err < UINT8_MAX) return err; /* * Since we allow UNIX errnos, map them to appropriate ATT protocol * and "Common Profile and Service" error codes. */ switch (err) { case -ENOENT: return BT_ATT_ERROR_INVALID_HANDLE; case -ENOMEM: return BT_ATT_ERROR_INSUFFICIENT_RESOURCES; case -EALREADY: return BT_ERROR_ALREADY_IN_PROGRESS; case -EOVERFLOW: return BT_ERROR_OUT_OF_RANGE; } return BT_ATT_ERROR_UNLIKELY; } int bt_att_chan_send_error_rsp(struct bt_att_chan *chan, uint8_t opcode, uint16_t handle, int error) { struct bt_att_pdu_error_rsp pdu; uint8_t ecode; if (!chan || !chan->att || !opcode) return -EINVAL; ecode = att_ecode_from_error(error); memset(&pdu, 0, sizeof(pdu)); pdu.opcode = opcode; put_le16(handle, &pdu.handle); pdu.ecode = ecode; return bt_att_chan_send_rsp(chan, BT_ATT_OP_ERROR_RSP, &pdu, sizeof(pdu)); } unsigned int bt_att_register(struct bt_att *att, uint8_t opcode, bt_att_notify_func_t callback, void *user_data, bt_att_destroy_func_t destroy) { struct att_notify *notify; if (!att || !callback || queue_isempty(att->chans)) return 0; notify = new0(struct att_notify, 1); notify->opcode = opcode; notify->callback = callback; notify->destroy = destroy; notify->user_data = user_data; if (att->next_reg_id < 1) att->next_reg_id = 1; notify->id = att->next_reg_id++; if (!queue_push_tail(att->notify_list, notify)) { free(notify); return 0; } return notify->id; } bool bt_att_unregister(struct bt_att *att, unsigned int id) { struct att_notify *notify; if (!att || !id) return false; notify = queue_remove_if(att->notify_list, match_notify_id, UINT_TO_PTR(id)); if (!notify) return false; destroy_att_notify(notify); return true; } bool bt_att_unregister_all(struct bt_att *att) { if (!att) return false; queue_remove_all(att->notify_list, NULL, NULL, destroy_att_notify); queue_remove_all(att->disconn_list, NULL, NULL, destroy_att_disconn); queue_remove_all(att->exchange_list, NULL, NULL, destroy_att_exchange); return true; } int bt_att_get_security(struct bt_att *att, uint8_t *enc_size) { struct bt_att_chan *chan; int ret; if (!att) return -EINVAL; chan = queue_peek_tail(att->chans); if (!chan) return -ENOTCONN; ret = bt_att_chan_get_security(chan); if (ret < 0) return ret; if (enc_size) *enc_size = att->enc_size; return ret; } bool bt_att_set_security(struct bt_att *att, int level) { struct bt_att_chan *chan; if (!att || level < BT_ATT_SECURITY_AUTO || level > BT_ATT_SECURITY_HIGH) return false; chan = queue_peek_tail(att->chans); if (!chan) return -ENOTCONN; return bt_att_chan_set_security(chan, level); } void bt_att_set_enc_key_size(struct bt_att *att, uint8_t enc_size) { if (!att) return; att->enc_size = enc_size; } static bool sign_set_key(struct sign_info **sign, uint8_t key[16], bt_att_counter_func_t func, void *user_data) { if (!(*sign)) *sign = new0(struct sign_info, 1); (*sign)->counter = func; (*sign)->user_data = user_data; memcpy((*sign)->key, key, 16); return true; } bool bt_att_set_local_key(struct bt_att *att, uint8_t sign_key[16], bt_att_counter_func_t func, void *user_data) { if (!att) return false; return sign_set_key(&att->local_sign, sign_key, func, user_data); } bool bt_att_set_remote_key(struct bt_att *att, uint8_t sign_key[16], bt_att_counter_func_t func, void *user_data) { if (!att) return false; return sign_set_key(&att->remote_sign, sign_key, func, user_data); } bool bt_att_has_crypto(struct bt_att *att) { if (!att) return false; return att->crypto ? true : false; } bool bt_att_set_retry(struct bt_att *att, unsigned int id, bool retry) { struct att_send_op *op; if (!id) return false; op = queue_find(att->req_queue, match_op_id, UINT_TO_PTR(id)); if (op) goto done; op = queue_find(att->ind_queue, match_op_id, UINT_TO_PTR(id)); if (op) goto done; op = queue_find(att->write_queue, match_op_id, UINT_TO_PTR(id)); done: if (!op) return false; op->retry = !retry; return true; } bluez-5.87/src/shared/PaxHeaders/mainloop-notify.c0000644000000000000000000000005015017252326017203 xustar0020 atime=1783092983 20 ctime=1783094100 bluez-5.87/src/shared/mainloop-notify.c0000644000000000000000000000675415017252326016700 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "mainloop.h" #include "mainloop-notify.h" #include "timeout.h" #include "util.h" #include "io.h" #define WATCHDOG_TRIGGER_FREQ 2 static int notify_fd = -1; static unsigned int watchdog; struct signal_data { struct io *io; mainloop_signal_func func; void *user_data; }; static struct signal_data *signal_data; static bool watchdog_callback(void *user_data) { mainloop_sd_notify("WATCHDOG=1"); return true; } void mainloop_notify_init(void) { const char *sock; struct sockaddr_un addr; const char *watchdog_usec; int msec; sock = getenv("NOTIFY_SOCKET"); if (!sock) return; /* check for abstract socket or absolute path */ if (sock[0] != '@' && sock[0] != '/') return; notify_fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (notify_fd < 0) return; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, sock, sizeof(addr.sun_path) - 1); if (addr.sun_path[0] == '@') addr.sun_path[0] = '\0'; if (connect(notify_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { close(notify_fd); notify_fd = -1; return; } watchdog_usec = getenv("WATCHDOG_USEC"); if (!watchdog_usec) return; msec = atoi(watchdog_usec) / 1000; if (msec < 0) return; watchdog = timeout_add(msec / WATCHDOG_TRIGGER_FREQ, watchdog_callback, NULL, NULL); } void mainloop_notify_exit(void) { if (notify_fd > 0) { close(notify_fd); notify_fd = -1; } timeout_remove(watchdog); } int mainloop_sd_notify(const char *state) { int err; if (notify_fd <= 0) { if (strcmp(state, "STATUS=Starting up")) return -ENOTCONN; /* Auto init only when starting up */ mainloop_notify_init(); if (notify_fd <= 0) return -ENOTCONN; } err = send(notify_fd, state, strlen(state), MSG_NOSIGNAL); if (err < 0) return -errno; return err; } static bool signal_read(struct io *io, void *user_data) { struct signal_data *data = user_data; struct signalfd_siginfo si; ssize_t result; int fd; fd = io_get_fd(io); result = read(fd, &si, sizeof(si)); if (result != sizeof(si) || si.ssi_signo > INT_MAX) return false; if (data && data->func) data->func(si.ssi_signo, data->user_data); return true; } static struct io *setup_signalfd(void *user_data) { struct io *io; sigset_t mask; int fd; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGUSR2); sigaddset(&mask, SIGCHLD); if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) return NULL; fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); if (fd < 0) return NULL; io = io_new(fd); io_set_close_on_destroy(io, true); io_set_read_handler(io, signal_read, user_data, free); return io; } int mainloop_run_with_signal(mainloop_signal_func func, void *user_data) { struct signal_data *data; struct io *io; int ret; if (!func) return -EINVAL; data = new0(struct signal_data, 1); data->func = func; data->user_data = user_data; io = setup_signalfd(data); if (!io) { free(data); return -errno; } ret = mainloop_run(); io_destroy(io); free(signal_data); return ret; } bluez-5.87/src/shared/PaxHeaders/shell.h0000644000000000000000000000005015017252326015173 xustar0020 atime=1783092780 20 ctime=1783094100 bluez-5.87/src/shared/shell.h0000644000000000000000000000506515017252326014662 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2017 Intel Corporation. All rights reserved. * * */ #include #include #define COLOR_OFF "\x1B[0m" #define COLOR_RED "\x1B[0;91m" #define COLOR_GREEN "\x1B[0;92m" #define COLOR_YELLOW "\x1B[0;93m" #define COLOR_BLUE "\x1B[0;94m" #define COLOR_BOLDGRAY "\x1B[1;30m" #define COLOR_BOLDWHITE "\x1B[1;37m" #define COLOR_HIGHLIGHT "\x1B[1;39m" struct bt_shell_menu; typedef void (*bt_shell_menu_cb_t)(int argc, char *argv[]); typedef char * (*bt_shell_menu_gen_t)(const char *text, int state); typedef void (*bt_shell_menu_disp_t) (char **matches, int num_matches, int max_length); typedef void (*bt_shell_prompt_input_func) (const char *input, void *user_data); typedef bool (*bt_shell_menu_exists_t) (const struct bt_shell_menu *menu); typedef void (*bt_shell_menu_pre_run_t) (const struct bt_shell_menu *menu); struct bt_shell_menu_entry { const char *cmd; const char *arg; bt_shell_menu_cb_t func; const char *desc; bt_shell_menu_gen_t gen; bt_shell_menu_disp_t disp; bt_shell_menu_exists_t exists; }; struct bt_shell_menu { const char *name; const char *desc; bt_shell_menu_pre_run_t pre_run; const struct bt_shell_menu_entry entries[]; }; struct bt_shell_opt { const struct option *options; size_t optno; const char *optstr; const char ***optarg; const char **help; }; void bt_shell_init(int argc, char **argv, const struct bt_shell_opt *opt); int bt_shell_run(void); int bt_shell_exec(const char *input); void bt_shell_quit(int status); void bt_shell_noninteractive_quit(int status); void bt_shell_handle_non_interactive_help(void); bool bt_shell_set_menu(const struct bt_shell_menu *menu); bool bt_shell_add_submenu(const struct bt_shell_menu *menu); bool bt_shell_remove_submenu(const struct bt_shell_menu *menu); void bt_shell_set_prompt(const char *string, const char *color); void bt_shell_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2))); void bt_shell_echo(const char *fmt, ...) __attribute__((format(printf, 1, 2))); void bt_shell_hexdump(const unsigned char *buf, size_t len); void bt_shell_usage(void); void bt_shell_prompt_input(const char *label, const char *msg, bt_shell_prompt_input_func func, void *user_data); int bt_shell_release_prompt(const char *input); bool bt_shell_attach(int fd); bool bt_shell_detach(void); void bt_shell_set_env(const char *name, void *value); void *bt_shell_get_env(const char *name); int bt_shell_get_timeout(void); void bt_shell_cleanup(void); bluez-5.87/src/shared/PaxHeaders/csip.h0000644000000000000000000000005014536422313015022 xustar0020 atime=1783092957 20 ctime=1783094100 bluez-5.87/src/shared/csip.h0000644000000000000000000000403414536422313014504 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. * */ #include #include #include "src/shared/io.h" #ifndef __packed #define __packed __attribute__((packed)) #endif struct bt_csip; enum { BT_CSIP_SIRK_ENCRYPT = 0x00, BT_CSIP_SIRK_CLEARTEXT = 0x01 }; typedef void (*bt_csip_ready_func_t)(struct bt_csip *csip, void *user_data); typedef void (*bt_csip_destroy_func_t)(void *user_data); typedef void (*bt_csip_debug_func_t)(const char *str, void *user_data); typedef void (*bt_csip_func_t)(struct bt_csip *csip, void *user_data); typedef bool (*bt_csip_encrypt_func_t)(struct bt_att *att, uint8_t k[16]); typedef bool (*bt_csip_sirk_func_t)(struct bt_csip *csip, uint8_t type, uint8_t k[16], uint8_t size, uint8_t rank, void *user_data); struct bt_csip *bt_csip_ref(struct bt_csip *csip); void bt_csip_unref(struct bt_csip *csip); bool bt_csip_attach(struct bt_csip *csip, struct bt_gatt_client *client); void bt_csip_detach(struct bt_csip *csip); bool bt_csip_set_debug(struct bt_csip *csip, bt_csip_debug_func_t func, void *user_data, bt_csip_destroy_func_t destroy); struct bt_att *bt_csip_get_att(struct bt_csip *csip); bool bt_csip_set_user_data(struct bt_csip *csip, void *user_data); /* Session related function */ unsigned int bt_csip_register(bt_csip_func_t added, bt_csip_func_t removed, void *user_data); bool bt_csip_unregister(unsigned int id); struct bt_csip *bt_csip_new(struct gatt_db *ldb, struct gatt_db *rdb); bool bt_csip_set_sirk(struct bt_csip *csip, bool encrypt, uint8_t k[16], uint8_t size, uint8_t rank, bt_csip_encrypt_func_t func); bool bt_csip_get_sirk(struct bt_csip *csip, uint8_t *type, uint8_t k[16], uint8_t *size, uint8_t *rank); unsigned int bt_csip_ready_register(struct bt_csip *csip, bt_csip_ready_func_t func, void *user_data, bt_csip_destroy_func_t destroy); bool bt_csip_ready_unregister(struct bt_csip *csip, unsigned int id); bluez-5.87/src/shared/PaxHeaders/gatt-db.c0000644000000000000000000000005015221752714015404 xustar0020 atime=1783092685 20 ctime=1783094100 bluez-5.87/src/shared/gatt-db.c0000644000000000000000000015526515221752714015103 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/timeout.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/crypto.h" #ifndef MAX #define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif #define MAX_CHAR_DECL_VALUE_LEN 19 #define MAX_INCLUDED_VALUE_LEN 6 #define ATTRIBUTE_TIMEOUT 5000 #define HASH_UPDATE_TIMEOUT 100 static const bt_uuid_t primary_service_uuid = { .type = BT_UUID16, .value.u16 = GATT_PRIM_SVC_UUID }; static const bt_uuid_t secondary_service_uuid = { .type = BT_UUID16, .value.u16 = GATT_SND_SVC_UUID }; static const bt_uuid_t characteristic_uuid = { .type = BT_UUID16, .value.u16 = GATT_CHARAC_UUID }; static const bt_uuid_t included_service_uuid = { .type = BT_UUID16, .value.u16 = GATT_INCLUDE_UUID }; static const bt_uuid_t ext_desc_uuid = { .type = BT_UUID16, .value.u16 = GATT_CHARAC_EXT_PROPER_UUID }; static const bt_uuid_t ccc_uuid = { .type = BT_UUID16, .value.u16 = GATT_CLIENT_CHARAC_CFG_UUID }; struct gatt_db_ccc { gatt_db_read_t read_func; gatt_db_write_t write_func; gatt_db_notify_t notify_func; void *user_data; }; struct gatt_db { int ref_count; struct bt_crypto *crypto; uint8_t hash[16]; unsigned int hash_id; uint16_t last_handle; struct queue *services; struct queue *notify_list; unsigned int next_notify_id; gatt_db_authorize_cb_t authorize; void *authorize_data; struct gatt_db_ccc *ccc; }; struct notify { unsigned int id; gatt_db_attribute_cb_t service_added; gatt_db_attribute_cb_t service_removed; gatt_db_authorize_cb_t authorize_cb; gatt_db_destroy_func_t destroy; void *user_data; }; struct attribute_notify { unsigned int id; gatt_db_attribute_cb_t removed; gatt_db_destroy_func_t destroy; void *user_data; }; struct pending_read { struct gatt_db_attribute *attrib; unsigned int id; unsigned int timeout_id; gatt_db_attribute_read_t func; void *user_data; }; struct pending_write { struct gatt_db_attribute *attrib; unsigned int id; unsigned int timeout_id; gatt_db_attribute_write_t func; void *user_data; }; struct gatt_db_attribute { struct gatt_db_service *service; uint16_t handle; bt_uuid_t uuid; uint32_t permissions; uint16_t value_len; uint8_t *value; gatt_db_read_t read_func; gatt_db_write_t write_func; gatt_db_notify_t notify_func; void *user_data; unsigned int read_id; struct queue *pending_reads; unsigned int write_id; struct queue *pending_writes; unsigned int next_notify_id; struct queue *notify_list; }; struct gatt_db_service { struct gatt_db *db; bool active; bool claimed; uint16_t num_handles; struct gatt_db_attribute **attributes; }; static void set_attribute_data(struct gatt_db_attribute *attribute, gatt_db_read_t read_func, gatt_db_write_t write_func, uint32_t permissions, void *user_data) { attribute->permissions = permissions; attribute->read_func = read_func; attribute->write_func = write_func; attribute->user_data = user_data; } static void pending_read_result(struct pending_read *p, int err, const uint8_t *data, size_t length) { if (p->timeout_id > 0) timeout_remove(p->timeout_id); p->func(p->attrib, err, data, length, p->user_data); free(p); } static void pending_read_free(void *data) { struct pending_read *p = data; pending_read_result(p, -ECANCELED, NULL, 0); } static void pending_write_result(struct pending_write *p, int err) { if (p->timeout_id > 0) timeout_remove(p->timeout_id); p->func(p->attrib, err, p->user_data); free(p); } static void pending_write_free(void *data) { struct pending_write *p = data; pending_write_result(p, -ECANCELED); } static void attribute_notify_destroy(void *data) { struct attribute_notify *notify = data; if (notify->destroy) notify->destroy(notify->user_data); free(notify); } static void attribute_destroy(struct gatt_db_attribute *attribute) { /* Attribute was not initialized by user */ if (!attribute) return; queue_destroy(attribute->pending_reads, pending_read_free); queue_destroy(attribute->pending_writes, pending_write_free); queue_destroy(attribute->notify_list, attribute_notify_destroy); free(attribute->value); free(attribute); } static struct gatt_db_attribute *new_attribute(struct gatt_db_service *service, uint16_t handle, const bt_uuid_t *type, const uint8_t *val, uint16_t len) { struct gatt_db_attribute *attribute; attribute = new0(struct gatt_db_attribute, 1); attribute->service = service; attribute->handle = handle; attribute->uuid = *type; attribute->value_len = len; if (len) { attribute->value = malloc0(len); if (!attribute->value) goto failed; memcpy(attribute->value, val, len); } attribute->pending_reads = queue_new(); attribute->pending_writes = queue_new(); attribute->notify_list = queue_new(); return attribute; failed: attribute_destroy(attribute); return NULL; } struct gatt_db *gatt_db_ref(struct gatt_db *db) { if (!db) return NULL; __sync_fetch_and_add(&db->ref_count, 1); return db; } struct gatt_db *gatt_db_new(void) { struct gatt_db *db; db = new0(struct gatt_db, 1); db->crypto = bt_crypto_new(); db->services = queue_new(); db->notify_list = queue_new(); db->last_handle = 0x0000; return gatt_db_ref(db); } static void service_clone(void *data, void *user_data) { struct gatt_db_service *service = data; struct gatt_db *db = user_data; struct gatt_db_service *clone; int i; clone = new0(struct gatt_db_service, 1); clone->db = db; clone->active = service->active; clone->num_handles = service->num_handles; clone->attributes = new0(struct gatt_db_attribute *, service->num_handles); /* Clone attributes */ for (i = 0; i < service->num_handles; i++) { struct gatt_db_attribute *attr = service->attributes[i]; if (!attr) continue; /* Only clone values for characteristics declaration since that * is considered when calculating the db hash. */ if (bt_uuid_len(&attr->uuid) != 2) { clone->attributes[i] = new_attribute(clone, attr->handle, &attr->uuid, NULL, 0); continue; } /* Attribute values that are used for generating the hash needs * to be cloned as well. */ switch (attr->uuid.value.u16) { case GATT_PRIM_SVC_UUID: case GATT_SND_SVC_UUID: case GATT_INCLUDE_UUID: case GATT_CHARAC_UUID: clone->attributes[i] = new_attribute(clone, attr->handle, &attr->uuid, attr->value, attr->value_len); break; default: clone->attributes[i] = new_attribute(clone, attr->handle, &attr->uuid, NULL, 0); break; } } queue_push_tail(db->services, clone); } struct gatt_db *gatt_db_clone(struct gatt_db *db) { struct gatt_db *clone; if (!db) return NULL; clone = gatt_db_new(); if (!clone) return NULL; queue_foreach(db->services, service_clone, clone); clone->last_handle = db->last_handle; return clone; } static void notify_destroy(void *data) { struct notify *notify = data; if (notify->destroy) notify->destroy(notify->user_data); free(notify); } static bool match_notify_id(const void *a, const void *b) { const struct notify *notify = a; unsigned int id = PTR_TO_UINT(b); return notify->id == id; } struct notify_data { struct gatt_db_attribute *attr; bool added; }; static void handle_notify(void *data, void *user_data) { struct notify *notify = data; struct notify_data *notify_data = user_data; if (notify_data->added) notify->service_added(notify_data->attr, notify->user_data); else notify->service_removed(notify_data->attr, notify->user_data); } struct hash_data { struct iovec *iov; uint16_t i; }; static void gen_hash_m(struct gatt_db_attribute *attr, void *user_data) { struct hash_data *hash = user_data; uint8_t *data; size_t len; if (!attr || !attr->value) return; if (bt_uuid_len(&attr->uuid) != 2) return; switch (attr->uuid.value.u16) { case GATT_PRIM_SVC_UUID: case GATT_SND_SVC_UUID: case GATT_INCLUDE_UUID: case GATT_CHARAC_UUID: /* Allocate space for handle + type + value */ len = 2 + 2 + attr->value_len; data = malloc(2 + 2 + attr->value_len); put_le16(attr->handle, data); bt_uuid_to_le(&attr->uuid, data + 2); memcpy(data + 4, attr->value, attr->value_len); break; case GATT_CHARAC_USER_DESC_UUID: case GATT_CLIENT_CHARAC_CFG_UUID: case GATT_SERVER_CHARAC_CFG_UUID: case GATT_CHARAC_FMT_UUID: case GATT_CHARAC_AGREG_FMT_UUID: /* Allocate space for handle + type */ len = 2 + 2; data = malloc(2 + 2 + attr->value_len); put_le16(attr->handle, data); bt_uuid_to_le(&attr->uuid, data + 2); break; default: return; } hash->iov[hash->i].iov_base = data; hash->iov[hash->i].iov_len = len; hash->i++; return; } static void service_gen_hash_m(struct gatt_db_attribute *attr, void *user_data) { gatt_db_service_foreach(attr, NULL, gen_hash_m, user_data); } static bool db_hash_update(void *user_data) { struct gatt_db *db = user_data; struct hash_data hash; uint16_t i; db->hash_id = 0; if (gatt_db_isempty(db) || !db->last_handle) return false; hash.iov = new0(struct iovec, db->last_handle + 1); hash.i = 0; gatt_db_foreach_service(db, NULL, service_gen_hash_m, &hash); bt_crypto_gatt_hash(db->crypto, hash.iov, db->last_handle + 1, db->hash); for (i = 0; i < hash.i; i++) free(hash.iov[i].iov_base); free(hash.iov); return false; } static void handle_attribute_notify(void *data, void *user_data) { struct attribute_notify *notify = data; struct gatt_db_attribute *attrib = user_data; if (notify->removed) notify->removed(attrib, notify->user_data); } static void notify_attribute_changed(struct gatt_db_service *service) { int i; for (i = 0; i < service->num_handles; i++) { struct gatt_db_attribute *attr = service->attributes[i]; if (!attr) continue; queue_foreach(attr->notify_list, handle_attribute_notify, attr); } } static void notify_service_changed(struct gatt_db *db, struct gatt_db_service *service, bool added) { struct notify_data data; if (!added) notify_attribute_changed(service); if (queue_isempty(db->notify_list)) return; data.attr = service->attributes[0]; data.added = added; gatt_db_ref(db); queue_foreach(db->notify_list, handle_notify, &data); /* Trigger hash update */ if (!db->hash_id && db->crypto) db->hash_id = timeout_add(HASH_UPDATE_TIMEOUT, db_hash_update, db, NULL); gatt_db_unref(db); } static void gatt_db_service_destroy(void *data) { struct gatt_db_service *service = data; int i; if (service->active) notify_service_changed(service->db, service, false); for (i = 0; i < service->num_handles; i++) attribute_destroy(service->attributes[i]); free(service->attributes); free(service); } static void gatt_db_destroy(struct gatt_db *db) { if (!db) return; bt_crypto_unref(db->crypto); /* * Clear the notify list before clearing the services to prevent the * latter from sending service_removed events. */ queue_destroy(db->notify_list, notify_destroy); db->notify_list = NULL; if (db->hash_id) timeout_remove(db->hash_id); queue_destroy(db->services, gatt_db_service_destroy); free(db->ccc); free(db); } void gatt_db_unref(struct gatt_db *db) { if (!db) return; if (__sync_sub_and_fetch(&db->ref_count, 1)) return; gatt_db_destroy(db); } bool gatt_db_isempty(struct gatt_db *db) { if (!db) return true; return queue_isempty(db->services); } static int uuid_to_le(const bt_uuid_t *uuid, uint8_t *dst) { bt_uuid_t uuid128; if (uuid->type == BT_UUID16) { put_le16(uuid->value.u16, dst); return bt_uuid_len(uuid); } bt_uuid_to_uuid128(uuid, &uuid128); bswap_128(&uuid128.value.u128, dst); return bt_uuid_len(&uuid128); } static bool le_to_uuid(const uint8_t *src, size_t len, bt_uuid_t *uuid) { uint128_t u128; if (len == 2) { bt_uuid16_create(uuid, get_le16(src)); return true; } if (len == 4) { bt_uuid32_create(uuid, get_le32(src)); return true; } if (len != 16) return false; bswap_128(src, &u128); bt_uuid128_create(uuid, u128); return true; } static struct gatt_db_service *gatt_db_service_create(const bt_uuid_t *uuid, uint16_t handle, bool primary, uint16_t num_handles) { struct gatt_db_service *service; const bt_uuid_t *type; uint8_t value[16]; uint16_t len; if (num_handles < 1) return NULL; service = new0(struct gatt_db_service, 1); service->attributes = new0(struct gatt_db_attribute *, num_handles); if (primary) type = &primary_service_uuid; else type = &secondary_service_uuid; len = uuid_to_le(uuid, value); service->attributes[0] = new_attribute(service, handle, type, value, len); if (!service->attributes[0]) { gatt_db_service_destroy(service); return NULL; } set_attribute_data(service->attributes[0], NULL, NULL, BT_ATT_PERM_READ, NULL); return service; } bool gatt_db_remove_service(struct gatt_db *db, struct gatt_db_attribute *attrib) { struct gatt_db_service *service; if (!db || !attrib) return false; service = attrib->service; queue_remove(db->services, service); gatt_db_service_destroy(service); return true; } bool gatt_db_clear(struct gatt_db *db) { return gatt_db_clear_range(db, 1, UINT16_MAX); } static void gatt_db_service_get_handles(const struct gatt_db_service *service, uint16_t *start_handle, uint16_t *end_handle) { if (start_handle) *start_handle = service->attributes[0]->handle; if (end_handle) *end_handle = service->attributes[0]->handle + service->num_handles - 1; } struct clear_range { uint16_t start, end; }; static bool match_range(const void *a, const void *b) { const struct gatt_db_service *service = a; const struct clear_range *range = b; uint16_t svc_start, svc_end; gatt_db_service_get_handles(service, &svc_start, &svc_end); return svc_start <= range->end && svc_end >= range->start; } bool gatt_db_clear_range(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle) { struct clear_range range; if (!db || start_handle > end_handle) return false; /* Check if it is a full clear */ if (start_handle == 1 && end_handle == UINT16_MAX) { queue_remove_all(db->services, NULL, NULL, gatt_db_service_destroy); goto done; } range.start = start_handle; range.end = end_handle; queue_remove_all(db->services, match_range, &range, gatt_db_service_destroy); done: if (gatt_db_isempty(db)) db->last_handle = 0; return true; } uint8_t *gatt_db_get_hash(struct gatt_db *db) { uint8_t hash[16] = {}; if (!db || !db->crypto) return NULL; /* Generate hash if if has not been generated yet */ if (db->hash_id || !memcmp(db->hash, hash, 16)) { timeout_remove(db->hash_id); db_hash_update(db); } return db->hash; } bool gatt_db_hash_support(struct gatt_db *db) { if (!db || !db->crypto) return false; return true; } static struct gatt_db_service *find_insert_loc(struct gatt_db *db, uint16_t start, uint16_t end, struct gatt_db_service **after) { const struct queue_entry *services_entry; struct gatt_db_service *service; uint16_t cur_start, cur_end; *after = NULL; services_entry = queue_get_entries(db->services); while (services_entry) { service = services_entry->data; gatt_db_service_get_handles(service, &cur_start, &cur_end); if (start >= cur_start && start <= cur_end) return service; if (end >= cur_start && end <= cur_end) return service; if (end < cur_start) return NULL; *after = service; services_entry = services_entry->next; } return NULL; } struct gatt_db_attribute *gatt_db_insert_service(struct gatt_db *db, uint16_t handle, const bt_uuid_t *uuid, bool primary, uint16_t num_handles) { struct gatt_db_service *service, *after; after = NULL; if (!db) return NULL; if (!handle) handle = db->last_handle + 1; if (num_handles < 1 || (handle + num_handles - 1) > UINT16_MAX) return NULL; service = find_insert_loc(db, handle, handle + num_handles - 1, &after); if (service) { const bt_uuid_t *type; bt_uuid_t value; struct gatt_db_attribute *attr = service->attributes[0]; if (!attr) return NULL; if (primary) type = &primary_service_uuid; else type = &secondary_service_uuid; gatt_db_attribute_get_service_uuid(attr, &value); /* Check if service match */ if (!bt_uuid_cmp(&attr->uuid, type) && !bt_uuid_cmp(&value, uuid) && service->num_handles == num_handles && attr->handle == handle) return attr; return NULL; } service = gatt_db_service_create(uuid, handle, primary, num_handles); if (!service) return NULL; if (after) { if (!queue_push_after(db->services, after, service)) goto fail; } else if (!queue_push_head(db->services, service)) { goto fail; } service->db = db; service->attributes[0]->handle = handle; service->num_handles = num_handles; /* Fast-forward last_handle if the new service was added to the end */ db->last_handle = MAX(handle + num_handles - 1, db->last_handle); return service->attributes[0]; fail: gatt_db_service_destroy(service); return NULL; } struct gatt_db_attribute *gatt_db_add_service(struct gatt_db *db, const bt_uuid_t *uuid, bool primary, uint16_t num_handles) { return gatt_db_insert_service(db, 0, uuid, primary, num_handles); } unsigned int gatt_db_register(struct gatt_db *db, gatt_db_attribute_cb_t service_added, gatt_db_attribute_cb_t service_removed, void *user_data, gatt_db_destroy_func_t destroy) { struct notify *notify; if (!db || !(service_added || service_removed)) return 0; notify = new0(struct notify, 1); notify->service_added = service_added; notify->service_removed = service_removed; notify->destroy = destroy; notify->user_data = user_data; if (db->next_notify_id < 1) db->next_notify_id = 1; notify->id = db->next_notify_id++; if (!queue_push_tail(db->notify_list, notify)) { free(notify); return 0; } return notify->id; } bool gatt_db_unregister(struct gatt_db *db, unsigned int id) { struct notify *notify; if (!db || !id) return false; notify = queue_find(db->notify_list, match_notify_id, UINT_TO_PTR(id)); if (!notify) return false; queue_remove(db->notify_list, notify); notify_destroy(notify); return true; } bool gatt_db_set_authorize(struct gatt_db *db, gatt_db_authorize_cb_t cb, void *user_data) { if (!db) return false; db->authorize = cb; db->authorize_data = user_data; return true; } static uint16_t service_get_attribute_index(struct gatt_db_service *service, uint16_t *handle, int end_offset) { int i = 0; if (!service || !service->attributes[0] || !handle) return 0; if (*handle) { /* Check if handle is in within service range */ if (*handle < service->attributes[0]->handle) return 0; /* Return index based on given handle */ i = *handle - service->attributes[0]->handle; } else { /* Here we look for first free attribute index with given * offset. */ while (i < (service->num_handles - end_offset) && service->attributes[i]) i++; } if (i >= (service->num_handles - end_offset)) return 0; /* Set handle based on the index */ if (!(*handle)) *handle = service->attributes[0]->handle + i; return i; } static struct gatt_db_attribute * service_insert_characteristic(struct gatt_db_service *service, uint16_t handle, uint16_t value_handle, const bt_uuid_t *uuid, uint32_t permissions, uint8_t properties, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data) { struct gatt_db_attribute **chrc; uint8_t value[MAX_CHAR_DECL_VALUE_LEN]; uint16_t len = 0; int i; /* Check if handle is in within service range */ if (handle && handle <= service->attributes[0]->handle) return NULL; /* * It is not possible to allocate last handle for a Characteristic * since it would not have space for its value: * 3.3.2 Characteristic Value Declaration * The Characteristic Value declaration contains the value of the * characteristic. It is the first Attribute after the characteristic * declaration. All characteristic definitions shall have a * Characteristic Value declaration. */ if ((handle == UINT16_MAX) || (value_handle && handle && value_handle <= handle)) return NULL; i = service_get_attribute_index(service, &handle, 1); if (!i) return NULL; value[0] = properties; len += sizeof(properties); /* We set handle of characteristic value, which will be added next */ put_le16(value_handle, &value[1]); len += sizeof(uint16_t); len += uuid_to_le(uuid, &value[3]); service->attributes[i] = new_attribute(service, handle, &characteristic_uuid, value, len); if (!service->attributes[i]) return NULL; chrc = &service->attributes[i]; set_attribute_data(service->attributes[i], NULL, NULL, BT_ATT_PERM_READ, NULL); i = service_get_attribute_index(service, &value_handle, 0); if (!i) { free(*chrc); *chrc = NULL; return NULL; } service->attributes[i] = new_attribute(service, value_handle, uuid, NULL, 0); if (!service->attributes[i]) { free(*chrc); *chrc = NULL; return NULL; } /* Update handle of characteristic value_handle if it has changed */ put_le16(value_handle, &value[1]); if (!(*chrc)->value) { free(*chrc); *chrc = NULL; return NULL; } if (memcmp((*chrc)->value, value, len)) memcpy((*chrc)->value, value, len); set_attribute_data(service->attributes[i], read_func, write_func, permissions, user_data); return service->attributes[i]; } struct gatt_db_attribute * gatt_db_insert_characteristic(struct gatt_db *db, uint16_t handle, uint16_t value_handle, const bt_uuid_t *uuid, uint32_t permissions, uint8_t properties, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data) { struct gatt_db_attribute *attrib; attrib = gatt_db_get_service(db, handle); if (!attrib) return NULL; return service_insert_characteristic(attrib->service, handle, value_handle, uuid, permissions, properties, read_func, write_func, user_data); } struct gatt_db_attribute * gatt_db_service_insert_characteristic(struct gatt_db_attribute *attrib, uint16_t handle, uint16_t value_handle, const bt_uuid_t *uuid, uint32_t permissions, uint8_t properties, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data) { if (!attrib) return NULL; return service_insert_characteristic(attrib->service, handle, value_handle, uuid, permissions, properties, read_func, write_func, user_data); } struct gatt_db_attribute * gatt_db_service_add_characteristic(struct gatt_db_attribute *attrib, const bt_uuid_t *uuid, uint32_t permissions, uint8_t properties, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data) { if (!attrib) return NULL; return service_insert_characteristic(attrib->service, 0, 0, uuid, permissions, properties, read_func, write_func, user_data); } static struct gatt_db_attribute * service_insert_descriptor(struct gatt_db_service *service, uint16_t handle, const bt_uuid_t *uuid, uint32_t permissions, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data) { int i; i = service_get_attribute_index(service, &handle, 0); if (!i) return NULL; service->attributes[i] = new_attribute(service, handle, uuid, NULL, 0); if (!service->attributes[i]) return NULL; set_attribute_data(service->attributes[i], read_func, write_func, permissions, user_data); return service->attributes[i]; } struct gatt_db_attribute * gatt_db_insert_descriptor(struct gatt_db *db, uint16_t handle, const bt_uuid_t *uuid, uint32_t permissions, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data) { struct gatt_db_attribute *attrib; attrib = gatt_db_get_service(db, handle); if (!attrib) return NULL; return service_insert_descriptor(attrib->service, handle, uuid, permissions, read_func, write_func, user_data); } struct gatt_db_attribute * gatt_db_service_insert_descriptor(struct gatt_db_attribute *attrib, uint16_t handle, const bt_uuid_t *uuid, uint32_t permissions, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data) { if (!attrib) return NULL; return service_insert_descriptor(attrib->service, handle, uuid, permissions, read_func, write_func, user_data); } struct gatt_db_attribute * gatt_db_service_add_descriptor(struct gatt_db_attribute *attrib, const bt_uuid_t *uuid, uint32_t permissions, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data) { if (!attrib) return NULL; return service_insert_descriptor(attrib->service, 0, uuid, permissions, read_func, write_func, user_data); } static void find_ccc_value(struct gatt_db_attribute *attrib, void *user_data) { uint16_t *handle = user_data; gatt_db_attribute_get_char_data(attrib, NULL, handle, NULL, NULL, NULL); } struct gatt_db_attribute * gatt_db_service_add_ccc(struct gatt_db_attribute *attrib, uint32_t permissions) { struct gatt_db *db; struct gatt_db_attribute *ccc; struct gatt_db_attribute *value; uint16_t handle = 0; if (!attrib || !permissions) return NULL; db = attrib->service->db; if (!db->ccc) return NULL; /* Locate value handle */ gatt_db_service_foreach_char(attrib, find_ccc_value, &handle); if (!handle) return NULL; value = gatt_db_get_attribute(db, handle); if (!value || value->notify_func) return NULL; ccc = service_insert_descriptor(attrib->service, 0, &ccc_uuid, permissions, db->ccc->read_func, db->ccc->write_func, db->ccc->user_data); if (!ccc) return ccc; gatt_db_attribute_set_fixed_length(ccc, 2); ccc->notify_func = db->ccc->notify_func; value->notify_func = db->ccc->notify_func; return ccc; } static void ccc_custom_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { struct gatt_db *db = attrib->service->db; db->ccc->read_func(attrib, id, offset, opcode, att, db->ccc->user_data); } static void custom_write_result(struct gatt_db_attribute *attr, int err, void *user_data) { int *result = user_data; *result = err; } static void ccc_custom_write(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, void *user_data) { struct gatt_db_ccc *data = user_data; struct gatt_db *db = attrib->service->db; struct pending_write *p; int err = 0; /* Create another pending write to handle results from custom write * function. */ p = new0(struct pending_write, 1); p->attrib = attrib; p->id = ++attrib->write_id; p->func = custom_write_result; p->user_data = &err; queue_push_tail(attrib->pending_writes, p); /* Call custom write function first */ data->write_func(attrib, p->id, offset, value, len, opcode, att, data->user_data); if (err) { gatt_db_attribute_write_result(attrib, id, err); return; } /* If custom write function did not return error proceed to call the * default CCC write function. */ db->ccc->write_func(attrib, id, offset, value, len, opcode, att, db->ccc->user_data); } struct gatt_db_attribute * gatt_db_service_add_ccc_custom(struct gatt_db_attribute *attrib, uint32_t permissions, gatt_db_write_t write_func, void *user_data) { struct gatt_db *db; struct gatt_db_attribute *ccc; struct gatt_db_attribute *value; uint16_t handle = 0; struct gatt_db_ccc *data; if (!attrib || !permissions) return NULL; db = attrib->service->db; if (!db->ccc) return NULL; /* Locate value handle */ gatt_db_service_foreach_char(attrib, find_ccc_value, &handle); if (!handle) return NULL; value = gatt_db_get_attribute(db, handle); if (!value || value->notify_func) return NULL; data = new0(struct gatt_db_ccc, 1); data->write_func = write_func; data->user_data = user_data; ccc = service_insert_descriptor(attrib->service, 0, &ccc_uuid, permissions, ccc_custom_read, ccc_custom_write, data); if (!ccc) { free(data); return NULL; } gatt_db_attribute_set_fixed_length(ccc, 2); ccc->notify_func = db->ccc->notify_func; value->notify_func = db->ccc->notify_func; return ccc; } void gatt_db_ccc_register(struct gatt_db *db, gatt_db_read_t read_func, gatt_db_write_t write_func, gatt_db_notify_t notify_func, void *user_data) { if (!db) return; if (!db->ccc) db->ccc = new0(struct gatt_db_ccc, 1); db->ccc->read_func = read_func; db->ccc->write_func = write_func; db->ccc->notify_func = notify_func; db->ccc->user_data = user_data; } static struct gatt_db_attribute * service_insert_included(struct gatt_db_service *service, uint16_t handle, struct gatt_db_attribute *include) { struct gatt_db_service *included; uint8_t value[MAX_INCLUDED_VALUE_LEN]; uint16_t included_handle, len = 0; int index; if (!include || !include->value || !include->service || !service) return NULL; included = include->service; /* Adjust include to point to the first attribute */ if (include != included->attributes[0]) include = included->attributes[0]; included_handle = include->handle; put_le16(included_handle, &value[len]); len += sizeof(uint16_t); put_le16(included_handle + included->num_handles - 1, &value[len]); len += sizeof(uint16_t); /* The Service UUID shall only be present when the UUID is a 16-bit * Bluetooth UUID. Vol 2. Part G. 3.2 */ if (include->value_len == sizeof(uint16_t)) { memcpy(&value[len], include->value, include->value_len); len += include->value_len; } index = service_get_attribute_index(service, &handle, 0); if (!index) return NULL; service->attributes[index] = new_attribute(service, handle, &included_service_uuid, value, len); if (!service->attributes[index]) return NULL; /* The Attribute Permissions shall be read only and not require * authentication or authorization. Vol 2. Part G. 3.2 * * TODO handle permissions */ set_attribute_data(service->attributes[index], NULL, NULL, BT_ATT_PERM_READ, NULL); return service->attributes[index]; } struct gatt_db_attribute * gatt_db_service_add_included(struct gatt_db_attribute *attrib, struct gatt_db_attribute *include) { if (!attrib || !include) return NULL; return service_insert_included(attrib->service, 0, include); } struct gatt_db_attribute * gatt_db_service_insert_included(struct gatt_db_attribute *attrib, uint16_t handle, struct gatt_db_attribute *include) { if (!attrib || !handle || !include) return NULL; return service_insert_included(attrib->service, handle, include); } struct gatt_db_attribute * gatt_db_insert_included(struct gatt_db *db, uint16_t handle, struct gatt_db_attribute *include) { struct gatt_db_attribute *attrib; attrib = gatt_db_get_service(db, handle); if (!attrib) return NULL; return service_insert_included(attrib->service, handle, include); } bool gatt_db_service_set_active(struct gatt_db_attribute *attrib, bool active) { struct gatt_db_service *service; if (!attrib) return false; service = attrib->service; if (service->active == active) return true; service->active = active; notify_service_changed(service->db, service, active); return true; } bool gatt_db_service_get_active(struct gatt_db_attribute *attrib) { if (!attrib) return false; return attrib->service->active; } bool gatt_db_service_set_claimed(struct gatt_db_attribute *attrib, bool claimed) { if (!attrib) return false; attrib->service->claimed = claimed; return true; } bool gatt_db_service_get_claimed(struct gatt_db_attribute *attrib) { if (!attrib) return false; return attrib->service->claimed; } static void read_by_group_type(struct gatt_db_attribute *attribute, void *user_data) { struct queue *queue = user_data; queue_push_tail(queue, attribute); } void gatt_db_read_by_group_type(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle, const bt_uuid_t type, struct queue *queue) { gatt_db_foreach_service_in_range(db, &type, read_by_group_type, queue, start_handle, end_handle); } struct find_by_type_value_data { gatt_db_attribute_cb_t func; void *user_data; const void *value; size_t value_len; unsigned int num_of_res; }; static void find_by_type(struct gatt_db_attribute *attribute, void *user_data) { struct find_by_type_value_data *search_data = user_data; if (!attribute) return; /* TODO: fix for read-callback based attributes */ if (search_data->value) { if (search_data->value_len != attribute->value_len) return; if (!attribute->value) return; if (memcmp(attribute->value, search_data->value, search_data->value_len)) return; } search_data->num_of_res++; search_data->func(attribute, search_data->user_data); } unsigned int gatt_db_find_by_type(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle, const bt_uuid_t *type, gatt_db_attribute_cb_t func, void *user_data) { struct find_by_type_value_data data; memset(&data, 0, sizeof(data)); data.func = func; data.user_data = user_data; gatt_db_foreach_in_range(db, type, find_by_type, &data, start_handle, end_handle); return data.num_of_res; } unsigned int gatt_db_find_by_type_value(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle, const bt_uuid_t *type, const void *value, size_t value_len, gatt_db_attribute_cb_t func, void *user_data) { struct find_by_type_value_data data; memset(&data, 0, sizeof(data)); data.func = func; data.user_data = user_data; data.value = value; data.value_len = value_len; gatt_db_foreach_in_range(db, type, find_by_type, &data, start_handle, end_handle); return data.num_of_res; } static void read_by_type(struct gatt_db_attribute *attribute, void *user_data) { struct queue *queue = user_data; queue_push_tail(queue, attribute); } void gatt_db_read_by_type(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle, const bt_uuid_t type, struct queue *queue) { gatt_db_foreach_in_range(db, &type, read_by_type, queue, start_handle, end_handle); } static void find_information(struct gatt_db_attribute *attribute, void *user_data) { struct queue *queue = user_data; queue_push_tail(queue, attribute); } void gatt_db_find_information(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle, struct queue *queue) { gatt_db_foreach_in_range(db, NULL, find_information, queue, start_handle, end_handle); } void gatt_db_foreach_service(struct gatt_db *db, const bt_uuid_t *uuid, gatt_db_attribute_cb_t func, void *user_data) { gatt_db_foreach_service_in_range(db, uuid, func, user_data, 0x0001, 0xffff); } struct foreach_data { gatt_db_attribute_cb_t func; const bt_uuid_t *uuid; void *user_data; uint16_t start, end; bool attr; }; static void foreach_service_in_range(void *data, void *user_data) { struct gatt_db_service *service = data; struct gatt_db_attribute *attribute = service->attributes[0]; struct foreach_data *foreach_data = user_data; bt_uuid_t uuid; if (foreach_data->uuid) { gatt_db_attribute_get_service_uuid(attribute, &uuid); if (bt_uuid_cmp(&uuid, foreach_data->uuid)) { /* Compare with attribute UUID in case it is a lookup * by group type. */ if (bt_uuid_cmp(&attribute->uuid, foreach_data->uuid)) return; } } foreach_data->func(service->attributes[0], foreach_data->user_data); } static void foreach_in_range(void *data, void *user_data) { struct gatt_db_service *service = data; struct foreach_data *foreach_data = user_data; uint16_t svc_start, svc_end; int i; if (!service->active) return; gatt_db_service_get_handles(service, &svc_start, &svc_end); /* Check if service is within requested range */ if (svc_start > foreach_data->end || svc_end < foreach_data->start) return; if (!foreach_data->attr) { if (svc_start < foreach_data->start) return; return foreach_service_in_range(data, user_data); } for (i = 0; i < service->num_handles; i++) { struct gatt_db_attribute *attribute = service->attributes[i]; if (!attribute) continue; if (attribute->handle < foreach_data->start) continue; if (attribute->handle > foreach_data->end) return; if (foreach_data->uuid && bt_uuid_cmp(foreach_data->uuid, &attribute->uuid)) continue; foreach_data->func(attribute, foreach_data->user_data); } } void gatt_db_foreach_service_in_range(struct gatt_db *db, const bt_uuid_t *uuid, gatt_db_attribute_cb_t func, void *user_data, uint16_t start_handle, uint16_t end_handle) { struct foreach_data data; if (!db || !func || start_handle > end_handle) return; data.func = func; data.uuid = uuid; data.user_data = user_data; data.start = start_handle; data.end = end_handle; data.attr = false; queue_foreach(db->services, foreach_in_range, &data); } void gatt_db_foreach_in_range(struct gatt_db *db, const bt_uuid_t *uuid, gatt_db_attribute_cb_t func, void *user_data, uint16_t start_handle, uint16_t end_handle) { struct foreach_data data; if (!db || !func || start_handle > end_handle) return; data.func = func; data.uuid = uuid; data.user_data = user_data; data.start = start_handle; data.end = end_handle; data.attr = true; queue_foreach(db->services, foreach_in_range, &data); } void gatt_db_service_foreach(struct gatt_db_attribute *attrib, const bt_uuid_t *uuid, gatt_db_attribute_cb_t func, void *user_data) { struct gatt_db_service *service; struct gatt_db_attribute *attr; uint16_t i; if (!attrib || !func) return; service = attrib->service; for (i = 0; i < service->num_handles; i++) { attr = service->attributes[i]; if (!attr) continue; if (uuid && bt_uuid_cmp(uuid, &attr->uuid)) continue; func(attr, user_data); } } void gatt_db_service_foreach_char(struct gatt_db_attribute *attrib, gatt_db_attribute_cb_t func, void *user_data) { gatt_db_service_foreach(attrib, &characteristic_uuid, func, user_data); } static int gatt_db_attribute_get_index(const struct gatt_db_attribute *attrib) { struct gatt_db_service *service; int index; if (!attrib) return -1; service = attrib->service; for (index = 0; index < service->num_handles; index++) { if (service->attributes[index] == attrib) return index; } return -1; } struct gatt_db_attribute * gatt_db_attribute_get_value(struct gatt_db_attribute *attrib) { struct gatt_db_service *service; int index; if (!attrib) return NULL; index = gatt_db_attribute_get_index(attrib); if (index <= 0) return NULL; service = attrib->service; if (!bt_uuid_cmp(&characteristic_uuid, &attrib->uuid)) return service->attributes[index + 1]; else if (service->attributes[index - 1] == NULL) return NULL; else if (!bt_uuid_cmp(&characteristic_uuid, &service->attributes[index - 1]->uuid)) return service->attributes[index]; return gatt_db_attribute_get_value(service->attributes[index - 1]); } void gatt_db_service_foreach_desc(struct gatt_db_attribute *attrib, gatt_db_attribute_cb_t func, void *user_data) { struct gatt_db_service *service; struct gatt_db_attribute *attr; int index; uint16_t i; if (!attrib || !func) return; attrib = gatt_db_attribute_get_value(attrib); if (!attrib) return; index = gatt_db_attribute_get_index(attrib); if (index < 0) return; service = attrib->service; /* Start from the attribute following the value handle */ for (i = index + 1; i < service->num_handles; i++) { attr = service->attributes[i]; if (!attr) continue; /* Return if we reached the end of this characteristic */ if (!bt_uuid_cmp(&characteristic_uuid, &attr->uuid) || !bt_uuid_cmp(&included_service_uuid, &attr->uuid)) return; func(attr, user_data); } } void gatt_db_service_foreach_incl(struct gatt_db_attribute *attrib, gatt_db_attribute_cb_t func, void *user_data) { gatt_db_service_foreach(attrib, &included_service_uuid, func, user_data); } static bool find_service_for_handle(const void *data, const void *user_data) { const struct gatt_db_service *service = data; uint16_t handle = PTR_TO_UINT(user_data); uint16_t start, end; gatt_db_service_get_handles(service, &start, &end); return (start <= handle) && (handle <= end); } struct gatt_db_attribute *gatt_db_get_service(struct gatt_db *db, uint16_t handle) { struct gatt_db_service *service; if (!db || !handle) return NULL; service = queue_find(db->services, find_service_for_handle, UINT_TO_PTR(handle)); if (!service) return NULL; return service->attributes[0]; } struct gatt_db_attribute *gatt_db_get_attribute(struct gatt_db *db, uint16_t handle) { struct gatt_db_attribute *attrib; struct gatt_db_service *service; int i; attrib = gatt_db_get_service(db, handle); if (!attrib) return NULL; service = attrib->service; for (i = 0; i < service->num_handles; i++) { if (!service->attributes[i]) continue; if (service->attributes[i]->handle == handle) return service->attributes[i]; } return NULL; } static bool find_service_with_uuid(const void *data, const void *user_data) { const struct gatt_db_service *service = data; const bt_uuid_t *uuid = user_data; bt_uuid_t svc_uuid; gatt_db_attribute_get_service_uuid(service->attributes[0], &svc_uuid); return bt_uuid_cmp(uuid, &svc_uuid) == 0; } struct gatt_db_attribute *gatt_db_get_service_with_uuid(struct gatt_db *db, const bt_uuid_t *uuid) { struct gatt_db_service *service; if (!db || !uuid) return NULL; service = queue_find(db->services, find_service_with_uuid, uuid); if (!service) return NULL; return service->attributes[0]; } const bt_uuid_t *gatt_db_attribute_get_type( const struct gatt_db_attribute *attrib) { if (!attrib) return NULL; return &attrib->uuid; } uint16_t gatt_db_attribute_get_handle(const struct gatt_db_attribute *attrib) { if (!attrib) return 0; return attrib->handle; } struct gatt_db_attribute * gatt_db_attribute_get_service(const struct gatt_db_attribute *attrib) { if (!attrib) return NULL; return attrib->service->attributes[0]; } bool gatt_db_attribute_get_service_uuid(const struct gatt_db_attribute *attrib, bt_uuid_t *uuid) { struct gatt_db_service *service; if (!attrib || !uuid) return false; service = attrib->service; if (service->attributes[0]->value_len == sizeof(uint16_t)) { uint16_t value; value = get_le16(service->attributes[0]->value); bt_uuid16_create(uuid, value); return true; } if (service->attributes[0]->value_len == sizeof(uint128_t)) { uint128_t value; bswap_128(service->attributes[0]->value, &value); bt_uuid128_create(uuid, value); return true; } return false; } bool gatt_db_attribute_get_service_handles( const struct gatt_db_attribute *attrib, uint16_t *start_handle, uint16_t *end_handle) { struct gatt_db_service *service; if (!attrib) return false; service = attrib->service; gatt_db_service_get_handles(service, start_handle, end_handle); return true; } bool gatt_db_attribute_get_service_data(const struct gatt_db_attribute *attrib, uint16_t *start_handle, uint16_t *end_handle, bool *primary, bt_uuid_t *uuid) { struct gatt_db_service *service; struct gatt_db_attribute *decl; if (!attrib) return false; service = attrib->service; decl = service->attributes[0]; gatt_db_service_get_handles(service, start_handle, end_handle); if (primary) *primary = bt_uuid_cmp(&decl->uuid, &secondary_service_uuid); if (!uuid) return true; /* * The service declaration attribute value is the 16 or 128 bit service * UUID. */ return le_to_uuid(decl->value, decl->value_len, uuid); } static void read_ext_prop_value(struct gatt_db_attribute *attrib, int err, const uint8_t *value, size_t length, void *user_data) { uint16_t *ext_prop = user_data; if (err || (length != sizeof(uint16_t))) return; *ext_prop = (uint16_t) value[0]; } static void read_ext_prop(struct gatt_db_attribute *attrib, void *user_data) { uint16_t *ext_prop = user_data; /* * If ext_prop is set that means extended properties descriptor * has been already found */ if (*ext_prop != 0) return; if (bt_uuid_cmp(&ext_desc_uuid, &attrib->uuid)) return; gatt_db_attribute_read(attrib, 0, BT_ATT_OP_READ_REQ, NULL, read_ext_prop_value, ext_prop); } static uint8_t get_char_extended_prop(const struct gatt_db_attribute *attrib) { uint16_t ext_prop; if (!attrib) return 0; if (bt_uuid_cmp(&characteristic_uuid, &attrib->uuid)) return 0; /* Check properties first */ if (!(attrib->value[0] & BT_GATT_CHRC_PROP_EXT_PROP)) return 0; ext_prop = 0; /* * Cast needed for foreach function. We do not change attrib during * this call */ gatt_db_service_foreach_desc((struct gatt_db_attribute *) attrib, read_ext_prop, &ext_prop); return ext_prop; } bool gatt_db_attribute_get_char_data(const struct gatt_db_attribute *attrib, uint16_t *handle, uint16_t *value_handle, uint8_t *properties, uint16_t *ext_prop, bt_uuid_t *uuid) { if (!attrib) return false; if (bt_uuid_cmp(&characteristic_uuid, &attrib->uuid)) { int index; /* Check if Characteristic Value was passed instead */ index = gatt_db_attribute_get_index(attrib); if (index <= 0) return false; attrib = attrib->service->attributes[index - 1]; if (bt_uuid_cmp(&characteristic_uuid, &attrib->uuid)) return false; } /* * Characteristic declaration value: * 1 octet: Characteristic properties * 2 octets: Characteristic value handle * 2 or 16 octets: characteristic UUID */ if (!attrib->value || (attrib->value_len != 5 && attrib->value_len != 19)) return false; if (handle) *handle = attrib->handle; if (properties) *properties = attrib->value[0]; if (ext_prop) *ext_prop = get_char_extended_prop(attrib); if (value_handle) *value_handle = get_le16(attrib->value + 1); if (!uuid) return true; return le_to_uuid(attrib->value + 3, attrib->value_len - 3, uuid); } bool gatt_db_attribute_get_incl_data(const struct gatt_db_attribute *attrib, uint16_t *handle, uint16_t *start_handle, uint16_t *end_handle) { if (!attrib) return false; if (bt_uuid_cmp(&included_service_uuid, &attrib->uuid)) return false; /* * Include definition value: * 2 octets: start handle of included service * 2 octets: end handle of included service * optional 2 octets: 16-bit Bluetooth UUID */ if (!attrib->value || attrib->value_len < 4 || attrib->value_len > 6) return false; /* * We only return the handles since the UUID can be easily obtained * from the corresponding attribute. */ if (handle) *handle = attrib->handle; if (start_handle) *start_handle = get_le16(attrib->value); if (end_handle) *end_handle = get_le16(attrib->value + 2); return true; } uint32_t gatt_db_attribute_get_permissions(const struct gatt_db_attribute *attrib) { if (!attrib) return 0; return attrib->permissions; } static bool read_timeout(void *user_data) { struct pending_read *p = user_data; p->timeout_id = 0; queue_remove(p->attrib->pending_reads, p); pending_read_result(p, -ETIMEDOUT, NULL, 0); return false; } static uint8_t attribute_authorize(struct gatt_db_attribute *attrib, uint8_t opcode, struct bt_att *att) { struct gatt_db *db = attrib->service->db; if (!db->authorize) return 0; return db->authorize(attrib, opcode, att, db->authorize_data); } bool gatt_db_attribute_set_fixed_length(struct gatt_db_attribute *attrib, uint16_t len) { struct gatt_db_service *service; if (!attrib) return false; service = attrib->service; /* Don't allow overwriting length of service attribute */ if (attrib->service->attributes[0] == attrib) return false; /* If attribute is a characteristic declaration adjust to its value */ if (!bt_uuid_cmp(&characteristic_uuid, &attrib->uuid)) { int i; /* Start from the attribute following the value handle */ for (i = 0; i < service->num_handles; i++) { if (service->attributes[i] == attrib) { attrib = service->attributes[i + 1]; break; } } } attrib->value_len = len; return true; } bool gatt_db_attribute_read(struct gatt_db_attribute *attrib, uint16_t offset, uint8_t opcode, struct bt_att *att, gatt_db_attribute_read_t func, void *user_data) { uint8_t *value; if (!attrib || !func) return false; /* Check boundaries if value_len is set */ if (attrib->value_len && offset > attrib->value_len) { func(attrib, BT_ATT_ERROR_INVALID_OFFSET, NULL, 0, user_data); return true; } if (attrib->read_func) { struct pending_read *p; uint8_t err; err = attribute_authorize(attrib, opcode, att); if (err) { func(attrib, err, NULL, 0, user_data); return true; } p = new0(struct pending_read, 1); p->attrib = attrib; p->id = ++attrib->read_id; p->timeout_id = timeout_add(ATTRIBUTE_TIMEOUT, read_timeout, p, NULL); p->func = func; p->user_data = user_data; queue_push_tail(attrib->pending_reads, p); attrib->read_func(attrib, p->id, offset, opcode, att, attrib->user_data); return true; } /* Guard against invalid access if offset equals to value length */ value = offset == attrib->value_len ? NULL : &attrib->value[offset]; func(attrib, 0, value, attrib->value_len - offset, user_data); return true; } static bool find_pending(const void *a, const void *b) { const struct pending_read *p = a; unsigned int id = PTR_TO_UINT(b); return p->id == id; } bool gatt_db_attribute_read_result(struct gatt_db_attribute *attrib, unsigned int id, int err, const uint8_t *value, size_t length) { struct pending_read *p; if (!attrib || !id) return false; p = queue_remove_if(attrib->pending_reads, find_pending, UINT_TO_PTR(id)); if (!p) return false; pending_read_result(p, err, value, length); return true; } static bool write_timeout(void *user_data) { struct pending_write *p = user_data; p->timeout_id = 0; queue_remove(p->attrib->pending_writes, p); pending_write_result(p, -ETIMEDOUT); return false; } bool gatt_db_attribute_write(struct gatt_db_attribute *attrib, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, gatt_db_attribute_write_t func, void *user_data) { uint8_t err = 0; if (!attrib || (!func && attrib->write_func)) return false; if (attrib->write_func) { struct pending_write *p; /* Check boundaries if value_len is set */ if (attrib->value_len) { if (offset > attrib->value_len) { err = BT_ATT_ERROR_INVALID_OFFSET; goto done; } if (offset + len > attrib->value_len) { err = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; goto done; } } err = attribute_authorize(attrib, opcode, att); if (err) goto done; p = new0(struct pending_write, 1); p->attrib = attrib; p->id = ++attrib->write_id; p->timeout_id = timeout_add(ATTRIBUTE_TIMEOUT, write_timeout, p, NULL); p->func = func; p->user_data = user_data; queue_push_tail(attrib->pending_writes, p); attrib->write_func(attrib, p->id, offset, value, len, opcode, att, attrib->user_data); return true; } /* Nothing to write just skip */ if (len == 0) goto done; /* For values stored in db allocate on demand */ if (!attrib->value || offset >= attrib->value_len || len > (unsigned) (attrib->value_len - offset)) { void *buf; buf = realloc(attrib->value, len + offset); if (!buf) return false; attrib->value = buf; /* Init data in the first allocation */ if (!attrib->value_len) memset(attrib->value, 0, offset); attrib->value_len = len + offset; } memcpy(&attrib->value[offset], value, len); done: if (func) func(attrib, err, user_data); return true; } bool gatt_db_attribute_write_result(struct gatt_db_attribute *attrib, unsigned int id, int err) { struct pending_write *p; if (!attrib || !id) return false; p = queue_remove_if(attrib->pending_writes, find_pending, UINT_TO_PTR(id)); if (!p) return false; pending_write_result(p, err); return true; } static void find_ccc(struct gatt_db_attribute *attrib, void *user_data) { struct gatt_db_attribute **ccc = user_data; if (*ccc) return; if (bt_uuid_cmp(&ccc_uuid, &attrib->uuid)) return; *ccc = attrib; } struct gatt_db_attribute * gatt_db_attribute_get_ccc(struct gatt_db_attribute *attrib) { struct gatt_db_attribute *ccc = NULL; if (!attrib) return NULL; gatt_db_service_foreach_desc(attrib, find_ccc, &ccc); return ccc; } bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib, const uint8_t *value, size_t len, struct bt_att *att) { struct gatt_db_attribute *ccc; struct gatt_db *db; void *notify_user_data; if (!attrib || !attrib->notify_func) return false; attrib = gatt_db_attribute_get_value(attrib); if (!attrib) return false; ccc = gatt_db_attribute_get_ccc(attrib); if (!ccc) return false; /* For custom CCC descriptors, use the database user_data for * notify_func. For regular CCC descriptors, use the CCC's user_data. */ db = attrib->service->db; if (ccc->write_func == ccc_custom_write && db && db->ccc) notify_user_data = db->ccc->user_data; else notify_user_data = ccc->user_data; attrib->notify_func(attrib, ccc, value, len, att, notify_user_data); return true; } bool gatt_db_attribute_reset(struct gatt_db_attribute *attrib) { if (!attrib) return false; if (!attrib->value || !attrib->value_len) return true; free(attrib->value); attrib->value = NULL; attrib->value_len = 0; return true; } void *gatt_db_attribute_get_user_data(struct gatt_db_attribute *attrib) { if (!attrib) return NULL; return attrib->user_data; } static bool match_attribute_notify_id(const void *a, const void *b) { const struct attribute_notify *notify = a; unsigned int id = PTR_TO_UINT(b); return notify->id == id; } unsigned int gatt_db_attribute_register(struct gatt_db_attribute *attrib, gatt_db_attribute_cb_t removed, void *user_data, gatt_db_destroy_func_t destroy) { struct attribute_notify *notify; if (!attrib || !removed) return 0; notify = new0(struct attribute_notify, 1); notify->removed = removed; notify->destroy = destroy; notify->user_data = user_data; if (attrib->next_notify_id < 1) attrib->next_notify_id = 1; notify->id = attrib->next_notify_id++; if (!queue_push_tail(attrib->notify_list, notify)) { free(notify); return 0; } return notify->id; } bool gatt_db_attribute_unregister(struct gatt_db_attribute *attrib, unsigned int id) { struct attribute_notify *notify; if (!attrib || !id) return false; notify = queue_find(attrib->notify_list, match_attribute_notify_id, UINT_TO_PTR(id)); if (!notify) return false; queue_remove(attrib->notify_list, notify); attribute_notify_destroy(notify); return true; } bluez-5.87/src/shared/PaxHeaders/att.h0000644000000000000000000000005015200607060014645 xustar0020 atime=1783092683 20 ctime=1783094099 bluez-5.87/src/shared/att.h0000644000000000000000000001062615200607060014333 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Google Inc. * * */ #include #include #include "src/shared/att-types.h" #define BT_ATT_DEBUG 0x00 #define BT_ATT_DEBUG_VERBOSE 0x01 #define BT_ATT_DEBUG_HEXDUMP 0x02 struct bt_att; struct bt_att_chan; struct bt_att *bt_att_new(int fd, bool ext_signed); struct bt_att *bt_att_ref(struct bt_att *att); void bt_att_unref(struct bt_att *att); bool bt_att_set_close_on_unref(struct bt_att *att, bool do_close); int bt_att_get_fd(struct bt_att *att); int bt_att_attach_fd(struct bt_att *att, int fd); int bt_att_get_channels(struct bt_att *att); typedef void (*bt_att_response_func_t)(uint8_t opcode, const void *pdu, uint16_t length, void *user_data); typedef void (*bt_att_notify_func_t)(struct bt_att_chan *chan, uint16_t mtu, uint8_t opcode, const void *pdu, uint16_t length, void *user_data); typedef void (*bt_att_destroy_func_t)(void *user_data); typedef void (*bt_att_debug_func_t)(const char *str, void *user_data); typedef void (*bt_att_timeout_func_t)(unsigned int id, uint8_t opcode, void *user_data); typedef void (*bt_att_disconnect_func_t)(int err, void *user_data); typedef void (*bt_att_exchange_func_t)(uint16_t mtu, void *user_data); typedef bool (*bt_att_counter_func_t)(uint32_t *sign_cnt, void *user_data); /* DB sync callback - notifies upper layer of DB_OUT_OF_SYNC error */ typedef void (*bt_att_db_sync_func_t)(const struct bt_att_pdu_error_rsp *error, const void *req_pdu, uint16_t req_len, unsigned int att_id, void *user_data); bool bt_att_set_debug(struct bt_att *att, uint8_t level, bt_att_debug_func_t callback, void *user_data, bt_att_destroy_func_t destroy); uint16_t bt_att_get_mtu(struct bt_att *att); bool bt_att_set_mtu(struct bt_att *att, uint16_t mtu); uint8_t bt_att_get_link_type(struct bt_att *att); bool bt_att_set_timeout_cb(struct bt_att *att, bt_att_timeout_func_t callback, void *user_data, bt_att_destroy_func_t destroy); bool bt_att_set_db_sync_cb(struct bt_att *att, bt_att_db_sync_func_t callback, void *user_data, bt_att_destroy_func_t destroy); unsigned int bt_att_send(struct bt_att *att, uint8_t opcode, const void *pdu, uint16_t length, bt_att_response_func_t callback, void *user_data, bt_att_destroy_func_t destroy); int bt_att_resend(struct bt_att *att, unsigned int id, uint8_t opcode, const void *pdu, uint16_t length, bt_att_response_func_t callback, void *user_data, bt_att_destroy_func_t destroy); unsigned int bt_att_chan_send(struct bt_att_chan *chan, uint8_t opcode, const void *pdu, uint16_t len, bt_att_response_func_t callback, void *user_data, bt_att_destroy_func_t destroy); #define bt_att_chan_send_rsp(chan, opcode, pdu, len) \ bt_att_chan_send(chan, opcode, pdu, len, NULL, NULL, NULL) bool bt_att_chan_cancel(struct bt_att_chan *chan, unsigned int id); bool bt_att_cancel(struct bt_att *att, unsigned int id); bool bt_att_cancel_all(struct bt_att *att); int bt_att_chan_send_error_rsp(struct bt_att_chan *chan, uint8_t opcode, uint16_t handle, int error); unsigned int bt_att_register(struct bt_att *att, uint8_t opcode, bt_att_notify_func_t callback, void *user_data, bt_att_destroy_func_t destroy); bool bt_att_unregister(struct bt_att *att, unsigned int id); unsigned int bt_att_register_disconnect(struct bt_att *att, bt_att_disconnect_func_t callback, void *user_data, bt_att_destroy_func_t destroy); bool bt_att_unregister_disconnect(struct bt_att *att, unsigned int id); unsigned int bt_att_register_exchange(struct bt_att *att, bt_att_exchange_func_t callback, void *user_data, bt_att_destroy_func_t destroy); bool bt_att_unregister_exchange(struct bt_att *att, unsigned int id); bool bt_att_unregister_all(struct bt_att *att); int bt_att_get_security(struct bt_att *att, uint8_t *enc_size); bool bt_att_set_security(struct bt_att *att, int level); void bt_att_set_enc_key_size(struct bt_att *att, uint8_t enc_size); bool bt_att_set_local_key(struct bt_att *att, uint8_t sign_key[16], bt_att_counter_func_t func, void *user_data); bool bt_att_set_remote_key(struct bt_att *att, uint8_t sign_key[16], bt_att_counter_func_t func, void *user_data); bool bt_att_has_crypto(struct bt_att *att); bool bt_att_set_retry(struct bt_att *att, unsigned int id, bool retry); bluez-5.87/src/shared/PaxHeaders/log.c0000644000000000000000000000005015063045254014641 xustar0020 atime=1783092929 20 ctime=1783094100 bluez-5.87/src/shared/log.c0000644000000000000000000000571715063045254014334 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "src/shared/util.h" #include "src/shared/log.h" struct log_hdr { uint16_t opcode; uint16_t index; uint16_t len; uint8_t priority; uint8_t ident_len; } __attribute__((packed)); struct log_l2cap_hdr { uint16_t cid; uint16_t psm; } __attribute__((packed)); static int log_fd = -1; int bt_log_sendmsg(uint16_t index, const char *label, int level, struct iovec *io, size_t io_len) { struct log_hdr hdr; struct msghdr msg; struct iovec iov[5]; size_t i; int err; if (io_len > 3) return -EMSGSIZE; log_fd = bt_log_open(); if (log_fd < 0) return log_fd; hdr.opcode = cpu_to_le16(0x0000); hdr.index = cpu_to_le16(index); hdr.ident_len = strlen(label) + 1; hdr.len = cpu_to_le16(2 + hdr.ident_len); hdr.priority = level; iov[0].iov_base = &hdr; iov[0].iov_len = sizeof(hdr); iov[1].iov_base = (void *) label; iov[1].iov_len = hdr.ident_len; memset(&msg, 0, sizeof(msg)); msg.msg_iov = iov; msg.msg_iovlen = 2; for (i = 0; i < io_len; i++) { iov[i + 2] = io[i]; hdr.len += io[i].iov_len; msg.msg_iovlen++; } err = sendmsg(log_fd, &msg, 0); if (err < 0) { err = -errno; close(log_fd); log_fd = -1; } return err; } int bt_log_open(void) { struct sockaddr_hci addr; int fd; static int err; if (err < 0) return err; if (log_fd >= 0) return log_fd; fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); if (fd < 0) { err = -errno; return -errno; } memset(&addr, 0, sizeof(addr)); addr.hci_family = AF_BLUETOOTH; addr.hci_dev = HCI_DEV_NONE; addr.hci_channel = HCI_CHANNEL_LOGGING; err = bind(fd, (struct sockaddr *) &addr, sizeof(addr)); if (err < 0) { err = -errno; close(fd); return err; } log_fd = fd; return fd; } int bt_log_vprintf(uint16_t index, const char *label, int level, const char *format, va_list ap) { struct iovec iov; char *str; int len; len = vasprintf(&str, format, ap); if (len < 0 || !str) return errno; len = strlen(str); /* Replace new line since btmon already adds it */ if (len > 1 && str[len - 1] == '\n') { str[len - 1] = '\0'; len--; } iov.iov_base = str; iov.iov_len = len + 1; len = bt_log_sendmsg(index, label, level, &iov, 1); free(str); return len; } int bt_log_printf(uint16_t index, const char *label, int level, const char *format, ...) { va_list ap; int err; va_start(ap, format); err = bt_log_vprintf(index, label, level, format, ap); va_end(ap); return err; } void bt_log_close(void) { if (log_fd < 0) return; close(log_fd); log_fd = -1; } bluez-5.87/src/PaxHeaders/main.conf0000644000000000000000000000005015221752714014243 xustar0020 atime=1783092685 20 ctime=1783094110 bluez-5.87/src/main.conf0000644000000000000000000003501015221752714013723 0ustar00rootroot[General] # Default adapter name # Defaults to 'BlueZ X.YZ' #Name = BlueZ # Default device class. Only the major and minor device class bits are # considered. Defaults to '0x000000'. #Class = 0x000100 # How long to stay in discoverable mode before going back to non-discoverable # The value is in seconds. Default is 180, i.e. 3 minutes. # 0 = disable timer, i.e. stay discoverable forever #DiscoverableTimeout = 0 # Always allow pairing even if there are no agent registered # Possible values: true, false # Default: false #AlwaysPairable = false # How long to stay in pairable mode before going back to non-discoverable # The value is in seconds. Default is 0. # 0 = disable timer, i.e. stay pairable forever #PairableTimeout = 0 # Use vendor id source (assigner), vendor, product and version information for # DID profile support. The values are separated by ":" and assigner, VID, PID # and version. # Possible vendor id source values: bluetooth, usb (default) or false (disabled) #DeviceID = bluetooth:1234:5678:abcd # Do reverse service discovery for previously unknown devices that connect to # us. For BR/EDR this option is really only needed for qualification since the # BITE tester doesn't like us doing reverse SDP for some test cases, for LE # this disables the GATT client functionally so it can be used in system which # can only operate as peripheral (see also GATT Client option). # Defaults to 'true'. #ReverseServiceDiscovery = true # Enable name resolving after inquiry. Set it to 'false' if you don't need # remote devices name and want shorter discovery cycle. Defaults to 'true'. #NameResolving = true # Enable runtime persistency of debug link keys. Default is false which # makes debug link keys valid only for the duration of the connection # that they were created for. #DebugKeys = false # Restricts all controllers to the specified transport. Default value # is "dual", i.e. both BR/EDR and LE enabled (when supported by the HW). # Possible values: "dual", "bredr", "le" #ControllerMode = dual # Maximum number of controllers allowed to be exposed to the system. # Default=0 (unlimited) #MaxControllers=0 # Enables Multi Profile Specification support. This allows to specify if # system supports only Multiple Profiles Single Device (MPSD) configuration # or both Multiple Profiles Single Device (MPSD) and Multiple Profiles Multiple # Devices (MPMD) configurations. # Possible values: "off", "single", "multiple" #MultiProfile = off # Permanently enables the Fast Connectable setting for adapters that # support it. When enabled other devices can connect faster to us, # however the tradeoff is increased power consumptions. This feature # will fully work only on kernel version 4.1 and newer. Defaults to # 'false'. #FastConnectable = false # Default privacy setting. # Enables use of private address. # Possible values for LE mode: "off", "network/on", "device" # Possible values for Dual mode: "off", "network/on", "device", # "limited-network", "limited-device" # # - off: Local privacy disabled. # # - network/on: A device will only accept advertising packets from peer # devices that contain private addresses. It may not be compatible with some # legacy devices since it requires the use of RPA(s) all the time. # # - device: A device in device privacy mode is only concerned about the # privacy of the device and will accept advertising packets from peer devices # that contain their Identity Address as well as ones that contain a private # address, even if the peer device has distributed its IRK in the past. # - limited-network: Apply Limited Discoverable Mode to advertising, which # follows the same policy as to BR/EDR that publishes the identity address when # discoverable, and Network Privacy Mode for scanning. # # - limited-device: Apply Limited Discoverable Mode to advertising, which # follows the same policy as to BR/EDR that publishes the identity address when # discoverable, and Device Privacy Mode for scanning. # # Defaults to "off" #Privacy = off # Specify the policy to the JUST-WORKS repairing initiated by peer # Possible values: "never", "confirm", "always" # Defaults to "never" #JustWorksRepairing = never # How long to keep temporary devices around # The value is in seconds. Default is 30. # 0 = disable timer, i.e. temporary devices stay around forever #TemporaryTimeout = 30 # Enables the device to issue an SDP request to update known services when # profile is connected. Defaults to true. #RefreshDiscovery = true # Default Secure Connections setting. # Enables the Secure Connections setting for adapters that support it. It # provides better crypto algorithms for BT links and also enables CTKD (cross # transport key derivation) during pairing on any link. # Possible values: "off", "on", "only" # - "off": Secure Connections are disabled # - "on": Secure Connections are enabled when peer device supports them # - "only": we allow only Secure Connections # Defaults to "on" #SecureConnections = on # Enables D-Bus experimental interfaces # Possible values: true or false #Experimental = false # Enables D-Bus testing interfaces # Possible values: true or false #Testing = false # Enables kernel experimental features, alternatively a list of UUIDs # can be given. # Possible values: true,false, # Possible UUIDS: # d4992530-b9ec-469f-ab01-6c481c47da1c (BlueZ Experimental Debug) # 671b10b5-42c0-4696-9227-eb28d1b049d6 (BlueZ Experimental Simultaneous Central and Peripheral) # 15c0a148-c273-11ea-b3de-0242ac130004 (BlueZ Experimental LL privacy) # 330859bc-7506-492d-9370-9a6f0614037f (BlueZ Experimental Bluetooth Quality Report) # a6695ace-ee7f-4fb9-881a-5fac66c629af (BlueZ Experimental Offload Codecs) # 6fbaf188-05e0-496a-9885-d6ddfdb4e03e (BlueZ Experimental ISO socket) # Defaults to false. #KernelExperimental = false # The duration to avoid retrying to resolve a peer's name, if the previous # try failed. # The value is in seconds. Default is 300, i.e. 5 minutes. #RemoteNameRequestRetryDelay = 300 # Filter out non-discoverable devices based on discoverable flags (General or # Limited). # Defaults to true # Setting to false is only recommended for an use-case where non-discoverable # device must be enumerated due to some requirement (e.g. not using GAP # discoverable modes for some odd reason which seems to be the behavior of # some stacks) or when testing bad/unintended behavior. #FilterDiscoverable = true [BR] # The following values are used to load default adapter parameters for BR/EDR. # BlueZ loads the values into the kernel before the adapter is powered if the # kernel supports the MGMT_LOAD_DEFAULT_PARAMETERS command. If a value isn't # provided, the kernel will be initialized to it's default value. The actual # value will vary based on the kernel version and thus aren't provided here. # The Bluetooth Core Specification should be consulted for the meaning and valid # domain of each of these values. # BR/EDR Page scan activity configuration #PageScanType= #PageScanInterval= #PageScanWindow= # BR/EDR Inquiry scan activity configuration #InquiryScanType= #InquiryScanInterval= #InquiryScanWindow= # BR/EDR Link supervision timeout #LinkSupervisionTimeout= # BR/EDR Page Timeout #PageTimeout= # BR/EDR Connection Idle Timeout #IdleTimeout= # BR/EDR Sniff Intervals #MinSniffInterval= #MaxSniffInterval= [LE] # Enable/Disable Central Address Resolution. # 0: disable # 1: enable # Defaults to 1 #CentralAddressResolution = 1 # The following values are used to load default adapter parameters for LE. # BlueZ loads the values into the kernel before the adapter is powered if the # kernel supports the MGMT_LOAD_DEFAULT_PARAMETERS command. If a value isn't # provided, the kernel will be initialized to it's default value. The actual # value will vary based on the kernel version and thus aren't provided here. # The Bluetooth Core Specification should be consulted for the meaning and valid # domain of each of these values. # LE advertisement interval (used for legacy advertisement interface only) #MinAdvertisementInterval= #MaxAdvertisementInterval= #MultiAdvertisementRotationInterval= # LE scanning parameters used for passive scanning supporting auto connect # scenarios #ScanIntervalAutoConnect= #ScanWindowAutoConnect= # LE scanning parameters used for passive scanning supporting wake from suspend # scenarios #ScanIntervalSuspend= #ScanWindowSuspend= # LE scanning parameters used for active scanning supporting discovery # procedure #ScanIntervalDiscovery= #ScanWindowDiscovery= # LE scanning parameters used for passive scanning supporting the advertisement # monitor Apis #ScanIntervalAdvMonitor= #ScanWindowAdvMonitor= # LE scanning parameters used for connection establishment. #ScanIntervalConnect= #ScanWindowConnect= # LE default connection parameters. These values are superseded by any # specific values provided via the Load Connection Parameters interface #MinConnectionInterval= #MaxConnectionInterval= #ConnectionLatency= #ConnectionSupervisionTimeout= #Autoconnecttimeout= # Scan duration during interleaving scan. Only used when scanning for ADV # monitors. The units are msec. # Default: 300 #AdvMonAllowlistScanDuration= # Default: 500 #AdvMonNoFilterScanDuration= # Enable/Disable Advertisement Monitor interleave scan for power saving. # 0: disable # 1: enable # Defaults to 1 #EnableAdvMonInterleaveScan= [GATT] # GATT attribute cache. # Possible values: # always: Always cache attributes even for devices not paired, this is # recommended as it is best for interoperability, with more consistent # reconnection times and enables proper tracking of notifications for all # devices. # yes: Only cache attributes of paired devices. # no: Never cache attributes # Default: always #Cache = always # This enables the GATT client functionally, so it can be disabled in system # which can only operate as a peripheral. # Defaults to 'true'. #Client = true # Minimum required Encryption Key Size for accessing secured characteristics. # Possible values: 0 and 7-16. 0 means don't care. # Defaults to 0 #KeySize = 0 # Exchange MTU size. # Possible values: 23-517 # Defaults to 517 #ExchangeMTU = 517 # Number of ATT channels, 1 is mandatory since it is used for ATT fixed channel # index 2-6 are used for EATT which is optional. # Possible values: 1-6 (1 disables EATT) # Default to 1 #Channels = 1 # Export claimed services by plugins # Possible values: no, read-only, read-write # Default: read-only #ExportClaimedServices = read-only # Security level: # Sets security level of ATT channel, setting security anything other than # auto requires Testing to be set, setting to low disables GATT server # attribite permissions. # Possible values: auto, [low=1, medium=2, high=3, fips=4 (Testing = true)] # Default = auto # Security = auto [ChannelSounding] # Current role of the device # Possible values: # 1 or "Initiator" - CS Initiator role, # Generally, CS Initiator acts as Client (Gatt role) and Central (Gap role) # 2 or "Reflector" - CS Reflector role, # Generally, CS Reflector acts as Server (Gatt role) and Peripheral (Gap role) # 3 or "Both" - Both Initiator and Reflector roles # Default: 3 (Both) #Role = 3 # Antenna Identifier to be used # Possible values: # 0x01-0x04 (antenna identifier to be used), # 0xFE - Antennas to be used in repetitive order, # 0xFF - Host doesn't have recommendation # Default: 0xFF (Host doesn't have recommendation) #CsSyncAntennaSel = 0xFF # Maximum Transmit power # Possible values: 0x81-0x14 (-127dBm to 20dBm) # Default: 0x14 (Max Power possible) #MaxTxPower = 0x14 [CSIS] # SIRK - Set Identification Resolution Key which is common for all the # sets. They SIRK key is used to identify its sets. This can be any # 128 bit value or a string value (e.g. product name) which is then hashed. # Possible Values: # 16 byte hexadecimal value: 861FAE703ED681F0C50B34155B6434FB # String value: "My Product Name" # Defaults to none #SIRK = # SIRK Encryption # Possible values: # true: Encrypt SIRK when read # false: Do not encrypt SIRK when read. (plaintext) # Defaults to true #Encryption = true # Total no of sets belongs to this Profile # Defaults to 0 #Size = 0 # Rank for the device # Defaults to 0 #Rank = 0 [AVDTP] # AVDTP L2CAP Signalling Channel Mode. # Possible values: # basic: Use L2CAP Basic Mode # ertm: Use L2CAP Enhanced Retransmission Mode #SessionMode = basic # AVDTP L2CAP Transport Channel Mode. # Possible values: # basic: Use L2CAP Basic Mode # streaming: Use L2CAP Streaming Mode #StreamMode = basic [AVRCP] # Allow SetAbsoluteVolume calls to a peer device that does not advertise the # AVRCP remote control target profile. If it does advertise this profile, the # version is ignored. #VolumeWithoutTarget = false # Validate that remote AVRCP profiles advertise the category-2 bit before # allowing SetAbsoluteVolume calls or registering for EVENT_VOLUME_CHANGED # notifications. #VolumeCategory = true [Policy] # # The ReconnectUUIDs defines the set of remote services that should try # to be reconnected to in case of a link loss (link supervision # timeout). The policy plugin should contain a sane set of values by # default, but this list can be overridden here. By setting the list to # empty the reconnection feature gets disabled. #ReconnectUUIDs=00001112-0000-1000-8000-00805f9b34fb,0000111f-0000-1000-8000-00805f9b34fb,0000110a-0000-1000-8000-00805f9b34fb,0000110b-0000-1000-8000-00805f9b34fb # ReconnectAttempts define the number of attempts to reconnect after a link # lost. Setting the value to 0 disables reconnecting feature. #ReconnectAttempts=7 # ReconnectIntervals define the set of intervals in seconds to use in between # attempts. # If the number of attempts defined in ReconnectAttempts is bigger than the # set of intervals the last interval is repeated until the last attempt. #ReconnectIntervals=1,2,4,8,16,32,64 # AutoEnable defines option to enable all controllers when they are found. # This includes adapters present on start as well as adapters that are plugged # in later on. Defaults to 'true'. #AutoEnable=true # Audio devices that were disconnected due to suspend will be reconnected on # resume. ResumeDelay determines the delay between when the controller # resumes from suspend and a connection attempt is made. A longer delay is # better for better co-existence with Wi-Fi. # The value is in seconds. # Default: 2 #ResumeDelay = 2 [AdvMon] # Default RSSI Sampling Period. This is used when a client registers an # advertisement monitor and leaves the RSSISamplingPeriod unset. # Possible values: # 0x00 Report all advertisements # N = 0xXX Report advertisements every N x 100 msec (range: 0x01 to 0xFE) # 0xFF Report only one advertisement per device during monitoring period # Default: 0xFF #RSSISamplingPeriod=0xFF bluez-5.87/src/PaxHeaders/oui.c0000644000000000000000000000005015063045254013406 xustar0020 atime=1783093297 20 ctime=1783094106 bluez-5.87/src/oui.c0000644000000000000000000000211715063045254013070 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include "bluetooth/bluetooth.h" #include "oui.h" #ifdef HAVE_UDEV #include char *batocomp(const bdaddr_t *ba) { struct udev *udev; struct udev_hwdb *hwdb; struct udev_list_entry *head, *entry; char modalias[11], *comp = NULL; sprintf(modalias, "OUI:%2.2X%2.2X%2.2X", ba->b[5], ba->b[4], ba->b[3]); udev = udev_new(); if (!udev) return NULL; hwdb = udev_hwdb_new(udev); if (!hwdb) goto done; head = udev_hwdb_get_properties_list_entry(hwdb, modalias, 0); udev_list_entry_foreach(entry, head) { const char *name = udev_list_entry_get_name(entry); if (name && !strcmp(name, "ID_OUI_FROM_DATABASE")) { comp = strdup(udev_list_entry_get_value(entry)); break; } } hwdb = udev_hwdb_unref(hwdb); done: udev = udev_unref(udev); return comp; } #else char *batocomp(const bdaddr_t *ba) { return NULL; } #endif bluez-5.87/src/PaxHeaders/adapter.h0000644000000000000000000000005015200607060014227 xustar0020 atime=1783092683 20 ctime=1783094106 bluez-5.87/src/adapter.h0000644000000000000000000002725015200607060013716 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #include #include #include #include #include #include "shared/mgmt.h" #define ADAPTER_INTERFACE "org.bluez.Adapter1" #define MAX_NAME_LENGTH 248 /* Invalid SSP passkey value used to indicate negative replies */ #define INVALID_PASSKEY 0xffffffff struct btd_adapter; struct btd_device; struct queue; struct btd_adapter *btd_adapter_get_default(void); bool btd_adapter_is_default(struct btd_adapter *adapter); uint16_t btd_adapter_get_index(struct btd_adapter *adapter); bool btd_adapter_has_cable_pairing_devices(struct btd_adapter *adapter); typedef void (*adapter_cb) (struct btd_adapter *adapter, gpointer user_data); typedef void (*oob_read_local_cb_t) (struct btd_adapter *adapter, const uint8_t *hash, const uint8_t *randomizer, void *user_data); typedef void (*oob_bonding_cb_t) (struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t status, void *user_data); struct oob_handler { oob_read_local_cb_t read_local_cb; oob_bonding_cb_t bonding_cb; bdaddr_t remote_addr; void *user_data; }; int adapter_init(void); void adapter_cleanup(void); void adapter_shutdown(void); void btd_adapter_foreach(adapter_cb func, gpointer user_data); typedef void (*btd_disconnect_cb) (struct btd_device *device, uint8_t reason); void btd_add_disconnect_cb(btd_disconnect_cb func); void btd_remove_disconnect_cb(btd_disconnect_cb func); typedef void (*btd_conn_fail_cb) (struct btd_device *device, uint8_t status); void btd_add_conn_fail_cb(btd_conn_fail_cb func); void btd_remove_conn_fail_cb(btd_conn_fail_cb func); struct btd_adapter *adapter_find(const bdaddr_t *sba); struct btd_adapter *adapter_find_by_id(int id); bool btd_adapter_get_pairable(struct btd_adapter *adapter); bool btd_adapter_get_powered(struct btd_adapter *adapter); bool btd_adapter_get_connectable(struct btd_adapter *adapter); bool btd_adapter_get_discoverable(struct btd_adapter *adapter); bool btd_adapter_get_bredr(struct btd_adapter *adapter); struct btd_gatt_database *btd_adapter_get_database(struct btd_adapter *adapter); uint32_t btd_adapter_get_class(struct btd_adapter *adapter); const char *btd_adapter_get_name(struct btd_adapter *adapter); void btd_adapter_remove_device(struct btd_adapter *adapter, struct btd_device *dev); struct btd_device *btd_adapter_get_device(struct btd_adapter *adapter, const bdaddr_t *addr, uint8_t addr_type); sdp_list_t *btd_adapter_get_services(struct btd_adapter *adapter); struct btd_device *btd_adapter_find_device(struct btd_adapter *adapter, const bdaddr_t *dst, uint8_t dst_type); struct btd_device *btd_adapter_find_device_by_path(struct btd_adapter *adapter, const char *path); struct btd_device *btd_adapter_find_device_by_fd(int fd); void btd_adapter_device_found(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type, int8_t rssi, uint32_t flags, const uint8_t *data, uint8_t data_len, bool monitoring); const char *adapter_get_path(struct btd_adapter *adapter); const bdaddr_t *btd_adapter_get_address(struct btd_adapter *adapter); uint8_t btd_adapter_get_address_type(struct btd_adapter *adapter); const char *btd_adapter_get_storage_dir(struct btd_adapter *adapter); int adapter_service_add(struct btd_adapter *adapter, sdp_record_t *rec); void adapter_service_remove(struct btd_adapter *adapter, uint32_t handle); struct agent *adapter_get_agent(struct btd_adapter *adapter); bool btd_adapter_uuid_is_allowed(struct btd_adapter *adapter, const char *uuid); struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter); void btd_adapter_unref(struct btd_adapter *adapter); void btd_adapter_set_class(struct btd_adapter *adapter, uint8_t major, uint8_t minor); int btd_adapter_set_name(struct btd_adapter *adapter, const char *name); struct btd_adapter_driver { const char *name; int (*probe)(struct btd_adapter *adapter); void (*remove)(struct btd_adapter *adapter); void (*resume)(struct btd_adapter *adapter); void (*device_added)(struct btd_adapter *adapter, struct btd_device *device); void (*device_removed)(struct btd_adapter *adapter, struct btd_device *device); void (*device_resolved)(struct btd_adapter *adapter, struct btd_device *device); /* Indicates the driver is experimental and shall only be registered * when experimental has been enabled (see: main.conf:Experimental). */ bool experimental; }; void device_resolved_drivers(struct btd_adapter *adapter, struct btd_device *device); typedef void (*service_auth_cb) (DBusError *derr, void *user_data); void adapter_add_profile(struct btd_adapter *adapter, gpointer p); void adapter_remove_profile(struct btd_adapter *adapter, gpointer p); int btd_register_adapter_driver(const struct btd_adapter_driver *driver); void btd_unregister_adapter_driver(const struct btd_adapter_driver *driver); guint btd_request_authorization(const bdaddr_t *src, const bdaddr_t *dst, const char *uuid, service_auth_cb cb, void *user_data); guint btd_request_authorization_cable_configured(const bdaddr_t *src, const bdaddr_t *dst, const char *uuid, service_auth_cb cb, void *user_data); int btd_cancel_authorization(guint id); int btd_adapter_restore_powered(struct btd_adapter *adapter); int btd_adapter_set_blocked(struct btd_adapter *adapter); typedef ssize_t (*btd_adapter_pin_cb_t) (struct btd_adapter *adapter, struct btd_device *dev, char *out, bool *display, unsigned int attempt); void btd_adapter_register_pin_cb(struct btd_adapter *adapter, btd_adapter_pin_cb_t cb); void btd_adapter_unregister_pin_cb(struct btd_adapter *adapter, btd_adapter_pin_cb_t cb); struct btd_adapter_pin_cb_iter *btd_adapter_pin_cb_iter_new( struct btd_adapter *adapter); void btd_adapter_pin_cb_iter_free(struct btd_adapter_pin_cb_iter *iter); bool btd_adapter_pin_cb_iter_end(struct btd_adapter_pin_cb_iter *iter); typedef void (*btd_msd_cb_t) (struct btd_adapter *adapter, struct btd_device *dev, uint16_t company, const uint8_t *data, uint8_t data_len); void btd_adapter_register_msd_cb(struct btd_adapter *adapter, btd_msd_cb_t cb); void btd_adapter_unregister_msd_cb(struct btd_adapter *adapter, btd_msd_cb_t cb); /* If TRUE, enables fast connectabe, i.e. reduces page scan interval and changes * type. If FALSE, disables fast connectable, i.e. sets page scan interval and * type to default values. Valid for both connectable and discoverable modes. */ int btd_adapter_set_fast_connectable(struct btd_adapter *adapter, gboolean enable); int btd_adapter_read_clock(struct btd_adapter *adapter, const bdaddr_t *bdaddr, int which, int timeout, uint32_t *clock, uint16_t *accuracy); int btd_adapter_block_address(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type); int btd_adapter_unblock_address(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type); int btd_adapter_disconnect_device(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type); int btd_adapter_remove_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type); int btd_adapter_pincode_reply(struct btd_adapter *adapter, const bdaddr_t *bdaddr, const char *pin, size_t pin_len); int btd_adapter_confirm_reply(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type, gboolean success); int btd_adapter_passkey_reply(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type, uint32_t passkey); int adapter_create_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t addr_type, enum mgmt_io_capability io_cap); int adapter_bonding_attempt(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t addr_type, enum mgmt_io_capability io_cap); int adapter_cancel_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t addr_type); int adapter_set_io_capability(struct btd_adapter *adapter, enum mgmt_io_capability io_cap); int btd_adapter_read_local_oob_data(struct btd_adapter *adapter); int btd_adapter_add_remote_oob_data(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t *hash, uint8_t *randomizer); int btd_adapter_remove_remote_oob_data(struct btd_adapter *adapter, const bdaddr_t *bdaddr); int btd_adapter_gatt_server_start(struct btd_adapter *adapter); void btd_adapter_gatt_server_stop(struct btd_adapter *adapter); bool btd_adapter_ssp_enabled(struct btd_adapter *adapter); int adapter_connect_list_add(struct btd_adapter *adapter, struct btd_device *device); void adapter_connect_list_remove(struct btd_adapter *adapter, struct btd_device *device); typedef void (*adapter_set_device_flags_func_t)(uint8_t status, uint16_t length, const void *param, void *user_data); int adapter_set_device_flags(struct btd_adapter *adapter, struct btd_device *device, uint32_t flags, adapter_set_device_flags_func_t func, void *user_data); void adapter_auto_connect_add(struct btd_adapter *adapter, struct btd_device *device); void adapter_auto_connect_remove(struct btd_adapter *adapter, struct btd_device *device); void adapter_accept_list_add(struct btd_adapter *adapter, struct btd_device *dev); void adapter_accept_list_remove(struct btd_adapter *adapter, struct btd_device *dev); void btd_adapter_set_oob_handler(struct btd_adapter *adapter, struct oob_handler *handler); gboolean btd_adapter_check_oob_handler(struct btd_adapter *adapter); void btd_adapter_for_each_device(struct btd_adapter *adapter, void (*cb)(struct btd_device *device, void *data), void *data); bool btd_le_connect_before_pairing(void); bool btd_adapter_has_settings(struct btd_adapter *adapter, uint32_t settings); enum experimental_features { EXP_FEAT_DEBUG = 1 << 0, EXP_FEAT_LE_SIMULT_ROLES = 1 << 1, EXP_FEAT_BQR = 1 << 2, EXP_FEAT_RPA_RESOLUTION = 1 << 3, EXP_FEAT_CODEC_OFFLOAD = 1 << 4, EXP_FEAT_ISO_SOCKET = 1 << 5, }; bool btd_adapter_has_exp_feature(struct btd_adapter *adapter, uint32_t feature); enum kernel_features { KERNEL_CONN_CONTROL = 1 << 0, KERNEL_BLOCKED_KEYS_SUPPORTED = 1 << 1, KERNEL_SET_SYSTEM_CONFIG = 1 << 2, KERNEL_EXP_FEATURES = 1 << 3, KERNEL_HAS_RESUME_EVT = 1 << 4, KERNEL_HAS_EXT_ADV_ADD_CMDS = 1 << 5, KERNEL_HAS_CONTROLLER_CAP_CMD = 1 << 6, }; bool btd_has_kernel_features(uint32_t feature); bool btd_adapter_set_allowed_uuids(struct btd_adapter *adapter, struct queue *uuids); bool btd_adapter_is_uuid_allowed(struct btd_adapter *adapter, const char *uuid_str); void btd_adapter_load_conn_param(struct btd_adapter *adapter, const bdaddr_t *peer, uint8_t bdaddr_type, uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout); void btd_adapter_store_conn_param(struct btd_adapter *adapter, const bdaddr_t *peer, uint8_t bdaddr_type, uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout); void btd_adapter_cancel_service_auth(struct btd_adapter *adapter, struct btd_device *device); typedef void (*btd_adapter_reply_event_t)(uint8_t status, uint16_t length, const void *param, void *user_data); typedef void (*btd_adapter_destroy_func_t)(void *user_data); unsigned int btd_adapter_send_cmd_event_sync(struct btd_adapter *adapter, uint16_t opcode, uint16_t length, const void *param, uint8_t event, btd_adapter_reply_event_t callback, void *user_data, btd_adapter_destroy_func_t destroy, uint8_t timeout); bluez-5.87/src/PaxHeaders/bluetooth.service.in0000644000000000000000000000005015200607060016432 xustar0020 atime=1783092683 20 ctime=1783094098 bluez-5.87/src/bluetooth.service.in0000644000000000000000000000137515200607060016121 0ustar00rootroot[Unit] Description=Bluetooth service Documentation=man:bluetoothd(8) ConditionPathIsDirectory=/sys/class/bluetooth [Service] Type=dbus BusName=org.bluez ExecStart=@PKGLIBEXECDIR@/bluetoothd NotifyAccess=main #WatchdogSec=10 #Restart=on-failure CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN CAP_NET_BIND_SERVICE LimitNPROC=1 # Filesystem lockdown ProtectHome=true ProtectSystem=strict PrivateTmp=true ProtectKernelTunables=true ProtectControlGroups=true StateDirectory=bluetooth StateDirectoryMode=0700 ConfigurationDirectory=bluetooth ConfigurationDirectoryMode=0555 # Execute Mappings MemoryDenyWriteExecute=true # Privilege escalation NoNewPrivileges=true # Real-time RestrictRealtime=true [Install] WantedBy=bluetooth.target Alias=dbus-org.bluez.service bluez-5.87/src/PaxHeaders/battery.h0000644000000000000000000000005014015011623014256 xustar0020 atime=1783093687 20 ctime=1783094106 bluez-5.87/src/battery.h0000644000000000000000000000123514015011623013740 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2020 Google LLC * * */ struct btd_adapter; struct btd_battery; struct btd_battery_provider_manager; struct btd_battery *btd_battery_register(const char *path, const char *source, const char *provider_path); bool btd_battery_unregister(struct btd_battery *battery); bool btd_battery_update(struct btd_battery *battery, uint8_t percentage); struct btd_battery_provider_manager * btd_battery_provider_manager_create(struct btd_adapter *adapter); void btd_battery_provider_manager_destroy( struct btd_battery_provider_manager *manager); bluez-5.87/src/PaxHeaders/agent.c0000644000000000000000000000005015200607060013700 xustar0020 atime=1783092683 20 ctime=1783094106 bluez-5.87/src/agent.c0000644000000000000000000005762615200607060013401 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "gdbus/gdbus.h" #include "log.h" #include "error.h" #include "btd.h" #include "dbus-common.h" #include "adapter.h" #include "device.h" #include "agent.h" #include "shared/queue.h" #define REQUEST_TIMEOUT (60 * 1000) /* 60 seconds */ #define AGENT_INTERFACE "org.bluez.Agent1" static GHashTable *agent_list; struct queue *default_agents = NULL; typedef enum { AGENT_REQUEST_PASSKEY, AGENT_REQUEST_CONFIRMATION, AGENT_REQUEST_AUTHORIZATION, AGENT_REQUEST_PINCODE, AGENT_REQUEST_AUTHORIZE_SERVICE, AGENT_REQUEST_DISPLAY_PINCODE, } agent_request_type_t; struct agent { int ref; char *owner; char *path; enum mgmt_io_capability capability; struct agent_request *request; guint watch; }; struct agent_request { agent_request_type_t type; struct btd_device *device; /* Weak reference */ struct agent *agent; DBusMessage *msg; DBusPendingCall *call; void *cb; void *user_data; GDestroyNotify destroy; }; static void agent_release(struct agent *agent) { DBusMessage *message; DBG("Releasing agent %s, %s", agent->owner, agent->path); if (agent->request) agent_cancel(agent); message = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "Release"); if (message == NULL) { error("Couldn't allocate D-Bus message"); return; } g_dbus_send_message(btd_get_dbus_connection(), message); } static int send_cancel_request(struct agent_request *req) { DBusMessage *message; DBG("Sending Cancel request to %s, %s", req->agent->owner, req->agent->path); message = dbus_message_new_method_call(req->agent->owner, req->agent->path, AGENT_INTERFACE, "Cancel"); if (message == NULL) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } g_dbus_send_message(btd_get_dbus_connection(), message); return 0; } static void agent_request_free(struct agent_request *req, gboolean destroy) { if (req->msg) dbus_message_unref(req->msg); if (req->call) dbus_pending_call_unref(req->call); if (req->agent && req->agent->request) req->agent->request = NULL; if (destroy && req->destroy) req->destroy(req->user_data); g_free(req); } static void set_io_cap(struct btd_adapter *adapter, gpointer user_data) { struct agent *agent = user_data; enum mgmt_io_capability io_cap; if (agent) io_cap = agent->capability; else io_cap = MGMT_IO_CAPABILITY_INVALID; adapter_set_io_capability(adapter, io_cap); } static bool add_default_agent(struct agent *agent) { if (queue_peek_head(default_agents) == agent) return true; queue_remove(default_agents, agent); if (!queue_push_head(default_agents, agent)) return false; DBG("Default agent set to %s %s", agent->owner, agent->path); btd_adapter_foreach(set_io_cap, agent); return true; } static void remove_default_agent(struct agent *agent) { if (queue_peek_head(default_agents) != agent) { queue_remove(default_agents, agent); return; } queue_remove(default_agents, agent); agent = queue_peek_head(default_agents); if (agent) DBG("Default agent set to %s %s", agent->owner, agent->path); else DBG("Default agent cleared"); btd_adapter_foreach(set_io_cap, agent); } static void agent_disconnect(DBusConnection *conn, void *user_data) { struct agent *agent = user_data; DBG("Agent %s disconnected", agent->owner); if (agent->watch > 0) { g_dbus_remove_watch(conn, agent->watch); agent->watch = 0; } remove_default_agent(agent); g_hash_table_remove(agent_list, agent->owner); } struct agent *agent_ref(struct agent *agent) { agent->ref++; DBG("%p: ref=%d", agent, agent->ref); return agent; } void agent_unref(struct agent *agent) { agent->ref--; DBG("%p: ref=%d", agent, agent->ref); if (agent->ref > 0) return; if (agent->request) { DBusError err; agent_pincode_cb pincode_cb; agent_passkey_cb passkey_cb; agent_cb cb; dbus_error_init(&err); dbus_set_error_const(&err, ERROR_INTERFACE ".Failed", "Canceled"); switch (agent->request->type) { case AGENT_REQUEST_PINCODE: pincode_cb = agent->request->cb; pincode_cb(agent, &err, NULL, agent->request->user_data); break; case AGENT_REQUEST_PASSKEY: passkey_cb = agent->request->cb; passkey_cb(agent, &err, 0, agent->request->user_data); break; case AGENT_REQUEST_CONFIRMATION: case AGENT_REQUEST_AUTHORIZATION: case AGENT_REQUEST_AUTHORIZE_SERVICE: case AGENT_REQUEST_DISPLAY_PINCODE: default: cb = agent->request->cb; cb(agent, &err, agent->request->user_data); } dbus_error_free(&err); agent_cancel(agent); } g_free(agent->owner); g_free(agent->path); g_free(agent); } struct agent *agent_get(const char *owner) { struct agent *agent; if (owner) { agent = g_hash_table_lookup(agent_list, owner); if (agent) return agent_ref(agent); } if (!queue_isempty(default_agents)) return agent_ref(queue_peek_head(default_agents)); return NULL; } static struct agent *agent_create( const char *name, const char *path, enum mgmt_io_capability capability) { struct agent *agent; agent = g_new0(struct agent, 1); agent->owner = g_strdup(name); agent->path = g_strdup(path); agent->capability = capability; agent->watch = g_dbus_add_disconnect_watch(btd_get_dbus_connection(), name, agent_disconnect, agent, NULL); if (queue_isempty(default_agents)) add_default_agent(agent); else queue_push_tail(default_agents, agent); return agent_ref(agent); } static struct agent_request *agent_request_new(struct agent *agent, struct btd_device *device, agent_request_type_t type, void *cb, void *user_data, GDestroyNotify destroy) { struct agent_request *req; req = g_new0(struct agent_request, 1); req->agent = agent; req->device = device; req->type = type; req->cb = cb; req->user_data = user_data; req->destroy = destroy; return req; } int agent_cancel(struct agent *agent) { if (!agent->request) return -EINVAL; if (agent->request->call) { dbus_pending_call_cancel(agent->request->call); send_cancel_request(agent->request); } agent_request_free(agent->request, TRUE); agent->request = NULL; return 0; } static void simple_agent_reply(DBusPendingCall *call, void *user_data) { struct agent_request *req = user_data; struct agent *agent = req->agent; DBusMessage *message; DBusError err; agent_cb cb = req->cb; /* steal_reply will always return non-NULL since the callback * is only called after a reply has been received */ message = dbus_pending_call_steal_reply(call); /* Protect from the callback freeing the agent */ agent_ref(agent); dbus_error_init(&err); if (dbus_set_error_from_message(&err, message)) { DBG("agent error reply: %s, %s", err.name, err.message); cb(agent, &err, req->user_data); if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) { error("Timed out waiting for reply from agent"); agent_cancel(agent); dbus_message_unref(message); dbus_error_free(&err); agent_unref(agent); return; } dbus_error_free(&err); goto done; } if (!dbus_message_get_args(message, &err, DBUS_TYPE_INVALID)) { error("Wrong reply signature: %s", err.message); cb(agent, &err, req->user_data); dbus_error_free(&err); goto done; } cb(agent, NULL, req->user_data); done: dbus_message_unref(message); agent->request = NULL; agent_request_free(req, TRUE); agent_unref(agent); } static int agent_call_authorize_service(struct agent_request *req, const char *uuid) { struct agent *agent = req->agent; const char *path; req->msg = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "AuthorizeService"); if (!req->msg) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } path = device_get_path(req->device); dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_STRING, &uuid, DBUS_TYPE_INVALID); if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg, &req->call, REQUEST_TIMEOUT) == FALSE) { error("D-Bus send failed"); return -EIO; } dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL); DBG("authorize service request was sent for %s", path); return 0; } static int agent_has_request(struct agent *agent, struct btd_device *device, agent_request_type_t type) { if (!agent->request) return 0; if (agent->request->type != type) return -EBUSY; /* Check if request pending is for the same address */ if (bacmp(device_get_address(agent->request->device), btd_adapter_get_address(device_get_adapter(device)))) return -EBUSY; /* It must match in either direction */ if (bacmp(device_get_address(device), btd_adapter_get_address( device_get_adapter(agent->request->device)))) return -EBUSY; return -EINPROGRESS; } int agent_authorize_service(struct agent *agent, struct btd_device *device, const char *uuid, agent_cb cb, void *user_data, GDestroyNotify destroy) { struct agent_request *req; int err; err = agent_has_request(agent, device, AGENT_REQUEST_AUTHORIZE_SERVICE); if (err) return err; req = agent_request_new(agent, device, AGENT_REQUEST_AUTHORIZE_SERVICE, cb, user_data, destroy); err = agent_call_authorize_service(req, uuid); if (err < 0) { agent_request_free(req, FALSE); return -ENOMEM; } agent->request = req; return 0; } static void pincode_reply(DBusPendingCall *call, void *user_data) { struct agent_request *req = user_data; struct agent *agent = req->agent; agent_pincode_cb cb = req->cb; DBusMessage *message; DBusError err; size_t len; char *pin; /* steal_reply will always return non-NULL since the callback * is only called after a reply has been received */ message = dbus_pending_call_steal_reply(call); /* Protect from the callback freeing the agent */ agent_ref(agent); dbus_error_init(&err); if (dbus_set_error_from_message(&err, message)) { error("Agent %s replied with an error: %s, %s", agent->path, err.name, err.message); cb(agent, &err, NULL, req->user_data); dbus_error_free(&err); goto done; } if (!dbus_message_get_args(message, &err, DBUS_TYPE_STRING, &pin, DBUS_TYPE_INVALID)) { error("Wrong passkey reply signature: %s", err.message); cb(agent, &err, NULL, req->user_data); dbus_error_free(&err); goto done; } len = strlen(pin); if (len > 16 || len < 1) { error("Invalid PIN length (%zu) from agent", len); dbus_set_error_const(&err, ERROR_INTERFACE ".InvalidArgs", "Invalid passkey length"); cb(agent, &err, NULL, req->user_data); dbus_error_free(&err); goto done; } cb(agent, NULL, pin, req->user_data); done: if (message) dbus_message_unref(message); dbus_pending_call_cancel(req->call); agent->request = NULL; agent_request_free(req, TRUE); agent_unref(agent); } static int pincode_request_new(struct agent_request *req, dbus_bool_t secure) { struct agent *agent = req->agent; const char *path; /* TODO: Add a new method or a new param to Agent interface to request secure pin. */ req->msg = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "RequestPinCode"); if (req->msg == NULL) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } path = device_get_path(req->device); dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg, &req->call, REQUEST_TIMEOUT) == FALSE) { error("D-Bus send failed"); return -EIO; } dbus_pending_call_set_notify(req->call, pincode_reply, req, NULL); return 0; } int agent_request_pincode(struct agent *agent, struct btd_device *device, agent_pincode_cb cb, gboolean secure, void *user_data, GDestroyNotify destroy) { struct agent_request *req; int err; if (agent->request) return -EBUSY; req = agent_request_new(agent, device, AGENT_REQUEST_PINCODE, cb, user_data, destroy); err = pincode_request_new(req, secure); if (err < 0) goto failed; agent->request = req; return 0; failed: agent_request_free(req, FALSE); return err; } static void passkey_reply(DBusPendingCall *call, void *user_data) { struct agent_request *req = user_data; struct agent *agent = req->agent; agent_passkey_cb cb = req->cb; DBusMessage *message; DBusError err; uint32_t passkey; /* steal_reply will always return non-NULL since the callback * is only called after a reply has been received */ message = dbus_pending_call_steal_reply(call); dbus_error_init(&err); if (dbus_set_error_from_message(&err, message)) { error("Agent replied with an error: %s, %s", err.name, err.message); cb(agent, &err, 0, req->user_data); dbus_error_free(&err); goto done; } if (!dbus_message_get_args(message, &err, DBUS_TYPE_UINT32, &passkey, DBUS_TYPE_INVALID)) { error("Wrong passkey reply signature: %s", err.message); cb(agent, &err, 0, req->user_data); dbus_error_free(&err); goto done; } cb(agent, NULL, passkey, req->user_data); done: if (message) dbus_message_unref(message); dbus_pending_call_cancel(req->call); agent->request = NULL; agent_request_free(req, TRUE); } static int passkey_request_new(struct agent_request *req) { struct agent *agent = req->agent; const char *path; req->msg = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "RequestPasskey"); if (req->msg == NULL) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } path = device_get_path(req->device); dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg, &req->call, REQUEST_TIMEOUT) == FALSE) { error("D-Bus send failed"); return -EIO; } dbus_pending_call_set_notify(req->call, passkey_reply, req, NULL); return 0; } int agent_request_passkey(struct agent *agent, struct btd_device *device, agent_passkey_cb cb, void *user_data, GDestroyNotify destroy) { struct agent_request *req; int err; if (agent->request) return -EBUSY; DBG("Calling Agent.RequestPasskey: name=%s, path=%s", agent->owner, agent->path); req = agent_request_new(agent, device, AGENT_REQUEST_PASSKEY, cb, user_data, destroy); err = passkey_request_new(req); if (err < 0) goto failed; agent->request = req; return 0; failed: agent_request_free(req, FALSE); return err; } static int confirmation_request_new(struct agent_request *req, uint32_t passkey) { struct agent *agent = req->agent; const char *path; req->msg = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "RequestConfirmation"); if (req->msg == NULL) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } path = device_get_path(req->device); dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_UINT32, &passkey, DBUS_TYPE_INVALID); if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg, &req->call, REQUEST_TIMEOUT) == FALSE) { error("D-Bus send failed"); return -EIO; } dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL); return 0; } int agent_request_confirmation(struct agent *agent, struct btd_device *device, uint32_t passkey, agent_cb cb, void *user_data, GDestroyNotify destroy) { struct agent_request *req; int err; err = agent_has_request(agent, device, AGENT_REQUEST_CONFIRMATION); if (err) return err; DBG("Calling Agent.RequestConfirmation: name=%s, path=%s, passkey=%06u", agent->owner, agent->path, passkey); req = agent_request_new(agent, device, AGENT_REQUEST_CONFIRMATION, cb, user_data, destroy); err = confirmation_request_new(req, passkey); if (err < 0) goto failed; agent->request = req; return 0; failed: agent_request_free(req, FALSE); return err; } static int authorization_request_new(struct agent_request *req) { struct agent *agent = req->agent; const char *path; req->msg = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "RequestAuthorization"); if (req->msg == NULL) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } path = device_get_path(req->device); dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg, &req->call, REQUEST_TIMEOUT) == FALSE) { error("D-Bus send failed"); return -EIO; } dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL); return 0; } int agent_request_authorization(struct agent *agent, struct btd_device *device, agent_cb cb, void *user_data, GDestroyNotify destroy) { struct agent_request *req; int err; err = agent_has_request(agent, device, AGENT_REQUEST_AUTHORIZATION); if (err) return err; DBG("Calling Agent.RequestAuthorization: name=%s, path=%s", agent->owner, agent->path); req = agent_request_new(agent, device, AGENT_REQUEST_AUTHORIZATION, cb, user_data, destroy); err = authorization_request_new(req); if (err < 0) goto failed; agent->request = req; return 0; failed: agent_request_free(req, FALSE); return err; } int agent_display_passkey(struct agent *agent, struct btd_device *device, uint32_t passkey, uint16_t entered) { DBusMessage *message; const char *dev_path = device_get_path(device); message = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "DisplayPasskey"); if (!message) { error("Couldn't allocate D-Bus message"); return -1; } dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &dev_path, DBUS_TYPE_UINT32, &passkey, DBUS_TYPE_UINT16, &entered, DBUS_TYPE_INVALID); if (!g_dbus_send_message(btd_get_dbus_connection(), message)) { error("D-Bus send failed"); return -1; } return 0; } static void display_pincode_reply(DBusPendingCall *call, void *user_data) { struct agent_request *req = user_data; struct agent *agent = req->agent; DBusMessage *message; DBusError err; agent_cb cb = req->cb; /* clear agent->request early; our callback will likely try * another request */ agent->request = NULL; /* steal_reply will always return non-NULL since the callback * is only called after a reply has been received */ message = dbus_pending_call_steal_reply(call); dbus_error_init(&err); if (dbus_set_error_from_message(&err, message)) { error("Agent replied with an error: %s, %s", err.name, err.message); cb(agent, &err, req->user_data); if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) { agent_cancel(agent); dbus_message_unref(message); dbus_error_free(&err); return; } dbus_error_free(&err); goto done; } if (!dbus_message_get_args(message, &err, DBUS_TYPE_INVALID)) { error("Wrong reply signature: %s", err.message); cb(agent, &err, req->user_data); dbus_error_free(&err); goto done; } cb(agent, NULL, req->user_data); done: dbus_message_unref(message); agent_request_free(req, TRUE); } static int display_pincode_request_new(struct agent_request *req, const char *pincode) { struct agent *agent = req->agent; const char *path; req->msg = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "DisplayPinCode"); if (req->msg == NULL) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } path = device_get_path(req->device); dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_STRING, &pincode, DBUS_TYPE_INVALID); if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg, &req->call, REQUEST_TIMEOUT) == FALSE) { error("D-Bus send failed"); return -EIO; } dbus_pending_call_set_notify(req->call, display_pincode_reply, req, NULL); return 0; } int agent_display_pincode(struct agent *agent, struct btd_device *device, const char *pincode, agent_cb cb, void *user_data, GDestroyNotify destroy) { struct agent_request *req; int err; err = agent_has_request(agent, device, AGENT_REQUEST_DISPLAY_PINCODE); if (err) return err; DBG("Calling Agent.DisplayPinCode: name=%s, path=%s, pincode=%s", agent->owner, agent->path, pincode); req = agent_request_new(agent, device, AGENT_REQUEST_DISPLAY_PINCODE, cb, user_data, destroy); err = display_pincode_request_new(req, pincode); if (err < 0) goto failed; agent->request = req; return 0; failed: agent_request_free(req, FALSE); return err; } enum mgmt_io_capability agent_get_io_capability(struct agent *agent) { return agent->capability; } static void agent_destroy(gpointer data) { struct agent *agent = data; DBG("agent %s", agent->owner); if (agent->watch > 0) { g_dbus_remove_watch(btd_get_dbus_connection(), agent->watch); agent->watch = 0; agent_release(agent); } remove_default_agent(agent); agent_unref(agent); } static DBusMessage *register_agent(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct agent *agent; const char *sender, *path, *capability; enum mgmt_io_capability cap; sender = dbus_message_get_sender(msg); agent = g_hash_table_lookup(agent_list, sender); if (agent) return btd_error_already_exists(msg); if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_STRING, &capability, DBUS_TYPE_INVALID) == FALSE) return btd_error_invalid_args(msg); cap = mgmt_parse_io_capability(capability); if (cap == MGMT_IO_CAPABILITY_INVALID) return btd_error_invalid_args(msg); agent = agent_create(sender, path, cap); if (!agent) return btd_error_invalid_args(msg); DBG("agent %s", agent->owner); g_hash_table_replace(agent_list, agent->owner, agent); return dbus_message_new_method_return(msg); } static DBusMessage *unregister_agent(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct agent *agent; const char *sender, *path; sender = dbus_message_get_sender(msg); agent = g_hash_table_lookup(agent_list, sender); if (!agent) return btd_error_does_not_exist(msg); DBG("agent %s", agent->owner); if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID) == FALSE) return btd_error_invalid_args(msg); if (g_str_equal(path, agent->path) == FALSE) return btd_error_does_not_exist(msg); agent_disconnect(conn, agent); return dbus_message_new_method_return(msg); } static DBusMessage *request_default(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct agent *agent; const char *sender, *path; sender = dbus_message_get_sender(msg); agent = g_hash_table_lookup(agent_list, sender); if (!agent) return btd_error_does_not_exist(msg); if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID) == FALSE) return btd_error_invalid_args(msg); if (g_str_equal(path, agent->path) == FALSE) return btd_error_does_not_exist(msg); if (!add_default_agent(agent)) return btd_error_failed(msg, "Failed to set as default"); return dbus_message_new_method_return(msg); } static const GDBusMethodTable methods[] = { { GDBUS_METHOD("RegisterAgent", GDBUS_ARGS({ "agent", "o"}, { "capability", "s" }), NULL, register_agent) }, { GDBUS_METHOD("UnregisterAgent", GDBUS_ARGS({ "agent", "o" }), NULL, unregister_agent) }, { GDBUS_METHOD("RequestDefaultAgent", GDBUS_ARGS({ "agent", "o" }), NULL, request_default ) }, { } }; void btd_agent_init(void) { agent_list = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, agent_destroy); default_agents = queue_new(); g_dbus_register_interface(btd_get_dbus_connection(), "/org/bluez", "org.bluez.AgentManager1", methods, NULL, NULL, NULL, NULL); } void btd_agent_cleanup(void) { g_dbus_unregister_interface(btd_get_dbus_connection(), "/org/bluez", "org.bluez.AgentManager1"); g_hash_table_destroy(agent_list); queue_destroy(default_agents, NULL); } bluez-5.87/src/PaxHeaders/uuid-helper.h0000644000000000000000000000005014015011623015027 xustar0020 atime=1783092818 20 ctime=1783094106 bluez-5.87/src/uuid-helper.h0000644000000000000000000000063014015011623014507 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ char *bt_modalias(uint16_t source, uint16_t vendor, uint16_t product, uint16_t version); char *bt_uuid2string(uuid_t *uuid); char *bt_name2string(const char *string); int bt_string2uuid(uuid_t *uuid, const char *string); bluez-5.87/src/PaxHeaders/storage.c0000644000000000000000000000005015063045254014256 xustar0020 atime=1783093757 20 ctime=1783094106 bluez-5.87/src/storage.c0000644000000000000000000000613615063045254013745 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "bluetooth/uuid.h" #include "textfile.h" #include "uuid-helper.h" #include "storage.h" /* When all services should trust a remote device */ #define GLOBAL_TRUST "[all]" struct match { GSList *keys; char *pattern; }; int read_discoverable_timeout(const char *src, int *timeout) { char filename[PATH_MAX], *str; create_name(filename, PATH_MAX, src, "config"); str = textfile_get(filename, "discovto"); if (!str) return -ENOENT; if (sscanf(str, "%d", timeout) != 1) { free(str); return -ENOENT; } free(str); return 0; } int read_pairable_timeout(const char *src, int *timeout) { char filename[PATH_MAX], *str; create_name(filename, PATH_MAX, src, "config"); str = textfile_get(filename, "pairto"); if (!str) return -ENOENT; if (sscanf(str, "%d", timeout) != 1) { free(str); return -ENOENT; } free(str); return 0; } int read_on_mode(const char *src, char *mode, int length) { char filename[PATH_MAX], *str; create_name(filename, PATH_MAX, src, "config"); str = textfile_get(filename, "onmode"); if (!str) return -ENOENT; strncpy(mode, str, length); mode[length - 1] = '\0'; free(str); return 0; } int read_local_name(const bdaddr_t *bdaddr, char *name) { char filename[PATH_MAX], *str; int len; char addr[18]; ba2str(bdaddr, addr); create_filename(filename, PATH_MAX, "/%s/config", addr); str = textfile_get(filename, "name"); if (!str) return -ENOENT; len = strlen(str); if (len > HCI_MAX_NAME_LENGTH) str[HCI_MAX_NAME_LENGTH] = '\0'; strcpy(name, str); free(str); return 0; } sdp_record_t *record_from_string(const char *str) { sdp_record_t *rec; int size, i, len; uint8_t *pdata; char tmp[3]; size = strlen(str)/2; pdata = g_malloc0(size); tmp[2] = 0; for (i = 0; i < size; i++) { memcpy(tmp, str + (i * 2), 2); pdata[i] = (uint8_t) strtol(tmp, NULL, 16); } rec = sdp_extract_pdu(pdata, size, &len); g_free(pdata); return rec; } sdp_record_t *find_record_in_list(sdp_list_t *recs, const char *uuid) { sdp_list_t *seq; for (seq = recs; seq; seq = seq->next) { sdp_record_t *rec = (sdp_record_t *) seq->data; sdp_list_t *svcclass = NULL; char *uuid_str; if (sdp_get_service_classes(rec, &svcclass) < 0) continue; /* Extract the uuid */ uuid_str = bt_uuid2string(svcclass->data); if (!uuid_str) { sdp_list_free(svcclass, free); continue; } if (!strcasecmp(uuid_str, uuid)) { sdp_list_free(svcclass, free); free(uuid_str); return rec; } sdp_list_free(svcclass, free); free(uuid_str); } return NULL; } bluez-5.87/src/PaxHeaders/profile.h0000644000000000000000000000005015142177620014260 xustar0020 atime=1783093608 20 ctime=1783094106 bluez-5.87/src/profile.h0000644000000000000000000000632215142177620013744 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * */ #define BTD_PROFILE_PRIORITY_LOW 0 #define BTD_PROFILE_PRIORITY_MEDIUM 1 #define BTD_PROFILE_PRIORITY_HIGH 2 #define BTD_PROFILE_BEARER_ANY 0 #define BTD_PROFILE_BEARER_LE 1 #define BTD_PROFILE_BEARER_BREDR 2 struct btd_service; #define BTD_PROFILE_UUID_CB(func_, ...) \ { \ .func = (func_), \ .count = ARRAY_SIZE(((const char *[]) { __VA_ARGS__ })), \ .uuids = ((const char *[]) { __VA_ARGS__ }), \ } struct btd_profile_uuid_cb { void (*func)(struct btd_service *service); unsigned int count; const char **uuids; }; struct btd_profile { const char *name; int priority; /* Indicates which bearer type this profile belongs to. Some profiles * may exist in both BR/EDR and LE, in which case they should be * registered with BTD_PROFILE_BEARER_ANY. */ int bearer; const char *local_uuid; const char *remote_uuid; bool auto_connect; /* Some profiles are considered safe to be handled internally and also * be exposed in the GATT API. This flag give such profiles exception * from being claimed internally. */ bool external; /* Indicates the profile is experimental and shall only be registered * when experimental has been enabled (see: main.conf:Experimental). */ bool experimental; /* Indicates the profile for testing only and shall only be registered * when testing has been enabled (see: main.conf:Testing). */ bool testing; /* Indicates the profile should be ordered after profiles providing * these remote uuids when connecting. The callback function is called * when all uuids have finished connecting (successfully or not). */ struct btd_profile_uuid_cb after_services; int (*device_probe) (struct btd_service *service); void (*device_remove) (struct btd_service *service); int (*connect) (struct btd_service *service); int (*disconnect) (struct btd_service *service); int (*accept) (struct btd_service *service); int (*adapter_probe) (struct btd_profile *p, struct btd_adapter *adapter); void (*adapter_remove) (struct btd_profile *p, struct btd_adapter *adapter); }; void btd_profile_foreach(void (*func)(struct btd_profile *p, void *data), void *data); int btd_profile_register(struct btd_profile *profile); void btd_profile_unregister(struct btd_profile *profile); typedef bool (*btd_profile_prop_exists)(const char *uuid, struct btd_device *dev, void *user_data); typedef bool (*btd_profile_prop_get)(const char *uuid, struct btd_device *dev, DBusMessageIter *iter, void *user_data); 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); bool btd_profile_remove_custom_prop(const char *uuid, const char *name); void btd_profile_init(void); void btd_profile_cleanup(void); struct btd_profile *btd_profile_find_remote_uuid(const char *uuid); typedef const struct btd_profile *(*btd_profile_list_get)(void *item, void *user_data); GSList *btd_profile_sort_list(GSList *list, btd_profile_list_get get, void *user_data); bluez-5.87/src/PaxHeaders/plugin.c0000644000000000000000000000005015063045254014110 xustar0020 atime=1783093828 20 ctime=1783094106 bluez-5.87/src/plugin.c0000644000000000000000000001211715063045254013573 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "btio/btio.h" #include "src/plugin.h" #include "src/log.h" #include "src/btd.h" #define IS_ENABLED(x) (x) static GSList *plugins = NULL; struct bluetooth_plugin { void *handle; const struct bluetooth_plugin_desc *desc; }; static int compare_priority(gconstpointer a, gconstpointer b) { const struct bluetooth_plugin_desc *plugin1 = a; const struct bluetooth_plugin_desc *plugin2 = b; return plugin2->priority - plugin1->priority; } static int init_plugin(const struct bluetooth_plugin_desc *desc) { int err; err = desc->init(); if (err < 0) { if (err == -ENOSYS || err == -ENOTSUP) DBG("System does not support %s plugin", desc->name); else error("Failed to init %s plugin", desc->name); } return err; } static gboolean add_external_plugin(void *handle, const struct bluetooth_plugin_desc *desc) { struct bluetooth_plugin *plugin; if (desc->init == NULL) return FALSE; if (g_str_equal(desc->version, VERSION) == FALSE) { error("Version mismatch for %s", desc->name); return FALSE; } plugin = g_try_new0(struct bluetooth_plugin, 1); if (plugin == NULL) return FALSE; plugin->handle = handle; plugin->desc = desc; if (init_plugin(desc) < 0) { g_free(plugin); return FALSE; } __btd_enable_debug(desc->debug_start, desc->debug_stop); plugins = g_slist_append(plugins, plugin); DBG("Plugin %s loaded", desc->name); return TRUE; } static void add_plugin(void *data, void *user_data) { struct bluetooth_plugin_desc *desc = data; struct bluetooth_plugin *plugin; DBG("Loading %s plugin", desc->name); plugin = g_try_new0(struct bluetooth_plugin, 1); if (plugin == NULL) return; plugin->desc = desc; if (init_plugin(desc) < 0) { g_free(plugin); return; } plugins = g_slist_append(plugins, plugin); DBG("Plugin %s loaded", desc->name); } static gboolean enable_plugin(const char *name, char **cli_enable, char **cli_disable) { if (cli_disable) { for (; *cli_disable; cli_disable++) if (g_pattern_match_simple(*cli_disable, name)) break; if (*cli_disable) { info("Excluding (cli) %s", name); return FALSE; } } if (cli_enable) { for (; *cli_enable; cli_enable++) if (g_pattern_match_simple(*cli_enable, name)) break; if (!*cli_enable) { info("Ignoring (cli) %s", name); return FALSE; } } return TRUE; } static void external_plugin_init(char **cli_enabled, char **cli_disabled) { GDir *dir; const char *file; info("Using external plugins is not officially supported.\n"); info("Consider upstreaming your plugins into the BlueZ project."); if (strlen(PLUGINDIR) == 0) return; DBG("Loading plugins %s", PLUGINDIR); dir = g_dir_open(PLUGINDIR, 0, NULL); if (!dir) return; while ((file = g_dir_read_name(dir)) != NULL) { const struct bluetooth_plugin_desc *desc; void *handle; char *filename; if (g_str_has_prefix(file, "lib") == TRUE || g_str_has_suffix(file, ".so") == FALSE) continue; filename = g_build_filename(PLUGINDIR, file, NULL); handle = dlopen(filename, RTLD_NOW); if (handle == NULL) { error("Can't load plugin %s: %s", filename, dlerror()); g_free(filename); continue; } g_free(filename); desc = dlsym(handle, "bluetooth_plugin_desc"); if (desc == NULL) { error("Can't load plugin description: %s", dlerror()); dlclose(handle); continue; } if (!enable_plugin(desc->name, cli_enabled, cli_disabled)) { dlclose(handle); continue; } if (add_external_plugin(handle, desc) == FALSE) dlclose(handle); } g_dir_close(dir); } #include "src/builtin.h" void plugin_init(const char *enable, const char *disable) { GSList *builtins = NULL; char **cli_disabled = NULL; char **cli_enabled = NULL; unsigned int i; /* Make a call to BtIO API so its symbols got resolved before the * plugins are loaded. */ bt_io_error_quark(); if (enable) cli_enabled = g_strsplit_set(enable, ", ", -1); if (disable) cli_disabled = g_strsplit_set(disable, ", ", -1); DBG("Loading builtin plugins"); for (i = 0; __bluetooth_builtin[i]; i++) { if (!enable_plugin(__bluetooth_builtin[i]->name, cli_enabled, cli_disabled)) continue; builtins = g_slist_insert_sorted(builtins, (void *) __bluetooth_builtin[i], compare_priority); } g_slist_foreach(builtins, add_plugin, NULL); if IS_ENABLED(EXTERNAL_PLUGINS) external_plugin_init(cli_enabled, cli_disabled); g_slist_free(builtins); g_strfreev(cli_enabled); g_strfreev(cli_disabled); } void plugin_cleanup(void) { GSList *list; DBG("Cleanup plugins"); for (list = plugins; list; list = list->next) { struct bluetooth_plugin *plugin = list->data; if (plugin->desc->exit) plugin->desc->exit(); if (plugin->handle != NULL) dlclose(plugin->handle); g_free(plugin); } g_slist_free(plugins); } bluez-5.87/src/PaxHeaders/profile.c0000644000000000000000000000005015221752714014254 xustar0020 atime=1783092685 20 ctime=1783094106 bluez-5.87/src/profile.c0000644000000000000000000017642215221752714013751 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "bluetooth/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 "btd.h" #include "adapter.h" #include "device.h" #include "profile.h" #include "service.h" #define DUN_DEFAULT_CHANNEL 1 #define SPP_DEFAULT_CHANNEL 3 #define HSP_HS_DEFAULT_CHANNEL 6 #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 BT_RX_MTU 32767 #define BTD_PROFILE_PSM_AUTO -1 #define BTD_PROFILE_CHAN_AUTO -1 #define HFP_HF_RECORD \ " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " #define HFP_AG_RECORD \ " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " /* SDP record for Headset role of HSP 1.2 profile with Erratum 3507 */ #define HSP_HS_RECORD \ " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " #define HSP_AG_RECORD \ " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " #define SPP_RECORD \ " \ \ \ \ \ %s \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " #define DUN_RECORD \ " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " #define OPP_RECORD \ " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " #define FTP_RECORD \ " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " #define PCE_RECORD \ " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " #define PSE_RECORD \ " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " #define MAS_RECORD \ " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " #define MNS_RECORD \ " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " #define SYNC_RECORD \ " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " #define GENERIC_RECORD \ " \ \ \ \ \ \ \ \ \ \ \ %s \ \ %s \ \ \ \ \ \ \ \ %s \ \ \ \ " 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; uint16_t imtu; 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); } } static struct btd_profile *btd_profile_find_uuid(const char *uuid) { GSList *l, *next; for (l = profiles; l != NULL; l = next) { struct btd_profile *p = l->data; if (!g_strcmp0(p->local_uuid, uuid)) return p; next = g_slist_next(l); } for (l = ext_profiles; l != NULL; l = next) { struct ext_profile *ext = l->data; struct btd_profile *p = &ext->p; if (!g_strcmp0(p->local_uuid, uuid)) return p; next = g_slist_next(l); } return NULL; } struct btd_profile *btd_profile_find_remote_uuid(const char *uuid) { GSList *l, *next; for (l = profiles; l != NULL; l = next) { struct btd_profile *p = l->data; if (!g_strcmp0(p->remote_uuid, uuid)) return p; next = g_slist_next(l); } for (l = ext_profiles; l != NULL; l = next) { struct ext_profile *ext = l->data; struct btd_profile *p = &ext->p; if (!g_strcmp0(p->remote_uuid, uuid)) return p; next = g_slist_next(l); } return NULL; } int btd_profile_register(struct btd_profile *profile) { if ((profile->bearer == BTD_PROFILE_BEARER_LE && btd_opts.mode == BT_MODE_BREDR) || (profile->bearer == BTD_PROFILE_BEARER_BREDR && btd_opts.mode == BT_MODE_LE)) { DBG("Bearer not enabled"); return -ENOTSUP; } if (profile->experimental && !(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) { DBG("D-Bus experimental not enabled"); return -ENOTSUP; } if (profile->testing && !(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_TESTING)) { DBG("D-Bus testing not enabled"); return -ENOTSUP; } 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 int get_supported_features(const sdp_record_t *rec, const char *uuid) { sdp_data_t *data; if (strcasecmp(uuid, HSP_AG_UUID) == 0) { /* HSP AG role does not provide any features */ return -ENOENT; } else if (strcasecmp(uuid, HSP_HS_UUID) == 0) { /* HSP HS role provides Remote Audio Volume Control */ data = sdp_data_get(rec, SDP_ATTR_REMOTE_AUDIO_VOLUME_CONTROL); if (!data || data->dtd != SDP_BOOL) return -ENOENT; else return data->val.int8 ? 0x1 : 0x0; } data = sdp_data_get(rec, SDP_ATTR_SUPPORTED_FEATURES); if (!data || data->dtd != SDP_UINT16) { return -ENOENT; } 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, features; bool has_features = false; 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) { features = get_supported_features(rec, remote_uuid); if (features >= 0) { conn->features = features; has_features = true; } 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 (has_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)) { if (err) { error("%s failed %s", ext->name, err->message); g_error_free(io_err); io_err = NULL; } else { error("Unable to get connect data for %s: %s", ext->name, io_err->message); 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); if (!btd_adapter_is_uuid_allowed(adapter_find(&src), uuid)) { info("UUID %s is not allowed. Ignoring the connection", uuid); return; } 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; const char *uuid = ext->service ? ext->service : ext->uuid; 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; } if (!btd_adapter_is_uuid_allowed(adapter_find(&src), uuid)) { info("UUID %s is not allowed. Ignoring the connection", uuid); 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); if (ext->imtu) bt_io_set(io, NULL, BT_IO_OPT_IMTU, ext->imtu, 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); 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_hs_record(struct ext_profile *ext, struct ext_io *l2cap, struct ext_io *rfcomm) { /* HSP 1.2: By default Remote Audio Volume Control is off */ return g_strdup_printf(HSP_HS_RECORD, rfcomm->chan, ext->version, ext->name, (ext->features & 0x1) ? "true" : "false"); } 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("", 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 " \ \ \ " #define VERSION_ATTR \ " \ \ \ \ \ \ \ " 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), "", 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; uint16_t imtu; } 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 = 0x0108, }, { .uuid = HSP_HS_UUID, .name = "Headset unit", .priority = BTD_PROFILE_PRIORITY_HIGH, .remote_uuid = HSP_AG_UUID, .channel = HSP_HS_DEFAULT_CHANNEL, .authorize = true, .auto_connect = true, .get_record = get_hsp_hs_record, .version = 0x0102, }, { .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 = 0x0108, /* HFP 1.7.2: By default features bitfield is 0b001001 */ .features = 0x09, }, { .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, .imtu = BT_RX_MTU, }, { .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 = 0x0103, .imtu = BT_RX_MTU, }, { .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, .imtu = BT_RX_MTU, }, { .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, .imtu = BT_RX_MTU, }, { .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 = 0x0104, .imtu = BT_RX_MTU, }, }; 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->imtu) ext->imtu = settings->imtu; 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); if (ext->service == NULL) return -EINVAL; } 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); btd_adapter_foreach(adapter_add_profile, &ext->p); return ext; } static void remove_ext(struct ext_profile *ext) { btd_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 (btd_profile_find_uuid(uuid)) { warn("%s tried to register %s which is already registered", sender, uuid); return btd_error_not_permitted(msg, "UUID already registered"); } 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"); } /* Stable sort of a list according to profile priority & after_services */ GSList *btd_profile_sort_list(GSList *list, btd_profile_list_get get, void *user_data) { GSList *result = NULL; GSList *remain = list; GHashTable *uuids; GSList *entry; const struct btd_profile *p; uuids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); /* Unsatisfied remote uuids */ for (entry = remain; entry; entry = g_slist_next(entry)) { p = get(entry->data, user_data); if (p->remote_uuid) g_hash_table_add(uuids, g_strdup(p->remote_uuid)); } /* Sort */ while (remain) { GSList *first_entry = remain; int max_priority = INT_MIN; /* Find max priority */ for (entry = remain; entry; entry = g_slist_next(entry)) { p = get(entry->data, user_data); if (p->priority > max_priority) { first_entry = entry; max_priority = p->priority; } } /* Find max priority entry with no unsatisfied dependencies */ for (entry = remain; entry; entry = g_slist_next(entry)) { unsigned int i; p = get(entry->data, user_data); if (p->priority < max_priority) continue; for (i = 0; i < p->after_services.count; ++i) if (g_hash_table_contains(uuids, p->after_services.uuids[i])) break; if (i == p->after_services.count) break; } /* If cyclic dependencies: break preserving priority & order */ if (!entry) entry = first_entry; p = get(entry->data, user_data); if (p->remote_uuid) g_hash_table_remove(uuids, p->remote_uuid); remain = g_slist_remove_link(remain, entry); result = g_slist_concat(result, entry); } g_hash_table_destroy(uuids); return result; } bluez-5.87/src/PaxHeaders/rfkill.c0000644000000000000000000000005015063045254014075 xustar0020 atime=1783093736 20 ctime=1783094106 bluez-5.87/src/rfkill.c0000644000000000000000000000722215063045254013561 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "log.h" #include "adapter.h" #include "btd.h" enum rfkill_type { RFKILL_TYPE_ALL = 0, RFKILL_TYPE_WLAN, RFKILL_TYPE_BLUETOOTH, RFKILL_TYPE_UWB, RFKILL_TYPE_WIMAX, RFKILL_TYPE_WWAN, }; enum rfkill_operation { RFKILL_OP_ADD = 0, RFKILL_OP_DEL, RFKILL_OP_CHANGE, RFKILL_OP_CHANGE_ALL, }; struct rfkill_event { uint32_t idx; uint8_t type; uint8_t op; uint8_t soft; uint8_t hard; }; #define RFKILL_EVENT_SIZE_V1 8 #define RFKILL_DEVICE_PATH "/dev/rfkill" static int get_adapter_id_for_rfkill(uint32_t rfkill_id) { char sysname[PATH_MAX]; int namefd; snprintf(sysname, sizeof(sysname) - 1, "/sys/class/rfkill/rfkill%u/name", rfkill_id); namefd = open(sysname, O_RDONLY); if (namefd < 0) return -1; memset(sysname, 0, sizeof(sysname)); if (read(namefd, sysname, sizeof(sysname) - 1) < 4) { close(namefd); return -1; } close(namefd); if (g_str_has_prefix(sysname, "hci") == FALSE) return -1; return atoi(sysname + 3); } int rfkill_get_blocked(uint16_t index) { int fd; int blocked = -1; fd = open(RFKILL_DEVICE_PATH, O_RDWR); if (fd < 0) { DBG("Failed to open RFKILL control device"); return -1; } while (1) { struct rfkill_event event = { 0 }; int id; ssize_t len; len = read(fd, &event, sizeof(event)); if (len < RFKILL_EVENT_SIZE_V1) break; id = get_adapter_id_for_rfkill(event.idx); if (index == id) { blocked = event.soft || event.hard; break; } } close(fd); return blocked; } static gboolean rfkill_event(GIOChannel *chan, GIOCondition cond, gpointer data) { struct rfkill_event event = { 0 }; struct btd_adapter *adapter; bool blocked = false; ssize_t len; int fd, id; if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR)) return FALSE; fd = g_io_channel_unix_get_fd(chan); len = read(fd, &event, sizeof(event)); if (len < 0) { if (errno == EAGAIN) return TRUE; return FALSE; } if (len < RFKILL_EVENT_SIZE_V1) return TRUE; DBG("RFKILL event idx %u type %u op %u soft %u hard %u", event.idx, event.type, event.op, event.soft, event.hard); if (event.soft || event.hard) blocked = true; if (event.op != RFKILL_OP_CHANGE) return TRUE; if (event.type != RFKILL_TYPE_BLUETOOTH && event.type != RFKILL_TYPE_ALL) return TRUE; id = get_adapter_id_for_rfkill(event.idx); if (id < 0) return TRUE; adapter = adapter_find_by_id(id); if (!adapter) return TRUE; DBG("RFKILL unblock for hci%d", id); if (blocked) btd_adapter_set_blocked(adapter); else btd_adapter_restore_powered(adapter); return TRUE; } static guint watch = 0; void rfkill_init(void) { int fd; GIOChannel *channel; errno = 0; fd = open(RFKILL_DEVICE_PATH, O_RDWR); if (fd < 0) { if (errno == ENOENT) { DBG("No RFKILL device available at '%s'", RFKILL_DEVICE_PATH); } else { error("Failed to open RFKILL control device: %s", strerror(errno)); } return; } channel = g_io_channel_unix_new(fd); g_io_channel_set_close_on_unref(channel, TRUE); watch = g_io_add_watch(channel, G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR, rfkill_event, NULL); g_io_channel_unref(channel); } void rfkill_exit(void) { if (watch == 0) return; g_source_remove(watch); watch = 0; } bluez-5.87/src/PaxHeaders/adapter.c0000644000000000000000000000005015221752714014234 xustar0020 atime=1783092685 20 ctime=1783094106 bluez-5.87/src/adapter.c0000644000000000000000000103734415221752714013731 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/hci_lib.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "bluetooth/uuid.h" #include "bluetooth/mgmt.h" #include "gdbus/gdbus.h" #include "log.h" #include "textfile.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/timeout.h" #include "btio/btio.h" #include "btd.h" #include "sdpd.h" #include "adapter.h" #include "device.h" #include "profile.h" #include "dbus-common.h" #include "error.h" #include "uuid-helper.h" #include "agent.h" #include "storage.h" #include "attrib/gattrib.h" #include "attrib/att.h" #include "attrib/gatt.h" #include "gatt-database.h" #include "advertising.h" #include "adv_monitor.h" #include "eir.h" #include "battery.h" #define MODE_OFF 0x00 #define MODE_CONNECTABLE 0x01 #define MODE_DISCOVERABLE 0x02 #define MODE_UNKNOWN 0xff #define CONN_SCAN_TIMEOUT (3) #define IDLE_DISCOV_TIMEOUT (5) #define TEMP_DEV_TIMEOUT (3 * 60) #define BONDING_TIMEOUT (2 * 60) #define SCAN_TYPE_BREDR (1 << BDADDR_BREDR) #define SCAN_TYPE_LE ((1 << BDADDR_LE_PUBLIC) | (1 << BDADDR_LE_RANDOM)) #define SCAN_TYPE_DUAL (SCAN_TYPE_BREDR | SCAN_TYPE_LE) #define HCI_RSSI_INVALID 127 #define DISTANCE_VAL_INVALID 0x7FFF #define PATHLOSS_MAX 137 /* * These are known security keys that have been compromised. * If this grows or there are needs to be platform specific, it is * conceivable that these could be read from a config file. */ static const struct mgmt_blocked_key_info blocked_keys[] = { /* Google Titan Security Keys */ { HCI_BLOCKED_KEY_TYPE_LTK, {0xbf, 0x01, 0xfb, 0x9d, 0x4e, 0xf3, 0xbc, 0x36, 0xd8, 0x74, 0xf5, 0x39, 0x41, 0x38, 0x68, 0x4c}}, { HCI_BLOCKED_KEY_TYPE_IRK, {0xa5, 0x99, 0xba, 0xe4, 0xe1, 0x7c, 0xa6, 0x18, 0x22, 0x8e, 0x07, 0x56, 0xb4, 0xe8, 0x5f, 0x01}}, }; struct mgmt_exp_uuid { uint8_t val[16]; const char *str; }; /* d4992530-b9ec-469f-ab01-6c481c47da1c */ static const struct mgmt_exp_uuid debug_uuid = { .val = { 0x1c, 0xda, 0x47, 0x1c, 0x48, 0x6c, 0x01, 0xab, 0x9f, 0x46, 0xec, 0xb9, 0x30, 0x25, 0x99, 0xd4 }, .str = "d4992530-b9ec-469f-ab01-6c481c47da1c" }; /* 671b10b5-42c0-4696-9227-eb28d1b049d6 */ static const struct mgmt_exp_uuid le_simult_central_peripheral_uuid = { .val = { 0xd6, 0x49, 0xb0, 0xd1, 0x28, 0xeb, 0x27, 0x92, 0x96, 0x46, 0xc0, 0x42, 0xb5, 0x10, 0x1b, 0x67 }, .str = "671b10b5-42c0-4696-9227-eb28d1b049d6" }; /* 330859bc-7506-492d-9370-9a6f0614037f */ static const struct mgmt_exp_uuid quality_report_uuid = { .val = { 0x7f, 0x03, 0x14, 0x06, 0x6f, 0x9a, 0x70, 0x93, 0x2d, 0x49, 0x06, 0x75, 0xbc, 0x59, 0x08, 0x33 }, .str = "330859bc-7506-492d-9370-9a6f0614037f" }; /* 15c0a148-c273-11ea-b3de-0242ac130004 */ static const struct mgmt_exp_uuid rpa_resolution_uuid = { .val = { 0x04, 0x00, 0x13, 0xac, 0x42, 0x02, 0xde, 0xb3, 0xea, 0x11, 0x73, 0xc2, 0x48, 0xa1, 0xc0, 0x15 }, .str = "15c0a148-c273-11ea-b3de-0242ac130004" }; /* a6695ace-ee7f-4fb9-881a-5fac66c629af */ static const struct mgmt_exp_uuid codec_offload_uuid = { .val = { 0xaf, 0x29, 0xc6, 0x66, 0xac, 0x5f, 0x1a, 0x88, 0xb9, 0x4f, 0x7f, 0xee, 0xce, 0x5a, 0x69, 0xa6 }, .str = "a6695ace-ee7f-4fb9-881a-5fac66c629af" }; /* 6fbaf188-05e0-496a-9885-d6ddfdb4e03e */ static const struct mgmt_exp_uuid iso_socket_uuid = { .val = { 0x3e, 0xe0, 0xb4, 0xfd, 0xdd, 0xd6, 0x85, 0x98, 0x6a, 0x49, 0xe0, 0x05, 0x88, 0xf1, 0xba, 0x6f }, .str = "6fbaf188-05e0-496a-9885-d6ddfdb4e03e" }; static DBusConnection *dbus_conn = NULL; static uint32_t kernel_features = 0; static GList *adapter_list = NULL; static unsigned int adapter_remaining = 0; static bool powering_down = false; static GSList *adapters = NULL; static struct mgmt *mgmt_primary = NULL; static uint8_t mgmt_version = 0; static uint8_t mgmt_revision = 0; static GSList *adapter_drivers = NULL; static GSList *disconnect_list = NULL; static GSList *conn_fail_list = NULL; struct link_key_info { bdaddr_t bdaddr; uint8_t bdaddr_type; unsigned char key[16]; uint8_t type; uint8_t pin_len; bool is_blocked; }; struct smp_ltk_info { bdaddr_t bdaddr; uint8_t bdaddr_type; uint8_t authenticated; bool central; uint8_t enc_size; uint16_t ediv; uint64_t rand; uint8_t val[16]; bool is_blocked; }; struct irk_info { bdaddr_t bdaddr; uint8_t bdaddr_type; uint8_t val[16]; bool is_blocked; }; struct conn_param { bdaddr_t bdaddr; uint8_t bdaddr_type; uint16_t min_interval; uint16_t max_interval; uint16_t latency; uint16_t timeout; }; struct discovery_filter { uint8_t type; char *pattern; uint16_t pathloss; int16_t rssi; GSList *uuids; bool duplicate; bool discoverable; bool auto_connect; }; struct discovery_client { struct btd_adapter *adapter; DBusMessage *msg; char *owner; guint watch; struct discovery_filter *discovery_filter; }; struct service_auth { guint id; unsigned int svc_id; service_auth_cb cb; void *user_data; const char *uuid; struct btd_device *device; struct btd_adapter *adapter; struct agent *agent; /* NULL for queued auths */ }; struct btd_adapter_pin_cb_iter { GSList *it; /* current callback function */ unsigned int attempt; /* number of times it() was called */ /* When the iterator reaches the end, it is NULL and attempt is 0 */ }; struct exp_pending { struct btd_adapter *adapter; unsigned int id; }; enum { ADAPTER_POWER_STATE_OFF, ADAPTER_POWER_STATE_ON, ADAPTER_POWER_STATE_ON_DISABLING, ADAPTER_POWER_STATE_OFF_ENABLING, ADAPTER_POWER_STATE_OFF_BLOCKED, }; struct btd_adapter { int ref_count; uint16_t dev_id; struct mgmt *mgmt; bdaddr_t bdaddr; /* controller Bluetooth address */ uint8_t bdaddr_type; /* address type */ uint8_t version; /* controller core spec version */ uint32_t dev_class; /* controller class of device */ char *name; /* controller device name */ char *short_name; /* controller short name */ uint32_t supported_settings; /* controller supported settings */ uint32_t pending_settings; /* pending controller settings */ uint32_t power_state; /* the power state */ uint32_t current_settings; /* current controller settings */ char *path; /* adapter object path */ uint16_t manufacturer; /* adapter manufacturer */ uint8_t major_class; /* configured major class */ uint8_t minor_class; /* configured minor class */ char *system_name; /* configured system name */ char *modalias; /* device id (modalias) */ bool stored_discoverable; /* stored discoverable mode */ uint32_t discoverable_timeout; /* discoverable time(sec) */ uint32_t pairable_timeout; /* pairable time(sec) */ char *current_alias; /* current adapter name alias */ char *stored_alias; /* stored adapter name alias */ bool discovering; /* discovering property state */ bool filtered_discovery; /* we are doing filtered discovery */ bool no_scan_restart_delay; /* when this flag is set, restart scan * without delay */ uint8_t discovery_type; /* current active discovery type */ uint8_t discovery_enable; /* discovery enabled/disabled */ bool discovery_suspended; /* discovery has been suspended */ bool discovery_discoverable; /* discoverable while discovering */ GSList *discovery_list; /* list of discovery clients */ GSList *set_filter_list; /* list of clients that specified * filter, but don't scan yet */ /* current discovery filter, if any */ struct mgmt_cp_start_service_discovery *current_discovery_filter; struct discovery_client *client; /* active discovery client */ GSList *discovery_found; /* list of found devices */ unsigned int discovery_idle_timeout; /* timeout between discovery * runs */ unsigned int passive_scan_timeout; /* timeout between passive scans */ unsigned int pairable_timeout_id; /* pairable timeout id */ guint auth_idle_id; /* Pending authorization dequeue */ GQueue *auths; /* Ongoing and pending auths */ bool pincode_requested; /* PIN requested during last bonding */ GSList *connections; /* Connected devices */ GSList *devices; /* Devices structure pointers */ GSList *connect_list; /* Devices to connect when found */ struct btd_device *connect_le; /* LE device waiting to be connected */ sdp_list_t *services; /* Services associated to adapter */ struct btd_gatt_database *database; struct btd_adv_manager *adv_manager; struct btd_adv_monitor_manager *adv_monitor_manager; struct btd_battery_provider_manager *battery_provider_manager; GHashTable *allowed_uuid_set; /* Set of allowed service UUIDs */ gboolean initialized; GSList *pin_callbacks; GSList *msd_callbacks; GSList *drivers; GSList *profiles; struct oob_handler *oob_handler; unsigned int db_id; /* Service event handler for GATT db */ bool is_default; /* true if adapter is default one */ struct queue *exp_pending; struct queue *exps; }; static char *adapter_power_state_str(uint32_t power_state) { switch (power_state) { case ADAPTER_POWER_STATE_OFF: return "off"; case ADAPTER_POWER_STATE_ON: return "on"; case ADAPTER_POWER_STATE_ON_DISABLING: return "on-disabling"; case ADAPTER_POWER_STATE_OFF_ENABLING: return "off-enabling"; case ADAPTER_POWER_STATE_OFF_BLOCKED: return "off-blocked"; } DBG("Invalid power state %d", power_state); return ""; } typedef enum { ADAPTER_AUTHORIZE_DISCONNECTED = 0, ADAPTER_AUTHORIZE_CHECK_CONNECTED } adapter_authorize_type; static struct btd_adapter *btd_adapter_lookup(uint16_t index) { GList *list; for (list = g_list_first(adapter_list); list; list = g_list_next(list)) { struct btd_adapter *adapter = list->data; if (adapter->dev_id == index) return adapter; } return NULL; } struct btd_adapter *btd_adapter_get_default(void) { GList *list; for (list = g_list_first(adapter_list); list; list = g_list_next(list)) { struct btd_adapter *adapter = list->data; if (adapter->is_default) return adapter; } return NULL; } bool btd_adapter_is_default(struct btd_adapter *adapter) { if (!adapter) return false; return adapter->is_default; } uint16_t btd_adapter_get_index(struct btd_adapter *adapter) { if (!adapter) return MGMT_INDEX_NONE; return adapter->dev_id; } bool btd_adapter_has_cable_pairing_devices(struct btd_adapter *adapter) { GSList *l; if (!adapter) return false; for (l = adapter->devices; l; l = l->next) { struct btd_device *device = l->data; if (device_is_cable_pairing(device)) return true; } return false; } static gboolean process_auth_queue(gpointer user_data); static void dev_class_changed_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; const struct mgmt_cod *rp = param; uint32_t dev_class; if (length < sizeof(*rp)) { btd_error(adapter->dev_id, "Wrong size of class of device changed parameters"); return; } dev_class = rp->val[0] | (rp->val[1] << 8) | (rp->val[2] << 16); if (dev_class == adapter->dev_class) return; DBG("Class: 0x%06x", dev_class); adapter->dev_class = dev_class; g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Class"); } static void set_dev_class_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to set device class: %s (0x%02x)", mgmt_errstr(status), status); return; } /* * The parameters are identical and also the task that is * required in both cases. So it is safe to just call the * event handling functions here. */ dev_class_changed_callback(adapter->dev_id, length, param, adapter); } static void set_dev_class(struct btd_adapter *adapter) { struct mgmt_cp_set_dev_class cp; /* * If the controller does not support BR/EDR operation, * there is no point in trying to set a major and minor * class value. * * This is an optimization for Low Energy only controllers. */ if (!(adapter->supported_settings & MGMT_SETTING_BREDR)) return; memset(&cp, 0, sizeof(cp)); /* * Silly workaround for a really stupid kernel bug :( * * All current kernel versions assign the major and minor numbers * straight to dev_class[0] and dev_class[1] without considering * the proper bit shifting. * * To make this work, shift the value in userspace for now until * we get a fixed kernel version. */ cp.major = adapter->major_class & 0x1f; cp.minor = adapter->minor_class << 2; DBG("sending set device class command for index %u", adapter->dev_id); if (mgmt_send(adapter->mgmt, MGMT_OP_SET_DEV_CLASS, adapter->dev_id, sizeof(cp), &cp, set_dev_class_complete, adapter, NULL) > 0) return; btd_error(adapter->dev_id, "Failed to set class of device for index %u", adapter->dev_id); } void btd_adapter_set_class(struct btd_adapter *adapter, uint8_t major, uint8_t minor) { if (adapter->major_class == major && adapter->minor_class == minor) return; DBG("class: major %u minor %u", major, minor); adapter->major_class = major; adapter->minor_class = minor; set_dev_class(adapter); } static uint8_t get_mode(const char *mode) { if (strcasecmp("off", mode) == 0) return MODE_OFF; else if (strcasecmp("connectable", mode) == 0) return MODE_CONNECTABLE; else if (strcasecmp("discoverable", mode) == 0) return MODE_DISCOVERABLE; else return MODE_UNKNOWN; } const char *btd_adapter_get_storage_dir(struct btd_adapter *adapter) { static char dir[25]; if (adapter->bdaddr_type == BDADDR_LE_RANDOM) { strcpy(dir, "static-"); ba2str(&adapter->bdaddr, dir + 7); } else { ba2str(&adapter->bdaddr, dir); } return dir; } uint8_t btd_adapter_get_address_type(struct btd_adapter *adapter) { return adapter->bdaddr_type; } static void store_adapter_info(struct btd_adapter *adapter) { GKeyFile *key_file; GError *gerr = NULL; char filename[PATH_MAX]; char *str; gsize length = 0; gboolean discoverable; key_file = g_key_file_new(); if (adapter->pairable_timeout != btd_opts.pairto) g_key_file_set_integer(key_file, "General", "PairableTimeout", adapter->pairable_timeout); if ((adapter->current_settings & MGMT_SETTING_DISCOVERABLE) && !adapter->discoverable_timeout) discoverable = TRUE; else discoverable = FALSE; g_key_file_set_boolean(key_file, "General", "Discoverable", discoverable); if (adapter->discoverable_timeout != btd_opts.discovto) g_key_file_set_integer(key_file, "General", "DiscoverableTimeout", adapter->discoverable_timeout); if (adapter->stored_alias) g_key_file_set_string(key_file, "General", "Alias", adapter->stored_alias); create_filename(filename, PATH_MAX, "/%s/settings", btd_adapter_get_storage_dir(adapter)); create_file(filename, 0600); str = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, str, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(str); g_key_file_free(key_file); } static void trigger_pairable_timeout(struct btd_adapter *adapter); static void adapter_start(struct btd_adapter *adapter); static void adapter_stop(struct btd_adapter *adapter); static void trigger_passive_scanning(struct btd_adapter *adapter); static bool set_mode(struct btd_adapter *adapter, uint16_t opcode, uint8_t mode); static void settings_changed(struct btd_adapter *adapter, uint32_t settings) { uint32_t changed_mask; changed_mask = adapter->current_settings ^ settings; adapter->current_settings = settings; adapter->pending_settings &= ~changed_mask; DBG("Changed settings: 0x%08x", changed_mask); DBG("Pending settings: 0x%08x", adapter->pending_settings); if (changed_mask & MGMT_SETTING_POWERED) { g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Powered"); if (adapter->current_settings & MGMT_SETTING_POWERED) { adapter_start(adapter); } else { adapter_stop(adapter); if (powering_down) { adapter_remaining--; if (!adapter_remaining) btd_exit(); } } } if ((changed_mask & MGMT_SETTING_LE) && btd_adapter_get_powered(adapter) && (adapter->current_settings & MGMT_SETTING_LE)) trigger_passive_scanning(adapter); if (changed_mask & MGMT_SETTING_DISCOVERABLE) { g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Discoverable"); /* Only persist discoverable setting if it was not set * temporarily by discovery. */ if (!adapter->discovery_discoverable) store_adapter_info(adapter); btd_adv_manager_refresh(adapter->adv_manager); } if (changed_mask & MGMT_SETTING_CONNECTABLE) g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Connectable"); if (changed_mask & MGMT_SETTING_BONDABLE) { g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Pairable"); trigger_pairable_timeout(adapter); } } static void adapter_set_power_state(struct btd_adapter *adapter, uint32_t value) { if (adapter->power_state == value) return; DBG("%s", adapter_power_state_str(value)); adapter->power_state = value; g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "PowerState"); } static void reset_power_state_target(struct btd_adapter *adapter, uint32_t value) { if (value && adapter->power_state == ADAPTER_POWER_STATE_OFF_ENABLING) { adapter_set_power_state(adapter, ADAPTER_POWER_STATE_ON); } else if (!value && adapter->power_state == ADAPTER_POWER_STATE_ON_DISABLING) { adapter_set_power_state(adapter, ADAPTER_POWER_STATE_OFF); } } static void new_settings_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; uint32_t settings; if (length < sizeof(settings)) { btd_error(adapter->dev_id, "Wrong size of new settings parameters"); return; } settings = get_le32(param); if (settings == adapter->current_settings) return; if ((adapter->current_settings ^ settings) & MGMT_SETTING_POWERED) { reset_power_state_target(adapter, settings & MGMT_SETTING_POWERED ? 0x01 : 0x00); } DBG("Settings: 0x%08x", settings); settings_changed(adapter, settings); } struct set_mode_data { struct btd_adapter *adapter; uint32_t setting; uint8_t value; }; static void set_mode_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct set_mode_data *data = user_data; struct btd_adapter *adapter = data->adapter; if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to set mode: %s (0x%02x)", mgmt_errstr(status), status); if (status == MGMT_STATUS_RFKILLED) adapter_set_power_state(adapter, ADAPTER_POWER_STATE_OFF_BLOCKED); adapter->pending_settings &= ~data->setting; if (status != MGMT_STATUS_RFKILLED && data->setting & MGMT_SETTING_POWERED) reset_power_state_target(adapter, data->value); return; } /* * The parameters are identical and also the task that is * required in both cases. So it is safe to just call the * event handling functions here. */ new_settings_callback(adapter->dev_id, length, param, adapter); } static void remove_temporary_devices(struct btd_adapter *adapter) { GSList *l, *next; for (l = adapter->devices; l; l = next) { struct btd_device *dev = l->data; next = g_slist_next(l); if (device_is_temporary(dev)) btd_adapter_remove_device(adapter, dev); } } static bool set_mode(struct btd_adapter *adapter, uint16_t opcode, uint8_t mode) { struct mgmt_mode cp; uint32_t setting = 0; struct set_mode_data *data; memset(&cp, 0, sizeof(cp)); cp.val = mode; switch (opcode) { case MGMT_OP_SET_POWERED: setting = MGMT_SETTING_POWERED; if (adapter->power_state != ADAPTER_POWER_STATE_OFF_BLOCKED) { adapter_set_power_state(adapter, mode ? ADAPTER_POWER_STATE_OFF_ENABLING : ADAPTER_POWER_STATE_ON_DISABLING); } break; case MGMT_OP_SET_CONNECTABLE: setting = MGMT_SETTING_CONNECTABLE; break; case MGMT_OP_SET_FAST_CONNECTABLE: setting = MGMT_SETTING_FAST_CONNECTABLE; break; case MGMT_OP_SET_DISCOVERABLE: setting = MGMT_SETTING_DISCOVERABLE; break; case MGMT_OP_SET_BONDABLE: setting = MGMT_SETTING_BONDABLE; break; } DBG("sending set mode command for index %u", adapter->dev_id); data = g_new0(struct set_mode_data, 1); data->adapter = adapter; data->setting = setting; data->value = mode; if (mgmt_send(adapter->mgmt, opcode, adapter->dev_id, sizeof(cp), &cp, set_mode_complete, data, g_free) > 0) { adapter->pending_settings |= setting; return true; } g_free(data); if (setting == MGMT_SETTING_POWERED) { /* cancel the earlier setting */ adapter_set_power_state(adapter, mode ? ADAPTER_POWER_STATE_OFF : ADAPTER_POWER_STATE_ON); } btd_error(adapter->dev_id, "Failed to set mode for index %u", adapter->dev_id); return false; } static bool set_discoverable(struct btd_adapter *adapter, uint8_t mode, uint16_t timeout) { struct mgmt_cp_set_discoverable cp; struct set_mode_data *data; memset(&cp, 0, sizeof(cp)); cp.val = mode; cp.timeout = htobs(timeout); DBG("sending set mode command for index %u", adapter->dev_id); if (btd_has_kernel_features(KERNEL_CONN_CONTROL)) { if (mode) set_mode(adapter, MGMT_OP_SET_CONNECTABLE, mode); else /* This also disables discoverable so we're done */ return set_mode(adapter, MGMT_OP_SET_CONNECTABLE, mode); } data = g_new0(struct set_mode_data, 1); data->adapter = adapter; data->setting = 0; if (mgmt_send(adapter->mgmt, MGMT_OP_SET_DISCOVERABLE, adapter->dev_id, sizeof(cp), &cp, set_mode_complete, data, g_free) > 0) return true; g_free(data); btd_error(adapter->dev_id, "Failed to set mode for index %u", adapter->dev_id); return false; } static bool pairable_timeout_handler(gpointer user_data) { struct btd_adapter *adapter = user_data; adapter->pairable_timeout_id = 0; set_mode(adapter, MGMT_OP_SET_BONDABLE, 0x00); return FALSE; } static void trigger_pairable_timeout(struct btd_adapter *adapter) { if (adapter->pairable_timeout_id > 0) { timeout_remove(adapter->pairable_timeout_id); adapter->pairable_timeout_id = 0; } if (!(adapter->current_settings & MGMT_SETTING_BONDABLE)) return; if (adapter->pairable_timeout > 0) adapter->pairable_timeout_id = timeout_add_seconds(adapter->pairable_timeout, pairable_timeout_handler, adapter, NULL); } static void local_name_changed_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; const struct mgmt_cp_set_local_name *rp = param; if (length < sizeof(*rp)) { btd_error(adapter->dev_id, "Wrong size of local name changed parameters"); return; } if (!g_strcmp0(adapter->short_name, (const char *) rp->short_name) && !g_strcmp0(adapter->name, (const char *) rp->name)) return; DBG("Name: %s", rp->name); DBG("Short name: %s", rp->short_name); g_free(adapter->name); adapter->name = g_strdup((const char *) rp->name); g_free(adapter->short_name); adapter->short_name = g_strdup((const char *) rp->short_name); /* * Changing the name (even manually via HCI) will update the * current alias property. * * In case the name is empty, use the short name. * * There is a difference between the stored alias (which is * configured by the user) and the current alias. The current * alias is temporary for the lifetime of the daemon. */ if (adapter->name && adapter->name[0] != '\0') { g_free(adapter->current_alias); adapter->current_alias = g_strdup(adapter->name); } else { g_free(adapter->current_alias); adapter->current_alias = g_strdup(adapter->short_name); } DBG("Current alias: %s", adapter->current_alias); if (!adapter->current_alias) return; g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Alias"); } static void set_local_name_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to set local name: %s (0x%02x)", mgmt_errstr(status), status); return; } /* * The parameters are identical and also the task that is * required in both cases. So it is safe to just call the * event handling functions here. */ local_name_changed_callback(adapter->dev_id, length, param, adapter); } static int set_name(struct btd_adapter *adapter, const char *name) { struct mgmt_cp_set_local_name cp; char maxname[MAX_NAME_LENGTH]; memset(maxname, 0, sizeof(maxname)); strncpy(maxname, name, MAX_NAME_LENGTH - 1); if (!g_utf8_validate(maxname, -1, NULL)) { btd_error(adapter->dev_id, "Name change failed: supplied name isn't valid UTF-8"); return -EINVAL; } memset(&cp, 0, sizeof(cp)); strncpy((char *) cp.name, maxname, sizeof(cp.name) - 1); DBG("sending set local name command for index %u", adapter->dev_id); if (mgmt_send(adapter->mgmt, MGMT_OP_SET_LOCAL_NAME, adapter->dev_id, sizeof(cp), &cp, set_local_name_complete, adapter, NULL) > 0) return 0; btd_error(adapter->dev_id, "Failed to set local name for index %u", adapter->dev_id); return -EIO; } int btd_adapter_set_name(struct btd_adapter *adapter, const char *name) { if (g_strcmp0(adapter->system_name, name) == 0) return 0; DBG("name: %s", name); g_free(adapter->system_name); adapter->system_name = g_strdup(name); g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Name"); /* alias is preferred over system name */ if (adapter->stored_alias) return 0; DBG("alias: %s", name); g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Alias"); return set_name(adapter, name); } struct btd_device *btd_adapter_find_device(struct btd_adapter *adapter, const bdaddr_t *dst, uint8_t bdaddr_type) { struct device_addr_type addr; struct btd_device *device; GSList *list; if (!adapter) return NULL; bacpy(&addr.bdaddr, dst); addr.bdaddr_type = bdaddr_type; list = g_slist_find_custom(adapter->devices, &addr, device_addr_type_cmp); if (!list) return NULL; device = list->data; /* * If we're looking up based on public address and the address * was not previously used over this bearer we may need to * update LE or BR/EDR support information. */ if (bdaddr_type == BDADDR_BREDR) device_set_bredr_support(device); else device_set_le_support(device, bdaddr_type); return device; } static int device_path_cmp(gconstpointer a, gconstpointer b) { const struct btd_device *device = a; const char *path = b; const char *dev_path = device_get_path(device); return strcasecmp(dev_path, path); } struct btd_device *btd_adapter_find_device_by_path(struct btd_adapter *adapter, const char *path) { GSList *list; if (!adapter) return NULL; list = g_slist_find_custom(adapter->devices, path, device_path_cmp); if (!list) return NULL; return list->data; } static void uuid_to_uuid128(uuid_t *uuid128, const uuid_t *uuid) { if (uuid->type == SDP_UUID16) sdp_uuid16_to_uuid128(uuid128, uuid); else if (uuid->type == SDP_UUID32) sdp_uuid32_to_uuid128(uuid128, uuid); else memcpy(uuid128, uuid, sizeof(*uuid)); } static bool is_supported_uuid(const uuid_t *uuid) { uuid_t tmp; /* mgmt versions from 1.3 onwards support all types of UUIDs */ if (MGMT_VERSION(mgmt_version, mgmt_revision) >= MGMT_VERSION(1, 3)) return true; uuid_to_uuid128(&tmp, uuid); if (!sdp_uuid128_to_uuid(&tmp)) return false; if (tmp.type != SDP_UUID16) return false; return true; } static void add_uuid_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to add UUID: %s (0x%02x)", mgmt_errstr(status), status); return; } /* * The parameters are identical and also the task that is * required in both cases. So it is safe to just call the * event handling functions here. */ dev_class_changed_callback(adapter->dev_id, length, param, adapter); if (adapter->initialized) g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "UUIDs"); } static int add_uuid(struct btd_adapter *adapter, uuid_t *uuid, uint8_t svc_hint) { struct mgmt_cp_add_uuid cp; uuid_t uuid128; uint128_t uint128; if (!is_supported_uuid(uuid)) { btd_warn(adapter->dev_id, "Ignoring unsupported UUID for addition"); return 0; } uuid_to_uuid128(&uuid128, uuid); ntoh128((uint128_t *) uuid128.value.uuid128.data, &uint128); htob128(&uint128, (uint128_t *) cp.uuid); cp.svc_hint = svc_hint; DBG("sending add uuid command for index %u", adapter->dev_id); if (mgmt_send(adapter->mgmt, MGMT_OP_ADD_UUID, adapter->dev_id, sizeof(cp), &cp, add_uuid_complete, adapter, NULL) > 0) return 0; btd_error(adapter->dev_id, "Failed to add UUID for index %u", adapter->dev_id); return -EIO; } static void remove_uuid_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to remove UUID: %s (0x%02x)", mgmt_errstr(status), status); return; } /* * The parameters are identical and also the task that is * required in both cases. So it is safe to just call the * event handling functions here. */ dev_class_changed_callback(adapter->dev_id, length, param, adapter); if (adapter->initialized) g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "UUIDs"); } static int remove_uuid(struct btd_adapter *adapter, uuid_t *uuid) { struct mgmt_cp_remove_uuid cp; uuid_t uuid128; uint128_t uint128; if (!is_supported_uuid(uuid)) { btd_warn(adapter->dev_id, "Ignoring unsupported UUID for removal"); return 0; } uuid_to_uuid128(&uuid128, uuid); ntoh128((uint128_t *) uuid128.value.uuid128.data, &uint128); htob128(&uint128, (uint128_t *) cp.uuid); DBG("sending remove uuid command for index %u", adapter->dev_id); if (mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_UUID, adapter->dev_id, sizeof(cp), &cp, remove_uuid_complete, adapter, NULL) > 0) return 0; btd_error(adapter->dev_id, "Failed to remove UUID for index %u", adapter->dev_id); return -EIO; } static void clear_uuids_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to clear UUIDs: %s (0x%02x)", mgmt_errstr(status), status); return; } /* * The parameters are identical and also the task that is * required in both cases. So it is safe to just call the * event handling functions here. */ dev_class_changed_callback(adapter->dev_id, length, param, adapter); } static int clear_uuids(struct btd_adapter *adapter) { struct mgmt_cp_remove_uuid cp; memset(&cp, 0, sizeof(cp)); DBG("sending clear uuids command for index %u", adapter->dev_id); if (mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_UUID, adapter->dev_id, sizeof(cp), &cp, clear_uuids_complete, adapter, NULL) > 0) return 0; btd_error(adapter->dev_id, "Failed to clear UUIDs for index %u", adapter->dev_id); return -EIO; } static uint8_t get_uuid_mask(uuid_t *uuid) { if (uuid->type != SDP_UUID16) return 0; switch (uuid->value.uuid16) { case DIALUP_NET_SVCLASS_ID: case CIP_SVCLASS_ID: return 0x42; /* Telephony & Networking */ case IRMC_SYNC_SVCLASS_ID: case OBEX_OBJPUSH_SVCLASS_ID: case OBEX_FILETRANS_SVCLASS_ID: case IRMC_SYNC_CMD_SVCLASS_ID: case PBAP_PSE_SVCLASS_ID: return 0x10; /* Object Transfer */ case HEADSET_SVCLASS_ID: case HANDSFREE_SVCLASS_ID: return 0x20; /* Audio */ case CORDLESS_TELEPHONY_SVCLASS_ID: case INTERCOM_SVCLASS_ID: case FAX_SVCLASS_ID: case SAP_SVCLASS_ID: /* * Setting the telephony bit for the handsfree audio gateway * role is not required by the HFP specification, but the * Nokia 616 carkit is just plain broken! It will refuse * pairing without this bit set. */ case HANDSFREE_AGW_SVCLASS_ID: return 0x40; /* Telephony */ case AUDIO_SOURCE_SVCLASS_ID: case VIDEO_SOURCE_SVCLASS_ID: return 0x08; /* Capturing */ case AUDIO_SINK_SVCLASS_ID: case VIDEO_SINK_SVCLASS_ID: return 0x04; /* Rendering */ case PANU_SVCLASS_ID: case NAP_SVCLASS_ID: case GN_SVCLASS_ID: return 0x02; /* Networking */ default: return 0; } } static int uuid_cmp(const void *a, const void *b) { const sdp_record_t *rec = a; const uuid_t *uuid = b; return sdp_uuid_cmp(&rec->svclass, uuid); } static void adapter_service_insert(struct btd_adapter *adapter, sdp_record_t *rec) { sdp_list_t *browse_list = NULL; uuid_t browse_uuid; gboolean new_uuid; DBG("%s", adapter->path); /* skip record without a browse group */ if (sdp_get_browse_groups(rec, &browse_list) < 0) { DBG("skipping record without browse group"); return; } sdp_uuid16_create(&browse_uuid, PUBLIC_BROWSE_GROUP); /* skip record without public browse group */ if (!sdp_list_find(browse_list, &browse_uuid, sdp_uuid_cmp)) goto done; if (sdp_list_find(adapter->services, &rec->svclass, uuid_cmp) == NULL) new_uuid = TRUE; else new_uuid = FALSE; adapter->services = sdp_list_insert_sorted(adapter->services, rec, record_sort); if (new_uuid) { uint8_t svc_hint = get_uuid_mask(&rec->svclass); add_uuid(adapter, &rec->svclass, svc_hint); } done: sdp_list_free(browse_list, free); } int adapter_service_add(struct btd_adapter *adapter, sdp_record_t *rec) { int ret; /* * If the controller does not support BR/EDR operation, * there is no point in trying to add SDP records. */ if (btd_opts.mode == BT_MODE_LE) return -ENOTSUP; DBG("%s", adapter->path); ret = add_record_to_server(&adapter->bdaddr, rec); if (ret < 0) return ret; adapter_service_insert(adapter, rec); return 0; } void adapter_service_remove(struct btd_adapter *adapter, uint32_t handle) { sdp_record_t *rec; if (!adapter) return; /* * If the controller does not support BR/EDR operation, * there is no point in trying to remote SDP records. */ if (btd_opts.mode == BT_MODE_LE) return; DBG("%s", adapter->path); rec = sdp_record_find(handle); if (!rec) return; adapter->services = sdp_list_remove(adapter->services, rec); if (sdp_list_find(adapter->services, &rec->svclass, uuid_cmp) == NULL) remove_uuid(adapter, &rec->svclass); remove_record_from_server(rec->handle); } static void adapter_add_device(struct btd_adapter *adapter, struct btd_device *device); static struct btd_device *adapter_create_device(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type) { struct btd_device *device; device = device_create(adapter, bdaddr, bdaddr_type); if (!device) return NULL; adapter_add_device(adapter, device); return device; } static void service_auth_cancel(struct service_auth *auth) { DBusError derr; if (auth->svc_id > 0) device_remove_svc_complete_callback(auth->device, auth->svc_id); dbus_error_init(&derr); dbus_set_error_const(&derr, ERROR_INTERFACE ".Canceled", NULL); auth->cb(&derr, auth->user_data); dbus_error_free(&derr); if (auth->agent != NULL) { agent_cancel(auth->agent); agent_unref(auth->agent); } g_free(auth); } static void adapter_remove_device(struct btd_adapter *adapter, struct btd_device *device); void btd_adapter_remove_device(struct btd_adapter *adapter, struct btd_device *dev) { adapter->connect_list = g_slist_remove(adapter->connect_list, dev); adapter_remove_device(adapter, dev); btd_adv_monitor_device_remove(adapter->adv_monitor_manager, dev); adapter->discovery_found = g_slist_remove(adapter->discovery_found, dev); adapter->connections = g_slist_remove(adapter->connections, dev); if (adapter->connect_le == dev) adapter->connect_le = NULL; btd_adapter_cancel_service_auth(adapter, dev); device_remove(dev, TRUE); } struct btd_device *btd_adapter_get_device(struct btd_adapter *adapter, const bdaddr_t *addr, uint8_t addr_type) { struct btd_device *device; if (!adapter) return NULL; device = btd_adapter_find_device(adapter, addr, addr_type); if (device) return device; return adapter_create_device(adapter, addr, addr_type); } struct btd_device *btd_adapter_find_device_by_fd(int fd) { bdaddr_t src, dst; uint8_t dst_type; GIOChannel *io = NULL; GError *gerr = NULL; struct btd_adapter *adapter; io = g_io_channel_unix_new(fd); if (!io) return NULL; bt_io_get(io, &gerr, BT_IO_OPT_SOURCE_BDADDR, &src, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_DEST_TYPE, &dst_type, BT_IO_OPT_INVALID); if (gerr) { error("bt_io_get: %s", gerr->message); g_error_free(gerr); g_io_channel_unref(io); return NULL; } g_io_channel_unref(io); adapter = adapter_find(&src); if (!adapter) return NULL; return btd_adapter_find_device(adapter, &dst, dst_type); } sdp_list_t *btd_adapter_get_services(struct btd_adapter *adapter) { return adapter->services; } static void passive_scanning_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; const struct mgmt_cp_start_discovery *rp = param; DBG("status 0x%02x", status); if (length < sizeof(*rp)) { btd_error(adapter->dev_id, "Wrong size of start scanning return parameters"); return; } if (status == MGMT_STATUS_SUCCESS) { adapter->discovery_type = rp->type; adapter->discovery_enable = 0x01; } } static bool passive_scanning_timeout(gpointer user_data) { struct btd_adapter *adapter = user_data; struct mgmt_cp_start_discovery cp; adapter->passive_scan_timeout = 0; cp.type = SCAN_TYPE_LE; mgmt_send(adapter->mgmt, MGMT_OP_START_DISCOVERY, adapter->dev_id, sizeof(cp), &cp, passive_scanning_complete, adapter, NULL); return FALSE; } static void trigger_passive_scanning(struct btd_adapter *adapter) { if (!(adapter->current_settings & MGMT_SETTING_LE)) return; DBG(""); if (adapter->passive_scan_timeout > 0) { timeout_remove(adapter->passive_scan_timeout); adapter->passive_scan_timeout = 0; } /* * When the kernel background scanning is available, there is * no need to start any discovery. The kernel will keep scanning * as long as devices are in its auto-connection list. */ if (btd_has_kernel_features(KERNEL_CONN_CONTROL)) return; /* * If any client is running a discovery right now, then do not * even try to start passive scanning. * * The discovery procedure is using interleaved scanning and * thus will discover Low Energy devices as well. */ if (adapter->discovery_list) return; if (adapter->discovery_enable == 0x01) return; /* * In case the discovery is suspended (for example for an ongoing * pairing attempt), then also do not start passive scanning. */ if (adapter->discovery_suspended) return; /* * If the list of connectable Low Energy devices is empty, * then do not start passive scanning. */ if (!adapter->connect_list) return; adapter->passive_scan_timeout = timeout_add_seconds(CONN_SCAN_TIMEOUT, passive_scanning_timeout, adapter, NULL); } static void stop_passive_scanning_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; struct btd_device *dev; int err; DBG("status 0x%02x (%s)", status, mgmt_errstr(status)); dev = adapter->connect_le; adapter->connect_le = NULL; /* * When the kernel background scanning is available, there is * no need to stop any discovery. The kernel will handle the * auto-connection by itself. */ if (btd_has_kernel_features(KERNEL_CONN_CONTROL)) return; /* * MGMT_STATUS_REJECTED may be returned from kernel because the passive * scan timer had expired in kernel and passive scan was disabled just * around the time we called stop_passive_scanning(). */ if (status != MGMT_STATUS_SUCCESS && status != MGMT_STATUS_REJECTED) { btd_error(adapter->dev_id, "Stopping passive scanning failed: %s", mgmt_errstr(status)); return; } adapter->discovery_type = 0x00; adapter->discovery_enable = 0x00; if (!dev) { DBG("Device removed while stopping passive scanning"); trigger_passive_scanning(adapter); return; } err = device_connect_le(dev); if (err < 0) { btd_error(adapter->dev_id, "LE auto connection failed: %s (%d)", strerror(-err), -err); trigger_passive_scanning(adapter); } } static void stop_passive_scanning(struct btd_adapter *adapter) { struct mgmt_cp_stop_discovery cp; DBG(""); /* If there are any normal discovery clients passive scanning * won't be running */ if (adapter->discovery_list) return; if (adapter->discovery_enable == 0x00) return; cp.type = adapter->discovery_type; mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, adapter->dev_id, sizeof(cp), &cp, stop_passive_scanning_complete, adapter, NULL); } static void cancel_passive_scanning(struct btd_adapter *adapter) { if (!(adapter->current_settings & MGMT_SETTING_LE)) return; DBG(""); if (adapter->passive_scan_timeout > 0) { timeout_remove(adapter->passive_scan_timeout); adapter->passive_scan_timeout = 0; } } static uint8_t get_scan_type(struct btd_adapter *adapter) { uint8_t type; if (adapter->current_settings & MGMT_SETTING_BREDR) type = SCAN_TYPE_BREDR; else type = 0; if (adapter->current_settings & MGMT_SETTING_LE) type |= SCAN_TYPE_LE; return type; } static void free_discovery_filter(struct discovery_filter *discovery_filter) { if (!discovery_filter) return; g_slist_free_full(discovery_filter->uuids, free); free(discovery_filter->pattern); g_free(discovery_filter); } static void invalidate_rssi_and_tx_power(gpointer a) { struct btd_device *dev = a; device_set_rssi(dev, 0); device_set_tx_power(dev, 127); } static void discovery_cleanup(struct btd_adapter *adapter, int timeout) { GSList *l, *next; adapter->discovery_type = 0x00; if (adapter->discovery_idle_timeout > 0) { timeout_remove(adapter->discovery_idle_timeout); adapter->discovery_idle_timeout = 0; } g_slist_free_full(adapter->discovery_found, invalidate_rssi_and_tx_power); adapter->discovery_found = NULL; if (!adapter->devices) return; for (l = adapter->devices; l != NULL; l = next) { struct btd_device *dev = l->data; next = g_slist_next(l); if (device_is_temporary(dev) && !device_is_connectable(dev) && !btd_device_is_connected(dev)) btd_adapter_remove_device(adapter, dev); } } static void discovery_free(void *user_data) { struct discovery_client *client = user_data; struct btd_adapter *adapter = client->adapter; DBG("%p", client); if (client->watch) g_dbus_remove_watch(dbus_conn, client->watch); if (client->discovery_filter) { free_discovery_filter(client->discovery_filter); client->discovery_filter = NULL; } if (client->msg) { if (client == adapter->client) { g_dbus_send_message(dbus_conn, btd_error_busy(client->msg)); adapter->client = NULL; } dbus_message_unref(client->msg); } g_free(client->owner); g_free(client); } static void discovery_remove(struct discovery_client *client) { struct btd_adapter *adapter = client->adapter; DBG("owner %s", client->owner); adapter->set_filter_list = g_slist_remove(adapter->set_filter_list, client); adapter->discovery_list = g_slist_remove(adapter->discovery_list, client); if (adapter->client == client) adapter->client = NULL; if (client->watch && client->discovery_filter) adapter->set_filter_list = g_slist_prepend( adapter->set_filter_list, client); else discovery_free(client); /* * If there are other client discoveries in progress, then leave * it active. If not, then make sure to stop the restart timeout. */ if (adapter->discovery_list) return; discovery_cleanup(adapter, TEMP_DEV_TIMEOUT); } static void trigger_start_discovery(struct btd_adapter *adapter, guint delay); static struct discovery_client *discovery_complete(struct btd_adapter *adapter, uint8_t status) { struct discovery_client *client = adapter->client; DBusMessage *reply; if (!client) return NULL; adapter->client = NULL; if (!client->msg) return client; if (!status) { g_dbus_send_reply(dbus_conn, client->msg, DBUS_TYPE_INVALID); } else { reply = btd_error_busy(client->msg); g_dbus_send_message(dbus_conn, reply); } dbus_message_unref(client->msg); client->msg = NULL; return client; } static void start_discovery_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; struct discovery_client *client; const struct mgmt_cp_start_discovery *rp = param; DBG("status 0x%02x", status); /* Is there are no clients the discovery must have been stopped while * discovery command was pending. */ if (!adapter->discovery_list) { struct mgmt_cp_stop_discovery cp; if (status != MGMT_STATUS_SUCCESS) return; /* Stop discovering as there are no clients left */ cp.type = rp->type; mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, adapter->dev_id, sizeof(cp), &cp, NULL, NULL, NULL); return; } if (length < sizeof(*rp)) { btd_error(adapter->dev_id, "Wrong size of start discovery return parameters"); discovery_complete(adapter, MGMT_STATUS_FAILED); return; } if (status == MGMT_STATUS_SUCCESS) { adapter->discovery_type = rp->type; adapter->discovery_enable = 0x01; if (adapter->current_discovery_filter) adapter->filtered_discovery = true; else adapter->filtered_discovery = false; discovery_complete(adapter, status); if (adapter->discovering) return; adapter->discovering = true; g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Discovering"); return; } /* Reply with an error if the first discovery has failed */ client = discovery_complete(adapter, status); if (client) { discovery_remove(client); return; } /* * In case the restart of the discovery failed, then just trigger * it for the next idle timeout again. */ trigger_start_discovery(adapter, IDLE_DISCOV_TIMEOUT * 2); } static bool start_discovery_timeout(gpointer user_data) { struct btd_adapter *adapter = user_data; struct mgmt_cp_start_service_discovery *sd_cp; uint8_t new_type; DBG(""); adapter->discovery_idle_timeout = 0; /* If we're doing filtered discovery, it must be quickly restarted */ adapter->no_scan_restart_delay = !!adapter->current_discovery_filter; DBG("adapter->current_discovery_filter == %d", !!adapter->current_discovery_filter); new_type = get_scan_type(adapter); if (adapter->discovery_enable == 0x01) { struct mgmt_cp_stop_discovery cp; /* * If we're asked to start regular discovery, and there is an * already running regular discovery and it has the same type, * then just keep it. */ if (!adapter->current_discovery_filter && !adapter->filtered_discovery && adapter->discovery_type == new_type) { if (adapter->discovering) return FALSE; adapter->discovering = true; g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Discovering"); return FALSE; } /* * Otherwise the current discovery must be stopped. So * queue up a stop discovery command. * * This can happen if a passive scanning for Low Energy * devices is ongoing, or scan type is changed between * regular and filtered, or filter was updated. */ cp.type = adapter->discovery_type; mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, adapter->dev_id, sizeof(cp), &cp, NULL, NULL, NULL); /* Don't even bother to try to quickly start discovery * just after stopping it, it would fail with status * MGMT_BUSY. Instead discovering_callback will take * care of that. */ return FALSE; } /* Regular discovery is required */ if (!adapter->current_discovery_filter) { struct mgmt_cp_start_discovery cp; cp.type = new_type; mgmt_send(adapter->mgmt, MGMT_OP_START_DISCOVERY, adapter->dev_id, sizeof(cp), &cp, start_discovery_complete, adapter, NULL); return FALSE; } /* Filtered discovery is required */ sd_cp = adapter->current_discovery_filter; DBG("sending MGMT_OP_START_SERVICE_DISCOVERY %d, %d, %d", sd_cp->rssi, sd_cp->type, btohs(sd_cp->uuid_count)); mgmt_send(adapter->mgmt, MGMT_OP_START_SERVICE_DISCOVERY, adapter->dev_id, sizeof(*sd_cp) + btohs(sd_cp->uuid_count) * 16, sd_cp, start_discovery_complete, adapter, NULL); return FALSE; } static void trigger_start_discovery(struct btd_adapter *adapter, guint delay) { DBG(""); cancel_passive_scanning(adapter); if (adapter->discovery_idle_timeout > 0) { timeout_remove(adapter->discovery_idle_timeout); adapter->discovery_idle_timeout = 0; } /* * If the controller got powered down in between, then ensure * that we do not keep trying to restart discovery. * * This is safe-guard and should actually never trigger. */ if (!btd_adapter_get_powered(adapter)) return; adapter->discovery_idle_timeout = timeout_add_seconds(delay, start_discovery_timeout, adapter, NULL); } static void suspend_discovery_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; DBG("status 0x%02x", status); if (status == MGMT_STATUS_SUCCESS) { adapter->discovery_type = 0x00; adapter->discovery_enable = 0x00; return; } } static void suspend_discovery(struct btd_adapter *adapter) { struct mgmt_cp_stop_discovery cp; DBG(""); adapter->discovery_suspended = true; /* * If there are no clients discovering right now, then there is * also nothing to suspend. */ if (!adapter->discovery_list) return; /* * In case of being inside the idle phase, make sure to remove * the timeout to not trigger a restart. * * The restart will be triggered when the discovery is resumed. */ if (adapter->discovery_idle_timeout > 0) { timeout_remove(adapter->discovery_idle_timeout); adapter->discovery_idle_timeout = 0; } if (adapter->discovery_enable == 0x00) return; cp.type = adapter->discovery_type; mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, adapter->dev_id, sizeof(cp), &cp, suspend_discovery_complete, adapter, NULL); } static void resume_discovery(struct btd_adapter *adapter) { DBG(""); adapter->discovery_suspended = false; /* * If there are no clients discovering right now, then there is * also nothing to resume. */ if (!adapter->discovery_list) return; /* * Treat a suspended discovery session the same as extra long * idle time for a normal discovery. So just trigger the default * restart procedure. */ trigger_start_discovery(adapter, IDLE_DISCOV_TIMEOUT); } static void discovering_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_discovering *ev = param; struct btd_adapter *adapter = user_data; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small discovering event"); return; } DBG("hci%u type %u discovering %u method %d", adapter->dev_id, ev->type, ev->discovering, adapter->filtered_discovery); if (adapter->discovery_enable == ev->discovering) return; adapter->discovery_type = ev->type; adapter->discovery_enable = ev->discovering; /* * Check for existing discoveries triggered by client applications * and ignore all others. * * If there are no clients, then it is good idea to trigger a * passive scanning attempt. */ if (!adapter->discovery_list) { if (!adapter->connect_le) trigger_passive_scanning(adapter); return; } if (adapter->discovery_suspended) return; switch (adapter->discovery_enable) { case 0x00: if (adapter->no_scan_restart_delay) trigger_start_discovery(adapter, 0); else trigger_start_discovery(adapter, IDLE_DISCOV_TIMEOUT); break; case 0x01: if (adapter->discovery_idle_timeout > 0) { timeout_remove(adapter->discovery_idle_timeout); adapter->discovery_idle_timeout = 0; } break; } } static bool set_discovery_discoverable(struct btd_adapter *adapter, bool enable) { if (adapter->discovery_discoverable == enable) return true; /* Reset discoverable filter if already set */ if (enable && (adapter->current_settings & MGMT_SETTING_DISCOVERABLE)) return true; adapter->discovery_discoverable = enable; return set_discoverable(adapter, enable, 0); } static void stop_discovery_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; struct discovery_client *client; DBG("status 0x%02x", status); client = discovery_complete(adapter, status); if (client) discovery_remove(client); if (status != MGMT_STATUS_SUCCESS) return; adapter->discovery_type = 0x00; adapter->discovery_enable = 0x00; adapter->filtered_discovery = false; adapter->no_scan_restart_delay = false; adapter->discovering = false; g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Discovering"); trigger_passive_scanning(adapter); } static int compare_sender(gconstpointer a, gconstpointer b) { const struct discovery_client *client = a; const char *sender = b; return g_strcmp0(client->owner, sender); } static gint g_strcmp(gconstpointer a, gconstpointer b) { return strcmp(a, b); } static void extract_unique_uuids(gpointer data, gpointer user_data) { char *uuid_str = data; GSList **uuids = user_data; if (!g_slist_find_custom(*uuids, uuid_str, g_strcmp)) *uuids = g_slist_insert_sorted(*uuids, uuid_str, g_strcmp); } /* * This method merges all adapter filters into rssi, transport and uuids. * Returns 1 if there was no filtered scan, 0 otherwise. */ static int merge_discovery_filters(struct btd_adapter *adapter, int *rssi, uint8_t *transport, GSList **uuids) { GSList *l; bool empty_uuid = false; bool has_regular_discovery = false; bool has_filtered_discovery = false; uint8_t adapter_scan_type = get_scan_type(adapter); for (l = adapter->discovery_list; l != NULL; l = g_slist_next(l)) { struct discovery_client *client = l->data; struct discovery_filter *item = client->discovery_filter; if (!item) { has_regular_discovery = true; continue; } /* * Detect empty filter with only discoverable * (which does not require a kernel filter) set. */ if (item->uuids == NULL && item->pathloss == DISTANCE_VAL_INVALID && item->rssi == DISTANCE_VAL_INVALID && item->type == adapter_scan_type && item->duplicate == false && item->pattern == NULL) { has_regular_discovery = true; continue; } has_filtered_discovery = true; *transport |= item->type; /* * Rule for merging rssi and pathloss into rssi field of kernel * filter is as follow: * - if there's any client without proximity filter, then do no * proximity filtering, * - if all clients specified RSSI, then use lowest value, * - if any client specified pathloss, then kernel filter should * do no proximity, as kernel can't compute pathloss. We'll do * filtering on our own. */ if (item->rssi == DISTANCE_VAL_INVALID) *rssi = HCI_RSSI_INVALID; else if (*rssi != HCI_RSSI_INVALID && *rssi >= item->rssi) *rssi = item->rssi; else if (item->pathloss != DISTANCE_VAL_INVALID) *rssi = HCI_RSSI_INVALID; if (!g_slist_length(item->uuids)) empty_uuid = true; g_slist_foreach(item->uuids, extract_unique_uuids, uuids); } /* If no proximity filtering is set, disable it */ if (*rssi == DISTANCE_VAL_INVALID) *rssi = HCI_RSSI_INVALID; /* * Empty_uuid variable determines whether there was any filter with no * uuids. In this case someone might be looking for all devices in * certain proximity, and we need to have empty uuids in kernel filter. */ if (empty_uuid) { g_slist_free(*uuids); *uuids = NULL; } if (has_regular_discovery) { if (!has_filtered_discovery) return 1; /* * It there is both regular and filtered scan running, then * clear whole filter to report all devices. */ *transport = adapter_scan_type; *rssi = HCI_RSSI_INVALID; g_slist_free(*uuids); *uuids = NULL; } return 0; } static void populate_mgmt_filter_uuids(uint8_t (*mgmt_uuids)[16], GSList *uuids) { GSList *l; for (l = uuids; l != NULL; l = g_slist_next(l)) { bt_uuid_t uuid, u128; uint128_t uint128; bt_string_to_uuid(&uuid, l->data); bt_uuid_to_uuid128(&uuid, &u128); ntoh128((uint128_t *) u128.value.u128.data, &uint128); htob128(&uint128, (uint128_t *) mgmt_uuids); mgmt_uuids++; } } /* * This method merges all adapter filters into one that will be send to kernel. * cp_ptr is set to null when regular non-filtered discovery is needed, * otherwise it's pointing to filter. Returns 0 on success, -1 on error */ static int discovery_filter_to_mgmt_cp(struct btd_adapter *adapter, struct mgmt_cp_start_service_discovery **cp_ptr) { GSList *uuids = NULL; struct mgmt_cp_start_service_discovery *cp; int rssi = DISTANCE_VAL_INVALID; int uuid_count; uint8_t discovery_type = 0; DBG(""); if (merge_discovery_filters(adapter, &rssi, &discovery_type, &uuids)) { /* There are only regular scans, run just regular scan. */ *cp_ptr = NULL; return 0; } uuid_count = g_slist_length(uuids); cp = g_try_malloc(sizeof(*cp) + 16*uuid_count); *cp_ptr = cp; if (!cp) { g_slist_free(uuids); return -1; } cp->type = discovery_type; cp->rssi = rssi; cp->uuid_count = htobs(uuid_count); populate_mgmt_filter_uuids(cp->uuids, uuids); g_slist_free(uuids); return 0; } static bool filters_equal(struct mgmt_cp_start_service_discovery *a, struct mgmt_cp_start_service_discovery *b) { if (!a && !b) return true; if ((!a && b) || (a && !b)) return false; if (a->type != b->type) return false; if (a->rssi != b->rssi) return false; /* * When we create mgmt_cp_start_service_discovery structure inside * discovery_filter_to_mgmt_cp, we always keep uuids sorted, and * unique, so we're safe to compare uuid_count, and uuids like that. */ if (a->uuid_count != b->uuid_count) return false; if (memcmp(a->uuids, b->uuids, 16 * a->uuid_count) != 0) return false; return true; } static int update_discovery_filter(struct btd_adapter *adapter) { struct mgmt_cp_start_service_discovery *sd_cp; DBG(""); if (discovery_filter_to_mgmt_cp(adapter, &sd_cp)) { btd_error(adapter->dev_id, "discovery_filter_to_mgmt_cp returned error"); return -ENOMEM; } /* Only attempt to overwrite current discoverable setting when not * discoverable. */ if (!(adapter->current_settings & MGMT_SETTING_DISCOVERABLE)) { GSList *l; for (l = adapter->discovery_list; l; l = g_slist_next(l)) { struct discovery_client *client = l->data; if (!client->discovery_filter) continue; if (client->discovery_filter->discoverable) { set_discovery_discoverable(adapter, true); break; } } } /* * If filters are equal, then don't update scan, except for when * starting discovery. */ if (filters_equal(adapter->current_discovery_filter, sd_cp) && adapter->discovering != false) { DBG("filters were equal, deciding to not restart the scan."); g_free(sd_cp); return 0; } g_free(adapter->current_discovery_filter); adapter->current_discovery_filter = sd_cp; trigger_start_discovery(adapter, 0); return -EINPROGRESS; } static int discovery_stop(struct discovery_client *client) { struct btd_adapter *adapter = client->adapter; struct mgmt_cp_stop_discovery cp; /* Check if there are more client discovering */ if (g_slist_next(adapter->discovery_list)) { discovery_remove(client); update_discovery_filter(adapter); return 0; } set_discovery_discoverable(adapter, false); /* * In the idle phase of a discovery, there is no need to stop it * and so it is enough to send out the signal and just return. */ if (adapter->discovery_enable == 0x00) { discovery_remove(client); adapter->discovering = false; g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Discovering"); trigger_passive_scanning(adapter); return 0; } cp.type = adapter->discovery_type; adapter->client = client; mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, adapter->dev_id, sizeof(cp), &cp, stop_discovery_complete, adapter, NULL); return -EINPROGRESS; } static void discovery_disconnect(DBusConnection *conn, void *user_data) { struct discovery_client *client = user_data; DBG("owner %s", client->owner); client->watch = 0; discovery_stop(client); } /* * Returns true if client was already discovering, false otherwise. *client * will point to discovering client, or client that have pre-set his filter. */ static bool get_discovery_client(struct btd_adapter *adapter, const char *owner, struct discovery_client **client) { GSList *list = g_slist_find_custom(adapter->discovery_list, owner, compare_sender); if (list) { *client = list->data; return true; } list = g_slist_find_custom(adapter->set_filter_list, owner, compare_sender); if (list) { *client = list->data; return false; } *client = NULL; return false; } static DBusMessage *start_discovery(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_adapter *adapter = user_data; const char *sender = dbus_message_get_sender(msg); struct discovery_client *client; bool is_discovering; int err; DBG("sender %s", sender); if (!btd_adapter_get_powered(adapter)) return btd_error_not_ready(msg); is_discovering = get_discovery_client(adapter, sender, &client); /* * Every client can only start one discovery, if the client * already started a discovery then return an error. */ if (is_discovering) return btd_error_busy(msg); /* * If there was pre-set filter, just reconnect it to discovery_list, * and trigger scan. */ if (client) { if (client->msg) return btd_error_busy(msg); adapter->set_filter_list = g_slist_remove( adapter->set_filter_list, client); adapter->discovery_list = g_slist_prepend( adapter->discovery_list, client); goto done; } client = g_new0(struct discovery_client, 1); client->adapter = adapter; client->owner = g_strdup(sender); client->discovery_filter = NULL; client->watch = g_dbus_add_disconnect_watch(dbus_conn, sender, discovery_disconnect, client, NULL); adapter->discovery_list = g_slist_prepend(adapter->discovery_list, client); done: /* * Just trigger the discovery here. In case an already running * discovery in idle phase exists, it will be restarted right * away. */ err = update_discovery_filter(adapter); if (!err) return dbus_message_new_method_return(msg); /* If the discovery has to be started wait it complete to reply */ if (err == -EINPROGRESS) { client->msg = dbus_message_ref(msg); adapter->client = client; return NULL; } return btd_error_failed(msg, strerror(-err)); } static bool parse_uuids(DBusMessageIter *value, struct discovery_filter *filter) { DBusMessageIter arriter; if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_ARRAY) return false; dbus_message_iter_recurse(value, &arriter); while (dbus_message_iter_get_arg_type(&arriter) != DBUS_TYPE_INVALID) { bt_uuid_t uuid, u128; char uuidstr[MAX_LEN_UUID_STR + 1]; char *uuid_param; if (dbus_message_iter_get_arg_type(&arriter) != DBUS_TYPE_STRING) return false; dbus_message_iter_get_basic(&arriter, &uuid_param); if (bt_string_to_uuid(&uuid, uuid_param)) return false; bt_uuid_to_uuid128(&uuid, &u128); bt_uuid_to_string(&u128, uuidstr, sizeof(uuidstr)); filter->uuids = g_slist_prepend(filter->uuids, strdup(uuidstr)); dbus_message_iter_next(&arriter); } return true; } static bool parse_rssi(DBusMessageIter *value, struct discovery_filter *filter) { if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_INT16) return false; dbus_message_iter_get_basic(value, &filter->rssi); /* -127 <= RSSI <= +20 (spec V4.2 [Vol 2, Part E] 7.7.65.2) */ if (filter->rssi > 20 || filter->rssi < -127) return false; return true; } static bool parse_pathloss(DBusMessageIter *value, struct discovery_filter *filter) { if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_UINT16) return false; dbus_message_iter_get_basic(value, &filter->pathloss); /* pathloss filter must be smaller that PATHLOSS_MAX */ if (filter->pathloss > PATHLOSS_MAX) return false; return true; } static bool parse_transport(DBusMessageIter *value, struct discovery_filter *filter) { char *transport_str; if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_STRING) return false; dbus_message_iter_get_basic(value, &transport_str); if (!strcmp(transport_str, "bredr")) filter->type = SCAN_TYPE_BREDR; else if (!strcmp(transport_str, "le")) filter->type = SCAN_TYPE_LE; else if (strcmp(transport_str, "auto")) return false; return true; } static bool parse_duplicate_data(DBusMessageIter *value, struct discovery_filter *filter) { dbus_bool_t duplicate = false; if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) return false; dbus_message_iter_get_basic(value, &duplicate); filter->duplicate = duplicate; return true; } static bool parse_discoverable(DBusMessageIter *value, struct discovery_filter *filter) { dbus_bool_t discoverable = false; if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) return false; dbus_message_iter_get_basic(value, &discoverable); filter->discoverable = discoverable; return true; } static bool parse_pattern(DBusMessageIter *value, struct discovery_filter *filter) { const char *pattern; if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_STRING) return false; dbus_message_iter_get_basic(value, &pattern); free(filter->pattern); filter->pattern = strdup(pattern); return true; } static bool parse_auto_connect(DBusMessageIter *value, struct discovery_filter *filter) { dbus_bool_t connect; if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) return false; dbus_message_iter_get_basic(value, &connect); filter->auto_connect = connect; return true; } struct filter_parser { const char *name; bool (*func)(DBusMessageIter *iter, struct discovery_filter *filter); } parsers[] = { { "UUIDs", parse_uuids }, { "RSSI", parse_rssi }, { "Pathloss", parse_pathloss }, { "Transport", parse_transport }, { "DuplicateData", parse_duplicate_data }, { "Discoverable", parse_discoverable }, { "Pattern", parse_pattern }, { "AutoConnect", parse_auto_connect }, { } }; static bool parse_discovery_filter_entry(char *key, DBusMessageIter *value, struct discovery_filter *filter) { struct filter_parser *parser; for (parser = parsers; parser && parser->name; parser++) { if (!strcmp(parser->name, key)) return parser->func(value, filter); } DBG("Unknown key parameter: %s!\n", key); return false; } /* * This method is responsible for parsing parameters to SetDiscoveryFilter. If * filter in msg was empty, sets *filter to NULL. If whole parsing was * successful, sets *filter to proper value. * Returns false on any error, and true on success. */ static bool parse_discovery_filter_dict(struct btd_adapter *adapter, struct discovery_filter **filter, DBusMessage *msg) { DBusMessageIter iter, subiter, dictiter, variantiter; bool is_empty = true; *filter = g_try_malloc(sizeof(**filter)); if (!*filter) return false; (*filter)->uuids = NULL; (*filter)->pathloss = DISTANCE_VAL_INVALID; (*filter)->rssi = DISTANCE_VAL_INVALID; (*filter)->type = get_scan_type(adapter); (*filter)->duplicate = false; (*filter)->discoverable = false; (*filter)->auto_connect = false; (*filter)->pattern = NULL; dbus_message_iter_init(msg, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) goto invalid_args; dbus_message_iter_recurse(&iter, &subiter); do { int type = dbus_message_iter_get_arg_type(&subiter); char *key; if (type == DBUS_TYPE_INVALID) break; is_empty = false; dbus_message_iter_recurse(&subiter, &dictiter); dbus_message_iter_get_basic(&dictiter, &key); if (!dbus_message_iter_next(&dictiter)) goto invalid_args; if (dbus_message_iter_get_arg_type(&dictiter) != DBUS_TYPE_VARIANT) goto invalid_args; dbus_message_iter_recurse(&dictiter, &variantiter); if (!parse_discovery_filter_entry(key, &variantiter, *filter)) goto invalid_args; dbus_message_iter_next(&subiter); } while (true); if (is_empty) { g_free(*filter); *filter = NULL; return true; } /* only pathlos or rssi can be set, never both */ if ((*filter)->pathloss != DISTANCE_VAL_INVALID && (*filter)->rssi != DISTANCE_VAL_INVALID) goto invalid_args; DBG("filtered discovery params: transport: %d rssi: %d pathloss: %d " " duplicate data: %s discoverable %s pattern %s auto-connect %s", (*filter)->type, (*filter)->rssi, (*filter)->pathloss, (*filter)->duplicate ? "true" : "false", (*filter)->discoverable ? "true" : "false", (*filter)->pattern, (*filter)->auto_connect ? "true" : "false"); return true; invalid_args: g_slist_free_full((*filter)->uuids, g_free); g_free(*filter); *filter = NULL; return false; } static DBusMessage *set_discovery_filter(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_adapter *adapter = user_data; struct discovery_client *client; struct discovery_filter *discovery_filter; const char *sender = dbus_message_get_sender(msg); bool is_discovering; DBG("sender %s", sender); if (!btd_adapter_get_powered(adapter)) return btd_error_not_ready(msg); if (MGMT_VERSION(mgmt_version, mgmt_revision) < MGMT_VERSION(1, 8)) return btd_error_not_supported(msg); /* parse parameters */ if (!parse_discovery_filter_dict(adapter, &discovery_filter, msg)) return btd_error_invalid_args(msg); is_discovering = get_discovery_client(adapter, sender, &client); if (client) { free_discovery_filter(client->discovery_filter); client->discovery_filter = discovery_filter; if (is_discovering) update_discovery_filter(adapter); if (discovery_filter || is_discovering) return dbus_message_new_method_return(msg); /* Removing pre-set filter */ adapter->set_filter_list = g_slist_remove( adapter->set_filter_list, client); discovery_free(client); DBG("successfully cleared pre-set filter"); } else if (discovery_filter) { /* Client pre-setting his filter for first time */ client = g_new0(struct discovery_client, 1); client->adapter = adapter; client->owner = g_strdup(sender); client->discovery_filter = discovery_filter; client->watch = g_dbus_add_disconnect_watch(dbus_conn, sender, discovery_disconnect, client, NULL); adapter->set_filter_list = g_slist_prepend( adapter->set_filter_list, client); DBG("successfully pre-set filter"); } return dbus_message_new_method_return(msg); } static DBusMessage *stop_discovery(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_adapter *adapter = user_data; const char *sender = dbus_message_get_sender(msg); struct discovery_client *client; GSList *list; int err; DBG("sender %s", sender); if (!btd_adapter_get_powered(adapter)) return btd_error_not_ready(msg); list = g_slist_find_custom(adapter->discovery_list, sender, compare_sender); if (!list) return btd_error_failed(msg, "No discovery started"); client = list->data; if (client->msg) return btd_error_busy(msg); err = discovery_stop(client); switch (err) { case 0: return dbus_message_new_method_return(msg); case -EINPROGRESS: client->msg = dbus_message_ref(msg); adapter->client = client; return NULL; default: return btd_error_failed(msg, strerror(-err)); } } static gboolean property_get_address(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; char addr[18]; const char *str = addr; ba2str(&adapter->bdaddr, addr); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); return TRUE; } static gboolean property_get_address_type(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; const char *str; if ((adapter->current_settings & MGMT_SETTING_LE) && (adapter->bdaddr_type == BDADDR_LE_RANDOM)) str = "random"; else str = "public"; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); return TRUE; } static gboolean property_get_name(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; const char *str = adapter->system_name ? : ""; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); return TRUE; } static gboolean property_get_alias(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; const char *str; if (adapter->current_alias) str = adapter->current_alias; else if (adapter->stored_alias) str = adapter->stored_alias; else str = adapter->system_name ? : ""; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); return TRUE; } static void property_set_alias(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *user_data) { struct btd_adapter *adapter = user_data; const char *name; int ret; dbus_message_iter_get_basic(iter, &name); if (g_str_equal(name, "") == TRUE) { if (adapter->stored_alias == NULL) { /* no alias set, nothing to restore */ g_dbus_pending_property_success(id); return; } /* restore to system name */ ret = set_name(adapter, adapter->system_name); } else { if (g_strcmp0(adapter->stored_alias, name) == 0) { /* alias already set, nothing to do */ g_dbus_pending_property_success(id); return; } /* set to alias */ ret = set_name(adapter, name); } if (ret >= 0) { g_free(adapter->stored_alias); if (g_str_equal(name, "") == TRUE) adapter->stored_alias = NULL; else adapter->stored_alias = g_strdup(name); store_adapter_info(adapter); g_dbus_pending_property_success(id); return; } if (ret == -EINVAL) g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); else g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", strerror(-ret)); } static gboolean property_get_class(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; dbus_uint32_t val = adapter->dev_class; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &val); return TRUE; } static gboolean property_get_mode(struct btd_adapter *adapter, uint32_t setting, DBusMessageIter *iter) { dbus_bool_t enable; enable = (adapter->current_settings & setting) ? TRUE : FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &enable); return TRUE; } struct property_set_data { struct btd_adapter *adapter; uint32_t setting; GDBusPendingPropertySet id; uint8_t value; }; static void property_set_mode_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct property_set_data *data = user_data; struct btd_adapter *adapter = data->adapter; DBG("%s (0x%02x)", mgmt_errstr(status), status); if (status != MGMT_STATUS_SUCCESS) { const char *dbus_err; btd_error(adapter->dev_id, "Failed to set mode: %s (0x%02x)", mgmt_errstr(status), status); if (status == MGMT_STATUS_RFKILLED) { dbus_err = ERROR_INTERFACE ".Blocked"; adapter_set_power_state(adapter, ADAPTER_POWER_STATE_OFF_BLOCKED); } else { dbus_err = ERROR_INTERFACE ".Failed"; } g_dbus_pending_property_error(data->id, dbus_err, mgmt_errstr(status)); adapter->pending_settings &= ~data->setting; if (status != MGMT_STATUS_RFKILLED && data->setting & MGMT_SETTING_POWERED) reset_power_state_target(adapter, data->value); return; } g_dbus_pending_property_success(data->id); /* * The parameters are identical and also the task that is * required in both cases. So it is safe to just call the * event handling functions here. */ new_settings_callback(adapter->dev_id, length, param, adapter); } static void clear_discoverable(struct btd_adapter *adapter) { if (!btd_has_kernel_features(KERNEL_CONN_CONTROL)) return; if (!(adapter->current_settings & MGMT_SETTING_DISCOVERABLE)) return; /* If no timeout is set do nothing as both connectable and discoverable * flags are persistent on power toggle. */ if (!adapter->discoverable_timeout) return; /* If timeout was set kernel clears discoverable on its own when * powering off controller. This would leave connectable flag set * after power on. * * With kernel control clearing connectable clear also discoverable * flag so we need to clear connectable. */ set_mode(adapter, MGMT_OP_SET_CONNECTABLE, 0x00); } static void property_set_mode(struct btd_adapter *adapter, uint32_t setting, DBusMessageIter *value, GDBusPendingPropertySet id) { struct property_set_data *data; struct mgmt_cp_set_discoverable cp; void *param; dbus_bool_t enable, current_enable; uint16_t opcode, len; uint8_t mode; dbus_message_iter_get_basic(value, &enable); if (adapter->pending_settings & setting) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".Busy", NULL); return; } if (adapter->current_settings & setting) current_enable = TRUE; else current_enable = FALSE; if (enable == current_enable) { g_dbus_pending_property_success(id); return; } mode = (enable == TRUE) ? 0x01 : 0x00; switch (setting) { case MGMT_SETTING_POWERED: opcode = MGMT_OP_SET_POWERED; param = &mode; len = sizeof(mode); if (!mode) { btd_adv_monitor_power_down( adapter->adv_monitor_manager); clear_discoverable(adapter); remove_temporary_devices(adapter); } break; case MGMT_SETTING_DISCOVERABLE: if (btd_has_kernel_features(KERNEL_CONN_CONTROL)) { if (mode) { set_mode(adapter, MGMT_OP_SET_CONNECTABLE, mode); } else { opcode = MGMT_OP_SET_CONNECTABLE; param = &mode; len = sizeof(mode); break; } } memset(&cp, 0, sizeof(cp)); cp.val = mode; if (cp.val) cp.timeout = htobs(adapter->discoverable_timeout); opcode = MGMT_OP_SET_DISCOVERABLE; param = &cp; len = sizeof(cp); break; case MGMT_SETTING_BONDABLE: opcode = MGMT_OP_SET_BONDABLE; param = &mode; len = sizeof(mode); break; case MGMT_SETTING_CONNECTABLE: opcode = MGMT_OP_SET_CONNECTABLE; param = &mode; len = sizeof(mode); break; default: goto failed; } DBG("sending %s command for index %u", mgmt_opstr(opcode), adapter->dev_id); data = g_try_new0(struct property_set_data, 1); if (!data) goto failed; data->adapter = adapter; data->setting = setting; data->id = id; data->setting = setting; data->value = mode; if (setting == MGMT_SETTING_POWERED && adapter->power_state != ADAPTER_POWER_STATE_OFF_BLOCKED) { adapter_set_power_state(adapter, mode ? ADAPTER_POWER_STATE_OFF_ENABLING : ADAPTER_POWER_STATE_ON_DISABLING); } if (mgmt_send(adapter->mgmt, opcode, adapter->dev_id, len, param, property_set_mode_complete, data, g_free) > 0) { adapter->pending_settings |= setting; return; } g_free(data); if (setting == MGMT_SETTING_POWERED) { /* cancel the earlier setting */ adapter_set_power_state(adapter, mode ? ADAPTER_POWER_STATE_OFF : ADAPTER_POWER_STATE_ON); } failed: btd_error(adapter->dev_id, "Failed to set mode for index %u", adapter->dev_id); g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", NULL); } static gboolean property_get_powered(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; return property_get_mode(adapter, MGMT_SETTING_POWERED, iter); } static void property_set_powered(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *user_data) { struct btd_adapter *adapter = user_data; if (powering_down) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", "Powering down"); return; } property_set_mode(adapter, MGMT_SETTING_POWERED, iter, id); } static gboolean property_get_power_state(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; const char *str; str = adapter_power_state_str(adapter->power_state); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); return TRUE; } static gboolean property_get_discoverable(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; return property_get_mode(adapter, MGMT_SETTING_DISCOVERABLE, iter); } static void property_set_discoverable(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *user_data) { struct btd_adapter *adapter = user_data; if (adapter->discoverable_timeout > 0 && !btd_adapter_get_powered(adapter)) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", "Not Powered"); return; } /* Reset discovery_discoverable as Discoverable takes precedence */ adapter->discovery_discoverable = false; property_set_mode(adapter, MGMT_SETTING_DISCOVERABLE, iter, id); } static gboolean property_get_discoverable_timeout( const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; dbus_uint32_t value = adapter->discoverable_timeout; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &value); return TRUE; } static void property_set_discoverable_timeout( const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *user_data) { struct btd_adapter *adapter = user_data; bool enabled; dbus_uint32_t value; dbus_message_iter_get_basic(iter, &value); adapter->discoverable_timeout = value; g_dbus_pending_property_success(id); store_adapter_info(adapter); g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "DiscoverableTimeout"); if (adapter->pending_settings & MGMT_SETTING_DISCOVERABLE) { if (adapter->current_settings & MGMT_SETTING_DISCOVERABLE) enabled = false; else enabled = true; } else { if (adapter->current_settings & MGMT_SETTING_DISCOVERABLE) enabled = true; else enabled = false; } if (enabled) set_discoverable(adapter, 0x01, adapter->discoverable_timeout); } static gboolean property_get_pairable(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; return property_get_mode(adapter, MGMT_SETTING_BONDABLE, iter); } static void property_set_pairable(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *user_data) { struct btd_adapter *adapter = user_data; property_set_mode(adapter, MGMT_SETTING_BONDABLE, iter, id); } static gboolean property_get_pairable_timeout( const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; dbus_uint32_t value = adapter->pairable_timeout; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &value); return TRUE; } static void property_set_pairable_timeout(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *user_data) { struct btd_adapter *adapter = user_data; dbus_uint32_t value; dbus_message_iter_get_basic(iter, &value); adapter->pairable_timeout = value; g_dbus_pending_property_success(id); store_adapter_info(adapter); g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "PairableTimeout"); trigger_pairable_timeout(adapter); } static gboolean property_get_discovering(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; dbus_bool_t discovering = adapter->discovering; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &discovering); return TRUE; } static void add_gatt_uuid(struct gatt_db_attribute *attrib, void *user_data) { GHashTable *uuids = user_data; bt_uuid_t uuid, u128; char uuidstr[MAX_LEN_UUID_STR + 1]; if (!gatt_db_service_get_active(attrib)) return; if (!gatt_db_attribute_get_service_uuid(attrib, &uuid)) return; bt_uuid_to_uuid128(&uuid, &u128); bt_uuid_to_string(&u128, uuidstr, sizeof(uuidstr)); g_hash_table_add(uuids, strdup(uuidstr)); } static void iter_append_uuid(gpointer key, gpointer value, gpointer user_data) { DBusMessageIter *iter = user_data; const char *uuid = key; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); } static gboolean property_get_uuids(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; DBusMessageIter entry; sdp_list_t *l; struct gatt_db *db; GHashTable *uuids; uuids = g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL); if (!uuids) return FALSE; /* SDP records */ for (l = adapter->services; l != NULL; l = l->next) { sdp_record_t *rec = l->data; char *uuid; uuid = bt_uuid2string(&rec->svclass); if (uuid == NULL) continue; g_hash_table_add(uuids, uuid); } /* GATT services */ db = btd_gatt_database_get_db(adapter->database); if (db) gatt_db_foreach_service(db, NULL, add_gatt_uuid, uuids); dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); g_hash_table_foreach(uuids, iter_append_uuid, &entry); dbus_message_iter_close_container(iter, &entry); g_hash_table_destroy(uuids); return TRUE; } static gboolean property_exists_modalias(const GDBusPropertyTable *property, void *user_data) { struct btd_adapter *adapter = user_data; return adapter->modalias ? TRUE : FALSE; } static gboolean property_get_modalias(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; const char *str = adapter->modalias ? : ""; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); return TRUE; } static gboolean property_get_roles(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; DBusMessageIter entry; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); if (adapter->supported_settings & MGMT_SETTING_LE) { const char *str = "central"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str); } if (adapter->supported_settings & MGMT_SETTING_ADVERTISING) { const char *str = "peripheral"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str); } if (queue_find(adapter->exps, NULL, le_simult_central_peripheral_uuid.val)) { const char *str = "central-peripheral"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str); } dbus_message_iter_close_container(iter, &entry); return TRUE; } static void property_append_experimental(void *data, void *user_data) { uint8_t *feature = data; DBusMessageIter *iter = user_data; uint128_t value; bt_uuid_t uuid; char str[MAX_LEN_UUID_STR + 1]; char *ptr; bswap_128(feature, &value); bt_uuid128_create(&uuid, value); bt_uuid_to_string(&uuid, str, sizeof(str)); ptr = str; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); } static gboolean property_get_experimental(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; DBusMessageIter entry; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); queue_foreach(adapter->exps, property_append_experimental, &entry); dbus_message_iter_close_container(iter, &entry); return TRUE; } static gboolean property_experimental_exists(const GDBusPropertyTable *property, void *data) { struct btd_adapter *adapter = data; return !queue_isempty(adapter->exps); } static gboolean property_get_manufacturer(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; dbus_uint16_t val = adapter->manufacturer; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &val); return TRUE; } static gboolean property_get_version(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; uint8_t val = adapter->version; dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &val); return TRUE; } static gboolean property_get_connectable(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_adapter *adapter = user_data; return property_get_mode(adapter, MGMT_SETTING_CONNECTABLE, iter); } static void property_set_connectable(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *user_data) { struct btd_adapter *adapter = user_data; property_set_mode(adapter, MGMT_SETTING_CONNECTABLE, iter, id); } static DBusMessage *remove_device(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_adapter *adapter = user_data; struct btd_device *device; const char *path; GSList *list; if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID) == FALSE) return btd_error_invalid_args(msg); list = g_slist_find_custom(adapter->devices, path, device_path_cmp); if (!list) return btd_error_does_not_exist(msg); if (!btd_adapter_get_powered(adapter)) return btd_error_not_ready(msg); device = list->data; btd_device_set_temporary(device, true); if (!btd_device_is_connected(device)) { btd_adapter_remove_device(adapter, device); return dbus_message_new_method_return(msg); } device_request_disconnect(device, msg); return NULL; } static DBusMessage *get_discovery_filters(DBusConnection *conn, DBusMessage *msg, void *user_data) { DBusMessage *reply; DBusMessageIter iter, array; struct filter_parser *parser; reply = dbus_message_new_method_return(msg); dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &array); for (parser = parsers; parser && parser->name; parser++) { dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &parser->name); } dbus_message_iter_close_container(&iter, &array); return reply; } struct device_connect_data { struct btd_adapter *adapter; bdaddr_t dst; uint8_t dst_type; DBusMessage *msg; }; static void device_connect_cb(GIOChannel *io, GError *gerr, gpointer user_data) { struct device_connect_data *data = user_data; struct btd_adapter *adapter = data->adapter; struct btd_device *device; const char *path; DBG("%s", gerr ? gerr->message : ""); if (gerr) goto failed; /* object might already exist due to mgmt socket event */ device = btd_adapter_get_device(adapter, &data->dst, data->dst_type); if (!device) goto failed; path = device_get_path(device); g_dbus_send_reply(dbus_conn, data->msg, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); /* continue with service discovery and connection */ btd_device_set_temporary(device, false); device_update_last_seen(device, data->dst_type, true); if (data->dst_type != BDADDR_BREDR){ g_io_channel_set_close_on_unref(io, FALSE); device_attach_att(device, io); } device_discover_services(device, data->dst_type, NULL); g_io_channel_unref(io); dbus_message_unref(data->msg); free(data); return; failed: g_dbus_send_error(dbus_conn, data->msg, "org.bluez.Failed", NULL); g_io_channel_unref(io); dbus_message_unref(data->msg); free(data); } static void device_connect(struct btd_adapter *adapter, const bdaddr_t *dst, uint8_t dst_type, DBusMessage *msg) { struct device_connect_data *data; GIOChannel *io; data = new0(struct device_connect_data, 1); data->adapter = adapter; bacpy(&data->dst, dst); data->dst_type = dst_type; data->msg = dbus_message_ref(msg); if (dst_type == BDADDR_BREDR) io = bt_io_connect(device_connect_cb, data, NULL, NULL, BT_IO_OPT_SOURCE_BDADDR, &adapter->bdaddr, BT_IO_OPT_SOURCE_TYPE, BDADDR_BREDR, BT_IO_OPT_DEST_BDADDR, dst, BT_IO_OPT_DEST_TYPE, BDADDR_BREDR, BT_IO_OPT_PSM, SDP_PSM, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_INVALID); else io = bt_io_connect(device_connect_cb, data, NULL, NULL, BT_IO_OPT_SOURCE_BDADDR, &adapter->bdaddr, BT_IO_OPT_SOURCE_TYPE, adapter->bdaddr_type, BT_IO_OPT_DEST_BDADDR, dst, BT_IO_OPT_DEST_TYPE, dst_type, BT_IO_OPT_CID, BT_ATT_CID, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_INVALID); if (!io) { g_dbus_send_message(dbus_conn, btd_error_failed(msg, "Connect failed")); dbus_message_unref(data->msg); free(data); } } static DBusMessage *connect_device(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_adapter *adapter = user_data; DBusMessageIter iter, subiter, dictiter, value; uint8_t addr_type = BDADDR_BREDR; bdaddr_t addr = *BDADDR_ANY; DBG("sender %s", dbus_message_get_sender(msg)); if (!btd_adapter_get_powered(adapter)) return btd_error_not_ready(msg); dbus_message_iter_init(msg, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) return btd_error_invalid_args(msg); dbus_message_iter_recurse(&iter, &subiter); while (true) { int type = dbus_message_iter_get_arg_type(&subiter); char *key; char *str; if (type == DBUS_TYPE_INVALID) break; dbus_message_iter_recurse(&subiter, &dictiter); dbus_message_iter_get_basic(&dictiter, &key); if (!dbus_message_iter_next(&dictiter)) return btd_error_invalid_args(msg); if (dbus_message_iter_get_arg_type(&dictiter) != DBUS_TYPE_VARIANT) return btd_error_invalid_args(msg); dbus_message_iter_recurse(&dictiter, &value); if (!strcmp(key, "Address")) { if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_STRING) return btd_error_invalid_args(msg); dbus_message_iter_get_basic(&value, &str); if (str2ba(str, &addr) < 0 ) return btd_error_invalid_args(msg); } else if (!strcmp(key, "AddressType")) { if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_STRING) return btd_error_invalid_args(msg); dbus_message_iter_get_basic(&value, &str); if (!strcmp(str, "public")) addr_type = BDADDR_LE_PUBLIC; else if (!strcmp(str, "random")) addr_type = BDADDR_LE_RANDOM; else return btd_error_invalid_args(msg); } else { return btd_error_invalid_args(msg); } dbus_message_iter_next(&subiter); } if (!bacmp(&addr, BDADDR_ANY)) return btd_error_invalid_args(msg); device_connect(adapter, &addr, addr_type, msg); return NULL; } static void update_device_allowed_services(void *data, void *user_data) { struct btd_device *device = data; btd_device_update_allowed_services(device); } static void add_uuid_to_uuid_set(void *data, void *user_data) { bt_uuid_t *uuid = data; GHashTable *uuid_set = user_data; if (!uuid) { error("Found NULL in UUID allowed list"); return; } g_hash_table_add(uuid_set, uuid); } static guint bt_uuid_hash(gconstpointer key) { const bt_uuid_t *uuid = key; bt_uuid_t my_uuid; if (!uuid) return 0; bt_uuid_to_uuid128(uuid, &my_uuid); return g_int64_hash(&my_uuid.value.u128.data[0]) ^ g_int64_hash(&my_uuid.value.u128.data[8]); } static gboolean bt_uuid_equal(gconstpointer v1, gconstpointer v2) { const bt_uuid_t *uuid1 = v1; const bt_uuid_t *uuid2 = v2; if (!uuid1 || !uuid2) return !uuid1 && !uuid2; return bt_uuid_cmp(uuid1, uuid2) == 0; } bool btd_adapter_set_allowed_uuids(struct btd_adapter *adapter, struct queue *uuids) { if (!adapter) return false; if (adapter->allowed_uuid_set) g_hash_table_destroy(adapter->allowed_uuid_set); adapter->allowed_uuid_set = g_hash_table_new(bt_uuid_hash, bt_uuid_equal); if (!adapter->allowed_uuid_set) { btd_error(adapter->dev_id, "Failed to allocate allowed_uuid_set"); return false; } queue_foreach(uuids, add_uuid_to_uuid_set, adapter->allowed_uuid_set); g_slist_foreach(adapter->devices, update_device_allowed_services, NULL); return true; } bool btd_adapter_is_uuid_allowed(struct btd_adapter *adapter, const char *uuid_str) { bt_uuid_t uuid; if (!adapter || !adapter->allowed_uuid_set) return true; if (bt_string_to_uuid(&uuid, uuid_str)) { btd_error(adapter->dev_id, "Failed to parse UUID string '%s'", uuid_str); return false; } return !g_hash_table_size(adapter->allowed_uuid_set) || g_hash_table_contains(adapter->allowed_uuid_set, &uuid); } static const GDBusMethodTable adapter_methods[] = { { GDBUS_ASYNC_METHOD("StartDiscovery", NULL, NULL, start_discovery) }, { GDBUS_METHOD("SetDiscoveryFilter", GDBUS_ARGS({ "properties", "a{sv}" }), NULL, set_discovery_filter) }, { GDBUS_ASYNC_METHOD("StopDiscovery", NULL, NULL, stop_discovery) }, { GDBUS_ASYNC_METHOD("RemoveDevice", GDBUS_ARGS({ "device", "o" }), NULL, remove_device) }, { GDBUS_METHOD("GetDiscoveryFilters", NULL, GDBUS_ARGS({ "filters", "as" }), get_discovery_filters) }, { GDBUS_EXPERIMENTAL_ASYNC_METHOD("ConnectDevice", GDBUS_ARGS({ "properties", "a{sv}" }), NULL, connect_device) }, { } }; static const GDBusPropertyTable adapter_properties[] = { { "Address", "s", property_get_address }, { "AddressType", "s", property_get_address_type }, { "Name", "s", property_get_name }, { "Alias", "s", property_get_alias, property_set_alias }, { "Class", "u", property_get_class }, { "Connectable", "b", property_get_connectable, property_set_connectable }, { "Powered", "b", property_get_powered, property_set_powered }, { "PowerState", "s", property_get_power_state }, { "Discoverable", "b", property_get_discoverable, property_set_discoverable }, { "DiscoverableTimeout", "u", property_get_discoverable_timeout, property_set_discoverable_timeout }, { "Pairable", "b", property_get_pairable, property_set_pairable }, { "PairableTimeout", "u", property_get_pairable_timeout, property_set_pairable_timeout }, { "Discovering", "b", property_get_discovering }, { "UUIDs", "as", property_get_uuids }, { "Modalias", "s", property_get_modalias, NULL, property_exists_modalias }, { "Roles", "as", property_get_roles }, { "ExperimentalFeatures", "as", property_get_experimental, NULL, property_experimental_exists }, { "Manufacturer", "q", property_get_manufacturer }, { "Version", "y", property_get_version }, { } }; static int str2buf(const char *str, uint8_t *buf, size_t blen) { int i, dlen; if (str == NULL) return -EINVAL; memset(buf, 0, blen); dlen = MIN((strlen(str) / 2), blen); for (i = 0; i < dlen; i++) sscanf(str + (i * 2), "%02hhX", &buf[i]); return 0; } static bool is_blocked_key(uint8_t key_type, uint8_t *key_value) { uint32_t i = 0; for (i = 0; i < ARRAY_SIZE(blocked_keys); ++i) { if (key_type == blocked_keys[i].type && !memcmp(blocked_keys[i].val, key_value, sizeof(blocked_keys[i].val))) return true; } return false; } static struct link_key_info *get_key_info(GKeyFile *key_file, const char *peer, uint8_t bdaddr_type) { struct link_key_info *info = NULL; char *str; str = g_key_file_get_string(key_file, "LinkKey", "Key", NULL); if (!str || strlen(str) < 32) goto failed; info = g_new0(struct link_key_info, 1); str2ba(peer, &info->bdaddr); info->bdaddr_type = bdaddr_type; /* Fix up address type if it was stored with the wrong * address type since Load Link Keys are only meant to * work with BR/EDR addresses as per MGMT documentation. */ if (info->bdaddr_type != BDADDR_BREDR) info->bdaddr_type = BDADDR_BREDR; if (!strncmp(str, "0x", 2)) str2buf(&str[2], info->key, sizeof(info->key)); else str2buf(&str[0], info->key, sizeof(info->key)); info->type = g_key_file_get_integer(key_file, "LinkKey", "Type", NULL); info->pin_len = g_key_file_get_integer(key_file, "LinkKey", "PINLength", NULL); info->is_blocked = is_blocked_key(HCI_BLOCKED_KEY_TYPE_LINKKEY, info->key); failed: g_free(str); return info; } static struct smp_ltk_info *get_ltk(GKeyFile *key_file, const char *peer, uint8_t peer_type, const char *group) { struct smp_ltk_info *ltk = NULL; char *key; char *rand = NULL; key = g_key_file_get_string(key_file, group, "Key", NULL); if (!key || strlen(key) < 32) goto failed; rand = g_key_file_get_string(key_file, group, "Rand", NULL); if (!rand) goto failed; ltk = g_new0(struct smp_ltk_info, 1); /* Default to assuming a central key */ ltk->central = true; str2ba(peer, <k->bdaddr); ltk->bdaddr_type = peer_type; /* Fix up address type if it was stored with the wrong * address type since Load Long Term Keys are only meant * to work with LE addresses as per MGMT documentation. */ if (ltk->bdaddr_type == BDADDR_BREDR) ltk->bdaddr_type = BDADDR_LE_PUBLIC; /* * Long term keys should respond to an identity address which can * either be a public address or a random static address. Keys * stored for resolvable random and unresolvable random addresses * are ignored. * * This is an extra sanity check for older kernel versions or older * daemons that might have been instructed to store long term keys * for these temporary addresses. */ if (ltk->bdaddr_type == BDADDR_LE_RANDOM && (ltk->bdaddr.b[5] & 0xc0) != 0xc0) { g_free(ltk); ltk = NULL; goto failed; } if (!strncmp(key, "0x", 2)) str2buf(&key[2], ltk->val, sizeof(ltk->val)); else str2buf(&key[0], ltk->val, sizeof(ltk->val)); if (!strncmp(rand, "0x", 2)) { uint64_t rand_le; str2buf(&rand[2], (uint8_t *) &rand_le, sizeof(rand_le)); ltk->rand = le64_to_cpu(rand_le); } else { sscanf(rand, "%" PRIu64, <k->rand); } ltk->authenticated = g_key_file_get_integer(key_file, group, "Authenticated", NULL); ltk->enc_size = g_key_file_get_integer(key_file, group, "EncSize", NULL); ltk->ediv = g_key_file_get_integer(key_file, group, "EDiv", NULL); ltk->is_blocked = is_blocked_key(HCI_BLOCKED_KEY_TYPE_LTK, ltk->val); failed: g_free(key); g_free(rand); return ltk; } static struct smp_ltk_info *get_ltk_info(GKeyFile *key_file, const char *peer, uint8_t bdaddr_type) { DBG("%s", peer); return get_ltk(key_file, peer, bdaddr_type, "LongTermKey"); } static struct smp_ltk_info *get_peripheral_ltk_info(GKeyFile *key_file, const char *peer, uint8_t bdaddr_type) { struct smp_ltk_info *ltk; DBG("%s", peer); /* Peripheral* is the proper term, but for now read both entries * so it won't break when user up/downgrades. Remove the other * term after a few releases. */ ltk = get_ltk(key_file, peer, bdaddr_type, "PeripheralLongTermKey"); if (!ltk) ltk = get_ltk(key_file, peer, bdaddr_type, "SlaveLongTermKey"); if (ltk) ltk->central = false; return ltk; } static struct irk_info *get_irk_info(GKeyFile *key_file, const char *peer, uint8_t bdaddr_type) { struct irk_info *irk = NULL; char *str; str = g_key_file_get_string(key_file, "IdentityResolvingKey", "Key", NULL); if (!str || strlen(str) < 32) goto failed; irk = g_new0(struct irk_info, 1); str2ba(peer, &irk->bdaddr); irk->bdaddr_type = bdaddr_type; /* Fix up address type if it was stored with the wrong * address type since Load Identity Keys are only meant * to work with LE addresses as per MGMT documentation. */ if (irk->bdaddr_type == BDADDR_BREDR) irk->bdaddr_type = BDADDR_LE_PUBLIC; if (!strncmp(str, "0x", 2)) str2buf(&str[2], irk->val, sizeof(irk->val)); else str2buf(&str[0], irk->val, sizeof(irk->val)); irk->is_blocked = is_blocked_key(HCI_BLOCKED_KEY_TYPE_LINKKEY, irk->val); failed: g_free(str); return irk; } static struct conn_param *get_conn_param(GKeyFile *key_file, const char *peer, uint8_t bdaddr_type) { struct conn_param *param; if (!g_key_file_has_group(key_file, "ConnectionParameters")) return NULL; param = g_new0(struct conn_param, 1); param->min_interval = g_key_file_get_integer(key_file, "ConnectionParameters", "MinInterval", NULL); param->max_interval = g_key_file_get_integer(key_file, "ConnectionParameters", "MaxInterval", NULL); param->latency = g_key_file_get_integer(key_file, "ConnectionParameters", "Latency", NULL); param->timeout = g_key_file_get_integer(key_file, "ConnectionParameters", "Timeout", NULL); str2ba(peer, ¶m->bdaddr); param->bdaddr_type = bdaddr_type; return param; } static int generate_and_write_irk(uint8_t *irk, GKeyFile *key_file, const char *filename) { struct bt_crypto *crypto; char str_irk_out[33]; gsize length = 0; GError *gerr = NULL; char *str; int i; crypto = bt_crypto_new(); if (!crypto) { error("Failed to open crypto"); return -1; } if (!bt_crypto_random_bytes(crypto, irk, 16)) { error("Failed to generate IRK"); bt_crypto_unref(crypto); return -1; } bt_crypto_unref(crypto); for (i = 0; i < 16; i++) sprintf(str_irk_out + (i * 2), "%02x", irk[i]); str_irk_out[32] = '\0'; info("Generated IRK successfully"); g_key_file_set_string(key_file, "General", "IdentityResolvingKey", str_irk_out); create_file(filename, S_IRUSR | S_IWUSR); str = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, str, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(str); DBG("Generated IRK written to file"); return 0; } static int load_irk(struct btd_adapter *adapter, uint8_t *irk) { char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; char *str_irk; int ret; create_filename(filename, PATH_MAX, "/%s/identity", btd_adapter_get_storage_dir(adapter)); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_error_free(gerr); } str_irk = g_key_file_get_string(key_file, "General", "IdentityResolvingKey", NULL); if (!str_irk) { info("No IRK stored"); ret = generate_and_write_irk(irk, key_file, filename); g_key_file_free(key_file); return ret; } g_key_file_free(key_file); if (strlen(str_irk) != 32 || str2buf(str_irk, irk, 16)) { /* TODO re-create new IRK here? */ error("Invalid IRK format, disabling privacy"); g_free(str_irk); return -1; } g_free(str_irk); DBG("Successfully read IRK from file"); return 0; } static void set_privacy_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to set privacy: %s (0x%02x)", mgmt_errstr(status), status); return; } DBG("Successfully set privacy for index %u", adapter->dev_id); } static int set_privacy(struct btd_adapter *adapter, uint8_t privacy) { struct mgmt_cp_set_privacy cp; memset(&cp, 0, sizeof(cp)); if (privacy) { uint8_t irk[16]; if (load_irk(adapter, irk) == 0) { cp.privacy = privacy; memcpy(cp.irk, irk, 16); } } DBG("sending set privacy command for index %u", adapter->dev_id); DBG("setting privacy mode 0x%02x for index %u", cp.privacy, adapter->dev_id); if (mgmt_send(adapter->mgmt, MGMT_OP_SET_PRIVACY, adapter->dev_id, sizeof(cp), &cp, set_privacy_complete, adapter, NULL) > 0) return 0; btd_error(adapter->dev_id, "Failed to set privacy for index %u", adapter->dev_id); return -1; } static void load_link_keys_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to load link keys for hci%u: %s (0x%02x)", adapter->dev_id, mgmt_errstr(status), status); return; } DBG("link keys loaded for hci%u", adapter->dev_id); } static void load_link_keys(struct btd_adapter *adapter, GSList *keys, bool debug_keys) { struct mgmt_cp_load_link_keys *cp; struct mgmt_link_key_info *key; size_t key_count, cp_size; unsigned int id; GSList *l; /* * If the controller does not support BR/EDR operation, * there is no point in trying to load the link keys into * the kernel. * * This is an optimization for Low Energy only controllers. */ if (!(adapter->supported_settings & MGMT_SETTING_BREDR)) return; key_count = g_slist_length(keys); DBG("hci%u keys %zu debug_keys %d", adapter->dev_id, key_count, debug_keys); cp_size = sizeof(*cp) + (key_count * sizeof(*key)); cp = g_try_malloc0(cp_size); if (cp == NULL) { btd_error(adapter->dev_id, "No memory for link keys for hci%u", adapter->dev_id); return; } /* * Even if the list of stored keys is empty, it is important to * load an empty list into the kernel. That way it is ensured * that no old keys from a previous daemon are present. * * In addition it is also the only way to toggle the different * behavior for debug keys. */ cp->debug_keys = debug_keys; cp->key_count = htobs(key_count); for (l = keys, key = cp->keys; l != NULL; l = g_slist_next(l), key++) { struct link_key_info *info = l->data; bacpy(&key->addr.bdaddr, &info->bdaddr); key->addr.type = info->bdaddr_type; key->type = info->type; memcpy(key->val, info->key, 16); key->pin_len = info->pin_len; } id = mgmt_send(adapter->mgmt, MGMT_OP_LOAD_LINK_KEYS, adapter->dev_id, cp_size, cp, load_link_keys_complete, adapter, NULL); g_free(cp); if (id == 0) btd_error(adapter->dev_id, "Failed to load link keys for hci%u", adapter->dev_id); } static void load_ltks_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to load LTKs for hci%u: %s (0x%02x)", adapter->dev_id, mgmt_errstr(status), status); } DBG("LTKs loaded for hci%u", adapter->dev_id); } static void load_ltks(struct btd_adapter *adapter, GSList *keys) { struct mgmt_cp_load_long_term_keys *cp; struct mgmt_ltk_info *key; size_t key_count, max_key_count, cp_size; GSList *l; uint16_t mtu; /* * If the controller does not support Low Energy operation, * there is no point in trying to load the long term keys * into the kernel. * * While there is no harm in loading keys into the kernel, * this is an optimization to avoid a confusing warning * message when the loading of the keys timed out due to * a kernel bug (see comment below). */ if (!(adapter->supported_settings & MGMT_SETTING_LE)) return; key_count = g_slist_length(keys); mtu = mgmt_get_mtu(adapter->mgmt); max_key_count = (mtu - sizeof(*cp)) / sizeof(*key); key_count = MIN(max_key_count, key_count); DBG("hci%u keys %zu", adapter->dev_id, key_count); cp_size = sizeof(*cp) + (key_count * sizeof(*key)); cp = g_try_malloc0(cp_size); if (cp == NULL) { btd_error(adapter->dev_id, "No memory for LTKs for hci%u", adapter->dev_id); return; } /* * Even if the list of stored keys is empty, it is important to * load an empty list into the kernel. That way it is ensured * that no old keys from a previous daemon are present. */ cp->key_count = htobs(key_count); for (l = keys, key = cp->keys; l && key_count; l = g_slist_next(l), key++, key_count--) { struct smp_ltk_info *info = l->data; struct btd_device *dev; bacpy(&key->addr.bdaddr, &info->bdaddr); key->addr.type = info->bdaddr_type; memcpy(key->val, info->val, sizeof(info->val)); key->rand = cpu_to_le64(info->rand); key->ediv = cpu_to_le16(info->ediv); key->type = info->authenticated; key->central = info->central; key->enc_size = info->enc_size; /* Mark device as paired as their LTKs can be loaded. */ dev = btd_adapter_find_device(adapter, &info->bdaddr, info->bdaddr_type); if (dev) { device_set_paired(dev, info->bdaddr_type); device_set_bonded(dev, info->bdaddr_type); device_set_ltk(dev, info->val, info->central, info->enc_size); } } /* * This timeout handling is needed since the kernel is stupid * and forgets to send a command complete response. However in * case of failures it does send a command status. */ if (!mgmt_send_timeout(adapter->mgmt, MGMT_OP_LOAD_LONG_TERM_KEYS, adapter->dev_id, cp_size, cp, load_ltks_complete, adapter, NULL, 2)) btd_error(adapter->dev_id, "Failed to load LTKs for hci%u", adapter->dev_id); g_free(cp); } static void load_irks_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; if (status == MGMT_STATUS_UNKNOWN_COMMAND) { btd_info(adapter->dev_id, "Load IRKs failed: Kernel doesn't support LE Privacy"); return; } if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to load IRKs for hci%u: %s (0x%02x)", adapter->dev_id, mgmt_errstr(status), status); return; } DBG("IRKs loaded for hci%u", adapter->dev_id); } static void load_irks(struct btd_adapter *adapter, GSList *irks) { struct mgmt_cp_load_irks *cp; struct mgmt_irk_info *irk; size_t irk_count, cp_size; unsigned int id; GSList *l; /* * If the controller does not support LE Privacy operation, * there is no support for loading identity resolving keys * into the kernel. */ if (!(adapter->supported_settings & MGMT_SETTING_PRIVACY)) return; irk_count = g_slist_length(irks); DBG("hci%u irks %zu", adapter->dev_id, irk_count); cp_size = sizeof(*cp) + (irk_count * sizeof(*irk)); cp = g_try_malloc0(cp_size); if (cp == NULL) { btd_error(adapter->dev_id, "No memory for IRKs for hci%u", adapter->dev_id); return; } /* * Even if the list of stored keys is empty, it is important to * load an empty list into the kernel. That way we tell the * kernel that we are able to handle New IRK events. */ cp->irk_count = htobs(irk_count); for (l = irks, irk = cp->irks; l != NULL; l = g_slist_next(l), irk++) { struct irk_info *info = l->data; bacpy(&irk->addr.bdaddr, &info->bdaddr); irk->addr.type = info->bdaddr_type; memcpy(irk->val, info->val, sizeof(irk->val)); } id = mgmt_send(adapter->mgmt, MGMT_OP_LOAD_IRKS, adapter->dev_id, cp_size, cp, load_irks_complete, adapter, NULL); g_free(cp); if (id == 0) btd_error(adapter->dev_id, "Failed to IRKs for hci%u", adapter->dev_id); } static void load_conn_params_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "hci%u Load Connection Parameters failed: %s (0x%02x)", adapter->dev_id, mgmt_errstr(status), status); return; } DBG("Connection Parameters loaded for hci%u", adapter->dev_id); } static void load_conn_params(struct btd_adapter *adapter, GSList *params) { struct mgmt_cp_load_conn_param *cp; struct mgmt_conn_param *param; size_t param_count, cp_size; unsigned int id; GSList *l; /* * If the controller does not support Low Energy operation, * there is no point in trying to load the connection * parameters into the kernel. */ if (!(adapter->supported_settings & MGMT_SETTING_LE)) return; param_count = g_slist_length(params); DBG("hci%u conn params %zu", adapter->dev_id, param_count); cp_size = sizeof(*cp) + (param_count * sizeof(*param)); cp = g_try_malloc0(cp_size); if (cp == NULL) { btd_error(adapter->dev_id, "Failed to allocate memory for connection parameters"); return; } cp->param_count = htobs(param_count); for (l = params, param = cp->params; l; l = g_slist_next(l), param++) { struct conn_param *info = l->data; bacpy(¶m->addr.bdaddr, &info->bdaddr); param->addr.type = info->bdaddr_type; param->min_interval = htobs(info->min_interval); param->max_interval = htobs(info->max_interval); param->latency = htobs(info->latency); param->timeout = htobs(info->timeout); } id = mgmt_send(adapter->mgmt, MGMT_OP_LOAD_CONN_PARAM, adapter->dev_id, cp_size, cp, load_conn_params_complete, adapter, NULL); g_free(cp); if (id == 0) btd_error(adapter->dev_id, "Load connection parameters failed"); } void btd_adapter_load_conn_param(struct btd_adapter *adapter, const bdaddr_t *peer, uint8_t bdaddr_type, uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout) { GSList *params = NULL; struct conn_param param; /* Only versions >= 1.23 support updating connection parameters */ if (MGMT_VERSION(mgmt_version, mgmt_revision) < MGMT_VERSION(1, 23)) return; bacpy(¶m.bdaddr, peer); param.bdaddr_type = bdaddr_type; param.max_interval = max_interval; param.min_interval = min_interval; param.latency = latency; param.timeout = timeout; params = g_slist_append(params, ¶m); load_conn_params(adapter, params); g_slist_free(params); } static uint8_t get_addr_type(GKeyFile *keyfile) { uint8_t addr_type; char *type; /* The AddressType is written to file only When dev->le is * set to true, as referenced in the update_technologies(). * Therefore, When type is NULL, it default to BDADDR_BREDR. */ type = g_key_file_get_string(keyfile, "General", "AddressType", NULL); if (!type) return BDADDR_BREDR; if (g_str_equal(type, "public")) addr_type = BDADDR_LE_PUBLIC; else if (g_str_equal(type, "static")) addr_type = BDADDR_LE_RANDOM; else addr_type = BDADDR_LE_PUBLIC; g_free(type); return addr_type; } static void probe_devices(void *user_data) { struct btd_device *device = user_data; device_probe_profiles(device, btd_device_get_uuids(device)); device_resolved_drivers(device_get_adapter(device), device); } static bool load_bredr_defaults(struct btd_adapter *adapter, struct mgmt_tlv_list *list, struct btd_br_defaults *defaults) { if (btd_opts.mode == BT_MODE_LE) return true; if (defaults->page_scan_type != 0xFFFF) { if (!mgmt_tlv_add_fixed(list, 0x0000, &defaults->page_scan_type)) return false; } if (defaults->page_scan_interval) { if (!mgmt_tlv_add_fixed(list, 0x0001, &defaults->page_scan_interval)) return false; } if (defaults->page_scan_win) { if (!mgmt_tlv_add_fixed(list, 0x0002, &defaults->page_scan_win)) return false; } if (defaults->scan_type != 0xFFFF) { if (!mgmt_tlv_add_fixed(list, 0x0003, &defaults->scan_type)) return false; } if (defaults->scan_interval) { if (!mgmt_tlv_add_fixed(list, 0x0004, &defaults->scan_interval)) return false; } if (defaults->scan_win) { if (!mgmt_tlv_add_fixed(list, 0x0005, &defaults->scan_win)) return false; } if (defaults->link_supervision_timeout) { if (!mgmt_tlv_add_fixed(list, 0x0006, &defaults->link_supervision_timeout)) return false; } if (defaults->page_timeout) { if (!mgmt_tlv_add_fixed(list, 0x0007, &defaults->page_timeout)) return false; } if (defaults->min_sniff_interval) { if (!mgmt_tlv_add_fixed(list, 0x0008, &defaults->min_sniff_interval)) return false; } if (defaults->max_sniff_interval) { if (!mgmt_tlv_add_fixed(list, 0x0009, &defaults->max_sniff_interval)) return false; } if (defaults->idle_timeout) { if (!mgmt_tlv_add_fixed(list, 0x0020, &defaults->idle_timeout)) return false; } return true; } static bool load_le_defaults(struct btd_adapter *adapter, struct mgmt_tlv_list *list, struct btd_le_defaults *defaults) { if (btd_opts.mode == BT_MODE_BREDR) return true; if (defaults->min_adv_interval) { if (!mgmt_tlv_add_fixed(list, 0x000a, &defaults->min_adv_interval)) return false; } if (defaults->max_adv_interval) { if (!mgmt_tlv_add_fixed(list, 0x000b, &defaults->max_adv_interval)) return false; } if (defaults->adv_rotation_interval) { if (!mgmt_tlv_add_fixed(list, 0x000c, &defaults->adv_rotation_interval)) return false; } if (defaults->scan_interval_autoconnect) { if (!mgmt_tlv_add_fixed(list, 0x000d, &defaults->scan_interval_autoconnect)) return false; } if (defaults->scan_win_autoconnect) { if (!mgmt_tlv_add_fixed(list, 0x000e, &defaults->scan_win_autoconnect)) return false; } if (defaults->scan_interval_suspend) { if (!mgmt_tlv_add_fixed(list, 0x000f, &defaults->scan_interval_suspend)) return false; } if (defaults->scan_win_suspend) { if (!mgmt_tlv_add_fixed(list, 0x0010, &defaults->scan_win_suspend)) return false; } if (defaults->scan_interval_discovery) { if (!mgmt_tlv_add_fixed(list, 0x0011, &defaults->scan_interval_discovery)) return false; } if (defaults->scan_win_discovery) { if (!mgmt_tlv_add_fixed(list, 0x0012, &defaults->scan_win_discovery)) return false; } if (defaults->scan_interval_adv_monitor) { if (!mgmt_tlv_add_fixed(list, 0x0013, &defaults->scan_interval_adv_monitor)) return false; } if (defaults->scan_win_adv_monitor) { if (!mgmt_tlv_add_fixed(list, 0x0014, &defaults->scan_win_adv_monitor)) return false; } if (defaults->scan_interval_connect) { if (!mgmt_tlv_add_fixed(list, 0x0015, &defaults->scan_interval_connect)) return false; } if (defaults->scan_win_connect) { if (!mgmt_tlv_add_fixed(list, 0x0016, &defaults->scan_win_connect)) return false; } if (defaults->min_conn_interval) { if (!mgmt_tlv_add_fixed(list, 0x0017, &defaults->min_conn_interval)) return false; } if (defaults->max_conn_interval) { if (!mgmt_tlv_add_fixed(list, 0x0018, &defaults->max_conn_interval)) return false; } if (defaults->conn_latency) { if (!mgmt_tlv_add_fixed(list, 0x0019, &defaults->conn_latency)) return false; } if (defaults->conn_lsto) { if (!mgmt_tlv_add_fixed(list, 0x001a, &defaults->conn_lsto)) return false; } if (defaults->autoconnect_timeout) { if (!mgmt_tlv_add_fixed(list, 0x001b, &defaults->autoconnect_timeout)) return false; } if (defaults->advmon_allowlist_scan_duration) { if (!mgmt_tlv_add_fixed(list, 0x001d, &defaults->advmon_allowlist_scan_duration)) return false; } if (defaults->advmon_no_filter_scan_duration) { if (!mgmt_tlv_add_fixed(list, 0x001e, &defaults->advmon_no_filter_scan_duration)) return false; } if (defaults->enable_advmon_interleave_scan != 0xFF) { if (!mgmt_tlv_add_fixed(list, 0x001f, &defaults->enable_advmon_interleave_scan)) return false; } return true; } static void load_defaults(struct btd_adapter *adapter) { struct mgmt_tlv_list *list; unsigned int err = 0; if (!btd_opts.defaults.num_entries || !btd_has_kernel_features(KERNEL_SET_SYSTEM_CONFIG)) return; list = mgmt_tlv_list_new(); if (!load_bredr_defaults(adapter, list, &btd_opts.defaults.br)) goto done; if (!load_le_defaults(adapter, list, &btd_opts.defaults.le)) goto done; /* No changes from defaults */ if (mgmt_tlv_list_size(list) == 0) { mgmt_tlv_list_free(list); return; } err = mgmt_send_tlv(adapter->mgmt, MGMT_OP_SET_DEF_SYSTEM_CONFIG, adapter->dev_id, list, NULL, NULL, NULL); done: if (!err) btd_error(adapter->dev_id, "Failed to set default system config for hci%u", adapter->dev_id); mgmt_tlv_list_free(list); } static void load_devices(struct btd_adapter *adapter) { char dirname[PATH_MAX]; GSList *keys = NULL; GSList *ltks = NULL; GSList *irks = NULL; GSList *params = NULL; GSList *added_devices = NULL; GError *gerr = NULL; DIR *dir; struct dirent *entry; create_filename(dirname, PATH_MAX, "/%s", btd_adapter_get_storage_dir(adapter)); dir = opendir(dirname); if (!dir) { btd_error(adapter->dev_id, "Unable to open adapter storage directory: %s", dirname); return; } while ((entry = readdir(dir)) != NULL) { struct btd_device *device; char filename[PATH_MAX]; GKeyFile *key_file; struct link_key_info *key_info; struct smp_ltk_info *ltk_info; struct smp_ltk_info *peripheral_ltk_info; GSList *list; struct irk_info *irk_info; struct conn_param *param; uint8_t bdaddr_type; if (entry->d_type == DT_UNKNOWN) entry->d_type = util_get_dt(dirname, entry->d_name); if (entry->d_type != DT_DIR || bachk(entry->d_name) < 0) continue; create_filename(filename, PATH_MAX, "/%s/%s/info", btd_adapter_get_storage_dir(adapter), entry->d_name); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } bdaddr_type = get_addr_type(key_file); key_info = get_key_info(key_file, entry->d_name, bdaddr_type); ltk_info = get_ltk_info(key_file, entry->d_name, bdaddr_type); peripheral_ltk_info = get_peripheral_ltk_info(key_file, entry->d_name, bdaddr_type); irk_info = get_irk_info(key_file, entry->d_name, bdaddr_type); // If any key for the device is blocked, we discard all. if ((key_info && key_info->is_blocked) || (ltk_info && ltk_info->is_blocked) || (peripheral_ltk_info && peripheral_ltk_info->is_blocked) || (irk_info && irk_info->is_blocked)) { if (key_info) { g_free(key_info); key_info = NULL; } if (ltk_info) { g_free(ltk_info); ltk_info = NULL; } if (peripheral_ltk_info) { g_free(peripheral_ltk_info); peripheral_ltk_info = NULL; } if (irk_info) { g_free(irk_info); irk_info = NULL; } goto free; } if (key_info) keys = g_slist_append(keys, key_info); if (ltk_info) ltks = g_slist_append(ltks, ltk_info); if (peripheral_ltk_info) ltks = g_slist_append(ltks, peripheral_ltk_info); if (irk_info) irks = g_slist_append(irks, irk_info); param = get_conn_param(key_file, entry->d_name, bdaddr_type); if (param) params = g_slist_append(params, param); list = g_slist_find_custom(adapter->devices, entry->d_name, device_address_cmp); if (list) { device = list->data; goto device_exist; } device = device_create_from_storage(adapter, entry->d_name, key_file); if (!device) goto free; if (irk_info) device_set_privacy(device, true, irk_info->val); btd_device_set_temporary(device, false); adapter_add_device(adapter, device); /* TODO: register services from pre-loaded list of primaries */ added_devices = g_slist_append(added_devices, device); device_exist: if (key_info) { device_set_paired(device, BDADDR_BREDR); device_set_bonded(device, BDADDR_BREDR); } free: g_key_file_free(key_file); } closedir(dir); load_link_keys(adapter, keys, btd_opts.debug_keys); g_slist_free_full(keys, g_free); load_ltks(adapter, ltks); g_slist_free_full(ltks, g_free); load_irks(adapter, irks); g_slist_free_full(irks, g_free); load_conn_params(adapter, params); g_slist_free_full(params, g_free); g_slist_free_full(added_devices, probe_devices); } int btd_adapter_block_address(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type) { struct mgmt_cp_block_device cp; char addr[18]; ba2str(bdaddr, addr); DBG("hci%u %s", adapter->dev_id, addr); memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); cp.addr.type = bdaddr_type; if (mgmt_send(adapter->mgmt, MGMT_OP_BLOCK_DEVICE, adapter->dev_id, sizeof(cp), &cp, NULL, NULL, NULL) > 0) return 0; return -EIO; } int btd_adapter_unblock_address(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type) { struct mgmt_cp_unblock_device cp; char addr[18]; ba2str(bdaddr, addr); DBG("hci%u %s", adapter->dev_id, addr); memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); cp.addr.type = bdaddr_type; if (mgmt_send(adapter->mgmt, MGMT_OP_UNBLOCK_DEVICE, adapter->dev_id, sizeof(cp), &cp, NULL, NULL, NULL) > 0) return 0; return -EIO; } static int clear_blocked(struct btd_adapter *adapter) { return btd_adapter_unblock_address(adapter, BDADDR_ANY, 0); } static void probe_driver(struct btd_adapter *adapter, gpointer user_data) { struct btd_adapter_driver *driver = user_data; int err; if (driver->probe == NULL) return; err = driver->probe(adapter); if (err < 0) { btd_error(adapter->dev_id, "%s: %s (%d)", driver->name, strerror(-err), -err); return; } adapter->drivers = g_slist_prepend(adapter->drivers, driver); } static void load_drivers(struct btd_adapter *adapter) { GSList *l; for (l = adapter_drivers; l; l = l->next) probe_driver(adapter, l->data); } static void probe_profile(struct btd_profile *profile, void *data) { struct btd_adapter *adapter = data; int err; if (profile->adapter_probe == NULL) return; err = profile->adapter_probe(profile, adapter); if (err < 0) { btd_error(adapter->dev_id, "%s: %s (%d)", profile->name, strerror(-err), -err); return; } adapter->profiles = g_slist_prepend(adapter->profiles, profile); } void adapter_add_profile(struct btd_adapter *adapter, gpointer p) { struct btd_profile *profile = p; if (!adapter->initialized) return; probe_profile(profile, adapter); g_slist_foreach(adapter->devices, device_probe_profile, profile); } void adapter_remove_profile(struct btd_adapter *adapter, gpointer p) { struct btd_profile *profile = p; if (!adapter->initialized) return; if (profile->device_remove) g_slist_foreach(adapter->devices, device_remove_profile, p); adapter->profiles = g_slist_remove(adapter->profiles, profile); if (profile->adapter_remove) profile->adapter_remove(profile, adapter); } static void device_added_drivers(struct btd_adapter *adapter, struct btd_device *device) { struct btd_adapter_driver *driver; GSList *l; for (l = adapter_drivers; l; l = l->next) { driver = l->data; if (driver->device_added) driver->device_added(adapter, device); } } static void device_removed_drivers(struct btd_adapter *adapter, struct btd_device *device) { struct btd_adapter_driver *driver; GSList *l; for (l = adapter_drivers; l; l = l->next) { driver = l->data; if (driver->device_removed) driver->device_removed(adapter, device); } } void device_resolved_drivers(struct btd_adapter *adapter, struct btd_device *device) { struct btd_adapter_driver *driver; GSList *l; for (l = adapter_drivers; l; l = l->next) { driver = l->data; if (driver->device_resolved) driver->device_resolved(adapter, device); } } static void adapter_add_device(struct btd_adapter *adapter, struct btd_device *device) { adapter->devices = g_slist_prepend(adapter->devices, device); device_added_drivers(adapter, device); } static void adapter_remove_device(struct btd_adapter *adapter, struct btd_device *device) { adapter->devices = g_slist_remove(adapter->devices, device); device_removed_drivers(adapter, device); } static void adapter_add_connection(struct btd_adapter *adapter, struct btd_device *device, uint8_t bdaddr_type, uint32_t flags) { device_add_connection(device, bdaddr_type, flags); if (g_slist_find(adapter->connections, device)) { btd_error(adapter->dev_id, "Device is already marked as connected"); return; } adapter->connections = g_slist_append(adapter->connections, device); } static void get_connections_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; const struct mgmt_rp_get_connections *rp = param; uint16_t i, conn_count; if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to get connections: %s (0x%02x)", mgmt_errstr(status), status); return; } if (length < sizeof(*rp)) { btd_error(adapter->dev_id, "Wrong size of get connections response"); return; } conn_count = btohs(rp->conn_count); DBG("Connection count: %d", conn_count); if (conn_count * sizeof(struct mgmt_addr_info) + sizeof(*rp) != length) { btd_error(adapter->dev_id, "Incorrect packet size for get connections response"); return; } for (i = 0; i < conn_count; i++) { const struct mgmt_addr_info *addr = &rp->addr[i]; struct btd_device *device; char address[18]; ba2str(&addr->bdaddr, address); DBG("Adding existing connection to %s", address); device = btd_adapter_get_device(adapter, &addr->bdaddr, addr->type); if (device) adapter_add_connection(adapter, device, addr->type, 0); } } static void load_connections(struct btd_adapter *adapter) { DBG("sending get connections command for index %u", adapter->dev_id); if (mgmt_send(adapter->mgmt, MGMT_OP_GET_CONNECTIONS, adapter->dev_id, 0, NULL, get_connections_complete, adapter, NULL) > 0) return; btd_error(adapter->dev_id, "Failed to get connections for index %u", adapter->dev_id); } bool btd_adapter_get_pairable(struct btd_adapter *adapter) { if (adapter->current_settings & MGMT_SETTING_BONDABLE) return true; return false; } bool btd_adapter_get_powered(struct btd_adapter *adapter) { if ((adapter->current_settings & MGMT_SETTING_POWERED) && !(adapter->pending_settings & MGMT_SETTING_POWERED)) return true; return false; } bool btd_adapter_get_connectable(struct btd_adapter *adapter) { if (adapter->current_settings & MGMT_SETTING_CONNECTABLE) return true; return false; } bool btd_adapter_get_discoverable(struct btd_adapter *adapter) { if (adapter->current_settings & MGMT_SETTING_DISCOVERABLE) return true; return false; } bool btd_adapter_get_bredr(struct btd_adapter *adapter) { if (adapter->current_settings & MGMT_SETTING_BREDR) return true; return false; } struct btd_gatt_database *btd_adapter_get_database(struct btd_adapter *adapter) { if (!adapter) return NULL; return adapter->database; } uint32_t btd_adapter_get_class(struct btd_adapter *adapter) { return adapter->dev_class; } const char *btd_adapter_get_name(struct btd_adapter *adapter) { if (adapter->stored_alias) return adapter->stored_alias; if (adapter->system_name) return adapter->system_name; return NULL; } int adapter_connect_list_add(struct btd_adapter *adapter, struct btd_device *device) { /* * If the adapter->connect_le device is getting added back to * the connect list it probably means that the connect attempt * failed and hence we should clear this pointer */ if (device == adapter->connect_le) adapter->connect_le = NULL; /* * If kernel background scanning is supported then the * adapter_auto_connect_add() function is used to maintain what to * connect. */ if (btd_has_kernel_features(KERNEL_CONN_CONTROL)) return 0; if (g_slist_find(adapter->connect_list, device)) { DBG("ignoring already added device %s", device_get_path(device)); goto done; } if (!(adapter->supported_settings & MGMT_SETTING_LE)) { btd_error(adapter->dev_id, "Can't add %s to non-LE capable adapter connect list", device_get_path(device)); return -ENOTSUP; } adapter->connect_list = g_slist_append(adapter->connect_list, device); DBG("%s added to %s's connect_list", device_get_path(device), adapter->system_name); done: if (!btd_adapter_get_powered(adapter)) return 0; trigger_passive_scanning(adapter); return 0; } void adapter_connect_list_remove(struct btd_adapter *adapter, struct btd_device *device) { /* * If the adapter->connect_le device is being removed from the * connect list it means the connection was successful and hence * the pointer should be cleared */ if (device == adapter->connect_le) adapter->connect_le = NULL; if (btd_has_kernel_features(KERNEL_CONN_CONTROL)) return; if (!g_slist_find(adapter->connect_list, device)) { DBG("device %s is not on the list, ignoring", device_get_path(device)); return; } adapter->connect_list = g_slist_remove(adapter->connect_list, device); DBG("%s removed from %s's connect_list", device_get_path(device), adapter->system_name); if (!adapter->connect_list) { stop_passive_scanning(adapter); return; } if (!btd_adapter_get_powered(adapter)) return; trigger_passive_scanning(adapter); } static void add_accept_list_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_add_device *rp = param; struct btd_adapter *adapter = user_data; struct btd_device *dev; char addr[18]; if (length < sizeof(*rp)) { btd_error(adapter->dev_id, "Too small Add Device complete event"); return; } ba2str(&rp->addr.bdaddr, addr); dev = btd_adapter_find_device(adapter, &rp->addr.bdaddr, rp->addr.type); if (!dev) { btd_error(adapter->dev_id, "Add Device complete for unknown device %s", addr); return; } if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to add device %s: %s (0x%02x)", addr, mgmt_errstr(status), status); return; } DBG("%s added to kernel accept list", addr); } void adapter_accept_list_add(struct btd_adapter *adapter, struct btd_device *dev) { struct mgmt_cp_add_device cp; if (!btd_has_kernel_features(KERNEL_CONN_CONTROL)) return; memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, device_get_address(dev)); cp.addr.type = BDADDR_BREDR; cp.action = 0x01; mgmt_send(adapter->mgmt, MGMT_OP_ADD_DEVICE, adapter->dev_id, sizeof(cp), &cp, add_accept_list_complete, adapter, NULL); } static void remove_accept_list_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_remove_device *rp = param; char addr[18]; if (length < sizeof(*rp)) { error("Too small Remove Device complete event"); return; } ba2str(&rp->addr.bdaddr, addr); if (status != MGMT_STATUS_SUCCESS) { error("Failed to remove device %s: %s (0x%02x)", addr, mgmt_errstr(status), status); return; } DBG("%s removed from kernel accept list", addr); } void adapter_accept_list_remove(struct btd_adapter *adapter, struct btd_device *dev) { struct mgmt_cp_remove_device cp; if (!btd_has_kernel_features(KERNEL_CONN_CONTROL)) return; memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, device_get_address(dev)); cp.addr.type = BDADDR_BREDR; mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_DEVICE, adapter->dev_id, sizeof(cp), &cp, remove_accept_list_complete, adapter, NULL); } static void set_device_privacy_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_device *dev = user_data; const struct mgmt_rp_set_device_flags *rp = param; if (status != MGMT_STATUS_SUCCESS) { error("Set device flags return status: %s", mgmt_errstr(status)); btd_device_set_pending_flags(dev, 0); return; } if (length < sizeof(*rp)) { error("Too small Set Device Flags complete event: %d", length); return; } btd_device_flags_changed(dev, btd_device_get_supported_flags(dev), btd_device_get_pending_flags(dev)); } static void add_device_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_add_device *rp = param; struct btd_adapter *adapter = user_data; struct btd_device *dev; char addr[18]; uint32_t flags; if (length < sizeof(*rp)) { btd_error(adapter->dev_id, "Too small Add Device complete event"); return; } ba2str(&rp->addr.bdaddr, addr); dev = btd_adapter_find_device(adapter, &rp->addr.bdaddr, rp->addr.type); if (!dev) { btd_error(adapter->dev_id, "Add Device complete for unknown device %s", addr); return; } if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to add device %s (%u): %s (0x%02x)", addr, rp->addr.type, mgmt_errstr(status), status); adapter->connect_list = g_slist_remove(adapter->connect_list, dev); return; } DBG("%s (%u) added to kernel connect list", addr, rp->addr.type); if (btd_opts.device_privacy) { flags = btd_device_get_current_flags(dev); /* Set Device Privacy Mode if it has not set the flag yet. */ if (!(flags & DEVICE_FLAG_DEVICE_PRIVACY)) { /* Include the pending flags, or they may get * overwritten. */ flags |= btd_device_get_pending_flags(dev); adapter_set_device_flags(adapter, dev, flags | DEVICE_FLAG_DEVICE_PRIVACY, set_device_privacy_complete, dev); return; } } /* Check if any flag was marked as pending before ADD_DEVICE * complete then set it now */ flags = btd_device_get_pending_flags(dev); if (flags) adapter_set_device_flags(adapter, dev, flags, set_device_privacy_complete, dev); } void adapter_auto_connect_add(struct btd_adapter *adapter, struct btd_device *device) { struct mgmt_cp_add_device cp; const bdaddr_t *bdaddr; uint8_t bdaddr_type; unsigned int id; if (!btd_has_kernel_features(KERNEL_CONN_CONTROL)) return; if (g_slist_find(adapter->connect_list, device)) { DBG("ignoring already added device %s", device_get_path(device)); return; } bdaddr = device_get_address(device); bdaddr_type = btd_device_get_bdaddr_type(device); if (bdaddr_type == BDADDR_BREDR) { DBG("auto-connection feature is not available for BR/EDR"); return; } memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); cp.addr.type = bdaddr_type; cp.action = 0x02; id = mgmt_send(adapter->mgmt, MGMT_OP_ADD_DEVICE, adapter->dev_id, sizeof(cp), &cp, add_device_complete, adapter, NULL); if (id == 0) return; adapter->connect_list = g_slist_append(adapter->connect_list, device); } int adapter_set_device_flags(struct btd_adapter *adapter, struct btd_device *device, uint32_t flags, mgmt_request_func_t func, void *user_data) { struct mgmt_cp_set_device_flags cp; uint32_t current = btd_device_get_current_flags(device); uint32_t supported = btd_device_get_supported_flags(device); uint32_t pending = btd_device_get_pending_flags(device); const bdaddr_t *bdaddr; uint8_t bdaddr_type; bool ll_privacy = btd_adapter_has_settings(adapter, MGMT_SETTING_LL_PRIVACY); unsigned int id; if (!btd_has_kernel_features(KERNEL_CONN_CONTROL) || (supported && (supported | flags) != supported)) return -EINVAL; /* Check if changing flags are pending */ if ((current ^ flags) == (flags & pending)) return -EINPROGRESS; /* Set Device Privacy Mode if it has not set the flag yet. */ if (btd_opts.device_privacy && !(flags & DEVICE_FLAG_DEVICE_PRIVACY)) flags |= DEVICE_FLAG_DEVICE_PRIVACY & supported & ~pending; /* Set Address Resolution if it has not been set the flag yet. */ if (ll_privacy && btd_opts.defaults.le.addr_resolution && device_get_privacy(device) && !(flags & DEVICE_FLAG_ADDRESS_RESOLUTION)) flags |= DEVICE_FLAG_ADDRESS_RESOLUTION & supported & ~pending; bdaddr = device_get_address(device); bdaddr_type = btd_device_get_bdaddr_type(device); memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); cp.addr.type = bdaddr_type; cp.current_flags = cpu_to_le32(flags); id = mgmt_send(adapter->mgmt, MGMT_OP_SET_DEVICE_FLAGS, adapter->dev_id, sizeof(cp), &cp, func, user_data, NULL); if (id != 0) btd_device_set_pending_flags(device, flags); return id == 0 ? -EBUSY : 0; } static void device_flags_changed_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_device_flags_changed *ev = param; struct btd_adapter *adapter = user_data; struct btd_device *dev; char addr[18]; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small Device Flags Changed event: %d", length); return; } ba2str(&ev->addr.bdaddr, addr); dev = btd_adapter_find_device(adapter, &ev->addr.bdaddr, ev->addr.type); if (!dev) { btd_error(adapter->dev_id, "Device Flags Changed for unknown device %s", addr); return; } btd_device_flags_changed(dev, ev->supported_flags, ev->current_flags); } static void remove_device_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_remove_device *rp = param; char addr[18]; if (length < sizeof(*rp)) { error("Too small Remove Device complete event"); return; } ba2str(&rp->addr.bdaddr, addr); if (status != MGMT_STATUS_SUCCESS) { error("Failed to remove device %s (%u): %s (0x%02x)", addr, rp->addr.type, mgmt_errstr(status), status); return; } DBG("%s (%u) removed from kernel connect list", addr, rp->addr.type); } void adapter_auto_connect_remove(struct btd_adapter *adapter, struct btd_device *device) { struct mgmt_cp_remove_device cp; const bdaddr_t *bdaddr; uint8_t bdaddr_type; unsigned int id; if (!btd_has_kernel_features(KERNEL_CONN_CONTROL)) return; if (!g_slist_find(adapter->connect_list, device)) { DBG("ignoring not added device %s", device_get_path(device)); return; } bdaddr = device_get_address(device); bdaddr_type = btd_device_get_bdaddr_type(device); if (bdaddr_type == BDADDR_BREDR) { DBG("auto-connection feature is not available for BR/EDR"); return; } memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); cp.addr.type = bdaddr_type; id = mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_DEVICE, adapter->dev_id, sizeof(cp), &cp, remove_device_complete, adapter, NULL); if (id == 0) return; adapter->connect_list = g_slist_remove(adapter->connect_list, device); } static void adapter_start(struct btd_adapter *adapter) { g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Powered"); adapter_set_power_state(adapter, ADAPTER_POWER_STATE_ON); DBG("adapter %s has been enabled", adapter->path); trigger_passive_scanning(adapter); } static void reply_pending_requests(struct btd_adapter *adapter) { GSList *l; if (!adapter) return; /* pending bonding */ for (l = adapter->devices; l; l = l->next) { struct btd_device *device = l->data; if (device_is_bonding(device, NULL)) device_bonding_failed(device, HCI_OE_USER_ENDED_CONNECTION); } } static void remove_driver(gpointer data, gpointer user_data) { struct btd_adapter_driver *driver = data; struct btd_adapter *adapter = user_data; if (driver->remove) driver->remove(adapter); } static void remove_profile(gpointer data, gpointer user_data) { struct btd_profile *profile = data; struct btd_adapter *adapter = user_data; if (profile->adapter_remove) profile->adapter_remove(profile, adapter); } static void unload_drivers(struct btd_adapter *adapter) { g_slist_foreach(adapter->drivers, remove_driver, adapter); g_slist_free(adapter->drivers); adapter->drivers = NULL; g_slist_foreach(adapter->profiles, remove_profile, adapter); g_slist_free(adapter->profiles); adapter->profiles = NULL; } static void free_service_auth(gpointer data, gpointer user_data) { struct service_auth *auth = data; g_free(auth); } static void remove_discovery_list(struct btd_adapter *adapter) { g_slist_free_full(adapter->set_filter_list, discovery_free); adapter->set_filter_list = NULL; g_slist_free_full(adapter->discovery_list, discovery_free); adapter->discovery_list = NULL; } static void cancel_exp_pending(void *data) { struct exp_pending *pending = data; struct btd_adapter *adapter = pending->adapter; pending->adapter = NULL; mgmt_cancel(adapter->mgmt, pending->id); g_free(pending); } static void adapter_free(gpointer user_data) { struct btd_adapter *adapter = user_data; DBG("%p", adapter); /* Make sure the adapter's discovery list is cleaned up before freeing * the adapter. */ remove_discovery_list(adapter); if (adapter->pairable_timeout_id > 0) { timeout_remove(adapter->pairable_timeout_id); adapter->pairable_timeout_id = 0; } if (adapter->passive_scan_timeout > 0) { timeout_remove(adapter->passive_scan_timeout); adapter->passive_scan_timeout = 0; } if (adapter->auth_idle_id) g_source_remove(adapter->auth_idle_id); g_queue_foreach(adapter->auths, free_service_auth, NULL); g_queue_free(adapter->auths); queue_destroy(adapter->exps, NULL); queue_destroy(adapter->exp_pending, cancel_exp_pending); /* * Unregister all handlers for this specific index since * the adapter bound to them is no longer valid. * * This also avoids having multiple instances of the same * handler in case indexes got removed and re-added. */ mgmt_unregister_index(adapter->mgmt, adapter->dev_id); /* * Cancel all pending commands for this specific index * since the adapter bound to them is no longer valid. */ mgmt_cancel_index(adapter->mgmt, adapter->dev_id); mgmt_unref(adapter->mgmt); sdp_list_free(adapter->services, NULL); g_slist_free(adapter->connections); g_free(adapter->path); g_free(adapter->name); g_free(adapter->short_name); g_free(adapter->system_name); g_free(adapter->stored_alias); g_free(adapter->current_alias); free(adapter->modalias); if (adapter->allowed_uuid_set) g_hash_table_destroy(adapter->allowed_uuid_set); g_free(adapter); } struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter) { __sync_fetch_and_add(&adapter->ref_count, 1); return adapter; } void btd_adapter_unref(struct btd_adapter *adapter) { if (__sync_sub_and_fetch(&adapter->ref_count, 1)) return; if (!adapter->path) { DBG("Freeing adapter %u", adapter->dev_id); adapter_free(adapter); return; } DBG("Freeing adapter %s", adapter->path); g_dbus_unregister_interface(dbus_conn, adapter->path, ADAPTER_INTERFACE); } static void convert_names_entry(char *key, char *value, void *user_data) { char *address = user_data; char *str = key; char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; char *data; gsize length = 0; if (strchr(key, '#')) str[17] = '\0'; if (bachk(str) != 0) return; create_filename(filename, PATH_MAX, "/%s/cache/%s", address, str); create_file(filename, 0600); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } g_key_file_set_string(key_file, "General", "Name", value); data = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(data); g_key_file_free(key_file); } struct device_converter { char *address; void (*cb)(GKeyFile *key_file, void *value); gboolean force; }; static void set_device_type(GKeyFile *key_file, char type) { char *techno; char *addr_type = NULL; char *str; switch (type) { case BDADDR_BREDR: techno = "BR/EDR"; break; case BDADDR_LE_PUBLIC: techno = "LE"; addr_type = "public"; break; case BDADDR_LE_RANDOM: techno = "LE"; addr_type = "static"; break; default: return; } str = g_key_file_get_string(key_file, "General", "SupportedTechnologies", NULL); if (!str) g_key_file_set_string(key_file, "General", "SupportedTechnologies", techno); else if (!strstr(str, techno)) g_key_file_set_string(key_file, "General", "SupportedTechnologies", "BR/EDR;LE"); g_free(str); if (addr_type) g_key_file_set_string(key_file, "General", "AddressType", addr_type); } static void convert_aliases_entry(GKeyFile *key_file, void *value) { g_key_file_set_string(key_file, "General", "Alias", value); } static void convert_trusts_entry(GKeyFile *key_file, void *value) { g_key_file_set_boolean(key_file, "General", "Trusted", TRUE); } static void convert_classes_entry(GKeyFile *key_file, void *value) { g_key_file_set_string(key_file, "General", "Class", value); } static void convert_blocked_entry(GKeyFile *key_file, void *value) { g_key_file_set_boolean(key_file, "General", "Blocked", TRUE); } static void convert_did_entry(GKeyFile *key_file, void *value) { char *vendor_str, *product_str, *version_str; uint16_t val; vendor_str = strchr(value, ' '); if (!vendor_str) return; *(vendor_str++) = 0; if (g_str_equal(value, "FFFF")) return; product_str = strchr(vendor_str, ' '); if (!product_str) return; *(product_str++) = 0; version_str = strchr(product_str, ' '); if (!version_str) return; *(version_str++) = 0; val = (uint16_t) strtol(value, NULL, 16); g_key_file_set_integer(key_file, "DeviceID", "Source", val); val = (uint16_t) strtol(vendor_str, NULL, 16); g_key_file_set_integer(key_file, "DeviceID", "Vendor", val); val = (uint16_t) strtol(product_str, NULL, 16); g_key_file_set_integer(key_file, "DeviceID", "Product", val); val = (uint16_t) strtol(version_str, NULL, 16); g_key_file_set_integer(key_file, "DeviceID", "Version", val); } static void convert_linkkey_entry(GKeyFile *key_file, void *value) { char *type_str, *length_str, *str; int val; type_str = strchr(value, ' '); if (!type_str) return; *(type_str++) = 0; length_str = strchr(type_str, ' '); if (!length_str) return; *(length_str++) = 0; str = g_strconcat("0x", value, NULL); g_key_file_set_string(key_file, "LinkKey", "Key", str); g_free(str); val = strtol(type_str, NULL, 16); g_key_file_set_integer(key_file, "LinkKey", "Type", val); val = strtol(length_str, NULL, 16); g_key_file_set_integer(key_file, "LinkKey", "PINLength", val); } static void convert_ltk_entry(GKeyFile *key_file, void *value) { char *auth_str, *rand_str, *str; int i, ret; unsigned char auth, central, enc_size; unsigned short ediv; auth_str = strchr(value, ' '); if (!auth_str) return; *(auth_str++) = 0; for (i = 0, rand_str = auth_str; i < 4; i++) { rand_str = strchr(rand_str, ' '); if (!rand_str || rand_str[1] == '\0') return; rand_str++; } ret = sscanf(auth_str, " %hhd %hhd %hhd %hd", &auth, ¢ral, &enc_size, &ediv); if (ret < 4) return; str = g_strconcat("0x", value, NULL); g_key_file_set_string(key_file, "LongTermKey", "Key", str); g_free(str); g_key_file_set_integer(key_file, "LongTermKey", "Authenticated", auth); g_key_file_set_integer(key_file, "LongTermKey", "EncSize", enc_size); g_key_file_set_integer(key_file, "LongTermKey", "EDiv", ediv); str = g_strconcat("0x", rand_str, NULL); g_key_file_set_string(key_file, "LongTermKey", "Rand", str); g_free(str); } static void convert_profiles_entry(GKeyFile *key_file, void *value) { g_strdelimit(value, " ", ';'); g_key_file_set_string(key_file, "General", "Services", value); } static void convert_appearances_entry(GKeyFile *key_file, void *value) { g_key_file_set_string(key_file, "General", "Appearance", value); } static void convert_entry(char *key, char *value, void *user_data) { struct device_converter *converter = user_data; char type = BDADDR_BREDR; char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; char *data; gsize length = 0; if (strchr(key, '#')) { key[17] = '\0'; type = key[18] - '0'; } if (bachk(key) != 0) return; if (converter->force == FALSE) { struct stat st; int err; create_filename(filename, PATH_MAX, "/%s/%s", converter->address, key); err = stat(filename, &st); if (err || !S_ISDIR(st.st_mode)) return; } create_filename(filename, PATH_MAX, "/%s/%s/info", converter->address, key); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } set_device_type(key_file, type); converter->cb(key_file, value); data = g_key_file_to_data(key_file, &length, NULL); if (length > 0) { create_file(filename, 0600); if (!g_file_set_contents(filename, data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } } g_free(data); g_key_file_free(key_file); } static void convert_file(char *file, char *address, void (*cb)(GKeyFile *key_file, void *value), gboolean force) { char filename[PATH_MAX]; struct device_converter converter; create_filename(filename, PATH_MAX, "/%s/%s", address, file); converter.address = address; converter.cb = cb; converter.force = force; textfile_foreach(filename, convert_entry, &converter); } static gboolean record_has_uuid(const sdp_record_t *rec, const char *profile_uuid) { sdp_list_t *pat; for (pat = rec->pattern; pat != NULL; pat = pat->next) { char *uuid; int ret; uuid = bt_uuid2string(pat->data); if (!uuid) continue; ret = strcasecmp(uuid, profile_uuid); free(uuid); if (ret == 0) return TRUE; } return FALSE; } static void store_attribute_uuid(GKeyFile *key_file, uint16_t start, uint16_t end, char *att_uuid, uuid_t uuid) { char handle[6], uuid_str[33]; int i; switch (uuid.type) { case SDP_UUID16: sprintf(uuid_str, "%4.4X", uuid.value.uuid16); break; case SDP_UUID32: sprintf(uuid_str, "%8.8X", uuid.value.uuid32); break; case SDP_UUID128: for (i = 0; i < 16; i++) sprintf(uuid_str + (i * 2), "%2.2X", uuid.value.uuid128.data[i]); break; default: uuid_str[0] = '\0'; } sprintf(handle, "%hu", start); g_key_file_set_string(key_file, handle, "UUID", att_uuid); g_key_file_set_string(key_file, handle, "Value", uuid_str); g_key_file_set_integer(key_file, handle, "EndGroupHandle", end); } static void store_sdp_record(char *local, char *peer, int handle, char *value) { char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; char handle_str[11]; char *data; gsize length = 0; create_filename(filename, PATH_MAX, "/%s/cache/%s", local, peer); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } sprintf(handle_str, "0x%8.8X", handle); g_key_file_set_string(key_file, "ServiceRecords", handle_str, value); data = g_key_file_to_data(key_file, &length, NULL); if (length > 0) { create_file(filename, 0600); if (!g_file_set_contents(filename, data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } } g_free(data); g_key_file_free(key_file); } static void convert_sdp_entry(char *key, char *value, void *user_data) { char *src_addr = user_data; char dst_addr[18]; char type = BDADDR_BREDR; int handle, ret; char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; struct stat st; sdp_record_t *rec; uuid_t uuid; char *att_uuid, *prim_uuid; uint16_t start = 0, end = 0, psm = 0; int err; char *data; gsize length = 0; ret = sscanf(key, "%17s#%hhu#%08X", dst_addr, &type, &handle); if (ret < 3) { ret = sscanf(key, "%17s#%08X", dst_addr, &handle); if (ret < 2) return; } if (bachk(dst_addr) != 0) return; /* Check if the device directory has been created as records should * only be converted for known devices */ create_filename(filename, PATH_MAX, "/%s/%s", src_addr, dst_addr); err = stat(filename, &st); if (err || !S_ISDIR(st.st_mode)) return; /* store device records in cache */ store_sdp_record(src_addr, dst_addr, handle, value); /* Retrieve device record and check if there is an * attribute entry in it */ sdp_uuid16_create(&uuid, ATT_UUID); att_uuid = bt_uuid2string(&uuid); sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); prim_uuid = bt_uuid2string(&uuid); rec = record_from_string(value); if (record_has_uuid(rec, att_uuid)) goto failed; /* TODO: Do this through btd_gatt_database */ if (!gatt_parse_record(rec, &uuid, &psm, &start, &end)) goto failed; create_filename(filename, PATH_MAX, "/%s/%s/attributes", src_addr, dst_addr); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } store_attribute_uuid(key_file, start, end, prim_uuid, uuid); data = g_key_file_to_data(key_file, &length, NULL); if (length > 0) { create_file(filename, 0600); if (!g_file_set_contents(filename, data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } } g_free(data); g_key_file_free(key_file); failed: sdp_record_free(rec); free(prim_uuid); free(att_uuid); } static void convert_primaries_entry(char *key, char *value, void *user_data) { char *address = user_data; int device_type = -1; uuid_t uuid; char **services, **service, *prim_uuid; char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; int ret; uint16_t start, end; char uuid_str[MAX_LEN_UUID_STR + 1]; char *data; gsize length = 0; if (strchr(key, '#')) { key[17] = '\0'; device_type = key[18] - '0'; } if (bachk(key) != 0) return; services = g_strsplit(value, " ", 0); if (services == NULL) return; sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); prim_uuid = bt_uuid2string(&uuid); create_filename(filename, PATH_MAX, "/%s/%s/attributes", address, key); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } for (service = services; *service; service++) { ret = sscanf(*service, "%04hX#%04hX#%s", &start, &end, uuid_str); if (ret < 3) continue; bt_string2uuid(&uuid, uuid_str); sdp_uuid128_to_uuid(&uuid); store_attribute_uuid(key_file, start, end, prim_uuid, uuid); } g_strfreev(services); data = g_key_file_to_data(key_file, &length, NULL); if (length == 0) goto end; create_file(filename, 0600); if (!g_file_set_contents(filename, data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } if (device_type < 0) goto end; g_free(data); g_key_file_free(key_file); create_filename(filename, PATH_MAX, "/%s/%s/info", address, key); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } set_device_type(key_file, device_type); data = g_key_file_to_data(key_file, &length, NULL); if (length > 0) { create_file(filename, 0600); if (!g_file_set_contents(filename, data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } } end: g_free(data); free(prim_uuid); g_key_file_free(key_file); } static void convert_ccc_entry(char *key, char *value, void *user_data) { char *src_addr = user_data; char dst_addr[18]; char type = BDADDR_BREDR; uint16_t handle; int ret, err; char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; struct stat st; char group[6]; char *data; gsize length = 0; ret = sscanf(key, "%17s#%hhu#%04hX", dst_addr, &type, &handle); if (ret < 3) return; if (bachk(dst_addr) != 0) return; /* Check if the device directory has been created as records should * only be converted for known devices */ create_filename(filename, PATH_MAX, "/%s/%s", src_addr, dst_addr); err = stat(filename, &st); if (err || !S_ISDIR(st.st_mode)) return; create_filename(filename, PATH_MAX, "/%s/%s/ccc", src_addr, dst_addr); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } sprintf(group, "%hu", handle); g_key_file_set_string(key_file, group, "Value", value); data = g_key_file_to_data(key_file, &length, NULL); if (length > 0) { create_file(filename, 0600); if (!g_file_set_contents(filename, data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } } g_free(data); g_key_file_free(key_file); } static void convert_gatt_entry(char *key, char *value, void *user_data) { char *src_addr = user_data; char dst_addr[18]; char type = BDADDR_BREDR; uint16_t handle; int ret, err; char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; struct stat st; char group[6]; char *data; gsize length = 0; ret = sscanf(key, "%17s#%hhu#%04hX", dst_addr, &type, &handle); if (ret < 3) return; if (bachk(dst_addr) != 0) return; /* Check if the device directory has been created as records should * only be converted for known devices */ create_filename(filename, PATH_MAX, "/%s/%s", src_addr, dst_addr); err = stat(filename, &st); if (err || !S_ISDIR(st.st_mode)) return; create_filename(filename, PATH_MAX, "/%s/%s/gatt", src_addr, dst_addr); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } sprintf(group, "%hu", handle); g_key_file_set_string(key_file, group, "Value", value); data = g_key_file_to_data(key_file, &length, NULL); if (length > 0) { create_file(filename, 0600); if (!g_file_set_contents(filename, data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } } g_free(data); g_key_file_free(key_file); } static void convert_proximity_entry(char *key, char *value, void *user_data) { char *src_addr = user_data; char *alert; char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; struct stat st; int err; char *data; gsize length = 0; if (!strchr(key, '#')) return; key[17] = '\0'; alert = &key[18]; if (bachk(key) != 0) return; /* Check if the device directory has been created as records should * only be converted for known devices */ create_filename(filename, PATH_MAX, "/%s/%s", src_addr, key); err = stat(filename, &st); if (err || !S_ISDIR(st.st_mode)) return; create_filename(filename, PATH_MAX, "/%s/%s/proximity", src_addr, key); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } g_key_file_set_string(key_file, alert, "Level", value); data = g_key_file_to_data(key_file, &length, NULL); if (length > 0) { create_file(filename, 0600); if (!g_file_set_contents(filename, data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } } g_free(data); g_key_file_free(key_file); } static void convert_device_storage(struct btd_adapter *adapter) { char filename[PATH_MAX]; char address[18]; ba2str(&adapter->bdaddr, address); /* Convert device's name cache */ create_filename(filename, PATH_MAX, "/%s/names", address); textfile_foreach(filename, convert_names_entry, address); /* Convert aliases */ convert_file("aliases", address, convert_aliases_entry, TRUE); /* Convert trusts */ convert_file("trusts", address, convert_trusts_entry, TRUE); /* Convert blocked */ convert_file("blocked", address, convert_blocked_entry, TRUE); /* Convert profiles */ convert_file("profiles", address, convert_profiles_entry, TRUE); /* Convert primaries */ create_filename(filename, PATH_MAX, "/%s/primaries", address); textfile_foreach(filename, convert_primaries_entry, address); /* Convert linkkeys */ convert_file("linkkeys", address, convert_linkkey_entry, TRUE); /* Convert longtermkeys */ convert_file("longtermkeys", address, convert_ltk_entry, TRUE); /* Convert classes */ convert_file("classes", address, convert_classes_entry, FALSE); /* Convert device ids */ convert_file("did", address, convert_did_entry, FALSE); /* Convert sdp */ create_filename(filename, PATH_MAX, "/%s/sdp", address); textfile_foreach(filename, convert_sdp_entry, address); /* Convert ccc */ create_filename(filename, PATH_MAX, "/%s/ccc", address); textfile_foreach(filename, convert_ccc_entry, address); /* Convert appearances */ convert_file("appearances", address, convert_appearances_entry, FALSE); /* Convert gatt */ create_filename(filename, PATH_MAX, "/%s/gatt", address); textfile_foreach(filename, convert_gatt_entry, address); /* Convert proximity */ create_filename(filename, PATH_MAX, "/%s/proximity", address); textfile_foreach(filename, convert_proximity_entry, address); } static void convert_config(struct btd_adapter *adapter, const char *filename, GKeyFile *key_file) { char address[18]; char str[MAX_NAME_LENGTH + 1]; char config_path[PATH_MAX]; int timeout; uint8_t mode; char *data; gsize length = 0; GError *gerr = NULL; ba2str(&adapter->bdaddr, address); create_filename(config_path, PATH_MAX, "/%s/config", address); if (read_pairable_timeout(address, &timeout) == 0) g_key_file_set_integer(key_file, "General", "PairableTimeout", timeout); if (read_discoverable_timeout(address, &timeout) == 0) g_key_file_set_integer(key_file, "General", "DiscoverableTimeout", timeout); if (read_on_mode(address, str, sizeof(str)) == 0) { mode = get_mode(str); g_key_file_set_boolean(key_file, "General", "Discoverable", mode == MODE_DISCOVERABLE); } if (read_local_name(&adapter->bdaddr, str) == 0) g_key_file_set_string(key_file, "General", "Alias", str); create_file(filename, 0600); data = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(data); } static void fix_storage(struct btd_adapter *adapter) { char filename[PATH_MAX]; char address[18]; char *converted; ba2str(&adapter->bdaddr, address); create_filename(filename, PATH_MAX, "/%s/config", address); converted = textfile_get(filename, "converted"); if (!converted) return; free(converted); textfile_del(filename, "converted"); create_filename(filename, PATH_MAX, "/%s/names", address); textfile_del(filename, "converted"); create_filename(filename, PATH_MAX, "/%s/aliases", address); textfile_del(filename, "converted"); create_filename(filename, PATH_MAX, "/%s/trusts", address); textfile_del(filename, "converted"); create_filename(filename, PATH_MAX, "/%s/blocked", address); textfile_del(filename, "converted"); create_filename(filename, PATH_MAX, "/%s/profiles", address); textfile_del(filename, "converted"); create_filename(filename, PATH_MAX, "/%s/primaries", address); textfile_del(filename, "converted"); create_filename(filename, PATH_MAX, "/%s/linkkeys", address); textfile_del(filename, "converted"); create_filename(filename, PATH_MAX, "/%s/longtermkeys", address); textfile_del(filename, "converted"); create_filename(filename, PATH_MAX, "/%s/classes", address); textfile_del(filename, "converted"); create_filename(filename, PATH_MAX, "/%s/did", address); textfile_del(filename, "converted"); create_filename(filename, PATH_MAX, "/%s/sdp", address); textfile_del(filename, "converted"); create_filename(filename, PATH_MAX, "/%s/ccc", address); textfile_del(filename, "converted"); create_filename(filename, PATH_MAX, "/%s/appearances", address); textfile_del(filename, "converted"); create_filename(filename, PATH_MAX, "/%s/gatt", address); textfile_del(filename, "converted"); create_filename(filename, PATH_MAX, "/%s/proximity", address); textfile_del(filename, "converted"); } static void load_config(struct btd_adapter *adapter) { GKeyFile *key_file; char filename[PATH_MAX]; struct stat st; GError *gerr = NULL; key_file = g_key_file_new(); create_filename(filename, PATH_MAX, "/%s/settings", btd_adapter_get_storage_dir(adapter)); if (stat(filename, &st) < 0) { convert_config(adapter, filename, key_file); convert_device_storage(adapter); } if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } /* Get alias */ adapter->stored_alias = g_key_file_get_string(key_file, "General", "Alias", NULL); if (!adapter->stored_alias) { /* fallback */ adapter->stored_alias = g_key_file_get_string(key_file, "General", "Name", NULL); } /* Get pairable timeout */ adapter->pairable_timeout = g_key_file_get_integer(key_file, "General", "PairableTimeout", &gerr); if (gerr) { adapter->pairable_timeout = btd_opts.pairto; g_error_free(gerr); gerr = NULL; } /* Get discoverable mode */ adapter->stored_discoverable = g_key_file_get_boolean(key_file, "General", "Discoverable", &gerr); if (gerr) { adapter->stored_discoverable = false; g_error_free(gerr); gerr = NULL; } /* Get discoverable timeout */ adapter->discoverable_timeout = g_key_file_get_integer(key_file, "General", "DiscoverableTimeout", &gerr); if (gerr) { adapter->discoverable_timeout = btd_opts.discovto; g_error_free(gerr); gerr = NULL; } g_key_file_free(key_file); } static struct btd_adapter *btd_adapter_new(uint16_t index) { struct btd_adapter *adapter; int blocked; adapter = g_try_new0(struct btd_adapter, 1); if (!adapter) return NULL; adapter->dev_id = index; adapter->mgmt = mgmt_ref(mgmt_primary); adapter->pincode_requested = false; blocked = rfkill_get_blocked(index); if (blocked > 0) adapter->power_state = ADAPTER_POWER_STATE_OFF_BLOCKED; /* * Setup default configuration values. These are either adapter * defaults or from a system wide configuration file. * * Some value might be overwritten later on by adapter specific * configuration. This is to make sure that sane defaults are * always present. */ adapter->system_name = g_strdup(btd_opts.name); adapter->major_class = (btd_opts.class & 0x001f00) >> 8; adapter->minor_class = (btd_opts.class & 0x0000fc) >> 2; adapter->modalias = bt_modalias(btd_opts.did_source, btd_opts.did_vendor, btd_opts.did_product, btd_opts.did_version); adapter->discoverable_timeout = btd_opts.discovto; adapter->pairable_timeout = btd_opts.pairto; DBG("System name: %s", adapter->system_name); DBG("Major class: %u", adapter->major_class); DBG("Minor class: %u", adapter->minor_class); DBG("Modalias: %s", adapter->modalias); DBG("Discoverable timeout: %u seconds", adapter->discoverable_timeout); DBG("Pairable timeout: %u seconds", adapter->pairable_timeout); if (blocked > 0) DBG("Power state: %s", adapter_power_state_str(adapter->power_state)); adapter->auths = g_queue_new(); adapter->exps = queue_new(); adapter->exp_pending = queue_new(); return btd_adapter_ref(adapter); } static void adapter_remove(struct btd_adapter *adapter) { GSList *l; struct gatt_db *db; DBG("Removing adapter %s", adapter->path); g_slist_free(adapter->connect_list); adapter->connect_list = NULL; for (l = adapter->devices; l; l = l->next) { device_removed_drivers(adapter, l->data); device_remove(l->data, FALSE); } g_slist_free(adapter->devices); adapter->devices = NULL; discovery_cleanup(adapter, 0); unload_drivers(adapter); db = btd_gatt_database_get_db(adapter->database); gatt_db_unregister(db, adapter->db_id); adapter->db_id = 0; btd_gatt_database_destroy(adapter->database); adapter->database = NULL; btd_adv_manager_destroy(adapter->adv_manager); adapter->adv_manager = NULL; btd_adv_monitor_manager_destroy(adapter->adv_monitor_manager); adapter->adv_monitor_manager = NULL; btd_battery_provider_manager_destroy(adapter->battery_provider_manager); adapter->battery_provider_manager = NULL; g_slist_free(adapter->pin_callbacks); adapter->pin_callbacks = NULL; g_slist_free(adapter->msd_callbacks); adapter->msd_callbacks = NULL; queue_remove_all(adapter->exp_pending, NULL, NULL, cancel_exp_pending); } const char *adapter_get_path(struct btd_adapter *adapter) { if (!adapter) return NULL; return adapter->path; } const bdaddr_t *btd_adapter_get_address(struct btd_adapter *adapter) { return &adapter->bdaddr; } static void confirm_name_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to confirm name for hci%u: %s (0x%02x)", adapter->dev_id, mgmt_errstr(status), status); return; } DBG("Confirm name complete for hci%u", adapter->dev_id); } static void confirm_name(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type, bool name_known) { struct mgmt_cp_confirm_name cp; char addr[18]; ba2str(bdaddr, addr); DBG("hci%d bdaddr %s name_known %u", adapter->dev_id, addr, name_known); memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); cp.addr.type = bdaddr_type; cp.name_known = name_known; /* * This timeout handling is needed since the kernel is stupid * and forgets to send a command complete response. However in * case of failures it does send a command status. */ if (!mgmt_reply_timeout(adapter->mgmt, MGMT_OP_CONFIRM_NAME, adapter->dev_id, sizeof(cp), &cp, confirm_name_complete, adapter, NULL, 2)) btd_error(adapter->dev_id, "Failed to confirm name for hci%u", adapter->dev_id); } static void adapter_msd_notify(struct btd_adapter *adapter, struct btd_device *dev, struct queue *msd_list) { GSList *cb_l, *cb_next; const struct queue_entry *msd_l, *msd_next; for (cb_l = adapter->msd_callbacks; cb_l != NULL; cb_l = cb_next) { btd_msd_cb_t cb = cb_l->data; cb_next = g_slist_next(cb_l); for (msd_l = queue_get_entries(msd_list); msd_l != NULL; msd_l = msd_next) { const struct eir_msd *msd = msd_l->data; msd_next = msd_l->next; cb(adapter, dev, msd->company, msd->data, msd->data_len); } } } static bool is_filter_match(GSList *discovery_filter, struct eir_data *eir_data, int8_t rssi) { GSList *l, *m; bool got_match = false; for (l = discovery_filter; l != NULL && got_match != true; l = g_slist_next(l)) { struct discovery_client *client = l->data; struct discovery_filter *item = client->discovery_filter; /* * If one of currently running scans is regular scan, then * return all devices as matches */ if (!item) { got_match = true; continue; } /* if someone started discovery with empty uuids, he wants all * devices in given proximity. */ if (!item->uuids) got_match = true; else { for (m = item->uuids; m != NULL && got_match != true; m = g_slist_next(m)) { /* m->data contains string representation of * uuid. */ if (queue_find(eir_data->services, m->data, g_strcmp) != NULL) got_match = true; } } if (got_match) { /* we have service match, check proximity */ if (item->rssi == DISTANCE_VAL_INVALID || item->rssi <= rssi || item->pathloss == DISTANCE_VAL_INVALID || (eir_data->tx_power != 127 && eir_data->tx_power - rssi <= item->pathloss)) return true; got_match = false; } } return got_match; } static void filter_duplicate_data(void *data, void *user_data) { struct discovery_client *client = data; bool *duplicate = user_data; if (*duplicate || !client->discovery_filter) return; *duplicate = client->discovery_filter->duplicate; } static bool device_is_discoverable(struct btd_adapter *adapter, struct eir_data *eir, const char *addr, uint8_t bdaddr_type, bool *auto_connect) { GSList *l; bool discoverable; if (bdaddr_type == BDADDR_BREDR || adapter->filtered_discovery) discoverable = true; else if (btd_opts.filter_discoverable) discoverable = eir->flags & (EIR_LIM_DISC | EIR_GEN_DISC); else discoverable = true; /* * Mark as not discoverable if no client has requested discovery and * report has not set any discoverable flags. */ if (!adapter->discovery_list && !discoverable) return false; /* Do a prefix match for both address and name if pattern is set */ for (l = adapter->discovery_list; l; l = g_slist_next(l)) { struct discovery_client *client = l->data; struct discovery_filter *filter = client->discovery_filter; size_t pattern_len; if (!filter || !filter->pattern) continue; /* Reset discoverable if a client has a pattern filter */ discoverable = false; pattern_len = strlen(filter->pattern); if (!pattern_len) { *auto_connect = filter->auto_connect; return true; } if (!strncasecmp(filter->pattern, addr, pattern_len)) { *auto_connect = filter->auto_connect; return true; } if (eir->name && !strncmp(filter->pattern, eir->name, pattern_len)) { *auto_connect = filter->auto_connect; return true; } } return discoverable; } void btd_adapter_device_found(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type, int8_t rssi, uint32_t flags, const uint8_t *data, uint8_t data_len, bool monitoring) { struct btd_device *dev; struct bt_ad *ad = NULL; struct eir_data eir_data; bool name_known, discoverable; char addr[18]; bool confirm; bool legacy; bool not_connectable; bool name_resolve_failed; bool scan_rsp; bool duplicate = false; bool auto_connect = false; struct queue *matched_monitors = NULL; confirm = (flags & MGMT_DEV_FOUND_CONFIRM_NAME); legacy = (flags & MGMT_DEV_FOUND_LEGACY_PAIRING); not_connectable = (flags & MGMT_DEV_FOUND_NOT_CONNECTABLE); name_resolve_failed = (flags & MGMT_DEV_FOUND_NAME_REQUEST_FAILED); scan_rsp = (flags & MGMT_DEV_FOUND_SCAN_RSP); if (!btd_adv_monitor_offload_enabled(adapter->adv_monitor_manager) || (MGMT_VERSION(mgmt_version, mgmt_revision) < MGMT_VERSION(1, 22))) { if (bdaddr_type != BDADDR_BREDR) ad = bt_ad_new_with_data(data_len, data); /* During the background scanning, update the device only when * the data match at least one Adv monitor */ if (ad) { matched_monitors = btd_adv_monitor_content_filter( adapter->adv_monitor_manager, ad); bt_ad_unref(ad); ad = NULL; monitoring = matched_monitors ? true : false; } } if (!adapter->discovering && !monitoring) return; memset(&eir_data, 0, sizeof(eir_data)); eir_parse(&eir_data, data, data_len); ba2str(bdaddr, addr); discoverable = device_is_discoverable(adapter, &eir_data, addr, bdaddr_type, &auto_connect); /* Monitor Devices advertising Broadcast Announcements if the * adapter is capable of synchronizing to it. */ if (eir_get_service_data(&eir_data, BCAA_SERVICE_UUID) && btd_adapter_has_settings(adapter, MGMT_SETTING_ISO_SYNC_RECEIVER)) monitoring = true; dev = btd_adapter_find_device(adapter, bdaddr, bdaddr_type); if (!dev) { /* In case of being just a scan response don't attempt to create * the device. */ if (scan_rsp) { eir_data_free(&eir_data); return; } /* If ISO Socket is enabled, monitor Devices advertising RSI * since those can be coordinated sets not marked as visible but * their object are needed. */ if (btd_adapter_has_exp_feature(adapter, EXP_FEAT_ISO_SOCKET) && eir_data.rsi) monitoring = true; if (!discoverable && !monitoring) { eir_data_free(&eir_data); return; } dev = adapter_create_device(adapter, bdaddr, bdaddr_type); } if (!dev) { btd_error(adapter->dev_id, "Unable to create object for found device %s", addr); eir_data_free(&eir_data); return; } device_update_last_seen(dev, bdaddr_type, !not_connectable); /* * FIXME: We need to check for non-zero flags first because * older kernels send separate adv_ind and scan_rsp. Newer * kernels send them merged, so once we know which mgmt version * supports this we can make the non-zero check conditional. */ if (bdaddr_type != BDADDR_BREDR && eir_data.flags && !(eir_data.flags & EIR_BREDR_UNSUP)) { device_set_bredr_support(dev); /* Update last seen for BR/EDR in case its flag is set */ device_update_last_seen(dev, BDADDR_BREDR, !not_connectable); } if (eir_data.name != NULL && eir_data.name_complete) device_store_cached_name(dev, eir_data.name); /* * Only skip devices that are not connected, are temporary, and there * is no active discovery session ongoing and no matched Adv monitors */ if (!btd_device_is_connected(dev) && (device_is_temporary(dev) && !adapter->discovery_list) && !monitoring) { eir_data_free(&eir_data); return; } /* If there is no matched Adv monitors, don't continue if not * discoverable or if active discovery filter don't match. */ if (!eir_data.rsi && !monitoring && (!discoverable || (adapter->filtered_discovery && !is_filter_match( adapter->discovery_list, &eir_data, rssi)))) { eir_data_free(&eir_data); return; } device_set_legacy(dev, legacy); if (name_resolve_failed) device_name_resolve_fail(dev); if (adapter->filtered_discovery) device_set_rssi_with_delta(dev, rssi, 0); else device_set_rssi(dev, rssi); if (eir_data.tx_power != 127) device_set_tx_power(dev, eir_data.tx_power); if (eir_data.appearance != 0) device_set_appearance(dev, eir_data.appearance); /* Report an unknown name to the kernel even if there is a short name * known, but still update the name with the known short name. */ name_known = device_name_known(dev); if (eir_data.name && (eir_data.name_complete || !name_known)) btd_device_device_set_name(dev, eir_data.name); if (eir_data.class != 0) device_set_class(dev, eir_data.class); if (eir_data.did_source || eir_data.did_vendor || eir_data.did_product || eir_data.did_version) btd_device_set_pnpid(dev, eir_data.did_source, eir_data.did_vendor, eir_data.did_product, eir_data.did_version); device_add_eir_uuids(dev, eir_data.services); if (adapter->discovery_list) g_slist_foreach(adapter->discovery_list, filter_duplicate_data, &duplicate); if (eir_data.msd_list) { device_set_manufacturer_data(dev, eir_data.msd_list, duplicate); adapter_msd_notify(adapter, dev, eir_data.msd_list); } if (eir_data.sd_list) device_set_service_data(dev, eir_data.sd_list, duplicate); if (eir_data.data_list) device_set_data(dev, eir_data.data_list, duplicate); if (bdaddr_type != BDADDR_BREDR) device_set_flags(dev, eir_data.flags); eir_data_free(&eir_data); /* After the device is updated, notify the matched Adv monitors */ if (matched_monitors) { btd_adv_monitor_notify_monitors(adapter->adv_monitor_manager, dev, rssi, matched_monitors); queue_destroy(matched_monitors, NULL); matched_monitors = NULL; } /* * Only if at least one client has requested discovery, maintain * list of found devices and name confirming for legacy devices. * Otherwise, this is an event from passive discovery and we * should check if the device needs connecting to. */ if (!adapter->discovery_list) goto connect_le; if (g_slist_find(adapter->discovery_found, dev)) return; /* If name is unknown but it's not allowed to resolve, don't send * MGMT_OP_CONFIRM_NAME. */ if (confirm && (name_known || device_is_name_resolve_allowed(dev))) confirm_name(adapter, bdaddr, bdaddr_type, name_known); adapter->discovery_found = g_slist_prepend(adapter->discovery_found, dev); /* If device has a pattern match and it also set auto-connect then * attempt to connect. */ if (!btd_device_is_connected(dev) && auto_connect) btd_device_connect_services(dev, NULL); return; connect_le: /* Ignore non-connectable events */ if (not_connectable) return; /* * If we're in the process of stopping passive scanning and * connecting another (or maybe even the same) LE device just * ignore this one. */ if (adapter->connect_le) return; /* * If kernel background scan is used then the kernel is * responsible for connecting. */ if (btd_has_kernel_features(KERNEL_CONN_CONTROL)) return; /* * If this is an LE device that's not connected and part of the * connect_list stop passive scanning so that a connection * attempt to it can be made */ if (bdaddr_type != BDADDR_BREDR && !btd_device_is_connected(dev) && g_slist_find(adapter->connect_list, dev)) { adapter->connect_le = dev; stop_passive_scanning(adapter); } } static void device_found_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_device_found *ev = param; struct btd_adapter *adapter = user_data; const uint8_t *eir; uint16_t eir_len; uint32_t flags; char addr[18]; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too short device found event (%u bytes)", length); return; } eir_len = btohs(ev->eir_len); if (length != sizeof(*ev) + eir_len) { btd_error(adapter->dev_id, "Device found event size mismatch (%u != %zu)", length, sizeof(*ev) + eir_len); return; } if (eir_len == 0) eir = NULL; else eir = ev->eir; flags = le32_to_cpu(ev->flags); ba2str(&ev->addr.bdaddr, addr); DBG("hci%u addr %s, rssi %d flags 0x%04x eir_len %u", index, addr, ev->rssi, flags, eir_len); btd_adapter_device_found(adapter, &ev->addr.bdaddr, ev->addr.type, ev->rssi, flags, eir, eir_len, false); } struct agent *adapter_get_agent(struct btd_adapter *adapter) { return agent_get(NULL); } static void adapter_remove_connection(struct btd_adapter *adapter, struct btd_device *device, uint8_t bdaddr_type, uint8_t reason) { bool remove_device = false; DBG(""); if (!g_slist_find(adapter->connections, device)) { btd_error(adapter->dev_id, "No matching connection for device"); return; } device_remove_connection(device, bdaddr_type, &remove_device, reason); device_cancel_authentication(device, TRUE); /* If another bearer is still connected */ if (btd_device_bearer_is_connected(device)) return; adapter->connections = g_slist_remove(adapter->connections, device); if (remove_device) { const char *path = device_get_path(device); DBG("Removing temporary device %s", path); btd_adapter_remove_device(adapter, device); } } static void adapter_stop(struct btd_adapter *adapter) { /* check pending requests */ reply_pending_requests(adapter); cancel_passive_scanning(adapter); remove_discovery_list(adapter); discovery_cleanup(adapter, 0); adapter->filtered_discovery = false; adapter->no_scan_restart_delay = false; g_free(adapter->current_discovery_filter); adapter->current_discovery_filter = NULL; set_discovery_discoverable(adapter, false); adapter->discovering = false; while (adapter->connections) { struct btd_device *device = adapter->connections->data; uint8_t addr_type = btd_device_get_bdaddr_type(device); adapter_remove_connection(adapter, device, BDADDR_BREDR, MGMT_DEV_DISCONN_UNKNOWN); if (addr_type != BDADDR_BREDR) adapter_remove_connection(adapter, device, addr_type, MGMT_DEV_DISCONN_UNKNOWN); } g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Discovering"); if (adapter->dev_class) { /* the kernel should reset the class of device when powering * down, but it does not. So force it here ... */ adapter->dev_class = 0; g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Class"); } g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "Powered"); g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "PowerState"); DBG("adapter %s has been disabled", adapter->path); } int btd_register_adapter_driver(const struct btd_adapter_driver *driver) { if (driver->experimental && !(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) { DBG("D-Bus experimental not enabled"); return -ENOTSUP; } adapter_drivers = g_slist_append(adapter_drivers, (void *) driver); if (driver->probe == NULL) return 0; btd_adapter_foreach(probe_driver, (void *) driver); return 0; } static void unload_driver(struct btd_adapter *adapter, gpointer data) { struct btd_adapter_driver *driver = data; if (driver->remove) driver->remove(adapter); adapter->drivers = g_slist_remove(adapter->drivers, data); } void btd_unregister_adapter_driver(const struct btd_adapter_driver *driver) { adapter_drivers = g_slist_remove(adapter_drivers, (void *) driver); btd_adapter_foreach(unload_driver, (void *) driver); } static void agent_auth_cb(struct agent *agent, DBusError *derr, void *user_data) { struct btd_adapter *adapter = user_data; struct service_auth *auth = g_queue_pop_head(adapter->auths); if (!auth) { DBG("No pending authorization"); return; } auth->cb(derr, auth->user_data); if (auth->agent) agent_unref(auth->agent); g_free(auth); /* Stop processing if queue is empty */ if (g_queue_is_empty(adapter->auths)) { if (adapter->auth_idle_id > 0) g_source_remove(adapter->auth_idle_id); return; } if (adapter->auth_idle_id > 0) return; adapter->auth_idle_id = g_idle_add(process_auth_queue, adapter); } static gboolean process_auth_queue(gpointer user_data) { struct btd_adapter *adapter = user_data; DBusError err; adapter->auth_idle_id = 0; dbus_error_init(&err); dbus_set_error_const(&err, ERROR_INTERFACE ".Rejected", NULL); while (!g_queue_is_empty(adapter->auths)) { struct service_auth *auth = adapter->auths->head->data; struct btd_device *device = auth->device; /* Wait services to be resolved before asking authorization */ if (auth->svc_id > 0) return FALSE; if (!btd_adapter_is_uuid_allowed(adapter, auth->uuid)) { auth->cb(&err, auth->user_data); goto next; } if (btd_device_is_trusted(device) == TRUE) { auth->cb(NULL, auth->user_data); goto next; } /* If agent is set authorization is already ongoing */ if (auth->agent) return FALSE; auth->agent = agent_get(NULL); if (auth->agent == NULL) { btd_warn(adapter->dev_id, "Authentication attempt without agent"); auth->cb(&err, auth->user_data); goto next; } if (agent_authorize_service(auth->agent, device, auth->uuid, agent_auth_cb, adapter, NULL) < 0) { auth->cb(&err, auth->user_data); goto next; } break; next: if (auth->agent) agent_unref(auth->agent); g_free(auth); g_queue_pop_head(adapter->auths); } dbus_error_free(&err); return FALSE; } static void svc_complete(struct btd_device *dev, int err, void *user_data) { struct service_auth *auth = user_data; struct btd_adapter *adapter = auth->adapter; auth->svc_id = 0; if (adapter->auth_idle_id != 0) return; adapter->auth_idle_id = g_idle_add(process_auth_queue, adapter); } static int adapter_authorize(struct btd_adapter *adapter, const bdaddr_t *dst, const char *uuid, adapter_authorize_type check_for_connection, service_auth_cb cb, void *user_data) { struct service_auth *auth; struct btd_device *device; static guint id = 0; device = btd_adapter_find_device(adapter, dst, BDADDR_BREDR); if (!device) return 0; if (device_is_disconnecting(device)) { DBG("Authorization request while disconnecting"); return 0; } /* Device connected? */ if (check_for_connection && !g_slist_find(adapter->connections, device)) btd_error(adapter->dev_id, "Authorization request for non-connected device!?"); auth = g_try_new0(struct service_auth, 1); if (!auth) return 0; auth->cb = cb; auth->user_data = user_data; auth->uuid = uuid; auth->device = device; auth->adapter = adapter; auth->id = ++id; if (check_for_connection) auth->svc_id = device_wait_for_svc_complete(device, svc_complete, auth); else { if (adapter->auth_idle_id == 0) adapter->auth_idle_id = g_idle_add(process_auth_queue, adapter); } g_queue_push_tail(adapter->auths, auth); return auth->id; } guint btd_request_authorization(const bdaddr_t *src, const bdaddr_t *dst, const char *uuid, service_auth_cb cb, void *user_data) { struct btd_adapter *adapter; GSList *l; if (bacmp(src, BDADDR_ANY) != 0) { adapter = adapter_find(src); if (!adapter) return 0; return adapter_authorize(adapter, dst, uuid, ADAPTER_AUTHORIZE_CHECK_CONNECTED, cb, user_data); } for (l = adapters; l != NULL; l = g_slist_next(l)) { guint id; adapter = l->data; id = adapter_authorize(adapter, dst, uuid, ADAPTER_AUTHORIZE_CHECK_CONNECTED, cb, user_data); if (id != 0) return id; } return 0; } guint btd_request_authorization_cable_configured(const bdaddr_t *src, const bdaddr_t *dst, const char *uuid, service_auth_cb cb, void *user_data) { struct btd_adapter *adapter; if (bacmp(src, BDADDR_ANY) == 0) return 0; adapter = adapter_find(src); if (!adapter) return 0; return adapter_authorize(adapter, dst, uuid, ADAPTER_AUTHORIZE_DISCONNECTED, cb, user_data); } static struct service_auth *find_authorization(guint id) { GSList *l; GList *l2; for (l = adapters; l != NULL; l = g_slist_next(l)) { struct btd_adapter *adapter = l->data; for (l2 = adapter->auths->head; l2 != NULL; l2 = l2->next) { struct service_auth *auth = l2->data; if (auth->id == id) return auth; } } return NULL; } int btd_cancel_authorization(guint id) { struct service_auth *auth; auth = find_authorization(id); if (auth == NULL) return -EPERM; if (auth->svc_id > 0) device_remove_svc_complete_callback(auth->device, auth->svc_id); g_queue_remove(auth->adapter->auths, auth); if (auth->agent) { agent_cancel(auth->agent); agent_unref(auth->agent); } g_free(auth); return 0; } int btd_adapter_restore_powered(struct btd_adapter *adapter) { bool powered; powered = btd_adapter_get_powered(adapter); if (adapter->power_state == ADAPTER_POWER_STATE_OFF_BLOCKED && rfkill_get_blocked(adapter->dev_id) == 0) { adapter_set_power_state(adapter, powered ? ADAPTER_POWER_STATE_ON : ADAPTER_POWER_STATE_OFF); } if (powered) return 0; set_mode(adapter, MGMT_OP_SET_POWERED, 0x01); return 0; } int btd_adapter_set_blocked(struct btd_adapter *adapter) { adapter_set_power_state(adapter, ADAPTER_POWER_STATE_OFF_BLOCKED); return 0; } void btd_adapter_register_pin_cb(struct btd_adapter *adapter, btd_adapter_pin_cb_t cb) { adapter->pin_callbacks = g_slist_prepend(adapter->pin_callbacks, cb); } void btd_adapter_unregister_pin_cb(struct btd_adapter *adapter, btd_adapter_pin_cb_t cb) { adapter->pin_callbacks = g_slist_remove(adapter->pin_callbacks, cb); } void btd_adapter_unregister_msd_cb(struct btd_adapter *adapter, btd_msd_cb_t cb) { adapter->msd_callbacks = g_slist_remove(adapter->msd_callbacks, cb); } void btd_adapter_register_msd_cb(struct btd_adapter *adapter, btd_msd_cb_t cb) { adapter->msd_callbacks = g_slist_prepend(adapter->msd_callbacks, cb); } int btd_adapter_set_fast_connectable(struct btd_adapter *adapter, gboolean enable) { if (!btd_adapter_get_powered(adapter)) return -EINVAL; set_mode(adapter, MGMT_OP_SET_FAST_CONNECTABLE, enable ? 0x01 : 0x00); return 0; } int btd_adapter_read_clock(struct btd_adapter *adapter, const bdaddr_t *bdaddr, int which, int timeout, uint32_t *clock, uint16_t *accuracy) { if (!btd_adapter_get_powered(adapter)) return -EINVAL; return -ENOSYS; } int btd_adapter_remove_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type) { struct mgmt_cp_unpair_device cp; memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); cp.addr.type = bdaddr_type; cp.disconnect = 1; if (mgmt_send(adapter->mgmt, MGMT_OP_UNPAIR_DEVICE, adapter->dev_id, sizeof(cp), &cp, NULL, NULL, NULL) > 0) return 0; return -EIO; } static void pincode_reply_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_device *device = user_data; /* If the MGMT_OP_PIN_CODE_REPLY command is acknowledged, move the * starting time to that point. This give a better sense of time * evaluating the pincode. */ device_bonding_restart_timer(device); } int btd_adapter_pincode_reply(struct btd_adapter *adapter, const bdaddr_t *bdaddr, const char *pin, size_t pin_len) { struct btd_device *device; unsigned int id; char addr[18]; ba2str(bdaddr, addr); DBG("hci%u addr %s pinlen %zu", adapter->dev_id, addr, pin_len); if (pin == NULL) { struct mgmt_cp_pin_code_neg_reply cp; memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); cp.addr.type = BDADDR_BREDR; id = mgmt_reply(adapter->mgmt, MGMT_OP_PIN_CODE_NEG_REPLY, adapter->dev_id, sizeof(cp), &cp, NULL, NULL, NULL); } else { struct mgmt_cp_pin_code_reply cp; if (pin_len > 16) return -EINVAL; memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); cp.addr.type = BDADDR_BREDR; cp.pin_len = pin_len; memcpy(cp.pin_code, pin, pin_len); /* Since a pincode was requested, update the starting time to * the point where the pincode is provided. */ device = btd_adapter_find_device(adapter, bdaddr, BDADDR_BREDR); device_bonding_restart_timer(device); id = mgmt_reply(adapter->mgmt, MGMT_OP_PIN_CODE_REPLY, adapter->dev_id, sizeof(cp), &cp, pincode_reply_complete, device, NULL); } if (id == 0) return -EIO; return 0; } int btd_adapter_confirm_reply(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type, gboolean success) { struct mgmt_cp_user_confirm_reply cp; uint16_t opcode; char addr[18]; ba2str(bdaddr, addr); DBG("hci%u addr %s success %d", adapter->dev_id, addr, success); if (success) opcode = MGMT_OP_USER_CONFIRM_REPLY; else opcode = MGMT_OP_USER_CONFIRM_NEG_REPLY; memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); cp.addr.type = bdaddr_type; if (mgmt_reply(adapter->mgmt, opcode, adapter->dev_id, sizeof(cp), &cp, NULL, NULL, NULL) > 0) return 0; return -EIO; } static void user_confirm_request_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_user_confirm_request *ev = param; struct btd_adapter *adapter = user_data; struct btd_device *device; char addr[18]; int err; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small user confirm request event"); return; } ba2str(&ev->addr.bdaddr, addr); DBG("hci%u %s confirm_hint %u", adapter->dev_id, addr, ev->confirm_hint); device = btd_adapter_get_device(adapter, &ev->addr.bdaddr, ev->addr.type); if (!device) { btd_error(adapter->dev_id, "Unable to get device object for %s", addr); return; } err = device_confirm_passkey(device, ev->addr.type, btohl(ev->value), ev->confirm_hint); if (err < 0) { btd_error(adapter->dev_id, "device_confirm_passkey: %s", strerror(-err)); btd_adapter_confirm_reply(adapter, &ev->addr.bdaddr, ev->addr.type, FALSE); } } int btd_adapter_passkey_reply(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type, uint32_t passkey) { unsigned int id; char addr[18]; ba2str(bdaddr, addr); DBG("hci%u addr %s passkey %06u", adapter->dev_id, addr, passkey); if (passkey == INVALID_PASSKEY) { struct mgmt_cp_user_passkey_neg_reply cp; memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); cp.addr.type = bdaddr_type; id = mgmt_reply(adapter->mgmt, MGMT_OP_USER_PASSKEY_NEG_REPLY, adapter->dev_id, sizeof(cp), &cp, NULL, NULL, NULL); } else { struct mgmt_cp_user_passkey_reply cp; memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); cp.addr.type = bdaddr_type; cp.passkey = htobl(passkey); id = mgmt_reply(adapter->mgmt, MGMT_OP_USER_PASSKEY_REPLY, adapter->dev_id, sizeof(cp), &cp, NULL, NULL, NULL); } if (id == 0) return -EIO; return 0; } static void user_passkey_request_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_user_passkey_request *ev = param; struct btd_adapter *adapter = user_data; struct btd_device *device; char addr[18]; int err; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small passkey request event"); return; } ba2str(&ev->addr.bdaddr, addr); DBG("hci%u %s", index, addr); device = btd_adapter_get_device(adapter, &ev->addr.bdaddr, ev->addr.type); if (!device) { btd_error(adapter->dev_id, "Unable to get device object for %s", addr); return; } err = device_request_passkey(device, ev->addr.type); if (err < 0) { btd_error(adapter->dev_id, "device_request_passkey: %s", strerror(-err)); btd_adapter_passkey_reply(adapter, &ev->addr.bdaddr, ev->addr.type, INVALID_PASSKEY); } } static void user_passkey_notify_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_passkey_notify *ev = param; struct btd_adapter *adapter = user_data; struct btd_device *device; uint32_t passkey; char addr[18]; int err; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small passkey notify event"); return; } ba2str(&ev->addr.bdaddr, addr); DBG("hci%u %s", index, addr); device = btd_adapter_get_device(adapter, &ev->addr.bdaddr, ev->addr.type); if (!device) { btd_error(adapter->dev_id, "Unable to get device object for %s", addr); return; } passkey = get_le32(&ev->passkey); DBG("passkey %06u entered %u", passkey, ev->entered); err = device_notify_passkey(device, ev->addr.type, passkey, ev->entered); if (err < 0) btd_error(adapter->dev_id, "device_notify_passkey: %s", strerror(-err)); } struct btd_adapter_pin_cb_iter *btd_adapter_pin_cb_iter_new( struct btd_adapter *adapter) { struct btd_adapter_pin_cb_iter *iter = g_new0(struct btd_adapter_pin_cb_iter, 1); iter->it = adapter->pin_callbacks; iter->attempt = 1; return iter; } void btd_adapter_pin_cb_iter_free(struct btd_adapter_pin_cb_iter *iter) { g_free(iter); } bool btd_adapter_pin_cb_iter_end(struct btd_adapter_pin_cb_iter *iter) { return iter->it == NULL && iter->attempt == 0; } static ssize_t btd_adapter_pin_cb_iter_next( struct btd_adapter_pin_cb_iter *iter, struct btd_adapter *adapter, struct btd_device *device, char *pin_buf, bool *display) { btd_adapter_pin_cb_t cb; ssize_t ret; while (iter->it != NULL) { cb = iter->it->data; ret = cb(adapter, device, pin_buf, display, iter->attempt); iter->attempt++; if (ret > 0) return ret; iter->attempt = 1; iter->it = g_slist_next(iter->it); } iter->attempt = 0; return 0; } static void pin_code_request_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_pin_code_request *ev = param; struct btd_adapter *adapter = user_data; struct btd_device *device; bool display = false; char pin[17]; ssize_t pinlen; char addr[18]; int err; struct btd_adapter_pin_cb_iter *iter; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small PIN code request event"); return; } ba2str(&ev->addr.bdaddr, addr); DBG("hci%u %s", adapter->dev_id, addr); device = btd_adapter_get_device(adapter, &ev->addr.bdaddr, ev->addr.type); if (!device) { btd_error(adapter->dev_id, "Unable to get device object for %s", addr); return; } /* Flag the request of a pincode to allow a bonding retry. */ adapter->pincode_requested = true; memset(pin, 0, sizeof(pin)); iter = device_bonding_iter(device); if (iter == NULL) pinlen = 0; else pinlen = btd_adapter_pin_cb_iter_next(iter, adapter, device, pin, &display); if (pinlen > 0 && (!ev->secure || pinlen == 16)) { if (display && device_is_bonding(device, NULL)) { err = device_notify_pincode(device, ev->secure, pin); if (err < 0) { btd_error(adapter->dev_id, "device_notify_pin: %s", strerror(-err)); btd_adapter_pincode_reply(adapter, &ev->addr.bdaddr, NULL, 0); } } else { btd_adapter_pincode_reply(adapter, &ev->addr.bdaddr, pin, pinlen); } return; } err = device_request_pincode(device, ev->secure); if (err < 0) { btd_error(adapter->dev_id, "device_request_pin: %s", strerror(-err)); btd_adapter_pincode_reply(adapter, &ev->addr.bdaddr, NULL, 0); } } int adapter_cancel_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t addr_type) { struct mgmt_addr_info cp; char addr[18]; ba2str(bdaddr, addr); DBG("hci%u bdaddr %s type %u", adapter->dev_id, addr, addr_type); memset(&cp, 0, sizeof(cp)); bacpy(&cp.bdaddr, bdaddr); cp.type = addr_type; if (mgmt_reply(adapter->mgmt, MGMT_OP_CANCEL_PAIR_DEVICE, adapter->dev_id, sizeof(cp), &cp, NULL, NULL, NULL) > 0) return 0; return -EIO; } static void check_oob_bonding_complete(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t status) { if (!adapter->oob_handler || !adapter->oob_handler->bonding_cb) return; if (bacmp(bdaddr, &adapter->oob_handler->remote_addr) != 0) return; adapter->oob_handler->bonding_cb(adapter, bdaddr, status, adapter->oob_handler->user_data); g_free(adapter->oob_handler); adapter->oob_handler = NULL; } static void bonding_complete(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t addr_type, uint8_t status) { struct btd_device *device; if (status == 0) device = btd_adapter_get_device(adapter, bdaddr, addr_type); else device = btd_adapter_find_device(adapter, bdaddr, addr_type); if (device != NULL) device_bonding_complete(device, addr_type, status); resume_discovery(adapter); check_oob_bonding_complete(adapter, bdaddr, status); } /* bonding_attempt_complete() handles the end of a "bonding attempt" checking if * it should begin a new attempt or complete the bonding. */ static void bonding_attempt_complete(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t addr_type, uint8_t status) { struct btd_device *device; char addr[18]; ba2str(bdaddr, addr); DBG("hci%u bdaddr %s type %u status 0x%x", adapter->dev_id, addr, addr_type, status); if (status == 0) device = btd_adapter_get_device(adapter, bdaddr, addr_type); else device = btd_adapter_find_device(adapter, bdaddr, addr_type); if (status == MGMT_STATUS_AUTH_FAILED && adapter->pincode_requested) { /* On failure, issue a bonding_retry if possible. */ if (device != NULL) { if (device_bonding_attempt_retry(device) == 0) return; } } /* Ignore disconnects during retry. */ if (status == MGMT_STATUS_DISCONNECTED && device && device_is_retrying(device)) return; /* In any other case, finish the bonding. */ bonding_complete(adapter, bdaddr, addr_type, status); } struct pair_device_data { struct btd_adapter *adapter; bdaddr_t bdaddr; uint8_t addr_type; }; static void free_pair_device_data(void *user_data) { struct pair_device_data *data = user_data; g_free(data); } static void pair_device_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_pair_device *rp = param; struct pair_device_data *data = user_data; struct btd_adapter *adapter = data->adapter; DBG("%s (0x%02x)", mgmt_errstr(status), status); /* Workaround for a kernel bug * * Broken kernels may reply to device pairing command with command * status instead of command complete event e.g. if adapter was not * powered. */ if (status != MGMT_STATUS_SUCCESS && length < sizeof(*rp)) { btd_error(adapter->dev_id, "Pair device failed: %s (0x%02x)", mgmt_errstr(status), status); bonding_attempt_complete(adapter, &data->bdaddr, data->addr_type, status); return; } if (length < sizeof(*rp)) { btd_error(adapter->dev_id, "Too small pair device response"); return; } bonding_attempt_complete(adapter, &rp->addr.bdaddr, rp->addr.type, status); } int adapter_create_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t addr_type, enum mgmt_io_capability io_cap) { suspend_discovery(adapter); return adapter_bonding_attempt(adapter, bdaddr, addr_type, io_cap); } /* Starts a new bonding attempt in a fresh new bonding_req or a retried one. */ int adapter_bonding_attempt(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t addr_type, enum mgmt_io_capability io_cap) { struct mgmt_cp_pair_device cp; char addr[18]; struct pair_device_data *data; unsigned int id; ba2str(bdaddr, addr); DBG("hci%u bdaddr %s type %d io_cap 0x%02x", adapter->dev_id, addr, addr_type, io_cap); /* Reset the pincode_requested flag for a new bonding attempt. */ adapter->pincode_requested = false; memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); cp.addr.type = addr_type; cp.io_cap = io_cap; data = g_new0(struct pair_device_data, 1); data->adapter = adapter; bacpy(&data->bdaddr, bdaddr); data->addr_type = addr_type; /* Due to a bug in the kernel it is possible that a LE pairing * request never times out. Therefore, add a timer to clean up * if no response arrives */ id = mgmt_send_timeout(adapter->mgmt, MGMT_OP_PAIR_DEVICE, adapter->dev_id, sizeof(cp), &cp, pair_device_complete, data, free_pair_device_data, BONDING_TIMEOUT); if (id == 0) { btd_error(adapter->dev_id, "Failed to pair %s for hci%u", addr, adapter->dev_id); free_pair_device_data(data); return -EIO; } return 0; } static void disconnect_notify(struct btd_device *dev, uint8_t reason) { GSList *l; for (l = disconnect_list; l; l = g_slist_next(l)) { btd_disconnect_cb disconnect_cb = l->data; disconnect_cb(dev, reason); } } static void dev_disconnected(struct btd_adapter *adapter, const struct mgmt_addr_info *addr, uint8_t reason) { struct btd_device *device; char dst[18]; ba2str(&addr->bdaddr, dst); DBG("Device %s disconnected, reason %u", dst, reason); device = btd_adapter_find_device(adapter, &addr->bdaddr, addr->type); if (device) { adapter_remove_connection(adapter, device, addr->type, reason); disconnect_notify(device, reason); } /* device could be still connected to different bearer */ if (!device || !btd_device_is_connected(device)) bonding_attempt_complete(adapter, &addr->bdaddr, addr->type, MGMT_STATUS_DISCONNECTED); } void btd_add_disconnect_cb(btd_disconnect_cb func) { disconnect_list = g_slist_append(disconnect_list, func); } void btd_remove_disconnect_cb(btd_disconnect_cb func) { disconnect_list = g_slist_remove(disconnect_list, func); } static void disconnect_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_disconnect *rp = param; struct btd_adapter *adapter = user_data; if (status == MGMT_STATUS_NOT_CONNECTED || status == MGMT_STATUS_DISCONNECTED) { btd_warn(adapter->dev_id, "Disconnecting failed: already disconnected"); } else if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to disconnect device: %s (0x%02x)", mgmt_errstr(status), status); return; } if (length < sizeof(*rp)) { btd_error(adapter->dev_id, "Too small device disconnect response"); return; } dev_disconnected(adapter, &rp->addr, MGMT_DEV_DISCONN_LOCAL_HOST); } int btd_adapter_disconnect_device(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type) { struct mgmt_cp_disconnect cp; memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); cp.addr.type = bdaddr_type; if (mgmt_send(adapter->mgmt, MGMT_OP_DISCONNECT, adapter->dev_id, sizeof(cp), &cp, disconnect_complete, adapter, NULL) > 0) return 0; return -EIO; } static void auth_failed_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_auth_failed *ev = param; struct btd_adapter *adapter = user_data; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small auth failed mgmt event"); return; } bonding_attempt_complete(adapter, &ev->addr.bdaddr, ev->addr.type, ev->status); } static void store_link_key(struct btd_adapter *adapter, struct btd_device *device, const uint8_t *key, uint8_t type, uint8_t pin_length) { char device_addr[18]; char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; gsize length = 0; char key_str[33]; char *str; int i; ba2str(device_get_address(device), device_addr); create_filename(filename, PATH_MAX, "/%s/%s/info", btd_adapter_get_storage_dir(adapter), device_addr); create_file(filename, 0600); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_error_free(gerr); g_key_file_free(key_file); return; } for (i = 0; i < 16; i++) sprintf(key_str + (i * 2), "%2.2X", key[i]); g_key_file_set_string(key_file, "LinkKey", "Key", key_str); g_key_file_set_integer(key_file, "LinkKey", "Type", type); g_key_file_set_integer(key_file, "LinkKey", "PINLength", pin_length); str = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, str, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(str); g_key_file_free(key_file); } static void new_link_key_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_new_link_key *ev = param; const struct mgmt_addr_info *addr = &ev->key.addr; struct btd_adapter *adapter = user_data; struct btd_device *device; char dst[18]; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small new link key event"); return; } ba2str(&addr->bdaddr, dst); DBG("hci%u new key for %s type %u pin_len %u store_hint %u", adapter->dev_id, dst, ev->key.type, ev->key.pin_len, ev->store_hint); if (ev->key.pin_len > 16) { btd_error(adapter->dev_id, "Invalid PIN length (%u) in new_key event", ev->key.pin_len); return; } device = btd_adapter_get_device(adapter, &addr->bdaddr, addr->type); if (!device) { btd_error(adapter->dev_id, "Unable to get device object for %s", dst); return; } if (ev->store_hint) { const struct mgmt_link_key_info *key = &ev->key; store_link_key(adapter, device, key->val, key->type, key->pin_len); device_set_bonded(device, BDADDR_BREDR); } bonding_complete(adapter, &addr->bdaddr, addr->type, 0); } static void store_ltk_group(struct btd_adapter *adapter, const bdaddr_t *peer, uint8_t bdaddr_type, const unsigned char *key, const char *group, uint8_t authenticated, uint8_t enc_size, uint16_t ediv, uint64_t rand) { char device_addr[18]; char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; char key_str[33]; gsize length = 0; char *str; int i; ba2str(peer, device_addr); create_filename(filename, PATH_MAX, "/%s/%s/info", btd_adapter_get_storage_dir(adapter), device_addr); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } for (i = 0; i < 16; i++) sprintf(key_str + (i * 2), "%2.2X", key[i]); g_key_file_set_string(key_file, group, "Key", key_str); g_key_file_set_integer(key_file, group, "Authenticated", authenticated); g_key_file_set_integer(key_file, group, "EncSize", enc_size); g_key_file_set_integer(key_file, group, "EDiv", ediv); g_key_file_set_uint64(key_file, group, "Rand", rand); create_file(filename, 0600); str = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, str, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(str); g_key_file_free(key_file); } static void store_longtermkey(struct btd_adapter *adapter, const bdaddr_t *peer, uint8_t bdaddr_type, const unsigned char *key, uint8_t central, uint8_t authenticated, uint8_t enc_size, uint16_t ediv, uint64_t rand) { if (central != 0x00 && central != 0x01) { error("Unsupported LTK type %u", central); return; } if (central) { store_ltk_group(adapter, peer, bdaddr_type, key, "LongTermKey", authenticated, enc_size, ediv, rand); } else { /* Peripheral* is the proper term, but for now keep duplicates * so it won't break when user up/downgrades. Remove the other * term after a few releases. */ store_ltk_group(adapter, peer, bdaddr_type, key, "PeripheralLongTermKey", authenticated, enc_size, ediv, rand); store_ltk_group(adapter, peer, bdaddr_type, key, "SlaveLongTermKey", authenticated, enc_size, ediv, rand); } } static void new_long_term_key_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_new_long_term_key *ev = param; const struct mgmt_addr_info *addr = &ev->key.addr; struct btd_adapter *adapter = user_data; struct btd_device *device; bool persistent; char dst[18]; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small long term key event"); return; } ba2str(&addr->bdaddr, dst); DBG("hci%u new LTK for %s type %u enc_size %u", adapter->dev_id, dst, ev->key.type, ev->key.enc_size); device = btd_adapter_get_device(adapter, &addr->bdaddr, addr->type); if (!device) { btd_error(adapter->dev_id, "Unable to get device object for %s", dst); return; } /* * Some older kernel versions set store_hint for long term keys * from resolvable and unresolvable random addresses, but there * is no point in storing these. Next time around the device * address will be invalid. * * So only for identity addresses (public and static random) use * the store_hint as an indication if the long term key should * be persistently stored. * */ if (addr->type == BDADDR_LE_RANDOM && (addr->bdaddr.b[5] & 0xc0) != 0xc0) persistent = false; else persistent = !!ev->store_hint; if (persistent) { const struct mgmt_ltk_info *key = &ev->key; uint16_t ediv; uint64_t rand; ediv = le16_to_cpu(key->ediv); rand = le64_to_cpu(key->rand); store_longtermkey(adapter, &key->addr.bdaddr, key->addr.type, key->val, key->central, key->type, key->enc_size, ediv, rand); device_set_bonded(device, addr->type); } device_set_ltk(device, ev->key.val, ev->key.central, ev->key.enc_size); bonding_complete(adapter, &addr->bdaddr, addr->type, 0); } static void new_csrk_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_new_csrk *ev = param; const struct mgmt_addr_info *addr = &ev->key.addr; const struct mgmt_csrk_info *key = &ev->key; struct btd_adapter *adapter = user_data; struct btd_device *device; char dst[18]; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small CSRK event"); return; } ba2str(&addr->bdaddr, dst); DBG("hci%u new CSRK for %s type %u", adapter->dev_id, dst, ev->key.type); device = btd_adapter_get_device(adapter, &addr->bdaddr, addr->type); if (!device) { btd_error(adapter->dev_id, "Unable to get device object for %s", dst); return; } device_set_csrk(device, key->val, 0, key->type, ev->store_hint); } static void store_irk(struct btd_adapter *adapter, const bdaddr_t *peer, uint8_t bdaddr_type, const unsigned char *key) { char device_addr[18]; char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; char *store_data; char str[33]; size_t length = 0; int i; ba2str(peer, device_addr); create_filename(filename, PATH_MAX, "/%s/%s/info", btd_adapter_get_storage_dir(adapter), device_addr); create_file(filename, 0600); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_error_free(gerr); g_key_file_free(key_file); return; } for (i = 0; i < 16; i++) sprintf(str + (i * 2), "%2.2X", key[i]); g_key_file_set_string(key_file, "IdentityResolvingKey", "Key", str); store_data = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, store_data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(store_data); g_key_file_free(key_file); } static void new_irk_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_new_irk *ev = param; const struct mgmt_addr_info *addr = &ev->key.addr; const struct mgmt_irk_info *irk = &ev->key; struct btd_adapter *adapter = user_data; struct btd_device *device, *duplicate; bool persistent; char dst[18], rpa[18]; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small New IRK event"); return; } ba2str(&addr->bdaddr, dst); ba2str(&ev->rpa, rpa); DBG("hci%u new IRK for %s RPA %s", adapter->dev_id, dst, rpa); if (bacmp(&ev->rpa, BDADDR_ANY)) { device = btd_adapter_get_device(adapter, &ev->rpa, BDADDR_LE_RANDOM); duplicate = btd_adapter_find_device(adapter, &addr->bdaddr, addr->type); if (duplicate == device) duplicate = NULL; } else { device = btd_adapter_get_device(adapter, &addr->bdaddr, addr->type); duplicate = NULL; } if (!device) { btd_error(adapter->dev_id, "Unable to get device object for %s", dst); return; } device_update_addr(device, &addr->bdaddr, addr->type, irk->val); if (duplicate) device_merge_duplicate(device, duplicate); persistent = !!ev->store_hint; if (!persistent) return; store_irk(adapter, &addr->bdaddr, addr->type, irk->val); btd_device_set_temporary(device, false); } void btd_adapter_store_conn_param(struct btd_adapter *adapter, const bdaddr_t *peer, uint8_t bdaddr_type, uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout) { char device_addr[18]; char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; char *store_data; size_t length = 0; ba2str(peer, device_addr); DBG(""); create_filename(filename, PATH_MAX, "/%s/%s/info", btd_adapter_get_storage_dir(adapter), device_addr); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } g_key_file_set_integer(key_file, "ConnectionParameters", "MinInterval", min_interval); g_key_file_set_integer(key_file, "ConnectionParameters", "MaxInterval", max_interval); g_key_file_set_integer(key_file, "ConnectionParameters", "Latency", latency); g_key_file_set_integer(key_file, "ConnectionParameters", "Timeout", timeout); create_file(filename, 0600); store_data = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, store_data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(store_data); g_key_file_free(key_file); } static void new_conn_param(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_new_conn_param *ev = param; struct btd_adapter *adapter = user_data; uint16_t min, max, latency, timeout; struct btd_device *dev; char dst[18]; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small New Connection Parameter event"); return; } ba2str(&ev->addr.bdaddr, dst); min = btohs(ev->min_interval); max = btohs(ev->max_interval); latency = btohs(ev->latency); timeout = btohs(ev->timeout); DBG("hci%u %s (%u) min 0x%04x max 0x%04x latency 0x%04x timeout 0x%04x", adapter->dev_id, dst, ev->addr.type, min, max, latency, timeout); dev = btd_adapter_get_device(adapter, &ev->addr.bdaddr, ev->addr.type); if (!dev) { btd_error(adapter->dev_id, "Unable to get device object for %s", dst); return; } if (!ev->store_hint) return; btd_adapter_store_conn_param(adapter, &ev->addr.bdaddr, ev->addr.type, ev->min_interval, ev->max_interval, ev->latency, ev->timeout); } int adapter_set_io_capability(struct btd_adapter *adapter, enum mgmt_io_capability io_cap) { struct mgmt_cp_set_io_capability cp; if (!btd_opts.pairable) { if (io_cap == MGMT_IO_CAPABILITY_INVALID) { if (adapter->current_settings & MGMT_SETTING_BONDABLE) set_mode(adapter, MGMT_OP_SET_BONDABLE, 0x00); return 0; } if (!(adapter->current_settings & MGMT_SETTING_BONDABLE)) set_mode(adapter, MGMT_OP_SET_BONDABLE, 0x01); } else if (io_cap == MGMT_IO_CAPABILITY_INVALID) io_cap = MGMT_IO_CAPABILITY_NOINPUTNOOUTPUT; memset(&cp, 0, sizeof(cp)); cp.io_capability = io_cap; if (mgmt_send(adapter->mgmt, MGMT_OP_SET_IO_CAPABILITY, adapter->dev_id, sizeof(cp), &cp, NULL, NULL, NULL) > 0) return 0; return -EIO; } int btd_adapter_add_remote_oob_data(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t *hash, uint8_t *randomizer) { struct mgmt_cp_add_remote_oob_data cp; char addr[18]; ba2str(bdaddr, addr); DBG("hci%d bdaddr %s", adapter->dev_id, addr); memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); memcpy(cp.hash192, hash, 16); if (randomizer) memcpy(cp.rand192, randomizer, 16); if (mgmt_send(adapter->mgmt, MGMT_OP_ADD_REMOTE_OOB_DATA, adapter->dev_id, sizeof(cp), &cp, NULL, NULL, NULL) > 0) return 0; return -EIO; } int btd_adapter_remove_remote_oob_data(struct btd_adapter *adapter, const bdaddr_t *bdaddr) { struct mgmt_cp_remove_remote_oob_data cp; char addr[18]; ba2str(bdaddr, addr); DBG("hci%d bdaddr %s", adapter->dev_id, addr); memset(&cp, 0, sizeof(cp)); bacpy(&cp.addr.bdaddr, bdaddr); if (mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_REMOTE_OOB_DATA, adapter->dev_id, sizeof(cp), &cp, NULL, NULL, NULL) > 0) return 0; return -EIO; } bool btd_adapter_ssp_enabled(struct btd_adapter *adapter) { if (adapter->current_settings & MGMT_SETTING_SSP) return true; return false; } void btd_adapter_set_oob_handler(struct btd_adapter *adapter, struct oob_handler *handler) { adapter->oob_handler = handler; } gboolean btd_adapter_check_oob_handler(struct btd_adapter *adapter) { return adapter->oob_handler != NULL; } static void read_local_oob_data_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_read_local_oob_data *rp = param; struct btd_adapter *adapter = user_data; const uint8_t *hash, *randomizer; if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Read local OOB data failed: %s (0x%02x)", mgmt_errstr(status), status); hash = NULL; randomizer = NULL; } else if (length < sizeof(*rp)) { btd_error(adapter->dev_id, "Too small read local OOB data response"); return; } else { hash = rp->hash192; randomizer = rp->rand192; } if (!adapter->oob_handler || !adapter->oob_handler->read_local_cb) return; adapter->oob_handler->read_local_cb(adapter, hash, randomizer, adapter->oob_handler->user_data); g_free(adapter->oob_handler); adapter->oob_handler = NULL; } int btd_adapter_read_local_oob_data(struct btd_adapter *adapter) { DBG("hci%u", adapter->dev_id); if (mgmt_send(adapter->mgmt, MGMT_OP_READ_LOCAL_OOB_DATA, adapter->dev_id, 0, NULL, read_local_oob_data_complete, adapter, NULL) > 0) return 0; return -EIO; } void btd_adapter_for_each_device(struct btd_adapter *adapter, void (*cb)(struct btd_device *device, void *data), void *data) { g_slist_foreach(adapter->devices, (GFunc) cb, data); } static int adapter_cmp(gconstpointer a, gconstpointer b) { struct btd_adapter *adapter = (struct btd_adapter *) a; const bdaddr_t *bdaddr = b; return bacmp(&adapter->bdaddr, bdaddr); } static int adapter_id_cmp(gconstpointer a, gconstpointer b) { struct btd_adapter *adapter = (struct btd_adapter *) a; uint16_t id = GPOINTER_TO_UINT(b); return adapter->dev_id == id ? 0 : -1; } struct btd_adapter *adapter_find(const bdaddr_t *sba) { GSList *match; match = g_slist_find_custom(adapters, sba, adapter_cmp); if (!match) return NULL; return match->data; } struct btd_adapter *adapter_find_by_id(int id) { GSList *match; match = g_slist_find_custom(adapters, GINT_TO_POINTER(id), adapter_id_cmp); if (!match) return NULL; return match->data; } void btd_adapter_foreach(adapter_cb func, gpointer user_data) { g_slist_foreach(adapters, (GFunc) func, user_data); } static int set_did(struct btd_adapter *adapter, uint16_t vendor, uint16_t product, uint16_t version, uint16_t source) { struct mgmt_cp_set_device_id cp; DBG("hci%u source %x vendor %x product %x version %x", adapter->dev_id, source, vendor, product, version); memset(&cp, 0, sizeof(cp)); cp.source = htobs(source); cp.vendor = htobs(vendor); cp.product = htobs(product); cp.version = htobs(version); if (mgmt_send(adapter->mgmt, MGMT_OP_SET_DEVICE_ID, adapter->dev_id, sizeof(cp), &cp, NULL, NULL, NULL) > 0) return 0; return -EIO; } static void services_modified(struct gatt_db_attribute *attrib, void *user_data) { struct btd_adapter *adapter = user_data; g_dbus_emit_property_changed(dbus_conn, adapter->path, ADAPTER_INTERFACE, "UUIDs"); } static int adapter_register(struct btd_adapter *adapter) { struct agent *agent; struct gatt_db *db; if (powering_down) return -EBUSY; adapter->path = g_strdup_printf("/org/bluez/hci%d", adapter->dev_id); if (!g_dbus_register_interface(dbus_conn, adapter->path, ADAPTER_INTERFACE, adapter_methods, NULL, adapter_properties, adapter, adapter_free)) { btd_error(adapter->dev_id, "Adapter interface init failed on path %s", adapter->path); g_free(adapter->path); adapter->path = NULL; return -EINVAL; } if (adapters == NULL) adapter->is_default = true; adapters = g_slist_append(adapters, adapter); agent = agent_get(NULL); if (agent) { enum mgmt_io_capability io_cap = agent_get_io_capability(agent); adapter_set_io_capability(adapter, io_cap); agent_unref(agent); } adapter->battery_provider_manager = btd_battery_provider_manager_create(adapter); /* Don't start GATT database and advertising managers on * non-LE controllers. */ if (!(adapter->supported_settings & MGMT_SETTING_LE) || btd_opts.mode == BT_MODE_BREDR) goto load; adapter->database = btd_gatt_database_new(adapter); if (!adapter->database) { btd_error(adapter->dev_id, "Failed to create GATT database for adapter"); adapters = g_slist_remove(adapters, adapter); return -EINVAL; } adapter->adv_manager = btd_adv_manager_new(adapter, adapter->mgmt); if (g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL) { if (adapter->supported_settings & MGMT_SETTING_LE) { adapter->adv_monitor_manager = btd_adv_monitor_manager_create(adapter, adapter->mgmt); if (!adapter->adv_monitor_manager) { btd_error(adapter->dev_id, "Failed to create Adv Monitor " "Manager for adapter"); return -EINVAL; } } else { btd_info(adapter->dev_id, "Adv Monitor Manager " "skipped, LE unavailable"); } } db = btd_gatt_database_get_db(adapter->database); adapter->db_id = gatt_db_register(db, services_modified, services_modified, adapter, NULL); load: mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_FLAGS_CHANGED, adapter->dev_id, device_flags_changed_callback, adapter, NULL); load_config(adapter); fix_storage(adapter); load_drivers(adapter); btd_profile_foreach(probe_profile, adapter); clear_blocked(adapter); load_defaults(adapter); load_devices(adapter); /* restore Service Changed CCC value for bonded devices */ btd_gatt_database_restore_svc_chng_ccc(adapter->database); /* retrieve the active connections: address the scenario where * the are active connections before the daemon've started */ if (btd_adapter_get_powered(adapter)) load_connections(adapter); adapter->initialized = TRUE; if (btd_opts.did_source) { /* DeviceID record is added by sdpd-server before any other * record is registered. */ adapter_service_insert(adapter, sdp_record_find(0x10000)); set_did(adapter, btd_opts.did_vendor, btd_opts.did_product, btd_opts.did_version, btd_opts.did_source); } DBG("Adapter %s registered", adapter->path); return 0; } static int adapter_unregister(struct btd_adapter *adapter) { DBG("Unregister path: %s", adapter->path); adapters = g_slist_remove(adapters, adapter); if (adapter->is_default && adapters != NULL) { struct btd_adapter *new_default; new_default = adapter_find_by_id(hci_get_route(NULL)); if (new_default == NULL) new_default = adapters->data; new_default->is_default = true; } adapter_list = g_list_remove(adapter_list, adapter); adapter_remove(adapter); btd_adapter_unref(adapter); return 0; } static void disconnected_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_device_disconnected *ev = param; struct btd_adapter *adapter = user_data; uint8_t reason; if (length < sizeof(struct mgmt_addr_info)) { btd_error(adapter->dev_id, "Too small device disconnected event"); return; } if (length < sizeof(*ev)) reason = MGMT_DEV_DISCONN_UNKNOWN; else reason = ev->reason; dev_disconnected(adapter, &ev->addr, reason); } static void connected_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_device_connected *ev = param; struct btd_adapter *adapter = user_data; struct btd_device *device; struct eir_data eir_data; uint16_t eir_len; char addr[18]; bool name_known; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small device connected event"); return; } eir_len = btohs(ev->eir_len); if (length < sizeof(*ev) + eir_len) { btd_error(adapter->dev_id, "Too small device connected event"); return; } ba2str(&ev->addr.bdaddr, addr); DBG("hci%u device %s connected eir_len %u", index, addr, eir_len); device = btd_adapter_get_device(adapter, &ev->addr.bdaddr, ev->addr.type); if (!device) { btd_error(adapter->dev_id, "Unable to get device object for %s", addr); return; } memset(&eir_data, 0, sizeof(eir_data)); if (eir_len > 0) eir_parse(&eir_data, ev->eir, eir_len); if (eir_data.class != 0) device_set_class(device, eir_data.class); adapter_add_connection(adapter, device, ev->addr.type, le32_to_cpu(ev->flags)); name_known = device_name_known(device); if (eir_data.name && (eir_data.name_complete || !name_known)) { device_store_cached_name(device, eir_data.name); btd_device_device_set_name(device, eir_data.name); } if (eir_data.msd_list) adapter_msd_notify(adapter, device, eir_data.msd_list); eir_data_free(&eir_data); } static void controller_resume_notify(struct btd_adapter *adapter) { GSList *l; for (l = adapter->drivers; l; l = g_slist_next(l)) { struct btd_adapter_driver *driver = l->data; if (driver->resume) driver->resume(adapter); } } static void controller_resume_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_controller_resume *ev = param; struct btd_adapter *adapter = user_data; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small device resume event"); return; } info("Controller resume with wake event 0x%x", ev->wake_reason); controller_resume_notify(adapter); } static void device_blocked_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_device_blocked *ev = param; struct btd_adapter *adapter = user_data; struct btd_device *device; char addr[18]; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small device blocked event"); return; } ba2str(&ev->addr.bdaddr, addr); DBG("hci%u %s blocked", index, addr); device = btd_adapter_find_device(adapter, &ev->addr.bdaddr, ev->addr.type); if (device) device_block(device, TRUE); } static void device_unblocked_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_device_unblocked *ev = param; struct btd_adapter *adapter = user_data; struct btd_device *device; char addr[18]; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small device unblocked event"); return; } ba2str(&ev->addr.bdaddr, addr); DBG("hci%u %s unblocked", index, addr); device = btd_adapter_find_device(adapter, &ev->addr.bdaddr, ev->addr.type); if (device) device_unblock(device, FALSE, TRUE); } static void conn_fail_notify(struct btd_device *dev, uint8_t status) { GSList *l; for (l = conn_fail_list; l; l = g_slist_next(l)) { btd_conn_fail_cb conn_fail_cb = l->data; conn_fail_cb(dev, status); } } void btd_add_conn_fail_cb(btd_conn_fail_cb func) { conn_fail_list = g_slist_append(conn_fail_list, func); } void btd_remove_conn_fail_cb(btd_conn_fail_cb func) { conn_fail_list = g_slist_remove(conn_fail_list, func); } static void connect_failed_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_connect_failed *ev = param; struct btd_adapter *adapter = user_data; struct btd_device *device; char addr[18]; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small connect failed event"); return; } ba2str(&ev->addr.bdaddr, addr); DBG("hci%u %s status %u", index, addr, ev->status); device = btd_adapter_find_device(adapter, &ev->addr.bdaddr, ev->addr.type); if (device) { conn_fail_notify(device, ev->status); /* If the device is in a bonding process cancel any auth request * sent to the agent before proceeding, but keep the bonding * request structure. */ if (device_is_bonding(device, NULL)) device_cancel_authentication(device, FALSE); } /* In the case of security mode 3 devices */ bonding_attempt_complete(adapter, &ev->addr.bdaddr, ev->addr.type, ev->status); /* If the device is scheduled to retry the bonding wait until the retry * happens. In other case, proceed with cancel the bondig. */ if (device && device_is_bonding(device, NULL) && !device_is_retrying(device)) { device_cancel_authentication(device, TRUE); device_bonding_failed(device, ev->status); } /* In the case the bonding was canceled or did exists, remove the device * when it is temporary. */ if (device && !device_is_bonding(device, NULL) && device_is_temporary(device)) btd_adapter_remove_device(adapter, device); } static void remove_keys(struct btd_adapter *adapter, struct btd_device *device, uint8_t type) { char device_addr[18]; char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; gsize length = 0; char *str; ba2str(device_get_address(device), device_addr); create_filename(filename, PATH_MAX, "/%s/%s/info", btd_adapter_get_storage_dir(adapter), device_addr); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } if (type == BDADDR_BREDR) { g_key_file_remove_group(key_file, "LinkKey", NULL); } else { g_key_file_remove_group(key_file, "LongTermKey", NULL); g_key_file_remove_group(key_file, "LocalSignatureKey", NULL); g_key_file_remove_group(key_file, "RemoteSignatureKey", NULL); g_key_file_remove_group(key_file, "IdentityResolvingKey", NULL); } str = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, str, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(str); g_key_file_free(key_file); } static void unpaired_callback(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_device_unpaired *ev = param; struct btd_adapter *adapter = user_data; struct btd_device *device; char addr[18]; if (length < sizeof(*ev)) { btd_error(adapter->dev_id, "Too small device unpaired event"); return; } ba2str(&ev->addr.bdaddr, addr); DBG("hci%u addr %s", index, addr); device = btd_adapter_find_device(adapter, &ev->addr.bdaddr, ev->addr.type); if (!device) { btd_warn(adapter->dev_id, "No device object for unpaired device %s", addr); return; } remove_keys(adapter, device, ev->addr.type); device_set_unpaired(device, ev->addr.type); } static void clear_devices_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { if (status != MGMT_STATUS_SUCCESS) { error("Failed to clear devices: %s (0x%02x)", mgmt_errstr(status), status); return; } } static int clear_devices(struct btd_adapter *adapter) { struct mgmt_cp_remove_device cp; if (!btd_has_kernel_features(KERNEL_CONN_CONTROL)) return 0; memset(&cp, 0, sizeof(cp)); DBG("sending clear devices command for index %u", adapter->dev_id); if (mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_DEVICE, adapter->dev_id, sizeof(cp), &cp, clear_devices_complete, adapter, NULL) > 0) return 0; btd_error(adapter->dev_id, "Failed to clear devices for index %u", adapter->dev_id); return -EIO; } static bool get_static_addr(struct btd_adapter *adapter) { struct bt_crypto *crypto; GKeyFile *file; GError *gerr = NULL; char filename[PATH_MAX]; char **addrs; char mfg[7]; char *str; bool ret; gsize len, i; snprintf(mfg, sizeof(mfg), "0x%04x", adapter->manufacturer); create_filename(filename, PATH_MAX, "/addresses"); file = g_key_file_new(); if (!g_key_file_load_from_file(file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } addrs = g_key_file_get_string_list(file, "Static", mfg, &len, NULL); if (addrs) { for (i = 0; i < len; i++) { bdaddr_t addr; str2ba(addrs[i], &addr); if (adapter_find(&addr)) continue; /* Usable address found in list */ bacpy(&adapter->bdaddr, &addr); adapter->bdaddr_type = BDADDR_LE_RANDOM; ret = true; goto done; } len++; addrs = g_renew(char *, addrs, len + 1); } else { len = 1; addrs = g_new(char *, len + 1); } /* Initialize slot for new address */ addrs[len - 1] = g_malloc(18); addrs[len] = NULL; crypto = bt_crypto_new(); if (!crypto) { error("Failed to open crypto"); ret = false; goto done; } ret = bt_crypto_random_bytes(crypto, &adapter->bdaddr, sizeof(adapter->bdaddr)); if (!ret) { error("Failed to generate static address"); bt_crypto_unref(crypto); goto done; } bt_crypto_unref(crypto); adapter->bdaddr.b[5] |= 0xc0; adapter->bdaddr_type = BDADDR_LE_RANDOM; ba2str(&adapter->bdaddr, addrs[len - 1]); g_key_file_set_string_list(file, "Static", mfg, (const char **)addrs, len); str = g_key_file_to_data(file, &len, NULL); if (!g_file_set_contents(filename, str, len, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(str); ret = true; done: g_key_file_free(file); g_strfreev(addrs); return ret; } static bool set_static_addr(struct btd_adapter *adapter) { struct mgmt_cp_set_static_address cp; /* dual-mode adapters must have a public address */ if (adapter->supported_settings & MGMT_SETTING_BREDR) return false; if (!(adapter->supported_settings & MGMT_SETTING_LE)) return false; DBG("Setting static address"); if (!get_static_addr(adapter)) return false; bacpy(&cp.bdaddr, &adapter->bdaddr); if (mgmt_send(adapter->mgmt, MGMT_OP_SET_STATIC_ADDRESS, adapter->dev_id, sizeof(cp), &cp, NULL, NULL, NULL) > 0) { return true; } return false; } static void set_blocked_keys_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to set blocked keys: %s (0x%02x)", mgmt_errstr(status), status); return; } DBG("Successfully set blocked keys for index %u", adapter->dev_id); } static bool set_blocked_keys(struct btd_adapter *adapter) { uint8_t buffer[sizeof(struct mgmt_cp_set_blocked_keys) + sizeof(blocked_keys)] = { 0 }; struct mgmt_cp_set_blocked_keys *cp = (struct mgmt_cp_set_blocked_keys *)buffer; int i; cp->key_count = ARRAY_SIZE(blocked_keys); for (i = 0; i < cp->key_count; ++i) { cp->keys[i].type = blocked_keys[i].type; memcpy(cp->keys[i].val, blocked_keys[i].val, sizeof(cp->keys[i].val)); } return mgmt_send(mgmt_primary, MGMT_OP_SET_BLOCKED_KEYS, adapter->dev_id, sizeof(buffer), buffer, set_blocked_keys_complete, adapter, NULL); } #define EXP_FEAT(_flag, _uuid, _func) \ { \ .flag = _flag, \ .uuid = _uuid, \ .func = _func, \ } static void exp_complete(void *user_data); static bool exp_mgmt_send(struct btd_adapter *adapter, uint16_t opcode, uint16_t index, uint16_t length, const void *param, mgmt_request_func_t callback) { struct exp_pending *pending; pending = g_new0(struct exp_pending, 1); pending->adapter = adapter; if (!queue_push_tail(adapter->exp_pending, pending)) { g_free(pending); return false; } pending->id = mgmt_send(adapter->mgmt, opcode, index, length, param, callback, pending, exp_complete); if (!pending->id) { queue_remove(adapter->exp_pending, pending); g_free(pending); return false; } return true; } static void set_exp_debug_complete(uint8_t status, uint16_t len, const void *param, void *user_data) { struct exp_pending *pending = user_data; struct btd_adapter *adapter = pending->adapter; uint8_t action; if (status != 0) { error("Set Experimental Debug failed with status 0x%02x (%s)", status, mgmt_errstr(status)); return; } action = btd_kernel_experimental_enabled(debug_uuid.str); DBG("Experimental Debug successfully %s", action ? "set" : "reset"); if (action) queue_push_tail(adapter->exps, (void *)debug_uuid.val); } static void exp_debug_func(struct btd_adapter *adapter, uint8_t action) { struct mgmt_cp_set_exp_feature cp; memset(&cp, 0, sizeof(cp)); memcpy(cp.uuid, debug_uuid.val, 16); cp.action = action; if (exp_mgmt_send(adapter, MGMT_OP_SET_EXP_FEATURE, MGMT_INDEX_NONE, sizeof(cp), &cp, set_exp_debug_complete)) return; btd_error(adapter->dev_id, "Failed to set exp debug"); } static void le_simult_central_peripheral_func(struct btd_adapter *adapter, uint8_t action) { if (action) queue_push_tail(adapter->exps, (void *)le_simult_central_peripheral_uuid.val); } static void quality_report_func(struct btd_adapter *adapter, uint8_t action) { if (action) queue_push_tail(adapter->exps, (void *)quality_report_uuid.val); } static void set_rpa_resolution_complete(uint8_t status, uint16_t len, const void *param, void *user_data) { struct exp_pending *pending = user_data; struct btd_adapter *adapter = pending->adapter; uint8_t action; if (status != 0) { error("Set RPA Resolution failed with status 0x%02x (%s)", status, mgmt_errstr(status)); return; } action = btd_kernel_experimental_enabled(rpa_resolution_uuid.str); DBG("RPA Resolution successfully %s", action ? "set" : "reset"); if (action) queue_push_tail(adapter->exps, (void *)rpa_resolution_uuid.val); } static void rpa_resolution_func(struct btd_adapter *adapter, uint8_t action) { struct mgmt_cp_set_exp_feature cp; memset(&cp, 0, sizeof(cp)); memcpy(cp.uuid, rpa_resolution_uuid.val, 16); cp.action = action; if (exp_mgmt_send(adapter, MGMT_OP_SET_EXP_FEATURE, adapter->dev_id, sizeof(cp), &cp, set_rpa_resolution_complete)) return; btd_error(adapter->dev_id, "Failed to set RPA Resolution"); } static void codec_offload_complete(uint8_t status, uint16_t len, const void *param, void *user_data) { struct exp_pending *pending = user_data; struct btd_adapter *adapter = pending->adapter; uint8_t action; if (status != 0) { error("Set Codec Offload failed with status 0x%02x (%s)", status, mgmt_errstr(status)); return; } action = btd_kernel_experimental_enabled(codec_offload_uuid.str); DBG("Codec Offload successfully %s", action ? "set" : "reset"); if (action) queue_push_tail(adapter->exps, (void *)codec_offload_uuid.val); } static void codec_offload_func(struct btd_adapter *adapter, uint8_t action) { struct mgmt_cp_set_exp_feature cp; memset(&cp, 0, sizeof(cp)); memcpy(cp.uuid, codec_offload_uuid.val, 16); cp.action = action; if (exp_mgmt_send(adapter, MGMT_OP_SET_EXP_FEATURE, adapter->dev_id, sizeof(cp), &cp, codec_offload_complete)) return; btd_error(adapter->dev_id, "Failed to set Codec Offload"); } static void iso_socket_complete(uint8_t status, uint16_t len, const void *param, void *user_data) { struct exp_pending *pending = user_data; struct btd_adapter *adapter = pending->adapter; uint8_t action; if (status != 0) { error("Set ISO Socket failed with status 0x%02x (%s)", status, mgmt_errstr(status)); return; } action = btd_kernel_experimental_enabled(iso_socket_uuid.str); DBG("ISO Socket successfully %s", action ? "set" : "reset"); if (action) queue_push_tail(adapter->exps, (void *)iso_socket_uuid.val); } static void iso_socket_func(struct btd_adapter *adapter, uint8_t action) { struct mgmt_cp_set_exp_feature cp; memset(&cp, 0, sizeof(cp)); memcpy(cp.uuid, iso_socket_uuid.val, 16); cp.action = action; if (exp_mgmt_send(adapter, MGMT_OP_SET_EXP_FEATURE, MGMT_INDEX_NONE, sizeof(cp), &cp, iso_socket_complete)) return; btd_error(adapter->dev_id, "Failed to set ISO Socket"); } static const struct exp_feat { uint32_t flag; const struct mgmt_exp_uuid *uuid; void (*func)(struct btd_adapter *adapter, uint8_t action); } exp_table[] = { EXP_FEAT(EXP_FEAT_DEBUG, &debug_uuid, exp_debug_func), EXP_FEAT(EXP_FEAT_LE_SIMULT_ROLES, &le_simult_central_peripheral_uuid, le_simult_central_peripheral_func), EXP_FEAT(EXP_FEAT_BQR, &quality_report_uuid, quality_report_func), EXP_FEAT(EXP_FEAT_RPA_RESOLUTION, &rpa_resolution_uuid, rpa_resolution_func), EXP_FEAT(EXP_FEAT_CODEC_OFFLOAD, &codec_offload_uuid, codec_offload_func), EXP_FEAT(EXP_FEAT_ISO_SOCKET, &iso_socket_uuid, iso_socket_func), }; static void read_exp_features_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct exp_pending *pending = user_data; struct btd_adapter *adapter = pending->adapter; const struct mgmt_rp_read_exp_features_info *rp = param; size_t feature_count = 0; size_t i = 0; DBG("index %u status 0x%02x", adapter->dev_id, status); if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to read exp features info: %s (0x%02x)", mgmt_errstr(status), status); return; } if (length < sizeof(*rp)) { btd_error(adapter->dev_id, "Response too small"); return; } feature_count = le16_to_cpu(rp->feature_count); if (length < sizeof(*rp) + (sizeof(*rp->features) * feature_count)) { btd_error(adapter->dev_id, "Response too small"); return; } for (i = 0; i < feature_count; ++i) { size_t j; for (j = 0; j < ARRAY_SIZE(exp_table); j++) { const struct exp_feat *feat = &exp_table[j]; const char *str; uint8_t action; if (memcmp(rp->features[i].uuid, feat->uuid->val, sizeof(rp->features[i].uuid))) continue; str = feat->uuid->str; action = btd_kernel_experimental_enabled(str); DBG("%s flags %u action %u", str, rp->features[i].flags, action); /* If already set don't attempt to set it again */ if (action == (rp->features[i].flags & BIT(0))) { if (action & BIT(0)) queue_push_tail(adapter->exps, (void *)feat->uuid->val); continue; } if (feat->func) feat->func(adapter, action); } } } static void read_exp_features(struct btd_adapter *adapter) { if (exp_mgmt_send(adapter, MGMT_OP_READ_EXP_FEATURES_INFO, adapter->dev_id, 0, NULL, read_exp_features_complete)) return; btd_error(adapter->dev_id, "Failed to read exp features info"); } static void read_info_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter = user_data; const struct mgmt_rp_read_info *rp = param; uint32_t missing_settings; int err; DBG("index %u status 0x%02x", adapter->dev_id, status); if (status != MGMT_STATUS_SUCCESS) { btd_error(adapter->dev_id, "Failed to read info for index %u: %s (0x%02x)", adapter->dev_id, mgmt_errstr(status), status); goto failed; } if (length < sizeof(*rp)) { btd_error(adapter->dev_id, "Too small read info complete response"); goto failed; } /* * Store controller information for class of device, device * name, short name and settings. * * During the lifetime of the controller these will be updated by * events and the information is required to keep the current * state of the controller. */ adapter->dev_class = rp->dev_class[0] | (rp->dev_class[1] << 8) | (rp->dev_class[2] << 16); adapter->name = g_strdup((const char *) rp->name); adapter->short_name = g_strdup((const char *) rp->short_name); adapter->manufacturer = btohs(rp->manufacturer); adapter->supported_settings = btohl(rp->supported_settings); adapter->current_settings = btohl(rp->current_settings); adapter->version = rp->version; clear_uuids(adapter); clear_devices(adapter); if (bacmp(&rp->bdaddr, BDADDR_ANY) == 0) { if (!set_static_addr(adapter)) { btd_error(adapter->dev_id, "No Bluetooth address for index %u", adapter->dev_id); goto failed; } } else { struct btd_adapter *tmp; tmp = adapter_find(&rp->bdaddr); if (tmp) { btd_error(adapter->dev_id, "Bluetooth address for index %u match index %u", adapter->dev_id, tmp->dev_id); goto failed; } bacpy(&adapter->bdaddr, &rp->bdaddr); if (!(adapter->supported_settings & MGMT_SETTING_LE)) adapter->bdaddr_type = BDADDR_BREDR; else adapter->bdaddr_type = BDADDR_LE_PUBLIC; } missing_settings = adapter->current_settings ^ adapter->supported_settings; switch (btd_opts.mode) { case BT_MODE_DUAL: if (missing_settings & MGMT_SETTING_LE) set_mode(adapter, MGMT_OP_SET_LE, 0x01); if (missing_settings & MGMT_SETTING_BREDR) set_mode(adapter, MGMT_OP_SET_BREDR, 0x01); if (missing_settings & MGMT_SETTING_SSP) set_mode(adapter, MGMT_OP_SET_SSP, 0x01); break; case BT_MODE_BREDR: if (!(adapter->supported_settings & MGMT_SETTING_BREDR)) { btd_error(adapter->dev_id, "Ignoring adapter without BR/EDR support"); goto failed; } if (missing_settings & MGMT_SETTING_BREDR) set_mode(adapter, MGMT_OP_SET_BREDR, 0x01); if (missing_settings & MGMT_SETTING_SSP) set_mode(adapter, MGMT_OP_SET_SSP, 0x01); if (adapter->current_settings & MGMT_SETTING_LE) set_mode(adapter, MGMT_OP_SET_LE, 0x00); break; case BT_MODE_LE: if (!(adapter->supported_settings & MGMT_SETTING_LE)) { btd_error(adapter->dev_id, "Ignoring adapter without LE support"); goto failed; } if (missing_settings & MGMT_SETTING_LE) set_mode(adapter, MGMT_OP_SET_LE, 0x01); if (adapter->current_settings & MGMT_SETTING_BREDR) set_mode(adapter, MGMT_OP_SET_BREDR, 0x00); break; } if (missing_settings & MGMT_SETTING_SECURE_CONN) set_mode(adapter, MGMT_OP_SET_SECURE_CONN, btd_opts.secure_conn); if (missing_settings & MGMT_SETTING_WIDEBAND_SPEECH) set_mode(adapter, MGMT_OP_SET_WIDEBAND_SPEECH, 0x01); if (adapter->supported_settings & MGMT_SETTING_PRIVACY) set_privacy(adapter, btd_opts.privacy); if (btd_opts.fast_conn && (missing_settings & MGMT_SETTING_FAST_CONNECTABLE)) set_mode(adapter, MGMT_OP_SET_FAST_CONNECTABLE, 0x01); err = adapter_register(adapter); if (err < 0) { btd_error(adapter->dev_id, "Unable to register new adapter"); goto failed; } /* * Register all event notification handlers for controller. * * The handlers are registered after a succcesful read of the * controller info. From now on they can track updates and * notifications. */ mgmt_register(adapter->mgmt, MGMT_EV_NEW_SETTINGS, adapter->dev_id, new_settings_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_CLASS_OF_DEV_CHANGED, adapter->dev_id, dev_class_changed_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_LOCAL_NAME_CHANGED, adapter->dev_id, local_name_changed_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_DISCOVERING, adapter->dev_id, discovering_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_FOUND, adapter->dev_id, device_found_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_DISCONNECTED, adapter->dev_id, disconnected_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_CONNECTED, adapter->dev_id, connected_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_CONNECT_FAILED, adapter->dev_id, connect_failed_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_UNPAIRED, adapter->dev_id, unpaired_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_AUTH_FAILED, adapter->dev_id, auth_failed_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_NEW_LINK_KEY, adapter->dev_id, new_link_key_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_NEW_LONG_TERM_KEY, adapter->dev_id, new_long_term_key_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_NEW_CSRK, adapter->dev_id, new_csrk_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_NEW_IRK, adapter->dev_id, new_irk_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_NEW_CONN_PARAM, adapter->dev_id, new_conn_param, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_BLOCKED, adapter->dev_id, device_blocked_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_UNBLOCKED, adapter->dev_id, device_unblocked_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_PIN_CODE_REQUEST, adapter->dev_id, pin_code_request_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_USER_CONFIRM_REQUEST, adapter->dev_id, user_confirm_request_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_USER_PASSKEY_REQUEST, adapter->dev_id, user_passkey_request_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_PASSKEY_NOTIFY, adapter->dev_id, user_passkey_notify_callback, adapter, NULL); mgmt_register(adapter->mgmt, MGMT_EV_CONTROLLER_RESUME, adapter->dev_id, controller_resume_callback, adapter, NULL); set_dev_class(adapter); set_name(adapter, btd_adapter_get_name(adapter)); if (btd_has_kernel_features(KERNEL_BLOCKED_KEYS_SUPPORTED) && !set_blocked_keys(adapter)) { btd_error(adapter->dev_id, "Failed to set blocked keys for index %u", adapter->dev_id); goto failed; } if (btd_opts.pairable && !(adapter->current_settings & MGMT_SETTING_BONDABLE)) set_mode(adapter, MGMT_OP_SET_BONDABLE, 0x01); if (!btd_has_kernel_features(KERNEL_CONN_CONTROL)) set_mode(adapter, MGMT_OP_SET_CONNECTABLE, 0x01); else if (adapter->current_settings & MGMT_SETTING_CONNECTABLE) set_mode(adapter, MGMT_OP_SET_CONNECTABLE, 0x00); if (adapter->stored_discoverable && !adapter->discoverable_timeout) set_discoverable(adapter, 0x01, 0); if (btd_adapter_get_powered(adapter)) adapter_start(adapter); return; failed: /* * Remove adapter from list in case of a failure. * * Leaving an adapter structure around for a controller that can * not be initialized makes no sense at the moment. * * This is a simplification to avoid constant checks if the * adapter is ready to do anything. */ adapter_list = g_list_remove(adapter_list, adapter); btd_adapter_unref(adapter); } static void reset_adv_monitors_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_remove_adv_monitor *rp = param; if (status != MGMT_STATUS_SUCCESS) { error("Failed to reset Adv Monitors: %s (0x%02x)", mgmt_errstr(status), status); return; } if (length < sizeof(*rp)) { error("Wrong size of remove Adv Monitor response for reset " "all Adv Monitors"); return; } DBG("Removed all Adv Monitors"); } static void reset_adv_monitors(uint16_t index) { struct mgmt_cp_remove_adv_monitor cp; DBG("sending remove Adv Monitor command with handle 0"); /* Handle 0 indicates to remove all */ cp.monitor_handle = 0; if (mgmt_send(mgmt_primary, MGMT_OP_REMOVE_ADV_MONITOR, index, sizeof(cp), &cp, reset_adv_monitors_complete, NULL, NULL) > 0) { return; } error("Failed to reset Adv Monitors"); } static void read_info(struct btd_adapter *adapter) { DBG("sending read info command for index %u", adapter->dev_id); if (mgmt_send(mgmt_primary, MGMT_OP_READ_INFO, adapter->dev_id, 0, NULL, read_info_complete, adapter, NULL) > 0) return; btd_error(adapter->dev_id, "Failed to read controller info for index %u", adapter->dev_id); adapter_list = g_list_remove(adapter_list, adapter); btd_adapter_unref(adapter); } static void exp_complete(void *user_data) { struct exp_pending *pending = user_data; struct btd_adapter *adapter = pending->adapter; if (!adapter) return; /* canceled */ queue_remove(adapter->exp_pending, pending); g_free(pending); if (queue_isempty(adapter->exp_pending)) { read_info(adapter); return; } DBG("index %u has %u pending MGMT EXP requests", adapter->dev_id, queue_length(adapter->exp_pending)); } static void index_added(uint16_t index, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter; DBG("index %u", index); adapter = btd_adapter_lookup(index); if (adapter) { btd_warn(adapter->dev_id, "Ignoring index added for an already existing adapter"); return; } /* Check if at maximum adapters allowed in the system then ignore the * adapter. */ if (btd_opts.max_adapters && btd_opts.max_adapters == g_slist_length(adapters)) return; reset_adv_monitors(index); adapter = btd_adapter_new(index); if (!adapter) { btd_error(index, "Unable to create new adapter for index %u", index); return; } if (btd_has_kernel_features(KERNEL_EXP_FEATURES)) read_exp_features(adapter); /* * Protect against potential two executions of read controller info. * * In case the start of the daemon and the action of adding a new * controller coincide this function might be called twice. * * To avoid the double execution of reading the controller info, * add the adapter already to the list. If an adapter is already * present, the second notification will cause a warning. If the * command fails the adapter is removed from the list again. */ adapter_list = g_list_append(adapter_list, adapter); if (queue_isempty(adapter->exp_pending)) read_info(adapter); } static void index_removed(uint16_t index, uint16_t length, const void *param, void *user_data) { struct btd_adapter *adapter; DBG("index %u", index); adapter = btd_adapter_lookup(index); if (!adapter) { warn("Ignoring index removal for a non-existent adapter"); return; } adapter_unregister(adapter); } static void read_index_list_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_read_index_list *rp = param; uint16_t num; int i; if (status != MGMT_STATUS_SUCCESS) { error("Failed to read index list: %s (0x%02x)", mgmt_errstr(status), status); return; } if (length < sizeof(*rp)) { error("Wrong size of read index list response"); return; } num = btohs(rp->num_controllers); DBG("Number of controllers: %d", num); if (num * sizeof(uint16_t) + sizeof(*rp) != length) { error("Incorrect packet size for index list response"); return; } for (i = 0; i < num; i++) { uint16_t index; index = btohs(rp->index[i]); DBG("Found index %u", index); /* * Pretend to be index added event notification. * * It is safe to just trigger the procedure for index * added notification. It does check against itself. */ index_added(index, 0, NULL, NULL); } } static void read_commands_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_read_commands *rp = param; uint16_t num_commands, num_events; size_t expected_len; int i; if (status != MGMT_STATUS_SUCCESS) { error("Failed to read supported commands: %s (0x%02x)", mgmt_errstr(status), status); return; } if (length < sizeof(*rp)) { error("Wrong size of read commands response"); return; } num_commands = btohs(rp->num_commands); num_events = btohs(rp->num_events); DBG("Number of commands: %d", num_commands); DBG("Number of events: %d", num_events); expected_len = sizeof(*rp) + num_commands * sizeof(uint16_t) + num_events * sizeof(uint16_t); if (length < expected_len) { error("Too small reply for supported commands: (%u != %zu)", length, expected_len); return; } for (i = 0; i < num_commands; i++) { uint16_t op = get_le16(rp->opcodes + i); switch (op) { case MGMT_OP_ADD_DEVICE: DBG("enabling kernel-side connection control"); kernel_features |= KERNEL_CONN_CONTROL; break; case MGMT_OP_SET_BLOCKED_KEYS: DBG("kernel supports the set_blocked_keys op"); kernel_features |= KERNEL_BLOCKED_KEYS_SUPPORTED; break; case MGMT_OP_SET_DEF_SYSTEM_CONFIG: DBG("kernel supports set system confic"); kernel_features |= KERNEL_SET_SYSTEM_CONFIG; break; case MGMT_OP_READ_EXP_FEATURES_INFO: DBG("kernel supports exp features"); kernel_features |= KERNEL_EXP_FEATURES; break; case MGMT_OP_ADD_EXT_ADV_PARAMS: DBG("kernel supports ext adv commands"); kernel_features |= KERNEL_HAS_EXT_ADV_ADD_CMDS; break; case MGMT_OP_READ_CONTROLLER_CAP: DBG("kernel supports controller cap command"); kernel_features |= KERNEL_HAS_CONTROLLER_CAP_CMD; break; default: break; } } for (i = 0; i < num_events; i++) { uint16_t ev = get_le16(rp->opcodes + num_commands + i); switch(ev) { case MGMT_EV_CONTROLLER_RESUME: DBG("kernel supports suspend/resume events"); kernel_features |= KERNEL_HAS_RESUME_EVT; break; } } } static void read_version_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_read_version *rp = param; if (status != MGMT_STATUS_SUCCESS) { error("Failed to read version information: %s (0x%02x)", mgmt_errstr(status), status); return; } if (length < sizeof(*rp)) { error("Wrong size of read version response"); return; } mgmt_version = rp->version; mgmt_revision = btohs(rp->revision); info("Bluetooth management interface %u.%u initialized", mgmt_version, mgmt_revision); if (mgmt_version < 1) { error("Version 1.0 or later of management interface required"); abort(); } DBG("sending read supported commands command"); /* * It is irrelevant if this command succeeds or fails. In case of * failure safe settings are assumed. */ mgmt_send(mgmt_primary, MGMT_OP_READ_COMMANDS, MGMT_INDEX_NONE, 0, NULL, read_commands_complete, NULL, NULL); mgmt_register(mgmt_primary, MGMT_EV_INDEX_ADDED, MGMT_INDEX_NONE, index_added, NULL, NULL); mgmt_register(mgmt_primary, MGMT_EV_INDEX_REMOVED, MGMT_INDEX_NONE, index_removed, NULL, NULL); DBG("sending read index list command"); if (mgmt_send(mgmt_primary, MGMT_OP_READ_INDEX_LIST, MGMT_INDEX_NONE, 0, NULL, read_index_list_complete, NULL, NULL) > 0) return; error("Failed to read controller index list"); } static void mgmt_debug(const char *str, void *user_data) { DBG_IDX(0xffff, "%s", str); } int adapter_init(void) { dbus_conn = btd_get_dbus_connection(); mgmt_primary = mgmt_new_default(); if (!mgmt_primary) { error("Failed to access management interface"); return -EIO; } mgmt_set_debug(mgmt_primary, mgmt_debug, NULL, NULL); DBG("sending read version command"); if (mgmt_send(mgmt_primary, MGMT_OP_READ_VERSION, MGMT_INDEX_NONE, 0, NULL, read_version_complete, NULL, NULL) > 0) return 0; error("Failed to read management version information"); return -EIO; } void adapter_cleanup(void) { g_list_free(adapter_list); while (adapters) { struct btd_adapter *adapter = adapters->data; adapter_remove(adapter); adapters = g_slist_remove(adapters, adapter); btd_adapter_unref(adapter); } /* * In case there is another reference active, clear out * registered handlers for index added and index removed. * * This is just an extra precaution to be safe, and in * reality should not make a difference. */ mgmt_unregister_index(mgmt_primary, MGMT_INDEX_NONE); /* * In case there is another reference active, cancel * all pending global commands. * * This is just an extra precaution to avoid callbacks * that potentially then could leak memory or access * an invalid structure. */ mgmt_cancel_index(mgmt_primary, MGMT_INDEX_NONE); mgmt_unref(mgmt_primary); mgmt_primary = NULL; dbus_conn = NULL; } void adapter_shutdown(void) { GList *list; DBG(""); powering_down = true; for (list = g_list_first(adapter_list); list; list = g_list_next(list)) { struct btd_adapter *adapter = list->data; if (!(adapter->current_settings & MGMT_SETTING_POWERED)) continue; clear_discoverable(adapter); remove_temporary_devices(adapter); set_mode(adapter, MGMT_OP_SET_POWERED, 0x00); adapter_remaining++; } if (!adapter_remaining) btd_exit(); } /* * Check if workaround for broken ATT server socket behavior is needed * where we need to connect an ATT client socket before pairing to get * early access to the ATT channel. */ bool btd_le_connect_before_pairing(void) { if (MGMT_VERSION(mgmt_version, mgmt_revision) < MGMT_VERSION(1, 4)) return true; return false; } bool btd_adapter_has_settings(struct btd_adapter *adapter, uint32_t settings) { if (!adapter) return false; return (adapter->current_settings & settings) ? true : false; } bool btd_has_kernel_features(uint32_t features) { return (kernel_features & features) ? true : false; } bool btd_adapter_has_exp_feature(struct btd_adapter *adapter, uint32_t feature) { size_t i; for (i = 0; i < ARRAY_SIZE(exp_table); i++) { const struct exp_feat *feat = &exp_table[i]; if ((feat->flag & feature) && queue_find(adapter->exps, NULL, feat->uuid->val)) return true; } return false; } void btd_adapter_cancel_service_auth(struct btd_adapter *adapter, struct btd_device *device) { GList *l; l = adapter->auths->head; while (l != NULL) { struct service_auth *auth = l->data; GList *next = g_list_next(l); if (auth->device != device) { l = next; continue; } g_queue_delete_link(adapter->auths, l); l = next; service_auth_cancel(auth); } } unsigned int btd_adapter_send_cmd_event_sync(struct btd_adapter *adapter, uint16_t opcode, uint16_t length, const void *param, uint8_t event, btd_adapter_reply_event_t callback, void *user_data, btd_adapter_destroy_func_t destroy, uint8_t timeout) { struct { struct mgmt_cp_hci_cmd_sync cp; uint8_t data[UINT8_MAX]; } pkt; memset(&pkt, 0, sizeof(pkt)); pkt.cp.opcode = cpu_to_le16(opcode); pkt.cp.event = event; pkt.cp.timeout = timeout; pkt.cp.params_len = cpu_to_le16(length); if (length) memcpy(pkt.data, param, length); return mgmt_send(adapter->mgmt, MGMT_OP_HCI_CMD_SYNC, adapter->dev_id, sizeof(pkt.cp) + length, &pkt, callback, user_data, destroy); } bluez-5.87/src/PaxHeaders/sdpd-request.c0000644000000000000000000000005015200607060015222 xustar0020 atime=1783092683 20 ctime=1783094106 bluez-5.87/src/sdpd-request.c0000644000000000000000000007035015200607060014710 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2001-2002 Nokia Corporation * Copyright (C) 2002-2003 Maxim Krasnyansky * Copyright (C) 2002-2010 Marcel Holtmann * Copyright (C) 2002-2003 Stephen Crane * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/l2cap.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "src/shared/util.h" #include "sdpd.h" #include "log.h" typedef struct { uint32_t timestamp; union { uint16_t maxBytesSent; uint16_t lastIndexSent; } cStateValue; } sdp_cont_state_t; #define SDP_CONT_STATE_SIZE (sizeof(uint8_t) + sizeof(sdp_cont_state_t)) typedef struct sdp_cont_info sdp_cont_info_t; struct sdp_cont_info { int sock; uint8_t opcode; uint32_t timestamp; sdp_buf_t buf; }; static sdp_list_t *cstates; static int cstate_match(const void *data, const void *user_data) { const sdp_cont_info_t *cinfo = data; const sdp_cont_state_t *cstate = user_data; /* Check timestamp */ return cinfo->timestamp - cstate->timestamp; } static void sdp_cont_info_free(sdp_cont_info_t *cinfo) { if (!cinfo) return; cstates = sdp_list_remove(cstates, cinfo); free(cinfo->buf.data); free(cinfo); } static sdp_cont_info_t *sdp_get_cont_info(sdp_req_t *req, sdp_cont_state_t *cstate) { sdp_list_t *list; list = sdp_list_find(cstates, cstate, cstate_match); if (list) { sdp_cont_info_t *cinfo = list->data; if (cinfo->opcode == req->opcode) return cinfo; /* Cleanup continuation if the opcode doesn't match since its * response buffer shall only be valid for the original requests */ sdp_cont_info_free(cinfo); return NULL; } /* Cleanup cstates if no continuation info could be found */ sdp_cstate_cleanup(req->sock); return NULL; } static uint32_t sdp_cstate_alloc_buf(sdp_req_t *req, sdp_buf_t *buf) { sdp_cont_info_t *cinfo = malloc(sizeof(sdp_cont_info_t)); uint8_t *data = malloc(buf->data_size); memcpy(data, buf->data, buf->data_size); memset(cinfo, 0, sizeof(sdp_cont_info_t)); cinfo->buf.data = data; cinfo->buf.data_size = buf->data_size; cinfo->buf.buf_size = buf->data_size; cinfo->timestamp = sdp_get_time(); cinfo->sock = req->sock; cinfo->opcode = req->opcode; cstates = sdp_list_append(cstates, cinfo); return cinfo->timestamp; } /* Additional values for checking datatype (not in spec) */ #define SDP_TYPE_UUID 0xfe #define SDP_TYPE_ATTRID 0xff struct attrid { uint8_t dtd; union { uint16_t uint16; uint32_t uint32; }; }; /* * Generic data element sequence extractor. Builds * a list whose elements are those found in the * sequence. The data type of elements found in the * sequence is returned in the reference pDataType */ static int extract_des(uint8_t *buf, int len, sdp_list_t **svcReqSeq, uint8_t *pDataType, uint8_t expectedType) { uint8_t seqType; int scanned, data_size = 0; short numberOfElements = 0; int seqlen = 0; sdp_list_t *pSeq = NULL; uint8_t dataType; int status = 0; const uint8_t *p; size_t bufsize; scanned = sdp_extract_seqtype(buf, len, &seqType, &data_size); SDPDBG("Seq type : %d", seqType); if (!scanned || (seqType != SDP_SEQ8 && seqType != SDP_SEQ16)) { error("Unknown seq type"); return -1; } p = buf + scanned; bufsize = len - scanned; SDPDBG("Data size : %d", data_size); for (;;) { char *pElem = NULL; int localSeqLength = 0; uuid_t *puuid; if (bufsize < sizeof(uint8_t)) { SDPDBG("->Unexpected end of buffer"); goto failed; } dataType = *p; SDPDBG("Data type: 0x%02x", dataType); if (expectedType == SDP_TYPE_UUID) { if (dataType != SDP_UUID16 && dataType != SDP_UUID32 && dataType != SDP_UUID128) { SDPDBG("->Unexpected Data type (expected UUID_ANY)"); goto failed; } } else if (expectedType == SDP_TYPE_ATTRID && (dataType != SDP_UINT16 && dataType != SDP_UINT32)) { SDPDBG("->Unexpected Data type (expected 0x%02x or 0x%02x)", SDP_UINT16, SDP_UINT32); goto failed; } else if (expectedType != SDP_TYPE_ATTRID && dataType != expectedType) { SDPDBG("->Unexpected Data type (expected 0x%02x)", expectedType); goto failed; } switch (dataType) { case SDP_UINT16: p += sizeof(uint8_t); seqlen += sizeof(uint8_t); bufsize -= sizeof(uint8_t); if (bufsize < sizeof(uint16_t)) { SDPDBG("->Unexpected end of buffer"); goto failed; } if (expectedType == SDP_TYPE_ATTRID) { struct attrid *aid; aid = malloc(sizeof(struct attrid)); aid->dtd = dataType; aid->uint16 = get_be16(p); pElem = (char *) aid; } else { uint16_t tmp; memcpy(&tmp, p, sizeof(tmp)); pElem = malloc(sizeof(uint16_t)); put_be16(tmp, pElem); } p += sizeof(uint16_t); seqlen += sizeof(uint16_t); bufsize -= sizeof(uint16_t); break; case SDP_UINT32: p += sizeof(uint8_t); seqlen += sizeof(uint8_t); bufsize -= sizeof(uint8_t); if (bufsize < (int)sizeof(uint32_t)) { SDPDBG("->Unexpected end of buffer"); goto failed; } if (expectedType == SDP_TYPE_ATTRID) { struct attrid *aid; aid = malloc(sizeof(struct attrid)); aid->dtd = dataType; aid->uint32 = get_be32(p); pElem = (char *) aid; } else { uint32_t tmp; memcpy(&tmp, p, sizeof(tmp)); pElem = malloc(sizeof(uint32_t)); put_be32(tmp, pElem); } p += sizeof(uint32_t); seqlen += sizeof(uint32_t); bufsize -= sizeof(uint32_t); break; case SDP_UUID16: case SDP_UUID32: case SDP_UUID128: puuid = malloc(sizeof(uuid_t)); status = sdp_uuid_extract(p, bufsize, puuid, &localSeqLength); if (status < 0) { free(puuid); goto failed; } pElem = (char *) puuid; seqlen += localSeqLength; p += localSeqLength; bufsize -= localSeqLength; break; default: return -1; } if (status == 0) { pSeq = sdp_list_append(pSeq, pElem); numberOfElements++; SDPDBG("No of elements : %d", numberOfElements); (void)numberOfElements; if (seqlen == data_size) break; else if (seqlen > data_size || seqlen > len) goto failed; } else free(pElem); } *svcReqSeq = pSeq; scanned += seqlen; *pDataType = dataType; return scanned; failed: sdp_list_free(pSeq, free); return -1; } static int sdp_set_cstate_pdu(sdp_buf_t *buf, sdp_cont_state_t *cstate) { uint8_t *pdata = buf->data + buf->data_size; int length = 0; if (cstate) { SDPDBG("Non null sdp_cstate_t id : 0x%x", cstate->timestamp); *pdata = sizeof(sdp_cont_state_t); pdata += sizeof(uint8_t); length += sizeof(uint8_t); memcpy(pdata, cstate, sizeof(sdp_cont_state_t)); length += sizeof(sdp_cont_state_t); } else { /* set "null" continuation state */ *pdata = 0; length += sizeof(uint8_t); } buf->data_size += length; return length; } static int sdp_cstate_get(sdp_req_t *req, uint8_t *buffer, size_t len, sdp_cont_state_t **cstate, sdp_cont_info_t **cinfo) { uint8_t cStateSize = *buffer; SDPDBG("Continuation State size : %d", cStateSize); if (cStateSize == 0) { /* Cleanup cstates if request doesn't contain a cstate */ sdp_cstate_cleanup(req->sock); *cstate = NULL; return 0; } buffer++; len--; if (len < sizeof(sdp_cont_state_t)) return -EINVAL; /* * Check if continuation state exists, if yes attempt * to get response remainder from cache, else send error */ *cstate = malloc(sizeof(sdp_cont_state_t)); if (!(*cstate)) return -ENOMEM; memcpy(*cstate, buffer, sizeof(sdp_cont_state_t)); SDPDBG("Cstate TS : 0x%x", (*cstate)->timestamp); SDPDBG("Bytes sent : %d", (*cstate)->cStateValue.maxBytesSent); *cinfo = sdp_get_cont_info(req, *cstate); return 0; } /* * The matching process is defined as "each and every UUID * specified in the "search pattern" must be present in the * "target pattern". Here "search pattern" is the set of UUIDs * specified by the service discovery client and "target pattern" * is the set of UUIDs present in a service record. * * Return 1 if each and every UUID in the search * pattern exists in the target pattern, 0 if the * match succeeds and -1 on error. */ static int sdp_match_uuid(sdp_list_t *search, sdp_list_t *pattern) { /* * The target is a sorted list, so we need not look * at all elements to confirm existence of an element * from the search pattern */ int patlen = sdp_list_len(pattern); if (patlen < sdp_list_len(search)) return -1; for (; search; search = search->next) { uuid_t *uuid128; void *data = search->data; sdp_list_t *list; if (data == NULL) return -1; /* create 128-bit form of the search UUID */ uuid128 = sdp_uuid_to_uuid128((uuid_t *)data); list = sdp_list_find(pattern, uuid128, sdp_uuid128_cmp); bt_free(uuid128); if (!list) return 0; } return 1; } /* * Service search request PDU. This method extracts the search pattern * (a sequence of UUIDs) and calls the matching function * to find matching services */ static int service_search_req(sdp_req_t *req, sdp_buf_t *buf) { int status = 0, i, plen, mlen, mtu, scanned; sdp_list_t *pattern = NULL; uint16_t expected, actual, rsp_count = 0; uint8_t dtd; sdp_cont_state_t *cstate = NULL; sdp_cont_info_t *cinfo = NULL; uint8_t *pCacheBuffer = NULL; int handleSize = 0; uint32_t cStateId = 0; uint8_t *pTotalRecordCount, *pCurrentRecordCount; uint8_t *pdata = req->buf + sizeof(sdp_pdu_hdr_t); size_t data_left = req->len - sizeof(sdp_pdu_hdr_t); scanned = extract_des(pdata, data_left, &pattern, &dtd, SDP_TYPE_UUID); if (scanned == -1) { status = SDP_INVALID_SYNTAX; goto done; } pdata += scanned; data_left -= scanned; plen = ntohs(((sdp_pdu_hdr_t *)(req->buf))->plen); mlen = scanned + sizeof(uint16_t) + 1; /* ensure we don't read past buffer */ if (plen < mlen || plen != mlen + *(uint8_t *)(pdata+sizeof(uint16_t))) { status = SDP_INVALID_SYNTAX; goto done; } if (data_left < sizeof(uint16_t)) { status = SDP_INVALID_SYNTAX; goto done; } expected = get_be16(pdata); SDPDBG("Expected count: %d", expected); SDPDBG("Bytes scanned : %d", scanned); pdata += sizeof(uint16_t); data_left -= sizeof(uint16_t); /* * Check if continuation state exists, if yes attempt * to get rsp remainder from continuation info, else send error */ if (sdp_cstate_get(req, pdata, data_left, &cstate, &cinfo) < 0) { status = SDP_INVALID_SYNTAX; goto done; } mtu = req->mtu - sizeof(sdp_pdu_hdr_t) - sizeof(uint16_t) - sizeof(uint16_t) - SDP_CONT_STATE_SIZE; actual = MIN(expected, mtu >> 2); /* make space in the rsp buffer for total and current record counts */ pdata = buf->data; /* total service record count = 0 */ pTotalRecordCount = pdata; put_be16(0, pdata); pdata += sizeof(uint16_t); buf->data_size += sizeof(uint16_t); /* current service record count = 0 */ pCurrentRecordCount = pdata; put_be16(0, pdata); pdata += sizeof(uint16_t); buf->data_size += sizeof(uint16_t); if (cstate == NULL) { /* for every record in the DB, do a pattern search */ sdp_list_t *list = sdp_get_record_list(); handleSize = 0; for (; list && rsp_count < expected; list = list->next) { sdp_record_t *rec = list->data; SDPDBG("Checking svcRec : 0x%x", rec->handle); if (sdp_match_uuid(pattern, rec->pattern) > 0 && sdp_check_access(rec->handle, &req->device)) { rsp_count++; put_be32(rec->handle, pdata); pdata += sizeof(uint32_t); handleSize += sizeof(uint32_t); } } SDPDBG("Match count: %d", rsp_count); buf->data_size += handleSize; put_be16(rsp_count, pTotalRecordCount); put_be16(rsp_count, pCurrentRecordCount); if (rsp_count > actual) { /* cache the rsp and generate a continuation state */ cStateId = sdp_cstate_alloc_buf(req, buf); /* * subtract handleSize since we now send only * a subset of handles */ buf->data_size -= handleSize; } else { /* NULL continuation state */ sdp_cont_info_free(cinfo); sdp_set_cstate_pdu(buf, NULL); } } /* under both the conditions below, the rsp buffer is not built yet */ if (cstate || cStateId > 0) { short lastIndex = 0; if (cstate) { if (cinfo) { /* Check if requesting more than available */ if (cstate->cStateValue.maxBytesSent >= cinfo->buf.data_size) { status = SDP_INVALID_CSTATE; goto done; } pCacheBuffer = cinfo->buf.data; /* get the rsp_count from the cached buffer */ rsp_count = get_be16(pCacheBuffer); /* get index of the last sdp_record_t sent */ lastIndex = cstate->cStateValue.lastIndexSent; } else { status = SDP_INVALID_CSTATE; goto done; } } else { pCacheBuffer = buf->data; lastIndex = 0; } /* * Set the local buffer pointer to after the * current record count and increment the cached * buffer pointer to beyond the counters */ pdata = pCurrentRecordCount + sizeof(uint16_t); /* increment beyond the totalCount and the currentCount */ pCacheBuffer += 2 * sizeof(uint16_t); if (cstate) { handleSize = 0; for (i = lastIndex; (i - lastIndex) < actual && i < rsp_count; i++) { memcpy(pdata, pCacheBuffer + i * sizeof(uint32_t), sizeof(uint32_t)); pdata += sizeof(uint32_t); handleSize += sizeof(uint32_t); } } else { handleSize = actual << 2; i = actual; } buf->data_size += handleSize; put_be16(rsp_count, pTotalRecordCount); put_be16(i - lastIndex, pCurrentRecordCount); if (i == rsp_count) { /* set "null" continuationState */ sdp_set_cstate_pdu(buf, NULL); sdp_cont_info_free(cinfo); } else { /* * there's more: set lastIndexSent to * the new value and move on */ sdp_cont_state_t newState; SDPDBG("Setting non-NULL sdp_cstate_t"); if (cstate) memcpy(&newState, cstate, sizeof(sdp_cont_state_t)); else { memset(&newState, 0, sizeof(sdp_cont_state_t)); newState.timestamp = cStateId; } newState.cStateValue.lastIndexSent = i; sdp_set_cstate_pdu(buf, &newState); } } done: free(cstate); if (pattern) sdp_list_free(pattern, free); return status; } /* * Extract attribute identifiers from the request PDU. * Clients could request a subset of attributes (by id) * from a service record, instead of the whole set. The * requested identifiers are present in the PDU form of * the request */ static int extract_attrs(sdp_record_t *rec, sdp_list_t *seq, sdp_buf_t *buf) { sdp_buf_t pdu; if (!rec) return SDP_INVALID_RECORD_HANDLE; if (seq == NULL) { SDPDBG("Attribute sequence is NULL"); return 0; } SDPDBG("Entries in attr seq : %d", sdp_list_len(seq)); sdp_gen_record_pdu(rec, &pdu); for (; seq; seq = seq->next) { struct attrid *aid = seq->data; SDPDBG("AttrDataType : %d", aid->dtd); if (aid->dtd == SDP_UINT16) { uint16_t attr = aid->uint16; sdp_data_t *a = sdp_data_get(rec, attr); if (a) sdp_append_to_pdu(buf, a); } else if (aid->dtd == SDP_UINT32) { uint32_t range = aid->uint32; uint16_t attr; uint16_t low = (0xffff0000 & range) >> 16; uint16_t high = 0x0000ffff & range; sdp_data_t *data; SDPDBG("attr range : 0x%x", range); SDPDBG("Low id : 0x%x", low); SDPDBG("High id : 0x%x", high); if (low == 0x0000 && high == 0xffff && pdu.data_size <= buf->buf_size) { /* copy it */ memcpy(buf->data, pdu.data, pdu.data_size); buf->data_size = pdu.data_size; break; } /* (else) sub-range of attributes */ for (attr = low; attr < high; attr++) { data = sdp_data_get(rec, attr); if (data) sdp_append_to_pdu(buf, data); } data = sdp_data_get(rec, high); if (data) sdp_append_to_pdu(buf, data); } else { error("Unexpected data type : 0x%x", aid->dtd); error("Expect uint16_t or uint32_t"); free(pdu.data); return SDP_INVALID_SYNTAX; } } free(pdu.data); return 0; } /* Build cstate response */ static int sdp_cstate_rsp(sdp_cont_info_t *cinfo, sdp_cont_state_t *cstate, sdp_buf_t *buf, uint16_t max) { sdp_buf_t *cache; uint16_t sent; if (!cinfo) return 0; if (cstate->cStateValue.maxBytesSent >= cinfo->buf.data_size) { sdp_cont_info_free(cinfo); return 0; } cache = &cinfo->buf; sent = MIN(max, cache->data_size - cstate->cStateValue.maxBytesSent); memcpy(buf->data, cache->data + cstate->cStateValue.maxBytesSent, sent); buf->data_size += sent; cstate->cStateValue.maxBytesSent += sent; SDPDBG("Response size : %d sending now : %d bytes sent so far : %d", cache->data_size, sent, cstate->cStateValue.maxBytesSent); if (cstate->cStateValue.maxBytesSent == cache->data_size) { sdp_cont_info_free(cinfo); return sdp_set_cstate_pdu(buf, NULL); } return sdp_set_cstate_pdu(buf, cstate); } /* * A request for the attributes of a service record. * First check if the service record (specified by * service record handle) exists, then call the attribute * streaming function */ static int service_attr_req(sdp_req_t *req, sdp_buf_t *buf) { sdp_cont_state_t *cstate = NULL; sdp_cont_info_t *cinfo = NULL; short cstate_size = 0; sdp_list_t *seq = NULL; uint8_t dtd = 0; int scanned = 0; unsigned int max_rsp_size; int status = 0, plen, mlen; uint8_t *pdata = req->buf + sizeof(sdp_pdu_hdr_t); size_t data_left = req->len - sizeof(sdp_pdu_hdr_t); uint32_t handle; if (data_left < sizeof(uint32_t)) { status = SDP_INVALID_SYNTAX; goto done; } handle = get_be32(pdata); pdata += sizeof(uint32_t); data_left -= sizeof(uint32_t); if (data_left < sizeof(uint16_t)) { status = SDP_INVALID_SYNTAX; goto done; } max_rsp_size = get_be16(pdata); pdata += sizeof(uint16_t); data_left -= sizeof(uint16_t); if (data_left < sizeof(sdp_pdu_hdr_t)) { status = SDP_INVALID_SYNTAX; goto done; } /* extract the attribute list */ scanned = extract_des(pdata, data_left, &seq, &dtd, SDP_TYPE_ATTRID); if (scanned == -1) { status = SDP_INVALID_SYNTAX; goto done; } pdata += scanned; data_left -= scanned; plen = ntohs(((sdp_pdu_hdr_t *)(req->buf))->plen); mlen = scanned + sizeof(uint32_t) + sizeof(uint16_t) + 1; /* ensure we don't read past buffer */ if (plen < mlen || plen != mlen + *(uint8_t *)pdata) { status = SDP_INVALID_PDU_SIZE; goto done; } /* * if continuation state exists, attempt * to get rsp remainder from cache, else send error */ if (sdp_cstate_get(req, pdata, data_left, &cstate, &cinfo) < 0) { status = SDP_INVALID_SYNTAX; goto done; } SDPDBG("SvcRecHandle : 0x%x", handle); SDPDBG("max_rsp_size : %d", max_rsp_size); /* * Check that max_rsp_size is within valid range * a minimum size of 0x0007 has to be used for data field */ if (max_rsp_size < 0x0007) { status = SDP_INVALID_SYNTAX; goto done; } /* * Calculate Attribute size according to MTU * We can send only (MTU - sizeof(sdp_pdu_hdr_t) - sizeof(sdp_cont_state_t)) */ max_rsp_size = MIN(max_rsp_size, req->mtu - sizeof(sdp_pdu_hdr_t) - sizeof(uint32_t) - SDP_CONT_STATE_SIZE - sizeof(uint16_t)); /* pull header for AttributeList byte count */ buf->data += sizeof(uint16_t); buf->buf_size -= sizeof(uint16_t); if (cstate) { cstate_size = sdp_cstate_rsp(cinfo, cstate, buf, max_rsp_size); if (!cstate_size) { status = SDP_INVALID_CSTATE; error("NULL cache buffer and non-NULL continuation state"); } } else { sdp_record_t *rec = sdp_record_find(handle); status = extract_attrs(rec, seq, buf); if (buf->data_size > max_rsp_size) { sdp_cont_state_t newState; memset((char *)&newState, 0, sizeof(sdp_cont_state_t)); newState.timestamp = sdp_cstate_alloc_buf(req, buf); /* * Reset the buffer size to the maximum expected and * set the sdp_cont_state_t */ SDPDBG("Creating continuation state of size : %d", buf->data_size); buf->data_size = max_rsp_size; newState.cStateValue.maxBytesSent = max_rsp_size; cstate_size = sdp_set_cstate_pdu(buf, &newState); } else { if (buf->data_size == 0) sdp_append_to_buf(buf, NULL, 0); cstate_size = sdp_set_cstate_pdu(buf, NULL); } } /* push header */ buf->data -= sizeof(uint16_t); buf->buf_size += sizeof(uint16_t); done: free(cstate); if (seq) sdp_list_free(seq, free); if (status) return status; /* set attribute list byte count */ put_be16(buf->data_size - cstate_size, buf->data); buf->data_size += sizeof(uint16_t); return 0; } /* * combined service search and attribute extraction */ static int service_search_attr_req(sdp_req_t *req, sdp_buf_t *buf) { int status = 0, plen, totscanned; uint8_t *pdata; unsigned int max; int scanned, rsp_count = 0; sdp_list_t *pattern = NULL, *seq = NULL, *svcList; sdp_cont_state_t *cstate = NULL; sdp_cont_info_t *cinfo = NULL; short cstate_size = 0; uint8_t dtd = 0; sdp_buf_t tmpbuf; size_t data_left; tmpbuf.data = NULL; pdata = req->buf + sizeof(sdp_pdu_hdr_t); data_left = req->len - sizeof(sdp_pdu_hdr_t); scanned = extract_des(pdata, data_left, &pattern, &dtd, SDP_TYPE_UUID); if (scanned == -1) { status = SDP_INVALID_SYNTAX; goto done; } totscanned = scanned; SDPDBG("Bytes scanned: %d", scanned); pdata += scanned; data_left -= scanned; if (data_left < sizeof(uint16_t)) { status = SDP_INVALID_SYNTAX; goto done; } max = get_be16(pdata); pdata += sizeof(uint16_t); data_left -= sizeof(uint16_t); SDPDBG("Max Attr expected: %d", max); if (data_left < sizeof(sdp_pdu_hdr_t)) { status = SDP_INVALID_SYNTAX; goto done; } /* extract the attribute list */ scanned = extract_des(pdata, data_left, &seq, &dtd, SDP_TYPE_ATTRID); if (scanned == -1) { status = SDP_INVALID_SYNTAX; goto done; } pdata += scanned; data_left -= scanned; totscanned += scanned + sizeof(uint16_t) + 1; plen = ntohs(((sdp_pdu_hdr_t *)(req->buf))->plen); if (plen < totscanned || plen != totscanned + *(uint8_t *)pdata) { status = SDP_INVALID_PDU_SIZE; goto done; } /* * if continuation state exists attempt * to get rsp remainder from cache, else send error */ if (sdp_cstate_get(req, pdata, data_left, &cstate, &cinfo) < 0) { status = SDP_INVALID_SYNTAX; goto done; } svcList = sdp_get_record_list(); tmpbuf.data = malloc(USHRT_MAX); tmpbuf.data_size = 0; tmpbuf.buf_size = USHRT_MAX; memset(tmpbuf.data, 0, USHRT_MAX); /* * Calculate Attribute size according to MTU * We can send only (MTU - sizeof(sdp_pdu_hdr_t) - sizeof(sdp_cont_state_t)) */ max = MIN(max, req->mtu - sizeof(sdp_pdu_hdr_t) - SDP_CONT_STATE_SIZE - sizeof(uint16_t)); /* pull header for AttributeList byte count */ buf->data += sizeof(uint16_t); buf->buf_size -= sizeof(uint16_t); if (cstate == NULL) { /* no continuation state -> create new response */ sdp_list_t *p; for (p = svcList; p; p = p->next) { sdp_record_t *rec = p->data; if (sdp_match_uuid(pattern, rec->pattern) > 0 && sdp_check_access(rec->handle, &req->device)) { rsp_count++; status = extract_attrs(rec, seq, &tmpbuf); SDPDBG("Response count : %d", rsp_count); SDPDBG("Local PDU size : %d", tmpbuf.data_size); if (status) { SDPDBG("Extract attr from record returns err"); break; } if (buf->data_size + tmpbuf.data_size < buf->buf_size) { /* to be sure no relocations */ sdp_append_to_buf(buf, tmpbuf.data, tmpbuf.data_size); tmpbuf.data_size = 0; memset(tmpbuf.data, 0, USHRT_MAX); } else { error("Relocation needed"); break; } SDPDBG("Net PDU size : %d", buf->data_size); } } if (buf->data_size > max) { sdp_cont_state_t newState; memset((char *)&newState, 0, sizeof(sdp_cont_state_t)); newState.timestamp = sdp_cstate_alloc_buf(req, buf); /* * Reset the buffer size to the maximum expected and * set the sdp_cont_state_t */ buf->data_size = max; newState.cStateValue.maxBytesSent = max; cstate_size = sdp_set_cstate_pdu(buf, &newState); } else cstate_size = sdp_set_cstate_pdu(buf, NULL); } else { cstate_size = sdp_cstate_rsp(cinfo, cstate, buf, max); if (!cstate_size) { status = SDP_INVALID_CSTATE; SDPDBG("Non-null continuation state, but null cache buffer"); } } if (!rsp_count && !cstate) { /* found nothing */ buf->data_size = 0; sdp_append_to_buf(buf, tmpbuf.data, tmpbuf.data_size); sdp_set_cstate_pdu(buf, NULL); } /* push header */ buf->data -= sizeof(uint16_t); buf->buf_size += sizeof(uint16_t); if (!status) { /* set attribute list byte count */ put_be16(buf->data_size - cstate_size, buf->data); buf->data_size += sizeof(uint16_t); } done: free(cstate); free(tmpbuf.data); if (pattern) sdp_list_free(pattern, free); if (seq) sdp_list_free(seq, free); return status; } /* * Top level request processor. Calls the appropriate processing * function based on request type. Handles service registration * client requests also. */ static void process_request(sdp_req_t *req) { sdp_pdu_hdr_t *reqhdr = (sdp_pdu_hdr_t *)req->buf; sdp_pdu_hdr_t *rsphdr; sdp_buf_t rsp; uint8_t *buf = malloc(USHRT_MAX); int status = SDP_INVALID_SYNTAX; memset(buf, 0, USHRT_MAX); rsp.data = buf + sizeof(sdp_pdu_hdr_t); rsp.data_size = 0; rsp.buf_size = USHRT_MAX - sizeof(sdp_pdu_hdr_t); rsphdr = (sdp_pdu_hdr_t *)buf; if (ntohs(reqhdr->plen) != req->len - sizeof(sdp_pdu_hdr_t)) { status = SDP_INVALID_PDU_SIZE; goto send_rsp; } req->opcode = reqhdr->pdu_id; switch (reqhdr->pdu_id) { case SDP_SVC_SEARCH_REQ: SDPDBG("Got a svc srch req"); status = service_search_req(req, &rsp); rsphdr->pdu_id = SDP_SVC_SEARCH_RSP; break; case SDP_SVC_ATTR_REQ: SDPDBG("Got a svc attr req"); status = service_attr_req(req, &rsp); rsphdr->pdu_id = SDP_SVC_ATTR_RSP; break; case SDP_SVC_SEARCH_ATTR_REQ: SDPDBG("Got a svc srch attr req"); status = service_search_attr_req(req, &rsp); rsphdr->pdu_id = SDP_SVC_SEARCH_ATTR_RSP; break; /* Following requests are allowed only for local connections */ case SDP_SVC_REGISTER_REQ: SDPDBG("Service register request"); if (req->local) { status = service_register_req(req, &rsp); rsphdr->pdu_id = SDP_SVC_REGISTER_RSP; } break; case SDP_SVC_UPDATE_REQ: SDPDBG("Service update request"); if (req->local) { status = service_update_req(req, &rsp); rsphdr->pdu_id = SDP_SVC_UPDATE_RSP; } break; case SDP_SVC_REMOVE_REQ: SDPDBG("Service removal request"); if (req->local) { status = service_remove_req(req, &rsp); rsphdr->pdu_id = SDP_SVC_REMOVE_RSP; } break; default: error("Unknown PDU ID : 0x%x received", reqhdr->pdu_id); status = SDP_INVALID_SYNTAX; break; } send_rsp: if (status) { /* Cleanup cstates on error */ sdp_cstate_cleanup(req->sock); rsphdr->pdu_id = SDP_ERROR_RSP; put_be16(status, rsp.data); rsp.data_size = sizeof(uint16_t); } SDPDBG("Sending rsp. status %d", status); rsphdr->tid = reqhdr->tid; rsphdr->plen = htons(rsp.data_size); /* point back to the real buffer start and set the real rsp length */ rsp.data_size += sizeof(sdp_pdu_hdr_t); rsp.data = buf; /* stream the rsp PDU */ if (send(req->sock, rsp.data, rsp.data_size, 0) < 0) error("send: %s (%d)", strerror(errno), errno); SDPDBG("Bytes Sent : %d", rsp.data_size); free(rsp.data); free(req->buf); } void handle_internal_request(int sk, int mtu, void *data, int len) { sdp_req_t req; bacpy(&req.device, BDADDR_ANY); bacpy(&req.bdaddr, BDADDR_LOCAL); req.local = 0; req.sock = sk; req.mtu = mtu; req.flags = 0; req.buf = data; req.len = len; process_request(&req); } void handle_request(int sk, uint8_t *data, int len) { struct sockaddr_l2 sa; socklen_t size; sdp_req_t req; size = sizeof(sa); if (getpeername(sk, (struct sockaddr *) &sa, &size) < 0) { error("getpeername: %s", strerror(errno)); return; } if (sa.l2_family == AF_BLUETOOTH) { struct l2cap_options lo; memset(&lo, 0, sizeof(lo)); size = sizeof(lo); if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &lo, &size) < 0) { error("getsockopt: %s", strerror(errno)); return; } bacpy(&req.bdaddr, &sa.l2_bdaddr); req.mtu = lo.omtu; req.local = 0; memset(&sa, 0, sizeof(sa)); size = sizeof(sa); if (getsockname(sk, (struct sockaddr *) &sa, &size) < 0) { error("getsockname: %s", strerror(errno)); return; } bacpy(&req.device, &sa.l2_bdaddr); } else { bacpy(&req.device, BDADDR_ANY); bacpy(&req.bdaddr, BDADDR_LOCAL); req.mtu = 2048; req.local = 1; } req.sock = sk; req.buf = data; req.len = len; process_request(&req); } void sdp_cstate_cleanup(int sock) { sdp_list_t *list; /* Remove any cinfo for the client */ for (list = cstates; list;) { sdp_cont_info_t *cinfo = list->data; list = list->next; if (cinfo->sock != sock) continue; sdp_cont_info_free(cinfo); } } bluez-5.87/src/PaxHeaders/device.c0000644000000000000000000000005015221752714014053 xustar0020 atime=1783092685 20 ctime=1783094106 bluez-5.87/src/device.c0000644000000000000000000060520515221752714013544 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright 2024 NXP * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "bluetooth/uuid.h" #include "gdbus/gdbus.h" #include "log.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/shared/gatt-server.h" #include "src/shared/ad.h" #include "src/shared/timeout.h" #include "btio/btio.h" #include "bluetooth/mgmt.h" #include "attrib/att.h" #include "btd.h" #include "adapter.h" #include "gatt-database.h" #include "attrib/gattrib.h" #include "device.h" #include "gatt-client.h" #include "profile.h" #include "service.h" #include "dbus-common.h" #include "error.h" #include "uuid-helper.h" #include "sdp-client.h" #include "attrib/gatt.h" #include "agent.h" #include "textfile.h" #include "storage.h" #include "eir.h" #include "settings.h" #include "set.h" #include "bearer.h" #define DISCONNECT_TIMER 2 #define DISCOVERY_TIMER 1 #define INVALID_FLAGS 0xff #define RSSI_THRESHOLD 8 #define AUTH_FAILURES_THRESHOLD 3 static DBusConnection *dbus_conn = NULL; static unsigned service_state_cb_id; struct btd_disconnect_data { guint id; disconnect_watch watch; void *user_data; GDestroyNotify destroy; }; struct bonding_req { DBusMessage *msg; guint listener_id; struct btd_device *device; uint8_t bdaddr_type; struct agent *agent; struct btd_adapter_pin_cb_iter *cb_iter; uint8_t status; guint retry_timer; struct timespec attempt_start_time; long last_attempt_duration_ms; }; typedef enum { AUTH_TYPE_PINCODE, AUTH_TYPE_PASSKEY, AUTH_TYPE_CONFIRM, AUTH_TYPE_NOTIFY_PASSKEY, AUTH_TYPE_NOTIFY_PINCODE, } auth_type_t; struct authentication_req { auth_type_t type; struct agent *agent; struct btd_device *device; uint8_t addr_type; uint32_t passkey; char *pincode; gboolean secure; }; enum { BROWSE_SDP, BROWSE_GATT }; struct browse_req { DBusMessage *msg; struct btd_device *device; uint8_t type; GSList *match_uuids; GSList *profiles_added; sdp_list_t *records; int search_uuid; int reconnect_attempt; guint listener_id; uint16_t sdp_flags; }; struct included_search { struct browse_req *req; GSList *services; GSList *current; }; struct svc_callback { unsigned int id; guint idle_id; struct btd_device *dev; device_svc_cb_t func; void *user_data; }; /* Per-bearer (LE or BR/EDR) device state */ struct bearer_state { bool prefer; bool paired; bool bonded; bool connected; bool svc_resolved; bool initiator; bool connectable; time_t last_seen; time_t last_used; }; struct ltk_info { uint8_t key[16]; bool central; uint8_t enc_size; }; struct csrk_info { uint8_t key[16]; uint32_t counter; bool auth; }; struct sirk_info { struct btd_device_set *set; uint8_t encrypted; uint8_t key[16]; uint8_t size; uint8_t rank; }; enum { WAKE_FLAG_DEFAULT = 0, WAKE_FLAG_ENABLED, WAKE_FLAG_DISABLED, }; enum { PREFER_LAST_USED = 0, PREFER_LE, PREFER_BREDR, PREFER_LAST_SEEN, }; struct btd_device { int ref_count; bdaddr_t conn_bdaddr; uint8_t conn_bdaddr_type; bdaddr_t bdaddr; uint8_t bdaddr_type; bool privacy; uint8_t *irk; char *path; struct btd_bearer *bredr; struct btd_bearer *le; bool pending_paired; /* "Paired" waiting for SDP */ bool svc_refreshed; bool refresh_discovery; /* Manage whether this device can wake the system from suspend. * - wake_support: Requires a profile that supports wake (i.e. HID) * - wake_allowed: Is wake currently allowed? * - pending_wake_allowed - Wake flag sent via set_device_flags * - wake_override - User configured wake setting */ bool wake_support; bool wake_allowed; bool pending_wake_allowed; uint8_t wake_override; GDBusPendingPropertySet wake_id; uint32_t supported_flags; uint32_t pending_flags; uint32_t current_flags; GSList *svc_callbacks; GSList *eir_uuids; struct bt_ad *ad; uint8_t ad_flags[1]; char name[MAX_NAME_LENGTH + 1]; char *alias; uint32_t class; uint16_t vendor_src; uint16_t vendor; uint16_t product; uint16_t version; uint16_t appearance; char *modalias; struct btd_adapter *adapter; GSList *uuids; GSList *primaries; /* List of primary services */ GSList *services; /* List of btd_service */ GSList *pending; /* Pending services */ GSList *watches; /* List of disconnect_data */ bool temporary; bool connectable; bool cable_pairing; unsigned int disconn_timer; unsigned int discov_timer; unsigned int temporary_timer; /* Temporary/disappear timer */ struct browse_req *browse; /* service discover request */ struct bonding_req *bonding; struct authentication_req *authr; /* authentication request */ uint8_t bonding_status; GSList *disconnects; /* disconnects message */ DBusMessage *connect; /* connect message */ DBusMessage *disconnect; /* disconnect message */ GAttrib *attrib; struct bt_att *att; /* The new ATT transport */ uint16_t att_mtu; /* The ATT MTU */ unsigned int att_disconn_id; /* * TODO: For now, device creates and owns the client-role gatt_db, but * this needs to be persisted in a more central place so that proper * attribute cache support can be built. */ struct gatt_db *db; /* GATT db cache */ unsigned int db_id; struct bt_gatt_client *client; /* GATT client instance */ struct bt_gatt_server *server; /* GATT server instance */ unsigned int gatt_ready_id; struct btd_gatt_client *client_dbus; uint8_t prefer_bearer; struct bearer_state bredr_state; struct bearer_state le_state; struct csrk_info *local_csrk; struct csrk_info *remote_csrk; struct ltk_info *ltk; struct queue *sirks; sdp_list_t *tmp_records; bool trusted; gboolean blocked; gboolean auto_connect; gboolean disable_auto_connect; gboolean general_connect; bool legacy; int8_t rssi; int8_t tx_power; GIOChannel *att_io; guint store_id; time_t name_resolve_failed_time; int8_t volume; uint32_t auth_failures; guint auth_retry_id; }; static const uint16_t uuid_list[] = { L2CAP_UUID, PNP_INFO_SVCLASS_ID, PUBLIC_BROWSE_GROUP, 0 }; static int device_browse_gatt(struct btd_device *device, DBusMessage *msg); static int device_browse_sdp(struct btd_device *device, DBusMessage *msg); static struct bearer_state *get_state(struct btd_device *dev, uint8_t bdaddr_type) { if (bdaddr_type == BDADDR_BREDR) return &dev->bredr_state; else return &dev->le_state; } bool btd_device_is_initiator(struct btd_device *dev) { if (dev->le_state.connected) return dev->le_state.initiator; else if (dev->bredr_state.connected) return dev->bredr_state.initiator; else if (dev->bonding) return true; return dev->att_io ? true : false; } static GSList *find_service_with_profile(GSList *list, struct btd_profile *p) { GSList *l; for (l = list; l != NULL; l = g_slist_next(l)) { struct btd_service *service = l->data; if (btd_service_get_profile(service) == p) return l; } return NULL; } static GSList *find_service_with_state(GSList *list, btd_service_state_t state) { GSList *l; for (l = list; l != NULL; l = g_slist_next(l)) { struct btd_service *service = l->data; if (btd_service_get_state(service) == state) return l; } return NULL; } static GSList *find_service_with_uuid(GSList *list, char *uuid) { GSList *l; for (l = list; l != NULL; l = g_slist_next(l)) { struct btd_service *service = l->data; struct btd_profile *profile = btd_service_get_profile(service); if (bt_uuid_strcmp(profile->remote_uuid, uuid) == 0) return l; } return NULL; } static const char *device_prefer_bearer_str(struct btd_device *device) { /* Check if both BR/EDR and LE bearer are supported */ if (!device->bredr || !device->le) return NULL; switch (device->prefer_bearer) { case PREFER_LAST_USED: return "last-used"; case PREFER_LE: return "le"; case PREFER_BREDR: return "bredr"; case PREFER_LAST_SEEN: return "last-seen"; } return NULL; } static bool device_set_prefer_bearer(struct btd_device *device, uint8_t bearer) { switch (bearer) { case PREFER_LAST_USED: device->prefer_bearer = PREFER_LAST_USED; return true; case PREFER_LE: device->prefer_bearer = PREFER_LE; device->le_state.prefer = true; device->bredr_state.prefer = false; return true; case PREFER_BREDR: device->prefer_bearer = PREFER_BREDR; device->bredr_state.prefer = true; device->le_state.prefer = false; return true; case PREFER_LAST_SEEN: device->prefer_bearer = PREFER_LAST_SEEN; device->bredr_state.prefer = false; device->le_state.prefer = false; return true; default: error("Unknown preferred bearer: %d", bearer); return false; } } static bool device_set_prefer_bearer_str(struct btd_device *device, const char *str) { if (!strcmp(str, "last-used")) return device_set_prefer_bearer(device, PREFER_LAST_USED); else if (!strcmp(str, "le")) return device_set_prefer_bearer(device, PREFER_LE); else if (!strcmp(str, "bredr")) return device_set_prefer_bearer(device, PREFER_BREDR); else if (!strcmp(str, "last-seen")) return device_set_prefer_bearer(device, PREFER_LAST_SEEN); error("Unknown preferred bearer: %s", str); return false; } static void update_technologies(GKeyFile *file, struct btd_device *dev) { const char *list[2]; size_t len = 0; const char *bearer; if (dev->bredr) list[len++] = "BR/EDR"; if (dev->le) { const char *type; if (dev->bdaddr_type == BDADDR_LE_PUBLIC) type = "public"; else type = "static"; g_key_file_set_string(file, "General", "AddressType", type); list[len++] = "LE"; } g_key_file_set_string_list(file, "General", "SupportedTechnologies", list, len); /* Store the PreferredBearer in case of dual-mode devices */ bearer = device_prefer_bearer_str(dev); if (bearer) { g_key_file_set_string(file, "General", "PreferredBearer", bearer); if (dev->prefer_bearer == PREFER_LAST_USED) { g_key_file_set_string(file, "General", "LastUsedBearer", dev->le_state.prefer ? "le" : "bredr"); } } } static void store_csrk(struct csrk_info *csrk, GKeyFile *key_file, const char *group) { char key[33]; int i; for (i = 0; i < 16; i++) sprintf(key + (i * 2), "%2.2X", csrk->key[i]); g_key_file_set_string(key_file, group, "Key", key); g_key_file_set_integer(key_file, group, "Counter", csrk->counter); g_key_file_set_boolean(key_file, group, "Authenticated", csrk->auth); } static void store_sirk(struct sirk_info *sirk, GKeyFile *key_file, uint8_t index) { char group[28]; char key[33]; int i; sprintf(group, "SetIdentityResolvingKey#%u", index); for (i = 0; i < 16; i++) sprintf(key + (i * 2), "%2.2X", sirk->key[i]); g_key_file_set_boolean(key_file, group, "Encrypted", sirk->encrypted); g_key_file_set_string(key_file, group, "Key", key); g_key_file_set_integer(key_file, group, "Size", sirk->size); g_key_file_set_integer(key_file, group, "Rank", sirk->rank); } static gboolean store_device_info_cb(gpointer user_data) { struct btd_device *device = user_data; GKeyFile *key_file; GError *gerr = NULL; char filename[PATH_MAX]; char device_addr[18]; char *str; char class[9]; char **uuids = NULL; gsize length = 0; device->store_id = 0; ba2str(&device->bdaddr, device_addr); create_filename(filename, PATH_MAX, "/%s/%s/info", btd_adapter_get_storage_dir(device->adapter), device_addr); create_file(filename, 0600); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_error_free(gerr); g_key_file_free(key_file); return FALSE; } g_key_file_set_string(key_file, "General", "Name", device->name); if (device->alias != NULL) g_key_file_set_string(key_file, "General", "Alias", device->alias); else g_key_file_remove_key(key_file, "General", "Alias", NULL); if (device->class) { sprintf(class, "0x%6.6x", device->class & 0xffffff); g_key_file_set_string(key_file, "General", "Class", class); } else { g_key_file_remove_key(key_file, "General", "Class", NULL); } if (device->appearance) { sprintf(class, "0x%4.4x", device->appearance); g_key_file_set_string(key_file, "General", "Appearance", class); } else { g_key_file_remove_key(key_file, "General", "Appearance", NULL); } update_technologies(key_file, device); g_key_file_set_boolean(key_file, "General", "Trusted", device->trusted); g_key_file_set_boolean(key_file, "General", "Blocked", device->blocked); g_key_file_set_boolean(key_file, "General", "CablePairing", device->cable_pairing); if (device->wake_override != WAKE_FLAG_DEFAULT) { g_key_file_set_boolean(key_file, "General", "WakeAllowed", device->wake_override == WAKE_FLAG_ENABLED); } if (device->uuids) { GSList *l; int i; uuids = g_new0(char *, g_slist_length(device->uuids) + 1); for (i = 0, l = device->uuids; l; l = g_slist_next(l), i++) uuids[i] = l->data; g_key_file_set_string_list(key_file, "General", "Services", (const char **)uuids, i); } else { g_key_file_remove_key(key_file, "General", "Services", NULL); } if (device->vendor_src) { g_key_file_set_integer(key_file, "DeviceID", "Source", device->vendor_src); g_key_file_set_integer(key_file, "DeviceID", "Vendor", device->vendor); g_key_file_set_integer(key_file, "DeviceID", "Product", device->product); g_key_file_set_integer(key_file, "DeviceID", "Version", device->version); } else { g_key_file_remove_group(key_file, "DeviceID", NULL); } if (device->local_csrk) store_csrk(device->local_csrk, key_file, "LocalSignatureKey"); if (device->remote_csrk) store_csrk(device->remote_csrk, key_file, "RemoteSignatureKey"); if (!queue_isempty(device->sirks)) { const struct queue_entry *entry; int i; for (entry = queue_get_entries(device->sirks), i = 0; entry; entry = entry->next, i++) { struct sirk_info *sirk = entry->data; store_sirk(sirk, key_file, i); } } str = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, str, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(str); g_key_file_free(key_file); g_free(uuids); return FALSE; } bool device_address_is_private(struct btd_device *dev) { if (dev->bdaddr_type != BDADDR_LE_RANDOM) return false; switch (dev->bdaddr.b[5] >> 6) { case 0x00: /* Private non-resolvable */ case 0x01: /* Private resolvable */ return true; default: return false; } } static void store_device_info(struct btd_device *device) { if (device->temporary || device->store_id > 0) return; if (device_address_is_private(device)) { DBG("Can't store info for private addressed device %s", device->path); return; } device->store_id = g_idle_add(store_device_info_cb, device); } void device_store_cached_name(struct btd_device *dev, const char *name) { char filename[PATH_MAX]; char d_addr[18]; GKeyFile *key_file; GError *gerr = NULL; char *data; char *data_old; gsize length = 0; gsize length_old = 0; if (device_address_is_private(dev)) { DBG("Can't store name for private addressed device %s", dev->path); return; } ba2str(&dev->bdaddr, d_addr); create_filename(filename, PATH_MAX, "/%s/cache/%s", btd_adapter_get_storage_dir(dev->adapter), d_addr); create_file(filename, 0600); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } data_old = g_key_file_to_data(key_file, &length_old, NULL); g_key_file_set_string(key_file, "General", "Name", name); data = g_key_file_to_data(key_file, &length, NULL); if ((length != length_old) || (memcmp(data, data_old, length))) { if (!g_file_set_contents(filename, data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } } g_free(data); g_free(data_old); g_key_file_free(key_file); } static void device_store_cached_name_resolve(struct btd_device *dev) { char filename[PATH_MAX]; char d_addr[18]; GKeyFile *key_file; GError *gerr = NULL; char *data; char *data_old; gsize length = 0; gsize length_old = 0; uint64_t failed_time; if (device_address_is_private(dev)) { DBG("Can't store name resolve for private addressed device %s", dev->path); return; } ba2str(&dev->bdaddr, d_addr); create_filename(filename, PATH_MAX, "/%s/cache/%s", btd_adapter_get_storage_dir(dev->adapter), d_addr); create_file(filename, 0600); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } failed_time = (uint64_t) dev->name_resolve_failed_time; data_old = g_key_file_to_data(key_file, &length_old, NULL); g_key_file_set_uint64(key_file, "NameResolving", "FailedTime", failed_time); data = g_key_file_to_data(key_file, &length, NULL); if ((length != length_old) || (memcmp(data, data_old, length))) { if (!g_file_set_contents(filename, data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } } g_free(data); g_free(data_old); g_key_file_free(key_file); } static void browse_request_free(struct browse_req *req) { struct btd_device *device = req->device; if (device->browse == req) device->browse = NULL; if (req->listener_id) g_dbus_remove_watch(dbus_conn, req->listener_id); if (req->msg) dbus_message_unref(req->msg); g_slist_free_full(req->profiles_added, g_free); if (req->records) sdp_list_free(req->records, (sdp_free_func_t) sdp_record_free); g_free(req); } static bool gatt_cache_is_enabled(struct btd_device *device) { switch (btd_opts.gatt_cache) { case BT_GATT_CACHE_YES: return device_is_paired(device, device->bdaddr_type); case BT_GATT_CACHE_NO: return false; case BT_GATT_CACHE_ALWAYS: default: return true; } } static void gatt_cache_cleanup(struct btd_device *device) { if (gatt_cache_is_enabled(device)) return; bt_gatt_client_cancel_all(device->client); gatt_db_clear(device->db); device->le_state.svc_resolved = false; } static void gatt_client_cleanup(struct btd_device *device) { if (!device->client) return; gatt_cache_cleanup(device); bt_gatt_client_set_service_changed(device->client, NULL, NULL, NULL); if (device->gatt_ready_id > 0) { bt_gatt_client_ready_unregister(device->client, device->gatt_ready_id); device->gatt_ready_id = 0; } bt_gatt_client_unref(device->client); device->client = NULL; } static void gatt_server_cleanup(struct btd_device *device) { if (!device->server) return; btd_gatt_database_att_disconnected( btd_adapter_get_database(device->adapter), device); bt_gatt_server_unref(device->server); device->server = NULL; } static void attio_cleanup(struct btd_device *device) { if (device->att_disconn_id) bt_att_unregister_disconnect(device->att, device->att_disconn_id); if (device->att_io) { g_io_channel_shutdown(device->att_io, FALSE, NULL); g_io_channel_unref(device->att_io); device->att_io = NULL; } gatt_client_cleanup(device); gatt_server_cleanup(device); if (device->att) { bt_att_unref(device->att); device->att = NULL; } if (device->attrib) { GAttrib *attrib = device->attrib; device->attrib = NULL; g_attrib_cancel_all(attrib); g_attrib_unref(attrib); } } static void browse_request_cancel(struct browse_req *req) { struct btd_device *device = req->device; struct btd_adapter *adapter = device->adapter; DBG(""); bt_cancel_discovery(btd_adapter_get_address(adapter), &device->bdaddr); attio_cleanup(device); browse_request_free(req); } static void svc_dev_remove(gpointer user_data) { struct svc_callback *cb = user_data; if (cb->idle_id > 0) g_source_remove(cb->idle_id); cb->func(cb->dev, -ENODEV, cb->user_data); g_free(cb); } static void device_free(gpointer user_data) { struct btd_device *device = user_data; btd_gatt_client_destroy(device->client_dbus); device->client_dbus = NULL; g_slist_free_full(device->uuids, g_free); g_slist_free_full(device->primaries, g_free); g_slist_free_full(device->svc_callbacks, svc_dev_remove); /* Reset callbacks since the device is going to be freed */ gatt_db_unregister(device->db, device->db_id); attio_cleanup(device); gatt_db_unref(device->db); bt_ad_unref(device->ad); if (device->tmp_records) sdp_list_free(device->tmp_records, (sdp_free_func_t) sdp_record_free); if (device->disconn_timer) timeout_remove(device->disconn_timer); if (device->discov_timer) timeout_remove(device->discov_timer); if (device->temporary_timer) timeout_remove(device->temporary_timer); if (device->connect) dbus_message_unref(device->connect); if (device->disconnect) dbus_message_unref(device->disconnect); DBG("%p", device); if (device->authr) { if (device->authr->agent) agent_unref(device->authr->agent); g_free(device->authr->pincode); g_free(device->authr); } if (device->eir_uuids) g_slist_free_full(device->eir_uuids, g_free); queue_destroy(device->sirks, free); btd_bearer_destroy(device->bredr); btd_bearer_destroy(device->le); g_free(device->local_csrk); g_free(device->remote_csrk); free(device->ltk); g_free(device->path); g_free(device->alias); free(device->modalias); g_free(device); } bool device_is_paired(struct btd_device *device, uint8_t bdaddr_type) { struct bearer_state *state = get_state(device, bdaddr_type); return state->paired; } bool device_is_bonded(struct btd_device *device, uint8_t bdaddr_type) { struct bearer_state *state = get_state(device, bdaddr_type); return state->bonded; } bool btd_device_is_trusted(struct btd_device *device) { return device->trusted; } bool device_is_cable_pairing(struct btd_device *device) { return device->cable_pairing; } static gboolean dev_property_get_address(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; char dstaddr[18]; const char *ptr = dstaddr; ba2str(&device->bdaddr, dstaddr); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); return TRUE; } static gboolean property_get_address_type(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_device *device = user_data; const char *str; if (device->le && device->bdaddr_type == BDADDR_LE_RANDOM) str = "random"; else str = "public"; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); return TRUE; } static gboolean dev_property_get_name(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; const char *ptr = device->name; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); return TRUE; } static gboolean dev_property_exists_name(const GDBusPropertyTable *property, void *data) { struct btd_device *dev = data; return device_name_known(dev); } static gboolean dev_property_get_alias(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; char dstaddr[18]; const char *ptr; /* Alias (fallback to name or address) */ if (device->alias != NULL) ptr = device->alias; else if (strlen(device->name) > 0) { ptr = device->name; } else { ba2str(&device->bdaddr, dstaddr); g_strdelimit(dstaddr, ":", '-'); ptr = dstaddr; } dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); return TRUE; } static void set_alias(GDBusPendingPropertySet id, const char *alias, void *data) { struct btd_device *device = data; /* No change */ if ((device->alias == NULL && g_str_equal(alias, "")) || g_strcmp0(device->alias, alias) == 0) { g_dbus_pending_property_success(id); return; } g_free(device->alias); device->alias = g_str_equal(alias, "") ? NULL : g_strdup(alias); store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Alias"); g_dbus_pending_property_success(id); } static void dev_property_set_alias(const GDBusPropertyTable *property, DBusMessageIter *value, GDBusPendingPropertySet id, void *data) { const char *alias; if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_STRING) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } dbus_message_iter_get_basic(value, &alias); set_alias(id, alias, data); } static gboolean dev_property_exists_class(const GDBusPropertyTable *property, void *data) { struct btd_device *device = data; return device->class != 0; } static gboolean dev_property_get_class(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; if (device->class == 0) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &device->class); return TRUE; } static gboolean get_appearance(const GDBusPropertyTable *property, void *data, uint16_t *appearance) { struct btd_device *device = data; if (dev_property_exists_class(property, data)) return FALSE; if (device->appearance) { *appearance = device->appearance; return TRUE; } return FALSE; } static gboolean dev_property_exists_appearance( const GDBusPropertyTable *property, void *data) { uint16_t appearance; return get_appearance(property, data, &appearance); } static gboolean dev_property_get_appearance(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { uint16_t appearance; if (!get_appearance(property, data, &appearance)) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &appearance); return TRUE; } const char *btd_device_get_icon(struct btd_device *device) { const char *icon = NULL; if (device->class != 0) icon = class_to_icon(device->class); else if (device->appearance != 0) icon = gap_appearance_to_icon(device->appearance); return icon; } static gboolean dev_property_exists_icon( const GDBusPropertyTable *property, void *data) { return btd_device_get_icon(data) != NULL; } static gboolean dev_property_get_icon(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { const char *icon; icon = btd_device_get_icon(data); if (icon == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &icon); return TRUE; } static gboolean dev_property_get_paired(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *dev = data; dbus_bool_t val; if (dev->bredr_state.paired || dev->le_state.paired) val = TRUE; else val = FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); return TRUE; } static gboolean dev_property_get_bonded(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *dev = data; dbus_bool_t val; if (dev->bredr_state.bonded || dev->le_state.bonded) val = TRUE; else val = FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); return TRUE; } static gboolean dev_property_get_legacy(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; dbus_bool_t val = device->legacy; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); return TRUE; } static gboolean dev_property_get_cable_pairing(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; dbus_bool_t val = device->cable_pairing; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); return TRUE; } static gboolean dev_property_get_rssi(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *dev = data; dbus_int16_t val = dev->rssi; dbus_message_iter_append_basic(iter, DBUS_TYPE_INT16, &val); return TRUE; } static gboolean dev_property_exists_rssi(const GDBusPropertyTable *property, void *data) { struct btd_device *dev = data; if (dev->rssi == 0) return FALSE; return TRUE; } static gboolean dev_property_get_tx_power(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *dev = data; dbus_int16_t val = dev->tx_power; dbus_message_iter_append_basic(iter, DBUS_TYPE_INT16, &val); return TRUE; } static gboolean dev_property_exists_tx_power(const GDBusPropertyTable *property, void *data) { struct btd_device *dev = data; if (dev->tx_power == 127) return FALSE; return TRUE; } static gboolean dev_property_get_svc_resolved(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; gboolean val = device->svc_refreshed; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); return TRUE; } static gboolean dev_property_flags_exist(const GDBusPropertyTable *property, void *data) { struct btd_device *device = data; return device->ad_flags[0] != INVALID_FLAGS; } static gboolean dev_property_get_flags(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; uint8_t *flags = device->ad_flags; DBusMessageIter array; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array); dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &flags, sizeof(device->ad_flags)); dbus_message_iter_close_container(iter, &array); return TRUE; } static gboolean dev_property_get_trusted(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; gboolean val = btd_device_is_trusted(device); dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); return TRUE; } static void set_trust(GDBusPendingPropertySet id, gboolean value, void *data) { struct btd_device *device = data; btd_device_set_trusted(device, value); g_dbus_pending_property_success(id); } static void dev_property_set_trusted(const GDBusPropertyTable *property, DBusMessageIter *value, GDBusPendingPropertySet id, void *data) { dbus_bool_t b; if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } dbus_message_iter_get_basic(value, &b); set_trust(id, b, data); } static gboolean dev_property_get_blocked(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &device->blocked); return TRUE; } static void set_blocked(GDBusPendingPropertySet id, gboolean value, void *data) { struct btd_device *device = data; int err; if (value) err = device_block(device, FALSE); else err = device_unblock(device, FALSE, FALSE); switch (-err) { case 0: g_dbus_pending_property_success(id); break; case EINVAL: g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", "Kernel lacks reject list support"); break; default: g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", strerror(-err)); break; } } static void dev_property_set_blocked(const GDBusPropertyTable *property, DBusMessageIter *value, GDBusPendingPropertySet id, void *data) { dbus_bool_t b; if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } dbus_message_iter_get_basic(value, &b); set_blocked(id, b, data); } static gboolean dev_property_get_connected(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *dev = data; dbus_bool_t connected; if (dev->bredr_state.connected || dev->le_state.connected) connected = TRUE; else connected = FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &connected); return TRUE; } static gboolean dev_property_get_uuids(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *dev = data; DBusMessageIter entry; GSList *l; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); if (dev->bredr_state.svc_resolved || dev->le_state.svc_resolved) l = dev->uuids; else if (dev->eir_uuids) l = dev->eir_uuids; else l = dev->uuids; for (; l != NULL; l = l->next) dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &l->data); dbus_message_iter_close_container(iter, &entry); return TRUE; } static gboolean dev_property_get_modalias(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; if (!device->modalias) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &device->modalias); return TRUE; } static gboolean dev_property_exists_modalias(const GDBusPropertyTable *property, void *data) { struct btd_device *device = data; return device->modalias ? TRUE : FALSE; } static gboolean dev_property_get_adapter(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; const char *str = adapter_get_path(device->adapter); dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &str); return TRUE; } static void append_manufacturer_data(void *data, void *user_data) { struct bt_ad_manufacturer_data *md = data; DBusMessageIter *dict = user_data; g_dbus_dict_append_basic_array(dict, DBUS_TYPE_UINT16, &md->manufacturer_id, DBUS_TYPE_BYTE, &md->data, md->len); } static gboolean dev_property_get_manufacturer_data(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; DBusMessageIter dict; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_UINT16_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); bt_ad_foreach_manufacturer_data(device->ad, append_manufacturer_data, &dict); dbus_message_iter_close_container(iter, &dict); return TRUE; } static gboolean dev_property_manufacturer_data_exist(const GDBusPropertyTable *property, void *data) { struct btd_device *device = data; return bt_ad_has_manufacturer_data(device->ad, NULL); } static void append_service_data(void *data, void *user_data) { struct bt_ad_service_data *sd = data; DBusMessageIter *dict = user_data; char uuid_str[MAX_LEN_UUID_STR]; bt_uuid_to_string(&sd->uuid, uuid_str, sizeof(uuid_str)); dict_append_array(dict, uuid_str, DBUS_TYPE_BYTE, &sd->data, sd->len); } static gboolean dev_property_get_service_data(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; DBusMessageIter dict; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); bt_ad_foreach_service_data(device->ad, append_service_data, &dict); dbus_message_iter_close_container(iter, &dict); return TRUE; } static gboolean dev_property_service_data_exist(const GDBusPropertyTable *property, void *data) { struct btd_device *device = data; return bt_ad_has_service_data(device->ad, NULL); } static void append_advertising_data(void *data, void *user_data) { struct bt_ad_data *ad = data; DBusMessageIter *dict = user_data; g_dbus_dict_append_basic_array(dict, DBUS_TYPE_BYTE, &ad->type, DBUS_TYPE_BYTE, &ad->data, ad->len); } static gboolean dev_property_get_advertising_data(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; DBusMessageIter dict; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_BYTE_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); bt_ad_foreach_data(device->ad, append_advertising_data, &dict); dbus_message_iter_close_container(iter, &dict); return TRUE; } static gboolean dev_property_advertising_data_exist(const GDBusPropertyTable *property, void *data) { struct btd_device *device = data; return bt_ad_has_data(device->ad, NULL) ? TRUE : FALSE; } static bool device_get_wake_support(struct btd_device *device) { return device->wake_support; } void device_set_wake_support(struct btd_device *device, bool wake_support) { device->wake_support = wake_support; if (device->wake_support) device->supported_flags |= DEVICE_FLAG_REMOTE_WAKEUP; else device->supported_flags &= ~DEVICE_FLAG_REMOTE_WAKEUP; /* If there is not override set, set the default the same as * support value. */ if (device->wake_override == WAKE_FLAG_DEFAULT) device_set_wake_override(device, wake_support); /* Set wake_allowed according to the override value. * Limit this to bonded device to avoid trying to set it * to new devices that are simply in range. */ if (device_is_bonded(device, device->bdaddr_type)) device_set_wake_allowed(device, device->wake_override == WAKE_FLAG_ENABLED, -1U); } static bool device_get_wake_allowed(struct btd_device *device) { return device->wake_allowed; } void device_set_wake_override(struct btd_device *device, bool wake_override) { if (wake_override) device->wake_override = WAKE_FLAG_ENABLED; else device->wake_override = WAKE_FLAG_DISABLED; } static void device_set_wake_allowed_complete(struct btd_device *device) { if (device->wake_id != -1U) { g_dbus_pending_property_success(device->wake_id); device->wake_id = -1U; } device->wake_allowed = device->pending_wake_allowed; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "WakeAllowed"); store_device_info(device); } static void set_wake_allowed_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_set_device_flags *rp = param; struct btd_device *dev = user_data; if (status != MGMT_STATUS_SUCCESS) { error("Set device flags return status: %s", mgmt_errstr(status)); if (dev->wake_id != -1U) { g_dbus_pending_property_error(dev->wake_id, ERROR_INTERFACE ".Failed", mgmt_errstr(status)); dev->wake_id = -1U; } dev->pending_wake_allowed = FALSE; dev->pending_flags = 0; return; } if (length < sizeof(*rp)) { error("Too small Set Device Flags complete event: %d", length); return; } btd_device_flags_changed(dev, dev->supported_flags, dev->pending_flags); } void device_set_wake_allowed(struct btd_device *device, bool wake_allowed, GDBusPendingPropertySet id) { uint32_t flags; /* Pending and current value are the same unless there is a change in * progress. Only update wake allowed if pending value doesn't match the * new value. */ if (device->wake_id != -1U && id != -1U) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".Busy", "Property change in progress"); return; } device->wake_id = id; device->pending_wake_allowed = wake_allowed; flags = device->current_flags; /* Include the pending flags, or they may get overwritten. */ flags |= device->pending_flags; if (wake_allowed) flags |= DEVICE_FLAG_REMOTE_WAKEUP; else flags &= ~DEVICE_FLAG_REMOTE_WAKEUP; adapter_set_device_flags(device->adapter, device, flags, set_wake_allowed_complete, device); } static void set_past_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { const struct mgmt_rp_set_device_flags *rp = param; struct btd_device *dev = user_data; if (status != MGMT_STATUS_SUCCESS) { error("Set device flags return status: %s", mgmt_errstr(status)); return; } if (length < sizeof(*rp)) { error("Too small Set Device Flags complete event: %d", length); return; } btd_device_flags_changed(dev, dev->supported_flags, dev->pending_flags); } void device_set_past_support(struct btd_device *device, bool value) { uint32_t flags; int err; if (!device) return; if (btd_device_flags_enabled(device, DEVICE_FLAG_PAST) == value) return; DBG("value %s", value ? "true" : "false"); flags = device->current_flags; /* Include the pending flags, or they may get overwritten. */ flags |= device->pending_flags; if (value) flags |= DEVICE_FLAG_PAST; else flags &= ~DEVICE_FLAG_PAST; err = adapter_set_device_flags(device->adapter, device, flags, set_past_complete, device); if (err) error("Failed to set past support: %s", strerror(-err)); } static gboolean dev_property_get_wake_allowed(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; dbus_bool_t wake_allowed = device_get_wake_allowed(device); dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &wake_allowed); return TRUE; } static void dev_property_set_wake_allowed(const GDBusPropertyTable *property, DBusMessageIter *value, GDBusPendingPropertySet id, void *data) { struct btd_device *device = data; dbus_bool_t b; if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } if (device->temporary) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".Unsupported", "Cannot set property while temporary"); return; } dbus_message_iter_get_basic(value, &b); /* Emit busy or success depending on current value. */ if (b == device->pending_wake_allowed) { if (device->wake_allowed == device->pending_wake_allowed) g_dbus_pending_property_success(id); else g_dbus_pending_property_error( id, ERROR_INTERFACE ".Busy", "Property change in progress"); return; } device_set_wake_override(device, b); device_set_wake_allowed(device, b, id); } static gboolean dev_property_wake_allowed_exist( const GDBusPropertyTable *property, void *data) { struct btd_device *device = data; return device_get_wake_support(device); } static void append_set(void *data, void *user_data) { struct sirk_info *info = data; const char *path; DBusMessageIter *iter = user_data; DBusMessageIter entry, dict; if (!info->set) return; path = btd_set_get_path(info->set); dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, &path); dbus_message_iter_open_container(&entry, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); g_dbus_dict_append_entry(&dict, "Rank", DBUS_TYPE_BYTE, &info->rank); dbus_message_iter_close_container(&entry, &dict); dbus_message_iter_close_container(iter, &entry); } static gboolean dev_property_get_set(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; DBusMessageIter array; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_OBJECT_PATH_AS_STRING DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &array); queue_foreach(device->sirks, append_set, &array); dbus_message_iter_close_container(iter, &array); return TRUE; } static gboolean dev_property_set_exists(const GDBusPropertyTable *property, void *data) { struct btd_device *device = data; return !queue_isempty(device->sirks); } static bool disconnect_all(gpointer user_data) { struct btd_device *device = user_data; device->disconn_timer = 0; if (device->bredr_state.connected) btd_adapter_disconnect_device(device->adapter, &device->bdaddr, BDADDR_BREDR); if (device->le_state.connected) btd_adapter_disconnect_device(device->adapter, &device->bdaddr, device->bdaddr_type); return FALSE; } int device_block(struct btd_device *device, gboolean update_only) { int err = 0; if (device->blocked) return 0; if (device->disconn_timer > 0) timeout_remove(device->disconn_timer); disconnect_all(device); while (device->services != NULL) { struct btd_service *service = device->services->data; device->services = g_slist_remove(device->services, service); service_remove(service); } if (!update_only) { if (device->le) err = btd_adapter_block_address(device->adapter, &device->bdaddr, device->bdaddr_type); if (!err && device->bredr) err = btd_adapter_block_address(device->adapter, &device->bdaddr, BDADDR_BREDR); } if (err < 0) return err; device->blocked = TRUE; store_device_info(device); btd_device_set_temporary(device, false); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Blocked"); return 0; } int device_unblock(struct btd_device *device, gboolean silent, gboolean update_only) { int err = 0; if (!device->blocked) return 0; if (!update_only) { if (device->le) err = btd_adapter_unblock_address(device->adapter, &device->bdaddr, device->bdaddr_type); if (!err && device->bredr) err = btd_adapter_unblock_address(device->adapter, &device->bdaddr, BDADDR_BREDR); } if (err < 0) return err; device->blocked = FALSE; store_device_info(device); if (!silent) { g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Blocked"); device_probe_profiles(device, device->uuids); } return 0; } static void browse_request_exit(DBusConnection *conn, void *user_data) { struct browse_req *req = user_data; DBG("Requestor exited"); browse_request_cancel(req); } static void bonding_request_cancel(struct bonding_req *bonding) { struct btd_device *device = bonding->device; struct btd_adapter *adapter = device->adapter; adapter_cancel_bonding(adapter, &device->bdaddr, device->bdaddr_type); } static void dev_disconn_service(gpointer a, gpointer b) { btd_service_disconnect(a); } void device_disconnect_watches_callback(struct btd_device *device) { if (!device || !device->watches) return; while (device->watches) { struct btd_disconnect_data *data = device->watches->data; if (data->watch) /* temporary is set if device is going to be removed */ data->watch(device, device->temporary, data->user_data); /* Check if the watch has been removed by callback function */ if (!g_slist_find(device->watches, data)) continue; device->watches = g_slist_remove(device->watches, data); g_free(data); } } void device_request_disconnect(struct btd_device *device, DBusMessage *msg) { if (device->bonding) bonding_request_cancel(device->bonding); if (device->browse) browse_request_cancel(device->browse); if (device->att_io) { g_io_channel_shutdown(device->att_io, FALSE, NULL); g_io_channel_unref(device->att_io); device->att_io = NULL; } if (device->connect) { const char *err_str; DBusMessage *reply; if (device->bonding_status == MGMT_STATUS_AUTH_FAILED) err_str = ERR_BREDR_CONN_KEY_MISSING; else err_str = ERR_BREDR_CONN_CANCELED; reply = btd_error_failed(device->connect, err_str); g_dbus_send_message(dbus_conn, reply); dbus_message_unref(device->connect); device->bonding_status = 0; device->connect = NULL; } if (btd_device_is_connected(device) && msg) device->disconnects = g_slist_append(device->disconnects, dbus_message_ref(msg)); if (device->disconn_timer) return; g_slist_foreach(device->services, dev_disconn_service, NULL); g_slist_free(device->pending); device->pending = NULL; device_disconnect_watches_callback(device); if (!btd_device_is_connected(device)) { if (msg) g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID); return; } device->disconn_timer = timeout_add_seconds(DISCONNECT_TIMER, disconnect_all, device, NULL); } bool device_is_disconnecting(struct btd_device *device) { return device->disconn_timer > 0; } bool device_is_connecting(struct btd_device *device) { return device->connect != NULL; } static void add_set(void *data, void *user_data) { struct sirk_info *sirk = data; struct btd_device *device = user_data; struct btd_device_set *set; if (!sirk->encrypted) return; set = btd_set_add_device(device, device->ltk->key, sirk->key, sirk->size); if (!set) return; if (sirk->set != set) { sirk->set = set; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Sets"); } } void device_set_ltk(struct btd_device *device, const uint8_t val[16], bool central, uint8_t enc_size) { if (!device->ltk) device->ltk = new0(struct ltk_info, 1); memcpy(device->ltk->key, val, sizeof(device->ltk->key)); device->ltk->central = central; device->ltk->enc_size = enc_size; bt_att_set_enc_key_size(device->att, enc_size); /* Check if there is any set/sirk that needs decryption */ queue_foreach(device->sirks, add_set, device); } bool btd_device_get_ltk(struct btd_device *device, uint8_t key[16], bool *central, uint8_t *enc_size) { if (!device || !device->ltk || !key) return false; memcpy(key, device->ltk->key, sizeof(device->ltk->key)); if (central) *central = device->ltk->central; if (enc_size) *enc_size = device->ltk->enc_size; return true; } void device_set_csrk(struct btd_device *device, const uint8_t val[16], uint32_t counter, uint8_t type, bool store_hint) { struct csrk_info **handle; struct csrk_info *csrk; bool auth; switch (type) { case 0x00: handle = &device->local_csrk; auth = FALSE; break; case 0x01: handle = &device->remote_csrk; auth = FALSE; break; case 0x02: handle = &device->local_csrk; auth = TRUE; break; case 0x03: handle = &device->remote_csrk; auth = TRUE; break; default: warn("Unsupported CSRK type %u", type); return; } if (!*handle) *handle = g_new0(struct csrk_info, 1); csrk = *handle; memcpy(csrk->key, val, sizeof(csrk->key)); csrk->counter = counter; csrk->auth = auth; if (!store_hint) return; store_device_info(device); btd_device_set_temporary(device, false); } static bool match_sirk(const void *data, const void *match_data) { const struct sirk_info *sirk = data; const uint8_t *key = match_data; return !memcmp(sirk->key, key, sizeof(sirk->key)); } static struct sirk_info *device_add_sirk_info(struct btd_device *device, bool encrypted, uint8_t key[16], uint8_t size, uint8_t rank) { struct sirk_info *sirk; sirk = queue_find(device->sirks, match_sirk, key); if (sirk) return sirk; sirk = new0(struct sirk_info, 1); sirk->encrypted = encrypted; memcpy(sirk->key, key, sizeof(sirk->key)); sirk->size = size; sirk->rank = rank; queue_push_tail(device->sirks, sirk); store_device_info(device); return sirk; } bool btd_device_add_set(struct btd_device *device, bool encrypted, uint8_t key[16], uint8_t size, uint8_t rank) { struct btd_device_set *set; struct sirk_info *sirk; if (encrypted && !device->ltk) return false; sirk = device_add_sirk_info(device, encrypted, key, size, rank); if (!sirk) return false; set = btd_set_add_device(device, encrypted ? device->ltk->key : NULL, key, size); if (!set) return false; if (sirk->set != set) { sirk->set = set; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Sets"); } return true; } static void device_set_auto_connect(struct btd_device *device, gboolean enable) { char addr[18]; const char *bearer; if (!device || !device->le) return; ba2str(&device->bdaddr, addr); DBG("%s auto connect: %d", addr, enable); if (device->auto_connect == enable) return; device->auto_connect = enable; /* Disabling auto connect */ if (enable == FALSE) { adapter_connect_list_remove(device->adapter, device); adapter_auto_connect_remove(device->adapter, device); return; } if (device_address_is_private(device)) return; /* Inhibit auto connect if BR/EDR bearer is preferred */ bearer = device_prefer_bearer_str(device); if (bearer && !strcasecmp(bearer, "bredr")) return; /* Enabling auto connect */ adapter_auto_connect_add(device->adapter, device); if (device->attrib) { DBG("Already connected"); return; } adapter_connect_list_add(device->adapter, device); } static DBusMessage *dev_disconnect(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *device = user_data; /* * If device is not trusted disable connections through passive * scanning until Device1.Connect is called */ if (device->auto_connect && !device->trusted) { device->disable_auto_connect = TRUE; device_set_auto_connect(device, FALSE); } device_request_disconnect(device, msg); return NULL; } static int connect_next(struct btd_device *dev) { struct btd_service *service; int err = -ENOENT; while (dev->pending) { service = dev->pending->data; err = btd_service_connect(service); if (!err) return 0; dev->pending = g_slist_delete_link(dev->pending, dev->pending); } return err; } static void device_profile_connected(struct btd_device *dev, struct btd_profile *profile, int err) { struct btd_service *pending; GSList *l; DBG("%s %s (%d)", profile->name, strerror(-err), -err); if (!err) btd_device_set_temporary(dev, false); if (dev->pending == NULL) goto done; if (!btd_device_is_connected(dev)) { switch (-err) { case EHOSTDOWN: /* page timeout */ case EHOSTUNREACH: /* adapter not powered */ case ECONNABORTED: /* adapter powered down */ goto done; } } pending = dev->pending->data; l = find_service_with_profile(dev->pending, profile); if (l != NULL) dev->pending = g_slist_delete_link(dev->pending, l); /* Only continue connecting the next profile if it matches the first * pending, otherwise it will trigger another connect to the same * profile */ if (profile != btd_service_get_profile(pending)) return; if (connect_next(dev) == 0) return; done: g_slist_free(dev->pending); dev->pending = NULL; if (!dev->connect) return; if (dbus_message_is_method_call(dev->connect, DEVICE_INTERFACE, "Connect")) { if (!err) dev->general_connect = TRUE; else if (find_service_with_state(dev->services, BTD_SERVICE_STATE_CONNECTED)) /* Reset error if there are services connected */ err = 0; } else if (dbus_message_is_method_call(dev->connect, BTD_BEARER_BREDR_INTERFACE, "Connect")) { /* Send D-Bus reply to bredr.connect() */ if (err) { btd_bearer_connected(dev->bredr, err); goto unref; } } DBG("returning response to %s", dbus_message_get_sender(dev->connect)); if (err) { /* Fallback to LE bearer if supported */ if (err == -EHOSTDOWN && dev->le && !dev->le_state.connected) { err = device_connect_le(dev); if (err == 0) return; } g_dbus_send_message(dbus_conn, btd_error_bredr_errno(dev->connect, err)); } else { /* Start passive SDP discovery to update known services */ if (dev->bredr && !dev->svc_refreshed && dev->refresh_discovery) device_browse_sdp(dev, NULL); g_dbus_send_reply(dbus_conn, dev->connect, DBUS_TYPE_INVALID); } unref: dbus_message_unref(dev->connect); dev->connect = NULL; } void device_add_eir_uuids(struct btd_device *dev, struct queue *uuids) { const struct queue_entry *q; GSList *added = NULL; if (dev->bredr_state.svc_resolved || dev->le_state.svc_resolved) return; for (q = queue_get_entries(uuids); q != NULL; q = q->next) { const char *str = q->data; if (g_slist_find_custom(dev->eir_uuids, str, bt_uuid_strcmp)) continue; added = g_slist_append(added, (void *)str); dev->eir_uuids = g_slist_append(dev->eir_uuids, g_strdup(str)); } device_probe_profiles(dev, added); g_slist_free(added); } static void add_manufacturer_data(void *data, void *user_data) { struct eir_msd *msd = data; struct btd_device *dev = user_data; if (!bt_ad_add_manufacturer_data(dev->ad, msd->company, msd->data, msd->data_len)) return; g_dbus_emit_property_changed(dbus_conn, dev->path, DEVICE_INTERFACE, "ManufacturerData"); } void device_set_manufacturer_data(struct btd_device *dev, struct queue *queue, bool duplicate) { if (duplicate) bt_ad_clear_manufacturer_data(dev->ad); queue_foreach(queue, add_manufacturer_data, dev); } static void add_service_data(void *data, void *user_data) { struct eir_sd *sd = data; struct btd_device *dev = user_data; bt_uuid_t uuid; struct queue *q; if (bt_string_to_uuid(&uuid, sd->uuid) < 0) return; if (!bt_ad_add_service_data(dev->ad, &uuid, sd->data, sd->data_len)) return; q = queue_new(); queue_push_tail(q, sd->uuid); device_add_eir_uuids(dev, q); queue_destroy(q, NULL); g_dbus_emit_property_changed(dbus_conn, dev->path, DEVICE_INTERFACE, "ServiceData"); } void device_set_service_data(struct btd_device *dev, struct queue *queue, bool duplicate) { if (duplicate) bt_ad_clear_service_data(dev->ad); queue_foreach(queue, add_service_data, dev); } static void add_data(void *data, void *user_data) { struct eir_ad *ad = data; struct btd_device *dev = user_data; if (!bt_ad_add_data(dev->ad, ad->type, ad->data, ad->len)) return; if (ad->type == EIR_TRANSPORT_DISCOVERY) g_dbus_emit_property_changed(dbus_conn, dev->path, DEVICE_INTERFACE, "AdvertisingData"); } void device_set_data(struct btd_device *dev, struct queue *queue, bool duplicate) { if (duplicate) bt_ad_clear_data(dev->ad); queue_foreach(queue, add_data, dev); } static struct btd_service *find_connectable_service(struct btd_device *dev, const char *uuid) { GSList *l; for (l = dev->services; l != NULL; l = g_slist_next(l)) { struct btd_service *service = l->data; struct btd_profile *p = btd_service_get_profile(service); if (!p->connect || !p->remote_uuid) continue; if (strcasecmp(uuid, p->remote_uuid) == 0) return service; } return NULL; } bool btd_device_all_services_allowed(struct btd_device *dev) { GSList *l; struct btd_adapter *adapter = dev->adapter; struct btd_service *service; struct btd_profile *profile; for (l = dev->services; l != NULL; l = g_slist_next(l)) { service = l->data; profile = btd_service_get_profile(service); if (!profile || !profile->auto_connect) continue; if (!btd_adapter_is_uuid_allowed(adapter, profile->remote_uuid)) return false; } return true; } void btd_device_update_allowed_services(struct btd_device *dev) { struct btd_adapter *adapter = dev->adapter; struct btd_service *service; struct btd_profile *profile; GSList *l; bool is_allowed; char addr[18]; /* If service discovery is ongoing, let the service discovery complete * callback call this function. */ if (dev->browse) { ba2str(&dev->bdaddr, addr); DBG("service discovery of %s is ongoing. Skip updating allowed " "services", addr); return; } for (l = dev->services; l != NULL; l = g_slist_next(l)) { service = l->data; profile = btd_service_get_profile(service); is_allowed = btd_adapter_is_uuid_allowed(adapter, profile->remote_uuid); btd_service_set_allowed(service, is_allowed); } } static const struct btd_profile *get_service_profile(void *data, void *user_data) { return btd_service_get_profile(data); } static GSList *create_pending_list(struct btd_device *dev, const char *uuid) { struct btd_service *service; struct btd_profile *p; GSList *l; if (uuid) { service = find_connectable_service(dev, uuid); if (!service) return dev->pending; if (btd_service_is_allowed(service)) return g_slist_prepend(dev->pending, service); info("service %s is blocked", uuid); return dev->pending; } for (l = dev->services; l != NULL; l = g_slist_next(l)) { service = l->data; p = btd_service_get_profile(service); if (!p->auto_connect) continue; if (!btd_service_is_allowed(service)) { info("service %s is blocked", p->remote_uuid); continue; } if (g_slist_find(dev->pending, service)) continue; if (btd_service_get_state(service) != BTD_SERVICE_STATE_DISCONNECTED) continue; dev->pending = g_slist_append(dev->pending, service); } /* Connect in priority order */ dev->pending = btd_profile_sort_list(dev->pending, get_service_profile, NULL); return dev->pending; } #define NVAL_TIME ((time_t) -1) #define SEEN_TRESHHOLD 300 static uint8_t select_conn_bearer(struct btd_device *dev) { time_t bredr_last = NVAL_TIME, le_last = NVAL_TIME; time_t current = time(NULL); /* Use preferred bearer or bonded bearer in case only one is bonded */ if (dev->bredr_state.prefer || (dev->bredr_state.bonded && !dev->le_state.bonded)) return BDADDR_BREDR; else if (dev->le_state.prefer || (!dev->bredr_state.bonded && dev->le_state.bonded)) return dev->bdaddr_type; /* If the address is random it can only be connected over LE */ if (dev->bdaddr_type == BDADDR_LE_RANDOM) return dev->bdaddr_type; if (dev->bredr_state.connectable && dev->bredr_state.last_seen) { bredr_last = current - dev->bredr_state.last_seen; if (bredr_last > SEEN_TRESHHOLD) bredr_last = NVAL_TIME; } if (dev->le_state.connectable && dev->le_state.last_seen) { le_last = current - dev->le_state.last_seen; if (le_last > SEEN_TRESHHOLD) le_last = NVAL_TIME; } if (le_last == NVAL_TIME && bredr_last == NVAL_TIME) return dev->bdaddr_type; if (dev->bredr && (!dev->le || le_last == NVAL_TIME)) return BDADDR_BREDR; if (dev->le && (!dev->bredr || bredr_last == NVAL_TIME)) return dev->bdaddr_type; /* * Prefer BR/EDR if time is the same since it might be from an * advertisement with BR/EDR flag set. */ if (bredr_last <= le_last && btd_adapter_get_bredr(dev->adapter)) return BDADDR_BREDR; return dev->bdaddr_type; } static void device_browse_cb(struct btd_device *dev, int err, void *user_data) { DBG("err %d (%s)", err, strerror(-err)); /* If there are not errors with discovery proceed conecting services */ if (!err) { dev->pending = create_pending_list(dev, NULL); connect_next(dev); } } int btd_device_connect_services(struct btd_device *dev, GSList *services) { GSList *l; uint8_t bdaddr_type; if (dev->pending || dev->connect || dev->browse) return -EBUSY; if (!btd_adapter_get_powered(dev->adapter)) return -ENETDOWN; bdaddr_type = select_conn_bearer(dev); if (bdaddr_type != BDADDR_BREDR) { if (dev->le_state.connected) return -EALREADY; return device_connect_le(dev); } if (!dev->bredr_state.svc_resolved) { int err; err = device_discover_services(dev, BDADDR_BREDR, NULL); if (err) return err; /* Wait for service discovery to complete before connecting to * profiles. */ device_wait_for_svc_complete(dev, device_browse_cb, NULL); return 0; } if (services) { for (l = services; l; l = g_slist_next(l)) { struct btd_service *service = l->data; dev->pending = g_slist_append(dev->pending, service); } } else { dev->pending = create_pending_list(dev, NULL); } return connect_next(dev); } DBusMessage *device_connect_profiles(struct btd_device *dev, uint8_t bdaddr_type, DBusMessage *msg, const char *uuid) { struct bearer_state *state = get_state(dev, bdaddr_type); int err; DBG("%s %s, client %s", dev->path, uuid ? uuid : "(all)", dbus_message_get_sender(msg)); if (dev->pending || dev->connect || dev->browse) return btd_error_in_progress_str(msg, ERR_BREDR_CONN_BUSY); if (!btd_adapter_get_powered(dev->adapter)) { return btd_error_not_ready_str(msg, ERR_BREDR_CONN_ADAPTER_NOT_POWERED); } btd_device_set_temporary(dev, false); if (!state->svc_resolved) goto resolve_services; dev->pending = create_pending_list(dev, uuid); if (!dev->pending) { if (dev->svc_refreshed) { if (dbus_message_is_method_call(msg, DEVICE_INTERFACE, "Connect") && find_service_with_state(dev->services, BTD_SERVICE_STATE_CONNECTED)) { return dbus_message_new_method_return(msg); } else { return btd_error_profile_unavailable(msg); } } goto resolve_services; } err = connect_next(dev); if (err < 0) { if (err == -EALREADY) return dbus_message_new_method_return(msg); return btd_error_bredr_errno(msg, err); } dev->connect = dbus_message_ref(msg); return NULL; resolve_services: DBG("Resolving services for %s", dev->path); err = device_discover_services(dev, bdaddr_type, msg); if (err < 0) { return btd_error_failed(msg, bdaddr_type == BDADDR_BREDR ? ERR_BREDR_CONN_SDP_SEARCH : ERR_LE_CONN_GATT_BROWSE); } return NULL; } static DBusMessage *dev_connect(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *dev = user_data; uint8_t bdaddr_type; if (dev->bonding) return btd_error_in_progress(msg); if (dev->bredr_state.connected) { /* * Check if services have been resolved and there is at least * one connected before switching to connect LE. */ if (dev->bredr_state.svc_resolved && find_service_with_state(dev->services, BTD_SERVICE_STATE_CONNECTED)) bdaddr_type = dev->bdaddr_type; else bdaddr_type = BDADDR_BREDR; } else if (dev->le_state.connected && dev->bredr) bdaddr_type = BDADDR_BREDR; else bdaddr_type = select_conn_bearer(dev); dev->auth_failures = 0; if (bdaddr_type != BDADDR_BREDR) { int err; if (dev->connect) return btd_error_in_progress(msg); if (dev->le_state.connected) return dbus_message_new_method_return(msg); btd_device_set_temporary(dev, false); if (dev->disable_auto_connect) { dev->disable_auto_connect = FALSE; device_set_auto_connect(dev, TRUE); } err = device_connect_le(dev); if (err < 0) return btd_error_failed(msg, strerror(-err)); dev->connect = dbus_message_ref(msg); return NULL; } return device_connect_profiles(dev, bdaddr_type, msg, NULL); } static DBusMessage *connect_profile(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *dev = user_data; const char *pattern; char *uuid; DBusMessage *reply; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, DBUS_TYPE_INVALID)) { return btd_error_invalid_args_str(msg, ERR_BREDR_CONN_INVALID_ARGUMENTS); } uuid = bt_name2string(pattern); if (uuid == NULL) return btd_error_invalid_args_str(msg, ERR_BREDR_CONN_INVALID_ARGUMENTS); reply = device_connect_profiles(dev, BDADDR_BREDR, msg, uuid); free(uuid); return reply; } static void device_profile_disconnected(struct btd_device *dev, struct btd_profile *profile, int err) { if (!dev->disconnect) return; if (err) g_dbus_send_message(dbus_conn, btd_error_failed(dev->disconnect, strerror(-err))); else g_dbus_send_reply(dbus_conn, dev->disconnect, DBUS_TYPE_INVALID); dbus_message_unref(dev->disconnect); dev->disconnect = NULL; } static DBusMessage *disconnect_profile(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_device *dev = user_data; struct btd_service *service; const char *pattern; char *uuid; int err; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); uuid = bt_name2string(pattern); if (uuid == NULL) return btd_error_invalid_args(msg); service = find_connectable_service(dev, uuid); free(uuid); if (!service) return btd_error_invalid_args(msg); if (dev->disconnect) return btd_error_in_progress(msg); if (btd_service_get_state(service) == BTD_SERVICE_STATE_DISCONNECTED) return dbus_message_new_method_return(msg); dev->disconnect = dbus_message_ref(msg); err = btd_service_disconnect(service); if (err == 0) return NULL; dbus_message_unref(dev->disconnect); dev->disconnect = NULL; if (err == -ENOTSUP) return btd_error_not_supported(msg); else if (err == -EALREADY) return dbus_message_new_method_return(msg); return btd_error_failed(msg, strerror(-err)); } static void store_services(struct btd_device *device) { char filename[PATH_MAX]; char dst_addr[18]; uuid_t uuid; char *prim_uuid; GKeyFile *key_file; GError *gerr = NULL; GSList *l; char *data; gsize length = 0; if (device_address_is_private(device)) { DBG("Can't store services for private addressed device %s", device->path); return; } sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); prim_uuid = bt_uuid2string(&uuid); if (prim_uuid == NULL) return; ba2str(&device->bdaddr, dst_addr); create_filename(filename, PATH_MAX, "/%s/%s/attributes", btd_adapter_get_storage_dir(device->adapter), dst_addr); key_file = g_key_file_new(); for (l = device->primaries; l; l = l->next) { struct gatt_primary *primary = l->data; char handle[6], uuid_str[33]; int i; sprintf(handle, "%hu", primary->range.start); bt_string2uuid(&uuid, primary->uuid); sdp_uuid128_to_uuid(&uuid); switch (uuid.type) { case SDP_UUID16: sprintf(uuid_str, "%4.4X", uuid.value.uuid16); break; case SDP_UUID32: sprintf(uuid_str, "%8.8X", uuid.value.uuid32); break; case SDP_UUID128: for (i = 0; i < 16; i++) sprintf(uuid_str + (i * 2), "%2.2X", uuid.value.uuid128.data[i]); break; default: uuid_str[0] = '\0'; } g_key_file_set_string(key_file, handle, "UUID", prim_uuid); g_key_file_set_string(key_file, handle, "Value", uuid_str); g_key_file_set_integer(key_file, handle, "EndGroupHandle", primary->range.end); } data = g_key_file_to_data(key_file, &length, NULL); if (length > 0) { create_file(filename, 0600); if (!g_file_set_contents(filename, data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } } free(prim_uuid); g_free(data); g_key_file_free(key_file); } static void store_gatt_db(struct btd_device *device) { char filename[PATH_MAX]; char dst_addr[18]; if (device_address_is_private(device)) { DBG("Can't store GATT db for private addressed device %s", device->path); return; } if (!gatt_cache_is_enabled(device)) return; ba2str(&device->bdaddr, dst_addr); create_filename(filename, PATH_MAX, "/%s/cache/%s", btd_adapter_get_storage_dir(device->adapter), dst_addr); create_file(filename, 0600); btd_settings_gatt_db_store(device->db, filename); } static void browse_request_complete(struct browse_req *req, uint8_t type, uint8_t bdaddr_type, int err) { struct btd_device *dev = req->device; DBusMessage *reply = NULL; DBusMessage *msg; if (req->type != type) return; if (!req->msg) goto done; if (dbus_message_is_method_call(req->msg, DEVICE_INTERFACE, "Pair")) { if (!device_is_paired(dev, bdaddr_type)) { reply = btd_error_failed(req->msg, "Not paired"); goto done; } if (dev->pending_paired) { if (bdaddr_type == BDADDR_BREDR) btd_bearer_paired(dev->bredr); else btd_bearer_paired(dev->le); g_dbus_emit_property_changed(dbus_conn, dev->path, DEVICE_INTERFACE, "Paired"); dev->pending_paired = false; } /* Disregard browse errors in case of Pair */ reply = g_dbus_create_reply(req->msg, DBUS_TYPE_INVALID); goto done; } else if (dbus_message_is_method_call(req->msg, BTD_BEARER_BREDR_INTERFACE, "Connect")) { /* Send D-Bus reply to bredr.connect() */ if (err && dev->bredr) { btd_bearer_connected(dev->bredr, err); goto done; } } if (err) { /* Fallback to LE bearer if supported */ if (err == -EHOSTDOWN && bdaddr_type == BDADDR_BREDR && dev->le && !dev->le_state.connected) { err = device_connect_le(dev); if (err == 0) goto done; } if (bdaddr_type == BDADDR_BREDR) reply = btd_error_bredr_errno(req->msg, err); else reply = btd_error_le_errno(req->msg, err); goto done; } /* if successfully resolved services we need to free browsing request * before passing message back to connect functions, otherwise * device->browse is set and "InProgress" error is returned instead * of actually connecting services */ msg = dbus_message_ref(req->msg); browse_request_free(req); req = NULL; if (dbus_message_is_method_call(msg, DEVICE_INTERFACE, "Connect")) reply = dev_connect(dbus_conn, msg, dev); else if (dbus_message_is_method_call(msg, DEVICE_INTERFACE, "ConnectProfile")) reply = connect_profile(dbus_conn, msg, dev); else reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID); dbus_message_unref(msg); done: if (reply) g_dbus_send_message(dbus_conn, reply); if (req) browse_request_free(req); } void device_set_refresh_discovery(struct btd_device *dev, bool refresh) { dev->refresh_discovery = refresh; } static void device_set_svc_refreshed(struct btd_device *device, bool value) { if (device->svc_refreshed == value) return; device->svc_refreshed = value; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "ServicesResolved"); } static void device_svc_resolved(struct btd_device *dev, uint8_t browse_type, uint8_t bdaddr_type, int err) { struct bearer_state *state = get_state(dev, bdaddr_type); struct browse_req *req = dev->browse; DBG("%s err %d", dev->path, err); state->svc_resolved = true; /* Disconnection notification can happen before this function * gets called, so don't set svc_refreshed for a disconnected * device. */ if (state->connected) device_set_svc_refreshed(dev, true); g_slist_free_full(dev->eir_uuids, g_free); dev->eir_uuids = NULL; if (dev->pending_paired) { if (bdaddr_type == BDADDR_BREDR) btd_bearer_paired(dev->bredr); else btd_bearer_paired(dev->le); g_dbus_emit_property_changed(dbus_conn, dev->path, DEVICE_INTERFACE, "Paired"); dev->pending_paired = false; } if (!dev->temporary) { store_device_info(dev); if (bdaddr_type != BDADDR_BREDR && err == 0) store_services(dev); } if (req) browse_request_complete(req, browse_type, bdaddr_type, err); while (dev->svc_callbacks) { struct svc_callback *cb = dev->svc_callbacks->data; if (cb->idle_id > 0) g_source_remove(cb->idle_id); cb->func(dev, err, cb->user_data); dev->svc_callbacks = g_slist_delete_link(dev->svc_callbacks, dev->svc_callbacks); g_free(cb); } btd_device_update_allowed_services(dev); device_resolved_drivers(dev->adapter, dev); } static struct bonding_req *bonding_request_new(DBusMessage *msg, struct btd_device *device, uint8_t bdaddr_type, struct agent *agent) { struct bonding_req *bonding; char addr[18]; ba2str(&device->bdaddr, addr); DBG("Requesting bonding for %s", addr); bonding = g_new0(struct bonding_req, 1); bonding->msg = dbus_message_ref(msg); bonding->bdaddr_type = bdaddr_type; bonding->cb_iter = btd_adapter_pin_cb_iter_new(device->adapter); /* Marks the bonding start time for the first attempt on request * construction. The following attempts will be updated on * device_bonding_retry. */ clock_gettime(CLOCK_MONOTONIC, &bonding->attempt_start_time); if (agent) bonding->agent = agent_ref(agent); return bonding; } void device_bonding_restart_timer(struct btd_device *device) { if (!device || !device->bonding) return; clock_gettime(CLOCK_MONOTONIC, &device->bonding->attempt_start_time); } static void bonding_request_stop_timer(struct bonding_req *bonding) { struct timespec current; clock_gettime(CLOCK_MONOTONIC, ¤t); /* Compute the time difference in ms. */ bonding->last_attempt_duration_ms = (current.tv_sec - bonding->attempt_start_time.tv_sec) * 1000L + (current.tv_nsec - bonding->attempt_start_time.tv_nsec) / 1000000L; } /* Returns the duration of the last bonding attempt in milliseconds. The * duration is measured starting from the latest of the following three * events and finishing when the Command complete event is received for the * authentication request: * - MGMT_OP_PAIR_DEVICE is sent, * - MGMT_OP_PIN_CODE_REPLY is sent and * - Command complete event is received for the sent MGMT_OP_PIN_CODE_REPLY. */ long device_bonding_last_duration(struct btd_device *device) { struct bonding_req *bonding = device->bonding; if (!bonding) return 0; return bonding->last_attempt_duration_ms; } static void create_bond_req_exit(DBusConnection *conn, void *user_data) { struct btd_device *device = user_data; char addr[18]; ba2str(&device->bdaddr, addr); DBG("%s: requestor exited before bonding was completed", addr); if (device->authr) device_cancel_authentication(device, FALSE); if (device->bonding) { device->bonding->listener_id = 0; device_request_disconnect(device, NULL); } } static void bonding_request_free(struct bonding_req *bonding) { if (!bonding) return; if (bonding->listener_id) g_dbus_remove_watch(dbus_conn, bonding->listener_id); if (bonding->msg) dbus_message_unref(bonding->msg); if (bonding->cb_iter) g_free(bonding->cb_iter); if (bonding->agent) { agent_cancel(bonding->agent); agent_unref(bonding->agent); bonding->agent = NULL; } if (bonding->retry_timer) g_source_remove(bonding->retry_timer); if (bonding->device) bonding->device->bonding = NULL; g_free(bonding); } static DBusMessage *pair_device(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_device *device = data; struct btd_adapter *adapter = device->adapter; struct bearer_state *state; uint8_t bdaddr_type; const char *sender; struct agent *agent; struct bonding_req *bonding; enum mgmt_io_capability io_cap; int err; btd_device_set_temporary(device, false); if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); if (device->bonding || device->connect) return btd_error_in_progress(msg); /* Only use this selection algorithms when device is combo * chip. Otherwise, it will use the wrong bearer to establish * a connection if the device is already paired, which will * stall the pairing procedure. For example, for a BLE only * device, if the device is already paired, and upper layer * issue the pair device again, it will set bdaddr_type to * BDADDR_BREDR since LE is bonded, then it goes with BR/EDR * bearer. */ if (device->bredr && device->le) { if (device->bredr_state.bonded) bdaddr_type = device->bdaddr_type; else if (device->le_state.bonded) bdaddr_type = BDADDR_BREDR; else bdaddr_type = select_conn_bearer(device); } else { bdaddr_type = device->bdaddr_type; } state = get_state(device, bdaddr_type); if (state->bonded) return btd_error_already_exists(msg); sender = dbus_message_get_sender(msg); agent = agent_get(sender); if (agent) io_cap = agent_get_io_capability(agent); else io_cap = MGMT_IO_CAPABILITY_NOINPUTNOOUTPUT; bonding = bonding_request_new(msg, device, bdaddr_type, agent); if (agent) agent_unref(agent); bonding->listener_id = g_dbus_add_disconnect_watch(dbus_conn, sender, create_bond_req_exit, device, NULL); device->bonding = bonding; bonding->device = device; /* Due to a bug in the kernel we might loose out on ATT commands * that arrive during the SMP procedure, so connect the ATT * channel first and only then start pairing (there's code for * this in the ATT connect callback) */ if (bdaddr_type != BDADDR_BREDR) { if (device->disable_auto_connect) { device->disable_auto_connect = FALSE; device_set_auto_connect(device, TRUE); } if (!state->connected && btd_le_connect_before_pairing()) err = device_connect_le(device); else if (!state->connected || !bt_att_set_security(device->att, BT_ATT_SECURITY_MEDIUM)) err = adapter_create_bonding(adapter, &device->bdaddr, device->bdaddr_type, io_cap); else err = 0; } else { err = adapter_create_bonding(adapter, &device->bdaddr, BDADDR_BREDR, io_cap); } if (err < 0) { bonding_request_free(device->bonding); return btd_error_failed(msg, strerror(-err)); } return NULL; } static DBusMessage *new_authentication_return(DBusMessage *msg, uint8_t status) { switch (status) { case MGMT_STATUS_SUCCESS: return dbus_message_new_method_return(msg); case MGMT_STATUS_CONNECT_FAILED: return dbus_message_new_error(msg, ERROR_INTERFACE ".ConnectionAttemptFailed", "Page Timeout"); case MGMT_STATUS_TIMEOUT: return dbus_message_new_error(msg, ERROR_INTERFACE ".AuthenticationTimeout", "Authentication Timeout"); case MGMT_STATUS_BUSY: case MGMT_STATUS_REJECTED: return dbus_message_new_error(msg, ERROR_INTERFACE ".AuthenticationRejected", "Authentication Rejected"); case MGMT_STATUS_CANCELLED: case MGMT_STATUS_NO_RESOURCES: case MGMT_STATUS_DISCONNECTED: return dbus_message_new_error(msg, ERROR_INTERFACE ".AuthenticationCanceled", "Authentication Canceled"); case MGMT_STATUS_ALREADY_PAIRED: return dbus_message_new_error(msg, ERROR_INTERFACE ".AlreadyExists", "Already Paired"); default: return dbus_message_new_error(msg, ERROR_INTERFACE ".AuthenticationFailed", "Authentication Failed"); } } void device_cancel_bonding(struct btd_device *device, uint8_t status) { struct bonding_req *bonding = device->bonding; DBusMessage *reply; char addr[18]; if (!bonding) return; ba2str(&device->bdaddr, addr); DBG("Canceling bonding request for %s", addr); if (device->authr) device_cancel_authentication(device, FALSE); reply = new_authentication_return(bonding->msg, status); g_dbus_send_message(dbus_conn, reply); bonding_request_cancel(bonding); bonding_request_free(bonding); } static DBusMessage *cancel_pairing(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_device *device = data; struct bonding_req *req = device->bonding; DBG(""); if (!req) { btd_adapter_remove_bonding(device->adapter, &device->bdaddr, device->bdaddr_type); return btd_error_does_not_exist(msg); } device_cancel_bonding(device, MGMT_STATUS_CANCELLED); return dbus_message_new_method_return(msg); } static sdp_list_t *read_device_records(struct btd_device *device); static DBusMessage *get_service_records(DBusConnection *conn, DBusMessage *msg, void *data) { DBusMessage *reply; DBusMessageIter records_arr, record; struct btd_device *device = data; sdp_list_t *cur; if (!btd_adapter_get_powered(device->adapter)) return btd_error_not_ready(msg); if (!btd_device_is_connected(device)) return btd_error_not_connected(msg); if (!device->bredr_state.svc_resolved) return btd_error_not_ready(msg); if (!device->tmp_records) { device->tmp_records = read_device_records(device); if (!device->tmp_records) return btd_error_does_not_exist(msg); } reply = dbus_message_new_method_return(msg); if (!reply) return btd_error_failed(msg, "Could not create method reply"); dbus_message_iter_init_append(reply, &records_arr); if (!dbus_message_iter_open_container(&records_arr, DBUS_TYPE_ARRAY, "ay", &record)) { dbus_message_unref(reply); return btd_error_failed(msg, "Could not initialize iterator"); } for (cur = device->tmp_records; cur; cur = cur->next) { DBusMessageIter record_bytes; sdp_record_t *rec = cur->data; sdp_buf_t buf; int result; result = sdp_gen_record_pdu(rec, &buf); if (result) { dbus_message_iter_abandon_container(&records_arr, &record); dbus_message_unref(reply); return btd_error_failed( msg, "Could not marshal service record"); } if (!dbus_message_iter_open_container(&record, DBUS_TYPE_ARRAY, "y", &record_bytes)) { bt_free(buf.data); dbus_message_iter_abandon_container(&records_arr, &record); dbus_message_unref(reply); return btd_error_failed( msg, "Could not initialize iterator"); } if (!dbus_message_iter_append_fixed_array( &record_bytes, DBUS_TYPE_BYTE, &buf.data, buf.data_size)) { bt_free(buf.data); dbus_message_iter_abandon_container(&record, &record_bytes); dbus_message_iter_abandon_container(&records_arr, &record); dbus_message_unref(reply); return btd_error_failed( msg, "Could not append record data to reply"); } dbus_message_iter_close_container(&record, &record_bytes); bt_free(buf.data); } dbus_message_iter_close_container(&records_arr, &record); return reply; } static const GDBusMethodTable device_methods[] = { { GDBUS_ASYNC_METHOD("Disconnect", NULL, NULL, dev_disconnect) }, { GDBUS_ASYNC_METHOD("Connect", NULL, NULL, dev_connect) }, { GDBUS_ASYNC_METHOD("ConnectProfile", GDBUS_ARGS({ "UUID", "s" }), NULL, connect_profile) }, { GDBUS_ASYNC_METHOD("DisconnectProfile", GDBUS_ARGS({ "UUID", "s" }), NULL, disconnect_profile) }, { GDBUS_ASYNC_METHOD("Pair", NULL, NULL, pair_device) }, { GDBUS_METHOD("CancelPairing", NULL, NULL, cancel_pairing) }, { GDBUS_EXPERIMENTAL_METHOD("GetServiceRecords", NULL, GDBUS_ARGS({ "Records", "aay" }), get_service_records) }, { } }; static const GDBusSignalTable device_signals[] = { { GDBUS_SIGNAL("Disconnected", GDBUS_ARGS({ "name", "s" }, { "message", "s" })) }, { } }; static gboolean dev_property_get_prefer_bearer(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device *device = data; const char *str = device_prefer_bearer_str(device); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); return TRUE; } static void dev_property_set_prefer_bearer(const GDBusPropertyTable *property, DBusMessageIter *value, GDBusPendingPropertySet id, void *data) { struct btd_device *device = data; const char *str; if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_STRING) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } dbus_message_iter_get_basic(value, &str); /* Check if current preferred bearer is the same */ if (!strcasecmp(device_prefer_bearer_str(device), str)) goto done; if (!device_set_prefer_bearer_str(device, str)) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } switch (device->prefer_bearer) { case PREFER_BREDR: /* Remove device from auto-connect list so the kernel does not * attempt to auto-connect to it in case it starts advertising. */ device_set_auto_connect(device, FALSE); break; case PREFER_LE: /* Add device to auto-connect list */ device_set_auto_connect(device, TRUE); break; } store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "PreferredBearer"); done: g_dbus_pending_property_success(id); } static gboolean dev_property_prefer_bearer_exists(const GDBusPropertyTable *property, void *data) { struct btd_device *device = data; return device_prefer_bearer_str(device) != NULL; } static const GDBusPropertyTable device_properties[] = { { "Address", "s", dev_property_get_address }, { "AddressType", "s", property_get_address_type }, { "Name", "s", dev_property_get_name, NULL, dev_property_exists_name }, { "Alias", "s", dev_property_get_alias, dev_property_set_alias }, { "Class", "u", dev_property_get_class, NULL, dev_property_exists_class }, { "Appearance", "q", dev_property_get_appearance, NULL, dev_property_exists_appearance }, { "Icon", "s", dev_property_get_icon, NULL, dev_property_exists_icon }, { "Paired", "b", dev_property_get_paired }, { "Bonded", "b", dev_property_get_bonded }, { "Trusted", "b", dev_property_get_trusted, dev_property_set_trusted }, { "Blocked", "b", dev_property_get_blocked, dev_property_set_blocked }, { "LegacyPairing", "b", dev_property_get_legacy }, { "CablePairing", "b", dev_property_get_cable_pairing }, { "RSSI", "n", dev_property_get_rssi, NULL, dev_property_exists_rssi }, { "Connected", "b", dev_property_get_connected }, { "UUIDs", "as", dev_property_get_uuids }, { "Modalias", "s", dev_property_get_modalias, NULL, dev_property_exists_modalias }, { "Adapter", "o", dev_property_get_adapter }, { "ManufacturerData", "a{qv}", dev_property_get_manufacturer_data, NULL, dev_property_manufacturer_data_exist }, { "ServiceData", "a{sv}", dev_property_get_service_data, NULL, dev_property_service_data_exist }, { "TxPower", "n", dev_property_get_tx_power, NULL, dev_property_exists_tx_power }, { "ServicesResolved", "b", dev_property_get_svc_resolved, NULL, NULL }, { "AdvertisingFlags", "ay", dev_property_get_flags, NULL, dev_property_flags_exist }, { "AdvertisingData", "a{yv}", dev_property_get_advertising_data, NULL, dev_property_advertising_data_exist }, { "WakeAllowed", "b", dev_property_get_wake_allowed, dev_property_set_wake_allowed, dev_property_wake_allowed_exist }, { "Sets", "a{oa{sv}}", dev_property_get_set, NULL, dev_property_set_exists }, { "PreferredBearer", "s", dev_property_get_prefer_bearer, dev_property_set_prefer_bearer, dev_property_prefer_bearer_exists, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { } }; uint8_t btd_device_get_bdaddr_type(struct btd_device *dev) { return dev->bdaddr_type; } bool btd_device_is_connected(struct btd_device *dev) { if (btd_device_bearer_is_connected(dev)) return true; return find_service_with_state(dev->services, BTD_SERVICE_STATE_CONNECTED); } bool btd_device_bearer_is_connected(struct btd_device *dev) { return dev->bredr_state.connected || dev->le_state.connected; } bool btd_device_bdaddr_type_connected(struct btd_device *dev, uint8_t type) { if (type == BDADDR_BREDR) return dev->bredr_state.connected; return dev->le_state.connected; } static void clear_temporary_timer(struct btd_device *dev) { if (dev->temporary_timer) { timeout_remove(dev->temporary_timer); dev->temporary_timer = 0; } } static void device_update_last_used(struct btd_device *device, uint8_t bdaddr_type) { struct bearer_state *state; state = get_state(device, bdaddr_type); state->last_used = time(NULL); if (device->prefer_bearer != PREFER_LAST_USED) return; /* If current policy is to prefer last used bearer update the state. */ state->prefer = true; if (bdaddr_type == BDADDR_BREDR) { if (device->le_state.prefer) { device->le_state.prefer = false; /* Remove device from auto-connect list so the kernel * does not attempt to auto-connect to it in case it * starts advertising. */ device_set_auto_connect(device, FALSE); } } else if (device->bredr_state.prefer) { device->bredr_state.prefer = false; /* Add device to auto-connect list */ device_set_auto_connect(device, TRUE); } store_device_info(device); } void device_add_connection(struct btd_device *dev, uint8_t bdaddr_type, uint32_t flags) { struct bearer_state *state = get_state(dev, bdaddr_type); if (dev->auth_retry_id) { timeout_remove(dev->auth_retry_id); dev->auth_retry_id = 0; dev->auth_failures = 0; } device_update_last_seen(dev, bdaddr_type, true); device_update_last_used(dev, bdaddr_type); if (state->connected) { char addr[18]; ba2str(&dev->bdaddr, addr); error("Device %s is already connected", addr); return; } bacpy(&dev->conn_bdaddr, &dev->bdaddr); dev->conn_bdaddr_type = dev->bdaddr_type; /* If this is the first connection over this bearer */ if (bdaddr_type == BDADDR_BREDR) { device_set_bredr_support(dev); btd_bearer_connected(dev->bredr, 0); } else { device_set_le_support(dev, bdaddr_type); btd_bearer_connected(dev->le, 0); } state->connected = true; state->initiator = flags & BIT(3); if (dev->le_state.connected && dev->bredr_state.connected) return; /* Remove temporary timer while connected */ clear_temporary_timer(dev); g_dbus_emit_property_changed(dbus_conn, dev->path, DEVICE_INTERFACE, "Connected"); } static bool device_service_connected(struct btd_device *dev) { if (find_service_with_state(dev->services, BTD_SERVICE_STATE_CONNECTING)) return true; return find_service_with_state(dev->services, BTD_SERVICE_STATE_CONNECTED); } static bool device_disappeared(gpointer user_data) { struct btd_device *dev = user_data; /* If there are services connected restart the timer to give more time * for the service to either complete the connection or disconnect. */ if (device_service_connected(dev)) return TRUE; dev->temporary_timer = 0; btd_adapter_remove_device(dev->adapter, dev); return FALSE; } static void set_temporary_timer(struct btd_device *dev, unsigned int timeout) { clear_temporary_timer(dev); if (!timeout) return; dev->temporary_timer = timeout_add_seconds(timeout, device_disappeared, dev, NULL); } static void device_disconnected(struct btd_device *device, uint8_t reason) { const char *name; const char *message; switch (reason) { case MGMT_DEV_DISCONN_UNKNOWN: name = "org.bluez.Reason.Unknown"; message = "Unspecified"; break; case MGMT_DEV_DISCONN_TIMEOUT: name = "org.bluez.Reason.Timeout"; message = "Connection timeout"; break; case MGMT_DEV_DISCONN_LOCAL_HOST: name = "org.bluez.Reason.Local"; message = "Connection terminated by local host"; break; case MGMT_DEV_DISCONN_REMOTE: name = "org.bluez.Reason.Remote"; message = "Connection terminated by remote user"; break; case MGMT_DEV_DISCONN_AUTH_FAILURE: name = "org.bluez.Reason.Authentication"; message = "Connection terminated due to authentication failure"; break; case MGMT_DEV_DISCONN_LOCAL_HOST_SUSPEND: name = "org.bluez.Reason.Suspend"; message = "Connection terminated by local host for suspend"; break; default: warn("Unknown disconnection value: %u", reason); name = "org.bluez.Reason.Unknown"; message = "Unspecified"; } g_dbus_emit_signal(dbus_conn, device->path, DEVICE_INTERFACE, "Disconnected", DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &message, DBUS_TYPE_INVALID); } static bool device_auth_failure_retry(gpointer user_data) { struct btd_device *device = user_data; device_set_auto_connect(device, TRUE); return false; } void device_remove_connection(struct btd_device *device, uint8_t bdaddr_type, bool *remove, uint8_t reason) { struct bearer_state *state = get_state(device, bdaddr_type); DBusMessage *reply; bool remove_device = false; bool paired_status_updated = false; if (!state->connected) return; state->connected = false; state->initiator = false; device->general_connect = FALSE; device_set_svc_refreshed(device, false); if (device->disconn_timer > 0) { timeout_remove(device->disconn_timer); device->disconn_timer = 0; } /* This could be executed while the client is waiting for Connect() but * att_connect_cb has not been invoked. * In that case reply the client that the connection failed. */ if (device->connect) { DBG("connection removed while Connect() is waiting reply"); reply = btd_error_failed(device->connect, ERR_BREDR_CONN_CANCELED); g_dbus_send_message(dbus_conn, reply); dbus_message_unref(device->connect); device->connect = NULL; } /* Update bearer interface */ if (bdaddr_type == BDADDR_BREDR) btd_bearer_disconnected(device->bredr, reason); else btd_bearer_disconnected(device->le, reason); /* Check paired status of both bearers since it's possible to be * paired but not connected via link key to LTK conversion. */ if (!device->bredr_state.connected && device->bredr_state.paired && !device->bredr_state.bonded) { btd_adapter_remove_bonding(device->adapter, &device->bdaddr, BDADDR_BREDR); device->bredr_state.paired = false; paired_status_updated = true; btd_bearer_paired(device->bredr); } if (!device->le_state.connected && device->le_state.paired && !device->le_state.bonded) { btd_adapter_remove_bonding(device->adapter, &device->bdaddr, device->bdaddr_type); device->le_state.paired = false; paired_status_updated = true; btd_bearer_paired(device->le); } /* report change only if both bearers are unpaired */ if (!device->bredr_state.paired && !device->le_state.paired && paired_status_updated) g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Paired"); if (device->bredr_state.connected || device->le_state.connected) return; device_update_last_seen(device, bdaddr_type, true); g_slist_free_full(device->eir_uuids, g_free); device->eir_uuids = NULL; /* * Check if device is marked for auto_connect before attempting to limit * the number of retries. */ if (device->auto_connect && reason == MGMT_DEV_DISCONN_AUTH_FAILURE) { /* * In case of an auth failure, implement an * exponential backoff retry logic. We disable auto * connect immediately to prevent us from retrying to * connect to this device again and start an 1, 2, 4 * second timers to re-enable that knob in hopes that * subsequent retires will be more successul */ device_set_auto_connect(device, FALSE); if (device->auth_failures < AUTH_FAILURES_THRESHOLD) { uint8_t timer = 1 << device->auth_failures; device->auth_retry_id = timeout_add_seconds(timer, device_auth_failure_retry, device, NULL); DBG("Auth failure, retrying in %d seconds", timer); device->auth_failures++; } else { DBG("Disabling auto connect due to too many auth " "failures"); device->auth_failures = 0; } } device_disconnected(device, reason); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Connected"); /* remove device only if both bearers are disconnected */ while (device->disconnects) { DBusMessage *msg = device->disconnects->data; if (dbus_message_is_method_call(msg, ADAPTER_INTERFACE, "RemoveDevice")) remove_device = true; g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID); device->disconnects = g_slist_remove(device->disconnects, msg); dbus_message_unref(msg); } if (remove_device) *remove = remove_device; } guint device_add_disconnect_watch(struct btd_device *device, disconnect_watch watch, void *user_data, GDestroyNotify destroy) { struct btd_disconnect_data *data; static guint id = 0; data = g_new0(struct btd_disconnect_data, 1); data->id = ++id; data->watch = watch; data->user_data = user_data; data->destroy = destroy; device->watches = g_slist_append(device->watches, data); return data->id; } void device_remove_disconnect_watch(struct btd_device *device, guint id) { GSList *l; for (l = device->watches; l; l = l->next) { struct btd_disconnect_data *data = l->data; if (data->id == id) { device->watches = g_slist_remove(device->watches, data); if (data->destroy) data->destroy(data->user_data); g_free(data); return; } } } static char *load_cached_name(struct btd_device *device, const char *local, const char *peer) { char filename[PATH_MAX]; GKeyFile *key_file; char *str = NULL; int len; if (device_address_is_private(device)) return NULL; create_filename(filename, PATH_MAX, "/%s/cache/%s", local, peer); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, NULL)) goto failed; str = g_key_file_get_string(key_file, "General", "Name", NULL); if (str) { len = strlen(str); if (len > HCI_MAX_NAME_LENGTH) str[HCI_MAX_NAME_LENGTH] = '\0'; } failed: g_key_file_free(key_file); return str; } static void load_cached_name_resolve(struct btd_device *device, const char *local, const char *peer) { char filename[PATH_MAX]; GKeyFile *key_file; uint64_t failed_time; if (device_address_is_private(device)) return; create_filename(filename, PATH_MAX, "/%s/cache/%s", local, peer); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, NULL)) goto failed; failed_time = g_key_file_get_uint64(key_file, "NameResolving", "FailedTime", NULL); device->name_resolve_failed_time = failed_time; failed: g_key_file_free(key_file); } static struct csrk_info *load_csrk(GKeyFile *key_file, const char *group) { struct csrk_info *csrk; char *str; int i; str = g_key_file_get_string(key_file, group, "Key", NULL); if (!str) return NULL; csrk = g_new0(struct csrk_info, 1); for (i = 0; i < 16; i++) { if (sscanf(str + (i * 2), "%2hhx", &csrk->key[i]) != 1) goto fail; } /* * In case of older storage this will return 0 which is fine since it * didn't support signing at that point the counter should never have * been used. */ csrk->counter = g_key_file_get_integer(key_file, group, "Counter", NULL); g_free(str); return csrk; fail: g_free(str); g_free(csrk); return NULL; } static struct sirk_info *load_sirk(GKeyFile *key_file, uint8_t index) { char group[28]; struct sirk_info *sirk; char *str; int i; sprintf(group, "SetIdentityResolvingKey#%u", index); str = g_key_file_get_string(key_file, group, "Key", NULL); if (!str) return NULL; sirk = g_new0(struct sirk_info, 1); for (i = 0; i < 16; i++) { if (sscanf(str + (i * 2), "%2hhx", &sirk->key[i]) != 1) goto fail; } sirk->encrypted = g_key_file_get_boolean(key_file, group, "Encrypted", NULL); sirk->size = g_key_file_get_integer(key_file, group, "Size", NULL); sirk->rank = g_key_file_get_integer(key_file, group, "Rank", NULL); g_free(str); return sirk; fail: g_free(str); g_free(sirk); return NULL; } static void load_sirks(struct btd_device *device, GKeyFile *key_file) { struct sirk_info *sirk; uint8_t i; for (i = 0; i < UINT8_MAX; i++) { sirk = load_sirk(key_file, i); if (!sirk) break; queue_push_tail(device->sirks, sirk); /* Only add DeviceSet object if sirk does need * decryption otherwise it has to wait for the LTK in * order to decrypt. */ if (!sirk->encrypted) btd_set_add_device(device, NULL, sirk->key, sirk->size); } } static void load_services(struct btd_device *device, char **uuids) { char **uuid; for (uuid = uuids; *uuid; uuid++) { if (g_slist_find_custom(device->uuids, *uuid, bt_uuid_strcmp)) continue; device->uuids = g_slist_insert_sorted(device->uuids, g_strdup(*uuid), bt_uuid_strcmp); } g_strfreev(uuids); } static void convert_info(struct btd_device *device, GKeyFile *key_file) { char filename[PATH_MAX]; char adapter_addr[18]; char device_addr[18]; char **uuids; char *str; gsize length = 0; GError *gerr = NULL; /* Load device profile list from legacy properties */ uuids = g_key_file_get_string_list(key_file, "General", "SDPServices", NULL, NULL); if (uuids) load_services(device, uuids); uuids = g_key_file_get_string_list(key_file, "General", "GATTServices", NULL, NULL); if (uuids) load_services(device, uuids); if (!device->uuids) return; /* Remove old entries so they are not loaded again */ g_key_file_remove_key(key_file, "General", "SDPServices", NULL); g_key_file_remove_key(key_file, "General", "GATTServices", NULL); ba2str(btd_adapter_get_address(device->adapter), adapter_addr); ba2str(&device->bdaddr, device_addr); create_filename(filename, PATH_MAX, "/%s/%s/info", adapter_addr, device_addr); str = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, str, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(str); store_device_info(device); } static void load_info(struct btd_device *device, const char *local, const char *peer, GKeyFile *key_file) { GError *gerr = NULL; char *str; gboolean store_needed = FALSE; gboolean blocked; gboolean wake_allowed; char **uuids; int source, vendor, product, version; char **techno, **t; /* Load device name from storage info file, if that fails fall back to * the cache. */ str = g_key_file_get_string(key_file, "General", "Name", NULL); if (str == NULL) { str = load_cached_name(device, local, peer); if (str) store_needed = TRUE; } if (str) { strcpy(device->name, str); g_free(str); } /* Load alias */ device->alias = g_key_file_get_string(key_file, "General", "Alias", NULL); /* Load class */ str = g_key_file_get_string(key_file, "General", "Class", NULL); if (str) { uint32_t class; if (sscanf(str, "%x", &class) == 1) device->class = class; g_free(str); } /* Load appearance */ str = g_key_file_get_string(key_file, "General", "Appearance", NULL); if (str) { device->appearance = strtol(str, NULL, 16); g_free(str); } /* Load device technology */ techno = g_key_file_get_string_list(key_file, "General", "SupportedTechnologies", NULL, NULL); if (!techno) goto next; for (t = techno; *t; t++) { if (g_str_equal(*t, "BR/EDR")) device->bredr = btd_bearer_new(device, BDADDR_BREDR); else if (g_str_equal(*t, "LE")) device->le = btd_bearer_new(device, BDADDR_LE_PUBLIC); else error("Unknown device technology"); } if (!device->le) { device->bdaddr_type = BDADDR_BREDR; } else { str = g_key_file_get_string(key_file, "General", "AddressType", NULL); if (str && g_str_equal(str, "public")) device->bdaddr_type = BDADDR_LE_PUBLIC; else if (str && g_str_equal(str, "static")) device->bdaddr_type = BDADDR_LE_RANDOM; else error("Unknown LE device technology"); g_free(str); device->local_csrk = load_csrk(key_file, "LocalSignatureKey"); device->remote_csrk = load_csrk(key_file, "RemoteSignatureKey"); load_sirks(device, key_file); } g_strfreev(techno); /* Load preferred bearer */ str = g_key_file_get_string(key_file, "General", "PreferredBearer", NULL); if (str) { device_set_prefer_bearer_str(device, str); g_free(str); /* Load last used bearer */ str = g_key_file_get_string(key_file, "General", "LastUsedBearer", NULL); if (str) { device_update_last_used(device, !strcmp(str, "le") ? device->bdaddr_type : BDADDR_BREDR); g_free(str); } } next: /* Load trust */ device->trusted = g_key_file_get_boolean(key_file, "General", "Trusted", NULL); /* Load device blocked */ blocked = g_key_file_get_boolean(key_file, "General", "Blocked", NULL); if (blocked) device_block(device, FALSE); device->cable_pairing = g_key_file_get_boolean(key_file, "General", "CablePairing", NULL); /* Load device profile list */ uuids = g_key_file_get_string_list(key_file, "General", "Services", NULL, NULL); if (uuids) { char filename[PATH_MAX]; char device_addr[18]; struct stat st; GKeyFile *key_file = g_key_file_new(); GError *gerr = NULL; load_services(device, uuids); ba2str(&device->bdaddr, device_addr); create_filename(filename, PATH_MAX, "/%s/cache/%s", btd_adapter_get_storage_dir(device->adapter), device_addr); /* Check if ServiceRecords cached group exists */ if (stat(filename, &st) < 0) { DBG("Missing cache file for ServiceRecords"); device->bredr_state.svc_resolved = false; } else if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { DBG("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); device->bredr_state.svc_resolved = false; } else if (!g_key_file_has_group(key_file, "ServiceRecords")) { DBG("Missing ServiceRecords from cache file"); device->bredr_state.svc_resolved = false; } else { /* Discovered services restored from storage */ device->bredr_state.svc_resolved = true; } g_key_file_free(key_file); } /* Load device id */ source = g_key_file_get_integer(key_file, "DeviceID", "Source", NULL); if (source) { vendor = g_key_file_get_integer(key_file, "DeviceID", "Vendor", NULL); product = g_key_file_get_integer(key_file, "DeviceID", "Product", NULL); version = g_key_file_get_integer(key_file, "DeviceID", "Version", NULL); btd_device_set_pnpid(device, source, vendor, product, version); } /* Wake allowed is only configured and stored if user changed it. * Otherwise, we enable if profile supports it. */ wake_allowed = g_key_file_get_boolean(key_file, "General", "WakeAllowed", &gerr); if (!gerr) { device_set_wake_override(device, wake_allowed); } else { g_error_free(gerr); gerr = NULL; } if (store_needed) store_device_info(device); } static void load_att_info(struct btd_device *device, const char *local, const char *peer) { char filename[PATH_MAX]; struct stat st; GKeyFile *key_file; GError *gerr = NULL; char *prim_uuid, *str; char **groups, **handle, *service_uuid; struct gatt_primary *prim; uuid_t uuid; char tmp[3]; int i; create_filename(filename, PATH_MAX, "/%s/%s/attributes", local, peer); /* Check if attributes file exists */ if (stat(filename, &st) < 0) return; key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } groups = g_key_file_get_groups(key_file, NULL); sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); prim_uuid = bt_uuid2string(&uuid); for (handle = groups; *handle; handle++) { gboolean uuid_ok; int end; str = g_key_file_get_string(key_file, *handle, "UUID", NULL); if (!str) continue; uuid_ok = g_str_equal(str, prim_uuid); g_free(str); if (!uuid_ok) continue; str = g_key_file_get_string(key_file, *handle, "Value", NULL); if (!str) continue; end = g_key_file_get_integer(key_file, *handle, "EndGroupHandle", NULL); if (end == 0) { g_free(str); continue; } prim = g_new0(struct gatt_primary, 1); prim->range.start = atoi(*handle); prim->range.end = end; switch (strlen(str)) { case 4: uuid.type = SDP_UUID16; sscanf(str, "%04hx", &uuid.value.uuid16); break; case 8: uuid.type = SDP_UUID32; sscanf(str, "%08x", &uuid.value.uuid32); break; case 32: uuid.type = SDP_UUID128; memset(tmp, 0, sizeof(tmp)); for (i = 0; i < 16; i++) { memcpy(tmp, str + (i * 2), 2); uuid.value.uuid128.data[i] = (uint8_t) strtol(tmp, NULL, 16); } break; default: g_free(str); g_free(prim); continue; } service_uuid = bt_uuid2string(&uuid); memcpy(prim->uuid, service_uuid, MAX_LEN_UUID_STR); free(service_uuid); g_free(str); device->primaries = g_slist_append(device->primaries, prim); } g_strfreev(groups); g_key_file_free(key_file); free(prim_uuid); } static void device_register_primaries(struct btd_device *device, GSList *prim_list, int psm) { device->primaries = g_slist_concat(device->primaries, prim_list); } static void add_primary(struct gatt_db_attribute *attr, void *user_data) { GSList **new_services = user_data; struct gatt_primary *prim; bt_uuid_t uuid; prim = g_new0(struct gatt_primary, 1); if (!prim) { DBG("Failed to allocate gatt_primary structure"); return; } gatt_db_attribute_get_service_handles(attr, &prim->range.start, &prim->range.end); gatt_db_attribute_get_service_uuid(attr, &uuid); bt_uuid_to_string(&uuid, prim->uuid, sizeof(prim->uuid)); *new_services = g_slist_append(*new_services, prim); } static void load_gatt_db(struct btd_device *device, const char *local, const char *peer) { char filename[PATH_MAX]; int err; if (!gatt_cache_is_enabled(device)) return; DBG("Restoring %s gatt database from file", peer); create_filename(filename, PATH_MAX, "/%s/cache/%s", local, peer); err = btd_settings_gatt_db_load(device->db, filename); if (err < 0) { if (err == -ENOENT) return; warn("Error loading db from cache for %s: %s (%d)", peer, strerror(-err), err); } g_slist_free_full(device->primaries, g_free); device->primaries = NULL; gatt_db_foreach_service(device->db, NULL, add_primary, &device->primaries); } static void device_add_uuids(struct btd_device *device, GSList *uuids) { GSList *l; bool changed = false; for (l = uuids; l != NULL; l = g_slist_next(l)) { GSList *match = g_slist_find_custom(device->uuids, l->data, bt_uuid_strcmp); if (match) continue; changed = true; device->uuids = g_slist_insert_sorted(device->uuids, g_strdup(l->data), bt_uuid_strcmp); } if (changed) g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "UUIDs"); } static bool device_match_profile(struct btd_device *device, struct btd_profile *profile, GSList *uuids) { GSList *l; if (profile->remote_uuid == NULL) return false; l = g_slist_find_custom(uuids, profile->remote_uuid, bt_uuid_strcmp); if (!l) return false; return true; } static void add_gatt_service(struct gatt_db_attribute *attr, void *user_data) { struct btd_device *device = user_data; struct btd_service *service; struct btd_profile *profile; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; GSList *l; gatt_db_attribute_get_service_uuid(attr, &uuid); bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); /* Check if service was already probed */ l = find_service_with_uuid(device->services, uuid_str); if (l) goto done; /* Add UUID and probe service */ btd_device_add_uuid(device, uuid_str); /* Check if service was probed */ l = find_service_with_uuid(device->services, uuid_str); if (!l) return; done: /* Mark service as active to skip discovering it again */ gatt_db_service_set_active(attr, true); service = l->data; profile = btd_service_get_profile(service); /* Claim attributes of internal profiles */ if (!profile->external) { /* Mark the service as claimed by the existing profile. */ gatt_db_service_set_claimed(attr, true); } /* Notify driver about the new connection */ service_accept(service, btd_device_is_initiator(device)); } static const struct btd_profile *get_gatt_profile(void *data, void *user_data) { struct gatt_db_attribute *attr = data; struct btd_profile *profile; bt_uuid_t uuid; struct btd_profile *dummy_profile = user_data; char *uuid_str = (char *)dummy_profile->remote_uuid; gatt_db_attribute_get_service_uuid(attr, &uuid); bt_uuid_to_string(&uuid, uuid_str, MAX_LEN_UUID_STR); profile = btd_profile_find_remote_uuid(uuid_str); if (!profile) profile = dummy_profile; return profile; } static void get_gatt_attrs(struct gatt_db_attribute *attr, void *user_data) { GSList **list = user_data; *list = g_slist_append(*list, attr); } static void device_add_gatt_services(struct btd_device *device) { char addr[18]; GSList *attrs = NULL; GSList *entry; char uuid_str[MAX_LEN_UUID_STR]; struct btd_profile dummy_profile = { .remote_uuid = uuid_str, }; ba2str(&device->bdaddr, addr); if (device->blocked) { DBG("Skipping profiles for blocked device %s", addr); return; } /* Probe and accept in profile priority order */ gatt_db_foreach_service(device->db, NULL, get_gatt_attrs, &attrs); attrs = btd_profile_sort_list(attrs, get_gatt_profile, &dummy_profile); for (entry = attrs; entry; entry = g_slist_next(entry)) add_gatt_service(entry->data, device); g_slist_free(attrs); } static void device_accept_gatt_profiles(struct btd_device *device) { GSList *l; bool initiator = btd_device_is_initiator(device); GSList *services; DBG("initiator %s", initiator ? "true" : "false"); /* Accept in profile priority order */ services = g_slist_copy(device->services); services = btd_profile_sort_list(services, get_service_profile, NULL); for (l = services; l != NULL; l = g_slist_next(l)) service_accept(l->data, initiator); g_slist_free(services); } static void device_remove_gatt_service(struct btd_device *device, struct gatt_db_attribute *attr) { struct btd_service *service; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; GSList *l; gatt_db_attribute_get_service_uuid(attr, &uuid); bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); l = find_service_with_uuid(device->services, uuid_str); if (!l) return; service = l->data; device->services = g_slist_delete_link(device->services, l); device->pending = g_slist_remove(device->pending, service); service_remove(service); } static gboolean gatt_services_changed(gpointer user_data) { struct btd_device *device = user_data; store_gatt_db(device); return FALSE; } static void gatt_service_added(struct gatt_db_attribute *attr, void *user_data) { struct btd_device *device = user_data; GSList *new_service = NULL; uint16_t start, end; if (!bt_gatt_client_is_ready(device->client)) return; gatt_db_attribute_get_service_data(attr, &start, &end, NULL, NULL); DBG("start: 0x%04x, end: 0x%04x", start, end); /* * TODO: Remove the primaries list entirely once all profiles use * shared/gatt. */ add_primary(attr, &new_service); if (!new_service) return; device_register_primaries(device, new_service, -1); add_gatt_service(attr, device); btd_gatt_client_service_added(device->client_dbus, attr); gatt_services_changed(device); } static gint prim_attr_cmp(gconstpointer a, gconstpointer b) { const struct gatt_primary *prim = a; const struct gatt_db_attribute *attr = b; uint16_t start, end; gatt_db_attribute_get_service_handles(attr, &start, &end); return !(prim->range.start == start && prim->range.end == end); } static gint prim_uuid_cmp(gconstpointer a, gconstpointer b) { const struct gatt_primary *prim = a; const char *uuid = b; return bt_uuid_strcmp(prim->uuid, uuid); } static void gatt_service_removed(struct gatt_db_attribute *attr, void *user_data) { struct btd_device *device = user_data; GSList *l; struct gatt_primary *prim; uint16_t start, end; /* * NOTE: shared/gatt-client clears the database in case of failure. This * triggers the service_removed callback for all affected services. * Hence, this function will be called in the following cases: * * 1. When a GATT service gets removed due to "Service Changed". * * 2. When a GATT service gets removed when the database get cleared * upon disconnection with a non-bonded device. * * 3. When a GATT service gets removed when the database get cleared * by shared/gatt-client when its initialization procedure fails, * e.g. due to an ATT protocol error or an unexpected disconnect. * In this case the gatt-client will not be ready. */ gatt_db_attribute_get_service_handles(attr, &start, &end); DBG("start: 0x%04x, end: 0x%04x", start, end); /* Remove the corresponding gatt_primary */ l = g_slist_find_custom(device->primaries, attr, prim_attr_cmp); if (!l) return; prim = l->data; device->primaries = g_slist_delete_link(device->primaries, l); /* * Remove the corresponding UUIDs entry and profile, only if this is * the last service with this UUID. */ l = g_slist_find_custom(device->uuids, prim->uuid, bt_uuid_strcmp); if (l && !g_slist_find_custom(device->primaries, prim->uuid, prim_uuid_cmp)) { /* * If this happened since the db was cleared for a non-bonded * device, then don't remove the btd_service just yet. We do * this so that we can avoid re-probing the profile if the same * GATT service is found on the device on re-connection. * However, if the device is marked as temporary, then we * remove it anyway. */ if (device->client || device->temporary == TRUE) device_remove_gatt_service(device, attr); g_free(l->data); device->uuids = g_slist_delete_link(device->uuids, l); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "UUIDs"); } g_free(prim); store_device_info(device); btd_gatt_client_service_removed(device->client_dbus, attr); gatt_services_changed(device); } static struct btd_device *device_new(struct btd_adapter *adapter, const char *address) { char *address_up; struct btd_device *device; const char *adapter_path = adapter_get_path(adapter); DBG("address %s", address); device = g_try_malloc0(sizeof(struct btd_device)); if (device == NULL) return NULL; device->tx_power = 127; device->volume = -1; device->wake_id = -1U; device->db = gatt_db_new(); if (!device->db) { g_free(device); return NULL; } memset(device->ad_flags, INVALID_FLAGS, sizeof(device->ad_flags)); device->ad = bt_ad_new(); if (!device->ad) { device_free(device); return NULL; } address_up = g_ascii_strup(address, -1); device->path = g_strdup_printf("%s/dev_%s", adapter_path, address_up); g_strdelimit(device->path, ":", '_'); g_free(address_up); str2ba(address, &device->bdaddr); device->client_dbus = btd_gatt_client_new(device); if (!device->client_dbus) { error("Failed to create btd_gatt_client"); device_free(device); return NULL; } DBG("Creating device %s", device->path); if (g_dbus_register_interface(dbus_conn, device->path, DEVICE_INTERFACE, device_methods, device_signals, device_properties, device, device_free) == FALSE) { error("Unable to register device interface for %s", address); device_free(device); return NULL; } device->adapter = adapter; device->sirks = queue_new(); device->temporary = true; device->db_id = gatt_db_register(device->db, gatt_service_added, gatt_service_removed, device, NULL); device->refresh_discovery = btd_opts.refresh_discovery; return btd_device_ref(device); } struct btd_device *device_create_from_storage(struct btd_adapter *adapter, const char *address, GKeyFile *key_file) { struct btd_device *device; const char *src_dir; DBG("address %s", address); device = device_new(adapter, address); if (device == NULL) return NULL; convert_info(device, key_file); src_dir = btd_adapter_get_storage_dir(adapter); load_info(device, src_dir, address, key_file); load_att_info(device, src_dir, address); return device; } struct btd_device *device_create(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type) { struct btd_device *device; char dst[18]; char *str; const char *storage_dir; ba2str(bdaddr, dst); DBG("dst %s", dst); device = device_new(adapter, dst); if (device == NULL) return NULL; device->bdaddr_type = bdaddr_type; if (bdaddr_type == BDADDR_BREDR) device->bredr = btd_bearer_new(device, BDADDR_BREDR); else device->le = btd_bearer_new(device, BDADDR_LE_PUBLIC); storage_dir = btd_adapter_get_storage_dir(adapter); str = load_cached_name(device, storage_dir, dst); if (str) { strcpy(device->name, str); g_free(str); } load_cached_name_resolve(device, storage_dir, dst); return device; } char *btd_device_get_storage_path(struct btd_device *device, const char *name) { char filename[PATH_MAX]; char dstaddr[18]; if (device_address_is_private(device)) { warn("Refusing storage path for private addressed device %s", device->path); return NULL; } ba2str(&device->bdaddr, dstaddr); if (!name) create_filename(filename, PATH_MAX, "/%s/%s", btd_adapter_get_storage_dir(device->adapter), dstaddr); else create_filename(filename, PATH_MAX, "/%s/%s/%s", btd_adapter_get_storage_dir(device->adapter), dstaddr, name); return strdup(filename); } void btd_device_device_set_name(struct btd_device *device, const char *name) { if (strncmp(name, device->name, MAX_NAME_LENGTH) == 0) return; DBG("%s %s", device->path, name); strncpy(device->name, name, MAX_NAME_LENGTH); store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Name"); if (device->alias != NULL) return; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Alias"); } void device_get_name(struct btd_device *device, char *name, size_t len) { if (name != NULL && len > 0) { strncpy(name, device->name, len - 1); name[len - 1] = '\0'; } } bool device_name_known(struct btd_device *device) { return device->name[0] != '\0'; } bool device_is_name_resolve_allowed(struct btd_device *device) { struct timespec now; if (!device) return false; clock_gettime(CLOCK_MONOTONIC, &now); /* failed_time is empty (0), meaning no prior failure. */ if (device->name_resolve_failed_time == 0) return true; /* now < failed_time, meaning the clock has somehow turned back, * possibly because of system restart. Allow just to be safe. */ if (now.tv_sec < device->name_resolve_failed_time) return true; /* now >= failed_time + name_request_retry_delay, meaning the * period of not sending name request is over. */ if (now.tv_sec >= (time_t)(device->name_resolve_failed_time + btd_opts.name_request_retry_delay)) return true; return false; } void device_name_resolve_fail(struct btd_device *device) { struct timespec now; if (!device) return; clock_gettime(CLOCK_MONOTONIC, &now); device->name_resolve_failed_time = now.tv_sec; device_store_cached_name_resolve(device); } void device_set_class(struct btd_device *device, uint32_t class) { if (device->class == class) return; DBG("%s 0x%06X", device->path, class); device->class = class; store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Class"); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Icon"); } void device_set_privacy(struct btd_device *device, bool value, const uint8_t *irk) { device->privacy = value; free(device->irk); if (irk) device->irk = util_memdup(irk, 16); else device->irk = NULL; } bool device_get_privacy(struct btd_device *device) { if (device->privacy) return true; return device_address_is_private(device); } void device_update_addr(struct btd_device *device, const bdaddr_t *bdaddr, uint8_t bdaddr_type, const uint8_t *irk) { bool auto_connect = device->auto_connect; device_set_privacy(device, true, irk); if (!bacmp(bdaddr, &device->bdaddr) && bdaddr_type == device->bdaddr_type) return; /* Since this function is only used for LE SMP Identity * Resolving purposes we can now assume LE is supported. */ if (!device->le) device->le = btd_bearer_new(device, BDADDR_LE_PUBLIC); /* Remove old address from accept/auto-connect list since its address * will be changed. */ if (auto_connect) device_set_auto_connect(device, FALSE); bacpy(&device->bdaddr, bdaddr); device->bdaddr_type = bdaddr_type; if (device->temporary) btd_device_set_temporary(device, false); else store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Address"); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "AddressType"); if (auto_connect) device_set_auto_connect(device, TRUE); } void device_set_bredr_support(struct btd_device *device) { if (btd_opts.mode == BT_MODE_LE || device->bredr) return; if (!device->bredr) device->bredr = btd_bearer_new(device, BDADDR_BREDR); if (device->le) g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "PreferredBearer"); store_device_info(device); } void device_set_le_support(struct btd_device *device, uint8_t bdaddr_type) { if (btd_opts.mode == BT_MODE_BREDR || device->le) return; if (!device->le) device->le = btd_bearer_new(device, BDADDR_LE_PUBLIC); device->bdaddr_type = bdaddr_type; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "AddressType"); if (device->bredr) g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "PreferredBearer"); store_device_info(device); } void device_update_last_seen(struct btd_device *device, uint8_t bdaddr_type, bool connectable) { struct bearer_state *state; state = get_state(device, bdaddr_type); state->last_seen = time(NULL); state->connectable = connectable; if (!device_is_temporary(device)) return; /* Restart temporary timer */ set_temporary_timer(device, btd_opts.tmpto); } void btd_device_set_connectable(struct btd_device *device, bool connectable) { device_update_last_seen(device, device->bdaddr_type, connectable); } /* It is possible that we have two device objects for the same device in * case it has first been discovered over BR/EDR and has a private * address when discovered over LE for the first time. In such a case we * need to inherit critical values from the duplicate so that we don't * overwrite them when writing to storage. The next time bluetoothd * starts the device will show up as a single instance. */ void device_merge_duplicate(struct btd_device *dev, struct btd_device *dup) { GSList *l; DBG(""); dev->bredr = dup->bredr; dev->trusted = dup->trusted; dev->blocked = dup->blocked; for (l = dup->uuids; l; l = g_slist_next(l)) dev->uuids = g_slist_append(dev->uuids, g_strdup(l->data)); if (dev->name[0] == '\0') strcpy(dev->name, dup->name); if (!dev->alias) dev->alias = g_strdup(dup->alias); dev->class = dup->class; dev->vendor_src = dup->vendor_src; dev->vendor = dup->vendor; dev->product = dup->product; dev->version = dup->version; } uint32_t btd_device_get_class(struct btd_device *device) { return device->class; } uint16_t btd_device_get_vendor(struct btd_device *device) { return device->vendor; } uint16_t btd_device_get_vendor_src(struct btd_device *device) { return device->vendor_src; } uint16_t btd_device_get_product(struct btd_device *device) { return device->product; } uint16_t btd_device_get_version(struct btd_device *device) { return device->version; } static void delete_folder_tree(const char *dirname) { DIR *dir; struct dirent *entry; char filename[PATH_MAX]; dir = opendir(dirname); if (dir == NULL) return; while ((entry = readdir(dir)) != NULL) { if (g_str_equal(entry->d_name, ".") || g_str_equal(entry->d_name, "..")) continue; if (entry->d_type == DT_UNKNOWN) entry->d_type = util_get_dt(dirname, entry->d_name); snprintf(filename, PATH_MAX, "%s/%s", dirname, entry->d_name); if (entry->d_type == DT_DIR) delete_folder_tree(filename); else unlink(filename); } closedir(dir); rmdir(dirname); } void device_remove_bonding(struct btd_device *device, uint8_t bdaddr_type) { if (bdaddr_type == BDADDR_BREDR) device->bredr_state.bonded = false; else device->le_state.bonded = false; if (!device->bredr_state.bonded && !device->le_state.bonded) btd_device_set_temporary(device, true); btd_adapter_remove_bonding(device->adapter, &device->bdaddr, bdaddr_type); } static void device_remove_stored(struct btd_device *device) { char device_addr[18]; char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; char *data; gsize length = 0; if (device->bredr_state.bonded) device_remove_bonding(device, BDADDR_BREDR); if (device->le_state.bonded) device_remove_bonding(device, device->bdaddr_type); device->bredr_state.paired = false; device->le_state.paired = false; if (device->blocked) device_unblock(device, TRUE, FALSE); ba2str(&device->bdaddr, device_addr); create_filename(filename, PATH_MAX, "/%s/%s", btd_adapter_get_storage_dir(device->adapter), device_addr); delete_folder_tree(filename); create_filename(filename, PATH_MAX, "/%s/cache/%s", btd_adapter_get_storage_dir(device->adapter), device_addr); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { g_error_free(gerr); g_key_file_free(key_file); return; } g_key_file_remove_group(key_file, "ServiceRecords", NULL); g_key_file_remove_group(key_file, "Attributes", NULL); g_key_file_remove_group(key_file, "Endpoints", NULL); data = g_key_file_to_data(key_file, &length, NULL); if (length > 0) { create_file(filename, 0600); if (!g_file_set_contents(filename, data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } } else { unlink(filename); } g_free(data); g_key_file_free(key_file); } void device_remove(struct btd_device *device, gboolean remove_stored) { DBG("Removing device %s", device->path); if (device->auth_retry_id) { device->auth_retry_id = 0; timeout_remove(device->auth_retry_id); } if (device->auto_connect) { device->disable_auto_connect = TRUE; device_set_auto_connect(device, FALSE); } if (device->bonding) { uint8_t status; if (device->bredr_state.connected) status = MGMT_STATUS_DISCONNECTED; else status = MGMT_STATUS_CONNECT_FAILED; device_cancel_bonding(device, status); } if (device->browse) browse_request_cancel(device->browse); while (device->services != NULL) { struct btd_service *service = device->services->data; device->services = g_slist_remove(device->services, service); service_remove(service); } g_slist_free(device->pending); device->pending = NULL; if (btd_device_is_connected(device)) { if (device->disconn_timer > 0) timeout_remove(device->disconn_timer); disconnect_all(device); } clear_temporary_timer(device); if (device->store_id > 0) { g_source_remove(device->store_id); device->store_id = 0; if (!remove_stored) store_device_info_cb(device); } if (remove_stored) device_remove_stored(device); btd_device_unref(device); } int device_address_cmp(gconstpointer a, gconstpointer b) { const struct btd_device *device = a; const char *address = b; char addr[18]; ba2str(&device->bdaddr, addr); return strcasecmp(addr, address); } int device_bdaddr_cmp(gconstpointer a, gconstpointer b) { const struct btd_device *device = a; const bdaddr_t *bdaddr = b; return bacmp(&device->bdaddr, bdaddr); } static bool addr_is_public(uint8_t addr_type) { if (addr_type == BDADDR_BREDR || addr_type == BDADDR_LE_PUBLIC) return true; return false; } static bool addr_is_resolvable(const bdaddr_t *bdaddr, uint8_t addr_type) { if (addr_type != BDADDR_LE_RANDOM) return false; switch (bdaddr->b[5] >> 6) { case 0x01: /* Private resolvable */ return true; default: return false; } } static int device_irk_cmp(const struct btd_device *device, const struct device_addr_type *addr) { struct bt_crypto *crypto; uint8_t hash[3]; if (!device->irk) return -1; crypto = bt_crypto_new(); if (!crypto) return -1; bt_crypto_ah(crypto, device->irk, addr->bdaddr.b + 3, hash); bt_crypto_unref(crypto); return memcmp(addr, hash, 3); } int device_addr_type_cmp(gconstpointer a, gconstpointer b) { const struct btd_device *dev = a; const struct device_addr_type *addr = b; int cmp; cmp = bacmp(&dev->bdaddr, &addr->bdaddr); /* * Address matches and both old and new are public addresses * (doesn't matter whether LE or BR/EDR, then consider this a * match. */ if (!cmp && addr_is_public(addr->bdaddr_type) && addr_is_public(dev->bdaddr_type)) return 0; if (addr->bdaddr_type == BDADDR_BREDR) { if (!dev->bredr) return -1; return cmp; } if (!dev->le) return -1; if (addr->bdaddr_type != dev->bdaddr_type) { if (dev->privacy && addr_is_resolvable(&addr->bdaddr, addr->bdaddr_type)) return device_irk_cmp(dev, addr); if (addr->bdaddr_type == dev->conn_bdaddr_type) return bacmp(&dev->conn_bdaddr, &addr->bdaddr); return -1; } return cmp; } static gboolean record_has_uuid(const sdp_record_t *rec, const char *profile_uuid) { sdp_list_t *pat; for (pat = rec->pattern; pat != NULL; pat = pat->next) { char *uuid; int ret; uuid = bt_uuid2string(pat->data); if (!uuid) continue; ret = strcasecmp(uuid, profile_uuid); free(uuid); if (ret == 0) return TRUE; } return FALSE; } GSList *btd_device_get_uuids(struct btd_device *device) { return device->uuids; } bool btd_device_has_uuid(struct btd_device *device, const char *uuid) { return g_slist_find_custom(device->uuids, uuid, (GCompareFunc)strcasecmp); } struct probe_data { struct btd_device *dev; GSList *uuids; }; static struct btd_service *probe_service(struct btd_device *device, struct btd_profile *profile, GSList *uuids) { GSList *l; struct btd_service *service; if (profile->device_probe == NULL) return NULL; if (!device_match_profile(device, profile, uuids)) return NULL; l = find_service_with_profile(device->services, profile); /* If the service already exists, return NULL so that it won't be added * to the device->services. */ if (l) return NULL; service = service_create(device, profile); if (service_probe(service)) { btd_service_unref(service); return NULL; } /* Only set auto connect if profile has set the flag and can really * accept connections. */ if (profile->auto_connect && profile->accept) { /* If temporary mark auto_connect as disabled so when the * device is connected it attempts to enable it. */ if (device->temporary) device->disable_auto_connect = TRUE; else device_set_auto_connect(device, TRUE); } return service; } static void dev_probe(struct btd_profile *p, void *user_data) { struct probe_data *d = user_data; struct btd_service *service; service = probe_service(d->dev, p, d->uuids); if (!service) return; d->dev->services = g_slist_append(d->dev->services, service); } void device_probe_profile(gpointer a, gpointer b) { struct btd_device *device = a; struct btd_profile *profile = b; struct btd_service *service; service = probe_service(device, profile, device->uuids); if (!service) return; device->services = g_slist_append(device->services, service); if (!profile->auto_connect || (!btd_device_is_connected(device) && !device->general_connect)) return; device->pending = g_slist_append(device->pending, service); if (g_slist_length(device->pending) == 1) connect_next(device); } void device_remove_profile(gpointer a, gpointer b) { struct btd_device *device = a; struct btd_profile *profile = b; struct btd_service *service; GSList *l; l = find_service_with_profile(device->services, profile); if (l == NULL) return; service = l->data; device->services = g_slist_delete_link(device->services, l); device->pending = g_slist_remove(device->pending, service); service_remove(service); } void device_probe_profiles(struct btd_device *device, GSList *uuids) { struct probe_data d = { device, uuids }; char addr[18]; if (!uuids) return; ba2str(&device->bdaddr, addr); if (device->blocked) { DBG("Skipping profiles for blocked device %s", addr); goto add_uuids; } btd_profile_foreach(dev_probe, &d); add_uuids: device_add_uuids(device, uuids); } static void store_sdp_record(GKeyFile *key_file, sdp_record_t *rec) { char handle_str[11]; sdp_buf_t buf; int size, i; char *str; sprintf(handle_str, "0x%8.8X", rec->handle); if (sdp_gen_record_pdu(rec, &buf) < 0) return; size = buf.data_size; str = g_malloc0(size*2+1); for (i = 0; i < size; i++) sprintf(str + (i * 2), "%02X", buf.data[i]); g_key_file_set_string(key_file, "ServiceRecords", handle_str, str); free(buf.data); g_free(str); } static void store_primaries_from_sdp_record(GKeyFile *key_file, sdp_record_t *rec) { uuid_t uuid; char *att_uuid, *prim_uuid; uint16_t start = 0, end = 0, psm = 0; char handle[6], uuid_str[33]; int i; sdp_uuid16_create(&uuid, ATT_UUID); att_uuid = bt_uuid2string(&uuid); sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); prim_uuid = bt_uuid2string(&uuid); if (!record_has_uuid(rec, att_uuid)) goto done; if (!gatt_parse_record(rec, &uuid, &psm, &start, &end)) goto done; sprintf(handle, "%hu", start); switch (uuid.type) { case SDP_UUID16: sprintf(uuid_str, "%4.4X", uuid.value.uuid16); break; case SDP_UUID32: sprintf(uuid_str, "%8.8X", uuid.value.uuid32); break; case SDP_UUID128: for (i = 0; i < 16; i++) sprintf(uuid_str + (i * 2), "%2.2X", uuid.value.uuid128.data[i]); break; default: uuid_str[0] = '\0'; } g_key_file_set_string(key_file, handle, "UUID", prim_uuid); g_key_file_set_string(key_file, handle, "Value", uuid_str); g_key_file_set_integer(key_file, handle, "EndGroupHandle", end); done: free(prim_uuid); free(att_uuid); } static int rec_cmp(const void *a, const void *b) { const sdp_record_t *r1 = a; const sdp_record_t *r2 = b; return r1->handle - r2->handle; } static int update_record(struct browse_req *req, const char *uuid, sdp_record_t *rec) { GSList *l; /* Check for duplicates */ if (sdp_list_find(req->records, rec, rec_cmp)) return -EALREADY; /* Copy record */ req->records = sdp_list_append(req->records, sdp_copy_record(rec)); /* Check if UUID is duplicated */ l = g_slist_find_custom(req->device->uuids, uuid, bt_uuid_strcmp); if (l == NULL) { l = g_slist_find_custom(req->profiles_added, uuid, bt_uuid_strcmp); if (l != NULL) return 0; req->profiles_added = g_slist_append(req->profiles_added, g_strdup(uuid)); } return 0; } static void update_bredr_services(struct browse_req *req, sdp_list_t *recs) { struct btd_device *device = req->device; sdp_list_t *seq; char srcaddr[18], dstaddr[18]; char sdp_file[PATH_MAX]; char att_file[PATH_MAX]; GKeyFile *sdp_key_file; GKeyFile *att_key_file; GError *gerr = NULL; char *data; gsize length = 0; ba2str(btd_adapter_get_address(device->adapter), srcaddr); ba2str(&device->bdaddr, dstaddr); create_filename(sdp_file, PATH_MAX, "/%s/cache/%s", srcaddr, dstaddr); create_file(sdp_file, 0600); sdp_key_file = g_key_file_new(); if (!g_key_file_load_from_file(sdp_key_file, sdp_file, 0, &gerr)) { error("Unable to load key file from %s: (%s)", sdp_file, gerr->message); g_clear_error(&gerr); g_key_file_free(sdp_key_file); sdp_key_file = NULL; } create_filename(att_file, PATH_MAX, "/%s/%s/attributes", srcaddr, dstaddr); create_file(att_file, 0600); att_key_file = g_key_file_new(); if (!g_key_file_load_from_file(att_key_file, att_file, 0, &gerr)) { error("Unable to load key file from %s: (%s)", att_file, gerr->message); g_clear_error(&gerr); g_key_file_free(att_key_file); att_key_file = NULL; } for (seq = recs; seq; seq = seq->next) { sdp_record_t *rec = (sdp_record_t *) seq->data; char *profile_uuid; if (!rec) break; /* If service class attribute is missing, svclass will be all * zero and the resulting uuid string will be NULL. */ profile_uuid = bt_uuid2string(&rec->svclass); if (!profile_uuid) continue; if (bt_uuid_strcmp(profile_uuid, PNP_UUID) == 0) { uint16_t source, vendor, product, version; sdp_data_t *pdlist; pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID_SOURCE); source = pdlist ? pdlist->val.uint16 : 0x0000; pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID); vendor = pdlist ? pdlist->val.uint16 : 0x0000; pdlist = sdp_data_get(rec, SDP_ATTR_PRODUCT_ID); product = pdlist ? pdlist->val.uint16 : 0x0000; pdlist = sdp_data_get(rec, SDP_ATTR_VERSION); version = pdlist ? pdlist->val.uint16 : 0x0000; if (source || vendor || product || version) btd_device_set_pnpid(device, source, vendor, product, version); } if (update_record(req, profile_uuid, rec) < 0) goto next; if (sdp_key_file) store_sdp_record(sdp_key_file, rec); if (att_key_file) store_primaries_from_sdp_record(att_key_file, rec); next: free(profile_uuid); } if (sdp_key_file) { data = g_key_file_to_data(sdp_key_file, &length, NULL); if (length > 0) { if (!g_file_set_contents(sdp_file, data, length, &gerr)) { error("Unable set contents for %s: (%s)", sdp_file, gerr->message); g_clear_error(&gerr); } } g_free(data); g_key_file_free(sdp_key_file); } if (att_key_file) { data = g_key_file_to_data(att_key_file, &length, NULL); if (length > 0) { if (!g_file_set_contents(att_file, data, length, &gerr)) { error("Unable set contents for %s: (%s)", att_file, gerr->message); g_clear_error(&gerr); } } g_free(data); g_key_file_free(att_key_file); } } static int primary_cmp(gconstpointer a, gconstpointer b) { return memcmp(a, b, sizeof(struct gatt_primary)); } static void update_gatt_uuids(struct browse_req *req, GSList *current, GSList *found) { GSList *l, *lmatch; /* Added Profiles */ for (l = found; l; l = g_slist_next(l)) { struct gatt_primary *prim = l->data; /* Entry found ? */ lmatch = g_slist_find_custom(current, prim, primary_cmp); if (lmatch) continue; /* New entry */ req->profiles_added = g_slist_append(req->profiles_added, g_strdup(prim->uuid)); DBG("UUID Added: %s", prim->uuid); } } static GSList *device_services_from_record(struct btd_device *device, GSList *profiles) { GSList *l, *prim_list = NULL; char *att_uuid; uuid_t proto_uuid; sdp_uuid16_create(&proto_uuid, ATT_UUID); att_uuid = bt_uuid2string(&proto_uuid); for (l = profiles; l; l = l->next) { const char *profile_uuid = l->data; const sdp_record_t *rec; struct gatt_primary *prim; uint16_t start = 0, end = 0, psm = 0; uuid_t prim_uuid; rec = btd_device_get_record(device, profile_uuid); if (!rec) continue; if (!record_has_uuid(rec, att_uuid)) continue; if (!gatt_parse_record(rec, &prim_uuid, &psm, &start, &end)) continue; prim = g_new0(struct gatt_primary, 1); prim->range.start = start; prim->range.end = end; sdp_uuid2strn(&prim_uuid, prim->uuid, sizeof(prim->uuid)); prim_list = g_slist_append(prim_list, prim); } free(att_uuid); return prim_list; } static void search_cb(sdp_list_t *recs, int err, gpointer user_data) { struct browse_req *req = user_data; struct btd_device *device = req->device; DBusMessage *reply; GSList *primaries; char addr[18]; ba2str(&device->bdaddr, addr); if (err < 0) { error("%s: error updating services: %s (%d)", addr, strerror(-err), -err); goto send_reply; } update_bredr_services(req, recs); if (device->tmp_records) sdp_list_free(device->tmp_records, (sdp_free_func_t) sdp_record_free); device->tmp_records = req->records; req->records = NULL; if (!req->profiles_added) { DBG("%s: No service update", addr); goto send_reply; } primaries = device_services_from_record(device, req->profiles_added); if (primaries) device_register_primaries(device, primaries, BT_ATT_PSM); /* * TODO: The btd_service instances for GATT services need to be * initialized with the service handles. Eventually this code should * perform ATT protocol service discovery over the ATT PSM to obtain * the full list of services and populate a client-role gatt_db over * BR/EDR. */ device_probe_profiles(device, req->profiles_added); /* Propagate services changes */ g_dbus_emit_property_changed(dbus_conn, req->device->path, DEVICE_INTERFACE, "UUIDs"); send_reply: /* If SDP search failed during an ongoing connection request, we should * reply to D-Bus method call. */ if (err < 0 && device->connect) { DBG("SDP failed during connection"); reply = btd_error_failed(device->connect, strerror(-err)); g_dbus_send_message(dbus_conn, reply); dbus_message_unref(device->connect); device->connect = NULL; } device_svc_resolved(device, BROWSE_SDP, BDADDR_BREDR, err); } static void browse_cb(sdp_list_t *recs, int err, gpointer user_data) { struct browse_req *req = user_data; struct btd_device *device = req->device; struct btd_adapter *adapter = device->adapter; uuid_t uuid; /* If we have a valid response and req->search_uuid == 2, then L2CAP * UUID & PNP searching was successful -- we are done */ if (err < 0 || (req->search_uuid == 2 && req->records)) { if (err == -ECONNRESET && req->reconnect_attempt < 1) { req->search_uuid--; req->reconnect_attempt++; } else goto done; } update_bredr_services(req, recs); /* Search for mandatory uuids */ if (uuid_list[req->search_uuid]) { sdp_uuid16_create(&uuid, uuid_list[req->search_uuid++]); bt_search_service(btd_adapter_get_address(adapter), &device->bdaddr, &uuid, browse_cb, user_data, NULL, req->sdp_flags); return; } done: search_cb(recs, err, user_data); } static bool device_get_auto_connect(struct btd_device *device) { if (device->disable_auto_connect) return false; return device->auto_connect; } static void disconnect_gatt_service(gpointer data, gpointer user_data) { struct btd_service *service = data; struct btd_profile *profile = btd_service_get_profile(service); /* Ignore if profile cannot accept connections */ if (!profile->accept) return; btd_service_disconnect(service); } static void att_disconnected_cb(int err, void *user_data) { struct btd_device *device = user_data; DBG(""); if (device->browse) goto done; DBG("%s (%d)", strerror(err), err); g_slist_foreach(device->services, disconnect_gatt_service, NULL); btd_gatt_client_disconnected(device->client_dbus); if (!device_get_auto_connect(device)) { DBG("Automatic connection disabled"); goto done; } /* * Keep scanning/re-connection active if disconnection reason * is connection timeout, remote user terminated connection or local * initiated disconnection. */ if (err == ETIMEDOUT || err == ECONNRESET || err == ECONNABORTED) adapter_connect_list_add(device->adapter, device); done: attio_cleanup(device); } static void register_gatt_services(struct btd_device *device) { struct browse_req *req = device->browse; GSList *services = NULL; if (!bt_gatt_client_is_ready(device->client)) return; /* * TODO: Remove the primaries list entirely once all profiles use * shared/gatt. */ gatt_db_foreach_service(device->db, NULL, add_primary, &services); btd_device_set_temporary(device, false); if (req) update_gatt_uuids(req, device->primaries, services); g_slist_free_full(device->primaries, g_free); device->primaries = NULL; device_register_primaries(device, services, -1); device_add_gatt_services(device); } static void gatt_client_init(struct btd_device *device); static void gatt_client_ready_cb(bool success, uint8_t att_ecode, void *user_data) { struct btd_device *device = user_data; DBG("status: %s, error: %u", success ? "success" : "failed", att_ecode); if (!success) { device_svc_resolved(device, BROWSE_GATT, device->bdaddr_type, -EIO); return; } register_gatt_services(device); btd_gatt_client_ready(device->client_dbus); device_svc_resolved(device, BROWSE_GATT, device->bdaddr_type, 0); store_gatt_db(device); } static void gatt_client_service_changed(uint16_t start_handle, uint16_t end_handle, void *user_data) { struct btd_device *device = user_data; DBG("start 0x%04x, end: 0x%04x", start_handle, end_handle); store_gatt_db(device); } static void gatt_debug(const char *str, void *user_data) { DBG_IDX(0xffff, "%s", str); } static void gatt_client_init(struct btd_device *device) { uint8_t features; gatt_client_cleanup(device); if (!btd_device_is_initiator(device) && !btd_opts.reverse_discovery) { DBG("Reverse service discovery disabled: skipping GATT client"); return; } if (!btd_device_is_initiator(device) && !btd_opts.gatt_client) { DBG("GATT client disabled: skipping GATT client"); return; } features = BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING | BT_GATT_CHRC_CLI_FEAT_NFY_MULTI; if (btd_opts.gatt_channels > 1) features |= BT_GATT_CHRC_CLI_FEAT_EATT; if (!btd_opts.gatt_seclevel && device->bonding) { DBG("Elevating security level since bonding is in progress"); bt_att_set_security(device->att, BT_ATT_SECURITY_MEDIUM); } device->client = bt_gatt_client_new(device->db, device->att, device->att_mtu, features); if (!device->client) { DBG("Failed to initialize"); return; } bt_gatt_client_set_debug(device->client, gatt_debug, NULL, NULL); g_attrib_attach_client(device->attrib, device->client); /* * If we have cache, notify existing service about the new connection * so they can react to notifications while discovering services */ if (!gatt_db_isempty(device->db)) device_accept_gatt_profiles(device); device->gatt_ready_id = bt_gatt_client_ready_register(device->client, gatt_client_ready_cb, device, NULL); if (!device->gatt_ready_id) { DBG("Failed to register GATT ready callback"); gatt_client_cleanup(device); return; } if (!bt_gatt_client_set_service_changed(device->client, gatt_client_service_changed, device, NULL)) { DBG("Failed to set service changed handler"); gatt_client_cleanup(device); return; } btd_gatt_client_connected(device->client_dbus); /* Only initiate EATT connection when acting as initiator, as acceptor * it shall be triggered only when ready to avoid possible clashes where * both sides attempt to connection at same time. */ if (btd_device_is_initiator(device)) btd_gatt_client_eatt_connect(device->client_dbus); } static void gatt_server_init(struct btd_device *device, struct btd_gatt_database *database) { struct gatt_db *db = btd_gatt_database_get_db(database); if (!db) { error("No local GATT database exists for this adapter"); return; } gatt_server_cleanup(device); device->server = bt_gatt_server_new(db, device->att, device->att_mtu, btd_opts.key_size); if (!device->server) { error("Failed to initialize bt_gatt_server"); return; } if (device->ltk) bt_att_set_enc_key_size(device->att, device->ltk->enc_size); if (btd_opts.gatt_seclevel == BT_ATT_SECURITY_LOW) bt_gatt_server_set_permissions(device->server, false); bt_gatt_server_set_debug(device->server, gatt_debug, NULL, NULL); btd_gatt_database_server_connected(database, device->server); } static bool local_counter(uint32_t *sign_cnt, void *user_data) { struct btd_device *dev = user_data; if (!dev->local_csrk) return false; *sign_cnt = dev->local_csrk->counter++; store_device_info(dev); return true; } static bool remote_counter(uint32_t *sign_cnt, void *user_data) { struct btd_device *dev = user_data; if (!dev->remote_csrk || *sign_cnt < dev->remote_csrk->counter) return false; dev->remote_csrk->counter = *sign_cnt; store_device_info(dev); return true; } bool device_attach_att(struct btd_device *dev, GIOChannel *io) { GError *gerr = NULL; GAttrib *attrib; BtIOSecLevel sec_level; uint16_t mtu; uint16_t cid; struct btd_gatt_database *database; const bdaddr_t *dst; char dstaddr[18]; bt_io_get(io, &gerr, BT_IO_OPT_SEC_LEVEL, &sec_level, BT_IO_OPT_IMTU, &mtu, BT_IO_OPT_CID, &cid, BT_IO_OPT_INVALID); if (gerr) { error("bt_io_get: %s", gerr->message); g_error_free(gerr); return false; } if (dev->att) { if (btd_opts.gatt_channels == bt_att_get_channels(dev->att)) { DBG("EATT channel limit reached"); return false; } if (!bt_att_attach_fd(dev->att, g_io_channel_unix_get_fd(io))) { DBG("EATT channel connected"); g_io_channel_set_close_on_unref(io, FALSE); return true; } error("Failed to attach EATT channel"); return false; } if (!btd_opts.gatt_seclevel && sec_level == BT_IO_SEC_LOW && dev->le_state.paired) { DBG("Elevating security level since LTK is available"); sec_level = BT_IO_SEC_MEDIUM; bt_io_set(io, &gerr, BT_IO_OPT_SEC_LEVEL, sec_level, BT_IO_OPT_INVALID); if (gerr) { error("bt_io_set: %s", gerr->message); g_error_free(gerr); return false; } } dev->att_mtu = MIN(mtu, btd_opts.gatt_mtu); attrib = g_attrib_new(io, cid == BT_ATT_CID ? BT_ATT_DEFAULT_LE_MTU : dev->att_mtu, false); if (!attrib) { error("Unable to create new GAttrib instance"); return false; } dev->attrib = attrib; dev->att = g_attrib_get_att(attrib); bt_att_ref(dev->att); bt_att_set_debug(dev->att, BT_ATT_DEBUG, gatt_debug, NULL, NULL); dev->att_disconn_id = bt_att_register_disconnect(dev->att, att_disconnected_cb, dev, NULL); bt_att_set_close_on_unref(dev->att, true); if (dev->local_csrk) bt_att_set_local_key(dev->att, dev->local_csrk->key, local_counter, dev); if (dev->remote_csrk) bt_att_set_remote_key(dev->att, dev->remote_csrk->key, remote_counter, dev); /* Force security level if it has been set */ if (btd_opts.gatt_seclevel) bt_att_set_security(dev->att, btd_opts.gatt_seclevel); database = btd_adapter_get_database(dev->adapter); dst = device_get_address(dev); ba2str(dst, dstaddr); if (gatt_db_isempty(dev->db)) load_gatt_db(dev, btd_adapter_get_storage_dir(dev->adapter), dstaddr); gatt_client_init(dev); gatt_server_init(dev, database); /* * Remove the device from the connect_list and give the passive * scanning another chance to be restarted in case there are * other devices in the connect_list. */ adapter_connect_list_remove(dev->adapter, dev); return true; } static void att_connect_cb(GIOChannel *io, GError *gerr, gpointer user_data) { struct btd_device *device = user_data; DBusMessage *reply; enum mgmt_io_capability io_cap; int err = 0; g_io_channel_unref(device->att_io); device->att_io = NULL; if (gerr) { DBG("%s", gerr->message); if (g_error_matches(gerr, BT_IO_ERROR, ECONNABORTED)) goto done; if (device_get_auto_connect(device)) { DBG("Enabling automatic connections"); adapter_connect_list_add(device->adapter, device); } if (device->browse) browse_request_complete(device->browse, BROWSE_GATT, device->bdaddr_type, -ECONNABORTED); err = -ECONNABORTED; goto done; } /* Update connected state */ device->le_state.connected = true; if (!device_attach_att(device, io)) goto done; if (!device->bonding) goto done; if (device->bonding->agent) io_cap = agent_get_io_capability(device->bonding->agent); else io_cap = MGMT_IO_CAPABILITY_NOINPUTNOOUTPUT; err = adapter_create_bonding(device->adapter, &device->bdaddr, device->bdaddr_type, io_cap); done: if (device->bonding && err < 0) { reply = btd_error_failed(device->bonding->msg, strerror(-err)); g_dbus_send_message(dbus_conn, reply); bonding_request_cancel(device->bonding); bonding_request_free(device->bonding); } if (!err) device_browse_gatt(device, NULL); /* Send D-Bus reply to le.connect() */ if (err && device->le) btd_bearer_connected(device->le, err); if (device->connect) { if (err < 0) reply = btd_error_le_errno(device->connect, err); else reply = dbus_message_new_method_return(device->connect); g_dbus_send_message(dbus_conn, reply); dbus_message_unref(device->connect); device->connect = NULL; } } int device_connect_le(struct btd_device *dev) { struct btd_adapter *adapter = dev->adapter; BtIOSecLevel sec_level; GIOChannel *io; GError *gerr = NULL; char addr[18]; /* There is one connection attempt going on */ if (dev->att_io || dev->att) return -EALREADY; ba2str(&dev->bdaddr, addr); DBG("Connection attempt to: %s", addr); /* Set as initiator */ dev->le_state.initiator = true; if (btd_opts.gatt_seclevel) sec_level = btd_opts.gatt_seclevel; else if (dev->le_state.paired) sec_level = BT_IO_SEC_MEDIUM; else sec_level = BT_IO_SEC_LOW; /* * This connection will help us catch any PDUs that comes before * pairing finishes */ io = bt_io_connect(att_connect_cb, dev, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, btd_adapter_get_address(adapter), BT_IO_OPT_SOURCE_TYPE, btd_adapter_get_address_type(adapter), BT_IO_OPT_DEST_BDADDR, &dev->bdaddr, BT_IO_OPT_DEST_TYPE, dev->bdaddr_type, BT_IO_OPT_CID, BT_ATT_CID, BT_IO_OPT_SEC_LEVEL, sec_level, BT_IO_OPT_INVALID); if (io == NULL) { if (dev->bonding) { DBusMessage *reply = btd_error_failed( dev->bonding->msg, gerr->message); g_dbus_send_message(dbus_conn, reply); bonding_request_cancel(dev->bonding); bonding_request_free(dev->bonding); } error("ATT bt_io_connect(%s): %s", addr, gerr->message); g_error_free(gerr); return -EIO; } /* Keep this, so we can cancel the connection */ dev->att_io = io; /* Restart temporary timer to give it time to connect/pair, etc. */ if (dev->temporary) set_temporary_timer(dev, btd_opts.tmpto); return 0; } static struct browse_req *browse_request_new(struct btd_device *device, uint8_t type, DBusMessage *msg) { struct browse_req *req; if (device->browse) return NULL; req = g_new0(struct browse_req, 1); req->device = device; req->type = type; device->browse = req; if (!msg) return req; req->msg = dbus_message_ref(msg); /* * Track the request owner to cancel it automatically if the owner * exits */ req->listener_id = g_dbus_add_disconnect_watch(dbus_conn, dbus_message_get_sender(msg), browse_request_exit, req, NULL); return req; } static int device_browse_gatt(struct btd_device *device, DBusMessage *msg) { struct btd_adapter *adapter = device->adapter; struct browse_req *req; req = browse_request_new(device, BROWSE_GATT, msg); if (!req) return -EBUSY; if (device->client) { /* * If discovery has not yet completed, then wait for gatt-client * to become ready. */ if (!bt_gatt_client_is_ready(device->client)) return 0; /* * Services have already been discovered, so signal this browse * request as resolved. */ device_svc_resolved(device, BROWSE_GATT, device->bdaddr_type, 0); return 0; } device->att_io = bt_io_connect(att_connect_cb, device, NULL, NULL, BT_IO_OPT_SOURCE_BDADDR, btd_adapter_get_address(adapter), BT_IO_OPT_SOURCE_TYPE, btd_adapter_get_address_type(adapter), BT_IO_OPT_DEST_BDADDR, &device->bdaddr, BT_IO_OPT_DEST_TYPE, device->bdaddr_type, BT_IO_OPT_CID, BT_ATT_CID, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_INVALID); if (device->att_io == NULL) { browse_request_free(req); return -EIO; } return 0; } static uint16_t get_sdp_flags(struct btd_device *device) { uint16_t vid, pid; vid = btd_device_get_vendor(device); pid = btd_device_get_product(device); /* Sony DualShock 4 is not respecting negotiated L2CAP MTU. This might * results in SDP response being dropped by kernel. Workaround this by * forcing SDP code to use bigger MTU while connecting. */ if (vid == 0x054c && pid == 0x05c4) return SDP_LARGE_MTU; if (btd_adapter_ssp_enabled(device->adapter)) return 0; /* if no EIR try matching Sony DualShock 4 with name and class */ if (!strncmp(device->name, "Wireless Controller", MAX_NAME_LENGTH) && device->class == 0x2508) return SDP_LARGE_MTU; return 0; } static int device_browse_sdp(struct btd_device *device, DBusMessage *msg) { struct btd_adapter *adapter = device->adapter; struct browse_req *req; uuid_t uuid; int err; req = browse_request_new(device, BROWSE_SDP, msg); if (!req) return -EBUSY; sdp_uuid16_create(&uuid, uuid_list[req->search_uuid++]); req->sdp_flags = get_sdp_flags(device); err = bt_search(btd_adapter_get_address(adapter), &device->bdaddr, &uuid, browse_cb, req, NULL, req->sdp_flags); if (err < 0) { browse_request_free(req); return err; } return err; } static gboolean device_is_browsing(struct btd_device *device, uint8_t bdaddr_type) { if (!device->browse) return FALSE; if (bdaddr_type == BDADDR_BREDR && device->browse->type == BROWSE_SDP) return TRUE; if (bdaddr_type != BDADDR_BREDR && device->browse->type == BROWSE_GATT) return TRUE; return FALSE; } void device_cancel_browse(struct btd_device *device, uint8_t bdaddr_type) { struct btd_adapter *adapter = device->adapter; DBG(""); if (!device_is_browsing(device, bdaddr_type)) return; if (bdaddr_type == BDADDR_BREDR) bt_cancel_discovery(btd_adapter_get_address(adapter), &device->bdaddr); else attio_cleanup(device); browse_request_free(device->browse); } int device_discover_services(struct btd_device *device, uint8_t bdaddr_type, DBusMessage *msg) { int err; if (bdaddr_type == BDADDR_BREDR) err = device_browse_sdp(device, msg); else err = device_browse_gatt(device, msg); if (err == 0 && device->discov_timer) { timeout_remove(device->discov_timer); device->discov_timer = 0; } return err; } struct btd_adapter *device_get_adapter(struct btd_device *device) { if (!device) return NULL; return device->adapter; } const bdaddr_t *device_get_address(struct btd_device *device) { return &device->bdaddr; } uint8_t device_get_le_address_type(struct btd_device *device) { return device->bdaddr_type; } const char *device_get_path(const struct btd_device *device) { if (!device) return NULL; return device->path; } gboolean device_is_temporary(struct btd_device *device) { return device->temporary; } void btd_device_set_temporary(struct btd_device *device, bool temporary) { if (!device) return; if (device->temporary == temporary) return; if (device_address_is_private(device)) return; DBG("temporary %d", temporary); device->temporary = temporary; if (temporary) { if (device->bredr) adapter_accept_list_remove(device->adapter, device); adapter_connect_list_remove(device->adapter, device); if (device->auto_connect) { device->disable_auto_connect = TRUE; device_set_auto_connect(device, FALSE); } set_temporary_timer(device, btd_opts.tmpto); return; } else clear_temporary_timer(device); if (device->bredr) adapter_accept_list_add(device->adapter, device); store_device_info(device); /* attributes were not stored when resolved if device was temporary */ if (device->bdaddr_type != BDADDR_BREDR && device->le_state.svc_resolved && g_slist_length(device->primaries) != 0) store_services(device); } void btd_device_set_trusted(struct btd_device *device, gboolean trusted) { if (!device) return; if (device->trusted == trusted) return; DBG("trusted %d", trusted); device->trusted = trusted; store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Trusted"); } void device_set_bonded(struct btd_device *device, uint8_t bdaddr_type) { struct bearer_state *state; if (!device) return; state = get_state(device, bdaddr_type); if (state->bonded) return; DBG("setting bonded for device to true"); state->bonded = true; if (bdaddr_type == BDADDR_BREDR) btd_bearer_bonded(device->bredr); else btd_bearer_bonded(device->le); btd_device_set_temporary(device, false); /* If the other bearer state was already true we don't need to * send any property signals. */ if (device->bredr_state.bonded == device->le_state.bonded) return; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Bonded"); } void device_set_legacy(struct btd_device *device, bool legacy) { if (!device) return; DBG("legacy %d", legacy); if (device->legacy == legacy) return; device->legacy = legacy; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "LegacyPairing"); } void device_set_cable_pairing(struct btd_device *device, bool cable_pairing) { if (!device) return; if (device->cable_pairing == cable_pairing) return; DBG("setting cable pairing %d", cable_pairing); device->cable_pairing = cable_pairing; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "CablePairing"); } void device_store_svc_chng_ccc(struct btd_device *device, uint8_t bdaddr_type, uint16_t value) { char filename[PATH_MAX]; char device_addr[18]; GKeyFile *key_file; GError *gerr = NULL; uint16_t old_value; gsize length = 0; char *str; ba2str(&device->bdaddr, device_addr); create_filename(filename, PATH_MAX, "/%s/%s/info", btd_adapter_get_storage_dir(device->adapter), device_addr); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } /* for bonded devices this is done on every connection so limit writes * to storage if no change needed */ if (bdaddr_type == BDADDR_BREDR) { old_value = g_key_file_get_integer(key_file, "ServiceChanged", "CCC_BR/EDR", NULL); if (old_value == value) goto done; g_key_file_set_integer(key_file, "ServiceChanged", "CCC_BR/EDR", value); } else { old_value = g_key_file_get_integer(key_file, "ServiceChanged", "CCC_LE", NULL); if (old_value == value) goto done; g_key_file_set_integer(key_file, "ServiceChanged", "CCC_LE", value); } create_file(filename, 0600); str = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, str, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(str); done: g_key_file_free(key_file); } void device_load_svc_chng_ccc(struct btd_device *device, uint16_t *ccc_le, uint16_t *ccc_bredr) { char filename[PATH_MAX]; char device_addr[18]; GKeyFile *key_file; GError *gerr = NULL; ba2str(&device->bdaddr, device_addr); create_filename(filename, PATH_MAX, "/%s/%s/info", btd_adapter_get_storage_dir(device->adapter), device_addr); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_error_free(gerr); } if (!g_key_file_has_group(key_file, "ServiceChanged")) { if (ccc_le) *ccc_le = 0x0000; if (ccc_bredr) *ccc_bredr = 0x0000; g_key_file_free(key_file); return; } if (ccc_le) *ccc_le = g_key_file_get_integer(key_file, "ServiceChanged", "CCC_LE", NULL); if (ccc_bredr) *ccc_bredr = g_key_file_get_integer(key_file, "ServiceChanged", "CCC_BR/EDR", NULL); g_key_file_free(key_file); } void device_set_rssi_with_delta(struct btd_device *device, int8_t rssi, int8_t delta_threshold) { if (!device) return; if (rssi == 0 || device->rssi == 0) { if (device->rssi == rssi) return; DBG("rssi %d", rssi); device->rssi = rssi; } else { int delta; if (device->rssi > rssi) delta = device->rssi - rssi; else delta = rssi - device->rssi; /* only report changes of delta_threshold dBm or more */ if (delta < delta_threshold) return; DBG("rssi %d delta %d", rssi, delta); device->rssi = rssi; } g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "RSSI"); } void device_set_rssi(struct btd_device *device, int8_t rssi) { device_set_rssi_with_delta(device, rssi, RSSI_THRESHOLD); } void device_set_tx_power(struct btd_device *device, int8_t tx_power) { if (!device) return; if (device->tx_power == tx_power) return; DBG("tx_power %d", tx_power); device->tx_power = tx_power; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "TxPower"); } void device_set_flags(struct btd_device *device, uint8_t flags) { if (!device) return; DBG("flags %d", flags); if (device->ad_flags[0] == flags) return; device->ad_flags[0] = flags; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "AdvertisingFlags"); } bool device_is_connectable(struct btd_device *device) { struct bearer_state *state; if (!device) return false; if (device->bredr) return true; state = get_state(device, device->bdaddr_type); return state->connectable; } static bool start_discovery_cb(gpointer user_data) { struct btd_device *device = user_data; if (device->bredr) device_browse_sdp(device, NULL); else device_browse_gatt(device, NULL); device->discov_timer = 0; return FALSE; } void device_set_paired(struct btd_device *dev, uint8_t bdaddr_type) { struct bearer_state *state = get_state(dev, bdaddr_type); if (state->paired) return; state->paired = true; if (bdaddr_type == BDADDR_BREDR) btd_bearer_paired(dev->bredr); else btd_bearer_paired(dev->le); /* If the other bearer state was already true we don't need to * send any property signals. */ if (dev->bredr_state.paired == dev->le_state.paired) return; if (!state->svc_resolved) { dev->pending_paired = true; return; } g_dbus_emit_property_changed(dbus_conn, dev->path, DEVICE_INTERFACE, "Paired"); } void device_set_unpaired(struct btd_device *dev, uint8_t bdaddr_type) { struct bearer_state *state = get_state(dev, bdaddr_type); if (!state->paired) return; state->paired = false; if (bdaddr_type == BDADDR_BREDR) btd_bearer_paired(dev->bredr); else btd_bearer_paired(dev->le); /* * If the other bearer state is still true we don't need to * send any property signals or remove device. */ if (dev->bredr_state.paired != dev->le_state.paired) { /* TODO disconnect only unpaired bearer */ if (state->connected) device_request_disconnect(dev, NULL); return; } g_dbus_emit_property_changed(dbus_conn, dev->path, DEVICE_INTERFACE, "Paired"); btd_device_set_temporary(dev, true); if (btd_device_is_connected(dev)) device_request_disconnect(dev, NULL); else btd_adapter_remove_device(dev->adapter, dev); } static void device_auth_req_free(struct btd_device *device) { struct authentication_req *authr = device->authr; if (!authr) return; if (authr->agent) agent_unref(authr->agent); g_free(authr->pincode); g_free(authr); device->authr = NULL; } bool device_is_retrying(struct btd_device *device) { struct bonding_req *bonding = device->bonding; return bonding && bonding->retry_timer > 0; } void device_bonding_complete(struct btd_device *device, uint8_t bdaddr_type, uint8_t status) { struct bonding_req *bonding = device->bonding; struct authentication_req *auth = device->authr; struct bearer_state *state = get_state(device, bdaddr_type); int err; DBG("bonding %p status 0x%02x", bonding, status); device->bonding_status = status; if (status == MGMT_STATUS_AUTH_FAILED) device_request_disconnect(device, NULL); if (auth && auth->agent) agent_cancel(auth->agent); if (status) { device_cancel_authentication(device, TRUE); /* Put the device back to the temporary state so that it will be * treated as a newly discovered device. */ if (!btd_device_bearer_is_connected(device) && !device_is_paired(device, bdaddr_type) && !btd_device_is_trusted(device)) btd_device_set_temporary(device, true); device_bonding_failed(device, status); return; } device_auth_req_free(device); /* Enable the wake_allowed property if required */ if (device->wake_override == WAKE_FLAG_ENABLED) device_set_wake_allowed(device, true, -1U); /* If we're already paired nothing more is needed */ if (state->paired) return; device_set_paired(device, bdaddr_type); /* If services are already resolved just reply to the pairing * request */ if (state->svc_resolved && bonding) { /* Attempt to store services for this device failed because it * was not paired. Now that we're paired retry. */ store_gatt_db(device); g_dbus_send_reply(dbus_conn, bonding->msg, DBUS_TYPE_INVALID); bonding_request_free(bonding); return; } /* If we were initiators start service discovery immediately. * However if the other end was the initator wait a few seconds * before SDP. This is due to potential IOP issues if the other * end starts doing SDP at the same time as us */ if (bonding) { DBG("Proceeding with service discovery"); /* If we are initiators remove any discovery timer and just * start discovering services directly */ err = device_discover_services(device, bdaddr_type, bonding->msg); if (err) { if (device->pending_paired) { g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Paired"); device->pending_paired = false; } /* Disregard browse errors in case of Pair */ g_dbus_send_reply(dbus_conn, bonding->msg, DBUS_TYPE_INVALID); } bonding_request_free(bonding); } else if (!state->svc_resolved) { if (!device->browse && !device->discov_timer && btd_opts.reverse_discovery) { /* If we are not initiators and there is no currently * active discovery or discovery timer, set discovery * timer */ DBG("setting timer for reverse service discovery"); device->discov_timer = timeout_add_seconds( DISCOVERY_TIMER, start_discovery_cb, device, NULL); } } } static gboolean svc_idle_cb(gpointer user_data) { struct svc_callback *cb = user_data; struct btd_device *dev = cb->dev; dev->svc_callbacks = g_slist_remove(dev->svc_callbacks, cb); cb->func(cb->dev, 0, cb->user_data); g_free(cb); return FALSE; } unsigned int device_wait_for_svc_complete(struct btd_device *dev, device_svc_cb_t func, void *user_data) { /* This API is only used for BR/EDR (for now) */ struct bearer_state *state = &dev->bredr_state; static unsigned int id = 0; struct svc_callback *cb; cb = g_new0(struct svc_callback, 1); cb->func = func; cb->user_data = user_data; cb->dev = dev; cb->id = ++id; dev->svc_callbacks = g_slist_prepend(dev->svc_callbacks, cb); if (state->svc_resolved || !btd_opts.reverse_discovery) cb->idle_id = g_idle_add(svc_idle_cb, cb); else { if (dev->discov_timer > 0) timeout_remove(dev->discov_timer); dev->discov_timer = timeout_add_seconds( 0, start_discovery_cb, dev, NULL); } return cb->id; } bool device_remove_svc_complete_callback(struct btd_device *dev, unsigned int id) { GSList *l; for (l = dev->svc_callbacks; l != NULL; l = g_slist_next(l)) { struct svc_callback *cb = l->data; if (cb->id != id) continue; if (cb->idle_id > 0) g_source_remove(cb->idle_id); dev->svc_callbacks = g_slist_remove(dev->svc_callbacks, cb); g_free(cb); return true; } return false; } gboolean device_is_bonding(struct btd_device *device, const char *sender) { struct bonding_req *bonding = device->bonding; if (!device->bonding) return FALSE; if (!sender) return TRUE; return g_str_equal(sender, dbus_message_get_sender(bonding->msg)); } static gboolean device_bonding_retry(gpointer data) { struct btd_device *device = data; struct btd_adapter *adapter = device_get_adapter(device); struct bonding_req *bonding = device->bonding; enum mgmt_io_capability io_cap; int err; if (!bonding) return FALSE; DBG("retrying bonding"); bonding->retry_timer = 0; /* Restart the bonding timer to the beginning of the pairing. If not * pincode request/reply occurs during this retry, * device_bonding_last_duration() will return a consistent value from * this point. */ device_bonding_restart_timer(device); if (bonding->agent) io_cap = agent_get_io_capability(bonding->agent); else io_cap = MGMT_IO_CAPABILITY_NOINPUTNOOUTPUT; err = adapter_bonding_attempt(adapter, &device->bdaddr, device->bdaddr_type, io_cap); if (err < 0) device_bonding_complete(device, bonding->bdaddr_type, bonding->status); return FALSE; } int device_bonding_attempt_retry(struct btd_device *device) { struct bonding_req *bonding = device->bonding; /* Ignore other failure events while retrying */ if (device_is_retrying(device)) return 0; if (!bonding) return -EINVAL; /* Mark the end of a bonding attempt to compute the delta for the * retry. */ bonding_request_stop_timer(bonding); if (btd_adapter_pin_cb_iter_end(bonding->cb_iter)) return -EINVAL; DBG("scheduling retry"); bonding->retry_timer = g_timeout_add(3000, device_bonding_retry, device); return 0; } void device_bonding_failed(struct btd_device *device, uint8_t status) { struct bonding_req *bonding = device->bonding; DBusMessage *reply; DBG("status %u", status); if (!bonding) return; if (device->authr) device_cancel_authentication(device, FALSE); reply = new_authentication_return(bonding->msg, status); g_dbus_send_message(dbus_conn, reply); bonding_request_free(bonding); } struct btd_adapter_pin_cb_iter *device_bonding_iter(struct btd_device *device) { if (device->bonding == NULL) return NULL; return device->bonding->cb_iter; } static void pincode_cb(struct agent *agent, DBusError *err, const char *pin, void *data) { struct authentication_req *auth = data; struct btd_device *device = auth->device; /* No need to reply anything if the authentication already failed */ if (auth->agent == NULL) return; btd_adapter_pincode_reply(device->adapter, &device->bdaddr, pin, pin ? strlen(pin) : 0); agent_unref(device->authr->agent); device->authr->agent = NULL; } static void confirm_cb(struct agent *agent, DBusError *err, void *data) { struct authentication_req *auth = data; struct btd_device *device = auth->device; /* No need to reply anything if the authentication already failed */ if (auth->agent == NULL) return; btd_adapter_confirm_reply(device->adapter, &device->bdaddr, auth->addr_type, err ? FALSE : TRUE); agent_unref(device->authr->agent); device->authr->agent = NULL; } static void passkey_cb(struct agent *agent, DBusError *err, uint32_t passkey, void *data) { struct authentication_req *auth = data; struct btd_device *device = auth->device; /* No need to reply anything if the authentication already failed */ if (auth->agent == NULL) return; if (err) passkey = INVALID_PASSKEY; btd_adapter_passkey_reply(device->adapter, &device->bdaddr, auth->addr_type, passkey); agent_unref(device->authr->agent); device->authr->agent = NULL; } static void display_pincode_cb(struct agent *agent, DBusError *err, void *data) { struct authentication_req *auth = data; struct btd_device *device = auth->device; pincode_cb(agent, err, auth->pincode, auth); g_free(device->authr->pincode); device->authr->pincode = NULL; } static struct authentication_req *new_auth(struct btd_device *device, uint8_t addr_type, auth_type_t type, gboolean secure) { struct authentication_req *auth; struct agent *agent; char addr[18]; ba2str(&device->bdaddr, addr); DBG("Requesting agent authentication for %s", addr); if (device->authr) { error("Authentication already requested for %s", addr); return NULL; } if (device->bonding && device->bonding->agent) agent = agent_ref(device->bonding->agent); else agent = agent_get(NULL); if (!agent) { error("No agent available for request type %d", type); return NULL; } auth = g_new0(struct authentication_req, 1); auth->agent = agent; auth->device = device; auth->type = type; auth->addr_type = addr_type; auth->secure = secure; device->authr = auth; return auth; } int device_request_pincode(struct btd_device *device, gboolean secure) { struct authentication_req *auth; int err; auth = new_auth(device, BDADDR_BREDR, AUTH_TYPE_PINCODE, secure); if (!auth) return -EPERM; err = agent_request_pincode(auth->agent, device, pincode_cb, secure, auth, NULL); if (err < 0) { error("Failed requesting authentication"); device_auth_req_free(device); } return err; } int device_request_passkey(struct btd_device *device, uint8_t type) { struct authentication_req *auth; int err; auth = new_auth(device, type, AUTH_TYPE_PASSKEY, FALSE); if (!auth) return -EPERM; err = agent_request_passkey(auth->agent, device, passkey_cb, auth, NULL); if (err < 0) { error("Failed requesting authentication"); device_auth_req_free(device); } return err; } int device_confirm_passkey(struct btd_device *device, uint8_t type, int32_t passkey, uint8_t confirm_hint) { struct authentication_req *auth; int err; /* Just-Works repairing policy */ if (confirm_hint && device_is_paired(device, type)) { if (btd_opts.jw_repairing == JW_REPAIRING_NEVER) { btd_adapter_confirm_reply(device->adapter, &device->bdaddr, type, FALSE); return 0; } else if (btd_opts.jw_repairing == JW_REPAIRING_ALWAYS) { btd_adapter_confirm_reply(device->adapter, &device->bdaddr, type, TRUE); return 0; } } auth = new_auth(device, type, AUTH_TYPE_CONFIRM, FALSE); if (!auth) return -EPERM; auth->passkey = passkey; if (confirm_hint) { if (device->bonding != NULL) { /* We know the client has indicated the intent to pair * with the peer device, so we can auto-accept. */ btd_adapter_confirm_reply(device->adapter, &device->bdaddr, type, TRUE); return 0; } err = agent_request_authorization(auth->agent, device, confirm_cb, auth, NULL); } else { err = agent_request_confirmation(auth->agent, device, passkey, confirm_cb, auth, NULL); } if (err < 0) { if (err == -EINPROGRESS) { /* Already in progress */ confirm_cb(auth->agent, NULL, auth); return 0; } error("Failed requesting authentication"); device_auth_req_free(device); } return err; } int device_notify_passkey(struct btd_device *device, uint8_t type, uint32_t passkey, uint8_t entered) { struct authentication_req *auth; int err; if (device->authr) { auth = device->authr; if (auth->type != AUTH_TYPE_NOTIFY_PASSKEY) return -EPERM; } else { auth = new_auth(device, type, AUTH_TYPE_NOTIFY_PASSKEY, FALSE); if (!auth) return -EPERM; } err = agent_display_passkey(auth->agent, device, passkey, entered); if (err < 0) { error("Failed requesting authentication"); device_auth_req_free(device); } return err; } int device_notify_pincode(struct btd_device *device, gboolean secure, const char *pincode) { struct authentication_req *auth; int err; auth = new_auth(device, BDADDR_BREDR, AUTH_TYPE_NOTIFY_PINCODE, secure); if (!auth) return -EPERM; auth->pincode = g_strdup(pincode); err = agent_display_pincode(auth->agent, device, pincode, display_pincode_cb, auth, NULL); if (err < 0) { if (err == -EINPROGRESS) { /* Already in progress */ display_pincode_cb(auth->agent, NULL, auth); return 0; } error("Failed requesting authentication"); device_auth_req_free(device); } return err; } static void cancel_authentication(struct authentication_req *auth) { struct agent *agent; DBusError err; if (!auth || !auth->agent) return; agent = auth->agent; auth->agent = NULL; dbus_error_init(&err); dbus_set_error_const(&err, ERROR_INTERFACE ".Canceled", NULL); switch (auth->type) { case AUTH_TYPE_PINCODE: pincode_cb(agent, &err, NULL, auth); break; case AUTH_TYPE_CONFIRM: confirm_cb(agent, &err, auth); break; case AUTH_TYPE_PASSKEY: passkey_cb(agent, &err, 0, auth); break; case AUTH_TYPE_NOTIFY_PASSKEY: /* User Notify doesn't require any reply */ break; case AUTH_TYPE_NOTIFY_PINCODE: pincode_cb(agent, &err, NULL, auth); break; } dbus_error_free(&err); } void device_cancel_authentication(struct btd_device *device, gboolean aborted) { struct authentication_req *auth = device->authr; char addr[18]; if (device->adapter) btd_adapter_cancel_service_auth(device->adapter, device); if (!auth) return; ba2str(&device->bdaddr, addr); DBG("Canceling authentication request for %s", addr); if (auth->agent) agent_cancel(auth->agent); if (!aborted) cancel_authentication(auth); device_auth_req_free(device); } gboolean device_is_authenticating(struct btd_device *device) { return (device->authr != NULL); } struct gatt_primary *btd_device_get_primary(struct btd_device *device, const char *uuid) { GSList *match; match = g_slist_find_custom(device->primaries, uuid, bt_uuid_strcmp); if (match) return match->data; return NULL; } GSList *btd_device_get_primaries(struct btd_device *device) { return device->primaries; } struct gatt_db *btd_device_get_gatt_db(struct btd_device *device) { if (!device) return NULL; return device->db; } bool btd_device_set_gatt_db(struct btd_device *device, struct gatt_db *db) { struct gatt_db *clone; if (!device) return false; clone = gatt_db_clone(db); if (!clone) return false; gatt_db_unregister(device->db, device->db_id); gatt_db_unref(device->db); device->db = clone; device->db_id = gatt_db_register(device->db, gatt_service_added, gatt_service_removed, device, NULL); return true; } struct bt_gatt_client *btd_device_get_gatt_client(struct btd_device *device) { if (!device) return NULL; return device->client; } void *btd_device_get_attrib(struct btd_device *device) { if (!device) return NULL; return device->attrib; } struct bt_gatt_server *btd_device_get_gatt_server(struct btd_device *device) { if (!device) return NULL; return device->server; } void btd_device_gatt_set_service_changed(struct btd_device *device, uint16_t start, uint16_t end) { /* * TODO: Remove this function and handle service changed via * gatt-client. */ } void btd_device_add_uuid(struct btd_device *device, const char *uuid) { GSList *uuid_list; char *new_uuid; new_uuid = g_strdup(uuid); uuid_list = g_slist_append(NULL, new_uuid); device_probe_profiles(device, uuid_list); g_free(new_uuid); g_slist_free(uuid_list); } static sdp_list_t *read_device_records(struct btd_device *device) { char local[18], peer[18]; char filename[PATH_MAX]; GKeyFile *key_file; GError *gerr = NULL; char **keys, **handle; char *str; sdp_list_t *recs = NULL; sdp_record_t *rec; ba2str(btd_adapter_get_address(device->adapter), local); ba2str(&device->bdaddr, peer); create_filename(filename, PATH_MAX, "/%s/cache/%s", local, peer); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_error_free(gerr); } keys = g_key_file_get_keys(key_file, "ServiceRecords", NULL, NULL); for (handle = keys; handle && *handle; handle++) { str = g_key_file_get_string(key_file, "ServiceRecords", *handle, NULL); if (!str) continue; rec = record_from_string(str); recs = sdp_list_append(recs, rec); g_free(str); } g_strfreev(keys); g_key_file_free(key_file); return recs; } void btd_device_set_record(struct btd_device *device, const char *uuid, const char *record) { /* This API is only used for BR/EDR */ struct bearer_state *state = &device->bredr_state; struct browse_req *req; sdp_list_t *recs = NULL; sdp_record_t *rec; if (!record) return; req = browse_request_new(device, BROWSE_SDP, NULL); if (!req) return; rec = record_from_string(record); recs = sdp_list_append(recs, rec); update_bredr_services(req, recs); sdp_list_free(recs, NULL); device->svc_refreshed = true; state->svc_resolved = true; device_probe_profiles(device, req->profiles_added); /* Propagate services changes */ g_dbus_emit_property_changed(dbus_conn, req->device->path, DEVICE_INTERFACE, "UUIDs"); device_svc_resolved(device, BROWSE_SDP, device->bdaddr_type, 0); } const sdp_record_t *btd_device_get_record(struct btd_device *device, const char *uuid) { /* Load records from storage if there is nothing in cache */ if (!device->tmp_records) { device->tmp_records = read_device_records(device); if (!device->tmp_records) return NULL; } return find_record_in_list(device->tmp_records, uuid); } struct btd_device *btd_device_ref(struct btd_device *device) { __sync_fetch_and_add(&device->ref_count, 1); return device; } static void remove_sirk_info(void *data, void *user_data) { struct sirk_info *info = data; struct btd_device *device = user_data; btd_set_remove_device(info->set, device); } void btd_device_unref(struct btd_device *device) { if (__sync_sub_and_fetch(&device->ref_count, 1)) return; if (!device->path) { error("freeing device without an object path"); return; } if (!queue_isempty(device->sirks)) queue_foreach(device->sirks, remove_sirk_info, device); DBG("Freeing device %s", device->path); g_dbus_unregister_interface(dbus_conn, device->path, DEVICE_INTERFACE); } int device_get_appearance(struct btd_device *device, uint16_t *value) { if (device->appearance == 0) return -1; if (value) *value = device->appearance; return 0; } void device_set_appearance(struct btd_device *device, uint16_t value) { const char *icon = gap_appearance_to_icon(value); if (device->appearance == value) return; g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Appearance"); if (icon) g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Icon"); device->appearance = value; store_device_info(device); } void btd_device_set_pnpid(struct btd_device *device, uint16_t source, uint16_t vendor, uint16_t product, uint16_t version) { if (device->vendor_src == source && device->version == version && device->vendor == vendor && device->product == product) return; device->vendor_src = source; device->vendor = vendor; device->product = product; device->version = version; free(device->modalias); device->modalias = bt_modalias(source, vendor, product, version); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Modalias"); store_device_info(device); } bool btd_device_flags_enabled(struct btd_device *dev, uint32_t flags) { const char *ll_privacy = "15c0a148-c273-11ea-b3de-0242ac130004"; if (!dev) return false; if (dev->current_flags & flags) return true; /* For backward compatibility check for LL Privacy experimental UUID * since that shall be equivalent to DEVICE_FLAG_ADDRESS_RESOLUTION on * older kernels. */ if ((flags & DEVICE_FLAG_ADDRESS_RESOLUTION) && btd_kernel_experimental_enabled(ll_privacy)) return true; return false; } uint32_t btd_device_get_current_flags(struct btd_device *dev) { return dev->current_flags; } uint32_t btd_device_get_supported_flags(struct btd_device *dev) { return dev->supported_flags; } void btd_device_set_pending_flags(struct btd_device *dev, uint32_t flags) { if (!dev) return; dev->pending_flags = flags; } uint32_t btd_device_get_pending_flags(struct btd_device *dev) { if (!dev) return 0; return dev->pending_flags; } /* This event is sent immediately after add device on all mgmt sockets. * Afterwards, it is only sent to mgmt sockets other than the one which called * set_device_flags. */ void btd_device_flags_changed(struct btd_device *dev, uint32_t supported_flags, uint32_t current_flags) { const uint32_t changed_flags = dev->current_flags ^ current_flags; bool flag_value; dev->supported_flags = supported_flags; dev->current_flags = current_flags; dev->pending_flags &= ~current_flags; if (!changed_flags) return; if (changed_flags & DEVICE_FLAG_REMOTE_WAKEUP && dev->wake_support) { flag_value = !!(current_flags & DEVICE_FLAG_REMOTE_WAKEUP); dev->pending_wake_allowed = flag_value; /* If an override exists and doesn't match the current state, * apply it. This logic will run after Add Device only and will * enable wake for previously paired devices. */ if (dev->wake_override != WAKE_FLAG_DEFAULT) { bool wake_allowed = dev->wake_override == WAKE_FLAG_ENABLED; if (flag_value != wake_allowed) device_set_wake_allowed(dev, wake_allowed, -1U); else device_set_wake_allowed_complete(dev); } else { device_set_wake_allowed_complete(dev); } } } static void service_state_changed(struct btd_service *service, btd_service_state_t old_state, btd_service_state_t new_state, void *user_data) { struct btd_profile *profile = btd_service_get_profile(service); struct btd_device *device = btd_service_get_device(service); int err = btd_service_get_error(service); if (new_state == BTD_SERVICE_STATE_CONNECTING || new_state == BTD_SERVICE_STATE_DISCONNECTING) return; if (old_state == BTD_SERVICE_STATE_CONNECTING) device_profile_connected(device, profile, err); else if (old_state == BTD_SERVICE_STATE_DISCONNECTING) device_profile_disconnected(device, profile, err); } struct btd_service *btd_device_get_service(struct btd_device *dev, const char *remote_uuid) { GSList *l; for (l = dev->services; l != NULL; l = g_slist_next(l)) { struct btd_service *service = l->data; struct btd_profile *p = btd_service_get_profile(service); if (g_str_equal(p->remote_uuid, remote_uuid)) return service; } return NULL; } void btd_device_init(void) { dbus_conn = btd_get_dbus_connection(); service_state_cb_id = btd_service_add_state_cb( service_state_changed, NULL); } void btd_device_cleanup(void) { btd_service_remove_state_cb(service_state_cb_id); } void btd_device_set_volume(struct btd_device *device, int8_t volume) { device->volume = volume; } int8_t btd_device_get_volume(struct btd_device *device) { return device->volume; } void btd_device_foreach_ad(struct btd_device *dev, bt_ad_func_t func, void *data) { bt_ad_foreach_data(dev->ad, func, data); } void btd_device_set_conn_param(struct btd_device *device, uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout) { /* Attempt to load the new connection parameters, in case it is * successful the MGMT_EV_NEW_CONN_PARAM will be generated which will * then trigger btd_adapter_store_conn_param. */ btd_adapter_load_conn_param(device->adapter, &device->bdaddr, device->bdaddr_type, min_interval, max_interval, latency, timeout); } void btd_device_foreach_service_data(struct btd_device *dev, bt_ad_func_t func, void *data) { bt_ad_foreach_service_data(dev->ad, func, data); } void btd_device_foreach_service(struct btd_device *dev, btd_device_service_func_t func, void *user_data) { GSList *l; for (l = dev->services; l; l = l->next) func(l->data, user_data); } void device_remove_pending_services(struct btd_device *dev, uint8_t bdaddr_type) { GSList *l = dev->pending; GSList *next; struct btd_service *service; struct btd_profile *profile; while (l) { next = l->next; service = l->data; profile = btd_service_get_profile(service); if (!profile) goto next; if (bdaddr_type == BDADDR_BREDR) { if (profile->bearer == BTD_PROFILE_BEARER_LE) goto next; } else { if (profile->bearer == BTD_PROFILE_BEARER_BREDR) goto next; } /* Matched: remove from pending list */ dev->pending = g_slist_remove(dev->pending, service); next: l = next; } } bluez-5.87/src/PaxHeaders/eir.h0000644000000000000000000000005015200607060013366 xustar0020 atime=1783092683 20 ctime=1783094106 bluez-5.87/src/eir.h0000644000000000000000000000762715200607060013063 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011 Nokia Corporation * Copyright (C) 2011 Marcel Holtmann * * */ #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/shared/queue.h" #define EIR_FLAGS 0x01 /* flags */ #define EIR_UUID16_SOME 0x02 /* 16-bit UUID, more available */ #define EIR_UUID16_ALL 0x03 /* 16-bit UUID, all listed */ #define EIR_UUID32_SOME 0x04 /* 32-bit UUID, more available */ #define EIR_UUID32_ALL 0x05 /* 32-bit UUID, all listed */ #define EIR_UUID128_SOME 0x06 /* 128-bit UUID, more available */ #define EIR_UUID128_ALL 0x07 /* 128-bit UUID, all listed */ #define EIR_NAME_SHORT 0x08 /* shortened local name */ #define EIR_NAME_COMPLETE 0x09 /* complete local name */ #define EIR_TX_POWER 0x0A /* transmit power level */ #define EIR_CLASS_OF_DEV 0x0D /* Class of Device */ #define EIR_SSP_HASH 0x0E /* SSP Hash */ #define EIR_SSP_RANDOMIZER 0x0F /* SSP Randomizer */ #define EIR_DEVICE_ID 0x10 /* device ID */ #define EIR_SOLICIT16 0x14 /* LE: Solicit UUIDs, 16-bit */ #define EIR_SOLICIT128 0x15 /* LE: Solicit UUIDs, 128-bit */ #define EIR_SVC_DATA16 0x16 /* LE: Service data, 16-bit UUID */ #define EIR_PUB_TRGT_ADDR 0x17 /* LE: Public Target Address */ #define EIR_RND_TRGT_ADDR 0x18 /* LE: Random Target Address */ #define EIR_GAP_APPEARANCE 0x19 /* GAP appearance */ #define EIR_SOLICIT32 0x1F /* LE: Solicit UUIDs, 32-bit */ #define EIR_SVC_DATA32 0x20 /* LE: Service data, 32-bit UUID */ #define EIR_SVC_DATA128 0x21 /* LE: Service data, 128-bit UUID */ #define EIR_TRANSPORT_DISCOVERY 0x26 /* Transport Discovery Service */ #define EIR_CSIP_RSI 0x2e /* Resolvable Set Identifier */ #define EIR_BC_NAME 0x30 /* Broadcast Name */ #define EIR_MANUFACTURER_DATA 0xFF /* Manufacturer Specific Data */ /* Flags Descriptions */ #define EIR_LIM_DISC 0x01 /* LE Limited Discoverable Mode */ #define EIR_GEN_DISC 0x02 /* LE General Discoverable Mode */ #define EIR_BREDR_UNSUP 0x04 /* BR/EDR Not Supported */ #define EIR_CONTROLLER 0x08 /* Simultaneous LE and BR/EDR to Same Device Capable (Controller) */ #define EIR_SIM_HOST 0x10 /* Simultaneous LE and BR/EDR to Same Device Capable (Host) */ #define EIR_SD_MAX_LEN 238 /* 240 (EIR) - 2 (len) */ #define EIR_MSD_MAX_LEN 236 /* 240 (EIR) - 2 (len & type) - 2 */ struct eir_msd { uint16_t company; uint8_t data[EIR_MSD_MAX_LEN]; uint8_t data_len; }; struct eir_sd { char *uuid; uint8_t data[EIR_SD_MAX_LEN]; uint8_t data_len; }; struct eir_ad { uint8_t type; uint8_t len; void *data; }; struct eir_data { struct queue *services; unsigned int flags; char *name; uint32_t class; uint16_t appearance; bool name_complete; bool rsi; int8_t tx_power; uint8_t *hash; uint8_t *randomizer; bdaddr_t addr; uint16_t did_vendor; uint16_t did_product; uint16_t did_version; uint16_t did_source; struct queue *msd_list; struct queue *sd_list; struct queue *data_list; }; void eir_data_free(struct eir_data *eir); void eir_parse(struct eir_data *eir, const uint8_t *eir_data, uint8_t eir_len); int eir_parse_oob(struct eir_data *eir, uint8_t *eir_data, uint16_t eir_len); int eir_create_oob(const bdaddr_t *addr, const char *name, uint32_t cod, const uint8_t *hash, const uint8_t *randomizer, uint16_t did_vendor, uint16_t did_product, uint16_t did_version, uint16_t did_source, sdp_list_t *uuids, uint8_t *data); struct eir_sd *eir_get_service_data(struct eir_data *eir, const char *uuid); bluez-5.87/src/PaxHeaders/org.bluez.service0000644000000000000000000000005011766125764015752 xustar0020 atime=1770587295 20 ctime=1783094110 bluez-5.87/src/org.bluez.service0000644000000000000000000000013711766125764015434 0ustar00rootroot[D-BUS Service] Name=org.bluez Exec=/bin/false User=root SystemdService=dbus-org.bluez.service bluez-5.87/src/PaxHeaders/storage.h0000644000000000000000000000005014015011623014250 xustar0020 atime=1783093606 20 ctime=1783094106 bluez-5.87/src/storage.h0000644000000000000000000000103414015011623013727 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2002-2010 Marcel Holtmann * * */ int read_discoverable_timeout(const char *src, int *timeout); int read_pairable_timeout(const char *src, int *timeout); int read_on_mode(const char *src, char *mode, int length); int read_local_name(const bdaddr_t *bdaddr, char *name); sdp_record_t *record_from_string(const char *str); sdp_record_t *find_record_in_list(sdp_list_t *recs, const char *uuid); bluez-5.87/src/PaxHeaders/uuid-helper.c0000644000000000000000000000005015063045254015035 xustar0020 atime=1783093349 20 ctime=1783094106 bluez-5.87/src/uuid-helper.c0000644000000000000000000001153215063045254014520 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "uuid-helper.h" char *bt_modalias(uint16_t source, uint16_t vendor, uint16_t product, uint16_t version) { char *str; int err; switch (source) { case 0x0001: err = asprintf(&str, "%s:v%04Xp%04Xd%04X", "bluetooth", vendor, product, version); break; case 0x0002: err = asprintf(&str, "%s:v%04Xp%04Xd%04X", "usb", vendor, product, version); break; default: return NULL; } if (err < 0) return NULL; return str; } char *bt_uuid2string(uuid_t *uuid) { char *str; uuid_t uuid128; unsigned int data0; unsigned short data1; unsigned short data2; unsigned short data3; unsigned int data4; unsigned short data5; int err; if (!uuid) return NULL; switch (uuid->type) { case SDP_UUID16: sdp_uuid16_to_uuid128(&uuid128, uuid); break; case SDP_UUID32: sdp_uuid32_to_uuid128(&uuid128, uuid); break; case SDP_UUID128: memcpy(&uuid128, uuid, sizeof(uuid_t)); break; default: /* Type of UUID unknown */ return NULL; } memcpy(&data0, &uuid128.value.uuid128.data[0], 4); memcpy(&data1, &uuid128.value.uuid128.data[4], 2); memcpy(&data2, &uuid128.value.uuid128.data[6], 2); memcpy(&data3, &uuid128.value.uuid128.data[8], 2); memcpy(&data4, &uuid128.value.uuid128.data[10], 4); memcpy(&data5, &uuid128.value.uuid128.data[14], 2); err = asprintf(&str, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x", ntohl(data0), ntohs(data1), ntohs(data2), ntohs(data3), ntohl(data4), ntohs(data5)); if (err < 0) return NULL; return str; } static struct { const char *name; uint16_t class; } bt_services[] = { { "a2dp-sink", AUDIO_SINK_SVCLASS_ID }, { "a2dp-source",AUDIO_SOURCE_SVCLASS_ID }, { "bip", IMAGING_SVCLASS_ID }, { "bpp", BASIC_PRINTING_SVCLASS_ID }, { "dun", DIALUP_NET_SVCLASS_ID }, { "fax", FAX_SVCLASS_ID }, { "ftp", OBEX_FILETRANS_SVCLASS_ID }, { "gnss", GNSS_SERVER_SVCLASS_ID }, { "hfp", HANDSFREE_SVCLASS_ID }, { "hfp-ag", HANDSFREE_AGW_SVCLASS_ID }, { "hfp-hf", HANDSFREE_SVCLASS_ID }, { "hsp", HEADSET_SVCLASS_ID }, { "hsp-ag", HEADSET_AGW_SVCLASS_ID }, { "hsp-hs", HEADSET_SVCLASS_ID }, { "map-mas", MAP_MSE_SVCLASS_ID }, { "map-mce", MAP_MCE_SVCLASS_ID }, { "map-mns", MAP_MCE_SVCLASS_ID }, { "map-mse", MAP_MSE_SVCLASS_ID }, { "opp", OBEX_OBJPUSH_SVCLASS_ID }, { "pbap", PBAP_SVCLASS_ID }, { "pbap-pce", PBAP_PCE_SVCLASS_ID }, { "pbap-pse", PBAP_PSE_SVCLASS_ID }, { "sap", SAP_SVCLASS_ID }, { "spp", SERIAL_PORT_SVCLASS_ID }, { "synch", IRMC_SYNC_SVCLASS_ID }, { } }; static uint16_t name2class(const char *pattern) { int i; for (i = 0; bt_services[i].name; i++) { if (strcasecmp(bt_services[i].name, pattern) == 0) return bt_services[i].class; } return 0; } static inline bool is_uuid128(const char *string) { return (strlen(string) == 36 && string[8] == '-' && string[13] == '-' && string[18] == '-' && string[23] == '-'); } static int string2uuid16(uuid_t *uuid, const char *string) { int length = strlen(string); char *endptr = NULL; uint16_t u16; if (length != 4 && length != 6) return -EINVAL; u16 = strtol(string, &endptr, 16); if (endptr && *endptr == '\0') { sdp_uuid16_create(uuid, u16); return 0; } return -EINVAL; } char *bt_name2string(const char *pattern) { uuid_t uuid; unsigned int uuid16; char *endptr = NULL; /* UUID 128 string format */ if (is_uuid128(pattern)) return strdup(pattern); /* Friendly service name format */ uuid16 = name2class(pattern); if (uuid16) goto proceed; /* HEX format */ uuid16 = strtoul(pattern, &endptr, 16); if (uuid16 <= 0xffff && *endptr == '\0') goto proceed; return NULL; proceed: sdp_uuid16_create(&uuid, uuid16); return bt_uuid2string(&uuid); } int bt_string2uuid(uuid_t *uuid, const char *string) { uint32_t data0, data4; uint16_t data1, data2, data3, data5; if (is_uuid128(string) && sscanf(string, "%08x-%04hx-%04hx-%04hx-%08x%04hx", &data0, &data1, &data2, &data3, &data4, &data5) == 6) { uint8_t val[16]; data0 = htonl(data0); data1 = htons(data1); data2 = htons(data2); data3 = htons(data3); data4 = htonl(data4); data5 = htons(data5); memcpy(&val[0], &data0, 4); memcpy(&val[4], &data1, 2); memcpy(&val[6], &data2, 2); memcpy(&val[8], &data3, 2); memcpy(&val[10], &data4, 4); memcpy(&val[14], &data5, 2); sdp_uuid128_create(uuid, val); return 0; } else { uint16_t class = name2class(string); if (class) { sdp_uuid16_create(uuid, class); return 0; } return string2uuid16(uuid, string); } } bluez-5.87/src/PaxHeaders/textfile.c0000644000000000000000000000005015200607060014426 xustar0020 atime=1783092684 20 ctime=1783094104 bluez-5.87/src/textfile.c0000644000000000000000000001774315200607060014123 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "textfile.h" int create_filename(char *str, size_t size, const char *fmt, ...) { static const char *prefix; static int prefix_len; char suffix[PATH_MAX]; va_list ap; int err; if (!prefix) { const char *statedir = getenv("STATE_DIRECTORY"); /* Check if running as service */ if (statedir) { prefix = statedir; /* Check if there multiple paths given */ if (strstr(prefix, ":")) prefix_len = strstr(prefix, ":") - prefix; else prefix_len = strlen(prefix); } else { prefix = STORAGEDIR; prefix_len = strlen(prefix); } } va_start(ap, fmt); err = vsnprintf(suffix, sizeof(suffix), fmt, ap); va_end(ap); if (err < 0) return err; return snprintf(str, size, "%*s%s", prefix_len, prefix, suffix); } static int create_dirs(const char *filename, const mode_t mode) { struct stat st; char dir[PATH_MAX + 1]; const char *prev, *next; int err; err = stat(filename, &st); if (!err && S_ISREG(st.st_mode)) return 0; memset(dir, 0, PATH_MAX + 1); strcat(dir, "/"); prev = strchr(filename, '/'); while (prev) { next = strchr(prev + 1, '/'); if (!next) break; if (next - prev == 1) { prev = next; continue; } strncat(dir, prev + 1, next - prev); mkdir(dir, mode); prev = next; } return 0; } int create_file(const char *filename, const mode_t mode) { int fd; create_dirs(filename, 0700); fd = open(filename, O_RDWR | O_CREAT, mode); if (fd < 0) return fd; close(fd); return 0; } int create_name(char *buf, size_t size, const char *address, const char *name) { return create_filename(buf, size, "/%s/%s", address, name); } static inline char *find_key(char *map, size_t size, const char *key, size_t len, int icase) { char *ptr = map; size_t ptrlen = size; while (ptrlen > len + 1) { int cmp = (icase) ? strncasecmp(ptr, key, len) : strncmp(ptr, key, len); if (cmp == 0) { if (ptr == map) { if (*(ptr + len) == ' ') return ptr; } else if ((*(ptr - 1) == '\r' || *(ptr - 1) == '\n') && *(ptr + len) == ' ') return ptr; } if (icase) { char *p1 = memchr(ptr + 1, tolower(*key), ptrlen - 1); char *p2 = memchr(ptr + 1, toupper(*key), ptrlen - 1); if (!p1) ptr = p2; else if (!p2) ptr = p1; else ptr = (p1 < p2) ? p1 : p2; } else ptr = memchr(ptr + 1, *key, ptrlen - 1); if (!ptr) return NULL; ptrlen = size - (ptr - map); } return NULL; } static inline int write_key_value(int fd, const char *key, const char *value) { char *str; size_t size; int err = 0; size = strlen(key) + strlen(value) + 2; str = malloc(size + 1); if (!str) return ENOMEM; sprintf(str, "%s %s\n", key, value); if (write(fd, str, size) < 0) err = -errno; free(str); return err; } static char *strnpbrk(const char *s, ssize_t len, const char *accept) { const char *p = s; const char *end; end = s + len - 1; while (p <= end && *p) { const char *a = accept; while (*a) { if (*p == *a) return (char *) p; a++; } p++; } return NULL; } static int write_key(const char *pathname, const char *key, const char *value, int icase) { struct stat st; char *map, *off, *end, *str; off_t size; size_t base; int fd, len, err = 0; fd = open(pathname, O_RDWR); if (fd < 0) return -errno; if (flock(fd, LOCK_EX) < 0) { err = -errno; goto close; } if (fstat(fd, &st) < 0) { err = -errno; goto unlock; } size = st.st_size; if (!size) { if (value) { lseek(fd, size, SEEK_SET); err = write_key_value(fd, key, value); } goto unlock; } map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED, fd, 0); if (!map || map == MAP_FAILED) { err = -errno; goto unlock; } len = strlen(key); off = find_key(map, size, key, len, icase); if (!off) { munmap(map, size); if (value) { lseek(fd, size, SEEK_SET); err = write_key_value(fd, key, value); } goto unlock; } base = off - map; end = strnpbrk(off, size, "\r\n"); if (!end) { err = -EILSEQ; goto unmap; } if (value && ((ssize_t) strlen(value) == end - off - len - 1) && !strncmp(off + len + 1, value, end - off - len - 1)) goto unmap; len = strspn(end, "\r\n"); end += len; len = size - (end - map); if (!len) { munmap(map, size); if (ftruncate(fd, base) < 0) { err = -errno; goto unlock; } lseek(fd, base, SEEK_SET); if (value) err = write_key_value(fd, key, value); goto unlock; } if (len < 0 || len > size) { err = -EILSEQ; goto unmap; } str = malloc(len); if (!str) { err = -errno; goto unmap; } memcpy(str, end, len); munmap(map, size); if (ftruncate(fd, base) < 0) { err = -errno; free(str); goto unlock; } lseek(fd, base, SEEK_SET); if (value) err = write_key_value(fd, key, value); if (write(fd, str, len) < 0) err = -errno; free(str); goto unlock; unmap: munmap(map, size); unlock: flock(fd, LOCK_UN); close: fdatasync(fd); close(fd); errno = -err; return err; } static char *read_key(const char *pathname, const char *key, int icase) { struct stat st; char *map, *off, *end, *str = NULL; off_t size; size_t len; int fd, err = 0; fd = open(pathname, O_RDONLY); if (fd < 0) return NULL; if (flock(fd, LOCK_SH) < 0) { err = -errno; goto close; } if (fstat(fd, &st) < 0) { err = -errno; goto unlock; } size = st.st_size; map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); if (!map || map == MAP_FAILED) { err = -errno; goto unlock; } len = strlen(key); off = find_key(map, size, key, len, icase); if (!off) { err = -EILSEQ; goto unmap; } end = strnpbrk(off, size - (off - map), "\r\n"); if (!end) { err = -EILSEQ; goto unmap; } str = malloc(end - off - len); if (!str) { err = -EILSEQ; goto unmap; } memset(str, 0, end - off - len); strncpy(str, off + len + 1, end - off - len - 1); unmap: munmap(map, size); unlock: flock(fd, LOCK_UN); close: close(fd); errno = -err; return str; } int textfile_put(const char *pathname, const char *key, const char *value) { return write_key(pathname, key, value, 0); } int textfile_del(const char *pathname, const char *key) { return write_key(pathname, key, NULL, 0); } char *textfile_get(const char *pathname, const char *key) { return read_key(pathname, key, 0); } int textfile_foreach(const char *pathname, textfile_cb func, void *data) { struct stat st; char *map, *off, *end, *key, *value; off_t size; size_t len; int fd, err = 0; fd = open(pathname, O_RDONLY); if (fd < 0) return -errno; if (flock(fd, LOCK_SH) < 0) { err = -errno; goto close; } if (fstat(fd, &st) < 0) { err = -errno; goto unlock; } size = st.st_size; map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); if (!map || map == MAP_FAILED) { err = -errno; goto unlock; } off = map; while (size - (off - map) > 0) { end = strnpbrk(off, size - (off - map), " "); if (!end) { err = -EILSEQ; break; } len = end - off; key = malloc(len + 1); if (!key) { err = -errno; break; } memset(key, 0, len + 1); memcpy(key, off, len); off = end + 1; if (size - (off - map) < 0) { err = -EILSEQ; free(key); break; } end = strnpbrk(off, size - (off - map), "\r\n"); if (!end) { err = -EILSEQ; free(key); break; } len = end - off; value = malloc(len + 1); if (!value) { err = -errno; free(key); break; } memset(value, 0, len + 1); memcpy(value, off, len); func(key, value, data); free(key); free(value); off = end + 1; } munmap(map, size); unlock: flock(fd, LOCK_UN); close: close(fd); errno = -err; return 0; } bluez-5.87/src/PaxHeaders/oui.h0000644000000000000000000000005014015011623013400 xustar0020 atime=1783093296 20 ctime=1783094106 bluez-5.87/src/oui.h0000644000000000000000000000033214015011623013057 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ char *batocomp(const bdaddr_t *ba); bluez-5.87/src/PaxHeaders/btd.h0000644000000000000000000000005015200607060013360 xustar0020 atime=1783092683 20 ctime=1783094106 bluez-5.87/src/btd.h0000644000000000000000000000706515200607060013051 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2000-2001 Qualcomm Incorporated * Copyright (C) 2002-2003 Maxim Krasnyansky * Copyright (C) 2002-2010 Marcel Holtmann * * */ #include typedef enum { BT_MODE_DUAL, BT_MODE_BREDR, BT_MODE_LE, } bt_mode_t; typedef enum { BT_GATT_CACHE_ALWAYS, BT_GATT_CACHE_YES, BT_GATT_CACHE_NO, } bt_gatt_cache_t; enum jw_repairing_t { JW_REPAIRING_NEVER, JW_REPAIRING_CONFIRM, JW_REPAIRING_ALWAYS, }; enum mps_mode_t { MPS_OFF, MPS_SINGLE, MPS_MULTIPLE, }; enum sc_mode_t { SC_OFF, SC_ON, SC_ONLY, }; enum bt_gatt_export_t { BT_GATT_EXPORT_OFF, BT_GATT_EXPORT_READ_ONLY, BT_GATT_EXPORT_READ_WRITE, }; struct btd_br_defaults { uint16_t page_scan_type; uint16_t page_scan_interval; uint16_t page_scan_win; uint16_t scan_type; uint16_t scan_interval; uint16_t scan_win; uint16_t link_supervision_timeout; uint16_t page_timeout; uint16_t min_sniff_interval; uint16_t max_sniff_interval; uint32_t idle_timeout; }; struct btd_le_defaults { uint8_t addr_resolution; uint16_t min_adv_interval; uint16_t max_adv_interval; uint16_t adv_rotation_interval; uint16_t scan_interval_autoconnect; uint16_t scan_win_autoconnect; uint16_t scan_interval_suspend; uint16_t scan_win_suspend; uint16_t scan_interval_discovery; uint16_t scan_win_discovery; uint16_t scan_interval_adv_monitor; uint16_t scan_win_adv_monitor; uint16_t scan_interval_connect; uint16_t scan_win_connect; uint16_t min_conn_interval; uint16_t max_conn_interval; uint16_t conn_latency; uint16_t conn_lsto; uint16_t autoconnect_timeout; uint16_t advmon_allowlist_scan_duration; uint16_t advmon_no_filter_scan_duration; uint8_t enable_advmon_interleave_scan; }; struct btd_le_bcs { uint8_t role; uint8_t cs_sync_ant_sel; int8_t max_tx_power; }; struct btd_defaults { uint16_t num_entries; struct btd_br_defaults br; struct btd_le_defaults le; struct btd_le_bcs bcs; }; struct btd_csis { bool encrypt; uint8_t sirk[16]; uint8_t size; uint8_t rank; }; struct btd_avdtp_opts { uint8_t session_mode; uint8_t stream_mode; }; struct btd_avrcp_opts { bool volume_without_target; bool volume_category; }; struct btd_advmon_opts { uint8_t rssi_sampling_period; }; struct btd_opts { char *name; uint32_t class; bool pairable; uint32_t pairto; uint32_t discovto; uint32_t tmpto; uint8_t privacy; bool device_privacy; uint32_t name_request_retry_delay; uint8_t secure_conn; struct btd_defaults defaults; bool reverse_discovery; bool name_resolv; bool debug_keys; bool fast_conn; bool refresh_discovery; bool experimental; bool testing; bool filter_discoverable; struct queue *kernel; uint16_t did_source; uint16_t did_vendor; uint16_t did_product; uint16_t did_version; bt_mode_t mode; uint16_t max_adapters; bt_gatt_cache_t gatt_cache; uint16_t gatt_mtu; uint8_t gatt_channels; bool gatt_client; enum bt_gatt_export_t gatt_export; bool gatt_seclevel; enum mps_mode_t mps; struct btd_avdtp_opts avdtp; struct btd_avrcp_opts avrcp; uint8_t key_size; enum jw_repairing_t jw_repairing; struct btd_advmon_opts advmon; struct btd_csis csis; }; extern struct btd_opts btd_opts; void plugin_init(const char *enable, const char *disable); void plugin_cleanup(void); void rfkill_init(void); void rfkill_exit(void); int rfkill_get_blocked(uint16_t index); GKeyFile *btd_get_main_conf(void); bool btd_kernel_experimental_enabled(const char *uuid); void btd_exit(void); bluez-5.87/src/PaxHeaders/dbus-common.h0000644000000000000000000000005014015011623015027 xustar0020 atime=1783093605 20 ctime=1783094106 bluez-5.87/src/dbus-common.h0000644000000000000000000000115114015011623014506 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ void dict_append_entry(DBusMessageIter *dict, const char *key, int type, void *val); void dict_append_array(DBusMessageIter *dict, const char *key, int type, void *val, int n_elements); void set_dbus_connection(DBusConnection *conn); DBusConnection *btd_get_dbus_connection(void); const char *class_to_icon(uint32_t class); const char *gap_appearance_to_icon(uint16_t appearance); bluez-5.87/src/PaxHeaders/device.h0000644000000000000000000000005015221752714014060 xustar0020 atime=1783092685 20 ctime=1783094106 bluez-5.87/src/device.h0000644000000000000000000002734715221752714013556 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright 2024 NXP * */ #include "src/shared/queue.h" #define DEVICE_INTERFACE "org.bluez.Device1" struct btd_device; struct btd_device *device_create(struct btd_adapter *adapter, const bdaddr_t *address, uint8_t bdaddr_type); struct btd_device *device_create_from_storage(struct btd_adapter *adapter, const char *address, GKeyFile *key_file); char *btd_device_get_storage_path(struct btd_device *device, const char *filename); void btd_device_device_set_name(struct btd_device *device, const char *name); void device_store_cached_name(struct btd_device *dev, const char *name); void device_get_name(struct btd_device *device, char *name, size_t len); bool device_name_known(struct btd_device *device); bool device_is_name_resolve_allowed(struct btd_device *device); void device_name_resolve_fail(struct btd_device *device); void device_set_class(struct btd_device *device, uint32_t class); bool device_address_is_private(struct btd_device *dev); void device_set_privacy(struct btd_device *device, bool value, const uint8_t *irk); bool device_get_privacy(struct btd_device *device); void device_update_addr(struct btd_device *device, const bdaddr_t *bdaddr, uint8_t bdaddr_type, const uint8_t *irk); void device_set_bredr_support(struct btd_device *device); void device_set_le_support(struct btd_device *device, uint8_t bdaddr_type); void device_update_last_seen(struct btd_device *device, uint8_t bdaddr_type, bool connectable); void device_merge_duplicate(struct btd_device *dev, struct btd_device *dup); uint32_t btd_device_get_class(struct btd_device *device); uint16_t btd_device_get_vendor(struct btd_device *device); uint16_t btd_device_get_vendor_src(struct btd_device *device); uint16_t btd_device_get_product(struct btd_device *device); uint16_t btd_device_get_version(struct btd_device *device); const char *btd_device_get_icon(struct btd_device *device); void device_remove_bonding(struct btd_device *device, uint8_t bdaddr_type); void device_remove(struct btd_device *device, gboolean remove_stored); int device_address_cmp(gconstpointer a, gconstpointer b); int device_bdaddr_cmp(gconstpointer a, gconstpointer b); /* Struct used by device_addr_type_cmp() */ struct device_addr_type { bdaddr_t bdaddr; uint8_t bdaddr_type; }; int device_addr_type_cmp(gconstpointer a, gconstpointer b); GSList *btd_device_get_uuids(struct btd_device *device); bool btd_device_has_uuid(struct btd_device *device, const char *uuid); void device_probe_profiles(struct btd_device *device, GSList *profiles); void btd_device_set_record(struct btd_device *device, const char *uuid, const char *record); const sdp_record_t *btd_device_get_record(struct btd_device *device, const char *uuid); struct gatt_primary *btd_device_get_primary(struct btd_device *device, const char *uuid); GSList *btd_device_get_primaries(struct btd_device *device); struct gatt_db *btd_device_get_gatt_db(struct btd_device *device); bool btd_device_set_gatt_db(struct btd_device *device, struct gatt_db *db); struct bt_gatt_client *btd_device_get_gatt_client(struct btd_device *device); struct bt_gatt_server *btd_device_get_gatt_server(struct btd_device *device); bool btd_device_is_initiator(struct btd_device *device); void *btd_device_get_attrib(struct btd_device *device); void btd_device_gatt_set_service_changed(struct btd_device *device, uint16_t start, uint16_t end); bool device_attach_att(struct btd_device *dev, GIOChannel *io); void btd_device_add_uuid(struct btd_device *device, const char *uuid); void device_add_eir_uuids(struct btd_device *dev, struct queue *uuids); void device_set_manufacturer_data(struct btd_device *dev, struct queue *queue, bool duplicate); void device_set_service_data(struct btd_device *dev, struct queue *queue, bool duplicate); void device_set_data(struct btd_device *dev, struct queue *queue, bool duplicate); void device_probe_profile(gpointer a, gpointer b); void device_remove_profile(gpointer a, gpointer b); struct btd_adapter *device_get_adapter(struct btd_device *device); const bdaddr_t *device_get_address(struct btd_device *device); uint8_t device_get_le_address_type(struct btd_device *device); const char *device_get_path(const struct btd_device *device); gboolean device_is_temporary(struct btd_device *device); bool device_is_connectable(struct btd_device *device); bool device_is_paired(struct btd_device *device, uint8_t bdaddr_type); bool device_is_bonded(struct btd_device *device, uint8_t bdaddr_type); bool btd_device_is_trusted(struct btd_device *device); bool device_is_cable_pairing(struct btd_device *device); void device_set_paired(struct btd_device *dev, uint8_t bdaddr_type); void device_set_unpaired(struct btd_device *dev, uint8_t bdaddr_type); void btd_device_set_temporary(struct btd_device *device, bool temporary); void btd_device_set_trusted(struct btd_device *device, gboolean trusted); void btd_device_set_connectable(struct btd_device *device, bool connectable); void device_set_bonded(struct btd_device *device, uint8_t bdaddr_type); void device_set_legacy(struct btd_device *device, bool legacy); void device_set_cable_pairing(struct btd_device *device, bool cable_pairing); void device_set_rssi_with_delta(struct btd_device *device, int8_t rssi, int8_t delta_threshold); void device_set_rssi(struct btd_device *device, int8_t rssi); void device_set_tx_power(struct btd_device *device, int8_t tx_power); void device_set_flags(struct btd_device *device, uint8_t flags); bool btd_device_is_connected(struct btd_device *dev); bool btd_device_bearer_is_connected(struct btd_device *dev); bool btd_device_bdaddr_type_connected(struct btd_device *dev, uint8_t type); uint8_t btd_device_get_bdaddr_type(struct btd_device *dev); bool device_is_retrying(struct btd_device *device); void device_bonding_complete(struct btd_device *device, uint8_t bdaddr_type, uint8_t status); gboolean device_is_bonding(struct btd_device *device, const char *sender); void device_bonding_attempt_failed(struct btd_device *device, uint8_t status); void device_bonding_failed(struct btd_device *device, uint8_t status); void device_cancel_bonding(struct btd_device *device, uint8_t status); struct btd_adapter_pin_cb_iter *device_bonding_iter(struct btd_device *device); int device_bonding_attempt_retry(struct btd_device *device); long device_bonding_last_duration(struct btd_device *device); void device_bonding_restart_timer(struct btd_device *device); int device_request_pincode(struct btd_device *device, gboolean secure); int device_request_passkey(struct btd_device *device, uint8_t type); int device_confirm_passkey(struct btd_device *device, uint8_t type, int32_t passkey, uint8_t confirm_hint); int device_notify_passkey(struct btd_device *device, uint8_t type, uint32_t passkey, uint8_t entered); int device_notify_pincode(struct btd_device *device, gboolean secure, const char *pincode); void device_cancel_authentication(struct btd_device *device, gboolean aborted); gboolean device_is_authenticating(struct btd_device *device); void device_cancel_browse(struct btd_device *device, uint8_t bdaddr_type); void device_add_connection(struct btd_device *dev, uint8_t bdaddr_type, uint32_t flags); void device_remove_connection(struct btd_device *device, uint8_t bdaddr_type, bool *remove, uint8_t reason); void device_request_disconnect(struct btd_device *device, DBusMessage *msg); bool device_is_disconnecting(struct btd_device *device); bool device_is_connecting(struct btd_device *device); void device_set_ltk(struct btd_device *device, const uint8_t val[16], bool central, uint8_t enc_size); bool btd_device_get_ltk(struct btd_device *device, uint8_t val[16], bool *central, uint8_t *enc_size); void device_set_csrk(struct btd_device *device, const uint8_t val[16], uint32_t counter, uint8_t type, bool store_hint); bool btd_device_add_set(struct btd_device *device, bool encrypted, uint8_t sirk[16], uint8_t size, uint8_t rank); void device_store_svc_chng_ccc(struct btd_device *device, uint8_t bdaddr_type, uint16_t value); void device_load_svc_chng_ccc(struct btd_device *device, uint16_t *ccc_le, uint16_t *ccc_bredr); void device_set_wake_support(struct btd_device *device, bool wake_support); void device_set_wake_override(struct btd_device *device, bool wake_override); void device_set_wake_allowed(struct btd_device *device, bool wake_allowed, guint32 id); void device_set_past_support(struct btd_device *device, bool value); void device_set_refresh_discovery(struct btd_device *dev, bool refresh); typedef void (*disconnect_watch) (struct btd_device *device, gboolean removal, void *user_data); guint device_add_disconnect_watch(struct btd_device *device, disconnect_watch watch, void *user_data, GDestroyNotify destroy); void device_remove_disconnect_watch(struct btd_device *device, guint id); void device_disconnect_watches_callback(struct btd_device *device); int device_get_appearance(struct btd_device *device, uint16_t *value); void device_set_appearance(struct btd_device *device, uint16_t value); struct btd_device *btd_device_ref(struct btd_device *device); void btd_device_unref(struct btd_device *device); int device_block(struct btd_device *device, gboolean update_only); int device_unblock(struct btd_device *device, gboolean silent, gboolean update_only); void btd_device_set_pnpid(struct btd_device *device, uint16_t source, uint16_t vendor, uint16_t product, uint16_t version); int device_connect_le(struct btd_device *dev); DBusMessage *device_connect_profiles(struct btd_device *dev, uint8_t bdaddr_type, DBusMessage *msg, const char *uuid); typedef void (*device_svc_cb_t) (struct btd_device *dev, int err, void *user_data); unsigned int device_wait_for_svc_complete(struct btd_device *dev, device_svc_cb_t func, void *user_data); bool device_remove_svc_complete_callback(struct btd_device *dev, unsigned int id); struct btd_service *btd_device_get_service(struct btd_device *dev, const char *remote_uuid); int device_discover_services(struct btd_device *device, uint8_t bdaddr_type, DBusMessage *msg); int btd_device_connect_services(struct btd_device *dev, GSList *services); bool btd_device_flags_enabled(struct btd_device *dev, uint32_t flags); uint32_t btd_device_get_current_flags(struct btd_device *dev); uint32_t btd_device_get_supported_flags(struct btd_device *dev); uint32_t btd_device_get_pending_flags(struct btd_device *dev); void btd_device_set_pending_flags(struct btd_device *dev, uint32_t flags); void btd_device_flags_changed(struct btd_device *dev, uint32_t supported_flags, uint32_t current_flags); bool btd_device_all_services_allowed(struct btd_device *dev); void btd_device_update_allowed_services(struct btd_device *dev); void btd_device_init(void); void btd_device_cleanup(void); void btd_device_set_volume(struct btd_device *dev, int8_t volume); int8_t btd_device_get_volume(struct btd_device *dev); typedef void (*bt_device_ad_func_t)(void *data, void *user_data); void btd_device_foreach_ad(struct btd_device *dev, bt_device_ad_func_t func, void *data); void btd_device_set_conn_param(struct btd_device *device, uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout); void btd_device_foreach_service_data(struct btd_device *dev, bt_device_ad_func_t func, void *data); typedef void (*btd_device_service_func_t)(struct btd_service *service, void *user_data); void btd_device_foreach_service(struct btd_device *dev, btd_device_service_func_t func, void *user_data); void device_remove_pending_services(struct btd_device *dev, uint8_t bdaddr_type); bluez-5.87/src/PaxHeaders/bearer.c0000644000000000000000000000005015221752714014054 xustar0020 atime=1783092685 20 ctime=1783094106 bluez-5.87/src/bearer.c0000644000000000000000000002513015221752714013536 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2025 Intel Corporation * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/mgmt.h" #include "bluetooth/uuid.h" #include "gdbus/gdbus.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/timeout.h" #include "log.h" #include "error.h" #include "btd.h" #include "adapter.h" #include "device.h" #include "profile.h" #include "service.h" #include "dbus-common.h" #include "bearer.h" #define DISCONNECT_TIMER 2 struct btd_bearer { struct btd_device *device; uint8_t type; const char *path; unsigned int disconn_timer; struct queue *disconnects; /* disconnects message */ /* Connect() is defined as a single in-flight operation. To preserve * the API semantics of org.bluez.Device1.Connect(), we do not queue * additional connect messages. */ DBusMessage *connect; /* connect message */ }; static void bearer_free_dbus_message(void *data) { dbus_message_unref((DBusMessage *)data); } static void bearer_free(void *data) { struct btd_bearer *bearer = data; free(bearer); } static void bearer_disconnect_service(struct btd_service *service, void *user_data) { uint8_t bdaddr_type = *(uint8_t *)user_data; struct btd_profile *profile = btd_service_get_profile(service); struct btd_device *device = btd_service_get_device(service); if (!profile || !device) return; if (bdaddr_type == BDADDR_BREDR) { if (profile->bearer == BTD_PROFILE_BEARER_LE) return; } else { if (profile->bearer == BTD_PROFILE_BEARER_BREDR) return; } DBG("Disconnecting profile %s for bearer addr type %u", profile->name ?: "(unknown)", bdaddr_type); btd_service_disconnect(service); } static bool bearer_disconnect_link(gpointer user_data) { struct btd_bearer *bearer = user_data; struct btd_device *device = bearer->device; bearer->disconn_timer = 0; if (btd_device_bdaddr_type_connected(device, bearer->type)) btd_adapter_disconnect_device(device_get_adapter(device), device_get_address(device), bearer->type); return FALSE; } static DBusMessage *bearer_connect(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_bearer *bearer = user_data; struct btd_device *device = bearer->device; int err; if (btd_device_bdaddr_type_connected(device, bearer->type)) { if (msg) return btd_error_already_connected(msg); return NULL; } if (device_is_bonding(device, NULL)) { if (msg) return btd_error_in_progress(msg); return NULL; } if (device_is_connecting(device) || bearer->connect) { if (msg) return btd_error_in_progress(msg); return NULL; } if (msg) bearer->connect = dbus_message_ref(msg); if (bearer->type == BDADDR_BREDR) return device_connect_profiles(device, BDADDR_BREDR, msg, NULL); else { btd_device_set_temporary(device, false); err = device_connect_le(device); if (err < 0) return btd_error_failed(msg, strerror(-err)); } return NULL; } static DBusMessage *bearer_disconnect(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_bearer *bearer = user_data; struct btd_device *device = bearer->device; if (!btd_device_bdaddr_type_connected(device, bearer->type)) { if (msg) return btd_error_not_connected(msg); return NULL; } /* org.bluez.Device1.Disconnect() is in progress. Since it tears down * both LE and BR/EDR bearers, it takes precedence over bearer-level * disconnects. Ignore any bearer-specific disconnect requests here. */ if (device_is_disconnecting(device)) { if (msg) return btd_error_in_progress(msg); return NULL; } if (msg) queue_push_tail(bearer->disconnects, dbus_message_ref(msg)); device_cancel_bonding(device, MGMT_STATUS_DISCONNECTED); device_cancel_browse(device, bearer->type); btd_device_foreach_service(device, bearer_disconnect_service, &bearer->type); device_remove_pending_services(device, bearer->type); if (bearer->disconn_timer) return NULL; bearer->disconn_timer = timeout_add_seconds(DISCONNECT_TIMER, bearer_disconnect_link, bearer, NULL); return NULL; } static const GDBusMethodTable bearer_methods[] = { { GDBUS_EXPERIMENTAL_ASYNC_METHOD("Connect", NULL, NULL, bearer_connect) }, { GDBUS_EXPERIMENTAL_ASYNC_METHOD("Disconnect", NULL, NULL, bearer_disconnect) }, {} }; static gboolean bearer_get_adapter(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_bearer *bearer = data; struct btd_adapter *adapter = device_get_adapter(bearer->device); const char *path = adapter_get_path(adapter); dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); return TRUE; } static gboolean bearer_get_paired(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_bearer *bearer = data; dbus_bool_t paired = device_is_paired(bearer->device, bearer->type); dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &paired); return TRUE; } static gboolean bearer_get_bonded(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_bearer *bearer = data; dbus_bool_t bonded = device_is_bonded(bearer->device, bearer->type); dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &bonded); return TRUE; } static gboolean bearer_get_connected(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_bearer *bearer = data; dbus_bool_t connected = btd_device_bdaddr_type_connected(bearer->device, bearer->type); dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &connected); return TRUE; } static const GDBusSignalTable bearer_signals[] = { { GDBUS_SIGNAL("Disconnected", GDBUS_ARGS({ "name", "s" }, { "message", "s" })) }, { } }; static const GDBusPropertyTable bearer_properties[] = { { "Adapter", "o", bearer_get_adapter, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Paired", "b", bearer_get_paired, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Bonded", "b", bearer_get_bonded, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Connected", "b", bearer_get_connected, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, {} }; static const char *bearer_interface(uint8_t type) { if (type == BDADDR_BREDR) return BTD_BEARER_BREDR_INTERFACE; else return BTD_BEARER_LE_INTERFACE; } struct btd_bearer *btd_bearer_new(struct btd_device *device, uint8_t type) { struct btd_bearer *bearer; switch (btd_opts.mode) { case BT_MODE_LE: if (type == BDADDR_BREDR) return NULL; break; case BT_MODE_BREDR: if (type != BDADDR_BREDR) return NULL; break; case BT_MODE_DUAL: break; } bearer = new0(struct btd_bearer, 1); bearer->device = device; bearer->type = type; bearer->path = device_get_path(device); bearer->disconnects = queue_new(); if (!g_dbus_register_interface(btd_get_dbus_connection(), bearer->path, bearer_interface(type), bearer_methods, bearer_signals, bearer_properties, bearer, bearer_free)) { error("Unable to register BREDR interface"); bearer->path = NULL; } return bearer; } void btd_bearer_destroy(struct btd_bearer *bearer) { if (!bearer) return; if (!bearer->path) { bearer_free(bearer); return; } if (bearer->disconnects) { queue_destroy(bearer->disconnects, bearer_free_dbus_message); bearer->disconnects = NULL; } if (bearer->connect) { dbus_message_unref(bearer->connect); bearer->connect = NULL; } g_dbus_unregister_interface(btd_get_dbus_connection(), bearer->path, bearer_interface(bearer->type)); } void btd_bearer_paired(struct btd_bearer *bearer) { if (!bearer || !bearer->path) return; g_dbus_emit_property_changed(btd_get_dbus_connection(), bearer->path, bearer_interface(bearer->type), "Paired"); } void btd_bearer_bonded(struct btd_bearer *bearer) { if (!bearer || !bearer->path) return; g_dbus_emit_property_changed(btd_get_dbus_connection(), bearer->path, bearer_interface(bearer->type), "Bonded"); } void btd_bearer_connected(struct btd_bearer *bearer, int err) { DBusMessage *reply; if (!bearer || !bearer->path) return; if (bearer->connect) { if (err) reply = bearer->type == BDADDR_BREDR ? btd_error_bredr_errno(bearer->connect, err) : btd_error_le_errno(bearer->connect, err); else reply = dbus_message_new_method_return( bearer->connect); g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(bearer->connect); bearer->connect = NULL; } g_dbus_emit_property_changed(btd_get_dbus_connection(), bearer->path, bearer_interface(bearer->type), "Connected"); } void btd_bearer_disconnected(struct btd_bearer *bearer, uint8_t reason) { const char *name; const char *message; DBusMessage *msg; const struct queue_entry *entry; if (!bearer || !bearer->path) return; if (!btd_device_is_connected(bearer->device)) device_disconnect_watches_callback(bearer->device); while (!queue_isempty(bearer->disconnects)) { entry = queue_get_entries(bearer->disconnects); msg = entry->data; g_dbus_send_reply(btd_get_dbus_connection(), msg, DBUS_TYPE_INVALID); queue_remove(bearer->disconnects, msg); dbus_message_unref(msg); } g_dbus_emit_property_changed(btd_get_dbus_connection(), bearer->path, bearer_interface(bearer->type), "Connected"); switch (reason) { case MGMT_DEV_DISCONN_UNKNOWN: name = "org.bluez.Reason.Unknown"; message = "Unspecified"; break; case MGMT_DEV_DISCONN_TIMEOUT: name = "org.bluez.Reason.Timeout"; message = "Connection timeout"; break; case MGMT_DEV_DISCONN_LOCAL_HOST: name = "org.bluez.Reason.Local"; message = "Connection terminated by local host"; break; case MGMT_DEV_DISCONN_REMOTE: name = "org.bluez.Reason.Remote"; message = "Connection terminated by remote user"; break; case MGMT_DEV_DISCONN_AUTH_FAILURE: name = "org.bluez.Reason.Authentication"; message = "Connection terminated due to authentication failure"; break; case MGMT_DEV_DISCONN_LOCAL_HOST_SUSPEND: name = "org.bluez.Reason.Suspend"; message = "Connection terminated by local host for suspend"; break; default: warn("Unknown disconnection value: %u", reason); name = "org.bluez.Reason.Unknown"; message = "Unspecified"; } g_dbus_emit_signal(btd_get_dbus_connection(), bearer->path, bearer_interface(bearer->type), "Disconnected", DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &message, DBUS_TYPE_INVALID); } bluez-5.87/src/PaxHeaders/main.c0000644000000000000000000000005015221752714013540 xustar0020 atime=1783092685 20 ctime=1783094106 bluez-5.87/src/main.c0000644000000000000000000011476415221752714013236 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2000-2001 Qualcomm Incorporated * Copyright (C) 2002-2003 Maxim Krasnyansky * Copyright (C) 2002-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "gdbus/gdbus.h" #include "btio/btio.h" #include "log.h" #include "backtrace.h" #include "shared/att-types.h" #include "shared/mainloop.h" #include "shared/timeout.h" #include "shared/queue.h" #include "shared/crypto.h" #include "bluetooth/uuid.h" #include "shared/util.h" #include "btd.h" #include "sdpd.h" #include "adapter.h" #include "device.h" #include "dbus-common.h" #include "agent.h" #include "profile.h" #define BLUEZ_NAME "org.bluez" #define DEFAULT_PAIRABLE_TIMEOUT 0 /* disabled */ #define DEFAULT_DISCOVERABLE_TIMEOUT 180 /* 3 minutes */ #define DEFAULT_TEMPORARY_TIMEOUT 30 /* 30 seconds */ #define DEFAULT_NAME_REQUEST_RETRY_DELAY 300 /* 5 minutes */ #define SHUTDOWN_GRACE_SECONDS 10 struct btd_opts btd_opts; static GKeyFile *main_conf; static char main_conf_file_path[PATH_MAX]; static const char *supported_options[] = { "Name", "Class", "DiscoverableTimeout", "AlwaysPairable", "PairableTimeout", "DeviceID", "ReverseServiceDiscovery", "NameResolving", "DebugKeys", "ControllerMode", "MaxControllers", "MultiProfile", "FastConnectable", "SecureConnections", "Privacy", "JustWorksRepairing", "TemporaryTimeout", "RefreshDiscovery", "Experimental", "Testing", "KernelExperimental", "RemoteNameRequestRetryDelay", "FilterDiscoverable", NULL }; static const char *br_options[] = { "PageScanType", "PageScanInterval", "PageScanWindow", "InquiryScanType", "InquiryScanInterval", "InquiryScanWindow", "LinkSupervisionTimeout", "PageTimeout", "IdleTimeout", "MinSniffInterval", "MaxSniffInterval", NULL }; static const char *le_options[] = { "CentralAddressResolution", "MinAdvertisementInterval", "MaxAdvertisementInterval", "MultiAdvertisementRotationInterval", "ScanIntervalAutoConnect", "ScanWindowAutoConnect", "ScanIntervalSuspend", "ScanWindowSuspend", "ScanIntervalDiscovery", "ScanWindowDiscovery", "ScanIntervalAdvMonitor", "ScanWindowAdvMonitor", "ScanIntervalConnect", "ScanWindowConnect", "MinConnectionInterval", "MaxConnectionInterval", "ConnectionLatency", "ConnectionSupervisionTimeout", "Autoconnecttimeout", "AdvMonAllowlistScanDuration", "AdvMonNoFilterScanDuration", "EnableAdvMonInterleaveScan", NULL }; static const char *policy_options[] = { "ReconnectUUIDs", "ReconnectAttempts", "ReconnectIntervals", "AutoEnable", "ResumeDelay", NULL }; static const char *gatt_options[] = { "Cache", "KeySize", "ExchangeMTU", "Channels", "Client", "ExportClaimedServices", "Security", NULL }; static const char *const bcs_options[] = { "Role", "CsSyncAntennaSel", "MaxTxPower", NULL }; static const char *csip_options[] = { "SIRK", "Encryption", "Size", "Rank", NULL }; static const char *avdtp_options[] = { "SessionMode", "StreamMode", NULL }; static const char *avrcp_options[] = { "VolumeWithoutTarget", "VolumeCategory", NULL }; static const char *advmon_options[] = { "RSSISamplingPeriod", NULL }; static const struct group_table { const char *name; const char * const *options; } valid_groups[] = { { "General", supported_options }, { "BR", br_options }, { "LE", le_options }, { "Policy", policy_options }, { "GATT", gatt_options }, { "CSIS", csip_options }, { "AVDTP", avdtp_options }, { "AVRCP", avrcp_options }, { "ChannelSounding", bcs_options }, { "AdvMon", advmon_options }, { } }; static int8_t check_sirk_alpha_numeric(char *str) { int8_t val = 0; char *s = str; if (strlen(s) != 32) /* 32 Bytes of Alpha numeric string */ return 0; for ( ; *s; s++) { if (((*s >= '0') & (*s <= '9')) || ((*s >= 'a') && (*s <= 'z')) || ((*s >= 'A') && (*s <= 'Z'))) { val = 1; } else { val = 0; break; } } return val; } static size_t hex2bin(const char *hexstr, uint8_t *buf, size_t buflen) { size_t i, len; if (!hexstr) return 0; len = MIN((strlen(hexstr) / 2), buflen); memset(buf, 0, len); for (i = 0; i < len; i++) { if (sscanf(hexstr + (i * 2), "%02hhX", &buf[i]) != 1) continue; } return len; } GKeyFile *btd_get_main_conf(void) { return main_conf; } static GKeyFile *load_config(const char *name) { GError *err = NULL; GKeyFile *keyfile; int len; if (name) snprintf(main_conf_file_path, PATH_MAX, "%s", name); else { const char *configdir = getenv("CONFIGURATION_DIRECTORY"); /* Check if running as service */ if (configdir) { /* Check if there multiple paths given */ if (strstr(configdir, ":")) len = strstr(configdir, ":") - configdir; else len = strlen(configdir); } else { configdir = CONFIGDIR; len = strlen(configdir); } snprintf(main_conf_file_path, PATH_MAX, "%*s/main.conf", len, configdir); } keyfile = g_key_file_new(); g_key_file_set_list_separator(keyfile, ','); if (!g_key_file_load_from_file(keyfile, main_conf_file_path, 0, &err)) { if (!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT)) error("Parsing %s failed: %s", main_conf_file_path, err->message); g_error_free(err); g_key_file_free(keyfile); return NULL; } return keyfile; } static void parse_did(const char *did) { int result; uint16_t vendor, product, version , source; vendor = 0x0000; product = 0x0000; version = 0x0000; source = 0x0002; if (!strcasecmp(did, "false")) { source = 0x0000; goto done; } result = sscanf(did, "bluetooth:%4hx:%4hx:%4hx", &vendor, &product, &version); if (result != EOF && result >= 2) { source = 0x0001; goto done; } result = sscanf(did, "usb:%4hx:%4hx:%4hx", &vendor, &product, &version); if (result != EOF && result >= 2) goto done; result = sscanf(did, "%4hx:%4hx:%4hx", &vendor, &product, &version); if (result == EOF || result < 2) return; done: btd_opts.did_source = source; btd_opts.did_vendor = vendor; btd_opts.did_product = product; btd_opts.did_version = version; } static bt_gatt_cache_t parse_gatt_cache_str(const char *cache) { if (!strcmp(cache, "always")) { return BT_GATT_CACHE_ALWAYS; } else if (!strcmp(cache, "yes")) { return BT_GATT_CACHE_YES; } else if (!strcmp(cache, "no")) { return BT_GATT_CACHE_NO; } else { DBG("Invalid value for KeepCache=%s", cache); return BT_GATT_CACHE_ALWAYS; } } static enum jw_repairing_t parse_jw_repairing(const char *jw_repairing) { if (!strcmp(jw_repairing, "never")) { return JW_REPAIRING_NEVER; } else if (!strcmp(jw_repairing, "confirm")) { return JW_REPAIRING_CONFIRM; } else if (!strcmp(jw_repairing, "always")) { return JW_REPAIRING_ALWAYS; } else { return JW_REPAIRING_NEVER; } } static void check_options(GKeyFile *config, const char *group, const char * const *options) { char **keys; int i; keys = g_key_file_get_keys(config, group, NULL, NULL); for (i = 0; keys != NULL && keys[i] != NULL; i++) { bool found; unsigned int j; found = false; for (j = 0; options != NULL && options[j] != NULL; j++) { if (g_str_equal(keys[i], options[j])) { found = true; break; } } if (!found) warn("Unknown key %s for group %s in %s", keys[i], group, main_conf_file_path); } g_strfreev(keys); } static void check_config(GKeyFile *config) { char **keys; int i; const struct group_table *group; if (!config) return; keys = g_key_file_get_groups(config, NULL); for (i = 0; keys != NULL && keys[i] != NULL; i++) { bool match = false; for (group = valid_groups; group && group->name ; group++) { if (g_str_equal(keys[i], group->name)) { match = true; break; } } if (!match) warn("Unknown group %s in %s", keys[i], main_conf_file_path); } g_strfreev(keys); for (group = valid_groups; group && group->name; group++) check_options(config, group->name, group->options); } static int get_mode(const char *str) { if (strcmp(str, "dual") == 0) return BT_MODE_DUAL; else if (strcmp(str, "bredr") == 0) return BT_MODE_BREDR; else if (strcmp(str, "le") == 0) return BT_MODE_LE; error("Unknown controller mode \"%s\"", str); return BT_MODE_DUAL; } static bool parse_config_string(GKeyFile *config, const char *group, const char *key, char **val) { GError *err = NULL; char *tmp; tmp = g_key_file_get_string(config, group, key, &err); if (err) { if (err->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) DBG("%s", err->message); g_error_free(err); return false; } DBG("%s.%s = %s", group, key, tmp); if (val) { g_free(*val); *val = tmp; } return true; } static bool parse_config_int(GKeyFile *config, const char *group, const char *key, int *val, size_t min, size_t max) { size_t tmp; char *str = NULL; char *endptr = NULL; if (!parse_config_string(config, group, key, &str)) return false; tmp = strtol(str, &endptr, 0); if (!endptr || *endptr != '\0') { error("%s.%s = %s is not integer", group, key, str); g_free(str); return false; } if (tmp < min) { g_free(str); warn("%s.%s = %zu is out of range (< %zu)", group, key, tmp, min); return false; } if (tmp > max) { g_free(str); warn("%s.%s = %zu is out of range (> %zu)", group, key, tmp, max); return false; } g_free(str); if (val) *val = tmp; return true; } static bool parse_config_signed_int(GKeyFile *config, const char *group, const char *key, int8_t *val, long min, long max) { char *str = NULL; char *endptr = NULL; long tmp; bool result = false; str = g_key_file_get_string(config, group, key, NULL); if (!str) return false; tmp = strtol(str, &endptr, 0); if (!endptr || *endptr != '\0') { warn("%s.%s = %s is not integer", group, key, str); goto cleanup; } if (tmp < min) { warn("%s.%s = %ld is out of range (< %ld)", group, key, tmp, min); goto cleanup; } if (tmp > max) { warn("%s.%s = %ld is out of range (> %ld)", group, key, tmp, max); goto cleanup; } if (val) *val = (int8_t) tmp; result = true; cleanup: g_free(str); return result; } struct config_param { const char * const val_name; void * const val; const size_t size; const uint32_t min; const uint32_t max; }; static void parse_mode_config(GKeyFile *config, const char *group, const struct config_param *params, size_t params_len) { size_t i; if (!config) return; for (i = 0; i < params_len; ++i) { int val; if (parse_config_int(config, group, params[i].val_name, &val, params[i].min, params[i].max)) { val = htobl(val); memcpy(params[i].val, &val, params[i].size); } ++btd_opts.defaults.num_entries; } } static void parse_br_config(GKeyFile *config) { static const struct config_param params[] = { { "PageScanType", &btd_opts.defaults.br.page_scan_type, sizeof(btd_opts.defaults.br.page_scan_type), 0, 1}, { "PageScanInterval", &btd_opts.defaults.br.page_scan_interval, sizeof(btd_opts.defaults.br.page_scan_interval), 0x0012, 0x1000}, { "PageScanWindow", &btd_opts.defaults.br.page_scan_win, sizeof(btd_opts.defaults.br.page_scan_win), 0x0011, 0x1000}, { "InquiryScanType", &btd_opts.defaults.br.scan_type, sizeof(btd_opts.defaults.br.scan_type), 0, 1}, { "InquiryScanInterval", &btd_opts.defaults.br.scan_interval, sizeof(btd_opts.defaults.br.scan_interval), 0x0012, 0x1000}, { "InquiryScanWindow", &btd_opts.defaults.br.scan_win, sizeof(btd_opts.defaults.br.scan_win), 0x0011, 0x1000}, { "LinkSupervisionTimeout", &btd_opts.defaults.br.link_supervision_timeout, sizeof(btd_opts.defaults.br.link_supervision_timeout), 0x0001, 0xFFFF}, { "PageTimeout", &btd_opts.defaults.br.page_timeout, sizeof(btd_opts.defaults.br.page_timeout), 0x0001, 0xFFFF}, { "MinSniffInterval", &btd_opts.defaults.br.min_sniff_interval, sizeof(btd_opts.defaults.br.min_sniff_interval), 0x0001, 0xFFFE}, { "MaxSniffInterval", &btd_opts.defaults.br.max_sniff_interval, sizeof(btd_opts.defaults.br.max_sniff_interval), 0x0001, 0xFFFE}, { "IdleTimeout", &btd_opts.defaults.br.idle_timeout, sizeof(btd_opts.defaults.br.idle_timeout), 500, 3600000}, }; if (btd_opts.mode == BT_MODE_LE) return; parse_mode_config(config, "BR", params, ARRAY_SIZE(params)); } static void parse_le_config(GKeyFile *config) { static const struct config_param params[] = { { "CentralAddressResolution", &btd_opts.defaults.le.addr_resolution, sizeof(btd_opts.defaults.le.addr_resolution), 0, 1}, { "MinAdvertisementInterval", &btd_opts.defaults.le.min_adv_interval, sizeof(btd_opts.defaults.le.min_adv_interval), 0x0020, 0x4000}, { "MaxAdvertisementInterval", &btd_opts.defaults.le.max_adv_interval, sizeof(btd_opts.defaults.le.max_adv_interval), 0x0020, 0x4000}, { "MultiAdvertisementRotationInterval", &btd_opts.defaults.le.adv_rotation_interval, sizeof(btd_opts.defaults.le.adv_rotation_interval), 0x0001, 0xFFFF}, { "ScanIntervalAutoConnect", &btd_opts.defaults.le.scan_interval_autoconnect, sizeof(btd_opts.defaults.le.scan_interval_autoconnect), 0x0004, 0x4000}, { "ScanWindowAutoConnect", &btd_opts.defaults.le.scan_win_autoconnect, sizeof(btd_opts.defaults.le.scan_win_autoconnect), 0x0004, 0x4000}, { "ScanIntervalSuspend", &btd_opts.defaults.le.scan_interval_suspend, sizeof(btd_opts.defaults.le.scan_interval_suspend), 0x0004, 0x4000}, { "ScanWindowSuspend", &btd_opts.defaults.le.scan_win_suspend, sizeof(btd_opts.defaults.le.scan_win_suspend), 0x0004, 0x4000}, { "ScanIntervalDiscovery", &btd_opts.defaults.le.scan_interval_discovery, sizeof(btd_opts.defaults.le.scan_interval_discovery), 0x0004, 0x4000}, { "ScanWindowDiscovery", &btd_opts.defaults.le.scan_win_discovery, sizeof(btd_opts.defaults.le.scan_win_discovery), 0x0004, 0x4000}, { "ScanIntervalAdvMonitor", &btd_opts.defaults.le.scan_interval_adv_monitor, sizeof(btd_opts.defaults.le.scan_interval_adv_monitor), 0x0004, 0x4000}, { "ScanWindowAdvMonitor", &btd_opts.defaults.le.scan_win_adv_monitor, sizeof(btd_opts.defaults.le.scan_win_adv_monitor), 0x0004, 0x4000}, { "ScanIntervalConnect", &btd_opts.defaults.le.scan_interval_connect, sizeof(btd_opts.defaults.le.scan_interval_connect), 0x0004, 0x4000}, { "ScanWindowConnect", &btd_opts.defaults.le.scan_win_connect, sizeof(btd_opts.defaults.le.scan_win_connect), 0x0004, 0x4000}, { "MinConnectionInterval", &btd_opts.defaults.le.min_conn_interval, sizeof(btd_opts.defaults.le.min_conn_interval), 0x0006, 0x0C80}, { "MaxConnectionInterval", &btd_opts.defaults.le.max_conn_interval, sizeof(btd_opts.defaults.le.max_conn_interval), 0x0006, 0x0C80}, { "ConnectionLatency", &btd_opts.defaults.le.conn_latency, sizeof(btd_opts.defaults.le.conn_latency), 0x0000, 0x01F3}, { "ConnectionSupervisionTimeout", &btd_opts.defaults.le.conn_lsto, sizeof(btd_opts.defaults.le.conn_lsto), 0x000A, 0x0C80}, { "Autoconnecttimeout", &btd_opts.defaults.le.autoconnect_timeout, sizeof(btd_opts.defaults.le.autoconnect_timeout), 0x0001, 0x4000}, { "AdvMonAllowlistScanDuration", &btd_opts.defaults.le.advmon_allowlist_scan_duration, sizeof(btd_opts.defaults.le.advmon_allowlist_scan_duration), 1, 10000}, { "AdvMonNoFilterScanDuration", &btd_opts.defaults.le.advmon_no_filter_scan_duration, sizeof(btd_opts.defaults.le.advmon_no_filter_scan_duration), 1, 10000}, { "EnableAdvMonInterleaveScan", &btd_opts.defaults.le.enable_advmon_interleave_scan, sizeof(btd_opts.defaults.le.enable_advmon_interleave_scan), 0, 1}, }; if (btd_opts.mode == BT_MODE_BREDR) return; parse_mode_config(config, "LE", params, ARRAY_SIZE(params)); } static bool match_experimental(const void *data, const void *match_data) { const char *value = data; const char *uuid = match_data; if (!strcmp(value, "*")) return true; return !strcasecmp(value, uuid); } bool btd_kernel_experimental_enabled(const char *uuid) { if (!btd_opts.kernel) return false; if (queue_find(btd_opts.kernel, match_experimental, uuid)) return true; return false; } static const char *valid_uuids[] = { "d4992530-b9ec-469f-ab01-6c481c47da1c", "671b10b5-42c0-4696-9227-eb28d1b049d6", "15c0a148-c273-11ea-b3de-0242ac130004", "330859bc-7506-492d-9370-9a6f0614037f", "a6695ace-ee7f-4fb9-881a-5fac66c629af", "6fbaf188-05e0-496a-9885-d6ddfdb4e03e", "*" }; static void btd_parse_kernel_experimental(char **list) { int i; if (btd_opts.kernel) { warn("Unable to parse KernelExperimental: list already set"); return; } btd_opts.kernel = queue_new(); for (i = 0; list[i]; i++) { size_t j; const char *uuid = list[i]; if (!strcasecmp("false", uuid) || !strcasecmp("off", uuid)) { queue_destroy(btd_opts.kernel, free); btd_opts.kernel = NULL; } if (!strcasecmp("true", uuid) || !strcasecmp("on", uuid)) uuid = "*"; for (j = 0; j < ARRAY_SIZE(valid_uuids); j++) { if (!strcasecmp(valid_uuids[j], uuid)) break; } /* Ignored if UUID is considered invalid */ if (j == ARRAY_SIZE(valid_uuids)) { warn("Invalid KernelExperimental UUID: %s", uuid); continue; } DBG("%s", uuid); queue_push_tail(btd_opts.kernel, strdup(uuid)); } } static bool gen_sirk(const char *str) { struct bt_crypto *crypto; int ret; crypto = bt_crypto_new(); if (!crypto) { error("Failed to open crypto"); return false; } ret = bt_crypto_sirk(crypto, str, btd_opts.did_vendor, btd_opts.did_product, btd_opts.did_version, btd_opts.did_source, btd_opts.csis.sirk); if (!ret) error("Failed to generate SIRK"); bt_crypto_unref(crypto); return ret; } static bool parse_config_u32(GKeyFile *config, const char *group, const char *key, uint32_t *val, uint32_t min, uint32_t max) { int tmp; if (!parse_config_int(config, group, key, &tmp, min, max)) return false; if (val) *val = tmp; return true; } static bool parse_config_u16(GKeyFile *config, const char *group, const char *key, uint16_t *val, uint16_t min, uint16_t max) { int tmp; if (!parse_config_int(config, group, key, &tmp, min, max)) return false; if (val) *val = tmp; return true; } static bool parse_config_u8(GKeyFile *config, const char *group, const char *key, uint8_t *val, uint8_t min, uint8_t max) { int tmp; if (!parse_config_int(config, group, key, &tmp, min, max)) return false; if (val) *val = tmp; return true; } static bool parse_config_bool(GKeyFile *config, const char *group, const char *key, bool *val) { GError *err = NULL; gboolean tmp; tmp = g_key_file_get_boolean(config, group, key, &err); if (err) { if (err->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) DBG("%s", err->message); g_error_free(err); return false; } DBG("%s.%s = %s", group, key, tmp ? "true" : "false"); if (val) *val = tmp; return true; } static void parse_privacy(GKeyFile *config) { char *str = NULL; if (!parse_config_string(config, "General", "Privacy", &str)) { btd_opts.privacy = 0x00; btd_opts.device_privacy = true; return; } if (!strcmp(str, "network") || !strcmp(str, "on")) { btd_opts.privacy = 0x01; } else if (!strcmp(str, "device")) { btd_opts.privacy = 0x01; btd_opts.device_privacy = true; } else if (!strcmp(str, "limited-network")) { if (btd_opts.mode != BT_MODE_DUAL) { DBG("Invalid privacy option: %s", str); btd_opts.privacy = 0x00; } btd_opts.privacy = 0x01; } else if (!strcmp(str, "limited-device")) { if (btd_opts.mode != BT_MODE_DUAL) { DBG("Invalid privacy option: %s", str); btd_opts.privacy = 0x00; } btd_opts.privacy = 0x02; btd_opts.device_privacy = true; } else if (!strcmp(str, "off")) { btd_opts.privacy = 0x00; btd_opts.device_privacy = true; } else { DBG("Invalid privacy option: %s", str); btd_opts.privacy = 0x00; } g_free(str); } static void parse_repairing(GKeyFile *config) { char *str = NULL; if (!parse_config_string(config, "General", "JustWorksRepairing", &str)) { btd_opts.jw_repairing = JW_REPAIRING_NEVER; return; } btd_opts.jw_repairing = parse_jw_repairing(str); g_free(str); } static bool parse_config_hex(GKeyFile *config, char *group, const char *key, uint32_t *val) { char *str = NULL; if (!parse_config_string(config, group, key, &str)) return false; if (val) *val = strtol(str, NULL, 16); g_free(str); return true; } static void parse_device_id(GKeyFile *config) { char *str = NULL; parse_config_string(config, "General", "DeviceID", &str); if (!str) return; parse_did(str); g_free(str); } static void parse_ctrl_mode(GKeyFile *config) { char *str = NULL; parse_config_string(config, "General", "ControllerMode", &str); if (!str) return; btd_opts.mode = get_mode(str); g_free(str); } static void parse_multi_profile(GKeyFile *config) { char *str = NULL; parse_config_string(config, "General", "MultiProfile", &str); if (!str) return; if (!strcmp(str, "single")) btd_opts.mps = MPS_SINGLE; else if (!strcmp(str, "multiple")) btd_opts.mps = MPS_MULTIPLE; else btd_opts.mps = MPS_OFF; g_free(str); } static gboolean parse_kernel_experimental(const char *key, const char *value, gpointer user_data, GError **error) { char **strlist; if (value && value[0] != '*') { strlist = g_strsplit(value, ",", -1); btd_parse_kernel_experimental(strlist); g_strfreev(strlist); } else { if (!btd_opts.kernel) btd_opts.kernel = queue_new(); queue_push_head(btd_opts.kernel, strdup("*")); } return TRUE; } static void parse_kernel_exp(GKeyFile *config) { char *str = NULL; if (!parse_config_string(config, "General", "KernelExperimental", &str)) return; parse_kernel_experimental(NULL, str, NULL, NULL); g_free(str); } static void parse_secure_conns(GKeyFile *config) { char *str = NULL; if (!parse_config_string(config, "General", "SecureConnections", &str)) return; if (!strcmp(str, "off")) btd_opts.secure_conn = SC_OFF; else if (!strcmp(str, "on")) btd_opts.secure_conn = SC_ON; else if (!strcmp(str, "only")) btd_opts.secure_conn = SC_ONLY; g_free(str); } static void parse_general(GKeyFile *config) { parse_config_string(config, "General", "Name", &btd_opts.name); parse_config_hex(config, "General", "Class", &btd_opts.class); parse_config_u32(config, "General", "DiscoverableTimeout", &btd_opts.discovto, 0, UINT32_MAX); parse_config_bool(config, "General", "AlwaysPairable", &btd_opts.pairable); parse_config_u32(config, "General", "PairableTimeout", &btd_opts.pairto, 0, UINT32_MAX); parse_device_id(config); parse_config_bool(config, "General", "ReverseServiceDiscovery", &btd_opts.reverse_discovery); parse_config_bool(config, "General", "NameResolving", &btd_opts.name_resolv); parse_config_bool(config, "General", "DebugKeys", &btd_opts.debug_keys); parse_ctrl_mode(config); parse_config_u16(config, "General", "MaxControllers", &btd_opts.max_adapters, 0, UINT16_MAX); parse_multi_profile(config); parse_config_bool(config, "General", "FastConnectable", &btd_opts.fast_conn); parse_privacy(config); parse_repairing(config); parse_config_u32(config, "General", "TemporaryTimeout", &btd_opts.tmpto, 0, UINT32_MAX); parse_config_bool(config, "General", "RefreshDiscovery", &btd_opts.refresh_discovery); parse_secure_conns(config); parse_config_bool(config, "General", "Experimental", &btd_opts.experimental); parse_config_bool(config, "General", "Testing", &btd_opts.testing); parse_kernel_exp(config); parse_config_u32(config, "General", "RemoteNameRequestRetryDelay", &btd_opts.name_request_retry_delay, 0, UINT32_MAX); parse_config_bool(config, "General", "FilterDiscoverable", &btd_opts.filter_discoverable); } static void parse_gatt_cache(GKeyFile *config) { char *str = NULL; parse_config_string(config, "GATT", "Cache", &str); if (!str) return; btd_opts.gatt_cache = parse_gatt_cache_str(str); g_free(str); } static enum bt_gatt_export_t parse_gatt_export_str(const char *str) { if (!strcmp(str, "no") || !strcmp(str, "false") || !strcmp(str, "off")) { return BT_GATT_EXPORT_OFF; } else if (!strcmp(str, "read-only")) { return BT_GATT_EXPORT_READ_ONLY; } else if (!strcmp(str, "read-write")) { return BT_GATT_EXPORT_READ_WRITE; } DBG("Invalid value for ExportClaimedServices=%s", str); return BT_GATT_EXPORT_READ_ONLY; } static void parse_gatt_export(GKeyFile *config) { char *str = NULL; parse_config_string(config, "GATT", "ExportClaimedServices", &str); if (!str) return; btd_opts.gatt_export = parse_gatt_export_str(str); g_free(str); } static uint8_t parse_gatt_seclevel_str(const char *str) { if (!strcmp(str, "auto")) return BT_ATT_SECURITY_AUTO; else if (!strcmp(str, "low") || !strcmp(str, "1")) return BT_ATT_SECURITY_LOW; else if (!strcmp(str, "medium") || !strcmp(str, "2")) return BT_ATT_SECURITY_MEDIUM; else if (!strcmp(str, "high") || !strcmp(str, "3")) return BT_ATT_SECURITY_HIGH; else if (!strcmp(str, "fips") || !strcmp(str, "4")) return BT_ATT_SECURITY_FIPS; DBG("Invalid value for Security=%s", str); return BT_ATT_SECURITY_AUTO; } static void parse_gatt_seclevel(GKeyFile *config) { char *str = NULL; if (!btd_opts.testing) return; parse_config_string(config, "GATT", "Security", &str); if (!str) return; btd_opts.gatt_seclevel = parse_gatt_seclevel_str(str); g_free(str); } static void parse_gatt(GKeyFile *config) { parse_gatt_cache(config); parse_config_u8(config, "GATT", "KeySize", &btd_opts.key_size, 7, 16); parse_config_u16(config, "GATT", "ExchangeMTU", &btd_opts.gatt_mtu, BT_ATT_DEFAULT_LE_MTU, BT_ATT_MAX_LE_MTU); parse_config_u8(config, "GATT", "Channels", &btd_opts.gatt_channels, 1, 6); parse_config_bool(config, "GATT", "Client", &btd_opts.gatt_client); parse_gatt_export(config); parse_gatt_seclevel(config); } static void parse_csis_sirk(GKeyFile *config) { char *str = NULL; if (!parse_config_string(config, "CSIS", "SIRK", &str)) return; if (strlen(str) == 32 && check_sirk_alpha_numeric(str)) hex2bin(str, btd_opts.csis.sirk, sizeof(btd_opts.csis.sirk)); else if (!gen_sirk(str)) DBG("Unable to generate SIRK from string"); g_free(str); } static void parse_csis(GKeyFile *config) { parse_csis_sirk(config); parse_config_bool(config, "CSIS", "Encryption", &btd_opts.csis.encrypt); parse_config_u8(config, "CSIS", "Size", &btd_opts.csis.size, 0, UINT8_MAX); parse_config_u8(config, "CSIS", "Rank", &btd_opts.csis.rank, 0, UINT8_MAX); } static bool parse_cs_role(GKeyFile *config, const char *group, const char *key, uint8_t *val) { GError *err = NULL; char *str = NULL; char *endptr = NULL; int numeric_val; /* Try to read as string first */ str = g_key_file_get_string(config, group, key, &err); if (err) { if (err->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) DBG("%s", err->message); g_error_free(err); return false; } DBG("%s.%s = %s", group, key, str); /* Check if it's a string value */ if (!strcmp(str, "Initiator") || !strcmp(str, "initiator")) { if (val) *val = 1; g_free(str); return true; } else if (!strcmp(str, "Reflector") || !strcmp(str, "reflector")) { if (val) *val = 2; g_free(str); return true; } else if (!strcmp(str, "Both") || !strcmp(str, "both")) { if (val) *val = 3; g_free(str); return true; } /* Try to parse as numeric value */ numeric_val = strtol(str, &endptr, 0); if (!endptr || *endptr != '\0') { warn("%s.%s = %s is not a valid value. " "Expected: 1/Initiator, 2/Reflector, or 3/Both", group, key, str); g_free(str); return false; } if (numeric_val < 1 || numeric_val > 3) { warn("%s.%s = %d is out of range. " "Valid values: 1 (Initiator), 2 (Reflector), 3 (Both)", group, key, numeric_val); g_free(str); return false; } if (val) *val = numeric_val; g_free(str); return true; } static void parse_le_cs_config(GKeyFile *config) { parse_cs_role(config, "ChannelSounding", "Role", &btd_opts.defaults.bcs.role); parse_config_u8(config, "ChannelSounding", "CsSyncAntennaSel", &btd_opts.defaults.bcs.cs_sync_ant_sel, 0x01, 0xFF); parse_config_signed_int(config, "ChannelSounding", "MaxTxPower", &btd_opts.defaults.bcs.max_tx_power, INT8_MIN, INT8_MAX); } static void parse_avdtp_session_mode(GKeyFile *config) { char *str = NULL; if (!parse_config_string(config, "AVDTP", "SessionMode", &str)) return; if (!strcmp(str, "basic")) btd_opts.avdtp.session_mode = BT_IO_MODE_BASIC; else if (!strcmp(str, "ertm")) btd_opts.avdtp.session_mode = BT_IO_MODE_ERTM; else { DBG("Invalid mode option: %s", str); btd_opts.avdtp.session_mode = BT_IO_MODE_BASIC; } g_free(str); } static void parse_avdtp_stream_mode(GKeyFile *config) { char *str = NULL; if (!parse_config_string(config, "AVDTP", "StreamMode", &str)) return; if (!strcmp(str, "basic")) btd_opts.avdtp.stream_mode = BT_IO_MODE_BASIC; else if (!strcmp(str, "streaming")) btd_opts.avdtp.stream_mode = BT_IO_MODE_STREAMING; else { DBG("Invalid mode option: %s", str); btd_opts.avdtp.stream_mode = BT_IO_MODE_BASIC; } g_free(str); } static void parse_avdtp(GKeyFile *config) { parse_avdtp_session_mode(config); parse_avdtp_stream_mode(config); } static void parse_avrcp(GKeyFile *config) { parse_config_bool(config, "AVRCP", "VolumeWithoutTarget", &btd_opts.avrcp.volume_without_target); parse_config_bool(config, "AVRCP", "VolumeCategory", &btd_opts.avrcp.volume_category); } static void parse_advmon(GKeyFile *config) { parse_config_u8(config, "AdvMon", "RSSISamplingPeriod", &btd_opts.advmon.rssi_sampling_period, 0, UINT8_MAX); } static void parse_config(GKeyFile *config) { if (!config) return; check_config(config); DBG("parsing %s", main_conf_file_path); /* Parse Groups */ parse_general(config); parse_br_config(config); parse_le_config(config); parse_gatt(config); parse_csis(config); parse_avdtp(config); parse_avrcp(config); parse_le_cs_config(config); parse_advmon(config); } static void init_defaults(void) { uint8_t major, minor; /* Default HCId settings */ memset(&btd_opts, 0, sizeof(btd_opts)); btd_opts.name = g_strdup_printf("BlueZ %s", VERSION); btd_opts.class = 0x000000; btd_opts.pairto = DEFAULT_PAIRABLE_TIMEOUT; btd_opts.discovto = DEFAULT_DISCOVERABLE_TIMEOUT; btd_opts.tmpto = DEFAULT_TEMPORARY_TIMEOUT; btd_opts.reverse_discovery = TRUE; btd_opts.name_resolv = TRUE; btd_opts.debug_keys = FALSE; btd_opts.refresh_discovery = TRUE; btd_opts.name_request_retry_delay = DEFAULT_NAME_REQUEST_RETRY_DELAY; btd_opts.secure_conn = SC_ON; btd_opts.filter_discoverable = true; btd_opts.defaults.num_entries = 0; btd_opts.defaults.br.page_scan_type = 0xFFFF; btd_opts.defaults.br.scan_type = 0xFFFF; btd_opts.defaults.le.addr_resolution = 0x01; btd_opts.defaults.le.enable_advmon_interleave_scan = 0xFF; if (sscanf(VERSION, "%hhu.%hhu", &major, &minor) != 2) return; btd_opts.did_source = 0x0002; /* USB */ btd_opts.did_vendor = 0x1d6b; /* Linux Foundation */ btd_opts.did_product = 0x0246; /* BlueZ */ btd_opts.did_version = (major << 8 | minor); btd_opts.gatt_cache = BT_GATT_CACHE_ALWAYS; btd_opts.gatt_mtu = BT_ATT_MAX_LE_MTU; btd_opts.gatt_channels = 1; btd_opts.gatt_client = true; btd_opts.gatt_export = BT_GATT_EXPORT_READ_ONLY; btd_opts.gatt_seclevel = BT_ATT_SECURITY_AUTO; btd_opts.avdtp.session_mode = BT_IO_MODE_BASIC; btd_opts.avdtp.stream_mode = BT_IO_MODE_BASIC; btd_opts.avrcp.volume_without_target = false; btd_opts.avrcp.volume_category = true; btd_opts.advmon.rssi_sampling_period = 0xFF; btd_opts.csis.encrypt = true; btd_opts.defaults.bcs.role = 0x03; btd_opts.defaults.bcs.cs_sync_ant_sel = 0xFF; btd_opts.defaults.bcs.max_tx_power = 0x14; } static void log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { int priority; if (log_level & (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING)) priority = 0x03; else priority = 0x06; btd_log(0xffff, priority, "GLib: %s", message); btd_backtrace(0xffff); } void btd_exit(void) { mainloop_quit(); } static bool quit_eventloop(gpointer user_data) { btd_exit(); return FALSE; } static void signal_callback(int signum, void *user_data) { static bool terminated = false; switch (signum) { case SIGINT: case SIGTERM: if (!terminated) { info("Terminating"); timeout_add_seconds(SHUTDOWN_GRACE_SECONDS, quit_eventloop, NULL, NULL); mainloop_sd_notify("STATUS=Powering down"); adapter_shutdown(); } terminated = true; break; case SIGUSR2: __btd_toggle_debug(); break; } } static char *option_debug = NULL; static char *option_plugin = NULL; static char *option_noplugin = NULL; static char *option_configfile = NULL; static gboolean option_compat = FALSE; static gboolean option_detach = TRUE; static gboolean option_experimental = FALSE; static gboolean option_testing = FALSE; static gboolean option_version = FALSE; static void free_options(void) { g_free(option_debug); option_debug = NULL; g_free(option_plugin); option_plugin = NULL; g_free(option_noplugin); option_noplugin = NULL; g_free(option_configfile); option_configfile = NULL; } static void disconnect_dbus(void) { DBusConnection *conn = btd_get_dbus_connection(); if (!conn || !dbus_connection_get_is_connected(conn)) return; g_dbus_detach_object_manager(conn); set_dbus_connection(NULL); dbus_connection_unref(conn); } static void disconnected_dbus(DBusConnection *conn, void *data) { info("Disconnected from D-Bus. Exiting."); mainloop_quit(); } static void dbus_debug(const char *str, void *data) { DBG_IDX(0xffff, "%s", str); } static int connect_dbus(void) { DBusConnection *conn; DBusError err; dbus_error_init(&err); conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, BLUEZ_NAME, &err); if (!conn) { if (dbus_error_is_set(&err)) { g_printerr("D-Bus setup failed: %s\n", err.message); dbus_error_free(&err); return -EIO; } return -EALREADY; } set_dbus_connection(conn); g_dbus_set_disconnect_function(conn, disconnected_dbus, NULL, NULL); g_dbus_attach_object_manager(conn); g_dbus_set_debug(dbus_debug, NULL, NULL); return 0; } static gboolean parse_debug(const char *key, const char *value, gpointer user_data, GError **error) { if (value) option_debug = g_strdup(value); else option_debug = g_strdup("*"); return TRUE; } static GOptionEntry options[] = { { "debug", 'd', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, parse_debug, "Specify debug options to enable", "DEBUG" }, { "plugin", 'p', 0, G_OPTION_ARG_STRING, &option_plugin, "Specify plugins to load", "NAME,..," }, { "noplugin", 'P', 0, G_OPTION_ARG_STRING, &option_noplugin, "Specify plugins not to load", "NAME,..." }, { "configfile", 'f', 0, G_OPTION_ARG_STRING, &option_configfile, "Specify an explicit path to the config file", "FILE"}, { "compat", 'C', 0, G_OPTION_ARG_NONE, &option_compat, "Provide deprecated command line interfaces" }, { "experimental", 'E', 0, G_OPTION_ARG_NONE, &option_experimental, "Enable experimental D-Bus interfaces" }, { "testing", 'T', 0, G_OPTION_ARG_NONE, &option_testing, "Enable testing D-Bus interfaces" }, { "kernel", 'K', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, parse_kernel_experimental, "Enable kernel experimental features" }, { "nodetach", 'n', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &option_detach, "Run with logging in foreground" }, { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, "Show version information and exit" }, { NULL }, }; int main(int argc, char *argv[]) { GOptionContext *context; GError *err = NULL; uint16_t sdp_mtu = 0; uint32_t sdp_flags = 0; int gdbus_flags = 0; init_defaults(); context = g_option_context_new(NULL); g_option_context_add_main_entries(context, options, NULL); if (g_option_context_parse(context, &argc, &argv, &err) == FALSE) { if (err != NULL) { g_printerr("%s\n", err->message); g_error_free(err); } else g_printerr("An unknown error occurred\n"); exit(1); } g_option_context_free(context); if (option_version == TRUE) { printf("%s\n", VERSION); exit(0); } btd_opts.experimental = option_experimental; btd_opts.testing = option_testing; umask(0077); btd_backtrace_init(); mainloop_init(); __btd_log_init(option_debug, option_detach); g_log_set_handler("GLib", G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, log_handler, NULL); mainloop_sd_notify("STATUS=Starting up"); main_conf = load_config(option_configfile); parse_config(main_conf); if (connect_dbus() < 0) { error("Unable to get on D-Bus"); exit(1); } if (btd_opts.experimental) gdbus_flags = G_DBUS_FLAG_ENABLE_EXPERIMENTAL; if (btd_opts.testing) gdbus_flags |= G_DBUS_FLAG_ENABLE_TESTING; g_dbus_set_flags(gdbus_flags); if (adapter_init() < 0) { error("Adapter handling initialization failed"); exit(1); } btd_device_init(); btd_agent_init(); btd_profile_init(); if (btd_opts.mode != BT_MODE_LE) { if (option_compat == TRUE) sdp_flags |= SDP_SERVER_COMPAT; start_sdp_server(sdp_mtu, sdp_flags); if (btd_opts.did_source > 0) register_device_id(btd_opts.did_source, btd_opts.did_vendor, btd_opts.did_product, btd_opts.did_version); } if (btd_opts.mps != MPS_OFF) register_mps(btd_opts.mps == MPS_MULTIPLE); /* Loading plugins has to be done after D-Bus has been setup since * the plugins might wanna expose some paths on the bus. However the * best order of how to init various subsystems of the Bluetooth * daemon needs to be re-worked. */ plugin_init(option_plugin, option_noplugin); /* no need to keep parsed option in memory */ free_options(); rfkill_init(); DBG("Entering main loop"); mainloop_sd_notify("STATUS=Running"); mainloop_sd_notify("READY=1"); mainloop_run_with_signal(signal_callback, NULL); mainloop_sd_notify("STATUS=Quitting"); plugin_cleanup(); btd_profile_cleanup(); btd_agent_cleanup(); btd_device_cleanup(); adapter_cleanup(); rfkill_exit(); if (btd_opts.mode != BT_MODE_LE) stop_sdp_server(); if (btd_opts.kernel) queue_destroy(btd_opts.kernel, free); if (main_conf) g_key_file_free(main_conf); disconnect_dbus(); info("Exit"); __btd_log_cleanup(); return 0; } bluez-5.87/src/PaxHeaders/sdpd-service.c0000644000000000000000000000005015063045254015202 xustar0020 atime=1783093740 20 ctime=1783094106 bluez-5.87/src/sdpd-service.c0000644000000000000000000007151315063045254014672 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2001-2002 Nokia Corporation * Copyright (C) 2002-2003 Maxim Krasnyansky * Copyright (C) 2002-2010 Marcel Holtmann * Copyright (C) 2002-2003 Stephen Crane * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "src/shared/util.h" #include "sdpd.h" #include "log.h" #define MPSD_HFP_AG_A2DP_SRC_ANSWER_CALL_DURING_AUDIO (1ULL << 0) #define MPSD_HFP_HF_A2DP_SNK_ANSWER_CALL_DURING_AUDIO (1ULL << 1) #define MPSD_HFP_AG_A2DP_SRC_OUTGOING_CALL_DURING_AUDIO (1ULL << 2) #define MPSD_HFP_HF_A2DP_SNK_OUTGOING_CALL_DURING_AUDIO (1ULL << 3) #define MPSD_HFP_AG_A2DP_SRC_REJECT_CALL_DURING_AUDIO (1ULL << 4) #define MPSD_HFP_HF_A2DP_SNK_SRC_REJECT_CALL_DURING_AUDIO (1ULL << 5) #define MPSD_HFP_AG_A2DP_SRC_TERMINATE_CALL_DURING_AVP (1ULL << 6) #define MPSD_HFP_HF_A2DP_SNK_TERMINATE_CALL_DURING_AVP (1ULL << 7) #define MPSD_HFP_AG_A2DP_SRC_PRESS_PLAY_DURING_ACTIVE_CALL (1ULL << 8) #define MPSD_HFP_HF_A2DP_SNK_PRESS_PLAY_DURING_ACTIVE_CALL (1ULL << 9) #define MPSD_HFP_AG_A2DP_SRC_START_AUDIO_STREAM_AFTER_PLAY (1ULL << 10) #define MPSD_HFP_HF_A2DP_SNK_START_AUDIO_STREAM_AFTER_PLAY (1ULL << 11) #define MPSD_HFP_AG_A2DP_SRC_SUSPEND_AUDIO_STREAM_ON_PAUSE (1ULL << 12) #define MPSD_HFP_HF_A2DP_SNK_SUSPEND_AUDIO_STREAM_ON_PAUSE (1ULL << 13) #define MPSD_HFP_AG_DUN_GW_DATA_COMM_DURING_VOICE_CALL (1ULL << 14) #define MPSD_HFP_HF_DUN_DT_DATA_COMM_DURING_VOICE_CALL (1ULL << 15) #define MPSD_HFP_AG_DUN_GW_OUTGOING_CALL_DURING_DATA_COMM (1ULL << 16) #define MPSD_HFP_HF_DUN_DT_OUTGOING_CALL_DURING_DATA_COMM (1ULL << 17) #define MPSD_HFP_AG_DUN_GW_INCOMING_CALL_DURING_DATA_COMM (1ULL << 18) #define MPSD_HFP_HF_DUN_DT_INCOMING_CALL_DURING_DATA_COMM (1ULL << 19) #define MPSD_A2DP_SRC_DUN_GW_START_AUDIO_DURING_DATA_COMM (1ULL << 20) #define MPSD_A2DP_SNK_DUN_DT_START_AUDIO_DURING_DATA_COMM (1ULL << 21) #define MPSD_A2DP_SRC_DUN_GW_DATA_COMM_DURING_AUDIO_STREAM (1ULL << 22) #define MPSD_A2DP_SNK_DUN_DT_DATA_COMM_DURING_AUDIO_STREAM (1ULL << 23) #define MPSD_HFP_AG_DUN_GW_TERMINATE_CALL_DURING_DATA_COMM (1ULL << 24) #define MPSD_HFP_HF_DUN_DT_TERMINATE_CALL_DURING_DATA_COMM (1ULL << 25) #define MPSD_HFP_AG_PAN_NAP_DATA_COMM_DURING_VOICE_CALL (1ULL << 26) #define MPSD_HFP_HF_PAN_PANU_DATA_COMM_DURING_VOICE_CALL (1ULL << 27) #define MPSD_HFP_AG_PAN_NAP_OUTGOING_CALL_DURING_DATA_COMM (1ULL << 28) #define MPSD_HFP_HF_PAN_PANU_OUTGOING_CALL_DURING_DATA_COMM (1ULL << 29) #define MPSD_HFP_AG_PAN_NAP_INCOMING_CALL_DURING_DATA_COMM (1ULL << 30) #define MPSD_HFP_HF_PAN_PANU_INCOMING_CALL_DURING_DATA_COMM (1ULL << 31) #define MPSD_A2DP_SRC_PAN_NAP_START_AUDIO_DURING_DATA_COMM (1ULL << 32) #define MPSD_A2DP_SNK_PAN_PANU_START_AUDIO_DURING_DATA_COMM (1ULL << 33) #define MPSD_A2DP_SRC_PAN_NAP_DATA_COMM_DURING_AUDIO_STREAM (1ULL << 34) #define MPSD_A2DP_SNK_PAN_PANU_DATA_COMM_DURING_AUDIO_STREAM (1ULL << 35) #define MPSD_A2DP_SRC_PBAP_SRV_PB_DL_DURING_AUDIO_STREAM (1ULL << 36) #define MPSD_A2DP_SNK_PBAP_CLI_PB_DL_DURING_AUDIO_STREAM (1ULL << 37) #define MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_ANSWER_CALL_DURING_AUDIO (1ULL << 0) #define MPMD_A2DP_SRC_AVRCP_TG_ANSWER_CALL_DURING_AUDIO (1ULL << 1) #define MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_OUTGOING_CALL_DURING_AUDIO (1ULL << 2) #define MPMD_A2DP_SRC_AVRCP_TG_OUTGOING_CALL_DURING_AUDIO (1ULL << 3) #define MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_REJECT_CALL_DURING_AUDIO (1ULL << 4) #define MPMD_A2DP_SRC_AVRCP_TG_REJECT_CALL_DURING_AUDIO (1ULL << 5) #define MPMD_HFP_AG_CALL_TERMINATION_DURING_AVP (1ULL << 6) #define MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_TERMINATION_DURING_AVP (1ULL << 7) #define MPMD_A2DP_SRC_AVRCP_TG_TERMINATION_DURING_AVP (1ULL << 8) #define MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_PLAY_DURING_CALL (1ULL << 9) #define MPMD_A2DP_SRC_AVRCP_TG_PRESS_PLAY_DURING_CALL (1ULL << 10) #define MPMD_AVRCP_CT_NO_A2DP_SNK_START_AUDIO_AFTER_PLAY (1ULL << 11) #define MPMD_A2DP_SRC_AVRCP_TG_START_AUDIO_AFTER_PLAY (1ULL << 12) #define MPMD_AVRCP_CT_NO_A2DP_SNK_SUSPEND_AUDIO_AFTER_PAUSE (1ULL << 13) #define MPMD_A2DP_SRC_AVRCP_TG_SUSPEND_AUDIO_AFTER_PAUSE (1ULL << 14) #define MPMD_A2DP_SRC_AVRCP_TG_START_AUDIO_DURING_DATA_COMM (1ULL << 15) #define MPMD_A2DP_SNK_AVRCP_CT_DUN_DT_AUDIO_DURING_DATA_COMM (1ULL << 16) #define MPMD_A2DP_SRC_AVRCP_TG_START_DATA_DURING_AUDIO (1ULL << 17) #define MPMD_A2DP_SNK_AVRCP_CT_DUN_DT_START_DATA_DURING_AUDIO (1ULL << 18) /* Note: in spec dependency bit position starts from 1 (bit 0 unused?) */ #define MPS_DEPS_SNIFF_MODE_DURRING_STREAMING (1ULL << 1) #define MPS_DEPS_GAVDP_REQUIREMENTS (1ULL << 2) #define MPS_DEPS_DIS_CONNECTION_ORDER_BEHAVIOR (1ULL << 3) /* * default MPS features are all disabled, will be updated if relevant service * is (un)registered */ #define MPS_MPSD_DEFAULT_FEATURES 0 #define MPS_MPMD_DEFAULT_FEATURES 0 /* * Those defines bits for all features that depend on specific profile and role. * If profile is not supported then all those bits should not be set in record */ #define MPS_MPSD_HFP_AG (MPSD_HFP_AG_A2DP_SRC_ANSWER_CALL_DURING_AUDIO | \ MPSD_HFP_AG_A2DP_SRC_OUTGOING_CALL_DURING_AUDIO | \ MPSD_HFP_AG_A2DP_SRC_REJECT_CALL_DURING_AUDIO | \ MPSD_HFP_AG_A2DP_SRC_TERMINATE_CALL_DURING_AVP | \ MPSD_HFP_AG_A2DP_SRC_PRESS_PLAY_DURING_ACTIVE_CALL | \ MPSD_HFP_AG_A2DP_SRC_START_AUDIO_STREAM_AFTER_PLAY | \ MPSD_HFP_AG_DUN_GW_DATA_COMM_DURING_VOICE_CALL | \ MPSD_HFP_AG_A2DP_SRC_SUSPEND_AUDIO_STREAM_ON_PAUSE | \ MPSD_HFP_AG_DUN_GW_OUTGOING_CALL_DURING_DATA_COMM | \ MPSD_HFP_AG_DUN_GW_INCOMING_CALL_DURING_DATA_COMM | \ MPSD_HFP_AG_DUN_GW_TERMINATE_CALL_DURING_DATA_COMM | \ MPSD_HFP_AG_PAN_NAP_DATA_COMM_DURING_VOICE_CALL | \ MPSD_HFP_AG_PAN_NAP_OUTGOING_CALL_DURING_DATA_COMM | \ MPSD_HFP_AG_PAN_NAP_INCOMING_CALL_DURING_DATA_COMM) #define MPS_MPSD_HFP_HF (MPSD_HFP_HF_A2DP_SNK_ANSWER_CALL_DURING_AUDIO | \ MPSD_HFP_HF_A2DP_SNK_OUTGOING_CALL_DURING_AUDIO | \ MPSD_HFP_HF_A2DP_SNK_SRC_REJECT_CALL_DURING_AUDIO | \ MPSD_HFP_HF_A2DP_SNK_TERMINATE_CALL_DURING_AVP | \ MPSD_HFP_HF_A2DP_SNK_PRESS_PLAY_DURING_ACTIVE_CALL | \ MPSD_HFP_HF_A2DP_SNK_START_AUDIO_STREAM_AFTER_PLAY | \ MPSD_HFP_HF_A2DP_SNK_SUSPEND_AUDIO_STREAM_ON_PAUSE | \ MPSD_HFP_HF_DUN_DT_DATA_COMM_DURING_VOICE_CALL | \ MPSD_HFP_HF_DUN_DT_OUTGOING_CALL_DURING_DATA_COMM | \ MPSD_HFP_HF_DUN_DT_INCOMING_CALL_DURING_DATA_COMM | \ MPSD_HFP_HF_DUN_DT_TERMINATE_CALL_DURING_DATA_COMM | \ MPSD_HFP_HF_PAN_PANU_DATA_COMM_DURING_VOICE_CALL | \ MPSD_HFP_HF_PAN_PANU_OUTGOING_CALL_DURING_DATA_COMM | \ MPSD_HFP_HF_PAN_PANU_INCOMING_CALL_DURING_DATA_COMM) #define MPS_MPSD_A2DP_SRC (MPSD_HFP_AG_A2DP_SRC_ANSWER_CALL_DURING_AUDIO | \ MPSD_HFP_AG_A2DP_SRC_OUTGOING_CALL_DURING_AUDIO | \ MPSD_HFP_AG_A2DP_SRC_REJECT_CALL_DURING_AUDIO | \ MPSD_HFP_AG_A2DP_SRC_TERMINATE_CALL_DURING_AVP | \ MPSD_HFP_AG_A2DP_SRC_PRESS_PLAY_DURING_ACTIVE_CALL | \ MPSD_HFP_AG_A2DP_SRC_START_AUDIO_STREAM_AFTER_PLAY | \ MPSD_HFP_AG_A2DP_SRC_SUSPEND_AUDIO_STREAM_ON_PAUSE | \ MPSD_A2DP_SRC_DUN_GW_START_AUDIO_DURING_DATA_COMM | \ MPSD_A2DP_SRC_DUN_GW_DATA_COMM_DURING_AUDIO_STREAM | \ MPSD_A2DP_SRC_PAN_NAP_START_AUDIO_DURING_DATA_COMM | \ MPSD_A2DP_SRC_PAN_NAP_DATA_COMM_DURING_AUDIO_STREAM | \ MPSD_A2DP_SRC_PBAP_SRV_PB_DL_DURING_AUDIO_STREAM) #define MPS_MPSD_A2DP_SNK (MPSD_HFP_HF_A2DP_SNK_ANSWER_CALL_DURING_AUDIO | \ MPSD_HFP_HF_A2DP_SNK_OUTGOING_CALL_DURING_AUDIO | \ MPSD_HFP_HF_A2DP_SNK_SRC_REJECT_CALL_DURING_AUDIO | \ MPSD_HFP_HF_A2DP_SNK_TERMINATE_CALL_DURING_AVP | \ MPSD_HFP_HF_A2DP_SNK_PRESS_PLAY_DURING_ACTIVE_CALL | \ MPSD_HFP_HF_A2DP_SNK_START_AUDIO_STREAM_AFTER_PLAY | \ MPSD_HFP_HF_A2DP_SNK_SUSPEND_AUDIO_STREAM_ON_PAUSE | \ MPSD_A2DP_SNK_DUN_DT_START_AUDIO_DURING_DATA_COMM | \ MPSD_A2DP_SNK_DUN_DT_DATA_COMM_DURING_AUDIO_STREAM | \ MPSD_A2DP_SNK_PAN_PANU_START_AUDIO_DURING_DATA_COMM | \ MPSD_A2DP_SNK_PAN_PANU_DATA_COMM_DURING_AUDIO_STREAM | \ MPSD_A2DP_SNK_PBAP_CLI_PB_DL_DURING_AUDIO_STREAM) #define MPS_MPSD_AVRCP_CT MPS_MPSD_A2DP_SNK #define MPS_MPSD_AVRCP_TG MPS_MPSD_A2DP_SRC #define MPS_MPSD_DUN_GW (MPSD_HFP_AG_DUN_GW_DATA_COMM_DURING_VOICE_CALL | \ MPSD_HFP_AG_DUN_GW_OUTGOING_CALL_DURING_DATA_COMM | \ MPSD_HFP_AG_DUN_GW_INCOMING_CALL_DURING_DATA_COMM | \ MPSD_A2DP_SRC_DUN_GW_START_AUDIO_DURING_DATA_COMM | \ MPSD_A2DP_SRC_DUN_GW_DATA_COMM_DURING_AUDIO_STREAM | \ MPSD_HFP_AG_DUN_GW_TERMINATE_CALL_DURING_DATA_COMM) #define MPS_MPSD_DUN_DT (MPSD_HFP_HF_DUN_DT_DATA_COMM_DURING_VOICE_CALL | \ MPSD_HFP_HF_DUN_DT_OUTGOING_CALL_DURING_DATA_COMM | \ MPSD_HFP_HF_DUN_DT_INCOMING_CALL_DURING_DATA_COMM | \ MPSD_A2DP_SNK_DUN_DT_START_AUDIO_DURING_DATA_COMM | \ MPSD_A2DP_SNK_DUN_DT_DATA_COMM_DURING_AUDIO_STREAM | \ MPSD_HFP_HF_DUN_DT_TERMINATE_CALL_DURING_DATA_COMM) #define MPS_MPSD_PAN_NAP (MPSD_HFP_AG_PAN_NAP_DATA_COMM_DURING_VOICE_CALL | \ MPSD_HFP_AG_PAN_NAP_OUTGOING_CALL_DURING_DATA_COMM | \ MPSD_HFP_AG_PAN_NAP_INCOMING_CALL_DURING_DATA_COMM | \ MPSD_A2DP_SRC_PAN_NAP_START_AUDIO_DURING_DATA_COMM | \ MPSD_A2DP_SRC_PAN_NAP_DATA_COMM_DURING_AUDIO_STREAM) #define MPS_MPSD_PAN_PANU (MPSD_HFP_HF_PAN_PANU_DATA_COMM_DURING_VOICE_CALL | \ MPSD_HFP_HF_PAN_PANU_OUTGOING_CALL_DURING_DATA_COMM | \ MPSD_HFP_HF_PAN_PANU_INCOMING_CALL_DURING_DATA_COMM | \ MPSD_A2DP_SNK_PAN_PANU_START_AUDIO_DURING_DATA_COMM | \ MPSD_A2DP_SNK_PAN_PANU_DATA_COMM_DURING_AUDIO_STREAM) #define MPS_MPSD_PBAP_SRC MPSD_A2DP_SRC_PBAP_SRV_PB_DL_DURING_AUDIO_STREAM #define MPS_MPSD_PBAP_CLI MPSD_A2DP_SNK_PBAP_CLI_PB_DL_DURING_AUDIO_STREAM #define MPS_MPSD_ALL (MPS_MPSD_HFP_AG | MPS_MPSD_HFP_HF | \ MPS_MPSD_A2DP_SRC | MPS_MPSD_A2DP_SNK | \ MPS_MPSD_AVRCP_CT | MPS_MPSD_AVRCP_TG | \ MPS_MPSD_DUN_GW | MPS_MPSD_DUN_DT | \ MPS_MPSD_PAN_NAP | MPS_MPSD_PAN_PANU | \ MPS_MPSD_PBAP_SRC | MPS_MPSD_PBAP_CLI) #define MPS_MPMD_HFP_AG MPMD_HFP_AG_CALL_TERMINATION_DURING_AVP #define MPS_MPMD_HFP_HF ( \ MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_ANSWER_CALL_DURING_AUDIO | \ MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_OUTGOING_CALL_DURING_AUDIO | \ MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_REJECT_CALL_DURING_AUDIO | \ MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_TERMINATION_DURING_AVP | \ MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_PLAY_DURING_CALL) #define MPS_MPMD_A2DP_SRC (MPMD_A2DP_SRC_AVRCP_TG_ANSWER_CALL_DURING_AUDIO | \ MPMD_A2DP_SRC_AVRCP_TG_OUTGOING_CALL_DURING_AUDIO | \ MPMD_A2DP_SRC_AVRCP_TG_REJECT_CALL_DURING_AUDIO | \ MPMD_A2DP_SRC_AVRCP_TG_TERMINATION_DURING_AVP | \ MPMD_A2DP_SRC_AVRCP_TG_PRESS_PLAY_DURING_CALL | \ MPMD_A2DP_SRC_AVRCP_TG_START_AUDIO_AFTER_PLAY | \ MPMD_A2DP_SRC_AVRCP_TG_SUSPEND_AUDIO_AFTER_PAUSE | \ MPMD_A2DP_SRC_AVRCP_TG_START_AUDIO_DURING_DATA_COMM | \ MPMD_A2DP_SRC_AVRCP_TG_START_DATA_DURING_AUDIO) #define MPS_MPMD_A2DP_SNK ( \ MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_ANSWER_CALL_DURING_AUDIO | \ MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_OUTGOING_CALL_DURING_AUDIO | \ MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_REJECT_CALL_DURING_AUDIO | \ MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_TERMINATION_DURING_AVP | \ MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_PLAY_DURING_CALL | \ MPMD_A2DP_SNK_AVRCP_CT_DUN_DT_AUDIO_DURING_DATA_COMM | \ MPMD_A2DP_SNK_AVRCP_CT_DUN_DT_START_DATA_DURING_AUDIO) #define MPS_MPMD_AVRCP_CT MPS_MPMD_A2DP_SNK /* should be set only if CT is supported but SNK is not supported */ #define MPS_MPMD_AVRCP_CT_ONLY ( \ MPMD_AVRCP_CT_NO_A2DP_SNK_START_AUDIO_AFTER_PLAY | \ MPMD_AVRCP_CT_NO_A2DP_SNK_SUSPEND_AUDIO_AFTER_PAUSE) #define MPS_MPMD_AVRCP_TG MPS_MPMD_A2DP_SRC #define MPS_MPMD_DUN_DT ( \ MPMD_A2DP_SNK_AVRCP_CT_DUN_DT_AUDIO_DURING_DATA_COMM | \ MPMD_A2DP_SNK_AVRCP_CT_DUN_DT_START_DATA_DURING_AUDIO) #define MPS_MPMD_ALL (MPS_MPMD_HFP_AG | MPS_MPMD_HFP_HF | MPS_MPMD_A2DP_SRC | \ MPS_MPMD_A2DP_SNK | MPS_MPMD_AVRCP_CT | \ MPS_MPMD_AVRCP_CT_ONLY | MPS_MPMD_AVRCP_TG | \ MPS_MPMD_DUN_DT) /* Assume all dependencies are supported */ #define MPS_DEFAULT_DEPS (MPS_DEPS_SNIFF_MODE_DURRING_STREAMING | \ MPS_DEPS_GAVDP_REQUIREMENTS | \ MPS_DEPS_DIS_CONNECTION_ORDER_BEHAVIOR) static sdp_record_t *server = NULL; static uint32_t fixed_dbts = 0; /* * List of version numbers supported by the SDP server. * Add to this list when newer versions are supported. */ static sdp_version_t sdpVnumArray[1] = { { 1, 0 } }; static const int sdpServerVnumEntries = 1; static uint32_t mps_handle = 0; static bool mps_mpmd = false; /* * A simple function which returns the time of day in * seconds. Used for updating the service db state * attribute of the service record of the SDP server */ uint32_t sdp_get_time(void) { /* * To handle failure in gettimeofday, so an old * value is returned and service does not fail */ static struct timeval tm; gettimeofday(&tm, NULL); return (uint32_t) tm.tv_sec; } /* * The service database state is an attribute of the service record * of the SDP server itself. This attribute is guaranteed to * change if any of the contents of the service repository * changes. This function updates the timestamp of value of * the svcDBState attribute * Set the SDP server DB. Simply a timestamp which is the marker * when the DB was modified. */ static void update_db_timestamp(void) { if (fixed_dbts) { sdp_data_t *d = sdp_data_alloc(SDP_UINT32, &fixed_dbts); sdp_attr_replace(server, SDP_ATTR_SVCDB_STATE, d); } else { uint32_t dbts = sdp_get_time(); sdp_data_t *d = sdp_data_alloc(SDP_UINT32, &dbts); sdp_attr_replace(server, SDP_ATTR_SVCDB_STATE, d); } } void set_fixed_db_timestamp(uint32_t dbts) { fixed_dbts = dbts; } void register_public_browse_group(void) { sdp_list_t *browselist; uuid_t bgscid, pbgid; sdp_data_t *sdpdata; sdp_record_t *browse = sdp_record_alloc(); browse->handle = SDP_SERVER_RECORD_HANDLE + 1; sdp_record_add(BDADDR_ANY, browse); sdpdata = sdp_data_alloc(SDP_UINT32, &browse->handle); sdp_attr_add(browse, SDP_ATTR_RECORD_HANDLE, sdpdata); sdp_uuid16_create(&bgscid, BROWSE_GRP_DESC_SVCLASS_ID); browselist = sdp_list_append(0, &bgscid); sdp_set_service_classes(browse, browselist); sdp_list_free(browselist, 0); sdp_uuid16_create(&pbgid, PUBLIC_BROWSE_GROUP); sdp_attr_add_new(browse, SDP_ATTR_GROUP_ID, SDP_UUID16, &pbgid.value.uuid16); } /* * The SDP server must present its own service record to * the service repository. This can be accessed by service * discovery clients. This method constructs a service record * and stores it in the repository */ void register_server_service(void) { sdp_list_t *classIDList; uuid_t classID; void **versions, **versionDTDs; uint8_t dtd; sdp_data_t *pData; int i; server = sdp_record_alloc(); server->pattern = NULL; /* Force the record to be SDP_SERVER_RECORD_HANDLE */ server->handle = SDP_SERVER_RECORD_HANDLE; sdp_record_add(BDADDR_ANY, server); sdp_attr_add(server, SDP_ATTR_RECORD_HANDLE, sdp_data_alloc(SDP_UINT32, &server->handle)); sdp_uuid16_create(&classID, SDP_SERVER_SVCLASS_ID); classIDList = sdp_list_append(0, &classID); sdp_set_service_classes(server, classIDList); sdp_list_free(classIDList, 0); /* * Set the version numbers supported, these are passed as arguments * to the server on command line. Now defaults to 1.0 * Build the version number sequence first */ versions = malloc(sdpServerVnumEntries * sizeof(void *)); versionDTDs = malloc(sdpServerVnumEntries * sizeof(void *)); dtd = SDP_UINT16; for (i = 0; i < sdpServerVnumEntries; i++) { uint16_t *version = malloc(sizeof(uint16_t)); *version = sdpVnumArray[i].major; *version = (*version << 8); *version |= sdpVnumArray[i].minor; versions[i] = version; versionDTDs[i] = &dtd; } pData = sdp_seq_alloc(versionDTDs, versions, sdpServerVnumEntries); for (i = 0; i < sdpServerVnumEntries; i++) free(versions[i]); free(versions); free(versionDTDs); sdp_attr_add(server, SDP_ATTR_VERSION_NUM_LIST, pData); update_db_timestamp(); } void register_device_id(uint16_t source, uint16_t vendor, uint16_t product, uint16_t version) { const uint16_t spec = 0x0103; const uint8_t primary = 1; sdp_list_t *class_list, *group_list, *profile_list; uuid_t class_uuid, group_uuid; sdp_data_t *sdp_data, *primary_data, *source_data; sdp_data_t *spec_data, *vendor_data, *product_data, *version_data; sdp_profile_desc_t profile; sdp_record_t *record = sdp_record_alloc(); DBG("Adding device id record for %04x:%04x:%04x:%04x", source, vendor, product, version); record->handle = sdp_next_handle(); sdp_record_add(BDADDR_ANY, record); sdp_data = sdp_data_alloc(SDP_UINT32, &record->handle); sdp_attr_add(record, SDP_ATTR_RECORD_HANDLE, sdp_data); sdp_uuid16_create(&class_uuid, PNP_INFO_SVCLASS_ID); class_list = sdp_list_append(0, &class_uuid); sdp_set_service_classes(record, class_list); sdp_list_free(class_list, NULL); sdp_uuid16_create(&group_uuid, PUBLIC_BROWSE_GROUP); group_list = sdp_list_append(NULL, &group_uuid); sdp_set_browse_groups(record, group_list); sdp_list_free(group_list, NULL); sdp_uuid16_create(&profile.uuid, PNP_INFO_PROFILE_ID); profile.version = spec; profile_list = sdp_list_append(NULL, &profile); sdp_set_profile_descs(record, profile_list); sdp_list_free(profile_list, NULL); spec_data = sdp_data_alloc(SDP_UINT16, &spec); sdp_attr_add(record, 0x0200, spec_data); vendor_data = sdp_data_alloc(SDP_UINT16, &vendor); sdp_attr_add(record, 0x0201, vendor_data); product_data = sdp_data_alloc(SDP_UINT16, &product); sdp_attr_add(record, 0x0202, product_data); version_data = sdp_data_alloc(SDP_UINT16, &version); sdp_attr_add(record, 0x0203, version_data); primary_data = sdp_data_alloc(SDP_BOOL, &primary); sdp_attr_add(record, 0x0204, primary_data); source_data = sdp_data_alloc(SDP_UINT16, &source); sdp_attr_add(record, 0x0205, source_data); update_db_timestamp(); } static bool class_supported(uint16_t class) { sdp_list_t *list; uuid_t uuid; sdp_uuid16_create(&uuid, class); for (list = sdp_get_record_list(); list; list = list->next) { sdp_record_t *rec = list->data; if (sdp_uuid_cmp(&rec->svclass, &uuid) == 0) return true; } return false; } static uint64_t mps_mpsd_features(void) { uint64_t feat = MPS_MPSD_ALL; if (!class_supported(HANDSFREE_AGW_SVCLASS_ID)) feat &= ~MPS_MPSD_HFP_AG; if (!class_supported(HANDSFREE_SVCLASS_ID)) feat &= ~MPS_MPSD_HFP_HF; if (!class_supported(AUDIO_SOURCE_SVCLASS_ID)) feat &= ~MPS_MPSD_A2DP_SRC; if (!class_supported(AUDIO_SINK_SVCLASS_ID)) feat &= ~MPS_MPSD_A2DP_SNK; if (!class_supported(AV_REMOTE_CONTROLLER_SVCLASS_ID)) feat &= ~MPS_MPSD_AVRCP_CT; if (!class_supported(AV_REMOTE_TARGET_SVCLASS_ID)) feat &= ~MPS_MPSD_AVRCP_TG; if (!class_supported(DIALUP_NET_SVCLASS_ID)) feat &= ~MPS_MPSD_DUN_GW; /* TODO */ feat &= ~MPS_MPSD_DUN_DT; if (!class_supported(NAP_SVCLASS_ID)) feat &= ~MPS_MPSD_PAN_NAP; if (!class_supported(PANU_SVCLASS_ID)) feat &= ~MPS_MPSD_PAN_PANU; if (!class_supported(PBAP_PSE_SVCLASS_ID)) feat &= ~MPS_MPSD_PBAP_SRC; if (!class_supported(PBAP_PCE_SVCLASS_ID)) feat &= ~MPS_MPSD_PBAP_CLI; return feat; } static uint64_t mps_mpmd_features(void) { uint64_t feat = MPS_MPMD_ALL; if (!class_supported(HANDSFREE_AGW_SVCLASS_ID)) feat &= ~MPS_MPMD_HFP_AG; if (!class_supported(HANDSFREE_SVCLASS_ID)) feat &= ~MPS_MPMD_HFP_HF; if (!class_supported(AUDIO_SOURCE_SVCLASS_ID)) feat &= ~MPS_MPMD_A2DP_SRC; if (!class_supported(AUDIO_SINK_SVCLASS_ID)) feat &= ~MPS_MPMD_A2DP_SNK; else feat &= ~MPS_MPMD_AVRCP_CT_ONLY; if (!class_supported(AV_REMOTE_CONTROLLER_SVCLASS_ID)) { feat &= ~MPS_MPMD_AVRCP_CT; feat &= ~MPS_MPMD_AVRCP_CT_ONLY; } if (!class_supported(AV_REMOTE_TARGET_SVCLASS_ID)) feat &= ~MPS_MPMD_AVRCP_TG; /* TODO */ feat &= ~MPS_MPMD_DUN_DT; return feat; } static sdp_record_t *mps_record(int mpmd) { sdp_data_t *mpsd_features, *mpmd_features, *dependencies; sdp_list_t *svclass_id, *pfseq, *root; uuid_t root_uuid, svclass_uuid; sdp_profile_desc_t profile; sdp_record_t *record; uint64_t mpsd_feat = MPS_MPSD_DEFAULT_FEATURES; uint64_t mpmd_feat = MPS_MPMD_DEFAULT_FEATURES; uint16_t deps = MPS_DEFAULT_DEPS; record = sdp_record_alloc(); if (!record) return NULL; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(NULL, &root_uuid); sdp_set_browse_groups(record, root); sdp_list_free(root, NULL); sdp_uuid16_create(&svclass_uuid, MPS_SVCLASS_ID); svclass_id = sdp_list_append(NULL, &svclass_uuid); sdp_set_service_classes(record, svclass_id); sdp_list_free(svclass_id, NULL); sdp_uuid16_create(&profile.uuid, MPS_PROFILE_ID); profile.version = 0x0100; pfseq = sdp_list_append(NULL, &profile); sdp_set_profile_descs(record, pfseq); sdp_list_free(pfseq, NULL); mpsd_features = sdp_data_alloc(SDP_UINT64, &mpsd_feat); sdp_attr_add(record, SDP_ATTR_MPSD_SCENARIOS, mpsd_features); if (mpmd) { mpmd_features = sdp_data_alloc(SDP_UINT64, &mpmd_feat); sdp_attr_add(record, SDP_ATTR_MPMD_SCENARIOS, mpmd_features); } dependencies = sdp_data_alloc(SDP_UINT16, &deps); sdp_attr_add(record, SDP_ATTR_MPS_DEPENDENCIES, dependencies); sdp_set_info_attr(record, "Multi Profile", 0, 0); return record; } void register_mps(bool mpmd) { sdp_record_t *record; record = mps_record(mpmd); if (!record) return; if (add_record_to_server(BDADDR_ANY, record) < 0) { sdp_record_free(record); return; } mps_handle = record->handle; mps_mpmd = mpmd; } static void update_mps(void) { sdp_record_t *rec; sdp_data_t *data; uint64_t mpsd_feat, mpmd_feat; if (!mps_handle) return; rec = sdp_record_find(mps_handle); if (!rec) return; mpsd_feat = mps_mpsd_features(); data = sdp_data_alloc(SDP_UINT64, &mpsd_feat); sdp_attr_replace(rec, SDP_ATTR_MPSD_SCENARIOS, data); if (mps_mpmd) { mpmd_feat = mps_mpmd_features(); data = sdp_data_alloc(SDP_UINT64, &mpmd_feat); sdp_attr_replace(rec, SDP_ATTR_MPMD_SCENARIOS, data); } } int add_record_to_server(const bdaddr_t *src, sdp_record_t *rec) { sdp_data_t *data; sdp_list_t *pattern; if (rec->handle == 0xffffffff) { rec->handle = sdp_next_handle(); if (rec->handle < 0x10000) return -ENOSPC; } else { if (sdp_record_find(rec->handle)) return -EEXIST; } DBG("Adding record with handle 0x%05x", rec->handle); sdp_record_add(src, rec); data = sdp_data_alloc(SDP_UINT32, &rec->handle); sdp_attr_replace(rec, SDP_ATTR_RECORD_HANDLE, data); if (sdp_data_get(rec, SDP_ATTR_BROWSE_GRP_LIST) == NULL) { uuid_t uuid; sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP); sdp_pattern_add_uuid(rec, &uuid); } for (pattern = rec->pattern; pattern; pattern = pattern->next) { char uuid[32]; if (pattern->data == NULL) continue; sdp_uuid2strn((uuid_t *) pattern->data, uuid, sizeof(uuid)); DBG("Record pattern UUID %s", uuid); } update_mps(); update_db_timestamp(); return 0; } int remove_record_from_server(uint32_t handle) { sdp_record_t *rec; /* Refuse to remove the server's own record */ if (handle == SDP_SERVER_RECORD_HANDLE) return -EINVAL; DBG("Removing record with handle 0x%05x", handle); rec = sdp_record_find(handle); if (!rec) return -ENOENT; if (sdp_record_remove(handle) == 0) { update_mps(); update_db_timestamp(); } sdp_record_free(rec); return 0; } /* FIXME: refactor for server-side */ static sdp_record_t *extract_pdu_server(bdaddr_t *device, uint8_t *p, unsigned int bufsize, uint32_t handleExpected, int *scanned) { int extractStatus = -1, localExtractedLength = 0; uint8_t dtd; int seqlen = 0; sdp_record_t *rec = NULL; uint16_t attrId, lookAheadAttrId; sdp_data_t *pAttr = NULL; uint32_t handle = 0xffffffff; *scanned = sdp_extract_seqtype(p, bufsize, &dtd, &seqlen); p += *scanned; bufsize -= *scanned; if (bufsize < sizeof(uint8_t) + sizeof(uint8_t)) { SDPDBG("Unexpected end of packet"); return NULL; } lookAheadAttrId = get_be16(p + sizeof(uint8_t)); SDPDBG("Look ahead attr id : %d", lookAheadAttrId); if (lookAheadAttrId == SDP_ATTR_RECORD_HANDLE) { if (bufsize < (sizeof(uint8_t) * 2) + sizeof(uint16_t) + sizeof(uint32_t)) { SDPDBG("Unexpected end of packet"); return NULL; } handle = get_be32(p + sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint8_t)); SDPDBG("SvcRecHandle : 0x%x", handle); rec = sdp_record_find(handle); } else if (handleExpected != 0xffffffff) rec = sdp_record_find(handleExpected); if (!rec) { rec = sdp_record_alloc(); rec->attrlist = NULL; if (lookAheadAttrId == SDP_ATTR_RECORD_HANDLE) { rec->handle = handle; sdp_record_add(device, rec); } else if (handleExpected != 0xffffffff) { rec->handle = handleExpected; sdp_record_add(device, rec); } } else { sdp_list_free(rec->attrlist, (sdp_free_func_t) sdp_data_free); rec->attrlist = NULL; } while (localExtractedLength < seqlen) { int attrSize = sizeof(uint8_t); int attrValueLength = 0; if (bufsize < attrSize + sizeof(uint16_t)) { SDPDBG("Unexpected end of packet: Terminating extraction of attributes"); break; } SDPDBG("Extract PDU, sequenceLength: %d localExtractedLength: %d", seqlen, localExtractedLength); dtd = *(uint8_t *) p; attrId = get_be16(p + attrSize); attrSize += sizeof(uint16_t); SDPDBG("DTD of attrId : %d Attr id : 0x%x", dtd, attrId); pAttr = sdp_extract_attr(p + attrSize, bufsize - attrSize, &attrValueLength, rec); SDPDBG("Attr id : 0x%x attrValueLength : %d", attrId, attrValueLength); attrSize += attrValueLength; if (pAttr == NULL) { SDPDBG("Terminating extraction of attributes"); break; } localExtractedLength += attrSize; p += attrSize; bufsize -= attrSize; sdp_attr_replace(rec, attrId, pAttr); extractStatus = 0; SDPDBG("Extract PDU, seqLength: %d localExtractedLength: %d", seqlen, localExtractedLength); } if (extractStatus == 0) { SDPDBG("Successful extracting of Svc Rec attributes"); #ifdef SDP_DEBUG sdp_print_service_attr(rec->attrlist); #endif *scanned += seqlen; } return rec; } /* * Add the newly created service record to the service repository */ int service_register_req(sdp_req_t *req, sdp_buf_t *rsp) { int scanned = 0; sdp_data_t *handle; uint8_t *p = req->buf + sizeof(sdp_pdu_hdr_t); int bufsize = req->len - sizeof(sdp_pdu_hdr_t); sdp_record_t *rec; req->flags = *p++; if (req->flags & SDP_DEVICE_RECORD) { bacpy(&req->device, (bdaddr_t *) p); p += sizeof(bdaddr_t); bufsize -= sizeof(bdaddr_t); } /* save image of PDU: we need it when clients request this attribute */ rec = extract_pdu_server(&req->device, p, bufsize, 0xffffffff, &scanned); if (!rec) goto invalid; if (rec->handle == 0xffffffff) { rec->handle = sdp_next_handle(); if (rec->handle < 0x10000) { sdp_record_free(rec); goto invalid; } } else { if (sdp_record_find(rec->handle)) { /* extract_pdu_server will add the record handle * if it is missing. So instead of failing, skip * the record adding to avoid duplication. */ goto success; } } sdp_record_add(&req->device, rec); if (!(req->flags & SDP_RECORD_PERSIST)) sdp_svcdb_set_collectable(rec, req->sock); handle = sdp_data_alloc(SDP_UINT32, &rec->handle); sdp_attr_replace(rec, SDP_ATTR_RECORD_HANDLE, handle); success: /* if the browse group descriptor is NULL, * ensure that the record belongs to the ROOT group */ if (sdp_data_get(rec, SDP_ATTR_BROWSE_GRP_LIST) == NULL) { uuid_t uuid; sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP); sdp_pattern_add_uuid(rec, &uuid); } update_db_timestamp(); /* Build a rsp buffer */ put_be32(rec->handle, rsp->data); rsp->data_size = sizeof(uint32_t); return 0; invalid: put_be16(SDP_INVALID_SYNTAX, rsp->data); rsp->data_size = sizeof(uint16_t); return -1; } /* * Update a service record */ int service_update_req(sdp_req_t *req, sdp_buf_t *rsp) { sdp_record_t *orec, *nrec; int status = 0, scanned = 0; uint8_t *p = req->buf + sizeof(sdp_pdu_hdr_t); int bufsize = req->len - sizeof(sdp_pdu_hdr_t); uint32_t handle = get_be32(p); SDPDBG("Svc Rec Handle: 0x%x", handle); p += sizeof(uint32_t); bufsize -= sizeof(uint32_t); orec = sdp_record_find(handle); SDPDBG("SvcRecOld: %p", orec); if (!orec) { status = SDP_INVALID_RECORD_HANDLE; goto done; } nrec = extract_pdu_server(BDADDR_ANY, p, bufsize, handle, &scanned); if (!nrec) { status = SDP_INVALID_SYNTAX; goto done; } assert(nrec == orec); update_db_timestamp(); done: p = rsp->data; put_be16(status, p); rsp->data_size = sizeof(uint16_t); return status; } /* * Remove a registered service record */ int service_remove_req(sdp_req_t *req, sdp_buf_t *rsp) { uint8_t *p = req->buf + sizeof(sdp_pdu_hdr_t); uint32_t handle = get_be32(p); sdp_record_t *rec; int status = 0; /* extract service record handle */ rec = sdp_record_find(handle); if (rec) { sdp_svcdb_collect(rec); status = sdp_record_remove(handle); sdp_record_free(rec); if (status == 0) update_db_timestamp(); } else { status = SDP_INVALID_RECORD_HANDLE; SDPDBG("Could not find record : 0x%x", handle); } p = rsp->data; put_be16(status, p); rsp->data_size = sizeof(uint16_t); return status; } bluez-5.87/src/PaxHeaders/set.c0000644000000000000000000000005014643061455013411 xustar0020 atime=1783093825 20 ctime=1783094106 bluez-5.87/src/set.c0000644000000000000000000002160514643061455013076 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2023 Intel Corporation * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/ad.h" #include "src/shared/crypto.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "log.h" #include "error.h" #include "adapter.h" #include "device.h" #include "dbus-common.h" #include "set.h" static struct queue *set_list; struct btd_device_set { struct btd_adapter *adapter; char *path; uint8_t sirk[16]; uint8_t size; bool auto_connect; struct queue *devices; struct btd_device *device; }; static DBusMessage *set_disconnect(DBusConnection *conn, DBusMessage *msg, void *user_data) { /* TODO */ return NULL; } static DBusMessage *set_connect(DBusConnection *conn, DBusMessage *msg, void *user_data) { /* TODO */ return NULL; } static const GDBusMethodTable set_methods[] = { { GDBUS_EXPERIMENTAL_ASYNC_METHOD("Disconnect", NULL, NULL, set_disconnect) }, { GDBUS_EXPERIMENTAL_ASYNC_METHOD("Connect", NULL, NULL, set_connect) }, {} }; static gboolean get_adapter(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device_set *set = data; const char *path = adapter_get_path(set->adapter); dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); return TRUE; } static gboolean get_auto_connect(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device_set *set = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &set->auto_connect); return TRUE; } static void set_auto_connect(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *data) { struct btd_device_set *set = data; dbus_bool_t b; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } dbus_message_iter_get_basic(iter, &b); set->auto_connect = b ? true : false; g_dbus_pending_property_success(id); } static void append_device(void *data, void *user_data) { struct btd_device *device = data; const char *path = device_get_path(device); DBusMessageIter *entry = user_data; dbus_message_iter_append_basic(entry, DBUS_TYPE_OBJECT_PATH, &path); } static gboolean get_devices(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device_set *set = data; DBusMessageIter entry; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH_AS_STRING, &entry); queue_foreach(set->devices, append_device, &entry); dbus_message_iter_close_container(iter, &entry); return TRUE; } static gboolean get_size(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_device_set *set = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &set->size); return TRUE; } static const GDBusPropertyTable set_properties[] = { { "Adapter", "o", get_adapter, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "AutoConnect", "b", get_auto_connect, set_auto_connect, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Devices", "ao", get_devices, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Size", "y", get_size, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, {} }; static void set_free(void *data) { struct btd_device_set *set = data; queue_destroy(set->devices, NULL); g_free(set->path); free(set); } static struct btd_device_set *set_new(struct btd_device *device, const uint8_t sirk[16], uint8_t size) { struct btd_device_set *set; set = new0(struct btd_device_set, 1); set->adapter = device_get_adapter(device); memcpy(set->sirk, sirk, sizeof(set->sirk)); set->size = size; set->auto_connect = true; set->devices = queue_new(); queue_push_tail(set->devices, device); set->path = g_strdup_printf("%s/set_%02x%02x%02x%02x%02x%02x%02x%02x" "%02x%02x%02x%02x%02x%02x%02x%02x", adapter_get_path(set->adapter), sirk[15], sirk[14], sirk[13], sirk[12], sirk[11], sirk[10], sirk[9], sirk[8], sirk[7], sirk[6], sirk[5], sirk[4], sirk[3], sirk[2], sirk[1], sirk[0]); DBG("Creating set %s", set->path); if (g_dbus_register_interface(btd_get_dbus_connection(), set->path, BTD_DEVICE_SET_INTERFACE, set_methods, NULL, set_properties, set, set_free) == FALSE) { error("Unable to register set interface"); set_free(set); return NULL; } return set; } static struct btd_device_set *set_find(struct btd_device *device, const uint8_t sirk[16]) { struct btd_adapter *adapter = device_get_adapter(device); const struct queue_entry *entry; for (entry = queue_get_entries(set_list); entry; entry = entry->next) { struct btd_device_set *set = entry->data; if (set->adapter != adapter) continue; if (!memcmp(set->sirk, sirk, sizeof(set->sirk))) return set; } return NULL; } static void set_connect_next(struct btd_device_set *set) { const struct queue_entry *entry; for (entry = queue_get_entries(set->devices); entry; entry = entry->next) { struct btd_device *device = entry->data; /* Only connect one at time(?) */ if (!device_connect_le(device)) return; } } static void set_add(struct btd_device_set *set, struct btd_device *device) { /* Check if device is already part of the set then skip to connect */ if (queue_find(set->devices, NULL, device)) goto done; DBG("set %s device %s", set->path, device_get_path(device)); queue_push_tail(set->devices, device); g_dbus_emit_property_changed(btd_get_dbus_connection(), set->path, BTD_DEVICE_SET_INTERFACE, "Devices"); done: /* Check if set is marked to auto-connect */ if (btd_device_is_connected(device) && set->auto_connect) set_connect_next(set); } static void foreach_rsi(void *data, void *user_data) { struct bt_ad_data *ad = data; struct btd_device_set *set = user_data; struct bt_crypto *crypto; uint8_t res[3]; if (ad->type != BT_AD_CSIP_RSI || ad->len < 6) return; crypto = bt_crypto_new(); if (!crypto) return; if (!bt_crypto_sih(crypto, set->sirk, ad->data + 3, res)) { bt_crypto_unref(crypto); return; } bt_crypto_unref(crypto); if (memcmp(ad->data, res, sizeof(res))) return; /* Attempt to use existing gatt_db from set if device has never been * connected before. * * If dbs don't really match bt_gatt_client will attempt to rediscover * the ranges that don't match. */ if (gatt_db_isempty(btd_device_get_gatt_db(set->device))) { struct btd_device *device; device = queue_get_entries(set->devices)->data; btd_device_set_gatt_db(set->device, btd_device_get_gatt_db(device)); } device_connect_le(set->device); } static void foreach_device(struct btd_device *device, void *data) { struct btd_device_set *set = data; /* Check if device is already part of the set then skip */ if (queue_find(set->devices, NULL, device)) return; set->device = device; btd_device_foreach_ad(device, foreach_rsi, set); } struct btd_device_set *btd_set_add_device(struct btd_device *device, const uint8_t *key, const uint8_t sirk_value[16], uint8_t size) { struct btd_device_set *set; uint8_t sirk[16]; memcpy(sirk, sirk_value, sizeof(sirk)); /* In case key has been set it means SIRK is encrypted */ if (key) { struct bt_crypto *crypto = bt_crypto_new(); if (!crypto) return NULL; /* sef and sdf are symmetric */ bt_crypto_sef(crypto, key, sirk, sirk); bt_crypto_unref(crypto); } /* Check if DeviceSet already exists */ set = set_find(device, sirk); if (set) { set_add(set, device); /* Check if there are new devices with RSI found */ goto done; } set = set_new(device, sirk, size); if (!set) return NULL; if (!set_list) set_list = queue_new(); queue_push_tail(set_list, set); done: /* Attempt to add devices which have matching RSI */ btd_adapter_for_each_device(device_get_adapter(device), foreach_device, set); return set; } bool btd_set_remove_device(struct btd_device_set *set, struct btd_device *device) { if (!set || !device) return false; if (!queue_remove_if(set->devices, NULL, device)) return false; if (!queue_isempty(set->devices)) { g_dbus_emit_property_changed(btd_get_dbus_connection(), set->path, BTD_DEVICE_SET_INTERFACE, "Devices"); return true; } if (!queue_remove(set_list, set)) return false; /* Unregister if there are no devices left in the set */ g_dbus_unregister_interface(btd_get_dbus_connection(), set->path, BTD_DEVICE_SET_INTERFACE); return true; } const char *btd_set_get_path(struct btd_device_set *set) { return set->path; } bluez-5.87/src/PaxHeaders/service.c0000644000000000000000000000005015142177620014253 xustar0020 atime=1783093792 20 ctime=1783094106 bluez-5.87/src/service.c0000644000000000000000000002747015142177620013746 0ustar00rootroot// 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 "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "src/shared/queue.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 queue *depends; struct queue *dependents; }; 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 depends_ready(void *item, void *user_data) { struct btd_service *service = item; struct btd_service *dep = user_data; struct btd_profile_uuid_cb *after = &service->profile->after_services; char addr[18]; if (dep && !queue_remove(service->depends, dep)) return; if (!service->depends || !queue_isempty(service->depends)) return; queue_destroy(service->depends, NULL); service->depends = NULL; if (!after->count && !after->func) return; ba2str(device_get_address(service->device), addr); DBG("%p: device %s profile %s dependencies ready", service, addr, service->profile->name); switch (service->state) { case BTD_SERVICE_STATE_CONNECTING: case BTD_SERVICE_STATE_CONNECTED: if (after->func) after->func(service); break; case BTD_SERVICE_STATE_UNAVAILABLE: case BTD_SERVICE_STATE_DISCONNECTING: case BTD_SERVICE_STATE_DISCONNECTED: break; } } static void service_ready(struct btd_service *service) { queue_foreach(service->dependents, depends_ready, service); queue_destroy(service->dependents, NULL); service->dependents = NULL; depends_ready(service, 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_CONNECTING) service_ready(service); 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; } static void depends_remove(void *item, void *user_data) { struct btd_service *service = item; queue_remove(service->dependents, user_data); } static void dependents_remove(void *item, void *user_data) { struct btd_service *service = item; queue_remove(service->depends, user_data); } void btd_service_unref(struct btd_service *service) { service->ref--; DBG("%p: ref=%d", service, service->ref); if (service->ref > 0) return; queue_foreach(service->depends, depends_remove, service); queue_foreach(service->dependents, dependents_remove, service); queue_destroy(service->depends, NULL); queue_destroy(service->dependents, NULL); 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); } static void add_depends(struct btd_service *service) { struct btd_profile_uuid_cb *after = &service->profile->after_services; unsigned int i; queue_foreach(service->depends, depends_remove, service); queue_destroy(service->depends, NULL); service->depends = queue_new(); for (i = 0; i < after->count; ++i) { const char *uuid = after->uuids[i]; struct btd_service *dep; dep = btd_device_get_service(service->device, uuid); if (!dep) continue; /* Profiles are sorted vs after_uuids, so the dependency will * have started connecting before us if it is going to connect. */ if (dep->state != BTD_SERVICE_STATE_CONNECTING) continue; if (queue_find(service->depends, NULL, dep)) continue; queue_push_tail(service->depends, dep); if (!dep->dependents) dep->dependents = queue_new(); queue_push_tail(dep->dependents, 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; add_depends(service); 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; } add_depends(service); 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); } bluez-5.87/src/PaxHeaders/bearer.h0000644000000000000000000000005015142177620014060 xustar0020 atime=1783093799 20 ctime=1783094106 bluez-5.87/src/bearer.h0000644000000000000000000000122615142177620013542 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2025 Intel Corporation * * */ #define BTD_BEARER_BREDR_INTERFACE "org.bluez.Bearer.BREDR1" #define BTD_BEARER_LE_INTERFACE "org.bluez.Bearer.LE1" struct btd_bearer; struct btd_bearer *btd_bearer_new(struct btd_device *device, uint8_t type); void btd_bearer_destroy(struct btd_bearer *bearer); void btd_bearer_paired(struct btd_bearer *bearer); void btd_bearer_bonded(struct btd_bearer *bearer); void btd_bearer_connected(struct btd_bearer *bearer, int err); void btd_bearer_disconnected(struct btd_bearer *bearer, uint8_t reason); bluez-5.87/src/PaxHeaders/dbus-common.c0000644000000000000000000000005014131623652015034 xustar0020 atime=1783093815 20 ctime=1783094106 bluez-5.87/src/dbus-common.c0000644000000000000000000000522314131623652014517 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright (C) 2005-2007 Johan Hedberg * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "gdbus/gdbus.h" #include "log.h" #include "dbus-common.h" static DBusConnection *connection = NULL; void dict_append_entry(DBusMessageIter *dict, const char *key, int type, void *val) { g_dbus_dict_append_entry(dict, key, type, val); } void dict_append_array(DBusMessageIter *dict, const char *key, int type, void *val, int n_elements) { g_dbus_dict_append_array(dict, key, type, val, n_elements); } void set_dbus_connection(DBusConnection *conn) { connection = conn; } DBusConnection *btd_get_dbus_connection(void) { return connection; } const char *class_to_icon(uint32_t class) { switch ((class & 0x1f00) >> 8) { case 0x01: return "computer"; case 0x02: switch ((class & 0xfc) >> 2) { case 0x01: case 0x02: case 0x03: case 0x05: return "phone"; case 0x04: return "modem"; } break; case 0x03: return "network-wireless"; case 0x04: switch ((class & 0xfc) >> 2) { case 0x01: case 0x02: return "audio-headset"; case 0x06: return "audio-headphones"; case 0x0b: /* VCR */ case 0x0c: /* Video Camera */ case 0x0d: /* Camcorder */ return "camera-video"; default: return "audio-card"; /* Other audio device */ } break; case 0x05: switch ((class & 0xc0) >> 6) { case 0x00: switch ((class & 0x1e) >> 2) { case 0x01: case 0x02: return "input-gaming"; } break; case 0x01: return "input-keyboard"; case 0x02: switch ((class & 0x1e) >> 2) { case 0x05: return "input-tablet"; default: return "input-mouse"; } } break; case 0x06: if (class & 0x80) return "printer"; if (class & 0x20) return "camera-photo"; break; } return NULL; } const char *gap_appearance_to_icon(uint16_t appearance) { switch ((appearance & 0xffc0) >> 6) { case 0x00: return "unknown"; case 0x01: return "phone"; case 0x02: return "computer"; case 0x05: return "video-display"; case 0x0a: return "multimedia-player"; case 0x0b: return "scanner"; case 0x0f: /* HID Generic */ switch (appearance & 0x3f) { case 0x01: return "input-keyboard"; case 0x02: return "input-mouse"; case 0x03: case 0x04: return "input-gaming"; case 0x05: return "input-tablet"; case 0x08: return "scanner"; } break; } return NULL; } bluez-5.87/src/PaxHeaders/sdp-xml.c0000644000000000000000000000005015063045254014176 xustar0020 atime=1783093751 20 ctime=1783094106 bluez-5.87/src/sdp-xml.c0000644000000000000000000005404115063045254013663 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2005-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "sdp-xml.h" #define DBG(...) (void)(0) #define error(...) (void)(0) #define SDP_XML_ENCODING_NORMAL 0 #define SDP_XML_ENCODING_HEX 1 #define STRBUFSIZE 1024 #define MAXINDENT 64 struct sdp_xml_data { char *text; /* Pointer to the current buffer */ int size; /* Size of the current buffer */ sdp_data_t *data; /* The current item being built */ struct sdp_xml_data *next; /* Next item on the stack */ char type; /* 0 = Text or Hexadecimal */ char *name; /* Name, optional in the dtd */ /* TODO: What is it used for? */ }; struct context_data { sdp_record_t *record; sdp_data_t attr_data; struct sdp_xml_data *stack_head; uint16_t attr_id; }; static int compute_seq_size(sdp_data_t *data) { int unit_size = data->unitSize; sdp_data_t *seq = data->val.dataseq; for (; seq; seq = seq->next) unit_size += seq->unitSize; return unit_size; } #define DEFAULT_XML_DATA_SIZE 1024 static struct sdp_xml_data *sdp_xml_data_alloc(void) { struct sdp_xml_data *elem; elem = malloc(sizeof(struct sdp_xml_data)); if (!elem) return NULL; memset(elem, 0, sizeof(struct sdp_xml_data)); /* Null terminate the text */ elem->size = DEFAULT_XML_DATA_SIZE; elem->text = malloc(DEFAULT_XML_DATA_SIZE); if (!elem->text) { free(elem); return NULL; } elem->text[0] = '\0'; return elem; } static struct sdp_xml_data *sdp_xml_data_expand(struct sdp_xml_data *elem) { char *newbuf; newbuf = malloc(elem->size * 2); if (!newbuf) return NULL; memcpy(newbuf, elem->text, elem->size); elem->size *= 2; free(elem->text); elem->text = newbuf; return elem; } static sdp_data_t *sdp_xml_parse_uuid128(const char *data) { uint128_t val; unsigned int i, j; char buf[3]; memset(&val, 0, sizeof(val)); buf[2] = '\0'; for (j = 0, i = 0; i < strlen(data);) { if (data[i] == '-') { i++; continue; } buf[0] = data[i]; buf[1] = data[i + 1]; if (j >= sizeof(val.data)) return NULL; val.data[j++] = strtoul(buf, 0, 16); i += 2; } if (j != sizeof(val.data)) return NULL; return sdp_data_alloc(SDP_UUID128, &val); } static sdp_data_t *sdp_xml_parse_uuid(const char *data, sdp_record_t *record) { sdp_data_t *ret; char *endptr; uint32_t val; uint16_t val2; int len; len = strlen(data); if (len == 36) { ret = sdp_xml_parse_uuid128(data); goto result; } val = strtoll(data, &endptr, 16); /* Couldn't parse */ if (*endptr != '\0') return NULL; if (val > USHRT_MAX) { ret = sdp_data_alloc(SDP_UUID32, &val); goto result; } val2 = val; ret = sdp_data_alloc(SDP_UUID16, &val2); result: if (record && ret) sdp_pattern_add_uuid(record, &ret->val.uuid); return ret; } static sdp_data_t *sdp_xml_parse_int(const char *data, uint8_t dtd) { char *endptr; sdp_data_t *ret = NULL; switch (dtd) { case SDP_BOOL: { uint8_t val = 0; if (!strcmp("true", data)) val = 1; else if (!strcmp("false", data)) val = 0; else return NULL; ret = sdp_data_alloc(dtd, &val); break; } case SDP_INT8: { int8_t val = strtoul(data, &endptr, 0); /* Failed to parse */ if ((endptr != data) && (*endptr != '\0')) return NULL; ret = sdp_data_alloc(dtd, &val); break; } case SDP_UINT8: { uint8_t val = strtoul(data, &endptr, 0); /* Failed to parse */ if ((endptr != data) && (*endptr != '\0')) return NULL; ret = sdp_data_alloc(dtd, &val); break; } case SDP_INT16: { int16_t val = strtoul(data, &endptr, 0); /* Failed to parse */ if ((endptr != data) && (*endptr != '\0')) return NULL; ret = sdp_data_alloc(dtd, &val); break; } case SDP_UINT16: { uint16_t val = strtoul(data, &endptr, 0); /* Failed to parse */ if ((endptr != data) && (*endptr != '\0')) return NULL; ret = sdp_data_alloc(dtd, &val); break; } case SDP_INT32: { int32_t val = strtoul(data, &endptr, 0); /* Failed to parse */ if ((endptr != data) && (*endptr != '\0')) return NULL; ret = sdp_data_alloc(dtd, &val); break; } case SDP_UINT32: { uint32_t val = strtoul(data, &endptr, 0); /* Failed to parse */ if ((endptr != data) && (*endptr != '\0')) return NULL; ret = sdp_data_alloc(dtd, &val); break; } case SDP_INT64: { int64_t val = strtoull(data, &endptr, 0); /* Failed to parse */ if ((endptr != data) && (*endptr != '\0')) return NULL; ret = sdp_data_alloc(dtd, &val); break; } case SDP_UINT64: { uint64_t val = strtoull(data, &endptr, 0); /* Failed to parse */ if ((endptr != data) && (*endptr != '\0')) return NULL; ret = sdp_data_alloc(dtd, &val); break; } case SDP_INT128: case SDP_UINT128: { uint128_t val; int i = 0; char buf[3]; buf[2] = '\0'; for (; i < 32; i += 2) { buf[0] = data[i]; buf[1] = data[i + 1]; val.data[i >> 1] = strtoul(buf, 0, 16); } ret = sdp_data_alloc(dtd, &val); break; } }; return ret; } static char *sdp_xml_parse_string_decode(const char *data, char encoding, uint32_t *length) { int len = strlen(data); char *text; if (encoding == SDP_XML_ENCODING_NORMAL) { text = strdup(data); *length = len; } else { char buf[3], *decoded; int i; decoded = malloc((len >> 1) + 1); if (!decoded) return NULL; /* Ensure the string is a power of 2 */ len = (len >> 1) << 1; buf[2] = '\0'; for (i = 0; i < len; i += 2) { buf[0] = data[i]; buf[1] = data[i + 1]; decoded[i >> 1] = strtoul(buf, 0, 16); } decoded[len >> 1] = '\0'; text = decoded; *length = len >> 1; } return text; } static sdp_data_t *sdp_xml_parse_url(const char *data) { uint8_t dtd = SDP_URL_STR8; char *url; uint32_t length; sdp_data_t *ret; url = sdp_xml_parse_string_decode(data, SDP_XML_ENCODING_NORMAL, &length); if (!url) return NULL; if (length > UCHAR_MAX) dtd = SDP_URL_STR16; ret = sdp_data_alloc_with_length(dtd, url, length); free(url); return ret; } static sdp_data_t *sdp_xml_parse_text(const char *data, char encoding) { uint8_t dtd = SDP_TEXT_STR8; char *text; uint32_t length; sdp_data_t *ret; text = sdp_xml_parse_string_decode(data, encoding, &length); if (!text) return NULL; if (length > UCHAR_MAX) dtd = SDP_TEXT_STR16; ret = sdp_data_alloc_with_length(dtd, text, length); free(text); return ret; } static sdp_data_t *sdp_xml_parse_nil(const char *data) { return sdp_data_alloc(SDP_DATA_NIL, 0); } static sdp_data_t *sdp_xml_parse_datatype(const char *el, struct sdp_xml_data *elem, sdp_record_t *record) { const char *data = elem->text; if (!strcmp(el, "boolean")) return sdp_xml_parse_int(data, SDP_BOOL); else if (!strcmp(el, "uint8")) return sdp_xml_parse_int(data, SDP_UINT8); else if (!strcmp(el, "uint16")) return sdp_xml_parse_int(data, SDP_UINT16); else if (!strcmp(el, "uint32")) return sdp_xml_parse_int(data, SDP_UINT32); else if (!strcmp(el, "uint64")) return sdp_xml_parse_int(data, SDP_UINT64); else if (!strcmp(el, "uint128")) return sdp_xml_parse_int(data, SDP_UINT128); else if (!strcmp(el, "int8")) return sdp_xml_parse_int(data, SDP_INT8); else if (!strcmp(el, "int16")) return sdp_xml_parse_int(data, SDP_INT16); else if (!strcmp(el, "int32")) return sdp_xml_parse_int(data, SDP_INT32); else if (!strcmp(el, "int64")) return sdp_xml_parse_int(data, SDP_INT64); else if (!strcmp(el, "int128")) return sdp_xml_parse_int(data, SDP_INT128); else if (!strcmp(el, "uuid")) return sdp_xml_parse_uuid(data, record); else if (!strcmp(el, "url")) return sdp_xml_parse_url(data); else if (!strcmp(el, "text")) return sdp_xml_parse_text(data, elem->type); else if (!strcmp(el, "nil")) return sdp_xml_parse_nil(data); return NULL; } static void element_start(GMarkupParseContext *context, const char *element_name, const char **attribute_names, const char **attribute_values, gpointer user_data, GError **err) { struct context_data *ctx_data = user_data; if (!strcmp(element_name, "record")) return; if (!strcmp(element_name, "attribute")) { int i; for (i = 0; attribute_names[i]; i++) { if (!strcmp(attribute_names[i], "id")) { ctx_data->attr_id = strtol(attribute_values[i], 0, 0); break; } } DBG("New attribute 0x%04x", ctx_data->attr_id); return; } if (ctx_data->stack_head) { struct sdp_xml_data *newelem = sdp_xml_data_alloc(); newelem->next = ctx_data->stack_head; ctx_data->stack_head = newelem; } else { ctx_data->stack_head = sdp_xml_data_alloc(); ctx_data->stack_head->next = NULL; } if (!strcmp(element_name, "sequence")) ctx_data->stack_head->data = sdp_data_alloc(SDP_SEQ8, NULL); else if (!strcmp(element_name, "alternate")) ctx_data->stack_head->data = sdp_data_alloc(SDP_ALT8, NULL); else { int i; /* Parse value, name, encoding */ for (i = 0; attribute_names[i]; i++) { if (!strcmp(attribute_names[i], "value")) { int curlen = strlen(ctx_data->stack_head->text); int attrlen = strlen(attribute_values[i]); /* Ensure we're big enough */ while ((curlen + 1 + attrlen) > ctx_data->stack_head->size) sdp_xml_data_expand(ctx_data->stack_head); memcpy(ctx_data->stack_head->text + curlen, attribute_values[i], attrlen); ctx_data->stack_head->text[curlen + attrlen] = '\0'; } if (!strcmp(attribute_names[i], "encoding")) { if (!strcmp(attribute_values[i], "hex")) ctx_data->stack_head->type = 1; } if (!strcmp(attribute_names[i], "name")) ctx_data->stack_head->name = strdup(attribute_values[i]); } ctx_data->stack_head->data = sdp_xml_parse_datatype(element_name, ctx_data->stack_head, ctx_data->record); if (ctx_data->stack_head->data == NULL) error("Can't parse element %s", element_name); } } static void sdp_xml_data_free(struct sdp_xml_data *elem) { if (elem->data) sdp_data_free(elem->data); free(elem->name); free(elem->text); free(elem); } static void element_end(GMarkupParseContext *context, const char *element_name, gpointer user_data, GError **err) { struct context_data *ctx_data = user_data; struct sdp_xml_data *elem; if (!strcmp(element_name, "record")) return; if (!strcmp(element_name, "attribute")) { if (ctx_data->stack_head && ctx_data->stack_head->data) { int ret = sdp_attr_add(ctx_data->record, ctx_data->attr_id, ctx_data->stack_head->data); if (ret == -1) DBG("Could not add attribute 0x%04x", ctx_data->attr_id); ctx_data->stack_head->data = NULL; sdp_xml_data_free(ctx_data->stack_head); ctx_data->stack_head = NULL; } else { DBG("No data for attribute 0x%04x", ctx_data->attr_id); } return; } if (!ctx_data->stack_head) return; if (!ctx_data->stack_head->data) { DBG("No data for %s", element_name); elem = ctx_data->stack_head; ctx_data->stack_head = ctx_data->stack_head->next; sdp_xml_data_free(elem); return; } if (!strcmp(element_name, "sequence")) { ctx_data->stack_head->data->unitSize = compute_seq_size(ctx_data->stack_head->data); if (ctx_data->stack_head->data->unitSize > USHRT_MAX) { ctx_data->stack_head->data->unitSize += sizeof(uint32_t); ctx_data->stack_head->data->dtd = SDP_SEQ32; } else if (ctx_data->stack_head->data->unitSize > UCHAR_MAX) { ctx_data->stack_head->data->unitSize += sizeof(uint16_t); ctx_data->stack_head->data->dtd = SDP_SEQ16; } else { ctx_data->stack_head->data->unitSize += sizeof(uint8_t); } } else if (!strcmp(element_name, "alternate")) { ctx_data->stack_head->data->unitSize = compute_seq_size(ctx_data->stack_head->data); if (ctx_data->stack_head->data->unitSize > USHRT_MAX) { ctx_data->stack_head->data->unitSize += sizeof(uint32_t); ctx_data->stack_head->data->dtd = SDP_ALT32; } else if (ctx_data->stack_head->data->unitSize > UCHAR_MAX) { ctx_data->stack_head->data->unitSize += sizeof(uint16_t); ctx_data->stack_head->data->dtd = SDP_ALT16; } else { ctx_data->stack_head->data->unitSize += sizeof(uint8_t); } } if (ctx_data->stack_head->next && ctx_data->stack_head->data && ctx_data->stack_head->next->data) { switch (ctx_data->stack_head->next->data->dtd) { case SDP_SEQ8: case SDP_SEQ16: case SDP_SEQ32: case SDP_ALT8: case SDP_ALT16: case SDP_ALT32: ctx_data->stack_head->next->data->val.dataseq = sdp_seq_append(ctx_data->stack_head->next->data->val.dataseq, ctx_data->stack_head->data); ctx_data->stack_head->data = NULL; break; } elem = ctx_data->stack_head; ctx_data->stack_head = ctx_data->stack_head->next; sdp_xml_data_free(elem); } } static GMarkupParser parser = { element_start, element_end, NULL, NULL, NULL }; sdp_record_t *sdp_xml_parse_record(const char *data, int size) { GMarkupParseContext *ctx; struct context_data *ctx_data; sdp_record_t *record; ctx_data = malloc(sizeof(*ctx_data)); if (!ctx_data) return NULL; record = sdp_record_alloc(); if (!record) { free(ctx_data); return NULL; } memset(ctx_data, 0, sizeof(*ctx_data)); ctx_data->record = record; ctx = g_markup_parse_context_new(&parser, 0, ctx_data, NULL); if (g_markup_parse_context_parse(ctx, data, size, NULL) == FALSE) { error("XML parsing error"); g_markup_parse_context_free(ctx); sdp_record_free(record); free(ctx_data); return NULL; } g_markup_parse_context_free(ctx); free(ctx_data); return record; } static void convert_raw_data_to_xml(sdp_data_t *value, int indent_level, void *data, void (*appender)(void *, const char *)) { int i, hex; char buf[STRBUFSIZE]; char indent[MAXINDENT]; if (!value) return; if (indent_level >= MAXINDENT) indent_level = MAXINDENT - 2; for (i = 0; i < indent_level; i++) indent[i] = '\t'; indent[i] = '\0'; buf[STRBUFSIZE - 1] = '\0'; switch (value->dtd) { case SDP_DATA_NIL: appender(data, indent); appender(data, "\n"); break; case SDP_BOOL: appender(data, indent); appender(data, "val.uint8 ? "true" : "false"); appender(data, "\" />\n"); break; case SDP_UINT8: appender(data, indent); appender(data, "val.uint8); appender(data, buf); appender(data, "\" />\n"); break; case SDP_UINT16: appender(data, indent); appender(data, "val.uint16); appender(data, buf); appender(data, "\" />\n"); break; case SDP_UINT32: appender(data, indent); appender(data, "val.uint32); appender(data, buf); appender(data, "\" />\n"); break; case SDP_UINT64: appender(data, indent); appender(data, "val.uint64); appender(data, buf); appender(data, "\" />\n"); break; case SDP_UINT128: appender(data, indent); appender(data, "val.uint128.data[i]); } appender(data, buf); appender(data, "\" />\n"); break; case SDP_INT8: appender(data, indent); appender(data, "val.int8); appender(data, buf); appender(data, "\" />\n"); break; case SDP_INT16: appender(data, indent); appender(data, "val.int16); appender(data, buf); appender(data, "\" />\n"); break; case SDP_INT32: appender(data, indent); appender(data, "val.int32); appender(data, buf); appender(data, "\" />\n"); break; case SDP_INT64: appender(data, indent); appender(data, "val.int64); appender(data, buf); appender(data, "\" />\n"); break; case SDP_INT128: appender(data, indent); appender(data, "val.int128.data[i]); } appender(data, buf); appender(data, "\" />\n"); break; case SDP_UUID16: appender(data, indent); appender(data, "val.uuid.value.uuid16); appender(data, buf); appender(data, "\" />\n"); break; case SDP_UUID32: appender(data, indent); appender(data, "val.uuid.value.uuid32); appender(data, buf); appender(data, "\" />\n"); break; case SDP_UUID128: appender(data, indent); appender(data, "val.uuid.value. uuid128.data[0], (unsigned char) value->val.uuid.value. uuid128.data[1], (unsigned char) value->val.uuid.value. uuid128.data[2], (unsigned char) value->val.uuid.value. uuid128.data[3], (unsigned char) value->val.uuid.value. uuid128.data[4], (unsigned char) value->val.uuid.value. uuid128.data[5], (unsigned char) value->val.uuid.value. uuid128.data[6], (unsigned char) value->val.uuid.value. uuid128.data[7], (unsigned char) value->val.uuid.value. uuid128.data[8], (unsigned char) value->val.uuid.value. uuid128.data[9], (unsigned char) value->val.uuid.value. uuid128.data[10], (unsigned char) value->val.uuid.value. uuid128.data[11], (unsigned char) value->val.uuid.value. uuid128.data[12], (unsigned char) value->val.uuid.value. uuid128.data[13], (unsigned char) value->val.uuid.value. uuid128.data[14], (unsigned char) value->val.uuid.value. uuid128.data[15]); appender(data, buf); appender(data, "\" />\n"); break; case SDP_TEXT_STR8: case SDP_TEXT_STR16: case SDP_TEXT_STR32: { int num_chars_to_escape = 0; int length = value->unitSize - 1; char *strBuf; hex = 0; for (i = 0; i < length; i++) { if (!isprint(value->val.str[i]) && value->val.str[i] != '\0') { hex = 1; break; } /* XML is evil, must do this... */ if ((value->val.str[i] == '<') || (value->val.str[i] == '>') || (value->val.str[i] == '"') || (value->val.str[i] == '&')) num_chars_to_escape++; } appender(data, indent); appender(data, "unitSize-1) * 2 + 1)); if (!strBuf) { DBG("No memory to convert raw data to xml"); return; } /* Unit Size seems to include the size for dtd It is thus off by 1 This is safe for Normal strings, but not hex encoded data */ for (i = 0; i < (value->unitSize-1); i++) sprintf(&strBuf[i*sizeof(char)*2], "%02x", (unsigned char) value->val.str[i]); strBuf[(value->unitSize-1) * 2] = '\0'; } else { int j; /* escape the XML disallowed chars */ strBuf = malloc(sizeof(char) * (value->unitSize + 1 + num_chars_to_escape * 4)); if (!strBuf) { DBG("No memory to convert raw data to xml"); return; } for (i = 0, j = 0; i < length; i++) { if (value->val.str[i] == '&') { strBuf[j++] = '&'; strBuf[j++] = 'a'; strBuf[j++] = 'm'; strBuf[j++] = 'p'; } else if (value->val.str[i] == '<') { strBuf[j++] = '&'; strBuf[j++] = 'l'; strBuf[j++] = 't'; } else if (value->val.str[i] == '>') { strBuf[j++] = '&'; strBuf[j++] = 'g'; strBuf[j++] = 't'; } else if (value->val.str[i] == '"') { strBuf[j++] = '&'; strBuf[j++] = 'q'; strBuf[j++] = 'u'; strBuf[j++] = 'o'; strBuf[j++] = 't'; } else if (value->val.str[i] == '\0') { strBuf[j++] = ' '; } else { strBuf[j++] = value->val.str[i]; } } strBuf[j] = '\0'; } appender(data, "value=\""); appender(data, strBuf); appender(data, "\" />\n"); free(strBuf); break; } case SDP_URL_STR8: case SDP_URL_STR16: case SDP_URL_STR32: { char *strBuf; appender(data, indent); appender(data, "val.str, value->unitSize - 1); appender(data, strBuf); free(strBuf); appender(data, "\" />\n"); break; } case SDP_SEQ8: case SDP_SEQ16: case SDP_SEQ32: appender(data, indent); appender(data, "\n"); convert_raw_data_to_xml(value->val.dataseq, indent_level + 1, data, appender); appender(data, indent); appender(data, "\n"); break; case SDP_ALT8: case SDP_ALT16: case SDP_ALT32: appender(data, indent); appender(data, "\n"); convert_raw_data_to_xml(value->val.dataseq, indent_level + 1, data, appender); appender(data, indent); appender(data, "\n"); break; } convert_raw_data_to_xml(value->next, indent_level, data, appender); } struct conversion_data { void *data; void (*appender)(void *data, const char *); }; static void convert_raw_attr_to_xml_func(void *val, void *data) { struct conversion_data *cd = data; sdp_data_t *value = val; char buf[STRBUFSIZE]; buf[STRBUFSIZE - 1] = '\0'; snprintf(buf, STRBUFSIZE - 1, "\t\n", value->attrId); cd->appender(cd->data, buf); convert_raw_data_to_xml(value, 2, cd->data, cd->appender); cd->appender(cd->data, "\t\n"); } /* * Will convert the sdp record to XML. The appender and data can be used * to control where to output the record (e.g. file or a data buffer). The * appender will be called repeatedly with data and the character buffer * (containing parts of the generated XML) to append. */ void convert_sdp_record_to_xml(sdp_record_t *rec, void *data, void (*appender)(void *, const char *)) { struct conversion_data cd; cd.data = data; cd.appender = appender; if (rec && rec->attrlist) { appender(data, "\n\n"); appender(data, "\n"); sdp_list_foreach(rec->attrlist, convert_raw_attr_to_xml_func, &cd); appender(data, "\n"); } } bluez-5.87/src/PaxHeaders/sdpd.h0000644000000000000000000000005014165411566013556 xustar0020 atime=1783093630 20 ctime=1783094106 bluez-5.87/src/sdpd.h0000644000000000000000000000405414165411566013242 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2001-2002 Nokia Corporation * Copyright (C) 2002-2003 Maxim Krasnyansky * Copyright (C) 2002-2010 Marcel Holtmann * Copyright (C) 2002-2003 Stephen Crane * * */ #ifdef SDP_DEBUG #include #define SDPDBG(fmt, arg...) syslog(LOG_DEBUG, "%s: " fmt "\n", __func__ , ## arg) #else #define SDPDBG(fmt...) #endif typedef struct request { bdaddr_t device; bdaddr_t bdaddr; int local; int sock; int mtu; int flags; uint8_t *buf; int len; uint8_t opcode; } sdp_req_t; void sdp_cstate_cleanup(int sock); void handle_internal_request(int sk, int mtu, void *data, int len); void handle_request(int sk, uint8_t *data, int len); void set_fixed_db_timestamp(uint32_t dbts); int service_register_req(sdp_req_t *req, sdp_buf_t *rsp); int service_update_req(sdp_req_t *req, sdp_buf_t *rsp); int service_remove_req(sdp_req_t *req, sdp_buf_t *rsp); void register_public_browse_group(void); void register_server_service(void); void register_device_id(uint16_t source, uint16_t vendor, uint16_t product, uint16_t version); void register_mps(bool mpmd); int record_sort(const void *r1, const void *r2); void sdp_svcdb_reset(void); void sdp_svcdb_collect_all(int sock); void sdp_svcdb_set_collectable(sdp_record_t *rec, int sock); void sdp_svcdb_collect(sdp_record_t *rec); sdp_record_t *sdp_record_find(uint32_t handle); void sdp_record_add(const bdaddr_t *device, sdp_record_t *rec); int sdp_record_remove(uint32_t handle); sdp_list_t *sdp_get_record_list(void); int sdp_check_access(uint32_t handle, bdaddr_t *device); uint32_t sdp_next_handle(void); uint32_t sdp_get_time(void); #define SDP_SERVER_COMPAT (1 << 0) #define SDP_SERVER_CENTRAL (1 << 1) int start_sdp_server(uint16_t mtu, uint32_t flags); void stop_sdp_server(void); int add_record_to_server(const bdaddr_t *src, sdp_record_t *rec); int remove_record_from_server(uint32_t handle); bluez-5.87/src/PaxHeaders/gatt-database.h0000644000000000000000000000005015110075565015321 xustar0020 atime=1783093610 20 ctime=1783094106 bluez-5.87/src/gatt-database.h0000644000000000000000000000154315110075565015005 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Google Inc. * * */ struct btd_gatt_database; struct btd_gatt_database *btd_gatt_database_new(struct btd_adapter *adapter); void btd_gatt_database_destroy(struct btd_gatt_database *database); struct btd_gatt_database *btd_gatt_database_get(struct gatt_db *db); struct gatt_db *btd_gatt_database_get_db(struct btd_gatt_database *database); struct btd_adapter * btd_gatt_database_get_adapter(struct btd_gatt_database *database); void btd_gatt_database_att_disconnected(struct btd_gatt_database *database, struct btd_device *device); void btd_gatt_database_server_connected(struct btd_gatt_database *database, struct bt_gatt_server *server); void btd_gatt_database_restore_svc_chng_ccc(struct btd_gatt_database *database); bluez-5.87/src/PaxHeaders/bluetooth.ver0000644000000000000000000000005014447506754015205 xustar0020 atime=1770587292 20 ctime=1783094106 bluez-5.87/src/bluetooth.ver0000644000000000000000000000032614447506754014667 0ustar00rootroot{ global: btd_*; g_dbus_*; info; error; debug; baswap; ba2str; /* Don't break LLVM sanitizers */ __asan*; __dfsan*; __lsan*; __msan*; __sanitizer*; __tsan*; __ubsan*; local: *; }; bluez-5.87/src/PaxHeaders/textfile.h0000644000000000000000000000005014267331527014451 xustar0020 atime=1783093123 20 ctime=1783094104 bluez-5.87/src/textfile.h0000644000000000000000000000137514267331527014140 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ int create_filename(char *str, size_t size, const char *fmt, ...) __attribute__((format(printf, 3, 4))); int create_file(const char *filename, const mode_t mode); int create_name(char *buf, size_t size, const char *address, const char *name); int textfile_put(const char *pathname, const char *key, const char *value); int textfile_del(const char *pathname, const char *key); char *textfile_get(const char *pathname, const char *key); typedef void (*textfile_cb) (char *key, char *value, void *data); int textfile_foreach(const char *pathname, textfile_cb func, void *data); bluez-5.87/src/PaxHeaders/bluetooth.conf0000644000000000000000000000005014015011623015307 xustar0020 atime=1770587295 20 ctime=1783094110 bluez-5.87/src/bluetooth.conf0000644000000000000000000000223614015011623014773 0ustar00rootroot bluez-5.87/src/PaxHeaders/settings.c0000644000000000000000000000005015063045254014452 xustar0020 atime=1783093153 20 ctime=1783094104 bluez-5.87/src/settings.c0000644000000000000000000003150615063045254014140 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "log.h" #include "src/shared/queue.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "settings.h" #define GATT_PRIM_SVC_UUID_STR "2800" #define GATT_SND_SVC_UUID_STR "2801" #define GATT_INCLUDE_UUID_STR "2802" #define GATT_CHARAC_UUID_STR "2803" static ssize_t str2val(const char *str, uint8_t *val, size_t len) { const char *pos = str; size_t i; for (i = 0; i < len; i++) { if (sscanf(pos, "%2hhx", &val[i]) != 1) break; pos += 2; } return i; } static void load_desc_value(struct gatt_db_attribute *attrib, int err, void *user_data) { } static int load_desc(struct gatt_db *db, char *handle, char *value, struct gatt_db_attribute *service) { char uuid_str[MAX_LEN_UUID_STR]; struct gatt_db_attribute *att; uint16_t handle_int; uint16_t val; bt_uuid_t uuid, ext_uuid; if (sscanf(handle, "%04hx", &handle_int) != 1) { DBG("Failed to parse handle: %s", handle); return -EIO; } /* Check if there is any value stored, otherwise it is just the UUID */ if (sscanf(value, "%04hx:%36s", &val, uuid_str) != 2) { if (sscanf(value, "%36s", uuid_str) != 1) { DBG("Failed to parse value: %s", value); return -EIO; } val = 0; } DBG("loading descriptor handle: 0x%04x, value: 0x%04x, value uuid: %s", handle_int, val, uuid_str); bt_string_to_uuid(&uuid, uuid_str); bt_uuid16_create(&ext_uuid, GATT_CHARAC_EXT_PROPER_UUID); /* If it is CEP then it must contain the value */ if (!bt_uuid_cmp(&uuid, &ext_uuid) && !val) return -EIO; att = gatt_db_service_insert_descriptor(service, handle_int, &uuid, 0, NULL, NULL, NULL); if (!att || gatt_db_attribute_get_handle(att) != handle_int) return -EIO; if (val) { if (!gatt_db_attribute_write(att, 0, (uint8_t *)&val, sizeof(val), 0, NULL, load_desc_value, NULL)) return -EIO; } return 0; } static int load_chrc(struct gatt_db *db, char *handle, char *value, struct gatt_db_attribute *service) { uint16_t properties, value_handle, handle_int; char uuid_str[MAX_LEN_UUID_STR]; struct gatt_db_attribute *att; char val_str[33]; uint8_t val[16]; size_t val_len; bt_uuid_t uuid; if (sscanf(handle, "%04hx", &handle_int) != 1) { DBG("Failed to parse handle: %s", handle); return -EIO; } /* Check if there is any value stored */ if (sscanf(value, GATT_CHARAC_UUID_STR ":%04hx:%02hx:%32s:%36s", &value_handle, &properties, val_str, uuid_str) != 4) { if (sscanf(value, GATT_CHARAC_UUID_STR ":%04hx:%02hx:%36s", &value_handle, &properties, uuid_str) != 3) return -EIO; val_len = 0; } else val_len = str2val(val_str, val, sizeof(val)); bt_string_to_uuid(&uuid, uuid_str); /* Log debug message. */ DBG("loading characteristic handle: 0x%04x, value handle: 0x%04x, " "properties 0x%04x value: %s uuid: %s", handle_int, value_handle, properties, val_len ? val_str : "", uuid_str); att = gatt_db_service_insert_characteristic(service, handle_int, value_handle, &uuid, 0, properties, NULL, NULL, NULL); if (!att || gatt_db_attribute_get_handle(att) != value_handle) return -EIO; if (val_len) { if (!gatt_db_attribute_write(att, 0, val, val_len, 0, NULL, load_desc_value, NULL)) return -EIO; } return 0; } static int load_incl(struct gatt_db *db, char *handle, char *value, struct gatt_db_attribute *service) { char uuid_str[MAX_LEN_UUID_STR]; struct gatt_db_attribute *att; uint16_t start, end; if (sscanf(handle, "%04hx", &start) != 1) { DBG("Failed to parse handle: %s", handle); return -EIO; } if (sscanf(value, GATT_INCLUDE_UUID_STR ":%04hx:%04hx:%36s", &start, &end, uuid_str) != 3) { DBG("Failed to parse value: %s", value); return -EIO; } /* Log debug message. */ DBG("loading included service: 0x%04x, end: 0x%04x, uuid: %s", start, end, uuid_str); att = gatt_db_get_attribute(db, start); if (!att) return -EIO; att = gatt_db_service_add_included(service, att); if (!att) return -EIO; return 0; } static int load_service(struct gatt_db *db, char *handle, char *value) { struct gatt_db_attribute *att; uint16_t start, end; char type[MAX_LEN_UUID_STR], uuid_str[MAX_LEN_UUID_STR]; bt_uuid_t uuid; bool primary; if (sscanf(handle, "%04hx", &start) != 1) { DBG("Failed to parse handle: %s", handle); return -EIO; } if (sscanf(value, "%36[^:]:%04hx:%36s", type, &end, uuid_str) != 3) { DBG("Failed to parse value: %s", value); return -EIO; } if (g_str_equal(type, GATT_PRIM_SVC_UUID_STR)) primary = true; else if (g_str_equal(type, GATT_SND_SVC_UUID_STR)) primary = false; else return -EIO; bt_string_to_uuid(&uuid, uuid_str); /* Log debug message. */ DBG("loading service: 0x%04x, end: 0x%04x, uuid: %s", start, end, uuid_str); att = gatt_db_insert_service(db, start, &uuid, primary, end - start + 1); if (!att) { DBG("Unable load service into db!"); return -EIO; } return 0; } static int gatt_db_load(struct gatt_db *db, GKeyFile *key_file, char **keys) { struct gatt_db_attribute *current_service; char **handle, *value, type[MAX_LEN_UUID_STR]; int ret; /* first load service definitions */ for (handle = keys; *handle; handle++) { value = g_key_file_get_string(key_file, "Attributes", *handle, NULL); if (!value || sscanf(value, "%36[^:]:", type) != 1) { g_free(value); return -EIO; } if (g_str_equal(type, GATT_PRIM_SVC_UUID_STR) || g_str_equal(type, GATT_SND_SVC_UUID_STR)) { ret = load_service(db, *handle, value); if (ret) { g_free(value); return ret; } } g_free(value); } current_service = NULL; /* then fill them with data*/ for (handle = keys; *handle; handle++) { value = g_key_file_get_string(key_file, "Attributes", *handle, NULL); if (!value || sscanf(value, "%36[^:]:", type) != 1) { g_free(value); return -EIO; } if (g_str_equal(type, GATT_PRIM_SVC_UUID_STR) || g_str_equal(type, GATT_SND_SVC_UUID_STR)) { uint16_t tmp; uint16_t start, end; bool primary; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; if (sscanf(*handle, "%04hx", &tmp) != 1) { g_free(value); return -EIO; } if (current_service) gatt_db_service_set_active(current_service, true); current_service = gatt_db_get_attribute(db, tmp); gatt_db_attribute_get_service_data(current_service, &start, &end, &primary, &uuid); bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); ret = 0; } else if (g_str_equal(type, GATT_INCLUDE_UUID_STR)) { ret = load_incl(db, *handle, value, current_service); } else if (g_str_equal(type, GATT_CHARAC_UUID_STR)) { ret = load_chrc(db, *handle, value, current_service); } else { ret = load_desc(db, *handle, value, current_service); } g_free(value); if (ret) { gatt_db_clear(db); return ret; } } if (current_service) gatt_db_service_set_active(current_service, true); return 0; } int btd_settings_gatt_db_load(struct gatt_db *db, const char *filename) { char **keys; GKeyFile *key_file; GError *gerr = NULL; int err; key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { DBG("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } keys = g_key_file_get_keys(key_file, "Attributes", NULL, NULL); if (!keys) { g_key_file_free(key_file); return -ENOENT; } err = gatt_db_load(db, key_file, keys); g_strfreev(keys); g_key_file_free(key_file); return err; } struct gatt_saver { struct gatt_db *db; uint16_t ext_props; GKeyFile *key_file; }; static void db_hash_read_value_cb(struct gatt_db_attribute *attrib, int err, const uint8_t *value, size_t length, void *user_data) { const uint8_t **hash = user_data; if (err || (length != 16)) return; *hash = value; } static void store_desc(struct gatt_db_attribute *attr, void *user_data) { struct gatt_saver *saver = user_data; GKeyFile *key_file = saver->key_file; char handle[6], value[100], uuid_str[MAX_LEN_UUID_STR]; const bt_uuid_t *uuid; bt_uuid_t ext_uuid; uint16_t handle_num; handle_num = gatt_db_attribute_get_handle(attr); sprintf(handle, "%04hx", handle_num); uuid = gatt_db_attribute_get_type(attr); bt_uuid_to_string(uuid, uuid_str, sizeof(uuid_str)); bt_uuid16_create(&ext_uuid, GATT_CHARAC_EXT_PROPER_UUID); if (!bt_uuid_cmp(uuid, &ext_uuid) && saver->ext_props) sprintf(value, "%04hx:%s", saver->ext_props, uuid_str); else sprintf(value, "%s", uuid_str); g_key_file_set_string(key_file, "Attributes", handle, value); } static void store_chrc(struct gatt_db_attribute *attr, void *user_data) { struct gatt_saver *saver = user_data; GKeyFile *key_file = saver->key_file; char handle[6], value[100], uuid_str[MAX_LEN_UUID_STR]; uint16_t handle_num, value_handle; uint8_t properties; bt_uuid_t uuid, hash_uuid; if (!gatt_db_attribute_get_char_data(attr, &handle_num, &value_handle, &properties, &saver->ext_props, &uuid)) { DBG("Unable to locate Characteristic data"); return; } sprintf(handle, "%04hx", handle_num); bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); /* Store Database Hash value if available */ bt_uuid16_create(&hash_uuid, GATT_CHARAC_DB_HASH); if (!bt_uuid_cmp(&uuid, &hash_uuid)) { const uint8_t *hash = NULL; attr = gatt_db_get_attribute(saver->db, value_handle); gatt_db_attribute_read(attr, 0, BT_ATT_OP_READ_REQ, NULL, db_hash_read_value_cb, &hash); if (hash) sprintf(value, GATT_CHARAC_UUID_STR ":%04hx:%02hhx:" "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx" "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx" "%02hhx%02hhx:%s", value_handle, properties, hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15], uuid_str); else sprintf(value, GATT_CHARAC_UUID_STR ":%04hx:%02hhx:%s", value_handle, properties, uuid_str); } else sprintf(value, GATT_CHARAC_UUID_STR ":%04hx:%02hhx:%s", value_handle, properties, uuid_str); g_key_file_set_string(key_file, "Attributes", handle, value); gatt_db_service_foreach_desc(attr, store_desc, saver); } static void store_incl(struct gatt_db_attribute *attr, void *user_data) { struct gatt_saver *saver = user_data; GKeyFile *key_file = saver->key_file; struct gatt_db_attribute *service; char handle[6], value[100], uuid_str[MAX_LEN_UUID_STR]; uint16_t handle_num, start, end; bt_uuid_t uuid; if (!gatt_db_attribute_get_incl_data(attr, &handle_num, &start, &end)) { DBG("Unable to locate Included data"); return; } service = gatt_db_get_attribute(saver->db, start); if (!service) { DBG("Unable to locate Included Service"); return; } sprintf(handle, "%04hx", handle_num); gatt_db_attribute_get_service_uuid(service, &uuid); bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); sprintf(value, GATT_INCLUDE_UUID_STR ":%04hx:%04hx:%s", start, end, uuid_str); g_key_file_set_string(key_file, "Attributes", handle, value); } static void store_service(struct gatt_db_attribute *attr, void *user_data) { struct gatt_saver *saver = user_data; GKeyFile *key_file = saver->key_file; char uuid_str[MAX_LEN_UUID_STR], handle[6], value[256]; uint16_t start, end; bt_uuid_t uuid; bool primary; char *type; if (!gatt_db_attribute_get_service_data(attr, &start, &end, &primary, &uuid)) { DBG("Unable to locate Service data"); return; } sprintf(handle, "%04hx", start); bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); if (primary) type = GATT_PRIM_SVC_UUID_STR; else type = GATT_SND_SVC_UUID_STR; sprintf(value, "%s:%04hx:%s", type, end, uuid_str); g_key_file_set_string(key_file, "Attributes", handle, value); gatt_db_service_foreach_incl(attr, store_incl, saver); gatt_db_service_foreach_char(attr, store_chrc, saver); } void btd_settings_gatt_db_store(struct gatt_db *db, const char *filename) { GKeyFile *key_file; GError *gerr = NULL; char *data; gsize length = 0; struct gatt_saver saver; key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { DBG("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } /* Remove current attributes since it might have changed */ g_key_file_remove_group(key_file, "Attributes", NULL); saver.key_file = key_file; saver.db = db; gatt_db_foreach_service(db, NULL, store_service, &saver); data = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, data, length, &gerr)) { DBG("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(data); g_key_file_free(key_file); } bluez-5.87/src/PaxHeaders/advertising.h0000644000000000000000000000005014015011623015123 xustar0020 atime=1783093759 20 ctime=1783094106 bluez-5.87/src/advertising.h0000644000000000000000000000064614015011623014612 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Google Inc. * * */ struct btd_adapter; struct btd_adv_manager; struct btd_adv_manager *btd_adv_manager_new(struct btd_adapter *adapter, struct mgmt *mgmt); void btd_adv_manager_destroy(struct btd_adv_manager *manager); void btd_adv_manager_refresh(struct btd_adv_manager *manager); bluez-5.87/src/PaxHeaders/backtrace.h0000644000000000000000000000005014015011623014523 xustar0020 atime=1783093736 20 ctime=1783094106 bluez-5.87/src/backtrace.h0000644000000000000000000000077414015011623014214 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #include void btd_backtrace_init(void); void btd_backtrace(uint16_t index); void btd_assertion_message_expr(const char *file, int line, const char *func, const char *expr); #define btd_assert(expr) do { \ if (expr) ; else \ btd_assertion_message_expr(__FILE__, __LINE__, __func__, #expr); \ } while (0) bluez-5.87/src/PaxHeaders/error.h0000644000000000000000000000005015063045254013750 xustar0020 atime=1783093359 20 ctime=1783094106 bluez-5.87/src/error.h0000644000000000000000000001036615063045254013437 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright (C) 2007-2008 Fabien Chevalier * * */ #include #include #define ERROR_INTERFACE "org.bluez.Error" #define ERROR_INTERFACE_BREDR "org.bluez.Error.BREDR" /* BR/EDR connection failure reasons */ #define ERR_BREDR_CONN_ALREADY_CONNECTED "br-connection-already-"\ "connected" #define ERR_BREDR_CONN_PAGE_TIMEOUT "br-connection-page-timeout" #define ERR_BREDR_CONN_SDP_SEARCH "br-connection-sdp-search" #define ERR_BREDR_CONN_CREATE_SOCKET "br-connection-create-socket" #define ERR_BREDR_CONN_INVALID_ARGUMENTS "br-connection-invalid-"\ "argument" #define ERR_BREDR_CONN_ADAPTER_NOT_POWERED "br-connection-adapter-not-"\ "powered" #define ERR_BREDR_CONN_NOT_SUPPORTED "br-connection-not-supported" #define ERR_BREDR_CONN_BAD_SOCKET "br-connection-bad-socket" #define ERR_BREDR_CONN_MEMORY_ALLOC "br-connection-memory-"\ "allocation" #define ERR_BREDR_CONN_BUSY "br-connection-busy" #define ERR_BREDR_CONN_CNCR_CONNECT_LIMIT "br-connection-concurrent-"\ "connection-limit" #define ERR_BREDR_CONN_TIMEOUT "br-connection-timeout" #define ERR_BREDR_CONN_REFUSED "br-connection-refused" #define ERR_BREDR_CONN_ABORT_BY_REMOTE "br-connection-aborted-by-"\ "remote" #define ERR_BREDR_CONN_ABORT_BY_LOCAL "br-connection-aborted-by-"\ "local" #define ERR_BREDR_CONN_LMP_PROTO_ERROR "br-connection-lmp-protocol-"\ "error" #define ERR_BREDR_CONN_CANCELED "br-connection-canceled" #define ERR_BREDR_CONN_KEY_MISSING "br-connection-key-missing" #define ERR_BREDR_CONN_UNKNOWN "br-connection-unknown" /* LE connection failure reasons */ #define ERR_LE_CONN_INVALID_ARGUMENTS "le-connection-invalid-arguments" #define ERR_LE_CONN_ADAPTER_NOT_POWERED "le-connection-adapter-not-powered" #define ERR_LE_CONN_NOT_SUPPORTED "le-connection-not-supported" #define ERR_LE_CONN_ALREADY_CONNECTED "le-connection-already-connected" #define ERR_LE_CONN_BAD_SOCKET "le-connection-bad-socket" #define ERR_LE_CONN_MEMORY_ALLOC "le-connection-memory-allocation" #define ERR_LE_CONN_BUSY "le-connection-busy" #define ERR_LE_CONN_REFUSED "le-connection-refused" #define ERR_LE_CONN_CREATE_SOCKET "le-connection-create-socket" #define ERR_LE_CONN_TIMEOUT "le-connection-timeout" #define ERR_LE_CONN_SYNC_CONNECT_LIMIT "le-connection-concurrent-connection-"\ "limit" #define ERR_LE_CONN_ABORT_BY_REMOTE "le-connection-abort-by-remote" #define ERR_LE_CONN_ABORT_BY_LOCAL "le-connection-abort-by-local" #define ERR_LE_CONN_LL_PROTO_ERROR "le-connection-link-layer-protocol-"\ "error" #define ERR_LE_CONN_GATT_BROWSE "le-connection-gatt-browsing" #define ERR_LE_CONN_KEY_MISSING "le-connection-key-missing" #define ERR_LE_CONN_UNKNOWN "le-connection-unknown" DBusMessage *btd_error_invalid_args(DBusMessage *msg); DBusMessage *btd_error_invalid_args_str(DBusMessage *msg, const char *str); DBusMessage *btd_error_busy(DBusMessage *msg); DBusMessage *btd_error_already_exists(DBusMessage *msg); DBusMessage *btd_error_not_supported(DBusMessage *msg); DBusMessage *btd_error_not_connected(DBusMessage *msg); DBusMessage *btd_error_already_connected(DBusMessage *msg); DBusMessage *btd_error_not_available(DBusMessage *msg); DBusMessage *btd_error_not_available_str(DBusMessage *msg, const char *str); DBusMessage *btd_error_in_progress(DBusMessage *msg); DBusMessage *btd_error_in_progress_str(DBusMessage *msg, const char *str); DBusMessage *btd_error_does_not_exist(DBusMessage *msg); DBusMessage *btd_error_not_authorized(DBusMessage *msg); DBusMessage *btd_error_not_permitted(DBusMessage *msg, const char *str); DBusMessage *btd_error_no_such_adapter(DBusMessage *msg); DBusMessage *btd_error_agent_not_available(DBusMessage *msg); DBusMessage *btd_error_not_ready(DBusMessage *msg); DBusMessage *btd_error_not_ready_str(DBusMessage *msg, const char *str); DBusMessage *btd_error_profile_unavailable(DBusMessage *msg); DBusMessage *btd_error_failed(DBusMessage *msg, const char *str); DBusMessage *btd_error_bredr_errno(DBusMessage *msg, int err); DBusMessage *btd_error_le_errno(DBusMessage *msg, int err); bluez-5.87/src/PaxHeaders/agent.h0000644000000000000000000000005015200607060013705 xustar0020 atime=1783092683 20 ctime=1783094106 bluez-5.87/src/agent.h0000644000000000000000000000361715200607060013375 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #include "shared/mgmt.h" struct agent; typedef void (*agent_cb) (struct agent *agent, DBusError *err, void *user_data); typedef void (*agent_pincode_cb) (struct agent *agent, DBusError *err, const char *pincode, void *user_data); typedef void (*agent_passkey_cb) (struct agent *agent, DBusError *err, uint32_t passkey, void *user_data); struct agent *agent_ref(struct agent *agent); void agent_unref(struct agent *agent); struct agent *agent_get(const char *owner); int agent_authorize_service(struct agent *agent, struct btd_device *device, const char *uuid, agent_cb cb, void *user_data, GDestroyNotify destroy); int agent_request_pincode(struct agent *agent, struct btd_device *device, agent_pincode_cb cb, gboolean secure, void *user_data, GDestroyNotify destroy); int agent_request_passkey(struct agent *agent, struct btd_device *device, agent_passkey_cb cb, void *user_data, GDestroyNotify destroy); int agent_request_confirmation(struct agent *agent, struct btd_device *device, uint32_t passkey, agent_cb cb, void *user_data, GDestroyNotify destroy); int agent_request_authorization(struct agent *agent, struct btd_device *device, agent_cb cb, void *user_data, GDestroyNotify destroy); int agent_display_passkey(struct agent *agent, struct btd_device *device, uint32_t passkey, uint16_t entered); int agent_display_pincode(struct agent *agent, struct btd_device *device, const char *pincode, agent_cb cb, void *user_data, GDestroyNotify destroy); int agent_cancel(struct agent *agent); enum mgmt_io_capability agent_get_io_capability(struct agent *agent); void btd_agent_init(void); void btd_agent_cleanup(void); bluez-5.87/src/PaxHeaders/gatt-client.c0000644000000000000000000000005015200607060015015 xustar0020 atime=1783092683 20 ctime=1783094106 bluez-5.87/src/gatt-client.c0000644000000000000000000017237015200607060014510 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Google Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "gdbus/gdbus.h" #include "btio/btio.h" #include "log.h" #include "error.h" #include "btd.h" #include "adapter.h" #include "device.h" #include "src/shared/io.h" #include "src/shared/queue.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/shared/util.h" #include "gatt-client.h" #include "dbus-common.h" #ifndef NELEM #define NELEM(x) (sizeof(x) / sizeof((x)[0])) #endif #define GATT_SERVICE_IFACE "org.bluez.GattService1" #define GATT_CHARACTERISTIC_IFACE "org.bluez.GattCharacteristic1" #define GATT_DESCRIPTOR_IFACE "org.bluez.GattDescriptor1" struct btd_gatt_client { struct btd_device *device; uint8_t features; bool ready; char devaddr[18]; struct gatt_db *db; struct bt_gatt_client *gatt; struct queue *services; struct queue *all_notify_clients; struct queue *ios; }; struct service { struct btd_gatt_client *client; bool primary; bool claimed; uint16_t start_handle; uint16_t end_handle; bt_uuid_t uuid; char *path; struct queue *chrcs; struct queue *incl_services; }; typedef bool (*async_dbus_op_complete_t)(void *data); struct async_dbus_op { int ref_count; unsigned int id; struct queue *msgs; void *data; uint16_t offset; async_dbus_op_complete_t complete; }; struct sock_io { DBusMessage *msg; struct io *io; void (*destroy)(void *data); void *data; }; struct characteristic { struct service *service; struct gatt_db_attribute *attr; uint16_t handle; uint16_t value_handle; uint8_t props; uint16_t ext_props; uint16_t ext_props_handle; bt_uuid_t uuid; char *path; unsigned int ready_id; unsigned int exchange_id; struct sock_io *write_io; struct sock_io *notify_io; struct async_dbus_op *read_op; struct async_dbus_op *write_op; struct queue *descs; bool notifying; struct queue *notify_clients; }; struct descriptor { struct characteristic *chrc; struct gatt_db_attribute *attr; uint16_t handle; bt_uuid_t uuid; char *path; struct async_dbus_op *read_op; struct async_dbus_op *write_op; }; static bool uuid_cmp(const bt_uuid_t *uuid, uint16_t u16) { bt_uuid_t uuid16; bt_uuid16_create(&uuid16, u16); return bt_uuid_cmp(uuid, &uuid16) == 0; } static gboolean descriptor_get_handle(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct descriptor *desc = data; uint16_t handle = desc->handle; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &handle); return TRUE; } static gboolean descriptor_get_uuid(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { char uuid[MAX_LEN_UUID_STR + 1]; const char *ptr = uuid; struct descriptor *desc = data; bt_uuid_to_string(&desc->uuid, uuid, sizeof(uuid)); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); return TRUE; } static gboolean descriptor_get_characteristic( const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct descriptor *desc = data; const char *str = desc->chrc->path; dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &str); return TRUE; } static void read_cb(struct gatt_db_attribute *attrib, int err, const uint8_t *value, size_t length, void *user_data) { DBusMessageIter *array = user_data; if (err) return; dbus_message_iter_append_fixed_array(array, DBUS_TYPE_BYTE, &value, length); } static gboolean descriptor_get_value(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct descriptor *desc = data; DBusMessageIter array; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "y", &array); gatt_db_attribute_read(desc->attr, 0, 0, NULL, read_cb, &array); dbus_message_iter_close_container(iter, &array); return TRUE; } static void read_check_cb(struct gatt_db_attribute *attrib, int err, const uint8_t *value, size_t length, void *user_data) { gboolean *ret = user_data; if (err) { *ret = FALSE; return; } *ret = TRUE; } static gboolean descriptor_value_exists(const GDBusPropertyTable *property, void *data) { struct descriptor *desc = data; gboolean ret; gatt_db_attribute_read(desc->attr, 0, 0, NULL, read_check_cb, &ret); return ret; } static int parse_value_arg(DBusMessageIter *iter, uint8_t **value, int *len) { DBusMessageIter array; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return -EINVAL; dbus_message_iter_recurse(iter, &array); dbus_message_iter_get_fixed_array(&array, value, len); return 0; } static void async_dbus_op_free(void *data) { struct async_dbus_op *op = data; queue_destroy(op->msgs, (void *)dbus_message_unref); free(op); } static struct async_dbus_op *async_dbus_op_ref(struct async_dbus_op *op) { __sync_fetch_and_add(&op->ref_count, 1); return op; } static void async_dbus_op_unref(void *data) { struct async_dbus_op *op = data; if (__sync_sub_and_fetch(&op->ref_count, 1)) return; async_dbus_op_free(op); } static void message_append_byte_array(DBusMessage *msg, const uint8_t *bytes, size_t len) { DBusMessageIter iter, array; dbus_message_iter_init_append(msg, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "y", &array); dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &bytes, len); dbus_message_iter_close_container(&iter, &array); } static DBusMessage *create_gatt_dbus_error(DBusMessage *msg, uint8_t att_ecode) { switch (att_ecode) { case BT_ATT_ERROR_READ_NOT_PERMITTED: return btd_error_not_permitted(msg, "Read not permitted"); case BT_ATT_ERROR_WRITE_NOT_PERMITTED: return btd_error_not_permitted(msg, "Write not permitted"); case BT_ATT_ERROR_AUTHENTICATION: case BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION: case BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE: return btd_error_not_permitted(msg, "Not paired"); case BT_ATT_ERROR_INVALID_OFFSET: return btd_error_invalid_args_str(msg, "Invalid offset"); case BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN: return btd_error_invalid_args_str(msg, "Invalid Length"); case BT_ATT_ERROR_AUTHORIZATION: return btd_error_not_authorized(msg); case BT_ATT_ERROR_REQUEST_NOT_SUPPORTED: return btd_error_not_supported(msg); case 0: return btd_error_failed(msg, "Operation failed"); default: return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", "Operation failed with ATT error: 0x%02x", att_ecode); } return NULL; } static void write_descriptor_cb(struct gatt_db_attribute *attr, int err, void *user_data) { struct descriptor *desc = user_data; if (err) return; g_dbus_emit_property_changed(btd_get_dbus_connection(), desc->path, GATT_DESCRIPTOR_IFACE, "Value"); } static void async_dbus_op_reply(struct async_dbus_op *op, int err, const uint8_t *value, ssize_t length) { const struct queue_entry *entry; DBusMessage *reply; op->id = 0; for (entry = queue_get_entries(op->msgs); entry; entry = entry->next) { DBusMessage *msg = entry->data; if (err) { reply = err > 0 ? create_gatt_dbus_error(msg, err) : btd_error_failed(msg, strerror(-err)); goto send_reply; } reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID); if (!reply) { error("Failed to allocate D-Bus message reply"); return; } if (length >= 0) message_append_byte_array(reply, value, length); send_reply: g_dbus_send_message(btd_get_dbus_connection(), reply); } } static void read_op_cb(struct gatt_db_attribute *attrib, int err, const uint8_t *value, size_t length, void *user_data) { struct async_dbus_op *op = user_data; async_dbus_op_reply(op, err, value, length); } static void desc_read_cb(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct async_dbus_op *op = user_data; struct descriptor *desc = op->data; if (!success) goto fail; if (!op->offset) gatt_db_attribute_reset(desc->attr); if (!gatt_db_attribute_write(desc->attr, op->offset, value, length, 0, NULL, write_descriptor_cb, desc)) { error("Failed to store attribute"); att_ecode = BT_ATT_ERROR_UNLIKELY; goto fail; } /* Read the stored data from db */ if (!gatt_db_attribute_read(desc->attr, op->offset, 0, NULL, read_op_cb, op)) { error("Failed to read database"); att_ecode = BT_ATT_ERROR_UNLIKELY; goto fail; } desc->read_op = NULL; return; fail: async_dbus_op_reply(op, att_ecode, NULL, 0); desc->read_op = NULL; } static int parse_options(DBusMessageIter *iter, uint16_t *offset, const char **type) { DBusMessageIter dict; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return -EINVAL; dbus_message_iter_recurse(iter, &dict); while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { const char *key; DBusMessageIter value, entry; int var; dbus_message_iter_recurse(&dict, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); var = dbus_message_iter_get_arg_type(&value); if (strcasecmp(key, "offset") == 0) { if (var != DBUS_TYPE_UINT16) return -EINVAL; dbus_message_iter_get_basic(&value, offset); } if (type && strcasecmp(key, "type") == 0) { if (var != DBUS_TYPE_STRING) return -EINVAL; dbus_message_iter_get_basic(&value, type); } dbus_message_iter_next(&dict); } return 0; } static struct async_dbus_op *async_dbus_op_new(DBusMessage *msg, void *data) { struct async_dbus_op *op; op = new0(struct async_dbus_op, 1); op->msgs = queue_new(); queue_push_tail(op->msgs, dbus_message_ref(msg)); op->data = data; return op; } static struct async_dbus_op *read_value(struct bt_gatt_client *gatt, DBusMessage *msg, uint16_t handle, uint16_t offset, bt_gatt_client_read_callback_t callback, void *data) { struct async_dbus_op *op; op = async_dbus_op_new(msg, data); op->offset = offset; op->id = bt_gatt_client_read_long_value(gatt, handle, offset, callback, async_dbus_op_ref(op), async_dbus_op_unref); if (op->id) return op; async_dbus_op_free(op); return NULL; } static DBusMessage *descriptor_read_value(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct descriptor *desc = user_data; struct bt_gatt_client *gatt = desc->chrc->service->client->gatt; DBusMessageIter iter; uint16_t offset = 0; if (!gatt) return btd_error_failed(msg, "Not connected"); dbus_message_iter_init(msg, &iter); if (parse_options(&iter, &offset, NULL)) return btd_error_invalid_args(msg); if (desc->read_op) { if (desc->read_op->offset != offset) return btd_error_in_progress(msg); queue_push_tail(desc->read_op->msgs, dbus_message_ref(msg)); return NULL; } desc->read_op = read_value(gatt, msg, desc->handle, offset, desc_read_cb, desc); if (!desc->read_op) return btd_error_failed(msg, "Failed to send read request"); return NULL; } static void write_result_cb(bool success, bool reliable_error, uint8_t att_ecode, void *user_data) { struct async_dbus_op *op = user_data; int err = 0; if (op->complete && !op->complete(op->data)) { err = -EFAULT; goto done; } if (!success) { if (reliable_error) err = -EFAULT; else err = att_ecode; } done: async_dbus_op_reply(op, err, NULL, -1); } static void write_cb(bool success, uint8_t att_ecode, void *user_data) { write_result_cb(success, false, att_ecode, user_data); } static struct async_dbus_op *start_long_write(DBusMessage *msg, uint16_t handle, struct bt_gatt_client *gatt, bool reliable, const uint8_t *value, size_t value_len, uint16_t offset, void *data, async_dbus_op_complete_t complete) { struct async_dbus_op *op; op = async_dbus_op_new(msg, data); op->complete = complete; op->offset = offset; op->id = bt_gatt_client_write_long_value(gatt, reliable, handle, offset, value, value_len, write_result_cb, op, async_dbus_op_free); if (!op->id) { async_dbus_op_free(op); return NULL; } return op; } static struct async_dbus_op *start_write_request(DBusMessage *msg, uint16_t handle, struct bt_gatt_client *gatt, const uint8_t *value, size_t value_len, void *data, async_dbus_op_complete_t complete) { struct async_dbus_op *op; op = async_dbus_op_new(msg, data); op->complete = complete; op->id = bt_gatt_client_write_value(gatt, handle, value, value_len, write_cb, op, async_dbus_op_free); if (!op->id) { async_dbus_op_free(op); return NULL; } return op; } static bool desc_write_complete(void *data) { struct descriptor *desc = data; desc->write_op = NULL; /* * The descriptor might have been unregistered during the read. Return * failure. */ return !!desc->chrc; } static DBusMessage *descriptor_write_value(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct descriptor *desc = user_data; struct bt_gatt_client *gatt = desc->chrc->service->client->gatt; DBusMessageIter iter; uint8_t *value = NULL; int value_len = 0; uint16_t offset = 0; if (!gatt) return btd_error_failed(msg, "Not connected"); if (desc->write_op) return btd_error_in_progress(msg); dbus_message_iter_init(msg, &iter); if (parse_value_arg(&iter, &value, &value_len)) return btd_error_invalid_args(msg); dbus_message_iter_next(&iter); if (parse_options(&iter, &offset, NULL)) return btd_error_invalid_args(msg); /* * Check if service was previously claimed by a plugin and if we shall * consider it read-only, in that case return not authorized. */ if (desc->chrc->service->claimed && btd_opts.gatt_export == BT_GATT_EXPORT_READ_ONLY) return btd_error_not_authorized(msg); /* * Don't allow writing to Client Characteristic Configuration * descriptors. We achieve this through the StartNotify and StopNotify * methods on GattCharacteristic1. */ if (uuid_cmp(&desc->uuid, GATT_CLIENT_CHARAC_CFG_UUID)) return btd_error_not_permitted(msg, "Write not permitted"); /* * Based on the value length and the MTU, either use a write or a long * write. */ if (value_len <= bt_gatt_client_get_mtu(gatt) - 3 && !offset) desc->write_op = start_write_request(msg, desc->handle, gatt, value, value_len, desc, desc_write_complete); else desc->write_op = start_long_write(msg, desc->handle, gatt, false, value, value_len, offset, desc, desc_write_complete); if (!desc->write_op) return btd_error_failed(msg, "Failed to initiate write"); return NULL; } static const GDBusPropertyTable descriptor_properties[] = { { "Handle", "q", descriptor_get_handle }, { "UUID", "s", descriptor_get_uuid }, { "Characteristic", "o", descriptor_get_characteristic, }, { "Value", "ay", descriptor_get_value, NULL, descriptor_value_exists }, { } }; static const GDBusMethodTable descriptor_methods[] = { { GDBUS_ASYNC_METHOD("ReadValue", GDBUS_ARGS({ "options", "a{sv}" }), GDBUS_ARGS({ "value", "ay" }), descriptor_read_value) }, { GDBUS_ASYNC_METHOD("WriteValue", GDBUS_ARGS({ "value", "ay" }, { "options", "a{sv}" }), NULL, descriptor_write_value) }, { } }; static void descriptor_free(void *data) { struct descriptor *desc = data; g_free(desc->path); free(desc); } static struct descriptor *descriptor_create(struct gatt_db_attribute *attr, struct characteristic *chrc) { struct descriptor *desc; desc = new0(struct descriptor, 1); desc->chrc = chrc; desc->attr = attr; desc->handle = gatt_db_attribute_get_handle(attr); bt_uuid_to_uuid128(gatt_db_attribute_get_type(attr), &desc->uuid); desc->path = g_strdup_printf("%s/desc%04x", chrc->path, desc->handle); if (!g_dbus_register_interface(btd_get_dbus_connection(), desc->path, GATT_DESCRIPTOR_IFACE, descriptor_methods, NULL, descriptor_properties, desc, descriptor_free)) { error("Unable to register GATT descriptor with handle 0x%04x", desc->handle); descriptor_free(desc); return NULL; } DBG("Exported GATT characteristic descriptor: %s", desc->path); if (uuid_cmp(&desc->uuid, GATT_CHARAC_EXT_PROPER_UUID)) chrc->ext_props_handle = desc->handle; return desc; } static void unregister_descriptor(void *data) { struct descriptor *desc = data; struct bt_gatt_client *gatt = desc->chrc->service->client->gatt; DBG("Removing GATT descriptor: %s", desc->path); if (desc->read_op) bt_gatt_client_cancel(gatt, desc->read_op->id); if (desc->write_op) bt_gatt_client_cancel(gatt, desc->write_op->id); desc->chrc = NULL; g_dbus_unregister_interface(btd_get_dbus_connection(), desc->path, GATT_DESCRIPTOR_IFACE); } static gboolean characteristic_get_handle(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct characteristic *chrc = data; uint16_t handle = chrc->handle; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &handle); return TRUE; } static gboolean characteristic_get_uuid(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { char uuid[MAX_LEN_UUID_STR + 1]; const char *ptr = uuid; struct characteristic *chrc = data; bt_uuid_to_string(&chrc->uuid, uuid, sizeof(uuid)); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); return TRUE; } static gboolean characteristic_get_service(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct characteristic *chrc = data; const char *str = chrc->service->path; dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &str); return TRUE; } static gboolean characteristic_get_value(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct characteristic *chrc = data; DBusMessageIter array; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "y", &array); gatt_db_attribute_read(chrc->attr, 0, 0, NULL, read_cb, &array); dbus_message_iter_close_container(iter, &array); return TRUE; } static gboolean characteristic_value_exists(const GDBusPropertyTable *property, void *data) { struct characteristic *chrc = data; gboolean ret; gatt_db_attribute_read(chrc->attr, 0, 0, NULL, read_check_cb, &ret); return ret; } static gboolean characteristic_get_notifying(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct characteristic *chrc = data; dbus_bool_t notifying = chrc->notifying ? TRUE : FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, ¬ifying); return TRUE; } static gboolean characteristic_notifying_exists(const GDBusPropertyTable *property, void *data) { struct characteristic *chrc = data; return (chrc->props & BT_GATT_CHRC_PROP_NOTIFY || chrc->props & BT_GATT_CHRC_PROP_INDICATE); } struct chrc_prop_data { uint8_t prop; char *str; }; static struct chrc_prop_data chrc_props[] = { /* Default Properties */ { BT_GATT_CHRC_PROP_BROADCAST, "broadcast" }, { BT_GATT_CHRC_PROP_READ, "read" }, { BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP, "write-without-response" }, { BT_GATT_CHRC_PROP_WRITE, "write" }, { BT_GATT_CHRC_PROP_NOTIFY, "notify" }, { BT_GATT_CHRC_PROP_INDICATE, "indicate" }, { BT_GATT_CHRC_PROP_AUTH, "authenticated-signed-writes" }, { BT_GATT_CHRC_PROP_EXT_PROP, "extended-properties" } }; static struct chrc_prop_data chrc_ext_props[] = { /* Extended Properties */ { BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE, "reliable-write" }, { BT_GATT_CHRC_EXT_PROP_WRITABLE_AUX, "writable-auxiliaries" } }; static gboolean characteristic_get_flags(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct characteristic *chrc = data; DBusMessageIter array; unsigned i; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "s", &array); for (i = 0; i < NELEM(chrc_props); i++) { if (chrc->props & chrc_props[i].prop) dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &chrc_props[i].str); } for (i = 0; i < NELEM(chrc_ext_props); i++) { if (chrc->ext_props & chrc_ext_props[i].prop) dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &chrc_ext_props[i].str); } dbus_message_iter_close_container(iter, &array); return TRUE; } static gboolean characteristic_get_write_acquired(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct characteristic *chrc = data; dbus_bool_t locked = chrc->write_io ? TRUE : FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &locked); return TRUE; } static gboolean characteristic_write_acquired_exists(const GDBusPropertyTable *property, void *data) { struct characteristic *chrc = data; return (chrc->props & BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP); } static gboolean characteristic_get_notify_acquired(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct characteristic *chrc = data; dbus_bool_t locked = chrc->notify_io ? TRUE : FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &locked); return TRUE; } static gboolean characteristic_notify_acquired_exists(const GDBusPropertyTable *property, void *data) { struct characteristic *chrc = data; return (chrc->props & BT_GATT_CHRC_PROP_NOTIFY); } static gboolean characteristic_get_mtu(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct characteristic *chrc = data; struct bt_gatt_client *gatt = chrc->service->client->gatt; struct bt_att *att; uint16_t mtu; att = bt_gatt_client_get_att(gatt); mtu = att ? bt_att_get_mtu(att) : BT_ATT_DEFAULT_LE_MTU; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &mtu); return TRUE; } static gboolean characteristic_mtu_exists(const GDBusPropertyTable *property, void *data) { struct characteristic *chrc = data; return chrc->service->client->gatt ? TRUE : FALSE; } static void write_characteristic_cb(struct gatt_db_attribute *attr, int err, void *user_data) { struct characteristic *chrc = user_data; if (err) return; g_dbus_emit_property_changed_full(btd_get_dbus_connection(), chrc->path, GATT_CHARACTERISTIC_IFACE, "Value", G_DBUS_PROPERTY_CHANGED_FLAG_FLUSH); } static void chrc_read_cb(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct async_dbus_op *op = user_data; struct characteristic *chrc = op->data; if (!success) goto fail; if (!op->offset) gatt_db_attribute_reset(chrc->attr); if (!gatt_db_attribute_write(chrc->attr, op->offset, value, length, 0, NULL, write_characteristic_cb, chrc)) { error("Failed to store attribute"); att_ecode = BT_ATT_ERROR_UNLIKELY; goto fail; } /* Read the stored data from db */ if (!gatt_db_attribute_read(chrc->attr, op->offset, 0, NULL, read_op_cb, op)) { error("Failed to read database"); att_ecode = BT_ATT_ERROR_UNLIKELY; goto fail; } chrc->read_op = NULL; return; fail: async_dbus_op_reply(op, att_ecode, NULL, 0); chrc->read_op = NULL; } static DBusMessage *characteristic_read_value(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct characteristic *chrc = user_data; struct bt_gatt_client *gatt = chrc->service->client->gatt; DBusMessageIter iter; uint16_t offset = 0; if (!gatt) return btd_error_failed(msg, "Not connected"); dbus_message_iter_init(msg, &iter); if (parse_options(&iter, &offset, NULL)) return btd_error_invalid_args(msg); if (chrc->read_op) { if (chrc->read_op->offset != offset) return btd_error_in_progress(msg); queue_push_tail(chrc->read_op->msgs, dbus_message_ref(msg)); return NULL; } chrc->read_op = read_value(gatt, msg, chrc->value_handle, offset, chrc_read_cb, chrc); if (!chrc->read_op) return btd_error_failed(msg, "Failed to send read request"); return NULL; } static bool chrc_write_complete(void *data) { struct characteristic *chrc = data; chrc->write_op = NULL; /* * The characteristic might have been unregistered during the read. * Return failure. */ return !!chrc->service; } static DBusMessage *characteristic_write_value(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct characteristic *chrc = user_data; struct bt_gatt_client *gatt = chrc->service->client->gatt; DBusMessageIter iter; uint8_t *value = NULL; int value_len = 0; bool supported = false; uint16_t offset = 0; const char *type = NULL; if (!gatt) return btd_error_failed(msg, "Not connected"); if (chrc->write_io) return btd_error_not_permitted(msg, "Write acquired"); if (chrc->write_op) return btd_error_in_progress(msg); dbus_message_iter_init(msg, &iter); if (parse_value_arg(&iter, &value, &value_len)) return btd_error_invalid_args(msg); dbus_message_iter_next(&iter); if (parse_options(&iter, &offset, &type)) return btd_error_invalid_args(msg); /* * Check if service was previously claimed by a plugin and if we shall * consider it read-only, in that case return not authorized. */ if (chrc->service->claimed && btd_opts.gatt_export == BT_GATT_EXPORT_READ_ONLY) return btd_error_not_authorized(msg); /* * Decide which write to use based on characteristic properties. For now * we don't perform signed writes since gatt-client doesn't support them * and the user can always encrypt the through pairing. The procedure to * use is determined based on the following priority: * * * "reliable-write" property set -> reliable long-write. * * "write" property set -> write request. * - If value is larger than MTU - 3: long-write * * "write-without-response" property set -> write command. */ if ((!type && (chrc->ext_props & BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE)) || (type && !strcasecmp(type, "reliable"))) { supported = true; chrc->write_op = start_long_write(msg, chrc->value_handle, gatt, true, value, value_len, offset, chrc, chrc_write_complete); if (chrc->write_op) return NULL; } if ((!type && chrc->props & BT_GATT_CHRC_PROP_WRITE) || (type && !strcasecmp(type, "request"))) { uint16_t mtu; supported = true; mtu = bt_gatt_client_get_mtu(gatt); if (!mtu) return btd_error_failed(msg, "No ATT transport"); if (value_len <= mtu - 3 && !offset) chrc->write_op = start_write_request(msg, chrc->value_handle, gatt, value, value_len, chrc, chrc_write_complete); else chrc->write_op = start_long_write(msg, chrc->value_handle, gatt, false, value, value_len, offset, chrc, chrc_write_complete); if (chrc->write_op) return NULL; } if ((type && strcasecmp(type, "command")) || offset || (!type && !(chrc->props & BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP))) goto fail; supported = true; if (bt_gatt_client_write_without_response(gatt, chrc->value_handle, chrc->props & BT_GATT_CHRC_PROP_AUTH, value, value_len)) return dbus_message_new_method_return(msg); fail: if (supported) return btd_error_failed(msg, "Failed to initiate write"); return btd_error_not_supported(msg); } static bool sock_read(struct io *io, void *user_data) { struct characteristic *chrc = user_data; struct bt_gatt_client *gatt = chrc->service->client->gatt; struct msghdr msg; uint8_t buf[512]; struct iovec iov; int fd = io_get_fd(io); ssize_t bytes_read; iov.iov_base = buf; iov.iov_len = sizeof(buf); memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; if (fd < 0) { error("io_get_fd() returned %d\n", fd); return false; } bytes_read = recvmsg(fd, &msg, MSG_DONTWAIT); if (bytes_read < 0) { error("recvmsg: %s", strerror(errno)); return false; } if (!gatt || bytes_read == 0) return false; bt_gatt_client_write_without_response(gatt, chrc->value_handle, chrc->props & BT_GATT_CHRC_PROP_AUTH, buf, bytes_read); return true; } static void sock_io_destroy(struct sock_io *io) { if (io->destroy) io->destroy(io->data); if (io->msg) dbus_message_unref(io->msg); io_destroy(io->io); free(io); } static void destroy_sock(struct characteristic *chrc, struct io *io) { queue_remove(chrc->service->client->ios, io); if (chrc->write_io && io == chrc->write_io->io) { sock_io_destroy(chrc->write_io); chrc->write_io = NULL; g_dbus_emit_property_changed(btd_get_dbus_connection(), chrc->path, GATT_CHARACTERISTIC_IFACE, "WriteAcquired"); } else if (chrc->notify_io) { sock_io_destroy(chrc->notify_io); chrc->notify_io = NULL; g_dbus_emit_property_changed(btd_get_dbus_connection(), chrc->path, GATT_CHARACTERISTIC_IFACE, "NotifyAcquired"); } } static bool sock_hup(struct io *io, void *user_data) { struct characteristic *chrc = user_data; DBG("%s: io %p", chrc->path, io); destroy_sock(chrc, io); return false; } static DBusMessage *create_sock(struct characteristic *chrc, DBusMessage *msg) { struct bt_gatt_client *gatt = chrc->service->client->gatt; int fds[2]; struct io *io; bool dir; uint16_t mtu; DBusMessage *reply; if (!gatt || !bt_gatt_client_is_ready(gatt)) return btd_error_failed(msg, "Not connected"); if (socketpair(AF_LOCAL, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, fds) < 0) return btd_error_failed(msg, strerror(errno)); dir = dbus_message_has_member(msg, "AcquireWrite"); io = io_new(fds[!dir]); if (!io) { close(fds[0]); close(fds[1]); return btd_error_failed(msg, strerror(EIO)); } io_set_close_on_destroy(io, true); if (!io_set_read_handler(io, sock_read, chrc, NULL)) goto fail; if (!io_set_disconnect_handler(io, sock_hup, chrc, NULL)) goto fail; mtu = bt_gatt_client_get_mtu(gatt); reply = g_dbus_create_reply(msg, DBUS_TYPE_UNIX_FD, &fds[dir], DBUS_TYPE_UINT16, &mtu, DBUS_TYPE_INVALID); close(fds[dir]); if (dir) { chrc->write_io->io = io; g_dbus_emit_property_changed(btd_get_dbus_connection(), chrc->path, GATT_CHARACTERISTIC_IFACE, "WriteAcquired"); } else { chrc->notify_io->io = io; g_dbus_emit_property_changed(btd_get_dbus_connection(), chrc->path, GATT_CHARACTERISTIC_IFACE, "NotifyAcquired"); } queue_push_tail(chrc->service->client->ios, io); DBG("%s: sender %s io %p", dbus_message_get_member(msg), dbus_message_get_sender(msg), io); return reply; fail: io_destroy(io); close(fds[dir]); return btd_error_failed(msg, strerror(EIO)); } static void characteristic_ready(bool success, uint8_t ecode, void *user_data) { struct characteristic *chrc = user_data; DBusMessage *reply; chrc->ready_id = 0; if (chrc->write_io && chrc->write_io->msg) { reply = create_sock(chrc, chrc->write_io->msg); g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(chrc->write_io->msg); chrc->write_io->msg = NULL; } if (chrc->notify_io && chrc->notify_io->msg) { reply = create_sock(chrc, chrc->notify_io->msg); g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(chrc->notify_io->msg); chrc->notify_io->msg = NULL; } } static DBusMessage *characteristic_acquire_write(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct characteristic *chrc = user_data; struct bt_gatt_client *gatt = chrc->service->client->gatt; if (!gatt) return btd_error_failed(msg, "Not connected"); /* * Check if service was previously claimed by a plugin and if we shall * consider it read-only, in that case return not authorized. */ if (chrc->service->claimed && btd_opts.gatt_export == BT_GATT_EXPORT_READ_ONLY) return btd_error_not_authorized(msg); if (chrc->write_io) return btd_error_not_permitted(msg, "Write acquired"); if (!(chrc->props & BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP)) return btd_error_not_supported(msg); chrc->write_io = new0(struct sock_io, 1); if (!bt_gatt_client_is_ready(gatt)) { /* GATT not ready, wait until it becomes ready */ if (!chrc->ready_id) chrc->ready_id = bt_gatt_client_ready_register(gatt, characteristic_ready, chrc, NULL); chrc->write_io->msg = dbus_message_ref(msg); return NULL; } return create_sock(chrc, msg); } struct notify_client { struct characteristic *chrc; int ref_count; char *owner; guint watch; unsigned int notify_id; }; static void notify_client_free(struct notify_client *client) { DBG("owner %s", client->owner); g_dbus_remove_watch(btd_get_dbus_connection(), client->watch); bt_gatt_client_unregister_notify(client->chrc->service->client->gatt, client->notify_id); free(client->owner); free(client); } static void notify_client_unref(void *data) { struct notify_client *client = data; DBG("owner %s", client->owner); if (__sync_sub_and_fetch(&client->ref_count, 1)) return; notify_client_free(client); } static struct notify_client *notify_client_ref(struct notify_client *client) { DBG("owner %s", client->owner); __sync_fetch_and_add(&client->ref_count, 1); return client; } static bool match_notifying(const void *a, const void *b) { const struct notify_client *client = a; return !!client->notify_id; } static void update_notifying(struct characteristic *chrc) { if (!chrc->notifying) return; if (queue_find(chrc->notify_clients, match_notifying, NULL)) return; chrc->notifying = false; g_dbus_emit_property_changed(btd_get_dbus_connection(), chrc->path, GATT_CHARACTERISTIC_IFACE, "Notifying"); } static void notify_client_disconnect(DBusConnection *conn, void *user_data) { struct notify_client *client = user_data; struct characteristic *chrc = client->chrc; DBG("owner %s", client->owner); queue_remove(chrc->notify_clients, client); queue_remove(chrc->service->client->all_notify_clients, client); update_notifying(chrc); notify_client_unref(client); } static struct notify_client *notify_client_create(struct characteristic *chrc, const char *owner) { struct notify_client *client; client = new0(struct notify_client, 1); client->chrc = chrc; client->owner = strdup(owner); if (!client->owner) { free(client); return NULL; } client->watch = g_dbus_add_disconnect_watch(btd_get_dbus_connection(), owner, notify_client_disconnect, client, NULL); if (!client->watch) { free(client->owner); free(client); return NULL; } return notify_client_ref(client); } static bool match_notify_sender(const void *a, const void *b) { const struct notify_client *client = a; const char *sender = b; return strcmp(client->owner, sender) == 0; } static void notify_cb(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct async_dbus_op *op = user_data; struct notify_client *client = op->data; struct characteristic *chrc = client->chrc; /* * Even if the value didn't change, we want to send a PropertiesChanged * signal so that we propagate the notification/indication to * applications. */ gatt_db_attribute_reset(chrc->attr); gatt_db_attribute_write(chrc->attr, 0, value, length, 0, NULL, write_characteristic_cb, chrc); } static void create_notify_reply(struct async_dbus_op *op, bool success, uint8_t att_ecode) { int err; if (success) err = 0; else err = att_ecode ? att_ecode : -ENOENT; async_dbus_op_reply(op, err, NULL, -1); } static void register_notify_cb(uint16_t att_ecode, void *user_data) { struct async_dbus_op *op = user_data; struct notify_client *client = op->data; struct characteristic *chrc = client->chrc; if (att_ecode) { queue_remove(chrc->notify_clients, client); queue_remove(chrc->service->client->all_notify_clients, client); notify_client_free(client); create_notify_reply(op, false, att_ecode); return; } if (!chrc->notifying) { chrc->notifying = true; g_dbus_emit_property_changed(btd_get_dbus_connection(), chrc->path, GATT_CHARACTERISTIC_IFACE, "Notifying"); } create_notify_reply(op, true, 0); } static void notify_io_cb(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct msghdr msg; struct iovec iov; struct notify_client *client = user_data; struct characteristic *chrc = client->chrc; int err; /* Drop notification if the sock is not ready */ if (!chrc->notify_io || !chrc->notify_io->io) return; iov.iov_base = (void *) value; iov.iov_len = length; memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; err = sendmsg(io_get_fd(chrc->notify_io->io), &msg, MSG_NOSIGNAL); if (err < 0) error("sendmsg: %s", strerror(errno)); } static void register_notify_io_cb(uint16_t att_ecode, void *user_data) { struct notify_client *client = user_data; struct characteristic *chrc = client->chrc; struct bt_gatt_client *gatt = chrc->service->client->gatt; if (att_ecode) { DBusMessage *reply = create_gatt_dbus_error(chrc->notify_io->msg, att_ecode); g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(chrc->notify_io->msg); chrc->notify_io->msg = NULL; destroy_sock(chrc, chrc->notify_io->io); return; } if (!bt_gatt_client_is_ready(gatt)) { if (!chrc->ready_id) chrc->ready_id = bt_gatt_client_ready_register(gatt, characteristic_ready, chrc, NULL); return; } characteristic_ready(true, 0, chrc); } static void notify_io_destroy(void *data) { struct notify_client *client = data; if (queue_remove(client->chrc->notify_clients, client)) notify_client_unref(client); notify_client_unref(client); } static DBusMessage *characteristic_acquire_notify(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct characteristic *chrc = user_data; struct bt_gatt_client *gatt = chrc->service->client->gatt; const char *sender = dbus_message_get_sender(msg); struct notify_client *client; if (!gatt) return btd_error_failed(msg, "Not connected"); if (chrc->notify_io) return btd_error_not_permitted(msg, "Notify acquired"); /* Each client can only have one active notify session. */ if (!queue_isempty(chrc->notify_clients)) return btd_error_in_progress(msg); if (!(chrc->props & (BT_GATT_CHRC_PROP_NOTIFY | BT_GATT_CHRC_PROP_INDICATE))) return btd_error_not_supported(msg); client = notify_client_create(chrc, sender); if (!client) return btd_error_failed(msg, "Failed allocate notify session"); client->notify_id = bt_gatt_client_register_notify(gatt, chrc->value_handle, register_notify_io_cb, notify_io_cb, client, NULL); if (!client->notify_id) { notify_client_unref(client); return btd_error_failed(msg, "Failed to subscribe"); } queue_push_tail(chrc->notify_clients, client); chrc->notify_io = new0(struct sock_io, 1); chrc->notify_io->data = notify_client_ref(client); chrc->notify_io->msg = dbus_message_ref(msg); chrc->notify_io->destroy = notify_io_destroy; return NULL; } static DBusMessage *characteristic_start_notify(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct characteristic *chrc = user_data; struct bt_gatt_client *gatt = chrc->service->client->gatt; const char *sender = dbus_message_get_sender(msg); struct async_dbus_op *op; struct notify_client *client; struct btd_device *device = chrc->service->client->device; if (device_is_disconnecting(device)) { error("Device is disconnecting. StartNotify is not allowed."); return btd_error_not_connected(msg); } if (chrc->notify_io) return btd_error_not_permitted(msg, "Notify acquired"); if (!(chrc->props & (BT_GATT_CHRC_PROP_NOTIFY | BT_GATT_CHRC_PROP_INDICATE))) return btd_error_not_supported(msg); /* Each client can only have one active notify session. */ client = queue_find(chrc->notify_clients, match_notify_sender, sender); if (client) return client->notify_id ? g_dbus_create_reply(msg, DBUS_TYPE_INVALID) : btd_error_in_progress(msg); client = notify_client_create(chrc, sender); if (!client) return btd_error_failed(msg, "Failed allocate notify session"); queue_push_tail(chrc->notify_clients, client); queue_push_tail(chrc->service->client->all_notify_clients, client); /* * If the device is currently not connected, return success. We will * automatically try and register all clients when a GATT client becomes * ready. */ if (!gatt) { DBusMessage *reply; reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID); if (reply) return reply; /* * Clean up and respond with an error instead of timing out to * avoid any ambiguities. */ error("Failed to construct D-Bus message reply"); goto fail; } op = async_dbus_op_new(msg, client); client->notify_id = bt_gatt_client_register_notify(gatt, chrc->value_handle, register_notify_cb, notify_cb, op, async_dbus_op_free); if (client->notify_id) return NULL; async_dbus_op_free(op); fail: queue_remove(chrc->notify_clients, client); queue_remove(chrc->service->client->all_notify_clients, client); /* Directly free the client */ notify_client_free(client); return btd_error_failed(msg, "Failed to register notify session"); } static DBusMessage *characteristic_stop_notify(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct characteristic *chrc = user_data; struct bt_gatt_client *gatt = chrc->service->client->gatt; const char *sender = dbus_message_get_sender(msg); struct notify_client *client; if (chrc->notify_io) { destroy_sock(chrc, chrc->notify_io->io); return dbus_message_new_method_return(msg); } client = queue_remove_if(chrc->notify_clients, match_notify_sender, (void *) sender); if (!client) return btd_error_failed(msg, "No notify session started"); queue_remove(chrc->service->client->all_notify_clients, client); bt_gatt_client_unregister_notify(gatt, client->notify_id); update_notifying(chrc); notify_client_unref(client); return dbus_message_new_method_return(msg); } static const GDBusPropertyTable characteristic_properties[] = { { "Handle", "q", characteristic_get_handle }, { "UUID", "s", characteristic_get_uuid, NULL, NULL }, { "Service", "o", characteristic_get_service, NULL, NULL }, { "Value", "ay", characteristic_get_value, NULL, characteristic_value_exists }, { "Notifying", "b", characteristic_get_notifying, NULL, characteristic_notifying_exists }, { "Flags", "as", characteristic_get_flags, NULL, NULL }, { "WriteAcquired", "b", characteristic_get_write_acquired, NULL, characteristic_write_acquired_exists }, { "NotifyAcquired", "b", characteristic_get_notify_acquired, NULL, characteristic_notify_acquired_exists }, { "MTU", "q", characteristic_get_mtu, NULL, characteristic_mtu_exists }, { } }; static const GDBusMethodTable characteristic_methods[] = { { GDBUS_ASYNC_METHOD("ReadValue", GDBUS_ARGS({ "options", "a{sv}" }), GDBUS_ARGS({ "value", "ay" }), characteristic_read_value) }, { GDBUS_ASYNC_METHOD("WriteValue", GDBUS_ARGS({ "value", "ay" }, { "options", "a{sv}" }), NULL, characteristic_write_value) }, { GDBUS_ASYNC_METHOD("AcquireWrite", GDBUS_ARGS({ "options", "a{sv}" }), GDBUS_ARGS({ "fd", "h" }, { "mtu", "q" }), characteristic_acquire_write) }, { GDBUS_ASYNC_METHOD("AcquireNotify", GDBUS_ARGS({ "options", "a{sv}" }), GDBUS_ARGS({ "fd", "h" }, { "mtu", "q" }), characteristic_acquire_notify) }, { GDBUS_ASYNC_METHOD("StartNotify", NULL, NULL, characteristic_start_notify) }, { GDBUS_METHOD("StopNotify", NULL, NULL, characteristic_stop_notify) }, { } }; static void remove_client(void *data) { struct notify_client *ntfy_client = data; struct btd_gatt_client *client = ntfy_client->chrc->service->client; queue_remove(client->all_notify_clients, ntfy_client); notify_client_unref(ntfy_client); } static void characteristic_free(void *data) { struct characteristic *chrc = data; struct bt_gatt_client *gatt = chrc->service->client->gatt; struct bt_att *att; /* List should be empty here */ queue_destroy(chrc->descs, NULL); if (chrc->write_io) { queue_remove(chrc->service->client->ios, chrc->write_io->io); sock_io_destroy(chrc->write_io); } if (chrc->notify_io) { queue_remove(chrc->service->client->ios, chrc->notify_io->io); sock_io_destroy(chrc->notify_io); } queue_destroy(chrc->notify_clients, remove_client); att = bt_gatt_client_get_att(gatt); if (att) bt_att_unregister_exchange(att, chrc->exchange_id); g_free(chrc->path); free(chrc); } static void att_exchange(uint16_t mtu, void *user_data) { struct characteristic *chrc = user_data; g_dbus_emit_property_changed(btd_get_dbus_connection(), chrc->path, GATT_CHARACTERISTIC_IFACE, "MTU"); } static struct characteristic *characteristic_create( struct gatt_db_attribute *attr, struct service *service) { struct characteristic *chrc; struct bt_gatt_client *gatt = service->client->gatt; struct bt_att *att; bt_uuid_t uuid; chrc = new0(struct characteristic, 1); chrc->descs = queue_new(); chrc->notify_clients = queue_new(); chrc->service = service; gatt_db_attribute_get_char_data(attr, &chrc->handle, &chrc->value_handle, &chrc->props, &chrc->ext_props, &uuid); chrc->attr = gatt_db_get_attribute(service->client->db, chrc->value_handle); if (!chrc->attr) { error("Attribute 0x%04x not found", chrc->value_handle); characteristic_free(chrc); return NULL; } bt_uuid_to_uuid128(&uuid, &chrc->uuid); chrc->path = g_strdup_printf("%s/char%04x", service->path, chrc->handle); if (!g_dbus_register_interface(btd_get_dbus_connection(), chrc->path, GATT_CHARACTERISTIC_IFACE, characteristic_methods, NULL, characteristic_properties, chrc, characteristic_free)) { error("Unable to register GATT characteristic with handle " "0x%04x", chrc->handle); characteristic_free(chrc); return NULL; } att = bt_gatt_client_get_att(gatt); if (att) chrc->exchange_id = bt_att_register_exchange(att, att_exchange, chrc, NULL); DBG("Exported GATT characteristic: %s", chrc->path); return chrc; } static void unregister_characteristic(void *data) { struct characteristic *chrc = data; struct bt_gatt_client *gatt = chrc->service->client->gatt; DBG("Removing GATT characteristic: %s", chrc->path); if (chrc->read_op) bt_gatt_client_cancel(gatt, chrc->read_op->id); if (chrc->write_op) bt_gatt_client_cancel(gatt, chrc->write_op->id); queue_remove_all(chrc->descs, NULL, NULL, unregister_descriptor); g_dbus_unregister_interface(btd_get_dbus_connection(), chrc->path, GATT_CHARACTERISTIC_IFACE); } static gboolean service_get_handle(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct service *service = data; uint16_t handle = service->start_handle; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &handle); return TRUE; } static gboolean service_get_uuid(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { char uuid[MAX_LEN_UUID_STR + 1]; const char *ptr = uuid; struct service *service = data; bt_uuid_to_string(&service->uuid, uuid, sizeof(uuid)); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); return TRUE; } static gboolean service_get_device(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct service *service = data; const char *str = device_get_path(service->client->device); dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &str); return TRUE; } static gboolean service_get_primary(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct service *service = data; dbus_bool_t primary; primary = service->primary ? TRUE : FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &primary); return TRUE; } static void append_incl_service_path(void *data, void *user_data) { struct service *incl_service = data; DBusMessageIter *array = user_data; dbus_message_iter_append_basic(array, DBUS_TYPE_OBJECT_PATH, &incl_service->path); } static gboolean service_get_includes(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct service *service = data; DBusMessageIter array; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{o}", &array); queue_foreach(service->incl_services, append_incl_service_path, &array); dbus_message_iter_close_container(iter, &array); return TRUE; } static const GDBusPropertyTable service_properties[] = { { "Handle", "q", service_get_handle }, { "UUID", "s", service_get_uuid }, { "Device", "o", service_get_device }, { "Primary", "b", service_get_primary }, { "Includes", "ao", service_get_includes }, { } }; static void service_free(void *data) { struct service *service = data; queue_destroy(service->chrcs, NULL); /* List should be empty here */ queue_destroy(service->incl_services, NULL); g_free(service->path); free(service); } static bool match_service_handle(const void *a, const void *b) { const struct service *service = a; uint16_t start_handle = PTR_TO_UINT(b); return service->start_handle == start_handle; } static struct service *service_create(struct gatt_db_attribute *attr, struct btd_gatt_client *client) { struct service *service; const char *device_path = device_get_path(client->device); bt_uuid_t uuid; uint16_t start_handle, end_handle; bool primary; gatt_db_attribute_get_service_data(attr, &start_handle, &end_handle, &primary, &uuid); /* Check if service is already on list then return NULL to skip it */ service = queue_find(client->services, match_service_handle, UINT_TO_PTR(start_handle)); if (service) return NULL; service = new0(struct service, 1); service->chrcs = queue_new(); service->incl_services = queue_new(); service->client = client; service->claimed = gatt_db_service_get_claimed(attr); gatt_db_attribute_get_service_data(attr, &service->start_handle, &service->end_handle, &service->primary, &uuid); bt_uuid_to_uuid128(&uuid, &service->uuid); service->path = g_strdup_printf("%s/service%04x", device_path, service->start_handle); if (!g_dbus_register_interface(btd_get_dbus_connection(), service->path, GATT_SERVICE_IFACE, NULL, NULL, service_properties, service, service_free)) { error("Unable to register GATT service with handle 0x%04x for " "device %s", service->start_handle, client->devaddr); service_free(service); return NULL; } DBG("Exported GATT service: %s", service->path); /* Set service active so we can skip discovering next time */ gatt_db_service_set_active(attr, true); /* Mark the service as claimed since it going to be exported */ gatt_db_service_set_claimed(attr, true); return service; } static void on_service_removed(void *data, void *user_data) { struct service *service = data; struct service *removed_service = user_data; if (queue_remove(service->incl_services, removed_service)) g_dbus_emit_property_changed(btd_get_dbus_connection(), service->path, GATT_SERVICE_IFACE, "Includes"); } static void unregister_service(void *data) { struct service *service = data; struct btd_gatt_client *client = service->client; DBG("Removing GATT service: %s", service->path); queue_remove_all(service->chrcs, NULL, NULL, unregister_characteristic); queue_remove_all(service->incl_services, NULL, NULL, NULL); queue_foreach(client->services, on_service_removed, service); g_dbus_unregister_interface(btd_get_dbus_connection(), service->path, GATT_SERVICE_IFACE); } struct export_data { void *root; bool failed; }; static void export_desc(struct gatt_db_attribute *attr, void *user_data) { struct descriptor *desc; struct export_data *data = user_data; struct characteristic *charac = data->root; if (data->failed) return; desc = descriptor_create(attr, charac); if (!desc) { data->failed = true; return; } queue_push_tail(charac->descs, desc); } static bool create_descriptors(struct gatt_db_attribute *attr, struct characteristic *charac) { struct export_data data; data.root = charac; data.failed = false; gatt_db_service_foreach_desc(attr, export_desc, &data); return !data.failed; } static void export_char(struct gatt_db_attribute *attr, void *user_data) { struct characteristic *charac; struct export_data *data = user_data; struct service *service = data->root; if (data->failed) return; charac = characteristic_create(attr, service); if (!charac) goto fail; if (!create_descriptors(attr, charac)) { unregister_characteristic(charac); goto fail; } queue_push_tail(service->chrcs, charac); return; fail: data->failed = true; } static bool create_characteristics(struct gatt_db_attribute *attr, struct service *service) { struct export_data data; data.root = service; data.failed = false; gatt_db_service_foreach_char(attr, export_char, &data); return !data.failed; } static void export_service(struct gatt_db_attribute *attr, void *user_data) { struct btd_gatt_client *client = user_data; struct service *service; if (gatt_db_service_get_claimed(attr)) { switch (btd_opts.gatt_export) { case BT_GATT_EXPORT_OFF: return; case BT_GATT_EXPORT_READ_ONLY: case BT_GATT_EXPORT_READ_WRITE: break; } } service = service_create(attr, client); if (!service) return; if (!create_characteristics(attr, service)) { error("Exporting characteristics failed"); unregister_service(service); return; } queue_push_tail(client->services, service); } struct update_incl_data { struct service *service; bool changed; }; static void update_included_service(struct gatt_db_attribute *attrib, void *user_data) { struct update_incl_data *update_data = user_data; struct btd_gatt_client *client = update_data->service->client; struct service *service = update_data->service; struct service *incl_service; uint16_t start_handle; gatt_db_attribute_get_incl_data(attrib, NULL, &start_handle, NULL); incl_service = queue_find(client->services, match_service_handle, UINT_TO_PTR(start_handle)); if (!incl_service) return; /* Check if service is already on list */ if (queue_find(service->incl_services, NULL, incl_service)) return; queue_push_tail(service->incl_services, incl_service); update_data->changed = true; } static void update_included_services(void *data, void *user_data) { struct btd_gatt_client *client = user_data; struct service *service = data; struct gatt_db_attribute *attr; struct update_incl_data inc_data = { .changed = false, .service = service, }; attr = gatt_db_get_attribute(client->db, service->start_handle); gatt_db_service_foreach_incl(attr, update_included_service, &inc_data); if (inc_data.changed) g_dbus_emit_property_changed(btd_get_dbus_connection(), service->path, GATT_SERVICE_IFACE, "Includes"); } static void create_services(struct btd_gatt_client *client) { DBG("Exporting objects for GATT services: %s", client->devaddr); gatt_db_foreach_service(client->db, NULL, export_service, client); queue_foreach(client->services, update_included_services, client); } struct btd_gatt_client *btd_gatt_client_new(struct btd_device *device) { struct btd_gatt_client *client; struct gatt_db *db; if (!device) return NULL; db = btd_device_get_gatt_db(device); if (!db) return NULL; client = new0(struct btd_gatt_client, 1); client->services = queue_new(); client->all_notify_clients = queue_new(); client->ios = queue_new(); client->device = device; ba2str(device_get_address(device), client->devaddr); client->db = gatt_db_ref(db); return client; } void btd_gatt_client_destroy(struct btd_gatt_client *client) { if (!client) return; client->ready = false; queue_destroy(client->services, unregister_service); queue_destroy(client->all_notify_clients, NULL); queue_destroy(client->ios, NULL); bt_gatt_client_unref(client->gatt); gatt_db_unref(client->db); free(client); } static void register_notify(void *data, void *user_data) { struct notify_client *notify_client = data; struct btd_gatt_client *client = user_data; struct async_dbus_op *op; DBG("Re-register subscribed notification client"); op = new0(struct async_dbus_op, 1); op->data = notify_client; notify_client->notify_id = bt_gatt_client_register_notify(client->gatt, notify_client->chrc->value_handle, register_notify_cb, notify_cb, op, async_dbus_op_free); if (notify_client->notify_id) return; async_dbus_op_free(op); DBG("Failed to re-register notification client"); queue_remove(notify_client->chrc->notify_clients, notify_client); queue_remove(client->all_notify_clients, notify_client); notify_client_free(notify_client); } void btd_gatt_client_ready(struct btd_gatt_client *client) { if (!client) return; if (!client->gatt) { struct bt_gatt_client *gatt; gatt = btd_device_get_gatt_client(client->device); client->gatt = bt_gatt_client_clone(gatt); if (!client->gatt) { error("GATT client not initialized"); return; } } client->ready = true; DBG("GATT client ready"); create_services(client); DBG("Features 0x%02x", client->features); if (!client->features) { client->features = bt_gatt_client_get_features(client->gatt); DBG("Update Features 0x%02x", client->features); if (client->features & BT_GATT_CHRC_CLI_FEAT_EATT) btd_gatt_client_eatt_connect(client); } } static void eatt_connect_cb(GIOChannel *io, GError *gerr, gpointer user_data) { struct btd_gatt_client *client = user_data; if (gerr) return; device_attach_att(client->device, io); } void btd_gatt_client_eatt_connect(struct btd_gatt_client *client) { struct bt_att *att = bt_gatt_client_get_att(client->gatt); struct btd_device *dev = client->device; struct btd_adapter *adapter = device_get_adapter(dev); GIOChannel *io; GError *gerr = NULL; char addr[18]; int i; if (!(client->features & BT_GATT_CHRC_CLI_FEAT_EATT) || !btd_device_is_initiator(dev)) return; if (bt_att_get_channels(att) == btd_opts.gatt_channels) return; ba2str(device_get_address(dev), addr); for (i = bt_att_get_channels(att); i < btd_opts.gatt_channels; i++) { int defer_timeout = i + 1 < btd_opts.gatt_channels ? 1 : 0; DBG("Connection attempt to: %s defer %s", addr, defer_timeout ? "true" : "false"); /* Attempt to connect using the Ext-Flowctl */ io = bt_io_connect(eatt_connect_cb, client, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, btd_adapter_get_address(adapter), BT_IO_OPT_SOURCE_TYPE, btd_adapter_get_address_type(adapter), BT_IO_OPT_DEST_BDADDR, device_get_address(dev), BT_IO_OPT_DEST_TYPE, device_get_le_address_type(dev), BT_IO_OPT_MODE, BT_IO_MODE_EXT_FLOWCTL, BT_IO_OPT_PSM, BT_ATT_EATT_PSM, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_MTU, btd_opts.gatt_mtu, BT_IO_OPT_DEFER_TIMEOUT, defer_timeout, BT_IO_OPT_INVALID); if (!io) { g_error_free(gerr); gerr = NULL; /* Fallback to legacy LE Mode */ io = bt_io_connect(eatt_connect_cb, client, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, btd_adapter_get_address(adapter), BT_IO_OPT_SOURCE_TYPE, btd_adapter_get_address_type(adapter), BT_IO_OPT_DEST_BDADDR, device_get_address(dev), BT_IO_OPT_DEST_TYPE, device_get_le_address_type(dev), BT_IO_OPT_PSM, BT_ATT_EATT_PSM, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_MTU, btd_opts.gatt_mtu, BT_IO_OPT_INVALID); if (!io) { error("EATT bt_io_connect(%s): %s", addr, gerr->message); g_error_free(gerr); return; } } g_io_channel_unref(io); } } void btd_gatt_client_connected(struct btd_gatt_client *client) { struct bt_gatt_client *gatt; gatt = btd_device_get_gatt_client(client->device); if (!gatt) { error("GATT client not initialized"); return; } DBG("Device connected."); bt_gatt_client_unref(client->gatt); client->gatt = bt_gatt_client_clone(gatt); /* * Services have already been created before. Re-enable notifications * for any pre-registered notification sessions. */ queue_foreach(client->all_notify_clients, register_notify, client); } void btd_gatt_client_service_added(struct btd_gatt_client *client, struct gatt_db_attribute *attrib) { if (!client || !attrib || !client->ready) return; export_service(attrib, client); queue_foreach(client->services, update_included_services, client); } void btd_gatt_client_service_removed(struct btd_gatt_client *client, struct gatt_db_attribute *attrib) { uint16_t start_handle, end_handle; if (!client || !attrib || !client->ready) return; gatt_db_attribute_get_service_handles(attrib, &start_handle, &end_handle); DBG("GATT Services Removed - start: 0x%04x, end: 0x%04x", start_handle, end_handle); queue_remove_all(client->services, match_service_handle, UINT_TO_PTR(start_handle), unregister_service); } static void clear_notify_id(void *data, void *user_data) { struct notify_client *client = data; client->notify_id = 0; } static void client_shutdown(void *data) { io_shutdown(data); } void btd_gatt_client_disconnected(struct btd_gatt_client *client) { if (!client || !client->gatt) return; DBG("Device disconnected. Cleaning up."); queue_remove_all(client->ios, NULL, NULL, client_shutdown); /* * TODO: Once GATT over BR/EDR is properly supported, we should pass the * correct bdaddr_type based on the transport over which GATT is being * done. */ queue_foreach(client->all_notify_clients, clear_notify_id, NULL); bt_gatt_client_unref(client->gatt); client->gatt = NULL; } struct foreach_service_data { btd_gatt_client_service_path_t func; void *user_data; }; static void client_service_foreach(void *data, void *user_data) { struct service *service = data; struct foreach_service_data *foreach_data = user_data; foreach_data->func(service->path, foreach_data->user_data); } void btd_gatt_client_foreach_service(struct btd_gatt_client *client, btd_gatt_client_service_path_t func, void *user_data) { struct foreach_service_data data; if (!client) return; data.func = func; data.user_data = user_data; queue_foreach(client->services, client_service_foreach, &data); } bluez-5.87/src/PaxHeaders/adv_monitor.h0000644000000000000000000000005014214376354015145 xustar0020 atime=1783093766 20 ctime=1783094106 bluez-5.87/src/adv_monitor.h0000644000000000000000000000220314214376354014623 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2020 Google LLC * * */ #ifndef __ADV_MONITOR_H #define __ADV_MONITOR_H #include #include "src/shared/ad.h" struct mgmt; struct queue; struct btd_device; struct btd_adapter; struct btd_adv_monitor_manager; struct btd_adv_monitor_pattern; struct btd_adv_monitor_manager *btd_adv_monitor_manager_create( struct btd_adapter *adapter, struct mgmt *mgmt); void btd_adv_monitor_manager_destroy(struct btd_adv_monitor_manager *manager); bool btd_adv_monitor_offload_enabled(struct btd_adv_monitor_manager *manager); struct queue *btd_adv_monitor_content_filter( struct btd_adv_monitor_manager *manager, struct bt_ad *ad); void btd_adv_monitor_notify_monitors(struct btd_adv_monitor_manager *manager, struct btd_device *device, int8_t rssi, struct queue *matched_monitors); void btd_adv_monitor_device_remove(struct btd_adv_monitor_manager *manager, struct btd_device *device); void btd_adv_monitor_power_down(struct btd_adv_monitor_manager *manager); #endif /* __ADV_MONITOR_H */ bluez-5.87/src/PaxHeaders/sdp-xml.h0000644000000000000000000000005014015011623014170 xustar0020 atime=1783093752 20 ctime=1783094106 bluez-5.87/src/sdp-xml.h0000644000000000000000000000055114015011623013652 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2005-2010 Marcel Holtmann * * */ void convert_sdp_record_to_xml(sdp_record_t *rec, void *user_data, void (*append_func) (void *, const char *)); sdp_record_t *sdp_xml_parse_record(const char *data, int size); bluez-5.87/src/PaxHeaders/eir.c0000644000000000000000000000005015200607060013361 xustar0020 atime=1783092683 20 ctime=1783094106 bluez-5.87/src/eir.c0000644000000000000000000003115615200607060013050 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011 Nokia Corporation * Copyright (C) 2011 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/sdp.h" #include "src/shared/util.h" #include "uuid-helper.h" #include "eir.h" #define EIR_OOB_MIN (2 + 6) static void sd_free(void *data) { struct eir_sd *sd = data; free(sd->uuid); g_free(sd); } static void data_free(void *data) { struct eir_ad *ad = data; g_free(ad->data); g_free(ad); } static struct queue * queue_append(struct queue *q, void *data) { if (!q) q = queue_new(); queue_push_tail(q, data); return q; } void eir_data_free(struct eir_data *eir) { queue_destroy(eir->services, g_free); eir->services = NULL; g_free(eir->name); eir->name = NULL; free(eir->hash); eir->hash = NULL; free(eir->randomizer); eir->randomizer = NULL; queue_destroy(eir->msd_list, g_free); eir->msd_list = NULL; queue_destroy(eir->sd_list, sd_free); eir->sd_list = NULL; queue_destroy(eir->data_list, data_free); eir->data_list = NULL; } static void eir_parse_uuid16(struct eir_data *eir, const void *data, uint8_t len) { const uint16_t *uuid16 = data; uuid_t service; char *uuid_str; unsigned int i; service.type = SDP_UUID16; for (i = 0; i < len / 2; i++, uuid16++) { service.value.uuid16 = get_le16(uuid16); uuid_str = bt_uuid2string(&service); if (!uuid_str) continue; eir->services = queue_append(eir->services, uuid_str); } } static void eir_parse_uuid32(struct eir_data *eir, const void *data, uint8_t len) { const uint32_t *uuid32 = data; uuid_t service; char *uuid_str; unsigned int i; service.type = SDP_UUID32; for (i = 0; i < len / 4; i++, uuid32++) { service.value.uuid32 = get_le32(uuid32); uuid_str = bt_uuid2string(&service); if (!uuid_str) continue; eir->services = queue_append(eir->services, uuid_str); } } static void eir_parse_uuid128(struct eir_data *eir, const uint8_t *data, uint8_t len) { const uint8_t *uuid_ptr = data; uuid_t service; char *uuid_str; unsigned int i; int k; service.type = SDP_UUID128; for (i = 0; i < len / 16; i++) { for (k = 0; k < 16; k++) service.value.uuid128.data[k] = uuid_ptr[16 - k - 1]; uuid_str = bt_uuid2string(&service); if (!uuid_str) continue; eir->services = queue_append(eir->services, uuid_str); uuid_ptr += 16; } } static char *name2utf8(const uint8_t *name, uint8_t len) { char utf8_name[HCI_MAX_NAME_LENGTH + 2]; memset(utf8_name, 0, sizeof(utf8_name)); strncpy(utf8_name, (char *) name, len); strtoutf8(utf8_name, len); /* Remove leading and trailing whitespace characters */ g_strstrip(utf8_name); return g_strdup(utf8_name); } static void eir_parse_msd(struct eir_data *eir, const uint8_t *data, uint8_t len) { struct eir_msd *msd; if (len < 2 || len > 2 + sizeof(msd->data)) return; msd = g_malloc(sizeof(*msd)); msd->company = get_le16(data); msd->data_len = len - 2; memcpy(&msd->data, data + 2, msd->data_len); eir->msd_list = queue_append(eir->msd_list, msd); } static void eir_parse_sd(struct eir_data *eir, uuid_t *service, const uint8_t *data, uint8_t len) { struct eir_sd *sd; char *uuid; uuid = bt_uuid2string(service); if (!uuid) return; sd = g_malloc(sizeof(*sd)); sd->uuid = uuid; sd->data_len = len; memcpy(&sd->data, data, sd->data_len); eir->sd_list = queue_append(eir->sd_list, sd); } static void eir_parse_uuid16_data(struct eir_data *eir, const uint8_t *data, uint8_t len) { uuid_t service; if (len < 2 || len > EIR_SD_MAX_LEN) return; service.type = SDP_UUID16; service.value.uuid16 = get_le16(data); eir_parse_sd(eir, &service, data + 2, len - 2); } static void eir_parse_uuid32_data(struct eir_data *eir, const uint8_t *data, uint8_t len) { uuid_t service; if (len < 4 || len > EIR_SD_MAX_LEN) return; service.type = SDP_UUID32; service.value.uuid32 = get_le32(data); eir_parse_sd(eir, &service, data + 4, len - 4); } static void eir_parse_uuid128_data(struct eir_data *eir, const uint8_t *data, uint8_t len) { uuid_t service; int k; if (len < 16 || len > EIR_SD_MAX_LEN) return; service.type = SDP_UUID128; for (k = 0; k < 16; k++) service.value.uuid128.data[k] = data[16 - k - 1]; eir_parse_sd(eir, &service, data + 16, len - 16); } static void eir_parse_data(struct eir_data *eir, uint8_t type, const uint8_t *data, uint8_t len) { struct eir_ad *ad; ad = g_malloc(sizeof(*ad)); ad->type = type; ad->len = len; ad->data = g_malloc(len); memcpy(ad->data, data, len); eir->data_list = queue_append(eir->data_list, ad); if (type == EIR_CSIP_RSI) eir->rsi = true; } void eir_parse(struct eir_data *eir, const uint8_t *eir_data, uint8_t eir_len) { uint16_t len = 0; eir->flags = 0; eir->tx_power = 127; /* No EIR data to parse */ if (eir_data == NULL) return; while (len < eir_len - 1) { uint8_t field_len = eir_data[0]; const uint8_t *data; uint8_t data_len; /* Check for the end of EIR */ if (field_len == 0) break; len += field_len + 1; /* Do not continue EIR Data parsing if got incorrect length */ if (len > eir_len) break; data = &eir_data[2]; data_len = field_len - 1; switch (eir_data[1]) { case EIR_UUID16_SOME: case EIR_UUID16_ALL: eir_parse_uuid16(eir, data, data_len); break; case EIR_UUID32_SOME: case EIR_UUID32_ALL: eir_parse_uuid32(eir, data, data_len); break; case EIR_UUID128_SOME: case EIR_UUID128_ALL: eir_parse_uuid128(eir, data, data_len); break; case EIR_FLAGS: if (data_len > 0) eir->flags = *data; break; case EIR_NAME_SHORT: case EIR_NAME_COMPLETE: case EIR_BC_NAME: /* Some vendors put a NUL byte terminator into * the name */ while (data_len > 0 && data[data_len - 1] == '\0') data_len--; g_free(eir->name); eir->name = name2utf8(data, data_len); eir->name_complete = eir_data[1] != EIR_NAME_SHORT; break; case EIR_TX_POWER: if (data_len < 1) break; eir->tx_power = (int8_t) data[0]; break; case EIR_CLASS_OF_DEV: if (data_len < 3) break; eir->class = data[0] | (data[1] << 8) | (data[2] << 16); break; case EIR_GAP_APPEARANCE: if (data_len < 2) break; eir->appearance = get_le16(data); break; case EIR_SSP_HASH: if (data_len < 16) break; eir->hash = util_memdup(data, 16); break; case EIR_SSP_RANDOMIZER: if (data_len < 16) break; eir->randomizer = util_memdup(data, 16); break; case EIR_DEVICE_ID: if (data_len < 8) break; eir->did_source = data[0] | (data[1] << 8); eir->did_vendor = data[2] | (data[3] << 8); eir->did_product = data[4] | (data[5] << 8); eir->did_version = data[6] | (data[7] << 8); break; case EIR_SVC_DATA16: eir_parse_uuid16_data(eir, data, data_len); break; case EIR_SVC_DATA32: eir_parse_uuid32_data(eir, data, data_len); break; case EIR_SVC_DATA128: eir_parse_uuid128_data(eir, data, data_len); break; case EIR_MANUFACTURER_DATA: eir_parse_msd(eir, data, data_len); break; default: eir_parse_data(eir, eir_data[1], data, data_len); break; } eir_data += field_len + 1; } } int eir_parse_oob(struct eir_data *eir, uint8_t *eir_data, uint16_t eir_len) { if (eir_len < EIR_OOB_MIN) return -1; if (eir_len != get_le16(eir_data)) return -1; eir_data += sizeof(uint16_t); eir_len -= sizeof(uint16_t); memcpy(&eir->addr, eir_data, sizeof(bdaddr_t)); eir_data += sizeof(bdaddr_t); eir_len -= sizeof(bdaddr_t); /* optional OOB EIR data */ if (eir_len > 0) eir_parse(eir, eir_data, eir_len); return 0; } #define SIZEOF_UUID128 16 static void eir_generate_uuid128(sdp_list_t *list, uint8_t *ptr, uint16_t *eir_len) { int i, k, uuid_count = 0; uint16_t len = *eir_len; uint8_t *uuid128; bool truncated = false; /* Store UUIDs in place, skip 2 bytes to write type and length later */ uuid128 = ptr + 2; for (; list; list = list->next) { sdp_record_t *rec = list->data; uuid_t *uuid = &rec->svclass; uint8_t *uuid128_data = uuid->value.uuid128.data; if (uuid->type != SDP_UUID128) continue; /* Stop if not enough space to put next UUID128 */ if ((len + 2 + SIZEOF_UUID128) > HCI_MAX_EIR_LENGTH) { truncated = true; break; } /* Check for duplicates, EIR data is Little Endian */ for (i = 0; i < uuid_count; i++) { for (k = 0; k < SIZEOF_UUID128; k++) { if (uuid128[i * SIZEOF_UUID128 + k] != uuid128_data[SIZEOF_UUID128 - 1 - k]) break; } if (k == SIZEOF_UUID128) break; } if (i < uuid_count) continue; /* EIR data is Little Endian */ for (k = 0; k < SIZEOF_UUID128; k++) uuid128[uuid_count * SIZEOF_UUID128 + k] = uuid128_data[SIZEOF_UUID128 - 1 - k]; len += SIZEOF_UUID128; uuid_count++; } if (uuid_count > 0 || truncated) { /* EIR Data length */ ptr[0] = (uuid_count * SIZEOF_UUID128) + 1; /* EIR Data type */ ptr[1] = truncated ? EIR_UUID128_SOME : EIR_UUID128_ALL; len += 2; *eir_len = len; } } int eir_create_oob(const bdaddr_t *addr, const char *name, uint32_t cod, const uint8_t *hash, const uint8_t *randomizer, uint16_t did_vendor, uint16_t did_product, uint16_t did_version, uint16_t did_source, sdp_list_t *uuids, uint8_t *data) { sdp_list_t *l; uint8_t *ptr = data; uint16_t eir_optional_len = 0; uint16_t eir_total_len; uint16_t uuid16[HCI_MAX_EIR_LENGTH / 2]; int i, uuid_count = 0; bool truncated = false; size_t name_len; eir_total_len = sizeof(uint16_t) + sizeof(bdaddr_t); ptr += sizeof(uint16_t); memcpy(ptr, addr, sizeof(bdaddr_t)); ptr += sizeof(bdaddr_t); if (cod > 0) { uint8_t class[3]; class[0] = (uint8_t) cod; class[1] = (uint8_t) (cod >> 8); class[2] = (uint8_t) (cod >> 16); *ptr++ = 4; *ptr++ = EIR_CLASS_OF_DEV; memcpy(ptr, class, sizeof(class)); ptr += sizeof(class); eir_optional_len += sizeof(class) + 2; } if (hash) { *ptr++ = 17; *ptr++ = EIR_SSP_HASH; memcpy(ptr, hash, 16); ptr += 16; eir_optional_len += 16 + 2; } if (randomizer) { *ptr++ = 17; *ptr++ = EIR_SSP_RANDOMIZER; memcpy(ptr, randomizer, 16); ptr += 16; eir_optional_len += 16 + 2; } name_len = strlen(name); if (name_len > 0) { /* EIR Data type */ if (name_len > 48) { name_len = 48; ptr[1] = EIR_NAME_SHORT; } else ptr[1] = EIR_NAME_COMPLETE; /* EIR Data length */ ptr[0] = name_len + 1; memcpy(ptr + 2, name, name_len); eir_optional_len += (name_len + 2); ptr += (name_len + 2); } if (did_vendor != 0x0000) { *ptr++ = 9; *ptr++ = EIR_DEVICE_ID; *ptr++ = (did_source & 0x00ff); *ptr++ = (did_source & 0xff00) >> 8; *ptr++ = (did_vendor & 0x00ff); *ptr++ = (did_vendor & 0xff00) >> 8; *ptr++ = (did_product & 0x00ff); *ptr++ = (did_product & 0xff00) >> 8; *ptr++ = (did_version & 0x00ff); *ptr++ = (did_version & 0xff00) >> 8; eir_optional_len += 10; } /* Group all UUID16 types */ for (l = uuids; l != NULL; l = l->next) { sdp_record_t *rec = l->data; uuid_t *uuid = &rec->svclass; if (uuid->type != SDP_UUID16) continue; if (uuid->value.uuid16 < 0x1100) continue; if (uuid->value.uuid16 == PNP_INFO_SVCLASS_ID) continue; /* Stop if not enough space to put next UUID16 */ if ((eir_optional_len + 2 + sizeof(uint16_t)) > HCI_MAX_EIR_LENGTH) { truncated = true; break; } /* Check for duplicates */ for (i = 0; i < uuid_count; i++) if (uuid16[i] == uuid->value.uuid16) break; if (i < uuid_count) continue; uuid16[uuid_count++] = uuid->value.uuid16; eir_optional_len += sizeof(uint16_t); } if (uuid_count > 0) { /* EIR Data length */ ptr[0] = (uuid_count * sizeof(uint16_t)) + 1; /* EIR Data type */ ptr[1] = truncated ? EIR_UUID16_SOME : EIR_UUID16_ALL; ptr += 2; eir_optional_len += 2; for (i = 0; i < uuid_count; i++) { *ptr++ = (uuid16[i] & 0x00ff); *ptr++ = (uuid16[i] & 0xff00) >> 8; } } /* Group all UUID128 types */ if (eir_optional_len <= HCI_MAX_EIR_LENGTH - 2) eir_generate_uuid128(uuids, ptr, &eir_optional_len); eir_total_len += eir_optional_len; /* store total length */ put_le16(eir_total_len, data); return eir_total_len; } static bool match_sd_uuid(const void *data, const void *user_data) { const struct eir_sd *sd = data; const char *uuid = user_data; return strcmp(sd->uuid, uuid) == 0; } struct eir_sd *eir_get_service_data(struct eir_data *eir, const char *uuid) { if (!eir || !uuid) return NULL; return queue_find(eir->sd_list, match_sd_uuid, uuid); } bluez-5.87/src/PaxHeaders/error.c0000644000000000000000000000005015063045254013743 xustar0020 atime=1783093765 20 ctime=1783094106 bluez-5.87/src/error.c0000644000000000000000000001335015063045254013426 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright (C) 2007-2008 Fabien Chevalier * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "gdbus/gdbus.h" #include "error.h" DBusMessage *btd_error_invalid_args(DBusMessage *msg) { return btd_error_invalid_args_str(msg, "Invalid arguments in method call"); } DBusMessage *btd_error_invalid_args_str(DBusMessage *msg, const char *str) { return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments", "%s", str); } DBusMessage *btd_error_busy(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", "Operation already in progress"); } DBusMessage *btd_error_already_exists(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyExists", "Already Exists"); } DBusMessage *btd_error_not_supported(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NotSupported", "Operation is not supported"); } DBusMessage *btd_error_not_connected(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NotConnected", "Not Connected"); } DBusMessage *btd_error_already_connected(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyConnected", "Already Connected"); } DBusMessage *btd_error_in_progress(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", "In Progress"); } DBusMessage *btd_error_in_progress_str(DBusMessage *msg, const char *str) { return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", "%s", str); } DBusMessage *btd_error_not_available(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable", "Operation currently not available"); } DBusMessage *btd_error_not_available_str(DBusMessage *msg, const char *str) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable", "%s", str); } DBusMessage *btd_error_does_not_exist(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExist", "Does Not Exist"); } DBusMessage *btd_error_not_authorized(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAuthorized", "Operation Not Authorized"); } DBusMessage *btd_error_not_permitted(DBusMessage *msg, const char *str) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NotPermitted", "%s", str); } DBusMessage *btd_error_no_such_adapter(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NoSuchAdapter", "No such adapter"); } DBusMessage *btd_error_agent_not_available(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".AgentNotAvailable", "Agent Not Available"); } DBusMessage *btd_error_not_ready(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady", "Resource Not Ready"); } DBusMessage *btd_error_not_ready_str(DBusMessage *msg, const char *str) { return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady", "%s", str); } DBusMessage *btd_error_profile_unavailable(DBusMessage *msg) { return g_dbus_create_error(msg, ERROR_INTERFACE_BREDR ".ProfileUnavailable", "No more profiles to connect to"); } DBusMessage *btd_error_failed(DBusMessage *msg, const char *str) { return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", "%s", str); } static const char *btd_error_bredr_str(int err) { switch (-err) { case EALREADY: case EISCONN: return ERR_BREDR_CONN_ALREADY_CONNECTED; case EHOSTDOWN: return ERR_BREDR_CONN_PAGE_TIMEOUT; case EIO: return ERR_BREDR_CONN_CREATE_SOCKET; case EINVAL: return ERR_BREDR_CONN_INVALID_ARGUMENTS; case EHOSTUNREACH: return ERR_BREDR_CONN_ADAPTER_NOT_POWERED; case EOPNOTSUPP: case EPROTONOSUPPORT: return ERR_BREDR_CONN_NOT_SUPPORTED; case EBADFD: return ERR_BREDR_CONN_BAD_SOCKET; case ENOMEM: return ERR_BREDR_CONN_MEMORY_ALLOC; case EBUSY: return ERR_BREDR_CONN_BUSY; case EMLINK: return ERR_BREDR_CONN_CNCR_CONNECT_LIMIT; case ETIMEDOUT: return ERR_BREDR_CONN_TIMEOUT; case ECONNREFUSED: return ERR_BREDR_CONN_REFUSED; case ECONNRESET: return ERR_BREDR_CONN_ABORT_BY_REMOTE; case ECONNABORTED: return ERR_BREDR_CONN_ABORT_BY_LOCAL; case EPROTO: return ERR_BREDR_CONN_LMP_PROTO_ERROR; case EBADE: return ERR_BREDR_CONN_KEY_MISSING; default: return ERR_BREDR_CONN_UNKNOWN; } } static const char *btd_error_le_str(int err) { switch (-err) { case EINVAL: return ERR_LE_CONN_INVALID_ARGUMENTS; case EHOSTUNREACH: return ERR_LE_CONN_ADAPTER_NOT_POWERED; case EOPNOTSUPP: case EPROTONOSUPPORT: return ERR_LE_CONN_NOT_SUPPORTED; case EALREADY: case EISCONN: return ERR_LE_CONN_ALREADY_CONNECTED; case EBADFD: return ERR_LE_CONN_BAD_SOCKET; case ENOMEM: return ERR_LE_CONN_MEMORY_ALLOC; case EBUSY: return ERR_LE_CONN_BUSY; case ECONNREFUSED: return ERR_LE_CONN_REFUSED; case EIO: return ERR_LE_CONN_CREATE_SOCKET; case ETIMEDOUT: return ERR_LE_CONN_TIMEOUT; case EMLINK: return ERR_LE_CONN_SYNC_CONNECT_LIMIT; case ECONNRESET: return ERR_LE_CONN_ABORT_BY_REMOTE; case ECONNABORTED: return ERR_LE_CONN_ABORT_BY_LOCAL; case EPROTO: return ERR_LE_CONN_LL_PROTO_ERROR; case EBADE: return ERR_LE_CONN_KEY_MISSING; default: return ERR_LE_CONN_UNKNOWN; } } DBusMessage *btd_error_bredr_errno(DBusMessage *msg, int err) { switch (err) { case ENOPROTOOPT: return btd_error_profile_unavailable(msg); default: return btd_error_failed(msg, btd_error_bredr_str(err)); } } DBusMessage *btd_error_le_errno(DBusMessage *msg, int err) { return btd_error_failed(msg, btd_error_le_str(err)); } bluez-5.87/src/PaxHeaders/genbuiltin0000644000000000000000000000005014572354773014546 xustar0020 atime=1783092780 20 ctime=1783094110 bluez-5.87/src/genbuiltin0000755000000000000000000000041414572354773014231 0ustar00rootroot#!/bin/sh for i in $* do echo "extern const struct bluetooth_plugin_desc __bluetooth_builtin_$i;" done echo echo "static const struct bluetooth_plugin_desc *__bluetooth_builtin[] = {" for i in $* do echo " &__bluetooth_builtin_$i," done echo " NULL" echo "};" bluez-5.87/src/PaxHeaders/log.c0000644000000000000000000000005015063045254013373 xustar0020 atime=1783093150 20 ctime=1783094101 bluez-5.87/src/log.c0000644000000000000000000000647415063045254013067 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "src/shared/util.h" #include "src/shared/log.h" #include "log.h" #define LOG_IDENT "bluetoothd" static void monitor_log(uint16_t index, int priority, const char *format, va_list ap) { bt_log_vprintf(index, LOG_IDENT, priority, format, ap); } void info(const char *format, ...) { va_list ap; va_start(ap, format); vsyslog(LOG_INFO, format, ap); va_end(ap); va_start(ap, format); monitor_log(HCI_DEV_NONE, LOG_INFO, format, ap); va_end(ap); } void btd_log(uint16_t index, int priority, const char *format, ...) { va_list ap; va_start(ap, format); vsyslog(priority, format, ap); va_end(ap); va_start(ap, format); monitor_log(index, priority, format, ap); va_end(ap); } void btd_error(uint16_t index, const char *format, ...) { va_list ap; va_start(ap, format); vsyslog(LOG_ERR, format, ap); va_end(ap); va_start(ap, format); monitor_log(index, LOG_ERR, format, ap); va_end(ap); } void btd_warn(uint16_t index, const char *format, ...) { va_list ap; va_start(ap, format); vsyslog(LOG_WARNING, format, ap); va_end(ap); va_start(ap, format); monitor_log(index, LOG_WARNING, format, ap); va_end(ap); } void btd_info(uint16_t index, const char *format, ...) { va_list ap; va_start(ap, format); vsyslog(LOG_INFO, format, ap); va_end(ap); va_start(ap, format); monitor_log(index, LOG_INFO, format, ap); va_end(ap); } void btd_debug(uint16_t index, const char *format, ...) { va_list ap; va_start(ap, format); vsyslog(LOG_DEBUG, format, ap); va_end(ap); va_start(ap, format); monitor_log(index, LOG_DEBUG, format, ap); va_end(ap); } extern struct btd_debug_desc __start___debug[]; extern struct btd_debug_desc __stop___debug[]; static char **enabled = NULL; static gboolean is_enabled(const struct btd_debug_desc *desc) { int i; if (enabled == NULL) return 0; for (i = 0; enabled[i] != NULL; i++) if (desc->file != NULL && g_pattern_match_simple(enabled[i], desc->file) == TRUE) return 1; return 0; } void __btd_enable_debug(struct btd_debug_desc *start, struct btd_debug_desc *stop) { struct btd_debug_desc *desc; if (start == NULL || stop == NULL) return; for (desc = start; desc < stop; desc++) { if (is_enabled(desc)) desc->flags |= BTD_DEBUG_FLAG_PRINT; } } void __btd_toggle_debug(void) { struct btd_debug_desc *desc; for (desc = __start___debug; desc < __stop___debug; desc++) desc->flags |= BTD_DEBUG_FLAG_PRINT; } void __btd_log_init(const char *debug, int detach) { int option = LOG_NDELAY | LOG_PID; if (debug != NULL) enabled = g_strsplit_set(debug, ":, ", 0); __btd_enable_debug(__start___debug, __stop___debug); bt_log_open(); if (!detach) option |= LOG_PERROR; openlog(LOG_IDENT, option, LOG_DAEMON); info("Bluetooth daemon %s", VERSION); } void __btd_log_cleanup(void) { closelog(); bt_log_close(); g_strfreev(enabled); } bluez-5.87/src/PaxHeaders/advertising.c0000644000000000000000000000005015221752714015133 xustar0020 atime=1783092685 20 ctime=1783094106 bluez-5.87/src/advertising.c0000644000000000000000000014344715221752714014631 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Google Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/mgmt.h" #include "bluetooth/sdp.h" #include "adapter.h" #include "dbus-common.h" #include "error.h" #include "log.h" #include "eir.h" #include "btd.h" #include "src/shared/ad.h" #include "src/shared/mgmt.h" #include "src/shared/queue.h" #include "src/shared/timeout.h" #include "src/shared/util.h" #include "src/shared/crypto.h" #include "advertising.h" #define LE_ADVERTISING_MGR_IFACE "org.bluez.LEAdvertisingManager1" #define LE_ADVERTISEMENT_IFACE "org.bluez.LEAdvertisement1" struct btd_adv_manager { struct btd_adapter *adapter; struct queue *clients; struct mgmt *mgmt; uint16_t mgmt_index; uint8_t max_adv_len; uint8_t max_scan_rsp_len; uint8_t max_ads; uint32_t supported_flags; uint64_t instance_bitmap; bool extended_add_cmds; int8_t min_tx_power; int8_t max_tx_power; }; #define AD_TYPE_BROADCAST 0 #define AD_TYPE_PERIPHERAL 1 /* BLUETOOTH SPECIFICATION Version 5.2 | Vol 4, Part E, page 2585 * defines tx power value indicating no preference */ #define ADV_TX_POWER_NO_PREFERENCE 0x7F struct btd_adv_client { struct btd_adv_manager *manager; char *owner; char *path; char *name; uint16_t appearance; uint16_t duration; uint16_t timeout; uint16_t discoverable_to; unsigned int to_id; unsigned int disc_to_id; unsigned int add_adv_id; GDBusClient *client; GDBusProxy *proxy; DBusMessage *reg; uint8_t type; /* Advertising type */ uint32_t flags; struct bt_ad *data; struct bt_ad *scan; uint8_t instance; uint32_t min_interval; uint32_t max_interval; int8_t tx_power; mgmt_request_func_t refresh_done_func; }; struct dbus_obj_match { const char *owner; const char *path; }; static bool match_client(const void *a, const void *b) { const struct btd_adv_client *client = a; const struct dbus_obj_match *match = b; if (match->owner && g_strcmp0(client->owner, match->owner)) return false; if (match->path && g_strcmp0(client->path, match->path)) return false; return true; } static void client_free(void *data) { struct btd_adv_client *client = data; if (client->to_id > 0) timeout_remove(client->to_id); if (client->disc_to_id > 0) timeout_remove(client->disc_to_id); if (client->client) { g_dbus_client_set_disconnect_watch(client->client, NULL, NULL); g_dbus_client_unref(client->client); } if (client->reg) { DBusMessage *reply; reply = btd_error_failed(client->reg, "Failed to complete registration"); g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(client->reg); client->reg = NULL; } if (client->add_adv_id) mgmt_cancel(client->manager->mgmt, client->add_adv_id); if (client->instance) util_clear_uid(&client->manager->instance_bitmap, client->instance); bt_ad_unref(client->data); bt_ad_unref(client->scan); g_dbus_proxy_unref(client->proxy); if (client->owner) g_free(client->owner); if (client->path) g_free(client->path); free(client->name); free(client); } static gboolean client_free_idle_cb(void *data) { client_free(data); return FALSE; } static void client_release(void *data) { struct btd_adv_client *client = data; DBG("Releasing advertisement %s, %s", client->owner, client->path); g_dbus_proxy_method_call(client->proxy, "Release", NULL, NULL, NULL, NULL); } static void client_destroy(void *data) { client_release(data); client_free(data); } static void remove_advertising(struct btd_adv_manager *manager, uint8_t instance) { struct mgmt_cp_remove_advertising cp; if (instance) DBG("instance %u", instance); else DBG("all instances"); cp.instance = instance; mgmt_send(manager->mgmt, MGMT_OP_REMOVE_ADVERTISING, manager->mgmt_index, sizeof(cp), &cp, NULL, NULL, NULL); } static void client_remove(struct btd_adv_client *client) { struct mgmt_cp_remove_advertising cp; g_dbus_client_set_proxy_handlers(client->client, NULL, NULL, NULL, client); g_dbus_client_set_disconnect_watch(client->client, NULL, NULL); cp.instance = client->instance; mgmt_send(client->manager->mgmt, MGMT_OP_REMOVE_ADVERTISING, client->manager->mgmt_index, sizeof(cp), &cp, NULL, NULL, NULL); queue_remove(client->manager->clients, client); g_idle_add(client_free_idle_cb, client); g_dbus_emit_property_changed(btd_get_dbus_connection(), adapter_get_path(client->manager->adapter), LE_ADVERTISING_MGR_IFACE, "SupportedInstances"); g_dbus_emit_property_changed(btd_get_dbus_connection(), adapter_get_path(client->manager->adapter), LE_ADVERTISING_MGR_IFACE, "ActiveInstances"); } static void client_disconnect_cb(DBusConnection *conn, void *user_data) { struct btd_adv_client *client = user_data; DBG("Client disconnected"); client_remove(client); } static bool parse_type(DBusMessageIter *iter, struct btd_adv_client *client) { const char *msg_type; if (!iter) return true; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return false; dbus_message_iter_get_basic(iter, &msg_type); if (!g_strcmp0(msg_type, "broadcast")) { client->type = AD_TYPE_BROADCAST; return true; } if (!g_strcmp0(msg_type, "peripheral")) { client->type = AD_TYPE_PERIPHERAL; return true; } return false; } static bool parse_service_uuids(DBusMessageIter *iter, struct bt_ad *ad) { DBusMessageIter ariter; if (!iter) { bt_ad_clear_service_uuid(ad); return true; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return false; dbus_message_iter_recurse(iter, &ariter); bt_ad_clear_service_uuid(ad); while (dbus_message_iter_get_arg_type(&ariter) == DBUS_TYPE_STRING) { const char *uuid_str; bt_uuid_t uuid; dbus_message_iter_get_basic(&ariter, &uuid_str); DBG("Adding ServiceUUID: %s", uuid_str); if (bt_string_to_uuid(&uuid, uuid_str) < 0) goto fail; if (!bt_ad_add_service_uuid(ad, &uuid)) goto fail; dbus_message_iter_next(&ariter); } return true; fail: bt_ad_clear_service_uuid(ad); return false; } static bool parse_service_uuids_ad(DBusMessageIter *iter, struct btd_adv_client *client) { return parse_service_uuids(iter, client->data); } static bool parse_service_uuids_sr(DBusMessageIter *iter, struct btd_adv_client *client) { return parse_service_uuids(iter, client->scan); } static bool parse_solicit_uuids(DBusMessageIter *iter, struct bt_ad *ad) { DBusMessageIter ariter; if (!iter) { bt_ad_clear_solicit_uuid(ad); return true; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return false; dbus_message_iter_recurse(iter, &ariter); bt_ad_clear_solicit_uuid(ad); while (dbus_message_iter_get_arg_type(&ariter) == DBUS_TYPE_STRING) { const char *uuid_str; bt_uuid_t uuid; dbus_message_iter_get_basic(&ariter, &uuid_str); DBG("Adding SolicitUUID: %s", uuid_str); if (bt_string_to_uuid(&uuid, uuid_str) < 0) goto fail; if (!bt_ad_add_solicit_uuid(ad, &uuid)) goto fail; dbus_message_iter_next(&ariter); } return true; fail: bt_ad_clear_solicit_uuid(ad); return false; } static bool parse_solicit_uuids_ad(DBusMessageIter *iter, struct btd_adv_client *client) { return parse_solicit_uuids(iter, client->data); } static bool parse_solicit_uuids_sr(DBusMessageIter *iter, struct btd_adv_client *client) { return parse_solicit_uuids(iter, client->scan); } static bool parse_manufacturer_data(DBusMessageIter *iter, struct bt_ad *ad) { DBusMessageIter entries; if (!iter) { bt_ad_clear_manufacturer_data(ad); return true; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return false; dbus_message_iter_recurse(iter, &entries); bt_ad_clear_manufacturer_data(ad); while (dbus_message_iter_get_arg_type(&entries) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter value, entry, array; uint16_t manuf_id; uint8_t *manuf_data; int len; dbus_message_iter_recurse(&entries, &entry); dbus_message_iter_get_basic(&entry, &manuf_id); dbus_message_iter_next(&entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) goto fail; dbus_message_iter_recurse(&entry, &value); if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_ARRAY) goto fail; dbus_message_iter_recurse(&value, &array); if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_BYTE) goto fail; dbus_message_iter_get_fixed_array(&array, &manuf_data, &len); DBG("Adding ManufacturerData for %04x", manuf_id); if (!bt_ad_add_manufacturer_data(ad, manuf_id, manuf_data, len)) goto fail; dbus_message_iter_next(&entries); } return true; fail: bt_ad_clear_manufacturer_data(ad); return false; } static bool parse_manufacturer_data_ad(DBusMessageIter *iter, struct btd_adv_client *client) { return parse_manufacturer_data(iter, client->data); } static bool parse_manufacturer_data_sr(DBusMessageIter *iter, struct btd_adv_client *client) { return parse_manufacturer_data(iter, client->scan); } static bool parse_service_data(DBusMessageIter *iter, struct bt_ad *ad) { DBusMessageIter entries; if (!iter) { bt_ad_clear_service_data(ad); return true; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return false; dbus_message_iter_recurse(iter, &entries); bt_ad_clear_service_data(ad); while (dbus_message_iter_get_arg_type(&entries) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter value, entry, array; const char *uuid_str; bt_uuid_t uuid; uint8_t *service_data; int len; dbus_message_iter_recurse(&entries, &entry); dbus_message_iter_get_basic(&entry, &uuid_str); if (bt_string_to_uuid(&uuid, uuid_str) < 0) goto fail; dbus_message_iter_next(&entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) goto fail; dbus_message_iter_recurse(&entry, &value); if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_ARRAY) goto fail; dbus_message_iter_recurse(&value, &array); if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_BYTE) goto fail; dbus_message_iter_get_fixed_array(&array, &service_data, &len); DBG("Adding ServiceData for %s", uuid_str); if (!bt_ad_add_service_data(ad, &uuid, service_data, len)) goto fail; dbus_message_iter_next(&entries); } return true; fail: bt_ad_clear_service_data(ad); return false; } static bool parse_service_data_ad(DBusMessageIter *iter, struct btd_adv_client *client) { return parse_service_data(iter, client->data); } static bool parse_service_data_sr(DBusMessageIter *iter, struct btd_adv_client *client) { return parse_service_data(iter, client->scan); } static bool validate_rsi(const uint8_t *data, uint8_t len) { struct bt_crypto *crypto; uint8_t zero[16] = {}; uint8_t hash[3]; bool ret; if (!data || len != 6) return false; /* Check if a valid SIRK has been set */ if (!memcmp(btd_opts.csis.sirk, zero, sizeof(zero))) return false; crypto = bt_crypto_new(); if (!crypto) return false; /* Generate a hash using SIRK and prand as input */ ret = bt_crypto_sih(crypto, btd_opts.csis.sirk, data + 3, hash); if (!ret) goto done; /* Check if hash matches */ ret = !(memcmp(hash, data, 3)); if (!ret) { error("RSI set invalid: hash mismatch"); goto done; } DBG("RSI validated"); done: bt_crypto_unref(crypto); return ret; } static bool set_rsi(struct btd_adv_client *client) { struct bt_crypto *crypto; uint8_t zero[16] = {}; struct bt_ad_data rsi = { .type = BT_AD_CSIP_RSI }; struct bt_ad_data *ad; uint8_t data[6]; bool ret; /* Check if a valid SIRK has been set */ if (!memcmp(btd_opts.csis.sirk, zero, sizeof(zero))) return false; if (!client) return true; /* Check if RSI needs to be set or data already contains RSI data */ ad = bt_ad_has_data(client->data, &rsi); if (ad) { ret = validate_rsi(ad->data, ad->len); return ret; } crypto = bt_crypto_new(); if (!crypto) return false; ret = bt_crypto_rsi(crypto, btd_opts.csis.sirk, data); bt_crypto_unref(crypto); if (!ret) return ret; return bt_ad_add_data(client->data, BT_AD_CSIP_RSI, data, sizeof(data)); } static struct adv_include { uint8_t flag; const char *name; bool (*set)(struct btd_adv_client *client); } includes[] = { { MGMT_ADV_FLAG_TX_POWER, "tx-power" }, { MGMT_ADV_FLAG_APPEARANCE, "appearance" }, { MGMT_ADV_FLAG_LOCAL_NAME, "local-name" }, { 0 , "rsi", set_rsi }, { }, }; static bool parse_includes(DBusMessageIter *iter, struct btd_adv_client *client) { DBusMessageIter entries; if (!iter) { client->flags = 0; return true; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return false; dbus_message_iter_recurse(iter, &entries); /* Reset flags before parsing */ client->flags = 0; while (dbus_message_iter_get_arg_type(&entries) == DBUS_TYPE_STRING) { const char *str; struct adv_include *inc; dbus_message_iter_get_basic(&entries, &str); for (inc = includes; inc && inc->name; inc++) { if (strcmp(str, inc->name)) continue; if (inc->set && inc->set(client)) { DBG("Including Feature: %s", str); continue; } if (!(client->manager->supported_flags & inc->flag)) continue; DBG("Including Feature: %s", str); client->flags |= inc->flag; } dbus_message_iter_next(&entries); } return true; } static bool parse_local_name(DBusMessageIter *iter, struct btd_adv_client *client) { const char *name; if (!iter) { free(client->name); client->name = NULL; return true; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return false; if (client->flags & MGMT_ADV_FLAG_LOCAL_NAME) { error("Local name already included"); return false; } dbus_message_iter_get_basic(iter, &name); free(client->name); /* Treat empty string the same as omitting since there is no point on * adding a empty name as AD data as it just take space that could be * used for something else. */ if (name[0] != '\0') client->name = strdup(name); else client->name = NULL; return true; } static bool parse_appearance(DBusMessageIter *iter, struct btd_adv_client *client) { if (!iter) { client->appearance = 0; return true; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) return false; if (client->flags & MGMT_ADV_FLAG_APPEARANCE) { error("Appearance already included"); return false; } dbus_message_iter_get_basic(iter, &client->appearance); return true; } static bool parse_duration(DBusMessageIter *iter, struct btd_adv_client *client) { if (!iter) { client->duration = 0; return true; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) return false; dbus_message_iter_get_basic(iter, &client->duration); return true; } static bool client_timeout(void *user_data) { struct btd_adv_client *client = user_data; DBG(""); client->to_id = 0; client_release(client); client_remove(client); return FALSE; } static bool parse_timeout(DBusMessageIter *iter, struct btd_adv_client *client) { if (!iter) { client->timeout = 0; timeout_remove(client->to_id); client->to_id = 0; return true; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) return false; dbus_message_iter_get_basic(iter, &client->timeout); if (client->to_id) timeout_remove(client->to_id); if (client->timeout > 0) client->to_id = timeout_add_seconds(client->timeout, client_timeout, client, NULL); return true; } static bool parse_data(DBusMessageIter *iter, struct bt_ad *ad) { DBusMessageIter entries; if (!iter) { bt_ad_clear_data(ad); return true; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return false; dbus_message_iter_recurse(iter, &entries); bt_ad_clear_data(ad); while (dbus_message_iter_get_arg_type(&entries) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter value, entry, array; uint8_t type; uint8_t *data; int len; dbus_message_iter_recurse(&entries, &entry); dbus_message_iter_get_basic(&entry, &type); dbus_message_iter_next(&entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) goto fail; dbus_message_iter_recurse(&entry, &value); if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_ARRAY) goto fail; dbus_message_iter_recurse(&value, &array); if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_BYTE) goto fail; dbus_message_iter_get_fixed_array(&array, &data, &len); DBG("Adding Data for type 0x%02x len %u", type, len); switch (type) { case BT_AD_CSIP_RSI: if (!validate_rsi(data, len)) goto fail; break; } if (!bt_ad_add_data(ad, type, data, len)) goto fail; dbus_message_iter_next(&entries); } return true; fail: bt_ad_clear_data(ad); return false; } static bool parse_data_ad(DBusMessageIter *iter, struct btd_adv_client *client) { return parse_data(iter, client->data); } static bool parse_data_sr(DBusMessageIter *iter, struct btd_adv_client *client) { return parse_data(iter, client->scan); } static bool set_flags(struct btd_adv_client *client, uint8_t flags) { /* Set BR/EDR Not Supported for LE only */ if (!btd_adapter_get_bredr(client->manager->adapter)) flags |= BT_AD_FLAG_NO_BREDR; /* Set BR/EDR Not Supported if adapter is not discoverable but the * instance is. */ if (!btd_adapter_get_discoverable(client->manager->adapter)) flags |= BT_AD_FLAG_NO_BREDR; if (!bt_ad_add_flags(client->data, &flags, 1)) return false; return true; } static bool parse_discoverable(DBusMessageIter *iter, struct btd_adv_client *client) { uint8_t flags; dbus_bool_t discoverable; if (!iter) { bt_ad_clear_flags(client->data); return true; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) return false; dbus_message_iter_get_basic(iter, &discoverable); /* For broadcast mode, need not add any flags * just return true without adding flags. */ if (discoverable) flags = BT_AD_FLAG_GENERAL; else if (client->type != AD_TYPE_BROADCAST) flags = 0x00; else return true; if (!set_flags(client , flags)) goto fail; DBG("Adding Flags 0x%02x", flags); return true; fail: bt_ad_clear_flags(client->data); return false; } static size_t calc_max_adv_len(struct btd_adv_client *client, uint32_t flags) { size_t max = client->manager->max_adv_len; /* * Flags which reduce the amount of space available for advertising. * See doc/mgmt-api.txt */ if (flags & MGMT_ADV_FLAG_TX_POWER) max -= 3; if (flags & (MGMT_ADV_FLAG_DISCOV | MGMT_ADV_FLAG_LIMITED_DISCOV | MGMT_ADV_FLAG_MANAGED_FLAGS)) max -= 3; if (flags & MGMT_ADV_FLAG_APPEARANCE) max -= 4; return max; } static uint8_t *generate_adv_data(struct btd_adv_client *client, uint32_t *flags, size_t *len) { if ((*flags & MGMT_ADV_FLAG_APPEARANCE) || client->appearance != UINT16_MAX) { uint16_t appearance; appearance = client->appearance; if (appearance == UINT16_MAX) /* TODO: Get the appearance from the adaptor once * supported. */ appearance = 0x000; bt_ad_add_appearance(client->data, appearance); } /* Scan response shall not be used when connectable and setting a * secondary PHY since that would end up using EA types instead of * legacy which doesn't support being connectable and scannable * simultaneously. */ if ((*flags & MGMT_ADV_FLAG_CONNECTABLE) && (*flags & MGMT_ADV_FLAG_SEC_MASK) && client->name) { *flags &= ~MGMT_ADV_FLAG_LOCAL_NAME; bt_ad_add_name(client->data, client->name); } return bt_ad_generate(client->data, len); } static uint8_t *generate_scan_rsp(struct btd_adv_client *client, uint32_t *flags, size_t *len) { if (client->name) { *flags &= ~MGMT_ADV_FLAG_LOCAL_NAME; bt_ad_add_name(client->scan, client->name); } else if (bt_ad_is_empty(client->scan)) { *len = 0; return NULL; } return bt_ad_generate(client->scan, len); } static bool adv_client_has_scan_response(struct btd_adv_client *client, uint32_t flags) { /* The local name isn't added into the bt_ad structure until * generate_scan_rsp is called, so we must check these conditions as * well. */ if (!(flags & MGMT_ADV_FLAG_LOCAL_NAME) && !client->name && bt_ad_is_empty(client->scan)) { return false; } /* Scan response shall not be used when connectable and setting a * secondary PHY since that would end up using EA types instead of * legacy which doesn't support being connectable and scannable * simultaneously. */ if (flags & MGMT_ADV_FLAG_CONNECTABLE && flags & MGMT_ADV_FLAG_SEC_MASK) return false; return true; } static int get_adv_flags(struct btd_adv_client *client) { uint32_t flags = 0; if (client->type == AD_TYPE_PERIPHERAL) { flags = MGMT_ADV_FLAG_CONNECTABLE; if (btd_adapter_get_discoverable(client->manager->adapter) && !(bt_ad_has_flags(client->data))) flags |= MGMT_ADV_FLAG_DISCOV; } flags |= client->flags; /* Detect if the length is bigger that legacy and secondary is not set * then force it to be set to ensure the kernel uses EA. */ if (bt_ad_length(client->data) > BT_AD_MAX_DATA_LEN && !(flags & MGMT_ADV_FLAG_SEC_MASK)) flags |= MGMT_ADV_FLAG_SEC_1M; return flags; } static int refresh_legacy_adv(struct btd_adv_client *client, mgmt_request_func_t func) { struct mgmt_cp_add_advertising *cp; uint8_t param_len; uint8_t *adv_data; size_t adv_data_len; uint8_t *scan_rsp; size_t scan_rsp_len = -1; uint32_t flags = 0; unsigned int mgmt_ret; DBG("Refreshing advertisement: %s", client->path); flags = get_adv_flags(client); adv_data = generate_adv_data(client, &flags, &adv_data_len); if (!adv_data || (adv_data_len > calc_max_adv_len(client, flags))) { error("Advertising data too long or couldn't be generated."); return -EINVAL; } scan_rsp = generate_scan_rsp(client, &flags, &scan_rsp_len); if (!scan_rsp && scan_rsp_len) { error("Scan data couldn't be generated."); free(adv_data); return -EINVAL; } param_len = sizeof(struct mgmt_cp_add_advertising) + adv_data_len + scan_rsp_len; cp = malloc0(param_len); if (!cp) { error("Couldn't allocate for MGMT!"); free(adv_data); free(scan_rsp); return -ENOMEM; } cp->flags = htobl(flags); cp->instance = client->instance; cp->duration = client->duration; cp->adv_data_len = adv_data_len; cp->scan_rsp_len = scan_rsp_len; memcpy(cp->data, adv_data, adv_data_len); if (scan_rsp) memcpy(cp->data + adv_data_len, scan_rsp, scan_rsp_len); free(adv_data); free(scan_rsp); mgmt_ret = mgmt_send(client->manager->mgmt, MGMT_OP_ADD_ADVERTISING, client->manager->mgmt_index, param_len, cp, func, client, NULL); if (!mgmt_ret) { error("Failed to add Advertising Data"); free(cp); return -EINVAL; } if (func) client->add_adv_id = mgmt_ret; free(cp); return 0; } static void add_adv_params_callback(uint8_t status, uint16_t length, const void *param, void *user_data); static int refresh_extended_adv(struct btd_adv_client *client, mgmt_request_func_t func) { struct mgmt_cp_add_ext_adv_params cp; uint32_t flags = 0; unsigned int mgmt_ret = 0; DBG("Refreshing advertisement parameters: %s", client->path); flags = get_adv_flags(client); memset(&cp, 0, sizeof(cp)); cp.instance = client->instance; /* Not all advertising instances will use all possible parameters. The * included_params bit field tells the kernel which parameters are * relevant, and sensible defaults will be used for the rest */ if (client->duration) { cp.duration = client->duration; flags |= MGMT_ADV_PARAM_DURATION; } if (client->min_interval && client->max_interval) { cp.min_interval = client->min_interval; cp.max_interval = client->max_interval; flags |= MGMT_ADV_PARAM_INTERVALS; } if (client->tx_power != ADV_TX_POWER_NO_PREFERENCE) { cp.tx_power = client->tx_power; flags |= MGMT_ADV_PARAM_TX_POWER; } /* Indicate that this instance will be configured as scannable */ if (adv_client_has_scan_response(client, flags) && client->manager->supported_flags & MGMT_ADV_PARAM_SCAN_RSP) { flags |= MGMT_ADV_PARAM_SCAN_RSP; } cp.flags = cpu_to_le32(flags); mgmt_ret = mgmt_send(client->manager->mgmt, MGMT_OP_ADD_EXT_ADV_PARAMS, client->manager->mgmt_index, sizeof(cp), &cp, add_adv_params_callback, client, NULL); if (!mgmt_ret) { error("Failed to request extended advertising parameters"); return -EINVAL; } /* Store callback, called after we set advertising data */ client->refresh_done_func = func; client->add_adv_id = mgmt_ret; return 0; } static int refresh_advertisement(struct btd_adv_client *client, mgmt_request_func_t func) { if (client->manager->extended_add_cmds) return refresh_extended_adv(client, func); return refresh_legacy_adv(client, func); } static bool client_discoverable_timeout(void *user_data) { struct btd_adv_client *client = user_data; DBG(""); client->disc_to_id = 0; bt_ad_clear_flags(client->data); refresh_advertisement(client, NULL); return FALSE; } static bool parse_discoverable_timeout(DBusMessageIter *iter, struct btd_adv_client *client) { if (!iter) { client->discoverable_to = 0; timeout_remove(client->disc_to_id); client->disc_to_id = 0; return true; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) return false; dbus_message_iter_get_basic(iter, &client->discoverable_to); if (client->disc_to_id) timeout_remove(client->disc_to_id); if (client->discoverable_to > 0) client->disc_to_id = timeout_add_seconds( client->discoverable_to, client_discoverable_timeout, client, NULL); return true; } static struct adv_secondary { int flag; const char *name; } secondary[] = { { MGMT_ADV_FLAG_SEC_1M, "1M" }, { MGMT_ADV_FLAG_SEC_2M, "2M" }, { MGMT_ADV_FLAG_SEC_CODED, "Coded" }, { }, }; static bool parse_secondary(DBusMessageIter *iter, struct btd_adv_client *client) { const char *str; struct adv_secondary *sec; if (!iter) { /* Reset secondary channels */ client->flags &= ~MGMT_ADV_FLAG_SEC_MASK; return true; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return false; /* Reset secondary channels before parsing */ client->flags &= ~MGMT_ADV_FLAG_SEC_MASK; dbus_message_iter_get_basic(iter, &str); for (sec = secondary; sec->name; sec++) { if (strcmp(str, sec->name)) continue; if (!(client->manager->supported_flags & sec->flag)) return false; DBG("Secondary Channel: %s", str); client->flags |= sec->flag; return true; } return false; } static bool parse_min_interval(DBusMessageIter *iter, struct btd_adv_client *client) { uint32_t min_interval_ms; if (!iter) { client->min_interval = 0; return false; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32) return false; dbus_message_iter_get_basic(iter, &min_interval_ms); /* Convert ms to jiffies to be used in adv request */ client->min_interval = min_interval_ms / 0.625; /* BLUETOOTH SPECIFICATION Version 5.2 | Vol 4, Part E, page 2584 * defines acceptable interval range */ if (client->min_interval < 0x20 || client->min_interval > 0xFFFFFF) { client->min_interval = 0; return false; } return true; } static bool parse_max_interval(DBusMessageIter *iter, struct btd_adv_client *client) { uint32_t max_interval_ms; if (!iter) { client->max_interval = 0; return false; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32) return false; dbus_message_iter_get_basic(iter, &max_interval_ms); /* Convert ms to jiffies to be used in adv request */ client->max_interval = max_interval_ms / 0.625; /* BLUETOOTH SPECIFICATION Version 5.2 | Vol 4, Part E, page 2584 * defines acceptable interval range */ if (client->max_interval < 0x20 || client->max_interval > 0xFFFFFF) { client->max_interval = 0; return false; } return true; } static bool parse_tx_power(DBusMessageIter *iter, struct btd_adv_client *client) { int16_t val; if (!iter) { client->tx_power = ADV_TX_POWER_NO_PREFERENCE; return false; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_INT16) return false; dbus_message_iter_get_basic(iter, &val); /* BLUETOOTH SPECIFICATION Version 5.2 | Vol 4, Part E, page 2585 * defines acceptable tx power range */ if (val < -127 || val > 20) return false; client->tx_power = val; return true; } static struct adv_parser { const char *name; bool (*func)(DBusMessageIter *iter, struct btd_adv_client *client); bool experimental; } parsers[] = { { "Type", parse_type }, { "ServiceUUIDs", parse_service_uuids_ad }, { "ScanResponseServiceUUIDs", parse_service_uuids_sr, true }, { "SolicitUUIDs", parse_solicit_uuids_ad }, { "ScanResponseSolicitUUIDs", parse_solicit_uuids_sr, true }, { "ManufacturerData", parse_manufacturer_data_ad }, { "ScanResponseManufacturerData", parse_manufacturer_data_sr, true }, { "ServiceData", parse_service_data_ad }, { "ScanResponseServiceData", parse_service_data_sr, true }, { "Includes", parse_includes }, { "LocalName", parse_local_name }, { "Appearance", parse_appearance }, { "Duration", parse_duration }, { "Timeout", parse_timeout }, { "Data", parse_data_ad }, { "ScanResponseData", parse_data_sr }, { "Discoverable", parse_discoverable }, { "DiscoverableTimeout", parse_discoverable_timeout }, { "SecondaryChannel", parse_secondary }, { "MinInterval", parse_min_interval }, { "MaxInterval", parse_max_interval }, { "TxPower", parse_tx_power }, { }, }; static void properties_changed(GDBusProxy *proxy, const char *name, DBusMessageIter *iter, void *user_data) { struct btd_adv_client *client = user_data; struct adv_parser *parser; for (parser = parsers; parser && parser->name; parser++) { if (strcmp(parser->name, name)) continue; /* Ignore experimental parsers if the experimental flag is not * set. */ if (parser->experimental && !(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) continue; if (parser->func(iter, client)) { refresh_advertisement(client, NULL); break; } } } static void add_client_complete(struct btd_adv_client *client, uint8_t status) { DBusMessage *reply; if (status) error("Failed to add advertisement: %s (0x%02x)", mgmt_errstr(status), status); /* If the advertising request was not started by a direct call from * the client, but rather by a refresh due to properties update or * our internal timer, there is nothing to reply to. */ if (!client->reg) return; if (status) { reply = btd_error_failed(client->reg, "Failed to register advertisement"); queue_remove(client->manager->clients, client); g_idle_add(client_free_idle_cb, client); } else { DBusMessageIter iter; /* Check if the attribute has the Instance property */ if (g_dbus_proxy_get_property(client->proxy, "Instance", &iter)) { g_dbus_proxy_set_property_basic(client->proxy, "Instance", DBUS_TYPE_BYTE, &client->instance, NULL, NULL, NULL); } reply = dbus_message_new_method_return(client->reg); } g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(client->reg); client->reg = NULL; } static void add_adv_callback(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adv_client *client = user_data; const struct mgmt_rp_add_advertising *rp = param; client->add_adv_id = 0; if (status) goto done; if (!param || length < sizeof(*rp)) { status = MGMT_STATUS_FAILED; goto done; } client->instance = rp->instance; g_dbus_client_set_disconnect_watch(client->client, client_disconnect_cb, client); DBG("Advertisement registered: %s", client->path); g_dbus_emit_property_changed(btd_get_dbus_connection(), adapter_get_path(client->manager->adapter), LE_ADVERTISING_MGR_IFACE, "SupportedInstances"); g_dbus_emit_property_changed(btd_get_dbus_connection(), adapter_get_path(client->manager->adapter), LE_ADVERTISING_MGR_IFACE, "ActiveInstances"); g_dbus_proxy_set_property_watch(client->proxy, properties_changed, client); done: add_client_complete(client, status); } static void add_adv_params_callback(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adv_client *client = user_data; const struct mgmt_rp_add_ext_adv_params *rp = param; struct mgmt_cp_add_ext_adv_data *cp = NULL; uint8_t param_len; uint8_t *adv_data = NULL; size_t adv_data_len; uint8_t *scan_rsp = NULL; size_t scan_rsp_len = 0; uint32_t flags = 0; unsigned int mgmt_ret; dbus_int16_t tx_power; client->add_adv_id = 0; if (status) goto fail; if (!param || length < sizeof(*rp)) { status = MGMT_STATUS_FAILED; goto fail; } DBG("Refreshing advertisement data: %s", client->path); /* Update tx power held by client */ tx_power = rp->tx_power; if (tx_power != ADV_TX_POWER_NO_PREFERENCE) g_dbus_proxy_set_property_basic(client->proxy, "TxPower", DBUS_TYPE_INT16, &tx_power, NULL, NULL, NULL); client->instance = rp->instance; flags = get_adv_flags(client); adv_data = generate_adv_data(client, &flags, &adv_data_len); if (!adv_data || (adv_data_len > rp->max_adv_data_len)) { error("Advertising data too long or couldn't be generated."); goto fail; } if (adv_client_has_scan_response(client, flags)) { scan_rsp = generate_scan_rsp(client, &flags, &scan_rsp_len); if ((!scan_rsp && scan_rsp_len) || scan_rsp_len > rp->max_scan_rsp_len) { error("Scan data couldn't be generated."); goto fail; } } param_len = sizeof(*cp) + adv_data_len + scan_rsp_len; cp = malloc0(param_len); if (!cp) { error("Couldn't allocate for MGMT!"); goto fail; } cp->instance = client->instance; cp->adv_data_len = adv_data_len; cp->scan_rsp_len = scan_rsp_len; memcpy(cp->data, adv_data, adv_data_len); if (scan_rsp) memcpy(cp->data + adv_data_len, scan_rsp, scan_rsp_len); free(adv_data); free(scan_rsp); adv_data = NULL; scan_rsp = NULL; /* Submit request to update instance data */ mgmt_ret = mgmt_send(client->manager->mgmt, MGMT_OP_ADD_EXT_ADV_DATA, client->manager->mgmt_index, param_len, cp, client->refresh_done_func, client, NULL); if (mgmt_ret && client->refresh_done_func) client->add_adv_id = mgmt_ret; /* Clear the callback */ client->refresh_done_func = NULL; if (!mgmt_ret) { error("Failed to add Advertising Data"); goto fail; } free(cp); cp = NULL; return; fail: if (adv_data) free(adv_data); if (scan_rsp) free(scan_rsp); if (cp) free(cp); if (!status) status = -EINVAL; /* Failure for any reason ends this advertising request */ add_client_complete(client, status); } static DBusMessage *parse_advertisement(struct btd_adv_client *client) { struct adv_parser *parser; int err; for (parser = parsers; parser && parser->name; parser++) { DBusMessageIter iter; if (!g_dbus_proxy_get_property(client->proxy, parser->name, &iter)) continue; if (!parser->func(&iter, client)) { error("Error parsing %s property", parser->name); goto fail; } } if (bt_ad_get_flags(client->data) & (BT_AD_FLAG_GENERAL | BT_AD_FLAG_LIMITED)) { /* BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part C * page 2042: * A device in the broadcast mode shall not set the * ‘LE General Discoverable Mode’ flag or the * ‘LE Limited Discoverable Mode’ flag in the Flags AD Type * as defined in [Core Specification Supplement], Part A, * Section 1.3. */ if (client->type == AD_TYPE_BROADCAST) { error("Broadcast cannot set flags"); goto fail; } /* Set Limited Discoverable if DiscoverableTimeout is set */ if (client->disc_to_id && !set_flags(client, BT_AD_FLAG_LIMITED)) { error("Failed to set Limited Discoverable Flag"); goto fail; } } else if (client->disc_to_id) { /* Ignore DiscoverableTimeout if not discoverable */ timeout_remove(client->disc_to_id); client->disc_to_id = 0; client->discoverable_to = 0; } if (client->timeout && client->timeout < client->discoverable_to) { /* DiscoverableTimeout must not be bigger than Timeout */ error("DiscoverableTimeout > Timeout"); goto fail; } if (client->min_interval > client->max_interval) { /* Min interval must not be bigger than max interval */ error("MinInterval must be less than MaxInterval (%u > %u)", client->min_interval, client->max_interval); goto fail; } err = refresh_advertisement(client, add_adv_callback); if (!err) return NULL; fail: return btd_error_failed(client->reg, "Failed to parse advertisement."); } static void client_proxy_added(GDBusProxy *proxy, void *data) { struct btd_adv_client *client = data; DBusMessage *reply; const char *interface; interface = g_dbus_proxy_get_interface(proxy); if (g_str_equal(interface, LE_ADVERTISEMENT_IFACE) == FALSE) return; reply = parse_advertisement(client); if (!reply) return; /* Failed to publish for some reason, remove. */ queue_remove(client->manager->clients, client); g_idle_add(client_free_idle_cb, client); g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(client->reg); client->reg = NULL; } static struct btd_adv_client *client_create(struct btd_adv_manager *manager, DBusConnection *conn, DBusMessage *msg, const char *path) { struct btd_adv_client *client; const char *sender = dbus_message_get_sender(msg); if (!path || !g_str_has_prefix(path, "/")) return NULL; client = new0(struct btd_adv_client, 1); client->client = g_dbus_client_new_full(conn, sender, path, path); if (!client->client) goto fail; client->owner = g_strdup(sender); if (!client->owner) goto fail; client->path = g_strdup(path); if (!client->path) goto fail; DBG("Adding proxy for %s", path); client->proxy = g_dbus_proxy_new(client->client, path, LE_ADVERTISEMENT_IFACE); if (!client->proxy) goto fail; g_dbus_client_set_proxy_handlers(client->client, client_proxy_added, NULL, NULL, client); client->data = bt_ad_new(); if (!client->data) goto fail; bt_ad_set_max_len(client->data, manager->max_adv_len); client->scan = bt_ad_new(); if (!client->scan) goto fail; bt_ad_set_max_len(client->scan, manager->max_scan_rsp_len); client->manager = manager; client->appearance = UINT16_MAX; client->tx_power = ADV_TX_POWER_NO_PREFERENCE; client->min_interval = 0; client->max_interval = 0; client->refresh_done_func = NULL; return client; fail: client_free(client); return NULL; } static DBusMessage *register_advertisement(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_adv_manager *manager = user_data; DBusMessageIter args; struct btd_adv_client *client; struct dbus_obj_match match; DBG("RegisterAdvertisement"); if (!dbus_message_iter_init(msg, &args)) return btd_error_invalid_args(msg); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) return btd_error_invalid_args(msg); dbus_message_iter_get_basic(&args, &match.path); match.owner = dbus_message_get_sender(msg); if (queue_find(manager->clients, match_client, &match)) return btd_error_already_exists(msg); dbus_message_iter_next(&args); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) return btd_error_invalid_args(msg); client = client_create(manager, conn, msg, match.path); if (!client) return btd_error_failed(msg, "Failed to register advertisement"); client->instance = util_get_uid(&manager->instance_bitmap, manager->max_ads); if (!client->instance) { client_free(client); return btd_error_not_permitted(msg, "Maximum advertisements reached"); } DBG("Registered advertisement at path %s", match.path); client->reg = dbus_message_ref(msg); queue_push_tail(manager->clients, client); return NULL; } static DBusMessage *unregister_advertisement(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_adv_manager *manager = user_data; DBusMessageIter args; struct btd_adv_client *client; struct dbus_obj_match match; DBG("UnregisterAdvertisement"); if (!dbus_message_iter_init(msg, &args)) return btd_error_invalid_args(msg); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) return btd_error_invalid_args(msg); dbus_message_iter_get_basic(&args, &match.path); match.owner = dbus_message_get_sender(msg); client = queue_find(manager->clients, match_client, &match); if (!client) return btd_error_does_not_exist(msg); client_remove(client); return dbus_message_new_method_return(msg); } static gboolean get_instances(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_adv_manager *manager = data; uint8_t instances; instances = manager->max_ads - queue_length(manager->clients); dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &instances); return TRUE; } static gboolean get_active_instances(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_adv_manager *manager = data; uint8_t instances; instances = queue_length(manager->clients); dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &instances); return TRUE; } static void append_include(struct btd_adv_manager *manager, DBusMessageIter *iter) { struct adv_include *inc; for (inc = includes; inc && inc->name; inc++) { if ((inc->set && inc->set(NULL)) || (manager->supported_flags & inc->flag)) dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &inc->name); } } static gboolean get_supported_includes(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_adv_manager *manager = data; DBusMessageIter entry; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); append_include(manager, &entry); dbus_message_iter_close_container(iter, &entry); return TRUE; } static void append_secondary(struct btd_adv_manager *manager, DBusMessageIter *iter) { struct adv_secondary *sec; for (sec = secondary; sec && sec->name; sec++) { if (manager->supported_flags & sec->flag) dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &sec->name); } } static gboolean secondary_exists(const GDBusPropertyTable *property, void *data) { struct btd_adv_manager *manager = data; /* 1M PHY shall always be supported if ext_adv is supported */ return manager->supported_flags & MGMT_ADV_FLAG_SEC_1M; } static gboolean get_supported_secondary(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_adv_manager *manager = data; DBusMessageIter entry; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); append_secondary(manager, &entry); dbus_message_iter_close_container(iter, &entry); return TRUE; } static struct adv_feature { int flag; const char *name; } features[] = { { MGMT_ADV_FLAG_CAN_SET_TX_POWER, "CanSetTxPower" }, { MGMT_ADV_FLAG_HW_OFFLOAD, "HardwareOffload" }, { }, }; static void append_features(struct btd_adv_manager *manager, DBusMessageIter *iter) { struct adv_feature *feat; for (feat = features; feat->name; feat++) { if (manager->supported_flags & feat->flag) dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &feat->name); } } static gboolean get_supported_features(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_adv_manager *manager = data; DBusMessageIter entry; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); append_features(manager, &entry); dbus_message_iter_close_container(iter, &entry); return TRUE; } static gboolean get_supported_cap(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct btd_adv_manager *manager = data; DBusMessageIter dict; int16_t min_tx_power = manager->min_tx_power; int16_t max_tx_power = manager->max_tx_power; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); if (min_tx_power != ADV_TX_POWER_NO_PREFERENCE) dict_append_entry(&dict, "MinTxPower", DBUS_TYPE_INT16, &min_tx_power); if (max_tx_power != ADV_TX_POWER_NO_PREFERENCE) dict_append_entry(&dict, "MaxTxPower", DBUS_TYPE_INT16, &max_tx_power); dict_append_entry(&dict, "MaxAdvLen", DBUS_TYPE_BYTE, &manager->max_adv_len); dict_append_entry(&dict, "MaxScnRspLen", DBUS_TYPE_BYTE, &manager->max_scan_rsp_len); dbus_message_iter_close_container(iter, &dict); return TRUE; } static const GDBusPropertyTable properties[] = { { "ActiveInstances", "y", get_active_instances, NULL, NULL }, { "SupportedInstances", "y", get_instances, NULL, NULL }, { "SupportedIncludes", "as", get_supported_includes, NULL, NULL }, { "SupportedSecondaryChannels", "as", get_supported_secondary, NULL, secondary_exists }, { "SupportedFeatures", "as", get_supported_features, NULL, NULL }, { "SupportedCapabilities", "a{sv}", get_supported_cap, NULL, NULL }, { } }; static const GDBusMethodTable methods[] = { { GDBUS_ASYNC_METHOD("RegisterAdvertisement", GDBUS_ARGS({ "advertisement", "o" }, { "options", "a{sv}" }), NULL, register_advertisement) }, { GDBUS_ASYNC_METHOD("UnregisterAdvertisement", GDBUS_ARGS({ "service", "o" }), NULL, unregister_advertisement) }, { } }; static void manager_destroy(void *user_data) { struct btd_adv_manager *manager = user_data; queue_destroy(manager->clients, client_destroy); mgmt_unref(manager->mgmt); free(manager); } static void read_adv_features_callback(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adv_manager *manager = user_data; const struct mgmt_rp_read_adv_features *feat = param; if (status || !param) { error("Failed to read advertising features: %s (0x%02x)", mgmt_errstr(status), status); return; } if (length < sizeof(*feat)) { error("Wrong size of read adv features response"); return; } manager->max_adv_len = feat->max_adv_data_len; manager->max_scan_rsp_len = feat->max_scan_rsp_len; manager->max_ads = feat->max_instances; manager->supported_flags |= feat->supported_flags; /* Registering interface after querying properties */ if (!g_dbus_register_interface(btd_get_dbus_connection(), adapter_get_path(manager->adapter), LE_ADVERTISING_MGR_IFACE, methods, NULL, properties, manager, NULL)) error("Failed to register " LE_ADVERTISING_MGR_IFACE); if (manager->max_ads == 0) return; /* Reset existing instances */ if (feat->num_instances) remove_advertising(manager, 0); /* Emit property update */ g_dbus_emit_property_changed(btd_get_dbus_connection(), adapter_get_path(manager->adapter), LE_ADVERTISING_MGR_IFACE, "SupportedFeatures"); g_dbus_emit_property_changed(btd_get_dbus_connection(), adapter_get_path(manager->adapter), LE_ADVERTISING_MGR_IFACE, "SupportedIncludes"); g_dbus_emit_property_changed(btd_get_dbus_connection(), adapter_get_path(manager->adapter), LE_ADVERTISING_MGR_IFACE, "SupportedSecondaryChannels"); } static void read_controller_cap_complete(uint8_t status, uint16_t length, const void *param, void *user_data) { struct btd_adv_manager *manager = user_data; const struct mgmt_rp_read_controller_cap *rp = param; const uint8_t *ptr = rp->cap; size_t offset = 0; uint8_t tag_len; uint8_t tag_type; if (status || !param) { error("Failed to read advertising features: %s (0x%02x)", mgmt_errstr(status), status); return; } if (sizeof(rp->cap_len) + rp->cap_len != length) { error("Controller capabilities malformed, size %zu != %u", sizeof(rp->cap_len) + rp->cap_len, length); return; } while (offset < rp->cap_len) { tag_len = ptr[offset++]; tag_type = ptr[offset++]; switch (tag_type) { case MGMT_CAP_LE_TX_PWR: if ((tag_len - sizeof(tag_type)) != 2*sizeof(manager->min_tx_power)) { error("TX power had unexpected length %d", tag_len); break; } memcpy(&manager->min_tx_power, &ptr[offset], tag_len); memcpy(&manager->max_tx_power, &ptr[offset+1], tag_len); } /* Step to the next entry */ offset += (tag_len - sizeof(tag_type)); } } static struct btd_adv_manager *manager_create(struct btd_adapter *adapter, struct mgmt *mgmt) { struct btd_adv_manager *manager; manager = new0(struct btd_adv_manager, 1); manager->adapter = adapter; manager->mgmt = mgmt_ref(mgmt); if (!manager->mgmt) { error("Failed to access management interface"); free(manager); return NULL; } manager->mgmt_index = btd_adapter_get_index(adapter); manager->clients = queue_new(); manager->supported_flags = MGMT_ADV_FLAG_LOCAL_NAME; manager->extended_add_cmds = btd_has_kernel_features(KERNEL_HAS_EXT_ADV_ADD_CMDS); manager->min_tx_power = ADV_TX_POWER_NO_PREFERENCE; manager->max_tx_power = ADV_TX_POWER_NO_PREFERENCE; if (!mgmt_send(manager->mgmt, MGMT_OP_READ_ADV_FEATURES, manager->mgmt_index, 0, NULL, read_adv_features_callback, manager, NULL)) { error("Failed to read advertising features"); goto fail; } /* Query controller capabilities. This will be used to display valid * advertising tx power range to the client. */ if (btd_has_kernel_features(KERNEL_HAS_CONTROLLER_CAP_CMD)) mgmt_send(manager->mgmt, MGMT_OP_READ_CONTROLLER_CAP, manager->mgmt_index, 0, NULL, read_controller_cap_complete, manager, NULL); return manager; fail: manager_destroy(manager); return NULL; } struct btd_adv_manager *btd_adv_manager_new(struct btd_adapter *adapter, struct mgmt *mgmt) { struct btd_adv_manager *manager; if (!adapter || !mgmt) return NULL; manager = manager_create(adapter, mgmt); if (!manager) return NULL; DBG("LE Advertising Manager created for adapter: %s", adapter_get_path(adapter)); return manager; } void btd_adv_manager_destroy(struct btd_adv_manager *manager) { if (!manager) return; g_dbus_unregister_interface(btd_get_dbus_connection(), adapter_get_path(manager->adapter), LE_ADVERTISING_MGR_IFACE); manager_destroy(manager); } static void manager_refresh(void *data, void *user_data) { struct btd_adv_client *client = data; refresh_advertisement(client, NULL); } void btd_adv_manager_refresh(struct btd_adv_manager *manager) { if (!manager) return; queue_foreach(manager->clients, manager_refresh, NULL); } bluez-5.87/PaxHeaders/profiles0000644000000000000000000000005015221755522013427 xustar0020 atime=1783094113 20 ctime=1783094098 bluez-5.87/profiles/0000755000000000000000000000000015221755522013165 5ustar00rootrootbluez-5.87/profiles/PaxHeaders/cups0000644000000000000000000000005015221755531014401 xustar0020 atime=1783094113 20 ctime=1783094105 bluez-5.87/profiles/cups/0000755000000000000000000000000015221755531014137 5ustar00rootrootbluez-5.87/profiles/cups/PaxHeaders/cups.h0000644000000000000000000000005014015011623015564 xustar0020 atime=1783093291 20 ctime=1783094105 bluez-5.87/profiles/cups/cups.h0000644000000000000000000000204114015011623015242 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2003-2010 Marcel Holtmann * * */ enum { /**** Backend exit codes ****/ CUPS_BACKEND_OK = 0, /* Job completed successfully */ CUPS_BACKEND_FAILED = 1, /* Job failed, use error-policy */ CUPS_BACKEND_AUTH_REQUIRED = 2, /* Job failed, authentication required */ CUPS_BACKEND_HOLD = 3, /* Job failed, hold job */ CUPS_BACKEND_STOP = 4, /* Job failed, stop queue */ CUPS_BACKEND_CANCEL = 5, /* Job failed, cancel job */ CUPS_BACKEND_RETRY = 6, /* Failure requires us to retry (BlueZ specific) */ }; int sdp_search_spp(sdp_session_t *sdp, uint8_t *channel); int sdp_search_hcrp(sdp_session_t *sdp, unsigned short *ctrl_psm, unsigned short *data_psm); int spp_print(bdaddr_t *src, bdaddr_t *dst, uint8_t channel, int fd, int copies, const char *cups_class); int hcrp_print(bdaddr_t *src, bdaddr_t *dst, unsigned short ctrl_psm, unsigned short data_psm, int fd, int copies, const char *cups_class); bluez-5.87/profiles/cups/PaxHeaders/spp.c0000644000000000000000000000005015063045254015422 xustar0020 atime=1783093293 20 ctime=1783094105 bluez-5.87/profiles/cups/spp.c0000644000000000000000000000423515063045254015107 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2003-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/rfcomm.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "cups.h" int spp_print(bdaddr_t *src, bdaddr_t *dst, uint8_t channel, int fd, int copies, const char *cups_class) { struct sockaddr_rc addr; unsigned char buf[2048]; int i, sk, err, len; if ((sk = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) { perror("ERROR: Can't create socket"); if (cups_class) return CUPS_BACKEND_FAILED; else return CUPS_BACKEND_RETRY; } addr.rc_family = AF_BLUETOOTH; bacpy(&addr.rc_bdaddr, src); addr.rc_channel = 0; if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { perror("ERROR: Can't bind socket"); close(sk); if (cups_class) return CUPS_BACKEND_FAILED; else return CUPS_BACKEND_RETRY; } addr.rc_family = AF_BLUETOOTH; bacpy(&addr.rc_bdaddr, dst); addr.rc_channel = channel; if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { perror("ERROR: Can't connect to device"); close(sk); if (cups_class) return CUPS_BACKEND_FAILED; else return CUPS_BACKEND_RETRY; } fputs("STATE: -connecting-to-device\n", stderr); /* Ignore SIGTERM signals if printing from stdin */ if (fd == 0) { #ifdef HAVE_SIGSET sigset(SIGTERM, SIG_IGN); #elif defined(HAVE_SIGACTION) memset(&action, 0, sizeof(action)); sigemptyset(&action.sa_mask); action.sa_handler = SIG_IGN; sigaction(SIGTERM, &action, NULL); #else signal(SIGTERM, SIG_IGN); #endif /* HAVE_SIGSET */ } for (i = 0; i < copies; i++) { if (fd != 0) { fprintf(stderr, "PAGE: 1 1\n"); lseek(fd, 0, SEEK_SET); } while ((len = read(fd, buf, sizeof(buf))) > 0) { err = write(sk, buf, len); if (err < 0) { perror("ERROR: Error writing to device"); close(sk); return CUPS_BACKEND_FAILED; } } } close(sk); return CUPS_BACKEND_OK; } bluez-5.87/profiles/cups/PaxHeaders/hcrp.c0000644000000000000000000000005015063045254015554 xustar0020 atime=1783093294 20 ctime=1783094105 bluez-5.87/profiles/cups/hcrp.c0000644000000000000000000001717615063045254015251 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2003-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/l2cap.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "cups.h" #define HCRP_PDU_CREDIT_GRANT 0x0001 #define HCRP_PDU_CREDIT_REQUEST 0x0002 #define HCRP_PDU_GET_LPT_STATUS 0x0005 #define HCRP_STATUS_FEATURE_UNSUPPORTED 0x0000 #define HCRP_STATUS_SUCCESS 0x0001 #define HCRP_STATUS_CREDIT_SYNC_ERROR 0x0002 #define HCRP_STATUS_GENERIC_FAILURE 0xffff struct hcrp_pdu_hdr { uint16_t pid; uint16_t tid; uint16_t plen; } __attribute__ ((packed)); #define HCRP_PDU_HDR_SIZE 6 struct hcrp_credit_grant_cp { uint32_t credit; } __attribute__ ((packed)); #define HCRP_CREDIT_GRANT_CP_SIZE 4 struct hcrp_credit_grant_rp { uint16_t status; } __attribute__ ((packed)); #define HCRP_CREDIT_GRANT_RP_SIZE 2 struct hcrp_credit_request_rp { uint16_t status; uint32_t credit; } __attribute__ ((packed)); #define HCRP_CREDIT_REQUEST_RP_SIZE 6 struct hcrp_get_lpt_status_rp { uint16_t status; uint8_t lpt_status; } __attribute__ ((packed)); #define HCRP_GET_LPT_STATUS_RP_SIZE 3 static int hcrp_credit_grant(int sk, uint16_t tid, uint32_t credit) { struct hcrp_pdu_hdr hdr; struct hcrp_credit_grant_cp cp; struct hcrp_credit_grant_rp rp; unsigned char buf[128]; int len; hdr.pid = htons(HCRP_PDU_CREDIT_GRANT); hdr.tid = htons(tid); hdr.plen = htons(HCRP_CREDIT_GRANT_CP_SIZE); cp.credit = credit; memcpy(buf, &hdr, HCRP_PDU_HDR_SIZE); memcpy(buf + HCRP_PDU_HDR_SIZE, &cp, HCRP_CREDIT_GRANT_CP_SIZE); len = write(sk, buf, HCRP_PDU_HDR_SIZE + HCRP_CREDIT_GRANT_CP_SIZE); if (len < 0) return len; len = read(sk, buf, sizeof(buf)); if (len < 0) return len; memcpy(&hdr, buf, HCRP_PDU_HDR_SIZE); memcpy(&rp, buf + HCRP_PDU_HDR_SIZE, HCRP_CREDIT_GRANT_RP_SIZE); if (ntohs(rp.status) != HCRP_STATUS_SUCCESS) { errno = EIO; return -1; } return 0; } static int hcrp_credit_request(int sk, uint16_t tid, uint32_t *credit) { struct hcrp_pdu_hdr hdr; struct hcrp_credit_request_rp rp; unsigned char buf[128]; int len; hdr.pid = htons(HCRP_PDU_CREDIT_REQUEST); hdr.tid = htons(tid); hdr.plen = htons(0); memcpy(buf, &hdr, HCRP_PDU_HDR_SIZE); len = write(sk, buf, HCRP_PDU_HDR_SIZE); if (len < 0) return len; len = read(sk, buf, sizeof(buf)); if (len < 0) return len; memcpy(&hdr, buf, HCRP_PDU_HDR_SIZE); memcpy(&rp, buf + HCRP_PDU_HDR_SIZE, HCRP_CREDIT_REQUEST_RP_SIZE); if (ntohs(rp.status) != HCRP_STATUS_SUCCESS) { errno = EIO; return -1; } if (credit) *credit = ntohl(rp.credit); return 0; } static int hcrp_get_lpt_status(int sk, uint16_t tid, uint8_t *lpt_status) { struct hcrp_pdu_hdr hdr; struct hcrp_get_lpt_status_rp rp; unsigned char buf[128]; int len; hdr.pid = htons(HCRP_PDU_GET_LPT_STATUS); hdr.tid = htons(tid); hdr.plen = htons(0); memcpy(buf, &hdr, HCRP_PDU_HDR_SIZE); len = write(sk, buf, HCRP_PDU_HDR_SIZE); if (len < 0) return len; len = read(sk, buf, sizeof(buf)); if (len < 0) return len; memcpy(&hdr, buf, HCRP_PDU_HDR_SIZE); memcpy(&rp, buf + HCRP_PDU_HDR_SIZE, HCRP_GET_LPT_STATUS_RP_SIZE); if (ntohs(rp.status) != HCRP_STATUS_SUCCESS) { errno = EIO; return -1; } if (lpt_status) *lpt_status = rp.lpt_status; return 0; } static inline int hcrp_get_next_tid(int tid) { if (tid > 0xf000) return 0; else return tid + 1; } int hcrp_print(bdaddr_t *src, bdaddr_t *dst, unsigned short ctrl_psm, unsigned short data_psm, int fd, int copies, const char *cups_class) { struct sockaddr_l2 addr; struct l2cap_options opts; socklen_t size; unsigned char buf[2048]; int i, ctrl_sk, data_sk, count, len, timeout = 0; unsigned int mtu; uint8_t status; uint16_t tid = 0; uint32_t tmp, credit = 0; if ((ctrl_sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) { perror("ERROR: Can't create socket"); if (cups_class) return CUPS_BACKEND_FAILED; else return CUPS_BACKEND_RETRY; } memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; bacpy(&addr.l2_bdaddr, src); if (bind(ctrl_sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { perror("ERROR: Can't bind socket"); close(ctrl_sk); if (cups_class) return CUPS_BACKEND_FAILED; else return CUPS_BACKEND_RETRY; } memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; bacpy(&addr.l2_bdaddr, dst); addr.l2_psm = htobs(ctrl_psm); if (connect(ctrl_sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { perror("ERROR: Can't connect to device"); close(ctrl_sk); if (cups_class) return CUPS_BACKEND_FAILED; else return CUPS_BACKEND_RETRY; } if ((data_sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) { perror("ERROR: Can't create socket"); close(ctrl_sk); if (cups_class) return CUPS_BACKEND_FAILED; else return CUPS_BACKEND_RETRY; } memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; bacpy(&addr.l2_bdaddr, src); if (bind(data_sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { perror("ERROR: Can't bind socket"); close(data_sk); close(ctrl_sk); if (cups_class) return CUPS_BACKEND_FAILED; else return CUPS_BACKEND_RETRY; } memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; bacpy(&addr.l2_bdaddr, dst); addr.l2_psm = htobs(data_psm); if (connect(data_sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { perror("ERROR: Can't connect to device"); close(data_sk); close(ctrl_sk); if (cups_class) return CUPS_BACKEND_FAILED; else return CUPS_BACKEND_RETRY; } fputs("STATE: -connecting-to-device\n", stderr); memset(&opts, 0, sizeof(opts)); size = sizeof(opts); if (getsockopt(data_sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &size) < 0) { perror("ERROR: Can't get socket options"); close(data_sk); close(ctrl_sk); if (cups_class) return CUPS_BACKEND_FAILED; else return CUPS_BACKEND_RETRY; } mtu = opts.omtu; /* Ignore SIGTERM signals if printing from stdin */ if (fd == 0) { #ifdef HAVE_SIGSET sigset(SIGTERM, SIG_IGN); #elif defined(HAVE_SIGACTION) memset(&action, 0, sizeof(action)); sigemptyset(&action.sa_mask); action.sa_handler = SIG_IGN; sigaction(SIGTERM, &action, NULL); #else signal(SIGTERM, SIG_IGN); #endif /* HAVE_SIGSET */ } tid = hcrp_get_next_tid(tid); if (hcrp_credit_grant(ctrl_sk, tid, 0) < 0) { fprintf(stderr, "ERROR: Can't grant initial credits\n"); close(data_sk); close(ctrl_sk); if (cups_class) return CUPS_BACKEND_FAILED; else return CUPS_BACKEND_RETRY; } for (i = 0; i < copies; i++) { if (fd != 0) { fprintf(stderr, "PAGE: 1 1\n"); lseek(fd, 0, SEEK_SET); } while (1) { if (credit < mtu) { tid = hcrp_get_next_tid(tid); if (!hcrp_credit_request(ctrl_sk, tid, &tmp)) { credit += tmp; timeout = 0; } } if (!credit) { if (timeout++ > 300) { tid = hcrp_get_next_tid(tid); if (!hcrp_get_lpt_status(ctrl_sk, tid, &status)) fprintf(stderr, "ERROR: LPT status 0x%02x\n", status); break; } sleep(1); continue; } count = read(fd, buf, (credit > mtu) ? mtu : credit); if (count <= 0) break; len = write(data_sk, buf, count); if (len < 0) { perror("ERROR: Error writing to device"); close(data_sk); close(ctrl_sk); return CUPS_BACKEND_FAILED; } if (len != count) fprintf(stderr, "ERROR: Can't send complete data\n"); credit -= len; } } close(data_sk); close(ctrl_sk); return CUPS_BACKEND_OK; } bluez-5.87/profiles/cups/PaxHeaders/sdp.c0000644000000000000000000000005015063045254015406 xustar0020 atime=1783093293 20 ctime=1783094105 bluez-5.87/profiles/cups/sdp.c0000644000000000000000000000423615063045254015074 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2003-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "cups.h" int sdp_search_hcrp(sdp_session_t *sdp, unsigned short *ctrl_psm, unsigned short *data_psm) { sdp_list_t *srch, *attrs, *rsp; uuid_t svclass; uint16_t attr1, attr2; int err; if (!sdp) return -1; sdp_uuid16_create(&svclass, HCR_PRINT_SVCLASS_ID); srch = sdp_list_append(NULL, &svclass); attr1 = SDP_ATTR_PROTO_DESC_LIST; attrs = sdp_list_append(NULL, &attr1); attr2 = SDP_ATTR_ADD_PROTO_DESC_LIST; attrs = sdp_list_append(attrs, &attr2); err = sdp_service_search_attr_req(sdp, srch, SDP_ATTR_REQ_INDIVIDUAL, attrs, &rsp); if (err) return -1; for (; rsp; rsp = rsp->next) { sdp_record_t *rec = (sdp_record_t *) rsp->data; sdp_list_t *protos; if (!sdp_get_access_protos(rec, &protos)) { unsigned short psm = sdp_get_proto_port(protos, L2CAP_UUID); if (psm > 0) { *ctrl_psm = psm; } } if (!sdp_get_add_access_protos(rec, &protos)) { unsigned short psm = sdp_get_proto_port(protos, L2CAP_UUID); if (psm > 0 && *ctrl_psm > 0) { *data_psm = psm; return 0; } } } return -1; } int sdp_search_spp(sdp_session_t *sdp, uint8_t *channel) { sdp_list_t *srch, *attrs, *rsp; uuid_t svclass; uint16_t attr; int err; if (!sdp) return -1; sdp_uuid16_create(&svclass, SERIAL_PORT_SVCLASS_ID); srch = sdp_list_append(NULL, &svclass); attr = SDP_ATTR_PROTO_DESC_LIST; attrs = sdp_list_append(NULL, &attr); err = sdp_service_search_attr_req(sdp, srch, SDP_ATTR_REQ_INDIVIDUAL, attrs, &rsp); if (err) return -1; for (; rsp; rsp = rsp->next) { sdp_record_t *rec = (sdp_record_t *) rsp->data; sdp_list_t *protos; if (!sdp_get_access_protos(rec, &protos)) { uint8_t ch = sdp_get_proto_port(protos, RFCOMM_UUID); if (ch > 0) { *channel = ch; return 0; } } } return -1; } bluez-5.87/profiles/cups/PaxHeaders/main.c0000644000000000000000000000005015063045254015544 xustar0020 atime=1783093291 20 ctime=1783094105 bluez-5.87/profiles/cups/main.c0000644000000000000000000004721115063045254015232 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2003-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "gdbus/gdbus.h" #include "cups.h" struct cups_device { char *bdaddr; char *name; char *id; }; static GSList *device_list = NULL; static GMainLoop *loop = NULL; static DBusConnection *conn = NULL; static gboolean doing_disco = FALSE; #define ATTRID_1284ID 0x0300 struct context_data { gboolean found; char *id; }; static void element_start(GMarkupParseContext *context, const char *element_name, const char **attribute_names, const char **attribute_values, gpointer user_data, GError **err) { struct context_data *ctx_data = user_data; if (!strcmp(element_name, "record")) return; if (!strcmp(element_name, "attribute")) { int i; for (i = 0; attribute_names[i]; i++) { if (strcmp(attribute_names[i], "id") != 0) continue; if (strtol(attribute_values[i], 0, 0) == ATTRID_1284ID) ctx_data->found = TRUE; break; } return; } if (ctx_data->found && !strcmp(element_name, "text")) { int i; for (i = 0; attribute_names[i]; i++) { if (!strcmp(attribute_names[i], "value")) { ctx_data->id = g_strdup(attribute_values[i] + 2); ctx_data->found = FALSE; } } } } static GMarkupParser parser = { element_start, NULL, NULL, NULL, NULL }; static char *sdp_xml_parse_record(const char *data) { GMarkupParseContext *ctx; struct context_data ctx_data; int size; size = strlen(data); ctx_data.found = FALSE; ctx_data.id = NULL; ctx = g_markup_parse_context_new(&parser, 0, &ctx_data, NULL); if (g_markup_parse_context_parse(ctx, data, size, NULL) == FALSE) { g_markup_parse_context_free(ctx); g_free(ctx_data.id); return NULL; } g_markup_parse_context_free(ctx); return ctx_data.id; } static char *device_get_ieee1284_id(const char *adapter, const char *device) { DBusMessage *message, *reply; DBusMessageIter iter, reply_iter; DBusMessageIter reply_iter_entry; const char *hcr_print = "00001126-0000-1000-8000-00805f9b34fb"; const char *xml; char *id = NULL; /* Look for the service handle of the HCRP service */ message = dbus_message_new_method_call("org.bluez", device, "org.bluez.Device1", "DiscoverServices"); dbus_message_iter_init_append(message, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &hcr_print); reply = dbus_connection_send_with_reply_and_block(conn, message, -1, NULL); dbus_message_unref(message); if (!reply) return NULL; dbus_message_iter_init(reply, &reply_iter); if (dbus_message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_ARRAY) { dbus_message_unref(reply); return NULL; } dbus_message_iter_recurse(&reply_iter, &reply_iter_entry); /* Hopefully we only get one handle, or take a punt */ while (dbus_message_iter_get_arg_type(&reply_iter_entry) == DBUS_TYPE_DICT_ENTRY) { guint32 key; DBusMessageIter dict_entry; dbus_message_iter_recurse(&reply_iter_entry, &dict_entry); /* Key ? */ dbus_message_iter_get_basic(&dict_entry, &key); if (!key) { dbus_message_iter_next(&reply_iter_entry); continue; } /* Try to get the value */ if (!dbus_message_iter_next(&dict_entry)) { dbus_message_iter_next(&reply_iter_entry); continue; } dbus_message_iter_get_basic(&dict_entry, &xml); id = sdp_xml_parse_record(xml); if (id != NULL) break; dbus_message_iter_next(&reply_iter_entry); } dbus_message_unref(reply); return id; } static void print_printer_details(const char *name, const char *bdaddr, const char *id) { char *uri, *escaped; escaped = g_strdelimit(g_strdup(name), "\"", '\''); uri = g_strdup_printf("bluetooth://%c%c%c%c%c%c%c%c%c%c%c%c", bdaddr[0], bdaddr[1], bdaddr[3], bdaddr[4], bdaddr[6], bdaddr[7], bdaddr[9], bdaddr[10], bdaddr[12], bdaddr[13], bdaddr[15], bdaddr[16]); printf("direct %s \"%s\" \"%s (Bluetooth)\"", uri, escaped, escaped); if (id != NULL) printf(" \"%s\"\n", id); else printf("\n"); g_free(escaped); g_free(uri); } static void add_device_to_list(const char *name, const char *bdaddr, const char *id) { struct cups_device *device; GSList *l; /* Look for the device in the list */ for (l = device_list; l != NULL; l = l->next) { device = (struct cups_device *) l->data; if (strcmp(device->bdaddr, bdaddr) == 0) { if (device->name != name) { g_free(device->name); device->name = g_strdup(name); } g_free(device->id); device->id = g_strdup(id); return; } } /* Or add it to the list if it's not there */ device = g_new0(struct cups_device, 1); device->bdaddr = g_strdup(bdaddr); device->name = g_strdup(name); device->id = g_strdup(id); device_list = g_slist_prepend(device_list, device); print_printer_details(device->name, device->bdaddr, device->id); } static gboolean parse_device_properties(DBusMessageIter *reply_iter, char **name, char **bdaddr) { guint32 class = 0; DBusMessageIter reply_iter_entry; if (dbus_message_iter_get_arg_type(reply_iter) != DBUS_TYPE_ARRAY) return FALSE; dbus_message_iter_recurse(reply_iter, &reply_iter_entry); while (dbus_message_iter_get_arg_type(&reply_iter_entry) == DBUS_TYPE_DICT_ENTRY) { const char *key; DBusMessageIter dict_entry, iter_dict_val; dbus_message_iter_recurse(&reply_iter_entry, &dict_entry); /* Key == Class ? */ dbus_message_iter_get_basic(&dict_entry, &key); if (!key) { dbus_message_iter_next(&reply_iter_entry); continue; } if (strcmp(key, "Class") != 0 && strcmp(key, "Alias") != 0 && strcmp(key, "Address") != 0) { dbus_message_iter_next(&reply_iter_entry); continue; } /* Try to get the value */ if (!dbus_message_iter_next(&dict_entry)) { dbus_message_iter_next(&reply_iter_entry); continue; } dbus_message_iter_recurse(&dict_entry, &iter_dict_val); if (strcmp(key, "Class") == 0) { dbus_message_iter_get_basic(&iter_dict_val, &class); } else { const char *value; dbus_message_iter_get_basic(&iter_dict_val, &value); if (strcmp(key, "Alias") == 0) { *name = g_strdup(value); } else if (bdaddr) { *bdaddr = g_strdup(value); } } dbus_message_iter_next(&reply_iter_entry); } if (class == 0) return FALSE; if (((class & 0x1f00) >> 8) == 0x06 && (class & 0x80)) return TRUE; return FALSE; } static gboolean device_is_printer(const char *adapter, const char *device_path, char **name, char **bdaddr) { DBusMessage *message, *reply; DBusMessageIter reply_iter; gboolean retval; message = dbus_message_new_method_call("org.bluez", device_path, "org.bluez.Device1", "GetProperties"); reply = dbus_connection_send_with_reply_and_block(conn, message, -1, NULL); dbus_message_unref(message); if (!reply) return FALSE; dbus_message_iter_init(reply, &reply_iter); retval = parse_device_properties(&reply_iter, name, bdaddr); dbus_message_unref(reply); return retval; } static void remote_device_found(const char *adapter, const char *bdaddr, const char *name) { DBusMessage *message, *reply; DBusMessageIter iter; char *object_path = NULL; char *id; assert(adapter != NULL); message = dbus_message_new_method_call("org.bluez", adapter, "org.bluez.Adapter1", "FindDevice"); dbus_message_iter_init_append(message, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &bdaddr); reply = dbus_connection_send_with_reply_and_block(conn, message, -1, NULL); dbus_message_unref(message); if (!reply) { message = dbus_message_new_method_call("org.bluez", adapter, "org.bluez.Adapter1", "CreateDevice"); dbus_message_iter_init_append(message, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &bdaddr); reply = dbus_connection_send_with_reply_and_block(conn, message, -1, NULL); dbus_message_unref(message); if (!reply) return; } if (dbus_message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID) == FALSE) { dbus_message_unref(reply); return; } id = device_get_ieee1284_id(adapter, object_path); add_device_to_list(name, bdaddr, id); g_free(id); dbus_message_unref(reply); } static void discovery_completed(void) { g_slist_free(device_list); device_list = NULL; g_main_loop_quit(loop); } static void remote_device_disappeared(const char *bdaddr) { GSList *l; for (l = device_list; l != NULL; l = l->next) { struct cups_device *device = l->data; if (strcmp(device->bdaddr, bdaddr) == 0) { g_free(device->name); g_free(device->bdaddr); g_free(device); device_list = g_slist_delete_link(device_list, l); return; } } } static gboolean list_known_printers(const char *adapter) { DBusMessageIter reply_iter, iter_array; DBusError error; DBusMessage *message, *reply; message = dbus_message_new_method_call("org.bluez", adapter, "org.bluez.Adapter1", "ListDevices"); if (message == NULL) return FALSE; dbus_error_init(&error); reply = dbus_connection_send_with_reply_and_block(conn, message, -1, &error); dbus_message_unref(message); if (dbus_error_is_set(&error)) { dbus_error_free(&error); return FALSE; } dbus_message_iter_init(reply, &reply_iter); if (dbus_message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_ARRAY) { dbus_message_unref(reply); return FALSE; } dbus_message_iter_recurse(&reply_iter, &iter_array); while (dbus_message_iter_get_arg_type(&iter_array) == DBUS_TYPE_OBJECT_PATH) { const char *object_path; char *name = NULL; char *bdaddr = NULL; dbus_message_iter_get_basic(&iter_array, &object_path); if (device_is_printer(adapter, object_path, &name, &bdaddr)) { char *id; id = device_get_ieee1284_id(adapter, object_path); add_device_to_list(name, bdaddr, id); g_free(id); } g_free(name); g_free(bdaddr); dbus_message_iter_next(&iter_array); } dbus_message_unref(reply); return FALSE; } static DBusHandlerResult filter_func(DBusConnection *connection, DBusMessage *message, void *user_data) { if (dbus_message_is_signal(message, "org.bluez.Adapter1", "DeviceFound")) { const char *adapter, *bdaddr; char *name; DBusMessageIter iter; dbus_message_iter_init(message, &iter); dbus_message_iter_get_basic(&iter, &bdaddr); dbus_message_iter_next(&iter); adapter = dbus_message_get_path(message); if (parse_device_properties(&iter, &name, NULL)) remote_device_found(adapter, bdaddr, name); g_free (name); } else if (dbus_message_is_signal(message, "org.bluez.Adapter1", "DeviceDisappeared")) { const char *bdaddr; dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &bdaddr, DBUS_TYPE_INVALID); remote_device_disappeared(bdaddr); } else if (dbus_message_is_signal(message, "org.bluez.Adapter1", "PropertyChanged")) { DBusMessageIter iter, value_iter; const char *name; gboolean discovering; dbus_message_iter_init(message, &iter); dbus_message_iter_get_basic(&iter, &name); if (name == NULL || strcmp(name, "Discovering") != 0) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; dbus_message_iter_next(&iter); dbus_message_iter_recurse(&iter, &value_iter); dbus_message_iter_get_basic(&value_iter, &discovering); if (discovering == FALSE && doing_disco) { doing_disco = FALSE; discovery_completed(); } } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static gboolean list_printers(void) { /* 1. Connect to the bus * 2. Get the manager * 3. Get the default adapter * 4. Get a list of devices * 5. Get the class of each device * 6. Print the details from each printer device */ DBusError error; dbus_bool_t hcid_exists; DBusMessage *reply, *message; DBusMessageIter reply_iter; char *adapter, *match; conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); if (conn == NULL) return TRUE; dbus_error_init(&error); hcid_exists = dbus_bus_name_has_owner(conn, "org.bluez", &error); if (dbus_error_is_set(&error)) { dbus_error_free(&error); return TRUE; } if (!hcid_exists) return TRUE; /* Get the default adapter */ message = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "DefaultAdapter"); if (message == NULL) { dbus_connection_unref(conn); return FALSE; } reply = dbus_connection_send_with_reply_and_block(conn, message, -1, &error); dbus_message_unref(message); if (dbus_error_is_set(&error)) { dbus_error_free(&error); dbus_connection_unref(conn); /* No adapter */ return TRUE; } dbus_message_iter_init(reply, &reply_iter); if (dbus_message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_OBJECT_PATH) { dbus_message_unref(reply); dbus_connection_unref(conn); return FALSE; } dbus_message_iter_get_basic(&reply_iter, &adapter); adapter = g_strdup(adapter); dbus_message_unref(reply); if (!dbus_connection_add_filter(conn, filter_func, adapter, g_free)) { g_free(adapter); dbus_connection_unref(conn); return FALSE; } #define MATCH_FORMAT \ "type='signal'," \ "interface='org.bluez.Adapter1'," \ "sender='org.bluez'," \ "path='%s'" match = g_strdup_printf(MATCH_FORMAT, adapter); dbus_bus_add_match(conn, match, &error); g_free(match); /* Add the the recent devices */ list_known_printers(adapter); doing_disco = TRUE; message = dbus_message_new_method_call("org.bluez", adapter, "org.bluez.Adapter1", "StartDiscovery"); if (!dbus_connection_send_with_reply(conn, message, NULL, -1)) { dbus_message_unref(message); dbus_connection_unref(conn); g_free(adapter); return FALSE; } dbus_message_unref(message); loop = g_main_loop_new(NULL, TRUE); g_main_loop_run(loop); g_free(adapter); dbus_connection_unref(conn); return TRUE; } static gboolean print_ieee1284(const char *bdaddr) { DBusMessage *message, *reply, *adapter_reply; DBusMessageIter iter; char *object_path = NULL; char *adapter; char *id; adapter_reply = NULL; conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); if (conn == NULL) return FALSE; message = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "DefaultAdapter"); adapter_reply = dbus_connection_send_with_reply_and_block(conn, message, -1, NULL); dbus_message_unref(message); if (!adapter_reply) return FALSE; if (dbus_message_get_args(adapter_reply, NULL, DBUS_TYPE_OBJECT_PATH, &adapter, DBUS_TYPE_INVALID) == FALSE) { dbus_message_unref(adapter_reply); return FALSE; } message = dbus_message_new_method_call("org.bluez", adapter, "org.bluez.Adapter1", "FindDevice"); dbus_message_iter_init_append(message, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &bdaddr); if (adapter_reply != NULL) dbus_message_unref(adapter_reply); reply = dbus_connection_send_with_reply_and_block(conn, message, -1, NULL); dbus_message_unref(message); if (!reply) { message = dbus_message_new_method_call("org.bluez", adapter, "org.bluez.Adapter1", "CreateDevice"); dbus_message_iter_init_append(message, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &bdaddr); reply = dbus_connection_send_with_reply_and_block(conn, message, -1, NULL); dbus_message_unref(message); if (!reply) return FALSE; } if (dbus_message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID) == FALSE) { dbus_message_unref(reply); return FALSE; } id = device_get_ieee1284_id(adapter, object_path); if (id == NULL) { dbus_message_unref(reply); return FALSE; } printf("%s", id); g_free(id); dbus_message_unref(reply); return TRUE; } /* * Usage: printer-uri job-id user title copies options [file] * */ int main(int argc, char *argv[]) { sdp_session_t *sdp; bdaddr_t bdaddr; unsigned short ctrl_psm, data_psm; uint8_t channel, b[6]; char *ptr, str[3], device[18], service[13]; const char *uri, *cups_class; int i, err, fd, copies, proto; /* Make sure status messages are not buffered */ setbuf(stderr, NULL); /* Make sure output is not buffered */ setbuf(stdout, NULL); /* Ignore SIGPIPE signals */ #ifdef HAVE_SIGSET sigset(SIGPIPE, SIG_IGN); #elif defined(HAVE_SIGACTION) memset(&action, 0, sizeof(action)); action.sa_handler = SIG_IGN; sigaction(SIGPIPE, &action, NULL); #else signal(SIGPIPE, SIG_IGN); #endif /* HAVE_SIGSET */ if (argc == 1) { if (list_printers() == TRUE) return CUPS_BACKEND_OK; else return CUPS_BACKEND_FAILED; } else if (argc == 3 && strcmp(argv[1], "--get-deviceid") == 0) { if (bachk(argv[2]) < 0) { fprintf(stderr, "Invalid Bluetooth address '%s'\n", argv[2]); return CUPS_BACKEND_FAILED; } if (print_ieee1284(argv[2]) == FALSE) return CUPS_BACKEND_FAILED; return CUPS_BACKEND_OK; } if (argc < 6 || argc > 7) { fprintf(stderr, "Usage: bluetooth job-id user title copies" " options [file]\n"); fprintf(stderr, " bluetooth --get-deviceid [bdaddr]\n"); return CUPS_BACKEND_FAILED; } if (argc == 6) { fd = 0; copies = 1; } else { if ((fd = open(argv[6], O_RDONLY)) < 0) { perror("ERROR: Unable to open print file"); return CUPS_BACKEND_FAILED; } copies = atoi(argv[4]); } uri = getenv("DEVICE_URI"); if (!uri) uri = argv[0]; if (strncasecmp(uri, "bluetooth://", 12)) { fprintf(stderr, "ERROR: No device URI found\n"); return CUPS_BACKEND_FAILED; } ptr = argv[0] + 12; for (i = 0; i < 6; i++) { strncpy(str, ptr, 2); b[i] = (uint8_t) strtol(str, NULL, 16); ptr += 2; } sprintf(device, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", b[0], b[1], b[2], b[3], b[4], b[5]); str2ba(device, &bdaddr); ptr = strchr(ptr, '/'); if (ptr) { strncpy(service, ptr + 1, 12); if (!strncasecmp(ptr + 1, "spp", 3)) proto = 1; else if (!strncasecmp(ptr + 1, "hcrp", 4)) proto = 2; else proto = 0; } else { strcpy(service, "auto"); proto = 0; } cups_class = getenv("CLASS"); fprintf(stderr, "DEBUG: %s device %s service %s fd %d copies %d class %s\n", argv[0], device, service, fd, copies, cups_class ? cups_class : "(none)"); fputs("STATE: +connecting-to-device\n", stderr); service_search: sdp = sdp_connect(BDADDR_ANY, &bdaddr, SDP_RETRY_IF_BUSY); if (!sdp) { fprintf(stderr, "ERROR: Can't open Bluetooth connection\n"); return CUPS_BACKEND_FAILED; } switch (proto) { case 1: err = sdp_search_spp(sdp, &channel); break; case 2: err = sdp_search_hcrp(sdp, &ctrl_psm, &data_psm); break; default: proto = 2; err = sdp_search_hcrp(sdp, &ctrl_psm, &data_psm); if (err) { proto = 1; err = sdp_search_spp(sdp, &channel); } break; } sdp_close(sdp); if (err) { if (cups_class) { fputs("INFO: Unable to contact printer, queuing on " "next printer in class...\n", stderr); sleep(5); return CUPS_BACKEND_FAILED; } sleep(20); fprintf(stderr, "ERROR: Can't get service information\n"); goto service_search; } connect: switch (proto) { case 1: err = spp_print(BDADDR_ANY, &bdaddr, channel, fd, copies, cups_class); break; case 2: err = hcrp_print(BDADDR_ANY, &bdaddr, ctrl_psm, data_psm, fd, copies, cups_class); break; default: err = CUPS_BACKEND_FAILED; fprintf(stderr, "ERROR: Unsupported protocol\n"); break; } if (err == CUPS_BACKEND_FAILED && cups_class) { fputs("INFO: Unable to contact printer, queuing on " "next printer in class...\n", stderr); sleep(5); return CUPS_BACKEND_FAILED; } else if (err == CUPS_BACKEND_RETRY) { sleep(20); goto connect; } if (fd != 0) close(fd); if (!err) fprintf(stderr, "INFO: Ready to print\n"); return err; } bluez-5.87/profiles/PaxHeaders/ranging0000644000000000000000000000005015221755531015054 xustar0020 atime=1783094113 20 ctime=1783094105 bluez-5.87/profiles/ranging/0000755000000000000000000000000015221755531014612 5ustar00rootrootbluez-5.87/profiles/ranging/PaxHeaders/rap.c0000644000000000000000000000005015221752714016057 xustar0020 atime=1783092685 20 ctime=1783094105 bluez-5.87/profiles/ranging/rap.c0000644000000000000000000002603715221752714015550 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * BlueZ - Bluetooth protocol stack for Linux * * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "gdbus/gdbus.h" #include "bluetooth/bluetooth.h" #include "bluetooth/l2cap.h" #include "bluetooth/uuid.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/gatt-database.h" #include "attrib/gattrib.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/shared/rap.h" #include "attrib/att.h" #include "src/log.h" #include "src/btd.h" struct rap_adapter_data { struct btd_adapter *adapter; struct bt_hci *hci; /* Shared HCI raw channel */ int ref_count; /* Number of devices using this adapter */ }; struct rap_data { struct btd_device *device; struct btd_service *service; struct bt_rap *rap; unsigned int ready_id; struct rap_adapter_data *adapter_data; /* Shared adapter-level HCI */ void *hci_sm; /* Per-device HCI state machine */ }; static struct queue *sessions; static struct queue *adapter_list; /* List of rap_adapter_data */ /* Adapter data management */ static bool match_adapter(const void *data, const void *match_data) { const struct rap_adapter_data *adapter_data = data; const struct btd_adapter *adapter = match_data; return adapter_data->adapter == adapter; } static struct rap_adapter_data * rap_adapter_data_find(struct btd_adapter *adapter) { return queue_find(adapter_list, match_adapter, adapter); } static struct rap_adapter_data * rap_adapter_data_new(struct btd_adapter *adapter) { struct rap_adapter_data *adapter_data; int16_t hci_index; hci_index = btd_adapter_get_index(adapter); DBG("Creating new adapter_data for hci%d", hci_index); adapter_data = new0(struct rap_adapter_data, 1); if (!adapter_data) { error("Failed to allocate adapter_data"); return NULL; } adapter_data->adapter = adapter; adapter_data->ref_count = 0; /* Create adapter list if needed */ if (!adapter_list) { DBG("Creating new adapter_list"); adapter_list = queue_new(); } /* Add to queue BEFORE creating HCI to prevent race condition */ queue_push_tail(adapter_list, adapter_data); DBG("Added adapter_data to queue"); /* Create HCI raw channel for this adapter */ DBG("Opening HCI raw device for hci%d", hci_index); adapter_data->hci = bt_hci_new_raw_device(hci_index); if (!adapter_data->hci) { error("Failed to create HCI raw device for hci%d", hci_index); queue_remove(adapter_list, adapter_data); free(adapter_data); return NULL; } DBG("HCI raw channel created successfully for hci%d", hci_index); return adapter_data; } static struct rap_adapter_data * rap_adapter_data_ref(struct btd_adapter *adapter) { struct rap_adapter_data *adapter_data; adapter_data = rap_adapter_data_find(adapter); if (!adapter_data) { adapter_data = rap_adapter_data_new(adapter); if (!adapter_data) return NULL; } adapter_data->ref_count++; return adapter_data; } static void rap_adapter_data_unref(struct rap_adapter_data *adapter_data) { if (!adapter_data) return; adapter_data->ref_count--; if (adapter_data->ref_count > 0) return; /* No more devices using this adapter, clean up */ DBG("Cleaning up adapter HCI channel"); if (adapter_data->hci) { bt_hci_unref(adapter_data->hci); adapter_data->hci = NULL; } queue_remove(adapter_list, adapter_data); free(adapter_data); if (queue_isempty(adapter_list)) { queue_destroy(adapter_list, NULL); adapter_list = NULL; } } static struct rap_data *rap_data_new(struct btd_device *device) { struct rap_data *data; data = new0(struct rap_data, 1); data->device = device; return data; } static void rap_debug(const char *str, void *user_data) { DBG_IDX(0xffff, "%s", str); } static void rap_data_add(struct rap_data *data) { DBG("%p", data); if (queue_find(sessions, NULL, data)) { error("data %p already added", data); return; } bt_rap_set_debug(data->rap, rap_debug, NULL, NULL); if (!sessions) sessions = queue_new(); queue_push_tail(sessions, data); if (data->service) btd_service_set_user_data(data->service, data); } static bool match_data(const void *data, const void *match_data) { const struct rap_data *mdata = data; const struct bt_rap *rap = match_data; return mdata->rap == rap; } static void rap_data_free(struct rap_data *data) { if (data->service) { btd_service_set_user_data(data->service, NULL); bt_rap_set_user_data(data->rap, NULL); } bt_rap_ready_unregister(data->rap, data->ready_id); /* Detach per-device HCI state machine */ if (data->hci_sm) { bt_rap_detach_hci(data->rap, data->hci_sm); data->hci_sm = NULL; } /* Release reference to shared adapter HCI channel */ if (data->adapter_data) { rap_adapter_data_unref(data->adapter_data); data->adapter_data = NULL; } bt_rap_unref(data->rap); free(data); } static void rap_data_remove(struct rap_data *data) { DBG("%p", data); if (!queue_remove(sessions, data)) return; rap_data_free(data); if (queue_isempty(sessions)) { queue_destroy(sessions, NULL); sessions = NULL; } } static void rap_detached(struct bt_rap *rap, void *user_data) { struct rap_data *data; DBG("%p", rap); data = queue_find(sessions, match_data, rap); if (!data) { error("unable to find session"); return; } rap_data_remove(data); } static void rap_ready(struct bt_rap *rap, void *user_data) { DBG("%p", rap); } static void rap_attached(struct bt_rap *rap, void *user_data) { struct rap_data *data; struct bt_att *att; struct btd_device *device; DBG("%p", rap); data = queue_find(sessions, match_data, rap); if (data) { DBG("data is already present"); return; } att = bt_rap_get_att(rap); if (!att) { error("Unable to get att"); return; } device = btd_adapter_find_device_by_fd(bt_att_get_fd(att)); if (!device) { error("unable to find device"); return; } data = rap_data_new(device); data->rap = rap; rap_data_add(data); } static int rap_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct btd_adapter *adapter = device_get_adapter(device); struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct rap_data *data = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); /* Ignore, if we probed for this device already */ if (data) { error("Profile probed twice for this device"); return -EINVAL; } data = rap_data_new(device); data->service = service; data->rap = bt_rap_new(btd_gatt_database_get_db(database), btd_device_get_gatt_db(device)); if (!data->rap) { error("unable to create RAP instance"); free(data); return -EINVAL; } rap_data_add(data); data->ready_id = bt_rap_ready_register(data->rap, rap_ready, service, NULL); bt_rap_set_user_data(data->rap, service); return 0; } static void rap_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct rap_data *data; char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); data = btd_service_get_user_data(service); if (!data) { error("RAP Service not handled by profile"); return; } rap_data_remove(data); } static int rap_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct btd_adapter *adapter = device_get_adapter(device); struct bt_gatt_client *client = btd_device_get_gatt_client(device); struct rap_data *data = btd_service_get_user_data(service); struct bt_att *att; const bdaddr_t *bdaddr; uint8_t bdaddr_type; uint16_t handle; char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); if (!data) { error("RAP Service not handled by profile"); return -EINVAL; } /* init shared adapter HCI channel */ if (!data->adapter_data) { data->adapter_data = rap_adapter_data_ref(adapter); if (!data->adapter_data) { error("Failed to get adapter HCI channel"); return -EINVAL; } DBG("Using shared HCI channel for adapter (ref_count=%d)", data->adapter_data->ref_count); } /* per-device HCI state machine */ if (!data->hci_sm) { data->hci_sm = bt_rap_attach_hci(data->rap, data->adapter_data->hci, btd_opts.defaults.bcs.role, btd_opts.defaults.bcs.cs_sync_ant_sel, btd_opts.defaults.bcs.max_tx_power); if (!data->hci_sm) { error("Failed to attach HCI state machine for device"); rap_adapter_data_unref(data->adapter_data); data->adapter_data = NULL; return -EINVAL; } DBG("HCI state machine attached successfully for device"); } if (!bt_rap_attach(data->rap, client)) { error("RAP unable to attach"); return -EINVAL; } /* Set up connection handle mapping for CS event routing */ att = bt_rap_get_att(data->rap); bdaddr = device_get_address(device); bdaddr_type = device_get_le_address_type(device); if (att && data->adapter_data->hci && data->hci_sm) { if (bt_hci_get_conn_handle(data->adapter_data->hci, (const uint8_t *) bdaddr, &handle)) { DBG("Found conn handle 0x%04X for %s", handle, addr); DBG("Setting up handle mapping: handle=0x%04X", handle); bt_rap_set_conn_hndl(data->hci_sm, data->rap, handle, (const uint8_t *) bdaddr, bdaddr_type, btd_device_is_initiator(device)); } else { error("Failed to find connection handle for device %s", addr); } } btd_service_connecting_complete(service, 0); return 0; } static int rap_disconnect(struct btd_service *service) { DBG(" "); btd_service_disconnecting_complete(service, 0); return 0; } static int rap_connect(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); return 0; } static int rap_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { struct btd_gatt_database *database = btd_adapter_get_database(adapter); DBG("RAP path %s", adapter_get_path(adapter)); bt_rap_add_db(btd_gatt_database_get_db(database)); return 0; } static void rap_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { DBG(""); } static struct btd_profile rap_profile = { .name = "rap", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .remote_uuid = GATT_UUID, .local_uuid = RAS_UUID, .device_probe = rap_probe, .device_remove = rap_remove, .accept = rap_accept, .connect = rap_connect, .disconnect = rap_disconnect, .adapter_probe = rap_server_probe, .adapter_remove = rap_server_remove, .experimental = true, }; static unsigned int rap_id; static int rap_init(void) { int err; err = btd_profile_register(&rap_profile); if (err) return err; rap_id = bt_rap_register(rap_attached, rap_detached, NULL); return 0; } static void rap_exit(void) { btd_profile_unregister(&rap_profile); bt_rap_unregister(rap_id); } BLUETOOTH_PLUGIN_DEFINE(rap, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, rap_init, rap_exit) bluez-5.87/profiles/ranging/PaxHeaders/rap_hci.c0000644000000000000000000000005015221752714016702 xustar0020 atime=1783092685 20 ctime=1783094105 bluez-5.87/profiles/ranging/rap_hci.c0000644000000000000000000013341115221752714016366 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * BlueZ - Bluetooth protocol stack for Linux * * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "lib/bluetooth/bluetooth.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/rap.h" #include "src/shared/att.h" #include "src/log.h" #include "monitor/bt.h" /* Macro to sign-extend an N-bit value to 16-bit signed integer */ #define SIGN_EXTEND_TO_16(val, bits) \ ((int16_t)(((val) ^ (1U << ((bits)-1))) - (1U << ((bits)-1)))) /* CS State Definitions */ enum cs_state { CS_STATE_INIT, CS_STATE_STOPPED, CS_STATE_STARTED, CS_STATE_WAIT_CONFIG_CMPLT, CS_STATE_WAIT_SEC_CMPLT, CS_STATE_WAIT_PROC_CMPLT, CS_STATE_HOLD, CS_STATE_UNSPECIFIED }; static const char * const state_names[] = { "CS_STATE_INIT", "CS_STATE_STOPPED", "CS_STATE_STARTED", "CS_STATE_WAIT_CONFIG_CMPLT", "CS_STATE_WAIT_SEC_CMPLT", "CS_STATE_WAIT_PROC_CMPLT", "CS_STATE_HOLD", "CS_STATE_UNSPECIFIED" }; _Static_assert(ARRAY_SIZE(state_names) == CS_STATE_UNSPECIFIED + 1, "state_names[] out of sync with enum cs_state"); /* Callback Function Type */ typedef void (*cs_callback_t)(uint16_t length, const void *param, void *user_data); /* State Machine Context */ struct cs_state_machine { enum cs_state current_state; enum cs_state old_state; struct bt_hci *hci; struct bt_rap *rap; struct queue *event_ids; bool initiator; bool procedure_active; struct bt_rap_hci_cs_options cs_opt; /* Per-instance CS options */ uint8_t role_enable; /* Role value for HCI commands (1, 2, or 3) */ struct queue *conn_mappings; /* Per-instance connection mappings */ struct timespec last_chan_class_time; /* For 1-second rate limit */ }; /* Connection Handle Mapping */ struct rap_conn_mapping { uint16_t handle; uint8_t bdaddr[6]; uint8_t bdaddr_type; bool is_central; /* true if local device is BLE Central on this link */ struct bt_att *att; struct bt_rap *rap; }; /* Connection Mapping Helper Functions */ static void mapping_free(void *data) { struct rap_conn_mapping *mapping = data; if (!mapping) return; free(mapping); } static bool match_mapping_handle(const void *a, const void *b) { const struct rap_conn_mapping *mapping = a; uint16_t handle = PTR_TO_UINT(b); return mapping->handle == handle; } static struct rap_conn_mapping *find_mapping_by_handle( struct cs_state_machine *sm, uint16_t handle) { if (!sm || !sm->conn_mappings) return NULL; return queue_find(sm->conn_mappings, match_mapping_handle, UINT_TO_PTR(handle)); } static bool add_conn_mapping(struct cs_state_machine *sm, uint16_t handle, const uint8_t *bdaddr, uint8_t bdaddr_type, bool is_central, struct bt_att *att, struct bt_rap *rap) { struct rap_conn_mapping *mapping; if (!sm) return false; /* Check if mapping already exists */ mapping = find_mapping_by_handle(sm, handle); if (mapping) { /* Update existing mapping */ if (bdaddr) memcpy(mapping->bdaddr, bdaddr, 6); mapping->bdaddr_type = bdaddr_type; mapping->is_central = is_central; mapping->att = att; mapping->rap = rap; return true; } /* Create new mapping */ mapping = new0(struct rap_conn_mapping, 1); if (!mapping) return false; mapping->handle = handle; if (bdaddr) memcpy(mapping->bdaddr, bdaddr, 6); mapping->bdaddr_type = bdaddr_type; mapping->is_central = is_central; mapping->att = att; mapping->rap = rap; return queue_push_tail(sm->conn_mappings, mapping); } static void remove_conn_mapping(struct cs_state_machine *sm, uint16_t handle) { struct rap_conn_mapping *mapping; if (!sm || !sm->conn_mappings) return; mapping = queue_remove_if(sm->conn_mappings, match_mapping_handle, UINT_TO_PTR(handle)); if (mapping) mapping_free(mapping); } /* State Machine Functions */ static void cs_state_machine_init(struct cs_state_machine *sm, struct bt_rap *rap, struct bt_hci *hci, uint8_t role, uint8_t cs_sync_ant_sel, int8_t max_tx_power) { if (!sm) return; sm->current_state = CS_STATE_UNSPECIFIED; sm->rap = rap; sm->hci = hci; sm->initiator = false; sm->procedure_active = false; sm->conn_mappings = queue_new(); /* Store role_enable for HCI commands (1, 2, or 3 from config) */ sm->role_enable = role; /* Initialize per-instance CS options * Note: cs_opt.role will be overwritten with actual role (0x00 or 0x01) * from config complete event, but role_enable preserves the HCI value */ sm->cs_opt.role = role; sm->cs_opt.cs_sync_ant_sel = cs_sync_ant_sel; sm->cs_opt.max_tx_power = max_tx_power; sm->cs_opt.rtt_type = 0; /* Will be set from config complete event */ } /* State Transition Logic */ static void cs_set_state(struct cs_state_machine *sm, enum cs_state new_state) { if (!sm) return; if (sm->current_state == new_state) return; /* Validate state values before array access */ if ((unsigned int)sm->current_state >= ARRAY_SIZE(state_names) || (unsigned int)new_state >= ARRAY_SIZE(state_names)) { error("Invalid state transition attempted"); return; } DBG("[STATE] Transition: %s → %s", state_names[sm->current_state], state_names[new_state]); sm->old_state = sm->current_state; sm->current_state = new_state; } static enum cs_state cs_get_current_state(struct cs_state_machine *sm) { return sm ? sm->current_state : CS_STATE_UNSPECIFIED; } static bool is_initiator_role(const struct cs_state_machine *sm) { return sm->role_enable == 0x01 || sm->role_enable == 0x03; } /* Helper function to send read remote capabilities for all connections */ static bool bt_rap_read_remote_supported_capabilities(void *hci_sm, uint16_t handle) { struct cs_state_machine *sm = hci_sm; struct bt_hci_cmd_le_cs_rd_rem_supp_cap cmd; unsigned int status; if (!sm || !sm->hci) { error("Invalid state machine or HCI"); return false; } DBG("Sending Read Remote Supported Capabilities for handle 0x%04X", handle); memset(&cmd, 0, sizeof(cmd)); cmd.handle = cpu_to_le16(handle); status = bt_hci_send(sm->hci, BT_HCI_CMD_LE_CS_RD_REM_SUPP_CAP, &cmd, sizeof(cmd), NULL, sm, NULL); if (!status) { error("Failed to send Read Remote Capabilities command"); return false; } DBG("Read Remote Capabilities command sent successfully"); return true; } /* Helper function to send read remote capabilities for all connections */ static void send_read_remote_cap_for_mapping(void *data, void *user_data) { struct rap_conn_mapping *mapping = data; struct cs_state_machine *sm = user_data; if (!mapping || !sm) return; DBG("Sending read remote capabilities for handle 0x%04X", mapping->handle); bt_rap_read_remote_supported_capabilities(sm, mapping->handle); } /* HCI Event Callbacks */ static void rap_rd_loc_supp_cap_done_cb(const void *data, uint8_t size, void *user_data) { const struct bt_hci_rsp_le_cs_rd_loc_supp_cap *rsp; struct cs_state_machine *sm = (struct cs_state_machine *) user_data; if (!sm || !data || size < sizeof(struct bt_hci_rsp_le_cs_rd_loc_supp_cap)) return; DBG("size=0x%02X", size); rsp = (const struct bt_hci_rsp_le_cs_rd_loc_supp_cap *) data; if (rsp->status != 0) { error("Read Local Supported Capabilities failed: 0x%02X", rsp->status); return; } DBG("Local CS Capabilities:"); DBG(" Num Config Supported: %u", rsp->num_config_supported); DBG(" Max Consecutive Procedures: %u", rsp->max_consecutive_procedures_supported); DBG(" Num Antennas: %u", rsp->num_antennas_supported); DBG(" Max Antenna Paths: %u", rsp->max_antenna_paths_supported); DBG(" Roles Supported: 0x%02X", rsp->roles_supported); DBG(" Modes Supported: 0x%02X", rsp->modes_supported); DBG(" RTT Capability: 0x%02X", rsp->rtt_capability); DBG(" RTT AA Only N: %u", rsp->rtt_aa_only_n); DBG(" RTT Sounding N: %u", rsp->rtt_sounding_n); DBG(" RTT Random Payload N: %u", rsp->rtt_random_payload_n); DBG(" NADM Sounding Capability: 0x%04X", rsp->nadm_sounding_capability); DBG(" NADM Random Capability: 0x%04X", rsp->nadm_random_capability); DBG(" CS Sync PHYs Supported: 0x%02X", rsp->cs_sync_phys_supported); DBG(" Subfeatures Supported: 0x%04X", rsp->subfeatures_supported); DBG(" T_IP1 Times Supported: 0x%04X", rsp->t_ip1_times_supported); DBG(" T_IP2 Times Supported: 0x%04X", rsp->t_ip2_times_supported); DBG(" T_FCS Times Supported: 0x%04X", rsp->t_fcs_times_supported); DBG(" T_PM Times Supported: 0x%04X", rsp->t_pm_times_supported); DBG(" T_SW Time Supported: %u", rsp->t_sw_time_supported); DBG(" TX SNR Capability: 0x%02X", rsp->tx_snr_capability); /* Transition to INIT state before reading remote capabilities */ cs_set_state(sm, CS_STATE_INIT); /* Send read remote capabilities for all connected devices */ if (sm->conn_mappings) { DBG("Sending read remote capabilities for all connections"); queue_foreach(sm->conn_mappings, send_read_remote_cap_for_mapping, sm); } } static void rap_send_hci_cs_create_config_command(struct cs_state_machine *sm, uint16_t handle) { struct bt_hci_cmd_le_cs_create_config cmd; unsigned int status; uint8_t channel_map[10] = { 0xFC, 0xFF, 0x7F, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F }; if (!sm || !sm->hci) { error("CS Create Config: sm or hci is null"); return; } DBG("Sending CS Create Config command for handle 0x%04X", handle); memset(&cmd, 0, sizeof(cmd)); cmd.handle = cpu_to_le16(handle); cmd.create_context = 1; /* Default values, will change to pick user given values later */ cmd.config_id = 0x00; cmd.main_mode_type = 0x01; cmd.sub_mode_type = 0xFF; cmd.min_main_mode_steps = 0x02; cmd.max_main_mode_steps = 0x03; cmd.main_mode_repetition = 0x01; cmd.mode_0_steps = 0x02; cmd.role = 0x00; cmd.rtt_type = 0x00; cmd.cs_sync_phy = 0x01; memcpy(cmd.channel_map, channel_map, 10); cmd.channel_map_repetition = 0x01; cmd.channel_selection_type = 0x00; cmd.ch3c_shape = 0x00; cmd.ch3c_jump = 0x02; cmd.reserved = 0x00; status = bt_hci_send(sm->hci, BT_HCI_CMD_LE_CS_CREATE_CONFIG, &cmd, sizeof(cmd), NULL, sm, NULL); if (!status) { error("Failed to send CS Create Config command"); cs_set_state(sm, CS_STATE_STOPPED); return; } DBG("CS Create Config command sent successfully"); } static void rap_def_settings_done_cb(const void *data, uint8_t size, void *user_data) { const struct bt_hci_rsp_le_cs_set_def_settings *rp; struct cs_state_machine *sm = user_data; if (!sm || !data || size < sizeof(*rp)) return; DBG("size=0x%02X", size); rp = (const struct bt_hci_rsp_le_cs_set_def_settings *) data; if (cs_get_current_state(sm) == CS_STATE_STOPPED || cs_get_current_state(sm) == CS_STATE_UNSPECIFIED) { DBG("Def settings response in terminal state, ignoring"); return; } if (rp->status == 0) { /* Success - proceed to configuration */ cs_set_state(sm, CS_STATE_WAIT_CONFIG_CMPLT); /* If role is initiator, send CS Create Config command */ if (is_initiator_role(sm)) rap_send_hci_cs_create_config_command(sm, rp->handle); else DBG("Reflector role: Waiting for CS Config Completed"); } else { /* Error - transition to stopped */ error("CS Set default setting failed with status 0x%02X", rp->status); cs_set_state(sm, CS_STATE_STOPPED); } } static void rap_send_hci_cs_remove_config_command(struct cs_state_machine *sm, uint16_t handle) { struct bt_hci_cmd_le_cs_remove_config cmd; unsigned int status; if (!sm || !sm->hci) { error("CS Remove Config: sm or hci is null"); return; } DBG("Sending CS Remove Config command for handle 0x%04X", handle); memset(&cmd, 0, sizeof(cmd)); cmd.handle = cpu_to_le16(handle); cmd.config_id = 0x00; /* Default config ID */ status = bt_hci_send(sm->hci, BT_HCI_CMD_LE_CS_REMOVE_CONFIG, &cmd, sizeof(cmd), NULL, sm, NULL); if (!status) { error("Failed to send CS Remove Config command"); cs_set_state(sm, CS_STATE_STOPPED); return; } DBG("CS Remove Config command sent successfully"); } static void rap_send_hci_cs_security_enable_command( struct cs_state_machine *sm, uint16_t handle) { struct bt_hci_cmd_le_cs_sec_enable cmd; unsigned int status; if (!sm || !sm->hci) { error("CS Security Enable: sm or hci is null"); return; } DBG("Sending CS Security Enable command for handle 0x%04X", handle); memset(&cmd, 0, sizeof(cmd)); cmd.handle = cpu_to_le16(handle); status = bt_hci_send(sm->hci, BT_HCI_CMD_LE_CS_SEC_ENABLE, &cmd, sizeof(cmd), NULL, sm, NULL); if (!status) { error("Failed to send CS Security Enable command"); cs_set_state(sm, CS_STATE_STOPPED); return; } DBG("CS Security Enable command sent successfully"); } static bool rap_send_hci_cs_set_procedure_parameters( struct cs_state_machine *sm, uint16_t handle) { struct bt_hci_cmd_le_cs_set_proc_params cmd; unsigned int status; uint8_t min_sub_event_len[3] = { 0x00, 0x20, 0x00 }; uint8_t max_sub_event_len[3] = { 0x03, 0x20, 0x00 }; if (!sm || !sm->hci) { error("CS Set Procedure Parameters: sm or hci is null"); return false; } DBG("Sending CS Set Procedure Parameters for handle 0x%04X", handle); memset(&cmd, 0, sizeof(cmd)); cmd.handle = cpu_to_le16(handle); /* Default values, will change to pick user given values later */ cmd.config_id = 0x00; cmd.max_procedure_len = 0x0640; cmd.min_procedure_interval = 0x1E; cmd.max_procedure_interval = 0x96; cmd.max_procedure_count = 0x00; memcpy(cmd.min_subevent_len, min_sub_event_len, 3); memcpy(cmd.max_subevent_len, max_sub_event_len, 3); cmd.tone_antenna_config_selection = 0x07; cmd.phy = 0x01; cmd.tx_power_delta = 0x80; cmd.preferred_peer_antenna = 0x03; cmd.snr_control_initiator = 0xFF; cmd.snr_control_reflector = 0xFF; status = bt_hci_send(sm->hci, BT_HCI_CMD_LE_CS_SET_PROC_PARAMS, &cmd, sizeof(cmd), NULL, sm, NULL); if (!status) { error("Failed to send CS Set Procedure Parameters command"); return false; } DBG("CS Set Procedure Parameters command sent successfully"); return true; } static bool rap_send_hci_cs_procedure_enable(struct cs_state_machine *sm, uint16_t handle, bool enable_proc) { struct bt_hci_cmd_le_cs_proc_enable cmd; unsigned int status; if (!sm || !sm->hci) { error("CS Procedure Enable: sm or hci is null"); return false; } DBG("Sending CS Procedure Enable for handle 0x%04X", handle); memset(&cmd, 0, sizeof(cmd)); cmd.handle = cpu_to_le16(handle); cmd.config_id = 0x00; /* Default config Id */ cmd.enable = enable_proc ? 0x01 : 0x00; status = bt_hci_send(sm->hci, BT_HCI_CMD_LE_CS_PROC_ENABLE, &cmd, sizeof(cmd), NULL, sm, NULL); if (!status) { error("Failed to send CS Procedure Enable command"); return false; } DBG("CS Procedure Enable command sent successfully"); return true; } static void rap_send_hci_def_settings_command(struct cs_state_machine *sm, const struct bt_hci_evt_le_cs_rd_rem_supp_cap_complete *ev) { struct bt_hci_cmd_le_cs_set_def_settings cp; unsigned int status; if (!sm || !sm->hci) { error("Set Def Settings: sm or hci is null"); return; } memset(&cp, 0, sizeof(cp)); if (ev->handle) cp.handle = ev->handle; cp.role_enable = sm->role_enable; /* Use preserved HCI command value */ cp.cs_sync_antenna_selection = sm->cs_opt.cs_sync_ant_sel; cp.max_tx_power = sm->cs_opt.max_tx_power; status = bt_hci_send(sm->hci, BT_HCI_CMD_LE_CS_SET_DEF_SETTINGS, &cp, sizeof(cp), rap_def_settings_done_cb, sm, NULL); DBG("sending set default settings case, status : %d", status); if (!status) error("Failed to send default settings cmd"); } static void rap_rd_rem_fae_cmplt_evt(const void *data, uint8_t size, void *user_data) { struct cs_state_machine *sm = (struct cs_state_machine *) user_data; const struct bt_hci_evt_le_cs_rd_rem_fae_complete *evt; struct iovec iov; int i; if (!sm || !data || size < sizeof(struct bt_hci_evt_le_cs_rd_rem_fae_complete)) return; /* Initialize iovec with the event data */ iov.iov_base = (void *) data; iov.iov_len = size; /* Pull the entire structure at once */ evt = util_iov_pull_mem(&iov, sizeof(*evt)); if (!evt) { error("Failed to pull remote FAE complete struct"); return; } DBG("status=0x%02X, handle=0x%04X", evt->status, evt->handle); /* Check status */ if (evt->status != 0) { /* Status 0x11 (Unsupported Feature or Parameter Value) means * the remote has zero FAE, the procedure continues * to the Default Settings step. */ if (evt->status == 0x11) { DBG("Remote FAE=0 (No_FAE), proceed to Def Settings"); if (is_initiator_role(sm)) { struct bt_hci_evt_le_cs_rd_rem_supp_cap_complete tmp_ev; memset(&tmp_ev, 0, sizeof(tmp_ev)); tmp_ev.handle = evt->handle; DBG("Initiator: send def settings (No_FAE)"); rap_send_hci_def_settings_command(sm, &tmp_ev); } else { DBG("Reflector role: continuing after No_FAE"); cs_set_state(sm, CS_STATE_INIT); } return; } error("Remote FAE Table read failed with status 0x%02X", evt->status); cs_set_state(sm, CS_STATE_STOPPED); return; } DBG("Remote FAE Table received:"); for (i = 0; i < 72; i += 8) { DBG(" [%02d-%02d]: %02X %02X %02X %02X %02X %02X %02X %02X", i, i+7, evt->remote_fae_table[i], evt->remote_fae_table[i+1], evt->remote_fae_table[i+2], evt->remote_fae_table[i+3], evt->remote_fae_table[i+4], evt->remote_fae_table[i+5], evt->remote_fae_table[i+6], evt->remote_fae_table[i+7]); } /* After receiving FAE Table, send default settings */ /* Local capabilities already read before this event */ if (is_initiator_role(sm)) { struct bt_hci_evt_le_cs_rd_rem_supp_cap_complete tmp_ev; memset(&tmp_ev, 0, sizeof(tmp_ev)); tmp_ev.handle = evt->handle; DBG("Initiator role: send def settings after FAE table"); rap_send_hci_def_settings_command(sm, &tmp_ev); } else { DBG("Reflector role: Proceeding after FAE Table"); cs_set_state(sm, CS_STATE_INIT); } } static bool bt_rap_read_remote_fae_table(void *hci_sm, uint16_t handle) { struct cs_state_machine *sm = hci_sm; struct bt_hci_cmd_le_cs_rd_rem_fae cmd; unsigned int status; if (!sm || !sm->hci) { error("Invalid state machine or HCI"); return false; } DBG("Sending Read Remote FAE Table for handle 0x%04X", handle); memset(&cmd, 0, sizeof(cmd)); cmd.handle = cpu_to_le16(handle); status = bt_hci_send(sm->hci, BT_HCI_CMD_LE_CS_RD_REM_FAE, &cmd, sizeof(cmd), NULL, sm, NULL); if (!status) { error("Failed to send Read Remote FAE Table command"); return false; } DBG("Read Remote FAE Table command sent successfully"); return true; } static void rap_rd_rmt_supp_cap_cmplt_evt(const void *data, uint8_t size, void *user_data) { struct cs_state_machine *sm = user_data; const struct bt_hci_evt_le_cs_rd_rem_supp_cap_complete *evt; struct iovec iov; uint16_t subfeatures_supported; if (!sm || !data || size < sizeof(*evt)) return; /* Initialize iovec with the event data */ iov.iov_base = (void *) data; iov.iov_len = size; /* Pull the entire structure at once */ evt = util_iov_pull_mem(&iov, sizeof(*evt)); if (!evt) { error("Failed to pull remote cap complete struct"); return; } DBG("status=0x%02X, handle=0x%04X", evt->status, evt->handle); /* Check status */ if (evt->status != 0) { error("Remote capabilities failed with status 0x%02X", evt->status); cs_set_state(sm, CS_STATE_STOPPED); return; } DBG("num_config=%u, ", evt->num_config_supported); DBG("max_consecutive_proc=%u, num_antennas=%u, ", evt->max_consecutive_procedures_supported, evt->num_antennas_supported); DBG("max_antenna_paths=%u, roles=0x%02X, modes=0x%02X", evt->max_antenna_paths_supported, evt->roles_supported, evt->modes_supported); subfeatures_supported = le16_to_cpu(evt->subfeatures_supported); DBG("subfeatures_supported=0x%04X", subfeatures_supported); /* Check Bit 1 of subfeatures_supported (0x0002) */ if (!(subfeatures_supported & 0x0002)) { DBG("Bit 1 not set, sending Read Remote FAE Table"); bt_rap_read_remote_fae_table(sm, evt->handle); return; } /* Local capabilities already read before this event */ if (is_initiator_role(sm)) { DBG("Initiator role: send def settings cmd for handle 0x%04X", evt->handle); rap_send_hci_def_settings_command(sm, evt); } else { DBG("Reflector role: send def settings cmd"); cs_set_state(sm, CS_STATE_INIT); rap_send_hci_def_settings_command(sm, evt); } } static void rap_cs_config_cmplt_evt(const void *data, uint8_t size, void *user_data) { struct cs_state_machine *sm = user_data; const struct bt_hci_evt_le_cs_config_complete *evt; struct rap_ev_cs_config_cmplt rap_ev; struct iovec iov; if (!sm || !data || size < sizeof(*evt)) return; /* Initialize iovec with the event data */ iov.iov_base = (void *) data; iov.iov_len = size; DBG("size=0x%02X", size); /* State Check */ if (cs_get_current_state(sm) != CS_STATE_WAIT_CONFIG_CMPLT) { DBG("Event received in Wrong State!! "); DBG("Expected : CS_STATE_WAIT_CONFIG_CMPLT"); return; } /* Pull the entire structure at once */ evt = util_iov_pull_mem(&iov, sizeof(*evt)); if (!evt) { error("Failed to pull config complete struct"); return; } DBG("status=0x%02X, handle=0x%04X", evt->status, evt->handle); /* Check status */ if (evt->status != 0) { if (evt->action != 0x00) { /* Create/update failed — try to remove the config */ error("Configuration failed with status 0x%02X", evt->status); rap_send_hci_cs_remove_config_command(sm, evt->handle); } else { error("CS Config Remove failed with status 0x%02X", evt->status); } cs_set_state(sm, CS_STATE_STOPPED); return; } /* Copy fields to rap_ev structure */ rap_ev.status = evt->status; rap_ev.conn_hdl = cpu_to_le16(evt->handle); rap_ev.config_id = evt->config_id; rap_ev.action = evt->action; rap_ev.main_mode_type = evt->main_mode_type; rap_ev.sub_mode_type = evt->sub_mode_type; rap_ev.min_main_mode_steps = evt->min_main_mode_steps; rap_ev.max_main_mode_steps = evt->max_main_mode_steps; rap_ev.main_mode_rep = evt->main_mode_repetition; rap_ev.mode_0_steps = evt->mode_0_steps; rap_ev.role = evt->role; rap_ev.rtt_type = evt->rtt_type; rap_ev.cs_sync_phy = evt->cs_sync_phy; memcpy(rap_ev.channel_map, evt->channel_map, 10); rap_ev.channel_map_rep = evt->channel_map_repetition; rap_ev.channel_sel_type = evt->channel_selection_type; rap_ev.ch3c_shape = evt->ch3c_shape; rap_ev.ch3c_jump = evt->ch3c_jump; rap_ev.reserved = evt->reserved; rap_ev.t_ip1_time = evt->t_ip1_time; rap_ev.t_ip2_time = evt->t_ip2_time; rap_ev.t_fcs_time = evt->t_fcs_time; rap_ev.t_pm_time = evt->t_pm_time; /* Store role and rtt_type from config complete event * Note: evt->role contains actual role * (CS_INITIATOR=0x00, CS_REFLECTOR=0x01) * which is different from the role_enable value sent in HCI command */ sm->cs_opt.role = rap_ev.role; sm->cs_opt.rtt_type = rap_ev.rtt_type; DBG("config_id=%u, action=%u, ", rap_ev.config_id, rap_ev.action); DBG("main_mode=%u, sub_mode=%u, role=%u, rtt_type=%u", rap_ev.main_mode_type, rap_ev.sub_mode_type, rap_ev.role, rap_ev.rtt_type); if (rap_ev.action == 0x00) { cs_set_state(sm, CS_STATE_UNSPECIFIED); DBG("CS Config Removed !!!"); bt_rap_hci_cs_config_complete_callback(size, &rap_ev, sm->rap); return; } /* Success - proceed to Security enable complete */ cs_set_state(sm, CS_STATE_WAIT_SEC_CMPLT); /* CS Security Enable may only be issued by the BLE Central */ if (rap_ev.role == 0x00) { /* Initiator role */ struct rap_conn_mapping *mapping; mapping = find_mapping_by_handle(sm, evt->handle); if (!mapping || !mapping->is_central) { error("CS Security Enable skipped: not BLE Central"); cs_set_state(sm, CS_STATE_STOPPED); return; } if (bt_att_get_security(mapping->att, NULL) < BT_ATT_SECURITY_MEDIUM) { error("CS Security Enable skipped: not encrypted"); cs_set_state(sm, CS_STATE_STOPPED); return; } DBG("Central,encrypted: Sending CS Security Enable command"); rap_send_hci_cs_security_enable_command(sm, evt->handle); } else { /* Reflector role */ DBG("Reflector role: Waiting for security enable event..."); } /* Send callback to RAP Profile */ bt_rap_hci_cs_config_complete_callback(size, &rap_ev, sm->rap); } static void rap_cs_sec_enable_cmplt_evt(const void *data, uint8_t size, void *user_data) { struct cs_state_machine *sm = user_data; struct rap_ev_cs_sec_enable_cmplt rap_ev; struct iovec iov; uint8_t status; uint16_t handle; if (!sm || !data || size < sizeof(struct bt_hci_evt_le_cs_sec_enable_complete)) return; /* Initialize iovec with the event data */ iov.iov_base = (void *) data; iov.iov_len = size; DBG("size=0x%02X", size); /* State Check */ if (cs_get_current_state(sm) != CS_STATE_WAIT_SEC_CMPLT) { DBG("Event received in Wrong State!! "); DBG("Expected : CS_STATE_WAIT_SEC_CMPLT"); return; } /* Parse all fields in order using iovec */ if (!util_iov_pull_u8(&iov, &status)) { error("Failed to parse Status"); return; } if (!util_iov_pull_le16(&iov, &handle)) { error("Failed to parse Connection_Handle"); return; } rap_ev.status = status; rap_ev.conn_hdl = cpu_to_le16(handle); DBG("status=0x%02X, handle=0x%04X", rap_ev.status, handle); if (rap_ev.status == 0) { /* Success - proceed to configuration */ cs_set_state(sm, CS_STATE_WAIT_PROC_CMPLT); /* Check if role is initiator */ if (sm->cs_opt.role == CS_INITIATOR) { DBG("Initiator role: Sending CS Set Procedure Params"); if (!rap_send_hci_cs_set_procedure_parameters( sm, handle)) { error("Failed to send CS Set Procedure Params"); cs_set_state(sm, CS_STATE_STOPPED); return; } DBG("Initiator role: Sending CS Procedure Enable"); if (!rap_send_hci_cs_procedure_enable(sm, handle, true)) { error("Failed to send CS Procedure Enable"); cs_set_state(sm, CS_STATE_STOPPED); return; } } else { /* Reflector role */ DBG("Reflector role: Waiting for CS Proc compl event"); } } else { /* Error - transition to stopped */ error("Security enable failed with status 0x%02X", rap_ev.status); cs_set_state(sm, CS_STATE_STOPPED); } /* Send callback to RAP Profile */ bt_rap_hci_cs_sec_enable_complete_callback(size, &rap_ev, sm->rap); } static void rap_cs_proc_enable_cmplt_evt(const void *data, uint8_t size, void *user_data) { struct cs_state_machine *sm = user_data; const struct bt_hci_evt_le_cs_proc_enable_complete *evt; struct rap_ev_cs_proc_enable_cmplt rap_ev; struct iovec iov; if (!sm || !data || size < sizeof(struct bt_hci_evt_le_cs_proc_enable_complete)) return; /* Initialize iovec with the event data */ iov.iov_base = (void *) data; iov.iov_len = size; DBG("size=0x%02X", size); /* State Check */ if (cs_get_current_state(sm) != CS_STATE_WAIT_PROC_CMPLT) { DBG("Event received in Wrong State!! "); DBG("Expected : CS_STATE_WAIT_PROC_CMPLT"); return; } /* Pull the entire structure at once */ evt = util_iov_pull_mem(&iov, sizeof(*evt)); if (!evt) { error("Failed to pull proc enable complete struct"); return; } DBG("status=0x%02X, handle=0x%04X", evt->status, evt->handle); /* Check status */ if (evt->status != 0) { error("Procedure enable failed with status 0x%02X", evt->status); cs_set_state(sm, CS_STATE_STOPPED); sm->procedure_active = false; return; } /* Copy fields to rap_ev structure */ rap_ev.status = evt->status; rap_ev.conn_hdl = cpu_to_le16(evt->handle); rap_ev.config_id = evt->config_id; rap_ev.state = evt->state; rap_ev.tone_ant_config_sel = evt->tone_antenna_config_selection; rap_ev.sel_tx_pwr = evt->selected_tx_power; memcpy(rap_ev.sub_evt_len, evt->subevent_len, 3); rap_ev.sub_evts_per_evt = evt->subevents_per_event; rap_ev.sub_evt_intrvl = evt->subevent_interval; rap_ev.evt_intrvl = evt->event_interval; rap_ev.proc_intrvl = evt->procedure_interval; rap_ev.proc_counter = evt->procedure_count; rap_ev.max_proc_len = evt->max_procedure_len; DBG("config_id=%u, state=%u, ", rap_ev.config_id, rap_ev.state); DBG("sub_evts_per_evt=%u, evt_intrvl=%u, proc_intrvl=%u", rap_ev.sub_evts_per_evt, rap_ev.evt_intrvl, rap_ev.proc_intrvl); /* Success - procedure started */ if (rap_ev.state == 0x01) { cs_set_state(sm, CS_STATE_STARTED); sm->procedure_active = true; } else if (rap_ev.state == 0x00) { cs_set_state(sm, CS_STATE_STOPPED); sm->procedure_active = false; } /* Send callback to RAP Profile */ bt_rap_hci_cs_procedure_enable_complete_callback(size, &rap_ev, sm->rap); } static void parse_i_q_sample(struct iovec *iov, int16_t *i_sample, int16_t *q_sample) { uint32_t buffer; uint32_t i12; uint32_t q12; /* Pull 24-bit little-endian value from iovec */ if (!util_iov_pull_le24(iov, &buffer)) { *i_sample = 0; *q_sample = 0; return; } /* Extract 12-bit I and Q values from 24-bit buffer */ i12 = buffer & 0x0FFFU; /* bits 0..11 */ q12 = (buffer >> 12) & 0x0FFFU; /* bits 12..23 */ /* Sign-extend 12-bit values to 16-bit using macro */ *i_sample = SIGN_EXTEND_TO_16(i12, 12); *q_sample = SIGN_EXTEND_TO_16(q12, 12); } /* Parse CS Mode 0 step data */ static void parse_mode_zero_data(struct iovec *iov, struct cs_mode_zero_data *mode_data, uint8_t cs_role) { uint16_t freq_offset; if (iov->iov_len < 3) { DBG("Mode 0: too short (<3)"); return; } util_iov_pull_u8(iov, &mode_data->packet_quality); util_iov_pull_u8(iov, &mode_data->packet_rssi_dbm); util_iov_pull_u8(iov, &mode_data->packet_ant); DBG("CS Step mode 0"); if (cs_role == CS_INITIATOR && iov->iov_len >= 4) { util_iov_pull_le16(iov, &freq_offset); mode_data->init_measured_freq_offset = freq_offset; } } /* Parse CS Mode 1 step data */ static void parse_mode_one_data(struct iovec *iov, struct cs_mode_one_data *mode_data, uint8_t cs_role, uint8_t cs_rtt_type) { uint16_t time_val; if (iov->iov_len < 4) { DBG("Mode 1: too short (<4)"); return; } DBG("CS Step mode 1"); /* Parse fixed fields in specification order */ if (!util_iov_pull_u8(iov, &mode_data->packet_quality) || !util_iov_pull_u8(iov, &mode_data->packet_nadm) || !util_iov_pull_u8(iov, &mode_data->packet_rssi_dbm) || !util_iov_pull_le16(iov, &time_val) || !util_iov_pull_u8(iov, &mode_data->packet_ant)) { DBG("Mode 1: failed to parse basic fields"); memset(mode_data, 0, sizeof(*mode_data)); return; } if (cs_role == CS_REFLECTOR) mode_data->tod_toa_refl = time_val; else mode_data->toa_tod_init = time_val; if ((cs_rtt_type == 0x01 || cs_rtt_type == 0x02) && iov->iov_len >= 6) { int16_t i_val, q_val; parse_i_q_sample(iov, &i_val, &q_val); mode_data->packet_pct1.i_sample = i_val; mode_data->packet_pct1.q_sample = q_val; parse_i_q_sample(iov, &i_val, &q_val); mode_data->packet_pct2.i_sample = i_val; mode_data->packet_pct2.q_sample = q_val; } } /* Parse CS Mode 2 step data */ static void parse_mode_two_data(struct iovec *iov, struct cs_mode_two_data *mode_data, uint8_t max_paths) { uint8_t k; if (iov->iov_len < 1) { DBG("Mode 2: too short (<1)"); return; } util_iov_pull_u8(iov, &mode_data->ant_perm_index); DBG("CS Step mode 2, max paths : %d", max_paths); for (k = 0; k < max_paths; k++) { int16_t i_val, q_val; if (iov->iov_len < 4) { DBG("Mode 2: insufficient PCT for path %u (rem=%zu)", k, iov->iov_len); break; } parse_i_q_sample(iov, &i_val, &q_val); mode_data->tone_pct[k].i_sample = i_val; mode_data->tone_pct[k].q_sample = q_val; util_iov_pull_u8(iov, &mode_data->tone_quality_indicator[k]); DBG("tone_quality_indicator : %d", mode_data->tone_quality_indicator[k]); DBG("[i, q] : %d, %d", mode_data->tone_pct[k].i_sample, mode_data->tone_pct[k].q_sample); } } /* Parse CS Mode 3 step data */ static void parse_mode_three_data(struct iovec *iov, struct cs_mode_three_data *mode_data, uint8_t cs_role, uint8_t cs_rtt_type, uint8_t max_paths) { uint8_t k; struct cs_mode_one_data *mode_one = &mode_data->mode_one_data; struct cs_mode_two_data *mode_two = &mode_data->mode_two_data; if (iov->iov_len < 4) { DBG("Mode 3: mode1 too short (<4)"); return; } DBG("CS Step mode 3"); /* Parse Mode 1 portion */ parse_mode_one_data(iov, mode_one, cs_role, cs_rtt_type); /* Parse Mode 2 portion */ if (iov->iov_len >= 1) { util_iov_pull_u8(iov, &mode_two->ant_perm_index); for (k = 0; k < max_paths; k++) { int16_t i_val, q_val; if (iov->iov_len < 4) break; parse_i_q_sample(iov, &i_val, &q_val); mode_two->tone_pct[k].i_sample = i_val; mode_two->tone_pct[k].q_sample = q_val; util_iov_pull_u8(iov, &mode_two->tone_quality_indicator[k]); } } } /* Parse a single CS step */ static void parse_cs_step(struct iovec *iov, struct cs_step_data *step, uint8_t cs_role, uint8_t cs_rtt_type, uint8_t max_paths) { uint8_t mode; uint8_t chnl; uint8_t length; /* Check if we have enough data for the 3-byte header */ if (iov->iov_len < 3) { DBG("Truncated header for step"); return; } /* Read mode, channel, and length (3-byte header) */ if (!util_iov_pull_u8(iov, &mode) || !util_iov_pull_u8(iov, &chnl) || !util_iov_pull_u8(iov, &length)) { DBG("Failed to read header for step"); return; } DBG("event->step_data_len : %d", length); step->step_mode = mode; step->step_chnl = chnl; step->step_data_length = length; DBG("Step: mode=%u chnl=%u data_len=%u", mode, chnl, length); if (iov->iov_len < length) { DBG("Truncated payload for step (need %u, have %zu)", length, iov->iov_len); return; } /* Parse step data based on mode */ switch (mode) { case CS_MODE_ZERO: parse_mode_zero_data(iov, &step->step_mode_data.mode_zero_data, cs_role); break; case CS_MODE_ONE: parse_mode_one_data(iov, &step->step_mode_data.mode_one_data, cs_role, cs_rtt_type); break; case CS_MODE_TWO: parse_mode_two_data(iov, &step->step_mode_data.mode_two_data, max_paths); break; case CS_MODE_THREE: parse_mode_three_data(iov, &step->step_mode_data.mode_three_data, cs_role, cs_rtt_type, max_paths); break; default: DBG("Unknown step mode %d", mode); /* Skip the entire step data */ util_iov_pull(iov, length); break; } } /* * Handle the common step-parsing tail shared by both subevent result variants. * Fixes truncation (num_steps_reported > CS_MAX_STEPS) by zeroing the step * count and trimming send_len to header_size, matching the abort-status path. */ static void cs_parse_steps(struct iovec *iov, uint8_t num_steps_reported, uint8_t proc_done_status, uint8_t subevt_done_status, uint8_t abort_reason, uint8_t cs_role, uint8_t cs_rtt_type, uint8_t max_paths, struct cs_step_data *step_data, uint8_t *num_steps_out, size_t *send_len, size_t header_size) { uint8_t steps = MIN(num_steps_reported, CS_MAX_STEPS); uint8_t i; if (num_steps_reported > CS_MAX_STEPS) { DBG("Too many steps reported: %u (max %u)", num_steps_reported, CS_MAX_STEPS); *num_steps_out = 0; *send_len = header_size; return; } if (subevt_done_status == 0xF || proc_done_status == 0xF) { DBG("CS Procedure/Subevent aborted: "); DBG("sub evt status = %d, proc status = %d, reason = %d", subevt_done_status, proc_done_status, abort_reason); /* * Step bytes were never parsed; zero-initialised step_data[] * entries would appear as spurious mode-0 quality=0 steps to * the BCS algorithm. Clear the count so an aborted subevent * carries no fake measurements. */ *num_steps_out = 0; *send_len = header_size; return; } for (i = 0; i < steps; i++) parse_cs_step(iov, &step_data[i], cs_role, cs_rtt_type, max_paths); } static void rap_cs_subevt_result_evt(const void *data, uint8_t size, void *user_data) { struct cs_state_machine *sm = (struct cs_state_machine *) user_data; struct rap_ev_cs_subevent_result *rap_ev; struct iovec iov; uint8_t cs_role; uint8_t cs_rtt_type; uint8_t max_paths; uint8_t steps; size_t send_len = 0; uint16_t handle; uint8_t config_id; uint16_t start_acl_conn_evt_counter; uint16_t proc_counter; uint16_t freq_comp; uint8_t ref_pwr_lvl; uint8_t proc_done_status; uint8_t subevt_done_status; uint8_t abort_reason; uint8_t num_ant_paths; uint8_t num_steps_reported; if (!sm || !data || size < sizeof(struct bt_hci_evt_le_cs_subevent_result)) return; /* Initialize iovec with the event data */ iov.iov_base = (void *) data; iov.iov_len = size; /* Check if Procedure is active or not */ if (!sm->procedure_active) { DBG("Received Subevent event when Procedure is inactive!"); return; } /* Parse header fields using iovec */ if (!util_iov_pull_le16(&iov, &handle)) { error("Failed to parse Connection_Handle"); return; } if (!util_iov_pull_u8(&iov, &config_id) || !util_iov_pull_le16(&iov, &start_acl_conn_evt_counter) || !util_iov_pull_le16(&iov, &proc_counter) || !util_iov_pull_le16(&iov, &freq_comp) || !util_iov_pull_u8(&iov, &ref_pwr_lvl) || !util_iov_pull_u8(&iov, &proc_done_status) || !util_iov_pull_u8(&iov, &subevt_done_status) || !util_iov_pull_u8(&iov, &abort_reason) || !util_iov_pull_u8(&iov, &num_ant_paths) || !util_iov_pull_u8(&iov, &num_steps_reported)) { error("Failed to parse subevent fields"); return; } cs_role = sm->cs_opt.role; cs_rtt_type = sm->cs_opt.rtt_type; max_paths = MIN((num_ant_paths + 1), CS_MAX_ANT_PATHS); steps = MIN(num_steps_reported, CS_MAX_STEPS); send_len = offsetof(struct rap_ev_cs_subevent_result, step_data) + steps * sizeof(struct cs_step_data); rap_ev = malloc0(send_len); if (!rap_ev) { error("Failed to allocate memory for subevent result\n"); return; } DBG("length=%u", size); rap_ev->conn_hdl = le16_to_cpu(handle); rap_ev->config_id = config_id; rap_ev->start_acl_conn_evt_counter = start_acl_conn_evt_counter; rap_ev->proc_counter = proc_counter; rap_ev->freq_comp = freq_comp; rap_ev->ref_pwr_lvl = ref_pwr_lvl; rap_ev->proc_done_status = proc_done_status; rap_ev->subevt_done_status = subevt_done_status; rap_ev->abort_reason = abort_reason; rap_ev->num_ant_paths = num_ant_paths; rap_ev->num_steps_reported = steps; cs_parse_steps(&iov, num_steps_reported, proc_done_status, subevt_done_status, abort_reason, cs_role, cs_rtt_type, max_paths, rap_ev->step_data, &rap_ev->num_steps_reported, &send_len, offsetof(struct rap_ev_cs_subevent_result, step_data)); DBG("CS subevent result processed: %zu bytes, ", send_len); bt_rap_hci_cs_subevent_result_callback(send_len, rap_ev, sm->rap); free(rap_ev); } static void rap_cs_subevt_result_cont_evt(const void *data, uint8_t size, void *user_data) { struct cs_state_machine *sm = (struct cs_state_machine *) user_data; struct rap_ev_cs_subevent_result_cont *rap_ev; struct iovec iov; uint8_t cs_role; uint8_t cs_rtt_type; uint8_t max_paths; uint8_t steps; size_t send_len = 0; uint16_t handle; uint8_t config_id; uint8_t proc_done_status; uint8_t subevt_done_status; uint8_t abort_reason; uint8_t num_ant_paths; uint8_t num_steps_reported; if (!sm || !data || size < sizeof(struct bt_hci_evt_le_cs_subevent_result_continue)) return; /* Initialize iovec with the event data */ iov.iov_base = (void *) data; iov.iov_len = size; /* Check if Procedure is active or not */ if (!sm->procedure_active) { error("Received Subevent when CS Procedure is inactive!"); return; } /* Parse header fields using iovec */ if (!util_iov_pull_le16(&iov, &handle)) { error("Failed to parse Connection_Handle"); return; } if (!util_iov_pull_u8(&iov, &config_id) || !util_iov_pull_u8(&iov, &proc_done_status) || !util_iov_pull_u8(&iov, &subevt_done_status) || !util_iov_pull_u8(&iov, &abort_reason) || !util_iov_pull_u8(&iov, &num_ant_paths) || !util_iov_pull_u8(&iov, &num_steps_reported)) { error("Failed to parse subevent continue fields "); return; } cs_role = sm->cs_opt.role; cs_rtt_type = sm->cs_opt.rtt_type; max_paths = MIN((num_ant_paths + 1), CS_MAX_ANT_PATHS); steps = MIN(num_steps_reported, CS_MAX_STEPS); send_len = offsetof(struct rap_ev_cs_subevent_result_cont, step_data) + steps * sizeof(struct cs_step_data); rap_ev = malloc0(send_len); if (!rap_ev) { error("Failed to allocate memory for subevent result\n"); return; } DBG("length=%u", size); rap_ev->conn_hdl = le16_to_cpu(handle); rap_ev->config_id = config_id; rap_ev->proc_done_status = proc_done_status; rap_ev->subevt_done_status = subevt_done_status; rap_ev->abort_reason = abort_reason; rap_ev->num_ant_paths = num_ant_paths; rap_ev->num_steps_reported = steps; cs_parse_steps(&iov, num_steps_reported, proc_done_status, subevt_done_status, abort_reason, cs_role, cs_rtt_type, max_paths, rap_ev->step_data, &rap_ev->num_steps_reported, &send_len, offsetof(struct rap_ev_cs_subevent_result_cont, step_data)); DBG("CS subevent result cont processed: %zu bytes, ", send_len); bt_rap_hci_cs_subevent_result_cont_callback(send_len, rap_ev, sm->rap); free(rap_ev); } /* Subevent handler function type */ /* Set Ch Classif cmd handling to be added after DBus support enabled */ /* This cmd is used by host to start cs distance measurement procedure * function will be used when user start distance measurement * keeping it unused till DBUS API is added */ static bool bt_rap_read_local_supported_capabilities( void *hci_sm) { struct cs_state_machine *sm = hci_sm; unsigned int status; if (!sm || !sm->hci) { error("Invalid state machine or HCI"); return false; } DBG("Sending Read Local Supported Capabilities command"); status = bt_hci_send(sm->hci, BT_HCI_CMD_LE_CS_RD_LOC_SUPP_CAP, NULL, 0, rap_rd_loc_supp_cap_done_cb, sm, NULL); if (!status) { error("Failed to send Read Local Supported Capabilities"); return false; } DBG("Read Local Supported Capabilities command sent successfully"); return true; } static void unregister_event_id(void *data, void *user_data) { struct bt_hci *hci = user_data; bt_hci_unregister_subevent(hci, PTR_TO_UINT(data)); } void *bt_rap_attach_hci(struct bt_rap *rap, struct bt_hci *hci, uint8_t role, uint8_t cs_sync_ant_sel, int8_t max_tx_power) { struct cs_state_machine *sm; unsigned int id; if (!rap || !hci) { error("rap or hci null"); return NULL; } /* Allocate per-instance state machine */ sm = new0(struct cs_state_machine, 1); if (!sm) { error("Failed to allocate state machine"); return NULL; } /* Initialize state machine with provided CS options */ cs_state_machine_init(sm, rap, hci, role, cs_sync_ant_sel, max_tx_power); sm->event_ids = queue_new(); if (!sm->event_ids) { error("Failed to allocate event_ids queue"); free(sm); return NULL; } /* Register each LE Meta subevent individually */ id = bt_hci_register_subevent(hci, BT_HCI_EVT_LE_CS_RD_REM_SUPP_CAP_COMPLETE, rap_rd_rmt_supp_cap_cmplt_evt, sm, NULL); if (!id) goto fail; queue_push_tail(sm->event_ids, UINT_TO_PTR(id)); id = bt_hci_register_subevent(hci, BT_HCI_EVT_LE_CS_RD_REM_FAE_COMPLETE, rap_rd_rem_fae_cmplt_evt, sm, NULL); if (!id) goto fail; queue_push_tail(sm->event_ids, UINT_TO_PTR(id)); id = bt_hci_register_subevent(hci, BT_HCI_EVT_LE_CS_CONFIG_COMPLETE, rap_cs_config_cmplt_evt, sm, NULL); if (!id) goto fail; queue_push_tail(sm->event_ids, UINT_TO_PTR(id)); id = bt_hci_register_subevent(hci, BT_HCI_EVT_LE_CS_SEC_ENABLE_COMPLETE, rap_cs_sec_enable_cmplt_evt, sm, NULL); if (!id) goto fail; queue_push_tail(sm->event_ids, UINT_TO_PTR(id)); id = bt_hci_register_subevent(hci, BT_HCI_EVT_LE_CS_PROC_ENABLE_COMPLETE, rap_cs_proc_enable_cmplt_evt, sm, NULL); if (!id) goto fail; queue_push_tail(sm->event_ids, UINT_TO_PTR(id)); id = bt_hci_register_subevent(hci, BT_HCI_EVT_LE_CS_SUBEVENT_RESULT, rap_cs_subevt_result_evt, sm, NULL); if (!id) goto fail; queue_push_tail(sm->event_ids, UINT_TO_PTR(id)); id = bt_hci_register_subevent(hci, BT_HCI_EVT_LE_CS_SUBEVENT_RESULT_CONTINUE, rap_cs_subevt_result_cont_evt, sm, NULL); if (!id) goto fail; queue_push_tail(sm->event_ids, UINT_TO_PTR(id)); DBG("CS options: role=%u, cs_sync_ant_sel=%u, max_tx_power=%d", role, cs_sync_ant_sel, max_tx_power); /* place holder, need DBus API to be called */ bt_rap_read_local_supported_capabilities(sm); return sm; fail: error("Failed to register hci le meta subevents"); queue_foreach(sm->event_ids, unregister_event_id, hci); queue_destroy(sm->event_ids, NULL); queue_destroy(sm->conn_mappings, mapping_free); free(sm); return NULL; } bool bt_rap_set_conn_hndl(void *hci_sm, struct bt_rap *rap, uint16_t handle, const uint8_t *bdaddr, uint8_t bdaddr_type, bool is_central) { struct cs_state_machine *sm = hci_sm; struct bt_att *att; if (!sm || !rap) return false; att = bt_rap_get_att(rap); if (!att) return false; DBG("Setting connection mapping: handle=0x%04X, ", handle); if (bdaddr) { DBG("bdaddr=%02x:%02x:%02x:%02x:%02x:%02x type=%u", bdaddr[5], bdaddr[4], bdaddr[3], bdaddr[2], bdaddr[1], bdaddr[0], bdaddr_type); } return add_conn_mapping(sm, handle, bdaddr, bdaddr_type, is_central, att, rap); } void bt_rap_clear_conn_handle(void *hci_sm, uint16_t handle) { struct cs_state_machine *sm = hci_sm; if (!sm) return; DBG("Clearing connection mapping: handle=0x%04X", handle); remove_conn_mapping(sm, handle); } void bt_rap_detach_hci(struct bt_rap *rap, void *hci_sm) { struct cs_state_machine *sm = hci_sm; if (!rap) return; DBG("Detaching RAP from HCI, cleaning up mappings"); /* Cleanup the per-instance state machine */ if (sm) { /* Unregister HCI events */ if (sm->hci) queue_foreach(sm->event_ids, unregister_event_id, sm->hci); queue_destroy(sm->event_ids, NULL); /* Clean up per-instance connection mappings. * Each state machine owns its own conn_mappings queue; * this destroys all mappings for this instance only. */ queue_destroy(sm->conn_mappings, mapping_free); /* Free the state machine */ free(sm); } } bluez-5.87/profiles/PaxHeaders/deviceinfo0000644000000000000000000000005015221755531015542 xustar0020 atime=1783094113 20 ctime=1783094105 bluez-5.87/profiles/deviceinfo/0000755000000000000000000000000015221755531015300 5ustar00rootrootbluez-5.87/profiles/deviceinfo/PaxHeaders/deviceinfo.c0000644000000000000000000000005015142177620020075 xustar0020 atime=1783093685 20 ctime=1783094105 bluez-5.87/profiles/deviceinfo/deviceinfo.c0000644000000000000000000000726315142177620017566 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012 Texas Instruments, Inc. * Copyright (C) 2015 Google Inc. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "attrib/gattrib.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "attrib/att.h" #include "src/log.h" #define PNP_ID_SIZE 7 static void read_pnpid_cb(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct btd_device *device = user_data; if (!success) { error("Error reading PNP_ID value: %s", att_ecode2str(att_ecode)); return; } if (length < PNP_ID_SIZE) { error("Error reading PNP_ID: Invalid pdu length received"); return; } btd_device_set_pnpid(device, value[0], get_le16(&value[1]), get_le16(&value[3]), get_le16(&value[5])); } static void handle_pnpid(struct btd_device *device, uint16_t value_handle) { struct bt_gatt_client *client = btd_device_get_gatt_client(device); if (!bt_gatt_client_read_value(client, value_handle, read_pnpid_cb, device, NULL)) DBG("Failed to send request to read pnpid"); } static void handle_characteristic(struct gatt_db_attribute *attr, void *user_data) { struct btd_device *device = user_data; uint16_t value_handle; bt_uuid_t uuid, pnpid_uuid; bt_string_to_uuid(&pnpid_uuid, PNPID_UUID); if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) { error("Failed to obtain characteristic data"); return; } if (bt_uuid_cmp(&pnpid_uuid, &uuid) == 0) handle_pnpid(device, value_handle); else { char uuid_str[MAX_LEN_UUID_STR]; bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); DBG("Unsupported characteristic: %s", uuid_str); } } static void foreach_deviceinfo_service(struct gatt_db_attribute *attr, void *user_data) { struct btd_device *device = user_data; gatt_db_service_foreach_char(attr, handle_characteristic, device); } static int deviceinfo_probe(struct btd_service *service) { return 0; } static void deviceinfo_remove(struct btd_service *service) { } static int deviceinfo_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct gatt_db *db = btd_device_get_gatt_db(device); char addr[18]; bt_uuid_t deviceinfo_uuid; ba2str(device_get_address(device), addr); DBG("deviceinfo profile accept (%s)", addr); /* Handle the device info service */ bt_string_to_uuid(&deviceinfo_uuid, DEVICE_INFORMATION_UUID); gatt_db_foreach_service(db, &deviceinfo_uuid, foreach_deviceinfo_service, device); btd_service_connecting_complete(service, 0); return 0; } static int deviceinfo_disconnect(struct btd_service *service) { btd_service_disconnecting_complete(service, 0); return 0; } static struct btd_profile deviceinfo_profile = { .name = "deviceinfo", .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = DEVICE_INFORMATION_UUID, .device_probe = deviceinfo_probe, .device_remove = deviceinfo_remove, .accept = deviceinfo_accept, .disconnect = deviceinfo_disconnect, }; static int deviceinfo_init(void) { return btd_profile_register(&deviceinfo_profile); } static void deviceinfo_exit(void) { btd_profile_unregister(&deviceinfo_profile); } BLUETOOTH_PLUGIN_DEFINE(deviceinfo, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, deviceinfo_init, deviceinfo_exit) bluez-5.87/profiles/deviceinfo/PaxHeaders/dis.h0000644000000000000000000000005014015011623016532 xustar0020 atime=1783093675 20 ctime=1783094105 bluez-5.87/profiles/deviceinfo/dis.h0000644000000000000000000000127114015011623016214 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * */ struct bt_dis; struct bt_dis *bt_dis_new(struct gatt_db *db); struct bt_dis *bt_dis_new_primary(void *primary); struct bt_dis *bt_dis_ref(struct bt_dis *dis); void bt_dis_unref(struct bt_dis *dis); bool bt_dis_attach(struct bt_dis *dis, void *gatt); void bt_dis_detach(struct bt_dis *dis); typedef void (*bt_dis_notify) (uint8_t source, uint16_t vendor, uint16_t product, uint16_t version, void *user_data); bool bt_dis_set_notification(struct bt_dis *dis, bt_dis_notify func, void *user_data); bluez-5.87/profiles/deviceinfo/PaxHeaders/dis.c0000644000000000000000000000005015063045254016540 xustar0020 atime=1783093679 20 ctime=1783094105 bluez-5.87/profiles/deviceinfo/dis.c0000644000000000000000000001540315063045254016224 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012 Texas Instruments, Inc. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "src/log.h" #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "attrib/gattrib.h" #include "attrib/att.h" #include "attrib/gatt.h" #include "profiles/deviceinfo/dis.h" #define DIS_UUID16 0x180a #define PNP_ID_SIZE 7 struct bt_dis { int ref_count; uint16_t handle; uint8_t source; uint16_t vendor; uint16_t product; uint16_t version; GAttrib *attrib; /* GATT connection */ struct gatt_primary *primary; /* Primary details */ bt_dis_notify notify; void *notify_data; struct queue *gatt_op; }; struct characteristic { struct gatt_char attr; /* Characteristic */ struct bt_dis *d; /* deviceinfo where the char belongs */ }; struct gatt_request { unsigned int id; struct bt_dis *dis; void *user_data; }; static void destroy_gatt_req(struct gatt_request *req) { queue_remove(req->dis->gatt_op, req); bt_dis_unref(req->dis); free(req); } static void dis_free(struct bt_dis *dis) { bt_dis_detach(dis); free(dis->primary); queue_destroy(dis->gatt_op, (void *) destroy_gatt_req); g_free(dis); } static void foreach_dis_char(struct gatt_db_attribute *attr, void *user_data) { struct bt_dis *dis = user_data; bt_uuid_t pnpid_uuid, uuid; uint16_t value_handle; /* Ignore if there are multiple instances */ if (dis->handle) return; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) return; /* Find PNPID characteristic's value handle */ bt_string_to_uuid(&pnpid_uuid, PNPID_UUID); if (bt_uuid_cmp(&pnpid_uuid, &uuid) == 0) dis->handle = value_handle; } static void foreach_dis_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_dis *dis = user_data; /* Ignore if there are multiple instances */ if (dis->handle) return; gatt_db_service_foreach_char(attr, foreach_dis_char, dis); } struct bt_dis *bt_dis_new(struct gatt_db *db) { struct bt_dis *dis; dis = g_try_new0(struct bt_dis, 1); if (!dis) return NULL; dis->gatt_op = queue_new(); if (db) { bt_uuid_t uuid; /* Handle the DIS service */ bt_uuid16_create(&uuid, DIS_UUID16); gatt_db_foreach_service(db, &uuid, foreach_dis_service, dis); if (!dis->handle) { dis_free(dis); return NULL; } } return bt_dis_ref(dis); } struct bt_dis *bt_dis_new_primary(void *primary) { struct bt_dis *dis; dis = g_try_new0(struct bt_dis, 1); if (!dis) return NULL; dis->gatt_op = queue_new(); if (primary) dis->primary = util_memdup(primary, sizeof(*dis->primary)); return bt_dis_ref(dis); } struct bt_dis *bt_dis_ref(struct bt_dis *dis) { if (!dis) return NULL; __sync_fetch_and_add(&dis->ref_count, 1); return dis; } void bt_dis_unref(struct bt_dis *dis) { if (!dis) return; if (__sync_sub_and_fetch(&dis->ref_count, 1)) return; dis_free(dis); } static struct gatt_request *create_request(struct bt_dis *dis, void *user_data) { struct gatt_request *req; req = new0(struct gatt_request, 1); req->user_data = user_data; req->dis = bt_dis_ref(dis); return req; } static bool set_and_store_gatt_req(struct bt_dis *dis, struct gatt_request *req, unsigned int id) { req->id = id; return queue_push_head(dis->gatt_op, req); } static void read_pnpid_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data) { struct gatt_request *req = user_data; struct bt_dis *dis = req->user_data; uint8_t value[PNP_ID_SIZE]; ssize_t vlen; destroy_gatt_req(req); if (status != 0) { error("Error reading PNP_ID value: %s", att_ecode2str(status)); return; } vlen = dec_read_resp(pdu, len, value, sizeof(value)); if (vlen < 0) { error("Error reading PNP_ID: Protocol error"); return; } if (vlen < 7) { error("Error reading PNP_ID: Invalid pdu length received"); return; } dis->source = value[0]; dis->vendor = get_le16(&value[1]); dis->product = get_le16(&value[3]); dis->version = get_le16(&value[5]); DBG("source: 0x%02X vendor: 0x%04X product: 0x%04X version: 0x%04X", dis->source, dis->vendor, dis->product, dis->version); if (dis->notify) dis->notify(dis->source, dis->vendor, dis->product, dis->version, dis->notify_data); } static void read_char(struct bt_dis *dis, GAttrib *attrib, uint16_t handle, GAttribResultFunc func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(dis, user_data); id = gatt_read_char(attrib, handle, func, req); if (set_and_store_gatt_req(dis, req, id)) return; error("dis: Could not read characteristic"); g_attrib_cancel(attrib, id); free(req); } static void discover_char(struct bt_dis *dis, GAttrib *attrib, uint16_t start, uint16_t end, bt_uuid_t *uuid, gatt_cb_t func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(dis, user_data); id = gatt_discover_char(attrib, start, end, uuid, func, req); if (set_and_store_gatt_req(dis, req, id)) return; error("dis: Could not send discover characteristic"); g_attrib_cancel(attrib, id); free(req); } static void configure_deviceinfo_cb(uint8_t status, GSList *characteristics, void *user_data) { struct gatt_request *req = user_data; struct bt_dis *d = req->user_data; GSList *l; destroy_gatt_req(req); if (status != 0) { error("Discover deviceinfo characteristics: %s", att_ecode2str(status)); return; } for (l = characteristics; l; l = l->next) { struct gatt_char *c = l->data; if (strcmp(c->uuid, PNPID_UUID) == 0) { d->handle = c->value_handle; read_char(d, d->attrib, d->handle, read_pnpid_cb, d); break; } } } bool bt_dis_attach(struct bt_dis *dis, void *attrib) { struct gatt_primary *primary = dis->primary; if (dis->attrib) return false; dis->attrib = g_attrib_ref(attrib); if (!dis->handle) discover_char(dis, dis->attrib, primary->range.start, primary->range.end, NULL, configure_deviceinfo_cb, dis); else read_char(dis, attrib, dis->handle, read_pnpid_cb, dis); return true; } static void cancel_gatt_req(struct gatt_request *req) { if (g_attrib_cancel(req->dis->attrib, req->id)) destroy_gatt_req(req); } void bt_dis_detach(struct bt_dis *dis) { if (!dis->attrib) return; queue_foreach(dis->gatt_op, (void *) cancel_gatt_req, NULL); g_attrib_unref(dis->attrib); dis->attrib = NULL; } bool bt_dis_set_notification(struct bt_dis *dis, bt_dis_notify func, void *user_data) { if (!dis) return false; dis->notify = func; dis->notify_data = user_data; return true; } bluez-5.87/profiles/PaxHeaders/network0000644000000000000000000000005015221755536015125 xustar0020 atime=1783094113 20 ctime=1783094110 bluez-5.87/profiles/network/0000755000000000000000000000000015221755536014663 5ustar00rootrootbluez-5.87/profiles/network/PaxHeaders/connection.c0000644000000000000000000000005015063045254017476 xustar0020 atime=1783093666 20 ctime=1783094105 bluez-5.87/profiles/network/connection.c0000644000000000000000000003130215063045254017156 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/bnep.h" #include "bluetooth/sdp.h" #include "gdbus/gdbus.h" #include "btio/btio.h" #include "src/log.h" #include "src/dbus-common.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/error.h" #include "bluetooth/uuid.h" #include "bnep.h" #include "connection.h" #define NETWORK_PEER_INTERFACE "org.bluez.Network1" #define BNEP_INTERFACE "bnep%d" typedef enum { CONNECTED, CONNECTING, DISCONNECTED } conn_state; struct network_peer { struct btd_device *device; GSList *connections; }; struct network_conn { struct btd_service *service; char dev[16]; /* Interface name */ uint16_t id; /* Role: Service Class Identifier */ conn_state state; GIOChannel *io; guint dc_id; struct network_peer *peer; DBusMessage *connect; struct bnep *session; }; static GSList *peers = NULL; static uint16_t get_pan_srv_id(const char *svc) { if (!strcasecmp(svc, "panu") || !strcasecmp(svc, PANU_UUID)) return BNEP_SVC_PANU; if (!strcasecmp(svc, "nap") || !strcasecmp(svc, NAP_UUID)) return BNEP_SVC_NAP; if (!strcasecmp(svc, "gn") || !strcasecmp(svc, GN_UUID)) return BNEP_SVC_GN; return 0; } static struct network_peer *find_peer(GSList *list, struct btd_device *device) { for (; list; list = list->next) { struct network_peer *peer = list->data; if (peer->device == device) return peer; } return NULL; } static struct network_conn *find_connection_by_state(GSList *list, conn_state state) { for (; list; list = list->next) { struct network_conn *nc = list->data; if (nc->state == state) return nc; } return NULL; } static void bnep_disconn_cb(gpointer data) { struct network_conn *nc = data; DBusConnection *conn = btd_get_dbus_connection(); const char *path = device_get_path(nc->peer->device); g_dbus_emit_property_changed(conn, path, NETWORK_PEER_INTERFACE, "Connected"); g_dbus_emit_property_changed(conn, path, NETWORK_PEER_INTERFACE, "Interface"); g_dbus_emit_property_changed(conn, path, NETWORK_PEER_INTERFACE, "UUID"); device_remove_disconnect_watch(nc->peer->device, nc->dc_id); nc->dc_id = 0; btd_service_disconnecting_complete(nc->service, 0); info("%s disconnected", nc->dev); nc->state = DISCONNECTED; memset(nc->dev, 0, sizeof(nc->dev)); strncpy(nc->dev, BNEP_INTERFACE, 16); nc->dev[15] = '\0'; bnep_free(nc->session); nc->session = NULL; } static void local_connect_cb(struct network_conn *nc, int err) { DBusConnection *conn = btd_get_dbus_connection(); const char *pdev = nc->dev; if (err < 0) { DBusMessage *reply = btd_error_failed(nc->connect, strerror(-err)); g_dbus_send_message(conn, reply); } else { g_dbus_send_reply(conn, nc->connect, DBUS_TYPE_STRING, &pdev, DBUS_TYPE_INVALID); } dbus_message_unref(nc->connect); nc->connect = NULL; } static void cancel_connection(struct network_conn *nc, int err) { btd_service_connecting_complete(nc->service, err); if (nc->connect) local_connect_cb(nc, err); if (nc->io) { g_io_channel_shutdown(nc->io, FALSE, NULL); g_io_channel_unref(nc->io); nc->io = NULL; } if (nc->state == CONNECTED) bnep_disconnect(nc->session); bnep_free(nc->session); nc->session = NULL; nc->state = DISCONNECTED; } static void connection_destroy(DBusConnection *conn, void *user_data) { struct network_conn *nc = user_data; cancel_connection(nc, -EIO); } static void disconnect_cb(struct btd_device *device, gboolean removal, void *user_data) { struct network_conn *nc = user_data; info("Network: disconnect %s", device_get_path(nc->peer->device)); connection_destroy(NULL, user_data); } static void bnep_conn_cb(char *iface, int err, void *data) { struct network_conn *nc = data; const char *path; DBusConnection *conn; DBG(""); if (err < 0) { error("connect failed %s", strerror(-err)); goto failed; } memcpy(nc->dev, iface, sizeof(nc->dev)); info("%s connected", nc->dev); btd_service_connecting_complete(nc->service, 0); if (nc->connect) local_connect_cb(nc, 0); conn = btd_get_dbus_connection(); path = device_get_path(nc->peer->device); g_dbus_emit_property_changed(conn, path, NETWORK_PEER_INTERFACE, "Connected"); g_dbus_emit_property_changed(conn, path, NETWORK_PEER_INTERFACE, "Interface"); g_dbus_emit_property_changed(conn, path, NETWORK_PEER_INTERFACE, "UUID"); nc->state = CONNECTED; nc->dc_id = device_add_disconnect_watch(nc->peer->device, disconnect_cb, nc, NULL); return; failed: cancel_connection(nc, -EIO); } static void connect_cb(GIOChannel *chan, GError *err, gpointer data) { struct network_conn *nc = data; int sk, perr; if (err) { error("%s", err->message); goto failed; } sk = g_io_channel_unix_get_fd(nc->io); nc->session = bnep_new(sk, BNEP_SVC_PANU, nc->id, BNEP_INTERFACE); if (!nc->session) goto failed; perr = bnep_connect(nc->session, bnep_conn_cb, bnep_disconn_cb, nc, nc); if (perr < 0) { error("bnep connect(): %s (%d)", strerror(-perr), -perr); goto failed; } if (nc->io) { g_io_channel_unref(nc->io); nc->io = NULL; } return; failed: cancel_connection(nc, -EIO); } static DBusMessage *local_connect(DBusConnection *conn, DBusMessage *msg, void *data) { struct network_peer *peer = data; struct btd_service *service; struct network_conn *nc; const char *svc; uint16_t id; int err; char uuid_str[MAX_LEN_UUID_STR]; bt_uuid_t uuid16, uuid128; if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &svc, DBUS_TYPE_INVALID) == FALSE) return btd_error_invalid_args(msg); id = get_pan_srv_id(svc); bt_uuid16_create(&uuid16, id); bt_uuid_to_uuid128(&uuid16, &uuid128); if (bt_uuid_to_string(&uuid128, uuid_str, MAX_LEN_UUID_STR) < 0) return btd_error_invalid_args(msg); service = btd_device_get_service(peer->device, uuid_str); if (service == NULL) return btd_error_not_supported(msg); nc = btd_service_get_user_data(service); if (nc->connect != NULL) return btd_error_busy(msg); err = connection_connect(nc->service); if (err < 0) return btd_error_failed(msg, strerror(-err)); nc->connect = dbus_message_ref(msg); return NULL; } /* Connect and initiate BNEP session */ int connection_connect(struct btd_service *svc) { struct network_conn *nc = btd_service_get_user_data(svc); struct network_peer *peer = nc->peer; uint16_t id = get_pan_srv_id(btd_service_get_profile(svc)->remote_uuid); GError *err = NULL; const bdaddr_t *src; const bdaddr_t *dst; DBG("id %u", id); if (nc->state != DISCONNECTED) return -EALREADY; src = btd_adapter_get_address(device_get_adapter(peer->device)); dst = device_get_address(peer->device); nc->io = bt_io_connect(connect_cb, nc, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_DEST_BDADDR, dst, BT_IO_OPT_PSM, BNEP_PSM, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_OMTU, BNEP_MTU, BT_IO_OPT_IMTU, BNEP_MTU, BT_IO_OPT_INVALID); if (!nc->io) return -EIO; nc->state = CONNECTING; return 0; } int connection_disconnect(struct btd_service *svc) { struct network_conn *nc = btd_service_get_user_data(svc); if (nc->state == DISCONNECTED) return 0; connection_destroy(NULL, nc); return 0; } static DBusMessage *local_disconnect(DBusConnection *conn, DBusMessage *msg, void *data) { struct network_peer *peer = data; GSList *l; for (l = peer->connections; l; l = l->next) { struct network_conn *nc = l->data; int err; if (nc->state == DISCONNECTED) continue; err = connection_disconnect(nc->service); if (err < 0) return btd_error_failed(msg, strerror(-err)); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } return btd_error_not_connected(msg); } static gboolean network_property_get_connected(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct network_peer *peer = data; struct network_conn *nc; dbus_bool_t connected; nc = find_connection_by_state(peer->connections, CONNECTED); connected = nc != NULL ? TRUE : FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &connected); return TRUE; } static gboolean network_property_exists(const GDBusPropertyTable *property, void *data) { struct network_peer *peer = data; struct network_conn *nc; nc = find_connection_by_state(peer->connections, CONNECTED); if (nc == NULL) return FALSE; return TRUE; } static gboolean network_property_get_interface(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct network_peer *peer = data; struct network_conn *nc; const char *iface; nc = find_connection_by_state(peer->connections, CONNECTED); if (nc == NULL) return FALSE; iface = nc->dev; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &iface); return TRUE; } static gboolean network_property_get_uuid(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct network_peer *peer = data; struct network_conn *nc; char uuid_str[MAX_LEN_UUID_STR]; const char *uuid = uuid_str; bt_uuid_t uuid16, uuid128; nc = find_connection_by_state(peer->connections, CONNECTED); if (nc == NULL) return FALSE; bt_uuid16_create(&uuid16, nc->id); bt_uuid_to_uuid128(&uuid16, &uuid128); bt_uuid_to_string(&uuid128, uuid_str, MAX_LEN_UUID_STR); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); return TRUE; } static void connection_free(void *data) { struct network_conn *nc = data; if (nc->dc_id) device_remove_disconnect_watch(nc->peer->device, nc->dc_id); connection_destroy(NULL, nc); if (nc->connect) dbus_message_unref(nc->connect); btd_service_unref(nc->service); g_free(nc); } static void peer_free(struct network_peer *peer) { g_slist_free_full(peer->connections, connection_free); btd_device_unref(peer->device); g_free(peer); } static void path_unregister(void *data) { struct network_peer *peer = data; DBG("Unregistered interface %s on path %s", NETWORK_PEER_INTERFACE, device_get_path(peer->device)); peers = g_slist_remove(peers, peer); peer_free(peer); } static const GDBusMethodTable connection_methods[] = { { GDBUS_ASYNC_METHOD("Connect", GDBUS_ARGS({"uuid", "s"}), GDBUS_ARGS({"interface", "s"}), local_connect) }, { GDBUS_METHOD("Disconnect", NULL, NULL, local_disconnect) }, { } }; static const GDBusPropertyTable connection_properties[] = { { "Connected", "b", network_property_get_connected }, { "Interface", "s", network_property_get_interface, NULL, network_property_exists }, { "UUID", "s", network_property_get_uuid, NULL, network_property_exists }, { } }; void connection_unregister(struct btd_service *svc) { struct btd_device *device = btd_service_get_device(svc); struct network_conn *conn = btd_service_get_user_data(svc); struct network_peer *peer = conn->peer; uint16_t id = get_pan_srv_id(btd_service_get_profile(svc)->remote_uuid); DBG("%s id %u", device_get_path(device), id); peer->connections = g_slist_remove(peer->connections, conn); connection_free(conn); if (peer->connections != NULL) return; g_dbus_unregister_interface(btd_get_dbus_connection(), device_get_path(device), NETWORK_PEER_INTERFACE); } static struct network_peer *create_peer(struct btd_device *device) { struct network_peer *peer; const char *path; peer = g_new0(struct network_peer, 1); peer->device = btd_device_ref(device); path = device_get_path(device); if (g_dbus_register_interface(btd_get_dbus_connection(), path, NETWORK_PEER_INTERFACE, connection_methods, NULL, connection_properties, peer, path_unregister) == FALSE) { error("D-Bus failed to register %s interface", NETWORK_PEER_INTERFACE); peer_free(peer); return NULL; } DBG("Registered interface %s on path %s", NETWORK_PEER_INTERFACE, path); return peer; } int connection_register(struct btd_service *svc) { struct btd_device *device = btd_service_get_device(svc); struct network_peer *peer; struct network_conn *nc; uint16_t id = get_pan_srv_id(btd_service_get_profile(svc)->remote_uuid); DBG("%s id %u", device_get_path(device), id); peer = find_peer(peers, device); if (!peer) { peer = create_peer(device); if (!peer) return -1; peers = g_slist_append(peers, peer); } nc = g_new0(struct network_conn, 1); nc->id = id; nc->service = btd_service_ref(svc); nc->state = DISCONNECTED; nc->peer = peer; btd_service_set_user_data(svc, nc); DBG("id %u registered", id); peer->connections = g_slist_append(peer->connections, nc); return 0; } bluez-5.87/profiles/network/PaxHeaders/bnep.h0000644000000000000000000000005014766002272016272 xustar0020 atime=1783093325 20 ctime=1783094105 bluez-5.87/profiles/network/bnep.h0000644000000000000000000000162714766002272015761 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ struct bnep; int bnep_init(void); int bnep_cleanup(void); struct bnep *bnep_new(int sk, uint16_t local_role, uint16_t remote_role, char *iface); void bnep_free(struct bnep *session); typedef void (*bnep_connect_cb) (char *iface, int err, void *data); typedef void (*bnep_disconnect_cb) (void *data); int bnep_connect(struct bnep *session, bnep_connect_cb conn_cb, bnep_disconnect_cb disconn_cb, void *conn_data, void *disconn_data); void bnep_disconnect(struct bnep *session); int bnep_server_add(int sk, char *bridge, char *iface, const bdaddr_t *addr, uint8_t *setup_data, int len); void bnep_server_delete(char *bridge, char *iface, const bdaddr_t *addr); int bnep_send_unkown_rsp(int sk, uint16_t resp); bluez-5.87/profiles/network/PaxHeaders/server.c0000644000000000000000000000005015063045254016645 xustar0020 atime=1783093664 20 ctime=1783094105 bluez-5.87/profiles/network/server.c0000644000000000000000000004071115063045254016331 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/bnep.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "bluetooth/uuid.h" #include "gdbus/gdbus.h" #include "btio/btio.h" #include "src/dbus-common.h" #include "src/adapter.h" #include "src/log.h" #include "src/error.h" #include "src/sdpd.h" #include "src/shared/util.h" #include "bnep.h" #include "server.h" #define NETWORK_SERVER_INTERFACE "org.bluez.NetworkServer1" #define BNEP_INTERFACE "bnep%d" #define SETUP_TIMEOUT 1 /* Pending Authorization */ struct network_session { bdaddr_t dst; /* Remote Bluetooth Address */ char dev[16]; /* Interface name */ GIOChannel *io; /* Pending connect channel */ guint watch; /* BNEP socket watch */ }; struct network_adapter { struct btd_adapter *adapter; /* Adapter pointer */ GIOChannel *io; /* Bnep socket */ struct network_session *setup; /* Setup in progress */ GSList *servers; /* Server register to adapter */ }; /* Main server structure */ struct network_server { bdaddr_t src; /* Bluetooth Local Address */ char *name; /* Server service name */ char *bridge; /* Bridge name */ uint32_t record_id; /* Service record id */ uint16_t id; /* Service class identifier */ GSList *sessions; /* Active connections */ struct network_adapter *na; /* Adapter reference */ guint watch_id; /* Client service watch */ }; static GSList *adapters = NULL; static gboolean security = TRUE; static struct network_adapter *find_adapter(GSList *list, struct btd_adapter *adapter) { for (; list; list = list->next) { struct network_adapter *na = list->data; if (na->adapter == adapter) return na; } return NULL; } static struct network_server *find_server(GSList *list, uint16_t id) { for (; list; list = list->next) { struct network_server *ns = list->data; if (ns->id == id) return ns; } return NULL; } static struct network_server *find_server_by_uuid(GSList *list, const char *uuid) { bt_uuid_t srv_uuid, bnep_uuid; if (!bt_string_to_uuid(&srv_uuid, uuid)) { for (; list; list = list->next) { struct network_server *ns = list->data; bt_uuid16_create(&bnep_uuid, ns->id); /* UUID value compare */ if (!bt_uuid_cmp(&srv_uuid, &bnep_uuid)) return ns; } } else { for (; list; list = list->next) { struct network_server *ns = list->data; /* String value compare */ switch (ns->id) { case BNEP_SVC_PANU: if (!strcasecmp(uuid, "panu")) return ns; break; case BNEP_SVC_NAP: if (!strcasecmp(uuid, "nap")) return ns; break; case BNEP_SVC_GN: if (!strcasecmp(uuid, "gn")) return ns; break; } } } return NULL; } static sdp_record_t *server_record_new(const char *name, uint16_t id) { sdp_list_t *svclass, *pfseq, *apseq, *root, *aproto; uuid_t root_uuid, pan, l2cap, bnep; sdp_profile_desc_t profile[1]; sdp_list_t *proto[2]; sdp_data_t *v, *p; uint16_t psm = BNEP_PSM, version = 0x0100; uint16_t security_desc = (security ? 0x0001 : 0x0000); uint16_t net_access_type = 0xfffe; uint32_t max_net_access_rate = 0; const char *desc = "Network service"; sdp_record_t *record; record = sdp_record_alloc(); if (!record) return NULL; record->attrlist = NULL; record->pattern = NULL; switch (id) { case BNEP_SVC_NAP: sdp_uuid16_create(&pan, NAP_SVCLASS_ID); svclass = sdp_list_append(NULL, &pan); sdp_set_service_classes(record, svclass); sdp_uuid16_create(&profile[0].uuid, NAP_PROFILE_ID); profile[0].version = 0x0100; pfseq = sdp_list_append(NULL, &profile[0]); sdp_set_profile_descs(record, pfseq); sdp_set_info_attr(record, name, NULL, desc); sdp_attr_add_new(record, SDP_ATTR_NET_ACCESS_TYPE, SDP_UINT16, &net_access_type); sdp_attr_add_new(record, SDP_ATTR_MAX_NET_ACCESSRATE, SDP_UINT32, &max_net_access_rate); break; case BNEP_SVC_GN: sdp_uuid16_create(&pan, GN_SVCLASS_ID); svclass = sdp_list_append(NULL, &pan); sdp_set_service_classes(record, svclass); sdp_uuid16_create(&profile[0].uuid, GN_PROFILE_ID); profile[0].version = 0x0100; pfseq = sdp_list_append(NULL, &profile[0]); sdp_set_profile_descs(record, pfseq); sdp_set_info_attr(record, name, NULL, desc); break; case BNEP_SVC_PANU: sdp_uuid16_create(&pan, PANU_SVCLASS_ID); svclass = sdp_list_append(NULL, &pan); sdp_set_service_classes(record, svclass); sdp_uuid16_create(&profile[0].uuid, PANU_PROFILE_ID); profile[0].version = 0x0100; pfseq = sdp_list_append(NULL, &profile[0]); sdp_set_profile_descs(record, pfseq); sdp_set_info_attr(record, name, NULL, desc); break; default: sdp_record_free(record); return NULL; } sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(NULL, &root_uuid); sdp_set_browse_groups(record, root); sdp_uuid16_create(&l2cap, L2CAP_UUID); proto[0] = sdp_list_append(NULL, &l2cap); p = sdp_data_alloc(SDP_UINT16, &psm); proto[0] = sdp_list_append(proto[0], p); apseq = sdp_list_append(NULL, proto[0]); sdp_uuid16_create(&bnep, BNEP_UUID); proto[1] = sdp_list_append(NULL, &bnep); v = sdp_data_alloc(SDP_UINT16, &version); proto[1] = sdp_list_append(proto[1], v); /* Supported protocols */ { uint16_t ptype[] = { 0x0800, /* IPv4 */ 0x0806, /* ARP */ }; sdp_data_t *head, *pseq; int p; for (p = 0, head = NULL; p < 2; p++) { sdp_data_t *data = sdp_data_alloc(SDP_UINT16, &ptype[p]); if (head) sdp_seq_append(head, data); else head = data; } pseq = sdp_data_alloc(SDP_SEQ16, head); proto[1] = sdp_list_append(proto[1], pseq); } apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(NULL, apseq); sdp_set_access_protos(record, aproto); sdp_add_lang_attr(record); sdp_attr_add_new(record, SDP_ATTR_SECURITY_DESC, SDP_UINT16, &security_desc); sdp_data_free(p); sdp_data_free(v); sdp_list_free(apseq, NULL); sdp_list_free(root, NULL); sdp_list_free(aproto, NULL); sdp_list_free(proto[0], NULL); sdp_list_free(proto[1], NULL); sdp_list_free(svclass, NULL); sdp_list_free(pfseq, NULL); return record; } static void session_free(void *data) { struct network_session *session = data; if (session->watch) g_source_remove(session->watch); if (session->io) g_io_channel_unref(session->io); g_free(session); } static void setup_destroy(void *user_data) { struct network_adapter *na = user_data; struct network_session *setup = na->setup; if (!setup) return; na->setup = NULL; session_free(setup); } static gboolean bnep_setup(GIOChannel *chan, GIOCondition cond, gpointer user_data) { const uint8_t bt_base[] = { 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB }; struct network_adapter *na = user_data; struct network_server *ns; uint8_t packet[BNEP_MTU]; struct bnep_setup_conn_req *req = (void *) packet; uint16_t dst_role = 0; uint32_t val; int n, sk; char *bridge = NULL; if (cond & G_IO_NVAL) return FALSE; if (cond & (G_IO_ERR | G_IO_HUP)) { error("Hangup or error on BNEP socket"); return FALSE; } sk = g_io_channel_unix_get_fd(chan); /* * BNEP_SETUP_CONNECTION_REQUEST_MSG should be read and left in case * of kernel setup connection msg handling. */ n = recv(sk, packet, sizeof(packet), MSG_PEEK); if (n < 0) { error("read(): %s(%d)", strerror(errno), errno); return FALSE; } /* * Initial received data packet is BNEP_SETUP_CONNECTION_REQUEST_MSG * minimal size of this frame is 3 octets: 1 byte of BNEP Type + * 1 byte of BNEP Control Type + 1 byte of BNEP services UUID size. */ if (n < 3) { /* Added a response to the error control command * This packet reply to any control message received, * which contains an unknown BNEP control type value. */ if (req->type == BNEP_CONTROL) bnep_send_unkown_rsp(sk, req->ctrl); error("To few setup connection request data received"); return FALSE; } switch (req->uuid_size) { case 2: dst_role = get_be16(req->service); break; case 16: if (memcmp(&req->service[4], bt_base, sizeof(bt_base)) != 0) break; /* fall through */ case 4: val = get_be32(req->service); if (val > 0xffff) break; dst_role = val; break; default: break; } ns = find_server(na->servers, dst_role); if (!ns || !ns->record_id || !ns->bridge) error("Server error, bridge not initialized: (0x%x)", dst_role); else bridge = ns->bridge; strncpy(na->setup->dev, BNEP_INTERFACE, 16); na->setup->dev[15] = '\0'; if (bnep_server_add(sk, bridge, na->setup->dev, &na->setup->dst, packet, n) < 0) error("BNEP server cannot be added"); na->setup = NULL; return FALSE; } static void connect_event(GIOChannel *chan, GError *err, gpointer user_data) { struct network_adapter *na = user_data; if (err) { error("%s", err->message); setup_destroy(na); return; } g_io_channel_set_close_on_unref(chan, TRUE); na->setup->watch = g_io_add_watch_full(chan, G_PRIORITY_DEFAULT, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, bnep_setup, na, setup_destroy); } static void auth_cb(DBusError *derr, void *user_data) { struct network_adapter *na = user_data; GError *err = NULL; if (derr) { error("Access denied: %s", derr->message); goto reject; } if (!bt_io_accept(na->setup->io, connect_event, na, NULL, &err)) { error("bt_io_accept: %s", err->message); g_error_free(err); goto reject; } return; reject: g_io_channel_shutdown(na->setup->io, TRUE, NULL); setup_destroy(na); } static void confirm_event(GIOChannel *chan, gpointer user_data) { struct network_adapter *na = user_data; bdaddr_t src, dst; char address[18]; GError *err = NULL; guint ret; bt_io_get(chan, &err, BT_IO_OPT_SOURCE_BDADDR, &src, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_DEST, address, BT_IO_OPT_INVALID); if (err) { error("%s", err->message); g_error_free(err); goto drop; } DBG("BNEP: incoming connect from %s", address); if (na->setup) { error("Refusing connect from %s: setup in progress", address); goto drop; } if (!na->servers) goto drop; na->setup = g_new0(struct network_session, 1); bacpy(&na->setup->dst, &dst); na->setup->io = g_io_channel_ref(chan); ret = btd_request_authorization(&src, &dst, BNEP_SVC_UUID, auth_cb, na); if (ret == 0) { error("Refusing connect from %s", address); setup_destroy(na); goto drop; } return; drop: g_io_channel_shutdown(chan, TRUE, NULL); } int server_init(gboolean secure) { security = secure; return 0; } static uint32_t register_server_record(struct network_server *ns) { sdp_record_t *record; record = server_record_new(ns->name, ns->id); if (!record) { error("Unable to allocate new service record"); return 0; } if (adapter_service_add(ns->na->adapter, record) < 0) { error("Failed to register service record"); sdp_record_free(record); return 0; } DBG("got record id 0x%x", record->handle); return record->handle; } static void server_remove_sessions(struct network_server *ns) { GSList *list; for (list = ns->sessions; list; list = list->next) { struct network_session *session = list->data; if (*session->dev == '\0') continue; bnep_server_delete(ns->bridge, session->dev, &session->dst); } g_slist_free_full(ns->sessions, session_free); ns->sessions = NULL; } static void server_disconnect(DBusConnection *conn, void *user_data) { struct network_server *ns = user_data; server_remove_sessions(ns); ns->watch_id = 0; if (ns->record_id) { adapter_service_remove(ns->na->adapter, ns->record_id); ns->record_id = 0; } g_free(ns->bridge); ns->bridge = NULL; } static DBusMessage *register_server(DBusConnection *conn, DBusMessage *msg, void *data) { struct network_adapter *na = data; struct network_server *ns; DBusMessage *reply; const char *uuid, *bridge; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &uuid, DBUS_TYPE_STRING, &bridge, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); ns = find_server_by_uuid(na->servers, uuid); if (ns == NULL) return btd_error_failed(msg, "Invalid UUID"); if (ns->record_id) return btd_error_already_exists(msg); reply = dbus_message_new_method_return(msg); if (!reply) return NULL; ns->record_id = register_server_record(ns); if (!ns->record_id) return btd_error_failed(msg, "SDP record registration failed"); g_free(ns->bridge); ns->bridge = g_strdup(bridge); ns->watch_id = g_dbus_add_disconnect_watch(conn, dbus_message_get_sender(msg), server_disconnect, ns, NULL); return reply; } static DBusMessage *unregister_server(DBusConnection *conn, DBusMessage *msg, void *data) { struct network_adapter *na = data; struct network_server *ns; DBusMessage *reply; const char *uuid; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &uuid, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); ns = find_server_by_uuid(na->servers, uuid); if (!ns) return btd_error_failed(msg, "Invalid UUID"); reply = dbus_message_new_method_return(msg); if (!reply) return NULL; g_dbus_remove_watch(conn, ns->watch_id); server_disconnect(conn, ns); return reply; } static void adapter_free(struct network_adapter *na) { if (na->io != NULL) { g_io_channel_shutdown(na->io, TRUE, NULL); g_io_channel_unref(na->io); } setup_destroy(na); btd_adapter_unref(na->adapter); g_free(na); } static void server_free(void *data) { struct network_server *ns = data; if (!ns) return; server_remove_sessions(ns); if (ns->record_id) adapter_service_remove(ns->na->adapter, ns->record_id); g_dbus_remove_watch(btd_get_dbus_connection(), ns->watch_id); g_free(ns->name); g_free(ns->bridge); g_free(ns); } static void path_unregister(void *data) { struct network_adapter *na = data; DBG("Unregistered interface %s on path %s", NETWORK_SERVER_INTERFACE, adapter_get_path(na->adapter)); g_slist_free_full(na->servers, server_free); adapters = g_slist_remove(adapters, na); adapter_free(na); } static const GDBusMethodTable server_methods[] = { { GDBUS_METHOD("Register", GDBUS_ARGS({ "uuid", "s" }, { "bridge", "s" }), NULL, register_server) }, { GDBUS_METHOD("Unregister", GDBUS_ARGS({ "uuid", "s" }), NULL, unregister_server) }, { } }; static struct network_adapter *create_adapter(struct btd_adapter *adapter) { struct network_adapter *na; GError *err = NULL; na = g_new0(struct network_adapter, 1); na->adapter = btd_adapter_ref(adapter); na->io = bt_io_listen(NULL, confirm_event, na, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, btd_adapter_get_address(adapter), BT_IO_OPT_PSM, BNEP_PSM, BT_IO_OPT_OMTU, BNEP_MTU, BT_IO_OPT_IMTU, BNEP_MTU, BT_IO_OPT_SEC_LEVEL, security ? BT_IO_SEC_MEDIUM : BT_IO_SEC_LOW, BT_IO_OPT_INVALID); if (!na->io) { error("%s", err->message); g_error_free(err); adapter_free(na); return NULL; } return na; } int server_register(struct btd_adapter *adapter, uint16_t id) { struct network_adapter *na; struct network_server *ns; const char *path; na = find_adapter(adapters, adapter); if (!na) { na = create_adapter(adapter); if (!na) return -EINVAL; adapters = g_slist_append(adapters, na); } ns = find_server(na->servers, id); if (ns) return 0; ns = g_new0(struct network_server, 1); ns->name = g_strdup("Network service"); path = adapter_get_path(adapter); if (g_slist_length(na->servers) > 0) goto done; if (!g_dbus_register_interface(btd_get_dbus_connection(), path, NETWORK_SERVER_INTERFACE, server_methods, NULL, NULL, na, path_unregister)) { error("D-Bus failed to register %s interface", NETWORK_SERVER_INTERFACE); server_free(ns); return -1; } DBG("Registered interface %s on path %s", NETWORK_SERVER_INTERFACE, path); done: bacpy(&ns->src, btd_adapter_get_address(adapter)); ns->id = id; ns->na = na; ns->record_id = 0; na->servers = g_slist_append(na->servers, ns); return 0; } int server_unregister(struct btd_adapter *adapter, uint16_t id) { struct network_adapter *na; struct network_server *ns; na = find_adapter(adapters, adapter); if (!na) return -EINVAL; ns = find_server(na->servers, id); if (!ns) return -EINVAL; na->servers = g_slist_remove(na->servers, ns); server_free(ns); if (g_slist_length(na->servers) > 0) return 0; g_dbus_unregister_interface(btd_get_dbus_connection(), adapter_get_path(adapter), NETWORK_SERVER_INTERFACE); return 0; } bluez-5.87/profiles/network/PaxHeaders/connection.h0000644000000000000000000000005014015011623017470 xustar0020 atime=1783093662 20 ctime=1783094105 bluez-5.87/profiles/network/connection.h0000644000000000000000000000062214015011623017151 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ int connection_register(struct btd_service *service); void connection_unregister(struct btd_service *service); int connection_connect(struct btd_service *service); int connection_disconnect(struct btd_service *service); bluez-5.87/profiles/network/PaxHeaders/manager.c0000644000000000000000000000005015221752714016753 xustar0020 atime=1783092685 20 ctime=1783094105 bluez-5.87/profiles/network/manager.c0000644000000000000000000001060315221752714016434 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/bnep.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/log.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "bnep.h" #include "connection.h" #include "server.h" static gboolean conf_security = TRUE; static void read_config(const char *file) { GKeyFile *keyfile; GError *err = NULL; keyfile = g_key_file_new(); if (!g_key_file_load_from_file(keyfile, file, 0, &err)) { g_clear_error(&err); goto done; } conf_security = !g_key_file_get_boolean(keyfile, "General", "DisableSecurity", &err); if (err) { DBG("%s: %s", file, err->message); g_clear_error(&err); } done: g_key_file_free(keyfile); DBG("Config options: Security=%s", conf_security ? "true" : "false"); } static int panu_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { const char *path = adapter_get_path(adapter); DBG("path %s", path); return server_register(adapter, BNEP_SVC_PANU); } static void panu_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { const char *path = adapter_get_path(adapter); DBG("path %s", path); server_unregister(adapter, BNEP_SVC_PANU); } static int gn_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { const char *path = adapter_get_path(adapter); DBG("path %s", path); return server_register(adapter, BNEP_SVC_GN); } static void gn_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { const char *path = adapter_get_path(adapter); DBG("path %s", path); server_unregister(adapter, BNEP_SVC_GN); } static int nap_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { const char *path = adapter_get_path(adapter); DBG("path %s", path); return server_register(adapter, BNEP_SVC_NAP); } static void nap_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { const char *path = adapter_get_path(adapter); DBG("path %s", path); server_unregister(adapter, BNEP_SVC_NAP); } static struct btd_profile panu_profile = { .name = "network-panu", .bearer = BTD_PROFILE_BEARER_BREDR, .local_uuid = NAP_UUID, .remote_uuid = PANU_UUID, .device_probe = connection_register, .device_remove = connection_unregister, .connect = connection_connect, .disconnect = connection_disconnect, .adapter_probe = panu_server_probe, .adapter_remove = panu_server_remove, }; static struct btd_profile gn_profile = { .name = "network-gn", .bearer = BTD_PROFILE_BEARER_BREDR, .local_uuid = PANU_UUID, .remote_uuid = GN_UUID, .device_probe = connection_register, .device_remove = connection_unregister, .connect = connection_connect, .disconnect = connection_disconnect, .adapter_probe = gn_server_probe, .adapter_remove = gn_server_remove, }; static struct btd_profile nap_profile = { .name = "network-nap", .bearer = BTD_PROFILE_BEARER_BREDR, .local_uuid = PANU_UUID, .remote_uuid = NAP_UUID, .device_probe = connection_register, .device_remove = connection_unregister, .connect = connection_connect, .disconnect = connection_disconnect, .adapter_probe = nap_server_probe, .adapter_remove = nap_server_remove, }; static int network_init(void) { int err; read_config(CONFIGDIR "/network.conf"); err = bnep_init(); if (err) { if (err == -EPROTONOSUPPORT) err = -ENOSYS; return err; } /* * There is one socket to handle the incoming connections. NAP, * GN and PANU servers share the same PSM. The initial BNEP message * (setup connection request) contains the destination service * field that defines which service the source is connecting to. */ if (server_init(conf_security) < 0) return -1; err = btd_profile_register(&panu_profile); if (err) return err; btd_profile_register(&gn_profile); btd_profile_register(&nap_profile); return 0; } static void network_exit(void) { btd_profile_unregister(&panu_profile); btd_profile_unregister(&gn_profile); btd_profile_unregister(&nap_profile); bnep_cleanup(); } BLUETOOTH_PLUGIN_DEFINE(network, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, network_init, network_exit) bluez-5.87/profiles/network/PaxHeaders/server.h0000644000000000000000000000005014015011623016637 xustar0020 atime=1783093662 20 ctime=1783094105 bluez-5.87/profiles/network/server.h0000644000000000000000000000053014015011623016316 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ int server_init(gboolean secure); int server_register(struct btd_adapter *adapter, uint16_t id); int server_unregister(struct btd_adapter *adapter, uint16_t id); bluez-5.87/profiles/network/PaxHeaders/bnep.c0000644000000000000000000000005015063045254016263 xustar0020 atime=1783093326 20 ctime=1783094105 bluez-5.87/profiles/network/bnep.c0000644000000000000000000003524315063045254015753 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/l2cap.h" #include "bluetooth/bnep.h" #include "bluetooth/uuid.h" #include "src/log.h" #include "src/shared/timeout.h" #include "src/shared/util.h" #include "btio/btio.h" #include "bnep.h" #define CON_SETUP_RETRIES 3 #define CON_SETUP_TO 9 static int ctl; struct __service_16 { uint16_t dst; uint16_t src; } __attribute__ ((packed)); struct bnep { GIOChannel *io; uint16_t src; uint16_t dst; bdaddr_t dst_addr; char iface[16]; guint attempts; unsigned int setup_to; guint watch; bnep_connect_cb conn_cb; void *conn_data; bnep_disconnect_cb disconn_cb; void *disconn_data; }; int bnep_init(void) { ctl = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_BNEP); if (ctl < 0) { int err = -errno; if (err == -EPROTONOSUPPORT) warn("kernel lacks bnep-protocol support"); else error("bnep: Failed to open control socket: %s (%d)", strerror(-err), -err); return err; } return 0; } int bnep_cleanup(void) { close(ctl); return 0; } static int bnep_conndel(const bdaddr_t *dst) { struct bnep_conndel_req req; memset(&req, 0, sizeof(req)); baswap((bdaddr_t *)&req.dst, dst); req.flags = 0; if (ioctl(ctl, BNEPCONNDEL, &req) < 0) { int err = -errno; error("bnep: Failed to kill connection: %s (%d)", strerror(-err), -err); return err; } return 0; } static int bnep_connadd(int sk, uint16_t role, char *dev) { struct bnep_connadd_req req; memset(&req, 0, sizeof(req)); strncpy(req.device, dev, 15); req.device[15] = '\0'; req.sock = sk; req.role = role; req.flags = (1 << BNEP_SETUP_RESPONSE); if (ioctl(ctl, BNEPCONNADD, &req) < 0) { int err = -errno; error("bnep: Failed to add device %s: %s(%d)", dev, strerror(-err), -err); return err; } strncpy(dev, req.device, 16); return 0; } static uint32_t bnep_getsuppfeat(void) { uint32_t feat; if (ioctl(ctl, BNEPGETSUPPFEAT, &feat) < 0) feat = 0; DBG("supported features: 0x%x", feat); return feat; } static int bnep_if_up(const char *devname) { struct ifreq ifr; int sk, err = 0; sk = socket(AF_INET, SOCK_DGRAM, 0); memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, devname, IF_NAMESIZE - 1); ifr.ifr_flags |= IFF_UP; ifr.ifr_flags |= IFF_MULTICAST; if (ioctl(sk, SIOCSIFFLAGS, (void *) &ifr) < 0) { err = -errno; error("bnep: Could not bring up %s: %s(%d)", devname, strerror(-err), -err); } close(sk); return err; } static int bnep_if_down(const char *devname) { struct ifreq ifr; int sk, err = 0; sk = socket(AF_INET, SOCK_DGRAM, 0); memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, devname, IF_NAMESIZE - 1); ifr.ifr_flags &= ~IFF_UP; /* Bring down the interface */ if (ioctl(sk, SIOCSIFFLAGS, (void *) &ifr) < 0) { err = -errno; error("bnep: Could not bring down %s: %s(%d)", devname, strerror(-err), -err); } close(sk); return err; } static gboolean bnep_watchdog_cb(GIOChannel *chan, GIOCondition cond, gpointer data) { struct bnep *session = data; if (session->disconn_cb) session->disconn_cb(session->disconn_data); return FALSE; } static gboolean bnep_setup_cb(GIOChannel *chan, GIOCondition cond, gpointer data) { struct bnep *session = data; struct bnep_control_rsp *rsp; struct timeval timeo; char pkt[BNEP_MTU]; ssize_t r; int sk; if (cond & G_IO_NVAL) return FALSE; if (session->setup_to > 0) { timeout_remove(session->setup_to); session->setup_to = 0; } if (cond & (G_IO_HUP | G_IO_ERR)) { error("bnep: Hangup or error on l2cap server socket"); goto failed; } sk = g_io_channel_unix_get_fd(chan); memset(pkt, 0, BNEP_MTU); r = read(sk, pkt, sizeof(pkt) - 1); if (r < 0) { error("bnep: IO Channel read error"); goto failed; } if (r == 0) { error("bnep: No packet received on l2cap socket"); goto failed; } errno = EPROTO; if ((size_t) r < sizeof(*rsp)) { error("bnep: Packet received is not bnep type"); goto failed; } rsp = (void *) pkt; if (rsp->type != BNEP_CONTROL) { error("bnep: Packet received is not bnep type"); goto failed; } if (rsp->ctrl != BNEP_SETUP_CONN_RSP) return TRUE; r = ntohs(rsp->resp); if (r != BNEP_SUCCESS) { error("bnep: failed"); goto failed; } memset(&timeo, 0, sizeof(timeo)); timeo.tv_sec = 0; if (setsockopt(sk, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)) < 0) { error("bnep: Set setsockopt failed: %s", strerror(errno)); goto failed; }; sk = g_io_channel_unix_get_fd(session->io); if (bnep_connadd(sk, session->src, session->iface) < 0) goto failed; if (bnep_if_up(session->iface) < 0) { bnep_conndel(&session->dst_addr); goto failed; } session->watch = g_io_add_watch(session->io, G_IO_ERR | G_IO_HUP | G_IO_NVAL, (GIOFunc) bnep_watchdog_cb, session); g_io_channel_unref(session->io); session->io = NULL; session->conn_cb(session->iface, 0, session->conn_data); return FALSE; failed: session->conn_cb(NULL, -EIO, session->conn_data); return FALSE; } static int bnep_setup_conn_req(struct bnep *session) { struct bnep_setup_conn_req *req; struct __service_16 *s; unsigned char pkt[BNEP_MTU]; int fd; /* Send request */ req = (void *) pkt; req->type = BNEP_CONTROL; req->ctrl = BNEP_SETUP_CONN_REQ; req->uuid_size = 2; /* 16bit UUID */ s = (void *) req->service; s->src = htons(session->src); s->dst = htons(session->dst); fd = g_io_channel_unix_get_fd(session->io); if (write(fd, pkt, sizeof(*req) + sizeof(*s)) < 0) { error("bnep: connection req send failed: %s", strerror(errno)); return -errno; } session->attempts++; return 0; } static bool bnep_conn_req_to(gpointer user_data) { struct bnep *session = user_data; if (session->attempts == CON_SETUP_RETRIES) { error("bnep: Too many bnep connection attempts"); } else { error("bnep: connection setup TO, retrying..."); if (bnep_setup_conn_req(session) == 0) return TRUE; } session->conn_cb(NULL, -ETIMEDOUT, session->conn_data); return FALSE; } struct bnep *bnep_new(int sk, uint16_t local_role, uint16_t remote_role, char *iface) { struct bnep *session; int dup_fd; dup_fd = dup(sk); if (dup_fd < 0) return NULL; session = g_new0(struct bnep, 1); session->io = g_io_channel_unix_new(dup_fd); session->src = local_role; session->dst = remote_role; strncpy(session->iface, iface, 15); session->iface[15] = '\0'; g_io_channel_set_close_on_unref(session->io, TRUE); session->watch = g_io_add_watch(session->io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, (GIOFunc) bnep_setup_cb, session); return session; } void bnep_free(struct bnep *session) { if (!session) return; if (session->io) { g_io_channel_shutdown(session->io, FALSE, NULL); g_io_channel_unref(session->io); session->io = NULL; } if (session->watch > 0) { g_source_remove(session->watch); session->watch = 0; } g_free(session); } int bnep_connect(struct bnep *session, bnep_connect_cb conn_cb, bnep_disconnect_cb disconn_cb, void *conn_data, void *disconn_data) { GError *gerr = NULL; int err; if (!session || !conn_cb || !disconn_cb) return -EINVAL; session->attempts = 0; session->conn_cb = conn_cb; session->disconn_cb = disconn_cb; session->conn_data = conn_data; session->disconn_data = disconn_data; bt_io_get(session->io, &gerr, BT_IO_OPT_DEST_BDADDR, &session->dst_addr, BT_IO_OPT_INVALID); if (gerr) { error("bnep: connect failed: %s", gerr->message); g_error_free(gerr); return -EINVAL; } err = bnep_setup_conn_req(session); if (err < 0) return err; session->setup_to = timeout_add_seconds(CON_SETUP_TO, bnep_conn_req_to, session, NULL); return 0; } void bnep_disconnect(struct bnep *session) { if (!session) return; if (session->watch > 0) { g_source_remove(session->watch); session->watch = 0; } if (session->io) { g_io_channel_unref(session->io); session->io = NULL; } bnep_if_down(session->iface); bnep_conndel(&session->dst_addr); } static int bnep_add_to_bridge(const char *devname, const char *bridge) { int ifindex; struct ifreq ifr; int sk, err = 0; if (!devname || !bridge) return -EINVAL; ifindex = if_nametoindex(devname); sk = socket(AF_INET, SOCK_STREAM, 0); if (sk < 0) return -1; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, bridge, IFNAMSIZ - 1); ifr.ifr_ifindex = ifindex; if (ioctl(sk, SIOCBRADDIF, &ifr) < 0) { err = -errno; error("bnep: Can't add %s to the bridge %s: %s(%d)", devname, bridge, strerror(-err), -err); } else { info("bnep: bridge %s: interface %s added", bridge, devname); } close(sk); return err; } static int bnep_del_from_bridge(const char *devname, const char *bridge) { int ifindex; struct ifreq ifr; int sk, err = 0; if (!devname || !bridge) return -EINVAL; ifindex = if_nametoindex(devname); sk = socket(AF_INET, SOCK_STREAM, 0); if (sk < 0) return -1; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, bridge, IFNAMSIZ - 1); ifr.ifr_ifindex = ifindex; if (ioctl(sk, SIOCBRDELIF, &ifr) < 0) { err = -errno; error("bnep: Can't delete %s from the bridge %s: %s(%d)", devname, bridge, strerror(-err), -err); } else { info("bnep: bridge %s: interface %s removed", bridge, devname); } close(sk); return err; } static ssize_t bnep_send_ctrl_rsp(int sk, uint8_t ctrl, uint16_t resp) { ssize_t sent; switch (ctrl) { case BNEP_CMD_NOT_UNDERSTOOD: { struct bnep_ctrl_cmd_not_understood_cmd rsp; rsp.type = BNEP_CONTROL; rsp.ctrl = ctrl; rsp.unkn_ctrl = (uint8_t) resp; sent = send(sk, &rsp, sizeof(rsp), 0); break; } case BNEP_FILTER_MULT_ADDR_RSP: case BNEP_FILTER_NET_TYPE_RSP: case BNEP_SETUP_CONN_RSP: { struct bnep_control_rsp rsp; rsp.type = BNEP_CONTROL; rsp.ctrl = ctrl; rsp.resp = htons(resp); sent = send(sk, &rsp, sizeof(rsp), 0); break; } default: error("bnep: wrong response type"); sent = -1; break; } return sent; } static uint16_t bnep_setup_decode(int sk, struct bnep_setup_conn_req *req, uint16_t *dst) { const uint8_t bt_base[] = { 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB }; uint16_t src; uint8_t *dest, *source; uint32_t val; if (((req->type != BNEP_CONTROL) && (req->type != (BNEP_CONTROL | BNEP_EXT_HEADER))) || req->ctrl != BNEP_SETUP_CONN_REQ) return BNEP_CONN_NOT_ALLOWED; dest = req->service; source = req->service + req->uuid_size; switch (req->uuid_size) { case 2: /* UUID16 */ *dst = get_be16(dest); src = get_be16(source); break; case 16: /* UUID128 */ /* Check that the bytes in the UUID, except the service ID * itself, are correct. The service ID is checked in * bnep_setup_chk(). */ if (memcmp(&dest[4], bt_base, sizeof(bt_base)) != 0) return BNEP_CONN_INVALID_DST; if (memcmp(&source[4], bt_base, sizeof(bt_base)) != 0) return BNEP_CONN_INVALID_SRC; /* fall through */ case 4: /* UUID32 */ val = get_be32(dest); if (val > 0xffff) return BNEP_CONN_INVALID_DST; *dst = val; val = get_be32(source); if (val > 0xffff) return BNEP_CONN_INVALID_SRC; src = val; break; default: return BNEP_CONN_INVALID_SVC; } /* Allowed PAN Profile scenarios */ switch (*dst) { case BNEP_SVC_NAP: case BNEP_SVC_GN: if (src == BNEP_SVC_PANU) return BNEP_SUCCESS; return BNEP_CONN_INVALID_SRC; case BNEP_SVC_PANU: if (src == BNEP_SVC_PANU || src == BNEP_SVC_GN || src == BNEP_SVC_NAP) return BNEP_SUCCESS; return BNEP_CONN_INVALID_SRC; } return BNEP_CONN_INVALID_DST; } static int bnep_server_add_legacy(int sk, uint16_t dst, char *bridge, char *iface, const bdaddr_t *addr, uint8_t *setup_data, int len) { int err, n; uint16_t rsp; n = read(sk, setup_data, len); if (n != len) { err = -EIO; rsp = BNEP_CONN_NOT_ALLOWED; goto reply; } err = bnep_connadd(sk, dst, iface); if (err < 0) { rsp = BNEP_CONN_NOT_ALLOWED; goto reply; } err = bnep_add_to_bridge(iface, bridge); if (err < 0) { bnep_conndel(addr); rsp = BNEP_CONN_NOT_ALLOWED; goto reply; } err = bnep_if_up(iface); if (err < 0) { bnep_del_from_bridge(iface, bridge); bnep_conndel(addr); rsp = BNEP_CONN_NOT_ALLOWED; goto reply; } rsp = BNEP_SUCCESS; reply: if (bnep_send_ctrl_rsp(sk, BNEP_SETUP_CONN_RSP, rsp) < 0) { err = -errno; error("bnep: send ctrl rsp error: %s (%d)", strerror(-err), -err); } return err; } int bnep_server_add(int sk, char *bridge, char *iface, const bdaddr_t *addr, uint8_t *setup_data, int len) { int err; uint32_t feat; uint16_t rsp, dst; struct bnep_setup_conn_req *req = (void *) setup_data; /* Highest known Control command ID * is BNEP_FILTER_MULT_ADDR_RSP = 0x06 */ if (req->type == BNEP_CONTROL && req->ctrl > BNEP_FILTER_MULT_ADDR_RSP) { error("bnep: cmd not understood"); err = bnep_send_ctrl_rsp(sk, BNEP_CMD_NOT_UNDERSTOOD, req->ctrl); if (err < 0) error("send not understood ctrl rsp error: %s (%d)", strerror(errno), errno); return err; } /* Processing BNEP_SETUP_CONNECTION_REQUEST_MSG */ rsp = bnep_setup_decode(sk, req, &dst); if (rsp != BNEP_SUCCESS) { err = -rsp; error("bnep: error while decoding setup connection request: %d", rsp); goto failed; } feat = bnep_getsuppfeat(); /* * Take out setup data if kernel doesn't support handling it, especially * setup request. If kernel would have set session flags, they should * be checked and handled respectively. */ if (!feat || !(feat & (1 << BNEP_SETUP_RESPONSE))) return bnep_server_add_legacy(sk, dst, bridge, iface, addr, setup_data, len); err = bnep_connadd(sk, dst, iface); if (err < 0) { rsp = BNEP_CONN_NOT_ALLOWED; goto failed; } err = bnep_add_to_bridge(iface, bridge); if (err < 0) goto failed_conn; err = bnep_if_up(iface); if (err < 0) goto failed_bridge; return 0; failed_bridge: bnep_del_from_bridge(iface, bridge); failed_conn: bnep_conndel(addr); return err; failed: if (bnep_send_ctrl_rsp(sk, BNEP_SETUP_CONN_RSP, rsp) < 0) { err = -errno; error("bnep: send ctrl rsp error: %s (%d)", strerror(-err), -err); } return err; } void bnep_server_delete(char *bridge, char *iface, const bdaddr_t *addr) { if (!bridge || !iface || !addr) return; bnep_del_from_bridge(iface, bridge); bnep_if_down(iface); bnep_conndel(addr); } int bnep_send_unkown_rsp(int sk, uint16_t resp) { return bnep_send_ctrl_rsp(sk, BNEP_CMD_NOT_UNDERSTOOD, resp); } bluez-5.87/profiles/network/PaxHeaders/network.conf0000644000000000000000000000005012066112417017527 xustar0020 atime=1770587295 20 ctime=1783094111 bluez-5.87/profiles/network/network.conf0000644000000000000000000000017012066112417017206 0ustar00rootroot# Configuration file for the network service [General] # Disable link encryption: default=false #DisableSecurity=true bluez-5.87/profiles/PaxHeaders/iap0000644000000000000000000000005015221755531014200 xustar0020 atime=1783094113 20 ctime=1783094105 bluez-5.87/profiles/iap/0000755000000000000000000000000015221755531013736 5ustar00rootrootbluez-5.87/profiles/iap/PaxHeaders/main.c0000644000000000000000000000005014572354773015360 xustar0020 atime=1783093362 20 ctime=1783094105 bluez-5.87/profiles/iap/main.c0000644000000000000000000002503514572354773015046 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #define IAP_PATH "/org/bluez/iap" #define IAP_UUID "00000000-deca-fade-deca-deafdecacafe" #define IAP_RECORD \ " \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ " static GMainLoop *main_loop; static guint iap_source = 0; static gboolean iap_handler(GIOChannel *channel, GIOCondition condition, gpointer user_data) { unsigned char buf[512]; ssize_t len; int fd; if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { iap_source = 0; return FALSE; } fd = g_io_channel_unix_get_fd(channel); len = read(fd, buf, sizeof(buf)); if (len < 0) { iap_source = 0; return FALSE; } return TRUE; } static guint create_source(int fd, GIOFunc func) { GIOChannel *channel; guint source; channel = g_io_channel_unix_new(fd); g_io_channel_set_close_on_unref(channel, TRUE); g_io_channel_set_encoding(channel, NULL, NULL); g_io_channel_set_buffered(channel, FALSE); source = g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, func, NULL); g_io_channel_unref(channel); return source; } static void remove_source(const char *path) { if (iap_source > 0) { g_source_remove(iap_source); iap_source = 0; } } static DBusMessage *release_profile(DBusConnection *conn, DBusMessage *msg, void *user_data) { g_print("Profile released\n"); remove_source(IAP_PATH); return dbus_message_new_method_return(msg); } static DBusMessage *new_connection(DBusConnection *conn, DBusMessage *msg, void *user_data) { const char *path, *device; int fd; g_print("New connection\n"); path = dbus_message_get_path(msg); dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_INVALID); g_print(" from %s\n", path); g_print(" for device %s with fd %d\n", device, fd); if (iap_source == 0) iap_source = create_source(fd, iap_handler); else close(fd); return dbus_message_new_method_return(msg); } static DBusMessage *request_disconnection(DBusConnection *conn, DBusMessage *msg, void *user_data) { DBusMessageIter iter; const char *path, *device; g_print("Request disconnection\n"); path = dbus_message_get_path(msg); dbus_message_iter_init(msg, &iter); dbus_message_iter_get_basic(&iter, &device); g_print(" from %s\n", path); g_print(" for device %s\n", device); remove_source(path); return dbus_message_new_method_return(msg); } static DBusMessage *cancel_request(DBusConnection *conn, DBusMessage *msg, void *user_data) { const char *path; g_print("Request canceled\n"); path = dbus_message_get_path(msg); g_print(" from %s\n", path); remove_source(path); return dbus_message_new_method_return(msg); } static const GDBusMethodTable methods[] = { { GDBUS_METHOD("Release", NULL, NULL, release_profile) }, { GDBUS_METHOD("NewConnection", GDBUS_ARGS({ "device", "o" }, { "fd", "h"}, { "opts", "a{sv}"}), NULL, new_connection) }, { GDBUS_METHOD("RequestDisconnection", GDBUS_ARGS({ "device", "o" }), NULL, request_disconnection) }, { GDBUS_METHOD("Cancel", NULL, NULL, cancel_request) }, { } }; static void register_profile_setup(DBusMessageIter *iter, void *user_data) { const char *path = IAP_PATH; const char *uuid = IAP_UUID; DBusMessageIter dict, entry, value; dbus_uint16_t channel; char *record; const char *str; dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); str = "Role"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &value); str = "server"; dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING, &str); dbus_message_iter_close_container(&entry, &value); dbus_message_iter_close_container(&dict, &entry); dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); str = "Channel"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_UINT16_AS_STRING, &value); channel = 23; dbus_message_iter_append_basic(&value, DBUS_TYPE_UINT16, &channel); dbus_message_iter_close_container(&entry, &value); dbus_message_iter_close_container(&dict, &entry); dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); str = "ServiceRecord"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &value); record = g_strdup_printf(IAP_RECORD, IAP_UUID, channel); dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING, &record); g_free(record); dbus_message_iter_close_container(&entry, &value); dbus_message_iter_close_container(&dict, &entry); dbus_message_iter_close_container(iter, &dict); } static void register_profile_reply(DBusMessage *message, void *user_data) { DBusError error; dbus_error_init(&error); if (dbus_set_error_from_message(&error, message) == TRUE) { g_print("Failed to register profile\n"); return; } g_print("Profile registered\n"); } static void connect_handler(DBusConnection *connection, void *user_data) { GDBusClient *client = user_data; GDBusProxy *proxy; g_print("Bluetooth connected\n"); proxy = g_dbus_proxy_new(client, "/org/bluez", "org.bluez.ProfileManager1"); if (!proxy) return; g_dbus_register_interface(connection, IAP_PATH, "org.bluez.Profile1", methods, NULL, NULL, NULL, NULL); g_dbus_proxy_method_call(proxy, "RegisterProfile", register_profile_setup, register_profile_reply, NULL, NULL); g_dbus_proxy_unref(proxy); } static void disconnect_handler(DBusConnection *connection, void *user_data) { g_print("Bluetooth disconnected\n"); g_dbus_unregister_interface(connection, IAP_PATH, "org.bluez.Profile1"); } static gboolean signal_handler(GIOChannel *channel, GIOCondition condition, gpointer user_data) { static unsigned int __terminated = 0; struct signalfd_siginfo si; ssize_t result; int fd; if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { g_main_loop_quit(main_loop); return FALSE; } fd = g_io_channel_unix_get_fd(channel); result = read(fd, &si, sizeof(si)); if (result != sizeof(si)) return FALSE; switch (si.ssi_signo) { case SIGINT: case SIGTERM: if (__terminated == 0) g_main_loop_quit(main_loop); __terminated = 1; break; } return TRUE; } static guint setup_signalfd(void) { GIOChannel *channel; guint source; sigset_t mask; int fd; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { perror("Failed to set signal mask"); return 0; } fd = signalfd(-1, &mask, 0); if (fd < 0) { perror("Failed to create signal descriptor"); return 0; } channel = g_io_channel_unix_new(fd); g_io_channel_set_close_on_unref(channel, TRUE); g_io_channel_set_encoding(channel, NULL, NULL); g_io_channel_set_buffered(channel, FALSE); source = g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, signal_handler, NULL); g_io_channel_unref(channel); return source; } static gboolean option_version = FALSE; static const GOptionEntry options[] = { { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, "Show version information and exit" }, { NULL }, }; int main(int argc, char *argv[]) { GOptionContext *context; GError *error = NULL; DBusConnection *dbus_conn; GDBusClient *client; guint signal; context = g_option_context_new(NULL); g_option_context_add_main_entries(context, options, NULL); if (g_option_context_parse(context, &argc, &argv, &error) == FALSE) { if (error != NULL) { g_printerr("%s\n", error->message); g_error_free(error); } else g_printerr("An unknown error occurred\n"); exit(1); } g_option_context_free(context); if (option_version == TRUE) { g_print("%s\n", VERSION); exit(0); } main_loop = g_main_loop_new(NULL, FALSE); dbus_conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); signal = setup_signalfd(); client = g_dbus_client_new(dbus_conn, "org.bluez", "/org/bluez"); g_dbus_client_set_connect_watch(client, connect_handler, client); g_dbus_client_set_disconnect_watch(client, disconnect_handler, NULL); g_main_loop_run(main_loop); g_dbus_client_unref(client); g_source_remove(signal); dbus_connection_unref(dbus_conn); g_main_loop_unref(main_loop); return 0; } bluez-5.87/profiles/PaxHeaders/scanparam0000644000000000000000000000005015221755531015374 xustar0020 atime=1783094113 20 ctime=1783094105 bluez-5.87/profiles/scanparam/0000755000000000000000000000000015221755531015132 5ustar00rootrootbluez-5.87/profiles/scanparam/PaxHeaders/scan.c0000644000000000000000000000005015142177620016540 xustar0020 atime=1783093684 20 ctime=1783094105 bluez-5.87/profiles/scanparam/scan.c0000644000000000000000000001527215142177620016230 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012 Nordic Semiconductor Inc. * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/log.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "attrib/att.h" #include "src/btd.h" #define SCAN_INTERVAL_WIN_UUID 0x2A4F #define SCAN_REFRESH_UUID 0x2A31 #define SERVER_REQUIRES_REFRESH 0x00 struct scan { struct btd_device *device; struct gatt_db *db; struct bt_gatt_client *client; struct gatt_db_attribute *attr; uint16_t iwhandle; guint refresh_cb_id; }; static void scan_free(struct scan *scan) { bt_gatt_client_unregister_notify(scan->client, scan->refresh_cb_id); gatt_db_unref(scan->db); bt_gatt_client_unref(scan->client); btd_device_unref(scan->device); g_free(scan); } static void write_scan_params(struct scan *scan) { uint8_t value[4]; /* Unless scan parameters are configured, use the known kernel default * parameters */ put_le16(btd_opts.defaults.le.scan_interval_autoconnect ? btd_opts.defaults.le.scan_interval_autoconnect : 0x60, &value[0]); put_le16(btd_opts.defaults.le.scan_win_autoconnect ? btd_opts.defaults.le.scan_win_autoconnect : 0x30, &value[2]); bt_gatt_client_write_without_response(scan->client, scan->iwhandle, false, value, sizeof(value)); } static void refresh_value_cb(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct scan *scan = user_data; DBG("Server requires refresh: %d", value[3]); if (value[3] == SERVER_REQUIRES_REFRESH) write_scan_params(scan); } static void refresh_ccc_written_cb(uint16_t att_ecode, void *user_data) { if (att_ecode != 0) { error("Scan Refresh: notifications not enabled %s", att_ecode2str(att_ecode)); return; } DBG("Scan Refresh: notification enabled"); } static void handle_refresh(struct scan *scan, uint16_t value_handle) { DBG("Scan Refresh handle: 0x%04x", value_handle); scan->refresh_cb_id = bt_gatt_client_register_notify(scan->client, value_handle, refresh_ccc_written_cb, refresh_value_cb, scan, NULL); } static void handle_iwin(struct scan *scan, uint16_t value_handle) { scan->iwhandle = value_handle; DBG("Scan Interval Window handle: 0x%04x", scan->iwhandle); write_scan_params(scan); } static void handle_characteristic(struct gatt_db_attribute *attr, void *user_data) { struct scan *scan = user_data; uint16_t value_handle; bt_uuid_t uuid, scan_interval_wind_uuid, scan_refresh_uuid; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) { error("Failed to obtain characteristic data"); return; } bt_uuid16_create(&scan_interval_wind_uuid, SCAN_INTERVAL_WIN_UUID); bt_uuid16_create(&scan_refresh_uuid, SCAN_REFRESH_UUID); if (bt_uuid_cmp(&scan_interval_wind_uuid, &uuid) == 0) handle_iwin(scan, value_handle); else if (bt_uuid_cmp(&scan_refresh_uuid, &uuid) == 0) handle_refresh(scan, value_handle); else { char uuid_str[MAX_LEN_UUID_STR]; bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); DBG("Unsupported characteristic: %s", uuid_str); } } static void foreach_scan_param_service(struct gatt_db_attribute *attr, void *user_data) { struct scan *scan = user_data; if (scan->attr) { error("More than one scan params service exists for this " "device"); return; } scan->attr = attr; gatt_db_service_foreach_char(scan->attr, handle_characteristic, scan); } static void scan_reset(struct scan *scan) { scan->attr = NULL; gatt_db_unref(scan->db); scan->db = NULL; bt_gatt_client_unref(scan->client); scan->client = NULL; } static int scan_param_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct gatt_db *db = btd_device_get_gatt_db(device); struct bt_gatt_client *client = btd_device_get_gatt_client(device); bt_uuid_t scan_parameters_uuid; struct scan *scan = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("Scan Parameters Client Driver profile accept (%s)", addr); if (!scan) { error("Scan Parameters service not handled by profile"); return -1; } scan->db = gatt_db_ref(db); scan->client = bt_gatt_client_clone(client); bt_string_to_uuid(&scan_parameters_uuid, SCAN_PARAMETERS_UUID); gatt_db_foreach_service(db, &scan_parameters_uuid, foreach_scan_param_service, scan); if (!scan->attr) { error("Scan Parameters attribute not found"); scan_reset(scan); return -1; } btd_service_connecting_complete(service, 0); return 0; } static int scan_param_disconnect(struct btd_service *service) { struct scan *scan = btd_service_get_user_data(service); scan_reset(scan); btd_service_disconnecting_complete(service, 0); return 0; } static void scan_param_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct scan *scan; char addr[18]; ba2str(device_get_address(device), addr); DBG("GAP profile remove (%s)", addr); scan = btd_service_get_user_data(service); if (!scan) { error("GAP service not handled by profile"); return; } scan_free(scan); } static int scan_param_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct scan *scan; char addr[18]; ba2str(device_get_address(device), addr); DBG("Scan Parameters Client Driver profile probe (%s)", addr); /* Ignore, if we were probed for this device already */ scan = btd_service_get_user_data(service); if (scan) { error("Profile probed twice for the same service!"); return -1; } scan = g_new0(struct scan, 1); if (!scan) return -1; scan->device = btd_device_ref(device); btd_service_set_user_data(service, scan); return 0; } static struct btd_profile scan_profile = { .name = "Scan Parameters Client Driver", .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = SCAN_PARAMETERS_UUID, .device_probe = scan_param_probe, .device_remove = scan_param_remove, .accept = scan_param_accept, .disconnect = scan_param_disconnect, }; static int scan_param_init(void) { return btd_profile_register(&scan_profile); } static void scan_param_exit(void) { btd_profile_unregister(&scan_profile); } BLUETOOTH_PLUGIN_DEFINE(scanparam, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, scan_param_init, scan_param_exit) bluez-5.87/profiles/scanparam/PaxHeaders/scpp.h0000644000000000000000000000005014015011623016552 xustar0020 atime=1783093675 20 ctime=1783094105 bluez-5.87/profiles/scanparam/scpp.h0000644000000000000000000000106314015011623016233 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * */ struct bt_scpp; struct bt_scpp *bt_scpp_new(void *primary); struct bt_scpp *bt_scpp_ref(struct bt_scpp *scan); void bt_scpp_unref(struct bt_scpp *scan); bool bt_scpp_attach(struct bt_scpp *scan, void *gatt); void bt_scpp_detach(struct bt_scpp *scan); bool bt_scpp_set_interval(struct bt_scpp *scan, uint16_t value); bool bt_scpp_set_window(struct bt_scpp *scan, uint16_t value); bluez-5.87/profiles/scanparam/PaxHeaders/scpp.c0000644000000000000000000000005015063045254016560 xustar0020 atime=1783093681 20 ctime=1783094105 bluez-5.87/profiles/scanparam/scpp.c0000644000000000000000000001620715063045254016247 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012 Nordic Semiconductor Inc. * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "src/log.h" #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "attrib/att.h" #include "attrib/gattrib.h" #include "attrib/gatt.h" #include "profiles/scanparam/scpp.h" #define SCAN_INTERVAL_WIN_UUID 0x2A4F #define SCAN_REFRESH_UUID 0x2A31 #define SCAN_INTERVAL 0x0060 #define SCAN_WINDOW 0x0030 #define SERVER_REQUIRES_REFRESH 0x00 struct bt_scpp { int ref_count; GAttrib *attrib; struct gatt_primary *primary; uint16_t interval; uint16_t window; uint16_t iwhandle; uint16_t refresh_handle; guint refresh_cb_id; struct queue *gatt_op; }; static void discover_char(struct bt_scpp *scpp, GAttrib *attrib, uint16_t start, uint16_t end, bt_uuid_t *uuid, gatt_cb_t func, gpointer user_data) { unsigned int id; id = gatt_discover_char(attrib, start, end, uuid, func, user_data); queue_push_head(scpp->gatt_op, UINT_TO_PTR(id)); } static void discover_desc(struct bt_scpp *scpp, GAttrib *attrib, uint16_t start, uint16_t end, bt_uuid_t *uuid, gatt_cb_t func, gpointer user_data) { unsigned int id; id = gatt_discover_desc(attrib, start, end, uuid, func, user_data); queue_push_head(scpp->gatt_op, UINT_TO_PTR(id)); } static void write_char(struct bt_scpp *scan, GAttrib *attrib, uint16_t handle, const uint8_t *value, size_t vlen, GAttribResultFunc func, gpointer user_data) { unsigned int id; id = gatt_write_char(attrib, handle, value, vlen, func, user_data); queue_push_head(scan->gatt_op, UINT_TO_PTR(id)); } static void scpp_free(struct bt_scpp *scan) { bt_scpp_detach(scan); free(scan->primary); queue_destroy(scan->gatt_op, NULL); /* cleared in bt_scpp_detach */ g_free(scan); } struct bt_scpp *bt_scpp_new(void *primary) { struct bt_scpp *scan; scan = g_try_new0(struct bt_scpp, 1); if (!scan) return NULL; scan->interval = SCAN_INTERVAL; scan->window = SCAN_WINDOW; scan->gatt_op = queue_new(); if (primary) scan->primary = util_memdup(primary, sizeof(*scan->primary)); return bt_scpp_ref(scan); } struct bt_scpp *bt_scpp_ref(struct bt_scpp *scan) { if (!scan) return NULL; __sync_fetch_and_add(&scan->ref_count, 1); return scan; } void bt_scpp_unref(struct bt_scpp *scan) { if (!scan) return; if (__sync_sub_and_fetch(&scan->ref_count, 1)) return; scpp_free(scan); } static void write_scan_params(GAttrib *attrib, uint16_t handle, uint16_t interval, uint16_t window) { uint8_t value[4]; put_le16(interval, &value[0]); put_le16(window, &value[2]); gatt_write_cmd(attrib, handle, value, sizeof(value), NULL, NULL); } static void refresh_value_cb(const uint8_t *pdu, uint16_t len, gpointer user_data) { struct bt_scpp *scan = user_data; DBG("Server requires refresh: %d", pdu[3]); if (pdu[3] == SERVER_REQUIRES_REFRESH) write_scan_params(scan->attrib, scan->iwhandle, scan->interval, scan->window); } static void ccc_written_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { struct bt_scpp *scan = user_data; if (status != 0) { error("Write Scan Refresh CCC failed: %s", att_ecode2str(status)); return; } DBG("Scan Refresh: notification enabled"); scan->refresh_cb_id = g_attrib_register(scan->attrib, ATT_OP_HANDLE_NOTIFY, scan->refresh_handle, refresh_value_cb, scan, NULL); } static void write_ccc(struct bt_scpp *scan, GAttrib *attrib, uint16_t handle, void *user_data) { uint8_t value[2]; put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value); write_char(scan, attrib, handle, value, sizeof(value), ccc_written_cb, user_data); } static void discover_descriptor_cb(uint8_t status, GSList *descs, void *user_data) { struct bt_scpp *scan = user_data; struct gatt_desc *desc; if (status != 0) { error("Discover descriptors failed: %s", att_ecode2str(status)); return; } /* There will be only one descriptor on list and it will be CCC */ desc = descs->data; write_ccc(scan, scan->attrib, desc->handle, scan); } static void refresh_discovered_cb(uint8_t status, GSList *chars, void *user_data) { struct bt_scpp *scan = user_data; struct gatt_char *chr; uint16_t start, end; bt_uuid_t uuid; if (status) { error("Scan Refresh %s", att_ecode2str(status)); return; } if (!chars) { DBG("Scan Refresh not supported"); return; } chr = chars->data; DBG("Scan Refresh handle: 0x%04x", chr->value_handle); start = chr->value_handle + 1; end = scan->primary->range.end; if (start > end) return; scan->refresh_handle = chr->value_handle; bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); discover_desc(scan, scan->attrib, start, end, &uuid, discover_descriptor_cb, user_data); } static void iwin_discovered_cb(uint8_t status, GSList *chars, void *user_data) { struct bt_scpp *scan = user_data; struct gatt_char *chr; if (status) { error("Discover Scan Interval Window: %s", att_ecode2str(status)); return; } chr = chars->data; scan->iwhandle = chr->value_handle; DBG("Scan Interval Window handle: 0x%04x", scan->iwhandle); write_scan_params(scan->attrib, scan->iwhandle, scan->interval, scan->window); } bool bt_scpp_attach(struct bt_scpp *scan, void *attrib) { bt_uuid_t iwin_uuid, refresh_uuid; if (!scan || scan->attrib || !scan->primary) return false; scan->attrib = g_attrib_ref(attrib); if (scan->iwhandle) write_scan_params(scan->attrib, scan->iwhandle, scan->interval, scan->window); else { bt_uuid16_create(&iwin_uuid, SCAN_INTERVAL_WIN_UUID); discover_char(scan, scan->attrib, scan->primary->range.start, scan->primary->range.end, &iwin_uuid, iwin_discovered_cb, scan); } if (scan->refresh_handle) scan->refresh_cb_id = g_attrib_register(scan->attrib, ATT_OP_HANDLE_NOTIFY, scan->refresh_handle, refresh_value_cb, scan, NULL); else { bt_uuid16_create(&refresh_uuid, SCAN_REFRESH_UUID); discover_char(scan, scan->attrib, scan->primary->range.start, scan->primary->range.end, &refresh_uuid, refresh_discovered_cb, scan); } return true; } static void cancel_gatt_req(void *data, void *user_data) { unsigned int id = PTR_TO_UINT(data); struct bt_scpp *scan = user_data; g_attrib_cancel(scan->attrib, id); } void bt_scpp_detach(struct bt_scpp *scan) { if (!scan || !scan->attrib) return; if (scan->refresh_cb_id > 0) { g_attrib_unregister(scan->attrib, scan->refresh_cb_id); scan->refresh_cb_id = 0; } queue_foreach(scan->gatt_op, cancel_gatt_req, scan); g_attrib_unref(scan->attrib); scan->attrib = NULL; } bool bt_scpp_set_interval(struct bt_scpp *scan, uint16_t value) { if (!scan) return false; /* TODO: Check valid range */ scan->interval = value; return true; } bool bt_scpp_set_window(struct bt_scpp *scan, uint16_t value) { if (!scan) return false; /* TODO: Check valid range */ scan->window = value; return true; } bluez-5.87/profiles/PaxHeaders/input0000644000000000000000000000005015221755537014574 xustar0020 atime=1783094113 20 ctime=1783094111 bluez-5.87/profiles/input/0000755000000000000000000000000015221755537014332 5ustar00rootrootbluez-5.87/profiles/input/PaxHeaders/suspend-none.c0000644000000000000000000000005014015011623017410 xustar0020 atime=1783093682 20 ctime=1783094105 bluez-5.87/profiles/input/suspend-none.c0000644000000000000000000000067114015011623017075 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012 Nordic Semiconductor Inc. * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT * * */ #ifdef HAVE_CONFIG_H #include #endif #include "src/log.h" #include "suspend.h" int suspend_init(suspend_event suspend, resume_event resume) { DBG(""); return 0; } void suspend_exit(void) { DBG(""); } bluez-5.87/profiles/input/PaxHeaders/server.c0000644000000000000000000000005015221752714016315 xustar0020 atime=1783092685 20 ctime=1783094105 bluez-5.87/profiles/input/server.c0000644000000000000000000002105215221752714015776 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/log.h" #include "src/uuid-helper.h" #include "btio/btio.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "sixaxis.h" #include "device.h" #include "server.h" struct confirm_data { bdaddr_t dst; GIOChannel *io; }; static GSList *servers = NULL; struct input_server { bdaddr_t src; GIOChannel *ctrl; GIOChannel *intr; struct confirm_data *confirm; }; static int server_cmp(gconstpointer s, gconstpointer user_data) { const struct input_server *server = s; const bdaddr_t *src = user_data; return bacmp(&server->src, src); } struct sixaxis_data { GIOChannel *chan; uint16_t psm; }; static void sixaxis_sdp_cb(struct btd_device *dev, int err, void *user_data) { struct sixaxis_data *data = user_data; const bdaddr_t *src; DBG("err %d (%s)", err, strerror(-err)); if (err < 0) goto fail; src = btd_adapter_get_address(device_get_adapter(dev)); if (input_device_set_channel(src, device_get_address(dev), data->psm, data->chan) < 0) goto fail; g_io_channel_unref(data->chan); g_free(data); return; fail: g_io_channel_shutdown(data->chan, TRUE, NULL); g_io_channel_unref(data->chan); g_free(data); } static void sixaxis_browse_sdp(const bdaddr_t *src, const bdaddr_t *dst, GIOChannel *chan, uint16_t psm) { struct btd_device *device; struct sixaxis_data *data; device = btd_adapter_find_device(adapter_find(src), dst, BDADDR_BREDR); if (!device) return; data = g_new0(struct sixaxis_data, 1); data->chan = g_io_channel_ref(chan); data->psm = psm; if (psm == L2CAP_PSM_HIDP_CTRL) device_discover_services(device, BDADDR_BREDR, NULL); device_wait_for_svc_complete(device, sixaxis_sdp_cb, data); } static bool dev_is_sixaxis(const bdaddr_t *src, const bdaddr_t *dst) { struct btd_device *device; uint16_t vid, pid; const struct cable_pairing *cp; device = btd_adapter_find_device(adapter_find(src), dst, BDADDR_BREDR); if (!device) return false; vid = btd_device_get_vendor(device); pid = btd_device_get_product(device); cp = get_pairing(vid, pid, NULL); if (cp && (cp->type == CABLE_PAIRING_SIXAXIS || cp->type == CABLE_PAIRING_DS4)) return true; return false; } static void connect_event_cb(GIOChannel *chan, GError *err, gpointer data) { uint16_t psm; bdaddr_t src, dst; char address[18]; GError *gerr = NULL; int ret; if (err) { error("%s", err->message); return; } bt_io_get(chan, &gerr, BT_IO_OPT_SOURCE_BDADDR, &src, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_PSM, &psm, BT_IO_OPT_INVALID); if (gerr) { error("%s", gerr->message); g_error_free(gerr); g_io_channel_shutdown(chan, TRUE, NULL); return; } ba2str(&dst, address); DBG("Incoming connection from %s on PSM %d", address, psm); ret = input_device_set_channel(&src, &dst, psm, chan); if (ret == 0) return; if (ret == -ENOENT && dev_is_sixaxis(&src, &dst)) { sixaxis_browse_sdp(&src, &dst, chan, psm); return; } error("Refusing input device connect: %s (%d)", strerror(-ret), -ret); /* Send unplug virtual cable to unknown devices */ if (ret == -ENOENT && psm == L2CAP_PSM_HIDP_CTRL) { unsigned char unplug = 0x15; int sk = g_io_channel_unix_get_fd(chan); if (write(sk, &unplug, sizeof(unplug)) < 0) error("Unable to send virtual cable unplug"); } g_io_channel_shutdown(chan, TRUE, NULL); } static void auth_callback(DBusError *derr, void *user_data) { struct input_server *server = user_data; struct confirm_data *confirm = server->confirm; GError *err = NULL; if (derr) { error("Access denied: %s", derr->message); goto reject; } if (!input_device_exists(&server->src, &confirm->dst) && !dev_is_sixaxis(&server->src, &confirm->dst)) return; if (!bt_io_accept(confirm->io, connect_event_cb, server, NULL, &err)) { error("bt_io_accept: %s", err->message); g_error_free(err); goto reject; } g_io_channel_unref(confirm->io); g_free(server->confirm); server->confirm = NULL; return; reject: g_io_channel_shutdown(confirm->io, TRUE, NULL); g_io_channel_unref(confirm->io); server->confirm = NULL; input_device_close_channels(&server->src, &confirm->dst); g_free(confirm); } static void confirm_event_cb(GIOChannel *chan, gpointer user_data) { struct input_server *server = user_data; bdaddr_t src, dst; GError *err = NULL; char addr[18]; guint ret; DBG(""); bt_io_get(chan, &err, BT_IO_OPT_SOURCE_BDADDR, &src, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_INVALID); if (err) { error("%s", err->message); g_error_free(err); g_io_channel_shutdown(chan, TRUE, NULL); return; } ba2str(&dst, addr); if (server->confirm) { error("Refusing connection from %s: setup in progress", addr); goto drop; } if (!input_device_exists(&src, &dst) && !dev_is_sixaxis(&src, &dst)) { error("Refusing connection from %s: unknown device", addr); goto drop; } server->confirm = g_new0(struct confirm_data, 1); server->confirm->io = g_io_channel_ref(chan); bacpy(&server->confirm->dst, &dst); ret = btd_request_authorization(&src, &dst, HID_UUID, auth_callback, server); if (ret != 0) return; error("input: authorization for device %s failed", addr); g_io_channel_unref(server->confirm->io); g_free(server->confirm); server->confirm = NULL; drop: input_device_close_channels(&src, &dst); g_io_channel_shutdown(chan, TRUE, NULL); } static BtIOSecLevel get_necessary_sec_level(bool device_cable_pairing) { /* Use lower security to allow the cable paired devices to connect. */ /* Unless classic bonded only mode is disabled, the security level */ /* will be bumped again for non cable paired devices in */ /* hidp_add_connection() */ if (device_cable_pairing) return BT_IO_SEC_LOW; return input_get_classic_bonded_only() ? BT_IO_SEC_MEDIUM : BT_IO_SEC_LOW; } int server_start(const bdaddr_t *src, bool device_sixaxis_cable_pairing) { struct input_server *server; GError *err = NULL; BtIOSecLevel sec_level; sec_level = get_necessary_sec_level(device_sixaxis_cable_pairing); server = g_new0(struct input_server, 1); bacpy(&server->src, src); server->ctrl = bt_io_listen(connect_event_cb, NULL, server, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL, BT_IO_OPT_SEC_LEVEL, sec_level, BT_IO_OPT_INVALID); if (!server->ctrl) { error("Failed to listen on control channel"); g_error_free(err); g_free(server); return -1; } server->intr = bt_io_listen(NULL, confirm_event_cb, server, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR, BT_IO_OPT_SEC_LEVEL, sec_level, BT_IO_OPT_INVALID); if (!server->intr) { error("Failed to listen on interrupt channel"); g_io_channel_unref(server->ctrl); g_error_free(err); g_free(server); return -1; } servers = g_slist_append(servers, server); return 0; } int server_set_cable_pairing(const bdaddr_t *src, bool device_cable_pairing) { struct input_server *server; GSList *l; BtIOSecLevel sec_level; BtIOSecLevel new_sec_level; GError *err = NULL; l = g_slist_find_custom(servers, src, server_cmp); if (!l) return -1; server = l->data; bt_io_get(server->ctrl, &err, BT_IO_OPT_SEC_LEVEL, &sec_level, BT_IO_OPT_INVALID); if (err) { error("%s", err->message); g_error_free(err); return -1; } new_sec_level = get_necessary_sec_level(device_cable_pairing); if (sec_level == new_sec_level) return -1; DBG("Applying the new security level to the listening input server"); if (!bt_io_set(server->ctrl, &err, BT_IO_OPT_SEC_LEVEL, new_sec_level, BT_IO_OPT_INVALID)) { error("bt_io_set(OPT_SEC_LEVEL): %s", err->message); g_error_free(err); return -1; } if (!bt_io_set(server->intr, &err, BT_IO_OPT_SEC_LEVEL, new_sec_level, BT_IO_OPT_INVALID)) { error("bt_io_set(OPT_SEC_LEVEL): %s", err->message); g_error_free(err); return -1; } return 0; } void server_stop(const bdaddr_t *src) { struct input_server *server; GSList *l; l = g_slist_find_custom(servers, src, server_cmp); if (!l) return; server = l->data; g_io_channel_shutdown(server->intr, TRUE, NULL); g_io_channel_unref(server->intr); g_io_channel_shutdown(server->ctrl, TRUE, NULL); g_io_channel_unref(server->ctrl); servers = g_slist_remove(servers, server); g_free(server); } bluez-5.87/profiles/input/PaxHeaders/sixaxis.h0000644000000000000000000000005015063045254016502 xustar0020 atime=1783093669 20 ctime=1783094105 bluez-5.87/profiles/input/sixaxis.h0000644000000000000000000000343215063045254016165 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2009,2017 Bastien Nocera * Copyright (C) 2011 Antonio Ospite * Copyright (C) 2013 Szymon Janc * * */ #ifndef _SIXAXIS_H_ #define _SIXAXIS_H_ typedef enum { CABLE_PAIRING_UNSUPPORTED = 0, CABLE_PAIRING_SIXAXIS, CABLE_PAIRING_DS4, } CablePairingType; struct cable_pairing { const char *name; uint16_t source; uint16_t vid; uint16_t pid; uint16_t version; CablePairingType type; }; static inline const struct cable_pairing * get_pairing(uint16_t vid, uint16_t pid, const char *name) { static const struct cable_pairing devices[] = { { .name = "Sony PLAYSTATION(R)3 Controller", .source = 0x0002, .vid = 0x054c, .pid = 0x0268, .version = 0x0000, .type = CABLE_PAIRING_SIXAXIS, }, { .name = "SHANWAN PS3 GamePad", .source = 0x0002, .vid = 0x054c, .pid = 0x0268, .version = 0x0000, .type = CABLE_PAIRING_SIXAXIS, }, { .name = "Navigation Controller", .source = 0x0002, .vid = 0x054c, .pid = 0x042f, .version = 0x0000, .type = CABLE_PAIRING_SIXAXIS, }, { .name = "Wireless Controller", .source = 0x0002, .vid = 0x054c, .pid = 0x05c4, .version = 0x0001, .type = CABLE_PAIRING_DS4, }, { .name = "Wireless Controller", .source = 0x0002, .vid = 0x054c, .pid = 0x09cc, .version = 0x0001, .type = CABLE_PAIRING_DS4, }, }; guint i; for (i = 0; i < G_N_ELEMENTS(devices); i++) { if (devices[i].vid != vid) continue; if (devices[i].pid != pid) continue; if (name && !g_str_has_suffix(name, devices[i].name)) continue; return &devices[i]; } return NULL; } #endif /* _SIXAXIS_H_ */ bluez-5.87/profiles/input/PaxHeaders/hog-lib.h0000644000000000000000000000005014643061455016337 xustar0020 atime=1783093674 20 ctime=1783094105 bluez-5.87/profiles/input/hog-lib.h0000644000000000000000000000150114643061455016015 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * */ struct bt_hog; struct bt_hog *bt_hog_new_default(const char *name, uint16_t vendor, uint16_t product, uint16_t version, uint8_t type, struct gatt_db *db); struct bt_hog *bt_hog_new(int fd, const char *name, uint16_t vendor, uint16_t product, uint16_t version, uint8_t type, struct gatt_db *db); struct bt_hog *bt_hog_ref(struct bt_hog *hog); void bt_hog_unref(struct bt_hog *hog); bool bt_hog_attach(struct bt_hog *hog, void *gatt); void bt_hog_detach(struct bt_hog *hog, bool force); int bt_hog_set_control_point(struct bt_hog *hog, bool suspend); int bt_hog_send_report(struct bt_hog *hog, void *data, size_t size, int type); bluez-5.87/profiles/input/PaxHeaders/suspend.h0000644000000000000000000000005014015011623016460 xustar0020 atime=1783093674 20 ctime=1783094105 bluez-5.87/profiles/input/suspend.h0000644000000000000000000000060514015011623016142 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012 Nordic Semiconductor Inc. * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT * * */ typedef void (*suspend_event) (void); typedef void (*resume_event) (void); int suspend_init(suspend_event suspend, resume_event resume); void suspend_exit(void); bluez-5.87/profiles/input/PaxHeaders/device.c0000644000000000000000000000005015200607060016234 xustar0020 atime=1783092683 20 ctime=1783094105 bluez-5.87/profiles/input/device.c0000644000000000000000000011504215200607060015720 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * Copyright (C) 2014 Google Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hidp.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "bluetooth/uuid.h" #include "gdbus/gdbus.h" #include "btio/btio.h" #include "src/log.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/storage.h" #include "src/dbus-common.h" #include "src/error.h" #include "src/sdp-client.h" #include "src/shared/timeout.h" #include "src/shared/uhid.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "device.h" #include "hidp_defs.h" #include "server.h" #define INPUT_INTERFACE "org.bluez.Input1" enum reconnect_mode_t { RECONNECT_NONE = 0, RECONNECT_DEVICE, RECONNECT_HOST, RECONNECT_ANY }; struct hidp_msg { uint8_t hdr; struct iovec *iov; }; struct input_device { struct btd_service *service; struct btd_device *device; char *path; bdaddr_t src; bdaddr_t dst; const sdp_record_t *rec; GIOChannel *ctrl_io; GIOChannel *intr_io; guint ctrl_watch; guint intr_watch; guint sec_watch; struct hidp_connadd_req *req; bool disable_sdp; enum reconnect_mode_t reconnect_mode; unsigned int reconnect_timer; uint32_t reconnect_attempt; struct bt_uhid *uhid; uint8_t report_req_pending; unsigned int report_req_timer; uint32_t report_rsp_id; bool virtual_cable_unplug; uint8_t type; unsigned int idle_timer; }; static int idle_timeout = 0; static uhid_state_t uhid_state = UHID_ENABLED; static bool classic_bonded_only = true; void input_set_idle_timeout(int timeout) { idle_timeout = timeout; } void input_set_userspace_hid(char *state) { if (!strcasecmp(state, "false") || !strcasecmp(state, "no") || !strcasecmp(state, "off")) uhid_state = UHID_DISABLED; else if (!strcasecmp(state, "true") || !strcasecmp(state, "yes") || !strcasecmp(state, "on")) uhid_state = UHID_ENABLED; else if (!strcasecmp(state, "persist")) uhid_state = UHID_PERSIST; else error("Unknown value '%s'", state); } void input_set_classic_bonded_only(bool state) { classic_bonded_only = state; } bool input_get_classic_bonded_only(void) { return classic_bonded_only; } static void input_device_enter_reconnect_mode(struct input_device *idev); static int connection_disconnect(struct input_device *idev, uint32_t flags); static bool input_device_bonded(struct input_device *idev) { /* HIDP is classic only; so consider it bonded if BDADDR_BREDR is */ return device_is_bonded(idev->device, BDADDR_BREDR); } static void input_device_free(struct input_device *idev) { bt_uhid_unref(idev->uhid); btd_service_unref(idev->service); btd_device_unref(idev->device); g_free(idev->path); if (idev->ctrl_watch > 0) g_source_remove(idev->ctrl_watch); if (idev->intr_watch > 0) g_source_remove(idev->intr_watch); if (idev->sec_watch > 0) g_source_remove(idev->sec_watch); if (idev->intr_io) g_io_channel_unref(idev->intr_io); if (idev->ctrl_io) g_io_channel_unref(idev->ctrl_io); if (idev->req) { g_free(idev->req->rd_data); g_free(idev->req); } if (idev->idle_timer) timeout_remove(idev->idle_timer); if (idev->reconnect_timer > 0) timeout_remove(idev->reconnect_timer); if (idev->report_req_timer > 0) timeout_remove(idev->report_req_timer); g_free(idev); } static void virtual_cable_unplug(struct input_device *idev) { /* HIDP is classic only; so only remove BDADDR_BREDR bonding */ device_remove_bonding(idev->device, BDADDR_BREDR); idev->virtual_cable_unplug = false; } static int uhid_disconnect(struct input_device *idev, bool force) { int err; if (!bt_uhid_created(idev->uhid)) return 0; /* Force destroy the node if virtual cable unplug flag has been set */ if (idev->virtual_cable_unplug && !force) force = true; if (!force && uhid_state != UHID_PERSIST) force = true; err = bt_uhid_destroy(idev->uhid, force); if (err < 0) { error("bt_uhid_destroy: %s", strerror(-err)); return err; } if (!bt_uhid_created(idev->uhid)) bt_uhid_unregister_all(idev->uhid); return err; } static bool input_device_idle_timeout(gpointer user_data) { struct input_device *idev = user_data; idev->idle_timer = 0; DBG("path=%s", idev->path); uhid_disconnect(idev, true); connection_disconnect(idev, 0); return false; } static void input_device_idle_reset(struct input_device *idev) { timeout_remove(idev->idle_timer); if (idle_timeout) idev->idle_timer = timeout_add_seconds(idle_timeout, input_device_idle_timeout, idev, NULL); } static bool hidp_send_message(struct input_device *idev, GIOChannel *chan, uint8_t hdr, const uint8_t *data, size_t size) { int fd; ssize_t len; struct iovec iov[2]; if (!chan) { error("BT socket not connected"); return false; } iov[0].iov_base = &hdr; iov[0].iov_len = sizeof(hdr); if (data == NULL) size = 0; iov[1].iov_base = (void *)data; iov[1].iov_len = size; fd = g_io_channel_unix_get_fd(chan); len = writev(fd, iov, 2); if (len < 0) { error("BT socket write error: %s (%d)", strerror(errno), errno); return false; } if ((size_t) len < size + 1) { error("BT socket write error: partial write (%zd of %zu bytes)", len, size + 1); return false; } input_device_idle_reset(idev); return true; } static bool hidp_send_ctrl_message(struct input_device *idev, uint8_t hdr, const uint8_t *data, size_t size) { if (hdr == (HIDP_TRANS_HID_CONTROL | HIDP_CTRL_VIRTUAL_CABLE_UNPLUG)) idev->virtual_cable_unplug = true; return hidp_send_message(idev, idev->ctrl_io, hdr, data, size); } static bool hidp_send_intr_message(struct input_device *idev, uint8_t hdr, const uint8_t *data, size_t size) { return hidp_send_message(idev, idev->intr_io, hdr, data, size); } static bool uhid_send_get_report_reply(struct input_device *idev, const uint8_t *data, size_t size, uint32_t id, uint16_t err) { int ret; if (data == NULL) size = 0; if (!bt_uhid_created(idev->uhid)) { DBG("HID report (%zu bytes) dropped", size); return false; } ret = bt_uhid_get_report_reply(idev->uhid, id, 0, err, data, size); if (ret < 0) { error("bt_uhid_get_report_reply: %s (%d)", strerror(-ret), -ret); return false; } DBG("HID report (%zu bytes)", size); return true; } static bool uhid_send_set_report_reply(struct input_device *idev, uint32_t id, uint16_t err) { int ret; if (!bt_uhid_created(idev->uhid)) return false; ret = bt_uhid_set_report_reply(idev->uhid, id, err); if (ret < 0) { error("bt_uhid_set_report_reply: %s (%d)", strerror(-ret), -ret); return false; } return true; } static bool uhid_send_input_report(struct input_device *idev, const uint8_t *data, size_t size) { int err; if (data == NULL) size = 0; if (!bt_uhid_created(idev->uhid)) { DBG("HID report (%zu bytes) dropped", size); return false; } err = bt_uhid_input(idev->uhid, 0, data, size); if (err < 0) { error("bt_uhid_input: %s (%d)", strerror(-err), -err); return false; } DBG("HID report (%zu bytes)", size); return true; } static bool hidp_recv_intr_data(GIOChannel *chan, struct input_device *idev) { int fd; ssize_t len; uint8_t hdr; uint8_t data[UHID_DATA_MAX + 1]; fd = g_io_channel_unix_get_fd(chan); len = read(fd, data, sizeof(data)); if (len < 0) { error("BT socket read error: %s (%d)", strerror(errno), errno); return false; } if (len == 0) { DBG("BT socket read returned 0 bytes"); return true; } input_device_idle_reset(idev); hdr = data[0]; if (hdr != (HIDP_TRANS_DATA | HIDP_DATA_RTYPE_INPUT)) { DBG("unsupported HIDP protocol header 0x%02x", hdr); return true; } if (len < 2) { DBG("received empty HID report"); return true; } uhid_send_input_report(idev, data + 1, len - 1); return true; } static gboolean intr_watch_cb(GIOChannel *chan, GIOCondition cond, gpointer data) { struct input_device *idev = data; char address[18]; if (cond & G_IO_IN) { if (hidp_recv_intr_data(chan, idev) && (cond == G_IO_IN)) return TRUE; } ba2str(&idev->dst, address); DBG("Device %s disconnected", address); /* Checking for ctrl_watch avoids a double g_io_channel_shutdown since * it's likely that ctrl_watch_cb has been queued for dispatching in * this mainloop iteration */ if ((cond & (G_IO_HUP | G_IO_ERR)) && idev->ctrl_watch) g_io_channel_shutdown(chan, TRUE, NULL); idev->intr_watch = 0; if (idev->intr_io) { g_io_channel_unref(idev->intr_io); idev->intr_io = NULL; } /* Close control channel if the closing of interrupt channel is not * initiated by the other party */ if (idev->ctrl_io && !(cond & (G_IO_NVAL | G_IO_ERR))) g_io_channel_shutdown(idev->ctrl_io, TRUE, NULL); btd_service_disconnecting_complete(idev->service, 0); /* Enter the auto-reconnect mode if needed */ input_device_enter_reconnect_mode(idev); if (!idev->ctrl_io && idev->virtual_cable_unplug) virtual_cable_unplug(idev); /* If connection abruptly ended, uhid might be not yet disconnected */ uhid_disconnect(idev, false); return FALSE; } static void hidp_recv_ctrl_handshake(struct input_device *idev, uint8_t param) { bool pending_req_complete = false; uint8_t pending_req_type; DBG(""); pending_req_type = idev->report_req_pending & HIDP_HEADER_TRANS_MASK; switch (param) { case HIDP_HSHK_SUCCESSFUL: if (pending_req_type == HIDP_TRANS_SET_REPORT) { DBG("SET_REPORT successful"); pending_req_complete = true; } else DBG("Spurious HIDP_HSHK_SUCCESSFUL"); break; case HIDP_HSHK_NOT_READY: case HIDP_HSHK_ERR_INVALID_REPORT_ID: case HIDP_HSHK_ERR_UNSUPPORTED_REQUEST: case HIDP_HSHK_ERR_INVALID_PARAMETER: case HIDP_HSHK_ERR_UNKNOWN: case HIDP_HSHK_ERR_FATAL: if (pending_req_type == HIDP_TRANS_GET_REPORT) { DBG("GET_REPORT failed (%u)", param); uhid_send_get_report_reply(idev, NULL, 0, idev->report_rsp_id, EIO); pending_req_complete = true; } else if (pending_req_type == HIDP_TRANS_SET_REPORT) { DBG("SET_REPORT failed (%u)", param); uhid_send_set_report_reply(idev, idev->report_rsp_id, EIO); pending_req_complete = true; } else DBG("Spurious HIDP_HSHK_ERR"); if (param == HIDP_HSHK_ERR_FATAL) hidp_send_ctrl_message(idev, HIDP_TRANS_HID_CONTROL | HIDP_CTRL_SOFT_RESET, NULL, 0); break; default: hidp_send_ctrl_message(idev, HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0); break; } if (pending_req_complete) { idev->report_req_pending = 0; if (idev->report_req_timer > 0) { timeout_remove(idev->report_req_timer); idev->report_req_timer = 0; } uhid_send_set_report_reply(idev, idev->report_rsp_id, 0); idev->report_rsp_id = 0; } } static void hidp_recv_ctrl_hid_control(struct input_device *idev, uint8_t param) { DBG(""); if (param == HIDP_CTRL_VIRTUAL_CABLE_UNPLUG) connection_disconnect(idev, (1 << HIDP_VIRTUAL_CABLE_UNPLUG)); } static void hidp_recv_ctrl_data(struct input_device *idev, uint8_t param, const uint8_t *data, size_t size) { uint8_t pending_req_type; uint8_t pending_req_param; DBG(""); pending_req_type = idev->report_req_pending & HIDP_HEADER_TRANS_MASK; if (pending_req_type != HIDP_TRANS_GET_REPORT && pending_req_type != HIDP_TRANS_SET_REPORT) { DBG("Spurious DATA on control channel"); return; } pending_req_param = idev->report_req_pending & HIDP_HEADER_PARAM_MASK; if (pending_req_param != param) { DBG("Received DATA RTYPE doesn't match pending request RTYPE"); return; } switch (param) { case HIDP_DATA_RTYPE_FEATURE: case HIDP_DATA_RTYPE_INPUT: case HIDP_DATA_RTYPE_OUTPUT: if (pending_req_type == HIDP_TRANS_GET_REPORT) uhid_send_get_report_reply(idev, data + 1, size - 1, idev->report_rsp_id, 0); else uhid_send_set_report_reply(idev, idev->report_rsp_id, 0); break; case HIDP_DATA_RTYPE_OTHER: DBG("Received DATA_RTYPE_OTHER"); break; default: hidp_send_ctrl_message(idev, HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0); break; } idev->report_req_pending = 0; if (idev->report_req_timer > 0) { timeout_remove(idev->report_req_timer); idev->report_req_timer = 0; } idev->report_rsp_id = 0; } static bool hidp_recv_ctrl_message(GIOChannel *chan, struct input_device *idev) { int fd; ssize_t len; uint8_t hdr, type, param; uint8_t data[UHID_DATA_MAX + 1]; fd = g_io_channel_unix_get_fd(chan); len = read(fd, data, sizeof(data)); if (len < 0) { error("BT socket read error: %s (%d)", strerror(errno), errno); return false; } if (len == 0) { DBG("BT socket read returned 0 bytes"); return true; } input_device_idle_reset(idev); hdr = data[0]; type = hdr & HIDP_HEADER_TRANS_MASK; param = hdr & HIDP_HEADER_PARAM_MASK; switch (type) { case HIDP_TRANS_HANDSHAKE: hidp_recv_ctrl_handshake(idev, param); break; case HIDP_TRANS_HID_CONTROL: hidp_recv_ctrl_hid_control(idev, param); break; case HIDP_TRANS_DATA: hidp_recv_ctrl_data(idev, param, data, len); break; default: error("unsupported HIDP control message"); hidp_send_ctrl_message(idev, HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_UNSUPPORTED_REQUEST, NULL, 0); break; } return true; } static gboolean ctrl_watch_cb(GIOChannel *chan, GIOCondition cond, gpointer data) { struct input_device *idev = data; char address[18]; if (cond & G_IO_IN) { if (hidp_recv_ctrl_message(chan, idev) && (cond == G_IO_IN)) return TRUE; } ba2str(&idev->dst, address); DBG("Device %s disconnected", address); /* Checking for intr_watch avoids a double g_io_channel_shutdown since * it's likely that intr_watch_cb has been queued for dispatching in * this mainloop iteration */ if ((cond & (G_IO_HUP | G_IO_ERR)) && idev->intr_watch) g_io_channel_shutdown(chan, TRUE, NULL); idev->ctrl_watch = 0; if (idev->ctrl_io) { g_io_channel_unref(idev->ctrl_io); idev->ctrl_io = NULL; } /* Close interrupt channel */ if (idev->intr_io && !(cond & G_IO_NVAL)) g_io_channel_shutdown(idev->intr_io, TRUE, NULL); /* It's possible this is triggered while the intr channel is not even * connected yet, therefore we are still in the connecting state. */ if (btd_service_get_state(idev->service) == BTD_SERVICE_STATE_CONNECTING) btd_service_connecting_complete(idev->service, -EIO); if (!idev->intr_io && idev->virtual_cable_unplug) virtual_cable_unplug(idev); return FALSE; } #define REPORT_REQ_TIMEOUT 3 static bool hidp_report_req_timeout(gpointer data) { struct input_device *idev = data; uint8_t pending_req_type; const char *req_type_str; char address[18]; ba2str(&idev->dst, address); pending_req_type = idev->report_req_pending & HIDP_HEADER_TRANS_MASK; switch (pending_req_type) { case HIDP_TRANS_GET_REPORT: req_type_str = "GET_REPORT"; uhid_send_get_report_reply(idev, NULL, 0, idev->report_rsp_id, ETIMEDOUT); break; case HIDP_TRANS_SET_REPORT: req_type_str = "SET_REPORT"; uhid_send_set_report_reply(idev, idev->report_rsp_id, ETIMEDOUT); break; default: /* Should never happen */ req_type_str = "OTHER_TRANS"; break; } error("Device %s HIDP %s request timed out", address, req_type_str); idev->report_req_pending = 0; idev->report_req_timer = 0; idev->report_rsp_id = 0; return FALSE; } static void hidp_send_output(struct uhid_event *ev, void *user_data) { struct input_device *idev = user_data; uint8_t hdr = HIDP_TRANS_DATA | HIDP_DATA_RTYPE_OUTPUT; bool sent; DBG(""); sent = hidp_send_intr_message(idev, hdr, ev->u.output.data, ev->u.output.size); if (!sent) uhid_disconnect(idev, true); } static void hidp_send_set_report(struct uhid_event *ev, void *user_data) { struct input_device *idev = user_data; uint8_t hdr; bool sent; DBG(""); switch (ev->u.set_report.rtype) { case UHID_FEATURE_REPORT: hdr = HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_FEATURE; break; case UHID_INPUT_REPORT: hdr = HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_INPUT; break; case UHID_OUTPUT_REPORT: hdr = HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_OUTPUT; break; default: DBG("Unsupported HID report type %u", ev->u.set_report.rtype); return; } if (idev->report_req_pending) { DBG("Old GET_REPORT or SET_REPORT still pending"); uhid_send_set_report_reply(idev, ev->u.set_report.id, EBUSY); return; } sent = hidp_send_ctrl_message(idev, hdr, ev->u.set_report.data, ev->u.set_report.size); if (sent) { idev->report_req_pending = hdr; idev->report_req_timer = timeout_add_seconds(REPORT_REQ_TIMEOUT, hidp_report_req_timeout, idev, NULL); idev->report_rsp_id = ev->u.set_report.id; } else { uhid_send_set_report_reply(idev, ev->u.set_report.id, EIO); /* Force UHID_DESTROY on error */ uhid_disconnect(idev, true); } } static void hidp_send_get_report(struct uhid_event *ev, void *user_data) { struct input_device *idev = user_data; uint8_t hdr; bool sent; DBG(""); if (idev->report_req_pending) { DBG("Old GET_REPORT or SET_REPORT still pending"); uhid_send_get_report_reply(idev, NULL, 0, ev->u.get_report.id, EBUSY); return; } /* Send GET_REPORT on control channel */ switch (ev->u.get_report.rtype) { case UHID_FEATURE_REPORT: hdr = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_FEATURE; break; case UHID_INPUT_REPORT: hdr = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_INPUT; break; case UHID_OUTPUT_REPORT: hdr = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_OUTPUT; break; default: DBG("Unsupported HID report type %u", ev->u.get_report.rtype); return; } sent = hidp_send_ctrl_message(idev, hdr, &ev->u.get_report.rnum, sizeof(ev->u.get_report.rnum)); if (sent) { idev->report_req_pending = hdr; idev->report_req_timer = timeout_add_seconds(REPORT_REQ_TIMEOUT, hidp_report_req_timeout, idev, NULL); idev->report_rsp_id = ev->u.get_report.id; } else { uhid_send_get_report_reply(idev, NULL, 0, ev->u.get_report.id, EIO); /* Force UHID_DESTROY on error */ uhid_disconnect(idev, true); } } static void epox_endian_quirk(unsigned char *data, int size) { /* USAGE_PAGE (Keyboard) 05 07 * USAGE_MINIMUM (0) 19 00 * USAGE_MAXIMUM (65280) 2A 00 FF <= must be FF 00 * LOGICAL_MINIMUM (0) 15 00 * LOGICAL_MAXIMUM (65280) 26 00 FF <= must be FF 00 */ unsigned char pattern[] = { 0x05, 0x07, 0x19, 0x00, 0x2a, 0x00, 0xff, 0x15, 0x00, 0x26, 0x00, 0xff }; unsigned int i; if (!data) return; for (i = 0; i < size - sizeof(pattern); i++) { if (!memcmp(data + i, pattern, sizeof(pattern))) { data[i + 5] = 0xff; data[i + 6] = 0x00; data[i + 10] = 0xff; data[i + 11] = 0x00; } } } static int create_hid_dev_name(const sdp_record_t *rec, struct hidp_connadd_req *req) { char sdesc[sizeof(req->name) / 2]; if (sdp_get_service_desc(rec, sdesc, sizeof(sdesc)) == 0) { char pname[sizeof(req->name) / 2]; if (sdp_get_provider_name(rec, pname, sizeof(pname)) == 0 && strncmp(sdesc, pname, 5) != 0) snprintf(req->name, sizeof(req->name), "%s %s", pname, sdesc); else snprintf(req->name, sizeof(req->name), "%s", sdesc); } else { return sdp_get_service_name(rec, req->name, sizeof(req->name)); } return 0; } /* See HID profile specification v1.0, "7.11.6 HIDDescriptorList" for details * on the attribute format. */ static int extract_hid_desc_data(const sdp_record_t *rec, struct hidp_connadd_req *req) { sdp_data_t *d; d = sdp_data_get(rec, SDP_ATTR_HID_DESCRIPTOR_LIST); if (!d) goto invalid_desc; if (!SDP_IS_SEQ(d->dtd)) goto invalid_desc; /* First HIDDescriptor */ d = d->val.dataseq; if (!SDP_IS_SEQ(d->dtd)) goto invalid_desc; /* ClassDescriptorType */ d = d->val.dataseq; if (d->dtd != SDP_UINT8) goto invalid_desc; /* ClassDescriptorData */ d = d->next; if (!d || !SDP_IS_TEXT_STR(d->dtd)) goto invalid_desc; /* * Report descriptor data is parsed by extract_str() which * will allocate N + 1 bytes for the incoming string to * include a zero delimiter. Since that zero delimiter isn't a * part of a report descriptor we adjust the size here to * account for that. */ req->rd_size = d->unitSize - 1; req->rd_data = g_try_malloc0(req->rd_size); if (req->rd_data) { memcpy(req->rd_data, d->val.str, req->rd_size); epox_endian_quirk(req->rd_data, req->rd_size); } return 0; invalid_desc: error("Missing or invalid HIDDescriptorList SDP attribute"); return -EINVAL; } static int extract_hid_record(struct input_device *idev, struct hidp_connadd_req *req) { sdp_data_t *pdlist; uint8_t attr_val; int err; if (!idev->rec) return -ENOENT; err = create_hid_dev_name(idev->rec, req); if (err < 0) DBG("No valid Service Name or Service Description found"); pdlist = sdp_data_get(idev->rec, SDP_ATTR_HID_PARSER_VERSION); req->parser = pdlist ? pdlist->val.uint16 : 0x0100; pdlist = sdp_data_get(idev->rec, SDP_ATTR_HID_DEVICE_SUBCLASS); req->subclass = pdlist ? pdlist->val.uint8 : 0; pdlist = sdp_data_get(idev->rec, SDP_ATTR_HID_COUNTRY_CODE); req->country = pdlist ? pdlist->val.uint8 : 0; pdlist = sdp_data_get(idev->rec, SDP_ATTR_HID_VIRTUAL_CABLE); attr_val = pdlist ? pdlist->val.uint8 : 0; if (attr_val) req->flags |= (1 << HIDP_VIRTUAL_CABLE_UNPLUG); pdlist = sdp_data_get(idev->rec, SDP_ATTR_HID_BOOT_DEVICE); attr_val = pdlist ? pdlist->val.uint8 : 0; if (attr_val) req->flags |= (1 << HIDP_BOOT_PROTOCOL_MODE); err = extract_hid_desc_data(idev->rec, req); if (err < 0) return err; return 0; } static int ioctl_connadd(struct hidp_connadd_req *req) { int ctl, err = 0; ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP); if (ctl < 0) return -errno; if (ioctl(ctl, HIDPCONNADD, req) < 0) err = -errno; close(ctl); return err; } static bool ioctl_is_connected(struct input_device *idev) { struct hidp_conninfo ci; int ctl; /* Standard HID */ ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP); if (ctl < 0) { error("Can't open HIDP control socket"); return false; } memset(&ci, 0, sizeof(ci)); bacpy(&ci.bdaddr, &idev->dst); if (ioctl(ctl, HIDPGETCONNINFO, &ci) < 0) { error("Can't get HIDP connection info"); close(ctl); return false; } close(ctl); if (ci.state != BT_CONNECTED) return false; return true; } static int ioctl_disconnect(struct input_device *idev, uint32_t flags) { struct hidp_conndel_req req; struct hidp_conninfo ci; int ctl, err = 0; ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP); if (ctl < 0) { error("Can't open HIDP control socket"); return -errno; } memset(&ci, 0, sizeof(ci)); bacpy(&ci.bdaddr, &idev->dst); if ((ioctl(ctl, HIDPGETCONNINFO, &ci) < 0) || (ci.state != BT_CONNECTED)) { close(ctl); return -ENOTCONN; } memset(&req, 0, sizeof(req)); bacpy(&req.bdaddr, &idev->dst); req.flags = flags; if (ioctl(ctl, HIDPCONNDEL, &req) < 0) { err = -errno; error("Can't delete the HID device: %s (%d)", strerror(-err), -err); } close(ctl); return err; } static int uhid_connadd(struct input_device *idev, struct hidp_connadd_req *req) { int err; if (bt_uhid_created(idev->uhid)) return bt_uhid_replay(idev->uhid); err = bt_uhid_create(idev->uhid, req->name, &idev->src, &idev->dst, req->vendor, req->product, req->version, req->country, idev->type, req->rd_data, req->rd_size); if (err < 0) { error("bt_uhid_create: %s", strerror(-err)); return err; } bt_uhid_register(idev->uhid, UHID_OUTPUT, hidp_send_output, idev); bt_uhid_register(idev->uhid, UHID_GET_REPORT, hidp_send_get_report, idev); bt_uhid_register(idev->uhid, UHID_SET_REPORT, hidp_send_set_report, idev); return err; } static gboolean encrypt_notify(GIOChannel *io, GIOCondition condition, gpointer data) { struct input_device *idev = data; int err; DBG(""); if (idev->uhid) err = uhid_connadd(idev, idev->req); else err = ioctl_connadd(idev->req); if (err < 0) { error("ioctl_connadd(): %s (%d)", strerror(-err), -err); if (idev->ctrl_io) { g_io_channel_shutdown(idev->ctrl_io, FALSE, NULL); g_io_channel_unref(idev->ctrl_io); idev->ctrl_io = NULL; } if (idev->intr_io) { g_io_channel_shutdown(idev->intr_io, FALSE, NULL); g_io_channel_unref(idev->intr_io); idev->intr_io = NULL; } } idev->sec_watch = 0; g_free(idev->req->rd_data); g_free(idev->req); idev->req = NULL; return FALSE; } static int hidp_add_connection(struct input_device *idev) { struct hidp_connadd_req *req; bool cable_pairing; GError *gerr = NULL; int err; req = g_new0(struct hidp_connadd_req, 1); req->ctrl_sock = g_io_channel_unix_get_fd(idev->ctrl_io); req->intr_sock = g_io_channel_unix_get_fd(idev->intr_io); req->flags = 0; req->idle_to = idle_timeout; err = extract_hid_record(idev, req); if (err < 0) { error("Could not parse HID SDP record: %s (%d)", strerror(-err), -err); goto cleanup; } req->vendor = btd_device_get_vendor(idev->device); req->product = btd_device_get_product(idev->device); req->version = btd_device_get_version(idev->device); if (device_name_known(idev->device)) device_get_name(idev->device, req->name, sizeof(req->name)); cable_pairing = device_is_cable_pairing(idev->device); /* Make sure the device is bonded if required */ if (!cable_pairing && classic_bonded_only && !input_device_bonded(idev)) { error("Rejected connection from !bonded device %s", idev->path); goto cleanup; } /* Encryption is mandatory for keyboards */ /* Some platforms may choose to require encryption for all devices */ /* Note that this only matters for pre 2.1 devices as otherwise the */ /* device is encrypted by default by the lower layers */ /* Don't enforce encryption for cable paired devices because they */ /* don't support it */ if (!cable_pairing && (classic_bonded_only || idev->type == BT_UHID_KEYBOARD)) { if (!bt_io_set(idev->intr_io, &gerr, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_INVALID)) { error("btio: %s", gerr->message); g_error_free(gerr); err = -EFAULT; goto cleanup; } idev->req = req; idev->sec_watch = g_io_add_watch(idev->intr_io, G_IO_OUT, encrypt_notify, idev); return 0; } if (idev->uhid) err = uhid_connadd(idev, req); else err = ioctl_connadd(req); cleanup: g_free(req->rd_data); g_free(req); return err; } static bool is_connected(struct input_device *idev) { if (idev->uhid) return (idev->intr_io != NULL && idev->ctrl_io != NULL); else return ioctl_is_connected(idev); } static int connection_disconnect(struct input_device *idev, uint32_t flags) { int sock; if (!is_connected(idev)) return -ENOTCONN; /* Standard HID disconnect * Intr channel must be disconnected before ctrl channel, so only * disconnect intr here, ctrl is disconnected in intr_watch_cb. */ if (idev->intr_io) { sock = g_io_channel_unix_get_fd(idev->intr_io); shutdown(sock, SHUT_WR); } if (flags & (1 << HIDP_VIRTUAL_CABLE_UNPLUG)) { idev->virtual_cable_unplug = true; if (idev->uhid) hidp_send_ctrl_message(idev, HIDP_TRANS_HID_CONTROL | HIDP_CTRL_VIRTUAL_CABLE_UNPLUG, NULL, 0); } if (idev->uhid) return uhid_disconnect(idev, false); else return ioctl_disconnect(idev, flags); } static bool is_device_sdp_disable(const sdp_record_t *rec) { sdp_data_t *data; data = sdp_data_get(rec, SDP_ATTR_HID_SDP_DISABLE); return data && data->val.uint8; } static enum reconnect_mode_t hid_reconnection_mode(bool reconnect_initiate, bool normally_connectable) { if (!reconnect_initiate && !normally_connectable) return RECONNECT_NONE; else if (!reconnect_initiate && normally_connectable) return RECONNECT_HOST; else if (reconnect_initiate && !normally_connectable) return RECONNECT_DEVICE; else /* (reconnect_initiate && normally_connectable) */ return RECONNECT_ANY; } static void extract_hid_props(struct input_device *idev, const sdp_record_t *rec) { /* Extract HID connectability */ bool reconnect_initiate, normally_connectable; sdp_data_t *pdlist; /* HIDNormallyConnectable is optional and assumed FALSE if not * present. */ pdlist = sdp_data_get(rec, SDP_ATTR_HID_RECONNECT_INITIATE); reconnect_initiate = pdlist ? pdlist->val.uint8 : TRUE; pdlist = sdp_data_get(rec, SDP_ATTR_HID_NORMALLY_CONNECTABLE); normally_connectable = pdlist ? pdlist->val.uint8 : FALSE; /* Update local values */ idev->reconnect_mode = hid_reconnection_mode(reconnect_initiate, normally_connectable); } static void input_device_update_rec(struct input_device *idev) { struct btd_profile *p = btd_service_get_profile(idev->service); const sdp_record_t *rec; rec = btd_device_get_record(idev->device, p->remote_uuid); if (!rec || idev->rec == rec) return; idev->rec = rec; idev->disable_sdp = is_device_sdp_disable(rec); /* Initialize device properties */ extract_hid_props(idev, rec); if (idev->disable_sdp) device_set_refresh_discovery(idev->device, false); } static int input_device_connected(struct input_device *idev) { int err; if (idev->intr_io == NULL || idev->ctrl_io == NULL) return -ENOTCONN; /* Attempt to update SDP record if it had changed */ input_device_update_rec(idev); err = hidp_add_connection(idev); if (err < 0) return err; btd_service_connecting_complete(idev->service, 0); return 0; } static void interrupt_connect_cb(GIOChannel *chan, GError *conn_err, gpointer user_data) { struct input_device *idev = user_data; GIOCondition cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL; int err; if (conn_err) { err = -EIO; goto failed; } err = input_device_connected(idev); if (err < 0) goto failed; if (idev->uhid) cond |= G_IO_IN; idev->intr_watch = g_io_add_watch(idev->intr_io, cond, intr_watch_cb, idev); return; failed: btd_service_connecting_complete(idev->service, err); /* So we guarantee the interrupt channel is closed before the * control channel (if we only do unref GLib will close it only * after returning control to the mainloop */ if (!conn_err) g_io_channel_shutdown(idev->intr_io, FALSE, NULL); g_io_channel_unref(idev->intr_io); idev->intr_io = NULL; if (idev->ctrl_io) { g_io_channel_unref(idev->ctrl_io); idev->ctrl_io = NULL; } } static void control_connect_cb(GIOChannel *chan, GError *conn_err, gpointer user_data) { struct input_device *idev = user_data; GIOCondition cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL; GIOChannel *io; GError *err = NULL; if (conn_err) { error("%s", conn_err->message); goto failed; } /* Connect to the HID interrupt channel */ io = bt_io_connect(interrupt_connect_cb, idev, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, &idev->src, BT_IO_OPT_DEST_BDADDR, &idev->dst, BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_INVALID); if (!io) { error("%s", err->message); g_error_free(err); goto failed; } idev->intr_io = io; if (idev->uhid) cond |= G_IO_IN; idev->ctrl_watch = g_io_add_watch(idev->ctrl_io, cond, ctrl_watch_cb, idev); return; failed: btd_service_connecting_complete(idev->service, -EIO); g_io_channel_unref(idev->ctrl_io); idev->ctrl_io = NULL; } static int dev_connect(struct input_device *idev) { GError *err = NULL; GIOChannel *io; BtIOSecLevel sec_level; if (idev->disable_sdp) bt_clear_cached_session(&idev->src, &idev->dst); /* encrypt connection if device is bonded */ if (input_device_bonded(idev)) sec_level = BT_IO_SEC_MEDIUM; else sec_level = BT_IO_SEC_LOW; io = bt_io_connect(control_connect_cb, idev, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, &idev->src, BT_IO_OPT_DEST_BDADDR, &idev->dst, BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL, BT_IO_OPT_SEC_LEVEL, sec_level, BT_IO_OPT_INVALID); idev->ctrl_io = io; if (err == NULL) return 0; error("%s", err->message); g_error_free(err); return -EIO; } static bool input_device_auto_reconnect(gpointer user_data) { struct input_device *idev = user_data; DBG("path=%s, attempt=%d", idev->path, idev->reconnect_attempt); /* Stop the recurrent reconnection attempts if the device is * reconnected or is marked for removal. */ if (device_is_temporary(idev->device) || btd_device_is_connected(idev->device)) goto bail; /* Only attempt an auto-reconnect for at most 3 minutes (6 * 30s). */ if (idev->reconnect_attempt >= 6) goto bail; /* Check if the profile is already connected. */ if (idev->ctrl_io) goto bail; if (is_connected(idev)) goto bail; idev->reconnect_attempt++; dev_connect(idev); return TRUE; bail: idev->reconnect_timer = 0; return FALSE; } static const char * const _reconnect_mode_str[] = { "none", "device", "host", "any" }; static const char *reconnect_mode_to_string(const enum reconnect_mode_t mode) { return _reconnect_mode_str[mode]; } static void input_device_enter_reconnect_mode(struct input_device *idev) { DBG("path=%s reconnect_mode=%s", idev->path, reconnect_mode_to_string(idev->reconnect_mode)); /* Make sure the device is bonded if required */ if (classic_bonded_only && !input_device_bonded(idev)) return; /* Only attempt an auto-reconnect when the device is required to * accept reconnections from the host. */ if (idev->reconnect_mode != RECONNECT_ANY && idev->reconnect_mode != RECONNECT_HOST) return; /* If the device is temporary we are not required to reconnect * with the device. This is likely the case of a removing device. */ if (device_is_temporary(idev->device) || btd_device_is_connected(idev->device)) return; if (idev->reconnect_timer > 0) timeout_remove(idev->reconnect_timer); DBG("registering auto-reconnect"); idev->reconnect_attempt = 0; idev->reconnect_timer = timeout_add_seconds(30, input_device_auto_reconnect, idev, NULL); } int input_device_connect(struct btd_service *service) { struct input_device *idev; DBG(""); idev = btd_service_get_user_data(service); if (idev->ctrl_io) return -EBUSY; if (is_connected(idev)) return -EALREADY; return dev_connect(idev); } int input_device_disconnect(struct btd_service *service) { struct input_device *idev; int err, flags; DBG(""); idev = btd_service_get_user_data(service); flags = device_is_temporary(idev->device) ? (1 << HIDP_VIRTUAL_CABLE_UNPLUG) : 0; err = connection_disconnect(idev, flags); if (err < 0) return err; return 0; } static struct input_device *input_device_new(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); const char *path = device_get_path(device); struct btd_adapter *adapter = device_get_adapter(device); struct input_device *idev; idev = g_new0(struct input_device, 1); bacpy(&idev->src, btd_adapter_get_address(adapter)); bacpy(&idev->dst, device_get_address(device)); idev->service = btd_service_ref(service); idev->device = btd_device_ref(device); idev->path = g_strdup(path); idev->type = bt_uhid_icon_to_type(btd_device_get_icon(device)); input_device_update_rec(idev); return idev; } static gboolean property_get_reconnect_mode( const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct input_device *idev = data; const char *str_mode = reconnect_mode_to_string(idev->reconnect_mode); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str_mode); return TRUE; } static const GDBusPropertyTable input_properties[] = { { "ReconnectMode", "s", property_get_reconnect_mode }, { } }; int input_device_register(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); const char *path = device_get_path(device); struct input_device *idev; DBG("%s", path); idev = input_device_new(service); if (!idev) return -EINVAL; if (uhid_state) { idev->uhid = bt_uhid_new_default(); if (!idev->uhid) { DBG("bt_uhid_new_default failed, switching to kernel " "mode"); uhid_state = UHID_DISABLED; } } if (g_dbus_register_interface(btd_get_dbus_connection(), idev->path, INPUT_INTERFACE, NULL, NULL, input_properties, idev, NULL) == FALSE) { error("Unable to register %s interface", INPUT_INTERFACE); input_device_free(idev); return -EINVAL; } btd_service_set_user_data(service, idev); device_set_wake_support(device, true); if (device_is_cable_pairing(device)) { struct btd_adapter *adapter = device_get_adapter(device); const bdaddr_t *bdaddr = btd_adapter_get_address(adapter); server_set_cable_pairing(bdaddr, true); } return 0; } static struct input_device *find_device(const bdaddr_t *src, const bdaddr_t *dst) { struct btd_device *device; struct btd_service *service; device = btd_adapter_find_device(adapter_find(src), dst, BDADDR_BREDR); if (device == NULL) return NULL; service = btd_device_get_service(device, HID_UUID); if (service == NULL) return NULL; return btd_service_get_user_data(service); } void input_device_unregister(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); const char *path = device_get_path(device); struct input_device *idev = btd_service_get_user_data(service); DBG("%s", path); g_dbus_unregister_interface(btd_get_dbus_connection(), idev->path, INPUT_INTERFACE); input_device_free(idev); } static int input_device_connadd(struct input_device *idev) { int err; err = input_device_connected(idev); if (err == 0) return 0; if (idev->ctrl_io) { g_io_channel_shutdown(idev->ctrl_io, FALSE, NULL); g_io_channel_unref(idev->ctrl_io); idev->ctrl_io = NULL; } if (idev->intr_io) { g_io_channel_shutdown(idev->intr_io, FALSE, NULL); g_io_channel_unref(idev->intr_io); idev->intr_io = NULL; } return err; } bool input_device_exists(const bdaddr_t *src, const bdaddr_t *dst) { if (find_device(src, dst)) return true; return false; } int input_device_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm, GIOChannel *io) { struct input_device *idev = find_device(src, dst); GIOCondition cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL; DBG("idev %p psm %d", idev, psm); if (!idev) return -ENOENT; if (uhid_state) cond |= G_IO_IN; switch (psm) { case L2CAP_PSM_HIDP_CTRL: if (idev->ctrl_io) return -EALREADY; idev->ctrl_io = g_io_channel_ref(io); idev->ctrl_watch = g_io_add_watch(idev->ctrl_io, cond, ctrl_watch_cb, idev); break; case L2CAP_PSM_HIDP_INTR: if (idev->intr_io) return -EALREADY; idev->intr_io = g_io_channel_ref(io); idev->intr_watch = g_io_add_watch(idev->intr_io, cond, intr_watch_cb, idev); break; } if (idev->intr_io && idev->ctrl_io) input_device_connadd(idev); return 0; } int input_device_close_channels(const bdaddr_t *src, const bdaddr_t *dst) { struct input_device *idev = find_device(src, dst); if (!idev) return -ENOENT; if (idev->intr_io) g_io_channel_shutdown(idev->intr_io, TRUE, NULL); if (idev->ctrl_io) g_io_channel_shutdown(idev->ctrl_io, TRUE, NULL); return 0; } bluez-5.87/profiles/input/PaxHeaders/hog.c0000644000000000000000000000005015142177620015563 xustar0020 atime=1783093674 20 ctime=1783094105 bluez-5.87/profiles/input/hog.c0000644000000000000000000001464715142177620015260 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012 Marcel Holtmann * Copyright (C) 2012 Nordic Semiconductor Inc. * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/log.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/shared/util.h" #include "src/shared/uhid.h" #include "src/shared/queue.h" #include "src/shared/att.h" #include "src/shared/gatt-client.h" #include "src/plugin.h" #include "suspend.h" #include "attrib/att.h" #include "attrib/gattrib.h" #include "attrib/gatt.h" #include "hog-lib.h" struct hog_device { struct btd_device *device; struct bt_hog *hog; uint8_t type; }; static gboolean suspend_supported = FALSE; static bool auto_sec = true; static bool uhid_state_persist = false; static struct queue *devices = NULL; static void hog_device_accept(struct hog_device *dev, struct gatt_db *db) { char name[248]; uint16_t vendor, product, version, type; if (dev->hog) return; if (device_name_known(dev->device)) device_get_name(dev->device, name, sizeof(name)); else strcpy(name, "bluez-hog-device"); vendor = btd_device_get_vendor(dev->device); product = btd_device_get_product(dev->device); version = btd_device_get_version(dev->device); type = bt_uhid_icon_to_type(btd_device_get_icon(dev->device)); DBG("name=%s vendor=0x%X, product=0x%X, version=0x%X", name, vendor, product, version); dev->hog = bt_hog_new_default(name, vendor, product, version, type, db); } static struct hog_device *hog_device_new(struct btd_device *device) { struct hog_device *dev; dev = new0(struct hog_device, 1); dev->device = btd_device_ref(device); if (!devices) devices = queue_new(); queue_push_tail(devices, dev); return dev; } static void hog_device_free(void *data) { struct hog_device *dev = data; queue_remove(devices, dev); if (queue_isempty(devices)) { queue_destroy(devices, NULL); devices = NULL; } btd_device_unref(dev->device); bt_hog_unref(dev->hog); free(dev); } static void set_suspend(gpointer data, gpointer user_data) { struct hog_device *dev = data; gboolean suspend = GPOINTER_TO_INT(user_data); bt_hog_set_control_point(dev->hog, suspend); } static void suspend_callback(void) { gboolean suspend = TRUE; DBG("Suspending ..."); queue_foreach(devices, set_suspend, GINT_TO_POINTER(suspend)); } static void resume_callback(void) { gboolean suspend = FALSE; DBG("Resuming ..."); queue_foreach(devices, set_suspend, GINT_TO_POINTER(suspend)); } static int hog_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); const char *path = device_get_path(device); struct hog_device *dev; DBG("path %s", path); dev = hog_device_new(device); if (!dev) return -EINVAL; btd_service_set_user_data(service, dev); device_set_wake_support(device, true); return 0; } static void hog_remove(struct btd_service *service) { struct hog_device *dev = btd_service_get_user_data(service); struct btd_device *device = btd_service_get_device(service); const char *path = device_get_path(device); DBG("path %s", path); hog_device_free(dev); } static int hog_accept(struct btd_service *service) { struct hog_device *dev = btd_service_get_user_data(service); struct btd_device *device = btd_service_get_device(service); struct gatt_db *db = btd_device_get_gatt_db(device); GAttrib *attrib = btd_device_get_attrib(device); if (!dev->hog) { hog_device_accept(dev, db); if (!dev->hog) return -EINVAL; } /* HOGP 1.0 Section 6.1 requires bonding */ if (!device_is_bonded(device, btd_device_get_bdaddr_type(device))) { struct bt_gatt_client *client; if (!auto_sec) return -ECONNREFUSED; client = btd_device_get_gatt_client(device); if (!bt_gatt_client_set_security(client, BT_ATT_SECURITY_MEDIUM)) return -ECONNREFUSED; } /* TODO: Replace GAttrib with bt_gatt_client */ bt_hog_attach(dev->hog, attrib); btd_service_connecting_complete(service, 0); return 0; } static int hog_disconnect(struct btd_service *service) { struct hog_device *dev = btd_service_get_user_data(service); if (uhid_state_persist) bt_hog_detach(dev->hog, false); else bt_hog_detach(dev->hog, true); btd_service_disconnecting_complete(service, 0); return 0; } static struct btd_profile hog_profile = { .name = "input-hog", .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = HOG_UUID, .device_probe = hog_probe, .device_remove = hog_remove, .accept = hog_accept, .disconnect = hog_disconnect, .auto_connect = true, }; static void hog_read_config(void) { const char filename[] = CONFIGDIR "/input.conf"; GKeyFile *config; GError *err = NULL; bool config_auto_sec; char *uhid_enabled; config = g_key_file_new(); if (!config) { error("Failed to allocate memory for config"); return; } if (!g_key_file_load_from_file(config, filename, 0, &err)) { if (!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT)) error("Parsing %s failed: %s", filename, err->message); g_error_free(err); g_key_file_free(config); return; } config_auto_sec = g_key_file_get_boolean(config, "General", "LEAutoSecurity", &err); if (!err) { DBG("input.conf: LEAutoSecurity=%s", config_auto_sec ? "true" : "false"); auto_sec = config_auto_sec; } else g_clear_error(&err); uhid_enabled = g_key_file_get_string(config, "General", "UserspaceHID", &err); if (!err) { DBG("input.conf: UserspaceHID=%s", uhid_enabled); uhid_state_persist = strcasecmp(uhid_enabled, "persist") == 0; g_free(uhid_enabled); } else g_clear_error(&err); g_key_file_free(config); } static int hog_init(void) { int err; hog_read_config(); err = suspend_init(suspend_callback, resume_callback); if (err < 0) error("Loading suspend plugin failed: %s (%d)", strerror(-err), -err); else suspend_supported = TRUE; return btd_profile_register(&hog_profile); } static void hog_exit(void) { if (suspend_supported) suspend_exit(); btd_profile_unregister(&hog_profile); } BLUETOOTH_PLUGIN_DEFINE(hog, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, hog_init, hog_exit) bluez-5.87/profiles/input/PaxHeaders/input.conf0000644000000000000000000000005014711225434016646 xustar0020 atime=1770587295 20 ctime=1783094111 bluez-5.87/profiles/input/input.conf0000644000000000000000000000200414711225434016323 0ustar00rootroot# Configuration file for the input service # This section contains options which are not specific to any # particular interface [General] # Set idle timeout (in seconds) before the connection will be disconnect and # the input device is removed. # Defaults: 0 (disabled) #IdleTimeout=0 # Enable HID protocol handling in userspace input profile # Possible values: # - persist: Use UHID in persistent mode (keyboard only) # - true: Use UHID instead # - false: User kernel HIDP # Defaults to true #UserspaceHID=true # Limit HID connections to bonded devices # The HID Profile does not specify that devices must be bonded, however some # platforms may want to make sure that input connections only come from bonded # device connections. Several older mice have been known for not supporting # pairing/encryption. # Defaults to true for security. #ClassicBondedOnly=true # LE upgrade security # Enables upgrades of security automatically if required. # Defaults to true to maximize device compatibility. #LEAutoSecurity=true bluez-5.87/profiles/input/PaxHeaders/device.h0000644000000000000000000000005015017252326016250 xustar0020 atime=1783093668 20 ctime=1783094105 bluez-5.87/profiles/input/device.h0000644000000000000000000000200015017252326015721 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #define L2CAP_PSM_HIDP_CTRL 0x11 #define L2CAP_PSM_HIDP_INTR 0x13 typedef enum { UHID_DISABLED = 0, UHID_ENABLED, UHID_PERSIST } uhid_state_t; struct input_device; struct input_conn; void input_set_idle_timeout(int timeout); void input_set_userspace_hid(char *state); void input_set_classic_bonded_only(bool state); bool input_get_classic_bonded_only(void); int input_device_register(struct btd_service *service); void input_device_unregister(struct btd_service *service); bool input_device_exists(const bdaddr_t *src, const bdaddr_t *dst); int input_device_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm, GIOChannel *io); int input_device_close_channels(const bdaddr_t *src, const bdaddr_t *dst); int input_device_connect(struct btd_service *service); int input_device_disconnect(struct btd_service *service); bluez-5.87/profiles/input/PaxHeaders/hidp_defs.h0000644000000000000000000000005014015011623016724 xustar0020 atime=1783093671 20 ctime=1783094105 bluez-5.87/profiles/input/hidp_defs.h0000644000000000000000000000356214015011623016413 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2003-2014 Marcel Holtmann * Copyright (C) 2014 Google Inc. * * */ #ifndef __HIDP_DEFS_H #define __HIDP_DEFS_H /* HIDP header masks */ #define HIDP_HEADER_TRANS_MASK 0xf0 #define HIDP_HEADER_PARAM_MASK 0x0f /* HIDP transaction types */ #define HIDP_TRANS_HANDSHAKE 0x00 #define HIDP_TRANS_HID_CONTROL 0x10 #define HIDP_TRANS_GET_REPORT 0x40 #define HIDP_TRANS_SET_REPORT 0x50 #define HIDP_TRANS_GET_PROTOCOL 0x60 #define HIDP_TRANS_SET_PROTOCOL 0x70 #define HIDP_TRANS_GET_IDLE 0x80 #define HIDP_TRANS_SET_IDLE 0x90 #define HIDP_TRANS_DATA 0xa0 #define HIDP_TRANS_DATC 0xb0 /* HIDP handshake results */ #define HIDP_HSHK_SUCCESSFUL 0x00 #define HIDP_HSHK_NOT_READY 0x01 #define HIDP_HSHK_ERR_INVALID_REPORT_ID 0x02 #define HIDP_HSHK_ERR_UNSUPPORTED_REQUEST 0x03 #define HIDP_HSHK_ERR_INVALID_PARAMETER 0x04 #define HIDP_HSHK_ERR_UNKNOWN 0x0e #define HIDP_HSHK_ERR_FATAL 0x0f /* HIDP control operation parameters */ #define HIDP_CTRL_NOP 0x00 #define HIDP_CTRL_HARD_RESET 0x01 #define HIDP_CTRL_SOFT_RESET 0x02 #define HIDP_CTRL_SUSPEND 0x03 #define HIDP_CTRL_EXIT_SUSPEND 0x04 #define HIDP_CTRL_VIRTUAL_CABLE_UNPLUG 0x05 /* HIDP data transaction headers */ #define HIDP_DATA_RTYPE_MASK 0x03 #define HIDP_DATA_RSRVD_MASK 0x0c #define HIDP_DATA_RTYPE_OTHER 0x00 #define HIDP_DATA_RTYPE_INPUT 0x01 #define HIDP_DATA_RTYPE_OUTPUT 0x02 #define HIDP_DATA_RTYPE_FEATURE 0x03 /* HIDP protocol header parameters */ #define HIDP_PROTO_BOOT 0x00 #define HIDP_PROTO_REPORT 0x01 #define HIDP_VIRTUAL_CABLE_UNPLUG 0 #define HIDP_BOOT_PROTOCOL_MODE 1 #define HIDP_BLUETOOTH_VENDOR_ID 9 #define HIDP_WAITING_FOR_RETURN 10 #define HIDP_WAITING_FOR_SEND_ACK 11 #endif /* __HIDP_DEFS_H */ bluez-5.87/profiles/input/PaxHeaders/manager.c0000644000000000000000000000005015221752714016421 xustar0020 atime=1783092685 20 ctime=1783094105 bluez-5.87/profiles/input/manager.c0000644000000000000000000000567415221752714016116 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "bluetooth/uuid.h" #include "src/log.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "device.h" #include "server.h" static int hid_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { return server_start(btd_adapter_get_address(adapter), btd_adapter_has_cable_pairing_devices(adapter)); } static void hid_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { server_stop(btd_adapter_get_address(adapter)); } static struct btd_profile input_profile = { .name = "input-hid", .bearer = BTD_PROFILE_BEARER_BREDR, .local_uuid = HID_UUID, .remote_uuid = HID_UUID, .auto_connect = true, .connect = input_device_connect, .disconnect = input_device_disconnect, .device_probe = input_device_register, .device_remove = input_device_unregister, .adapter_probe = hid_server_probe, .adapter_remove = hid_server_remove, }; static GKeyFile *load_config_file(const char *file) { GKeyFile *keyfile; GError *err = NULL; keyfile = g_key_file_new(); if (!g_key_file_load_from_file(keyfile, file, 0, &err)) { if (!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT)) error("Parsing %s failed: %s", file, err->message); g_error_free(err); g_key_file_free(keyfile); return NULL; } return keyfile; } static int input_init(void) { GKeyFile *config; GError *err = NULL; config = load_config_file(CONFIGDIR "/input.conf"); if (config) { int idle_timeout; gboolean classic_bonded_only; char *uhid_enabled; idle_timeout = g_key_file_get_integer(config, "General", "IdleTimeout", &err); if (!err) { DBG("input.conf: IdleTimeout=%d", idle_timeout); input_set_idle_timeout(idle_timeout); } else g_clear_error(&err); uhid_enabled = g_key_file_get_string(config, "General", "UserspaceHID", &err); if (!err) { DBG("input.conf: UserspaceHID=%s", uhid_enabled); input_set_userspace_hid(uhid_enabled); g_free(uhid_enabled); } else g_clear_error(&err); classic_bonded_only = g_key_file_get_boolean(config, "General", "ClassicBondedOnly", &err); if (!err) { DBG("input.conf: ClassicBondedOnly=%s", classic_bonded_only ? "true" : "false"); input_set_classic_bonded_only(classic_bonded_only); } else g_clear_error(&err); } if (config) g_key_file_free(config); return btd_profile_register(&input_profile); } static void input_exit(void) { btd_profile_unregister(&input_profile); } BLUETOOTH_PLUGIN_DEFINE(input, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, input_init, input_exit) bluez-5.87/profiles/input/PaxHeaders/hog-lib.c0000644000000000000000000000005015110075565016327 xustar0020 atime=1783093675 20 ctime=1783094105 bluez-5.87/profiles/input/hog-lib.c0000644000000000000000000012141515110075565016014 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. * Copyright (C) 2012 Marcel Holtmann * Copyright (C) 2012 Nordic Semiconductor Inc. * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/shared/util.h" #include "src/shared/uhid.h" #include "src/shared/queue.h" #include "src/shared/att.h" #include "src/shared/gatt-db.h" #include "src/log.h" #include "attrib/att.h" #include "attrib/gattrib.h" #include "attrib/gatt.h" #include "btio/btio.h" #include "profiles/scanparam/scpp.h" #include "profiles/deviceinfo/dis.h" #include "profiles/battery/bas.h" #include "profiles/input/hog-lib.h" #define HOG_UUID16 0x1812 #define HOG_INFO_UUID 0x2A4A #define HOG_REPORT_MAP_UUID 0x2A4B #define HOG_REPORT_UUID 0x2A4D #define HOG_PROTO_MODE_UUID 0x2A4E #define HOG_CONTROL_POINT_UUID 0x2A4C #define HOG_REPORT_TYPE_INPUT 1 #define HOG_REPORT_TYPE_OUTPUT 2 #define HOG_REPORT_TYPE_FEATURE 3 #define HOG_PROTO_MODE_BOOT 0 #define HOG_PROTO_MODE_REPORT 1 #define HID_INFO_SIZE 4 #define ATT_NOTIFICATION_HEADER_SIZE 3 struct bt_hog { int ref_count; char *name; uint16_t vendor; uint16_t product; uint16_t version; uint8_t type; struct gatt_db_attribute *attr; struct gatt_primary *primary; GAttrib *attrib; GSList *reports; struct bt_uhid *uhid; int uhid_fd; uint64_t uhid_flags; uint16_t bcdhid; uint8_t bcountrycode; uint16_t proto_mode_handle; uint16_t ctrlpt_handle; uint8_t flags; unsigned int getrep_att; uint32_t getrep_id; unsigned int setrep_att; uint32_t setrep_id; unsigned int report_map_id; struct bt_scpp *scpp; struct bt_dis *dis; struct queue *bas; GSList *instances; struct queue *gatt_op; struct gatt_db *gatt_db; struct gatt_db_attribute *report_map_attr; }; struct report { struct bt_hog *hog; bool numbered; uint8_t id; uint8_t type; uint16_t handle; uint16_t value_handle; uint8_t properties; uint16_t ccc_handle; guint notifyid; uint16_t len; uint8_t *value; }; struct gatt_request { unsigned int id; struct bt_hog *hog; void *user_data; }; static struct gatt_request *create_request(struct bt_hog *hog, void *user_data) { struct gatt_request *req; req = new0(struct gatt_request, 1); if (!req) return NULL; req->user_data = user_data; req->hog = bt_hog_ref(hog); return req; } static bool set_and_store_gatt_req(struct bt_hog *hog, struct gatt_request *req, unsigned int id) { req->id = id; return queue_push_head(hog->gatt_op, req); } static void destroy_gatt_req(void *data) { struct gatt_request *req = data; bt_hog_unref(req->hog); free(req); } static void read_report_map(struct bt_hog *hog); static void remove_gatt_req(struct gatt_request *req, uint8_t status) { struct bt_hog *hog = req->hog; queue_remove(hog->gatt_op, req); if (!status && queue_isempty(hog->gatt_op)) { /* Report Map must be read last since that can result * in uhid being created and the driver may start to * use UHID_SET_REPORT which requires the report->id to * be known what attribute to send to. */ read_report_map(hog); } destroy_gatt_req(req); } static void write_char(struct bt_hog *hog, GAttrib *attrib, uint16_t handle, const uint8_t *value, size_t vlen, GAttribResultFunc func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(hog, user_data); if (!req) return; id = gatt_write_char(attrib, handle, value, vlen, func, req); if (!id) { error("hog: Could not write char"); return; } if (!set_and_store_gatt_req(hog, req, id)) { error("hog: Failed to queue write char req"); g_attrib_cancel(attrib, id); free(req); } } static unsigned int read_char(struct bt_hog *hog, GAttrib *attrib, uint16_t handle, GAttribResultFunc func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(hog, user_data); if (!req) return 0; id = gatt_read_char(attrib, handle, func, req); if (!id) { error("hog: Could not read char"); return 0; } if (!set_and_store_gatt_req(hog, req, id)) { error("hog: Failed to queue read char req"); g_attrib_cancel(attrib, id); free(req); return 0; } return id; } static void discover_desc(struct bt_hog *hog, GAttrib *attrib, uint16_t start, uint16_t end, gatt_cb_t func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(hog, user_data); if (!req) return; id = gatt_discover_desc(attrib, start, end, NULL, func, req); if (!id) { error("hog: Could not discover descriptors"); return; } if (!set_and_store_gatt_req(hog, req, id)) { error("hog: Failed to queue discover descriptors req"); g_attrib_cancel(attrib, id); free(req); } } static void discover_char(struct bt_hog *hog, GAttrib *attrib, uint16_t start, uint16_t end, bt_uuid_t *uuid, gatt_cb_t func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(hog, user_data); if (!req) return; id = gatt_discover_char(attrib, start, end, uuid, func, req); if (!id) { error("hog: Could not discover characteristic"); return; } if (!set_and_store_gatt_req(hog, req, id)) { error("hog: Failed to queue discover characteristic req"); g_attrib_cancel(attrib, id); free(req); } } static void discover_primary(struct bt_hog *hog, GAttrib *attrib, bt_uuid_t *uuid, gatt_cb_t func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(hog, user_data); if (!req) return; id = gatt_discover_primary(attrib, uuid, func, req); if (!id) { error("hog: Could not send discover primary"); return; } if (!set_and_store_gatt_req(hog, req, id)) { error("hog: Failed to queue discover primary req"); g_attrib_cancel(attrib, id); free(req); } } static void find_included(struct bt_hog *hog, GAttrib *attrib, uint16_t start, uint16_t end, gatt_cb_t func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(hog, user_data); if (!req) return; id = gatt_find_included(attrib, start, end, func, req); if (!id) { error("hog: Could not find included"); return; } if (!set_and_store_gatt_req(hog, req, id)) { error("hog: Failed to queue find included req"); g_attrib_cancel(attrib, id); free(req); } } static void report_value_cb(const guint8 *pdu, guint16 len, gpointer user_data) { struct report *report = user_data; struct bt_hog *hog = report->hog; int err; if (len < ATT_NOTIFICATION_HEADER_SIZE) { error("Malformed ATT notification"); return; } pdu += ATT_NOTIFICATION_HEADER_SIZE; len -= ATT_NOTIFICATION_HEADER_SIZE; err = bt_uhid_input(hog->uhid, report->numbered ? report->id : 0, pdu, len); if (err < 0) error("bt_uhid_input: %s (%d)", strerror(-err), -err); } static void report_notify_destroy(void *user_data) { struct report *report = user_data; DBG(""); report->notifyid = 0; } static void report_ccc_written_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { struct gatt_request *req = user_data; struct report *report = req->user_data; struct bt_hog *hog = report->hog; if (status != 0) { error("Write report characteristic descriptor failed: %s", att_ecode2str(status)); goto remove; } if (report->notifyid) goto remove; report->notifyid = g_attrib_register(hog->attrib, ATT_OP_HANDLE_NOTIFY, report->value_handle, report_value_cb, report, report_notify_destroy); if (!report->notifyid) { error("Unable to register report notification: handle 0x%04x", report->value_handle); goto remove; } DBG("Report characteristic descriptor written: notifications enabled"); remove: remove_gatt_req(req, status); } static void write_ccc(struct bt_hog *hog, GAttrib *attrib, uint16_t handle, void *user_data) { uint8_t value[2]; put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value); write_char(hog, attrib, handle, value, sizeof(value), report_ccc_written_cb, user_data); } static void ccc_read_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data) { struct gatt_request *req = user_data; struct report *report = req->user_data; if (status != 0) { error("Error reading CCC value: %s", att_ecode2str(status)); goto remove; } write_ccc(report->hog, report->hog->attrib, report->ccc_handle, report); remove: remove_gatt_req(req, status); } static const char *type_to_string(uint8_t type) { switch (type) { case HOG_REPORT_TYPE_INPUT: return "input"; case HOG_REPORT_TYPE_OUTPUT: return "output"; case HOG_REPORT_TYPE_FEATURE: return "feature"; } return NULL; } static void report_reference_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { struct gatt_request *req = user_data; struct report *report = req->user_data; if (status != 0) { error("Read Report Reference descriptor failed: %s", att_ecode2str(status)); goto remove; } if (plen != 3) { error("Malformed ATT read response"); goto remove; } report->id = pdu[1]; report->type = pdu[2]; DBG("Report 0x%04x: id 0x%02x type %s", report->value_handle, report->id, type_to_string(report->type)); /* Enable notifications only for Input Reports */ if (report->type == HOG_REPORT_TYPE_INPUT) read_char(report->hog, report->hog->attrib, report->ccc_handle, ccc_read_cb, report); remove: remove_gatt_req(req, status); } static void external_report_reference_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data); static void discover_external_cb(uint8_t status, GSList *descs, void *user_data) { struct gatt_request *req = user_data; struct bt_hog *hog = req->user_data; if (status != 0) { error("Discover external descriptors failed: %s", att_ecode2str(status)); goto remove; } for ( ; descs; descs = descs->next) { struct gatt_desc *desc = descs->data; read_char(hog, hog->attrib, desc->handle, external_report_reference_cb, hog); } remove: remove_gatt_req(req, status); } static void discover_external(struct bt_hog *hog, GAttrib *attrib, uint16_t start, uint16_t end, gpointer user_data) { bt_uuid_t uuid; if (start > end) return; bt_uuid16_create(&uuid, GATT_EXTERNAL_REPORT_REFERENCE); discover_desc(hog, attrib, start, end, discover_external_cb, user_data); } static void discover_report_cb(uint8_t status, GSList *descs, void *user_data) { struct gatt_request *req = user_data; struct report *report = req->user_data; struct bt_hog *hog = report->hog; if (status != 0) { error("Discover report descriptors failed: %s", att_ecode2str(status)); goto remove; } for ( ; descs; descs = descs->next) { struct gatt_desc *desc = descs->data; switch (desc->uuid16) { case GATT_CLIENT_CHARAC_CFG_UUID: report->ccc_handle = desc->handle; break; case GATT_REPORT_REFERENCE: read_char(hog, hog->attrib, desc->handle, report_reference_cb, report); break; } } remove: remove_gatt_req(req, status); } static void discover_report(struct bt_hog *hog, GAttrib *attrib, uint16_t start, uint16_t end, gpointer user_data) { if (start > end) return; discover_desc(hog, attrib, start, end, discover_report_cb, user_data); } static void report_read_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data) { struct gatt_request *req = user_data; struct report *report = req->user_data; if (status != 0) { error("Error reading Report value: %s", att_ecode2str(status)); goto remove; } if (report->value) free(report->value); report->value = util_memdup(pdu, len); report->len = len; remove: remove_gatt_req(req, status); } static int report_chrc_cmp(const void *data, const void *user_data) { const struct report *report = data; const struct gatt_char *decl = user_data; return report->handle - decl->handle; } static struct report *report_new(struct bt_hog *hog, struct gatt_char *chr) { struct report *report; GSList *l; if (!chr) return NULL; /* Skip if report already exists */ l = g_slist_find_custom(hog->reports, chr, report_chrc_cmp); if (l) return l->data; report = g_new0(struct report, 1); report->hog = hog; report->handle = chr->handle; report->value_handle = chr->value_handle; report->properties = chr->properties; hog->reports = g_slist_append(hog->reports, report); read_char(hog, hog->attrib, chr->value_handle, report_read_cb, report); return report; } static void external_service_char_cb(uint8_t status, GSList *chars, void *user_data) { struct gatt_request *req = user_data; struct bt_hog *hog = req->user_data; struct gatt_primary *primary = hog->primary; struct report *report; GSList *l; if (status != 0) { const char *str = att_ecode2str(status); DBG("Discover external service characteristic failed: %s", str); goto remove; } for (l = chars; l; l = g_slist_next(l)) { struct gatt_char *chr, *next; uint16_t start, end; chr = l->data; next = l->next ? l->next->data : NULL; if (!chr) continue; DBG("0x%04x UUID: %s properties: %02x", chr->handle, chr->uuid, chr->properties); report = report_new(hog, chr); start = chr->value_handle + 1; end = (next ? next->handle - 1 : primary->range.end); discover_report(hog, hog->attrib, start, end, report); } remove: remove_gatt_req(req, status); } static void external_report_reference_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { struct gatt_request *req = user_data; struct bt_hog *hog = req->user_data; uint16_t uuid16; bt_uuid_t uuid; if (status != 0) { error("Read External Report Reference descriptor failed: %s", att_ecode2str(status)); goto remove; } if (plen != 3) { error("Malformed ATT read response"); goto remove; } uuid16 = get_le16(&pdu[1]); DBG("External report reference read, external report characteristic " "UUID: 0x%04x", uuid16); /* Do not discover if is not a Report */ if (uuid16 != HOG_REPORT_UUID) goto remove; bt_uuid16_create(&uuid, uuid16); discover_char(hog, hog->attrib, 0x0001, 0xffff, &uuid, external_service_char_cb, hog); remove: remove_gatt_req(req, status); } static int report_cmp(gconstpointer a, gconstpointer b) { const struct report *ra = a, *rb = b; /* sort by type first.. */ if (ra->type != rb->type) return ra->type - rb->type; /* skip id check in case of reports not being numbered */ if (!ra->numbered && !rb->numbered) return 0; /* ..then by id */ return ra->id - rb->id; } static struct report *find_report(struct bt_hog *hog, uint8_t type, uint8_t id) { struct report cmp; GSList *l; memset(&cmp, 0, sizeof(cmp)); cmp.type = type; cmp.id = id; switch (type) { case HOG_REPORT_TYPE_FEATURE: if (hog->flags & UHID_DEV_NUMBERED_FEATURE_REPORTS) cmp.numbered = true; break; case HOG_REPORT_TYPE_OUTPUT: if (hog->flags & UHID_DEV_NUMBERED_OUTPUT_REPORTS) cmp.numbered = true; break; case HOG_REPORT_TYPE_INPUT: if (hog->flags & UHID_DEV_NUMBERED_INPUT_REPORTS) cmp.numbered = true; break; } l = g_slist_find_custom(hog->reports, &cmp, report_cmp); return l ? l->data : NULL; } static struct report *find_report_by_rtype(struct bt_hog *hog, uint8_t rtype, uint8_t id) { uint8_t type; switch (rtype) { case UHID_FEATURE_REPORT: type = HOG_REPORT_TYPE_FEATURE; break; case UHID_OUTPUT_REPORT: type = HOG_REPORT_TYPE_OUTPUT; break; case UHID_INPUT_REPORT: type = HOG_REPORT_TYPE_INPUT; break; default: return NULL; } return find_report(hog, type, id); } static void output_written_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { struct gatt_request *req = user_data; if (status != 0) error("Write output report failed: %s", att_ecode2str(status)); remove_gatt_req(req, status); } static void forward_report(struct uhid_event *ev, void *user_data) { struct bt_hog *hog = user_data; struct report *report; void *data; int size; report = find_report_by_rtype(hog, ev->u.output.rtype, ev->u.output.data[0]); if (!report) return; data = ev->u.output.data; size = ev->u.output.size; if (report->numbered && size > 0) { data++; --size; } DBG("Sending report type %d ID %d to handle 0x%X", report->type, report->id, report->value_handle); if (hog->attrib == NULL) return; if (report->properties & GATT_CHR_PROP_WRITE) write_char(hog, hog->attrib, report->value_handle, data, size, output_written_cb, hog); else if (report->properties & GATT_CHR_PROP_WRITE_WITHOUT_RESP) gatt_write_cmd(hog->attrib, report->value_handle, data, size, NULL, NULL); } static void set_numbered(void *data, void *user_data) { struct report *report = data; struct bt_hog *hog = user_data; switch (report->type) { case HOG_REPORT_TYPE_INPUT: if (hog->uhid_flags & UHID_DEV_NUMBERED_INPUT_REPORTS) report->numbered = true; break; case HOG_REPORT_TYPE_OUTPUT: if (hog->uhid_flags & UHID_DEV_NUMBERED_OUTPUT_REPORTS) report->numbered = true; break; case HOG_REPORT_TYPE_FEATURE: if (hog->uhid_flags & UHID_DEV_NUMBERED_FEATURE_REPORTS) report->numbered = true; break; } } static void start_flags(struct uhid_event *ev, void *user_data) { struct bt_hog *hog = user_data; hog->uhid_flags = ev->u.start.dev_flags; DBG("uHID device flags: 0x%16" PRIx64, hog->uhid_flags); if (hog->uhid_flags) g_slist_foreach(hog->reports, set_numbered, hog); } static void set_report_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { struct bt_hog *hog = user_data; int err; hog->setrep_att = 0; if (status != 0) error("Error setting Report value: %s", att_ecode2str(status)); err = bt_uhid_set_report_reply(hog->uhid, hog->setrep_id, status); if (err < 0) error("bt_uhid_set_report_reply: %s", strerror(-err)); } static void uhid_destroy(struct bt_hog *hog, bool force) { int err; if (!hog->uhid) return; bt_uhid_unregister_all(hog->uhid); err = bt_uhid_destroy(hog->uhid, force); if (err < 0) { error("bt_uhid_destroy: %s", strerror(-err)); return; } } static void set_report(struct uhid_event *ev, void *user_data) { struct bt_hog *hog = user_data; struct report *report; void *data; int size; int err; /* Destroy input device if there is an attempt to communicate with it * while disconnected. */ if (hog->attrib == NULL) { uhid_destroy(hog, true); return; } /* uhid never sends reqs in parallel; if there's a req, it timed out */ if (hog->setrep_att) { g_attrib_cancel(hog->attrib, hog->setrep_att); hog->setrep_att = 0; } hog->setrep_id = ev->u.set_report.id; report = find_report_by_rtype(hog, ev->u.set_report.rtype, ev->u.set_report.rnum); if (!report) { err = ENOTSUP; goto fail; } data = ev->u.set_report.data; size = ev->u.set_report.size; if (report->numbered && size > 0) { data++; --size; } DBG("Sending report type %d ID %d to handle 0x%X", report->type, report->id, report->value_handle); hog->setrep_att = gatt_write_char(hog->attrib, report->value_handle, data, size, set_report_cb, hog); if (!hog->setrep_att) { err = ENOMEM; goto fail; } return; fail: /* cancel the request on failure */ set_report_cb(err, NULL, 0, hog); } static void report_reply(struct bt_hog *hog, uint8_t status, uint8_t id, uint16_t len, const uint8_t *data) { int err; hog->getrep_att = 0; err = bt_uhid_get_report_reply(hog->uhid, hog->getrep_id, id, status, data, len); if (err < 0) error("bt_uhid_get_report_reply: %s", strerror(-err)); } static void get_report_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data) { struct report *report = user_data; struct bt_hog *hog = report->hog; if (status != 0) { error("Error reading Report value: %s", att_ecode2str(status)); goto exit; } if (len == 0) { error("Error reading Report, length %d", len); status = EIO; goto exit; } if (pdu[0] != 0x0b) { error("Error reading Report, invalid response: %02x", pdu[0]); status = EPROTO; goto exit; } --len; ++pdu; exit: report_reply(hog, status, report->numbered ? report->id : 0, len, pdu); } static void get_report(struct uhid_event *ev, void *user_data) { struct bt_hog *hog = user_data; struct report *report; guint8 err; /* Destroy input device if there is an attempt to communicate with it * while disconnected. */ if (hog->attrib == NULL) { uhid_destroy(hog, true); return; } /* uhid never sends reqs in parallel; if there's a req, it timed out */ if (hog->getrep_att) { g_attrib_cancel(hog->attrib, hog->getrep_att); hog->getrep_att = 0; } hog->getrep_id = ev->u.get_report.id; report = find_report_by_rtype(hog, ev->u.get_report.rtype, ev->u.get_report.rnum); if (!report) { err = ENOTSUP; goto fail; } hog->getrep_att = gatt_read_char(hog->attrib, report->value_handle, get_report_cb, report); if (!hog->getrep_att) { err = ENOMEM; goto fail; } return; fail: /* reply with an error on failure */ report_reply(hog, err, 0, 0, NULL); } static void uhid_create(struct bt_hog *hog, uint8_t *report_map, size_t report_map_len) { uint8_t *value = report_map; size_t vlen = report_map_len; int err; GError *gerr = NULL; bdaddr_t src, dst; bt_io_get(g_attrib_get_channel(hog->attrib), &gerr, BT_IO_OPT_SOURCE_BDADDR, &src, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_INVALID); if (gerr) { error("Failed to connection details: %s", gerr->message); g_error_free(gerr); return; } err = bt_uhid_create(hog->uhid, hog->name, &src, &dst, hog->vendor, hog->product, hog->version, hog->bcountrycode, hog->type, value, vlen); if (err < 0) { error("bt_uhid_create: %s", strerror(-err)); return; } bt_uhid_register(hog->uhid, UHID_START, start_flags, hog); bt_uhid_register(hog->uhid, UHID_OUTPUT, forward_report, hog); bt_uhid_register(hog->uhid, UHID_GET_REPORT, get_report, hog); bt_uhid_register(hog->uhid, UHID_SET_REPORT, set_report, hog); DBG("HoG created uHID device"); } static void db_report_map_write_value_cb(struct gatt_db_attribute *attr, int err, void *user_data) { if (err) error("Error writing report map value to gatt db"); } static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { struct gatt_request *req = user_data; struct bt_hog *hog = req->user_data; uint8_t *value; ssize_t vlen; remove_gatt_req(req, status); if (status != 0) { error("Report Map read failed: %s", att_ecode2str(status)); return; } value = new0(uint8_t, plen); vlen = dec_read_resp(pdu, plen, value, plen); if (vlen < 0) { error("ATT protocol error"); goto done; } uhid_create(hog, value, vlen); /* Cache the report map if gatt_db is available */ if (hog->report_map_attr) { gatt_db_attribute_write(hog->report_map_attr, 0, value, vlen, 0, NULL, db_report_map_write_value_cb, NULL); } done: free(value); } static void read_report_map(struct bt_hog *hog) { uint16_t handle; if (!hog->report_map_attr || bt_uhid_created(hog->uhid) || hog->report_map_id) return; handle = gatt_db_attribute_get_handle(hog->report_map_attr); hog->report_map_id = read_char(hog, hog->attrib, handle, report_map_read_cb, hog); } static void info_read_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { struct gatt_request *req = user_data; struct bt_hog *hog = req->user_data; uint8_t value[HID_INFO_SIZE]; ssize_t vlen; if (status != 0) { error("HID Information read failed: %s", att_ecode2str(status)); goto remove; } vlen = dec_read_resp(pdu, plen, value, sizeof(value)); if (vlen != 4) { error("ATT protocol error"); goto remove; } hog->bcdhid = get_le16(&value[0]); hog->bcountrycode = value[2]; hog->flags = value[3]; DBG("bcdHID: 0x%04X bCountryCode: 0x%02X Flags: 0x%02X", hog->bcdhid, hog->bcountrycode, hog->flags); remove: remove_gatt_req(req, status); } static void proto_mode_read_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { struct gatt_request *req = user_data; struct bt_hog *hog = req->user_data; uint8_t value; ssize_t vlen; if (status != 0) { error("Protocol Mode characteristic read failed: %s", att_ecode2str(status)); goto remove; } vlen = dec_read_resp(pdu, plen, &value, sizeof(value)); if (vlen < 0) { error("ATT protocol error"); goto remove; } if (value == HOG_PROTO_MODE_BOOT) { uint8_t nval = HOG_PROTO_MODE_REPORT; DBG("HoG is operating in Boot Protocol Mode"); gatt_write_cmd(hog->attrib, hog->proto_mode_handle, &nval, sizeof(nval), NULL, NULL); } else if (value == HOG_PROTO_MODE_REPORT) DBG("HoG is operating in Report Protocol Mode"); remove: remove_gatt_req(req, status); } static void char_discovered_cb(uint8_t status, GSList *chars, void *user_data) { struct gatt_request *req = user_data; struct bt_hog *hog = req->user_data; struct gatt_primary *primary = hog->primary; bt_uuid_t report_uuid, report_map_uuid, info_uuid; bt_uuid_t proto_mode_uuid, ctrlpt_uuid; struct report *report; GSList *l; uint16_t info_handle = 0, proto_mode_handle = 0; DBG("HoG inspecting characteristics"); if (status != 0) { DBG("Discover all characteristics failed: %s", att_ecode2str(status)); goto remove; } bt_uuid16_create(&report_uuid, HOG_REPORT_UUID); bt_uuid16_create(&report_map_uuid, HOG_REPORT_MAP_UUID); bt_uuid16_create(&info_uuid, HOG_INFO_UUID); bt_uuid16_create(&proto_mode_uuid, HOG_PROTO_MODE_UUID); bt_uuid16_create(&ctrlpt_uuid, HOG_CONTROL_POINT_UUID); for (l = chars; l; l = g_slist_next(l)) { struct gatt_char *chr, *next; bt_uuid_t uuid; uint16_t start, end; chr = l->data; next = l->next ? l->next->data : NULL; if (!chr) continue; DBG("0x%04x UUID: %s properties: %02x", chr->handle, chr->uuid, chr->properties); bt_string_to_uuid(&uuid, chr->uuid); start = chr->value_handle + 1; end = (next ? next->handle - 1 : primary->range.end); if (bt_uuid_cmp(&uuid, &report_uuid) == 0) { report = report_new(hog, chr); discover_report(hog, hog->attrib, start, end, report); } else if (bt_uuid_cmp(&uuid, &report_map_uuid) == 0) { DBG("HoG discovering report map"); read_char(hog, hog->attrib, chr->value_handle, report_map_read_cb, hog); discover_external(hog, hog->attrib, start, end, hog); } else if (bt_uuid_cmp(&uuid, &info_uuid) == 0) info_handle = chr->value_handle; else if (bt_uuid_cmp(&uuid, &proto_mode_uuid) == 0) proto_mode_handle = chr->value_handle; else if (bt_uuid_cmp(&uuid, &ctrlpt_uuid) == 0) hog->ctrlpt_handle = chr->value_handle; } if (proto_mode_handle) { hog->proto_mode_handle = proto_mode_handle; read_char(hog, hog->attrib, proto_mode_handle, proto_mode_read_cb, hog); } if (info_handle) read_char(hog, hog->attrib, info_handle, info_read_cb, hog); remove: remove_gatt_req(req, status); } static void report_free(void *data) { struct report *report = data; free(report->value); g_free(report); } static bool cancel_gatt_req(const void *data, const void *user_data) { struct gatt_request *req = (void *) data; const struct bt_hog *hog = user_data; return g_attrib_cancel(hog->attrib, req->id); } static void hog_free(void *data) { struct bt_hog *hog = data; bt_hog_detach(hog, true); uhid_destroy(hog, true); queue_destroy(hog->bas, (void *) bt_bas_unref); g_slist_free_full(hog->instances, hog_free); bt_scpp_unref(hog->scpp); bt_dis_unref(hog->dis); bt_uhid_unref(hog->uhid); g_slist_free_full(hog->reports, report_free); g_free(hog->name); free(hog->primary); queue_destroy(hog->gatt_op, (void *) destroy_gatt_req); if (hog->gatt_db) gatt_db_unref(hog->gatt_db); g_free(hog); } struct bt_hog *bt_hog_new_default(const char *name, uint16_t vendor, uint16_t product, uint16_t version, uint8_t type, struct gatt_db *db) { return bt_hog_new(-1, name, vendor, product, version, type, db); } static void foreach_hog_report(struct gatt_db_attribute *attr, void *user_data) { struct report *report = user_data; struct bt_hog *hog = report->hog; const bt_uuid_t *uuid; bt_uuid_t ref_uuid, ccc_uuid; uint16_t handle; handle = gatt_db_attribute_get_handle(attr); uuid = gatt_db_attribute_get_type(attr); bt_uuid16_create(&ref_uuid, GATT_REPORT_REFERENCE); if (!bt_uuid_cmp(&ref_uuid, uuid)) { read_char(hog, hog->attrib, handle, report_reference_cb, report); return; } bt_uuid16_create(&ccc_uuid, GATT_CLIENT_CHARAC_CFG_UUID); if (!bt_uuid_cmp(&ccc_uuid, uuid)) report->ccc_handle = handle; } static int report_attr_cmp(const void *data, const void *user_data) { const struct report *report = data; const struct gatt_db_attribute *attr = user_data; return report->handle - gatt_db_attribute_get_handle(attr); } static struct report *report_add(struct bt_hog *hog, struct gatt_db_attribute *attr) { struct report *report; GSList *l; /* Skip if report already exists */ l = g_slist_find_custom(hog->reports, attr, report_attr_cmp); if (l) return l->data; report = g_new0(struct report, 1); report->hog = hog; gatt_db_attribute_get_char_data(attr, &report->handle, &report->value_handle, &report->properties, NULL, NULL); hog->reports = g_slist_append(hog->reports, report); read_char(hog, hog->attrib, report->value_handle, report_read_cb, report); return report; } static void foreach_hog_external(struct gatt_db_attribute *attr, void *user_data) { struct bt_hog *hog = user_data; const bt_uuid_t *uuid; bt_uuid_t ext_uuid; uint16_t handle; handle = gatt_db_attribute_get_handle(attr); uuid = gatt_db_attribute_get_type(attr); bt_uuid16_create(&ext_uuid, GATT_EXTERNAL_REPORT_REFERENCE); if (!bt_uuid_cmp(&ext_uuid, uuid)) read_char(hog, hog->attrib, handle, external_report_reference_cb, hog); } static void db_report_map_read_value_cb(struct gatt_db_attribute *attrib, int err, const uint8_t *value, size_t length, void *user_data) { struct iovec *map = user_data; if (err) { error("Error reading report map from gatt db %s", strerror(-err)); return; } if (!length) return; map->iov_len = length; map->iov_base = (void *) value; } static void foreach_hog_chrc(struct gatt_db_attribute *attr, void *user_data) { struct bt_hog *hog = user_data; bt_uuid_t uuid, report_uuid, report_map_uuid, info_uuid; bt_uuid_t proto_mode_uuid, ctrlpt_uuid; uint16_t handle, value_handle; struct iovec map = {}; gatt_db_attribute_get_char_data(attr, &handle, &value_handle, NULL, NULL, &uuid); bt_uuid16_create(&report_uuid, HOG_REPORT_UUID); if (!bt_uuid_cmp(&report_uuid, &uuid)) { struct report *report = report_add(hog, attr); gatt_db_service_foreach_desc(attr, foreach_hog_report, report); return; } bt_uuid16_create(&report_map_uuid, HOG_REPORT_MAP_UUID); if (!bt_uuid_cmp(&report_map_uuid, &uuid)) { if (hog->gatt_db) { /* Try to read the cache of report map if available */ hog->report_map_attr = gatt_db_get_attribute( hog->gatt_db, value_handle); gatt_db_attribute_read(hog->report_map_attr, 0, BT_ATT_OP_READ_REQ, NULL, db_report_map_read_value_cb, &map); } if (map.iov_len) { /* Report map found in the cache, straight to creating * UHID to optimize reconnection. */ uhid_create(hog, map.iov_base, map.iov_len); } gatt_db_service_foreach_desc(attr, foreach_hog_external, hog); } bt_uuid16_create(&info_uuid, HOG_INFO_UUID); if (!bt_uuid_cmp(&info_uuid, &uuid)) { read_char(hog, hog->attrib, value_handle, info_read_cb, hog); return; } bt_uuid16_create(&proto_mode_uuid, HOG_PROTO_MODE_UUID); if (!bt_uuid_cmp(&proto_mode_uuid, &uuid)) { hog->proto_mode_handle = value_handle; read_char(hog, hog->attrib, value_handle, proto_mode_read_cb, hog); } bt_uuid16_create(&ctrlpt_uuid, HOG_CONTROL_POINT_UUID); if (!bt_uuid_cmp(&ctrlpt_uuid, &uuid)) hog->ctrlpt_handle = value_handle; } static struct bt_hog *hog_new(int fd, const char *name, uint16_t vendor, uint16_t product, uint16_t version, uint8_t type, struct gatt_db_attribute *attr) { struct bt_uhid *uhid; struct bt_hog *hog; if (fd < 0) uhid = bt_uhid_new_default(); else uhid = bt_uhid_new(fd); if (!uhid) { DBG("Unable to create UHID"); return NULL; } hog = g_try_new0(struct bt_hog, 1); if (!hog) return NULL; hog->gatt_op = queue_new(); hog->bas = queue_new(); hog->uhid_fd = fd; hog->uhid = uhid; if (!hog->gatt_op || !hog->bas) { hog_free(hog); return NULL; } hog->name = g_strdup(name); hog->vendor = vendor; hog->product = product; hog->version = version; hog->type = type; hog->attr = attr; return hog; } static void hog_attach_instance(struct bt_hog *hog, struct gatt_db_attribute *attr) { struct bt_hog *instance; if (!hog->attr) { hog->attr = attr; return; } instance = hog_new(hog->uhid_fd, hog->name, hog->vendor, hog->product, hog->version, hog->type, attr); if (!instance) return; instance->gatt_db = gatt_db_ref(hog->gatt_db); hog->instances = g_slist_append(hog->instances, bt_hog_ref(instance)); } static void foreach_hog_service(struct gatt_db_attribute *attr, void *user_data) { struct bt_hog *hog = user_data; hog_attach_instance(hog, attr); } static void dis_notify(uint8_t source, uint16_t vendor, uint16_t product, uint16_t version, void *user_data) { struct bt_hog *hog = user_data; GSList *l; hog->vendor = vendor; hog->product = product; hog->version = version; for (l = hog->instances; l; l = l->next) { struct bt_hog *instance = l->data; instance->vendor = vendor; instance->product = product; instance->version = version; } } struct bt_hog *bt_hog_new(int fd, const char *name, uint16_t vendor, uint16_t product, uint16_t version, uint8_t type, struct gatt_db *db) { struct bt_hog *hog; hog = hog_new(fd, name, vendor, product, version, type, NULL); if (!hog) return NULL; hog->gatt_db = gatt_db_ref(db); if (db) { bt_uuid_t uuid; /* Handle the HID services */ bt_uuid16_create(&uuid, HOG_UUID16); gatt_db_foreach_service(db, &uuid, foreach_hog_service, hog); if (!hog->attr) { hog_free(hog); return NULL; } /* Try creating a DIS instance in case pid/vid are not set */ if (!vendor && !product) { hog->dis = bt_dis_new(db); bt_dis_set_notification(hog->dis, dis_notify, hog); } } return bt_hog_ref(hog); } struct bt_hog *bt_hog_ref(struct bt_hog *hog) { if (!hog) return NULL; __sync_fetch_and_add(&hog->ref_count, 1); return hog; } void bt_hog_unref(struct bt_hog *hog) { if (!hog) return; if (__sync_sub_and_fetch(&hog->ref_count, 1)) return; hog_free(hog); } static void find_included_cb(uint8_t status, GSList *services, void *user_data) { struct gatt_request *req = user_data; GSList *l; DBG(""); if (status) { DBG("Find included failed: %s", att_ecode2str(status)); goto remove; } for (l = services; l; l = l->next) { struct gatt_included *include = l->data; DBG("included: handle %x, uuid %s", include->handle, include->uuid); } remove: remove_gatt_req(req, status); } static void hog_attach_scpp(struct bt_hog *hog, struct gatt_primary *primary) { if (hog->scpp) { bt_scpp_attach(hog->scpp, hog->attrib); return; } hog->scpp = bt_scpp_new(primary); if (hog->scpp) bt_scpp_attach(hog->scpp, hog->attrib); } static void hog_attach_dis(struct bt_hog *hog, struct gatt_primary *primary) { if (hog->dis) { bt_dis_attach(hog->dis, hog->attrib); return; } hog->dis = bt_dis_new_primary(primary); if (hog->dis) { bt_dis_set_notification(hog->dis, dis_notify, hog); bt_dis_attach(hog->dis, hog->attrib); } } static void hog_attach_bas(struct bt_hog *hog, struct gatt_primary *primary) { struct bt_bas *instance; instance = bt_bas_new(primary); bt_bas_attach(instance, hog->attrib); queue_push_head(hog->bas, instance); } static void hog_attach_hog(struct bt_hog *hog, struct gatt_primary *primary) { struct bt_hog *instance; if (!hog->primary) { hog->primary = util_memdup(primary, sizeof(*primary)); discover_char(hog, hog->attrib, primary->range.start, primary->range.end, NULL, char_discovered_cb, hog); find_included(hog, hog->attrib, primary->range.start, primary->range.end, find_included_cb, hog); return; } instance = bt_hog_new(hog->uhid_fd, hog->name, hog->vendor, hog->product, hog->version, hog->type, hog->gatt_db); if (!instance) return; instance->primary = util_memdup(primary, sizeof(*primary)); find_included(instance, hog->attrib, primary->range.start, primary->range.end, find_included_cb, instance); bt_hog_attach(instance, hog->attrib); hog->instances = g_slist_append(hog->instances, instance); } static void primary_cb(uint8_t status, GSList *services, void *user_data) { struct gatt_request *req = user_data; struct bt_hog *hog = req->user_data; struct gatt_primary *primary; GSList *l; DBG(""); if (status) { DBG("Discover primary failed: %s", att_ecode2str(status)); goto remove; } if (!services) { DBG("No primary service found"); goto remove; } for (l = services; l; l = l->next) { primary = l->data; if (strcmp(primary->uuid, SCAN_PARAMETERS_UUID) == 0) { hog_attach_scpp(hog, primary); continue; } if (strcmp(primary->uuid, DEVICE_INFORMATION_UUID) == 0) { hog_attach_dis(hog, primary); continue; } if (strcmp(primary->uuid, BATTERY_UUID) == 0) { hog_attach_bas(hog, primary); continue; } if (strcmp(primary->uuid, HOG_UUID) == 0) hog_attach_hog(hog, primary); } remove: remove_gatt_req(req, status); } bool bt_hog_attach(struct bt_hog *hog, void *gatt) { GSList *l; if (hog->attrib) return false; hog->attrib = g_attrib_ref(gatt); if (!hog->attr && !hog->primary) { discover_primary(hog, hog->attrib, NULL, primary_cb, hog); return true; } if (hog->scpp) bt_scpp_attach(hog->scpp, gatt); if (hog->dis) bt_dis_attach(hog->dis, gatt); queue_foreach(hog->bas, (void *) bt_bas_attach, gatt); for (l = hog->instances; l; l = l->next) { struct bt_hog *instance = l->data; bt_hog_attach(instance, gatt); } if (!bt_uhid_created(hog->uhid)) { DBG("HoG discovering characteristics"); if (hog->attr) gatt_db_service_foreach_char(hog->attr, foreach_hog_chrc, hog); else discover_char(hog, hog->attrib, hog->primary->range.start, hog->primary->range.end, NULL, char_discovered_cb, hog); } if (!bt_uhid_created(hog->uhid)) return true; /* If UHID is already created, set up the report value handlers to * optimize reconnection. */ for (l = hog->reports; l; l = l->next) { struct report *r = l->data; if (r->notifyid) continue; r->notifyid = g_attrib_register(hog->attrib, ATT_OP_HANDLE_NOTIFY, r->value_handle, report_value_cb, r, report_notify_destroy); if (!r->notifyid) error("Unable to register report notification: " "handle 0x%04x", r->value_handle); } /* Attempt to replay get/set report messages since the driver might not * be aware the device has been disconnected in the meantime. */ bt_uhid_replay(hog->uhid); return true; } void bt_hog_detach(struct bt_hog *hog, bool force) { GSList *l; if (!hog) return; if (!hog->attrib) goto done; queue_foreach(hog->bas, (void *) bt_bas_detach, NULL); for (l = hog->instances; l; l = l->next) { struct bt_hog *instance = l->data; bt_hog_detach(instance, force); } for (l = hog->reports; l; l = l->next) { struct report *r = l->data; if (r->notifyid > 0) { g_attrib_unregister(hog->attrib, r->notifyid); r->notifyid = 0; } } if (hog->scpp) bt_scpp_detach(hog->scpp); if (hog->dis) bt_dis_detach(hog->dis); queue_remove_all(hog->gatt_op, cancel_gatt_req, hog, destroy_gatt_req); g_attrib_unref(hog->attrib); hog->attrib = NULL; done: uhid_destroy(hog, force); } int bt_hog_set_control_point(struct bt_hog *hog, bool suspend) { uint8_t value = suspend ? 0x00 : 0x01; if (hog->attrib == NULL) return -ENOTCONN; if (hog->ctrlpt_handle == 0) return -ENOTSUP; gatt_write_cmd(hog->attrib, hog->ctrlpt_handle, &value, sizeof(value), NULL, NULL); return 0; } int bt_hog_send_report(struct bt_hog *hog, void *data, size_t size, int type) { struct report *report; GSList *l; if (!hog) return -EINVAL; if (!hog->attrib) return -ENOTCONN; report = find_report(hog, type, 0); if (!report) return -ENOTSUP; DBG("hog: Write report, handle 0x%X", report->value_handle); if (report->properties & GATT_CHR_PROP_WRITE) write_char(hog, hog->attrib, report->value_handle, data, size, output_written_cb, hog); if (report->properties & GATT_CHR_PROP_WRITE_WITHOUT_RESP) gatt_write_cmd(hog->attrib, report->value_handle, data, size, NULL, NULL); for (l = hog->instances; l; l = l->next) { struct bt_hog *instance = l->data; bt_hog_send_report(instance, data, size, type); } return 0; } bluez-5.87/profiles/input/PaxHeaders/server.h0000644000000000000000000000005015017252326016317 xustar0020 atime=1783093668 20 ctime=1783094105 bluez-5.87/profiles/input/server.h0000644000000000000000000000055515017252326016005 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ int server_start(const bdaddr_t *src, bool device_cable_pairing); int server_set_cable_pairing(const bdaddr_t *src, bool device_cable_pairing); void server_stop(const bdaddr_t *src); bluez-5.87/profiles/PaxHeaders/audio0000644000000000000000000000005015221755532014531 xustar0020 atime=1783094113 20 ctime=1783094106 bluez-5.87/profiles/audio/0000755000000000000000000000000015221755532014267 5ustar00rootrootbluez-5.87/profiles/audio/PaxHeaders/player.h0000644000000000000000000000005015017252326016247 xustar0020 atime=1783093623 20 ctime=1783094105 bluez-5.87/profiles/audio/player.h0000644000000000000000000001114715017252326015734 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2004-2009 Marcel Holtmann * Copyright (C) 2012-2012 Intel Corporation * * */ typedef enum { PLAYER_ITEM_TYPE_AUDIO, PLAYER_ITEM_TYPE_VIDEO, PLAYER_ITEM_TYPE_FOLDER, PLAYER_ITEM_TYPE_INVALID, } player_item_type_t; typedef enum { PLAYER_FOLDER_TYPE_MIXED, PLAYER_FOLDER_TYPE_TITLES, PLAYER_FOLDER_TYPE_ALBUMS, PLAYER_FOLDER_TYPE_ARTISTS, PLAYER_FOLDER_TYPE_GENRES, PLAYER_FOLDER_TYPE_PLAYLISTS, PLAYER_FOLDER_TYPE_YEARS, PLAYER_FOLDER_TYPE_INVALID, } player_folder_type_t; struct media_player; struct media_item; struct media_player_callback { bool (*set_setting)(struct media_player *mp, const char *key, const char *value, void *user_data); int (*play)(struct media_player *mp, void *user_data); int (*pause)(struct media_player *mp, void *user_data); int (*stop)(struct media_player *mp, void *user_data); int (*next)(struct media_player *mp, void *user_data); int (*previous)(struct media_player *mp, void *user_data); int (*fast_forward)(struct media_player *mp, void *user_data); int (*rewind)(struct media_player *mp, void *user_data); int (*press)(struct media_player *mp, uint8_t avc_key, void *user_data); int (*hold)(struct media_player *mp, uint8_t avc_key, void *user_data); int (*release)(struct media_player *mp, void *user_data); int (*list_items)(struct media_player *mp, const char *name, uint32_t start, uint32_t end, void *user_data); int (*change_folder)(struct media_player *mp, const char *path, uint64_t uid, void *user_data); int (*search)(struct media_player *mp, const char *string, void *user_data); int (*play_item)(struct media_player *mp, const char *name, uint64_t uid, void *user_data); int (*add_to_nowplaying)(struct media_player *mp, const char *name, uint64_t uid, void *user_data); int (*total_items)(struct media_player *mp, const char *name, void *user_data); }; struct media_player *media_player_controller_create(const char *path, const char *type, uint16_t id); const char *media_player_get_path(struct media_player *mp); void media_player_destroy(struct media_player *mp); void media_player_set_duration(struct media_player *mp, uint32_t duration); void media_player_set_position(struct media_player *mp, uint32_t position); void media_player_set_setting(struct media_player *mp, const char *key, const char *value); const char *media_player_get_status(struct media_player *mp); void media_player_set_status(struct media_player *mp, const char *status); void media_player_clear_metadata(struct media_player *mp); void media_player_set_metadata(struct media_player *mp, struct media_item *item, const char *key, void *data, size_t len); void media_player_metadata_changed(struct media_player *mp); void media_player_set_type(struct media_player *mp, const char *type); void media_player_set_subtype(struct media_player *mp, const char *subtype); void media_player_set_name(struct media_player *mp, const char *name); void media_player_set_browsable(struct media_player *mp, bool enabled); bool media_player_get_browsable(struct media_player *mp); void media_player_set_searchable(struct media_player *mp, bool enabled); void media_player_set_folder(struct media_player *mp, const char *path, uint32_t items); void media_player_set_playlist(struct media_player *mp, const char *name); struct media_item *media_player_set_playlist_item(struct media_player *mp, uint64_t uid); void media_player_clear_playlist(struct media_player *mp); void media_player_set_obex_port(struct media_player *mp, uint16_t port); struct media_item *media_player_create_folder(struct media_player *mp, const char *name, player_folder_type_t type, uint64_t uid); struct media_item *media_player_create_item(struct media_player *mp, const char *name, player_item_type_t type, uint64_t uid); void media_player_play_item_complete(struct media_player *mp, int err); void media_item_set_playable(struct media_item *item, bool value); void media_player_list_complete(struct media_player *mp, GSList *items, int err); void media_player_change_folder_complete(struct media_player *player, const char *path, uint64_t uid, int ret); void media_player_search_complete(struct media_player *mp, int ret); void media_player_total_items_complete(struct media_player *mp, uint32_t num_of_items); void media_player_set_callbacks(struct media_player *mp, const struct media_player_callback *cbs, void *user_data); bluez-5.87/profiles/audio/PaxHeaders/vcp.h0000644000000000000000000000005015142177620015545 xustar0020 atime=1783093618 20 ctime=1783094106 bluez-5.87/profiles/audio/vcp.h0000644000000000000000000000103115142177620015221 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2024 StreamUnlimited Engineering GmbH * * */ #ifdef HAVE_VCP int bt_audio_vcp_get_volume(struct btd_device *device); int bt_audio_vcp_set_volume(struct btd_device *device, uint8_t volume); #else static inline int bt_audio_vcp_get_volume(struct btd_device *device) { return -ENODEV; } static inline int bt_audio_vcp_set_volume(struct btd_device *device, uint8_t volume) { return -ENODEV; } #endif bluez-5.87/profiles/audio/PaxHeaders/media.c0000644000000000000000000000005015221752714016030 xustar0020 atime=1783093609 20 ctime=1783094105 bluez-5.87/profiles/audio/media.c0000644000000000000000000026436015221752714015524 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2004-2009 Marcel Holtmann * Copyright (C) 2011 BMW Car IT GmbH. All rights reserved. * Copyright 2023 NXP * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "bluetooth/mgmt.h" #include "gdbus/gdbus.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/device.h" #include "src/dbus-common.h" #include "src/profile.h" #include "src/service.h" #include "src/uuid-helper.h" #include "src/log.h" #include "src/error.h" #include "src/gatt-database.h" #include "src/shared/asha.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/att.h" #include "src/shared/bap.h" #include "src/shared/bap-debug.h" #include "src/shared/tmap.h" #include "src/shared/gmap.h" #ifdef HAVE_A2DP #include "avdtp.h" #endif #include "media.h" #include "transport.h" #ifdef HAVE_A2DP #include "a2dp.h" #endif #define MEDIA_INTERFACE "org.bluez.Media1" #define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1" #define MEDIA_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player" /* Timeout should be less than avdtp request timeout (4 seconds) */ #define A2DP_REQUEST_TIMEOUT_MSEC (3 * 1000) /* Timeout should be less than ATT timeout (30 seconds) */ #define BAP_REQUEST_TIMEOUT_MSEC (3 * 1000) struct media_app { struct media_adapter *adapter; GDBusClient *client; DBusMessage *reg; char *sender; /* Application bus id */ char *path; /* Application object path */ struct queue *proxies; /* Application proxies */ struct queue *endpoints; /* Application endpoints */ struct queue *players; /* Application players */ int err; }; struct media_adapter { struct btd_adapter *btd_adapter; struct queue *apps; /* Application list */ GSList *endpoints; /* Endpoints list */ GSList *players; /* Players list */ int so_timestamping; }; struct endpoint_request { struct media_endpoint *endpoint; struct media_transport *transport; DBusMessage *msg; DBusPendingCall *call; media_endpoint_cb_t cb; GDestroyNotify destroy; void *user_data; }; struct endpoint_features { uint8_t tmap_role; uint8_t gmap_role; uint32_t gmap_features; }; struct media_endpoint { #ifdef HAVE_A2DP struct a2dp_sep *sep; #endif struct bt_bap_pac *pac; struct bt_asha_device *asha; char *sender; /* Endpoint DBus bus id */ char *path; /* Endpoint object path */ char *uuid; /* Endpoint property UUID */ uint8_t codec; /* Endpoint codec */ uint16_t cid; /* Endpoint company ID */ uint16_t vid; /* Endpoint vendor codec ID */ bool delay_reporting;/* Endpoint delay_reporting */ struct bt_bap_pac_qos qos; /* Endpoint qos */ uint8_t *capabilities; /* Endpoint property capabilities */ size_t size; /* Endpoint capabilities size */ uint8_t *metadata; /* Endpoint property metadata */ size_t metadata_size; /* Endpoint metadata size */ guint hs_watch; guint ag_watch; guint watch; GSList *requests; struct media_adapter *adapter; GSList *transports; struct endpoint_features features; }; struct local_player { struct media_adapter *adapter; char *sender; /* Player DBus bus id */ char *path; /* Player object path */ GHashTable *settings; /* Player settings */ GHashTable *track; /* Player current track */ guint watch; guint properties_watch; guint seek_watch; char *status; uint32_t position; uint32_t duration; GTimer *timer; bool play; bool pause; bool next; bool previous; bool control; char *name; struct queue *cbs; }; struct local_player_watch { unsigned int id; local_player_added_t cb; void *user_data; }; struct local_player_cbs { struct local_player *mp; unsigned int id; const struct local_player_callback *cb; void *user_data; }; static GSList *adapters = NULL; static struct queue *local_player_watches; static void endpoint_request_free(struct endpoint_request *request) { if (request->call) dbus_pending_call_unref(request->call); if (request->destroy) request->destroy(request->user_data); dbus_message_unref(request->msg); g_free(request); } static void media_endpoint_cancel(struct endpoint_request *request) { struct media_endpoint *endpoint = request->endpoint; DBG("Canceling %s: name = %s path = %s", dbus_message_get_member(request->msg), dbus_message_get_destination(request->msg), dbus_message_get_path(request->msg)); if (request->call) dbus_pending_call_cancel(request->call); endpoint->requests = g_slist_remove(endpoint->requests, request); if (request->cb) request->cb(endpoint, NULL, -1, request->user_data); endpoint_request_free(request); } static void media_endpoint_cancel_all(struct media_endpoint *endpoint) { while (endpoint->requests != NULL) media_endpoint_cancel(endpoint->requests->data); } static void media_endpoint_destroy(struct media_endpoint *endpoint) { DBG("sender=%s path=%s", endpoint->sender, endpoint->path); media_endpoint_cancel_all(endpoint); g_slist_free_full(endpoint->transports, (GDestroyNotify) media_transport_destroy); endpoint->transports = NULL; if (endpoint->pac) { bt_bap_remove_pac(endpoint->pac); endpoint->pac = NULL; } g_dbus_remove_watch(btd_get_dbus_connection(), endpoint->watch); g_free(endpoint->capabilities); g_free(endpoint->metadata); g_free(endpoint->sender); g_free(endpoint->path); g_free(endpoint->uuid); g_free(endpoint); } static struct media_endpoint *media_adapter_find_endpoint( struct media_adapter *adapter, const char *sender, const char *path, const char *uuid) { GSList *l; for (l = adapter->endpoints; l; l = l->next) { struct media_endpoint *endpoint = l->data; if (sender && g_strcmp0(endpoint->sender, sender) != 0) continue; if (path && g_strcmp0(endpoint->path, path) != 0) continue; if (uuid && strcasecmp(endpoint->uuid, uuid) != 0) continue; return endpoint; } return NULL; } static void update_features(struct media_adapter *adapter) { struct endpoint_features all = { 0 }; GSList *list; struct btd_gatt_database *database; struct gatt_db *db; struct bt_tmap *tmap; struct bt_gmap *gmap; for (list = adapter->endpoints; list; list = list->next) { struct media_endpoint *endpoint = list->data; all.tmap_role |= endpoint->features.tmap_role; all.gmap_role |= endpoint->features.gmap_role; all.gmap_features |= endpoint->features.gmap_features; } database = btd_adapter_get_database(adapter->btd_adapter); db = btd_gatt_database_get_db(database); tmap = bt_tmap_find(db); gmap = bt_gmap_find(db); bt_tmap_set_role(tmap, all.tmap_role); bt_gmap_set_role(gmap, all.gmap_role); bt_gmap_set_features(gmap, all.gmap_features); } static void media_endpoint_remove(void *data) { struct media_endpoint *endpoint = data; struct media_adapter *adapter = endpoint->adapter; #ifdef HAVE_A2DP if (endpoint->sep) { a2dp_remove_sep(endpoint->sep); return; } #endif info("Endpoint unregistered: sender=%s path=%s", endpoint->sender, endpoint->path); adapter->endpoints = g_slist_remove(adapter->endpoints, endpoint); if (media_adapter_find_endpoint(adapter, NULL, NULL, endpoint->uuid) == NULL) btd_profile_remove_custom_prop(endpoint->uuid, "MediaEndpoints"); media_endpoint_destroy(endpoint); update_features(adapter); } static void media_endpoint_exit(DBusConnection *connection, void *user_data) { struct media_endpoint *endpoint = user_data; endpoint->watch = 0; media_endpoint_remove(endpoint); } static struct media_adapter *find_adapter(struct btd_device *device) { GSList *l; for (l = adapters; l; l = l->next) { struct media_adapter *adapter = l->data; if (adapter->btd_adapter == device_get_adapter(device)) return adapter; } return NULL; } static void endpoint_remove_transport(struct media_endpoint *endpoint, struct media_transport *transport) { if (!endpoint || !transport) return; endpoint->transports = g_slist_remove(endpoint->transports, transport); media_transport_destroy(transport); } static void clear_configuration(struct media_endpoint *endpoint, struct media_transport *transport) { DBusMessage *msg; const char *path; msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, MEDIA_ENDPOINT_INTERFACE, "ClearConfiguration"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); goto done; } path = media_transport_get_path(transport); dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); g_dbus_send_message(btd_get_dbus_connection(), msg); done: endpoint_remove_transport(endpoint, transport); } static void clear_endpoint(struct media_endpoint *endpoint) { media_endpoint_cancel_all(endpoint); while (endpoint->transports != NULL) clear_configuration(endpoint, endpoint->transports->data); } static void endpoint_reply(DBusPendingCall *call, void *user_data) { struct endpoint_request *request = user_data; struct media_endpoint *endpoint = request->endpoint; DBusMessage *reply; DBusMessageIter args, props; DBusError err; uint8_t error_code; void *ret = NULL; int size = -1; /* steal_reply will always return non-NULL since the callback * is only called after a reply has been received */ reply = dbus_pending_call_steal_reply(call); dbus_error_init(&err); if (dbus_set_error_from_message(&err, reply)) { error("Endpoint replied with an error: %s", err.name); /* Clear endpoint configuration in case of NO_REPLY error */ if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) { clear_endpoint(endpoint); dbus_message_unref(reply); dbus_error_free(&err); return; } #ifdef HAVE_A2DP if (dbus_message_is_method_call(request->msg, MEDIA_ENDPOINT_INTERFACE, "SetConfiguration")) { endpoint_remove_transport(endpoint, request->transport); error_code = a2dp_parse_config_error(err.name); ret = &error_code; size = 1; } #endif dbus_error_free(&err); goto done; } if (dbus_message_is_method_call(request->msg, MEDIA_ENDPOINT_INTERFACE, "SelectConfiguration")) { DBusMessageIter args, array; uint8_t *configuration; dbus_message_iter_init(reply, &args); dbus_message_iter_recurse(&args, &array); dbus_message_iter_get_fixed_array(&array, &configuration, &size); ret = configuration; goto done; } else if (dbus_message_is_method_call(request->msg, MEDIA_ENDPOINT_INTERFACE, "SelectProperties")) { dbus_message_iter_init(reply, &args); dbus_message_iter_recurse(&args, &props); ret = &props; goto done; } else if (!dbus_message_get_args(reply, &err, DBUS_TYPE_INVALID)) { error("Wrong reply signature: %s", err.message); dbus_error_free(&err); goto done; } size = 1; error_code = 0; ret = &error_code; done: dbus_message_unref(reply); if (request->cb) request->cb(endpoint, ret, size, request->user_data); endpoint->requests = g_slist_remove(endpoint->requests, request); endpoint_request_free(request); } static gboolean media_endpoint_async_call(DBusMessage *msg, struct media_endpoint *endpoint, struct media_transport *transport, media_endpoint_cb_t cb, void *user_data, GDestroyNotify destroy, int timeout_msec) { struct endpoint_request *request; request = g_new0(struct endpoint_request, 1); if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), msg, &request->call, timeout_msec) == FALSE) { error("D-Bus send failed"); g_free(request); return FALSE; } dbus_pending_call_set_notify(request->call, endpoint_reply, request, NULL); request->endpoint = endpoint; request->transport = transport; request->msg = msg; request->cb = cb; request->destroy = destroy; request->user_data = user_data; endpoint->requests = g_slist_append(endpoint->requests, request); DBG("Calling %s: name = %s path = %s", dbus_message_get_member(msg), dbus_message_get_destination(msg), dbus_message_get_path(msg)); return TRUE; } static gboolean select_configuration(struct media_endpoint *endpoint, uint8_t *capabilities, size_t length, media_endpoint_cb_t cb, void *user_data, GDestroyNotify destroy) { DBusMessage *msg; msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, MEDIA_ENDPOINT_INTERFACE, "SelectConfiguration"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); return FALSE; } dbus_message_append_args(msg, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &capabilities, length, DBUS_TYPE_INVALID); return media_endpoint_async_call(msg, endpoint, NULL, cb, user_data, destroy, -1); } static int transport_device_cmp(gconstpointer data, gconstpointer user_data) { struct media_transport *transport = (struct media_transport *) data; const struct btd_device *device = user_data; const struct btd_device *dev = media_transport_get_dev(transport); if (device == dev) return 0; return -1; } static struct media_transport *find_device_transport( struct media_endpoint *endpoint, struct btd_device *device) { GSList *match; match = g_slist_find_custom(endpoint->transports, device, transport_device_cmp); if (match == NULL) return NULL; return match->data; } #ifdef HAVE_A2DP struct a2dp_config_data { struct a2dp_setup *setup; a2dp_endpoint_config_t cb; }; static gboolean set_configuration(struct media_endpoint *endpoint, uint8_t *configuration, size_t size, media_endpoint_cb_t cb, void *user_data, GDestroyNotify destroy) { struct a2dp_config_data *data = user_data; struct btd_device *device = a2dp_setup_get_device(data->setup); DBusConnection *conn = btd_get_dbus_connection(); DBusMessage *msg; const char *path; DBusMessageIter iter; struct media_transport *transport; int8_t init_volume; transport = find_device_transport(endpoint, device); if (transport != NULL) return FALSE; transport = media_transport_create(device, a2dp_setup_remote_path(data->setup), configuration, size, endpoint, NULL); if (transport == NULL) return FALSE; init_volume = btd_device_get_volume(device); media_transport_set_a2dp_volume(device, init_volume); msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, MEDIA_ENDPOINT_INTERFACE, "SetConfiguration"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); media_transport_destroy(transport); return FALSE; } endpoint->transports = g_slist_append(endpoint->transports, transport); dbus_message_iter_init_append(msg, &iter); path = media_transport_get_path(transport); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); g_dbus_get_properties(conn, path, "org.bluez.MediaTransport1", &iter); return media_endpoint_async_call(msg, endpoint, transport, cb, user_data, destroy, A2DP_REQUEST_TIMEOUT_MSEC); } #endif static void release_endpoint(struct media_endpoint *endpoint) { DBusMessage *msg; DBG("sender=%s path=%s", endpoint->sender, endpoint->path); /* already exit */ if (endpoint->watch == 0) goto done; clear_endpoint(endpoint); msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, MEDIA_ENDPOINT_INTERFACE, "Release"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); return; } g_dbus_send_message(btd_get_dbus_connection(), msg); done: media_endpoint_remove(endpoint); } static const char *get_name(struct a2dp_sep *sep, void *user_data) { struct media_endpoint *endpoint = user_data; return endpoint->sender; } static const char *get_path(struct a2dp_sep *sep, void *user_data) { struct media_endpoint *endpoint = user_data; return endpoint->path; } static size_t get_capabilities(struct a2dp_sep *sep, uint8_t **capabilities, void *user_data) { struct media_endpoint *endpoint = user_data; *capabilities = endpoint->capabilities; return endpoint->size; } #ifdef HAVE_A2DP struct a2dp_select_data { struct a2dp_setup *setup; a2dp_endpoint_select_t cb; }; static void select_cb(struct media_endpoint *endpoint, void *ret, int size, void *user_data) { struct a2dp_select_data *data = user_data; data->cb(data->setup, ret, size); } static int select_config(struct a2dp_sep *sep, uint8_t *capabilities, size_t length, struct a2dp_setup *setup, a2dp_endpoint_select_t cb, void *user_data) { struct media_endpoint *endpoint = user_data; struct a2dp_select_data *data; data = g_new0(struct a2dp_select_data, 1); data->setup = setup; data->cb = cb; if (select_configuration(endpoint, capabilities, length, select_cb, data, g_free) == TRUE) return 0; g_free(data); return -ENOMEM; } static void config_cb(struct media_endpoint *endpoint, void *ret, int size, void *user_data) { struct a2dp_config_data *data = user_data; uint8_t *ret_value = ret; data->cb(data->setup, ret_value ? *ret_value : 1); } static int set_config(struct a2dp_sep *sep, uint8_t *configuration, size_t length, struct a2dp_setup *setup, a2dp_endpoint_config_t cb, void *user_data) { struct media_endpoint *endpoint = user_data; struct a2dp_config_data *data; data = g_new0(struct a2dp_config_data, 1); data->setup = setup; data->cb = cb; if (set_configuration(endpoint, configuration, length, config_cb, data, g_free) == TRUE) return 0; g_free(data); return -ENOMEM; } static void clear_config(struct a2dp_sep *sep, struct btd_device *device, void *user_data) { struct media_endpoint *endpoint = user_data; struct media_transport *transport; if (!device) { clear_endpoint(endpoint); return; } transport = find_device_transport(endpoint, device); if (!transport) return; clear_configuration(endpoint, transport); } static void set_delay(struct a2dp_sep *sep, uint16_t delay, void *user_data) { struct media_endpoint *endpoint = user_data; if (endpoint->transports == NULL) return; media_transport_update_delay(endpoint->transports->data, delay); } static struct a2dp_endpoint a2dp_endpoint = { .get_name = get_name, .get_path = get_path, .get_capabilities = get_capabilities, .select_configuration = select_config, .set_configuration = set_config, .clear_configuration = clear_config, .set_delay = set_delay }; static void a2dp_destroy_endpoint(void *user_data) { struct media_endpoint *endpoint = user_data; endpoint->sep = NULL; release_endpoint(endpoint); } static bool endpoint_init_a2dp_source(struct media_endpoint *endpoint, int *err) { endpoint->sep = a2dp_add_sep(endpoint->adapter->btd_adapter, AVDTP_SEP_TYPE_SOURCE, endpoint->codec, endpoint->delay_reporting, &a2dp_endpoint, endpoint, a2dp_destroy_endpoint, err); if (endpoint->sep == NULL) return false; return true; } static bool endpoint_init_a2dp_sink(struct media_endpoint *endpoint, int *err) { endpoint->sep = a2dp_add_sep(endpoint->adapter->btd_adapter, AVDTP_SEP_TYPE_SINK, endpoint->codec, endpoint->delay_reporting, &a2dp_endpoint, endpoint, a2dp_destroy_endpoint, err); if (endpoint->sep == NULL) return false; return true; } #endif struct pac_select_data { struct bt_bap_pac *pac; bt_bap_pac_select_t cb; void *user_data; }; static int parse_array(DBusMessageIter *iter, struct iovec *iov) { DBusMessageIter array; if (!iov) return 0; dbus_message_iter_recurse(iter, &array); dbus_message_iter_get_fixed_array(&array, &iov->iov_base, (int *)&iov->iov_len); return 0; } static int parse_ucast_qos(DBusMessageIter *iter, struct bt_bap_qos *qos) { DBusMessageIter array; const char *key; struct bt_bap_io_qos io_qos; dbus_message_iter_recurse(iter, &array); memset(&io_qos, 0, sizeof(io_qos)); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter value, entry; int var; dbus_message_iter_recurse(&array, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); var = dbus_message_iter_get_arg_type(&value); if (!strcasecmp(key, "CIG")) { if (var != DBUS_TYPE_BYTE) goto fail; dbus_message_iter_get_basic(&value, &qos->ucast.cig_id); } else if (!strcasecmp(key, "CIS")) { if (var != DBUS_TYPE_BYTE) goto fail; dbus_message_iter_get_basic(&value, &qos->ucast.cis_id); } else if (!strcasecmp(key, "Interval")) { if (var != DBUS_TYPE_UINT32) goto fail; dbus_message_iter_get_basic(&value, &io_qos.interval); } else if (!strcasecmp(key, "Framing")) { if (var != DBUS_TYPE_BYTE) goto fail; dbus_message_iter_get_basic(&value, &qos->ucast.framing); } else if (!strcasecmp(key, "PHY")) { if (var != DBUS_TYPE_BYTE) goto fail; dbus_message_iter_get_basic(&value, &io_qos.phys); } else if (!strcasecmp(key, "SDU")) { if (var != DBUS_TYPE_UINT16) goto fail; dbus_message_iter_get_basic(&value, &io_qos.sdu); } else if (!strcasecmp(key, "Retransmissions")) { if (var != DBUS_TYPE_BYTE) goto fail; dbus_message_iter_get_basic(&value, &io_qos.rtn); } else if (!strcasecmp(key, "Latency")) { if (var != DBUS_TYPE_UINT16) goto fail; dbus_message_iter_get_basic(&value, &io_qos.latency); } else if (!strcasecmp(key, "PresentationDelay")) { if (var != DBUS_TYPE_UINT32) goto fail; dbus_message_iter_get_basic(&value, &qos->ucast.delay); } else if (!strcasecmp(key, "TargetLatency")) { if (var != DBUS_TYPE_BYTE) goto fail; dbus_message_iter_get_basic(&value, &qos->ucast.target_latency); } dbus_message_iter_next(&array); } memcpy(&qos->ucast.io_qos, &io_qos, sizeof(io_qos)); return 0; fail: DBG("Failed parsing %s", key); return -EINVAL; } static int parse_select_properties(DBusMessageIter *props, struct iovec *caps, struct iovec *metadata, struct bt_bap_qos *qos) { const char *key; while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter value, entry; int var; dbus_message_iter_recurse(props, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); var = dbus_message_iter_get_arg_type(&value); if (!strcasecmp(key, "Capabilities")) { if (var != DBUS_TYPE_ARRAY) goto fail; if (parse_array(&value, caps)) goto fail; } else if (!strcasecmp(key, "Metadata")) { if (var != DBUS_TYPE_ARRAY) goto fail; if (parse_array(&value, metadata)) goto fail; } else if (!strcasecmp(key, "QoS")) { if (var != DBUS_TYPE_ARRAY) goto fail; if (parse_ucast_qos(&value, qos)) goto fail; } dbus_message_iter_next(props); } return 0; fail: DBG("Failed parsing %s", key); return -EINVAL; } static void pac_select_cb(struct media_endpoint *endpoint, void *ret, int size, void *user_data) { struct pac_select_data *data = user_data; DBusMessageIter *iter = ret; int err; struct iovec caps, meta; struct bt_bap_qos qos; if (!ret) { err = -EPERM; goto done; } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) { DBG("Unexpected argument type: %c != %c", dbus_message_iter_get_arg_type(iter), DBUS_TYPE_DICT_ENTRY); err = -EINVAL; goto done; } memset(&qos, 0, sizeof(qos)); /* Mark CIG and CIS to be auto assigned */ qos.ucast.cig_id = BT_ISO_QOS_CIG_UNSET; qos.ucast.cis_id = BT_ISO_QOS_CIS_UNSET; memset(&caps, 0, sizeof(caps)); memset(&meta, 0, sizeof(meta)); err = parse_select_properties(iter, &caps, &meta, &qos); if (err < 0) DBG("Unable to parse properties"); done: data->cb(data->pac, err, &caps, &meta, &qos, data->user_data); } static int pac_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, uint32_t location, struct bt_bap_pac_qos *qos, bt_bap_pac_select_t cb, void *cb_data, void *user_data) { struct media_endpoint *endpoint = user_data; struct iovec *caps; struct iovec *metadata; const char *endpoint_path; struct pac_select_data *data; DBusMessage *msg; DBusMessageIter iter, dict; const char *key = "Capabilities"; uint32_t loc; bt_bap_pac_get_codec(rpac, NULL, &caps, &metadata); if (!caps) return -EINVAL; msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, MEDIA_ENDPOINT_INTERFACE, "SelectProperties"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } data = new0(struct pac_select_data, 1); data->pac = lpac; data->cb = cb; data->user_data = cb_data; dbus_message_iter_init_append(msg, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict); endpoint_path = bt_bap_pac_get_user_data(rpac); if (endpoint_path) g_dbus_dict_append_entry(&dict, "Endpoint", DBUS_TYPE_OBJECT_PATH, &endpoint_path); g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &key, DBUS_TYPE_BYTE, &caps->iov_base, caps->iov_len); loc = bt_bap_pac_get_locations(rpac); if (loc) g_dbus_dict_append_entry(&dict, "Locations", DBUS_TYPE_UINT32, &loc); if (location) g_dbus_dict_append_entry(&dict, "ChannelAllocation", DBUS_TYPE_UINT32, &location); if (metadata) { key = "Metadata"; g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &key, DBUS_TYPE_BYTE, &metadata->iov_base, metadata->iov_len); } if (qos && qos->phys) { DBusMessageIter entry, variant, qos_dict; key = "QoS"; dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "a{sv}", &variant); dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "{sv}", &qos_dict); g_dbus_dict_append_entry(&qos_dict, "Framing", DBUS_TYPE_BYTE, &qos->framing); g_dbus_dict_append_entry(&qos_dict, "PHY", DBUS_TYPE_BYTE, &qos->phys); g_dbus_dict_append_entry(&qos_dict, "Retransmissions", DBUS_TYPE_BYTE, &qos->rtn); g_dbus_dict_append_entry(&qos_dict, "MaximumLatency", DBUS_TYPE_UINT16, &qos->latency); g_dbus_dict_append_entry(&qos_dict, "MinimumDelay", DBUS_TYPE_UINT32, &qos->pd_min); g_dbus_dict_append_entry(&qos_dict, "MaximumDelay", DBUS_TYPE_UINT32, &qos->pd_max); g_dbus_dict_append_entry(&qos_dict, "PreferredMinimumDelay", DBUS_TYPE_UINT32, &qos->ppd_min); g_dbus_dict_append_entry(&qos_dict, "PreferredMaximumDelay", DBUS_TYPE_UINT32, &qos->ppd_max); dbus_message_iter_close_container(&variant, &qos_dict); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); } dbus_message_iter_close_container(&iter, &dict); if (!media_endpoint_async_call(msg, endpoint, NULL, pac_select_cb, data, free, -1)) return -EIO; return 0; } static void pac_cancel_select(struct bt_bap_pac *lpac, bt_bap_pac_select_t cb, void *cb_data, void *user_data) { struct media_endpoint *endpoint = user_data; GSList *l = endpoint->requests; while (l) { struct endpoint_request *req = l->data; struct pac_select_data *data; if (req->cb != pac_select_cb) { l = g_slist_next(l); continue; } data = req->user_data; if (data->pac != lpac || data->cb != cb || data->user_data != cb_data) { l = g_slist_next(l); continue; } media_endpoint_cancel(req); l = endpoint->requests; } } struct pac_config_data { struct bt_bap_stream *stream; bt_bap_pac_config_t cb; void *user_data; }; static int transport_cmp(gconstpointer data, gconstpointer user_data) { const struct media_transport *transport = data; if (media_transport_get_stream((void *)transport) == user_data) return 0; return -1; } static struct media_transport *find_transport(struct media_endpoint *endpoint, void *stream) { GSList *match; match = g_slist_find_custom(endpoint->transports, stream, transport_cmp); if (match == NULL) return NULL; return match->data; } static void pac_config_cb(struct media_endpoint *endpoint, void *ret, int size, void *user_data) { struct pac_config_data *data = user_data; uint8_t *error_code = ret; struct media_transport *transport; /* If transport was cleared, configuration was cancelled */ transport = find_transport(endpoint, data->stream); if (!transport) return; data->cb(data->stream, (error_code && *error_code == 0) ? 0 : -EINVAL); } static struct media_transport *pac_ucast_config(struct bt_bap_stream *stream, struct iovec *cfg, struct media_endpoint *endpoint) { struct bt_bap *bap = bt_bap_stream_get_session(stream); struct btd_service *service = bt_bap_get_user_data(bap); struct btd_device *device; const char *path; if (service) device = btd_service_get_device(service); else { struct bt_att *att = bt_bap_get_att(bap); int fd = bt_att_get_fd(att); device = btd_adapter_find_device_by_fd(fd); } if (!device) { error("Unable to find device"); return NULL; } path = bt_bap_stream_get_user_data(stream); return media_transport_create(device, path, cfg->iov_base, cfg->iov_len, endpoint, stream); } static struct media_transport *pac_bcast_config(struct bt_bap_stream *stream, struct iovec *cfg, struct media_endpoint *endpoint) { struct bt_bap *bap = bt_bap_stream_get_session(stream); struct btd_adapter *adapter = endpoint->adapter->btd_adapter; struct btd_device *device; const char *path; if (!adapter) return NULL; if (!strcmp(endpoint->uuid, BCAA_SERVICE_UUID)) device = NULL; else device = btd_service_get_device(bt_bap_get_user_data(bap)); path = bt_bap_stream_get_user_data(stream); return media_transport_create(device, path, cfg->iov_base, cfg->iov_len, endpoint, stream); } static int pac_config(struct bt_bap_stream *stream, struct iovec *cfg, struct bt_bap_qos *qos, bt_bap_pac_config_t cb, void *user_data) { struct media_endpoint *endpoint = user_data; DBusConnection *conn = btd_get_dbus_connection(); struct pac_config_data *data; struct media_transport *transport; DBusMessage *msg; DBusMessageIter iter; const char *path; int timeout_msec; DBG("endpoint %p stream %p", endpoint, stream); transport = find_transport(endpoint, stream); if (transport) clear_configuration(endpoint, transport); switch (bt_bap_stream_get_type(stream)) { case BT_BAP_STREAM_TYPE_UCAST: transport = pac_ucast_config(stream, cfg, endpoint); timeout_msec = BAP_REQUEST_TIMEOUT_MSEC; break; case BT_BAP_STREAM_TYPE_BCAST: transport = pac_bcast_config(stream, cfg, endpoint); /* If we are sink, BASS remote may be waiting: custom timeout */ if (bt_bap_stream_get_dir(stream) == BT_BAP_SOURCE) timeout_msec = BAP_REQUEST_TIMEOUT_MSEC; else timeout_msec = -1; break; default: transport = NULL; break; } if (!transport) return -EINVAL; endpoint->transports = g_slist_append(endpoint->transports, transport); msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, MEDIA_ENDPOINT_INTERFACE, "SetConfiguration"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); endpoint_remove_transport(endpoint, transport); return -ENOMEM; } data = new0(struct pac_config_data, 1); data->stream = stream; data->cb = cb; data->user_data = user_data; dbus_message_iter_init_append(msg, &iter); path = media_transport_get_path(transport); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); g_dbus_get_properties(conn, path, "org.bluez.MediaTransport1", &iter); if (!media_endpoint_async_call(msg, endpoint, transport, pac_config_cb, data, free, timeout_msec)) return -EIO; return 0; } static void pac_clear(struct bt_bap_stream *stream, void *user_data) { struct media_endpoint *endpoint = user_data; struct media_transport *transport; DBG("endpoint %p stream %p", endpoint, stream); transport = find_transport(endpoint, stream); if (transport) clear_configuration(endpoint, transport); } static struct bt_bap_pac_ops pac_ops = { .select = pac_select, .cancel_select = pac_cancel_select, .config = pac_config, .clear = pac_clear, }; static void bap_debug(const char *str, void *user_data) { DBG("%s", str); } static bool endpoint_init_pac(struct media_endpoint *endpoint, uint8_t type, int *err) { struct btd_gatt_database *database; struct gatt_db *db; struct iovec data; struct iovec *metadata = NULL; char *name; if (!(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) { DBG("D-Bus experimental not enabled"); *err = -ENOTSUP; return false; } database = btd_adapter_get_database(endpoint->adapter->btd_adapter); if (!database) { error("Adapter database not found"); return false; } if (!bt_bap_debug_caps(endpoint->capabilities, endpoint->size, bap_debug, NULL)) { error("Unable to parse endpoint capabilities"); return false; } if (!bt_bap_debug_metadata(endpoint->metadata, endpoint->metadata_size, bap_debug, NULL)) { error("Unable to parse endpoint metadata"); return false; } db = btd_gatt_database_get_db(database); data.iov_base = endpoint->capabilities; data.iov_len = endpoint->size; if (asprintf(&name, "%s:%s", endpoint->sender, endpoint->path) < 0) { error("Could not allocate name for pac %s:%s", endpoint->sender, endpoint->path); free(name); return false; } /* TODO: Add support for metadata */ if (endpoint->metadata_size) { metadata = g_new0(struct iovec, 1); metadata->iov_base = endpoint->metadata; metadata->iov_len = endpoint->metadata_size; } endpoint->pac = bt_bap_add_vendor_pac_full(db, name, type, endpoint->codec, endpoint->cid, endpoint->vid, &endpoint->qos, &data, metadata, &pac_ops, endpoint); if (!endpoint->pac) { error("Unable to create PAC"); free(name); free(metadata); return false; } DBG("PAC %s registered", name); free(name); free(metadata); return true; } static bool endpoint_init_pac_sink(struct media_endpoint *endpoint, int *err) { return endpoint_init_pac(endpoint, BT_BAP_SINK, err); } static bool endpoint_init_pac_source(struct media_endpoint *endpoint, int *err) { return endpoint_init_pac(endpoint, BT_BAP_SOURCE, err); } static bool endpoint_init_broadcast_source(struct media_endpoint *endpoint, int *err) { return endpoint_init_pac(endpoint, BT_BAP_BCAST_SOURCE, err); } static bool endpoint_init_broadcast_sink(struct media_endpoint *endpoint, int *err) { return endpoint_init_pac(endpoint, BT_BAP_BCAST_SINK, err); } static bool endpoint_init_asha(struct media_endpoint *endpoint, int *err) { return true; } static bool endpoint_properties_exists(const char *uuid, struct btd_device *dev, void *user_data) { struct media_adapter *adapter; adapter = find_adapter(dev); if (adapter == NULL) return false; if (media_adapter_find_endpoint(adapter, NULL, NULL, uuid) == NULL) return false; return true; } static void append_endpoint(struct media_endpoint *endpoint, DBusMessageIter *dict) { DBusMessageIter entry, var, props; dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &endpoint->sender); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "a{sv}", &var); dbus_message_iter_open_container(&var, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &props); dict_append_entry(&props, "Path", DBUS_TYPE_OBJECT_PATH, &endpoint->path); dict_append_entry(&props, "Codec", DBUS_TYPE_BYTE, &endpoint->codec); dict_append_array(&props, "Capabilities", DBUS_TYPE_BYTE, &endpoint->capabilities, endpoint->size); dbus_message_iter_close_container(&var, &props); dbus_message_iter_close_container(&entry, &var); dbus_message_iter_close_container(dict, &entry); } static bool endpoint_properties_get(const char *uuid, struct btd_device *dev, DBusMessageIter *iter, void *user_data) { struct media_adapter *adapter; DBusMessageIter dict; GSList *l; adapter = find_adapter(dev); if (adapter == NULL) return false; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); for (l = adapter->endpoints; l; l = l->next) { struct media_endpoint *endpoint = l->data; if (strcasecmp(endpoint->uuid, uuid) != 0) continue; append_endpoint(endpoint, &dict); } dbus_message_iter_close_container(iter, &dict); return true; } static bool a2dp_endpoint_supported(struct btd_adapter *adapter) { if (!btd_adapter_has_settings(adapter, MGMT_SETTING_BREDR)) return false; return true; } static bool experimental_endpoint_supported(struct btd_adapter *adapter) { if (!btd_adapter_has_exp_feature(adapter, EXP_FEAT_ISO_SOCKET)) return false; if (!btd_adapter_has_settings(adapter, MGMT_SETTING_CIS_CENTRAL | MGMT_SETTING_CIS_PERIPHERAL)) return false; return g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL; } static bool experimental_broadcaster_ep_supported(struct btd_adapter *adapter) { if (!btd_adapter_has_exp_feature(adapter, EXP_FEAT_ISO_SOCKET)) return false; if (!btd_adapter_has_settings(adapter, MGMT_SETTING_ISO_BROADCASTER)) return false; return g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL; } static bool experimental_bcast_sink_ep_supported(struct btd_adapter *adapter) { if (!btd_adapter_has_exp_feature(adapter, EXP_FEAT_ISO_SOCKET)) return false; if (!btd_adapter_has_settings(adapter, MGMT_SETTING_ISO_SYNC_RECEIVER)) return false; return g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL; } static bool experimental_asha_supported(struct btd_adapter *adapter) { return g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL; } static const struct media_endpoint_init { const char *uuid; bool (*func)(struct media_endpoint *endpoint, int *err); bool (*supported)(struct btd_adapter *adapter); } init_table[] = { #ifdef HAVE_A2DP { A2DP_SOURCE_UUID, endpoint_init_a2dp_source, a2dp_endpoint_supported }, { A2DP_SINK_UUID, endpoint_init_a2dp_sink, a2dp_endpoint_supported }, #endif { PAC_SINK_UUID, endpoint_init_pac_sink, experimental_endpoint_supported }, { PAC_SOURCE_UUID, endpoint_init_pac_source, experimental_endpoint_supported }, { BCAA_SERVICE_UUID, endpoint_init_broadcast_source, experimental_broadcaster_ep_supported }, { BAA_SERVICE_UUID, endpoint_init_broadcast_sink, experimental_bcast_sink_ep_supported }, { ASHA_PROFILE_UUID, endpoint_init_asha, experimental_asha_supported }, }; static struct media_endpoint * media_endpoint_create(struct media_adapter *adapter, const char *sender, const char *path, const char *uuid, gboolean delay_reporting, uint8_t codec, uint16_t cid, uint16_t vid, struct bt_bap_pac_qos *qos, uint8_t *capabilities, int size, uint8_t *metadata, int metadata_size, struct endpoint_features *feat, int *err) { struct media_endpoint *endpoint; const struct media_endpoint_init *init; size_t i; bool succeeded = false; endpoint = g_new0(struct media_endpoint, 1); endpoint->sender = g_strdup(sender); endpoint->path = g_strdup(path); endpoint->uuid = g_strdup(uuid); endpoint->codec = codec; endpoint->cid = cid; endpoint->vid = vid; endpoint->delay_reporting = delay_reporting; if (qos) endpoint->qos = *qos; if (feat) endpoint->features = *feat; if (size > 0) { endpoint->capabilities = g_new(uint8_t, size); memcpy(endpoint->capabilities, capabilities, size); endpoint->size = size; } if (metadata_size > 0) { endpoint->metadata = g_new(uint8_t, metadata_size); memcpy(endpoint->metadata, metadata, metadata_size); endpoint->metadata_size = metadata_size; } endpoint->adapter = adapter; for (i = 0; i < ARRAY_SIZE(init_table); i++) { init = &init_table[i]; if (!init->supported(adapter->btd_adapter)) continue; if (!strcasecmp(init->uuid, uuid)) { succeeded = init->func(endpoint, err); break; } } if (!succeeded) { error("Unable initialize endpoint for UUID %s", uuid); media_endpoint_destroy(endpoint); return NULL; } endpoint->watch = g_dbus_add_disconnect_watch(btd_get_dbus_connection(), sender, media_endpoint_exit, endpoint, NULL); if (media_adapter_find_endpoint(adapter, NULL, NULL, uuid) == NULL) { btd_profile_add_custom_prop(uuid, "a{sv}", "MediaEndpoints", endpoint_properties_exists, endpoint_properties_get, NULL); } adapter->endpoints = g_slist_append(adapter->endpoints, endpoint); info("Endpoint registered: sender=%s path=%s", sender, path); update_features(adapter); if (err) *err = 0; return endpoint; } static void parse_tmap_role(struct endpoint_features *features, uint32_t data) { features->tmap_role |= data; } static void parse_gmap_role(struct endpoint_features *features, uint32_t data) { features->gmap_role |= data; } static void parse_gmap_feature(struct endpoint_features *features, uint32_t data) { features->gmap_features |= data; } #define TMAP_ROLE(key) \ { .uuid = TMAS_UUID_STR, .name = key ## _STR, .data = key, \ .func = parse_tmap_role }, #define GMAP_ROLE(key) \ { .uuid = GMAS_UUID_STR, .name = key ## _STR, .data = key, \ .func = parse_gmap_role }, #define GMAP_FEATURE(key) \ { .uuid = GMAS_UUID_STR, .name = key ## _STR, .data = key, \ .func = parse_gmap_feature }, static const struct endpoint_feature_dbus { const char *uuid; const char *name; uint32_t data; void (*func)(struct endpoint_features *features, uint32_t data); } endpoint_features[] = { BT_TMAP_ROLE_LIST(TMAP_ROLE) BT_GMAP_ROLE_LIST(GMAP_ROLE) BT_GMAP_FEATURE_LIST(GMAP_FEATURE) }; static void parse_endpoint_feature(const char *uuid, const char *value, struct endpoint_features *features) { unsigned int i; for (i = 0; i < ARRAY_SIZE(endpoint_features); ++i) { const struct endpoint_feature_dbus *f = &endpoint_features[i]; if (strcmp(uuid, f->uuid) == 0 && strcmp(value, f->name) == 0) { f->func(features, f->data); break; } } } static int parse_endpoint_features(DBusMessageIter *iter, struct endpoint_features *features) { while (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_DICT_ENTRY) { const char *uuid, *name; DBusMessageIter value, entry, array; int var; dbus_message_iter_recurse(iter, &entry); dbus_message_iter_get_basic(&entry, &uuid); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); var = dbus_message_iter_get_arg_type(&value); if (var != DBUS_TYPE_ARRAY) return -EINVAL; dbus_message_iter_recurse(&value, &array); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) { dbus_message_iter_get_basic(&array, &name); parse_endpoint_feature(uuid, name, features); dbus_message_iter_next(&array); } dbus_message_iter_next(iter); } return 0; } struct vendor { uint16_t cid; uint16_t vid; } __packed; static int parse_properties(DBusMessageIter *props, const char **uuid, gboolean *delay_reporting, uint8_t *codec, uint16_t *cid, uint16_t *vid, struct bt_bap_pac_qos *qos, uint8_t **capabilities, int *size, uint8_t **metadata, int *metadata_size, struct endpoint_features *features) { gboolean has_uuid = FALSE; gboolean has_codec = FALSE; struct vendor vendor; while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { const char *key; DBusMessageIter value, entry; int var; dbus_message_iter_recurse(props, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); var = dbus_message_iter_get_arg_type(&value); if (strcasecmp(key, "UUID") == 0) { if (var != DBUS_TYPE_STRING) return -EINVAL; dbus_message_iter_get_basic(&value, uuid); has_uuid = TRUE; } else if (strcasecmp(key, "Codec") == 0) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(&value, codec); has_codec = TRUE; } else if (strcasecmp(key, "Vendor") == 0) { if (var != DBUS_TYPE_UINT32) return -EINVAL; dbus_message_iter_get_basic(&value, &vendor); *cid = vendor.cid; *vid = vendor.vid; } else if (strcasecmp(key, "DelayReporting") == 0) { if (var != DBUS_TYPE_BOOLEAN) return -EINVAL; dbus_message_iter_get_basic(&value, delay_reporting); } else if (strcasecmp(key, "Capabilities") == 0) { DBusMessageIter array; if (var != DBUS_TYPE_ARRAY) return -EINVAL; dbus_message_iter_recurse(&value, &array); dbus_message_iter_get_fixed_array(&array, capabilities, size); } else if (strcasecmp(key, "Metadata") == 0) { DBusMessageIter array; if (var != DBUS_TYPE_ARRAY) return -EINVAL; dbus_message_iter_recurse(&value, &array); dbus_message_iter_get_fixed_array(&array, metadata, metadata_size); } else if (strcasecmp(key, "Framing") == 0) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(&value, &qos->framing); } else if (strcasecmp(key, "PHY") == 0) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(&value, &qos->phys); } else if (strcasecmp(key, "Retransmissions") == 0) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(&value, &qos->rtn); } else if (strcasecmp(key, "MinimumDelay") == 0) { if (var != DBUS_TYPE_UINT16) return -EINVAL; dbus_message_iter_get_basic(&value, &qos->pd_min); } else if (strcasecmp(key, "MaximumDelay") == 0) { if (var != DBUS_TYPE_UINT16) return -EINVAL; dbus_message_iter_get_basic(&value, &qos->pd_max); } else if (strcasecmp(key, "PreferredMinimumDelay") == 0) { if (var != DBUS_TYPE_UINT16) return -EINVAL; dbus_message_iter_get_basic(&value, &qos->ppd_min); } else if (strcasecmp(key, "PreferredMaximumDelay") == 0) { if (var != DBUS_TYPE_UINT16) return -EINVAL; dbus_message_iter_get_basic(&value, &qos->ppd_max); } else if (strcasecmp(key, "Locations") == 0) { if (var != DBUS_TYPE_UINT32) return -EINVAL; dbus_message_iter_get_basic(&value, &qos->location); } else if (strcasecmp(key, "Context") == 0) { if (var != DBUS_TYPE_UINT16) return -EINVAL; dbus_message_iter_get_basic(&value, &qos->context); } else if (strcasecmp(key, "SupportedContext") == 0) { if (var != DBUS_TYPE_UINT16) return -EINVAL; dbus_message_iter_get_basic(&value, &qos->supported_context); } else if (strcasecmp(key, "SupportedFeatures") == 0) { DBusMessageIter array; if (var != DBUS_TYPE_ARRAY) return -EINVAL; dbus_message_iter_recurse(&value, &array); if (parse_endpoint_features(&array, features) < 0) return -EINVAL; } dbus_message_iter_next(props); } return (has_uuid && has_codec) ? 0 : -EINVAL; } static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_adapter *adapter = data; DBusMessageIter args, props; const char *sender, *path, *uuid; gboolean delay_reporting = FALSE; uint8_t codec = 0; uint16_t cid = 0; uint16_t vid = 0; struct bt_bap_pac_qos qos = {}; struct endpoint_features features = { 0 }; uint8_t *capabilities = NULL; uint8_t *metadata = NULL; int size = 0; int metadata_size = 0; int err; sender = dbus_message_get_sender(msg); dbus_message_iter_init(msg, &args); dbus_message_iter_get_basic(&args, &path); dbus_message_iter_next(&args); if (media_adapter_find_endpoint(adapter, sender, path, NULL) != NULL) return btd_error_already_exists(msg); dbus_message_iter_recurse(&args, &props); if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) return btd_error_invalid_args(msg); if (parse_properties(&props, &uuid, &delay_reporting, &codec, &cid, &vid, &qos, &capabilities, &size, &metadata, &metadata_size, &features) < 0) return btd_error_invalid_args(msg); if (media_endpoint_create(adapter, sender, path, uuid, delay_reporting, codec, cid, vid, &qos, capabilities, size, metadata, metadata_size, &features, &err) == NULL) { if (err == -EPROTONOSUPPORT) return btd_error_not_supported(msg); else return btd_error_invalid_args(msg); } return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *unregister_endpoint(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_adapter *adapter = data; struct media_endpoint *endpoint; const char *sender, *path; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); sender = dbus_message_get_sender(msg); endpoint = media_adapter_find_endpoint(adapter, sender, path, NULL); if (endpoint == NULL) return btd_error_does_not_exist(msg); media_endpoint_remove(endpoint); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static struct local_player *media_adapter_find_player( struct media_adapter *adapter, const char *sender, const char *path) { GSList *l; for (l = adapter->players; l; l = l->next) { struct local_player *mp = l->data; if (sender && g_strcmp0(mp->sender, sender) != 0) continue; if (path && g_strcmp0(mp->path, path) != 0) continue; return mp; } return NULL; } static void release_player(struct local_player *mp) { DBusMessage *msg; DBG("sender=%s path=%s", mp->sender, mp->path); msg = dbus_message_new_method_call(mp->sender, mp->path, MEDIA_PLAYER_INTERFACE, "Release"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); return; } g_dbus_send_message(btd_get_dbus_connection(), msg); } #define LOCAL_PLAYER_EMIT_CB(mp_, method_, ...) \ do { \ const struct queue_entry *entry, *next; \ for (entry = queue_get_entries(mp_->cbs); entry; \ entry = next) { \ struct local_player_cbs *cbs = entry->data; \ next = entry->next; \ if (cbs->cb->method_) \ cbs->cb->method_(__VA_ARGS__); \ } \ } while (0) static void local_player_emit_status_changed(struct local_player *mp) { LOCAL_PLAYER_EMIT_CB(mp, status_changed, cbs->user_data); } static void local_player_emit_track_position(struct local_player *mp, uint32_t old, uint32_t new) { LOCAL_PLAYER_EMIT_CB(mp, track_position, old, new, cbs->user_data); } static void local_player_emit_track_changed(struct local_player *mp) { LOCAL_PLAYER_EMIT_CB(mp, track_changed, cbs->user_data); } static void local_player_emit_settings_changed(struct local_player *mp, const char *key) { LOCAL_PLAYER_EMIT_CB(mp, settings_changed, key, cbs->user_data); } static void local_player_emit_player_removed(struct local_player *mp) { LOCAL_PLAYER_EMIT_CB(mp, player_removed, cbs->user_data); } #undef LOCAL_PLAYER_EMIT_CB static void local_player_emit_player_added(struct local_player *mp) { const struct queue_entry *entry; for (entry = queue_get_entries(local_player_watches); entry; entry = entry->next) { struct local_player_watch *w = entry->data; w->cb(mp, w->user_data); } } static void local_player_destroy(struct local_player *mp) { DBusConnection *conn = btd_get_dbus_connection(); struct media_adapter *adapter = mp->adapter; DBG("sender=%s path=%s", mp->sender, mp->path); local_player_emit_player_removed(mp); queue_destroy(mp->cbs, free); mp->cbs = NULL; adapter->players = g_slist_remove(adapter->players, mp); release_player(mp); g_dbus_remove_watch(conn, mp->watch); g_dbus_remove_watch(conn, mp->properties_watch); g_dbus_remove_watch(conn, mp->seek_watch); if (mp->track) g_hash_table_unref(mp->track); if (mp->settings) g_hash_table_unref(mp->settings); g_timer_destroy(mp->timer); g_free(mp->sender); g_free(mp->path); g_free(mp->status); g_free(mp->name); g_free(mp); } static void local_player_remove(void *data) { struct local_player *mp = data; info("Player unregistered: sender=%s path=%s", mp->sender, mp->path); local_player_destroy(mp); } GList *local_player_list_settings(struct local_player *mp) { DBG(""); if (mp->settings == NULL) return NULL; return g_hash_table_get_keys(mp->settings); } const char *local_player_get_setting(struct local_player *mp, const char *key) { DBG("%s", key); return g_hash_table_lookup(mp->settings, key); } const char *local_player_get_player_name(struct local_player *mp) { if (!mp->name) return "Player"; return mp->name; } bool local_player_have_track(struct local_player *mp) { return mp->track; } static void set_shuffle_setting(DBusMessageIter *iter, const char *value) { const char *key = "Shuffle"; dbus_bool_t val; DBusMessageIter var; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &key); dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &var); val = strcasecmp(value, "off") != 0; dbus_message_iter_append_basic(&var, DBUS_TYPE_BOOLEAN, &val); dbus_message_iter_close_container(iter, &var); } static const char *repeat_to_loop_status(const char *value) { if (strcasecmp(value, "off") == 0) return "None"; else if (strcasecmp(value, "singletrack") == 0) return "Track"; else if (strcasecmp(value, "alltracks") == 0) return "Playlist"; else if (strcasecmp(value, "group") == 0) return "Playlist"; return NULL; } static void set_repeat_setting(DBusMessageIter *iter, const char *value) { const char *key = "LoopStatus"; const char *val; DBusMessageIter var; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &key); dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &var); val = repeat_to_loop_status(value); dbus_message_iter_append_basic(&var, DBUS_TYPE_STRING, &val); dbus_message_iter_close_container(iter, &var); } int local_player_set_setting(struct local_player *mp, const char *key, const char *value) { const char *iface = MEDIA_PLAYER_INTERFACE; DBusMessage *msg; DBusMessageIter iter; const char *curval; DBG("%s = %s", key, value); curval = g_hash_table_lookup(mp->settings, key); if (!curval) return -EINVAL; if (strcasecmp(curval, value) == 0) return 0; msg = dbus_message_new_method_call(mp->sender, mp->path, DBUS_INTERFACE_PROPERTIES, "Set"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &iface); if (strcasecmp(key, "Shuffle") == 0) set_shuffle_setting(&iter, value); else if (strcasecmp(key, "Repeat") == 0) set_repeat_setting(&iter, value); g_dbus_send_message(btd_get_dbus_connection(), msg); return 0; } GList *local_player_list_metadata(struct local_player *mp) { DBG(""); if (mp->track == NULL) return NULL; return g_hash_table_get_keys(mp->track); } const char *local_player_get_metadata(struct local_player *mp, const char *key) { DBG("%s", key); if (mp->track == NULL) return NULL; return g_hash_table_lookup(mp->track, key); } const char *local_player_get_status(struct local_player *mp) { return mp->status; } uint32_t local_player_get_position(struct local_player *mp) { double timedelta; uint32_t sec, msec; if (mp->status == NULL || strcasecmp(mp->status, "Playing") != 0) return mp->position; timedelta = g_timer_elapsed(mp->timer, NULL); sec = (uint32_t) timedelta; msec = (uint32_t) ((timedelta - sec) * 1000); return mp->position + sec * 1000 + msec; } uint32_t local_player_get_duration(struct local_player *mp) { return mp->duration; } static bool local_player_send(struct local_player *mp, const char *name) { DBusMessage *msg; msg = dbus_message_new_method_call(mp->sender, mp->path, MEDIA_PLAYER_INTERFACE, name); if (msg == NULL) { error("Couldn't allocate D-Bus message"); return false; } g_dbus_send_message(btd_get_dbus_connection(), msg); return true; } bool local_player_play(struct local_player *mp) { DBG(""); if (!mp->play || !mp->control) return false; return local_player_send(mp, "Play"); } bool local_player_stop(struct local_player *mp) { DBG(""); if (!mp->control) return false; return local_player_send(mp, "Stop"); } bool local_player_pause(struct local_player *mp) { DBG(""); if (!mp->pause || !mp->control) return false; return local_player_send(mp, "Pause"); } bool local_player_next(struct local_player *mp) { DBG(""); if (!mp->next || !mp->control) return false; return local_player_send(mp, "Next"); } bool local_player_previous(struct local_player *mp) { DBG(""); if (!mp->previous || !mp->control) return false; return local_player_send(mp, "Previous"); } struct btd_adapter *local_player_get_adapter(struct local_player *mp) { return mp->adapter->btd_adapter; } static void local_player_exit(DBusConnection *connection, void *user_data) { struct local_player *mp = user_data; mp->watch = 0; local_player_remove(mp); } static gboolean set_status(struct local_player *mp, DBusMessageIter *iter) { const char *value; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(iter, &value); DBG("Status=%s", value); if (g_strcmp0(mp->status, value) == 0) return TRUE; mp->position = local_player_get_position(mp); g_timer_start(mp->timer); g_free(mp->status); mp->status = g_strdup(value); local_player_emit_status_changed(mp); return TRUE; } static gboolean set_position(struct local_player *mp, DBusMessageIter *iter) { uint64_t value; uint32_t old_position; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_INT64) return FALSE; dbus_message_iter_get_basic(iter, &value); value /= 1000; old_position = local_player_get_position(mp); mp->position = value; g_timer_start(mp->timer); DBG("Position=%u", mp->position); local_player_emit_track_position(mp, old_position, mp->position); return TRUE; } static void set_metadata(struct local_player *mp, const char *key, const char *value) { DBG("%s=%s", key, value); g_hash_table_replace(mp->track, g_strdup(key), g_strdup(value)); } static gboolean parse_string_metadata(struct local_player *mp, const char *key, DBusMessageIter *iter) { const char *value; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(iter, &value); set_metadata(mp, key, value); return TRUE; } static gboolean parse_array_metadata(struct local_player *mp, const char *key, DBusMessageIter *iter) { DBusMessageIter array; const char *value; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return FALSE; dbus_message_iter_recurse(iter, &array); if (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_INVALID) return TRUE; if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(&array, &value); set_metadata(mp, key, value); return TRUE; } static gboolean parse_int64_metadata(struct local_player *mp, const char *key, DBusMessageIter *iter) { uint64_t value; char valstr[20]; int type; type = dbus_message_iter_get_arg_type(iter); if (type == DBUS_TYPE_UINT64) warn("expected DBUS_TYPE_INT64 got DBUS_TYPE_UINT64"); else if (type != DBUS_TYPE_INT64) return FALSE; dbus_message_iter_get_basic(iter, &value); if (strcasecmp(key, "Duration") == 0) { value /= 1000; mp->duration = value; } snprintf(valstr, 20, "%" PRIu64, value); set_metadata(mp, key, valstr); return TRUE; } static gboolean parse_int32_metadata(struct local_player *mp, const char *key, DBusMessageIter *iter) { uint32_t value; char valstr[20]; int type; type = dbus_message_iter_get_arg_type(iter); if (type == DBUS_TYPE_UINT32) warn("expected DBUS_TYPE_INT32 got DBUS_TYPE_UINT32"); else if (type != DBUS_TYPE_INT32) return FALSE; dbus_message_iter_get_basic(iter, &value); snprintf(valstr, 20, "%u", value); set_metadata(mp, key, valstr); return TRUE; } static gboolean parse_player_metadata(struct local_player *mp, DBusMessageIter *iter) { DBusMessageIter dict; DBusMessageIter var; int ctype; gboolean title = FALSE; ctype = dbus_message_iter_get_arg_type(iter); if (ctype != DBUS_TYPE_ARRAY) return FALSE; dbus_message_iter_recurse(iter, &dict); if (mp->track != NULL) g_hash_table_unref(mp->track); mp->track = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); while ((ctype = dbus_message_iter_get_arg_type(&dict)) != DBUS_TYPE_INVALID) { DBusMessageIter entry; const char *key; if (ctype != DBUS_TYPE_DICT_ENTRY) return FALSE; dbus_message_iter_recurse(&dict, &entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) return FALSE; dbus_message_iter_recurse(&entry, &var); if (strcasecmp(key, "xesam:title") == 0) { if (!parse_string_metadata(mp, "Title", &var)) return FALSE; title = TRUE; } else if (strcasecmp(key, "xesam:artist") == 0) { if (!parse_array_metadata(mp, "Artist", &var)) return FALSE; } else if (strcasecmp(key, "xesam:album") == 0) { if (!parse_string_metadata(mp, "Album", &var)) return FALSE; } else if (strcasecmp(key, "xesam:genre") == 0) { if (!parse_array_metadata(mp, "Genre", &var)) return FALSE; } else if (strcasecmp(key, "mpris:length") == 0) { if (!parse_int64_metadata(mp, "Duration", &var)) return FALSE; } else if (strcasecmp(key, "xesam:trackNumber") == 0) { if (!parse_int32_metadata(mp, "TrackNumber", &var)) return FALSE; } else DBG("%s not supported, ignoring", key); dbus_message_iter_next(&dict); } if (title == FALSE) g_hash_table_insert(mp->track, g_strdup("Title"), g_strdup("")); mp->position = 0; g_timer_start(mp->timer); local_player_emit_track_changed(mp); return TRUE; } static gboolean set_property(struct local_player *mp, const char *key, const char *value) { const char *curval; curval = g_hash_table_lookup(mp->settings, key); if (g_strcmp0(curval, value) == 0) return TRUE; DBG("%s=%s", key, value); g_hash_table_replace(mp->settings, g_strdup(key), g_strdup(value)); local_player_emit_settings_changed(mp, key); return TRUE; } static gboolean set_shuffle(struct local_player *mp, DBusMessageIter *iter) { dbus_bool_t value; const char *strvalue; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) return FALSE; dbus_message_iter_get_basic(iter, &value); strvalue = value ? "alltracks" : "off"; return set_property(mp, "Shuffle", strvalue); } static const char *loop_status_to_repeat(const char *value) { if (strcasecmp(value, "None") == 0) return "off"; else if (strcasecmp(value, "Track") == 0) return "singletrack"; else if (strcasecmp(value, "Playlist") == 0) return "alltracks"; return NULL; } static gboolean set_repeat(struct local_player *mp, DBusMessageIter *iter) { const char *value; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(iter, &value); value = loop_status_to_repeat(value); if (value == NULL) return FALSE; return set_property(mp, "Repeat", value); } static gboolean set_flag(struct local_player *mp, DBusMessageIter *iter, bool *var) { dbus_bool_t value; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) return FALSE; dbus_message_iter_get_basic(iter, &value); *var = value; return TRUE; } static gboolean set_name(struct local_player *mp, DBusMessageIter *iter) { const char *value; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(iter, &value); if (g_strcmp0(mp->name, value) == 0) return TRUE; g_free(mp->name); mp->name = g_strdup(value); return TRUE; } static gboolean set_player_property(struct local_player *mp, const char *key, DBusMessageIter *entry) { DBusMessageIter var; if (dbus_message_iter_get_arg_type(entry) != DBUS_TYPE_VARIANT) return FALSE; dbus_message_iter_recurse(entry, &var); if (strcasecmp(key, "PlaybackStatus") == 0) return set_status(mp, &var); if (strcasecmp(key, "Position") == 0) return set_position(mp, &var); if (strcasecmp(key, "Metadata") == 0) return parse_player_metadata(mp, &var); if (strcasecmp(key, "Shuffle") == 0) return set_shuffle(mp, &var); if (strcasecmp(key, "LoopStatus") == 0) return set_repeat(mp, &var); if (strcasecmp(key, "CanPlay") == 0) return set_flag(mp, &var, &mp->play); if (strcasecmp(key, "CanPause") == 0) return set_flag(mp, &var, &mp->pause); if (strcasecmp(key, "CanGoNext") == 0) return set_flag(mp, &var, &mp->next); if (strcasecmp(key, "CanGoPrevious") == 0) return set_flag(mp, &var, &mp->previous); if (strcasecmp(key, "CanControl") == 0) return set_flag(mp, &var, &mp->control); if (strcasecmp(key, "Identity") == 0) return set_name(mp, &var); DBG("%s not supported, ignoring", key); return TRUE; } static gboolean parse_player_properties(struct local_player *mp, DBusMessageIter *iter) { DBusMessageIter dict; int ctype; ctype = dbus_message_iter_get_arg_type(iter); if (ctype != DBUS_TYPE_ARRAY) return FALSE; dbus_message_iter_recurse(iter, &dict); while ((ctype = dbus_message_iter_get_arg_type(&dict)) != DBUS_TYPE_INVALID) { DBusMessageIter entry; const char *key; if (ctype != DBUS_TYPE_DICT_ENTRY) return FALSE; dbus_message_iter_recurse(&dict, &entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); if (set_player_property(mp, key, &entry) == FALSE) return FALSE; dbus_message_iter_next(&dict); } return TRUE; } static gboolean properties_changed(DBusConnection *connection, DBusMessage *msg, void *user_data) { struct local_player *mp = user_data; DBusMessageIter iter; DBG("sender=%s path=%s", mp->sender, mp->path); dbus_message_iter_init(msg, &iter); dbus_message_iter_next(&iter); parse_player_properties(mp, &iter); return TRUE; } static gboolean position_changed(DBusConnection *connection, DBusMessage *msg, void *user_data) { struct local_player *mp = user_data; DBusMessageIter iter; DBG("sender=%s path=%s", mp->sender, mp->path); dbus_message_iter_init(msg, &iter); set_position(mp, &iter); return TRUE; } static struct local_player *local_player_create(struct media_adapter *adapter, const char *sender, const char *path, int *err) { DBusConnection *conn = btd_get_dbus_connection(); struct local_player *mp; mp = g_new0(struct local_player, 1); mp->adapter = adapter; mp->sender = g_strdup(sender); mp->path = g_strdup(path); mp->timer = g_timer_new(); mp->cbs = queue_new(); mp->watch = g_dbus_add_disconnect_watch(conn, sender, local_player_exit, mp, NULL); mp->properties_watch = g_dbus_add_properties_watch(conn, sender, path, MEDIA_PLAYER_INTERFACE, properties_changed, mp, NULL); mp->seek_watch = g_dbus_add_signal_watch(conn, sender, path, MEDIA_PLAYER_INTERFACE, "Seeked", position_changed, mp, NULL); mp->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); adapter->players = g_slist_append(adapter->players, mp); info("Player registered: sender=%s path=%s", sender, path); if (err) *err = 0; return mp; } static bool match_local_player(const void *data, const void *match) { const struct local_player_cbs *cbs = data; return cbs->id == PTR_TO_UINT(match); } unsigned int local_player_register_callbacks(struct local_player *mp, const struct local_player_callback *cb, void *user_data) { struct local_player_cbs *cbs; unsigned int id = 1; if (!cb) return 0; while (id && queue_find(mp->cbs, match_local_player, UINT_TO_PTR(id))) id++; if (!id) return 0; cbs = new0(struct local_player_cbs, 1); cbs->mp = mp; cbs->id = id; cbs->cb = cb; cbs->user_data = user_data; queue_push_tail(mp->cbs, cbs); return id; } void local_player_unregister_callbacks(struct local_player *mp, unsigned int id) { struct local_player_cb *cbs; cbs = queue_remove_if(mp->cbs, match_local_player, UINT_TO_PTR(id)); free(cbs); } static bool match_local_player_watch(const void *data, const void *match) { const struct local_player_watch *w = data; return w->id == PTR_TO_UINT(match); } static void local_player_watch_added(void *data, void *user_data) { struct local_player *mp = data; struct local_player_watch *w = user_data; w->cb(mp, w->user_data); } unsigned int local_player_register_watch(local_player_added_t cb, void *user_data) { struct local_player_watch *w; unsigned int id = 1; GSList *l; if (!cb) return 0; while (id && queue_find(local_player_watches, match_local_player_watch, UINT_TO_PTR(id))) id++; if (!id) return 0; w = new0(struct local_player_watch, 1); w->id = id; w->cb = cb; w->user_data = user_data; if (!local_player_watches) local_player_watches = queue_new(); queue_push_tail(local_player_watches, w); /* Emit existing */ for (l = adapters; l; l = l->next) { struct media_adapter *adapter = l->data; const struct queue_entry *entry; GSList *pl; for (pl = adapter->players; pl; pl = pl->next) local_player_watch_added(pl->data, w); for (entry = queue_get_entries(adapter->apps); entry; entry = entry->next) { struct media_app *app = entry->data; queue_foreach(app->players, local_player_watch_added, w); } } return id; } void local_player_unregister_watch(unsigned int id) { struct local_player_watch *w; w = queue_remove_if(local_player_watches, match_local_player_watch, UINT_TO_PTR(id)); free(w); if (queue_isempty(local_player_watches)) { queue_destroy(local_player_watches, NULL); local_player_watches = NULL; } } static DBusMessage *register_player(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_adapter *adapter = data; struct local_player *mp; DBusMessageIter args; const char *sender, *path; int err; sender = dbus_message_get_sender(msg); dbus_message_iter_init(msg, &args); dbus_message_iter_get_basic(&args, &path); dbus_message_iter_next(&args); if (media_adapter_find_player(adapter, sender, path) != NULL) return btd_error_already_exists(msg); mp = local_player_create(adapter, sender, path, &err); if (mp == NULL) { if (err == -EPROTONOSUPPORT) return btd_error_not_supported(msg); else return btd_error_invalid_args(msg); } if (parse_player_properties(mp, &args) == FALSE) { local_player_destroy(mp); return btd_error_invalid_args(msg); } local_player_emit_player_added(mp); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *unregister_player(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_adapter *adapter = data; struct local_player *player; const char *sender, *path; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); sender = dbus_message_get_sender(msg); player = media_adapter_find_player(adapter, sender, path); if (player == NULL) return btd_error_does_not_exist(msg); local_player_remove(player); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static void app_free(void *data) { struct media_app *app = data; queue_destroy(app->proxies, NULL); queue_destroy(app->endpoints, media_endpoint_remove); queue_destroy(app->players, local_player_remove); if (app->client) { g_dbus_client_set_disconnect_watch(app->client, NULL, NULL); g_dbus_client_set_proxy_handlers(app->client, NULL, NULL, NULL, NULL); g_dbus_client_set_ready_watch(app->client, NULL, NULL); g_dbus_client_unref(app->client); } if (app->reg) dbus_message_unref(app->reg); g_free(app->sender); g_free(app->path); free(app); } static void client_disconnect_cb(DBusConnection *conn, void *user_data) { struct media_app *app = user_data; struct media_adapter *adapter = app->adapter; DBG("Client disconnected"); if (queue_remove(adapter->apps, app)) app_free(app); } static void app_register_endpoint(void *data, void *user_data) { struct media_app *app = user_data; GDBusProxy *proxy = data; const char *iface = g_dbus_proxy_get_interface(proxy); const char *path = g_dbus_proxy_get_path(proxy); const char *uuid; gboolean delay_reporting = FALSE; uint8_t codec; struct vendor vendor; struct bt_bap_pac_qos qos; uint8_t *capabilities = NULL; int size = 0; uint8_t *metadata = NULL; int metadata_size = 0; struct endpoint_features features = { 0 }; DBusMessageIter iter, array; struct media_endpoint *endpoint; if (app->err) return; if (strcmp(iface, MEDIA_ENDPOINT_INTERFACE)) return; /* Parse properties */ if (!g_dbus_proxy_get_property(proxy, "UUID", &iter)) goto fail; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) goto fail; dbus_message_iter_get_basic(&iter, &uuid); if (!g_dbus_proxy_get_property(proxy, "Codec", &iter)) goto fail; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BYTE) goto fail; dbus_message_iter_get_basic(&iter, &codec); memset(&vendor, 0, sizeof(vendor)); if (g_dbus_proxy_get_property(proxy, "Vendor", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) goto fail; dbus_message_iter_get_basic(&iter, &vendor); } /* DelayReporting and Capabilities are considered optional */ if (g_dbus_proxy_get_property(proxy, "DelayReporting", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) goto fail; dbus_message_iter_get_basic(&iter, &delay_reporting); } if (g_dbus_proxy_get_property(proxy, "Capabilities", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) goto fail; dbus_message_iter_recurse(&iter, &array); dbus_message_iter_get_fixed_array(&array, &capabilities, &size); } if (g_dbus_proxy_get_property(proxy, "Metadata", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) goto fail; dbus_message_iter_recurse(&iter, &array); dbus_message_iter_get_fixed_array(&array, &metadata, &metadata_size); } /* Parse QoS preferences */ memset(&qos, 0, sizeof(qos)); if (g_dbus_proxy_get_property(proxy, "Framing", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BYTE) goto fail; dbus_message_iter_get_basic(&iter, &qos.framing); } if (g_dbus_proxy_get_property(proxy, "PHY", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BYTE) goto fail; dbus_message_iter_get_basic(&iter, &qos.phys); } if (g_dbus_proxy_get_property(proxy, "MaximumLatency", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT16) goto fail; dbus_message_iter_get_basic(&iter, &qos.latency); } if (g_dbus_proxy_get_property(proxy, "MinimumDelay", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) goto fail; dbus_message_iter_get_basic(&iter, &qos.pd_min); } if (g_dbus_proxy_get_property(proxy, "MaximumDelay", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) goto fail; dbus_message_iter_get_basic(&iter, &qos.pd_max); } if (g_dbus_proxy_get_property(proxy, "PreferredMinimumDelay", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) goto fail; dbus_message_iter_get_basic(&iter, &qos.ppd_min); } if (g_dbus_proxy_get_property(proxy, "PreferredMaximumDelay", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) goto fail; dbus_message_iter_get_basic(&iter, &qos.ppd_min); } if (g_dbus_proxy_get_property(proxy, "Locations", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) goto fail; dbus_message_iter_get_basic(&iter, &qos.location); } if (g_dbus_proxy_get_property(proxy, "Context", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT16) goto fail; dbus_message_iter_get_basic(&iter, &qos.context); } if (g_dbus_proxy_get_property(proxy, "SupportedContext", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT16) goto fail; dbus_message_iter_get_basic(&iter, &qos.supported_context); } if (g_dbus_proxy_get_property(proxy, "SupportedFeatures", &iter)) { DBusMessageIter array; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) goto fail; dbus_message_iter_recurse(&iter, &array); if (parse_endpoint_features(&array, &features) < 0) goto fail; } endpoint = media_endpoint_create(app->adapter, app->sender, path, uuid, delay_reporting, codec, vendor.cid, vendor.vid, &qos, capabilities, size, metadata, metadata_size, &features, &app->err); if (!endpoint) { error("Unable to register endpoint %s:%s: %s", app->sender, path, strerror(-app->err)); return; } queue_push_tail(app->endpoints, endpoint); return; fail: app->err = -EINVAL; } static void app_register_player(void *data, void *user_data) { struct media_app *app = user_data; GDBusProxy *proxy = data; const char *iface = g_dbus_proxy_get_interface(proxy); const char *path = g_dbus_proxy_get_path(proxy); struct local_player *player; DBusMessageIter iter; if (app->err) return; if (strcmp(iface, MEDIA_PLAYER_INTERFACE)) return; player = local_player_create(app->adapter, app->sender, path, &app->err); if (!player) return; if (g_dbus_proxy_get_property(proxy, "PlaybackStatus", &iter)) { if (!set_status(player, &iter)) goto fail; } if (g_dbus_proxy_get_property(proxy, "Position", &iter)) { if (!set_position(player, &iter)) goto fail; } if (g_dbus_proxy_get_property(proxy, "Metadata", &iter)) { if (!parse_player_metadata(player, &iter)) goto fail; } if (g_dbus_proxy_get_property(proxy, "Shuffle", &iter)) { if (!set_shuffle(player, &iter)) goto fail; } if (g_dbus_proxy_get_property(proxy, "LoopStatus", &iter)) { if (!set_repeat(player, &iter)) goto fail; } if (g_dbus_proxy_get_property(proxy, "CanPlay", &iter)) { if (!set_flag(player, &iter, &player->play)) goto fail; } if (g_dbus_proxy_get_property(proxy, "CanPause", &iter)) { if (!set_flag(player, &iter, &player->pause)) goto fail; } if (g_dbus_proxy_get_property(proxy, "CanGoNext", &iter)) { if (!set_flag(player, &iter, &player->next)) goto fail; } if (g_dbus_proxy_get_property(proxy, "CanGoPrevious", &iter)) { if (!set_flag(player, &iter, &player->previous)) goto fail; } if (g_dbus_proxy_get_property(proxy, "CanControl", &iter)) { if (!set_flag(player, &iter, &player->control)) goto fail; } if (g_dbus_proxy_get_property(proxy, "Identity", &iter)) { if (!set_name(player, &iter)) goto fail; } local_player_emit_player_added(player); queue_push_tail(app->players, player); return; fail: app->err = -EINVAL; error("Unable to register player %s:%s: %s", app->sender, path, strerror(-app->err)); local_player_destroy(player); } static void remove_app(void *data) { struct media_app *app = data; /* * Set callback to NULL to avoid potential race condition * when calling remove_app and GDBusClient unref. */ g_dbus_client_set_disconnect_watch(app->client, NULL, NULL); /* * Set proxy handlers to NULL, so that this gets called only once when * the first proxy that belongs to this service gets removed. */ g_dbus_client_set_proxy_handlers(app->client, NULL, NULL, NULL, NULL); queue_remove(app->adapter->apps, app); app_free(app); } static void client_ready_cb(GDBusClient *client, void *user_data) { struct media_app *app = user_data; DBusMessage *reply; bool fail = false; /* * Process received objects */ if (queue_isempty(app->proxies)) { error("No object received"); fail = true; reply = btd_error_failed(app->reg, "No object received"); goto reply; } if (app->err) { if (app->err == -EPROTONOSUPPORT) reply = btd_error_not_supported(app->reg); else reply = btd_error_invalid_args(app->reg); goto reply; } if ((queue_isempty(app->endpoints) && queue_isempty(app->players))) { error("No valid external Media objects found"); fail = true; reply = btd_error_failed(app->reg, "No valid media object found"); goto reply; } DBG("Media application registered: %s:%s", app->sender, app->path); reply = dbus_message_new_method_return(app->reg); reply: g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(app->reg); app->reg = NULL; if (fail) remove_app(app); } static void proxy_added_cb(GDBusProxy *proxy, void *user_data) { struct media_app *app = user_data; const char *iface, *path; if (app->err) return; queue_push_tail(app->proxies, proxy); iface = g_dbus_proxy_get_interface(proxy); path = g_dbus_proxy_get_path(proxy); DBG("Proxy added: %s, iface: %s", path, iface); app_register_endpoint(proxy, app); app_register_player(proxy, app); } static bool match_endpoint_by_path(const void *a, const void *b) { const struct media_endpoint *endpoint = a; const char *path = b; return !strcmp(endpoint->path, path); } static bool match_player_by_path(const void *a, const void *b) { const struct local_player *player = a; const char *path = b; return !strcmp(player->path, path); } static void proxy_removed_cb(GDBusProxy *proxy, void *user_data) { struct media_app *app = user_data; struct media_endpoint *endpoint; const char *iface, *path; iface = g_dbus_proxy_get_interface(proxy); path = g_dbus_proxy_get_path(proxy); if (!strcmp(iface, MEDIA_ENDPOINT_INTERFACE)) { endpoint = queue_remove_if(app->endpoints, match_endpoint_by_path, (void *) path); if (!endpoint) return; if (!g_slist_find(app->adapter->endpoints, endpoint)) return; DBG("Proxy removed - removing endpoint: %s", endpoint->path); media_endpoint_remove(endpoint); } else if (!strcmp(iface, MEDIA_PLAYER_INTERFACE)) { struct local_player *player; player = queue_remove_if(app->players, match_player_by_path, (void *) path); if (!player) return; if (!g_slist_find(app->adapter->players, player)) return; DBG("Proxy removed - removing player: %s", player->path); local_player_remove(player); } } static struct media_app *create_app(DBusConnection *conn, DBusMessage *msg, const char *path) { struct media_app *app; const char *sender = dbus_message_get_sender(msg); if (!path || !g_str_has_prefix(path, "/")) return NULL; app = new0(struct media_app, 1); app->client = g_dbus_client_new_full(conn, sender, path, path); if (!app->client) goto fail; app->sender = g_strdup(sender); if (!app->sender) goto fail; app->path = g_strdup(path); if (!app->path) goto fail; app->proxies = queue_new(); app->endpoints = queue_new(); app->players = queue_new(); app->reg = dbus_message_ref(msg); g_dbus_client_set_disconnect_watch(app->client, client_disconnect_cb, app); g_dbus_client_set_proxy_handlers(app->client, proxy_added_cb, proxy_removed_cb, NULL, app); g_dbus_client_set_ready_watch(app->client, client_ready_cb, app); return app; fail: app_free(app); return NULL; } struct match_data { const char *path; const char *sender; }; static bool match_app(const void *a, const void *b) { const struct media_app *app = a; const struct match_data *data = b; return g_strcmp0(app->path, data->path) == 0 && g_strcmp0(app->sender, data->sender) == 0; } static DBusMessage *register_app(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct media_adapter *adapter = user_data; const char *sender = dbus_message_get_sender(msg); DBusMessageIter args; const char *path; struct media_app *app; struct match_data match_data; if (!dbus_message_iter_init(msg, &args)) return btd_error_invalid_args(msg); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) return btd_error_invalid_args(msg); dbus_message_iter_get_basic(&args, &path); match_data.path = path; match_data.sender = sender; if (queue_find(adapter->apps, match_app, &match_data)) return btd_error_already_exists(msg); dbus_message_iter_next(&args); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) return btd_error_invalid_args(msg); app = create_app(conn, msg, path); if (!app) return btd_error_failed(msg, "Failed to register application"); DBG("Registering application: %s:%s", sender, path); app->adapter = adapter; queue_push_tail(adapter->apps, app); return NULL; } static DBusMessage *unregister_app(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct media_adapter *adapter = user_data; const char *sender = dbus_message_get_sender(msg); const char *path; DBusMessageIter args; struct media_app *app; struct match_data match_data; if (!dbus_message_iter_init(msg, &args)) return btd_error_invalid_args(msg); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) return btd_error_invalid_args(msg); dbus_message_iter_get_basic(&args, &path); match_data.path = path; match_data.sender = sender; app = queue_remove_if(adapter->apps, match_app, &match_data); if (!app) return btd_error_does_not_exist(msg); app_free(app); return dbus_message_new_method_return(msg); } static const GDBusMethodTable media_methods[] = { { GDBUS_METHOD("RegisterEndpoint", GDBUS_ARGS({ "endpoint", "o" }, { "properties", "a{sv}" }), NULL, register_endpoint) }, { GDBUS_METHOD("UnregisterEndpoint", GDBUS_ARGS({ "endpoint", "o" }), NULL, unregister_endpoint) }, { GDBUS_METHOD("RegisterPlayer", GDBUS_ARGS({ "player", "o" }, { "properties", "a{sv}" }), NULL, register_player) }, { GDBUS_METHOD("UnregisterPlayer", GDBUS_ARGS({ "player", "o" }), NULL, unregister_player) }, { GDBUS_ASYNC_METHOD("RegisterApplication", GDBUS_ARGS({ "application", "o" }, { "options", "a{sv}" }), NULL, register_app) }, { GDBUS_ASYNC_METHOD("UnregisterApplication", GDBUS_ARGS({ "application", "o" }), NULL, unregister_app) }, { }, }; static gboolean supported_uuids(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_adapter *adapter = data; DBusMessageIter entry; size_t i; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); for (i = 0; i < ARRAY_SIZE(init_table); i++) { const struct media_endpoint_init *init = &init_table[i]; if (init->supported(adapter->btd_adapter)) dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &init->uuid); } dbus_message_iter_close_container(iter, &entry); return TRUE; } static bool probe_tx_timestamping(struct media_adapter *adapter) { struct ifreq ifr = {}; struct ethtool_ts_info cmd = {}; int sk = -1; /* TX timestamping requires support from BlueZ in order to not mistake * errqueue for socket errors in media stream sockets. This is always * enabled (io_glib_add_err_watch), so need only check kernel side here. */ if (adapter->so_timestamping != -1) goto done; snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "hci%u", btd_adapter_get_index(adapter->btd_adapter)); ifr.ifr_data = (void *)&cmd; cmd.cmd = ETHTOOL_GET_TS_INFO; /* Check kernel reports some support for TX timestamping for L2CAP. If * yes then kernel version is new enough to have TX timestamping * available for other socket types too. */ sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (sk < 0) goto error; if (ioctl(sk, SIOCETHTOOL, &ifr)) goto error; close(sk); adapter->so_timestamping = cmd.so_timestamping; done: return adapter->so_timestamping & SOF_TIMESTAMPING_TX_SOFTWARE; error: if (sk >= 0) close(sk); adapter->so_timestamping = 0; return false; } static const struct { const char *name; bool (*probe)(struct media_adapter *adapter); } features[] = { { "tx-timestamping", probe_tx_timestamping }, }; static gboolean supported_features(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_adapter *adapter = data; DBusMessageIter entry; size_t i; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); for (i = 0; i < ARRAY_SIZE(features); ++i) if (features[i].probe(adapter)) dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &features[i].name); dbus_message_iter_close_container(iter, &entry); return TRUE; } static const GDBusPropertyTable media_properties[] = { { "SupportedUUIDs", "as", supported_uuids }, { "SupportedFeatures", "as", supported_features }, { } }; static void path_free(void *data) { struct media_adapter *adapter = data; GSList *l; queue_destroy(adapter->apps, app_free); for (l = adapter->endpoints; l;) { struct media_endpoint *endpoint = l->data; l = g_slist_next(l); release_endpoint(endpoint); } for (l = adapter->players; l;) { struct local_player *mp = l->data; l = g_slist_next(l); local_player_destroy(mp); } adapters = g_slist_remove(adapters, adapter); btd_adapter_unref(adapter->btd_adapter); g_free(adapter); } int media_register(struct btd_adapter *btd_adapter) { struct media_adapter *adapter; adapter = g_new0(struct media_adapter, 1); adapter->btd_adapter = btd_adapter_ref(btd_adapter); adapter->apps = queue_new(); adapter->so_timestamping = -1; if (!g_dbus_register_interface(btd_get_dbus_connection(), adapter_get_path(btd_adapter), MEDIA_INTERFACE, media_methods, NULL, media_properties, adapter, path_free)) { error("D-Bus failed to register %s path", adapter_get_path(btd_adapter)); path_free(adapter); return -1; } adapters = g_slist_append(adapters, adapter); return 0; } void media_unregister(struct btd_adapter *btd_adapter) { GSList *l; for (l = adapters; l; l = l->next) { struct media_adapter *adapter = l->data; if (adapter->btd_adapter == btd_adapter) { g_dbus_unregister_interface(btd_get_dbus_connection(), adapter_get_path(btd_adapter), MEDIA_INTERFACE); return; } } } #ifdef HAVE_A2DP struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint) { return endpoint->sep; } #endif const char *media_endpoint_get_uuid(struct media_endpoint *endpoint) { return endpoint->uuid; } bool media_endpoint_get_delay_reporting(struct media_endpoint *endpoint) { return endpoint->delay_reporting; } uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint) { return endpoint->codec; } struct btd_adapter *media_endpoint_get_btd_adapter( struct media_endpoint *endpoint) { return endpoint->adapter->btd_adapter; } bool media_endpoint_is_broadcast(struct media_endpoint *endpoint) { if (!strcmp(endpoint->uuid, BCAA_SERVICE_UUID) || !strcmp(endpoint->uuid, BAA_SERVICE_UUID)) return true; return false; } const struct media_endpoint *media_endpoint_get_asha(void) { /* * Because ASHA does not require the application to register an * endpoint, we need a minimal media_endpoint for transport creation to * work, so let's create one */ static struct media_endpoint asha_endpoint = { .uuid = ASHA_PROFILE_UUID, .codec = 0x1, /* Currently on G.722 is defined by the spec */ }; return &asha_endpoint; } bluez-5.87/profiles/audio/PaxHeaders/micp.c0000644000000000000000000000005015221752714015701 xustar0020 atime=1783093710 20 ctime=1783094106 bluez-5.87/profiles/audio/micp.c0000644000000000000000000001555015221752714015370 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2023 NXP Semiconductors. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/dbus-common.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-server.h" #include "src/shared/micp.h" #include "btio/btio.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/gatt-database.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #include "src/error.h" #define MICS_UUID_STR "0000184D-0000-1000-8000-00805f9b34fb" struct micp_data { struct btd_device *device; struct btd_service *service; struct bt_micp *micp; unsigned int ready_id; }; static struct queue *sessions; static void micp_debug(const char *str, void *user_data) { DBG_IDX(0xffff, "%s", str); } static int micp_disconnect(struct btd_service *service) { return 0; } static struct micp_data *micp_data_new(struct btd_device *device) { struct micp_data *data; data = new0(struct micp_data, 1); g_assert(data); data->device = device; return data; } static void micp_data_add(struct micp_data *data) { DBG("data %p", data); if (queue_find(sessions, NULL, data)) { error("data %p already added", data); return; } bt_micp_set_debug(data->micp, micp_debug, NULL, NULL); if (!sessions) sessions = queue_new(); queue_push_tail(sessions, data); if (data->service) btd_service_set_user_data(data->service, data); } static bool match_data(const void *data, const void *match_data) { const struct micp_data *mdata = data; const struct bt_micp *micp = match_data; return mdata->micp == micp; } static void micp_data_free(struct micp_data *data) { if (data->service) { btd_service_set_user_data(data->service, NULL); bt_micp_set_user_data(data->micp, NULL); } bt_micp_ready_unregister(data->micp, data->ready_id); bt_micp_unref(data->micp); free(data); } static void micp_data_remove(struct micp_data *data) { DBG("data %p", data); if (!queue_remove(sessions, data)) return; micp_data_free(data); if (queue_isempty(sessions)) { queue_destroy(sessions, NULL); sessions = NULL; } } static void micp_detached(struct bt_micp *micp, void *user_data) { struct micp_data *data; DBG("%p", micp); data = queue_find(sessions, match_data, micp); if (!data) { error("unable to find session"); return; } micp_data_remove(data); } static void micp_ready(struct bt_micp *micp, void *user_data) { DBG("micp %p\n", micp); } static void micp_attached(struct bt_micp *micp, void *user_data) { struct micp_data *data; struct bt_att *att; struct btd_device *device; DBG("%p", micp); data = queue_find(sessions, match_data, micp); if (data) return; att = bt_micp_get_att(micp); if (!att) return; device = btd_adapter_find_device_by_fd(bt_att_get_fd(att)); if (!device) { error("unable to find device"); return; } data = micp_data_new(device); g_assert(data); data->micp = micp; micp_data_add(data); } static int micp_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct btd_adapter *adapter = device_get_adapter(device); struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct micp_data *data = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); /*Ignore, if we probed for this device already */ if (data) { error("Profile probed twice for this device"); return -EINVAL; } data = micp_data_new(device); data->service = service; data->micp = bt_micp_new(btd_gatt_database_get_db(database), btd_device_get_gatt_db(device)); if (!data->micp) { error("unable to create MICP instance"); free(data); return -EINVAL; } micp_data_add(data); data->ready_id = bt_micp_ready_register(data->micp, micp_ready, service, NULL); bt_micp_set_user_data(data->micp, service); return 0; } static void micp_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct micp_data *data; char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); data = btd_service_get_user_data(service); if (!data) { error("MICP Service not handled by profile"); return; } micp_data_remove(data); } static int micp_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct bt_gatt_client *client = btd_device_get_gatt_client(device); struct micp_data *data = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); if (!data) { error("MICP Service not handled by profile"); return -EINVAL; } if (!bt_micp_attach(data->micp, client)) { error("MICP unable to attach"); return -EINVAL; } btd_service_connecting_complete(service, 0); return 0; } static int micp_connect(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); return 0; } static int micp_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { struct btd_gatt_database *database = btd_adapter_get_database(adapter); DBG("MICP path %s", adapter_get_path(adapter)); bt_micp_add_db(btd_gatt_database_get_db(database)); return 0; } static void micp_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { DBG("MICP remove adapter"); } static struct btd_profile micp_profile = { .name = "micp", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = MICS_UUID_STR, .device_probe = micp_probe, .device_remove = micp_remove, .accept = micp_accept, .connect = micp_connect, .disconnect = micp_disconnect, .adapter_probe = micp_server_probe, .adapter_remove = micp_server_remove, }; static unsigned int micp_id; static int micp_init(void) { int err; if (!(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) { DBG("D-Bus experimental not enabled"); return -ENOTSUP; } err = btd_profile_register(&micp_profile); if (err) return err; micp_id = bt_micp_register(micp_attached, micp_detached, NULL); return 0; } static void micp_exit(void) { if (g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL) { btd_profile_unregister(&micp_profile); bt_micp_unregister(micp_id); } } BLUETOOTH_PLUGIN_DEFINE(micp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, micp_init, micp_exit) bluez-5.87/profiles/audio/PaxHeaders/source.c0000644000000000000000000000005015063045254016247 xustar0020 atime=1783093627 20 ctime=1783094105 bluez-5.87/profiles/audio/source.c0000644000000000000000000002237315063045254015737 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright (C) 2009 Joao Paulo Rechi Vita * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "gdbus/gdbus.h" #include "src/log.h" #include "src/adapter.h" #include "src/device.h" #include "src/service.h" #include "src/error.h" #include "src/dbus-common.h" #include "src/shared/queue.h" #include "avdtp.h" #include "media.h" #include "a2dp.h" #include "source.h" struct source { struct btd_service *service; struct avdtp *session; struct avdtp_stream *stream; unsigned int cb_id; avdtp_session_state_t session_state; avdtp_state_t stream_state; source_state_t state; unsigned int connect_id; unsigned int disconnect_id; unsigned int avdtp_callback_id; }; struct source_state_callback { source_state_cb cb; struct btd_service *service; void *user_data; unsigned int id; }; static GSList *source_callbacks = NULL; static const char *str_state[] = { "SOURCE_STATE_DISCONNECTED", "SOURCE_STATE_CONNECTING", "SOURCE_STATE_CONNECTED", "SOURCE_STATE_PLAYING", }; static void source_set_state(struct source *source, source_state_t new_state) { struct btd_device *dev = btd_service_get_device(source->service); source_state_t old_state = source->state; GSList *l; source->state = new_state; DBG("State changed %s: %s -> %s", device_get_path(dev), str_state[old_state], str_state[new_state]); for (l = source_callbacks; l != NULL; l = l->next) { struct source_state_callback *cb = l->data; if (cb->service != source->service) continue; cb->cb(source->service, old_state, new_state, cb->user_data); } if (new_state != SOURCE_STATE_DISCONNECTED) return; if (source->session) { avdtp_unref(source->session); source->session = NULL; } } static void avdtp_state_callback(struct btd_device *dev, struct avdtp *session, avdtp_session_state_t old_state, avdtp_session_state_t new_state, void *user_data) { struct source *source = user_data; switch (new_state) { case AVDTP_SESSION_STATE_DISCONNECTED: source_set_state(source, SOURCE_STATE_DISCONNECTED); btd_service_disconnecting_complete(source->service, 0); break; case AVDTP_SESSION_STATE_CONNECTING: source_set_state(source, SOURCE_STATE_CONNECTING); break; case AVDTP_SESSION_STATE_CONNECTED: break; } source->session_state = new_state; } static void stream_state_changed(struct avdtp_stream *stream, avdtp_state_t old_state, avdtp_state_t new_state, struct avdtp_error *err, void *user_data) { struct btd_service *service = user_data; struct source *source = btd_service_get_user_data(service); if (err) return; switch (new_state) { case AVDTP_STATE_IDLE: if (source->connect_id > 0) { a2dp_cancel(source->connect_id); source->connect_id = 0; } if (source->disconnect_id > 0) { a2dp_cancel(source->disconnect_id); source->disconnect_id = 0; } if (source->session) { avdtp_unref(source->session); source->session = NULL; } source->stream = NULL; source->cb_id = 0; break; case AVDTP_STATE_OPEN: btd_service_connecting_complete(source->service, 0); source_set_state(source, SOURCE_STATE_CONNECTED); break; case AVDTP_STATE_STREAMING: source_set_state(source, SOURCE_STATE_PLAYING); break; case AVDTP_STATE_CONFIGURED: case AVDTP_STATE_CLOSING: case AVDTP_STATE_ABORTING: default: break; } source->stream_state = new_state; } static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep, struct avdtp_stream *stream, int err, void *user_data) { struct source *source = user_data; source->connect_id = 0; if (stream) return; avdtp_unref(source->session); source->session = NULL; btd_service_connecting_complete(source->service, err); } static void select_complete(struct avdtp *session, struct a2dp_sep *sep, GSList *caps, int err, void *user_data) { struct source *source = user_data; int id; source->connect_id = 0; if (err) goto failed; if (caps == NULL) goto failed; id = a2dp_config(session, sep, stream_setup_complete, caps, source); if (id == 0) goto failed; source->connect_id = id; return; failed: btd_service_connecting_complete(source->service, -EIO); avdtp_unref(source->session); source->session = NULL; } static void discovery_complete(struct avdtp *session, GSList *seps, int err, void *user_data) { struct source *source = user_data; int id; source->connect_id = 0; if (err) { avdtp_unref(source->session); source->session = NULL; goto failed; } DBG("Discovery complete"); id = a2dp_select_capabilities(source->session, AVDTP_SEP_TYPE_SOURCE, NULL, select_complete, source); if (id == 0) { err = -EIO; goto failed; } source->connect_id = id; return; failed: btd_service_connecting_complete(source->service, err); avdtp_unref(source->session); source->session = NULL; } gboolean source_setup_stream(struct btd_service *service, struct avdtp *session) { struct source *source = btd_service_get_user_data(service); if (source->connect_id > 0 || source->disconnect_id > 0) return FALSE; if (!source->session) { if (session) source->session = avdtp_ref(session); else source->session = a2dp_avdtp_get( btd_service_get_device(service)); if (!source->session) { DBG("Unable to get a session"); return FALSE; } } source->connect_id = a2dp_discover(source->session, discovery_complete, source); if (source->connect_id == 0) { avdtp_unref(source->session); source->session = NULL; return FALSE; } return TRUE; } int source_connect(struct btd_service *service) { struct source *source = btd_service_get_user_data(service); if (source->connect_id > 0 || source->disconnect_id > 0) return -EBUSY; if (source->state == SOURCE_STATE_CONNECTING) return -EBUSY; if (source->stream_state >= AVDTP_STATE_OPEN) return -EALREADY; if (!source_setup_stream(service, NULL)) { DBG("Failed to create a stream"); return -EIO; } DBG("stream creation in progress"); return 0; } static void source_free(struct btd_service *service) { struct source *source = btd_service_get_user_data(service); if (source->cb_id) avdtp_stream_remove_cb(source->session, source->stream, source->cb_id); if (source->session) { avdtp_unref(source->session); source->session = NULL; } if (source->connect_id > 0) { btd_service_connecting_complete(source->service, -ECANCELED); a2dp_cancel(source->connect_id); source->connect_id = 0; } if (source->disconnect_id > 0) { btd_service_disconnecting_complete(source->service, -ECANCELED); a2dp_cancel(source->disconnect_id); source->disconnect_id = 0; } avdtp_remove_state_cb(source->avdtp_callback_id); btd_service_unref(source->service); g_free(source); } void source_unregister(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); DBG("%s", device_get_path(dev)); source_free(service); } int source_init(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); struct source *source; DBG("%s", device_get_path(dev)); source = g_new0(struct source, 1); source->service = btd_service_ref(service); source->avdtp_callback_id = avdtp_add_state_cb(dev, avdtp_state_callback, source); btd_service_set_user_data(service, source); return 0; } gboolean source_new_stream(struct btd_service *service, struct avdtp *session, struct avdtp_stream *stream) { struct source *source = btd_service_get_user_data(service); if (source->stream) return FALSE; if (!source->session) source->session = avdtp_ref(session); source->stream = stream; source->cb_id = avdtp_stream_add_cb(session, stream, stream_state_changed, service); return TRUE; } int source_disconnect(struct btd_service *service) { struct source *source = btd_service_get_user_data(service); if (!source->session) return -ENOTCONN; /* cancel pending connect */ if (source->connect_id > 0) { avdtp_unref(source->session); source->session = NULL; a2dp_cancel(source->connect_id); source->connect_id = 0; btd_service_disconnecting_complete(source->service, 0); return 0; } /* disconnect already ongoing */ if (source->disconnect_id > 0) return -EBUSY; if (!source->stream) return -ENOTCONN; return avdtp_close(source->session, source->stream, FALSE); } unsigned int source_add_state_cb(struct btd_service *service, source_state_cb cb, void *user_data) { struct source_state_callback *state_cb; static unsigned int id = 0; state_cb = g_new(struct source_state_callback, 1); state_cb->cb = cb; state_cb->service = service; state_cb->user_data = user_data; state_cb->id = ++id; source_callbacks = g_slist_append(source_callbacks, state_cb); return state_cb->id; } gboolean source_remove_state_cb(unsigned int id) { GSList *l; for (l = source_callbacks; l != NULL; l = l->next) { struct source_state_callback *cb = l->data; if (cb && cb->id == id) { source_callbacks = g_slist_remove(source_callbacks, cb); g_free(cb); return TRUE; } } return FALSE; } bluez-5.87/profiles/audio/PaxHeaders/transport.c0000644000000000000000000000005015221752714017005 xustar0020 atime=1783093617 20 ctime=1783094105 bluez-5.87/profiles/audio/transport.c0000644000000000000000000022124415221752714016473 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2004-2009 Marcel Holtmann * Copyright 2023-2025 NXP * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "gdbus/gdbus.h" #include "btio/btio.h" #include "src/adapter.h" #include "src/device.h" #include "src/dbus-common.h" #include "src/log.h" #include "src/error.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/bap.h" #include "src/shared/bass.h" #include "src/shared/io.h" #include "src/btd.h" #ifdef HAVE_A2DP #include "avdtp.h" #include "a2dp.h" #include "sink.h" #include "source.h" #ifdef HAVE_AVRCP #include "avrcp.h" #endif #endif #ifdef HAVE_ASHA #include "asha.h" #endif #include "media.h" #include "transport.h" #include "vcp.h" #define MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport1" typedef enum { TRANSPORT_STATE_IDLE, /* Not acquired and suspended */ TRANSPORT_STATE_PENDING, /* Playing but not acquired */ /* Playing but not acquired, applicable only for transports * created by a broadcast sink */ TRANSPORT_STATE_BROADCASTING, TRANSPORT_STATE_REQUESTING, /* Acquire in progress */ TRANSPORT_STATE_ACTIVE, /* Acquired and playing */ TRANSPORT_STATE_SUSPENDING, /* Release in progress */ } transport_state_t; static const char *str_state[] = { "TRANSPORT_STATE_IDLE", "TRANSPORT_STATE_PENDING", "TRANSPORT_STATE_BROADCASTING", "TRANSPORT_STATE_REQUESTING", "TRANSPORT_STATE_ACTIVE", "TRANSPORT_STATE_SUSPENDING", }; struct media_request { DBusMessage *msg; guint id; }; struct media_owner { struct media_transport *transport; struct media_request *pending; char *name; guint watch; }; struct a2dp_transport { struct avdtp *session; uint16_t delay; int8_t volume; guint watch; guint resume_id; gboolean cancel_resume; guint cancel_id; }; struct bap_transport { struct bt_bap_stream *stream; unsigned int state_id; bool linked; struct bt_bap_qos qos; guint resume_id; struct iovec *meta; guint chan_id; /* desync variable is only writable when testing is enabled; * otherwise, it defaults to false, meaning the Enable/Acquire * operations synchronize input/output directions that share * the same ISO socket CID/CIG. */ bool desync; }; struct media_transport_ops { const char *uuid; const GDBusPropertyTable *properties; const GDBusPropertyTable *test_properties; void (*set_owner)(struct media_transport *transport, struct media_owner *owner); void (*remove_owner)(struct media_transport *transport, struct media_owner *owner); void *(*init)(struct media_transport *transport, void *stream); guint (*resume)(struct media_transport *transport, struct media_owner *owner); guint (*suspend)(struct media_transport *transport, struct media_owner *owner); void (*cancel)(struct media_transport *transport, guint id); void (*set_state)(struct media_transport *transport, transport_state_t state); void *(*get_stream)(struct media_transport *transport); int (*get_volume)(struct media_transport *transport); int (*set_volume)(struct media_transport *transport, int level); int (*set_delay)(struct media_transport *transport, uint16_t delay); void (*update_links)(const struct media_transport *transport); GDestroyNotify destroy; }; struct media_transport { char *path; /* Transport object path */ struct btd_device *device; /* Transport device */ struct btd_adapter *adapter; /* Transport adapter bcast*/ char *remote_endpoint; /* Transport remote SEP */ struct media_endpoint *endpoint; /* Transport endpoint */ struct media_owner *owner; /* Transport owner */ uint8_t *configuration; /* Transport configuration */ int size; /* Transport configuration size */ int fd; /* Transport file descriptor */ uint16_t imtu; /* Transport input mtu */ uint16_t omtu; /* Transport output mtu */ transport_state_t state; const struct media_transport_ops *ops; void *data; }; static GSList *transports = NULL; static const char *state2str(transport_state_t state) { switch (state) { case TRANSPORT_STATE_IDLE: case TRANSPORT_STATE_REQUESTING: return "idle"; case TRANSPORT_STATE_PENDING: return "pending"; case TRANSPORT_STATE_BROADCASTING: return "broadcasting"; case TRANSPORT_STATE_ACTIVE: case TRANSPORT_STATE_SUSPENDING: return "active"; } return NULL; } static gboolean state_in_use(transport_state_t state) { switch (state) { case TRANSPORT_STATE_IDLE: case TRANSPORT_STATE_PENDING: case TRANSPORT_STATE_BROADCASTING: return FALSE; case TRANSPORT_STATE_REQUESTING: case TRANSPORT_STATE_ACTIVE: case TRANSPORT_STATE_SUSPENDING: return TRUE; } return FALSE; } static struct media_transport * find_transport_by_bap_stream(const struct bt_bap_stream *stream) { GSList *l; for (l = transports; l; l = g_slist_next(l)) { struct media_transport *transport = l->data; const char *uuid = media_endpoint_get_uuid(transport->endpoint); struct bap_transport *bap; if (strcasecmp(uuid, PAC_SINK_UUID) && strcasecmp(uuid, PAC_SOURCE_UUID) && strcasecmp(uuid, BAA_SERVICE_UUID)) continue; bap = transport->data; if (bap->stream == stream) return transport; } return NULL; } static void transport_set_state(struct media_transport *transport, transport_state_t state) { transport_state_t old_state = transport->state; const char *str; if (old_state == state) return; transport->state = state; DBG("State changed %s: %s -> %s", transport->path, str_state[old_state], str_state[state]); str = state2str(state); if (g_strcmp0(str, state2str(old_state)) != 0) g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, "State"); /* Update transport specific data */ if (transport->ops && transport->ops->set_state) transport->ops->set_state(transport, state); } void media_transport_destroy(struct media_transport *transport) { char *path; path = g_strdup(transport->path); g_dbus_unregister_interface(btd_get_dbus_connection(), path, MEDIA_TRANSPORT_INTERFACE); g_free(path); } static struct media_request *media_request_create(DBusMessage *msg, guint id) { struct media_request *req; req = g_new0(struct media_request, 1); req->msg = dbus_message_ref(msg); req->id = id; DBG("Request created: method=%s id=%u", dbus_message_get_member(msg), id); return req; } static void media_request_reply(struct media_request *req, int err) { DBusMessage *reply; DBG("Request %s Reply %s", dbus_message_get_member(req->msg), strerror(err)); if (!err) reply = g_dbus_create_reply(req->msg, DBUS_TYPE_INVALID); else reply = g_dbus_create_error(req->msg, ERROR_INTERFACE ".Failed", "%s", strerror(err)); g_dbus_send_message(btd_get_dbus_connection(), reply); } static void media_owner_remove(struct media_owner *owner) { struct media_transport *transport = owner->transport; struct media_request *req = owner->pending; if (!req) return; DBG("Owner %s Request %s", owner->name, dbus_message_get_member(req->msg)); if (req->id && transport->ops && transport->ops->cancel) transport->ops->cancel(transport, req->id); owner->pending = NULL; if (req->msg) dbus_message_unref(req->msg); g_free(req); } static void media_owner_free(struct media_owner *owner) { struct media_transport *transport = owner->transport; DBG("Owner %s", owner->name); media_owner_remove(owner); if (transport) transport->owner = NULL; g_free(owner->name); g_free(owner); } static void linked_transport_remove_owner(void *data, void *user_data) { struct bt_bap_stream *stream = data; struct media_owner *owner = user_data; struct media_transport *transport; transport = find_transport_by_bap_stream(stream); if (!transport) { error("Unable to find transport"); return; } DBG("Transport %s Owner %s", transport->path, owner->name); transport->owner = NULL; } static guint media_transport_suspend(struct media_transport *transport, struct media_owner *owner) { if (!state_in_use(transport->state)) return 0; DBG("Transport %s Owner %s", transport->path, owner ? owner->name : ""); if (transport->ops && transport->ops->suspend) return transport->ops->suspend(transport, owner); return 0; } static void transport_bap_remove_owner(struct media_transport *transport, struct media_owner *owner) { struct bap_transport *bap = transport->data; if (bap && bap->linked && !bap->desync) queue_foreach(bt_bap_stream_io_get_links(bap->stream), linked_transport_remove_owner, owner); } static void media_transport_remove_owner(struct media_transport *transport) { struct media_owner *owner = transport->owner; if (!transport->owner) return; DBG("Transport %s Owner %s", transport->path, owner->name); /* Reply if owner has a pending request */ if (owner->pending) media_request_reply(owner->pending, EIO); transport->owner = NULL; if (transport->ops && transport->ops->remove_owner) transport->ops->remove_owner(transport, owner); if (owner->watch) g_dbus_remove_watch(btd_get_dbus_connection(), owner->watch); media_owner_free(owner); media_transport_suspend(transport, NULL); } static gboolean media_transport_set_fd(struct media_transport *transport, int fd, uint16_t imtu, uint16_t omtu) { if (transport->fd == fd) return TRUE; transport->fd = fd; transport->imtu = imtu; transport->omtu = omtu; info("%s: fd(%d) ready", transport->path, fd); return TRUE; } #ifdef HAVE_A2DP static void *transport_a2dp_get_stream(struct media_transport *transport) { struct a2dp_transport *a2dp = transport->data; struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint); if (!sep) return NULL; return a2dp_sep_get_stream(sep, a2dp->session); } static void a2dp_suspend_complete(struct avdtp *session, int err, void *user_data) { struct media_owner *owner = user_data; struct media_transport *transport = owner->transport; struct a2dp_transport *a2dp = transport->data; struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint); /* Release always succeeds */ if (owner->pending) { owner->pending->id = 0; media_request_reply(owner->pending, 0); media_owner_remove(owner); } a2dp_sep_unlock(sep, a2dp->session); transport_set_state(transport, TRANSPORT_STATE_IDLE); media_transport_remove_owner(transport); } static guint transport_a2dp_suspend(struct media_transport *transport, struct media_owner *owner) { struct a2dp_transport *a2dp = transport->data; struct media_endpoint *endpoint = transport->endpoint; struct a2dp_sep *sep = media_endpoint_get_sep(endpoint); if (owner != NULL) { if (a2dp->resume_id) { a2dp->cancel_resume = TRUE; return a2dp->resume_id; } return a2dp_suspend(a2dp->session, sep, a2dp_suspend_complete, owner); } transport_set_state(transport, TRANSPORT_STATE_IDLE); a2dp_sep_unlock(sep, a2dp->session); return 0; } static gboolean a2dp_cancel_resume_cb(void *user_data) { struct media_owner *owner = user_data; struct media_transport *transport = owner->transport; struct a2dp_transport *a2dp = transport->data; guint id; a2dp->cancel_id = 0; if (!owner->pending) goto fail; owner->pending->id = 0; /* The suspend fails e.g. if stream was closed/aborted. This happens if * SetConfiguration() was called while we were waiting for the START to * complete. * * We bail out from the Release() with error in that case. */ id = transport_a2dp_suspend(transport, owner); if (id) owner->pending->id = id; else goto fail; return FALSE; fail: media_transport_remove_owner(transport); return FALSE; } static void a2dp_resume_complete(struct avdtp *session, int err, void *user_data) { struct media_owner *owner = user_data; struct media_request *req = owner->pending; struct media_transport *transport = owner->transport; struct a2dp_transport *a2dp = transport->data; struct avdtp_stream *stream; int fd; uint16_t imtu, omtu; gboolean ret; a2dp->resume_id = 0; if (!req) goto fail; req->id = 0; if (err) goto fail; if (a2dp->cancel_resume) { DBG("cancel resume"); a2dp->cancel_id = g_idle_add(a2dp_cancel_resume_cb, owner); return; } stream = transport_a2dp_get_stream(transport); if (stream == NULL) goto fail; ret = avdtp_stream_get_transport(stream, &fd, &imtu, &omtu, NULL); if (ret == FALSE) goto fail; media_transport_set_fd(transport, fd, imtu, omtu); ret = g_dbus_send_reply(btd_get_dbus_connection(), req->msg, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_UINT16, &imtu, DBUS_TYPE_UINT16, &omtu, DBUS_TYPE_INVALID); if (ret == FALSE) goto fail; media_owner_remove(owner); transport_set_state(transport, TRANSPORT_STATE_ACTIVE); return; fail: media_transport_remove_owner(transport); } static guint transport_a2dp_resume(struct media_transport *transport, struct media_owner *owner) { struct a2dp_transport *a2dp = transport->data; struct media_endpoint *endpoint = transport->endpoint; struct a2dp_sep *sep = media_endpoint_get_sep(endpoint); guint id; if (a2dp->resume_id || a2dp->cancel_id) return 0; if (a2dp->session == NULL) { a2dp->session = a2dp_avdtp_get(transport->device); if (a2dp->session == NULL) return 0; } if (state_in_use(transport->state)) { id = a2dp_resume(a2dp->session, sep, a2dp_resume_complete, owner); goto done; } if (a2dp_sep_lock(sep, a2dp->session) == FALSE) return 0; id = a2dp_resume(a2dp->session, sep, a2dp_resume_complete, owner); if (id == 0) { a2dp_sep_unlock(sep, a2dp->session); return 0; } if (transport->state == TRANSPORT_STATE_IDLE) transport_set_state(transport, TRANSPORT_STATE_REQUESTING); done: a2dp->resume_id = id; a2dp->cancel_resume = FALSE; return id; } static void transport_a2dp_cancel(struct media_transport *transport, guint id) { struct a2dp_transport *a2dp = transport->data; /* a2dp_cancel() results to ABORT->IDLE->disconnect. For START we * instead wait the operation out. */ if (id == a2dp->resume_id) { a2dp->cancel_resume = TRUE; return; } a2dp_cancel(id); } static void transport_a2dp_remove_owner(struct media_transport *transport, struct media_owner *owner) { struct a2dp_transport *a2dp = transport->data; /* Cancel any pending operations for the owner */ if (a2dp->cancel_id) { g_source_remove(a2dp->cancel_id); a2dp->cancel_id = 0; } if (a2dp->resume_id) { a2dp_cancel(a2dp->resume_id); a2dp->resume_id = 0; } a2dp->cancel_resume = FALSE; } static int transport_a2dp_get_volume(struct media_transport *transport) { struct a2dp_transport *a2dp = transport->data; return a2dp->volume; } #ifdef HAVE_AVRCP static int transport_a2dp_src_set_volume(struct media_transport *transport, int level) { struct a2dp_transport *a2dp = transport->data; if (level < 0 || level > 127) return -EINVAL; if (a2dp->volume == level) return 0; return avrcp_set_volume(transport->device, level, false); } static int transport_a2dp_snk_set_volume(struct media_transport *transport, int level) { struct a2dp_transport *a2dp = transport->data; bool notify; if (level < 0 || level > 127) return -EINVAL; if (a2dp->volume == level) return 0; notify = a2dp->watch ? true : false; if (notify) { a2dp->volume = level; g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, "Volume"); } return avrcp_set_volume(transport->device, level, notify); } #endif static int transport_a2dp_snk_set_delay(struct media_transport *transport, uint16_t delay) { struct a2dp_transport *a2dp = transport->data; struct avdtp_stream *stream; if (a2dp->delay == delay) return 0; if (a2dp->session == NULL) { a2dp->session = a2dp_avdtp_get(transport->device); if (a2dp->session == NULL) return -EIO; } stream = media_transport_get_stream(transport); if (stream == NULL) return -EIO; if (a2dp->watch) { a2dp->delay = delay; g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, "Delay"); } return avdtp_delay_report(a2dp->session, stream, delay); } #endif /* HAVE_A2DP */ static void media_owner_exit(DBusConnection *connection, void *user_data) { struct media_owner *owner = user_data; DBG("Owner %s", owner->name); owner->watch = 0; media_owner_remove(owner); media_transport_remove_owner(owner->transport); } static void linked_transport_set_owner(void *data, void *user_data) { struct bt_bap_stream *stream = data; struct media_owner *owner = user_data; struct media_transport *transport; transport = find_transport_by_bap_stream(stream); if (!transport) { error("Unable to find transport"); return; } DBG("Transport %s Owner %s", transport->path, owner->name); transport->owner = owner; } static void transport_bap_set_owner(struct media_transport *transport, struct media_owner *owner) { struct bap_transport *bap = transport->data; if (bap && bap->linked && !bap->desync) queue_foreach(bt_bap_stream_io_get_links(bap->stream), linked_transport_set_owner, owner); } static void media_transport_set_owner(struct media_transport *transport, struct media_owner *owner) { DBG("Transport %s Owner %s", transport->path, owner->name); transport->owner = owner; if (transport->ops && transport->ops->set_owner) transport->ops->set_owner(transport, owner); owner->transport = transport; owner->watch = g_dbus_add_disconnect_watch(btd_get_dbus_connection(), owner->name, media_owner_exit, owner, NULL); } static struct media_owner *media_owner_create(DBusMessage *msg) { struct media_owner *owner; owner = g_new0(struct media_owner, 1); owner->name = g_strdup(dbus_message_get_sender(msg)); DBG("Owner created: sender=%s", owner->name); return owner; } static void media_owner_add(struct media_owner *owner, struct media_request *req) { DBG("Owner %s Request %s", owner->name, dbus_message_get_member(req->msg)); owner->pending = req; } static void *transport_bap_get_stream(struct media_transport *transport) { struct bap_transport *bap = transport->data; return bap->stream; } static guint media_transport_resume(struct media_transport *transport, struct media_owner *owner) { DBG("Transport %s Owner %s", transport->path, owner ? owner->name : ""); if (transport->ops && transport->ops->resume) return transport->ops->resume(transport, owner); return 0; } static DBusMessage *acquire(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_transport *transport = data; struct media_owner *owner; struct media_request *req = NULL; guint id; if (transport->owner != NULL) return btd_error_not_authorized(msg); if (transport->state >= TRANSPORT_STATE_REQUESTING) return btd_error_not_authorized(msg); owner = media_owner_create(msg); if (!strcmp(media_endpoint_get_uuid(transport->endpoint), BAA_SERVICE_UUID) || !strcmp(media_endpoint_get_uuid(transport->endpoint), BCAA_SERVICE_UUID)) { req = media_request_create(msg, 0x00); media_owner_add(owner, req); media_transport_set_owner(transport, owner); } id = media_transport_resume(transport, owner); if (id == 0) { media_owner_free(owner); return btd_error_not_authorized(msg); } if (!req) { req = media_request_create(msg, id); media_owner_add(owner, req); media_transport_set_owner(transport, owner); } return NULL; } static DBusMessage *try_acquire(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_transport *transport = data; struct media_owner *owner; struct media_request *req; guint id; if (transport->owner != NULL) return btd_error_not_authorized(msg); if (transport->state >= TRANSPORT_STATE_REQUESTING) return btd_error_not_authorized(msg); if ((transport->state != TRANSPORT_STATE_PENDING) && (transport->state != TRANSPORT_STATE_BROADCASTING)) return btd_error_not_available(msg); owner = media_owner_create(msg); id = media_transport_resume(transport, owner); if (id == 0) { media_owner_free(owner); return btd_error_not_authorized(msg); } req = media_request_create(msg, id); media_owner_add(owner, req); media_transport_set_owner(transport, owner); return NULL; } static void bap_stop_complete(struct bt_bap_stream *stream, uint8_t code, uint8_t reason, void *user_data) { struct media_owner *owner = user_data; struct media_request *req; struct media_transport *transport; if (!owner) return; req = owner->pending; /* Release always succeeds */ if (req) { req->id = 0; media_request_reply(req, 0); media_owner_remove(owner); } transport = owner->transport; if (transport) { transport_set_state(transport, TRANSPORT_STATE_IDLE); media_transport_remove_owner(transport); } } static void bap_disable_complete(struct bt_bap_stream *stream, uint8_t code, uint8_t reason, void *user_data) { bap_stop_complete(stream, code, reason, user_data); } static DBusMessage *release(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_transport *transport = data; struct media_owner *owner = transport->owner; const char *sender; struct media_request *req; guint id; sender = dbus_message_get_sender(msg); if (owner == NULL || g_strcmp0(owner->name, sender) != 0) return btd_error_not_authorized(msg); DBG("Owner %s", owner->name); if (owner->pending) { const char *member; member = dbus_message_get_member(owner->pending->msg); /* Cancel Acquire request if that exist */ if (g_str_equal(member, "Acquire")) { media_request_reply(owner->pending, ECANCELED); media_owner_remove(owner); } else { return btd_error_in_progress(msg); } } transport_set_state(transport, TRANSPORT_STATE_SUSPENDING); id = media_transport_suspend(transport, owner); if (id == 0) { media_transport_remove_owner(transport); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } req = media_request_create(msg, id); media_owner_add(owner, req); return NULL; } static gboolean get_device(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; const char *path; if (transport->device) path = device_get_path(transport->device); else path = adapter_get_path(transport->adapter); dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); return TRUE; } static gboolean get_uuid(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; const char *uuid = media_endpoint_get_uuid(transport->endpoint); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); return TRUE; } static gboolean get_codec(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; uint8_t codec = media_endpoint_get_codec(transport->endpoint); dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &codec); return TRUE; } static gboolean get_configuration(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; DBusMessageIter array; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array); dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &transport->configuration, transport->size); dbus_message_iter_close_container(iter, &array); return TRUE; } static gboolean get_state(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; const char *state = state2str(transport->state); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &state); return TRUE; } #ifdef HAVE_A2DP static gboolean delay_reporting_exists(const GDBusPropertyTable *property, void *data) { struct media_transport *transport = data; struct media_endpoint *endpoint = transport->endpoint; struct avdtp_stream *stream; /* Local A2DP sink decides itself if it has delay reporting */ if (!strcmp(media_endpoint_get_uuid(endpoint), A2DP_SINK_UUID)) return media_endpoint_get_delay_reporting(endpoint); stream = media_transport_get_stream(transport); if (stream == NULL) return FALSE; return avdtp_stream_has_delay_reporting(stream); } static gboolean get_delay_report(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; struct a2dp_transport *a2dp = transport->data; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &a2dp->delay); return TRUE; } static int media_transport_set_delay(struct media_transport *transport, uint16_t delay) { DBG("Transport %s delay %d", transport->path, delay); if (transport->ops && transport->ops->set_delay) return transport->ops->set_delay(transport, delay); return 0; } static void set_delay_report(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *data) { struct media_transport *transport = data; struct media_owner *owner = transport->owner; const char *sender; uint16_t arg; int err; if (owner != NULL) { /* If the transport is acquired, do not allow to modify * the delay anyone but the owner. */ sender = g_dbus_pending_property_get_sender(id); if (g_strcmp0(owner->name, sender) != 0) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".NotAuthorized", "Operation Not Authorized"); return; } } if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Expected UINT16"); return; } dbus_message_iter_get_basic(iter, &arg); err = media_transport_set_delay(transport, arg); if (err) { error("Unable to set delay: %s (%d)", strerror(-err), err); g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", "Internal error %s (%d)", strerror(-err), err); return; } g_dbus_pending_property_success(id); } #endif /* HAVE_A2DP */ static int media_transport_get_volume(struct media_transport *transport, int *volume) { if (transport->ops && transport->ops->get_volume) { int ret = transport->ops->get_volume(transport); if (ret < 0) return ret; *volume = ret; return 0; } return -EINVAL; } static gboolean volume_exists(const GDBusPropertyTable *property, void *data) { struct media_transport *transport = data; int volume; if (media_transport_get_volume(transport, &volume)) return FALSE; return volume >= 0; } static gboolean get_volume(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; int level; uint16_t volume; if (media_transport_get_volume(transport, &level)) return FALSE; volume = level; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &volume); return TRUE; } static int media_transport_set_volume(struct media_transport *transport, int level) { DBG("Transport %s level %d", transport->path, level); if (transport->ops && transport->ops->set_volume) return transport->ops->set_volume(transport, level); return 0; } static void set_volume(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *data) { struct media_transport *transport = data; uint16_t arg; int err; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Expected UINT16"); return; } dbus_message_iter_get_basic(iter, &arg); err = media_transport_set_volume(transport, arg); if (err == -EINVAL) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid volume value"); return; } else if (err) { error("Unable to set volume: %s (%d)", strerror(-err), err); g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", "Internal error %s (%d)", strerror(-err), err); return; } g_dbus_pending_property_success(id); } static gboolean endpoint_exists(const GDBusPropertyTable *property, void *data) { struct media_transport *transport = data; return transport->remote_endpoint != NULL; } static gboolean get_endpoint(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &transport->remote_endpoint); return TRUE; } static DBusMessage *select_transport(DBusConnection *conn, DBusMessage *msg, void *data); static DBusMessage *unselect_transport(DBusConnection *conn, DBusMessage *msg, void *data); static const GDBusMethodTable transport_methods[] = { { GDBUS_ASYNC_METHOD("Acquire", NULL, GDBUS_ARGS({ "fd", "h" }, { "mtu_r", "q" }, { "mtu_w", "q" }), acquire) }, { GDBUS_ASYNC_METHOD("TryAcquire", NULL, GDBUS_ARGS({ "fd", "h" }, { "mtu_r", "q" }, { "mtu_w", "q" }), try_acquire) }, { GDBUS_ASYNC_METHOD("Release", NULL, NULL, release) }, { GDBUS_ASYNC_METHOD("Select", NULL, NULL, select_transport) }, { GDBUS_ASYNC_METHOD("Unselect", NULL, NULL, unselect_transport) }, { }, }; #ifdef HAVE_A2DP static const GDBusPropertyTable transport_a2dp_properties[] = { { "Device", "o", get_device }, { "UUID", "s", get_uuid }, { "Codec", "y", get_codec }, { "Configuration", "ay", get_configuration }, { "State", "s", get_state }, { "Delay", "q", get_delay_report, set_delay_report, delay_reporting_exists }, { "Volume", "q", get_volume, set_volume, volume_exists }, { "Endpoint", "o", get_endpoint, NULL, endpoint_exists, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { } }; #endif /* HAVE_A2DP */ static void append_io_qos(DBusMessageIter *dict, struct bt_bap_io_qos *qos) { dict_append_entry(dict, "Interval", DBUS_TYPE_UINT32, &qos->interval); dict_append_entry(dict, "Latency", DBUS_TYPE_UINT16, &qos->latency); dict_append_entry(dict, "SDU", DBUS_TYPE_UINT16, &qos->sdu); dict_append_entry(dict, "PHY", DBUS_TYPE_BYTE, &qos->phys); dict_append_entry(dict, "Retransmissions", DBUS_TYPE_BYTE, &qos->rtn); } static gboolean get_ucast_qos(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; DBusMessageIter dict; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); dict_append_entry(&dict, "CIG", DBUS_TYPE_BYTE, &bap->qos.ucast.cig_id); dict_append_entry(&dict, "CIS", DBUS_TYPE_BYTE, &bap->qos.ucast.cis_id); dict_append_entry(&dict, "Framing", DBUS_TYPE_BYTE, &bap->qos.ucast.framing); dict_append_entry(&dict, "PresentationDelay", DBUS_TYPE_UINT32, &bap->qos.ucast.delay); append_io_qos(&dict, &bap->qos.ucast.io_qos); dbus_message_iter_close_container(iter, &dict); return TRUE; } static gboolean get_location(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; uint32_t location = bt_bap_stream_get_location(bap->stream); dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &location); return TRUE; } static gboolean get_metadata(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; struct iovec *meta = bt_bap_stream_get_metadata(bap->stream); DBusMessageIter array; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array); if (meta) dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &meta->iov_base, meta->iov_len); dbus_message_iter_close_container(iter, &array); return TRUE; } static void bap_metadata_complete(struct bt_bap_stream *stream, uint8_t code, uint8_t reason, void *user_data) { GDBusPendingPropertySet id = PTR_TO_UINT(user_data); if (code) g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", "Unable to set metadata"); else g_dbus_pending_property_success(id); } static void set_metadata(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; DBusMessageIter array; struct iovec iov; int len; int ret; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Expected ARRAY"); return; } dbus_message_iter_recurse(iter, &array); dbus_message_iter_get_fixed_array(&array, &iov.iov_base, &len); iov.iov_len = len; ret = bt_bap_stream_metadata(bap->stream, &iov, bap_metadata_complete, UINT_TO_PTR(id)); if (!ret) g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid metadata"); } static gboolean links_exists(const GDBusPropertyTable *property, void *data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; return bap->linked; } static void append_link(void *data, void *user_data) { struct bt_bap_stream *stream = data; DBusMessageIter *array = user_data; struct media_transport *transport; if (!stream) return; transport = find_transport_by_bap_stream(stream); if (!transport) { error("Unable to find transport"); return; } dbus_message_iter_append_basic(array, DBUS_TYPE_OBJECT_PATH, &transport->path); } static gboolean get_links(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; struct queue *links = bt_bap_stream_io_get_links(bap->stream); DBusMessageIter array; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH_AS_STRING, &array); queue_foreach(links, append_link, &array); dbus_message_iter_close_container(iter, &array); return TRUE; } static struct media_transport *find_transport_by_path(const char *path) { GSList *l; for (l = transports; l; l = g_slist_next(l)) { struct media_transport *transport = l->data; if (g_str_equal(path, transport->path)) return transport; } return NULL; } static void set_links(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *user_data) { struct media_transport *transport = user_data; struct bap_transport *bap = transport->data; DBusMessageIter array; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } dbus_message_iter_recurse(iter, &array); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_OBJECT_PATH) { struct media_transport *link; struct bap_transport *bap_link; const char *path; dbus_message_iter_get_basic(&array, &path); link = find_transport_by_path(path); if (!link) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } bap_link = link->data; /* Link stream */ bt_bap_stream_io_link(bap->stream, bap_link->stream); dbus_message_iter_next(&array); } g_dbus_pending_property_success(id); } static gboolean get_desync(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &bap->desync); return TRUE; } static void transport_desynchronize(void *data, void *user_data) { struct bt_bap_stream *link = data; bool desync = PTR_TO_UINT(user_data); struct media_transport *transport; struct bap_transport *bap; transport = find_transport_by_bap_stream(link); bap = transport->data; bap->desync = desync; g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, "Desynchronized"); } static void set_desync(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Expected BOOLEAN"); return; } dbus_message_iter_get_basic(iter, &bap->desync); g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, "Desynchronized"); /* Set desynchronized for all the links */ queue_foreach(bt_bap_stream_io_get_links(bap->stream), transport_desynchronize, UINT_TO_PTR(bap->desync)); g_dbus_pending_property_success(id); } static gboolean qos_ucast_exists(const GDBusPropertyTable *property, void *data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; return bap->qos.ucast.io_qos.phys != 0x00; } static const GDBusPropertyTable transport_bap_uc_properties[] = { { "Device", "o", get_device }, { "UUID", "s", get_uuid }, { "Codec", "y", get_codec }, { "Configuration", "ay", get_configuration }, { "State", "s", get_state }, { "QoS", "a{sv}", get_ucast_qos, NULL, qos_ucast_exists }, { "Endpoint", "o", get_endpoint, NULL, endpoint_exists }, { "Location", "u", get_location }, { "Metadata", "ay", get_metadata, set_metadata }, { "Links", "ao", get_links, NULL, links_exists }, { "Volume", "q", get_volume, set_volume, volume_exists }, { } }; static const GDBusPropertyTable transport_bap_uc_test_properties[] = { { "Device", "o", get_device }, { "UUID", "s", get_uuid }, { "Codec", "y", get_codec }, { "Configuration", "ay", get_configuration }, { "State", "s", get_state }, { "QoS", "a{sv}", get_ucast_qos, NULL, qos_ucast_exists }, { "Endpoint", "o", get_endpoint, NULL, endpoint_exists }, { "Location", "u", get_location }, { "Metadata", "ay", get_metadata, set_metadata }, { "Links", "ao", get_links, NULL, links_exists }, { "Volume", "q", get_volume, set_volume, volume_exists }, { "Desynchronized", "b", get_desync, set_desync, links_exists}, { } }; static gboolean get_bcast_qos(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; DBusMessageIter dict; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); dict_append_entry(&dict, "BIG", DBUS_TYPE_BYTE, &bap->qos.bcast.big); dict_append_entry(&dict, "BIS", DBUS_TYPE_BYTE, &bap->qos.bcast.bis); dict_append_entry(&dict, "SyncFactor", DBUS_TYPE_BYTE, &bap->qos.bcast.sync_factor); dict_append_entry(&dict, "Packing", DBUS_TYPE_BYTE, &bap->qos.bcast.packing); dict_append_entry(&dict, "Framing", DBUS_TYPE_BYTE, &bap->qos.bcast.framing); dict_append_entry(&dict, "Encryption", DBUS_TYPE_BYTE, &bap->qos.bcast.encryption); if (bap->qos.bcast.bcode) dict_append_array(&dict, "BCode", DBUS_TYPE_BYTE, &bap->qos.bcast.bcode->iov_base, bap->qos.bcast.bcode->iov_len); dict_append_entry(&dict, "Options", DBUS_TYPE_BYTE, &bap->qos.bcast.options); dict_append_entry(&dict, "Skip", DBUS_TYPE_UINT16, &bap->qos.bcast.skip); dict_append_entry(&dict, "SyncTimeout", DBUS_TYPE_UINT16, &bap->qos.bcast.sync_timeout); dict_append_entry(&dict, "SyncType", DBUS_TYPE_BYTE, &bap->qos.bcast.sync_cte_type); dict_append_entry(&dict, "MSE", DBUS_TYPE_BYTE, &bap->qos.bcast.mse); dict_append_entry(&dict, "Timeout", DBUS_TYPE_UINT16, &bap->qos.bcast.timeout); dict_append_entry(&dict, "PresentationDelay", DBUS_TYPE_UINT32, &bap->qos.bcast.delay); append_io_qos(&dict, &bap->qos.bcast.io_qos); dbus_message_iter_close_container(iter, &dict); return TRUE; } static gboolean qos_bcast_exists(const GDBusPropertyTable *property, void *data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; return bap->qos.bcast.io_qos.phys != 0x00; } static void bcast_qos_set(void *user_data, int err) { GDBusPendingPropertySet id = GPOINTER_TO_UINT(user_data); if (!err) g_dbus_pending_property_success(id); else g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", "Failed to set Broadcast Code"); } static void bap_update_bcast_qos(const struct media_transport *transport); static void set_bcast_qos(const GDBusPropertyTable *property, DBusMessageIter *dict, GDBusPendingPropertySet id, void *data) { DBusMessageIter array, entry, value; struct media_transport *transport = data; struct bap_transport *bap = transport->data; struct bt_bap_qos *bap_qos = bt_bap_stream_get_qos(bap->stream); char *key; dbus_message_iter_recurse(dict, &array); dbus_message_iter_recurse(&array, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); if (!strcasecmp(key, "BCode")) { uint8_t *val; int len; DBusMessageIter array; uint8_t empty_bcode[BT_BASS_BCAST_CODE_SIZE] = {0}; dbus_message_iter_recurse(&value, &array); dbus_message_iter_get_fixed_array(&array, &val, &len); if (len > BT_BASS_BCAST_CODE_SIZE) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } if (!memcmp(val, empty_bcode, len)) { /* If the user did not provide a Broadcast Code * for the encrypted stream, request the code from * Broadcast Assistants, if any are available. */ bt_bap_req_bcode(bap->stream, bcast_qos_set, GUINT_TO_POINTER(id)); return; } bap_qos->bcast.bcode = util_iov_new(val, len); } bt_bap_stream_qos(bap->stream, bap_qos, NULL, NULL); bap_update_bcast_qos(transport); g_dbus_pending_property_success(id); } static const GDBusPropertyTable transport_bap_bc_properties[] = { { "Device", "o", get_device }, { "UUID", "s", get_uuid }, { "Codec", "y", get_codec }, { "Configuration", "ay", get_configuration }, { "State", "s", get_state }, { "QoS", "a{sv}", get_bcast_qos, set_bcast_qos, qos_bcast_exists }, { "Endpoint", "o", get_endpoint, NULL, endpoint_exists }, { "Location", "u", get_location }, { "Metadata", "ay", get_metadata, set_metadata }, { "Links", "ao", get_links, set_links, NULL }, { } }; #ifdef HAVE_ASHA static gboolean get_asha_delay(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; struct bt_asha_device *asha_dev = transport->data; uint16_t delay; // Delay property is in 1/10ths of ms, while ASHA RenderDelay is in ms delay = bt_asha_device_get_render_delay(asha_dev) * 10; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &delay); return TRUE; } static const GDBusPropertyTable transport_asha_properties[] = { { "Device", "o", get_device }, { "Endpoint", "o", get_endpoint, NULL, endpoint_exists }, { "UUID", "s", get_uuid }, { "Codec", "y", get_codec }, { "State", "s", get_state }, { "Delay", "q", get_asha_delay }, { "Volume", "q", get_volume, set_volume, volume_exists }, { } }; #endif /* HAVE_ASHA */ #ifdef HAVE_A2DP static void transport_a2dp_destroy(void *data) { struct a2dp_transport *a2dp = data; if (a2dp->session) avdtp_unref(a2dp->session); free(a2dp); } static void transport_a2dp_src_destroy(void *data) { struct a2dp_transport *a2dp = data; if (a2dp->watch) sink_remove_state_cb(a2dp->watch); transport_a2dp_destroy(data); } static void transport_a2dp_snk_destroy(void *data) { struct a2dp_transport *a2dp = data; if (a2dp->watch) source_remove_state_cb(a2dp->watch); transport_a2dp_destroy(data); } #endif /* HAVE_A2DP */ static void media_transport_free(void *data) { struct media_transport *transport = data; transports = g_slist_remove(transports, transport); if (transport->owner) media_transport_remove_owner(transport); if (transport->ops && transport->ops->destroy) transport->ops->destroy(transport->data); g_free(transport->remote_endpoint); g_free(transport->configuration); g_free(transport->path); g_free(transport); } static void transport_update_playing(struct media_transport *transport, gboolean playing) { DBG("%s State=%s Playing=%d", transport->path, str_state[transport->state], playing); if (playing == FALSE) { if (!strcmp(media_endpoint_get_uuid(transport->endpoint), BAA_SERVICE_UUID)) { if ((transport->state == TRANSPORT_STATE_BROADCASTING) || (transport->state == TRANSPORT_STATE_ACTIVE)) transport_set_state(transport, TRANSPORT_STATE_IDLE); } else { if (transport->state == TRANSPORT_STATE_PENDING) transport_set_state(transport, TRANSPORT_STATE_IDLE); else if (transport->state == TRANSPORT_STATE_ACTIVE) { /* Remove owner */ if (transport->owner != NULL) media_transport_remove_owner(transport); } } } else if (transport->state == TRANSPORT_STATE_IDLE) { if (!strcmp(media_endpoint_get_uuid(transport->endpoint), BAA_SERVICE_UUID)) transport_set_state(transport, TRANSPORT_STATE_BROADCASTING); else transport_set_state(transport, TRANSPORT_STATE_PENDING); } } static DBusMessage *select_transport(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_transport *transport = data; if (transport->owner != NULL) return btd_error_not_authorized(msg); if (transport->state >= TRANSPORT_STATE_REQUESTING) return btd_error_not_authorized(msg); if (!strcmp(media_endpoint_get_uuid(transport->endpoint), BAA_SERVICE_UUID)) { transport_update_playing(transport, TRUE); } return dbus_message_new_method_return(msg); } static DBusMessage *unselect_transport(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_transport *transport = data; if (!strcmp(media_endpoint_get_uuid(transport->endpoint), BAA_SERVICE_UUID)) { transport_update_playing(transport, FALSE); } return dbus_message_new_method_return(msg); } #ifdef HAVE_A2DP static void sink_state_changed(struct btd_service *service, sink_state_t old_state, sink_state_t new_state, void *user_data) { struct media_transport *transport = user_data; if (new_state == SINK_STATE_PLAYING) transport_update_playing(transport, TRUE); else transport_update_playing(transport, FALSE); } static void source_state_changed(struct btd_service *service, source_state_t old_state, source_state_t new_state, void *user_data) { struct media_transport *transport = user_data; if (new_state == SOURCE_STATE_PLAYING) transport_update_playing(transport, TRUE); else transport_update_playing(transport, FALSE); } static void *transport_a2dp_src_init(struct media_transport *transport, void *stream) { struct btd_service *service; struct a2dp_transport *a2dp; service = btd_device_get_service(transport->device, A2DP_SINK_UUID); if (!service) return NULL; a2dp = new0(struct a2dp_transport, 1); a2dp->volume = -1; a2dp->watch = sink_add_state_cb(service, sink_state_changed, transport); return a2dp; } static void *transport_a2dp_snk_init(struct media_transport *transport, void *stream) { struct btd_service *service; struct a2dp_transport *a2dp; service = btd_device_get_service(transport->device, A2DP_SOURCE_UUID); if (!service) return NULL; a2dp = new0(struct a2dp_transport, 1); a2dp->volume = 127; a2dp->watch = source_add_state_cb(service, source_state_changed, transport); return a2dp; } #endif /* HAVE_A2DP */ static void bap_enable_complete(struct bt_bap_stream *stream, uint8_t code, uint8_t reason, void *user_data) { struct media_owner *owner = user_data; if (code) media_transport_remove_owner(owner->transport); } static void bap_resume_complete(struct media_transport *transport) { struct bap_transport *bap = transport->data; struct media_owner *owner = transport->owner; DBG("stream %p owner %p resume complete", bap->stream, owner); if (bap->resume_id) { g_source_remove(bap->resume_id); bap->resume_id = 0; } if (!owner) return; if (owner->pending) owner->pending->id = 0; if (transport->fd < 0) { media_transport_remove_owner(transport); return; } if (owner->pending) { gboolean ret; ret = g_dbus_send_reply(btd_get_dbus_connection(), owner->pending->msg, DBUS_TYPE_UNIX_FD, &transport->fd, DBUS_TYPE_UINT16, &transport->imtu, DBUS_TYPE_UINT16, &transport->omtu, DBUS_TYPE_INVALID); if (!ret) { media_transport_remove_owner(transport); return; } } media_owner_remove(owner); transport_set_state(transport, TRANSPORT_STATE_ACTIVE); } static void bap_update_links(const struct media_transport *transport); static bool match_link_transport(const void *data, const void *user_data) { const struct bt_bap_stream *stream = data; const struct media_transport *transport; transport = find_transport_by_bap_stream(stream); if (!transport) return false; bap_update_links(transport); return true; } static void transport_bap_update_links_uc( const struct media_transport *transport) { struct bap_transport *bap = transport->data; struct queue *links = bt_bap_stream_io_get_links(bap->stream); if (bap->linked == !queue_isempty(links)) return; bap->linked = !queue_isempty(links); /* Check if the links transport has been create yet */ if (bap->linked && !queue_find(links, match_link_transport, NULL)) { bap->linked = false; return; } g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, "Links"); g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, "Desynchronized"); DBG("stream %p linked %s", bap->stream, bap->linked ? "true" : "false"); } static void transport_bap_update_links_bc( const struct media_transport *transport) { struct bap_transport *bap = transport->data; struct queue *links = bt_bap_stream_io_get_links(bap->stream); if (!queue_isempty(links)) bap->linked = true; else bap->linked = false; g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, "Links"); DBG("stream %p linked %s", bap->stream, bap->linked ? "true" : "false"); } static void bap_update_links(const struct media_transport *transport) { if (transport->ops && transport->ops->update_links) transport->ops->update_links(transport); } static void bap_update_qos(const struct media_transport *transport) { struct bap_transport *bap = transport->data; struct bt_bap_qos *qos; qos = bt_bap_stream_get_qos(bap->stream); if (!memcmp(qos, &bap->qos, sizeof(struct bt_bap_qos))) return; bap->qos = *qos; g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, "QoS"); } static gboolean bap_resume_complete_cb(void *data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; bap->resume_id = 0; bap_resume_complete(transport); return FALSE; } static gboolean bap_resume_wait_cb(void *data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; struct media_owner *owner = transport->owner; /* bap_state_changed will call completion callback when ready */ DBG("stream %p owner %p resume wait", bap->stream, owner); bap->resume_id = 0; if (owner && owner->pending) owner->pending->id = 0; return FALSE; } static void bap_update_bcast_qos(const struct media_transport *transport) { struct bap_transport *bap = transport->data; struct bt_bap_qos *qos; qos = bt_bap_stream_get_qos(bap->stream); if (!memcmp(qos, &bap->qos, sizeof(struct bt_bap_qos))) return; bap->qos = *qos; g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, "QoS"); g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, "Codec"); g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, "Configuration"); } static guint transport_bap_resume(struct media_transport *transport, struct media_owner *owner) { struct bap_transport *bap = transport->data; guint id; if (!bap->stream) return 0; if (bap->resume_id) return 0; bap_update_links(transport); switch (bt_bap_stream_get_state(bap->stream)) { case BT_BAP_STREAM_STATE_ENABLING: bap_enable_complete(bap->stream, 0x00, 0x00, owner); bap->resume_id = g_idle_add(bap_resume_wait_cb, transport); return bap->resume_id; case BT_BAP_STREAM_STATE_STREAMING: bap->resume_id = g_idle_add(bap_resume_complete_cb, transport); return bap->resume_id; } id = bt_bap_stream_enable(bap->stream, bap->desync ? false : bap->linked, NULL, bap_enable_complete, owner); if (!id) return 0; if (transport->state == TRANSPORT_STATE_IDLE) transport_set_state(transport, TRANSPORT_STATE_REQUESTING); return id; } static void update_links(void *data, void *user_data) { struct bt_bap_stream *link = data; struct media_transport *transport; transport = find_transport_by_bap_stream(link); if (!transport) { error("Unable to find transport"); return; } bap_update_links(transport); } static void transport_unlink(void *data, void *user_data) { struct bt_bap_stream *link = data; struct bt_bap_stream *stream = user_data; struct media_transport *transport; transport = find_transport_by_bap_stream(link); if (!transport) { error("Unable to find transport"); return; } bt_bap_stream_io_unlink(link, stream); bap_update_links(transport); /* Emit property changed for all remaining links */ queue_foreach(bt_bap_stream_io_get_links(link), update_links, NULL); } static guint transport_bap_suspend(struct media_transport *transport, struct media_owner *owner) { struct bap_transport *bap = transport->data; struct queue *links = bt_bap_stream_io_get_links(bap->stream); bt_bap_stream_func_t func = NULL; guint id; if (!bap->stream) return 0; if (owner) func = bap_disable_complete; else transport_set_state(transport, TRANSPORT_STATE_IDLE); if (bt_bap_stream_get_type(bap->stream) == BT_BAP_STREAM_TYPE_BCAST) /* Unlink stream from all its links */ queue_foreach(links, transport_unlink, bap->stream); bap_update_links(transport); id = bt_bap_stream_disable(bap->stream, bap->linked, func, owner); if (bt_bap_stream_get_type(bap->stream) == BT_BAP_STREAM_TYPE_BCAST) { if (transport->owner == owner) bap_disable_complete(bap->stream, 0x00, 0x00, owner); return 0; } return id; } static void bap_clear_chan(struct bap_transport *bap) { if (bap->chan_id) { g_source_remove(bap->chan_id); bap->chan_id = 0; } } static void transport_bap_cancel(struct media_transport *transport, guint id) { struct bap_transport *bap = transport->data; bap_clear_chan(bap); if (id == bap->resume_id && bap->resume_id) { g_source_remove(bap->resume_id); bap->resume_id = 0; return; } if (!bap->stream) return; bt_bap_stream_cancel(bap->stream, id); } static void link_set_state(void *data, void *user_data) { struct bt_bap_stream *stream = data; transport_state_t state = PTR_TO_UINT(user_data); struct media_transport *transport; transport = find_transport_by_bap_stream(stream); if (!transport) { error("Unable to find transport"); return; } transport_set_state(transport, state); } static void transport_bap_set_state(struct media_transport *transport, transport_state_t state) { struct bap_transport *bap = transport->data; if (!bap->linked || bap->desync) return; /* Update links */ queue_foreach(bt_bap_stream_io_get_links(bap->stream), link_set_state, UINT_TO_PTR(state)); } static void bap_metadata_changed(struct media_transport *transport) { struct bap_transport *bap = transport->data; struct iovec *meta; /* Update metadata if it has changed */ meta = bt_bap_stream_get_metadata(bap->stream); DBG("stream %p: metadata %p old %p", bap->stream, meta, bap->meta); if (util_iov_memcmp(meta, bap->meta)) { util_iov_free(bap->meta, 1); bap->meta = util_iov_dup(meta, 1); g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, "Metadata"); } } static gboolean bap_transport_fd_ready(GIOChannel *chan, GIOCondition cond, gpointer data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; int fd; uint16_t imtu, omtu; GError *err = NULL; if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) { error("Transport connection failed"); goto done; } if (!bt_io_get(chan, &err, BT_IO_OPT_OMTU, &omtu, BT_IO_OPT_IMTU, &imtu, BT_IO_OPT_INVALID)) { error("%s", err->message); goto done; } fd = g_io_channel_unix_get_fd(chan); media_transport_set_fd(transport, fd, imtu, omtu); transport_update_playing(transport, TRUE); done: bap->chan_id = 0; bap_resume_complete(transport); return FALSE; } static void bap_state_changed(struct bt_bap_stream *stream, uint8_t old_state, uint8_t new_state, void *user_data) { struct media_transport *transport = user_data; struct bap_transport *bap = transport->data; struct media_owner *owner = transport->owner; struct io *io; GIOChannel *chan; int fd; if (bap->stream != stream) return; DBG("stream %p: %s(%u) -> %s(%u)", stream, bt_bap_stream_statestr(old_state), old_state, bt_bap_stream_statestr(new_state), new_state); switch (new_state) { case BT_BAP_STREAM_STATE_IDLE: case BT_BAP_STREAM_STATE_CONFIG: case BT_BAP_STREAM_STATE_QOS: /* If a request is pending wait it to complete */ if (owner && owner->pending) return; bap_update_links(transport); if (!media_endpoint_is_broadcast(transport->endpoint)) bap_update_qos(transport); else if (bt_bap_stream_io_dir(stream) != BT_BAP_BCAST_SOURCE) bap_update_bcast_qos(transport); transport_update_playing(transport, FALSE); return; case BT_BAP_STREAM_STATE_DISABLING: return; case BT_BAP_STREAM_STATE_ENABLING: if (!bt_bap_stream_get_io(stream)) return; bap_metadata_changed(transport); break; case BT_BAP_STREAM_STATE_STREAMING: if ((bt_bap_stream_io_dir(stream) == BT_BAP_BCAST_SOURCE) || (bt_bap_stream_io_dir(stream) == BT_BAP_BCAST_SINK)) bap_update_bcast_qos(transport); bap_metadata_changed(transport); break; case BT_BAP_STREAM_STATE_RELEASING: if (bt_bap_stream_io_dir(stream) == BT_BAP_BCAST_SINK) return; transport_update_playing(transport, FALSE); goto done; } io = bt_bap_stream_get_io(stream); if (!io) { error("Unable to get stream IO"); /* TODO: Fail if IO has not been established */ goto done; } fd = io_get_fd(io); if (fd < 0) { error("Unable to get IO fd"); goto done; } /* Wait for FD to become ready */ bap_clear_chan(bap); chan = g_io_channel_unix_new(fd); bap->chan_id = g_io_add_watch(chan, G_IO_OUT | G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, bap_transport_fd_ready, transport); g_io_channel_unref(chan); if (bap->chan_id) return; done: bap_clear_chan(bap); bap_resume_complete(transport); } static void bap_connecting(struct bt_bap_stream *stream, bool state, int fd, void *user_data) { struct media_transport *transport = user_data; struct bap_transport *bap = transport->data; if (bap->stream != stream) return; bap_update_links(transport); /* IO connected; re-run Enabling to complete a deferred Acquire. */ if (!state && fd >= 0 && bt_bap_stream_get_state(stream) == BT_BAP_STREAM_STATE_ENABLING) bap_state_changed(stream, BT_BAP_STREAM_STATE_ENABLING, BT_BAP_STREAM_STATE_ENABLING, user_data); } static bool transport_bap_is_playback(struct media_transport *transport) { struct bap_transport *bap = transport->data; uint8_t dir = bt_bap_stream_get_dir(bap->stream); /* Unicast to local/remote Server Sink, or Broadcast */ return dir & (BT_BAP_SINK | BT_BAP_BCAST_SINK | BT_BAP_BCAST_SOURCE); } static int transport_bap_get_volume(struct media_transport *transport) { if (transport_bap_is_playback(transport)) return bt_audio_vcp_get_volume(transport->device); else return -ENOTSUP; /* TODO: MICP */ } static int transport_bap_set_volume(struct media_transport *transport, int volume) { if (volume < 0 || volume > 255) return -EINVAL; if (transport_bap_is_playback(transport)) return bt_audio_vcp_set_volume(transport->device, volume); else return -ENOTSUP; /* TODO: MICP */ } static void transport_bap_destroy(void *data) { struct bap_transport *bap = data; bap_clear_chan(bap); util_iov_free(bap->meta, 1); bt_bap_state_unregister(bt_bap_stream_get_session(bap->stream), bap->state_id); free(bap); } static void *transport_bap_init(struct media_transport *transport, void *stream) { struct bt_bap_qos *qos; struct bap_transport *bap; qos = bt_bap_stream_get_qos(stream); bap = new0(struct bap_transport, 1); bap->stream = stream; bap->qos = *qos; bap->state_id = bt_bap_state_register(bt_bap_stream_get_session(stream), bap_state_changed, bap_connecting, transport, NULL); return bap; } #ifdef HAVE_ASHA static void asha_transport_sync_state(struct media_transport *transport, struct bt_asha_device *asha_dev) { switch (bt_asha_device_get_state(asha_dev)) { case ASHA_STOPPED: transport_set_state(transport, TRANSPORT_STATE_IDLE); break; case ASHA_STARTING: transport_set_state(transport, TRANSPORT_STATE_REQUESTING); break; case ASHA_STARTED: transport_set_state(transport, TRANSPORT_STATE_ACTIVE); break; } } static void asha_transport_state_cb(int status, void *user_data) { struct media_owner *owner = user_data; struct media_transport *transport = owner->transport; struct bt_asha_device *asha_dev; enum bt_asha_state_t state; if (!transport) { DBG("Lost owner while connecting, bailing"); return; } asha_dev = transport->data; state = bt_asha_device_get_state(asha_dev); if (state == ASHA_STARTED) { int fd; uint16_t imtu, omtu; gboolean ret; fd = bt_asha_device_get_fd(asha_dev); imtu = bt_asha_device_get_imtu(asha_dev); omtu = bt_asha_device_get_omtu(asha_dev); media_transport_set_fd(transport, fd, imtu, omtu); owner->pending->id = 0; ret = g_dbus_send_reply(btd_get_dbus_connection(), owner->pending->msg, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_UINT16, &imtu, DBUS_TYPE_UINT16, &omtu, DBUS_TYPE_INVALID); if (!ret) { media_transport_remove_owner(transport); return; } media_owner_remove(owner); } else if (state == ASHA_STOPPED) { if (owner->pending) { owner->pending->id = 0; media_request_reply(owner->pending, 0); media_owner_remove(owner); } media_transport_remove_owner(transport); } asha_transport_sync_state(transport, asha_dev); } static gboolean asha_transport_suspend_cb(void *user_data) { asha_transport_state_cb(-1, user_data); return FALSE; } static guint transport_asha_resume(struct media_transport *transport, struct media_owner *owner) { struct bt_asha_device *asha_dev = transport->data; guint ret; ret = bt_asha_device_start(asha_dev, asha_transport_state_cb, owner); asha_transport_sync_state(transport, asha_dev); return ret > 0 ? ret : 0; } static guint transport_asha_suspend(struct media_transport *transport, struct media_owner *owner) { struct bt_asha_device *asha_dev = transport->data; guint ret = 0; if (owner) { ret = bt_asha_device_stop(asha_dev); asha_transport_sync_state(transport, asha_dev); g_idle_add(asha_transport_suspend_cb, owner); } else { ret = bt_asha_device_stop(asha_dev); /* We won't have a callback to set the final state */ transport_set_state(transport, TRANSPORT_STATE_IDLE); } return ret; } static void transport_asha_cancel(struct media_transport *transport, guint id) { struct bt_asha_device *asha_dev = transport->data; enum bt_asha_state_t state = bt_asha_device_get_state(asha_dev); if (id != bt_asha_device_device_get_resume_id(asha_dev)) { /* Not current, ignore */ DBG("Ignoring cancel request for id %d", id); return; } if (state == ASHA_STARTING || state == ASHA_STARTED) { DBG("Cancel requested, stopping"); bt_asha_device_stop(asha_dev); /* We won't have a callback to set the final state */ transport_set_state(transport, TRANSPORT_STATE_IDLE); } } static int transport_asha_get_volume(struct media_transport *transport) { struct bt_asha_device *asha_dev = transport->data; int8_t volume; int scaled_volume; volume = bt_asha_device_get_volume(asha_dev); /* Convert -128-0 to 0-127 */ scaled_volume = ((((int) volume) + 128) * 127) / 128; return scaled_volume; } static int transport_asha_set_volume(struct media_transport *transport, int volume) { struct bt_asha_device *asha_dev = transport->data; int scaled_volume; if (volume < 0 || volume > 127) return -EINVAL; /* Convert 0-127 to -128-0 */ scaled_volume = ((((int) volume) * 128) / 127) - 128; return bt_asha_device_set_volume(asha_dev, scaled_volume) ? 0 : -EIO; } static void *transport_asha_init(struct media_transport *transport, void *data) { /* We just store the struct asha_device on the transport */ return data; } #endif /* HAVE_ASHA */ #define TRANSPORT_OPS(_uuid, _props, _set_owner, _remove_owner, _init, \ _resume, _suspend, _cancel, _set_state, _get_stream, \ _get_volume, _set_volume, _set_delay, _update_links, \ _destroy, _test_props) \ { \ .uuid = _uuid, \ .properties = _props, \ .test_properties = _test_props, \ .set_owner = _set_owner, \ .remove_owner = _remove_owner, \ .init = _init, \ .resume = _resume, \ .suspend = _suspend, \ .cancel = _cancel, \ .set_state = _set_state, \ .get_stream = _get_stream, \ .get_volume = _get_volume, \ .set_volume = _set_volume, \ .set_delay = _set_delay, \ .update_links = _update_links, \ .destroy = _destroy \ } #define A2DP_OPS(_uuid, _init, _set_volume, _set_delay, _destroy) \ TRANSPORT_OPS(_uuid, transport_a2dp_properties, NULL, \ transport_a2dp_remove_owner, _init, \ transport_a2dp_resume, transport_a2dp_suspend, \ transport_a2dp_cancel, NULL, \ transport_a2dp_get_stream, transport_a2dp_get_volume, \ _set_volume, _set_delay, NULL, _destroy, NULL) #define BAP_OPS(_uuid, _props, _set_owner, _remove_owner, _update_links, \ _set_state, _test_props) \ TRANSPORT_OPS(_uuid, _props, _set_owner, _remove_owner,\ transport_bap_init, \ transport_bap_resume, transport_bap_suspend, \ transport_bap_cancel, _set_state, \ transport_bap_get_stream, transport_bap_get_volume, \ transport_bap_set_volume, NULL, \ _update_links, transport_bap_destroy, _test_props) #define BAP_UC_OPS(_uuid) \ BAP_OPS(_uuid, transport_bap_uc_properties, \ transport_bap_set_owner, transport_bap_remove_owner, \ transport_bap_update_links_uc, \ transport_bap_set_state, \ transport_bap_uc_test_properties) #define BAP_BC_OPS(_uuid) \ BAP_OPS(_uuid, transport_bap_bc_properties, NULL, NULL, \ transport_bap_update_links_bc, NULL, NULL) #define ASHA_OPS(_uuid) \ TRANSPORT_OPS(_uuid, transport_asha_properties, NULL, NULL, \ transport_asha_init, \ transport_asha_resume, transport_asha_suspend, \ transport_asha_cancel, NULL, NULL, \ transport_asha_get_volume, transport_asha_set_volume, \ NULL, NULL, NULL, NULL) static const struct media_transport_ops transport_ops[] = { #ifdef HAVE_A2DP A2DP_OPS(A2DP_SOURCE_UUID, transport_a2dp_src_init, #ifdef HAVE_AVRCP transport_a2dp_src_set_volume, #else NULL, #endif NULL, transport_a2dp_src_destroy), A2DP_OPS(A2DP_SINK_UUID, transport_a2dp_snk_init, #ifdef HAVE_AVRCP transport_a2dp_snk_set_volume, #else NULL, #endif transport_a2dp_snk_set_delay, transport_a2dp_snk_destroy), #endif /* HAVE_A2DP */ BAP_UC_OPS(PAC_SOURCE_UUID), BAP_UC_OPS(PAC_SINK_UUID), BAP_BC_OPS(BCAA_SERVICE_UUID), BAP_BC_OPS(BAA_SERVICE_UUID), #ifdef HAVE_ASHA ASHA_OPS(ASHA_PROFILE_UUID), #endif /* HAVE_ASHA */ }; static const struct media_transport_ops * media_transport_find_ops(const char *uuid) { size_t i; for (i = 0; i < ARRAY_SIZE(transport_ops); i++) { const struct media_transport_ops *ops = &transport_ops[i]; if (!strcasecmp(uuid, ops->uuid)) return ops; } return NULL; } struct media_transport *media_transport_create(struct btd_device *device, const char *remote_endpoint, uint8_t *configuration, size_t size, void *data, void *stream) { struct media_endpoint *endpoint = data; struct media_transport *transport; const struct media_transport_ops *ops; int fd; const GDBusPropertyTable *properties; transport = g_new0(struct media_transport, 1); if (device) transport->device = device; else transport->adapter = media_endpoint_get_btd_adapter(endpoint); transport->endpoint = endpoint; transport->configuration = util_memdup(configuration, size); transport->size = size; transport->remote_endpoint = g_strdup(remote_endpoint); for (fd = g_slist_length(transports); fd < UINT8_MAX; fd++) { char *path; if (device) path = g_strdup_printf("%s/fd%d", remote_endpoint ? remote_endpoint : device_get_path(device), fd); else path = g_strdup_printf("%s/fd%d", remote_endpoint ? remote_endpoint : adapter_get_path(transport->adapter), fd); /* Check if transport already exists */ if (!find_transport_by_path(path)) { transport->path = path; break; } g_free(path); } if (!transport->path) { error("Unable to allocate transport path"); goto fail; } transport->fd = -1; ops = media_transport_find_ops(media_endpoint_get_uuid(endpoint)); if (!ops) goto fail; transport->ops = ops; if (ops->init) { transport->data = ops->init(transport, stream); if (!transport->data) goto fail; } if (btd_opts.testing && ops->test_properties) properties = ops->test_properties; else properties = ops->properties; if (g_dbus_register_interface(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, transport_methods, NULL, properties, transport, media_transport_free) == FALSE) { error("Could not register transport %s", transport->path); goto fail; } transports = g_slist_append(transports, transport); return transport; fail: media_transport_free(transport); return NULL; } const char *media_transport_get_path(struct media_transport *transport) { return transport->path; } void *media_transport_get_stream(struct media_transport *transport) { if (transport->ops && transport->ops->get_stream) return transport->ops->get_stream(transport); return NULL; } void media_transport_update_delay(struct media_transport *transport, uint16_t delay) { #ifdef HAVE_A2DP struct a2dp_transport *a2dp = transport->data; /* Check if delay really changed */ if (a2dp->delay == delay) return; if (a2dp->session == NULL) a2dp->session = a2dp_avdtp_get(transport->device); a2dp->delay = delay; g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, "Delay"); #endif /* HAVE_A2DP */ } struct btd_device *media_transport_get_dev(struct media_transport *transport) { return transport->device; } static void media_transport_emit_volume(struct media_transport *transport) { int volume; if (media_transport_get_volume(transport, &volume)) return; g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, "Volume"); } int media_transport_get_a2dp_volume(struct btd_device *dev) { GSList *l; if (dev == NULL) return -1; /* Attempt to locate the transport to get its volume */ for (l = transports; l; l = l->next) { struct media_transport *transport = l->data; if (transport->device != dev) continue; /* A2DP only */ if (media_endpoint_get_sep(transport->endpoint)) { int volume; if (!media_transport_get_volume(transport, &volume)) return volume; return -1; } } /* If no transport, use device volume. This is a workaround for the lack * of ordering between AVRCP and A2DP session start. (Note BAP+VCP do * not have this issue.) */ return btd_device_get_volume(dev); } void media_transport_set_a2dp_volume(struct btd_device *dev, int volume) { GSList *l; if (dev == NULL || volume < 0 || volume > 127) return; /* Attempt to locate the transport to set its volume */ for (l = transports; l; l = l->next) { struct media_transport *transport = l->data; struct a2dp_transport *a2dp; if (transport->device != dev) continue; if (!media_endpoint_get_sep(transport->endpoint)) continue; a2dp = transport->data; if (a2dp->volume != volume) { a2dp->volume = volume; media_transport_emit_volume(transport); } break; } btd_device_set_volume(dev, volume); } void media_transport_volume_changed(struct btd_device *dev) { GSList *l; if (dev == NULL) return; for (l = transports; l; l = l->next) { struct media_transport *transport = l->data; if (transport->device != dev) continue; media_transport_emit_volume(transport); } } const char *media_transport_stream_path(void *stream) { GSList *l; if (!stream) return NULL; for (l = transports; l; l = l->next) { struct media_transport *transport = l->data; if (media_transport_get_stream(transport) == stream) return transport->path; } return NULL; } bluez-5.87/profiles/audio/PaxHeaders/sink.h0000644000000000000000000000005014015011623015705 xustar0020 atime=1783093618 20 ctime=1783094105 bluez-5.87/profiles/audio/sink.h0000644000000000000000000000212014015011623015361 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ typedef enum { SINK_STATE_DISCONNECTED, SINK_STATE_CONNECTING, SINK_STATE_CONNECTED, SINK_STATE_PLAYING, } sink_state_t; typedef void (*sink_state_cb) (struct btd_service *service, sink_state_t old_state, sink_state_t new_state, void *user_data); struct btd_service; unsigned int sink_add_state_cb(struct btd_service *service, sink_state_cb cb, void *user_data); gboolean sink_remove_state_cb(unsigned int id); int sink_init(struct btd_service *service); void sink_unregister(struct btd_service *service); gboolean sink_is_active(struct btd_service *service); int sink_connect(struct btd_service *service); gboolean sink_new_stream(struct btd_service *service, struct avdtp *session, struct avdtp_stream *stream); gboolean sink_setup_stream(struct btd_service *service, struct avdtp *session); int sink_disconnect(struct btd_service *service); bluez-5.87/profiles/audio/PaxHeaders/a2dp.h0000644000000000000000000000005015063045254015602 xustar0020 atime=1783093610 20 ctime=1783094105 bluez-5.87/profiles/audio/a2dp.h0000644000000000000000000001065115063045254015266 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright (C) 2011 BMW Car IT GmbH. All rights reserved. * * */ struct a2dp_sep; struct a2dp_setup; typedef void (*a2dp_endpoint_select_t) (struct a2dp_setup *setup, void *ret, int size); typedef void (*a2dp_endpoint_config_t) (struct a2dp_setup *setup, uint8_t error_code); struct a2dp_endpoint { const char *(*get_name) (struct a2dp_sep *sep, void *user_data); const char *(*get_path) (struct a2dp_sep *sep, void *user_data); size_t (*get_capabilities) (struct a2dp_sep *sep, uint8_t **capabilities, void *user_data); int (*select_configuration) (struct a2dp_sep *sep, uint8_t *capabilities, size_t length, struct a2dp_setup *setup, a2dp_endpoint_select_t cb, void *user_data); int (*set_configuration) (struct a2dp_sep *sep, uint8_t *configuration, size_t length, struct a2dp_setup *setup, a2dp_endpoint_config_t cb, void *user_data); void (*clear_configuration) (struct a2dp_sep *sep, struct btd_device *device, void *user_data); void (*set_delay) (struct a2dp_sep *sep, uint16_t delay, void *user_data); }; typedef void (*a2dp_discover_cb_t) (struct avdtp *session, GSList *seps, int err, void *user_data); typedef void (*a2dp_select_cb_t) (struct avdtp *session, struct a2dp_sep *sep, GSList *caps, int err, void *user_data); typedef void (*a2dp_config_cb_t) (struct avdtp *session, struct a2dp_sep *sep, struct avdtp_stream *stream, int err, void *user_data); typedef void (*a2dp_stream_cb_t) (struct avdtp *session, int err, void *user_data); struct a2dp_sep *a2dp_add_sep(struct btd_adapter *adapter, uint8_t type, uint8_t codec, gboolean delay_reporting, struct a2dp_endpoint *endpoint, void *user_data, GDestroyNotify destroy, int *err); void a2dp_remove_sep(struct a2dp_sep *sep); unsigned int a2dp_discover(struct avdtp *session, a2dp_discover_cb_t cb, void *user_data); unsigned int a2dp_select_capabilities(struct avdtp *session, uint8_t type, const char *sender, a2dp_select_cb_t cb, void *user_data); unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep, a2dp_config_cb_t cb, GSList *caps, void *user_data); uint8_t a2dp_parse_config_error(const char *error_name); unsigned int a2dp_resume(struct avdtp *session, struct a2dp_sep *sep, a2dp_stream_cb_t cb, void *user_data); unsigned int a2dp_suspend(struct avdtp *session, struct a2dp_sep *sep, a2dp_stream_cb_t cb, void *user_data); gboolean a2dp_cancel(unsigned int id); gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session); gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session); struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep, struct avdtp *session); struct btd_device *a2dp_setup_get_device(struct a2dp_setup *setup); const char *a2dp_setup_remote_path(struct a2dp_setup *setup); struct avdtp *a2dp_avdtp_get(struct btd_device *device); enum a2dp_error_codes { A2DP_INVALID_CODEC_TYPE = 0xc1, A2DP_NOT_SUPPORTED_CODEC_TYPE = 0xc2, A2DP_INVALID_SAMPLING_FREQUENCY = 0xc3, A2DP_NOT_SUPPORTED_SAMPLING_FREQUENCY = 0xc4, A2DP_INVALID_CHANNEL_MODE = 0xc5, A2DP_NOT_SUPPORTED_CHANNEL_MODE = 0xc6, A2DP_INVALID_SUBBANDS = 0xc7, A2DP_NOT_SUPPORTED_SUBBANDS = 0xc8, A2DP_INVALID_ALLOCATION_METHOD = 0xc9, A2DP_NOT_SUPPORTED_ALLOCATION_METHOD = 0xca, A2DP_INVALID_MINIMUM_BITPOOL_VALUE = 0xcb, A2DP_NOT_SUPPORTED_MINIMUM_BITPOOL_VALUE = 0xcc, A2DP_INVALID_MAXIMUM_BITPOOL_VALUE = 0xcd, A2DP_NOT_SUPPORTED_MAXIMUM_BITPOOL_VALUE = 0xce, A2DP_INVALID_LAYER = 0xcf, A2DP_NOT_SUPPORTED_LAYER = 0xd0, A2DP_NOT_SUPPORTED_CRC = 0xd1, A2DP_NOT_SUPPORTED_MPF = 0xd2, A2DP_NOT_SUPPORTED_VBR = 0xd3, A2DP_INVALID_BIT_RATE = 0xd4, A2DP_NOT_SUPPORTED_BIT_RATE = 0xd5, A2DP_INVALID_OBJECT_TYPE = 0xd6, A2DP_NOT_SUPPORTED_OBJECT_TYPE = 0xd7, A2DP_INVALID_CHANNELS = 0xd8, A2DP_NOT_SUPPORTED_CHANNELS = 0xd9, A2DP_INVALID_VERSION = 0xda, A2DP_NOT_SUPPORTED_VERSION = 0xdb, A2DP_NOT_SUPPORTED_MAXIMUM_SUL = 0xdc, A2DP_INVALID_BLOCK_LENGTH = 0xdd, A2DP_INVALID_CP_TYPE = 0xe0, A2DP_INVALID_CP_FORMAT = 0xe1, A2DP_INVALID_CODEC_PARAMETER = 0xe2, A2DP_NOT_SUPPORTED_CODEC_PARAMETER = 0xe3, A2DP_INVALID_DRC = 0xe4, A2DP_NOT_SUPPORTED_DRC = 0xe5, }; bluez-5.87/profiles/audio/PaxHeaders/media.h0000644000000000000000000000005015142177620016034 xustar0020 atime=1783093610 20 ctime=1783094105 bluez-5.87/profiles/audio/media.h0000644000000000000000000000566415142177620015530 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2004-2009 Marcel Holtmann * * */ struct media_endpoint; struct local_player; typedef void (*media_endpoint_cb_t) (struct media_endpoint *endpoint, void *ret, int size, void *user_data); int media_register(struct btd_adapter *btd_adapter); void media_unregister(struct btd_adapter *btd_adapter); #ifdef HAVE_A2DP struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint); #else struct a2dp_sep; static inline struct a2dp_sep *media_endpoint_get_sep( struct media_endpoint *endpoint) { return NULL; } #endif const char *media_endpoint_get_uuid(struct media_endpoint *endpoint); bool media_endpoint_get_delay_reporting(struct media_endpoint *endpoint); uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint); struct btd_adapter *media_endpoint_get_btd_adapter( struct media_endpoint *endpoint); bool media_endpoint_is_broadcast(struct media_endpoint *endpoint); const struct media_endpoint *media_endpoint_get_asha(void); /* * Local media player */ struct local_player_callback { void (*status_changed)(void *user_data); void (*track_position)(uint32_t old_ms, uint32_t new_ms, void *user_data); void (*track_changed)(void *user_data); void (*settings_changed)(const char *key, void *user_data); /* Player removed (no further callbacks) */ void (*player_removed)(void *user_data); }; unsigned int local_player_register_callbacks(struct local_player *lp, const struct local_player_callback *cb, void *user_data); void local_player_unregister_callbacks(struct local_player *lp, unsigned int id); struct btd_adapter *local_player_get_adapter(struct local_player *lp); GList *local_player_list_settings(struct local_player *lp); const char *local_player_get_setting(struct local_player *lp, const char *key); int local_player_set_setting(struct local_player *lp, const char *key, const char *value); const char *local_player_get_metadata(struct local_player *lp, const char *key); GList *local_player_list_metadata(struct local_player *lp); const char *local_player_get_status(struct local_player *lp); uint32_t local_player_get_position(struct local_player *lp); uint32_t local_player_get_duration(struct local_player *lp); const char *local_player_get_player_name(struct local_player *lp); bool local_player_have_track(struct local_player *lp); bool local_player_play(struct local_player *lp); bool local_player_stop(struct local_player *lp); bool local_player_pause(struct local_player *lp); bool local_player_next(struct local_player *lp); bool local_player_previous(struct local_player *lp); typedef void (*local_player_added_t)(struct local_player *lp, void *user_data); unsigned int local_player_register_watch(local_player_added_t cb, void *user_data); void local_player_unregister_watch(unsigned int id); bluez-5.87/profiles/audio/PaxHeaders/hfp-hf.c0000644000000000000000000000005015221752714016121 xustar0020 atime=1783093720 20 ctime=1783094106 bluez-5.87/profiles/audio/hfp-hf.c0000644000000000000000000002735315221752714015614 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * Copyright © 2025 Collabora Ltd. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "bluetooth/uuid.h" #include "gdbus/gdbus.h" #include "btio/btio.h" #include "src/adapter.h" #include "src/btd.h" #include "src/dbus-common.h" #include "src/device.h" #include "src/error.h" #include "src/log.h" #include "src/plugin.h" #include "src/profile.h" #include "src/service.h" #include "src/shared/hfp.h" #include "src/shared/queue.h" #include "telephony.h" #define URI "tel" #define URI_PREFIX URI ":" struct hfp_device { struct telephony *telephony; uint16_t version; GIOChannel *io; struct hfp_hf *hf; struct queue *calls; }; static void hfp_hf_debug(const char *str, void *user_data) { DBG_IDX(0xffff, "%s", str); } static enum call_state hfp_call_status_to_call_state( enum hfp_call_status status) { switch (status) { case CALL_STATUS_ACTIVE: return CALL_STATE_ACTIVE; break; case CALL_STATUS_HELD: return CALL_STATE_HELD; break; case CALL_STATUS_DIALING: return CALL_STATE_DIALING; break; case CALL_STATUS_ALERTING: return CALL_STATE_ALERTING; break; case CALL_STATUS_INCOMING: return CALL_STATE_INCOMING; break; case CALL_STATUS_WAITING: return CALL_STATE_WAITING; break; case CALL_STATUS_RESPONSE_AND_HOLD: return CALL_STATE_RESPONSE_AND_HOLD; break; default: DBG("Unknown hfp_call_status: %u", status); } return CALL_STATE_DISCONNECTED; } static bool call_id_cmp(const void *data, const void *match_data) { struct call *call = (struct call *) data; uint16_t id = GPOINTER_TO_UINT(match_data); return call->idx == id; } static void device_destroy(struct hfp_device *dev) { DBG("%s", telephony_get_path(dev->telephony)); telephony_set_state(dev->telephony, DISCONNECTING); if (dev->hf) { hfp_hf_unref(dev->hf); dev->hf = NULL; } if (dev->io) { g_io_channel_unref(dev->io); dev->io = NULL; } telephony_unregister_interface(dev->telephony); } static void hfp_hf_update_indicator(enum hfp_indicator indicator, uint32_t val, void *user_data) { struct hfp_device *dev = user_data; switch (indicator) { case HFP_INDICATOR_SERVICE: telephony_set_network_service(dev->telephony, val); break; case HFP_INDICATOR_CALL: break; case HFP_INDICATOR_CALLSETUP: break; case HFP_INDICATOR_CALLHELD: break; case HFP_INDICATOR_SIGNAL: telephony_set_signal(dev->telephony, val); break; case HFP_INDICATOR_ROAM: telephony_set_roaming(dev->telephony, val); break; case HFP_INDICATOR_BATTCHG: telephony_set_battchg(dev->telephony, val); break; case HFP_INDICATOR_LAST: default: DBG("Unknown signal indicator: %u", indicator); } } static void hfp_hf_update_inband_ring(bool enabled, void *user_data) { struct hfp_device *dev = user_data; telephony_set_inband_ringtone(dev->telephony, enabled); } static void hfp_hf_update_operator(const char *operator_name, void *user_data) { struct hfp_device *dev = user_data; telephony_set_operator_name(dev->telephony, operator_name); } static void hfp_hf_call_added(uint id, enum hfp_call_status status, void *user_data) { struct hfp_device *dev = user_data; struct call *call; const char *number; bool mpty; call = telephony_new_call(dev->telephony, id, hfp_call_status_to_call_state(status), NULL); if ((number = hfp_hf_call_get_number(dev->hf, id)) != NULL) call->line_id = g_strdup(number); if (hfp_hf_call_get_multiparty(dev->hf, id, &mpty)) call->multiparty = mpty; if (telephony_call_register_interface(call)) { telephony_free_call(call); return; } queue_push_tail(dev->calls, call); } static void hfp_hf_call_removed(uint id, void *user_data) { struct hfp_device *dev = user_data; struct call *call; call = queue_find(dev->calls, call_id_cmp, GUINT_TO_POINTER(id)); if (!call) { DBG("Unknown call id: %u", id); return; } telephony_call_set_state(call, CALL_STATE_DISCONNECTED); queue_remove(dev->calls, call); telephony_call_unregister_interface(call); } static void hfp_hf_session_ready_cb(enum hfp_result res, enum hfp_error cme_err, void *user_data) { struct hfp_device *dev = user_data; if (res != HFP_RESULT_OK) { error("Session setup error: %d, dropping connection", res); hfp_hf_disconnect(dev->hf); return; } telephony_set_state(dev->telephony, CONNECTED); } static void hfp_hf_call_status_updated(uint id, enum hfp_call_status status, void *user_data) { struct hfp_device *dev = user_data; struct call *call; call = queue_find(dev->calls, call_id_cmp, GUINT_TO_POINTER(id)); if (!call) { DBG("Unknown call id: %u", id); return; } telephony_call_set_state(call, hfp_call_status_to_call_state(status)); } static void hfp_hf_call_line_id_updated(uint id, const char *number, unsigned int type, void *user_data) { struct hfp_device *dev = user_data; struct call *call; call = queue_find(dev->calls, call_id_cmp, GUINT_TO_POINTER(id)); if (!call) { DBG("Unknown call id: %u", id); return; } telephony_call_set_line_id(call, number); } static struct hfp_hf_callbacks hf_session_callbacks = { .session_ready = hfp_hf_session_ready_cb, .update_indicator = hfp_hf_update_indicator, .update_operator = hfp_hf_update_operator, .update_inband_ring = hfp_hf_update_inband_ring, .call_added = hfp_hf_call_added, .call_removed = hfp_hf_call_removed, .call_status_updated = hfp_hf_call_status_updated, .call_line_id_updated = hfp_hf_call_line_id_updated, }; static void hfp_disconnect_watch(void *user_data) { struct hfp_device *dev = user_data; DBG(""); queue_destroy(dev->calls, NULL); dev->calls = NULL; device_destroy(user_data); } static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data) { struct hfp_device *dev = user_data; struct btd_service *service = telephony_get_service(dev->telephony); DBG(""); if (err) { error("%s", err->message); goto failed; } dev->hf = hfp_hf_new(g_io_channel_unix_get_fd(chan)); if (!dev->hf) { error("Could not create hfp io"); goto failed; } hfp_hf_set_debug(dev->hf, hfp_hf_debug, NULL, NULL); g_io_channel_set_close_on_unref(chan, FALSE); hfp_hf_set_close_on_unref(dev->hf, true); hfp_hf_set_disconnect_handler(dev->hf, hfp_disconnect_watch, dev, NULL); hfp_hf_session_register(dev->hf, &hf_session_callbacks, dev); dev->calls = queue_new(); if (!hfp_hf_session(dev->hf)) { error("Could not start SLC creation"); hfp_hf_disconnect(dev->hf); goto failed; } telephony_set_state(dev->telephony, SESSION_CONNECTING); btd_service_connecting_complete(service, 0); return; failed: g_io_channel_shutdown(chan, TRUE, NULL); device_destroy(dev); } static void cmd_complete(enum hfp_result res, enum hfp_error cme_err, void *user_data) { DBusMessage *msg = user_data; if (res != HFP_RESULT_OK) { DBusMessage *reply; const char *name = dbus_message_get_member(msg); error("Command %s error: %d", name, res); reply = g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", "Command %s failed: %d", name, res); g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(msg); return; } g_dbus_send_reply(btd_get_dbus_connection(), msg, DBUS_TYPE_INVALID); dbus_message_unref(msg); } static DBusMessage *dial(DBusConnection *conn, DBusMessage *msg, void *profile_data) { struct hfp_device *dev = profile_data; const char *number; bool ret; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number, DBUS_TYPE_INVALID)) { return btd_error_invalid_args(msg); } if (strncmp(number, URI_PREFIX, strlen(URI_PREFIX)) != 0) return btd_error_invalid_args(msg); ret = hfp_hf_dial(dev->hf, number + strlen(URI_PREFIX), cmd_complete, dbus_message_ref(msg)); if (!ret) return btd_error_failed(msg, "Dial command failed"); return NULL; } static DBusMessage *call_answer(DBusConnection *conn, DBusMessage *msg, void *call_data) { struct call *call = call_data; struct hfp_device *dev = telephony_get_profile_data(call->device); bool ret; ret = hfp_hf_call_answer(dev->hf, call->idx, cmd_complete, dbus_message_ref(msg)); if (!ret) return btd_error_failed(msg, "Answer call command failed"); return NULL; } static DBusMessage *call_hangup(DBusConnection *conn, DBusMessage *msg, void *call_data) { struct call *call = call_data; struct hfp_device *dev = telephony_get_profile_data(call->device); bool ret; ret = hfp_hf_call_hangup(dev->hf, call->idx, cmd_complete, dbus_message_ref(msg)); if (!ret) return btd_error_failed(msg, "Hangup call command failed"); return NULL; } struct telephony_callbacks hfp_callbacks = { .dial = dial, .call_answer = call_answer, .call_hangup = call_hangup, }; static int hfp_connect(struct btd_service *service) { struct hfp_device *dev; struct btd_profile *p; const sdp_record_t *rec; sdp_list_t *list, *protos; sdp_profile_desc_t *desc; int channel; bdaddr_t src, dst; GError *err = NULL; DBG(""); dev = btd_service_get_user_data(service); p = btd_service_get_profile(service); rec = btd_device_get_record(telephony_get_device(dev->telephony), p->remote_uuid); if (!rec) return -EIO; if (sdp_get_profile_descs(rec, &list) == 0) { desc = list->data; dev->version = desc->version; } sdp_list_free(list, free); if (sdp_get_access_protos(rec, &protos) < 0) { error("unable to get access protocols from record"); return -EIO; } channel = 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 (channel <= 0) { error("unable to get RFCOMM channel from record"); return -EIO; } src = telephony_get_src(dev->telephony); dst = telephony_get_dst(dev->telephony); dev->io = bt_io_connect(connect_cb, dev, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, &src, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_CHANNEL, channel, BT_IO_OPT_INVALID); if (dev->io == NULL) { error("unable to start connection"); return -EIO; } return telephony_register_interface(dev->telephony); } static int hfp_disconnect(struct btd_service *service) { struct hfp_device *dev; DBG(""); dev = btd_service_get_user_data(service); if (dev->hf) hfp_hf_disconnect(dev->hf); btd_service_disconnecting_complete(service, 0); return 0; } static int hfp_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); const char *path = device_get_path(device); struct hfp_device *dev; DBG("%s", path); dev = g_new0(struct hfp_device, 1); if (!dev) return -EINVAL; dev->telephony = telephony_new(service, dev, &hfp_callbacks); btd_service_set_user_data(service, dev); telephony_add_uri_scheme(dev->telephony, URI); return 0; } static void hfp_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); const char *path = device_get_path(device); struct hfp_device *dev; DBG("%s", path); dev = btd_service_get_user_data(service); telephony_free(dev->telephony); g_free(dev); } static struct btd_profile hfp_hf_profile = { .name = "hfp", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .remote_uuid = HFP_AG_UUID, .device_probe = hfp_probe, .device_remove = hfp_remove, .auto_connect = true, .connect = hfp_connect, .disconnect = hfp_disconnect, .experimental = true, }; static int hfp_init(void) { return btd_profile_register(&hfp_hf_profile); } static void hfp_exit(void) { btd_profile_unregister(&hfp_hf_profile); } BLUETOOTH_PLUGIN_DEFINE(hfp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, hfp_init, hfp_exit) bluez-5.87/profiles/audio/PaxHeaders/vcp.c0000644000000000000000000000005015142177620015540 xustar0020 atime=1783093708 20 ctime=1783094106 bluez-5.87/profiles/audio/vcp.c0000644000000000000000000001667515142177620015240 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/dbus-common.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/shared/gatt-server.h" #include "src/shared/vcp.h" #include "btio/btio.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/gatt-database.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #include "src/error.h" #include "vcp.h" #include "transport.h" #define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1" struct vcp_data { struct btd_device *device; struct btd_service *service; struct bt_vcp *vcp; }; static struct queue *sessions; static void vcp_debug(const char *str, void *user_data) { DBG_IDX(0xffff, "%s", str); } static int vcp_disconnect(struct btd_service *service) { struct vcp_data *data = btd_service_get_user_data(service); DBG(""); if (!data) { error("VCP service not handled by profile"); return -EINVAL; } bt_vcp_detach(data->vcp); btd_service_disconnecting_complete(service, 0); return 0; } static struct vcp_data *vcp_data_new(struct btd_device *device) { struct vcp_data *data; data = new0(struct vcp_data, 1); data->device = device; return data; } static bool match_data(const void *data, const void *match_data) { const struct vcp_data *vdata = data; const struct bt_vcp *vcp = match_data; return vdata->vcp == vcp; } static void vcp_volume_changed(struct bt_vcp *vcp, uint8_t volume) { struct vcp_data *data = queue_find(sessions, match_data, vcp); if (data) media_transport_volume_changed(data->device); } static void vcp_data_add(struct vcp_data *data) { DBG("data %p", data); if (queue_find(sessions, NULL, data)) { error("data %p already added", data); return; } bt_vcp_set_debug(data->vcp, vcp_debug, NULL, NULL); bt_vcp_set_volume_callback(data->vcp, vcp_volume_changed); if (!sessions) sessions = queue_new(); queue_push_tail(sessions, data); if (data->service) btd_service_set_user_data(data->service, data); } static bool match_device(const void *data, const void *match_data) { const struct vcp_data *vdata = data; const struct btd_device *device = match_data; return vdata->device == device; } static void vcp_data_free(struct vcp_data *data) { if (data->service) { btd_service_set_user_data(data->service, NULL); bt_vcp_set_user_data(data->vcp, NULL); } bt_vcp_unref(data->vcp); free(data); } static void vcp_data_remove(struct vcp_data *data) { DBG("data %p", data); if (!queue_remove(sessions, data)) return; vcp_data_free(data); if (queue_isempty(sessions)) { queue_destroy(sessions, NULL); sessions = NULL; } } int bt_audio_vcp_get_volume(struct btd_device *device) { struct vcp_data *data = queue_find(sessions, match_device, device); if (data) return bt_vcp_get_volume(data->vcp); return -ENODEV; } int bt_audio_vcp_set_volume(struct btd_device *device, uint8_t volume) { struct vcp_data *data = queue_find(sessions, match_device, device); if (data) return bt_vcp_set_volume(data->vcp, volume) ? 0 : -EIO; return -ENODEV; } static void vcp_remote_client_detached(struct bt_vcp *vcp, void *user_data) { struct vcp_data *data; DBG("%p", vcp); data = queue_find(sessions, match_data, vcp); if (!data) { error("Unable to find vcp session"); return; } vcp_data_remove(data); } static void vcp_remote_client_attached(struct bt_vcp *vcp, void *user_data) { struct vcp_data *data; struct bt_att *att; struct btd_device *device; DBG("%p", vcp); data = queue_find(sessions, match_data, vcp); if (data) return; att = bt_vcp_get_att(vcp); if (!att) return; device = btd_adapter_find_device_by_fd(bt_att_get_fd(att)); if (!device) { error("Unable to find device"); return; } data = vcp_data_new(device); data->vcp = vcp; vcp_data_add(data); } static int vcp_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct btd_adapter *adapter = device_get_adapter(device); struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct vcp_data *data = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); /* Ignore, if we were probed for this device already */ if (data) { error("Profile probed twice for the same device!"); return -EINVAL; } data = vcp_data_new(device); data->service = service; data->vcp = bt_vcp_new(btd_gatt_database_get_db(database), btd_device_get_gatt_db(device)); if (!data->vcp) { error("Unable to create VCP instance"); free(data); return -EINVAL; } vcp_data_add(data); bt_vcp_set_user_data(data->vcp, service); return 0; } static void vcp_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct vcp_data *data; char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); data = btd_service_get_user_data(service); if (!data) { error("VCP service not handled by profile"); return; } vcp_data_remove(data); } static void vcp_ready(struct bt_vcp *vcp, void *user_data) { struct btd_service *service = user_data; btd_service_connecting_complete(service, 0); } static int vcp_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct bt_gatt_client *client = btd_device_get_gatt_client(device); struct vcp_data *data = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); if (!data) { error("VCP service not handled by profile"); return -EINVAL; } if (!bt_vcp_attach(data->vcp, client, vcp_ready, service)) { error("VCP unable to attach"); return -EINVAL; } return 0; } static int vcp_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { struct btd_gatt_database *database = btd_adapter_get_database(adapter); DBG("VCP path %s", adapter_get_path(adapter)); bt_vcp_add_db(btd_gatt_database_get_db(database)); return 0; } static void vcp_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { DBG("VCP remove Adapter"); } static struct btd_profile vcp_profile = { .name = "vcp", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = VCS_UUID_STR, .device_probe = vcp_probe, .device_remove = vcp_remove, .accept = vcp_accept, .disconnect = vcp_disconnect, .adapter_probe = vcp_server_probe, .adapter_remove = vcp_server_remove, .experimental = true, }; static unsigned int vcp_id = 0; static int vcp_init(void) { int err; err = btd_profile_register(&vcp_profile); if (err) return err; vcp_id = bt_vcp_register(vcp_remote_client_attached, vcp_remote_client_detached, NULL); return 0; } static void vcp_exit(void) { btd_profile_unregister(&vcp_profile); bt_vcp_unregister(vcp_id); } BLUETOOTH_PLUGIN_DEFINE(vcp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, vcp_init, vcp_exit) bluez-5.87/profiles/audio/PaxHeaders/a2dp.c0000644000000000000000000000005015221752714015577 xustar0020 atime=1783093629 20 ctime=1783094105 bluez-5.87/profiles/audio/a2dp.c0000644000000000000000000025111515221752714015265 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright (C) 2011 BMW Car IT GmbH. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "bluetooth/uuid.h" #include "gdbus/gdbus.h" #include "src/btd.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/device.h" #include "src/dbus-common.h" #include "src/error.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #include "src/sdpd.h" #include "src/textfile.h" #include "src/shared/queue.h" #include "src/shared/timeout.h" #include "src/shared/util.h" #include "btio/btio.h" #include "avdtp.h" #include "sink.h" #include "source.h" #include "a2dp.h" #include "a2dp-codecs.h" #include "media.h" /* The duration that streams without users are allowed to stay in * STREAMING state. */ #define SUSPEND_TIMEOUT 5 #define AVDTP_PSM 25 #define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1" struct a2dp_stream { struct avdtp *session; struct avdtp_stream *stream; unsigned int suspend_timer; gboolean locked; gboolean suspending; gboolean starting; }; struct a2dp_sep { struct a2dp_server *server; struct a2dp_endpoint *endpoint; uint8_t type; uint8_t codec; struct avdtp_local_sep *lsep; struct queue *streams; gboolean delay_reporting; void *user_data; GDestroyNotify destroy; }; struct a2dp_setup_cb { struct a2dp_setup *setup; a2dp_discover_cb_t discover_cb; a2dp_select_cb_t select_cb; a2dp_config_cb_t config_cb; a2dp_stream_cb_t resume_cb; a2dp_stream_cb_t suspend_cb; guint source_id; void *user_data; unsigned int id; }; struct a2dp_setup { struct a2dp_channel *chan; struct avdtp *session; struct queue *eps; struct a2dp_sep *sep; struct a2dp_remote_sep *rsep; struct avdtp_stream *stream; struct avdtp_error *err; avdtp_set_configuration_cb setconf_cb; GSList *seps; GSList *caps; gboolean reconfigure; gboolean start; GSList *cb; GIOChannel *io; guint id; int ref; }; struct a2dp_server { struct btd_adapter *adapter; GSList *sinks; GSList *sources; uint32_t source_record_id; uint32_t sink_record_id; gboolean sink_enabled; gboolean source_enabled; uint64_t seid_pool; GIOChannel *io; struct queue *seps; struct queue *channels; }; struct a2dp_remote_sep { struct a2dp_channel *chan; char *path; struct avdtp_remote_sep *sep; bool from_cache; }; struct a2dp_last_used { struct a2dp_sep *lsep; struct a2dp_remote_sep *rsep; }; struct a2dp_channel { struct a2dp_server *server; struct btd_device *device; GIOChannel *io; guint io_id; unsigned int state_id; unsigned int auth_id; struct avdtp *session; struct queue *seps; struct a2dp_last_used *last_used; }; static GSList *servers = NULL; static GSList *setups = NULL; static unsigned int cb_id = 0; struct a2dp_error { const char *error_name; uint8_t error_code; }; #define A2DP_ERROR_PREFIX ERROR_INTERFACE ".A2DP." static struct a2dp_error config_errors[] = { {"InvalidCodecType", A2DP_INVALID_CODEC_TYPE}, {"NotSupportedCodecType", A2DP_NOT_SUPPORTED_CODEC_TYPE}, {"InvalidSamplingFrequency", A2DP_INVALID_SAMPLING_FREQUENCY}, {"NotSupportedSamplingFrequency", A2DP_NOT_SUPPORTED_SAMPLING_FREQUENCY}, {"InvalidChannelMode", A2DP_INVALID_CHANNEL_MODE}, {"NotSupportedChannelMode", A2DP_NOT_SUPPORTED_CHANNEL_MODE}, {"InvalidSubbands", A2DP_INVALID_SUBBANDS}, {"NotSupportedSubbands", A2DP_NOT_SUPPORTED_SUBBANDS}, {"InvalidAllocationMethod", A2DP_INVALID_ALLOCATION_METHOD}, {"NotSupportedAllocationMethod", A2DP_NOT_SUPPORTED_ALLOCATION_METHOD}, {"InvalidMinimumBitpoolValue", A2DP_INVALID_MINIMUM_BITPOOL_VALUE}, {"NotSupportedMinimumBitpoolValue", A2DP_NOT_SUPPORTED_MINIMUM_BITPOOL_VALUE}, {"InvalidMaximumBitpoolValue", A2DP_INVALID_MAXIMUM_BITPOOL_VALUE}, {"NotSupportedMaximumBitpoolValue", A2DP_NOT_SUPPORTED_MAXIMUM_BITPOOL_VALUE}, {"InvalidLayer", A2DP_INVALID_LAYER}, {"NotSupportedLayer", A2DP_NOT_SUPPORTED_LAYER}, {"NotSupportedCRC", A2DP_NOT_SUPPORTED_CRC}, {"NotSupportedMPF", A2DP_NOT_SUPPORTED_MPF}, {"NotSupportedVBR", A2DP_NOT_SUPPORTED_VBR}, {"InvalidBitRate", A2DP_INVALID_BIT_RATE}, {"NotSupportedBitRate", A2DP_NOT_SUPPORTED_BIT_RATE}, {"InvalidObjectType", A2DP_INVALID_OBJECT_TYPE}, {"NotSupportedObjectType", A2DP_NOT_SUPPORTED_OBJECT_TYPE}, {"InvalidChannels", A2DP_INVALID_CHANNELS}, {"NotSupportedChannels", A2DP_NOT_SUPPORTED_CHANNELS}, {"InvalidVersion", A2DP_INVALID_VERSION}, {"NotSupportedVersion", A2DP_NOT_SUPPORTED_VERSION}, {"NotSupportedMaximumSUL", A2DP_NOT_SUPPORTED_MAXIMUM_SUL}, {"InvalidBlockLength", A2DP_INVALID_BLOCK_LENGTH}, {"InvalidCPType", A2DP_INVALID_CP_TYPE}, {"InvalidCPFormat", A2DP_INVALID_CP_FORMAT}, {"InvalidCodecParameter", A2DP_INVALID_CODEC_PARAMETER}, {"NotSupportedCodecParameter", A2DP_NOT_SUPPORTED_CODEC_PARAMETER}, {"InvalidDRC", A2DP_INVALID_DRC}, {"NotSupportedDRC", A2DP_NOT_SUPPORTED_DRC} }; uint8_t a2dp_parse_config_error(const char *error_name) { size_t prefix_length; size_t i; prefix_length = strlen(A2DP_ERROR_PREFIX); if (strncmp(A2DP_ERROR_PREFIX, error_name, prefix_length)) return AVDTP_UNSUPPORTED_CONFIGURATION; error_name += prefix_length; for (i = 0; i < ARRAY_SIZE(config_errors); i++) { if (strcmp(config_errors[i].error_name, error_name) == 0) return config_errors[i].error_code; } return AVDTP_UNSUPPORTED_CONFIGURATION; } static struct a2dp_setup *setup_ref(struct a2dp_setup *setup) { setup->ref++; DBG("%p: ref=%d", setup, setup->ref); return setup; } static bool match_by_session(const void *data, const void *user_data) { const struct a2dp_channel *chan = data; const struct avdtp *session = user_data; return chan->session == session; } static struct a2dp_channel *find_channel(struct avdtp *session) { GSList *l; for (l = servers; l; l = g_slist_next(l)) { struct a2dp_server *server = l->data; struct a2dp_channel *chan; chan = queue_find(server->channels, match_by_session, session); if (chan) return chan; } return NULL; } static struct a2dp_setup *setup_new(struct avdtp *session) { struct a2dp_setup *setup; struct a2dp_channel *chan; chan = find_channel(session); if (!chan) return NULL; setup = g_new0(struct a2dp_setup, 1); setup->session = avdtp_ref(session); setup->chan = find_channel(session); setups = g_slist_append(setups, setup); return setup; } static void setup_free(struct a2dp_setup *s) { DBG("%p", s); if (s->io) { g_io_channel_shutdown(s->io, TRUE, NULL); g_io_channel_unref(s->io); } if (s->id) g_source_remove(s->id); queue_destroy(s->eps, NULL); setups = g_slist_remove(setups, s); if (s->session) avdtp_unref(s->session); g_slist_free_full(s->cb, g_free); g_slist_free_full(s->caps, g_free); g_free(s->err); g_free(s); } static void setup_unref(struct a2dp_setup *setup) { setup->ref--; DBG("%p: ref=%d", setup, setup->ref); if (setup->ref > 0) return; setup_free(setup); } static struct a2dp_setup_cb *setup_cb_new(struct a2dp_setup *setup) { struct a2dp_setup_cb *cb; cb = g_new0(struct a2dp_setup_cb, 1); cb->setup = setup; cb->id = ++cb_id; return cb; } static struct a2dp_setup_cb *setup_cb_add(struct a2dp_setup *setup) { struct a2dp_setup_cb *cb; cb = setup_cb_new(setup); setup->cb = g_slist_append(setup->cb, cb); return cb; } static void setup_cb_free(struct a2dp_setup_cb *cb) { struct a2dp_setup *setup = cb->setup; if (cb->source_id) g_source_remove(cb->source_id); setup->cb = g_slist_remove(setup->cb, cb); setup_unref(cb->setup); g_free(cb); } static void setup_error_set(struct a2dp_setup *setup, struct avdtp_error *err) { if (!err) { g_free(setup->err); setup->err = NULL; } else { if (!setup->err) setup->err = g_new0(struct avdtp_error, 1); memcpy(setup->err, err, sizeof(struct avdtp_error)); } } static void setup_error_init(struct a2dp_setup *setup, uint8_t type, int id) { struct avdtp_error err; avdtp_error_init(&err, type, id); setup_error_set(setup, &err); } static void finalize_setup_errno(struct a2dp_setup *s, int err, GSourceFunc cb1, ...) { GSourceFunc finalize; va_list args; if (err < 0) setup_error_init(s, AVDTP_ERRNO, -err); va_start(args, cb1); finalize = cb1; setup_ref(s); while (finalize != NULL) { finalize(s); finalize = va_arg(args, GSourceFunc); } setup_unref(s); va_end(args); } static int error_to_errno(struct avdtp_error *err) { int perr; if (!err) return 0; if (avdtp_error_category(err) != AVDTP_ERRNO) return -EIO; perr = avdtp_error_posix_errno(err); switch (perr) { case EHOSTDOWN: case ECONNABORTED: case EBADE: case ECONNREFUSED: return -perr; default: /* An unexpected error has occurred setup may be attempted * again. */ return -EAGAIN; } } static gboolean finalize_config(gpointer data) { struct a2dp_setup *s = data; GSList *l; struct avdtp_stream *stream = s->err ? NULL : s->stream; for (l = s->cb; l != NULL; ) { struct a2dp_setup_cb *cb = l->data; l = l->next; if (!cb->config_cb) continue; cb->config_cb(s->session, s->sep, stream, error_to_errno(s->err), cb->user_data); setup_cb_free(cb); } return FALSE; } static gboolean finalize_resume(gpointer data) { struct a2dp_setup *s = data; GSList *l; for (l = s->cb; l != NULL; ) { struct a2dp_setup_cb *cb = l->data; l = l->next; if (!cb->resume_cb) continue; cb->resume_cb(s->session, error_to_errno(s->err), cb->user_data); setup_cb_free(cb); } return FALSE; } static gboolean finalize_suspend(gpointer data) { struct a2dp_setup *s = data; GSList *l; for (l = s->cb; l != NULL; ) { struct a2dp_setup_cb *cb = l->data; l = l->next; if (!cb->suspend_cb) continue; cb->suspend_cb(s->session, error_to_errno(s->err), cb->user_data); setup_cb_free(cb); } return FALSE; } static void finalize_select(struct a2dp_setup *s) { GSList *l; for (l = s->cb; l != NULL; ) { struct a2dp_setup_cb *cb = l->data; l = l->next; if (!cb->select_cb) continue; cb->select_cb(s->session, s->sep, s->caps, error_to_errno(s->err), cb->user_data); setup_cb_free(cb); } } static void finalize_discover(struct a2dp_setup *s) { GSList *l; for (l = s->cb; l != NULL; ) { struct a2dp_setup_cb *cb = l->data; l = l->next; if (!cb->discover_cb) continue; cb->discover_cb(s->session, s->seps, error_to_errno(s->err), cb->user_data); setup_cb_free(cb); } } static gboolean finalize_all(gpointer data) { struct a2dp_setup *s = data; struct avdtp_stream *stream = s->err ? NULL : s->stream; GSList *l; for (l = s->cb; l != NULL; ) { struct a2dp_setup_cb *cb = l->data; l = l->next; if (cb->discover_cb) { cb->discover_cb(s->session, s->seps, error_to_errno(s->err), cb->user_data); } else if (cb->select_cb) { cb->select_cb(s->session, s->sep, s->caps, error_to_errno(s->err), cb->user_data); } else if (cb->suspend_cb) { cb->suspend_cb(s->session, error_to_errno(s->err), cb->user_data); } else if (cb->resume_cb) { cb->resume_cb(s->session, error_to_errno(s->err), cb->user_data); } else if (cb->config_cb) { cb->config_cb(s->session, s->sep, stream, error_to_errno(s->err), cb->user_data); } else warn("setup_cb doesn't have any callback function"); setup_cb_free(cb); } return FALSE; } static struct a2dp_setup *find_setup_by_session(struct avdtp *session) { GSList *l; for (l = setups; l != NULL; l = l->next) { struct a2dp_setup *setup = l->data; if (setup->session == session) return setup; } return NULL; } static struct a2dp_setup *a2dp_setup_get(struct avdtp *session) { struct a2dp_setup *setup; setup = find_setup_by_session(session); if (!setup) { setup = setup_new(session); if (!setup) return NULL; } return setup_ref(setup); } static struct a2dp_setup *find_setup_by_stream(struct avdtp_stream *stream) { GSList *l; for (l = setups; l != NULL; l = l->next) { struct a2dp_setup *setup = l->data; if (setup->stream == stream) return setup; } return NULL; } static bool match_stream_session(const void *data, const void *user_data) { const struct a2dp_stream *stream = data; const struct avdtp *session = user_data; return stream->session == session; } static struct a2dp_stream *a2dp_stream_new(struct a2dp_sep *sep, struct avdtp *session) { struct a2dp_stream *stream; if (!sep->streams) sep->streams = queue_new(); stream = new0(struct a2dp_stream, 1); stream->session = avdtp_ref(session); queue_push_tail(sep->streams, stream); return stream; } static struct a2dp_stream *a2dp_stream_get(struct a2dp_sep *sep, struct avdtp *session) { struct a2dp_stream *stream; DBG("sep %p session %p", sep, session); stream = queue_find(sep->streams, match_stream_session, session); if (stream) return stream; return a2dp_stream_new(sep, session); } static void a2dp_stream_starting(struct a2dp_sep *sep, struct avdtp *session) { struct a2dp_stream *stream; stream = a2dp_stream_get(sep, session); if (!stream) return; stream->starting = TRUE; } static void a2dp_stream_free(void *data) { struct a2dp_stream *stream = data; if (stream->suspend_timer) { timeout_remove(stream->suspend_timer); stream->suspend_timer = 0; } avdtp_unref(stream->session); free(stream); } static bool match_stream(const void *data, const void *user_data) { const struct a2dp_stream *astream = data; const struct avdtp_stream *stream = user_data; return astream->stream == stream; } static void a2dp_stream_destroy(struct a2dp_sep *sep, struct avdtp_stream *stream) { struct a2dp_stream *astream; DBG("sep %p stream %p", sep, stream); astream = queue_remove_if(sep->streams, match_stream, stream); if (astream) a2dp_stream_free(astream); } static void stream_state_changed(struct avdtp_stream *stream, avdtp_state_t old_state, avdtp_state_t new_state, struct avdtp_error *err, void *user_data) { struct a2dp_sep *sep = user_data; struct a2dp_stream *a2dp_stream; struct btd_device *dev = NULL; if (new_state == AVDTP_STATE_OPEN) { struct a2dp_setup *setup; int err; setup = find_setup_by_stream(stream); if (!setup || !setup->start || setup->err) return; setup->start = FALSE; err = avdtp_start(setup->session, stream); if (err < 0 && err != -EINPROGRESS) { error("avdtp_start: %s (%d)", strerror(-err), -err); finalize_setup_errno(setup, err, finalize_resume, NULL); return; } a2dp_stream_starting(sep, setup->session); return; } if (new_state != AVDTP_STATE_IDLE) return; a2dp_stream = queue_find(sep->streams, match_stream, stream); if (a2dp_stream) { dev = avdtp_get_device(a2dp_stream->session); a2dp_stream_destroy(sep, stream); } if (sep->endpoint && sep->endpoint->clear_configuration) sep->endpoint->clear_configuration(sep, dev, sep->user_data); } static gboolean auto_config(gpointer data) { struct a2dp_setup *setup = data; struct btd_device *dev = NULL; struct btd_service *service; struct a2dp_stream *stream; dev = avdtp_get_device(setup->session); if (setup->sep->type == AVDTP_SEP_TYPE_SOURCE) service = btd_device_get_service(dev, A2DP_SINK_UUID); else service = btd_device_get_service(dev, A2DP_SOURCE_UUID); if (service == NULL) { error("Unable to find btd service"); return FALSE; } /* Check if configuration was aborted */ stream = queue_find(setup->sep->streams, match_stream, setup->stream); if (!stream) return FALSE; if (setup->err != NULL) goto done; avdtp_stream_add_cb(setup->session, setup->stream, stream_state_changed, setup->sep); if (setup->sep->type == AVDTP_SEP_TYPE_SOURCE) { sink_new_stream(service, setup->session, setup->stream); } else { source_new_stream(service, setup->session, setup->stream); } done: if (setup->setconf_cb) setup->setconf_cb(setup->session, setup->stream, setup->err); finalize_config(setup); setup_error_set(setup, NULL); setup_unref(setup); return FALSE; } static void endpoint_setconf_cb(struct a2dp_setup *setup, uint8_t error_code) { if (error_code != 0) setup_error_init(setup, AVDTP_MEDIA_CODEC, error_code); auto_config(setup); setup_unref(setup); } static gboolean endpoint_match_codec_ind(struct avdtp *session, struct avdtp_media_codec_capability *codec, void *user_data) { struct a2dp_sep *sep = user_data; a2dp_vendor_codec_t *remote_codec; a2dp_vendor_codec_t *local_codec; uint8_t *capabilities; size_t length; if (codec->media_codec_type != A2DP_CODEC_VENDOR) return TRUE; if (sep->endpoint == NULL) return FALSE; length = sep->endpoint->get_capabilities(sep, &capabilities, sep->user_data); if (length < sizeof(a2dp_vendor_codec_t)) return FALSE; local_codec = (a2dp_vendor_codec_t *) capabilities; remote_codec = (a2dp_vendor_codec_t *) codec->data; if (A2DP_GET_VENDOR_ID(*remote_codec) != A2DP_GET_VENDOR_ID(*local_codec)) return FALSE; if (A2DP_GET_CODEC_ID(*remote_codec) != A2DP_GET_CODEC_ID(*local_codec)) return FALSE; DBG("vendor 0x%08x codec 0x%04x", A2DP_GET_VENDOR_ID(*remote_codec), A2DP_GET_CODEC_ID(*remote_codec)); return TRUE; } static void reverse_discover(struct avdtp *session, GSList *seps, int err, void *user_data) { DBG("err %d", err); } static gboolean endpoint_setconf_ind(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, GSList *caps, avdtp_set_configuration_cb cb, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; struct a2dp_setup *setup; struct a2dp_stream *a2dp_stream; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: Set_Configuration_Ind", sep); else DBG("Source %p: Set_Configuration_Ind", sep); a2dp_stream = a2dp_stream_get(a2dp_sep, session); if (!a2dp_stream) return FALSE; setup = a2dp_setup_get(session); if (!setup) return FALSE; a2dp_stream->stream = stream; setup->sep = a2dp_sep; setup->stream = stream; setup->setconf_cb = cb; for (; caps != NULL; caps = g_slist_next(caps)) { struct avdtp_service_capability *cap = caps->data; struct avdtp_media_codec_capability *codec; gboolean ret; if (cap->category == AVDTP_DELAY_REPORTING && !a2dp_sep->delay_reporting) { setup_error_init(setup, AVDTP_DELAY_REPORTING, AVDTP_UNSUPPORTED_CONFIGURATION); goto done; } if (cap->category != AVDTP_MEDIA_CODEC) continue; codec = (struct avdtp_media_codec_capability *) cap->data; if (codec->media_codec_type != a2dp_sep->codec) { setup_error_init(setup, AVDTP_MEDIA_CODEC, AVDTP_UNSUPPORTED_CONFIGURATION); goto done; } ret = a2dp_sep->endpoint->set_configuration(a2dp_sep, codec->data, cap->length - sizeof(*codec), setup_ref(setup), endpoint_setconf_cb, a2dp_sep->user_data); if (ret == 0) { /* Attempt to reverse discover if there are no remote * SEPs. */ if (queue_isempty(setup->chan->seps)) a2dp_discover(session, reverse_discover, NULL); return TRUE; } setup_error_init(setup, AVDTP_MEDIA_CODEC, AVDTP_UNSUPPORTED_CONFIGURATION); setup_unref(setup); break; } done: g_idle_add(auto_config, setup); return TRUE; } static gboolean endpoint_getcap_ind(struct avdtp *session, struct avdtp_local_sep *sep, gboolean get_all, GSList **caps, uint8_t *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; struct avdtp_service_capability *media_transport, *media_codec; struct avdtp_media_codec_capability *codec_caps; uint8_t *capabilities; size_t length; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: Get_Capability_Ind", sep); else DBG("Source %p: Get_Capability_Ind", sep); *caps = NULL; media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, NULL, 0); *caps = g_slist_append(*caps, media_transport); length = a2dp_sep->endpoint->get_capabilities(a2dp_sep, &capabilities, a2dp_sep->user_data); codec_caps = g_malloc0(sizeof(*codec_caps) + length); codec_caps->media_type = AVDTP_MEDIA_TYPE_AUDIO; codec_caps->media_codec_type = a2dp_sep->codec; memcpy(codec_caps->data, capabilities, length); media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, codec_caps, sizeof(*codec_caps) + length); *caps = g_slist_append(*caps, media_codec); g_free(codec_caps); if (get_all) { struct avdtp_service_capability *delay_reporting; delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING, NULL, 0); *caps = g_slist_append(*caps, delay_reporting); } return TRUE; } static void endpoint_open_cb(struct a2dp_setup *setup, uint8_t error_code) { int err = error_to_errno(setup->err); if (error_code != 0) { setup->stream = NULL; finalize_setup_errno(setup, -EPERM, finalize_config, NULL); goto done; } if (err == 0) err = avdtp_open(setup->session, setup->stream); if (err == 0) goto done; error("avdtp_open %s (%d)", strerror(-err), -err); setup->stream = NULL; finalize_setup_errno(setup, err, finalize_config, NULL); done: setup_unref(setup); } static bool match_remote_sep(const void *data, const void *user_data) { const struct a2dp_remote_sep *sep = data; const struct avdtp_remote_sep *rsep = user_data; return sep->sep == rsep; } static void store_remote_sep(void *data, void *user_data) { struct a2dp_remote_sep *sep = data; GKeyFile *key_file = user_data; char seid[4], value[9 + 512]; struct avdtp_service_capability *service = avdtp_get_codec(sep->sep); struct avdtp_media_codec_capability *codec; unsigned int i; ssize_t offset; if (!service) return; codec = (void *) service->data; sprintf(seid, "%02hhx", avdtp_get_seid(sep->sep)); offset = sprintf(value, "%02hhx:%02hhx:%02hhx:", avdtp_get_type(sep->sep), codec->media_codec_type, avdtp_get_delay_reporting(sep->sep)); for (i = 0; i < service->length - sizeof(*codec); i++) offset += sprintf(value + offset, "%02hhx", codec->data[i]); g_key_file_set_string(key_file, "Endpoints", seid, value); } static void store_remote_seps(struct a2dp_channel *chan) { struct btd_device *device = chan->device; char filename[PATH_MAX]; char dst_addr[18]; GKeyFile *key_file; GError *gerr = NULL; char *data; gsize length = 0; ba2str(device_get_address(device), dst_addr); create_filename(filename, PATH_MAX, "/%s/cache/%s", btd_adapter_get_storage_dir(device_get_adapter(device)), dst_addr); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } data = g_key_file_get_string(key_file, "Endpoints", "LastUsed", NULL); /* Remove current endpoints since it might have changed */ g_key_file_remove_group(key_file, "Endpoints", NULL); queue_foreach(chan->seps, store_remote_sep, key_file); if (data) { g_key_file_set_string(key_file, "Endpoints", "LastUsed", data); g_free(data); } data = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(data); g_key_file_free(key_file); } static void invalidate_remote_cache(struct a2dp_setup *setup, struct avdtp_error *err) { if (err->category == AVDTP_ERRNO) return; /* Attempt to unregister Remote SEP if configuration fails and it was * loaded from cache. */ if (setup->rsep && setup->rsep->from_cache) { warn("Invalidating Remote SEP from cache"); avdtp_unregister_remote_sep(setup->session, setup->rsep->sep); /* Update cache */ store_remote_seps(setup->chan); /* Set error to -EAGAIN so the likes of policy plugin can * reattempt to connect. */ setup_error_init(setup, AVDTP_ERRNO, -EAGAIN); } } static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; struct a2dp_setup *setup; struct a2dp_stream *a2dp_stream; struct btd_device *dev; struct btd_service *service; int ret; dev = avdtp_get_device(session); if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) { DBG("Sink %p: Set_Configuration_Cfm", sep); service = btd_device_get_service(dev, A2DP_SOURCE_UUID); } else { DBG("Source %p: Set_Configuration_Cfm", sep); service = btd_device_get_service(dev, A2DP_SINK_UUID); } if (service == NULL) { error("Unable to find btd service"); return; } setup = find_setup_by_session(session); if (err) { if (setup) { setup_ref(setup); setup_error_set(setup, err); invalidate_remote_cache(setup, err); finalize_config(setup); setup_error_set(setup, NULL); setup_unref(setup); } return; } avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep); a2dp_stream = a2dp_stream_get(a2dp_sep, session); if (a2dp_stream) a2dp_stream->stream = stream; if (!setup) return; /* Notify D-Bus interface of the new stream */ if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE) { sink_new_stream(service, session, setup->stream); } else { source_new_stream(service, session, setup->stream); } /* Notify Endpoint */ if (a2dp_sep->endpoint) { struct avdtp_service_capability *service; struct avdtp_media_codec_capability *codec; int err; service = avdtp_stream_get_codec(stream); codec = (struct avdtp_media_codec_capability *) service->data; err = a2dp_sep->endpoint->set_configuration(a2dp_sep, codec->data, service->length - sizeof(*codec), setup_ref(setup), endpoint_open_cb, a2dp_sep->user_data); if (err == 0) return; setup->stream = NULL; finalize_setup_errno(setup, -EPERM, finalize_config, NULL); setup_unref(setup); return; } ret = avdtp_open(session, stream); if (ret < 0) { error("avdtp_open %s (%d)", strerror(-ret), -ret); setup->stream = NULL; finalize_setup_errno(setup, ret, finalize_config, NULL); } } static gboolean getconf_ind(struct avdtp *session, struct avdtp_local_sep *sep, uint8_t *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: Get_Configuration_Ind", sep); else DBG("Source %p: Get_Configuration_Ind", sep); return TRUE; } static void getconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: Set_Configuration_Cfm", sep); else DBG("Source %p: Set_Configuration_Cfm", sep); } static void store_last_used(struct a2dp_channel *chan, uint8_t lseid, uint8_t rseid) { GKeyFile *key_file; GError *gerr = NULL; char filename[PATH_MAX]; char dst_addr[18]; char value[6]; char *data; size_t len = 0; ba2str(device_get_address(chan->device), dst_addr); create_filename(filename, PATH_MAX, "/%s/cache/%s", btd_adapter_get_storage_dir(device_get_adapter(chan->device)), dst_addr); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_clear_error(&gerr); } sprintf(value, "%02hhx:%02hhx", lseid, rseid); g_key_file_set_string(key_file, "Endpoints", "LastUsed", value); data = g_key_file_to_data(key_file, &len, NULL); if (!g_file_set_contents(filename, data, len, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(data); g_key_file_free(key_file); } static void add_last_used(struct a2dp_channel *chan, struct a2dp_sep *lsep, struct a2dp_remote_sep *rsep) { if (!chan->last_used) chan->last_used = new0(struct a2dp_last_used, 1); chan->last_used->lsep = lsep; chan->last_used->rsep = rsep; } static void update_last_used(struct a2dp_channel *chan, struct a2dp_sep *lsep, struct avdtp_stream *stream) { struct avdtp_remote_sep *rsep; struct a2dp_remote_sep *sep; rsep = avdtp_stream_get_remote_sep(stream); sep = queue_find(chan->seps, match_remote_sep, rsep); if (!sep) { error("Unable to find remote SEP"); return; } /* Check if already stored then skip */ if (chan->last_used && (chan->last_used->lsep == lsep && chan->last_used->rsep == sep)) return; add_last_used(chan, lsep, sep); store_last_used(chan, avdtp_sep_get_seid(lsep->lsep), avdtp_get_seid(rsep)); } static gboolean open_ind(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, uint8_t *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; struct a2dp_setup *setup; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: Open_Ind", sep); else DBG("Source %p: Open_Ind", sep); setup = a2dp_setup_get(session); if (!setup) return FALSE; setup->stream = stream; if (!err && setup->chan) update_last_used(setup->chan, a2dp_sep, stream); if (setup->reconfigure) setup->reconfigure = FALSE; finalize_config(setup); return TRUE; } static void open_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; struct a2dp_setup *setup; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: Open_Cfm", sep); else DBG("Source %p: Open_Cfm", sep); setup = find_setup_by_session(session); if (!setup) return; if (setup->reconfigure) setup->reconfigure = FALSE; if (err) { setup->stream = NULL; setup_error_set(setup, err); if (setup->start) finalize_resume(setup); } else if (setup->chan) update_last_used(setup->chan, a2dp_sep, stream); finalize_config(setup); return; } static bool suspend_timeout(struct a2dp_stream *stream) { if (avdtp_suspend(stream->session, stream->stream) == 0) stream->suspending = TRUE; stream->suspend_timer = 0; return FALSE; } static gboolean start_ind(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, uint8_t *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; struct a2dp_stream *a2dp_stream; struct a2dp_setup *setup; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: Start_Ind", sep); else DBG("Source %p: Start_Ind", sep); a2dp_stream = queue_find(a2dp_sep->streams, match_stream, stream); if (!a2dp_stream) return FALSE; if (!a2dp_stream->locked) a2dp_stream->suspend_timer = timeout_add_seconds( SUSPEND_TIMEOUT, (timeout_func_t) suspend_timeout, a2dp_stream, NULL); if (!a2dp_stream->starting) return TRUE; a2dp_stream->starting = FALSE; setup = find_setup_by_session(session); if (setup) finalize_resume(setup); return TRUE; } static void start_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; struct a2dp_stream *a2dp_stream; struct a2dp_setup *setup; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: Start_Cfm", sep); else DBG("Source %p: Start_Cfm", sep); a2dp_stream = queue_find(a2dp_sep->streams, match_stream, stream); if (!a2dp_stream) return; a2dp_stream->starting = FALSE; setup = find_setup_by_session(session); if (!setup) return; if (err) { setup->stream = NULL; setup_error_set(setup, err); } finalize_resume(setup); } static gboolean suspend_ind(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, uint8_t *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; struct a2dp_stream *a2dp_stream; struct a2dp_setup *setup; gboolean start; int start_err; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: Suspend_Ind", sep); else DBG("Source %p: Suspend_Ind", sep); a2dp_stream = queue_find(a2dp_sep->streams, match_stream, stream); if (!a2dp_stream) return FALSE; if (a2dp_stream->suspend_timer) { timeout_remove(a2dp_stream->suspend_timer); a2dp_stream->suspend_timer = 0; } if (!a2dp_stream->suspending) return TRUE; a2dp_stream->suspending = FALSE; setup = find_setup_by_session(session); if (!setup) return TRUE; start = setup->start; setup->start = FALSE; finalize_suspend(setup); if (!start) return TRUE; start_err = avdtp_start(session, a2dp_stream->stream); if (start_err < 0 && start_err != -EINPROGRESS) { error("avdtp_start: %s (%d)", strerror(-start_err), -start_err); finalize_setup_errno(setup, start_err, finalize_resume, NULL); } return TRUE; } static void suspend_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; struct a2dp_stream *a2dp_stream; struct a2dp_setup *setup; gboolean start; int start_err; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: Suspend_Cfm", sep); else DBG("Source %p: Suspend_Cfm", sep); a2dp_stream = queue_find(a2dp_sep->streams, match_stream, stream); if (!a2dp_stream) return; a2dp_stream->suspending = FALSE; setup = find_setup_by_session(session); if (!setup) return; start = setup->start; setup->start = FALSE; if (err) { setup->stream = NULL; setup_error_set(setup, err); } finalize_suspend(setup); if (!start) return; if (err) { finalize_resume(setup); return; } start_err = avdtp_start(session, a2dp_stream->stream); if (start_err < 0 && start_err != -EINPROGRESS) { error("avdtp_start: %s (%d)", strerror(-start_err), -start_err); finalize_setup_errno(setup, start_err, finalize_suspend, NULL); } } static gboolean close_ind(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, uint8_t *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; struct a2dp_setup *setup; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: Close_Ind", sep); else DBG("Source %p: Close_Ind", sep); setup = find_setup_by_session(session); if (!setup) return TRUE; finalize_setup_errno(setup, -ECONNRESET, finalize_suspend, finalize_resume, NULL); return TRUE; } static struct a2dp_remote_sep *find_remote_sep(struct a2dp_channel *chan, struct a2dp_sep *sep) { struct avdtp_remote_sep *rsep; if (!chan || !sep) return NULL; rsep = avdtp_find_remote_sep(chan->session, sep->lsep); return queue_find(chan->seps, match_remote_sep, rsep); } static gboolean a2dp_reconfigure(gpointer data) { struct a2dp_setup *setup = data; struct a2dp_sep *sep = setup->sep; int posix_err; struct avdtp_media_codec_capability *rsep_codec; struct avdtp_service_capability *cap; setup->id = 0; if (setup->err) { posix_err = error_to_errno(setup->err); goto failed; } if (!sep->lsep) { error("no valid local SEP"); posix_err = -EINVAL; goto failed; } if (setup->rsep) { cap = avdtp_get_codec(setup->rsep->sep); rsep_codec = (struct avdtp_media_codec_capability *) cap->data; /* Check that codec really match after closing */ if (sep->codec != rsep_codec->media_codec_type) setup->rsep = NULL; } if (!setup->rsep) setup->rsep = find_remote_sep(setup->chan, sep); if (!setup->rsep) { error("unable to find remote SEP"); posix_err = -EINVAL; goto failed; } posix_err = avdtp_set_configuration(setup->session, setup->rsep->sep, sep->lsep, setup->caps, &setup->stream); if (posix_err < 0) { error("avdtp_set_configuration: %s", strerror(-posix_err)); goto failed; } return FALSE; failed: finalize_setup_errno(setup, posix_err, finalize_config, NULL); return FALSE; } static bool setup_reconfigure(struct a2dp_setup *setup) { if (!setup->reconfigure || setup->id) return false; DBG("%p", setup); setup->id = g_idle_add(a2dp_reconfigure, setup); setup->reconfigure = FALSE; return true; } static struct a2dp_remote_sep *get_remote_sep(struct a2dp_channel *chan, struct avdtp_stream *stream) { struct avdtp_remote_sep *rsep; rsep = avdtp_stream_get_remote_sep(stream); return queue_find(chan->seps, match_remote_sep, rsep); } static void close_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; struct a2dp_setup *setup; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: Close_Cfm", sep); else DBG("Source %p: Close_Cfm", sep); setup = find_setup_by_session(session); if (!setup) return; if (err) { setup->stream = NULL; setup_error_set(setup, err); finalize_config(setup); return; } if (!setup->rsep) setup->rsep = get_remote_sep(setup->chan, stream); setup_reconfigure(setup); } static void abort_ind(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, uint8_t *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; struct a2dp_setup *setup; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: Abort_Ind", sep); else DBG("Source %p: Abort_Ind", sep); a2dp_stream_destroy(a2dp_sep, stream); setup = find_setup_by_session(session); if (!setup) return; finalize_setup_errno(setup, -ECONNRESET, finalize_suspend, finalize_resume, finalize_config, NULL); return; } static void abort_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; struct a2dp_setup *setup; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: Abort_Cfm", sep); else DBG("Source %p: Abort_Cfm", sep); setup = find_setup_by_session(session); if (!setup) return; if (setup_reconfigure(setup)) return; setup_unref(setup); } static gboolean reconf_ind(struct avdtp *session, struct avdtp_local_sep *sep, uint8_t *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: ReConfigure_Ind", sep); else DBG("Source %p: ReConfigure_Ind", sep); return TRUE; } static gboolean endpoint_delayreport_ind(struct avdtp *session, struct avdtp_local_sep *sep, uint8_t rseid, uint16_t delay, uint8_t *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: DelayReport_Ind", sep); else DBG("Source %p: DelayReport_Ind", sep); if (a2dp_sep->endpoint == NULL || a2dp_sep->endpoint->set_delay == NULL) return FALSE; a2dp_sep->endpoint->set_delay(a2dp_sep, delay, a2dp_sep->user_data); return TRUE; } static void reconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; struct a2dp_setup *setup; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: ReConfigure_Cfm", sep); else DBG("Source %p: ReConfigure_Cfm", sep); setup = find_setup_by_session(session); if (!setup) return; if (err) { setup->stream = NULL; setup_error_set(setup, err); } finalize_config(setup); } static void delay_report_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) DBG("Sink %p: DelayReport_Cfm", sep); else DBG("Source %p: DelayReport_Cfm", sep); } static struct avdtp_sep_cfm cfm = { .set_configuration = setconf_cfm, .get_configuration = getconf_cfm, .open = open_cfm, .start = start_cfm, .suspend = suspend_cfm, .close = close_cfm, .abort = abort_cfm, .reconfigure = reconf_cfm, .delay_report = delay_report_cfm, }; static struct avdtp_sep_ind endpoint_ind = { .match_codec = endpoint_match_codec_ind, .get_capability = endpoint_getcap_ind, .set_configuration = endpoint_setconf_ind, .get_configuration = getconf_ind, .open = open_ind, .start = start_ind, .suspend = suspend_ind, .close = close_ind, .abort = abort_ind, .reconfigure = reconf_ind, .delayreport = endpoint_delayreport_ind, }; static sdp_record_t *a2dp_record(uint8_t type) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, l2cap_uuid, avdtp_uuid, a2dp_uuid; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[2]; sdp_record_t *record; sdp_data_t *psm, *version, *features; uint16_t lp = AVDTP_UUID; uint16_t a2dp_ver = 0x0104, avdtp_ver = 0x0103, feat = 0x000f; record = sdp_record_alloc(); if (!record) return NULL; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(record, root); if (type == AVDTP_SEP_TYPE_SOURCE) sdp_uuid16_create(&a2dp_uuid, AUDIO_SOURCE_SVCLASS_ID); else sdp_uuid16_create(&a2dp_uuid, AUDIO_SINK_SVCLASS_ID); svclass_id = sdp_list_append(0, &a2dp_uuid); sdp_set_service_classes(record, svclass_id); sdp_uuid16_create(&profile[0].uuid, ADVANCED_AUDIO_PROFILE_ID); profile[0].version = a2dp_ver; pfseq = sdp_list_append(0, &profile[0]); sdp_set_profile_descs(record, pfseq); sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap_uuid); psm = sdp_data_alloc(SDP_UINT16, &lp); proto[0] = sdp_list_append(proto[0], psm); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&avdtp_uuid, AVDTP_UUID); proto[1] = sdp_list_append(0, &avdtp_uuid); version = sdp_data_alloc(SDP_UINT16, &avdtp_ver); proto[1] = sdp_list_append(proto[1], version); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(record, aproto); features = sdp_data_alloc(SDP_UINT16, &feat); sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); if (type == AVDTP_SEP_TYPE_SOURCE) sdp_set_info_attr(record, "Audio Source", 0, 0); else sdp_set_info_attr(record, "Audio Sink", 0, 0); free(psm); free(version); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(pfseq, 0); sdp_list_free(aproto, 0); sdp_list_free(root, 0); sdp_list_free(svclass_id, 0); return record; } static struct a2dp_server *find_server(GSList *list, struct btd_adapter *a) { for (; list; list = list->next) { struct a2dp_server *server = list->data; if (server->adapter == a) return server; } return NULL; } static void remote_sep_free(void *data) { struct a2dp_remote_sep *sep = data; avdtp_remote_sep_set_destroy(sep->sep, NULL, NULL); free(sep->path); free(sep); } static void remove_remote_sep(void *data) { struct a2dp_remote_sep *sep = data; if (!sep->path) { remote_sep_free(sep); return; } g_dbus_unregister_interface(btd_get_dbus_connection(), sep->path, MEDIA_ENDPOINT_INTERFACE); } static void channel_free(void *data) { struct a2dp_channel *chan = data; struct a2dp_setup *setup; if (chan->auth_id > 0) btd_cancel_authorization(chan->auth_id); if (chan->io_id > 0) g_source_remove(chan->io_id); if (chan->io) { g_io_channel_shutdown(chan->io, TRUE, NULL); g_io_channel_unref(chan->io); } avdtp_remove_state_cb(chan->state_id); queue_destroy(chan->seps, remove_remote_sep); free(chan->last_used); setup = find_setup_by_session(chan->session); if (setup) { setup->chan = NULL; setup_ref(setup); /* Finalize pending commands before we NULL setup->session */ finalize_setup_errno(setup, -ENOTCONN, finalize_all, NULL); avdtp_unref(setup->session); setup->session = NULL; setup_unref(setup); } g_free(chan); } static void channel_remove(struct a2dp_channel *chan) { struct a2dp_server *server = chan->server; DBG("chan %p", chan); queue_remove(server->channels, chan); channel_free(chan); } static gboolean disconnect_cb(GIOChannel *io, GIOCondition cond, gpointer data) { struct a2dp_channel *chan = data; DBG("chan %p", chan); chan->io_id = 0; channel_remove(chan); return FALSE; } static void caps_add_codec(GSList **l, uint8_t codec, uint8_t *caps, size_t size) { struct avdtp_service_capability *media_transport, *media_codec; struct avdtp_media_codec_capability *cap; media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, NULL, 0); *l = g_slist_append(*l, media_transport); cap = g_malloc0(sizeof(*cap) + size); cap->media_type = AVDTP_MEDIA_TYPE_AUDIO; cap->media_codec_type = codec; memcpy(cap->data, caps, size); media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, cap, sizeof(*cap) + size); *l = g_slist_append(*l, media_codec); g_free(cap); } struct client { const char *sender; const char *path; }; static int match_client(const void *data, const void *user_data) { struct a2dp_sep *sep = (void *) data; const struct a2dp_endpoint *endpoint = sep->endpoint; const struct client *client = user_data; if (strcmp(client->sender, endpoint->get_name(sep, sep->user_data))) return -1; return strcmp(client->path, endpoint->get_path(sep, sep->user_data)); } static struct a2dp_sep *find_sep(struct a2dp_server *server, uint8_t type, const char *sender, const char *path) { GSList *l; struct client client = { sender, path }; l = type == AVDTP_SEP_TYPE_SINK ? server->sources : server->sinks; l = g_slist_find_custom(l, &client, match_client); if (l) return l->data; return NULL; } static int parse_properties(DBusMessageIter *props, uint8_t **caps, int *size) { while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { const char *key; DBusMessageIter value, entry; int var; dbus_message_iter_recurse(props, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); var = dbus_message_iter_get_arg_type(&value); if (strcasecmp(key, "Capabilities") == 0) { DBusMessageIter array; if (var != DBUS_TYPE_ARRAY) return -EINVAL; dbus_message_iter_recurse(&value, &array); dbus_message_iter_get_fixed_array(&array, caps, size); return 0; } dbus_message_iter_next(props); } return -EINVAL; } static void reconfig_cb(struct avdtp *session, struct a2dp_sep *sep, struct avdtp_stream *stream, int err, void *user_data) { DBusMessage *msg = user_data; if (err) g_dbus_send_message(btd_get_dbus_connection(), btd_error_failed(msg, strerror(-err))); else g_dbus_send_reply(btd_get_dbus_connection(), msg, DBUS_TYPE_INVALID); dbus_message_unref(msg); } static int a2dp_reconfig(struct a2dp_channel *chan, const char *sender, struct a2dp_sep *lsep, struct a2dp_remote_sep *rsep, uint8_t *caps, int size, void *user_data) { struct a2dp_setup *setup; struct a2dp_stream *stream; struct a2dp_setup_cb *cb_data; GSList *l; int err; if (!chan->session) return -ENOTCONN; setup = a2dp_setup_get(chan->session); if (!setup) return -ENOMEM; cb_data = setup_cb_add(setup); cb_data->config_cb = reconfig_cb; cb_data->user_data = user_data; setup->sep = lsep; setup->rsep = rsep; g_slist_free_full(setup->caps, g_free); setup->caps = NULL; caps_add_codec(&setup->caps, setup->sep->codec, caps, size); l = avdtp_get_type(rsep->sep) == AVDTP_SEP_TYPE_SINK ? chan->server->sources : chan->server->sinks; /* Check for existing stream and close it */ for (; l; l = g_slist_next(l)) { struct a2dp_sep *tmp = l->data; /* Attempt to reconfigure if a stream already exists */ stream = queue_find(tmp->streams, match_stream_session, chan->session); if (stream) { /* Only allow switching sep from the same sender */ if (strcmp(sender, tmp->endpoint->get_name(tmp, tmp->user_data))) { err = -EPERM; goto fail; } /* Check if stream is for the channel */ if (!avdtp_has_stream(chan->session, stream->stream)) continue; err = avdtp_close(chan->session, stream->stream, FALSE); if (err < 0) { err = avdtp_abort(chan->session, stream->stream); if (err < 0) { error("avdtp_abort: %s", strerror(-err)); goto fail; } } setup->reconfigure = TRUE; return 0; } } err = avdtp_set_configuration(setup->session, setup->rsep->sep, lsep->lsep, setup->caps, &setup->stream); if (err < 0) { error("avdtp_set_configuration: %s", strerror(-err)); goto fail; } return 0; fail: setup_cb_free(cb_data); return err; } static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg, void *data) { struct a2dp_remote_sep *rsep = data; struct a2dp_channel *chan = rsep->chan; struct a2dp_sep *lsep = NULL; struct avdtp_service_capability *service; struct avdtp_media_codec_capability *codec; DBusMessageIter args, props; const char *sender, *path; uint8_t *caps; int err, size = 0; sender = dbus_message_get_sender(msg); dbus_message_iter_init(msg, &args); dbus_message_iter_get_basic(&args, &path); dbus_message_iter_next(&args); lsep = find_sep(chan->server, avdtp_get_type(rsep->sep), sender, path); if (!lsep) return btd_error_invalid_args(msg); service = avdtp_get_codec(rsep->sep); codec = (struct avdtp_media_codec_capability *) service->data; /* Check if codec really matches */ if (!endpoint_match_codec_ind(chan->session, codec, lsep)) return btd_error_invalid_args(msg); dbus_message_iter_recurse(&args, &props); if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) return btd_error_invalid_args(msg); if (parse_properties(&props, &caps, &size) < 0) return btd_error_invalid_args(msg); err = a2dp_reconfig(chan, sender, lsep, rsep, caps, size, dbus_message_ref(msg)); if (err < 0) { dbus_message_unref(msg); return btd_error_failed(msg, strerror(-err)); } return NULL; } static const GDBusMethodTable sep_methods[] = { { GDBUS_ASYNC_METHOD("SetConfiguration", GDBUS_ARGS({ "endpoint", "o" }, { "properties", "a{sv}" } ), NULL, set_configuration) }, { }, }; static gboolean get_uuid(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct a2dp_remote_sep *sep = data; const char *uuid; switch (avdtp_get_type(sep->sep)) { case AVDTP_SEP_TYPE_SOURCE: uuid = A2DP_SOURCE_UUID; break; case AVDTP_SEP_TYPE_SINK: uuid = A2DP_SINK_UUID; break; default: uuid = ""; } dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); return TRUE; } static gboolean get_codec(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct a2dp_remote_sep *sep = data; struct avdtp_service_capability *cap = avdtp_get_codec(sep->sep); struct avdtp_media_codec_capability *codec = (void *) cap->data; dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &codec->media_codec_type); return TRUE; } static gboolean get_capabilities(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct a2dp_remote_sep *sep = data; struct avdtp_service_capability *service = avdtp_get_codec(sep->sep); struct avdtp_media_codec_capability *codec = (void *) service->data; uint8_t *caps = codec->data; DBusMessageIter array; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array); dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &caps, service->length - sizeof(*codec)); dbus_message_iter_close_container(iter, &array); return TRUE; } static gboolean get_device(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct a2dp_remote_sep *sep = data; const char *path; path = device_get_path(sep->chan->device); dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); return TRUE; } static gboolean get_delay_reporting(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct a2dp_remote_sep *sep = data; dbus_bool_t delay_report; delay_report = avdtp_get_delay_reporting(sep->sep); dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &delay_report); return TRUE; } static const GDBusPropertyTable sep_properties[] = { { "UUID", "s", get_uuid, NULL, NULL }, { "Codec", "y", get_codec, NULL, NULL }, { "Capabilities", "ay", get_capabilities, NULL, NULL }, { "Device", "o", get_device, NULL, NULL }, { "DelayReporting", "b", get_delay_reporting, NULL, NULL }, { } }; static void remote_sep_destroy(void *user_data) { struct a2dp_remote_sep *sep = user_data; if (queue_remove(sep->chan->seps, sep)) remove_remote_sep(sep); } static struct a2dp_remote_sep *register_remote_sep(void *data, void *user_data) { struct avdtp_remote_sep *rsep = data; struct a2dp_channel *chan = user_data; struct a2dp_remote_sep *sep; sep = queue_find(chan->seps, match_remote_sep, rsep); if (sep) return sep; if (!avdtp_get_codec(rsep)) { error("Unable to get remote sep codec"); return NULL; } sep = new0(struct a2dp_remote_sep, 1); sep->chan = chan; sep->sep = rsep; if (asprintf(&sep->path, "%s/sep%d", device_get_path(chan->device), avdtp_get_seid(rsep)) < 0) { error("Could not allocate path for remote sep %s/sep%d", device_get_path(chan->device), avdtp_get_seid(rsep)); sep->path = NULL; goto done; } if (g_dbus_register_interface(btd_get_dbus_connection(), sep->path, MEDIA_ENDPOINT_INTERFACE, sep_methods, NULL, sep_properties, sep, remote_sep_free) == FALSE) { error("Could not register remote sep %s", sep->path); free(sep->path); free(sep); return NULL; } DBG("Found remote SEP: %s", sep->path); avdtp_remote_sep_set_destroy(rsep, sep, remote_sep_destroy); done: queue_push_tail(chan->seps, sep); return sep; } static bool match_seid(const void *data, const void *user_data) { const struct a2dp_remote_sep *sep = data; const uint8_t *seid = user_data; return avdtp_get_seid(sep->sep) == *seid; } static int match_sep(const void *data, const void *user_data) { struct a2dp_sep *sep = (void *) data; const uint8_t *seid = user_data; return *seid - avdtp_sep_get_seid(sep->lsep); } static struct a2dp_sep *find_sep_by_seid(struct a2dp_server *server, uint8_t seid) { GSList *l; l = g_slist_find_custom(server->sources, &seid, match_sep); if (l) return l->data; l = g_slist_find_custom(server->sinks, &seid, match_sep); if (l) return l->data; return NULL; } static void load_remote_sep(struct a2dp_channel *chan, GKeyFile *key_file, char **seids) { struct a2dp_sep *lsep; struct a2dp_remote_sep *sep; struct avdtp_remote_sep *rsep; uint8_t lseid, rseid; char *value; bool update = false; if (!seids) return; for (; *seids; seids++) { uint8_t type; uint8_t codec; uint8_t delay_reporting; GSList *l = NULL; char caps[513]; uint8_t data[128]; int i, size; if (sscanf(*seids, "%02hhx", &rseid) != 1) continue; value = g_key_file_get_string(key_file, "Endpoints", *seids, NULL); if (!value) continue; /* Try loading with delay_reporting first */ if (sscanf(value, "%02hhx:%02hhx:%02hhx:%512s", &type, &codec, &delay_reporting, caps) != 4) { /* Try old format */ if (sscanf(value, "%02hhx:%02hhx:%512s", &type, &codec, caps) != 3) { warn("Unable to load Endpoint: seid %u", rseid); g_free(value); continue; } delay_reporting = false; } for (i = 0, size = strlen(caps); i < size && i >= 2; i += 2) { uint8_t *tmp = data + i / 2; if (sscanf(caps + i, "%02hhx", tmp) != 1) { warn("Unable to load Endpoint: seid %u", rseid); break; } } g_free(value); if (i != size) continue; caps_add_codec(&l, codec, data, size / 2); rsep = avdtp_register_remote_sep(chan->session, rseid, type, l, delay_reporting); if (!rsep) { warn("Unable to register Endpoint: seid %u", rseid); continue; } sep = register_remote_sep(rsep, chan); if (!sep) { avdtp_unregister_remote_sep(chan->session, rsep); update = true; continue; } sep->from_cache = true; } /* Update cache */ if (update) store_remote_seps(chan); value = g_key_file_get_string(key_file, "Endpoints", "LastUsed", NULL); if (!value) return; if (sscanf(value, "%02hhx:%02hhx", &lseid, &rseid) != 2) { warn("Unable to load LastUsed"); g_free(value); return; } g_free(value); lsep = find_sep_by_seid(chan->server, lseid); if (!lsep) { warn("Unable to load LastUsed: lseid %u not found", lseid); return; } sep = queue_find(chan->seps, match_seid, &rseid); if (!sep) { warn("Unable to load LastUsed: rseid %u not found", rseid); return; } DBG("LastUsed: lseid %u rseid %u", lseid, rseid); add_last_used(chan, lsep, sep); } static void load_remote_seps(struct a2dp_channel *chan) { struct btd_device *device = chan->device; char filename[PATH_MAX]; char dst_addr[18]; char **keys; GKeyFile *key_file; GError *gerr = NULL; ba2str(device_get_address(device), dst_addr); create_filename(filename, PATH_MAX, "/%s/cache/%s", btd_adapter_get_storage_dir(device_get_adapter(device)), dst_addr); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_error_free(gerr); } keys = g_key_file_get_keys(key_file, "Endpoints", NULL, NULL); load_remote_sep(chan, key_file, keys); g_strfreev(keys); g_key_file_free(key_file); } static void avdtp_state_cb(struct btd_device *dev, struct avdtp *session, avdtp_session_state_t old_state, avdtp_session_state_t new_state, void *user_data) { struct a2dp_channel *chan = user_data; switch (new_state) { case AVDTP_SESSION_STATE_DISCONNECTED: if (chan->session == session) channel_remove(chan); break; case AVDTP_SESSION_STATE_CONNECTING: break; case AVDTP_SESSION_STATE_CONNECTED: if (!chan->session) chan->session = session; load_remote_seps(chan); break; } } static struct a2dp_channel *channel_new(struct a2dp_server *server, struct btd_device *device, GIOChannel *io) { struct a2dp_channel *chan; chan = g_new0(struct a2dp_channel, 1); chan->server = server; chan->device = device; chan->seps = queue_new(); chan->state_id = avdtp_add_state_cb(device, avdtp_state_cb, chan); if (!queue_push_tail(server->channels, chan)) { g_free(chan); return NULL; } if (!io) return chan; chan->io = g_io_channel_ref(io); chan->io_id = g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL, (GIOFunc) disconnect_cb, chan); return chan; } static bool match_by_device(const void *data, const void *user_data) { const struct a2dp_channel *chan = data; const struct btd_device *device = user_data; return chan->device == device; } struct avdtp *a2dp_avdtp_get(struct btd_device *device) { struct a2dp_server *server; struct a2dp_channel *chan; server = find_server(servers, device_get_adapter(device)); if (server == NULL) return NULL; chan = queue_find(server->channels, match_by_device, device); if (!chan) { chan = channel_new(server, device, NULL); if (!chan) return NULL; } if (chan->session) return avdtp_ref(chan->session); /* Check if there is any SEP available */ if (!queue_isempty(server->seps)) goto found; DBG("Unable to find any available SEP"); return NULL; found: chan->session = avdtp_new(chan->io, device, server->seps); if (!chan->session) { channel_remove(chan); return NULL; } return avdtp_ref(chan->session); } static void connect_cb(GIOChannel *io, GError *err, gpointer user_data) { struct a2dp_channel *chan = user_data; if (err) { error("%s", err->message); goto fail; } if (!chan->session) { chan->session = avdtp_new(chan->io, chan->device, chan->server->seps); if (!chan->session) { error("Unable to create AVDTP session"); goto fail; } } g_io_channel_unref(chan->io); chan->io = NULL; g_source_remove(chan->io_id); chan->io_id = 0; return; fail: channel_remove(chan); } static void auth_cb(DBusError *derr, void *user_data) { struct a2dp_channel *chan = user_data; GError *err = NULL; chan->auth_id = 0; if (derr && dbus_error_is_set(derr)) { error("Access denied: %s", derr->message); goto fail; } if (!bt_io_accept(chan->io, connect_cb, chan, NULL, &err)) { error("bt_io_accept: %s", err->message); g_error_free(err); goto fail; } return; fail: channel_remove(chan); } static void transport_cb(GIOChannel *io, GError *err, gpointer user_data) { struct a2dp_setup *setup = user_data; uint16_t omtu, imtu; if (!g_slist_find(setups, setup)) { warn("bt_io_accept: setup %p no longer valid", setup); g_io_channel_shutdown(io, TRUE, NULL); return; } if (err) { error("%s", err->message); goto drop; } bt_io_get(io, &err, BT_IO_OPT_OMTU, &omtu, BT_IO_OPT_IMTU, &imtu, BT_IO_OPT_INVALID); if (err) { error("%s", err->message); g_error_free(err); goto drop; } if (!avdtp_stream_set_transport(setup->stream, g_io_channel_unix_get_fd(io), imtu, omtu)) goto drop; g_io_channel_set_close_on_unref(io, FALSE); g_io_channel_unref(setup->io); setup->io = NULL; setup_unref(setup); return; drop: setup_unref(setup); g_io_channel_shutdown(io, TRUE, NULL); } static void confirm_cb(GIOChannel *io, gpointer data) { struct a2dp_server *server = data; struct a2dp_channel *chan; char address[18]; bdaddr_t src, dst; GError *err = NULL; struct btd_device *device; bt_io_get(io, &err, BT_IO_OPT_SOURCE_BDADDR, &src, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_DEST, address, BT_IO_OPT_INVALID); if (err) { error("%s", err->message); g_error_free(err); goto drop; } DBG("AVDTP: incoming connect from %s", address); device = btd_adapter_find_device(adapter_find(&src), &dst, BDADDR_BREDR); if (!device) goto drop; chan = queue_find(server->channels, match_by_device, device); if (chan) { struct a2dp_setup *setup; setup = find_setup_by_session(chan->session); if (!setup || !setup->stream) goto drop; if (setup->io || avdtp_stream_get_transport(setup->stream, NULL, NULL, NULL, NULL)) { error("transport channel already exists"); goto drop; } if (!bt_io_accept(io, transport_cb, setup, NULL, &err)) { error("bt_io_accept: %s", err->message); g_error_free(err); goto drop; } /* * Reference the channel so it can be shutdown properly * stopping bt_io_accept from calling the callback with invalid * setup pointer. */ setup->io = g_io_channel_ref(io); return; } chan = channel_new(server, device, io); if (!chan) goto drop; chan->auth_id = btd_request_authorization(&src, &dst, ADVANCED_AUDIO_UUID, auth_cb, chan); if (chan->auth_id == 0 && !chan->session) channel_remove(chan); return; drop: g_io_channel_shutdown(io, TRUE, NULL); } static bool a2dp_server_listen(struct a2dp_server *server) { GError *err = NULL; BtIOMode mode; if (server->io) return true; mode = btd_opts.avdtp.session_mode; server->io = bt_io_listen(NULL, confirm_cb, server, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, btd_adapter_get_address(server->adapter), BT_IO_OPT_PSM, AVDTP_PSM, BT_IO_OPT_MODE, mode, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_CENTRAL, true, /* Set Input MTU to 0 for auto-tune attempt */ BT_IO_OPT_IMTU, 0, BT_IO_OPT_INVALID); if (server->io) return true; error("%s", err->message); g_error_free(err); return false; } static struct a2dp_server *a2dp_server_register(struct btd_adapter *adapter) { struct a2dp_server *server; server = g_new0(struct a2dp_server, 1); server->adapter = btd_adapter_ref(adapter); server->seps = queue_new(); server->channels = queue_new(); servers = g_slist_append(servers, server); return server; } static void a2dp_unregister_sep(struct a2dp_sep *sep) { struct a2dp_server *server = sep->server; if (sep->destroy) { sep->destroy(sep->user_data); sep->endpoint = NULL; } avdtp_unregister_sep(server->seps, &server->seid_pool, sep->lsep); queue_destroy(sep->streams, a2dp_stream_free); g_free(sep); if (!queue_isempty(server->seps)) return; if (server->io) { g_io_channel_shutdown(server->io, TRUE, NULL); g_io_channel_unref(server->io); server->io = NULL; } } static void a2dp_server_unregister(struct a2dp_server *server) { servers = g_slist_remove(servers, server); queue_destroy(server->channels, channel_free); queue_destroy(server->seps, NULL); if (server->io) { g_io_channel_shutdown(server->io, TRUE, NULL); g_io_channel_unref(server->io); } btd_adapter_unref(server->adapter); g_free(server); } struct a2dp_sep *a2dp_add_sep(struct btd_adapter *adapter, uint8_t type, uint8_t codec, gboolean delay_reporting, struct a2dp_endpoint *endpoint, void *user_data, GDestroyNotify destroy, int *err) { struct a2dp_server *server; struct a2dp_sep *sep; GSList **l; uint32_t *record_id; sdp_record_t *record; server = find_server(servers, adapter); if (server == NULL) { if (err) *err = -EPROTONOSUPPORT; return NULL; } if (type == AVDTP_SEP_TYPE_SINK && !server->sink_enabled) { if (err) *err = -EPROTONOSUPPORT; return NULL; } if (type == AVDTP_SEP_TYPE_SOURCE && !server->source_enabled) { if (err) *err = -EPROTONOSUPPORT; return NULL; } sep = g_new0(struct a2dp_sep, 1); sep->lsep = avdtp_register_sep(server->seps, &server->seid_pool, type, AVDTP_MEDIA_TYPE_AUDIO, codec, delay_reporting, &endpoint_ind, &cfm, sep); if (sep->lsep == NULL) { g_free(sep); if (err) *err = -EINVAL; return NULL; } sep->server = server; sep->endpoint = endpoint; sep->codec = codec; sep->type = type; sep->delay_reporting = delay_reporting; if (type == AVDTP_SEP_TYPE_SOURCE) { l = &server->sources; record_id = &server->source_record_id; } else { l = &server->sinks; record_id = &server->sink_record_id; } if (*record_id != 0) goto add; record = a2dp_record(type); if (!record) { error("Unable to allocate new service record"); a2dp_unregister_sep(sep); if (err) *err = -EINVAL; return NULL; } if (adapter_service_add(server->adapter, record) < 0) { error("Unable to register A2DP service record"); sdp_record_free(record); a2dp_unregister_sep(sep); if (err) *err = -EINVAL; return NULL; } if (!a2dp_server_listen(server)) { sdp_record_free(record); a2dp_unregister_sep(sep); if (err) *err = -EINVAL; return NULL; } *record_id = record->handle; add: *l = g_slist_append(*l, sep); sep->user_data = user_data; sep->destroy = destroy; if (err) *err = 0; return sep; } static bool match_locked(const void *data, const void *user_data) { const struct a2dp_stream *stream = data; return stream->locked; } void a2dp_remove_sep(struct a2dp_sep *sep) { struct a2dp_server *server = sep->server; if (sep->type == AVDTP_SEP_TYPE_SOURCE) { if (g_slist_find(server->sources, sep) == NULL) return; server->sources = g_slist_remove(server->sources, sep); if (server->sources == NULL && server->source_record_id) { adapter_service_remove(server->adapter, server->source_record_id); server->source_record_id = 0; } } else { if (g_slist_find(server->sinks, sep) == NULL) return; server->sinks = g_slist_remove(server->sinks, sep); if (server->sinks == NULL && server->sink_record_id) { adapter_service_remove(server->adapter, server->sink_record_id); server->sink_record_id = 0; } } if (queue_find(sep->streams, match_locked, NULL)) return; a2dp_unregister_sep(sep); } static void select_cb(struct a2dp_setup *setup, void *ret, int size) { struct avdtp_service_capability *service; struct avdtp_media_codec_capability *codec; int err; if (setup->err) goto done; if (size >= 0) { caps_add_codec(&setup->caps, setup->sep->codec, ret, size); goto done; } setup->sep = queue_pop_head(setup->eps); if (!setup->sep) { error("Unable to select a valid configuration"); goto done; } setup->rsep = find_remote_sep(setup->chan, setup->sep); service = avdtp_get_codec(setup->rsep->sep); codec = (struct avdtp_media_codec_capability *) service->data; err = setup->sep->endpoint->select_configuration(setup->sep, codec->data, service->length - sizeof(*codec), setup, select_cb, setup->sep->user_data); if (err == 0) return; done: finalize_select(setup); setup_unref(setup); } static struct queue *a2dp_find_eps(struct avdtp *session, GSList *list, const char *sender) { struct a2dp_channel *chan = find_channel(session); struct queue *seps = NULL; for (; list; list = list->next) { struct a2dp_sep *sep = list->data; struct avdtp_remote_sep *rsep; /* Use sender's endpoint if available */ if (sender) { const char *name; if (sep->endpoint == NULL) continue; name = sep->endpoint->get_name(sep, sep->user_data); if (g_strcmp0(sender, name) != 0) continue; } rsep = avdtp_find_remote_sep(session, sep->lsep); if (!rsep) continue; if (!seps) seps = queue_new(); /* Prepend last used so it is preferred over others */ if (chan->last_used && (chan->last_used->lsep == sep && chan->last_used->rsep->sep == rsep)) queue_push_head(seps, sep); else queue_push_tail(seps, sep); } return seps; } static struct queue *a2dp_select_eps(struct avdtp *session, uint8_t type, const char *sender) { struct a2dp_server *server; struct queue *seps; GSList *l; server = find_server(servers, avdtp_get_adapter(session)); if (!server) return NULL; l = type == AVDTP_SEP_TYPE_SINK ? server->sources : server->sinks; /* Check sender's seps first */ seps = a2dp_find_eps(session, l, sender); if (seps != NULL) return seps; return a2dp_find_eps(session, l, NULL); } static void foreach_register_remote_sep(void *data, void *user_data) { register_remote_sep(data, user_data); } static void discover_cb(struct avdtp *session, GSList *seps, struct avdtp_error *err, void *user_data) { struct a2dp_setup *setup = user_data; uint16_t version = avdtp_get_version(session); DBG("version 0x%04x err %p", version, err); setup->seps = seps; if (err) setup_error_set(setup, err); if (!err) { g_slist_foreach(seps, foreach_register_remote_sep, setup->chan); /* Only store version has been initialized as features like * Delay Reporting may not be queried if the version in * unknown. */ if (version) store_remote_seps(setup->chan); } finalize_discover(setup); } unsigned int a2dp_discover(struct avdtp *session, a2dp_discover_cb_t cb, void *user_data) { struct a2dp_setup *setup; struct a2dp_setup_cb *cb_data; setup = a2dp_setup_get(session); if (!setup) return 0; /* Don't add cb since avdtp_discover can end up disconnecting the * session causing the cb to be freed. */ cb_data = setup_cb_new(setup); cb_data->discover_cb = cb; cb_data->user_data = user_data; if (avdtp_discover(session, discover_cb, setup) == 0) { setup->cb = g_slist_append(setup->cb, cb_data); return cb_data->id; } setup_cb_free(cb_data); return 0; } unsigned int a2dp_select_capabilities(struct avdtp *session, uint8_t type, const char *sender, a2dp_select_cb_t cb, void *user_data) { struct a2dp_setup *setup; struct a2dp_setup_cb *cb_data; struct queue *eps; struct avdtp_service_capability *service; struct avdtp_media_codec_capability *codec; int err; eps = a2dp_select_eps(session, type, sender); if (!eps) { error("Unable to select SEP"); return 0; } setup = a2dp_setup_get(session); if (!setup) return 0; cb_data = setup_cb_add(setup); cb_data->select_cb = cb; cb_data->user_data = user_data; setup->eps = eps; setup->sep = queue_pop_head(eps); setup->rsep = find_remote_sep(setup->chan, setup->sep); if (setup->rsep == NULL) { error("Could not find remote sep"); goto fail; } service = avdtp_get_codec(setup->rsep->sep); codec = (struct avdtp_media_codec_capability *) service->data; err = setup->sep->endpoint->select_configuration(setup->sep, codec->data, service->length - sizeof(*codec), setup_ref(setup), select_cb, setup->sep->user_data); if (err == 0) return cb_data->id; setup_unref(setup); fail: setup_cb_free(cb_data); return 0; } unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep, a2dp_config_cb_t cb, GSList *caps, void *user_data) { struct a2dp_setup_cb *cb_data; GSList *l; struct a2dp_server *server; struct a2dp_setup *setup; struct a2dp_stream *stream; struct avdtp_service_capability *cap; struct avdtp_media_codec_capability *codec_cap = NULL; int posix_err; server = find_server(servers, avdtp_get_adapter(session)); if (!server) return 0; for (l = caps; l != NULL; l = l->next) { cap = l->data; if (cap->category != AVDTP_MEDIA_CODEC) continue; codec_cap = (void *) cap->data; break; } if (!codec_cap) return 0; if (sep->codec != codec_cap->media_codec_type) return 0; DBG("a2dp_config: selected SEP %p", sep->lsep); setup = a2dp_setup_get(session); if (!setup) return 0; cb_data = setup_cb_add(setup); cb_data->config_cb = cb; cb_data->user_data = user_data; stream = queue_find(sep->streams, match_stream_session, session); setup->sep = sep; setup->stream = stream ? stream->stream : NULL; /* Copy given caps if they are different than current caps */ if (setup->caps != caps) { g_slist_free_full(setup->caps, g_free); setup->caps = g_slist_copy(caps); } switch (avdtp_stream_get_state(setup->stream)) { case AVDTP_STATE_IDLE: if (sep->type == AVDTP_SEP_TYPE_SOURCE) l = server->sources; else l = server->sinks; for (; l != NULL; l = l->next) { struct a2dp_sep *tmp = l->data; stream = queue_find(tmp->streams, match_stream_session, session); if (!stream) continue; if (avdtp_has_stream(session, stream->stream)) break; } if (l != NULL) { if (stream->locked) goto failed; setup->reconfigure = TRUE; if (avdtp_close(session, stream->stream, FALSE) < 0) { error("avdtp_close failed"); goto failed; } break; } setup->rsep = find_remote_sep(setup->chan, sep); if (setup->rsep == NULL) { error("No matching ACP and INT SEPs found"); goto failed; } posix_err = avdtp_set_configuration(session, setup->rsep->sep, sep->lsep, caps, &setup->stream); if (posix_err < 0) { error("avdtp_set_configuration: %s", strerror(-posix_err)); goto failed; } break; case AVDTP_STATE_OPEN: case AVDTP_STATE_STREAMING: if (avdtp_stream_has_capabilities(setup->stream, caps)) { DBG("Configuration match: resuming"); cb_data->source_id = g_idle_add(finalize_config, setup); } else if (!setup->reconfigure) { setup->reconfigure = TRUE; if (avdtp_close(session, setup->stream, FALSE) < 0) { error("avdtp_close failed"); goto failed; } } break; case AVDTP_STATE_CONFIGURED: case AVDTP_STATE_CLOSING: case AVDTP_STATE_ABORTING: default: error("SEP in bad state for requesting a new stream"); goto failed; } return cb_data->id; failed: setup_cb_free(cb_data); return 0; } unsigned int a2dp_resume(struct avdtp *session, struct a2dp_sep *sep, a2dp_stream_cb_t cb, void *user_data) { struct a2dp_setup_cb *cb_data; struct a2dp_setup *setup; struct a2dp_stream *stream; setup = a2dp_setup_get(session); if (!setup) return 0; cb_data = setup_cb_add(setup); cb_data->resume_cb = cb; cb_data->user_data = user_data; if (setup->reconfigure) goto failed; stream = queue_find(sep->streams, match_stream_session, session); setup->sep = sep; setup->stream = stream ? stream->stream : NULL; switch (avdtp_stream_get_state(setup->stream)) { case AVDTP_STATE_IDLE: goto failed; case AVDTP_STATE_CONFIGURED: setup->start = TRUE; break; case AVDTP_STATE_OPEN: if (avdtp_start(session, setup->stream) < 0) { error("avdtp_start failed"); goto failed; } stream->starting = TRUE; break; case AVDTP_STATE_STREAMING: if (!stream->suspending && stream->suspend_timer) { timeout_remove(stream->suspend_timer); stream->suspend_timer = 0; } if (stream->suspending) setup->start = TRUE; else cb_data->source_id = g_idle_add(finalize_resume, setup); break; case AVDTP_STATE_CLOSING: case AVDTP_STATE_ABORTING: default: error("SEP in bad state for resume"); goto failed; } return cb_data->id; failed: setup_cb_free(cb_data); return 0; } unsigned int a2dp_suspend(struct avdtp *session, struct a2dp_sep *sep, a2dp_stream_cb_t cb, void *user_data) { struct a2dp_setup_cb *cb_data; struct a2dp_setup *setup; struct a2dp_stream *stream; setup = a2dp_setup_get(session); if (!setup) return 0; cb_data = setup_cb_add(setup); cb_data->suspend_cb = cb; cb_data->user_data = user_data; if (setup->reconfigure) goto failed; stream = queue_find(sep->streams, match_stream_session, session); setup->sep = sep; setup->stream = stream ? stream->stream : NULL; switch (avdtp_stream_get_state(setup->stream)) { case AVDTP_STATE_IDLE: error("a2dp_suspend: no stream to suspend"); goto failed; break; case AVDTP_STATE_OPEN: cb_data->source_id = g_idle_add(finalize_suspend, setup); break; case AVDTP_STATE_STREAMING: if (avdtp_suspend(session, setup->stream) < 0) { error("avdtp_suspend failed"); goto failed; } stream->suspending = TRUE; break; case AVDTP_STATE_CONFIGURED: case AVDTP_STATE_CLOSING: case AVDTP_STATE_ABORTING: default: error("SEP in bad state for suspend"); goto failed; } return cb_data->id; failed: setup_cb_free(cb_data); return 0; } gboolean a2dp_cancel(unsigned int id) { GSList *ls; for (ls = setups; ls != NULL; ls = g_slist_next(ls)) { struct a2dp_setup *setup = ls->data; GSList *l; for (l = setup->cb; l != NULL; l = g_slist_next(l)) { struct a2dp_setup_cb *cb = l->data; if (cb->id != id) continue; setup_ref(setup); setup_cb_free(cb); if (!setup->cb) { DBG("aborting setup %p", setup); if (!avdtp_abort(setup->session, setup->stream)) return TRUE; } setup_unref(setup); return TRUE; } } return FALSE; } gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session) { struct a2dp_stream *stream; stream = queue_find(sep->streams, match_stream_session, session); if (!stream || stream->locked) return FALSE; DBG("stream %p locked", sep->lsep); stream->locked = TRUE; return TRUE; } gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session) { struct a2dp_server *server = sep->server; struct a2dp_stream *stream; avdtp_state_t state; GSList *l; stream = queue_find(sep->streams, match_stream_session, session); if (!stream) return FALSE; state = avdtp_stream_get_state(stream->stream); stream->locked = FALSE; DBG("stream %p unlocked", stream); if (sep->type == AVDTP_SEP_TYPE_SOURCE) l = server->sources; else l = server->sinks; /* Unregister sep if it was removed */ if (g_slist_find(l, sep) == NULL) { a2dp_unregister_sep(sep); return TRUE; } if (!stream->stream || state == AVDTP_STATE_IDLE) return TRUE; switch (state) { case AVDTP_STATE_OPEN: /* Set timer here */ break; case AVDTP_STATE_STREAMING: if (avdtp_suspend(session, stream->stream) == 0) stream->suspending = TRUE; break; case AVDTP_STATE_IDLE: case AVDTP_STATE_CONFIGURED: case AVDTP_STATE_CLOSING: case AVDTP_STATE_ABORTING: default: break; } return TRUE; } struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep, struct avdtp *session) { struct a2dp_stream *stream; stream = queue_find(sep->streams, match_stream_session, session); if (stream) return stream->stream; return NULL; } struct btd_device *a2dp_setup_get_device(struct a2dp_setup *setup) { if (setup->session == NULL) return NULL; return avdtp_get_device(setup->session); } const char *a2dp_setup_remote_path(struct a2dp_setup *setup) { if (setup->rsep) { return setup->rsep->path; } return NULL; } static int a2dp_source_probe(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); DBG("path %s", device_get_path(dev)); source_init(service); return 0; } static void a2dp_source_remove(struct btd_service *service) { source_unregister(service); } static int a2dp_sink_probe(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); DBG("path %s", device_get_path(dev)); return sink_init(service); } static void a2dp_sink_remove(struct btd_service *service) { sink_unregister(service); } static int a2dp_source_connect(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); struct btd_adapter *adapter = device_get_adapter(dev); struct a2dp_server *server; const char *path = device_get_path(dev); DBG("path %s", path); server = find_server(servers, adapter); if (!server || !server->sink_enabled) { DBG("Unexpected error: cannot find server"); return -EPROTONOSUPPORT; } /* Return protocol not available if no record/endpoint exists */ if (server->sink_record_id == 0) return -ENOPROTOOPT; return source_connect(service); } static int a2dp_source_disconnect(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); const char *path = device_get_path(dev); DBG("path %s", path); return source_disconnect(service); } static int a2dp_sink_connect(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); struct btd_adapter *adapter = device_get_adapter(dev); struct a2dp_server *server; const char *path = device_get_path(dev); DBG("path %s", path); server = find_server(servers, adapter); if (!server || !server->source_enabled) { DBG("Unexpected error: cannot find server"); return -EPROTONOSUPPORT; } /* Return protocol not available if no record/endpoint exists */ if (server->source_record_id == 0) return -ENOPROTOOPT; return sink_connect(service); } static int a2dp_sink_disconnect(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); const char *path = device_get_path(dev); DBG("path %s", path); return sink_disconnect(service); } static int a2dp_source_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { struct a2dp_server *server; DBG("path %s", adapter_get_path(adapter)); server = find_server(servers, adapter); if (server != NULL) goto done; server = a2dp_server_register(adapter); if (server == NULL) return -EPROTONOSUPPORT; done: server->source_enabled = TRUE; return 0; } static void a2dp_source_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { struct a2dp_server *server; DBG("path %s", adapter_get_path(adapter)); server = find_server(servers, adapter); if (!server) return; g_slist_free_full(server->sources, (GDestroyNotify) a2dp_unregister_sep); if (server->source_record_id) { adapter_service_remove(server->adapter, server->source_record_id); server->source_record_id = 0; } if (server->sink_record_id) return; a2dp_server_unregister(server); } static int a2dp_sink_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { struct a2dp_server *server; DBG("path %s", adapter_get_path(adapter)); server = find_server(servers, adapter); if (server != NULL) goto done; server = a2dp_server_register(adapter); if (server == NULL) return -EPROTONOSUPPORT; done: server->sink_enabled = TRUE; return 0; } static void a2dp_sink_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { struct a2dp_server *server; DBG("path %s", adapter_get_path(adapter)); server = find_server(servers, adapter); if (!server) return; g_slist_free_full(server->sinks, (GDestroyNotify) a2dp_unregister_sep); if (server->sink_record_id) { adapter_service_remove(server->adapter, server->sink_record_id); server->sink_record_id = 0; } if (server->source_record_id) return; a2dp_server_unregister(server); } static int media_server_probe(struct btd_adapter *adapter) { DBG("path %s", adapter_get_path(adapter)); return media_register(adapter); } static void media_server_remove(struct btd_adapter *adapter) { DBG("path %s", adapter_get_path(adapter)); media_unregister(adapter); } static struct btd_profile a2dp_source_profile = { .name = "a2dp-source", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .bearer = BTD_PROFILE_BEARER_BREDR, .remote_uuid = A2DP_SOURCE_UUID, .device_probe = a2dp_source_probe, .device_remove = a2dp_source_remove, .auto_connect = true, .connect = a2dp_source_connect, .disconnect = a2dp_source_disconnect, .adapter_probe = a2dp_sink_server_probe, .adapter_remove = a2dp_sink_server_remove, /* Connect source after sink, to prefer sink when conflicting */ .after_services = BTD_PROFILE_UUID_CB(NULL, A2DP_SINK_UUID), }; static struct btd_profile a2dp_sink_profile = { .name = "a2dp-sink", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .bearer = BTD_PROFILE_BEARER_BREDR, .remote_uuid = A2DP_SINK_UUID, .device_probe = a2dp_sink_probe, .device_remove = a2dp_sink_remove, .auto_connect = true, .connect = a2dp_sink_connect, .disconnect = a2dp_sink_disconnect, .adapter_probe = a2dp_source_server_probe, .adapter_remove = a2dp_source_server_remove, }; static struct btd_adapter_driver media_driver = { .name = "media", .probe = media_server_probe, .remove = media_server_remove, }; static int a2dp_init(void) { int err; btd_register_adapter_driver(&media_driver); err = btd_profile_register(&a2dp_source_profile); if (err) return err; err = btd_profile_register(&a2dp_sink_profile); if (err) { btd_profile_unregister(&a2dp_source_profile); return err; } return 0; } static void a2dp_exit(void) { btd_unregister_adapter_driver(&media_driver); btd_profile_unregister(&a2dp_source_profile); btd_profile_unregister(&a2dp_sink_profile); } BLUETOOTH_PLUGIN_DEFINE(a2dp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, a2dp_init, a2dp_exit) bluez-5.87/profiles/audio/PaxHeaders/bass.c0000644000000000000000000000005015221752714015701 xustar0020 atime=1783093701 20 ctime=1783094106 bluez-5.87/profiles/audio/bass.c0000644000000000000000000016521615221752714015375 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright 2023-2025 NXP * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "bluetooth/iso.h" #include "bluetooth/mgmt.h" #include "src/dbus-common.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/shared/gatt-server.h" #include "src/adapter.h" #include "src/shared/bass.h" #include "src/shared/bap.h" #include "src/shared/ad.h" #include "src/shared/io.h" #include "btio/btio.h" #include "src/plugin.h" #include "src/gatt-database.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #include "src/error.h" #define BASS_UUID_STR "0000184f-0000-1000-8000-00805f9b34fb" #define BCAAS_UUID_STR "00001852-0000-1000-8000-00805f9b34fb" #define MEDIA_ASSISTANT_INTERFACE "org.bluez.MediaAssistant1" enum assistant_state { ASSISTANT_STATE_IDLE, /* Assistant object was created for * the stream */ ASSISTANT_STATE_PENDING, /* Assistant object was pushed */ ASSISTANT_STATE_REQUESTING, /* Remote device requires * Broadcast_Code */ ASSISTANT_STATE_ACTIVE, /* Remote device started receiving * stream */ ASSISTANT_STATE_LOCAL, /* Assistant object was created for * local stream */ }; static const char *const str_state[] = { "ASSISTANT_STATE_IDLE", "ASSISTANT_STATE_PENDING", "ASSISTANT_STATE_REQUESTING", "ASSISTANT_STATE_ACTIVE", "ASSISTANT_STATE_LOCAL", }; struct bass_data { struct btd_adapter *adapter; struct btd_device *device; struct btd_service *service; struct bt_bap *bap; struct bt_bass *bass; struct bt_bap_stream *stream; unsigned int src_id; unsigned int cp_id; unsigned int bis_id; unsigned int state_id; }; struct bass_assistant { struct btd_adapter *adapter; /* Broadcast source device */ struct btd_device *device; /* Broadcast source device */ struct bass_data *data; /* BASS session with peer device */ uint8_t sgrp; uint8_t sid; uint8_t bis; uint32_t bid; struct bt_bap_qos qos; struct iovec *meta; struct iovec *caps; enum assistant_state state; uint8_t src_id; struct queue *srcs; /* Per-device source tracking */ char *path; }; struct bass_src { struct bt_bass *bass; uint8_t src_id; }; struct bass_delegator { struct btd_device *device; /* Broadcast source device */ struct btd_service *service; struct bt_bcast_src *src; struct bt_bap *bap; unsigned int state_id; unsigned int bcode_id; uint8_t sid; uint8_t *bcode; unsigned int timeout; struct queue *bcode_reqs; struct queue *setups; unsigned int io_id; GIOChannel *io; }; struct bass_setup { struct bass_delegator *dg; char *path; struct bt_bap_stream *stream; uint8_t bis; struct bt_bap_qos qos; struct iovec *meta; struct iovec *config; struct bt_bap_pac *lpac; }; struct bass_bcode_req { struct bass_setup *setup; bt_bap_bcode_reply_t cb; void *user_data; }; static struct queue *sessions; static struct queue *assistants; static struct queue *delegators; static const char *state2str(enum assistant_state state); static struct bass_data *bass_data_new(struct btd_adapter *adapter, struct btd_device *device); static void bass_data_add(struct bass_data *data); static void bass_data_remove(struct bass_data *data); static void bis_probe(uint8_t sid, uint8_t bis, uint8_t sgrp, struct iovec *caps, struct iovec *meta, struct bt_bap_qos *qos, void *user_data); static bool match_src_data(const void *data, const void *match_data) { const struct bass_src *src = data; const struct bt_bass *bass = match_data; return src->bass == bass; } static struct bass_src *assistant_add_src(struct bass_assistant *assistant, struct bt_bass *bass, uint8_t id); static void bis_remove(struct bt_bap *bap, void *user_data); static void bass_debug(const char *str, void *user_data) { DBG_IDX(0xffff, "%s", str); } static gboolean req_timeout(gpointer user_data) { struct bass_delegator *dg = user_data; struct bass_bcode_req *req; DBG("delegator %p", dg); dg->timeout = 0; while ((req = queue_pop_head(dg->bcode_reqs))) { if (req->cb) req->cb(req->user_data, -ETIMEDOUT); free(req); } return FALSE; } static bool delegator_match_bap(const void *data, const void *match_data) { const struct bass_delegator *dg = data; const struct bt_bap *bap = match_data; return dg->bap == bap; } static void setup_set_bcode(uint8_t *bcode, struct bass_setup *setup, bt_bap_bcode_reply_t cb, void *user_data) { struct bt_bap_qos *qos = bt_bap_stream_get_qos(setup->stream); /* Allocate Broadcast Code inside setup QoS */ util_iov_free(setup->qos.bcast.bcode, 1); setup->qos.bcast.bcode = util_iov_new(bcode, BT_BASS_BCAST_CODE_SIZE); /* Refresh stream bcode */ qos->bcast.bcode = setup->qos.bcast.bcode; if (cb) cb(user_data, 0); } static bool match_setup_stream(const void *data, const void *user_data) { const struct bass_setup *setup = data; const struct bt_bap_stream *stream = user_data; return setup->stream == stream; } static void bass_req_bcode(struct bt_bap_stream *stream, bt_bap_bcode_reply_t reply, void *reply_data, void *user_data) { struct bt_bap *bap = bt_bap_stream_get_session(stream); struct bass_delegator *dg; struct bass_bcode_req *req; struct bass_setup *setup; dg = queue_find(delegators, delegator_match_bap, bap); if (!dg) { reply(reply_data, -EINVAL); return; } setup = queue_find(dg->setups, match_setup_stream, stream); if (!setup) { reply(reply_data, -EINVAL); return; } if (dg->bcode) { uint8_t empty_bcode[BT_BASS_BCAST_CODE_SIZE] = {0}; if (memcmp(dg->bcode, empty_bcode, BT_BASS_BCAST_CODE_SIZE)) { /* Broadcast Code has already been received before. */ setup_set_bcode(dg->bcode, setup, reply, reply_data); return; } error("dg %p has invalid bcode", dg); free(dg->bcode); dg->bcode = NULL; } /* Create a request for the Broadcast Code. The request * will be considered handled when the Broadcast Code is * received from a Broadcast Assistant. */ req = new0(struct bass_bcode_req, 1); if (!req) return; req->setup = setup; req->cb = reply; req->user_data = reply_data; queue_push_tail(dg->bcode_reqs, req); /* Mark the encryption status as "Broadcast Code Required" * in the Broadcast Receive State characteristic and notify * Broadcast Assistants. */ bt_bass_set_enc(dg->src, BT_BASS_BIG_ENC_STATE_BCODE_REQ); /* Add timeout for Broadcast Assistants to provide the Code. */ if (!dg->timeout) dg->timeout = g_timeout_add_seconds(10, req_timeout, dg); } static int stream_get_bis(struct bt_bap_stream *stream) { char *path = bt_bap_stream_get_user_data(stream); const char *strbis; int bis; strbis = strstr(path, "/bis"); if (!strbis) return 0; if (sscanf(strbis, "/bis%d", &bis) < 0) return 0; return bis; } static void append_stream(void *data, void *user_data) { struct bt_bap_stream *stream = data; struct sockaddr_iso_bc *addr = user_data; uint8_t bis = stream_get_bis(stream); DBG("%d", bis); addr->bc_bis[addr->bc_num_bis] = bis; addr->bc_num_bis++; } static bool link_io_unset(const void *data, const void *match_data) { struct bt_bap_stream *link = (struct bt_bap_stream *)data; return !bt_bap_stream_get_io(link); } static void connect_cb(GIOChannel *io, GError *err, void *user_data) { struct bass_setup *setup = user_data; struct bt_bap_stream *stream; struct queue *links; int fd; DBG(""); if (!setup || !setup->stream) return; stream = setup->stream; links = bt_bap_stream_io_get_links(stream); /* Set fds for the stream and all its links. */ if (bt_bap_stream_get_io(stream)) stream = queue_find(links, link_io_unset, NULL); fd = g_io_channel_unix_get_fd(io); if (bt_bap_stream_set_io(stream, fd)) { g_io_channel_set_close_on_unref(io, FALSE); } } static bool link_enabled(const void *data, const void *match_data) { struct bt_bap_stream *stream = (struct bt_bap_stream *)data; uint8_t state = bt_bap_stream_get_state(stream); return ((state == BT_BAP_STREAM_STATE_ENABLING) || bt_bap_stream_get_io(stream)); } static void setup_free(void *data) { struct bass_setup *setup = data; DBG("setup %p", setup); util_iov_free(setup->qos.bcast.bcode, 1); util_iov_free(setup->meta, 1); util_iov_free(setup->config, 1); free(setup->path); /* Clear bis index from the bis sync bitmask, if it * has been previously set. */ bt_bass_clear_bis_sync(setup->dg->src, setup->bis); free(setup); } static void delegator_free(struct bass_delegator *dg); static void delegator_disconnect(struct bass_delegator *dg) { struct btd_device *device = dg->device; struct btd_service *service = dg->service; struct btd_profile *profile = btd_service_get_profile(service); DBG("%p", dg); /* Remove delegator from the queue and free it before removing the * service profile. This prevents delegator_attach from finding a * stale entry when a new delegator is created for the same device, * and also avoids use-after-free since device_remove_profile may * trigger bap_detached -> delegator_detach which would otherwise * try to free the delegator again. */ queue_remove(delegators, dg); btd_service_set_user_data(service, NULL); delegator_free(dg); /* Disconnect service so BAP driver is cleanup properly and bt_bap is * detached from the device. */ btd_service_disconnect(service); /* Remove service since delegator has been freed at this point. * delegator_detach (triggered by bap_detached) will be a no-op * since the delegator has already been removed from the queue. */ device_remove_profile(device, profile); /* If the device is no longer consider connected it means no other * service was connected so it has no longer any use and can be safely * removed. */ if (!btd_device_is_connected(device)) { struct btd_adapter *adapter; adapter = device_get_adapter(device); btd_adapter_remove_device(adapter, device); } } static bool match_bcode_setup(const void *data, const void *user_data) { const struct bass_bcode_req *req = data; const struct bass_setup *setup = user_data; return req->setup == setup; } static void setup_clear(struct bass_setup *setup, int bis) { struct bass_delegator *dg = setup->dg; struct bass_bcode_req *req; DBG("%p", setup); bt_bass_clear_bis_sync(dg->src, bis); setup->stream = NULL; if (!queue_remove(setup->dg->setups, setup)) /* Setup has already been removed from the queue (e.g. during * handle_mod_src_req PA_SYNC_NO_SYNC processing). Skip * disconnect check and free since the caller handles cleanup. */ return; /* Remove any pending bcode request associated with setup */ req = queue_remove_if(dg->bcode_reqs, match_bcode_setup, setup); if (req) { free(req); if (dg->timeout) { g_source_remove(dg->timeout); dg->timeout = 0; } } setup_free(setup); if (queue_isempty(dg->setups)) delegator_disconnect(dg); } static void bap_state_changed(struct bt_bap_stream *stream, uint8_t old_state, uint8_t new_state, void *user_data) { struct bass_delegator *dg = user_data; int bis; struct bt_bap *bap = bt_bap_stream_get_session(stream); struct sockaddr_iso_bc iso_bc_addr = {0}; struct queue *links; GError *gerr = NULL; struct bt_bap_qos *bap_qos = bt_bap_stream_get_qos(stream); struct bt_iso_qos qos; struct bass_setup *setup = queue_find(dg->setups, match_setup_stream, stream); if (setup == NULL) return; if (dg->bap != bap) return; bis = stream_get_bis(stream); DBG("stream %p: %s(%u) -> %s(%u)", stream, bt_bap_stream_statestr(old_state), old_state, bt_bap_stream_statestr(new_state), new_state); switch (new_state) { case BT_BAP_STREAM_STATE_ENABLING: links = bt_bap_stream_io_get_links(stream); if (bt_bap_stream_get_io(stream) || queue_find(links, link_enabled, NULL)) /* The first enabled link will create and set fds * for all links. * * If the stream io has already been set, the stream * will automatically be started once all state_changed * callbacks are notified. * * If there is any other linked stream that has already * been enabled, the stream fd will be set once it is * notified from kernel and the stream will be started. */ break; iso_bc_addr.bc_bdaddr_type = btd_device_get_bdaddr_type(dg->device); memcpy(&iso_bc_addr.bc_bdaddr, device_get_address(dg->device), sizeof(bdaddr_t)); append_stream(stream, &iso_bc_addr); queue_foreach(links, append_stream, &iso_bc_addr); bt_bap_qos_to_iso_qos(bap_qos, &qos); if (!bt_io_set(dg->io, &gerr, BT_IO_OPT_QOS, &qos, BT_IO_OPT_INVALID)) { error("bt_io_set: %s", gerr->message); g_error_free(gerr); break; } if (!bt_io_bcast_accept(dg->io, connect_cb, setup, NULL, &gerr, BT_IO_OPT_ISO_BC_NUM_BIS, iso_bc_addr.bc_num_bis, BT_IO_OPT_ISO_BC_BIS, iso_bc_addr.bc_bis, BT_IO_OPT_INVALID)) { error("bt_io_bcast_accept: %s", gerr->message); g_error_free(gerr); } break; case BT_BAP_STREAM_STATE_STREAMING: /* BAP stream was started. Mark BIS index as synced inside the * Broadcast Receive State characteristic and notify peers about * the update. */ bt_bass_set_bis_sync(dg->src, bis); break; case BT_BAP_STREAM_STATE_CONFIG: if (old_state == BT_BAP_STREAM_STATE_STREAMING) /* BAP stream was disabled. Clear BIS index from the * bitmask inside the Broadcast Receive State * characteristic and notify peers about the update. */ bt_bass_clear_bis_sync(dg->src, bis); break; case BT_BAP_STREAM_STATE_IDLE: setup_clear(setup, bis); break; } } static void bass_req_bcode_cb(void *user_data, int err) { struct bass_setup *setup = user_data; if (!err) { if (asprintf(&setup->path, "%s/bis%d", device_get_path(setup->dg->device), setup->bis) < 0) return; bt_bap_stream_set_user_data(setup->stream, setup->path); bt_bap_stream_config(setup->stream, &setup->qos, setup->config, NULL, NULL); bt_bap_stream_metadata(setup->stream, setup->meta, NULL, NULL); } } static void setup_configure_stream(struct bass_setup *setup) { uint8_t empty_bcode[BT_BASS_BCAST_CODE_SIZE] = {0}; setup->stream = bt_bap_stream_new(setup->dg->bap, setup->lpac, NULL, &setup->qos, setup->config); if (!setup->stream) return; /* Before configuring the stream, check whether it is encrypted. * If so, request the broadcast code from the client. */ if ((setup->qos.bcast.encryption) && (!memcmp(setup->qos.bcast.bcode->iov_base, empty_bcode, sizeof(empty_bcode)))) { bass_req_bcode(setup->stream, bass_req_bcode_cb, setup, NULL); return; } if (asprintf(&setup->path, "%s/bis%d", device_get_path(setup->dg->device), setup->bis) < 0) return; bt_bap_stream_set_user_data(setup->stream, setup->path); bt_bap_stream_config(setup->stream, &setup->qos, setup->config, NULL, NULL); bt_bap_stream_metadata(setup->stream, setup->meta, NULL, NULL); } static void stream_unlink(void *data, void *user_data) { struct bt_bap_stream *link = data; struct bt_bap_stream *stream = user_data; bt_bap_stream_io_unlink(link, stream); } static void bass_remove_bis(struct bass_setup *setup) { struct queue *links = bt_bap_stream_io_get_links(setup->stream); DBG("%p", setup); queue_foreach(links, stream_unlink, setup->stream); bt_bap_stream_release(setup->stream, NULL, NULL); } static void setup_disable_streaming(void *data, void *user_data) { struct bass_setup *setup = data; struct queue *links = bt_bap_stream_io_get_links(setup->stream); if (!setup->stream) return; if (bt_bap_stream_get_state(setup->stream) != BT_BAP_STREAM_STATE_STREAMING) return; queue_foreach(links, stream_unlink, setup->stream); bt_bap_stream_disable(setup->stream, false, NULL, NULL); } static void bass_add_bis(struct bass_setup *setup) { DBG("%p", setup); queue_foreach(setup->dg->setups, setup_disable_streaming, NULL); setup_configure_stream(setup); } static void bis_handler(uint8_t sid, uint8_t bis, uint8_t sgrp, struct iovec *caps, struct iovec *meta, struct bt_bap_qos *qos, void *user_data) { struct bass_delegator *dg = user_data; struct bt_bap_pac *lpac; struct bass_setup *setup; /* Check if this stream caps match any local PAC */ bt_bap_verify_bis(dg->bap, bis, caps, &lpac); if (!lpac) return; setup = new0(struct bass_setup, 1); if (!setup) return; setup->dg = dg; setup->bis = bis; setup->lpac = lpac; setup->qos = *qos; setup->qos.bcast.bcode = util_iov_dup(qos->bcast.bcode, 1); setup->meta = util_iov_dup(meta, 1); setup->config = util_iov_dup(caps, 1); queue_push_tail(setup->dg->setups, setup); /* Only handle streams required by the Broadcast Assistant. */ if (!bt_bass_check_bis(dg->src, bis)) return; setup_configure_stream(setup); } static gboolean big_info_cb(GIOChannel *io, GIOCondition cond, gpointer user_data) { struct bass_delegator *dg = user_data; GError *err = NULL; struct bt_iso_base base; struct bt_iso_qos qos; struct iovec iov; struct bt_bap_qos bap_qos = {0}; uint8_t sid; dg->io_id = 0; bt_io_get(io, &err, BT_IO_OPT_BASE, &base, BT_IO_OPT_QOS, &qos, BT_IO_OPT_ISO_BC_SID, &sid, BT_IO_OPT_INVALID); if (err) { error("%s", err->message); g_error_free(err); return FALSE; } iov.iov_base = base.base; iov.iov_len = base.base_len; /* Create BAP QoS structure */ bt_bap_iso_qos_to_bap_qos(&qos, &bap_qos); bt_bap_parse_base(sid, &iov, &bap_qos, bass_debug, bis_handler, dg); util_iov_free(bap_qos.bcast.bcode, 1); return FALSE; } static void confirm_cb(GIOChannel *io, void *user_data) { struct bass_delegator *dg = user_data; DBG(""); /* Close the listen io */ g_io_channel_shutdown(dg->io, TRUE, NULL); g_io_channel_unref(dg->io); g_io_channel_ref(io); dg->io = io; /* Update Broadcast Receive State characteristic value and notify * peers. */ if (bt_bass_set_pa_sync(dg->src, BT_BASS_SYNCHRONIZED_TO_PA)) DBG("Failed to update Broadcast Receive State characteristic"); /* Register BAP stream state changed callback. */ dg->state_id = bt_bap_state_register(dg->bap, bap_state_changed, NULL, dg, NULL); /* Register callback to handle Broadcast Code requests from * upper layers. */ dg->bcode_id = bt_bap_bcode_cb_register(dg->bap, bass_req_bcode, NULL, NULL); dg->io_id = g_io_add_watch(io, G_IO_OUT, big_info_cb, dg); } static void src_ad_search_bid(void *data, void *user_data) { struct bt_ad_service_data *sd = data; struct bass_assistant *assistant = user_data; struct iovec iov; if (sd->uuid.type != BT_UUID16 || sd->uuid.value.u16 != BCAA_SERVICE) return; iov.iov_base = sd->data; iov.iov_len = sd->len; util_iov_pull_le24(&iov, &assistant->bid); } static struct bass_assistant * assistant_new(struct btd_adapter *adapter, struct btd_device *device, struct bass_data *data, uint8_t sgrp, uint8_t sid, uint8_t bis, struct bt_bap_qos *qos, struct iovec *meta, struct iovec *caps) { struct bass_assistant *assistant; char src_addr[18]; assistant = new0(struct bass_assistant, 1); if (!assistant) return NULL; DBG("assistant %p", assistant); assistant->adapter = adapter; assistant->device = device; assistant->data = data; assistant->sgrp = sgrp; assistant->sid = sid; assistant->bis = bis; assistant->qos = *qos; /* Create an internal copy for bcode */ assistant->qos.bcast.bcode = util_iov_dup(qos->bcast.bcode, 1); assistant->meta = util_iov_dup(meta, 1); assistant->caps = util_iov_dup(caps, 1); if (device) { btd_device_foreach_service_data(device, src_ad_search_bid, assistant); ba2str(device_get_address(device), src_addr); assistant->path = g_strdup_printf("%s/src_%s/sid%d/bis%d", device_get_path(data->device), src_addr, sid, bis); } else { assistant->path = g_strdup_printf("%s/sid%d/bis%d", adapter_get_path(data->adapter), sid, bis); assistant->state = ASSISTANT_STATE_LOCAL; } g_strdelimit(assistant->path, ":", '_'); if (!assistants) assistants = queue_new(); queue_push_tail(assistants, assistant); return assistant; } static void assistant_set_state(struct bass_assistant *assistant, enum assistant_state state) { enum assistant_state old_state = assistant->state; const char *str; if (old_state == state || old_state == ASSISTANT_STATE_LOCAL) return; assistant->state = state; DBG("State changed %s: %s -> %s", assistant->path, str_state[old_state], str_state[state]); str = state2str(state); if (g_strcmp0(str, state2str(old_state)) != 0) g_dbus_emit_property_changed(btd_get_dbus_connection(), assistant->path, MEDIA_ASSISTANT_INTERFACE, "State"); } static int assistant_parse_qos(struct bass_assistant *assistant, DBusMessageIter *iter) { DBusMessageIter dict; const char *key; dbus_message_iter_recurse(iter, &dict); while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter value, entry; int var; dbus_message_iter_recurse(&dict, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); var = dbus_message_iter_get_arg_type(&value); if (!strcasecmp(key, "BCode")) { DBusMessageIter array; struct iovec iov = {0}; if (var != DBUS_TYPE_ARRAY) return -EINVAL; dbus_message_iter_recurse(&value, &array); dbus_message_iter_get_fixed_array(&array, &iov.iov_base, (int *)&iov.iov_len); if (iov.iov_len != BT_BASS_BCAST_CODE_SIZE) { error("Invalid size for BCode: %zu != 16", iov.iov_len); return -EINVAL; } util_iov_free(assistant->qos.bcast.bcode, 1); assistant->qos.bcast.bcode = util_iov_dup(&iov, 1); return 0; } dbus_message_iter_next(&dict); } return 0; } static bool match_device(const void *data, const void *match_data) { const struct bass_data *bdata = data; const struct btd_device *device = match_data; return bdata->device == device; } static int assistant_parse_props(struct bass_assistant *assistant, DBusMessageIter *props) { DBusMessageIter value, entry, array; const char *key, *path; struct btd_device *device; struct bass_data *data; while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { dbus_message_iter_recurse(props, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); if (!strcasecmp(key, "Metadata")) { struct iovec iov; if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_ARRAY) goto fail; dbus_message_iter_recurse(&value, &array); dbus_message_iter_get_fixed_array(&array, &iov.iov_base, (int *)&iov.iov_len); util_iov_free(assistant->meta, 1); assistant->meta = util_iov_dup(&iov, 1); DBG("Parsed Metadata"); } else if (!strcasecmp(key, "QoS")) { if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_ARRAY) goto fail; if (assistant_parse_qos(assistant, &value)) goto fail; DBG("Parsed QoS"); } else if (!strcasecmp(key, "Device")) { if (assistant->state != ASSISTANT_STATE_LOCAL) { error("Device property is for local assistant " "only"); goto fail; } if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_OBJECT_PATH) goto fail; dbus_message_iter_get_basic(&value, &path); device = btd_adapter_find_device_by_path( assistant->adapter, path); if (!device) { error("Unable to find device %s", path); goto fail; } data = queue_find(sessions, match_device, device); if (!data) { error("Unable to find data for device %s", path); goto fail; } if (!data->bass) { error("Unable to find bass for device %s", path); goto fail; } if (assistant->data->bass) bt_bass_unref(assistant->data->bass); assistant->data->bass = bt_bass_ref(data->bass); } dbus_message_iter_next(props); } return 0; fail: DBG("Failed parsing %s", key); return -EINVAL; } static bool match_bass(const void *data, const void *match_data) { const struct bass_data *bdata = data; const struct bt_bass *bass = match_data; /* Ignore data from own broadcast source */ if (!bdata->device) return false; return bdata->bass == bass; } static void assistant_past(struct bass_assistant *assistant) { struct io *io = bt_bap_stream_get_io(assistant->data->stream); struct btd_device *device = assistant->device; int sk; struct sockaddr_iso *addr; int err; DBG(""); if (!io) { error("stream io not set"); return; } sk = io_get_fd(io); if (sk < 0) return; if (!device) { struct bt_bass *bass = assistant->data->bass; struct bass_data *data; data = queue_find(sessions, match_bass, bass); if (!data) { error("Unable to find data for bass %p", bass); return; } device = data->device; if (!device) { error("Unable to find device for bass %p", bass); return; } } addr = malloc(sizeof(*addr) + sizeof(*addr->iso_bc)); memset(addr, 0, sizeof(*addr) + sizeof(*addr->iso_bc)); addr->iso_family = AF_BLUETOOTH; bacpy(&addr->iso_bc->bc_bdaddr, (void *) device_get_address(device)); addr->iso_bc->bc_bdaddr_type = device_get_le_address_type(device); err = bind(sk, (struct sockaddr *) addr, sizeof(*addr) + sizeof(*addr->iso_bc)); if (err) error("bind: %s", strerror(errno)); free(addr); } static DBusMessage *push_mod_src(struct bass_assistant *assistant, DBusMessage *msg) { struct bt_bass_bcast_audio_scan_cp_hdr hdr; struct bt_bass_mod_src_params params = {0}; struct iovec iov = {0}; uint32_t bis_sync = 0; uint8_t meta_len = 0; int err; hdr.op = BT_BASS_MOD_SRC; params.id = assistant->src_id; params.pa_sync = PA_SYNC_NO_SYNC; params.pa_interval = PA_INTERVAL_UNKNOWN; params.num_subgroups = assistant->sgrp + 1; util_iov_append(&iov, ¶ms, sizeof(params)); for (uint8_t sgrp = 0; sgrp < params.num_subgroups; sgrp++) { util_iov_append(&iov, &bis_sync, sizeof(bis_sync)); util_iov_append(&iov, &meta_len, sizeof(meta_len)); } err = bt_bass_send(assistant->data->bass, &hdr, &iov); if (err) { DBG("Unable to send BASS Write Command"); return btd_error_failed(msg, strerror(-err)); } free(iov.iov_base); if (assistant->device) { assistant_set_state(assistant, ASSISTANT_STATE_IDLE); } else { struct bass_src *src; src = queue_remove_if(assistant->srcs, match_src_data, assistant->data->bass); free(src); } return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *push_add_src(struct bass_assistant *assistant, DBusMessage *msg) { struct bt_bass_bcast_audio_scan_cp_hdr hdr; struct bt_bass_add_src_params params = {0}; struct iovec iov = {0}; uint32_t bis_sync = 0; uint8_t meta_len = 0; int err; struct io *io; hdr.op = BT_BASS_ADD_SRC; if (assistant->device) { if (device_get_le_address_type(assistant->device) == BDADDR_LE_PUBLIC) params.addr_type = BT_BASS_ADDR_PUBLIC; else params.addr_type = BT_BASS_ADDR_RANDOM; bacpy(¶ms.addr, device_get_address(assistant->device)); params.sid = assistant->sid; put_le24(assistant->bid, params.bid); params.pa_sync = PA_SYNC_NO_PAST; params.pa_interval = PA_INTERVAL_UNKNOWN; params.num_subgroups = assistant->sgrp + 1; } else { io = bt_bap_stream_get_io(assistant->data->stream); if (io) { int fd = io_get_fd(io); struct sockaddr_iso addr; socklen_t olen = sizeof(addr); memset(&addr, 0, sizeof(addr)); if (getsockname(fd, (struct sockaddr *) &addr, &olen) < 0) { error("getsockname: %s", strerror(errno)); return btd_error_invalid_args(msg); } if (addr.iso_bdaddr_type == BDADDR_LE_PUBLIC) params.addr_type = BT_BASS_ADDR_PUBLIC; else params.addr_type = BT_BASS_ADDR_RANDOM; bacpy(¶ms.addr, &addr.iso_bdaddr); } else { if (btd_adapter_get_address_type(assistant->adapter) == BDADDR_LE_PUBLIC) params.addr_type = BT_BASS_ADDR_PUBLIC; else params.addr_type = BT_BASS_ADDR_RANDOM; bacpy(¶ms.addr, btd_adapter_get_address(assistant->adapter)); } params.sid = assistant->sid; /* TODO: Add a way to recover BID */ put_le24(assistant->bid, params.bid); if (assistant->data->stream && btd_adapter_has_settings(assistant->adapter, MGMT_SETTING_PAST_SENDER)) params.pa_sync = PA_SYNC_PAST; else params.pa_sync = PA_SYNC_NO_PAST; params.pa_interval = PA_INTERVAL_UNKNOWN; params.num_subgroups = assistant->sgrp + 1; } util_iov_append(&iov, ¶ms, sizeof(params)); /* Metadata and the BIS index associated with the MediaAssistant * object will be set in the subgroup they belong to. For the other * subgroups, no metadata and no BIS index will be provided. */ for (uint8_t sgrp = 0; sgrp < assistant->sgrp; sgrp++) { util_iov_append(&iov, &bis_sync, sizeof(bis_sync)); util_iov_append(&iov, &meta_len, sizeof(meta_len)); } /* Use 0xFFFFFFFF to indicate no preference (any BIS index) to allow * delegators to sync with BIG with multiple BIS */ bis_sync = 0xFFFFFFFF; meta_len = assistant->meta->iov_len; util_iov_append(&iov, &bis_sync, sizeof(bis_sync)); util_iov_append(&iov, &meta_len, sizeof(meta_len)); util_iov_append(&iov, assistant->meta->iov_base, assistant->meta->iov_len); err = bt_bass_send(assistant->data->bass, &hdr, &iov); if (err) { DBG("Unable to send BASS Write Command"); return btd_error_failed(msg, strerror(-err)); } free(iov.iov_base); if (assistant->state == ASSISTANT_STATE_LOCAL) assistant_add_src(assistant, assistant->data->bass, 0); else assistant_set_state(assistant, ASSISTANT_STATE_PENDING); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *push(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct bass_assistant *assistant = user_data; DBusMessageIter props, dict; DBG(""); dbus_message_iter_init(msg, &props); if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_ARRAY) { DBG("Unable to parse properties"); return btd_error_invalid_args(msg); } dbus_message_iter_recurse(&props, &dict); if (assistant_parse_props(assistant, &dict)) { DBG("Unable to parse properties"); return btd_error_invalid_args(msg); } if (assistant->state == ASSISTANT_STATE_ACTIVE) { return push_mod_src(assistant, msg); } else if (assistant->state == ASSISTANT_STATE_LOCAL) { struct bass_src *src; src = queue_find(assistant->srcs, match_src_data, assistant->data->bass); if (src) { assistant->src_id = src->src_id; return push_mod_src(assistant, msg); } } return push_add_src(assistant, msg); } static const GDBusMethodTable assistant_methods[] = { {GDBUS_EXPERIMENTAL_ASYNC_METHOD("Push", GDBUS_ARGS({ "Props", "a{sv}" }), NULL, push)}, {}, }; static const char *state2str(enum assistant_state state) { switch (state) { case ASSISTANT_STATE_IDLE: return "idle"; case ASSISTANT_STATE_PENDING: return "pending"; case ASSISTANT_STATE_REQUESTING: return "requesting"; case ASSISTANT_STATE_ACTIVE: return "active"; case ASSISTANT_STATE_LOCAL: return "local"; } return NULL; } static gboolean get_state(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bass_assistant *assistant = data; const char *state = state2str(assistant->state); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &state); return TRUE; } static gboolean get_metadata(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bass_assistant *assistant = data; DBusMessageIter array; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array); if (assistant->meta) dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &assistant->meta->iov_base, assistant->meta->iov_len); dbus_message_iter_close_container(iter, &array); return TRUE; } static gboolean get_qos(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bass_assistant *assistant = data; DBusMessageIter dict; uint8_t arr[BT_BASS_BCAST_CODE_SIZE] = {0}; uint8_t *bcode = arr; if (assistant->qos.bcast.bcode) memcpy(arr, assistant->qos.bcast.bcode->iov_base, BT_BASS_BCAST_CODE_SIZE); dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); dict_append_entry(&dict, "Encryption", DBUS_TYPE_BYTE, &assistant->qos.bcast.encryption); dict_append_array(&dict, "BCode", DBUS_TYPE_BYTE, &bcode, BT_BASS_BCAST_CODE_SIZE); dbus_message_iter_close_container(iter, &dict); return TRUE; } static const GDBusPropertyTable assistant_properties[] = { { "State", "s", get_state }, { "Metadata", "ay", get_metadata, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "QoS", "a{sv}", get_qos, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { } }; static void assistant_free(void *data) { struct bass_assistant *assistant = data; g_free(assistant->path); util_iov_free(assistant->meta, 1); util_iov_free(assistant->caps, 1); queue_destroy(assistant->srcs, free); free(assistant); } static void bis_src_handler(uint8_t sid, uint8_t bis, uint8_t sgrp, struct iovec *caps, struct iovec *meta, struct bt_bap_qos *qos, void *user_data) { struct bass_data *data = user_data; struct bass_assistant *assistant; char addr[18]; ba2str(btd_adapter_get_address(data->adapter), addr); DBG("%s data %p BIS %d", addr, data, bis); assistant = assistant_new(data->adapter, NULL, data, sgrp, sid, bis, qos, meta, caps); if (!g_dbus_register_interface(btd_get_dbus_connection(), assistant->path, MEDIA_ASSISTANT_INTERFACE, assistant_methods, NULL, assistant_properties, assistant, assistant_free)) DBG("Could not register path %s", assistant->path); } static bool assistant_match_data(const void *data, const void *match_data) { const struct bass_assistant *assistant = data; const struct bass_data *bdata = match_data; return (assistant->data == bdata); } static void unregister_assistant(void *data) { struct bass_assistant *assistant = data; DBG("%p", assistant); g_dbus_unregister_interface(btd_get_dbus_connection(), assistant->path, MEDIA_ASSISTANT_INTERFACE); } static void bap_state_src_changed(struct bt_bap_stream *stream, uint8_t old_state, uint8_t new_state, void *user_data) { struct bass_data *data = user_data; struct assistant *assistant; struct bt_bap_qos *qos = NULL; struct iovec *base; DBG("stream %p: %s(%u) -> %s(%u)", stream, bt_bap_stream_statestr(old_state), old_state, bt_bap_stream_statestr(new_state), new_state); switch (new_state) { case BT_BAP_STREAM_STATE_IDLE: /* Unregister assistant object if one exists */ assistant = queue_remove_if(assistants, assistant_match_data, data); if (assistant) unregister_assistant(assistant); data->stream = NULL; break; case BT_BAP_STREAM_STATE_STREAMING: /* If already streaming don't attempt to parse it again */ if (old_state == new_state) break; base = bt_bap_stream_get_base(stream); if (!base) { error("Unable to read BASE of stream %p", stream); break; } if (!bt_bap_stream_io_get_qos(stream, NULL, &qos)) { error("Unable to read QoS of stream %p", stream); break; } bt_bap_parse_base(0x00, base, qos, bass_debug, bis_src_handler, data); data->stream = stream; break; } } static void bap_bc_attached(struct bt_bap *bap, void *user_data) { struct btd_gatt_database *db; struct btd_adapter *adapter; struct bass_data *data; /* Only handle broadcast session */ if (bt_bap_get_att(bap)) return; DBG("%p", bap); db = btd_gatt_database_get(bt_bap_get_db(bap, false)); if (!db) return; adapter = btd_gatt_database_get_adapter(db); if (!adapter) return; /* Create BASS session with the local Broadcast Source */ data = bass_data_new(adapter, NULL); data->bap = bap; data->state_id = bt_bap_state_register(bap, bap_state_src_changed, NULL, data, NULL); bass_data_add(data); } static bool delegator_match_device(const void *data, const void *match_data) { const struct bass_delegator *dg = data; const struct btd_device *device = match_data; return dg->device == device; } static void delegator_attach(struct bt_bap *bap, struct btd_device *device, struct btd_service *service) { struct bass_delegator *dg; GError *err = NULL; dg = queue_find(delegators, delegator_match_device, device); if (!dg) /* Only probe devices added via Broadcast Assistants */ return; DBG("delegator %p", dg); if (dg->service) /* Service has already been probed */ return; dg->service = service; dg->bap = bap; dg->io = bt_io_listen(NULL, confirm_cb, dg, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, btd_adapter_get_address(device_get_adapter(device)), BT_IO_OPT_SOURCE_TYPE, btd_adapter_get_address_type(device_get_adapter(device)), BT_IO_OPT_DEST_BDADDR, device_get_address(device), BT_IO_OPT_DEST_TYPE, btd_device_get_bdaddr_type(device), BT_IO_OPT_MODE, BT_IO_MODE_ISO, BT_IO_OPT_QOS, &bap_sink_pa_qos, BT_IO_OPT_ISO_BC_SID, dg->sid, BT_IO_OPT_INVALID); if (!dg->io) { error("%s", err->message); g_error_free(err); return; } /* Take ownership for the service by setting the user data. */ btd_service_set_user_data(service, dg); } static void bap_attached(struct bt_bap *bap, void *user_data) { struct btd_service *service; struct btd_profile *p; struct btd_device *device; struct btd_adapter *adapter; struct bass_data *data; service = bt_bap_get_user_data(bap); if (!service) return bap_bc_attached(bap, user_data); DBG("%p", bap); p = btd_service_get_profile(service); if (!p) return; /* Only handle sessions with Broadcast Sources */ if (!g_str_equal(p->remote_uuid, BCAAS_UUID_STR)) return; device = btd_service_get_device(service); adapter = device_get_adapter(device); /* Create BASS session with the Broadcast Source */ data = bass_data_new(adapter, device); /* * Store BAP reference so match_bap() can find this session * when bap_detached() is called during device removal. */ data->bap = bap; data->bis_id = bt_bap_bis_cb_register(bap, bis_probe, bis_remove, device, NULL); bass_data_add(data); delegator_attach(bap, device, service); } static bool match_bap(const void *data, const void *match_data) { const struct bass_data *d = data; const struct bt_bap *bap = match_data; return (d->bap == bap); } static void bap_bc_detached(struct bt_bap *bap, struct bass_data *data) { DBG("%p", bap); /* * Unregister BIS callback before removing session to ensure * no stale device pointers remain in the BAP callback queue. */ if (data->bis_id) { bt_bap_bis_cb_unregister(bap, data->bis_id); data->bis_id = 0; } bt_bap_state_unregister(bap, data->state_id); bass_data_remove(data); } static void delegator_free(struct bass_delegator *dg) { DBG("%p", dg); if (dg->io_id) g_source_remove(dg->io_id); if (dg->io) { g_io_channel_shutdown(dg->io, TRUE, NULL); g_io_channel_unref(dg->io); } queue_destroy(dg->setups, setup_free); /* Update Broadcast Receive State characteristic value and notify * peers. */ if (bt_bass_set_pa_sync(dg->src, BT_BASS_NOT_SYNCHRONIZED_TO_PA)) DBG("Failed to update Broadcast Receive State characteristic"); /* Unregister BAP stream state changed callback. */ bt_bap_state_unregister(dg->bap, dg->state_id); bt_bap_bcode_cb_unregister(dg->bap, dg->bcode_id); if (dg->timeout) g_source_remove(dg->timeout); queue_destroy(dg->bcode_reqs, free); free(dg->bcode); free(dg); } static bool match_service(const void *data, const void *match_data) { const struct bass_data *bdata = data; const struct btd_service *service = match_data; return bdata->service == service; } static void delegator_detach(struct btd_service *service) { struct bass_delegator *dg; dg = btd_service_get_user_data(service); if (!dg) return; if (!queue_remove(delegators, dg)) return; DBG("%p", dg); delegator_free(dg); btd_service_set_user_data(service, NULL); } static void bap_detached(struct bt_bap *bap, void *user_data) { struct btd_service *service; struct bass_data *data; data = queue_find(sessions, match_bap, bap); if (data) return bap_bc_detached(bap, data); DBG("%p", bap); service = bt_bap_get_user_data(bap); if (!service) return; /* Remove BASS session with the Broadcast Source device */ data = queue_find(sessions, match_service, service); if (data) { bt_bap_bis_cb_unregister(bap, data->bis_id); bt_bap_state_unregister(bap, data->state_id); bass_data_remove(data); } delegator_detach(service); } static void bis_probe(uint8_t sid, uint8_t bis, uint8_t sgrp, struct iovec *caps, struct iovec *meta, struct bt_bap_qos *qos, void *user_data) { struct btd_device *device = user_data; const struct queue_entry *entry; struct bt_bap *bap; struct bt_bap_pac *pac; struct bass_assistant *assistant; char addr[18]; for (entry = queue_get_entries(sessions); entry; entry = entry->next) { struct bass_data *data = entry->data; struct btd_adapter *adapter = device_get_adapter(data->device); if (!bt_bass_get_client(data->bass)) /* Only client sessions must be handled */ continue; bap = bt_bap_get_session(bt_bass_get_att(data->bass), NULL); if (!bap) continue; /* Check stream capabilities against peer caps. */ bt_bap_verify_bis(bap, bis, caps, &pac); if (!pac) /* Capabilities did not match. */ continue; ba2str(device_get_address(device), addr); DBG("%s data %p BIS %d", addr, data, bis); assistant = assistant_new(adapter, device, data, sgrp, sid, bis, qos, meta, caps); if (g_dbus_register_interface(btd_get_dbus_connection(), assistant->path, MEDIA_ASSISTANT_INTERFACE, assistant_methods, NULL, assistant_properties, assistant, assistant_free) == FALSE) DBG("Could not register path %s", assistant->path); } } static bool assistant_match_device(const void *data, const void *match_data) { const struct bass_assistant *assistant = data; const struct btd_device *device = match_data; return (assistant->device == device); } static void bis_remove(struct bt_bap *bap, void *user_data) { struct btd_device *device = user_data; queue_remove_all(assistants, assistant_match_device, device, unregister_assistant); } static struct bass_data *bass_data_new(struct btd_adapter *adapter, struct btd_device *device) { struct bass_data *data; data = new0(struct bass_data, 1); data->adapter = adapter; data->device = device; return data; } static void bass_data_add(struct bass_data *data) { bool initiator = false; DBG("data %p", data); if (queue_find(sessions, NULL, data)) { error("data %p already added", data); return; } bt_bass_set_debug(data->bass, bass_debug, NULL, NULL); if (!sessions) sessions = queue_new(); queue_push_tail(sessions, data); if (data->service) { btd_service_set_user_data(data->service, data); initiator = btd_service_is_initiator(data->service); } if ((!initiator && btd_adapter_has_settings(data->adapter, MGMT_SETTING_PAST_RECEIVER)) || (initiator && btd_adapter_has_settings(data->adapter, MGMT_SETTING_PAST_SENDER))) device_set_past_support(data->device, true); } static void bass_data_free(struct bass_data *data) { if (data->service) { btd_service_set_user_data(data->service, NULL); bt_bass_set_user_data(data->bass, NULL); } bt_bass_src_unregister(data->bass, data->src_id); bt_bass_cp_handler_unregister(data->bass, data->cp_id); bt_bass_unref(data->bass); queue_remove_all(assistants, assistant_match_data, data, unregister_assistant); free(data); } static void bass_data_remove(struct bass_data *data) { DBG("data %p", data); if (!queue_remove(sessions, data)) return; bass_data_free(data); if (queue_isempty(sessions)) { queue_destroy(sessions, NULL); sessions = NULL; } } static void bass_detached(struct bt_bass *bass, void *user_data) { struct bass_data *data; DBG("%p", bass); data = queue_find(sessions, match_bass, bass); if (!data) { error("Unable to find bass session"); return; } /* If there is a service it means there is BASS thus we can keep * instance allocated. */ if (data->service) return; bass_data_remove(data); } static struct bass_delegator * bass_delegator_new(struct btd_device *device, struct bt_bcast_src *src, uint8_t sid) { struct bass_delegator *dg; dg = new0(struct bass_delegator, 1); if (!dg) return NULL; dg->device = device; dg->src = src; dg->sid = sid; dg->bcode_reqs = queue_new(); dg->setups = queue_new(); if (!delegators) delegators = queue_new(); queue_push_tail(delegators, dg); DBG("delegator %p", dg); /* Add Broadcast Audio Announcement Service UUID * to device and probe service. */ btd_device_add_uuid(device, BCAAS_UUID_STR); if (!dg->service) error("Unable to probe service for %s", BCAAS_UUID_STR); return dg; } static int handle_add_src_req(struct bt_bcast_src *bcast_src, struct bt_bass_add_src_params *params, struct bass_data *data) { struct btd_adapter *adapter = device_get_adapter(data->device); struct btd_device *device; struct bass_delegator *dg; /* Detect if PAST can be used then it can be used as destination since * PAST Receiver uses the ACL connection itself. */ if (params->pa_sync == PA_SYNC_PAST) { /* Check if MGMT_SETTING_PAST_RECEIVER is supported then set * DEVICE_FLAG_PAST since the device is requesting PAST to be * used. */ if (btd_adapter_has_settings(data->adapter, MGMT_SETTING_PAST_RECEIVER)) { device_set_past_support(data->device, true); device = data->device; goto done; } } /* Create device for Broadcast Source using the parameters * provided by Broadcast Assistant. */ device = btd_adapter_get_device(adapter, ¶ms->addr, params->addr_type); if (!device) { DBG("Unable to get device"); return -EINVAL; } done: DBG("device %p", device); /* Probe Broadcast Source, if it has not already been * autonomously probed inside BAP. */ if (!btd_device_get_service(device, BCAAS_UUID_STR)) { dg = bass_delegator_new(device, bcast_src, params->sid); if (!dg) return -ENOMEM; } /* Set PA sync state, this has to be done after probing otherwise there * is a race where the remote may receive BT_BASS_SYNC_INFO_RE and start * PAST before the kernel has sent PAST Parameter command that enables * receiving it. */ if (params->pa_sync == PA_SYNC_PAST) { if (btd_adapter_has_settings(data->adapter, MGMT_SETTING_PAST_RECEIVER)) bt_bass_set_pa_sync(bcast_src, BT_BASS_SYNC_INFO_RE); else bt_bass_set_pa_sync(bcast_src, BT_BASS_NO_PAST); } return 0; } static bool delegator_match_src(const void *data, const void *match_data) { const struct bass_delegator *dg = data; const struct bt_bcast_src *src = match_data; return dg->src == src; } static int handle_set_bcode_req(struct bt_bcast_src *bcast_src, struct bt_bass_set_bcast_code_params *params, struct bass_data *data) { struct bass_delegator *dg; struct bass_bcode_req *req; dg = queue_find(delegators, delegator_match_src, bcast_src); if (!dg) return -EINVAL; dg->bcode = new0(uint8_t, BT_BASS_BCAST_CODE_SIZE); memcpy(dg->bcode, params->bcast_code, BT_BASS_BCAST_CODE_SIZE); if (dg->timeout) { g_source_remove(dg->timeout); dg->timeout = 0; } /* Set the Broadcast Code for each stream that required it. */ while ((req = queue_pop_head(dg->bcode_reqs))) { setup_set_bcode(dg->bcode, req->setup, req->cb, req->user_data); free(req); } return 0; } static bool check_bis_sync(struct bt_bass_mod_src_params *params, uint8_t bis) { uint32_t bitmask = 1 << (bis - 1); struct iovec iov = { .iov_base = params->subgroup_data, .iov_len = 0, }; /* Calculate subgroup data length based on each subgroup's * bis_sync (4 bytes) + meta_len (1 byte) + meta fields. */ for (uint8_t i = 0; i < params->num_subgroups; i++) { uint32_t bis_sync; uint8_t meta_len; iov.iov_len += sizeof(bis_sync) + sizeof(meta_len); memcpy(&bis_sync, params->subgroup_data + iov.iov_len - sizeof(bis_sync) - sizeof(meta_len), sizeof(bis_sync)); memcpy(&meta_len, params->subgroup_data + iov.iov_len - sizeof(meta_len), sizeof(meta_len)); if (le32_to_cpu(bis_sync) & bitmask) return true; iov.iov_len += meta_len; } return false; } static void bass_update_bis_sync(struct bass_delegator *dg, struct bt_bass_mod_src_params *params) { const struct queue_entry *entry; /* Check if existing setups if BIS needs to be added/removed */ for (entry = queue_get_entries(dg->setups); entry;) { struct bass_setup *setup = entry->data; uint8_t state; /* Prefetch next entry since the likes of bass_remove_bis can * end up removing the next entry. */ entry = entry->next; state = bt_bap_stream_get_state(setup->stream); DBG("stream %p: BIS %d state %s(%u)", setup->stream, setup->bis, bt_bap_stream_statestr(state), state); if (!setup->stream && check_bis_sync(params, setup->bis)) bass_add_bis(setup); else if (setup->stream && state == BT_BAP_STREAM_STATE_STREAMING && !check_bis_sync(params, setup->bis)) bass_remove_bis(setup); } } static int handle_mod_src_req(struct bt_bcast_src *bcast_src, struct bt_bass_mod_src_params *params, struct bass_data *data) { struct bass_delegator *dg; uint8_t sync_state; int err = 0; DBG(""); dg = queue_find(delegators, delegator_match_src, bcast_src); if (!dg) return -EINVAL; err = bt_bass_get_pa_sync(bcast_src, &sync_state); if (err) return err; DBG("PA sync state %d", sync_state); switch (sync_state) { case BT_BASS_SYNCHRONIZED_TO_PA: if (params->pa_sync == PA_SYNC_NO_SYNC) { /* Release all setups. Note: bass_remove_bis may * trigger synchronous state transitions that call * setup_clear which will return early since the * setup has already been removed from the queue. */ struct bass_setup *setup; while ((setup = queue_pop_head(dg->setups))) { bass_remove_bis(setup); setup_free(setup); } delegator_disconnect(dg); } else bass_update_bis_sync(dg, params); break; case BT_BASS_NOT_SYNCHRONIZED_TO_PA: if (params->pa_sync == PA_SYNC_NO_PAST) { struct btd_adapter *adapter = device_get_adapter(dg->device); GError *err = NULL; dg->io = bt_io_listen(NULL, confirm_cb, dg, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, btd_adapter_get_address(adapter), BT_IO_OPT_SOURCE_TYPE, btd_adapter_get_address_type(adapter), BT_IO_OPT_DEST_BDADDR, device_get_address(dg->device), BT_IO_OPT_DEST_TYPE, btd_device_get_bdaddr_type(dg->device), BT_IO_OPT_MODE, BT_IO_MODE_ISO, BT_IO_OPT_QOS, &bap_sink_pa_qos, BT_IO_OPT_ISO_BC_SID, dg->sid, BT_IO_OPT_INVALID); if (!dg->io) { error("%s", err->message); g_error_free(err); } } break; } return 0; } static int cp_handler(struct bt_bcast_src *bcast_src, uint8_t op, void *params, void *user_data) { struct bass_data *data = user_data; int err = 0; switch (op) { case BT_BASS_ADD_SRC: err = handle_add_src_req(bcast_src, params, data); break; case BT_BASS_SET_BCAST_CODE: err = handle_set_bcode_req(bcast_src, params, data); break; case BT_BASS_MOD_SRC: err = handle_mod_src_req(bcast_src, params, data); break; } return err; } static void bass_attached(struct bt_bass *bass, void *user_data) { struct bass_data *data; struct bt_att *att; struct btd_device *device; DBG("%p", bass); data = queue_find(sessions, match_bass, bass); if (data) return; att = bt_bass_get_att(bass); if (!att) return; device = btd_adapter_find_device_by_fd(bt_att_get_fd(att)); if (!device) { error("Unable to find device"); return; } data = bass_data_new(device_get_adapter(device), device); data->bass = bass; data->cp_id = bt_bass_cp_handler_register(data->bass, cp_handler, NULL, data); bass_data_add(data); } static void bass_handle_bcode_req(struct bass_assistant *assistant, int id) { struct bt_bass_bcast_audio_scan_cp_hdr hdr; struct bt_bass_set_bcast_code_params params = {0}; struct iovec iov = {0}; int err; assistant_set_state(assistant, ASSISTANT_STATE_REQUESTING); hdr.op = BT_BASS_SET_BCAST_CODE; params.id = id; if (assistant->qos.bcast.bcode) memcpy(params.bcast_code, assistant->qos.bcast.bcode->iov_base, BT_BASS_BCAST_CODE_SIZE); iov.iov_base = malloc0(sizeof(params)); if (!iov.iov_base) return; util_iov_push_mem(&iov, sizeof(params), ¶ms); err = bt_bass_send(assistant->data->bass, &hdr, &iov); if (err) { DBG("Unable to send BASS Write Command"); return; } free(iov.iov_base); } static struct bass_src *assistant_add_src(struct bass_assistant *assistant, struct bt_bass *bass, uint8_t id) { struct bass_src *src; if (!assistant->srcs) assistant->srcs = queue_new(); src = queue_find(assistant->srcs, match_src_data, bass); if (src) { src->src_id = id; return src; } src = new0(struct bass_src, 1); src->bass = bass; src->src_id = id; queue_push_tail(assistant->srcs, src); return src; } static void bass_src_changed(uint8_t id, uint32_t bid, uint8_t state, uint8_t enc, uint32_t bis_sync, void *user_data) { struct bass_data *data = user_data; const struct queue_entry *entry; for (entry = queue_get_entries(assistants); entry; entry = entry->next) { struct bass_assistant *assistant = entry->data; uint32_t bis = 1 << (assistant->bis - 1); if (bid && assistant->bid != bid) /* Only handle assistant objects * that match the source */ continue; /* If BID is not set it may happen to be local stream so ignore * non-local assistants unless their BID is also not set. */ if (!bid && assistant->bid) continue; if (state == BT_BASS_SYNC_INFO_RE) { assistant_past(assistant); return; } switch (enc) { case BT_BASS_BIG_ENC_STATE_BCODE_REQ: if (assistant->state != ASSISTANT_STATE_PENDING && assistant->state != ASSISTANT_STATE_LOCAL) /* Only handle assistant objects that * have been pushed by the user */ break; /* Provide Broadcast Code to peer */ bass_handle_bcode_req(assistant, id); if (assistant->state == ASSISTANT_STATE_LOCAL) return; break; case BT_BASS_BIG_ENC_STATE_NO_ENC: if (assistant->state != ASSISTANT_STATE_PENDING) /* Only handle assistant objects that * have been pushed by the user */ break; /* Match BIS index */ if (bis & bis_sync) { assistant->src_id = id; assistant_set_state(assistant, ASSISTANT_STATE_ACTIVE); } break; case BT_BASS_BIG_ENC_STATE_DEC: /* Only handle assistant objects that * have requested a Broadcast Code */ if (assistant->state != ASSISTANT_STATE_REQUESTING && assistant->state != ASSISTANT_STATE_LOCAL) break; /* Match BIS index */ if (bis & bis_sync) { assistant->src_id = id; if (assistant->state == ASSISTANT_STATE_LOCAL) { assistant_add_src(assistant, data->bass, id); break; } assistant_set_state(assistant, ASSISTANT_STATE_ACTIVE); } break; default: continue; } } } static int bass_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct btd_adapter *adapter = device_get_adapter(device); struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct bass_data *data = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); /* Ignore, if we were probed for this device already */ if (data) { error("Profile probed twice for the same device!"); return -EINVAL; } data = bass_data_new(adapter, device); data->service = service; data->bass = bt_bass_new(btd_gatt_database_get_db(database), btd_device_get_gatt_db(device), btd_adapter_get_address(adapter)); if (!data->bass) { error("Unable to create BASS instance"); free(data); return -EINVAL; } bass_data_add(data); bt_bass_set_user_data(data->bass, service); /* Register callback to be called when notifications for * Broadcast Receive State characteristics are received. */ data->src_id = bt_bass_src_register(data->bass, bass_src_changed, data, NULL); data->cp_id = bt_bass_cp_handler_register(data->bass, cp_handler, NULL, data); return 0; } static void bass_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct bass_data *data; char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); data = btd_service_get_user_data(service); if (!data) { error("BASS service not handled by profile"); return; } bass_data_remove(data); } static int bass_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct bt_gatt_client *client = btd_device_get_gatt_client(device); struct bass_data *data = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); if (!data) { error("BASS service not handled by profile"); return -EINVAL; } /* Only attach client if initiator of the connection otherwise act as * delegator. */ if (btd_service_is_initiator(service) && !bt_bass_attach(data->bass, client)) { error("BASS unable to attach"); return -EINVAL; } btd_service_connecting_complete(service, 0); return 0; } static int bass_disconnect(struct btd_service *service) { struct bass_data *data = btd_service_get_user_data(service); struct btd_device *device = btd_service_get_device(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); bt_bass_detach(data->bass); btd_service_disconnecting_complete(service, 0); return 0; } static int bass_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { struct btd_gatt_database *database = btd_adapter_get_database(adapter); DBG("BASS path %s", adapter_get_path(adapter)); bt_bass_add_db(btd_gatt_database_get_db(database), btd_adapter_get_address(adapter)); return 0; } static void bass_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { DBG("BASS remove Adapter"); } static struct btd_profile bass_service = { .name = "bass", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = BASS_UUID_STR, .device_probe = bass_probe, .device_remove = bass_remove, .accept = bass_accept, .disconnect = bass_disconnect, .adapter_probe = bass_server_probe, .adapter_remove = bass_server_remove, .experimental = true, }; static unsigned int bass_id; static unsigned int bap_id; static int bass_init(void) { int err; err = btd_profile_register(&bass_service); if (err) return err; bass_id = bt_bass_register(bass_attached, bass_detached, NULL); bap_id = bt_bap_register(bap_attached, bap_detached, NULL); return 0; } static void bass_exit(void) { btd_profile_unregister(&bass_service); bt_bass_unregister(bass_id); bt_bap_unregister(bap_id); } BLUETOOTH_PLUGIN_DEFINE(bass, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, bass_init, bass_exit) bluez-5.87/profiles/audio/PaxHeaders/asha.c0000644000000000000000000000005015142177620015664 xustar0020 atime=1783093716 20 ctime=1783094106 bluez-5.87/profiles/audio/asha.c0000644000000000000000000003006215142177620015346 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2024 Asymptotic Inc. * * Author: Arun Raghavan * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include "btio/btio.h" #include "gdbus/gdbus.h" #include "bluetooth/bluetooth.h" #include "bluetooth/l2cap.h" #include "bluetooth/uuid.h" #include "src/dbus-common.h" #include "src/adapter.h" #include "src/device.h" #include "src/log.h" #include "src/plugin.h" #include "src/profile.h" #include "src/service.h" #include "src/shared/util.h" #include "profiles/audio/asha.h" #include "profiles/audio/media.h" #include "profiles/audio/transport.h" #define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1" /* 2 byte SDU length, 1 byte sequence number, and then 20ms of G.722 */ #define ASHA_MIN_MTU 163 /* The default of 672 does not work */ #define ASHA_CONNECTION_MTU 512 struct bt_asha_device { struct bt_asha *asha; struct btd_device *device; struct media_transport *transport; GIOChannel *io; uint16_t imtu, omtu; unsigned int resume_id; }; static char *make_endpoint_path(struct bt_asha_device *asha_dev) { char *path; int err; err = asprintf(&path, "%s/asha", device_get_path(asha_dev->device)); if (err < 0) { error("Could not allocate path for remote %s", device_get_path(asha_dev->device)); return NULL; } return path; } struct connect_data { struct bt_asha_device *asha_dev; bt_asha_cb_t cb; void *cb_user_data; }; static void connect_cb(GIOChannel *io, GError *err, gpointer user_data) { struct connect_data *conn_data = user_data; struct bt_asha_device *asha_dev = conn_data->asha_dev; GError *gerr = NULL; if (err) { error("%s", err->message); return; } if (!bt_io_get(io, &gerr, BT_IO_OPT_IMTU, &asha_dev->imtu, BT_IO_OPT_OMTU, &asha_dev->omtu, BT_IO_OPT_INVALID)) { /* Let this be non-fatal? */ asha_dev->omtu = ASHA_MIN_MTU; asha_dev->imtu = ASHA_CONNECTION_MTU; error("Could not get L2CAP CoC socket MTU: %s", gerr->message); g_error_free(gerr); } asha_dev->io = io; bt_asha_start(asha_dev->asha, conn_data->cb, conn_data->cb_user_data); } static int asha_connect_socket(struct bt_asha_device *asha_dev, bt_asha_cb_t cb, void *user_data) { GError *gerr = NULL; const bdaddr_t *src_addr; struct connect_data *conn_data; if (asha_dev->asha->state != ASHA_STOPPED) { error("ASHA device connect failed. Bad state %d", asha_dev->asha->state); return 0; } conn_data = g_new0(struct connect_data, 1); conn_data->asha_dev = asha_dev; conn_data->cb = cb; conn_data->cb_user_data = user_data; src_addr = btd_adapter_get_address( device_get_adapter(asha_dev->device)); if (!bt_io_connect(connect_cb, conn_data, g_free, &gerr, BT_IO_OPT_MODE, BT_IO_MODE_LE_FLOWCTL, BT_IO_OPT_SOURCE_TYPE, BDADDR_LE_PUBLIC, BT_IO_OPT_SOURCE_BDADDR, src_addr, BT_IO_OPT_DEST_TYPE, BDADDR_LE_PUBLIC, BT_IO_OPT_DEST_BDADDR, device_get_address(asha_dev->device), BT_IO_OPT_PSM, asha_dev->asha->psm, BT_IO_OPT_OMTU, ASHA_MIN_MTU, BT_IO_OPT_IMTU, ASHA_CONNECTION_MTU, BT_IO_OPT_INVALID)) { error("Could not open L2CAP CoC socket: %s", gerr->message); g_error_free(gerr); goto error; } DBG("L2CAP CoC socket is open"); return 0; error: return -1; } unsigned int bt_asha_device_start(struct bt_asha_device *asha_dev, bt_asha_cb_t cb, void *user_data) { int ret; btd_device_set_conn_param(asha_dev->device, 0x0010 /* min interval = 1.25ms intervals => 20ms */, 0x0010 /* max interval = 1.25ms intervals => 20ms */, 0x000A /* 10 events' latency */, 0x0064 /* 1s timeout */); ret = asha_connect_socket(asha_dev, cb, user_data); if (ret < 0) return 0; else return (++asha_dev->resume_id); } unsigned int bt_asha_device_stop(struct bt_asha_device *asha_dev) { bt_asha_stop(asha_dev->asha); if (asha_dev->io) { g_io_channel_shutdown(asha_dev->io, TRUE, NULL); g_io_channel_unref(asha_dev->io); asha_dev->io = NULL; }; return asha_dev->resume_id; } void bt_asha_device_state_reset(struct bt_asha_device *asha_dev) { if (asha_dev->io) { g_io_channel_shutdown(asha_dev->io, TRUE, NULL); g_io_channel_unref(asha_dev->io); asha_dev->io = NULL; }; bt_asha_state_reset(asha_dev->asha); asha_dev->resume_id = 0; } unsigned int bt_asha_device_device_get_resume_id( struct bt_asha_device *asha_dev) { return asha_dev->resume_id; } enum bt_asha_state_t bt_asha_device_get_state( struct bt_asha_device *asha_dev) { return asha_dev->asha->state; } uint16_t bt_asha_device_get_render_delay(struct bt_asha_device *asha_dev) { return asha_dev->asha->render_delay; } int8_t bt_asha_device_get_volume(struct bt_asha_device *asha_dev) { return asha_dev->asha->volume; } bool bt_asha_device_set_volume(struct bt_asha_device *asha_dev, int8_t volume) { return bt_asha_set_volume(asha_dev->asha, volume); } int bt_asha_device_get_fd(struct bt_asha_device *asha_dev) { return g_io_channel_unix_get_fd(asha_dev->io); } uint16_t bt_asha_device_get_imtu(struct bt_asha_device *asha_dev) { return asha_dev->imtu; } uint16_t bt_asha_device_get_omtu(struct bt_asha_device *asha_dev) { return asha_dev->omtu; } static gboolean get_uuid(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { const char *uuid; uuid = ASHA_PROFILE_UUID; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); return TRUE; } static gboolean get_side(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bt_asha_device *asha_dev = data; const char *side = asha_dev->asha->right_side ? "right" : "left"; /* Use a string in case we want to support more types in the future */ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &side); return TRUE; } static gboolean get_binaural(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bt_asha_device *asha_dev = data; dbus_bool_t binaural = asha_dev->asha->binaural; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &binaural); return TRUE; } static gboolean get_hisyncid(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bt_asha_device *asha_dev = data; DBusMessageIter array; uint8_t *hisyncid = asha_dev->asha->hisyncid; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array); dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &hisyncid, sizeof(asha_dev->asha->hisyncid)); dbus_message_iter_close_container(iter, &array); return TRUE; } static gboolean get_codecs(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bt_asha_device *asha_dev = data; dbus_uint16_t codecs = asha_dev->asha->codec_ids; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &codecs); return TRUE; } static gboolean get_device(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bt_asha_device *asha_dev = data; const char *path; path = device_get_path(asha_dev->device); dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); return TRUE; } static gboolean get_transport(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bt_asha_device *asha_dev = data; const char *path; path = media_transport_get_path(asha_dev->transport); dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); return TRUE; } static int asha_source_device_probe(struct btd_service *service) { struct bt_asha_device *asha_dev; struct btd_device *device = btd_service_get_device(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("Probing ASHA device %s", addr); asha_dev = g_new0(struct bt_asha_device, 1); asha_dev->device = device; asha_dev->asha = bt_asha_new(); asha_dev->io = NULL; btd_service_set_user_data(service, asha_dev); return 0; } static void asha_source_device_remove(struct btd_service *service) { struct bt_asha_device *asha_dev; struct btd_device *device = btd_service_get_device(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("Removing ASHA device %s", addr); asha_dev = btd_service_get_user_data(service); if (!asha_dev) { /* Can this actually happen? */ DBG("Not handling ASHA profile"); return; } bt_asha_free(asha_dev->asha); g_free(asha_dev); } static const GDBusMethodTable asha_ep_methods[] = { { }, }; static const GDBusPropertyTable asha_ep_properties[] = { { "UUID", "s", get_uuid, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Side", "s", get_side, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Binaural", "b", get_binaural, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "HiSyncId", "ay", get_hisyncid, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Codecs", "q", get_codecs, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Device", "o", get_device, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Transport", "o", get_transport, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { } }; static void asha_source_endpoint_register(struct bt_asha_device *asha_dev) { char *path; const struct media_endpoint *asha_ep; path = make_endpoint_path(asha_dev); if (!path) goto error; if (g_dbus_register_interface(btd_get_dbus_connection(), path, MEDIA_ENDPOINT_INTERFACE, asha_ep_methods, NULL, asha_ep_properties, asha_dev, NULL) == FALSE) { error("Could not register remote ep %s", path); goto error; } asha_ep = media_endpoint_get_asha(); asha_dev->transport = media_transport_create(asha_dev->device, path, NULL, 0, (void *) asha_ep, asha_dev); error: if (path) free(path); } static void asha_source_endpoint_unregister(struct bt_asha_device *asha) { char *path; path = make_endpoint_path(asha); if (!path) goto error; g_dbus_unregister_interface(btd_get_dbus_connection(), path, MEDIA_ENDPOINT_INTERFACE); if (asha->transport) { media_transport_destroy(asha->transport); asha->transport = NULL; } error: if (path) free(path); } static int asha_source_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct gatt_db *db = btd_device_get_gatt_db(device); struct bt_gatt_client *client = btd_device_get_gatt_client(device); struct bt_asha_device *asha_dev = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("Accepting ASHA connection on %s", addr); if (!asha_dev) { /* Can this actually happen? */ DBG("Not handling ASHA profile"); return -1; } if (!bt_asha_attach(asha_dev->asha, db, client, (bt_asha_attach_cb_t) asha_source_endpoint_register, asha_dev)) { return -1; } btd_service_connecting_complete(service, 0); return 0; } static int asha_source_disconnect(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct bt_asha_device *asha_dev = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("Disconnecting ASHA on %s", addr); if (!asha_dev) { /* Can this actually happen? */ DBG("Not handling ASHA profile"); return -1; } asha_source_endpoint_unregister(asha_dev); bt_asha_reset(asha_dev->asha); btd_service_disconnecting_complete(service, 0); return 0; } static struct btd_profile asha_source_profile = { .name = "asha-source", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = ASHA_PROFILE_UUID, .experimental = true, .device_probe = asha_source_device_probe, .device_remove = asha_source_device_remove, .auto_connect = true, .accept = asha_source_accept, .disconnect = asha_source_disconnect, }; static int asha_init(void) { int err; err = btd_profile_register(&asha_source_profile); if (err) return err; return 0; } static void asha_exit(void) { btd_profile_unregister(&asha_source_profile); } BLUETOOTH_PLUGIN_DEFINE(asha, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, asha_init, asha_exit) bluez-5.87/profiles/audio/PaxHeaders/bap.c0000644000000000000000000000005015221752714015513 xustar0020 atime=1783093693 20 ctime=1783094105 bluez-5.87/profiles/audio/bap.c0000644000000000000000000030002015221752714015167 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. * Copyright 2023-2025 NXP * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "bluetooth/iso.h" #include "src/btd.h" #include "src/dbus-common.h" #include "src/shared/util.h" #include "src/shared/io.h" #include "src/shared/att.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/shared/gatt-server.h" #include "src/shared/bap.h" #include "src/shared/tmap.h" #include "src/shared/gmap.h" #include "btio/btio.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/gatt-database.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #include "src/error.h" #include "transport.h" #define ISO_SOCKET_UUID "6fbaf188-05e0-496a-9885-d6ddfdb4e03e" #define PACS_UUID_STR "00001850-0000-1000-8000-00805f9b34fb" #define BCAAS_UUID_STR "00001852-0000-1000-8000-00805f9b34fb" #define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1" #define MEDIA_INTERFACE "org.bluez.Media1" struct bap_setup; typedef void (*bap_setup_ready_func_t)(struct bap_setup *setup, int code, uint8_t reason, void *data); typedef void (*bap_setup_close_func_t)(struct bap_setup *setup, void *data); typedef void (*bap_select_done_t)(int err, void *data); struct bap_setup { struct bap_ep *ep; struct bap_data *data; struct bt_bap_stream *stream; struct bt_bap_qos qos; int (*qos_parser)(struct bap_setup *setup, const char *key, int var, DBusMessageIter *iter); GIOChannel *io; unsigned int io_id; bool want_qos; bool want_io; bool want_cis; bool cis_active; uint8_t sid; bool config_pending; bool readying; bool closing; struct iovec *caps; struct iovec *metadata; unsigned int id; struct iovec *base; bap_setup_ready_func_t ready_cb; void *ready_cb_data; bap_setup_close_func_t close_cb; void *close_cb_data; void (*destroy)(struct bap_setup *setup); }; struct bap_select { struct bap_data *data; struct queue *eps; bool reconfigure; int remaining; int err; bap_select_done_t done_cb; void *done_cb_data; }; struct bap_ep { char *path; struct bap_data *data; struct bt_bap_pac *lpac; struct bt_bap_pac *rpac; uint32_t locations; uint16_t supported_context; uint16_t context; struct queue *setups; struct bap_select *select; bool reconfigure; }; struct bap_data { struct btd_device *device; struct btd_adapter *adapter; struct btd_service *service; struct bt_bap *bap; unsigned int ready_id; unsigned int state_id; unsigned int pac_id; struct queue *srcs; struct queue *snks; struct queue *bcast; struct queue *bcast_snks; struct queue *server_streams; GIOChannel *listen_io; unsigned int io_id; unsigned int cig_update_id; bool services_ready; bool bap_ready; }; static struct queue *sessions; static int setup_config(struct bap_setup *setup, bap_setup_ready_func_t cb, void *user_data); static int bap_select_all(struct bap_data *data, bool reconfigure, bap_select_done_t cb, void *user_data); static void setup_create_io(struct bap_data *data, struct bap_setup *setup, struct bt_bap_stream *stream, int defer); static void bap_update_cigs(struct bap_data *data); static void bap_debug(const char *str, void *user_data) { DBG_IDX(0xffff, "%s", str); } static void ep_unregister(void *data) { struct bap_ep *ep = data; DBG("ep %p path %s", ep, ep->path); g_dbus_unregister_interface(btd_get_dbus_connection(), ep->path, MEDIA_ENDPOINT_INTERFACE); } static void setup_free(void *data); static void bap_data_free(struct bap_data *data) { struct queue *bcast_snks = data->bcast_snks; if (data->listen_io) { g_io_channel_shutdown(data->listen_io, TRUE, NULL); g_io_channel_unref(data->listen_io); } if (data->io_id) g_source_remove(data->io_id); if (data->service && btd_service_get_user_data(data->service) == data) btd_service_set_user_data(data->service, NULL); queue_destroy(data->snks, ep_unregister); queue_destroy(data->srcs, ep_unregister); queue_destroy(data->bcast, ep_unregister); queue_destroy(data->server_streams, NULL); data->bcast_snks = NULL; queue_destroy(bcast_snks, setup_free); bt_bap_ready_unregister(data->bap, data->ready_id); bt_bap_state_unregister(data->bap, data->state_id); bt_bap_pac_unregister(data->bap, data->pac_id); bt_bap_unref(data->bap); if (data->cig_update_id) g_source_remove(data->cig_update_id); free(data); } static void bap_data_remove(struct bap_data *data) { DBG("data %p", data); if (!queue_remove(sessions, data)) return; bap_data_free(data); if (queue_isempty(sessions)) { queue_destroy(sessions, NULL); sessions = NULL; } } static void bap_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct bap_data *data; char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); data = btd_service_get_user_data(service); if (!data) { error("BAP service not handled by profile"); return; } bap_data_remove(data); } static gboolean get_uuid(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bap_ep *ep = data; const char *uuid; if (queue_find(ep->data->snks, NULL, ep)) uuid = PAC_SINK_UUID; else if (queue_find(ep->data->srcs, NULL, ep)) uuid = PAC_SOURCE_UUID; else if ((queue_find(ep->data->bcast, NULL, ep) && (bt_bap_pac_get_type(ep->lpac) == BT_BAP_BCAST_SINK))) uuid = BCAA_SERVICE_UUID; else uuid = BAA_SERVICE_UUID; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); return TRUE; } static gboolean get_codec(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bap_ep *ep = data; uint8_t codec; /* For broadcast source, rpac is null so the codec * is retrieved from the lpac */ if (ep->rpac == NULL) bt_bap_pac_get_codec(ep->lpac, &codec, NULL, NULL); else bt_bap_pac_get_codec(ep->rpac, &codec, NULL, NULL); dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &codec); return TRUE; } static gboolean has_capabilities(const GDBusPropertyTable *property, void *data) { struct bap_ep *ep = data; struct iovec *d = NULL; bt_bap_pac_get_codec(ep->rpac, NULL, &d, NULL); if (d) return TRUE; return FALSE; } static gboolean get_capabilities(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bap_ep *ep = data; DBusMessageIter array; struct iovec *d; bt_bap_pac_get_codec(ep->rpac, NULL, &d, NULL); dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array); dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &d->iov_base, d->iov_len); dbus_message_iter_close_container(iter, &array); return TRUE; } static gboolean has_metadata(const GDBusPropertyTable *property, void *data) { struct bap_ep *ep = data; struct iovec *d = NULL; bt_bap_pac_get_codec(ep->rpac, NULL, NULL, &d); if (d) return TRUE; return FALSE; } static gboolean get_metadata(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bap_ep *ep = data; DBusMessageIter array; struct iovec *d; bt_bap_pac_get_codec(ep->rpac, NULL, NULL, &d); dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array); dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &d->iov_base, d->iov_len); dbus_message_iter_close_container(iter, &array); return TRUE; } static gboolean get_device(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bap_ep *ep = data; const char *path; if (bt_bap_pac_get_type(ep->lpac) == BT_BAP_BCAST_SOURCE) path = adapter_get_path(ep->data->adapter); else path = device_get_path(ep->data->device); dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); return TRUE; } static gboolean get_locations(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bap_ep *ep = data; ep->locations = bt_bap_pac_get_locations(ep->rpac); dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &ep->locations); return TRUE; } static gboolean get_supported_context(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bap_ep *ep = data; ep->supported_context = bt_bap_pac_get_supported_context(ep->rpac); dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &ep->supported_context); return TRUE; } static gboolean get_context(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bap_ep *ep = data; ep->context = bt_bap_pac_get_context(ep->rpac); dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &ep->context); return TRUE; } static gboolean qos_exists(const GDBusPropertyTable *property, void *data) { struct bap_ep *ep = data; struct bt_bap_pac_qos *qos; qos = bt_bap_pac_get_qos(ep->rpac); if (!qos) return FALSE; return TRUE; } static gboolean get_qos(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bap_ep *ep = data; struct bt_bap_pac_qos *qos; DBusMessageIter dict; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); qos = bt_bap_pac_get_qos(ep->rpac); if (!qos) return FALSE; dict_append_entry(&dict, "Framing", DBUS_TYPE_BYTE, &qos->framing); dict_append_entry(&dict, "PHY", DBUS_TYPE_BYTE, &qos->phys); dict_append_entry(&dict, "Retransmissions", DBUS_TYPE_BYTE, &qos->rtn); dict_append_entry(&dict, "MaximumLatency", DBUS_TYPE_UINT16, &qos->latency); dict_append_entry(&dict, "MinimumDelay", DBUS_TYPE_UINT32, &qos->pd_min); dict_append_entry(&dict, "MaximumDelay", DBUS_TYPE_UINT32, &qos->pd_max); dict_append_entry(&dict, "PreferredMinimumDelay", DBUS_TYPE_UINT32, &qos->ppd_min); dict_append_entry(&dict, "PreferredMaximumDelay", DBUS_TYPE_UINT32, &qos->ppd_max); dbus_message_iter_close_container(iter, &dict); return TRUE; } static bool probe_tmap_role(struct bap_ep *ep, uint32_t data) { struct gatt_db *db = bt_bap_get_db(ep->data->bap, true); return bt_tmap_get_role(bt_tmap_find(db)) & data; } static bool probe_gmap_role(struct bap_ep *ep, uint32_t data) { struct gatt_db *db = bt_bap_get_db(ep->data->bap, true); return bt_gmap_get_role(bt_gmap_find(db)) & data; } static bool probe_gmap_feature(struct bap_ep *ep, uint32_t data) { struct gatt_db *db = bt_bap_get_db(ep->data->bap, true); return bt_gmap_get_features(bt_gmap_find(db)) & data; } struct feature { const char *name; bool (*probe)(struct bap_ep *ep, uint32_t data); uint32_t data; }; #define TMAP_ROLE(key) { key ## _STR, probe_tmap_role, key }, static const struct feature tmap_features[] = { BT_TMAP_ROLE_LIST(TMAP_ROLE) }; #define GMAP_ROLE(key) { key ## _STR, probe_gmap_role, key }, #define GMAP_FEATURE(key) { key ## _STR, probe_gmap_feature, key }, static const struct feature gmap_features[] = { BT_GMAP_ROLE_LIST(GMAP_ROLE) BT_GMAP_FEATURE_LIST(GMAP_FEATURE) }; static const struct { const char *uuid; const struct feature *items; size_t count; } features[] = { { TMAS_UUID_STR, tmap_features, ARRAY_SIZE(tmap_features) }, { GMAS_UUID_STR, gmap_features, ARRAY_SIZE(gmap_features) }, }; static gboolean supported_features(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct bap_ep *ep = data; DBusMessageIter dict, entry, variant, list; size_t i, j; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict); for (i = 0; i < ARRAY_SIZE(features); ++i) { for (j = 0; j < features[i].count; ++j) { const struct feature *feature = &features[i].items[j]; if (feature->probe(ep, feature->data)) break; } if (j == features[i].count) continue; dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &features[i].uuid); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "as", &variant); dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "s", &list); for (j = 0; j < features[i].count; ++j) { const struct feature *feature = &features[i].items[j]; if (!feature->probe(ep, feature->data)) continue; dbus_message_iter_append_basic(&list, DBUS_TYPE_STRING, &feature->name); } dbus_message_iter_close_container(&variant, &list); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); } dbus_message_iter_close_container(iter, &dict); return TRUE; } static const GDBusPropertyTable ep_properties[] = { { "UUID", "s", get_uuid, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Codec", "y", get_codec, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Capabilities", "ay", get_capabilities, NULL, has_capabilities, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Metadata", "ay", get_metadata, NULL, has_metadata, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Device", "o", get_device, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Locations", "u", get_locations, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "SupportedContext", "q", get_supported_context, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "Context", "q", get_context, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "QoS", "a{sv}", get_qos, NULL, qos_exists, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { "SupportedFeatures", "a{sv}", supported_features, NULL, NULL, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { } }; static int parse_array(DBusMessageIter *iter, struct iovec *iov) { DBusMessageIter array; if (!iov) return 0; dbus_message_iter_recurse(iter, &array); dbus_message_iter_get_fixed_array(&array, &iov->iov_base, (int *)&iov->iov_len); return 0; } static int parse_io_qos(const char *key, int var, DBusMessageIter *iter, struct bt_bap_io_qos *qos) { if (!strcasecmp(key, "Interval")) { if (var != DBUS_TYPE_UINT32) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->interval); } else if (!strcasecmp(key, "PHY")) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->phys); } else if (!strcasecmp(key, "SDU")) { if (var != DBUS_TYPE_UINT16) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->sdu); } else if (!strcasecmp(key, "Retransmissions")) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->rtn); } else if (!strcasecmp(key, "Latency")) { if (var != DBUS_TYPE_UINT16) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->latency); } return 0; } static int setup_parse_ucast_qos(struct bap_setup *setup, const char *key, int var, DBusMessageIter *iter) { struct bt_bap_qos *qos = &setup->qos; if (!strcasecmp(key, "CIG")) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->ucast.cig_id); } else if (!strcasecmp(key, "CIS")) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->ucast.cis_id); } else if (!strcasecmp(key, "Framing")) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->ucast.framing); } else if (!strcasecmp(key, "PresentationDelay")) { if (var != DBUS_TYPE_UINT32) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->ucast.delay); } else if (!strcasecmp(key, "TargetLatency")) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->ucast.target_latency); } else { int err; err = parse_io_qos(key, var, iter, &qos->ucast.io_qos); if (err) return err; } return 0; } static void setup_bcast_destroy(struct bap_setup *setup) { struct bt_bap_qos *qos = &setup->qos; util_iov_free(qos->bcast.bcode, 1); } static int setup_parse_bcast_qos(struct bap_setup *setup, const char *key, int var, DBusMessageIter *iter) { struct bt_bap_qos *qos = &setup->qos; if (!strcasecmp(key, "Encryption")) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->bcast.encryption); } else if (!strcasecmp(key, "BIG")) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->bcast.big); } else if (!strcasecmp(key, "Options")) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->bcast.options); } else if (!strcasecmp(key, "Skip")) { if (var != DBUS_TYPE_UINT16) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->bcast.skip); } else if (!strcasecmp(key, "SyncTimeout")) { if (var != DBUS_TYPE_UINT16) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->bcast.sync_timeout); } else if (!strcasecmp(key, "SyncType")) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->bcast.sync_cte_type); } else if (!strcasecmp(key, "SyncFactor")) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->bcast.sync_factor); } else if (!strcasecmp(key, "MSE")) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->bcast.mse); } else if (!strcasecmp(key, "Timeout")) { if (var != DBUS_TYPE_UINT16) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->bcast.timeout); } else if (!strcasecmp(key, "PresentationDelay")) { if (var != DBUS_TYPE_UINT32) return -EINVAL; dbus_message_iter_get_basic(iter, &qos->bcast.delay); } else if (!strcasecmp(key, "BCode")) { struct iovec iov; if (var != DBUS_TYPE_ARRAY) return -EINVAL; memset(&iov, 0, sizeof(iov)); parse_array(iter, &iov); if (iov.iov_len != 16) { error("Invalid size for BCode: %zu != 16", iov.iov_len); return -EINVAL; } util_iov_free(qos->bcast.bcode, 1); qos->bcast.bcode = util_iov_dup(&iov, 1); } else { int err; err = parse_io_qos(key, var, iter, &qos->bcast.io_qos); if (err) return err; } return 0; } static int setup_parse_qos(struct bap_setup *setup, DBusMessageIter *iter) { DBusMessageIter array; const char *key; dbus_message_iter_recurse(iter, &array); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter value, entry; int var, err; dbus_message_iter_recurse(&array, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); var = dbus_message_iter_get_arg_type(&value); err = setup->qos_parser(setup, key, var, &value); if (err) { DBG("Failed parsing %s", key); return err; } dbus_message_iter_next(&array); } return 0; } static int setup_parse_configuration(struct bap_setup *setup, DBusMessageIter *props) { const char *key; struct iovec iov; memset(&iov, 0, sizeof(iov)); while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter value, entry; int var; dbus_message_iter_recurse(props, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); var = dbus_message_iter_get_arg_type(&value); if (!strcasecmp(key, "Capabilities")) { if (var != DBUS_TYPE_ARRAY) goto fail; if (parse_array(&value, &iov)) goto fail; util_iov_free(setup->caps, 1); setup->caps = util_iov_dup(&iov, 1); } else if (!strcasecmp(key, "Metadata")) { if (var != DBUS_TYPE_ARRAY) goto fail; if (parse_array(&value, &iov)) goto fail; util_iov_free(setup->metadata, 1); setup->metadata = util_iov_dup(&iov, 1); } else if (!strcasecmp(key, "QoS")) { if (var != DBUS_TYPE_ARRAY) goto fail; if (setup_parse_qos(setup, &value)) goto fail; } dbus_message_iter_next(props); } return 0; fail: DBG("Failed parsing %s", key); return -EINVAL; } static void setup_ready(struct bap_setup *setup, int code, uint8_t reason) { if (!setup->readying) return; setup->readying = false; if (setup->ready_cb) { setup->ready_cb(setup, code, reason, setup->ready_cb_data); setup->ready_cb = NULL; setup->ready_cb_data = NULL; } bap_update_cigs(setup->ep->data); } static void qos_cb(struct bt_bap_stream *stream, uint8_t code, uint8_t reason, void *user_data) { struct bap_setup *setup = user_data; DBG("stream %p code 0x%02x reason 0x%02x", stream, code, reason); setup->id = 0; if (code) setup_ready(setup, code, reason); bap_update_cigs(setup->ep->data); } static int setup_qos(struct bap_setup *setup) { struct bap_data *data = setup->ep->data; struct bt_bap_stream *stream = setup->stream; if (!stream) return -EINVAL; if (setup->closing) return -EINVAL; if (bt_bap_stream_get_state(stream) != BT_BAP_STREAM_STATE_CONFIG) goto error; if (setup->id) goto error; setup_create_io(data, setup, stream, true); if (!setup->io) { error("Unable to create io"); goto error; } /* Wait QoS response to respond */ setup->id = bt_bap_stream_qos(stream, &setup->qos, qos_cb, setup); if (!setup->id) { error("Failed to Configure QoS"); goto error; } /* Bcast does not call the callback */ if (bt_bap_stream_get_type(setup->stream) == BT_BAP_STREAM_TYPE_BCAST) setup->id = 0; return 0; error: if (bt_bap_stream_get_state(stream) != BT_BAP_STREAM_STATE_RELEASING) bt_bap_stream_release(stream, NULL, NULL); return -EIO; } static void config_cb(struct bt_bap_stream *stream, uint8_t code, uint8_t reason, void *user_data) { struct bap_setup *setup = user_data; DBG("stream %p code 0x%02x reason 0x%02x", stream, code, reason); setup->id = 0; if (code) setup_ready(setup, code, reason); bap_update_cigs(setup->ep->data); } static void setup_io_close(void *data, void *user_data) { struct bap_setup *setup = data; int fd; if (setup->io_id) { g_source_remove(setup->io_id); setup->io_id = 0; } if (!setup->io) return; DBG("setup %p", setup); fd = g_io_channel_unix_get_fd(setup->io); close(fd); g_io_channel_unref(setup->io); setup->io = NULL; setup->cis_active = false; bt_bap_stream_io_connecting(setup->stream, -1); } static bool release_stream(struct bt_bap_stream *stream) { if (!stream) return true; switch (bt_bap_stream_get_state(stream)) { case BT_BAP_STREAM_STATE_IDLE: return true; case BT_BAP_STREAM_STATE_RELEASING: return false; default: bt_bap_stream_release(stream, NULL, NULL); return false; } } static int setup_close(struct bap_setup *setup, bap_setup_close_func_t cb, void *user_data) { if (setup->closing) return -EBUSY; DBG("%p", setup); setup->close_cb = cb; setup->close_cb_data = user_data; setup->closing = true; bt_bap_stream_unlock(setup->stream); if (release_stream(setup->stream)) { setup_free(setup); return 0; } return 0; } struct ep_close_data { int remaining; int count; const char *path; void (*cb)(int count, void *user_data); void *user_data; }; static void ep_close_setup_cb(struct bap_setup *setup, void *user_data) { struct ep_close_data *epdata = user_data; epdata->remaining--; DBG("closed setup %p remain %d", setup, epdata->remaining); if (epdata->remaining) return; if (epdata->cb) epdata->cb(epdata->count, epdata->user_data); free(epdata); } static void ep_close_setup(void *data, void *user_data) { struct bap_setup *setup = data; struct ep_close_data *epdata = user_data; struct bt_bap_stream *stream = setup->stream; const char *path = media_transport_stream_path(stream); if (epdata->path && (!path || strcmp(epdata->path, path))) return; epdata->remaining++; if (setup_close(setup, ep_close_setup_cb, epdata)) epdata->remaining--; else epdata->count++; } static void ep_close(struct bap_ep *ep, const char *transport_path, void (*cb)(int count, void *user_data), void *user_data) { struct ep_close_data *epdata; DBG("close ep %p path %s", ep, transport_path ? transport_path : "-"); epdata = new0(struct ep_close_data, 1); epdata->cb = cb; epdata->path = transport_path; epdata->user_data = user_data; epdata->remaining = 1; if (ep) queue_foreach(ep->setups, ep_close_setup, epdata); epdata->path = NULL; ep_close_setup_cb(NULL, epdata); } static struct bap_setup *setup_new(struct bap_ep *ep) { struct bap_setup *setup; setup = new0(struct bap_setup, 1); setup->ep = ep; /* Broadcast Source has endpoints in bcast list, Broadcast Sink * does not have endpoints */ if (((ep != NULL) && queue_find(ep->data->bcast, NULL, ep)) || (ep == NULL)) { /* Mark BIG and BIS to be auto assigned */ setup->qos.bcast.big = BT_ISO_QOS_BIG_UNSET; setup->qos.bcast.bis = BT_ISO_QOS_BIS_UNSET; setup->qos.bcast.sync_factor = BT_ISO_SYNC_FACTOR; setup->qos.bcast.sync_timeout = BT_ISO_SYNC_TIMEOUT; setup->qos.bcast.timeout = BT_ISO_SYNC_TIMEOUT; setup->qos_parser = setup_parse_bcast_qos; setup->destroy = setup_bcast_destroy; } else { /* Mark CIG and CIS to be auto assigned */ setup->qos.ucast.cig_id = BT_ISO_QOS_CIG_UNSET; setup->qos.ucast.cis_id = BT_ISO_QOS_CIS_UNSET; setup->qos_parser = setup_parse_ucast_qos; } if (ep) { if (!ep->setups) ep->setups = queue_new(); queue_push_tail(ep->setups, setup); DBG("ep %p setup %p", ep, setup); } return setup; } static void setup_free(void *data) { struct bap_setup *setup = data; bool closing = setup->closing; DBG("%p", setup); setup->closing = true; setup_ready(setup, -ECANCELED, 0); if (closing && setup->close_cb) setup->close_cb(setup, setup->close_cb_data); if (setup->stream && setup->id) { bt_bap_stream_cancel(setup->stream, setup->id); setup->id = 0; } if (setup->ep) queue_remove(setup->ep->setups, setup); if (setup->data) queue_remove(setup->data->bcast_snks, setup); setup_io_close(setup, NULL); util_iov_free(setup->caps, 1); util_iov_free(setup->metadata, 1); util_iov_free(setup->base, 1); if (setup->destroy) setup->destroy(setup); bt_bap_stream_unlock(setup->stream); if (!closing) { /* Release if not already done */ release_stream(setup->stream); } if (setup->ep) bap_update_cigs(setup->ep->data); free(setup); } static bool match_io_qos(const struct bt_bap_io_qos *io_qos, const struct bt_bap_io_qos *match) { if (io_qos->interval != match->interval) return false; if (io_qos->latency != match->latency) return false; if (io_qos->sdu != match->sdu) return false; if (io_qos->phys != match->phys) return false; if (io_qos->rtn != match->rtn) return false; return true; } static bool match_bcast_qos(const struct bt_bap_bcast_qos *qos, const struct bt_bap_bcast_qos *match) { if (qos->sync_factor != match->sync_factor) return false; if (qos->packing != match->packing) return false; if (qos->framing != match->framing) return false; if (qos->encryption != match->encryption) return false; if (qos->encryption && util_iov_memcmp(qos->bcode, match->bcode)) return false; if (qos->options != match->options) return false; if (qos->skip != match->skip) return false; if (qos->sync_timeout != match->sync_timeout) return false; if (qos->sync_cte_type != match->sync_cte_type) return false; if (qos->mse != match->mse) return false; if (qos->timeout != match->timeout) return false; if (qos->pa_sync != match->pa_sync) return false; return match_io_qos(&qos->io_qos, &match->io_qos); } static bool setup_mismatch_qos(const void *data, const void *user_data) { const struct bap_setup *setup = data; const struct bap_setup *match = user_data; /* Match setups that are part of the same BIG */ if (setup == match || setup->qos.bcast.big == BT_ISO_QOS_BIG_UNSET || setup->qos.bcast.big != match->qos.bcast.big) return false; return !match_bcast_qos(&setup->qos.bcast, &match->qos.bcast); } struct set_configuration_data { struct bap_setup *setup; DBusMessage *msg; }; static void set_configuration_ready(struct bap_setup *setup, int code, uint8_t reason, void *user_data) { struct set_configuration_data *data = user_data; DBusMessage *reply; if (!code) reply = dbus_message_new_method_return(data->msg); else if (code == -ECANCELED) reply = btd_error_failed(data->msg, "Canceled"); else reply = btd_error_failed(data->msg, "Unable to configure"); g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(data->msg); free(data); } static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg, void *data) { struct bap_ep *ep = data; struct bap_setup *setup; const char *path; DBusMessageIter args, props; struct set_configuration_data *cbdata; dbus_message_iter_init(msg, &args); dbus_message_iter_get_basic(&args, &path); dbus_message_iter_next(&args); dbus_message_iter_recurse(&args, &props); if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) return btd_error_invalid_args(msg); setup = setup_new(ep); if (setup_parse_configuration(setup, &props) < 0) { DBG("Unable to parse configuration"); setup_free(setup); return btd_error_invalid_args(msg); } if (bt_bap_pac_get_type(ep->lpac) == BT_BAP_BCAST_SOURCE) /* All streams in a BIG should have the same QoS. * Check that the new configuration matches previous ones. */ if (queue_find(setup->ep->setups, setup_mismatch_qos, setup)) { setup_free(setup); return btd_error_invalid_args(msg); } cbdata = new0(struct set_configuration_data, 1); cbdata->setup = setup; cbdata->msg = dbus_message_ref(msg); if (setup_config(setup, set_configuration_ready, cbdata)) { DBG("Unable to config stream"); setup_free(setup); free(cbdata); return btd_error_invalid_args(msg); } switch (bt_bap_stream_get_type(setup->stream)) { case BT_BAP_STREAM_TYPE_BCAST: if (ep->data->service) service_set_connecting(ep->data->service); break; } return NULL; } struct clear_configuration_data { DBusMessage *msg; bool all; }; static void clear_configuration_cb(int count, void *user_data) { struct clear_configuration_data *data = user_data; DBusMessage *reply; DBG("%p", data); if (!data->all && count == 0) reply = btd_error_invalid_args(data->msg); else reply = dbus_message_new_method_return(data->msg); g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(data->msg); free(data); } static DBusMessage *clear_configuration(DBusConnection *conn, DBusMessage *msg, void *data) { struct bap_ep *ep = data; const char *path; struct clear_configuration_data *cbdata; DBusMessageIter args; dbus_message_iter_init(msg, &args); dbus_message_iter_get_basic(&args, &path); if (strcmp(path, ep->path) == 0) path = NULL; cbdata = new0(struct clear_configuration_data, 1); cbdata->msg = dbus_message_ref(msg); cbdata->all = (path == NULL); DBG("%p %s %s", cbdata, ep->path, path ? path : "NULL"); ep_close(ep, path, clear_configuration_cb, cbdata); return NULL; } static int reconfigure_parse(DBusMessageIter *props, bool *defer) { const char *key; if (dbus_message_iter_get_arg_type(props) != DBUS_TYPE_DICT_ENTRY) return -EINVAL; while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter value, entry; int var; dbus_message_iter_recurse(props, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); var = dbus_message_iter_get_arg_type(&value); if (!strcasecmp(key, "Defer")) { dbus_bool_t flag; if (var != DBUS_TYPE_BOOLEAN) goto fail; dbus_message_iter_get_basic(&value, &flag); *defer = flag; } dbus_message_iter_next(props); } return 0; fail: DBG("Failed parsing %s", key); return -EINVAL; } struct reconfigure_data { int remaining; struct bap_data *data; DBusMessage *msg; }; static void reconfigure_select_cb(int err, void *user_data) { struct reconfigure_data *data = user_data; DBusMessage *reply; if (!err) reply = dbus_message_new_method_return(data->msg); else reply = btd_error_failed(data->msg, "Failed to configure"); g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(data->msg); free(data); } static void reconfigure_close_cb(int count, void *user_data) { struct reconfigure_data *data = user_data; data->remaining--; DBG("remain %d", data->remaining); if (data->remaining) return; bap_select_all(data->data, true, reconfigure_select_cb, data); } static void ep_close_if_reconfigure(void *obj, void *user_data) { struct bap_ep *ep = obj; struct reconfigure_data *data = user_data; if (ep->reconfigure) { data->remaining++; ep_close(ep, NULL, reconfigure_close_cb, data); } } static DBusMessage *reconfigure(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct bap_ep *ep = user_data; struct bap_data *data = ep->data; struct reconfigure_data *cbdata; bool defer = false; DBusMessageIter args, props; switch (bt_bap_pac_get_type(ep->lpac)) { case BT_BAP_SOURCE: case BT_BAP_SINK: break; default: return btd_error_invalid_args(msg); } dbus_message_iter_init(msg, &args); dbus_message_iter_recurse(&args, &props); if (reconfigure_parse(&props, &defer)) return btd_error_invalid_args(msg); DBG("%s defer %d", ep->path, (int)defer); ep->reconfigure = true; if (defer) return dbus_message_new_method_return(msg); cbdata = new0(struct reconfigure_data, 1); cbdata->data = ep->data; cbdata->msg = dbus_message_ref(msg); cbdata->remaining = 1; queue_foreach(data->snks, ep_close_if_reconfigure, cbdata); queue_foreach(data->srcs, ep_close_if_reconfigure, cbdata); reconfigure_close_cb(0, cbdata); return NULL; } static bool stream_io_unset(const void *data, const void *user_data) { struct bt_bap_stream *stream = (struct bt_bap_stream *)data; return !bt_bap_stream_get_io(stream); } static void iso_bcast_confirm_cb(GIOChannel *io, GError *err, void *user_data) { struct bap_setup *setup = user_data; struct bt_bap_stream *stream = setup->stream; int fd; struct bap_data *bap_data = setup->data; if (err) error("BIG Sync failed: %s", err->message); else DBG("BIG Sync completed"); /* The order of the BIS fds notified from kernel corresponds * to the order of the BISes that were enqueued before * calling bt_io_bcast_accept. */ if (bt_bap_stream_get_io(stream)) stream = queue_find(bt_bap_stream_io_get_links(stream), stream_io_unset, NULL); fd = g_io_channel_unix_get_fd(io); if (bt_bap_stream_set_io(stream, fd)) g_io_channel_set_close_on_unref(io, FALSE); if (!queue_find(bt_bap_stream_io_get_links(stream), stream_io_unset, NULL)) { /* All fds have been notified. Mark service as connected. */ btd_service_connecting_complete(bap_data->service, 0); g_io_channel_unref(bap_data->listen_io); g_io_channel_shutdown(bap_data->listen_io, TRUE, NULL); bap_data->listen_io = NULL; } } static void create_stream_for_bis(struct bap_data *bap_data, struct bt_bap_pac *lpac, uint8_t sid, uint8_t bis, uint8_t sgrp, struct bt_bap_qos *qos, struct iovec *caps, struct iovec *meta, char *path) { struct bap_setup *setup; setup = setup_new(NULL); setup->qos = *qos; /* Create an internal copy for bcode */ setup->qos.bcast.bcode = util_iov_dup(qos->bcast.bcode, 1); setup->qos.bcast.big = sgrp; setup->qos.bcast.bis = bis; setup->data = bap_data; queue_push_tail(bap_data->bcast_snks, setup); /* Create and configure stream */ setup->stream = bt_bap_stream_new(bap_data->bap, lpac, NULL, &setup->qos, caps); bt_bap_stream_lock(setup->stream); setup->sid = sid; bt_bap_stream_set_user_data(setup->stream, path); bt_bap_stream_config(setup->stream, &setup->qos, caps, NULL, NULL); bt_bap_stream_metadata(setup->stream, meta, NULL, NULL); } static void bis_handler(uint8_t sid, uint8_t bis, uint8_t sgrp, struct iovec *caps, struct iovec *meta, struct bt_bap_qos *qos, void *user_data) { struct bap_data *data = user_data; struct bt_bap_pac *lpac; char *path; bt_bap_bis_probe(data->bap, sid, bis, sgrp, caps, meta, qos); /* Check if this BIS matches any local PAC */ bt_bap_verify_bis(data->bap, bis, caps, &lpac); if (!lpac) return; if (asprintf(&path, "%s/sid%d/bis%d", device_get_path(data->device), sid, bis) < 0) return; create_stream_for_bis(data, lpac, sid, bis, sgrp, qos, caps, meta, path); } static gboolean big_info_report_cb(GIOChannel *io, GIOCondition cond, gpointer user_data) { GError *err = NULL; struct bap_data *data = user_data; struct bt_iso_base base; struct bt_iso_qos qos; struct iovec iov; struct bt_bap_qos bap_qos = {0}; uint8_t sid; DBG("BIG Info received"); bt_io_get(io, &err, BT_IO_OPT_BASE, &base, BT_IO_OPT_QOS, &qos, BT_IO_OPT_ISO_BC_SID, &sid, BT_IO_OPT_INVALID); if (err) { error("%s", err->message); g_error_free(err); g_io_channel_shutdown(io, TRUE, NULL); data->io_id = 0; return FALSE; } /* Close the listen io */ g_io_channel_shutdown(data->listen_io, TRUE, NULL); g_io_channel_unref(data->listen_io); data->listen_io = NULL; /* For short-lived PA, the sync is no longer needed at * this point, so the io can be closed. */ g_io_channel_shutdown(io, TRUE, NULL); /* Analyze received BASE data and create remote media endpoints for each * BIS matching our capabilities */ iov.iov_base = base.base; iov.iov_len = base.base_len; /* Create BAP QoS structure */ bt_bap_iso_qos_to_bap_qos(&qos, &bap_qos); bt_bap_parse_base(sid, &iov, &bap_qos, bap_debug, bis_handler, data); util_iov_free(bap_qos.bcast.bcode, 1); service_set_connecting(data->service); data->io_id = 0; return FALSE; } static void iso_pa_sync_confirm_cb(GIOChannel *io, void *user_data) { struct bap_data *data = user_data; /* PA Sync was established, wait for BIG Info report so that the * encryption flag is also available. */ DBG("PA Sync done"); data->io_id = g_io_add_watch(io, G_IO_OUT, big_info_report_cb, user_data); } static bool match_adapter(const void *data, const void *match_data) { const struct bap_data *bdata = data; const struct btd_adapter *adapter = match_data; return bdata->adapter == adapter; } static const GDBusMethodTable ep_methods[] = { { GDBUS_EXPERIMENTAL_ASYNC_METHOD("SetConfiguration", GDBUS_ARGS({ "endpoint", "o" }, { "Configuration", "a{sv}" } ), NULL, set_configuration) }, { GDBUS_EXPERIMENTAL_ASYNC_METHOD("ClearConfiguration", GDBUS_ARGS({ "transport", "o" }), NULL, clear_configuration) }, { GDBUS_EXPERIMENTAL_ASYNC_METHOD("Reconfigure", GDBUS_ARGS( { "properties", "a{sv}" }), NULL, reconfigure) }, { }, }; static void ep_cancel_select(struct bap_ep *ep); static void ep_free(void *data) { struct bap_ep *ep = data; struct queue *setups = ep->setups; ep->setups = NULL; queue_destroy(setups, setup_free); ep_cancel_select(ep); free(ep->path); free(ep); } struct match_ep { struct bt_bap_pac *lpac; struct bt_bap_pac *rpac; }; static bool match_ep(const void *data, const void *user_data) { const struct bap_ep *ep = data; const struct match_ep *match = user_data; if (ep->lpac != match->lpac) return false; return ep->rpac == match->rpac; } static struct bap_ep *ep_register_bcast(struct bap_data *data, struct bt_bap_pac *lpac, struct bt_bap_pac *rpac) { struct btd_adapter *adapter = data->adapter; struct btd_device *device = data->device; struct bap_ep *ep; struct queue *queue; int i, err = 0; const char *suffix; struct match_ep match = { lpac, rpac }; switch (bt_bap_pac_get_type(lpac)) { case BT_BAP_BCAST_SOURCE: case BT_BAP_BCAST_SINK: queue = data->bcast; i = queue_length(data->bcast); suffix = "bcast"; break; default: return NULL; } ep = queue_find(queue, match_ep, &match); if (ep) return ep; ep = new0(struct bap_ep, 1); ep->data = data; ep->lpac = lpac; ep->rpac = rpac; if (device) ep->data->device = device; switch (bt_bap_pac_get_type(lpac)) { case BT_BAP_BCAST_SOURCE: err = asprintf(&ep->path, "%s/pac_%s%d", adapter_get_path(adapter), suffix, i); break; case BT_BAP_BCAST_SINK: err = asprintf(&ep->path, "%s/pac_%s%d", device_get_path(device), suffix, i); break; } if (err < 0) { error("Could not allocate path for remote pac %s/pac%d", adapter_get_path(adapter), i); free(ep); return NULL; } if (g_dbus_register_interface(btd_get_dbus_connection(), ep->path, MEDIA_ENDPOINT_INTERFACE, ep_methods, NULL, ep_properties, ep, ep_free) == FALSE) { error("Could not register remote ep %s", ep->path); ep_free(ep); return NULL; } /* * The broadcast source local endpoint has only lpac and broadcast * sink local endpoint has a rpac and a lpac */ if (rpac) bt_bap_pac_set_user_data(rpac, ep->path); DBG("ep %p lpac %p rpac %p path %s", ep, ep->lpac, ep->rpac, ep->path); queue_push_tail(queue, ep); return ep; } static void ep_update_properties(struct bap_ep *ep) { if (!ep->rpac) return; if (ep->locations != bt_bap_pac_get_locations(ep->rpac)) g_dbus_emit_property_changed(btd_get_dbus_connection(), ep->path, MEDIA_ENDPOINT_INTERFACE, "Locations"); if (ep->supported_context != bt_bap_pac_get_supported_context(ep->rpac)) g_dbus_emit_property_changed(btd_get_dbus_connection(), ep->path, MEDIA_ENDPOINT_INTERFACE, "SupportedContext"); if (ep->context != bt_bap_pac_get_context(ep->rpac)) g_dbus_emit_property_changed(btd_get_dbus_connection(), ep->path, MEDIA_ENDPOINT_INTERFACE, "Context"); } static struct bap_ep *ep_register(struct btd_service *service, struct bt_bap_pac *lpac, struct bt_bap_pac *rpac) { struct btd_device *device = btd_service_get_device(service); struct bap_data *data = btd_service_get_user_data(service); struct bap_ep *ep; struct queue *queue; int i, err; const char *suffix; struct match_ep match = { lpac, rpac }; switch (bt_bap_pac_get_type(rpac)) { case BT_BAP_SINK: queue = data->snks; i = queue_length(data->snks); suffix = "sink"; break; case BT_BAP_SOURCE: queue = data->srcs; i = queue_length(data->srcs); suffix = "source"; break; default: return NULL; } ep = queue_find(queue, match_ep, &match); if (ep) { ep_update_properties(ep); return ep; } ep = new0(struct bap_ep, 1); ep->data = data; ep->lpac = lpac; ep->rpac = rpac; err = asprintf(&ep->path, "%s/pac_%s%d", device_get_path(device), suffix, i); if (err < 0) { error("Could not allocate path for remote pac %s/pac%d", device_get_path(device), i); free(ep); return NULL; } if (g_dbus_register_interface(btd_get_dbus_connection(), ep->path, MEDIA_ENDPOINT_INTERFACE, ep_methods, NULL, ep_properties, ep, ep_free) == FALSE) { error("Could not register remote ep %s", ep->path); ep_free(ep); return NULL; } bt_bap_pac_set_user_data(rpac, ep->path); DBG("ep %p lpac %p rpac %p path %s", ep, ep->lpac, ep->rpac, ep->path); queue_push_tail(queue, ep); return ep; } static int setup_config(struct bap_setup *setup, bap_setup_ready_func_t cb, void *user_data) { struct bap_ep *ep = setup->ep; if (setup->readying) return -EBUSY; if (setup->closing) return -EINVAL; DBG("setup %p caps %p metadata %p", setup, setup->caps, setup->metadata); /* TODO: Check if stream capabilities match add support for Latency * and PHY. */ if (!setup->stream) { setup->stream = bt_bap_stream_new(ep->data->bap, ep->lpac, ep->rpac, &setup->qos, setup->caps); bt_bap_stream_lock(setup->stream); } bt_bap_stream_set_user_data(setup->stream, ep->path); setup->id = bt_bap_stream_config(setup->stream, &setup->qos, setup->caps, config_cb, setup); if (!setup->id) return -EINVAL; switch (bt_bap_stream_get_type(setup->stream)) { case BT_BAP_STREAM_TYPE_UCAST: setup->config_pending = true; break; case BT_BAP_STREAM_TYPE_BCAST: /* Broadcast does not call the callback */ setup->id = 0; cb(setup, 0, 0, user_data); break; } if (setup->metadata && setup->metadata->iov_len) bt_bap_stream_metadata(setup->stream, setup->metadata, NULL, NULL); /* Don't set ready* field if there is no callback pending */ if (!setup->id) return 0; setup->readying = true; setup->ready_cb = cb; setup->ready_cb_data = user_data; return 0; } static void bap_config_setup_cb(struct bap_setup *setup, int code, uint8_t reason, void *user_data) { struct bap_select *select = user_data; select->remaining--; DBG("setup %p code %d remain %d", setup, code, select->remaining); if (code) select->err = code; if (select->remaining) return; if (select->done_cb) select->done_cb(select->err, select->done_cb_data); free(select); } static void bap_config_setup(void *item, void *user_data) { struct bap_setup *setup = item; struct bap_select *select = user_data; select->remaining++; if (setup_config(setup, bap_config_setup_cb, select)) { DBG("Unable to config stream"); setup_free(setup); select->remaining--; } } static void bap_config(void *data, void *user_data) { struct bap_ep *ep = data; queue_foreach(ep->setups, bap_config_setup, user_data); } static void pac_select_clear_ep(void *data) { struct bap_ep *ep = data; ep->select = NULL; } static void bap_select_complete(struct bap_select *select) { select->remaining--; DBG("selecting %d", select->remaining); if (select->remaining) return; DBG("configure (err %d)", select->err); queue_destroy(select->eps, pac_select_clear_ep); select->remaining++; if (!select->err) { queue_foreach(select->data->srcs, bap_config, select); queue_foreach(select->data->snks, bap_config, select); } bap_config_setup_cb(NULL, 0, 0, select); } static void select_cb(struct bt_bap_pac *pac, int err, struct iovec *caps, struct iovec *metadata, struct bt_bap_qos *qos, void *user_data) { struct bap_ep *ep = user_data; struct bap_select *select = ep->select; struct bap_setup *setup; if (err) { error("err %d", err); goto done; } setup = setup_new(ep); setup->caps = util_iov_dup(caps, 1); setup->metadata = util_iov_dup(metadata, 1); setup->qos = *qos; done: bap_select_complete(select); } static bool pac_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, void *user_data) { struct bap_select *select = user_data; struct bap_data *data = select->data; struct match_ep match = { lpac, rpac }; struct queue *queue; struct bap_ep *ep; switch (bt_bap_pac_get_type(rpac)) { case BT_BAP_SINK: queue = data->snks; break; case BT_BAP_SOURCE: queue = data->srcs; break; default: return true; } ep = queue_find(queue, match_ep, &match); if (!ep) { error("Unable to find endpoint for pac %p", rpac); return true; } if (ep->select && ep->select != select) { select->err = -EBUSY; return true; } if (select->reconfigure && !ep->reconfigure) return true; ep->reconfigure = false; /* TODO: Cache LRU? */ if (!ep->select) { ep->select = select; queue_push_tail(select->eps, ep); } bt_bap_select(data->bap, lpac, rpac, 0, &select->remaining, select_cb, ep); /* For initial configuration consider only one endpoint (for each * direction). */ return select->reconfigure; } static int bap_select_all(struct bap_data *data, bool reconfigure, bap_select_done_t cb, void *user_data) { struct bap_select *select; if (!btd_service_is_initiator(data->service)) return -EINVAL; DBG("data %p reconfig %d", data, (int)reconfigure); select = new0(struct bap_select, 1); select->reconfigure = reconfigure; select->remaining = 1; select->data = data; select->eps = queue_new(); select->done_cb = cb; select->done_cb_data = user_data; bt_bap_foreach_pac(data->bap, BT_BAP_SOURCE, pac_select, select); bt_bap_foreach_pac(data->bap, BT_BAP_SINK, pac_select, select); bap_select_complete(select); return 0; } static bool pac_register(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, void *user_data) { struct btd_service *service = user_data; struct bap_ep *ep; DBG("lpac %p rpac %p", lpac, rpac); ep = ep_register(service, lpac, rpac); if (!ep) error("Unable to register endpoint for pac %p", rpac); return true; } static bool pac_cancel_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, void *user_data) { struct bap_ep *ep = user_data; bt_bap_cancel_select(lpac, select_cb, ep); return true; } static void ep_cancel_select(struct bap_ep *ep) { struct bt_bap *bap = ep->data->bap; struct bap_select *select; bt_bap_foreach_pac(bap, BT_BAP_SOURCE, pac_cancel_select, ep); bt_bap_foreach_pac(bap, BT_BAP_SINK, pac_cancel_select, ep); select = ep->select; if (select) { queue_remove(select->eps, ep); ep->select = NULL; } } static bool pac_found_bcast(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, void *user_data) { struct bap_data *data = user_data; struct bap_ep *ep; DBG("lpac %p rpac %p", lpac, rpac); ep = ep_register_bcast(user_data, lpac, rpac); if (!ep) { error("Unable to register endpoint for pac %p", rpac); return true; } /* Mark the device as connetable if an Endpoint is registered */ if (data->device) btd_device_set_connectable(data->device, true); return true; } static void bap_ucast_start(struct bap_data *data) { struct btd_service *service = data->service; struct bt_bap *bap = data->bap; DBG("bap %p", bap); /* Register all ep before selecting, so that sound server * knows all. */ bt_bap_foreach_pac(bap, BT_BAP_SOURCE, pac_register, service); bt_bap_foreach_pac(bap, BT_BAP_SINK, pac_register, service); bap_select_all(data, false, NULL, NULL); } static bool match_setup_stream(const void *data, const void *user_data) { const struct bap_setup *setup = data; const struct bt_bap_stream *stream = user_data; return setup->stream == stream; } static bool match_ep_stream(const void *data, const void *user_data) { const struct bap_ep *ep = data; const struct bt_bap_stream *stream = user_data; return queue_find(ep->setups, match_setup_stream, stream); } static struct bap_setup *bap_find_setup_by_stream(struct bap_data *data, struct bt_bap_stream *stream) { struct bap_ep *ep = NULL; struct queue *queue = NULL; switch (bt_bap_stream_get_type(stream)) { case BT_BAP_STREAM_TYPE_UCAST: ep = queue_find(data->snks, match_ep_stream, stream); if (!ep) ep = queue_find(data->srcs, match_ep_stream, stream); break; case BT_BAP_STREAM_TYPE_BCAST: ep = queue_find(data->bcast, match_ep_stream, stream); break; } if (ep) queue = ep->setups; else queue = data->bcast_snks; return queue_find(queue, match_setup_stream, stream); } static void iso_connect_bcast_cb(GIOChannel *chan, GError *err, gpointer user_data) { struct bt_bap_stream *stream = user_data; int fd; if (err) { error("%s", err->message); bt_bap_stream_set_io(stream, -1); return; } DBG("ISO connected"); fd = g_io_channel_unix_get_fd(chan); if (bt_bap_stream_set_io(stream, fd)) { bt_bap_stream_start(stream, NULL, NULL); g_io_channel_set_close_on_unref(chan, FALSE); return; } error("Unable to set IO"); bt_bap_stream_set_io(stream, -1); } static void iso_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) { struct bt_bap_stream *stream = user_data; int fd; if (err) { error("%s", err->message); bt_bap_stream_set_io(stream, -1); return; } DBG("ISO connected"); fd = g_io_channel_unix_get_fd(chan); if (bt_bap_stream_set_io(stream, fd)) { g_io_channel_set_close_on_unref(chan, FALSE); return; } error("Unable to set IO"); bt_bap_stream_set_io(stream, -1); } static void bap_iso_qos(struct bt_bap_qos *qos, struct bt_iso_io_qos *io) { if (!qos) return; io->interval = qos->ucast.io_qos.interval; io->latency = qos->ucast.io_qos.latency; io->sdu = qos->ucast.io_qos.sdu; io->phys = qos->ucast.io_qos.phys; io->rtn = qos->ucast.io_qos.rtn; } static bool match_stream_qos(const void *data, const void *user_data) { const struct bt_bap_stream *stream = data; const struct bt_iso_qos *iso_qos = user_data; struct bt_bap_qos *qos; qos = bt_bap_stream_get_qos((void *)stream); if (iso_qos->ucast.cig != qos->ucast.cig_id) return false; return iso_qos->ucast.cis == qos->ucast.cis_id; } static void iso_confirm_cb(GIOChannel *io, void *user_data) { struct bap_data *data = user_data; struct bt_bap_stream *stream; struct bt_iso_qos qos; char address[18]; GError *err = NULL; bt_io_get(io, &err, BT_IO_OPT_DEST, address, BT_IO_OPT_QOS, &qos, BT_IO_OPT_INVALID); if (err) { error("%s", err->message); g_error_free(err); goto drop; } DBG("ISO: incoming connect from %s (CIG 0x%02x CIS 0x%02x)", address, qos.ucast.cig, qos.ucast.cis); stream = queue_remove_if(data->server_streams, match_stream_qos, &qos); if (!stream) { error("No matching stream found"); goto drop; } if (!bt_io_accept(io, iso_connect_cb, stream, NULL, &err)) { error("bt_io_accept: %s", err->message); g_error_free(err); goto drop; } return; drop: g_io_channel_shutdown(io, TRUE, NULL); } static void setup_accept_io(struct bap_setup *setup, struct bt_bap_stream *stream, int fd, int defer) { char c; struct pollfd pfd; socklen_t len; if (fd < 0 || defer) return; /* Check if socket has DEFER_SETUP set */ len = sizeof(defer); if (getsockopt(fd, SOL_BLUETOOTH, BT_DEFER_SETUP, &defer, &len) < 0) /* Ignore errors since the fd may be connected already */ return; if (!defer) return; DBG("stream %p fd %d defer %s", stream, fd, defer ? "true" : "false"); memset(&pfd, 0, sizeof(pfd)); pfd.fd = fd; pfd.events = POLLOUT; if (poll(&pfd, 1, 0) < 0) { error("poll: %s (%d)", strerror(errno), errno); goto fail; } if (!(pfd.revents & POLLOUT)) { if (read(fd, &c, 1) < 0) { error("read: %s (%d)", strerror(errno), errno); goto fail; } } setup->cis_active = true; return; fail: close(fd); } struct find_cig_data { const struct btd_adapter *adapter; bool (*func)(const void *data, const void *match_data); struct bap_setup *found; struct queue *cigs; uint8_t cig; }; static bool find_cig_readying_setup(const void *data, const void *match_data) { struct bap_setup *setup = (void *)data; struct find_cig_data *info = (void *)match_data; struct bt_bap_stream *stream = setup->stream; struct bt_bap_qos *qos = bt_bap_stream_get_qos(stream); /* Streams with automatically assigned CIG are considered to potentially * belong to any CIG. */ if (qos && qos->ucast.cig_id != info->cig && info->cig != BT_ISO_QOS_CIG_UNSET && qos->ucast.cig_id != BT_ISO_QOS_CIG_UNSET) return false; return setup->readying || setup->closing || setup->config_pending || setup->id; } static bool find_cig_busy_setup(const void *data, const void *match_data) { const struct bap_setup *setup = data; struct find_cig_data *info = (void *)match_data; struct bt_bap_stream *stream = setup->stream; struct bt_bap_qos *qos = bt_bap_stream_get_qos(stream); if (qos && qos->ucast.cig_id != info->cig && info->cig != BT_ISO_QOS_CIG_UNSET && qos->ucast.cig_id != BT_ISO_QOS_CIG_UNSET) return false; return setup->cis_active || setup->closing || setup->config_pending || setup->id; } static bool find_cig_enumerate_setup(const void *data, const void *match_data) { const struct bap_setup *setup = data; struct find_cig_data *info = (void *)match_data; struct bt_bap_stream *stream = setup->stream; struct bt_bap_qos *qos = bt_bap_stream_get_qos(stream); if (qos && info->cigs) { queue_remove(info->cigs, UINT_TO_PTR(qos->ucast.cig_id)); queue_push_tail(info->cigs, UINT_TO_PTR(qos->ucast.cig_id)); } return false; } static bool find_cig_ep(const void *data, const void *match_data) { const struct bap_ep *ep = data; struct find_cig_data *info = (void *)match_data; info->found = queue_find(ep->setups, info->func, match_data); return info->found; } static bool find_cig_session(const void *data, const void *match_data) { const struct bap_data *session = data; const struct find_cig_data *info = match_data; if (device_get_adapter(session->device) != info->adapter) return false; return queue_find(session->snks, find_cig_ep, match_data) || queue_find(session->srcs, find_cig_ep, match_data); } static struct bap_setup *find_cig_busy(struct bap_data *data, uint8_t cig) { struct find_cig_data info = { .adapter = device_get_adapter(data->device), .cig = cig, .func = find_cig_busy_setup, }; queue_find(sessions, find_cig_session, &info); return info.found; } static struct bap_setup *find_cig_readying(struct bap_data *data, uint8_t cig) { struct find_cig_data info = { .adapter = device_get_adapter(data->device), .cig = cig, .func = find_cig_readying_setup, }; queue_find(sessions, find_cig_session, &info); return info.found; } static struct queue *find_cig_enumerate(struct bap_data *data) { struct find_cig_data info = { .adapter = device_get_adapter(data->device), .func = find_cig_enumerate_setup, .cigs = queue_new(), }; queue_find(sessions, find_cig_session, &info); return info.cigs; } struct update_cig_data { struct btd_adapter *adapter; void (*func)(void *data, void *match_data); uint8_t cig; unsigned int count; }; static void update_cig_setup_enable(void *data, void *match_data) { struct bap_setup *setup = data; struct update_cig_data *info = match_data; struct bt_bap_stream *stream = setup->stream; struct bt_bap_qos *qos = bt_bap_stream_get_qos(stream); if (qos && qos->ucast.cig_id != info->cig) return; if (!stream || !setup->io || setup->closing || setup->cis_active) return; if (!setup->want_cis) return; if (bt_bap_stream_get_state(stream) != BT_BAP_STREAM_STATE_ENABLING) return; DBG("%p", setup); setup->want_cis = false; setup_create_io(setup->ep->data, setup, setup->stream, false); info->count++; } static void update_cig_setup_io(void *data, void *match_data) { struct bap_setup *setup = data; struct update_cig_data *info = match_data; struct bt_bap_stream *stream = setup->stream; struct bt_bap_qos *qos = bt_bap_stream_get_qos(stream); if (qos && qos->ucast.cig_id != info->cig) return; if (!setup->want_io || !stream || setup->io || setup->closing) return; if (bt_bap_stream_get_state(stream) != BT_BAP_STREAM_STATE_QOS) return; DBG("%p", setup); setup->want_io = false; setup_create_io(setup->ep->data, setup, setup->stream, true); info->count++; } static void update_cig_setup_qos(void *data, void *match_data) { struct bap_setup *setup = data; struct update_cig_data *info = match_data; struct bt_bap_stream *stream = setup->stream; int err; struct bt_bap_qos *qos = bt_bap_stream_get_qos(stream); if (qos && qos->ucast.cig_id != info->cig) return; if (!setup->want_qos || !stream || setup->closing) return; if (bt_bap_stream_get_state(stream) != BT_BAP_STREAM_STATE_CONFIG) return; DBG("%p", setup); setup->want_qos = false; err = setup_qos(setup); if (err) setup_ready(setup, err, 0); else info->count++; } static void update_cig_check_ep(void *data, void *match_data) { struct bap_ep *ep = data; struct update_cig_data *info = match_data; queue_foreach(ep->setups, info->func, match_data); } static void update_cig_check_session(void *data, void *match_data) { struct bap_data *session = data; struct update_cig_data *info = match_data; if (device_get_adapter(session->device) != info->adapter) return; queue_foreach(session->snks, update_cig_check_ep, match_data); queue_foreach(session->srcs, update_cig_check_ep, match_data); } static void bap_update_cig(void *item, void *user_data) { unsigned int cig = PTR_TO_UINT(item); struct bap_data *data = user_data; struct update_cig_data info; struct bap_setup *setup; info.adapter = device_get_adapter(data->device); info.count = 0; info.cig = cig; DBG("adapter %p CIG 0x%x", info.adapter, info.cig); /* Do stream QoS & IO re-creation only when CIG is no longer * busy and all pending Config/QoS requests have completed. */ setup = find_cig_busy(data, cig); if (!setup) { /* Recreate IO before QoS of new streams, so that we reserve * their CIS IDs in kernel before allocating new streams. */ info.func = update_cig_setup_io; queue_foreach(sessions, update_cig_check_session, &info); info.func = update_cig_setup_qos; queue_foreach(sessions, update_cig_check_session, &info); } else { DBG("setup %p stream %p busy", setup, setup->stream); } /* Do CIS creation only after all setups have finished readying. */ setup = find_cig_readying(data, cig); if (!setup) { info.func = update_cig_setup_enable; queue_foreach(sessions, update_cig_check_session, &info); } else { DBG("setup %p stream %p readying", setup, setup->stream); } } static gboolean bap_update_cigs_cb(void *user_data) { struct bap_data *data = user_data; struct queue *cigs; bool unset; data->cig_update_id = 0; cigs = find_cig_enumerate(data); unset = queue_remove(cigs, UINT_TO_PTR(BT_ISO_QOS_CIG_UNSET)); queue_foreach(cigs, bap_update_cig, data); queue_destroy(cigs, NULL); /* Handle streams with unset CIG last, so that kernel CIG allocation * knows which IDs are reserved. */ if (unset) bap_update_cig(UINT_TO_PTR(BT_ISO_QOS_CIG_UNSET), data); return FALSE; } static void bap_update_cigs(struct bap_data *data) { if (data->cig_update_id) return; data->cig_update_id = g_idle_add(bap_update_cigs_cb, data); } static void setup_io_disconnected(int cond, void *user_data) { struct bap_setup *setup = user_data; setup->io_id = 0; DBG("%p", setup); setup_io_close(setup, NULL); bap_update_cigs(setup->ep->data); } static void bap_connect_bcast_io_cb(GIOChannel *chan, GError *err, gpointer user_data) { struct bap_setup *setup = user_data; if (!setup->stream) return; iso_connect_bcast_cb(chan, err, setup->stream); } static void bap_connect_io_cb(GIOChannel *chan, GError *err, gpointer user_data) { struct bap_setup *setup = user_data; if (!setup->stream) return; iso_connect_cb(chan, err, setup->stream); } static void setup_connect_io(struct bap_data *data, struct bap_setup *setup, struct bt_bap_stream *stream, struct bt_iso_qos *qos, int defer) { struct btd_adapter *adapter = device_get_adapter(data->device); GIOChannel *io; GError *err = NULL; int fd; /* If IO already set skip creating it again */ if (bt_bap_stream_get_io(stream)) { DBG("setup %p stream %p has existing io", setup, stream); return; } if (bt_bap_stream_io_is_connecting(stream, &fd)) { setup_accept_io(setup, stream, fd, defer); return; } /* If IO channel still up */ if (setup->io) { DBG("setup %p stream %p io already up", setup, stream); return; } if (setup->io_id) { g_source_remove(setup->io_id); setup->io_id = 0; } DBG("setup %p stream %p defer %s", setup, stream, defer ? "true" : "false"); io = bt_io_connect(bap_connect_io_cb, setup, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, btd_adapter_get_address(adapter), BT_IO_OPT_SOURCE_TYPE, btd_adapter_get_address_type(adapter), BT_IO_OPT_DEST_BDADDR, device_get_address(data->device), BT_IO_OPT_DEST_TYPE, device_get_le_address_type(data->device), BT_IO_OPT_MODE, BT_IO_MODE_ISO, BT_IO_OPT_QOS, qos, BT_IO_OPT_DEFER_TIMEOUT, defer, BT_IO_OPT_INVALID); if (!io) { error("%s", err->message); g_error_free(err); return; } setup->io_id = io_glib_add_err_watch(io, setup_io_disconnected, setup); setup->io = io; setup->cis_active = !defer; bt_bap_stream_io_connecting(stream, g_io_channel_unix_get_fd(io)); } static void setup_connect_io_broadcast(struct bap_data *data, struct bap_setup *setup, struct bt_bap_stream *stream, struct bt_iso_qos *qos, int defer) { struct btd_adapter *adapter = data->adapter; GIOChannel *io = NULL; GError *err = NULL; bdaddr_t dst_addr = {0}; char addr[18]; struct bt_iso_base base; /* If IO already set and we are in the creation step, * skip creating it again */ if (bt_bap_stream_get_io(stream)) return; if (setup->io_id) { g_source_remove(setup->io_id); setup->io_id = 0; } base.base_len = setup->base->iov_len; memset(base.base, 0, 248); memcpy(base.base, setup->base->iov_base, setup->base->iov_len); ba2str(btd_adapter_get_address(adapter), addr); DBG("setup %p stream %p", setup, stream); io = bt_io_connect(bap_connect_bcast_io_cb, setup, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, btd_adapter_get_address(adapter), BT_IO_OPT_SOURCE_TYPE, btd_adapter_get_address_type(adapter), BT_IO_OPT_DEST_BDADDR, &dst_addr, BT_IO_OPT_DEST_TYPE, BDADDR_LE_PUBLIC, BT_IO_OPT_MODE, BT_IO_MODE_ISO, BT_IO_OPT_QOS, qos, BT_IO_OPT_BASE, &base, BT_IO_OPT_DEFER_TIMEOUT, defer, BT_IO_OPT_INVALID); if (!io) { error("%s", err->message); g_error_free(err); return; } setup->io_id = io_glib_add_err_watch(io, setup_io_disconnected, setup); setup->io = io; bt_bap_stream_io_connecting(stream, g_io_channel_unix_get_fd(io)); } static void setup_listen_io(struct bap_data *data, struct bt_bap_stream *stream, struct bt_iso_qos *qos) { struct btd_adapter *adapter = device_get_adapter(data->device); GIOChannel *io; GError *err = NULL; DBG("stream %p", stream); if (!data->server_streams) data->server_streams = queue_new(); if (!queue_find(data->server_streams, NULL, stream)) queue_push_tail(data->server_streams, stream); /* If IO already set skip creating it again */ if (bt_bap_stream_get_io(stream) || data->listen_io) return; io = bt_io_listen(NULL, iso_confirm_cb, data, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, btd_adapter_get_address(adapter), BT_IO_OPT_SOURCE_TYPE, btd_adapter_get_address_type(adapter), BT_IO_OPT_DEST_BDADDR, BDADDR_ANY, BT_IO_OPT_DEST_TYPE, BDADDR_LE_PUBLIC, BT_IO_OPT_MODE, BT_IO_MODE_ISO, BT_IO_OPT_QOS, qos, BT_IO_OPT_INVALID); if (!io) { error("%s", err->message); g_error_free(err); return; } data->listen_io = io; } static int pa_sync(struct bap_data *data); static void pa_and_big_sync(struct bap_setup *setup); static void setup_accept_io_broadcast(struct bap_data *data, struct bap_setup *setup) { pa_and_big_sync(setup); } static void setup_create_ucast_io(struct bap_data *data, struct bap_setup *setup, struct bt_bap_stream *stream, int defer) { struct bt_bap_qos *qos[2] = {}; struct bt_iso_qos iso_qos; if (!bt_bap_stream_io_get_qos(stream, &qos[0], &qos[1])) { error("bt_bap_stream_get_qos_links: failed"); return; } memset(&iso_qos, 0, sizeof(iso_qos)); iso_qos.ucast.cig = qos[0] ? qos[0]->ucast.cig_id : qos[1]->ucast.cig_id; iso_qos.ucast.cis = qos[0] ? qos[0]->ucast.cis_id : qos[1]->ucast.cis_id; iso_qos.ucast.framing = qos[0] ? qos[0]->ucast.framing : qos[1]->ucast.framing; bap_iso_qos(qos[0], &iso_qos.ucast.in); bap_iso_qos(qos[1], &iso_qos.ucast.out); if (setup) setup_connect_io(data, setup, stream, &iso_qos, defer); else setup_listen_io(data, stream, &iso_qos); } static void setup_create_bcast_io(struct bap_data *data, struct bap_setup *setup, struct bt_bap_stream *stream, int defer) { struct bt_bap_qos *qos = &setup->qos; struct iovec *bcode = qos->bcast.bcode; struct bt_iso_qos iso_qos; memset(&iso_qos, 0, sizeof(iso_qos)); iso_qos.bcast.big = setup->qos.bcast.big; iso_qos.bcast.bis = setup->qos.bcast.bis; iso_qos.bcast.sync_factor = setup->qos.bcast.sync_factor; iso_qos.bcast.packing = setup->qos.bcast.packing; iso_qos.bcast.framing = setup->qos.bcast.framing; iso_qos.bcast.encryption = setup->qos.bcast.encryption; if (bcode && bcode->iov_base) memcpy(iso_qos.bcast.bcode, bcode->iov_base, bcode->iov_len); iso_qos.bcast.options = setup->qos.bcast.options; iso_qos.bcast.skip = setup->qos.bcast.skip; iso_qos.bcast.sync_timeout = setup->qos.bcast.sync_timeout; iso_qos.bcast.sync_cte_type = setup->qos.bcast.sync_cte_type; iso_qos.bcast.mse = setup->qos.bcast.mse; iso_qos.bcast.timeout = setup->qos.bcast.timeout; memcpy(&iso_qos.bcast.out, &setup->qos.bcast.io_qos, sizeof(struct bt_iso_io_qos)); if (bt_bap_stream_get_dir(stream) == BT_BAP_BCAST_SINK) setup_connect_io_broadcast(data, setup, stream, &iso_qos, defer); else setup_accept_io_broadcast(data, setup); } static void setup_create_io(struct bap_data *data, struct bap_setup *setup, struct bt_bap_stream *stream, int defer) { if (setup && setup->closing) return; DBG("setup %p stream %p defer %s", setup, stream, defer ? "true" : "false"); switch (bt_bap_stream_get_type(stream)) { case BT_BAP_STREAM_TYPE_UCAST: setup_create_ucast_io(data, setup, stream, defer); break; case BT_BAP_STREAM_TYPE_BCAST: setup_create_bcast_io(data, setup, stream, defer); break; } } static void bap_state(struct bt_bap_stream *stream, uint8_t old_state, uint8_t new_state, void *user_data) { struct bap_data *data = user_data; struct bap_setup *setup; DBG("stream %p: %s(%u) -> %s(%u)", stream, bt_bap_stream_statestr(old_state), old_state, bt_bap_stream_statestr(new_state), new_state); /* Ignore transitions back to same state (ASCS allows some of these). * Of these we need to handle only the config->config case, which will * occur when reconfiguring the codec from initial config state. */ if (new_state == old_state && new_state != BT_BAP_STREAM_STATE_CONFIG) return; setup = bap_find_setup_by_stream(data, stream); if (setup && setup->closing) { if (old_state == BT_BAP_STREAM_STATE_RELEASING) { setup_free(setup); return; } } switch (new_state) { case BT_BAP_STREAM_STATE_IDLE: /* Release stream if idle */ if (setup) setup_free(setup); else queue_remove(data->server_streams, stream); break; case BT_BAP_STREAM_STATE_CONFIG: if (setup) { setup->config_pending = false; setup->want_qos = true; bap_update_cigs(setup->ep->data); } break; case BT_BAP_STREAM_STATE_QOS: if (setup) { setup->want_qos = false; setup->want_io = true; setup_ready(setup, 0, 0); bap_update_cigs(setup->ep->data); } else { setup_create_io(data, setup, stream, true); } break; case BT_BAP_STREAM_STATE_ENABLING: if (setup) { setup->want_cis = true; bap_update_cigs(setup->ep->data); } break; case BT_BAP_STREAM_STATE_STREAMING: /* Order of STREAMING and iso_connect_cb() is nondeterministic. * * If iso_connect_cb() did not complete yet, mark IO as * connected regardless, otherwise transport fails acquiring it. * If the connect doesn't actually succeed, it is handled via * normal disconnect flow. */ if (setup) { int fd; if (!setup->io || !setup->cis_active) break; if (!bt_bap_stream_io_is_connecting(stream, &fd)) break; if (fd != g_io_channel_unix_get_fd(setup->io)) break; DBG("setup %p stream %p io not yet ready", setup, stream); bt_bap_stream_set_io(stream, fd); } break; } } /* This function will call setup_create_io on all BISes from a BIG. * The defer parameter will be set on true on all but the last one. * This is done to inform the kernel when to when to start the BIG. */ static bool create_io_bises(struct bap_setup *setup, uint8_t nb_bises, struct bap_data *data) { const struct queue_entry *entry; struct bap_setup *ent_setup; bool defer = true; uint8_t active_bis_cnt = 1; for (entry = queue_get_entries(setup->ep->setups); entry; entry = entry->next) { ent_setup = entry->data; if (bt_bap_stream_get_qos(ent_setup->stream)->bcast.big != bt_bap_stream_get_qos(setup->stream)->bcast.big) continue; if (active_bis_cnt == nb_bises) defer = false; setup_create_io(data, ent_setup, ent_setup->stream, defer); if (!ent_setup->io) { error("Unable to create io"); goto fail; } active_bis_cnt++; } return true; fail: /* Clear the io of the created sockets if one * socket creation fails. */ for (entry = queue_get_entries(setup->ep->setups); entry; entry = entry->next) { ent_setup = entry->data; if (bt_bap_stream_get_qos(ent_setup->stream)->bcast.big != bt_bap_stream_get_qos(setup->stream)->bcast.big) continue; if (setup->io) g_io_channel_unref(setup->io); } return false; } static void iterate_setup_update_base(void *data, void *user_data) { struct bap_setup *setup = data; struct bap_setup *data_setup = user_data; if ((setup->stream != data_setup->stream) && (setup->qos.bcast.big == data_setup->qos.bcast.big)) { if (setup->base) util_iov_free(setup->base, 1); setup->base = util_iov_dup(data_setup->base, 1); } } /* Function checks the state of all streams in the same BIG * as the parameter stream, so it can decide if any sockets need * to be created. Returns he number of streams that need a socket * from that BIG. */ static uint8_t get_streams_nb_by_state(struct bap_setup *setup) { const struct queue_entry *entry; struct bap_setup *ent_setup; uint8_t stream_cnt = 0; if (setup->qos.bcast.big == BT_ISO_QOS_BIG_UNSET) /* If BIG ID is unset this is a single BIS BIG. * return 1 as create one socket only for this BIS */ return 1; for (entry = queue_get_entries(setup->ep->setups); entry; entry = entry->next) { ent_setup = entry->data; /* Skip the current stream form testing */ if (ent_setup == setup) { stream_cnt++; continue; } /* Test only BISes for the same BIG */ if (bt_bap_stream_get_qos(ent_setup->stream)->bcast.big != bt_bap_stream_get_qos(setup->stream)->bcast.big) continue; if (bt_bap_stream_get_state(ent_setup->stream) == BT_BAP_STREAM_STATE_STREAMING) /* If one stream in a multiple BIS BIG is in * streaming state this means that just the current * stream must have is socket created so return 1. */ return 1; else if (bt_bap_stream_get_state(ent_setup->stream) != BT_BAP_STREAM_STATE_ENABLING) /* Not all streams form a BIG have received transport * acquire, so wait for the other streams to. */ return 0; stream_cnt++; } /* Return the number of streams for the BIG * as all are ready to create sockets */ return stream_cnt; } static void bap_state_bcast_src(struct bt_bap_stream *stream, uint8_t old_state, uint8_t new_state, void *user_data) { struct bap_data *data = user_data; struct bap_setup *setup; bool defer = false; uint8_t nb_bises = 0; DBG("stream %p: %s(%u) -> %s(%u)", stream, bt_bap_stream_statestr(old_state), old_state, bt_bap_stream_statestr(new_state), new_state); /* Ignore transitions back to same state */ if (new_state == old_state) return; setup = bap_find_setup_by_stream(data, stream); switch (new_state) { case BT_BAP_STREAM_STATE_IDLE: /* Release stream if idle */ if (setup) setup_free(setup); break; case BT_BAP_STREAM_STATE_CONFIG: // TO DO Reconfiguration break; /* Use the ENABLING state to know when a transport * linked to a stream has been acquired by a process * and in the case of a BIG with one BIS stream goes * in the ENABLING state waiting for the response * from the kernel that the BIG has been created * so it can go to the streaming state. * For the case of a BIG with multiple BISes, * the BIG is created when all BISes are acquired. * So we use the ENABLING state to verify that all * transports attached to that streams form BIG have * been acquired so we can create the BIG. */ case BT_BAP_STREAM_STATE_ENABLING: /* If the stream attached to a broadcast * source endpoint generate the base. */ if (setup->base == NULL) { setup->base = bt_bap_stream_get_base( setup->stream); /* Set the generated BASE on all setups * from the same BIG. */ queue_foreach(setup->ep->setups, iterate_setup_update_base, setup); } /* The kernel has 2 requirements when handling * multiple BIS connections for the same BIG: * 1 - setup_create_io for all but the last BIS * must be with defer true so we can inform the * kernel when to start the BIG. * 2 - The order in which the setup_create_io * are called must be in the order of BIS * indexes in BASE from first to last. * To address this requirement we will call * setup_create_io on all BISes only when all * transport acquire have been received and will * send it in the order of the BIS index * from BASE. */ nb_bises = get_streams_nb_by_state(setup); if (nb_bises == 1) { setup_create_io(data, setup, stream, defer); if (!setup->io) { error("Unable to create io"); if (old_state != BT_BAP_STREAM_STATE_RELEASING) bt_bap_stream_release(stream, NULL, NULL); } break; } else if (nb_bises == 0) break; if (!create_io_bises(setup, nb_bises, data)) { if (old_state != BT_BAP_STREAM_STATE_RELEASING) bt_bap_stream_release(stream, NULL, NULL); } break; } } static bool link_enabled(const void *data, const void *match_data) { struct bt_bap_stream *stream = (struct bt_bap_stream *)data; uint8_t state = bt_bap_stream_get_state(stream); return ((state == BT_BAP_STREAM_STATE_ENABLING) || bt_bap_stream_get_io(stream)); } static void bap_state_bcast_sink(struct bt_bap_stream *stream, uint8_t old_state, uint8_t new_state, void *user_data) { struct bap_data *data = user_data; struct bap_setup *setup; bool defer = false; DBG("stream %p: %s(%u) -> %s(%u)", stream, bt_bap_stream_statestr(old_state), old_state, bt_bap_stream_statestr(new_state), new_state); if (new_state == old_state && new_state != BT_BAP_STREAM_STATE_CONFIG) return; setup = bap_find_setup_by_stream(data, stream); if (!setup) return; switch (new_state) { case BT_BAP_STREAM_STATE_IDLE: /* Release stream if idle */ if (setup) setup_free(setup); break; case BT_BAP_STREAM_STATE_CONFIG: if (!setup) break; if (old_state == BT_BAP_STREAM_STATE_STREAMING) setup_io_close(setup, NULL); break; case BT_BAP_STREAM_STATE_ENABLING: /* For a Broadcast Sink, the ENABLING state suggests that * the upper layer process requires the stream to start * receiving audio. This state is used to differentiate * between all configured streams and the ones that have * been enabled by the upper layer. * * Create stream io if not already created and if no * link has been enabled or started. * * The first enabled link will create and set fds for * all links. */ if (!bt_bap_stream_get_io(stream) && !queue_find(bt_bap_stream_io_get_links(stream), link_enabled, NULL)) setup_create_io(data, setup, stream, defer); break; } } static void pac_added(struct bt_bap_pac *pac, void *user_data) { struct btd_service *service = user_data; struct bap_data *data; DBG("pac %p", pac); if (btd_service_get_state(service) != BTD_SERVICE_STATE_CONNECTED) return; data = btd_service_get_user_data(service); bt_bap_foreach_pac(data->bap, BT_BAP_SOURCE, pac_register, service); bt_bap_foreach_pac(data->bap, BT_BAP_SINK, pac_register, service); bap_select_all(data, false, NULL, NULL); } static void pac_added_broadcast(struct bt_bap_pac *pac, void *user_data) { struct bap_data *data = user_data; /* * If pac type is BT_BAP_BCAST_SOURCE locally create an endpoint * without a remote pac. * If pac type is BT_BAP_BCAST_SOURCE and remote then look for a * local broadcast sink pac locally before creating an endpoint. */ if (bt_bap_pac_bcast_is_local(data->bap, pac) && (bt_bap_pac_get_type(pac) == BT_BAP_BCAST_SOURCE)) pac_found_bcast(pac, NULL, user_data); else bt_bap_foreach_pac(data->bap, bt_bap_pac_get_type(pac), pac_found_bcast, data); } static bool ep_match_pac(const void *data, const void *match_data) { const struct bap_ep *ep = data; const struct bt_bap_pac *pac = match_data; return ep->rpac == pac || ep->lpac == pac; } static void pac_removed(struct bt_bap_pac *pac, void *user_data) { struct btd_service *service = user_data; struct bap_data *data; struct queue *queue; struct bap_ep *ep; DBG("pac %p", pac); if (btd_service_get_state(service) != BTD_SERVICE_STATE_CONNECTED) return; data = btd_service_get_user_data(service); switch (bt_bap_pac_get_type(pac)) { case BT_BAP_SINK: queue = data->srcs; break; case BT_BAP_SOURCE: queue = data->snks; break; default: return; } ep = queue_remove_if(queue, ep_match_pac, pac); if (!ep) return; ep_unregister(ep); } static void pac_removed_broadcast(struct bt_bap_pac *pac, void *user_data) { struct bap_data *data = user_data; struct queue *queue; struct bap_ep *ep; DBG("pac %p", pac); switch (bt_bap_pac_get_type(pac)) { case BT_BAP_SINK: queue = data->srcs; break; case BT_BAP_SOURCE: queue = data->snks; break; case BT_BAP_BCAST_SOURCE: queue = data->bcast; break; default: return; } ep = queue_remove_if(queue, ep_match_pac, pac); if (!ep) return; ep_unregister(ep); } static struct bap_data *bap_data_new(struct btd_device *device) { struct bap_data *data; data = new0(struct bap_data, 1); data->device = device; data->srcs = queue_new(); data->snks = queue_new(); data->bcast = queue_new(); return data; } static void bap_data_add(struct bap_data *data) { DBG("data %p", data); if (queue_find(sessions, NULL, data)) { error("data %p already added", data); return; } bt_bap_set_debug(data->bap, bap_debug, NULL, NULL); if (!sessions) sessions = queue_new(); queue_push_tail(sessions, data); if (data->service && !btd_service_get_user_data(data->service)) btd_service_set_user_data(data->service, data); } static bool match_data(const void *data, const void *match_data) { const struct bap_data *bdata = data; const struct bt_bap *bap = match_data; return bdata->bap == bap; } static bool io_get_qos(GIOChannel *io, struct bt_iso_qos *qos) { GError *err = NULL; bool ret; ret = bt_io_get(io, &err, BT_IO_OPT_QOS, qos, BT_IO_OPT_INVALID); if (!ret) { error("%s", err->message); g_error_free(err); } return ret; } static void bap_connecting(struct bt_bap_stream *stream, bool state, int fd, void *user_data) { struct bap_data *data = user_data; struct bap_setup *setup; struct bt_bap_qos *qos; GIOChannel *io; if (!state) return; setup = bap_find_setup_by_stream(data, stream); if (!setup) return; qos = &setup->qos; if (!setup->io) { io = g_io_channel_unix_new(fd); setup->io_id = io_glib_add_err_watch(io, setup_io_disconnected, setup); setup->io = io; } else io = setup->io; g_io_channel_set_close_on_unref(io, FALSE); /* Attempt to get CIG/CIS if they have not been set */ if (qos->ucast.cig_id == BT_ISO_QOS_CIG_UNSET || qos->ucast.cis_id == BT_ISO_QOS_CIS_UNSET) { struct bt_iso_qos iso_qos; if (!io_get_qos(io, &iso_qos)) { g_io_channel_unref(io); return; } qos->ucast.cig_id = iso_qos.ucast.cig; qos->ucast.cis_id = iso_qos.ucast.cis; } DBG("stream %p fd %d: CIG 0x%02x CIS 0x%02x", stream, fd, qos->ucast.cig_id, qos->ucast.cis_id); } static void bap_connecting_bcast(struct bt_bap_stream *stream, bool state, int fd, void *user_data) { struct bap_data *data = user_data; struct bap_setup *setup; GIOChannel *io; if (!state) return; setup = bap_find_setup_by_stream(data, stream); if (!setup) return; if (!setup->io) { io = g_io_channel_unix_new(fd); setup->io_id = io_glib_add_err_watch(io, setup_io_disconnected, setup); setup->io = io; } else io = setup->io; g_io_channel_set_close_on_unref(io, FALSE); /* Attempt to get BIG/BIS if they have not been set */ if (setup->qos.bcast.big == BT_ISO_QOS_BIG_UNSET || setup->qos.bcast.bis == BT_ISO_QOS_BIS_UNSET) { struct bt_iso_qos iso_qos; if (!io_get_qos(io, &iso_qos)) { g_io_channel_unref(io); return; } setup->qos.bcast.big = iso_qos.bcast.big; setup->qos.bcast.bis = iso_qos.bcast.bis; bt_bap_stream_qos(setup->stream, &setup->qos, NULL, NULL); } DBG("stream %p fd %d: BIG 0x%02x BIS 0x%02x", stream, fd, setup->qos.bcast.big, setup->qos.bcast.bis); } static void bap_attached(struct bt_bap *bap, void *user_data) { struct bap_data *data; struct bt_att *att; struct btd_device *device; DBG("%p", bap); data = queue_find(sessions, match_data, bap); if (data) return; att = bt_bap_get_att(bap); if (!att) return; device = btd_adapter_find_device_by_fd(bt_att_get_fd(att)); if (!device) { error("Unable to find device"); return; } data = bap_data_new(device); data->bap = bap; bap_data_add(data); data->state_id = bt_bap_state_register(data->bap, bap_state, bap_connecting, data, NULL); } static void bap_detached(struct bt_bap *bap, void *user_data) { struct bap_data *data; DBG("%p", bap); data = queue_find(sessions, match_data, bap); if (!data) { error("Unable to find bap session"); return; } /* If there is a service it means there is PACS thus we can keep * instance allocated. */ if (data->service) return; bap_data_remove(data); } static int pa_sync(struct bap_data *data) { GError *err = NULL; uint8_t sid = 0xff; if (data->listen_io) { DBG("Already probed"); return -1; } DBG("Create PA sync with this source"); data->listen_io = bt_io_listen(NULL, iso_pa_sync_confirm_cb, data, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, btd_adapter_get_address(data->adapter), BT_IO_OPT_SOURCE_TYPE, btd_adapter_get_address_type(data->adapter), BT_IO_OPT_DEST_BDADDR, device_get_address(data->device), BT_IO_OPT_DEST_TYPE, btd_device_get_bdaddr_type(data->device), BT_IO_OPT_MODE, BT_IO_MODE_ISO, BT_IO_OPT_QOS, &bap_sink_pa_qos, BT_IO_OPT_ISO_BC_SID, sid, BT_IO_OPT_INVALID); if (!data->listen_io) { error("%s", err->message); g_error_free(err); } return 0; } static void append_setup(void *data, void *user_data) { struct bt_bap_stream *stream = data; struct sockaddr_iso_bc *addr = user_data; char *path = bt_bap_stream_get_user_data(stream); int bis = 1; int s_err; const char *strbis = NULL; strbis = strstr(path, "/bis"); if (!strbis) { DBG("bis index cannot be found"); return; } s_err = sscanf(strbis, "/bis%d", &bis); if (s_err == -1) { DBG("sscanf error"); return; } DBG("Do BIG Sync with BIS %d", bis); addr->bc_bis[addr->bc_num_bis] = bis; addr->bc_num_bis++; } static void setup_refresh_qos(void *data, void *user_data) { struct bt_bap_stream *stream = data; struct bap_data *bap_data = user_data; struct bap_setup *setup = bap_find_setup_by_stream(bap_data, stream); setup->qos = *bt_bap_stream_get_qos(stream); } static void iso_do_big_sync(GIOChannel *io, void *user_data) { GError *err = NULL; struct bap_setup *setup = user_data; struct bap_data *data = setup->data; struct sockaddr_iso_bc iso_bc_addr = {0}; struct bt_iso_qos qos; struct queue *links = bt_bap_stream_io_get_links(setup->stream); DBG("PA Sync done"); g_io_channel_unref(data->listen_io); g_io_channel_shutdown(data->listen_io, TRUE, NULL); data->listen_io = io; g_io_channel_ref(data->listen_io); /* Append each linked BIS to the BIG sync request */ append_setup(setup->stream, &iso_bc_addr); queue_foreach(links, append_setup, &iso_bc_addr); /* Refresh qos stored in setups */ setup->qos = *bt_bap_stream_get_qos(setup->stream); queue_foreach(links, setup_refresh_qos, data); /* Set the user requested QOS */ bt_bap_qos_to_iso_qos(&setup->qos, &qos); if (!bt_io_set(io, &err, BT_IO_OPT_QOS, &qos, BT_IO_OPT_INVALID)) { error("bt_io_set: %s", err->message); g_error_free(err); } if (!bt_io_bcast_accept(io, iso_bcast_confirm_cb, setup, NULL, &err, BT_IO_OPT_ISO_BC_NUM_BIS, iso_bc_addr.bc_num_bis, BT_IO_OPT_ISO_BC_BIS, iso_bc_addr.bc_bis, BT_IO_OPT_INVALID)) { error("bt_io_bcast_accept: %s", err->message); g_error_free(err); } } static void pa_and_big_sync(struct bap_setup *setup) { GError *err = NULL; struct bap_data *bap_data = setup->data; DBG("Create PA sync with this source"); bap_data->listen_io = bt_io_listen(NULL, iso_do_big_sync, setup, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, btd_adapter_get_address(bap_data->adapter), BT_IO_OPT_DEST_BDADDR, device_get_address(bap_data->device), BT_IO_OPT_DEST_TYPE, btd_device_get_bdaddr_type(bap_data->device), BT_IO_OPT_MODE, BT_IO_MODE_ISO, BT_IO_OPT_QOS, &bap_sink_pa_qos, BT_IO_OPT_ISO_BC_SID, setup->sid, BT_IO_OPT_INVALID); if (!bap_data->listen_io) { error("%s", err->message); g_error_free(err); } } static void bap_ready(struct bt_bap *bap, void *user_data) { struct btd_service *service = user_data; struct bap_data *data = btd_service_get_user_data(service); DBG("bap %p", bap); data->bap_ready = true; if (data->services_ready) bap_ucast_start(data); } static void bap_services_ready(struct btd_service *service) { struct bap_data *data = btd_service_get_user_data(service); DBG("%p", data); data->services_ready = true; if (data->bap_ready) bap_ucast_start(data); } static int bap_bcast_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct btd_adapter *adapter = device_get_adapter(device); struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct bap_data *data; struct bt_bap *bap; if (!btd_adapter_has_exp_feature(adapter, EXP_FEAT_ISO_SOCKET)) { error("BAP requires ISO Socket which is not enabled"); return -ENOTSUP; } bap = bt_bap_new(btd_gatt_database_get_db(database), btd_gatt_database_get_db(database)); if (!bap) { error("Unable to create BAP instance"); return -EINVAL; } bt_bap_set_user_data(bap, service); if (!bt_bap_attach(bap, NULL)) { error("BAP unable to attach"); bt_bap_unref(bap); return -EINVAL; } data = bap_data_new(device); data->service = service; data->adapter = adapter; data->device = device; data->bap = bap; data->bcast_snks = queue_new(); bap_data_add(data); data->ready_id = bt_bap_ready_register(data->bap, bap_ready, service, NULL); data->state_id = bt_bap_state_register(data->bap, bap_state_bcast_sink, bap_connecting_bcast, data, NULL); data->pac_id = bt_bap_pac_register(data->bap, pac_added_broadcast, pac_removed_broadcast, data, NULL); if (btd_service_get_user_data(service) == data) /* If the reference to the bap session has been set as service * user data, it means the broadcaster was autonomously probed. * Thus, the Broadcast Sink needs to create short lived PA sync * to discover streams. * * If the service user data does not match the bap session, it * means that the broadcaster was probed via a Broadcast * Assistant from the BASS plugin, where stream discovery and * configuration will also be handled. */ pa_sync(data); return 0; } static bool match_service(const void *data, const void *match_data) { const struct bap_data *bdata = data; const struct btd_service *service = match_data; return bdata->service == service; } static void bap_bcast_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct bap_data *data; struct queue *bcast_snks; char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); /* Lookup the bap session for this service since in case of * bass_delegator its user data is set by bass plugin. */ data = queue_find(sessions, match_service, service); if (!data) { error("BAP service not handled by profile"); return; } /* Clean up before bis_remove and data_remove */ if (data->bcast_snks) { bcast_snks = data->bcast_snks; data->bcast_snks = NULL; queue_destroy(bcast_snks, setup_free); } bt_bap_bis_remove(data->bap); bap_data_remove(data); } static int bap_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct btd_adapter *adapter = device_get_adapter(device); struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct bap_data *data = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); if (!btd_adapter_has_exp_feature(adapter, EXP_FEAT_ISO_SOCKET)) { error("BAP requires ISO Socket which is not enabled"); return -ENOTSUP; } /* Ignore, if we were probed for this device already */ if (data) { error("Profile probed twice for the same device!"); return -EINVAL; } data = bap_data_new(device); data->service = service; data->bap = bt_bap_new(btd_gatt_database_get_db(database), btd_device_get_gatt_db(device)); if (!data->bap) { error("Unable to create BAP instance"); free(data); return -EINVAL; } bap_data_add(data); data->ready_id = bt_bap_ready_register(data->bap, bap_ready, service, NULL); data->state_id = bt_bap_state_register(data->bap, bap_state, bap_connecting, data, NULL); data->pac_id = bt_bap_pac_register(data->bap, pac_added, pac_removed, service, NULL); bt_bap_set_user_data(data->bap, service); return 0; } static int bap_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct bt_gatt_client *client = btd_device_get_gatt_client(device); struct bap_data *data = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); if (!data) { error("BAP service not handled by profile"); return -EINVAL; } data->bap_ready = false; data->services_ready = false; if (!bt_bap_attach(data->bap, client)) { error("BAP unable to attach"); return -EINVAL; } btd_service_connecting_complete(service, 0); return 0; } static bool ep_remove(const void *data, const void *match_data) { ep_unregister((void *)data); return true; } static int bap_disconnect(struct btd_service *service) { struct bap_data *data = btd_service_get_user_data(service); queue_remove_all(data->snks, ep_remove, NULL, NULL); queue_remove_all(data->srcs, ep_remove, NULL, NULL); queue_destroy(data->server_streams, NULL); data->server_streams = NULL; bt_bap_detach(data->bap); btd_service_disconnecting_complete(service, 0); return 0; } static int bap_bcast_disconnect(struct btd_service *service) { struct bap_data *data; struct queue *bcast_snks; /* Lookup the bap session for this service since in case of * bass_delegator its user data is set by bass plugin. */ data = queue_find(sessions, match_service, service); if (!data) { error("BAP service not handled by profile"); return -EINVAL; } /* Clean up broadcast sinks before detach (like unicast does) */ if (data->bcast_snks) { bcast_snks = data->bcast_snks; data->bcast_snks = NULL; queue_destroy(bcast_snks, setup_free); } bt_bap_detach(data->bap); btd_service_disconnecting_complete(service, 0); return 0; } static int bap_adapter_probe(struct btd_profile *p, struct btd_adapter *adapter) { struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct bap_data *data; char addr[18]; ba2str(btd_adapter_get_address(adapter), addr); DBG("%s", addr); if (!btd_kernel_experimental_enabled(ISO_SOCKET_UUID)) { error("BAP requires ISO Socket which is not enabled"); return -ENOTSUP; } data = bap_data_new(NULL); data->bap = bt_bap_new(btd_gatt_database_get_db(database), btd_gatt_database_get_db(database)); if (!data->bap) { error("Unable to create BAP instance"); free(data); return -EINVAL; } bap_data_add(data); if (!bt_bap_attach_broadcast(data->bap)) { error("BAP unable to attach"); return -EINVAL; } data->adapter = adapter; data->state_id = bt_bap_state_register(data->bap, bap_state_bcast_src, bap_connecting_bcast, data, NULL); data->pac_id = bt_bap_pac_register(data->bap, pac_added_broadcast, pac_removed_broadcast, data, NULL); return 0; } static void bap_adapter_remove(struct btd_profile *p, struct btd_adapter *adapter) { struct bap_data *data = queue_find(sessions, match_adapter, adapter); char addr[18]; ba2str(btd_adapter_get_address(adapter), addr); DBG("%s", addr); if (!data) { error("BAP service not handled by profile"); return; } bap_data_remove(data); } static struct btd_profile bap_profile = { .name = "bap", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = PACS_UUID_STR, .device_probe = bap_probe, .device_remove = bap_remove, .accept = bap_accept, .disconnect = bap_disconnect, .adapter_probe = bap_adapter_probe, .adapter_remove = bap_adapter_remove, .auto_connect = true, .experimental = true, .after_services = BTD_PROFILE_UUID_CB(bap_services_ready, VCS_UUID_STR, TMAS_UUID_STR, GMAS_UUID_STR), }; static struct btd_profile bap_bcast_profile = { .name = "bcaa", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = BCAAS_UUID_STR, .device_probe = bap_bcast_probe, .device_remove = bap_bcast_remove, .disconnect = bap_bcast_disconnect, .auto_connect = false, .experimental = true, }; static unsigned int bap_id = 0; static int bap_init(void) { int err; err = btd_profile_register(&bap_profile); if (err) return err; err = btd_profile_register(&bap_bcast_profile); if (err) return err; bap_id = bt_bap_register(bap_attached, bap_detached, NULL); return 0; } static void bap_exit(void) { btd_profile_unregister(&bap_profile); bt_bap_unregister(bap_id); } BLUETOOTH_PLUGIN_DEFINE(bap, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, bap_init, bap_exit) bluez-5.87/profiles/audio/PaxHeaders/transport.h0000644000000000000000000000005015142177620017011 xustar0020 atime=1783093610 20 ctime=1783094105 bluez-5.87/profiles/audio/transport.h0000644000000000000000000000236215142177620016475 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2004-2009 Marcel Holtmann * * */ struct media_transport; struct media_transport *media_transport_create(struct btd_device *device, const char *remote_endpoint, uint8_t *configuration, size_t size, void *data, void *stream); void media_transport_destroy(struct media_transport *transport); const char *media_transport_get_path(struct media_transport *transport); void *media_transport_get_stream(struct media_transport *transport); struct btd_device *media_transport_get_dev(struct media_transport *transport); void media_transport_update_delay(struct media_transport *transport, uint16_t delay); void media_transport_update_volume(struct media_transport *transport, int volume); void transport_get_properties(struct media_transport *transport, DBusMessageIter *iter); int media_transport_get_a2dp_volume(struct btd_device *dev); void media_transport_set_a2dp_volume(struct btd_device *dev, int volume); void media_transport_volume_changed(struct btd_device *dev); const char *media_transport_stream_path(void *stream); bluez-5.87/profiles/audio/PaxHeaders/tmap.c0000644000000000000000000000005015142177620015711 xustar0020 atime=1783093714 20 ctime=1783094106 bluez-5.87/profiles/audio/tmap.c0000644000000000000000000001040515142177620015372 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2025 Pauli Virtanen. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/dbus-common.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/shared/gatt-server.h" #include "src/shared/tmap.h" #include "btio/btio.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/gatt-database.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #include "src/error.h" #include "vcp.h" #include "transport.h" #define TMAS_UUID_STR "00001855-0000-1000-8000-00805f9b34fb" static void tmap_debug(const char *str, void *user_data) { DBG_IDX(0xffff, "%s", str); } static void service_ready(struct bt_tmap *tmap, void *user_data) { struct btd_service *service = user_data; btd_service_connecting_complete(service, 0); } static struct bt_tmap *add_service(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct bt_gatt_client *client = btd_device_get_gatt_client(device); struct bt_tmap *tmap = btd_service_get_user_data(service); if (tmap) return tmap; tmap = bt_tmap_attach(client, service_ready, service); if (!tmap) { error("TMAP client unable to attach"); return NULL; } bt_tmap_set_debug(tmap, tmap_debug, NULL, NULL); btd_service_set_user_data(service, tmap); return tmap; } static void remove_service(struct btd_service *service) { struct bt_tmap *tmap = btd_service_get_user_data(service); if (!tmap) return; btd_service_set_user_data(service, NULL); bt_tmap_unref(tmap); } static int tmap_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct bt_tmap *tmap; char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); tmap = add_service(service); if (!tmap) return -EINVAL; return 0; } static int tmap_disconnect(struct btd_service *service) { remove_service(service); btd_service_disconnecting_complete(service, 0); return 0; } static int tmap_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); return 0; } static void tmap_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); remove_service(service); } static int tmap_adapter_probe(struct btd_profile *p, struct btd_adapter *adapter) { struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct bt_tmap *tmap; DBG("Add TMAP server %s", adapter_get_path(adapter)); tmap = bt_tmap_add_db(btd_gatt_database_get_db(database)); bt_tmap_set_debug(tmap, tmap_debug, NULL, NULL); return 0; } static void tmap_adapter_remove(struct btd_profile *p, struct btd_adapter *adapter) { struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct bt_tmap *tmap; DBG("Remove TMAP server %s", adapter_get_path(adapter)); tmap = bt_tmap_find(btd_gatt_database_get_db(database)); bt_tmap_unref(tmap); } static struct btd_profile tmap_profile = { .name = "tmap", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .remote_uuid = TMAS_UUID_STR, .device_probe = tmap_probe, .device_remove = tmap_remove, .accept = tmap_accept, .disconnect = tmap_disconnect, .adapter_probe = tmap_adapter_probe, .adapter_remove = tmap_adapter_remove, .experimental = true, }; static int tmap_init(void) { int err; err = btd_profile_register(&tmap_profile); if (err) return err; return 0; } static void tmap_exit(void) { btd_profile_unregister(&tmap_profile); } BLUETOOTH_PLUGIN_DEFINE(tmap, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, tmap_init, tmap_exit) bluez-5.87/profiles/audio/PaxHeaders/csip.c0000644000000000000000000000005015142177620015706 xustar0020 atime=1783093712 20 ctime=1783094106 bluez-5.87/profiles/audio/csip.c0000644000000000000000000002307215142177620015373 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/dbus-common.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/shared/gatt-server.h" #include "src/shared/csip.h" #include "src/shared/crypto.h" #include "btio/btio.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/gatt-database.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #include "src/error.h" #include "src/btd.h" #define CSIS_UUID_STR "00001846-0000-1000-8000-00805f9b34fb" struct csis_data { struct btd_adapter *adapter; struct bt_csip *csip; }; struct csip_data { struct btd_adapter *adapter; struct btd_device *device; struct btd_service *service; struct bt_csip *csip; unsigned int ready_id; }; static struct queue *sessions; static struct queue *servers; static void csip_debug(const char *str, void *user_data) { DBG_IDX(0xffff, "%s", str); } static struct csip_data *csip_data_new(struct btd_device *device) { struct csip_data *data; data = new0(struct csip_data, 1); data->device = device; return data; } static void csip_data_add(struct csip_data *data) { DBG("data %p", data); if (queue_find(sessions, NULL, data)) { error("data %p already added", data); return; } bt_csip_set_debug(data->csip, csip_debug, NULL, NULL); if (!sessions) sessions = queue_new(); queue_push_tail(sessions, data); if (data->service) btd_service_set_user_data(data->service, data); } static int csip_disconnect(struct btd_service *service) { struct csip_data *data = btd_service_get_user_data(service); bt_csip_detach(data->csip); btd_service_disconnecting_complete(service, 0); return 0; } static bool match_data(const void *data, const void *match_data) { const struct csip_data *vdata = data; const struct bt_csip *csip = match_data; return vdata->csip == csip; } static void csip_data_free(struct csip_data *data) { if (data->service) { btd_service_set_user_data(data->service, NULL); bt_csip_set_user_data(data->csip, NULL); } bt_csip_ready_unregister(data->csip, data->ready_id); bt_csip_unref(data->csip); free(data); } static void csip_data_remove(struct csip_data *data) { DBG("data %p", data); if (!queue_remove(sessions, data)) return; csip_data_free(data); if (queue_isempty(sessions)) { queue_destroy(sessions, NULL); sessions = NULL; } } static void csip_detached(struct bt_csip *csip, void *user_data) { struct csip_data *data; DBG("%p", csip); data = queue_find(sessions, match_data, csip); if (!data) { error("Unable to find csip session"); return; } /* If there is a service it means there is CSIS thus we can keep * instance allocated. */ if (data->service) return; csip_data_remove(data); } static void csip_attached(struct bt_csip *csip, void *user_data) { struct csip_data *data; struct bt_att *att; struct btd_device *device; DBG("%p", csip); data = queue_find(sessions, match_data, csip); if (data) return; att = bt_csip_get_att(csip); if (!att) return; device = btd_adapter_find_device_by_fd(bt_att_get_fd(att)); if (!device) { error("Unable to find device"); return; } data = csip_data_new(device); data->csip = csip; csip_data_add(data); } static int csip_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct bt_gatt_client *client = btd_device_get_gatt_client(device); struct csip_data *data = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); if (!data) { error("CSIP service not handled by profile"); return -EINVAL; } if (!bt_csip_attach(data->csip, client)) { error("CSIP unable to attach"); return -EINVAL; } btd_service_connecting_complete(service, 0); return 0; } static void csip_ready(struct bt_csip *csip, void *user_data) { struct btd_service *service = user_data; struct btd_device *device = btd_service_get_device(service); uint8_t type, size, rank; uint8_t k[16]; DBG("csip %p", csip); if (!bt_csip_get_sirk(csip, &type, k, &size, &rank)) { error("Unable to read SIRK"); return; } btd_device_add_set(device, type == BT_CSIP_SIRK_ENCRYPT ? true : false, k, size, rank); } static int csip_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct btd_adapter *adapter = device_get_adapter(device); struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct csip_data *data = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); /* Ignore, if we were probed for this device already */ if (data) { error("Profile probed twice for the same device!"); return -EINVAL; } data = csip_data_new(device); data->service = service; data->csip = bt_csip_new(btd_gatt_database_get_db(database), btd_device_get_gatt_db(device)); if (!data->csip) { error("Unable to create CSIP instance"); free(data); return -EINVAL; } csip_data_add(data); data->ready_id = bt_csip_ready_register(data->csip, csip_ready, service, NULL); bt_csip_set_user_data(data->csip, service); return 0; } static void csip_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct csip_data *data; char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); data = btd_service_get_user_data(service); if (!data) { error("CSIP service not handled by profile"); return; } csip_data_remove(data); } static struct btd_profile csip_profile = { .name = "csip", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = CSIS_UUID_STR, .device_probe = csip_probe, .device_remove = csip_remove, .accept = csip_accept, .disconnect = csip_disconnect, .experimental = true, }; static bool csis_encrypt(struct bt_att *att, uint8_t val[16]) { struct btd_device *device; struct bt_crypto *crypto; uint8_t ltk[16]; bool ret; device = btd_adapter_find_device_by_fd(bt_att_get_fd(att)); if (!device) { error("Unable to find device"); return false; } if (!btd_device_get_ltk(device, ltk, NULL, NULL)) { error("Unable to get device LTK"); return false; } crypto = bt_crypto_new(); if (!crypto) { error("Failed to open crypto"); return false; } ret = bt_crypto_sef(crypto, ltk, val, val); if (!ret) error("Failed to encrypt SIRK using LTK"); bt_crypto_unref(crypto); return ret; } static void csis_data_add(struct csis_data *data) { DBG("data %p", data); if (queue_find(servers, NULL, data)) { error("data %p already added", data); return; } bt_csip_set_debug(data->csip, csip_debug, NULL, NULL); bt_csip_set_sirk(data->csip, btd_opts.csis.encrypt, btd_opts.csis.sirk, btd_opts.csis.size, btd_opts.csis.rank, csis_encrypt); if (!servers) servers = queue_new(); queue_push_tail(servers, data); } static struct csis_data *csis_data_new(struct btd_adapter *adapter) { struct csis_data *data; data = new0(struct csis_data, 1); data->adapter = adapter; return data; } static int csis_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct csis_data *data; DBG("path %s", adapter_get_path(adapter)); data = csis_data_new(adapter); data->csip = bt_csip_new(btd_gatt_database_get_db(database), NULL); if (!data->csip) { error("Unable to create CSIP instance"); free(data); return -EINVAL; } csis_data_add(data); return 0; } static bool match_csis(const void *data, const void *match_data) { const struct csis_data *csis = data; const struct btd_adapter *adapter = match_data; return csis->adapter == adapter; } static void csis_data_free(struct csis_data *data) { bt_csip_unref(data->csip); free(data); } static void csis_data_remove(struct csis_data *data) { DBG("data %p", data); csis_data_free(data); if (queue_isempty(servers)) { queue_destroy(servers, NULL); servers = NULL; } } static void csis_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { struct csis_data *data; DBG("path %s", adapter_get_path(adapter)); data = queue_remove_if(servers, match_csis, adapter); if (!data) return; csis_data_remove(data); } static struct btd_profile csis_profile = { .name = "csis", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .bearer = BTD_PROFILE_BEARER_LE, .local_uuid = CSIS_UUID_STR, .adapter_probe = csis_server_probe, .adapter_remove = csis_server_remove, .experimental = true, }; static unsigned int csip_id; static int csip_init(void) { int err; err = btd_profile_register(&csis_profile); if (err) return err; err = btd_profile_register(&csip_profile); if (err) return err; csip_id = bt_csip_register(csip_attached, csip_detached, NULL); return 0; } static void csip_exit(void) { btd_profile_unregister(&csis_profile); btd_profile_unregister(&csip_profile); bt_csip_unregister(csip_id); } BLUETOOTH_PLUGIN_DEFINE(csip, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, csip_init, csip_exit) bluez-5.87/profiles/audio/PaxHeaders/control.c0000644000000000000000000000005015063045254016427 xustar0020 atime=1783093646 20 ctime=1783094105 bluez-5.87/profiles/audio/control.c0000644000000000000000000002123615063045254016114 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright (C) 2011 Texas Instruments, Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "bluetooth/uuid.h" #include "gdbus/gdbus.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #include "src/error.h" #include "src/sdpd.h" #include "src/uuid-helper.h" #include "src/dbus-common.h" #include "avctp.h" #include "control.h" #include "player.h" static GSList *devices = NULL; struct control { struct btd_device *dev; struct avctp *session; struct btd_service *target; struct btd_service *remote; unsigned int avctp_id; const char *player; }; static void state_changed(struct btd_device *dev, avctp_state_t old_state, avctp_state_t new_state, int err, void *user_data) { struct control *control = user_data; DBusConnection *conn = btd_get_dbus_connection(); const char *path = device_get_path(dev); switch (new_state) { case AVCTP_STATE_DISCONNECTED: control->session = NULL; control->player = NULL; g_dbus_emit_property_changed(conn, path, AUDIO_CONTROL_INTERFACE, "Connected"); g_dbus_emit_property_changed(conn, path, AUDIO_CONTROL_INTERFACE, "Player"); break; case AVCTP_STATE_CONNECTING: if (control->session) break; control->session = avctp_get(dev); break; case AVCTP_STATE_CONNECTED: g_dbus_emit_property_changed(conn, path, AUDIO_CONTROL_INTERFACE, "Connected"); break; case AVCTP_STATE_BROWSING_CONNECTING: case AVCTP_STATE_BROWSING_CONNECTED: default: return; } } int control_connect(struct btd_service *service) { struct control *control = btd_service_get_user_data(service); if (control->session) return -EALREADY; control->session = avctp_connect(control->dev); if (!control->session) return -EIO; return 0; } int control_disconnect(struct btd_service *service) { struct control *control = btd_service_get_user_data(service); if (!control || !control->session) return -ENOTCONN; avctp_disconnect(control->session); return 0; } static DBusMessage *key_pressed(DBusConnection *conn, DBusMessage *msg, uint8_t op, bool hold, void *data) { struct control *control = data; int err; if (!control->session) return btd_error_not_connected(msg); if (!control->target) return btd_error_not_supported(msg); err = avctp_send_passthrough(control->session, op, hold); if (err < 0) return btd_error_failed(msg, strerror(-err)); return dbus_message_new_method_return(msg); } static DBusMessage *control_volume_up(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_VOLUME_UP, false, data); } static DBusMessage *control_volume_down(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_VOLUME_DOWN, false, data); } static DBusMessage *control_play(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_PLAY, false, data); } static DBusMessage *control_pause(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_PAUSE, false, data); } static DBusMessage *control_stop(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_STOP, false, data); } static DBusMessage *control_next(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_FORWARD, false, data); } static DBusMessage *control_previous(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_BACKWARD, false, data); } static DBusMessage *control_fast_forward(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_FAST_FORWARD, true, data); } static DBusMessage *control_rewind(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_REWIND, true, data); } static gboolean control_property_get_connected( const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct control *control = data; dbus_bool_t value = (control->session != NULL); dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); return TRUE; } static gboolean control_player_exists(const GDBusPropertyTable *property, void *data) { struct control *control = data; return control->player != NULL; } static gboolean control_get_player(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct control *control = data; if (!control->player) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &control->player); return TRUE; } static const GDBusMethodTable control_methods[] = { { GDBUS_DEPRECATED_METHOD("Play", NULL, NULL, control_play) }, { GDBUS_DEPRECATED_METHOD("Pause", NULL, NULL, control_pause) }, { GDBUS_DEPRECATED_METHOD("Stop", NULL, NULL, control_stop) }, { GDBUS_DEPRECATED_METHOD("Next", NULL, NULL, control_next) }, { GDBUS_DEPRECATED_METHOD("Previous", NULL, NULL, control_previous) }, { GDBUS_DEPRECATED_METHOD("VolumeUp", NULL, NULL, control_volume_up) }, { GDBUS_DEPRECATED_METHOD("VolumeDown", NULL, NULL, control_volume_down) }, { GDBUS_DEPRECATED_METHOD("FastForward", NULL, NULL, control_fast_forward) }, { GDBUS_DEPRECATED_METHOD("Rewind", NULL, NULL, control_rewind) }, { } }; static const GDBusPropertyTable control_properties[] = { { "Connected", "b", control_property_get_connected }, { "Player", "o", control_get_player, NULL, control_player_exists }, { } }; static void path_unregister(void *data) { struct control *control = data; DBG("Unregistered interface %s on path %s", AUDIO_CONTROL_INTERFACE, device_get_path(control->dev)); if (control->session) avctp_disconnect(control->session); avctp_remove_state_cb(control->avctp_id); if (control->target) { btd_service_set_user_data(control->target, NULL); btd_service_unref(control->target); } if (control->remote) { btd_service_set_user_data(control->remote, NULL); btd_service_unref(control->remote); } devices = g_slist_remove(devices, control); g_free(control); } void control_unregister(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); g_dbus_unregister_interface(btd_get_dbus_connection(), device_get_path(dev), AUDIO_CONTROL_INTERFACE); } static struct control *find_control(struct btd_device *dev) { GSList *l; for (l = devices; l; l = l->next) { struct control *control = l->data; if (control->dev == dev) return control; } return NULL; } static struct control *control_init(struct btd_service *service) { struct control *control; struct btd_device *dev = btd_service_get_device(service); control = find_control(dev); if (control != NULL) return control; control = g_new0(struct control, 1); if (!g_dbus_register_interface(btd_get_dbus_connection(), device_get_path(dev), AUDIO_CONTROL_INTERFACE, control_methods, NULL, control_properties, control, path_unregister)) { g_free(control); return NULL; } DBG("Registered interface %s on path %s", AUDIO_CONTROL_INTERFACE, device_get_path(dev)); control->dev = dev; control->avctp_id = avctp_add_state_cb(dev, state_changed, control); devices = g_slist_prepend(devices, control); return control; } int control_init_target(struct btd_service *service) { struct control *control; control = control_init(service); if (control == NULL) return -EINVAL; control->target = btd_service_ref(service); btd_service_set_user_data(service, control); return 0; } int control_init_remote(struct btd_service *service) { struct control *control; control = control_init(service); if (control == NULL) return -EINVAL; control->remote = btd_service_ref(service); btd_service_set_user_data(service, control); return 0; } int control_set_player(struct btd_service *service, const char *path) { struct control *control = btd_service_get_user_data(service); if (!control->session) return -ENOTCONN; if (g_strcmp0(control->player, path) == 0) return -EALREADY; control->player = path; g_dbus_emit_property_changed(btd_get_dbus_connection(), device_get_path(control->dev), AUDIO_CONTROL_INTERFACE, "Player"); return 0; } bluez-5.87/profiles/audio/PaxHeaders/avrcp.h0000644000000000000000000000005015142177620016070 xustar0020 atime=1783093618 20 ctime=1783094105 bluez-5.87/profiles/audio/avrcp.h0000644000000000000000000000707415142177620015561 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ /* player attributes */ #define AVRCP_ATTRIBUTE_ILEGAL 0x00 #define AVRCP_ATTRIBUTE_EQUALIZER 0x01 #define AVRCP_ATTRIBUTE_REPEAT_MODE 0x02 #define AVRCP_ATTRIBUTE_SHUFFLE 0x03 #define AVRCP_ATTRIBUTE_SCAN 0x04 #define AVRCP_ATTRIBUTE_LAST AVRCP_ATTRIBUTE_SCAN /* equalizer values */ #define AVRCP_EQUALIZER_OFF 0x01 #define AVRCP_EQUALIZER_ON 0x02 /* repeat mode values */ #define AVRCP_REPEAT_MODE_OFF 0x01 #define AVRCP_REPEAT_MODE_SINGLE 0x02 #define AVRCP_REPEAT_MODE_ALL 0x03 #define AVRCP_REPEAT_MODE_GROUP 0x04 /* shuffle values */ #define AVRCP_SHUFFLE_OFF 0x01 #define AVRCP_SHUFFLE_ALL 0x02 #define AVRCP_SHUFFLE_GROUP 0x03 /* scan values */ #define AVRCP_SCAN_OFF 0x01 #define AVRCP_SCAN_ALL 0x02 #define AVRCP_SCAN_GROUP 0x03 /* media attributes */ #define AVRCP_MEDIA_ATTRIBUTE_ILLEGAL 0x00 #define AVRCP_MEDIA_ATTRIBUTE_TITLE 0x01 #define AVRCP_MEDIA_ATTRIBUTE_ARTIST 0x02 #define AVRCP_MEDIA_ATTRIBUTE_ALBUM 0x03 #define AVRCP_MEDIA_ATTRIBUTE_TRACK 0x04 #define AVRCP_MEDIA_ATTRIBUTE_N_TRACKS 0x05 #define AVRCP_MEDIA_ATTRIBUTE_GENRE 0x06 #define AVRCP_MEDIA_ATTRIBUTE_DURATION 0x07 #define AVRCP_MEDIA_ATTRIBUTE_IMG_HANDLE 0x08 #define AVRCP_MEDIA_ATTRIBUTE_LAST AVRCP_MEDIA_ATTRIBUTE_IMG_HANDLE /* play status */ #define AVRCP_PLAY_STATUS_STOPPED 0x00 #define AVRCP_PLAY_STATUS_PLAYING 0x01 #define AVRCP_PLAY_STATUS_PAUSED 0x02 #define AVRCP_PLAY_STATUS_FWD_SEEK 0x03 #define AVRCP_PLAY_STATUS_REV_SEEK 0x04 #define AVRCP_PLAY_STATUS_ERROR 0xFF /* Notification events */ #define AVRCP_EVENT_STATUS_CHANGED 0x01 #define AVRCP_EVENT_TRACK_CHANGED 0x02 #define AVRCP_EVENT_TRACK_REACHED_END 0x03 #define AVRCP_EVENT_TRACK_REACHED_START 0x04 #define AVRCP_EVENT_PLAYBACK_POS_CHANGED 0x05 #define AVRCP_EVENT_SETTINGS_CHANGED 0x08 #define AVRCP_EVENT_NOW_PLAYING_CHANGED 0x09 #define AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED 0x0a #define AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED 0x0b #define AVRCP_EVENT_UIDS_CHANGED 0x0c #define AVRCP_EVENT_VOLUME_CHANGED 0x0d #define AVRCP_EVENT_LAST AVRCP_EVENT_VOLUME_CHANGED struct avrcp_player_cb { GList *(*list_settings)(void *user_data); const char *(*get_setting)(const char *key, void *user_data); int (*set_setting)(const char *key, const char *value, void *user_data); uint64_t (*get_uid)(void *user_data); const char *(*get_metadata)(const char *key, void *user_data); GList *(*list_metadata)(void *user_data); const char *(*get_status)(void *user_data); uint32_t (*get_position)(void *user_data); uint32_t (*get_duration)(void *user_data); const char *(*get_name)(void *user_data); bool (*play)(void *user_data); bool (*stop)(void *user_data); bool (*pause)(void *user_data); bool (*next)(void *user_data); bool (*previous)(void *user_data); }; int avrcp_set_volume(struct btd_device *dev, int8_t volume, bool notify); struct avrcp_player *avrcp_register_player(struct btd_adapter *adapter, struct avrcp_player_cb *cb, void *user_data, GDestroyNotify destroy); void avrcp_unregister_player(struct avrcp_player *player); void avrcp_player_event(struct avrcp_player *player, uint8_t id, const void *data); size_t avrcp_handle_vendor_reject(uint8_t *code, uint8_t *operands); size_t avrcp_browsing_general_reject(uint8_t *operands); struct avrcp_player *avrcp_get_target_player_by_device(struct btd_device *dev); void avrcp_player_init(void); void avrcp_player_exit(void); bluez-5.87/profiles/audio/PaxHeaders/gmap.c0000644000000000000000000000005015142177620015674 xustar0020 atime=1783093715 20 ctime=1783094106 bluez-5.87/profiles/audio/gmap.c0000644000000000000000000001033415142177620015356 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2025 Pauli Virtanen. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/dbus-common.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/shared/gatt-server.h" #include "src/shared/gmap.h" #include "btio/btio.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/gatt-database.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #include "src/error.h" #define GMAS_UUID_STR "00001858-0000-1000-8000-00805f9b34fb" static void gmap_debug(const char *str, void *user_data) { DBG_IDX(0xffff, "%s", str); } static void service_ready(struct bt_gmap *gmap, void *user_data) { struct btd_service *service = user_data; btd_service_connecting_complete(service, 0); } static struct bt_gmap *add_service(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct bt_gatt_client *client = btd_device_get_gatt_client(device); struct bt_gmap *gmap = btd_service_get_user_data(service); if (gmap) return gmap; gmap = bt_gmap_attach(client, service_ready, service); if (!gmap) { error("GMAP client unable to attach"); return NULL; } bt_gmap_set_debug(gmap, gmap_debug, NULL, NULL); btd_service_set_user_data(service, gmap); return gmap; } static void remove_service(struct btd_service *service) { struct bt_gmap *gmap = btd_service_get_user_data(service); if (!gmap) return; btd_service_set_user_data(service, NULL); bt_gmap_unref(gmap); } static int gmap_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct bt_gmap *gmap; char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); gmap = add_service(service); if (!gmap) return -EINVAL; return 0; } static int gmap_disconnect(struct btd_service *service) { remove_service(service); btd_service_disconnecting_complete(service, 0); return 0; } static int gmap_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); return 0; } static void gmap_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); remove_service(service); } static int gmap_adapter_probe(struct btd_profile *p, struct btd_adapter *adapter) { struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct bt_gmap *gmap; DBG("Add GMAP server %s", adapter_get_path(adapter)); gmap = bt_gmap_add_db(btd_gatt_database_get_db(database)); bt_gmap_set_debug(gmap, gmap_debug, NULL, NULL); return 0; } static void gmap_adapter_remove(struct btd_profile *p, struct btd_adapter *adapter) { struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct bt_gmap *gmap; DBG("Remove GMAP server %s", adapter_get_path(adapter)); gmap = bt_gmap_find(btd_gatt_database_get_db(database)); bt_gmap_unref(gmap); } static struct btd_profile gmap_profile = { .name = "gmap", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .remote_uuid = GMAS_UUID_STR, .device_probe = gmap_probe, .device_remove = gmap_remove, .accept = gmap_accept, .disconnect = gmap_disconnect, .adapter_probe = gmap_adapter_probe, .adapter_remove = gmap_adapter_remove, .experimental = true, }; static int gmap_init(void) { int err; err = btd_profile_register(&gmap_profile); if (err) return err; return 0; } static void gmap_exit(void) { btd_profile_unregister(&gmap_profile); } BLUETOOTH_PLUGIN_DEFINE(gmap, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, gmap_init, gmap_exit) bluez-5.87/profiles/audio/PaxHeaders/asha.h0000644000000000000000000000005015017252326015667 xustar0020 atime=1783093618 20 ctime=1783094106 bluez-5.87/profiles/audio/asha.h0000644000000000000000000000215515017252326015353 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2024 Asymptotic Inc. * * Author: Arun Raghavan * * */ #include #include #include "src/shared/asha.h" struct bt_asha_device; unsigned int bt_asha_device_start(struct bt_asha_device *asha_dev, bt_asha_cb_t cb, void *user_data); unsigned int bt_asha_device_stop(struct bt_asha_device *asha_dev); void bt_asha_device_state_reset(struct bt_asha_device *asha_dev); unsigned int bt_asha_device_device_get_resume_id( struct bt_asha_device *asha_dev); uint16_t bt_asha_device_get_render_delay(struct bt_asha_device *asha_dev); enum bt_asha_state_t bt_asha_device_get_state( struct bt_asha_device *asha_dev); int bt_asha_device_get_fd(struct bt_asha_device *asha_dev); uint16_t bt_asha_device_get_omtu(struct bt_asha_device *asha_dev); uint16_t bt_asha_device_get_imtu(struct bt_asha_device *asha_dev); int8_t bt_asha_device_get_volume(struct bt_asha_device *asha_dev); bool bt_asha_device_set_volume(struct bt_asha_device *asha_dev, int8_t volume); bluez-5.87/profiles/audio/PaxHeaders/avctp.c0000644000000000000000000000005015142177620016065 xustar0020 atime=1783093648 20 ctime=1783094105 bluez-5.87/profiles/audio/avctp.c0000644000000000000000000013644715142177620015565 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright (C) 2011 Texas Instruments, Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/l2cap.h" #include "bluetooth/uuid.h" #include "btio/btio.h" #include "src/adapter.h" #include "src/device.h" #include "src/log.h" #include "src/error.h" #include "src/shared/timeout.h" #include "src/shared/util.h" #include "src/shared/uinput.h" #include "avctp.h" #include "avrcp.h" /* AV/C Panel 1.23, page 76: * command with the pressed value is valid for two seconds */ #define AVC_PRESS_TIMEOUT 2 /* We need to send hold event before AVC_PRESS time runs out */ #define AVC_HOLD_TIMEOUT 1 #define CONTROL_TIMEOUT 10 #define BROWSING_TIMEOUT 10 #define PASSTHROUGH_QUEUE 0 #define CONTROL_QUEUE 1 #define QUIRK_NO_RELEASE 1 << 0 /* Message types */ #define AVCTP_COMMAND 0 #define AVCTP_RESPONSE 1 /* Packet types */ #define AVCTP_PACKET_SINGLE 0 #define AVCTP_PACKET_START 1 #define AVCTP_PACKET_CONTINUE 2 #define AVCTP_PACKET_END 3 #if __BYTE_ORDER == __LITTLE_ENDIAN struct avctp_header { uint8_t ipid:1; uint8_t cr:1; uint8_t packet_type:2; uint8_t transaction:4; uint16_t pid; } __attribute__ ((packed)); #define AVCTP_HEADER_LENGTH 3 struct avc_header { uint8_t code:4; uint8_t _hdr0:4; uint8_t subunit_id:3; uint8_t subunit_type:5; uint8_t opcode; } __attribute__ ((packed)); #elif __BYTE_ORDER == __BIG_ENDIAN struct avctp_header { uint8_t transaction:4; uint8_t packet_type:2; uint8_t cr:1; uint8_t ipid:1; uint16_t pid; } __attribute__ ((packed)); #define AVCTP_HEADER_LENGTH 3 struct avc_header { uint8_t _hdr0:4; uint8_t code:4; uint8_t subunit_type:5; uint8_t subunit_id:3; uint8_t opcode; } __attribute__ ((packed)); #else #error "Unknown byte order" #endif struct avctp_state_callback { avctp_state_cb cb; struct btd_device *dev; unsigned int id; void *user_data; }; struct avctp_server { struct btd_adapter *adapter; GIOChannel *control_io; GIOChannel *browsing_io; GSList *sessions; }; struct avctp_control_req { struct avctp_pending_req *p; uint8_t code; uint8_t subunit; uint8_t op; uint8_t *operands; uint16_t operand_count; avctp_rsp_cb func; void *user_data; }; struct avctp_browsing_req { struct avctp_pending_req *p; uint8_t *operands; uint16_t operand_count; avctp_browsing_rsp_cb func; void *user_data; }; typedef int (*avctp_process_cb) (void *data); struct avctp_pending_req { struct avctp_queue *queue; uint8_t transaction; unsigned int timeout; bool retry; int err; avctp_process_cb process; void *data; GDestroyNotify destroy; }; struct avctp_queue { struct avctp_channel *chan; struct avctp_pending_req *p; GQueue *queue; guint process_id; }; struct avctp_channel { struct avctp *session; GIOChannel *io; uint8_t transaction; guint watch; uint16_t imtu; uint16_t omtu; uint8_t *buffer; GSList *handlers; GSList *queues; GSList *processed; GDestroyNotify destroy; }; struct key_pressed { uint16_t op; unsigned int timer; bool hold; }; struct avctp { struct avctp_server *server; struct btd_device *device; avctp_state_t state; struct bt_uinput *uinput; guint auth_id; unsigned int passthrough_id; unsigned int unit_id; unsigned int subunit_id; struct avctp_channel *control; struct avctp_channel *browsing; struct avctp_passthrough_handler *handler; uint8_t key_quirks[256]; struct key_pressed key; bool initiator; }; struct avctp_passthrough_handler { avctp_passthrough_cb cb; void *user_data; unsigned int id; }; struct avctp_pdu_handler { uint8_t opcode; avctp_control_pdu_cb cb; void *user_data; unsigned int id; }; struct avctp_browsing_pdu_handler { avctp_browsing_pdu_cb cb; void *user_data; unsigned int id; GDestroyNotify destroy; }; static const struct bt_uinput_key_map key_map[] = { { "SELECT", AVC_SELECT, KEY_SELECT }, { "UP", AVC_UP, KEY_UP }, { "DOWN", AVC_DOWN, KEY_DOWN }, { "LEFT", AVC_LEFT, KEY_LEFT }, { "RIGHT", AVC_RIGHT, KEY_RIGHT }, { "ROOT MENU", AVC_ROOT_MENU, KEY_MENU }, { "CONTENTS MENU", AVC_CONTENTS_MENU, KEY_PROGRAM }, { "FAVORITE MENU", AVC_FAVORITE_MENU, KEY_FAVORITES }, { "EXIT", AVC_EXIT, KEY_EXIT }, { "ON DEMAND MENU", AVC_ON_DEMAND_MENU, KEY_MENU }, { "APPS MENU", AVC_APPS_MENU, KEY_MENU }, { "0", AVC_0, KEY_0 }, { "1", AVC_1, KEY_1 }, { "2", AVC_2, KEY_2 }, { "3", AVC_3, KEY_3 }, { "4", AVC_4, KEY_4 }, { "5", AVC_5, KEY_5 }, { "6", AVC_6, KEY_6 }, { "7", AVC_7, KEY_7 }, { "8", AVC_8, KEY_8 }, { "9", AVC_9, KEY_9 }, { "DOT", AVC_DOT, KEY_DOT }, { "ENTER", AVC_ENTER, KEY_ENTER }, { "CHANNEL UP", AVC_CHANNEL_UP, KEY_CHANNELUP }, { "CHANNEL DOWN", AVC_CHANNEL_DOWN, KEY_CHANNELDOWN }, { "CHANNEL PREVIOUS", AVC_CHANNEL_PREVIOUS, KEY_LAST }, { "INPUT SELECT", AVC_INPUT_SELECT, KEY_CONFIG }, { "INFO", AVC_INFO, KEY_INFO }, { "HELP", AVC_HELP, KEY_HELP }, { "POWER", AVC_POWER, KEY_POWER2 }, { "VOLUME UP", AVC_VOLUME_UP, KEY_VOLUMEUP }, { "VOLUME DOWN", AVC_VOLUME_DOWN, KEY_VOLUMEDOWN }, { "MUTE", AVC_MUTE, KEY_MUTE }, { "PLAY", AVC_PLAY, KEY_PLAYCD }, { "STOP", AVC_STOP, KEY_STOPCD }, { "PAUSE", AVC_PAUSE, KEY_PAUSECD }, { "FORWARD", AVC_FORWARD, KEY_NEXTSONG }, { "BACKWARD", AVC_BACKWARD, KEY_PREVIOUSSONG }, { "RECORD", AVC_RECORD, KEY_RECORD }, { "REWIND", AVC_REWIND, KEY_REWIND }, { "FAST FORWARD", AVC_FAST_FORWARD, KEY_FASTFORWARD }, { "LIST", AVC_LIST, KEY_LIST }, { "F1", AVC_F1, KEY_F1 }, { "F2", AVC_F2, KEY_F2 }, { "F3", AVC_F3, KEY_F3 }, { "F4", AVC_F4, KEY_F4 }, { "F5", AVC_F5, KEY_F5 }, { "F6", AVC_F6, KEY_F6 }, { "F7", AVC_F7, KEY_F7 }, { "F8", AVC_F8, KEY_F8 }, { "F9", AVC_F9, KEY_F9 }, { "RED", AVC_RED, KEY_RED }, { "GREEN", AVC_GREEN, KEY_GREEN }, { "BLUE", AVC_BLUE, KEY_BLUE }, { "YELLOW", AVC_YELLOW, KEY_YELLOW }, { NULL } }; static GSList *callbacks = NULL; static GSList *servers = NULL; static void auth_cb(DBusError *derr, void *user_data); static gboolean process_queue(gpointer user_data); static gboolean avctp_passthrough_rsp(struct avctp *session, uint8_t code, uint8_t subunit, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data); static bool auto_release(gpointer user_data) { struct avctp *session = user_data; session->key.timer = 0; DBG("AV/C: key press timeout"); bt_uinput_send_key(session->uinput, session->key.op, 0); return FALSE; } static void handle_press(struct avctp *session, uint16_t op) { if (session->key.timer > 0) { g_source_remove(session->key.timer); /* Only auto release if keys are different */ if (session->key.op == op) goto done; bt_uinput_send_key(session->uinput, session->key.op, 0); } session->key.op = op; bt_uinput_send_key(session->uinput, op, 1); done: session->key.timer = timeout_add_seconds(AVC_PRESS_TIMEOUT, auto_release, session, NULL); } static void handle_release(struct avctp *session, uint16_t op) { if (session->key.timer > 0) { timeout_remove(session->key.timer); session->key.timer = 0; } bt_uinput_send_key(session->uinput, op, 0); } static size_t handle_panel_passthrough(struct avctp *session, uint8_t transaction, uint8_t *code, uint8_t *subunit, uint8_t *operands, size_t operand_count, void *user_data) { struct avctp_passthrough_handler *handler = session->handler; const char *status; int pressed, i; if (*code != AVC_CTYPE_CONTROL || *subunit != AVC_SUBUNIT_PANEL) { *code = AVC_CTYPE_REJECTED; return operand_count; } if (operand_count == 0) goto done; if (operands[0] & 0x80) { status = "released"; pressed = 0; } else { status = "pressed"; pressed = 1; } if (session->key.timer == 0 && handler != NULL) { if (handler->cb(session, operands[0] & 0x7F, pressed, handler->user_data)) goto done; } for (i = 0; key_map[i].name != NULL; i++) { uint8_t key_quirks; if ((operands[0] & 0x7F) != key_map[i].code) continue; DBG("AV/C: %s %s", key_map[i].name, status); key_quirks = session->key_quirks[key_map[i].code]; if (key_quirks & QUIRK_NO_RELEASE) { if (!pressed) { DBG("AV/C: Ignoring release"); break; } DBG("AV/C: treating key press as press + release"); bt_uinput_send_key(session->uinput, key_map[i].uinput, 1); bt_uinput_send_key(session->uinput, key_map[i].uinput, 0); break; } if (pressed) handle_press(session, key_map[i].uinput); else handle_release(session, key_map[i].uinput); break; } if (key_map[i].name == NULL) { DBG("AV/C: unknown button 0x%02X %s", operands[0] & 0x7F, status); *code = AVC_CTYPE_NOT_IMPLEMENTED; return operand_count; } done: *code = AVC_CTYPE_ACCEPTED; return operand_count; } static size_t handle_unit_info(struct avctp *session, uint8_t transaction, uint8_t *code, uint8_t *subunit, uint8_t *operands, size_t operand_count, void *user_data) { if (*code != AVC_CTYPE_STATUS) { *code = AVC_CTYPE_REJECTED; return 0; } *code = AVC_CTYPE_STABLE; /* The first operand should be 0x07 for the UNITINFO response. * Neither AVRCP (section 22.1, page 117) nor AVC Digital * Interface Command Set (section 9.2.1, page 45) specs * explain this value but both use it */ if (operand_count >= 1) operands[0] = 0x07; if (operand_count >= 2) operands[1] = AVC_SUBUNIT_PANEL << 3; DBG("reply to AVC_OP_UNITINFO"); return operand_count; } static size_t handle_subunit_info(struct avctp *session, uint8_t transaction, uint8_t *code, uint8_t *subunit, uint8_t *operands, size_t operand_count, void *user_data) { if (*code != AVC_CTYPE_STATUS) { *code = AVC_CTYPE_REJECTED; return 0; } *code = AVC_CTYPE_STABLE; /* The first operand should be 0x07 for the UNITINFO response. * Neither AVRCP (section 22.1, page 117) nor AVC Digital * Interface Command Set (section 9.2.1, page 45) specs * explain this value but both use it */ if (operand_count >= 2) operands[1] = AVC_SUBUNIT_PANEL << 3; DBG("reply to AVC_OP_SUBUNITINFO"); return operand_count; } static struct avctp_pdu_handler *find_handler(GSList *list, uint8_t opcode) { for (; list; list = list->next) { struct avctp_pdu_handler *handler = list->data; if (handler->opcode == opcode) return handler; } return NULL; } static void pending_destroy(gpointer data, gpointer user_data) { struct avctp_pending_req *req = data; if (req->destroy) req->destroy(req->data); if (req->timeout > 0) timeout_remove(req->timeout); g_free(req); } static void avctp_queue_destroy(void *data) { struct avctp_queue *queue = data; if (queue->process_id > 0) g_source_remove(queue->process_id); if (queue->p) pending_destroy(queue->p, NULL); g_queue_foreach(queue->queue, pending_destroy, NULL); g_queue_free(queue->queue); g_free(queue); } static void avctp_channel_destroy(struct avctp_channel *chan) { g_io_channel_shutdown(chan->io, TRUE, NULL); g_io_channel_unref(chan->io); if (chan->watch) g_source_remove(chan->watch); if (chan->destroy) chan->destroy(chan); g_free(chan->buffer); g_slist_foreach(chan->processed, pending_destroy, NULL); g_slist_free(chan->processed); g_slist_free_full(chan->queues, avctp_queue_destroy); g_slist_free_full(chan->handlers, g_free); g_free(chan); } static void avctp_disconnected(struct avctp *session) { struct avctp_server *server; if (!session) return; if (session->browsing) avctp_channel_destroy(session->browsing); if (session->control) avctp_channel_destroy(session->control); if (session->auth_id != 0) { btd_cancel_authorization(session->auth_id); session->auth_id = 0; } if (session->key.timer > 0) timeout_remove(session->key.timer); if (session->uinput) { char address[18]; ba2str(device_get_address(session->device), address); DBG("AVCTP: closing uinput for %s", address); bt_uinput_destroy(session->uinput); session->uinput = NULL; } server = session->server; server->sessions = g_slist_remove(server->sessions, session); btd_device_unref(session->device); g_free(session); } static void avctp_set_state(struct avctp *session, avctp_state_t new_state, int err) { GSList *l; avctp_state_t old_state = session->state; session->state = new_state; for (l = callbacks; l != NULL; l = l->next) { struct avctp_state_callback *cb = l->data; if (cb->dev && cb->dev != session->device) continue; cb->cb(session->device, old_state, new_state, err, cb->user_data); } switch (new_state) { case AVCTP_STATE_DISCONNECTED: DBG("AVCTP Disconnected"); avctp_disconnected(session); break; case AVCTP_STATE_CONNECTING: DBG("AVCTP Connecting"); break; case AVCTP_STATE_CONNECTED: DBG("AVCTP Connected"); break; case AVCTP_STATE_BROWSING_CONNECTING: DBG("AVCTP Browsing Connecting"); break; case AVCTP_STATE_BROWSING_CONNECTED: DBG("AVCTP Browsing Connected"); break; default: error("Invalid AVCTP state %d", new_state); return; } } static uint8_t chan_get_transaction(struct avctp_channel *chan) { GSList *l, *tmp; uint8_t transaction; if (!chan->processed) goto done; tmp = g_slist_copy(chan->processed); /* Find first unused transaction id */ for (l = tmp; l; l = g_slist_next(l)) { struct avctp_pending_req *req = l->data; if (req->transaction == chan->transaction) { chan->transaction++; chan->transaction %= 16; tmp = g_slist_delete_link(tmp, l); l = tmp; } } g_slist_free(tmp); done: transaction = chan->transaction; chan->transaction++; chan->transaction %= 16; return transaction; } static int avctp_send(struct avctp_channel *control, uint8_t transaction, uint8_t cr, uint8_t code, uint8_t subunit, uint8_t opcode, uint8_t *operands, size_t operand_count) { struct avctp_header *avctp; struct avc_header *avc; struct msghdr msg; struct iovec iov[2]; int sk, err = 0; iov[0].iov_base = control->buffer; iov[0].iov_len = sizeof(*avctp) + sizeof(*avc); iov[1].iov_base = operands; iov[1].iov_len = operand_count; if (control->omtu < (iov[0].iov_len + iov[1].iov_len)) return -EOVERFLOW; sk = g_io_channel_unix_get_fd(control->io); memset(control->buffer, 0, iov[0].iov_len); avctp = (void *) control->buffer; avc = (void *) avctp + sizeof(*avctp); if (transaction > 16) transaction = chan_get_transaction(control); avctp->transaction = transaction; avctp->packet_type = AVCTP_PACKET_SINGLE; avctp->cr = cr; avctp->pid = htons(AV_REMOTE_SVCLASS_ID); avc->code = code; avc->subunit_type = subunit; avc->opcode = opcode; memset(&msg, 0, sizeof(msg)); msg.msg_iov = iov; msg.msg_iovlen = 2; if (sendmsg(sk, &msg, 0) < 0) err = -errno; return err; } static int avctp_browsing_send(struct avctp_queue *queue, uint8_t transaction, uint8_t cr, uint8_t *operands, size_t operand_count) { struct avctp_channel *browsing = queue->chan; struct avctp_header *avctp; struct msghdr msg; struct iovec iov[2]; int sk, err = 0; iov[0].iov_base = browsing->buffer; iov[0].iov_len = sizeof(*avctp); iov[1].iov_base = operands; iov[1].iov_len = operand_count; if (browsing->omtu < (iov[0].iov_len + iov[1].iov_len)) return -EOVERFLOW; sk = g_io_channel_unix_get_fd(browsing->io); memset(browsing->buffer, 0, iov[0].iov_len); avctp = (void *) browsing->buffer; avctp->transaction = transaction; avctp->packet_type = AVCTP_PACKET_SINGLE; avctp->cr = cr; avctp->pid = htons(AV_REMOTE_SVCLASS_ID); memset(&msg, 0, sizeof(msg)); msg.msg_iov = iov; msg.msg_iovlen = 2; if (sendmsg(sk, &msg, 0) < 0) err = -errno; return err; } static void control_req_destroy(void *data) { struct avctp_control_req *req = data; struct avctp_pending_req *p = req->p; struct avctp *session = p->queue->chan->session; if (p->err == 0 || req->func == NULL) goto done; req->func(session, AVC_CTYPE_REJECTED, req->subunit, p->transaction, NULL, 0, req->user_data); done: free(req->operands); g_free(req); } static void browsing_req_destroy(void *data) { struct avctp_browsing_req *req = data; struct avctp_pending_req *p = req->p; struct avctp *session = p->queue->chan->session; if (p->err == 0 || req->func == NULL) goto done; req->func(session, NULL, 0, req->user_data); done: free(req->operands); g_free(req); } static bool req_timeout(gpointer user_data) { struct avctp_queue *queue = user_data; struct avctp_pending_req *p = queue->p; DBG("transaction %u retry %s", p->transaction, p->retry ? "true" : "false"); p->timeout = 0; if (p->retry) { p->process(p->data); return FALSE; } p->err = -ETIMEDOUT; pending_destroy(p, NULL); queue->p = NULL; if (queue->process_id == 0) queue->process_id = g_idle_add(process_queue, queue); return FALSE; } static int process_passthrough(void *data) { struct avctp_control_req *req = data; struct avctp_pending_req *p = req->p; int ret; ret = avctp_send(p->queue->chan, p->transaction, AVCTP_COMMAND, req->code, req->subunit, req->op, req->operands, req->operand_count); if (ret < 0) return ret; p->timeout = timeout_add_seconds(AVC_PRESS_TIMEOUT, req_timeout, p->queue, NULL); return 0; } static int process_control(void *data) { struct avctp_control_req *req = data; struct avctp_pending_req *p = req->p; int ret; ret = avctp_send(p->queue->chan, p->transaction, AVCTP_COMMAND, req->code, req->subunit, req->op, req->operands, req->operand_count); if (ret < 0) return ret; p->retry = !p->retry; p->timeout = timeout_add_seconds(CONTROL_TIMEOUT, req_timeout, p->queue, NULL); return 0; } static int process_browsing(void *data) { struct avctp_browsing_req *req = data; struct avctp_pending_req *p = req->p; int ret; ret = avctp_browsing_send(p->queue, p->transaction, AVCTP_COMMAND, req->operands, req->operand_count); if (ret < 0) return ret; p->timeout = timeout_add_seconds(BROWSING_TIMEOUT, req_timeout, p->queue, NULL); return 0; } static gboolean process_queue(void *user_data) { struct avctp_queue *queue = user_data; struct avctp_pending_req *p = queue->p; queue->process_id = 0; if (p != NULL) return FALSE; while ((p = g_queue_pop_head(queue->queue))) { if (p->process(p->data) == 0) break; pending_destroy(p, NULL); } if (p == NULL) return FALSE; queue->p = p; return FALSE; } static void control_response(struct avctp_channel *control, struct avctp_header *avctp, struct avc_header *avc, uint8_t *operands, size_t operand_count) { struct avctp_pending_req *p; struct avctp_control_req *req; struct avctp_queue *queue; GSList *l; if (avc->opcode == AVC_OP_PASSTHROUGH) queue = g_slist_nth_data(control->queues, PASSTHROUGH_QUEUE); else queue = g_slist_nth_data(control->queues, CONTROL_QUEUE); p = queue->p; if (p && p->transaction == avctp->transaction) { req = p->data; if (req->op != avc->opcode) goto done; control->processed = g_slist_prepend(control->processed, p); if (p->timeout > 0) { timeout_remove(p->timeout); p->timeout = 0; } queue->p = NULL; if (queue->process_id == 0) queue->process_id = g_idle_add(process_queue, queue); } done: for (l = control->processed; l; l = l->next) { p = l->data; req = p->data; if (p->transaction != avctp->transaction) continue; if (req->op != avc->opcode) continue; if (req->func && req->func(control->session, avc->code, avc->subunit_type, p->transaction, operands, operand_count, req->user_data)) return; control->processed = g_slist_remove(control->processed, p); pending_destroy(p, NULL); return; } } static void browsing_response(struct avctp_channel *browsing, struct avctp_header *avctp, uint8_t *operands, size_t operand_count) { struct avctp_pending_req *p; struct avctp_browsing_req *req; struct avctp_queue *queue; GSList *l; queue = g_slist_nth_data(browsing->queues, 0); p = queue->p; if (p && p->transaction == avctp->transaction) { browsing->processed = g_slist_prepend(browsing->processed, p); if (p->timeout > 0) { timeout_remove(p->timeout); p->timeout = 0; } queue->p = NULL; if (queue->process_id == 0) queue->process_id = g_idle_add(process_queue, queue); } for (l = browsing->processed; l; l = l->next) { p = l->data; req = p->data; if (p->transaction != avctp->transaction) continue; if (req->func && req->func(browsing->session, operands, operand_count, req->user_data)) return; browsing->processed = g_slist_remove(browsing->processed, p); pending_destroy(p, NULL); return; } } static gboolean session_browsing_cb(GIOChannel *chan, GIOCondition cond, gpointer data) { struct avctp *session = data; struct avctp_channel *browsing = session->browsing; uint8_t *buf = browsing->buffer; uint8_t *operands; struct avctp_header *avctp; int sock, ret, packet_size, operand_count; struct avctp_browsing_pdu_handler *handler; if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) goto failed; sock = g_io_channel_unix_get_fd(chan); ret = read(sock, buf, browsing->imtu); if (ret <= 0) goto failed; avctp = (struct avctp_header *) buf; if (avctp->packet_type != AVCTP_PACKET_SINGLE) goto failed; operands = buf + AVCTP_HEADER_LENGTH; ret -= AVCTP_HEADER_LENGTH; operand_count = ret; if (avctp->cr == AVCTP_RESPONSE) { browsing_response(browsing, avctp, operands, operand_count); return TRUE; } packet_size = AVCTP_HEADER_LENGTH; avctp->cr = AVCTP_RESPONSE; handler = g_slist_nth_data(browsing->handlers, 0); if (handler == NULL) { DBG("handler not found"); packet_size += avrcp_browsing_general_reject(operands); goto send; } packet_size += handler->cb(session, avctp->transaction, operands, operand_count, handler->user_data); send: if (packet_size != 0) { ret = write(sock, buf, packet_size); if (ret != packet_size) goto failed; } return TRUE; failed: DBG("AVCTP Browsing: disconnected"); avctp_set_state(session, AVCTP_STATE_CONNECTED, 0); if (session->browsing) { avctp_channel_destroy(session->browsing); session->browsing = NULL; } return FALSE; } static gboolean session_cb(GIOChannel *chan, GIOCondition cond, gpointer data) { struct avctp *session = data; struct avctp_channel *control = session->control; uint8_t *buf = control->buffer; uint8_t *operands, code, subunit; struct avctp_header *avctp; struct avc_header *avc; int ret, packet_size, operand_count, sock; struct avctp_pdu_handler *handler; if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) goto failed; sock = g_io_channel_unix_get_fd(chan); ret = read(sock, buf, control->imtu); if (ret <= 0) goto failed; if (ret < AVCTP_HEADER_LENGTH) { error("Too small AVCTP packet"); goto failed; } avctp = (struct avctp_header *) buf; ret -= AVCTP_HEADER_LENGTH; if (ret < AVC_HEADER_LENGTH) { error("Too small AVC packet"); goto failed; } avc = (struct avc_header *) (buf + AVCTP_HEADER_LENGTH); ret -= AVC_HEADER_LENGTH; operands = (uint8_t *) avc + AVC_HEADER_LENGTH; operand_count = ret; if (avctp->cr == AVCTP_RESPONSE) { control_response(control, avctp, avc, operands, operand_count); return TRUE; } packet_size = AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH; avctp->cr = AVCTP_RESPONSE; if (avctp->packet_type != AVCTP_PACKET_SINGLE) { avc->code = AVC_CTYPE_NOT_IMPLEMENTED; goto done; } if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) { avctp->ipid = 1; packet_size = AVCTP_HEADER_LENGTH; goto done; } handler = find_handler(control->handlers, avc->opcode); if (!handler) { DBG("handler not found for 0x%02x", avc->opcode); packet_size += avrcp_handle_vendor_reject(&code, operands); avc->code = code; goto done; } code = avc->code; subunit = avc->subunit_type; packet_size += handler->cb(session, avctp->transaction, &code, &subunit, operands, operand_count, handler->user_data); avc->code = code; avc->subunit_type = subunit; done: ret = write(sock, buf, packet_size); if (ret != packet_size) goto failed; return TRUE; failed: DBG("AVCTP session %p got disconnected", session); avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); return FALSE; } static void uinput_debug(const char *str, void *data) { DBG_IDX(0xffff, "%s", str); } static void init_uinput(struct avctp *session) { struct btd_adapter *adapter = device_get_adapter(session->device); char name[UINPUT_MAX_NAME_SIZE]; struct input_id id; int err; device_get_name(session->device, name, sizeof(name)); if (g_str_equal(name, "Nokia CK-20W")) { session->key_quirks[AVC_FORWARD] |= QUIRK_NO_RELEASE; session->key_quirks[AVC_BACKWARD] |= QUIRK_NO_RELEASE; session->key_quirks[AVC_PLAY] |= QUIRK_NO_RELEASE; session->key_quirks[AVC_PAUSE] |= QUIRK_NO_RELEASE; } id.bustype = BUS_BLUETOOTH; id.vendor = btd_device_get_vendor(session->device); id.product = btd_device_get_product(session->device); id.version = btd_device_get_version(session->device); session->uinput = bt_uinput_new(name, " (AVRCP)", btd_adapter_get_address(adapter), &id); bt_uinput_set_debug(session->uinput, uinput_debug, NULL); err = bt_uinput_create(session->uinput, key_map); if (err < 0) { error("AVRCP: failed to create uinput for %s: %s", name, strerror(-err)); bt_uinput_destroy(session->uinput); session->uinput = NULL; } else { DBG("AVRCP: uinput initialized for %s", name); } } static struct avctp_queue *avctp_queue_create(struct avctp_channel *chan) { struct avctp_queue *queue; queue = g_new0(struct avctp_queue, 1); queue->chan = chan; queue->queue = g_queue_new(); return queue; } static struct avctp_channel *avctp_channel_create(struct avctp *session, GIOChannel *io, int queues, GDestroyNotify destroy) { struct avctp_channel *chan; chan = g_new0(struct avctp_channel, 1); chan->session = session; chan->io = g_io_channel_ref(io); chan->destroy = destroy; while (queues--) { struct avctp_queue *queue; queue = avctp_queue_create(chan); chan->queues = g_slist_prepend(chan->queues, queue); } return chan; } static void handler_free(void *data) { struct avctp_browsing_pdu_handler *handler = data; if (handler->destroy) handler->destroy(handler->user_data); g_free(data); } static void avctp_destroy_browsing(void *data) { struct avctp_channel *chan = data; g_slist_free_full(chan->handlers, handler_free); chan->handlers = NULL; } static void avctp_connect_browsing_cb(GIOChannel *chan, GError *err, gpointer data) { struct avctp *session = data; struct avctp_channel *browsing = session->browsing; struct avctp_queue *queue; char address[18]; uint16_t imtu, omtu; GError *gerr = NULL; if (err) { error("Browsing: %s", err->message); goto fail; } bt_io_get(chan, &gerr, BT_IO_OPT_DEST, &address, BT_IO_OPT_IMTU, &imtu, BT_IO_OPT_OMTU, &omtu, BT_IO_OPT_INVALID); if (gerr) { error("%s", gerr->message); g_io_channel_shutdown(chan, TRUE, NULL); g_io_channel_unref(chan); g_error_free(gerr); goto fail; } DBG("AVCTP Browsing: connected to %s", address); if (browsing == NULL) { browsing = avctp_channel_create(session, chan, 1, avctp_destroy_browsing); session->browsing = browsing; } browsing->imtu = imtu; browsing->omtu = omtu; browsing->buffer = g_malloc0(MAX(imtu, omtu)); browsing->watch = g_io_add_watch(session->browsing->io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, (GIOFunc) session_browsing_cb, session); avctp_set_state(session, AVCTP_STATE_BROWSING_CONNECTED, 0); /* Process any request that was pending the connection to complete */ queue = g_slist_nth_data(browsing->queues, 0); if (queue->process_id == 0 && !g_queue_is_empty(queue->queue)) queue->process_id = g_idle_add(process_queue, queue); return; fail: avctp_set_state(session, AVCTP_STATE_CONNECTED, 0); if (session->browsing) { avctp_channel_destroy(session->browsing); session->browsing = NULL; } } static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data) { struct avctp *session = data; char address[18]; uint16_t imtu, omtu; GError *gerr = NULL; if (err) { avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); error("%s", err->message); return; } bt_io_get(chan, &gerr, BT_IO_OPT_DEST, &address, BT_IO_OPT_IMTU, &imtu, BT_IO_OPT_OMTU, &omtu, BT_IO_OPT_INVALID); if (gerr) { avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); error("%s", gerr->message); g_error_free(gerr); return; } DBG("AVCTP: connected to %s", address); if (session->control == NULL) session->control = avctp_channel_create(session, chan, 2, NULL); session->control->imtu = imtu; session->control->omtu = omtu; session->control->buffer = g_malloc0(MAX(imtu, omtu)); session->control->watch = g_io_add_watch(session->control->io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, (GIOFunc) session_cb, session); session->passthrough_id = avctp_register_pdu_handler(session, AVC_OP_PASSTHROUGH, handle_panel_passthrough, NULL); session->unit_id = avctp_register_pdu_handler(session, AVC_OP_UNITINFO, handle_unit_info, NULL); session->subunit_id = avctp_register_pdu_handler(session, AVC_OP_SUBUNITINFO, handle_subunit_info, NULL); init_uinput(session); avctp_set_state(session, AVCTP_STATE_CONNECTED, 0); } static void auth_cb(DBusError *derr, void *user_data) { struct avctp *session = user_data; GError *err = NULL; session->auth_id = 0; if (session->control->watch > 0) { g_source_remove(session->control->watch); session->control->watch = 0; } if (derr && dbus_error_is_set(derr)) { error("Access denied: %s", derr->message); avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); return; } if (!bt_io_accept(session->control->io, avctp_connect_cb, session, NULL, &err)) { error("bt_io_accept: %s", err->message); g_error_free(err); avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); } } static struct avctp_server *find_server(GSList *list, struct btd_adapter *a) { for (; list; list = list->next) { struct avctp_server *server = list->data; if (server->adapter == a) return server; } return NULL; } static struct avctp *find_session(GSList *list, struct btd_device *device) { for (; list != NULL; list = g_slist_next(list)) { struct avctp *s = list->data; if (s->device == device) return s; } return NULL; } static struct avctp *avctp_get_internal(struct btd_device *device) { struct avctp_server *server; struct avctp *session; server = find_server(servers, device_get_adapter(device)); if (server == NULL) return NULL; session = find_session(server->sessions, device); if (session) return session; session = g_new0(struct avctp, 1); session->server = server; session->device = btd_device_ref(device); session->state = AVCTP_STATE_DISCONNECTED; session->key.op = AVC_INVALID; server->sessions = g_slist_append(server->sessions, session); return session; } static void avctp_control_confirm(struct avctp *session, GIOChannel *chan, struct btd_device *dev) { const bdaddr_t *src; const bdaddr_t *dst; if (session->control != NULL) { error("Control: Refusing unexpected connect"); g_io_channel_shutdown(chan, TRUE, NULL); /* * Close AVCTP channel if remote tried connect * at the same time * AVRCP SPEC V1.5 4.1.1 Connection Establishment */ avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EAGAIN); return; } avctp_set_state(session, AVCTP_STATE_CONNECTING, 0); session->control = avctp_channel_create(session, chan, 2, NULL); src = btd_adapter_get_address(device_get_adapter(dev)); dst = device_get_address(dev); session->auth_id = btd_request_authorization(src, dst, AVRCP_REMOTE_UUID, auth_cb, session); if (session->auth_id == 0) goto drop; session->control->watch = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL, session_cb, session); return; drop: avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); } static void avctp_browsing_confirm(struct avctp *session, GIOChannel *chan, struct btd_device *dev) { GError *err = NULL; if (session->control == NULL || session->browsing != NULL) { error("Browsing: Refusing unexpected connect"); g_io_channel_shutdown(chan, TRUE, NULL); return; } if (bt_io_accept(chan, avctp_connect_browsing_cb, session, NULL, &err)) { avctp_set_state(session, AVCTP_STATE_BROWSING_CONNECTING, 0); session->browsing = avctp_channel_create(session, chan, 1, avctp_destroy_browsing); return; } error("Browsing: %s", err->message); g_error_free(err); return; } static void avctp_confirm_cb(GIOChannel *chan, gpointer data) { struct avctp *session; char address[18]; bdaddr_t src, dst; GError *err = NULL; uint16_t psm; struct btd_device *device; bt_io_get(chan, &err, BT_IO_OPT_SOURCE_BDADDR, &src, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_DEST, address, BT_IO_OPT_PSM, &psm, BT_IO_OPT_INVALID); if (err) { error("%s", err->message); g_error_free(err); g_io_channel_shutdown(chan, TRUE, NULL); return; } DBG("AVCTP: incoming connect from %s", address); device = btd_adapter_find_device(adapter_find(&src), &dst, BDADDR_BREDR); if (!device) return; session = avctp_get_internal(device); if (session == NULL) return; if (btd_device_get_service(device, AVRCP_REMOTE_UUID) == NULL) btd_device_add_uuid(device, AVRCP_REMOTE_UUID); if (btd_device_get_service(device, AVRCP_TARGET_UUID) == NULL) btd_device_add_uuid(device, AVRCP_TARGET_UUID); switch (psm) { case AVCTP_CONTROL_PSM: avctp_control_confirm(session, chan, device); break; case AVCTP_BROWSING_PSM: avctp_browsing_confirm(session, chan, device); break; } return; } static GIOChannel *avctp_server_socket(const bdaddr_t *src, gboolean central, uint8_t mode, uint16_t psm) { GError *err = NULL; GIOChannel *io; io = bt_io_listen(NULL, avctp_confirm_cb, NULL, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_PSM, psm, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_CENTRAL, central, BT_IO_OPT_MODE, mode, BT_IO_OPT_INVALID); if (!io) { error("%s", err->message); g_error_free(err); } return io; } int avctp_register(struct btd_adapter *adapter, bool central, bool *browsing) { struct avctp_server *server; const bdaddr_t *src = btd_adapter_get_address(adapter); server = g_new0(struct avctp_server, 1); server->control_io = avctp_server_socket(src, central, BT_IO_MODE_BASIC, AVCTP_CONTROL_PSM); if (!server->control_io) { g_free(server); return -1; } server->browsing_io = avctp_server_socket(src, central, BT_IO_MODE_ERTM, AVCTP_BROWSING_PSM); if (browsing) *browsing = server->browsing_io ? true : false; server->adapter = btd_adapter_ref(adapter); servers = g_slist_append(servers, server); return 0; } void avctp_unregister(struct btd_adapter *adapter) { struct avctp_server *server; server = find_server(servers, adapter); if (!server) return; while (server->sessions) avctp_disconnected(server->sessions->data); servers = g_slist_remove(servers, server); if (server->browsing_io) { g_io_channel_shutdown(server->browsing_io, TRUE, NULL); g_io_channel_unref(server->browsing_io); server->browsing_io = NULL; } g_io_channel_shutdown(server->control_io, TRUE, NULL); g_io_channel_unref(server->control_io); btd_adapter_unref(server->adapter); g_free(server); } static struct avctp_pending_req *pending_create(struct avctp_queue *queue, avctp_process_cb process, void *data, GDestroyNotify destroy) { struct avctp_pending_req *p; p = g_new0(struct avctp_pending_req, 1); p->queue = queue; p->transaction = chan_get_transaction(queue->chan); p->process = process; p->data = data; p->destroy = destroy; return p; } static int avctp_send_req(struct avctp *session, uint8_t code, uint8_t subunit, uint8_t opcode, uint8_t *operands, size_t operand_count, avctp_rsp_cb func, void *user_data) { struct avctp_channel *control = session->control; struct avctp_queue *queue; struct avctp_pending_req *p; struct avctp_control_req *req; if (control == NULL) return -ENOTCONN; /* If the request set a callback send it directly */ if (!func) return avctp_send(session->control, -1, AVCTP_COMMAND, code, subunit, opcode, operands, operand_count); req = g_new0(struct avctp_control_req, 1); req->code = code; req->subunit = subunit; req->op = opcode; req->func = func; req->operands = util_memdup(operands, operand_count); req->operand_count = operand_count; req->user_data = user_data; if (opcode == AVC_OP_PASSTHROUGH) { queue = g_slist_nth_data(control->queues, PASSTHROUGH_QUEUE); p = pending_create(queue, process_passthrough, req, control_req_destroy); } else { queue = g_slist_nth_data(control->queues, CONTROL_QUEUE); p = pending_create(queue, process_control, req, control_req_destroy); } req->p = p; g_queue_push_tail(queue->queue, p); if (queue->process_id == 0) queue->process_id = g_idle_add(process_queue, queue); return 0; } int avctp_send_browsing_req(struct avctp *session, uint8_t *operands, size_t operand_count, avctp_browsing_rsp_cb func, void *user_data) { struct avctp_channel *browsing = session->browsing; struct avctp_queue *queue; struct avctp_pending_req *p; struct avctp_browsing_req *req; if (browsing == NULL) return -ENOTCONN; req = g_new0(struct avctp_browsing_req, 1); req->func = func; req->operands = util_memdup(operands, operand_count); req->operand_count = operand_count; req->user_data = user_data; queue = g_slist_nth_data(browsing->queues, 0); p = pending_create(queue, process_browsing, req, browsing_req_destroy); req->p = p; g_queue_push_tail(queue->queue, p); /* Connection did not complete, delay process of the request */ if (browsing->watch == 0) return 0; if (queue->process_id == 0) queue->process_id = g_idle_add(process_queue, queue); return 0; } static const char *op2str(uint8_t op) { int i; for (i = 0; key_map[i].name != NULL; i++) { if ((op & 0x7F) == key_map[i].code) return key_map[i].name; } return "UNKNOWN"; } static int avctp_passthrough_press(struct avctp *session, uint8_t op) { uint8_t operands[2]; DBG("%s", op2str(op)); /* Button pressed */ operands[0] = op & 0x7f; operands[1] = 0; return avctp_send_req(session, AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH, operands, sizeof(operands), avctp_passthrough_rsp, NULL); } static int avctp_passthrough_release(struct avctp *session, uint8_t op) { uint8_t operands[2]; DBG("%s", op2str(op)); /* Button released */ operands[0] = op | 0x80; operands[1] = 0; return avctp_send_req(session, AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH, operands, sizeof(operands), NULL, NULL); } static bool repeat_timeout(gpointer user_data) { struct avctp *session = user_data; avctp_passthrough_press(session, session->key.op); return TRUE; } static int release_pressed(struct avctp *session) { int ret = avctp_passthrough_release(session, session->key.op); if (session->key.timer > 0) timeout_remove(session->key.timer); session->key.timer = 0; session->key.op = AVC_INVALID; session->key.hold = false; return ret; } static bool hold_pressed(struct avctp *session, uint8_t op) { if (session->key.op != op || !session->key.hold) return FALSE; if (session->key.timer == 0) session->key.timer = timeout_add_seconds(AVC_HOLD_TIMEOUT, repeat_timeout, session, NULL); return TRUE; } static gboolean avctp_passthrough_rsp(struct avctp *session, uint8_t code, uint8_t subunit, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data) { uint8_t op = operands[0]; if (code != AVC_CTYPE_ACCEPTED) return FALSE; if (hold_pressed(session, op)) return FALSE; if (op == session->key.op) release_pressed(session); return FALSE; } int avctp_send_passthrough(struct avctp *session, uint8_t op, bool hold) { if (op & 0x80) return -EINVAL; /* Release previously unreleased key */ if (session->key.op != AVC_INVALID && session->key.op != op) release_pressed(session); session->key.op = op; session->key.hold = hold; return avctp_passthrough_press(session, op); } int avctp_send_release_passthrough(struct avctp *session) { if (session->key.op != AVC_INVALID) return release_pressed(session); return 0; } int avctp_send_vendordep(struct avctp *session, uint8_t transaction, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count) { struct avctp_channel *control = session->control; if (control == NULL) return -ENOTCONN; return avctp_send(control, transaction, AVCTP_RESPONSE, code, subunit, AVC_OP_VENDORDEP, operands, operand_count); } int avctp_send_vendordep_req(struct avctp *session, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, avctp_rsp_cb func, void *user_data) { return avctp_send_req(session, code, subunit, AVC_OP_VENDORDEP, operands, operand_count, func, user_data); } unsigned int avctp_add_state_cb(struct btd_device *dev, avctp_state_cb cb, void *user_data) { struct avctp_state_callback *state_cb; static unsigned int id = 0; state_cb = g_new(struct avctp_state_callback, 1); state_cb->cb = cb; state_cb->dev = dev; state_cb->id = ++id; state_cb->user_data = user_data; callbacks = g_slist_append(callbacks, state_cb); return state_cb->id; } gboolean avctp_remove_state_cb(unsigned int id) { GSList *l; for (l = callbacks; l != NULL; l = l->next) { struct avctp_state_callback *cb = l->data; if (cb && cb->id == id) { callbacks = g_slist_remove(callbacks, cb); g_free(cb); return TRUE; } } return FALSE; } unsigned int avctp_register_passthrough_handler(struct avctp *session, avctp_passthrough_cb cb, void *user_data) { struct avctp_channel *control = session->control; struct avctp_passthrough_handler *handler; static unsigned int id = 0; if (control == NULL || session->handler != NULL) return 0; handler = g_new(struct avctp_passthrough_handler, 1); handler->cb = cb; handler->user_data = user_data; handler->id = ++id; session->handler = handler; return handler->id; } bool avctp_unregister_passthrough_handler(unsigned int id) { GSList *l; for (l = servers; l; l = l->next) { struct avctp_server *server = l->data; GSList *s; for (s = server->sessions; s; s = s->next) { struct avctp *session = s->data; if (session->handler == NULL) continue; if (session->handler->id == id) { g_free(session->handler); session->handler = NULL; return true; } } } return false; } unsigned int avctp_register_pdu_handler(struct avctp *session, uint8_t opcode, avctp_control_pdu_cb cb, void *user_data) { struct avctp_channel *control = session->control; struct avctp_pdu_handler *handler; static unsigned int id = 0; if (control == NULL) return 0; handler = find_handler(control->handlers, opcode); if (handler) return 0; handler = g_new(struct avctp_pdu_handler, 1); handler->opcode = opcode; handler->cb = cb; handler->user_data = user_data; handler->id = ++id; control->handlers = g_slist_append(control->handlers, handler); return handler->id; } unsigned int avctp_register_browsing_pdu_handler(struct avctp *session, avctp_browsing_pdu_cb cb, void *user_data, GDestroyNotify destroy) { struct avctp_channel *browsing = session->browsing; struct avctp_browsing_pdu_handler *handler; static unsigned int id = 0; if (browsing == NULL) return 0; if (browsing->handlers != NULL) return 0; handler = g_new(struct avctp_browsing_pdu_handler, 1); handler->cb = cb; handler->user_data = user_data; handler->id = ++id; handler->destroy = destroy; browsing->handlers = g_slist_append(browsing->handlers, handler); return handler->id; } gboolean avctp_unregister_pdu_handler(unsigned int id) { GSList *l; for (l = servers; l; l = l->next) { struct avctp_server *server = l->data; GSList *s; for (s = server->sessions; s; s = s->next) { struct avctp *session = s->data; struct avctp_channel *control = session->control; GSList *h; if (control == NULL) continue; for (h = control->handlers; h; h = h->next) { struct avctp_pdu_handler *handler = h->data; if (handler->id != id) continue; control->handlers = g_slist_remove( control->handlers, handler); g_free(handler); return TRUE; } } } return FALSE; } gboolean avctp_unregister_browsing_pdu_handler(unsigned int id) { GSList *l; for (l = servers; l; l = l->next) { struct avctp_server *server = l->data; GSList *s; for (s = server->sessions; s; s = s->next) { struct avctp *session = s->data; struct avctp_channel *browsing = session->browsing; GSList *h; if (browsing == NULL) continue; for (h = browsing->handlers; h; h = h->next) { struct avctp_browsing_pdu_handler *handler = h->data; if (handler->id != id) continue; browsing->handlers = g_slist_remove( browsing->handlers, handler); g_free(handler); return TRUE; } } } return FALSE; } struct avctp *avctp_connect(struct btd_device *device) { struct avctp *session; GError *err = NULL; GIOChannel *io; const bdaddr_t *src; session = avctp_get_internal(device); if (!session) return NULL; if (session->state > AVCTP_STATE_DISCONNECTED) return session; avctp_set_state(session, AVCTP_STATE_CONNECTING, 0); src = btd_adapter_get_address(session->server->adapter); io = bt_io_connect(avctp_connect_cb, session, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_DEST_BDADDR, device_get_address(session->device), BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_PSM, AVCTP_CONTROL_PSM, BT_IO_OPT_INVALID); if (err) { avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); error("%s", err->message); g_error_free(err); return NULL; } session->control = avctp_channel_create(session, io, 2, NULL); session->initiator = true; g_io_channel_unref(io); return session; } int avctp_connect_browsing(struct avctp *session) { const bdaddr_t *src; GError *err = NULL; GIOChannel *io; if (session->state != AVCTP_STATE_CONNECTED) return -ENOTCONN; if (session->browsing != NULL) return 0; avctp_set_state(session, AVCTP_STATE_BROWSING_CONNECTING, 0); src = btd_adapter_get_address(session->server->adapter); io = bt_io_connect(avctp_connect_browsing_cb, session, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_DEST_BDADDR, device_get_address(session->device), BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_PSM, AVCTP_BROWSING_PSM, BT_IO_OPT_MODE, BT_IO_MODE_ERTM, BT_IO_OPT_INVALID); if (err) { error("%s", err->message); g_error_free(err); return -EIO; } session->browsing = avctp_channel_create(session, io, 1, avctp_destroy_browsing); g_io_channel_unref(io); return 0; } void avctp_disconnect(struct avctp *session) { if (session->state == AVCTP_STATE_DISCONNECTED) return; avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); } struct avctp *avctp_get(struct btd_device *device) { return avctp_get_internal(device); } bool avctp_is_initiator(struct avctp *session) { return session->initiator; } bool avctp_supports_avc(uint8_t avc) { int i; for (i = 0; key_map[i].name != NULL; i++) { if (key_map[i].code == avc) return true; } return false; } bluez-5.87/profiles/audio/PaxHeaders/a2dp-codecs.h0000644000000000000000000000005014572354773017055 xustar0020 atime=1783092806 20 ctime=1783094105 bluez-5.87/profiles/audio/a2dp-codecs.h0000644000000000000000000003060014572354773016535 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright (C) 2018 Pali Rohár * * */ #include #include #define A2DP_CODEC_SBC 0x00 #define A2DP_CODEC_MPEG12 0x01 #define A2DP_CODEC_MPEG24 0x02 #define A2DP_CODEC_ATRAC 0x04 #define A2DP_CODEC_VENDOR 0xFF #define SBC_SAMPLING_FREQ_16000 (1 << 3) #define SBC_SAMPLING_FREQ_32000 (1 << 2) #define SBC_SAMPLING_FREQ_44100 (1 << 1) #define SBC_SAMPLING_FREQ_48000 1 #define SBC_CHANNEL_MODE_MONO (1 << 3) #define SBC_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) #define SBC_CHANNEL_MODE_STEREO (1 << 1) #define SBC_CHANNEL_MODE_JOINT_STEREO 1 #define SBC_BLOCK_LENGTH_4 (1 << 3) #define SBC_BLOCK_LENGTH_8 (1 << 2) #define SBC_BLOCK_LENGTH_12 (1 << 1) #define SBC_BLOCK_LENGTH_16 1 #define SBC_SUBBANDS_4 (1 << 1) #define SBC_SUBBANDS_8 1 #define SBC_ALLOCATION_SNR (1 << 1) #define SBC_ALLOCATION_LOUDNESS 1 #define SBC_MIN_BITPOOL 2 #define SBC_MAX_BITPOOL 250 /* Other settings: * Block length = 16 * Allocation method = Loudness * Subbands = 8 */ #define SBC_BITPOOL_MQ_MONO_44100 19 #define SBC_BITPOOL_MQ_MONO_48000 18 #define SBC_BITPOOL_MQ_JOINT_STEREO_44100 35 #define SBC_BITPOOL_MQ_JOINT_STEREO_48000 33 #define SBC_BITPOOL_HQ_MONO_44100 31 #define SBC_BITPOOL_HQ_MONO_48000 29 #define SBC_BITPOOL_HQ_JOINT_STEREO_44100 53 #define SBC_BITPOOL_HQ_JOINT_STEREO_48000 51 #define MPEG_CHANNEL_MODE_MONO (1 << 3) #define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) #define MPEG_CHANNEL_MODE_STEREO (1 << 1) #define MPEG_CHANNEL_MODE_JOINT_STEREO 1 #define MPEG_LAYER_MP1 (1 << 2) #define MPEG_LAYER_MP2 (1 << 1) #define MPEG_LAYER_MP3 1 #define MPEG_SAMPLING_FREQ_16000 (1 << 5) #define MPEG_SAMPLING_FREQ_22050 (1 << 4) #define MPEG_SAMPLING_FREQ_24000 (1 << 3) #define MPEG_SAMPLING_FREQ_32000 (1 << 2) #define MPEG_SAMPLING_FREQ_44100 (1 << 1) #define MPEG_SAMPLING_FREQ_48000 1 #define MPEG_BIT_RATE_INDEX_0 (1 << 0) #define MPEG_BIT_RATE_INDEX_1 (1 << 1) #define MPEG_BIT_RATE_INDEX_2 (1 << 2) #define MPEG_BIT_RATE_INDEX_3 (1 << 3) #define MPEG_BIT_RATE_INDEX_4 (1 << 4) #define MPEG_BIT_RATE_INDEX_5 (1 << 5) #define MPEG_BIT_RATE_INDEX_6 (1 << 6) #define MPEG_BIT_RATE_INDEX_7 (1 << 7) #define MPEG_BIT_RATE_INDEX_8 (1 << 8) #define MPEG_BIT_RATE_INDEX_9 (1 << 9) #define MPEG_BIT_RATE_INDEX_10 (1 << 10) #define MPEG_BIT_RATE_INDEX_11 (1 << 11) #define MPEG_BIT_RATE_INDEX_12 (1 << 12) #define MPEG_BIT_RATE_INDEX_13 (1 << 13) #define MPEG_BIT_RATE_INDEX_14 (1 << 14) #define MPEG_MP1_BIT_RATE_32000 MPEG_BIT_RATE_INDEX_1 #define MPEG_MP1_BIT_RATE_64000 MPEG_BIT_RATE_INDEX_2 #define MPEG_MP1_BIT_RATE_96000 MPEG_BIT_RATE_INDEX_3 #define MPEG_MP1_BIT_RATE_128000 MPEG_BIT_RATE_INDEX_4 #define MPEG_MP1_BIT_RATE_160000 MPEG_BIT_RATE_INDEX_5 #define MPEG_MP1_BIT_RATE_192000 MPEG_BIT_RATE_INDEX_6 #define MPEG_MP1_BIT_RATE_224000 MPEG_BIT_RATE_INDEX_7 #define MPEG_MP1_BIT_RATE_256000 MPEG_BIT_RATE_INDEX_8 #define MPEG_MP1_BIT_RATE_288000 MPEG_BIT_RATE_INDEX_9 #define MPEG_MP1_BIT_RATE_320000 MPEG_BIT_RATE_INDEX_10 #define MPEG_MP1_BIT_RATE_352000 MPEG_BIT_RATE_INDEX_11 #define MPEG_MP1_BIT_RATE_384000 MPEG_BIT_RATE_INDEX_12 #define MPEG_MP1_BIT_RATE_416000 MPEG_BIT_RATE_INDEX_13 #define MPEG_MP1_BIT_RATE_448000 MPEG_BIT_RATE_INDEX_14 #define MPEG_MP2_BIT_RATE_32000 MPEG_BIT_RATE_INDEX_1 #define MPEG_MP2_BIT_RATE_48000 MPEG_BIT_RATE_INDEX_2 #define MPEG_MP2_BIT_RATE_56000 MPEG_BIT_RATE_INDEX_3 #define MPEG_MP2_BIT_RATE_64000 MPEG_BIT_RATE_INDEX_4 #define MPEG_MP2_BIT_RATE_80000 MPEG_BIT_RATE_INDEX_5 #define MPEG_MP2_BIT_RATE_96000 MPEG_BIT_RATE_INDEX_6 #define MPEG_MP2_BIT_RATE_112000 MPEG_BIT_RATE_INDEX_7 #define MPEG_MP2_BIT_RATE_128000 MPEG_BIT_RATE_INDEX_8 #define MPEG_MP2_BIT_RATE_160000 MPEG_BIT_RATE_INDEX_9 #define MPEG_MP2_BIT_RATE_192000 MPEG_BIT_RATE_INDEX_10 #define MPEG_MP2_BIT_RATE_224000 MPEG_BIT_RATE_INDEX_11 #define MPEG_MP2_BIT_RATE_256000 MPEG_BIT_RATE_INDEX_12 #define MPEG_MP2_BIT_RATE_320000 MPEG_BIT_RATE_INDEX_13 #define MPEG_MP2_BIT_RATE_384000 MPEG_BIT_RATE_INDEX_14 #define MPEG_MP3_BIT_RATE_32000 MPEG_BIT_RATE_INDEX_1 #define MPEG_MP3_BIT_RATE_40000 MPEG_BIT_RATE_INDEX_2 #define MPEG_MP3_BIT_RATE_48000 MPEG_BIT_RATE_INDEX_3 #define MPEG_MP3_BIT_RATE_56000 MPEG_BIT_RATE_INDEX_4 #define MPEG_MP3_BIT_RATE_64000 MPEG_BIT_RATE_INDEX_5 #define MPEG_MP3_BIT_RATE_80000 MPEG_BIT_RATE_INDEX_6 #define MPEG_MP3_BIT_RATE_96000 MPEG_BIT_RATE_INDEX_7 #define MPEG_MP3_BIT_RATE_112000 MPEG_BIT_RATE_INDEX_8 #define MPEG_MP3_BIT_RATE_128000 MPEG_BIT_RATE_INDEX_9 #define MPEG_MP3_BIT_RATE_160000 MPEG_BIT_RATE_INDEX_10 #define MPEG_MP3_BIT_RATE_192000 MPEG_BIT_RATE_INDEX_11 #define MPEG_MP3_BIT_RATE_224000 MPEG_BIT_RATE_INDEX_12 #define MPEG_MP3_BIT_RATE_256000 MPEG_BIT_RATE_INDEX_13 #define MPEG_MP3_BIT_RATE_320000 MPEG_BIT_RATE_INDEX_14 #define MPEG_BIT_RATE_FREE MPEG_BIT_RATE_INDEX_0 #define MPEG_GET_BITRATE(a) ((uint16_t)(a).bitrate1 << 8 | (a).bitrate2) #define MPEG_SET_BITRATE(a, b) \ do { \ (a).bitrate1 = ((b) >> 8) & 0x7f; \ (a).bitrate2 = (b) & 0xff; \ } while (0) #define AAC_OBJECT_TYPE_MPEG2_AAC_LC 0x80 #define AAC_OBJECT_TYPE_MPEG4_AAC_LC 0x40 #define AAC_OBJECT_TYPE_MPEG4_AAC_LTP 0x20 #define AAC_OBJECT_TYPE_MPEG4_AAC_SCA 0x10 #define AAC_SAMPLING_FREQ_8000 0x0800 #define AAC_SAMPLING_FREQ_11025 0x0400 #define AAC_SAMPLING_FREQ_12000 0x0200 #define AAC_SAMPLING_FREQ_16000 0x0100 #define AAC_SAMPLING_FREQ_22050 0x0080 #define AAC_SAMPLING_FREQ_24000 0x0040 #define AAC_SAMPLING_FREQ_32000 0x0020 #define AAC_SAMPLING_FREQ_44100 0x0010 #define AAC_SAMPLING_FREQ_48000 0x0008 #define AAC_SAMPLING_FREQ_64000 0x0004 #define AAC_SAMPLING_FREQ_88200 0x0002 #define AAC_SAMPLING_FREQ_96000 0x0001 #define AAC_CHANNELS_1 0x02 #define AAC_CHANNELS_2 0x01 #define AAC_GET_BITRATE(a) ((a).bitrate1 << 16 | \ (a).bitrate2 << 8 | (a).bitrate3) #define AAC_GET_FREQUENCY(a) ((a).frequency1 << 4 | (a).frequency2) #define AAC_SET_BITRATE(a, b) \ do { \ (a).bitrate1 = (b >> 16) & 0x7f; \ (a).bitrate2 = (b >> 8) & 0xff; \ (a).bitrate3 = b & 0xff; \ } while (0) #define AAC_SET_FREQUENCY(a, f) \ do { \ (a).frequency1 = (f >> 4) & 0xff; \ (a).frequency2 = f & 0x0f; \ } while (0) #define AAC_INIT_BITRATE(b) \ .bitrate1 = (b >> 16) & 0x7f, \ .bitrate2 = (b >> 8) & 0xff, \ .bitrate3 = b & 0xff, #define AAC_INIT_FREQUENCY(f) \ .frequency1 = (f >> 4) & 0xff, \ .frequency2 = f & 0x0f, #define APTX_VENDOR_ID 0x0000004f #define APTX_CODEC_ID 0x0001 #define APTX_CHANNEL_MODE_MONO 0x01 #define APTX_CHANNEL_MODE_STEREO 0x02 #define APTX_SAMPLING_FREQ_16000 0x08 #define APTX_SAMPLING_FREQ_32000 0x04 #define APTX_SAMPLING_FREQ_44100 0x02 #define APTX_SAMPLING_FREQ_48000 0x01 #define FASTSTREAM_VENDOR_ID 0x0000000a #define FASTSTREAM_CODEC_ID 0x0001 #define FASTSTREAM_DIRECTION_SINK 0x1 #define FASTSTREAM_DIRECTION_SOURCE 0x2 #define FASTSTREAM_SINK_SAMPLING_FREQ_44100 0x2 #define FASTSTREAM_SINK_SAMPLING_FREQ_48000 0x1 #define FASTSTREAM_SOURCE_SAMPLING_FREQ_16000 0x2 #define APTX_LL_VENDOR_ID 0x0000000a #define APTX_LL_CODEC_ID 0x0002 /* Default parameters for aptX Low Latency encoder */ /* Target codec buffer level = 180 */ #define APTX_LL_TARGET_LEVEL2 0xb4 #define APTX_LL_TARGET_LEVEL1 0x00 /* Initial codec buffer level = 360 */ #define APTX_LL_INITIAL_LEVEL2 0x68 #define APTX_LL_INITIAL_LEVEL1 0x01 /* SRA max rate 0.005 * 10000 = 50 */ #define APTX_LL_SRA_MAX_RATE 0x32 /* SRA averaging time = 1s */ #define APTX_LL_SRA_AVG_TIME 0x01 /* Good working codec buffer level = 180 */ #define APTX_LL_GOOD_WORKING_LEVEL2 0xB4 #define APTX_LL_GOOD_WORKING_LEVEL1 0x00 #define APTX_HD_VENDOR_ID 0x000000D7 #define APTX_HD_CODEC_ID 0x0024 #define LDAC_VENDOR_ID 0x0000012d #define LDAC_CODEC_ID 0x00aa #define LDAC_SAMPLING_FREQ_44100 0x20 #define LDAC_SAMPLING_FREQ_48000 0x10 #define LDAC_SAMPLING_FREQ_88200 0x08 #define LDAC_SAMPLING_FREQ_96000 0x04 #define LDAC_SAMPLING_FREQ_176400 0x02 #define LDAC_SAMPLING_FREQ_192000 0x01 #define LDAC_CHANNEL_MODE_MONO 0x04 #define LDAC_CHANNEL_MODE_DUAL 0x02 #define LDAC_CHANNEL_MODE_STEREO 0x01 #define OPUS_G_VENDOR_ID 0x000000e0 #define OPUS_G_CODEC_ID 0x0001 #define OPUS_G_FREQUENCY_48000 0x80 #define OPUS_G_DURATION_100 0x08 #define OPUS_G_DURATION_200 0x10 #define OPUS_G_CHANNELS_MONO 0x01 #define OPUS_G_CHANNELS_STEREO 0x02 #define OPUS_G_CHANNELS_DUAL 0x04 typedef struct { uint8_t vendor_id4; uint8_t vendor_id3; uint8_t vendor_id2; uint8_t vendor_id1; uint8_t codec_id2; uint8_t codec_id1; } __attribute__ ((packed)) a2dp_vendor_codec_t; #define A2DP_GET_VENDOR_ID(a) ( \ (((uint32_t)(a).vendor_id4) << 0) | \ (((uint32_t)(a).vendor_id3) << 8) | \ (((uint32_t)(a).vendor_id2) << 16) | \ (((uint32_t)(a).vendor_id1) << 24) \ ) #define A2DP_GET_CODEC_ID(a) ((a).codec_id2 | (((uint16_t)(a).codec_id1) << 8)) #define A2DP_SET_VENDOR_ID_CODEC_ID(v, c) ((a2dp_vendor_codec_t){ \ .vendor_id4 = (((v) >> 0) & 0xff), \ .vendor_id3 = (((v) >> 8) & 0xff), \ .vendor_id2 = (((v) >> 16) & 0xff), \ .vendor_id1 = (((v) >> 24) & 0xff), \ .codec_id2 = (((c) >> 0) & 0xff), \ .codec_id1 = (((c) >> 8) & 0xff), \ }) typedef struct { uint8_t reserved; uint8_t target_level2; uint8_t target_level1; uint8_t initial_level2; uint8_t initial_level1; uint8_t sra_max_rate; uint8_t sra_avg_time; uint8_t good_working_level2; uint8_t good_working_level1; } __attribute__ ((packed)) a2dp_aptx_ll_new_caps_t; typedef struct { a2dp_vendor_codec_t info; uint8_t frequency; uint8_t channel_mode; } __attribute__ ((packed)) a2dp_ldac_t; #if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ __BYTE_ORDER == __LITTLE_ENDIAN typedef struct { uint8_t channel_mode:4; uint8_t frequency:4; uint8_t allocation_method:2; uint8_t subbands:2; uint8_t block_length:4; uint8_t min_bitpool; uint8_t max_bitpool; } __attribute__ ((packed)) a2dp_sbc_t; typedef struct { uint8_t channel_mode:4; uint8_t crc:1; uint8_t layer:3; uint8_t frequency:6; uint8_t mpf:1; uint8_t rfa:1; uint8_t bitrate1:7; uint8_t vbr:1; uint8_t bitrate2; } __attribute__ ((packed)) a2dp_mpeg_t; typedef struct { uint8_t object_type; uint8_t frequency1; uint8_t rfa:2; uint8_t channels:2; uint8_t frequency2:4; uint8_t bitrate1:7; uint8_t vbr:1; uint8_t bitrate2; uint8_t bitrate3; } __attribute__ ((packed)) a2dp_aac_t; typedef struct { a2dp_vendor_codec_t info; uint8_t channel_mode:4; uint8_t frequency:4; } __attribute__ ((packed)) a2dp_aptx_t; typedef struct { a2dp_vendor_codec_t info; uint8_t direction; uint8_t sink_frequency:4; uint8_t source_frequency:4; } __attribute__ ((packed)) a2dp_faststream_t; typedef struct { a2dp_aptx_t aptx; uint8_t bidirect_link:1; uint8_t has_new_caps:1; uint8_t reserved:6; a2dp_aptx_ll_new_caps_t new_caps[0]; } __attribute__ ((packed)) a2dp_aptx_ll_t; #elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ __BYTE_ORDER == __BIG_ENDIAN typedef struct { uint8_t frequency:4; uint8_t channel_mode:4; uint8_t block_length:4; uint8_t subbands:2; uint8_t allocation_method:2; uint8_t min_bitpool; uint8_t max_bitpool; } __attribute__ ((packed)) a2dp_sbc_t; typedef struct { uint8_t layer:3; uint8_t crc:1; uint8_t channel_mode:4; uint8_t rfa:1; uint8_t mpf:1; uint8_t frequency:6; uint8_t vbr:1; uint8_t bitrate1:7; uint8_t bitrate2; } __attribute__ ((packed)) a2dp_mpeg_t; typedef struct { uint8_t object_type; uint8_t frequency1; uint8_t frequency2:4; uint8_t channels:2; uint8_t rfa:2; uint8_t vbr:1; uint8_t bitrate1:7; uint8_t bitrate2; uint8_t bitrate3; } __attribute__ ((packed)) a2dp_aac_t; typedef struct { a2dp_vendor_codec_t info; uint8_t frequency:4; uint8_t channel_mode:4; } __attribute__ ((packed)) a2dp_aptx_t; typedef struct { a2dp_vendor_codec_t info; uint8_t direction; uint8_t source_frequency:4; uint8_t sink_frequency:4; } __attribute__ ((packed)) a2dp_faststream_t; typedef struct { a2dp_aptx_t aptx; uint8_t reserved:6; uint8_t has_new_caps:1; uint8_t bidirect_link:1; a2dp_aptx_ll_new_caps_t new_caps[0]; } __attribute__ ((packed)) a2dp_aptx_ll_t; #else #error "Unknown byte order" #endif typedef struct { a2dp_aptx_t aptx; uint8_t reserved0; uint8_t reserved1; uint8_t reserved2; uint8_t reserved3; } __attribute__ ((packed)) a2dp_aptx_hd_t; typedef struct { a2dp_vendor_codec_t info; uint8_t data; } __attribute__ ((packed)) a2dp_opus_g_t; bluez-5.87/profiles/audio/PaxHeaders/avrcp.c0000644000000000000000000000005015221752714016064 xustar0020 atime=1783093652 20 ctime=1783094105 bluez-5.87/profiles/audio/avrcp.c0000644000000000000000000035400615221752714015555 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright (C) 2011 Texas Instruments, Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "bluetooth/uuid.h" #include "gdbus/gdbus.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #include "src/error.h" #include "src/sdpd.h" #include "src/dbus-common.h" #include "src/shared/timeout.h" #include "src/shared/util.h" #include "src/btd.h" #include "avctp.h" #include "avrcp.h" #include "control.h" #include "media.h" #include "player.h" #include "transport.h" /* Company IDs for vendor dependent commands */ #define IEEEID_BTSIG 0x001958 /* Status codes */ #define AVRCP_STATUS_INVALID_COMMAND 0x00 #define AVRCP_STATUS_INVALID_PARAM 0x01 #define AVRCP_STATUS_PARAM_NOT_FOUND 0x02 #define AVRCP_STATUS_INTERNAL_ERROR 0x03 #define AVRCP_STATUS_SUCCESS 0x04 #define AVRCP_STATUS_UID_CHANGED 0x05 #define AVRCP_STATUS_DOES_NOT_EXIST 0x09 #define AVRCP_STATUS_INVALID_SCOPE 0x0a #define AVRCP_STATUS_OUT_OF_BOUNDS 0x0b #define AVRCP_STATUS_INVALID_PLAYER_ID 0x11 #define AVRCP_STATUS_PLAYER_NOT_BROWSABLE 0x12 #define AVRCP_STATUS_NO_AVAILABLE_PLAYERS 0x15 #define AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED 0x16 /* Packet types */ #define AVRCP_PACKET_TYPE_SINGLE 0x00 #define AVRCP_PACKET_TYPE_START 0x01 #define AVRCP_PACKET_TYPE_CONTINUING 0x02 #define AVRCP_PACKET_TYPE_END 0x03 /* PDU types for metadata transfer */ #define AVRCP_GET_CAPABILITIES 0x10 #define AVRCP_LIST_PLAYER_ATTRIBUTES 0X11 #define AVRCP_LIST_PLAYER_VALUES 0x12 #define AVRCP_GET_CURRENT_PLAYER_VALUE 0x13 #define AVRCP_SET_PLAYER_VALUE 0x14 #define AVRCP_GET_PLAYER_ATTRIBUTE_TEXT 0x15 #define AVRCP_GET_PLAYER_VALUE_TEXT 0x16 #define AVRCP_DISPLAYABLE_CHARSET 0x17 #define AVRCP_CT_BATTERY_STATUS 0x18 #define AVRCP_GET_ELEMENT_ATTRIBUTES 0x20 #define AVRCP_GET_PLAY_STATUS 0x30 #define AVRCP_REGISTER_NOTIFICATION 0x31 #define AVRCP_REQUEST_CONTINUING 0x40 #define AVRCP_ABORT_CONTINUING 0x41 #define AVRCP_SET_ABSOLUTE_VOLUME 0x50 #define AVRCP_SET_ADDRESSED_PLAYER 0x60 #define AVRCP_SET_BROWSED_PLAYER 0x70 #define AVRCP_GET_FOLDER_ITEMS 0x71 #define AVRCP_CHANGE_PATH 0x72 #define AVRCP_GET_ITEM_ATTRIBUTES 0x73 #define AVRCP_PLAY_ITEM 0x74 #define AVRCP_GET_TOTAL_NUMBER_OF_ITEMS 0x75 #define AVRCP_SEARCH 0x80 #define AVRCP_ADD_TO_NOW_PLAYING 0x90 #define AVRCP_GENERAL_REJECT 0xA0 /* Capabilities for AVRCP_GET_CAPABILITIES pdu */ #define CAP_COMPANY_ID 0x02 #define CAP_EVENTS_SUPPORTED 0x03 #define AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH 5 #define AVRCP_GET_CAPABILITIES_PARAM_LENGTH 1 #define AVRCP_FEATURE_CATEGORY_1 0x0001 #define AVRCP_FEATURE_CATEGORY_2 0x0002 #define AVRCP_FEATURE_CATEGORY_3 0x0004 #define AVRCP_FEATURE_CATEGORY_4 0x0008 #define AVRCP_FEATURE_TG_PLAYER_SETTINGS 0x0010 #define AVRCP_FEATURE_TG_GROUP_NAVIGATION 0x0020 #define AVRCP_FEATURE_BROWSING 0x0040 #define AVRCP_FEATURE_TG_MULTIPLE_PLAYER 0x0080 #define AVRCP_FEATURE_TG_COVERT_ART 0x0100 #define AVRCP_FEATURE_CT_GET_IMAGE_PROP 0x0080 #define AVRCP_FEATURE_CT_GET_IMAGE 0x0100 #define AVRCP_FEATURE_CT_GET_THUMBNAIL 0x0200 #define AVRCP_BATTERY_STATUS_NORMAL 0 #define AVRCP_BATTERY_STATUS_WARNING 1 #define AVRCP_BATTERY_STATUS_CRITICAL 2 #define AVRCP_BATTERY_STATUS_EXTERNAL 3 #define AVRCP_BATTERY_STATUS_FULL_CHARGE 4 #define AVRCP_CHARSET_UTF8 106 #define AVRCP_BROWSING_TIMEOUT 1 #define AVRCP_CT_VERSION 0x0106 #define AVRCP_TG_VERSION 0x0106 #define AVRCP_SCOPE_MEDIA_PLAYER_LIST 0x00 #define AVRCP_SCOPE_MEDIA_PLAYER_VFS 0x01 #define AVRCP_SCOPE_SEARCH 0x02 #define AVRCP_SCOPE_NOW_PLAYING 0x03 #if __BYTE_ORDER == __LITTLE_ENDIAN struct avrcp_header { uint8_t company_id[3]; uint8_t pdu_id; uint8_t packet_type:2; uint8_t rsvd:6; uint16_t params_len; uint8_t params[0]; } __attribute__ ((packed)); #define AVRCP_HEADER_LENGTH 7 #elif __BYTE_ORDER == __BIG_ENDIAN struct avrcp_header { uint8_t company_id[3]; uint8_t pdu_id; uint8_t rsvd:6; uint8_t packet_type:2; uint16_t params_len; uint8_t params[0]; } __attribute__ ((packed)); #define AVRCP_HEADER_LENGTH 7 #else #error "Unknown byte order" #endif #define AVRCP_MTU (AVC_MTU - AVC_HEADER_LENGTH) #define AVRCP_PDU_MTU (AVRCP_MTU - AVRCP_HEADER_LENGTH) struct avrcp_browsing_header { uint8_t pdu_id; uint16_t param_len; uint8_t params[0]; } __attribute__ ((packed)); #define AVRCP_BROWSING_HEADER_LENGTH 3 struct get_folder_items_rsp { uint8_t status; uint16_t uid_counter; uint16_t num_items; uint8_t data[0]; } __attribute__ ((packed)); struct folder_item { uint8_t type; uint16_t len; uint8_t data[0]; } __attribute__ ((packed)); struct player_item { uint16_t player_id; uint8_t type; uint32_t subtype; uint8_t status; uint8_t features[16]; uint16_t charset; uint16_t namelen; char name[0]; } __attribute__ ((packed)); struct get_total_number_of_items_rsp { uint8_t status; uint16_t uid_counter; uint32_t num_items; } __attribute__ ((packed)); struct avrcp_server { struct btd_adapter *adapter; bool browsing; uint32_t tg_record_id; uint32_t ct_record_id; GSList *players; GSList *sessions; }; struct pending_pdu { uint8_t pdu_id; GList *attr_ids; uint16_t offset; }; struct pending_list_items { GSList *items; uint32_t start; uint32_t end; uint64_t total; }; struct avrcp_player { struct avrcp_server *server; GSList *sessions; uint16_t id; uint8_t scope; uint64_t uid; uint16_t uid_counter; bool browsed; bool addressed; uint8_t *features; char *path; guint changed_id; struct pending_list_items *p; char *change_path; uint64_t change_uid; struct avrcp_player_cb *cb; void *user_data; GDestroyNotify destroy; }; struct avrcp_data { struct avrcp_player *player; uint16_t version; int features; uint16_t obex_port; GSList *players; uint16_t registered_events; uint8_t transaction_events[AVRCP_EVENT_LAST + 1]; }; struct avrcp { struct avrcp_server *server; struct avctp *conn; struct btd_device *dev; struct avrcp_data *target; struct avrcp_data *controller; const struct passthrough_handler *passthrough_handlers; const struct control_pdu_handler *control_handlers; unsigned int passthrough_id; unsigned int control_id; unsigned int browsing_id; unsigned int browsing_timer; uint16_t supported_events; uint8_t transaction; struct pending_pdu *pending_pdu; }; struct passthrough_handler { uint8_t op; bool (*func) (struct avrcp *session); }; struct control_pdu_handler { uint8_t pdu_id; uint8_t code; uint8_t (*func) (struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction); }; static const struct { uint8_t feature_bit; uint8_t avc; } passthrough_map[] = { { 0, AVC_SELECT }, { 1, AVC_UP }, { 2, AVC_DOWN }, { 3, AVC_LEFT }, { 4, AVC_RIGHT }, { 5, AVC_RIGHT_UP }, { 6, AVC_RIGHT_DOWN }, { 7, AVC_LEFT_UP }, { 8, AVC_LEFT_DOWN }, { 9, AVC_ROOT_MENU }, { 10, AVC_SETUP_MENU }, { 11, AVC_CONTENTS_MENU }, { 12, AVC_FAVORITE_MENU }, { 13, AVC_EXIT }, { 14, AVC_0 }, { 15, AVC_1 }, { 16, AVC_2 }, { 17, AVC_3 }, { 18, AVC_4 }, { 19, AVC_5 }, { 20, AVC_6 }, { 21, AVC_7 }, { 22, AVC_8 }, { 23, AVC_9 }, { 24, AVC_DOT }, { 25, AVC_ENTER }, { 26, AVC_CLEAR }, { 27, AVC_CHANNEL_UP }, { 28, AVC_CHANNEL_DOWN }, { 29, AVC_CHANNEL_PREVIOUS }, { 30, AVC_SOUND_SELECT }, { 31, AVC_INPUT_SELECT }, { 32, AVC_INFO }, { 33, AVC_HELP }, { 34, AVC_PAGE_UP }, { 35, AVC_PAGE_DOWN }, { 36, AVC_POWER }, { 37, AVC_VOLUME_UP }, { 38, AVC_VOLUME_DOWN }, { 39, AVC_MUTE }, { 40, AVC_PLAY }, { 41, AVC_STOP }, { 42, AVC_PAUSE }, { 43, AVC_RECORD }, { 44, AVC_REWIND }, { 45, AVC_FAST_FORWARD }, { 46, AVC_EJECT }, { 47, AVC_FORWARD }, { 48, AVC_BACKWARD }, { 49, AVC_ANGLE }, { 50, AVC_SUBPICTURE }, { 51, AVC_F1 }, { 52, AVC_F2 }, { 53, AVC_F3 }, { 54, AVC_F4 }, { 55, AVC_F5 }, { 56, AVC_VENDOR_UNIQUE }, { 0xff, 0xff } }; static GSList *servers = NULL; static unsigned int avctp_id = 0; /* Default feature bit mask for media player */ static uint8_t default_features[16]; /* Company IDs supported by this device */ static const uint32_t company_ids[] = { IEEEID_BTSIG, }; static void avrcp_register_notification(struct avrcp *session, uint8_t event); static GList *player_list_settings(struct avrcp_player *player); static void avrcp_browsing_record(sdp_record_t *record, sdp_data_t *version) { sdp_list_t *apseq; uuid_t l2cap, avctp; sdp_list_t *aproto, *proto[2]; sdp_data_t *psm; uint16_t ap = AVCTP_BROWSING_PSM; sdp_uuid16_create(&l2cap, L2CAP_UUID); proto[0] = sdp_list_append(NULL, &l2cap); psm = sdp_data_alloc(SDP_UINT16, &ap); proto[0] = sdp_list_append(proto[0], psm); apseq = sdp_list_append(NULL, proto[0]); sdp_uuid16_create(&avctp, AVCTP_UUID); proto[1] = sdp_list_append(NULL, &avctp); proto[1] = sdp_list_append(proto[1], version); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(NULL, apseq); sdp_set_add_access_protos(record, aproto); free(psm); sdp_list_free(proto[0], NULL); sdp_list_free(proto[1], NULL); sdp_list_free(apseq, NULL); sdp_list_free(aproto, NULL); } static sdp_record_t *avrcp_ct_record(bool browsing) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, l2cap, avctp, avrct, avrctr; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[2]; sdp_record_t *record; sdp_data_t *psm[2], *version, *features; uint16_t lp = AVCTP_CONTROL_PSM; uint16_t avctp_ver = 0x0104; uint16_t feat = ( AVRCP_FEATURE_CATEGORY_1 | AVRCP_FEATURE_CATEGORY_2 | AVRCP_FEATURE_CATEGORY_3 | AVRCP_FEATURE_CATEGORY_4 | AVRCP_FEATURE_CT_GET_IMAGE_PROP | AVRCP_FEATURE_CT_GET_IMAGE | AVRCP_FEATURE_CT_GET_THUMBNAIL); record = sdp_record_alloc(); if (!record) return NULL; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(NULL, &root_uuid); sdp_set_browse_groups(record, root); /* Service Class ID List */ sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID); svclass_id = sdp_list_append(NULL, &avrct); sdp_uuid16_create(&avrctr, AV_REMOTE_CONTROLLER_SVCLASS_ID); svclass_id = sdp_list_append(svclass_id, &avrctr); sdp_set_service_classes(record, svclass_id); /* Protocol Descriptor List */ sdp_uuid16_create(&l2cap, L2CAP_UUID); proto[0] = sdp_list_append(NULL, &l2cap); psm[0] = sdp_data_alloc(SDP_UINT16, &lp); proto[0] = sdp_list_append(proto[0], psm[0]); apseq = sdp_list_append(NULL, proto[0]); sdp_uuid16_create(&avctp, AVCTP_UUID); proto[1] = sdp_list_append(NULL, &avctp); version = sdp_data_alloc(SDP_UINT16, &avctp_ver); proto[1] = sdp_list_append(proto[1], version); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(NULL, apseq); sdp_set_access_protos(record, aproto); /* Additional Protocol Descriptor List */ if (browsing) { feat |= AVRCP_FEATURE_BROWSING; avrcp_browsing_record(record, version); } /* Bluetooth Profile Descriptor List */ sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); profile[0].version = AVRCP_CT_VERSION; pfseq = sdp_list_append(NULL, &profile[0]); sdp_set_profile_descs(record, pfseq); features = sdp_data_alloc(SDP_UINT16, &feat); sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); sdp_set_info_attr(record, "AVRCP CT", NULL, NULL); free(psm[0]); free(version); sdp_list_free(proto[0], NULL); sdp_list_free(proto[1], NULL); sdp_list_free(apseq, NULL); sdp_list_free(pfseq, NULL); sdp_list_free(aproto, NULL); sdp_list_free(root, NULL); sdp_list_free(svclass_id, NULL); return record; } static sdp_record_t *avrcp_tg_record(bool browsing) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, l2cap, avctp, avrtg; sdp_profile_desc_t profile[1]; sdp_list_t *aproto_control, *proto_control[2]; sdp_record_t *record; sdp_data_t *psm_control, *version, *features; uint16_t lp = AVCTP_CONTROL_PSM; uint16_t avctp_ver = 0x0104; uint16_t feat = ( AVRCP_FEATURE_CATEGORY_1 | AVRCP_FEATURE_CATEGORY_2 | AVRCP_FEATURE_CATEGORY_3 | AVRCP_FEATURE_CATEGORY_4 | AVRCP_FEATURE_TG_PLAYER_SETTINGS); record = sdp_record_alloc(); if (!record) return NULL; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(NULL, &root_uuid); sdp_set_browse_groups(record, root); /* Service Class ID List */ sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID); svclass_id = sdp_list_append(NULL, &avrtg); sdp_set_service_classes(record, svclass_id); /* Protocol Descriptor List */ sdp_uuid16_create(&l2cap, L2CAP_UUID); proto_control[0] = sdp_list_append(NULL, &l2cap); psm_control = sdp_data_alloc(SDP_UINT16, &lp); proto_control[0] = sdp_list_append(proto_control[0], psm_control); apseq = sdp_list_append(NULL, proto_control[0]); sdp_uuid16_create(&avctp, AVCTP_UUID); proto_control[1] = sdp_list_append(NULL, &avctp); version = sdp_data_alloc(SDP_UINT16, &avctp_ver); proto_control[1] = sdp_list_append(proto_control[1], version); apseq = sdp_list_append(apseq, proto_control[1]); aproto_control = sdp_list_append(NULL, apseq); sdp_set_access_protos(record, aproto_control); /* Additional Protocol Descriptor List */ if (browsing) { feat |= AVRCP_FEATURE_BROWSING; avrcp_browsing_record(record, version); } /* Bluetooth Profile Descriptor List */ sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); profile[0].version = AVRCP_TG_VERSION; pfseq = sdp_list_append(NULL, &profile[0]); sdp_set_profile_descs(record, pfseq); features = sdp_data_alloc(SDP_UINT16, &feat); sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); sdp_set_info_attr(record, "AVRCP TG", NULL, NULL); free(psm_control); free(version); sdp_list_free(proto_control[0], NULL); sdp_list_free(proto_control[1], NULL); sdp_list_free(apseq, NULL); sdp_list_free(aproto_control, NULL); sdp_list_free(pfseq, NULL); sdp_list_free(root, NULL); sdp_list_free(svclass_id, NULL); return record; } static void populate_default_features(void) { int i; for (i = 0; passthrough_map[i].feature_bit != 0xff; i++) { if (avctp_supports_avc(passthrough_map[i].avc)) { uint8_t bit = passthrough_map[i].feature_bit; default_features[bit >> 3] |= (1 << (bit & 7)); } } /* supports at least AVRCP 1.4 */ default_features[7] |= (1 << 2); /* supports GetTotalNumberOfItems browsing command */ default_features[8] |= (1 << 3); } static unsigned int attr_get_max_val(uint8_t attr) { switch (attr) { case AVRCP_ATTRIBUTE_EQUALIZER: return AVRCP_EQUALIZER_ON; case AVRCP_ATTRIBUTE_REPEAT_MODE: return AVRCP_REPEAT_MODE_GROUP; case AVRCP_ATTRIBUTE_SHUFFLE: return AVRCP_SHUFFLE_GROUP; case AVRCP_ATTRIBUTE_SCAN: return AVRCP_SCAN_GROUP; } return 0; } static const char *battery_status_to_str(uint8_t status) { switch (status) { case AVRCP_BATTERY_STATUS_NORMAL: return "normal"; case AVRCP_BATTERY_STATUS_WARNING: return "warning"; case AVRCP_BATTERY_STATUS_CRITICAL: return "critical"; case AVRCP_BATTERY_STATUS_EXTERNAL: return "external"; case AVRCP_BATTERY_STATUS_FULL_CHARGE: return "fullcharge"; } return NULL; } /* * get_company_id: * * Get three-byte Company_ID from incoming AVRCP message */ static uint32_t get_company_id(const uint8_t cid[3]) { return cid[0] << 16 | cid[1] << 8 | cid[2]; } /* * set_company_id: * * Set three-byte Company_ID into outgoing AVRCP message */ static void set_company_id(uint8_t cid[3], uint32_t cid_in) { cid[0] = (cid_in & 0xff0000) >> 16; cid[1] = (cid_in & 0x00ff00) >> 8; cid[2] = (cid_in & 0x0000ff); } static const char *attr_to_str(uint8_t attr) { switch (attr) { case AVRCP_ATTRIBUTE_EQUALIZER: return "Equalizer"; case AVRCP_ATTRIBUTE_REPEAT_MODE: return "Repeat"; case AVRCP_ATTRIBUTE_SHUFFLE: return "Shuffle"; case AVRCP_ATTRIBUTE_SCAN: return "Scan"; } return NULL; } static int attrval_to_val(uint8_t attr, const char *value) { int ret; switch (attr) { case AVRCP_ATTRIBUTE_EQUALIZER: if (!strcmp(value, "off")) ret = AVRCP_EQUALIZER_OFF; else if (!strcmp(value, "on")) ret = AVRCP_EQUALIZER_ON; else ret = -EINVAL; return ret; case AVRCP_ATTRIBUTE_REPEAT_MODE: if (!strcmp(value, "off")) ret = AVRCP_REPEAT_MODE_OFF; else if (!strcmp(value, "singletrack")) ret = AVRCP_REPEAT_MODE_SINGLE; else if (!strcmp(value, "alltracks")) ret = AVRCP_REPEAT_MODE_ALL; else if (!strcmp(value, "group")) ret = AVRCP_REPEAT_MODE_GROUP; else ret = -EINVAL; return ret; case AVRCP_ATTRIBUTE_SHUFFLE: if (!strcmp(value, "off")) ret = AVRCP_SHUFFLE_OFF; else if (!strcmp(value, "alltracks")) ret = AVRCP_SHUFFLE_ALL; else if (!strcmp(value, "group")) ret = AVRCP_SHUFFLE_GROUP; else ret = -EINVAL; return ret; case AVRCP_ATTRIBUTE_SCAN: if (!strcmp(value, "off")) ret = AVRCP_SCAN_OFF; else if (!strcmp(value, "alltracks")) ret = AVRCP_SCAN_ALL; else if (!strcmp(value, "group")) ret = AVRCP_SCAN_GROUP; else ret = -EINVAL; return ret; } return -EINVAL; } static int attr_to_val(const char *str) { if (!strcasecmp(str, "Equalizer")) return AVRCP_ATTRIBUTE_EQUALIZER; else if (!strcasecmp(str, "Repeat")) return AVRCP_ATTRIBUTE_REPEAT_MODE; else if (!strcasecmp(str, "Shuffle")) return AVRCP_ATTRIBUTE_SHUFFLE; else if (!strcasecmp(str, "Scan")) return AVRCP_ATTRIBUTE_SCAN; return -EINVAL; } static int player_get_setting(struct avrcp_player *player, uint8_t id) { const char *key; const char *value; if (player == NULL) return -ENOENT; key = attr_to_str(id); if (key == NULL) return -EINVAL; value = player->cb->get_setting(key, player->user_data); if (value == NULL) return -EINVAL; return attrval_to_val(id, value); } static int play_status_to_val(const char *status) { if (!strcasecmp(status, "stopped")) return AVRCP_PLAY_STATUS_STOPPED; else if (!strcasecmp(status, "playing")) return AVRCP_PLAY_STATUS_PLAYING; else if (!strcasecmp(status, "paused")) return AVRCP_PLAY_STATUS_PAUSED; else if (!strcasecmp(status, "forward-seek")) return AVRCP_PLAY_STATUS_FWD_SEEK; else if (!strcasecmp(status, "reverse-seek")) return AVRCP_PLAY_STATUS_REV_SEEK; else if (!strcasecmp(status, "error")) return AVRCP_PLAY_STATUS_ERROR; return -EINVAL; } static uint16_t player_settings_changed(struct avrcp_player *player, struct avrcp_header *pdu) { GList *settings = player_list_settings(player); int size = 2; for (; settings; settings = settings->next) { const char *key = settings->data; int attr; int val; attr = attr_to_val(key); if (attr < 0) continue; val = player_get_setting(player, attr); if (val < 0) continue; pdu->params[size++] = attr; pdu->params[size++] = val; } g_list_free(settings); pdu->params[1] = (size - 2) >> 1; return size; } void avrcp_player_event(struct avrcp_player *player, uint8_t id, const void *data) { uint8_t buf[AVRCP_HEADER_LENGTH + 9]; struct avrcp_header *pdu = (void *) buf; uint8_t code; uint16_t size; GSList *l; if (player->sessions == NULL) return; memset(buf, 0, sizeof(buf)); set_company_id(pdu->company_id, IEEEID_BTSIG); pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION; DBG("id=%u", id); if (id != AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED && player->changed_id) { code = AVC_CTYPE_REJECTED; size = 1; pdu->params[0] = AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED; goto done; } code = AVC_CTYPE_CHANGED; pdu->params[0] = id; switch (id) { case AVRCP_EVENT_STATUS_CHANGED: size = 2; pdu->params[1] = play_status_to_val(data); break; case AVRCP_EVENT_TRACK_CHANGED: size = 9; memcpy(&pdu->params[1], data, sizeof(uint64_t)); break; case AVRCP_EVENT_TRACK_REACHED_END: case AVRCP_EVENT_TRACK_REACHED_START: size = 1; break; case AVRCP_EVENT_SETTINGS_CHANGED: size = player_settings_changed(player, pdu); break; case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: size = 5; memcpy(&pdu->params[1], &player->id, sizeof(uint16_t)); memcpy(&pdu->params[3], &player->uid_counter, sizeof(uint16_t)); break; case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED: size = 1; break; default: error("Unknown event %u", id); return; } done: pdu->params_len = cpu_to_be16(size); for (l = player->sessions; l; l = l->next) { struct avrcp *session = l->data; struct avrcp_data *target = session->target; int err; if (!target || !(target->registered_events & (1 << id))) continue; err = avctp_send_vendordep(session->conn, target->transaction_events[id], code, AVC_SUBUNIT_PANEL, buf, size + AVRCP_HEADER_LENGTH); if (err < 0) continue; /* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */ target->registered_events ^= 1 << id; } return; } static const char *metadata_to_str(uint32_t id) { switch (id) { case AVRCP_MEDIA_ATTRIBUTE_TITLE: return "Title"; case AVRCP_MEDIA_ATTRIBUTE_ARTIST: return "Artist"; case AVRCP_MEDIA_ATTRIBUTE_ALBUM: return "Album"; case AVRCP_MEDIA_ATTRIBUTE_GENRE: return "Genre"; case AVRCP_MEDIA_ATTRIBUTE_TRACK: return "TrackNumber"; case AVRCP_MEDIA_ATTRIBUTE_N_TRACKS: return "NumberOfTracks"; case AVRCP_MEDIA_ATTRIBUTE_DURATION: return "Duration"; case AVRCP_MEDIA_ATTRIBUTE_IMG_HANDLE: return "ImgHandle"; } return NULL; } static const char *player_get_metadata(struct avrcp_player *player, uint32_t id) { const char *key; key = metadata_to_str(id); if (key == NULL) return NULL; if (player != NULL) return player->cb->get_metadata(key, player->user_data); if (id == AVRCP_MEDIA_ATTRIBUTE_TITLE) return ""; return NULL; } static uint16_t player_write_media_attribute(struct avrcp_player *player, uint32_t id, uint8_t *buf, uint16_t *pos, uint16_t *offset) { uint16_t len; uint16_t attr_len; const char *value = NULL; DBG("%u", id); value = player_get_metadata(player, id); if (value == NULL) { *offset = 0; return 0; } attr_len = strlen(value); value = ((char *) value) + *offset; len = attr_len - *offset; if (len > AVRCP_PDU_MTU - *pos) { len = AVRCP_PDU_MTU - *pos; *offset += len; } else { *offset = 0; } memcpy(&buf[*pos], value, len); *pos += len; return attr_len; } struct avrcp_media_attribute_hdr { uint32_t id; uint16_t charset; uint16_t len; } __attribute__ ((packed)); static GList *player_fill_media_attribute(struct avrcp_player *player, GList *attr_ids, uint8_t *buf, uint16_t *pos, uint16_t *offset) { struct avrcp_media_attribute_hdr *hdr = NULL; GList *l; for (l = attr_ids; l != NULL; l = g_list_delete_link(l, l)) { uint32_t attr = GPOINTER_TO_UINT(l->data); uint16_t attr_len; if (*offset == 0) { if (*pos + sizeof(*hdr) >= AVRCP_PDU_MTU) break; hdr = (void *) &buf[*pos]; hdr->id = cpu_to_be32(attr); /* Always use UTF-8 */ hdr->charset = cpu_to_be16(AVRCP_CHARSET_UTF8); *pos += sizeof(*hdr); } attr_len = player_write_media_attribute(player, attr, buf, pos, offset); if (hdr != NULL) hdr->len = cpu_to_be16(attr_len); if (*offset > 0) break; } return l; } static struct pending_pdu *pending_pdu_new(uint8_t pdu_id, GList *attr_ids, unsigned int offset) { struct pending_pdu *pending = g_new(struct pending_pdu, 1); pending->pdu_id = pdu_id; pending->attr_ids = attr_ids; pending->offset = offset; return pending; } static gboolean session_abort_pending_pdu(struct avrcp *session) { if (session->pending_pdu == NULL) return FALSE; g_list_free(session->pending_pdu->attr_ids); g_free(session->pending_pdu); session->pending_pdu = NULL; return TRUE; } static const char *attrval_to_str(uint8_t attr, uint8_t value) { switch (attr) { case AVRCP_ATTRIBUTE_EQUALIZER: switch (value) { case AVRCP_EQUALIZER_ON: return "on"; case AVRCP_EQUALIZER_OFF: return "off"; } break; case AVRCP_ATTRIBUTE_REPEAT_MODE: switch (value) { case AVRCP_REPEAT_MODE_OFF: return "off"; case AVRCP_REPEAT_MODE_SINGLE: return "singletrack"; case AVRCP_REPEAT_MODE_ALL: return "alltracks"; case AVRCP_REPEAT_MODE_GROUP: return "group"; } break; /* Shuffle and scan have the same values */ case AVRCP_ATTRIBUTE_SHUFFLE: case AVRCP_ATTRIBUTE_SCAN: switch (value) { case AVRCP_SCAN_OFF: return "off"; case AVRCP_SCAN_ALL: return "alltracks"; case AVRCP_SCAN_GROUP: return "group"; } break; } return NULL; } static int player_set_setting(struct avrcp_player *player, uint8_t id, uint8_t val) { const char *key, *value; key = attr_to_str(id); if (key == NULL) return -EINVAL; value = attrval_to_str(id, val); if (value == NULL) return -EINVAL; if (player == NULL) return -ENOENT; return player->cb->set_setting(key, value, player->user_data); } static uint8_t avrcp_handle_get_capabilities(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { uint16_t len = be16_to_cpu(pdu->params_len); unsigned int i; if (len != 1) goto err; DBG("id=%u", pdu->params[0]); switch (pdu->params[0]) { case CAP_COMPANY_ID: for (i = 0; i < G_N_ELEMENTS(company_ids); i++) { set_company_id(&pdu->params[2 + i * 3], company_ids[i]); } pdu->params_len = cpu_to_be16(2 + (3 * G_N_ELEMENTS(company_ids))); pdu->params[1] = G_N_ELEMENTS(company_ids); return AVC_CTYPE_STABLE; case CAP_EVENTS_SUPPORTED: pdu->params[1] = 0; for (i = 1; i <= AVRCP_EVENT_LAST; i++) { if (session->supported_events & (1 << i)) { pdu->params[1]++; pdu->params[pdu->params[1] + 1] = i; } } pdu->params_len = cpu_to_be16(2 + pdu->params[1]); return AVC_CTYPE_STABLE; } err: pdu->params_len = cpu_to_be16(1); pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; return AVC_CTYPE_REJECTED; } static struct avrcp_player *target_get_player(struct avrcp *session) { if (!session->target) return NULL; return session->target->player; } static uint8_t avrcp_handle_list_player_attributes(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { struct avrcp_player *player = target_get_player(session); uint16_t len = be16_to_cpu(pdu->params_len); unsigned int i; if (len != 0) { pdu->params_len = cpu_to_be16(1); pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; return AVC_CTYPE_REJECTED; } if (!player) goto done; for (i = 1; i <= AVRCP_ATTRIBUTE_SCAN; i++) { if (player_get_setting(player, i) < 0) continue; len++; pdu->params[len] = i; } done: pdu->params[0] = len; pdu->params_len = cpu_to_be16(len + 1); return AVC_CTYPE_STABLE; } static uint8_t avrcp_handle_list_player_values(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { struct avrcp_player *player = target_get_player(session); uint16_t len = be16_to_cpu(pdu->params_len); unsigned int i; if (len != 1) goto err; if (player_get_setting(player, pdu->params[0]) < 0) goto err; len = attr_get_max_val(pdu->params[0]); for (i = 1; i <= len; i++) pdu->params[i] = i; pdu->params[0] = len; pdu->params_len = cpu_to_be16(len + 1); return AVC_CTYPE_STABLE; err: pdu->params_len = cpu_to_be16(1); pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; return AVC_CTYPE_REJECTED; } static uint32_t str_to_metadata(const char *str) { if (strcasecmp(str, "Title") == 0) return AVRCP_MEDIA_ATTRIBUTE_TITLE; else if (strcasecmp(str, "Artist") == 0) return AVRCP_MEDIA_ATTRIBUTE_ARTIST; else if (strcasecmp(str, "Album") == 0) return AVRCP_MEDIA_ATTRIBUTE_ALBUM; else if (strcasecmp(str, "Genre") == 0) return AVRCP_MEDIA_ATTRIBUTE_GENRE; else if (strcasecmp(str, "TrackNumber") == 0) return AVRCP_MEDIA_ATTRIBUTE_TRACK; else if (strcasecmp(str, "NumberOfTracks") == 0) return AVRCP_MEDIA_ATTRIBUTE_N_TRACKS; else if (strcasecmp(str, "Duration") == 0) return AVRCP_MEDIA_ATTRIBUTE_DURATION; else if (strcasecmp(str, "ImgHandle") == 0) return AVRCP_MEDIA_ATTRIBUTE_IMG_HANDLE; return 0; } static GList *player_list_metadata(struct avrcp_player *player) { GList *l, *attrs = NULL; if (player == NULL) return g_list_prepend(NULL, GUINT_TO_POINTER(AVRCP_MEDIA_ATTRIBUTE_TITLE)); l = player->cb->list_metadata(player->user_data); for (; l; l = l->next) { const char *key = l->data; attrs = g_list_append(attrs, GUINT_TO_POINTER(str_to_metadata(key))); } if (attrs == NULL) return g_list_prepend(NULL, GUINT_TO_POINTER(AVRCP_MEDIA_ATTRIBUTE_TITLE)); return attrs; } static uint8_t avrcp_handle_get_element_attributes(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { struct avrcp_player *player = target_get_player(session); uint16_t len = be16_to_cpu(pdu->params_len); uint64_t identifier = get_le64(&pdu->params[0]); uint16_t pos; uint8_t nattr; GList *attr_ids; uint16_t offset; if (len < 9 || identifier != 0) goto err; nattr = pdu->params[8]; if (len < nattr * sizeof(uint32_t) + 1) goto err; if (!nattr) { /* * Return all available information, at least * title must be returned if there's a track selected. */ attr_ids = player_list_metadata(player); len = g_list_length(attr_ids); } else { unsigned int i; for (i = 0, len = 0, attr_ids = NULL; i < nattr; i++) { uint32_t id; id = get_be32(&pdu->params[9] + (i * sizeof(id))); /* Don't add invalid attributes */ if (id == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL || id > AVRCP_MEDIA_ATTRIBUTE_LAST) continue; len++; attr_ids = g_list_prepend(attr_ids, GUINT_TO_POINTER(id)); } attr_ids = g_list_reverse(attr_ids); } if (!len) goto err; session_abort_pending_pdu(session); pos = 1; offset = 0; attr_ids = player_fill_media_attribute(player, attr_ids, pdu->params, &pos, &offset); if (attr_ids != NULL) { session->pending_pdu = pending_pdu_new(pdu->pdu_id, attr_ids, offset); pdu->packet_type = AVRCP_PACKET_TYPE_START; } pdu->params[0] = len; pdu->params_len = cpu_to_be16(pos); return AVC_CTYPE_STABLE; err: pdu->params_len = cpu_to_be16(1); pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; return AVC_CTYPE_REJECTED; } static uint8_t avrcp_handle_get_current_player_value(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { struct avrcp_player *player = target_get_player(session); uint16_t len = be16_to_cpu(pdu->params_len); uint8_t *settings; unsigned int i; if (len <= 1 || pdu->params[0] != len - 1) goto err; /* * Save a copy of requested settings because we can override them * while responding */ settings = util_memdup(&pdu->params[1], pdu->params[0]); len = 0; /* * From sec. 5.7 of AVRCP 1.3 spec, we should ignore non-existent IDs * and send a response with the existent ones. Only if all IDs are * non-existent we should send an error. */ for (i = 0; i < pdu->params[0]; i++) { int val; if (settings[i] < AVRCP_ATTRIBUTE_EQUALIZER || settings[i] > AVRCP_ATTRIBUTE_SCAN) { DBG("Ignoring %u", settings[i]); continue; } val = player_get_setting(player, settings[i]); if (val < 0) continue; pdu->params[++len] = settings[i]; pdu->params[++len] = val; } free(settings); if (len) { pdu->params[0] = len / 2; pdu->params_len = cpu_to_be16(len + 1); return AVC_CTYPE_STABLE; } error("No valid attributes in request"); err: pdu->params_len = cpu_to_be16(1); pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; return AVC_CTYPE_REJECTED; } static uint8_t avrcp_handle_set_player_value(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { struct avrcp_player *player = target_get_player(session); uint16_t len = be16_to_cpu(pdu->params_len); unsigned int i; uint8_t *param; if (len < 3 || len > 2 * pdu->params[0] + 1U || player == NULL) goto err; /* * From sec. 5.7 of AVRCP 1.3 spec, we should ignore non-existent IDs * and set the existent ones. Sec. 5.2.4 is not clear however how to * indicate that a certain ID was not accepted. If at least one * attribute is valid, we respond with no parameters. Otherwise an * AVRCP_STATUS_INVALID_PARAM is sent. */ for (len = 0, i = 0, param = &pdu->params[1]; i < pdu->params[0]; i++, param += 2) { if (player_set_setting(player, param[0], param[1]) < 0) continue; len++; } if (len) { pdu->params_len = 0; return AVC_CTYPE_ACCEPTED; } err: pdu->params_len = cpu_to_be16(1); pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; return AVC_CTYPE_REJECTED; } static uint8_t avrcp_handle_displayable_charset(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { uint16_t len = be16_to_cpu(pdu->params_len); if (len < 3) { pdu->params_len = cpu_to_be16(1); pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; return AVC_CTYPE_REJECTED; } /* * We acknowledge the commands, but we always use UTF-8 for * encoding since CT is obliged to support it. */ pdu->params_len = 0; return AVC_CTYPE_STABLE; } static uint8_t avrcp_handle_ct_battery_status(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { uint16_t len = be16_to_cpu(pdu->params_len); const char *valstr; if (len != 1) goto err; valstr = battery_status_to_str(pdu->params[0]); if (valstr == NULL) goto err; pdu->params_len = 0; return AVC_CTYPE_STABLE; err: pdu->params_len = cpu_to_be16(1); pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; return AVC_CTYPE_REJECTED; } static uint32_t player_get_position(struct avrcp_player *player) { if (player == NULL) return 0; return player->cb->get_position(player->user_data); } static uint32_t player_get_duration(struct avrcp_player *player) { uint32_t num; if (player == NULL) return UINT32_MAX; num = player->cb->get_duration(player->user_data); if (num == 0) return UINT32_MAX; return num; } static uint8_t player_get_status(struct avrcp_player *player) { const char *value; if (player == NULL) return AVRCP_PLAY_STATUS_STOPPED; value = player->cb->get_status(player->user_data); if (value == NULL) return AVRCP_PLAY_STATUS_STOPPED; return play_status_to_val(value); } static uint16_t player_get_id(struct avrcp_player *player) { if (player == NULL) return 0x0000; return player->id; } static uint16_t player_get_uid_counter(struct avrcp_player *player) { if (player == NULL) return 0x0000; return player->uid_counter; } static uint8_t avrcp_handle_get_play_status(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { struct avrcp_player *player = target_get_player(session); uint16_t len = be16_to_cpu(pdu->params_len); uint32_t position; uint32_t duration; if (len != 0) { pdu->params_len = cpu_to_be16(1); pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; return AVC_CTYPE_REJECTED; } position = player_get_position(player); duration = player_get_duration(player); put_be32(duration, &pdu->params[0]); put_be32(position, &pdu->params[4]); pdu->params[8] = player_get_status(player); pdu->params_len = cpu_to_be16(9); return AVC_CTYPE_STABLE; } static uint64_t player_get_uid(struct avrcp_player *player) { if (player == NULL) return UINT64_MAX; return player->cb->get_uid(player->user_data); } static GList *player_list_settings(struct avrcp_player *player) { if (player == NULL) return NULL; return player->cb->list_settings(player->user_data); } static bool avrcp_handle_play(struct avrcp *session) { struct avrcp_player *player = target_get_player(session); if (player == NULL) return false; return player->cb->play(player->user_data); } static bool avrcp_handle_stop(struct avrcp *session) { struct avrcp_player *player = target_get_player(session); if (player == NULL) return false; return player->cb->stop(player->user_data); } static bool avrcp_handle_pause(struct avrcp *session) { struct avrcp_player *player = target_get_player(session); if (player == NULL) return false; return player->cb->pause(player->user_data); } static bool avrcp_handle_next(struct avrcp *session) { struct avrcp_player *player = target_get_player(session); if (player == NULL) return false; return player->cb->next(player->user_data); } static bool avrcp_handle_previous(struct avrcp *session) { struct avrcp_player *player = target_get_player(session); if (player == NULL) return false; return player->cb->previous(player->user_data); } static const struct passthrough_handler passthrough_handlers[] = { { AVC_PLAY, avrcp_handle_play }, { AVC_STOP, avrcp_handle_stop }, { AVC_PAUSE, avrcp_handle_pause }, { AVC_FORWARD, avrcp_handle_next }, { AVC_BACKWARD, avrcp_handle_previous }, { }, }; static bool handle_passthrough(struct avctp *conn, uint8_t op, bool pressed, void *user_data) { struct avrcp *session = user_data; const struct passthrough_handler *handler; for (handler = session->passthrough_handlers; handler->func; handler++) { if (handler->op == op) break; } if (handler->func == NULL) return false; /* Do not trigger handler on release */ if (!pressed) return true; return handler->func(session); } static uint8_t avrcp_handle_register_notification(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { struct avrcp_player *player = target_get_player(session); struct btd_device *dev = session->dev; uint16_t len = be16_to_cpu(pdu->params_len); uint64_t uid; int8_t volume; /* * 1 byte for EventID, 4 bytes for Playback interval but the latest * one is applicable only for EVENT_PLAYBACK_POS_CHANGED. See AVRCP * 1.3 spec, section 5.4.2. */ if (len != 5) goto err; /* Check if event is supported otherwise reject */ if (!(session->supported_events & (1 << pdu->params[0]))) goto err; if (!session->target) { pdu->params_len = cpu_to_be16(1); pdu->params[0] = AVRCP_STATUS_INVALID_COMMAND; return AVC_CTYPE_REJECTED; } switch (pdu->params[0]) { case AVRCP_EVENT_STATUS_CHANGED: len = 2; pdu->params[1] = player_get_status(player); break; case AVRCP_EVENT_TRACK_CHANGED: len = 9; uid = player_get_uid(player); memcpy(&pdu->params[1], &uid, sizeof(uint64_t)); break; case AVRCP_EVENT_TRACK_REACHED_END: case AVRCP_EVENT_TRACK_REACHED_START: len = 1; break; case AVRCP_EVENT_SETTINGS_CHANGED: len = player_settings_changed(player, pdu); break; case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: len = 5; bt_put_be16(player_get_id(player), &pdu->params[1]); bt_put_be16(player_get_uid_counter(player), &pdu->params[3]); break; case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED: len = 1; break; case AVRCP_EVENT_VOLUME_CHANGED: volume = media_transport_get_a2dp_volume(dev); if (volume < 0) goto err; pdu->params[1] = volume; len = 2; break; default: /* All other events are not supported yet */ goto err; } /* Register event and save the transaction used */ session->target->registered_events |= (1 << pdu->params[0]); session->target->transaction_events[pdu->params[0]] = transaction; pdu->params_len = cpu_to_be16(len); return AVC_CTYPE_INTERIM; err: pdu->params_len = cpu_to_be16(1); pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; return AVC_CTYPE_REJECTED; } static uint8_t avrcp_handle_request_continuing(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { struct avrcp_player *player = target_get_player(session); uint16_t len = be16_to_cpu(pdu->params_len); struct pending_pdu *pending; if (len != 1 || session->pending_pdu == NULL) goto err; pending = session->pending_pdu; if (pending->pdu_id != pdu->params[0]) goto err; len = 0; pending->attr_ids = player_fill_media_attribute(player, pending->attr_ids, pdu->params, &len, &pending->offset); pdu->pdu_id = pending->pdu_id; if (pending->attr_ids == NULL) { g_free(session->pending_pdu); session->pending_pdu = NULL; pdu->packet_type = AVRCP_PACKET_TYPE_END; } else { pdu->packet_type = AVRCP_PACKET_TYPE_CONTINUING; } pdu->params_len = cpu_to_be16(len); return AVC_CTYPE_STABLE; err: pdu->params_len = cpu_to_be16(1); pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; return AVC_CTYPE_REJECTED; } static uint8_t avrcp_handle_abort_continuing(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { uint16_t len = be16_to_cpu(pdu->params_len); struct pending_pdu *pending; if (len != 1 || session->pending_pdu == NULL) goto err; pending = session->pending_pdu; if (pending->pdu_id != pdu->params[0]) goto err; session_abort_pending_pdu(session); pdu->params_len = 0; return AVC_CTYPE_ACCEPTED; err: pdu->params_len = cpu_to_be16(1); pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; return AVC_CTYPE_REJECTED; } /* SetAbsoluteVolume requires at least version 1.4 and a category-2 */ static bool avrcp_volume_supported(struct avrcp_data *data) { if (!data || data->version < 0x0104) return false; if (btd_opts.avrcp.volume_category && !(data->features & AVRCP_FEATURE_CATEGORY_2)) return false; return true; } static uint8_t avrcp_handle_set_absolute_volume(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { uint16_t len = be16_to_cpu(pdu->params_len); int8_t volume; if (len != 1) goto err; if (!avrcp_volume_supported(session->target)) { error("SetAbsoluteVolume not supported"); goto err; } volume = pdu->params[0] = pdu->params[0] & 0x7F; media_transport_set_a2dp_volume(session->dev, volume); return AVC_CTYPE_ACCEPTED; err: pdu->params_len = cpu_to_be16(1); pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; return AVC_CTYPE_REJECTED; } static struct avrcp_player *find_tg_player(struct avrcp *session, uint16_t id) { struct avrcp_server *server = session->server; GSList *l; for (l = server->players; l; l = l->next) { struct avrcp_player *player = l->data; if (player->id == id) return player; } return NULL; } static gboolean notify_addressed_player_changed(gpointer user_data) { struct avrcp_player *player = user_data; uint8_t events[6] = { AVRCP_EVENT_STATUS_CHANGED, AVRCP_EVENT_TRACK_CHANGED, AVRCP_EVENT_TRACK_REACHED_START, AVRCP_EVENT_TRACK_REACHED_END, AVRCP_EVENT_SETTINGS_CHANGED, AVRCP_EVENT_PLAYBACK_POS_CHANGED }; uint8_t i; /* * Set changed_id to an non-zero value to indicate addreddsed player * changed. */ player->changed_id = 1; avrcp_player_event(player, AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED, NULL); /* * TG shall complete all player specific * notifications with AV/C C-Type REJECTED * with error code as Addressed Player Changed. */ for (i = 0; i < sizeof(events); i++) avrcp_player_event(player, events[i], NULL); player->changed_id = 0; return FALSE; } static uint8_t avrcp_handle_set_addressed_player(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { struct avrcp_player *player; uint16_t len = be16_to_cpu(pdu->params_len); uint16_t player_id = 0; uint8_t status; if (len < 1) { status = AVRCP_STATUS_INVALID_PARAM; goto err; } player_id = bt_get_be16(&pdu->params[0]); player = find_tg_player(session, player_id); pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; if (player) { player->addressed = true; status = AVRCP_STATUS_SUCCESS; pdu->params_len = cpu_to_be16(len); pdu->params[0] = status; } else { status = AVRCP_STATUS_INVALID_PLAYER_ID; goto err; } /* Don't emit player changed immediately since PTS expect the * response of SetAddressedPlayer before the event. */ player->changed_id = g_idle_add(notify_addressed_player_changed, player); return AVC_CTYPE_ACCEPTED; err: pdu->params_len = cpu_to_be16(sizeof(status)); pdu->params[0] = status; return AVC_CTYPE_REJECTED; } static const struct control_pdu_handler control_handlers[] = { { AVRCP_GET_CAPABILITIES, AVC_CTYPE_STATUS, avrcp_handle_get_capabilities }, { AVRCP_LIST_PLAYER_ATTRIBUTES, AVC_CTYPE_STATUS, avrcp_handle_list_player_attributes }, { AVRCP_LIST_PLAYER_VALUES, AVC_CTYPE_STATUS, avrcp_handle_list_player_values }, { AVRCP_GET_ELEMENT_ATTRIBUTES, AVC_CTYPE_STATUS, avrcp_handle_get_element_attributes }, { AVRCP_GET_CURRENT_PLAYER_VALUE, AVC_CTYPE_STATUS, avrcp_handle_get_current_player_value }, { AVRCP_SET_PLAYER_VALUE, AVC_CTYPE_CONTROL, avrcp_handle_set_player_value }, { AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, AVC_CTYPE_STATUS, NULL }, { AVRCP_GET_PLAYER_VALUE_TEXT, AVC_CTYPE_STATUS, NULL }, { AVRCP_DISPLAYABLE_CHARSET, AVC_CTYPE_STATUS, avrcp_handle_displayable_charset }, { AVRCP_CT_BATTERY_STATUS, AVC_CTYPE_STATUS, avrcp_handle_ct_battery_status }, { AVRCP_GET_PLAY_STATUS, AVC_CTYPE_STATUS, avrcp_handle_get_play_status }, { AVRCP_REGISTER_NOTIFICATION, AVC_CTYPE_NOTIFY, avrcp_handle_register_notification }, { AVRCP_SET_ABSOLUTE_VOLUME, AVC_CTYPE_CONTROL, avrcp_handle_set_absolute_volume }, { AVRCP_REQUEST_CONTINUING, AVC_CTYPE_CONTROL, avrcp_handle_request_continuing }, { AVRCP_ABORT_CONTINUING, AVC_CTYPE_CONTROL, avrcp_handle_abort_continuing }, { AVRCP_SET_ADDRESSED_PLAYER, AVC_CTYPE_CONTROL, avrcp_handle_set_addressed_player }, { }, }; /* handle vendordep pdu inside an avctp packet */ static size_t handle_vendordep_pdu(struct avctp *conn, uint8_t transaction, uint8_t *code, uint8_t *subunit, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; const struct control_pdu_handler *handler; struct avrcp_header *pdu = (void *) operands; uint32_t company_id = get_company_id(pdu->company_id); if (company_id != IEEEID_BTSIG) { *code = AVC_CTYPE_NOT_IMPLEMENTED; return 0; } DBG("AVRCP PDU 0x%02X, company 0x%06X len 0x%04X", pdu->pdu_id, company_id, be16_to_cpu(pdu->params_len)); pdu->packet_type = 0; pdu->rsvd = 0; if (operand_count < AVRCP_HEADER_LENGTH) { pdu->params[0] = AVRCP_STATUS_INVALID_COMMAND; goto err_metadata; } operands += sizeof(*pdu); operand_count -= sizeof(*pdu); if (be16_to_cpu(pdu->params_len) != operand_count) { DBG("AVRCP PDU parameters length don't match"); pdu->params[0] = AVRCP_STATUS_PARAM_NOT_FOUND; goto err_metadata; } for (handler = session->control_handlers; handler->pdu_id; handler++) { if (handler->pdu_id == pdu->pdu_id) break; } if (handler->pdu_id != pdu->pdu_id || handler->code != *code) { pdu->params[0] = AVRCP_STATUS_INVALID_COMMAND; goto err_metadata; } if (!handler->func) { pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; goto err_metadata; } *code = handler->func(session, pdu, transaction); if (*code != AVC_CTYPE_REJECTED && pdu->pdu_id != AVRCP_GET_ELEMENT_ATTRIBUTES && pdu->pdu_id != AVRCP_REQUEST_CONTINUING && pdu->pdu_id != AVRCP_ABORT_CONTINUING) session_abort_pending_pdu(session); return AVRCP_HEADER_LENGTH + be16_to_cpu(pdu->params_len); err_metadata: pdu->params_len = cpu_to_be16(1); *code = AVC_CTYPE_REJECTED; return AVRCP_HEADER_LENGTH + 1; } static void avrcp_handle_media_player_list(struct avrcp *session, struct avrcp_browsing_header *pdu, uint32_t start_item, uint32_t end_item) { struct avrcp_player *player = session->target->player; struct get_folder_items_rsp *rsp; const char *name = NULL; GSList *l; rsp = (void *)pdu->params; rsp->status = AVRCP_STATUS_SUCCESS; rsp->uid_counter = cpu_to_be16(player_get_uid_counter(player)); rsp->num_items = 0; pdu->param_len = sizeof(*rsp); for (l = g_slist_nth(session->server->players, start_item); l; l = g_slist_next(l)) { struct avrcp_player *player = l->data; struct folder_item *folder; struct player_item *item; uint16_t namelen; if (rsp->num_items == (end_item - start_item) + 1) break; folder = (void *)&pdu->params[pdu->param_len]; folder->type = 0x01; /* Media Player */ pdu->param_len += sizeof(*folder); item = (void *)folder->data; item->player_id = cpu_to_be16(player->id); item->type = 0x01; /* Audio */ item->subtype = cpu_to_be32(0x00000001); /* Audio Book */ item->status = player_get_status(player); /* Assign Default Feature Bit Mask */ memcpy(&item->features, &default_features, sizeof(default_features)); item->charset = cpu_to_be16(AVRCP_CHARSET_UTF8); name = player->cb->get_name(player->user_data); namelen = strlen(name); item->namelen = cpu_to_be16(namelen); memcpy(item->name, name, namelen); folder->len = cpu_to_be16(sizeof(*item) + namelen); pdu->param_len += sizeof(*item) + namelen; rsp->num_items++; } /* If no player could be found respond with an error */ if (!rsp->num_items) goto failed; rsp->num_items = cpu_to_be16(rsp->num_items); pdu->param_len = cpu_to_be16(pdu->param_len); return; failed: pdu->params[0] = AVRCP_STATUS_OUT_OF_BOUNDS; pdu->param_len = cpu_to_be16(1); } static void avrcp_handle_get_folder_items(struct avrcp *session, struct avrcp_browsing_header *pdu, uint8_t transaction) { uint32_t start_item = 0; uint32_t end_item = 0; uint8_t scope; uint8_t status = AVRCP_STATUS_SUCCESS; if (be16_to_cpu(pdu->param_len) < 10) { status = AVRCP_STATUS_INVALID_PARAM; goto failed; } scope = pdu->params[0]; start_item = bt_get_be32(&pdu->params[1]); end_item = bt_get_be32(&pdu->params[5]); DBG("scope 0x%02x start_item 0x%08x end_item 0x%08x", scope, start_item, end_item); if (end_item < start_item) { status = AVRCP_STATUS_INVALID_PARAM; goto failed; } switch (scope) { case AVRCP_SCOPE_MEDIA_PLAYER_LIST: avrcp_handle_media_player_list(session, pdu, start_item, end_item); break; case AVRCP_SCOPE_MEDIA_PLAYER_VFS: case AVRCP_SCOPE_SEARCH: case AVRCP_SCOPE_NOW_PLAYING: default: status = AVRCP_STATUS_INVALID_SCOPE; goto failed; } return; failed: pdu->params[0] = status; pdu->param_len = cpu_to_be16(1); } static void avrcp_handle_media_player_list_num_items(struct avrcp *session, struct avrcp_browsing_header *pdu) { struct avrcp_player *player = target_get_player(session); struct get_total_number_of_items_rsp *rsp; rsp = (void *)pdu->params; rsp->status = AVRCP_STATUS_SUCCESS; rsp->uid_counter = cpu_to_be16(player_get_uid_counter(player)); rsp->num_items = cpu_to_be32(g_slist_length(session->server->players)); pdu->param_len = cpu_to_be16(sizeof(*rsp)); } static void avrcp_handle_get_total_number_of_items(struct avrcp *session, struct avrcp_browsing_header *pdu, uint8_t transaction) { uint8_t scope; uint8_t status = AVRCP_STATUS_SUCCESS; if (be16_to_cpu(pdu->param_len) != 1) { status = AVRCP_STATUS_INVALID_PARAM; goto failed; } scope = pdu->params[0]; switch (scope) { case AVRCP_SCOPE_MEDIA_PLAYER_LIST: avrcp_handle_media_player_list_num_items(session, pdu); break; case AVRCP_SCOPE_MEDIA_PLAYER_VFS: case AVRCP_SCOPE_SEARCH: case AVRCP_SCOPE_NOW_PLAYING: default: status = AVRCP_STATUS_INVALID_SCOPE; goto failed; } return; failed: pdu->params[0] = status; pdu->param_len = cpu_to_be16(1); } static const struct browsing_pdu_handler { uint8_t pdu_id; void (*func) (struct avrcp *session, struct avrcp_browsing_header *pdu, uint8_t transaction); } browsing_handlers[] = { { AVRCP_GET_FOLDER_ITEMS, avrcp_handle_get_folder_items }, { AVRCP_GET_TOTAL_NUMBER_OF_ITEMS, avrcp_handle_get_total_number_of_items }, { }, }; size_t avrcp_browsing_general_reject(uint8_t *operands) { struct avrcp_browsing_header *pdu = (void *) operands; uint8_t status; pdu->pdu_id = AVRCP_GENERAL_REJECT; status = AVRCP_STATUS_INVALID_COMMAND; pdu->param_len = cpu_to_be16(sizeof(status)); memcpy(pdu->params, &status, (sizeof(status))); return AVRCP_BROWSING_HEADER_LENGTH + sizeof(status); } static size_t handle_browsing_pdu(struct avctp *conn, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; const struct browsing_pdu_handler *handler; struct avrcp_browsing_header *pdu = (void *) operands; DBG("AVRCP Browsing PDU 0x%02X, len 0x%04X", pdu->pdu_id, be16_to_cpu(pdu->param_len)); for (handler = browsing_handlers; handler->pdu_id; handler++) { if (handler->pdu_id == pdu->pdu_id) goto done; } return avrcp_browsing_general_reject(operands); done: session->transaction = transaction; handler->func(session, pdu, transaction); return AVRCP_BROWSING_HEADER_LENGTH + be16_to_cpu(pdu->param_len); } size_t avrcp_handle_vendor_reject(uint8_t *code, uint8_t *operands) { struct avrcp_header *pdu = (void *) operands; uint32_t company_id = get_company_id(pdu->company_id); *code = AVC_CTYPE_REJECTED; pdu->params_len = cpu_to_be16(1); pdu->params[0] = AVRCP_STATUS_INTERNAL_ERROR; DBG("rejecting AVRCP PDU 0x%02X, company 0x%06X len 0x%04X", pdu->pdu_id, company_id, be16_to_cpu(pdu->params_len)); return AVRCP_HEADER_LENGTH + 1; } static struct avrcp_server *find_server(GSList *list, struct btd_adapter *a) { for (; list; list = list->next) { struct avrcp_server *server = list->data; if (server->adapter == a) return server; } return NULL; } static const char *status_to_string(uint8_t status) { switch (status) { case AVRCP_PLAY_STATUS_STOPPED: return "stopped"; case AVRCP_PLAY_STATUS_PLAYING: return "playing"; case AVRCP_PLAY_STATUS_PAUSED: return "paused"; case AVRCP_PLAY_STATUS_FWD_SEEK: return "forward-seek"; case AVRCP_PLAY_STATUS_REV_SEEK: return "reverse-seek"; case AVRCP_PLAY_STATUS_ERROR: return "error"; default: return NULL; } } static gboolean avrcp_get_play_status_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->controller->player; struct media_player *mp = player->user_data; struct avrcp_header *pdu = (void *) operands; uint32_t duration; uint32_t position; uint8_t status; if (pdu == NULL || code == AVC_CTYPE_REJECTED || be16_to_cpu(pdu->params_len) != 9) return FALSE; duration = get_be32(pdu->params); media_player_set_duration(mp, duration); position = get_be32(pdu->params + 4); media_player_set_position(mp, position); status = get_u8(pdu->params + 8); media_player_set_status(mp, status_to_string(status)); return FALSE; } static void avrcp_get_play_status(struct avrcp *session) { uint8_t buf[AVRCP_HEADER_LENGTH]; struct avrcp_header *pdu = (void *) buf; memset(buf, 0, sizeof(buf)); set_company_id(pdu->company_id, IEEEID_BTSIG); pdu->pdu_id = AVRCP_GET_PLAY_STATUS; pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; avctp_send_vendordep_req(session->conn, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, buf, sizeof(buf), avrcp_get_play_status_rsp, session); } static const char *status_to_str(uint8_t status) { switch (status) { case AVRCP_STATUS_INVALID_COMMAND: return "Invalid Command"; case AVRCP_STATUS_INVALID_PARAM: return "Invalid Parameter"; case AVRCP_STATUS_INTERNAL_ERROR: return "Internal Error"; case AVRCP_STATUS_SUCCESS: return "Success"; default: return "Unknown"; } } static gboolean avrcp_player_value_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->controller->player; struct media_player *mp = player->user_data; struct avrcp_header *pdu = (void *) operands; uint8_t count; int i; if (pdu == NULL) { media_player_set_setting(mp, "Error", "Timeout"); return FALSE; } if (code == AVC_CTYPE_REJECTED) { media_player_set_setting(mp, "Error", status_to_str(pdu->params[0])); return FALSE; } count = pdu->params[0]; if (pdu->params_len < count * 2) return FALSE; for (i = 1; count > 0; count--, i += 2) { const char *key; const char *value; key = attr_to_str(pdu->params[i]); if (key == NULL) continue; value = attrval_to_str(pdu->params[i], pdu->params[i + 1]); if (value == NULL) continue; media_player_set_setting(mp, key, value); } return FALSE; } static void avrcp_get_current_player_value(struct avrcp *session, uint8_t *attrs, uint8_t count) { uint8_t buf[AVRCP_HEADER_LENGTH + AVRCP_ATTRIBUTE_LAST + 1]; struct avrcp_header *pdu = (void *) buf; uint16_t length = AVRCP_HEADER_LENGTH + count + 1; memset(buf, 0, sizeof(buf)); set_company_id(pdu->company_id, IEEEID_BTSIG); pdu->pdu_id = AVRCP_GET_CURRENT_PLAYER_VALUE; pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; pdu->params_len = cpu_to_be16(count + 1); pdu->params[0] = count; memcpy(pdu->params + 1, attrs, count); avctp_send_vendordep_req(session->conn, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, buf, length, avrcp_player_value_rsp, session); } static gboolean avrcp_list_player_attributes_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data) { uint8_t attrs[AVRCP_ATTRIBUTE_LAST]; struct avrcp *session = user_data; struct avrcp_header *pdu = (void *) operands; uint8_t len, count = 0; int i; if (code == AVC_CTYPE_REJECTED || code == AVC_CTYPE_NOT_IMPLEMENTED) return FALSE; len = pdu->params[0]; if (be16_to_cpu(pdu->params_len) < count) { error("Invalid parameters"); return FALSE; } for (i = 0; len > 0; len--, i++) { /* Don't query invalid attributes */ if (pdu->params[i + 1] == AVRCP_ATTRIBUTE_ILEGAL || pdu->params[i + 1] > AVRCP_ATTRIBUTE_LAST) continue; attrs[count++] = pdu->params[i + 1]; } avrcp_get_current_player_value(session, attrs, count); return FALSE; } static void avrcp_list_player_attributes(struct avrcp *session) { uint8_t buf[AVRCP_HEADER_LENGTH]; struct avrcp_header *pdu = (void *) buf; memset(buf, 0, sizeof(buf)); set_company_id(pdu->company_id, IEEEID_BTSIG); pdu->pdu_id = AVRCP_LIST_PLAYER_ATTRIBUTES; pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; avctp_send_vendordep_req(session->conn, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, buf, sizeof(buf), avrcp_list_player_attributes_rsp, session); } static void avrcp_parse_attribute_list(struct avrcp_player *player, struct media_item *item, uint8_t *operands, uint8_t count) { struct media_player *mp = player->user_data; int i; for (i = 0; count > 0; count--) { uint32_t id; uint16_t charset, len; id = get_be32(&operands[i]); i += sizeof(uint32_t); charset = get_be16(&operands[i]); i += sizeof(uint16_t); len = get_be16(&operands[i]); i += sizeof(uint16_t); if (charset == 106) { const char *key = metadata_to_str(id); if (key != NULL) media_player_set_metadata(mp, item, metadata_to_str(id), &operands[i], len); } i += len; } } static void avrcp_abort_continuing(struct avrcp *session, uint8_t pdu_id) { uint8_t buf[AVRCP_HEADER_LENGTH + 1]; struct avrcp_header *pdu = (void *) buf; memset(buf, 0, sizeof(buf)); set_company_id(pdu->company_id, IEEEID_BTSIG); pdu->pdu_id = AVRCP_ABORT_CONTINUING; pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; pdu->params_len = cpu_to_be16(1); pdu->params[0] = pdu_id; avctp_send_vendordep_req(session->conn, AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, buf, sizeof(buf), NULL, session); } static gboolean avrcp_get_element_attributes_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->controller->player; struct avrcp_header *pdu = (void *) operands; struct media_player *mp = player->user_data; struct media_item *item; uint8_t count; if (code == AVC_CTYPE_REJECTED) return FALSE; /* Abort fragmented responses as reassembly is not supported */ if (pdu->packet_type == AVRCP_PACKET_TYPE_START || pdu->packet_type == AVRCP_PACKET_TYPE_CONTINUING) { avrcp_abort_continuing(session, AVRCP_GET_ELEMENT_ATTRIBUTES); return FALSE; } count = pdu->params[0]; if (be16_to_cpu(pdu->params_len) - 1 < count * 8) { error("Invalid parameters"); return FALSE; } media_player_clear_metadata(mp); item = media_player_set_playlist_item(mp, player->uid); avrcp_parse_attribute_list(player, item, &pdu->params[1], count); media_player_metadata_changed(mp); avrcp_get_play_status(session); return FALSE; } static void avrcp_get_element_attributes(struct avrcp *session) { uint8_t buf[AVRCP_HEADER_LENGTH + 9]; struct avrcp_header *pdu = (void *) buf; uint16_t length; memset(buf, 0, sizeof(buf)); set_company_id(pdu->company_id, IEEEID_BTSIG); pdu->pdu_id = AVRCP_GET_ELEMENT_ATTRIBUTES; pdu->params_len = cpu_to_be16(9); pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; length = AVRCP_HEADER_LENGTH + be16_to_cpu(pdu->params_len); avctp_send_vendordep_req(session->conn, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, buf, length, avrcp_get_element_attributes_rsp, session); } static const char *type_to_string(uint8_t type) { switch (type & 0x0F) { case 0x01: return "Audio"; case 0x02: return "Video"; case 0x03: return "Audio, Video"; case 0x04: return "Audio Broadcasting"; case 0x05: return "Audio, Audio Broadcasting"; case 0x06: return "Video, Audio Broadcasting"; case 0x07: return "Audio, Video, Audio Broadcasting"; case 0x08: return "Video Broadcasting"; case 0x09: return "Audio, Video Broadcasting"; case 0x0A: return "Video, Video Broadcasting"; case 0x0B: return "Audio, Video, Video Broadcasting"; case 0x0C: return "Audio Broadcasting, Video Broadcasting"; case 0x0D: return "Audio, Audio Broadcasting, Video Broadcasting"; case 0x0E: return "Video, Audio Broadcasting, Video Broadcasting"; case 0x0F: return "Audio, Video, Audio Broadcasting, Video Broadcasting"; } return "None"; } static const char *subtype_to_string(uint32_t subtype) { switch (subtype & 0x03) { case 0x01: return "Audio Book"; case 0x02: return "Podcast"; case 0x03: return "Audio Book, Podcast"; } return "None"; } static struct media_item *parse_media_element(struct avrcp *session, uint8_t *operands, uint16_t len) { struct avrcp_player *player; struct media_player *mp; struct media_item *item; uint16_t namelen, namesize; char name[255]; uint64_t uid; uint8_t count; if (len < 13) return NULL; uid = get_be64(&operands[0]); memset(name, 0, sizeof(name)); namesize = get_be16(&operands[11]); namelen = MIN(namesize, sizeof(name) - 1); if (namelen > 0) { memcpy(name, &operands[13], namelen); strtoutf8(name, namelen); } count = operands[13 + namesize]; player = session->controller->player; mp = player->user_data; item = media_player_create_item(mp, name, PLAYER_ITEM_TYPE_AUDIO, uid); if (item == NULL) return NULL; media_item_set_playable(item, true); avrcp_parse_attribute_list(player, item, &operands[14 + namesize], count); return item; } static struct media_item *parse_media_folder(struct avrcp *session, uint8_t *operands, uint16_t len) { struct avrcp_player *player = session->controller->player; struct media_player *mp = player->user_data; struct media_item *item; uint16_t namelen; char name[255]; uint64_t uid; uint8_t type; uint8_t playable; if (len < 12) return NULL; uid = get_be64(&operands[0]); type = operands[8]; playable = operands[9]; memset(name, 0, sizeof(name)); namelen = MIN(get_be16(&operands[12]), sizeof(name) - 1); if (namelen > 0) memcpy(name, &operands[14], namelen); item = media_player_create_folder(mp, name, type, uid); if (!item) return NULL; media_item_set_playable(item, playable & 0x01); return item; } static void avrcp_list_items(struct avrcp *session, uint32_t start, uint32_t end); static gboolean avrcp_list_items_rsp(struct avctp *conn, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp_browsing_header *pdu = (void *) operands; struct avrcp *session = user_data; struct avrcp_player *player = session->controller->player; struct pending_list_items *p = player->p; uint16_t count; uint64_t items; uint64_t new_items = 0; size_t i; int err = 0; if (player->p == NULL) { media_player_list_complete(player->user_data, NULL, -EINVAL); return FALSE; } if (pdu == NULL) { err = -ETIMEDOUT; goto done; } /* AVRCP 1.5 - Page 76: * If the TG receives a GetFolderItems command for an empty folder then * the TG shall return the error (= Range Out of Bounds) in the status * field of the GetFolderItems response. */ if (pdu->params[0] == AVRCP_STATUS_OUT_OF_BOUNDS) goto done; if (pdu->params[0] != AVRCP_STATUS_SUCCESS || operand_count < 5) { err = -EINVAL; goto done; } count = get_be16(&operands[6]); if (count == 0) goto done; for (i = 8; count && i + 3 < operand_count; count--) { struct media_item *item; uint8_t type; uint16_t len; type = operands[i++]; len = get_be16(&operands[i]); i += 2; if (type != 0x03 && type != 0x02) { i += len; continue; } if (i + len > operand_count) { error("Invalid item length"); break; } if (type == 0x03) item = parse_media_element(session, &operands[i], len); else item = parse_media_folder(session, &operands[i], len); if (item) { p->items = g_slist_append(p->items, item); new_items++; } i += len; } items = g_slist_length(p->items); DBG("start %u end %u items %" PRIu64 " total %" PRIu64 "", p->start, p->end, items, p->total); if (new_items > 0 && items < p->total) { avrcp_list_items(session, p->start + items, p->end); return FALSE; } done: media_player_list_complete(player->user_data, p->items, err); g_slist_free(p->items); g_free(p); player->p = NULL; return FALSE; } static void avrcp_list_items(struct avrcp *session, uint32_t start, uint32_t end) { uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 10 + AVRCP_MEDIA_ATTRIBUTE_LAST * sizeof(uint32_t)]; struct avrcp_player *player = session->controller->player; struct avrcp_browsing_header *pdu = (void *) buf; uint16_t length = AVRCP_BROWSING_HEADER_LENGTH + 10; memset(buf, 0, sizeof(buf)); pdu->pdu_id = AVRCP_GET_FOLDER_ITEMS; pdu->param_len = cpu_to_be16(10 + (6 * sizeof(uint32_t))); pdu->params[0] = player->scope; put_be32(start, &pdu->params[1]); put_be32(end, &pdu->params[5]); pdu->params[9] = 6; /* Only the title (0x01) is mandatory. This can be extended to * support AVRCP_MEDIA_ATTRIBUTE_* attributes */ put_be32(AVRCP_MEDIA_ATTRIBUTE_TITLE, &pdu->params[10]); put_be32(AVRCP_MEDIA_ATTRIBUTE_ARTIST, &pdu->params[14]); put_be32(AVRCP_MEDIA_ATTRIBUTE_ALBUM, &pdu->params[18]); put_be32(AVRCP_MEDIA_ATTRIBUTE_TRACK, &pdu->params[22]); put_be32(AVRCP_MEDIA_ATTRIBUTE_DURATION, &pdu->params[26]); put_be32(AVRCP_MEDIA_ATTRIBUTE_IMG_HANDLE, &pdu->params[30]); length += 6 * sizeof(uint32_t); avctp_send_browsing_req(session->conn, buf, length, avrcp_list_items_rsp, session); } static gboolean avrcp_change_path_rsp(struct avctp *conn, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp_browsing_header *pdu = (void *) operands; struct avrcp *session = user_data; struct avrcp_player *player = session->controller->player; struct media_player *mp = player->user_data; int ret; if (pdu == NULL) { ret = -ETIMEDOUT; goto done; } if (pdu->params[0] != AVRCP_STATUS_SUCCESS) { ret = -EINVAL; goto done; } ret = get_be32(&pdu->params[1]); done: if (ret < 0) { g_free(player->change_path); player->change_path = NULL; } else { g_free(player->path); player->path = player->change_path; player->change_path = NULL; } media_player_change_folder_complete(mp, player->path, player->change_uid, ret); player->change_uid = 0; return FALSE; } static gboolean avrcp_set_browsed_player_rsp(struct avctp *conn, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->controller->player; struct media_player *mp = player->user_data; struct avrcp_browsing_header *pdu = (void *) operands; uint32_t items; char **folders; uint8_t depth, count; size_t i; if (pdu == NULL || pdu->params[0] != AVRCP_STATUS_SUCCESS || operand_count < 13) return FALSE; player->uid_counter = get_be16(&pdu->params[1]); player->browsed = true; items = get_be32(&pdu->params[3]); depth = pdu->params[9]; folders = g_new0(char *, depth + 2); folders[0] = g_strdup("/Filesystem"); for (i = 10, count = 1; count - 1 < depth && i < operand_count; count++) { uint8_t len; len = pdu->params[i++]; if (!len) continue; if (i + len > operand_count) { error("Invalid folder length"); break; } folders[count] = util_memdup(&pdu->params[i], len); i += len; } player->path = g_build_pathv("/", folders); g_strfreev(folders); media_player_set_folder(mp, player->path, items); return FALSE; } static void avrcp_set_browsed_player(struct avrcp *session, struct avrcp_player *player) { uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 2]; struct avrcp_browsing_header *pdu = (void *) buf; uint16_t id; memset(buf, 0, sizeof(buf)); pdu->pdu_id = AVRCP_SET_BROWSED_PLAYER; id = cpu_to_be16(player->id); memcpy(pdu->params, &id, 2); pdu->param_len = cpu_to_be16(2); avctp_send_browsing_req(session->conn, buf, sizeof(buf), avrcp_set_browsed_player_rsp, session); } static gboolean avrcp_get_item_attributes_rsp(struct avctp *conn, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->controller->player; struct avrcp_browsing_header *pdu = (void *) operands; struct media_player *mp = player->user_data; struct media_item *item; uint8_t count; if (pdu == NULL) { avrcp_get_element_attributes(session); return FALSE; } if (pdu->params[0] != AVRCP_STATUS_SUCCESS || operand_count < 4) { avrcp_get_element_attributes(session); return FALSE; } count = pdu->params[1]; if (be16_to_cpu(pdu->param_len) - 1 < count * 8) { error("Invalid parameters"); return FALSE; } media_player_clear_metadata(mp); item = media_player_set_playlist_item(mp, player->uid); avrcp_parse_attribute_list(player, item, &pdu->params[2], count); media_player_metadata_changed(mp); avrcp_get_play_status(session); return FALSE; } static void avrcp_get_item_attributes(struct avrcp *session, uint64_t uid) { struct avrcp_player *player = session->controller->player; uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 12]; struct avrcp_browsing_header *pdu = (void *) buf; memset(buf, 0, sizeof(buf)); pdu->pdu_id = AVRCP_GET_ITEM_ATTRIBUTES; pdu->params[0] = 0x03; put_be64(uid, &pdu->params[1]); put_be16(player->uid_counter, &pdu->params[9]); pdu->param_len = cpu_to_be16(12); avctp_send_browsing_req(session->conn, buf, sizeof(buf), avrcp_get_item_attributes_rsp, session); } static void avrcp_player_parse_features(struct avrcp_player *player, uint8_t *features) { struct media_player *mp = player->user_data; player->features = util_memdup(features, 16); if (features[7] & 0x08) { media_player_set_browsable(mp, true); media_player_create_folder(mp, "/Filesystem", PLAYER_FOLDER_TYPE_MIXED, 0); } if (features[7] & 0x10) media_player_set_searchable(mp, true); if (features[8] & 0x02) { media_player_create_folder(mp, "/NowPlaying", PLAYER_FOLDER_TYPE_MIXED, 0); media_player_set_playlist(mp, "/NowPlaying"); } } static void avrcp_set_player_value(struct avrcp *session, uint8_t attr, uint8_t val) { uint8_t buf[AVRCP_HEADER_LENGTH + 3]; struct avrcp_header *pdu = (void *) buf; uint8_t length; memset(buf, 0, sizeof(buf)); set_company_id(pdu->company_id, IEEEID_BTSIG); pdu->pdu_id = AVRCP_SET_PLAYER_VALUE; pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; pdu->params[0] = 1; pdu->params[1] = attr; pdu->params[2] = val; pdu->params_len = cpu_to_be16(3); length = AVRCP_HEADER_LENGTH + be16_to_cpu(pdu->params_len); avctp_send_vendordep_req(session->conn, AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, buf, length, avrcp_player_value_rsp, session); } static gboolean avrcp_set_addressed_player_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->controller->player; struct avrcp_header *pdu = (void *) operands; if (!pdu || code != AVC_CTYPE_ACCEPTED) return FALSE; player->addressed = true; return FALSE; } static void avrcp_set_addressed_player(struct avrcp *session, struct avrcp_player *player) { uint8_t buf[AVRCP_HEADER_LENGTH + 2]; struct avrcp_header *pdu = (void *) buf; uint16_t id; memset(buf, 0, sizeof(buf)); set_company_id(pdu->company_id, IEEEID_BTSIG); pdu->pdu_id = AVRCP_SET_ADDRESSED_PLAYER; pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; id = cpu_to_be16(player->id); memcpy(pdu->params, &id, 2); pdu->params_len = cpu_to_be16(2); avctp_send_vendordep_req(session->conn, AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, buf, sizeof(buf), avrcp_set_addressed_player_rsp, session); } static void set_addressed_player(struct avrcp *session, struct avrcp_player *player) { if (!player || !player->id || player->addressed || session->controller->version < 0x0104) return; /* Set player as addressed */ avrcp_set_addressed_player(session, player); } static void set_browsed_player(struct avrcp *session, struct avrcp_player *player) { if (!player || !player->id || player->browsed) return; if (media_player_get_browsable(player->user_data)) avrcp_set_browsed_player(session, player); } static void set_ct_player(struct avrcp *session, struct avrcp_player *player) { struct btd_service *service; if (session->controller->player == player) goto done; service = btd_device_get_service(session->dev, AVRCP_TARGET_UUID); if (service == NULL) { error("Unable to find btd service"); return; } session->controller->player = player; control_set_player(service, player ? media_player_get_path(player->user_data) : NULL); done: set_addressed_player(session, player); set_browsed_player(session, player); } static bool ct_set_setting(struct media_player *mp, const char *key, const char *value, void *user_data) { struct avrcp_player *player = user_data; int attr; int val; struct avrcp *session; session = player->sessions->data; if (session == NULL) return false; if (session->controller->version < 0x0103) return false; set_ct_player(session, player); attr = attr_to_val(key); if (attr < 0) return false; val = attrval_to_val(attr, value); if (val < 0) return false; avrcp_set_player_value(session, attr, val); return true; } static int ct_press(struct avrcp_player *player, uint8_t op) { struct avrcp *session = player->sessions->data; if (session == NULL) return -ENOTCONN; set_ct_player(session, player); return avctp_send_passthrough(session->conn, op, false); } static int ct_hold(struct avrcp_player *player, uint8_t op) { struct avrcp *session = player->sessions->data; if (session == NULL) return -ENOTCONN; set_ct_player(session, player); return avctp_send_passthrough(session->conn, op, true); } static int ct_release(struct avrcp_player *player) { struct avrcp *session = player->sessions->data; if (session == NULL) return -ENOTCONN; set_ct_player(session, player); return avctp_send_release_passthrough(session->conn); } static int ct_play(struct media_player *mp, void *user_data) { struct avrcp_player *player = user_data; return ct_press(player, AVC_PLAY); } static int ct_pause(struct media_player *mp, void *user_data) { struct avrcp_player *player = user_data; return ct_press(player, AVC_PAUSE); } static int ct_stop(struct media_player *mp, void *user_data) { struct avrcp_player *player = user_data; return ct_press(player, AVC_STOP); } static int ct_next(struct media_player *mp, void *user_data) { struct avrcp_player *player = user_data; return ct_press(player, AVC_FORWARD); } static int ct_previous(struct media_player *mp, void *user_data) { struct avrcp_player *player = user_data; return ct_press(player, AVC_BACKWARD); } static int ct_fast_forward(struct media_player *mp, void *user_data) { struct avrcp_player *player = user_data; return ct_hold(player, AVC_FAST_FORWARD); } static int ct_rewind(struct media_player *mp, void *user_data) { struct avrcp_player *player = user_data; return ct_hold(player, AVC_REWIND); } static int ct_press_key(struct media_player *mp, uint8_t avc_key, void *user_data) { struct avrcp_player *player = user_data; return ct_press(player, avc_key); } static int ct_hold_key(struct media_player *mp, uint8_t avc_key, void *user_data) { struct avrcp_player *player = user_data; return ct_hold(player, avc_key); } static int ct_release_key(struct media_player *mp, void *user_data) { struct avrcp_player *player = user_data; return ct_release(player); } static int ct_list_items(struct media_player *mp, const char *name, uint32_t start, uint32_t end, void *user_data) { struct avrcp_player *player = user_data; struct avrcp *session; struct pending_list_items *p; if (player->p != NULL) return -EBUSY; session = player->sessions->data; set_ct_player(session, player); if (g_str_has_prefix(name, "/NowPlaying")) player->scope = 0x03; else if (g_str_has_suffix(name, "/search")) player->scope = 0x02; else player->scope = 0x01; avrcp_list_items(session, start, end); p = g_new0(struct pending_list_items, 1); p->start = start; p->end = end; p->total = (uint64_t) (p->end - p->start) + 1; player->p = p; return 0; } static void avrcp_change_path(struct avrcp *session, uint8_t direction, uint64_t uid) { struct avrcp_player *player = session->controller->player; uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 11]; struct avrcp_browsing_header *pdu = (void *) buf; memset(buf, 0, sizeof(buf)); put_be16(player->uid_counter, &pdu->params[0]); pdu->params[2] = direction; put_be64(uid, &pdu->params[3]); pdu->pdu_id = AVRCP_CHANGE_PATH; pdu->param_len = cpu_to_be16(11); avctp_send_browsing_req(session->conn, buf, sizeof(buf), avrcp_change_path_rsp, session); } static int ct_change_folder(struct media_player *mp, const char *path, uint64_t uid, void *user_data) { struct avrcp_player *player = user_data; struct avrcp *session; uint8_t direction; session = player->sessions->data; set_ct_player(session, player); player->change_path = g_strdup(path); player->change_uid = uid; direction = g_str_has_prefix(path, player->path) ? 0x01 : 0x00; avrcp_change_path(session, direction, uid); return 0; } static gboolean avrcp_search_rsp(struct avctp *conn, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp_browsing_header *pdu = (void *) operands; struct avrcp *session = (void *) user_data; struct avrcp_player *player = session->controller->player; struct media_player *mp = player->user_data; int ret; if (pdu == NULL) { ret = -ETIMEDOUT; goto done; } if (pdu->params[0] != AVRCP_STATUS_SUCCESS || operand_count < 7) { ret = -EINVAL; goto done; } player->uid_counter = get_be16(&pdu->params[1]); ret = get_be32(&pdu->params[3]); done: media_player_search_complete(mp, ret); return FALSE; } static void avrcp_search(struct avrcp *session, const char *string) { uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 255]; struct avrcp_browsing_header *pdu = (void *) buf; uint16_t len, stringlen; memset(buf, 0, sizeof(buf)); len = AVRCP_BROWSING_HEADER_LENGTH + 4; stringlen = strnlen(string, sizeof(buf) - len); len += stringlen; put_be16(AVRCP_CHARSET_UTF8, &pdu->params[0]); put_be16(stringlen, &pdu->params[2]); memcpy(&pdu->params[4], string, stringlen); pdu->pdu_id = AVRCP_SEARCH; pdu->param_len = cpu_to_be16(len - AVRCP_BROWSING_HEADER_LENGTH); avctp_send_browsing_req(session->conn, buf, len, avrcp_search_rsp, session); } static int ct_search(struct media_player *mp, const char *string, void *user_data) { struct avrcp_player *player = user_data; struct avrcp *session; session = player->sessions->data; set_ct_player(session, player); avrcp_search(session, string); return 0; } static gboolean avrcp_play_item_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp_header *pdu = (void *) operands; struct avrcp *session = (void *) user_data; struct avrcp_player *player = session->controller->player; struct media_player *mp = player->user_data; int ret = 0; if (pdu == NULL) { ret = -ETIMEDOUT; goto done; } if (pdu->params[0] != AVRCP_STATUS_SUCCESS) { switch (pdu->params[0]) { case AVRCP_STATUS_UID_CHANGED: case AVRCP_STATUS_DOES_NOT_EXIST: ret = -ENOENT; break; default: ret = -EINVAL; break; } goto done; } done: media_player_play_item_complete(mp, ret); return FALSE; } static void avrcp_play_item(struct avrcp *session, uint64_t uid) { uint8_t buf[AVRCP_HEADER_LENGTH + 11]; struct avrcp_player *player = session->controller->player; struct avrcp_header *pdu = (void *) buf; uint16_t length; memset(buf, 0, sizeof(buf)); set_company_id(pdu->company_id, IEEEID_BTSIG); pdu->pdu_id = AVRCP_PLAY_ITEM; pdu->params_len = cpu_to_be16(11); pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; pdu->params[0] = player->scope; put_be64(uid, &pdu->params[1]); put_be16(player->uid_counter, &pdu->params[9]); length = AVRCP_HEADER_LENGTH + be16_to_cpu(pdu->params_len); avctp_send_vendordep_req(session->conn, AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, buf, length, avrcp_play_item_rsp, session); } static int ct_play_item(struct media_player *mp, const char *name, uint64_t uid, void *user_data) { struct avrcp_player *player = user_data; struct avrcp *session; if (player->p != NULL) return -EBUSY; session = player->sessions->data; set_ct_player(session, player); if (g_strrstr(name, "/NowPlaying")) player->scope = 0x03; else if (g_strrstr(name, "/Search")) player->scope = 0x02; else player->scope = 0x01; avrcp_play_item(session, uid); return 0; } static void avrcp_add_to_nowplaying(struct avrcp *session, uint64_t uid) { uint8_t buf[AVRCP_HEADER_LENGTH + 11]; struct avrcp_player *player = session->controller->player; struct avrcp_header *pdu = (void *) buf; uint16_t length; memset(buf, 0, sizeof(buf)); set_company_id(pdu->company_id, IEEEID_BTSIG); pdu->pdu_id = AVRCP_ADD_TO_NOW_PLAYING; pdu->params_len = cpu_to_be16(11); pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; pdu->params[0] = player->scope; put_be64(uid, &pdu->params[1]); put_be16(player->uid_counter, &pdu->params[9]); length = AVRCP_HEADER_LENGTH + be16_to_cpu(pdu->params_len); avctp_send_vendordep_req(session->conn, AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, buf, length, NULL, session); } static int ct_add_to_nowplaying(struct media_player *mp, const char *name, uint64_t uid, void *user_data) { struct avrcp_player *player = user_data; struct avrcp *session; if (player->p != NULL) return -EBUSY; session = player->sessions->data; if (g_strrstr(name, "/NowPlaying")) player->scope = 0x03; else player->scope = 0x01; set_ct_player(session, player); avrcp_add_to_nowplaying(session, uid); return 0; } static gboolean avrcp_get_total_numberofitems_rsp(struct avctp *conn, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp_browsing_header *pdu = (void *) operands; struct avrcp *session = user_data; struct avrcp_player *player = session->controller->player; struct media_player *mp = player->user_data; uint32_t num_of_items = 0; if (pdu == NULL) return -ETIMEDOUT; if (pdu->params[0] != AVRCP_STATUS_SUCCESS || operand_count < 7) return -EINVAL; if (pdu->params[0] == AVRCP_STATUS_OUT_OF_BOUNDS) goto done; player->uid_counter = get_be16(&pdu->params[1]); num_of_items = get_be32(&pdu->params[3]); if (!num_of_items) return -EINVAL; done: media_player_total_items_complete(mp, num_of_items); return FALSE; } static void avrcp_get_total_numberofitems(struct avrcp *session) { uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 7]; struct avrcp_player *player = session->controller->player; struct avrcp_browsing_header *pdu = (void *) buf; memset(buf, 0, sizeof(buf)); pdu->pdu_id = AVRCP_GET_TOTAL_NUMBER_OF_ITEMS; pdu->param_len = cpu_to_be16(7 + sizeof(uint32_t)); pdu->params[0] = player->scope; avctp_send_browsing_req(session->conn, buf, sizeof(buf), avrcp_get_total_numberofitems_rsp, session); } static int ct_get_total_numberofitems(struct media_player *mp, const char *name, void *user_data) { struct avrcp_player *player = user_data; struct avrcp *session; session = player->sessions->data; set_ct_player(session, player); if (session->controller->version != 0x0106) { error("version not supported"); return -1; } if (g_str_has_prefix(name, "/NowPlaying")) player->scope = 0x03; else if (g_str_has_suffix(name, "/search")) player->scope = 0x02; else player->scope = 0x01; avrcp_get_total_numberofitems(session); return 0; } static const struct media_player_callback ct_cbs = { .set_setting = ct_set_setting, .play = ct_play, .pause = ct_pause, .stop = ct_stop, .next = ct_next, .previous = ct_previous, .fast_forward = ct_fast_forward, .rewind = ct_rewind, .press = ct_press_key, .hold = ct_hold_key, .release = ct_release_key, .list_items = ct_list_items, .change_folder = ct_change_folder, .search = ct_search, .play_item = ct_play_item, .add_to_nowplaying = ct_add_to_nowplaying, .total_items = ct_get_total_numberofitems, }; static struct avrcp_player *create_ct_player(struct avrcp *session, uint16_t id) { struct avrcp_player *player; struct media_player *mp; const char *path; player = g_new0(struct avrcp_player, 1); player->id = id; player->sessions = g_slist_prepend(player->sessions, session); path = device_get_path(session->dev); mp = media_player_controller_create(path, "avrcp", id); if (mp == NULL) { g_free(player); return NULL; } media_player_set_obex_port(mp, session->controller->obex_port); media_player_set_callbacks(mp, &ct_cbs, player); player->user_data = mp; player->destroy = (GDestroyNotify) media_player_destroy; if (session->controller->player == NULL) set_ct_player(session, player); session->controller->players = g_slist_prepend( session->controller->players, player); return player; } static struct avrcp_player *find_ct_player(struct avrcp *session, uint16_t id) { GSList *l; for (l = session->controller->players; l; l = l->next) { struct avrcp_player *player = l->data; if (player->id == 0) { player->id = id; return player; } if (player->id == id) return player; } return NULL; } static struct avrcp_player * avrcp_parse_media_player_item(struct avrcp *session, uint8_t *operands, uint16_t len) { struct avrcp_player *player; struct media_player *mp; uint16_t id, namelen; uint32_t subtype; const char *curval, *strval; char name[255]; if (len < 28) return NULL; id = get_be16(&operands[0]); player = find_ct_player(session, id); if (player == NULL) { player = create_ct_player(session, id); if (player == NULL) return NULL; } else if (player->features != NULL) return player; mp = player->user_data; media_player_set_type(mp, type_to_string(operands[2])); subtype = get_be32(&operands[3]); media_player_set_subtype(mp, subtype_to_string(subtype)); curval = media_player_get_status(mp); strval = status_to_string(operands[7]); if (g_strcmp0(curval, strval) != 0) { media_player_set_status(mp, strval); avrcp_get_play_status(session); } avrcp_player_parse_features(player, &operands[8]); namelen = get_be16(&operands[26]); if (namelen > 0 && namelen + 28 == len) { namelen = MIN(namelen, sizeof(name) - 1); memcpy(name, &operands[28], namelen); name[namelen] = '\0'; media_player_set_name(mp, name); } if (player->addressed) set_browsed_player(session, player); return player; } static void player_destroy(gpointer data) { struct avrcp_player *player = data; if (player->destroy) player->destroy(player->user_data); if (player->changed_id > 0) g_source_remove(player->changed_id); g_slist_free(player->sessions); g_free(player->path); g_free(player->change_path); free(player->features); g_free(player); } static void player_remove(gpointer data) { struct avrcp_player *player = data; GSList *l; /* Don't remove reserved player */ if (!player->id) return; for (l = player->sessions; l; l = l->next) { struct avrcp *session = l->data; struct avrcp_data *controller = session->controller; controller->players = g_slist_remove(controller->players, player); /* Check if current player is being removed */ if (controller->player == player) set_ct_player(session, g_slist_nth_data( controller->players, 0)); } player_destroy(player); } static gboolean avrcp_get_media_player_list_rsp(struct avctp *conn, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp_browsing_header *pdu = (void *) operands; struct avrcp *session = user_data; uint16_t count; size_t i; GSList *removed; if (pdu == NULL || pdu->params[0] != AVRCP_STATUS_SUCCESS || operand_count < 5) return FALSE; removed = g_slist_copy(session->controller->players); count = get_be16(&operands[6]); for (i = 8; count && i < operand_count; count--) { struct avrcp_player *player; uint8_t type; uint16_t len; type = operands[i++]; len = get_be16(&operands[i]); i += 2; if (type != 0x01) { i += len; continue; } if (i + len > operand_count) { error("Invalid player item length"); return FALSE; } player = avrcp_parse_media_player_item(session, &operands[i], len); if (player) removed = g_slist_remove(removed, player); i += len; } g_slist_free_full(removed, player_remove); /* There should always be an active player */ if (!session->controller->player) create_ct_player(session, 0); return FALSE; } static void avrcp_get_media_player_list(struct avrcp *session) { uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 10]; struct avrcp_browsing_header *pdu = (void *) buf; memset(buf, 0, sizeof(buf)); pdu->pdu_id = AVRCP_GET_FOLDER_ITEMS; put_be32(0, &pdu->params[1]); put_be32(UINT32_MAX, &pdu->params[5]); pdu->param_len = cpu_to_be16(10); avctp_send_browsing_req(session->conn, buf, sizeof(buf), avrcp_get_media_player_list_rsp, session); } static void avrcp_volume_changed(struct avrcp *session, struct avrcp_header *pdu) { int8_t volume; if (!avrcp_volume_supported(session->controller)) { error("EVENT_VOLUME_CHANGED not supported"); return; } volume = pdu->params[1] & 0x7F; /* Always attempt to update the transport volume */ media_transport_set_a2dp_volume(session->dev, volume); } static void avrcp_status_changed(struct avrcp *session, struct avrcp_header *pdu) { struct avrcp_player *player = session->controller->player; struct media_player *mp = player->user_data; uint8_t value; const char *curval, *strval; value = pdu->params[1]; curval = media_player_get_status(mp); strval = status_to_string(value); if (g_strcmp0(curval, strval) == 0) return; media_player_set_status(mp, strval); avrcp_get_play_status(session); } static void avrcp_track_changed(struct avrcp *session, struct avrcp_header *pdu) { if (session->browsing_id) { struct avrcp_player *player = session->controller->player; player->uid = get_be64(&pdu->params[1]); avrcp_get_item_attributes(session, player->uid); } else avrcp_get_element_attributes(session); } static void avrcp_playback_pos_changed(struct avrcp *session, struct avrcp_header *pdu) { struct avrcp_player *player = session->controller->player; struct media_player *mp = player->user_data; uint32_t position; position = get_be32(&pdu->params[1]); media_player_set_position(mp, position); } static void avrcp_setting_changed(struct avrcp *session, struct avrcp_header *pdu) { struct avrcp_player *player = session->controller->player; struct media_player *mp = player->user_data; uint8_t count = pdu->params[1]; int i; for (i = 2; count > 0; count--, i += 2) { const char *key; const char *value; key = attr_to_str(pdu->params[i]); if (key == NULL) continue; value = attrval_to_str(pdu->params[i], pdu->params[i + 1]); if (value == NULL) continue; media_player_set_setting(mp, key, value); } } static void avrcp_now_playing_changed(struct avrcp *session, struct avrcp_header *pdu) { struct avrcp_player *player = session->controller->player; struct media_player *mp = player->user_data; DBG("NowPlaying changed"); /* reset the list_items operation, if it is in progress or else we will * crash because _clear_playlist() frees the items */ if (player->p) { g_slist_free(player->p->items); g_free(player->p); player->p = NULL; } media_player_clear_playlist(mp); } static void avrcp_available_players_changed(struct avrcp *session, struct avrcp_header *pdu) { avrcp_get_media_player_list(session); } static void avrcp_addressed_player_changed(struct avrcp *session, struct avrcp_header *pdu) { struct avrcp_player *player = session->controller->player; uint16_t id = get_be16(&pdu->params[1]); if (player != NULL && player->id == id) return; player = find_ct_player(session, id); if (player == NULL) { player = create_ct_player(session, id); if (player == NULL) return; } player->addressed = true; player->uid_counter = get_be16(&pdu->params[3]); set_ct_player(session, player); if (player->features != NULL) return; avrcp_get_media_player_list(session); } static void avrcp_uids_changed(struct avrcp *session, struct avrcp_header *pdu) { struct avrcp_player *player = session->controller->player; player->uid_counter = get_be16(&pdu->params[1]); } static gboolean avrcp_handle_event(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_data *controller = session->controller; struct avrcp_header *pdu = (void *) operands; uint8_t event; if (!pdu) return FALSE; if (!controller) return FALSE; if ((code != AVC_CTYPE_INTERIM && code != AVC_CTYPE_CHANGED)) { if (pdu->params[0] == AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED && code == AVC_CTYPE_REJECTED) { int i; /* Lookup event by transaction */ for (i = 0; i <= AVRCP_EVENT_LAST; i++) { if (controller->transaction_events[i] == transaction) { event = i; goto changed; } } } return FALSE; } event = pdu->params[0]; if (code == AVC_CTYPE_CHANGED) { goto changed; } switch (event) { case AVRCP_EVENT_VOLUME_CHANGED: avrcp_volume_changed(session, pdu); break; case AVRCP_EVENT_STATUS_CHANGED: avrcp_status_changed(session, pdu); break; case AVRCP_EVENT_TRACK_CHANGED: avrcp_track_changed(session, pdu); break; case AVRCP_EVENT_PLAYBACK_POS_CHANGED: avrcp_playback_pos_changed(session, pdu); break; case AVRCP_EVENT_SETTINGS_CHANGED: avrcp_setting_changed(session, pdu); break; case AVRCP_EVENT_NOW_PLAYING_CHANGED: avrcp_now_playing_changed(session, pdu); break; case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED: avrcp_available_players_changed(session, pdu); break; case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: avrcp_addressed_player_changed(session, pdu); break; case AVRCP_EVENT_UIDS_CHANGED: avrcp_uids_changed(session, pdu); break; default: if (event > AVRCP_EVENT_LAST) { warn("Unsupported event: %u", event); return FALSE; } break; } controller->registered_events |= (1 << event); controller->transaction_events[event] = transaction; return TRUE; changed: controller->registered_events ^= (1 << event); controller->transaction_events[event] = 0; avrcp_register_notification(session, event); return FALSE; } static void avrcp_register_notification(struct avrcp *session, uint8_t event) { uint8_t buf[AVRCP_HEADER_LENGTH + AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH]; struct avrcp_header *pdu = (void *) buf; uint8_t length; memset(buf, 0, sizeof(buf)); set_company_id(pdu->company_id, IEEEID_BTSIG); pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION; pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; pdu->params[0] = event; /* * Set maximum interval possible for position changed as we only * use it to resync. */ if (event == AVRCP_EVENT_PLAYBACK_POS_CHANGED) bt_put_be32(UINT32_MAX / 1000, &pdu->params[1]); pdu->params_len = cpu_to_be16(AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH); length = AVRCP_HEADER_LENGTH + be16_to_cpu(pdu->params_len); avctp_send_vendordep_req(session->conn, AVC_CTYPE_NOTIFY, AVC_SUBUNIT_PANEL, buf, length, avrcp_handle_event, session); } static gboolean avrcp_get_capabilities_resp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_header *pdu = (void *) operands; uint16_t events = 0; uint8_t count; if (code == AVC_CTYPE_REJECTED || code == AVC_CTYPE_NOT_IMPLEMENTED || pdu == NULL || pdu->params[0] != CAP_EVENTS_SUPPORTED) return FALSE; /* Connect browsing if pending */ if (session->browsing_timer > 0) { timeout_remove(session->browsing_timer); session->browsing_timer = 0; avctp_connect_browsing(session->conn); } count = pdu->params[1]; for (; count > 0; count--) { uint8_t event = pdu->params[1 + count]; events |= (1 << event); switch (event) { case AVRCP_EVENT_STATUS_CHANGED: case AVRCP_EVENT_TRACK_CHANGED: case AVRCP_EVENT_PLAYBACK_POS_CHANGED: case AVRCP_EVENT_SETTINGS_CHANGED: case AVRCP_EVENT_NOW_PLAYING_CHANGED: case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: case AVRCP_EVENT_UIDS_CHANGED: case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED: /* These events above require a player */ if (!session->controller || !session->controller->player) break; /* fall through */ case AVRCP_EVENT_VOLUME_CHANGED: avrcp_register_notification(session, event); break; } } if (!session->controller || !session->controller->player) return FALSE; /* Skip if player status/metadata if only volume changes is supported */ if (events == (1 << AVRCP_EVENT_VOLUME_CHANGED)) return FALSE; if ((session->controller->features & AVRCP_FEATURE_TG_PLAYER_SETTINGS) && !(events & (1 << AVRCP_EVENT_SETTINGS_CHANGED))) avrcp_list_player_attributes(session); if (!(events & (1 << AVRCP_EVENT_STATUS_CHANGED))) avrcp_get_play_status(session); if (!(events & (1 << AVRCP_EVENT_STATUS_CHANGED))) avrcp_get_element_attributes(session); return FALSE; } static void avrcp_get_capabilities(struct avrcp *session) { uint8_t buf[AVRCP_HEADER_LENGTH + AVRCP_GET_CAPABILITIES_PARAM_LENGTH]; struct avrcp_header *pdu = (void *) buf; memset(buf, 0, sizeof(buf)); set_company_id(pdu->company_id, IEEEID_BTSIG); pdu->pdu_id = AVRCP_GET_CAPABILITIES; pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; pdu->params[0] = CAP_EVENTS_SUPPORTED; pdu->params_len = cpu_to_be16(AVRCP_GET_CAPABILITIES_PARAM_LENGTH); avctp_send_vendordep_req(session->conn, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, buf, sizeof(buf), avrcp_get_capabilities_resp, session); } static struct avrcp *find_session(GSList *list, struct btd_device *dev) { for (; list; list = list->next) { struct avrcp *session = list->data; if (session->dev == dev) return session; } return NULL; } static void destroy_browsing(void *data) { struct avrcp *session = data; session->browsing_id = 0; } static void session_init_browsing(struct avrcp *session) { if (session->browsing_timer > 0) { timeout_remove(session->browsing_timer); session->browsing_timer = 0; } session->browsing_id = avctp_register_browsing_pdu_handler( session->conn, handle_browsing_pdu, session, destroy_browsing); } static struct avrcp_data *data_init(struct avrcp *session, const char *uuid) { struct avrcp_data *data; const sdp_record_t *rec; sdp_list_t *list, *protos; sdp_profile_desc_t *desc; int port = 0; data = g_new0(struct avrcp_data, 1); rec = btd_device_get_record(session->dev, uuid); if (rec == NULL) return data; if (sdp_get_profile_descs(rec, &list) == 0) { desc = list->data; data->version = desc->version; } sdp_get_int_attr(rec, SDP_ATTR_SUPPORTED_FEATURES, &data->features); sdp_list_free(list, free); if ((g_strcmp0(uuid, AVRCP_TARGET_UUID) != 0) || !(data->features & AVRCP_FEATURE_TG_COVERT_ART) || (sdp_get_add_access_protos(rec, &protos) != 0)) return data; /* Get the PSM port from the Additional Protocol Descriptor list * entry containing OBEX UUID */ for (list = protos; list; list = list->next) { sdp_list_t *p; for (p = list->data; p; p = p->next) { sdp_data_t *seq = p->data; if ((sdp_uuid_to_proto(&seq->val.uuid) == OBEX_UUID) && SDP_IS_UUID(seq->dtd)) { port = sdp_get_proto_port(list, L2CAP_UUID); goto done; } } } done: if (port > 0) data->obex_port = port; sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); sdp_list_free(protos, NULL); return data; } static bool connect_browsing(gpointer user_data) { struct avrcp *session = user_data; session->browsing_timer = 0; avctp_connect_browsing(session->conn); return FALSE; } static void avrcp_connect_browsing(struct avrcp *session) { /* Immediately connect browsing channel if initiator otherwise delay * it to avoid possible collisions */ if (avctp_is_initiator(session->conn)) { avctp_connect_browsing(session->conn); return; } if (session->browsing_timer > 0) return; session->browsing_timer = timeout_add_seconds(AVRCP_BROWSING_TIMEOUT, connect_browsing, session, NULL); } static void target_init(struct avrcp *session) { struct avrcp_server *server = session->server; struct avrcp_data *target; struct avrcp_player *player; struct btd_service *service; if (session->target != NULL) return; service = btd_device_get_service(session->dev, AVRCP_REMOTE_UUID); if (service == NULL) { error("Unable to find btd service"); return; } target = data_init(session, AVRCP_REMOTE_UUID); session->target = target; DBG("%p version 0x%04x", target, target->version); btd_service_connecting_complete(service, 0); player = g_slist_nth_data(server->players, 0); if (player != NULL) { int8_t init_volume; target->player = player; player->sessions = g_slist_prepend(player->sessions, session); init_volume = btd_device_get_volume(session->dev); media_transport_set_a2dp_volume(session->dev, init_volume); } session->supported_events |= (1 << AVRCP_EVENT_STATUS_CHANGED) | (1 << AVRCP_EVENT_TRACK_CHANGED) | (1 << AVRCP_EVENT_TRACK_REACHED_START) | (1 << AVRCP_EVENT_TRACK_REACHED_END) | (1 << AVRCP_EVENT_SETTINGS_CHANGED); if (target->version < 0x0104) return; if (avrcp_volume_supported(target)) { session->supported_events |= (1 << AVRCP_EVENT_VOLUME_CHANGED); /* Check if transport volume hasn't been initialized then set it * to max so it works properly if the controller attempts to * subscribe to AVRCP_EVENT_VOLUME_CHANGED. */ if (media_transport_get_a2dp_volume(session->dev) < 0) media_transport_set_a2dp_volume(session->dev, 127); } session->supported_events |= (1 << AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED) | (1 << AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED); /* Only check capabilities if controller is not supported */ if (session->controller == NULL) avrcp_get_capabilities(session); if (!(target->features & AVRCP_FEATURE_BROWSING)) return; avrcp_connect_browsing(session); } static void controller_init(struct avrcp *session) { struct avrcp_player *player; struct btd_service *service; struct avrcp_data *controller; if (session->controller != NULL) return; service = btd_device_get_service(session->dev, AVRCP_TARGET_UUID); if (service == NULL) { error("Unable to find btd service"); return; } controller = data_init(session, AVRCP_TARGET_UUID); session->controller = controller; DBG("%p version 0x%04x", controller, controller->version); if (controller->obex_port) DBG("%p OBEX PSM 0x%04x", controller, controller->obex_port); btd_service_connecting_complete(service, 0); /* Only create player if category 1 is supported */ if (controller->features & AVRCP_FEATURE_CATEGORY_1) { player = create_ct_player(session, 0); if (player == NULL) return; } if (controller->version < 0x0103) return; avrcp_get_capabilities(session); if (controller->version < 0x0104) return; if (!(controller->features & AVRCP_FEATURE_BROWSING)) return; avrcp_connect_browsing(session); } static void session_init_control(struct avrcp *session) { session->passthrough_id = avctp_register_passthrough_handler( session->conn, handle_passthrough, session); session->passthrough_handlers = passthrough_handlers; session->control_id = avctp_register_pdu_handler(session->conn, AVC_OP_VENDORDEP, handle_vendordep_pdu, session); session->control_handlers = control_handlers; if (btd_device_get_service(session->dev, AVRCP_TARGET_UUID) != NULL) controller_init(session); if (btd_device_get_service(session->dev, AVRCP_REMOTE_UUID) != NULL) target_init(session); } static void controller_destroy(struct avrcp *session) { struct avrcp_data *controller = session->controller; DBG("%p", controller); g_slist_free_full(controller->players, player_destroy); g_free(controller); } static void target_destroy(struct avrcp *session) { struct avrcp_data *target = session->target; struct avrcp_player *player = target->player; DBG("%p", target); if (player != NULL) player->sessions = g_slist_remove(player->sessions, session); g_free(target); } static void session_destroy(struct avrcp *session, int err) { struct avrcp_server *server = session->server; struct btd_service *service; server->sessions = g_slist_remove(server->sessions, session); session_abort_pending_pdu(session); service = btd_device_get_service(session->dev, AVRCP_TARGET_UUID); if (service != NULL) { if (session->control_id == 0) btd_service_connecting_complete(service, err); else btd_service_disconnecting_complete(service, 0); } service = btd_device_get_service(session->dev, AVRCP_REMOTE_UUID); if (service != NULL) { if (session->control_id == 0) btd_service_connecting_complete(service, err); else btd_service_disconnecting_complete(service, 0); } if (session->browsing_timer > 0) timeout_remove(session->browsing_timer); if (session->controller != NULL) controller_destroy(session); if (session->target != NULL) target_destroy(session); if (session->passthrough_id > 0) avctp_unregister_passthrough_handler(session->passthrough_id); if (session->control_id > 0) avctp_unregister_pdu_handler(session->control_id); if (session->browsing_id > 0) avctp_unregister_browsing_pdu_handler(session->browsing_id); g_free(session); } static struct avrcp *session_create(struct avrcp_server *server, struct btd_device *device) { struct avrcp *session; session = g_new0(struct avrcp, 1); session->server = server; session->conn = avctp_connect(device); session->dev = device; server->sessions = g_slist_append(server->sessions, session); return session; } static void state_changed(struct btd_device *device, avctp_state_t old_state, avctp_state_t new_state, int err, void *user_data) { struct avrcp_server *server; struct avrcp *session; server = find_server(servers, device_get_adapter(device)); if (!server) return; session = find_session(server->sessions, device); switch (new_state) { case AVCTP_STATE_DISCONNECTED: if (session == NULL) break; session_destroy(session, err); break; case AVCTP_STATE_CONNECTING: if (session != NULL) break; session_create(server, device); break; case AVCTP_STATE_CONNECTED: if (session == NULL || session->control_id > 0) break; session_init_control(session); break; case AVCTP_STATE_BROWSING_CONNECTED: if (session == NULL || session->browsing_id > 0) break; session_init_browsing(session); break; case AVCTP_STATE_BROWSING_CONNECTING: default: return; } } static struct avrcp_server *avrcp_server_register(struct btd_adapter *adapter) { struct avrcp_server *server; bool browsing; if (avctp_register(adapter, TRUE, &browsing) < 0) return NULL; server = g_new0(struct avrcp_server, 1); server->adapter = btd_adapter_ref(adapter); server->browsing = browsing; servers = g_slist_append(servers, server); if (!avctp_id) avctp_id = avctp_add_state_cb(NULL, state_changed, NULL); return server; } static void avrcp_server_unregister(struct avrcp_server *server) { g_slist_free_full(server->sessions, g_free); g_slist_free_full(server->players, player_destroy); servers = g_slist_remove(servers, server); avctp_unregister(server->adapter); btd_adapter_unref(server->adapter); g_free(server); if (servers) return; if (avctp_id) { avctp_remove_state_cb(avctp_id); avctp_id = 0; } } struct avrcp_player *avrcp_register_player(struct btd_adapter *adapter, struct avrcp_player_cb *cb, void *user_data, GDestroyNotify destroy) { struct avrcp_server *server; struct avrcp_player *player; GSList *l; static uint16_t id = 0; server = find_server(servers, adapter); if (!server) return NULL; player = g_new0(struct avrcp_player, 1); player->id = ++id; player->server = server; player->cb = cb; player->user_data = user_data; player->destroy = destroy; server->players = g_slist_append(server->players, player); /* Assign player to session without current player */ for (l = server->sessions; l; l = l->next) { struct avrcp *session = l->data; struct avrcp_data *target = session->target; if (target == NULL) continue; if (target->player == NULL) { target->player = player; player->sessions = g_slist_append(player->sessions, session); } } avrcp_player_event(player, AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED, NULL); return player; } void avrcp_unregister_player(struct avrcp_player *player) { struct avrcp_server *server = player->server; GSList *l; server->players = g_slist_remove(server->players, player); /* Remove player from sessions using it */ for (l = player->sessions; l; l = l->next) { struct avrcp *session = l->data; struct avrcp_data *target = session->target; if (target == NULL) continue; if (target->player != player) continue; target->player = g_slist_nth_data(server->players, 0); if (target->player) { target->player->sessions = g_slist_append( target->player->sessions, session); notify_addressed_player_changed(player); } } avrcp_player_event(player, AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED, NULL); player_destroy(player); } static gboolean avrcp_handle_set_volume(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_header *pdu = (void *) operands; int8_t volume; if (code == AVC_CTYPE_REJECTED || code == AVC_CTYPE_NOT_IMPLEMENTED || pdu == NULL) return FALSE; volume = pdu->params[0] & 0x7F; /* Always attempt to update the transport volume */ media_transport_set_a2dp_volume(session->dev, volume); return FALSE; } static int avrcp_event(struct avrcp *session, uint8_t id, const void *data) { uint8_t buf[AVRCP_HEADER_LENGTH + 2]; struct avrcp_header *pdu = (void *) buf; uint8_t code; uint16_t size; int err; /* Verify that the event is registered */ if (!session || !(session->target->registered_events & (1 << id))) return -ENOENT; memset(buf, 0, sizeof(buf)); set_company_id(pdu->company_id, IEEEID_BTSIG); pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION; code = AVC_CTYPE_CHANGED; pdu->params[0] = id; DBG("id=%u", id); switch (id) { case AVRCP_EVENT_VOLUME_CHANGED: size = 2; memcpy(&pdu->params[1], data, sizeof(uint8_t)); break; default: error("Unknown event %u", id); return -EINVAL; } pdu->params_len = cpu_to_be16(size); err = avctp_send_vendordep(session->conn, session->target->transaction_events[id], code, AVC_SUBUNIT_PANEL, buf, size + AVRCP_HEADER_LENGTH); if (err < 0) return err; /* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */ session->target->registered_events ^= 1 << id; return err; } static bool avrcp_target_event_registered(struct avrcp *session, uint8_t event) { return session->target && session->target->registered_events & (1 << event); } int avrcp_set_volume(struct btd_device *dev, int8_t volume, bool notify) { struct avrcp_server *server; struct avrcp *session; uint8_t buf[AVRCP_HEADER_LENGTH + 1]; struct avrcp_header *pdu = (void *) buf; if (volume < 0) return -EINVAL; server = find_server(servers, device_get_adapter(dev)); if (server == NULL) return -EINVAL; session = find_session(server->sessions, dev); if (session == NULL) return -ENOTCONN; if (notify) { if (!avrcp_volume_supported(session->target)) { error("EVENT_VOLUME_CHANGED not supported"); return -ENOTSUP; } return avrcp_event(session, AVRCP_EVENT_VOLUME_CHANGED, &volume); } if (btd_opts.avrcp.volume_without_target) { /* If there is no target profile (we didn't create a controller * for it), allow the call to pass through if the remote * controller registered for a volume changed event. */ if (!session->controller && !avrcp_target_event_registered(session, AVRCP_EVENT_VOLUME_CHANGED)) return -ENOTSUP; } else if (!avrcp_volume_supported(session->controller)) { error("SetAbsoluteVolume not supported"); return -ENOTSUP; } memset(buf, 0, sizeof(buf)); set_company_id(pdu->company_id, IEEEID_BTSIG); pdu->pdu_id = AVRCP_SET_ABSOLUTE_VOLUME; pdu->params[0] = volume; pdu->params_len = cpu_to_be16(1); return avctp_send_vendordep_req(session->conn, AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, buf, sizeof(buf), avrcp_handle_set_volume, session); } struct avrcp_player *avrcp_get_target_player_by_device(struct btd_device *dev) { struct avrcp_server *server; struct avrcp *session; struct avrcp_data *target; server = find_server(servers, device_get_adapter(dev)); if (server == NULL) return NULL; session = find_session(server->sessions, dev); if (session == NULL) return NULL; target = session->target; if (target == NULL) return NULL; return target->player; } static int avrcp_connect(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); const char *path = device_get_path(dev); DBG("path %s", path); return control_connect(service); } static int avrcp_disconnect(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); const char *path = device_get_path(dev); DBG("path %s", path); return control_disconnect(service); } static int avrcp_target_probe(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); DBG("path %s", device_get_path(dev)); return control_init_target(service); } static void avrcp_target_remove(struct btd_service *service) { control_unregister(service); } static void avrcp_target_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { struct avrcp_server *server; DBG("path %s", adapter_get_path(adapter)); server = find_server(servers, adapter); if (!server) return; if (server->tg_record_id != 0) { adapter_service_remove(adapter, server->tg_record_id); server->tg_record_id = 0; } if (server->ct_record_id == 0) avrcp_server_unregister(server); } static int avrcp_target_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { sdp_record_t *record; struct avrcp_server *server; DBG("path %s", adapter_get_path(adapter)); server = find_server(servers, adapter); if (server != NULL) goto done; server = avrcp_server_register(adapter); if (server == NULL) return -EPROTONOSUPPORT; done: record = avrcp_tg_record(server->browsing); if (!record) { error("Unable to allocate new service record"); avrcp_target_server_remove(p, adapter); return -1; } if (adapter_service_add(adapter, record) < 0) { error("Unable to register AVRCP target service record"); avrcp_target_server_remove(p, adapter); sdp_record_free(record); return -1; } server->tg_record_id = record->handle; return 0; } static struct btd_profile avrcp_target_profile = { .name = "audio-avrcp-target", .bearer = BTD_PROFILE_BEARER_BREDR, .remote_uuid = AVRCP_TARGET_UUID, .device_probe = avrcp_target_probe, .device_remove = avrcp_target_remove, .connect = avrcp_connect, .disconnect = avrcp_disconnect, .adapter_probe = avrcp_target_server_probe, .adapter_remove = avrcp_target_server_remove, }; static int avrcp_controller_probe(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); DBG("path %s", device_get_path(dev)); return control_init_remote(service); } static void avrcp_controller_remove(struct btd_service *service) { control_unregister(service); } static void avrcp_controller_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { struct avrcp_server *server; DBG("path %s", adapter_get_path(adapter)); server = find_server(servers, adapter); if (!server) return; if (server->ct_record_id != 0) { adapter_service_remove(adapter, server->ct_record_id); server->ct_record_id = 0; } if (server->tg_record_id == 0) avrcp_server_unregister(server); } static int avrcp_controller_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { sdp_record_t *record; struct avrcp_server *server; DBG("path %s", adapter_get_path(adapter)); server = find_server(servers, adapter); if (server != NULL) goto done; server = avrcp_server_register(adapter); if (server == NULL) return -EPROTONOSUPPORT; done: record = avrcp_ct_record(server->browsing); if (!record) { error("Unable to allocate new service record"); avrcp_controller_server_remove(p, adapter); return -1; } if (adapter_service_add(adapter, record) < 0) { error("Unable to register AVRCP service record"); avrcp_controller_server_remove(p, adapter); sdp_record_free(record); return -1; } server->ct_record_id = record->handle; return 0; } static struct btd_profile avrcp_controller_profile = { .name = "avrcp-controller", .bearer = BTD_PROFILE_BEARER_BREDR, .remote_uuid = AVRCP_REMOTE_UUID, .device_probe = avrcp_controller_probe, .device_remove = avrcp_controller_remove, .connect = avrcp_connect, .disconnect = avrcp_disconnect, .adapter_probe = avrcp_controller_server_probe, .adapter_remove = avrcp_controller_server_remove, }; static int avrcp_init(void) { int err; err = btd_profile_register(&avrcp_controller_profile); if (err) return err; err = btd_profile_register(&avrcp_target_profile); if (err) { btd_profile_unregister(&avrcp_controller_profile); return err; } populate_default_features(); avrcp_player_init(); return 0; } static void avrcp_exit(void) { avrcp_player_exit(); btd_profile_unregister(&avrcp_controller_profile); btd_profile_unregister(&avrcp_target_profile); } BLUETOOTH_PLUGIN_DEFINE(avrcp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, avrcp_init, avrcp_exit) bluez-5.87/profiles/audio/PaxHeaders/telephony.h0000644000000000000000000000005015142177620016764 xustar0020 atime=1783093718 20 ctime=1783094106 bluez-5.87/profiles/audio/telephony.h0000644000000000000000000000757515142177620016463 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright © 2025 Collabora Ltd. * * */ enum connection_state { CONNECTING = 0, SESSION_CONNECTING, CONNECTED, DISCONNECTING }; enum call_state { CALL_STATE_ACTIVE = 0, CALL_STATE_HELD, CALL_STATE_DIALING, CALL_STATE_ALERTING, CALL_STATE_INCOMING, CALL_STATE_WAITING, CALL_STATE_RESPONSE_AND_HOLD, CALL_STATE_DISCONNECTED, }; struct telephony; struct telephony_callbacks { DBusMessage *(*dial)(DBusConnection *conn, DBusMessage *msg, void *profile_data); DBusMessage *(*swap_calls)(DBusConnection *conn, DBusMessage *msg, void *profile_data); DBusMessage *(*release_and_answer)(DBusConnection *conn, DBusMessage *msg, void *profile_data); DBusMessage *(*release_and_swap)(DBusConnection *conn, DBusMessage *msg, void *profile_data); DBusMessage *(*hold_and_answer)(DBusConnection *conn, DBusMessage *msg, void *profile_data); DBusMessage *(*hangup_all)(DBusConnection *conn, DBusMessage *msg, void *profile_data); DBusMessage *(*create_multiparty)(DBusConnection *conn, DBusMessage *msg, void *profile_data); DBusMessage *(*send_tones)(DBusConnection *conn, DBusMessage *msg, void *profile_data); DBusMessage *(*call_answer)(DBusConnection *conn, DBusMessage *msg, void *call_data); DBusMessage *(*call_hangup)(DBusConnection *conn, DBusMessage *msg, void *call_data); }; struct call { struct telephony *device; char *path; uint8_t idx; char *line_id; char *incoming_line; char *name; bool multiparty; enum call_state state; DBusMessage *pending_msg; }; struct telephony *telephony_new(struct btd_service *service, void *profile_data, struct telephony_callbacks *cbs); void telephony_free(struct telephony *telephony); int telephony_register_interface(struct telephony *telephony); void telephony_unregister_interface(struct telephony *telephony); struct btd_service *telephony_get_service(struct telephony *telephony); struct btd_device *telephony_get_device(struct telephony *telephony); const char *telephony_get_path(struct telephony *telephony); bdaddr_t telephony_get_src(struct telephony *telephony); bdaddr_t telephony_get_dst(struct telephony *telephony); void *telephony_get_profile_data(struct telephony *telephony); void telephony_add_uri_scheme(struct telephony *telephony, const char *scheme); void telephony_remove_uri_scheme(struct telephony *telephony, const char *scheme); void telephony_set_state(struct telephony *telephony, enum connection_state state); enum connection_state telephony_get_state(struct telephony *telephony); void telephony_set_network_service(struct telephony *telephony, bool service); bool telephony_get_network_service(struct telephony *telephony); void telephony_set_signal(struct telephony *telephony, uint8_t signal); uint8_t telephony_get_signal(struct telephony *telephony); void telephony_set_roaming(struct telephony *telephony, bool roaming); bool telephony_get_roaming(struct telephony *telephony); void telephony_set_battchg(struct telephony *telephony, uint8_t battchg); uint8_t telephony_get_battchg(struct telephony *telephony); void telephony_set_operator_name(struct telephony *telephony, const char *name); const char *telephony_get_operator_name(struct telephony *telephony); void telephony_set_inband_ringtone(struct telephony *telephony, bool enabled); bool telephony_get_inband_ringtone(struct telephony *telephony); struct call *telephony_new_call(struct telephony *telephony, uint8_t idx, enum call_state state, void *user_data); void telephony_free_call(struct call *call); int telephony_call_register_interface(struct call *call); void telephony_call_unregister_interface(struct call *call); void telephony_call_set_state(struct call *call, enum call_state state); void telephony_call_set_line_id(struct call *call, const char *line_id); bluez-5.87/profiles/audio/PaxHeaders/source.h0000644000000000000000000000005014015011623016241 xustar0020 atime=1783093618 20 ctime=1783094105 bluez-5.87/profiles/audio/source.h0000644000000000000000000000215714015011623015727 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright (C) 2009 Joao Paulo Rechi Vita * * */ typedef enum { SOURCE_STATE_DISCONNECTED, SOURCE_STATE_CONNECTING, SOURCE_STATE_CONNECTED, SOURCE_STATE_PLAYING, } source_state_t; typedef void (*source_state_cb) (struct btd_service *service, source_state_t old_state, source_state_t new_state, void *user_data); struct btd_service; unsigned int source_add_state_cb(struct btd_service *service, source_state_cb cb, void *user_data); gboolean source_remove_state_cb(unsigned int id); int source_init(struct btd_service *service); void source_unregister(struct btd_service *service); int source_connect(struct btd_service *service); gboolean source_new_stream(struct btd_service *service, struct avdtp *session, struct avdtp_stream *stream); gboolean source_setup_stream(struct btd_service *service, struct avdtp *session); int source_disconnect(struct btd_service *service); bluez-5.87/profiles/audio/PaxHeaders/mcp.c0000644000000000000000000000005015142177620015527 xustar0020 atime=1783093706 20 ctime=1783094106 bluez-5.87/profiles/audio/mcp.c0000644000000000000000000006300515142177620015214 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2020 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/dbus-common.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/shared/gatt-server.h" #include "src/shared/mcp.h" #include "src/shared/mcs.h" #include "src/shared/uinput.h" #include "btio/btio.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/gatt-database.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #include "src/error.h" #include "player.h" #include "media.h" #define MCS_UUID_STR "00001848-0000-1000-8000-00805f9b34fb" #define GMCS_UUID_STR "00001849-0000-1000-8000-00805f9b34fb" /* * Remote player */ struct remote_player { struct bt_mcp *mcp; uint8_t ccid; struct media_player *mp; uint8_t playing_order; }; static char *name2utf8(const uint8_t *name, uint16_t len) { char *utf8_name; utf8_name = malloc(len + 1); if (!utf8_name) return NULL; if (len) memcpy(utf8_name, name, len); utf8_name[len] = 0; strtoutf8(utf8_name, len); /* Remove leading and trailing whitespace characters */ g_strstrip(utf8_name); return utf8_name; } static const char *mcp_status_val_to_string(uint8_t status) { switch (status) { case BT_MCS_STATE_PLAYING: return "playing"; case BT_MCS_STATE_PAUSED: return "paused"; case BT_MCS_STATE_INACTIVE: return "stopped"; case BT_MCS_STATE_SEEKING: /* TODO: find a way for fwd/rvs seeking, probably by storing * control point operation sent before */ return "forward-seek"; default: return "error"; } } static void remote_media_player_name(void *data, const uint8_t *value, uint16_t length) { struct remote_player *remote = data; char *name; name = name2utf8(value, length); if (!name) return; DBG("Media Player Name %s", (const char *)name); media_player_set_name(remote->mp, name); free(name); } static void remote_track_changed(void *data) { struct remote_player *remote = data; DBG("Track Changed"); media_player_metadata_changed(remote->mp); } static void remote_track_title(void *data, const uint8_t *value, uint16_t length) { struct remote_player *remote = data; char *name; uint16_t len; name = name2utf8(value, length); if (!name) return; len = strlen(name); DBG("Track Title %s", (const char *)name); media_player_set_metadata(remote->mp, NULL, "Title", name, len); media_player_metadata_changed(remote->mp); free(name); } static void remote_track_duration(void *data, int32_t duration_centisec) { struct remote_player *remote = data; if (duration_centisec == BT_MCS_POSITION_UNAVAILABLE) { media_player_set_duration(remote->mp, 0); return; } if (duration_centisec < 0) duration_centisec = 0; media_player_set_duration(remote->mp, duration_centisec * 10); } static void remote_track_position(void *data, int32_t position_centisec) { struct remote_player *remote = data; if (position_centisec == BT_MCS_POSITION_UNAVAILABLE) { media_player_set_position(remote->mp, 0); return; } if (position_centisec < 0) position_centisec = 0; media_player_set_position(remote->mp, position_centisec * 10); } static const struct { uint16_t basic; uint16_t repeat; bool shuffle; bool single; } playing_orders[] = { { BT_MCS_ORDER_SINGLE_ONCE, BT_MCS_ORDER_SINGLE_REPEAT, .single = true }, { BT_MCS_ORDER_IN_ORDER_ONCE, BT_MCS_ORDER_IN_ORDER_REPEAT }, { BT_MCS_ORDER_OLDEST_ONCE, BT_MCS_ORDER_OLDEST_REPEAT }, { BT_MCS_ORDER_NEWEST_ONCE, BT_MCS_ORDER_NEWEST_REPEAT }, { BT_MCS_ORDER_SHUFFLE_ONCE, BT_MCS_ORDER_SHUFFLE_REPEAT, .shuffle = true }, }; static bool get_playing_order(uint8_t order, const char **repeat, const char **shuffle) { unsigned int i; *repeat = "off"; *shuffle = "off"; for (i = 0; i < ARRAY_SIZE(playing_orders); ++i) { *shuffle = playing_orders[i].shuffle ? "alltracks" : "off"; if (order == playing_orders[i].basic) { break; } else if (order == playing_orders[i].repeat) { *repeat = playing_orders[i].single ? "singletrack" : "alltracks"; break; } } if (i == ARRAY_SIZE(playing_orders)) return false; return true; } static void remote_playing_order(void *data, uint8_t order) { struct remote_player *remote = data; const char *repeat, *shuffle; remote->playing_order = order; if (!get_playing_order(order, &repeat, &shuffle)) return; media_player_set_setting(remote->mp, "Repeat", repeat); media_player_set_setting(remote->mp, "Shuffle", shuffle); } static void remote_media_state(void *data, uint8_t status) { struct remote_player *remote = data; media_player_set_status(remote->mp, mcp_status_val_to_string(status)); } static void remote_destroy(void *data) { struct remote_player *remote = data; media_player_destroy(remote->mp); free(data); } static const struct bt_mcp_listener_callback remote_cb = { .media_player_name = remote_media_player_name, .track_changed = remote_track_changed, .track_title = remote_track_title, .track_duration = remote_track_duration, .track_position = remote_track_position, .playing_order = remote_playing_order, .media_state = remote_media_state, .destroy = remote_destroy, }; static int remote_mp_play(struct media_player *mp, void *user_data) { struct remote_player *remote = user_data; return bt_mcp_play(remote->mcp, remote->ccid); } static int remote_mp_pause(struct media_player *mp, void *user_data) { struct remote_player *remote = user_data; return bt_mcp_pause(remote->mcp, remote->ccid); } static int remote_mp_stop(struct media_player *mp, void *user_data) { struct remote_player *remote = user_data; return bt_mcp_stop(remote->mcp, remote->ccid); } static int remote_mp_next(struct media_player *mp, void *user_data) { struct remote_player *remote = user_data; return bt_mcp_next_track(remote->mcp, remote->ccid); } static int remote_mp_previous(struct media_player *mp, void *user_data) { struct remote_player *remote = user_data; return bt_mcp_previous_track(remote->mcp, remote->ccid); } static bool remote_mp_set_setting(struct media_player *mp, const char *key, const char *value, void *user_data) { struct remote_player *remote = user_data; unsigned int i; if (strcmp(key, "Repeat") == 0) { bool repeat = (strcasecmp(value, "alltracks") == 0); uint8_t order = remote->playing_order; /* Some sensible mapping, 1-to-1 not possible */ for (i = 0; i < ARRAY_SIZE(playing_orders); ++i) { if (order == playing_orders[i].basic) { if (repeat) order = playing_orders[i].repeat; break; } else if (order == playing_orders[i].repeat) { if (!repeat) order = playing_orders[i].basic; break; } } if (strcasecmp(value, "singletrack") == 0) order = BT_MCS_ORDER_SINGLE_REPEAT; DBG("Set Repeat %s -> 0x%02x", value, order); if (order == remote->playing_order) return true; return bt_mcp_set_playing_order(remote->mcp, remote->ccid, order); } if (strcmp(key, "Shuffle") == 0) { bool shuffle = (strcasecmp(value, "off") != 0); uint8_t order = remote->playing_order; /* Some sensible mapping, 1-to-1 not possible */ switch (order) { case BT_MCS_ORDER_SHUFFLE_ONCE: if (!shuffle) order = BT_MCS_ORDER_IN_ORDER_ONCE; break; case BT_MCS_ORDER_SHUFFLE_REPEAT: if (!shuffle) order = BT_MCS_ORDER_IN_ORDER_REPEAT; break; case BT_MCS_ORDER_SINGLE_ONCE: case BT_MCS_ORDER_IN_ORDER_ONCE: case BT_MCS_ORDER_OLDEST_ONCE: case BT_MCS_ORDER_NEWEST_ONCE: if (shuffle) order = BT_MCS_ORDER_SHUFFLE_ONCE; break; case BT_MCS_ORDER_SINGLE_REPEAT: case BT_MCS_ORDER_IN_ORDER_REPEAT: case BT_MCS_ORDER_OLDEST_REPEAT: case BT_MCS_ORDER_NEWEST_REPEAT: if (shuffle) order = BT_MCS_ORDER_SHUFFLE_REPEAT; break; } DBG("Set Shuffle %s -> 0x%02x", value, order); if (order == remote->playing_order) return true; return bt_mcp_set_playing_order(remote->mcp, remote->ccid, order); } return false; } static const struct media_player_callback remote_mp_cb = { .play = remote_mp_play, .pause = remote_mp_pause, .stop = remote_mp_stop, .next = remote_mp_next, .previous = remote_mp_previous, .set_setting = remote_mp_set_setting, }; static void mcp_ccid(void *data, uint8_t ccid, bool gmcs) { struct btd_service *service = data; struct btd_device *device = btd_service_get_device(service); struct bt_mcp *mcp = btd_service_get_user_data(service); struct remote_player *remote; struct media_player *mp; mp = media_player_controller_create(device_get_path(device), gmcs ? "mcp_gmcs" : "mcp_mcs", ccid); if (!mp) { DBG("Unable to create Media Player"); return; } remote = new0(struct remote_player, 1); remote->mcp = mcp; remote->ccid = ccid; remote->mp = mp; media_player_set_callbacks(remote->mp, &remote_mp_cb, remote); if (!bt_mcp_add_listener(mcp, ccid, &remote_cb, remote)) { DBG("Unable to register Media Player with MCP"); media_player_destroy(mp); free(remote); return; } } static void mcp_debug(void *data, const char *str) { DBG_IDX(0xffff, "%s", str); } static void mcp_ready(void *data) { struct btd_service *service = data; btd_service_connecting_complete(service, 0); } static const struct bt_mcp_callback mcp_cb = { .ccid = mcp_ccid, .debug = mcp_debug, .ready = mcp_ready, }; /* * Local player. * * TODO: maybe expose multiple MCS instances, as many as there are players. We'd * have to keep unused instances around in inactive state, so that we don't * consume ATT handles when players disappear/reappear. * * If an instance has no local player, for GMCS we forward key presses to * uinput. Other MCS instances should do nothing when inactive. */ struct mcs_instance; struct player_link { struct local_player *lp; unsigned int id; struct mcs_instance *instance; }; struct mcs_instance { struct btd_adapter *adapter; struct bt_mcs *mcs; struct queue *player_links; bool at_start; /* GMCS-specific */ struct bt_uinput *uinput; unsigned int player_watch_id; }; static const struct bt_uinput_key_map key_map[] = { { "Play", BT_MCS_CMD_PLAY, KEY_PLAYCD }, { "Stop", BT_MCS_CMD_STOP, KEY_STOPCD }, { "Pause", BT_MCS_CMD_PAUSE, KEY_PAUSECD }, { "Next Track", BT_MCS_CMD_NEXT_TRACK, KEY_NEXTSONG }, { "Prev Track", BT_MCS_CMD_PREV_TRACK, KEY_PREVIOUSSONG }, { NULL } }; static struct queue *servers; static struct player_link *mcs_get_active(struct mcs_instance *mcs) { return queue_peek_head(mcs->player_links); } static bool player_link_is_active(struct player_link *p) { return mcs_get_active(p->instance) == p; } static void mcs_update_media_state(struct mcs_instance *mcs) { struct player_link *p = mcs_get_active(mcs); const char *status = NULL; uint8_t state; mcs->at_start = false; if (p) status = local_player_get_status(p->lp); if (!status) { state = BT_MCS_STATE_INACTIVE; } else if (!strcasecmp(status, "playing")) { state = BT_MCS_STATE_PLAYING; } else if (!strcasecmp(status, "stopped")) { mcs->at_start = true; state = BT_MCS_STATE_PAUSED; } else if (!strcasecmp(status, "paused")) { state = BT_MCS_STATE_PAUSED; } else if (!strcasecmp(status, "forward-seek") || !strcasecmp(status, "backward-seek")) { state = BT_MCS_STATE_SEEKING; } else { state = BT_MCS_STATE_INACTIVE; } bt_mcs_set_media_state(mcs->mcs, state); bt_mcs_changed(mcs->mcs, MCS_TRACK_POSITION_CHRC_UUID); } static void mcs_player_changed(struct mcs_instance *mcs) { struct player_link *p = mcs_get_active(mcs); const char *name = p ? local_player_get_player_name(p->lp) : NULL; DBG("active: %p %s", p, name ? name : ""); bt_mcs_changed(mcs->mcs, MCS_MEDIA_PLAYER_NAME_CHRC_UUID); mcs_update_media_state(mcs); bt_mcs_changed(mcs->mcs, MCS_TRACK_TITLE_CHRC_UUID); bt_mcs_changed(mcs->mcs, MCS_TRACK_DURATION_CHRC_UUID); bt_mcs_changed(mcs->mcs, MCS_TRACK_POSITION_CHRC_UUID); bt_mcs_changed(mcs->mcs, MCS_PLAYBACK_SPEED_CHRC_UUID); bt_mcs_changed(mcs->mcs, MCS_SEEKING_SPEED_CHRC_UUID); bt_mcs_changed(mcs->mcs, MCS_PLAYING_ORDER_CHRC_UUID); bt_mcs_changed(mcs->mcs, MCS_TRACK_CHANGED_CHRC_UUID); } static bool player_link_make_active(struct player_link *p) { struct mcs_instance *mcs = p->instance; if (player_link_is_active(p)) return false; queue_remove(mcs->player_links, p); queue_push_head(mcs->player_links, p); mcs_player_changed(mcs); return true; } static void lp_status_changed(void *user_data) { struct player_link *p = user_data; struct mcs_instance *mcs = p->instance; const char *status = local_player_get_status(p->lp); /* Make the last player to start playing active */ if (!strcasecmp(status, "playing")) { if (player_link_make_active(p)) return; } if (!player_link_is_active(p)) return; mcs_update_media_state(mcs); } static void lp_track_position(uint32_t old_ms, uint32_t new_ms, void *user_data) { struct player_link *p = user_data; struct mcs_instance *mcs = p->instance; if (!player_link_is_active(p)) return; bt_mcs_changed(mcs->mcs, MCS_TRACK_POSITION_CHRC_UUID); } static void lp_track_changed(void *user_data) { struct player_link *p = user_data; struct mcs_instance *mcs = p->instance; mcs->at_start = false; if (!player_link_is_active(p)) return; bt_mcs_changed(mcs->mcs, MCS_TRACK_TITLE_CHRC_UUID); bt_mcs_changed(mcs->mcs, MCS_TRACK_POSITION_CHRC_UUID); bt_mcs_changed(mcs->mcs, MCS_TRACK_DURATION_CHRC_UUID); bt_mcs_changed(mcs->mcs, MCS_TRACK_CHANGED_CHRC_UUID); } static void lp_settings_changed(const char *key, void *user_data) { struct player_link *p = user_data; struct mcs_instance *mcs = p->instance; if (!player_link_is_active(p)) return; if (strcasecmp(key, "Shuffle") && strcasecmp(key, "Repeat")) return; bt_mcs_changed(mcs->mcs, MCS_PLAYING_ORDER_CHRC_UUID); } static void lp_player_removed(void *user_data) { struct player_link *p = user_data; struct mcs_instance *mcs = p->instance; bool active = player_link_is_active(p); DBG("%p", p); queue_remove(mcs->player_links, p); free(p); if (active) mcs_player_changed(mcs); } const struct local_player_callback local_player_cb = { .status_changed = lp_status_changed, .track_position = lp_track_position, .track_changed = lp_track_changed, .settings_changed = lp_settings_changed, .player_removed = lp_player_removed, }; static bool mcs_command(struct mcs_instance *mcs, uint8_t cmd) { unsigned int i; /* Emulate media key press */ if (!mcs->uinput) return false; for (i = 0; i < ARRAY_SIZE(key_map); ++i) { if (key_map[i].code == cmd) { DBG("MCS press %s", key_map[i].name); bt_uinput_send_key(mcs->uinput, key_map[i].uinput, 1); bt_uinput_send_key(mcs->uinput, key_map[i].uinput, 0); break; } } /* We are inactive, so command does not cause state changes and * does not succeed, even though we do generate the key presses. * This should be OK vs. MCP v1.0.1 p. 26 */ return false; } static bool mcs_play(void *data) { struct mcs_instance *mcs = data; struct player_link *p = mcs_get_active(mcs); if (p && local_player_play(p->lp)) return true; return mcs_command(mcs, BT_MCS_CMD_PLAY); } static bool mcs_pause(void *data) { struct mcs_instance *mcs = data; struct player_link *p = mcs_get_active(mcs); if (p && local_player_pause(p->lp)) return true; return mcs_command(mcs, BT_MCS_CMD_PAUSE); } static bool mcs_stop(void *data) { struct mcs_instance *mcs = data; struct player_link *p = mcs_get_active(mcs); if (p && local_player_stop(p->lp)) { mcs->at_start = true; return true; } return mcs_command(mcs, BT_MCS_CMD_STOP); } static bool mcs_next_track(void *data) { struct mcs_instance *mcs = data; struct player_link *p = mcs_get_active(mcs); if (p && local_player_next(p->lp)) { mcs->at_start = true; return true; } return mcs_command(data, BT_MCS_CMD_NEXT_TRACK); } static bool mcs_previous_track(void *data) { struct mcs_instance *mcs = data; struct player_link *p = mcs_get_active(mcs); if (p && local_player_previous(p->lp)) { mcs->at_start = true; return true; } return mcs_command(data, BT_MCS_CMD_PREV_TRACK); } static void mcs_media_player_name(void *data, struct iovec *buf, size_t size) { struct mcs_instance *mcs = data; struct player_link *p = mcs_get_active(mcs); const char *name = NULL; if (p) name = local_player_get_player_name(p->lp); if (!name) name = btd_adapter_get_name(mcs->adapter); snprintf((void *)buf->iov_base, size, "%s", name); util_iov_push(buf, strlen(buf->iov_base)); } static void mcs_track_title(void *data, struct iovec *buf, size_t size) { struct mcs_instance *mcs = data; struct player_link *p = mcs_get_active(mcs); const char *name = NULL; if (p) name = local_player_get_metadata(p->lp, "Title"); if (!name) name = ""; snprintf((void *)buf->iov_base, size, "%s", name); util_iov_push(buf, strlen(buf->iov_base)); } static int32_t mcs_track_duration(void *data) { struct mcs_instance *mcs = data; struct player_link *p = mcs_get_active(mcs); const char *duration = NULL; long duration_ms; if (p) duration = local_player_get_metadata(p->lp, "Duration"); if (!duration) return BT_MCS_DURATION_UNAVAILABLE; duration_ms = atol(duration); return duration_ms / 10; } static int32_t mcs_track_position(void *data) { struct mcs_instance *mcs = data; struct player_link *p = mcs_get_active(mcs); if (!p) return BT_MCS_POSITION_UNAVAILABLE; if (mcs->at_start) return 0; return local_player_get_position(p->lp) / 10; } static uint8_t mcs_playing_order(void *data) { struct mcs_instance *mcs = data; struct player_link *p = mcs_get_active(mcs); bool shuffle, repeat, single; const char *shuffle_str, *repeat_str; if (!p) return BT_MCS_ORDER_IN_ORDER_ONCE; shuffle_str = local_player_get_setting(p->lp, "Shuffle"); repeat_str = local_player_get_setting(p->lp, "Repeat"); shuffle = shuffle_str && strcasecmp(shuffle_str, "off"); repeat = repeat_str && strcasecmp(repeat_str, "off"); single = repeat_str && !strcasecmp(repeat_str, "singletrack"); if (single) return BT_MCS_ORDER_SINGLE_REPEAT; if (shuffle) return repeat ? BT_MCS_ORDER_SHUFFLE_REPEAT : BT_MCS_ORDER_SHUFFLE_ONCE; return repeat ? BT_MCS_ORDER_IN_ORDER_REPEAT : BT_MCS_ORDER_IN_ORDER_ONCE; } static uint16_t mcs_playing_order_supported(void *data) { struct mcs_instance *mcs = data; struct player_link *p = mcs_get_active(mcs); if (!p) return BT_MCS_ORDER_SUPPORTED_IN_ORDER_ONCE; return BT_MCS_ORDER_SUPPORTED_SINGLE_REPEAT | BT_MCS_ORDER_SUPPORTED_IN_ORDER_ONCE | BT_MCS_ORDER_SUPPORTED_IN_ORDER_REPEAT | BT_MCS_ORDER_SUPPORTED_SHUFFLE_ONCE | BT_MCS_ORDER_SUPPORTED_SHUFFLE_REPEAT; } static bool mcs_set_track_position(void *data, int32_t value) { /* TODO: add support to setting position in org.bluez.MediaPlayer */ return false; } static bool mcs_set_playing_order(void *data, uint8_t value) { struct mcs_instance *mcs = data; struct player_link *p = mcs_get_active(mcs); const char *repeat, *shuffle; bool ok = true; if (!p) return false; if (!get_playing_order(value, &repeat, &shuffle)) return false; if (local_player_set_setting(p->lp, "Shuffle", shuffle) < 0) ok = false; if (local_player_set_setting(p->lp, "Repeat", repeat) < 0) ok = false; bt_mcs_changed(mcs->mcs, MCS_PLAYING_ORDER_CHRC_UUID); return ok; } static void player_link_destroy(void *data) { struct player_link *p = data; DBG("%p", p); local_player_unregister_callbacks(p->lp, p->id); free(p); } static void mcs_destroy(void *data) { struct mcs_instance *mcs = data; DBG("destroy %p", data); queue_remove(servers, mcs); bt_uinput_destroy(mcs->uinput); queue_destroy(mcs->player_links, player_link_destroy); if (mcs->player_watch_id) local_player_unregister_watch(mcs->player_watch_id); free(mcs); } static void mcs_debug(void *data, const char *str) { DBG_IDX(0xffff, "%s", str); } static const struct bt_mcs_callback gmcs_cb = { .media_player_name = mcs_media_player_name, .track_title = mcs_track_title, .track_duration = mcs_track_duration, .track_position = mcs_track_position, .playing_order = mcs_playing_order, .playing_order_supported = mcs_playing_order_supported, .set_track_position = mcs_set_track_position, .set_playing_order = mcs_set_playing_order, .play = mcs_play, .pause = mcs_pause, .stop = mcs_stop, .next_track = mcs_next_track, .previous_track = mcs_previous_track, .debug = mcs_debug, .destroy = mcs_destroy, }; static void uinput_debug(const char *str, void *data) { DBG_IDX(0xffff, "%s", str); } static void gmcs_player_added(struct local_player *lp, void *user_data) { struct mcs_instance *gmcs = user_data; struct btd_adapter *adapter = local_player_get_adapter(lp); struct player_link *p; if (adapter != gmcs->adapter) return; p = new0(struct player_link, 1); p->lp = lp; p->id = local_player_register_callbacks(lp, &local_player_cb, p); p->instance = gmcs; if (!p->id) { free(p); return; } DBG("%p", p); queue_push_tail(gmcs->player_links, p); if (queue_length(gmcs->player_links) == 1) mcs_player_changed(gmcs); } static struct mcs_instance *gmcs_new(struct btd_adapter *adapter) { struct mcs_instance *gmcs; const char *name = btd_adapter_get_name(adapter); int err; gmcs = new0(struct mcs_instance, 1); gmcs->adapter = adapter; gmcs->uinput = bt_uinput_new(name, " (MCS)", btd_adapter_get_address(adapter), NULL); bt_uinput_set_debug(gmcs->uinput, uinput_debug, gmcs); err = bt_uinput_create(gmcs->uinput, key_map); if (err < 0) { error("MCS: failed to init uinput for %s: %s", name, strerror(-err)); bt_uinput_destroy(gmcs->uinput); gmcs->uinput = NULL; } gmcs->player_links = queue_new(); gmcs->player_watch_id = local_player_register_watch(gmcs_player_added, gmcs); DBG("new %p", gmcs); return gmcs; } /* * Profile */ static struct btd_profile mcp_gmcs_profile; static int add_service(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct bt_gatt_client *client = btd_device_get_gatt_client(device); struct bt_mcp *mcp = btd_service_get_user_data(service); bool gmcs; if (mcp) return -EALREADY; gmcs = btd_service_get_profile(service) == &mcp_gmcs_profile; mcp = bt_mcp_attach(client, gmcs, &mcp_cb, service); if (!mcp) { DBG("Unable to attach MCP"); return -EINVAL; } btd_service_set_user_data(service, mcp); return 0; } static void remove_service(struct btd_service *service) { struct bt_mcp *mcp = btd_service_get_user_data(service); btd_service_set_user_data(service, NULL); bt_mcp_detach(mcp); } static int mcp_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); return add_service(service); } static int mcp_connect(struct btd_service *service) { return 0; } static int mcp_disconnect(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); remove_service(service); btd_service_disconnecting_complete(service, 0); return 0; } static int mcp_probe(struct btd_service *service) { return 0; } static void mcp_remove(struct btd_service *service) { remove_service(service); } static int gmcs_probe(struct btd_profile *p, struct btd_adapter *adapter) { struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct gatt_db *db = btd_gatt_database_get_db(database); struct mcs_instance *gmcs; DBG("Add GMCS server %s", adapter_get_path(adapter)); gmcs = gmcs_new(adapter); if (!gmcs) return -EINVAL; gmcs->mcs = bt_mcs_register(db, true, &gmcs_cb, gmcs); if (!gmcs->mcs) { mcs_destroy(gmcs); return -EINVAL; } if (!servers) servers = queue_new(); queue_push_tail(servers, gmcs); return 0; } static void gmcs_remove(struct btd_profile *p, struct btd_adapter *adapter) { struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct gatt_db *db = btd_gatt_database_get_db(database); DBG("Remove GMCS server %s", adapter_get_path(adapter)); bt_mcs_unregister_all(db); } static struct btd_profile mcp_gmcs_profile = { .name = "mcp-gmcs", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = GMCS_UUID_STR, .device_probe = mcp_probe, .device_remove = mcp_remove, .accept = mcp_accept, .connect = mcp_connect, .disconnect = mcp_disconnect, .adapter_probe = gmcs_probe, .adapter_remove = gmcs_remove, .experimental = true, }; static struct btd_profile mcp_mcs_profile = { .name = "mcp-mcs", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = MCS_UUID_STR, .device_probe = mcp_probe, .device_remove = mcp_remove, .accept = mcp_accept, .connect = mcp_connect, .disconnect = mcp_disconnect, .adapter_probe = NULL, .adapter_remove = NULL, .experimental = true, }; static int mcp_init(void) { int err; err = btd_profile_register(&mcp_gmcs_profile); if (err) return err; err = btd_profile_register(&mcp_mcs_profile); if (err) { btd_profile_unregister(&mcp_gmcs_profile); return err; } return err; } static void mcp_exit(void) { btd_profile_unregister(&mcp_gmcs_profile); btd_profile_unregister(&mcp_mcs_profile); } BLUETOOTH_PLUGIN_DEFINE(mcp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, mcp_init, mcp_exit) bluez-5.87/profiles/audio/PaxHeaders/avrcp-player.c0000644000000000000000000000005015142177620017355 xustar0020 atime=1783093660 20 ctime=1783094105 bluez-5.87/profiles/audio/avrcp-player.c0000644000000000000000000001325615142177620017045 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright 2025 Pauli Virtanen * */ #include #include #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/adapter.h" #include "src/device.h" #include "src/log.h" #include "avrcp.h" #include "media.h" struct player_link { struct local_player *lp; struct avrcp_player *avrcp; unsigned int id; }; static unsigned int watch_id; static struct queue *players; static GList *lp_list_settings(void *user_data) { struct player_link *p = user_data; return local_player_list_settings(p->lp); } static const char *lp_get_setting(const char *key, void *user_data) { struct player_link *p = user_data; return local_player_get_setting(p->lp, key); } static int lp_set_setting(const char *key, const char *value, void *user_data) { struct player_link *p = user_data; return local_player_set_setting(p->lp, key, value); } static uint64_t lp_get_uid(void *user_data) { struct player_link *p = user_data; if (!local_player_have_track(p->lp)) return UINT64_MAX; return 0; } static const char *lp_get_metadata(const char *key, void *user_data) { struct player_link *p = user_data; return local_player_get_metadata(p->lp, key); } static GList *lp_list_metadata(void *user_data) { struct player_link *p = user_data; return local_player_list_metadata(p->lp); } static const char *lp_get_status(void *user_data) { struct player_link *p = user_data; return local_player_get_status(p->lp); } static uint32_t lp_get_position(void *user_data) { struct player_link *p = user_data; return local_player_get_position(p->lp); } static uint32_t lp_get_duration(void *user_data) { struct player_link *p = user_data; return local_player_get_duration(p->lp); } static const char *lp_get_name(void *user_data) { struct player_link *p = user_data; return local_player_get_player_name(p->lp); } static bool lp_play(void *user_data) { struct player_link *p = user_data; return local_player_play(p->lp); } static bool lp_stop(void *user_data) { struct player_link *p = user_data; return local_player_stop(p->lp); } static bool lp_pause(void *user_data) { struct player_link *p = user_data; return local_player_pause(p->lp); } static bool lp_next(void *user_data) { struct player_link *p = user_data; return local_player_next(p->lp); } static bool lp_previous(void *user_data) { struct player_link *p = user_data; return local_player_previous(p->lp); } static struct avrcp_player_cb avrcp_cb = { .list_settings = lp_list_settings, .get_setting = lp_get_setting, .set_setting = lp_set_setting, .list_metadata = lp_list_metadata, .get_uid = lp_get_uid, .get_metadata = lp_get_metadata, .get_position = lp_get_position, .get_duration = lp_get_duration, .get_status = lp_get_status, .get_name = lp_get_name, .play = lp_play, .stop = lp_stop, .pause = lp_pause, .next = lp_next, .previous = lp_previous, }; static void status_changed(void *user_data) { struct player_link *p = user_data; avrcp_player_event(p->avrcp, AVRCP_EVENT_STATUS_CHANGED, local_player_get_status(p->lp)); } static void track_position(uint32_t old, uint32_t position, void *user_data) { struct player_link *p = user_data; uint32_t duration = local_player_get_duration(p->lp); const char *status; if (position > old) status = "forward-seek"; else status = "reverse-seek"; if (!position) { avrcp_player_event(p->avrcp, AVRCP_EVENT_TRACK_REACHED_START, NULL); return; } /* * If position is the maximum value allowed or greater than track's * duration, we send a track-reached-end event. */ if (position == UINT32_MAX || position >= duration) { avrcp_player_event(p->avrcp, AVRCP_EVENT_TRACK_REACHED_END, NULL); return; } /* Send a status change to force resync the position */ avrcp_player_event(p->avrcp, AVRCP_EVENT_STATUS_CHANGED, status); } static void track_changed(void *user_data) { struct player_link *p = user_data; uint64_t uid = lp_get_uid(p->lp); avrcp_player_event(p->avrcp, AVRCP_EVENT_TRACK_CHANGED, &uid); avrcp_player_event(p->avrcp, AVRCP_EVENT_TRACK_REACHED_START, NULL); } static void settings_changed(const char *key, void *user_data) { struct player_link *p = user_data; avrcp_player_event(p->avrcp, AVRCP_EVENT_SETTINGS_CHANGED, key); } static void player_removed(void *user_data) { struct player_link *p = user_data; DBG("%p", p); avrcp_unregister_player(p->avrcp); } static const struct local_player_callback player_cb = { .status_changed = status_changed, .track_position = track_position, .track_changed = track_changed, .settings_changed = settings_changed, .player_removed = player_removed, }; static void player_destroy(gpointer data) { struct player_link *p = data; DBG("%p", p); queue_remove(players, p); local_player_unregister_callbacks(p->lp, p->id); free(p); } static void player_added(struct local_player *lp, void *user_data) { struct btd_adapter *adapter = local_player_get_adapter(lp); struct player_link *p; p = new0(struct player_link, 1); p->lp = lp; p->id = local_player_register_callbacks(lp, &player_cb, p); if (!p->id) { free(p); return; } p->avrcp = avrcp_register_player(adapter, &avrcp_cb, p, player_destroy); if (!p->avrcp) { local_player_unregister_callbacks(lp, p->id); free(p); return; } DBG("%p", p); queue_push_tail(players, p); } void avrcp_player_init(void) { DBG(""); if (watch_id) return; watch_id = local_player_register_watch(player_added, NULL); players = queue_new(); } void avrcp_player_exit(void) { DBG(""); queue_destroy(players, player_removed); players = NULL; if (!watch_id) return; local_player_unregister_watch(watch_id); watch_id = 0; } bluez-5.87/profiles/audio/PaxHeaders/avdtp.h0000644000000000000000000000005015017252326016071 xustar0020 atime=1783093610 20 ctime=1783094105 bluez-5.87/profiles/audio/avdtp.h0000644000000000000000000002445415017252326015563 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ typedef enum { AVDTP_SESSION_STATE_DISCONNECTED, AVDTP_SESSION_STATE_CONNECTING, AVDTP_SESSION_STATE_CONNECTED } avdtp_session_state_t; struct avdtp; struct avdtp_server; struct avdtp_stream; struct avdtp_local_sep; struct avdtp_remote_sep; struct avdtp_error { uint8_t category; union { uint8_t error_code; int posix_errno; } err; }; /* SEP capability categories */ #define AVDTP_MEDIA_TRANSPORT 0x01 #define AVDTP_REPORTING 0x02 #define AVDTP_RECOVERY 0x03 #define AVDTP_CONTENT_PROTECTION 0x04 #define AVDTP_HEADER_COMPRESSION 0x05 #define AVDTP_MULTIPLEXING 0x06 #define AVDTP_MEDIA_CODEC 0x07 #define AVDTP_DELAY_REPORTING 0x08 #define AVDTP_ERRNO 0xff /* AVDTP error definitions */ #define AVDTP_BAD_HEADER_FORMAT 0x01 #define AVDTP_BAD_LENGTH 0x11 #define AVDTP_BAD_ACP_SEID 0x12 #define AVDTP_SEP_IN_USE 0x13 #define AVDTP_SEP_NOT_IN_USE 0x14 #define AVDTP_BAD_SERV_CATEGORY 0x17 #define AVDTP_BAD_PAYLOAD_FORMAT 0x18 #define AVDTP_NOT_SUPPORTED_COMMAND 0x19 #define AVDTP_INVALID_CAPABILITIES 0x1A #define AVDTP_BAD_RECOVERY_TYPE 0x22 #define AVDTP_BAD_MEDIA_TRANSPORT_FORMAT 0x23 #define AVDTP_BAD_RECOVERY_FORMAT 0x25 #define AVDTP_BAD_ROHC_FORMAT 0x26 #define AVDTP_BAD_CP_FORMAT 0x27 #define AVDTP_BAD_MULTIPLEXING_FORMAT 0x28 #define AVDTP_UNSUPPORTED_CONFIGURATION 0x29 #define AVDTP_BAD_STATE 0x31 /* SEP types definitions */ #define AVDTP_SEP_TYPE_SOURCE 0x00 #define AVDTP_SEP_TYPE_SINK 0x01 /* Media types definitions */ #define AVDTP_MEDIA_TYPE_AUDIO 0x00 #define AVDTP_MEDIA_TYPE_VIDEO 0x01 #define AVDTP_MEDIA_TYPE_MULTIMEDIA 0x02 typedef enum { AVDTP_STATE_IDLE, AVDTP_STATE_CONFIGURED, AVDTP_STATE_OPEN, AVDTP_STATE_STREAMING, AVDTP_STATE_CLOSING, AVDTP_STATE_ABORTING, } avdtp_state_t; struct avdtp_service_capability { uint8_t category; uint8_t length; uint8_t data[0]; } __attribute__ ((packed)); #if __BYTE_ORDER == __LITTLE_ENDIAN struct avdtp_media_codec_capability { uint8_t rfa0:4; uint8_t media_type:4; uint8_t media_codec_type; uint8_t data[0]; } __attribute__ ((packed)); #elif __BYTE_ORDER == __BIG_ENDIAN struct avdtp_media_codec_capability { uint8_t media_type:4; uint8_t rfa0:4; uint8_t media_codec_type; uint8_t data[0]; } __attribute__ ((packed)); #else #error "Unknown byte order" #endif typedef void (*avdtp_session_state_cb) (struct btd_device *dev, struct avdtp *session, avdtp_session_state_t old_state, avdtp_session_state_t new_state, void *user_data); typedef void (*avdtp_stream_state_cb) (struct avdtp_stream *stream, avdtp_state_t old_state, avdtp_state_t new_state, struct avdtp_error *err, void *user_data); typedef void (*avdtp_set_configuration_cb) (struct avdtp *session, struct avdtp_stream *stream, struct avdtp_error *err); /* Callbacks for when a reply is received to a command that we sent */ struct avdtp_sep_cfm { void (*set_configuration) (struct avdtp *session, struct avdtp_local_sep *lsep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data); void (*get_configuration) (struct avdtp *session, struct avdtp_local_sep *lsep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data); void (*open) (struct avdtp *session, struct avdtp_local_sep *lsep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data); void (*start) (struct avdtp *session, struct avdtp_local_sep *lsep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data); void (*suspend) (struct avdtp *session, struct avdtp_local_sep *lsep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data); void (*close) (struct avdtp *session, struct avdtp_local_sep *lsep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data); void (*abort) (struct avdtp *session, struct avdtp_local_sep *lsep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data); void (*reconfigure) (struct avdtp *session, struct avdtp_local_sep *lsep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data); void (*delay_report) (struct avdtp *session, struct avdtp_local_sep *lsep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data); }; /* Callbacks for indicating when we received a new command. The return value * indicates whether the command should be rejected or accepted */ struct avdtp_sep_ind { gboolean (*match_codec) (struct avdtp *session, struct avdtp_media_codec_capability *codec, void *user_data); gboolean (*get_capability) (struct avdtp *session, struct avdtp_local_sep *sep, gboolean get_all, GSList **caps, uint8_t *err, void *user_data); gboolean (*set_configuration) (struct avdtp *session, struct avdtp_local_sep *lsep, struct avdtp_stream *stream, GSList *caps, avdtp_set_configuration_cb cb, void *user_data); gboolean (*get_configuration) (struct avdtp *session, struct avdtp_local_sep *lsep, uint8_t *err, void *user_data); gboolean (*open) (struct avdtp *session, struct avdtp_local_sep *lsep, struct avdtp_stream *stream, uint8_t *err, void *user_data); gboolean (*start) (struct avdtp *session, struct avdtp_local_sep *lsep, struct avdtp_stream *stream, uint8_t *err, void *user_data); gboolean (*suspend) (struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, uint8_t *err, void *user_data); gboolean (*close) (struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, uint8_t *err, void *user_data); void (*abort) (struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, uint8_t *err, void *user_data); gboolean (*reconfigure) (struct avdtp *session, struct avdtp_local_sep *lsep, uint8_t *err, void *user_data); gboolean (*delayreport) (struct avdtp *session, struct avdtp_local_sep *lsep, uint8_t rseid, uint16_t delay, uint8_t *err, void *user_data); }; typedef void (*avdtp_discover_cb_t) (struct avdtp *session, GSList *seps, struct avdtp_error *err, void *user_data); void avdtp_unref(struct avdtp *session); struct avdtp *avdtp_ref(struct avdtp *session); struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, void *data, int size); struct avdtp_remote_sep *avdtp_register_remote_sep(struct avdtp *session, uint8_t seid, uint8_t type, GSList *caps, bool delay_reporting); int avdtp_unregister_remote_sep(struct avdtp *session, struct avdtp_remote_sep *rsep); typedef void (*avdtp_remote_sep_destroy_t)(void *user_data); void avdtp_remote_sep_set_destroy(struct avdtp_remote_sep *sep, void *user_data, avdtp_remote_sep_destroy_t destroy); uint8_t avdtp_get_seid(struct avdtp_remote_sep *sep); uint8_t avdtp_get_type(struct avdtp_remote_sep *sep); struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep); bool avdtp_get_delay_reporting(struct avdtp_remote_sep *sep); int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb, void *user_data); gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream); unsigned int avdtp_stream_add_cb(struct avdtp *session, struct avdtp_stream *stream, avdtp_stream_state_cb cb, void *data); gboolean avdtp_stream_remove_cb(struct avdtp *session, struct avdtp_stream *stream, unsigned int id); gboolean avdtp_stream_set_transport(struct avdtp_stream *stream, int fd, size_t imtu, size_t omtu); gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock, uint16_t *imtu, uint16_t *omtu, GSList **caps); struct avdtp_service_capability *avdtp_stream_get_codec( struct avdtp_stream *stream); gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream, GSList *caps); gboolean avdtp_stream_has_delay_reporting(struct avdtp_stream *stream); struct avdtp_remote_sep *avdtp_stream_get_remote_sep( struct avdtp_stream *stream); unsigned int avdtp_add_state_cb(struct btd_device *dev, avdtp_session_state_cb cb, void *user_data); gboolean avdtp_remove_state_cb(unsigned int id); int avdtp_set_configuration(struct avdtp *session, struct avdtp_remote_sep *rsep, struct avdtp_local_sep *lsep, GSList *caps, struct avdtp_stream **stream); int avdtp_get_configuration(struct avdtp *session, struct avdtp_stream *stream); int avdtp_open(struct avdtp *session, struct avdtp_stream *stream); int avdtp_start(struct avdtp *session, struct avdtp_stream *stream); int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream); int avdtp_close(struct avdtp *session, struct avdtp_stream *stream, gboolean immediate); int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream); int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream, uint16_t delay); struct avdtp_local_sep *avdtp_register_sep(struct queue *lseps, uint64_t *seid_pool, uint8_t type, uint8_t media_type, uint8_t codec_type, gboolean delay_reporting, struct avdtp_sep_ind *ind, struct avdtp_sep_cfm *cfm, void *user_data); /* Find a matching pair of local and remote SEP ID's */ struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session, struct avdtp_local_sep *lsep); int avdtp_unregister_sep(struct queue *lseps, uint64_t *seid_pool, struct avdtp_local_sep *sep); avdtp_state_t avdtp_stream_get_state(struct avdtp_stream *stream); uint8_t avdtp_sep_get_seid(struct avdtp_local_sep *sep); void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id); const char *avdtp_strerror(struct avdtp_error *err); uint8_t avdtp_error_category(struct avdtp_error *err); int avdtp_error_error_code(struct avdtp_error *err); int avdtp_error_posix_errno(struct avdtp_error *err); struct btd_adapter *avdtp_get_adapter(struct avdtp *session); struct btd_device *avdtp_get_device(struct avdtp *session); struct avdtp_server *avdtp_get_server(struct avdtp_local_sep *lsep); struct avdtp *avdtp_new(GIOChannel *chan, struct btd_device *device, struct queue *lseps); uint16_t avdtp_get_version(struct avdtp *session); bluez-5.87/profiles/audio/PaxHeaders/player.c0000644000000000000000000000005015200607060016233 xustar0020 atime=1783092683 20 ctime=1783094105 bluez-5.87/profiles/audio/player.c0000644000000000000000000013711715200607060015726 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2004-2009 Marcel Holtmann * Copyright (C) 2012-2012 Intel Corporation * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "src/log.h" #include "src/dbus-common.h" #include "src/error.h" #include "src/shared/util.h" #include "player.h" #define MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer1" #define MEDIA_FOLDER_INTERFACE "org.bluez.MediaFolder1" #define MEDIA_ITEM_INTERFACE "org.bluez.MediaItem1" struct player_callback { const struct media_player_callback *cbs; void *user_data; }; struct pending_req { GDBusPendingPropertySet id; const char *key; const char *value; }; struct media_item { struct media_player *player; char *path; /* Item object path */ char *name; /* Item name */ player_item_type_t type; /* Item type */ player_folder_type_t folder_type; /* Folder type */ bool playable; /* Item playable flag */ uint64_t uid; /* Item uid */ GHashTable *metadata; /* Item metadata */ }; struct media_folder { struct media_folder *parent; struct media_item *item; /* Folder item */ uint32_t number_of_items;/* Number of items */ GSList *subfolders; GSList *items; DBusMessage *msg; }; struct media_player { char *device; /* Device path */ char *name; /* Player name */ char *type; /* Player type */ char *subtype; /* Player subtype */ bool browsable; /* Player browsing feature */ bool searchable; /* Player searching feature */ struct media_folder *scope; /* Player current scope */ struct media_folder *folder; /* Player current folder */ struct media_folder *search; /* Player search folder */ struct media_folder *playlist; /* Player current playlist */ char *path; /* Player object path */ GHashTable *settings; /* Player settings */ GHashTable *track; /* Player current track */ char *status; uint32_t position; GTimer *progress; struct player_callback *cb; GSList *pending; GSList *folders; uint16_t obex_port; }; static void append_track(void *key, void *value, void *user_data) { DBusMessageIter *dict = user_data; const char *strkey = key; if (strcasecmp(strkey, "Duration") == 0 || strcasecmp(strkey, "TrackNumber") == 0 || strcasecmp(strkey, "NumberOfTracks") == 0) { uint32_t num = atoi(value); dict_append_entry(dict, key, DBUS_TYPE_UINT32, &num); } else if (strcasecmp(strkey, "Item") == 0) { dict_append_entry(dict, key, DBUS_TYPE_OBJECT_PATH, &value); } else { dict_append_entry(dict, key, DBUS_TYPE_STRING, &value); } } static struct pending_req *find_pending(struct media_player *mp, const char *key) { GSList *l; for (l = mp->pending; l; l = l->next) { struct pending_req *p = l->data; if (strcasecmp(key, p->key) == 0) return p; } return NULL; } static struct pending_req *pending_new(GDBusPendingPropertySet id, const char *key, const char *value) { struct pending_req *p; p = g_new0(struct pending_req, 1); p->id = id; p->key = key; p->value = value; return p; } static uint32_t media_player_get_position(struct media_player *mp) { double timedelta; uint32_t sec, msec; if (g_strcmp0(mp->status, "playing") != 0 || mp->position == UINT32_MAX) return mp->position; timedelta = g_timer_elapsed(mp->progress, NULL); sec = (uint32_t) timedelta; msec = (uint32_t) ((timedelta - sec) * 1000); return mp->position + sec * 1000 + msec; } static gboolean get_position(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_player *mp = data; uint32_t position; position = media_player_get_position(mp); dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &position); return TRUE; } static gboolean status_exists(const GDBusPropertyTable *property, void *data) { struct media_player *mp = data; return mp->status != NULL; } static gboolean get_status(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_player *mp = data; if (mp->status == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->status); return TRUE; } static gboolean setting_exists(const GDBusPropertyTable *property, void *data) { struct media_player *mp = data; const char *value; value = g_hash_table_lookup(mp->settings, property->name); return value ? TRUE : FALSE; } static gboolean get_setting(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_player *mp = data; const char *value; value = g_hash_table_lookup(mp->settings, property->name); if (value == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &value); return TRUE; } static void player_set_setting(struct media_player *mp, GDBusPendingPropertySet id, const char *key, const char *value) { struct player_callback *cb = mp->cb; struct pending_req *p; if (cb == NULL || cb->cbs->set_setting == NULL) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".NotSupported", "Operation is not supported"); return; } p = find_pending(mp, key); if (p != NULL) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InProgress", "Operation already in progress"); return; } if (!cb->cbs->set_setting(mp, key, value, cb->user_data)) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } p = pending_new(id, key, value); mp->pending = g_slist_append(mp->pending, p); } static void set_setting(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *data) { struct media_player *mp = data; const char *value, *current; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } dbus_message_iter_get_basic(iter, &value); current = g_hash_table_lookup(mp->settings, property->name); if (g_strcmp0(current, value) == 0) { g_dbus_pending_property_success(id); return; } player_set_setting(mp, id, property->name, value); } static gboolean track_exists(const GDBusPropertyTable *property, void *data) { struct media_player *mp = data; return g_hash_table_size(mp->track) != 0; } static gboolean get_track(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_player *mp = data; DBusMessageIter dict; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); g_hash_table_foreach(mp->track, append_track, &dict); dbus_message_iter_close_container(iter, &dict); return TRUE; } static gboolean get_device(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_player *mp = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &mp->device); return TRUE; } static gboolean name_exists(const GDBusPropertyTable *property, void *data) { struct media_player *mp = data; return mp->name != NULL; } static gboolean get_name(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_player *mp = data; if (mp->name == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->name); return TRUE; } static gboolean type_exists(const GDBusPropertyTable *property, void *data) { struct media_player *mp = data; return mp->type != NULL; } static gboolean get_type(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_player *mp = data; if (mp->type == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->type); return TRUE; } static gboolean subtype_exists(const GDBusPropertyTable *property, void *data) { struct media_player *mp = data; return mp->subtype != NULL; } static gboolean get_subtype(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_player *mp = data; if (mp->subtype == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->subtype); return TRUE; } static gboolean browsable_exists(const GDBusPropertyTable *property, void *data) { struct media_player *mp = data; return mp->folder != NULL; } static gboolean get_browsable(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_player *mp = data; dbus_bool_t value; if (mp->folder == NULL) return FALSE; value = mp->browsable; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); return TRUE; } static gboolean searchable_exists(const GDBusPropertyTable *property, void *data) { struct media_player *mp = data; return mp->folder != NULL; } static gboolean get_searchable(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_player *mp = data; dbus_bool_t value; if (mp->folder == NULL) return FALSE; value = mp->searchable; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); return TRUE; } static gboolean playlist_exists(const GDBusPropertyTable *property, void *data) { struct media_player *mp = data; return mp->playlist != NULL; } static gboolean get_playlist(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_player *mp = data; struct media_folder *playlist = mp->playlist; if (playlist == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &playlist->item->path); return TRUE; } static gboolean obexport_exists(const GDBusPropertyTable *property, void *data) { struct media_player *mp = data; return mp->obex_port != 0; } static gboolean get_obexport(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_player *mp = data; if (mp->obex_port == 0) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &mp->obex_port); return TRUE; } static DBusMessage *media_player_play(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_player *mp = data; struct player_callback *cb = mp->cb; int err; if (cb->cbs->play == NULL) return btd_error_not_supported(msg); err = cb->cbs->play(mp, cb->user_data); if (err < 0) return btd_error_failed(msg, strerror(-err)); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *media_player_pause(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_player *mp = data; struct player_callback *cb = mp->cb; int err; if (cb->cbs->pause == NULL) return btd_error_not_supported(msg); err = cb->cbs->pause(mp, cb->user_data); if (err < 0) return btd_error_failed(msg, strerror(-err)); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *media_player_stop(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_player *mp = data; struct player_callback *cb = mp->cb; int err; if (cb->cbs->stop == NULL) return btd_error_not_supported(msg); err = cb->cbs->stop(mp, cb->user_data); if (err < 0) return btd_error_failed(msg, strerror(-err)); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *media_player_next(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_player *mp = data; struct player_callback *cb = mp->cb; int err; if (cb->cbs->next == NULL) return btd_error_not_supported(msg); err = cb->cbs->next(mp, cb->user_data); if (err < 0) return btd_error_failed(msg, strerror(-err)); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *media_player_previous(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_player *mp = data; struct player_callback *cb = mp->cb; int err; if (cb->cbs->previous == NULL) return btd_error_not_supported(msg); err = cb->cbs->previous(mp, cb->user_data); if (err < 0) return btd_error_failed(msg, strerror(-err)); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *media_player_fast_forward(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_player *mp = data; struct player_callback *cb = mp->cb; int err; if (cb->cbs->fast_forward == NULL) return btd_error_not_supported(msg); err = cb->cbs->fast_forward(mp, cb->user_data); if (err < 0) return btd_error_failed(msg, strerror(-err)); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *media_player_rewind(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_player *mp = data; struct player_callback *cb = mp->cb; int err; if (cb->cbs->rewind == NULL) return btd_error_not_supported(msg); err = cb->cbs->rewind(mp, cb->user_data); if (err < 0) return btd_error_failed(msg, strerror(-err)); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *media_player_press(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_player *mp = data; struct player_callback *cb = mp->cb; int err; uint8_t avc_key; if (cb->cbs->press == NULL) return btd_error_not_supported(msg); if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BYTE, &avc_key, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); err = cb->cbs->press(mp, avc_key, cb->user_data); if (err < 0) return btd_error_failed(msg, strerror(-err)); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *media_player_hold(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_player *mp = data; struct player_callback *cb = mp->cb; int err; uint8_t avc_key; if (cb->cbs->hold == NULL) return btd_error_not_supported(msg); if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BYTE, &avc_key, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); err = cb->cbs->hold(mp, avc_key, cb->user_data); if (err < 0) return btd_error_failed(msg, strerror(-err)); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *media_player_release(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_player *mp = data; struct player_callback *cb = mp->cb; int err; if (cb->cbs->release == NULL) return btd_error_not_supported(msg); err = cb->cbs->release(mp, cb->user_data); if (err < 0) return btd_error_failed(msg, strerror(-err)); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static void parse_folder_list(gpointer data, gpointer user_data) { struct media_item *item = data; DBusMessageIter *array = user_data; DBusMessageIter entry; dbus_message_iter_open_container(array, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, &item->path); g_dbus_get_properties(btd_get_dbus_connection(), item->path, MEDIA_ITEM_INTERFACE, &entry); dbus_message_iter_close_container(array, &entry); } void media_player_list_complete(struct media_player *mp, GSList *items, int err) { struct media_folder *folder = mp->scope; DBusMessage *reply; DBusMessageIter iter, array; if (folder == NULL || folder->msg == NULL) return; if (err < 0) { reply = btd_error_failed(folder->msg, strerror(-err)); goto done; } reply = dbus_message_new_method_return(folder->msg); dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_OBJECT_PATH_AS_STRING DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &array); g_slist_foreach(items, parse_folder_list, &array); dbus_message_iter_close_container(&iter, &array); done: g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(folder->msg); folder->msg = NULL; } static struct media_item * media_player_create_subfolder(struct media_player *mp, const char *name, uint64_t uid) { struct media_folder *folder = mp->scope; struct media_item *item; char *path; path = g_strdup_printf("%s/%s", folder->item->name, name); DBG("%s", path); item = media_player_create_item(mp, path, PLAYER_ITEM_TYPE_FOLDER, uid); g_free(path); return item; } void media_player_search_complete(struct media_player *mp, int ret) { struct media_folder *folder = mp->scope; struct media_folder *search = mp->search; DBusMessage *reply; if (folder == NULL || folder->msg == NULL) return; if (ret < 0) { reply = btd_error_failed(folder->msg, strerror(-ret)); goto done; } if (search == NULL) { search = g_new0(struct media_folder, 1); search->item = media_player_create_subfolder(mp, "search", 0); mp->search = search; mp->folders = g_slist_prepend(mp->folders, search); } search->number_of_items = ret; reply = g_dbus_create_reply(folder->msg, DBUS_TYPE_OBJECT_PATH, &search->item->path, DBUS_TYPE_INVALID); done: g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(folder->msg); folder->msg = NULL; } void media_player_total_items_complete(struct media_player *mp, uint32_t num_of_items) { struct media_folder *folder = mp->scope; if (folder == NULL || folder->msg == NULL) return; if (folder->number_of_items != num_of_items) { folder->number_of_items = num_of_items; g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, MEDIA_FOLDER_INTERFACE, "NumberOfItems"); } } static const GDBusMethodTable media_player_methods[] = { { GDBUS_METHOD("Play", NULL, NULL, media_player_play) }, { GDBUS_METHOD("Pause", NULL, NULL, media_player_pause) }, { GDBUS_METHOD("Stop", NULL, NULL, media_player_stop) }, { GDBUS_METHOD("Next", NULL, NULL, media_player_next) }, { GDBUS_METHOD("Previous", NULL, NULL, media_player_previous) }, { GDBUS_METHOD("FastForward", NULL, NULL, media_player_fast_forward) }, { GDBUS_METHOD("Rewind", NULL, NULL, media_player_rewind) }, { GDBUS_METHOD("Press", GDBUS_ARGS({"avc_key", "y"}), NULL, media_player_press) }, { GDBUS_METHOD("Hold", GDBUS_ARGS({"avc_key", "y"}), NULL, media_player_hold) }, { GDBUS_METHOD("Release", NULL, NULL, media_player_release) }, { } }; static const GDBusSignalTable media_player_signals[] = { { } }; static const GDBusPropertyTable media_player_properties[] = { { "Name", "s", get_name, NULL, name_exists }, { "Type", "s", get_type, NULL, type_exists }, { "Subtype", "s", get_subtype, NULL, subtype_exists }, { "Position", "u", get_position, NULL, NULL }, { "Status", "s", get_status, NULL, status_exists }, { "Equalizer", "s", get_setting, set_setting, setting_exists }, { "Repeat", "s", get_setting, set_setting, setting_exists }, { "Shuffle", "s", get_setting, set_setting, setting_exists }, { "Scan", "s", get_setting, set_setting, setting_exists }, { "Track", "a{sv}", get_track, NULL, track_exists }, { "Device", "o", get_device, NULL, NULL }, { "Browsable", "b", get_browsable, NULL, browsable_exists }, { "Searchable", "b", get_searchable, NULL, searchable_exists }, { "Playlist", "o", get_playlist, NULL, playlist_exists }, { "ObexPort", "q", get_obexport, NULL, obexport_exists, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { } }; static DBusMessage *media_folder_search(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_player *mp = data; struct media_folder *folder = mp->scope; struct player_callback *cb = mp->cb; DBusMessageIter iter; const char *string; int err; dbus_message_iter_init(msg, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) return btd_error_invalid_args(msg); dbus_message_iter_get_basic(&iter, &string); if (!mp->searchable || folder != mp->folder || !cb->cbs->search) return btd_error_not_supported(msg); if (folder->msg != NULL) return btd_error_failed(msg, strerror(EINVAL)); err = cb->cbs->search(mp, string, cb->user_data); if (err < 0) return btd_error_failed(msg, strerror(-err)); folder->msg = dbus_message_ref(msg); return NULL; } static int parse_filters(struct media_player *player, DBusMessageIter *iter, uint32_t *start, uint32_t *end) { struct media_folder *folder = player->scope; DBusMessageIter dict; int ctype; *start = 0; *end = folder->number_of_items ? folder->number_of_items - 1 : UINT32_MAX; ctype = dbus_message_iter_get_arg_type(iter); if (ctype != DBUS_TYPE_ARRAY) return FALSE; dbus_message_iter_recurse(iter, &dict); while ((ctype = dbus_message_iter_get_arg_type(&dict)) != DBUS_TYPE_INVALID) { DBusMessageIter entry, var; const char *key; if (ctype != DBUS_TYPE_DICT_ENTRY) return -EINVAL; dbus_message_iter_recurse(&dict, &entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) return -EINVAL; dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) return -EINVAL; dbus_message_iter_recurse(&entry, &var); if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_UINT32) return -EINVAL; if (strcasecmp(key, "Start") == 0) dbus_message_iter_get_basic(&var, start); else if (strcasecmp(key, "End") == 0) dbus_message_iter_get_basic(&var, end); dbus_message_iter_next(&dict); } if (folder->number_of_items > 0 && *end > folder->number_of_items) *end = folder->number_of_items; return 0; } static DBusMessage *media_folder_list_items(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_player *mp = data; struct media_folder *folder = mp->scope; struct player_callback *cb = mp->cb; DBusMessageIter iter; uint32_t start, end; int err; dbus_message_iter_init(msg, &iter); if (parse_filters(mp, &iter, &start, &end) < 0) return btd_error_invalid_args(msg); if (cb->cbs->list_items == NULL) return btd_error_not_supported(msg); if (folder->msg != NULL) return btd_error_failed(msg, strerror(EBUSY)); err = cb->cbs->list_items(mp, folder->item->name, start, end, cb->user_data); if (err < 0) return btd_error_failed(msg, strerror(-err)); folder->msg = dbus_message_ref(msg); return NULL; } static void media_item_free(struct media_item *item) { if (item->metadata != NULL) g_hash_table_unref(item->metadata); g_free(item->path); g_free(item->name); g_free(item); } static void media_item_destroy(void *data) { struct media_item *item = data; DBG("%s", item->path); g_dbus_unregister_interface(btd_get_dbus_connection(), item->path, MEDIA_ITEM_INTERFACE); media_item_free(item); } static void media_folder_destroy(void *data) { struct media_folder *folder = data; g_slist_free_full(folder->subfolders, media_folder_destroy); g_slist_free_full(folder->items, media_item_destroy); if (folder->msg != NULL) dbus_message_unref(folder->msg); media_item_destroy(folder->item); g_free(folder); } static void media_player_change_scope(struct media_player *mp, struct media_folder *folder) { struct player_callback *cb = mp->cb; int err; if (mp->scope == folder) return; DBG("%s", folder->item->name); /* Skip setting current folder if folder is current playlist/search */ if (folder == mp->playlist || folder == mp->search) goto cleanup; mp->folder = folder; /* Skip item cleanup if scope is the current playlist */ if (mp->scope == mp->playlist) goto done; cleanup: g_slist_free_full(mp->scope->items, media_item_destroy); mp->scope->items = NULL; /* Destroy search folder if it exists and is not being set as scope */ if (mp->search != NULL && folder != mp->search) { mp->folders = g_slist_remove(mp->folders, mp->search); media_folder_destroy(mp->search); mp->search = NULL; } done: mp->scope = folder; if (cb->cbs->total_items) { err = cb->cbs->total_items(mp, folder->item->name, cb->user_data); if (err < 0) DBG("Failed to get total num of items"); } else { g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, MEDIA_FOLDER_INTERFACE, "NumberOfItems"); } g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, MEDIA_FOLDER_INTERFACE, "Name"); } static struct media_folder *find_folder(GSList *folders, const char *pattern) { GSList *l; for (l = folders; l; l = l->next) { struct media_folder *folder = l->data; if (g_str_equal(folder->item->name, pattern)) return folder; if (g_str_equal(folder->item->path, pattern)) return folder; folder = find_folder(folder->subfolders, pattern); if (folder != NULL) return folder; } return NULL; } static struct media_folder *media_player_find_folder(struct media_player *mp, const char *pattern) { return find_folder(mp->folders, pattern); } static DBusMessage *media_folder_change_folder(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_player *mp = data; struct media_folder *folder = mp->scope; struct player_callback *cb = mp->cb; const char *path; int err; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); if (folder->msg != NULL) return btd_error_failed(msg, strerror(EBUSY)); folder = media_player_find_folder(mp, path); if (folder == NULL) return btd_error_invalid_args(msg); if (mp->scope == folder) return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); if (folder == mp->playlist || folder == mp->folder || folder == mp->search) { media_player_change_scope(mp, folder); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } /* * ChangePath can only navigate one level up/down so check if folder * is direct child or parent of the current folder otherwise fail. */ if (!g_slist_find(mp->folder->subfolders, folder) && !g_slist_find(folder->subfolders, mp->folder)) return btd_error_invalid_args(msg); if (cb->cbs->change_folder == NULL) return btd_error_not_supported(msg); err = cb->cbs->change_folder(mp, folder->item->name, folder->item->uid, cb->user_data); if (err < 0) return btd_error_failed(msg, strerror(-err)); mp->scope->msg = dbus_message_ref(msg); return NULL; } static gboolean folder_name_exists(const GDBusPropertyTable *property, void *data) { struct media_player *mp = data; struct media_folder *folder = mp->scope; if (folder == NULL || folder->item == NULL) return FALSE; return folder->item->name != NULL; } static gboolean get_folder_name(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_player *mp = data; struct media_folder *folder = mp->scope; if (folder == NULL || folder->item == NULL) return FALSE; DBG("%s", folder->item->name); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &folder->item->name); return TRUE; } static gboolean items_exists(const GDBusPropertyTable *property, void *data) { struct media_player *mp = data; return mp->scope != NULL; } static gboolean get_items(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_player *mp = data; struct media_folder *folder = mp->scope; if (folder == NULL) return FALSE; DBG("%u", folder->number_of_items); dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &folder->number_of_items); return TRUE; } static const GDBusMethodTable media_folder_methods[] = { { GDBUS_ASYNC_METHOD("Search", GDBUS_ARGS({ "string", "s" }, { "filter", "a{sv}" }), GDBUS_ARGS({ "folder", "o" }), media_folder_search) }, { GDBUS_ASYNC_METHOD("ListItems", GDBUS_ARGS({ "filter", "a{sv}" }), GDBUS_ARGS({ "items", "a{oa{sv}}" }), media_folder_list_items) }, { GDBUS_ASYNC_METHOD("ChangeFolder", GDBUS_ARGS({ "folder", "o" }), NULL, media_folder_change_folder) }, { } }; static const GDBusPropertyTable media_folder_properties[] = { { "Name", "s", get_folder_name, NULL, folder_name_exists }, { "NumberOfItems", "u", get_items, NULL, items_exists }, { } }; static void media_player_set_scope(struct media_player *mp, struct media_folder *folder) { if (mp->scope == NULL) { if (!g_dbus_register_interface(btd_get_dbus_connection(), mp->path, MEDIA_FOLDER_INTERFACE, media_folder_methods, NULL, media_folder_properties, mp, NULL)) { error("D-Bus failed to register %s on %s path", MEDIA_FOLDER_INTERFACE, mp->path); return; } mp->scope = folder; return; } return media_player_change_scope(mp, folder); } static struct media_folder * media_player_find_folder_by_uid(struct media_player *mp, uint64_t uid) { struct media_folder *folder = mp->scope; struct media_folder *parent = folder->parent; GSList *l; if (parent && parent->item->uid == uid) return parent; for (l = folder->subfolders; l; l = l->next) { struct media_folder *folder = l->data; if (folder->item->uid == uid) return folder; } return NULL; } static void media_player_set_folder_by_uid(struct media_player *mp, uint64_t uid, uint32_t number_of_items) { struct media_folder *folder; DBG("uid %" PRIu64 " number of items %u", uid, number_of_items); folder = media_player_find_folder_by_uid(mp, uid); if (folder == NULL) { error("Unknown folder: %" PRIu64, uid); return; } folder->number_of_items = number_of_items; media_player_set_scope(mp, folder); } void media_player_change_folder_complete(struct media_player *mp, const char *path, uint64_t uid, int ret) { struct media_folder *folder = mp->scope; DBusMessage *reply; if (folder == NULL || folder->msg == NULL) return; if (ret < 0) { reply = btd_error_failed(folder->msg, strerror(-ret)); goto done; } media_player_set_folder_by_uid(mp, uid, ret); reply = g_dbus_create_reply(folder->msg, DBUS_TYPE_INVALID); done: g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(folder->msg); folder->msg = NULL; } void media_player_destroy(struct media_player *mp) { DBG("%s", mp->path); g_dbus_unregister_interface(btd_get_dbus_connection(), mp->path, MEDIA_PLAYER_INTERFACE); if (mp->track) g_hash_table_unref(mp->track); if (mp->settings) g_hash_table_unref(mp->settings); if (mp->scope) g_dbus_unregister_interface(btd_get_dbus_connection(), mp->path, MEDIA_FOLDER_INTERFACE); g_slist_free_full(mp->pending, g_free); g_slist_free_full(mp->folders, media_folder_destroy); g_timer_destroy(mp->progress); g_free(mp->cb); g_free(mp->status); g_free(mp->path); g_free(mp->device); g_free(mp->subtype); g_free(mp->type); g_free(mp->name); g_free(mp); } struct media_player *media_player_controller_create(const char *path, const char *type, uint16_t id) { struct media_player *mp; mp = g_new0(struct media_player, 1); mp->device = g_strdup(path); mp->path = g_strdup_printf("%s/%s/player%u", path, type, id); mp->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); mp->track = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); mp->progress = g_timer_new(); if (!g_dbus_register_interface(btd_get_dbus_connection(), mp->path, MEDIA_PLAYER_INTERFACE, media_player_methods, media_player_signals, media_player_properties, mp, NULL)) { error("D-Bus failed to register %s path", mp->path); media_player_destroy(mp); return NULL; } DBG("%s", mp->path); return mp; } const char *media_player_get_path(struct media_player *mp) { return mp->path; } void media_player_set_duration(struct media_player *mp, uint32_t duration) { char *value, *curval; DBG("%u", duration); /* Only update duration if track exists */ if (g_hash_table_size(mp->track) == 0) return; /* Ignore if duration is already set to value > 0 */ curval = g_hash_table_lookup(mp->track, "Duration"); if (curval != NULL && atoi(curval) != 0) return; value = g_strdup_printf("%u", duration); g_hash_table_replace(mp->track, g_strdup("Duration"), value); g_dbus_emit_property_changed_full(btd_get_dbus_connection(), mp->path, MEDIA_PLAYER_INTERFACE, "Track", G_DBUS_PROPERTY_CHANGED_FLAG_FLUSH); } void media_player_set_position(struct media_player *mp, uint32_t position) { DBG("%u", position); /* Only update duration if track exists */ if (g_hash_table_size(mp->track) == 0) return; mp->position = position; g_timer_start(mp->progress); g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, MEDIA_PLAYER_INTERFACE, "Position"); } void media_player_set_setting(struct media_player *mp, const char *key, const char *value) { char *curval; struct pending_req *p; DBG("%s: %s", key, value); if (strcasecmp(key, "Error") == 0) { p = g_slist_nth_data(mp->pending, 0); if (p == NULL) return; g_dbus_pending_property_error(p->id, ERROR_INTERFACE ".Failed", value); goto send; } curval = g_hash_table_lookup(mp->settings, key); if (g_strcmp0(curval, value) == 0) goto done; g_hash_table_replace(mp->settings, g_strdup(key), g_strdup(value)); g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, MEDIA_PLAYER_INTERFACE, key); done: p = find_pending(mp, key); if (p == NULL) return; if (strcasecmp(value, p->value) == 0) g_dbus_pending_property_success(p->id); else g_dbus_pending_property_error(p->id, ERROR_INTERFACE ".NotSupported", "Operation is not supported"); send: mp->pending = g_slist_remove(mp->pending, p); g_free(p); return; } const char *media_player_get_status(struct media_player *mp) { return mp->status; } void media_player_set_status(struct media_player *mp, const char *status) { DBG("%s", status); if (g_strcmp0(mp->status, status) == 0) return; g_free(mp->status); mp->status = g_strdup(status); g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, MEDIA_PLAYER_INTERFACE, "Status"); mp->position = media_player_get_position(mp); g_timer_start(mp->progress); } static gboolean remove_metadata(void *key, void *value, void *user_data) { if (!strcmp(key, "Duration")) return FALSE; return strcmp(key, "Item") ? TRUE : FALSE; } void media_player_clear_metadata(struct media_player *mp) { if (!mp) return; g_hash_table_foreach_remove(mp->track, remove_metadata, NULL); } void media_player_set_metadata(struct media_player *mp, struct media_item *item, const char *key, void *data, size_t len) { char *value, *curval; GHashTable *metadata = item ? item->metadata : mp->track; value = g_strndup(data, len); /* Ensure this is a valid UTF-8 string */ strtoutf8(value, len); DBG("%s: %s", key, value); curval = g_hash_table_lookup(metadata, key); if (g_strcmp0(curval, value) == 0) { g_free(value); return; } g_hash_table_replace(metadata, g_strdup(key), value); } void media_player_metadata_changed(struct media_player *mp) { char *item; if (!mp) return; g_dbus_emit_property_changed_full(btd_get_dbus_connection(), mp->path, MEDIA_PLAYER_INTERFACE, "Track", G_DBUS_PROPERTY_CHANGED_FLAG_FLUSH); item = g_hash_table_lookup(mp->track, "Item"); if (item == NULL) return; g_dbus_emit_property_changed_full(btd_get_dbus_connection(), item, MEDIA_ITEM_INTERFACE, "Metadata", G_DBUS_PROPERTY_CHANGED_FLAG_FLUSH); } void media_player_set_type(struct media_player *mp, const char *type) { if (g_strcmp0(mp->type, type) == 0) return; DBG("%s", type); mp->type = g_strdup(type); g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, MEDIA_PLAYER_INTERFACE, "Type"); } void media_player_set_subtype(struct media_player *mp, const char *subtype) { if (g_strcmp0(mp->subtype, subtype) == 0) return; DBG("%s", subtype); mp->subtype = g_strdup(subtype); g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, MEDIA_PLAYER_INTERFACE, "Subtype"); } void media_player_set_name(struct media_player *mp, const char *name) { if (g_strcmp0(mp->name, name) == 0) return; DBG("%s", name); mp->name = g_strdup(name); g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, MEDIA_PLAYER_INTERFACE, "Name"); } void media_player_set_browsable(struct media_player *mp, bool enabled) { if (mp->browsable == enabled) return; DBG("%s", enabled ? "true" : "false"); mp->browsable = enabled; g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, MEDIA_PLAYER_INTERFACE, "Browsable"); } bool media_player_get_browsable(struct media_player *mp) { return mp->browsable; } void media_player_set_searchable(struct media_player *mp, bool enabled) { if (mp->searchable == enabled) return; DBG("%s", enabled ? "true" : "false"); mp->searchable = enabled; g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, MEDIA_PLAYER_INTERFACE, "Searchable"); } void media_player_set_folder(struct media_player *mp, const char *name, uint32_t number_of_items) { struct media_folder *folder; DBG("%s number of items %u", name, number_of_items); folder = media_player_find_folder(mp, name); if (folder == NULL) { error("Unknown folder: %s", name); return; } folder->number_of_items = number_of_items; media_player_set_scope(mp, folder); } void media_player_set_playlist(struct media_player *mp, const char *name) { struct media_folder *folder; DBG("%s", name); folder = media_player_find_folder(mp, name); if (folder == NULL) { error("Unknown folder: %s", name); return; } mp->playlist = folder; g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, MEDIA_PLAYER_INTERFACE, "Playlist"); } static struct media_item *media_folder_find_item(struct media_folder *folder, uint64_t uid) { GSList *l; if (uid == 0) return NULL; for (l = folder->items; l; l = l->next) { struct media_item *item = l->data; if (item->uid == uid) return item; } return NULL; } static DBusMessage *media_item_play(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_item *item = data; struct media_player *mp = item->player; struct media_folder *folder = mp->scope; struct player_callback *cb = mp->cb; const char *path; int err; if (!item->playable || !cb->cbs->play_item) return btd_error_not_supported(msg); if (folder->msg) return btd_error_failed(msg, strerror(EBUSY)); path = mp->search && folder == mp->search ? "/Search" : item->path; err = cb->cbs->play_item(mp, path, item->uid, cb->user_data); if (err < 0) return btd_error_failed(msg, strerror(-err)); folder->msg = dbus_message_ref(msg); return NULL; } static DBusMessage *media_item_add_to_nowplaying(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_item *item = data; struct media_player *mp = item->player; struct player_callback *cb = mp->cb; int err; if (!item->playable || !cb->cbs->play_item) return btd_error_not_supported(msg); err = cb->cbs->add_to_nowplaying(mp, item->path, item->uid, cb->user_data); if (err < 0) return btd_error_failed(msg, strerror(-err)); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static gboolean get_player(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_item *item = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &item->player->path); return TRUE; } static gboolean item_name_exists(const GDBusPropertyTable *property, void *data) { struct media_item *item = data; return item->name != NULL; } static gboolean get_item_name(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_item *item = data; if (item->name == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &item->name); return TRUE; } static const char *type_to_string(uint8_t type) { switch (type) { case PLAYER_ITEM_TYPE_AUDIO: return "audio"; case PLAYER_ITEM_TYPE_VIDEO: return "video"; case PLAYER_ITEM_TYPE_FOLDER: return "folder"; } return NULL; } static gboolean get_item_type(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_item *item = data; const char *string; string = type_to_string(item->type); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string); return TRUE; } static gboolean get_playable(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_item *item = data; dbus_bool_t value; value = item->playable; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); return TRUE; } static const char *folder_type_to_string(uint8_t type) { switch (type) { case PLAYER_FOLDER_TYPE_MIXED: return "mixed"; case PLAYER_FOLDER_TYPE_TITLES: return "titles"; case PLAYER_FOLDER_TYPE_ALBUMS: return "albums"; case PLAYER_FOLDER_TYPE_ARTISTS: return "artists"; case PLAYER_FOLDER_TYPE_GENRES: return "genres"; case PLAYER_FOLDER_TYPE_PLAYLISTS: return "playlists"; case PLAYER_FOLDER_TYPE_YEARS: return "years"; } return NULL; } static gboolean folder_type_exists(const GDBusPropertyTable *property, void *data) { struct media_item *item = data; return folder_type_to_string(item->folder_type) != NULL; } static gboolean get_folder_type(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_item *item = data; const char *string; string = folder_type_to_string(item->folder_type); if (string == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string); return TRUE; } static gboolean metadata_exists(const GDBusPropertyTable *property, void *data) { struct media_item *item = data; return item->metadata != NULL; } static void append_metadata(void *key, void *value, void *user_data) { DBusMessageIter *dict = user_data; const char *strkey = key; if (strcasecmp(strkey, "Item") == 0) return; if (strcasecmp(strkey, "Duration") == 0 || strcasecmp(strkey, "TrackNumber") == 0 || strcasecmp(strkey, "NumberOfTracks") == 0) { uint32_t num = atoi(value); dict_append_entry(dict, key, DBUS_TYPE_UINT32, &num); } else { dict_append_entry(dict, key, DBUS_TYPE_STRING, &value); } } static gboolean get_metadata(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_item *item = data; DBusMessageIter dict; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); if (g_hash_table_size(item->metadata) > 0) g_hash_table_foreach(item->metadata, append_metadata, &dict); else if (item->name != NULL) dict_append_entry(&dict, "Title", DBUS_TYPE_STRING, &item->name); dbus_message_iter_close_container(iter, &dict); return TRUE; } static const GDBusMethodTable media_item_methods[] = { { GDBUS_ASYNC_METHOD("Play", NULL, NULL, media_item_play) }, { GDBUS_METHOD("AddtoNowPlaying", NULL, NULL, media_item_add_to_nowplaying) }, { } }; static const GDBusPropertyTable media_item_properties[] = { { "Player", "o", get_player, NULL, NULL }, { "Name", "s", get_item_name, NULL, item_name_exists }, { "Type", "s", get_item_type, NULL, NULL }, { "FolderType", "s", get_folder_type, NULL, folder_type_exists }, { "Playable", "b", get_playable, NULL, NULL }, { "Metadata", "a{sv}", get_metadata, NULL, metadata_exists }, { } }; void media_player_play_item_complete(struct media_player *mp, int err) { struct media_folder *folder = mp->scope; DBusMessage *reply; if (folder == NULL || folder->msg == NULL) return; if (err < 0) { reply = btd_error_failed(folder->msg, strerror(-err)); goto done; } reply = g_dbus_create_reply(folder->msg, DBUS_TYPE_INVALID); done: g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(folder->msg); folder->msg = NULL; } void media_item_set_playable(struct media_item *item, bool value) { if (item->playable == value) return; item->playable = value; g_dbus_emit_property_changed(btd_get_dbus_connection(), item->path, MEDIA_ITEM_INTERFACE, "Playable"); } static struct media_item *media_folder_create_item(struct media_player *mp, struct media_folder *folder, const char *name, player_item_type_t type, uint64_t uid) { struct media_item *item; const char *strtype; item = media_folder_find_item(folder, uid); if (item != NULL) return item; strtype = type_to_string(type); if (strtype == NULL) return NULL; DBG("%s type %s uid %" PRIu64 "", name, strtype, uid); item = g_new0(struct media_item, 1); item->player = mp; item->uid = uid; if (!uid && name[0] == '/') item->path = g_strdup_printf("%s%s", mp->path, name); else item->path = g_strdup_printf("%s/item%" PRIu64 "", folder->item->path, uid); item->name = g_strdup(name); item->type = type; item->folder_type = PLAYER_FOLDER_TYPE_INVALID; if (!g_dbus_register_interface(btd_get_dbus_connection(), item->path, MEDIA_ITEM_INTERFACE, media_item_methods, NULL, media_item_properties, item, NULL)) { error("D-Bus failed to register %s on %s path", MEDIA_ITEM_INTERFACE, item->path); media_item_free(item); return NULL; } if (type != PLAYER_ITEM_TYPE_FOLDER) { folder->items = g_slist_prepend(folder->items, item); item->metadata = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); } DBG("%s", item->path); return item; } struct media_item *media_player_create_item(struct media_player *mp, const char *name, player_item_type_t type, uint64_t uid) { return media_folder_create_item(mp, mp->scope, name, type, uid); } struct media_item *media_player_create_folder(struct media_player *mp, const char *name, player_folder_type_t type, uint64_t uid) { struct media_folder *folder; struct media_item *item; if (uid > 0) folder = media_player_find_folder_by_uid(mp, uid); else folder = media_player_find_folder(mp, name); if (folder != NULL) return folder->item; if (uid > 0) item = media_player_create_subfolder(mp, name, uid); else item = media_player_create_item(mp, name, PLAYER_ITEM_TYPE_FOLDER, uid); if (item == NULL) return NULL; folder = g_new0(struct media_folder, 1); folder->item = item; item->folder_type = type; if (mp->folder != NULL) goto done; mp->folder = folder; done: if (uid > 0) { folder->parent = mp->folder; mp->folder->subfolders = g_slist_prepend( mp->folder->subfolders, folder); } else mp->folders = g_slist_prepend(mp->folders, folder); return item; } void media_player_set_callbacks(struct media_player *mp, const struct media_player_callback *cbs, void *user_data) { struct player_callback *cb; if (mp->cb) g_free(mp->cb); cb = g_new0(struct player_callback, 1); cb->cbs = cbs; cb->user_data = user_data; mp->cb = cb; } struct media_item *media_player_set_playlist_item(struct media_player *mp, uint64_t uid) { struct media_folder *folder = mp->playlist; struct media_item *item; char *path; DBG("%" PRIu64 "", uid); if (folder == NULL || uid == 0) return NULL; item = media_folder_create_item(mp, folder, NULL, PLAYER_ITEM_TYPE_AUDIO, uid); if (item == NULL) return NULL; media_item_set_playable(item, true); if (mp->track != item->metadata) { g_hash_table_unref(mp->track); mp->track = g_hash_table_ref(item->metadata); } path = g_hash_table_lookup(mp->track, "Item"); if (path && !strcmp(path, item->path)) return item; g_hash_table_replace(mp->track, g_strdup("Item"), g_strdup(item->path)); return item; } void media_player_clear_playlist(struct media_player *mp) { if (mp->playlist) { g_slist_free_full(mp->playlist->items, media_item_destroy); mp->playlist->items = NULL; } g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, MEDIA_PLAYER_INTERFACE, "Playlist"); } void media_player_set_obex_port(struct media_player *mp, uint16_t port) { mp->obex_port = port; } bluez-5.87/profiles/audio/PaxHeaders/ccp.c0000644000000000000000000000005015142177620015515 xustar0020 atime=1783093711 20 ctime=1783094106 bluez-5.87/profiles/audio/ccp.c0000644000000000000000000001161715142177620015204 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2024 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/dbus-common.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/shared/gatt-server.h" #include "src/shared/ccp.h" #include "btio/btio.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/gatt-database.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #include "src/error.h" #define GTBS_UUID_STR "0000184C-0000-1000-8000-00805f9b34fb" struct ccp_data { struct btd_device *device; struct btd_service *service; struct bt_ccp *ccp; unsigned int state_id; }; static void ccp_debug(const char *str, void *user_data) { DBG_IDX(0xffff, "%s", str); } static struct ccp_data *ccp_data_new(struct btd_device *device) { struct ccp_data *data; data = new0(struct ccp_data, 1); data->device = device; return data; } static int ccp_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct btd_adapter *adapter = device_get_adapter(device); struct btd_gatt_database *database = btd_adapter_get_database(adapter); struct ccp_data *data = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); if (data) { error("Profile probed twice for the same device!"); return -EINVAL; } data = ccp_data_new(device); data->service = service; data->ccp = bt_ccp_new(btd_gatt_database_get_db(database), btd_device_get_gatt_db(device)); bt_ccp_set_debug(data->ccp, ccp_debug, NULL, NULL); btd_service_set_user_data(service, data); return 0; } static void ccp_data_free(struct ccp_data *data) { if (data->service) { btd_service_set_user_data(data->service, NULL); bt_ccp_set_user_data(data->ccp, NULL); } bt_ccp_unref(data->ccp); free(data); } static void ccp_data_remove(struct ccp_data *data) { DBG("data %p", data); ccp_data_free(data); } static void ccp_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct ccp_data *data; char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); data = btd_service_get_user_data(service); if (!data) { error("CCP service not handled by profile"); return; } ccp_data_remove(data); } static int ccp_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct bt_gatt_client *client = btd_device_get_gatt_client(device); struct ccp_data *data = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); if (!bt_ccp_attach(data->ccp, client)) { error("CCP unable to attach"); return -EINVAL; } /* TODO: register telephony operations here */ btd_service_connecting_complete(service, 0); return 0; } static int ccp_connect(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); return 0; } static int ccp_disconnect(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct ccp_data *data = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("%s", addr); bt_ccp_detach(data->ccp); btd_service_disconnecting_complete(service, 0); return 0; } static int ccp_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { struct btd_gatt_database *database = btd_adapter_get_database(adapter); bt_ccp_register(btd_gatt_database_get_db(database)); return 0; } static void ccp_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { DBG("CCP remove adapter"); } static struct btd_profile ccp_profile = { .name = "ccp", .priority = BTD_PROFILE_PRIORITY_MEDIUM, .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = GTBS_UUID_STR, .device_probe = ccp_probe, .device_remove = ccp_remove, .accept = ccp_accept, .connect = ccp_connect, .disconnect = ccp_disconnect, .adapter_probe = ccp_server_probe, .adapter_remove = ccp_server_remove, .testing = true, }; static int ccp_init(void) { return btd_profile_register(&ccp_profile); } static void ccp_exit(void) { btd_profile_unregister(&ccp_profile); } BLUETOOTH_PLUGIN_DEFINE(ccp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, ccp_init, ccp_exit) bluez-5.87/profiles/audio/PaxHeaders/sink.c0000644000000000000000000000005015063045254015713 xustar0020 atime=1783093628 20 ctime=1783094105 bluez-5.87/profiles/audio/sink.c0000644000000000000000000002206615063045254015402 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "gdbus/gdbus.h" #include "src/log.h" #include "src/adapter.h" #include "src/device.h" #include "src/service.h" #include "src/error.h" #include "src/dbus-common.h" #include "src/shared/queue.h" #include "avdtp.h" #include "media.h" #include "a2dp.h" #include "sink.h" #define STREAM_SETUP_RETRY_TIMER 2 struct sink { struct btd_service *service; struct avdtp *session; struct avdtp_stream *stream; unsigned int cb_id; avdtp_session_state_t session_state; avdtp_state_t stream_state; sink_state_t state; unsigned int connect_id; unsigned int disconnect_id; unsigned int avdtp_callback_id; }; struct sink_state_callback { sink_state_cb cb; struct btd_service *service; void *user_data; unsigned int id; }; static GSList *sink_callbacks = NULL; static const char *str_state[] = { "SINK_STATE_DISCONNECTED", "SINK_STATE_CONNECTING", "SINK_STATE_CONNECTED", "SINK_STATE_PLAYING", }; static void sink_set_state(struct sink *sink, sink_state_t new_state) { struct btd_service *service = sink->service; struct btd_device *dev = btd_service_get_device(service); sink_state_t old_state = sink->state; GSList *l; sink->state = new_state; DBG("State changed %s: %s -> %s", device_get_path(dev), str_state[old_state], str_state[new_state]); for (l = sink_callbacks; l != NULL; l = l->next) { struct sink_state_callback *cb = l->data; if (cb->service != service) continue; cb->cb(service, old_state, new_state, cb->user_data); } if (new_state != SINK_STATE_DISCONNECTED) return; if (sink->session) { avdtp_unref(sink->session); sink->session = NULL; } } static void avdtp_state_callback(struct btd_device *dev, struct avdtp *session, avdtp_session_state_t old_state, avdtp_session_state_t new_state, void *user_data) { struct sink *sink = user_data; switch (new_state) { case AVDTP_SESSION_STATE_DISCONNECTED: sink_set_state(sink, SINK_STATE_DISCONNECTED); btd_service_disconnecting_complete(sink->service, 0); break; case AVDTP_SESSION_STATE_CONNECTING: sink_set_state(sink, SINK_STATE_CONNECTING); break; case AVDTP_SESSION_STATE_CONNECTED: break; } sink->session_state = new_state; } static void stream_state_changed(struct avdtp_stream *stream, avdtp_state_t old_state, avdtp_state_t new_state, struct avdtp_error *err, void *user_data) { struct btd_service *service = user_data; struct sink *sink = btd_service_get_user_data(service); if (err) return; switch (new_state) { case AVDTP_STATE_IDLE: if (sink->connect_id > 0) { a2dp_cancel(sink->connect_id); sink->connect_id = 0; } if (sink->disconnect_id > 0) { a2dp_cancel(sink->disconnect_id); sink->disconnect_id = 0; } if (sink->session) { avdtp_unref(sink->session); sink->session = NULL; } sink->stream = NULL; sink->cb_id = 0; break; case AVDTP_STATE_OPEN: btd_service_connecting_complete(sink->service, 0); sink_set_state(sink, SINK_STATE_CONNECTED); break; case AVDTP_STATE_STREAMING: sink_set_state(sink, SINK_STATE_PLAYING); break; case AVDTP_STATE_CONFIGURED: case AVDTP_STATE_CLOSING: case AVDTP_STATE_ABORTING: default: break; } sink->stream_state = new_state; } static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep, struct avdtp_stream *stream, int err, void *user_data) { struct sink *sink = user_data; sink->connect_id = 0; if (stream) return; avdtp_unref(sink->session); sink->session = NULL; btd_service_connecting_complete(sink->service, err); } static void select_complete(struct avdtp *session, struct a2dp_sep *sep, GSList *caps, int err, void *user_data) { struct sink *sink = user_data; int id; sink->connect_id = 0; if (err) goto failed; id = a2dp_config(session, sep, stream_setup_complete, caps, sink); if (id == 0) goto failed; sink->connect_id = id; return; failed: btd_service_connecting_complete(sink->service, -EIO); avdtp_unref(sink->session); sink->session = NULL; } static void discovery_complete(struct avdtp *session, GSList *seps, int err, void *user_data) { struct sink *sink = user_data; int id; sink->connect_id = 0; if (err) { avdtp_unref(sink->session); sink->session = NULL; goto failed; } DBG("Discovery complete"); id = a2dp_select_capabilities(sink->session, AVDTP_SEP_TYPE_SINK, NULL, select_complete, sink); if (id == 0) { err = -EIO; goto failed; } sink->connect_id = id; return; failed: btd_service_connecting_complete(sink->service, err); avdtp_unref(sink->session); sink->session = NULL; } gboolean sink_setup_stream(struct btd_service *service, struct avdtp *session) { struct sink *sink = btd_service_get_user_data(service); if (sink->connect_id > 0 || sink->disconnect_id > 0) return FALSE; if (!sink->session) { if (session) sink->session = avdtp_ref(session); else sink->session = a2dp_avdtp_get( btd_service_get_device(service)); if (!sink->session) { DBG("Unable to get a session"); return FALSE; } } sink->connect_id = a2dp_discover(sink->session, discovery_complete, sink); if (sink->connect_id == 0) { avdtp_unref(sink->session); sink->session = NULL; return FALSE; } return TRUE; } int sink_connect(struct btd_service *service) { struct sink *sink = btd_service_get_user_data(service); if (sink->connect_id > 0 || sink->disconnect_id > 0) return -EBUSY; if (sink->state == SINK_STATE_CONNECTING) return -EBUSY; if (sink->stream_state >= AVDTP_STATE_OPEN) return -EALREADY; if (!sink_setup_stream(service, NULL)) { DBG("Failed to create a stream"); return -EIO; } DBG("stream creation in progress"); return 0; } static void sink_free(struct btd_service *service) { struct sink *sink = btd_service_get_user_data(service); if (sink->cb_id) avdtp_stream_remove_cb(sink->session, sink->stream, sink->cb_id); if (sink->session) { avdtp_unref(sink->session); sink->session = NULL; } if (sink->connect_id > 0) { btd_service_connecting_complete(sink->service, -ECANCELED); a2dp_cancel(sink->connect_id); sink->connect_id = 0; } if (sink->disconnect_id > 0) { btd_service_disconnecting_complete(sink->service, -ECANCELED); a2dp_cancel(sink->disconnect_id); sink->disconnect_id = 0; } avdtp_remove_state_cb(sink->avdtp_callback_id); btd_service_unref(sink->service); g_free(sink); } void sink_unregister(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); DBG("%s", device_get_path(dev)); sink_free(service); } int sink_init(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); struct sink *sink; DBG("%s", device_get_path(dev)); sink = g_new0(struct sink, 1); sink->service = btd_service_ref(service); sink->avdtp_callback_id = avdtp_add_state_cb(dev, avdtp_state_callback, sink); btd_service_set_user_data(service, sink); return 0; } gboolean sink_is_active(struct btd_service *service) { struct sink *sink = btd_service_get_user_data(service); if (sink->session) return TRUE; return FALSE; } gboolean sink_new_stream(struct btd_service *service, struct avdtp *session, struct avdtp_stream *stream) { struct sink *sink = btd_service_get_user_data(service); if (sink->stream) return FALSE; if (!sink->session) sink->session = avdtp_ref(session); sink->stream = stream; sink->cb_id = avdtp_stream_add_cb(session, stream, stream_state_changed, service); return TRUE; } int sink_disconnect(struct btd_service *service) { struct sink *sink = btd_service_get_user_data(service); if (!sink->session) return -ENOTCONN; /* cancel pending connect */ if (sink->connect_id > 0) { avdtp_unref(sink->session); sink->session = NULL; a2dp_cancel(sink->connect_id); sink->connect_id = 0; btd_service_disconnecting_complete(sink->service, 0); return 0; } /* disconnect already ongoing */ if (sink->disconnect_id > 0) return -EBUSY; if (!sink->stream) return -ENOTCONN; return avdtp_close(sink->session, sink->stream, FALSE); } unsigned int sink_add_state_cb(struct btd_service *service, sink_state_cb cb, void *user_data) { struct sink_state_callback *state_cb; static unsigned int id = 0; state_cb = g_new(struct sink_state_callback, 1); state_cb->cb = cb; state_cb->service = service; state_cb->user_data = user_data; state_cb->id = ++id; sink_callbacks = g_slist_append(sink_callbacks, state_cb); return state_cb->id; } gboolean sink_remove_state_cb(unsigned int id) { GSList *l; for (l = sink_callbacks; l != NULL; l = l->next) { struct sink_state_callback *cb = l->data; if (cb && cb->id == id) { sink_callbacks = g_slist_remove(sink_callbacks, cb); g_free(cb); return TRUE; } } return FALSE; } bluez-5.87/profiles/audio/PaxHeaders/avctp.h0000644000000000000000000000005014131623652016070 xustar0020 atime=1783093647 20 ctime=1783094105 bluez-5.87/profiles/audio/avctp.h0000644000000000000000000001275114131623652015557 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #define AVCTP_CONTROL_PSM 23 #define AVCTP_BROWSING_PSM 27 #define AVC_MTU 512 #define AVC_HEADER_LENGTH 3 /* ctype entries */ #define AVC_CTYPE_CONTROL 0x0 #define AVC_CTYPE_STATUS 0x1 #define AVC_CTYPE_NOTIFY 0x3 #define AVC_CTYPE_NOT_IMPLEMENTED 0x8 #define AVC_CTYPE_ACCEPTED 0x9 #define AVC_CTYPE_REJECTED 0xA #define AVC_CTYPE_STABLE 0xC #define AVC_CTYPE_CHANGED 0xD #define AVC_CTYPE_INTERIM 0xF /* opcodes */ #define AVC_OP_VENDORDEP 0x00 #define AVC_OP_UNITINFO 0x30 #define AVC_OP_SUBUNITINFO 0x31 #define AVC_OP_PASSTHROUGH 0x7c /* subunits of interest */ #define AVC_SUBUNIT_PANEL 0x09 /* operands in passthrough commands */ #define AVC_SELECT 0x00 #define AVC_UP 0x01 #define AVC_DOWN 0x02 #define AVC_LEFT 0x03 #define AVC_RIGHT 0x04 #define AVC_RIGHT_UP 0x05 #define AVC_RIGHT_DOWN 0x06 #define AVC_LEFT_UP 0x07 #define AVC_LEFT_DOWN 0x08 #define AVC_ROOT_MENU 0x09 #define AVC_SETUP_MENU 0x0a #define AVC_CONTENTS_MENU 0x0b #define AVC_FAVORITE_MENU 0x0c #define AVC_EXIT 0x0d #define AVC_ON_DEMAND_MENU 0x0e #define AVC_APPS_MENU 0x0f #define AVC_0 0x20 #define AVC_1 0x21 #define AVC_2 0x22 #define AVC_3 0x23 #define AVC_4 0x24 #define AVC_5 0x25 #define AVC_6 0x26 #define AVC_7 0x27 #define AVC_8 0x28 #define AVC_9 0x29 #define AVC_DOT 0x2a #define AVC_ENTER 0x2b #define AVC_CLEAR 0x2c #define AVC_CHANNEL_UP 0x30 #define AVC_CHANNEL_DOWN 0x31 #define AVC_CHANNEL_PREVIOUS 0x32 #define AVC_SOUND_SELECT 0x33 #define AVC_INPUT_SELECT 0x34 #define AVC_INFO 0x35 #define AVC_HELP 0x36 #define AVC_PAGE_UP 0x37 #define AVC_PAGE_DOWN 0x38 #define AVC_LOCK 0x3a #define AVC_POWER 0x40 #define AVC_VOLUME_UP 0x41 #define AVC_VOLUME_DOWN 0x42 #define AVC_MUTE 0x43 #define AVC_PLAY 0x44 #define AVC_STOP 0x45 #define AVC_PAUSE 0x46 #define AVC_RECORD 0x47 #define AVC_REWIND 0x48 #define AVC_FAST_FORWARD 0x49 #define AVC_EJECT 0x4a #define AVC_FORWARD 0x4b #define AVC_BACKWARD 0x4c #define AVC_LIST 0x4d #define AVC_ANGLE 0x50 #define AVC_SUBPICTURE 0x51 #define AVC_F1 0x71 #define AVC_F2 0x72 #define AVC_F3 0x73 #define AVC_F4 0x74 #define AVC_F5 0x75 #define AVC_F6 0x76 #define AVC_F7 0x77 #define AVC_F8 0x78 #define AVC_F9 0x79 #define AVC_RED 0x7a #define AVC_GREEN 0x7b #define AVC_BLUE 0x7c #define AVC_YELLOW 0x7c #define AVC_VENDOR_UNIQUE 0x7e #define AVC_INVALID 0xff struct avctp; typedef enum { AVCTP_STATE_DISCONNECTED = 0, AVCTP_STATE_CONNECTING, AVCTP_STATE_CONNECTED, AVCTP_STATE_BROWSING_CONNECTING, AVCTP_STATE_BROWSING_CONNECTED } avctp_state_t; typedef void (*avctp_state_cb) (struct btd_device *dev, avctp_state_t old_state, avctp_state_t new_state, int err, void *user_data); typedef bool (*avctp_passthrough_cb) (struct avctp *session, uint8_t op, bool pressed, void *user_data); typedef size_t (*avctp_control_pdu_cb) (struct avctp *session, uint8_t transaction, uint8_t *code, uint8_t *subunit, uint8_t *operands, size_t operand_count, void *user_data); typedef gboolean (*avctp_rsp_cb) (struct avctp *session, uint8_t code, uint8_t subunit, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data); typedef gboolean (*avctp_browsing_rsp_cb) (struct avctp *session, uint8_t *operands, size_t operand_count, void *user_data); typedef size_t (*avctp_browsing_pdu_cb) (struct avctp *session, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data); unsigned int avctp_add_state_cb(struct btd_device *dev, avctp_state_cb cb, void *user_data); gboolean avctp_remove_state_cb(unsigned int id); int avctp_register(struct btd_adapter *adapter, bool central, bool *browsing); void avctp_unregister(struct btd_adapter *adapter); struct avctp *avctp_connect(struct btd_device *device); struct avctp *avctp_get(struct btd_device *device); bool avctp_is_initiator(struct avctp *session); int avctp_connect_browsing(struct avctp *session); void avctp_disconnect(struct avctp *session); unsigned int avctp_register_passthrough_handler(struct avctp *session, avctp_passthrough_cb cb, void *user_data); bool avctp_unregister_passthrough_handler(unsigned int id); unsigned int avctp_register_pdu_handler(struct avctp *session, uint8_t opcode, avctp_control_pdu_cb cb, void *user_data); gboolean avctp_unregister_pdu_handler(unsigned int id); unsigned int avctp_register_browsing_pdu_handler(struct avctp *session, avctp_browsing_pdu_cb cb, void *user_data, GDestroyNotify destroy); gboolean avctp_unregister_browsing_pdu_handler(unsigned int id); int avctp_send_passthrough(struct avctp *session, uint8_t op, bool hold); int avctp_send_release_passthrough(struct avctp *session); int avctp_send_vendordep(struct avctp *session, uint8_t transaction, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count); int avctp_send_vendordep_req(struct avctp *session, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, avctp_rsp_cb func, void *user_data); int avctp_send_browsing_req(struct avctp *session, uint8_t *operands, size_t operand_count, avctp_browsing_rsp_cb func, void *user_data); bool avctp_supports_avc(uint8_t avc); bluez-5.87/profiles/audio/PaxHeaders/avdtp.c0000644000000000000000000000005015221752714016067 xustar0020 atime=1783093637 20 ctime=1783094105 bluez-5.87/profiles/audio/avdtp.c0000644000000000000000000026746415221752714015573 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "bluetooth/uuid.h" #include "btio/btio.h" #include "src/btd.h" #include "src/log.h" #include "src/shared/io.h" #include "src/shared/timeout.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/adapter.h" #include "src/device.h" #include "avdtp.h" #include "sink.h" #include "source.h" #define AVDTP_PSM 25 #define MAX_SEID 0x3E #ifndef MAX # define MAX(x, y) ((x) > (y) ? (x) : (y)) #endif #define AVDTP_DISCOVER 0x01 #define AVDTP_GET_CAPABILITIES 0x02 #define AVDTP_SET_CONFIGURATION 0x03 #define AVDTP_GET_CONFIGURATION 0x04 #define AVDTP_RECONFIGURE 0x05 #define AVDTP_OPEN 0x06 #define AVDTP_START 0x07 #define AVDTP_CLOSE 0x08 #define AVDTP_SUSPEND 0x09 #define AVDTP_ABORT 0x0A #define AVDTP_SECURITY_CONTROL 0x0B #define AVDTP_GET_ALL_CAPABILITIES 0x0C #define AVDTP_DELAY_REPORT 0x0D #define AVDTP_PKT_TYPE_SINGLE 0x00 #define AVDTP_PKT_TYPE_START 0x01 #define AVDTP_PKT_TYPE_CONTINUE 0x02 #define AVDTP_PKT_TYPE_END 0x03 #define AVDTP_MSG_TYPE_COMMAND 0x00 #define AVDTP_MSG_TYPE_GEN_REJECT 0x01 #define AVDTP_MSG_TYPE_ACCEPT 0x02 #define AVDTP_MSG_TYPE_REJECT 0x03 #define REQ_TIMEOUT 6 #define SUSPEND_TIMEOUT 10 #define ABORT_TIMEOUT 2 #define DISCONNECT_TIMEOUT 1 #define START_TIMEOUT 1 #if __BYTE_ORDER == __LITTLE_ENDIAN struct avdtp_common_header { uint8_t message_type:2; uint8_t packet_type:2; uint8_t transaction:4; } __attribute__ ((packed)); struct avdtp_single_header { uint8_t message_type:2; uint8_t packet_type:2; uint8_t transaction:4; uint8_t signal_id:6; uint8_t rfa0:2; } __attribute__ ((packed)); struct avdtp_start_header { uint8_t message_type:2; uint8_t packet_type:2; uint8_t transaction:4; uint8_t no_of_packets; uint8_t signal_id:6; uint8_t rfa0:2; } __attribute__ ((packed)); struct avdtp_continue_header { uint8_t message_type:2; uint8_t packet_type:2; uint8_t transaction:4; } __attribute__ ((packed)); struct seid_info { uint8_t rfa0:1; uint8_t inuse:1; uint8_t seid:6; uint8_t rfa2:3; uint8_t type:1; uint8_t media_type:4; } __attribute__ ((packed)); struct seid { uint8_t rfa0:2; uint8_t seid:6; } __attribute__ ((packed)); #elif __BYTE_ORDER == __BIG_ENDIAN struct avdtp_common_header { uint8_t transaction:4; uint8_t packet_type:2; uint8_t message_type:2; } __attribute__ ((packed)); struct avdtp_single_header { uint8_t transaction:4; uint8_t packet_type:2; uint8_t message_type:2; uint8_t rfa0:2; uint8_t signal_id:6; } __attribute__ ((packed)); struct avdtp_start_header { uint8_t transaction:4; uint8_t packet_type:2; uint8_t message_type:2; uint8_t no_of_packets; uint8_t rfa0:2; uint8_t signal_id:6; } __attribute__ ((packed)); struct avdtp_continue_header { uint8_t transaction:4; uint8_t packet_type:2; uint8_t message_type:2; } __attribute__ ((packed)); struct seid_info { uint8_t seid:6; uint8_t inuse:1; uint8_t rfa0:1; uint8_t media_type:4; uint8_t type:1; uint8_t rfa2:3; } __attribute__ ((packed)); struct seid { uint8_t seid:6; uint8_t rfa0:2; } __attribute__ ((packed)); #else #error "Unknown byte order" #endif /* packets */ struct discover_resp { struct seid_info seps[0]; } __attribute__ ((packed)); struct getcap_resp { uint8_t caps[0]; } __attribute__ ((packed)); struct start_req { union { struct seid required[1]; struct seid seids[0]; }; } __attribute__ ((packed)); struct suspend_req { union { struct seid required[1]; struct seid seids[0]; }; } __attribute__ ((packed)); struct seid_rej { uint8_t error; } __attribute__ ((packed)); struct conf_rej { uint8_t category; uint8_t error; } __attribute__ ((packed)); #if __BYTE_ORDER == __LITTLE_ENDIAN struct seid_req { uint8_t rfa0:2; uint8_t acp_seid:6; } __attribute__ ((packed)); struct setconf_req { uint8_t rfa0:2; uint8_t acp_seid:6; uint8_t rfa1:2; uint8_t int_seid:6; uint8_t caps[0]; } __attribute__ ((packed)); struct stream_rej { uint8_t rfa0:2; uint8_t acp_seid:6; uint8_t error; } __attribute__ ((packed)); struct reconf_req { uint8_t rfa0:2; uint8_t acp_seid:6; uint8_t serv_cap; uint8_t serv_cap_len; uint8_t caps[0]; } __attribute__ ((packed)); struct delay_req { uint8_t rfa0:2; uint8_t acp_seid:6; uint16_t delay; } __attribute__ ((packed)); #elif __BYTE_ORDER == __BIG_ENDIAN struct seid_req { uint8_t acp_seid:6; uint8_t rfa0:2; } __attribute__ ((packed)); struct setconf_req { uint8_t acp_seid:6; uint8_t rfa0:2; uint8_t int_seid:6; uint8_t rfa1:2; uint8_t caps[0]; } __attribute__ ((packed)); struct stream_rej { uint8_t acp_seid:6; uint8_t rfa0:2; uint8_t error; } __attribute__ ((packed)); struct reconf_req { uint8_t acp_seid:6; uint8_t rfa0:2; uint8_t serv_cap; uint8_t serv_cap_len; uint8_t caps[0]; } __attribute__ ((packed)); struct delay_req { uint8_t acp_seid:6; uint8_t rfa0:2; uint16_t delay; } __attribute__ ((packed)); #else #error "Unknown byte order" #endif struct in_buf { gboolean active; int no_of_packets; uint8_t transaction; uint8_t signal_id; uint8_t buf[1024]; uint8_t data_size; }; struct pending_req { uint8_t transaction; uint8_t signal_id; void *data; size_t data_size; struct avdtp_stream *stream; /* Set if the request targeted a stream */ unsigned int timeout; gboolean collided; }; struct avdtp_remote_sep { uint8_t seid; uint8_t type; uint8_t media_type; struct avdtp_service_capability *codec; gboolean delay_reporting; bool discovered; GSList *caps; /* of type struct avdtp_service_capability */ struct avdtp_stream *stream; avdtp_remote_sep_destroy_t destroy; void *user_data; }; struct avdtp_local_sep { struct seid_info info; uint8_t codec; gboolean delay_reporting; GSList *caps; struct avdtp_sep_ind *ind; struct avdtp_sep_cfm *cfm; void *user_data; }; struct stream_callback { avdtp_stream_state_cb cb; void *user_data; unsigned int id; }; struct avdtp_state_callback { avdtp_session_state_cb cb; struct btd_device *dev; unsigned int id; void *user_data; }; struct discover_callback { unsigned int id; avdtp_discover_cb_t cb; void *user_data; }; struct avdtp_stream { GIOChannel *io; uint16_t imtu; uint16_t omtu; struct avdtp *session; struct avdtp_local_sep *lsep; uint8_t rseid; GSList *caps; GSList *callbacks; struct avdtp_service_capability *codec; guint io_id; /* Transport GSource ID */ unsigned int timer; /* Waiting for other side to close or open * the transport channel */ gboolean open_acp; /* If we are in ACT role for Open */ gboolean close_int; /* If we are in INT role for Close */ gboolean abort_int; /* If we are in INT role for Abort */ unsigned int start_timer; /* Wait START command timer */ gboolean delay_reporting; uint16_t delay; /* AVDTP 1.3 Delay Reporting feature */ gboolean starting; /* only valid while sep state == OPEN */ avdtp_state_t state; }; /* Structure describing an AVDTP connection between two devices */ struct avdtp { unsigned int ref; uint16_t version; struct queue *lseps; struct btd_device *device; avdtp_session_state_t state; GIOChannel *io; guint io_id; GSList *seps; /* Elements of type struct avdtp_remote_sep * */ GSList *streams; /* Elements of type struct avdtp_stream * */ GSList *req_queue; /* Elements of type struct pending_req * */ GSList *prio_queue; /* Same as req_queue but is processed before it */ struct avdtp_stream *pending_open; GIOChannel *pending_open_io; uint32_t phy; uint16_t imtu; uint16_t omtu; struct in_buf in_resp; struct in_buf in_cmd; char *buf; struct discover_callback *discover; struct pending_req *req; unsigned int dc_timer; int dc_timeout; /* Attempt stream setup instead of disconnecting */ gboolean stream_setup; }; static GSList *state_callbacks = NULL; static struct queue *streams = NULL; static int send_request(struct avdtp *session, gboolean priority, struct avdtp_stream *stream, uint8_t signal_id, void *buffer, size_t size); static gboolean avdtp_parse_resp(struct avdtp *session, struct avdtp_stream *stream, uint8_t transaction, uint8_t signal_id, void *buf, int size); static gboolean avdtp_parse_rej(struct avdtp *session, struct avdtp_stream *stream, uint8_t transaction, uint8_t signal_id, void *buf, int size); static int process_queue(struct avdtp *session); static void avdtp_stream_set_state(struct avdtp_stream *stream, avdtp_state_t state); static const char *avdtp_statestr(avdtp_state_t state) { switch (state) { case AVDTP_STATE_IDLE: return "IDLE"; case AVDTP_STATE_CONFIGURED: return "CONFIGURED"; case AVDTP_STATE_OPEN: return "OPEN"; case AVDTP_STATE_STREAMING: return "STREAMING"; case AVDTP_STATE_CLOSING: return "CLOSING"; case AVDTP_STATE_ABORTING: return "ABORTING"; default: return ""; } } static gboolean try_send(int sk, void *data, size_t len) { int err; do { err = send(sk, data, len, 0); } while (err < 0 && errno == EINTR); if (err < 0) { error("send: %s (%d)", strerror(errno), errno); return FALSE; } else if ((size_t) err != len) { error("try_send: complete buffer not sent (%d/%zu bytes)", err, len); return FALSE; } return TRUE; } static gboolean avdtp_send(struct avdtp *session, uint8_t transaction, uint8_t message_type, uint8_t signal_id, void *data, size_t len) { unsigned int cont_fragments, sent; struct avdtp_start_header start; struct avdtp_continue_header cont; int sock; if (session->io == NULL) { error("avdtp_send: session is closed"); return FALSE; } sock = g_io_channel_unix_get_fd(session->io); /* Single packet - no fragmentation */ if (sizeof(struct avdtp_single_header) + len <= session->omtu) { struct avdtp_single_header single; memset(&single, 0, sizeof(single)); single.transaction = transaction; single.packet_type = AVDTP_PKT_TYPE_SINGLE; single.message_type = message_type; single.signal_id = signal_id; memcpy(session->buf, &single, sizeof(single)); if (data) memcpy(session->buf + sizeof(single), data, len); return try_send(sock, session->buf, sizeof(single) + len); } /* Check if there is enough space to start packet */ if (session->omtu < sizeof(start)) { error("No enough space to fragment packet"); return FALSE; } /* Count the number of needed fragments */ cont_fragments = (len - (session->omtu - sizeof(start))) / (session->omtu - sizeof(cont)) + 1; DBG("%zu bytes split into %d fragments", len, cont_fragments + 1); /* Send the start packet */ memset(&start, 0, sizeof(start)); start.transaction = transaction; start.packet_type = AVDTP_PKT_TYPE_START; start.message_type = message_type; start.no_of_packets = cont_fragments + 1; start.signal_id = signal_id; memcpy(session->buf, &start, sizeof(start)); memcpy(session->buf + sizeof(start), data, session->omtu - sizeof(start)); if (!try_send(sock, session->buf, session->omtu)) return FALSE; DBG("first packet with %zu bytes sent", session->omtu - sizeof(start)); sent = session->omtu - sizeof(start); /* Send the continue fragments and the end packet */ while (sent < len) { int left, to_copy; left = len - sent; if (left + sizeof(cont) > session->omtu) { cont.packet_type = AVDTP_PKT_TYPE_CONTINUE; to_copy = session->omtu - sizeof(cont); DBG("sending continue with %d bytes", to_copy); } else { cont.packet_type = AVDTP_PKT_TYPE_END; to_copy = left; DBG("sending end with %d bytes", to_copy); } cont.transaction = transaction; cont.message_type = message_type; memcpy(session->buf, &cont, sizeof(cont)); memcpy(session->buf + sizeof(cont), data + sent, to_copy); if (!try_send(sock, session->buf, to_copy + sizeof(cont))) return FALSE; sent += to_copy; } return TRUE; } static void pending_req_free(void *data) { struct pending_req *req = data; if (req->timeout) timeout_remove(req->timeout); free(req->data); g_free(req); } static void close_stream(struct avdtp_stream *stream) { int sock; if (stream->io == NULL) return; sock = g_io_channel_unix_get_fd(stream->io); shutdown(sock, SHUT_RDWR); g_io_channel_shutdown(stream->io, FALSE, NULL); g_io_channel_unref(stream->io); stream->io = NULL; } static bool stream_close_timeout(gpointer user_data) { struct avdtp_stream *stream = user_data; DBG("Timed out waiting for peer to close the transport channel"); stream->timer = 0; close_stream(stream); return FALSE; } static bool stream_open_timeout(gpointer user_data) { struct avdtp_stream *stream = user_data; DBG("Timed out waiting for peer to open the transport channel"); stream->timer = 0; stream->session->pending_open = NULL; if (stream->session->pending_open_io) { g_io_channel_unref(stream->session->pending_open_io); stream->session->pending_open_io = NULL; } avdtp_abort(stream->session, stream); return FALSE; } static void stream_set_timer(struct avdtp_stream *stream, guint timeout, timeout_func_t func) { if (stream->timer) timeout_remove(stream->timer); stream->timer = timeout_add_seconds(timeout, func, stream, NULL); } static void stream_set_pending_open(struct avdtp_stream *stream, GIOChannel *io) { stream->open_acp = TRUE; stream->session->pending_open = stream; stream->session->pending_open_io = io; stream_set_timer(stream, REQ_TIMEOUT, stream_open_timeout); } void avdtp_error_init(struct avdtp_error *err, uint8_t category, int id) { err->category = category; if (category == AVDTP_ERRNO) err->err.posix_errno = id; else err->err.error_code = id; } uint8_t avdtp_error_category(struct avdtp_error *err) { return err->category; } int avdtp_error_error_code(struct avdtp_error *err) { assert(err->category != AVDTP_ERRNO); return err->err.error_code; } int avdtp_error_posix_errno(struct avdtp_error *err) { assert(err->category == AVDTP_ERRNO); return err->err.posix_errno; } static struct avdtp_stream *find_stream_by_rseid(struct avdtp *session, uint8_t rseid) { GSList *l; for (l = session->streams; l != NULL; l = g_slist_next(l)) { struct avdtp_stream *stream = l->data; if (stream->rseid == rseid) return stream; } return NULL; } static struct avdtp_remote_sep *find_remote_sep(GSList *seps, uint8_t seid) { GSList *l; for (l = seps; l != NULL; l = g_slist_next(l)) { struct avdtp_remote_sep *sep = l->data; if (sep->seid == seid) return sep; } return NULL; } static void avdtp_set_state(struct avdtp *session, avdtp_session_state_t new_state) { GSList *l; avdtp_session_state_t old_state = session->state; session->state = new_state; for (l = state_callbacks; l != NULL;) { struct avdtp_state_callback *cb = l->data; l = g_slist_next(l); if (session->device != cb->dev) continue; cb->cb(cb->dev, session, old_state, new_state, cb->user_data); } } static void stream_free(void *data) { struct avdtp_stream *stream = data; struct avdtp_remote_sep *rsep; rsep = find_remote_sep(stream->session->seps, stream->rseid); if (rsep) rsep->stream = NULL; if (stream->timer) timeout_remove(stream->timer); if (stream->io) close_stream(stream); if (stream->io_id) g_source_remove(stream->io_id); g_slist_free_full(stream->callbacks, g_free); g_slist_free_full(stream->caps, g_free); queue_remove(streams, stream); g_free(stream); } static void transport_cb(int cond, void *data) { struct avdtp_stream *stream = data; struct avdtp_local_sep *sep = stream->lsep; DBG(""); if (stream->close_int && sep->cfm && sep->cfm->close) sep->cfm->close(stream->session, sep, stream, NULL, sep->user_data); if (!(cond & G_IO_NVAL)) close_stream(stream); stream->io_id = 0; if (!stream->abort_int) avdtp_stream_set_state(stream, AVDTP_STATE_IDLE); } static int get_send_buffer_size(int sk) { int size; socklen_t optlen = sizeof(size); if (getsockopt(sk, SOL_SOCKET, SO_SNDBUF, &size, &optlen) < 0) { int err = -errno; error("getsockopt(SO_SNDBUF) failed: %s (%d)", strerror(-err), -err); return err; } /* * Doubled value is returned by getsockopt since kernel uses that * space for its own purposes (see man 7 socket, bookkeeping overhead * for SO_SNDBUF). */ return size / 2; } static int set_send_buffer_size(int sk, int size) { socklen_t optlen = sizeof(size); if (setsockopt(sk, SOL_SOCKET, SO_SNDBUF, &size, optlen) < 0) { int err = -errno; error("setsockopt(SO_SNDBUF) failed: %s (%d)", strerror(-err), -err); return err; } return 0; } static void handle_transport_connect(struct avdtp *session, GIOChannel *io, uint16_t imtu, uint16_t omtu) { struct avdtp_stream *stream = session->pending_open; struct avdtp_local_sep *sep = stream->lsep; int sk, buf_size, min_buf_size; GError *err = NULL; session->pending_open = NULL; if (stream->timer) { timeout_remove(stream->timer); stream->timer = 0; } if (io == NULL) { if (!stream->open_acp && sep->cfm && sep->cfm->open) { struct avdtp_error err; avdtp_error_init(&err, AVDTP_ERRNO, EIO); sep->cfm->open(session, sep, NULL, &err, sep->user_data); } return; } if (stream->io == NULL) stream->io = g_io_channel_ref(io); stream->omtu = omtu; stream->imtu = imtu; /* Apply special settings only if local SEP is of type SRC */ if (sep->info.type != AVDTP_SEP_TYPE_SOURCE) goto proceed; bt_io_set(stream->io, &err, BT_IO_OPT_FLUSHABLE, TRUE, BT_IO_OPT_INVALID); if (err != NULL) { error("Enabling flushable packets failed: %s", err->message); g_error_free(err); } else DBG("Flushable packets enabled"); sk = g_io_channel_unix_get_fd(stream->io); buf_size = get_send_buffer_size(sk); if (buf_size < 0) goto proceed; DBG("sk %d, omtu %d, send buffer size %d", sk, omtu, buf_size); min_buf_size = omtu * 2; if (buf_size < min_buf_size) { DBG("send buffer size to be increassed to %d", min_buf_size); set_send_buffer_size(sk, min_buf_size); } proceed: if (!stream->open_acp && sep->cfm && sep->cfm->open) sep->cfm->open(session, sep, stream, NULL, sep->user_data); avdtp_stream_set_state(stream, AVDTP_STATE_OPEN); stream->io_id = io_glib_add_err_watch(io, transport_cb, stream); /* Release pending IO */ if (session->pending_open_io) { g_io_channel_unref(session->pending_open_io); session->pending_open_io = NULL; } } static int pending_req_cmp(gconstpointer a, gconstpointer b) { const struct pending_req *req = a; const struct avdtp_stream *stream = b; if (req->stream == stream) return 0; return -1; } static void cleanup_queue(struct avdtp *session, struct avdtp_stream *stream) { GSList *l; struct pending_req *req; while ((l = g_slist_find_custom(session->prio_queue, stream, pending_req_cmp))) { req = l->data; pending_req_free(req); session->prio_queue = g_slist_remove(session->prio_queue, req); } while ((l = g_slist_find_custom(session->req_queue, stream, pending_req_cmp))) { req = l->data; pending_req_free(req); session->req_queue = g_slist_remove(session->req_queue, req); } } static void handle_unanswered_req(struct avdtp *session, struct avdtp_stream *stream) { struct pending_req *req; struct avdtp_local_sep *lsep; struct avdtp_error err; if (session->req->signal_id == AVDTP_ABORT) { /* Avoid freeing the Abort request here */ DBG("handle_unanswered_req: Abort req, returning"); session->req->stream = NULL; return; } req = session->req; session->req = NULL; avdtp_error_init(&err, AVDTP_ERRNO, EIO); lsep = stream->lsep; switch (req->signal_id) { case AVDTP_RECONFIGURE: error("No reply to Reconfigure request"); if (lsep && lsep->cfm && lsep->cfm->reconfigure) lsep->cfm->reconfigure(session, lsep, stream, &err, lsep->user_data); break; case AVDTP_OPEN: error("No reply to Open request"); if (lsep && lsep->cfm && lsep->cfm->open) lsep->cfm->open(session, lsep, stream, &err, lsep->user_data); break; case AVDTP_START: error("No reply to Start request"); if (lsep && lsep->cfm && lsep->cfm->start) lsep->cfm->start(session, lsep, stream, &err, lsep->user_data); break; case AVDTP_SUSPEND: error("No reply to Suspend request"); if (lsep && lsep->cfm && lsep->cfm->suspend) lsep->cfm->suspend(session, lsep, stream, &err, lsep->user_data); break; case AVDTP_CLOSE: error("No reply to Close request"); if (lsep && lsep->cfm && lsep->cfm->close) lsep->cfm->close(session, lsep, stream, &err, lsep->user_data); break; case AVDTP_SET_CONFIGURATION: error("No reply to SetConfiguration request"); if (lsep && lsep->cfm && lsep->cfm->set_configuration) lsep->cfm->set_configuration(session, lsep, stream, &err, lsep->user_data); } pending_req_free(req); } static void avdtp_stream_set_state(struct avdtp_stream *stream, avdtp_state_t state) { struct avdtp *session; avdtp_state_t old_state; struct avdtp_error err, *err_ptr = NULL; GSList *l; if (!stream) { error("Error changing sep state: stream not available"); return; } session = stream->session; if (stream->state == state) { avdtp_error_init(&err, AVDTP_ERRNO, EIO); DBG("stream state change failed: %s", avdtp_strerror(&err)); err_ptr = &err; } else { err_ptr = NULL; DBG("stream state changed: %s -> %s", avdtp_statestr(stream->state), avdtp_statestr(state)); } old_state = stream->state; stream->state = state; switch (state) { case AVDTP_STATE_CONFIGURED: if (stream->lsep->info.type == AVDTP_SEP_TYPE_SINK) avdtp_delay_report(session, stream, stream->delay); break; case AVDTP_STATE_OPEN: stream->starting = FALSE; break; case AVDTP_STATE_STREAMING: if (stream->start_timer) { timeout_remove(stream->start_timer); stream->start_timer = 0; } stream->open_acp = FALSE; break; case AVDTP_STATE_CLOSING: case AVDTP_STATE_ABORTING: if (stream->start_timer) { timeout_remove(stream->start_timer); stream->start_timer = 0; } break; case AVDTP_STATE_IDLE: if (stream->start_timer) { timeout_remove(stream->start_timer); stream->start_timer = 0; } if (session->pending_open == stream) handle_transport_connect(session, NULL, 0, 0); if (session->req && session->req->stream == stream) handle_unanswered_req(session, stream); /* Remove pending commands for this stream from the queue */ cleanup_queue(session, stream); session->streams = g_slist_remove(session->streams, stream); break; default: break; } l = stream->callbacks; while (l != NULL) { struct stream_callback *cb = l->data; l = g_slist_next(l); cb->cb(stream, old_state, state, err_ptr, cb->user_data); } if (state == AVDTP_STATE_IDLE) stream_free(stream); } static void sep_free(gpointer data) { struct avdtp_remote_sep *sep = data; if (sep->destroy) sep->destroy(sep->user_data); g_slist_free_full(sep->caps, g_free); g_free(sep); } static void remove_disappeared(void *data, void *user_data) { struct avdtp_remote_sep *sep = data; struct avdtp *session = user_data; if (sep->discovered) return; DBG("seid %d disappeared", sep->seid); session->seps = g_slist_remove(session->seps, sep); sep_free(sep); } static void finalize_discovery(struct avdtp *session, int err) { struct discover_callback *discover = session->discover; struct avdtp_error avdtp_err; if (!discover) return; avdtp_error_init(&avdtp_err, AVDTP_ERRNO, err); if (discover->id > 0) g_source_remove(discover->id); if (!err) g_slist_foreach(session->seps, remove_disappeared, session); if (discover->cb) discover->cb(session, session->seps, err ? &avdtp_err : NULL, discover->user_data); g_free(discover); session->discover = NULL; } static void release_stream(struct avdtp_stream *stream, struct avdtp *session) { struct avdtp_local_sep *sep = stream->lsep; if (sep->cfm && sep->cfm->abort && (stream->state != AVDTP_STATE_ABORTING || stream->abort_int)) sep->cfm->abort(session, sep, stream, NULL, sep->user_data); avdtp_stream_set_state(stream, AVDTP_STATE_IDLE); } static void remove_disconnect_timer(struct avdtp *session) { if (!session->dc_timer) return; timeout_remove(session->dc_timer); session->dc_timer = 0; session->stream_setup = FALSE; /* Release disconnect timer reference */ avdtp_unref(session); } static void avdtp_free(void *data) { struct avdtp *session = data; DBG("%p", session); g_slist_free_full(session->streams, stream_free); if (session->io) { g_io_channel_shutdown(session->io, FALSE, NULL); g_io_channel_unref(session->io); } if (session->io_id) { g_source_remove(session->io_id); session->io_id = 0; } if (session->req) pending_req_free(session->req); g_slist_free_full(session->req_queue, pending_req_free); g_slist_free_full(session->prio_queue, pending_req_free); g_slist_free_full(session->seps, sep_free); g_free(session->buf); btd_device_unref(session->device); g_free(session); } static void connection_lost(struct avdtp *session, int err) { char address[18]; session = avdtp_ref(session); ba2str(device_get_address(session->device), address); DBG("Disconnected from %s", address); g_slist_foreach(session->streams, (GFunc) release_stream, session); session->streams = NULL; finalize_discovery(session, err); avdtp_set_state(session, AVDTP_SESSION_STATE_DISCONNECTED); avdtp_unref(session); } static bool disconnect_timeout(gpointer user_data) { struct avdtp *session = user_data; struct btd_service *service; gboolean stream_setup; session->dc_timer = 0; stream_setup = session->stream_setup; session->stream_setup = FALSE; service = btd_device_get_service(session->device, A2DP_SINK_UUID); if (service && stream_setup) { sink_setup_stream(service, session); goto done; } service = btd_device_get_service(session->device, A2DP_SOURCE_UUID); if (service && stream_setup) { source_setup_stream(service, session); goto done; } connection_lost(session, ETIMEDOUT); done: /* Release disconnect timer reference */ avdtp_unref(session); return FALSE; } static void set_disconnect_timer(struct avdtp *session) { /* Take a ref while disconnect timer is active */ avdtp_ref(session); DBG("timeout %d", session->dc_timeout); session->dc_timer = timeout_add_seconds(session->dc_timeout, disconnect_timeout, session, NULL); } void avdtp_unref(struct avdtp *session) { if (!session) return; session->ref--; DBG("%p: ref=%d", session, session->ref); if (session->ref > 0) return; switch (session->state) { case AVDTP_SESSION_STATE_CONNECTED: /* Only set disconnect timer if there are local endpoints * otherwise disconnect immediately. */ if (queue_isempty(session->lseps)) connection_lost(session, ECONNRESET); else set_disconnect_timer(session); break; case AVDTP_SESSION_STATE_CONNECTING: connection_lost(session, ECONNABORTED); break; case AVDTP_SESSION_STATE_DISCONNECTED: default: avdtp_free(session); break; } } struct avdtp *avdtp_ref(struct avdtp *session) { session->ref++; DBG("%p: ref=%d", session, session->ref); remove_disconnect_timer(session); return session; } static bool match_by_seid(const void *data, const void *user_data) { const struct avdtp_local_sep *sep = data; uint8_t seid = PTR_TO_UINT(user_data); return sep->info.seid == seid; } static struct avdtp_local_sep *find_local_sep_by_seid(struct avdtp *session, uint8_t seid) { return queue_find(session->lseps, match_by_seid, INT_TO_PTR(seid)); } static struct avdtp_stream *find_stream_by_lsep(struct avdtp *session, struct avdtp_local_sep *sep) { GSList *l; for (l = session->streams; l != NULL; l = g_slist_next(l)) { struct avdtp_stream *stream = l->data; if (stream->lsep == sep) return stream; } return NULL; } static struct avdtp_stream *find_stream_by_lseid(struct avdtp *session, uint8_t type) { struct avdtp_local_sep *sep; sep = find_local_sep_by_seid(session, type); if (!sep) return NULL; return find_stream_by_lsep(session, sep); } struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session, struct avdtp_local_sep *lsep) { GSList *l; for (l = session->seps; l != NULL; l = g_slist_next(l)) { struct avdtp_remote_sep *sep = l->data; struct avdtp_service_capability *cap; struct avdtp_media_codec_capability *codec_data; /* Type must be different: source <-> sink */ if (sep->type == lsep->info.type) continue; if (sep->media_type != lsep->info.media_type) continue; if (!sep->codec) continue; cap = sep->codec; codec_data = (void *) cap->data; if (codec_data->media_codec_type != lsep->codec) continue; if (lsep->ind && lsep->ind->match_codec) if (!lsep->ind->match_codec(session, codec_data, lsep->user_data)) continue; if (sep->stream == NULL) return sep; } return NULL; } static GSList *caps_to_list(uint8_t *data, size_t size, struct avdtp_service_capability **codec, gboolean *delay_reporting, uint8_t *err) { struct avdtp_service_capability *cap; GSList *caps; if (delay_reporting) *delay_reporting = FALSE; if (size < sizeof(*cap)) return NULL; for (caps = NULL; size >= sizeof(*cap);) { struct avdtp_service_capability *cpy; cap = (struct avdtp_service_capability *)data; /* Verify that the Media Transport capability's length = 0. * Reject otherwise */ if (cap->category == AVDTP_MEDIA_TRANSPORT && cap->length != 0) { error("Invalid media transport in getcap resp"); if (err) *err = AVDTP_BAD_MEDIA_TRANSPORT_FORMAT; break; } if (sizeof(*cap) + cap->length > size) { error("Invalid capability data in getcap resp"); break; } if (cap->category == AVDTP_MEDIA_CODEC && cap->length < sizeof(**codec)) { error("Invalid codec data in getcap resp"); break; } cpy = util_malloc(sizeof(*cpy) + cap->length); memcpy(cpy, cap, sizeof(*cap) + cap->length); size -= sizeof(*cap) + cap->length; data += sizeof(*cap) + cap->length; caps = g_slist_append(caps, cpy); switch (cap->category) { case AVDTP_MEDIA_CODEC: if (codec) *codec = cpy; break; case AVDTP_DELAY_REPORTING: if (delay_reporting) *delay_reporting = TRUE; break; } } return caps; } static gboolean avdtp_unknown_cmd(struct avdtp *session, uint8_t transaction, uint8_t signal_id) { return avdtp_send(session, transaction, AVDTP_MSG_TYPE_GEN_REJECT, signal_id, NULL, 0); } static void copy_seps(void *data, void *user_data) { struct avdtp_local_sep *sep = data; struct seid_info **p = user_data; memcpy(*p, &sep->info, sizeof(struct seid_info)); *p = *p + 1; } static gboolean avdtp_discover_cmd(struct avdtp *session, uint8_t transaction, void *buf, int size) { unsigned int rsp_size, sep_count; struct seid_info *seps, *p; gboolean ret; sep_count = queue_length(session->lseps); if (sep_count == 0) { uint8_t err = AVDTP_NOT_SUPPORTED_COMMAND; return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, AVDTP_DISCOVER, &err, sizeof(err)); } rsp_size = sep_count * sizeof(struct seid_info); seps = g_new0(struct seid_info, sep_count); p = seps; queue_foreach(session->lseps, copy_seps, &p); ret = avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, AVDTP_DISCOVER, seps, rsp_size); g_free(seps); return ret; } static gboolean avdtp_getcap_cmd(struct avdtp *session, uint8_t transaction, struct seid_req *req, unsigned int size, gboolean get_all) { GSList *l, *caps; struct avdtp_local_sep *sep = NULL; unsigned int rsp_size; uint8_t err, buf[1024], *ptr = buf; uint8_t cmd; cmd = get_all ? AVDTP_GET_ALL_CAPABILITIES : AVDTP_GET_CAPABILITIES; if (size < sizeof(struct seid_req)) { err = AVDTP_BAD_LENGTH; goto failed; } sep = find_local_sep_by_seid(session, req->acp_seid); if (!sep) { err = AVDTP_BAD_ACP_SEID; goto failed; } if (!sep->ind->get_capability(session, sep, get_all, &caps, &err, sep->user_data)) goto failed; for (l = caps, rsp_size = 0; l != NULL; l = g_slist_next(l)) { struct avdtp_service_capability *cap = l->data; if (rsp_size + cap->length + 2 > sizeof(buf)) break; memcpy(ptr, cap, cap->length + 2); rsp_size += cap->length + 2; ptr += cap->length + 2; g_free(cap); } g_slist_free(caps); return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, cmd, buf, rsp_size); failed: return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, cmd, &err, sizeof(err)); } static void setconf_cb(struct avdtp *session, struct avdtp_stream *stream, struct avdtp_error *err) { struct conf_rej rej; if (err != NULL) { rej.error = err->err.error_code; rej.category = AVDTP_UNSUPPORTED_CONFIGURATION; avdtp_send(session, session->in_cmd.transaction, AVDTP_MSG_TYPE_REJECT, AVDTP_SET_CONFIGURATION, &rej, sizeof(rej)); stream_free(stream); return; } if (!avdtp_send(session, session->in_cmd.transaction, AVDTP_MSG_TYPE_ACCEPT, AVDTP_SET_CONFIGURATION, NULL, 0)) { stream_free(stream); return; } session->streams = g_slist_append(session->streams, stream); avdtp_stream_set_state(stream, AVDTP_STATE_CONFIGURED); } static struct avdtp_stream *stream_new(struct avdtp *session, struct avdtp_local_sep *lsep, uint8_t rseid) { struct avdtp_stream *stream; stream = new0(struct avdtp_stream, 1); stream->session = session; stream->lsep = lsep; stream->rseid = rseid; if (!streams) streams = queue_new(); queue_push_tail(streams, stream); return stream; } static gboolean avdtp_setconf_cmd(struct avdtp *session, uint8_t transaction, struct setconf_req *req, unsigned int size) { struct conf_rej rej; struct avdtp_local_sep *sep; struct avdtp_stream *stream; uint8_t err = 0, category = 0x00; struct btd_service *service; if (size < sizeof(struct setconf_req)) { error("Too short getcap request"); return FALSE; } sep = find_local_sep_by_seid(session, req->acp_seid); if (!sep) { err = AVDTP_BAD_ACP_SEID; goto failed; } /* check if SEID is in use */ if (find_stream_by_lsep(session, sep)) { err = AVDTP_SEP_IN_USE; goto failed; } switch (sep->info.type) { case AVDTP_SEP_TYPE_SOURCE: service = btd_device_get_service(session->device, A2DP_SINK_UUID); if (service == NULL) { btd_device_add_uuid(session->device, A2DP_SINK_UUID); service = btd_device_get_service(session->device, A2DP_SINK_UUID); if (service == NULL) { error("Unable to get a audio sink object"); err = AVDTP_BAD_STATE; goto failed; } } break; case AVDTP_SEP_TYPE_SINK: service = btd_device_get_service(session->device, A2DP_SOURCE_UUID); if (service == NULL) { btd_device_add_uuid(session->device, A2DP_SOURCE_UUID); service = btd_device_get_service(session->device, A2DP_SOURCE_UUID); if (service == NULL) { error("Unable to get a audio source object"); err = AVDTP_BAD_STATE; goto failed; } } break; } stream = stream_new(session, sep, req->int_seid); stream->caps = caps_to_list(req->caps, size - sizeof(struct setconf_req), &stream->codec, &stream->delay_reporting, &err); if (err) goto failed_stream; if (!stream->caps || !stream->codec) { err = AVDTP_UNSUPPORTED_CONFIGURATION; category = 0x00; goto failed_stream; } if (stream->delay_reporting && session->version < 0x0103) session->version = 0x0103; if (sep->ind && sep->ind->set_configuration) { if (!sep->ind->set_configuration(session, sep, stream, stream->caps, setconf_cb, sep->user_data)) { err = AVDTP_UNSUPPORTED_CONFIGURATION; category = 0x00; goto failed_stream; } } else { if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, AVDTP_SET_CONFIGURATION, NULL, 0)) { stream_free(stream); return FALSE; } session->streams = g_slist_append(session->streams, stream); avdtp_stream_set_state(stream, AVDTP_STATE_CONFIGURED); } return TRUE; failed_stream: stream_free(stream); failed: rej.error = err; rej.category = category; return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, AVDTP_SET_CONFIGURATION, &rej, sizeof(rej)); } static gboolean avdtp_getconf_cmd(struct avdtp *session, uint8_t transaction, struct seid_req *req, int size) { GSList *l; struct avdtp_stream *stream; int rsp_size; uint8_t err; uint8_t buf[1024]; uint8_t *ptr = buf; if (size < (int) sizeof(struct seid_req)) { error("Too short getconf request"); return FALSE; } memset(buf, 0, sizeof(buf)); stream = find_stream_by_lseid(session, req->acp_seid); if (!stream) { err = AVDTP_BAD_ACP_SEID; goto failed; } if (!stream->caps) { err = AVDTP_UNSUPPORTED_CONFIGURATION; goto failed; } for (l = stream->caps, rsp_size = 0; l != NULL; l = g_slist_next(l)) { struct avdtp_service_capability *cap = l->data; if (rsp_size + cap->length + 2 > (int) sizeof(buf)) break; memcpy(ptr, cap, cap->length + 2); rsp_size += cap->length + 2; ptr += cap->length + 2; } return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, AVDTP_GET_CONFIGURATION, buf, rsp_size); failed: return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, AVDTP_GET_CONFIGURATION, &err, sizeof(err)); } static gboolean avdtp_reconf_cmd(struct avdtp *session, uint8_t transaction, struct seid_req *req, int size) { struct conf_rej rej; rej.error = AVDTP_NOT_SUPPORTED_COMMAND; rej.category = 0x00; return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, AVDTP_RECONFIGURE, &rej, sizeof(rej)); } static void check_seid_collision(struct pending_req *req, uint8_t id) { struct seid_req *seid = req->data; if (seid->acp_seid == id) req->collided = TRUE; } static void check_start_collision(struct pending_req *req, uint8_t id) { struct start_req *start = req->data; int count = 1 + req->data_size - sizeof(struct start_req); int i; for (i = 0; i < count; i++) { struct seid seid = start->seids[i]; if (seid.seid == id) { req->collided = TRUE; return; } } } static void check_suspend_collision(struct pending_req *req, uint8_t id) { struct suspend_req *suspend = req->data; int count = 1 + req->data_size - sizeof(struct suspend_req); int i; for (i = 0; i < count; i++) { struct seid seid = suspend->seids[i]; if (seid.seid == id) { req->collided = TRUE; return; } } } static void avdtp_check_collision(struct avdtp *session, uint8_t cmd, struct avdtp_stream *stream) { struct pending_req *req = session->req; if (req == NULL || (req->signal_id != cmd && cmd != AVDTP_ABORT)) return; if (cmd == AVDTP_ABORT) cmd = req->signal_id; switch (cmd) { case AVDTP_OPEN: case AVDTP_CLOSE: check_seid_collision(req, stream->rseid); break; case AVDTP_START: check_start_collision(req, stream->rseid); break; case AVDTP_SUSPEND: check_suspend_collision(req, stream->rseid); break; } } static gboolean avdtp_open_cmd(struct avdtp *session, uint8_t transaction, struct seid_req *req, unsigned int size) { struct avdtp_local_sep *sep; struct avdtp_stream *stream; uint8_t err; if (size < sizeof(struct seid_req)) { error("Too short abort request"); return FALSE; } sep = find_local_sep_by_seid(session, req->acp_seid); if (!sep) { err = AVDTP_BAD_ACP_SEID; goto failed; } stream = find_stream_by_lsep(session, sep); if (!stream || stream->state != AVDTP_STATE_CONFIGURED) { err = AVDTP_BAD_STATE; goto failed; } /* Check if the stream is pending and there is an IO set already */ if (stream == session->pending_open && session->pending_open_io) { handle_transport_connect(session, session->pending_open_io, stream->imtu, stream->omtu); return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, AVDTP_OPEN, NULL, 0); } if (sep->ind && sep->ind->open && !session->pending_open) { if (!sep->ind->open(session, sep, stream, &err, sep->user_data)) goto failed; } avdtp_check_collision(session, AVDTP_OPEN, stream); if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, AVDTP_OPEN, NULL, 0)) return FALSE; if (!session->pending_open) stream_set_pending_open(stream, NULL); return TRUE; failed: return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, AVDTP_OPEN, &err, sizeof(err)); } static gboolean avdtp_start_cmd(struct avdtp *session, uint8_t transaction, struct start_req *req, unsigned int size) { struct avdtp_local_sep *sep; struct avdtp_stream *stream; struct stream_rej rej; uint8_t err, failed_seid; int seid_count, i; if (size < sizeof(struct start_req)) { error("Too short start request"); return FALSE; } seid_count = 1 + size - sizeof(struct start_req); for (i = 0; i < seid_count; i++) { struct seid seid = req->seids[i]; failed_seid = seid.seid; stream = find_stream_by_lseid(session, seid.seid); if (!stream) { err = AVDTP_BAD_ACP_SEID; goto failed; } /* Also reject start cmd if state is not open */ if (stream->state != AVDTP_STATE_OPEN) { err = AVDTP_BAD_STATE; goto failed; } sep = stream->lsep; stream->starting = TRUE; if (sep->ind && sep->ind->start) { if (!sep->ind->start(session, sep, stream, &err, sep->user_data)) goto failed; } avdtp_check_collision(session, AVDTP_START, stream); avdtp_stream_set_state(stream, AVDTP_STATE_STREAMING); } return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, AVDTP_START, NULL, 0); failed: DBG("Rejecting (%d)", err); memset(&rej, 0, sizeof(rej)); rej.acp_seid = failed_seid; rej.error = err; return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, AVDTP_START, &rej, sizeof(rej)); } static gboolean avdtp_close_cmd(struct avdtp *session, uint8_t transaction, struct seid_req *req, unsigned int size) { struct avdtp_local_sep *sep; struct avdtp_stream *stream; uint8_t err; if (size < sizeof(struct seid_req)) { error("Too short close request"); return FALSE; } stream = find_stream_by_lseid(session, req->acp_seid); if (!stream) { err = AVDTP_BAD_ACP_SEID; goto failed; } if (stream->state != AVDTP_STATE_OPEN && stream->state != AVDTP_STATE_STREAMING) { err = AVDTP_BAD_STATE; goto failed; } sep = stream->lsep; if (sep->ind && sep->ind->close) { if (!sep->ind->close(session, sep, stream, &err, sep->user_data)) goto failed; } avdtp_check_collision(session, AVDTP_CLOSE, stream); avdtp_stream_set_state(stream, AVDTP_STATE_CLOSING); session->dc_timeout = DISCONNECT_TIMEOUT; if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, AVDTP_CLOSE, NULL, 0)) return FALSE; stream->timer = timeout_add_seconds(REQ_TIMEOUT, stream_close_timeout, stream, NULL); return TRUE; failed: return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, AVDTP_CLOSE, &err, sizeof(err)); } static gboolean avdtp_suspend_cmd(struct avdtp *session, uint8_t transaction, struct suspend_req *req, unsigned int size) { struct avdtp_local_sep *sep; struct avdtp_stream *stream; struct stream_rej rej; uint8_t err, failed_seid; int seid_count, i; if (size < sizeof(struct suspend_req)) { error("Too short suspend request"); return FALSE; } seid_count = 1 + size - sizeof(struct suspend_req); for (i = 0; i < seid_count; i++) { struct seid seid = req->seids[i]; failed_seid = seid.seid; stream = find_stream_by_lseid(session, seid.seid); if (!stream) { err = AVDTP_BAD_ACP_SEID; goto failed; } if (stream->state != AVDTP_STATE_STREAMING) { err = AVDTP_BAD_STATE; goto failed; } sep = stream->lsep; if (sep->ind && sep->ind->suspend) { if (!sep->ind->suspend(session, sep, stream, &err, sep->user_data)) goto failed; } avdtp_check_collision(session, AVDTP_SUSPEND, stream); avdtp_stream_set_state(stream, AVDTP_STATE_OPEN); } return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, AVDTP_SUSPEND, NULL, 0); failed: memset(&rej, 0, sizeof(rej)); rej.acp_seid = failed_seid; rej.error = err; return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, AVDTP_SUSPEND, &rej, sizeof(rej)); } static gboolean avdtp_abort_cmd(struct avdtp *session, uint8_t transaction, struct seid_req *req, unsigned int size) { struct avdtp_local_sep *sep; struct avdtp_stream *stream; uint8_t err; gboolean ret; if (size < sizeof(struct seid_req)) { error("Too short abort request"); return FALSE; } stream = find_stream_by_lseid(session, req->acp_seid); if (!stream) return TRUE; sep = stream->lsep; if (sep->ind && sep->ind->abort) sep->ind->abort(session, sep, stream, &err, sep->user_data); avdtp_check_collision(session, AVDTP_ABORT, stream); ret = avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, AVDTP_ABORT, NULL, 0); if (ret) { avdtp_stream_set_state(stream, AVDTP_STATE_ABORTING); session->dc_timeout = DISCONNECT_TIMEOUT; } return ret; } static gboolean avdtp_secctl_cmd(struct avdtp *session, uint8_t transaction, struct seid_req *req, int size) { return avdtp_unknown_cmd(session, transaction, AVDTP_SECURITY_CONTROL); } static gboolean avdtp_delayreport_cmd(struct avdtp *session, uint8_t transaction, struct delay_req *req, unsigned int size) { struct avdtp_local_sep *sep; struct avdtp_stream *stream; uint8_t err; if (size < sizeof(struct delay_req)) { error("Too short delay report request"); return FALSE; } stream = find_stream_by_lseid(session, req->acp_seid); if (!stream) { err = AVDTP_BAD_ACP_SEID; goto failed; } if (stream->state != AVDTP_STATE_CONFIGURED && stream->state != AVDTP_STATE_OPEN && stream->state != AVDTP_STATE_STREAMING) { err = AVDTP_BAD_STATE; goto failed; } sep = stream->lsep; stream->delay = ntohs(req->delay); if (sep->ind && sep->ind->delayreport) { if (!sep->ind->delayreport(session, sep, stream->rseid, stream->delay, &err, sep->user_data)) goto failed; } return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, AVDTP_DELAY_REPORT, NULL, 0); failed: return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, AVDTP_DELAY_REPORT, &err, sizeof(err)); } static gboolean avdtp_parse_cmd(struct avdtp *session, uint8_t transaction, uint8_t signal_id, void *buf, int size) { /* Reset disconnect timer if command is received */ if (session->dc_timer) { timeout_remove(session->dc_timer); session->dc_timer = timeout_add_seconds(session->dc_timeout, disconnect_timeout, session, NULL); } switch (signal_id) { case AVDTP_DISCOVER: DBG("Received DISCOVER_CMD"); return avdtp_discover_cmd(session, transaction, buf, size); case AVDTP_GET_CAPABILITIES: DBG("Received GET_CAPABILITIES_CMD"); return avdtp_getcap_cmd(session, transaction, buf, size, FALSE); case AVDTP_GET_ALL_CAPABILITIES: DBG("Received GET_ALL_CAPABILITIES_CMD"); return avdtp_getcap_cmd(session, transaction, buf, size, TRUE); case AVDTP_SET_CONFIGURATION: DBG("Received SET_CONFIGURATION_CMD"); return avdtp_setconf_cmd(session, transaction, buf, size); case AVDTP_GET_CONFIGURATION: DBG("Received GET_CONFIGURATION_CMD"); return avdtp_getconf_cmd(session, transaction, buf, size); case AVDTP_RECONFIGURE: DBG("Received RECONFIGURE_CMD"); return avdtp_reconf_cmd(session, transaction, buf, size); case AVDTP_OPEN: DBG("Received OPEN_CMD"); return avdtp_open_cmd(session, transaction, buf, size); case AVDTP_START: DBG("Received START_CMD"); return avdtp_start_cmd(session, transaction, buf, size); case AVDTP_CLOSE: DBG("Received CLOSE_CMD"); return avdtp_close_cmd(session, transaction, buf, size); case AVDTP_SUSPEND: DBG("Received SUSPEND_CMD"); return avdtp_suspend_cmd(session, transaction, buf, size); case AVDTP_ABORT: DBG("Received ABORT_CMD"); return avdtp_abort_cmd(session, transaction, buf, size); case AVDTP_SECURITY_CONTROL: DBG("Received SECURITY_CONTROL_CMD"); return avdtp_secctl_cmd(session, transaction, buf, size); case AVDTP_DELAY_REPORT: DBG("Received DELAY_REPORT_CMD"); return avdtp_delayreport_cmd(session, transaction, buf, size); default: DBG("Received unknown request id %u", signal_id); return avdtp_unknown_cmd(session, transaction, signal_id); } } enum avdtp_parse_result { PARSE_ERROR, PARSE_FRAGMENT, PARSE_SUCCESS }; static enum avdtp_parse_result avdtp_parse_data(struct avdtp *session, void *buf, size_t size) { struct avdtp_common_header *header = buf; struct avdtp_single_header *single = (void *) session->buf; struct avdtp_start_header *start = (void *) session->buf; void *payload; gsize payload_size; struct in_buf *in; if (header->message_type == AVDTP_MSG_TYPE_COMMAND) in = &session->in_cmd; else in = &session->in_resp; switch (header->packet_type) { case AVDTP_PKT_TYPE_SINGLE: if (size < sizeof(*single)) { error("Received too small single packet (%zu bytes)", size); return PARSE_ERROR; } if (in->active) { error("SINGLE: Invalid AVDTP packet fragmentation"); return PARSE_ERROR; } payload = session->buf + sizeof(*single); payload_size = size - sizeof(*single); in->active = TRUE; in->data_size = 0; in->no_of_packets = 1; in->transaction = header->transaction; in->signal_id = single->signal_id; break; case AVDTP_PKT_TYPE_START: if (size < sizeof(*start)) { error("Received too small start packet (%zu bytes)", size); return PARSE_ERROR; } if (in->active) { error("START: Invalid AVDTP packet fragmentation"); return PARSE_ERROR; } in->active = TRUE; in->data_size = 0; in->transaction = header->transaction; in->no_of_packets = start->no_of_packets; in->signal_id = start->signal_id; payload = session->buf + sizeof(*start); payload_size = size - sizeof(*start); break; case AVDTP_PKT_TYPE_CONTINUE: if (size < sizeof(struct avdtp_continue_header)) { error("Received too small continue packet (%zu bytes)", size); return PARSE_ERROR; } if (!in->active) { error("CONTINUE: Invalid AVDTP packet fragmentation"); return PARSE_ERROR; } if (in->transaction != header->transaction) { error("Continue transaction id doesn't match"); return PARSE_ERROR; } if (in->no_of_packets <= 1) { error("Too few continue packets"); return PARSE_ERROR; } payload = session->buf + sizeof(struct avdtp_continue_header); payload_size = size - sizeof(struct avdtp_continue_header); break; case AVDTP_PKT_TYPE_END: if (size < sizeof(struct avdtp_continue_header)) { error("Received too small end packet (%zu bytes)", size); return PARSE_ERROR; } if (!in->active) { error("END: Invalid AVDTP packet fragmentation"); return PARSE_ERROR; } if (in->transaction != header->transaction) { error("End transaction id doesn't match"); return PARSE_ERROR; } if (in->no_of_packets > 1) { error("Got an end packet too early"); return PARSE_ERROR; } payload = session->buf + sizeof(struct avdtp_continue_header); payload_size = size - sizeof(struct avdtp_continue_header); break; default: error("Invalid AVDTP packet type 0x%02X", header->packet_type); return PARSE_ERROR; } if (in->data_size + payload_size > sizeof(in->buf)) { error("Not enough incoming buffer space!"); return PARSE_ERROR; } memcpy(in->buf + in->data_size, payload, payload_size); in->data_size += payload_size; if (in->no_of_packets > 1) { in->no_of_packets--; DBG("Received AVDTP fragment. %d to go", in->no_of_packets); return PARSE_FRAGMENT; } in->active = FALSE; return PARSE_SUCCESS; } static gboolean session_cb(GIOChannel *chan, GIOCondition cond, gpointer data) { struct avdtp *session = data; struct avdtp_common_header *header; ssize_t size; int fd; DBG(""); if (cond & G_IO_NVAL) return FALSE; header = (void *) session->buf; if (cond & (G_IO_HUP | G_IO_ERR)) goto failed; fd = g_io_channel_unix_get_fd(chan); size = read(fd, session->buf, session->imtu); if (size < 0) { error("IO Channel read error"); goto failed; } if ((size_t) size < sizeof(struct avdtp_common_header)) { error("Received too small packet (%zu bytes)", size); goto failed; } switch (avdtp_parse_data(session, session->buf, size)) { case PARSE_ERROR: goto failed; case PARSE_FRAGMENT: return TRUE; case PARSE_SUCCESS: break; } if (header->message_type == AVDTP_MSG_TYPE_COMMAND) { if (!avdtp_parse_cmd(session, session->in_cmd.transaction, session->in_cmd.signal_id, session->in_cmd.buf, session->in_cmd.data_size)) { error("Unable to handle command. Disconnecting"); goto failed; } if (session->req && session->req->collided) { DBG("Collision detected"); goto next; } return TRUE; } if (session->req == NULL) { error("No pending request, ignoring message"); return TRUE; } if (header->transaction != session->req->transaction) { error("Transaction label doesn't match"); return TRUE; } if (session->in_resp.signal_id != session->req->signal_id) { error("Response signal doesn't match"); return TRUE; } timeout_remove(session->req->timeout); session->req->timeout = 0; switch (header->message_type) { case AVDTP_MSG_TYPE_ACCEPT: if (!avdtp_parse_resp(session, session->req->stream, session->in_resp.transaction, session->in_resp.signal_id, session->in_resp.buf, session->in_resp.data_size)) { error("Unable to parse accept response"); goto failed; } break; case AVDTP_MSG_TYPE_REJECT: if (!avdtp_parse_rej(session, session->req->stream, session->in_resp.transaction, session->in_resp.signal_id, session->in_resp.buf, session->in_resp.data_size)) { error("Unable to parse reject response"); goto failed; } break; case AVDTP_MSG_TYPE_GEN_REJECT: error("Received a General Reject message"); break; default: error("Unknown message type 0x%02X", header->message_type); break; } next: pending_req_free(session->req); session->req = NULL; process_queue(session); return TRUE; failed: connection_lost(session, EIO); return FALSE; } static uint16_t get_version(struct avdtp *session) { const sdp_record_t *rec; sdp_list_t *protos; sdp_data_t *proto_desc; uint16_t ver = 0x0000; rec = btd_device_get_record(session->device, A2DP_SINK_UUID); if (!rec) rec = btd_device_get_record(session->device, A2DP_SOURCE_UUID); if (!rec) return ver; if (sdp_get_access_protos(rec, &protos) < 0) return ver; proto_desc = sdp_get_proto_desc(protos, AVDTP_UUID); if (proto_desc && proto_desc->dtd == SDP_UINT16) ver = proto_desc->val.uint16; sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); sdp_list_free(protos, NULL); return ver; } static void avdtp_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) { struct avdtp *session = user_data; char address[18]; int err_no = EIO; if (err) { err_no = err->code; error("%s", err->message); goto failed; } if (!session->io) session->io = g_io_channel_ref(chan); /* Check if kernel supports reading packet types */ bt_io_get(chan, NULL, BT_IO_OPT_PHY, &session->phy, BT_IO_OPT_INVALID); bt_io_get(chan, &err, BT_IO_OPT_OMTU, &session->omtu, BT_IO_OPT_IMTU, &session->imtu, BT_IO_OPT_INVALID); if (err) { err_no = err->code; error("%s", err->message); goto failed; } ba2str(device_get_address(session->device), address); DBG("AVDTP: connected %s channel to %s", session->pending_open ? "transport" : "signaling", address); if (session->state == AVDTP_SESSION_STATE_CONNECTING) { DBG("AVDTP imtu=%u, omtu=%u", session->imtu, session->omtu); session->buf = g_malloc0(MAX(session->imtu, session->omtu)); avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTED); if (session->io_id) g_source_remove(session->io_id); session->io_id = g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, (GIOFunc) session_cb, session); if (session->stream_setup) set_disconnect_timer(session); } else if (session->pending_open) handle_transport_connect(session, chan, session->imtu, session->omtu); else goto failed; process_queue(session); return; failed: if (session->pending_open) { struct avdtp_stream *stream = session->pending_open; handle_transport_connect(session, NULL, 0, 0); if (avdtp_abort(session, stream) < 0) avdtp_stream_set_state(stream, AVDTP_STATE_IDLE); } else connection_lost(session, err_no); } struct avdtp *avdtp_new(GIOChannel *chan, struct btd_device *device, struct queue *lseps) { struct avdtp *session; session = g_new0(struct avdtp, 1); session->device = btd_device_ref(device); /* We don't use avdtp_set_state() here since this isn't a state change * but just setting of the initial state */ session->state = AVDTP_SESSION_STATE_DISCONNECTED; session->lseps = lseps; session->version = get_version(session); if (!chan) return session; avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTING); btd_device_add_uuid(device, ADVANCED_AUDIO_UUID); session->io = g_io_channel_ref(chan); session->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL, (GIOFunc) session_cb, session); /* This is so that avdtp_connect_cb will know to do the right thing * with respect to the disconnect timer */ session->stream_setup = TRUE; session->dc_timeout = DISCONNECT_TIMEOUT; avdtp_connect_cb(chan, NULL, session); return session; } uint16_t avdtp_get_version(struct avdtp *session) { return session->version; } static GIOChannel *l2cap_connect(struct avdtp *session, BtIOMode mode) { GError *err = NULL; GIOChannel *io; const bdaddr_t *src; src = btd_adapter_get_address(device_get_adapter(session->device)); if (session->phy) io = bt_io_connect(avdtp_connect_cb, session, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_DEST_BDADDR, device_get_address(session->device), BT_IO_OPT_PSM, AVDTP_PSM, BT_IO_OPT_MODE, mode, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, /* Set Input MTU to 0 to auto-tune */ BT_IO_OPT_IMTU, 0, BT_IO_OPT_INVALID); else io = bt_io_connect(avdtp_connect_cb, session, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_DEST_BDADDR, device_get_address(session->device), BT_IO_OPT_PSM, AVDTP_PSM, BT_IO_OPT_MODE, mode, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_INVALID); if (!io) { error("%s", err->message); g_error_free(err); return NULL; } return io; } static void queue_request(struct avdtp *session, struct pending_req *req, gboolean priority) { if (priority) session->prio_queue = g_slist_append(session->prio_queue, req); else session->req_queue = g_slist_append(session->req_queue, req); } static uint8_t req_get_seid(struct pending_req *req) { if (req->signal_id == AVDTP_DISCOVER) return 0; return ((struct seid_req *) (req->data))->acp_seid; } static int cancel_request(struct avdtp *session, int err) { struct pending_req *req; struct seid_req sreq; struct avdtp_local_sep *lsep; struct avdtp_stream *stream; uint8_t seid; struct avdtp_error averr; req = session->req; session->req = NULL; avdtp_error_init(&averr, AVDTP_ERRNO, err); seid = req_get_seid(req); if (seid) stream = find_stream_by_rseid(session, seid); else stream = NULL; if (stream) lsep = stream->lsep; else lsep = NULL; switch (req->signal_id) { case AVDTP_RECONFIGURE: error("Reconfigure: %s (%d)", strerror(err), err); if (lsep && lsep->cfm && lsep->cfm->reconfigure) lsep->cfm->reconfigure(session, lsep, stream, &averr, lsep->user_data); break; case AVDTP_OPEN: error("Open: %s (%d)", strerror(err), err); if (lsep && lsep->cfm && lsep->cfm->open) lsep->cfm->open(session, lsep, stream, &averr, lsep->user_data); break; case AVDTP_START: error("Start: %s (%d)", strerror(err), err); if (lsep && lsep->cfm && lsep->cfm->start) { lsep->cfm->start(session, lsep, stream, &averr, lsep->user_data); if (stream) stream->starting = FALSE; } break; case AVDTP_SUSPEND: error("Suspend: %s (%d)", strerror(err), err); if (lsep && lsep->cfm && lsep->cfm->suspend) lsep->cfm->suspend(session, lsep, stream, &averr, lsep->user_data); break; case AVDTP_CLOSE: error("Close: %s (%d)", strerror(err), err); if (lsep && lsep->cfm && lsep->cfm->close) { lsep->cfm->close(session, lsep, stream, &averr, lsep->user_data); if (stream) stream->close_int = FALSE; } break; case AVDTP_SET_CONFIGURATION: error("SetConfiguration: %s (%d)", strerror(err), err); if (lsep && lsep->cfm && lsep->cfm->set_configuration) lsep->cfm->set_configuration(session, lsep, stream, &averr, lsep->user_data); break; case AVDTP_DISCOVER: error("Discover: %s (%d)", strerror(err), err); goto failed; case AVDTP_GET_CAPABILITIES: error("GetCapabilities: %s (%d)", strerror(err), err); goto failed; case AVDTP_ABORT: error("Abort: %s (%d)", strerror(err), err); goto failed; } if (!stream) goto failed; memset(&sreq, 0, sizeof(sreq)); sreq.acp_seid = seid; err = send_request(session, TRUE, stream, AVDTP_ABORT, &sreq, sizeof(sreq)); if (err < 0) { error("Unable to send abort request"); goto failed; } stream->abort_int = TRUE; goto done; failed: connection_lost(session, err); done: pending_req_free(req); return err; } static bool request_timeout(gpointer user_data) { struct avdtp *session = user_data; cancel_request(session, ETIMEDOUT); return FALSE; } static int send_req(struct avdtp *session, gboolean priority, struct pending_req *req) { static int transaction = 0; int err, timeout; if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) { BtIOMode mode = btd_opts.avdtp.session_mode; session->io = l2cap_connect(session, mode); if (!session->io) { /* Report disconnection anyways, as the other layers * are using this state for cleanup. */ avdtp_set_state(session, AVDTP_SESSION_STATE_DISCONNECTED); err = -EIO; goto failed; } avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTING); } if (session->state < AVDTP_SESSION_STATE_CONNECTED || session->req != NULL) { queue_request(session, req, priority); return 0; } req->transaction = transaction++; transaction %= 16; /* FIXME: Should we retry to send if the buffer was not totally sent or in case of EINTR? */ if (!avdtp_send(session, req->transaction, AVDTP_MSG_TYPE_COMMAND, req->signal_id, req->data, req->data_size)) { err = -EIO; goto failed; } session->req = req; switch (req->signal_id) { case AVDTP_ABORT: timeout = ABORT_TIMEOUT; break; case AVDTP_SUSPEND: timeout = SUSPEND_TIMEOUT; break; default: timeout = REQ_TIMEOUT; } req->timeout = timeout_add_seconds(timeout, request_timeout, session, NULL); return 0; failed: free(req->data); g_free(req); return err; } static int send_request(struct avdtp *session, gboolean priority, struct avdtp_stream *stream, uint8_t signal_id, void *buffer, size_t size) { struct pending_req *req; if (stream && stream->abort_int && signal_id != AVDTP_ABORT) { DBG("Unable to send requests while aborting"); return -EINVAL; } req = g_new0(struct pending_req, 1); req->signal_id = signal_id; req->data = util_memdup(buffer, size); req->data_size = size; req->stream = stream; return send_req(session, priority, req); } static gboolean avdtp_discover_resp(struct avdtp *session, struct discover_resp *resp, int size) { int sep_count, i; uint8_t getcap_cmd; int ret = 0; gboolean getcap_pending = FALSE; if (session->version >= 0x0103) getcap_cmd = AVDTP_GET_ALL_CAPABILITIES; else getcap_cmd = AVDTP_GET_CAPABILITIES; sep_count = size / sizeof(struct seid_info); for (i = 0; i < sep_count; i++) { struct avdtp_remote_sep *sep; struct avdtp_stream *stream; struct seid_req req; DBG("seid %d type %d media %d in use %d", resp->seps[i].seid, resp->seps[i].type, resp->seps[i].media_type, resp->seps[i].inuse); stream = find_stream_by_rseid(session, resp->seps[i].seid); sep = find_remote_sep(session->seps, resp->seps[i].seid); if (sep && sep->type == resp->seps[i].type && sep->media_type == resp->seps[i].media_type && sep->codec) { sep->discovered = true; continue; } if (resp->seps[i].inuse && !stream) continue; sep = g_new0(struct avdtp_remote_sep, 1); session->seps = g_slist_append(session->seps, sep); sep->stream = stream; sep->seid = resp->seps[i].seid; sep->type = resp->seps[i].type; sep->media_type = resp->seps[i].media_type; sep->discovered = true; memset(&req, 0, sizeof(req)); req.acp_seid = sep->seid; ret = send_request(session, TRUE, NULL, getcap_cmd, &req, sizeof(req)); if (ret < 0) { session->seps = g_slist_remove(session->seps, sep); g_free(sep); break; } getcap_pending = TRUE; } if (!getcap_pending) finalize_discovery(session, -ret); return TRUE; } static gboolean avdtp_get_capabilities_resp(struct avdtp *session, struct getcap_resp *resp, unsigned int size) { struct avdtp_remote_sep *sep; uint8_t seid; /* Check for minimum required packet size includes: * 1. getcap resp header * 2. media transport capability (2 bytes) * 3. media codec capability type + length (2 bytes) * 4. the actual media codec elements * */ if (size < (sizeof(struct getcap_resp) + 4 + sizeof(struct avdtp_media_codec_capability))) { error("Too short getcap resp packet"); return FALSE; } seid = ((struct seid_req *) session->req->data)->acp_seid; sep = find_remote_sep(session->seps, seid); DBG("seid %d type %d media %d", sep->seid, sep->type, sep->media_type); if (sep->caps) { g_slist_free_full(sep->caps, g_free); sep->caps = NULL; sep->codec = NULL; sep->delay_reporting = FALSE; } sep->caps = caps_to_list(resp->caps, size - sizeof(struct getcap_resp), &sep->codec, &sep->delay_reporting, NULL); return TRUE; } static gboolean avdtp_set_configuration_resp(struct avdtp *session, struct avdtp_stream *stream, struct avdtp_single_header *resp, int size) { struct avdtp_local_sep *sep = stream->lsep; if (sep->cfm && sep->cfm->set_configuration) sep->cfm->set_configuration(session, sep, stream, NULL, sep->user_data); avdtp_stream_set_state(stream, AVDTP_STATE_CONFIGURED); return TRUE; } static gboolean avdtp_reconfigure_resp(struct avdtp *session, struct avdtp_stream *stream, struct avdtp_single_header *resp, int size) { return TRUE; } static gboolean avdtp_open_resp(struct avdtp *session, struct avdtp_stream *stream, struct seid_rej *resp, int size) { BtIOMode mode = btd_opts.avdtp.stream_mode; stream->io = l2cap_connect(session, mode); if (!stream->io) { avdtp_stream_set_state(stream, AVDTP_STATE_IDLE); return FALSE; } session->pending_open = stream; return TRUE; } static gboolean avdtp_start_resp(struct avdtp *session, struct avdtp_stream *stream, struct seid_rej *resp, int size) { struct avdtp_local_sep *sep = stream->lsep; if (sep->cfm && sep->cfm->start) sep->cfm->start(session, sep, stream, NULL, sep->user_data); /* We might be in STREAMING already if both sides send START_CMD at the * same time and the one in SNK role doesn't reject it as it should */ if (stream->state != AVDTP_STATE_STREAMING) avdtp_stream_set_state(stream, AVDTP_STATE_STREAMING); return TRUE; } static gboolean avdtp_close_resp(struct avdtp *session, struct avdtp_stream *stream, struct seid_rej *resp, int size) { avdtp_stream_set_state(stream, AVDTP_STATE_CLOSING); /* Delay CLOSING->IDLE until remote acknowledges L2CAP channel closure. * * It is not explicitly stated in AVDTP v1.3 Sec. 6.13, but some devices * refuse commands sent immediately after L2CAP Disconnect Req, so wait * until Rsp. */ if (stream->io) shutdown(g_io_channel_unix_get_fd(stream->io), SHUT_WR); return TRUE; } static gboolean avdtp_suspend_resp(struct avdtp *session, struct avdtp_stream *stream, void *data, int size) { struct avdtp_local_sep *sep = stream->lsep; avdtp_stream_set_state(stream, AVDTP_STATE_OPEN); if (sep->cfm && sep->cfm->suspend) sep->cfm->suspend(session, sep, stream, NULL, sep->user_data); return TRUE; } static gboolean avdtp_abort_resp(struct avdtp *session, struct avdtp_stream *stream, struct seid_rej *resp, int size) { struct avdtp_local_sep *sep = stream->lsep; avdtp_stream_set_state(stream, AVDTP_STATE_ABORTING); if (sep->cfm && sep->cfm->abort) sep->cfm->abort(session, sep, stream, NULL, sep->user_data); avdtp_stream_set_state(stream, AVDTP_STATE_IDLE); return TRUE; } static gboolean avdtp_delay_report_resp(struct avdtp *session, struct avdtp_stream *stream, void *data, int size) { struct avdtp_local_sep *sep = stream->lsep; if (sep->cfm && sep->cfm->delay_report) sep->cfm->delay_report(session, sep, stream, NULL, sep->user_data); return TRUE; } static gboolean avdtp_parse_resp(struct avdtp *session, struct avdtp_stream *stream, uint8_t transaction, uint8_t signal_id, void *buf, int size) { struct pending_req *next; const char *get_all = ""; if (session->prio_queue) next = session->prio_queue->data; else if (session->req_queue) next = session->req_queue->data; else next = NULL; switch (signal_id) { case AVDTP_DISCOVER: DBG("DISCOVER request succeeded"); return avdtp_discover_resp(session, buf, size); case AVDTP_GET_ALL_CAPABILITIES: get_all = "ALL_"; /* fall through */ case AVDTP_GET_CAPABILITIES: DBG("GET_%sCAPABILITIES request succeeded", get_all); if (!avdtp_get_capabilities_resp(session, buf, size)) return FALSE; if (!(next && (next->signal_id == AVDTP_GET_CAPABILITIES || next->signal_id == AVDTP_GET_ALL_CAPABILITIES))) finalize_discovery(session, 0); return TRUE; } /* The remaining commands require an existing stream so bail out * here if the stream got unexpectedly disconnected */ if (!stream) { DBG("AVDTP: stream was closed while waiting for reply"); return TRUE; } switch (signal_id) { case AVDTP_SET_CONFIGURATION: DBG("SET_CONFIGURATION request succeeded"); return avdtp_set_configuration_resp(session, stream, buf, size); case AVDTP_RECONFIGURE: DBG("RECONFIGURE request succeeded"); return avdtp_reconfigure_resp(session, stream, buf, size); case AVDTP_OPEN: DBG("OPEN request succeeded"); return avdtp_open_resp(session, stream, buf, size); case AVDTP_SUSPEND: DBG("SUSPEND request succeeded"); return avdtp_suspend_resp(session, stream, buf, size); case AVDTP_START: DBG("START request succeeded"); return avdtp_start_resp(session, stream, buf, size); case AVDTP_CLOSE: DBG("CLOSE request succeeded"); return avdtp_close_resp(session, stream, buf, size); case AVDTP_ABORT: DBG("ABORT request succeeded"); return avdtp_abort_resp(session, stream, buf, size); case AVDTP_DELAY_REPORT: DBG("DELAY_REPORT request succeeded"); return avdtp_delay_report_resp(session, stream, buf, size); } error("Unknown signal id in accept response: %u", signal_id); return TRUE; } static gboolean seid_rej_to_err(struct seid_rej *rej, unsigned int size, struct avdtp_error *err) { if (size < sizeof(struct seid_rej)) { error("Too small packet for seid_rej"); return FALSE; } avdtp_error_init(err, 0x00, rej->error); return TRUE; } static gboolean conf_rej_to_err(struct conf_rej *rej, unsigned int size, struct avdtp_error *err) { if (size < sizeof(struct conf_rej)) { error("Too small packet for conf_rej"); return FALSE; } avdtp_error_init(err, rej->category, rej->error); return TRUE; } static gboolean stream_rej_to_err(struct stream_rej *rej, unsigned int size, struct avdtp_error *err, uint8_t *acp_seid) { if (size < sizeof(struct stream_rej)) { error("Too small packet for stream_rej"); return FALSE; } avdtp_error_init(err, 0x00, rej->error); if (acp_seid) *acp_seid = rej->acp_seid; return TRUE; } static gboolean avdtp_parse_rej(struct avdtp *session, struct avdtp_stream *stream, uint8_t transaction, uint8_t signal_id, void *buf, int size) { struct avdtp_error err; uint8_t acp_seid; struct avdtp_local_sep *sep = stream ? stream->lsep : NULL; switch (signal_id) { case AVDTP_DISCOVER: if (!seid_rej_to_err(buf, size, &err)) return FALSE; error("DISCOVER request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); return TRUE; case AVDTP_GET_CAPABILITIES: case AVDTP_GET_ALL_CAPABILITIES: if (!seid_rej_to_err(buf, size, &err)) return FALSE; error("GET_CAPABILITIES request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); return TRUE; case AVDTP_OPEN: if (!seid_rej_to_err(buf, size, &err)) return FALSE; error("OPEN request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); if (sep && sep->cfm && sep->cfm->open) sep->cfm->open(session, sep, stream, &err, sep->user_data); return TRUE; case AVDTP_SET_CONFIGURATION: if (!conf_rej_to_err(buf, size, &err)) return FALSE; error("SET_CONFIGURATION request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); if (sep && sep->cfm && sep->cfm->set_configuration) sep->cfm->set_configuration(session, sep, stream, &err, sep->user_data); return TRUE; case AVDTP_RECONFIGURE: if (!conf_rej_to_err(buf, size, &err)) return FALSE; error("RECONFIGURE request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); if (sep && sep->cfm && sep->cfm->reconfigure) sep->cfm->reconfigure(session, sep, stream, &err, sep->user_data); return TRUE; case AVDTP_START: if (!stream_rej_to_err(buf, size, &err, &acp_seid)) return FALSE; error("START request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); if (sep && sep->cfm && sep->cfm->start) { sep->cfm->start(session, sep, stream, &err, sep->user_data); stream->starting = FALSE; } return TRUE; case AVDTP_SUSPEND: if (!stream_rej_to_err(buf, size, &err, &acp_seid)) return FALSE; error("SUSPEND request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); if (sep && sep->cfm && sep->cfm->suspend) sep->cfm->suspend(session, sep, stream, &err, sep->user_data); return TRUE; case AVDTP_CLOSE: if (!stream_rej_to_err(buf, size, &err, &acp_seid)) return FALSE; error("CLOSE request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); if (sep && sep->cfm && sep->cfm->close) { sep->cfm->close(session, sep, stream, &err, sep->user_data); stream->close_int = FALSE; } return TRUE; case AVDTP_ABORT: if (!stream_rej_to_err(buf, size, &err, &acp_seid)) return FALSE; error("ABORT request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); if (sep && sep->cfm && sep->cfm->abort) sep->cfm->abort(session, sep, stream, &err, sep->user_data); return FALSE; case AVDTP_DELAY_REPORT: if (!stream_rej_to_err(buf, size, &err, &acp_seid)) return FALSE; error("DELAY_REPORT request rejected: %s (%d)", avdtp_strerror(&err), err.err.error_code); if (sep && sep->cfm && sep->cfm->delay_report) sep->cfm->delay_report(session, sep, stream, &err, sep->user_data); return TRUE; default: error("Unknown reject response signal id: %u", signal_id); return TRUE; } } struct avdtp_service_capability *avdtp_stream_get_codec( struct avdtp_stream *stream) { GSList *l; for (l = stream->caps; l; l = l->next) { struct avdtp_service_capability *cap = l->data; if (cap->category == AVDTP_MEDIA_CODEC) return cap; } return NULL; } static gboolean avdtp_stream_has_capability(struct avdtp_stream *stream, struct avdtp_service_capability *cap) { GSList *l; struct avdtp_service_capability *stream_cap; for (l = stream->caps; l; l = g_slist_next(l)) { stream_cap = l->data; if (stream_cap->category != cap->category || stream_cap->length != cap->length) continue; if (memcmp(stream_cap->data, cap->data, cap->length) == 0) return TRUE; } return FALSE; } gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream, GSList *caps) { for (; caps; caps = g_slist_next(caps)) { struct avdtp_service_capability *cap = caps->data; if (!avdtp_stream_has_capability(stream, cap)) return FALSE; } return TRUE; } gboolean avdtp_stream_has_delay_reporting(struct avdtp_stream *stream) { return stream->delay_reporting; } struct avdtp_remote_sep *avdtp_stream_get_remote_sep( struct avdtp_stream *stream) { GSList *l; for (l = stream->session->seps; l; l = l->next) { struct avdtp_remote_sep *sep = l->data; if (sep->seid == stream->rseid) return sep; } return NULL; } gboolean avdtp_stream_set_transport(struct avdtp_stream *stream, int fd, size_t imtu, size_t omtu) { GIOChannel *io = g_io_channel_unix_new(fd); if (stream != stream->session->pending_open) { uint8_t err; if (stream->session->pending_open) goto failed; /* Attempt to Open there is no pending stream set yet */ if (stream->lsep->ind && stream->lsep->ind->open) { if (!stream->lsep->ind->open(stream->session, stream->lsep, stream, &err, stream->lsep->user_data)) goto failed; } stream_set_pending_open(stream, io); stream->imtu = imtu; stream->omtu = omtu; return TRUE; } handle_transport_connect(stream->session, io, imtu, omtu); g_io_channel_unref(io); return TRUE; failed: g_io_channel_unref(io); return FALSE; } gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock, uint16_t *imtu, uint16_t *omtu, GSList **caps) { if (stream->io == NULL) return FALSE; if (sock) *sock = g_io_channel_unix_get_fd(stream->io); if (omtu) *omtu = stream->omtu; if (imtu) *imtu = stream->imtu; if (caps) *caps = stream->caps; return TRUE; } static int process_queue(struct avdtp *session) { GSList **queue, *l; struct pending_req *req; if (session->req) return 0; if (session->prio_queue) queue = &session->prio_queue; else queue = &session->req_queue; if (!*queue) return 0; l = *queue; req = l->data; *queue = g_slist_remove(*queue, req); return send_req(session, FALSE, req); } uint8_t avdtp_get_seid(struct avdtp_remote_sep *sep) { return sep->seid; } uint8_t avdtp_get_type(struct avdtp_remote_sep *sep) { return sep->type; } struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep) { return sep->codec; } bool avdtp_get_delay_reporting(struct avdtp_remote_sep *sep) { return sep->delay_reporting; } struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, void *data, int length) { struct avdtp_service_capability *cap; if (category < AVDTP_MEDIA_TRANSPORT || category > AVDTP_DELAY_REPORTING) return NULL; cap = g_malloc(sizeof(struct avdtp_service_capability) + length); cap->category = category; cap->length = length; if (data) memcpy(cap->data, data, length); return cap; } struct avdtp_remote_sep *avdtp_register_remote_sep(struct avdtp *session, uint8_t seid, uint8_t type, GSList *caps, bool delay_reporting) { struct avdtp_remote_sep *sep; GSList *l; sep = find_remote_sep(session->seps, seid); if (sep) return sep; sep = g_new0(struct avdtp_remote_sep, 1); session->seps = g_slist_append(session->seps, sep); sep->seid = seid; sep->type = type; sep->media_type = AVDTP_MEDIA_TYPE_AUDIO; sep->caps = caps; sep->delay_reporting = delay_reporting; for (l = caps; l; l = g_slist_next(l)) { struct avdtp_service_capability *cap = l->data; if (cap->category == AVDTP_MEDIA_CODEC) sep->codec = cap; } DBG("seid %d type %d media %d delay_reporting %s", sep->seid, sep->type, sep->media_type, sep->delay_reporting ? "true" : "false"); return sep; } int avdtp_unregister_remote_sep(struct avdtp *session, struct avdtp_remote_sep *rsep) { if (!session || !rsep) return -EINVAL; session->seps = g_slist_remove(session->seps, rsep); sep_free(rsep); return 0; } void avdtp_remote_sep_set_destroy(struct avdtp_remote_sep *sep, void *user_data, avdtp_remote_sep_destroy_t destroy) { if (!sep) return; sep->user_data = user_data; sep->destroy = destroy; } static gboolean process_discover(gpointer data) { struct avdtp *session = data; session->discover->id = 0; finalize_discovery(session, 0); return FALSE; } int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb, void *user_data) { int err; if (session->discover) return -EBUSY; session->discover = g_new0(struct discover_callback, 1); if (session->seps) { struct avdtp_remote_sep *sep = session->seps->data; /* Check that SEP have been discovered as it may be loaded from * cache. */ if (sep->discovered) { session->discover->cb = cb; session->discover->user_data = user_data; session->discover->id = g_idle_add(process_discover, session); return 0; } } err = send_request(session, FALSE, NULL, AVDTP_DISCOVER, NULL, 0); if (err == 0) { session->discover->cb = cb; session->discover->user_data = user_data; } else if (session->discover) { g_free(session->discover); session->discover = NULL; } return err; } gboolean avdtp_stream_remove_cb(struct avdtp *session, struct avdtp_stream *stream, unsigned int id) { GSList *l; struct stream_callback *cb; if (!stream) return FALSE; for (cb = NULL, l = stream->callbacks; l != NULL; l = l->next) { struct stream_callback *tmp = l->data; if (tmp && tmp->id == id) { cb = tmp; break; } } if (!cb) return FALSE; stream->callbacks = g_slist_remove(stream->callbacks, cb); g_free(cb); return TRUE; } unsigned int avdtp_stream_add_cb(struct avdtp *session, struct avdtp_stream *stream, avdtp_stream_state_cb cb, void *data) { struct stream_callback *stream_cb; static unsigned int id = 0; stream_cb = g_new(struct stream_callback, 1); stream_cb->cb = cb; stream_cb->user_data = data; stream_cb->id = ++id; stream->callbacks = g_slist_append(stream->callbacks, stream_cb); return stream_cb->id; } int avdtp_get_configuration(struct avdtp *session, struct avdtp_stream *stream) { struct seid_req req; if (session->state < AVDTP_SESSION_STATE_CONNECTED) return -EINVAL; memset(&req, 0, sizeof(req)); req.acp_seid = stream->rseid; return send_request(session, FALSE, stream, AVDTP_GET_CONFIGURATION, &req, sizeof(req)); } static void copy_capabilities(gpointer data, gpointer user_data) { struct avdtp_service_capability *src_cap = data; struct avdtp_service_capability *dst_cap; GSList **l = user_data; dst_cap = avdtp_service_cap_new(src_cap->category, src_cap->data, src_cap->length); *l = g_slist_append(*l, dst_cap); } int avdtp_set_configuration(struct avdtp *session, struct avdtp_remote_sep *rsep, struct avdtp_local_sep *lsep, GSList *caps, struct avdtp_stream **stream) { struct setconf_req *req; struct avdtp_stream *new_stream; unsigned char *ptr; int err, caps_len; struct avdtp_service_capability *cap; GSList *l; if (session->state != AVDTP_SESSION_STATE_CONNECTED) return -ENOTCONN; if (!(lsep && rsep)) return -EINVAL; DBG("%p: int_seid=%u, acp_seid=%u", session, lsep->info.seid, rsep->seid); new_stream = stream_new(session, lsep, rsep->seid); if (rsep->delay_reporting && lsep->delay_reporting) { struct avdtp_service_capability *delay_reporting; delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING, NULL, 0); caps = g_slist_append(caps, delay_reporting); new_stream->delay_reporting = TRUE; } g_slist_foreach(caps, copy_capabilities, &new_stream->caps); /* Calculate total size of request */ for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) { cap = l->data; caps_len += cap->length + 2; } req = g_malloc0(sizeof(struct setconf_req) + caps_len); req->int_seid = lsep->info.seid; req->acp_seid = rsep->seid; /* Copy the capabilities into the request */ for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) { cap = l->data; memcpy(ptr, cap, cap->length + 2); ptr += cap->length + 2; } err = send_request(session, FALSE, new_stream, AVDTP_SET_CONFIGURATION, req, sizeof(struct setconf_req) + caps_len); if (err < 0) stream_free(new_stream); else { rsep->stream = new_stream; session->streams = g_slist_append(session->streams, new_stream); if (stream) *stream = new_stream; session->dc_timeout = DISCONNECT_TIMEOUT; } g_free(req); return err; } int avdtp_open(struct avdtp *session, struct avdtp_stream *stream) { struct seid_req req; if (!g_slist_find(session->streams, stream)) return -EINVAL; if (stream->state > AVDTP_STATE_CONFIGURED) return -EINVAL; memset(&req, 0, sizeof(req)); req.acp_seid = stream->rseid; return send_request(session, FALSE, stream, AVDTP_OPEN, &req, sizeof(req)); } static bool start_timeout(gpointer user_data) { struct avdtp_stream *stream = user_data; struct avdtp *session = stream->session; stream->open_acp = FALSE; if (avdtp_start(session, stream) < 0) error("wait_timeout: avdtp_start failed"); stream->start_timer = 0; return FALSE; } int avdtp_start(struct avdtp *session, struct avdtp_stream *stream) { struct start_req req; int ret; if (!g_slist_find(session->streams, stream)) return -EINVAL; if (stream->state != AVDTP_STATE_OPEN) return -EINVAL; /* Recommendation 12: * If the RD has configured and opened a stream it is also responsible * to start the streaming via GAVDP_START. */ if (stream->open_acp) { /* If timer already active wait it */ if (stream->start_timer) return 0; stream->start_timer = timeout_add_seconds(START_TIMEOUT, start_timeout, stream, NULL); return 0; } if (stream->close_int == TRUE) { error("avdtp_start: rejecting start since close is initiated"); return -EINVAL; } if (stream->starting == TRUE) { DBG("stream already started"); return -EINPROGRESS; } memset(&req, 0, sizeof(req)); req.required->seid = stream->rseid; ret = send_request(session, FALSE, stream, AVDTP_START, &req, sizeof(req)); if (ret == 0) stream->starting = TRUE; return ret; } int avdtp_close(struct avdtp *session, struct avdtp_stream *stream, gboolean immediate) { struct seid_req req; int ret; if (!g_slist_find(session->streams, stream)) return -EINVAL; if (stream->state < AVDTP_STATE_OPEN) return -EINVAL; if (stream->close_int == TRUE) { error("avdtp_close: rejecting since close is already initiated"); return -EINVAL; } if (immediate && session->req && stream == session->req->stream) return avdtp_abort(session, stream); memset(&req, 0, sizeof(req)); req.acp_seid = stream->rseid; ret = send_request(session, FALSE, stream, AVDTP_CLOSE, &req, sizeof(req)); if (ret == 0) { stream->close_int = TRUE; session->dc_timeout = 0; } return ret; } int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream) { struct seid_req req; if (!g_slist_find(session->streams, stream)) return -EINVAL; if (stream->state <= AVDTP_STATE_OPEN || stream->close_int) return -EINVAL; memset(&req, 0, sizeof(req)); req.acp_seid = stream->rseid; return send_request(session, FALSE, stream, AVDTP_SUSPEND, &req, sizeof(req)); } int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream) { struct seid_req req; int ret; if (!stream && session->discover) { /* Don't call cb since it being aborted */ session->discover->cb = NULL; finalize_discovery(session, ECANCELED); return -EALREADY; } if (!g_slist_find(session->streams, stream)) return -EINVAL; if (stream->state == AVDTP_STATE_ABORTING) return -EINVAL; avdtp_stream_set_state(stream, AVDTP_STATE_ABORTING); if (session->req && stream == session->req->stream) return cancel_request(session, ECANCELED); memset(&req, 0, sizeof(req)); req.acp_seid = stream->rseid; ret = send_request(session, TRUE, stream, AVDTP_ABORT, &req, sizeof(req)); if (ret == 0) { stream->abort_int = TRUE; session->dc_timeout = 0; } return ret; } int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream, uint16_t delay) { struct delay_req req; if (!g_slist_find(session->streams, stream)) return -EINVAL; if (stream->state != AVDTP_STATE_CONFIGURED && stream->state != AVDTP_STATE_OPEN && stream->state != AVDTP_STATE_STREAMING) return -EINVAL; if (!stream->delay_reporting || session->version < 0x0103) return -ENOTSUP; stream->delay = delay; memset(&req, 0, sizeof(req)); req.acp_seid = stream->rseid; req.delay = htons(delay); return send_request(session, TRUE, stream, AVDTP_DELAY_REPORT, &req, sizeof(req)); } struct avdtp_local_sep *avdtp_register_sep(struct queue *lseps, uint64_t *seid_pool, uint8_t type, uint8_t media_type, uint8_t codec_type, gboolean delay_reporting, struct avdtp_sep_ind *ind, struct avdtp_sep_cfm *cfm, void *user_data) { struct avdtp_local_sep *sep; uint8_t seid = util_get_uid(seid_pool, MAX_SEID); if (!seid) return NULL; sep = g_new0(struct avdtp_local_sep, 1); sep->info.seid = seid; sep->info.type = type; sep->info.media_type = media_type; sep->codec = codec_type; sep->ind = ind; sep->cfm = cfm; sep->user_data = user_data; sep->delay_reporting = delay_reporting; DBG("SEP %p registered: type:%d codec:%d seid_pool:%p seid:%d", sep, sep->info.type, sep->codec, seid_pool, sep->info.seid); if (!queue_push_tail(lseps, sep)) { g_free(sep); util_clear_uid(seid_pool, seid); return NULL; } return sep; } static void release_stream_by_lsep(void *data, void *user_data) { struct avdtp_stream *stream = data; struct avdtp_local_sep *sep = user_data; if (stream->lsep != sep) return; release_stream(stream, stream->session); } int avdtp_unregister_sep(struct queue *lseps, uint64_t *seid_pool, struct avdtp_local_sep *sep) { if (!sep) return -EINVAL; queue_foreach(streams, release_stream_by_lsep, sep); DBG("SEP %p unregistered: type:%d codec:%d seid_pool:%p seid:%d", sep, sep->info.type, sep->codec, seid_pool, sep->info.seid); util_clear_uid(seid_pool, sep->info.seid); queue_remove(lseps, sep); g_free(sep); return 0; } const char *avdtp_strerror(struct avdtp_error *err) { if (err->category == AVDTP_ERRNO) return strerror(err->err.posix_errno); switch(err->err.error_code) { case AVDTP_BAD_HEADER_FORMAT: return "Bad Header Format"; case AVDTP_BAD_LENGTH: return "Bad Packet Length"; case AVDTP_BAD_ACP_SEID: return "Bad Acceptor SEID"; case AVDTP_SEP_IN_USE: return "Stream End Point in Use"; case AVDTP_SEP_NOT_IN_USE: return "Stream End Point Not in Use"; case AVDTP_BAD_SERV_CATEGORY: return "Bad Service Category"; case AVDTP_BAD_PAYLOAD_FORMAT: return "Bad Payload format"; case AVDTP_NOT_SUPPORTED_COMMAND: return "Command Not Supported"; case AVDTP_INVALID_CAPABILITIES: return "Invalid Capabilities"; case AVDTP_BAD_RECOVERY_TYPE: return "Bad Recovery Type"; case AVDTP_BAD_MEDIA_TRANSPORT_FORMAT: return "Bad Media Transport Format"; case AVDTP_BAD_RECOVERY_FORMAT: return "Bad Recovery Format"; case AVDTP_BAD_ROHC_FORMAT: return "Bad Header Compression Format"; case AVDTP_BAD_CP_FORMAT: return "Bad Content Protection Format"; case AVDTP_BAD_MULTIPLEXING_FORMAT: return "Bad Multiplexing Format"; case AVDTP_UNSUPPORTED_CONFIGURATION: return "Configuration not supported"; case AVDTP_BAD_STATE: return "Bad State"; default: return "Unknown error"; } } avdtp_state_t avdtp_stream_get_state(struct avdtp_stream *stream) { if (!stream) return AVDTP_STATE_IDLE; return stream->state; } uint8_t avdtp_sep_get_seid(struct avdtp_local_sep *sep) { return sep->info.seid; } struct btd_adapter *avdtp_get_adapter(struct avdtp *session) { if (!session) return NULL; return device_get_adapter(session->device); } struct btd_device *avdtp_get_device(struct avdtp *session) { if (!session) return NULL; return session->device; } gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream) { if (!session || !stream) return FALSE; return g_slist_find(session->streams, stream) ? TRUE : FALSE; } unsigned int avdtp_add_state_cb(struct btd_device *dev, avdtp_session_state_cb cb, void *user_data) { struct avdtp_state_callback *state_cb; static unsigned int id = 0; state_cb = g_new(struct avdtp_state_callback, 1); state_cb->cb = cb; state_cb->dev = dev; state_cb->id = ++id; state_cb->user_data = user_data; state_callbacks = g_slist_append(state_callbacks, state_cb); return state_cb->id; } gboolean avdtp_remove_state_cb(unsigned int id) { GSList *l; for (l = state_callbacks; l != NULL; l = l->next) { struct avdtp_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; } bluez-5.87/profiles/audio/PaxHeaders/telephony.c0000644000000000000000000000005015142177620016757 xustar0020 atime=1783093718 20 ctime=1783094106 bluez-5.87/profiles/audio/telephony.c0000644000000000000000000005045715142177620016453 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright © 2025 Collabora Ltd. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" #include "bluetooth/uuid.h" #include "gdbus/gdbus.h" #include "btio/btio.h" #include "src/adapter.h" #include "src/btd.h" #include "src/dbus-common.h" #include "src/device.h" #include "src/error.h" #include "src/log.h" #include "src/plugin.h" #include "src/profile.h" #include "src/service.h" #include "src/shared/hfp.h" #include "telephony.h" #define TELEPHONY_INTERFACE "org.bluez.Telephony1" #define TELEPHONY_CALL_INTERFACE "org.bluez.Call1" struct telephony { struct btd_service *service; struct btd_device *device; char *path; bdaddr_t src; bdaddr_t dst; void *profile_data; struct telephony_callbacks *cbs; GSList *uri_schemes; enum connection_state state; bool network_service; uint8_t signal; bool roaming; uint8_t battchg; char *operator_name; bool inband_ringtone; }; static const char *state_to_string(enum connection_state state) { switch (state) { case CONNECTING: return "connecting"; case SESSION_CONNECTING: return "session_connecting"; case CONNECTED: return "connected"; case DISCONNECTING: return "disconnecting"; } return NULL; } static const char *call_state_to_string(enum call_state state) { switch (state) { case CALL_STATE_ACTIVE: return "active"; case CALL_STATE_HELD: return "held"; case CALL_STATE_DIALING: return "dialing"; case CALL_STATE_ALERTING: return "alerting"; case CALL_STATE_INCOMING: return "incoming"; case CALL_STATE_WAITING: return "waiting"; case CALL_STATE_RESPONSE_AND_HOLD: return "response_and_hold"; case CALL_STATE_DISCONNECTED: return "disconnected"; } return NULL; } struct telephony *telephony_new(struct btd_service *service, void *profile_data, struct telephony_callbacks *cbs) { struct btd_device *device = btd_service_get_device(service); const char *path = device_get_path(device); struct btd_adapter *adapter = device_get_adapter(device); struct telephony *ag; static int id; ag = g_new0(struct telephony, 1); bacpy(&ag->src, btd_adapter_get_address(adapter)); bacpy(&ag->dst, device_get_address(device)); ag->service = btd_service_ref(service); ag->device = btd_device_ref(device); ag->path = g_strdup_printf("%s/telephony%u", path, id++); ag->profile_data = profile_data; ag->cbs = cbs; return ag; } void telephony_free(struct telephony *telephony) { btd_service_unref(telephony->service); btd_device_unref(telephony->device); g_free(telephony->operator_name); g_slist_free_full(telephony->uri_schemes, g_free); g_free(telephony->path); g_free(telephony); } static DBusMessage *dial(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct telephony *telephony = user_data; if (telephony->cbs && telephony->cbs->dial) return telephony->cbs->dial(conn, msg, telephony->profile_data); return btd_error_not_supported(msg); } static DBusMessage *swap_calls(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct telephony *telephony = user_data; if (telephony->cbs && telephony->cbs->swap_calls) return telephony->cbs->swap_calls(conn, msg, telephony->profile_data); return btd_error_not_supported(msg); } static DBusMessage *release_and_answer(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct telephony *telephony = user_data; if (telephony->cbs && telephony->cbs->release_and_answer) return telephony->cbs->release_and_answer(conn, msg, telephony->profile_data); return btd_error_not_supported(msg); } static DBusMessage *release_and_swap(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct telephony *telephony = user_data; if (telephony->cbs && telephony->cbs->release_and_swap) return telephony->cbs->release_and_swap(conn, msg, telephony->profile_data); return btd_error_not_supported(msg); } static DBusMessage *hold_and_answer(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct telephony *telephony = user_data; if (telephony->cbs && telephony->cbs->hold_and_answer) return telephony->cbs->hold_and_answer(conn, msg, telephony->profile_data); return btd_error_not_supported(msg); } static DBusMessage *hangup_all(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct telephony *telephony = user_data; if (telephony->cbs && telephony->cbs->hangup_all) return telephony->cbs->hangup_all(conn, msg, telephony->profile_data); return btd_error_not_supported(msg); } static DBusMessage *create_multiparty(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct telephony *telephony = user_data; if (telephony->cbs && telephony->cbs->create_multiparty) return telephony->cbs->create_multiparty(conn, msg, telephony->profile_data); return btd_error_not_supported(msg); } static DBusMessage *send_tones(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct telephony *telephony = user_data; if (telephony->cbs && telephony->cbs->send_tones) return telephony->cbs->send_tones(conn, msg, telephony->profile_data); return btd_error_not_supported(msg); } static gboolean property_get_uuid(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct telephony *telephony = user_data; struct btd_profile *p = btd_service_get_profile(telephony->service); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &p->remote_uuid); return TRUE; } static gboolean property_get_uri_schemes(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct telephony *telephony = user_data; DBusMessageIter entry; GSList *l; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); for (l = telephony->uri_schemes; l != NULL; l = l->next) dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &l->data); dbus_message_iter_close_container(iter, &entry); return TRUE; } static gboolean property_get_state(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct telephony *telephony = user_data; const char *string; string = state_to_string(telephony->state); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string); return TRUE; } static gboolean property_get_service(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct telephony *telephony = user_data; dbus_bool_t value; value = telephony->network_service; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); return TRUE; } static gboolean property_get_signal(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct telephony *telephony = user_data; dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &telephony->signal); return TRUE; } static gboolean property_get_roaming(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct telephony *telephony = user_data; dbus_bool_t value; value = telephony->roaming; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); return TRUE; } static gboolean property_get_battchg(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct telephony *telephony = user_data; dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &telephony->battchg); return TRUE; } static gboolean property_operator_name_exists( const GDBusPropertyTable *property, void *user_data) { struct telephony *telephony = user_data; return telephony->operator_name != NULL; } static gboolean property_get_operator_name(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct telephony *telephony = user_data; if (telephony->operator_name == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &telephony->operator_name); return TRUE; } static gboolean property_get_inband_ringtone(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct telephony *telephony = user_data; dbus_bool_t value; value = telephony->inband_ringtone; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); return TRUE; } static const GDBusMethodTable telephony_methods[] = { { GDBUS_ASYNC_METHOD("Dial", GDBUS_ARGS({"uri", "s"}), NULL, dial) }, { GDBUS_ASYNC_METHOD("SwapCalls", NULL, NULL, swap_calls) }, { GDBUS_ASYNC_METHOD("ReleaseAndAnswer", NULL, NULL, release_and_answer) }, { GDBUS_ASYNC_METHOD("ReleaseAndSwap", NULL, NULL, release_and_swap) }, { GDBUS_ASYNC_METHOD("HoldAndAnswer", NULL, NULL, hold_and_answer) }, { GDBUS_ASYNC_METHOD("HangupAll", NULL, NULL, hangup_all) }, { GDBUS_ASYNC_METHOD("CreateMultiparty", NULL, GDBUS_ARGS({ "calls", "ao" }), create_multiparty) }, { GDBUS_ASYNC_METHOD("SendTones", GDBUS_ARGS({"number", "s"}), NULL, send_tones) }, { } }; static const GDBusPropertyTable telephony_properties[] = { { "UUID", "s", property_get_uuid }, { "SupportedURISchemes", "as", property_get_uri_schemes }, { "State", "s", property_get_state }, { "Service", "b", property_get_service }, { "Signal", "y", property_get_signal }, { "Roaming", "b", property_get_roaming }, { "BattChg", "y", property_get_battchg }, { "OperatorName", "s", property_get_operator_name, NULL, property_operator_name_exists }, { "InbandRingtone", "b", property_get_inband_ringtone }, { } }; static void path_unregister(void *data) { struct telephony *telephony = data; DBG("Unregistered interface %s on path %s", TELEPHONY_INTERFACE, telephony->path); } int telephony_register_interface(struct telephony *telephony) { if (telephony->cbs == NULL) return -EINVAL; if (!g_dbus_register_interface(btd_get_dbus_connection(), telephony->path, TELEPHONY_INTERFACE, telephony_methods, NULL, telephony_properties, telephony, path_unregister)) { return -EINVAL; } DBG("Registered interface %s on path %s", TELEPHONY_INTERFACE, telephony->path); return 0; } void telephony_unregister_interface(struct telephony *telephony) { g_dbus_unregister_interface(btd_get_dbus_connection(), telephony->path, TELEPHONY_INTERFACE); } struct btd_service *telephony_get_service(struct telephony *telephony) { return telephony->service; } struct btd_device *telephony_get_device(struct telephony *telephony) { return telephony->device; } const char *telephony_get_path(struct telephony *telephony) { return telephony->path; } bdaddr_t telephony_get_src(struct telephony *telephony) { return telephony->src; } bdaddr_t telephony_get_dst(struct telephony *telephony) { return telephony->dst; } void *telephony_get_profile_data(struct telephony *telephony) { return telephony->profile_data; } void telephony_add_uri_scheme(struct telephony *telephony, const char *scheme) { telephony->uri_schemes = g_slist_append(telephony->uri_schemes, g_strdup(scheme)); g_dbus_emit_property_changed(btd_get_dbus_connection(), telephony->path, TELEPHONY_INTERFACE, "SupportedURISchemes"); } void telephony_remove_uri_scheme(struct telephony *telephony, const char *scheme) { GSList *l; l = g_slist_find_custom(telephony->uri_schemes, scheme, (GCompareFunc)strcasecmp); if (!l) return; telephony->uri_schemes = g_slist_delete_link(telephony->uri_schemes, l); g_dbus_emit_property_changed(btd_get_dbus_connection(), telephony->path, TELEPHONY_INTERFACE, "SupportedURISchemes"); } void telephony_set_state(struct telephony *telephony, enum connection_state state) { char address[18]; if (telephony->state == state) return; ba2str(&telephony->dst, address); DBG("device %s state %s -> %s", address, state_to_string(telephony->state), state_to_string(state)); telephony->state = state; g_dbus_emit_property_changed(btd_get_dbus_connection(), telephony->path, TELEPHONY_INTERFACE, "State"); } enum connection_state telephony_get_state(struct telephony *telephony) { return telephony->state; } void telephony_set_network_service(struct telephony *telephony, bool service) { char address[18]; if (telephony->network_service == service) return; ba2str(&telephony->dst, address); DBG("device %s network service %u -> %u", address, telephony->network_service, service); telephony->network_service = service; g_dbus_emit_property_changed(btd_get_dbus_connection(), telephony->path, TELEPHONY_INTERFACE, "Service"); } bool telephony_get_network_service(struct telephony *telephony) { return telephony->network_service; } void telephony_set_signal(struct telephony *telephony, uint8_t signal) { char address[18]; if (telephony->signal == signal) return; ba2str(&telephony->dst, address); DBG("device %s signal %u -> %u", address, telephony->signal, signal); telephony->signal = signal; g_dbus_emit_property_changed(btd_get_dbus_connection(), telephony->path, TELEPHONY_INTERFACE, "Signal"); } uint8_t telephony_get_signal(struct telephony *telephony) { return telephony->signal; } void telephony_set_roaming(struct telephony *telephony, bool roaming) { char address[18]; if (telephony->roaming == roaming) return; ba2str(&telephony->dst, address); DBG("device %s roaming %u -> %u", address, telephony->roaming, roaming); telephony->roaming = roaming; g_dbus_emit_property_changed(btd_get_dbus_connection(), telephony->path, TELEPHONY_INTERFACE, "Roaming"); } bool telephony_get_roaming(struct telephony *telephony) { return telephony->roaming; } void telephony_set_battchg(struct telephony *telephony, uint8_t battchg) { char address[18]; if (telephony->battchg == battchg) return; ba2str(&telephony->dst, address); DBG("device %s battchg %u -> %u", address, telephony->battchg, battchg); telephony->battchg = battchg; g_dbus_emit_property_changed(btd_get_dbus_connection(), telephony->path, TELEPHONY_INTERFACE, "BattChg"); } uint8_t telephony_get_battchg(struct telephony *telephony) { return telephony->battchg; } void telephony_set_operator_name(struct telephony *telephony, const char *name) { char address[18]; if (telephony->operator_name && g_str_equal(telephony->operator_name, name)) return; ba2str(&telephony->dst, address); DBG("device %s operator name %s -> %s", address, telephony->operator_name, name); if (telephony->operator_name) g_free(telephony->operator_name); telephony->operator_name = g_strdup(name); g_dbus_emit_property_changed(btd_get_dbus_connection(), telephony->path, TELEPHONY_INTERFACE, "OperatorName"); } const char *telephony_get_operator_name(struct telephony *telephony) { return telephony->operator_name; } void telephony_set_inband_ringtone(struct telephony *telephony, bool enabled) { char address[18]; if (telephony->inband_ringtone == enabled) return; ba2str(&telephony->dst, address); DBG("device %s inband ringtone %u -> %u", address, telephony->inband_ringtone, enabled); telephony->inband_ringtone = enabled; g_dbus_emit_property_changed(btd_get_dbus_connection(), telephony->path, TELEPHONY_INTERFACE, "InbandRingtone"); } bool telephony_get_inband_ringtone(struct telephony *telephony) { return telephony->inband_ringtone; } struct call *telephony_new_call(struct telephony *telephony, uint8_t idx, enum call_state state, void *user_data) { struct call *call; call = g_new0(struct call, 1); call->device = telephony; call->state = state; call->idx = idx; call->path = g_strdup_printf("%s/call%u", telephony->path, call->idx); return call; } void telephony_free_call(struct call *call) { if (call->pending_msg) dbus_message_unref(call->pending_msg); g_free(call->name); g_free(call->incoming_line); g_free(call->line_id); g_free(call->path); g_free(call); } static DBusMessage *call_answer(DBusConnection *conn, DBusMessage *msg, void *call_data) { struct call *call = call_data; struct telephony *telephony = call->device; return telephony->cbs->call_answer(conn, msg, call_data); } static DBusMessage *call_hangup(DBusConnection *conn, DBusMessage *msg, void *call_data) { struct call *call = call_data; struct telephony *telephony = call->device; return telephony->cbs->call_hangup(conn, msg, call_data); } static gboolean call_line_id_exists(const GDBusPropertyTable *property, void *data) { struct call *call = data; return call->line_id != NULL; } static gboolean call_property_get_line_id( const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct call *call = data; if (call->line_id == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &call->line_id); return TRUE; } static gboolean call_incoming_line_exists(const GDBusPropertyTable *property, void *data) { struct call *call = data; return call->incoming_line != NULL; } static gboolean call_property_get_incoming_line( const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct call *call = data; if (call->incoming_line == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &call->incoming_line); return TRUE; } static gboolean call_name_exists(const GDBusPropertyTable *property, void *data) { struct call *call = data; return call->name != NULL; } static gboolean call_property_get_name(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct call *call = data; if (call->name == NULL) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &call->name); return TRUE; } static gboolean call_property_get_multiparty( const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct call *call = data; dbus_bool_t value; value = call->multiparty; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); return TRUE; } static gboolean call_property_get_state(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct call *call = data; const char *string; string = call_state_to_string(call->state); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string); return TRUE; } static const GDBusMethodTable telephony_call_methods[] = { { GDBUS_ASYNC_METHOD("Answer", NULL, NULL, call_answer) }, { GDBUS_ASYNC_METHOD("Hangup", NULL, NULL, call_hangup) }, { } }; static const GDBusPropertyTable telephony_call_properties[] = { { "LineIdentification", "s", call_property_get_line_id, NULL, call_line_id_exists }, { "IncomingLine", "s", call_property_get_incoming_line, NULL, call_incoming_line_exists }, { "Name", "s", call_property_get_name, NULL, call_name_exists }, { "Multiparty", "b", call_property_get_multiparty }, { "State", "s", call_property_get_state }, { } }; static void call_path_unregister(void *user_data) { struct call *call = user_data; DBG("Unregistered interface %s on path %s", TELEPHONY_CALL_INTERFACE, call->path); telephony_free_call(call); } int telephony_call_register_interface(struct call *call) { if (call->device->cbs == NULL) return -EINVAL; if (!g_dbus_register_interface(btd_get_dbus_connection(), call->path, TELEPHONY_CALL_INTERFACE, telephony_call_methods, NULL, telephony_call_properties, call, call_path_unregister)) { return -EINVAL; } DBG("Registered interface %s on path %s", TELEPHONY_CALL_INTERFACE, call->path); return 0; } void telephony_call_unregister_interface(struct call *call) { g_dbus_unregister_interface(btd_get_dbus_connection(), call->path, TELEPHONY_CALL_INTERFACE); } void telephony_call_set_state(struct call *call, enum call_state state) { if (call->state == state) return; DBG("%s state %s -> %s", call->path, call_state_to_string(call->state), call_state_to_string(state)); call->state = state; g_dbus_emit_property_changed(btd_get_dbus_connection(), call->path, TELEPHONY_CALL_INTERFACE, "State"); } void telephony_call_set_line_id(struct call *call, const char *line_id) { if (call->line_id && g_str_equal(call->line_id, line_id)) return; g_free(call->line_id); call->line_id = g_strdup(line_id); DBG("%s line_id: %s", call->path, call->line_id); g_dbus_emit_property_changed(btd_get_dbus_connection(), call->path, TELEPHONY_CALL_INTERFACE, "LineIdentification"); } bluez-5.87/profiles/audio/PaxHeaders/control.h0000644000000000000000000000005014015011623016421 xustar0020 atime=1783093647 20 ctime=1783094105 bluez-5.87/profiles/audio/control.h0000644000000000000000000000120714015011623016102 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * */ #define AUDIO_CONTROL_INTERFACE "org.bluez.MediaControl1" struct btd_service; int control_init_target(struct btd_service *service); int control_init_remote(struct btd_service *service); void control_unregister(struct btd_service *service); int control_connect(struct btd_service *service); int control_disconnect(struct btd_service *service); int control_set_player(struct btd_service *service, const char *path); bluez-5.87/profiles/PaxHeaders/midi0000644000000000000000000000005015221755531014351 xustar0020 atime=1783094113 20 ctime=1783094105 bluez-5.87/profiles/midi/0000755000000000000000000000000015221755531014107 5ustar00rootrootbluez-5.87/profiles/midi/PaxHeaders/midi.c0000644000000000000000000000005015142177620015513 xustar0020 atime=1778585135 20 ctime=1783094105 bluez-5.87/profiles/midi/midi.c0000644000000000000000000003126715142177620015205 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015,2016 Felipe F. Tonello * Copyright (C) 2016 ROLI Ltd. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/shared/io.h" #include "src/log.h" #include "attrib/att.h" #include "libmidi.h" struct midi { struct btd_device *dev; struct gatt_db *db; struct bt_gatt_client *client; unsigned int io_cb_id; struct io *io; uint16_t midi_io_handle; /* ALSA handlers */ snd_seq_t *seq_handle; int seq_client_id; int seq_port_id; /* MIDI parser*/ struct midi_read_parser midi_in; struct midi_write_parser midi_out; }; static void foreach_cb(const struct midi_write_parser *parser, void *user_data) { struct midi *midi = user_data; bt_gatt_client_write_without_response(midi->client, midi->midi_io_handle, false, midi_write_data(parser), midi_write_data_size(parser)); } static bool midi_write_cb(struct io *io, void *user_data) { struct midi *midi = user_data; int err; do { snd_seq_event_t *event = NULL; err = snd_seq_event_input(midi->seq_handle, &event); if (err < 0 || !event) break; midi_read_ev(&midi->midi_out, event, foreach_cb, midi); } while (err > 0); if (midi_write_has_data(&midi->midi_out)) bt_gatt_client_write_without_response(midi->client, midi->midi_io_handle, false, midi_write_data(&midi->midi_out), midi_write_data_size(&midi->midi_out)); midi_write_reset(&midi->midi_out); return true; } static void midi_io_value_cb(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct midi *midi = user_data; snd_seq_event_t ev; unsigned int i = 0; if (length < 3) { warn("MIDI I/O: Wrong packet format: length is %u bytes but it should " "be at least 3 bytes", length); return; } snd_seq_ev_clear(&ev); snd_seq_ev_set_source(&ev, midi->seq_port_id); snd_seq_ev_set_subs(&ev); snd_seq_ev_set_direct(&ev); midi_read_reset(&midi->midi_in); while (i < length) { size_t count = midi_read_raw(&midi->midi_in, value + i, length - i, &ev); if (count == 0) goto _err; if (ev.type != SND_SEQ_EVENT_NONE) snd_seq_event_output_direct(midi->seq_handle, &ev); i += count; } return; _err: error("Wrong BLE-MIDI message"); } static void midi_io_ccc_written_cb(uint16_t att_ecode, void *user_data) { if (att_ecode != 0) { error("MIDI I/O: notifications not enabled %s", att_ecode2str(att_ecode)); return; } DBG("MIDI I/O: notification enabled"); } static void midi_io_initial_read_cb(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct midi *midi = user_data; if (!success) { error("MIDI I/O: Failed to read initial request"); return; } /* request notify */ midi->io_cb_id = bt_gatt_client_register_notify(midi->client, midi->midi_io_handle, midi_io_ccc_written_cb, midi_io_value_cb, midi, NULL); } static void handle_midi_io(struct midi *midi, uint16_t value_handle) { DBG("MIDI I/O handle: 0x%04x", value_handle); midi->midi_io_handle = value_handle; /* * The BLE-MIDI 1.0 spec specifies that The Central shall attempt to * read the MIDI I/O characteristic of the Peripheral right after * estrablhishing a connection with the accessory. */ if (!bt_gatt_client_read_value(midi->client, value_handle, midi_io_initial_read_cb, midi, NULL)) DBG("MIDI I/O: Failed to send request to read initial value"); } static void handle_characteristic(struct gatt_db_attribute *attr, void *user_data) { struct midi *midi = user_data; uint16_t value_handle; bt_uuid_t uuid, midi_io_uuid; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) { error("Failed to obtain characteristic data"); return; } bt_string_to_uuid(&midi_io_uuid, MIDI_IO_UUID); if (bt_uuid_cmp(&midi_io_uuid, &uuid) == 0) handle_midi_io(midi, value_handle); else { char uuid_str[MAX_LEN_UUID_STR]; bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); DBG("Unsupported characteristic: %s", uuid_str); } } static void foreach_midi_service(struct gatt_db_attribute *attr, void *user_data) { struct midi *midi = user_data; gatt_db_service_foreach_char(attr, handle_characteristic, midi); } static int midi_device_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct midi *midi; char addr[18]; ba2str(device_get_address(device), addr); DBG("MIDI GATT Driver profile probe (%s)", addr); /* Ignore, if we were probed for this device already */ midi = btd_service_get_user_data(service); if (midi) { error("Profile probed twice for the same device!"); return -EADDRINUSE; } midi = g_new0(struct midi, 1); if (!midi) return -ENOMEM; midi->dev = btd_device_ref(device); btd_service_set_user_data(service, midi); return 0; } static void midi_device_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct midi *midi; char addr[18]; ba2str(device_get_address(device), addr); DBG("MIDI GATT Driver profile remove (%s)", addr); midi = btd_service_get_user_data(service); if (!midi) { error("MIDI Service not handled by profile"); return; } if (midi->seq_handle) { midi_read_free(&midi->midi_in); midi_write_free(&midi->midi_out); io_destroy(midi->io); snd_seq_delete_simple_port(midi->seq_handle, midi->seq_port_id); midi->seq_port_id = 0; snd_seq_close(midi->seq_handle); } btd_device_unref(midi->dev); g_free(midi); } static int midi_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct gatt_db *db = btd_device_get_gatt_db(device); struct bt_gatt_client *client = btd_device_get_gatt_client(device); bt_uuid_t midi_uuid; struct pollfd pfd; struct midi *midi; char addr[18]; char device_name[MAX_NAME_LENGTH + 11]; /* 11 = " Bluetooth\0"*/ int err; snd_seq_client_info_t *info; ba2str(device_get_address(device), addr); DBG("MIDI GATT Driver profile accept (%s)", addr); midi = btd_service_get_user_data(service); if (!midi) { error("MIDI Service not handled by profile"); return -ENODEV; } /* Port Name */ memset(device_name, 0, sizeof(device_name)); if (device_name_known(device)) device_get_name(device, device_name, sizeof(device_name)); else strncpy(device_name, addr, sizeof(device_name)); /* ALSA Sequencer Client and Port Setup */ err = snd_seq_open(&midi->seq_handle, "default", SND_SEQ_OPEN_DUPLEX, 0); if (err < 0) { error("Could not open ALSA Sequencer: %s (%d)", snd_strerror(err), err); return err; } err = snd_seq_nonblock(midi->seq_handle, SND_SEQ_NONBLOCK); if (err < 0) { error("Could not set nonblock mode: %s (%d)", snd_strerror(err), err); goto _err_handle; } err = snd_seq_set_client_name(midi->seq_handle, device_name); if (err < 0) { error("Could not configure ALSA client: %s (%d)", snd_strerror(err), err); goto _err_handle; } err = snd_seq_client_id(midi->seq_handle); if (err < 0) { error("Could not retrieve ALSA client: %s (%d)", snd_strerror(err), err); goto _err_handle; } midi->seq_client_id = err; err = snd_seq_create_simple_port(midi->seq_handle, strcat(device_name, " Bluetooth"), SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_HARDWARE); if (err < 0) { error("Could not create ALSA port: %s (%d)", snd_strerror(err), err); goto _err_handle; } midi->seq_port_id = err; snd_seq_client_info_alloca(&info); err = snd_seq_get_client_info(midi->seq_handle, info); if (err < 0) goto _err_port; /* list of relevant sequencer events */ snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_NOTEOFF); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_NOTEON); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_KEYPRESS); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_CONTROLLER); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_PGMCHANGE); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_CHANPRESS); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_PITCHBEND); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_SYSEX); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_QFRAME); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_SONGPOS); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_SONGSEL); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_TUNE_REQUEST); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_CLOCK); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_START); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_CONTINUE); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_STOP); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_SENSING); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_RESET); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_CONTROL14); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_NONREGPARAM); snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_REGPARAM); err = snd_seq_set_client_info(midi->seq_handle, info); if (err < 0) goto _err_port; /* Input file descriptors */ snd_seq_poll_descriptors(midi->seq_handle, &pfd, 1, POLLIN); midi->io = io_new(pfd.fd); if (!midi->io) { error("Could not allocate I/O eventloop"); goto _err_port; } io_set_read_handler(midi->io, midi_write_cb, midi, NULL); midi->db = gatt_db_ref(db); midi->client = bt_gatt_client_ref(client); err = midi_read_init(&midi->midi_in); if (err < 0) { error("Could not initialise MIDI input parser"); goto _err_port; } err = midi_write_init(&midi->midi_out, bt_gatt_client_get_mtu(midi->client) - 3); if (err < 0) { error("Could not initialise MIDI output parser"); goto _err_midi; } bt_string_to_uuid(&midi_uuid, MIDI_UUID); gatt_db_foreach_service(db, &midi_uuid, foreach_midi_service, midi); btd_service_connecting_complete(service, 0); return 0; _err_midi: midi_read_free(&midi->midi_in); _err_port: snd_seq_delete_simple_port(midi->seq_handle, midi->seq_port_id); _err_handle: snd_seq_close(midi->seq_handle); midi->seq_handle = NULL; btd_service_connecting_complete(service, err); return err; } static int midi_disconnect(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct midi *midi; char addr[18]; ba2str(device_get_address(device), addr); DBG("MIDI GATT Driver profile disconnect (%s)", addr); midi = btd_service_get_user_data(service); if (!midi) { error("MIDI Service not handled by profile"); return -ENODEV; } midi_read_free(&midi->midi_in); midi_write_free(&midi->midi_out); io_destroy(midi->io); snd_seq_delete_simple_port(midi->seq_handle, midi->seq_port_id); midi->seq_port_id = 0; snd_seq_close(midi->seq_handle); midi->seq_handle = NULL; /* Clean-up any old client/db */ bt_gatt_client_unregister_notify(midi->client, midi->io_cb_id); bt_gatt_client_unref(midi->client); gatt_db_unref(midi->db); btd_service_disconnecting_complete(service, 0); return 0; } static struct btd_profile midi_profile = { .name = "MIDI GATT Driver", .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = MIDI_UUID, .priority = BTD_PROFILE_PRIORITY_HIGH, .auto_connect = true, .device_probe = midi_device_probe, .device_remove = midi_device_remove, .accept = midi_accept, .disconnect = midi_disconnect, }; static int midi_init(void) { return btd_profile_register(&midi_profile); } static void midi_exit(void) { btd_profile_unregister(&midi_profile); } BLUETOOTH_PLUGIN_DEFINE(midi, VERSION, BLUETOOTH_PLUGIN_PRIORITY_HIGH, midi_init, midi_exit); bluez-5.87/profiles/midi/PaxHeaders/libmidi.h0000644000000000000000000000005015221752714016210 xustar0020 atime=1783092685 20 ctime=1783094105 bluez-5.87/profiles/midi/libmidi.h0000644000000000000000000000652715221752714015703 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015,2016 Felipe F. Tonello * Copyright (C) 2016 ROLI Ltd. * */ #ifndef LIBMIDI_H #define LIBMIDI_H #include #include #include #define MIDI_UUID "03B80E5A-EDE8-4B33-A751-6CE34EC4C700" #define MIDI_IO_UUID "7772E5DB-3868-4112-A1A9-F2669D106BF3" #define MIDI_MAX_TIMESTAMP 8191 #define MIDI_MSG_MAX_SIZE 12 #define MIDI_SYSEX_MAX_SIZE (4 * 1024) struct midi_buffer { uint8_t *data; size_t len; }; /* MIDI I/O Write parser */ struct midi_write_parser { int64_t rtime; /* last writer's real time */ snd_seq_event_type_t rstatus; /* running status event type */ struct midi_buffer midi_stream; /* MIDI I/O byte stream */ size_t stream_size; /* what is the maximum size of the midi_stream array */ snd_midi_event_t *midi_ev; /* midi<->seq event */ }; int midi_write_init(struct midi_write_parser *parser, size_t buffer_size); static inline void midi_write_free(struct midi_write_parser *parser) { free(parser->midi_stream.data); snd_midi_event_free(parser->midi_ev); } static inline void midi_write_reset(struct midi_write_parser *parser) { parser->rstatus = SND_SEQ_EVENT_NONE; parser->midi_stream.len = 0; } static inline bool midi_write_has_data(const struct midi_write_parser *parser) { return parser->midi_stream.len > 0; } static inline const uint8_t * midi_write_data(const struct midi_write_parser *parser) { return parser->midi_stream.data; } static inline size_t midi_write_data_size(const struct midi_write_parser *parser) { return parser->midi_stream.len; } typedef void (*midi_read_ev_cb)(const struct midi_write_parser *parser, void *); /* It creates BLE-MIDI raw packets from the a sequencer event. If the packet is full, then it calls write_cb and resets its internal state as many times as necessary. */ void midi_read_ev(struct midi_write_parser *parser, const snd_seq_event_t *ev, midi_read_ev_cb write_cb, void *user_data); /* MIDI I/O Read parser */ struct midi_read_parser { uint8_t rstatus; /* running status byte */ int64_t rtime; /* last reader's real time */ int16_t timestamp; /* last MIDI-BLE timestamp */ int8_t timestamp_low; /* MIDI-BLE timestampLow from the current packet */ int8_t timestamp_high; /* MIDI-BLE timestampHigh from the current packet */ bool timestamp_high_set; /* timestampHigh read */ struct midi_buffer sysex_stream; /* SysEx stream */ snd_midi_event_t *midi_ev; /* midi<->seq event */ }; int midi_read_init(struct midi_read_parser *parser); static inline void midi_read_free(struct midi_read_parser *parser) { free(parser->sysex_stream.data); snd_midi_event_free(parser->midi_ev); } static inline void midi_read_reset(struct midi_read_parser *parser) { parser->rstatus = 0; parser->timestamp_low = 0; parser->timestamp_high = 0; parser->timestamp_high_set = false; } /* Parses raw BLE-MIDI messages and populates a sequencer event representing the current MIDI message. It returns how much raw data was processed. */ size_t midi_read_raw(struct midi_read_parser *parser, const uint8_t *data, size_t size, snd_seq_event_t *ev /* OUT */); #endif /* LIBMIDI_H */ bluez-5.87/profiles/midi/PaxHeaders/libmidi.c0000644000000000000000000000005015221752714016203 xustar0020 atime=1783092685 20 ctime=1783094105 bluez-5.87/profiles/midi/libmidi.c0000644000000000000000000003120115221752714015661 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015,2016 Felipe F. Tonello * Copyright (C) 2016 ROLI Ltd. * */ #include /* Avoid linkage problem on unit-tests */ #ifndef MIDI_TEST #include "src/backtrace.h" #define MIDI_ASSERT(_expr) btd_assert(_expr) #else #define MIDI_ASSERT(_expr) g_assert(_expr) #endif #include "libmidi.h" inline static void buffer_append_byte(struct midi_buffer *buffer, const uint8_t byte) { buffer->data[buffer->len++] = byte; } inline static void buffer_append_data(struct midi_buffer *buffer, const uint8_t *data, size_t size) { memcpy(buffer->data + buffer->len, data, size); buffer->len += size; } inline static uint8_t buffer_reverse_get(struct midi_buffer *buffer, size_t i) { MIDI_ASSERT(buffer->len > i); return buffer->data[buffer->len - (i + 1)]; } inline static void buffer_reverse_set(struct midi_buffer *buffer, size_t i, const uint8_t byte) { MIDI_ASSERT(buffer->len > i); buffer->data[buffer->len - (i + 1)] = byte; } inline static size_t parser_get_available_size(struct midi_write_parser *parser) { return parser->stream_size - parser->midi_stream.len; } inline static uint8_t sysex_get(const snd_seq_event_t *ev, size_t i) { MIDI_ASSERT(ev->data.ext.len > i); return ((uint8_t*)ev->data.ext.ptr)[i]; } inline static void append_timestamp_high_maybe(struct midi_write_parser *parser) { uint8_t timestamp_high = 0x80; if (midi_write_has_data(parser)) return; /* Make sure timesampt_high is assigned a non-zero value */ do { /* convert µs to ms */ parser->rtime = g_get_monotonic_time() / 1000; timestamp_high |= (parser->rtime & 0x1F80) >> 7; } while (timestamp_high == 0x80); /* set timestampHigh */ buffer_append_byte(&parser->midi_stream, timestamp_high); } inline static void append_timestamp_low(struct midi_write_parser *parser) { const uint8_t timestamp_low = 0x80 | (parser->rtime & 0x7F); buffer_append_byte(&parser->midi_stream, timestamp_low); } int midi_write_init(struct midi_write_parser *parser, size_t buffer_size) { int err; parser->rtime = 0; parser->rstatus = SND_SEQ_EVENT_NONE; parser->stream_size = buffer_size; parser->midi_stream.data = malloc(buffer_size); if (!parser->midi_stream.data) return -ENOMEM; parser->midi_stream.len = 0; err = snd_midi_event_new(buffer_size, &parser->midi_ev); if (err < 0) free(parser->midi_stream.data); return err; } int midi_read_init(struct midi_read_parser *parser) { int err; parser->rstatus = 0; parser->rtime = -1; parser->timestamp = 0; parser->timestamp_low = 0; parser->timestamp_high = 0; parser->timestamp_high_set = false; parser->sysex_stream.data = malloc(MIDI_SYSEX_MAX_SIZE); if (!parser->sysex_stream.data) return -ENOMEM; parser->sysex_stream.len = 0; err = snd_midi_event_new(MIDI_MSG_MAX_SIZE, &parser->midi_ev); if (err < 0) free(parser->sysex_stream.data); return err; } /* Algorithm: 1) check initial timestampLow: if used_sysex == 0, then tsLow = 1, else tsLow = 0 2) calculate sysex size of current packet: 2a) first check special case: if midi->out_length - 1 (tsHigh) - tsLow == sysex_length - used_sysex size is: min(midi->out_length - 1 - tsLow, sysex_length - used_sysex - 1) 2b) else size is: min(midi->out_length - 1 - tsLow, sysex_length - used_sysex) 3) check if packet contains F7: fill respective tsLow byte */ static void read_ev_sysex(struct midi_write_parser *parser, const snd_seq_event_t *ev, midi_read_ev_cb write_cb, void *user_data) { unsigned int used_sysex = 0; /* We need at least 2 bytes (timestampLow + F0) */ if (parser_get_available_size(parser) < 2) { /* send current message and start new one */ write_cb(parser, user_data); midi_write_reset(parser); append_timestamp_high_maybe(parser); } /* timestampLow on initial F0 */ if (sysex_get(ev, 0) == 0xF0) append_timestamp_low(parser); do { unsigned int size_of_sysex; append_timestamp_high_maybe(parser); size_of_sysex = MIN(parser_get_available_size(parser), ev->data.ext.len - used_sysex); if (parser_get_available_size(parser) == ev->data.ext.len - used_sysex) size_of_sysex--; buffer_append_data(&parser->midi_stream, ev->data.ext.ptr + used_sysex, size_of_sysex); used_sysex += size_of_sysex; if (parser_get_available_size(parser) <= 1 && buffer_reverse_get(&parser->midi_stream, 0) != 0xF7) { write_cb(parser, user_data); midi_write_reset(parser); } } while (used_sysex < ev->data.ext.len); /* check for F7 and update respective timestampLow byte */ if (midi_write_has_data(parser) && buffer_reverse_get(&parser->midi_stream, 0) == 0xF7) { /* remove 0xF7 from buffer, append timestamp and add 0xF7 back again */ parser->midi_stream.len--; append_timestamp_low(parser); buffer_append_byte(&parser->midi_stream, 0xF7); } } static void read_ev_others(struct midi_write_parser *parser, const snd_seq_event_t *ev, midi_read_ev_cb write_cb, void *user_data) { int length; /* check for running status */ if (parser->rstatus != ev->type) { snd_midi_event_reset_decode(parser->midi_ev); append_timestamp_low(parser); } /* each midi message has timestampLow byte to follow */ length = snd_midi_event_decode(parser->midi_ev, parser->midi_stream.data + parser->midi_stream.len, parser_get_available_size(parser), ev); if (length == -ENOMEM) { /* remove previously added timestampLow */ if (parser->rstatus != ev->type) parser->midi_stream.len--; write_cb(parser, user_data); /* cleanup state for next packet */ snd_midi_event_reset_decode(parser->midi_ev); midi_write_reset(parser); append_timestamp_high_maybe(parser); append_timestamp_low(parser); length = snd_midi_event_decode(parser->midi_ev, parser->midi_stream.data + parser->midi_stream.len, parser_get_available_size(parser), ev); } if (length > 0) parser->midi_stream.len += length; } void midi_read_ev(struct midi_write_parser *parser, const snd_seq_event_t *ev, midi_read_ev_cb write_cb, void *user_data) { MIDI_ASSERT(write_cb); append_timestamp_high_maybe(parser); /* SysEx is special case: SysEx has two timestampLow bytes, before F0 and F7 */ if (ev->type == SND_SEQ_EVENT_SYSEX) read_ev_sysex(parser, ev, write_cb, user_data); else read_ev_others(parser, ev, write_cb, user_data); parser->rstatus = ev->type; if (parser_get_available_size(parser) == 0) { write_cb(parser, user_data); midi_write_reset(parser); } } static void update_ev_timestamp(struct midi_read_parser *parser, snd_seq_event_t *ev, uint16_t ts_low) { int delta_timestamp; int delta_rtime; int64_t rtime_current; uint16_t timestamp; /* time_low overwflow results on time_high to increment by one */ if (parser->timestamp_low > ts_low) parser->timestamp_high++; timestamp = (parser->timestamp_high << 7) | parser->timestamp_low; rtime_current = g_get_monotonic_time() / 1000; /* convert µs to ms */ delta_timestamp = timestamp - (int)parser->timestamp; delta_rtime = rtime_current - parser->rtime; if (delta_rtime > MIDI_MAX_TIMESTAMP) parser->rtime = rtime_current; else { /* If delta_timestamp is way to big than delta_rtime, this means that the device sent a message in the past, so we have to compensate for this. */ if (delta_timestamp > 7000 && delta_rtime < 1000) delta_timestamp = 0; /* check if timestamp did overflow */ if (delta_timestamp < 0) { /* same timestamp in the past problem */ if ((delta_timestamp + MIDI_MAX_TIMESTAMP) > 7000 && delta_rtime < 1000) delta_timestamp = 0; else delta_timestamp = delta_timestamp + MIDI_MAX_TIMESTAMP; } parser->rtime += delta_timestamp; } parser->timestamp += delta_timestamp; if (parser->timestamp > MIDI_MAX_TIMESTAMP) parser->timestamp %= MIDI_MAX_TIMESTAMP + 1; /* set event timestamp */ /* TODO: update event timestamp here! */ } static size_t handle_end_of_sysex(struct midi_read_parser *parser, snd_seq_event_t *ev, const uint8_t *data, size_t sysex_length) { uint8_t time_low; /* At this time, timestampLow is copied as the last byte, instead of 0xF7 */ buffer_append_data(&parser->sysex_stream, data, sysex_length); time_low = buffer_reverse_get(&parser->sysex_stream, 0) & 0x7F; /* Remove timestamp byte */ buffer_reverse_set(&parser->sysex_stream, 0, 0xF7); /* Update event */ update_ev_timestamp(parser, ev, time_low); snd_seq_ev_set_sysex(ev, parser->sysex_stream.len, parser->sysex_stream.data); return sysex_length + 1; /* +1 because of timestampLow */ } /* Searches the end of a SysEx message that contains a timestampLow * before the SysEx end byte. Returns the number of bytes of valid * SysEx payload in the buffer. */ static size_t sysex_eox_len(const uint8_t *data, size_t size) { size_t i = 0; MIDI_ASSERT(size > 0); if (data[i] == 0xF0) i++; /* Search for timestamp low */ while (i < size) { if ((data[i] & 0x80)) { i++; break; } i++; } return (i < size && data[i] == 0xF7) ? i : 0; } size_t midi_read_raw(struct midi_read_parser *parser, const uint8_t *data, size_t size, snd_seq_event_t *ev /* OUT */) { size_t midi_size = 0; size_t i = 0; bool err = false; if (!parser->timestamp_high_set) { parser->timestamp_high = data[i++] & 0x3F; parser->timestamp_high_set = true; } snd_midi_event_reset_encode(parser->midi_ev); /* timestamp byte */ if (data[i] & 0x80) { update_ev_timestamp(parser, ev, data[i] & 0x7F); /* check for wrong BLE-MIDI message size */ if (++i == size) { err = true; goto _finish; } } /* cleanup sysex_stream if message is broken or is a new SysEx */ if (data[i] >= 0x80 && data[i] != 0xF7 && parser->sysex_stream.len > 0) parser->sysex_stream.len = 0; switch (data[i]) { case 0xF8 ... 0XFF: /* System Real-Time Messages */ midi_size = 1; break; /* System Common Messages */ case 0xF0: /* SysEx Start */ { size_t sysex_length; /* cleanup Running Status Message */ parser->rstatus = 0; sysex_length = sysex_eox_len(data + i, size - i); /* Search for End of SysEx message in one BLE message */ if (sysex_length > 0) { midi_size = handle_end_of_sysex(parser, ev, data + i, sysex_length); } else { buffer_append_data(&parser->sysex_stream, data + i, size - i); err = true; /* Not an actual error, just incomplete message */ midi_size = size - i; } goto _finish; } case 0xF1: case 0xF3: midi_size = 2; break; case 0xF2: midi_size = 3; break; case 0xF4: case 0xF5: /* Ignore */ i++; err = true; goto _finish; break; case 0xF6: midi_size = 1; break; case 0xF7: /* SysEx End */ buffer_append_byte(&parser->sysex_stream, 0xF7); snd_seq_ev_set_sysex(ev, parser->sysex_stream.len, parser->sysex_stream.data); midi_size = 1; /* timestampLow was already processed */ goto _finish; case 0x80 ... 0xEF: /* * Channel Voice Messages, Channel Mode Messages * and Control Change Messages. */ parser->rstatus = data[i]; midi_size = (data[i] >= 0xC0 && data[i] <= 0xDF) ? 2 : 3; break; case 0x00 ... 0x7F: /* Check for SysEx messages */ if (parser->sysex_stream.len > 0) { size_t sysex_length; sysex_length = sysex_eox_len(data + i, size - i); if (sysex_length > 0) { midi_size = handle_end_of_sysex(parser, ev, data + i, sysex_length); } else { buffer_append_data(&parser->sysex_stream, data + i, size - i); err = true; /* Not an actual error, just incomplete message */ midi_size = size - i; } goto _finish; } /* Running State Message was not set */ if (parser->rstatus == 0) { midi_size = 1; err = true; goto _finish; } snd_midi_event_encode_byte(parser->midi_ev, parser->rstatus, ev); midi_size = (parser->rstatus >= 0xC0 && parser->rstatus <= 0xDF) ? 1 : 2; break; } if ((i + midi_size) > size) { err = true; goto _finish; } snd_midi_event_encode(parser->midi_ev, data + i, midi_size, ev); _finish: if (err) ev->type = SND_SEQ_EVENT_NONE; return i + midi_size; } bluez-5.87/profiles/PaxHeaders/battery0000644000000000000000000000005015221755531015101 xustar0020 atime=1783094113 20 ctime=1783094105 bluez-5.87/profiles/battery/0000755000000000000000000000000015221755531014637 5ustar00rootrootbluez-5.87/profiles/battery/PaxHeaders/battery.c0000644000000000000000000000005015142177620016773 xustar0020 atime=1783093686 20 ctime=1783094105 bluez-5.87/profiles/battery/battery.c0000644000000000000000000001737615142177620016472 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT * Copyright (C) 2014 Google Inc. * Copyright (C) 2017 Red Hat Inc. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #include "src/battery.h" #include "attrib/att.h" #define BATTERY_INTERFACE "org.bluez.Battery1" #define BATT_UUID16 0x180f /* Generic Attribute/Access Service */ struct batt { struct btd_battery *battery; struct btd_device *device; struct gatt_db *db; struct bt_gatt_client *client; struct gatt_db_attribute *attr; unsigned int batt_level_cb_id; uint16_t batt_level_io_handle; uint8_t *initial_value; uint8_t percentage; }; static void batt_free(struct batt *batt) { gatt_db_unref(batt->db); bt_gatt_client_unref(batt->client); btd_device_unref(batt->device); free(batt->initial_value); if (batt->battery) btd_battery_unregister(batt->battery); g_free(batt); } static void batt_reset(struct batt *batt) { batt->attr = NULL; batt->percentage = -1; gatt_db_unref(batt->db); batt->db = NULL; bt_gatt_client_unref(batt->client); batt->client = NULL; g_free (batt->initial_value); batt->initial_value = NULL; if (batt->battery) { btd_battery_unregister(batt->battery); batt->battery = NULL; } } static void parse_battery_level(struct batt *batt, const uint8_t *value) { uint8_t percentage = value[0]; DBG("Battery Level updated: %d%%", percentage); if (!batt->battery) { free(batt->initial_value); batt->initial_value = util_memdup(value, 1); DBG("Battery not yet registered"); return; } if (batt->percentage != percentage) { batt->percentage = percentage; btd_battery_update(batt->battery, batt->percentage); } } static void batt_io_value_cb(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct batt *batt = user_data; if (value_handle == batt->batt_level_io_handle) { if (!length) return; parse_battery_level(batt, value); } else { g_assert_not_reached(); } } static void batt_io_ccc_written_cb(uint16_t att_ecode, void *user_data) { struct batt *batt = user_data; if (att_ecode != 0) { error("Battery Level: notifications not enabled %s", att_ecode2str(att_ecode)); return; } batt->battery = btd_battery_register(device_get_path(batt->device), "GATT Battery Service", NULL); if (!batt->battery) { batt_reset(batt); return; } parse_battery_level(batt, batt->initial_value); g_free (batt->initial_value); batt->initial_value = NULL; DBG("Battery Level: notification enabled"); } static void read_initial_battery_level_cb(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct batt *batt = user_data; if (!success) { DBG("Reading battery level failed with ATT error: %u", att_ecode); return; } if (!length) return; batt->initial_value = util_memdup(value, length); /* request notify */ batt->batt_level_cb_id = bt_gatt_client_register_notify(batt->client, batt->batt_level_io_handle, batt_io_ccc_written_cb, batt_io_value_cb, batt, NULL); } static void handle_battery_level(struct batt *batt, uint16_t value_handle) { batt->batt_level_io_handle = value_handle; if (!bt_gatt_client_read_value(batt->client, batt->batt_level_io_handle, read_initial_battery_level_cb, batt, NULL)) DBG("Failed to send request to read battery level"); } static bool uuid_cmp(uint16_t u16, const bt_uuid_t *uuid) { bt_uuid_t lhs; bt_uuid16_create(&lhs, u16); return bt_uuid_cmp(&lhs, uuid) == 0; } static void handle_characteristic(struct gatt_db_attribute *attr, void *user_data) { struct batt *batt = user_data; uint16_t value_handle; bt_uuid_t uuid; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) { error("Failed to obtain characteristic data"); return; } if (uuid_cmp(GATT_CHARAC_BATTERY_LEVEL, &uuid)) { handle_battery_level(batt, value_handle); } else { char uuid_str[MAX_LEN_UUID_STR]; bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); DBG("Unsupported characteristic: %s", uuid_str); } } static void handle_batt_service(struct batt *batt) { gatt_db_service_foreach_char(batt->attr, handle_characteristic, batt); } static int batt_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct batt *batt = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("BATT profile probe (%s)", addr); /* Ignore, if we were probed for this device already */ if (batt) { error("Profile probed twice for the same device!"); return -1; } batt = g_new0(struct batt, 1); if (!batt) return -1; batt->percentage = -1; batt->device = btd_device_ref(device); btd_service_set_user_data(service, batt); return 0; } static void batt_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct batt *batt; char addr[18]; ba2str(device_get_address(device), addr); DBG("BATT profile remove (%s)", addr); batt = btd_service_get_user_data(service); if (!batt) { error("BATT service not handled by profile"); return; } batt_free(batt); } static void foreach_batt_service(struct gatt_db_attribute *attr, void *user_data) { struct batt *batt = user_data; if (batt->attr) { error("More than one BATT service exists for this device"); return; } batt->attr = attr; handle_batt_service(batt); } static int batt_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct gatt_db *db = btd_device_get_gatt_db(device); struct bt_gatt_client *client = btd_device_get_gatt_client(device); struct batt *batt = btd_service_get_user_data(service); char addr[18]; bt_uuid_t batt_uuid; ba2str(device_get_address(device), addr); DBG("BATT profile accept (%s)", addr); if (!batt) { error("BATT service not handled by profile"); return -1; } batt->db = gatt_db_ref(db); batt->client = bt_gatt_client_clone(client); /* Handle the BATT services */ bt_uuid16_create(&batt_uuid, BATT_UUID16); gatt_db_foreach_service(db, &batt_uuid, foreach_batt_service, batt); if (!batt->attr) { error("BATT attribute not found"); batt_reset(batt); return -1; } btd_service_connecting_complete(service, 0); return 0; } static int batt_disconnect(struct btd_service *service) { struct batt *batt = btd_service_get_user_data(service); batt_reset(batt); btd_service_disconnecting_complete(service, 0); return 0; } static struct btd_profile batt_profile = { .name = "batt-profile", .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = BATTERY_UUID, .device_probe = batt_probe, .device_remove = batt_remove, .accept = batt_accept, .disconnect = batt_disconnect, }; static int batt_init(void) { return btd_profile_register(&batt_profile); } static void batt_exit(void) { btd_profile_unregister(&batt_profile); } BLUETOOTH_PLUGIN_DEFINE(battery, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, batt_init, batt_exit) bluez-5.87/profiles/battery/PaxHeaders/bas.h0000644000000000000000000000005014015011623016057 xustar0020 atime=1783093675 20 ctime=1783094105 bluez-5.87/profiles/battery/bas.h0000644000000000000000000000064214015011623015542 0ustar00rootroot/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * */ struct bt_bas; struct bt_bas *bt_bas_new(void *primary); struct bt_bas *bt_bas_ref(struct bt_bas *bas); void bt_bas_unref(struct bt_bas *bas); bool bt_bas_attach(struct bt_bas *bas, void *gatt); void bt_bas_detach(struct bt_bas *bas); bluez-5.87/profiles/battery/PaxHeaders/bas.c0000644000000000000000000000005015063045254016065 xustar0020 atime=1783093680 20 ctime=1783094105 bluez-5.87/profiles/battery/bas.c0000644000000000000000000001526515063045254015557 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "src/log.h" #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "attrib/gattrib.h" #include "attrib/att.h" #include "attrib/gatt.h" #include "profiles/battery/bas.h" #define ATT_NOTIFICATION_HEADER_SIZE 3 #define ATT_READ_RESPONSE_HEADER_SIZE 1 struct bt_bas { int ref_count; GAttrib *attrib; struct gatt_primary *primary; uint16_t handle; uint16_t ccc_handle; guint id; struct queue *gatt_op; }; struct gatt_request { unsigned int id; struct bt_bas *bas; void *user_data; }; static void destroy_gatt_req(struct gatt_request *req) { queue_remove(req->bas->gatt_op, req); bt_bas_unref(req->bas); free(req); } static void bas_free(struct bt_bas *bas) { bt_bas_detach(bas); free(bas->primary); queue_destroy(bas->gatt_op, (void *) destroy_gatt_req); free(bas); } struct bt_bas *bt_bas_new(void *primary) { struct bt_bas *bas; bas = new0(struct bt_bas, 1); bas->gatt_op = queue_new(); if (primary) bas->primary = util_memdup(primary, sizeof(*bas->primary)); return bt_bas_ref(bas); } struct bt_bas *bt_bas_ref(struct bt_bas *bas) { if (!bas) return NULL; __sync_fetch_and_add(&bas->ref_count, 1); return bas; } void bt_bas_unref(struct bt_bas *bas) { if (!bas) return; if (__sync_sub_and_fetch(&bas->ref_count, 1)) return; bas_free(bas); } static struct gatt_request *create_request(struct bt_bas *bas, void *user_data) { struct gatt_request *req; req = new0(struct gatt_request, 1); req->user_data = user_data; req->bas = bt_bas_ref(bas); return req; } static void set_and_store_gatt_req(struct bt_bas *bas, struct gatt_request *req, unsigned int id) { req->id = id; queue_push_head(bas->gatt_op, req); } static void write_char(struct bt_bas *bas, GAttrib *attrib, uint16_t handle, const uint8_t *value, size_t vlen, GAttribResultFunc func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(bas, user_data); id = gatt_write_char(attrib, handle, value, vlen, func, req); set_and_store_gatt_req(bas, req, id); } static void read_char(struct bt_bas *bas, GAttrib *attrib, uint16_t handle, GAttribResultFunc func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(bas, user_data); id = gatt_read_char(attrib, handle, func, req); set_and_store_gatt_req(bas, req, id); } static void discover_char(struct bt_bas *bas, GAttrib *attrib, uint16_t start, uint16_t end, bt_uuid_t *uuid, gatt_cb_t func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(bas, user_data); id = gatt_discover_char(attrib, start, end, uuid, func, req); set_and_store_gatt_req(bas, req, id); } static void discover_desc(struct bt_bas *bas, GAttrib *attrib, uint16_t start, uint16_t end, bt_uuid_t *uuid, gatt_cb_t func, gpointer user_data) { struct gatt_request *req; unsigned int id; req = create_request(bas, user_data); id = gatt_discover_desc(attrib, start, end, uuid, func, req); set_and_store_gatt_req(bas, req, id); } static void notification_cb(const guint8 *pdu, guint16 len, gpointer user_data) { DBG("Battery Level at %u", pdu[ATT_NOTIFICATION_HEADER_SIZE]); } static void read_value_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data) { DBG("Battery Level at %u", pdu[ATT_READ_RESPONSE_HEADER_SIZE]); } static void ccc_written_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { struct gatt_request *req = user_data; struct bt_bas *bas = req->user_data; destroy_gatt_req(req); if (status != 0) { error("Write Scan Refresh CCC failed: %s", att_ecode2str(status)); return; } DBG("Battery Level: notification enabled"); bas->id = g_attrib_register(bas->attrib, ATT_OP_HANDLE_NOTIFY, bas->handle, notification_cb, bas, NULL); } static void write_ccc(struct bt_bas *bas, GAttrib *attrib, uint16_t handle, void *user_data) { uint8_t value[2]; put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value); write_char(bas, attrib, handle, value, sizeof(value), ccc_written_cb, user_data); } static void ccc_read_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data) { struct gatt_request *req = user_data; struct bt_bas *bas = req->user_data; destroy_gatt_req(req); if (status != 0) { error("Error reading CCC value: %s", att_ecode2str(status)); return; } write_ccc(bas, bas->attrib, bas->ccc_handle, bas); } static void discover_descriptor_cb(uint8_t status, GSList *descs, void *user_data) { struct gatt_request *req = user_data; struct bt_bas *bas = req->user_data; struct gatt_desc *desc; destroy_gatt_req(req); if (status != 0) { error("Discover descriptors failed: %s", att_ecode2str(status)); return; } /* There will be only one descriptor on list and it will be CCC */ desc = descs->data; bas->ccc_handle = desc->handle; read_char(bas, bas->attrib, desc->handle, ccc_read_cb, bas); } static void bas_discovered_cb(uint8_t status, GSList *chars, void *user_data) { struct gatt_request *req = user_data; struct bt_bas *bas = req->user_data; struct gatt_char *chr; uint16_t start, end; bt_uuid_t uuid; destroy_gatt_req(req); if (status) { error("Battery: %s", att_ecode2str(status)); return; } chr = chars->data; bas->handle = chr->value_handle; DBG("Battery handle: 0x%04x", bas->handle); read_char(bas, bas->attrib, bas->handle, read_value_cb, bas); start = chr->value_handle + 1; end = bas->primary->range.end; bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); discover_desc(bas, bas->attrib, start, end, &uuid, discover_descriptor_cb, bas); } bool bt_bas_attach(struct bt_bas *bas, void *attrib) { if (!bas || bas->attrib || !bas->primary) return false; bas->attrib = g_attrib_ref(attrib); if (bas->handle > 0) return true; discover_char(bas, bas->attrib, bas->primary->range.start, bas->primary->range.end, NULL, bas_discovered_cb, bas); return true; } static void cancel_gatt_req(struct gatt_request *req) { if (g_attrib_cancel(req->bas->attrib, req->id)) destroy_gatt_req(req); } void bt_bas_detach(struct bt_bas *bas) { if (!bas || !bas->attrib) return; if (bas->id > 0) { g_attrib_unregister(bas->attrib, bas->id); bas->id = 0; } queue_foreach(bas->gatt_op, (void *) cancel_gatt_req, NULL); g_attrib_unref(bas->attrib); bas->attrib = NULL; } bluez-5.87/profiles/PaxHeaders/gap0000644000000000000000000000005015221755531014176 xustar0020 atime=1783094113 20 ctime=1783094105 bluez-5.87/profiles/gap/0000755000000000000000000000000015221755531013734 5ustar00rootrootbluez-5.87/profiles/gap/PaxHeaders/gas.c0000644000000000000000000000005015142177620015170 xustar0020 atime=1783093683 20 ctime=1783094105 bluez-5.87/profiles/gap/gas.c0000644000000000000000000002126015142177620014652 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT * Copyright (C) 2014 Google Inc. * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #define GAP_UUID16 0x1800 /* Generic Attribute/Access Service */ struct gas { struct btd_device *device; struct gatt_db *db; struct bt_gatt_client *client; struct gatt_db_attribute *attr; }; static void gas_reset(struct gas *gas) { gas->attr = NULL; gatt_db_unref(gas->db); gas->db = NULL; bt_gatt_client_unref(gas->client); gas->client = NULL; } static void gas_free(struct gas *gas) { gas_reset(gas); btd_device_unref(gas->device); g_free(gas); } static char *name2utf8(const uint8_t *name, uint16_t len) { char utf8_name[HCI_MAX_NAME_LENGTH + 2]; len = MIN(len, sizeof(utf8_name) - 1); memset(utf8_name, 0, sizeof(utf8_name)); strncpy(utf8_name, (char *) name, len); strtoutf8(utf8_name, len); /* Remove leading and trailing whitespace characters */ g_strstrip(utf8_name); return g_strdup(utf8_name); } static void read_device_name_cb(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct gas *gas = user_data; char *name; if (!success) { DBG("Reading device name failed with ATT error: %u", att_ecode); return; } if (!length) return; name = name2utf8(value, length); DBG("GAP Device Name: %s", name); btd_device_device_set_name(gas->device, name); g_free(name); } static void handle_device_name(struct gas *gas, uint16_t value_handle) { if (!bt_gatt_client_read_long_value(gas->client, value_handle, 0, read_device_name_cb, gas, NULL)) DBG("Failed to send request to read device name"); } static void read_appearance_cb(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct gas *gas = user_data; uint16_t appearance; if (!success) { DBG("Reading appearance failed with ATT error: %u", att_ecode); return; } /* The appearance value is a 16-bit unsigned integer */ if (length != 2) { DBG("Malformed appearance value"); return; } appearance = get_le16(value); DBG("GAP Appearance: 0x%04x", appearance); device_set_appearance(gas->device, appearance); } static void handle_appearance(struct gas *gas, uint16_t value_handle) { uint16_t value; if (!device_get_appearance(gas->device, &value)) return; if (!bt_gatt_client_read_value(gas->client, value_handle, read_appearance_cb, gas, NULL)) DBG("Failed to send request to read appearance"); } static void read_ppcp_cb(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct gas *gas = user_data; uint16_t min_interval, max_interval, latency, timeout, max_latency; if (!success) { DBG("Reading PPCP failed with ATT error: %u", att_ecode); return; } if (length != 8) { DBG("Malformed PPCP value"); return; } min_interval = get_le16(&value[0]); max_interval = get_le16(&value[2]); latency = get_le16(&value[4]); timeout = get_le16(&value[6]); /* 0xffff indicates no specific min/max */ if (min_interval == 0xffff) min_interval = 0x0018; /* 30.0ms */ if (max_interval == 0xffff) max_interval = 0x0028; /* 50.0ms */ DBG("GAP Peripheral Preferred Connection Parameters:"); DBG("\tMinimum connection interval: %u", min_interval); DBG("\tMaximum connection interval: %u", max_interval); DBG("\tSlave latency: %u", latency); DBG("\tConnection Supervision timeout multiplier: %u", timeout); /* avoid persisting connection parameters that are not valid */ if (min_interval > max_interval || min_interval < 6 || max_interval > 3200) { warn("GAS PPCP: Invalid Connection Parameters values"); return; } if (timeout < 10 || timeout > 3200) { warn("GAS PPCP: Invalid Connection Parameters values"); return; } if (max_interval >= timeout * 8) { warn("GAS PPCP: Invalid Connection Parameters values"); return; } max_latency = (timeout * 4 / max_interval) - 1; if (latency > 499 || latency > max_latency) { warn("GAS PPCP: Invalid Connection Parameters values"); return; } btd_device_set_conn_param(gas->device, min_interval, max_interval, latency, timeout); } static void handle_ppcp(struct gas *gas, uint16_t value_handle) { if (!bt_gatt_client_read_value(gas->client, value_handle, read_ppcp_cb, gas, NULL)) DBG("Failed to send request to read PPCP"); } static inline bool uuid_cmp(uint16_t u16, const bt_uuid_t *uuid) { bt_uuid_t lhs; bt_uuid16_create(&lhs, u16); return bt_uuid_cmp(&lhs, uuid) == 0; } static void handle_characteristic(struct gatt_db_attribute *attr, void *user_data) { struct gas *gas = user_data; uint16_t value_handle; bt_uuid_t uuid; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) { error("Failed to obtain characteristic data"); return; } if (uuid_cmp(GATT_CHARAC_DEVICE_NAME, &uuid)) handle_device_name(gas, value_handle); else if (uuid_cmp(GATT_CHARAC_APPEARANCE, &uuid)) handle_appearance(gas, value_handle); else if (uuid_cmp(GATT_CHARAC_PERIPHERAL_PREF_CONN, &uuid)) handle_ppcp(gas, value_handle); else { char uuid_str[MAX_LEN_UUID_STR]; /* TODO: Support peripheral privacy feature */ bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); DBG("Unsupported characteristic: %s", uuid_str); } } static int gap_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct gas *gas = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("GAP profile probe (%s)", addr); /* Ignore, if we were probed for this device already */ if (gas) { error("Profile probed twice for the same device!"); return -1; } gas = g_new0(struct gas, 1); if (!gas) return -1; gas->device = btd_device_ref(device); btd_service_set_user_data(service, gas); return 0; } static void gap_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct gas *gas; char addr[18]; ba2str(device_get_address(device), addr); DBG("GAP profile remove (%s)", addr); gas = btd_service_get_user_data(service); if (!gas) { error("GAP service not handled by profile"); return; } gas_free(gas); } static void foreach_gap_service(struct gatt_db_attribute *attr, void *user_data) { struct gas *gas = user_data; if (gas->attr) { error("More than one GAP service exists for this device"); return; } gas->attr = attr; gatt_db_service_foreach_char(gas->attr, handle_characteristic, gas); } static int gap_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct gatt_db *db = btd_device_get_gatt_db(device); struct bt_gatt_client *client = btd_device_get_gatt_client(device); struct gas *gas = btd_service_get_user_data(service); char addr[18]; bt_uuid_t gap_uuid; int err = 0; ba2str(device_get_address(device), addr); DBG("GAP profile accept (%s)", addr); if (!gas) { error("GAP service not handled by profile"); err = -1; goto _finish; } /* Check if attribute already has been discovered */ if (gas->attr) goto _finish; gas->db = gatt_db_ref(db); gas->client = bt_gatt_client_clone(client); /* Handle the GAP services */ bt_uuid16_create(&gap_uuid, GAP_UUID16); gatt_db_foreach_service(db, &gap_uuid, foreach_gap_service, gas); if (!gas->attr) { error("GAP attribute not found"); gas_reset(gas); err = -1; } _finish: btd_service_connecting_complete(service, err); return err; } static int gap_disconnect(struct btd_service *service) { btd_service_disconnecting_complete(service, 0); return 0; } static struct btd_profile gap_profile = { .name = "gap-profile", .bearer = BTD_PROFILE_BEARER_LE, .remote_uuid = GAP_UUID, .device_probe = gap_probe, .device_remove = gap_remove, .accept = gap_accept, .disconnect = gap_disconnect, }; static int gap_init(void) { return btd_profile_register(&gap_profile); } static void gap_exit(void) { btd_profile_unregister(&gap_profile); } BLUETOOTH_PLUGIN_DEFINE(gap, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, gap_init, gap_exit) bluez-5.87/PaxHeaders/config.guess0000644000000000000000000000005015221753000014163 xustar0020 atime=1783092749 20 ctime=1783094098 bluez-5.87/config.guess0000755000000000000000000014306715221753000013662 0ustar00rootroot#! /bin/sh # Attempt to guess a canonical system name. # Copyright 1992-2024 Free Software Foundation, Inc. # shellcheck disable=SC2006,SC2268 # see below for rationale timestamp='2024-07-27' # This file 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 3 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, see . # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that # program. This Exception is an additional permission under section 7 # of the GNU General Public License, version 3 ("GPLv3"). # # Originally written by Per Bothner; maintained since 2000 by Ben Elliston. # # You can get the latest version of this script from: # https://git.savannah.gnu.org/cgit/config.git/plain/config.guess # # Please send patches to . # The "shellcheck disable" line above the timestamp inhibits complaints # about features and limitations of the classic Bourne shell that were # superseded or lifted in POSIX. However, this script identifies a wide # variety of pre-POSIX systems that do not have POSIX shells at all, and # even some reasonably current systems (Solaris 10 as case-in-point) still # have a pre-POSIX /bin/sh. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] Output the configuration name of the system '$me' is run on. Options: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. Copyright 1992-2024 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try '$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit ;; --version | -v ) echo "$version" ; exit ;; --help | --h* | -h ) echo "$usage"; exit ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; * ) break ;; esac done if test $# != 0; then echo "$me: too many arguments$help" >&2 exit 1 fi # Just in case it came from the environment. GUESS= # CC_FOR_BUILD -- compiler used by this script. Note that the use of a # compiler to aid in system detection is discouraged as it requires # temporary files to be created and, as you can see below, it is a # headache to deal with in a portable fashion. # Historically, 'CC_FOR_BUILD' used to be named 'HOST_CC'. We still # use 'HOST_CC' if defined, but it is deprecated. # Portable tmp directory creation inspired by the Autoconf team. tmp= # shellcheck disable=SC2172 trap 'test -z "$tmp" || rm -fr "$tmp"' 0 1 2 13 15 set_cc_for_build() { # prevent multiple calls if $tmp is already set test "$tmp" && return 0 : "${TMPDIR=/tmp}" # shellcheck disable=SC2039,SC3028 { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir "$tmp" 2>/dev/null) ; } || { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir "$tmp" 2>/dev/null) && echo "Warning: creating insecure temp directory" >&2 ; } || { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } dummy=$tmp/dummy case ${CC_FOR_BUILD-},${HOST_CC-},${CC-} in ,,) echo "int x;" > "$dummy.c" for driver in cc gcc c17 c99 c89 ; do if ($driver -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then CC_FOR_BUILD=$driver break fi done if test x"$CC_FOR_BUILD" = x ; then CC_FOR_BUILD=no_compiler_found fi ;; ,,*) CC_FOR_BUILD=$CC ;; ,*,*) CC_FOR_BUILD=$HOST_CC ;; esac } # This is needed to find uname on a Pyramid OSx when run in the BSD universe. # (ghazi@noc.rutgers.edu 1994-08-24) if test -f /.attbin/uname ; then PATH=$PATH:/.attbin ; export PATH fi UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown case $UNAME_SYSTEM in Linux|GNU|GNU/*) LIBC=unknown set_cc_for_build cat <<-EOF > "$dummy.c" #if defined(__ANDROID__) LIBC=android #else #include #if defined(__UCLIBC__) LIBC=uclibc #elif defined(__dietlibc__) LIBC=dietlibc #elif defined(__GLIBC__) LIBC=gnu #elif defined(__LLVM_LIBC__) LIBC=llvm #else #include /* First heuristic to detect musl libc. */ #ifdef __DEFINED_va_list LIBC=musl #endif #endif #endif EOF cc_set_libc=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'` eval "$cc_set_libc" # Second heuristic to detect musl libc. if [ "$LIBC" = unknown ] && command -v ldd >/dev/null && ldd --version 2>&1 | grep -q ^musl; then LIBC=musl fi # If the system lacks a compiler, then just pick glibc. # We could probably try harder. if [ "$LIBC" = unknown ]; then LIBC=gnu fi ;; esac # Note: order is significant - the case branches are not exclusive. case $UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently # switched to ELF, *-*-netbsd* would select the old # object file format. This provides both forward # compatibility and a consistent mechanism for selecting the # object file format. # # Note: NetBSD doesn't particularly care about the vendor # portion of the name. We always set it to "unknown". UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \ /sbin/sysctl -n hw.machine_arch 2>/dev/null || \ /usr/sbin/sysctl -n hw.machine_arch 2>/dev/null || \ echo unknown)` case $UNAME_MACHINE_ARCH in aarch64eb) machine=aarch64_be-unknown ;; armeb) machine=armeb-unknown ;; arm*) machine=arm-unknown ;; sh3el) machine=shl-unknown ;; sh3eb) machine=sh-unknown ;; sh5el) machine=sh5le-unknown ;; earmv*) arch=`echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,'` endian=`echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p'` machine=${arch}${endian}-unknown ;; *) machine=$UNAME_MACHINE_ARCH-unknown ;; esac # The Operating System including object format, if it has switched # to ELF recently (or will in the future) and ABI. case $UNAME_MACHINE_ARCH in earm*) os=netbsdelf ;; arm*|i386|m68k|ns32k|sh3*|sparc|vax) set_cc_for_build if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ELF__ then # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). # Return netbsd for either. FIX? os=netbsd else os=netbsdelf fi ;; *) os=netbsd ;; esac # Determine ABI tags. case $UNAME_MACHINE_ARCH in earm*) expr='s/^earmv[0-9]/-eabi/;s/eb$//' abi=`echo "$UNAME_MACHINE_ARCH" | sed -e "$expr"` ;; esac # The OS release # Debian GNU/NetBSD machines have a different userland, and # thus, need a distinct triplet. However, they do not need # kernel version information, so it can be replaced with a # suitable tag, in the style of linux-gnu. case $UNAME_VERSION in Debian*) release='-gnu' ;; *) release=`echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2` ;; esac # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. GUESS=$machine-${os}${release}${abi-} ;; *:Bitrig:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-bitrig$UNAME_RELEASE ;; *:OpenBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-openbsd$UNAME_RELEASE ;; *:SecBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/SecBSD.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-secbsd$UNAME_RELEASE ;; *:LibertyBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-libertybsd$UNAME_RELEASE ;; *:MidnightBSD:*:*) GUESS=$UNAME_MACHINE-unknown-midnightbsd$UNAME_RELEASE ;; *:ekkoBSD:*:*) GUESS=$UNAME_MACHINE-unknown-ekkobsd$UNAME_RELEASE ;; *:SolidBSD:*:*) GUESS=$UNAME_MACHINE-unknown-solidbsd$UNAME_RELEASE ;; *:OS108:*:*) GUESS=$UNAME_MACHINE-unknown-os108_$UNAME_RELEASE ;; macppc:MirBSD:*:*) GUESS=powerpc-unknown-mirbsd$UNAME_RELEASE ;; *:MirBSD:*:*) GUESS=$UNAME_MACHINE-unknown-mirbsd$UNAME_RELEASE ;; *:Sortix:*:*) GUESS=$UNAME_MACHINE-unknown-sortix ;; *:Twizzler:*:*) GUESS=$UNAME_MACHINE-unknown-twizzler ;; *:Redox:*:*) GUESS=$UNAME_MACHINE-unknown-redox ;; mips:OSF1:*.*) GUESS=mips-dec-osf1 ;; alpha:OSF1:*:*) # Reset EXIT trap before exiting to avoid spurious non-zero exit code. trap '' 0 case $UNAME_RELEASE in *4.0) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` ;; *5.*) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` ;; esac # According to Compaq, /usr/sbin/psrinfo has been available on # OSF/1 and Tru64 systems produced since 1995. I hope that # covers most systems running today. This code pipes the CPU # types through head -n 1, so we only detect the type of CPU 0. ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` case $ALPHA_CPU_TYPE in "EV4 (21064)") UNAME_MACHINE=alpha ;; "EV4.5 (21064)") UNAME_MACHINE=alpha ;; "LCA4 (21066/21068)") UNAME_MACHINE=alpha ;; "EV5 (21164)") UNAME_MACHINE=alphaev5 ;; "EV5.6 (21164A)") UNAME_MACHINE=alphaev56 ;; "EV5.6 (21164PC)") UNAME_MACHINE=alphapca56 ;; "EV5.7 (21164PC)") UNAME_MACHINE=alphapca57 ;; "EV6 (21264)") UNAME_MACHINE=alphaev6 ;; "EV6.7 (21264A)") UNAME_MACHINE=alphaev67 ;; "EV6.8CB (21264C)") UNAME_MACHINE=alphaev68 ;; "EV6.8AL (21264B)") UNAME_MACHINE=alphaev68 ;; "EV6.8CX (21264D)") UNAME_MACHINE=alphaev68 ;; "EV6.9A (21264/EV69A)") UNAME_MACHINE=alphaev69 ;; "EV7 (21364)") UNAME_MACHINE=alphaev7 ;; "EV7.9 (21364A)") UNAME_MACHINE=alphaev79 ;; esac # A Pn.n version is a patched version. # A Vn.n version is a released version. # A Tn.n version is a released field test version. # A Xn.n version is an unreleased experimental baselevel. # 1.2 uses "1.2" for uname -r. OSF_REL=`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` GUESS=$UNAME_MACHINE-dec-osf$OSF_REL ;; Amiga*:UNIX_System_V:4.0:*) GUESS=m68k-unknown-sysv4 ;; *:[Aa]miga[Oo][Ss]:*:*) GUESS=$UNAME_MACHINE-unknown-amigaos ;; *:[Mm]orph[Oo][Ss]:*:*) GUESS=$UNAME_MACHINE-unknown-morphos ;; *:OS/390:*:*) GUESS=i370-ibm-openedition ;; *:z/VM:*:*) GUESS=s390-ibm-zvmoe ;; *:OS400:*:*) GUESS=powerpc-ibm-os400 ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) GUESS=arm-acorn-riscix$UNAME_RELEASE ;; arm*:riscos:*:*|arm*:RISCOS:*:*) GUESS=arm-unknown-riscos ;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) GUESS=hppa1.1-hitachi-hiuxmpp ;; Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. case `(/bin/universe) 2>/dev/null` in att) GUESS=pyramid-pyramid-sysv3 ;; *) GUESS=pyramid-pyramid-bsd ;; esac ;; NILE*:*:*:dcosx) GUESS=pyramid-pyramid-svr4 ;; DRS?6000:unix:4.0:6*) GUESS=sparc-icl-nx6 ;; DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) case `/usr/bin/uname -p` in sparc) GUESS=sparc-icl-nx7 ;; esac ;; s390x:SunOS:*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=$UNAME_MACHINE-ibm-solaris2$SUN_REL ;; sun4H:SunOS:5.*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=sparc-hal-solaris2$SUN_REL ;; sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=sparc-sun-solaris2$SUN_REL ;; i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) GUESS=i386-pc-auroraux$UNAME_RELEASE ;; i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) set_cc_for_build SUN_ARCH=i386 # If there is a compiler, see if it is configured for 64-bit objects. # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. # This test works for both compilers. if test "$CC_FOR_BUILD" != no_compiler_found; then if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -m64 -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then SUN_ARCH=x86_64 fi fi SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=$SUN_ARCH-pc-solaris2$SUN_REL ;; sun4*:SunOS:6*:*) # According to config.sub, this is the proper way to canonicalize # SunOS6. Hard to guess exactly what SunOS6 will be like, but # it's likely to be more like Solaris than SunOS4. SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=sparc-sun-solaris3$SUN_REL ;; sun4*:SunOS:*:*) case `/usr/bin/arch -k` in Series*|S4*) UNAME_RELEASE=`uname -v` ;; esac # Japanese Language versions have a version number like '4.1.3-JL'. SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/'` GUESS=sparc-sun-sunos$SUN_REL ;; sun3*:SunOS:*:*) GUESS=m68k-sun-sunos$UNAME_RELEASE ;; sun*:*:4.2BSD:*) UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3 case `/bin/arch` in sun3) GUESS=m68k-sun-sunos$UNAME_RELEASE ;; sun4) GUESS=sparc-sun-sunos$UNAME_RELEASE ;; esac ;; aushp:SunOS:*:*) GUESS=sparc-auspex-sunos$UNAME_RELEASE ;; # The situation for MiNT is a little confusing. The machine name # can be virtually everything (everything which is not # "atarist" or "atariste" at least should have a processor # > m68000). The system name ranges from "MiNT" over "FreeMiNT" # to the lowercase version "mint" (or "freemint"). Finally # the system name "TOS" denotes a system which is actually not # MiNT. But MiNT is downward compatible to TOS, so this should # be no problem. atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) GUESS=m68k-atari-mint$UNAME_RELEASE ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) GUESS=m68k-atari-mint$UNAME_RELEASE ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) GUESS=m68k-atari-mint$UNAME_RELEASE ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) GUESS=m68k-milan-mint$UNAME_RELEASE ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) GUESS=m68k-hades-mint$UNAME_RELEASE ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) GUESS=m68k-unknown-mint$UNAME_RELEASE ;; m68k:machten:*:*) GUESS=m68k-apple-machten$UNAME_RELEASE ;; powerpc:machten:*:*) GUESS=powerpc-apple-machten$UNAME_RELEASE ;; RISC*:Mach:*:*) GUESS=mips-dec-mach_bsd4.3 ;; RISC*:ULTRIX:*:*) GUESS=mips-dec-ultrix$UNAME_RELEASE ;; VAX*:ULTRIX*:*:*) GUESS=vax-dec-ultrix$UNAME_RELEASE ;; 2020:CLIX:*:* | 2430:CLIX:*:*) GUESS=clipper-intergraph-clix$UNAME_RELEASE ;; mips:*:*:UMIPS | mips:*:*:RISCos) set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #ifdef __cplusplus #include /* for printf() prototype */ int main (int argc, char *argv[]) { #else int main (argc, argv) int argc; char *argv[]; { #endif #if defined (host_mips) && defined (MIPSEB) #if defined (SYSTYPE_SYSV) printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_SVR4) printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0); #endif #endif exit (-1); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" && dummyarg=`echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p'` && SYSTEM_NAME=`"$dummy" "$dummyarg"` && { echo "$SYSTEM_NAME"; exit; } GUESS=mips-mips-riscos$UNAME_RELEASE ;; Motorola:PowerMAX_OS:*:*) GUESS=powerpc-motorola-powermax ;; Motorola:*:4.3:PL8-*) GUESS=powerpc-harris-powermax ;; Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) GUESS=powerpc-harris-powermax ;; Night_Hawk:Power_UNIX:*:*) GUESS=powerpc-harris-powerunix ;; m88k:CX/UX:7*:*) GUESS=m88k-harris-cxux7 ;; m88k:*:4*:R4*) GUESS=m88k-motorola-sysv4 ;; m88k:*:3*:R3*) GUESS=m88k-motorola-sysv3 ;; AViiON:dgux:*:*) # DG/UX returns AViiON for all architectures UNAME_PROCESSOR=`/usr/bin/uname -p` if test "$UNAME_PROCESSOR" = mc88100 || test "$UNAME_PROCESSOR" = mc88110 then if test "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx || \ test "$TARGET_BINARY_INTERFACE"x = x then GUESS=m88k-dg-dgux$UNAME_RELEASE else GUESS=m88k-dg-dguxbcs$UNAME_RELEASE fi else GUESS=i586-dg-dgux$UNAME_RELEASE fi ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) GUESS=m88k-dolphin-sysv3 ;; M88*:*:R3*:*) # Delta 88k system running SVR3 GUESS=m88k-motorola-sysv3 ;; XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) GUESS=m88k-tektronix-sysv3 ;; Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) GUESS=m68k-tektronix-bsd ;; *:IRIX*:*:*) IRIX_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/g'` GUESS=mips-sgi-irix$IRIX_REL ;; ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. GUESS=romp-ibm-aix # uname -m gives an 8 hex-code CPU id ;; # Note that: echo "'`uname -s`'" gives 'AIX ' i*86:AIX:*:*) GUESS=i386-ibm-aix ;; ia64:AIX:*:*) if test -x /usr/bin/oslevel ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=$UNAME_VERSION.$UNAME_RELEASE fi GUESS=$UNAME_MACHINE-ibm-aix$IBM_REV ;; *:AIX:2:3) if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #include int main () { if (!__power_pc()) exit(1); puts("powerpc-ibm-aix3.2.5"); exit(0); } EOF if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` then GUESS=$SYSTEM_NAME else GUESS=rs6000-ibm-aix3.2.5 fi elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then GUESS=rs6000-ibm-aix3.2.4 else GUESS=rs6000-ibm-aix3.2 fi ;; *:AIX:*:[4567]) IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then IBM_ARCH=rs6000 else IBM_ARCH=powerpc fi if test -x /usr/bin/lslpp ; then IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | \ awk -F: '{ print $3 }' | sed s/[0-9]*$/0/` else IBM_REV=$UNAME_VERSION.$UNAME_RELEASE fi GUESS=$IBM_ARCH-ibm-aix$IBM_REV ;; *:AIX:*:*) GUESS=rs6000-ibm-aix ;; ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*) GUESS=romp-ibm-bsd4.4 ;; ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and GUESS=romp-ibm-bsd$UNAME_RELEASE # 4.3 with uname added to ;; # report: romp-ibm BSD 4.3 *:BOSX:*:*) GUESS=rs6000-bull-bosx ;; DPX/2?00:B.O.S.:*:*) GUESS=m68k-bull-sysv3 ;; 9000/[34]??:4.3bsd:1.*:*) GUESS=m68k-hp-bsd ;; hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) GUESS=m68k-hp-bsd4.4 ;; 9000/[34678]??:HP-UX:*:*) HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'` case $UNAME_MACHINE in 9000/31?) HP_ARCH=m68000 ;; 9000/[34]??) HP_ARCH=m68k ;; 9000/[678][0-9][0-9]) if test -x /usr/bin/getconf; then sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` case $sc_cpu_version in 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1 532) # CPU_PA_RISC2_0 case $sc_kernel_bits in 32) HP_ARCH=hppa2.0n ;; 64) HP_ARCH=hppa2.0w ;; '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20 esac ;; esac fi if test "$HP_ARCH" = ""; then set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #define _HPUX_SOURCE #include #include int main () { #if defined(_SC_KERNEL_BITS) long bits = sysconf(_SC_KERNEL_BITS); #endif long cpu = sysconf (_SC_CPU_VERSION); switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0"); break; case CPU_PA_RISC1_1: puts ("hppa1.1"); break; case CPU_PA_RISC2_0: #if defined(_SC_KERNEL_BITS) switch (bits) { case 64: puts ("hppa2.0w"); break; case 32: puts ("hppa2.0n"); break; default: puts ("hppa2.0"); break; } break; #else /* !defined(_SC_KERNEL_BITS) */ puts ("hppa2.0"); break; #endif default: puts ("hppa1.0"); break; } exit (0); } EOF (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=`"$dummy"` test -z "$HP_ARCH" && HP_ARCH=hppa fi ;; esac if test "$HP_ARCH" = hppa2.0w then set_cc_for_build # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler # generating 64-bit code. GNU and HP use different nomenclature: # # $ CC_FOR_BUILD=cc ./config.guess # => hppa2.0w-hp-hpux11.23 # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess # => hppa64-hp-hpux11.23 if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | grep -q __LP64__ then HP_ARCH=hppa2.0w else HP_ARCH=hppa64 fi fi GUESS=$HP_ARCH-hp-hpux$HPUX_REV ;; ia64:HP-UX:*:*) HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'` GUESS=ia64-hp-hpux$HPUX_REV ;; 3050*:HI-UX:*:*) set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #include int main () { long cpu = sysconf (_SC_CPU_VERSION); /* The order matters, because CPU_IS_HP_MC68K erroneously returns true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct results, however. */ if (CPU_IS_PA_RISC (cpu)) { switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; default: puts ("hppa-hitachi-hiuxwe2"); break; } } else if (CPU_IS_HP_MC68K (cpu)) puts ("m68k-hitachi-hiuxwe2"); else puts ("unknown-hitachi-hiuxwe2"); exit (0); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` && { echo "$SYSTEM_NAME"; exit; } GUESS=unknown-hitachi-hiuxwe2 ;; 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*) GUESS=hppa1.1-hp-bsd ;; 9000/8??:4.3bsd:*:*) GUESS=hppa1.0-hp-bsd ;; *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) GUESS=hppa1.0-hp-mpeix ;; hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*) GUESS=hppa1.1-hp-osf ;; hp8??:OSF1:*:*) GUESS=hppa1.0-hp-osf ;; i*86:OSF1:*:*) if test -x /usr/sbin/sysversion ; then GUESS=$UNAME_MACHINE-unknown-osf1mk else GUESS=$UNAME_MACHINE-unknown-osf1 fi ;; parisc*:Lites*:*:*) GUESS=hppa1.1-hp-lites ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) GUESS=c1-convex-bsd ;; C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit ;; C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) GUESS=c34-convex-bsd ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) GUESS=c38-convex-bsd ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) GUESS=c4-convex-bsd ;; CRAY*Y-MP:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=ymp-cray-unicos$CRAY_REL ;; CRAY*[A-Z]90:*:*:*) echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ -e 's/\.[^.]*$/.X/' exit ;; CRAY*TS:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=t90-cray-unicos$CRAY_REL ;; CRAY*T3E:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=alphaev5-cray-unicosmk$CRAY_REL ;; CRAY*SV1:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=sv1-cray-unicos$CRAY_REL ;; *:UNICOS/mp:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=craynv-cray-unicosmp$CRAY_REL ;; F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` FUJITSU_REL=`echo "$UNAME_RELEASE" | sed -e 's/ /_/'` GUESS=${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL} ;; 5000:UNIX_System_V:4.*:*) FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` FUJITSU_REL=`echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'` GUESS=sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL} ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) GUESS=$UNAME_MACHINE-pc-bsdi$UNAME_RELEASE ;; sparc*:BSD/OS:*:*) GUESS=sparc-unknown-bsdi$UNAME_RELEASE ;; *:BSD/OS:*:*) GUESS=$UNAME_MACHINE-unknown-bsdi$UNAME_RELEASE ;; arm:FreeBSD:*:*) UNAME_PROCESSOR=`uname -p` set_cc_for_build if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_PCS_VFP then FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabi else FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabihf fi ;; *:FreeBSD:*:*) UNAME_PROCESSOR=`uname -p` case $UNAME_PROCESSOR in amd64) UNAME_PROCESSOR=x86_64 ;; i386) UNAME_PROCESSOR=i586 ;; esac FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL ;; i*:CYGWIN*:*) GUESS=$UNAME_MACHINE-pc-cygwin ;; *:MINGW64*:*) GUESS=$UNAME_MACHINE-pc-mingw64 ;; *:MINGW*:*) GUESS=$UNAME_MACHINE-pc-mingw32 ;; *:MSYS*:*) GUESS=$UNAME_MACHINE-pc-msys ;; i*:PW*:*) GUESS=$UNAME_MACHINE-pc-pw32 ;; *:SerenityOS:*:*) GUESS=$UNAME_MACHINE-pc-serenity ;; *:Interix*:*) case $UNAME_MACHINE in x86) GUESS=i586-pc-interix$UNAME_RELEASE ;; authenticamd | genuineintel | EM64T) GUESS=x86_64-unknown-interix$UNAME_RELEASE ;; IA64) GUESS=ia64-unknown-interix$UNAME_RELEASE ;; esac ;; i*:UWIN*:*) GUESS=$UNAME_MACHINE-pc-uwin ;; amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) GUESS=x86_64-pc-cygwin ;; prep*:SunOS:5.*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=powerpcle-unknown-solaris2$SUN_REL ;; *:GNU:*:*) # the GNU system GNU_ARCH=`echo "$UNAME_MACHINE" | sed -e 's,[-/].*$,,'` GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's,/.*$,,'` GUESS=$GNU_ARCH-unknown-$LIBC$GNU_REL ;; *:GNU/*:*:*) # other systems with GNU libc and userland GNU_SYS=`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"` GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_MACHINE-unknown-$GNU_SYS$GNU_REL-$LIBC ;; x86_64:[Mm]anagarm:*:*|i?86:[Mm]anagarm:*:*) GUESS="$UNAME_MACHINE-pc-managarm-mlibc" ;; *:[Mm]anagarm:*:*) GUESS="$UNAME_MACHINE-unknown-managarm-mlibc" ;; *:Minix:*:*) GUESS=$UNAME_MACHINE-unknown-minix ;; aarch64:Linux:*:*) set_cc_for_build CPU=$UNAME_MACHINE LIBCABI=$LIBC if test "$CC_FOR_BUILD" != no_compiler_found; then ABI=64 sed 's/^ //' << EOF > "$dummy.c" #ifdef __ARM_EABI__ #ifdef __ARM_PCS_VFP ABI=eabihf #else ABI=eabi #endif #endif EOF cc_set_abi=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^ABI' | sed 's, ,,g'` eval "$cc_set_abi" case $ABI in eabi | eabihf) CPU=armv8l; LIBCABI=$LIBC$ABI ;; esac fi GUESS=$CPU-unknown-linux-$LIBCABI ;; aarch64_be:Linux:*:*) UNAME_MACHINE=aarch64_be GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; alpha:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' /proc/cpuinfo 2>/dev/null` in EV5) UNAME_MACHINE=alphaev5 ;; EV56) UNAME_MACHINE=alphaev56 ;; PCA56) UNAME_MACHINE=alphapca56 ;; PCA57) UNAME_MACHINE=alphapca56 ;; EV6) UNAME_MACHINE=alphaev6 ;; EV67) UNAME_MACHINE=alphaev67 ;; EV68*) UNAME_MACHINE=alphaev68 ;; esac objdump --private-headers /bin/sh | grep -q ld.so.1 if test "$?" = 0 ; then LIBC=gnulibc1 ; fi GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; arc:Linux:*:* | arceb:Linux:*:* | arc32:Linux:*:* | arc64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; arm*:Linux:*:*) set_cc_for_build if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_EABI__ then GUESS=$UNAME_MACHINE-unknown-linux-$LIBC else if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_PCS_VFP then GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabi else GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabihf fi fi ;; avr32*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; cris:Linux:*:*) GUESS=$UNAME_MACHINE-axis-linux-$LIBC ;; crisv32:Linux:*:*) GUESS=$UNAME_MACHINE-axis-linux-$LIBC ;; e2k:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; frv:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; hexagon:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; i*86:Linux:*:*) GUESS=$UNAME_MACHINE-pc-linux-$LIBC ;; ia64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; k1om:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; kvx:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; kvx:cos:*:*) GUESS=$UNAME_MACHINE-unknown-cos ;; kvx:mbr:*:*) GUESS=$UNAME_MACHINE-unknown-mbr ;; loongarch32:Linux:*:* | loongarch64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; m32r*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; m68*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; mips:Linux:*:* | mips64:Linux:*:*) set_cc_for_build IS_GLIBC=0 test x"${LIBC}" = xgnu && IS_GLIBC=1 sed 's/^ //' << EOF > "$dummy.c" #undef CPU #undef mips #undef mipsel #undef mips64 #undef mips64el #if ${IS_GLIBC} && defined(_ABI64) LIBCABI=gnuabi64 #else #if ${IS_GLIBC} && defined(_ABIN32) LIBCABI=gnuabin32 #else LIBCABI=${LIBC} #endif #endif #if ${IS_GLIBC} && defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6 CPU=mipsisa64r6 #else #if ${IS_GLIBC} && !defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6 CPU=mipsisa32r6 #else #if defined(__mips64) CPU=mips64 #else CPU=mips #endif #endif #endif #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) MIPS_ENDIAN=el #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) MIPS_ENDIAN= #else MIPS_ENDIAN= #endif #endif EOF cc_set_vars=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI'` eval "$cc_set_vars" test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; } ;; mips64el:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; openrisc*:Linux:*:*) GUESS=or1k-unknown-linux-$LIBC ;; or32:Linux:*:* | or1k*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; padre:Linux:*:*) GUESS=sparc-unknown-linux-$LIBC ;; parisc64:Linux:*:* | hppa64:Linux:*:*) GUESS=hppa64-unknown-linux-$LIBC ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in PA7*) GUESS=hppa1.1-unknown-linux-$LIBC ;; PA8*) GUESS=hppa2.0-unknown-linux-$LIBC ;; *) GUESS=hppa-unknown-linux-$LIBC ;; esac ;; ppc64:Linux:*:*) GUESS=powerpc64-unknown-linux-$LIBC ;; ppc:Linux:*:*) GUESS=powerpc-unknown-linux-$LIBC ;; ppc64le:Linux:*:*) GUESS=powerpc64le-unknown-linux-$LIBC ;; ppcle:Linux:*:*) GUESS=powerpcle-unknown-linux-$LIBC ;; riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; s390:Linux:*:* | s390x:Linux:*:*) GUESS=$UNAME_MACHINE-ibm-linux-$LIBC ;; sh64*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; sh*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; sparc:Linux:*:* | sparc64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; tile*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; vax:Linux:*:*) GUESS=$UNAME_MACHINE-dec-linux-$LIBC ;; x86_64:Linux:*:*) set_cc_for_build CPU=$UNAME_MACHINE LIBCABI=$LIBC if test "$CC_FOR_BUILD" != no_compiler_found; then ABI=64 sed 's/^ //' << EOF > "$dummy.c" #ifdef __i386__ ABI=x86 #else #ifdef __ILP32__ ABI=x32 #endif #endif EOF cc_set_abi=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^ABI' | sed 's, ,,g'` eval "$cc_set_abi" case $ABI in x86) CPU=i686 ;; x32) LIBCABI=${LIBC}x32 ;; esac fi GUESS=$CPU-pc-linux-$LIBCABI ;; xtensa*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; i*86:DYNIX/ptx:4*:*) # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. # earlier versions are messed up and put the nodename in both # sysname and nodename. GUESS=i386-sequent-sysv4 ;; i*86:UNIX_SV:4.2MP:2.*) # Unixware is an offshoot of SVR4, but it has its own version # number series starting with 2... # I am not positive that other SVR4 systems won't match this, # I just have to hope. -- rms. # Use sysv4.2uw... so that sysv4* matches it. GUESS=$UNAME_MACHINE-pc-sysv4.2uw$UNAME_VERSION ;; i*86:OS/2:*:*) # If we were able to find 'uname', then EMX Unix compatibility # is probably installed. GUESS=$UNAME_MACHINE-pc-os2-emx ;; i*86:XTS-300:*:STOP) GUESS=$UNAME_MACHINE-unknown-stop ;; i*86:atheos:*:*) GUESS=$UNAME_MACHINE-unknown-atheos ;; i*86:syllable:*:*) GUESS=$UNAME_MACHINE-pc-syllable ;; i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) GUESS=i386-unknown-lynxos$UNAME_RELEASE ;; i*86:*DOS:*:*) GUESS=$UNAME_MACHINE-pc-msdosdjgpp ;; i*86:*:4.*:*) UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'` if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then GUESS=$UNAME_MACHINE-univel-sysv$UNAME_REL else GUESS=$UNAME_MACHINE-pc-sysv$UNAME_REL fi ;; i*86:*:5:[678]*) # UnixWare 7.x, OpenUNIX and OpenServer 6. case `/bin/uname -X | grep "^Machine"` in *486*) UNAME_MACHINE=i486 ;; *Pentium) UNAME_MACHINE=i586 ;; *Pent*|*Celeron) UNAME_MACHINE=i686 ;; esac GUESS=$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} ;; i*86:*:3.2:*) if test -f /usr/options/cb.name; then UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ && UNAME_MACHINE=i586 (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ && UNAME_MACHINE=i686 (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ && UNAME_MACHINE=i686 GUESS=$UNAME_MACHINE-pc-sco$UNAME_REL else GUESS=$UNAME_MACHINE-pc-sysv32 fi ;; pc:*:*:*) # Left here for compatibility: # uname -m prints for DJGPP always 'pc', but it prints nothing about # the processor, so we play safe by assuming i586. # Note: whatever this is, it MUST be the same as what config.sub # prints for the "djgpp" host, or else GDB configure will decide that # this is a cross-build. GUESS=i586-pc-msdosdjgpp ;; Intel:Mach:3*:*) GUESS=i386-pc-mach3 ;; paragon:*:*:*) GUESS=i860-intel-osf1 ;; i860:*:4.*:*) # i860-SVR4 if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then GUESS=i860-stardent-sysv$UNAME_RELEASE # Stardent Vistra i860-SVR4 else # Add other i860-SVR4 vendors below as they are discovered. GUESS=i860-unknown-sysv$UNAME_RELEASE # Unknown i860-SVR4 fi ;; mini*:CTIX:SYS*5:*) # "miniframe" GUESS=m68010-convergent-sysv ;; mc68k:UNIX:SYSTEM5:3.51m) GUESS=m68k-convergent-sysv ;; M680?0:D-NIX:5.3:*) GUESS=m68k-diab-dnix ;; M68*:*:R3V[5678]*:*) test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) OS_REL='' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4; exit; } ;; NCR*:*:4.2:* | MPRAS*:*:4.2:*) OS_REL='.3' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) GUESS=m68k-unknown-lynxos$UNAME_RELEASE ;; mc68030:UNIX_System_V:4.*:*) GUESS=m68k-atari-sysv4 ;; TSUNAMI:LynxOS:2.*:*) GUESS=sparc-unknown-lynxos$UNAME_RELEASE ;; rs6000:LynxOS:2.*:*) GUESS=rs6000-unknown-lynxos$UNAME_RELEASE ;; PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) GUESS=powerpc-unknown-lynxos$UNAME_RELEASE ;; SM[BE]S:UNIX_SV:*:*) GUESS=mips-dde-sysv$UNAME_RELEASE ;; RM*:ReliantUNIX-*:*:*) GUESS=mips-sni-sysv4 ;; RM*:SINIX-*:*:*) GUESS=mips-sni-sysv4 ;; *:SINIX-*:*:*) if uname -p 2>/dev/null >/dev/null ; then UNAME_MACHINE=`(uname -p) 2>/dev/null` GUESS=$UNAME_MACHINE-sni-sysv4 else GUESS=ns32k-sni-sysv fi ;; PENTIUM:*:4.0*:*) # Unisys 'ClearPath HMP IX 4000' SVR4/MP effort # says GUESS=i586-unisys-sysv4 ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm GUESS=hppa1.1-stratus-sysv4 ;; *:*:*:FTX*) # From seanf@swdc.stratus.com. GUESS=i860-stratus-sysv4 ;; i*86:VOS:*:*) # From Paul.Green@stratus.com. GUESS=$UNAME_MACHINE-stratus-vos ;; *:VOS:*:*) # From Paul.Green@stratus.com. GUESS=hppa1.1-stratus-vos ;; mc68*:A/UX:*:*) GUESS=m68k-apple-aux$UNAME_RELEASE ;; news*:NEWS-OS:6*:*) GUESS=mips-sony-newsos6 ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) if test -d /usr/nec; then GUESS=mips-nec-sysv$UNAME_RELEASE else GUESS=mips-unknown-sysv$UNAME_RELEASE fi ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. GUESS=powerpc-be-beos ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. GUESS=powerpc-apple-beos ;; BePC:BeOS:*:*) # BeOS running on Intel PC compatible. GUESS=i586-pc-beos ;; BePC:Haiku:*:*) # Haiku running on Intel PC compatible. GUESS=i586-pc-haiku ;; ppc:Haiku:*:*) # Haiku running on Apple PowerPC GUESS=powerpc-apple-haiku ;; *:Haiku:*:*) # Haiku modern gcc (not bound by BeOS compat) GUESS=$UNAME_MACHINE-unknown-haiku ;; SX-4:SUPER-UX:*:*) GUESS=sx4-nec-superux$UNAME_RELEASE ;; SX-5:SUPER-UX:*:*) GUESS=sx5-nec-superux$UNAME_RELEASE ;; SX-6:SUPER-UX:*:*) GUESS=sx6-nec-superux$UNAME_RELEASE ;; SX-7:SUPER-UX:*:*) GUESS=sx7-nec-superux$UNAME_RELEASE ;; SX-8:SUPER-UX:*:*) GUESS=sx8-nec-superux$UNAME_RELEASE ;; SX-8R:SUPER-UX:*:*) GUESS=sx8r-nec-superux$UNAME_RELEASE ;; SX-ACE:SUPER-UX:*:*) GUESS=sxace-nec-superux$UNAME_RELEASE ;; Power*:Rhapsody:*:*) GUESS=powerpc-apple-rhapsody$UNAME_RELEASE ;; *:Rhapsody:*:*) GUESS=$UNAME_MACHINE-apple-rhapsody$UNAME_RELEASE ;; arm64:Darwin:*:*) GUESS=aarch64-apple-darwin$UNAME_RELEASE ;; *:Darwin:*:*) UNAME_PROCESSOR=`uname -p` case $UNAME_PROCESSOR in unknown) UNAME_PROCESSOR=powerpc ;; esac if command -v xcode-select > /dev/null 2> /dev/null && \ ! xcode-select --print-path > /dev/null 2> /dev/null ; then # Avoid executing cc if there is no toolchain installed as # cc will be a stub that puts up a graphical alert # prompting the user to install developer tools. CC_FOR_BUILD=no_compiler_found else set_cc_for_build fi if test "$CC_FOR_BUILD" != no_compiler_found; then if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then case $UNAME_PROCESSOR in i386) UNAME_PROCESSOR=x86_64 ;; powerpc) UNAME_PROCESSOR=powerpc64 ;; esac fi # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_PPC >/dev/null then UNAME_PROCESSOR=powerpc fi elif test "$UNAME_PROCESSOR" = i386 ; then # uname -m returns i386 or x86_64 UNAME_PROCESSOR=$UNAME_MACHINE fi GUESS=$UNAME_PROCESSOR-apple-darwin$UNAME_RELEASE ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=`uname -p` if test "$UNAME_PROCESSOR" = x86; then UNAME_PROCESSOR=i386 UNAME_MACHINE=pc fi GUESS=$UNAME_PROCESSOR-$UNAME_MACHINE-nto-qnx$UNAME_RELEASE ;; *:QNX:*:4*) GUESS=i386-pc-qnx ;; NEO-*:NONSTOP_KERNEL:*:*) GUESS=neo-tandem-nsk$UNAME_RELEASE ;; NSE-*:NONSTOP_KERNEL:*:*) GUESS=nse-tandem-nsk$UNAME_RELEASE ;; NSR-*:NONSTOP_KERNEL:*:*) GUESS=nsr-tandem-nsk$UNAME_RELEASE ;; NSV-*:NONSTOP_KERNEL:*:*) GUESS=nsv-tandem-nsk$UNAME_RELEASE ;; NSX-*:NONSTOP_KERNEL:*:*) GUESS=nsx-tandem-nsk$UNAME_RELEASE ;; *:NonStop-UX:*:*) GUESS=mips-compaq-nonstopux ;; BS2000:POSIX*:*:*) GUESS=bs2000-siemens-sysv ;; DS/*:UNIX_System_V:*:*) GUESS=$UNAME_MACHINE-$UNAME_SYSTEM-$UNAME_RELEASE ;; *:Plan9:*:*) # "uname -m" is not consistent, so use $cputype instead. 386 # is converted to i386 for consistency with other x86 # operating systems. if test "${cputype-}" = 386; then UNAME_MACHINE=i386 elif test "x${cputype-}" != x; then UNAME_MACHINE=$cputype fi GUESS=$UNAME_MACHINE-unknown-plan9 ;; *:TOPS-10:*:*) GUESS=pdp10-unknown-tops10 ;; *:TENEX:*:*) GUESS=pdp10-unknown-tenex ;; KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) GUESS=pdp10-dec-tops20 ;; XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) GUESS=pdp10-xkl-tops20 ;; *:TOPS-20:*:*) GUESS=pdp10-unknown-tops20 ;; *:ITS:*:*) GUESS=pdp10-unknown-its ;; SEI:*:*:SEIUX) GUESS=mips-sei-seiux$UNAME_RELEASE ;; *:DragonFly:*:*) DRAGONFLY_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_MACHINE-unknown-dragonfly$DRAGONFLY_REL ;; *:*VMS:*:*) UNAME_MACHINE=`(uname -p) 2>/dev/null` case $UNAME_MACHINE in A*) GUESS=alpha-dec-vms ;; I*) GUESS=ia64-dec-vms ;; V*) GUESS=vax-dec-vms ;; esac ;; *:XENIX:*:SysV) GUESS=i386-pc-xenix ;; i*86:skyos:*:*) SKYOS_REL=`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'` GUESS=$UNAME_MACHINE-pc-skyos$SKYOS_REL ;; i*86:rdos:*:*) GUESS=$UNAME_MACHINE-pc-rdos ;; i*86:Fiwix:*:*) GUESS=$UNAME_MACHINE-pc-fiwix ;; *:AROS:*:*) GUESS=$UNAME_MACHINE-unknown-aros ;; x86_64:VMkernel:*:*) GUESS=$UNAME_MACHINE-unknown-esx ;; amd64:Isilon\ OneFS:*:*) GUESS=x86_64-unknown-onefs ;; *:Unleashed:*:*) GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE ;; *:Ironclad:*:*) GUESS=$UNAME_MACHINE-unknown-ironclad ;; esac # Do we have a guess based on uname results? if test "x$GUESS" != x; then echo "$GUESS" exit fi # No uname command or uname output not recognized. set_cc_for_build cat > "$dummy.c" < #include #endif #if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__) #if defined (vax) || defined (__vax) || defined (__vax__) || defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__) #include #if defined(_SIZE_T_) || defined(SIGLOST) #include #endif #endif #endif int main () { #if defined (sony) #if defined (MIPSEB) /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, I don't know.... */ printf ("mips-sony-bsd\n"); exit (0); #else #include printf ("m68k-sony-newsos%s\n", #ifdef NEWSOS4 "4" #else "" #endif ); exit (0); #endif #endif #if defined (NeXT) #if !defined (__ARCHITECTURE__) #define __ARCHITECTURE__ "m68k" #endif int version; version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; if (version < 4) printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); else printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); exit (0); #endif #if defined (MULTIMAX) || defined (n16) #if defined (UMAXV) printf ("ns32k-encore-sysv\n"); exit (0); #else #if defined (CMU) printf ("ns32k-encore-mach\n"); exit (0); #else printf ("ns32k-encore-bsd\n"); exit (0); #endif #endif #endif #if defined (__386BSD__) printf ("i386-pc-bsd\n"); exit (0); #endif #if defined (sequent) #if defined (i386) printf ("i386-sequent-dynix\n"); exit (0); #endif #if defined (ns32000) printf ("ns32k-sequent-dynix\n"); exit (0); #endif #endif #if defined (_SEQUENT_) struct utsname un; uname(&un); if (strncmp(un.version, "V2", 2) == 0) { printf ("i386-sequent-ptx2\n"); exit (0); } if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ printf ("i386-sequent-ptx1\n"); exit (0); } printf ("i386-sequent-ptx\n"); exit (0); #endif #if defined (vax) #if !defined (ultrix) #include #if defined (BSD) #if BSD == 43 printf ("vax-dec-bsd4.3\n"); exit (0); #else #if BSD == 199006 printf ("vax-dec-bsd4.3reno\n"); exit (0); #else printf ("vax-dec-bsd\n"); exit (0); #endif #endif #else printf ("vax-dec-bsd\n"); exit (0); #endif #else #if defined(_SIZE_T_) || defined(SIGLOST) struct utsname un; uname (&un); printf ("vax-dec-ultrix%s\n", un.release); exit (0); #else printf ("vax-dec-ultrix\n"); exit (0); #endif #endif #endif #if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__) #if defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__) #if defined(_SIZE_T_) || defined(SIGLOST) struct utsname *un; uname (&un); printf ("mips-dec-ultrix%s\n", un.release); exit (0); #else printf ("mips-dec-ultrix\n"); exit (0); #endif #endif #endif #if defined (alliant) && defined (i860) printf ("i860-alliant-bsd\n"); exit (0); #endif exit (1); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null && SYSTEM_NAME=`"$dummy"` && { echo "$SYSTEM_NAME"; exit; } # Apollos put the system type in the environment. test -d /usr/apollo && { echo "$ISP-apollo-$SYSTYPE"; exit; } echo "$0: unable to guess system type" >&2 case $UNAME_MACHINE:$UNAME_SYSTEM in mips:Linux | mips64:Linux) # If we got here on MIPS GNU/Linux, output extra information. cat >&2 <&2 <&2 </dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` /bin/uname -X = `(/bin/uname -X) 2>/dev/null` hostinfo = `(hostinfo) 2>/dev/null` /bin/universe = `(/bin/universe) 2>/dev/null` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` /bin/arch = `(/bin/arch) 2>/dev/null` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` UNAME_MACHINE = "$UNAME_MACHINE" UNAME_RELEASE = "$UNAME_RELEASE" UNAME_SYSTEM = "$UNAME_SYSTEM" UNAME_VERSION = "$UNAME_VERSION" EOF fi exit 1 # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: bluez-5.87/PaxHeaders/ChangeLog0000644000000000000000000000005015221752714013433 xustar0020 atime=1783092684 20 ctime=1783094098 bluez-5.87/ChangeLog0000644000000000000000000030711015221752714013116 0ustar00rootrootver 5.87: Fix issue with GATT database and out of sync errors. Fix issue with BASS and setting a stream to idle. Fix issue with BASS and rescanning broadcast sources. Fix issue with BAP and broadcast sink cleanup. Fix issue with BAP and endpoint configuration. Fix issue with BAP and ASE control point properties. Fix issue with BAP and BIG/BIS receiver QoS structures. Fix issue with AVRCP and tracking of TG and CT events. Fix issue with PBAP and Database Identifier length. Fix issue with MCP and ATT disconnect events. ver 5.86: Fix issue with number of retries on authentication failures. Fix issue with G.722 @ 16 kHz codec ID value reported by transport. Add support for Telephony interface. Add support for Ranging profile. Add support for GMAP service. Add support for TMAP service. ver 5.85: Fix issue with handling display of battery charge level. Fix issue with BASS permissions not requiring encryption. Fix issue with handling abort for OBEX SRM operation. Fix issue with handling device privacy. Add support for HFP call answer support. Add support for HFP simple 3-way call support. ver 5.84: Fix issue with AVRCP and handling invalid UTF-8 item name. Fix issue with exposing coordinate sets if LE Audio is disabled. Fix issue with BAP and not responding to SetConfiguration. Add support for BAP unicast endpoint reconfiguration. Add support for BASS and encrypted broadcast source. Add support for HFP and Call Line Identification. ver 5.83: Fix issue with handling BAP and removal of PAC. Fix issue with handling SID for broadcast receiver. Fix issue with handling HSP/HFP reconnection policy. Fix issue with handling cable pairing and Sixaxis controllers. Fix issue with handling virtual cable unplug for HID devices. Fix issue with handling service records for HID devices. Add support for AVDTP and TX timestamps. ver 5.82: Fix issue with handling BAP state transitions. Fix issue with handling D-Bus interface removal. ver 5.81: Fix issue with handling MAP and supported features. Fix issue with handling SDP record for Phonebook Access Client. Fix issue with handling AVRCP PDU parameters length mismatch. Fix issue with handling AVRCP PDU for SetAbsoluteVolume. Fix issue with handling AVDTP bad media transport format. Fix issue with handling support for LL Privacy setting. ver 5.80: Fix issue with handling address type for all types of keys. Fix issue with handling maximum number of GATT channels. Fix issue with handling MTU auto-tuning feature. Fix issue with handling AVRCP volume in reconfigured transports. Fix issue with handling VCP volume setting requests. Fix issue with handling VCP connection management. Fix issue with handling MAP qualification. Fix issue with handling PBAP qualification. Fix issue with handling BNEP qualification. Add support for PreferredBearer device property. Add support for SupportedTypes Message Access property. Add support for HFP, A2DP, AVRCP, AVCTP and MAP latest versions. ver 5.79: Fix issue with handling address type while pairing. Add support for allowing to set A2DP transport delay. Add support for persistent userspace HID operation. Add support for handling syncing to multiple BISes. ver 5.78: Fix issue with handling notification of scanned BISes to BASS Fix issue with handling checking BIS caps against peer caps. Fix issue with handling MGMT Set Device Flags overwrites. Fix issue with handling ASE notification order. Fix issue with handling BIG Info report events. Fix issue with handling PACS Server role. Fix issue with registering UHID_START multiple times. Fix issue with pairing method not setting auto-connect. ver 5.77: Fix issue with storing and handling connection parameters. Fix issue with handling device that are marked as temporary. Fix issue with HID and special handling for non-keyboards. Fix issue with BR/EDR not support when discoverable is off. Add support for initial implementation of ASHA profile. ver 5.76: Fix issue with broadcast channel location and stream capabilities. Fix issue with handling BIS management and synchronization. Fix issue with handling Extended Advertising. Fix issue with UserspaceHID and replay structures. Add support for providing PPCP characteristic. ver 5.75: Fix issue with build system and header inclusion. ver 5.74: Fix issue with not enabling Wideband Speech when available. Fix issue with UserspaceHID and Bluetooth Classic devices. Fix issue with checking for services being connected. Fix issue with GATT client connection creation. Fix issue with OBEX and small file transfers. Fix issue with handling pairing with Apple AirPods. ver 5.73: Fix issue with BAP and setting up broadcast source. Fix issue with BAP and register all endpoints. Fix issue with BAP and missing metadata property. Fix issue with BAP and not handling out of order responses. Fix issue with BAP and attempting to set device as connectable. Add support for CCP plugin for call control profile. ver 5.72: Fix issue with BAP and handling stream IO linking. Fix issue with BAP and setup of multiple streams per endpoint. Fix issue with AVDTP and potential incorrect transaction label. Fix issue with A2DP and handling crash on suspend. Fix issue with GATT database and an invalid pointer. Add support for AICS service. ver 5.71: Fix issue with not registering CSIS service. Fix issue with registering pairing callbacks. Fix issue with corruption during discovery filter parsing. ver 5.70: Fix issue with not sending GATT confirmations. Fix issue with not handling initiator properly. Fix issue with not checking PBAP counter length. Add support for MICP profile and MICS service. ver 5.69: Fix issue with BAP enabling state correctly when resuming. Fix issue with detaching source ASEs only after Stop Ready. Fix issue with handling VCP audio location and descriptor. Fix issue with generating IRK for adapter with privacy enabled. Add support for BAP broadcast sink. ver 5.68: Fix issue with A2DP and handling of Transport.Acquire. ver 5.67: Fix issue with BAP and initiating QoS and Enable procedures. Fix issue with BAP and detaching streams when PAC is removed. Fix issue with BAP and reading all instances of PAC. Fix issue with BAP and not being able to reconfigure. Fix issue with BAP and transport configuration changes. Fix issue with BAP and handling unexpected disconnect. Fix issue with GATT and not removing pending services. Fix issue with GATT and client ready handling. Fix issue with handling fallback to transient hostname. Add support for SecureConnections configuration option. Add support for Mesh Remove Provisioning. Add support for Mesh Private Beacons. ver 5.66: Fix issue with A2DP and transport connection collisions. Fix issue with allowing application specific error codes. Fix issue with not setting initiator flag correctly. Fix issue with HoG Report MAP size handling. Add initial support for Basic Audio Profile. Add initial support for Volume Control Profile. ver 5.65: Fix issue with A2DP cache invalidation handling. Fix issue with A2DP and not initialized SEP codec. Fix issue with A2DP and multiple SetConfiguration to same SEP Fix issue with AVRCP and not properly initialized volume. Fix issue with SDP records when operating in LE only mode. Fix issue with HoG and not reading report map of instances. Fix issue with GATT server crashing while disconnecting. Fix issue with not removing connected devices. Fix issue with enabling wake support without RPA Resolution. Fix issue with pairing failed due to the error of Already Paired. Add support for CONFIGURATION_DIRECTORY environment variable. Add support for STATE_DIRECTORY environment variable. Add support for "Bonded" property with Device API. Add experimental support for ISO socket. ver 5.64: Fix issue with handling A2DP discover procedure. Fix issue with media endpoint replies and SetConfiguration. Fix issue with HoG queuing events before report map is read. Fix issue with HoG and read order of GATT attributes. Fix issue with HoG and not using UHID_CREATE2 interface. Fix issue with failed scanning for 5 minutes after reboot. ver 5.63: Fix issue with storing IRK causing invalid read access. Fix issue with disconnecting due to GattCharacteristic1.MTU. Add support for Device{Found,Lost} of advertising monitoring. ver 5.62: Fix issue with handling truncation when loading LTKs. Fix issue with accepting Exchange MTU on EATT bearer. Fix issue with clearing DeviceLost timers on power down. Fix issue with AVCTP browsing channel and missing ERTM. Fix issue with AVDTP and local SEID pool for each adapter. Add support for BR/EDR and LE connection failure reasons. ver 5.61: Fix issue with A2DP while waiting for command response. Fix issue with A2DP when SetConfiguration fails. Fix issue with device removal handling. Fix issue with storing discoverable setting. Add support for Central Address Resolution characteristic. Add support for admin policy plugin. ver 5.60: Fix issue with reading from RFKILL device node. Fix issue with AVDTP and parsing capabilities. Fix issue with UnregisterApplication handling. Fix issue with RegisterProfile if UUID already exists. Fix issue with GATT client attribute read with offset. Fix issue with non-discoverable device and advertising monitor. ver 5.59: Fix issue with string to UUID-32 conversion. Fix issue with connect request if SDP search failed. Fix issue with accepting invalid AVDTP capabilities. Fix issue with unregister handling of AVRCP player. ver 5.58: Fix issue with usage of deprecated GLib functions. ver 5.57: Fix issue with handling GATT notification PDU parsing. Fix issue with registering DIS without a valid source. Fix issue with removing remote SEPs when loading from cache. ver 5.56: Fix issue with setting AVDTP disconnect timer. Fix issue with AVDTP not sending GetCapabilities. Fix issue with AVDTP connecting using streaming mode. Fix issue with handling A2DP and remote SEP disappearing. Fix issue with handling session of A2DP channels. Fix issue with GATT and handling device removal. Fix issue with GATT not accepting multiple requests. Fix issue with HID report value callback registration. Add support for new advertising management command. Add support for battery D-Bus interface. ver 5.55: Fix issue with handling security level for HoG. Fix issue with handling HIDSDPDisable attribute. Fix issue with handling HID virtual cable unplug. Fix issue with handling HID channel disconnect order. Fix issue with handling AVDTP delay reporting states. Fix issue with handling AVRCP notification events. Fix issue with handling AVRCP list player attributes. Fix issue with handling AVRCP category 1 player settings. Fix issue with handling AVRCP media player passthrough bitmask. Fix issue with handling HFP 1.7 default features. Fix issue with handling GATT disconnecting handling. Fix issue with handling GATT database hash. Fix issue with handling service changed characteristic. Fix issue with handling read of multiple characteristic values. Fix issue with handling Just-Works auto-accept pairing. Fix issue with handling authentication of bonded devices. Fix issue with handling L2CAP streaming mode for AVDTP. Fix issue with handling SysEx parser for MIDI support. Fix issue with handling configured scan parameter values. Fix issue with handling temporary devices removal. Fix issue with handling advertising flags. ver 5.54: Fix issue with HOGP to accept data only from bonded devices. Fix issue with A2DP sessions being connected at the same time. Fix issue with class UUID matches before connecting profile. Add support for handling MTU auto-tuning option for AVDTP. Add support for new policy for Just-Works repairing. Add support for Enhanced ATT bearer (EATT). ver 5.53: Fix issue with handling unregistration for advertisment. Fix issue with A2DP and handling recovering process. Fix issue with udpating input device information. Add support for loading blocked keys. ver 5.52: Fix issue with AVDTP session disconnect timeout handling. Mark media endpoint APIs as stable interfaces. ver 5.51: Fix issue with first agent not being registered as default. Fix issue with loading devices without Service Changed CCC. Fix issue with GATT client and extended property reading. Fix issue with handling GATT client invalid read behavior. Fix issue with handling GATT disconnect handler removal. Fix issue with missing GATT/GAP service records for SDP. Fix issue with checking SDP continuation state length. Fix issue with HID device removal on HoG disconnect. Fix issue with AVDTP and session destroy handling. Fix issue with AVCTP and output MTU accounting. Fix issue with AVRCP and creating media items. Add support for GATT database caching feature. Add experimental support for Bluetooth Mesh Profile. ver 5.50: Fix issue with GATT and reading long values. Fix issue with GATT and reading multiple includes. Fix issue with GATT and service changes when offline. Fix issue with handling secondary service discovery. Fix issue with handling persistency of CCC values. Fix issue with handling Mesh session on disconnection. Fix issue with handling Mesh proxy PDU SAR message length. Fix issue with handling Mesh default heartbeat TTL value. Add support for Mesh node-reset operation handling. Add support for GATT authorization request handling. Add support for GATT minimum key size requirements. Add support for GATT server and included services. Add support for handling additional advertising data. Add support for handling separate discoverable state. Add support for enabling HFP version 1.7 features. Add support for dedicated Bluetooth logging daemon. ver 5.49: Fix issue with configuring discoverable advertising flag. Fix issue with bearer selection and single mode controllers. Fix issue with Connect and ConnectProfile returning in progress. Fix issue with missing Paired property change when not bonded. Fix issue with storage for controllers without public address. Fix issue with handling AVCTP disconnecting the channel queue. Fix issue with not clearing connectable setting on power off. Fix issue with creating multiple mgmt socket instances. Fix issue with GATT server and BR/EDR only devices. Fix issue with InterfaceAdded event ordering. Add support for generic ConnectDevice method call. Add support for Mesh heartbeat client functionality. ver 5.48: Fix issue with subscriptions for unpaired devices. Fix issue with handling A2DP and no available SEP. Fix issue with handling AVCTP change path support. Fix issue with handling AVCTP browsing channel. Fix issue with handling AVCTP passthrough PDUs. Fix issue with handling detaching of controller. Fix issue with handling start discovery results. Fix issue with handling non-connectable devices. Fix issue with handling unused parameter in WriteValue. Add support for service side AcquireWrite and AcquireNotify. Add support for providing address type information. Add support for cable based authentication and pairing. Add support for Bluetooth Low-Energy battery service. Add support for BTP client for qualification testing. Add support for additional Mesh control functionality. Mark advertising manager APIs as stable interfaces. ver 5.47: Fix issue with handling AcquireNotify registration. Fix issue with handling support for reconnection interval. Fix issue with handling A2DP transport and accepting streams. Fix issue with fallback from BR/EDR to LE bearer handling. Add support for appearance and local name advertising data. Add support for retrieving the supported discovery filters. Add support for decoding Bluetooth 5.0 commands and events. Add support for decoding Bluetooth Mesh advertising bearer. Add support for Bluetooth Mesh control application. ver 5.46: Fix issue with handling ATT over BR/EDR connections. Fix issue with SDP browsing cleanup after connection. Fix issue with pointer dereference and OPP Put request. Fix issue with identity address updates during pairing. Fix issue with not removing services that had disappeared. Add support for improved discovery of included services. Add support for simplified characteristics discovery. Add support for GATT caching configuration option. Add experimental support for AcquireWrite and AcquireNotify. ver 5.45: Fix issue with agent support in Bluetooth client tool. Fix issue with handling re-connection policy. Fix issue with handling unknown ATT commands. Fix issue with handling GATT Service Includes property. Fix issue with handling PullAll for OBEX transfers. Fix issue with handling delay in AVDTP Suspend responses. Fix issue with handling decoding of management frames. Add support for frame counters in Bluetooth monitor tool. ver 5.44: Fix issue with GAP and GATT service registration. Fix issue with wrong address type for ATT sockets. Fix issue with dictionary entries for advertising. Fix issue with device information and HID over GATT. Fix issue with handling secondary service discovery. Fix issue with handling Attribute Read Long procedure. Fix issue with handling Attribute Write Long procedure. Fix issue with handling abort of AVDTP SetConfiguration. Add support for single-mode static address configuration. Add support for MIDI over Bluetooth Low Energy. ver 5.43: Fix issue with HID over GATT support. Fix issue with ATT Find By Type response handling. Fix issue with handling insufficient authentication. Fix issue with bonding while pairing is in progress. Fix issue with BR/EDR pairing for dual-mode devices. Fix issue with handling profile policy resets. Fix issue with connecting state of services. Fix issue with handling PAN GN Master role. Add support for enabling LE Privacy feature. ver 5.42: Fix issue with PBAP call logs from different folders. Fix issue with OBEX over L2CAP and PowerPC architecture. Fix issue with BR/EDR over LE selection during discovery. Fix issue with selection of bearer after bonding. Fix issue with handling socket recv() return values. Fix issue with setting connecting service state. Fix issue with setting correct ATT default MTU value. Fix issue with not setting AVRCP player identifier. Fix issue with handling AVRCP browsable player. Fix issue with addressing AVRCP player changes. Add support for new management tracing capability. Mark GATT D-Bus APIs as stable interfaces. ver 5.41: Fix issue with service state changes handling. Fix issue with AVRCP and no available player. Fix issue with handling discovery filters. Fix issue with handling temporary addresses. Fix issue with GATT MTU size and BR/EDR links. Fix issue with OBEX and creating directories. ver 5.40: Fix issue with not storing GATT attributes. Fix issue with optional GATT notifications. Fix issue with reading GATT extended properties. Fix issue with GATT device name properties. Fix issue with previously paired devices. Fix issue with handling device removal. Fix issue with profile connection handling. Add support for TTY monitor protocol. ver 5.39: Fix issue with missing uHID kernel support. Fix issue with GATT reliable write handling. Fix issue with GATT service changed handling. Fix issue with GATT execute write handling. Fix issue with AVRCP player event handling. Fix issue with AVRCP controller handling. Fix issue with AVDTP connection handling. Fix issue with AVDTP error handling. ver 5.38: Fix issue with stack overflow and UUID handling. Fix issue with ObjectManager interface and GATT. Fix issue with GATT database and error handling. Fix issue with GATT client notifications. Fix issue with GATT object ordering. Fix issue with GATT default MTU exchange. Fix issue with device attribute clearing. Fix issue with AVRCP capabilities request. ver 5.37: Fix issue with registering external profiles. Fix issue with connecting external profiles. Fix issue with GATT service changed handling. Fix issue with not emitting GattServices update. Convert to unified HID over GATT profile support. Convert to KeyboardDisplay as default IO capability. Install btattach utility by default. ver 5.36: Fix issue with PBAP headers for size query. Fix issue with AVRCP current player handling. Fix issue with device information handling. Fix issue with device disconnect handling. Fix issue with duplicate connect handling. Fix issue with attribute claiming for drivers. ver 5.35: Fix issue with connected devices after discovery. Fix issue with profile support and LTK loading. Fix issue with AVRCP events for volume control. Fix issue with OBEX session owner handling. Fix issue with HID over GATT setup failures. Fix issue with GATT notification registration. Fix issue with GATT cache validation feature. Add support for persistent GATT database. Add support for controller enabling option. ver 5.34: Fix issue with GATT profiles and auto-connect. Fix issue with missing GoepL2CapPsm SDP data. Fix issue with suspending AVDTP endpoints. Fix issue with audio service state on disconnect. Add support for AVRCP Set Addressed Player feature. Add support for AVRCP Get Folder Items feature. Add support for Android 5.1 HFP WBS callbacks. ver 5.33: Fix issue with memory leak in GATT database. Fix issue with AVDTP set configuration handling. Fix issue with AVDTP discover procedure. Fix issue with not emitting Paired property. ver 5.32: Fix issue with OPP GET request path handling. Fix issue with ATT information request errors. Fix issue with advertising instance numbers. Fix issue with overwriting SDP record cache. Fix issue with new connections during disconnect. Add support for GATT security auto-elevation. ver 5.31: Fix issue with crash in networking interface. Fix issue with crash when creating endless GATT loops. Fix issue with memory leak when connecting services. Fix issue with memory leak creating new D-Bus proxy. Fix issue with profile connections from remote devices. Fix issue with GATT over BR/EDR and MTU notification. Fix issue with HID and dual mode remote devices. Fix issue with handling A2DP vendor codec setup. Fix issue with AVRCP and syncing player state. Fix issue with GATT secondary discovery handling. Fix issue with wrong characteristic allocation. Add support for handling BNEP setup response. Add support for setting GATT database security flags. Add support for setting discovery filters interface. Add support for user controlled advertising interface. Update Android qualification documentation to PTS 6.1 release. ver 5.30: Fix compilation error in C++ due to inline function. Fix issue with missing storage of device information. Fix issue with GATT client and gaps in service handles. Fix issue with AVDTP discovery callback crashing. Fix issue with AVCTP channel handling in case of conflicts. Fix issue with AVRCP target and get capabilities command. Add experimental support for LE advertising manager API. Add support for Android 5.1 GATT MTU exchange API. ver 5.29: Fix issue with AVCTP initial key repeat timeout. Fix issue with Android application disconnect handling. Fix issue with Android support and service notifications. Fix issue with Android support and Exchange MTU Request. Fix issue with Android HFP support and AT+CMER handling. Fix issue with Android HFP support and SLC setup. Fix issue with Android HFP support and call hold status. Fix issue with Android HFP support and indicator handling. Fix issue with Android HFP support and SCO/eSCO disconnection. Fix issue with Android HID over GATT support and battery service. Fix issue with GATT sending Exchange MTU Request for BR/EDR. Fix issue with GATT notification support without CCC. Fix issue with GATT object life-time after disconnects. Fix issue with GATT notification handling API. Add experimental support for GATT client D-Bus API. Add experimental support for GATT server D-Bus API. Add support for Multi Profile Specification. Update Android qualification documentation to PTS 6.0 release. ver 5.28: Fix issue with GATT device discovery and probing. Fix issue with bearer selection for dual-mode devices. Fix issue with device removal while connected. Fix issue with device name setting from inquiry response. Fix issue with missing termination of name characteristic. Fix issue with UTF-8 length handling for device name. Fix issue with AVCTP key auto release handling. Fix issue with AVCTP key press repetition handling. Fix issue with payload sizes and GATT notifications. Fix issue with memory corruption and GATT notifications. Add support for HID proxy switching and CSR 8510 A10 devices. Add support for Broadcom hex2hcd conversion utility. ver 5.27: Fix issue with endian handling and management interface. Fix issue with pending GATT operations when disconnecting. Fix issue with 128-bit UUID conversions for HID over GATT. Add support for Android 5.0 SELinux policies. ver 5.26: Fix issue with handling A2DP XCASE connection state. Fix issue with crash and A2DP configuration failures. Fix issue with crash during OBEX session shutdown. Add support for version 1.2 of Phonebook Access Profile. Add support for HID over GATT get and set report handling. Add support for Low Energy Secure Connections feature. Add support for Bluetooth 4.2 commands and events. Add support for Android 5.0 Bluetooth features. ver 5.25: Fix issue with SCO connection after codec negotiation. Fix issue with GATT and secondary service discovery. Fix issue with GATT write descriptor callback. Fix issue with MAP supported features bits. Add support for MAP local time and timezone offset. Add support for PBAP speed-dial and favorites folders. Add support for PBAP speed-dial and identifier filters. Add support for controller mode configuration option. Add initial support for Android Lollipop features. ver 5.24: Fix issue with storing of connection parameters. Add support for Phonebook Access Profile 1.2 features. Add support for Message Access Profile 1.2 event reports. Add support for Android Bluetooth configuration options. ver 5.23: Fix issue with concurrent authorization requests. Fix issue with HID report identifier mismatch. Fix issue with crash when receiving uHID events. Fix issue with crash and OBEX disconnect handling. Fix issue with OBEX client transfers and suspend. Fix issue with parsing of MAP application parameters. Fix issue with devices rejecting AVRCP GetCapabilities. Add support for kernel whitelist and Android Bluetooth. ver 5.22: Fix issue with UHID_OUTPUT events mapping. Fix issue with UHID_FEATURE events handling. Fix issue with UINT32_MAX overflow and AVRCP. Fix issue when dirent type DT_UNKNOWN is returned. Add support for kernel whitelist filtering feature. Add support for Android Bluetooth GATT over BR/EDR. ver 5.21: Fix issue with SDP requests and wrong PDU size. Fix issue with handling passive scanning triggers. Add support for storing and loading connection parameters. Add support for kernel background auto-connection feature. Add support for Android Bluetooth Scan Parameters feature. Add support for Android Bluetooth Device Information feature. Add support for Android Bluetooth Health Device interface. ver 5.20: Fix issue with LED handling of PS3 controllers. Add support for Android Bluetooth GATT server interface. Add support for Android Bluetooth HID over GATT feature. Add support for Android Bluetooth multi-profile feature. Add support for Android Bluetooth aptX audio integration. Note: aptX codec not included ver 5.19: Fix issue with OBEX Put-Delete and Create-Empty methods. Fix issue with AVRCP browsable/searchable player properties. Fix issue with handling multiple default agents. Fix issue with handling unpair event per bearer. Fix issue with HID over GATT report ID presence. Add support for HID protocol handling in userspace. Add support for Bluetooth reconnection policy framework. Add support for Android Bluetooth SCO over HCI transport. Add support for Android Bluetooth audio quality control. Add support for Android Bluetooth Low Energy only mode. ver 5.18: Fix issue with identifying LE single mode devices. Fix issue with L2CAP and RFCOMM peer address lookup. Add support for handling OBEX authentication procedure. Add support for Android Bluetooth GATT client interface. ver 5.17: Fix issue with not resetting OBEX SRM setup. Fix issue with BR/EDR devices and auto-connect list. Fix issue with bonding complete detection as peripheral. Fix issue with not updating bearer timestamp of connections. Fix issue with paired property for multiple bearers. Add support for Android Bluetooth Handsfree interface. Add support for Android Bluetooth Wideband speech. ver 5.16: Fix issue with HID over GATT physical location. Fix issue with HID over GATT unique identifier. Fix issue with missing paired property notification. Fix issue with endianess of long term key storage. Add support for storing signature resolving keys. Add support for Android Bluetooth AVRCP interface. ver 5.15: Fix issue with LE enabling and background scanning. Fix issue with HID over GATT input device name. Fix issue with storage of slave long term keys. Add support for handling identity resolving keys. Add support for Android Bluetooth A2DP interface. Add support for Android Bluetooth audio interface. ver 5.14: Fix issue with marking PS3 controllers as trusted. Fix issue with authorization of PS3 controllers. Add support for DualShock 4 controller detection. Add support for legacy pairing emulation. Add support for secure simple pairing emulation. Add support for automated pairing testing. Add support for RFCOMM protocol testing. Add support for HCI controller testing. ver 5.13: Fix issue with PS3 controller detection. Add support for data transfers to L2CAP testing tool. Add support for delay reporting to AVDTP testing tool. Add support for Android Bluetooth Core interface. Add support for Android Bluetooth Socket interface. Add support for Android Bluetooth HID Host interface. Add support for Android Bluetooth PAN interface. ver 5.12: Fix issue with missing reply to DisconnectProfile. Fix issue with icon property and class of device changes. Fix issue with HID devices when SDP record is not available. Fix issue with handling auto-pairing of printers. Fix issue with agent authorization handling. Add support for PS3 controller setup and pairing. Add support for LE L2CAP CoC test capabilities. Add support for AVDTP qualification test cases. Add support for SMP cryptographic test cases. ver 5.11: Fix issue with connection attempt when not powered. Fix issue with assigning player to AVRCP target role. Fix issue with OBEX default cache directory. Fix issue with SDP search error handling. Fix issue with processing of SDP records. Fix issue with HID to HCI switching utility. Fix issue with mgmt end-to-end testing tool. Fix issue with L2CAP end-to-end testing tool. Add support for SMP end-to-end testing tool. Add support for more Wii controllers. ver 5.10: Fix issue with discoverable timeout handling. Fix issue with MAP messages and record version. Fix issue with MAP messages and status events. Fix issue with MAP messages and relative folders. Fix issue with MAP messages and type property signals. Fix issue with transfer size for OBEX GET operations. Fix issue with AVRCP service class identifier. Fix issue with AVRCP tracking seeked signal. Add support for OBEX command line client. ver 5.9: Fix issue with network service and adapter removal. Fix issue with misleading OBEX error messages. Fix issue with OBEX transport reference handling. Fix issue with memory leak with MAP event handler. Fix issue with missing MAP property changed signal. Fix issue with message type property values. Fix issue with empty UUID list for devices. Fix issue with profile agent cancel method. Remove dependency on USB library. ver 5.8: Fix issue with missing OBEX session properties. Fix issue with missing SDP service refresh. Fix issue with SDP attribute range check. Fix issue with priority for SDP transactions. Fix issue with service discovery after pairing. Fix issue with race condition in service list. Fix issue with input service state transition. Fix issue with default authorization for profiles. Fix issue with AVRCP browsing channel connections. Add support for AVRCP role agnostic sessions. ver 5.7: Fix issue with missing UUID discovery during pairing. Fix issue with broken patch for SDP range check handling. Fix issue with AVRCP usage of UID=0 for paused/stopped. Add support MAP notification dispatching. ver 5.6: Fix issue with incoming connections without SDP record. Fix issue with canceling ongoing device connections. Fix issue with handling failed connection attempts. Fix issue with pending resume during A2DP open failures. Fix issue with registering AVRCP unsupported notification. Fix issue with listing available AVRCP target settings. Fix issue with missing error for OBEX SetPath commands. Fix issue with missing OBEX session command queue. Fix issue with retrieving multiple MAP event reports. Add support for command line player utility. ver 5.5: Fix issue with race condition between SDP and properties. Fix issue with handling storage of private device addresses. Fix issue with NFC out-of-band pairing and power states. Fix issue with short name during device update handling. Fix issue with handling AVRCP without A2DP being present. Add support for handling AVRCP pass-through operations. Add support for automatically reconnecting HID devices. Add support for automatically pairing of devices. ver 5.4: Fix issue with invalid memory access and SDP service search. Add support for available player changed event for controller. Add support for UIDs changed event for AVRCP controller. Add support for mandatory AVRCP pass-through operations. Add support for Message Notification Service (MNS) server. Add support for agent methods within command line client. ver 5.3: Fix issue with registering invalid profiles. Fix issue with inconsistent A2DP transport state. Fix issue with A2DP resume while in configured state. Fix issue with buffer overflow when processing SDP response. Fix issue with missing range check for SDP attribute response. Fix issue with missing validation of SDP data elements. Fix issue with missing fallback to static hostname. Fix issue with default adapter assignment. ver 5.2: Fix issue with connection handling for Low Energy. Fix issue with broken device discovery handling. Fix issue with invalid memory access within A2DP. Fix issue with handling empty path name of SetPath. Fix issue with handling Message Access Profile filters. Fix issue with handling network service unregistration. Fix issue with not handling bogus device pairing results. Fix issue with initial service discovery and profile manager. Add support for AVRCP volume notifications. Add support for AVRCP browsing commands. ver 5.1: Fix issue with crash when removing OBEX session. Fix issue with HID device disconnected from kernel. Fix issue with buffer overflow when parsing HID SDP record. Fix issue with SDP_TEXT_STR16 and SDP_URL_STR16 parsing. Add support for integration with systemd's hostname daemon. Add support for separate adapter alias property. Add support for adapter and device modalias properties. Add support for official BlueZ device information. Add support for asynchronous management interface handling. Add tool for testing management interface compliance. Add tool for testing SDP qualification requirements. Add tool for testing various EIR and AD data records. ver 5.0: Introduce D-Bus Properties and ObjectManager interfaces. Add support for generic profile interface. Add support for global agent interface. Add support for integrated OBEX daemon. Add support for integrated hcidump utility. Add support for Bluetooth tracing and monitor utility. Add support for Bluetooth command line client utility. Remove support for Handsfree gateway handling. Remove support for GStreamer A2DP and SBC elements. Disable default installation of Bluetooth library. ver 4.101: Fix issue with missing BlueZ service file. Fix issue with aborting A2DP setup during AVDTP start. Fix issue with handling of multiple A2DP indication. Fix issue with handling AVDTP abort with invalid SEID. Fix issue with rejecting AVDTP abort commands. Add support for handling AVDTP command collision. ver 4.100: Fix issue with crashing when SCO connection fails. Fix issue with HFP gateway failing on first GSM connection. Fix issue with AVRCP and handling of vendor commands. Fix issue with handling AVRCP subunit info command. Fix issue with missing capability for AVRCP track reached end. Fix issue with AVDTP signaling and GStreamer SBC NULL check. Fix issue with AVDTP Reconfigure Reject message. Fix issue with incorrect EIR length parsing. Fix issue with SDP disconnect for HIDSDPDisable. Fix issue with SDP interoperability with Mac OS X Lion. Fix issue with reverse SDP discovery with some devices. Fix issue with discovering state during power off operation. Add support for AVRCP Volume Changed notifications. Add support for AVRCP Set Absolute Volume handling. Add support for display legacy PIN code agent method. Add support for multiple media transports per endpoint. Add support for discovering device information characteristics. Add support for vendor source for Device ID setting. Add support for immediate alert server. Add support for link loss server. Notes: This version requires D-Bus 1.4 or later. This version requires GLib 2.28 or later. ver 4.99: Fix issue with missing retries for BNEP connection setup. Fix issue with not showing name if first EIR has no details. Fix issue with running SDP discovery for LE devices. Add support for GATT using 128-bit Bluetooth UUIDs. Add support for retrieving key size information. Add support for storing Long Term Keys. Add support for Proximity Reporter API. Add support for KeyboardDisplay IO capability. Add support for version 1.0 of management API. Add support for monitoring interface. ver 4.98: Fix issue with adapter list upon initialization failure. Fix issue with missing legacy property for Low Energy. Fix issue with missing EIR information handling. Fix issue with device address type tracking. Fix issue with alert level characteristic. Fix issue with headset shutdown handling. Fix issue with Wiimote address handling. Add support for advanced l2test options. Add support for attribute protocol and multiple adapters. ver 4.97: Update support for proximity profile. Fix issue with SBC audio decoding quality. Fix multiple issues with HFP support. Fix multiple issues with A2DP support. Fix multiple issues with AVDTP support. Fix multiple issues with AVRCP support. Add support for AVRCP meta-data transfer. Add support for Bluetooth based thermometers. ver 4.96: Fix issue with race condition in AVDTP stream start. Fix issue with global adapter offline switching. Fix issue with pairing and No Bonding devices. Add support for Nintendo Wii Remote pairing. ver 4.95: Fix issue with AVCTP replies with invalid PID. Fix issue with AVRCP and unknown packet types. Fix issue with AVRCP not using NOT_IMPLEMENTED correctly. Fix issue with AVDTP discovery if all endpoints are in use. Fix issue with invalid memory writes and media support. Fix issue with not removing device alias and unbonding. Fix issue with device disconnects and offline mode handling. Add support for setting adapter name based on machine-info. Add support for systemd service configuration. ver 4.94: Fix issue with invalid read of memory in various modules. Fix issue with buffer overflow when sending AVDTP commands. Fix issue with response to vendor dependent AVRCP commands. Fix issue with headset when not able to reply with ERROR. Fix issue with crash when creating a device from storage. Fix issue with handling non UTF-8 devices names. Add support for improved discovery procedure. ver 4.93: Fix issue with property type and Health Main channel. Fix issue with crash when removing devices. Add support for hid2hci and udev integration. ver 4.92: Fix issue with handling of A2DP suspend response. Fix issue with crashing when acquiring A2DP stream. Fix issue with missing check for valid SCO before shutdown. Fix issue with waiting for POLLERR when disconnecting SCO. Fix issue with disconnect after primary service discovery. Fix issue with attribute interface registration. Add support for primary services over BR/EDR. Add support for flushable packets of A2DP media. ver 4.91: Fix issue with LMP version string and hciconfig. Fix issue with missing discovery signal when scanning. Fix issue with wrong state and canceling name resolving. Fix issue with missing check during adapter initialization. Fix issue with missing protocol not supported error and A2DP. Fix issue with crash during driver unregistering and A2DP. Fix issue with crash when receiving AVDTP close command. Fix issue with remote SEP handling when A2DP codec changes. Fix issue with SCO hangup handling and state changes. Fix issue with security level and MCAP instances. Fix issue with memory leak and HDP data channels. Add support for discover characteristics by UUID to gatttool. Add initial support for Out-of-Band association model. Add initial support for SIM Access Profile. ver 4.90: Fix issue with setting of global mode property. Fix issue with handling of RequestSession responses. Fix issue with TP_BNEP_CTRL_BV_01_C qualification test. Fix issue with too short AVDTP request timeout. Add support for SIM Access Profile manager. Add support for new UUID utility functions. Add support for attribute server notifications. Add support for client characteristic configuration. Update support for interactive GATT utility. ver 4.89: Fix issue with name resolving when discovery is suspended. Fix issue with parsing flags of advertising report. Fix issue with SEP handling if interface is disabled. Fix issue with device object creation on disconnect event. Fix issue with indicators whenever the driver is initialized. Fix issue with call indicator when parsing call info reply. Fix issue with crash and allowed GATT MTU was too large. Add support for SDP record of Primary GATT services. Add support for interactive mode for GATT utility. ver 4.88: Fix issue with HID channel reference count handling. Fix issue with daemon exit on badly formatted AT+VTS. Fix issue with crash while parsing of endpoint properties. Fix issue with possible crash on AVDTP Suspend request timeout. Fix issue with stopping inquiry before adapter is initialized. Fix issue with creating device object when connection fails. Fix issue with sending HCIDEVUP when adapter is already up. Fix issue with handling bonding IO channel closing. Fix agent cancellation in security mode 3 situations. Update pairing code to support management interface. ver 4.87: Fix issue with initialization when adapter is already up. Fix issue with attribute server MTU and incoming connections. Fix issue with duplicate characteristics after discovery. ver 4.86: Revert wrong fix for SDP PDU size error response. Fix various memory leaks in A2DP and AVDTP support. Add Routing property to MediaTransport interface Add proper tracking mechanism to NREC status. Add READ_BLOB_REQUEST support to attribute server. ver 4.85: Fix issue with event mask setting for older adapters. Fix issue with device creation and pairing failures. Add support for telephony support via oFono. Add support for characteristic security level. Update support for service registration. ver 4.84: Fix issue with wrong parameters and device found signals. Fix issue with leaking EIR data if RSSI does not change. Fix issue with adapter initialization state. Fix issue with closing of SDP server sockets. ver 4.83: Fix issue with already connected HFP/HSP endpoints. Fix missing reply when create device is canceled. Fix memory leak within the attribute server. Fix memory leak with unused extended inquiry name. Fix setting paired state when device->authr is false. Fix clearing authentication request for renewed keys. Add support for storing link keys in runtime memory. Update support for primary service discovery. ver 4.82: Fix crash with mmap of files with multiples of page size. Fix HFP response and hold (AT+BTRH) command response. Fix device creation error response when powered off. Fix device removal when connecting/browsing fails. Add initial attribute permission implementation. Add AVDTP SRC stream send buffer size verification. Add support for setting link policy based on features. ver 4.81: Fix issue with telephony driver initialization. Fix issue with adapter services list initialization. Fix crash after simultaneous authentication requests. Add support for primary service search on device creation. ver 4.80: Fix legacy link key storing for some buggy adapters. Fix invalid memory access when EIR field length is zero. Fix adapter initialization to wait for kernel HCI commands. Fix initialization of adapters which are already up. Fix possible race condition when initializing adapters. Fix possible crashes when attempting to connect AVDTP. Fix not aborting sink stream configuration on disconnect. Fix not indicating disconnected state when connecting to AVDTP. Fix not dropping AVDTP session when canceling stream setup. Fix AVDTP abort not being send when the state is idle. Fix regression with Low Energy and interleave discovery. Add a new configuration option to disable Low Energy support. Add iwmmxt optimization for SBC for ARM PXA series CPUs. Update support for GATT Primary Service Discovery. Update MCAP and HDP support. ver 4.79: Fix issue with adapter initialization race condition. Update new Bluetooth Management interface support. ver 4.78: Fix various issues with AVDTP timer handling. Fix various issues with handling of mode changes. Fix issue with audio disconnect watch in connecting state. Fix issue with handling call waiting indicators in telephony. Fix issue with handling UUID parameter and RegisterEndpoint. Add initial support for Bluetooth Management interface. Add support for Application property to HealthChannel. ver 4.77: Fix issue with device name and accessing already freed memory. Fix issue with handling CHLD=0 command for handsfree. Fix issue with manager properties and no adapters. Fix issue with properties and broken service records. Fix issue with A2DP playback and sample rate changes. Update MCAP and HDP support. ver 4.76: Fix issue in telephony driver with hanging up held call. Fix issue in telephony driver with notifications when on hold. Fix issue with blocking on setconf confirmation callback. Fix issue with not always signaling new streams as sinks. Fix issue with errors in case of endpoint request timeout. Fix issue with HFP/HSP microphone and speaker gain values. Add source if the device attempt to configure local sink stream. Add PSM option for GATT/ATT over BR/EDR on gatttool. Add support for GATT/ATT Attribute Write Request. Update MCAP and HDP support. ver 4.75: Fix use of uninitialized variable on legacy pairing. Fix mismatch of attribute protocol opcode. ver 4.74: Fix regression for Legacy Pairing. Fix wrong PSM value for attribute protocol. Fix issue with RSSI field in advertising reports. Add support for Add BR/EDR and LE interleaved discovery. Add support for GATT write characteristic value option. Add support for specifying download address for AR300x. ver 4.73: Fix problem with EIR data when setting the name. Fix reading local name from command complete event. Fix registering local endpoints with disabled socket interface. Add support for more HCI operations using ops infrastructure. Add support for GATT characteristic hierarchy. Add support for GATT indications. ver 4.72: Fix memory leak while connecting BTIO channels. Fix crash with GStreamer plugin if SBC is not supported. Fix issue with GATT server stop sending notifications. Fix issue with GATT and dealing with the minimum MTU size. Fix issue with file descriptor leak in GATT client. Add support for UUID 128-bit handling in attribute client. Add support for encoders/decoders for MTU Exchange. Add support for the MTU Exchange procedure to the server. Add support for a per channel MTU to the ATT server. Add support for Characteristic interface. Add support for new Media API and framework. Add initial support for HDP plugin. ver 4.71: Fix compilation when SBC support in not enabled. Fix crash with RequestSession and application disconnects. Fix memory leak and possible crash when removing audio device. Fix issue with closing stream of locked sep when reconfiguring. Fix issue where discovery could interfere with bonding. Fix issue with Connected status when PS3 BD remote connects. Fix issue with lifetime of fake input devices. Add support for compile time option of oui.txt path. Add support for printing IEEE1284 device ID for CUPS. Add plugin for setting adapter class via DMI. Add more features for attribute protocol and profile. Add initial support for MCAP. ver 4.70: Fix incoming call indication handling when in WAITING state. Fix various SDP related qualification test case issues. Fix logic to write EIR when SDP records are changed. Fix UTF-8 validity check for remote names in EIR. Add support for UUID-128 extended inquiry response. Add service UUIDs from EIR to the DeviceFound signal. Add fast connectable feature for Handsfree profile. Add HCI command and event definitions for AMP support. Add firmware download support for Qualcommh devices. Add host level support for Atheros AR300x device. Add initial support of ATT and GATT for basic rate. ver 4.69: Fix issue with calling g_option_context_free() twice. Fix inconsistencies with initial LE commands and events. Add support for telephony ClearLastNumber method. Add support for network server interface. ver 4.68: Fix initialization of adapters in RAW mode. Fix signal strength for HFP in Maemo's telephony support. Add support for following the radio state via Maemo's MCE. Add initial set of LE commands and events definitions. Add mode option for L2CAP sockets to the BtIO API. ver 4.67: Fix issue with authentication reply when bonding already completed. Fix issue with not canceling authentication when bonding fails. Fix issue with changed combination keys and temporary storage. Fix issue with sdp_get_supp_feat library function. Fix issue with missing unblock on device removal. Fix issue with not waiting for mode change completion. Add ARMv6 optimized version of analysis filter for SBC encoder. ver 4.66: Fix regression with full debug enabling via SIGUSR2. Fix redundant speaker/microphone gains being sent. Fix not emitting PropertyChanged for SpeakerGain/MicrophoneGain. Fix issue with storage usage when a record is not found in memory. Fix issue with DiscoverServices not retrieving any records. Fix audio profile disconnection order to match whitepaper. Fix auto-accept confirmation when local agent has NoInputNoOutput. Fix remote just-works SSP when MITM protection is required. Fix performing dedicated bonding without MITM requirement. Add support for storing debug link keys in runtime memory. ver 4.65: Fix issues with general bonding being default setting now. Fix driver removal upon device removal. Add new "Blocked" property to device objects. Add hciconfig support for blacklisting. Add support for dynamic debug feature. ver 4.64: Fix invalid memory access in headset_get_nrec function. Fix issue with disconnect event on higher protocol layers. Fix issue with list parsing in sdp_set_supp_features function. Fix device object reference counting for SDP browse requests. Add missing memory checks whenever memory is allocated for SDP. Add support for exporting local services via D-Bus. Add more L2CAP Enhanced Retransmission test options. ver 4.63: Fix avdtp_abort not canceling pending requests. Fix stale connection when abort gets rejected. ver 4.62: Fix accidental symbol breakage with inquiry transmit power. Fix using invalid data from previous headset connection. Fix double free on AVDTP Abort response. Fix possible crash while verifying AVDTP version. Fix missing inuse flag when AVDTP stream is configured. Add support for Bluetooth controller types. ver 4.61: Fix issues with Read Inquiry Response Transmit Power Level. Fix possible invalid read when removing a temporary device. Fix mode restoration when remember_powered is false. Fix conference call releasing in telephony-maemo. Fix segmentation fault with authorization during headset disconnects. Add support for handling unanswered AVDTP request on disconnect. Add support for handling Inquiry Response Transmit Power Level. Add support for caching of remote host features. Add preliminary voice dialing support for HSP. ver 4.60: Fix voice mailbox number reading from SIM. Fix some races with D-Bus mainloop integration. Add helpers for D-Bus signal watches. ver 4.59: Add values for Bluetooth 4.0 specification. Add SDP functions for HDP support. Add test scripts for input and audio. Fix missing close on BtIO create_io function. Fix sending incorrect AVDTP commands after timeout occurs. Fix timer removal when device disconnects unexpectedly. Fix Extended Inquiry Response record for Device ID. ver 4.58: Fix crash when adapter agent exists during authentication. Fix CK-20W quirks for play and pause events. ver 4.57: Fix unloading of drivers for uninitialized adapters. Fix debug message to use requested and not opened SEID. Fix codec selection for GStreamer plugin. Fix deleting of SDP records during service updates. Fix deleting of SDP records when a device is removed. Fix handling when the SDP record is modified on remote device. Fix potential buffer overflow by using snprintf instead of sprintf. Fix const declarations for some storage function parameters. ver 4.56: Add missing values from Bluetooth 3.0 specification. Add proper tracking of device paired status. Fix tracking of devices without permanently stored link key. Fix issue with link key removal after connection failures. Fix legacy pairing information based on remote host features. Fix off-by-one issue with AVDTP capability parsing. Fix AVRCP, AVCTP, AVDTP, A2DP and HFP version numbers. Fix agent canceling before calling agent_destroy. Fix service record parsing with an empty UUID list. Fix various SDP related memory leaks. ver 4.55: Add support for POSIX capabilities dropping. Add special quirk for the Nokia CK-20W car kit. Fix error code handling for AVDTP SetConfiguration response. Fix updating out of range list when RSSI hasn't changed. Fix various memory leaks and unnecessary error checks. ver 4.54: Add introspection interface to output of introspection calls. Fix stream handling when media transport disconnects prematurely. Fix command timeout handling when there's no stream. Fix headset_suspend_stream behavior for invalid states Fix issue with AVDTP ABORTING state transition. Fix issue with AVDTP suspend while closing. ver 4.53: Fix issue with telephony connection state notifications. Fix AVDTP stream leak for invalid media transport config. Fix audio connection authorization handling with timeouts. Fix race condition in authorizing audio connections. Fix device authorized setting for AVRCP-only connections. Fix duplicate attempts from device to connect signal channel. ver 4.52: Add AVCTP support to test utility. Fix AVDTP Abort when transport closes before response. Fix authorization when the audio profiles are slow to connect. Fix potential AVDTP reference leaks. ver 4.51: Add utility for basic AVDTP testing. Add support for configuring L2CAP FCS option. Fix discovery mode for CUPS 1.4.x and later. Fix global state tracking of audio service. Fix last issues with the new build system. ver 4.50: Fix issue with missing manual pages in distribution. Fix issue with the configuration and state directories. Fix issue with creating include directory. Fix dependencies of include file generation. ver 4.49: Add simple test program for basic GAP testing. Add support for confirmation requests to agent example. Add support for full non-recursive build. Add five millisecond delay for Simple Pairing auto-accept. Fix Class of Device setting when InitiallyPowered=false. ver 4.48: Add library function for comparing UUID values. Add support for creating all plugins as builtins. Add support for async handling of service class changes. Add support for source interface to audio IPC. Fix device name settings when device is off or down. Fix issue with enabled SCO server when not necessary. Fix missing D-Bus access policy for CUPS backend. Fix discovery results of CUPS backend. Fix initialization handling of Maemo telephony. ver 4.47: Add support for RFKILL unblock handling. Add support for serial proxy configurations. Add support for caching service class updates. Fix issues with updating SDP service records. Fix usage of limited discoverable mode. Remove deprecated methods and signals for AudioSource. ver 4.46: Add support for A2DP sink role. Fix clearing svc_cache before the adapter is up. Fix various pointer after free usages. Fix various memory leaks. ver 4.45: Fix UDEV_DATADIR fallback if pkg-config fails. Fix adapter cleanup and setup prototypes. Fix double-free with out-of-range devices. Fix inband ring setting to be per-headset. Fix handling of Maemo CSD startup. ver 4.44: Add some missing manual pages. Fix missing number prefix when installing udev rules. Fix program prefix used in Bluetooth udev rules. Fix three-way calling indicator order. Fix downgrade/upgrade of callheld indicator. Fix +CIEV sending when indicator value changes. Fix signal handling for Maemo telephony driver. Fix parsing issues with messages from Maemo CSD. Fix issue with duplicate active calls. ver 4.43: Add support for udev based on-demand startup. Fix verbose error reporting of CUPS backend. Fix various string length issues. Fix issues with Maemo telephony driver. Fix another device setup and temporary flag issue. Fix and update example agent implementation. ver 4.42: Add TI WL1271 to Texas Instruments chip list. Add special udev mode to bluetoothd. Fix regression when there is no agent registered. Fix error return when bonding socket hang up. Fix SCO server socket for HFP handsfree role. Fix shutdown on SCO socket before closing. Fix shutdown on A2DP audio stream channel before closing. Fix issue with asserting on AVDTP reference count bugs. Fix authorization denied issue with certain headsets. Fix AVRCP UNITINFO and SUBUNIT INFO responses. Fix discovery cancel issues in case SDP discovery fails. ver 4.41: Fix pairing even if the ACL gets dropped before successful SDP. Fix regression which caused device to be removed after pairing. Fix HSP record fetching when remote device doesn't support it. Fix SDP discovery canceling when clearing hs->pending. Fix headset never connecting on the first attempt. Fix headset state tracking if bt_search_service() fails. Fix maximum headset connection count check. Fix AVDTP Discover timeout handling. Fix also UI_SET_KEYBIT for the new pause and play key codes. ver 4.40: Add telephony driver for oFono telephony stack. Add support for Dell specific HID proxy switching. Add support for running hid2hci from udev. Add mapping for AVRCP Play and Pause to dedicated key codes. Fix AVRCP keycodes to better match existing X keymap support. Fix various quoting issues within telephony support. Fix memory allocation issue when generating PDUs for SDP. Fix race condition on device removal. Fix non-cancelable issue with CreateDevice method. Fix non-working CancelDiscovery method call. ver 4.39: Add workaround for dealing with unknown inquiry complete. Fix discovering when using software scheduler. Fix wrong NoInputNoOutput IO capability string. Fix race condition with agent during pairing. Fix agent cancellation for security mode 3 acceptor failure. Fix temporary flag removal when device creation fails. Fix hciattach to use ppoll instead of poll. Fix service class update when adapter is down. Fix service classes race condition during startup. Fix release of audio client before freeing the device. ver 4.38: Add support for builtin plugins. Add framework for adapter operations. Add constants for Enhanced Retransmission modes. Fix HCI socket leak in device_remove_bonding. Fix various format string issues. Fix crashes with various free functions. Fix issues with Headset and A2DP drivers to load again. Fix sending AVRCP button released passthrough messages Fix bug which prevent input devices to work after restart. Fix issue with interpretation of UUID-128 as channel. ver 4.37: Add version value for Bluetooth 3.0 devices. Add additional L2CAP extended feature mask bits. Add support for loading plugins in priority order. Add support for more detailed usage of disconnect watches. Add support for AVRCP volume control. Add saturated clipping of SBC decoder output to 16-bit. Fix potentially infinite recursion of adapter_up. Fix SCO handling in the case of an incoming call. Fix input service to use confirm callback. Fix cleanup of temporary device entries from storage. ver 4.36: Add proper tracking of AVCTP connect attempts. Add support to channel pattern in Serial interface. Fix A2DP sink crash if removing device while connecting. Fix error handling if HFP indicators aren't initialized. Fix segfault while handling an incoming SCO connection. Fix Serial.Disconnect to abort connection attempt. ver 4.35: Add support for Handsfree profile headset role. Add additional checks for open SEIDs from clients. Fix device removal while audio IPC client is connected. Fix device removal when an authorization request is pending. Fix incoming AVDTP connect while authorization in progress. Fix disconnection timers for audio support. Fix various potential NULL pointer deferences. Fix callheld indicator value for multiple calls. Fix voice number type usage. Fix GDBus watch handling. ver 4.34: Add support for version checks of plugins. Add support for class property on adapter interface. Add support for second SDP attempt after connection reset. Add support for more detailed audio states. Add support for HFP+A2DP auto connection feature. Add support for new and improved audio IPC. Add program for testing audio IPC interface. Fix various AVDTP qualification related issues. Fix broken SDP AttributeIdList parsing. Fix invalid memory access of SDP URL handling. Fix local class of device race conditions. Fix issue with periodic inquiry on startup. Fix missing temporary devices in some situations. Fix SBC alignment issue for encoding with four subbands. ver 4.33: Add Paired property to the DeviceFound signals. Add support for Headset profile 1.2 version. Fix broken network configuration when IPv6 is disabled. Fix network regression that caused disconnection. Fix SDP truncation of strings with NULL values. Fix service discovery handling of CUPS helper. ver 4.32: Fix broken SDP record handling. Fix SDP data buffer parsing. Fix more SDP memory leaks. Fix read scan enable calls. Fix A2DP stream handling. ver 4.31: Add support for new BtIO helper library. Fix AVDTP session close issue. Fix SDP memory leaks. Fix various uninitialized memory issues. Fix duplicate signal emissions. Fix property changes request handling. Fix class of device storage handling. ver 4.30: Add CID field to L2CAP socket address structure. Fix reset of authentication requirements after bonding. Fix storing of link keys when using dedicated bonding. Fix storing of pre-Bluetooth 2.1 link keys. Fix resetting trust settings on every reboot. Fix handling of local name changes. Fix memory leaks in hciconfig and hcitool ver 4.29: Use AVRCP version 1.0 for now. Decrease AVDTP idle timeout to one second. Delay AVRCP connection when remote device connects A2DP. Add workaround for AVDTP stream setup with broken headsets. Add missing three-way calling feature bit for Handsfree. Fix handsfree callheld indicator updating. Fix parsing of all AT commands within the buffer. Fix authentication replies when disconnected. Fix handling of debug combination keys. Fix handling of changed combination keys. Fix handling of link keys when using no bonding. Fix handling of invalid/unknown authentication requirements. Fix closing of L2CAP raw socket used for dedicated bonding. ver 4.28: Add AVDTP signal fragmentation support. Add more SBC performance optimizations. Add more SBC audio quality improvements. Use native byte order for audio plugins. Set the adapter alias only after checking the EIR data. Fix auto-disconnect issue with explicit A2DP connections. Fix invalid memory access of ALSA plugin. Fix compilation with -Wsign-compare. ver 4.27: Add more SBC optimization (MMX and ARM NEON). Add BT_SECURITY and BT_DEFER_SETUP definitions. Add support for deferred connection setup. Add support for fragmentation of data packets. Add option to trigger dedicated bonding. Follow MITM requirements from remote device. Require MITM for dedicated bonding if capabilities allow it. Fix IO capabilities for non-pairing and pairing cases. Fix no-bonding connections in non-bondable mode. Fix new pairing detection with SSP. Fix bonding with pre-2.1 devices and newer kernels. Fix LIAC setting while toggling Pairable property. Fix device creation for incoming security mode 3 connects. Fix crash within A2DP with bogus pointer. Fix issue with sdp_copy_record() function. Fix crash with extract_des() if sdp_uuid_extract() fails. ver 4.26: Use of constant shift in SBC quantization code. Add possibility to analyze 4 blocks at once in encoder. Fix correct handling of frame sizes in the encoder. Fix for big endian problems in SBC codec. Fix audio client socket to always be non-blocking. Update telephony support for Maemo. ver 4.25: Fix receiving data over the audio control socket. Fix subbands selection for joint-stereo in SBC encoder. Add new SBC analysis filter function. ver 4.24: Fix signal emissions when removing adapters. Fix missing adapter signals on exit. Add support for bringing adapters down on exit. Add support for RememberPowered option. Add support for verbose compiler warnings. Add more options to SBC encoder. ver 4.23: Update audio IPC for better codec handling. Fix bitstream optimization for SBC encoder. Fix length header values of IPC messages. Fix multiple coding style violations. Fix FindDevice to handle temporary devices. Add configuration option for DeviceID. Add support for InitiallyPowered option. Add missing signals for manager properties. Add telephony support for Maemo. ver 4.22: Add deny statements to D-Bus access policy. Add support for LegacyPairing property. Add support for global properties. Add more commands to telephony testing script. Add sender checks for serial and network interfaces. Remove deprecated methods and signals from input interface. Remove deprecated methods and signals from network interface. Remove OffMode option and always use device down. ver 4.21: Fix adapter initialization logic. Fix adapter setup and start security manager early. Fix usage issue with first_init variable. ver 4.20: Cleanup session handling. Cleanup mode setting handling. Fix issue with concurrent audio clients. Fix issue with HFP/HSP suspending. Fix AT result code syntax handling. Add Handsfree support for AT+NREC. Add PairableTimeout adapter property. ver 4.19: Fix installation of manual pages for old daemons. Fix D-Bus signal emmissions for CreateDevice. Fix issues with UUID probing. Fix +BSRF syntax issue. Add Pairable adapter property. Add sdp_copy_record() library function. ver 4.18: Fix release before close issue with RFCOMM TTYs. Fix Connected property on input interface. Fix DeviceFound signals during initial name resolving. Fix service discovery handling. Fix duplicate UUID detection. Fix SBC gain mismatch and decoding handling. Add more options to SBC encoder and decoder. Add special any adapter object for service interface. Add variable prefix to adapter and device object paths. ver 4.17: Fix SBC encoder not writing last frame. Fix missing timer for A2DP suspend. Add more supported devices to hid2hci utility. Add additional functionality to Handsfree support. ver 4.16: Fix wrong parameter usage of watch callbacks. Fix parameters for callback upon path removal. Fix unloading of adapter drivers. ver 4.15: Fix various A2DP state machine issues. Fix some issues with the Handsfree error reporting. Fix format string warnings with recent GCC versions. Remove dependency on GModule. ver 4.14: Fix types of property arrays. Fix potential crash with input devices. Fix PS3 BD remote input event generation. Allow dynamic adapter driver registration. Update udev rules. ver 4.13: Fix service discovery and UUID handling. Fix bonding issues with Simple Pairing. Fix file descriptor misuse of SCO connections. Fix various memory leaks in the device handling. Fix AVCTP disconnect handling. Fix GStreamer modes for MP3 encoding. Add operator selection to Handsfree support. ver 4.12: Fix crash with missing icon value. Fix error checks of HAL plugin. Fix SCO server socket cleanup on exit. Fix memory leaks from DBusPendingCall. Fix handling of pending authorization requests. Fix missing protocol UUIDs in record pattern. ver 4.11: Change SCO server socket into a generic one. Add test script for dummy telephony plugin. Fix uninitialized reply of multiple GetProperties methods. ver 4.10: Fix memory leaks with HAL messages. Add more advanced handsfree features. Add properties to audio, input and network interfaces. Stop device discovery timer on device removal. ver 4.9: Fix signals for Powered and Discoverable properties. Fix handling of Alias and Icon properties. Fix duplicate entries for service UUIDs. ver 4.8: Fix retrieving of formfactor value. Fix retrieving of local and remote extended features. Fix potential NULL pointer dereference during pairing. Fix crash with browsing due to a remotely initated pairing. ver 4.7: Fix pairing and service discovery logic. Fix crashes during suspend and resume. Fix race condition within devdown mode. Add RequestSession and ReleaseSession methods. Add Powered and Discoverable properties. Add Devices property and deprecate ListDevices. Add workaround for a broken carkit from Nokia. ver 4.6: Fix Device ID record handling. Fix service browsing and storage. Fix authentication and encryption for input devices. Fix adapter name initialization. ver 4.5: Fix initialization issue with new adapters. Send HID authentication request without blocking. Hide the verbose SDP debug behind SDP_DEBUG. Add extra UUIDs for service discovery. Add SCO server socket listener. Add authorization support to service plugin. ver 4.4: Add temporary fix for the CUPS compile issue. Add service-api.txt to distribution. Mention the variable prefix of an object path ver 4.3: Add dummy driver for telephony support. Add support for discovery sessions. Add service plugin for external services. Various cleanups. ver 4.2: Avoid memory copies in A2DP write routine. Fix broken logic with Simple Pairing check and old kernels. Allow non-bondable and outgoing SDP without agent. Only remove the bonding for non-temporary devices. Cleanup various unnecessary includes. Make more unexported functions static. Add basic infrastructure for gtk-doc support. ver 4.1: Add 30 seconds timeout to BNEP connection setup phase. Avoid memory copies in A2DP write routine for ALSA. Make sure to include compat/sdp.h in the distribution. ver 4.0: Initial public release. ver 3.36: Add init routines for TI BRF chips. Add extra attributes to the serial port record. Add example record for headset audio gateway record. Use Handsfree version 0x0105 for the gateway role. Fix SDP record registration with specific record handles. Fix BCSP sent/receive handling. Fix various includes for cross-compilation. Allow link mode settings for outgoing connections. Allow bonding during periodic inquiry. ver 3.35: Add two additional company identifiers. Add UUID-128 support for service discovery. Fix usage of friendly names for service discovery. Fix authorization when experiemental is disabled. Fix uninitialized variable in passkey request handling. Enable output of timestamps for l2test and rctest. ver 3.34: Replace various SDP functions with safe versions. Add additional length validation for incoming SDP packets. Use safe function versions for SDP client handling. Fix issue with RemoveDevice during discovery procedure. Fix collect for non-persistent service records. ver 3.33: Add functions for reading and writing the link policy settings. Add definition for authentication requirements. Add support for handling Simple Pairing. Add Simple Pairing support to Agent interface. Add ReleaseMode method to Adapter interface. Add DiscoverServices method to Device interface. Remove obsolete code and cleanup the repository. Move over to use the libgdbus API. Enable PIE by default if supported. ver 3.32: Add OCF constants for synchronous flow control enabling. Add support for switching HID proxy devices from Dell. Add more Bluetooth client/server helper functions. Add support for input service idle timeout option. Fix BNEP reconnection handling. Fix return value for snd_pcm_hw_params() calls. Use upper-case addresses for object paths. Remove HAL support helpers. Remove inotify support. Remove service daemon activation handling. Remove uneeded D-Bus API extension. ver 3.31: Create device object for all pairing cases. Convert authorization to internal function calls. Add initial support for Headset Audio Gateway role. Add generic Bluetooth helper functions for GLib. Fix endiannes handling of connection handles. Don't optimize when debug is enabled. ver 3.30: Convert audio service into a plugin. Convert input service into a plugin. Convert serial service into a plugin. Convert network service into a plugin. Emit old device signals when a property is changed. Fix missing DiscoverDevices and CancelDiscovery methods. Add another company identifier. Add basic support for Bluetooth sessions. Add avinfo utility for AVDTP/A2DP classification. Remove build option for deprecated sdpd binary. ver 3.29: Introduce new D-Bus based API. Add more SBC optimizations. Add support for PS3 remote devices. Fix alignment trap in SDP server. Fix memory leak in sdp_get_uuidseq_attr function. ver 3.28: Add support for MCAP UUIDs. Add support for role switch for audio service. Add disconnect timer for audio service. Add disconnect detection to ALSA plugin. Add more SBC optimizations. Fix alignment issue of SDP server. Remove support for SDP parsing via expat. ver 3.27: Update uinput.h with extra key definitions. Add support for input connect/disconnect callbacks. Add ifdefs around some baud rate definitions. Add another company identifier. Add proper HFP service level connection handling. Add basic headset automatic disconnect support. Add support for new SBC API. Fix SBC decoder noise at high bitpools. Use 32-bit multipliers for further SBC optimization. Check for RFCOMM connection state in SCO connect callback. Make use of parameters selected in ALSA plugin. ver 3.26: Fix compilation issues with UCHAR_MAX, USHRT_MAX and UINT_MAX. Improve handling of different audio transports. Enable services by default and keep old daemons disabled. ver 3.25: Add limited support for Handsfree profile. Add limited support for MPEG12/MP3 codec. Add basic support for UNITINFO and SUBUNITINFO. Add more SBC optimizations. Fix external service (un)registration. Allow GetInfo and GetAddress to fail. ver 3.24: Add definitions for MDP. Add TCP connection support for serial proxy. Add fix for Logitech HID proxy switching. Add missing macros, MIN, MAX, ABS and CLAMP. Add more SBC encoder optimizations. Add initial mechanism to handle headset commands. Fix connecting to handsfree profile headsets. Use proper function for checking signal name. ver 3.23: Fix remote name request handling bug. Fix key search function to honor the mmap area size. Fix Avahi integration of network service. Add new plugin communication for audio service. Enable basic AVRCP support by default. More optimizations to the SBC library. Create common error definitions. ver 3.22: Add missing include file from audio service. Add SBC conformance test utility. Add basic uinput support for AVRCP. Fix L2CAP socket leak in audio service. Fix buffer usage in GStreamer plugin. Fix remote name request event handling. ver 3.21: Add constant for Bluetooth socket options level. Add initial AVRCP support. Add A2DP sink support to GStreamer plugin. Fix interoperability with A2DP suspend. Fix sign error in 8-subband encoder. Fix handling of service classes length size. Store Extended Inquiry Response data information. Publish device id information through EIR. Support higher baud rates for Ericcson based chips. ver 3.20: Fix GStreamer plugin file type detection. Fix potential infinite loop in inotify support. Fix D-Bus signatures for dict handling. Fix issues with service activation. Fix SDP failure handling of audio service. Fix various memory leaks in input service. Add secure device creation method to input service. Add service information methods to serial service. Add config file support to network service. Add scripting capability to network service. Add special on-mode handling. Add optimization for SBC encoder. Add tweaks for D-Bus 1.1.x libraries. Add support for inquiry transmit power level. ver 3.19: Limit range of bitpool announced while in ACP side. Use poll instead of usleep to wait for worker thread. Use default event mask from the specification. Add L2CAP mode constants. Add HID proxy support for Logitech diNovo Edge dongle. Add refresh option to re-request device names. Show correct connection link type. ver 3.18: Don't allocate memory for the Bluetooth base UUID. Implement proper locking for headsets. Fix various A2DP SEP locking issues. Fix and cleanup audio stream handling. Fix stream starting if suspend request is pending. Fix A2DP and AVDTP endianess problems. Add network timeout and retransmission support. Add more detailed decoding of EIR elements. ver 3.17: Fix supported commands bit calculation. Fix crashes in audio and network services. Check PAN source and destination roles. Only export the needed symbols for the plugins. ver 3.16: Update company identifier list. Add support for headsets with SCO audio over HCI. Add support for auto-create through ALSA plugin. Add support for ALSA plugin parameters. Add GStreamer plugin with SBC decoder and encoder. Fix network service NAP, GN and PANU servers. Set EIR information from SDP database. ver 3.15: Add A2DP support to the audio service. Add proxy support to the serial service. Extract main service class for later use. Set service classes value from SDP database. ver 3.14: Add missing signals for the adapter interface. Add definitions and functions for Simple Pairing. Add basic commands for Simple Pairing. Add correct Simple Pairing and EIR interaction. Add missing properties for remote information. Add EPoX endian quirk to the input service. Fix HID descriptor import and storage functions. Fix handling of adapters in raw mode. Fix remote device listing methods. ver 3.13: Fix some issues with the headset support. Fix concurrent pending connection attempts. Fix usage of devname instead of netdev. Add identifier for Nokia SyncML records. Add command for reading the CSR chip revision. Add generic CSR radio test support. Update HCI command table. ver 3.12: Add missing HCI command text descriptions Add missing HCI commands structures. Add missing HCI event structures. Add common bachk() function. Add support for limited discovery mode. Add support for setting of event mask. Add GetRemoteServiceIdentifiers method. Add skeleton for local D-Bus server. Add headset gain control methods. Fix various headset implementation issues. Fix various serial port service issues. Fix various input service issues. Let CUPS plugin discover printers in range. Improve the BCM2035 UART init routine. Ignore connection events for non-ACL links. ver 3.11: Update API documentation. Minimize SDP root records and browse groups. Use same decoder for text and URL strings. Fix URL data size handling. Fix SDP pattern extraction for XML. Fix network connection persistent state. Add network connection helper methods. Add initial version of serial port support. Add class of device tracking. ver 3.10.1: Add option to disable installation of manual pages. Fix input service encryption setup. Fix serial service methods. Fix network service connection handling. Provide a simple init script. ver 3.10: Add initial version of network service. Add initial version of serial service. Add initial version of input service. Add initial version of audio service. Add authorization framework. Add integer based SBC library. Add version code for Bluetooth 2.1 specification. Add ESCO_LINK connection type constant. Export sdp_uuid32_to_uuid128() function. ver 3.9: Add RemoteDeviceDisconnectRequested signal. Add updated service framework. Add embedded GLib library. Add support for using system GLib library. Create internal SDP server library. ver 3.8: Sort discovered devices list based on their RSSI. Send DiscoverableTimeoutChanged signal. Fix local and remote name validity checking. Add ListRemoteDevices and ListRecentRemoteDevices methods. Add basic integration of confirmation concept. Add support for service record description via XML. Add support for external commands to the RFCOMM utility. Add experimental service and authorization API. Add functions for registering binary records. ver 3.7: Fix class of device handling. Fix error replies with pairing and security mode 3. Fix disconnect method for RFCOMM connections. Add match pattern for service searches. Add support for prioritized watches. Add additional PDU length checks. Fix CSRC value for partial responses. ver 3.6.1: Fix IO channel race conditions. Fix pairing issues on big endian systems. Fix pairing issues with page timeout errors. Fix pairing state for security mode 3 requests. Switch to user as default security manager mode. ver 3.6: Update D-Bus based RFCOMM interface support. Use L2CAP raw sockets for HCI connection creation. Add periodic discovery support to the D-Bus interface. Add initial support for device names via EIR. Add proper UTF-8 validation of device names. Add support for the J-Three keyboard. Fix issues with the asynchronous API for SDP. ver 3.5: Fix and cleanup watch functionality. Add support for periodic inquiry mode. Add support for asynchronous SDP requests. Add more request owner tracking. Add asynchronous API for SDP. Document pageto and discovto options. ver 3.4: Improve error reporting for failed HCI commands. Improve handling of CancelBonding. Fixed bonding reply message when disconnected. Fix UUID128 string lookup handling. Fix malloc() versus bt_malloc() usage. ver 3.3: Don't change inquiry mode for Bluetooth 1.1 adapters. Add udev rules for Bluetooth serial PCMCIA cards. Add Cancel and Release methods for passkey agents. Add GetRemoteClass method. Convert to using ppoll() and pselect(). Initialize allocated memory to zero. Remove bcm203x firmware loader. Remove kernel specific timeouts. Add additional private data field for SDP sessions. Add host controller to host flow control defines. Add host number of completed packets defines. Initialize various memory to zero before usage. ver 3.2: Only check for the low-level D-Bus library. Update possible device minor classes. Fix timeout for pending reply. Add more Inquiry with RSSI quirks. Sleep only 100 msecs for device detection. Don't send BondingCreated on link key renewal. Allow storing of all UTF-8 remote device names. Create storage filenames with a generic function. Fix handling of SDP strings. Add adapter type for SDIO cards. Add features bit for link supervision timeout. ver 3.1: Add missing placeholders for feature bits. Fix handling of raw mode devices. Fix busy loop in UUID extraction routine. Remove inquiry mode setting. Remove auth and encrypt settings. ver 3.0: Implement the new BlueZ D-Bus API. Fix broken behavior with EVT_CMD_STATUS. Add features bit for pause encryption. Add additional EIR error code. Add more company identifiers. Add another Phonebook Access identifier. Update sniff subrating data structures. ver 2.25: Use %jx instead of %llx for uint64_t and int64_t. Allow null-terminated text strings. Add UUID for N-Gage games. Add UUID for Apple Macintosh Attributes. Add Apple attributes and iSync records. Add definitions for Apple Agent. Add support for the Handsfree Audio Gateway service. Add support for choosing a specific record handle. Add support for dialup/telephone connections. Add definitions for Apple Agent. Add support for record handle on service registration. ver 2.24: Fix display of SDP text and data strings. Add support for device scan property. Add support for additional access protocols. Update the D-Bus policy configuration file. ver 2.23: Update the new D-Bus interface. Make dfutool ready for big endian architectures. Add support for AVRCP specific service records. Add support for writing complex BCCMD commands. Add the new BCCMD interface utility. Add MicroBCSP implementation from CSR. Add constants and definitions for sniff subrating. Add support for allocation of binary text elements. Add HCI emulation tool. Add fake HID support for old EPoX presenters. Reject connections from unknown HID devices. Fix service discovery deadlocks with Samsung D600 phones. ver 2.22: Remove D-Bus 0.23 support. Add initial version of the new D-Bus interface. Add support for extended inquiry response commands. Add support for the Logitech diNovo Media Desktop Laser. Add compile time buffer checks (FORTIFY SOURCE). Decode reserved LMP feature bits. Fix errno overwrite problems. Fix profile descriptor problem with Samsung phones. ver 2.21: Move create_dirs() and create_file() into the textfile library. Let textfile_put() also replace the last key value pair. Fix memory leaks with textfile_get() usage. Fix infinite loops and false positive matches. Don't retrieve stored link keys for RAW devices. Document the putkey and delkey commands. Show supported commands also in clear text. Support volatile changes of the BD_ADDR for CSR chips. Add support for identification of supported commands. Add missing OCF declarations for the security filter. Add two new company identifiers. ver 2.20: Add UUIDs for video distribution profile. Add UUIDs for phonebook access profile. Add attribute identifier for supported repositories. Add definitions for extended inquiry response. Add functions for extended inquiry response. Add support for extended inquiry response. Add support for HotSync service record. Add support for ActiveSync service record. Add ActiveSync networking support. Fix D-Bus crashes with new API versions. ver 2.19: Fix the GCC 4.0 warnings. Fix the routing for dealing with raw devices. Fix off by one memory allocation error. Fix security problem with escape characters in device name. Add per device service record functions. Send D-Bus signals for inquiry results and remote name resolves. Add support for device specific SDP records. ver 2.18: Support D-Bus 0.23 and 0.33 API versions. Support reading of complex BCCMD values. Support minimum and maximum encryption key length. Add support for reading and writing the inquiry scan type. Add definitions for connection accept timeout and scan enable. Add support for inquiry scan type. Add tool for the CSR BCCMD interface. Add first draft of the Audio/Video control utility. Add disconnect timer support for the A2DP ALSA plugin. Make SBC parameters configurable. Replace non-printable characters in device names. Remove hci_vhci.h header file. Remove hci_uart.h header file. ver 2.17: Set the storage directory through ${localstatedir}. Add the textfile library for ASCII based file access. Add support for return link keys event. Add support for voice setting configuration. Add support for page scan timeout configuration. Add support for storing and deleting of stored link keys. Add support for searching for services with UUID-128. Add support for retrieving all possible service records. Add support for a raw mode view of service records. Add support for HID information caching in hidd. Add support for authentication in pand and dund. Add support for changing BD_ADDR of CSR chips. Add pskey utility for changing CSR persistent storage values. Add the firmware upgrade utility. Add connection caching for the A2DP ALSA plugin. Add functions for stored link keys. Add definitions for PIN type and unit key. Add SDP_WAIT_ON_CLOSE flag for sdp_connect(). Include stdio.h in bluetooth.h header file. Include sys/socket.h in the header files. ver 2.16: Store link keys in ASCII based file format. Support device name caching. Support zero length data sizes in l2test. Change default l2ping data size to 44 bytes. Hide the server record and the public browse group root. Read BD_ADDR if not set and if it is a raw device. Add SDP language attributes. Add support for browsing the L2CAP group. Add support for stored pin codes for outgoing connections. Add support for local commands and extended features. Add support for reading CSR panic and fault codes. Add config option for setting the inquiry mode. Add OUI decoding support. Use unlimited inquiry responses as default. Use cached device names for PIN request. Use the clock offset when getting the remote names. Add function for reading local supported commands. Add function for reading local extended features. Add function for reading remote extended features. Add function for getting the remote name with a clock offset. Add function for extracting the OUI from a BD_ADDR. Add inquiry info structure with RSSI and page scan mode. Fix buffer allocation for features to string conversion. Support inquiry with unlimited number of responses. ver 2.15: Enable the RFCOMM service level security. Add deprecated functions for reading the name. Add command for reading the clock offset. Add command for reading the clock. Add function for reading the clock. Add function for reading the local Bluetooth address. Add function for reading the local supported features. Don't configure raw devices. Don't set inquiry scan or page scan on raw devices. Don't show extended information for raw devices. Support L2CAP signal sizes bigger than 2048 bytes. Cleanup of the socket handling code of the test programs. Use better way for unaligned access. Remove sdp_internal.h and its usage. ver 2.14: Make use of additional connection information. Use library function for reading the RSSI. Use library function for reading the link quality. Use library function for reading the transmit power level. Use library functions for the link supervision timeout. Add tool for changing the device address. Add function for reading the RSSI. Add function for reading the link quality. Add function for reading the transmit power level. Add functions for the link supervision timeout. Remove deprecated functions. Update AM_PATH_BLUEZ macro. ver 2.13: Use file permission 0600 for the link key file. Add support for HID attribute descriptions. Add support for Device ID attributes. Add Device ID and HID attribute definitions. Update the UUID constants and its translations. Update L2CAP socket option definitions. Update connection information definitions. Various whitespace cleanups. ver 2.12: Inherit the device specific options from the default. Use --device for selecting the source device. Add --nosdp option for devices with resource limitation. Add support and parameter option for secure mode. Add a lot of build ids and hardware revisions. Add service classes and profile ids for WAP. Add simple AM_PATH_BLUEZ macro. Update UUID translation tables. Correct kernel interface for CMTP and HIDP support. ver 2.11: Initial support for the kernel security manager. Various cleanups to avoid inclusion of kernel headers. Fix output when the CUPS backend is called without arguments. Fix problems with a 64 bit userland. Use Bluetooth library functions if available. Use standard numbering scheme of SDP record handles. Use bit zero for vendor packets in the filter type bitmask. Add SIM Access types for service discovery. Add more audio/video profile translations. Add another company identifier. Add the missing HCI error codes. Add RFCOMM socket options. Add definition for the SECURE link mode. Add functions for reading and writing the inquiry mode. Add functions for AFH related settings and information. Add version identifier for the Bluetooth 2.0 specification. Add a master option to the hidd. Add support for changing the link key of a connection. Add support for requesting encryption on keyboards. Add support for revision information of Digianswer devices. Add support for the Zoom, IBM and TDK PCMCIA cards. Add checks for the OpenOBEX and the ALSA libraries. Add experimental mRouter support. ver 2.10: Use a define for the configuration directory. Fix string initialization for flags translation. Fix and extend the unaligned access macros. Make compiling with debug information optional. Don't override CFLAGS from configure. Check for usb_get_busses() and usb_interrupt_read(). Add optional support for compiling with PIE. Make installation of the init scripts optional. Make compiling with debug information optional. Don't override CFLAGS from configure. ver 2.9: Retry SDP connect if busy in the CUPS backend. Use packet type and allow role switch in hcitool. Use the functions from the USB library for hid2hci. Add Broadcom firmware loader. Add EPoX endian quirk for buggy keyboards. Add L2CAP info type and info result definitions. Add value for L2CAP_CONF_RFC_MODE. Change RSSI value to signed instead of unsigned. Allow UUID32 values as protocol identifiers. Update the autoconf/automake scripts. ver 2.8: Use LIBS and LDADD instead of LDFLAGS. Use HIDP subclass field for HID boot protocol. Set olen before calling getsockopt() in pand. Restore signals for dev-up script. Add PID file support for pand. Add size parameter to expand_name() in hcid. Add support for audio source and audio sink SDP records. Add support for HID virtual cable unplug. Add support for AmbiCom BT2000C card. Add defines and UUID's for audio/video profiles. Add AVDTP protocol identifier. Add HIDP subclass field. Add PKGConfig support. Fix the event code of inquiry with RSSI. Remove dummy SDP library. ver 2.7: Fix display of decoded LMP features. Update company identifiers. Add AFH related types. Add first bits from EDR prototyping specification. Add support for inquiry with RSSI. Add HCRP related SDP functions. Add HIDP header file. Add support for getting the AFH channel map. Add support for AFH mode. Add support for inquiry mode. Add Bluetooth backend for CUPS. Add the hid2hci utility. Add the hidd utility. Add the pand utility. Add the dund utility. More endian bug fixes. Give udev some time to create the RFCOMM device nodes. Release the TTY if no device node is found. New startup script for the Bluetooth subsystem. Update to the autoconf stuff. ver 2.6: Change default prefix to /usr. Add manpages for hcid and hcid.conf. Add the sdpd server daemon. Add the sdptool utility. Add the ciptool utility. Add new company identifiers. Add BNEP and CMTP header files. Add the SDP library. Use R2 for default value of pscan_rep_mode. ver 2.5: Add decoding of Bluetooth 1.2 features. Add link manager version parameter for Bluetooth 1.2. Add new company identifiers. Add D-Bus support for PIN request. Support for transmit power level. Support for park, sniff and hold mode. Support for role switch. Support for reading the clock offset. Support for requesting authentication. Support for setting connection encryption. Show revision information for Broadcom devices. Replace unprintable characters in device name. Use R1 for default value of pscan_rep_mode. Fix some 64-bit problems. Fix some endian problems. Report an error on PIN helper failure. Update bluepin script for GTK2. ver 2.4: Increase number of inquiry responses. Support for transmit power level. Display all 8 bytes of the features. Add support for reading and writing of IAC. Correct decoding class of device. Use Ericsson revision command for ST Microelectronics devices. Display AVM firmware version with 'revision' command. New code for CSR specific revision information. Support for ST Microelectronics specific initialization. Support for 3Com card version 3.0. Support for TDK, IBM and Socket cards. Support for initial baud rate. Update man pages. Fixes for some memory leaks. ver 2.3: Added const qualifiers to appropriate function arguments. Minor fixes. CSR firmware version is now displayed by 'revision' command. Voice command is working properly on big endian machines. Added support for Texas Bluetooth modules. Added support for high UART baud rates on Ericsson modules. BCSP initialization fixes. Support for role switch command (hcitool). RFCOMM config file parser fixes. Update man pages. Removed GLib dependency. ver 2.2: Updated RFCOMM header file. Additional HCI command and event defines. Support for voice settings (hciconfig). Minor hcitool fixes. Improved configure script. Added Headset testing tool. Updated man pages. RPM package. ver 2.1.1: Resurrect hci_remote_name. ver 2.1: Added hci_{read, write}_class_of_dev(). Added hci_{read, write}_current_iac_lap(). Added hci_write_local_name(). Added RFCOMM header file. Minor fixes. Improved BCSP initialization (hciattach). Support for displaying link quality (hcitool). Support for changing link supervision timeout (hcitool). New RFCOMM TTY configuration tool (rfcomm). Minor fixes and updates. ver 2.0: Additional company IDs. BCSP initialization (hciattach). Minor hciconfig fixes. ver 2.0-pr13: Support for multiple pairing modes. Link key database handling fixes. ver 2.0-pre12: Removed max link key limit. Keys never expire. Link key database is always updated. Reread PIN on SIGHUP (hcid). Bluetooth script starts SDPd, if installed. Other minor fixes. ver 2.0-pre11: Improved link key management and more verbose logging (hcid). Fixed scan command (hcitool). ver 2.0-pre10: Fix hci_inquiry function to return errors and accept user buffers. New functions hci_devba, hci_devid, hci_for_each_dev and hci_get_route. Additional company IDs. Makefile and other minor fixes. Support for reading RSSI, remote name and changing connection type (hcitool). Device initialization fixes (hcid). Other minor fixes and improvements. Build environment cleanup and fixes. ver 2.0-pre9: Improved bluepin. Working X authentication. Improved hcitool. New flexible cmd syntax, additional commands. Human readable display of the device features. LMP features to string translation support. Additional HCI command and event defines. Extended hci_filter API. ver 2.0-pre8: Additional HCI ioctls and defines. All strings and buffers are allocated dynamically. ba2str, str2ba automatically swap bdaddress. Additional hciconfig commands. Support for ACL and SCO MTU ioctls. Support for Inventel and COM1 UART based devices. Minor hcitool fixes. Improved l2test. New L2CAP test modes. Minor fixes and cleanup. ver 2.0-pre7: Bluetooth libraries and header files is now a separate package. New build environment uses automake and libtool. Massive header files cleanup. Bluetooth utilities is now a separate package. New build environment uses automake. Moved all config files and security data to /etc/bluetooth. Various cleanups. ver 2.0-pre6: API cleanup and additions. Improved hcitool. l2test minor output fixes. hciattach opt to display list of supported devices. ver 2.0-pre4: HCI filter enhancements. ver 2.0-pre3: Cleanup. ver 2.0-pre2: Additional HCI library functions. Improved CSR baud rate initialization. PCMCIA scripts fixes and enhancements. Documentation update. ver 2.0-pre1: New UART initialization utility. Hot plugging support for UART based PCMCIA devices. SCO testing utility. New authentication utility (bluepin). Minor fixes and improvements. bluez-5.87/PaxHeaders/gdbus0000644000000000000000000000005015221755523012711 xustar0020 atime=1783094113 20 ctime=1783094099 bluez-5.87/gdbus/0000755000000000000000000000000015221755523012447 5ustar00rootrootbluez-5.87/gdbus/PaxHeaders/polkit.c0000644000000000000000000000005014015011623014416 xustar0020 atime=1783092875 20 ctime=1783094099 bluez-5.87/gdbus/polkit.c0000644000000000000000000001134514015011623014103 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * D-Bus helper library * * Copyright (C) 2004-2011 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include int polkit_check_authorization(DBusConnection *conn, const char *action, gboolean interaction, void (*function) (dbus_bool_t authorized, void *user_data), void *user_data, int timeout); static void add_dict_with_string_value(DBusMessageIter *iter, const char *key, const char *str) { DBusMessageIter dict, entry, value; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &value); dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING, &str); dbus_message_iter_close_container(&entry, &value); dbus_message_iter_close_container(&dict, &entry); dbus_message_iter_close_container(iter, &dict); } static void add_empty_string_dict(DBusMessageIter *iter) { DBusMessageIter dict; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); dbus_message_iter_close_container(iter, &dict); } static void add_arguments(DBusConnection *conn, DBusMessageIter *iter, const char *action, dbus_uint32_t flags) { const char *busname = dbus_bus_get_unique_name(conn); const char *kind = "system-bus-name"; const char *cancel = ""; DBusMessageIter subject; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &subject); dbus_message_iter_append_basic(&subject, DBUS_TYPE_STRING, &kind); add_dict_with_string_value(&subject, "name", busname); dbus_message_iter_close_container(iter, &subject); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &action); add_empty_string_dict(iter); dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &flags); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &cancel); } static dbus_bool_t parse_result(DBusMessageIter *iter) { DBusMessageIter result; dbus_bool_t authorized, challenge; dbus_message_iter_recurse(iter, &result); dbus_message_iter_get_basic(&result, &authorized); dbus_message_iter_get_basic(&result, &challenge); return authorized; } struct authorization_data { void (*function) (dbus_bool_t authorized, void *user_data); void *user_data; }; static void authorization_reply(DBusPendingCall *call, void *user_data) { struct authorization_data *data = user_data; DBusMessage *reply; DBusMessageIter iter; dbus_bool_t authorized = FALSE; reply = dbus_pending_call_steal_reply(call); if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) goto done; if (dbus_message_has_signature(reply, "(bba{ss})") == FALSE) goto done; dbus_message_iter_init(reply, &iter); authorized = parse_result(&iter); done: if (data->function != NULL) data->function(authorized, data->user_data); dbus_message_unref(reply); dbus_pending_call_unref(call); } #define AUTHORITY_DBUS "org.freedesktop.PolicyKit1" #define AUTHORITY_INTF "org.freedesktop.PolicyKit1.Authority" #define AUTHORITY_PATH "/org/freedesktop/PolicyKit1/Authority" int polkit_check_authorization(DBusConnection *conn, const char *action, gboolean interaction, void (*function) (dbus_bool_t authorized, void *user_data), void *user_data, int timeout) { struct authorization_data *data; DBusMessage *msg; DBusMessageIter iter; DBusPendingCall *call; dbus_uint32_t flags = 0x00000000; if (conn == NULL) return -EINVAL; data = dbus_malloc0(sizeof(*data)); if (data == NULL) return -ENOMEM; msg = dbus_message_new_method_call(AUTHORITY_DBUS, AUTHORITY_PATH, AUTHORITY_INTF, "CheckAuthorization"); if (msg == NULL) { dbus_free(data); return -ENOMEM; } if (interaction == TRUE) flags |= 0x00000001; if (action == NULL) action = "org.freedesktop.policykit.exec"; dbus_message_iter_init_append(msg, &iter); add_arguments(conn, &iter, action, flags); if (dbus_connection_send_with_reply(conn, msg, &call, timeout) == FALSE) { dbus_message_unref(msg); dbus_free(data); return -EIO; } if (call == NULL) { dbus_message_unref(msg); dbus_free(data); return -EIO; } data->function = function; data->user_data = user_data; dbus_pending_call_set_notify(call, authorization_reply, data, dbus_free); dbus_message_unref(msg); return 0; } bluez-5.87/gdbus/PaxHeaders/gdbus.h0000644000000000000000000000005014711225433014236 xustar0020 atime=1783092781 20 ctime=1783094099 bluez-5.87/gdbus/gdbus.h0000644000000000000000000003532014711225433013722 0ustar00rootroot/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * * D-Bus helper library * * Copyright (C) 2004-2011 Marcel Holtmann * Copyright 2024 NXP * * */ #ifndef __GDBUS_H #define __GDBUS_H #ifdef __cplusplus extern "C" { #endif #include #include typedef struct GDBusArgInfo GDBusArgInfo; typedef struct GDBusMethodTable GDBusMethodTable; typedef struct GDBusSignalTable GDBusSignalTable; typedef struct GDBusPropertyTable GDBusPropertyTable; typedef struct GDBusSecurityTable GDBusSecurityTable; typedef void (* GDBusWatchFunction) (DBusConnection *connection, void *user_data); typedef void (* GDBusMessageFunction) (DBusConnection *connection, DBusMessage *message, void *user_data); typedef gboolean (* GDBusSignalFunction) (DBusConnection *connection, DBusMessage *message, void *user_data); DBusConnection *g_dbus_setup_bus(DBusBusType type, const char *name, DBusError *error); DBusConnection *g_dbus_setup_private(DBusBusType type, const char *name, DBusError *error); gboolean g_dbus_request_name(DBusConnection *connection, const char *name, DBusError *error); gboolean g_dbus_set_disconnect_function(DBusConnection *connection, GDBusWatchFunction function, void *user_data, DBusFreeFunction destroy); typedef void (* GDBusDestroyFunction) (void *user_data); typedef DBusMessage * (* GDBusMethodFunction) (DBusConnection *connection, DBusMessage *message, void *user_data); typedef gboolean (*GDBusPropertyGetter)(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data); typedef guint32 GDBusPendingPropertySet; typedef void (*GDBusPropertySetter)(const GDBusPropertyTable *property, DBusMessageIter *value, GDBusPendingPropertySet id, void *data); typedef gboolean (*GDBusPropertyExists)(const GDBusPropertyTable *property, void *data); typedef guint32 GDBusPendingReply; typedef void (* GDBusSecurityFunction) (DBusConnection *connection, const char *action, gboolean interaction, GDBusPendingReply pending); enum GDBusFlags { G_DBUS_FLAG_ENABLE_EXPERIMENTAL = (1 << 0), G_DBUS_FLAG_ENABLE_TESTING = (1 << 1), }; enum GDBusMethodFlags { G_DBUS_METHOD_FLAG_DEPRECATED = (1 << 0), G_DBUS_METHOD_FLAG_NOREPLY = (1 << 1), G_DBUS_METHOD_FLAG_ASYNC = (1 << 2), G_DBUS_METHOD_FLAG_EXPERIMENTAL = (1 << 3), G_DBUS_METHOD_FLAG_TESTING = (1 << 4), }; enum GDBusSignalFlags { G_DBUS_SIGNAL_FLAG_DEPRECATED = (1 << 0), G_DBUS_SIGNAL_FLAG_EXPERIMENTAL = (1 << 1), G_DBUS_SIGNAL_FLAG_TESTING = (1 << 2), }; enum GDBusPropertyFlags { G_DBUS_PROPERTY_FLAG_DEPRECATED = (1 << 0), G_DBUS_PROPERTY_FLAG_EXPERIMENTAL = (1 << 1), G_DBUS_PROPERTY_FLAG_TESTING = (1 << 2), }; enum GDBusSecurityFlags { G_DBUS_SECURITY_FLAG_DEPRECATED = (1 << 0), G_DBUS_SECURITY_FLAG_BUILTIN = (1 << 1), G_DBUS_SECURITY_FLAG_ALLOW_INTERACTION = (1 << 2), }; enum GDbusPropertyChangedFlags { G_DBUS_PROPERTY_CHANGED_FLAG_FLUSH = (1 << 0), }; typedef enum GDBusMethodFlags GDBusMethodFlags; typedef enum GDBusSignalFlags GDBusSignalFlags; typedef enum GDBusPropertyFlags GDBusPropertyFlags; typedef enum GDBusSecurityFlags GDBusSecurityFlags; typedef enum GDbusPropertyChangedFlags GDbusPropertyChangedFlags; struct GDBusArgInfo { const char *name; const char *signature; }; struct GDBusMethodTable { const char *name; GDBusMethodFunction function; GDBusMethodFlags flags; unsigned int privilege; const GDBusArgInfo *in_args; const GDBusArgInfo *out_args; }; struct GDBusSignalTable { const char *name; GDBusSignalFlags flags; const GDBusArgInfo *args; }; struct GDBusPropertyTable { const char *name; const char *type; GDBusPropertyGetter get; GDBusPropertySetter set; GDBusPropertyExists exists; GDBusPropertyFlags flags; }; struct GDBusSecurityTable { unsigned int privilege; const char *action; GDBusSecurityFlags flags; GDBusSecurityFunction function; }; #define GDBUS_ARGS(args...) (const GDBusArgInfo[]) { args, { } } #define GDBUS_METHOD(_name, _in_args, _out_args, _function) \ .name = _name, \ .in_args = _in_args, \ .out_args = _out_args, \ .function = _function #define GDBUS_ASYNC_METHOD(_name, _in_args, _out_args, _function) \ .name = _name, \ .in_args = _in_args, \ .out_args = _out_args, \ .function = _function, \ .flags = G_DBUS_METHOD_FLAG_ASYNC #define GDBUS_DEPRECATED_METHOD(_name, _in_args, _out_args, _function) \ .name = _name, \ .in_args = _in_args, \ .out_args = _out_args, \ .function = _function, \ .flags = G_DBUS_METHOD_FLAG_DEPRECATED #define GDBUS_DEPRECATED_ASYNC_METHOD(_name, _in_args, _out_args, _function) \ .name = _name, \ .in_args = _in_args, \ .out_args = _out_args, \ .function = _function, \ .flags = G_DBUS_METHOD_FLAG_ASYNC | G_DBUS_METHOD_FLAG_DEPRECATED #define GDBUS_EXPERIMENTAL_METHOD(_name, _in_args, _out_args, _function) \ .name = _name, \ .in_args = _in_args, \ .out_args = _out_args, \ .function = _function, \ .flags = G_DBUS_METHOD_FLAG_EXPERIMENTAL #define GDBUS_EXPERIMENTAL_ASYNC_METHOD(_name, _in_args, _out_args, _function) \ .name = _name, \ .in_args = _in_args, \ .out_args = _out_args, \ .function = _function, \ .flags = G_DBUS_METHOD_FLAG_ASYNC | G_DBUS_METHOD_FLAG_EXPERIMENTAL #define GDBUS_TESTING_METHOD(_name, _in_args, _out_args, _function) \ .name = _name, \ .in_args = _in_args, \ .out_args = _out_args, \ .function = _function, \ .flags = G_DBUS_METHOD_FLAG_TESTING #define GDBUS_TESTING_ASYNC_METHOD(_name, _in_args, _out_args, _function) \ .name = _name, \ .in_args = _in_args, \ .out_args = _out_args, \ .function = _function, \ .flags = G_DBUS_METHOD_FLAG_ASYNC | G_DBUS_METHOD_FLAG_TESTING #define GDBUS_NOREPLY_METHOD(_name, _in_args, _out_args, _function) \ .name = _name, \ .in_args = _in_args, \ .out_args = _out_args, \ .function = _function, \ .flags = G_DBUS_METHOD_FLAG_NOREPLY #define GDBUS_SIGNAL(_name, _args) \ .name = _name, \ .args = _args #define GDBUS_DEPRECATED_SIGNAL(_name, _args) \ .name = _name, \ .args = _args, \ .flags = G_DBUS_SIGNAL_FLAG_DEPRECATED #define GDBUS_EXPERIMENTAL_SIGNAL(_name, _args) \ .name = _name, \ .args = _args, \ .flags = G_DBUS_SIGNAL_FLAG_EXPERIMENTAL #define GDBUS_TESTING_SIGNAL(_name, _args) \ .name = _name, \ .args = _args, \ .flags = G_DBUS_SIGNAL_FLAG_EXPERIMENTAL void g_dbus_set_flags(int flags); int g_dbus_get_flags(void); typedef void (*g_dbus_destroy_func_t)(void *user_data); typedef void (*g_dbus_debug_func_t)(const char *str, void *user_data); void g_dbus_set_debug(g_dbus_debug_func_t cb, void *user_data, g_dbus_destroy_func_t destroy); gboolean g_dbus_register_interface(DBusConnection *connection, const char *path, const char *name, const GDBusMethodTable *methods, const GDBusSignalTable *signals, const GDBusPropertyTable *properties, void *user_data, GDBusDestroyFunction destroy); gboolean g_dbus_unregister_interface(DBusConnection *connection, const char *path, const char *name); gboolean g_dbus_register_security(const GDBusSecurityTable *security); gboolean g_dbus_unregister_security(const GDBusSecurityTable *security); void g_dbus_pending_success(DBusConnection *connection, GDBusPendingReply pending); void g_dbus_pending_error(DBusConnection *connection, GDBusPendingReply pending, const char *name, const char *format, ...) __attribute__((format(printf, 4, 5))); void g_dbus_pending_error_valist(DBusConnection *connection, GDBusPendingReply pending, const char *name, const char *format, va_list args); DBusMessage *g_dbus_create_error(DBusMessage *message, const char *name, const char *format, ...) __attribute__((format(printf, 3, 4))); DBusMessage *g_dbus_create_error_valist(DBusMessage *message, const char *name, const char *format, va_list args); DBusMessage *g_dbus_create_reply(DBusMessage *message, int type, ...); DBusMessage *g_dbus_create_reply_valist(DBusMessage *message, int type, va_list args); gboolean g_dbus_send_message(DBusConnection *connection, DBusMessage *message); gboolean g_dbus_send_message_with_reply(DBusConnection *connection, DBusMessage *message, DBusPendingCall **call, int timeout); gboolean g_dbus_send_error(DBusConnection *connection, DBusMessage *message, const char *name, const char *format, ...) __attribute__((format(printf, 4, 5))); gboolean g_dbus_send_error_valist(DBusConnection *connection, DBusMessage *message, const char *name, const char *format, va_list args); gboolean g_dbus_send_reply(DBusConnection *connection, DBusMessage *message, int type, ...); gboolean g_dbus_send_reply_valist(DBusConnection *connection, DBusMessage *message, int type, va_list args); gboolean g_dbus_emit_signal(DBusConnection *connection, const char *path, const char *interface, const char *name, int type, ...); gboolean g_dbus_emit_signal_valist(DBusConnection *connection, const char *path, const char *interface, const char *name, int type, va_list args); guint g_dbus_add_service_watch(DBusConnection *connection, const char *name, GDBusWatchFunction connect, GDBusWatchFunction disconnect, void *user_data, GDBusDestroyFunction destroy); guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name, GDBusWatchFunction function, void *user_data, GDBusDestroyFunction destroy); guint g_dbus_add_signal_watch(DBusConnection *connection, const char *sender, const char *path, const char *interface, const char *member, GDBusSignalFunction function, void *user_data, GDBusDestroyFunction destroy); guint g_dbus_add_properties_watch(DBusConnection *connection, const char *sender, const char *path, const char *interface, GDBusSignalFunction function, void *user_data, GDBusDestroyFunction destroy); gboolean g_dbus_remove_watch(DBusConnection *connection, guint tag); void g_dbus_remove_all_watches(DBusConnection *connection); const char *g_dbus_pending_property_get_sender(GDBusPendingPropertySet id); void g_dbus_pending_property_success(GDBusPendingPropertySet id); void g_dbus_pending_property_error_valist(GDBusPendingReply id, const char *name, const char *format, va_list args); void g_dbus_pending_property_error(GDBusPendingReply id, const char *name, const char *format, ...); /* * Note that when multiple properties for a given object path are changed * in the same mainloop iteration, they will be grouped with the last * property changed. If this behaviour is undesired, use * g_dbus_emit_property_changed_full() with the * G_DBUS_PROPERTY_CHANGED_FLAG_FLUSH flag, causing the signal to ignore * any grouping. */ void g_dbus_emit_property_changed(DBusConnection *connection, const char *path, const char *interface, const char *name); void g_dbus_emit_property_changed_full(DBusConnection *connection, const char *path, const char *interface, const char *name, GDbusPropertyChangedFlags flags); gboolean g_dbus_get_properties(DBusConnection *connection, const char *path, const char *interface, DBusMessageIter *iter); gboolean g_dbus_attach_object_manager(DBusConnection *connection); gboolean g_dbus_detach_object_manager(DBusConnection *connection); typedef struct GDBusClient GDBusClient; typedef struct GDBusProxy GDBusProxy; GDBusProxy *g_dbus_proxy_new(GDBusClient *client, const char *path, const char *interface); GDBusProxy *g_dbus_proxy_ref(GDBusProxy *proxy); void g_dbus_proxy_unref(GDBusProxy *proxy); const char *g_dbus_proxy_get_path(const GDBusProxy *proxy); const char *g_dbus_proxy_get_interface(GDBusProxy *proxy); gboolean g_dbus_proxy_get_property(GDBusProxy *proxy, const char *name, DBusMessageIter *iter); GDBusProxy *g_dbus_proxy_lookup(GList *list, int *index, const char *path, const char *interface); char *g_dbus_proxy_path_lookup(GList *list, int *index, const char *path); gboolean g_dbus_proxy_refresh_property(GDBusProxy *proxy, const char *name); typedef void (* GDBusResultFunction) (const DBusError *error, void *user_data); gboolean g_dbus_proxy_set_property_basic(GDBusProxy *proxy, const char *name, int type, const void *value, GDBusResultFunction function, void *user_data, GDBusDestroyFunction destroy); gboolean g_dbus_proxy_set_property_dict(GDBusProxy *proxy, const char *name, GDBusResultFunction function, void *user_data, GDBusDestroyFunction destroy, char *entry, ...); gboolean g_dbus_proxy_set_property_array(GDBusProxy *proxy, const char *name, int type, const void *value, size_t size, GDBusResultFunction function, void *user_data, GDBusDestroyFunction destroy); void g_dbus_dict_append_entry(DBusMessageIter *dict, const char *key, int type, void *val); void g_dbus_dict_append_basic_array(DBusMessageIter *dict, int key_type, const void *key, int type, void *val, int n_elements); void g_dbus_dict_append_array(DBusMessageIter *dict, const char *key, int type, void *val, int n_elements); typedef void (* GDBusSetupFunction) (DBusMessageIter *iter, void *user_data); typedef void (* GDBusReturnFunction) (DBusMessage *message, void *user_data); gboolean g_dbus_proxy_method_call(GDBusProxy *proxy, const char *method, GDBusSetupFunction setup, GDBusReturnFunction function, void *user_data, GDBusDestroyFunction destroy); typedef void (* GDBusClientFunction) (GDBusClient *client, void *user_data); typedef void (* GDBusProxyFunction) (GDBusProxy *proxy, void *user_data); typedef void (* GDBusPropertyFunction) (GDBusProxy *proxy, const char *name, DBusMessageIter *iter, void *user_data); gboolean g_dbus_proxy_set_property_watch(GDBusProxy *proxy, GDBusPropertyFunction function, void *user_data); gboolean g_dbus_proxy_set_removed_watch(GDBusProxy *proxy, GDBusProxyFunction destroy, void *user_data); GDBusClient *g_dbus_client_new(DBusConnection *connection, const char *service, const char *path); GDBusClient *g_dbus_client_new_full(DBusConnection *connection, const char *service, const char *path, const char *root_path); GDBusClient *g_dbus_client_ref(GDBusClient *client); void g_dbus_client_unref(GDBusClient *client); gboolean g_dbus_client_set_connect_watch(GDBusClient *client, GDBusWatchFunction function, void *user_data); gboolean g_dbus_client_set_disconnect_watch(GDBusClient *client, GDBusWatchFunction function, void *user_data); gboolean g_dbus_client_set_signal_watch(GDBusClient *client, GDBusMessageFunction function, void *user_data); gboolean g_dbus_client_set_ready_watch(GDBusClient *client, GDBusClientFunction ready, void *user_data); gboolean g_dbus_client_set_proxy_handlers(GDBusClient *client, GDBusProxyFunction proxy_added, GDBusProxyFunction proxy_removed, GDBusPropertyFunction property_changed, void *user_data); #ifdef __cplusplus } #endif #endif /* __GDBUS_H */ bluez-5.87/gdbus/PaxHeaders/object.c0000644000000000000000000000005014773211264014400 xustar0020 atime=1783092867 20 ctime=1783094099 bluez-5.87/gdbus/object.c0000644000000000000000000013733714773211264014077 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * D-Bus helper library * * Copyright (C) 2004-2011 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "gdbus.h" /* define MAX_INPUT for musl */ #ifndef MAX_INPUT #define MAX_INPUT _POSIX_MAX_INPUT #endif #include "src/shared/util.h" #define info(fmt...) #define error(fmt...) #define debug(fmt...) #define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" #ifndef DBUS_ERROR_UNKNOWN_PROPERTY #define DBUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty" #endif #ifndef DBUS_ERROR_PROPERTY_READ_ONLY #define DBUS_ERROR_PROPERTY_READ_ONLY "org.freedesktop.DBus.Error.PropertyReadOnly" #endif struct generic_data { unsigned int refcount; DBusConnection *conn; char *path; GSList *interfaces; GSList *objects; GSList *added; GSList *removed; guint process_id; gboolean pending_prop; char *introspect; struct generic_data *parent; }; struct interface_data { char *name; const GDBusMethodTable *methods; const GDBusSignalTable *signals; const GDBusPropertyTable *properties; GSList *pending_prop; void *user_data; GDBusDestroyFunction destroy; }; struct security_data { GDBusPendingReply pending; DBusMessage *message; const GDBusMethodTable *method; void *iface_user_data; }; struct property_data { DBusConnection *conn; GDBusPendingPropertySet id; DBusMessage *message; }; struct debug_data { g_dbus_debug_func_t func; g_dbus_destroy_func_t destroy; void *data; }; static int global_flags = 0; static struct generic_data *root; static GSList *pending = NULL; static struct debug_data debug = { NULL, NULL, NULL }; static gboolean process_changes(gpointer user_data); static void process_properties_from_interface(struct generic_data *data, struct interface_data *iface); static void process_property_changes(struct generic_data *data); static void print_arguments(GString *gstr, const GDBusArgInfo *args, const char *direction) { for (; args && args->name; args++) { g_string_append_printf(gstr, "name, args->signature); if (direction) g_string_append_printf(gstr, " direction=\"%s\"/>\n", direction); else g_string_append_printf(gstr, "/>\n"); } } #define G_DBUS_ANNOTATE(name_, value_) \ "" #define G_DBUS_ANNOTATE_DEPRECATED \ G_DBUS_ANNOTATE("Deprecated", "true") #define G_DBUS_ANNOTATE_NOREPLY \ G_DBUS_ANNOTATE("Method.NoReply", "true") static gboolean check_experimental(int flags, int flag) { if (!(flags & flag)) return FALSE; return !(global_flags & G_DBUS_FLAG_ENABLE_EXPERIMENTAL); } static bool check_testing(int flags, int flag) { if (!(flags & flag)) return false; return !(global_flags & G_DBUS_FLAG_ENABLE_TESTING); } static void g_dbus_debug(const char *format, ...) { va_list va; char str[MAX_INPUT]; if (!format || !debug.func) return; va_start(va, format); vsnprintf(str, sizeof(str), format, va); debug.func(str, debug.data); va_end(va); } static void generate_interface_xml(GString *gstr, struct interface_data *iface) { const GDBusMethodTable *method; const GDBusSignalTable *signal; const GDBusPropertyTable *property; for (method = iface->methods; method && method->name; method++) { if (check_experimental(method->flags, G_DBUS_METHOD_FLAG_EXPERIMENTAL)) continue; if (check_testing(method->flags, G_DBUS_METHOD_FLAG_TESTING)) continue; g_string_append_printf(gstr, "", method->name); print_arguments(gstr, method->in_args, "in"); print_arguments(gstr, method->out_args, "out"); if (method->flags & G_DBUS_METHOD_FLAG_DEPRECATED) g_string_append_printf(gstr, G_DBUS_ANNOTATE_DEPRECATED); if (method->flags & G_DBUS_METHOD_FLAG_NOREPLY) g_string_append_printf(gstr, G_DBUS_ANNOTATE_NOREPLY); g_string_append_printf(gstr, ""); } for (signal = iface->signals; signal && signal->name; signal++) { if (check_experimental(signal->flags, G_DBUS_SIGNAL_FLAG_EXPERIMENTAL)) continue; if (check_testing(signal->flags, G_DBUS_SIGNAL_FLAG_TESTING)) continue; g_string_append_printf(gstr, "", signal->name); print_arguments(gstr, signal->args, NULL); if (signal->flags & G_DBUS_SIGNAL_FLAG_DEPRECATED) g_string_append_printf(gstr, G_DBUS_ANNOTATE_DEPRECATED); g_string_append_printf(gstr, "\n"); } for (property = iface->properties; property && property->name; property++) { if (check_experimental(property->flags, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL)) continue; if (check_testing(property->flags, G_DBUS_PROPERTY_FLAG_TESTING)) continue; g_string_append_printf(gstr, "", property->name, property->type, property->get ? "read" : "", property->set ? "write" : ""); if (property->flags & G_DBUS_PROPERTY_FLAG_DEPRECATED) g_string_append_printf(gstr, G_DBUS_ANNOTATE_DEPRECATED); g_string_append_printf(gstr, ""); } } static void generate_introspection_xml(DBusConnection *conn, struct generic_data *data, const char *path) { GSList *list; GString *gstr; char **children; int i; g_free(data->introspect); gstr = g_string_new(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE); g_string_append_printf(gstr, ""); for (list = data->interfaces; list; list = list->next) { struct interface_data *iface = list->data; g_string_append_printf(gstr, "", iface->name); generate_interface_xml(gstr, iface); g_string_append_printf(gstr, ""); } if (!dbus_connection_list_registered(conn, path, &children)) goto done; for (i = 0; children[i]; i++) g_string_append_printf(gstr, "", children[i]); dbus_free_string_array(children); done: g_string_append_printf(gstr, ""); data->introspect = g_string_free(gstr, FALSE); } static DBusMessage *introspect(DBusConnection *connection, DBusMessage *message, void *user_data) { struct generic_data *data = user_data; DBusMessage *reply; if (data->introspect == NULL) generate_introspection_xml(connection, data, dbus_message_get_path(message)); reply = dbus_message_new_method_return(message); if (reply == NULL) return NULL; dbus_message_append_args(reply, DBUS_TYPE_STRING, &data->introspect, DBUS_TYPE_INVALID); return reply; } static DBusHandlerResult process_message(DBusConnection *connection, DBusMessage *message, const GDBusMethodTable *method, void *iface_user_data) { DBusMessage *reply; reply = method->function(connection, message, iface_user_data); if (method->flags & G_DBUS_METHOD_FLAG_NOREPLY || dbus_message_get_no_reply(message)) { if (reply != NULL) dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } if (method->flags & G_DBUS_METHOD_FLAG_ASYNC) { if (reply == NULL) return DBUS_HANDLER_RESULT_HANDLED; } if (reply == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; g_dbus_send_message(connection, reply); return DBUS_HANDLER_RESULT_HANDLED; } static GDBusPendingReply next_pending = 1; static GSList *pending_security = NULL; static const GDBusSecurityTable *security_table = NULL; void g_dbus_pending_success(DBusConnection *connection, GDBusPendingReply pending) { GSList *list; for (list = pending_security; list; list = list->next) { struct security_data *secdata = list->data; if (secdata->pending != pending) continue; pending_security = g_slist_remove(pending_security, secdata); process_message(connection, secdata->message, secdata->method, secdata->iface_user_data); dbus_message_unref(secdata->message); g_free(secdata); return; } } void g_dbus_pending_error_valist(DBusConnection *connection, GDBusPendingReply pending, const char *name, const char *format, va_list args) { GSList *list; for (list = pending_security; list; list = list->next) { struct security_data *secdata = list->data; if (secdata->pending != pending) continue; pending_security = g_slist_remove(pending_security, secdata); g_dbus_send_error_valist(connection, secdata->message, name, format, args); dbus_message_unref(secdata->message); g_free(secdata); return; } } void g_dbus_pending_error(DBusConnection *connection, GDBusPendingReply pending, const char *name, const char *format, ...) { va_list args; va_start(args, format); g_dbus_pending_error_valist(connection, pending, name, format, args); va_end(args); } int polkit_check_authorization(DBusConnection *conn, const char *action, gboolean interaction, void (*function) (dbus_bool_t authorized, void *user_data), void *user_data, int timeout); struct builtin_security_data { DBusConnection *conn; GDBusPendingReply pending; }; static void builtin_security_result(dbus_bool_t authorized, void *user_data) { struct builtin_security_data *data = user_data; if (authorized == TRUE) g_dbus_pending_success(data->conn, data->pending); else g_dbus_pending_error(data->conn, data->pending, DBUS_ERROR_AUTH_FAILED, NULL); g_free(data); } static void builtin_security_function(DBusConnection *conn, const char *action, gboolean interaction, GDBusPendingReply pending) { struct builtin_security_data *data; data = g_new0(struct builtin_security_data, 1); data->conn = conn; data->pending = pending; if (polkit_check_authorization(conn, action, interaction, builtin_security_result, data, 30000) < 0) g_dbus_pending_error(conn, pending, NULL, NULL); } static gboolean check_privilege(DBusConnection *conn, DBusMessage *msg, const GDBusMethodTable *method, void *iface_user_data) { const GDBusSecurityTable *security; for (security = security_table; security && security->privilege; security++) { struct security_data *secdata; gboolean interaction; if (security->privilege != method->privilege) continue; secdata = g_new(struct security_data, 1); secdata->pending = next_pending++; secdata->message = dbus_message_ref(msg); secdata->method = method; secdata->iface_user_data = iface_user_data; pending_security = g_slist_prepend(pending_security, secdata); if (security->flags & G_DBUS_SECURITY_FLAG_ALLOW_INTERACTION) interaction = TRUE; else interaction = FALSE; if (!(security->flags & G_DBUS_SECURITY_FLAG_BUILTIN) && security->function) security->function(conn, security->action, interaction, secdata->pending); else builtin_security_function(conn, security->action, interaction, secdata->pending); return TRUE; } return FALSE; } static GDBusPendingPropertySet next_pending_property = 1; static GSList *pending_property_set; static int pending_property_data_compare_id(const void *data, const void *user_data) { const struct property_data *propdata = data; const GDBusPendingPropertySet *id = user_data; return propdata->id - *id; } static struct property_data *remove_pending_property_data( GDBusPendingPropertySet id) { struct property_data *propdata; GSList *l; l = g_slist_find_custom(pending_property_set, &id, pending_property_data_compare_id); if (l == NULL) return NULL; propdata = l->data; pending_property_set = g_slist_delete_link(pending_property_set, l); return propdata; } const char *g_dbus_pending_property_get_sender(GDBusPendingPropertySet id) { struct property_data *propdata; GSList *l; l = g_slist_find_custom(pending_property_set, &id, pending_property_data_compare_id); if (l == NULL) return NULL; propdata = l->data; return dbus_message_get_sender(propdata->message); } void g_dbus_pending_property_success(GDBusPendingPropertySet id) { struct property_data *propdata; propdata = remove_pending_property_data(id); if (propdata == NULL) return; g_dbus_send_reply(propdata->conn, propdata->message, DBUS_TYPE_INVALID); dbus_message_unref(propdata->message); g_free(propdata); } void g_dbus_pending_property_error_valist(GDBusPendingReply id, const char *name, const char *format, va_list args) { struct property_data *propdata; propdata = remove_pending_property_data(id); if (propdata == NULL) return; g_dbus_send_error_valist(propdata->conn, propdata->message, name, format, args); dbus_message_unref(propdata->message); g_free(propdata); } void g_dbus_pending_property_error(GDBusPendingReply id, const char *name, const char *format, ...) { va_list args; va_start(args, format); g_dbus_pending_property_error_valist(id, name, format, args); va_end(args); } static void reset_parent(gpointer data, gpointer user_data) { struct generic_data *child = data; struct generic_data *parent = user_data; child->parent = parent; } static void append_property(struct interface_data *iface, const GDBusPropertyTable *p, DBusMessageIter *dict) { DBusMessageIter entry, value; 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, &value, iface->user_data); dbus_message_iter_close_container(&entry, &value); dbus_message_iter_close_container(dict, &entry); } static void append_properties(struct interface_data *data, DBusMessageIter *iter) { DBusMessageIter dict; const GDBusPropertyTable *p; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); for (p = data->properties; p && p->name; p++) { if (check_experimental(p->flags, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL)) continue; if (check_testing(p->flags, G_DBUS_PROPERTY_FLAG_TESTING)) continue; if (p->get == NULL) continue; if (p->exists != NULL && !p->exists(p, data->user_data)) continue; append_property(data, p, &dict); } dbus_message_iter_close_container(iter, &dict); } static void append_interface(gpointer data, gpointer user_data) { struct interface_data *iface = data; DBusMessageIter *array = user_data; DBusMessageIter entry; dbus_message_iter_open_container(array, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &iface->name); append_properties(data, &entry); dbus_message_iter_close_container(array, &entry); } static const char *dbus_message_type_string(DBusMessage *msg) { return dbus_message_type_to_string(dbus_message_get_type(msg)); } static void g_dbus_send_unref(DBusConnection *conn, DBusMessage *msg) { g_dbus_debug("[%s] %s.%s", dbus_message_type_string(msg), dbus_message_get_interface(msg), dbus_message_get_member(msg)); dbus_connection_send(conn, msg, NULL); dbus_message_unref(msg); } static void emit_interfaces_added(struct generic_data *data) { DBusMessage *signal; DBusMessageIter iter, array; if (root == NULL || data == root) return; /* Emit InterfacesAdded on the parent first so it appears first on the * bus as child objects may point to it. */ if (data->parent && data->parent->added) emit_interfaces_added(data->parent); signal = dbus_message_new_signal(root->path, DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesAdded"); if (signal == NULL) return; dbus_message_iter_init_append(signal, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &data->path); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &array); g_slist_foreach(data->added, append_interface, &array); g_slist_free(data->added); data->added = NULL; dbus_message_iter_close_container(&iter, &array); /* Use g_dbus_send_unref to avoid recursive calls to g_dbus_flush */ g_dbus_send_unref(data->conn, signal); } static struct interface_data *find_interface(GSList *interfaces, const char *name) { GSList *list; if (name == NULL) return NULL; for (list = interfaces; list; list = list->next) { struct interface_data *iface = list->data; if (!strcmp(name, iface->name)) return iface; } return NULL; } static gboolean g_dbus_args_have_signature(const GDBusArgInfo *args, DBusMessage *message) { const char *sig = dbus_message_get_signature(message); const char *p = NULL; for (; args && args->signature && *sig; args++) { p = args->signature; for (; *sig && *p; sig++, p++) { if (*p != *sig) return FALSE; } } if (*sig || (p && *p) || (args && args->signature)) return FALSE; return TRUE; } static void add_pending(struct generic_data *data) { guint old_id = data->process_id; data->process_id = g_idle_add(process_changes, data); if (old_id > 0) { /* * If the element already had an old idler, remove the old one, * no need to re-add it to the pending list. */ g_source_remove(old_id); return; } pending = g_slist_append(pending, data); } static gboolean remove_interface(struct generic_data *data, const char *name) { struct interface_data *iface; iface = find_interface(data->interfaces, name); if (iface == NULL) return FALSE; process_properties_from_interface(data, iface); data->interfaces = g_slist_remove(data->interfaces, iface); if (iface->destroy) { iface->destroy(iface->user_data); iface->user_data = NULL; } /* * Interface being removed was just added, on the same mainloop * iteration? Don't send any signal */ if (g_slist_find(data->added, iface)) { data->added = g_slist_remove(data->added, iface); g_free(iface->name); g_free(iface); return TRUE; } if (data->parent == NULL) { g_free(iface->name); g_free(iface); return TRUE; } data->removed = g_slist_prepend(data->removed, iface->name); g_free(iface); add_pending(data); return TRUE; } static struct generic_data *invalidate_parent_data(DBusConnection *conn, const char *child_path) { struct generic_data *data = NULL, *child = NULL, *parent = NULL; char *parent_path, *slash; parent_path = g_strdup(child_path); slash = strrchr(parent_path, '/'); if (slash == NULL) goto done; if (slash == parent_path && parent_path[1] != '\0') parent_path[1] = '\0'; else *slash = '\0'; if (!strlen(parent_path)) goto done; if (dbus_connection_get_object_path_data(conn, parent_path, (void *) &data) == FALSE) { goto done; } parent = invalidate_parent_data(conn, parent_path); if (data == NULL) { data = parent; if (data == NULL) goto done; } g_free(data->introspect); data->introspect = NULL; if (!dbus_connection_get_object_path_data(conn, child_path, (void *) &child)) goto done; if (child == NULL || g_slist_find(data->objects, child) != NULL) goto done; if (g_slist_find(parent->objects, child)) goto done; data->objects = g_slist_prepend(data->objects, child); child->parent = data; done: g_free(parent_path); return data; } static inline const GDBusPropertyTable *find_property(const GDBusPropertyTable *properties, const char *name) { const GDBusPropertyTable *p; for (p = properties; p && p->name; p++) { if (strcmp(name, p->name) != 0) continue; if (check_experimental(p->flags, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL)) break; if (check_testing(p->flags, G_DBUS_PROPERTY_FLAG_TESTING)) break; return p; } return NULL; } static DBusMessage *properties_get(DBusConnection *connection, DBusMessage *message, void *user_data) { struct generic_data *data = user_data; struct interface_data *iface; const GDBusPropertyTable *property; const char *interface, *name; DBusMessageIter iter, value; DBusMessage *reply; if (!dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)) return NULL; iface = find_interface(data->interfaces, interface); if (iface == NULL) return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, "No such interface '%s'", interface); property = find_property(iface->properties, name); if (property == NULL) return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, "No such property '%s'", name); if (property->exists != NULL && !property->exists(property, iface->user_data)) return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, "No such property '%s'", name); if (property->get == NULL) return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, "Property '%s' is not readable", name); reply = dbus_message_new_method_return(message); if (reply == NULL) return NULL; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, property->type, &value); if (!property->get(property, &value, iface->user_data)) { dbus_message_unref(reply); return NULL; } dbus_message_iter_close_container(&iter, &value); return reply; } static DBusMessage *properties_get_all(DBusConnection *connection, DBusMessage *message, void *user_data) { struct generic_data *data = user_data; struct interface_data *iface; const char *interface; DBusMessageIter iter; DBusMessage *reply; if (!dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_INVALID)) return NULL; iface = find_interface(data->interfaces, interface); if (iface == NULL) return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, "No such interface '%s'", interface); reply = dbus_message_new_method_return(message); if (reply == NULL) return NULL; dbus_message_iter_init_append(reply, &iter); append_properties(iface, &iter); return reply; } static DBusMessage *properties_set(DBusConnection *connection, DBusMessage *message, void *user_data) { struct generic_data *data = user_data; DBusMessageIter iter, sub; struct interface_data *iface; const GDBusPropertyTable *property; const char *name, *interface; struct property_data *propdata; gboolean valid_signature; char *signature; if (!dbus_message_iter_init(message, &iter)) return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, "No arguments given"); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, "Invalid argument type: '%c'", dbus_message_iter_get_arg_type(&iter)); dbus_message_iter_get_basic(&iter, &interface); dbus_message_iter_next(&iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, "Invalid argument type: '%c'", dbus_message_iter_get_arg_type(&iter)); dbus_message_iter_get_basic(&iter, &name); dbus_message_iter_next(&iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, "Invalid argument type: '%c'", dbus_message_iter_get_arg_type(&iter)); dbus_message_iter_recurse(&iter, &sub); iface = find_interface(data->interfaces, interface); if (iface == NULL) return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, "No such interface '%s'", interface); property = find_property(iface->properties, name); if (property == NULL) return g_dbus_create_error(message, DBUS_ERROR_UNKNOWN_PROPERTY, "No such property '%s'", name); if (property->set == NULL) return g_dbus_create_error(message, DBUS_ERROR_PROPERTY_READ_ONLY, "Property '%s' is not writable", name); if (property->exists != NULL && !property->exists(property, iface->user_data)) return g_dbus_create_error(message, DBUS_ERROR_UNKNOWN_PROPERTY, "No such property '%s'", name); signature = dbus_message_iter_get_signature(&sub); valid_signature = strcmp(signature, property->type) ? FALSE : TRUE; dbus_free(signature); if (!valid_signature) return g_dbus_create_error(message, DBUS_ERROR_INVALID_SIGNATURE, "Invalid signature for '%s'", name); propdata = g_new(struct property_data, 1); propdata->id = next_pending_property++; propdata->message = dbus_message_ref(message); propdata->conn = connection; pending_property_set = g_slist_prepend(pending_property_set, propdata); property->set(property, &sub, propdata->id, iface->user_data); return NULL; } static const GDBusMethodTable properties_methods[] = { { GDBUS_METHOD("Get", GDBUS_ARGS({ "interface", "s" }, { "name", "s" }), GDBUS_ARGS({ "value", "v" }), properties_get) }, { GDBUS_ASYNC_METHOD("Set", GDBUS_ARGS({ "interface", "s" }, { "name", "s" }, { "value", "v" }), NULL, properties_set) }, { GDBUS_METHOD("GetAll", GDBUS_ARGS({ "interface", "s" }), GDBUS_ARGS({ "properties", "a{sv}" }), properties_get_all) }, { } }; static const GDBusSignalTable properties_signals[] = { { GDBUS_SIGNAL("PropertiesChanged", GDBUS_ARGS({ "interface", "s" }, { "changed_properties", "a{sv}" }, { "invalidated_properties", "as"})) }, { } }; static void append_name(gpointer data, gpointer user_data) { char *name = data; DBusMessageIter *iter = user_data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &name); } static void emit_interfaces_removed(struct generic_data *data) { DBusMessage *signal; DBusMessageIter iter, array; if (root == NULL || data == root) return; signal = dbus_message_new_signal(root->path, DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesRemoved"); if (signal == NULL) return; dbus_message_iter_init_append(signal, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &data->path); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &array); g_slist_foreach(data->removed, append_name, &array); g_slist_free_full(data->removed, g_free); data->removed = NULL; dbus_message_iter_close_container(&iter, &array); /* Use g_dbus_send_unref to avoid recursive calls to g_dbus_flush */ g_dbus_send_unref(data->conn, signal); } static void remove_pending(struct generic_data *data) { if (data->process_id > 0) { g_source_remove(data->process_id); data->process_id = 0; } pending = g_slist_remove(pending, data); } static gboolean process_changes(gpointer user_data) { struct generic_data *data = user_data; remove_pending(data); if (data->added != NULL) emit_interfaces_added(data); /* Flush pending properties */ if (data->pending_prop == TRUE) process_property_changes(data); if (data->removed != NULL) emit_interfaces_removed(data); data->process_id = 0; return FALSE; } static void generic_unregister(DBusConnection *connection, void *user_data) { struct generic_data *data = user_data; struct generic_data *parent = data->parent; if (parent != NULL) parent->objects = g_slist_remove(parent->objects, data); if (data->process_id > 0) { g_source_remove(data->process_id); data->process_id = 0; process_changes(data); } g_slist_foreach(data->objects, reset_parent, data->parent); g_slist_free(data->objects); dbus_connection_unref(data->conn); g_free(data->introspect); g_free(data->path); g_free(data); } static DBusHandlerResult generic_message(DBusConnection *connection, DBusMessage *message, void *user_data) { struct generic_data *data = user_data; struct interface_data *iface; const GDBusMethodTable *method; const char *interface; g_dbus_debug("[%s:%s] > %s.%s [#%d]", dbus_message_get_sender(message), dbus_message_type_string(message), dbus_message_get_interface(message), dbus_message_get_member(message), dbus_message_get_serial(message)); interface = dbus_message_get_interface(message); iface = find_interface(data->interfaces, interface); if (iface == NULL) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; for (method = iface->methods; method && method->name && method->function; method++) { if (dbus_message_is_method_call(message, iface->name, method->name) == FALSE) continue; if (check_experimental(method->flags, G_DBUS_METHOD_FLAG_EXPERIMENTAL)) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if (check_testing(method->flags, G_DBUS_METHOD_FLAG_TESTING)) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if (g_dbus_args_have_signature(method->in_args, message) == FALSE) continue; if (check_privilege(connection, message, method, iface->user_data) == TRUE) return DBUS_HANDLER_RESULT_HANDLED; return process_message(connection, message, method, iface->user_data); } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static DBusObjectPathVTable generic_table = { .unregister_function = generic_unregister, .message_function = generic_message, }; static const GDBusMethodTable introspect_methods[] = { { GDBUS_METHOD("Introspect", NULL, GDBUS_ARGS({ "xml", "s" }), introspect) }, { } }; static void append_interfaces(struct generic_data *data, DBusMessageIter *iter) { DBusMessageIter array; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &array); g_slist_foreach(data->interfaces, append_interface, &array); dbus_message_iter_close_container(iter, &array); } static void append_object(gpointer data, gpointer user_data) { struct generic_data *child = data; DBusMessageIter *array = user_data; DBusMessageIter entry; dbus_message_iter_open_container(array, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, &child->path); append_interfaces(child, &entry); dbus_message_iter_close_container(array, &entry); g_slist_foreach(child->objects, append_object, user_data); } static DBusMessage *get_objects(DBusConnection *connection, DBusMessage *message, void *user_data) { struct generic_data *data = user_data; DBusMessage *reply; DBusMessageIter iter; DBusMessageIter array; reply = dbus_message_new_method_return(message); if (reply == NULL) return NULL; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_OBJECT_PATH_AS_STRING DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &array); g_slist_foreach(data->objects, append_object, &array); dbus_message_iter_close_container(&iter, &array); return reply; } static const GDBusMethodTable manager_methods[] = { { GDBUS_METHOD("GetManagedObjects", NULL, GDBUS_ARGS({ "objects", "a{oa{sa{sv}}}" }), get_objects) }, { } }; static const GDBusSignalTable manager_signals[] = { { GDBUS_SIGNAL("InterfacesAdded", GDBUS_ARGS({ "object", "o" }, { "interfaces", "a{sa{sv}}" })) }, { GDBUS_SIGNAL("InterfacesRemoved", GDBUS_ARGS({ "object", "o" }, { "interfaces", "as" })) }, { } }; static gboolean add_interface(struct generic_data *data, const char *name, const GDBusMethodTable *methods, const GDBusSignalTable *signals, const GDBusPropertyTable *properties, void *user_data, GDBusDestroyFunction destroy) { struct interface_data *iface; const GDBusMethodTable *method; const GDBusSignalTable *signal; const GDBusPropertyTable *property; for (method = methods; method && method->name; method++) { if (!check_experimental(method->flags, G_DBUS_METHOD_FLAG_EXPERIMENTAL)) goto done; if (!check_testing(method->flags, G_DBUS_METHOD_FLAG_TESTING)) goto done; } for (signal = signals; signal && signal->name; signal++) { if (!check_experimental(signal->flags, G_DBUS_SIGNAL_FLAG_EXPERIMENTAL)) goto done; if (!check_testing(signal->flags, G_DBUS_SIGNAL_FLAG_TESTING)) goto done; } for (property = properties; property && property->name; property++) { if (!check_experimental(property->flags, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL)) goto done; if (!check_testing(property->flags, G_DBUS_PROPERTY_FLAG_TESTING)) goto done; } /* Nothing to register */ return FALSE; done: iface = g_new0(struct interface_data, 1); iface->name = g_strdup(name); iface->methods = methods; iface->signals = signals; iface->properties = properties; iface->user_data = user_data; iface->destroy = destroy; data->interfaces = g_slist_append(data->interfaces, iface); if (data->parent == NULL) return TRUE; data->added = g_slist_append(data->added, iface); add_pending(data); return TRUE; } static struct generic_data *object_path_ref(DBusConnection *connection, const char *path) { struct generic_data *data; if (dbus_connection_get_object_path_data(connection, path, (void *) &data) == TRUE) { if (data != NULL) { data->refcount++; return data; } } data = g_new0(struct generic_data, 1); data->conn = dbus_connection_ref(connection); data->path = g_strdup(path); data->refcount = 1; data->introspect = g_strdup(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE ""); if (!dbus_connection_register_object_path(connection, path, &generic_table, data)) { dbus_connection_unref(data->conn); g_free(data->path); g_free(data->introspect); g_free(data); return NULL; } invalidate_parent_data(connection, path); add_interface(data, DBUS_INTERFACE_INTROSPECTABLE, introspect_methods, NULL, NULL, data, NULL); return data; } static void object_path_unref(DBusConnection *connection, const char *path) { struct generic_data *data = NULL; if (dbus_connection_get_object_path_data(connection, path, (void *) &data) == FALSE) return; if (data == NULL) return; data->refcount--; if (data->refcount > 0) return; remove_interface(data, DBUS_INTERFACE_INTROSPECTABLE); remove_interface(data, DBUS_INTERFACE_PROPERTIES); invalidate_parent_data(data->conn, data->path); dbus_connection_unregister_object_path(data->conn, data->path); } static gboolean check_signal(DBusConnection *conn, const char *path, const char *interface, const char *name, const GDBusArgInfo **args) { struct generic_data *data = NULL; struct interface_data *iface; const GDBusSignalTable *signal; *args = NULL; if (!dbus_connection_get_object_path_data(conn, path, (void *) &data) || data == NULL) { error("dbus_connection_emit_signal: path %s isn't registered", path); return FALSE; } iface = find_interface(data->interfaces, interface); if (iface == NULL) { error("dbus_connection_emit_signal: %s does not implement %s", path, interface); return FALSE; } for (signal = iface->signals; signal && signal->name; signal++) { if (strcmp(signal->name, name) != 0) continue; if (signal->flags & G_DBUS_SIGNAL_FLAG_EXPERIMENTAL) { const char *env = g_getenv("GDBUS_EXPERIMENTAL"); if (g_strcmp0(env, "1") != 0) break; } *args = signal->args; return TRUE; } error("No signal named %s on interface %s", name, interface); return FALSE; } gboolean g_dbus_register_interface(DBusConnection *connection, const char *path, const char *name, const GDBusMethodTable *methods, const GDBusSignalTable *signals, const GDBusPropertyTable *properties, void *user_data, GDBusDestroyFunction destroy) { struct generic_data *data; if (!dbus_validate_path(path, NULL)) { error("Invalid object path: %s", path); return FALSE; } if (!dbus_validate_interface(name, NULL)) { error("Invalid interface: %s", name); return FALSE; } data = object_path_ref(connection, path); if (data == NULL) return FALSE; if (find_interface(data->interfaces, name)) { object_path_unref(connection, path); return FALSE; } if (!add_interface(data, name, methods, signals, properties, user_data, destroy)) { object_path_unref(connection, path); return FALSE; } if (properties != NULL && !find_interface(data->interfaces, DBUS_INTERFACE_PROPERTIES)) add_interface(data, DBUS_INTERFACE_PROPERTIES, properties_methods, properties_signals, NULL, data, NULL); g_free(data->introspect); data->introspect = NULL; return TRUE; } gboolean g_dbus_unregister_interface(DBusConnection *connection, const char *path, const char *name) { struct generic_data *data = NULL; if (path == NULL) return FALSE; if (dbus_connection_get_object_path_data(connection, path, (void *) &data) == FALSE) return FALSE; if (data == NULL) return FALSE; if (remove_interface(data, name) == FALSE) return FALSE; g_free(data->introspect); data->introspect = NULL; object_path_unref(connection, data->path); return TRUE; } gboolean g_dbus_register_security(const GDBusSecurityTable *security) { if (security_table != NULL) return FALSE; security_table = security; return TRUE; } gboolean g_dbus_unregister_security(const GDBusSecurityTable *security) { security_table = NULL; return TRUE; } DBusMessage *g_dbus_create_error_valist(DBusMessage *message, const char *name, const char *format, va_list args) { char str[1024]; /* Check if the message can be replied */ if (dbus_message_get_no_reply(message)) return NULL; if (format) vsnprintf(str, sizeof(str), format, args); else str[0] = '\0'; return dbus_message_new_error(message, name, str); } DBusMessage *g_dbus_create_error(DBusMessage *message, const char *name, const char *format, ...) { va_list args; DBusMessage *reply; va_start(args, format); reply = g_dbus_create_error_valist(message, name, format, args); va_end(args); return reply; } DBusMessage *g_dbus_create_reply_valist(DBusMessage *message, int type, va_list args) { DBusMessage *reply; /* Check if the message can be replied */ if (dbus_message_get_no_reply(message)) return NULL; reply = dbus_message_new_method_return(message); if (reply == NULL) return NULL; if (dbus_message_append_args_valist(reply, type, args) == FALSE) { dbus_message_unref(reply); return NULL; } return reply; } DBusMessage *g_dbus_create_reply(DBusMessage *message, int type, ...) { va_list args; DBusMessage *reply; va_start(args, type); reply = g_dbus_create_reply_valist(message, type, args); va_end(args); return reply; } static void g_dbus_flush(DBusConnection *connection) { GSList *l; for (l = pending; l;) { struct generic_data *data = l->data; l = l->next; if (data->conn != connection) continue; process_changes(data); } } gboolean g_dbus_send_message(DBusConnection *connection, DBusMessage *message) { dbus_bool_t result = FALSE; if (!message) return FALSE; if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL) dbus_message_set_no_reply(message, TRUE); else if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL) { const char *path = dbus_message_get_path(message); const char *interface = dbus_message_get_interface(message); const char *name = dbus_message_get_member(message); const GDBusArgInfo *args; if (!check_signal(connection, path, interface, name, &args)) goto out; } /* Flush pending signal to guarantee message order */ g_dbus_flush(connection); switch (dbus_message_get_type(message)) { case DBUS_MESSAGE_TYPE_METHOD_RETURN: g_dbus_debug("[%s:%s] < [#%d]", dbus_message_get_destination(message), dbus_message_type_string(message), dbus_message_get_reply_serial(message)); break; case DBUS_MESSAGE_TYPE_ERROR: g_dbus_debug("[%s:%s] < %s [#%d]", dbus_message_get_destination(message), dbus_message_type_string(message), dbus_message_get_error_name(message), dbus_message_get_reply_serial(message)); break; case DBUS_MESSAGE_TYPE_SIGNAL: g_dbus_debug("[%s] %s.%s", dbus_message_type_string(message), dbus_message_get_interface(message), dbus_message_get_member(message)); break; default: g_dbus_debug("[%s:%s] < %s.%s", dbus_message_get_destination(message), dbus_message_type_string(message), dbus_message_get_interface(message), dbus_message_get_member(message)); break; } result = dbus_connection_send(connection, message, NULL); out: dbus_message_unref(message); return result; } gboolean g_dbus_send_message_with_reply(DBusConnection *connection, DBusMessage *message, DBusPendingCall **call, int timeout) { dbus_bool_t ret; /* Flush pending signal to guarantee message order */ g_dbus_flush(connection); ret = dbus_connection_send_with_reply(connection, message, call, timeout); if (ret == TRUE && call != NULL && *call == NULL) { error("Unable to send message (passing fd blocked?)"); return FALSE; } g_dbus_debug("[%s:%s] < %s.%s", dbus_message_get_destination(message), dbus_message_type_string(message), dbus_message_get_interface(message), dbus_message_get_member(message)); return ret; } gboolean g_dbus_send_error_valist(DBusConnection *connection, DBusMessage *message, const char *name, const char *format, va_list args) { DBusMessage *error; error = g_dbus_create_error_valist(message, name, format, args); if (error == NULL) return FALSE; return g_dbus_send_message(connection, error); } gboolean g_dbus_send_error(DBusConnection *connection, DBusMessage *message, const char *name, const char *format, ...) { va_list args; gboolean result; va_start(args, format); result = g_dbus_send_error_valist(connection, message, name, format, args); va_end(args); return result; } gboolean g_dbus_send_reply_valist(DBusConnection *connection, DBusMessage *message, int type, va_list args) { DBusMessage *reply; reply = g_dbus_create_reply_valist(message, type, args); if (!reply) return FALSE; return g_dbus_send_message(connection, reply); } gboolean g_dbus_send_reply(DBusConnection *connection, DBusMessage *message, int type, ...) { va_list args; gboolean result; va_start(args, type); result = g_dbus_send_reply_valist(connection, message, type, args); va_end(args); return result; } gboolean g_dbus_emit_signal(DBusConnection *connection, const char *path, const char *interface, const char *name, int type, ...) { va_list args; gboolean result; va_start(args, type); result = g_dbus_emit_signal_valist(connection, path, interface, name, type, args); va_end(args); return result; } gboolean g_dbus_emit_signal_valist(DBusConnection *connection, const char *path, const char *interface, const char *name, int type, va_list args) { DBusMessage *signal; dbus_bool_t ret; const GDBusArgInfo *args_info; if (!check_signal(connection, path, interface, name, &args_info)) return FALSE; signal = dbus_message_new_signal(path, interface, name); if (signal == NULL) { error("Unable to allocate new %s.%s signal", interface, name); return FALSE; } ret = dbus_message_append_args_valist(signal, type, args); if (!ret) goto fail; if (g_dbus_args_have_signature(args_info, signal) == FALSE) { error("%s.%s: got unexpected signature '%s'", interface, name, dbus_message_get_signature(signal)); ret = FALSE; goto fail; } return g_dbus_send_message(connection, signal); fail: dbus_message_unref(signal); return ret; } static void process_properties_from_interface(struct generic_data *data, struct interface_data *iface) { GSList *l; DBusMessage *signal; DBusMessageIter iter, dict, array; GSList *invalidated; if (iface->pending_prop == NULL) return; signal = dbus_message_new_signal(data->path, DBUS_INTERFACE_PROPERTIES, "PropertiesChanged"); if (signal == NULL) { error("Unable to allocate new " DBUS_INTERFACE_PROPERTIES ".PropertiesChanged signal"); return; } iface->pending_prop = g_slist_reverse(iface->pending_prop); dbus_message_iter_init_append(signal, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &iface->name); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); invalidated = NULL; for (l = iface->pending_prop; l != NULL; l = l->next) { GDBusPropertyTable *p = l->data; if (p->get == NULL) continue; if (p->exists != NULL && !p->exists(p, iface->user_data)) { invalidated = g_slist_prepend(invalidated, p); continue; } append_property(iface, p, &dict); } dbus_message_iter_close_container(&iter, &dict); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &array); for (l = invalidated; l != NULL; l = g_slist_next(l)) { GDBusPropertyTable *p = l->data; dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &p->name); } g_slist_free(invalidated); dbus_message_iter_close_container(&iter, &array); g_slist_free(iface->pending_prop); iface->pending_prop = NULL; /* Use g_dbus_send_unref to avoid recursive calls to g_dbus_flush */ g_dbus_send_unref(data->conn, signal); } static void process_property_changes(struct generic_data *data) { GSList *l; data->pending_prop = FALSE; for (l = data->interfaces; l != NULL; l = l->next) { struct interface_data *iface = l->data; process_properties_from_interface(data, iface); } } void g_dbus_emit_property_changed_full(DBusConnection *connection, const char *path, const char *interface, const char *name, GDbusPropertyChangedFlags flags) { const GDBusPropertyTable *property; struct generic_data *data; struct interface_data *iface; if (path == NULL) return; if (!dbus_connection_get_object_path_data(connection, path, (void **) &data) || data == NULL) return; iface = find_interface(data->interfaces, interface); if (iface == NULL) return; /* * If ObjectManager is attached, don't emit property changed if * interface is not yet published */ if (root && g_slist_find(data->added, iface)) return; property = find_property(iface->properties, name); if (property == NULL) { error("Could not find property %s in %p", name, iface->properties); return; } if (g_slist_find(iface->pending_prop, (void *) property) != NULL) return; data->pending_prop = TRUE; iface->pending_prop = g_slist_prepend(iface->pending_prop, (void *) property); if (flags & G_DBUS_PROPERTY_CHANGED_FLAG_FLUSH) process_property_changes(data); else add_pending(data); } void g_dbus_emit_property_changed(DBusConnection *connection, const char *path, const char *interface, const char *name) { g_dbus_emit_property_changed_full(connection, path, interface, name, 0); } gboolean g_dbus_get_properties(DBusConnection *connection, const char *path, const char *interface, DBusMessageIter *iter) { struct generic_data *data; struct interface_data *iface; if (path == NULL) return FALSE; if (!dbus_connection_get_object_path_data(connection, path, (void **) &data) || data == NULL) return FALSE; iface = find_interface(data->interfaces, interface); if (iface == NULL) return FALSE; append_properties(iface, iter); return TRUE; } gboolean g_dbus_attach_object_manager(DBusConnection *connection) { struct generic_data *data; data = object_path_ref(connection, "/"); if (data == NULL) return FALSE; add_interface(data, DBUS_INTERFACE_OBJECT_MANAGER, manager_methods, manager_signals, NULL, data, NULL); root = data; return TRUE; } gboolean g_dbus_detach_object_manager(DBusConnection *connection) { if (!g_dbus_unregister_interface(connection, "/", DBUS_INTERFACE_OBJECT_MANAGER)) return FALSE; root = NULL; return TRUE; } void g_dbus_set_flags(int flags) { global_flags = flags; } int g_dbus_get_flags(void) { return global_flags; } void g_dbus_set_debug(g_dbus_debug_func_t cb, void *user_data, g_dbus_destroy_func_t destroy) { if (debug.destroy) debug.destroy(debug.data); debug.func = cb; debug.destroy = destroy; debug.data = user_data; } bluez-5.87/gdbus/PaxHeaders/mainloop.c0000644000000000000000000000005014015011623014732 xustar0020 atime=1783092864 20 ctime=1783094099 bluez-5.87/gdbus/mainloop.c0000644000000000000000000001655014015011623014422 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * D-Bus helper library * * Copyright (C) 2004-2011 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "gdbus.h" #define info(fmt...) #define error(fmt...) #define debug(fmt...) struct timeout_handler { guint id; DBusTimeout *timeout; }; struct watch_info { guint id; DBusWatch *watch; DBusConnection *conn; }; struct disconnect_data { GDBusWatchFunction function; void *user_data; }; static gboolean disconnected_signal(DBusConnection *conn, DBusMessage *msg, void *data) { struct disconnect_data *dc_data = data; error("Got disconnected from the system message bus"); dc_data->function(conn, dc_data->user_data); dbus_connection_unref(conn); return TRUE; } static gboolean message_dispatch(void *data) { DBusConnection *conn = data; /* Dispatch messages */ while (dbus_connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS); dbus_connection_unref(conn); return FALSE; } static inline void queue_dispatch(DBusConnection *conn, DBusDispatchStatus status) { if (status == DBUS_DISPATCH_DATA_REMAINS) g_idle_add(message_dispatch, dbus_connection_ref(conn)); } static gboolean watch_func(GIOChannel *chan, GIOCondition cond, gpointer data) { struct watch_info *info = data; unsigned int flags = 0; DBusDispatchStatus status; DBusConnection *conn; if (cond & G_IO_IN) flags |= DBUS_WATCH_READABLE; if (cond & G_IO_OUT) flags |= DBUS_WATCH_WRITABLE; if (cond & G_IO_HUP) flags |= DBUS_WATCH_HANGUP; if (cond & G_IO_ERR) flags |= DBUS_WATCH_ERROR; /* Protect connection from being destroyed by dbus_watch_handle */ conn = dbus_connection_ref(info->conn); dbus_watch_handle(info->watch, flags); status = dbus_connection_get_dispatch_status(conn); queue_dispatch(conn, status); dbus_connection_unref(conn); return TRUE; } static void watch_info_free(void *data) { struct watch_info *info = data; if (info->id > 0) { g_source_remove(info->id); info->id = 0; } dbus_connection_unref(info->conn); g_free(info); } static dbus_bool_t add_watch(DBusWatch *watch, void *data) { DBusConnection *conn = data; GIOCondition cond = G_IO_HUP | G_IO_ERR; GIOChannel *chan; struct watch_info *info; unsigned int flags; int fd; if (!dbus_watch_get_enabled(watch)) return TRUE; info = g_new0(struct watch_info, 1); fd = dbus_watch_get_unix_fd(watch); chan = g_io_channel_unix_new(fd); info->watch = watch; info->conn = dbus_connection_ref(conn); dbus_watch_set_data(watch, info, watch_info_free); flags = dbus_watch_get_flags(watch); if (flags & DBUS_WATCH_READABLE) cond |= G_IO_IN; if (flags & DBUS_WATCH_WRITABLE) cond |= G_IO_OUT; info->id = g_io_add_watch(chan, cond, watch_func, info); g_io_channel_unref(chan); return TRUE; } static void remove_watch(DBusWatch *watch, void *data) { if (dbus_watch_get_enabled(watch)) return; /* will trigger watch_info_free() */ dbus_watch_set_data(watch, NULL, NULL); } static void watch_toggled(DBusWatch *watch, void *data) { /* Because we just exit on OOM, enable/disable is * no different from add/remove */ if (dbus_watch_get_enabled(watch)) add_watch(watch, data); else remove_watch(watch, data); } static gboolean timeout_handler_dispatch(gpointer data) { struct timeout_handler *handler = data; handler->id = 0; /* if not enabled should not be polled by the main loop */ if (!dbus_timeout_get_enabled(handler->timeout)) return FALSE; dbus_timeout_handle(handler->timeout); return FALSE; } static void timeout_handler_free(void *data) { struct timeout_handler *handler = data; if (handler->id > 0) { g_source_remove(handler->id); handler->id = 0; } g_free(handler); } static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) { int interval = dbus_timeout_get_interval(timeout); struct timeout_handler *handler; if (!dbus_timeout_get_enabled(timeout)) return TRUE; handler = g_new0(struct timeout_handler, 1); handler->timeout = timeout; dbus_timeout_set_data(timeout, handler, timeout_handler_free); handler->id = g_timeout_add(interval, timeout_handler_dispatch, handler); return TRUE; } static void remove_timeout(DBusTimeout *timeout, void *data) { /* will trigger timeout_handler_free() */ dbus_timeout_set_data(timeout, NULL, NULL); } static void timeout_toggled(DBusTimeout *timeout, void *data) { if (dbus_timeout_get_enabled(timeout)) add_timeout(timeout, data); else remove_timeout(timeout, data); } static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *data) { if (!dbus_connection_get_is_connected(conn)) return; queue_dispatch(conn, status); } static inline void setup_dbus_with_main_loop(DBusConnection *conn) { dbus_connection_set_watch_functions(conn, add_watch, remove_watch, watch_toggled, conn, NULL); dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, timeout_toggled, NULL, NULL); dbus_connection_set_dispatch_status_function(conn, dispatch_status, NULL, NULL); } static gboolean setup_bus(DBusConnection *conn, const char *name, DBusError *error) { gboolean result; DBusDispatchStatus status; if (name != NULL) { result = g_dbus_request_name(conn, name, error); if (error != NULL) { if (dbus_error_is_set(error) == TRUE) return FALSE; } if (result == FALSE) return FALSE; } setup_dbus_with_main_loop(conn); status = dbus_connection_get_dispatch_status(conn); queue_dispatch(conn, status); return TRUE; } DBusConnection *g_dbus_setup_bus(DBusBusType type, const char *name, DBusError *error) { DBusConnection *conn; conn = dbus_bus_get(type, error); if (error != NULL) { if (dbus_error_is_set(error) == TRUE) return NULL; } if (conn == NULL) return NULL; if (setup_bus(conn, name, error) == FALSE) { dbus_connection_unref(conn); return NULL; } return conn; } DBusConnection *g_dbus_setup_private(DBusBusType type, const char *name, DBusError *error) { DBusConnection *conn; conn = dbus_bus_get_private(type, error); if (error != NULL) { if (dbus_error_is_set(error) == TRUE) return NULL; } if (conn == NULL) return NULL; if (setup_bus(conn, name, error) == FALSE) { dbus_connection_close(conn); dbus_connection_unref(conn); return NULL; } return conn; } gboolean g_dbus_request_name(DBusConnection *connection, const char *name, DBusError *error) { int result; result = dbus_bus_request_name(connection, name, DBUS_NAME_FLAG_DO_NOT_QUEUE, error); if (error != NULL) { if (dbus_error_is_set(error) == TRUE) return FALSE; } if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { if (error != NULL) dbus_set_error(error, name, "Name already in use"); return FALSE; } return TRUE; } gboolean g_dbus_set_disconnect_function(DBusConnection *connection, GDBusWatchFunction function, void *user_data, DBusFreeFunction destroy) { struct disconnect_data *dc_data; dc_data = g_new0(struct disconnect_data, 1); dc_data->function = function; dc_data->user_data = user_data; dbus_connection_set_exit_on_disconnect(connection, FALSE); if (g_dbus_add_signal_watch(connection, NULL, NULL, DBUS_INTERFACE_LOCAL, "Disconnected", disconnected_signal, dc_data, g_free) == 0) { error("Failed to add watch for D-Bus Disconnected signal"); g_free(dc_data); return FALSE; } return TRUE; } bluez-5.87/gdbus/PaxHeaders/watch.c0000644000000000000000000000005014621503015014226 xustar0020 atime=1783092865 20 ctime=1783094099 bluez-5.87/gdbus/watch.c0000644000000000000000000004347314621503015013722 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * D-Bus helper library * * Copyright (C) 2004-2011 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "gdbus.h" #define info(fmt...) #define error(fmt...) #define debug(fmt...) static DBusHandlerResult message_filter(DBusConnection *connection, DBusMessage *message, void *user_data); static guint listener_id = 0; static GSList *listeners = NULL; struct service_data { DBusConnection *conn; DBusPendingCall *call; char *name; const char *owner; guint id; struct filter_callback *callback; }; struct filter_callback { GDBusWatchFunction conn_func; GDBusWatchFunction disc_func; GDBusSignalFunction signal_func; GDBusDestroyFunction destroy_func; struct service_data *data; void *user_data; guint id; }; struct filter_data { DBusConnection *connection; DBusHandleMessageFunction handle_func; char *name; char *owner; char *path; char *interface; char *member; char *argument; GSList *callbacks; GSList *processed; guint name_watch; gboolean lock; gboolean registered; }; static struct filter_data *filter_data_find_match(DBusConnection *connection, const char *name, const char *owner, const char *path, const char *interface, const char *member, const char *argument) { GSList *current; for (current = listeners; current != NULL; current = current->next) { struct filter_data *data = current->data; if (connection != data->connection) continue; if (g_strcmp0(name, data->name) != 0) continue; if (g_strcmp0(owner, data->owner) != 0) continue; if (g_strcmp0(path, data->path) != 0) continue; if (g_strcmp0(interface, data->interface) != 0) continue; if (g_strcmp0(member, data->member) != 0) continue; if (g_strcmp0(argument, data->argument) != 0) continue; return data; } return NULL; } static struct filter_data *filter_data_find(DBusConnection *connection) { GSList *current; for (current = listeners; current != NULL; current = current->next) { struct filter_data *data = current->data; if (connection != data->connection) continue; return data; } return NULL; } static gboolean format_rule(struct filter_data *data, char *rule, size_t size) { const char *sender; int offset, ret; offset = snprintf(rule, size, "type='signal'"); if (offset < 0) return FALSE; sender = data->name ? : data->owner; if (sender) { ret = snprintf(rule + offset, size - offset, ",sender='%s'", sender); if (ret < 0) return FALSE; offset += ret; } if (data->path) { ret = snprintf(rule + offset, size - offset, ",path='%s'", data->path); if (ret < 0) return FALSE; offset += ret; } if (data->interface) { ret = snprintf(rule + offset, size - offset, ",interface='%s'", data->interface); if (ret < 0) return FALSE; offset += ret; } if (data->member) { ret = snprintf(rule + offset, size - offset, ",member='%s'", data->member); if (ret < 0) return FALSE; offset += ret; } if (data->argument) { ret = snprintf(rule + offset, size - offset, ",arg0='%s'", data->argument); if (ret < 0) return FALSE; } return TRUE; } static gboolean add_match(struct filter_data *data, DBusHandleMessageFunction filter) { DBusError err; char rule[DBUS_MAXIMUM_MATCH_RULE_LENGTH]; format_rule(data, rule, sizeof(rule)); dbus_error_init(&err); dbus_bus_add_match(data->connection, rule, &err); if (dbus_error_is_set(&err)) { error("Adding match rule \"%s\" failed: %s", rule, err.message); dbus_error_free(&err); return FALSE; } data->handle_func = filter; data->registered = TRUE; return TRUE; } static gboolean remove_match(struct filter_data *data) { DBusError err; char rule[DBUS_MAXIMUM_MATCH_RULE_LENGTH]; format_rule(data, rule, sizeof(rule)); dbus_error_init(&err); dbus_bus_remove_match(data->connection, rule, &err); if (dbus_error_is_set(&err)) { error("Removing owner match rule for %s failed: %s", rule, err.message); dbus_error_free(&err); return FALSE; } return TRUE; } static void filter_data_free(struct filter_data *data) { GSList *l; /* Remove filter if there are no listeners left for the connection */ if (filter_data_find(data->connection) == NULL) dbus_connection_remove_filter(data->connection, message_filter, NULL); for (l = data->callbacks; l != NULL; l = l->next) g_free(l->data); g_slist_free(data->callbacks); g_dbus_remove_watch(data->connection, data->name_watch); g_free(data->name); g_free(data->owner); g_free(data->path); g_free(data->interface); g_free(data->member); g_free(data->argument); dbus_connection_unref(data->connection); g_free(data); } static struct filter_data *filter_data_get(DBusConnection *connection, DBusHandleMessageFunction filter, const char *sender, const char *path, const char *interface, const char *member, const char *argument) { struct filter_data *data; const char *name = NULL, *owner = NULL; if (filter_data_find(connection) == NULL) { if (!dbus_connection_add_filter(connection, message_filter, NULL, NULL)) { error("dbus_connection_add_filter() failed"); return NULL; } } if (sender == NULL) goto proceed; if (sender[0] == ':') owner = sender; else name = sender; proceed: data = filter_data_find_match(connection, name, owner, path, interface, member, argument); if (data) return data; data = g_new0(struct filter_data, 1); data->connection = dbus_connection_ref(connection); data->name = g_strdup(name); data->owner = g_strdup(owner); data->path = g_strdup(path); data->interface = g_strdup(interface); data->member = g_strdup(member); data->argument = g_strdup(argument); if (!add_match(data, filter)) { filter_data_free(data); return NULL; } listeners = g_slist_append(listeners, data); return data; } static struct filter_callback *filter_data_find_callback( struct filter_data *data, guint id) { GSList *l; for (l = data->callbacks; l; l = l->next) { struct filter_callback *cb = l->data; if (cb->id == id) return cb; } for (l = data->processed; l; l = l->next) { struct filter_callback *cb = l->data; if (cb->id == id) return cb; } return NULL; } static void filter_data_call_and_free(struct filter_data *data) { GSList *l; for (l = data->callbacks; l != NULL; l = l->next) { struct filter_callback *cb = l->data; if (cb->disc_func) cb->disc_func(data->connection, cb->user_data); if (cb->destroy_func) cb->destroy_func(cb->user_data); g_free(cb); } filter_data_free(data); } static struct filter_callback *filter_data_add_callback( struct filter_data *data, GDBusWatchFunction connect, GDBusWatchFunction disconnect, GDBusSignalFunction signal, GDBusDestroyFunction destroy, void *user_data) { struct filter_callback *cb = NULL; cb = g_new0(struct filter_callback, 1); cb->conn_func = connect; cb->disc_func = disconnect; cb->signal_func = signal; cb->destroy_func = destroy; cb->user_data = user_data; cb->id = ++listener_id; if (data->lock) data->processed = g_slist_append(data->processed, cb); else data->callbacks = g_slist_append(data->callbacks, cb); return cb; } static void service_data_free(struct service_data *data) { struct filter_callback *callback = data->callback; dbus_connection_unref(data->conn); if (data->call) dbus_pending_call_unref(data->call); if (data->id) g_source_remove(data->id); g_free(data->name); g_free(data); callback->data = NULL; } /* Returns TRUE if data is freed */ static gboolean filter_data_remove_callback(struct filter_data *data, struct filter_callback *cb) { data->callbacks = g_slist_remove(data->callbacks, cb); data->processed = g_slist_remove(data->processed, cb); /* Cancel pending operations */ if (cb->data) { if (cb->data->call) dbus_pending_call_cancel(cb->data->call); service_data_free(cb->data); } if (cb->destroy_func) cb->destroy_func(cb->user_data); g_free(cb); /* Don't remove the filter if other callbacks exist or data is lock * processing callbacks */ if (data->callbacks || data->lock) return FALSE; if (data->registered && !remove_match(data)) return FALSE; listeners = g_slist_remove(listeners, data); filter_data_free(data); return TRUE; } static DBusHandlerResult signal_filter(DBusConnection *connection, DBusMessage *message, void *user_data) { struct filter_data *data = user_data; struct filter_callback *cb; while (data->callbacks) { cb = data->callbacks->data; if (cb->signal_func && !cb->signal_func(connection, message, cb->user_data)) { if (filter_data_remove_callback(data, cb)) break; continue; } /* Check if the watch was removed/freed by the callback * function */ if (!g_slist_find(data->callbacks, cb)) continue; data->callbacks = g_slist_remove(data->callbacks, cb); data->processed = g_slist_append(data->processed, cb); } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static void update_name_cache(const char *name, const char *owner) { GSList *l; for (l = listeners; l != NULL; l = l->next) { struct filter_data *data = l->data; if (g_strcmp0(data->name, name) != 0) continue; g_free(data->owner); data->owner = g_strdup(owner); } } static const char *check_name_cache(const char *name) { GSList *l; for (l = listeners; l != NULL; l = l->next) { struct filter_data *data = l->data; if (g_strcmp0(data->name, name) != 0) continue; return data->owner; } return NULL; } static DBusHandlerResult service_filter(DBusConnection *connection, DBusMessage *message, void *user_data) { struct filter_data *data = user_data; struct filter_callback *cb; char *name, *old, *new; if (!dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old, DBUS_TYPE_STRING, &new, DBUS_TYPE_INVALID)) { error("Invalid arguments for NameOwnerChanged signal"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } update_name_cache(name, new); while (data->callbacks) { cb = data->callbacks->data; if (*new == '\0') { if (cb->disc_func) cb->disc_func(connection, cb->user_data); } else { if (cb->conn_func) cb->conn_func(connection, cb->user_data); } /* Check if the watch was removed/freed by the callback * function */ if (!g_slist_find(data->callbacks, cb)) continue; /* Only auto remove if it is a bus name watch */ if (data->argument[0] == ':' && (cb->conn_func == NULL || cb->disc_func == NULL)) { if (filter_data_remove_callback(data, cb)) break; continue; } data->callbacks = g_slist_remove(data->callbacks, cb); data->processed = g_slist_append(data->processed, cb); } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static DBusHandlerResult message_filter(DBusConnection *connection, DBusMessage *message, void *user_data) { struct filter_data *data; const char *sender, *path, *iface, *member, *arg = NULL; GSList *current, *delete_listener = NULL; /* Only filter signals */ if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; sender = dbus_message_get_sender(message); path = dbus_message_get_path(message); iface = dbus_message_get_interface(message); member = dbus_message_get_member(message); dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &arg, DBUS_TYPE_INVALID); /* If sender != NULL it is always the owner */ for (current = listeners; current != NULL; current = current->next) { data = current->data; if (connection != data->connection) continue; if (!sender && data->owner) continue; if (data->owner && g_str_equal(sender, data->owner) == FALSE) continue; if (data->path && g_str_equal(path, data->path) == FALSE) continue; if (data->interface && g_str_equal(iface, data->interface) == FALSE) continue; if (data->member && g_str_equal(member, data->member) == FALSE) continue; if (data->argument && g_str_equal(arg, data->argument) == FALSE) continue; if (data->handle_func) { data->lock = TRUE; data->handle_func(connection, message, data); data->callbacks = data->processed; data->processed = NULL; data->lock = FALSE; } if (!data->callbacks) delete_listener = g_slist_prepend(delete_listener, current); } if (delete_listener == NULL) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; for (current = delete_listener; current != NULL; current = delete_listener->next) { GSList *l = current->data; data = l->data; /* Has any other callback added callbacks back to this data? */ if (data->callbacks != NULL) continue; remove_match(data); listeners = g_slist_delete_link(listeners, l); filter_data_free(data); } g_slist_free(delete_listener); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static gboolean update_service(void *user_data) { struct service_data *data = user_data; struct filter_callback *cb = data->callback; DBusConnection *conn; conn = dbus_connection_ref(data->conn); service_data_free(data); if (cb->conn_func) cb->conn_func(conn, cb->user_data); dbus_connection_unref(conn); return FALSE; } static void service_reply(DBusPendingCall *call, void *user_data) { struct service_data *data = user_data; DBusMessage *reply; DBusError err; reply = dbus_pending_call_steal_reply(call); if (reply == NULL) return; dbus_error_init(&err); if (dbus_set_error_from_message(&err, reply)) goto fail; if (dbus_message_get_args(reply, &err, DBUS_TYPE_STRING, &data->owner, DBUS_TYPE_INVALID) == FALSE) goto fail; update_service(data); goto done; fail: error("%s", err.message); dbus_error_free(&err); service_data_free(data); done: dbus_message_unref(reply); } static void check_service(DBusConnection *connection, const char *name, struct filter_callback *callback) { DBusMessage *message; struct service_data *data; data = g_try_malloc0(sizeof(*data)); if (data == NULL) { error("Can't allocate data structure"); return; } data->conn = dbus_connection_ref(connection); data->name = g_strdup(name); data->callback = callback; callback->data = data; data->owner = check_name_cache(name); if (data->owner != NULL) { data->id = g_idle_add(update_service, data); return; } message = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetNameOwner"); if (message == NULL) { error("Can't allocate new message"); g_free(data); return; } dbus_message_append_args(message, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID); if (dbus_connection_send_with_reply(connection, message, &data->call, -1) == FALSE) { error("Failed to execute method call"); g_free(data); goto done; } if (data->call == NULL) { error("D-Bus connection not available"); g_free(data); goto done; } dbus_pending_call_set_notify(data->call, service_reply, data, NULL); done: dbus_message_unref(message); } guint g_dbus_add_service_watch(DBusConnection *connection, const char *name, GDBusWatchFunction connect, GDBusWatchFunction disconnect, void *user_data, GDBusDestroyFunction destroy) { struct filter_data *data; struct filter_callback *cb; if (name == NULL) return 0; data = filter_data_get(connection, service_filter, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "NameOwnerChanged", name); if (data == NULL) return 0; cb = filter_data_add_callback(data, connect, disconnect, NULL, destroy, user_data); if (cb == NULL) return 0; if (connect) check_service(connection, name, cb); return cb->id; } guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name, GDBusWatchFunction func, void *user_data, GDBusDestroyFunction destroy) { return g_dbus_add_service_watch(connection, name, NULL, func, user_data, destroy); } guint g_dbus_add_signal_watch(DBusConnection *connection, const char *sender, const char *path, const char *interface, const char *member, GDBusSignalFunction function, void *user_data, GDBusDestroyFunction destroy) { struct filter_data *data; struct filter_callback *cb; data = filter_data_get(connection, signal_filter, sender, path, interface, member, NULL); if (data == NULL) return 0; cb = filter_data_add_callback(data, NULL, NULL, function, destroy, user_data); if (cb == NULL) return 0; if (data->name != NULL && data->name_watch == 0) data->name_watch = g_dbus_add_service_watch(connection, data->name, NULL, NULL, NULL, NULL); return cb->id; } guint g_dbus_add_properties_watch(DBusConnection *connection, const char *sender, const char *path, const char *interface, GDBusSignalFunction function, void *user_data, GDBusDestroyFunction destroy) { struct filter_data *data; struct filter_callback *cb; data = filter_data_get(connection, signal_filter, sender, path, DBUS_INTERFACE_PROPERTIES, "PropertiesChanged", interface); if (data == NULL) return 0; cb = filter_data_add_callback(data, NULL, NULL, function, destroy, user_data); if (cb == NULL) return 0; if (data->name != NULL && data->name_watch == 0) data->name_watch = g_dbus_add_service_watch(connection, data->name, NULL, NULL, NULL, NULL); return cb->id; } gboolean g_dbus_remove_watch(DBusConnection *connection, guint id) { struct filter_data *data; struct filter_callback *cb; GSList *ldata; if (id == 0) return FALSE; for (ldata = listeners; ldata; ldata = ldata->next) { data = ldata->data; cb = filter_data_find_callback(data, id); if (cb) { filter_data_remove_callback(data, cb); return TRUE; } } return FALSE; } void g_dbus_remove_all_watches(DBusConnection *connection) { struct filter_data *data; while ((data = filter_data_find(connection))) { listeners = g_slist_remove(listeners, data); filter_data_call_and_free(data); } } bluez-5.87/gdbus/PaxHeaders/client.c0000644000000000000000000000005014711225433014403 xustar0020 atime=1783092871 20 ctime=1783094099 bluez-5.87/gdbus/client.c0000644000000000000000000011074714711225433014076 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * D-Bus helper library * * Copyright (C) 2004-2011 Marcel Holtmann * Copyright 2024 NXP * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include "gdbus.h" #define METHOD_CALL_TIMEOUT (300 * 1000) #ifndef DBUS_INTERFACE_OBJECT_MANAGER #define DBUS_INTERFACE_OBJECT_MANAGER DBUS_INTERFACE_DBUS ".ObjectManager" #endif struct GDBusClient { int ref_count; DBusConnection *dbus_conn; char *service_name; char *base_path; char *root_path; guint watch; guint added_watch; guint removed_watch; GPtrArray *match_rules; DBusPendingCall *pending_call; DBusPendingCall *get_objects_call; GDBusWatchFunction connect_func; void *connect_data; GDBusWatchFunction disconn_func; gboolean connected; void *disconn_data; GDBusMessageFunction signal_func; void *signal_data; GDBusProxyFunction proxy_added; GDBusProxyFunction proxy_removed; GDBusClientFunction ready; void *ready_data; GDBusPropertyFunction property_changed; void *user_data; GList *proxy_list; }; struct GDBusProxy { int ref_count; GDBusClient *client; char *obj_path; char *interface; GHashTable *prop_list; guint watch; GDBusPropertyFunction prop_func; void *prop_data; GDBusProxyFunction removed_func; void *removed_data; DBusPendingCall *get_all_call; gboolean pending; }; struct prop_entry { char *name; int type; DBusMessage *msg; }; static void modify_match_reply(DBusPendingCall *call, void *user_data) { DBusMessage *reply = dbus_pending_call_steal_reply(call); DBusError error; dbus_error_init(&error); if (dbus_set_error_from_message(&error, reply) == TRUE) dbus_error_free(&error); dbus_message_unref(reply); } static gboolean modify_match(DBusConnection *conn, const char *member, const char *rule) { DBusMessage *msg; DBusPendingCall *call; msg = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, member); if (msg == NULL) return FALSE; dbus_message_append_args(msg, DBUS_TYPE_STRING, &rule, DBUS_TYPE_INVALID); if (g_dbus_send_message_with_reply(conn, msg, &call, -1) == FALSE) { dbus_message_unref(msg); return FALSE; } dbus_pending_call_set_notify(call, modify_match_reply, NULL, NULL); dbus_pending_call_unref(call); dbus_message_unref(msg); return TRUE; } static void append_variant(DBusMessageIter *iter, int type, const void *val) { DBusMessageIter value; char sig[2] = { type, '\0' }; dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, sig, &value); dbus_message_iter_append_basic(&value, type, val); dbus_message_iter_close_container(iter, &value); } static void append_array_variant(DBusMessageIter *iter, int type, void *val, int n_elements) { DBusMessageIter variant, array; char type_sig[2] = { type, '\0' }; char array_sig[3] = { DBUS_TYPE_ARRAY, type, '\0' }; dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, array_sig, &variant); dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, type_sig, &array); if (dbus_type_is_fixed(type) == TRUE) { dbus_message_iter_append_fixed_array(&array, type, val, n_elements); } else if (type == DBUS_TYPE_STRING || type == DBUS_TYPE_OBJECT_PATH) { const char ***str_array = val; int i; for (i = 0; i < n_elements; i++) dbus_message_iter_append_basic(&array, type, &((*str_array)[i])); } dbus_message_iter_close_container(&variant, &array); dbus_message_iter_close_container(iter, &variant); } static void dict_append_basic(DBusMessageIter *dict, int key_type, const void *key, int type, void *val) { DBusMessageIter entry; if (type == DBUS_TYPE_STRING) { const char *str = *((const char **) val); if (str == NULL) return; } dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, key_type, key); append_variant(&entry, type, val); dbus_message_iter_close_container(dict, &entry); } void g_dbus_dict_append_entry(DBusMessageIter *dict, const char *key, int type, void *val) { dict_append_basic(dict, DBUS_TYPE_STRING, &key, type, val); } void g_dbus_dict_append_basic_array(DBusMessageIter *dict, int key_type, const void *key, int type, void *val, int n_elements) { DBusMessageIter entry; dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, key_type, key); append_array_variant(&entry, type, val, n_elements); dbus_message_iter_close_container(dict, &entry); } void g_dbus_dict_append_array(DBusMessageIter *dict, const char *key, int type, void *val, int n_elements) { g_dbus_dict_append_basic_array(dict, DBUS_TYPE_STRING, &key, type, val, n_elements); } static void append_dict_variant(DBusMessageIter *iter, char *entry, int type, void *val, ...) { DBusMessageIter variant, dict; va_list args; int array_type, size; dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &variant); dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); va_start(args, val); if (type == DBUS_TYPE_ARRAY) { array_type = va_arg(args, int); size = va_arg(args, int); g_dbus_dict_append_array(&dict, entry, array_type, val, size); } else g_dbus_dict_append_entry(&dict, entry, type, val); va_end(args); dbus_message_iter_close_container(&variant, &dict); dbus_message_iter_close_container(iter, &variant); } static void iter_append_iter(DBusMessageIter *base, DBusMessageIter *iter) { int type; type = dbus_message_iter_get_arg_type(iter); if (dbus_type_is_basic(type)) { const void *value; dbus_message_iter_get_basic(iter, &value); dbus_message_iter_append_basic(base, type, &value); } else if (dbus_type_is_container(type)) { DBusMessageIter iter_sub, base_sub; char *sig; dbus_message_iter_recurse(iter, &iter_sub); switch (type) { case DBUS_TYPE_ARRAY: case DBUS_TYPE_VARIANT: sig = dbus_message_iter_get_signature(&iter_sub); break; default: sig = NULL; break; } dbus_message_iter_open_container(base, type, sig, &base_sub); if (sig != NULL) dbus_free(sig); while (dbus_message_iter_get_arg_type(&iter_sub) != DBUS_TYPE_INVALID) { iter_append_iter(&base_sub, &iter_sub); dbus_message_iter_next(&iter_sub); } dbus_message_iter_close_container(base, &base_sub); } } static void prop_entry_update(struct prop_entry *prop, DBusMessageIter *iter) { DBusMessage *msg; DBusMessageIter base; msg = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_RETURN); if (msg == NULL) return; dbus_message_iter_init_append(msg, &base); iter_append_iter(&base, iter); if (prop->msg != NULL) dbus_message_unref(prop->msg); prop->msg = dbus_message_copy(msg); dbus_message_unref(msg); } static struct prop_entry *prop_entry_new(const char *name, DBusMessageIter *iter) { struct prop_entry *prop; prop = g_try_new0(struct prop_entry, 1); if (prop == NULL) return NULL; prop->name = g_strdup(name); prop->type = dbus_message_iter_get_arg_type(iter); prop_entry_update(prop, iter); return prop; } static void prop_entry_free(gpointer data) { struct prop_entry *prop = data; if (prop->msg != NULL) dbus_message_unref(prop->msg); g_free(prop->name); g_free(prop); } static void add_property(GDBusProxy *proxy, const char *name, DBusMessageIter *iter, gboolean send_changed) { GDBusClient *client = proxy->client; DBusMessageIter value; struct prop_entry *prop; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT) return; dbus_message_iter_recurse(iter, &value); prop = g_hash_table_lookup(proxy->prop_list, name); if (prop != NULL) { prop_entry_update(prop, &value); goto done; } prop = prop_entry_new(name, &value); if (prop == NULL) return; g_hash_table_replace(proxy->prop_list, prop->name, prop); done: if (proxy->prop_func) proxy->prop_func(proxy, name, &value, proxy->prop_data); if (client == NULL || send_changed == FALSE) return; if (client->property_changed) client->property_changed(proxy, name, &value, client->user_data); } static void update_properties(GDBusProxy *proxy, DBusMessageIter *iter, gboolean send_changed) { DBusMessageIter dict; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return; dbus_message_iter_recurse(iter, &dict); while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter entry; const char *name; dbus_message_iter_recurse(&dict, &entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) break; dbus_message_iter_get_basic(&entry, &name); dbus_message_iter_next(&entry); add_property(proxy, name, &entry, send_changed); dbus_message_iter_next(&dict); } } static void proxy_added(GDBusClient *client, GDBusProxy *proxy) { if (!proxy->pending) return; if (client->proxy_added) client->proxy_added(proxy, client->user_data); proxy->pending = FALSE; } static void get_all_properties_reply(DBusPendingCall *call, void *user_data) { GDBusProxy *proxy = user_data; GDBusClient *client = proxy->client; DBusMessage *reply = dbus_pending_call_steal_reply(call); DBusMessageIter iter; DBusError error; g_dbus_client_ref(client); dbus_error_init(&error); if (dbus_set_error_from_message(&error, reply) == TRUE) { dbus_error_free(&error); goto done; } dbus_message_iter_init(reply, &iter); update_properties(proxy, &iter, FALSE); done: proxy_added(client, proxy); dbus_message_unref(reply); dbus_pending_call_unref(proxy->get_all_call); proxy->get_all_call = NULL; g_dbus_client_unref(client); } static void get_all_properties(GDBusProxy *proxy) { GDBusClient *client = proxy->client; const char *service_name = client->service_name; DBusMessage *msg; if (proxy->get_all_call) return; msg = dbus_message_new_method_call(service_name, proxy->obj_path, DBUS_INTERFACE_PROPERTIES, "GetAll"); if (msg == NULL) return; dbus_message_append_args(msg, DBUS_TYPE_STRING, &proxy->interface, DBUS_TYPE_INVALID); if (g_dbus_send_message_with_reply(client->dbus_conn, msg, &proxy->get_all_call, -1) == FALSE) { dbus_message_unref(msg); return; } dbus_pending_call_set_notify(proxy->get_all_call, get_all_properties_reply, proxy, NULL); dbus_message_unref(msg); } GDBusProxy *g_dbus_proxy_lookup(GList *list, int *index, const char *path, const char *interface) { GList *l; if (!interface) return NULL; for (l = g_list_nth(list, index ? *index : 0); l; l = g_list_next(l)) { GDBusProxy *proxy = l->data; const char *proxy_iface = g_dbus_proxy_get_interface(proxy); const char *proxy_path = g_dbus_proxy_get_path(proxy); if (index) (*index)++; if (g_str_equal(proxy_iface, interface) == TRUE && g_str_equal(proxy_path, path) == TRUE) return proxy; } return NULL; } char *g_dbus_proxy_path_lookup(GList *list, int *index, const char *path) { int len = strlen(path); GList *l; for (l = g_list_nth(list, index ? *index : 0); l; l = g_list_next(l)) { GDBusProxy *proxy = l->data; const char *proxy_path = g_dbus_proxy_get_path(proxy); if (index) (*index)++; if (!strncasecmp(proxy_path, path, len)) return strdup(proxy_path); } return NULL; } static gboolean properties_changed(DBusConnection *conn, DBusMessage *msg, void *user_data) { GDBusProxy *proxy = user_data; GDBusClient *client = proxy->client; DBusMessageIter iter, entry; const char *interface; if (dbus_message_iter_init(msg, &iter) == FALSE) return TRUE; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) return TRUE; dbus_message_iter_get_basic(&iter, &interface); dbus_message_iter_next(&iter); update_properties(proxy, &iter, TRUE); dbus_message_iter_next(&iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) return TRUE; dbus_message_iter_recurse(&iter, &entry); while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRING) { const char *name; dbus_message_iter_get_basic(&entry, &name); g_hash_table_remove(proxy->prop_list, name); if (proxy->prop_func) proxy->prop_func(proxy, name, NULL, proxy->prop_data); if (client->property_changed) client->property_changed(proxy, name, NULL, client->user_data); dbus_message_iter_next(&entry); } return TRUE; } static GDBusProxy *proxy_new(GDBusClient *client, const char *path, const char *interface) { GDBusProxy *proxy; proxy = g_try_new0(GDBusProxy, 1); if (proxy == NULL) return NULL; proxy->client = client; proxy->obj_path = g_strdup(path); proxy->interface = g_strdup(interface); proxy->prop_list = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, prop_entry_free); proxy->watch = g_dbus_add_properties_watch(client->dbus_conn, client->service_name, proxy->obj_path, proxy->interface, properties_changed, proxy, NULL); proxy->pending = TRUE; client->proxy_list = g_list_append(client->proxy_list, proxy); return g_dbus_proxy_ref(proxy); } static void proxy_free(gpointer data) { GDBusProxy *proxy = data; if (proxy->client) { GDBusClient *client = proxy->client; if (proxy->get_all_call != NULL) { dbus_pending_call_cancel(proxy->get_all_call); dbus_pending_call_unref(proxy->get_all_call); proxy->get_all_call = NULL; } if (client->proxy_removed) client->proxy_removed(proxy, client->user_data); g_dbus_remove_watch(client->dbus_conn, proxy->watch); g_hash_table_remove_all(proxy->prop_list); proxy->client = NULL; } if (proxy->removed_func) proxy->removed_func(proxy, proxy->removed_data); g_dbus_proxy_unref(proxy); } static void proxy_remove(GDBusClient *client, const char *path, const char *interface) { GList *list; for (list = g_list_first(client->proxy_list); list; list = g_list_next(list)) { GDBusProxy *proxy = list->data; if (g_str_equal(proxy->interface, interface) == TRUE && g_str_equal(proxy->obj_path, path) == TRUE) { client->proxy_list = g_list_delete_link(client->proxy_list, list); proxy_free(proxy); break; } } } static void start_service(GDBusProxy *proxy) { GDBusClient *client = proxy->client; const char *service_name = client->service_name; dbus_uint32_t flags = 0; DBusMessage *msg; msg = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "StartServiceByName"); if (msg == NULL) return; dbus_message_append_args(msg, DBUS_TYPE_STRING, &service_name, DBUS_TYPE_UINT32, &flags, DBUS_TYPE_INVALID); g_dbus_send_message(client->dbus_conn, msg); return; } GDBusProxy *g_dbus_proxy_new(GDBusClient *client, const char *path, const char *interface) { GDBusProxy *proxy; if (client == NULL) return NULL; proxy = g_dbus_proxy_lookup(client->proxy_list, NULL, path, interface); if (proxy) return g_dbus_proxy_ref(proxy); proxy = proxy_new(client, path, interface); if (proxy == NULL) return NULL; if (!client->connected) { /* Force service to start */ start_service(proxy); return g_dbus_proxy_ref(proxy); } if (!client->get_objects_call) get_all_properties(proxy); return g_dbus_proxy_ref(proxy); } GDBusProxy *g_dbus_proxy_ref(GDBusProxy *proxy) { if (proxy == NULL) return NULL; __sync_fetch_and_add(&proxy->ref_count, 1); return proxy; } void g_dbus_proxy_unref(GDBusProxy *proxy) { if (proxy == NULL) return; if (__sync_sub_and_fetch(&proxy->ref_count, 1) > 0) return; if (proxy->get_all_call != NULL) { dbus_pending_call_cancel(proxy->get_all_call); dbus_pending_call_unref(proxy->get_all_call); } g_hash_table_destroy(proxy->prop_list); g_free(proxy->obj_path); g_free(proxy->interface); g_free(proxy); } const char *g_dbus_proxy_get_path(const GDBusProxy *proxy) { if (proxy == NULL) return NULL; return proxy->obj_path; } const char *g_dbus_proxy_get_interface(GDBusProxy *proxy) { if (proxy == NULL) return NULL; return proxy->interface; } gboolean g_dbus_proxy_get_property(GDBusProxy *proxy, const char *name, DBusMessageIter *iter) { struct prop_entry *prop; if (proxy == NULL || name == NULL) return FALSE; prop = g_hash_table_lookup(proxy->prop_list, name); if (prop == NULL) return FALSE; if (prop->msg == NULL) return FALSE; if (dbus_message_iter_init(prop->msg, iter) == FALSE) return FALSE; return TRUE; } struct refresh_property_data { GDBusProxy *proxy; char *name; }; static void refresh_property_free(gpointer user_data) { struct refresh_property_data *data = user_data; g_free(data->name); g_free(data); } static void refresh_property_reply(DBusPendingCall *call, void *user_data) { struct refresh_property_data *data = user_data; DBusMessage *reply = dbus_pending_call_steal_reply(call); DBusError error; dbus_error_init(&error); if (dbus_set_error_from_message(&error, reply) == FALSE) { DBusMessageIter iter; dbus_message_iter_init(reply, &iter); add_property(data->proxy, data->name, &iter, TRUE); } else dbus_error_free(&error); dbus_message_unref(reply); } gboolean g_dbus_proxy_refresh_property(GDBusProxy *proxy, const char *name) { struct refresh_property_data *data; GDBusClient *client; DBusMessage *msg; DBusMessageIter iter; DBusPendingCall *call; if (proxy == NULL || name == NULL) return FALSE; client = proxy->client; if (client == NULL) return FALSE; data = g_try_new0(struct refresh_property_data, 1); if (data == NULL) return FALSE; data->proxy = proxy; data->name = g_strdup(name); msg = dbus_message_new_method_call(client->service_name, proxy->obj_path, DBUS_INTERFACE_PROPERTIES, "Get"); if (msg == NULL) { refresh_property_free(data); return FALSE; } dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &proxy->interface); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name); if (g_dbus_send_message_with_reply(client->dbus_conn, msg, &call, -1) == FALSE) { dbus_message_unref(msg); refresh_property_free(data); return FALSE; } dbus_pending_call_set_notify(call, refresh_property_reply, data, refresh_property_free); dbus_pending_call_unref(call); dbus_message_unref(msg); return TRUE; } struct set_property_data { GDBusResultFunction function; void *user_data; GDBusDestroyFunction destroy; }; static void set_property_reply(DBusPendingCall *call, void *user_data) { struct set_property_data *data = user_data; DBusMessage *reply = dbus_pending_call_steal_reply(call); DBusError error; dbus_error_init(&error); dbus_set_error_from_message(&error, reply); if (data->function) data->function(&error, data->user_data); if (data->destroy) data->destroy(data->user_data); dbus_error_free(&error); dbus_message_unref(reply); } gboolean g_dbus_proxy_set_property_basic(GDBusProxy *proxy, const char *name, int type, const void *value, GDBusResultFunction function, void *user_data, GDBusDestroyFunction destroy) { struct set_property_data *data; GDBusClient *client; DBusMessage *msg; DBusMessageIter iter; DBusPendingCall *call; if (proxy == NULL || name == NULL || value == NULL) return FALSE; if (dbus_type_is_basic(type) == FALSE) return FALSE; client = proxy->client; if (client == NULL) return FALSE; data = g_try_new0(struct set_property_data, 1); if (data == NULL) return FALSE; data->function = function; data->user_data = user_data; data->destroy = destroy; msg = dbus_message_new_method_call(client->service_name, proxy->obj_path, DBUS_INTERFACE_PROPERTIES, "Set"); if (msg == NULL) { g_free(data); return FALSE; } dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &proxy->interface); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name); append_variant(&iter, type, value); if (g_dbus_send_message_with_reply(client->dbus_conn, msg, &call, -1) == FALSE) { dbus_message_unref(msg); g_free(data); return FALSE; } dbus_pending_call_set_notify(call, set_property_reply, data, g_free); dbus_pending_call_unref(call); dbus_message_unref(msg); return TRUE; } gboolean g_dbus_proxy_set_property_dict(GDBusProxy *proxy, const char *name, GDBusResultFunction function, void *user_data, GDBusDestroyFunction destroy, char *entry, ...) { struct set_property_data *data; GDBusClient *client; DBusMessage *msg; DBusMessageIter iter; DBusPendingCall *call; va_list args; int type, array_type, size; void *value; if (proxy == NULL || name == NULL) return FALSE; client = proxy->client; if (client == NULL) return FALSE; data = g_try_new0(struct set_property_data, 1); if (data == NULL) return FALSE; data->function = function; data->user_data = user_data; data->destroy = destroy; msg = dbus_message_new_method_call(client->service_name, proxy->obj_path, DBUS_INTERFACE_PROPERTIES, "Set"); if (msg == NULL) { g_free(data); return FALSE; } dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &proxy->interface); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name); va_start(args, entry); while (entry != NULL) { type = va_arg(args, int); if (type == DBUS_TYPE_ARRAY) { array_type = va_arg(args, int); size = va_arg(args, int); value = va_arg(args, void *); append_dict_variant(&iter, entry, type, &value, array_type, size); } else { value = va_arg(args, void *); append_dict_variant(&iter, entry, type, &value); } entry = va_arg(args, char *); } va_end(args); if (g_dbus_send_message_with_reply(client->dbus_conn, msg, &call, -1) == FALSE) { dbus_message_unref(msg); g_free(data); return FALSE; } dbus_pending_call_set_notify(call, set_property_reply, data, g_free); dbus_pending_call_unref(call); dbus_message_unref(msg); return TRUE; } gboolean g_dbus_proxy_set_property_array(GDBusProxy *proxy, const char *name, int type, const void *value, size_t size, GDBusResultFunction function, void *user_data, GDBusDestroyFunction destroy) { struct set_property_data *data; GDBusClient *client; DBusMessage *msg; DBusMessageIter iter; DBusPendingCall *call; if (!proxy || !name || !value) return FALSE; if (!dbus_type_is_basic(type)) return FALSE; client = proxy->client; if (!client) return FALSE; data = g_try_new0(struct set_property_data, 1); if (!data) return FALSE; data->function = function; data->user_data = user_data; data->destroy = destroy; msg = dbus_message_new_method_call(client->service_name, proxy->obj_path, DBUS_INTERFACE_PROPERTIES, "Set"); if (!msg) { g_free(data); return FALSE; } dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &proxy->interface); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name); append_array_variant(&iter, type, &value, size); if (g_dbus_send_message_with_reply(client->dbus_conn, msg, &call, -1) == FALSE) { dbus_message_unref(msg); g_free(data); return FALSE; } dbus_pending_call_set_notify(call, set_property_reply, data, g_free); dbus_pending_call_unref(call); dbus_message_unref(msg); return TRUE; } struct method_call_data { GDBusReturnFunction function; void *user_data; GDBusDestroyFunction destroy; }; static void method_call_reply(DBusPendingCall *call, void *user_data) { struct method_call_data *data = user_data; DBusMessage *reply = dbus_pending_call_steal_reply(call); if (data->function) data->function(reply, data->user_data); if (data->destroy) data->destroy(data->user_data); dbus_message_unref(reply); } gboolean g_dbus_proxy_method_call(GDBusProxy *proxy, const char *method, GDBusSetupFunction setup, GDBusReturnFunction function, void *user_data, GDBusDestroyFunction destroy) { struct method_call_data *data; GDBusClient *client; DBusMessage *msg; DBusPendingCall *call; if (proxy == NULL || method == NULL) return FALSE; client = proxy->client; if (client == NULL) return FALSE; msg = dbus_message_new_method_call(client->service_name, proxy->obj_path, proxy->interface, method); if (msg == NULL) return FALSE; if (setup) { DBusMessageIter iter; dbus_message_iter_init_append(msg, &iter); setup(&iter, user_data); } if (!function) return g_dbus_send_message(client->dbus_conn, msg); data = g_try_new0(struct method_call_data, 1); if (data == NULL) return FALSE; data->function = function; data->user_data = user_data; data->destroy = destroy; if (g_dbus_send_message_with_reply(client->dbus_conn, msg, &call, METHOD_CALL_TIMEOUT) == FALSE) { dbus_message_unref(msg); g_free(data); return FALSE; } dbus_pending_call_set_notify(call, method_call_reply, data, g_free); dbus_pending_call_unref(call); dbus_message_unref(msg); return TRUE; } gboolean g_dbus_proxy_set_property_watch(GDBusProxy *proxy, GDBusPropertyFunction function, void *user_data) { if (proxy == NULL) return FALSE; proxy->prop_func = function; proxy->prop_data = user_data; return TRUE; } gboolean g_dbus_proxy_set_removed_watch(GDBusProxy *proxy, GDBusProxyFunction function, void *user_data) { if (proxy == NULL) return FALSE; proxy->removed_func = function; proxy->removed_data = user_data; return TRUE; } static void refresh_properties(GList *list) { GList *l; for (l = g_list_first(list); l; l = g_list_next(l)) { GDBusProxy *proxy = list->data; if (proxy->pending) get_all_properties(proxy); } } static void parse_properties(GDBusClient *client, const char *path, const char *interface, DBusMessageIter *iter) { GDBusProxy *proxy; if (g_str_equal(interface, DBUS_INTERFACE_INTROSPECTABLE) == TRUE) return; if (g_str_equal(interface, DBUS_INTERFACE_PROPERTIES) == TRUE) return; proxy = g_dbus_proxy_lookup(client->proxy_list, NULL, path, interface); if (proxy && !proxy->pending) { update_properties(proxy, iter, FALSE); return; } if (!proxy) { proxy = proxy_new(client, path, interface); if (proxy == NULL) return; } update_properties(proxy, iter, FALSE); proxy_added(client, proxy); } static void parse_interfaces(GDBusClient *client, const char *path, DBusMessageIter *iter) { DBusMessageIter dict; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return; dbus_message_iter_recurse(iter, &dict); while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter entry; const char *interface; dbus_message_iter_recurse(&dict, &entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) break; dbus_message_iter_get_basic(&entry, &interface); dbus_message_iter_next(&entry); parse_properties(client, path, interface, &entry); dbus_message_iter_next(&dict); } } static gboolean interfaces_added(DBusConnection *conn, DBusMessage *msg, void *user_data) { GDBusClient *client = user_data; DBusMessageIter iter; const char *path; if (dbus_message_iter_init(msg, &iter) == FALSE) return TRUE; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) return TRUE; dbus_message_iter_get_basic(&iter, &path); dbus_message_iter_next(&iter); g_dbus_client_ref(client); parse_interfaces(client, path, &iter); g_dbus_client_unref(client); return TRUE; } static gboolean interfaces_removed(DBusConnection *conn, DBusMessage *msg, void *user_data) { GDBusClient *client = user_data; DBusMessageIter iter, entry; const char *path; if (dbus_message_iter_init(msg, &iter) == FALSE) return TRUE; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) return TRUE; dbus_message_iter_get_basic(&iter, &path); dbus_message_iter_next(&iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) return TRUE; dbus_message_iter_recurse(&iter, &entry); g_dbus_client_ref(client); while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRING) { const char *interface; dbus_message_iter_get_basic(&entry, &interface); proxy_remove(client, path, interface); dbus_message_iter_next(&entry); } g_dbus_client_unref(client); return TRUE; } static void parse_managed_objects(GDBusClient *client, DBusMessage *msg) { DBusMessageIter iter, dict; if (dbus_message_iter_init(msg, &iter) == FALSE) return; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) return; dbus_message_iter_recurse(&iter, &dict); while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter entry; const char *path; dbus_message_iter_recurse(&dict, &entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_OBJECT_PATH) break; dbus_message_iter_get_basic(&entry, &path); dbus_message_iter_next(&entry); parse_interfaces(client, path, &entry); dbus_message_iter_next(&dict); } } static void get_managed_objects_reply(DBusPendingCall *call, void *user_data) { GDBusClient *client = user_data; DBusMessage *reply = dbus_pending_call_steal_reply(call); DBusError error; g_dbus_client_ref(client); dbus_error_init(&error); if (dbus_set_error_from_message(&error, reply) == TRUE) { dbus_error_free(&error); goto done; } parse_managed_objects(client, reply); done: if (client->ready) client->ready(client, client->ready_data); dbus_message_unref(reply); dbus_pending_call_unref(client->get_objects_call); client->get_objects_call = NULL; refresh_properties(client->proxy_list); g_dbus_client_unref(client); } static void get_managed_objects(GDBusClient *client) { DBusMessage *msg; if (!client->connected) return; if ((!client->proxy_added && !client->proxy_removed) || !client->root_path) { refresh_properties(client->proxy_list); return; } if (client->get_objects_call != NULL) return; msg = dbus_message_new_method_call(client->service_name, client->root_path, DBUS_INTERFACE_OBJECT_MANAGER, "GetManagedObjects"); if (msg == NULL) return; dbus_message_append_args(msg, DBUS_TYPE_INVALID); if (g_dbus_send_message_with_reply(client->dbus_conn, msg, &client->get_objects_call, -1) == FALSE) { dbus_message_unref(msg); return; } dbus_pending_call_set_notify(client->get_objects_call, get_managed_objects_reply, client, NULL); dbus_message_unref(msg); } static void service_connect(DBusConnection *conn, void *user_data) { GDBusClient *client = user_data; g_dbus_client_ref(client); client->connected = TRUE; get_managed_objects(client); if (client->connect_func) client->connect_func(conn, client->connect_data); g_dbus_client_unref(client); } static void service_disconnect(DBusConnection *conn, void *user_data) { GDBusClient *client = user_data; client->connected = FALSE; g_list_free_full(client->proxy_list, proxy_free); client->proxy_list = NULL; if (client->disconn_func) client->disconn_func(conn, client->disconn_data); } static DBusHandlerResult message_filter(DBusConnection *connection, DBusMessage *message, void *user_data) { GDBusClient *client = user_data; const char *sender, *path, *interface; if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; sender = dbus_message_get_sender(message); if (sender == NULL) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; path = dbus_message_get_path(message); interface = dbus_message_get_interface(message); if (g_str_has_prefix(path, client->base_path) == FALSE) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if (g_str_equal(interface, DBUS_INTERFACE_PROPERTIES) == TRUE) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if (client->signal_func) client->signal_func(connection, message, client->signal_data); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } GDBusClient *g_dbus_client_new(DBusConnection *connection, const char *service, const char *path) { return g_dbus_client_new_full(connection, service, path, "/"); } GDBusClient *g_dbus_client_new_full(DBusConnection *connection, const char *service, const char *path, const char *root_path) { GDBusClient *client; unsigned int i; if (!connection || !service) return NULL; client = g_try_new0(GDBusClient, 1); if (client == NULL) return NULL; if (dbus_connection_add_filter(connection, message_filter, client, NULL) == FALSE) { g_free(client); return NULL; } client->dbus_conn = dbus_connection_ref(connection); client->service_name = g_strdup(service); client->base_path = g_strdup(path); client->root_path = g_strdup(root_path); client->connected = FALSE; client->match_rules = g_ptr_array_sized_new(1); g_ptr_array_set_free_func(client->match_rules, g_free); client->watch = g_dbus_add_service_watch(connection, service, service_connect, service_disconnect, client, NULL); if (!root_path) return g_dbus_client_ref(client); client->added_watch = g_dbus_add_signal_watch(connection, service, client->root_path, DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesAdded", interfaces_added, client, NULL); client->removed_watch = g_dbus_add_signal_watch(connection, service, client->root_path, DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesRemoved", interfaces_removed, client, NULL); g_ptr_array_add(client->match_rules, g_strdup_printf("type='signal'," "sender='%s',path_namespace='%s'", client->service_name, client->base_path)); for (i = 0; i < client->match_rules->len; i++) { modify_match(client->dbus_conn, "AddMatch", g_ptr_array_index(client->match_rules, i)); } return g_dbus_client_ref(client); } GDBusClient *g_dbus_client_ref(GDBusClient *client) { if (client == NULL) return NULL; __sync_fetch_and_add(&client->ref_count, 1); return client; } void g_dbus_client_unref(GDBusClient *client) { unsigned int i; if (client == NULL) return; if (__sync_sub_and_fetch(&client->ref_count, 1) > 0) return; if (client->pending_call != NULL) { dbus_pending_call_cancel(client->pending_call); dbus_pending_call_unref(client->pending_call); } if (client->get_objects_call != NULL) { dbus_pending_call_cancel(client->get_objects_call); dbus_pending_call_unref(client->get_objects_call); } for (i = 0; i < client->match_rules->len; i++) { modify_match(client->dbus_conn, "RemoveMatch", g_ptr_array_index(client->match_rules, i)); } g_ptr_array_free(client->match_rules, TRUE); dbus_connection_remove_filter(client->dbus_conn, message_filter, client); g_list_free_full(client->proxy_list, proxy_free); /* * Don't call disconn_func twice if disconnection * was previously reported. */ if (client->disconn_func && client->connected) client->disconn_func(client->dbus_conn, client->disconn_data); g_dbus_remove_watch(client->dbus_conn, client->watch); g_dbus_remove_watch(client->dbus_conn, client->added_watch); g_dbus_remove_watch(client->dbus_conn, client->removed_watch); dbus_connection_unref(client->dbus_conn); g_free(client->service_name); g_free(client->base_path); g_free(client->root_path); g_free(client); } gboolean g_dbus_client_set_connect_watch(GDBusClient *client, GDBusWatchFunction function, void *user_data) { if (client == NULL) return FALSE; client->connect_func = function; client->connect_data = user_data; return TRUE; } gboolean g_dbus_client_set_disconnect_watch(GDBusClient *client, GDBusWatchFunction function, void *user_data) { if (client == NULL) return FALSE; client->disconn_func = function; client->disconn_data = user_data; return TRUE; } gboolean g_dbus_client_set_signal_watch(GDBusClient *client, GDBusMessageFunction function, void *user_data) { if (client == NULL) return FALSE; client->signal_func = function; client->signal_data = user_data; return TRUE; } gboolean g_dbus_client_set_ready_watch(GDBusClient *client, GDBusClientFunction ready, void *user_data) { if (client == NULL) return FALSE; client->ready = ready; client->ready_data = user_data; return TRUE; } gboolean g_dbus_client_set_proxy_handlers(GDBusClient *client, GDBusProxyFunction proxy_added, GDBusProxyFunction proxy_removed, GDBusPropertyFunction property_changed, void *user_data) { if (client == NULL) return FALSE; client->proxy_added = proxy_added; client->proxy_removed = proxy_removed; client->property_changed = property_changed; client->user_data = user_data; if (proxy_added || proxy_removed || property_changed) get_managed_objects(client); return TRUE; } bluez-5.87/PaxHeaders/Makefile.in0000644000000000000000000000005015221753005013720 xustar0020 atime=1783092766 20 ctime=1783094098 bluez-5.87/Makefile.in0000644000000000000000000335315415221753005013417 0ustar00rootroot# Makefile.in generated by automake 1.18.1 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2025 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) am__rm_f = rm -f $(am__rm_f_notfound) am__rm_rf = rm -rf $(am__rm_f_notfound) pkgdatadir = $(datadir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ bin_PROGRAMS = $(am__EXEEXT_1) $(am__EXEEXT_2) $(am__EXEEXT_3) \ $(am__EXEEXT_4) $(am__EXEEXT_5) $(am__EXEEXT_6) noinst_PROGRAMS = $(am__EXEEXT_7) $(am__EXEEXT_8) $(am__EXEEXT_9) \ $(am__EXEEXT_10) $(am__EXEEXT_11) $(am__EXEEXT_16) pkglibexec_PROGRAMS = src/bluetoothd$(EXEEXT) $(am__EXEEXT_17) \ $(am__EXEEXT_18) $(am__EXEEXT_19) @DATAFILES_TRUE@@OBEX_TRUE@am__append_1 = obexd/src/obex.conf @LIBRARY_TRUE@am__append_2 = $(lib_headers) @LIBRARY_TRUE@am__append_3 = lib/libbluetooth.la @EXTERNAL_ELL_FALSE@am__append_4 = ell/libell-internal.la @LIBSHARED_ELL_TRUE@am__append_5 = src/libshared-ell.la @READLINE_TRUE@am__append_6 = src/shared/shell.c src/shared/shell.h @ADMIN_TRUE@am__append_7 = admin @ADMIN_TRUE@am__append_8 = plugins/admin.c @NFC_TRUE@am__append_9 = neard @NFC_TRUE@am__append_10 = plugins/neard.c @A2DP_TRUE@am__append_11 = a2dp @A2DP_TRUE@am__append_12 = profiles/audio/source.h profiles/audio/source.c \ @A2DP_TRUE@ profiles/audio/sink.h profiles/audio/sink.c \ @A2DP_TRUE@ profiles/audio/a2dp.h profiles/audio/a2dp.c \ @A2DP_TRUE@ profiles/audio/avdtp.h profiles/audio/avdtp.c \ @A2DP_TRUE@ profiles/audio/a2dp-codecs.h @AVRCP_TRUE@am__append_13 = avrcp @AVRCP_TRUE@am__append_14 = profiles/audio/control.h profiles/audio/control.c \ @AVRCP_TRUE@ profiles/audio/avctp.h profiles/audio/avctp.c \ @AVRCP_TRUE@ profiles/audio/avrcp.h profiles/audio/avrcp.c \ @AVRCP_TRUE@ profiles/audio/avrcp-player.c @NETWORK_TRUE@am__append_15 = network @NETWORK_TRUE@am__append_16 = profiles/network/manager.c \ @NETWORK_TRUE@ profiles/network/bnep.h profiles/network/bnep.c \ @NETWORK_TRUE@ profiles/network/server.h profiles/network/server.c \ @NETWORK_TRUE@ profiles/network/connection.h \ @NETWORK_TRUE@ profiles/network/connection.c @HID_TRUE@am__append_17 = input @HID_TRUE@am__append_18 = profiles/input/manager.c \ @HID_TRUE@ profiles/input/server.h profiles/input/server.c \ @HID_TRUE@ profiles/input/device.h profiles/input/device.c \ @HID_TRUE@ profiles/input/hidp_defs.h profiles/input/sixaxis.h @HOG_TRUE@am__append_19 = hog @HOG_TRUE@am__append_20 = profiles/input/hog.c \ @HOG_TRUE@ profiles/input/hog-lib.c profiles/input/hog-lib.h \ @HOG_TRUE@ profiles/deviceinfo/dis.c profiles/deviceinfo/dis.h \ @HOG_TRUE@ profiles/battery/bas.c profiles/battery/bas.h \ @HOG_TRUE@ profiles/scanparam/scpp.c profiles/scanparam/scpp.h \ @HOG_TRUE@ profiles/input/suspend.h profiles/input/suspend-none.c @MIDI_TRUE@am__append_21 = midi @MIDI_TRUE@am__append_22 = profiles/midi/midi.c \ @MIDI_TRUE@ profiles/midi/libmidi.h \ @MIDI_TRUE@ profiles/midi/libmidi.c @MIDI_TRUE@am__append_23 = $(ALSA_CFLAGS) @MIDI_TRUE@am__append_24 = $(ALSA_LIBS) @SIXAXIS_TRUE@am__append_25 = sixaxis @SIXAXIS_TRUE@am__append_26 = plugins/sixaxis.c @SIXAXIS_TRUE@am__append_27 = $(UDEV_LIBS) @BAP_TRUE@am__append_28 = bap @BAP_TRUE@am__append_29 = profiles/audio/bap.c @BASS_TRUE@am__append_30 = bass @BASS_TRUE@am__append_31 = profiles/audio/bass.c @MCP_TRUE@am__append_32 = mcp @MCP_TRUE@am__append_33 = profiles/audio/mcp.c @VCP_TRUE@am__append_34 = vcp @VCP_TRUE@am__append_35 = profiles/audio/vcp.h profiles/audio/vcp.c @MICP_TRUE@am__append_36 = micp @MICP_TRUE@am__append_37 = profiles/audio/micp.c @CCP_TRUE@am__append_38 = ccp @CCP_TRUE@am__append_39 = profiles/audio/ccp.c @CSIP_TRUE@am__append_40 = csip @CSIP_TRUE@am__append_41 = profiles/audio/csip.c @TMAP_TRUE@am__append_42 = tmap @TMAP_TRUE@am__append_43 = profiles/audio/tmap.c @GMAP_TRUE@am__append_44 = gmap @GMAP_TRUE@am__append_45 = profiles/audio/gmap.c @ASHA_TRUE@am__append_46 = asha @ASHA_TRUE@am__append_47 = profiles/audio/asha.h profiles/audio/asha.c @HFP_TRUE@am__append_48 = hfp @HFP_TRUE@am__append_49 = profiles/audio/telephony.h profiles/audio/telephony.c profiles/audio/hfp-hf.c @EXTERNAL_PLUGINS_TRUE@am__append_50 = src/bluetooth.ver # SPDX-License-Identifier: GPL-2.0 @CLIENT_TRUE@am__append_51 = client/bluetoothctl @MONITOR_TRUE@am__append_52 = monitor/btmon @MANPAGES_TRUE@@MONITOR_TRUE@am__append_53 = doc/btmon.1 @LOGGER_TRUE@am__append_54 = tools/btmon-logger @LOGGER_TRUE@@SYSTEMD_TRUE@am__append_55 = tools/bluetooth-logger.service @TESTING_TRUE@am__append_56 = emulator/btvirt emulator/b1ee emulator/hfp \ @TESTING_TRUE@ peripheral/btsensor tools/3dsp \ @TESTING_TRUE@ tools/mgmt-tester tools/gap-tester \ @TESTING_TRUE@ tools/l2cap-tester tools/sco-tester \ @TESTING_TRUE@ tools/smp-tester tools/hci-tester \ @TESTING_TRUE@ tools/rfcomm-tester tools/bnep-tester \ @TESTING_TRUE@ tools/userchan-tester tools/iso-tester \ @TESTING_TRUE@ tools/mesh-tester tools/ioctl-tester \ @TESTING_TRUE@ tools/6lowpan-tester @TOOLS_TRUE@am__append_57 = tools/rctest tools/l2test tools/l2ping tools/bluemoon \ @TOOLS_TRUE@ tools/hex2hcd tools/mpris-proxy tools/btattach tools/isotest @TOOLS_TRUE@am__append_58 = tools/bdaddr tools/avinfo tools/avtest \ @TOOLS_TRUE@ tools/scotest tools/hwdb \ @TOOLS_TRUE@ tools/hcieventmask tools/hcisecfilter \ @TOOLS_TRUE@ tools/btinfo tools/btconfig \ @TOOLS_TRUE@ tools/btsnoop tools/btproxy \ @TOOLS_TRUE@ tools/btiotest tools/bneptest \ @TOOLS_TRUE@ tools/cltest tools/oobtest tools/advtest \ @TOOLS_TRUE@ tools/seq2bseq tools/nokfw tools/rtlfw \ @TOOLS_TRUE@ tools/bcmfw tools/create-image \ @TOOLS_TRUE@ tools/eddystone tools/ibeacon \ @TOOLS_TRUE@ tools/btgatt-client tools/btgatt-server \ @TOOLS_TRUE@ tools/test-runner tools/check-selftest \ @TOOLS_TRUE@ tools/gatt-service profiles/iap/iapd @SYSTEMD_TRUE@@TOOLS_TRUE@am__append_59 = tools/mpris-proxy.service @MANPAGES_TRUE@@TOOLS_TRUE@am__append_60 = doc/rctest.1 doc/l2ping.1 doc/btattach.1 doc/isotest.1 \ @MANPAGES_TRUE@@TOOLS_TRUE@ doc/bluetoothctl.1 \ @MANPAGES_TRUE@@TOOLS_TRUE@ doc/bluetoothctl-mgmt.1 \ @MANPAGES_TRUE@@TOOLS_TRUE@ doc/bluetoothctl-monitor.1 doc/bluetoothctl-admin.1 \ @MANPAGES_TRUE@@TOOLS_TRUE@ doc/bluetoothctl-advertise.1 doc/bluetoothctl-endpoint.1 \ @MANPAGES_TRUE@@TOOLS_TRUE@ doc/bluetoothctl-gatt.1 doc/bluetoothctl-player.1 \ @MANPAGES_TRUE@@TOOLS_TRUE@ doc/bluetoothctl-scan.1 doc/bluetoothctl-transport.1 \ @MANPAGES_TRUE@@TOOLS_TRUE@ doc/bluetoothctl-assistant.1 doc/bluetoothctl-hci.1 \ @MANPAGES_TRUE@@TOOLS_TRUE@ doc/bluetoothctl-telephony.1 @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@am__append_61 = tools/meshctl @MESH_TRUE@@TOOLS_TRUE@am__append_62 = tools/mesh-cfgclient \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh-cfgtest @TOOLS_TRUE@am__append_63 = tools/mesh-gatt/local_node.json tools/mesh-gatt/prov_db.json @DEPRECATED_TRUE@@TOOLS_TRUE@am__append_64 = tools/hciattach tools/hciconfig tools/hcitool \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/rfcomm tools/sdptool tools/ciptool @DEPRECATED_TRUE@@MANPAGES_TRUE@@TOOLS_TRUE@am__append_65 = doc/hciattach.1 doc/hciconfig.1 doc/hcitool.1 \ @DEPRECATED_TRUE@@MANPAGES_TRUE@@TOOLS_TRUE@ doc/rfcomm.1 doc/sdptool.1 doc/ciptool.1 @HID2HCI_TRUE@udev_PROGRAMS = tools/hid2hci$(EXEEXT) @HID2HCI_TRUE@@MANPAGES_TRUE@am__append_66 = doc/hid2hci.1 @READLINE_TRUE@am__append_67 = tools/btmgmt tools/obex-client-tool tools/obex-server-tool \ @READLINE_TRUE@ tools/bluetooth-player tools/obexctl @DEPRECATED_TRUE@@READLINE_TRUE@am__append_68 = attrib/gatttool @CUPS_TRUE@cups_PROGRAMS = profiles/cups/bluetooth$(EXEEXT) @BTPCLIENT_TRUE@am__append_69 = client/btpclient/btpclient client/btpclient/btpclientctl # SPDX-License-Identifier: GPL-2.0 @OBEX_TRUE@@SYSTEMD_TRUE@am__append_70 = obexd/src/obex.service @EXPERIMENTAL_TRUE@@OBEX_TRUE@am__append_71 = pcsuite @EXPERIMENTAL_TRUE@@OBEX_TRUE@am__append_72 = obexd/plugins/pcsuite.c @OBEX_TRUE@am__append_73 = obexd/plugins/phonebook-dummy.c obexd/plugins/phonebook-ebook.c \ @OBEX_TRUE@ obexd/plugins/phonebook-tracker.c @OBEX_TRUE@am__append_74 = obexd/src/obexd @OBEX_TRUE@@SYSTEMD_TRUE@am__append_75 = -DSYSTEMD @OBEX_TRUE@@SYSTEMD_TRUE@am__append_76 = -lsystemd # SPDX-License-Identifier: GPL-2.0 @DATAFILES_TRUE@@MESH_TRUE@am__append_77 = mesh/bluetooth-mesh.conf @DATAFILES_TRUE@@MESH_TRUE@am__append_78 = mesh/mesh-main.conf @MESH_TRUE@@SYSTEMD_TRUE@am__append_79 = mesh/bluetooth-mesh.service @MESH_TRUE@@SYSTEMD_TRUE@am__append_80 = mesh/org.bluez.mesh.service @MESH_TRUE@am__append_81 = mesh/bluetooth-meshd @MANPAGES_TRUE@@MESH_TRUE@am__append_82 = doc/bluetooth-meshd.8 @MESH_TRUE@am__append_83 = doc/bluetooth-meshd.8 @HID2HCI_TRUE@am__append_84 = $(rules_DATA) @OBEX_TRUE@am__append_85 = unit/test-gobex-header unit/test-gobex-packet unit/test-gobex \ @OBEX_TRUE@ unit/test-gobex-transfer unit/test-gobex-apparam @MIDI_TRUE@am__append_86 = unit/test-midi @MESH_TRUE@am__append_87 = unit/test-mesh-crypto @MAINTAINER_MODE_TRUE@am__append_88 = $(unit_tests) TESTS = $(am__EXEEXT_15) @DBUS_RUN_SESSION_TRUE@am__append_89 = dbus-run-session -- subdir = . ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \ $(am__configure_deps) $(am__dist_zshcompletion_DATA_DIST) \ $(am__pkginclude_HEADERS_DIST) $(am__DIST_COMMON) am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ configure.lineno config.status.lineno mkinstalldirs = $(install_sh) -d CONFIG_HEADER = config.h CONFIG_CLEAN_FILES = lib/bluez.pc doc/bluetooth-meshd.rst \ mesh/bluetooth-mesh.service obexd/src/obex.service \ obexd/src/org.bluez.obex.service doc/bluetoothd.rst \ src/bluetooth.service tools/bluetooth-logger.service \ tools/mpris-proxy.service CONFIG_CLEAN_VPATH_FILES = @CLIENT_TRUE@am__EXEEXT_1 = client/bluetoothctl$(EXEEXT) @MONITOR_TRUE@am__EXEEXT_2 = monitor/btmon$(EXEEXT) @TOOLS_TRUE@am__EXEEXT_3 = tools/rctest$(EXEEXT) tools/l2test$(EXEEXT) \ @TOOLS_TRUE@ tools/l2ping$(EXEEXT) tools/bluemoon$(EXEEXT) \ @TOOLS_TRUE@ tools/hex2hcd$(EXEEXT) tools/mpris-proxy$(EXEEXT) \ @TOOLS_TRUE@ tools/btattach$(EXEEXT) tools/isotest$(EXEEXT) @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@am__EXEEXT_4 = tools/meshctl$(EXEEXT) @MESH_TRUE@@TOOLS_TRUE@am__EXEEXT_5 = tools/mesh-cfgclient$(EXEEXT) \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh-cfgtest$(EXEEXT) @DEPRECATED_TRUE@@TOOLS_TRUE@am__EXEEXT_6 = tools/hciattach$(EXEEXT) \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciconfig$(EXEEXT) \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hcitool$(EXEEXT) \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/rfcomm$(EXEEXT) \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/sdptool$(EXEEXT) \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/ciptool$(EXEEXT) am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(cupsdir)" \ "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(udevdir)" \ "$(DESTDIR)$(libdir)" "$(DESTDIR)$(plugindir)" \ "$(DESTDIR)$(testdir)" "$(DESTDIR)$(man1dir)" \ "$(DESTDIR)$(man5dir)" "$(DESTDIR)$(man7dir)" \ "$(DESTDIR)$(man8dir)" "$(DESTDIR)$(confdir)" \ "$(DESTDIR)$(dbusdir)" "$(DESTDIR)$(dbussessionbusdir)" \ "$(DESTDIR)$(dbussystembusdir)" \ "$(DESTDIR)$(zshcompletiondir)" "$(DESTDIR)$(pkgconfigdir)" \ "$(DESTDIR)$(rulesdir)" "$(DESTDIR)$(statedir)" \ "$(DESTDIR)$(systemdsystemunitdir)" \ "$(DESTDIR)$(systemduserunitdir)" "$(DESTDIR)$(pkgincludedir)" @TESTING_TRUE@am__EXEEXT_7 = emulator/btvirt$(EXEEXT) \ @TESTING_TRUE@ emulator/b1ee$(EXEEXT) emulator/hfp$(EXEEXT) \ @TESTING_TRUE@ peripheral/btsensor$(EXEEXT) tools/3dsp$(EXEEXT) \ @TESTING_TRUE@ tools/mgmt-tester$(EXEEXT) \ @TESTING_TRUE@ tools/gap-tester$(EXEEXT) \ @TESTING_TRUE@ tools/l2cap-tester$(EXEEXT) \ @TESTING_TRUE@ tools/sco-tester$(EXEEXT) \ @TESTING_TRUE@ tools/smp-tester$(EXEEXT) \ @TESTING_TRUE@ tools/hci-tester$(EXEEXT) \ @TESTING_TRUE@ tools/rfcomm-tester$(EXEEXT) \ @TESTING_TRUE@ tools/bnep-tester$(EXEEXT) \ @TESTING_TRUE@ tools/userchan-tester$(EXEEXT) \ @TESTING_TRUE@ tools/iso-tester$(EXEEXT) \ @TESTING_TRUE@ tools/mesh-tester$(EXEEXT) \ @TESTING_TRUE@ tools/ioctl-tester$(EXEEXT) \ @TESTING_TRUE@ tools/6lowpan-tester$(EXEEXT) @TOOLS_TRUE@am__EXEEXT_8 = tools/bdaddr$(EXEEXT) tools/avinfo$(EXEEXT) \ @TOOLS_TRUE@ tools/avtest$(EXEEXT) tools/scotest$(EXEEXT) \ @TOOLS_TRUE@ tools/hwdb$(EXEEXT) tools/hcieventmask$(EXEEXT) \ @TOOLS_TRUE@ tools/hcisecfilter$(EXEEXT) tools/btinfo$(EXEEXT) \ @TOOLS_TRUE@ tools/btconfig$(EXEEXT) tools/btsnoop$(EXEEXT) \ @TOOLS_TRUE@ tools/btproxy$(EXEEXT) tools/btiotest$(EXEEXT) \ @TOOLS_TRUE@ tools/bneptest$(EXEEXT) tools/cltest$(EXEEXT) \ @TOOLS_TRUE@ tools/oobtest$(EXEEXT) tools/advtest$(EXEEXT) \ @TOOLS_TRUE@ tools/seq2bseq$(EXEEXT) tools/nokfw$(EXEEXT) \ @TOOLS_TRUE@ tools/rtlfw$(EXEEXT) tools/bcmfw$(EXEEXT) \ @TOOLS_TRUE@ tools/create-image$(EXEEXT) \ @TOOLS_TRUE@ tools/eddystone$(EXEEXT) tools/ibeacon$(EXEEXT) \ @TOOLS_TRUE@ tools/btgatt-client$(EXEEXT) \ @TOOLS_TRUE@ tools/btgatt-server$(EXEEXT) \ @TOOLS_TRUE@ tools/test-runner$(EXEEXT) \ @TOOLS_TRUE@ tools/check-selftest$(EXEEXT) \ @TOOLS_TRUE@ tools/gatt-service$(EXEEXT) \ @TOOLS_TRUE@ profiles/iap/iapd$(EXEEXT) @READLINE_TRUE@am__EXEEXT_9 = tools/btmgmt$(EXEEXT) \ @READLINE_TRUE@ tools/obex-client-tool$(EXEEXT) \ @READLINE_TRUE@ tools/obex-server-tool$(EXEEXT) \ @READLINE_TRUE@ tools/bluetooth-player$(EXEEXT) \ @READLINE_TRUE@ tools/obexctl$(EXEEXT) @DEPRECATED_TRUE@@READLINE_TRUE@am__EXEEXT_10 = \ @DEPRECATED_TRUE@@READLINE_TRUE@ attrib/gatttool$(EXEEXT) @BTPCLIENT_TRUE@am__EXEEXT_11 = client/btpclient/btpclient$(EXEEXT) \ @BTPCLIENT_TRUE@ client/btpclient/btpclientctl$(EXEEXT) @OBEX_TRUE@am__EXEEXT_12 = unit/test-gobex-header$(EXEEXT) \ @OBEX_TRUE@ unit/test-gobex-packet$(EXEEXT) \ @OBEX_TRUE@ unit/test-gobex$(EXEEXT) \ @OBEX_TRUE@ unit/test-gobex-transfer$(EXEEXT) \ @OBEX_TRUE@ unit/test-gobex-apparam$(EXEEXT) @MIDI_TRUE@am__EXEEXT_13 = unit/test-midi$(EXEEXT) @MESH_TRUE@am__EXEEXT_14 = unit/test-mesh-crypto$(EXEEXT) am__EXEEXT_15 = unit/test-tester$(EXEEXT) unit/test-eir$(EXEEXT) \ unit/test-uuid$(EXEEXT) unit/test-textfile$(EXEEXT) \ unit/test-crc$(EXEEXT) unit/test-crypto$(EXEEXT) \ unit/test-ecc$(EXEEXT) unit/test-ringbuf$(EXEEXT) \ unit/test-queue$(EXEEXT) unit/test-mgmt$(EXEEXT) \ unit/test-uhid$(EXEEXT) unit/test-profile$(EXEEXT) \ unit/test-sdp$(EXEEXT) unit/test-avdtp$(EXEEXT) \ unit/test-avctp$(EXEEXT) unit/test-avrcp$(EXEEXT) \ unit/test-hfp$(EXEEXT) unit/test-gdbus-client$(EXEEXT) \ $(am__EXEEXT_12) unit/test-lib$(EXEEXT) \ unit/test-util$(EXEEXT) unit/test-gatt$(EXEEXT) \ unit/test-hog$(EXEEXT) unit/test-gattrib$(EXEEXT) \ unit/test-bap$(EXEEXT) unit/test-micp$(EXEEXT) \ unit/test-bass$(EXEEXT) unit/test-vcp$(EXEEXT) \ unit/test-tmap$(EXEEXT) unit/test-gmap$(EXEEXT) \ unit/test-mcp$(EXEEXT) unit/test-battery$(EXEEXT) \ unit/test-rap$(EXEEXT) $(am__EXEEXT_13) $(am__EXEEXT_14) @MAINTAINER_MODE_TRUE@am__EXEEXT_16 = $(am__EXEEXT_15) @LOGGER_TRUE@am__EXEEXT_17 = tools/btmon-logger$(EXEEXT) @OBEX_TRUE@am__EXEEXT_18 = obexd/src/obexd$(EXEEXT) @MESH_TRUE@am__EXEEXT_19 = mesh/bluetooth-meshd$(EXEEXT) PROGRAMS = $(bin_PROGRAMS) $(cups_PROGRAMS) $(noinst_PROGRAMS) \ $(pkglibexec_PROGRAMS) $(udev_PROGRAMS) LIBRARIES = $(noinst_LIBRARIES) am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && echo $$files | $(am__xargs_n) 40 $(am__rm_f); }; \ } LTLIBRARIES = $(lib_LTLIBRARIES) $(noinst_LTLIBRARIES) \ $(plugin_LTLIBRARIES) ell_libell_internal_la_LIBADD = am__ell_libell_internal_la_SOURCES_DIST = ell/util.h ell/log.h \ ell/queue.h ell/hashmap.h ell/random.h ell/signal.h ell/time.h \ ell/time-private.h ell/timeout.h ell/cipher.h ell/checksum.h \ ell/io.h ell/idle.h ell/main.h ell/settings.h ell/strv.h \ ell/string.h ell/utf8.h ell/dbus.h ell/dbus-service.h \ ell/dbus-client.h ell/key.h ell/cert.h ell/pem.h ell/base64.h \ ell/asn1-private.h ell/cert-private.h ell/pem-private.h \ ell/uuid.h ell/useful.h ell/main-private.h ell/tester.h \ ell/tls.h ell/tls-private.h ell/ecc.h ell/ecc-private.h \ ell/cleanup.h ell/ecdh.h ell/private.h ell/missing.h \ ell/util.c ell/log.c ell/queue.c ell/hashmap.c ell/random.c \ ell/signal.c ell/time.c ell/timeout.c ell/io.c ell/idle.c \ ell/main.c ell/settings.c ell/strv.c ell/string.c ell/cipher.c \ ell/checksum.c ell/pem.c ell/cert.c ell/cert-crypto.c \ ell/key.c ell/base64.c ell/utf8.c ell/dbus-private.h \ ell/dbus.c ell/dbus-message.c ell/dbus-util.c \ ell/dbus-service.c ell/dbus-client.c ell/dbus-name-cache.c \ ell/dbus-filter.c ell/gvariant-private.h ell/gvariant-util.c \ ell/siphash-private.h ell/siphash.c ell/uuid.c ell/tester.c \ ell/tls.c ell/tls-extensions.c ell/tls-suites.c \ ell/tls-record.c ell/ecc.c ell/ecc-external.c ell/ecdh.c am__objects_1 = am__dirstamp = $(am__leading_dot)dirstamp @EXTERNAL_ELL_FALSE@am__objects_2 = ell/util.lo ell/log.lo \ @EXTERNAL_ELL_FALSE@ ell/queue.lo ell/hashmap.lo ell/random.lo \ @EXTERNAL_ELL_FALSE@ ell/signal.lo ell/time.lo ell/timeout.lo \ @EXTERNAL_ELL_FALSE@ ell/io.lo ell/idle.lo ell/main.lo \ @EXTERNAL_ELL_FALSE@ ell/settings.lo ell/strv.lo ell/string.lo \ @EXTERNAL_ELL_FALSE@ ell/cipher.lo ell/checksum.lo ell/pem.lo \ @EXTERNAL_ELL_FALSE@ ell/cert.lo ell/cert-crypto.lo ell/key.lo \ @EXTERNAL_ELL_FALSE@ ell/base64.lo ell/utf8.lo ell/dbus.lo \ @EXTERNAL_ELL_FALSE@ ell/dbus-message.lo ell/dbus-util.lo \ @EXTERNAL_ELL_FALSE@ ell/dbus-service.lo ell/dbus-client.lo \ @EXTERNAL_ELL_FALSE@ ell/dbus-name-cache.lo ell/dbus-filter.lo \ @EXTERNAL_ELL_FALSE@ ell/gvariant-util.lo ell/siphash.lo \ @EXTERNAL_ELL_FALSE@ ell/uuid.lo ell/tester.lo ell/tls.lo \ @EXTERNAL_ELL_FALSE@ ell/tls-extensions.lo ell/tls-suites.lo \ @EXTERNAL_ELL_FALSE@ ell/tls-record.lo ell/ecc.lo \ @EXTERNAL_ELL_FALSE@ ell/ecc-external.lo ell/ecdh.lo @EXTERNAL_ELL_FALSE@am_ell_libell_internal_la_OBJECTS = \ @EXTERNAL_ELL_FALSE@ $(am__objects_1) $(am__objects_2) \ @EXTERNAL_ELL_FALSE@ $(am__objects_1) ell_libell_internal_la_OBJECTS = $(am_ell_libell_internal_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = @EXTERNAL_ELL_FALSE@am_ell_libell_internal_la_rpath = gdbus_libgdbus_internal_la_LIBADD = am_gdbus_libgdbus_internal_la_OBJECTS = gdbus/mainloop.lo \ gdbus/watch.lo gdbus/object.lo gdbus/client.lo gdbus/polkit.lo gdbus_libgdbus_internal_la_OBJECTS = \ $(am_gdbus_libgdbus_internal_la_OBJECTS) lib_libbluetooth_internal_la_LIBADD = am__objects_3 = lib/bluetooth/bluetooth.lo lib/bluetooth/hci.lo \ lib/bluetooth/sdp.lo am__objects_4 = lib/bluetooth/uuid.lo am_lib_libbluetooth_internal_la_OBJECTS = $(am__objects_1) \ $(am__objects_3) $(am__objects_1) $(am__objects_4) lib_libbluetooth_internal_la_OBJECTS = \ $(am_lib_libbluetooth_internal_la_OBJECTS) lib_libbluetooth_la_LIBADD = am__lib_libbluetooth_la_SOURCES_DIST = lib/bluetooth/bluetooth.h \ lib/bluetooth/hci.h lib/bluetooth/hci_lib.h \ lib/bluetooth/sco.h lib/bluetooth/l2cap.h lib/bluetooth/sdp.h \ lib/bluetooth/sdp_lib.h lib/bluetooth/rfcomm.h \ lib/bluetooth/bnep.h lib/bluetooth/cmtp.h lib/bluetooth/hidp.h \ lib/bluetooth/bluetooth.c lib/bluetooth/hci.c \ lib/bluetooth/sdp.c @LIBRARY_TRUE@am_lib_libbluetooth_la_OBJECTS = $(am__objects_1) \ @LIBRARY_TRUE@ $(am__objects_3) lib_libbluetooth_la_OBJECTS = $(am_lib_libbluetooth_la_OBJECTS) lib_libbluetooth_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(lib_libbluetooth_la_LDFLAGS) \ $(LDFLAGS) -o $@ @LIBRARY_TRUE@am_lib_libbluetooth_la_rpath = -rpath $(libdir) src_libshared_ell_la_LIBADD = am__src_libshared_ell_la_SOURCES_DIST = src/shared/io.h \ src/shared/timeout.h src/shared/queue.h src/shared/queue.c \ src/shared/util.h src/shared/util.c src/shared/mgmt.h \ src/shared/mgmt.c src/shared/crypto.h src/shared/crypto.c \ src/shared/ecc.h src/shared/ecc.c src/shared/ringbuf.h \ src/shared/ringbuf.c src/shared/tester.h src/shared/hci.h \ src/shared/hci.c src/shared/hci-crypto.h \ src/shared/hci-crypto.c src/shared/hfp.h src/shared/hfp.c \ src/shared/uhid.h src/shared/uhid.c src/shared/pcap.h \ src/shared/pcap.c src/shared/btsnoop.h src/shared/btsnoop.c \ src/shared/ad.h src/shared/ad.c src/shared/att-types.h \ src/shared/att.h src/shared/att.c src/shared/gatt-helpers.h \ src/shared/gatt-helpers.c src/shared/gatt-client.h \ src/shared/gatt-client.c src/shared/gatt-server.h \ src/shared/gatt-server.c src/shared/gatt-db.h \ src/shared/gatt-db.c src/shared/gap.h src/shared/gap.c \ src/shared/log.h src/shared/log.c src/shared/bap.h \ src/shared/bap.c src/shared/ascs.h src/shared/bap-debug.h \ src/shared/bap-debug.c src/shared/mcs.h src/shared/mcp.h \ src/shared/mcp.c src/shared/vcp.c src/shared/vcp.h \ src/shared/micp.c src/shared/micp.h src/shared/csip.c \ src/shared/csip.h src/shared/bass.h src/shared/bass.c \ src/shared/ccp.h src/shared/ccp.c src/shared/tmap.c \ src/shared/tmap.h src/shared/gmap.c src/shared/gmap.h \ src/shared/lc3.h src/shared/tty.h src/shared/bap-defs.h \ src/shared/asha.h src/shared/asha.c src/shared/battery.h \ src/shared/battery.c src/shared/uinput.h src/shared/uinput.c \ src/shared/rap.h src/shared/rap.c src/shared/shell.c \ src/shared/shell.h src/shared/io-ell.c \ src/shared/timeout-ell.c src/shared/mainloop.h \ src/shared/mainloop-ell.c @READLINE_TRUE@am__objects_5 = src/shared/libshared_ell_la-shell.lo am__objects_6 = src/shared/libshared_ell_la-queue.lo \ src/shared/libshared_ell_la-util.lo \ src/shared/libshared_ell_la-mgmt.lo \ src/shared/libshared_ell_la-crypto.lo \ src/shared/libshared_ell_la-ecc.lo \ src/shared/libshared_ell_la-ringbuf.lo \ src/shared/libshared_ell_la-hci.lo \ src/shared/libshared_ell_la-hci-crypto.lo \ src/shared/libshared_ell_la-hfp.lo \ src/shared/libshared_ell_la-uhid.lo \ src/shared/libshared_ell_la-pcap.lo \ src/shared/libshared_ell_la-btsnoop.lo \ src/shared/libshared_ell_la-ad.lo \ src/shared/libshared_ell_la-att.lo \ src/shared/libshared_ell_la-gatt-helpers.lo \ src/shared/libshared_ell_la-gatt-client.lo \ src/shared/libshared_ell_la-gatt-server.lo \ src/shared/libshared_ell_la-gatt-db.lo \ src/shared/libshared_ell_la-gap.lo \ src/shared/libshared_ell_la-log.lo \ src/shared/libshared_ell_la-bap.lo \ src/shared/libshared_ell_la-bap-debug.lo \ src/shared/libshared_ell_la-mcp.lo \ src/shared/libshared_ell_la-vcp.lo \ src/shared/libshared_ell_la-micp.lo \ src/shared/libshared_ell_la-csip.lo \ src/shared/libshared_ell_la-bass.lo \ src/shared/libshared_ell_la-ccp.lo \ src/shared/libshared_ell_la-tmap.lo \ src/shared/libshared_ell_la-gmap.lo \ src/shared/libshared_ell_la-asha.lo \ src/shared/libshared_ell_la-battery.lo \ src/shared/libshared_ell_la-uinput.lo \ src/shared/libshared_ell_la-rap.lo $(am__objects_5) @LIBSHARED_ELL_TRUE@am_src_libshared_ell_la_OBJECTS = \ @LIBSHARED_ELL_TRUE@ $(am__objects_6) \ @LIBSHARED_ELL_TRUE@ src/shared/libshared_ell_la-io-ell.lo \ @LIBSHARED_ELL_TRUE@ src/shared/libshared_ell_la-timeout-ell.lo \ @LIBSHARED_ELL_TRUE@ src/shared/libshared_ell_la-mainloop-ell.lo src_libshared_ell_la_OBJECTS = $(am_src_libshared_ell_la_OBJECTS) src_libshared_ell_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(src_libshared_ell_la_CFLAGS) $(CFLAGS) \ $(src_libshared_ell_la_LDFLAGS) $(LDFLAGS) -o $@ @LIBSHARED_ELL_TRUE@am_src_libshared_ell_la_rpath = src_libshared_glib_la_LIBADD = am__src_libshared_glib_la_SOURCES_DIST = src/shared/io.h \ src/shared/timeout.h src/shared/queue.h src/shared/queue.c \ src/shared/util.h src/shared/util.c src/shared/mgmt.h \ src/shared/mgmt.c src/shared/crypto.h src/shared/crypto.c \ src/shared/ecc.h src/shared/ecc.c src/shared/ringbuf.h \ src/shared/ringbuf.c src/shared/tester.h src/shared/hci.h \ src/shared/hci.c src/shared/hci-crypto.h \ src/shared/hci-crypto.c src/shared/hfp.h src/shared/hfp.c \ src/shared/uhid.h src/shared/uhid.c src/shared/pcap.h \ src/shared/pcap.c src/shared/btsnoop.h src/shared/btsnoop.c \ src/shared/ad.h src/shared/ad.c src/shared/att-types.h \ src/shared/att.h src/shared/att.c src/shared/gatt-helpers.h \ src/shared/gatt-helpers.c src/shared/gatt-client.h \ src/shared/gatt-client.c src/shared/gatt-server.h \ src/shared/gatt-server.c src/shared/gatt-db.h \ src/shared/gatt-db.c src/shared/gap.h src/shared/gap.c \ src/shared/log.h src/shared/log.c src/shared/bap.h \ src/shared/bap.c src/shared/ascs.h src/shared/bap-debug.h \ src/shared/bap-debug.c src/shared/mcs.h src/shared/mcp.h \ src/shared/mcp.c src/shared/vcp.c src/shared/vcp.h \ src/shared/micp.c src/shared/micp.h src/shared/csip.c \ src/shared/csip.h src/shared/bass.h src/shared/bass.c \ src/shared/ccp.h src/shared/ccp.c src/shared/tmap.c \ src/shared/tmap.h src/shared/gmap.c src/shared/gmap.h \ src/shared/lc3.h src/shared/tty.h src/shared/bap-defs.h \ src/shared/asha.h src/shared/asha.c src/shared/battery.h \ src/shared/battery.c src/shared/uinput.h src/shared/uinput.c \ src/shared/rap.h src/shared/rap.c src/shared/shell.c \ src/shared/shell.h src/shared/io-glib.c \ src/shared/timeout-glib.c src/shared/mainloop-glib.c \ src/shared/mainloop-notify.h src/shared/mainloop-notify.c \ src/shared/tester.c @READLINE_TRUE@am__objects_7 = src/shared/libshared_glib_la-shell.lo am__objects_8 = src/shared/libshared_glib_la-queue.lo \ src/shared/libshared_glib_la-util.lo \ src/shared/libshared_glib_la-mgmt.lo \ src/shared/libshared_glib_la-crypto.lo \ src/shared/libshared_glib_la-ecc.lo \ src/shared/libshared_glib_la-ringbuf.lo \ src/shared/libshared_glib_la-hci.lo \ src/shared/libshared_glib_la-hci-crypto.lo \ src/shared/libshared_glib_la-hfp.lo \ src/shared/libshared_glib_la-uhid.lo \ src/shared/libshared_glib_la-pcap.lo \ src/shared/libshared_glib_la-btsnoop.lo \ src/shared/libshared_glib_la-ad.lo \ src/shared/libshared_glib_la-att.lo \ src/shared/libshared_glib_la-gatt-helpers.lo \ src/shared/libshared_glib_la-gatt-client.lo \ src/shared/libshared_glib_la-gatt-server.lo \ src/shared/libshared_glib_la-gatt-db.lo \ src/shared/libshared_glib_la-gap.lo \ src/shared/libshared_glib_la-log.lo \ src/shared/libshared_glib_la-bap.lo \ src/shared/libshared_glib_la-bap-debug.lo \ src/shared/libshared_glib_la-mcp.lo \ src/shared/libshared_glib_la-vcp.lo \ src/shared/libshared_glib_la-micp.lo \ src/shared/libshared_glib_la-csip.lo \ src/shared/libshared_glib_la-bass.lo \ src/shared/libshared_glib_la-ccp.lo \ src/shared/libshared_glib_la-tmap.lo \ src/shared/libshared_glib_la-gmap.lo \ src/shared/libshared_glib_la-asha.lo \ src/shared/libshared_glib_la-battery.lo \ src/shared/libshared_glib_la-uinput.lo \ src/shared/libshared_glib_la-rap.lo $(am__objects_7) am_src_libshared_glib_la_OBJECTS = $(am__objects_8) \ src/shared/libshared_glib_la-io-glib.lo \ src/shared/libshared_glib_la-timeout-glib.lo \ src/shared/libshared_glib_la-mainloop-glib.lo \ src/shared/libshared_glib_la-mainloop-notify.lo \ src/shared/libshared_glib_la-tester.lo src_libshared_glib_la_OBJECTS = $(am_src_libshared_glib_la_OBJECTS) src_libshared_glib_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(src_libshared_glib_la_CFLAGS) $(CFLAGS) \ $(src_libshared_glib_la_LDFLAGS) $(LDFLAGS) -o $@ src_libshared_mainloop_la_LIBADD = am__src_libshared_mainloop_la_SOURCES_DIST = src/shared/io.h \ src/shared/timeout.h src/shared/queue.h src/shared/queue.c \ src/shared/util.h src/shared/util.c src/shared/mgmt.h \ src/shared/mgmt.c src/shared/crypto.h src/shared/crypto.c \ src/shared/ecc.h src/shared/ecc.c src/shared/ringbuf.h \ src/shared/ringbuf.c src/shared/tester.h src/shared/hci.h \ src/shared/hci.c src/shared/hci-crypto.h \ src/shared/hci-crypto.c src/shared/hfp.h src/shared/hfp.c \ src/shared/uhid.h src/shared/uhid.c src/shared/pcap.h \ src/shared/pcap.c src/shared/btsnoop.h src/shared/btsnoop.c \ src/shared/ad.h src/shared/ad.c src/shared/att-types.h \ src/shared/att.h src/shared/att.c src/shared/gatt-helpers.h \ src/shared/gatt-helpers.c src/shared/gatt-client.h \ src/shared/gatt-client.c src/shared/gatt-server.h \ src/shared/gatt-server.c src/shared/gatt-db.h \ src/shared/gatt-db.c src/shared/gap.h src/shared/gap.c \ src/shared/log.h src/shared/log.c src/shared/bap.h \ src/shared/bap.c src/shared/ascs.h src/shared/bap-debug.h \ src/shared/bap-debug.c src/shared/mcs.h src/shared/mcp.h \ src/shared/mcp.c src/shared/vcp.c src/shared/vcp.h \ src/shared/micp.c src/shared/micp.h src/shared/csip.c \ src/shared/csip.h src/shared/bass.h src/shared/bass.c \ src/shared/ccp.h src/shared/ccp.c src/shared/tmap.c \ src/shared/tmap.h src/shared/gmap.c src/shared/gmap.h \ src/shared/lc3.h src/shared/tty.h src/shared/bap-defs.h \ src/shared/asha.h src/shared/asha.c src/shared/battery.h \ src/shared/battery.c src/shared/uinput.h src/shared/uinput.c \ src/shared/rap.h src/shared/rap.c src/shared/shell.c \ src/shared/shell.h src/shared/io-mainloop.c \ src/shared/timeout-mainloop.c src/shared/mainloop.h \ src/shared/mainloop.c src/shared/mainloop-notify.h \ src/shared/mainloop-notify.c @READLINE_TRUE@am__objects_9 = \ @READLINE_TRUE@ src/shared/libshared_mainloop_la-shell.lo am__objects_10 = src/shared/libshared_mainloop_la-queue.lo \ src/shared/libshared_mainloop_la-util.lo \ src/shared/libshared_mainloop_la-mgmt.lo \ src/shared/libshared_mainloop_la-crypto.lo \ src/shared/libshared_mainloop_la-ecc.lo \ src/shared/libshared_mainloop_la-ringbuf.lo \ src/shared/libshared_mainloop_la-hci.lo \ src/shared/libshared_mainloop_la-hci-crypto.lo \ src/shared/libshared_mainloop_la-hfp.lo \ src/shared/libshared_mainloop_la-uhid.lo \ src/shared/libshared_mainloop_la-pcap.lo \ src/shared/libshared_mainloop_la-btsnoop.lo \ src/shared/libshared_mainloop_la-ad.lo \ src/shared/libshared_mainloop_la-att.lo \ src/shared/libshared_mainloop_la-gatt-helpers.lo \ src/shared/libshared_mainloop_la-gatt-client.lo \ src/shared/libshared_mainloop_la-gatt-server.lo \ src/shared/libshared_mainloop_la-gatt-db.lo \ src/shared/libshared_mainloop_la-gap.lo \ src/shared/libshared_mainloop_la-log.lo \ src/shared/libshared_mainloop_la-bap.lo \ src/shared/libshared_mainloop_la-bap-debug.lo \ src/shared/libshared_mainloop_la-mcp.lo \ src/shared/libshared_mainloop_la-vcp.lo \ src/shared/libshared_mainloop_la-micp.lo \ src/shared/libshared_mainloop_la-csip.lo \ src/shared/libshared_mainloop_la-bass.lo \ src/shared/libshared_mainloop_la-ccp.lo \ src/shared/libshared_mainloop_la-tmap.lo \ src/shared/libshared_mainloop_la-gmap.lo \ src/shared/libshared_mainloop_la-asha.lo \ src/shared/libshared_mainloop_la-battery.lo \ src/shared/libshared_mainloop_la-uinput.lo \ src/shared/libshared_mainloop_la-rap.lo $(am__objects_9) am_src_libshared_mainloop_la_OBJECTS = $(am__objects_10) \ src/shared/libshared_mainloop_la-io-mainloop.lo \ src/shared/libshared_mainloop_la-timeout-mainloop.lo \ src/shared/libshared_mainloop_la-mainloop.lo \ src/shared/libshared_mainloop_la-mainloop-notify.lo src_libshared_mainloop_la_OBJECTS = \ $(am_src_libshared_mainloop_la_OBJECTS) src_libshared_mainloop_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) \ $(src_libshared_mainloop_la_LDFLAGS) $(LDFLAGS) -o $@ am__attrib_gatttool_SOURCES_DIST = attrib/gatttool.c attrib/att.c \ attrib/gatt.c attrib/gattrib.c btio/btio.c attrib/gatttool.h \ attrib/interactive.c attrib/utils.c src/log.c client/display.c \ client/display.h @DEPRECATED_TRUE@@READLINE_TRUE@am_attrib_gatttool_OBJECTS = \ @DEPRECATED_TRUE@@READLINE_TRUE@ attrib/gatttool.$(OBJEXT) \ @DEPRECATED_TRUE@@READLINE_TRUE@ attrib/att.$(OBJEXT) \ @DEPRECATED_TRUE@@READLINE_TRUE@ attrib/gatt.$(OBJEXT) \ @DEPRECATED_TRUE@@READLINE_TRUE@ attrib/gattrib.$(OBJEXT) \ @DEPRECATED_TRUE@@READLINE_TRUE@ btio/btio.$(OBJEXT) \ @DEPRECATED_TRUE@@READLINE_TRUE@ attrib/interactive.$(OBJEXT) \ @DEPRECATED_TRUE@@READLINE_TRUE@ attrib/utils.$(OBJEXT) \ @DEPRECATED_TRUE@@READLINE_TRUE@ src/log.$(OBJEXT) \ @DEPRECATED_TRUE@@READLINE_TRUE@ client/display.$(OBJEXT) attrib_gatttool_OBJECTS = $(am_attrib_gatttool_OBJECTS) am__DEPENDENCIES_1 = @DEPRECATED_TRUE@@READLINE_TRUE@attrib_gatttool_DEPENDENCIES = \ @DEPRECATED_TRUE@@READLINE_TRUE@ lib/libbluetooth-internal.la \ @DEPRECATED_TRUE@@READLINE_TRUE@ src/libshared-glib.la \ @DEPRECATED_TRUE@@READLINE_TRUE@ $(am__DEPENDENCIES_1) am__client_bluetoothctl_SOURCES_DIST = client/main.c client/print.h \ client/print.c client/display.h client/display.c \ client/agent.h client/agent.c client/advertising.h \ client/advertising.c client/adv_monitor.h client/adv_monitor.c \ client/gatt.h client/gatt.c client/admin.h client/admin.c \ client/player.h client/player.c client/mgmt.h client/mgmt.c \ client/assistant.h client/assistant.c client/hci.h \ client/hci.c client/telephony.h client/telephony.c @CLIENT_TRUE@am_client_bluetoothctl_OBJECTS = client/main.$(OBJEXT) \ @CLIENT_TRUE@ client/print.$(OBJEXT) client/display.$(OBJEXT) \ @CLIENT_TRUE@ client/agent.$(OBJEXT) \ @CLIENT_TRUE@ client/advertising.$(OBJEXT) \ @CLIENT_TRUE@ client/adv_monitor.$(OBJEXT) \ @CLIENT_TRUE@ client/gatt.$(OBJEXT) client/admin.$(OBJEXT) \ @CLIENT_TRUE@ client/player.$(OBJEXT) client/mgmt.$(OBJEXT) \ @CLIENT_TRUE@ client/assistant.$(OBJEXT) client/hci.$(OBJEXT) \ @CLIENT_TRUE@ client/telephony.$(OBJEXT) client_bluetoothctl_OBJECTS = $(am_client_bluetoothctl_OBJECTS) @CLIENT_TRUE@client_bluetoothctl_DEPENDENCIES = \ @CLIENT_TRUE@ lib/libbluetooth-internal.la \ @CLIENT_TRUE@ gdbus/libgdbus-internal.la src/libshared-glib.la \ @CLIENT_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) am__client_btpclient_btpclient_SOURCES_DIST = \ client/btpclient/btpclient.c client/btpclient/btpclient.h \ src/shared/btp.c src/shared/btp.h client/btpclient/bap.c \ client/btpclient/bap.h client/btpclient/core.c \ client/btpclient/core.h client/btpclient/gap.c \ client/btpclient/gap.h client/btpclient/gatt.c \ client/btpclient/gatt.h @BTPCLIENT_TRUE@am_client_btpclient_btpclient_OBJECTS = \ @BTPCLIENT_TRUE@ client/btpclient/btpclient.$(OBJEXT) \ @BTPCLIENT_TRUE@ src/shared/btp.$(OBJEXT) \ @BTPCLIENT_TRUE@ client/btpclient/bap.$(OBJEXT) \ @BTPCLIENT_TRUE@ client/btpclient/core.$(OBJEXT) \ @BTPCLIENT_TRUE@ client/btpclient/gap.$(OBJEXT) \ @BTPCLIENT_TRUE@ client/btpclient/gatt.$(OBJEXT) client_btpclient_btpclient_OBJECTS = \ $(am_client_btpclient_btpclient_OBJECTS) @EXTERNAL_ELL_FALSE@am__DEPENDENCIES_2 = ell/libell-internal.la @BTPCLIENT_TRUE@client_btpclient_btpclient_DEPENDENCIES = \ @BTPCLIENT_TRUE@ lib/libbluetooth-internal.la \ @BTPCLIENT_TRUE@ src/libshared-ell.la $(am__DEPENDENCIES_2) am__client_btpclient_btpclientctl_SOURCES_DIST = \ client/btpclient/btpclientctl.c client/display.c @BTPCLIENT_TRUE@am_client_btpclient_btpclientctl_OBJECTS = \ @BTPCLIENT_TRUE@ client/btpclient/btpclientctl.$(OBJEXT) \ @BTPCLIENT_TRUE@ client/display.$(OBJEXT) client_btpclient_btpclientctl_OBJECTS = \ $(am_client_btpclient_btpclientctl_OBJECTS) @BTPCLIENT_TRUE@client_btpclient_btpclientctl_DEPENDENCIES = \ @BTPCLIENT_TRUE@ src/libshared-mainloop.la \ @BTPCLIENT_TRUE@ src/libshared-glib.la \ @BTPCLIENT_TRUE@ lib/libbluetooth-internal.la am__emulator_b1ee_SOURCES_DIST = emulator/b1ee.c @TESTING_TRUE@am_emulator_b1ee_OBJECTS = emulator/b1ee.$(OBJEXT) emulator_b1ee_OBJECTS = $(am_emulator_b1ee_OBJECTS) @TESTING_TRUE@emulator_b1ee_DEPENDENCIES = src/libshared-mainloop.la am__emulator_btvirt_SOURCES_DIST = emulator/main.c monitor/bt.h \ emulator/serial.h emulator/serial.c emulator/server.h \ emulator/server.c emulator/vhci.h emulator/vhci.c \ emulator/btdev.h emulator/btdev.c emulator/bthost.h \ emulator/bthost.c emulator/smp.c emulator/phy.h emulator/phy.c \ emulator/le.h emulator/le.c @TESTING_TRUE@am_emulator_btvirt_OBJECTS = emulator/main.$(OBJEXT) \ @TESTING_TRUE@ emulator/serial.$(OBJEXT) \ @TESTING_TRUE@ emulator/server.$(OBJEXT) \ @TESTING_TRUE@ emulator/vhci.$(OBJEXT) emulator/btdev.$(OBJEXT) \ @TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) \ @TESTING_TRUE@ emulator/phy.$(OBJEXT) emulator/le.$(OBJEXT) emulator_btvirt_OBJECTS = $(am_emulator_btvirt_OBJECTS) @TESTING_TRUE@emulator_btvirt_DEPENDENCIES = \ @TESTING_TRUE@ lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-mainloop.la am__emulator_hfp_SOURCES_DIST = emulator/hfp.c @TESTING_TRUE@am_emulator_hfp_OBJECTS = emulator/hfp.$(OBJEXT) emulator_hfp_OBJECTS = $(am_emulator_hfp_OBJECTS) @TESTING_TRUE@emulator_hfp_DEPENDENCIES = src/libshared-mainloop.la am__mesh_bluetooth_meshd_SOURCES_DIST = mesh/mesh.h mesh/mesh.c \ mesh/net-keys.h mesh/net-keys.c mesh/mesh-io.h mesh/mesh-io.c \ mesh/mesh-mgmt.h mesh/mesh-mgmt.c mesh/error.h \ mesh/mesh-io-api.h mesh/mesh-io-unit.h mesh/mesh-io-unit.c \ mesh/mesh-io-mgmt.h mesh/mesh-io-mgmt.c mesh/mesh-io-generic.h \ mesh/mesh-io-generic.c mesh/net.h mesh/net.c mesh/crypto.h \ mesh/crypto.c mesh/friend.h mesh/friend.c mesh/appkey.h \ mesh/appkey.c mesh/node.h mesh/node.c mesh/provision.h \ mesh/prov.h mesh/model.h mesh/model.c mesh/cfgmod.h \ mesh/cfgmod-server.c mesh/remprv.h mesh/remprv-server.c \ mesh/mesh-config.h mesh/mesh-config-json.c mesh/util.h \ mesh/util.c mesh/dbus.h mesh/dbus.c mesh/agent.h mesh/agent.c \ mesh/prov-acceptor.c mesh/prov-initiator.c mesh/manager.h \ mesh/manager.c mesh/pb-adv.h mesh/pb-adv.c mesh/keyring.h \ mesh/keyring.c mesh/rpl.h mesh/rpl.c mesh/prv-beacon.h \ mesh/prvbeac-server.c mesh/mesh-defs.h mesh/main.c @MESH_TRUE@am__objects_11 = mesh/mesh.$(OBJEXT) \ @MESH_TRUE@ mesh/net-keys.$(OBJEXT) mesh/mesh-io.$(OBJEXT) \ @MESH_TRUE@ mesh/mesh-mgmt.$(OBJEXT) \ @MESH_TRUE@ mesh/mesh-io-unit.$(OBJEXT) \ @MESH_TRUE@ mesh/mesh-io-mgmt.$(OBJEXT) \ @MESH_TRUE@ mesh/mesh-io-generic.$(OBJEXT) mesh/net.$(OBJEXT) \ @MESH_TRUE@ mesh/crypto.$(OBJEXT) mesh/friend.$(OBJEXT) \ @MESH_TRUE@ mesh/appkey.$(OBJEXT) mesh/node.$(OBJEXT) \ @MESH_TRUE@ mesh/model.$(OBJEXT) mesh/cfgmod-server.$(OBJEXT) \ @MESH_TRUE@ mesh/remprv-server.$(OBJEXT) \ @MESH_TRUE@ mesh/mesh-config-json.$(OBJEXT) mesh/util.$(OBJEXT) \ @MESH_TRUE@ mesh/dbus.$(OBJEXT) mesh/agent.$(OBJEXT) \ @MESH_TRUE@ mesh/prov-acceptor.$(OBJEXT) \ @MESH_TRUE@ mesh/prov-initiator.$(OBJEXT) \ @MESH_TRUE@ mesh/manager.$(OBJEXT) mesh/pb-adv.$(OBJEXT) \ @MESH_TRUE@ mesh/keyring.$(OBJEXT) mesh/rpl.$(OBJEXT) \ @MESH_TRUE@ mesh/prvbeac-server.$(OBJEXT) @MESH_TRUE@am_mesh_bluetooth_meshd_OBJECTS = $(am__objects_11) \ @MESH_TRUE@ mesh/main.$(OBJEXT) mesh_bluetooth_meshd_OBJECTS = $(am_mesh_bluetooth_meshd_OBJECTS) @MESH_TRUE@mesh_bluetooth_meshd_DEPENDENCIES = src/libshared-ell.la \ @MESH_TRUE@ $(am__DEPENDENCIES_2) am__monitor_btmon_SOURCES_DIST = monitor/main.c monitor/bt.h \ monitor/display.h monitor/display.c monitor/hcidump.h \ monitor/hcidump.c monitor/ellisys.h monitor/ellisys.c \ monitor/control.h monitor/control.c monitor/packet.h \ monitor/packet.c monitor/vendor.h monitor/vendor.c \ monitor/lmp.h monitor/lmp.c monitor/crc.h monitor/crc.c \ monitor/ll.h monitor/ll.c monitor/l2cap.h monitor/l2cap.c \ monitor/sdp.h monitor/sdp.c monitor/avctp.h monitor/avctp.c \ monitor/avdtp.h monitor/avdtp.c monitor/a2dp.h monitor/a2dp.c \ monitor/rfcomm.h monitor/rfcomm.c monitor/bnep.h \ monitor/bnep.c monitor/hwdb.h monitor/hwdb.c monitor/keys.h \ monitor/keys.c monitor/analyze.h monitor/analyze.c \ monitor/intel.h monitor/intel.c monitor/broadcom.h \ monitor/broadcom.c monitor/msft.h monitor/msft.c \ monitor/jlink.h monitor/jlink.c monitor/tty.h \ monitor/emulator.h monitor/att.h monitor/att.c src/log.h \ src/log.c src/textfile.h src/textfile.c src/settings.h \ src/settings.c @MONITOR_TRUE@am_monitor_btmon_OBJECTS = monitor/main.$(OBJEXT) \ @MONITOR_TRUE@ monitor/display.$(OBJEXT) \ @MONITOR_TRUE@ monitor/hcidump.$(OBJEXT) \ @MONITOR_TRUE@ monitor/ellisys.$(OBJEXT) \ @MONITOR_TRUE@ monitor/control.$(OBJEXT) \ @MONITOR_TRUE@ monitor/packet.$(OBJEXT) \ @MONITOR_TRUE@ monitor/vendor.$(OBJEXT) monitor/lmp.$(OBJEXT) \ @MONITOR_TRUE@ monitor/crc.$(OBJEXT) monitor/ll.$(OBJEXT) \ @MONITOR_TRUE@ monitor/l2cap.$(OBJEXT) monitor/sdp.$(OBJEXT) \ @MONITOR_TRUE@ monitor/avctp.$(OBJEXT) monitor/avdtp.$(OBJEXT) \ @MONITOR_TRUE@ monitor/a2dp.$(OBJEXT) monitor/rfcomm.$(OBJEXT) \ @MONITOR_TRUE@ monitor/bnep.$(OBJEXT) monitor/hwdb.$(OBJEXT) \ @MONITOR_TRUE@ monitor/keys.$(OBJEXT) monitor/analyze.$(OBJEXT) \ @MONITOR_TRUE@ monitor/intel.$(OBJEXT) \ @MONITOR_TRUE@ monitor/broadcom.$(OBJEXT) \ @MONITOR_TRUE@ monitor/msft.$(OBJEXT) monitor/jlink.$(OBJEXT) \ @MONITOR_TRUE@ monitor/att.$(OBJEXT) src/log.$(OBJEXT) \ @MONITOR_TRUE@ src/textfile.$(OBJEXT) src/settings.$(OBJEXT) monitor_btmon_OBJECTS = $(am_monitor_btmon_OBJECTS) @MONITOR_TRUE@monitor_btmon_DEPENDENCIES = \ @MONITOR_TRUE@ lib/libbluetooth-internal.la \ @MONITOR_TRUE@ src/libshared-mainloop.la $(am__DEPENDENCIES_1) \ @MONITOR_TRUE@ $(am__DEPENDENCIES_1) am__obexd_src_obexd_SOURCES_DIST = btio/btio.h btio/btio.c \ gobex/gobex.h gobex/gobex.c gobex/gobex-defs.h \ gobex/gobex-defs.c gobex/gobex-packet.c gobex/gobex-packet.h \ gobex/gobex-header.c gobex/gobex-header.h \ gobex/gobex-transfer.c gobex/gobex-debug.h \ gobex/gobex-apparam.c gobex/gobex-apparam.h \ obexd/plugins/filesystem.c obexd/plugins/filesystem.h \ obexd/plugins/bluetooth.c obexd/plugins/pcsuite.c \ obexd/plugins/opp.c obexd/plugins/ftp.c obexd/plugins/ftp.h \ obexd/plugins/irmc.c obexd/plugins/pbap.c \ obexd/plugins/vcard.h obexd/plugins/vcard.c \ obexd/plugins/phonebook.h \ obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c \ obexd/plugins/mas.c obexd/src/map_ap.h \ obexd/plugins/messages.h obexd/plugins/messages-dummy.c \ obexd/client/mns.c obexd/client/map-event.h obexd/src/main.c \ obexd/src/obexd.h obexd/src/plugin.h obexd/src/plugin.c \ obexd/src/log.h obexd/src/log.c obexd/src/logind.h \ obexd/src/logind.c obexd/src/manager.h obexd/src/manager.c \ obexd/src/obex.h obexd/src/obex.c obexd/src/obex-priv.h \ obexd/src/mimetype.h obexd/src/mimetype.c obexd/src/service.h \ obexd/src/service.c obexd/src/transport.h \ obexd/src/transport.c obexd/src/server.h obexd/src/server.c \ obexd/client/manager.h obexd/client/manager.c \ obexd/client/session.h obexd/client/session.c \ obexd/client/bluetooth.h obexd/client/bluetooth.c \ obexd/client/sync.h obexd/client/sync.c obexd/client/pbap.h \ obexd/client/pbap.c obexd/client/ftp.h obexd/client/ftp.c \ obexd/client/opp.h obexd/client/opp.c obexd/client/map.h \ obexd/client/map.c obexd/client/bip.h obexd/client/bip.c \ obexd/client/bip-common.h obexd/client/bip-common.c \ obexd/client/map-event.c obexd/client/transfer.h \ obexd/client/transfer.c obexd/client/transport.h \ obexd/client/transport.c obexd/client/driver.h \ obexd/client/driver.c am__objects_12 = btio/obexd-btio.$(OBJEXT) am__objects_13 = gobex/obexd-gobex.$(OBJEXT) \ gobex/obexd-gobex-defs.$(OBJEXT) \ gobex/obexd-gobex-packet.$(OBJEXT) \ gobex/obexd-gobex-header.$(OBJEXT) \ gobex/obexd-gobex-transfer.$(OBJEXT) \ gobex/obexd-gobex-apparam.$(OBJEXT) @EXPERIMENTAL_TRUE@@OBEX_TRUE@am__objects_14 = obexd/plugins/obexd-pcsuite.$(OBJEXT) @OBEX_TRUE@am__objects_15 = obexd/plugins/obexd-filesystem.$(OBJEXT) \ @OBEX_TRUE@ obexd/plugins/obexd-bluetooth.$(OBJEXT) \ @OBEX_TRUE@ $(am__objects_14) obexd/plugins/obexd-opp.$(OBJEXT) \ @OBEX_TRUE@ obexd/plugins/obexd-ftp.$(OBJEXT) \ @OBEX_TRUE@ obexd/plugins/obexd-irmc.$(OBJEXT) \ @OBEX_TRUE@ obexd/plugins/obexd-pbap.$(OBJEXT) \ @OBEX_TRUE@ obexd/plugins/obexd-vcard.$(OBJEXT) \ @OBEX_TRUE@ obexd/plugins/obexd-phonebook-@PLUGIN_PHONEBOOK@.$(OBJEXT) \ @OBEX_TRUE@ obexd/plugins/obexd-mas.$(OBJEXT) \ @OBEX_TRUE@ obexd/plugins/obexd-messages-dummy.$(OBJEXT) \ @OBEX_TRUE@ obexd/client/obexd-mns.$(OBJEXT) @OBEX_TRUE@am_obexd_src_obexd_OBJECTS = $(am__objects_12) \ @OBEX_TRUE@ $(am__objects_13) $(am__objects_15) \ @OBEX_TRUE@ obexd/src/obexd-main.$(OBJEXT) \ @OBEX_TRUE@ obexd/src/obexd-plugin.$(OBJEXT) \ @OBEX_TRUE@ obexd/src/obexd-log.$(OBJEXT) \ @OBEX_TRUE@ obexd/src/obexd-logind.$(OBJEXT) \ @OBEX_TRUE@ obexd/src/obexd-manager.$(OBJEXT) \ @OBEX_TRUE@ obexd/src/obexd-obex.$(OBJEXT) \ @OBEX_TRUE@ obexd/src/obexd-mimetype.$(OBJEXT) \ @OBEX_TRUE@ obexd/src/obexd-service.$(OBJEXT) \ @OBEX_TRUE@ obexd/src/obexd-transport.$(OBJEXT) \ @OBEX_TRUE@ obexd/src/obexd-server.$(OBJEXT) \ @OBEX_TRUE@ obexd/client/obexd-manager.$(OBJEXT) \ @OBEX_TRUE@ obexd/client/obexd-session.$(OBJEXT) \ @OBEX_TRUE@ obexd/client/obexd-bluetooth.$(OBJEXT) \ @OBEX_TRUE@ obexd/client/obexd-sync.$(OBJEXT) \ @OBEX_TRUE@ obexd/client/obexd-pbap.$(OBJEXT) \ @OBEX_TRUE@ obexd/client/obexd-ftp.$(OBJEXT) \ @OBEX_TRUE@ obexd/client/obexd-opp.$(OBJEXT) \ @OBEX_TRUE@ obexd/client/obexd-map.$(OBJEXT) \ @OBEX_TRUE@ obexd/client/obexd-bip.$(OBJEXT) \ @OBEX_TRUE@ obexd/client/obexd-bip-common.$(OBJEXT) \ @OBEX_TRUE@ obexd/client/obexd-map-event.$(OBJEXT) \ @OBEX_TRUE@ obexd/client/obexd-transfer.$(OBJEXT) \ @OBEX_TRUE@ obexd/client/obexd-transport.$(OBJEXT) \ @OBEX_TRUE@ obexd/client/obexd-driver.$(OBJEXT) am__objects_16 = $(am__objects_1) nodist_obexd_src_obexd_OBJECTS = $(am__objects_16) obexd_src_obexd_OBJECTS = $(am_obexd_src_obexd_OBJECTS) \ $(nodist_obexd_src_obexd_OBJECTS) @OBEX_TRUE@obexd_src_obexd_DEPENDENCIES = \ @OBEX_TRUE@ lib/libbluetooth-internal.la \ @OBEX_TRUE@ gdbus/libgdbus-internal.la src/libshared-glib.la \ @OBEX_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ @OBEX_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) obexd_src_obexd_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(obexd_src_obexd_LDFLAGS) $(LDFLAGS) \ -o $@ am__peripheral_btsensor_SOURCES_DIST = peripheral/main.c \ peripheral/efivars.h peripheral/efivars.c peripheral/attach.h \ peripheral/attach.c peripheral/log.h peripheral/log.c \ peripheral/gap.h peripheral/gap.c peripheral/gatt.h \ peripheral/gatt.c @TESTING_TRUE@am_peripheral_btsensor_OBJECTS = \ @TESTING_TRUE@ peripheral/main.$(OBJEXT) \ @TESTING_TRUE@ peripheral/efivars.$(OBJEXT) \ @TESTING_TRUE@ peripheral/attach.$(OBJEXT) \ @TESTING_TRUE@ peripheral/log.$(OBJEXT) \ @TESTING_TRUE@ peripheral/gap.$(OBJEXT) \ @TESTING_TRUE@ peripheral/gatt.$(OBJEXT) peripheral_btsensor_OBJECTS = $(am_peripheral_btsensor_OBJECTS) @TESTING_TRUE@peripheral_btsensor_DEPENDENCIES = \ @TESTING_TRUE@ src/libshared-mainloop.la \ @TESTING_TRUE@ lib/libbluetooth-internal.la am__profiles_cups_bluetooth_SOURCES_DIST = profiles/cups/main.c \ profiles/cups/cups.h profiles/cups/sdp.c profiles/cups/spp.c \ profiles/cups/hcrp.c @CUPS_TRUE@am_profiles_cups_bluetooth_OBJECTS = \ @CUPS_TRUE@ profiles/cups/main.$(OBJEXT) \ @CUPS_TRUE@ profiles/cups/sdp.$(OBJEXT) \ @CUPS_TRUE@ profiles/cups/spp.$(OBJEXT) \ @CUPS_TRUE@ profiles/cups/hcrp.$(OBJEXT) profiles_cups_bluetooth_OBJECTS = \ $(am_profiles_cups_bluetooth_OBJECTS) @CUPS_TRUE@profiles_cups_bluetooth_DEPENDENCIES = \ @CUPS_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ @CUPS_TRUE@ lib/libbluetooth-internal.la \ @CUPS_TRUE@ gdbus/libgdbus-internal.la am__profiles_iap_iapd_SOURCES_DIST = profiles/iap/main.c @TOOLS_TRUE@am_profiles_iap_iapd_OBJECTS = \ @TOOLS_TRUE@ profiles/iap/main.$(OBJEXT) profiles_iap_iapd_OBJECTS = $(am_profiles_iap_iapd_OBJECTS) @TOOLS_TRUE@profiles_iap_iapd_DEPENDENCIES = \ @TOOLS_TRUE@ gdbus/libgdbus-internal.la $(am__DEPENDENCIES_1) \ @TOOLS_TRUE@ $(am__DEPENDENCIES_1) am__src_bluetoothd_SOURCES_DIST = plugins/hostname.c \ plugins/autopair.c plugins/policy.c profiles/audio/media.h \ profiles/audio/media.c profiles/audio/transport.h \ profiles/audio/transport.c profiles/audio/player.h \ profiles/audio/player.c plugins/admin.c plugins/neard.c \ profiles/audio/source.h profiles/audio/source.c \ profiles/audio/sink.h profiles/audio/sink.c \ profiles/audio/a2dp.h profiles/audio/a2dp.c \ profiles/audio/avdtp.h profiles/audio/avdtp.c \ profiles/audio/a2dp-codecs.h profiles/audio/control.h \ profiles/audio/control.c profiles/audio/avctp.h \ profiles/audio/avctp.c profiles/audio/avrcp.h \ profiles/audio/avrcp.c profiles/audio/avrcp-player.c \ profiles/network/manager.c profiles/network/bnep.h \ profiles/network/bnep.c profiles/network/server.h \ profiles/network/server.c profiles/network/connection.h \ profiles/network/connection.c profiles/input/manager.c \ profiles/input/server.h profiles/input/server.c \ profiles/input/device.h profiles/input/device.c \ profiles/input/hidp_defs.h profiles/input/sixaxis.h \ profiles/input/hog.c profiles/input/hog-lib.c \ profiles/input/hog-lib.h profiles/deviceinfo/dis.c \ profiles/deviceinfo/dis.h profiles/battery/bas.c \ profiles/battery/bas.h profiles/scanparam/scpp.c \ profiles/scanparam/scpp.h profiles/input/suspend.h \ profiles/input/suspend-none.c profiles/gap/gas.c \ profiles/scanparam/scan.c profiles/deviceinfo/deviceinfo.c \ profiles/midi/midi.c profiles/midi/libmidi.h \ profiles/midi/libmidi.c profiles/battery/battery.c \ profiles/ranging/rap.c profiles/ranging/rap_hci.c \ plugins/sixaxis.c profiles/audio/bap.c profiles/audio/bass.c \ profiles/audio/mcp.c profiles/audio/vcp.h profiles/audio/vcp.c \ profiles/audio/micp.c profiles/audio/ccp.c \ profiles/audio/csip.c profiles/audio/tmap.c \ profiles/audio/gmap.c profiles/audio/asha.h \ profiles/audio/asha.c profiles/audio/telephony.h \ profiles/audio/telephony.c profiles/audio/hfp-hf.c \ attrib/att.h attrib/att-database.h attrib/att.c attrib/gatt.h \ attrib/gatt.c attrib/gattrib.h attrib/gattrib.c btio/btio.h \ btio/btio.c src/log.h src/log.c src/backtrace.h \ src/backtrace.c src/rfkill.c src/btd.h src/sdpd.h \ src/sdpd-server.c src/sdpd-request.c src/sdpd-service.c \ src/sdpd-database.c src/gatt-database.h src/gatt-database.c \ src/sdp-xml.h src/sdp-xml.c src/sdp-client.h src/sdp-client.c \ src/textfile.h src/textfile.c src/uuid-helper.h \ src/uuid-helper.c src/storage.h src/storage.c \ src/advertising.h src/advertising.c src/agent.h src/agent.c \ src/error.h src/error.c src/adapter.h src/adapter.c \ src/profile.h src/profile.c src/service.h src/service.c \ src/gatt-client.h src/gatt-client.c src/device.h src/device.c \ src/dbus-common.c src/dbus-common.h src/eir.h src/eir.c \ src/adv_monitor.h src/adv_monitor.c src/battery.h \ src/battery.c src/settings.h src/settings.c src/set.h \ src/set.c src/bearer.h src/bearer.c src/plugin.h src/plugin.c \ src/main.c src/bluetooth.ver @ADMIN_TRUE@am__objects_17 = plugins/bluetoothd-admin.$(OBJEXT) @NFC_TRUE@am__objects_18 = plugins/bluetoothd-neard.$(OBJEXT) @A2DP_TRUE@am__objects_19 = \ @A2DP_TRUE@ profiles/audio/bluetoothd-source.$(OBJEXT) \ @A2DP_TRUE@ profiles/audio/bluetoothd-sink.$(OBJEXT) \ @A2DP_TRUE@ profiles/audio/bluetoothd-a2dp.$(OBJEXT) \ @A2DP_TRUE@ profiles/audio/bluetoothd-avdtp.$(OBJEXT) @AVRCP_TRUE@am__objects_20 = \ @AVRCP_TRUE@ profiles/audio/bluetoothd-control.$(OBJEXT) \ @AVRCP_TRUE@ profiles/audio/bluetoothd-avctp.$(OBJEXT) \ @AVRCP_TRUE@ profiles/audio/bluetoothd-avrcp.$(OBJEXT) \ @AVRCP_TRUE@ profiles/audio/bluetoothd-avrcp-player.$(OBJEXT) @NETWORK_TRUE@am__objects_21 = \ @NETWORK_TRUE@ profiles/network/bluetoothd-manager.$(OBJEXT) \ @NETWORK_TRUE@ profiles/network/bluetoothd-bnep.$(OBJEXT) \ @NETWORK_TRUE@ profiles/network/bluetoothd-server.$(OBJEXT) \ @NETWORK_TRUE@ profiles/network/bluetoothd-connection.$(OBJEXT) @HID_TRUE@am__objects_22 = \ @HID_TRUE@ profiles/input/bluetoothd-manager.$(OBJEXT) \ @HID_TRUE@ profiles/input/bluetoothd-server.$(OBJEXT) \ @HID_TRUE@ profiles/input/bluetoothd-device.$(OBJEXT) @HOG_TRUE@am__objects_23 = profiles/input/bluetoothd-hog.$(OBJEXT) \ @HOG_TRUE@ profiles/input/bluetoothd-hog-lib.$(OBJEXT) \ @HOG_TRUE@ profiles/deviceinfo/bluetoothd-dis.$(OBJEXT) \ @HOG_TRUE@ profiles/battery/bluetoothd-bas.$(OBJEXT) \ @HOG_TRUE@ profiles/scanparam/bluetoothd-scpp.$(OBJEXT) \ @HOG_TRUE@ profiles/input/bluetoothd-suspend-none.$(OBJEXT) @MIDI_TRUE@am__objects_24 = profiles/midi/bluetoothd-midi.$(OBJEXT) \ @MIDI_TRUE@ profiles/midi/bluetoothd-libmidi.$(OBJEXT) @SIXAXIS_TRUE@am__objects_25 = plugins/bluetoothd-sixaxis.$(OBJEXT) @BAP_TRUE@am__objects_26 = profiles/audio/bluetoothd-bap.$(OBJEXT) @BASS_TRUE@am__objects_27 = profiles/audio/bluetoothd-bass.$(OBJEXT) @MCP_TRUE@am__objects_28 = profiles/audio/bluetoothd-mcp.$(OBJEXT) @VCP_TRUE@am__objects_29 = profiles/audio/bluetoothd-vcp.$(OBJEXT) @MICP_TRUE@am__objects_30 = profiles/audio/bluetoothd-micp.$(OBJEXT) @CCP_TRUE@am__objects_31 = profiles/audio/bluetoothd-ccp.$(OBJEXT) @CSIP_TRUE@am__objects_32 = profiles/audio/bluetoothd-csip.$(OBJEXT) @TMAP_TRUE@am__objects_33 = profiles/audio/bluetoothd-tmap.$(OBJEXT) @GMAP_TRUE@am__objects_34 = profiles/audio/bluetoothd-gmap.$(OBJEXT) @ASHA_TRUE@am__objects_35 = profiles/audio/bluetoothd-asha.$(OBJEXT) @HFP_TRUE@am__objects_36 = \ @HFP_TRUE@ profiles/audio/bluetoothd-telephony.$(OBJEXT) \ @HFP_TRUE@ profiles/audio/bluetoothd-hfp-hf.$(OBJEXT) am__objects_37 = plugins/bluetoothd-hostname.$(OBJEXT) \ plugins/bluetoothd-autopair.$(OBJEXT) \ plugins/bluetoothd-policy.$(OBJEXT) \ profiles/audio/bluetoothd-media.$(OBJEXT) \ profiles/audio/bluetoothd-transport.$(OBJEXT) \ profiles/audio/bluetoothd-player.$(OBJEXT) $(am__objects_17) \ $(am__objects_18) $(am__objects_19) $(am__objects_20) \ $(am__objects_21) $(am__objects_22) $(am__objects_23) \ profiles/gap/bluetoothd-gas.$(OBJEXT) \ profiles/scanparam/bluetoothd-scan.$(OBJEXT) \ profiles/deviceinfo/bluetoothd-deviceinfo.$(OBJEXT) \ $(am__objects_24) \ profiles/battery/bluetoothd-battery.$(OBJEXT) \ profiles/ranging/bluetoothd-rap.$(OBJEXT) \ profiles/ranging/bluetoothd-rap_hci.$(OBJEXT) \ $(am__objects_25) $(am__objects_26) $(am__objects_27) \ $(am__objects_28) $(am__objects_29) $(am__objects_30) \ $(am__objects_31) $(am__objects_32) $(am__objects_33) \ $(am__objects_34) $(am__objects_35) $(am__objects_36) am__objects_38 = attrib/bluetoothd-att.$(OBJEXT) \ attrib/bluetoothd-gatt.$(OBJEXT) \ attrib/bluetoothd-gattrib.$(OBJEXT) am__objects_39 = btio/bluetoothd-btio.$(OBJEXT) am__objects_40 = $(am__objects_38) $(am__objects_39) \ src/bluetoothd-log.$(OBJEXT) \ src/bluetoothd-backtrace.$(OBJEXT) \ src/bluetoothd-rfkill.$(OBJEXT) \ src/bluetoothd-sdpd-server.$(OBJEXT) \ src/bluetoothd-sdpd-request.$(OBJEXT) \ src/bluetoothd-sdpd-service.$(OBJEXT) \ src/bluetoothd-sdpd-database.$(OBJEXT) \ src/bluetoothd-gatt-database.$(OBJEXT) \ src/bluetoothd-sdp-xml.$(OBJEXT) \ src/bluetoothd-sdp-client.$(OBJEXT) \ src/bluetoothd-textfile.$(OBJEXT) \ src/bluetoothd-uuid-helper.$(OBJEXT) \ src/bluetoothd-storage.$(OBJEXT) \ src/bluetoothd-advertising.$(OBJEXT) \ src/bluetoothd-agent.$(OBJEXT) src/bluetoothd-error.$(OBJEXT) \ src/bluetoothd-adapter.$(OBJEXT) \ src/bluetoothd-profile.$(OBJEXT) \ src/bluetoothd-service.$(OBJEXT) \ src/bluetoothd-gatt-client.$(OBJEXT) \ src/bluetoothd-device.$(OBJEXT) \ src/bluetoothd-dbus-common.$(OBJEXT) \ src/bluetoothd-eir.$(OBJEXT) \ src/bluetoothd-adv_monitor.$(OBJEXT) \ src/bluetoothd-battery.$(OBJEXT) \ src/bluetoothd-settings.$(OBJEXT) src/bluetoothd-set.$(OBJEXT) \ src/bluetoothd-bearer.$(OBJEXT) am_src_bluetoothd_OBJECTS = $(am__objects_37) $(am__objects_40) \ src/bluetoothd-plugin.$(OBJEXT) src/bluetoothd-main.$(OBJEXT) \ $(am__objects_1) nodist_src_bluetoothd_OBJECTS = $(am__objects_1) src_bluetoothd_OBJECTS = $(am_src_bluetoothd_OBJECTS) \ $(nodist_src_bluetoothd_OBJECTS) am__DEPENDENCIES_3 = lib/libbluetooth-internal.la \ gdbus/libgdbus-internal.la src/libshared-glib.la \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) @MIDI_TRUE@am__DEPENDENCIES_4 = $(am__DEPENDENCIES_1) @SIXAXIS_TRUE@am__DEPENDENCIES_5 = $(am__DEPENDENCIES_1) am__DEPENDENCIES_6 = $(am__DEPENDENCIES_4) $(am__DEPENDENCIES_5) src_bluetoothd_DEPENDENCIES = $(am__DEPENDENCIES_3) \ $(am__DEPENDENCIES_6) src_bluetoothd_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(src_bluetoothd_LDFLAGS) $(LDFLAGS) -o \ $@ am__tools_3dsp_SOURCES_DIST = tools/3dsp.c monitor/bt.h @TESTING_TRUE@am_tools_3dsp_OBJECTS = tools/3dsp.$(OBJEXT) tools_3dsp_OBJECTS = $(am_tools_3dsp_OBJECTS) @TESTING_TRUE@tools_3dsp_DEPENDENCIES = src/libshared-mainloop.la am__tools_6lowpan_tester_SOURCES_DIST = tools/6lowpan-tester.c \ monitor/bt.h emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c emulator/btdev.h \ emulator/btdev.c emulator/bthost.h emulator/bthost.c \ emulator/smp.c @TESTING_TRUE@am_tools_6lowpan_tester_OBJECTS = \ @TESTING_TRUE@ tools/6lowpan-tester.$(OBJEXT) \ @TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ @TESTING_TRUE@ emulator/vhci.$(OBJEXT) emulator/btdev.$(OBJEXT) \ @TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) tools_6lowpan_tester_OBJECTS = $(am_tools_6lowpan_tester_OBJECTS) @TESTING_TRUE@tools_6lowpan_tester_DEPENDENCIES = \ @TESTING_TRUE@ lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) am__tools_advtest_SOURCES_DIST = tools/advtest.c @TOOLS_TRUE@am_tools_advtest_OBJECTS = tools/advtest.$(OBJEXT) tools_advtest_OBJECTS = $(am_tools_advtest_OBJECTS) @TOOLS_TRUE@tools_advtest_DEPENDENCIES = lib/libbluetooth-internal.la \ @TOOLS_TRUE@ src/libshared-mainloop.la tools_avinfo_SOURCES = tools/avinfo.c tools_avinfo_OBJECTS = tools/avinfo.$(OBJEXT) @TOOLS_TRUE@tools_avinfo_DEPENDENCIES = lib/libbluetooth-internal.la tools_avtest_SOURCES = tools/avtest.c tools_avtest_OBJECTS = tools/avtest.$(OBJEXT) @TOOLS_TRUE@tools_avtest_DEPENDENCIES = lib/libbluetooth-internal.la tools_bcmfw_SOURCES = tools/bcmfw.c tools_bcmfw_OBJECTS = tools/bcmfw.$(OBJEXT) tools_bcmfw_LDADD = $(LDADD) am__tools_bdaddr_SOURCES_DIST = tools/bdaddr.c src/oui.h src/oui.c @TOOLS_TRUE@am_tools_bdaddr_OBJECTS = tools/bdaddr.$(OBJEXT) \ @TOOLS_TRUE@ src/oui.$(OBJEXT) tools_bdaddr_OBJECTS = $(am_tools_bdaddr_OBJECTS) @TOOLS_TRUE@tools_bdaddr_DEPENDENCIES = lib/libbluetooth-internal.la \ @TOOLS_TRUE@ $(am__DEPENDENCIES_1) am__tools_bluemoon_SOURCES_DIST = tools/bluemoon.c monitor/bt.h @TOOLS_TRUE@am_tools_bluemoon_OBJECTS = tools/bluemoon.$(OBJEXT) tools_bluemoon_OBJECTS = $(am_tools_bluemoon_OBJECTS) @TOOLS_TRUE@tools_bluemoon_DEPENDENCIES = src/libshared-mainloop.la am__tools_bluetooth_player_SOURCES_DIST = tools/bluetooth-player.c \ client/print.c client/player.c @READLINE_TRUE@am_tools_bluetooth_player_OBJECTS = \ @READLINE_TRUE@ tools/bluetooth-player.$(OBJEXT) \ @READLINE_TRUE@ client/print.$(OBJEXT) client/player.$(OBJEXT) tools_bluetooth_player_OBJECTS = $(am_tools_bluetooth_player_OBJECTS) @READLINE_TRUE@tools_bluetooth_player_DEPENDENCIES = \ @READLINE_TRUE@ gdbus/libgdbus-internal.la \ @READLINE_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) \ @READLINE_TRUE@ $(am__DEPENDENCIES_1) am__tools_bnep_tester_SOURCES_DIST = tools/bnep-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c emulator/vhci.h \ emulator/vhci.c emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c emulator/smp.c @TESTING_TRUE@am_tools_bnep_tester_OBJECTS = \ @TESTING_TRUE@ tools/bnep-tester.$(OBJEXT) \ @TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ @TESTING_TRUE@ emulator/vhci.$(OBJEXT) emulator/btdev.$(OBJEXT) \ @TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) tools_bnep_tester_OBJECTS = $(am_tools_bnep_tester_OBJECTS) @TESTING_TRUE@tools_bnep_tester_DEPENDENCIES = \ @TESTING_TRUE@ lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) am__tools_bneptest_SOURCES_DIST = tools/bneptest.c btio/btio.h \ btio/btio.c src/log.h src/log.c profiles/network/bnep.h \ profiles/network/bnep.c @TOOLS_TRUE@am_tools_bneptest_OBJECTS = tools/bneptest.$(OBJEXT) \ @TOOLS_TRUE@ btio/btio.$(OBJEXT) src/log.$(OBJEXT) \ @TOOLS_TRUE@ profiles/network/bnep.$(OBJEXT) tools_bneptest_OBJECTS = $(am_tools_bneptest_OBJECTS) @TOOLS_TRUE@tools_bneptest_DEPENDENCIES = \ @TOOLS_TRUE@ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) \ @TOOLS_TRUE@ src/libshared-mainloop.la am__tools_btattach_SOURCES_DIST = tools/btattach.c monitor/bt.h @TOOLS_TRUE@am_tools_btattach_OBJECTS = tools/btattach.$(OBJEXT) tools_btattach_OBJECTS = $(am_tools_btattach_OBJECTS) @TOOLS_TRUE@tools_btattach_DEPENDENCIES = src/libshared-mainloop.la am__tools_btconfig_SOURCES_DIST = tools/btconfig.c @TOOLS_TRUE@am_tools_btconfig_OBJECTS = tools/btconfig.$(OBJEXT) tools_btconfig_OBJECTS = $(am_tools_btconfig_OBJECTS) @TOOLS_TRUE@tools_btconfig_DEPENDENCIES = src/libshared-mainloop.la am__tools_btgatt_client_SOURCES_DIST = tools/btgatt-client.c \ src/uuid-helper.c @TOOLS_TRUE@am_tools_btgatt_client_OBJECTS = \ @TOOLS_TRUE@ tools/btgatt-client.$(OBJEXT) \ @TOOLS_TRUE@ src/uuid-helper.$(OBJEXT) tools_btgatt_client_OBJECTS = $(am_tools_btgatt_client_OBJECTS) @TOOLS_TRUE@tools_btgatt_client_DEPENDENCIES = \ @TOOLS_TRUE@ src/libshared-mainloop.la \ @TOOLS_TRUE@ lib/libbluetooth-internal.la am__tools_btgatt_server_SOURCES_DIST = tools/btgatt-server.c \ src/uuid-helper.c @TOOLS_TRUE@am_tools_btgatt_server_OBJECTS = \ @TOOLS_TRUE@ tools/btgatt-server.$(OBJEXT) \ @TOOLS_TRUE@ src/uuid-helper.$(OBJEXT) tools_btgatt_server_OBJECTS = $(am_tools_btgatt_server_OBJECTS) @TOOLS_TRUE@tools_btgatt_server_DEPENDENCIES = \ @TOOLS_TRUE@ src/libshared-mainloop.la \ @TOOLS_TRUE@ lib/libbluetooth-internal.la am__tools_btinfo_SOURCES_DIST = tools/btinfo.c monitor/bt.h @TOOLS_TRUE@am_tools_btinfo_OBJECTS = tools/btinfo.$(OBJEXT) tools_btinfo_OBJECTS = $(am_tools_btinfo_OBJECTS) @TOOLS_TRUE@tools_btinfo_DEPENDENCIES = src/libshared-mainloop.la am__tools_btiotest_SOURCES_DIST = tools/btiotest.c btio/btio.h \ btio/btio.c @TOOLS_TRUE@am_tools_btiotest_OBJECTS = tools/btiotest.$(OBJEXT) \ @TOOLS_TRUE@ btio/btio.$(OBJEXT) tools_btiotest_OBJECTS = $(am_tools_btiotest_OBJECTS) @TOOLS_TRUE@tools_btiotest_DEPENDENCIES = \ @TOOLS_TRUE@ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am__tools_btmgmt_SOURCES_DIST = tools/btmgmt.c src/uuid-helper.c \ client/display.c client/mgmt.c @READLINE_TRUE@am_tools_btmgmt_OBJECTS = tools/btmgmt.$(OBJEXT) \ @READLINE_TRUE@ src/uuid-helper.$(OBJEXT) \ @READLINE_TRUE@ client/display.$(OBJEXT) client/mgmt.$(OBJEXT) tools_btmgmt_OBJECTS = $(am_tools_btmgmt_OBJECTS) @READLINE_TRUE@tools_btmgmt_DEPENDENCIES = \ @READLINE_TRUE@ lib/libbluetooth-internal.la \ @READLINE_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) am__tools_btmon_logger_SOURCES_DIST = tools/btmon-logger.c @LOGGER_TRUE@am_tools_btmon_logger_OBJECTS = \ @LOGGER_TRUE@ tools/btmon-logger.$(OBJEXT) tools_btmon_logger_OBJECTS = $(am_tools_btmon_logger_OBJECTS) @LOGGER_TRUE@tools_btmon_logger_DEPENDENCIES = \ @LOGGER_TRUE@ src/libshared-mainloop.la am__tools_btproxy_SOURCES_DIST = tools/btproxy.c monitor/bt.h @TOOLS_TRUE@am_tools_btproxy_OBJECTS = tools/btproxy.$(OBJEXT) tools_btproxy_OBJECTS = $(am_tools_btproxy_OBJECTS) @TOOLS_TRUE@tools_btproxy_DEPENDENCIES = src/libshared-mainloop.la am__tools_btsnoop_SOURCES_DIST = tools/btsnoop.c @TOOLS_TRUE@am_tools_btsnoop_OBJECTS = tools/btsnoop.$(OBJEXT) tools_btsnoop_OBJECTS = $(am_tools_btsnoop_OBJECTS) @TOOLS_TRUE@tools_btsnoop_DEPENDENCIES = src/libshared-mainloop.la tools_check_selftest_SOURCES = tools/check-selftest.c tools_check_selftest_OBJECTS = tools/check-selftest.$(OBJEXT) tools_check_selftest_LDADD = $(LDADD) tools_ciptool_SOURCES = tools/ciptool.c tools_ciptool_OBJECTS = tools/ciptool.$(OBJEXT) @DEPRECATED_TRUE@@TOOLS_TRUE@tools_ciptool_DEPENDENCIES = \ @DEPRECATED_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la am__tools_cltest_SOURCES_DIST = tools/cltest.c @TOOLS_TRUE@am_tools_cltest_OBJECTS = tools/cltest.$(OBJEXT) tools_cltest_OBJECTS = $(am_tools_cltest_OBJECTS) @TOOLS_TRUE@tools_cltest_DEPENDENCIES = lib/libbluetooth-internal.la \ @TOOLS_TRUE@ src/libshared-mainloop.la am__tools_create_image_SOURCES_DIST = tools/create-image.c @TOOLS_TRUE@am_tools_create_image_OBJECTS = \ @TOOLS_TRUE@ tools/create-image.$(OBJEXT) tools_create_image_OBJECTS = $(am_tools_create_image_OBJECTS) tools_create_image_LDADD = $(LDADD) am__tools_eddystone_SOURCES_DIST = tools/eddystone.c monitor/bt.h @TOOLS_TRUE@am_tools_eddystone_OBJECTS = tools/eddystone.$(OBJEXT) tools_eddystone_OBJECTS = $(am_tools_eddystone_OBJECTS) @TOOLS_TRUE@tools_eddystone_DEPENDENCIES = src/libshared-mainloop.la am__tools_gap_tester_SOURCES_DIST = tools/gap-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c emulator/vhci.h \ emulator/vhci.c emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c emulator/smp.c @TESTING_TRUE@am_tools_gap_tester_OBJECTS = \ @TESTING_TRUE@ tools/gap-tester.$(OBJEXT) \ @TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ @TESTING_TRUE@ emulator/vhci.$(OBJEXT) emulator/btdev.$(OBJEXT) \ @TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) tools_gap_tester_OBJECTS = $(am_tools_gap_tester_OBJECTS) @TESTING_TRUE@tools_gap_tester_DEPENDENCIES = \ @TESTING_TRUE@ lib/libbluetooth-internal.la \ @TESTING_TRUE@ gdbus/libgdbus-internal.la src/libshared-glib.la \ @TESTING_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) am__tools_gatt_service_SOURCES_DIST = tools/gatt-service.c @TOOLS_TRUE@am_tools_gatt_service_OBJECTS = \ @TOOLS_TRUE@ tools/gatt-service.$(OBJEXT) tools_gatt_service_OBJECTS = $(am_tools_gatt_service_OBJECTS) @TOOLS_TRUE@tools_gatt_service_DEPENDENCIES = \ @TOOLS_TRUE@ gdbus/libgdbus-internal.la \ @TOOLS_TRUE@ src/libshared-mainloop.la $(am__DEPENDENCIES_1) \ @TOOLS_TRUE@ $(am__DEPENDENCIES_1) am__tools_hci_tester_SOURCES_DIST = tools/hci-tester.c monitor/bt.h @TESTING_TRUE@am_tools_hci_tester_OBJECTS = \ @TESTING_TRUE@ tools/hci-tester.$(OBJEXT) tools_hci_tester_OBJECTS = $(am_tools_hci_tester_OBJECTS) @TESTING_TRUE@tools_hci_tester_DEPENDENCIES = src/libshared-glib.la \ @TESTING_TRUE@ $(am__DEPENDENCIES_1) am__tools_hciattach_SOURCES_DIST = tools/hciattach.c tools/hciattach.h \ tools/hciattach_st.c tools/hciattach_ti.c \ tools/hciattach_tialt.c tools/hciattach_ath3k.c \ tools/hciattach_qualcomm.c tools/hciattach_intel.c \ tools/hciattach_bcm43xx.c @DEPRECATED_TRUE@@TOOLS_TRUE@am_tools_hciattach_OBJECTS = \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach.$(OBJEXT) \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_st.$(OBJEXT) \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_ti.$(OBJEXT) \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_tialt.$(OBJEXT) \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_ath3k.$(OBJEXT) \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_qualcomm.$(OBJEXT) \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_intel.$(OBJEXT) \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_bcm43xx.$(OBJEXT) tools_hciattach_OBJECTS = $(am_tools_hciattach_OBJECTS) @DEPRECATED_TRUE@@TOOLS_TRUE@tools_hciattach_DEPENDENCIES = \ @DEPRECATED_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la am__tools_hciconfig_SOURCES_DIST = tools/hciconfig.c @DEPRECATED_TRUE@@TOOLS_TRUE@am_tools_hciconfig_OBJECTS = \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciconfig.$(OBJEXT) tools_hciconfig_OBJECTS = $(am_tools_hciconfig_OBJECTS) @DEPRECATED_TRUE@@TOOLS_TRUE@tools_hciconfig_DEPENDENCIES = \ @DEPRECATED_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la tools_hcieventmask_SOURCES = tools/hcieventmask.c tools_hcieventmask_OBJECTS = tools/hcieventmask.$(OBJEXT) @TOOLS_TRUE@tools_hcieventmask_DEPENDENCIES = \ @TOOLS_TRUE@ lib/libbluetooth-internal.la tools_hcisecfilter_SOURCES = tools/hcisecfilter.c tools_hcisecfilter_OBJECTS = tools/hcisecfilter.$(OBJEXT) tools_hcisecfilter_LDADD = $(LDADD) am__tools_hcitool_SOURCES_DIST = tools/hcitool.c src/oui.h src/oui.c @DEPRECATED_TRUE@@TOOLS_TRUE@am_tools_hcitool_OBJECTS = \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hcitool.$(OBJEXT) \ @DEPRECATED_TRUE@@TOOLS_TRUE@ src/oui.$(OBJEXT) tools_hcitool_OBJECTS = $(am_tools_hcitool_OBJECTS) @DEPRECATED_TRUE@@TOOLS_TRUE@tools_hcitool_DEPENDENCIES = \ @DEPRECATED_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la \ @DEPRECATED_TRUE@@TOOLS_TRUE@ $(am__DEPENDENCIES_1) am__tools_hex2hcd_SOURCES_DIST = tools/hex2hcd.c tools/missing.h @TOOLS_TRUE@am_tools_hex2hcd_OBJECTS = tools/hex2hcd.$(OBJEXT) tools_hex2hcd_OBJECTS = $(am_tools_hex2hcd_OBJECTS) tools_hex2hcd_LDADD = $(LDADD) tools_hid2hci_SOURCES = tools/hid2hci.c tools_hid2hci_OBJECTS = tools/hid2hci.$(OBJEXT) @HID2HCI_TRUE@tools_hid2hci_DEPENDENCIES = $(am__DEPENDENCIES_1) tools_hwdb_SOURCES = tools/hwdb.c tools_hwdb_OBJECTS = tools/hwdb.$(OBJEXT) @TOOLS_TRUE@tools_hwdb_DEPENDENCIES = lib/libbluetooth-internal.la am__tools_ibeacon_SOURCES_DIST = tools/ibeacon.c monitor/bt.h @TOOLS_TRUE@am_tools_ibeacon_OBJECTS = tools/ibeacon.$(OBJEXT) tools_ibeacon_OBJECTS = $(am_tools_ibeacon_OBJECTS) @TOOLS_TRUE@tools_ibeacon_DEPENDENCIES = src/libshared-mainloop.la am__tools_ioctl_tester_SOURCES_DIST = tools/ioctl-tester.c \ monitor/bt.h emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c emulator/btdev.h \ emulator/btdev.c emulator/bthost.h emulator/bthost.c \ emulator/smp.c @TESTING_TRUE@am_tools_ioctl_tester_OBJECTS = \ @TESTING_TRUE@ tools/ioctl-tester.$(OBJEXT) \ @TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ @TESTING_TRUE@ emulator/vhci.$(OBJEXT) emulator/btdev.$(OBJEXT) \ @TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) tools_ioctl_tester_OBJECTS = $(am_tools_ioctl_tester_OBJECTS) @TESTING_TRUE@tools_ioctl_tester_DEPENDENCIES = \ @TESTING_TRUE@ lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) am__tools_iso_tester_SOURCES_DIST = tools/iso-tester.c tools/tester.h \ monitor/bt.h emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c emulator/btdev.h \ emulator/btdev.c emulator/bthost.h emulator/bthost.c \ emulator/smp.c @TESTING_TRUE@am_tools_iso_tester_OBJECTS = \ @TESTING_TRUE@ tools/iso-tester.$(OBJEXT) \ @TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ @TESTING_TRUE@ emulator/vhci.$(OBJEXT) emulator/btdev.$(OBJEXT) \ @TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) tools_iso_tester_OBJECTS = $(am_tools_iso_tester_OBJECTS) @TESTING_TRUE@tools_iso_tester_DEPENDENCIES = \ @TESTING_TRUE@ lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) tools_isotest_SOURCES = tools/isotest.c tools_isotest_OBJECTS = tools/isotest.$(OBJEXT) @TOOLS_TRUE@tools_isotest_DEPENDENCIES = lib/libbluetooth-internal.la am__tools_l2cap_tester_SOURCES_DIST = tools/l2cap-tester.c \ tools/tester.h monitor/bt.h emulator/hciemu.h \ emulator/hciemu.c emulator/vhci.h emulator/vhci.c \ emulator/btdev.h emulator/btdev.c emulator/bthost.h \ emulator/bthost.c emulator/smp.c @TESTING_TRUE@am_tools_l2cap_tester_OBJECTS = \ @TESTING_TRUE@ tools/l2cap-tester.$(OBJEXT) \ @TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ @TESTING_TRUE@ emulator/vhci.$(OBJEXT) emulator/btdev.$(OBJEXT) \ @TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) tools_l2cap_tester_OBJECTS = $(am_tools_l2cap_tester_OBJECTS) @TESTING_TRUE@tools_l2cap_tester_DEPENDENCIES = \ @TESTING_TRUE@ lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) tools_l2ping_SOURCES = tools/l2ping.c tools_l2ping_OBJECTS = tools/l2ping.$(OBJEXT) @TOOLS_TRUE@tools_l2ping_DEPENDENCIES = lib/libbluetooth-internal.la tools_l2test_SOURCES = tools/l2test.c tools_l2test_OBJECTS = tools/l2test.$(OBJEXT) @TOOLS_TRUE@tools_l2test_DEPENDENCIES = lib/libbluetooth-internal.la am__tools_mesh_cfgclient_SOURCES_DIST = tools/mesh-cfgclient.c \ tools/mesh/model.h tools/mesh/config-model.h \ tools/mesh/cfgcli.h tools/mesh/cfgcli.c tools/mesh/keys.h \ tools/mesh/keys.c tools/mesh/util.h tools/mesh/util.c \ tools/mesh/remote.h tools/mesh/remote.c tools/mesh/agent.h \ tools/mesh/agent.c tools/mesh/mesh-db.h tools/mesh/mesh-db.c \ mesh/util.h mesh/util.c mesh/crypto.h mesh/crypto.c @MESH_TRUE@@TOOLS_TRUE@am_tools_mesh_cfgclient_OBJECTS = \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh-cfgclient.$(OBJEXT) \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh/cfgcli.$(OBJEXT) \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh/keys.$(OBJEXT) \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh/util.$(OBJEXT) \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh/remote.$(OBJEXT) \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh/agent.$(OBJEXT) \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh/mesh-db.$(OBJEXT) \ @MESH_TRUE@@TOOLS_TRUE@ mesh/util.$(OBJEXT) \ @MESH_TRUE@@TOOLS_TRUE@ mesh/crypto.$(OBJEXT) tools_mesh_cfgclient_OBJECTS = $(am_tools_mesh_cfgclient_OBJECTS) @MESH_TRUE@@TOOLS_TRUE@tools_mesh_cfgclient_DEPENDENCIES = \ @MESH_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la \ @MESH_TRUE@@TOOLS_TRUE@ src/libshared-ell.la \ @MESH_TRUE@@TOOLS_TRUE@ $(am__DEPENDENCIES_2) am__tools_mesh_cfgtest_SOURCES_DIST = tools/mesh-cfgtest.c @MESH_TRUE@@TOOLS_TRUE@am_tools_mesh_cfgtest_OBJECTS = \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh-cfgtest.$(OBJEXT) tools_mesh_cfgtest_OBJECTS = $(am_tools_mesh_cfgtest_OBJECTS) @MESH_TRUE@@TOOLS_TRUE@tools_mesh_cfgtest_DEPENDENCIES = \ @MESH_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la \ @MESH_TRUE@@TOOLS_TRUE@ src/libshared-ell.la \ @MESH_TRUE@@TOOLS_TRUE@ $(am__DEPENDENCIES_2) am__tools_mesh_tester_SOURCES_DIST = tools/mesh-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c emulator/vhci.h \ emulator/vhci.c emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c emulator/smp.c @TESTING_TRUE@am_tools_mesh_tester_OBJECTS = \ @TESTING_TRUE@ tools/mesh-tester.$(OBJEXT) \ @TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ @TESTING_TRUE@ emulator/vhci.$(OBJEXT) emulator/btdev.$(OBJEXT) \ @TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) tools_mesh_tester_OBJECTS = $(am_tools_mesh_tester_OBJECTS) @TESTING_TRUE@tools_mesh_tester_DEPENDENCIES = \ @TESTING_TRUE@ lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) am__tools_meshctl_SOURCES_DIST = tools/meshctl.c tools/mesh/agent.h \ tools/mesh/agent.c tools/mesh/config-model.h \ tools/mesh-gatt/mesh-net.h tools/mesh-gatt/node.h \ tools/mesh-gatt/node.c tools/mesh-gatt/gatt.h \ tools/mesh-gatt/gatt.c tools/mesh-gatt/crypto.h \ tools/mesh-gatt/crypto.c tools/mesh-gatt/keys.h \ tools/mesh-gatt/net.h tools/mesh-gatt/net.c \ tools/mesh-gatt/prov.h tools/mesh-gatt/prov.c \ tools/mesh-gatt/util.h tools/mesh-gatt/util.c \ tools/mesh-gatt/prov-db.h tools/mesh-gatt/prov-db.c \ tools/mesh-gatt/config-client.c \ tools/mesh-gatt/config-server.c tools/mesh-gatt/onoff-model.h \ tools/mesh-gatt/onoff-model.c @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@am_tools_meshctl_OBJECTS = tools/meshctl.$(OBJEXT) \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/agent.$(OBJEXT) \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/node.$(OBJEXT) \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/gatt.$(OBJEXT) \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/crypto.$(OBJEXT) \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/net.$(OBJEXT) \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/prov.$(OBJEXT) \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/util.$(OBJEXT) \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/prov-db.$(OBJEXT) \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/config-client.$(OBJEXT) \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/config-server.$(OBJEXT) \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/onoff-model.$(OBJEXT) tools_meshctl_OBJECTS = $(am_tools_meshctl_OBJECTS) @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@tools_meshctl_DEPENDENCIES = gdbus/libgdbus-internal.la \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ src/libshared-glib.la \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ $(am__DEPENDENCIES_1) \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ $(am__DEPENDENCIES_1) am__tools_mgmt_tester_SOURCES_DIST = tools/mgmt-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c emulator/vhci.h \ emulator/vhci.c emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c emulator/smp.c @TESTING_TRUE@am_tools_mgmt_tester_OBJECTS = \ @TESTING_TRUE@ tools/mgmt-tester.$(OBJEXT) \ @TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ @TESTING_TRUE@ emulator/vhci.$(OBJEXT) emulator/btdev.$(OBJEXT) \ @TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) tools_mgmt_tester_OBJECTS = $(am_tools_mgmt_tester_OBJECTS) @TESTING_TRUE@tools_mgmt_tester_DEPENDENCIES = \ @TESTING_TRUE@ lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) am__tools_mpris_proxy_SOURCES_DIST = tools/mpris-proxy.c @TOOLS_TRUE@am_tools_mpris_proxy_OBJECTS = \ @TOOLS_TRUE@ tools/mpris-proxy.$(OBJEXT) tools_mpris_proxy_OBJECTS = $(am_tools_mpris_proxy_OBJECTS) @TOOLS_TRUE@tools_mpris_proxy_DEPENDENCIES = \ @TOOLS_TRUE@ gdbus/libgdbus-internal.la $(am__DEPENDENCIES_1) \ @TOOLS_TRUE@ $(am__DEPENDENCIES_1) am__tools_nokfw_SOURCES_DIST = tools/nokfw.c @TOOLS_TRUE@am_tools_nokfw_OBJECTS = tools/nokfw.$(OBJEXT) tools_nokfw_OBJECTS = $(am_tools_nokfw_OBJECTS) tools_nokfw_LDADD = $(LDADD) am__tools_obex_client_tool_SOURCES_DIST = gobex/gobex.h gobex/gobex.c \ gobex/gobex-defs.h gobex/gobex-defs.c gobex/gobex-packet.c \ gobex/gobex-packet.h gobex/gobex-header.c gobex/gobex-header.h \ gobex/gobex-transfer.c gobex/gobex-debug.h \ gobex/gobex-apparam.c gobex/gobex-apparam.h btio/btio.h \ btio/btio.c tools/obex-client-tool.c am__objects_41 = gobex/gobex.$(OBJEXT) gobex/gobex-defs.$(OBJEXT) \ gobex/gobex-packet.$(OBJEXT) gobex/gobex-header.$(OBJEXT) \ gobex/gobex-transfer.$(OBJEXT) gobex/gobex-apparam.$(OBJEXT) am__objects_42 = btio/btio.$(OBJEXT) @READLINE_TRUE@am_tools_obex_client_tool_OBJECTS = $(am__objects_41) \ @READLINE_TRUE@ $(am__objects_42) \ @READLINE_TRUE@ tools/obex-client-tool.$(OBJEXT) tools_obex_client_tool_OBJECTS = $(am_tools_obex_client_tool_OBJECTS) @READLINE_TRUE@tools_obex_client_tool_DEPENDENCIES = \ @READLINE_TRUE@ lib/libbluetooth-internal.la \ @READLINE_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) am__tools_obex_server_tool_SOURCES_DIST = gobex/gobex.h gobex/gobex.c \ gobex/gobex-defs.h gobex/gobex-defs.c gobex/gobex-packet.c \ gobex/gobex-packet.h gobex/gobex-header.c gobex/gobex-header.h \ gobex/gobex-transfer.c gobex/gobex-debug.h \ gobex/gobex-apparam.c gobex/gobex-apparam.h btio/btio.h \ btio/btio.c tools/obex-server-tool.c @READLINE_TRUE@am_tools_obex_server_tool_OBJECTS = $(am__objects_41) \ @READLINE_TRUE@ $(am__objects_42) \ @READLINE_TRUE@ tools/obex-server-tool.$(OBJEXT) tools_obex_server_tool_OBJECTS = $(am_tools_obex_server_tool_OBJECTS) @READLINE_TRUE@tools_obex_server_tool_DEPENDENCIES = \ @READLINE_TRUE@ lib/libbluetooth-internal.la \ @READLINE_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) am__tools_obexctl_SOURCES_DIST = tools/obexctl.c @READLINE_TRUE@am_tools_obexctl_OBJECTS = tools/obexctl.$(OBJEXT) tools_obexctl_OBJECTS = $(am_tools_obexctl_OBJECTS) @READLINE_TRUE@tools_obexctl_DEPENDENCIES = \ @READLINE_TRUE@ gdbus/libgdbus-internal.la \ @READLINE_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) \ @READLINE_TRUE@ $(am__DEPENDENCIES_1) am__tools_oobtest_SOURCES_DIST = tools/oobtest.c @TOOLS_TRUE@am_tools_oobtest_OBJECTS = tools/oobtest.$(OBJEXT) tools_oobtest_OBJECTS = $(am_tools_oobtest_OBJECTS) @TOOLS_TRUE@tools_oobtest_DEPENDENCIES = lib/libbluetooth-internal.la \ @TOOLS_TRUE@ src/libshared-mainloop.la tools_rctest_SOURCES = tools/rctest.c tools_rctest_OBJECTS = tools/rctest.$(OBJEXT) @TOOLS_TRUE@tools_rctest_DEPENDENCIES = lib/libbluetooth-internal.la tools_rfcomm_SOURCES = tools/rfcomm.c tools_rfcomm_OBJECTS = tools/rfcomm.$(OBJEXT) @DEPRECATED_TRUE@@TOOLS_TRUE@tools_rfcomm_DEPENDENCIES = \ @DEPRECATED_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la am__tools_rfcomm_tester_SOURCES_DIST = tools/rfcomm-tester.c \ monitor/bt.h emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c emulator/btdev.h \ emulator/btdev.c emulator/bthost.h emulator/bthost.c \ emulator/smp.c @TESTING_TRUE@am_tools_rfcomm_tester_OBJECTS = \ @TESTING_TRUE@ tools/rfcomm-tester.$(OBJEXT) \ @TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ @TESTING_TRUE@ emulator/vhci.$(OBJEXT) emulator/btdev.$(OBJEXT) \ @TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) tools_rfcomm_tester_OBJECTS = $(am_tools_rfcomm_tester_OBJECTS) @TESTING_TRUE@tools_rfcomm_tester_DEPENDENCIES = \ @TESTING_TRUE@ lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) am__tools_rtlfw_SOURCES_DIST = tools/rtlfw.c @TOOLS_TRUE@am_tools_rtlfw_OBJECTS = tools/rtlfw.$(OBJEXT) tools_rtlfw_OBJECTS = $(am_tools_rtlfw_OBJECTS) tools_rtlfw_LDADD = $(LDADD) am__tools_sco_tester_SOURCES_DIST = tools/sco-tester.c tools/tester.h \ monitor/bt.h emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c emulator/btdev.h \ emulator/btdev.c emulator/bthost.h emulator/bthost.c \ emulator/smp.c @TESTING_TRUE@am_tools_sco_tester_OBJECTS = \ @TESTING_TRUE@ tools/sco-tester.$(OBJEXT) \ @TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ @TESTING_TRUE@ emulator/vhci.$(OBJEXT) emulator/btdev.$(OBJEXT) \ @TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) tools_sco_tester_OBJECTS = $(am_tools_sco_tester_OBJECTS) @TESTING_TRUE@tools_sco_tester_DEPENDENCIES = \ @TESTING_TRUE@ lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) tools_scotest_SOURCES = tools/scotest.c tools_scotest_OBJECTS = tools/scotest.$(OBJEXT) @TOOLS_TRUE@tools_scotest_DEPENDENCIES = lib/libbluetooth-internal.la am__tools_sdptool_SOURCES_DIST = tools/sdptool.c src/sdp-xml.h \ src/sdp-xml.c @DEPRECATED_TRUE@@TOOLS_TRUE@am_tools_sdptool_OBJECTS = \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/sdptool.$(OBJEXT) \ @DEPRECATED_TRUE@@TOOLS_TRUE@ src/sdp-xml.$(OBJEXT) tools_sdptool_OBJECTS = $(am_tools_sdptool_OBJECTS) @DEPRECATED_TRUE@@TOOLS_TRUE@tools_sdptool_DEPENDENCIES = \ @DEPRECATED_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la \ @DEPRECATED_TRUE@@TOOLS_TRUE@ $(am__DEPENDENCIES_1) am__tools_seq2bseq_SOURCES_DIST = tools/seq2bseq.c @TOOLS_TRUE@am_tools_seq2bseq_OBJECTS = tools/seq2bseq.$(OBJEXT) tools_seq2bseq_OBJECTS = $(am_tools_seq2bseq_OBJECTS) tools_seq2bseq_LDADD = $(LDADD) am__tools_smp_tester_SOURCES_DIST = tools/smp-tester.c monitor/bt.h \ emulator/hciemu.h emulator/hciemu.c emulator/vhci.h \ emulator/vhci.c emulator/btdev.h emulator/btdev.c \ emulator/bthost.h emulator/bthost.c emulator/smp.c @TESTING_TRUE@am_tools_smp_tester_OBJECTS = \ @TESTING_TRUE@ tools/smp-tester.$(OBJEXT) \ @TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ @TESTING_TRUE@ emulator/vhci.$(OBJEXT) emulator/btdev.$(OBJEXT) \ @TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) tools_smp_tester_OBJECTS = $(am_tools_smp_tester_OBJECTS) @TESTING_TRUE@tools_smp_tester_DEPENDENCIES = \ @TESTING_TRUE@ lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) tools_test_runner_SOURCES = tools/test-runner.c tools_test_runner_OBJECTS = tools/test_runner-test-runner.$(OBJEXT) tools_test_runner_LDADD = $(LDADD) tools_test_runner_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tools_test_runner_CFLAGS) $(CFLAGS) \ $(tools_test_runner_LDFLAGS) $(LDFLAGS) -o $@ am__tools_userchan_tester_SOURCES_DIST = tools/userchan-tester.c \ monitor/bt.h emulator/hciemu.h emulator/hciemu.c \ emulator/vhci.h emulator/vhci.c emulator/btdev.h \ emulator/btdev.c emulator/bthost.h emulator/bthost.c \ emulator/smp.c @TESTING_TRUE@am_tools_userchan_tester_OBJECTS = \ @TESTING_TRUE@ tools/userchan-tester.$(OBJEXT) \ @TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ @TESTING_TRUE@ emulator/vhci.$(OBJEXT) emulator/btdev.$(OBJEXT) \ @TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) tools_userchan_tester_OBJECTS = $(am_tools_userchan_tester_OBJECTS) @TESTING_TRUE@tools_userchan_tester_DEPENDENCIES = \ @TESTING_TRUE@ lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) am_unit_test_avctp_OBJECTS = unit/test-avctp.$(OBJEXT) \ src/log.$(OBJEXT) unit/avctp.$(OBJEXT) unit_test_avctp_OBJECTS = $(am_unit_test_avctp_OBJECTS) unit_test_avctp_DEPENDENCIES = src/libshared-glib.la \ $(am__DEPENDENCIES_1) am_unit_test_avdtp_OBJECTS = unit/test-avdtp.$(OBJEXT) \ src/log.$(OBJEXT) unit/avdtp.$(OBJEXT) unit_test_avdtp_OBJECTS = $(am_unit_test_avdtp_OBJECTS) unit_test_avdtp_DEPENDENCIES = src/libshared-glib.la \ $(am__DEPENDENCIES_1) am_unit_test_avrcp_OBJECTS = unit/test-avrcp.$(OBJEXT) \ src/log.$(OBJEXT) unit/avctp.$(OBJEXT) \ unit/avrcp-lib.$(OBJEXT) unit_test_avrcp_OBJECTS = $(am_unit_test_avrcp_OBJECTS) unit_test_avrcp_DEPENDENCIES = lib/libbluetooth-internal.la \ src/libshared-glib.la $(am__DEPENDENCIES_1) am_unit_test_bap_OBJECTS = unit/test-bap.$(OBJEXT) unit_test_bap_OBJECTS = $(am_unit_test_bap_OBJECTS) unit_test_bap_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am_unit_test_bass_OBJECTS = unit/test-bass.$(OBJEXT) $(am__objects_42) unit_test_bass_OBJECTS = $(am_unit_test_bass_OBJECTS) unit_test_bass_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am_unit_test_battery_OBJECTS = unit/test-battery.$(OBJEXT) unit_test_battery_OBJECTS = $(am_unit_test_battery_OBJECTS) unit_test_battery_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am_unit_test_crc_OBJECTS = unit/test-crc.$(OBJEXT) \ monitor/crc.$(OBJEXT) unit_test_crc_OBJECTS = $(am_unit_test_crc_OBJECTS) unit_test_crc_DEPENDENCIES = src/libshared-glib.la \ $(am__DEPENDENCIES_1) am_unit_test_crypto_OBJECTS = unit/test-crypto.$(OBJEXT) unit_test_crypto_OBJECTS = $(am_unit_test_crypto_OBJECTS) unit_test_crypto_DEPENDENCIES = src/libshared-glib.la \ $(am__DEPENDENCIES_1) am_unit_test_ecc_OBJECTS = unit/test-ecc.$(OBJEXT) unit_test_ecc_OBJECTS = $(am_unit_test_ecc_OBJECTS) unit_test_ecc_DEPENDENCIES = src/libshared-glib.la \ $(am__DEPENDENCIES_1) am_unit_test_eir_OBJECTS = unit/test-eir.$(OBJEXT) src/eir.$(OBJEXT) \ src/uuid-helper.$(OBJEXT) unit_test_eir_OBJECTS = $(am_unit_test_eir_OBJECTS) unit_test_eir_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am_unit_test_gatt_OBJECTS = unit/test-gatt.$(OBJEXT) unit_test_gatt_OBJECTS = $(am_unit_test_gatt_OBJECTS) unit_test_gatt_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am_unit_test_gattrib_OBJECTS = unit/test-gattrib.$(OBJEXT) \ attrib/gattrib.$(OBJEXT) $(am__objects_42) src/log.$(OBJEXT) unit_test_gattrib_OBJECTS = $(am_unit_test_gattrib_OBJECTS) unit_test_gattrib_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) am_unit_test_gdbus_client_OBJECTS = unit/test-gdbus-client.$(OBJEXT) unit_test_gdbus_client_OBJECTS = $(am_unit_test_gdbus_client_OBJECTS) unit_test_gdbus_client_DEPENDENCIES = gdbus/libgdbus-internal.la \ src/libshared-glib.la $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) am_unit_test_gmap_OBJECTS = unit/test-gmap.$(OBJEXT) $(am__objects_42) unit_test_gmap_OBJECTS = $(am_unit_test_gmap_OBJECTS) unit_test_gmap_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am__unit_test_gobex_SOURCES_DIST = gobex/gobex.h gobex/gobex.c \ gobex/gobex-defs.h gobex/gobex-defs.c gobex/gobex-packet.c \ gobex/gobex-packet.h gobex/gobex-header.c gobex/gobex-header.h \ gobex/gobex-transfer.c gobex/gobex-debug.h \ gobex/gobex-apparam.c gobex/gobex-apparam.h unit/util.c \ unit/util.h unit/test-gobex.c @OBEX_TRUE@am_unit_test_gobex_OBJECTS = $(am__objects_41) \ @OBEX_TRUE@ unit/util.$(OBJEXT) unit/test-gobex.$(OBJEXT) unit_test_gobex_OBJECTS = $(am_unit_test_gobex_OBJECTS) @OBEX_TRUE@unit_test_gobex_DEPENDENCIES = src/libshared-glib.la \ @OBEX_TRUE@ $(am__DEPENDENCIES_1) am__unit_test_gobex_apparam_SOURCES_DIST = gobex/gobex.h gobex/gobex.c \ gobex/gobex-defs.h gobex/gobex-defs.c gobex/gobex-packet.c \ gobex/gobex-packet.h gobex/gobex-header.c gobex/gobex-header.h \ gobex/gobex-transfer.c gobex/gobex-debug.h \ gobex/gobex-apparam.c gobex/gobex-apparam.h unit/util.c \ unit/util.h unit/test-gobex-apparam.c @OBEX_TRUE@am_unit_test_gobex_apparam_OBJECTS = $(am__objects_41) \ @OBEX_TRUE@ unit/util.$(OBJEXT) \ @OBEX_TRUE@ unit/test-gobex-apparam.$(OBJEXT) unit_test_gobex_apparam_OBJECTS = \ $(am_unit_test_gobex_apparam_OBJECTS) @OBEX_TRUE@unit_test_gobex_apparam_DEPENDENCIES = \ @OBEX_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) am__unit_test_gobex_header_SOURCES_DIST = gobex/gobex.h gobex/gobex.c \ gobex/gobex-defs.h gobex/gobex-defs.c gobex/gobex-packet.c \ gobex/gobex-packet.h gobex/gobex-header.c gobex/gobex-header.h \ gobex/gobex-transfer.c gobex/gobex-debug.h \ gobex/gobex-apparam.c gobex/gobex-apparam.h unit/util.c \ unit/util.h unit/test-gobex-header.c @OBEX_TRUE@am_unit_test_gobex_header_OBJECTS = $(am__objects_41) \ @OBEX_TRUE@ unit/util.$(OBJEXT) \ @OBEX_TRUE@ unit/test-gobex-header.$(OBJEXT) unit_test_gobex_header_OBJECTS = $(am_unit_test_gobex_header_OBJECTS) @OBEX_TRUE@unit_test_gobex_header_DEPENDENCIES = \ @OBEX_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) am__unit_test_gobex_packet_SOURCES_DIST = gobex/gobex.h gobex/gobex.c \ gobex/gobex-defs.h gobex/gobex-defs.c gobex/gobex-packet.c \ gobex/gobex-packet.h gobex/gobex-header.c gobex/gobex-header.h \ gobex/gobex-transfer.c gobex/gobex-debug.h \ gobex/gobex-apparam.c gobex/gobex-apparam.h unit/util.c \ unit/util.h unit/test-gobex-packet.c @OBEX_TRUE@am_unit_test_gobex_packet_OBJECTS = $(am__objects_41) \ @OBEX_TRUE@ unit/util.$(OBJEXT) \ @OBEX_TRUE@ unit/test-gobex-packet.$(OBJEXT) unit_test_gobex_packet_OBJECTS = $(am_unit_test_gobex_packet_OBJECTS) @OBEX_TRUE@unit_test_gobex_packet_DEPENDENCIES = \ @OBEX_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) am__unit_test_gobex_transfer_SOURCES_DIST = gobex/gobex.h \ gobex/gobex.c gobex/gobex-defs.h gobex/gobex-defs.c \ gobex/gobex-packet.c gobex/gobex-packet.h gobex/gobex-header.c \ gobex/gobex-header.h gobex/gobex-transfer.c \ gobex/gobex-debug.h gobex/gobex-apparam.c \ gobex/gobex-apparam.h unit/util.c unit/util.h \ unit/test-gobex-transfer.c @OBEX_TRUE@am_unit_test_gobex_transfer_OBJECTS = $(am__objects_41) \ @OBEX_TRUE@ unit/util.$(OBJEXT) \ @OBEX_TRUE@ unit/test-gobex-transfer.$(OBJEXT) unit_test_gobex_transfer_OBJECTS = \ $(am_unit_test_gobex_transfer_OBJECTS) @OBEX_TRUE@unit_test_gobex_transfer_DEPENDENCIES = \ @OBEX_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) am_unit_test_hfp_OBJECTS = unit/test-hfp.$(OBJEXT) unit_test_hfp_OBJECTS = $(am_unit_test_hfp_OBJECTS) unit_test_hfp_DEPENDENCIES = src/libshared-glib.la \ $(am__DEPENDENCIES_1) am_unit_test_hog_OBJECTS = unit/test-hog.$(OBJEXT) $(am__objects_42) \ profiles/input/hog-lib.$(OBJEXT) \ profiles/scanparam/scpp.$(OBJEXT) \ profiles/battery/bas.$(OBJEXT) \ profiles/deviceinfo/dis.$(OBJEXT) src/log.$(OBJEXT) \ attrib/att.$(OBJEXT) attrib/gatt.$(OBJEXT) \ attrib/gattrib.$(OBJEXT) unit_test_hog_OBJECTS = $(am_unit_test_hog_OBJECTS) unit_test_hog_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am_unit_test_lib_OBJECTS = unit/test-lib.$(OBJEXT) unit_test_lib_OBJECTS = $(am_unit_test_lib_OBJECTS) unit_test_lib_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am_unit_test_mcp_OBJECTS = unit/test-mcp.$(OBJEXT) $(am__objects_42) unit_test_mcp_OBJECTS = $(am_unit_test_mcp_OBJECTS) unit_test_mcp_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am__unit_test_mesh_crypto_SOURCES_DIST = unit/test-mesh-crypto.c \ mesh/crypto.h ell/internal ell/ell.h @MESH_TRUE@am_unit_test_mesh_crypto_OBJECTS = \ @MESH_TRUE@ unit/test_mesh_crypto-test-mesh-crypto.$(OBJEXT) unit_test_mesh_crypto_OBJECTS = $(am_unit_test_mesh_crypto_OBJECTS) @MESH_TRUE@unit_test_mesh_crypto_DEPENDENCIES = $(am__DEPENDENCIES_2) am_unit_test_mgmt_OBJECTS = unit/test-mgmt.$(OBJEXT) unit_test_mgmt_OBJECTS = $(am_unit_test_mgmt_OBJECTS) unit_test_mgmt_DEPENDENCIES = src/libshared-glib.la \ $(am__DEPENDENCIES_1) am_unit_test_micp_OBJECTS = unit/test-micp.$(OBJEXT) unit_test_micp_OBJECTS = $(am_unit_test_micp_OBJECTS) unit_test_micp_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am__unit_test_midi_SOURCES_DIST = unit/test-midi.c \ profiles/midi/libmidi.h profiles/midi/libmidi.c @MIDI_TRUE@am_unit_test_midi_OBJECTS = \ @MIDI_TRUE@ unit/test_midi-test-midi.$(OBJEXT) \ @MIDI_TRUE@ profiles/midi/unit_test_midi-libmidi.$(OBJEXT) unit_test_midi_OBJECTS = $(am_unit_test_midi_OBJECTS) @MIDI_TRUE@unit_test_midi_DEPENDENCIES = src/libshared-glib.la \ @MIDI_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) am__objects_43 = attrib/att.$(OBJEXT) attrib/gatt.$(OBJEXT) \ attrib/gattrib.$(OBJEXT) am__objects_44 = $(am__objects_43) $(am__objects_42) src/log.$(OBJEXT) \ src/backtrace.$(OBJEXT) src/rfkill.$(OBJEXT) \ src/sdpd-server.$(OBJEXT) src/sdpd-request.$(OBJEXT) \ src/sdpd-service.$(OBJEXT) src/sdpd-database.$(OBJEXT) \ src/gatt-database.$(OBJEXT) src/sdp-xml.$(OBJEXT) \ src/sdp-client.$(OBJEXT) src/textfile.$(OBJEXT) \ src/uuid-helper.$(OBJEXT) src/storage.$(OBJEXT) \ src/advertising.$(OBJEXT) src/agent.$(OBJEXT) \ src/error.$(OBJEXT) src/adapter.$(OBJEXT) \ src/profile.$(OBJEXT) src/service.$(OBJEXT) \ src/gatt-client.$(OBJEXT) src/device.$(OBJEXT) \ src/dbus-common.$(OBJEXT) src/eir.$(OBJEXT) \ src/adv_monitor.$(OBJEXT) src/battery.$(OBJEXT) \ src/settings.$(OBJEXT) src/set.$(OBJEXT) src/bearer.$(OBJEXT) am_unit_test_profile_OBJECTS = unit/test-profile.$(OBJEXT) \ unit/btd.$(OBJEXT) $(am__objects_44) unit_test_profile_OBJECTS = $(am_unit_test_profile_OBJECTS) unit_test_profile_DEPENDENCIES = $(am__DEPENDENCIES_3) am_unit_test_queue_OBJECTS = unit/test-queue.$(OBJEXT) unit_test_queue_OBJECTS = $(am_unit_test_queue_OBJECTS) unit_test_queue_DEPENDENCIES = src/libshared-glib.la \ $(am__DEPENDENCIES_1) am_unit_test_rap_OBJECTS = unit/test-rap.$(OBJEXT) $(am__objects_42) unit_test_rap_OBJECTS = $(am_unit_test_rap_OBJECTS) unit_test_rap_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am_unit_test_ringbuf_OBJECTS = unit/test-ringbuf.$(OBJEXT) unit_test_ringbuf_OBJECTS = $(am_unit_test_ringbuf_OBJECTS) unit_test_ringbuf_DEPENDENCIES = src/libshared-glib.la \ $(am__DEPENDENCIES_1) am_unit_test_sdp_OBJECTS = unit/test-sdp.$(OBJEXT) \ src/sdpd-database.$(OBJEXT) src/log.$(OBJEXT) \ src/sdpd-service.$(OBJEXT) src/sdpd-request.$(OBJEXT) unit_test_sdp_OBJECTS = $(am_unit_test_sdp_OBJECTS) unit_test_sdp_DEPENDENCIES = lib/libbluetooth-internal.la \ src/libshared-glib.la $(am__DEPENDENCIES_1) am_unit_test_tester_OBJECTS = unit/test-tester.$(OBJEXT) unit_test_tester_OBJECTS = $(am_unit_test_tester_OBJECTS) unit_test_tester_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am_unit_test_textfile_OBJECTS = unit/test-textfile.$(OBJEXT) \ src/textfile.$(OBJEXT) unit_test_textfile_OBJECTS = $(am_unit_test_textfile_OBJECTS) unit_test_textfile_DEPENDENCIES = src/libshared-glib.la \ $(am__DEPENDENCIES_1) am_unit_test_tmap_OBJECTS = unit/test-tmap.$(OBJEXT) $(am__objects_42) unit_test_tmap_OBJECTS = $(am_unit_test_tmap_OBJECTS) unit_test_tmap_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am_unit_test_uhid_OBJECTS = unit/test-uhid.$(OBJEXT) unit_test_uhid_OBJECTS = $(am_unit_test_uhid_OBJECTS) unit_test_uhid_DEPENDENCIES = src/libshared-glib.la \ $(am__DEPENDENCIES_1) unit_test_util_SOURCES = unit/test-util.c unit_test_util_OBJECTS = unit/test-util.$(OBJEXT) unit_test_util_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am_unit_test_uuid_OBJECTS = unit/test-uuid.$(OBJEXT) unit_test_uuid_OBJECTS = $(am_unit_test_uuid_OBJECTS) unit_test_uuid_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am_unit_test_vcp_OBJECTS = unit/test-vcp.$(OBJEXT) $(am__objects_42) unit_test_vcp_OBJECTS = $(am_unit_test_vcp_OBJECTS) unit_test_vcp_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) SCRIPTS = $(test_SCRIPTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = attrib/$(DEPDIR)/att.Po \ attrib/$(DEPDIR)/bluetoothd-att.Po \ attrib/$(DEPDIR)/bluetoothd-gatt.Po \ attrib/$(DEPDIR)/bluetoothd-gattrib.Po \ attrib/$(DEPDIR)/gatt.Po attrib/$(DEPDIR)/gattrib.Po \ attrib/$(DEPDIR)/gatttool.Po attrib/$(DEPDIR)/interactive.Po \ attrib/$(DEPDIR)/utils.Po btio/$(DEPDIR)/bluetoothd-btio.Po \ btio/$(DEPDIR)/btio.Po btio/$(DEPDIR)/obexd-btio.Po \ client/$(DEPDIR)/admin.Po client/$(DEPDIR)/adv_monitor.Po \ client/$(DEPDIR)/advertising.Po client/$(DEPDIR)/agent.Po \ client/$(DEPDIR)/assistant.Po client/$(DEPDIR)/display.Po \ client/$(DEPDIR)/gatt.Po client/$(DEPDIR)/hci.Po \ client/$(DEPDIR)/main.Po client/$(DEPDIR)/mgmt.Po \ client/$(DEPDIR)/player.Po client/$(DEPDIR)/print.Po \ client/$(DEPDIR)/telephony.Po \ client/btpclient/$(DEPDIR)/bap.Po \ client/btpclient/$(DEPDIR)/btpclient.Po \ client/btpclient/$(DEPDIR)/btpclientctl.Po \ client/btpclient/$(DEPDIR)/core.Po \ client/btpclient/$(DEPDIR)/gap.Po \ client/btpclient/$(DEPDIR)/gatt.Po ell/$(DEPDIR)/base64.Plo \ ell/$(DEPDIR)/cert-crypto.Plo ell/$(DEPDIR)/cert.Plo \ ell/$(DEPDIR)/checksum.Plo ell/$(DEPDIR)/cipher.Plo \ ell/$(DEPDIR)/dbus-client.Plo ell/$(DEPDIR)/dbus-filter.Plo \ ell/$(DEPDIR)/dbus-message.Plo \ ell/$(DEPDIR)/dbus-name-cache.Plo \ ell/$(DEPDIR)/dbus-service.Plo ell/$(DEPDIR)/dbus-util.Plo \ ell/$(DEPDIR)/dbus.Plo ell/$(DEPDIR)/ecc-external.Plo \ ell/$(DEPDIR)/ecc.Plo ell/$(DEPDIR)/ecdh.Plo \ ell/$(DEPDIR)/gvariant-util.Plo ell/$(DEPDIR)/hashmap.Plo \ ell/$(DEPDIR)/idle.Plo ell/$(DEPDIR)/io.Plo \ ell/$(DEPDIR)/key.Plo ell/$(DEPDIR)/log.Plo \ ell/$(DEPDIR)/main.Plo ell/$(DEPDIR)/pem.Plo \ ell/$(DEPDIR)/queue.Plo ell/$(DEPDIR)/random.Plo \ ell/$(DEPDIR)/settings.Plo ell/$(DEPDIR)/signal.Plo \ ell/$(DEPDIR)/siphash.Plo ell/$(DEPDIR)/string.Plo \ ell/$(DEPDIR)/strv.Plo ell/$(DEPDIR)/tester.Plo \ ell/$(DEPDIR)/time.Plo ell/$(DEPDIR)/timeout.Plo \ ell/$(DEPDIR)/tls-extensions.Plo ell/$(DEPDIR)/tls-record.Plo \ ell/$(DEPDIR)/tls-suites.Plo ell/$(DEPDIR)/tls.Plo \ ell/$(DEPDIR)/utf8.Plo ell/$(DEPDIR)/util.Plo \ ell/$(DEPDIR)/uuid.Plo emulator/$(DEPDIR)/b1ee.Po \ emulator/$(DEPDIR)/btdev.Po emulator/$(DEPDIR)/bthost.Po \ emulator/$(DEPDIR)/hciemu.Po emulator/$(DEPDIR)/hfp.Po \ emulator/$(DEPDIR)/le.Po emulator/$(DEPDIR)/main.Po \ emulator/$(DEPDIR)/phy.Po emulator/$(DEPDIR)/serial.Po \ emulator/$(DEPDIR)/server.Po emulator/$(DEPDIR)/smp.Po \ emulator/$(DEPDIR)/vhci.Po gdbus/$(DEPDIR)/client.Plo \ gdbus/$(DEPDIR)/mainloop.Plo gdbus/$(DEPDIR)/object.Plo \ gdbus/$(DEPDIR)/polkit.Plo gdbus/$(DEPDIR)/watch.Plo \ gobex/$(DEPDIR)/gobex-apparam.Po gobex/$(DEPDIR)/gobex-defs.Po \ gobex/$(DEPDIR)/gobex-header.Po \ gobex/$(DEPDIR)/gobex-packet.Po \ gobex/$(DEPDIR)/gobex-transfer.Po gobex/$(DEPDIR)/gobex.Po \ gobex/$(DEPDIR)/obexd-gobex-apparam.Po \ gobex/$(DEPDIR)/obexd-gobex-defs.Po \ gobex/$(DEPDIR)/obexd-gobex-header.Po \ gobex/$(DEPDIR)/obexd-gobex-packet.Po \ gobex/$(DEPDIR)/obexd-gobex-transfer.Po \ gobex/$(DEPDIR)/obexd-gobex.Po \ lib/bluetooth/$(DEPDIR)/bluetooth.Plo \ lib/bluetooth/$(DEPDIR)/hci.Plo \ lib/bluetooth/$(DEPDIR)/sdp.Plo \ lib/bluetooth/$(DEPDIR)/uuid.Plo mesh/$(DEPDIR)/agent.Po \ mesh/$(DEPDIR)/appkey.Po mesh/$(DEPDIR)/cfgmod-server.Po \ mesh/$(DEPDIR)/crypto.Po mesh/$(DEPDIR)/dbus.Po \ mesh/$(DEPDIR)/friend.Po mesh/$(DEPDIR)/keyring.Po \ mesh/$(DEPDIR)/main.Po mesh/$(DEPDIR)/manager.Po \ mesh/$(DEPDIR)/mesh-config-json.Po \ mesh/$(DEPDIR)/mesh-io-generic.Po \ mesh/$(DEPDIR)/mesh-io-mgmt.Po mesh/$(DEPDIR)/mesh-io-unit.Po \ mesh/$(DEPDIR)/mesh-io.Po mesh/$(DEPDIR)/mesh-mgmt.Po \ mesh/$(DEPDIR)/mesh.Po mesh/$(DEPDIR)/model.Po \ mesh/$(DEPDIR)/net-keys.Po mesh/$(DEPDIR)/net.Po \ mesh/$(DEPDIR)/node.Po mesh/$(DEPDIR)/pb-adv.Po \ mesh/$(DEPDIR)/prov-acceptor.Po \ mesh/$(DEPDIR)/prov-initiator.Po \ mesh/$(DEPDIR)/prvbeac-server.Po \ mesh/$(DEPDIR)/remprv-server.Po mesh/$(DEPDIR)/rpl.Po \ mesh/$(DEPDIR)/util.Po monitor/$(DEPDIR)/a2dp.Po \ monitor/$(DEPDIR)/analyze.Po monitor/$(DEPDIR)/att.Po \ monitor/$(DEPDIR)/avctp.Po monitor/$(DEPDIR)/avdtp.Po \ monitor/$(DEPDIR)/bnep.Po monitor/$(DEPDIR)/broadcom.Po \ monitor/$(DEPDIR)/control.Po monitor/$(DEPDIR)/crc.Po \ monitor/$(DEPDIR)/display.Po monitor/$(DEPDIR)/ellisys.Po \ monitor/$(DEPDIR)/hcidump.Po monitor/$(DEPDIR)/hwdb.Po \ monitor/$(DEPDIR)/intel.Po monitor/$(DEPDIR)/jlink.Po \ monitor/$(DEPDIR)/keys.Po monitor/$(DEPDIR)/l2cap.Po \ monitor/$(DEPDIR)/ll.Po monitor/$(DEPDIR)/lmp.Po \ monitor/$(DEPDIR)/main.Po monitor/$(DEPDIR)/msft.Po \ monitor/$(DEPDIR)/packet.Po monitor/$(DEPDIR)/rfcomm.Po \ monitor/$(DEPDIR)/sdp.Po monitor/$(DEPDIR)/vendor.Po \ obexd/client/$(DEPDIR)/obexd-bip-common.Po \ obexd/client/$(DEPDIR)/obexd-bip.Po \ obexd/client/$(DEPDIR)/obexd-bluetooth.Po \ obexd/client/$(DEPDIR)/obexd-driver.Po \ obexd/client/$(DEPDIR)/obexd-ftp.Po \ obexd/client/$(DEPDIR)/obexd-manager.Po \ obexd/client/$(DEPDIR)/obexd-map-event.Po \ obexd/client/$(DEPDIR)/obexd-map.Po \ obexd/client/$(DEPDIR)/obexd-mns.Po \ obexd/client/$(DEPDIR)/obexd-opp.Po \ obexd/client/$(DEPDIR)/obexd-pbap.Po \ obexd/client/$(DEPDIR)/obexd-session.Po \ obexd/client/$(DEPDIR)/obexd-sync.Po \ obexd/client/$(DEPDIR)/obexd-transfer.Po \ obexd/client/$(DEPDIR)/obexd-transport.Po \ obexd/plugins/$(DEPDIR)/obexd-bluetooth.Po \ obexd/plugins/$(DEPDIR)/obexd-filesystem.Po \ obexd/plugins/$(DEPDIR)/obexd-ftp.Po \ obexd/plugins/$(DEPDIR)/obexd-irmc.Po \ obexd/plugins/$(DEPDIR)/obexd-mas.Po \ obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Po \ obexd/plugins/$(DEPDIR)/obexd-opp.Po \ obexd/plugins/$(DEPDIR)/obexd-pbap.Po \ obexd/plugins/$(DEPDIR)/obexd-pcsuite.Po \ obexd/plugins/$(DEPDIR)/obexd-phonebook-@PLUGIN_PHONEBOOK@.Po \ obexd/plugins/$(DEPDIR)/obexd-vcard.Po \ obexd/src/$(DEPDIR)/obexd-log.Po \ obexd/src/$(DEPDIR)/obexd-logind.Po \ obexd/src/$(DEPDIR)/obexd-main.Po \ obexd/src/$(DEPDIR)/obexd-manager.Po \ obexd/src/$(DEPDIR)/obexd-mimetype.Po \ obexd/src/$(DEPDIR)/obexd-obex.Po \ obexd/src/$(DEPDIR)/obexd-plugin.Po \ obexd/src/$(DEPDIR)/obexd-server.Po \ obexd/src/$(DEPDIR)/obexd-service.Po \ obexd/src/$(DEPDIR)/obexd-transport.Po \ peripheral/$(DEPDIR)/attach.Po peripheral/$(DEPDIR)/efivars.Po \ peripheral/$(DEPDIR)/gap.Po peripheral/$(DEPDIR)/gatt.Po \ peripheral/$(DEPDIR)/log.Po peripheral/$(DEPDIR)/main.Po \ plugins/$(DEPDIR)/bluetoothd-admin.Po \ plugins/$(DEPDIR)/bluetoothd-autopair.Po \ plugins/$(DEPDIR)/bluetoothd-hostname.Po \ plugins/$(DEPDIR)/bluetoothd-neard.Po \ plugins/$(DEPDIR)/bluetoothd-policy.Po \ plugins/$(DEPDIR)/bluetoothd-sixaxis.Po \ profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Po \ profiles/audio/$(DEPDIR)/bluetoothd-asha.Po \ profiles/audio/$(DEPDIR)/bluetoothd-avctp.Po \ profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po \ profiles/audio/$(DEPDIR)/bluetoothd-avrcp-player.Po \ profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po \ profiles/audio/$(DEPDIR)/bluetoothd-bap.Po \ profiles/audio/$(DEPDIR)/bluetoothd-bass.Po \ profiles/audio/$(DEPDIR)/bluetoothd-ccp.Po \ profiles/audio/$(DEPDIR)/bluetoothd-control.Po \ profiles/audio/$(DEPDIR)/bluetoothd-csip.Po \ profiles/audio/$(DEPDIR)/bluetoothd-gmap.Po \ profiles/audio/$(DEPDIR)/bluetoothd-hfp-hf.Po \ profiles/audio/$(DEPDIR)/bluetoothd-mcp.Po \ profiles/audio/$(DEPDIR)/bluetoothd-media.Po \ profiles/audio/$(DEPDIR)/bluetoothd-micp.Po \ profiles/audio/$(DEPDIR)/bluetoothd-player.Po \ profiles/audio/$(DEPDIR)/bluetoothd-sink.Po \ profiles/audio/$(DEPDIR)/bluetoothd-source.Po \ profiles/audio/$(DEPDIR)/bluetoothd-telephony.Po \ profiles/audio/$(DEPDIR)/bluetoothd-tmap.Po \ profiles/audio/$(DEPDIR)/bluetoothd-transport.Po \ profiles/audio/$(DEPDIR)/bluetoothd-vcp.Po \ profiles/battery/$(DEPDIR)/bas.Po \ profiles/battery/$(DEPDIR)/bluetoothd-bas.Po \ profiles/battery/$(DEPDIR)/bluetoothd-battery.Po \ profiles/cups/$(DEPDIR)/hcrp.Po \ profiles/cups/$(DEPDIR)/main.Po profiles/cups/$(DEPDIR)/sdp.Po \ profiles/cups/$(DEPDIR)/spp.Po \ profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Po \ profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Po \ profiles/deviceinfo/$(DEPDIR)/dis.Po \ profiles/gap/$(DEPDIR)/bluetoothd-gas.Po \ profiles/iap/$(DEPDIR)/main.Po \ profiles/input/$(DEPDIR)/bluetoothd-device.Po \ profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Po \ profiles/input/$(DEPDIR)/bluetoothd-hog.Po \ profiles/input/$(DEPDIR)/bluetoothd-manager.Po \ profiles/input/$(DEPDIR)/bluetoothd-server.Po \ profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Po \ profiles/input/$(DEPDIR)/hog-lib.Po \ profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Po \ profiles/midi/$(DEPDIR)/bluetoothd-midi.Po \ profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Po \ profiles/network/$(DEPDIR)/bluetoothd-bnep.Po \ profiles/network/$(DEPDIR)/bluetoothd-connection.Po \ profiles/network/$(DEPDIR)/bluetoothd-manager.Po \ profiles/network/$(DEPDIR)/bluetoothd-server.Po \ profiles/network/$(DEPDIR)/bnep.Po \ profiles/ranging/$(DEPDIR)/bluetoothd-rap.Po \ profiles/ranging/$(DEPDIR)/bluetoothd-rap_hci.Po \ profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Po \ profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Po \ profiles/scanparam/$(DEPDIR)/scpp.Po src/$(DEPDIR)/adapter.Po \ src/$(DEPDIR)/adv_monitor.Po src/$(DEPDIR)/advertising.Po \ src/$(DEPDIR)/agent.Po src/$(DEPDIR)/backtrace.Po \ src/$(DEPDIR)/battery.Po src/$(DEPDIR)/bearer.Po \ src/$(DEPDIR)/bluetoothd-adapter.Po \ src/$(DEPDIR)/bluetoothd-adv_monitor.Po \ src/$(DEPDIR)/bluetoothd-advertising.Po \ src/$(DEPDIR)/bluetoothd-agent.Po \ src/$(DEPDIR)/bluetoothd-backtrace.Po \ src/$(DEPDIR)/bluetoothd-battery.Po \ src/$(DEPDIR)/bluetoothd-bearer.Po \ src/$(DEPDIR)/bluetoothd-dbus-common.Po \ src/$(DEPDIR)/bluetoothd-device.Po \ src/$(DEPDIR)/bluetoothd-eir.Po \ src/$(DEPDIR)/bluetoothd-error.Po \ src/$(DEPDIR)/bluetoothd-gatt-client.Po \ src/$(DEPDIR)/bluetoothd-gatt-database.Po \ src/$(DEPDIR)/bluetoothd-log.Po \ src/$(DEPDIR)/bluetoothd-main.Po \ src/$(DEPDIR)/bluetoothd-plugin.Po \ src/$(DEPDIR)/bluetoothd-profile.Po \ src/$(DEPDIR)/bluetoothd-rfkill.Po \ src/$(DEPDIR)/bluetoothd-sdp-client.Po \ src/$(DEPDIR)/bluetoothd-sdp-xml.Po \ src/$(DEPDIR)/bluetoothd-sdpd-database.Po \ src/$(DEPDIR)/bluetoothd-sdpd-request.Po \ src/$(DEPDIR)/bluetoothd-sdpd-server.Po \ src/$(DEPDIR)/bluetoothd-sdpd-service.Po \ src/$(DEPDIR)/bluetoothd-service.Po \ src/$(DEPDIR)/bluetoothd-set.Po \ src/$(DEPDIR)/bluetoothd-settings.Po \ src/$(DEPDIR)/bluetoothd-storage.Po \ src/$(DEPDIR)/bluetoothd-textfile.Po \ src/$(DEPDIR)/bluetoothd-uuid-helper.Po \ src/$(DEPDIR)/dbus-common.Po src/$(DEPDIR)/device.Po \ src/$(DEPDIR)/eir.Po src/$(DEPDIR)/error.Po \ src/$(DEPDIR)/gatt-client.Po src/$(DEPDIR)/gatt-database.Po \ src/$(DEPDIR)/log.Po src/$(DEPDIR)/oui.Po \ src/$(DEPDIR)/profile.Po src/$(DEPDIR)/rfkill.Po \ src/$(DEPDIR)/sdp-client.Po src/$(DEPDIR)/sdp-xml.Po \ src/$(DEPDIR)/sdpd-database.Po src/$(DEPDIR)/sdpd-request.Po \ src/$(DEPDIR)/sdpd-server.Po src/$(DEPDIR)/sdpd-service.Po \ src/$(DEPDIR)/service.Po src/$(DEPDIR)/set.Po \ src/$(DEPDIR)/settings.Po src/$(DEPDIR)/storage.Po \ src/$(DEPDIR)/textfile.Po src/$(DEPDIR)/uuid-helper.Po \ src/shared/$(DEPDIR)/btp.Po \ src/shared/$(DEPDIR)/libshared_ell_la-ad.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-asha.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-att.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-bap-debug.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-bap.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-bass.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-battery.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-btsnoop.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-ccp.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-crypto.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-csip.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-ecc.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-gap.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-gatt-client.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-gatt-db.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-gatt-helpers.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-gatt-server.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-gmap.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-hci-crypto.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-hci.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-hfp.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-io-ell.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-log.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-mainloop-ell.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-mcp.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-mgmt.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-micp.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-pcap.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-queue.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-rap.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-ringbuf.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-shell.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-timeout-ell.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-tmap.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-uhid.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-uinput.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-util.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-vcp.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-ad.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-asha.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-att.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-bap-debug.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-bap.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-bass.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-battery.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-btsnoop.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-ccp.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-crypto.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-csip.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-ecc.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-gap.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-gatt-client.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-gatt-db.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-gatt-helpers.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-gatt-server.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-gmap.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-hci-crypto.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-hci.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-hfp.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-io-glib.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-log.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-mainloop-glib.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-mainloop-notify.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-mcp.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-mgmt.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-micp.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-pcap.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-queue.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-rap.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-ringbuf.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-shell.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-tester.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-timeout-glib.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-tmap.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-uhid.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-uinput.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-util.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-vcp.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-ad.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-asha.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-att.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-bap-debug.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-bap.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-bass.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-battery.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-btsnoop.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-ccp.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-crypto.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-csip.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-ecc.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-gap.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-client.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-db.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-helpers.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-server.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-gmap.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-hci-crypto.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-hci.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-hfp.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-io-mainloop.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-log.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-mainloop-notify.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-mainloop.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-mcp.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-mgmt.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-micp.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-pcap.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-queue.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-rap.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-ringbuf.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-shell.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-timeout-mainloop.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-tmap.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-uhid.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-uinput.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-util.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-vcp.Plo \ tools/$(DEPDIR)/3dsp.Po tools/$(DEPDIR)/6lowpan-tester.Po \ tools/$(DEPDIR)/advtest.Po tools/$(DEPDIR)/avinfo.Po \ tools/$(DEPDIR)/avtest.Po tools/$(DEPDIR)/bcmfw.Po \ tools/$(DEPDIR)/bdaddr.Po tools/$(DEPDIR)/bluemoon.Po \ tools/$(DEPDIR)/bluetooth-player.Po \ tools/$(DEPDIR)/bnep-tester.Po tools/$(DEPDIR)/bneptest.Po \ tools/$(DEPDIR)/btattach.Po tools/$(DEPDIR)/btconfig.Po \ tools/$(DEPDIR)/btgatt-client.Po \ tools/$(DEPDIR)/btgatt-server.Po tools/$(DEPDIR)/btinfo.Po \ tools/$(DEPDIR)/btiotest.Po tools/$(DEPDIR)/btmgmt.Po \ tools/$(DEPDIR)/btmon-logger.Po tools/$(DEPDIR)/btproxy.Po \ tools/$(DEPDIR)/btsnoop.Po tools/$(DEPDIR)/check-selftest.Po \ tools/$(DEPDIR)/ciptool.Po tools/$(DEPDIR)/cltest.Po \ tools/$(DEPDIR)/create-image.Po tools/$(DEPDIR)/eddystone.Po \ tools/$(DEPDIR)/gap-tester.Po tools/$(DEPDIR)/gatt-service.Po \ tools/$(DEPDIR)/hci-tester.Po tools/$(DEPDIR)/hciattach.Po \ tools/$(DEPDIR)/hciattach_ath3k.Po \ tools/$(DEPDIR)/hciattach_bcm43xx.Po \ tools/$(DEPDIR)/hciattach_intel.Po \ tools/$(DEPDIR)/hciattach_qualcomm.Po \ tools/$(DEPDIR)/hciattach_st.Po \ tools/$(DEPDIR)/hciattach_ti.Po \ tools/$(DEPDIR)/hciattach_tialt.Po \ tools/$(DEPDIR)/hciconfig.Po tools/$(DEPDIR)/hcieventmask.Po \ tools/$(DEPDIR)/hcisecfilter.Po tools/$(DEPDIR)/hcitool.Po \ tools/$(DEPDIR)/hex2hcd.Po tools/$(DEPDIR)/hid2hci.Po \ tools/$(DEPDIR)/hwdb.Po tools/$(DEPDIR)/ibeacon.Po \ tools/$(DEPDIR)/ioctl-tester.Po tools/$(DEPDIR)/iso-tester.Po \ tools/$(DEPDIR)/isotest.Po tools/$(DEPDIR)/l2cap-tester.Po \ tools/$(DEPDIR)/l2ping.Po tools/$(DEPDIR)/l2test.Po \ tools/$(DEPDIR)/mesh-cfgclient.Po \ tools/$(DEPDIR)/mesh-cfgtest.Po tools/$(DEPDIR)/mesh-tester.Po \ tools/$(DEPDIR)/meshctl.Po tools/$(DEPDIR)/mgmt-tester.Po \ tools/$(DEPDIR)/mpris-proxy.Po tools/$(DEPDIR)/nokfw.Po \ tools/$(DEPDIR)/obex-client-tool.Po \ tools/$(DEPDIR)/obex-server-tool.Po tools/$(DEPDIR)/obexctl.Po \ tools/$(DEPDIR)/oobtest.Po tools/$(DEPDIR)/rctest.Po \ tools/$(DEPDIR)/rfcomm-tester.Po tools/$(DEPDIR)/rfcomm.Po \ tools/$(DEPDIR)/rtlfw.Po tools/$(DEPDIR)/sco-tester.Po \ tools/$(DEPDIR)/scotest.Po tools/$(DEPDIR)/sdptool.Po \ tools/$(DEPDIR)/seq2bseq.Po tools/$(DEPDIR)/smp-tester.Po \ tools/$(DEPDIR)/test_runner-test-runner.Po \ tools/$(DEPDIR)/userchan-tester.Po \ tools/mesh-gatt/$(DEPDIR)/config-client.Po \ tools/mesh-gatt/$(DEPDIR)/config-server.Po \ tools/mesh-gatt/$(DEPDIR)/crypto.Po \ tools/mesh-gatt/$(DEPDIR)/gatt.Po \ tools/mesh-gatt/$(DEPDIR)/net.Po \ tools/mesh-gatt/$(DEPDIR)/node.Po \ tools/mesh-gatt/$(DEPDIR)/onoff-model.Po \ tools/mesh-gatt/$(DEPDIR)/prov-db.Po \ tools/mesh-gatt/$(DEPDIR)/prov.Po \ tools/mesh-gatt/$(DEPDIR)/util.Po \ tools/mesh/$(DEPDIR)/agent.Po tools/mesh/$(DEPDIR)/cfgcli.Po \ tools/mesh/$(DEPDIR)/keys.Po tools/mesh/$(DEPDIR)/mesh-db.Po \ tools/mesh/$(DEPDIR)/remote.Po tools/mesh/$(DEPDIR)/util.Po \ unit/$(DEPDIR)/avctp.Po unit/$(DEPDIR)/avdtp.Po \ unit/$(DEPDIR)/avrcp-lib.Po unit/$(DEPDIR)/btd.Po \ unit/$(DEPDIR)/test-avctp.Po unit/$(DEPDIR)/test-avdtp.Po \ unit/$(DEPDIR)/test-avrcp.Po unit/$(DEPDIR)/test-bap.Po \ unit/$(DEPDIR)/test-bass.Po unit/$(DEPDIR)/test-battery.Po \ unit/$(DEPDIR)/test-crc.Po unit/$(DEPDIR)/test-crypto.Po \ unit/$(DEPDIR)/test-ecc.Po unit/$(DEPDIR)/test-eir.Po \ unit/$(DEPDIR)/test-gatt.Po unit/$(DEPDIR)/test-gattrib.Po \ unit/$(DEPDIR)/test-gdbus-client.Po \ unit/$(DEPDIR)/test-gmap.Po \ unit/$(DEPDIR)/test-gobex-apparam.Po \ unit/$(DEPDIR)/test-gobex-header.Po \ unit/$(DEPDIR)/test-gobex-packet.Po \ unit/$(DEPDIR)/test-gobex-transfer.Po \ unit/$(DEPDIR)/test-gobex.Po unit/$(DEPDIR)/test-hfp.Po \ unit/$(DEPDIR)/test-hog.Po unit/$(DEPDIR)/test-lib.Po \ unit/$(DEPDIR)/test-mcp.Po unit/$(DEPDIR)/test-mgmt.Po \ unit/$(DEPDIR)/test-micp.Po unit/$(DEPDIR)/test-profile.Po \ unit/$(DEPDIR)/test-queue.Po unit/$(DEPDIR)/test-rap.Po \ unit/$(DEPDIR)/test-ringbuf.Po unit/$(DEPDIR)/test-sdp.Po \ unit/$(DEPDIR)/test-tester.Po unit/$(DEPDIR)/test-textfile.Po \ unit/$(DEPDIR)/test-tmap.Po unit/$(DEPDIR)/test-uhid.Po \ unit/$(DEPDIR)/test-util.Po unit/$(DEPDIR)/test-uuid.Po \ unit/$(DEPDIR)/test-vcp.Po \ unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Po \ unit/$(DEPDIR)/test_midi-test-midi.Po unit/$(DEPDIR)/util.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(ell_libell_internal_la_SOURCES) \ $(gdbus_libgdbus_internal_la_SOURCES) \ $(lib_libbluetooth_internal_la_SOURCES) \ $(lib_libbluetooth_la_SOURCES) $(src_libshared_ell_la_SOURCES) \ $(src_libshared_glib_la_SOURCES) \ $(src_libshared_mainloop_la_SOURCES) \ $(attrib_gatttool_SOURCES) $(client_bluetoothctl_SOURCES) \ $(client_btpclient_btpclient_SOURCES) \ $(client_btpclient_btpclientctl_SOURCES) \ $(emulator_b1ee_SOURCES) $(emulator_btvirt_SOURCES) \ $(emulator_hfp_SOURCES) $(mesh_bluetooth_meshd_SOURCES) \ $(monitor_btmon_SOURCES) $(obexd_src_obexd_SOURCES) \ $(nodist_obexd_src_obexd_SOURCES) \ $(peripheral_btsensor_SOURCES) \ $(profiles_cups_bluetooth_SOURCES) \ $(profiles_iap_iapd_SOURCES) $(src_bluetoothd_SOURCES) \ $(nodist_src_bluetoothd_SOURCES) $(tools_3dsp_SOURCES) \ $(tools_6lowpan_tester_SOURCES) $(tools_advtest_SOURCES) \ tools/avinfo.c tools/avtest.c tools/bcmfw.c \ $(tools_bdaddr_SOURCES) $(tools_bluemoon_SOURCES) \ $(tools_bluetooth_player_SOURCES) $(tools_bnep_tester_SOURCES) \ $(tools_bneptest_SOURCES) $(tools_btattach_SOURCES) \ $(tools_btconfig_SOURCES) $(tools_btgatt_client_SOURCES) \ $(tools_btgatt_server_SOURCES) $(tools_btinfo_SOURCES) \ $(tools_btiotest_SOURCES) $(tools_btmgmt_SOURCES) \ $(tools_btmon_logger_SOURCES) $(tools_btproxy_SOURCES) \ $(tools_btsnoop_SOURCES) tools/check-selftest.c \ tools/ciptool.c $(tools_cltest_SOURCES) \ $(tools_create_image_SOURCES) $(tools_eddystone_SOURCES) \ $(tools_gap_tester_SOURCES) $(tools_gatt_service_SOURCES) \ $(tools_hci_tester_SOURCES) $(tools_hciattach_SOURCES) \ $(tools_hciconfig_SOURCES) tools/hcieventmask.c \ tools/hcisecfilter.c $(tools_hcitool_SOURCES) \ $(tools_hex2hcd_SOURCES) tools/hid2hci.c tools/hwdb.c \ $(tools_ibeacon_SOURCES) $(tools_ioctl_tester_SOURCES) \ $(tools_iso_tester_SOURCES) tools/isotest.c \ $(tools_l2cap_tester_SOURCES) tools/l2ping.c tools/l2test.c \ $(tools_mesh_cfgclient_SOURCES) $(tools_mesh_cfgtest_SOURCES) \ $(tools_mesh_tester_SOURCES) $(tools_meshctl_SOURCES) \ $(tools_mgmt_tester_SOURCES) $(tools_mpris_proxy_SOURCES) \ $(tools_nokfw_SOURCES) $(tools_obex_client_tool_SOURCES) \ $(tools_obex_server_tool_SOURCES) $(tools_obexctl_SOURCES) \ $(tools_oobtest_SOURCES) tools/rctest.c tools/rfcomm.c \ $(tools_rfcomm_tester_SOURCES) $(tools_rtlfw_SOURCES) \ $(tools_sco_tester_SOURCES) tools/scotest.c \ $(tools_sdptool_SOURCES) $(tools_seq2bseq_SOURCES) \ $(tools_smp_tester_SOURCES) tools/test-runner.c \ $(tools_userchan_tester_SOURCES) $(unit_test_avctp_SOURCES) \ $(unit_test_avdtp_SOURCES) $(unit_test_avrcp_SOURCES) \ $(unit_test_bap_SOURCES) $(unit_test_bass_SOURCES) \ $(unit_test_battery_SOURCES) $(unit_test_crc_SOURCES) \ $(unit_test_crypto_SOURCES) $(unit_test_ecc_SOURCES) \ $(unit_test_eir_SOURCES) $(unit_test_gatt_SOURCES) \ $(unit_test_gattrib_SOURCES) $(unit_test_gdbus_client_SOURCES) \ $(unit_test_gmap_SOURCES) $(unit_test_gobex_SOURCES) \ $(unit_test_gobex_apparam_SOURCES) \ $(unit_test_gobex_header_SOURCES) \ $(unit_test_gobex_packet_SOURCES) \ $(unit_test_gobex_transfer_SOURCES) $(unit_test_hfp_SOURCES) \ $(unit_test_hog_SOURCES) $(unit_test_lib_SOURCES) \ $(unit_test_mcp_SOURCES) $(unit_test_mesh_crypto_SOURCES) \ $(unit_test_mgmt_SOURCES) $(unit_test_micp_SOURCES) \ $(unit_test_midi_SOURCES) $(unit_test_profile_SOURCES) \ $(unit_test_queue_SOURCES) $(unit_test_rap_SOURCES) \ $(unit_test_ringbuf_SOURCES) $(unit_test_sdp_SOURCES) \ $(unit_test_tester_SOURCES) $(unit_test_textfile_SOURCES) \ $(unit_test_tmap_SOURCES) $(unit_test_uhid_SOURCES) \ unit/test-util.c $(unit_test_uuid_SOURCES) \ $(unit_test_vcp_SOURCES) DIST_SOURCES = $(am__ell_libell_internal_la_SOURCES_DIST) \ $(gdbus_libgdbus_internal_la_SOURCES) \ $(lib_libbluetooth_internal_la_SOURCES) \ $(am__lib_libbluetooth_la_SOURCES_DIST) \ $(am__src_libshared_ell_la_SOURCES_DIST) \ $(am__src_libshared_glib_la_SOURCES_DIST) \ $(am__src_libshared_mainloop_la_SOURCES_DIST) \ $(am__attrib_gatttool_SOURCES_DIST) \ $(am__client_bluetoothctl_SOURCES_DIST) \ $(am__client_btpclient_btpclient_SOURCES_DIST) \ $(am__client_btpclient_btpclientctl_SOURCES_DIST) \ $(am__emulator_b1ee_SOURCES_DIST) \ $(am__emulator_btvirt_SOURCES_DIST) \ $(am__emulator_hfp_SOURCES_DIST) \ $(am__mesh_bluetooth_meshd_SOURCES_DIST) \ $(am__monitor_btmon_SOURCES_DIST) \ $(am__obexd_src_obexd_SOURCES_DIST) \ $(am__peripheral_btsensor_SOURCES_DIST) \ $(am__profiles_cups_bluetooth_SOURCES_DIST) \ $(am__profiles_iap_iapd_SOURCES_DIST) \ $(am__src_bluetoothd_SOURCES_DIST) \ $(am__tools_3dsp_SOURCES_DIST) \ $(am__tools_6lowpan_tester_SOURCES_DIST) \ $(am__tools_advtest_SOURCES_DIST) tools/avinfo.c \ tools/avtest.c tools/bcmfw.c $(am__tools_bdaddr_SOURCES_DIST) \ $(am__tools_bluemoon_SOURCES_DIST) \ $(am__tools_bluetooth_player_SOURCES_DIST) \ $(am__tools_bnep_tester_SOURCES_DIST) \ $(am__tools_bneptest_SOURCES_DIST) \ $(am__tools_btattach_SOURCES_DIST) \ $(am__tools_btconfig_SOURCES_DIST) \ $(am__tools_btgatt_client_SOURCES_DIST) \ $(am__tools_btgatt_server_SOURCES_DIST) \ $(am__tools_btinfo_SOURCES_DIST) \ $(am__tools_btiotest_SOURCES_DIST) \ $(am__tools_btmgmt_SOURCES_DIST) \ $(am__tools_btmon_logger_SOURCES_DIST) \ $(am__tools_btproxy_SOURCES_DIST) \ $(am__tools_btsnoop_SOURCES_DIST) tools/check-selftest.c \ tools/ciptool.c $(am__tools_cltest_SOURCES_DIST) \ $(am__tools_create_image_SOURCES_DIST) \ $(am__tools_eddystone_SOURCES_DIST) \ $(am__tools_gap_tester_SOURCES_DIST) \ $(am__tools_gatt_service_SOURCES_DIST) \ $(am__tools_hci_tester_SOURCES_DIST) \ $(am__tools_hciattach_SOURCES_DIST) \ $(am__tools_hciconfig_SOURCES_DIST) tools/hcieventmask.c \ tools/hcisecfilter.c $(am__tools_hcitool_SOURCES_DIST) \ $(am__tools_hex2hcd_SOURCES_DIST) tools/hid2hci.c tools/hwdb.c \ $(am__tools_ibeacon_SOURCES_DIST) \ $(am__tools_ioctl_tester_SOURCES_DIST) \ $(am__tools_iso_tester_SOURCES_DIST) tools/isotest.c \ $(am__tools_l2cap_tester_SOURCES_DIST) tools/l2ping.c \ tools/l2test.c $(am__tools_mesh_cfgclient_SOURCES_DIST) \ $(am__tools_mesh_cfgtest_SOURCES_DIST) \ $(am__tools_mesh_tester_SOURCES_DIST) \ $(am__tools_meshctl_SOURCES_DIST) \ $(am__tools_mgmt_tester_SOURCES_DIST) \ $(am__tools_mpris_proxy_SOURCES_DIST) \ $(am__tools_nokfw_SOURCES_DIST) \ $(am__tools_obex_client_tool_SOURCES_DIST) \ $(am__tools_obex_server_tool_SOURCES_DIST) \ $(am__tools_obexctl_SOURCES_DIST) \ $(am__tools_oobtest_SOURCES_DIST) tools/rctest.c \ tools/rfcomm.c $(am__tools_rfcomm_tester_SOURCES_DIST) \ $(am__tools_rtlfw_SOURCES_DIST) \ $(am__tools_sco_tester_SOURCES_DIST) tools/scotest.c \ $(am__tools_sdptool_SOURCES_DIST) \ $(am__tools_seq2bseq_SOURCES_DIST) \ $(am__tools_smp_tester_SOURCES_DIST) tools/test-runner.c \ $(am__tools_userchan_tester_SOURCES_DIST) \ $(unit_test_avctp_SOURCES) $(unit_test_avdtp_SOURCES) \ $(unit_test_avrcp_SOURCES) $(unit_test_bap_SOURCES) \ $(unit_test_bass_SOURCES) $(unit_test_battery_SOURCES) \ $(unit_test_crc_SOURCES) $(unit_test_crypto_SOURCES) \ $(unit_test_ecc_SOURCES) $(unit_test_eir_SOURCES) \ $(unit_test_gatt_SOURCES) $(unit_test_gattrib_SOURCES) \ $(unit_test_gdbus_client_SOURCES) $(unit_test_gmap_SOURCES) \ $(am__unit_test_gobex_SOURCES_DIST) \ $(am__unit_test_gobex_apparam_SOURCES_DIST) \ $(am__unit_test_gobex_header_SOURCES_DIST) \ $(am__unit_test_gobex_packet_SOURCES_DIST) \ $(am__unit_test_gobex_transfer_SOURCES_DIST) \ $(unit_test_hfp_SOURCES) $(unit_test_hog_SOURCES) \ $(unit_test_lib_SOURCES) $(unit_test_mcp_SOURCES) \ $(am__unit_test_mesh_crypto_SOURCES_DIST) \ $(unit_test_mgmt_SOURCES) $(unit_test_micp_SOURCES) \ $(am__unit_test_midi_SOURCES_DIST) \ $(unit_test_profile_SOURCES) $(unit_test_queue_SOURCES) \ $(unit_test_rap_SOURCES) $(unit_test_ringbuf_SOURCES) \ $(unit_test_sdp_SOURCES) $(unit_test_tester_SOURCES) \ $(unit_test_textfile_SOURCES) $(unit_test_tmap_SOURCES) \ $(unit_test_uhid_SOURCES) unit/test-util.c \ $(unit_test_uuid_SOURCES) $(unit_test_vcp_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac man1dir = $(mandir)/man1 man5dir = $(mandir)/man5 man7dir = $(mandir)/man7 man8dir = $(mandir)/man8 NROFF = nroff MANS = $(man_MANS) am__dist_zshcompletion_DATA_DIST = completion/zsh/_bluetoothctl DATA = $(conf_DATA) $(dbus_DATA) $(dbussessionbus_DATA) \ $(dbussystembus_DATA) $(dist_zshcompletion_DATA) \ $(pkgconfig_DATA) $(rules_DATA) $(state_DATA) \ $(systemdsystemunit_DATA) $(systemduserunit_DATA) am__pkginclude_HEADERS_DIST = lib/bluetooth/bluetooth.h \ lib/bluetooth/hci.h lib/bluetooth/hci_lib.h \ lib/bluetooth/sco.h lib/bluetooth/l2cap.h lib/bluetooth/sdp.h \ lib/bluetooth/sdp_lib.h lib/bluetooth/rfcomm.h \ lib/bluetooth/bnep.h lib/bluetooth/cmtp.h lib/bluetooth/hidp.h HEADERS = $(pkginclude_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) \ config.h.in # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` AM_RECURSIVE_TARGETS = cscope check recheck am__tty_colors_dummy = \ mgn= red= grn= lgn= blu= brg= std=; \ am__color_tests=no am__tty_colors = { \ $(am__tty_colors_dummy); \ if test "X$(AM_COLOR_TESTS)" = Xno; then \ am__color_tests=no; \ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \ am__color_tests=yes; \ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \ am__color_tests=yes; \ fi; \ if test $$am__color_tests = yes; then \ red=''; \ grn=''; \ lgn=''; \ blu=''; \ mgn=''; \ brg=''; \ std=''; \ fi; \ } am__recheck_rx = ^[ ]*:recheck:[ ]* am__global_test_result_rx = ^[ ]*:global-test-result:[ ]* am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]* # A command that, given a newline-separated list of test names on the # standard input, print the name of the tests that are to be re-run # upon "make recheck". am__list_recheck_tests = $(AWK) '{ \ recheck = 1; \ while ((rc = (getline line < ($$0 ".trs"))) != 0) \ { \ if (rc < 0) \ { \ if ((getline line2 < ($$0 ".log")) < 0) \ recheck = 0; \ break; \ } \ else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \ { \ recheck = 0; \ break; \ } \ else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \ { \ break; \ } \ }; \ if (recheck) \ print $$0; \ close ($$0 ".trs"); \ close ($$0 ".log"); \ }' # A command that, given a newline-separated list of test names on the # standard input, create the global log from their .trs and .log files. am__create_global_log = $(AWK) ' \ function fatal(msg) \ { \ print "fatal: making $@: " msg | "cat >&2"; \ exit 1; \ } \ function rst_section(header) \ { \ print header; \ len = length(header); \ for (i = 1; i <= len; i = i + 1) \ printf "="; \ printf "\n\n"; \ } \ { \ copy_in_global_log = 1; \ global_test_result = "RUN"; \ while ((rc = (getline line < ($$0 ".trs"))) != 0) \ { \ if (rc < 0) \ fatal("failed to read from " $$0 ".trs"); \ if (line ~ /$(am__global_test_result_rx)/) \ { \ sub("$(am__global_test_result_rx)", "", line); \ sub("[ ]*$$", "", line); \ global_test_result = line; \ } \ else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \ copy_in_global_log = 0; \ }; \ if (copy_in_global_log) \ { \ rst_section(global_test_result ": " $$0); \ while ((rc = (getline line < ($$0 ".log"))) != 0) \ { \ if (rc < 0) \ fatal("failed to read from " $$0 ".log"); \ print line; \ }; \ printf "\n"; \ }; \ close ($$0 ".trs"); \ close ($$0 ".log"); \ }' # Restructured Text title. am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; } # Solaris 10 'make', and several other traditional 'make' implementations, # pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it # by disabling -e (using the XSI extension "set +e") if it's set. am__sh_e_setup = case $$- in *e*) set +e;; esac # Default flags passed to test drivers. am__common_driver_flags = \ --color-tests "$$am__color_tests" \ $$am__collect_skipped_logs \ --enable-hard-errors "$$am__enable_hard_errors" \ --expect-failure "$$am__expect_failure" # To be inserted before the command running the test. Creates the # directory for the log if needed. Stores in $dir the directory # containing $f, in $tst the test, in $log the log. Executes the # developer-defined test setup AM_TESTS_ENVIRONMENT (if any), and # passes TESTS_ENVIRONMENT. Set up options for the wrapper that # will run the test scripts (or their associated LOG_COMPILER, if # thy have one). am__check_pre = \ $(am__sh_e_setup); \ $(am__vpath_adj_setup) $(am__vpath_adj) \ $(am__tty_colors); \ srcdir=$(srcdir); export srcdir; \ case "$@" in \ */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \ *) am__odir=.;; \ esac; \ test "x$$am__odir" = x"." || test -d "$$am__odir" \ || $(MKDIR_P) "$$am__odir" || exit $$?; \ if test -f "./$$f"; then dir=./; \ elif test -f "$$f"; then dir=; \ else dir="$(srcdir)/"; fi; \ tst=$$dir$$f; log='$@'; \ if test -n '$(IGNORE_SKIPPED_LOGS)'; then \ am__collect_skipped_logs='--collect-skipped-logs no'; \ else \ am__collect_skipped_logs=''; \ fi; \ if test -n '$(DISABLE_HARD_ERRORS)'; then \ am__enable_hard_errors=no; \ else \ am__enable_hard_errors=yes; \ fi; \ case " $(XFAIL_TESTS) " in \ *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \ am__expect_failure=yes;; \ *) \ am__expect_failure=no;; \ esac; \ $(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT) # A shell command to get the names of the tests scripts with any registered # extension removed (i.e., equivalently, the names of the test logs, with # the '.log' extension removed). The result is saved in the shell variable # '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly, # we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)", # since that might cause problem with VPATH rewrites for suffix-less tests. # See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'. am__set_TESTS_bases = \ bases='$(TEST_LOGS)'; \ bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \ bases=`echo $$bases` AM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING)' RECHECK_LOGS = $(TEST_LOGS) TEST_SUITE_LOG = test-suite.log TEST_EXTENSIONS = @EXEEXT@ .test LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS) am__set_b = \ case '$@' in \ */*) \ case '$*' in \ */*) b='$*';; \ *) b=`echo '$@' | sed 's/\.log$$//'`; \ esac;; \ *) \ b='$*';; \ esac am__test_logs1 = $(TESTS:=.log) am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log) TEST_LOGS = $(am__test_logs2:.test.log=.log) TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \ $(TEST_LOG_FLAGS) am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.mesh \ $(srcdir)/Makefile.obexd $(srcdir)/Makefile.plugins \ $(srcdir)/Makefile.tools $(srcdir)/config.h.in \ $(top_srcdir)/doc/bluetooth-meshd.rst.in \ $(top_srcdir)/doc/bluetoothd.rst.in \ $(top_srcdir)/lib/bluez.pc.in \ $(top_srcdir)/mesh/bluetooth-mesh.service.in \ $(top_srcdir)/obexd/src/obex.service.in \ $(top_srcdir)/obexd/src/org.bluez.obex.service.in \ $(top_srcdir)/src/bluetooth.service.in \ $(top_srcdir)/tools/bluetooth-logger.service.in \ $(top_srcdir)/tools/mpris-proxy.service.in AUTHORS COPYING \ COPYING.LIB ChangeLog INSTALL NEWS README TODO compile \ config.guess config.sub depcomp install-sh ltmain.sh missing \ test-driver DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) distdir = $(PACKAGE)-$(VERSION) top_distdir = $(distdir) am__remove_distdir = \ if test -d "$(distdir)"; then \ find "$(distdir)" -type d ! -perm -700 -exec chmod u+rwx {} ';' \ ; rm -rf "$(distdir)" \ || { sleep 5 && rm -rf "$(distdir)"; }; \ else :; fi am__post_remove_distdir = $(am__remove_distdir) GZIP_ENV = -9 DIST_ARCHIVES = $(distdir).tar.xz DIST_TARGETS = dist-xz # Exists only to be overridden by the user if desired. AM_DISTCHECK_DVI_TARGET = dvi distuninstallcheck_listfiles = find . -type f -print am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \ | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$' distcleancheck_listfiles = \ find . \( -type f -a \! \ \( -name .nfs* -o -name .smb* -o -name .__afs* \) \) -print pkgincludedir = $(includedir)/bluetooth pkglibexecdir = @PKGLIBEXECDIR@ ACLOCAL = @ACLOCAL@ ALSA_CFLAGS = @ALSA_CFLAGS@ ALSA_LIBS = @ALSA_LIBS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ AR = @AR@ ASAN_LIB = @ASAN_LIB@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BACKTRACE_CFLAGS = @BACKTRACE_CFLAGS@ BACKTRACE_LIBS = @BACKTRACE_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CFLAGS = @CFLAGS@ CONFIGDIR = @CONFIGDIR@ CPPFLAGS = @CPPFLAGS@ CSCOPE = @CSCOPE@ CTAGS = @CTAGS@ CUPS_SERVERBIN = @CUPS_SERVERBIN@ CYGPATH_W = @CYGPATH_W@ DBUS_CFLAGS = @DBUS_CFLAGS@ DBUS_CONFDIR = @DBUS_CONFDIR@ DBUS_LIBS = @DBUS_LIBS@ DBUS_SESSIONBUSDIR = @DBUS_SESSIONBUSDIR@ DBUS_SYSTEMBUSDIR = @DBUS_SYSTEMBUSDIR@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ ELL_CFLAGS = @ELL_CFLAGS@ ELL_LIBS = @ELL_LIBS@ ETAGS = @ETAGS@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FILECMD = @FILECMD@ GLIB_CFLAGS = @GLIB_CFLAGS@ GLIB_LIBS = @GLIB_LIBS@ GREP = @GREP@ ICAL_CFLAGS = @ICAL_CFLAGS@ ICAL_LIBS = @ICAL_LIBS@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ JSONC_CFLAGS = @JSONC_CFLAGS@ JSONC_LIBS = @JSONC_LIBS@ LD = @LD@ LDFLAGS = @LDFLAGS@ LIBEBOOK_CFLAGS = @LIBEBOOK_CFLAGS@ LIBEBOOK_LIBS = @LIBEBOOK_LIBS@ LIBEDATESERVER_CFLAGS = @LIBEDATESERVER_CFLAGS@ LIBEDATESERVER_LIBS = @LIBEDATESERVER_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBTOOL = @LIBTOOL@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MESH_STORAGEDIR = @MESH_STORAGEDIR@ MISC_CFLAGS = @MISC_CFLAGS@ MISC_LDFLAGS = @MISC_LDFLAGS@ MKDIR_P = @MKDIR_P@ NM = @NM@ NMEDIT = @NMEDIT@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ PKGBINDIR = @PKGBINDIR@ PKGLIBEXECDIR = @PKGLIBEXECDIR@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ PLUGIN_PHONEBOOK = @PLUGIN_PHONEBOOK@ RANLIB = @RANLIB@ RST2MAN = @RST2MAN@ SED = @SED@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ STRIP = @STRIP@ SYSTEMD_SYSTEMUNITDIR = @SYSTEMD_SYSTEMUNITDIR@ SYSTEMD_USERUNITDIR = @SYSTEMD_USERUNITDIR@ UDEV_CFLAGS = @UDEV_CFLAGS@ UDEV_DIR = @UDEV_DIR@ UDEV_LIBS = @UDEV_LIBS@ VERSION = @VERSION@ WARNING_CFLAGS = @WARNING_CFLAGS@ ZSH_COMPLETIONDIR = @ZSH_COMPLETIONDIR@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__rm_f_notfound = @am__rm_f_notfound@ am__tar = @am__tar@ am__untar = @am__untar@ am__xargs_n = @am__xargs_n@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ docdir = @docdir@ dvidir = @dvidir@ enable_coverage = @enable_coverage@ enable_dbus_run_session = @enable_dbus_run_session@ enable_valgrind = @enable_valgrind@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ srcdir = @srcdir@ sysconfdir = @sysconfdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ # SPDX-License-Identifier: GPL-2.0 AM_MAKEFLAGS = --no-print-directory AM_CPPFLAGS = $(DBUS_CFLAGS) $(GLIB_CFLAGS) -I$(srcdir)/lib lib_LTLIBRARIES = $(am__append_3) noinst_LIBRARIES = noinst_LTLIBRARIES = lib/libbluetooth-internal.la \ gdbus/libgdbus-internal.la $(am__append_4) \ src/libshared-glib.la src/libshared-mainloop.la \ $(am__append_5) CLEANFILES = $(ell_built_sources) $(builtin_files) obexd/src/builtin.h \ $(am__append_84) EXTRA_DIST = src/org.bluez.service src/genbuiltin src/bluetooth.conf \ src/main.conf profiles/network/network.conf \ profiles/input/input.conf obexd/src/obex.conf $(am__append_63) \ $(am__append_73) obexd/src/genbuiltin mesh/bluetooth-mesh.conf \ mesh/org.bluez.mesh.service mesh/mesh-main.conf \ tools/hid2hci.rules $(test_scripts) doc/assigned-numbers.rst \ doc/supported-features.txt doc/test-coverage.txt \ doc/test-runner.rst doc/settings-storage.txt \ doc/hci-protocol.rst doc/mgmt-protocol.rst \ doc/l2cap-protocol.rst doc/rfcomm-protocol.rst \ doc/sco-protocol.rst doc/iso-protocol.rst \ doc/org.bluez.Adapter.rst doc/org.bluez.Device.rst \ doc/org.bluez.Bearer.LE.rst doc/org.bluez.Bearer.BREDR.rst \ doc/org.bluez.DeviceSet.rst doc/org.bluez.AgentManager.rst \ doc/org.bluez.Agent.rst doc/org.bluez.ProfileManager.rst \ doc/org.bluez.Profile.rst doc/org.bluez.NetworkServer.rst \ doc/org.bluez.Network.rst doc/org.bluez.Input.rst \ doc/org.bluez.BatteryProviderManager.rst \ doc/org.bluez.BatteryProvider.rst doc/org.bluez.Battery.rst \ doc/org.bluez.AdminPolicySet.rst \ doc/org.bluez.AdminPolicyStatus.rst doc/org.bluez.Media.rst \ doc/org.bluez.MediaControl.rst doc/org.bluez.MediaPlayer.rst \ doc/org.bluez.MediaFolder.rst doc/org.bluez.MediaItem.rst \ doc/org.bluez.MediaEndpoint.rst \ doc/org.bluez.MediaTransport.rst \ doc/org.bluez.MediaAssistant.rst doc/org.bluez.GattManager.rst \ doc/org.bluez.GattProfile.rst doc/org.bluez.GattService.rst \ doc/org.bluez.GattCharacteristic.rst \ doc/org.bluez.GattDescriptor.rst \ doc/org.bluez.LEAdvertisingManager.rst \ doc/org.bluez.LEAdvertisement.rst \ doc/org.bluez.AdvertisementMonitorManager.rst \ doc/org.bluez.AdvertisementMonitor.rst \ doc/org.bluez.obex.Client.rst doc/org.bluez.obex.Session.rst \ doc/org.bluez.obex.Transfer.rst \ doc/org.bluez.obex.ObjectPush.rst \ doc/org.bluez.obex.FileTransfer.rst \ doc/org.bluez.obex.Synchronization.rst \ doc/org.bluez.obex.PhonebookAccess.rst \ doc/org.bluez.obex.MessageAccess.rst \ doc/org.bluez.obex.Message.rst \ doc/org.bluez.obex.AgentManager.rst \ doc/org.bluez.obex.Agent.rst doc/org.bluez.obex.Image.rst \ doc/org.bluez.Telephony.rst doc/org.bluez.Call.rst \ doc/org.bluez.ThermometerManager.rst \ doc/org.bluez.Thermometer.rst \ doc/org.bluez.ThermometerWatcher.rst doc/pics-opp.txt \ doc/pixit-opp.txt doc/pts-opp.txt doc/btsnoop-protocol.rst \ doc/btmon-advertising.rst doc/btmon-le-audio.rst \ doc/btmon-cs.rst doc/btmon-l2cap.rst doc/btmon-smp.rst \ doc/btmon-gatt.rst doc/btmon-connections.rst \ doc/btmon-hci-init.rst doc/btmon-classic-audio.rst \ doc/btmon-a2dp.rst doc/btmon-hfp.rst doc/btmon-mgmt.rst \ tools/magic.btsnoop $(manual_pages) $(patsubst %.1,%.rst, \ $(patsubst %.8,%.rst,$(manual_pages))) pkginclude_HEADERS = $(am__append_2) AM_CFLAGS = $(MISC_CFLAGS) $(WARNING_CFLAGS) $(UDEV_CFLAGS) $(LIBEBOOK_CFLAGS) \ $(LIBEDATASERVER_CFLAGS) $(ell_cflags) AM_LDFLAGS = $(MISC_LDFLAGS) confdir = $(sysconfdir)/bluetooth statedir = $(localstatedir)/lib/bluetooth @DATAFILES_TRUE@dbusdir = $(DBUS_CONFDIR)/dbus-1/system.d @DATAFILES_TRUE@dbus_DATA = src/bluetooth.conf $(am__append_1) \ @DATAFILES_TRUE@ $(am__append_77) @DATAFILES_TRUE@conf_DATA = src/main.conf profiles/input/input.conf \ @DATAFILES_TRUE@ profiles/network/network.conf $(am__append_78) @DATAFILES_TRUE@state_DATA = @SYSTEMD_TRUE@systemdsystemunitdir = $(SYSTEMD_SYSTEMUNITDIR) @SYSTEMD_TRUE@systemdsystemunit_DATA = src/bluetooth.service \ @SYSTEMD_TRUE@ $(am__append_55) $(am__append_79) @SYSTEMD_TRUE@systemduserunitdir = $(SYSTEMD_USERUNITDIR) @SYSTEMD_TRUE@systemduserunit_DATA = $(am__append_59) $(am__append_70) @SYSTEMD_TRUE@dbussystembusdir = $(DBUS_SYSTEMBUSDIR) @SYSTEMD_TRUE@dbussystembus_DATA = src/org.bluez.service \ @SYSTEMD_TRUE@ $(am__append_80) plugindir = $(libdir)/bluetooth/plugins build_plugindir = $(plugindir) @MANPAGES_TRUE@man_MANS = doc/bluetoothd.8 doc/hci.7 doc/mgmt.7 \ @MANPAGES_TRUE@ doc/l2cap.7 doc/rfcomm.7 doc/sco.7 doc/iso.7 \ @MANPAGES_TRUE@ doc/btsnoop.7 doc/org.bluez.Adapter.5 \ @MANPAGES_TRUE@ doc/org.bluez.Device.5 \ @MANPAGES_TRUE@ doc/org.bluez.Bearer.LE.5 \ @MANPAGES_TRUE@ doc/org.bluez.Bearer.BREDR.5 \ @MANPAGES_TRUE@ doc/org.bluez.DeviceSet.5 \ @MANPAGES_TRUE@ doc/org.bluez.AgentManager.5 \ @MANPAGES_TRUE@ doc/org.bluez.Agent.5 \ @MANPAGES_TRUE@ doc/org.bluez.ProfileManager.5 \ @MANPAGES_TRUE@ doc/org.bluez.Profile.5 \ @MANPAGES_TRUE@ doc/org.bluez.NetworkServer.5 \ @MANPAGES_TRUE@ doc/org.bluez.Network.5 doc/org.bluez.Input.5 \ @MANPAGES_TRUE@ doc/org.bluez.BatteryProviderManager.5 \ @MANPAGES_TRUE@ doc/org.bluez.BatteryProvider.5 \ @MANPAGES_TRUE@ doc/org.bluez.Battery.5 \ @MANPAGES_TRUE@ doc/org.bluez.AdminPolicySet.5 \ @MANPAGES_TRUE@ doc/org.bluez.AdminPolicyStatus.5 \ @MANPAGES_TRUE@ doc/org.bluez.Media.5 \ @MANPAGES_TRUE@ doc/org.bluez.MediaControl.5 \ @MANPAGES_TRUE@ doc/org.bluez.MediaPlayer.5 \ @MANPAGES_TRUE@ doc/org.bluez.MediaFolder.5 \ @MANPAGES_TRUE@ doc/org.bluez.MediaItem.5 \ @MANPAGES_TRUE@ doc/org.bluez.MediaEndpoint.5 \ @MANPAGES_TRUE@ doc/org.bluez.MediaTransport.5 \ @MANPAGES_TRUE@ doc/org.bluez.MediaAssistant.5 \ @MANPAGES_TRUE@ doc/org.bluez.GattManager.5 \ @MANPAGES_TRUE@ doc/org.bluez.GattProfile.5 \ @MANPAGES_TRUE@ doc/org.bluez.GattService.5 \ @MANPAGES_TRUE@ doc/org.bluez.GattCharacteristic.5 \ @MANPAGES_TRUE@ doc/org.bluez.GattDescriptor.5 \ @MANPAGES_TRUE@ doc/org.bluez.LEAdvertisingManager.5 \ @MANPAGES_TRUE@ doc/org.bluez.LEAdvertisement.5 \ @MANPAGES_TRUE@ doc/org.bluez.AdvertisementMonitorManager.5 \ @MANPAGES_TRUE@ doc/org.bluez.AdvertisementMonitor.5 \ @MANPAGES_TRUE@ doc/org.bluez.obex.Client.5 \ @MANPAGES_TRUE@ doc/org.bluez.obex.Session.5 \ @MANPAGES_TRUE@ doc/org.bluez.obex.Transfer.5 \ @MANPAGES_TRUE@ doc/org.bluez.obex.ObjectPush.5 \ @MANPAGES_TRUE@ doc/org.bluez.obex.FileTransfer.5 \ @MANPAGES_TRUE@ doc/org.bluez.obex.Synchronization.5 \ @MANPAGES_TRUE@ doc/org.bluez.obex.PhonebookAccess.5 \ @MANPAGES_TRUE@ doc/org.bluez.obex.MessageAccess.5 \ @MANPAGES_TRUE@ doc/org.bluez.obex.Message.5 \ @MANPAGES_TRUE@ doc/org.bluez.obex.AgentManager.5 \ @MANPAGES_TRUE@ doc/org.bluez.obex.Agent.5 \ @MANPAGES_TRUE@ doc/org.bluez.obex.Image.5 \ @MANPAGES_TRUE@ doc/org.bluez.Telephony.5 doc/org.bluez.Call.5 \ @MANPAGES_TRUE@ doc/org.bluez.ThermometerManager.5 \ @MANPAGES_TRUE@ doc/org.bluez.Thermometer.5 \ @MANPAGES_TRUE@ doc/org.bluez.ThermometerWatcher.5 \ @MANPAGES_TRUE@ $(am__append_53) $(am__append_60) \ @MANPAGES_TRUE@ $(am__append_65) $(am__append_66) \ @MANPAGES_TRUE@ $(am__append_82) manual_pages = doc/bluetoothd.8 doc/hci.7 doc/mgmt.7 doc/l2cap.7 \ doc/rfcomm.7 doc/sco.7 doc/iso.7 doc/btsnoop.7 \ doc/org.bluez.Adapter.5 doc/org.bluez.Device.5 \ doc/org.bluez.Bearer.LE.5 doc/org.bluez.Bearer.BREDR.5 \ doc/org.bluez.DeviceSet.5 doc/org.bluez.AgentManager.5 \ doc/org.bluez.Agent.5 doc/org.bluez.ProfileManager.5 \ doc/org.bluez.Profile.5 doc/org.bluez.NetworkServer.5 \ doc/org.bluez.Network.5 doc/org.bluez.Input.5 \ doc/org.bluez.BatteryProviderManager.5 \ doc/org.bluez.BatteryProvider.5 doc/org.bluez.Battery.5 \ doc/org.bluez.AdminPolicySet.5 \ doc/org.bluez.AdminPolicyStatus.5 doc/org.bluez.Media.5 \ doc/org.bluez.MediaControl.5 doc/org.bluez.MediaPlayer.5 \ doc/org.bluez.MediaFolder.5 doc/org.bluez.MediaItem.5 \ doc/org.bluez.MediaEndpoint.5 doc/org.bluez.MediaTransport.5 \ doc/org.bluez.MediaAssistant.5 doc/org.bluez.GattManager.5 \ doc/org.bluez.GattProfile.5 doc/org.bluez.GattService.5 \ doc/org.bluez.GattCharacteristic.5 \ doc/org.bluez.GattDescriptor.5 \ doc/org.bluez.LEAdvertisingManager.5 \ doc/org.bluez.LEAdvertisement.5 \ doc/org.bluez.AdvertisementMonitorManager.5 \ doc/org.bluez.AdvertisementMonitor.5 \ doc/org.bluez.obex.Client.5 doc/org.bluez.obex.Session.5 \ doc/org.bluez.obex.Transfer.5 doc/org.bluez.obex.ObjectPush.5 \ doc/org.bluez.obex.FileTransfer.5 \ doc/org.bluez.obex.Synchronization.5 \ doc/org.bluez.obex.PhonebookAccess.5 \ doc/org.bluez.obex.MessageAccess.5 \ doc/org.bluez.obex.Message.5 doc/org.bluez.obex.AgentManager.5 \ doc/org.bluez.obex.Agent.5 doc/org.bluez.obex.Image.5 \ doc/org.bluez.Telephony.5 doc/org.bluez.Call.5 \ doc/org.bluez.ThermometerManager.5 doc/org.bluez.Thermometer.5 \ doc/org.bluez.ThermometerWatcher.5 doc/btmon.1 doc/hciattach.1 \ doc/hciconfig.1 doc/hcitool.1 doc/rfcomm.1 doc/sdptool.1 \ doc/ciptool.1 doc/rctest.1 doc/l2ping.1 doc/btattach.1 \ doc/bdaddr.1 doc/isotest.1 doc/btmgmt.1 doc/bluetoothctl.1 \ doc/bluetoothctl-mgmt.1 doc/bluetoothctl-monitor.1 \ doc/bluetoothctl-admin.1 doc/bluetoothctl-advertise.1 \ doc/bluetoothctl-endpoint.1 doc/bluetoothctl-gatt.1 \ doc/bluetoothctl-player.1 doc/bluetoothctl-scan.1 \ doc/bluetoothctl-transport.1 doc/bluetoothctl-assistant.1 \ doc/bluetoothctl-hci.1 doc/bluetoothctl-telephony.1 \ doc/hid2hci.1 $(am__append_83) plugin_LTLIBRARIES = lib_sources = lib/bluetooth/bluetooth.c lib/bluetooth/hci.c lib/bluetooth/sdp.c lib_headers = lib/bluetooth/bluetooth.h lib/bluetooth/hci.h lib/bluetooth/hci_lib.h \ lib/bluetooth/sco.h lib/bluetooth/l2cap.h lib/bluetooth/sdp.h lib/bluetooth/sdp_lib.h \ lib/bluetooth/rfcomm.h lib/bluetooth/bnep.h lib/bluetooth/cmtp.h lib/bluetooth/hidp.h extra_headers = lib/bluetooth/mgmt.h lib/bluetooth/uuid.h lib/bluetooth/iso.h extra_sources = lib/bluetooth/uuid.c BUILT_SOURCES = $(ell_built_sources) src/builtin.h obexd/src/builtin.h @LIBRARY_TRUE@lib_libbluetooth_la_SOURCES = $(lib_headers) $(lib_sources) @LIBRARY_TRUE@lib_libbluetooth_la_LDFLAGS = $(AM_LDFLAGS) -version-info 22:16:19 lib_libbluetooth_internal_la_SOURCES = $(lib_headers) $(lib_sources) \ $(extra_headers) $(extra_sources) gdbus_libgdbus_internal_la_SOURCES = gdbus/gdbus.h \ gdbus/mainloop.c gdbus/watch.c \ gdbus/object.c gdbus/client.c gdbus/polkit.c @EXTERNAL_ELL_FALSE@ell_cflags = @EXTERNAL_ELL_TRUE@ell_cflags = @ELL_CFLAGS@ @EXTERNAL_ELL_FALSE@ell_ldadd = ell/libell-internal.la @EXTERNAL_ELL_TRUE@ell_ldadd = @ELL_LIBS@ @EXTERNAL_ELL_FALSE@ell_built_sources = ell/shared ell/internal ell/ell.h @EXTERNAL_ELL_TRUE@ell_built_sources = ell/shared @EXTERNAL_ELL_FALSE@ell_headers = ell/util.h \ @EXTERNAL_ELL_FALSE@ ell/log.h \ @EXTERNAL_ELL_FALSE@ ell/queue.h \ @EXTERNAL_ELL_FALSE@ ell/hashmap.h \ @EXTERNAL_ELL_FALSE@ ell/random.h \ @EXTERNAL_ELL_FALSE@ ell/signal.h \ @EXTERNAL_ELL_FALSE@ ell/time.h \ @EXTERNAL_ELL_FALSE@ ell/time-private.h \ @EXTERNAL_ELL_FALSE@ ell/timeout.h \ @EXTERNAL_ELL_FALSE@ ell/cipher.h \ @EXTERNAL_ELL_FALSE@ ell/checksum.h \ @EXTERNAL_ELL_FALSE@ ell/io.h \ @EXTERNAL_ELL_FALSE@ ell/idle.h \ @EXTERNAL_ELL_FALSE@ ell/main.h \ @EXTERNAL_ELL_FALSE@ ell/settings.h \ @EXTERNAL_ELL_FALSE@ ell/strv.h \ @EXTERNAL_ELL_FALSE@ ell/string.h \ @EXTERNAL_ELL_FALSE@ ell/utf8.h \ @EXTERNAL_ELL_FALSE@ ell/dbus.h \ @EXTERNAL_ELL_FALSE@ ell/dbus-service.h \ @EXTERNAL_ELL_FALSE@ ell/dbus-client.h \ @EXTERNAL_ELL_FALSE@ ell/key.h \ @EXTERNAL_ELL_FALSE@ ell/cert.h \ @EXTERNAL_ELL_FALSE@ ell/pem.h \ @EXTERNAL_ELL_FALSE@ ell/base64.h \ @EXTERNAL_ELL_FALSE@ ell/asn1-private.h \ @EXTERNAL_ELL_FALSE@ ell/cert-private.h \ @EXTERNAL_ELL_FALSE@ ell/pem-private.h \ @EXTERNAL_ELL_FALSE@ ell/uuid.h \ @EXTERNAL_ELL_FALSE@ ell/useful.h \ @EXTERNAL_ELL_FALSE@ ell/main-private.h \ @EXTERNAL_ELL_FALSE@ ell/tester.h \ @EXTERNAL_ELL_FALSE@ ell/tls.h \ @EXTERNAL_ELL_FALSE@ ell/tls-private.h \ @EXTERNAL_ELL_FALSE@ ell/ecc.h \ @EXTERNAL_ELL_FALSE@ ell/ecc-private.h \ @EXTERNAL_ELL_FALSE@ ell/cleanup.h \ @EXTERNAL_ELL_FALSE@ ell/ecdh.h @EXTERNAL_ELL_FALSE@ell_sources = ell/private.h ell/missing.h \ @EXTERNAL_ELL_FALSE@ ell/util.c \ @EXTERNAL_ELL_FALSE@ ell/log.c \ @EXTERNAL_ELL_FALSE@ ell/queue.c \ @EXTERNAL_ELL_FALSE@ ell/hashmap.c \ @EXTERNAL_ELL_FALSE@ ell/random.c \ @EXTERNAL_ELL_FALSE@ ell/signal.c \ @EXTERNAL_ELL_FALSE@ ell/time.c \ @EXTERNAL_ELL_FALSE@ ell/timeout.c \ @EXTERNAL_ELL_FALSE@ ell/io.c \ @EXTERNAL_ELL_FALSE@ ell/idle.c \ @EXTERNAL_ELL_FALSE@ ell/main.c \ @EXTERNAL_ELL_FALSE@ ell/settings.c \ @EXTERNAL_ELL_FALSE@ ell/strv.c \ @EXTERNAL_ELL_FALSE@ ell/string.c \ @EXTERNAL_ELL_FALSE@ ell/cipher.c \ @EXTERNAL_ELL_FALSE@ ell/checksum.c \ @EXTERNAL_ELL_FALSE@ ell/pem.c \ @EXTERNAL_ELL_FALSE@ ell/cert.c \ @EXTERNAL_ELL_FALSE@ ell/cert-crypto.c \ @EXTERNAL_ELL_FALSE@ ell/key.c \ @EXTERNAL_ELL_FALSE@ ell/base64.c \ @EXTERNAL_ELL_FALSE@ ell/utf8.c \ @EXTERNAL_ELL_FALSE@ ell/dbus-private.h \ @EXTERNAL_ELL_FALSE@ ell/dbus.c \ @EXTERNAL_ELL_FALSE@ ell/dbus-message.c \ @EXTERNAL_ELL_FALSE@ ell/dbus-util.c \ @EXTERNAL_ELL_FALSE@ ell/dbus-service.c \ @EXTERNAL_ELL_FALSE@ ell/dbus-client.c \ @EXTERNAL_ELL_FALSE@ ell/dbus-name-cache.c \ @EXTERNAL_ELL_FALSE@ ell/dbus-filter.c \ @EXTERNAL_ELL_FALSE@ ell/gvariant-private.h \ @EXTERNAL_ELL_FALSE@ ell/gvariant-util.c \ @EXTERNAL_ELL_FALSE@ ell/siphash-private.h \ @EXTERNAL_ELL_FALSE@ ell/siphash.c \ @EXTERNAL_ELL_FALSE@ ell/uuid.c \ @EXTERNAL_ELL_FALSE@ ell/tester.c \ @EXTERNAL_ELL_FALSE@ ell/tls.c \ @EXTERNAL_ELL_FALSE@ ell/tls-extensions.c \ @EXTERNAL_ELL_FALSE@ ell/tls-suites.c \ @EXTERNAL_ELL_FALSE@ ell/tls-record.c \ @EXTERNAL_ELL_FALSE@ ell/ecc.c \ @EXTERNAL_ELL_FALSE@ ell/ecc-external.c \ @EXTERNAL_ELL_FALSE@ ell/ecdh.c @EXTERNAL_ELL_FALSE@ell_shared = ell/useful.h @EXTERNAL_ELL_FALSE@ell_libell_internal_la_SOURCES = $(ell_headers) $(ell_sources) $(ell_shared) shared_sources = src/shared/io.h src/shared/timeout.h \ src/shared/queue.h src/shared/queue.c src/shared/util.h \ src/shared/util.c src/shared/mgmt.h src/shared/mgmt.c \ src/shared/crypto.h src/shared/crypto.c src/shared/ecc.h \ src/shared/ecc.c src/shared/ringbuf.h src/shared/ringbuf.c \ src/shared/tester.h src/shared/hci.h src/shared/hci.c \ src/shared/hci-crypto.h src/shared/hci-crypto.c \ src/shared/hfp.h src/shared/hfp.c src/shared/uhid.h \ src/shared/uhid.c src/shared/pcap.h src/shared/pcap.c \ src/shared/btsnoop.h src/shared/btsnoop.c src/shared/ad.h \ src/shared/ad.c src/shared/att-types.h src/shared/att.h \ src/shared/att.c src/shared/gatt-helpers.h \ src/shared/gatt-helpers.c src/shared/gatt-client.h \ src/shared/gatt-client.c src/shared/gatt-server.h \ src/shared/gatt-server.c src/shared/gatt-db.h \ src/shared/gatt-db.c src/shared/gap.h src/shared/gap.c \ src/shared/log.h src/shared/log.c src/shared/bap.h \ src/shared/bap.c src/shared/ascs.h src/shared/bap-debug.h \ src/shared/bap-debug.c src/shared/mcs.h src/shared/mcp.h \ src/shared/mcp.c src/shared/vcp.c src/shared/vcp.h \ src/shared/micp.c src/shared/micp.h src/shared/csip.c \ src/shared/csip.h src/shared/bass.h src/shared/bass.c \ src/shared/ccp.h src/shared/ccp.c src/shared/tmap.c \ src/shared/tmap.h src/shared/gmap.c src/shared/gmap.h \ src/shared/lc3.h src/shared/tty.h src/shared/bap-defs.h \ src/shared/asha.h src/shared/asha.c src/shared/battery.h \ src/shared/battery.c src/shared/uinput.h src/shared/uinput.c \ src/shared/rap.h src/shared/rap.c $(am__append_6) src_libshared_glib_la_SOURCES = $(shared_sources) \ src/shared/io-glib.c \ src/shared/timeout-glib.c \ src/shared/mainloop-glib.c \ src/shared/mainloop-notify.h \ src/shared/mainloop-notify.c \ src/shared/tester.c src_libshared_glib_la_LDFLAGS = $(AM_LDFLAGS) src_libshared_glib_la_CFLAGS = $(AM_CFLAGS) src_libshared_mainloop_la_SOURCES = $(shared_sources) \ src/shared/io-mainloop.c \ src/shared/timeout-mainloop.c \ src/shared/mainloop.h src/shared/mainloop.c \ src/shared/mainloop-notify.h \ src/shared/mainloop-notify.c src_libshared_mainloop_la_LDFLAGS = $(AM_LDFLAGS) src_libshared_mainloop_la_CFLAGS = $(AM_CFLAGS) @LIBSHARED_ELL_TRUE@src_libshared_ell_la_SOURCES = $(shared_sources) \ @LIBSHARED_ELL_TRUE@ src/shared/io-ell.c \ @LIBSHARED_ELL_TRUE@ src/shared/timeout-ell.c \ @LIBSHARED_ELL_TRUE@ src/shared/mainloop.h \ @LIBSHARED_ELL_TRUE@ src/shared/mainloop-ell.c @LIBSHARED_ELL_TRUE@src_libshared_ell_la_LDFLAGS = $(AM_LDFLAGS) @LIBSHARED_ELL_TRUE@src_libshared_ell_la_CFLAGS = $(AM_CFLAGS) attrib_sources = attrib/att.h attrib/att-database.h attrib/att.c \ attrib/gatt.h attrib/gatt.c \ attrib/gattrib.h attrib/gattrib.c btio_sources = btio/btio.h btio/btio.c gobex_sources = gobex/gobex.h gobex/gobex.c \ gobex/gobex-defs.h gobex/gobex-defs.c \ gobex/gobex-packet.c gobex/gobex-packet.h \ gobex/gobex-header.c gobex/gobex-header.h \ gobex/gobex-transfer.c gobex/gobex-debug.h \ gobex/gobex-apparam.c gobex/gobex-apparam.h # SPDX-License-Identifier: GPL-2.0 builtin_modules = hostname autopair policy $(am__append_7) \ $(am__append_9) $(am__append_11) $(am__append_13) \ $(am__append_15) $(am__append_17) $(am__append_19) gap \ scanparam deviceinfo $(am__append_21) battery rap \ $(am__append_25) $(am__append_28) $(am__append_30) \ $(am__append_32) $(am__append_34) $(am__append_36) \ $(am__append_38) $(am__append_40) $(am__append_42) \ $(am__append_44) $(am__append_46) $(am__append_48) builtin_sources = plugins/hostname.c plugins/autopair.c \ plugins/policy.c profiles/audio/media.h profiles/audio/media.c \ profiles/audio/transport.h profiles/audio/transport.c \ profiles/audio/player.h profiles/audio/player.c \ $(am__append_8) $(am__append_10) $(am__append_12) \ $(am__append_14) $(am__append_16) $(am__append_18) \ $(am__append_20) profiles/gap/gas.c profiles/scanparam/scan.c \ profiles/deviceinfo/deviceinfo.c $(am__append_22) \ profiles/battery/battery.c profiles/ranging/rap.c \ profiles/ranging/rap_hci.c $(am__append_26) $(am__append_29) \ $(am__append_31) $(am__append_33) $(am__append_35) \ $(am__append_37) $(am__append_39) $(am__append_41) \ $(am__append_43) $(am__append_45) $(am__append_47) \ $(am__append_49) builtin_cppflags = $(am__append_23) builtin_ldadd = $(am__append_24) $(am__append_27) bluetoothd_internal_sources = \ $(attrib_sources) $(btio_sources) \ src/log.h src/log.c \ src/backtrace.h src/backtrace.c \ src/rfkill.c src/btd.h src/sdpd.h \ src/sdpd-server.c src/sdpd-request.c \ src/sdpd-service.c src/sdpd-database.c \ src/gatt-database.h src/gatt-database.c \ src/sdp-xml.h src/sdp-xml.c \ src/sdp-client.h src/sdp-client.c \ src/textfile.h src/textfile.c \ src/uuid-helper.h src/uuid-helper.c \ src/storage.h src/storage.c \ src/advertising.h src/advertising.c \ src/agent.h src/agent.c \ src/error.h src/error.c \ src/adapter.h src/adapter.c \ src/profile.h src/profile.c \ src/service.h src/service.c \ src/gatt-client.h src/gatt-client.c \ src/device.h src/device.c \ src/dbus-common.c src/dbus-common.h \ src/eir.h src/eir.c \ src/adv_monitor.h src/adv_monitor.c \ src/battery.h src/battery.c \ src/settings.h src/settings.c \ src/set.h src/set.c \ src/bearer.h src/bearer.c bluetoothd_internal_ldadd = lib/libbluetooth-internal.la \ gdbus/libgdbus-internal.la \ src/libshared-glib.la \ $(BACKTRACE_LIBS) $(GLIB_LIBS) $(DBUS_LIBS) -ldl -lrt src_bluetoothd_SOURCES = $(builtin_sources) \ $(bluetoothd_internal_sources) src/plugin.h src/plugin.c \ src/main.c $(am__append_50) src_bluetoothd_LDADD = $(bluetoothd_internal_ldadd) \ $(builtin_ldadd) @EXTERNAL_PLUGINS_TRUE@src_bluetoothd_LDFLAGS = $(AM_LDFLAGS) -Wl,--export-dynamic \ @EXTERNAL_PLUGINS_TRUE@ -Wl,--version-script=$(srcdir)/src/bluetooth.ver src_bluetoothd_CPPFLAGS = $(AM_CPPFLAGS) -DBLUETOOTH_PLUGIN_BUILTIN \ -DPLUGINDIR=\""$(build_plugindir)"\" \ $(BACKTRACE_CFLAGS) $(builtin_cppflags) src_bluetoothd_SHORTNAME = bluetoothd builtin_files = src/builtin.h nodist_src_bluetoothd_SOURCES = $(builtin_files) test_scripts = test/bluezutils.py test/dbusdef.py \ test/monitor-bluetooth test/list-devices test/test-discovery \ test/test-manager test/test-adapter test/test-device \ test/simple-agent test/simple-endpoint test/test-network \ test/test-profile test/service-record.dtd test/service-did.xml \ test/service-spp.xml test/service-opp.xml test/service-ftp.xml \ test/simple-player test/test-nap test/test-hfp test/opp-client \ test/ftp-client test/pbap-client test/map-client \ test/example-advertisement test/example-gatt-server \ test/example-gatt-client test/test-gatt-profile test/test-mesh \ test/agent.py unit_tests = unit/test-tester unit/test-eir unit/test-uuid \ unit/test-textfile unit/test-crc unit/test-crypto \ unit/test-ecc unit/test-ringbuf unit/test-queue unit/test-mgmt \ unit/test-uhid unit/test-profile unit/test-sdp unit/test-avdtp \ unit/test-avctp unit/test-avrcp unit/test-hfp \ unit/test-gdbus-client $(am__append_85) unit/test-lib \ unit/test-util unit/test-gatt unit/test-hog unit/test-gattrib \ unit/test-bap unit/test-micp unit/test-bass unit/test-vcp \ unit/test-tmap unit/test-gmap unit/test-mcp unit/test-battery \ unit/test-rap $(am__append_86) $(am__append_87) @CLIENT_TRUE@client_bluetoothctl_SOURCES = client/main.c \ @CLIENT_TRUE@ client/print.h client/print.c \ @CLIENT_TRUE@ client/display.h client/display.c \ @CLIENT_TRUE@ client/agent.h client/agent.c \ @CLIENT_TRUE@ client/advertising.h \ @CLIENT_TRUE@ client/advertising.c \ @CLIENT_TRUE@ client/adv_monitor.h \ @CLIENT_TRUE@ client/adv_monitor.c \ @CLIENT_TRUE@ client/gatt.h client/gatt.c \ @CLIENT_TRUE@ client/admin.h client/admin.c \ @CLIENT_TRUE@ client/player.h client/player.c \ @CLIENT_TRUE@ client/mgmt.h client/mgmt.c \ @CLIENT_TRUE@ client/assistant.h client/assistant.c \ @CLIENT_TRUE@ client/hci.h client/hci.c \ @CLIENT_TRUE@ client/telephony.h client/telephony.c @CLIENT_TRUE@client_bluetoothctl_LDADD = lib/libbluetooth-internal.la \ @CLIENT_TRUE@ gdbus/libgdbus-internal.la src/libshared-glib.la \ @CLIENT_TRUE@ $(GLIB_LIBS) $(DBUS_LIBS) -lreadline @ZSH_COMPLETIONS_TRUE@zshcompletiondir = $(ZSH_COMPLETIONDIR) @ZSH_COMPLETIONS_TRUE@dist_zshcompletion_DATA = completion/zsh/_bluetoothctl @MONITOR_TRUE@monitor_btmon_SOURCES = monitor/main.c monitor/bt.h \ @MONITOR_TRUE@ monitor/display.h monitor/display.c \ @MONITOR_TRUE@ monitor/hcidump.h monitor/hcidump.c \ @MONITOR_TRUE@ monitor/ellisys.h monitor/ellisys.c \ @MONITOR_TRUE@ monitor/control.h monitor/control.c \ @MONITOR_TRUE@ monitor/packet.h monitor/packet.c \ @MONITOR_TRUE@ monitor/vendor.h monitor/vendor.c \ @MONITOR_TRUE@ monitor/lmp.h monitor/lmp.c \ @MONITOR_TRUE@ monitor/crc.h monitor/crc.c \ @MONITOR_TRUE@ monitor/ll.h monitor/ll.c \ @MONITOR_TRUE@ monitor/l2cap.h monitor/l2cap.c \ @MONITOR_TRUE@ monitor/sdp.h monitor/sdp.c \ @MONITOR_TRUE@ monitor/avctp.h monitor/avctp.c \ @MONITOR_TRUE@ monitor/avdtp.h monitor/avdtp.c \ @MONITOR_TRUE@ monitor/a2dp.h monitor/a2dp.c \ @MONITOR_TRUE@ monitor/rfcomm.h monitor/rfcomm.c \ @MONITOR_TRUE@ monitor/bnep.h monitor/bnep.c \ @MONITOR_TRUE@ monitor/hwdb.h monitor/hwdb.c \ @MONITOR_TRUE@ monitor/keys.h monitor/keys.c \ @MONITOR_TRUE@ monitor/analyze.h monitor/analyze.c \ @MONITOR_TRUE@ monitor/intel.h monitor/intel.c \ @MONITOR_TRUE@ monitor/broadcom.h monitor/broadcom.c \ @MONITOR_TRUE@ monitor/msft.h monitor/msft.c \ @MONITOR_TRUE@ monitor/jlink.h monitor/jlink.c \ @MONITOR_TRUE@ monitor/tty.h monitor/emulator.h \ @MONITOR_TRUE@ monitor/att.h monitor/att.c \ @MONITOR_TRUE@ src/log.h src/log.c \ @MONITOR_TRUE@ src/textfile.h src/textfile.c \ @MONITOR_TRUE@ src/settings.h src/settings.c @MONITOR_TRUE@monitor_btmon_LDADD = lib/libbluetooth-internal.la \ @MONITOR_TRUE@ src/libshared-mainloop.la \ @MONITOR_TRUE@ $(GLIB_LIBS) $(UDEV_LIBS) -ldl @LOGGER_TRUE@tools_btmon_logger_SOURCES = tools/btmon-logger.c @LOGGER_TRUE@tools_btmon_logger_LDADD = src/libshared-mainloop.la @TESTING_TRUE@emulator_btvirt_SOURCES = emulator/main.c monitor/bt.h \ @TESTING_TRUE@ emulator/serial.h emulator/serial.c \ @TESTING_TRUE@ emulator/server.h emulator/server.c \ @TESTING_TRUE@ emulator/vhci.h emulator/vhci.c \ @TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ @TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ @TESTING_TRUE@ emulator/smp.c \ @TESTING_TRUE@ emulator/phy.h emulator/phy.c \ @TESTING_TRUE@ emulator/le.h emulator/le.c @TESTING_TRUE@emulator_btvirt_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la @TESTING_TRUE@emulator_b1ee_SOURCES = emulator/b1ee.c @TESTING_TRUE@emulator_b1ee_LDADD = src/libshared-mainloop.la @TESTING_TRUE@emulator_hfp_SOURCES = emulator/hfp.c @TESTING_TRUE@emulator_hfp_LDADD = src/libshared-mainloop.la @TESTING_TRUE@peripheral_btsensor_SOURCES = peripheral/main.c \ @TESTING_TRUE@ peripheral/efivars.h peripheral/efivars.c \ @TESTING_TRUE@ peripheral/attach.h peripheral/attach.c \ @TESTING_TRUE@ peripheral/log.h peripheral/log.c \ @TESTING_TRUE@ peripheral/gap.h peripheral/gap.c \ @TESTING_TRUE@ peripheral/gatt.h peripheral/gatt.c @TESTING_TRUE@peripheral_btsensor_LDADD = src/libshared-mainloop.la \ @TESTING_TRUE@ lib/libbluetooth-internal.la @TESTING_TRUE@tools_3dsp_SOURCES = tools/3dsp.c monitor/bt.h @TESTING_TRUE@tools_3dsp_LDADD = src/libshared-mainloop.la @TESTING_TRUE@tools_mgmt_tester_SOURCES = tools/mgmt-tester.c monitor/bt.h \ @TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ @TESTING_TRUE@ emulator/vhci.h emulator/vhci.c \ @TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ @TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ @TESTING_TRUE@ emulator/smp.c @TESTING_TRUE@tools_mgmt_tester_LDADD = lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) @TESTING_TRUE@tools_mesh_tester_SOURCES = tools/mesh-tester.c monitor/bt.h \ @TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ @TESTING_TRUE@ emulator/vhci.h emulator/vhci.c \ @TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ @TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ @TESTING_TRUE@ emulator/smp.c @TESTING_TRUE@tools_mesh_tester_LDADD = lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) @TESTING_TRUE@tools_l2cap_tester_SOURCES = tools/l2cap-tester.c tools/tester.h monitor/bt.h \ @TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ @TESTING_TRUE@ emulator/vhci.h emulator/vhci.c \ @TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ @TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ @TESTING_TRUE@ emulator/smp.c @TESTING_TRUE@tools_l2cap_tester_LDADD = lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) @TESTING_TRUE@tools_rfcomm_tester_SOURCES = tools/rfcomm-tester.c monitor/bt.h \ @TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ @TESTING_TRUE@ emulator/vhci.h emulator/vhci.c \ @TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ @TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ @TESTING_TRUE@ emulator/smp.c @TESTING_TRUE@tools_rfcomm_tester_LDADD = lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) @TESTING_TRUE@tools_bnep_tester_SOURCES = tools/bnep-tester.c monitor/bt.h \ @TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ @TESTING_TRUE@ emulator/vhci.h emulator/vhci.c \ @TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ @TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ @TESTING_TRUE@ emulator/smp.c @TESTING_TRUE@tools_bnep_tester_LDADD = lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) @TESTING_TRUE@tools_smp_tester_SOURCES = tools/smp-tester.c monitor/bt.h \ @TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ @TESTING_TRUE@ emulator/vhci.h emulator/vhci.c \ @TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ @TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ @TESTING_TRUE@ emulator/smp.c @TESTING_TRUE@tools_smp_tester_LDADD = lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) @TESTING_TRUE@tools_gap_tester_SOURCES = tools/gap-tester.c monitor/bt.h \ @TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ @TESTING_TRUE@ emulator/vhci.h emulator/vhci.c \ @TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ @TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ @TESTING_TRUE@ emulator/smp.c @TESTING_TRUE@tools_gap_tester_LDADD = lib/libbluetooth-internal.la \ @TESTING_TRUE@ gdbus/libgdbus-internal.la \ @TESTING_TRUE@ src/libshared-glib.la \ @TESTING_TRUE@ $(GLIB_LIBS) $(DBUS_LIBS) @TESTING_TRUE@tools_sco_tester_SOURCES = tools/sco-tester.c tools/tester.h monitor/bt.h \ @TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ @TESTING_TRUE@ emulator/vhci.h emulator/vhci.c \ @TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ @TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ @TESTING_TRUE@ emulator/smp.c @TESTING_TRUE@tools_sco_tester_LDADD = lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) @TESTING_TRUE@tools_hci_tester_SOURCES = tools/hci-tester.c monitor/bt.h @TESTING_TRUE@tools_hci_tester_LDADD = src/libshared-glib.la $(GLIB_LIBS) @TESTING_TRUE@tools_userchan_tester_SOURCES = tools/userchan-tester.c monitor/bt.h \ @TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ @TESTING_TRUE@ emulator/vhci.h emulator/vhci.c \ @TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ @TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ @TESTING_TRUE@ emulator/smp.c @TESTING_TRUE@tools_userchan_tester_LDADD = lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) @TESTING_TRUE@tools_iso_tester_SOURCES = tools/iso-tester.c tools/tester.h monitor/bt.h \ @TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ @TESTING_TRUE@ emulator/vhci.h emulator/vhci.c \ @TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ @TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ @TESTING_TRUE@ emulator/smp.c @TESTING_TRUE@tools_iso_tester_LDADD = lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) @TESTING_TRUE@tools_ioctl_tester_SOURCES = tools/ioctl-tester.c monitor/bt.h \ @TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ @TESTING_TRUE@ emulator/vhci.h emulator/vhci.c \ @TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ @TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ @TESTING_TRUE@ emulator/smp.c @TESTING_TRUE@tools_ioctl_tester_LDADD = lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) @TESTING_TRUE@tools_6lowpan_tester_SOURCES = tools/6lowpan-tester.c monitor/bt.h \ @TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ @TESTING_TRUE@ emulator/vhci.h emulator/vhci.c \ @TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ @TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ @TESTING_TRUE@ emulator/smp.c @TESTING_TRUE@tools_6lowpan_tester_LDADD = lib/libbluetooth-internal.la \ @TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) @TOOLS_TRUE@tools_bdaddr_SOURCES = tools/bdaddr.c src/oui.h src/oui.c @TOOLS_TRUE@tools_bdaddr_LDADD = lib/libbluetooth-internal.la $(UDEV_LIBS) @TOOLS_TRUE@tools_avinfo_LDADD = lib/libbluetooth-internal.la @TOOLS_TRUE@tools_avtest_LDADD = lib/libbluetooth-internal.la @TOOLS_TRUE@tools_scotest_LDADD = lib/libbluetooth-internal.la @TOOLS_TRUE@tools_hwdb_LDADD = lib/libbluetooth-internal.la @TOOLS_TRUE@tools_hcieventmask_LDADD = lib/libbluetooth-internal.la @TOOLS_TRUE@tools_btinfo_SOURCES = tools/btinfo.c monitor/bt.h @TOOLS_TRUE@tools_btinfo_LDADD = src/libshared-mainloop.la @TOOLS_TRUE@tools_btattach_SOURCES = tools/btattach.c monitor/bt.h @TOOLS_TRUE@tools_btattach_LDADD = src/libshared-mainloop.la @TOOLS_TRUE@tools_btconfig_SOURCES = tools/btconfig.c @TOOLS_TRUE@tools_btconfig_LDADD = src/libshared-mainloop.la @TOOLS_TRUE@tools_btsnoop_SOURCES = tools/btsnoop.c @TOOLS_TRUE@tools_btsnoop_LDADD = src/libshared-mainloop.la @TOOLS_TRUE@tools_btproxy_SOURCES = tools/btproxy.c monitor/bt.h @TOOLS_TRUE@tools_btproxy_LDADD = src/libshared-mainloop.la @TOOLS_TRUE@tools_btiotest_SOURCES = tools/btiotest.c btio/btio.h btio/btio.c @TOOLS_TRUE@tools_btiotest_LDADD = lib/libbluetooth-internal.la $(GLIB_LIBS) @TOOLS_TRUE@tools_bneptest_SOURCES = tools/bneptest.c \ @TOOLS_TRUE@ btio/btio.h btio/btio.c \ @TOOLS_TRUE@ src/log.h src/log.c \ @TOOLS_TRUE@ profiles/network/bnep.h profiles/network/bnep.c @TOOLS_TRUE@tools_bneptest_LDADD = lib/libbluetooth-internal.la $(GLIB_LIBS) \ @TOOLS_TRUE@ src/libshared-mainloop.la @TOOLS_TRUE@tools_cltest_SOURCES = tools/cltest.c @TOOLS_TRUE@tools_cltest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la @TOOLS_TRUE@tools_oobtest_SOURCES = tools/oobtest.c @TOOLS_TRUE@tools_oobtest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la @TOOLS_TRUE@tools_advtest_SOURCES = tools/advtest.c @TOOLS_TRUE@tools_advtest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la @TOOLS_TRUE@tools_seq2bseq_SOURCES = tools/seq2bseq.c @TOOLS_TRUE@tools_nokfw_SOURCES = tools/nokfw.c @TOOLS_TRUE@tools_rtlfw_SOURCES = tools/rtlfw.c @TOOLS_TRUE@tools_create_image_SOURCES = tools/create-image.c @TOOLS_TRUE@tools_eddystone_SOURCES = tools/eddystone.c monitor/bt.h @TOOLS_TRUE@tools_eddystone_LDADD = src/libshared-mainloop.la @TOOLS_TRUE@tools_ibeacon_SOURCES = tools/ibeacon.c monitor/bt.h @TOOLS_TRUE@tools_ibeacon_LDADD = src/libshared-mainloop.la @TOOLS_TRUE@tools_btgatt_client_SOURCES = tools/btgatt-client.c src/uuid-helper.c @TOOLS_TRUE@tools_btgatt_client_LDADD = src/libshared-mainloop.la \ @TOOLS_TRUE@ lib/libbluetooth-internal.la @TOOLS_TRUE@tools_btgatt_server_SOURCES = tools/btgatt-server.c src/uuid-helper.c @TOOLS_TRUE@tools_btgatt_server_LDADD = src/libshared-mainloop.la \ @TOOLS_TRUE@ lib/libbluetooth-internal.la @TOOLS_TRUE@tools_test_runner_CFLAGS = $(WARNING_CFLAGS) @TOOLS_TRUE@tools_test_runner_LDFLAGS = @TOOLS_TRUE@tools_rctest_LDADD = lib/libbluetooth-internal.la @TOOLS_TRUE@tools_l2test_LDADD = lib/libbluetooth-internal.la @TOOLS_TRUE@tools_l2ping_LDADD = lib/libbluetooth-internal.la @TOOLS_TRUE@tools_bluemoon_SOURCES = tools/bluemoon.c monitor/bt.h @TOOLS_TRUE@tools_bluemoon_LDADD = src/libshared-mainloop.la @TOOLS_TRUE@tools_hex2hcd_SOURCES = tools/hex2hcd.c tools/missing.h @TOOLS_TRUE@tools_mpris_proxy_SOURCES = tools/mpris-proxy.c @TOOLS_TRUE@tools_mpris_proxy_LDADD = gdbus/libgdbus-internal.la $(GLIB_LIBS) $(DBUS_LIBS) @TOOLS_TRUE@tools_gatt_service_SOURCES = tools/gatt-service.c @TOOLS_TRUE@tools_gatt_service_LDADD = gdbus/libgdbus-internal.la \ @TOOLS_TRUE@ src/libshared-mainloop.la $(GLIB_LIBS) $(DBUS_LIBS) @TOOLS_TRUE@tools_isotest_LDADD = lib/libbluetooth-internal.la @TOOLS_TRUE@profiles_iap_iapd_SOURCES = profiles/iap/main.c @TOOLS_TRUE@profiles_iap_iapd_LDADD = gdbus/libgdbus-internal.la $(GLIB_LIBS) $(DBUS_LIBS) @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@tools_meshctl_SOURCES = tools/meshctl.c \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/agent.h tools/mesh/agent.c \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/config-model.h\ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/mesh-net.h \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/node.h tools/mesh-gatt/node.c \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/gatt.h tools/mesh-gatt/gatt.c \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/crypto.h\ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/crypto.c \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/keys.h \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/net.h tools/mesh-gatt/net.c \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/prov.h tools/mesh-gatt/prov.c \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/util.h tools/mesh-gatt/util.c \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/prov-db.h \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/prov-db.c \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/config-client.c \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/config-server.c \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/onoff-model.h \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ tools/mesh-gatt/onoff-model.c @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@tools_meshctl_LDADD = gdbus/libgdbus-internal.la src/libshared-glib.la \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la \ @DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@ $(GLIB_LIBS) $(DBUS_LIBS) -ljson-c -lreadline @MESH_TRUE@@TOOLS_TRUE@tools_mesh_cfgclient_SOURCES = tools/mesh-cfgclient.c \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh/model.h tools/mesh/config-model.h \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh/cfgcli.h tools/mesh/cfgcli.c \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh/keys.h tools/mesh/keys.c \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh/util.h tools/mesh/util.c \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh/remote.h tools/mesh/remote.c \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh/agent.h tools/mesh/agent.c \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh/mesh-db.h tools/mesh/mesh-db.c \ @MESH_TRUE@@TOOLS_TRUE@ mesh/util.h mesh/util.c \ @MESH_TRUE@@TOOLS_TRUE@ mesh/crypto.h mesh/crypto.c @MESH_TRUE@@TOOLS_TRUE@tools_mesh_cfgclient_LDADD = lib/libbluetooth-internal.la src/libshared-ell.la \ @MESH_TRUE@@TOOLS_TRUE@ $(ell_ldadd) -ljson-c -lreadline @MESH_TRUE@@TOOLS_TRUE@tools_mesh_cfgtest_SOURCES = tools/mesh-cfgtest.c @MESH_TRUE@@TOOLS_TRUE@tools_mesh_cfgtest_LDADD = lib/libbluetooth-internal.la src/libshared-ell.la \ @MESH_TRUE@@TOOLS_TRUE@ $(ell_ldadd) @DEPRECATED_TRUE@@TOOLS_TRUE@tools_hciattach_SOURCES = tools/hciattach.c tools/hciattach.h \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_st.c \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_ti.c \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_tialt.c \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_ath3k.c \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_qualcomm.c \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_intel.c \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_bcm43xx.c @DEPRECATED_TRUE@@TOOLS_TRUE@tools_hciattach_LDADD = lib/libbluetooth-internal.la @DEPRECATED_TRUE@@TOOLS_TRUE@tools_hciconfig_SOURCES = tools/hciconfig.c @DEPRECATED_TRUE@@TOOLS_TRUE@tools_hciconfig_LDADD = lib/libbluetooth-internal.la @DEPRECATED_TRUE@@TOOLS_TRUE@tools_hcitool_SOURCES = tools/hcitool.c src/oui.h src/oui.c @DEPRECATED_TRUE@@TOOLS_TRUE@tools_hcitool_LDADD = lib/libbluetooth-internal.la $(UDEV_LIBS) @DEPRECATED_TRUE@@TOOLS_TRUE@tools_sdptool_SOURCES = tools/sdptool.c src/sdp-xml.h src/sdp-xml.c @DEPRECATED_TRUE@@TOOLS_TRUE@tools_sdptool_LDADD = lib/libbluetooth-internal.la $(GLIB_LIBS) @DEPRECATED_TRUE@@TOOLS_TRUE@tools_ciptool_LDADD = lib/libbluetooth-internal.la @DEPRECATED_TRUE@@TOOLS_TRUE@tools_rfcomm_LDADD = lib/libbluetooth-internal.la @HID2HCI_TRUE@udevdir = $(UDEV_DIR) @HID2HCI_TRUE@tools_hid2hci_LDADD = $(UDEV_LIBS) @READLINE_TRUE@tools_obex_client_tool_SOURCES = $(gobex_sources) $(btio_sources) \ @READLINE_TRUE@ tools/obex-client-tool.c @READLINE_TRUE@tools_obex_client_tool_LDADD = lib/libbluetooth-internal.la \ @READLINE_TRUE@ src/libshared-glib.la $(GLIB_LIBS) -lreadline @READLINE_TRUE@tools_obex_server_tool_SOURCES = $(gobex_sources) $(btio_sources) \ @READLINE_TRUE@ tools/obex-server-tool.c @READLINE_TRUE@tools_obex_server_tool_LDADD = lib/libbluetooth-internal.la \ @READLINE_TRUE@ src/libshared-glib.la $(GLIB_LIBS) @READLINE_TRUE@tools_bluetooth_player_SOURCES = tools/bluetooth-player.c client/print.c \ @READLINE_TRUE@ client/player.c @READLINE_TRUE@tools_bluetooth_player_LDADD = gdbus/libgdbus-internal.la \ @READLINE_TRUE@ src/libshared-glib.la \ @READLINE_TRUE@ $(GLIB_LIBS) $(DBUS_LIBS) -lreadline @READLINE_TRUE@tools_obexctl_SOURCES = tools/obexctl.c @READLINE_TRUE@tools_obexctl_LDADD = gdbus/libgdbus-internal.la src/libshared-glib.la \ @READLINE_TRUE@ $(GLIB_LIBS) $(DBUS_LIBS) -lreadline @READLINE_TRUE@tools_btmgmt_SOURCES = tools/btmgmt.c src/uuid-helper.c client/display.c \ @READLINE_TRUE@ client/mgmt.c @READLINE_TRUE@tools_btmgmt_LDADD = lib/libbluetooth-internal.la src/libshared-glib.la \ @READLINE_TRUE@ $(GLIB_LIBS) -lreadline @DEPRECATED_TRUE@@READLINE_TRUE@attrib_gatttool_SOURCES = attrib/gatttool.c attrib/att.c attrib/gatt.c \ @DEPRECATED_TRUE@@READLINE_TRUE@ attrib/gattrib.c btio/btio.c \ @DEPRECATED_TRUE@@READLINE_TRUE@ attrib/gatttool.h attrib/interactive.c \ @DEPRECATED_TRUE@@READLINE_TRUE@ attrib/utils.c src/log.c client/display.c \ @DEPRECATED_TRUE@@READLINE_TRUE@ client/display.h @DEPRECATED_TRUE@@READLINE_TRUE@attrib_gatttool_LDADD = lib/libbluetooth-internal.la \ @DEPRECATED_TRUE@@READLINE_TRUE@ src/libshared-glib.la $(GLIB_LIBS) -lreadline @CUPS_SERVERBIN_FALSE@@CUPS_TRUE@cupsdir = $(libdir)/cups/backend @CUPS_SERVERBIN_TRUE@@CUPS_TRUE@cupsdir = $(CUPS_SERVERBIN)/backend @CUPS_TRUE@profiles_cups_bluetooth_SOURCES = profiles/cups/main.c \ @CUPS_TRUE@ profiles/cups/cups.h \ @CUPS_TRUE@ profiles/cups/sdp.c \ @CUPS_TRUE@ profiles/cups/spp.c \ @CUPS_TRUE@ profiles/cups/hcrp.c @CUPS_TRUE@profiles_cups_bluetooth_LDADD = $(GLIB_LIBS) $(DBUS_LIBS) \ @CUPS_TRUE@ lib/libbluetooth-internal.la \ @CUPS_TRUE@ gdbus/libgdbus-internal.la @BTPCLIENT_TRUE@client_btpclient_btpclient_SOURCES = client/btpclient/btpclient.c \ @BTPCLIENT_TRUE@ client/btpclient/btpclient.h \ @BTPCLIENT_TRUE@ src/shared/btp.c src/shared/btp.h \ @BTPCLIENT_TRUE@ client/btpclient/bap.c client/btpclient/bap.h \ @BTPCLIENT_TRUE@ client/btpclient/core.c client/btpclient/core.h \ @BTPCLIENT_TRUE@ client/btpclient/gap.c client/btpclient/gap.h \ @BTPCLIENT_TRUE@ client/btpclient/gatt.c client/btpclient/gatt.h @BTPCLIENT_TRUE@client_btpclient_btpclient_LDADD = lib/libbluetooth-internal.la \ @BTPCLIENT_TRUE@ src/libshared-ell.la $(ell_ldadd) @BTPCLIENT_TRUE@client_btpclient_btpclientctl_SOURCES = client/btpclient/btpclientctl.c client/display.c @BTPCLIENT_TRUE@client_btpclient_btpclientctl_LDADD = src/libshared-mainloop.la src/libshared-glib.la \ @BTPCLIENT_TRUE@ lib/libbluetooth-internal.la -lreadline @OBEX_TRUE@dbussessionbusdir = $(DBUS_SESSIONBUSDIR) @OBEX_TRUE@dbussessionbus_DATA = obexd/src/org.bluez.obex.service @OBEX_TRUE@obex_plugindir = $(libdir)/obex/plugins @OBEX_TRUE@obexd_builtin_modules = filesystem bluetooth \ @OBEX_TRUE@ $(am__append_71) opp ftp irmc pbap mas mns @OBEX_TRUE@obexd_builtin_sources = obexd/plugins/filesystem.c \ @OBEX_TRUE@ obexd/plugins/filesystem.h \ @OBEX_TRUE@ obexd/plugins/bluetooth.c $(am__append_72) \ @OBEX_TRUE@ obexd/plugins/opp.c obexd/plugins/ftp.c \ @OBEX_TRUE@ obexd/plugins/ftp.h obexd/plugins/irmc.c \ @OBEX_TRUE@ obexd/plugins/pbap.c obexd/plugins/vcard.h \ @OBEX_TRUE@ obexd/plugins/vcard.c obexd/plugins/phonebook.h \ @OBEX_TRUE@ obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c \ @OBEX_TRUE@ obexd/plugins/mas.c obexd/src/map_ap.h \ @OBEX_TRUE@ obexd/plugins/messages.h \ @OBEX_TRUE@ obexd/plugins/messages-dummy.c obexd/client/mns.c \ @OBEX_TRUE@ obexd/src/map_ap.h obexd/client/map-event.h @OBEX_TRUE@obexd_builtin_nodist = @OBEX_TRUE@obexd_src_obexd_SOURCES = $(btio_sources) $(gobex_sources) \ @OBEX_TRUE@ $(obexd_builtin_sources) \ @OBEX_TRUE@ obexd/src/main.c obexd/src/obexd.h \ @OBEX_TRUE@ obexd/src/plugin.h obexd/src/plugin.c \ @OBEX_TRUE@ obexd/src/log.h obexd/src/log.c \ @OBEX_TRUE@ obexd/src/logind.h obexd/src/logind.c \ @OBEX_TRUE@ obexd/src/manager.h obexd/src/manager.c \ @OBEX_TRUE@ obexd/src/obex.h obexd/src/obex.c obexd/src/obex-priv.h \ @OBEX_TRUE@ obexd/src/mimetype.h obexd/src/mimetype.c \ @OBEX_TRUE@ obexd/src/service.h obexd/src/service.c \ @OBEX_TRUE@ obexd/src/transport.h obexd/src/transport.c \ @OBEX_TRUE@ obexd/src/server.h obexd/src/server.c \ @OBEX_TRUE@ obexd/client/manager.h obexd/client/manager.c \ @OBEX_TRUE@ obexd/client/session.h obexd/client/session.c \ @OBEX_TRUE@ obexd/client/bluetooth.h obexd/client/bluetooth.c \ @OBEX_TRUE@ obexd/client/sync.h obexd/client/sync.c \ @OBEX_TRUE@ obexd/client/pbap.h obexd/client/pbap.c \ @OBEX_TRUE@ obexd/client/ftp.h obexd/client/ftp.c \ @OBEX_TRUE@ obexd/client/opp.h obexd/client/opp.c \ @OBEX_TRUE@ obexd/client/map.h obexd/client/map.c \ @OBEX_TRUE@ obexd/client/bip.h obexd/client/bip.c \ @OBEX_TRUE@ obexd/client/bip-common.h obexd/client/bip-common.c \ @OBEX_TRUE@ obexd/client/map-event.h obexd/client/map-event.c \ @OBEX_TRUE@ obexd/client/transfer.h obexd/client/transfer.c \ @OBEX_TRUE@ obexd/client/transport.h obexd/client/transport.c \ @OBEX_TRUE@ obexd/client/driver.h obexd/client/driver.c \ @OBEX_TRUE@ obexd/src/map_ap.h @OBEX_TRUE@obexd_src_obexd_LDADD = lib/libbluetooth-internal.la \ @OBEX_TRUE@ gdbus/libgdbus-internal.la \ @OBEX_TRUE@ src/libshared-glib.la \ @OBEX_TRUE@ $(ICAL_LIBS) $(DBUS_LIBS) $(LIBEBOOK_LIBS) \ @OBEX_TRUE@ $(LIBEDATASERVER_LIBS) $(GLIB_LIBS) -ldl @EXTERNAL_PLUGINS_FALSE@@OBEX_TRUE@obexd_src_obexd_LDFLAGS = \ @EXTERNAL_PLUGINS_FALSE@@OBEX_TRUE@ $(am__append_76) @EXTERNAL_PLUGINS_TRUE@@OBEX_TRUE@obexd_src_obexd_LDFLAGS = \ @EXTERNAL_PLUGINS_TRUE@@OBEX_TRUE@ $(AM_LDFLAGS) \ @EXTERNAL_PLUGINS_TRUE@@OBEX_TRUE@ -Wl,--export-dynamic \ @EXTERNAL_PLUGINS_TRUE@@OBEX_TRUE@ $(am__append_76) @OBEX_TRUE@obexd_src_obexd_CPPFLAGS = $(AM_CPPFLAGS) $(GLIB_CFLAGS) \ @OBEX_TRUE@ $(DBUS_CFLAGS) $(ICAL_CFLAGS) -DOBEX_PLUGIN_BUILTIN \ @OBEX_TRUE@ -DPLUGINDIR=\""$(obex_plugindir)"\" \ @OBEX_TRUE@ -D_FILE_OFFSET_BITS=64 -I$(builddir)/obexd/src \ @OBEX_TRUE@ $(am__append_75) obexd_src_obexd_SHORTNAME = obexd obexd_builtin_files = obexd/src/builtin.h $(obexd_builtin_nodist) nodist_obexd_src_obexd_SOURCES = $(obexd_builtin_files) @MESH_TRUE@mesh_sources = mesh/mesh.h mesh/mesh.c \ @MESH_TRUE@ mesh/net-keys.h mesh/net-keys.c \ @MESH_TRUE@ mesh/mesh-io.h mesh/mesh-io.c \ @MESH_TRUE@ mesh/mesh-mgmt.h mesh/mesh-mgmt.c \ @MESH_TRUE@ mesh/error.h mesh/mesh-io-api.h \ @MESH_TRUE@ mesh/mesh-io-unit.h mesh/mesh-io-unit.c \ @MESH_TRUE@ mesh/mesh-io-mgmt.h mesh/mesh-io-mgmt.c \ @MESH_TRUE@ mesh/mesh-io-generic.h mesh/mesh-io-generic.c \ @MESH_TRUE@ mesh/net.h mesh/net.c \ @MESH_TRUE@ mesh/crypto.h mesh/crypto.c \ @MESH_TRUE@ mesh/friend.h mesh/friend.c \ @MESH_TRUE@ mesh/appkey.h mesh/appkey.c \ @MESH_TRUE@ mesh/node.h mesh/node.c \ @MESH_TRUE@ mesh/provision.h mesh/prov.h \ @MESH_TRUE@ mesh/model.h mesh/model.c \ @MESH_TRUE@ mesh/cfgmod.h mesh/cfgmod-server.c \ @MESH_TRUE@ mesh/remprv.h mesh/remprv-server.c \ @MESH_TRUE@ mesh/mesh-config.h mesh/mesh-config-json.c \ @MESH_TRUE@ mesh/util.h mesh/util.c \ @MESH_TRUE@ mesh/dbus.h mesh/dbus.c \ @MESH_TRUE@ mesh/agent.h mesh/agent.c \ @MESH_TRUE@ mesh/prov-acceptor.c mesh/prov-initiator.c \ @MESH_TRUE@ mesh/manager.h mesh/manager.c \ @MESH_TRUE@ mesh/pb-adv.h mesh/pb-adv.c \ @MESH_TRUE@ mesh/keyring.h mesh/keyring.c \ @MESH_TRUE@ mesh/rpl.h mesh/rpl.c \ @MESH_TRUE@ mesh/prv-beacon.h mesh/prvbeac-server.c \ @MESH_TRUE@ mesh/mesh-defs.h @MESH_TRUE@mesh_bluetooth_meshd_SOURCES = $(mesh_sources) mesh/main.c @MESH_TRUE@mesh_bluetooth_meshd_LDADD = src/libshared-ell.la $(ell_ldadd) -ljson-c @HID2HCI_TRUE@rulesdir = $(UDEV_DIR)/rules.d @HID2HCI_TRUE@rules_DATA = tools/97-hid2hci.rules @TEST_TRUE@testdir = $(pkglibdir)/test @TEST_TRUE@test_SCRIPTS = $(test_scripts) unit_test_tester_SOURCES = unit/test-tester.c unit_test_tester_LDADD = src/libshared-glib.la lib/libbluetooth-internal.la \ $(GLIB_LIBS) unit_test_eir_SOURCES = unit/test-eir.c src/eir.c src/uuid-helper.c unit_test_eir_LDADD = src/libshared-glib.la lib/libbluetooth-internal.la \ $(GLIB_LIBS) unit_test_uuid_SOURCES = unit/test-uuid.c unit_test_uuid_LDADD = src/libshared-glib.la lib/libbluetooth-internal.la \ $(GLIB_LIBS) unit_test_textfile_SOURCES = unit/test-textfile.c src/textfile.h src/textfile.c unit_test_textfile_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_crc_SOURCES = unit/test-crc.c monitor/crc.h monitor/crc.c unit_test_crc_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_crypto_SOURCES = unit/test-crypto.c unit_test_crypto_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_ecc_SOURCES = unit/test-ecc.c unit_test_ecc_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_ringbuf_SOURCES = unit/test-ringbuf.c unit_test_ringbuf_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_queue_SOURCES = unit/test-queue.c unit_test_queue_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_mgmt_SOURCES = unit/test-mgmt.c unit_test_mgmt_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_uhid_SOURCES = unit/test-uhid.c unit_test_uhid_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_profile_SOURCES = unit/test-profile.c \ unit/btd.c $(bluetoothd_internal_sources) unit_test_profile_LDADD = $(bluetoothd_internal_ldadd) unit_test_sdp_SOURCES = unit/test-sdp.c \ src/sdpd.h src/sdpd-database.c \ src/log.h src/log.c \ src/sdpd-service.c src/sdpd-request.c unit_test_sdp_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) unit_test_avdtp_SOURCES = unit/test-avdtp.c \ src/log.h src/log.c \ unit/avdtp.c unit/avdtp.h unit_test_avdtp_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_avctp_SOURCES = unit/test-avctp.c \ src/log.h src/log.c \ unit/avctp.c unit/avctp.h unit_test_avctp_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_avrcp_SOURCES = unit/test-avrcp.c \ src/log.h src/log.c \ unit/avctp.c unit/avctp.h \ unit/avrcp-lib.c unit/avrcp-lib.h unit_test_avrcp_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) unit_test_hfp_SOURCES = unit/test-hfp.c unit_test_hfp_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_gdbus_client_SOURCES = unit/test-gdbus-client.c unit_test_gdbus_client_LDADD = gdbus/libgdbus-internal.la \ src/libshared-glib.la $(GLIB_LIBS) $(DBUS_LIBS) @OBEX_TRUE@unit_test_gobex_SOURCES = $(gobex_sources) unit/util.c unit/util.h \ @OBEX_TRUE@ unit/test-gobex.c @OBEX_TRUE@unit_test_gobex_LDADD = src/libshared-glib.la $(GLIB_LIBS) @OBEX_TRUE@unit_test_gobex_packet_SOURCES = $(gobex_sources) unit/util.c unit/util.h \ @OBEX_TRUE@ unit/test-gobex-packet.c @OBEX_TRUE@unit_test_gobex_packet_LDADD = src/libshared-glib.la $(GLIB_LIBS) @OBEX_TRUE@unit_test_gobex_header_SOURCES = $(gobex_sources) unit/util.c unit/util.h \ @OBEX_TRUE@ unit/test-gobex-header.c @OBEX_TRUE@unit_test_gobex_header_LDADD = src/libshared-glib.la $(GLIB_LIBS) @OBEX_TRUE@unit_test_gobex_transfer_SOURCES = $(gobex_sources) unit/util.c unit/util.h \ @OBEX_TRUE@ unit/test-gobex-transfer.c @OBEX_TRUE@unit_test_gobex_transfer_LDADD = src/libshared-glib.la $(GLIB_LIBS) @OBEX_TRUE@unit_test_gobex_apparam_SOURCES = $(gobex_sources) unit/util.c unit/util.h \ @OBEX_TRUE@ unit/test-gobex-apparam.c @OBEX_TRUE@unit_test_gobex_apparam_LDADD = src/libshared-glib.la $(GLIB_LIBS) unit_test_lib_SOURCES = unit/test-lib.c unit_test_lib_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_test_util_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_test_gatt_SOURCES = unit/test-gatt.c unit_test_gatt_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_test_hog_SOURCES = unit/test-hog.c \ $(btio_sources) \ profiles/input/hog-lib.h profiles/input/hog-lib.c \ profiles/scanparam/scpp.h profiles/scanparam/scpp.c \ profiles/battery/bas.h profiles/battery/bas.c \ profiles/deviceinfo/dis.h profiles/deviceinfo/dis.c \ src/log.h src/log.c \ attrib/att.h attrib/att.c \ attrib/gatt.h attrib/gatt.c \ attrib/gattrib.h attrib/gattrib.c unit_test_hog_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_test_gattrib_SOURCES = unit/test-gattrib.c attrib/gattrib.c \ $(btio_sources) src/log.h src/log.c unit_test_gattrib_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la \ $(GLIB_LIBS) $(DBUS_LIBS) -ldl -lrt unit_test_bap_SOURCES = unit/test-bap.c unit_test_bap_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_test_micp_SOURCES = unit/test-micp.c unit_test_micp_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_test_bass_SOURCES = unit/test-bass.c $(btio_sources) unit_test_bass_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_test_vcp_SOURCES = unit/test-vcp.c $(btio_sources) unit_test_vcp_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_test_tmap_SOURCES = unit/test-tmap.c $(btio_sources) unit_test_tmap_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_test_gmap_SOURCES = unit/test-gmap.c $(btio_sources) unit_test_gmap_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_test_mcp_SOURCES = unit/test-mcp.c $(btio_sources) unit_test_mcp_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_test_battery_SOURCES = unit/test-battery.c unit_test_battery_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) unit_test_rap_SOURCES = unit/test-rap.c $(btio_sources) unit_test_rap_LDADD = src/libshared-glib.la \ lib/libbluetooth-internal.la $(GLIB_LIBS) @MIDI_TRUE@unit_test_midi_CPPFLAGS = $(AM_CPPFLAGS) $(ALSA_CFLAGS) -DMIDI_TEST @MIDI_TRUE@unit_test_midi_SOURCES = unit/test-midi.c \ @MIDI_TRUE@ profiles/midi/libmidi.h \ @MIDI_TRUE@ profiles/midi/libmidi.c @MIDI_TRUE@unit_test_midi_LDADD = src/libshared-glib.la \ @MIDI_TRUE@ $(GLIB_LIBS) $(ALSA_LIBS) @MESH_TRUE@unit_test_mesh_crypto_CPPFLAGS = $(ell_cflags) @MESH_TRUE@unit_test_mesh_crypto_SOURCES = unit/test-mesh-crypto.c \ @MESH_TRUE@ mesh/crypto.h ell/internal ell/ell.h @MESH_TRUE@unit_test_mesh_crypto_LDADD = $(ell_ldadd) AM_TESTS_ENVIRONMENT = MALLOC_CHECK_=3 MALLOC_PERTURB_=69 \ $(am__append_89) @VALGRIND_TRUE@LOG_COMPILER = valgrind --error-exitcode=1 --num-callers=30 @VALGRIND_TRUE@LOG_FLAGS = --trace-children=yes --leak-check=full --show-reachable=no \ @VALGRIND_TRUE@ --suppressions=$(srcdir)/tools/valgrind.supp --quiet pkgconfigdir = $(libdir)/pkgconfig @LIBRARY_TRUE@pkgconfig_DATA = lib/bluez.pc # pkg-config gives absolute paths that do not work with distcheck that wants to use its own $(prefix) AM_DISTCHECK_CONFIGURE_FLAGS = --disable-datafiles --enable-library \ --enable-midi \ --enable-manpages \ --enable-mesh \ --enable-btpclient \ --disable-systemd \ --disable-udev \ --disable-cups \ --with-dbusconfdir=$${dc_install_base}/$(DBUS_CONFDIR) \ --with-dbussystembusdir=$${dc_install_base}/$(DBUS_SYSTEMBUSDIR) \ --with-dbussessionbusdir=$${dc_install_base}/$(DBUS_SESSIONBUSDIR) DISTCLEANFILES = $(pkgconfig_DATA) $(unit_tests) $(manual_pages) MAINTAINERCLEANFILES = Makefile.in \ aclocal.m4 configure config.h.in config.sub config.guess \ ltmain.sh depcomp compile missing install-sh mkinstalldirs test-driver @RUN_RST2MAN_FALSE@RST2MAN_PROCESS = $(AM_V_GEN)test -f $@ || \ @RUN_RST2MAN_FALSE@ { echo "Generated manual page $@ does not exist"; false; } @RUN_RST2MAN_TRUE@RST2MAN_PROCESS = $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ @RUN_RST2MAN_TRUE@ $(RST2MAN) --strict --no-raw \ @RUN_RST2MAN_TRUE@ --no-generator --no-datestamp $< $@ all: $(BUILT_SOURCES) config.h $(MAKE) $(AM_MAKEFLAGS) all-am .SUFFIXES: .SUFFIXES: .c .lo .log .o .obj .test .test$(EXEEXT) .trs am--refresh: Makefile @: $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(srcdir)/Makefile.plugins $(srcdir)/Makefile.tools $(srcdir)/Makefile.obexd $(srcdir)/Makefile.mesh $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \ $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \ && exit 0; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ echo ' $(SHELL) ./config.status'; \ $(SHELL) ./config.status;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles);; \ esac; $(srcdir)/Makefile.plugins $(srcdir)/Makefile.tools $(srcdir)/Makefile.obexd $(srcdir)/Makefile.mesh $(am__empty): $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) $(SHELL) ./config.status --recheck $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) $(am__cd) $(srcdir) && $(AUTOCONF) $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) $(am__aclocal_m4_deps): config.h: stamp-h1 @test -f $@ || rm -f stamp-h1 @test -f $@ || $(MAKE) $(AM_MAKEFLAGS) stamp-h1 stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status $(AM_V_at)rm -f stamp-h1 $(AM_V_GEN)cd $(top_builddir) && $(SHELL) ./config.status config.h $(srcdir)/config.h.in: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) $(AM_V_GEN)($(am__cd) $(top_srcdir) && $(AUTOHEADER)) $(AM_V_at)rm -f stamp-h1 $(AM_V_at)touch $@ distclean-hdr: -rm -f config.h stamp-h1 lib/bluez.pc: $(top_builddir)/config.status $(top_srcdir)/lib/bluez.pc.in cd $(top_builddir) && $(SHELL) ./config.status $@ doc/bluetooth-meshd.rst: $(top_builddir)/config.status $(top_srcdir)/doc/bluetooth-meshd.rst.in cd $(top_builddir) && $(SHELL) ./config.status $@ mesh/bluetooth-mesh.service: $(top_builddir)/config.status $(top_srcdir)/mesh/bluetooth-mesh.service.in cd $(top_builddir) && $(SHELL) ./config.status $@ obexd/src/obex.service: $(top_builddir)/config.status $(top_srcdir)/obexd/src/obex.service.in cd $(top_builddir) && $(SHELL) ./config.status $@ obexd/src/org.bluez.obex.service: $(top_builddir)/config.status $(top_srcdir)/obexd/src/org.bluez.obex.service.in cd $(top_builddir) && $(SHELL) ./config.status $@ doc/bluetoothd.rst: $(top_builddir)/config.status $(top_srcdir)/doc/bluetoothd.rst.in cd $(top_builddir) && $(SHELL) ./config.status $@ src/bluetooth.service: $(top_builddir)/config.status $(top_srcdir)/src/bluetooth.service.in cd $(top_builddir) && $(SHELL) ./config.status $@ tools/bluetooth-logger.service: $(top_builddir)/config.status $(top_srcdir)/tools/bluetooth-logger.service.in cd $(top_builddir) && $(SHELL) ./config.status $@ tools/mpris-proxy.service: $(top_builddir)/config.status $(top_srcdir)/tools/mpris-proxy.service.in cd $(top_builddir) && $(SHELL) ./config.status $@ install-binPROGRAMS: $(bin_PROGRAMS) @$(NORMAL_INSTALL) @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ } \ ; done uninstall-binPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(bindir)" && $(am__rm_f) $$files clean-binPROGRAMS: $(am__rm_f) $(bin_PROGRAMS) test -z "$(EXEEXT)" || $(am__rm_f) $(bin_PROGRAMS:$(EXEEXT)=) install-cupsPROGRAMS: $(cups_PROGRAMS) @$(NORMAL_INSTALL) @list='$(cups_PROGRAMS)'; test -n "$(cupsdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(cupsdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(cupsdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(cupsdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(cupsdir)$$dir" || exit $$?; \ } \ ; done uninstall-cupsPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(cups_PROGRAMS)'; test -n "$(cupsdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(cupsdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(cupsdir)" && $(am__rm_f) $$files clean-cupsPROGRAMS: $(am__rm_f) $(cups_PROGRAMS) test -z "$(EXEEXT)" || $(am__rm_f) $(cups_PROGRAMS:$(EXEEXT)=) clean-noinstPROGRAMS: $(am__rm_f) $(noinst_PROGRAMS) test -z "$(EXEEXT)" || $(am__rm_f) $(noinst_PROGRAMS:$(EXEEXT)=) install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && $(am__rm_f) $$files clean-pkglibexecPROGRAMS: $(am__rm_f) $(pkglibexec_PROGRAMS) test -z "$(EXEEXT)" || $(am__rm_f) $(pkglibexec_PROGRAMS:$(EXEEXT)=) install-udevPROGRAMS: $(udev_PROGRAMS) @$(NORMAL_INSTALL) @list='$(udev_PROGRAMS)'; test -n "$(udevdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(udevdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(udevdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(udevdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(udevdir)$$dir" || exit $$?; \ } \ ; done uninstall-udevPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(udev_PROGRAMS)'; test -n "$(udevdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(udevdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(udevdir)" && $(am__rm_f) $$files clean-udevPROGRAMS: $(am__rm_f) $(udev_PROGRAMS) test -z "$(EXEEXT)" || $(am__rm_f) $(udev_PROGRAMS:$(EXEEXT)=) clean-noinstLIBRARIES: -$(am__rm_f) $(noinst_LIBRARIES) install-libLTLIBRARIES: $(lib_LTLIBRARIES) @$(NORMAL_INSTALL) @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ list2=; for p in $$list; do \ if test -f $$p; then \ list2="$$list2 $$p"; \ else :; fi; \ done; \ test -z "$$list2" || { \ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ } uninstall-libLTLIBRARIES: @$(NORMAL_UNINSTALL) @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ for p in $$list; do \ $(am__strip_dir) \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ done clean-libLTLIBRARIES: -$(am__rm_f) $(lib_LTLIBRARIES) @list='$(lib_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ echo rm -f $${locs}; \ $(am__rm_f) $${locs} clean-noinstLTLIBRARIES: -$(am__rm_f) $(noinst_LTLIBRARIES) @list='$(noinst_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ echo rm -f $${locs}; \ $(am__rm_f) $${locs} install-pluginLTLIBRARIES: $(plugin_LTLIBRARIES) @$(NORMAL_INSTALL) @list='$(plugin_LTLIBRARIES)'; test -n "$(plugindir)" || list=; \ list2=; for p in $$list; do \ if test -f $$p; then \ list2="$$list2 $$p"; \ else :; fi; \ done; \ test -z "$$list2" || { \ echo " $(MKDIR_P) '$(DESTDIR)$(plugindir)'"; \ $(MKDIR_P) "$(DESTDIR)$(plugindir)" || exit 1; \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(plugindir)'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(plugindir)"; \ } uninstall-pluginLTLIBRARIES: @$(NORMAL_UNINSTALL) @list='$(plugin_LTLIBRARIES)'; test -n "$(plugindir)" || list=; \ for p in $$list; do \ $(am__strip_dir) \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(plugindir)/$$f'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(plugindir)/$$f"; \ done clean-pluginLTLIBRARIES: -$(am__rm_f) $(plugin_LTLIBRARIES) @list='$(plugin_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ echo rm -f $${locs}; \ $(am__rm_f) $${locs} ell/$(am__dirstamp): @$(MKDIR_P) ell @: >>ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) ell/$(DEPDIR) @: >>ell/$(DEPDIR)/$(am__dirstamp) ell/util.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/log.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/queue.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/hashmap.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/random.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/signal.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/time.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/timeout.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/io.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/idle.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/main.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/settings.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/strv.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/string.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/cipher.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/checksum.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/pem.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/cert.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/cert-crypto.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/key.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/base64.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/utf8.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/dbus.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/dbus-message.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/dbus-util.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/dbus-service.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/dbus-client.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/dbus-name-cache.lo: ell/$(am__dirstamp) \ ell/$(DEPDIR)/$(am__dirstamp) ell/dbus-filter.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/gvariant-util.lo: ell/$(am__dirstamp) \ ell/$(DEPDIR)/$(am__dirstamp) ell/siphash.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/uuid.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/tester.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/tls.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/tls-extensions.lo: ell/$(am__dirstamp) \ ell/$(DEPDIR)/$(am__dirstamp) ell/tls-suites.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/tls-record.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/ecc.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/ecc-external.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/ecdh.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) ell/libell-internal.la: $(ell_libell_internal_la_OBJECTS) $(ell_libell_internal_la_DEPENDENCIES) $(EXTRA_ell_libell_internal_la_DEPENDENCIES) ell/$(am__dirstamp) $(AM_V_CCLD)$(LINK) $(am_ell_libell_internal_la_rpath) $(ell_libell_internal_la_OBJECTS) $(ell_libell_internal_la_LIBADD) $(LIBS) gdbus/$(am__dirstamp): @$(MKDIR_P) gdbus @: >>gdbus/$(am__dirstamp) gdbus/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) gdbus/$(DEPDIR) @: >>gdbus/$(DEPDIR)/$(am__dirstamp) gdbus/mainloop.lo: gdbus/$(am__dirstamp) \ gdbus/$(DEPDIR)/$(am__dirstamp) gdbus/watch.lo: gdbus/$(am__dirstamp) gdbus/$(DEPDIR)/$(am__dirstamp) gdbus/object.lo: gdbus/$(am__dirstamp) gdbus/$(DEPDIR)/$(am__dirstamp) gdbus/client.lo: gdbus/$(am__dirstamp) gdbus/$(DEPDIR)/$(am__dirstamp) gdbus/polkit.lo: gdbus/$(am__dirstamp) gdbus/$(DEPDIR)/$(am__dirstamp) gdbus/libgdbus-internal.la: $(gdbus_libgdbus_internal_la_OBJECTS) $(gdbus_libgdbus_internal_la_DEPENDENCIES) $(EXTRA_gdbus_libgdbus_internal_la_DEPENDENCIES) gdbus/$(am__dirstamp) $(AM_V_CCLD)$(LINK) $(gdbus_libgdbus_internal_la_OBJECTS) $(gdbus_libgdbus_internal_la_LIBADD) $(LIBS) lib/bluetooth/$(am__dirstamp): @$(MKDIR_P) lib/bluetooth @: >>lib/bluetooth/$(am__dirstamp) lib/bluetooth/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) lib/bluetooth/$(DEPDIR) @: >>lib/bluetooth/$(DEPDIR)/$(am__dirstamp) lib/bluetooth/bluetooth.lo: lib/bluetooth/$(am__dirstamp) \ lib/bluetooth/$(DEPDIR)/$(am__dirstamp) lib/bluetooth/hci.lo: lib/bluetooth/$(am__dirstamp) \ lib/bluetooth/$(DEPDIR)/$(am__dirstamp) lib/bluetooth/sdp.lo: lib/bluetooth/$(am__dirstamp) \ lib/bluetooth/$(DEPDIR)/$(am__dirstamp) lib/bluetooth/uuid.lo: lib/bluetooth/$(am__dirstamp) \ lib/bluetooth/$(DEPDIR)/$(am__dirstamp) lib/$(am__dirstamp): @$(MKDIR_P) lib @: >>lib/$(am__dirstamp) lib/libbluetooth-internal.la: $(lib_libbluetooth_internal_la_OBJECTS) $(lib_libbluetooth_internal_la_DEPENDENCIES) $(EXTRA_lib_libbluetooth_internal_la_DEPENDENCIES) lib/$(am__dirstamp) $(AM_V_CCLD)$(LINK) $(lib_libbluetooth_internal_la_OBJECTS) $(lib_libbluetooth_internal_la_LIBADD) $(LIBS) lib/libbluetooth.la: $(lib_libbluetooth_la_OBJECTS) $(lib_libbluetooth_la_DEPENDENCIES) $(EXTRA_lib_libbluetooth_la_DEPENDENCIES) lib/$(am__dirstamp) $(AM_V_CCLD)$(lib_libbluetooth_la_LINK) $(am_lib_libbluetooth_la_rpath) $(lib_libbluetooth_la_OBJECTS) $(lib_libbluetooth_la_LIBADD) $(LIBS) src/shared/$(am__dirstamp): @$(MKDIR_P) src/shared @: >>src/shared/$(am__dirstamp) src/shared/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) src/shared/$(DEPDIR) @: >>src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-queue.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-util.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-mgmt.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-crypto.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-ecc.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-ringbuf.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-hci.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-hci-crypto.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-hfp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-uhid.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-pcap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-btsnoop.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-ad.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-att.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-gatt-helpers.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-gatt-client.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-gatt-server.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-gatt-db.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-gap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-log.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-bap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-bap-debug.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-mcp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-vcp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-micp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-csip.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-bass.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-ccp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-tmap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-gmap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-asha.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-battery.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-uinput.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-rap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-shell.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-io-ell.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-timeout-ell.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-mainloop-ell.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/$(am__dirstamp): @$(MKDIR_P) src @: >>src/$(am__dirstamp) src/libshared-ell.la: $(src_libshared_ell_la_OBJECTS) $(src_libshared_ell_la_DEPENDENCIES) $(EXTRA_src_libshared_ell_la_DEPENDENCIES) src/$(am__dirstamp) $(AM_V_CCLD)$(src_libshared_ell_la_LINK) $(am_src_libshared_ell_la_rpath) $(src_libshared_ell_la_OBJECTS) $(src_libshared_ell_la_LIBADD) $(LIBS) src/shared/libshared_glib_la-queue.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-util.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-mgmt.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-crypto.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-ecc.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-ringbuf.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-hci.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-hci-crypto.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-hfp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-uhid.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-pcap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-btsnoop.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-ad.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-att.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-gatt-helpers.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-gatt-client.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-gatt-server.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-gatt-db.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-gap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-log.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-bap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-bap-debug.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-mcp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-vcp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-micp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-csip.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-bass.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-ccp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-tmap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-gmap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-asha.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-battery.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-uinput.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-rap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-shell.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-io-glib.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-timeout-glib.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-mainloop-glib.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-mainloop-notify.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-tester.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/libshared-glib.la: $(src_libshared_glib_la_OBJECTS) $(src_libshared_glib_la_DEPENDENCIES) $(EXTRA_src_libshared_glib_la_DEPENDENCIES) src/$(am__dirstamp) $(AM_V_CCLD)$(src_libshared_glib_la_LINK) $(src_libshared_glib_la_OBJECTS) $(src_libshared_glib_la_LIBADD) $(LIBS) src/shared/libshared_mainloop_la-queue.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-util.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-mgmt.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-crypto.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-ecc.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-ringbuf.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-hci.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-hci-crypto.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-hfp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-uhid.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-pcap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-btsnoop.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-ad.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-att.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-gatt-helpers.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-gatt-client.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-gatt-server.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-gatt-db.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-gap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-log.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-bap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-bap-debug.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-mcp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-vcp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-micp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-csip.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-bass.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-ccp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-tmap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-gmap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-asha.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-battery.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-uinput.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-rap.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-shell.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-io-mainloop.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-timeout-mainloop.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-mainloop.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-mainloop-notify.lo: \ src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/libshared-mainloop.la: $(src_libshared_mainloop_la_OBJECTS) $(src_libshared_mainloop_la_DEPENDENCIES) $(EXTRA_src_libshared_mainloop_la_DEPENDENCIES) src/$(am__dirstamp) $(AM_V_CCLD)$(src_libshared_mainloop_la_LINK) $(src_libshared_mainloop_la_OBJECTS) $(src_libshared_mainloop_la_LIBADD) $(LIBS) attrib/$(am__dirstamp): @$(MKDIR_P) attrib @: >>attrib/$(am__dirstamp) attrib/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) attrib/$(DEPDIR) @: >>attrib/$(DEPDIR)/$(am__dirstamp) attrib/gatttool.$(OBJEXT): attrib/$(am__dirstamp) \ attrib/$(DEPDIR)/$(am__dirstamp) attrib/att.$(OBJEXT): attrib/$(am__dirstamp) \ attrib/$(DEPDIR)/$(am__dirstamp) attrib/gatt.$(OBJEXT): attrib/$(am__dirstamp) \ attrib/$(DEPDIR)/$(am__dirstamp) attrib/gattrib.$(OBJEXT): attrib/$(am__dirstamp) \ attrib/$(DEPDIR)/$(am__dirstamp) btio/$(am__dirstamp): @$(MKDIR_P) btio @: >>btio/$(am__dirstamp) btio/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) btio/$(DEPDIR) @: >>btio/$(DEPDIR)/$(am__dirstamp) btio/btio.$(OBJEXT): btio/$(am__dirstamp) \ btio/$(DEPDIR)/$(am__dirstamp) attrib/interactive.$(OBJEXT): attrib/$(am__dirstamp) \ attrib/$(DEPDIR)/$(am__dirstamp) attrib/utils.$(OBJEXT): attrib/$(am__dirstamp) \ attrib/$(DEPDIR)/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) src/$(DEPDIR) @: >>src/$(DEPDIR)/$(am__dirstamp) src/log.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) client/$(am__dirstamp): @$(MKDIR_P) client @: >>client/$(am__dirstamp) client/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) client/$(DEPDIR) @: >>client/$(DEPDIR)/$(am__dirstamp) client/display.$(OBJEXT): client/$(am__dirstamp) \ client/$(DEPDIR)/$(am__dirstamp) attrib/gatttool$(EXEEXT): $(attrib_gatttool_OBJECTS) $(attrib_gatttool_DEPENDENCIES) $(EXTRA_attrib_gatttool_DEPENDENCIES) attrib/$(am__dirstamp) @rm -f attrib/gatttool$(EXEEXT) $(AM_V_CCLD)$(LINK) $(attrib_gatttool_OBJECTS) $(attrib_gatttool_LDADD) $(LIBS) client/main.$(OBJEXT): client/$(am__dirstamp) \ client/$(DEPDIR)/$(am__dirstamp) client/print.$(OBJEXT): client/$(am__dirstamp) \ client/$(DEPDIR)/$(am__dirstamp) client/agent.$(OBJEXT): client/$(am__dirstamp) \ client/$(DEPDIR)/$(am__dirstamp) client/advertising.$(OBJEXT): client/$(am__dirstamp) \ client/$(DEPDIR)/$(am__dirstamp) client/adv_monitor.$(OBJEXT): client/$(am__dirstamp) \ client/$(DEPDIR)/$(am__dirstamp) client/gatt.$(OBJEXT): client/$(am__dirstamp) \ client/$(DEPDIR)/$(am__dirstamp) client/admin.$(OBJEXT): client/$(am__dirstamp) \ client/$(DEPDIR)/$(am__dirstamp) client/player.$(OBJEXT): client/$(am__dirstamp) \ client/$(DEPDIR)/$(am__dirstamp) client/mgmt.$(OBJEXT): client/$(am__dirstamp) \ client/$(DEPDIR)/$(am__dirstamp) client/assistant.$(OBJEXT): client/$(am__dirstamp) \ client/$(DEPDIR)/$(am__dirstamp) client/hci.$(OBJEXT): client/$(am__dirstamp) \ client/$(DEPDIR)/$(am__dirstamp) client/telephony.$(OBJEXT): client/$(am__dirstamp) \ client/$(DEPDIR)/$(am__dirstamp) client/bluetoothctl$(EXEEXT): $(client_bluetoothctl_OBJECTS) $(client_bluetoothctl_DEPENDENCIES) $(EXTRA_client_bluetoothctl_DEPENDENCIES) client/$(am__dirstamp) @rm -f client/bluetoothctl$(EXEEXT) $(AM_V_CCLD)$(LINK) $(client_bluetoothctl_OBJECTS) $(client_bluetoothctl_LDADD) $(LIBS) client/btpclient/$(am__dirstamp): @$(MKDIR_P) client/btpclient @: >>client/btpclient/$(am__dirstamp) client/btpclient/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) client/btpclient/$(DEPDIR) @: >>client/btpclient/$(DEPDIR)/$(am__dirstamp) client/btpclient/btpclient.$(OBJEXT): \ client/btpclient/$(am__dirstamp) \ client/btpclient/$(DEPDIR)/$(am__dirstamp) src/shared/btp.$(OBJEXT): src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) client/btpclient/bap.$(OBJEXT): client/btpclient/$(am__dirstamp) \ client/btpclient/$(DEPDIR)/$(am__dirstamp) client/btpclient/core.$(OBJEXT): client/btpclient/$(am__dirstamp) \ client/btpclient/$(DEPDIR)/$(am__dirstamp) client/btpclient/gap.$(OBJEXT): client/btpclient/$(am__dirstamp) \ client/btpclient/$(DEPDIR)/$(am__dirstamp) client/btpclient/gatt.$(OBJEXT): client/btpclient/$(am__dirstamp) \ client/btpclient/$(DEPDIR)/$(am__dirstamp) client/btpclient/btpclient$(EXEEXT): $(client_btpclient_btpclient_OBJECTS) $(client_btpclient_btpclient_DEPENDENCIES) $(EXTRA_client_btpclient_btpclient_DEPENDENCIES) client/btpclient/$(am__dirstamp) @rm -f client/btpclient/btpclient$(EXEEXT) $(AM_V_CCLD)$(LINK) $(client_btpclient_btpclient_OBJECTS) $(client_btpclient_btpclient_LDADD) $(LIBS) client/btpclient/btpclientctl.$(OBJEXT): \ client/btpclient/$(am__dirstamp) \ client/btpclient/$(DEPDIR)/$(am__dirstamp) client/btpclient/btpclientctl$(EXEEXT): $(client_btpclient_btpclientctl_OBJECTS) $(client_btpclient_btpclientctl_DEPENDENCIES) $(EXTRA_client_btpclient_btpclientctl_DEPENDENCIES) client/btpclient/$(am__dirstamp) @rm -f client/btpclient/btpclientctl$(EXEEXT) $(AM_V_CCLD)$(LINK) $(client_btpclient_btpclientctl_OBJECTS) $(client_btpclient_btpclientctl_LDADD) $(LIBS) emulator/$(am__dirstamp): @$(MKDIR_P) emulator @: >>emulator/$(am__dirstamp) emulator/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) emulator/$(DEPDIR) @: >>emulator/$(DEPDIR)/$(am__dirstamp) emulator/b1ee.$(OBJEXT): emulator/$(am__dirstamp) \ emulator/$(DEPDIR)/$(am__dirstamp) emulator/b1ee$(EXEEXT): $(emulator_b1ee_OBJECTS) $(emulator_b1ee_DEPENDENCIES) $(EXTRA_emulator_b1ee_DEPENDENCIES) emulator/$(am__dirstamp) @rm -f emulator/b1ee$(EXEEXT) $(AM_V_CCLD)$(LINK) $(emulator_b1ee_OBJECTS) $(emulator_b1ee_LDADD) $(LIBS) emulator/main.$(OBJEXT): emulator/$(am__dirstamp) \ emulator/$(DEPDIR)/$(am__dirstamp) emulator/serial.$(OBJEXT): emulator/$(am__dirstamp) \ emulator/$(DEPDIR)/$(am__dirstamp) emulator/server.$(OBJEXT): emulator/$(am__dirstamp) \ emulator/$(DEPDIR)/$(am__dirstamp) emulator/vhci.$(OBJEXT): emulator/$(am__dirstamp) \ emulator/$(DEPDIR)/$(am__dirstamp) emulator/btdev.$(OBJEXT): emulator/$(am__dirstamp) \ emulator/$(DEPDIR)/$(am__dirstamp) emulator/bthost.$(OBJEXT): emulator/$(am__dirstamp) \ emulator/$(DEPDIR)/$(am__dirstamp) emulator/smp.$(OBJEXT): emulator/$(am__dirstamp) \ emulator/$(DEPDIR)/$(am__dirstamp) emulator/phy.$(OBJEXT): emulator/$(am__dirstamp) \ emulator/$(DEPDIR)/$(am__dirstamp) emulator/le.$(OBJEXT): emulator/$(am__dirstamp) \ emulator/$(DEPDIR)/$(am__dirstamp) emulator/btvirt$(EXEEXT): $(emulator_btvirt_OBJECTS) $(emulator_btvirt_DEPENDENCIES) $(EXTRA_emulator_btvirt_DEPENDENCIES) emulator/$(am__dirstamp) @rm -f emulator/btvirt$(EXEEXT) $(AM_V_CCLD)$(LINK) $(emulator_btvirt_OBJECTS) $(emulator_btvirt_LDADD) $(LIBS) emulator/hfp.$(OBJEXT): emulator/$(am__dirstamp) \ emulator/$(DEPDIR)/$(am__dirstamp) emulator/hfp$(EXEEXT): $(emulator_hfp_OBJECTS) $(emulator_hfp_DEPENDENCIES) $(EXTRA_emulator_hfp_DEPENDENCIES) emulator/$(am__dirstamp) @rm -f emulator/hfp$(EXEEXT) $(AM_V_CCLD)$(LINK) $(emulator_hfp_OBJECTS) $(emulator_hfp_LDADD) $(LIBS) mesh/$(am__dirstamp): @$(MKDIR_P) mesh @: >>mesh/$(am__dirstamp) mesh/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) mesh/$(DEPDIR) @: >>mesh/$(DEPDIR)/$(am__dirstamp) mesh/mesh.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/net-keys.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/mesh-io.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/mesh-mgmt.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/mesh-io-unit.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/mesh-io-mgmt.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/mesh-io-generic.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/net.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/crypto.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/friend.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/appkey.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/node.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/model.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/cfgmod-server.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/remprv-server.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/mesh-config-json.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/util.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/dbus.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/agent.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/prov-acceptor.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/prov-initiator.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/manager.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/pb-adv.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/keyring.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/rpl.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/prvbeac-server.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/main.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/bluetooth-meshd$(EXEEXT): $(mesh_bluetooth_meshd_OBJECTS) $(mesh_bluetooth_meshd_DEPENDENCIES) $(EXTRA_mesh_bluetooth_meshd_DEPENDENCIES) mesh/$(am__dirstamp) @rm -f mesh/bluetooth-meshd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(mesh_bluetooth_meshd_OBJECTS) $(mesh_bluetooth_meshd_LDADD) $(LIBS) monitor/$(am__dirstamp): @$(MKDIR_P) monitor @: >>monitor/$(am__dirstamp) monitor/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) monitor/$(DEPDIR) @: >>monitor/$(DEPDIR)/$(am__dirstamp) monitor/main.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/display.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/hcidump.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/ellisys.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/control.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/packet.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/vendor.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/lmp.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/crc.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/ll.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/l2cap.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/sdp.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/avctp.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/avdtp.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/a2dp.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/rfcomm.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/bnep.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/hwdb.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/keys.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/analyze.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/intel.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/broadcom.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/msft.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/jlink.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) monitor/att.$(OBJEXT): monitor/$(am__dirstamp) \ monitor/$(DEPDIR)/$(am__dirstamp) src/textfile.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/settings.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) monitor/btmon$(EXEEXT): $(monitor_btmon_OBJECTS) $(monitor_btmon_DEPENDENCIES) $(EXTRA_monitor_btmon_DEPENDENCIES) monitor/$(am__dirstamp) @rm -f monitor/btmon$(EXEEXT) $(AM_V_CCLD)$(LINK) $(monitor_btmon_OBJECTS) $(monitor_btmon_LDADD) $(LIBS) btio/obexd-btio.$(OBJEXT): btio/$(am__dirstamp) \ btio/$(DEPDIR)/$(am__dirstamp) gobex/$(am__dirstamp): @$(MKDIR_P) gobex @: >>gobex/$(am__dirstamp) gobex/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) gobex/$(DEPDIR) @: >>gobex/$(DEPDIR)/$(am__dirstamp) gobex/obexd-gobex.$(OBJEXT): gobex/$(am__dirstamp) \ gobex/$(DEPDIR)/$(am__dirstamp) gobex/obexd-gobex-defs.$(OBJEXT): gobex/$(am__dirstamp) \ gobex/$(DEPDIR)/$(am__dirstamp) gobex/obexd-gobex-packet.$(OBJEXT): gobex/$(am__dirstamp) \ gobex/$(DEPDIR)/$(am__dirstamp) gobex/obexd-gobex-header.$(OBJEXT): gobex/$(am__dirstamp) \ gobex/$(DEPDIR)/$(am__dirstamp) gobex/obexd-gobex-transfer.$(OBJEXT): gobex/$(am__dirstamp) \ gobex/$(DEPDIR)/$(am__dirstamp) gobex/obexd-gobex-apparam.$(OBJEXT): gobex/$(am__dirstamp) \ gobex/$(DEPDIR)/$(am__dirstamp) obexd/plugins/$(am__dirstamp): @$(MKDIR_P) obexd/plugins @: >>obexd/plugins/$(am__dirstamp) obexd/plugins/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) obexd/plugins/$(DEPDIR) @: >>obexd/plugins/$(DEPDIR)/$(am__dirstamp) obexd/plugins/obexd-filesystem.$(OBJEXT): \ obexd/plugins/$(am__dirstamp) \ obexd/plugins/$(DEPDIR)/$(am__dirstamp) obexd/plugins/obexd-bluetooth.$(OBJEXT): \ obexd/plugins/$(am__dirstamp) \ obexd/plugins/$(DEPDIR)/$(am__dirstamp) obexd/plugins/obexd-pcsuite.$(OBJEXT): obexd/plugins/$(am__dirstamp) \ obexd/plugins/$(DEPDIR)/$(am__dirstamp) obexd/plugins/obexd-opp.$(OBJEXT): obexd/plugins/$(am__dirstamp) \ obexd/plugins/$(DEPDIR)/$(am__dirstamp) obexd/plugins/obexd-ftp.$(OBJEXT): obexd/plugins/$(am__dirstamp) \ obexd/plugins/$(DEPDIR)/$(am__dirstamp) obexd/plugins/obexd-irmc.$(OBJEXT): obexd/plugins/$(am__dirstamp) \ obexd/plugins/$(DEPDIR)/$(am__dirstamp) obexd/plugins/obexd-pbap.$(OBJEXT): obexd/plugins/$(am__dirstamp) \ obexd/plugins/$(DEPDIR)/$(am__dirstamp) obexd/plugins/obexd-vcard.$(OBJEXT): obexd/plugins/$(am__dirstamp) \ obexd/plugins/$(DEPDIR)/$(am__dirstamp) obexd/plugins/obexd-phonebook-@PLUGIN_PHONEBOOK@.$(OBJEXT): \ obexd/plugins/$(am__dirstamp) \ obexd/plugins/$(DEPDIR)/$(am__dirstamp) obexd/plugins/obexd-mas.$(OBJEXT): obexd/plugins/$(am__dirstamp) \ obexd/plugins/$(DEPDIR)/$(am__dirstamp) obexd/plugins/obexd-messages-dummy.$(OBJEXT): \ obexd/plugins/$(am__dirstamp) \ obexd/plugins/$(DEPDIR)/$(am__dirstamp) obexd/client/$(am__dirstamp): @$(MKDIR_P) obexd/client @: >>obexd/client/$(am__dirstamp) obexd/client/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) obexd/client/$(DEPDIR) @: >>obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/client/obexd-mns.$(OBJEXT): obexd/client/$(am__dirstamp) \ obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/src/$(am__dirstamp): @$(MKDIR_P) obexd/src @: >>obexd/src/$(am__dirstamp) obexd/src/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) obexd/src/$(DEPDIR) @: >>obexd/src/$(DEPDIR)/$(am__dirstamp) obexd/src/obexd-main.$(OBJEXT): obexd/src/$(am__dirstamp) \ obexd/src/$(DEPDIR)/$(am__dirstamp) obexd/src/obexd-plugin.$(OBJEXT): obexd/src/$(am__dirstamp) \ obexd/src/$(DEPDIR)/$(am__dirstamp) obexd/src/obexd-log.$(OBJEXT): obexd/src/$(am__dirstamp) \ obexd/src/$(DEPDIR)/$(am__dirstamp) obexd/src/obexd-logind.$(OBJEXT): obexd/src/$(am__dirstamp) \ obexd/src/$(DEPDIR)/$(am__dirstamp) obexd/src/obexd-manager.$(OBJEXT): obexd/src/$(am__dirstamp) \ obexd/src/$(DEPDIR)/$(am__dirstamp) obexd/src/obexd-obex.$(OBJEXT): obexd/src/$(am__dirstamp) \ obexd/src/$(DEPDIR)/$(am__dirstamp) obexd/src/obexd-mimetype.$(OBJEXT): obexd/src/$(am__dirstamp) \ obexd/src/$(DEPDIR)/$(am__dirstamp) obexd/src/obexd-service.$(OBJEXT): obexd/src/$(am__dirstamp) \ obexd/src/$(DEPDIR)/$(am__dirstamp) obexd/src/obexd-transport.$(OBJEXT): obexd/src/$(am__dirstamp) \ obexd/src/$(DEPDIR)/$(am__dirstamp) obexd/src/obexd-server.$(OBJEXT): obexd/src/$(am__dirstamp) \ obexd/src/$(DEPDIR)/$(am__dirstamp) obexd/client/obexd-manager.$(OBJEXT): obexd/client/$(am__dirstamp) \ obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/client/obexd-session.$(OBJEXT): obexd/client/$(am__dirstamp) \ obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/client/obexd-bluetooth.$(OBJEXT): obexd/client/$(am__dirstamp) \ obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/client/obexd-sync.$(OBJEXT): obexd/client/$(am__dirstamp) \ obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/client/obexd-pbap.$(OBJEXT): obexd/client/$(am__dirstamp) \ obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/client/obexd-ftp.$(OBJEXT): obexd/client/$(am__dirstamp) \ obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/client/obexd-opp.$(OBJEXT): obexd/client/$(am__dirstamp) \ obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/client/obexd-map.$(OBJEXT): obexd/client/$(am__dirstamp) \ obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/client/obexd-bip.$(OBJEXT): obexd/client/$(am__dirstamp) \ obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/client/obexd-bip-common.$(OBJEXT): obexd/client/$(am__dirstamp) \ obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/client/obexd-map-event.$(OBJEXT): obexd/client/$(am__dirstamp) \ obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/client/obexd-transfer.$(OBJEXT): obexd/client/$(am__dirstamp) \ obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/client/obexd-transport.$(OBJEXT): obexd/client/$(am__dirstamp) \ obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/client/obexd-driver.$(OBJEXT): obexd/client/$(am__dirstamp) \ obexd/client/$(DEPDIR)/$(am__dirstamp) obexd/src/obexd$(EXEEXT): $(obexd_src_obexd_OBJECTS) $(obexd_src_obexd_DEPENDENCIES) $(EXTRA_obexd_src_obexd_DEPENDENCIES) obexd/src/$(am__dirstamp) @rm -f obexd/src/obexd$(EXEEXT) $(AM_V_CCLD)$(obexd_src_obexd_LINK) $(obexd_src_obexd_OBJECTS) $(obexd_src_obexd_LDADD) $(LIBS) peripheral/$(am__dirstamp): @$(MKDIR_P) peripheral @: >>peripheral/$(am__dirstamp) peripheral/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) peripheral/$(DEPDIR) @: >>peripheral/$(DEPDIR)/$(am__dirstamp) peripheral/main.$(OBJEXT): peripheral/$(am__dirstamp) \ peripheral/$(DEPDIR)/$(am__dirstamp) peripheral/efivars.$(OBJEXT): peripheral/$(am__dirstamp) \ peripheral/$(DEPDIR)/$(am__dirstamp) peripheral/attach.$(OBJEXT): peripheral/$(am__dirstamp) \ peripheral/$(DEPDIR)/$(am__dirstamp) peripheral/log.$(OBJEXT): peripheral/$(am__dirstamp) \ peripheral/$(DEPDIR)/$(am__dirstamp) peripheral/gap.$(OBJEXT): peripheral/$(am__dirstamp) \ peripheral/$(DEPDIR)/$(am__dirstamp) peripheral/gatt.$(OBJEXT): peripheral/$(am__dirstamp) \ peripheral/$(DEPDIR)/$(am__dirstamp) peripheral/btsensor$(EXEEXT): $(peripheral_btsensor_OBJECTS) $(peripheral_btsensor_DEPENDENCIES) $(EXTRA_peripheral_btsensor_DEPENDENCIES) peripheral/$(am__dirstamp) @rm -f peripheral/btsensor$(EXEEXT) $(AM_V_CCLD)$(LINK) $(peripheral_btsensor_OBJECTS) $(peripheral_btsensor_LDADD) $(LIBS) profiles/cups/$(am__dirstamp): @$(MKDIR_P) profiles/cups @: >>profiles/cups/$(am__dirstamp) profiles/cups/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) profiles/cups/$(DEPDIR) @: >>profiles/cups/$(DEPDIR)/$(am__dirstamp) profiles/cups/main.$(OBJEXT): profiles/cups/$(am__dirstamp) \ profiles/cups/$(DEPDIR)/$(am__dirstamp) profiles/cups/sdp.$(OBJEXT): profiles/cups/$(am__dirstamp) \ profiles/cups/$(DEPDIR)/$(am__dirstamp) profiles/cups/spp.$(OBJEXT): profiles/cups/$(am__dirstamp) \ profiles/cups/$(DEPDIR)/$(am__dirstamp) profiles/cups/hcrp.$(OBJEXT): profiles/cups/$(am__dirstamp) \ profiles/cups/$(DEPDIR)/$(am__dirstamp) profiles/cups/bluetooth$(EXEEXT): $(profiles_cups_bluetooth_OBJECTS) $(profiles_cups_bluetooth_DEPENDENCIES) $(EXTRA_profiles_cups_bluetooth_DEPENDENCIES) profiles/cups/$(am__dirstamp) @rm -f profiles/cups/bluetooth$(EXEEXT) $(AM_V_CCLD)$(LINK) $(profiles_cups_bluetooth_OBJECTS) $(profiles_cups_bluetooth_LDADD) $(LIBS) profiles/iap/$(am__dirstamp): @$(MKDIR_P) profiles/iap @: >>profiles/iap/$(am__dirstamp) profiles/iap/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) profiles/iap/$(DEPDIR) @: >>profiles/iap/$(DEPDIR)/$(am__dirstamp) profiles/iap/main.$(OBJEXT): profiles/iap/$(am__dirstamp) \ profiles/iap/$(DEPDIR)/$(am__dirstamp) profiles/iap/iapd$(EXEEXT): $(profiles_iap_iapd_OBJECTS) $(profiles_iap_iapd_DEPENDENCIES) $(EXTRA_profiles_iap_iapd_DEPENDENCIES) profiles/iap/$(am__dirstamp) @rm -f profiles/iap/iapd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(profiles_iap_iapd_OBJECTS) $(profiles_iap_iapd_LDADD) $(LIBS) plugins/$(am__dirstamp): @$(MKDIR_P) plugins @: >>plugins/$(am__dirstamp) plugins/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) plugins/$(DEPDIR) @: >>plugins/$(DEPDIR)/$(am__dirstamp) plugins/bluetoothd-hostname.$(OBJEXT): plugins/$(am__dirstamp) \ plugins/$(DEPDIR)/$(am__dirstamp) plugins/bluetoothd-autopair.$(OBJEXT): plugins/$(am__dirstamp) \ plugins/$(DEPDIR)/$(am__dirstamp) plugins/bluetoothd-policy.$(OBJEXT): plugins/$(am__dirstamp) \ plugins/$(DEPDIR)/$(am__dirstamp) profiles/audio/$(am__dirstamp): @$(MKDIR_P) profiles/audio @: >>profiles/audio/$(am__dirstamp) profiles/audio/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) profiles/audio/$(DEPDIR) @: >>profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-media.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-transport.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-player.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) plugins/bluetoothd-admin.$(OBJEXT): plugins/$(am__dirstamp) \ plugins/$(DEPDIR)/$(am__dirstamp) plugins/bluetoothd-neard.$(OBJEXT): plugins/$(am__dirstamp) \ plugins/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-source.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-sink.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-a2dp.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-avdtp.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-control.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-avctp.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-avrcp.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-avrcp-player.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/network/$(am__dirstamp): @$(MKDIR_P) profiles/network @: >>profiles/network/$(am__dirstamp) profiles/network/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) profiles/network/$(DEPDIR) @: >>profiles/network/$(DEPDIR)/$(am__dirstamp) profiles/network/bluetoothd-manager.$(OBJEXT): \ profiles/network/$(am__dirstamp) \ profiles/network/$(DEPDIR)/$(am__dirstamp) profiles/network/bluetoothd-bnep.$(OBJEXT): \ profiles/network/$(am__dirstamp) \ profiles/network/$(DEPDIR)/$(am__dirstamp) profiles/network/bluetoothd-server.$(OBJEXT): \ profiles/network/$(am__dirstamp) \ profiles/network/$(DEPDIR)/$(am__dirstamp) profiles/network/bluetoothd-connection.$(OBJEXT): \ profiles/network/$(am__dirstamp) \ profiles/network/$(DEPDIR)/$(am__dirstamp) profiles/input/$(am__dirstamp): @$(MKDIR_P) profiles/input @: >>profiles/input/$(am__dirstamp) profiles/input/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) profiles/input/$(DEPDIR) @: >>profiles/input/$(DEPDIR)/$(am__dirstamp) profiles/input/bluetoothd-manager.$(OBJEXT): \ profiles/input/$(am__dirstamp) \ profiles/input/$(DEPDIR)/$(am__dirstamp) profiles/input/bluetoothd-server.$(OBJEXT): \ profiles/input/$(am__dirstamp) \ profiles/input/$(DEPDIR)/$(am__dirstamp) profiles/input/bluetoothd-device.$(OBJEXT): \ profiles/input/$(am__dirstamp) \ profiles/input/$(DEPDIR)/$(am__dirstamp) profiles/input/bluetoothd-hog.$(OBJEXT): \ profiles/input/$(am__dirstamp) \ profiles/input/$(DEPDIR)/$(am__dirstamp) profiles/input/bluetoothd-hog-lib.$(OBJEXT): \ profiles/input/$(am__dirstamp) \ profiles/input/$(DEPDIR)/$(am__dirstamp) profiles/deviceinfo/$(am__dirstamp): @$(MKDIR_P) profiles/deviceinfo @: >>profiles/deviceinfo/$(am__dirstamp) profiles/deviceinfo/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) profiles/deviceinfo/$(DEPDIR) @: >>profiles/deviceinfo/$(DEPDIR)/$(am__dirstamp) profiles/deviceinfo/bluetoothd-dis.$(OBJEXT): \ profiles/deviceinfo/$(am__dirstamp) \ profiles/deviceinfo/$(DEPDIR)/$(am__dirstamp) profiles/battery/$(am__dirstamp): @$(MKDIR_P) profiles/battery @: >>profiles/battery/$(am__dirstamp) profiles/battery/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) profiles/battery/$(DEPDIR) @: >>profiles/battery/$(DEPDIR)/$(am__dirstamp) profiles/battery/bluetoothd-bas.$(OBJEXT): \ profiles/battery/$(am__dirstamp) \ profiles/battery/$(DEPDIR)/$(am__dirstamp) profiles/scanparam/$(am__dirstamp): @$(MKDIR_P) profiles/scanparam @: >>profiles/scanparam/$(am__dirstamp) profiles/scanparam/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) profiles/scanparam/$(DEPDIR) @: >>profiles/scanparam/$(DEPDIR)/$(am__dirstamp) profiles/scanparam/bluetoothd-scpp.$(OBJEXT): \ profiles/scanparam/$(am__dirstamp) \ profiles/scanparam/$(DEPDIR)/$(am__dirstamp) profiles/input/bluetoothd-suspend-none.$(OBJEXT): \ profiles/input/$(am__dirstamp) \ profiles/input/$(DEPDIR)/$(am__dirstamp) profiles/gap/$(am__dirstamp): @$(MKDIR_P) profiles/gap @: >>profiles/gap/$(am__dirstamp) profiles/gap/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) profiles/gap/$(DEPDIR) @: >>profiles/gap/$(DEPDIR)/$(am__dirstamp) profiles/gap/bluetoothd-gas.$(OBJEXT): profiles/gap/$(am__dirstamp) \ profiles/gap/$(DEPDIR)/$(am__dirstamp) profiles/scanparam/bluetoothd-scan.$(OBJEXT): \ profiles/scanparam/$(am__dirstamp) \ profiles/scanparam/$(DEPDIR)/$(am__dirstamp) profiles/deviceinfo/bluetoothd-deviceinfo.$(OBJEXT): \ profiles/deviceinfo/$(am__dirstamp) \ profiles/deviceinfo/$(DEPDIR)/$(am__dirstamp) profiles/midi/$(am__dirstamp): @$(MKDIR_P) profiles/midi @: >>profiles/midi/$(am__dirstamp) profiles/midi/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) profiles/midi/$(DEPDIR) @: >>profiles/midi/$(DEPDIR)/$(am__dirstamp) profiles/midi/bluetoothd-midi.$(OBJEXT): \ profiles/midi/$(am__dirstamp) \ profiles/midi/$(DEPDIR)/$(am__dirstamp) profiles/midi/bluetoothd-libmidi.$(OBJEXT): \ profiles/midi/$(am__dirstamp) \ profiles/midi/$(DEPDIR)/$(am__dirstamp) profiles/battery/bluetoothd-battery.$(OBJEXT): \ profiles/battery/$(am__dirstamp) \ profiles/battery/$(DEPDIR)/$(am__dirstamp) profiles/ranging/$(am__dirstamp): @$(MKDIR_P) profiles/ranging @: >>profiles/ranging/$(am__dirstamp) profiles/ranging/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) profiles/ranging/$(DEPDIR) @: >>profiles/ranging/$(DEPDIR)/$(am__dirstamp) profiles/ranging/bluetoothd-rap.$(OBJEXT): \ profiles/ranging/$(am__dirstamp) \ profiles/ranging/$(DEPDIR)/$(am__dirstamp) profiles/ranging/bluetoothd-rap_hci.$(OBJEXT): \ profiles/ranging/$(am__dirstamp) \ profiles/ranging/$(DEPDIR)/$(am__dirstamp) plugins/bluetoothd-sixaxis.$(OBJEXT): plugins/$(am__dirstamp) \ plugins/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-bap.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-bass.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-mcp.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-vcp.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-micp.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-ccp.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-csip.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-tmap.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-gmap.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-asha.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-telephony.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-hfp-hf.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) attrib/bluetoothd-att.$(OBJEXT): attrib/$(am__dirstamp) \ attrib/$(DEPDIR)/$(am__dirstamp) attrib/bluetoothd-gatt.$(OBJEXT): attrib/$(am__dirstamp) \ attrib/$(DEPDIR)/$(am__dirstamp) attrib/bluetoothd-gattrib.$(OBJEXT): attrib/$(am__dirstamp) \ attrib/$(DEPDIR)/$(am__dirstamp) btio/bluetoothd-btio.$(OBJEXT): btio/$(am__dirstamp) \ btio/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-log.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-backtrace.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-rfkill.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-sdpd-server.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-sdpd-request.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-sdpd-service.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-sdpd-database.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-gatt-database.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-sdp-xml.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-sdp-client.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-textfile.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-uuid-helper.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-storage.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-advertising.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-agent.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-error.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-adapter.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-profile.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-service.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-gatt-client.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-device.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-dbus-common.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-eir.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-adv_monitor.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-battery.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-settings.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-set.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-bearer.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-plugin.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-main.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd$(EXEEXT): $(src_bluetoothd_OBJECTS) $(src_bluetoothd_DEPENDENCIES) $(EXTRA_src_bluetoothd_DEPENDENCIES) src/$(am__dirstamp) @rm -f src/bluetoothd$(EXEEXT) $(AM_V_CCLD)$(src_bluetoothd_LINK) $(src_bluetoothd_OBJECTS) $(src_bluetoothd_LDADD) $(LIBS) tools/$(am__dirstamp): @$(MKDIR_P) tools @: >>tools/$(am__dirstamp) tools/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) tools/$(DEPDIR) @: >>tools/$(DEPDIR)/$(am__dirstamp) tools/3dsp.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/3dsp$(EXEEXT): $(tools_3dsp_OBJECTS) $(tools_3dsp_DEPENDENCIES) $(EXTRA_tools_3dsp_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/3dsp$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_3dsp_OBJECTS) $(tools_3dsp_LDADD) $(LIBS) tools/6lowpan-tester.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) emulator/hciemu.$(OBJEXT): emulator/$(am__dirstamp) \ emulator/$(DEPDIR)/$(am__dirstamp) tools/6lowpan-tester$(EXEEXT): $(tools_6lowpan_tester_OBJECTS) $(tools_6lowpan_tester_DEPENDENCIES) $(EXTRA_tools_6lowpan_tester_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/6lowpan-tester$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_6lowpan_tester_OBJECTS) $(tools_6lowpan_tester_LDADD) $(LIBS) tools/advtest.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/advtest$(EXEEXT): $(tools_advtest_OBJECTS) $(tools_advtest_DEPENDENCIES) $(EXTRA_tools_advtest_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/advtest$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_advtest_OBJECTS) $(tools_advtest_LDADD) $(LIBS) tools/avinfo.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/avinfo$(EXEEXT): $(tools_avinfo_OBJECTS) $(tools_avinfo_DEPENDENCIES) $(EXTRA_tools_avinfo_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/avinfo$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_avinfo_OBJECTS) $(tools_avinfo_LDADD) $(LIBS) tools/avtest.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/avtest$(EXEEXT): $(tools_avtest_OBJECTS) $(tools_avtest_DEPENDENCIES) $(EXTRA_tools_avtest_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/avtest$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_avtest_OBJECTS) $(tools_avtest_LDADD) $(LIBS) tools/bcmfw.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/bcmfw$(EXEEXT): $(tools_bcmfw_OBJECTS) $(tools_bcmfw_DEPENDENCIES) $(EXTRA_tools_bcmfw_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/bcmfw$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_bcmfw_OBJECTS) $(tools_bcmfw_LDADD) $(LIBS) tools/bdaddr.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) src/oui.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) tools/bdaddr$(EXEEXT): $(tools_bdaddr_OBJECTS) $(tools_bdaddr_DEPENDENCIES) $(EXTRA_tools_bdaddr_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/bdaddr$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_bdaddr_OBJECTS) $(tools_bdaddr_LDADD) $(LIBS) tools/bluemoon.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/bluemoon$(EXEEXT): $(tools_bluemoon_OBJECTS) $(tools_bluemoon_DEPENDENCIES) $(EXTRA_tools_bluemoon_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/bluemoon$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_bluemoon_OBJECTS) $(tools_bluemoon_LDADD) $(LIBS) tools/bluetooth-player.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/bluetooth-player$(EXEEXT): $(tools_bluetooth_player_OBJECTS) $(tools_bluetooth_player_DEPENDENCIES) $(EXTRA_tools_bluetooth_player_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/bluetooth-player$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_bluetooth_player_OBJECTS) $(tools_bluetooth_player_LDADD) $(LIBS) tools/bnep-tester.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/bnep-tester$(EXEEXT): $(tools_bnep_tester_OBJECTS) $(tools_bnep_tester_DEPENDENCIES) $(EXTRA_tools_bnep_tester_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/bnep-tester$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_bnep_tester_OBJECTS) $(tools_bnep_tester_LDADD) $(LIBS) tools/bneptest.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) profiles/network/bnep.$(OBJEXT): profiles/network/$(am__dirstamp) \ profiles/network/$(DEPDIR)/$(am__dirstamp) tools/bneptest$(EXEEXT): $(tools_bneptest_OBJECTS) $(tools_bneptest_DEPENDENCIES) $(EXTRA_tools_bneptest_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/bneptest$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_bneptest_OBJECTS) $(tools_bneptest_LDADD) $(LIBS) tools/btattach.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/btattach$(EXEEXT): $(tools_btattach_OBJECTS) $(tools_btattach_DEPENDENCIES) $(EXTRA_tools_btattach_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/btattach$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_btattach_OBJECTS) $(tools_btattach_LDADD) $(LIBS) tools/btconfig.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/btconfig$(EXEEXT): $(tools_btconfig_OBJECTS) $(tools_btconfig_DEPENDENCIES) $(EXTRA_tools_btconfig_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/btconfig$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_btconfig_OBJECTS) $(tools_btconfig_LDADD) $(LIBS) tools/btgatt-client.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) src/uuid-helper.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) tools/btgatt-client$(EXEEXT): $(tools_btgatt_client_OBJECTS) $(tools_btgatt_client_DEPENDENCIES) $(EXTRA_tools_btgatt_client_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/btgatt-client$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_btgatt_client_OBJECTS) $(tools_btgatt_client_LDADD) $(LIBS) tools/btgatt-server.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/btgatt-server$(EXEEXT): $(tools_btgatt_server_OBJECTS) $(tools_btgatt_server_DEPENDENCIES) $(EXTRA_tools_btgatt_server_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/btgatt-server$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_btgatt_server_OBJECTS) $(tools_btgatt_server_LDADD) $(LIBS) tools/btinfo.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/btinfo$(EXEEXT): $(tools_btinfo_OBJECTS) $(tools_btinfo_DEPENDENCIES) $(EXTRA_tools_btinfo_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/btinfo$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_btinfo_OBJECTS) $(tools_btinfo_LDADD) $(LIBS) tools/btiotest.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/btiotest$(EXEEXT): $(tools_btiotest_OBJECTS) $(tools_btiotest_DEPENDENCIES) $(EXTRA_tools_btiotest_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/btiotest$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_btiotest_OBJECTS) $(tools_btiotest_LDADD) $(LIBS) tools/btmgmt.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/btmgmt$(EXEEXT): $(tools_btmgmt_OBJECTS) $(tools_btmgmt_DEPENDENCIES) $(EXTRA_tools_btmgmt_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/btmgmt$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_btmgmt_OBJECTS) $(tools_btmgmt_LDADD) $(LIBS) tools/btmon-logger.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/btmon-logger$(EXEEXT): $(tools_btmon_logger_OBJECTS) $(tools_btmon_logger_DEPENDENCIES) $(EXTRA_tools_btmon_logger_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/btmon-logger$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_btmon_logger_OBJECTS) $(tools_btmon_logger_LDADD) $(LIBS) tools/btproxy.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/btproxy$(EXEEXT): $(tools_btproxy_OBJECTS) $(tools_btproxy_DEPENDENCIES) $(EXTRA_tools_btproxy_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/btproxy$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_btproxy_OBJECTS) $(tools_btproxy_LDADD) $(LIBS) tools/btsnoop.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/btsnoop$(EXEEXT): $(tools_btsnoop_OBJECTS) $(tools_btsnoop_DEPENDENCIES) $(EXTRA_tools_btsnoop_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/btsnoop$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_btsnoop_OBJECTS) $(tools_btsnoop_LDADD) $(LIBS) tools/check-selftest.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/check-selftest$(EXEEXT): $(tools_check_selftest_OBJECTS) $(tools_check_selftest_DEPENDENCIES) $(EXTRA_tools_check_selftest_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/check-selftest$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_check_selftest_OBJECTS) $(tools_check_selftest_LDADD) $(LIBS) tools/ciptool.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/ciptool$(EXEEXT): $(tools_ciptool_OBJECTS) $(tools_ciptool_DEPENDENCIES) $(EXTRA_tools_ciptool_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/ciptool$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_ciptool_OBJECTS) $(tools_ciptool_LDADD) $(LIBS) tools/cltest.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/cltest$(EXEEXT): $(tools_cltest_OBJECTS) $(tools_cltest_DEPENDENCIES) $(EXTRA_tools_cltest_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/cltest$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_cltest_OBJECTS) $(tools_cltest_LDADD) $(LIBS) tools/create-image.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/create-image$(EXEEXT): $(tools_create_image_OBJECTS) $(tools_create_image_DEPENDENCIES) $(EXTRA_tools_create_image_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/create-image$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_create_image_OBJECTS) $(tools_create_image_LDADD) $(LIBS) tools/eddystone.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/eddystone$(EXEEXT): $(tools_eddystone_OBJECTS) $(tools_eddystone_DEPENDENCIES) $(EXTRA_tools_eddystone_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/eddystone$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_eddystone_OBJECTS) $(tools_eddystone_LDADD) $(LIBS) tools/gap-tester.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/gap-tester$(EXEEXT): $(tools_gap_tester_OBJECTS) $(tools_gap_tester_DEPENDENCIES) $(EXTRA_tools_gap_tester_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/gap-tester$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_gap_tester_OBJECTS) $(tools_gap_tester_LDADD) $(LIBS) tools/gatt-service.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/gatt-service$(EXEEXT): $(tools_gatt_service_OBJECTS) $(tools_gatt_service_DEPENDENCIES) $(EXTRA_tools_gatt_service_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/gatt-service$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_gatt_service_OBJECTS) $(tools_gatt_service_LDADD) $(LIBS) tools/hci-tester.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hci-tester$(EXEEXT): $(tools_hci_tester_OBJECTS) $(tools_hci_tester_DEPENDENCIES) $(EXTRA_tools_hci_tester_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/hci-tester$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_hci_tester_OBJECTS) $(tools_hci_tester_LDADD) $(LIBS) tools/hciattach.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hciattach_st.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hciattach_ti.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hciattach_tialt.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hciattach_ath3k.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hciattach_qualcomm.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hciattach_intel.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hciattach_bcm43xx.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hciattach$(EXEEXT): $(tools_hciattach_OBJECTS) $(tools_hciattach_DEPENDENCIES) $(EXTRA_tools_hciattach_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/hciattach$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_hciattach_OBJECTS) $(tools_hciattach_LDADD) $(LIBS) tools/hciconfig.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hciconfig$(EXEEXT): $(tools_hciconfig_OBJECTS) $(tools_hciconfig_DEPENDENCIES) $(EXTRA_tools_hciconfig_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/hciconfig$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_hciconfig_OBJECTS) $(tools_hciconfig_LDADD) $(LIBS) tools/hcieventmask.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hcieventmask$(EXEEXT): $(tools_hcieventmask_OBJECTS) $(tools_hcieventmask_DEPENDENCIES) $(EXTRA_tools_hcieventmask_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/hcieventmask$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_hcieventmask_OBJECTS) $(tools_hcieventmask_LDADD) $(LIBS) tools/hcisecfilter.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hcisecfilter$(EXEEXT): $(tools_hcisecfilter_OBJECTS) $(tools_hcisecfilter_DEPENDENCIES) $(EXTRA_tools_hcisecfilter_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/hcisecfilter$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_hcisecfilter_OBJECTS) $(tools_hcisecfilter_LDADD) $(LIBS) tools/hcitool.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hcitool$(EXEEXT): $(tools_hcitool_OBJECTS) $(tools_hcitool_DEPENDENCIES) $(EXTRA_tools_hcitool_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/hcitool$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_hcitool_OBJECTS) $(tools_hcitool_LDADD) $(LIBS) tools/hex2hcd.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hex2hcd$(EXEEXT): $(tools_hex2hcd_OBJECTS) $(tools_hex2hcd_DEPENDENCIES) $(EXTRA_tools_hex2hcd_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/hex2hcd$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_hex2hcd_OBJECTS) $(tools_hex2hcd_LDADD) $(LIBS) tools/hid2hci.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hid2hci$(EXEEXT): $(tools_hid2hci_OBJECTS) $(tools_hid2hci_DEPENDENCIES) $(EXTRA_tools_hid2hci_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/hid2hci$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_hid2hci_OBJECTS) $(tools_hid2hci_LDADD) $(LIBS) tools/hwdb.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/hwdb$(EXEEXT): $(tools_hwdb_OBJECTS) $(tools_hwdb_DEPENDENCIES) $(EXTRA_tools_hwdb_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/hwdb$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_hwdb_OBJECTS) $(tools_hwdb_LDADD) $(LIBS) tools/ibeacon.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/ibeacon$(EXEEXT): $(tools_ibeacon_OBJECTS) $(tools_ibeacon_DEPENDENCIES) $(EXTRA_tools_ibeacon_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/ibeacon$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_ibeacon_OBJECTS) $(tools_ibeacon_LDADD) $(LIBS) tools/ioctl-tester.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/ioctl-tester$(EXEEXT): $(tools_ioctl_tester_OBJECTS) $(tools_ioctl_tester_DEPENDENCIES) $(EXTRA_tools_ioctl_tester_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/ioctl-tester$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_ioctl_tester_OBJECTS) $(tools_ioctl_tester_LDADD) $(LIBS) tools/iso-tester.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/iso-tester$(EXEEXT): $(tools_iso_tester_OBJECTS) $(tools_iso_tester_DEPENDENCIES) $(EXTRA_tools_iso_tester_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/iso-tester$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_iso_tester_OBJECTS) $(tools_iso_tester_LDADD) $(LIBS) tools/isotest.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/isotest$(EXEEXT): $(tools_isotest_OBJECTS) $(tools_isotest_DEPENDENCIES) $(EXTRA_tools_isotest_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/isotest$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_isotest_OBJECTS) $(tools_isotest_LDADD) $(LIBS) tools/l2cap-tester.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/l2cap-tester$(EXEEXT): $(tools_l2cap_tester_OBJECTS) $(tools_l2cap_tester_DEPENDENCIES) $(EXTRA_tools_l2cap_tester_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/l2cap-tester$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_l2cap_tester_OBJECTS) $(tools_l2cap_tester_LDADD) $(LIBS) tools/l2ping.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/l2ping$(EXEEXT): $(tools_l2ping_OBJECTS) $(tools_l2ping_DEPENDENCIES) $(EXTRA_tools_l2ping_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/l2ping$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_l2ping_OBJECTS) $(tools_l2ping_LDADD) $(LIBS) tools/l2test.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/l2test$(EXEEXT): $(tools_l2test_OBJECTS) $(tools_l2test_DEPENDENCIES) $(EXTRA_tools_l2test_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/l2test$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_l2test_OBJECTS) $(tools_l2test_LDADD) $(LIBS) tools/mesh-cfgclient.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/mesh/$(am__dirstamp): @$(MKDIR_P) tools/mesh @: >>tools/mesh/$(am__dirstamp) tools/mesh/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) tools/mesh/$(DEPDIR) @: >>tools/mesh/$(DEPDIR)/$(am__dirstamp) tools/mesh/cfgcli.$(OBJEXT): tools/mesh/$(am__dirstamp) \ tools/mesh/$(DEPDIR)/$(am__dirstamp) tools/mesh/keys.$(OBJEXT): tools/mesh/$(am__dirstamp) \ tools/mesh/$(DEPDIR)/$(am__dirstamp) tools/mesh/util.$(OBJEXT): tools/mesh/$(am__dirstamp) \ tools/mesh/$(DEPDIR)/$(am__dirstamp) tools/mesh/remote.$(OBJEXT): tools/mesh/$(am__dirstamp) \ tools/mesh/$(DEPDIR)/$(am__dirstamp) tools/mesh/agent.$(OBJEXT): tools/mesh/$(am__dirstamp) \ tools/mesh/$(DEPDIR)/$(am__dirstamp) tools/mesh/mesh-db.$(OBJEXT): tools/mesh/$(am__dirstamp) \ tools/mesh/$(DEPDIR)/$(am__dirstamp) tools/mesh-cfgclient$(EXEEXT): $(tools_mesh_cfgclient_OBJECTS) $(tools_mesh_cfgclient_DEPENDENCIES) $(EXTRA_tools_mesh_cfgclient_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/mesh-cfgclient$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_mesh_cfgclient_OBJECTS) $(tools_mesh_cfgclient_LDADD) $(LIBS) tools/mesh-cfgtest.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/mesh-cfgtest$(EXEEXT): $(tools_mesh_cfgtest_OBJECTS) $(tools_mesh_cfgtest_DEPENDENCIES) $(EXTRA_tools_mesh_cfgtest_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/mesh-cfgtest$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_mesh_cfgtest_OBJECTS) $(tools_mesh_cfgtest_LDADD) $(LIBS) tools/mesh-tester.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/mesh-tester$(EXEEXT): $(tools_mesh_tester_OBJECTS) $(tools_mesh_tester_DEPENDENCIES) $(EXTRA_tools_mesh_tester_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/mesh-tester$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_mesh_tester_OBJECTS) $(tools_mesh_tester_LDADD) $(LIBS) tools/meshctl.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/mesh-gatt/$(am__dirstamp): @$(MKDIR_P) tools/mesh-gatt @: >>tools/mesh-gatt/$(am__dirstamp) tools/mesh-gatt/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) tools/mesh-gatt/$(DEPDIR) @: >>tools/mesh-gatt/$(DEPDIR)/$(am__dirstamp) tools/mesh-gatt/node.$(OBJEXT): tools/mesh-gatt/$(am__dirstamp) \ tools/mesh-gatt/$(DEPDIR)/$(am__dirstamp) tools/mesh-gatt/gatt.$(OBJEXT): tools/mesh-gatt/$(am__dirstamp) \ tools/mesh-gatt/$(DEPDIR)/$(am__dirstamp) tools/mesh-gatt/crypto.$(OBJEXT): tools/mesh-gatt/$(am__dirstamp) \ tools/mesh-gatt/$(DEPDIR)/$(am__dirstamp) tools/mesh-gatt/net.$(OBJEXT): tools/mesh-gatt/$(am__dirstamp) \ tools/mesh-gatt/$(DEPDIR)/$(am__dirstamp) tools/mesh-gatt/prov.$(OBJEXT): tools/mesh-gatt/$(am__dirstamp) \ tools/mesh-gatt/$(DEPDIR)/$(am__dirstamp) tools/mesh-gatt/util.$(OBJEXT): tools/mesh-gatt/$(am__dirstamp) \ tools/mesh-gatt/$(DEPDIR)/$(am__dirstamp) tools/mesh-gatt/prov-db.$(OBJEXT): tools/mesh-gatt/$(am__dirstamp) \ tools/mesh-gatt/$(DEPDIR)/$(am__dirstamp) tools/mesh-gatt/config-client.$(OBJEXT): \ tools/mesh-gatt/$(am__dirstamp) \ tools/mesh-gatt/$(DEPDIR)/$(am__dirstamp) tools/mesh-gatt/config-server.$(OBJEXT): \ tools/mesh-gatt/$(am__dirstamp) \ tools/mesh-gatt/$(DEPDIR)/$(am__dirstamp) tools/mesh-gatt/onoff-model.$(OBJEXT): \ tools/mesh-gatt/$(am__dirstamp) \ tools/mesh-gatt/$(DEPDIR)/$(am__dirstamp) tools/meshctl$(EXEEXT): $(tools_meshctl_OBJECTS) $(tools_meshctl_DEPENDENCIES) $(EXTRA_tools_meshctl_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/meshctl$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_meshctl_OBJECTS) $(tools_meshctl_LDADD) $(LIBS) tools/mgmt-tester.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/mgmt-tester$(EXEEXT): $(tools_mgmt_tester_OBJECTS) $(tools_mgmt_tester_DEPENDENCIES) $(EXTRA_tools_mgmt_tester_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/mgmt-tester$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_mgmt_tester_OBJECTS) $(tools_mgmt_tester_LDADD) $(LIBS) tools/mpris-proxy.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/mpris-proxy$(EXEEXT): $(tools_mpris_proxy_OBJECTS) $(tools_mpris_proxy_DEPENDENCIES) $(EXTRA_tools_mpris_proxy_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/mpris-proxy$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_mpris_proxy_OBJECTS) $(tools_mpris_proxy_LDADD) $(LIBS) tools/nokfw.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/nokfw$(EXEEXT): $(tools_nokfw_OBJECTS) $(tools_nokfw_DEPENDENCIES) $(EXTRA_tools_nokfw_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/nokfw$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_nokfw_OBJECTS) $(tools_nokfw_LDADD) $(LIBS) gobex/gobex.$(OBJEXT): gobex/$(am__dirstamp) \ gobex/$(DEPDIR)/$(am__dirstamp) gobex/gobex-defs.$(OBJEXT): gobex/$(am__dirstamp) \ gobex/$(DEPDIR)/$(am__dirstamp) gobex/gobex-packet.$(OBJEXT): gobex/$(am__dirstamp) \ gobex/$(DEPDIR)/$(am__dirstamp) gobex/gobex-header.$(OBJEXT): gobex/$(am__dirstamp) \ gobex/$(DEPDIR)/$(am__dirstamp) gobex/gobex-transfer.$(OBJEXT): gobex/$(am__dirstamp) \ gobex/$(DEPDIR)/$(am__dirstamp) gobex/gobex-apparam.$(OBJEXT): gobex/$(am__dirstamp) \ gobex/$(DEPDIR)/$(am__dirstamp) tools/obex-client-tool.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/obex-client-tool$(EXEEXT): $(tools_obex_client_tool_OBJECTS) $(tools_obex_client_tool_DEPENDENCIES) $(EXTRA_tools_obex_client_tool_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/obex-client-tool$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_obex_client_tool_OBJECTS) $(tools_obex_client_tool_LDADD) $(LIBS) tools/obex-server-tool.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/obex-server-tool$(EXEEXT): $(tools_obex_server_tool_OBJECTS) $(tools_obex_server_tool_DEPENDENCIES) $(EXTRA_tools_obex_server_tool_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/obex-server-tool$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_obex_server_tool_OBJECTS) $(tools_obex_server_tool_LDADD) $(LIBS) tools/obexctl.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/obexctl$(EXEEXT): $(tools_obexctl_OBJECTS) $(tools_obexctl_DEPENDENCIES) $(EXTRA_tools_obexctl_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/obexctl$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_obexctl_OBJECTS) $(tools_obexctl_LDADD) $(LIBS) tools/oobtest.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/oobtest$(EXEEXT): $(tools_oobtest_OBJECTS) $(tools_oobtest_DEPENDENCIES) $(EXTRA_tools_oobtest_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/oobtest$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_oobtest_OBJECTS) $(tools_oobtest_LDADD) $(LIBS) tools/rctest.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/rctest$(EXEEXT): $(tools_rctest_OBJECTS) $(tools_rctest_DEPENDENCIES) $(EXTRA_tools_rctest_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/rctest$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_rctest_OBJECTS) $(tools_rctest_LDADD) $(LIBS) tools/rfcomm.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/rfcomm$(EXEEXT): $(tools_rfcomm_OBJECTS) $(tools_rfcomm_DEPENDENCIES) $(EXTRA_tools_rfcomm_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/rfcomm$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_rfcomm_OBJECTS) $(tools_rfcomm_LDADD) $(LIBS) tools/rfcomm-tester.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/rfcomm-tester$(EXEEXT): $(tools_rfcomm_tester_OBJECTS) $(tools_rfcomm_tester_DEPENDENCIES) $(EXTRA_tools_rfcomm_tester_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/rfcomm-tester$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_rfcomm_tester_OBJECTS) $(tools_rfcomm_tester_LDADD) $(LIBS) tools/rtlfw.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/rtlfw$(EXEEXT): $(tools_rtlfw_OBJECTS) $(tools_rtlfw_DEPENDENCIES) $(EXTRA_tools_rtlfw_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/rtlfw$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_rtlfw_OBJECTS) $(tools_rtlfw_LDADD) $(LIBS) tools/sco-tester.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/sco-tester$(EXEEXT): $(tools_sco_tester_OBJECTS) $(tools_sco_tester_DEPENDENCIES) $(EXTRA_tools_sco_tester_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/sco-tester$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_sco_tester_OBJECTS) $(tools_sco_tester_LDADD) $(LIBS) tools/scotest.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/scotest$(EXEEXT): $(tools_scotest_OBJECTS) $(tools_scotest_DEPENDENCIES) $(EXTRA_tools_scotest_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/scotest$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_scotest_OBJECTS) $(tools_scotest_LDADD) $(LIBS) tools/sdptool.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) src/sdp-xml.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) tools/sdptool$(EXEEXT): $(tools_sdptool_OBJECTS) $(tools_sdptool_DEPENDENCIES) $(EXTRA_tools_sdptool_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/sdptool$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_sdptool_OBJECTS) $(tools_sdptool_LDADD) $(LIBS) tools/seq2bseq.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/seq2bseq$(EXEEXT): $(tools_seq2bseq_OBJECTS) $(tools_seq2bseq_DEPENDENCIES) $(EXTRA_tools_seq2bseq_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/seq2bseq$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_seq2bseq_OBJECTS) $(tools_seq2bseq_LDADD) $(LIBS) tools/smp-tester.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/smp-tester$(EXEEXT): $(tools_smp_tester_OBJECTS) $(tools_smp_tester_DEPENDENCIES) $(EXTRA_tools_smp_tester_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/smp-tester$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_smp_tester_OBJECTS) $(tools_smp_tester_LDADD) $(LIBS) tools/test_runner-test-runner.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/test-runner$(EXEEXT): $(tools_test_runner_OBJECTS) $(tools_test_runner_DEPENDENCIES) $(EXTRA_tools_test_runner_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/test-runner$(EXEEXT) $(AM_V_CCLD)$(tools_test_runner_LINK) $(tools_test_runner_OBJECTS) $(tools_test_runner_LDADD) $(LIBS) tools/userchan-tester.$(OBJEXT): tools/$(am__dirstamp) \ tools/$(DEPDIR)/$(am__dirstamp) tools/userchan-tester$(EXEEXT): $(tools_userchan_tester_OBJECTS) $(tools_userchan_tester_DEPENDENCIES) $(EXTRA_tools_userchan_tester_DEPENDENCIES) tools/$(am__dirstamp) @rm -f tools/userchan-tester$(EXEEXT) $(AM_V_CCLD)$(LINK) $(tools_userchan_tester_OBJECTS) $(tools_userchan_tester_LDADD) $(LIBS) unit/$(am__dirstamp): @$(MKDIR_P) unit @: >>unit/$(am__dirstamp) unit/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) unit/$(DEPDIR) @: >>unit/$(DEPDIR)/$(am__dirstamp) unit/test-avctp.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/avctp.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-avctp$(EXEEXT): $(unit_test_avctp_OBJECTS) $(unit_test_avctp_DEPENDENCIES) $(EXTRA_unit_test_avctp_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-avctp$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_avctp_OBJECTS) $(unit_test_avctp_LDADD) $(LIBS) unit/test-avdtp.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/avdtp.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-avdtp$(EXEEXT): $(unit_test_avdtp_OBJECTS) $(unit_test_avdtp_DEPENDENCIES) $(EXTRA_unit_test_avdtp_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-avdtp$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_avdtp_OBJECTS) $(unit_test_avdtp_LDADD) $(LIBS) unit/test-avrcp.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/avrcp-lib.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-avrcp$(EXEEXT): $(unit_test_avrcp_OBJECTS) $(unit_test_avrcp_DEPENDENCIES) $(EXTRA_unit_test_avrcp_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-avrcp$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_avrcp_OBJECTS) $(unit_test_avrcp_LDADD) $(LIBS) unit/test-bap.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-bap$(EXEEXT): $(unit_test_bap_OBJECTS) $(unit_test_bap_DEPENDENCIES) $(EXTRA_unit_test_bap_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-bap$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_bap_OBJECTS) $(unit_test_bap_LDADD) $(LIBS) unit/test-bass.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-bass$(EXEEXT): $(unit_test_bass_OBJECTS) $(unit_test_bass_DEPENDENCIES) $(EXTRA_unit_test_bass_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-bass$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_bass_OBJECTS) $(unit_test_bass_LDADD) $(LIBS) unit/test-battery.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-battery$(EXEEXT): $(unit_test_battery_OBJECTS) $(unit_test_battery_DEPENDENCIES) $(EXTRA_unit_test_battery_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-battery$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_battery_OBJECTS) $(unit_test_battery_LDADD) $(LIBS) unit/test-crc.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-crc$(EXEEXT): $(unit_test_crc_OBJECTS) $(unit_test_crc_DEPENDENCIES) $(EXTRA_unit_test_crc_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-crc$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_crc_OBJECTS) $(unit_test_crc_LDADD) $(LIBS) unit/test-crypto.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-crypto$(EXEEXT): $(unit_test_crypto_OBJECTS) $(unit_test_crypto_DEPENDENCIES) $(EXTRA_unit_test_crypto_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-crypto$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_crypto_OBJECTS) $(unit_test_crypto_LDADD) $(LIBS) unit/test-ecc.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-ecc$(EXEEXT): $(unit_test_ecc_OBJECTS) $(unit_test_ecc_DEPENDENCIES) $(EXTRA_unit_test_ecc_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-ecc$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_ecc_OBJECTS) $(unit_test_ecc_LDADD) $(LIBS) unit/test-eir.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) src/eir.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) unit/test-eir$(EXEEXT): $(unit_test_eir_OBJECTS) $(unit_test_eir_DEPENDENCIES) $(EXTRA_unit_test_eir_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-eir$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_eir_OBJECTS) $(unit_test_eir_LDADD) $(LIBS) unit/test-gatt.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-gatt$(EXEEXT): $(unit_test_gatt_OBJECTS) $(unit_test_gatt_DEPENDENCIES) $(EXTRA_unit_test_gatt_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-gatt$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_gatt_OBJECTS) $(unit_test_gatt_LDADD) $(LIBS) unit/test-gattrib.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-gattrib$(EXEEXT): $(unit_test_gattrib_OBJECTS) $(unit_test_gattrib_DEPENDENCIES) $(EXTRA_unit_test_gattrib_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-gattrib$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_gattrib_OBJECTS) $(unit_test_gattrib_LDADD) $(LIBS) unit/test-gdbus-client.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-gdbus-client$(EXEEXT): $(unit_test_gdbus_client_OBJECTS) $(unit_test_gdbus_client_DEPENDENCIES) $(EXTRA_unit_test_gdbus_client_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-gdbus-client$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_gdbus_client_OBJECTS) $(unit_test_gdbus_client_LDADD) $(LIBS) unit/test-gmap.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-gmap$(EXEEXT): $(unit_test_gmap_OBJECTS) $(unit_test_gmap_DEPENDENCIES) $(EXTRA_unit_test_gmap_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-gmap$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_gmap_OBJECTS) $(unit_test_gmap_LDADD) $(LIBS) unit/util.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-gobex.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-gobex$(EXEEXT): $(unit_test_gobex_OBJECTS) $(unit_test_gobex_DEPENDENCIES) $(EXTRA_unit_test_gobex_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-gobex$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_gobex_OBJECTS) $(unit_test_gobex_LDADD) $(LIBS) unit/test-gobex-apparam.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-gobex-apparam$(EXEEXT): $(unit_test_gobex_apparam_OBJECTS) $(unit_test_gobex_apparam_DEPENDENCIES) $(EXTRA_unit_test_gobex_apparam_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-gobex-apparam$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_gobex_apparam_OBJECTS) $(unit_test_gobex_apparam_LDADD) $(LIBS) unit/test-gobex-header.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-gobex-header$(EXEEXT): $(unit_test_gobex_header_OBJECTS) $(unit_test_gobex_header_DEPENDENCIES) $(EXTRA_unit_test_gobex_header_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-gobex-header$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_gobex_header_OBJECTS) $(unit_test_gobex_header_LDADD) $(LIBS) unit/test-gobex-packet.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-gobex-packet$(EXEEXT): $(unit_test_gobex_packet_OBJECTS) $(unit_test_gobex_packet_DEPENDENCIES) $(EXTRA_unit_test_gobex_packet_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-gobex-packet$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_gobex_packet_OBJECTS) $(unit_test_gobex_packet_LDADD) $(LIBS) unit/test-gobex-transfer.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-gobex-transfer$(EXEEXT): $(unit_test_gobex_transfer_OBJECTS) $(unit_test_gobex_transfer_DEPENDENCIES) $(EXTRA_unit_test_gobex_transfer_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-gobex-transfer$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_gobex_transfer_OBJECTS) $(unit_test_gobex_transfer_LDADD) $(LIBS) unit/test-hfp.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-hfp$(EXEEXT): $(unit_test_hfp_OBJECTS) $(unit_test_hfp_DEPENDENCIES) $(EXTRA_unit_test_hfp_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-hfp$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_hfp_OBJECTS) $(unit_test_hfp_LDADD) $(LIBS) unit/test-hog.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) profiles/input/hog-lib.$(OBJEXT): profiles/input/$(am__dirstamp) \ profiles/input/$(DEPDIR)/$(am__dirstamp) profiles/scanparam/scpp.$(OBJEXT): profiles/scanparam/$(am__dirstamp) \ profiles/scanparam/$(DEPDIR)/$(am__dirstamp) profiles/battery/bas.$(OBJEXT): profiles/battery/$(am__dirstamp) \ profiles/battery/$(DEPDIR)/$(am__dirstamp) profiles/deviceinfo/dis.$(OBJEXT): \ profiles/deviceinfo/$(am__dirstamp) \ profiles/deviceinfo/$(DEPDIR)/$(am__dirstamp) unit/test-hog$(EXEEXT): $(unit_test_hog_OBJECTS) $(unit_test_hog_DEPENDENCIES) $(EXTRA_unit_test_hog_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-hog$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_hog_OBJECTS) $(unit_test_hog_LDADD) $(LIBS) unit/test-lib.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-lib$(EXEEXT): $(unit_test_lib_OBJECTS) $(unit_test_lib_DEPENDENCIES) $(EXTRA_unit_test_lib_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-lib$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_lib_OBJECTS) $(unit_test_lib_LDADD) $(LIBS) unit/test-mcp.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-mcp$(EXEEXT): $(unit_test_mcp_OBJECTS) $(unit_test_mcp_DEPENDENCIES) $(EXTRA_unit_test_mcp_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-mcp$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_mcp_OBJECTS) $(unit_test_mcp_LDADD) $(LIBS) unit/test_mesh_crypto-test-mesh-crypto.$(OBJEXT): \ unit/$(am__dirstamp) unit/$(DEPDIR)/$(am__dirstamp) unit/test-mesh-crypto$(EXEEXT): $(unit_test_mesh_crypto_OBJECTS) $(unit_test_mesh_crypto_DEPENDENCIES) $(EXTRA_unit_test_mesh_crypto_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-mesh-crypto$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_mesh_crypto_OBJECTS) $(unit_test_mesh_crypto_LDADD) $(LIBS) unit/test-mgmt.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-mgmt$(EXEEXT): $(unit_test_mgmt_OBJECTS) $(unit_test_mgmt_DEPENDENCIES) $(EXTRA_unit_test_mgmt_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-mgmt$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_mgmt_OBJECTS) $(unit_test_mgmt_LDADD) $(LIBS) unit/test-micp.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-micp$(EXEEXT): $(unit_test_micp_OBJECTS) $(unit_test_micp_DEPENDENCIES) $(EXTRA_unit_test_micp_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-micp$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_micp_OBJECTS) $(unit_test_micp_LDADD) $(LIBS) unit/test_midi-test-midi.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) profiles/midi/unit_test_midi-libmidi.$(OBJEXT): \ profiles/midi/$(am__dirstamp) \ profiles/midi/$(DEPDIR)/$(am__dirstamp) unit/test-midi$(EXEEXT): $(unit_test_midi_OBJECTS) $(unit_test_midi_DEPENDENCIES) $(EXTRA_unit_test_midi_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-midi$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_midi_OBJECTS) $(unit_test_midi_LDADD) $(LIBS) unit/test-profile.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/btd.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) src/backtrace.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/rfkill.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/sdpd-server.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/sdpd-request.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/sdpd-service.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/sdpd-database.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/gatt-database.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/sdp-client.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/storage.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/advertising.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/agent.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) src/error.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) src/adapter.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/profile.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/service.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/gatt-client.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/device.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/dbus-common.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/adv_monitor.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/battery.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) src/set.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) src/bearer.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) unit/test-profile$(EXEEXT): $(unit_test_profile_OBJECTS) $(unit_test_profile_DEPENDENCIES) $(EXTRA_unit_test_profile_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-profile$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_profile_OBJECTS) $(unit_test_profile_LDADD) $(LIBS) unit/test-queue.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-queue$(EXEEXT): $(unit_test_queue_OBJECTS) $(unit_test_queue_DEPENDENCIES) $(EXTRA_unit_test_queue_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-queue$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_queue_OBJECTS) $(unit_test_queue_LDADD) $(LIBS) unit/test-rap.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-rap$(EXEEXT): $(unit_test_rap_OBJECTS) $(unit_test_rap_DEPENDENCIES) $(EXTRA_unit_test_rap_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-rap$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_rap_OBJECTS) $(unit_test_rap_LDADD) $(LIBS) unit/test-ringbuf.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-ringbuf$(EXEEXT): $(unit_test_ringbuf_OBJECTS) $(unit_test_ringbuf_DEPENDENCIES) $(EXTRA_unit_test_ringbuf_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-ringbuf$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_ringbuf_OBJECTS) $(unit_test_ringbuf_LDADD) $(LIBS) unit/test-sdp.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-sdp$(EXEEXT): $(unit_test_sdp_OBJECTS) $(unit_test_sdp_DEPENDENCIES) $(EXTRA_unit_test_sdp_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-sdp$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_sdp_OBJECTS) $(unit_test_sdp_LDADD) $(LIBS) unit/test-tester.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-tester$(EXEEXT): $(unit_test_tester_OBJECTS) $(unit_test_tester_DEPENDENCIES) $(EXTRA_unit_test_tester_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-tester$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_tester_OBJECTS) $(unit_test_tester_LDADD) $(LIBS) unit/test-textfile.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-textfile$(EXEEXT): $(unit_test_textfile_OBJECTS) $(unit_test_textfile_DEPENDENCIES) $(EXTRA_unit_test_textfile_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-textfile$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_textfile_OBJECTS) $(unit_test_textfile_LDADD) $(LIBS) unit/test-tmap.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-tmap$(EXEEXT): $(unit_test_tmap_OBJECTS) $(unit_test_tmap_DEPENDENCIES) $(EXTRA_unit_test_tmap_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-tmap$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_tmap_OBJECTS) $(unit_test_tmap_LDADD) $(LIBS) unit/test-uhid.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-uhid$(EXEEXT): $(unit_test_uhid_OBJECTS) $(unit_test_uhid_DEPENDENCIES) $(EXTRA_unit_test_uhid_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-uhid$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_uhid_OBJECTS) $(unit_test_uhid_LDADD) $(LIBS) unit/test-util.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-util$(EXEEXT): $(unit_test_util_OBJECTS) $(unit_test_util_DEPENDENCIES) $(EXTRA_unit_test_util_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-util$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_util_OBJECTS) $(unit_test_util_LDADD) $(LIBS) unit/test-uuid.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-uuid$(EXEEXT): $(unit_test_uuid_OBJECTS) $(unit_test_uuid_DEPENDENCIES) $(EXTRA_unit_test_uuid_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-uuid$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_uuid_OBJECTS) $(unit_test_uuid_LDADD) $(LIBS) unit/test-vcp.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) unit/test-vcp$(EXEEXT): $(unit_test_vcp_OBJECTS) $(unit_test_vcp_DEPENDENCIES) $(EXTRA_unit_test_vcp_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-vcp$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_vcp_OBJECTS) $(unit_test_vcp_LDADD) $(LIBS) install-testSCRIPTS: $(test_SCRIPTS) @$(NORMAL_INSTALL) @list='$(test_SCRIPTS)'; test -n "$(testdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(testdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(testdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n' \ -e 'h;s|.*|.|' \ -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) { files[d] = files[d] " " $$1; \ if (++n[d] == $(am__install_max)) { \ print "f", d, files[d]; n[d] = 0; files[d] = "" } } \ else { print "f", d "/" $$4, $$1 } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(testdir)$$dir'"; \ $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(testdir)$$dir" || exit $$?; \ } \ ; done uninstall-testSCRIPTS: @$(NORMAL_UNINSTALL) @list='$(test_SCRIPTS)'; test -n "$(testdir)" || exit 0; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 's,.*/,,;$(transform)'`; \ dir='$(DESTDIR)$(testdir)'; $(am__uninstall_files_from_dir) mostlyclean-compile: -rm -f *.$(OBJEXT) -rm -f attrib/*.$(OBJEXT) -rm -f btio/*.$(OBJEXT) -rm -f client/*.$(OBJEXT) -rm -f client/btpclient/*.$(OBJEXT) -rm -f ell/*.$(OBJEXT) -rm -f ell/*.lo -rm -f emulator/*.$(OBJEXT) -rm -f gdbus/*.$(OBJEXT) -rm -f gdbus/*.lo -rm -f gobex/*.$(OBJEXT) -rm -f lib/bluetooth/*.$(OBJEXT) -rm -f lib/bluetooth/*.lo -rm -f mesh/*.$(OBJEXT) -rm -f monitor/*.$(OBJEXT) -rm -f obexd/client/*.$(OBJEXT) -rm -f obexd/plugins/*.$(OBJEXT) -rm -f obexd/src/*.$(OBJEXT) -rm -f peripheral/*.$(OBJEXT) -rm -f plugins/*.$(OBJEXT) -rm -f profiles/audio/*.$(OBJEXT) -rm -f profiles/battery/*.$(OBJEXT) -rm -f profiles/cups/*.$(OBJEXT) -rm -f profiles/deviceinfo/*.$(OBJEXT) -rm -f profiles/gap/*.$(OBJEXT) -rm -f profiles/iap/*.$(OBJEXT) -rm -f profiles/input/*.$(OBJEXT) -rm -f profiles/midi/*.$(OBJEXT) -rm -f profiles/network/*.$(OBJEXT) -rm -f profiles/ranging/*.$(OBJEXT) -rm -f profiles/scanparam/*.$(OBJEXT) -rm -f src/*.$(OBJEXT) -rm -f src/shared/*.$(OBJEXT) -rm -f src/shared/*.lo -rm -f tools/*.$(OBJEXT) -rm -f tools/mesh-gatt/*.$(OBJEXT) -rm -f tools/mesh/*.$(OBJEXT) -rm -f unit/*.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/att.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/bluetoothd-att.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/bluetoothd-gatt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/bluetoothd-gattrib.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/gatt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/gattrib.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/gatttool.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/interactive.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/utils.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@btio/$(DEPDIR)/bluetoothd-btio.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@btio/$(DEPDIR)/btio.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@btio/$(DEPDIR)/obexd-btio.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/admin.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/adv_monitor.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/advertising.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/agent.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/assistant.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/display.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/gatt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/hci.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/mgmt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/player.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/print.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/telephony.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/btpclient/$(DEPDIR)/bap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/btpclient/$(DEPDIR)/btpclient.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/btpclient/$(DEPDIR)/btpclientctl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/btpclient/$(DEPDIR)/core.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/btpclient/$(DEPDIR)/gap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/btpclient/$(DEPDIR)/gatt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/base64.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/cert-crypto.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/cert.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/checksum.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/cipher.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/dbus-client.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/dbus-filter.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/dbus-message.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/dbus-name-cache.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/dbus-service.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/dbus-util.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/dbus.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/ecc-external.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/ecc.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/ecdh.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/gvariant-util.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/hashmap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/idle.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/io.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/key.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/log.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/main.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/pem.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/queue.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/random.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/settings.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/signal.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/siphash.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/string.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/strv.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/tester.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/time.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/timeout.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/tls-extensions.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/tls-record.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/tls-suites.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/tls.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/utf8.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/util.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/uuid.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/b1ee.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/btdev.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/bthost.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/hciemu.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/hfp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/le.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/phy.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/serial.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/smp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/vhci.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gdbus/$(DEPDIR)/client.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gdbus/$(DEPDIR)/mainloop.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gdbus/$(DEPDIR)/object.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gdbus/$(DEPDIR)/polkit.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gdbus/$(DEPDIR)/watch.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/gobex-apparam.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/gobex-defs.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/gobex-header.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/gobex-packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/gobex-transfer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/gobex.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/obexd-gobex-apparam.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/obexd-gobex-defs.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/obexd-gobex-header.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/obexd-gobex-packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/obexd-gobex-transfer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/obexd-gobex.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/bluetooth/$(DEPDIR)/bluetooth.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/bluetooth/$(DEPDIR)/hci.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/bluetooth/$(DEPDIR)/sdp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@lib/bluetooth/$(DEPDIR)/uuid.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/agent.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/appkey.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/cfgmod-server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/crypto.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/dbus.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/friend.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/keyring.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/manager.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/mesh-config-json.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/mesh-io-generic.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/mesh-io-mgmt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/mesh-io-unit.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/mesh-io.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/mesh-mgmt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/mesh.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/model.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/net-keys.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/net.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/node.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/pb-adv.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/prov-acceptor.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/prov-initiator.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/prvbeac-server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/remprv-server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/rpl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/a2dp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/analyze.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/att.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/avctp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/avdtp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/bnep.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/broadcom.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/control.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/crc.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/display.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/ellisys.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/hcidump.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/hwdb.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/intel.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/jlink.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/keys.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/l2cap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/ll.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/lmp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/msft.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/rfcomm.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/sdp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/vendor.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-bip-common.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-bip.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-bluetooth.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-driver.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-ftp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-manager.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-map-event.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-map.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-mns.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-opp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-pbap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-session.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-sync.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-transfer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-transport.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-bluetooth.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-filesystem.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-ftp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-irmc.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-mas.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-opp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-pbap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-pcsuite.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-phonebook-@PLUGIN_PHONEBOOK@.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-vcard.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-log.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-logind.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-manager.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-mimetype.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-obex.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-plugin.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-service.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-transport.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@peripheral/$(DEPDIR)/attach.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@peripheral/$(DEPDIR)/efivars.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@peripheral/$(DEPDIR)/gap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@peripheral/$(DEPDIR)/gatt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@peripheral/$(DEPDIR)/log.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@peripheral/$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/bluetoothd-admin.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/bluetoothd-autopair.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/bluetoothd-hostname.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/bluetoothd-neard.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/bluetoothd-policy.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/bluetoothd-sixaxis.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-asha.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-avctp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-avrcp-player.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-bap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-bass.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-ccp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-control.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-csip.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-gmap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-hfp-hf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-mcp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-media.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-micp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-player.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-sink.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-source.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-telephony.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-tmap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-transport.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-vcp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/battery/$(DEPDIR)/bas.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/battery/$(DEPDIR)/bluetoothd-bas.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/battery/$(DEPDIR)/bluetoothd-battery.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/cups/$(DEPDIR)/hcrp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/cups/$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/cups/$(DEPDIR)/sdp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/cups/$(DEPDIR)/spp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/deviceinfo/$(DEPDIR)/dis.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/gap/$(DEPDIR)/bluetoothd-gas.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/iap/$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/input/$(DEPDIR)/bluetoothd-device.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/input/$(DEPDIR)/bluetoothd-hog.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/input/$(DEPDIR)/bluetoothd-manager.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/input/$(DEPDIR)/bluetoothd-server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/input/$(DEPDIR)/hog-lib.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/midi/$(DEPDIR)/bluetoothd-midi.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/network/$(DEPDIR)/bluetoothd-bnep.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/network/$(DEPDIR)/bluetoothd-connection.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/network/$(DEPDIR)/bluetoothd-manager.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/network/$(DEPDIR)/bluetoothd-server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/network/$(DEPDIR)/bnep.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/ranging/$(DEPDIR)/bluetoothd-rap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/ranging/$(DEPDIR)/bluetoothd-rap_hci.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/scanparam/$(DEPDIR)/scpp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/adapter.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/adv_monitor.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/advertising.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/agent.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/backtrace.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/battery.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bearer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-adapter.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-adv_monitor.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-advertising.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-agent.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-backtrace.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-battery.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-bearer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-dbus-common.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-device.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-eir.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-error.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-gatt-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-gatt-database.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-log.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-plugin.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-profile.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-rfkill.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-sdp-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-sdp-xml.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-sdpd-database.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-sdpd-request.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-sdpd-server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-sdpd-service.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-service.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-set.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-storage.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-textfile.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-uuid-helper.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/dbus-common.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/device.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/eir.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/error.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/gatt-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/gatt-database.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/log.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/oui.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/profile.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/rfkill.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdp-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdp-xml.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdpd-database.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdpd-request.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdpd-server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdpd-service.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/service.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/set.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/storage.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/textfile.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/uuid-helper.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/btp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-ad.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-asha.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-att.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-bap-debug.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-bap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-bass.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-battery.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-btsnoop.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-ccp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-crypto.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-csip.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-ecc.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-gap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-gatt-client.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-gatt-db.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-gatt-helpers.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-gatt-server.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-gmap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-hci-crypto.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-hci.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-hfp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-io-ell.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-log.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-mainloop-ell.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-mcp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-mgmt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-micp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-pcap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-queue.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-rap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-ringbuf.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-shell.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-timeout-ell.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-tmap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-uhid.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-uinput.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-util.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-vcp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-ad.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-asha.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-att.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-bap-debug.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-bap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-bass.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-battery.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-btsnoop.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-ccp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-crypto.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-csip.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-ecc.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-gap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-gatt-client.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-gatt-db.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-gatt-helpers.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-gatt-server.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-gmap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-hci-crypto.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-hci.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-hfp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-io-glib.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-log.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-mainloop-glib.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-mainloop-notify.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-mcp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-mgmt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-micp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-pcap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-queue.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-rap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-ringbuf.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-shell.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-tester.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-timeout-glib.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-tmap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-uhid.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-uinput.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-util.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-vcp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-ad.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-asha.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-att.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-bap-debug.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-bap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-bass.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-battery.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-btsnoop.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-ccp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-crypto.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-csip.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-ecc.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-gap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-client.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-db.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-helpers.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-server.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-gmap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-hci-crypto.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-hci.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-hfp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-io-mainloop.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-log.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-mainloop-notify.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-mainloop.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-mcp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-mgmt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-micp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-pcap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-queue.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-rap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-ringbuf.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-shell.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-timeout-mainloop.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-tmap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-uhid.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-uinput.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-util.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-vcp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/3dsp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/6lowpan-tester.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/advtest.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/avinfo.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/avtest.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/bcmfw.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/bdaddr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/bluemoon.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/bluetooth-player.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/bnep-tester.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/bneptest.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btattach.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btconfig.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btgatt-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btgatt-server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btinfo.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btiotest.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btmgmt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btmon-logger.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btproxy.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btsnoop.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/check-selftest.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/ciptool.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/cltest.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/create-image.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/eddystone.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/gap-tester.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/gatt-service.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hci-tester.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_ath3k.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_bcm43xx.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_intel.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_qualcomm.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_st.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_ti.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_tialt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciconfig.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hcieventmask.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hcisecfilter.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hcitool.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hex2hcd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hid2hci.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hwdb.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/ibeacon.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/ioctl-tester.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/iso-tester.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/isotest.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/l2cap-tester.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/l2ping.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/l2test.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/mesh-cfgclient.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/mesh-cfgtest.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/mesh-tester.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/meshctl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/mgmt-tester.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/mpris-proxy.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/nokfw.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/obex-client-tool.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/obex-server-tool.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/obexctl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/oobtest.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/rctest.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/rfcomm-tester.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/rfcomm.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/rtlfw.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/sco-tester.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/scotest.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/sdptool.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/seq2bseq.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/smp-tester.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/test_runner-test-runner.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/userchan-tester.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh-gatt/$(DEPDIR)/config-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh-gatt/$(DEPDIR)/config-server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh-gatt/$(DEPDIR)/crypto.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh-gatt/$(DEPDIR)/gatt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh-gatt/$(DEPDIR)/net.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh-gatt/$(DEPDIR)/node.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh-gatt/$(DEPDIR)/onoff-model.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh-gatt/$(DEPDIR)/prov-db.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh-gatt/$(DEPDIR)/prov.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh-gatt/$(DEPDIR)/util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/agent.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/cfgcli.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/keys.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/mesh-db.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/remote.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/avctp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/avdtp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/avrcp-lib.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/btd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-avctp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-avdtp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-avrcp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-bap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-bass.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-battery.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-crc.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-crypto.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-ecc.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-eir.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gatt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gattrib.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gdbus-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gmap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gobex-apparam.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gobex-header.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gobex-packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gobex-transfer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gobex.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-hfp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-hog.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-lib.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-mcp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-mgmt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-micp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-profile.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-queue.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-rap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-ringbuf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-sdp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-tester.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-textfile.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-tmap.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-uhid.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-uuid.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-vcp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_midi-test-midi.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/util.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @: >>$@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ @am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ @am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ @am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ @am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ @am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ @am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< src/shared/libshared_ell_la-queue.lo: src/shared/queue.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-queue.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-queue.Tpo -c -o src/shared/libshared_ell_la-queue.lo `test -f 'src/shared/queue.c' || echo '$(srcdir)/'`src/shared/queue.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-queue.Tpo src/shared/$(DEPDIR)/libshared_ell_la-queue.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/queue.c' object='src/shared/libshared_ell_la-queue.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-queue.lo `test -f 'src/shared/queue.c' || echo '$(srcdir)/'`src/shared/queue.c src/shared/libshared_ell_la-util.lo: src/shared/util.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-util.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-util.Tpo -c -o src/shared/libshared_ell_la-util.lo `test -f 'src/shared/util.c' || echo '$(srcdir)/'`src/shared/util.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-util.Tpo src/shared/$(DEPDIR)/libshared_ell_la-util.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/util.c' object='src/shared/libshared_ell_la-util.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-util.lo `test -f 'src/shared/util.c' || echo '$(srcdir)/'`src/shared/util.c src/shared/libshared_ell_la-mgmt.lo: src/shared/mgmt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-mgmt.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-mgmt.Tpo -c -o src/shared/libshared_ell_la-mgmt.lo `test -f 'src/shared/mgmt.c' || echo '$(srcdir)/'`src/shared/mgmt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-mgmt.Tpo src/shared/$(DEPDIR)/libshared_ell_la-mgmt.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/mgmt.c' object='src/shared/libshared_ell_la-mgmt.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-mgmt.lo `test -f 'src/shared/mgmt.c' || echo '$(srcdir)/'`src/shared/mgmt.c src/shared/libshared_ell_la-crypto.lo: src/shared/crypto.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-crypto.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-crypto.Tpo -c -o src/shared/libshared_ell_la-crypto.lo `test -f 'src/shared/crypto.c' || echo '$(srcdir)/'`src/shared/crypto.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-crypto.Tpo src/shared/$(DEPDIR)/libshared_ell_la-crypto.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/crypto.c' object='src/shared/libshared_ell_la-crypto.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-crypto.lo `test -f 'src/shared/crypto.c' || echo '$(srcdir)/'`src/shared/crypto.c src/shared/libshared_ell_la-ecc.lo: src/shared/ecc.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-ecc.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-ecc.Tpo -c -o src/shared/libshared_ell_la-ecc.lo `test -f 'src/shared/ecc.c' || echo '$(srcdir)/'`src/shared/ecc.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-ecc.Tpo src/shared/$(DEPDIR)/libshared_ell_la-ecc.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/ecc.c' object='src/shared/libshared_ell_la-ecc.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-ecc.lo `test -f 'src/shared/ecc.c' || echo '$(srcdir)/'`src/shared/ecc.c src/shared/libshared_ell_la-ringbuf.lo: src/shared/ringbuf.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-ringbuf.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-ringbuf.Tpo -c -o src/shared/libshared_ell_la-ringbuf.lo `test -f 'src/shared/ringbuf.c' || echo '$(srcdir)/'`src/shared/ringbuf.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-ringbuf.Tpo src/shared/$(DEPDIR)/libshared_ell_la-ringbuf.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/ringbuf.c' object='src/shared/libshared_ell_la-ringbuf.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-ringbuf.lo `test -f 'src/shared/ringbuf.c' || echo '$(srcdir)/'`src/shared/ringbuf.c src/shared/libshared_ell_la-hci.lo: src/shared/hci.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-hci.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-hci.Tpo -c -o src/shared/libshared_ell_la-hci.lo `test -f 'src/shared/hci.c' || echo '$(srcdir)/'`src/shared/hci.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-hci.Tpo src/shared/$(DEPDIR)/libshared_ell_la-hci.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/hci.c' object='src/shared/libshared_ell_la-hci.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-hci.lo `test -f 'src/shared/hci.c' || echo '$(srcdir)/'`src/shared/hci.c src/shared/libshared_ell_la-hci-crypto.lo: src/shared/hci-crypto.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-hci-crypto.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-hci-crypto.Tpo -c -o src/shared/libshared_ell_la-hci-crypto.lo `test -f 'src/shared/hci-crypto.c' || echo '$(srcdir)/'`src/shared/hci-crypto.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-hci-crypto.Tpo src/shared/$(DEPDIR)/libshared_ell_la-hci-crypto.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/hci-crypto.c' object='src/shared/libshared_ell_la-hci-crypto.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-hci-crypto.lo `test -f 'src/shared/hci-crypto.c' || echo '$(srcdir)/'`src/shared/hci-crypto.c src/shared/libshared_ell_la-hfp.lo: src/shared/hfp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-hfp.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-hfp.Tpo -c -o src/shared/libshared_ell_la-hfp.lo `test -f 'src/shared/hfp.c' || echo '$(srcdir)/'`src/shared/hfp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-hfp.Tpo src/shared/$(DEPDIR)/libshared_ell_la-hfp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/hfp.c' object='src/shared/libshared_ell_la-hfp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-hfp.lo `test -f 'src/shared/hfp.c' || echo '$(srcdir)/'`src/shared/hfp.c src/shared/libshared_ell_la-uhid.lo: src/shared/uhid.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-uhid.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-uhid.Tpo -c -o src/shared/libshared_ell_la-uhid.lo `test -f 'src/shared/uhid.c' || echo '$(srcdir)/'`src/shared/uhid.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-uhid.Tpo src/shared/$(DEPDIR)/libshared_ell_la-uhid.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/uhid.c' object='src/shared/libshared_ell_la-uhid.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-uhid.lo `test -f 'src/shared/uhid.c' || echo '$(srcdir)/'`src/shared/uhid.c src/shared/libshared_ell_la-pcap.lo: src/shared/pcap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-pcap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-pcap.Tpo -c -o src/shared/libshared_ell_la-pcap.lo `test -f 'src/shared/pcap.c' || echo '$(srcdir)/'`src/shared/pcap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-pcap.Tpo src/shared/$(DEPDIR)/libshared_ell_la-pcap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/pcap.c' object='src/shared/libshared_ell_la-pcap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-pcap.lo `test -f 'src/shared/pcap.c' || echo '$(srcdir)/'`src/shared/pcap.c src/shared/libshared_ell_la-btsnoop.lo: src/shared/btsnoop.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-btsnoop.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-btsnoop.Tpo -c -o src/shared/libshared_ell_la-btsnoop.lo `test -f 'src/shared/btsnoop.c' || echo '$(srcdir)/'`src/shared/btsnoop.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-btsnoop.Tpo src/shared/$(DEPDIR)/libshared_ell_la-btsnoop.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/btsnoop.c' object='src/shared/libshared_ell_la-btsnoop.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-btsnoop.lo `test -f 'src/shared/btsnoop.c' || echo '$(srcdir)/'`src/shared/btsnoop.c src/shared/libshared_ell_la-ad.lo: src/shared/ad.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-ad.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-ad.Tpo -c -o src/shared/libshared_ell_la-ad.lo `test -f 'src/shared/ad.c' || echo '$(srcdir)/'`src/shared/ad.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-ad.Tpo src/shared/$(DEPDIR)/libshared_ell_la-ad.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/ad.c' object='src/shared/libshared_ell_la-ad.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-ad.lo `test -f 'src/shared/ad.c' || echo '$(srcdir)/'`src/shared/ad.c src/shared/libshared_ell_la-att.lo: src/shared/att.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-att.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-att.Tpo -c -o src/shared/libshared_ell_la-att.lo `test -f 'src/shared/att.c' || echo '$(srcdir)/'`src/shared/att.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-att.Tpo src/shared/$(DEPDIR)/libshared_ell_la-att.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/att.c' object='src/shared/libshared_ell_la-att.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-att.lo `test -f 'src/shared/att.c' || echo '$(srcdir)/'`src/shared/att.c src/shared/libshared_ell_la-gatt-helpers.lo: src/shared/gatt-helpers.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-gatt-helpers.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-gatt-helpers.Tpo -c -o src/shared/libshared_ell_la-gatt-helpers.lo `test -f 'src/shared/gatt-helpers.c' || echo '$(srcdir)/'`src/shared/gatt-helpers.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-gatt-helpers.Tpo src/shared/$(DEPDIR)/libshared_ell_la-gatt-helpers.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gatt-helpers.c' object='src/shared/libshared_ell_la-gatt-helpers.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-gatt-helpers.lo `test -f 'src/shared/gatt-helpers.c' || echo '$(srcdir)/'`src/shared/gatt-helpers.c src/shared/libshared_ell_la-gatt-client.lo: src/shared/gatt-client.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-gatt-client.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-gatt-client.Tpo -c -o src/shared/libshared_ell_la-gatt-client.lo `test -f 'src/shared/gatt-client.c' || echo '$(srcdir)/'`src/shared/gatt-client.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-gatt-client.Tpo src/shared/$(DEPDIR)/libshared_ell_la-gatt-client.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gatt-client.c' object='src/shared/libshared_ell_la-gatt-client.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-gatt-client.lo `test -f 'src/shared/gatt-client.c' || echo '$(srcdir)/'`src/shared/gatt-client.c src/shared/libshared_ell_la-gatt-server.lo: src/shared/gatt-server.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-gatt-server.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-gatt-server.Tpo -c -o src/shared/libshared_ell_la-gatt-server.lo `test -f 'src/shared/gatt-server.c' || echo '$(srcdir)/'`src/shared/gatt-server.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-gatt-server.Tpo src/shared/$(DEPDIR)/libshared_ell_la-gatt-server.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gatt-server.c' object='src/shared/libshared_ell_la-gatt-server.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-gatt-server.lo `test -f 'src/shared/gatt-server.c' || echo '$(srcdir)/'`src/shared/gatt-server.c src/shared/libshared_ell_la-gatt-db.lo: src/shared/gatt-db.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-gatt-db.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-gatt-db.Tpo -c -o src/shared/libshared_ell_la-gatt-db.lo `test -f 'src/shared/gatt-db.c' || echo '$(srcdir)/'`src/shared/gatt-db.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-gatt-db.Tpo src/shared/$(DEPDIR)/libshared_ell_la-gatt-db.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gatt-db.c' object='src/shared/libshared_ell_la-gatt-db.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-gatt-db.lo `test -f 'src/shared/gatt-db.c' || echo '$(srcdir)/'`src/shared/gatt-db.c src/shared/libshared_ell_la-gap.lo: src/shared/gap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-gap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-gap.Tpo -c -o src/shared/libshared_ell_la-gap.lo `test -f 'src/shared/gap.c' || echo '$(srcdir)/'`src/shared/gap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-gap.Tpo src/shared/$(DEPDIR)/libshared_ell_la-gap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gap.c' object='src/shared/libshared_ell_la-gap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-gap.lo `test -f 'src/shared/gap.c' || echo '$(srcdir)/'`src/shared/gap.c src/shared/libshared_ell_la-log.lo: src/shared/log.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-log.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-log.Tpo -c -o src/shared/libshared_ell_la-log.lo `test -f 'src/shared/log.c' || echo '$(srcdir)/'`src/shared/log.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-log.Tpo src/shared/$(DEPDIR)/libshared_ell_la-log.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/log.c' object='src/shared/libshared_ell_la-log.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-log.lo `test -f 'src/shared/log.c' || echo '$(srcdir)/'`src/shared/log.c src/shared/libshared_ell_la-bap.lo: src/shared/bap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-bap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-bap.Tpo -c -o src/shared/libshared_ell_la-bap.lo `test -f 'src/shared/bap.c' || echo '$(srcdir)/'`src/shared/bap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-bap.Tpo src/shared/$(DEPDIR)/libshared_ell_la-bap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/bap.c' object='src/shared/libshared_ell_la-bap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-bap.lo `test -f 'src/shared/bap.c' || echo '$(srcdir)/'`src/shared/bap.c src/shared/libshared_ell_la-bap-debug.lo: src/shared/bap-debug.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-bap-debug.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-bap-debug.Tpo -c -o src/shared/libshared_ell_la-bap-debug.lo `test -f 'src/shared/bap-debug.c' || echo '$(srcdir)/'`src/shared/bap-debug.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-bap-debug.Tpo src/shared/$(DEPDIR)/libshared_ell_la-bap-debug.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/bap-debug.c' object='src/shared/libshared_ell_la-bap-debug.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-bap-debug.lo `test -f 'src/shared/bap-debug.c' || echo '$(srcdir)/'`src/shared/bap-debug.c src/shared/libshared_ell_la-mcp.lo: src/shared/mcp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-mcp.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-mcp.Tpo -c -o src/shared/libshared_ell_la-mcp.lo `test -f 'src/shared/mcp.c' || echo '$(srcdir)/'`src/shared/mcp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-mcp.Tpo src/shared/$(DEPDIR)/libshared_ell_la-mcp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/mcp.c' object='src/shared/libshared_ell_la-mcp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-mcp.lo `test -f 'src/shared/mcp.c' || echo '$(srcdir)/'`src/shared/mcp.c src/shared/libshared_ell_la-vcp.lo: src/shared/vcp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-vcp.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-vcp.Tpo -c -o src/shared/libshared_ell_la-vcp.lo `test -f 'src/shared/vcp.c' || echo '$(srcdir)/'`src/shared/vcp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-vcp.Tpo src/shared/$(DEPDIR)/libshared_ell_la-vcp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/vcp.c' object='src/shared/libshared_ell_la-vcp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-vcp.lo `test -f 'src/shared/vcp.c' || echo '$(srcdir)/'`src/shared/vcp.c src/shared/libshared_ell_la-micp.lo: src/shared/micp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-micp.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-micp.Tpo -c -o src/shared/libshared_ell_la-micp.lo `test -f 'src/shared/micp.c' || echo '$(srcdir)/'`src/shared/micp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-micp.Tpo src/shared/$(DEPDIR)/libshared_ell_la-micp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/micp.c' object='src/shared/libshared_ell_la-micp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-micp.lo `test -f 'src/shared/micp.c' || echo '$(srcdir)/'`src/shared/micp.c src/shared/libshared_ell_la-csip.lo: src/shared/csip.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-csip.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-csip.Tpo -c -o src/shared/libshared_ell_la-csip.lo `test -f 'src/shared/csip.c' || echo '$(srcdir)/'`src/shared/csip.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-csip.Tpo src/shared/$(DEPDIR)/libshared_ell_la-csip.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/csip.c' object='src/shared/libshared_ell_la-csip.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-csip.lo `test -f 'src/shared/csip.c' || echo '$(srcdir)/'`src/shared/csip.c src/shared/libshared_ell_la-bass.lo: src/shared/bass.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-bass.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-bass.Tpo -c -o src/shared/libshared_ell_la-bass.lo `test -f 'src/shared/bass.c' || echo '$(srcdir)/'`src/shared/bass.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-bass.Tpo src/shared/$(DEPDIR)/libshared_ell_la-bass.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/bass.c' object='src/shared/libshared_ell_la-bass.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-bass.lo `test -f 'src/shared/bass.c' || echo '$(srcdir)/'`src/shared/bass.c src/shared/libshared_ell_la-ccp.lo: src/shared/ccp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-ccp.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-ccp.Tpo -c -o src/shared/libshared_ell_la-ccp.lo `test -f 'src/shared/ccp.c' || echo '$(srcdir)/'`src/shared/ccp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-ccp.Tpo src/shared/$(DEPDIR)/libshared_ell_la-ccp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/ccp.c' object='src/shared/libshared_ell_la-ccp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-ccp.lo `test -f 'src/shared/ccp.c' || echo '$(srcdir)/'`src/shared/ccp.c src/shared/libshared_ell_la-tmap.lo: src/shared/tmap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-tmap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-tmap.Tpo -c -o src/shared/libshared_ell_la-tmap.lo `test -f 'src/shared/tmap.c' || echo '$(srcdir)/'`src/shared/tmap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-tmap.Tpo src/shared/$(DEPDIR)/libshared_ell_la-tmap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/tmap.c' object='src/shared/libshared_ell_la-tmap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-tmap.lo `test -f 'src/shared/tmap.c' || echo '$(srcdir)/'`src/shared/tmap.c src/shared/libshared_ell_la-gmap.lo: src/shared/gmap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-gmap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-gmap.Tpo -c -o src/shared/libshared_ell_la-gmap.lo `test -f 'src/shared/gmap.c' || echo '$(srcdir)/'`src/shared/gmap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-gmap.Tpo src/shared/$(DEPDIR)/libshared_ell_la-gmap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gmap.c' object='src/shared/libshared_ell_la-gmap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-gmap.lo `test -f 'src/shared/gmap.c' || echo '$(srcdir)/'`src/shared/gmap.c src/shared/libshared_ell_la-asha.lo: src/shared/asha.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-asha.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-asha.Tpo -c -o src/shared/libshared_ell_la-asha.lo `test -f 'src/shared/asha.c' || echo '$(srcdir)/'`src/shared/asha.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-asha.Tpo src/shared/$(DEPDIR)/libshared_ell_la-asha.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/asha.c' object='src/shared/libshared_ell_la-asha.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-asha.lo `test -f 'src/shared/asha.c' || echo '$(srcdir)/'`src/shared/asha.c src/shared/libshared_ell_la-battery.lo: src/shared/battery.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-battery.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-battery.Tpo -c -o src/shared/libshared_ell_la-battery.lo `test -f 'src/shared/battery.c' || echo '$(srcdir)/'`src/shared/battery.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-battery.Tpo src/shared/$(DEPDIR)/libshared_ell_la-battery.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/battery.c' object='src/shared/libshared_ell_la-battery.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-battery.lo `test -f 'src/shared/battery.c' || echo '$(srcdir)/'`src/shared/battery.c src/shared/libshared_ell_la-uinput.lo: src/shared/uinput.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-uinput.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-uinput.Tpo -c -o src/shared/libshared_ell_la-uinput.lo `test -f 'src/shared/uinput.c' || echo '$(srcdir)/'`src/shared/uinput.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-uinput.Tpo src/shared/$(DEPDIR)/libshared_ell_la-uinput.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/uinput.c' object='src/shared/libshared_ell_la-uinput.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-uinput.lo `test -f 'src/shared/uinput.c' || echo '$(srcdir)/'`src/shared/uinput.c src/shared/libshared_ell_la-rap.lo: src/shared/rap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-rap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-rap.Tpo -c -o src/shared/libshared_ell_la-rap.lo `test -f 'src/shared/rap.c' || echo '$(srcdir)/'`src/shared/rap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-rap.Tpo src/shared/$(DEPDIR)/libshared_ell_la-rap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/rap.c' object='src/shared/libshared_ell_la-rap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-rap.lo `test -f 'src/shared/rap.c' || echo '$(srcdir)/'`src/shared/rap.c src/shared/libshared_ell_la-shell.lo: src/shared/shell.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-shell.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-shell.Tpo -c -o src/shared/libshared_ell_la-shell.lo `test -f 'src/shared/shell.c' || echo '$(srcdir)/'`src/shared/shell.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-shell.Tpo src/shared/$(DEPDIR)/libshared_ell_la-shell.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/shell.c' object='src/shared/libshared_ell_la-shell.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-shell.lo `test -f 'src/shared/shell.c' || echo '$(srcdir)/'`src/shared/shell.c src/shared/libshared_ell_la-io-ell.lo: src/shared/io-ell.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-io-ell.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-io-ell.Tpo -c -o src/shared/libshared_ell_la-io-ell.lo `test -f 'src/shared/io-ell.c' || echo '$(srcdir)/'`src/shared/io-ell.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-io-ell.Tpo src/shared/$(DEPDIR)/libshared_ell_la-io-ell.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/io-ell.c' object='src/shared/libshared_ell_la-io-ell.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-io-ell.lo `test -f 'src/shared/io-ell.c' || echo '$(srcdir)/'`src/shared/io-ell.c src/shared/libshared_ell_la-timeout-ell.lo: src/shared/timeout-ell.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-timeout-ell.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-timeout-ell.Tpo -c -o src/shared/libshared_ell_la-timeout-ell.lo `test -f 'src/shared/timeout-ell.c' || echo '$(srcdir)/'`src/shared/timeout-ell.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-timeout-ell.Tpo src/shared/$(DEPDIR)/libshared_ell_la-timeout-ell.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/timeout-ell.c' object='src/shared/libshared_ell_la-timeout-ell.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-timeout-ell.lo `test -f 'src/shared/timeout-ell.c' || echo '$(srcdir)/'`src/shared/timeout-ell.c src/shared/libshared_ell_la-mainloop-ell.lo: src/shared/mainloop-ell.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-mainloop-ell.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-mainloop-ell.Tpo -c -o src/shared/libshared_ell_la-mainloop-ell.lo `test -f 'src/shared/mainloop-ell.c' || echo '$(srcdir)/'`src/shared/mainloop-ell.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-mainloop-ell.Tpo src/shared/$(DEPDIR)/libshared_ell_la-mainloop-ell.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/mainloop-ell.c' object='src/shared/libshared_ell_la-mainloop-ell.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-mainloop-ell.lo `test -f 'src/shared/mainloop-ell.c' || echo '$(srcdir)/'`src/shared/mainloop-ell.c src/shared/libshared_glib_la-queue.lo: src/shared/queue.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-queue.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-queue.Tpo -c -o src/shared/libshared_glib_la-queue.lo `test -f 'src/shared/queue.c' || echo '$(srcdir)/'`src/shared/queue.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-queue.Tpo src/shared/$(DEPDIR)/libshared_glib_la-queue.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/queue.c' object='src/shared/libshared_glib_la-queue.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-queue.lo `test -f 'src/shared/queue.c' || echo '$(srcdir)/'`src/shared/queue.c src/shared/libshared_glib_la-util.lo: src/shared/util.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-util.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-util.Tpo -c -o src/shared/libshared_glib_la-util.lo `test -f 'src/shared/util.c' || echo '$(srcdir)/'`src/shared/util.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-util.Tpo src/shared/$(DEPDIR)/libshared_glib_la-util.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/util.c' object='src/shared/libshared_glib_la-util.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-util.lo `test -f 'src/shared/util.c' || echo '$(srcdir)/'`src/shared/util.c src/shared/libshared_glib_la-mgmt.lo: src/shared/mgmt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-mgmt.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-mgmt.Tpo -c -o src/shared/libshared_glib_la-mgmt.lo `test -f 'src/shared/mgmt.c' || echo '$(srcdir)/'`src/shared/mgmt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-mgmt.Tpo src/shared/$(DEPDIR)/libshared_glib_la-mgmt.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/mgmt.c' object='src/shared/libshared_glib_la-mgmt.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-mgmt.lo `test -f 'src/shared/mgmt.c' || echo '$(srcdir)/'`src/shared/mgmt.c src/shared/libshared_glib_la-crypto.lo: src/shared/crypto.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-crypto.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-crypto.Tpo -c -o src/shared/libshared_glib_la-crypto.lo `test -f 'src/shared/crypto.c' || echo '$(srcdir)/'`src/shared/crypto.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-crypto.Tpo src/shared/$(DEPDIR)/libshared_glib_la-crypto.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/crypto.c' object='src/shared/libshared_glib_la-crypto.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-crypto.lo `test -f 'src/shared/crypto.c' || echo '$(srcdir)/'`src/shared/crypto.c src/shared/libshared_glib_la-ecc.lo: src/shared/ecc.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-ecc.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-ecc.Tpo -c -o src/shared/libshared_glib_la-ecc.lo `test -f 'src/shared/ecc.c' || echo '$(srcdir)/'`src/shared/ecc.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-ecc.Tpo src/shared/$(DEPDIR)/libshared_glib_la-ecc.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/ecc.c' object='src/shared/libshared_glib_la-ecc.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-ecc.lo `test -f 'src/shared/ecc.c' || echo '$(srcdir)/'`src/shared/ecc.c src/shared/libshared_glib_la-ringbuf.lo: src/shared/ringbuf.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-ringbuf.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-ringbuf.Tpo -c -o src/shared/libshared_glib_la-ringbuf.lo `test -f 'src/shared/ringbuf.c' || echo '$(srcdir)/'`src/shared/ringbuf.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-ringbuf.Tpo src/shared/$(DEPDIR)/libshared_glib_la-ringbuf.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/ringbuf.c' object='src/shared/libshared_glib_la-ringbuf.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-ringbuf.lo `test -f 'src/shared/ringbuf.c' || echo '$(srcdir)/'`src/shared/ringbuf.c src/shared/libshared_glib_la-hci.lo: src/shared/hci.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-hci.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-hci.Tpo -c -o src/shared/libshared_glib_la-hci.lo `test -f 'src/shared/hci.c' || echo '$(srcdir)/'`src/shared/hci.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-hci.Tpo src/shared/$(DEPDIR)/libshared_glib_la-hci.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/hci.c' object='src/shared/libshared_glib_la-hci.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-hci.lo `test -f 'src/shared/hci.c' || echo '$(srcdir)/'`src/shared/hci.c src/shared/libshared_glib_la-hci-crypto.lo: src/shared/hci-crypto.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-hci-crypto.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-hci-crypto.Tpo -c -o src/shared/libshared_glib_la-hci-crypto.lo `test -f 'src/shared/hci-crypto.c' || echo '$(srcdir)/'`src/shared/hci-crypto.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-hci-crypto.Tpo src/shared/$(DEPDIR)/libshared_glib_la-hci-crypto.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/hci-crypto.c' object='src/shared/libshared_glib_la-hci-crypto.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-hci-crypto.lo `test -f 'src/shared/hci-crypto.c' || echo '$(srcdir)/'`src/shared/hci-crypto.c src/shared/libshared_glib_la-hfp.lo: src/shared/hfp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-hfp.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-hfp.Tpo -c -o src/shared/libshared_glib_la-hfp.lo `test -f 'src/shared/hfp.c' || echo '$(srcdir)/'`src/shared/hfp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-hfp.Tpo src/shared/$(DEPDIR)/libshared_glib_la-hfp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/hfp.c' object='src/shared/libshared_glib_la-hfp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-hfp.lo `test -f 'src/shared/hfp.c' || echo '$(srcdir)/'`src/shared/hfp.c src/shared/libshared_glib_la-uhid.lo: src/shared/uhid.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-uhid.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-uhid.Tpo -c -o src/shared/libshared_glib_la-uhid.lo `test -f 'src/shared/uhid.c' || echo '$(srcdir)/'`src/shared/uhid.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-uhid.Tpo src/shared/$(DEPDIR)/libshared_glib_la-uhid.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/uhid.c' object='src/shared/libshared_glib_la-uhid.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-uhid.lo `test -f 'src/shared/uhid.c' || echo '$(srcdir)/'`src/shared/uhid.c src/shared/libshared_glib_la-pcap.lo: src/shared/pcap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-pcap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-pcap.Tpo -c -o src/shared/libshared_glib_la-pcap.lo `test -f 'src/shared/pcap.c' || echo '$(srcdir)/'`src/shared/pcap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-pcap.Tpo src/shared/$(DEPDIR)/libshared_glib_la-pcap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/pcap.c' object='src/shared/libshared_glib_la-pcap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-pcap.lo `test -f 'src/shared/pcap.c' || echo '$(srcdir)/'`src/shared/pcap.c src/shared/libshared_glib_la-btsnoop.lo: src/shared/btsnoop.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-btsnoop.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-btsnoop.Tpo -c -o src/shared/libshared_glib_la-btsnoop.lo `test -f 'src/shared/btsnoop.c' || echo '$(srcdir)/'`src/shared/btsnoop.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-btsnoop.Tpo src/shared/$(DEPDIR)/libshared_glib_la-btsnoop.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/btsnoop.c' object='src/shared/libshared_glib_la-btsnoop.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-btsnoop.lo `test -f 'src/shared/btsnoop.c' || echo '$(srcdir)/'`src/shared/btsnoop.c src/shared/libshared_glib_la-ad.lo: src/shared/ad.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-ad.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-ad.Tpo -c -o src/shared/libshared_glib_la-ad.lo `test -f 'src/shared/ad.c' || echo '$(srcdir)/'`src/shared/ad.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-ad.Tpo src/shared/$(DEPDIR)/libshared_glib_la-ad.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/ad.c' object='src/shared/libshared_glib_la-ad.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-ad.lo `test -f 'src/shared/ad.c' || echo '$(srcdir)/'`src/shared/ad.c src/shared/libshared_glib_la-att.lo: src/shared/att.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-att.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-att.Tpo -c -o src/shared/libshared_glib_la-att.lo `test -f 'src/shared/att.c' || echo '$(srcdir)/'`src/shared/att.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-att.Tpo src/shared/$(DEPDIR)/libshared_glib_la-att.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/att.c' object='src/shared/libshared_glib_la-att.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-att.lo `test -f 'src/shared/att.c' || echo '$(srcdir)/'`src/shared/att.c src/shared/libshared_glib_la-gatt-helpers.lo: src/shared/gatt-helpers.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-gatt-helpers.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-gatt-helpers.Tpo -c -o src/shared/libshared_glib_la-gatt-helpers.lo `test -f 'src/shared/gatt-helpers.c' || echo '$(srcdir)/'`src/shared/gatt-helpers.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-gatt-helpers.Tpo src/shared/$(DEPDIR)/libshared_glib_la-gatt-helpers.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gatt-helpers.c' object='src/shared/libshared_glib_la-gatt-helpers.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-gatt-helpers.lo `test -f 'src/shared/gatt-helpers.c' || echo '$(srcdir)/'`src/shared/gatt-helpers.c src/shared/libshared_glib_la-gatt-client.lo: src/shared/gatt-client.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-gatt-client.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-gatt-client.Tpo -c -o src/shared/libshared_glib_la-gatt-client.lo `test -f 'src/shared/gatt-client.c' || echo '$(srcdir)/'`src/shared/gatt-client.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-gatt-client.Tpo src/shared/$(DEPDIR)/libshared_glib_la-gatt-client.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gatt-client.c' object='src/shared/libshared_glib_la-gatt-client.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-gatt-client.lo `test -f 'src/shared/gatt-client.c' || echo '$(srcdir)/'`src/shared/gatt-client.c src/shared/libshared_glib_la-gatt-server.lo: src/shared/gatt-server.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-gatt-server.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-gatt-server.Tpo -c -o src/shared/libshared_glib_la-gatt-server.lo `test -f 'src/shared/gatt-server.c' || echo '$(srcdir)/'`src/shared/gatt-server.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-gatt-server.Tpo src/shared/$(DEPDIR)/libshared_glib_la-gatt-server.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gatt-server.c' object='src/shared/libshared_glib_la-gatt-server.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-gatt-server.lo `test -f 'src/shared/gatt-server.c' || echo '$(srcdir)/'`src/shared/gatt-server.c src/shared/libshared_glib_la-gatt-db.lo: src/shared/gatt-db.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-gatt-db.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-gatt-db.Tpo -c -o src/shared/libshared_glib_la-gatt-db.lo `test -f 'src/shared/gatt-db.c' || echo '$(srcdir)/'`src/shared/gatt-db.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-gatt-db.Tpo src/shared/$(DEPDIR)/libshared_glib_la-gatt-db.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gatt-db.c' object='src/shared/libshared_glib_la-gatt-db.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-gatt-db.lo `test -f 'src/shared/gatt-db.c' || echo '$(srcdir)/'`src/shared/gatt-db.c src/shared/libshared_glib_la-gap.lo: src/shared/gap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-gap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-gap.Tpo -c -o src/shared/libshared_glib_la-gap.lo `test -f 'src/shared/gap.c' || echo '$(srcdir)/'`src/shared/gap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-gap.Tpo src/shared/$(DEPDIR)/libshared_glib_la-gap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gap.c' object='src/shared/libshared_glib_la-gap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-gap.lo `test -f 'src/shared/gap.c' || echo '$(srcdir)/'`src/shared/gap.c src/shared/libshared_glib_la-log.lo: src/shared/log.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-log.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-log.Tpo -c -o src/shared/libshared_glib_la-log.lo `test -f 'src/shared/log.c' || echo '$(srcdir)/'`src/shared/log.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-log.Tpo src/shared/$(DEPDIR)/libshared_glib_la-log.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/log.c' object='src/shared/libshared_glib_la-log.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-log.lo `test -f 'src/shared/log.c' || echo '$(srcdir)/'`src/shared/log.c src/shared/libshared_glib_la-bap.lo: src/shared/bap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-bap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-bap.Tpo -c -o src/shared/libshared_glib_la-bap.lo `test -f 'src/shared/bap.c' || echo '$(srcdir)/'`src/shared/bap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-bap.Tpo src/shared/$(DEPDIR)/libshared_glib_la-bap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/bap.c' object='src/shared/libshared_glib_la-bap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-bap.lo `test -f 'src/shared/bap.c' || echo '$(srcdir)/'`src/shared/bap.c src/shared/libshared_glib_la-bap-debug.lo: src/shared/bap-debug.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-bap-debug.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-bap-debug.Tpo -c -o src/shared/libshared_glib_la-bap-debug.lo `test -f 'src/shared/bap-debug.c' || echo '$(srcdir)/'`src/shared/bap-debug.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-bap-debug.Tpo src/shared/$(DEPDIR)/libshared_glib_la-bap-debug.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/bap-debug.c' object='src/shared/libshared_glib_la-bap-debug.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-bap-debug.lo `test -f 'src/shared/bap-debug.c' || echo '$(srcdir)/'`src/shared/bap-debug.c src/shared/libshared_glib_la-mcp.lo: src/shared/mcp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-mcp.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-mcp.Tpo -c -o src/shared/libshared_glib_la-mcp.lo `test -f 'src/shared/mcp.c' || echo '$(srcdir)/'`src/shared/mcp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-mcp.Tpo src/shared/$(DEPDIR)/libshared_glib_la-mcp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/mcp.c' object='src/shared/libshared_glib_la-mcp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-mcp.lo `test -f 'src/shared/mcp.c' || echo '$(srcdir)/'`src/shared/mcp.c src/shared/libshared_glib_la-vcp.lo: src/shared/vcp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-vcp.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-vcp.Tpo -c -o src/shared/libshared_glib_la-vcp.lo `test -f 'src/shared/vcp.c' || echo '$(srcdir)/'`src/shared/vcp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-vcp.Tpo src/shared/$(DEPDIR)/libshared_glib_la-vcp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/vcp.c' object='src/shared/libshared_glib_la-vcp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-vcp.lo `test -f 'src/shared/vcp.c' || echo '$(srcdir)/'`src/shared/vcp.c src/shared/libshared_glib_la-micp.lo: src/shared/micp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-micp.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-micp.Tpo -c -o src/shared/libshared_glib_la-micp.lo `test -f 'src/shared/micp.c' || echo '$(srcdir)/'`src/shared/micp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-micp.Tpo src/shared/$(DEPDIR)/libshared_glib_la-micp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/micp.c' object='src/shared/libshared_glib_la-micp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-micp.lo `test -f 'src/shared/micp.c' || echo '$(srcdir)/'`src/shared/micp.c src/shared/libshared_glib_la-csip.lo: src/shared/csip.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-csip.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-csip.Tpo -c -o src/shared/libshared_glib_la-csip.lo `test -f 'src/shared/csip.c' || echo '$(srcdir)/'`src/shared/csip.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-csip.Tpo src/shared/$(DEPDIR)/libshared_glib_la-csip.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/csip.c' object='src/shared/libshared_glib_la-csip.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-csip.lo `test -f 'src/shared/csip.c' || echo '$(srcdir)/'`src/shared/csip.c src/shared/libshared_glib_la-bass.lo: src/shared/bass.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-bass.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-bass.Tpo -c -o src/shared/libshared_glib_la-bass.lo `test -f 'src/shared/bass.c' || echo '$(srcdir)/'`src/shared/bass.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-bass.Tpo src/shared/$(DEPDIR)/libshared_glib_la-bass.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/bass.c' object='src/shared/libshared_glib_la-bass.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-bass.lo `test -f 'src/shared/bass.c' || echo '$(srcdir)/'`src/shared/bass.c src/shared/libshared_glib_la-ccp.lo: src/shared/ccp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-ccp.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-ccp.Tpo -c -o src/shared/libshared_glib_la-ccp.lo `test -f 'src/shared/ccp.c' || echo '$(srcdir)/'`src/shared/ccp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-ccp.Tpo src/shared/$(DEPDIR)/libshared_glib_la-ccp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/ccp.c' object='src/shared/libshared_glib_la-ccp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-ccp.lo `test -f 'src/shared/ccp.c' || echo '$(srcdir)/'`src/shared/ccp.c src/shared/libshared_glib_la-tmap.lo: src/shared/tmap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-tmap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-tmap.Tpo -c -o src/shared/libshared_glib_la-tmap.lo `test -f 'src/shared/tmap.c' || echo '$(srcdir)/'`src/shared/tmap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-tmap.Tpo src/shared/$(DEPDIR)/libshared_glib_la-tmap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/tmap.c' object='src/shared/libshared_glib_la-tmap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-tmap.lo `test -f 'src/shared/tmap.c' || echo '$(srcdir)/'`src/shared/tmap.c src/shared/libshared_glib_la-gmap.lo: src/shared/gmap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-gmap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-gmap.Tpo -c -o src/shared/libshared_glib_la-gmap.lo `test -f 'src/shared/gmap.c' || echo '$(srcdir)/'`src/shared/gmap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-gmap.Tpo src/shared/$(DEPDIR)/libshared_glib_la-gmap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gmap.c' object='src/shared/libshared_glib_la-gmap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-gmap.lo `test -f 'src/shared/gmap.c' || echo '$(srcdir)/'`src/shared/gmap.c src/shared/libshared_glib_la-asha.lo: src/shared/asha.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-asha.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-asha.Tpo -c -o src/shared/libshared_glib_la-asha.lo `test -f 'src/shared/asha.c' || echo '$(srcdir)/'`src/shared/asha.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-asha.Tpo src/shared/$(DEPDIR)/libshared_glib_la-asha.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/asha.c' object='src/shared/libshared_glib_la-asha.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-asha.lo `test -f 'src/shared/asha.c' || echo '$(srcdir)/'`src/shared/asha.c src/shared/libshared_glib_la-battery.lo: src/shared/battery.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-battery.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-battery.Tpo -c -o src/shared/libshared_glib_la-battery.lo `test -f 'src/shared/battery.c' || echo '$(srcdir)/'`src/shared/battery.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-battery.Tpo src/shared/$(DEPDIR)/libshared_glib_la-battery.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/battery.c' object='src/shared/libshared_glib_la-battery.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-battery.lo `test -f 'src/shared/battery.c' || echo '$(srcdir)/'`src/shared/battery.c src/shared/libshared_glib_la-uinput.lo: src/shared/uinput.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-uinput.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-uinput.Tpo -c -o src/shared/libshared_glib_la-uinput.lo `test -f 'src/shared/uinput.c' || echo '$(srcdir)/'`src/shared/uinput.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-uinput.Tpo src/shared/$(DEPDIR)/libshared_glib_la-uinput.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/uinput.c' object='src/shared/libshared_glib_la-uinput.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-uinput.lo `test -f 'src/shared/uinput.c' || echo '$(srcdir)/'`src/shared/uinput.c src/shared/libshared_glib_la-rap.lo: src/shared/rap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-rap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-rap.Tpo -c -o src/shared/libshared_glib_la-rap.lo `test -f 'src/shared/rap.c' || echo '$(srcdir)/'`src/shared/rap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-rap.Tpo src/shared/$(DEPDIR)/libshared_glib_la-rap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/rap.c' object='src/shared/libshared_glib_la-rap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-rap.lo `test -f 'src/shared/rap.c' || echo '$(srcdir)/'`src/shared/rap.c src/shared/libshared_glib_la-shell.lo: src/shared/shell.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-shell.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-shell.Tpo -c -o src/shared/libshared_glib_la-shell.lo `test -f 'src/shared/shell.c' || echo '$(srcdir)/'`src/shared/shell.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-shell.Tpo src/shared/$(DEPDIR)/libshared_glib_la-shell.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/shell.c' object='src/shared/libshared_glib_la-shell.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-shell.lo `test -f 'src/shared/shell.c' || echo '$(srcdir)/'`src/shared/shell.c src/shared/libshared_glib_la-io-glib.lo: src/shared/io-glib.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-io-glib.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-io-glib.Tpo -c -o src/shared/libshared_glib_la-io-glib.lo `test -f 'src/shared/io-glib.c' || echo '$(srcdir)/'`src/shared/io-glib.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-io-glib.Tpo src/shared/$(DEPDIR)/libshared_glib_la-io-glib.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/io-glib.c' object='src/shared/libshared_glib_la-io-glib.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-io-glib.lo `test -f 'src/shared/io-glib.c' || echo '$(srcdir)/'`src/shared/io-glib.c src/shared/libshared_glib_la-timeout-glib.lo: src/shared/timeout-glib.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-timeout-glib.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-timeout-glib.Tpo -c -o src/shared/libshared_glib_la-timeout-glib.lo `test -f 'src/shared/timeout-glib.c' || echo '$(srcdir)/'`src/shared/timeout-glib.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-timeout-glib.Tpo src/shared/$(DEPDIR)/libshared_glib_la-timeout-glib.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/timeout-glib.c' object='src/shared/libshared_glib_la-timeout-glib.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-timeout-glib.lo `test -f 'src/shared/timeout-glib.c' || echo '$(srcdir)/'`src/shared/timeout-glib.c src/shared/libshared_glib_la-mainloop-glib.lo: src/shared/mainloop-glib.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-mainloop-glib.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-mainloop-glib.Tpo -c -o src/shared/libshared_glib_la-mainloop-glib.lo `test -f 'src/shared/mainloop-glib.c' || echo '$(srcdir)/'`src/shared/mainloop-glib.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-mainloop-glib.Tpo src/shared/$(DEPDIR)/libshared_glib_la-mainloop-glib.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/mainloop-glib.c' object='src/shared/libshared_glib_la-mainloop-glib.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-mainloop-glib.lo `test -f 'src/shared/mainloop-glib.c' || echo '$(srcdir)/'`src/shared/mainloop-glib.c src/shared/libshared_glib_la-mainloop-notify.lo: src/shared/mainloop-notify.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-mainloop-notify.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-mainloop-notify.Tpo -c -o src/shared/libshared_glib_la-mainloop-notify.lo `test -f 'src/shared/mainloop-notify.c' || echo '$(srcdir)/'`src/shared/mainloop-notify.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-mainloop-notify.Tpo src/shared/$(DEPDIR)/libshared_glib_la-mainloop-notify.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/mainloop-notify.c' object='src/shared/libshared_glib_la-mainloop-notify.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-mainloop-notify.lo `test -f 'src/shared/mainloop-notify.c' || echo '$(srcdir)/'`src/shared/mainloop-notify.c src/shared/libshared_glib_la-tester.lo: src/shared/tester.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-tester.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-tester.Tpo -c -o src/shared/libshared_glib_la-tester.lo `test -f 'src/shared/tester.c' || echo '$(srcdir)/'`src/shared/tester.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-tester.Tpo src/shared/$(DEPDIR)/libshared_glib_la-tester.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/tester.c' object='src/shared/libshared_glib_la-tester.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-tester.lo `test -f 'src/shared/tester.c' || echo '$(srcdir)/'`src/shared/tester.c src/shared/libshared_mainloop_la-queue.lo: src/shared/queue.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-queue.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-queue.Tpo -c -o src/shared/libshared_mainloop_la-queue.lo `test -f 'src/shared/queue.c' || echo '$(srcdir)/'`src/shared/queue.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-queue.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-queue.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/queue.c' object='src/shared/libshared_mainloop_la-queue.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-queue.lo `test -f 'src/shared/queue.c' || echo '$(srcdir)/'`src/shared/queue.c src/shared/libshared_mainloop_la-util.lo: src/shared/util.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-util.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-util.Tpo -c -o src/shared/libshared_mainloop_la-util.lo `test -f 'src/shared/util.c' || echo '$(srcdir)/'`src/shared/util.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-util.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-util.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/util.c' object='src/shared/libshared_mainloop_la-util.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-util.lo `test -f 'src/shared/util.c' || echo '$(srcdir)/'`src/shared/util.c src/shared/libshared_mainloop_la-mgmt.lo: src/shared/mgmt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-mgmt.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-mgmt.Tpo -c -o src/shared/libshared_mainloop_la-mgmt.lo `test -f 'src/shared/mgmt.c' || echo '$(srcdir)/'`src/shared/mgmt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-mgmt.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-mgmt.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/mgmt.c' object='src/shared/libshared_mainloop_la-mgmt.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-mgmt.lo `test -f 'src/shared/mgmt.c' || echo '$(srcdir)/'`src/shared/mgmt.c src/shared/libshared_mainloop_la-crypto.lo: src/shared/crypto.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-crypto.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-crypto.Tpo -c -o src/shared/libshared_mainloop_la-crypto.lo `test -f 'src/shared/crypto.c' || echo '$(srcdir)/'`src/shared/crypto.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-crypto.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-crypto.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/crypto.c' object='src/shared/libshared_mainloop_la-crypto.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-crypto.lo `test -f 'src/shared/crypto.c' || echo '$(srcdir)/'`src/shared/crypto.c src/shared/libshared_mainloop_la-ecc.lo: src/shared/ecc.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-ecc.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-ecc.Tpo -c -o src/shared/libshared_mainloop_la-ecc.lo `test -f 'src/shared/ecc.c' || echo '$(srcdir)/'`src/shared/ecc.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-ecc.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-ecc.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/ecc.c' object='src/shared/libshared_mainloop_la-ecc.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-ecc.lo `test -f 'src/shared/ecc.c' || echo '$(srcdir)/'`src/shared/ecc.c src/shared/libshared_mainloop_la-ringbuf.lo: src/shared/ringbuf.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-ringbuf.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-ringbuf.Tpo -c -o src/shared/libshared_mainloop_la-ringbuf.lo `test -f 'src/shared/ringbuf.c' || echo '$(srcdir)/'`src/shared/ringbuf.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-ringbuf.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-ringbuf.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/ringbuf.c' object='src/shared/libshared_mainloop_la-ringbuf.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-ringbuf.lo `test -f 'src/shared/ringbuf.c' || echo '$(srcdir)/'`src/shared/ringbuf.c src/shared/libshared_mainloop_la-hci.lo: src/shared/hci.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-hci.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-hci.Tpo -c -o src/shared/libshared_mainloop_la-hci.lo `test -f 'src/shared/hci.c' || echo '$(srcdir)/'`src/shared/hci.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-hci.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-hci.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/hci.c' object='src/shared/libshared_mainloop_la-hci.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-hci.lo `test -f 'src/shared/hci.c' || echo '$(srcdir)/'`src/shared/hci.c src/shared/libshared_mainloop_la-hci-crypto.lo: src/shared/hci-crypto.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-hci-crypto.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-hci-crypto.Tpo -c -o src/shared/libshared_mainloop_la-hci-crypto.lo `test -f 'src/shared/hci-crypto.c' || echo '$(srcdir)/'`src/shared/hci-crypto.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-hci-crypto.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-hci-crypto.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/hci-crypto.c' object='src/shared/libshared_mainloop_la-hci-crypto.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-hci-crypto.lo `test -f 'src/shared/hci-crypto.c' || echo '$(srcdir)/'`src/shared/hci-crypto.c src/shared/libshared_mainloop_la-hfp.lo: src/shared/hfp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-hfp.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-hfp.Tpo -c -o src/shared/libshared_mainloop_la-hfp.lo `test -f 'src/shared/hfp.c' || echo '$(srcdir)/'`src/shared/hfp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-hfp.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-hfp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/hfp.c' object='src/shared/libshared_mainloop_la-hfp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-hfp.lo `test -f 'src/shared/hfp.c' || echo '$(srcdir)/'`src/shared/hfp.c src/shared/libshared_mainloop_la-uhid.lo: src/shared/uhid.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-uhid.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-uhid.Tpo -c -o src/shared/libshared_mainloop_la-uhid.lo `test -f 'src/shared/uhid.c' || echo '$(srcdir)/'`src/shared/uhid.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-uhid.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-uhid.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/uhid.c' object='src/shared/libshared_mainloop_la-uhid.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-uhid.lo `test -f 'src/shared/uhid.c' || echo '$(srcdir)/'`src/shared/uhid.c src/shared/libshared_mainloop_la-pcap.lo: src/shared/pcap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-pcap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-pcap.Tpo -c -o src/shared/libshared_mainloop_la-pcap.lo `test -f 'src/shared/pcap.c' || echo '$(srcdir)/'`src/shared/pcap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-pcap.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-pcap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/pcap.c' object='src/shared/libshared_mainloop_la-pcap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-pcap.lo `test -f 'src/shared/pcap.c' || echo '$(srcdir)/'`src/shared/pcap.c src/shared/libshared_mainloop_la-btsnoop.lo: src/shared/btsnoop.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-btsnoop.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-btsnoop.Tpo -c -o src/shared/libshared_mainloop_la-btsnoop.lo `test -f 'src/shared/btsnoop.c' || echo '$(srcdir)/'`src/shared/btsnoop.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-btsnoop.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-btsnoop.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/btsnoop.c' object='src/shared/libshared_mainloop_la-btsnoop.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-btsnoop.lo `test -f 'src/shared/btsnoop.c' || echo '$(srcdir)/'`src/shared/btsnoop.c src/shared/libshared_mainloop_la-ad.lo: src/shared/ad.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-ad.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-ad.Tpo -c -o src/shared/libshared_mainloop_la-ad.lo `test -f 'src/shared/ad.c' || echo '$(srcdir)/'`src/shared/ad.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-ad.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-ad.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/ad.c' object='src/shared/libshared_mainloop_la-ad.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-ad.lo `test -f 'src/shared/ad.c' || echo '$(srcdir)/'`src/shared/ad.c src/shared/libshared_mainloop_la-att.lo: src/shared/att.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-att.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-att.Tpo -c -o src/shared/libshared_mainloop_la-att.lo `test -f 'src/shared/att.c' || echo '$(srcdir)/'`src/shared/att.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-att.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-att.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/att.c' object='src/shared/libshared_mainloop_la-att.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-att.lo `test -f 'src/shared/att.c' || echo '$(srcdir)/'`src/shared/att.c src/shared/libshared_mainloop_la-gatt-helpers.lo: src/shared/gatt-helpers.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-gatt-helpers.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-helpers.Tpo -c -o src/shared/libshared_mainloop_la-gatt-helpers.lo `test -f 'src/shared/gatt-helpers.c' || echo '$(srcdir)/'`src/shared/gatt-helpers.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-helpers.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-helpers.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gatt-helpers.c' object='src/shared/libshared_mainloop_la-gatt-helpers.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-gatt-helpers.lo `test -f 'src/shared/gatt-helpers.c' || echo '$(srcdir)/'`src/shared/gatt-helpers.c src/shared/libshared_mainloop_la-gatt-client.lo: src/shared/gatt-client.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-gatt-client.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-client.Tpo -c -o src/shared/libshared_mainloop_la-gatt-client.lo `test -f 'src/shared/gatt-client.c' || echo '$(srcdir)/'`src/shared/gatt-client.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-client.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-client.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gatt-client.c' object='src/shared/libshared_mainloop_la-gatt-client.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-gatt-client.lo `test -f 'src/shared/gatt-client.c' || echo '$(srcdir)/'`src/shared/gatt-client.c src/shared/libshared_mainloop_la-gatt-server.lo: src/shared/gatt-server.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-gatt-server.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-server.Tpo -c -o src/shared/libshared_mainloop_la-gatt-server.lo `test -f 'src/shared/gatt-server.c' || echo '$(srcdir)/'`src/shared/gatt-server.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-server.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-server.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gatt-server.c' object='src/shared/libshared_mainloop_la-gatt-server.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-gatt-server.lo `test -f 'src/shared/gatt-server.c' || echo '$(srcdir)/'`src/shared/gatt-server.c src/shared/libshared_mainloop_la-gatt-db.lo: src/shared/gatt-db.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-gatt-db.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-db.Tpo -c -o src/shared/libshared_mainloop_la-gatt-db.lo `test -f 'src/shared/gatt-db.c' || echo '$(srcdir)/'`src/shared/gatt-db.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-db.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-db.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gatt-db.c' object='src/shared/libshared_mainloop_la-gatt-db.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-gatt-db.lo `test -f 'src/shared/gatt-db.c' || echo '$(srcdir)/'`src/shared/gatt-db.c src/shared/libshared_mainloop_la-gap.lo: src/shared/gap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-gap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-gap.Tpo -c -o src/shared/libshared_mainloop_la-gap.lo `test -f 'src/shared/gap.c' || echo '$(srcdir)/'`src/shared/gap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-gap.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-gap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gap.c' object='src/shared/libshared_mainloop_la-gap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-gap.lo `test -f 'src/shared/gap.c' || echo '$(srcdir)/'`src/shared/gap.c src/shared/libshared_mainloop_la-log.lo: src/shared/log.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-log.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-log.Tpo -c -o src/shared/libshared_mainloop_la-log.lo `test -f 'src/shared/log.c' || echo '$(srcdir)/'`src/shared/log.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-log.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-log.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/log.c' object='src/shared/libshared_mainloop_la-log.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-log.lo `test -f 'src/shared/log.c' || echo '$(srcdir)/'`src/shared/log.c src/shared/libshared_mainloop_la-bap.lo: src/shared/bap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-bap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-bap.Tpo -c -o src/shared/libshared_mainloop_la-bap.lo `test -f 'src/shared/bap.c' || echo '$(srcdir)/'`src/shared/bap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-bap.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-bap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/bap.c' object='src/shared/libshared_mainloop_la-bap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-bap.lo `test -f 'src/shared/bap.c' || echo '$(srcdir)/'`src/shared/bap.c src/shared/libshared_mainloop_la-bap-debug.lo: src/shared/bap-debug.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-bap-debug.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-bap-debug.Tpo -c -o src/shared/libshared_mainloop_la-bap-debug.lo `test -f 'src/shared/bap-debug.c' || echo '$(srcdir)/'`src/shared/bap-debug.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-bap-debug.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-bap-debug.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/bap-debug.c' object='src/shared/libshared_mainloop_la-bap-debug.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-bap-debug.lo `test -f 'src/shared/bap-debug.c' || echo '$(srcdir)/'`src/shared/bap-debug.c src/shared/libshared_mainloop_la-mcp.lo: src/shared/mcp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-mcp.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-mcp.Tpo -c -o src/shared/libshared_mainloop_la-mcp.lo `test -f 'src/shared/mcp.c' || echo '$(srcdir)/'`src/shared/mcp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-mcp.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-mcp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/mcp.c' object='src/shared/libshared_mainloop_la-mcp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-mcp.lo `test -f 'src/shared/mcp.c' || echo '$(srcdir)/'`src/shared/mcp.c src/shared/libshared_mainloop_la-vcp.lo: src/shared/vcp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-vcp.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-vcp.Tpo -c -o src/shared/libshared_mainloop_la-vcp.lo `test -f 'src/shared/vcp.c' || echo '$(srcdir)/'`src/shared/vcp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-vcp.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-vcp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/vcp.c' object='src/shared/libshared_mainloop_la-vcp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-vcp.lo `test -f 'src/shared/vcp.c' || echo '$(srcdir)/'`src/shared/vcp.c src/shared/libshared_mainloop_la-micp.lo: src/shared/micp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-micp.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-micp.Tpo -c -o src/shared/libshared_mainloop_la-micp.lo `test -f 'src/shared/micp.c' || echo '$(srcdir)/'`src/shared/micp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-micp.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-micp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/micp.c' object='src/shared/libshared_mainloop_la-micp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-micp.lo `test -f 'src/shared/micp.c' || echo '$(srcdir)/'`src/shared/micp.c src/shared/libshared_mainloop_la-csip.lo: src/shared/csip.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-csip.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-csip.Tpo -c -o src/shared/libshared_mainloop_la-csip.lo `test -f 'src/shared/csip.c' || echo '$(srcdir)/'`src/shared/csip.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-csip.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-csip.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/csip.c' object='src/shared/libshared_mainloop_la-csip.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-csip.lo `test -f 'src/shared/csip.c' || echo '$(srcdir)/'`src/shared/csip.c src/shared/libshared_mainloop_la-bass.lo: src/shared/bass.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-bass.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-bass.Tpo -c -o src/shared/libshared_mainloop_la-bass.lo `test -f 'src/shared/bass.c' || echo '$(srcdir)/'`src/shared/bass.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-bass.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-bass.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/bass.c' object='src/shared/libshared_mainloop_la-bass.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-bass.lo `test -f 'src/shared/bass.c' || echo '$(srcdir)/'`src/shared/bass.c src/shared/libshared_mainloop_la-ccp.lo: src/shared/ccp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-ccp.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-ccp.Tpo -c -o src/shared/libshared_mainloop_la-ccp.lo `test -f 'src/shared/ccp.c' || echo '$(srcdir)/'`src/shared/ccp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-ccp.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-ccp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/ccp.c' object='src/shared/libshared_mainloop_la-ccp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-ccp.lo `test -f 'src/shared/ccp.c' || echo '$(srcdir)/'`src/shared/ccp.c src/shared/libshared_mainloop_la-tmap.lo: src/shared/tmap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-tmap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-tmap.Tpo -c -o src/shared/libshared_mainloop_la-tmap.lo `test -f 'src/shared/tmap.c' || echo '$(srcdir)/'`src/shared/tmap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-tmap.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-tmap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/tmap.c' object='src/shared/libshared_mainloop_la-tmap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-tmap.lo `test -f 'src/shared/tmap.c' || echo '$(srcdir)/'`src/shared/tmap.c src/shared/libshared_mainloop_la-gmap.lo: src/shared/gmap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-gmap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-gmap.Tpo -c -o src/shared/libshared_mainloop_la-gmap.lo `test -f 'src/shared/gmap.c' || echo '$(srcdir)/'`src/shared/gmap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-gmap.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-gmap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/gmap.c' object='src/shared/libshared_mainloop_la-gmap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-gmap.lo `test -f 'src/shared/gmap.c' || echo '$(srcdir)/'`src/shared/gmap.c src/shared/libshared_mainloop_la-asha.lo: src/shared/asha.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-asha.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-asha.Tpo -c -o src/shared/libshared_mainloop_la-asha.lo `test -f 'src/shared/asha.c' || echo '$(srcdir)/'`src/shared/asha.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-asha.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-asha.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/asha.c' object='src/shared/libshared_mainloop_la-asha.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-asha.lo `test -f 'src/shared/asha.c' || echo '$(srcdir)/'`src/shared/asha.c src/shared/libshared_mainloop_la-battery.lo: src/shared/battery.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-battery.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-battery.Tpo -c -o src/shared/libshared_mainloop_la-battery.lo `test -f 'src/shared/battery.c' || echo '$(srcdir)/'`src/shared/battery.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-battery.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-battery.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/battery.c' object='src/shared/libshared_mainloop_la-battery.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-battery.lo `test -f 'src/shared/battery.c' || echo '$(srcdir)/'`src/shared/battery.c src/shared/libshared_mainloop_la-uinput.lo: src/shared/uinput.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-uinput.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-uinput.Tpo -c -o src/shared/libshared_mainloop_la-uinput.lo `test -f 'src/shared/uinput.c' || echo '$(srcdir)/'`src/shared/uinput.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-uinput.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-uinput.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/uinput.c' object='src/shared/libshared_mainloop_la-uinput.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-uinput.lo `test -f 'src/shared/uinput.c' || echo '$(srcdir)/'`src/shared/uinput.c src/shared/libshared_mainloop_la-rap.lo: src/shared/rap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-rap.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-rap.Tpo -c -o src/shared/libshared_mainloop_la-rap.lo `test -f 'src/shared/rap.c' || echo '$(srcdir)/'`src/shared/rap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-rap.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-rap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/rap.c' object='src/shared/libshared_mainloop_la-rap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-rap.lo `test -f 'src/shared/rap.c' || echo '$(srcdir)/'`src/shared/rap.c src/shared/libshared_mainloop_la-shell.lo: src/shared/shell.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-shell.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-shell.Tpo -c -o src/shared/libshared_mainloop_la-shell.lo `test -f 'src/shared/shell.c' || echo '$(srcdir)/'`src/shared/shell.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-shell.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-shell.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/shell.c' object='src/shared/libshared_mainloop_la-shell.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-shell.lo `test -f 'src/shared/shell.c' || echo '$(srcdir)/'`src/shared/shell.c src/shared/libshared_mainloop_la-io-mainloop.lo: src/shared/io-mainloop.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-io-mainloop.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-io-mainloop.Tpo -c -o src/shared/libshared_mainloop_la-io-mainloop.lo `test -f 'src/shared/io-mainloop.c' || echo '$(srcdir)/'`src/shared/io-mainloop.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-io-mainloop.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-io-mainloop.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/io-mainloop.c' object='src/shared/libshared_mainloop_la-io-mainloop.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-io-mainloop.lo `test -f 'src/shared/io-mainloop.c' || echo '$(srcdir)/'`src/shared/io-mainloop.c src/shared/libshared_mainloop_la-timeout-mainloop.lo: src/shared/timeout-mainloop.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-timeout-mainloop.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-timeout-mainloop.Tpo -c -o src/shared/libshared_mainloop_la-timeout-mainloop.lo `test -f 'src/shared/timeout-mainloop.c' || echo '$(srcdir)/'`src/shared/timeout-mainloop.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-timeout-mainloop.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-timeout-mainloop.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/timeout-mainloop.c' object='src/shared/libshared_mainloop_la-timeout-mainloop.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-timeout-mainloop.lo `test -f 'src/shared/timeout-mainloop.c' || echo '$(srcdir)/'`src/shared/timeout-mainloop.c src/shared/libshared_mainloop_la-mainloop.lo: src/shared/mainloop.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-mainloop.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-mainloop.Tpo -c -o src/shared/libshared_mainloop_la-mainloop.lo `test -f 'src/shared/mainloop.c' || echo '$(srcdir)/'`src/shared/mainloop.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-mainloop.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-mainloop.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/mainloop.c' object='src/shared/libshared_mainloop_la-mainloop.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-mainloop.lo `test -f 'src/shared/mainloop.c' || echo '$(srcdir)/'`src/shared/mainloop.c src/shared/libshared_mainloop_la-mainloop-notify.lo: src/shared/mainloop-notify.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-mainloop-notify.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-mainloop-notify.Tpo -c -o src/shared/libshared_mainloop_la-mainloop-notify.lo `test -f 'src/shared/mainloop-notify.c' || echo '$(srcdir)/'`src/shared/mainloop-notify.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-mainloop-notify.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-mainloop-notify.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/mainloop-notify.c' object='src/shared/libshared_mainloop_la-mainloop-notify.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-mainloop-notify.lo `test -f 'src/shared/mainloop-notify.c' || echo '$(srcdir)/'`src/shared/mainloop-notify.c btio/obexd-btio.o: btio/btio.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT btio/obexd-btio.o -MD -MP -MF btio/$(DEPDIR)/obexd-btio.Tpo -c -o btio/obexd-btio.o `test -f 'btio/btio.c' || echo '$(srcdir)/'`btio/btio.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) btio/$(DEPDIR)/obexd-btio.Tpo btio/$(DEPDIR)/obexd-btio.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='btio/btio.c' object='btio/obexd-btio.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o btio/obexd-btio.o `test -f 'btio/btio.c' || echo '$(srcdir)/'`btio/btio.c btio/obexd-btio.obj: btio/btio.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT btio/obexd-btio.obj -MD -MP -MF btio/$(DEPDIR)/obexd-btio.Tpo -c -o btio/obexd-btio.obj `if test -f 'btio/btio.c'; then $(CYGPATH_W) 'btio/btio.c'; else $(CYGPATH_W) '$(srcdir)/btio/btio.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) btio/$(DEPDIR)/obexd-btio.Tpo btio/$(DEPDIR)/obexd-btio.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='btio/btio.c' object='btio/obexd-btio.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o btio/obexd-btio.obj `if test -f 'btio/btio.c'; then $(CYGPATH_W) 'btio/btio.c'; else $(CYGPATH_W) '$(srcdir)/btio/btio.c'; fi` gobex/obexd-gobex.o: gobex/gobex.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex.o -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex.Tpo -c -o gobex/obexd-gobex.o `test -f 'gobex/gobex.c' || echo '$(srcdir)/'`gobex/gobex.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex.Tpo gobex/$(DEPDIR)/obexd-gobex.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex.c' object='gobex/obexd-gobex.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex.o `test -f 'gobex/gobex.c' || echo '$(srcdir)/'`gobex/gobex.c gobex/obexd-gobex.obj: gobex/gobex.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex.obj -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex.Tpo -c -o gobex/obexd-gobex.obj `if test -f 'gobex/gobex.c'; then $(CYGPATH_W) 'gobex/gobex.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex.Tpo gobex/$(DEPDIR)/obexd-gobex.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex.c' object='gobex/obexd-gobex.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex.obj `if test -f 'gobex/gobex.c'; then $(CYGPATH_W) 'gobex/gobex.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex.c'; fi` gobex/obexd-gobex-defs.o: gobex/gobex-defs.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-defs.o -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-defs.Tpo -c -o gobex/obexd-gobex-defs.o `test -f 'gobex/gobex-defs.c' || echo '$(srcdir)/'`gobex/gobex-defs.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-defs.Tpo gobex/$(DEPDIR)/obexd-gobex-defs.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-defs.c' object='gobex/obexd-gobex-defs.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-defs.o `test -f 'gobex/gobex-defs.c' || echo '$(srcdir)/'`gobex/gobex-defs.c gobex/obexd-gobex-defs.obj: gobex/gobex-defs.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-defs.obj -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-defs.Tpo -c -o gobex/obexd-gobex-defs.obj `if test -f 'gobex/gobex-defs.c'; then $(CYGPATH_W) 'gobex/gobex-defs.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-defs.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-defs.Tpo gobex/$(DEPDIR)/obexd-gobex-defs.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-defs.c' object='gobex/obexd-gobex-defs.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-defs.obj `if test -f 'gobex/gobex-defs.c'; then $(CYGPATH_W) 'gobex/gobex-defs.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-defs.c'; fi` gobex/obexd-gobex-packet.o: gobex/gobex-packet.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-packet.o -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-packet.Tpo -c -o gobex/obexd-gobex-packet.o `test -f 'gobex/gobex-packet.c' || echo '$(srcdir)/'`gobex/gobex-packet.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-packet.Tpo gobex/$(DEPDIR)/obexd-gobex-packet.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-packet.c' object='gobex/obexd-gobex-packet.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-packet.o `test -f 'gobex/gobex-packet.c' || echo '$(srcdir)/'`gobex/gobex-packet.c gobex/obexd-gobex-packet.obj: gobex/gobex-packet.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-packet.obj -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-packet.Tpo -c -o gobex/obexd-gobex-packet.obj `if test -f 'gobex/gobex-packet.c'; then $(CYGPATH_W) 'gobex/gobex-packet.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-packet.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-packet.Tpo gobex/$(DEPDIR)/obexd-gobex-packet.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-packet.c' object='gobex/obexd-gobex-packet.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-packet.obj `if test -f 'gobex/gobex-packet.c'; then $(CYGPATH_W) 'gobex/gobex-packet.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-packet.c'; fi` gobex/obexd-gobex-header.o: gobex/gobex-header.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-header.o -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-header.Tpo -c -o gobex/obexd-gobex-header.o `test -f 'gobex/gobex-header.c' || echo '$(srcdir)/'`gobex/gobex-header.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-header.Tpo gobex/$(DEPDIR)/obexd-gobex-header.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-header.c' object='gobex/obexd-gobex-header.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-header.o `test -f 'gobex/gobex-header.c' || echo '$(srcdir)/'`gobex/gobex-header.c gobex/obexd-gobex-header.obj: gobex/gobex-header.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-header.obj -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-header.Tpo -c -o gobex/obexd-gobex-header.obj `if test -f 'gobex/gobex-header.c'; then $(CYGPATH_W) 'gobex/gobex-header.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-header.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-header.Tpo gobex/$(DEPDIR)/obexd-gobex-header.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-header.c' object='gobex/obexd-gobex-header.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-header.obj `if test -f 'gobex/gobex-header.c'; then $(CYGPATH_W) 'gobex/gobex-header.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-header.c'; fi` gobex/obexd-gobex-transfer.o: gobex/gobex-transfer.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-transfer.o -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-transfer.Tpo -c -o gobex/obexd-gobex-transfer.o `test -f 'gobex/gobex-transfer.c' || echo '$(srcdir)/'`gobex/gobex-transfer.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-transfer.Tpo gobex/$(DEPDIR)/obexd-gobex-transfer.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-transfer.c' object='gobex/obexd-gobex-transfer.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-transfer.o `test -f 'gobex/gobex-transfer.c' || echo '$(srcdir)/'`gobex/gobex-transfer.c gobex/obexd-gobex-transfer.obj: gobex/gobex-transfer.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-transfer.obj -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-transfer.Tpo -c -o gobex/obexd-gobex-transfer.obj `if test -f 'gobex/gobex-transfer.c'; then $(CYGPATH_W) 'gobex/gobex-transfer.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-transfer.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-transfer.Tpo gobex/$(DEPDIR)/obexd-gobex-transfer.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-transfer.c' object='gobex/obexd-gobex-transfer.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-transfer.obj `if test -f 'gobex/gobex-transfer.c'; then $(CYGPATH_W) 'gobex/gobex-transfer.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-transfer.c'; fi` gobex/obexd-gobex-apparam.o: gobex/gobex-apparam.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-apparam.o -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-apparam.Tpo -c -o gobex/obexd-gobex-apparam.o `test -f 'gobex/gobex-apparam.c' || echo '$(srcdir)/'`gobex/gobex-apparam.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-apparam.Tpo gobex/$(DEPDIR)/obexd-gobex-apparam.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-apparam.c' object='gobex/obexd-gobex-apparam.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-apparam.o `test -f 'gobex/gobex-apparam.c' || echo '$(srcdir)/'`gobex/gobex-apparam.c gobex/obexd-gobex-apparam.obj: gobex/gobex-apparam.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-apparam.obj -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-apparam.Tpo -c -o gobex/obexd-gobex-apparam.obj `if test -f 'gobex/gobex-apparam.c'; then $(CYGPATH_W) 'gobex/gobex-apparam.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-apparam.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-apparam.Tpo gobex/$(DEPDIR)/obexd-gobex-apparam.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-apparam.c' object='gobex/obexd-gobex-apparam.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-apparam.obj `if test -f 'gobex/gobex-apparam.c'; then $(CYGPATH_W) 'gobex/gobex-apparam.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-apparam.c'; fi` obexd/plugins/obexd-filesystem.o: obexd/plugins/filesystem.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-filesystem.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-filesystem.Tpo -c -o obexd/plugins/obexd-filesystem.o `test -f 'obexd/plugins/filesystem.c' || echo '$(srcdir)/'`obexd/plugins/filesystem.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-filesystem.Tpo obexd/plugins/$(DEPDIR)/obexd-filesystem.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/filesystem.c' object='obexd/plugins/obexd-filesystem.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-filesystem.o `test -f 'obexd/plugins/filesystem.c' || echo '$(srcdir)/'`obexd/plugins/filesystem.c obexd/plugins/obexd-filesystem.obj: obexd/plugins/filesystem.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-filesystem.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-filesystem.Tpo -c -o obexd/plugins/obexd-filesystem.obj `if test -f 'obexd/plugins/filesystem.c'; then $(CYGPATH_W) 'obexd/plugins/filesystem.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/filesystem.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-filesystem.Tpo obexd/plugins/$(DEPDIR)/obexd-filesystem.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/filesystem.c' object='obexd/plugins/obexd-filesystem.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-filesystem.obj `if test -f 'obexd/plugins/filesystem.c'; then $(CYGPATH_W) 'obexd/plugins/filesystem.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/filesystem.c'; fi` obexd/plugins/obexd-bluetooth.o: obexd/plugins/bluetooth.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-bluetooth.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-bluetooth.Tpo -c -o obexd/plugins/obexd-bluetooth.o `test -f 'obexd/plugins/bluetooth.c' || echo '$(srcdir)/'`obexd/plugins/bluetooth.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-bluetooth.Tpo obexd/plugins/$(DEPDIR)/obexd-bluetooth.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/bluetooth.c' object='obexd/plugins/obexd-bluetooth.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-bluetooth.o `test -f 'obexd/plugins/bluetooth.c' || echo '$(srcdir)/'`obexd/plugins/bluetooth.c obexd/plugins/obexd-bluetooth.obj: obexd/plugins/bluetooth.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-bluetooth.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-bluetooth.Tpo -c -o obexd/plugins/obexd-bluetooth.obj `if test -f 'obexd/plugins/bluetooth.c'; then $(CYGPATH_W) 'obexd/plugins/bluetooth.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/bluetooth.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-bluetooth.Tpo obexd/plugins/$(DEPDIR)/obexd-bluetooth.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/bluetooth.c' object='obexd/plugins/obexd-bluetooth.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-bluetooth.obj `if test -f 'obexd/plugins/bluetooth.c'; then $(CYGPATH_W) 'obexd/plugins/bluetooth.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/bluetooth.c'; fi` obexd/plugins/obexd-pcsuite.o: obexd/plugins/pcsuite.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-pcsuite.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-pcsuite.Tpo -c -o obexd/plugins/obexd-pcsuite.o `test -f 'obexd/plugins/pcsuite.c' || echo '$(srcdir)/'`obexd/plugins/pcsuite.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-pcsuite.Tpo obexd/plugins/$(DEPDIR)/obexd-pcsuite.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/pcsuite.c' object='obexd/plugins/obexd-pcsuite.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-pcsuite.o `test -f 'obexd/plugins/pcsuite.c' || echo '$(srcdir)/'`obexd/plugins/pcsuite.c obexd/plugins/obexd-pcsuite.obj: obexd/plugins/pcsuite.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-pcsuite.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-pcsuite.Tpo -c -o obexd/plugins/obexd-pcsuite.obj `if test -f 'obexd/plugins/pcsuite.c'; then $(CYGPATH_W) 'obexd/plugins/pcsuite.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/pcsuite.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-pcsuite.Tpo obexd/plugins/$(DEPDIR)/obexd-pcsuite.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/pcsuite.c' object='obexd/plugins/obexd-pcsuite.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-pcsuite.obj `if test -f 'obexd/plugins/pcsuite.c'; then $(CYGPATH_W) 'obexd/plugins/pcsuite.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/pcsuite.c'; fi` obexd/plugins/obexd-opp.o: obexd/plugins/opp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-opp.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-opp.Tpo -c -o obexd/plugins/obexd-opp.o `test -f 'obexd/plugins/opp.c' || echo '$(srcdir)/'`obexd/plugins/opp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-opp.Tpo obexd/plugins/$(DEPDIR)/obexd-opp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/opp.c' object='obexd/plugins/obexd-opp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-opp.o `test -f 'obexd/plugins/opp.c' || echo '$(srcdir)/'`obexd/plugins/opp.c obexd/plugins/obexd-opp.obj: obexd/plugins/opp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-opp.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-opp.Tpo -c -o obexd/plugins/obexd-opp.obj `if test -f 'obexd/plugins/opp.c'; then $(CYGPATH_W) 'obexd/plugins/opp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/opp.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-opp.Tpo obexd/plugins/$(DEPDIR)/obexd-opp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/opp.c' object='obexd/plugins/obexd-opp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-opp.obj `if test -f 'obexd/plugins/opp.c'; then $(CYGPATH_W) 'obexd/plugins/opp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/opp.c'; fi` obexd/plugins/obexd-ftp.o: obexd/plugins/ftp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-ftp.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-ftp.Tpo -c -o obexd/plugins/obexd-ftp.o `test -f 'obexd/plugins/ftp.c' || echo '$(srcdir)/'`obexd/plugins/ftp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-ftp.Tpo obexd/plugins/$(DEPDIR)/obexd-ftp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/ftp.c' object='obexd/plugins/obexd-ftp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-ftp.o `test -f 'obexd/plugins/ftp.c' || echo '$(srcdir)/'`obexd/plugins/ftp.c obexd/plugins/obexd-ftp.obj: obexd/plugins/ftp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-ftp.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-ftp.Tpo -c -o obexd/plugins/obexd-ftp.obj `if test -f 'obexd/plugins/ftp.c'; then $(CYGPATH_W) 'obexd/plugins/ftp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/ftp.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-ftp.Tpo obexd/plugins/$(DEPDIR)/obexd-ftp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/ftp.c' object='obexd/plugins/obexd-ftp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-ftp.obj `if test -f 'obexd/plugins/ftp.c'; then $(CYGPATH_W) 'obexd/plugins/ftp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/ftp.c'; fi` obexd/plugins/obexd-irmc.o: obexd/plugins/irmc.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-irmc.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-irmc.Tpo -c -o obexd/plugins/obexd-irmc.o `test -f 'obexd/plugins/irmc.c' || echo '$(srcdir)/'`obexd/plugins/irmc.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-irmc.Tpo obexd/plugins/$(DEPDIR)/obexd-irmc.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/irmc.c' object='obexd/plugins/obexd-irmc.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-irmc.o `test -f 'obexd/plugins/irmc.c' || echo '$(srcdir)/'`obexd/plugins/irmc.c obexd/plugins/obexd-irmc.obj: obexd/plugins/irmc.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-irmc.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-irmc.Tpo -c -o obexd/plugins/obexd-irmc.obj `if test -f 'obexd/plugins/irmc.c'; then $(CYGPATH_W) 'obexd/plugins/irmc.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/irmc.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-irmc.Tpo obexd/plugins/$(DEPDIR)/obexd-irmc.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/irmc.c' object='obexd/plugins/obexd-irmc.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-irmc.obj `if test -f 'obexd/plugins/irmc.c'; then $(CYGPATH_W) 'obexd/plugins/irmc.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/irmc.c'; fi` obexd/plugins/obexd-pbap.o: obexd/plugins/pbap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-pbap.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-pbap.Tpo -c -o obexd/plugins/obexd-pbap.o `test -f 'obexd/plugins/pbap.c' || echo '$(srcdir)/'`obexd/plugins/pbap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-pbap.Tpo obexd/plugins/$(DEPDIR)/obexd-pbap.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/pbap.c' object='obexd/plugins/obexd-pbap.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-pbap.o `test -f 'obexd/plugins/pbap.c' || echo '$(srcdir)/'`obexd/plugins/pbap.c obexd/plugins/obexd-pbap.obj: obexd/plugins/pbap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-pbap.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-pbap.Tpo -c -o obexd/plugins/obexd-pbap.obj `if test -f 'obexd/plugins/pbap.c'; then $(CYGPATH_W) 'obexd/plugins/pbap.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/pbap.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-pbap.Tpo obexd/plugins/$(DEPDIR)/obexd-pbap.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/pbap.c' object='obexd/plugins/obexd-pbap.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-pbap.obj `if test -f 'obexd/plugins/pbap.c'; then $(CYGPATH_W) 'obexd/plugins/pbap.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/pbap.c'; fi` obexd/plugins/obexd-vcard.o: obexd/plugins/vcard.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-vcard.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-vcard.Tpo -c -o obexd/plugins/obexd-vcard.o `test -f 'obexd/plugins/vcard.c' || echo '$(srcdir)/'`obexd/plugins/vcard.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-vcard.Tpo obexd/plugins/$(DEPDIR)/obexd-vcard.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/vcard.c' object='obexd/plugins/obexd-vcard.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-vcard.o `test -f 'obexd/plugins/vcard.c' || echo '$(srcdir)/'`obexd/plugins/vcard.c obexd/plugins/obexd-vcard.obj: obexd/plugins/vcard.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-vcard.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-vcard.Tpo -c -o obexd/plugins/obexd-vcard.obj `if test -f 'obexd/plugins/vcard.c'; then $(CYGPATH_W) 'obexd/plugins/vcard.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/vcard.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-vcard.Tpo obexd/plugins/$(DEPDIR)/obexd-vcard.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/vcard.c' object='obexd/plugins/obexd-vcard.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-vcard.obj `if test -f 'obexd/plugins/vcard.c'; then $(CYGPATH_W) 'obexd/plugins/vcard.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/vcard.c'; fi` obexd/plugins/obexd-phonebook-@PLUGIN_PHONEBOOK@.o: obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-phonebook-@PLUGIN_PHONEBOOK@.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-phonebook-@PLUGIN_PHONEBOOK@.Tpo -c -o obexd/plugins/obexd-phonebook-@PLUGIN_PHONEBOOK@.o `test -f 'obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c' || echo '$(srcdir)/'`obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-phonebook-@PLUGIN_PHONEBOOK@.Tpo obexd/plugins/$(DEPDIR)/obexd-phonebook-@PLUGIN_PHONEBOOK@.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c' object='obexd/plugins/obexd-phonebook-@PLUGIN_PHONEBOOK@.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-phonebook-@PLUGIN_PHONEBOOK@.o `test -f 'obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c' || echo '$(srcdir)/'`obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c obexd/plugins/obexd-phonebook-@PLUGIN_PHONEBOOK@.obj: obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-phonebook-@PLUGIN_PHONEBOOK@.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-phonebook-@PLUGIN_PHONEBOOK@.Tpo -c -o obexd/plugins/obexd-phonebook-@PLUGIN_PHONEBOOK@.obj `if test -f 'obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c'; then $(CYGPATH_W) 'obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-phonebook-@PLUGIN_PHONEBOOK@.Tpo obexd/plugins/$(DEPDIR)/obexd-phonebook-@PLUGIN_PHONEBOOK@.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c' object='obexd/plugins/obexd-phonebook-@PLUGIN_PHONEBOOK@.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-phonebook-@PLUGIN_PHONEBOOK@.obj `if test -f 'obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c'; then $(CYGPATH_W) 'obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c'; fi` obexd/plugins/obexd-mas.o: obexd/plugins/mas.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-mas.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-mas.Tpo -c -o obexd/plugins/obexd-mas.o `test -f 'obexd/plugins/mas.c' || echo '$(srcdir)/'`obexd/plugins/mas.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-mas.Tpo obexd/plugins/$(DEPDIR)/obexd-mas.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/mas.c' object='obexd/plugins/obexd-mas.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-mas.o `test -f 'obexd/plugins/mas.c' || echo '$(srcdir)/'`obexd/plugins/mas.c obexd/plugins/obexd-mas.obj: obexd/plugins/mas.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-mas.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-mas.Tpo -c -o obexd/plugins/obexd-mas.obj `if test -f 'obexd/plugins/mas.c'; then $(CYGPATH_W) 'obexd/plugins/mas.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/mas.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-mas.Tpo obexd/plugins/$(DEPDIR)/obexd-mas.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/mas.c' object='obexd/plugins/obexd-mas.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-mas.obj `if test -f 'obexd/plugins/mas.c'; then $(CYGPATH_W) 'obexd/plugins/mas.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/mas.c'; fi` obexd/plugins/obexd-messages-dummy.o: obexd/plugins/messages-dummy.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-messages-dummy.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Tpo -c -o obexd/plugins/obexd-messages-dummy.o `test -f 'obexd/plugins/messages-dummy.c' || echo '$(srcdir)/'`obexd/plugins/messages-dummy.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Tpo obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/messages-dummy.c' object='obexd/plugins/obexd-messages-dummy.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-messages-dummy.o `test -f 'obexd/plugins/messages-dummy.c' || echo '$(srcdir)/'`obexd/plugins/messages-dummy.c obexd/plugins/obexd-messages-dummy.obj: obexd/plugins/messages-dummy.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-messages-dummy.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Tpo -c -o obexd/plugins/obexd-messages-dummy.obj `if test -f 'obexd/plugins/messages-dummy.c'; then $(CYGPATH_W) 'obexd/plugins/messages-dummy.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/messages-dummy.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Tpo obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/messages-dummy.c' object='obexd/plugins/obexd-messages-dummy.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-messages-dummy.obj `if test -f 'obexd/plugins/messages-dummy.c'; then $(CYGPATH_W) 'obexd/plugins/messages-dummy.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/messages-dummy.c'; fi` obexd/client/obexd-mns.o: obexd/client/mns.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-mns.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-mns.Tpo -c -o obexd/client/obexd-mns.o `test -f 'obexd/client/mns.c' || echo '$(srcdir)/'`obexd/client/mns.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-mns.Tpo obexd/client/$(DEPDIR)/obexd-mns.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/mns.c' object='obexd/client/obexd-mns.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-mns.o `test -f 'obexd/client/mns.c' || echo '$(srcdir)/'`obexd/client/mns.c obexd/client/obexd-mns.obj: obexd/client/mns.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-mns.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-mns.Tpo -c -o obexd/client/obexd-mns.obj `if test -f 'obexd/client/mns.c'; then $(CYGPATH_W) 'obexd/client/mns.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/mns.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-mns.Tpo obexd/client/$(DEPDIR)/obexd-mns.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/mns.c' object='obexd/client/obexd-mns.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-mns.obj `if test -f 'obexd/client/mns.c'; then $(CYGPATH_W) 'obexd/client/mns.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/mns.c'; fi` obexd/src/obexd-main.o: obexd/src/main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-main.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-main.Tpo -c -o obexd/src/obexd-main.o `test -f 'obexd/src/main.c' || echo '$(srcdir)/'`obexd/src/main.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-main.Tpo obexd/src/$(DEPDIR)/obexd-main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/main.c' object='obexd/src/obexd-main.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-main.o `test -f 'obexd/src/main.c' || echo '$(srcdir)/'`obexd/src/main.c obexd/src/obexd-main.obj: obexd/src/main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-main.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-main.Tpo -c -o obexd/src/obexd-main.obj `if test -f 'obexd/src/main.c'; then $(CYGPATH_W) 'obexd/src/main.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/main.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-main.Tpo obexd/src/$(DEPDIR)/obexd-main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/main.c' object='obexd/src/obexd-main.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-main.obj `if test -f 'obexd/src/main.c'; then $(CYGPATH_W) 'obexd/src/main.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/main.c'; fi` obexd/src/obexd-plugin.o: obexd/src/plugin.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-plugin.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-plugin.Tpo -c -o obexd/src/obexd-plugin.o `test -f 'obexd/src/plugin.c' || echo '$(srcdir)/'`obexd/src/plugin.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-plugin.Tpo obexd/src/$(DEPDIR)/obexd-plugin.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/plugin.c' object='obexd/src/obexd-plugin.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-plugin.o `test -f 'obexd/src/plugin.c' || echo '$(srcdir)/'`obexd/src/plugin.c obexd/src/obexd-plugin.obj: obexd/src/plugin.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-plugin.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-plugin.Tpo -c -o obexd/src/obexd-plugin.obj `if test -f 'obexd/src/plugin.c'; then $(CYGPATH_W) 'obexd/src/plugin.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/plugin.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-plugin.Tpo obexd/src/$(DEPDIR)/obexd-plugin.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/plugin.c' object='obexd/src/obexd-plugin.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-plugin.obj `if test -f 'obexd/src/plugin.c'; then $(CYGPATH_W) 'obexd/src/plugin.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/plugin.c'; fi` obexd/src/obexd-log.o: obexd/src/log.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-log.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-log.Tpo -c -o obexd/src/obexd-log.o `test -f 'obexd/src/log.c' || echo '$(srcdir)/'`obexd/src/log.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-log.Tpo obexd/src/$(DEPDIR)/obexd-log.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/log.c' object='obexd/src/obexd-log.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-log.o `test -f 'obexd/src/log.c' || echo '$(srcdir)/'`obexd/src/log.c obexd/src/obexd-log.obj: obexd/src/log.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-log.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-log.Tpo -c -o obexd/src/obexd-log.obj `if test -f 'obexd/src/log.c'; then $(CYGPATH_W) 'obexd/src/log.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/log.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-log.Tpo obexd/src/$(DEPDIR)/obexd-log.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/log.c' object='obexd/src/obexd-log.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-log.obj `if test -f 'obexd/src/log.c'; then $(CYGPATH_W) 'obexd/src/log.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/log.c'; fi` obexd/src/obexd-logind.o: obexd/src/logind.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-logind.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-logind.Tpo -c -o obexd/src/obexd-logind.o `test -f 'obexd/src/logind.c' || echo '$(srcdir)/'`obexd/src/logind.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-logind.Tpo obexd/src/$(DEPDIR)/obexd-logind.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/logind.c' object='obexd/src/obexd-logind.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-logind.o `test -f 'obexd/src/logind.c' || echo '$(srcdir)/'`obexd/src/logind.c obexd/src/obexd-logind.obj: obexd/src/logind.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-logind.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-logind.Tpo -c -o obexd/src/obexd-logind.obj `if test -f 'obexd/src/logind.c'; then $(CYGPATH_W) 'obexd/src/logind.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/logind.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-logind.Tpo obexd/src/$(DEPDIR)/obexd-logind.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/logind.c' object='obexd/src/obexd-logind.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-logind.obj `if test -f 'obexd/src/logind.c'; then $(CYGPATH_W) 'obexd/src/logind.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/logind.c'; fi` obexd/src/obexd-manager.o: obexd/src/manager.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-manager.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-manager.Tpo -c -o obexd/src/obexd-manager.o `test -f 'obexd/src/manager.c' || echo '$(srcdir)/'`obexd/src/manager.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-manager.Tpo obexd/src/$(DEPDIR)/obexd-manager.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/manager.c' object='obexd/src/obexd-manager.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-manager.o `test -f 'obexd/src/manager.c' || echo '$(srcdir)/'`obexd/src/manager.c obexd/src/obexd-manager.obj: obexd/src/manager.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-manager.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-manager.Tpo -c -o obexd/src/obexd-manager.obj `if test -f 'obexd/src/manager.c'; then $(CYGPATH_W) 'obexd/src/manager.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/manager.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-manager.Tpo obexd/src/$(DEPDIR)/obexd-manager.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/manager.c' object='obexd/src/obexd-manager.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-manager.obj `if test -f 'obexd/src/manager.c'; then $(CYGPATH_W) 'obexd/src/manager.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/manager.c'; fi` obexd/src/obexd-obex.o: obexd/src/obex.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-obex.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-obex.Tpo -c -o obexd/src/obexd-obex.o `test -f 'obexd/src/obex.c' || echo '$(srcdir)/'`obexd/src/obex.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-obex.Tpo obexd/src/$(DEPDIR)/obexd-obex.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/obex.c' object='obexd/src/obexd-obex.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-obex.o `test -f 'obexd/src/obex.c' || echo '$(srcdir)/'`obexd/src/obex.c obexd/src/obexd-obex.obj: obexd/src/obex.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-obex.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-obex.Tpo -c -o obexd/src/obexd-obex.obj `if test -f 'obexd/src/obex.c'; then $(CYGPATH_W) 'obexd/src/obex.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/obex.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-obex.Tpo obexd/src/$(DEPDIR)/obexd-obex.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/obex.c' object='obexd/src/obexd-obex.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-obex.obj `if test -f 'obexd/src/obex.c'; then $(CYGPATH_W) 'obexd/src/obex.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/obex.c'; fi` obexd/src/obexd-mimetype.o: obexd/src/mimetype.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-mimetype.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-mimetype.Tpo -c -o obexd/src/obexd-mimetype.o `test -f 'obexd/src/mimetype.c' || echo '$(srcdir)/'`obexd/src/mimetype.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-mimetype.Tpo obexd/src/$(DEPDIR)/obexd-mimetype.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/mimetype.c' object='obexd/src/obexd-mimetype.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-mimetype.o `test -f 'obexd/src/mimetype.c' || echo '$(srcdir)/'`obexd/src/mimetype.c obexd/src/obexd-mimetype.obj: obexd/src/mimetype.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-mimetype.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-mimetype.Tpo -c -o obexd/src/obexd-mimetype.obj `if test -f 'obexd/src/mimetype.c'; then $(CYGPATH_W) 'obexd/src/mimetype.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/mimetype.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-mimetype.Tpo obexd/src/$(DEPDIR)/obexd-mimetype.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/mimetype.c' object='obexd/src/obexd-mimetype.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-mimetype.obj `if test -f 'obexd/src/mimetype.c'; then $(CYGPATH_W) 'obexd/src/mimetype.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/mimetype.c'; fi` obexd/src/obexd-service.o: obexd/src/service.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-service.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-service.Tpo -c -o obexd/src/obexd-service.o `test -f 'obexd/src/service.c' || echo '$(srcdir)/'`obexd/src/service.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-service.Tpo obexd/src/$(DEPDIR)/obexd-service.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/service.c' object='obexd/src/obexd-service.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-service.o `test -f 'obexd/src/service.c' || echo '$(srcdir)/'`obexd/src/service.c obexd/src/obexd-service.obj: obexd/src/service.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-service.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-service.Tpo -c -o obexd/src/obexd-service.obj `if test -f 'obexd/src/service.c'; then $(CYGPATH_W) 'obexd/src/service.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/service.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-service.Tpo obexd/src/$(DEPDIR)/obexd-service.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/service.c' object='obexd/src/obexd-service.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-service.obj `if test -f 'obexd/src/service.c'; then $(CYGPATH_W) 'obexd/src/service.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/service.c'; fi` obexd/src/obexd-transport.o: obexd/src/transport.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-transport.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-transport.Tpo -c -o obexd/src/obexd-transport.o `test -f 'obexd/src/transport.c' || echo '$(srcdir)/'`obexd/src/transport.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-transport.Tpo obexd/src/$(DEPDIR)/obexd-transport.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/transport.c' object='obexd/src/obexd-transport.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-transport.o `test -f 'obexd/src/transport.c' || echo '$(srcdir)/'`obexd/src/transport.c obexd/src/obexd-transport.obj: obexd/src/transport.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-transport.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-transport.Tpo -c -o obexd/src/obexd-transport.obj `if test -f 'obexd/src/transport.c'; then $(CYGPATH_W) 'obexd/src/transport.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/transport.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-transport.Tpo obexd/src/$(DEPDIR)/obexd-transport.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/transport.c' object='obexd/src/obexd-transport.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-transport.obj `if test -f 'obexd/src/transport.c'; then $(CYGPATH_W) 'obexd/src/transport.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/transport.c'; fi` obexd/src/obexd-server.o: obexd/src/server.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-server.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-server.Tpo -c -o obexd/src/obexd-server.o `test -f 'obexd/src/server.c' || echo '$(srcdir)/'`obexd/src/server.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-server.Tpo obexd/src/$(DEPDIR)/obexd-server.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/server.c' object='obexd/src/obexd-server.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-server.o `test -f 'obexd/src/server.c' || echo '$(srcdir)/'`obexd/src/server.c obexd/src/obexd-server.obj: obexd/src/server.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-server.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-server.Tpo -c -o obexd/src/obexd-server.obj `if test -f 'obexd/src/server.c'; then $(CYGPATH_W) 'obexd/src/server.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/server.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-server.Tpo obexd/src/$(DEPDIR)/obexd-server.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/server.c' object='obexd/src/obexd-server.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-server.obj `if test -f 'obexd/src/server.c'; then $(CYGPATH_W) 'obexd/src/server.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/server.c'; fi` obexd/client/obexd-manager.o: obexd/client/manager.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-manager.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-manager.Tpo -c -o obexd/client/obexd-manager.o `test -f 'obexd/client/manager.c' || echo '$(srcdir)/'`obexd/client/manager.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-manager.Tpo obexd/client/$(DEPDIR)/obexd-manager.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/manager.c' object='obexd/client/obexd-manager.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-manager.o `test -f 'obexd/client/manager.c' || echo '$(srcdir)/'`obexd/client/manager.c obexd/client/obexd-manager.obj: obexd/client/manager.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-manager.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-manager.Tpo -c -o obexd/client/obexd-manager.obj `if test -f 'obexd/client/manager.c'; then $(CYGPATH_W) 'obexd/client/manager.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/manager.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-manager.Tpo obexd/client/$(DEPDIR)/obexd-manager.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/manager.c' object='obexd/client/obexd-manager.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-manager.obj `if test -f 'obexd/client/manager.c'; then $(CYGPATH_W) 'obexd/client/manager.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/manager.c'; fi` obexd/client/obexd-session.o: obexd/client/session.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-session.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-session.Tpo -c -o obexd/client/obexd-session.o `test -f 'obexd/client/session.c' || echo '$(srcdir)/'`obexd/client/session.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-session.Tpo obexd/client/$(DEPDIR)/obexd-session.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/session.c' object='obexd/client/obexd-session.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-session.o `test -f 'obexd/client/session.c' || echo '$(srcdir)/'`obexd/client/session.c obexd/client/obexd-session.obj: obexd/client/session.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-session.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-session.Tpo -c -o obexd/client/obexd-session.obj `if test -f 'obexd/client/session.c'; then $(CYGPATH_W) 'obexd/client/session.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/session.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-session.Tpo obexd/client/$(DEPDIR)/obexd-session.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/session.c' object='obexd/client/obexd-session.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-session.obj `if test -f 'obexd/client/session.c'; then $(CYGPATH_W) 'obexd/client/session.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/session.c'; fi` obexd/client/obexd-bluetooth.o: obexd/client/bluetooth.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-bluetooth.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-bluetooth.Tpo -c -o obexd/client/obexd-bluetooth.o `test -f 'obexd/client/bluetooth.c' || echo '$(srcdir)/'`obexd/client/bluetooth.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-bluetooth.Tpo obexd/client/$(DEPDIR)/obexd-bluetooth.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/bluetooth.c' object='obexd/client/obexd-bluetooth.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-bluetooth.o `test -f 'obexd/client/bluetooth.c' || echo '$(srcdir)/'`obexd/client/bluetooth.c obexd/client/obexd-bluetooth.obj: obexd/client/bluetooth.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-bluetooth.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-bluetooth.Tpo -c -o obexd/client/obexd-bluetooth.obj `if test -f 'obexd/client/bluetooth.c'; then $(CYGPATH_W) 'obexd/client/bluetooth.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/bluetooth.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-bluetooth.Tpo obexd/client/$(DEPDIR)/obexd-bluetooth.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/bluetooth.c' object='obexd/client/obexd-bluetooth.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-bluetooth.obj `if test -f 'obexd/client/bluetooth.c'; then $(CYGPATH_W) 'obexd/client/bluetooth.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/bluetooth.c'; fi` obexd/client/obexd-sync.o: obexd/client/sync.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-sync.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-sync.Tpo -c -o obexd/client/obexd-sync.o `test -f 'obexd/client/sync.c' || echo '$(srcdir)/'`obexd/client/sync.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-sync.Tpo obexd/client/$(DEPDIR)/obexd-sync.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/sync.c' object='obexd/client/obexd-sync.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-sync.o `test -f 'obexd/client/sync.c' || echo '$(srcdir)/'`obexd/client/sync.c obexd/client/obexd-sync.obj: obexd/client/sync.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-sync.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-sync.Tpo -c -o obexd/client/obexd-sync.obj `if test -f 'obexd/client/sync.c'; then $(CYGPATH_W) 'obexd/client/sync.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/sync.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-sync.Tpo obexd/client/$(DEPDIR)/obexd-sync.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/sync.c' object='obexd/client/obexd-sync.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-sync.obj `if test -f 'obexd/client/sync.c'; then $(CYGPATH_W) 'obexd/client/sync.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/sync.c'; fi` obexd/client/obexd-pbap.o: obexd/client/pbap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-pbap.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-pbap.Tpo -c -o obexd/client/obexd-pbap.o `test -f 'obexd/client/pbap.c' || echo '$(srcdir)/'`obexd/client/pbap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-pbap.Tpo obexd/client/$(DEPDIR)/obexd-pbap.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/pbap.c' object='obexd/client/obexd-pbap.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-pbap.o `test -f 'obexd/client/pbap.c' || echo '$(srcdir)/'`obexd/client/pbap.c obexd/client/obexd-pbap.obj: obexd/client/pbap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-pbap.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-pbap.Tpo -c -o obexd/client/obexd-pbap.obj `if test -f 'obexd/client/pbap.c'; then $(CYGPATH_W) 'obexd/client/pbap.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/pbap.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-pbap.Tpo obexd/client/$(DEPDIR)/obexd-pbap.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/pbap.c' object='obexd/client/obexd-pbap.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-pbap.obj `if test -f 'obexd/client/pbap.c'; then $(CYGPATH_W) 'obexd/client/pbap.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/pbap.c'; fi` obexd/client/obexd-ftp.o: obexd/client/ftp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-ftp.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-ftp.Tpo -c -o obexd/client/obexd-ftp.o `test -f 'obexd/client/ftp.c' || echo '$(srcdir)/'`obexd/client/ftp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-ftp.Tpo obexd/client/$(DEPDIR)/obexd-ftp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/ftp.c' object='obexd/client/obexd-ftp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-ftp.o `test -f 'obexd/client/ftp.c' || echo '$(srcdir)/'`obexd/client/ftp.c obexd/client/obexd-ftp.obj: obexd/client/ftp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-ftp.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-ftp.Tpo -c -o obexd/client/obexd-ftp.obj `if test -f 'obexd/client/ftp.c'; then $(CYGPATH_W) 'obexd/client/ftp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/ftp.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-ftp.Tpo obexd/client/$(DEPDIR)/obexd-ftp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/ftp.c' object='obexd/client/obexd-ftp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-ftp.obj `if test -f 'obexd/client/ftp.c'; then $(CYGPATH_W) 'obexd/client/ftp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/ftp.c'; fi` obexd/client/obexd-opp.o: obexd/client/opp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-opp.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-opp.Tpo -c -o obexd/client/obexd-opp.o `test -f 'obexd/client/opp.c' || echo '$(srcdir)/'`obexd/client/opp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-opp.Tpo obexd/client/$(DEPDIR)/obexd-opp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/opp.c' object='obexd/client/obexd-opp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-opp.o `test -f 'obexd/client/opp.c' || echo '$(srcdir)/'`obexd/client/opp.c obexd/client/obexd-opp.obj: obexd/client/opp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-opp.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-opp.Tpo -c -o obexd/client/obexd-opp.obj `if test -f 'obexd/client/opp.c'; then $(CYGPATH_W) 'obexd/client/opp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/opp.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-opp.Tpo obexd/client/$(DEPDIR)/obexd-opp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/opp.c' object='obexd/client/obexd-opp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-opp.obj `if test -f 'obexd/client/opp.c'; then $(CYGPATH_W) 'obexd/client/opp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/opp.c'; fi` obexd/client/obexd-map.o: obexd/client/map.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-map.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-map.Tpo -c -o obexd/client/obexd-map.o `test -f 'obexd/client/map.c' || echo '$(srcdir)/'`obexd/client/map.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-map.Tpo obexd/client/$(DEPDIR)/obexd-map.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/map.c' object='obexd/client/obexd-map.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-map.o `test -f 'obexd/client/map.c' || echo '$(srcdir)/'`obexd/client/map.c obexd/client/obexd-map.obj: obexd/client/map.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-map.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-map.Tpo -c -o obexd/client/obexd-map.obj `if test -f 'obexd/client/map.c'; then $(CYGPATH_W) 'obexd/client/map.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/map.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-map.Tpo obexd/client/$(DEPDIR)/obexd-map.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/map.c' object='obexd/client/obexd-map.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-map.obj `if test -f 'obexd/client/map.c'; then $(CYGPATH_W) 'obexd/client/map.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/map.c'; fi` obexd/client/obexd-bip.o: obexd/client/bip.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-bip.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-bip.Tpo -c -o obexd/client/obexd-bip.o `test -f 'obexd/client/bip.c' || echo '$(srcdir)/'`obexd/client/bip.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-bip.Tpo obexd/client/$(DEPDIR)/obexd-bip.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/bip.c' object='obexd/client/obexd-bip.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-bip.o `test -f 'obexd/client/bip.c' || echo '$(srcdir)/'`obexd/client/bip.c obexd/client/obexd-bip.obj: obexd/client/bip.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-bip.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-bip.Tpo -c -o obexd/client/obexd-bip.obj `if test -f 'obexd/client/bip.c'; then $(CYGPATH_W) 'obexd/client/bip.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/bip.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-bip.Tpo obexd/client/$(DEPDIR)/obexd-bip.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/bip.c' object='obexd/client/obexd-bip.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-bip.obj `if test -f 'obexd/client/bip.c'; then $(CYGPATH_W) 'obexd/client/bip.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/bip.c'; fi` obexd/client/obexd-bip-common.o: obexd/client/bip-common.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-bip-common.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-bip-common.Tpo -c -o obexd/client/obexd-bip-common.o `test -f 'obexd/client/bip-common.c' || echo '$(srcdir)/'`obexd/client/bip-common.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-bip-common.Tpo obexd/client/$(DEPDIR)/obexd-bip-common.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/bip-common.c' object='obexd/client/obexd-bip-common.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-bip-common.o `test -f 'obexd/client/bip-common.c' || echo '$(srcdir)/'`obexd/client/bip-common.c obexd/client/obexd-bip-common.obj: obexd/client/bip-common.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-bip-common.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-bip-common.Tpo -c -o obexd/client/obexd-bip-common.obj `if test -f 'obexd/client/bip-common.c'; then $(CYGPATH_W) 'obexd/client/bip-common.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/bip-common.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-bip-common.Tpo obexd/client/$(DEPDIR)/obexd-bip-common.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/bip-common.c' object='obexd/client/obexd-bip-common.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-bip-common.obj `if test -f 'obexd/client/bip-common.c'; then $(CYGPATH_W) 'obexd/client/bip-common.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/bip-common.c'; fi` obexd/client/obexd-map-event.o: obexd/client/map-event.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-map-event.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-map-event.Tpo -c -o obexd/client/obexd-map-event.o `test -f 'obexd/client/map-event.c' || echo '$(srcdir)/'`obexd/client/map-event.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-map-event.Tpo obexd/client/$(DEPDIR)/obexd-map-event.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/map-event.c' object='obexd/client/obexd-map-event.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-map-event.o `test -f 'obexd/client/map-event.c' || echo '$(srcdir)/'`obexd/client/map-event.c obexd/client/obexd-map-event.obj: obexd/client/map-event.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-map-event.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-map-event.Tpo -c -o obexd/client/obexd-map-event.obj `if test -f 'obexd/client/map-event.c'; then $(CYGPATH_W) 'obexd/client/map-event.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/map-event.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-map-event.Tpo obexd/client/$(DEPDIR)/obexd-map-event.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/map-event.c' object='obexd/client/obexd-map-event.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-map-event.obj `if test -f 'obexd/client/map-event.c'; then $(CYGPATH_W) 'obexd/client/map-event.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/map-event.c'; fi` obexd/client/obexd-transfer.o: obexd/client/transfer.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-transfer.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-transfer.Tpo -c -o obexd/client/obexd-transfer.o `test -f 'obexd/client/transfer.c' || echo '$(srcdir)/'`obexd/client/transfer.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-transfer.Tpo obexd/client/$(DEPDIR)/obexd-transfer.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/transfer.c' object='obexd/client/obexd-transfer.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-transfer.o `test -f 'obexd/client/transfer.c' || echo '$(srcdir)/'`obexd/client/transfer.c obexd/client/obexd-transfer.obj: obexd/client/transfer.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-transfer.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-transfer.Tpo -c -o obexd/client/obexd-transfer.obj `if test -f 'obexd/client/transfer.c'; then $(CYGPATH_W) 'obexd/client/transfer.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/transfer.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-transfer.Tpo obexd/client/$(DEPDIR)/obexd-transfer.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/transfer.c' object='obexd/client/obexd-transfer.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-transfer.obj `if test -f 'obexd/client/transfer.c'; then $(CYGPATH_W) 'obexd/client/transfer.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/transfer.c'; fi` obexd/client/obexd-transport.o: obexd/client/transport.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-transport.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-transport.Tpo -c -o obexd/client/obexd-transport.o `test -f 'obexd/client/transport.c' || echo '$(srcdir)/'`obexd/client/transport.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-transport.Tpo obexd/client/$(DEPDIR)/obexd-transport.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/transport.c' object='obexd/client/obexd-transport.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-transport.o `test -f 'obexd/client/transport.c' || echo '$(srcdir)/'`obexd/client/transport.c obexd/client/obexd-transport.obj: obexd/client/transport.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-transport.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-transport.Tpo -c -o obexd/client/obexd-transport.obj `if test -f 'obexd/client/transport.c'; then $(CYGPATH_W) 'obexd/client/transport.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/transport.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-transport.Tpo obexd/client/$(DEPDIR)/obexd-transport.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/transport.c' object='obexd/client/obexd-transport.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-transport.obj `if test -f 'obexd/client/transport.c'; then $(CYGPATH_W) 'obexd/client/transport.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/transport.c'; fi` obexd/client/obexd-driver.o: obexd/client/driver.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-driver.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-driver.Tpo -c -o obexd/client/obexd-driver.o `test -f 'obexd/client/driver.c' || echo '$(srcdir)/'`obexd/client/driver.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-driver.Tpo obexd/client/$(DEPDIR)/obexd-driver.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/driver.c' object='obexd/client/obexd-driver.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-driver.o `test -f 'obexd/client/driver.c' || echo '$(srcdir)/'`obexd/client/driver.c obexd/client/obexd-driver.obj: obexd/client/driver.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-driver.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-driver.Tpo -c -o obexd/client/obexd-driver.obj `if test -f 'obexd/client/driver.c'; then $(CYGPATH_W) 'obexd/client/driver.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/driver.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-driver.Tpo obexd/client/$(DEPDIR)/obexd-driver.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/driver.c' object='obexd/client/obexd-driver.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-driver.obj `if test -f 'obexd/client/driver.c'; then $(CYGPATH_W) 'obexd/client/driver.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/driver.c'; fi` plugins/bluetoothd-hostname.o: plugins/hostname.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-hostname.o -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-hostname.Tpo -c -o plugins/bluetoothd-hostname.o `test -f 'plugins/hostname.c' || echo '$(srcdir)/'`plugins/hostname.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-hostname.Tpo plugins/$(DEPDIR)/bluetoothd-hostname.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/hostname.c' object='plugins/bluetoothd-hostname.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-hostname.o `test -f 'plugins/hostname.c' || echo '$(srcdir)/'`plugins/hostname.c plugins/bluetoothd-hostname.obj: plugins/hostname.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-hostname.obj -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-hostname.Tpo -c -o plugins/bluetoothd-hostname.obj `if test -f 'plugins/hostname.c'; then $(CYGPATH_W) 'plugins/hostname.c'; else $(CYGPATH_W) '$(srcdir)/plugins/hostname.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-hostname.Tpo plugins/$(DEPDIR)/bluetoothd-hostname.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/hostname.c' object='plugins/bluetoothd-hostname.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-hostname.obj `if test -f 'plugins/hostname.c'; then $(CYGPATH_W) 'plugins/hostname.c'; else $(CYGPATH_W) '$(srcdir)/plugins/hostname.c'; fi` plugins/bluetoothd-autopair.o: plugins/autopair.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-autopair.o -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-autopair.Tpo -c -o plugins/bluetoothd-autopair.o `test -f 'plugins/autopair.c' || echo '$(srcdir)/'`plugins/autopair.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-autopair.Tpo plugins/$(DEPDIR)/bluetoothd-autopair.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/autopair.c' object='plugins/bluetoothd-autopair.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-autopair.o `test -f 'plugins/autopair.c' || echo '$(srcdir)/'`plugins/autopair.c plugins/bluetoothd-autopair.obj: plugins/autopair.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-autopair.obj -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-autopair.Tpo -c -o plugins/bluetoothd-autopair.obj `if test -f 'plugins/autopair.c'; then $(CYGPATH_W) 'plugins/autopair.c'; else $(CYGPATH_W) '$(srcdir)/plugins/autopair.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-autopair.Tpo plugins/$(DEPDIR)/bluetoothd-autopair.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/autopair.c' object='plugins/bluetoothd-autopair.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-autopair.obj `if test -f 'plugins/autopair.c'; then $(CYGPATH_W) 'plugins/autopair.c'; else $(CYGPATH_W) '$(srcdir)/plugins/autopair.c'; fi` plugins/bluetoothd-policy.o: plugins/policy.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-policy.o -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-policy.Tpo -c -o plugins/bluetoothd-policy.o `test -f 'plugins/policy.c' || echo '$(srcdir)/'`plugins/policy.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-policy.Tpo plugins/$(DEPDIR)/bluetoothd-policy.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/policy.c' object='plugins/bluetoothd-policy.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-policy.o `test -f 'plugins/policy.c' || echo '$(srcdir)/'`plugins/policy.c plugins/bluetoothd-policy.obj: plugins/policy.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-policy.obj -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-policy.Tpo -c -o plugins/bluetoothd-policy.obj `if test -f 'plugins/policy.c'; then $(CYGPATH_W) 'plugins/policy.c'; else $(CYGPATH_W) '$(srcdir)/plugins/policy.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-policy.Tpo plugins/$(DEPDIR)/bluetoothd-policy.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/policy.c' object='plugins/bluetoothd-policy.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-policy.obj `if test -f 'plugins/policy.c'; then $(CYGPATH_W) 'plugins/policy.c'; else $(CYGPATH_W) '$(srcdir)/plugins/policy.c'; fi` profiles/audio/bluetoothd-media.o: profiles/audio/media.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-media.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-media.Tpo -c -o profiles/audio/bluetoothd-media.o `test -f 'profiles/audio/media.c' || echo '$(srcdir)/'`profiles/audio/media.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-media.Tpo profiles/audio/$(DEPDIR)/bluetoothd-media.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/media.c' object='profiles/audio/bluetoothd-media.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-media.o `test -f 'profiles/audio/media.c' || echo '$(srcdir)/'`profiles/audio/media.c profiles/audio/bluetoothd-media.obj: profiles/audio/media.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-media.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-media.Tpo -c -o profiles/audio/bluetoothd-media.obj `if test -f 'profiles/audio/media.c'; then $(CYGPATH_W) 'profiles/audio/media.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/media.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-media.Tpo profiles/audio/$(DEPDIR)/bluetoothd-media.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/media.c' object='profiles/audio/bluetoothd-media.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-media.obj `if test -f 'profiles/audio/media.c'; then $(CYGPATH_W) 'profiles/audio/media.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/media.c'; fi` profiles/audio/bluetoothd-transport.o: profiles/audio/transport.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-transport.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-transport.Tpo -c -o profiles/audio/bluetoothd-transport.o `test -f 'profiles/audio/transport.c' || echo '$(srcdir)/'`profiles/audio/transport.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-transport.Tpo profiles/audio/$(DEPDIR)/bluetoothd-transport.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/transport.c' object='profiles/audio/bluetoothd-transport.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-transport.o `test -f 'profiles/audio/transport.c' || echo '$(srcdir)/'`profiles/audio/transport.c profiles/audio/bluetoothd-transport.obj: profiles/audio/transport.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-transport.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-transport.Tpo -c -o profiles/audio/bluetoothd-transport.obj `if test -f 'profiles/audio/transport.c'; then $(CYGPATH_W) 'profiles/audio/transport.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/transport.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-transport.Tpo profiles/audio/$(DEPDIR)/bluetoothd-transport.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/transport.c' object='profiles/audio/bluetoothd-transport.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-transport.obj `if test -f 'profiles/audio/transport.c'; then $(CYGPATH_W) 'profiles/audio/transport.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/transport.c'; fi` profiles/audio/bluetoothd-player.o: profiles/audio/player.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-player.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-player.Tpo -c -o profiles/audio/bluetoothd-player.o `test -f 'profiles/audio/player.c' || echo '$(srcdir)/'`profiles/audio/player.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-player.Tpo profiles/audio/$(DEPDIR)/bluetoothd-player.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/player.c' object='profiles/audio/bluetoothd-player.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-player.o `test -f 'profiles/audio/player.c' || echo '$(srcdir)/'`profiles/audio/player.c profiles/audio/bluetoothd-player.obj: profiles/audio/player.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-player.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-player.Tpo -c -o profiles/audio/bluetoothd-player.obj `if test -f 'profiles/audio/player.c'; then $(CYGPATH_W) 'profiles/audio/player.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/player.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-player.Tpo profiles/audio/$(DEPDIR)/bluetoothd-player.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/player.c' object='profiles/audio/bluetoothd-player.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-player.obj `if test -f 'profiles/audio/player.c'; then $(CYGPATH_W) 'profiles/audio/player.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/player.c'; fi` plugins/bluetoothd-admin.o: plugins/admin.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-admin.o -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-admin.Tpo -c -o plugins/bluetoothd-admin.o `test -f 'plugins/admin.c' || echo '$(srcdir)/'`plugins/admin.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-admin.Tpo plugins/$(DEPDIR)/bluetoothd-admin.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/admin.c' object='plugins/bluetoothd-admin.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-admin.o `test -f 'plugins/admin.c' || echo '$(srcdir)/'`plugins/admin.c plugins/bluetoothd-admin.obj: plugins/admin.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-admin.obj -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-admin.Tpo -c -o plugins/bluetoothd-admin.obj `if test -f 'plugins/admin.c'; then $(CYGPATH_W) 'plugins/admin.c'; else $(CYGPATH_W) '$(srcdir)/plugins/admin.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-admin.Tpo plugins/$(DEPDIR)/bluetoothd-admin.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/admin.c' object='plugins/bluetoothd-admin.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-admin.obj `if test -f 'plugins/admin.c'; then $(CYGPATH_W) 'plugins/admin.c'; else $(CYGPATH_W) '$(srcdir)/plugins/admin.c'; fi` plugins/bluetoothd-neard.o: plugins/neard.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-neard.o -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-neard.Tpo -c -o plugins/bluetoothd-neard.o `test -f 'plugins/neard.c' || echo '$(srcdir)/'`plugins/neard.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-neard.Tpo plugins/$(DEPDIR)/bluetoothd-neard.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/neard.c' object='plugins/bluetoothd-neard.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-neard.o `test -f 'plugins/neard.c' || echo '$(srcdir)/'`plugins/neard.c plugins/bluetoothd-neard.obj: plugins/neard.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-neard.obj -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-neard.Tpo -c -o plugins/bluetoothd-neard.obj `if test -f 'plugins/neard.c'; then $(CYGPATH_W) 'plugins/neard.c'; else $(CYGPATH_W) '$(srcdir)/plugins/neard.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-neard.Tpo plugins/$(DEPDIR)/bluetoothd-neard.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/neard.c' object='plugins/bluetoothd-neard.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-neard.obj `if test -f 'plugins/neard.c'; then $(CYGPATH_W) 'plugins/neard.c'; else $(CYGPATH_W) '$(srcdir)/plugins/neard.c'; fi` profiles/audio/bluetoothd-source.o: profiles/audio/source.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-source.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-source.Tpo -c -o profiles/audio/bluetoothd-source.o `test -f 'profiles/audio/source.c' || echo '$(srcdir)/'`profiles/audio/source.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-source.Tpo profiles/audio/$(DEPDIR)/bluetoothd-source.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/source.c' object='profiles/audio/bluetoothd-source.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-source.o `test -f 'profiles/audio/source.c' || echo '$(srcdir)/'`profiles/audio/source.c profiles/audio/bluetoothd-source.obj: profiles/audio/source.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-source.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-source.Tpo -c -o profiles/audio/bluetoothd-source.obj `if test -f 'profiles/audio/source.c'; then $(CYGPATH_W) 'profiles/audio/source.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/source.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-source.Tpo profiles/audio/$(DEPDIR)/bluetoothd-source.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/source.c' object='profiles/audio/bluetoothd-source.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-source.obj `if test -f 'profiles/audio/source.c'; then $(CYGPATH_W) 'profiles/audio/source.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/source.c'; fi` profiles/audio/bluetoothd-sink.o: profiles/audio/sink.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-sink.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-sink.Tpo -c -o profiles/audio/bluetoothd-sink.o `test -f 'profiles/audio/sink.c' || echo '$(srcdir)/'`profiles/audio/sink.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-sink.Tpo profiles/audio/$(DEPDIR)/bluetoothd-sink.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/sink.c' object='profiles/audio/bluetoothd-sink.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-sink.o `test -f 'profiles/audio/sink.c' || echo '$(srcdir)/'`profiles/audio/sink.c profiles/audio/bluetoothd-sink.obj: profiles/audio/sink.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-sink.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-sink.Tpo -c -o profiles/audio/bluetoothd-sink.obj `if test -f 'profiles/audio/sink.c'; then $(CYGPATH_W) 'profiles/audio/sink.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/sink.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-sink.Tpo profiles/audio/$(DEPDIR)/bluetoothd-sink.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/sink.c' object='profiles/audio/bluetoothd-sink.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-sink.obj `if test -f 'profiles/audio/sink.c'; then $(CYGPATH_W) 'profiles/audio/sink.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/sink.c'; fi` profiles/audio/bluetoothd-a2dp.o: profiles/audio/a2dp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-a2dp.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Tpo -c -o profiles/audio/bluetoothd-a2dp.o `test -f 'profiles/audio/a2dp.c' || echo '$(srcdir)/'`profiles/audio/a2dp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/a2dp.c' object='profiles/audio/bluetoothd-a2dp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-a2dp.o `test -f 'profiles/audio/a2dp.c' || echo '$(srcdir)/'`profiles/audio/a2dp.c profiles/audio/bluetoothd-a2dp.obj: profiles/audio/a2dp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-a2dp.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Tpo -c -o profiles/audio/bluetoothd-a2dp.obj `if test -f 'profiles/audio/a2dp.c'; then $(CYGPATH_W) 'profiles/audio/a2dp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/a2dp.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/a2dp.c' object='profiles/audio/bluetoothd-a2dp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-a2dp.obj `if test -f 'profiles/audio/a2dp.c'; then $(CYGPATH_W) 'profiles/audio/a2dp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/a2dp.c'; fi` profiles/audio/bluetoothd-avdtp.o: profiles/audio/avdtp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-avdtp.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Tpo -c -o profiles/audio/bluetoothd-avdtp.o `test -f 'profiles/audio/avdtp.c' || echo '$(srcdir)/'`profiles/audio/avdtp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/avdtp.c' object='profiles/audio/bluetoothd-avdtp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-avdtp.o `test -f 'profiles/audio/avdtp.c' || echo '$(srcdir)/'`profiles/audio/avdtp.c profiles/audio/bluetoothd-avdtp.obj: profiles/audio/avdtp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-avdtp.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Tpo -c -o profiles/audio/bluetoothd-avdtp.obj `if test -f 'profiles/audio/avdtp.c'; then $(CYGPATH_W) 'profiles/audio/avdtp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/avdtp.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/avdtp.c' object='profiles/audio/bluetoothd-avdtp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-avdtp.obj `if test -f 'profiles/audio/avdtp.c'; then $(CYGPATH_W) 'profiles/audio/avdtp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/avdtp.c'; fi` profiles/audio/bluetoothd-control.o: profiles/audio/control.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-control.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-control.Tpo -c -o profiles/audio/bluetoothd-control.o `test -f 'profiles/audio/control.c' || echo '$(srcdir)/'`profiles/audio/control.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-control.Tpo profiles/audio/$(DEPDIR)/bluetoothd-control.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/control.c' object='profiles/audio/bluetoothd-control.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-control.o `test -f 'profiles/audio/control.c' || echo '$(srcdir)/'`profiles/audio/control.c profiles/audio/bluetoothd-control.obj: profiles/audio/control.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-control.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-control.Tpo -c -o profiles/audio/bluetoothd-control.obj `if test -f 'profiles/audio/control.c'; then $(CYGPATH_W) 'profiles/audio/control.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/control.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-control.Tpo profiles/audio/$(DEPDIR)/bluetoothd-control.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/control.c' object='profiles/audio/bluetoothd-control.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-control.obj `if test -f 'profiles/audio/control.c'; then $(CYGPATH_W) 'profiles/audio/control.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/control.c'; fi` profiles/audio/bluetoothd-avctp.o: profiles/audio/avctp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-avctp.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-avctp.Tpo -c -o profiles/audio/bluetoothd-avctp.o `test -f 'profiles/audio/avctp.c' || echo '$(srcdir)/'`profiles/audio/avctp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-avctp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-avctp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/avctp.c' object='profiles/audio/bluetoothd-avctp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-avctp.o `test -f 'profiles/audio/avctp.c' || echo '$(srcdir)/'`profiles/audio/avctp.c profiles/audio/bluetoothd-avctp.obj: profiles/audio/avctp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-avctp.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-avctp.Tpo -c -o profiles/audio/bluetoothd-avctp.obj `if test -f 'profiles/audio/avctp.c'; then $(CYGPATH_W) 'profiles/audio/avctp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/avctp.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-avctp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-avctp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/avctp.c' object='profiles/audio/bluetoothd-avctp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-avctp.obj `if test -f 'profiles/audio/avctp.c'; then $(CYGPATH_W) 'profiles/audio/avctp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/avctp.c'; fi` profiles/audio/bluetoothd-avrcp.o: profiles/audio/avrcp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-avrcp.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Tpo -c -o profiles/audio/bluetoothd-avrcp.o `test -f 'profiles/audio/avrcp.c' || echo '$(srcdir)/'`profiles/audio/avrcp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/avrcp.c' object='profiles/audio/bluetoothd-avrcp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-avrcp.o `test -f 'profiles/audio/avrcp.c' || echo '$(srcdir)/'`profiles/audio/avrcp.c profiles/audio/bluetoothd-avrcp.obj: profiles/audio/avrcp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-avrcp.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Tpo -c -o profiles/audio/bluetoothd-avrcp.obj `if test -f 'profiles/audio/avrcp.c'; then $(CYGPATH_W) 'profiles/audio/avrcp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/avrcp.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/avrcp.c' object='profiles/audio/bluetoothd-avrcp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-avrcp.obj `if test -f 'profiles/audio/avrcp.c'; then $(CYGPATH_W) 'profiles/audio/avrcp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/avrcp.c'; fi` profiles/audio/bluetoothd-avrcp-player.o: profiles/audio/avrcp-player.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-avrcp-player.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-avrcp-player.Tpo -c -o profiles/audio/bluetoothd-avrcp-player.o `test -f 'profiles/audio/avrcp-player.c' || echo '$(srcdir)/'`profiles/audio/avrcp-player.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-avrcp-player.Tpo profiles/audio/$(DEPDIR)/bluetoothd-avrcp-player.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/avrcp-player.c' object='profiles/audio/bluetoothd-avrcp-player.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-avrcp-player.o `test -f 'profiles/audio/avrcp-player.c' || echo '$(srcdir)/'`profiles/audio/avrcp-player.c profiles/audio/bluetoothd-avrcp-player.obj: profiles/audio/avrcp-player.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-avrcp-player.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-avrcp-player.Tpo -c -o profiles/audio/bluetoothd-avrcp-player.obj `if test -f 'profiles/audio/avrcp-player.c'; then $(CYGPATH_W) 'profiles/audio/avrcp-player.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/avrcp-player.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-avrcp-player.Tpo profiles/audio/$(DEPDIR)/bluetoothd-avrcp-player.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/avrcp-player.c' object='profiles/audio/bluetoothd-avrcp-player.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-avrcp-player.obj `if test -f 'profiles/audio/avrcp-player.c'; then $(CYGPATH_W) 'profiles/audio/avrcp-player.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/avrcp-player.c'; fi` profiles/network/bluetoothd-manager.o: profiles/network/manager.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-manager.o -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-manager.Tpo -c -o profiles/network/bluetoothd-manager.o `test -f 'profiles/network/manager.c' || echo '$(srcdir)/'`profiles/network/manager.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-manager.Tpo profiles/network/$(DEPDIR)/bluetoothd-manager.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/manager.c' object='profiles/network/bluetoothd-manager.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-manager.o `test -f 'profiles/network/manager.c' || echo '$(srcdir)/'`profiles/network/manager.c profiles/network/bluetoothd-manager.obj: profiles/network/manager.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-manager.obj -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-manager.Tpo -c -o profiles/network/bluetoothd-manager.obj `if test -f 'profiles/network/manager.c'; then $(CYGPATH_W) 'profiles/network/manager.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/manager.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-manager.Tpo profiles/network/$(DEPDIR)/bluetoothd-manager.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/manager.c' object='profiles/network/bluetoothd-manager.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-manager.obj `if test -f 'profiles/network/manager.c'; then $(CYGPATH_W) 'profiles/network/manager.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/manager.c'; fi` profiles/network/bluetoothd-bnep.o: profiles/network/bnep.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-bnep.o -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-bnep.Tpo -c -o profiles/network/bluetoothd-bnep.o `test -f 'profiles/network/bnep.c' || echo '$(srcdir)/'`profiles/network/bnep.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-bnep.Tpo profiles/network/$(DEPDIR)/bluetoothd-bnep.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/bnep.c' object='profiles/network/bluetoothd-bnep.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-bnep.o `test -f 'profiles/network/bnep.c' || echo '$(srcdir)/'`profiles/network/bnep.c profiles/network/bluetoothd-bnep.obj: profiles/network/bnep.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-bnep.obj -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-bnep.Tpo -c -o profiles/network/bluetoothd-bnep.obj `if test -f 'profiles/network/bnep.c'; then $(CYGPATH_W) 'profiles/network/bnep.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/bnep.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-bnep.Tpo profiles/network/$(DEPDIR)/bluetoothd-bnep.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/bnep.c' object='profiles/network/bluetoothd-bnep.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-bnep.obj `if test -f 'profiles/network/bnep.c'; then $(CYGPATH_W) 'profiles/network/bnep.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/bnep.c'; fi` profiles/network/bluetoothd-server.o: profiles/network/server.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-server.o -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-server.Tpo -c -o profiles/network/bluetoothd-server.o `test -f 'profiles/network/server.c' || echo '$(srcdir)/'`profiles/network/server.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-server.Tpo profiles/network/$(DEPDIR)/bluetoothd-server.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/server.c' object='profiles/network/bluetoothd-server.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-server.o `test -f 'profiles/network/server.c' || echo '$(srcdir)/'`profiles/network/server.c profiles/network/bluetoothd-server.obj: profiles/network/server.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-server.obj -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-server.Tpo -c -o profiles/network/bluetoothd-server.obj `if test -f 'profiles/network/server.c'; then $(CYGPATH_W) 'profiles/network/server.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/server.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-server.Tpo profiles/network/$(DEPDIR)/bluetoothd-server.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/server.c' object='profiles/network/bluetoothd-server.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-server.obj `if test -f 'profiles/network/server.c'; then $(CYGPATH_W) 'profiles/network/server.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/server.c'; fi` profiles/network/bluetoothd-connection.o: profiles/network/connection.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-connection.o -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-connection.Tpo -c -o profiles/network/bluetoothd-connection.o `test -f 'profiles/network/connection.c' || echo '$(srcdir)/'`profiles/network/connection.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-connection.Tpo profiles/network/$(DEPDIR)/bluetoothd-connection.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/connection.c' object='profiles/network/bluetoothd-connection.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-connection.o `test -f 'profiles/network/connection.c' || echo '$(srcdir)/'`profiles/network/connection.c profiles/network/bluetoothd-connection.obj: profiles/network/connection.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-connection.obj -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-connection.Tpo -c -o profiles/network/bluetoothd-connection.obj `if test -f 'profiles/network/connection.c'; then $(CYGPATH_W) 'profiles/network/connection.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/connection.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-connection.Tpo profiles/network/$(DEPDIR)/bluetoothd-connection.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/connection.c' object='profiles/network/bluetoothd-connection.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-connection.obj `if test -f 'profiles/network/connection.c'; then $(CYGPATH_W) 'profiles/network/connection.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/connection.c'; fi` profiles/input/bluetoothd-manager.o: profiles/input/manager.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-manager.o -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-manager.Tpo -c -o profiles/input/bluetoothd-manager.o `test -f 'profiles/input/manager.c' || echo '$(srcdir)/'`profiles/input/manager.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-manager.Tpo profiles/input/$(DEPDIR)/bluetoothd-manager.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/manager.c' object='profiles/input/bluetoothd-manager.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-manager.o `test -f 'profiles/input/manager.c' || echo '$(srcdir)/'`profiles/input/manager.c profiles/input/bluetoothd-manager.obj: profiles/input/manager.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-manager.obj -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-manager.Tpo -c -o profiles/input/bluetoothd-manager.obj `if test -f 'profiles/input/manager.c'; then $(CYGPATH_W) 'profiles/input/manager.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/manager.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-manager.Tpo profiles/input/$(DEPDIR)/bluetoothd-manager.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/manager.c' object='profiles/input/bluetoothd-manager.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-manager.obj `if test -f 'profiles/input/manager.c'; then $(CYGPATH_W) 'profiles/input/manager.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/manager.c'; fi` profiles/input/bluetoothd-server.o: profiles/input/server.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-server.o -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-server.Tpo -c -o profiles/input/bluetoothd-server.o `test -f 'profiles/input/server.c' || echo '$(srcdir)/'`profiles/input/server.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-server.Tpo profiles/input/$(DEPDIR)/bluetoothd-server.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/server.c' object='profiles/input/bluetoothd-server.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-server.o `test -f 'profiles/input/server.c' || echo '$(srcdir)/'`profiles/input/server.c profiles/input/bluetoothd-server.obj: profiles/input/server.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-server.obj -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-server.Tpo -c -o profiles/input/bluetoothd-server.obj `if test -f 'profiles/input/server.c'; then $(CYGPATH_W) 'profiles/input/server.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/server.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-server.Tpo profiles/input/$(DEPDIR)/bluetoothd-server.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/server.c' object='profiles/input/bluetoothd-server.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-server.obj `if test -f 'profiles/input/server.c'; then $(CYGPATH_W) 'profiles/input/server.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/server.c'; fi` profiles/input/bluetoothd-device.o: profiles/input/device.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-device.o -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-device.Tpo -c -o profiles/input/bluetoothd-device.o `test -f 'profiles/input/device.c' || echo '$(srcdir)/'`profiles/input/device.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-device.Tpo profiles/input/$(DEPDIR)/bluetoothd-device.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/device.c' object='profiles/input/bluetoothd-device.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-device.o `test -f 'profiles/input/device.c' || echo '$(srcdir)/'`profiles/input/device.c profiles/input/bluetoothd-device.obj: profiles/input/device.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-device.obj -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-device.Tpo -c -o profiles/input/bluetoothd-device.obj `if test -f 'profiles/input/device.c'; then $(CYGPATH_W) 'profiles/input/device.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/device.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-device.Tpo profiles/input/$(DEPDIR)/bluetoothd-device.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/device.c' object='profiles/input/bluetoothd-device.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-device.obj `if test -f 'profiles/input/device.c'; then $(CYGPATH_W) 'profiles/input/device.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/device.c'; fi` profiles/input/bluetoothd-hog.o: profiles/input/hog.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-hog.o -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-hog.Tpo -c -o profiles/input/bluetoothd-hog.o `test -f 'profiles/input/hog.c' || echo '$(srcdir)/'`profiles/input/hog.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-hog.Tpo profiles/input/$(DEPDIR)/bluetoothd-hog.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/hog.c' object='profiles/input/bluetoothd-hog.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-hog.o `test -f 'profiles/input/hog.c' || echo '$(srcdir)/'`profiles/input/hog.c profiles/input/bluetoothd-hog.obj: profiles/input/hog.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-hog.obj -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-hog.Tpo -c -o profiles/input/bluetoothd-hog.obj `if test -f 'profiles/input/hog.c'; then $(CYGPATH_W) 'profiles/input/hog.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/hog.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-hog.Tpo profiles/input/$(DEPDIR)/bluetoothd-hog.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/hog.c' object='profiles/input/bluetoothd-hog.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-hog.obj `if test -f 'profiles/input/hog.c'; then $(CYGPATH_W) 'profiles/input/hog.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/hog.c'; fi` profiles/input/bluetoothd-hog-lib.o: profiles/input/hog-lib.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-hog-lib.o -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Tpo -c -o profiles/input/bluetoothd-hog-lib.o `test -f 'profiles/input/hog-lib.c' || echo '$(srcdir)/'`profiles/input/hog-lib.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Tpo profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/hog-lib.c' object='profiles/input/bluetoothd-hog-lib.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-hog-lib.o `test -f 'profiles/input/hog-lib.c' || echo '$(srcdir)/'`profiles/input/hog-lib.c profiles/input/bluetoothd-hog-lib.obj: profiles/input/hog-lib.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-hog-lib.obj -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Tpo -c -o profiles/input/bluetoothd-hog-lib.obj `if test -f 'profiles/input/hog-lib.c'; then $(CYGPATH_W) 'profiles/input/hog-lib.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/hog-lib.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Tpo profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/hog-lib.c' object='profiles/input/bluetoothd-hog-lib.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-hog-lib.obj `if test -f 'profiles/input/hog-lib.c'; then $(CYGPATH_W) 'profiles/input/hog-lib.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/hog-lib.c'; fi` profiles/deviceinfo/bluetoothd-dis.o: profiles/deviceinfo/dis.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/deviceinfo/bluetoothd-dis.o -MD -MP -MF profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Tpo -c -o profiles/deviceinfo/bluetoothd-dis.o `test -f 'profiles/deviceinfo/dis.c' || echo '$(srcdir)/'`profiles/deviceinfo/dis.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Tpo profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/deviceinfo/dis.c' object='profiles/deviceinfo/bluetoothd-dis.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/deviceinfo/bluetoothd-dis.o `test -f 'profiles/deviceinfo/dis.c' || echo '$(srcdir)/'`profiles/deviceinfo/dis.c profiles/deviceinfo/bluetoothd-dis.obj: profiles/deviceinfo/dis.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/deviceinfo/bluetoothd-dis.obj -MD -MP -MF profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Tpo -c -o profiles/deviceinfo/bluetoothd-dis.obj `if test -f 'profiles/deviceinfo/dis.c'; then $(CYGPATH_W) 'profiles/deviceinfo/dis.c'; else $(CYGPATH_W) '$(srcdir)/profiles/deviceinfo/dis.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Tpo profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/deviceinfo/dis.c' object='profiles/deviceinfo/bluetoothd-dis.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/deviceinfo/bluetoothd-dis.obj `if test -f 'profiles/deviceinfo/dis.c'; then $(CYGPATH_W) 'profiles/deviceinfo/dis.c'; else $(CYGPATH_W) '$(srcdir)/profiles/deviceinfo/dis.c'; fi` profiles/battery/bluetoothd-bas.o: profiles/battery/bas.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/battery/bluetoothd-bas.o -MD -MP -MF profiles/battery/$(DEPDIR)/bluetoothd-bas.Tpo -c -o profiles/battery/bluetoothd-bas.o `test -f 'profiles/battery/bas.c' || echo '$(srcdir)/'`profiles/battery/bas.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/battery/$(DEPDIR)/bluetoothd-bas.Tpo profiles/battery/$(DEPDIR)/bluetoothd-bas.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/battery/bas.c' object='profiles/battery/bluetoothd-bas.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/battery/bluetoothd-bas.o `test -f 'profiles/battery/bas.c' || echo '$(srcdir)/'`profiles/battery/bas.c profiles/battery/bluetoothd-bas.obj: profiles/battery/bas.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/battery/bluetoothd-bas.obj -MD -MP -MF profiles/battery/$(DEPDIR)/bluetoothd-bas.Tpo -c -o profiles/battery/bluetoothd-bas.obj `if test -f 'profiles/battery/bas.c'; then $(CYGPATH_W) 'profiles/battery/bas.c'; else $(CYGPATH_W) '$(srcdir)/profiles/battery/bas.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/battery/$(DEPDIR)/bluetoothd-bas.Tpo profiles/battery/$(DEPDIR)/bluetoothd-bas.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/battery/bas.c' object='profiles/battery/bluetoothd-bas.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/battery/bluetoothd-bas.obj `if test -f 'profiles/battery/bas.c'; then $(CYGPATH_W) 'profiles/battery/bas.c'; else $(CYGPATH_W) '$(srcdir)/profiles/battery/bas.c'; fi` profiles/scanparam/bluetoothd-scpp.o: profiles/scanparam/scpp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/scanparam/bluetoothd-scpp.o -MD -MP -MF profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Tpo -c -o profiles/scanparam/bluetoothd-scpp.o `test -f 'profiles/scanparam/scpp.c' || echo '$(srcdir)/'`profiles/scanparam/scpp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Tpo profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/scanparam/scpp.c' object='profiles/scanparam/bluetoothd-scpp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/scanparam/bluetoothd-scpp.o `test -f 'profiles/scanparam/scpp.c' || echo '$(srcdir)/'`profiles/scanparam/scpp.c profiles/scanparam/bluetoothd-scpp.obj: profiles/scanparam/scpp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/scanparam/bluetoothd-scpp.obj -MD -MP -MF profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Tpo -c -o profiles/scanparam/bluetoothd-scpp.obj `if test -f 'profiles/scanparam/scpp.c'; then $(CYGPATH_W) 'profiles/scanparam/scpp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/scanparam/scpp.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Tpo profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/scanparam/scpp.c' object='profiles/scanparam/bluetoothd-scpp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/scanparam/bluetoothd-scpp.obj `if test -f 'profiles/scanparam/scpp.c'; then $(CYGPATH_W) 'profiles/scanparam/scpp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/scanparam/scpp.c'; fi` profiles/input/bluetoothd-suspend-none.o: profiles/input/suspend-none.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-suspend-none.o -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Tpo -c -o profiles/input/bluetoothd-suspend-none.o `test -f 'profiles/input/suspend-none.c' || echo '$(srcdir)/'`profiles/input/suspend-none.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Tpo profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/suspend-none.c' object='profiles/input/bluetoothd-suspend-none.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-suspend-none.o `test -f 'profiles/input/suspend-none.c' || echo '$(srcdir)/'`profiles/input/suspend-none.c profiles/input/bluetoothd-suspend-none.obj: profiles/input/suspend-none.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-suspend-none.obj -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Tpo -c -o profiles/input/bluetoothd-suspend-none.obj `if test -f 'profiles/input/suspend-none.c'; then $(CYGPATH_W) 'profiles/input/suspend-none.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/suspend-none.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Tpo profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/suspend-none.c' object='profiles/input/bluetoothd-suspend-none.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-suspend-none.obj `if test -f 'profiles/input/suspend-none.c'; then $(CYGPATH_W) 'profiles/input/suspend-none.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/suspend-none.c'; fi` profiles/gap/bluetoothd-gas.o: profiles/gap/gas.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/gap/bluetoothd-gas.o -MD -MP -MF profiles/gap/$(DEPDIR)/bluetoothd-gas.Tpo -c -o profiles/gap/bluetoothd-gas.o `test -f 'profiles/gap/gas.c' || echo '$(srcdir)/'`profiles/gap/gas.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/gap/$(DEPDIR)/bluetoothd-gas.Tpo profiles/gap/$(DEPDIR)/bluetoothd-gas.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/gap/gas.c' object='profiles/gap/bluetoothd-gas.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/gap/bluetoothd-gas.o `test -f 'profiles/gap/gas.c' || echo '$(srcdir)/'`profiles/gap/gas.c profiles/gap/bluetoothd-gas.obj: profiles/gap/gas.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/gap/bluetoothd-gas.obj -MD -MP -MF profiles/gap/$(DEPDIR)/bluetoothd-gas.Tpo -c -o profiles/gap/bluetoothd-gas.obj `if test -f 'profiles/gap/gas.c'; then $(CYGPATH_W) 'profiles/gap/gas.c'; else $(CYGPATH_W) '$(srcdir)/profiles/gap/gas.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/gap/$(DEPDIR)/bluetoothd-gas.Tpo profiles/gap/$(DEPDIR)/bluetoothd-gas.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/gap/gas.c' object='profiles/gap/bluetoothd-gas.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/gap/bluetoothd-gas.obj `if test -f 'profiles/gap/gas.c'; then $(CYGPATH_W) 'profiles/gap/gas.c'; else $(CYGPATH_W) '$(srcdir)/profiles/gap/gas.c'; fi` profiles/scanparam/bluetoothd-scan.o: profiles/scanparam/scan.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/scanparam/bluetoothd-scan.o -MD -MP -MF profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Tpo -c -o profiles/scanparam/bluetoothd-scan.o `test -f 'profiles/scanparam/scan.c' || echo '$(srcdir)/'`profiles/scanparam/scan.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Tpo profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/scanparam/scan.c' object='profiles/scanparam/bluetoothd-scan.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/scanparam/bluetoothd-scan.o `test -f 'profiles/scanparam/scan.c' || echo '$(srcdir)/'`profiles/scanparam/scan.c profiles/scanparam/bluetoothd-scan.obj: profiles/scanparam/scan.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/scanparam/bluetoothd-scan.obj -MD -MP -MF profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Tpo -c -o profiles/scanparam/bluetoothd-scan.obj `if test -f 'profiles/scanparam/scan.c'; then $(CYGPATH_W) 'profiles/scanparam/scan.c'; else $(CYGPATH_W) '$(srcdir)/profiles/scanparam/scan.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Tpo profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/scanparam/scan.c' object='profiles/scanparam/bluetoothd-scan.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/scanparam/bluetoothd-scan.obj `if test -f 'profiles/scanparam/scan.c'; then $(CYGPATH_W) 'profiles/scanparam/scan.c'; else $(CYGPATH_W) '$(srcdir)/profiles/scanparam/scan.c'; fi` profiles/deviceinfo/bluetoothd-deviceinfo.o: profiles/deviceinfo/deviceinfo.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/deviceinfo/bluetoothd-deviceinfo.o -MD -MP -MF profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Tpo -c -o profiles/deviceinfo/bluetoothd-deviceinfo.o `test -f 'profiles/deviceinfo/deviceinfo.c' || echo '$(srcdir)/'`profiles/deviceinfo/deviceinfo.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Tpo profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/deviceinfo/deviceinfo.c' object='profiles/deviceinfo/bluetoothd-deviceinfo.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/deviceinfo/bluetoothd-deviceinfo.o `test -f 'profiles/deviceinfo/deviceinfo.c' || echo '$(srcdir)/'`profiles/deviceinfo/deviceinfo.c profiles/deviceinfo/bluetoothd-deviceinfo.obj: profiles/deviceinfo/deviceinfo.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/deviceinfo/bluetoothd-deviceinfo.obj -MD -MP -MF profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Tpo -c -o profiles/deviceinfo/bluetoothd-deviceinfo.obj `if test -f 'profiles/deviceinfo/deviceinfo.c'; then $(CYGPATH_W) 'profiles/deviceinfo/deviceinfo.c'; else $(CYGPATH_W) '$(srcdir)/profiles/deviceinfo/deviceinfo.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Tpo profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/deviceinfo/deviceinfo.c' object='profiles/deviceinfo/bluetoothd-deviceinfo.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/deviceinfo/bluetoothd-deviceinfo.obj `if test -f 'profiles/deviceinfo/deviceinfo.c'; then $(CYGPATH_W) 'profiles/deviceinfo/deviceinfo.c'; else $(CYGPATH_W) '$(srcdir)/profiles/deviceinfo/deviceinfo.c'; fi` profiles/midi/bluetoothd-midi.o: profiles/midi/midi.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/midi/bluetoothd-midi.o -MD -MP -MF profiles/midi/$(DEPDIR)/bluetoothd-midi.Tpo -c -o profiles/midi/bluetoothd-midi.o `test -f 'profiles/midi/midi.c' || echo '$(srcdir)/'`profiles/midi/midi.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/midi/$(DEPDIR)/bluetoothd-midi.Tpo profiles/midi/$(DEPDIR)/bluetoothd-midi.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/midi/midi.c' object='profiles/midi/bluetoothd-midi.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/midi/bluetoothd-midi.o `test -f 'profiles/midi/midi.c' || echo '$(srcdir)/'`profiles/midi/midi.c profiles/midi/bluetoothd-midi.obj: profiles/midi/midi.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/midi/bluetoothd-midi.obj -MD -MP -MF profiles/midi/$(DEPDIR)/bluetoothd-midi.Tpo -c -o profiles/midi/bluetoothd-midi.obj `if test -f 'profiles/midi/midi.c'; then $(CYGPATH_W) 'profiles/midi/midi.c'; else $(CYGPATH_W) '$(srcdir)/profiles/midi/midi.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/midi/$(DEPDIR)/bluetoothd-midi.Tpo profiles/midi/$(DEPDIR)/bluetoothd-midi.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/midi/midi.c' object='profiles/midi/bluetoothd-midi.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/midi/bluetoothd-midi.obj `if test -f 'profiles/midi/midi.c'; then $(CYGPATH_W) 'profiles/midi/midi.c'; else $(CYGPATH_W) '$(srcdir)/profiles/midi/midi.c'; fi` profiles/midi/bluetoothd-libmidi.o: profiles/midi/libmidi.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/midi/bluetoothd-libmidi.o -MD -MP -MF profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Tpo -c -o profiles/midi/bluetoothd-libmidi.o `test -f 'profiles/midi/libmidi.c' || echo '$(srcdir)/'`profiles/midi/libmidi.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Tpo profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/midi/libmidi.c' object='profiles/midi/bluetoothd-libmidi.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/midi/bluetoothd-libmidi.o `test -f 'profiles/midi/libmidi.c' || echo '$(srcdir)/'`profiles/midi/libmidi.c profiles/midi/bluetoothd-libmidi.obj: profiles/midi/libmidi.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/midi/bluetoothd-libmidi.obj -MD -MP -MF profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Tpo -c -o profiles/midi/bluetoothd-libmidi.obj `if test -f 'profiles/midi/libmidi.c'; then $(CYGPATH_W) 'profiles/midi/libmidi.c'; else $(CYGPATH_W) '$(srcdir)/profiles/midi/libmidi.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Tpo profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/midi/libmidi.c' object='profiles/midi/bluetoothd-libmidi.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/midi/bluetoothd-libmidi.obj `if test -f 'profiles/midi/libmidi.c'; then $(CYGPATH_W) 'profiles/midi/libmidi.c'; else $(CYGPATH_W) '$(srcdir)/profiles/midi/libmidi.c'; fi` profiles/battery/bluetoothd-battery.o: profiles/battery/battery.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/battery/bluetoothd-battery.o -MD -MP -MF profiles/battery/$(DEPDIR)/bluetoothd-battery.Tpo -c -o profiles/battery/bluetoothd-battery.o `test -f 'profiles/battery/battery.c' || echo '$(srcdir)/'`profiles/battery/battery.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/battery/$(DEPDIR)/bluetoothd-battery.Tpo profiles/battery/$(DEPDIR)/bluetoothd-battery.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/battery/battery.c' object='profiles/battery/bluetoothd-battery.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/battery/bluetoothd-battery.o `test -f 'profiles/battery/battery.c' || echo '$(srcdir)/'`profiles/battery/battery.c profiles/battery/bluetoothd-battery.obj: profiles/battery/battery.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/battery/bluetoothd-battery.obj -MD -MP -MF profiles/battery/$(DEPDIR)/bluetoothd-battery.Tpo -c -o profiles/battery/bluetoothd-battery.obj `if test -f 'profiles/battery/battery.c'; then $(CYGPATH_W) 'profiles/battery/battery.c'; else $(CYGPATH_W) '$(srcdir)/profiles/battery/battery.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/battery/$(DEPDIR)/bluetoothd-battery.Tpo profiles/battery/$(DEPDIR)/bluetoothd-battery.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/battery/battery.c' object='profiles/battery/bluetoothd-battery.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/battery/bluetoothd-battery.obj `if test -f 'profiles/battery/battery.c'; then $(CYGPATH_W) 'profiles/battery/battery.c'; else $(CYGPATH_W) '$(srcdir)/profiles/battery/battery.c'; fi` profiles/ranging/bluetoothd-rap.o: profiles/ranging/rap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/ranging/bluetoothd-rap.o -MD -MP -MF profiles/ranging/$(DEPDIR)/bluetoothd-rap.Tpo -c -o profiles/ranging/bluetoothd-rap.o `test -f 'profiles/ranging/rap.c' || echo '$(srcdir)/'`profiles/ranging/rap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/ranging/$(DEPDIR)/bluetoothd-rap.Tpo profiles/ranging/$(DEPDIR)/bluetoothd-rap.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/ranging/rap.c' object='profiles/ranging/bluetoothd-rap.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/ranging/bluetoothd-rap.o `test -f 'profiles/ranging/rap.c' || echo '$(srcdir)/'`profiles/ranging/rap.c profiles/ranging/bluetoothd-rap.obj: profiles/ranging/rap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/ranging/bluetoothd-rap.obj -MD -MP -MF profiles/ranging/$(DEPDIR)/bluetoothd-rap.Tpo -c -o profiles/ranging/bluetoothd-rap.obj `if test -f 'profiles/ranging/rap.c'; then $(CYGPATH_W) 'profiles/ranging/rap.c'; else $(CYGPATH_W) '$(srcdir)/profiles/ranging/rap.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/ranging/$(DEPDIR)/bluetoothd-rap.Tpo profiles/ranging/$(DEPDIR)/bluetoothd-rap.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/ranging/rap.c' object='profiles/ranging/bluetoothd-rap.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/ranging/bluetoothd-rap.obj `if test -f 'profiles/ranging/rap.c'; then $(CYGPATH_W) 'profiles/ranging/rap.c'; else $(CYGPATH_W) '$(srcdir)/profiles/ranging/rap.c'; fi` profiles/ranging/bluetoothd-rap_hci.o: profiles/ranging/rap_hci.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/ranging/bluetoothd-rap_hci.o -MD -MP -MF profiles/ranging/$(DEPDIR)/bluetoothd-rap_hci.Tpo -c -o profiles/ranging/bluetoothd-rap_hci.o `test -f 'profiles/ranging/rap_hci.c' || echo '$(srcdir)/'`profiles/ranging/rap_hci.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/ranging/$(DEPDIR)/bluetoothd-rap_hci.Tpo profiles/ranging/$(DEPDIR)/bluetoothd-rap_hci.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/ranging/rap_hci.c' object='profiles/ranging/bluetoothd-rap_hci.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/ranging/bluetoothd-rap_hci.o `test -f 'profiles/ranging/rap_hci.c' || echo '$(srcdir)/'`profiles/ranging/rap_hci.c profiles/ranging/bluetoothd-rap_hci.obj: profiles/ranging/rap_hci.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/ranging/bluetoothd-rap_hci.obj -MD -MP -MF profiles/ranging/$(DEPDIR)/bluetoothd-rap_hci.Tpo -c -o profiles/ranging/bluetoothd-rap_hci.obj `if test -f 'profiles/ranging/rap_hci.c'; then $(CYGPATH_W) 'profiles/ranging/rap_hci.c'; else $(CYGPATH_W) '$(srcdir)/profiles/ranging/rap_hci.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/ranging/$(DEPDIR)/bluetoothd-rap_hci.Tpo profiles/ranging/$(DEPDIR)/bluetoothd-rap_hci.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/ranging/rap_hci.c' object='profiles/ranging/bluetoothd-rap_hci.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/ranging/bluetoothd-rap_hci.obj `if test -f 'profiles/ranging/rap_hci.c'; then $(CYGPATH_W) 'profiles/ranging/rap_hci.c'; else $(CYGPATH_W) '$(srcdir)/profiles/ranging/rap_hci.c'; fi` plugins/bluetoothd-sixaxis.o: plugins/sixaxis.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-sixaxis.o -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-sixaxis.Tpo -c -o plugins/bluetoothd-sixaxis.o `test -f 'plugins/sixaxis.c' || echo '$(srcdir)/'`plugins/sixaxis.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-sixaxis.Tpo plugins/$(DEPDIR)/bluetoothd-sixaxis.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/sixaxis.c' object='plugins/bluetoothd-sixaxis.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-sixaxis.o `test -f 'plugins/sixaxis.c' || echo '$(srcdir)/'`plugins/sixaxis.c plugins/bluetoothd-sixaxis.obj: plugins/sixaxis.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-sixaxis.obj -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-sixaxis.Tpo -c -o plugins/bluetoothd-sixaxis.obj `if test -f 'plugins/sixaxis.c'; then $(CYGPATH_W) 'plugins/sixaxis.c'; else $(CYGPATH_W) '$(srcdir)/plugins/sixaxis.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-sixaxis.Tpo plugins/$(DEPDIR)/bluetoothd-sixaxis.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/sixaxis.c' object='plugins/bluetoothd-sixaxis.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-sixaxis.obj `if test -f 'plugins/sixaxis.c'; then $(CYGPATH_W) 'plugins/sixaxis.c'; else $(CYGPATH_W) '$(srcdir)/plugins/sixaxis.c'; fi` profiles/audio/bluetoothd-bap.o: profiles/audio/bap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-bap.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-bap.Tpo -c -o profiles/audio/bluetoothd-bap.o `test -f 'profiles/audio/bap.c' || echo '$(srcdir)/'`profiles/audio/bap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-bap.Tpo profiles/audio/$(DEPDIR)/bluetoothd-bap.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/bap.c' object='profiles/audio/bluetoothd-bap.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-bap.o `test -f 'profiles/audio/bap.c' || echo '$(srcdir)/'`profiles/audio/bap.c profiles/audio/bluetoothd-bap.obj: profiles/audio/bap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-bap.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-bap.Tpo -c -o profiles/audio/bluetoothd-bap.obj `if test -f 'profiles/audio/bap.c'; then $(CYGPATH_W) 'profiles/audio/bap.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/bap.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-bap.Tpo profiles/audio/$(DEPDIR)/bluetoothd-bap.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/bap.c' object='profiles/audio/bluetoothd-bap.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-bap.obj `if test -f 'profiles/audio/bap.c'; then $(CYGPATH_W) 'profiles/audio/bap.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/bap.c'; fi` profiles/audio/bluetoothd-bass.o: profiles/audio/bass.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-bass.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-bass.Tpo -c -o profiles/audio/bluetoothd-bass.o `test -f 'profiles/audio/bass.c' || echo '$(srcdir)/'`profiles/audio/bass.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-bass.Tpo profiles/audio/$(DEPDIR)/bluetoothd-bass.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/bass.c' object='profiles/audio/bluetoothd-bass.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-bass.o `test -f 'profiles/audio/bass.c' || echo '$(srcdir)/'`profiles/audio/bass.c profiles/audio/bluetoothd-bass.obj: profiles/audio/bass.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-bass.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-bass.Tpo -c -o profiles/audio/bluetoothd-bass.obj `if test -f 'profiles/audio/bass.c'; then $(CYGPATH_W) 'profiles/audio/bass.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/bass.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-bass.Tpo profiles/audio/$(DEPDIR)/bluetoothd-bass.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/bass.c' object='profiles/audio/bluetoothd-bass.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-bass.obj `if test -f 'profiles/audio/bass.c'; then $(CYGPATH_W) 'profiles/audio/bass.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/bass.c'; fi` profiles/audio/bluetoothd-mcp.o: profiles/audio/mcp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-mcp.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-mcp.Tpo -c -o profiles/audio/bluetoothd-mcp.o `test -f 'profiles/audio/mcp.c' || echo '$(srcdir)/'`profiles/audio/mcp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-mcp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-mcp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/mcp.c' object='profiles/audio/bluetoothd-mcp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-mcp.o `test -f 'profiles/audio/mcp.c' || echo '$(srcdir)/'`profiles/audio/mcp.c profiles/audio/bluetoothd-mcp.obj: profiles/audio/mcp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-mcp.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-mcp.Tpo -c -o profiles/audio/bluetoothd-mcp.obj `if test -f 'profiles/audio/mcp.c'; then $(CYGPATH_W) 'profiles/audio/mcp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/mcp.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-mcp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-mcp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/mcp.c' object='profiles/audio/bluetoothd-mcp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-mcp.obj `if test -f 'profiles/audio/mcp.c'; then $(CYGPATH_W) 'profiles/audio/mcp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/mcp.c'; fi` profiles/audio/bluetoothd-vcp.o: profiles/audio/vcp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-vcp.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-vcp.Tpo -c -o profiles/audio/bluetoothd-vcp.o `test -f 'profiles/audio/vcp.c' || echo '$(srcdir)/'`profiles/audio/vcp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-vcp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-vcp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/vcp.c' object='profiles/audio/bluetoothd-vcp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-vcp.o `test -f 'profiles/audio/vcp.c' || echo '$(srcdir)/'`profiles/audio/vcp.c profiles/audio/bluetoothd-vcp.obj: profiles/audio/vcp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-vcp.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-vcp.Tpo -c -o profiles/audio/bluetoothd-vcp.obj `if test -f 'profiles/audio/vcp.c'; then $(CYGPATH_W) 'profiles/audio/vcp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/vcp.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-vcp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-vcp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/vcp.c' object='profiles/audio/bluetoothd-vcp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-vcp.obj `if test -f 'profiles/audio/vcp.c'; then $(CYGPATH_W) 'profiles/audio/vcp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/vcp.c'; fi` profiles/audio/bluetoothd-micp.o: profiles/audio/micp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-micp.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-micp.Tpo -c -o profiles/audio/bluetoothd-micp.o `test -f 'profiles/audio/micp.c' || echo '$(srcdir)/'`profiles/audio/micp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-micp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-micp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/micp.c' object='profiles/audio/bluetoothd-micp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-micp.o `test -f 'profiles/audio/micp.c' || echo '$(srcdir)/'`profiles/audio/micp.c profiles/audio/bluetoothd-micp.obj: profiles/audio/micp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-micp.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-micp.Tpo -c -o profiles/audio/bluetoothd-micp.obj `if test -f 'profiles/audio/micp.c'; then $(CYGPATH_W) 'profiles/audio/micp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/micp.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-micp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-micp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/micp.c' object='profiles/audio/bluetoothd-micp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-micp.obj `if test -f 'profiles/audio/micp.c'; then $(CYGPATH_W) 'profiles/audio/micp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/micp.c'; fi` profiles/audio/bluetoothd-ccp.o: profiles/audio/ccp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-ccp.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-ccp.Tpo -c -o profiles/audio/bluetoothd-ccp.o `test -f 'profiles/audio/ccp.c' || echo '$(srcdir)/'`profiles/audio/ccp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-ccp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-ccp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/ccp.c' object='profiles/audio/bluetoothd-ccp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-ccp.o `test -f 'profiles/audio/ccp.c' || echo '$(srcdir)/'`profiles/audio/ccp.c profiles/audio/bluetoothd-ccp.obj: profiles/audio/ccp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-ccp.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-ccp.Tpo -c -o profiles/audio/bluetoothd-ccp.obj `if test -f 'profiles/audio/ccp.c'; then $(CYGPATH_W) 'profiles/audio/ccp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/ccp.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-ccp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-ccp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/ccp.c' object='profiles/audio/bluetoothd-ccp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-ccp.obj `if test -f 'profiles/audio/ccp.c'; then $(CYGPATH_W) 'profiles/audio/ccp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/ccp.c'; fi` profiles/audio/bluetoothd-csip.o: profiles/audio/csip.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-csip.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-csip.Tpo -c -o profiles/audio/bluetoothd-csip.o `test -f 'profiles/audio/csip.c' || echo '$(srcdir)/'`profiles/audio/csip.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-csip.Tpo profiles/audio/$(DEPDIR)/bluetoothd-csip.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/csip.c' object='profiles/audio/bluetoothd-csip.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-csip.o `test -f 'profiles/audio/csip.c' || echo '$(srcdir)/'`profiles/audio/csip.c profiles/audio/bluetoothd-csip.obj: profiles/audio/csip.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-csip.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-csip.Tpo -c -o profiles/audio/bluetoothd-csip.obj `if test -f 'profiles/audio/csip.c'; then $(CYGPATH_W) 'profiles/audio/csip.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/csip.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-csip.Tpo profiles/audio/$(DEPDIR)/bluetoothd-csip.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/csip.c' object='profiles/audio/bluetoothd-csip.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-csip.obj `if test -f 'profiles/audio/csip.c'; then $(CYGPATH_W) 'profiles/audio/csip.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/csip.c'; fi` profiles/audio/bluetoothd-tmap.o: profiles/audio/tmap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-tmap.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-tmap.Tpo -c -o profiles/audio/bluetoothd-tmap.o `test -f 'profiles/audio/tmap.c' || echo '$(srcdir)/'`profiles/audio/tmap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-tmap.Tpo profiles/audio/$(DEPDIR)/bluetoothd-tmap.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/tmap.c' object='profiles/audio/bluetoothd-tmap.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-tmap.o `test -f 'profiles/audio/tmap.c' || echo '$(srcdir)/'`profiles/audio/tmap.c profiles/audio/bluetoothd-tmap.obj: profiles/audio/tmap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-tmap.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-tmap.Tpo -c -o profiles/audio/bluetoothd-tmap.obj `if test -f 'profiles/audio/tmap.c'; then $(CYGPATH_W) 'profiles/audio/tmap.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/tmap.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-tmap.Tpo profiles/audio/$(DEPDIR)/bluetoothd-tmap.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/tmap.c' object='profiles/audio/bluetoothd-tmap.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-tmap.obj `if test -f 'profiles/audio/tmap.c'; then $(CYGPATH_W) 'profiles/audio/tmap.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/tmap.c'; fi` profiles/audio/bluetoothd-gmap.o: profiles/audio/gmap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-gmap.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-gmap.Tpo -c -o profiles/audio/bluetoothd-gmap.o `test -f 'profiles/audio/gmap.c' || echo '$(srcdir)/'`profiles/audio/gmap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-gmap.Tpo profiles/audio/$(DEPDIR)/bluetoothd-gmap.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/gmap.c' object='profiles/audio/bluetoothd-gmap.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-gmap.o `test -f 'profiles/audio/gmap.c' || echo '$(srcdir)/'`profiles/audio/gmap.c profiles/audio/bluetoothd-gmap.obj: profiles/audio/gmap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-gmap.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-gmap.Tpo -c -o profiles/audio/bluetoothd-gmap.obj `if test -f 'profiles/audio/gmap.c'; then $(CYGPATH_W) 'profiles/audio/gmap.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/gmap.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-gmap.Tpo profiles/audio/$(DEPDIR)/bluetoothd-gmap.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/gmap.c' object='profiles/audio/bluetoothd-gmap.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-gmap.obj `if test -f 'profiles/audio/gmap.c'; then $(CYGPATH_W) 'profiles/audio/gmap.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/gmap.c'; fi` profiles/audio/bluetoothd-asha.o: profiles/audio/asha.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-asha.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-asha.Tpo -c -o profiles/audio/bluetoothd-asha.o `test -f 'profiles/audio/asha.c' || echo '$(srcdir)/'`profiles/audio/asha.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-asha.Tpo profiles/audio/$(DEPDIR)/bluetoothd-asha.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/asha.c' object='profiles/audio/bluetoothd-asha.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-asha.o `test -f 'profiles/audio/asha.c' || echo '$(srcdir)/'`profiles/audio/asha.c profiles/audio/bluetoothd-asha.obj: profiles/audio/asha.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-asha.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-asha.Tpo -c -o profiles/audio/bluetoothd-asha.obj `if test -f 'profiles/audio/asha.c'; then $(CYGPATH_W) 'profiles/audio/asha.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/asha.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-asha.Tpo profiles/audio/$(DEPDIR)/bluetoothd-asha.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/asha.c' object='profiles/audio/bluetoothd-asha.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-asha.obj `if test -f 'profiles/audio/asha.c'; then $(CYGPATH_W) 'profiles/audio/asha.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/asha.c'; fi` profiles/audio/bluetoothd-telephony.o: profiles/audio/telephony.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-telephony.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-telephony.Tpo -c -o profiles/audio/bluetoothd-telephony.o `test -f 'profiles/audio/telephony.c' || echo '$(srcdir)/'`profiles/audio/telephony.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-telephony.Tpo profiles/audio/$(DEPDIR)/bluetoothd-telephony.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/telephony.c' object='profiles/audio/bluetoothd-telephony.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-telephony.o `test -f 'profiles/audio/telephony.c' || echo '$(srcdir)/'`profiles/audio/telephony.c profiles/audio/bluetoothd-telephony.obj: profiles/audio/telephony.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-telephony.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-telephony.Tpo -c -o profiles/audio/bluetoothd-telephony.obj `if test -f 'profiles/audio/telephony.c'; then $(CYGPATH_W) 'profiles/audio/telephony.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/telephony.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-telephony.Tpo profiles/audio/$(DEPDIR)/bluetoothd-telephony.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/telephony.c' object='profiles/audio/bluetoothd-telephony.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-telephony.obj `if test -f 'profiles/audio/telephony.c'; then $(CYGPATH_W) 'profiles/audio/telephony.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/telephony.c'; fi` profiles/audio/bluetoothd-hfp-hf.o: profiles/audio/hfp-hf.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-hfp-hf.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-hfp-hf.Tpo -c -o profiles/audio/bluetoothd-hfp-hf.o `test -f 'profiles/audio/hfp-hf.c' || echo '$(srcdir)/'`profiles/audio/hfp-hf.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-hfp-hf.Tpo profiles/audio/$(DEPDIR)/bluetoothd-hfp-hf.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/hfp-hf.c' object='profiles/audio/bluetoothd-hfp-hf.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-hfp-hf.o `test -f 'profiles/audio/hfp-hf.c' || echo '$(srcdir)/'`profiles/audio/hfp-hf.c profiles/audio/bluetoothd-hfp-hf.obj: profiles/audio/hfp-hf.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-hfp-hf.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-hfp-hf.Tpo -c -o profiles/audio/bluetoothd-hfp-hf.obj `if test -f 'profiles/audio/hfp-hf.c'; then $(CYGPATH_W) 'profiles/audio/hfp-hf.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/hfp-hf.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-hfp-hf.Tpo profiles/audio/$(DEPDIR)/bluetoothd-hfp-hf.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/hfp-hf.c' object='profiles/audio/bluetoothd-hfp-hf.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-hfp-hf.obj `if test -f 'profiles/audio/hfp-hf.c'; then $(CYGPATH_W) 'profiles/audio/hfp-hf.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/hfp-hf.c'; fi` attrib/bluetoothd-att.o: attrib/att.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT attrib/bluetoothd-att.o -MD -MP -MF attrib/$(DEPDIR)/bluetoothd-att.Tpo -c -o attrib/bluetoothd-att.o `test -f 'attrib/att.c' || echo '$(srcdir)/'`attrib/att.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) attrib/$(DEPDIR)/bluetoothd-att.Tpo attrib/$(DEPDIR)/bluetoothd-att.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='attrib/att.c' object='attrib/bluetoothd-att.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o attrib/bluetoothd-att.o `test -f 'attrib/att.c' || echo '$(srcdir)/'`attrib/att.c attrib/bluetoothd-att.obj: attrib/att.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT attrib/bluetoothd-att.obj -MD -MP -MF attrib/$(DEPDIR)/bluetoothd-att.Tpo -c -o attrib/bluetoothd-att.obj `if test -f 'attrib/att.c'; then $(CYGPATH_W) 'attrib/att.c'; else $(CYGPATH_W) '$(srcdir)/attrib/att.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) attrib/$(DEPDIR)/bluetoothd-att.Tpo attrib/$(DEPDIR)/bluetoothd-att.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='attrib/att.c' object='attrib/bluetoothd-att.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o attrib/bluetoothd-att.obj `if test -f 'attrib/att.c'; then $(CYGPATH_W) 'attrib/att.c'; else $(CYGPATH_W) '$(srcdir)/attrib/att.c'; fi` attrib/bluetoothd-gatt.o: attrib/gatt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT attrib/bluetoothd-gatt.o -MD -MP -MF attrib/$(DEPDIR)/bluetoothd-gatt.Tpo -c -o attrib/bluetoothd-gatt.o `test -f 'attrib/gatt.c' || echo '$(srcdir)/'`attrib/gatt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) attrib/$(DEPDIR)/bluetoothd-gatt.Tpo attrib/$(DEPDIR)/bluetoothd-gatt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='attrib/gatt.c' object='attrib/bluetoothd-gatt.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o attrib/bluetoothd-gatt.o `test -f 'attrib/gatt.c' || echo '$(srcdir)/'`attrib/gatt.c attrib/bluetoothd-gatt.obj: attrib/gatt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT attrib/bluetoothd-gatt.obj -MD -MP -MF attrib/$(DEPDIR)/bluetoothd-gatt.Tpo -c -o attrib/bluetoothd-gatt.obj `if test -f 'attrib/gatt.c'; then $(CYGPATH_W) 'attrib/gatt.c'; else $(CYGPATH_W) '$(srcdir)/attrib/gatt.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) attrib/$(DEPDIR)/bluetoothd-gatt.Tpo attrib/$(DEPDIR)/bluetoothd-gatt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='attrib/gatt.c' object='attrib/bluetoothd-gatt.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o attrib/bluetoothd-gatt.obj `if test -f 'attrib/gatt.c'; then $(CYGPATH_W) 'attrib/gatt.c'; else $(CYGPATH_W) '$(srcdir)/attrib/gatt.c'; fi` attrib/bluetoothd-gattrib.o: attrib/gattrib.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT attrib/bluetoothd-gattrib.o -MD -MP -MF attrib/$(DEPDIR)/bluetoothd-gattrib.Tpo -c -o attrib/bluetoothd-gattrib.o `test -f 'attrib/gattrib.c' || echo '$(srcdir)/'`attrib/gattrib.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) attrib/$(DEPDIR)/bluetoothd-gattrib.Tpo attrib/$(DEPDIR)/bluetoothd-gattrib.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='attrib/gattrib.c' object='attrib/bluetoothd-gattrib.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o attrib/bluetoothd-gattrib.o `test -f 'attrib/gattrib.c' || echo '$(srcdir)/'`attrib/gattrib.c attrib/bluetoothd-gattrib.obj: attrib/gattrib.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT attrib/bluetoothd-gattrib.obj -MD -MP -MF attrib/$(DEPDIR)/bluetoothd-gattrib.Tpo -c -o attrib/bluetoothd-gattrib.obj `if test -f 'attrib/gattrib.c'; then $(CYGPATH_W) 'attrib/gattrib.c'; else $(CYGPATH_W) '$(srcdir)/attrib/gattrib.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) attrib/$(DEPDIR)/bluetoothd-gattrib.Tpo attrib/$(DEPDIR)/bluetoothd-gattrib.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='attrib/gattrib.c' object='attrib/bluetoothd-gattrib.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o attrib/bluetoothd-gattrib.obj `if test -f 'attrib/gattrib.c'; then $(CYGPATH_W) 'attrib/gattrib.c'; else $(CYGPATH_W) '$(srcdir)/attrib/gattrib.c'; fi` btio/bluetoothd-btio.o: btio/btio.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT btio/bluetoothd-btio.o -MD -MP -MF btio/$(DEPDIR)/bluetoothd-btio.Tpo -c -o btio/bluetoothd-btio.o `test -f 'btio/btio.c' || echo '$(srcdir)/'`btio/btio.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) btio/$(DEPDIR)/bluetoothd-btio.Tpo btio/$(DEPDIR)/bluetoothd-btio.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='btio/btio.c' object='btio/bluetoothd-btio.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o btio/bluetoothd-btio.o `test -f 'btio/btio.c' || echo '$(srcdir)/'`btio/btio.c btio/bluetoothd-btio.obj: btio/btio.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT btio/bluetoothd-btio.obj -MD -MP -MF btio/$(DEPDIR)/bluetoothd-btio.Tpo -c -o btio/bluetoothd-btio.obj `if test -f 'btio/btio.c'; then $(CYGPATH_W) 'btio/btio.c'; else $(CYGPATH_W) '$(srcdir)/btio/btio.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) btio/$(DEPDIR)/bluetoothd-btio.Tpo btio/$(DEPDIR)/bluetoothd-btio.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='btio/btio.c' object='btio/bluetoothd-btio.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o btio/bluetoothd-btio.obj `if test -f 'btio/btio.c'; then $(CYGPATH_W) 'btio/btio.c'; else $(CYGPATH_W) '$(srcdir)/btio/btio.c'; fi` src/bluetoothd-log.o: src/log.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-log.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-log.Tpo -c -o src/bluetoothd-log.o `test -f 'src/log.c' || echo '$(srcdir)/'`src/log.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-log.Tpo src/$(DEPDIR)/bluetoothd-log.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/log.c' object='src/bluetoothd-log.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-log.o `test -f 'src/log.c' || echo '$(srcdir)/'`src/log.c src/bluetoothd-log.obj: src/log.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-log.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-log.Tpo -c -o src/bluetoothd-log.obj `if test -f 'src/log.c'; then $(CYGPATH_W) 'src/log.c'; else $(CYGPATH_W) '$(srcdir)/src/log.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-log.Tpo src/$(DEPDIR)/bluetoothd-log.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/log.c' object='src/bluetoothd-log.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-log.obj `if test -f 'src/log.c'; then $(CYGPATH_W) 'src/log.c'; else $(CYGPATH_W) '$(srcdir)/src/log.c'; fi` src/bluetoothd-backtrace.o: src/backtrace.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-backtrace.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-backtrace.Tpo -c -o src/bluetoothd-backtrace.o `test -f 'src/backtrace.c' || echo '$(srcdir)/'`src/backtrace.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-backtrace.Tpo src/$(DEPDIR)/bluetoothd-backtrace.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/backtrace.c' object='src/bluetoothd-backtrace.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-backtrace.o `test -f 'src/backtrace.c' || echo '$(srcdir)/'`src/backtrace.c src/bluetoothd-backtrace.obj: src/backtrace.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-backtrace.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-backtrace.Tpo -c -o src/bluetoothd-backtrace.obj `if test -f 'src/backtrace.c'; then $(CYGPATH_W) 'src/backtrace.c'; else $(CYGPATH_W) '$(srcdir)/src/backtrace.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-backtrace.Tpo src/$(DEPDIR)/bluetoothd-backtrace.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/backtrace.c' object='src/bluetoothd-backtrace.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-backtrace.obj `if test -f 'src/backtrace.c'; then $(CYGPATH_W) 'src/backtrace.c'; else $(CYGPATH_W) '$(srcdir)/src/backtrace.c'; fi` src/bluetoothd-rfkill.o: src/rfkill.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-rfkill.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-rfkill.Tpo -c -o src/bluetoothd-rfkill.o `test -f 'src/rfkill.c' || echo '$(srcdir)/'`src/rfkill.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-rfkill.Tpo src/$(DEPDIR)/bluetoothd-rfkill.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/rfkill.c' object='src/bluetoothd-rfkill.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-rfkill.o `test -f 'src/rfkill.c' || echo '$(srcdir)/'`src/rfkill.c src/bluetoothd-rfkill.obj: src/rfkill.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-rfkill.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-rfkill.Tpo -c -o src/bluetoothd-rfkill.obj `if test -f 'src/rfkill.c'; then $(CYGPATH_W) 'src/rfkill.c'; else $(CYGPATH_W) '$(srcdir)/src/rfkill.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-rfkill.Tpo src/$(DEPDIR)/bluetoothd-rfkill.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/rfkill.c' object='src/bluetoothd-rfkill.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-rfkill.obj `if test -f 'src/rfkill.c'; then $(CYGPATH_W) 'src/rfkill.c'; else $(CYGPATH_W) '$(srcdir)/src/rfkill.c'; fi` src/bluetoothd-sdpd-server.o: src/sdpd-server.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-server.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-server.Tpo -c -o src/bluetoothd-sdpd-server.o `test -f 'src/sdpd-server.c' || echo '$(srcdir)/'`src/sdpd-server.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-server.Tpo src/$(DEPDIR)/bluetoothd-sdpd-server.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-server.c' object='src/bluetoothd-sdpd-server.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-server.o `test -f 'src/sdpd-server.c' || echo '$(srcdir)/'`src/sdpd-server.c src/bluetoothd-sdpd-server.obj: src/sdpd-server.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-server.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-server.Tpo -c -o src/bluetoothd-sdpd-server.obj `if test -f 'src/sdpd-server.c'; then $(CYGPATH_W) 'src/sdpd-server.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-server.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-server.Tpo src/$(DEPDIR)/bluetoothd-sdpd-server.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-server.c' object='src/bluetoothd-sdpd-server.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-server.obj `if test -f 'src/sdpd-server.c'; then $(CYGPATH_W) 'src/sdpd-server.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-server.c'; fi` src/bluetoothd-sdpd-request.o: src/sdpd-request.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-request.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-request.Tpo -c -o src/bluetoothd-sdpd-request.o `test -f 'src/sdpd-request.c' || echo '$(srcdir)/'`src/sdpd-request.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-request.Tpo src/$(DEPDIR)/bluetoothd-sdpd-request.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-request.c' object='src/bluetoothd-sdpd-request.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-request.o `test -f 'src/sdpd-request.c' || echo '$(srcdir)/'`src/sdpd-request.c src/bluetoothd-sdpd-request.obj: src/sdpd-request.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-request.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-request.Tpo -c -o src/bluetoothd-sdpd-request.obj `if test -f 'src/sdpd-request.c'; then $(CYGPATH_W) 'src/sdpd-request.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-request.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-request.Tpo src/$(DEPDIR)/bluetoothd-sdpd-request.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-request.c' object='src/bluetoothd-sdpd-request.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-request.obj `if test -f 'src/sdpd-request.c'; then $(CYGPATH_W) 'src/sdpd-request.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-request.c'; fi` src/bluetoothd-sdpd-service.o: src/sdpd-service.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-service.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-service.Tpo -c -o src/bluetoothd-sdpd-service.o `test -f 'src/sdpd-service.c' || echo '$(srcdir)/'`src/sdpd-service.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-service.Tpo src/$(DEPDIR)/bluetoothd-sdpd-service.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-service.c' object='src/bluetoothd-sdpd-service.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-service.o `test -f 'src/sdpd-service.c' || echo '$(srcdir)/'`src/sdpd-service.c src/bluetoothd-sdpd-service.obj: src/sdpd-service.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-service.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-service.Tpo -c -o src/bluetoothd-sdpd-service.obj `if test -f 'src/sdpd-service.c'; then $(CYGPATH_W) 'src/sdpd-service.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-service.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-service.Tpo src/$(DEPDIR)/bluetoothd-sdpd-service.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-service.c' object='src/bluetoothd-sdpd-service.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-service.obj `if test -f 'src/sdpd-service.c'; then $(CYGPATH_W) 'src/sdpd-service.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-service.c'; fi` src/bluetoothd-sdpd-database.o: src/sdpd-database.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-database.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-database.Tpo -c -o src/bluetoothd-sdpd-database.o `test -f 'src/sdpd-database.c' || echo '$(srcdir)/'`src/sdpd-database.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-database.Tpo src/$(DEPDIR)/bluetoothd-sdpd-database.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-database.c' object='src/bluetoothd-sdpd-database.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-database.o `test -f 'src/sdpd-database.c' || echo '$(srcdir)/'`src/sdpd-database.c src/bluetoothd-sdpd-database.obj: src/sdpd-database.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-database.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-database.Tpo -c -o src/bluetoothd-sdpd-database.obj `if test -f 'src/sdpd-database.c'; then $(CYGPATH_W) 'src/sdpd-database.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-database.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-database.Tpo src/$(DEPDIR)/bluetoothd-sdpd-database.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-database.c' object='src/bluetoothd-sdpd-database.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-database.obj `if test -f 'src/sdpd-database.c'; then $(CYGPATH_W) 'src/sdpd-database.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-database.c'; fi` src/bluetoothd-gatt-database.o: src/gatt-database.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-gatt-database.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-gatt-database.Tpo -c -o src/bluetoothd-gatt-database.o `test -f 'src/gatt-database.c' || echo '$(srcdir)/'`src/gatt-database.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-gatt-database.Tpo src/$(DEPDIR)/bluetoothd-gatt-database.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/gatt-database.c' object='src/bluetoothd-gatt-database.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-gatt-database.o `test -f 'src/gatt-database.c' || echo '$(srcdir)/'`src/gatt-database.c src/bluetoothd-gatt-database.obj: src/gatt-database.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-gatt-database.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-gatt-database.Tpo -c -o src/bluetoothd-gatt-database.obj `if test -f 'src/gatt-database.c'; then $(CYGPATH_W) 'src/gatt-database.c'; else $(CYGPATH_W) '$(srcdir)/src/gatt-database.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-gatt-database.Tpo src/$(DEPDIR)/bluetoothd-gatt-database.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/gatt-database.c' object='src/bluetoothd-gatt-database.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-gatt-database.obj `if test -f 'src/gatt-database.c'; then $(CYGPATH_W) 'src/gatt-database.c'; else $(CYGPATH_W) '$(srcdir)/src/gatt-database.c'; fi` src/bluetoothd-sdp-xml.o: src/sdp-xml.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdp-xml.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdp-xml.Tpo -c -o src/bluetoothd-sdp-xml.o `test -f 'src/sdp-xml.c' || echo '$(srcdir)/'`src/sdp-xml.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdp-xml.Tpo src/$(DEPDIR)/bluetoothd-sdp-xml.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdp-xml.c' object='src/bluetoothd-sdp-xml.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdp-xml.o `test -f 'src/sdp-xml.c' || echo '$(srcdir)/'`src/sdp-xml.c src/bluetoothd-sdp-xml.obj: src/sdp-xml.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdp-xml.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdp-xml.Tpo -c -o src/bluetoothd-sdp-xml.obj `if test -f 'src/sdp-xml.c'; then $(CYGPATH_W) 'src/sdp-xml.c'; else $(CYGPATH_W) '$(srcdir)/src/sdp-xml.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdp-xml.Tpo src/$(DEPDIR)/bluetoothd-sdp-xml.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdp-xml.c' object='src/bluetoothd-sdp-xml.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdp-xml.obj `if test -f 'src/sdp-xml.c'; then $(CYGPATH_W) 'src/sdp-xml.c'; else $(CYGPATH_W) '$(srcdir)/src/sdp-xml.c'; fi` src/bluetoothd-sdp-client.o: src/sdp-client.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdp-client.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdp-client.Tpo -c -o src/bluetoothd-sdp-client.o `test -f 'src/sdp-client.c' || echo '$(srcdir)/'`src/sdp-client.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdp-client.Tpo src/$(DEPDIR)/bluetoothd-sdp-client.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdp-client.c' object='src/bluetoothd-sdp-client.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdp-client.o `test -f 'src/sdp-client.c' || echo '$(srcdir)/'`src/sdp-client.c src/bluetoothd-sdp-client.obj: src/sdp-client.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdp-client.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdp-client.Tpo -c -o src/bluetoothd-sdp-client.obj `if test -f 'src/sdp-client.c'; then $(CYGPATH_W) 'src/sdp-client.c'; else $(CYGPATH_W) '$(srcdir)/src/sdp-client.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdp-client.Tpo src/$(DEPDIR)/bluetoothd-sdp-client.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdp-client.c' object='src/bluetoothd-sdp-client.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdp-client.obj `if test -f 'src/sdp-client.c'; then $(CYGPATH_W) 'src/sdp-client.c'; else $(CYGPATH_W) '$(srcdir)/src/sdp-client.c'; fi` src/bluetoothd-textfile.o: src/textfile.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-textfile.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-textfile.Tpo -c -o src/bluetoothd-textfile.o `test -f 'src/textfile.c' || echo '$(srcdir)/'`src/textfile.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-textfile.Tpo src/$(DEPDIR)/bluetoothd-textfile.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/textfile.c' object='src/bluetoothd-textfile.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-textfile.o `test -f 'src/textfile.c' || echo '$(srcdir)/'`src/textfile.c src/bluetoothd-textfile.obj: src/textfile.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-textfile.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-textfile.Tpo -c -o src/bluetoothd-textfile.obj `if test -f 'src/textfile.c'; then $(CYGPATH_W) 'src/textfile.c'; else $(CYGPATH_W) '$(srcdir)/src/textfile.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-textfile.Tpo src/$(DEPDIR)/bluetoothd-textfile.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/textfile.c' object='src/bluetoothd-textfile.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-textfile.obj `if test -f 'src/textfile.c'; then $(CYGPATH_W) 'src/textfile.c'; else $(CYGPATH_W) '$(srcdir)/src/textfile.c'; fi` src/bluetoothd-uuid-helper.o: src/uuid-helper.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-uuid-helper.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-uuid-helper.Tpo -c -o src/bluetoothd-uuid-helper.o `test -f 'src/uuid-helper.c' || echo '$(srcdir)/'`src/uuid-helper.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-uuid-helper.Tpo src/$(DEPDIR)/bluetoothd-uuid-helper.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/uuid-helper.c' object='src/bluetoothd-uuid-helper.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-uuid-helper.o `test -f 'src/uuid-helper.c' || echo '$(srcdir)/'`src/uuid-helper.c src/bluetoothd-uuid-helper.obj: src/uuid-helper.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-uuid-helper.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-uuid-helper.Tpo -c -o src/bluetoothd-uuid-helper.obj `if test -f 'src/uuid-helper.c'; then $(CYGPATH_W) 'src/uuid-helper.c'; else $(CYGPATH_W) '$(srcdir)/src/uuid-helper.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-uuid-helper.Tpo src/$(DEPDIR)/bluetoothd-uuid-helper.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/uuid-helper.c' object='src/bluetoothd-uuid-helper.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-uuid-helper.obj `if test -f 'src/uuid-helper.c'; then $(CYGPATH_W) 'src/uuid-helper.c'; else $(CYGPATH_W) '$(srcdir)/src/uuid-helper.c'; fi` src/bluetoothd-storage.o: src/storage.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-storage.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-storage.Tpo -c -o src/bluetoothd-storage.o `test -f 'src/storage.c' || echo '$(srcdir)/'`src/storage.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-storage.Tpo src/$(DEPDIR)/bluetoothd-storage.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/storage.c' object='src/bluetoothd-storage.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-storage.o `test -f 'src/storage.c' || echo '$(srcdir)/'`src/storage.c src/bluetoothd-storage.obj: src/storage.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-storage.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-storage.Tpo -c -o src/bluetoothd-storage.obj `if test -f 'src/storage.c'; then $(CYGPATH_W) 'src/storage.c'; else $(CYGPATH_W) '$(srcdir)/src/storage.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-storage.Tpo src/$(DEPDIR)/bluetoothd-storage.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/storage.c' object='src/bluetoothd-storage.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-storage.obj `if test -f 'src/storage.c'; then $(CYGPATH_W) 'src/storage.c'; else $(CYGPATH_W) '$(srcdir)/src/storage.c'; fi` src/bluetoothd-advertising.o: src/advertising.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-advertising.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-advertising.Tpo -c -o src/bluetoothd-advertising.o `test -f 'src/advertising.c' || echo '$(srcdir)/'`src/advertising.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-advertising.Tpo src/$(DEPDIR)/bluetoothd-advertising.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/advertising.c' object='src/bluetoothd-advertising.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-advertising.o `test -f 'src/advertising.c' || echo '$(srcdir)/'`src/advertising.c src/bluetoothd-advertising.obj: src/advertising.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-advertising.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-advertising.Tpo -c -o src/bluetoothd-advertising.obj `if test -f 'src/advertising.c'; then $(CYGPATH_W) 'src/advertising.c'; else $(CYGPATH_W) '$(srcdir)/src/advertising.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-advertising.Tpo src/$(DEPDIR)/bluetoothd-advertising.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/advertising.c' object='src/bluetoothd-advertising.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-advertising.obj `if test -f 'src/advertising.c'; then $(CYGPATH_W) 'src/advertising.c'; else $(CYGPATH_W) '$(srcdir)/src/advertising.c'; fi` src/bluetoothd-agent.o: src/agent.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-agent.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-agent.Tpo -c -o src/bluetoothd-agent.o `test -f 'src/agent.c' || echo '$(srcdir)/'`src/agent.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-agent.Tpo src/$(DEPDIR)/bluetoothd-agent.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/agent.c' object='src/bluetoothd-agent.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-agent.o `test -f 'src/agent.c' || echo '$(srcdir)/'`src/agent.c src/bluetoothd-agent.obj: src/agent.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-agent.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-agent.Tpo -c -o src/bluetoothd-agent.obj `if test -f 'src/agent.c'; then $(CYGPATH_W) 'src/agent.c'; else $(CYGPATH_W) '$(srcdir)/src/agent.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-agent.Tpo src/$(DEPDIR)/bluetoothd-agent.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/agent.c' object='src/bluetoothd-agent.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-agent.obj `if test -f 'src/agent.c'; then $(CYGPATH_W) 'src/agent.c'; else $(CYGPATH_W) '$(srcdir)/src/agent.c'; fi` src/bluetoothd-error.o: src/error.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-error.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-error.Tpo -c -o src/bluetoothd-error.o `test -f 'src/error.c' || echo '$(srcdir)/'`src/error.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-error.Tpo src/$(DEPDIR)/bluetoothd-error.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/error.c' object='src/bluetoothd-error.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-error.o `test -f 'src/error.c' || echo '$(srcdir)/'`src/error.c src/bluetoothd-error.obj: src/error.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-error.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-error.Tpo -c -o src/bluetoothd-error.obj `if test -f 'src/error.c'; then $(CYGPATH_W) 'src/error.c'; else $(CYGPATH_W) '$(srcdir)/src/error.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-error.Tpo src/$(DEPDIR)/bluetoothd-error.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/error.c' object='src/bluetoothd-error.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-error.obj `if test -f 'src/error.c'; then $(CYGPATH_W) 'src/error.c'; else $(CYGPATH_W) '$(srcdir)/src/error.c'; fi` src/bluetoothd-adapter.o: src/adapter.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-adapter.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-adapter.Tpo -c -o src/bluetoothd-adapter.o `test -f 'src/adapter.c' || echo '$(srcdir)/'`src/adapter.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-adapter.Tpo src/$(DEPDIR)/bluetoothd-adapter.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/adapter.c' object='src/bluetoothd-adapter.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-adapter.o `test -f 'src/adapter.c' || echo '$(srcdir)/'`src/adapter.c src/bluetoothd-adapter.obj: src/adapter.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-adapter.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-adapter.Tpo -c -o src/bluetoothd-adapter.obj `if test -f 'src/adapter.c'; then $(CYGPATH_W) 'src/adapter.c'; else $(CYGPATH_W) '$(srcdir)/src/adapter.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-adapter.Tpo src/$(DEPDIR)/bluetoothd-adapter.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/adapter.c' object='src/bluetoothd-adapter.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-adapter.obj `if test -f 'src/adapter.c'; then $(CYGPATH_W) 'src/adapter.c'; else $(CYGPATH_W) '$(srcdir)/src/adapter.c'; fi` src/bluetoothd-profile.o: src/profile.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-profile.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-profile.Tpo -c -o src/bluetoothd-profile.o `test -f 'src/profile.c' || echo '$(srcdir)/'`src/profile.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-profile.Tpo src/$(DEPDIR)/bluetoothd-profile.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/profile.c' object='src/bluetoothd-profile.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-profile.o `test -f 'src/profile.c' || echo '$(srcdir)/'`src/profile.c src/bluetoothd-profile.obj: src/profile.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-profile.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-profile.Tpo -c -o src/bluetoothd-profile.obj `if test -f 'src/profile.c'; then $(CYGPATH_W) 'src/profile.c'; else $(CYGPATH_W) '$(srcdir)/src/profile.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-profile.Tpo src/$(DEPDIR)/bluetoothd-profile.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/profile.c' object='src/bluetoothd-profile.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-profile.obj `if test -f 'src/profile.c'; then $(CYGPATH_W) 'src/profile.c'; else $(CYGPATH_W) '$(srcdir)/src/profile.c'; fi` src/bluetoothd-service.o: src/service.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-service.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-service.Tpo -c -o src/bluetoothd-service.o `test -f 'src/service.c' || echo '$(srcdir)/'`src/service.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-service.Tpo src/$(DEPDIR)/bluetoothd-service.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/service.c' object='src/bluetoothd-service.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-service.o `test -f 'src/service.c' || echo '$(srcdir)/'`src/service.c src/bluetoothd-service.obj: src/service.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-service.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-service.Tpo -c -o src/bluetoothd-service.obj `if test -f 'src/service.c'; then $(CYGPATH_W) 'src/service.c'; else $(CYGPATH_W) '$(srcdir)/src/service.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-service.Tpo src/$(DEPDIR)/bluetoothd-service.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/service.c' object='src/bluetoothd-service.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-service.obj `if test -f 'src/service.c'; then $(CYGPATH_W) 'src/service.c'; else $(CYGPATH_W) '$(srcdir)/src/service.c'; fi` src/bluetoothd-gatt-client.o: src/gatt-client.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-gatt-client.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-gatt-client.Tpo -c -o src/bluetoothd-gatt-client.o `test -f 'src/gatt-client.c' || echo '$(srcdir)/'`src/gatt-client.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-gatt-client.Tpo src/$(DEPDIR)/bluetoothd-gatt-client.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/gatt-client.c' object='src/bluetoothd-gatt-client.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-gatt-client.o `test -f 'src/gatt-client.c' || echo '$(srcdir)/'`src/gatt-client.c src/bluetoothd-gatt-client.obj: src/gatt-client.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-gatt-client.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-gatt-client.Tpo -c -o src/bluetoothd-gatt-client.obj `if test -f 'src/gatt-client.c'; then $(CYGPATH_W) 'src/gatt-client.c'; else $(CYGPATH_W) '$(srcdir)/src/gatt-client.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-gatt-client.Tpo src/$(DEPDIR)/bluetoothd-gatt-client.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/gatt-client.c' object='src/bluetoothd-gatt-client.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-gatt-client.obj `if test -f 'src/gatt-client.c'; then $(CYGPATH_W) 'src/gatt-client.c'; else $(CYGPATH_W) '$(srcdir)/src/gatt-client.c'; fi` src/bluetoothd-device.o: src/device.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-device.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-device.Tpo -c -o src/bluetoothd-device.o `test -f 'src/device.c' || echo '$(srcdir)/'`src/device.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-device.Tpo src/$(DEPDIR)/bluetoothd-device.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/device.c' object='src/bluetoothd-device.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-device.o `test -f 'src/device.c' || echo '$(srcdir)/'`src/device.c src/bluetoothd-device.obj: src/device.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-device.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-device.Tpo -c -o src/bluetoothd-device.obj `if test -f 'src/device.c'; then $(CYGPATH_W) 'src/device.c'; else $(CYGPATH_W) '$(srcdir)/src/device.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-device.Tpo src/$(DEPDIR)/bluetoothd-device.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/device.c' object='src/bluetoothd-device.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-device.obj `if test -f 'src/device.c'; then $(CYGPATH_W) 'src/device.c'; else $(CYGPATH_W) '$(srcdir)/src/device.c'; fi` src/bluetoothd-dbus-common.o: src/dbus-common.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-dbus-common.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-dbus-common.Tpo -c -o src/bluetoothd-dbus-common.o `test -f 'src/dbus-common.c' || echo '$(srcdir)/'`src/dbus-common.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-dbus-common.Tpo src/$(DEPDIR)/bluetoothd-dbus-common.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dbus-common.c' object='src/bluetoothd-dbus-common.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-dbus-common.o `test -f 'src/dbus-common.c' || echo '$(srcdir)/'`src/dbus-common.c src/bluetoothd-dbus-common.obj: src/dbus-common.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-dbus-common.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-dbus-common.Tpo -c -o src/bluetoothd-dbus-common.obj `if test -f 'src/dbus-common.c'; then $(CYGPATH_W) 'src/dbus-common.c'; else $(CYGPATH_W) '$(srcdir)/src/dbus-common.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-dbus-common.Tpo src/$(DEPDIR)/bluetoothd-dbus-common.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dbus-common.c' object='src/bluetoothd-dbus-common.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-dbus-common.obj `if test -f 'src/dbus-common.c'; then $(CYGPATH_W) 'src/dbus-common.c'; else $(CYGPATH_W) '$(srcdir)/src/dbus-common.c'; fi` src/bluetoothd-eir.o: src/eir.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-eir.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-eir.Tpo -c -o src/bluetoothd-eir.o `test -f 'src/eir.c' || echo '$(srcdir)/'`src/eir.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-eir.Tpo src/$(DEPDIR)/bluetoothd-eir.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/eir.c' object='src/bluetoothd-eir.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-eir.o `test -f 'src/eir.c' || echo '$(srcdir)/'`src/eir.c src/bluetoothd-eir.obj: src/eir.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-eir.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-eir.Tpo -c -o src/bluetoothd-eir.obj `if test -f 'src/eir.c'; then $(CYGPATH_W) 'src/eir.c'; else $(CYGPATH_W) '$(srcdir)/src/eir.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-eir.Tpo src/$(DEPDIR)/bluetoothd-eir.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/eir.c' object='src/bluetoothd-eir.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-eir.obj `if test -f 'src/eir.c'; then $(CYGPATH_W) 'src/eir.c'; else $(CYGPATH_W) '$(srcdir)/src/eir.c'; fi` src/bluetoothd-adv_monitor.o: src/adv_monitor.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-adv_monitor.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-adv_monitor.Tpo -c -o src/bluetoothd-adv_monitor.o `test -f 'src/adv_monitor.c' || echo '$(srcdir)/'`src/adv_monitor.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-adv_monitor.Tpo src/$(DEPDIR)/bluetoothd-adv_monitor.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/adv_monitor.c' object='src/bluetoothd-adv_monitor.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-adv_monitor.o `test -f 'src/adv_monitor.c' || echo '$(srcdir)/'`src/adv_monitor.c src/bluetoothd-adv_monitor.obj: src/adv_monitor.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-adv_monitor.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-adv_monitor.Tpo -c -o src/bluetoothd-adv_monitor.obj `if test -f 'src/adv_monitor.c'; then $(CYGPATH_W) 'src/adv_monitor.c'; else $(CYGPATH_W) '$(srcdir)/src/adv_monitor.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-adv_monitor.Tpo src/$(DEPDIR)/bluetoothd-adv_monitor.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/adv_monitor.c' object='src/bluetoothd-adv_monitor.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-adv_monitor.obj `if test -f 'src/adv_monitor.c'; then $(CYGPATH_W) 'src/adv_monitor.c'; else $(CYGPATH_W) '$(srcdir)/src/adv_monitor.c'; fi` src/bluetoothd-battery.o: src/battery.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-battery.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-battery.Tpo -c -o src/bluetoothd-battery.o `test -f 'src/battery.c' || echo '$(srcdir)/'`src/battery.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-battery.Tpo src/$(DEPDIR)/bluetoothd-battery.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/battery.c' object='src/bluetoothd-battery.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-battery.o `test -f 'src/battery.c' || echo '$(srcdir)/'`src/battery.c src/bluetoothd-battery.obj: src/battery.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-battery.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-battery.Tpo -c -o src/bluetoothd-battery.obj `if test -f 'src/battery.c'; then $(CYGPATH_W) 'src/battery.c'; else $(CYGPATH_W) '$(srcdir)/src/battery.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-battery.Tpo src/$(DEPDIR)/bluetoothd-battery.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/battery.c' object='src/bluetoothd-battery.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-battery.obj `if test -f 'src/battery.c'; then $(CYGPATH_W) 'src/battery.c'; else $(CYGPATH_W) '$(srcdir)/src/battery.c'; fi` src/bluetoothd-settings.o: src/settings.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-settings.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-settings.Tpo -c -o src/bluetoothd-settings.o `test -f 'src/settings.c' || echo '$(srcdir)/'`src/settings.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-settings.Tpo src/$(DEPDIR)/bluetoothd-settings.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings.c' object='src/bluetoothd-settings.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-settings.o `test -f 'src/settings.c' || echo '$(srcdir)/'`src/settings.c src/bluetoothd-settings.obj: src/settings.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-settings.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-settings.Tpo -c -o src/bluetoothd-settings.obj `if test -f 'src/settings.c'; then $(CYGPATH_W) 'src/settings.c'; else $(CYGPATH_W) '$(srcdir)/src/settings.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-settings.Tpo src/$(DEPDIR)/bluetoothd-settings.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/settings.c' object='src/bluetoothd-settings.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-settings.obj `if test -f 'src/settings.c'; then $(CYGPATH_W) 'src/settings.c'; else $(CYGPATH_W) '$(srcdir)/src/settings.c'; fi` src/bluetoothd-set.o: src/set.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-set.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-set.Tpo -c -o src/bluetoothd-set.o `test -f 'src/set.c' || echo '$(srcdir)/'`src/set.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-set.Tpo src/$(DEPDIR)/bluetoothd-set.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/set.c' object='src/bluetoothd-set.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-set.o `test -f 'src/set.c' || echo '$(srcdir)/'`src/set.c src/bluetoothd-set.obj: src/set.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-set.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-set.Tpo -c -o src/bluetoothd-set.obj `if test -f 'src/set.c'; then $(CYGPATH_W) 'src/set.c'; else $(CYGPATH_W) '$(srcdir)/src/set.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-set.Tpo src/$(DEPDIR)/bluetoothd-set.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/set.c' object='src/bluetoothd-set.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-set.obj `if test -f 'src/set.c'; then $(CYGPATH_W) 'src/set.c'; else $(CYGPATH_W) '$(srcdir)/src/set.c'; fi` src/bluetoothd-bearer.o: src/bearer.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-bearer.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-bearer.Tpo -c -o src/bluetoothd-bearer.o `test -f 'src/bearer.c' || echo '$(srcdir)/'`src/bearer.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-bearer.Tpo src/$(DEPDIR)/bluetoothd-bearer.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/bearer.c' object='src/bluetoothd-bearer.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-bearer.o `test -f 'src/bearer.c' || echo '$(srcdir)/'`src/bearer.c src/bluetoothd-bearer.obj: src/bearer.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-bearer.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-bearer.Tpo -c -o src/bluetoothd-bearer.obj `if test -f 'src/bearer.c'; then $(CYGPATH_W) 'src/bearer.c'; else $(CYGPATH_W) '$(srcdir)/src/bearer.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-bearer.Tpo src/$(DEPDIR)/bluetoothd-bearer.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/bearer.c' object='src/bluetoothd-bearer.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-bearer.obj `if test -f 'src/bearer.c'; then $(CYGPATH_W) 'src/bearer.c'; else $(CYGPATH_W) '$(srcdir)/src/bearer.c'; fi` src/bluetoothd-plugin.o: src/plugin.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-plugin.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-plugin.Tpo -c -o src/bluetoothd-plugin.o `test -f 'src/plugin.c' || echo '$(srcdir)/'`src/plugin.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-plugin.Tpo src/$(DEPDIR)/bluetoothd-plugin.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/plugin.c' object='src/bluetoothd-plugin.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-plugin.o `test -f 'src/plugin.c' || echo '$(srcdir)/'`src/plugin.c src/bluetoothd-plugin.obj: src/plugin.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-plugin.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-plugin.Tpo -c -o src/bluetoothd-plugin.obj `if test -f 'src/plugin.c'; then $(CYGPATH_W) 'src/plugin.c'; else $(CYGPATH_W) '$(srcdir)/src/plugin.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-plugin.Tpo src/$(DEPDIR)/bluetoothd-plugin.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/plugin.c' object='src/bluetoothd-plugin.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-plugin.obj `if test -f 'src/plugin.c'; then $(CYGPATH_W) 'src/plugin.c'; else $(CYGPATH_W) '$(srcdir)/src/plugin.c'; fi` src/bluetoothd-main.o: src/main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-main.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-main.Tpo -c -o src/bluetoothd-main.o `test -f 'src/main.c' || echo '$(srcdir)/'`src/main.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-main.Tpo src/$(DEPDIR)/bluetoothd-main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/main.c' object='src/bluetoothd-main.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-main.o `test -f 'src/main.c' || echo '$(srcdir)/'`src/main.c src/bluetoothd-main.obj: src/main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-main.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-main.Tpo -c -o src/bluetoothd-main.obj `if test -f 'src/main.c'; then $(CYGPATH_W) 'src/main.c'; else $(CYGPATH_W) '$(srcdir)/src/main.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-main.Tpo src/$(DEPDIR)/bluetoothd-main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/main.c' object='src/bluetoothd-main.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-main.obj `if test -f 'src/main.c'; then $(CYGPATH_W) 'src/main.c'; else $(CYGPATH_W) '$(srcdir)/src/main.c'; fi` tools/test_runner-test-runner.o: tools/test-runner.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tools_test_runner_CFLAGS) $(CFLAGS) -MT tools/test_runner-test-runner.o -MD -MP -MF tools/$(DEPDIR)/test_runner-test-runner.Tpo -c -o tools/test_runner-test-runner.o `test -f 'tools/test-runner.c' || echo '$(srcdir)/'`tools/test-runner.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tools/$(DEPDIR)/test_runner-test-runner.Tpo tools/$(DEPDIR)/test_runner-test-runner.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tools/test-runner.c' object='tools/test_runner-test-runner.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tools_test_runner_CFLAGS) $(CFLAGS) -c -o tools/test_runner-test-runner.o `test -f 'tools/test-runner.c' || echo '$(srcdir)/'`tools/test-runner.c tools/test_runner-test-runner.obj: tools/test-runner.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tools_test_runner_CFLAGS) $(CFLAGS) -MT tools/test_runner-test-runner.obj -MD -MP -MF tools/$(DEPDIR)/test_runner-test-runner.Tpo -c -o tools/test_runner-test-runner.obj `if test -f 'tools/test-runner.c'; then $(CYGPATH_W) 'tools/test-runner.c'; else $(CYGPATH_W) '$(srcdir)/tools/test-runner.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tools/$(DEPDIR)/test_runner-test-runner.Tpo tools/$(DEPDIR)/test_runner-test-runner.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tools/test-runner.c' object='tools/test_runner-test-runner.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tools_test_runner_CFLAGS) $(CFLAGS) -c -o tools/test_runner-test-runner.obj `if test -f 'tools/test-runner.c'; then $(CYGPATH_W) 'tools/test-runner.c'; else $(CYGPATH_W) '$(srcdir)/tools/test-runner.c'; fi` unit/test_mesh_crypto-test-mesh-crypto.o: unit/test-mesh-crypto.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unit/test_mesh_crypto-test-mesh-crypto.o -MD -MP -MF unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Tpo -c -o unit/test_mesh_crypto-test-mesh-crypto.o `test -f 'unit/test-mesh-crypto.c' || echo '$(srcdir)/'`unit/test-mesh-crypto.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Tpo unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='unit/test-mesh-crypto.c' object='unit/test_mesh_crypto-test-mesh-crypto.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unit/test_mesh_crypto-test-mesh-crypto.o `test -f 'unit/test-mesh-crypto.c' || echo '$(srcdir)/'`unit/test-mesh-crypto.c unit/test_mesh_crypto-test-mesh-crypto.obj: unit/test-mesh-crypto.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unit/test_mesh_crypto-test-mesh-crypto.obj -MD -MP -MF unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Tpo -c -o unit/test_mesh_crypto-test-mesh-crypto.obj `if test -f 'unit/test-mesh-crypto.c'; then $(CYGPATH_W) 'unit/test-mesh-crypto.c'; else $(CYGPATH_W) '$(srcdir)/unit/test-mesh-crypto.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Tpo unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='unit/test-mesh-crypto.c' object='unit/test_mesh_crypto-test-mesh-crypto.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unit/test_mesh_crypto-test-mesh-crypto.obj `if test -f 'unit/test-mesh-crypto.c'; then $(CYGPATH_W) 'unit/test-mesh-crypto.c'; else $(CYGPATH_W) '$(srcdir)/unit/test-mesh-crypto.c'; fi` unit/test_midi-test-midi.o: unit/test-midi.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unit/test_midi-test-midi.o -MD -MP -MF unit/$(DEPDIR)/test_midi-test-midi.Tpo -c -o unit/test_midi-test-midi.o `test -f 'unit/test-midi.c' || echo '$(srcdir)/'`unit/test-midi.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) unit/$(DEPDIR)/test_midi-test-midi.Tpo unit/$(DEPDIR)/test_midi-test-midi.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='unit/test-midi.c' object='unit/test_midi-test-midi.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unit/test_midi-test-midi.o `test -f 'unit/test-midi.c' || echo '$(srcdir)/'`unit/test-midi.c unit/test_midi-test-midi.obj: unit/test-midi.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unit/test_midi-test-midi.obj -MD -MP -MF unit/$(DEPDIR)/test_midi-test-midi.Tpo -c -o unit/test_midi-test-midi.obj `if test -f 'unit/test-midi.c'; then $(CYGPATH_W) 'unit/test-midi.c'; else $(CYGPATH_W) '$(srcdir)/unit/test-midi.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) unit/$(DEPDIR)/test_midi-test-midi.Tpo unit/$(DEPDIR)/test_midi-test-midi.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='unit/test-midi.c' object='unit/test_midi-test-midi.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unit/test_midi-test-midi.obj `if test -f 'unit/test-midi.c'; then $(CYGPATH_W) 'unit/test-midi.c'; else $(CYGPATH_W) '$(srcdir)/unit/test-midi.c'; fi` profiles/midi/unit_test_midi-libmidi.o: profiles/midi/libmidi.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/midi/unit_test_midi-libmidi.o -MD -MP -MF profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Tpo -c -o profiles/midi/unit_test_midi-libmidi.o `test -f 'profiles/midi/libmidi.c' || echo '$(srcdir)/'`profiles/midi/libmidi.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Tpo profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/midi/libmidi.c' object='profiles/midi/unit_test_midi-libmidi.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/midi/unit_test_midi-libmidi.o `test -f 'profiles/midi/libmidi.c' || echo '$(srcdir)/'`profiles/midi/libmidi.c profiles/midi/unit_test_midi-libmidi.obj: profiles/midi/libmidi.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/midi/unit_test_midi-libmidi.obj -MD -MP -MF profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Tpo -c -o profiles/midi/unit_test_midi-libmidi.obj `if test -f 'profiles/midi/libmidi.c'; then $(CYGPATH_W) 'profiles/midi/libmidi.c'; else $(CYGPATH_W) '$(srcdir)/profiles/midi/libmidi.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Tpo profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/midi/libmidi.c' object='profiles/midi/unit_test_midi-libmidi.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/midi/unit_test_midi-libmidi.obj `if test -f 'profiles/midi/libmidi.c'; then $(CYGPATH_W) 'profiles/midi/libmidi.c'; else $(CYGPATH_W) '$(srcdir)/profiles/midi/libmidi.c'; fi` mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs -rm -rf attrib/.libs attrib/_libs -rm -rf client/.libs client/_libs -rm -rf client/btpclient/.libs client/btpclient/_libs -rm -rf ell/.libs ell/_libs -rm -rf emulator/.libs emulator/_libs -rm -rf gdbus/.libs gdbus/_libs -rm -rf lib/.libs lib/_libs -rm -rf lib/bluetooth/.libs lib/bluetooth/_libs -rm -rf mesh/.libs mesh/_libs -rm -rf monitor/.libs monitor/_libs -rm -rf obexd/src/.libs obexd/src/_libs -rm -rf peripheral/.libs peripheral/_libs -rm -rf profiles/cups/.libs profiles/cups/_libs -rm -rf profiles/iap/.libs profiles/iap/_libs -rm -rf src/.libs src/_libs -rm -rf src/shared/.libs src/shared/_libs -rm -rf tools/.libs tools/_libs -rm -rf unit/.libs unit/_libs distclean-libtool: -rm -f libtool config.lt install-man1: $(man_MANS) @$(NORMAL_INSTALL) @list1=''; \ list2='$(man_MANS)'; \ test -n "$(man1dir)" \ && test -n "`echo $$list1$$list2`" \ || exit 0; \ echo " $(MKDIR_P) '$(DESTDIR)$(man1dir)'"; \ $(MKDIR_P) "$(DESTDIR)$(man1dir)" || exit 1; \ { for i in $$list1; do echo "$$i"; done; \ if test -n "$$list2"; then \ for i in $$list2; do echo "$$i"; done \ | sed -n '/\.1[a-z]*$$/p'; \ fi; \ } | while read p; do \ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; echo "$$p"; \ done | \ sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ sed 'N;N;s,\n, ,g' | { \ list=; while read file base inst; do \ if test "$$base" = "$$inst"; then list="$$list $$file"; else \ echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man1dir)/$$inst'"; \ $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man1dir)/$$inst" || exit $$?; \ fi; \ done; \ for i in $$list; do echo "$$i"; done | $(am__base_list) | \ while read files; do \ test -z "$$files" || { \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man1dir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(man1dir)" || exit $$?; }; \ done; } uninstall-man1: @$(NORMAL_UNINSTALL) @list=''; test -n "$(man1dir)" || exit 0; \ files=`{ for i in $$list; do echo "$$i"; done; \ l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ sed -n '/\.1[a-z]*$$/p'; \ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ dir='$(DESTDIR)$(man1dir)'; $(am__uninstall_files_from_dir) install-man5: $(man_MANS) @$(NORMAL_INSTALL) @list1=''; \ list2='$(man_MANS)'; \ test -n "$(man5dir)" \ && test -n "`echo $$list1$$list2`" \ || exit 0; \ echo " $(MKDIR_P) '$(DESTDIR)$(man5dir)'"; \ $(MKDIR_P) "$(DESTDIR)$(man5dir)" || exit 1; \ { for i in $$list1; do echo "$$i"; done; \ if test -n "$$list2"; then \ for i in $$list2; do echo "$$i"; done \ | sed -n '/\.5[a-z]*$$/p'; \ fi; \ } | while read p; do \ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; echo "$$p"; \ done | \ sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^5][0-9a-z]*$$,5,;x' \ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ sed 'N;N;s,\n, ,g' | { \ list=; while read file base inst; do \ if test "$$base" = "$$inst"; then list="$$list $$file"; else \ echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man5dir)/$$inst'"; \ $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man5dir)/$$inst" || exit $$?; \ fi; \ done; \ for i in $$list; do echo "$$i"; done | $(am__base_list) | \ while read files; do \ test -z "$$files" || { \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man5dir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(man5dir)" || exit $$?; }; \ done; } uninstall-man5: @$(NORMAL_UNINSTALL) @list=''; test -n "$(man5dir)" || exit 0; \ files=`{ for i in $$list; do echo "$$i"; done; \ l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ sed -n '/\.5[a-z]*$$/p'; \ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^5][0-9a-z]*$$,5,;x' \ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ dir='$(DESTDIR)$(man5dir)'; $(am__uninstall_files_from_dir) install-man7: $(man_MANS) @$(NORMAL_INSTALL) @list1=''; \ list2='$(man_MANS)'; \ test -n "$(man7dir)" \ && test -n "`echo $$list1$$list2`" \ || exit 0; \ echo " $(MKDIR_P) '$(DESTDIR)$(man7dir)'"; \ $(MKDIR_P) "$(DESTDIR)$(man7dir)" || exit 1; \ { for i in $$list1; do echo "$$i"; done; \ if test -n "$$list2"; then \ for i in $$list2; do echo "$$i"; done \ | sed -n '/\.7[a-z]*$$/p'; \ fi; \ } | while read p; do \ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; echo "$$p"; \ done | \ sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^7][0-9a-z]*$$,7,;x' \ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ sed 'N;N;s,\n, ,g' | { \ list=; while read file base inst; do \ if test "$$base" = "$$inst"; then list="$$list $$file"; else \ echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man7dir)/$$inst'"; \ $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man7dir)/$$inst" || exit $$?; \ fi; \ done; \ for i in $$list; do echo "$$i"; done | $(am__base_list) | \ while read files; do \ test -z "$$files" || { \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man7dir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(man7dir)" || exit $$?; }; \ done; } uninstall-man7: @$(NORMAL_UNINSTALL) @list=''; test -n "$(man7dir)" || exit 0; \ files=`{ for i in $$list; do echo "$$i"; done; \ l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ sed -n '/\.7[a-z]*$$/p'; \ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^7][0-9a-z]*$$,7,;x' \ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ dir='$(DESTDIR)$(man7dir)'; $(am__uninstall_files_from_dir) install-man8: $(man_MANS) @$(NORMAL_INSTALL) @list1=''; \ list2='$(man_MANS)'; \ test -n "$(man8dir)" \ && test -n "`echo $$list1$$list2`" \ || exit 0; \ echo " $(MKDIR_P) '$(DESTDIR)$(man8dir)'"; \ $(MKDIR_P) "$(DESTDIR)$(man8dir)" || exit 1; \ { for i in $$list1; do echo "$$i"; done; \ if test -n "$$list2"; then \ for i in $$list2; do echo "$$i"; done \ | sed -n '/\.8[a-z]*$$/p'; \ fi; \ } | while read p; do \ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; echo "$$p"; \ done | \ sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ sed 'N;N;s,\n, ,g' | { \ list=; while read file base inst; do \ if test "$$base" = "$$inst"; then list="$$list $$file"; else \ echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man8dir)/$$inst'"; \ $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man8dir)/$$inst" || exit $$?; \ fi; \ done; \ for i in $$list; do echo "$$i"; done | $(am__base_list) | \ while read files; do \ test -z "$$files" || { \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man8dir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(man8dir)" || exit $$?; }; \ done; } uninstall-man8: @$(NORMAL_UNINSTALL) @list=''; test -n "$(man8dir)" || exit 0; \ files=`{ for i in $$list; do echo "$$i"; done; \ l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ sed -n '/\.8[a-z]*$$/p'; \ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ dir='$(DESTDIR)$(man8dir)'; $(am__uninstall_files_from_dir) install-confDATA: $(conf_DATA) @$(NORMAL_INSTALL) @list='$(conf_DATA)'; test -n "$(confdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(confdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(confdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(confdir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(confdir)" || exit $$?; \ done uninstall-confDATA: @$(NORMAL_UNINSTALL) @list='$(conf_DATA)'; test -n "$(confdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(confdir)'; $(am__uninstall_files_from_dir) install-dbusDATA: $(dbus_DATA) @$(NORMAL_INSTALL) @list='$(dbus_DATA)'; test -n "$(dbusdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(dbusdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(dbusdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(dbusdir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(dbusdir)" || exit $$?; \ done uninstall-dbusDATA: @$(NORMAL_UNINSTALL) @list='$(dbus_DATA)'; test -n "$(dbusdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(dbusdir)'; $(am__uninstall_files_from_dir) install-dbussessionbusDATA: $(dbussessionbus_DATA) @$(NORMAL_INSTALL) @list='$(dbussessionbus_DATA)'; test -n "$(dbussessionbusdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(dbussessionbusdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(dbussessionbusdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(dbussessionbusdir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(dbussessionbusdir)" || exit $$?; \ done uninstall-dbussessionbusDATA: @$(NORMAL_UNINSTALL) @list='$(dbussessionbus_DATA)'; test -n "$(dbussessionbusdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(dbussessionbusdir)'; $(am__uninstall_files_from_dir) install-dbussystembusDATA: $(dbussystembus_DATA) @$(NORMAL_INSTALL) @list='$(dbussystembus_DATA)'; test -n "$(dbussystembusdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(dbussystembusdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(dbussystembusdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(dbussystembusdir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(dbussystembusdir)" || exit $$?; \ done uninstall-dbussystembusDATA: @$(NORMAL_UNINSTALL) @list='$(dbussystembus_DATA)'; test -n "$(dbussystembusdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(dbussystembusdir)'; $(am__uninstall_files_from_dir) install-dist_zshcompletionDATA: $(dist_zshcompletion_DATA) @$(NORMAL_INSTALL) @list='$(dist_zshcompletion_DATA)'; test -n "$(zshcompletiondir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(zshcompletiondir)'"; \ $(MKDIR_P) "$(DESTDIR)$(zshcompletiondir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(zshcompletiondir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(zshcompletiondir)" || exit $$?; \ done uninstall-dist_zshcompletionDATA: @$(NORMAL_UNINSTALL) @list='$(dist_zshcompletion_DATA)'; test -n "$(zshcompletiondir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(zshcompletiondir)'; $(am__uninstall_files_from_dir) install-pkgconfigDATA: $(pkgconfig_DATA) @$(NORMAL_INSTALL) @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkgconfigdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkgconfigdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkgconfigdir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgconfigdir)" || exit $$?; \ done uninstall-pkgconfigDATA: @$(NORMAL_UNINSTALL) @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkgconfigdir)'; $(am__uninstall_files_from_dir) install-rulesDATA: $(rules_DATA) @$(NORMAL_INSTALL) @list='$(rules_DATA)'; test -n "$(rulesdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(rulesdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(rulesdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(rulesdir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(rulesdir)" || exit $$?; \ done uninstall-rulesDATA: @$(NORMAL_UNINSTALL) @list='$(rules_DATA)'; test -n "$(rulesdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(rulesdir)'; $(am__uninstall_files_from_dir) install-stateDATA: $(state_DATA) @$(NORMAL_INSTALL) @list='$(state_DATA)'; test -n "$(statedir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(statedir)'"; \ $(MKDIR_P) "$(DESTDIR)$(statedir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(statedir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(statedir)" || exit $$?; \ done uninstall-stateDATA: @$(NORMAL_UNINSTALL) @list='$(state_DATA)'; test -n "$(statedir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(statedir)'; $(am__uninstall_files_from_dir) install-systemdsystemunitDATA: $(systemdsystemunit_DATA) @$(NORMAL_INSTALL) @list='$(systemdsystemunit_DATA)'; test -n "$(systemdsystemunitdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(systemdsystemunitdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(systemdsystemunitdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(systemdsystemunitdir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(systemdsystemunitdir)" || exit $$?; \ done uninstall-systemdsystemunitDATA: @$(NORMAL_UNINSTALL) @list='$(systemdsystemunit_DATA)'; test -n "$(systemdsystemunitdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(systemdsystemunitdir)'; $(am__uninstall_files_from_dir) install-systemduserunitDATA: $(systemduserunit_DATA) @$(NORMAL_INSTALL) @list='$(systemduserunit_DATA)'; test -n "$(systemduserunitdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(systemduserunitdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(systemduserunitdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(systemduserunitdir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(systemduserunitdir)" || exit $$?; \ done uninstall-systemduserunitDATA: @$(NORMAL_UNINSTALL) @list='$(systemduserunit_DATA)'; test -n "$(systemduserunitdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(systemduserunitdir)'; $(am__uninstall_files_from_dir) install-pkgincludeHEADERS: $(pkginclude_HEADERS) @$(NORMAL_INSTALL) @list='$(pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkgincludedir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkgincludedir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkgincludedir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkgincludedir)" || exit $$?; \ done uninstall-pkgincludeHEADERS: @$(NORMAL_UNINSTALL) @list='$(pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkgincludedir)'; $(am__uninstall_files_from_dir) ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscope: cscope.files test ! -s cscope.files \ || $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS) clean-cscope: -rm -f cscope.files cscope.files: clean-cscope cscopelist cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags -rm -f cscope.out cscope.in.out cscope.po.out cscope.files # Recover from deleted '.trs' file; this should ensure that # "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create # both 'foo.log' and 'foo.trs'. Break the recipe in two subshells # to avoid problems with "make -n". .log.trs: rm -f $< $@ $(MAKE) $(AM_MAKEFLAGS) $< # Leading 'am--fnord' is there to ensure the list of targets does not # expand to empty, as could happen e.g. with make check TESTS=''. am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck) am--force-recheck: @: $(TEST_SUITE_LOG): $(TEST_LOGS) @$(am__set_TESTS_bases); \ am__f_ok () { test -f "$$1" && test -r "$$1"; }; \ redo_bases=`for i in $$bases; do \ am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \ done`; \ if test -n "$$redo_bases"; then \ redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \ redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \ if $(am__make_dryrun); then :; else \ rm -f $$redo_logs && rm -f $$redo_results || exit 1; \ fi; \ fi; \ if test -n "$$am__remaking_logs"; then \ echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \ "recursion detected" >&2; \ elif test -n "$$redo_logs"; then \ am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \ fi; \ if $(am__make_dryrun); then :; else \ st=0; \ errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \ for i in $$redo_bases; do \ test -f $$i.trs && test -r $$i.trs \ || { echo "$$errmsg $$i.trs" >&2; st=1; }; \ test -f $$i.log && test -r $$i.log \ || { echo "$$errmsg $$i.log" >&2; st=1; }; \ done; \ test $$st -eq 0 || exit 1; \ fi @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \ ws='[ ]'; \ results=`for b in $$bases; do echo $$b.trs; done`; \ test -n "$$results" || results=/dev/null; \ all=` grep "^$$ws*:test-result:" $$results | wc -l`; \ pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \ fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \ skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \ xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \ xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \ error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \ if test `expr $$fail + $$xpass + $$error` -eq 0; then \ success=true; \ else \ success=false; \ fi; \ br='==================='; br=$$br$$br$$br$$br; \ result_count () \ { \ if test x"$$1" = x"--maybe-color"; then \ maybe_colorize=yes; \ elif test x"$$1" = x"--no-color"; then \ maybe_colorize=no; \ else \ echo "$@: invalid 'result_count' usage" >&2; exit 4; \ fi; \ shift; \ desc=$$1 count=$$2; \ if test $$maybe_colorize = yes && test $$count -gt 0; then \ color_start=$$3 color_end=$$std; \ else \ color_start= color_end=; \ fi; \ echo "$${color_start}# $$desc $$count$${color_end}"; \ }; \ create_testsuite_report () \ { \ result_count $$1 "TOTAL:" $$all "$$brg"; \ result_count $$1 "PASS: " $$pass "$$grn"; \ result_count $$1 "SKIP: " $$skip "$$blu"; \ result_count $$1 "XFAIL:" $$xfail "$$lgn"; \ result_count $$1 "FAIL: " $$fail "$$red"; \ result_count $$1 "XPASS:" $$xpass "$$red"; \ result_count $$1 "ERROR:" $$error "$$mgn"; \ }; \ output_system_information () \ { \ echo; \ { uname -a | $(AWK) '{ \ printf "System information (uname -a):"; \ for (i = 1; i < NF; ++i) \ { \ if (i != 2) \ printf " %s", $$i; \ } \ printf "\n"; \ }'; } 2>&1; \ if test -r /etc/os-release; then \ echo "Distribution information (/etc/os-release):"; \ sed 8q /etc/os-release; \ elif test -r /etc/issue; then \ echo "Distribution information (/etc/issue):"; \ cat /etc/issue; \ fi; \ }; \ please_report () \ { \ echo "Some test(s) failed. Please report this to $(PACKAGE_BUGREPORT),"; \ echo "together with the test-suite.log file (gzipped) and your system"; \ echo "information. Thanks."; \ }; \ { \ echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \ $(am__rst_title); \ create_testsuite_report --no-color; \ output_system_information; \ echo; \ echo ".. contents:: :depth: 2"; \ echo; \ for b in $$bases; do echo $$b; done \ | $(am__create_global_log); \ } >$(TEST_SUITE_LOG).tmp || exit 1; \ mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \ if $$success; then \ col="$$grn"; \ else \ col="$$red"; \ test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \ fi; \ echo "$${col}$$br$${std}"; \ echo "$${col}Testsuite summary"$(AM_TESTSUITE_SUMMARY_HEADER)"$${std}"; \ echo "$${col}$$br$${std}"; \ create_testsuite_report --maybe-color; \ echo "$$col$$br$$std"; \ if $$success; then :; else \ echo "$${col}See $(subdir)/$(TEST_SUITE_LOG) for debugging.$${std}";\ if test -n "$(PACKAGE_BUGREPORT)"; then \ please_report | sed -e "s/^/$${col}/" -e s/'$$'/"$${std}"/; \ fi; \ echo "$$col$$br$$std"; \ fi; \ $$success || exit 1 check-TESTS: @$(am__rm_f) $(RECHECK_LOGS) @$(am__rm_f) $(RECHECK_LOGS:.log=.trs) @$(am__rm_f) $(TEST_SUITE_LOG) @set +e; $(am__set_TESTS_bases); \ log_list=`for i in $$bases; do echo $$i.log; done`; \ log_list=`echo $$log_list`; \ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \ exit $$?; recheck: all @$(am__rm_f) $(TEST_SUITE_LOG) @set +e; $(am__set_TESTS_bases); \ bases=`for i in $$bases; do echo $$i; done \ | $(am__list_recheck_tests)` || exit 1; \ log_list=`for i in $$bases; do echo $$i.log; done`; \ log_list=`echo $$log_list`; \ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \ am__force_recheck=am--force-recheck \ TEST_LOGS="$$log_list"; \ exit $$? unit/test-tester.log: unit/test-tester$(EXEEXT) @p='unit/test-tester$(EXEEXT)'; \ b='unit/test-tester'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-eir.log: unit/test-eir$(EXEEXT) @p='unit/test-eir$(EXEEXT)'; \ b='unit/test-eir'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-uuid.log: unit/test-uuid$(EXEEXT) @p='unit/test-uuid$(EXEEXT)'; \ b='unit/test-uuid'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-textfile.log: unit/test-textfile$(EXEEXT) @p='unit/test-textfile$(EXEEXT)'; \ b='unit/test-textfile'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-crc.log: unit/test-crc$(EXEEXT) @p='unit/test-crc$(EXEEXT)'; \ b='unit/test-crc'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-crypto.log: unit/test-crypto$(EXEEXT) @p='unit/test-crypto$(EXEEXT)'; \ b='unit/test-crypto'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-ecc.log: unit/test-ecc$(EXEEXT) @p='unit/test-ecc$(EXEEXT)'; \ b='unit/test-ecc'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-ringbuf.log: unit/test-ringbuf$(EXEEXT) @p='unit/test-ringbuf$(EXEEXT)'; \ b='unit/test-ringbuf'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-queue.log: unit/test-queue$(EXEEXT) @p='unit/test-queue$(EXEEXT)'; \ b='unit/test-queue'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-mgmt.log: unit/test-mgmt$(EXEEXT) @p='unit/test-mgmt$(EXEEXT)'; \ b='unit/test-mgmt'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-uhid.log: unit/test-uhid$(EXEEXT) @p='unit/test-uhid$(EXEEXT)'; \ b='unit/test-uhid'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-profile.log: unit/test-profile$(EXEEXT) @p='unit/test-profile$(EXEEXT)'; \ b='unit/test-profile'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-sdp.log: unit/test-sdp$(EXEEXT) @p='unit/test-sdp$(EXEEXT)'; \ b='unit/test-sdp'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-avdtp.log: unit/test-avdtp$(EXEEXT) @p='unit/test-avdtp$(EXEEXT)'; \ b='unit/test-avdtp'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-avctp.log: unit/test-avctp$(EXEEXT) @p='unit/test-avctp$(EXEEXT)'; \ b='unit/test-avctp'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-avrcp.log: unit/test-avrcp$(EXEEXT) @p='unit/test-avrcp$(EXEEXT)'; \ b='unit/test-avrcp'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-hfp.log: unit/test-hfp$(EXEEXT) @p='unit/test-hfp$(EXEEXT)'; \ b='unit/test-hfp'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-gdbus-client.log: unit/test-gdbus-client$(EXEEXT) @p='unit/test-gdbus-client$(EXEEXT)'; \ b='unit/test-gdbus-client'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-gobex-header.log: unit/test-gobex-header$(EXEEXT) @p='unit/test-gobex-header$(EXEEXT)'; \ b='unit/test-gobex-header'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-gobex-packet.log: unit/test-gobex-packet$(EXEEXT) @p='unit/test-gobex-packet$(EXEEXT)'; \ b='unit/test-gobex-packet'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-gobex.log: unit/test-gobex$(EXEEXT) @p='unit/test-gobex$(EXEEXT)'; \ b='unit/test-gobex'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-gobex-transfer.log: unit/test-gobex-transfer$(EXEEXT) @p='unit/test-gobex-transfer$(EXEEXT)'; \ b='unit/test-gobex-transfer'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-gobex-apparam.log: unit/test-gobex-apparam$(EXEEXT) @p='unit/test-gobex-apparam$(EXEEXT)'; \ b='unit/test-gobex-apparam'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-lib.log: unit/test-lib$(EXEEXT) @p='unit/test-lib$(EXEEXT)'; \ b='unit/test-lib'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-util.log: unit/test-util$(EXEEXT) @p='unit/test-util$(EXEEXT)'; \ b='unit/test-util'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-gatt.log: unit/test-gatt$(EXEEXT) @p='unit/test-gatt$(EXEEXT)'; \ b='unit/test-gatt'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-hog.log: unit/test-hog$(EXEEXT) @p='unit/test-hog$(EXEEXT)'; \ b='unit/test-hog'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-gattrib.log: unit/test-gattrib$(EXEEXT) @p='unit/test-gattrib$(EXEEXT)'; \ b='unit/test-gattrib'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-bap.log: unit/test-bap$(EXEEXT) @p='unit/test-bap$(EXEEXT)'; \ b='unit/test-bap'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-micp.log: unit/test-micp$(EXEEXT) @p='unit/test-micp$(EXEEXT)'; \ b='unit/test-micp'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-bass.log: unit/test-bass$(EXEEXT) @p='unit/test-bass$(EXEEXT)'; \ b='unit/test-bass'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-vcp.log: unit/test-vcp$(EXEEXT) @p='unit/test-vcp$(EXEEXT)'; \ b='unit/test-vcp'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-tmap.log: unit/test-tmap$(EXEEXT) @p='unit/test-tmap$(EXEEXT)'; \ b='unit/test-tmap'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-gmap.log: unit/test-gmap$(EXEEXT) @p='unit/test-gmap$(EXEEXT)'; \ b='unit/test-gmap'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-mcp.log: unit/test-mcp$(EXEEXT) @p='unit/test-mcp$(EXEEXT)'; \ b='unit/test-mcp'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-battery.log: unit/test-battery$(EXEEXT) @p='unit/test-battery$(EXEEXT)'; \ b='unit/test-battery'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-rap.log: unit/test-rap$(EXEEXT) @p='unit/test-rap$(EXEEXT)'; \ b='unit/test-rap'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-midi.log: unit/test-midi$(EXEEXT) @p='unit/test-midi$(EXEEXT)'; \ b='unit/test-midi'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-mesh-crypto.log: unit/test-mesh-crypto$(EXEEXT) @p='unit/test-mesh-crypto$(EXEEXT)'; \ b='unit/test-mesh-crypto'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) .test.log: @p='$<'; \ $(am__set_b); \ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) @am__EXEEXT_TRUE@.test$(EXEEXT).log: @am__EXEEXT_TRUE@ @p='$<'; \ @am__EXEEXT_TRUE@ $(am__set_b); \ @am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \ @am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \ @am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \ @am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT) distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) $(am__remove_distdir) $(AM_V_at)$(MKDIR_P) "$(distdir)" @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done -test -n "$(am__skip_mode_fix)" \ || find "$(distdir)" -type d ! -perm -755 \ -exec chmod u+rwx,go+rx {} \; -o \ ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ || chmod -R a+r "$(distdir)" dist-gzip: distdir tardir=$(distdir) && $(am__tar) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).tar.gz $(am__post_remove_distdir) dist-bzip2: distdir tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2 $(am__post_remove_distdir) dist-bzip3: distdir tardir=$(distdir) && $(am__tar) | bzip3 -c >$(distdir).tar.bz3 $(am__post_remove_distdir) dist-lzip: distdir tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz $(am__post_remove_distdir) dist-xz: distdir tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz $(am__post_remove_distdir) dist-zstd: distdir tardir=$(distdir) && $(am__tar) | zstd -c $${ZSTD_CLEVEL-$${ZSTD_OPT--19}} >$(distdir).tar.zst $(am__post_remove_distdir) dist-tarZ: distdir @echo WARNING: "Support for distribution archives compressed with" \ "legacy program 'compress' is deprecated." >&2 @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z $(am__post_remove_distdir) dist-shar: distdir @echo WARNING: "Support for shar distribution archives is" \ "deprecated." >&2 @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 shar $(distdir) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).shar.gz $(am__post_remove_distdir) dist-zip: distdir -rm -f $(distdir).zip zip -rq $(distdir).zip $(distdir) $(am__post_remove_distdir) dist dist-all: $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:' $(am__post_remove_distdir) # This target untars the dist file and tries a VPATH configuration. Then # it guarantees that the distribution is self-contained by making another # tarfile. distcheck: dist case '$(DIST_ARCHIVES)' in \ *.tar.gz*) \ eval GZIP= gzip -dc $(distdir).tar.gz | $(am__untar) ;;\ *.tar.bz2*) \ bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ *.tar.bz3*) \ bzip3 -dc $(distdir).tar.bz3 | $(am__untar) ;;\ *.tar.lz*) \ lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ *.tar.xz*) \ xz -dc $(distdir).tar.xz | $(am__untar) ;;\ *.tar.Z*) \ uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ *.shar.gz*) \ eval GZIP= gzip -dc $(distdir).shar.gz | unshar ;;\ *.zip*) \ unzip $(distdir).zip ;;\ *.tar.zst*) \ zstd -dc $(distdir).tar.zst | $(am__untar) ;;\ esac chmod -R a-w $(distdir) chmod u+w $(distdir) mkdir $(distdir)/_build $(distdir)/_build/sub $(distdir)/_inst chmod a-w $(distdir) test -d $(distdir)/_build || exit 0; \ dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ && am__cwd=`pwd` \ && $(am__cd) $(distdir)/_build/sub \ && ../../configure \ $(AM_DISTCHECK_CONFIGURE_FLAGS) \ $(DISTCHECK_CONFIGURE_FLAGS) \ --srcdir=../.. --prefix="$$dc_install_base" \ && $(MAKE) $(AM_MAKEFLAGS) \ && $(MAKE) $(AM_MAKEFLAGS) $(AM_DISTCHECK_DVI_TARGET) \ && $(MAKE) $(AM_MAKEFLAGS) check \ && $(MAKE) $(AM_MAKEFLAGS) install \ && $(MAKE) $(AM_MAKEFLAGS) installcheck \ && $(MAKE) $(AM_MAKEFLAGS) uninstall \ && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ distuninstallcheck \ && chmod -R a-w "$$dc_install_base" \ && ({ \ (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ } || { rm -rf "$$dc_destdir"; exit 1; }) \ && rm -rf "$$dc_destdir" \ && $(MAKE) $(AM_MAKEFLAGS) dist \ && rm -rf $(DIST_ARCHIVES) \ && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \ && cd "$$am__cwd" \ || exit 1 $(am__post_remove_distdir) @(echo "$(distdir) archives ready for distribution: "; \ list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' distuninstallcheck: @test -n '$(distuninstallcheck_dir)' || { \ echo 'ERROR: trying to run $@ with an empty' \ '$$(distuninstallcheck_dir)' >&2; \ exit 1; \ }; \ $(am__cd) '$(distuninstallcheck_dir)' || { \ echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \ exit 1; \ }; \ test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \ || { echo "ERROR: files left after uninstall:" ; \ if test -n "$(DESTDIR)"; then \ echo " (check DESTDIR support)"; \ fi ; \ $(distuninstallcheck_listfiles) ; \ exit 1; } >&2 distcleancheck: distclean @if test '$(srcdir)' = . ; then \ echo "ERROR: distcleancheck can only run from a VPATH build" ; \ exit 1 ; \ fi @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ || { echo "ERROR: files left in build directory after distclean:" ; \ $(distcleancheck_listfiles) ; \ exit 1; } >&2 check-am: all-am $(MAKE) $(AM_MAKEFLAGS) check-TESTS check: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) check-am all-am: Makefile $(PROGRAMS) $(LIBRARIES) $(LTLIBRARIES) $(SCRIPTS) \ $(MANS) $(DATA) $(HEADERS) config.h install-binPROGRAMS: install-libLTLIBRARIES install-cupsPROGRAMS: install-libLTLIBRARIES install-pkglibexecPROGRAMS: install-libLTLIBRARIES install-udevPROGRAMS: install-libLTLIBRARIES install-pluginLTLIBRARIES: install-libLTLIBRARIES installdirs: for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(cupsdir)" "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(udevdir)" "$(DESTDIR)$(libdir)" "$(DESTDIR)$(plugindir)" "$(DESTDIR)$(testdir)" "$(DESTDIR)$(man1dir)" "$(DESTDIR)$(man5dir)" "$(DESTDIR)$(man7dir)" "$(DESTDIR)$(man8dir)" "$(DESTDIR)$(confdir)" "$(DESTDIR)$(dbusdir)" "$(DESTDIR)$(dbussessionbusdir)" "$(DESTDIR)$(dbussystembusdir)" "$(DESTDIR)$(zshcompletiondir)" "$(DESTDIR)$(pkgconfigdir)" "$(DESTDIR)$(rulesdir)" "$(DESTDIR)$(statedir)" "$(DESTDIR)$(systemdsystemunitdir)" "$(DESTDIR)$(systemduserunitdir)" "$(DESTDIR)$(pkgincludedir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) install-am install-exec: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: -$(am__rm_f) $(TEST_LOGS) -$(am__rm_f) $(TEST_LOGS:.log=.trs) -$(am__rm_f) $(TEST_SUITE_LOG) clean-generic: -$(am__rm_f) $(CLEANFILES) distclean-generic: -$(am__rm_f) $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || $(am__rm_f) $(CONFIG_CLEAN_VPATH_FILES) -$(am__rm_f) $(DISTCLEANFILES) -$(am__rm_f) attrib/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) attrib/$(am__dirstamp) -$(am__rm_f) btio/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) btio/$(am__dirstamp) -$(am__rm_f) client/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) client/$(am__dirstamp) -$(am__rm_f) client/btpclient/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) client/btpclient/$(am__dirstamp) -$(am__rm_f) ell/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) ell/$(am__dirstamp) -$(am__rm_f) emulator/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) emulator/$(am__dirstamp) -$(am__rm_f) gdbus/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) gdbus/$(am__dirstamp) -$(am__rm_f) gobex/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) gobex/$(am__dirstamp) -$(am__rm_f) lib/$(am__dirstamp) -$(am__rm_f) lib/bluetooth/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) lib/bluetooth/$(am__dirstamp) -$(am__rm_f) mesh/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) mesh/$(am__dirstamp) -$(am__rm_f) monitor/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) monitor/$(am__dirstamp) -$(am__rm_f) obexd/client/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) obexd/client/$(am__dirstamp) -$(am__rm_f) obexd/plugins/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) obexd/plugins/$(am__dirstamp) -$(am__rm_f) obexd/src/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) obexd/src/$(am__dirstamp) -$(am__rm_f) peripheral/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) peripheral/$(am__dirstamp) -$(am__rm_f) plugins/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) plugins/$(am__dirstamp) -$(am__rm_f) profiles/audio/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) profiles/audio/$(am__dirstamp) -$(am__rm_f) profiles/battery/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) profiles/battery/$(am__dirstamp) -$(am__rm_f) profiles/cups/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) profiles/cups/$(am__dirstamp) -$(am__rm_f) profiles/deviceinfo/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) profiles/deviceinfo/$(am__dirstamp) -$(am__rm_f) profiles/gap/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) profiles/gap/$(am__dirstamp) -$(am__rm_f) profiles/iap/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) profiles/iap/$(am__dirstamp) -$(am__rm_f) profiles/input/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) profiles/input/$(am__dirstamp) -$(am__rm_f) profiles/midi/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) profiles/midi/$(am__dirstamp) -$(am__rm_f) profiles/network/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) profiles/network/$(am__dirstamp) -$(am__rm_f) profiles/ranging/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) profiles/ranging/$(am__dirstamp) -$(am__rm_f) profiles/scanparam/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) profiles/scanparam/$(am__dirstamp) -$(am__rm_f) src/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) src/$(am__dirstamp) -$(am__rm_f) src/shared/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) src/shared/$(am__dirstamp) -$(am__rm_f) tools/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) tools/$(am__dirstamp) -$(am__rm_f) tools/mesh-gatt/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) tools/mesh-gatt/$(am__dirstamp) -$(am__rm_f) tools/mesh/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) tools/mesh/$(am__dirstamp) -$(am__rm_f) unit/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) unit/$(am__dirstamp) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." -$(am__rm_f) $(BUILT_SOURCES) -$(am__rm_f) $(MAINTAINERCLEANFILES) clean: clean-am clean-am: clean-binPROGRAMS clean-cupsPROGRAMS clean-generic \ clean-libLTLIBRARIES clean-libtool clean-local \ clean-noinstLIBRARIES clean-noinstLTLIBRARIES \ clean-noinstPROGRAMS clean-pkglibexecPROGRAMS \ clean-pluginLTLIBRARIES clean-udevPROGRAMS mostlyclean-am distclean: distclean-am -rm -f $(am__CONFIG_DISTCLEAN_FILES) -rm -f attrib/$(DEPDIR)/att.Po -rm -f attrib/$(DEPDIR)/bluetoothd-att.Po -rm -f attrib/$(DEPDIR)/bluetoothd-gatt.Po -rm -f attrib/$(DEPDIR)/bluetoothd-gattrib.Po -rm -f attrib/$(DEPDIR)/gatt.Po -rm -f attrib/$(DEPDIR)/gattrib.Po -rm -f attrib/$(DEPDIR)/gatttool.Po -rm -f attrib/$(DEPDIR)/interactive.Po -rm -f attrib/$(DEPDIR)/utils.Po -rm -f btio/$(DEPDIR)/bluetoothd-btio.Po -rm -f btio/$(DEPDIR)/btio.Po -rm -f btio/$(DEPDIR)/obexd-btio.Po -rm -f client/$(DEPDIR)/admin.Po -rm -f client/$(DEPDIR)/adv_monitor.Po -rm -f client/$(DEPDIR)/advertising.Po -rm -f client/$(DEPDIR)/agent.Po -rm -f client/$(DEPDIR)/assistant.Po -rm -f client/$(DEPDIR)/display.Po -rm -f client/$(DEPDIR)/gatt.Po -rm -f client/$(DEPDIR)/hci.Po -rm -f client/$(DEPDIR)/main.Po -rm -f client/$(DEPDIR)/mgmt.Po -rm -f client/$(DEPDIR)/player.Po -rm -f client/$(DEPDIR)/print.Po -rm -f client/$(DEPDIR)/telephony.Po -rm -f client/btpclient/$(DEPDIR)/bap.Po -rm -f client/btpclient/$(DEPDIR)/btpclient.Po -rm -f client/btpclient/$(DEPDIR)/btpclientctl.Po -rm -f client/btpclient/$(DEPDIR)/core.Po -rm -f client/btpclient/$(DEPDIR)/gap.Po -rm -f client/btpclient/$(DEPDIR)/gatt.Po -rm -f ell/$(DEPDIR)/base64.Plo -rm -f ell/$(DEPDIR)/cert-crypto.Plo -rm -f ell/$(DEPDIR)/cert.Plo -rm -f ell/$(DEPDIR)/checksum.Plo -rm -f ell/$(DEPDIR)/cipher.Plo -rm -f ell/$(DEPDIR)/dbus-client.Plo -rm -f ell/$(DEPDIR)/dbus-filter.Plo -rm -f ell/$(DEPDIR)/dbus-message.Plo -rm -f ell/$(DEPDIR)/dbus-name-cache.Plo -rm -f ell/$(DEPDIR)/dbus-service.Plo -rm -f ell/$(DEPDIR)/dbus-util.Plo -rm -f ell/$(DEPDIR)/dbus.Plo -rm -f ell/$(DEPDIR)/ecc-external.Plo -rm -f ell/$(DEPDIR)/ecc.Plo -rm -f ell/$(DEPDIR)/ecdh.Plo -rm -f ell/$(DEPDIR)/gvariant-util.Plo -rm -f ell/$(DEPDIR)/hashmap.Plo -rm -f ell/$(DEPDIR)/idle.Plo -rm -f ell/$(DEPDIR)/io.Plo -rm -f ell/$(DEPDIR)/key.Plo -rm -f ell/$(DEPDIR)/log.Plo -rm -f ell/$(DEPDIR)/main.Plo -rm -f ell/$(DEPDIR)/pem.Plo -rm -f ell/$(DEPDIR)/queue.Plo -rm -f ell/$(DEPDIR)/random.Plo -rm -f ell/$(DEPDIR)/settings.Plo -rm -f ell/$(DEPDIR)/signal.Plo -rm -f ell/$(DEPDIR)/siphash.Plo -rm -f ell/$(DEPDIR)/string.Plo -rm -f ell/$(DEPDIR)/strv.Plo -rm -f ell/$(DEPDIR)/tester.Plo -rm -f ell/$(DEPDIR)/time.Plo -rm -f ell/$(DEPDIR)/timeout.Plo -rm -f ell/$(DEPDIR)/tls-extensions.Plo -rm -f ell/$(DEPDIR)/tls-record.Plo -rm -f ell/$(DEPDIR)/tls-suites.Plo -rm -f ell/$(DEPDIR)/tls.Plo -rm -f ell/$(DEPDIR)/utf8.Plo -rm -f ell/$(DEPDIR)/util.Plo -rm -f ell/$(DEPDIR)/uuid.Plo -rm -f emulator/$(DEPDIR)/b1ee.Po -rm -f emulator/$(DEPDIR)/btdev.Po -rm -f emulator/$(DEPDIR)/bthost.Po -rm -f emulator/$(DEPDIR)/hciemu.Po -rm -f emulator/$(DEPDIR)/hfp.Po -rm -f emulator/$(DEPDIR)/le.Po -rm -f emulator/$(DEPDIR)/main.Po -rm -f emulator/$(DEPDIR)/phy.Po -rm -f emulator/$(DEPDIR)/serial.Po -rm -f emulator/$(DEPDIR)/server.Po -rm -f emulator/$(DEPDIR)/smp.Po -rm -f emulator/$(DEPDIR)/vhci.Po -rm -f gdbus/$(DEPDIR)/client.Plo -rm -f gdbus/$(DEPDIR)/mainloop.Plo -rm -f gdbus/$(DEPDIR)/object.Plo -rm -f gdbus/$(DEPDIR)/polkit.Plo -rm -f gdbus/$(DEPDIR)/watch.Plo -rm -f gobex/$(DEPDIR)/gobex-apparam.Po -rm -f gobex/$(DEPDIR)/gobex-defs.Po -rm -f gobex/$(DEPDIR)/gobex-header.Po -rm -f gobex/$(DEPDIR)/gobex-packet.Po -rm -f gobex/$(DEPDIR)/gobex-transfer.Po -rm -f gobex/$(DEPDIR)/gobex.Po -rm -f gobex/$(DEPDIR)/obexd-gobex-apparam.Po -rm -f gobex/$(DEPDIR)/obexd-gobex-defs.Po -rm -f gobex/$(DEPDIR)/obexd-gobex-header.Po -rm -f gobex/$(DEPDIR)/obexd-gobex-packet.Po -rm -f gobex/$(DEPDIR)/obexd-gobex-transfer.Po -rm -f gobex/$(DEPDIR)/obexd-gobex.Po -rm -f lib/bluetooth/$(DEPDIR)/bluetooth.Plo -rm -f lib/bluetooth/$(DEPDIR)/hci.Plo -rm -f lib/bluetooth/$(DEPDIR)/sdp.Plo -rm -f lib/bluetooth/$(DEPDIR)/uuid.Plo -rm -f mesh/$(DEPDIR)/agent.Po -rm -f mesh/$(DEPDIR)/appkey.Po -rm -f mesh/$(DEPDIR)/cfgmod-server.Po -rm -f mesh/$(DEPDIR)/crypto.Po -rm -f mesh/$(DEPDIR)/dbus.Po -rm -f mesh/$(DEPDIR)/friend.Po -rm -f mesh/$(DEPDIR)/keyring.Po -rm -f mesh/$(DEPDIR)/main.Po -rm -f mesh/$(DEPDIR)/manager.Po -rm -f mesh/$(DEPDIR)/mesh-config-json.Po -rm -f mesh/$(DEPDIR)/mesh-io-generic.Po -rm -f mesh/$(DEPDIR)/mesh-io-mgmt.Po -rm -f mesh/$(DEPDIR)/mesh-io-unit.Po -rm -f mesh/$(DEPDIR)/mesh-io.Po -rm -f mesh/$(DEPDIR)/mesh-mgmt.Po -rm -f mesh/$(DEPDIR)/mesh.Po -rm -f mesh/$(DEPDIR)/model.Po -rm -f mesh/$(DEPDIR)/net-keys.Po -rm -f mesh/$(DEPDIR)/net.Po -rm -f mesh/$(DEPDIR)/node.Po -rm -f mesh/$(DEPDIR)/pb-adv.Po -rm -f mesh/$(DEPDIR)/prov-acceptor.Po -rm -f mesh/$(DEPDIR)/prov-initiator.Po -rm -f mesh/$(DEPDIR)/prvbeac-server.Po -rm -f mesh/$(DEPDIR)/remprv-server.Po -rm -f mesh/$(DEPDIR)/rpl.Po -rm -f mesh/$(DEPDIR)/util.Po -rm -f monitor/$(DEPDIR)/a2dp.Po -rm -f monitor/$(DEPDIR)/analyze.Po -rm -f monitor/$(DEPDIR)/att.Po -rm -f monitor/$(DEPDIR)/avctp.Po -rm -f monitor/$(DEPDIR)/avdtp.Po -rm -f monitor/$(DEPDIR)/bnep.Po -rm -f monitor/$(DEPDIR)/broadcom.Po -rm -f monitor/$(DEPDIR)/control.Po -rm -f monitor/$(DEPDIR)/crc.Po -rm -f monitor/$(DEPDIR)/display.Po -rm -f monitor/$(DEPDIR)/ellisys.Po -rm -f monitor/$(DEPDIR)/hcidump.Po -rm -f monitor/$(DEPDIR)/hwdb.Po -rm -f monitor/$(DEPDIR)/intel.Po -rm -f monitor/$(DEPDIR)/jlink.Po -rm -f monitor/$(DEPDIR)/keys.Po -rm -f monitor/$(DEPDIR)/l2cap.Po -rm -f monitor/$(DEPDIR)/ll.Po -rm -f monitor/$(DEPDIR)/lmp.Po -rm -f monitor/$(DEPDIR)/main.Po -rm -f monitor/$(DEPDIR)/msft.Po -rm -f monitor/$(DEPDIR)/packet.Po -rm -f monitor/$(DEPDIR)/rfcomm.Po -rm -f monitor/$(DEPDIR)/sdp.Po -rm -f monitor/$(DEPDIR)/vendor.Po -rm -f obexd/client/$(DEPDIR)/obexd-bip-common.Po -rm -f obexd/client/$(DEPDIR)/obexd-bip.Po -rm -f obexd/client/$(DEPDIR)/obexd-bluetooth.Po -rm -f obexd/client/$(DEPDIR)/obexd-driver.Po -rm -f obexd/client/$(DEPDIR)/obexd-ftp.Po -rm -f obexd/client/$(DEPDIR)/obexd-manager.Po -rm -f obexd/client/$(DEPDIR)/obexd-map-event.Po -rm -f obexd/client/$(DEPDIR)/obexd-map.Po -rm -f obexd/client/$(DEPDIR)/obexd-mns.Po -rm -f obexd/client/$(DEPDIR)/obexd-opp.Po -rm -f obexd/client/$(DEPDIR)/obexd-pbap.Po -rm -f obexd/client/$(DEPDIR)/obexd-session.Po -rm -f obexd/client/$(DEPDIR)/obexd-sync.Po -rm -f obexd/client/$(DEPDIR)/obexd-transfer.Po -rm -f obexd/client/$(DEPDIR)/obexd-transport.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-bluetooth.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-filesystem.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-ftp.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-irmc.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-mas.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-opp.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-pbap.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-pcsuite.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-phonebook-@PLUGIN_PHONEBOOK@.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-vcard.Po -rm -f obexd/src/$(DEPDIR)/obexd-log.Po -rm -f obexd/src/$(DEPDIR)/obexd-logind.Po -rm -f obexd/src/$(DEPDIR)/obexd-main.Po -rm -f obexd/src/$(DEPDIR)/obexd-manager.Po -rm -f obexd/src/$(DEPDIR)/obexd-mimetype.Po -rm -f obexd/src/$(DEPDIR)/obexd-obex.Po -rm -f obexd/src/$(DEPDIR)/obexd-plugin.Po -rm -f obexd/src/$(DEPDIR)/obexd-server.Po -rm -f obexd/src/$(DEPDIR)/obexd-service.Po -rm -f obexd/src/$(DEPDIR)/obexd-transport.Po -rm -f peripheral/$(DEPDIR)/attach.Po -rm -f peripheral/$(DEPDIR)/efivars.Po -rm -f peripheral/$(DEPDIR)/gap.Po -rm -f peripheral/$(DEPDIR)/gatt.Po -rm -f peripheral/$(DEPDIR)/log.Po -rm -f peripheral/$(DEPDIR)/main.Po -rm -f plugins/$(DEPDIR)/bluetoothd-admin.Po -rm -f plugins/$(DEPDIR)/bluetoothd-autopair.Po -rm -f plugins/$(DEPDIR)/bluetoothd-hostname.Po -rm -f plugins/$(DEPDIR)/bluetoothd-neard.Po -rm -f plugins/$(DEPDIR)/bluetoothd-policy.Po -rm -f plugins/$(DEPDIR)/bluetoothd-sixaxis.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-asha.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avctp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avrcp-player.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-bap.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-bass.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-ccp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-control.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-csip.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-gmap.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-hfp-hf.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-mcp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-media.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-micp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-player.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-sink.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-source.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-telephony.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-tmap.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-transport.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-vcp.Po -rm -f profiles/battery/$(DEPDIR)/bas.Po -rm -f profiles/battery/$(DEPDIR)/bluetoothd-bas.Po -rm -f profiles/battery/$(DEPDIR)/bluetoothd-battery.Po -rm -f profiles/cups/$(DEPDIR)/hcrp.Po -rm -f profiles/cups/$(DEPDIR)/main.Po -rm -f profiles/cups/$(DEPDIR)/sdp.Po -rm -f profiles/cups/$(DEPDIR)/spp.Po -rm -f profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Po -rm -f profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Po -rm -f profiles/deviceinfo/$(DEPDIR)/dis.Po -rm -f profiles/gap/$(DEPDIR)/bluetoothd-gas.Po -rm -f profiles/iap/$(DEPDIR)/main.Po -rm -f profiles/input/$(DEPDIR)/bluetoothd-device.Po -rm -f profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Po -rm -f profiles/input/$(DEPDIR)/bluetoothd-hog.Po -rm -f profiles/input/$(DEPDIR)/bluetoothd-manager.Po -rm -f profiles/input/$(DEPDIR)/bluetoothd-server.Po -rm -f profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Po -rm -f profiles/input/$(DEPDIR)/hog-lib.Po -rm -f profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Po -rm -f profiles/midi/$(DEPDIR)/bluetoothd-midi.Po -rm -f profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Po -rm -f profiles/network/$(DEPDIR)/bluetoothd-bnep.Po -rm -f profiles/network/$(DEPDIR)/bluetoothd-connection.Po -rm -f profiles/network/$(DEPDIR)/bluetoothd-manager.Po -rm -f profiles/network/$(DEPDIR)/bluetoothd-server.Po -rm -f profiles/network/$(DEPDIR)/bnep.Po -rm -f profiles/ranging/$(DEPDIR)/bluetoothd-rap.Po -rm -f profiles/ranging/$(DEPDIR)/bluetoothd-rap_hci.Po -rm -f profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Po -rm -f profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Po -rm -f profiles/scanparam/$(DEPDIR)/scpp.Po -rm -f src/$(DEPDIR)/adapter.Po -rm -f src/$(DEPDIR)/adv_monitor.Po -rm -f src/$(DEPDIR)/advertising.Po -rm -f src/$(DEPDIR)/agent.Po -rm -f src/$(DEPDIR)/backtrace.Po -rm -f src/$(DEPDIR)/battery.Po -rm -f src/$(DEPDIR)/bearer.Po -rm -f src/$(DEPDIR)/bluetoothd-adapter.Po -rm -f src/$(DEPDIR)/bluetoothd-adv_monitor.Po -rm -f src/$(DEPDIR)/bluetoothd-advertising.Po -rm -f src/$(DEPDIR)/bluetoothd-agent.Po -rm -f src/$(DEPDIR)/bluetoothd-backtrace.Po -rm -f src/$(DEPDIR)/bluetoothd-battery.Po -rm -f src/$(DEPDIR)/bluetoothd-bearer.Po -rm -f src/$(DEPDIR)/bluetoothd-dbus-common.Po -rm -f src/$(DEPDIR)/bluetoothd-device.Po -rm -f src/$(DEPDIR)/bluetoothd-eir.Po -rm -f src/$(DEPDIR)/bluetoothd-error.Po -rm -f src/$(DEPDIR)/bluetoothd-gatt-client.Po -rm -f src/$(DEPDIR)/bluetoothd-gatt-database.Po -rm -f src/$(DEPDIR)/bluetoothd-log.Po -rm -f src/$(DEPDIR)/bluetoothd-main.Po -rm -f src/$(DEPDIR)/bluetoothd-plugin.Po -rm -f src/$(DEPDIR)/bluetoothd-profile.Po -rm -f src/$(DEPDIR)/bluetoothd-rfkill.Po -rm -f src/$(DEPDIR)/bluetoothd-sdp-client.Po -rm -f src/$(DEPDIR)/bluetoothd-sdp-xml.Po -rm -f src/$(DEPDIR)/bluetoothd-sdpd-database.Po -rm -f src/$(DEPDIR)/bluetoothd-sdpd-request.Po -rm -f src/$(DEPDIR)/bluetoothd-sdpd-server.Po -rm -f src/$(DEPDIR)/bluetoothd-sdpd-service.Po -rm -f src/$(DEPDIR)/bluetoothd-service.Po -rm -f src/$(DEPDIR)/bluetoothd-set.Po -rm -f src/$(DEPDIR)/bluetoothd-settings.Po -rm -f src/$(DEPDIR)/bluetoothd-storage.Po -rm -f src/$(DEPDIR)/bluetoothd-textfile.Po -rm -f src/$(DEPDIR)/bluetoothd-uuid-helper.Po -rm -f src/$(DEPDIR)/dbus-common.Po -rm -f src/$(DEPDIR)/device.Po -rm -f src/$(DEPDIR)/eir.Po -rm -f src/$(DEPDIR)/error.Po -rm -f src/$(DEPDIR)/gatt-client.Po -rm -f src/$(DEPDIR)/gatt-database.Po -rm -f src/$(DEPDIR)/log.Po -rm -f src/$(DEPDIR)/oui.Po -rm -f src/$(DEPDIR)/profile.Po -rm -f src/$(DEPDIR)/rfkill.Po -rm -f src/$(DEPDIR)/sdp-client.Po -rm -f src/$(DEPDIR)/sdp-xml.Po -rm -f src/$(DEPDIR)/sdpd-database.Po -rm -f src/$(DEPDIR)/sdpd-request.Po -rm -f src/$(DEPDIR)/sdpd-server.Po -rm -f src/$(DEPDIR)/sdpd-service.Po -rm -f src/$(DEPDIR)/service.Po -rm -f src/$(DEPDIR)/set.Po -rm -f src/$(DEPDIR)/settings.Po -rm -f src/$(DEPDIR)/storage.Po -rm -f src/$(DEPDIR)/textfile.Po -rm -f src/$(DEPDIR)/uuid-helper.Po -rm -f src/shared/$(DEPDIR)/btp.Po -rm -f src/shared/$(DEPDIR)/libshared_ell_la-ad.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-asha.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-att.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-bap-debug.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-bap.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-bass.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-battery.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-btsnoop.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-ccp.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-crypto.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-csip.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-ecc.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gap.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gatt-client.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gatt-db.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gatt-helpers.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gatt-server.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gmap.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-hci-crypto.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-hci.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-hfp.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-io-ell.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-log.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-mainloop-ell.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-mcp.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-mgmt.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-micp.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-pcap.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-queue.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-rap.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-ringbuf.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-shell.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-timeout-ell.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-tmap.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-uhid.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-uinput.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-util.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-vcp.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-ad.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-asha.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-att.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-bap-debug.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-bap.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-bass.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-battery.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-btsnoop.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-ccp.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-crypto.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-csip.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-ecc.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gap.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gatt-client.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gatt-db.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gatt-helpers.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gatt-server.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gmap.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-hci-crypto.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-hci.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-hfp.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-io-glib.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-log.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-mainloop-glib.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-mainloop-notify.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-mcp.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-mgmt.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-micp.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-pcap.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-queue.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-rap.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-ringbuf.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-shell.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-tester.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-timeout-glib.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-tmap.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-uhid.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-uinput.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-util.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-vcp.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-ad.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-asha.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-att.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-bap-debug.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-bap.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-bass.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-battery.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-btsnoop.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-ccp.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-crypto.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-csip.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-ecc.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gap.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-client.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-db.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-helpers.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-server.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gmap.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-hci-crypto.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-hci.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-hfp.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-io-mainloop.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-log.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-mainloop-notify.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-mainloop.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-mcp.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-mgmt.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-micp.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-pcap.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-queue.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-rap.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-ringbuf.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-shell.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-timeout-mainloop.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-tmap.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-uhid.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-uinput.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-util.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-vcp.Plo -rm -f tools/$(DEPDIR)/3dsp.Po -rm -f tools/$(DEPDIR)/6lowpan-tester.Po -rm -f tools/$(DEPDIR)/advtest.Po -rm -f tools/$(DEPDIR)/avinfo.Po -rm -f tools/$(DEPDIR)/avtest.Po -rm -f tools/$(DEPDIR)/bcmfw.Po -rm -f tools/$(DEPDIR)/bdaddr.Po -rm -f tools/$(DEPDIR)/bluemoon.Po -rm -f tools/$(DEPDIR)/bluetooth-player.Po -rm -f tools/$(DEPDIR)/bnep-tester.Po -rm -f tools/$(DEPDIR)/bneptest.Po -rm -f tools/$(DEPDIR)/btattach.Po -rm -f tools/$(DEPDIR)/btconfig.Po -rm -f tools/$(DEPDIR)/btgatt-client.Po -rm -f tools/$(DEPDIR)/btgatt-server.Po -rm -f tools/$(DEPDIR)/btinfo.Po -rm -f tools/$(DEPDIR)/btiotest.Po -rm -f tools/$(DEPDIR)/btmgmt.Po -rm -f tools/$(DEPDIR)/btmon-logger.Po -rm -f tools/$(DEPDIR)/btproxy.Po -rm -f tools/$(DEPDIR)/btsnoop.Po -rm -f tools/$(DEPDIR)/check-selftest.Po -rm -f tools/$(DEPDIR)/ciptool.Po -rm -f tools/$(DEPDIR)/cltest.Po -rm -f tools/$(DEPDIR)/create-image.Po -rm -f tools/$(DEPDIR)/eddystone.Po -rm -f tools/$(DEPDIR)/gap-tester.Po -rm -f tools/$(DEPDIR)/gatt-service.Po -rm -f tools/$(DEPDIR)/hci-tester.Po -rm -f tools/$(DEPDIR)/hciattach.Po -rm -f tools/$(DEPDIR)/hciattach_ath3k.Po -rm -f tools/$(DEPDIR)/hciattach_bcm43xx.Po -rm -f tools/$(DEPDIR)/hciattach_intel.Po -rm -f tools/$(DEPDIR)/hciattach_qualcomm.Po -rm -f tools/$(DEPDIR)/hciattach_st.Po -rm -f tools/$(DEPDIR)/hciattach_ti.Po -rm -f tools/$(DEPDIR)/hciattach_tialt.Po -rm -f tools/$(DEPDIR)/hciconfig.Po -rm -f tools/$(DEPDIR)/hcieventmask.Po -rm -f tools/$(DEPDIR)/hcisecfilter.Po -rm -f tools/$(DEPDIR)/hcitool.Po -rm -f tools/$(DEPDIR)/hex2hcd.Po -rm -f tools/$(DEPDIR)/hid2hci.Po -rm -f tools/$(DEPDIR)/hwdb.Po -rm -f tools/$(DEPDIR)/ibeacon.Po -rm -f tools/$(DEPDIR)/ioctl-tester.Po -rm -f tools/$(DEPDIR)/iso-tester.Po -rm -f tools/$(DEPDIR)/isotest.Po -rm -f tools/$(DEPDIR)/l2cap-tester.Po -rm -f tools/$(DEPDIR)/l2ping.Po -rm -f tools/$(DEPDIR)/l2test.Po -rm -f tools/$(DEPDIR)/mesh-cfgclient.Po -rm -f tools/$(DEPDIR)/mesh-cfgtest.Po -rm -f tools/$(DEPDIR)/mesh-tester.Po -rm -f tools/$(DEPDIR)/meshctl.Po -rm -f tools/$(DEPDIR)/mgmt-tester.Po -rm -f tools/$(DEPDIR)/mpris-proxy.Po -rm -f tools/$(DEPDIR)/nokfw.Po -rm -f tools/$(DEPDIR)/obex-client-tool.Po -rm -f tools/$(DEPDIR)/obex-server-tool.Po -rm -f tools/$(DEPDIR)/obexctl.Po -rm -f tools/$(DEPDIR)/oobtest.Po -rm -f tools/$(DEPDIR)/rctest.Po -rm -f tools/$(DEPDIR)/rfcomm-tester.Po -rm -f tools/$(DEPDIR)/rfcomm.Po -rm -f tools/$(DEPDIR)/rtlfw.Po -rm -f tools/$(DEPDIR)/sco-tester.Po -rm -f tools/$(DEPDIR)/scotest.Po -rm -f tools/$(DEPDIR)/sdptool.Po -rm -f tools/$(DEPDIR)/seq2bseq.Po -rm -f tools/$(DEPDIR)/smp-tester.Po -rm -f tools/$(DEPDIR)/test_runner-test-runner.Po -rm -f tools/$(DEPDIR)/userchan-tester.Po -rm -f tools/mesh-gatt/$(DEPDIR)/config-client.Po -rm -f tools/mesh-gatt/$(DEPDIR)/config-server.Po -rm -f tools/mesh-gatt/$(DEPDIR)/crypto.Po -rm -f tools/mesh-gatt/$(DEPDIR)/gatt.Po -rm -f tools/mesh-gatt/$(DEPDIR)/net.Po -rm -f tools/mesh-gatt/$(DEPDIR)/node.Po -rm -f tools/mesh-gatt/$(DEPDIR)/onoff-model.Po -rm -f tools/mesh-gatt/$(DEPDIR)/prov-db.Po -rm -f tools/mesh-gatt/$(DEPDIR)/prov.Po -rm -f tools/mesh-gatt/$(DEPDIR)/util.Po -rm -f tools/mesh/$(DEPDIR)/agent.Po -rm -f tools/mesh/$(DEPDIR)/cfgcli.Po -rm -f tools/mesh/$(DEPDIR)/keys.Po -rm -f tools/mesh/$(DEPDIR)/mesh-db.Po -rm -f tools/mesh/$(DEPDIR)/remote.Po -rm -f tools/mesh/$(DEPDIR)/util.Po -rm -f unit/$(DEPDIR)/avctp.Po -rm -f unit/$(DEPDIR)/avdtp.Po -rm -f unit/$(DEPDIR)/avrcp-lib.Po -rm -f unit/$(DEPDIR)/btd.Po -rm -f unit/$(DEPDIR)/test-avctp.Po -rm -f unit/$(DEPDIR)/test-avdtp.Po -rm -f unit/$(DEPDIR)/test-avrcp.Po -rm -f unit/$(DEPDIR)/test-bap.Po -rm -f unit/$(DEPDIR)/test-bass.Po -rm -f unit/$(DEPDIR)/test-battery.Po -rm -f unit/$(DEPDIR)/test-crc.Po -rm -f unit/$(DEPDIR)/test-crypto.Po -rm -f unit/$(DEPDIR)/test-ecc.Po -rm -f unit/$(DEPDIR)/test-eir.Po -rm -f unit/$(DEPDIR)/test-gatt.Po -rm -f unit/$(DEPDIR)/test-gattrib.Po -rm -f unit/$(DEPDIR)/test-gdbus-client.Po -rm -f unit/$(DEPDIR)/test-gmap.Po -rm -f unit/$(DEPDIR)/test-gobex-apparam.Po -rm -f unit/$(DEPDIR)/test-gobex-header.Po -rm -f unit/$(DEPDIR)/test-gobex-packet.Po -rm -f unit/$(DEPDIR)/test-gobex-transfer.Po -rm -f unit/$(DEPDIR)/test-gobex.Po -rm -f unit/$(DEPDIR)/test-hfp.Po -rm -f unit/$(DEPDIR)/test-hog.Po -rm -f unit/$(DEPDIR)/test-lib.Po -rm -f unit/$(DEPDIR)/test-mcp.Po -rm -f unit/$(DEPDIR)/test-mgmt.Po -rm -f unit/$(DEPDIR)/test-micp.Po -rm -f unit/$(DEPDIR)/test-profile.Po -rm -f unit/$(DEPDIR)/test-queue.Po -rm -f unit/$(DEPDIR)/test-rap.Po -rm -f unit/$(DEPDIR)/test-ringbuf.Po -rm -f unit/$(DEPDIR)/test-sdp.Po -rm -f unit/$(DEPDIR)/test-tester.Po -rm -f unit/$(DEPDIR)/test-textfile.Po -rm -f unit/$(DEPDIR)/test-tmap.Po -rm -f unit/$(DEPDIR)/test-uhid.Po -rm -f unit/$(DEPDIR)/test-util.Po -rm -f unit/$(DEPDIR)/test-uuid.Po -rm -f unit/$(DEPDIR)/test-vcp.Po -rm -f unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Po -rm -f unit/$(DEPDIR)/test_midi-test-midi.Po -rm -f unit/$(DEPDIR)/util.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-hdr distclean-libtool distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-confDATA install-cupsPROGRAMS \ install-dbusDATA install-dbussessionbusDATA \ install-dbussystembusDATA install-dist_zshcompletionDATA \ install-man install-pkgconfigDATA install-pkgincludeHEADERS \ install-pluginLTLIBRARIES install-rulesDATA install-stateDATA \ install-systemdsystemunitDATA install-systemduserunitDATA \ install-testSCRIPTS install-udevPROGRAMS @$(NORMAL_INSTALL) $(MAKE) $(AM_MAKEFLAGS) install-data-hook install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-binPROGRAMS install-libLTLIBRARIES \ install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-man1 install-man5 install-man7 install-man8 install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f $(am__CONFIG_DISTCLEAN_FILES) -rm -rf $(top_srcdir)/autom4te.cache -rm -f attrib/$(DEPDIR)/att.Po -rm -f attrib/$(DEPDIR)/bluetoothd-att.Po -rm -f attrib/$(DEPDIR)/bluetoothd-gatt.Po -rm -f attrib/$(DEPDIR)/bluetoothd-gattrib.Po -rm -f attrib/$(DEPDIR)/gatt.Po -rm -f attrib/$(DEPDIR)/gattrib.Po -rm -f attrib/$(DEPDIR)/gatttool.Po -rm -f attrib/$(DEPDIR)/interactive.Po -rm -f attrib/$(DEPDIR)/utils.Po -rm -f btio/$(DEPDIR)/bluetoothd-btio.Po -rm -f btio/$(DEPDIR)/btio.Po -rm -f btio/$(DEPDIR)/obexd-btio.Po -rm -f client/$(DEPDIR)/admin.Po -rm -f client/$(DEPDIR)/adv_monitor.Po -rm -f client/$(DEPDIR)/advertising.Po -rm -f client/$(DEPDIR)/agent.Po -rm -f client/$(DEPDIR)/assistant.Po -rm -f client/$(DEPDIR)/display.Po -rm -f client/$(DEPDIR)/gatt.Po -rm -f client/$(DEPDIR)/hci.Po -rm -f client/$(DEPDIR)/main.Po -rm -f client/$(DEPDIR)/mgmt.Po -rm -f client/$(DEPDIR)/player.Po -rm -f client/$(DEPDIR)/print.Po -rm -f client/$(DEPDIR)/telephony.Po -rm -f client/btpclient/$(DEPDIR)/bap.Po -rm -f client/btpclient/$(DEPDIR)/btpclient.Po -rm -f client/btpclient/$(DEPDIR)/btpclientctl.Po -rm -f client/btpclient/$(DEPDIR)/core.Po -rm -f client/btpclient/$(DEPDIR)/gap.Po -rm -f client/btpclient/$(DEPDIR)/gatt.Po -rm -f ell/$(DEPDIR)/base64.Plo -rm -f ell/$(DEPDIR)/cert-crypto.Plo -rm -f ell/$(DEPDIR)/cert.Plo -rm -f ell/$(DEPDIR)/checksum.Plo -rm -f ell/$(DEPDIR)/cipher.Plo -rm -f ell/$(DEPDIR)/dbus-client.Plo -rm -f ell/$(DEPDIR)/dbus-filter.Plo -rm -f ell/$(DEPDIR)/dbus-message.Plo -rm -f ell/$(DEPDIR)/dbus-name-cache.Plo -rm -f ell/$(DEPDIR)/dbus-service.Plo -rm -f ell/$(DEPDIR)/dbus-util.Plo -rm -f ell/$(DEPDIR)/dbus.Plo -rm -f ell/$(DEPDIR)/ecc-external.Plo -rm -f ell/$(DEPDIR)/ecc.Plo -rm -f ell/$(DEPDIR)/ecdh.Plo -rm -f ell/$(DEPDIR)/gvariant-util.Plo -rm -f ell/$(DEPDIR)/hashmap.Plo -rm -f ell/$(DEPDIR)/idle.Plo -rm -f ell/$(DEPDIR)/io.Plo -rm -f ell/$(DEPDIR)/key.Plo -rm -f ell/$(DEPDIR)/log.Plo -rm -f ell/$(DEPDIR)/main.Plo -rm -f ell/$(DEPDIR)/pem.Plo -rm -f ell/$(DEPDIR)/queue.Plo -rm -f ell/$(DEPDIR)/random.Plo -rm -f ell/$(DEPDIR)/settings.Plo -rm -f ell/$(DEPDIR)/signal.Plo -rm -f ell/$(DEPDIR)/siphash.Plo -rm -f ell/$(DEPDIR)/string.Plo -rm -f ell/$(DEPDIR)/strv.Plo -rm -f ell/$(DEPDIR)/tester.Plo -rm -f ell/$(DEPDIR)/time.Plo -rm -f ell/$(DEPDIR)/timeout.Plo -rm -f ell/$(DEPDIR)/tls-extensions.Plo -rm -f ell/$(DEPDIR)/tls-record.Plo -rm -f ell/$(DEPDIR)/tls-suites.Plo -rm -f ell/$(DEPDIR)/tls.Plo -rm -f ell/$(DEPDIR)/utf8.Plo -rm -f ell/$(DEPDIR)/util.Plo -rm -f ell/$(DEPDIR)/uuid.Plo -rm -f emulator/$(DEPDIR)/b1ee.Po -rm -f emulator/$(DEPDIR)/btdev.Po -rm -f emulator/$(DEPDIR)/bthost.Po -rm -f emulator/$(DEPDIR)/hciemu.Po -rm -f emulator/$(DEPDIR)/hfp.Po -rm -f emulator/$(DEPDIR)/le.Po -rm -f emulator/$(DEPDIR)/main.Po -rm -f emulator/$(DEPDIR)/phy.Po -rm -f emulator/$(DEPDIR)/serial.Po -rm -f emulator/$(DEPDIR)/server.Po -rm -f emulator/$(DEPDIR)/smp.Po -rm -f emulator/$(DEPDIR)/vhci.Po -rm -f gdbus/$(DEPDIR)/client.Plo -rm -f gdbus/$(DEPDIR)/mainloop.Plo -rm -f gdbus/$(DEPDIR)/object.Plo -rm -f gdbus/$(DEPDIR)/polkit.Plo -rm -f gdbus/$(DEPDIR)/watch.Plo -rm -f gobex/$(DEPDIR)/gobex-apparam.Po -rm -f gobex/$(DEPDIR)/gobex-defs.Po -rm -f gobex/$(DEPDIR)/gobex-header.Po -rm -f gobex/$(DEPDIR)/gobex-packet.Po -rm -f gobex/$(DEPDIR)/gobex-transfer.Po -rm -f gobex/$(DEPDIR)/gobex.Po -rm -f gobex/$(DEPDIR)/obexd-gobex-apparam.Po -rm -f gobex/$(DEPDIR)/obexd-gobex-defs.Po -rm -f gobex/$(DEPDIR)/obexd-gobex-header.Po -rm -f gobex/$(DEPDIR)/obexd-gobex-packet.Po -rm -f gobex/$(DEPDIR)/obexd-gobex-transfer.Po -rm -f gobex/$(DEPDIR)/obexd-gobex.Po -rm -f lib/bluetooth/$(DEPDIR)/bluetooth.Plo -rm -f lib/bluetooth/$(DEPDIR)/hci.Plo -rm -f lib/bluetooth/$(DEPDIR)/sdp.Plo -rm -f lib/bluetooth/$(DEPDIR)/uuid.Plo -rm -f mesh/$(DEPDIR)/agent.Po -rm -f mesh/$(DEPDIR)/appkey.Po -rm -f mesh/$(DEPDIR)/cfgmod-server.Po -rm -f mesh/$(DEPDIR)/crypto.Po -rm -f mesh/$(DEPDIR)/dbus.Po -rm -f mesh/$(DEPDIR)/friend.Po -rm -f mesh/$(DEPDIR)/keyring.Po -rm -f mesh/$(DEPDIR)/main.Po -rm -f mesh/$(DEPDIR)/manager.Po -rm -f mesh/$(DEPDIR)/mesh-config-json.Po -rm -f mesh/$(DEPDIR)/mesh-io-generic.Po -rm -f mesh/$(DEPDIR)/mesh-io-mgmt.Po -rm -f mesh/$(DEPDIR)/mesh-io-unit.Po -rm -f mesh/$(DEPDIR)/mesh-io.Po -rm -f mesh/$(DEPDIR)/mesh-mgmt.Po -rm -f mesh/$(DEPDIR)/mesh.Po -rm -f mesh/$(DEPDIR)/model.Po -rm -f mesh/$(DEPDIR)/net-keys.Po -rm -f mesh/$(DEPDIR)/net.Po -rm -f mesh/$(DEPDIR)/node.Po -rm -f mesh/$(DEPDIR)/pb-adv.Po -rm -f mesh/$(DEPDIR)/prov-acceptor.Po -rm -f mesh/$(DEPDIR)/prov-initiator.Po -rm -f mesh/$(DEPDIR)/prvbeac-server.Po -rm -f mesh/$(DEPDIR)/remprv-server.Po -rm -f mesh/$(DEPDIR)/rpl.Po -rm -f mesh/$(DEPDIR)/util.Po -rm -f monitor/$(DEPDIR)/a2dp.Po -rm -f monitor/$(DEPDIR)/analyze.Po -rm -f monitor/$(DEPDIR)/att.Po -rm -f monitor/$(DEPDIR)/avctp.Po -rm -f monitor/$(DEPDIR)/avdtp.Po -rm -f monitor/$(DEPDIR)/bnep.Po -rm -f monitor/$(DEPDIR)/broadcom.Po -rm -f monitor/$(DEPDIR)/control.Po -rm -f monitor/$(DEPDIR)/crc.Po -rm -f monitor/$(DEPDIR)/display.Po -rm -f monitor/$(DEPDIR)/ellisys.Po -rm -f monitor/$(DEPDIR)/hcidump.Po -rm -f monitor/$(DEPDIR)/hwdb.Po -rm -f monitor/$(DEPDIR)/intel.Po -rm -f monitor/$(DEPDIR)/jlink.Po -rm -f monitor/$(DEPDIR)/keys.Po -rm -f monitor/$(DEPDIR)/l2cap.Po -rm -f monitor/$(DEPDIR)/ll.Po -rm -f monitor/$(DEPDIR)/lmp.Po -rm -f monitor/$(DEPDIR)/main.Po -rm -f monitor/$(DEPDIR)/msft.Po -rm -f monitor/$(DEPDIR)/packet.Po -rm -f monitor/$(DEPDIR)/rfcomm.Po -rm -f monitor/$(DEPDIR)/sdp.Po -rm -f monitor/$(DEPDIR)/vendor.Po -rm -f obexd/client/$(DEPDIR)/obexd-bip-common.Po -rm -f obexd/client/$(DEPDIR)/obexd-bip.Po -rm -f obexd/client/$(DEPDIR)/obexd-bluetooth.Po -rm -f obexd/client/$(DEPDIR)/obexd-driver.Po -rm -f obexd/client/$(DEPDIR)/obexd-ftp.Po -rm -f obexd/client/$(DEPDIR)/obexd-manager.Po -rm -f obexd/client/$(DEPDIR)/obexd-map-event.Po -rm -f obexd/client/$(DEPDIR)/obexd-map.Po -rm -f obexd/client/$(DEPDIR)/obexd-mns.Po -rm -f obexd/client/$(DEPDIR)/obexd-opp.Po -rm -f obexd/client/$(DEPDIR)/obexd-pbap.Po -rm -f obexd/client/$(DEPDIR)/obexd-session.Po -rm -f obexd/client/$(DEPDIR)/obexd-sync.Po -rm -f obexd/client/$(DEPDIR)/obexd-transfer.Po -rm -f obexd/client/$(DEPDIR)/obexd-transport.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-bluetooth.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-filesystem.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-ftp.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-irmc.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-mas.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-opp.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-pbap.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-pcsuite.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-phonebook-@PLUGIN_PHONEBOOK@.Po -rm -f obexd/plugins/$(DEPDIR)/obexd-vcard.Po -rm -f obexd/src/$(DEPDIR)/obexd-log.Po -rm -f obexd/src/$(DEPDIR)/obexd-logind.Po -rm -f obexd/src/$(DEPDIR)/obexd-main.Po -rm -f obexd/src/$(DEPDIR)/obexd-manager.Po -rm -f obexd/src/$(DEPDIR)/obexd-mimetype.Po -rm -f obexd/src/$(DEPDIR)/obexd-obex.Po -rm -f obexd/src/$(DEPDIR)/obexd-plugin.Po -rm -f obexd/src/$(DEPDIR)/obexd-server.Po -rm -f obexd/src/$(DEPDIR)/obexd-service.Po -rm -f obexd/src/$(DEPDIR)/obexd-transport.Po -rm -f peripheral/$(DEPDIR)/attach.Po -rm -f peripheral/$(DEPDIR)/efivars.Po -rm -f peripheral/$(DEPDIR)/gap.Po -rm -f peripheral/$(DEPDIR)/gatt.Po -rm -f peripheral/$(DEPDIR)/log.Po -rm -f peripheral/$(DEPDIR)/main.Po -rm -f plugins/$(DEPDIR)/bluetoothd-admin.Po -rm -f plugins/$(DEPDIR)/bluetoothd-autopair.Po -rm -f plugins/$(DEPDIR)/bluetoothd-hostname.Po -rm -f plugins/$(DEPDIR)/bluetoothd-neard.Po -rm -f plugins/$(DEPDIR)/bluetoothd-policy.Po -rm -f plugins/$(DEPDIR)/bluetoothd-sixaxis.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-asha.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avctp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avrcp-player.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-bap.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-bass.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-ccp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-control.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-csip.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-gmap.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-hfp-hf.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-mcp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-media.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-micp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-player.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-sink.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-source.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-telephony.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-tmap.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-transport.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-vcp.Po -rm -f profiles/battery/$(DEPDIR)/bas.Po -rm -f profiles/battery/$(DEPDIR)/bluetoothd-bas.Po -rm -f profiles/battery/$(DEPDIR)/bluetoothd-battery.Po -rm -f profiles/cups/$(DEPDIR)/hcrp.Po -rm -f profiles/cups/$(DEPDIR)/main.Po -rm -f profiles/cups/$(DEPDIR)/sdp.Po -rm -f profiles/cups/$(DEPDIR)/spp.Po -rm -f profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Po -rm -f profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Po -rm -f profiles/deviceinfo/$(DEPDIR)/dis.Po -rm -f profiles/gap/$(DEPDIR)/bluetoothd-gas.Po -rm -f profiles/iap/$(DEPDIR)/main.Po -rm -f profiles/input/$(DEPDIR)/bluetoothd-device.Po -rm -f profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Po -rm -f profiles/input/$(DEPDIR)/bluetoothd-hog.Po -rm -f profiles/input/$(DEPDIR)/bluetoothd-manager.Po -rm -f profiles/input/$(DEPDIR)/bluetoothd-server.Po -rm -f profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Po -rm -f profiles/input/$(DEPDIR)/hog-lib.Po -rm -f profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Po -rm -f profiles/midi/$(DEPDIR)/bluetoothd-midi.Po -rm -f profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Po -rm -f profiles/network/$(DEPDIR)/bluetoothd-bnep.Po -rm -f profiles/network/$(DEPDIR)/bluetoothd-connection.Po -rm -f profiles/network/$(DEPDIR)/bluetoothd-manager.Po -rm -f profiles/network/$(DEPDIR)/bluetoothd-server.Po -rm -f profiles/network/$(DEPDIR)/bnep.Po -rm -f profiles/ranging/$(DEPDIR)/bluetoothd-rap.Po -rm -f profiles/ranging/$(DEPDIR)/bluetoothd-rap_hci.Po -rm -f profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Po -rm -f profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Po -rm -f profiles/scanparam/$(DEPDIR)/scpp.Po -rm -f src/$(DEPDIR)/adapter.Po -rm -f src/$(DEPDIR)/adv_monitor.Po -rm -f src/$(DEPDIR)/advertising.Po -rm -f src/$(DEPDIR)/agent.Po -rm -f src/$(DEPDIR)/backtrace.Po -rm -f src/$(DEPDIR)/battery.Po -rm -f src/$(DEPDIR)/bearer.Po -rm -f src/$(DEPDIR)/bluetoothd-adapter.Po -rm -f src/$(DEPDIR)/bluetoothd-adv_monitor.Po -rm -f src/$(DEPDIR)/bluetoothd-advertising.Po -rm -f src/$(DEPDIR)/bluetoothd-agent.Po -rm -f src/$(DEPDIR)/bluetoothd-backtrace.Po -rm -f src/$(DEPDIR)/bluetoothd-battery.Po -rm -f src/$(DEPDIR)/bluetoothd-bearer.Po -rm -f src/$(DEPDIR)/bluetoothd-dbus-common.Po -rm -f src/$(DEPDIR)/bluetoothd-device.Po -rm -f src/$(DEPDIR)/bluetoothd-eir.Po -rm -f src/$(DEPDIR)/bluetoothd-error.Po -rm -f src/$(DEPDIR)/bluetoothd-gatt-client.Po -rm -f src/$(DEPDIR)/bluetoothd-gatt-database.Po -rm -f src/$(DEPDIR)/bluetoothd-log.Po -rm -f src/$(DEPDIR)/bluetoothd-main.Po -rm -f src/$(DEPDIR)/bluetoothd-plugin.Po -rm -f src/$(DEPDIR)/bluetoothd-profile.Po -rm -f src/$(DEPDIR)/bluetoothd-rfkill.Po -rm -f src/$(DEPDIR)/bluetoothd-sdp-client.Po -rm -f src/$(DEPDIR)/bluetoothd-sdp-xml.Po -rm -f src/$(DEPDIR)/bluetoothd-sdpd-database.Po -rm -f src/$(DEPDIR)/bluetoothd-sdpd-request.Po -rm -f src/$(DEPDIR)/bluetoothd-sdpd-server.Po -rm -f src/$(DEPDIR)/bluetoothd-sdpd-service.Po -rm -f src/$(DEPDIR)/bluetoothd-service.Po -rm -f src/$(DEPDIR)/bluetoothd-set.Po -rm -f src/$(DEPDIR)/bluetoothd-settings.Po -rm -f src/$(DEPDIR)/bluetoothd-storage.Po -rm -f src/$(DEPDIR)/bluetoothd-textfile.Po -rm -f src/$(DEPDIR)/bluetoothd-uuid-helper.Po -rm -f src/$(DEPDIR)/dbus-common.Po -rm -f src/$(DEPDIR)/device.Po -rm -f src/$(DEPDIR)/eir.Po -rm -f src/$(DEPDIR)/error.Po -rm -f src/$(DEPDIR)/gatt-client.Po -rm -f src/$(DEPDIR)/gatt-database.Po -rm -f src/$(DEPDIR)/log.Po -rm -f src/$(DEPDIR)/oui.Po -rm -f src/$(DEPDIR)/profile.Po -rm -f src/$(DEPDIR)/rfkill.Po -rm -f src/$(DEPDIR)/sdp-client.Po -rm -f src/$(DEPDIR)/sdp-xml.Po -rm -f src/$(DEPDIR)/sdpd-database.Po -rm -f src/$(DEPDIR)/sdpd-request.Po -rm -f src/$(DEPDIR)/sdpd-server.Po -rm -f src/$(DEPDIR)/sdpd-service.Po -rm -f src/$(DEPDIR)/service.Po -rm -f src/$(DEPDIR)/set.Po -rm -f src/$(DEPDIR)/settings.Po -rm -f src/$(DEPDIR)/storage.Po -rm -f src/$(DEPDIR)/textfile.Po -rm -f src/$(DEPDIR)/uuid-helper.Po -rm -f src/shared/$(DEPDIR)/btp.Po -rm -f src/shared/$(DEPDIR)/libshared_ell_la-ad.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-asha.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-att.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-bap-debug.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-bap.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-bass.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-battery.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-btsnoop.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-ccp.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-crypto.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-csip.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-ecc.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gap.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gatt-client.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gatt-db.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gatt-helpers.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gatt-server.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gmap.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-hci-crypto.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-hci.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-hfp.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-io-ell.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-log.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-mainloop-ell.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-mcp.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-mgmt.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-micp.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-pcap.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-queue.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-rap.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-ringbuf.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-shell.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-timeout-ell.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-tmap.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-uhid.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-uinput.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-util.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-vcp.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-ad.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-asha.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-att.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-bap-debug.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-bap.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-bass.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-battery.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-btsnoop.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-ccp.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-crypto.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-csip.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-ecc.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gap.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gatt-client.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gatt-db.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gatt-helpers.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gatt-server.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gmap.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-hci-crypto.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-hci.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-hfp.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-io-glib.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-log.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-mainloop-glib.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-mainloop-notify.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-mcp.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-mgmt.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-micp.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-pcap.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-queue.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-rap.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-ringbuf.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-shell.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-tester.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-timeout-glib.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-tmap.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-uhid.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-uinput.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-util.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-vcp.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-ad.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-asha.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-att.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-bap-debug.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-bap.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-bass.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-battery.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-btsnoop.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-ccp.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-crypto.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-csip.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-ecc.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gap.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-client.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-db.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-helpers.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-server.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gmap.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-hci-crypto.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-hci.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-hfp.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-io-mainloop.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-log.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-mainloop-notify.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-mainloop.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-mcp.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-mgmt.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-micp.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-pcap.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-queue.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-rap.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-ringbuf.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-shell.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-timeout-mainloop.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-tmap.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-uhid.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-uinput.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-util.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-vcp.Plo -rm -f tools/$(DEPDIR)/3dsp.Po -rm -f tools/$(DEPDIR)/6lowpan-tester.Po -rm -f tools/$(DEPDIR)/advtest.Po -rm -f tools/$(DEPDIR)/avinfo.Po -rm -f tools/$(DEPDIR)/avtest.Po -rm -f tools/$(DEPDIR)/bcmfw.Po -rm -f tools/$(DEPDIR)/bdaddr.Po -rm -f tools/$(DEPDIR)/bluemoon.Po -rm -f tools/$(DEPDIR)/bluetooth-player.Po -rm -f tools/$(DEPDIR)/bnep-tester.Po -rm -f tools/$(DEPDIR)/bneptest.Po -rm -f tools/$(DEPDIR)/btattach.Po -rm -f tools/$(DEPDIR)/btconfig.Po -rm -f tools/$(DEPDIR)/btgatt-client.Po -rm -f tools/$(DEPDIR)/btgatt-server.Po -rm -f tools/$(DEPDIR)/btinfo.Po -rm -f tools/$(DEPDIR)/btiotest.Po -rm -f tools/$(DEPDIR)/btmgmt.Po -rm -f tools/$(DEPDIR)/btmon-logger.Po -rm -f tools/$(DEPDIR)/btproxy.Po -rm -f tools/$(DEPDIR)/btsnoop.Po -rm -f tools/$(DEPDIR)/check-selftest.Po -rm -f tools/$(DEPDIR)/ciptool.Po -rm -f tools/$(DEPDIR)/cltest.Po -rm -f tools/$(DEPDIR)/create-image.Po -rm -f tools/$(DEPDIR)/eddystone.Po -rm -f tools/$(DEPDIR)/gap-tester.Po -rm -f tools/$(DEPDIR)/gatt-service.Po -rm -f tools/$(DEPDIR)/hci-tester.Po -rm -f tools/$(DEPDIR)/hciattach.Po -rm -f tools/$(DEPDIR)/hciattach_ath3k.Po -rm -f tools/$(DEPDIR)/hciattach_bcm43xx.Po -rm -f tools/$(DEPDIR)/hciattach_intel.Po -rm -f tools/$(DEPDIR)/hciattach_qualcomm.Po -rm -f tools/$(DEPDIR)/hciattach_st.Po -rm -f tools/$(DEPDIR)/hciattach_ti.Po -rm -f tools/$(DEPDIR)/hciattach_tialt.Po -rm -f tools/$(DEPDIR)/hciconfig.Po -rm -f tools/$(DEPDIR)/hcieventmask.Po -rm -f tools/$(DEPDIR)/hcisecfilter.Po -rm -f tools/$(DEPDIR)/hcitool.Po -rm -f tools/$(DEPDIR)/hex2hcd.Po -rm -f tools/$(DEPDIR)/hid2hci.Po -rm -f tools/$(DEPDIR)/hwdb.Po -rm -f tools/$(DEPDIR)/ibeacon.Po -rm -f tools/$(DEPDIR)/ioctl-tester.Po -rm -f tools/$(DEPDIR)/iso-tester.Po -rm -f tools/$(DEPDIR)/isotest.Po -rm -f tools/$(DEPDIR)/l2cap-tester.Po -rm -f tools/$(DEPDIR)/l2ping.Po -rm -f tools/$(DEPDIR)/l2test.Po -rm -f tools/$(DEPDIR)/mesh-cfgclient.Po -rm -f tools/$(DEPDIR)/mesh-cfgtest.Po -rm -f tools/$(DEPDIR)/mesh-tester.Po -rm -f tools/$(DEPDIR)/meshctl.Po -rm -f tools/$(DEPDIR)/mgmt-tester.Po -rm -f tools/$(DEPDIR)/mpris-proxy.Po -rm -f tools/$(DEPDIR)/nokfw.Po -rm -f tools/$(DEPDIR)/obex-client-tool.Po -rm -f tools/$(DEPDIR)/obex-server-tool.Po -rm -f tools/$(DEPDIR)/obexctl.Po -rm -f tools/$(DEPDIR)/oobtest.Po -rm -f tools/$(DEPDIR)/rctest.Po -rm -f tools/$(DEPDIR)/rfcomm-tester.Po -rm -f tools/$(DEPDIR)/rfcomm.Po -rm -f tools/$(DEPDIR)/rtlfw.Po -rm -f tools/$(DEPDIR)/sco-tester.Po -rm -f tools/$(DEPDIR)/scotest.Po -rm -f tools/$(DEPDIR)/sdptool.Po -rm -f tools/$(DEPDIR)/seq2bseq.Po -rm -f tools/$(DEPDIR)/smp-tester.Po -rm -f tools/$(DEPDIR)/test_runner-test-runner.Po -rm -f tools/$(DEPDIR)/userchan-tester.Po -rm -f tools/mesh-gatt/$(DEPDIR)/config-client.Po -rm -f tools/mesh-gatt/$(DEPDIR)/config-server.Po -rm -f tools/mesh-gatt/$(DEPDIR)/crypto.Po -rm -f tools/mesh-gatt/$(DEPDIR)/gatt.Po -rm -f tools/mesh-gatt/$(DEPDIR)/net.Po -rm -f tools/mesh-gatt/$(DEPDIR)/node.Po -rm -f tools/mesh-gatt/$(DEPDIR)/onoff-model.Po -rm -f tools/mesh-gatt/$(DEPDIR)/prov-db.Po -rm -f tools/mesh-gatt/$(DEPDIR)/prov.Po -rm -f tools/mesh-gatt/$(DEPDIR)/util.Po -rm -f tools/mesh/$(DEPDIR)/agent.Po -rm -f tools/mesh/$(DEPDIR)/cfgcli.Po -rm -f tools/mesh/$(DEPDIR)/keys.Po -rm -f tools/mesh/$(DEPDIR)/mesh-db.Po -rm -f tools/mesh/$(DEPDIR)/remote.Po -rm -f tools/mesh/$(DEPDIR)/util.Po -rm -f unit/$(DEPDIR)/avctp.Po -rm -f unit/$(DEPDIR)/avdtp.Po -rm -f unit/$(DEPDIR)/avrcp-lib.Po -rm -f unit/$(DEPDIR)/btd.Po -rm -f unit/$(DEPDIR)/test-avctp.Po -rm -f unit/$(DEPDIR)/test-avdtp.Po -rm -f unit/$(DEPDIR)/test-avrcp.Po -rm -f unit/$(DEPDIR)/test-bap.Po -rm -f unit/$(DEPDIR)/test-bass.Po -rm -f unit/$(DEPDIR)/test-battery.Po -rm -f unit/$(DEPDIR)/test-crc.Po -rm -f unit/$(DEPDIR)/test-crypto.Po -rm -f unit/$(DEPDIR)/test-ecc.Po -rm -f unit/$(DEPDIR)/test-eir.Po -rm -f unit/$(DEPDIR)/test-gatt.Po -rm -f unit/$(DEPDIR)/test-gattrib.Po -rm -f unit/$(DEPDIR)/test-gdbus-client.Po -rm -f unit/$(DEPDIR)/test-gmap.Po -rm -f unit/$(DEPDIR)/test-gobex-apparam.Po -rm -f unit/$(DEPDIR)/test-gobex-header.Po -rm -f unit/$(DEPDIR)/test-gobex-packet.Po -rm -f unit/$(DEPDIR)/test-gobex-transfer.Po -rm -f unit/$(DEPDIR)/test-gobex.Po -rm -f unit/$(DEPDIR)/test-hfp.Po -rm -f unit/$(DEPDIR)/test-hog.Po -rm -f unit/$(DEPDIR)/test-lib.Po -rm -f unit/$(DEPDIR)/test-mcp.Po -rm -f unit/$(DEPDIR)/test-mgmt.Po -rm -f unit/$(DEPDIR)/test-micp.Po -rm -f unit/$(DEPDIR)/test-profile.Po -rm -f unit/$(DEPDIR)/test-queue.Po -rm -f unit/$(DEPDIR)/test-rap.Po -rm -f unit/$(DEPDIR)/test-ringbuf.Po -rm -f unit/$(DEPDIR)/test-sdp.Po -rm -f unit/$(DEPDIR)/test-tester.Po -rm -f unit/$(DEPDIR)/test-textfile.Po -rm -f unit/$(DEPDIR)/test-tmap.Po -rm -f unit/$(DEPDIR)/test-uhid.Po -rm -f unit/$(DEPDIR)/test-util.Po -rm -f unit/$(DEPDIR)/test-uuid.Po -rm -f unit/$(DEPDIR)/test-vcp.Po -rm -f unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Po -rm -f unit/$(DEPDIR)/test_midi-test-midi.Po -rm -f unit/$(DEPDIR)/util.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic \ maintainer-clean-local mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-binPROGRAMS uninstall-confDATA \ uninstall-cupsPROGRAMS uninstall-dbusDATA \ uninstall-dbussessionbusDATA uninstall-dbussystembusDATA \ uninstall-dist_zshcompletionDATA uninstall-libLTLIBRARIES \ uninstall-man uninstall-pkgconfigDATA \ uninstall-pkgincludeHEADERS uninstall-pkglibexecPROGRAMS \ uninstall-pluginLTLIBRARIES uninstall-rulesDATA \ uninstall-stateDATA uninstall-systemdsystemunitDATA \ uninstall-systemduserunitDATA uninstall-testSCRIPTS \ uninstall-udevPROGRAMS @$(NORMAL_INSTALL) $(MAKE) $(AM_MAKEFLAGS) uninstall-hook uninstall-man: uninstall-man1 uninstall-man5 uninstall-man7 \ uninstall-man8 .MAKE: all check check-am install install-am install-data-am \ install-exec install-strip uninstall-am .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles am--refresh check \ check-TESTS check-am clean clean-binPROGRAMS clean-cscope \ clean-cupsPROGRAMS clean-generic clean-libLTLIBRARIES \ clean-libtool clean-local clean-noinstLIBRARIES \ clean-noinstLTLIBRARIES clean-noinstPROGRAMS \ clean-pkglibexecPROGRAMS clean-pluginLTLIBRARIES \ clean-udevPROGRAMS cscope cscopelist-am ctags ctags-am dist \ dist-all dist-bzip2 dist-bzip3 dist-gzip dist-lzip dist-shar \ dist-tarZ dist-xz dist-zip dist-zstd distcheck distclean \ distclean-compile distclean-generic distclean-hdr \ distclean-libtool distclean-tags distcleancheck distdir \ distuninstallcheck dvi dvi-am html html-am info info-am \ install install-am install-binPROGRAMS install-confDATA \ install-cupsPROGRAMS install-data install-data-am \ install-data-hook install-dbusDATA install-dbussessionbusDATA \ install-dbussystembusDATA install-dist_zshcompletionDATA \ install-dvi install-dvi-am install-exec install-exec-am \ install-html install-html-am install-info install-info-am \ install-libLTLIBRARIES install-man install-man1 install-man5 \ install-man7 install-man8 install-pdf install-pdf-am \ install-pkgconfigDATA install-pkgincludeHEADERS \ install-pkglibexecPROGRAMS install-pluginLTLIBRARIES \ install-ps install-ps-am install-rulesDATA install-stateDATA \ install-strip install-systemdsystemunitDATA \ install-systemduserunitDATA install-testSCRIPTS \ install-udevPROGRAMS installcheck installcheck-am installdirs \ maintainer-clean maintainer-clean-generic \ maintainer-clean-local mostlyclean mostlyclean-compile \ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ recheck tags tags-am uninstall uninstall-am \ uninstall-binPROGRAMS uninstall-confDATA \ uninstall-cupsPROGRAMS uninstall-dbusDATA \ uninstall-dbussessionbusDATA uninstall-dbussystembusDATA \ uninstall-dist_zshcompletionDATA uninstall-hook \ uninstall-libLTLIBRARIES uninstall-man uninstall-man1 \ uninstall-man5 uninstall-man7 uninstall-man8 \ uninstall-pkgconfigDATA uninstall-pkgincludeHEADERS \ uninstall-pkglibexecPROGRAMS uninstall-pluginLTLIBRARIES \ uninstall-rulesDATA uninstall-stateDATA \ uninstall-systemdsystemunitDATA uninstall-systemduserunitDATA \ uninstall-testSCRIPTS uninstall-udevPROGRAMS .PRECIOUS: Makefile bluetoothd-fix-permissions: install -dm755 $(DESTDIR)$(confdir) install -dm700 $(DESTDIR)$(statedir) @BTPCLIENT_TRUE@client/btpclient/btpclient.$(OBJEXT): src/libshared-ell.la ell/internal @OBEX_TRUE@@SYSTEMD_TRUE@obexd-add-service-symlink: @OBEX_TRUE@@SYSTEMD_TRUE@ $(LN_S) -f obex.service $(DESTDIR)$(SYSTEMD_USERUNITDIR)/dbus-org.bluez.obex.service @OBEX_TRUE@@SYSTEMD_TRUE@obexd-remove-service-symlink: @OBEX_TRUE@@SYSTEMD_TRUE@ rm -f $(DESTDIR)$(SYSTEMD_USERUNITDIR)/dbus-org.bluez.obex.service @OBEX_TRUE@@SYSTEMD_FALSE@obexd-add-service-symlink: @OBEX_TRUE@@SYSTEMD_FALSE@obexd-remove-service-symlink: @OBEX_FALSE@obexd-add-service-symlink: @OBEX_FALSE@obexd-remove-service-symlink: obexd/src/plugin.$(OBJEXT): obexd/src/builtin.h obexd/src/builtin.h: obexd/src/genbuiltin $(obexd_builtin_sources) $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_GEN)$(srcdir)/obexd/src/genbuiltin $(obexd_builtin_modules) > $@ @MESH_TRUE@mesh/mesh.$(OBJEXT): ell/internal @MESH_TRUE@mesh/main.$(OBJEXT): src/builtin.h lib/bluetooth/bluetooth.h @SYSTEMD_TRUE@install-data-hook: obexd-add-service-symlink @SYSTEMD_FALSE@install-data-hook: bluetoothd-fix-permissions obexd-add-service-symlink uninstall-hook: obexd-remove-service-symlink %.1: %.rst Makefile $(RST2MAN_PROCESS) %.5: %.rst Makefile $(RST2MAN_PROCESS) %.7: %-protocol.rst Makefile $(RST2MAN_PROCESS) %.8: %.rst Makefile $(RST2MAN_PROCESS) src/builtin.h: src/genbuiltin $(builtin_sources) $(AM_V_GEN)$(srcdir)/src/genbuiltin $(builtin_modules) > $@ tools/%.rules: $(AM_V_at)$(MKDIR_P) tools $(AM_V_GEN)cp $(srcdir)/$(subst 97-,,$@) $@ ell/shared: Makefile $(AM_V_at)$(MKDIR_P) ell $(AM_V_GEN)for f in $(ell_shared) ; do \ if [ ! -f $$f ] ; then \ $(LN_S) -t ell -f $(abs_srcdir)/../ell/$$f ; \ fi \ done > $@ ell/internal: Makefile $(AM_V_at)$(MKDIR_P) ell $(AM_V_GEN)for f in $(ell_headers) $(ell_sources) ; do \ if [ ! -f $$f ] ; then \ $(LN_S) -t ell -f $(abs_srcdir)/../ell/$$f ; \ fi \ done > $@ ell/ell.h: Makefile $(AM_V_at)echo -n > $@ $(AM_V_GEN)for f in $(ell_headers) ; do \ echo "#include <$$f>" >> $@ ; \ done maintainer-clean-local: -rm -rf ell clean-coverage: @COVERAGE_TRUE@ @lcov --directory $(top_builddir) --zerocounters @COVERAGE_TRUE@ $(RM) -r coverage $(top_builddir)/coverage.info @COVERAGE_TRUE@coverage: check @COVERAGE_TRUE@ @lcov --compat-libtool --directory $(top_builddir) --capture \ @COVERAGE_TRUE@ --output-file $(top_builddir)/coverage.info @COVERAGE_TRUE@ $(AM_V_at)$(MKDIR_P) coverage @COVERAGE_TRUE@ @genhtml -o coverage/ $(top_builddir)/coverage.info clean-local: clean-coverage -find $(top_builddir) -name "*.gcno" -delete -find $(top_builddir) -name "*.gcda" -delete # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: # Tell GNU make to disable its built-in pattern rules. %:: %,v %:: RCS/%,v %:: RCS/% %:: s.% %:: SCCS/s.% bluez-5.87/PaxHeaders/plugins0000644000000000000000000000005015221755531013265 xustar0020 atime=1783094113 20 ctime=1783094105 bluez-5.87/plugins/0000755000000000000000000000000015221755531013023 5ustar00rootrootbluez-5.87/plugins/PaxHeaders/sixaxis.c0000644000000000000000000000005015200607060015164 xustar0020 atime=1783092683 20 ctime=1783094105 bluez-5.87/plugins/sixaxis.c0000644000000000000000000003312715200607060014653 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2009 Bastien Nocera * Copyright (C) 2011 Antonio Ospite * Copyright (C) 2013 Szymon Janc * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "src/adapter.h" #include "src/device.h" #include "src/agent.h" #include "src/plugin.h" #include "src/log.h" #include "src/shared/util.h" #include "profiles/input/server.h" #include "profiles/input/sixaxis.h" #define DS4_FEATURE_REPORT_PAIRING_INFO 0x12 #define DS4_FEATURE_REPORT_PAIRING_INFO_SIZE 16 struct authentication_closure { guint auth_id; char *sysfs_path; struct btd_adapter *adapter; struct btd_device *device; int fd; bdaddr_t bdaddr; /* device bdaddr */ CablePairingType type; }; struct authentication_destroy_closure { struct authentication_closure *closure; bool remove_device; }; static struct udev *ctx = NULL; static struct udev_monitor *monitor = NULL; static guint watch_id = 0; /* key = sysfs_path (const str), value = auth_closure */ static GHashTable *pending_auths = NULL; #define SIXAXIS_HID_SDP_RECORD "3601920900000A000100000900013503191124090004"\ "350D35061901000900113503190011090006350909656E09006A090100090009350"\ "8350619112409010009000D350F350D350619010009001335031900110901002513"\ "576972656C65737320436F6E74726F6C6C65720901012513576972656C657373204"\ "36F6E74726F6C6C6572090102251B536F6E7920436F6D707574657220456E746572"\ "7461696E6D656E74090200090100090201090100090202080009020308210902042"\ "8010902052801090206359A35980822259405010904A101A1028501750895011500"\ "26FF00810375019513150025013500450105091901291381027501950D0600FF810"\ "3150026FF0005010901A10075089504350046FF0009300931093209358102C00501"\ "75089527090181027508953009019102750895300901B102C0A1028502750895300"\ "901B102C0A10285EE750895300901B102C0A10285EF750895300901B102C0C00902"\ "07350835060904090901000902082800090209280109020A280109020B090100090"\ "20C093E8009020D280009020E2800" /* Make sure to unset auth_id if already handled */ static void auth_closure_destroy(struct authentication_closure *closure, bool remove_device) { if (closure->auth_id) btd_cancel_authorization(closure->auth_id); if (remove_device) btd_adapter_remove_device(closure->adapter, closure->device); close(closure->fd); g_free(closure->sysfs_path); g_free(closure); } static int sixaxis_get_device_bdaddr(int fd, bdaddr_t *bdaddr) { uint8_t buf[18]; int ret; memset(buf, 0, sizeof(buf)); buf[0] = 0xf2; ret = ioctl(fd, HIDIOCGFEATURE(sizeof(buf)), buf); if (ret < 0) { error("sixaxis: failed to read device address (%s)", strerror(errno)); return ret; } baswap(bdaddr, (bdaddr_t *) (buf + 4)); return 0; } static int ds4_get_device_bdaddr(int fd, bdaddr_t *bdaddr) { uint8_t buf[DS4_FEATURE_REPORT_PAIRING_INFO_SIZE]; int ret; memset(buf, 0, sizeof(buf)); buf[0] = DS4_FEATURE_REPORT_PAIRING_INFO; ret = ioctl(fd, HIDIOCGFEATURE(sizeof(buf)), buf); if (ret < 0) { error("sixaxis: failed to read DS4 device address (%s)", strerror(errno)); return ret; } /* address is little-endian on DS4 */ bacpy(bdaddr, (bdaddr_t*) (buf + 1)); return 0; } static int get_device_bdaddr(int fd, bdaddr_t *bdaddr, CablePairingType type) { if (type == CABLE_PAIRING_SIXAXIS) return sixaxis_get_device_bdaddr(fd, bdaddr); else if (type == CABLE_PAIRING_DS4) return ds4_get_device_bdaddr(fd, bdaddr); return -1; } static int sixaxis_get_central_bdaddr(int fd, bdaddr_t *bdaddr) { uint8_t buf[8]; int ret; memset(buf, 0, sizeof(buf)); buf[0] = 0xf5; ret = ioctl(fd, HIDIOCGFEATURE(sizeof(buf)), buf); if (ret < 0) { error("sixaxis: failed to read central address (%s)", strerror(errno)); return ret; } baswap(bdaddr, (bdaddr_t *) (buf + 2)); return 0; } static int ds4_get_central_bdaddr(int fd, bdaddr_t *bdaddr) { uint8_t buf[DS4_FEATURE_REPORT_PAIRING_INFO_SIZE]; int ret; memset(buf, 0, sizeof(buf)); buf[0] = DS4_FEATURE_REPORT_PAIRING_INFO; ret = ioctl(fd, HIDIOCGFEATURE(sizeof(buf)), buf); if (ret < 0) { error("sixaxis: failed to read DS4 central address (%s)", strerror(errno)); return ret; } /* address is little-endian on DS4 */ bacpy(bdaddr, (bdaddr_t*) (buf + 10)); return 0; } static int get_central_bdaddr(int fd, bdaddr_t *bdaddr, CablePairingType type) { if (type == CABLE_PAIRING_SIXAXIS) return sixaxis_get_central_bdaddr(fd, bdaddr); else if (type == CABLE_PAIRING_DS4) return ds4_get_central_bdaddr(fd, bdaddr); return -1; } static int sixaxis_set_central_bdaddr(int fd, const bdaddr_t *bdaddr) { uint8_t buf[8]; int ret; buf[0] = 0xf5; buf[1] = 0x01; baswap((bdaddr_t *) (buf + 2), bdaddr); ret = ioctl(fd, HIDIOCSFEATURE(sizeof(buf)), buf); if (ret < 0) error("sixaxis: failed to write central address (%s)", strerror(errno)); return ret; } static int ds4_set_central_bdaddr(int fd, const bdaddr_t *bdaddr) { uint8_t buf[23]; int ret; buf[0] = 0x13; bacpy((bdaddr_t*) (buf + 1), bdaddr); /* TODO: we could put the key here but there is no way to force a re-loading of link keys to the kernel from here. */ memset(buf + 7, 0, 16); ret = ioctl(fd, HIDIOCSFEATURE(sizeof(buf)), buf); if (ret < 0) error("sixaxis: failed to write DS4 central address (%s)", strerror(errno)); return ret; } static int set_central_bdaddr(int fd, const bdaddr_t *bdaddr, CablePairingType type) { if (type == CABLE_PAIRING_SIXAXIS) return sixaxis_set_central_bdaddr(fd, bdaddr); else if (type == CABLE_PAIRING_DS4) return ds4_set_central_bdaddr(fd, bdaddr); return -1; } static bool is_auth_pending(struct authentication_closure *closure) { GHashTableIter iter; gpointer value; g_hash_table_iter_init(&iter, pending_auths); while (g_hash_table_iter_next(&iter, NULL, &value)) { struct authentication_closure *c = value; if (c == closure) return true; } return false; } static gboolean auth_closure_destroy_idle(gpointer user_data) { struct authentication_destroy_closure *destroy = user_data; auth_closure_destroy(destroy->closure, destroy->remove_device); g_free(destroy); return false; } static void agent_auth_cb(DBusError *derr, void *user_data) { struct authentication_closure *closure = user_data; struct authentication_destroy_closure *destroy; char central_addr[18], adapter_addr[18], device_addr[18]; bdaddr_t central_bdaddr; const bdaddr_t *adapter_bdaddr; bool remove_device = true; if (!is_auth_pending(closure)) return; /* Don't try to remove this auth, we're handling it already */ closure->auth_id = 0; if (derr != NULL) { DBG("Agent replied negatively, removing temporary device"); goto out; } if (get_central_bdaddr(closure->fd, ¢ral_bdaddr, closure->type) < 0) goto out; adapter_bdaddr = btd_adapter_get_address(closure->adapter); if (bacmp(adapter_bdaddr, ¢ral_bdaddr)) { if (set_central_bdaddr(closure->fd, adapter_bdaddr, closure->type) < 0) goto out; } remove_device = false; btd_device_set_temporary(closure->device, false); if (closure->type == CABLE_PAIRING_SIXAXIS) { btd_device_set_record(closure->device, HID_UUID, SIXAXIS_HID_SDP_RECORD); } device_set_cable_pairing(closure->device, true); server_set_cable_pairing(adapter_bdaddr, true); ba2str(&closure->bdaddr, device_addr); ba2str(¢ral_bdaddr, central_addr); ba2str(adapter_bdaddr, adapter_addr); DBG("remote %s old_central %s new_central %s", device_addr, central_addr, adapter_addr); out: g_hash_table_steal(pending_auths, closure->sysfs_path); /* btd_adapter_remove_device() cannot be called in this * callback or it would lead to a double-free in while * trying to cancel the authentication that's being processed, * so clean up in an idle */ destroy = g_new0(struct authentication_destroy_closure, 1); destroy->closure = closure; destroy->remove_device = remove_device; g_idle_add(auth_closure_destroy_idle, destroy); } static bool setup_device(int fd, const char *sysfs_path, const struct cable_pairing *cp, struct btd_adapter *adapter) { bdaddr_t device_bdaddr; const bdaddr_t *adapter_bdaddr; struct btd_device *device; struct authentication_closure *closure; if (get_device_bdaddr(fd, &device_bdaddr, cp->type) < 0) return false; /* This can happen if controller was plugged while already setup and * connected eg. to charge up battery. */ device = btd_adapter_find_device(adapter, &device_bdaddr, BDADDR_BREDR); if (device && btd_device_has_uuid(device, HID_UUID) && (btd_device_is_connected(device) || btd_device_is_trusted(device))) { char device_addr[18]; ba2str(&device_bdaddr, device_addr); DBG("device %s already known, skipping", device_addr); return false; } device = btd_adapter_get_device(adapter, &device_bdaddr, BDADDR_BREDR); if (!device) { error("sixaxis: unable to set up a new device"); return false; } info("sixaxis: setting up new device"); btd_device_device_set_name(device, cp->name); btd_device_set_pnpid(device, cp->source, cp->vid, cp->pid, cp->version); btd_device_set_temporary(device, true); closure = g_new0(struct authentication_closure, 1); if (!closure) { btd_adapter_remove_device(adapter, device); return false; } closure->adapter = adapter; closure->device = device; closure->sysfs_path = g_strdup(sysfs_path); closure->fd = fd; bacpy(&closure->bdaddr, &device_bdaddr); closure->type = cp->type; adapter_bdaddr = btd_adapter_get_address(adapter); closure->auth_id = btd_request_authorization_cable_configured( adapter_bdaddr, &device_bdaddr, HID_UUID, agent_auth_cb, closure); if (closure->auth_id == 0) { error("sixaxis: could not request cable authorization"); auth_closure_destroy(closure, true); return false; } g_hash_table_insert(pending_auths, closure->sysfs_path, closure); return true; } static const struct cable_pairing * get_pairing_type_for_device(struct udev_device *udevice, uint16_t *bus, char **sysfs_path) { struct udev_device *hid_parent; const char *hid_name; const char *hid_id; const struct cable_pairing *cp; uint16_t vid, pid; hid_parent = udev_device_get_parent_with_subsystem_devtype(udevice, "hid", NULL); if (!hid_parent) return NULL; hid_id = udev_device_get_property_value(hid_parent, "HID_ID"); if (!hid_id || sscanf(hid_id, "%hx:%hx:%hx", bus, &vid, &pid) != 3) return NULL; hid_name = udev_device_get_property_value(hid_parent, "HID_NAME"); cp = get_pairing(vid, pid, hid_name); *sysfs_path = g_strdup(udev_device_get_syspath(udevice)); return cp; } static void device_added(struct udev_device *udevice) { struct btd_adapter *adapter; uint16_t bus; char *sysfs_path = NULL; const struct cable_pairing *cp; int fd; adapter = btd_adapter_get_default(); if (!adapter) return; cp = get_pairing_type_for_device(udevice, &bus, &sysfs_path); if (!cp || (cp->type != CABLE_PAIRING_SIXAXIS && cp->type != CABLE_PAIRING_DS4)) { g_free(sysfs_path); return; } if (bus != BUS_USB) { g_free(sysfs_path); return; } info("sixaxis: compatible device connected: %s (%04X:%04X %s)", cp->name, cp->vid, cp->pid, sysfs_path); fd = open(udev_device_get_devnode(udevice), O_RDWR); if (fd < 0) { g_free(sysfs_path); return; } /* Only close the fd if an authentication is not pending */ if (!setup_device(fd, sysfs_path, cp, adapter)) close(fd); g_free(sysfs_path); } static void device_removed(struct udev_device *udevice) { struct authentication_closure *closure; const char *sysfs_path; sysfs_path = udev_device_get_syspath(udevice); if (!sysfs_path) return; closure = g_hash_table_lookup(pending_auths, sysfs_path); if (!closure) return; g_hash_table_steal(pending_auths, sysfs_path); auth_closure_destroy(closure, true); } static gboolean monitor_watch(GIOChannel *source, GIOCondition condition, gpointer data) { struct udev_device *udevice; udevice = udev_monitor_receive_device(monitor); if (!udevice) return TRUE; if (!g_strcmp0(udev_device_get_action(udevice), "add")) device_added(udevice); else if (!g_strcmp0(udev_device_get_action(udevice), "remove")) device_removed(udevice); udev_device_unref(udevice); return TRUE; } static int sixaxis_init(void) { GIOChannel *channel; DBG(""); ctx = udev_new(); if (!ctx) return -EIO; monitor = udev_monitor_new_from_netlink(ctx, "udev"); if (!monitor) { udev_unref(ctx); ctx = NULL; return -EIO; } /* Listen for newly connected hidraw interfaces */ udev_monitor_filter_add_match_subsystem_devtype(monitor, "hidraw", NULL); udev_monitor_enable_receiving(monitor); channel = g_io_channel_unix_new(udev_monitor_get_fd(monitor)); watch_id = g_io_add_watch(channel, G_IO_IN, monitor_watch, NULL); g_io_channel_unref(channel); pending_auths = g_hash_table_new(g_str_hash, g_str_equal); return 0; } static void sixaxis_exit(void) { GHashTableIter iter; gpointer value; DBG(""); g_hash_table_iter_init(&iter, pending_auths); while (g_hash_table_iter_next(&iter, NULL, &value)) { struct authentication_closure *closure = value; auth_closure_destroy(closure, true); } g_hash_table_destroy(pending_auths); pending_auths = NULL; g_source_remove(watch_id); watch_id = 0; udev_monitor_unref(monitor); monitor = NULL; udev_unref(ctx); ctx = NULL; } BLUETOOTH_PLUGIN_DEFINE(sixaxis, VERSION, BLUETOOTH_PLUGIN_PRIORITY_LOW, sixaxis_init, sixaxis_exit) bluez-5.87/plugins/PaxHeaders/autopair.c0000644000000000000000000000005015063045254015330 xustar0020 atime=1783093606 20 ctime=1783094105 bluez-5.87/plugins/autopair.c0000644000000000000000000001641215063045254015015 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012 Google Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/device.h" #include "src/log.h" #include "src/storage.h" #include "src/shared/util.h" /* * Nintendo Wii Remote devices require the bdaddr of the host as pin input for * authentication. This plugin registers a pin-callback and forces this pin * to be used for authentication. * * There are two ways to place the wiimote into discoverable mode. * - Pressing the red-sync button on the back of the wiimote. This module * supports pairing via this method. Auto-reconnect should be possible after * the device was paired once. * - Pressing the 1+2 buttons on the front of the wiimote. This module does * not support this method since this method never enables auto-reconnect. * Hence, pairing is not needed. Use it without pairing if you want. * After connecting the wiimote you should immediately connect to the input * service of the wiimote. If you don't, the wiimote will close the connection. * The wiimote waits about 5 seconds until it turns off again. * Auto-reconnect is only enabled when pairing with the wiimote via the red * sync-button and then connecting to the input service. If you do not connect * to the input service, then auto-reconnect is not enabled. * If enabled, the wiimote connects to the host automatically when any button * is pressed. */ static uint16_t wii_ids[][2] = { { 0x057e, 0x0306 }, /* 1st gen */ { 0x054c, 0x0306 }, /* LEGO wiimote */ { 0x057e, 0x0330 }, /* 2nd gen */ }; static const char *wii_names[] = { "Nintendo RVL-CNT-01", /* 1st gen */ "Nintendo RVL-CNT-01-TR", /* 2nd gen */ "Nintendo RVL-CNT-01-UC", /* Wii U Pro Controller */ "Nintendo RVL-WBC-01", /* Balance Board */ }; static ssize_t wii_pincb(struct btd_adapter *adapter, struct btd_device *device, char *pinbuf, bool *display, unsigned int attempt) { uint16_t vendor, product; char addr[18], name[25]; unsigned int i; /* Only try the pin code once per device. If it's not correct then it's * an unknown device. */ if (attempt > 1) return 0; ba2str(device_get_address(device), addr); vendor = btd_device_get_vendor(device); product = btd_device_get_product(device); device_get_name(device, name, sizeof(name)); for (i = 0; i < G_N_ELEMENTS(wii_ids); ++i) { if (vendor == wii_ids[i][0] && product == wii_ids[i][1]) goto found; } for (i = 0; i < G_N_ELEMENTS(wii_names); ++i) { if (g_str_equal(name, wii_names[i])) goto found; } return 0; found: DBG("Forcing fixed pin on detected wiimote %s", addr); memcpy(pinbuf, btd_adapter_get_address(adapter), 6); return 6; } /* * Plugin to handle automatic pairing of devices with reduced user * interaction, including implementing the recommendation of the HID spec * for keyboard devices. * * The plugin works by intercepting the PIN request for devices; if the * device is a keyboard a random six-digit numeric PIN is generated and * returned, flagged for displaying using DisplayPinCode. * */ static ssize_t autopair_pincb(struct btd_adapter *adapter, struct btd_device *device, char *pinbuf, bool *display, unsigned int attempt) { char addr[18]; char pinstr[7]; char name[25]; uint32_t class; uint32_t val; ssize_t ret; /* Try with the wii_pincb first */ ret = wii_pincb(adapter, device, pinbuf, display, attempt); if (ret > 0) return ret; ba2str(device_get_address(device), addr); class = btd_device_get_class(device); device_get_name(device, name, sizeof(name)); DBG("device '%s' (%s) class: 0x%x vid/pid: 0x%X/0x%X", name, addr, class, btd_device_get_vendor (device), btd_device_get_product (device)); /* The iCade shouldn't use random PINs like normal keyboards */ if (strstr(name, "iCade") != NULL) return 0; /* This is a class-based pincode guesser. Ignore devices with an * unknown class. */ if (class == 0) return 0; switch ((class & 0x1f00) >> 8) { case 0x04: /* Audio/Video */ switch ((class & 0xfc) >> 2) { case 0x01: /* Wearable Headset Device */ case 0x02: /* Hands-free Device */ case 0x06: /* Headphones */ case 0x07: /* Portable Audio */ case 0x0a: /* HiFi Audio Device */ { const char *pincodes[] = { "0000", "1234", "1111" }; const char *pincode; if (attempt > G_N_ELEMENTS(pincodes)) return 0; pincode = pincodes[attempt - 1]; memcpy(pinbuf, pincode, strlen(pincode)); return strlen(pincode); } } break; case 0x05: /* Peripheral */ switch ((class & 0xc0) >> 6) { case 0x00: switch ((class & 0x1e) >> 2) { case 0x01: /* Joystick */ case 0x02: /* Gamepad */ case 0x03: /* Remote Control */ if (attempt > 1) return 0; memcpy(pinbuf, "0000", 4); return 4; } break; case 0x01: /* Keyboard */ case 0x03: /* Combo keyboard/pointing device */ /* For keyboards rejecting the first random code * in less than 500ms, try a fixed code. */ if (attempt > 1 && device_bonding_last_duration(device) < 500) { /* Don't try more than one dumb code */ if (attempt > 2) return 0; /* Try "0000" as the code for the second * attempt. */ memcpy(pinbuf, "0000", 4); return 4; } /* Never try more than 3 random pincodes. */ if (attempt >= 4) return 0; if (util_getrandom(&val, sizeof(val), 0) < 0) { error("Failed to get a random pincode"); return 0; } snprintf(pinstr, sizeof(pinstr), "%06u", val % 1000000); *display = true; memcpy(pinbuf, pinstr, 6); return 6; case 0x02: /* Pointing device */ if (attempt > 1) return 0; memcpy(pinbuf, "0000", 4); return 4; } break; case 0x06: /* Imaging */ if (class & 0x80) { /* Printer */ if (attempt > 1) return 0; memcpy(pinbuf, "0000", 4); return 4; } break; } return 0; } static int autopair_probe(struct btd_adapter *adapter) { btd_adapter_register_pin_cb(adapter, autopair_pincb); return 0; } static void autopair_remove(struct btd_adapter *adapter) { btd_adapter_unregister_pin_cb(adapter, autopair_pincb); } static struct btd_adapter_driver autopair_driver = { .name = "autopair", .probe = autopair_probe, .remove = autopair_remove, }; static int autopair_init(void) { /* Initialize the random seed from /dev/urandom */ unsigned int seed; int fd, err; ssize_t n; fd = open("/dev/urandom", O_RDONLY); if (fd < 0) { err = -errno; error("Failed to open /dev/urandom: %s (%d)", strerror(-err), -err); return err; } n = read(fd, &seed, sizeof(seed)); if (n < (ssize_t) sizeof(seed)) { err = (n == -1) ? -errno : -EIO; error("Failed to read %zu bytes from /dev/urandom: %s (%d)", sizeof(seed), strerror(-err), -err); close(fd); return err; } close(fd); srand(seed); return btd_register_adapter_driver(&autopair_driver); } static void autopair_exit(void) { btd_unregister_adapter_driver(&autopair_driver); } BLUETOOTH_PLUGIN_DEFINE(autopair, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, autopair_init, autopair_exit) bluez-5.87/plugins/PaxHeaders/neard.c0000644000000000000000000000005015200607060014565 xustar0020 atime=1783092683 20 ctime=1783094105 bluez-5.87/plugins/neard.c0000644000000000000000000005010615200607060014250 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2013 Tieto Poland * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/hci.h" #include "bluetooth/sdp.h" #include "gdbus/gdbus.h" #include "src/plugin.h" #include "src/log.h" #include "src/dbus-common.h" #include "src/adapter.h" #include "src/device.h" #include "src/eir.h" #include "src/agent.h" #include "src/btd.h" #include "src/shared/util.h" #define NEARD_NAME "org.neard" #define NEARD_PATH "/" #define NEARD_MANAGER_INTERFACE "org.neard.Manager" #define AGENT_INTERFACE "org.neard.HandoverAgent" #define AGENT_PATH "/org/bluez/neard_handover_agent" #define AGENT_CARRIER_TYPE "bluetooth" #define ERROR_INTERFACE "org.neard.HandoverAgent.Error" static guint watcher_id = 0; static char *neard_service = NULL; static bool agent_register_postpone = false; /* For NFC mimetype limits max OOB EIR size */ #define NFC_OOB_EIR_MAX UINT8_MAX enum cps { CPS_ACTIVE, CPS_INACTIVE, CPS_ACTIVATING, CPS_UNKNOWN, }; struct oob_params { bdaddr_t address; uint32_t class; char *name; struct queue *services; uint8_t *hash; uint8_t *randomizer; uint8_t *pin; int pin_len; enum cps power_state; }; static void free_oob_params(struct oob_params *params) { queue_destroy(params->services, free); g_free(params->name); g_free(params->hash); g_free(params->randomizer); free(params->pin); } static DBusMessage *error_reply(DBusMessage *msg, int error) { const char *name; if (error == EINPROGRESS) name = ERROR_INTERFACE ".InProgress"; else name = ERROR_INTERFACE ".Failed"; return g_dbus_create_error(msg, name , "%s", strerror(error)); } static void register_agent(bool append_carrier); static void register_agent_cb(DBusPendingCall *call, void *user_data) { DBusMessage *reply; DBusError err; static bool try_fallback = true; reply = dbus_pending_call_steal_reply(call); dbus_error_init(&err); if (dbus_set_error_from_message(&err, reply)) { if (g_str_equal(DBUS_ERROR_UNKNOWN_METHOD, err.name) && try_fallback) { DBG("Register to neard failed, trying legacy way"); register_agent(false); try_fallback = false; } else { error("neard manager replied with an error: %s, %s", err.name, err.message); g_dbus_unregister_interface(btd_get_dbus_connection(), AGENT_PATH, AGENT_INTERFACE); try_fallback = true; } dbus_error_free(&err); dbus_message_unref(reply); return; } dbus_message_unref(reply); neard_service = g_strdup(dbus_message_get_sender(reply)); try_fallback = true; info("Registered as neard handover agent"); } static void register_agent(bool append_carrier) { DBusMessage *message; DBusPendingCall *call; const char *path = AGENT_PATH; const char *carrier = AGENT_CARRIER_TYPE; message = dbus_message_new_method_call(NEARD_NAME, NEARD_PATH, NEARD_MANAGER_INTERFACE, "RegisterHandoverAgent"); if (!message) { error("Couldn't allocate D-Bus message"); return; } dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); if (append_carrier) dbus_message_append_args(message, DBUS_TYPE_STRING, &carrier, DBUS_TYPE_INVALID); if (!g_dbus_send_message_with_reply(btd_get_dbus_connection(), message, &call, -1)) { dbus_message_unref(message); error("D-Bus send failed"); return; } dbus_pending_call_set_notify(call, register_agent_cb, NULL, NULL); dbus_pending_call_unref(call); dbus_message_unref(message); } static void unregister_agent(void) { DBusMessage *message; const char *path = AGENT_PATH; const char *carrier = AGENT_CARRIER_TYPE; g_free(neard_service); neard_service = NULL; message = dbus_message_new_method_call(NEARD_NAME, NEARD_PATH, NEARD_MANAGER_INTERFACE, "UnregisterHandoverAgent"); if (!message) { error("Couldn't allocate D-Bus message"); goto unregister; } dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); dbus_message_append_args(message, DBUS_TYPE_STRING, &carrier, DBUS_TYPE_INVALID); if (!g_dbus_send_message(btd_get_dbus_connection(), message)) error("D-Bus send failed"); unregister: g_dbus_unregister_interface(btd_get_dbus_connection(), AGENT_PATH, AGENT_INTERFACE); } static void add_power_state(DBusMessageIter *dict, struct btd_adapter *adapter) { const char *state; if (btd_adapter_get_powered(adapter) && btd_adapter_get_connectable(adapter)) state = "active"; else state = "inactive"; dict_append_entry(dict, "State", DBUS_TYPE_STRING, &state); } static DBusMessage *create_request_oob_reply(struct btd_adapter *adapter, const uint8_t *hash, const uint8_t *randomizer, DBusMessage *msg) { DBusMessage *reply; DBusMessageIter iter; DBusMessageIter dict; uint8_t eir[NFC_OOB_EIR_MAX]; uint8_t *peir = eir; int len; len = eir_create_oob(btd_adapter_get_address(adapter), btd_adapter_get_name(adapter), btd_adapter_get_class(adapter), hash, randomizer, btd_opts.did_vendor, btd_opts.did_product, btd_opts.did_version, btd_opts.did_source, btd_adapter_get_services(adapter), eir); reply = dbus_message_new_method_return(msg); if (!reply) return NULL; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); dict_append_array(&dict, "EIR", DBUS_TYPE_BYTE, &peir, len); add_power_state(&dict, adapter); dbus_message_iter_close_container(&iter, &dict); return reply; } static void read_local_complete(struct btd_adapter *adapter, const uint8_t *hash, const uint8_t *randomizer, void *user_data) { DBusMessage *msg = user_data; DBusMessage *reply; DBG(""); if (neard_service == NULL) { dbus_message_unref(msg); if (agent_register_postpone) { agent_register_postpone = false; register_agent(true); } return; } if (hash && randomizer) reply = create_request_oob_reply(adapter, hash, randomizer, msg); else reply = error_reply(msg, EIO); dbus_message_unref(msg); if (!g_dbus_send_message(btd_get_dbus_connection(), reply)) error("D-Bus send failed"); } static void bonding_complete(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t status, void *user_data) { DBusMessage *msg = user_data; DBusMessage *reply; DBG(""); if (neard_service == NULL) { dbus_message_unref(msg); if (agent_register_postpone) { agent_register_postpone = false; register_agent(true); } return; } if (status) reply = error_reply(msg, EIO); else reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID); dbus_message_unref(msg); if (!g_dbus_send_message(btd_get_dbus_connection(), reply)) error("D-Bus send failed"); } static int check_device(struct btd_device *device) { if (!device) return -ENOENT; /* If already paired */ if (device_is_paired(device, BDADDR_BREDR)) { DBG("already paired"); return -EALREADY; } /* Pairing in progress... */ if (device_is_bonding(device, NULL)) { DBG("pairing in progress"); return -EINPROGRESS; } return 0; } static int process_eir(uint8_t *eir, size_t size, struct oob_params *remote) { struct eir_data eir_data; DBG("size %zu", size); memset(&eir_data, 0, sizeof(eir_data)); if (eir_parse_oob(&eir_data, eir, size) < 0) return -EINVAL; bacpy(&remote->address, &eir_data.addr); remote->class = eir_data.class; remote->name = eir_data.name; eir_data.name = NULL; remote->services = eir_data.services; eir_data.services = NULL; remote->hash = eir_data.hash; eir_data.hash = NULL; remote->randomizer = eir_data.randomizer; eir_data.randomizer = NULL; eir_data_free(&eir_data); return 0; } /* * This is (barely documented) Nokia extension format, most work was done by * reverse engineering. * * Binary format varies among different devices, type depends on first byte * 0x00 - BT address not reversed, 16 bytes authentication data (all zeros) * 0x01 - BT address not reversed, 16 bytes authentication data (4 digit PIN, * padded with zeros) * 0x02 - BT address not reversed, 16 bytes authentication data (not sure if * 16 digit PIN or link key?, Nokia refers to it as ' Public Key') * 0x10 - BT address reversed, no authentication data * 0x24 - BT address not reversed, 4 bytes authentication data (4 digit PIN) * * General structure: * 1 byte - marker * 6 bytes - BT address (reversed or not, depends on marker) * 3 bytes - Class of Device * 0, 4 or 16 bytes - authentication data, interpretation depends on marker * 1 bytes - name length * N bytes - name */ static int process_nokia_long (void *data, size_t size, uint8_t marker, struct oob_params *remote) { struct { bdaddr_t address; uint8_t class[3]; uint8_t authentication[16]; uint8_t name_len; uint8_t name[0]; } __attribute__((packed)) *n = data; if (size != sizeof(*n) + n->name_len) return -EINVAL; /* address is not reverted */ baswap(&remote->address, &n->address); remote->class = n->class[0] | (n->class[1] << 8) | (n->class[2] << 16); if (n->name_len > 0) remote->name = g_strndup((char *)n->name, n->name_len); if (marker == 0x01) { remote->pin = util_memdup(n->authentication, 4); remote->pin_len = 4; } else if (marker == 0x02) { remote->pin = util_memdup(n->authentication, 16); remote->pin_len = 16; } return 0; } static int process_nokia_short (void *data, size_t size, struct oob_params *remote) { struct { bdaddr_t address; uint8_t class[3]; uint8_t authentication[4]; uint8_t name_len; uint8_t name[0]; } __attribute__((packed)) *n = data; if (size != sizeof(*n) + n->name_len) return -EINVAL; /* address is not reverted */ baswap(&remote->address, &n->address); remote->class = n->class[0] | (n->class[1] << 8) | (n->class[2] << 16); if (n->name_len > 0) remote->name = g_strndup((char *)n->name, n->name_len); remote->pin = util_memdup(n->authentication, 4); remote->pin_len = 4; return 0; } static int process_nokia_extra_short (void *data, size_t size, struct oob_params *remote) { struct { bdaddr_t address; uint8_t class[3]; uint8_t name_len; uint8_t name[0]; } __attribute__((packed)) *n = data; if (size != sizeof(*n) + n->name_len) return -EINVAL; bacpy(&remote->address, &n->address); remote->class = n->class[0] | (n->class[1] << 8) | (n->class[2] << 16); if (n->name_len > 0) remote->name = g_strndup((char *)n->name, n->name_len); return 0; } static int process_nokia_com_bt(uint8_t *data, size_t size, struct oob_params *remote) { uint8_t marker; marker = *data++; size--; DBG("marker: 0x%.2x size: %zu", marker, size); switch (marker) { case 0x00: case 0x01: case 0x02: return process_nokia_long(data, size, marker, remote); case 0x10: return process_nokia_extra_short(data, size, remote); case 0x24: return process_nokia_short(data, size, remote); default: warn("Not supported Nokia NFC extension (0x%.2x)", marker); return -EPROTONOSUPPORT; } } static enum cps process_state(const char *state) { if (strcasecmp(state, "active") == 0) return CPS_ACTIVE; if (strcasecmp(state, "activating") == 0) return CPS_ACTIVATING; if (strcasecmp(state, "inactive") == 0) return CPS_INACTIVE; return CPS_UNKNOWN; } static int process_message(DBusMessage *msg, struct oob_params *remote) { DBusMessageIter iter; DBusMessageIter dict; dbus_message_iter_init(msg, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) return -EINVAL; /* set CPS to unknown in case State was not provided */ remote->power_state = CPS_UNKNOWN; dbus_message_iter_recurse(&iter, &dict); while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter value; DBusMessageIter entry; const char *key; dbus_message_iter_recurse(&dict, &entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) goto error; dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); if (strcasecmp(key, "EIR") == 0) { DBusMessageIter array; uint8_t *eir; int size; /* nokia.com:bt and EIR should not be passed together */ if (bacmp(&remote->address, BDADDR_ANY) != 0) goto error; if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_ARRAY) goto error; dbus_message_iter_recurse(&value, &array); dbus_message_iter_get_fixed_array(&array, &eir, &size); if (process_eir(eir, size, remote) < 0) goto error; } else if (strcasecmp(key, "nokia.com:bt") == 0) { DBusMessageIter array; uint8_t *data; int size; /* nokia.com:bt and EIR should not be passed together */ if (bacmp(&remote->address, BDADDR_ANY) != 0) goto error; if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_ARRAY) goto error; dbus_message_iter_recurse(&value, &array); dbus_message_iter_get_fixed_array(&array, &data, &size); if (process_nokia_com_bt(data, size, remote)) goto error; } else if (strcasecmp(key, "State") == 0) { const char *state; if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_STRING) goto error; dbus_message_iter_get_basic(&value, &state); remote->power_state = process_state(state); if (remote->power_state == CPS_UNKNOWN) goto error; } dbus_message_iter_next(&dict); } /* Check if 'State' was passed along with one of other fields */ if (remote->power_state != CPS_UNKNOWN && bacmp(&remote->address, BDADDR_ANY) == 0) return -EINVAL; return 0; error: if (bacmp(&remote->address, BDADDR_ANY) != 0) { free_oob_params(remote); memset(remote, 0, sizeof(*remote)); } return -EINVAL; } static int check_adapter(struct btd_adapter *adapter) { if (!adapter) return -ENOENT; if (btd_adapter_check_oob_handler(adapter)) return -EINPROGRESS; if (!btd_adapter_ssp_enabled(adapter)) return -ENOTSUP; return 0; } static void store_params(struct btd_adapter *adapter, struct btd_device *device, struct oob_params *params) { if (params->class != 0) device_set_class(device, params->class); if (params->name) { device_store_cached_name(device, params->name); btd_device_device_set_name(device, params->name); } if (params->services) device_add_eir_uuids(device, params->services); if (params->hash) { btd_adapter_add_remote_oob_data(adapter, ¶ms->address, params->hash, params->randomizer); } else if (params->pin_len) { /* TODO * Handle PIN, for now only discovery mode and 'common' PINs * that might be provided by agent will work correctly. */ } } static DBusMessage *push_oob(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_adapter *adapter; struct agent *agent; struct oob_handler *handler; struct oob_params remote; struct btd_device *device; uint8_t io_cap; int err; if (neard_service == NULL || !g_str_equal(neard_service, dbus_message_get_sender(msg))) return error_reply(msg, EPERM); DBG(""); adapter = btd_adapter_get_default(); err = check_adapter(adapter); if (err < 0) return error_reply(msg, -err); if (!btd_adapter_get_powered(adapter)) return error_reply(msg, ENONET); agent = adapter_get_agent(adapter); if (!agent) return error_reply(msg, ENONET); io_cap = agent_get_io_capability(agent); agent_unref(agent); memset(&remote, 0, sizeof(remote)); err = process_message(msg, &remote); if (err < 0) return error_reply(msg, -err); if (bacmp(&remote.address, BDADDR_ANY) == 0) { free_oob_params(&remote); return error_reply(msg, EINVAL); } device = btd_adapter_get_device(adapter, &remote.address, BDADDR_BREDR); err = check_device(device); if (err < 0) { free_oob_params(&remote); /* already paired, reply immediately */ if (err == -EALREADY) return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); return error_reply(msg, -err); } if (!btd_adapter_get_pairable(adapter)) { free_oob_params(&remote); return error_reply(msg, ENONET); } store_params(adapter, device, &remote); free_oob_params(&remote); err = adapter_create_bonding(adapter, device_get_address(device), BDADDR_BREDR, io_cap); if (err < 0) return error_reply(msg, -err); handler = g_new0(struct oob_handler, 1); handler->bonding_cb = bonding_complete; bacpy(&handler->remote_addr, device_get_address(device)); handler->user_data = dbus_message_ref(msg); btd_adapter_set_oob_handler(adapter, handler); return NULL; } static DBusMessage *request_oob(DBusConnection *conn, DBusMessage *msg, void *data) { struct btd_adapter *adapter; struct oob_handler *handler; struct oob_params remote; struct btd_device *device; int err; if (neard_service == NULL || !g_str_equal(neard_service, dbus_message_get_sender(msg))) return error_reply(msg, EPERM); DBG(""); adapter = btd_adapter_get_default(); err = check_adapter(adapter); if (err < 0) return error_reply(msg, -err); memset(&remote, 0, sizeof(remote)); err = process_message(msg, &remote); if (err < 0) return error_reply(msg, -err); if (bacmp(&remote.address, BDADDR_ANY) == 0) { if (btd_adapter_get_powered(adapter)) goto read_local; goto done; } device = btd_adapter_get_device(adapter, &remote.address, BDADDR_BREDR); err = check_device(device); if (err < 0) goto done; if (!btd_adapter_get_pairable(adapter)) { err = -ENONET; goto done; } store_params(adapter, device, &remote); if (remote.hash && btd_adapter_get_powered(adapter)) goto read_local; done: free_oob_params(&remote); if (err < 0 && err != -EALREADY) return error_reply(msg, -err); return create_request_oob_reply(adapter, NULL, NULL, msg); read_local: free_oob_params(&remote); err = btd_adapter_read_local_oob_data(adapter); if (err < 0) return error_reply(msg, -err); handler = g_new0(struct oob_handler, 1); handler->read_local_cb = read_local_complete; handler->user_data = dbus_message_ref(msg); btd_adapter_set_oob_handler(adapter, handler); return NULL; } static DBusMessage *release(DBusConnection *conn, DBusMessage *msg, void *user_data) { if (neard_service == NULL || !g_str_equal(neard_service, dbus_message_get_sender(msg))) return error_reply(msg, EPERM); DBG(""); g_free(neard_service); neard_service = NULL; g_dbus_unregister_interface(conn, AGENT_PATH, AGENT_INTERFACE); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static const GDBusMethodTable neard_methods[] = { { GDBUS_ASYNC_METHOD("RequestOOB", GDBUS_ARGS({ "data", "a{sv}" }), GDBUS_ARGS({ "data", "a{sv}" }), request_oob) }, { GDBUS_ASYNC_METHOD("PushOOB", GDBUS_ARGS({ "data", "a{sv}"}), NULL, push_oob) }, { GDBUS_METHOD("Release", NULL, NULL, release) }, { } }; static void neard_appeared(DBusConnection *conn, void *user_data) { struct btd_adapter *adapter; DBG(""); if (!g_dbus_register_interface(conn, AGENT_PATH, AGENT_INTERFACE, neard_methods, NULL, NULL, NULL, NULL)) { error("neard interface init failed on path " AGENT_PATH); return; } /* * If there is pending action ongoing when neard appeared, possibly * due to neard crash or release before action was completed, postpone * register until action is finished. */ adapter = btd_adapter_get_default(); if (adapter && btd_adapter_check_oob_handler(adapter)) agent_register_postpone = true; else register_agent(true); } static void neard_vanished(DBusConnection *conn, void *user_data) { DBG(""); /* neard existed without unregistering agent */ if (neard_service != NULL) { g_free(neard_service); neard_service = NULL; g_dbus_unregister_interface(conn, AGENT_PATH, AGENT_INTERFACE); } } static int neard_init(void) { DBG("Setup neard plugin"); watcher_id = g_dbus_add_service_watch(btd_get_dbus_connection(), NEARD_NAME, neard_appeared, neard_vanished, NULL, NULL); if (watcher_id == 0) return -ENOMEM; return 0; } static void neard_exit(void) { DBG("Cleanup neard plugin"); g_dbus_remove_watch(btd_get_dbus_connection(), watcher_id); watcher_id = 0; if (neard_service != NULL) unregister_agent(); } BLUETOOTH_PLUGIN_DEFINE(neard, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, neard_init, neard_exit) bluez-5.87/plugins/PaxHeaders/hostname.c0000644000000000000000000000005015142177620015323 xustar0020 atime=1783093605 20 ctime=1783094105 bluez-5.87/plugins/hostname.c0000644000000000000000000001746415142177620015020 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "gdbus/gdbus.h" #include "src/dbus-common.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/log.h" /* http://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm */ #define MAJOR_CLASS_MISCELLANEOUS 0x00 #define MAJOR_CLASS_COMPUTER 0x01 #define MINOR_CLASS_UNCATEGORIZED 0x00 #define MINOR_CLASS_DESKTOP 0x01 #define MINOR_CLASS_SERVER 0x02 #define MINOR_CLASS_LAPTOP 0x03 #define MINOR_CLASS_HANDHELD 0x04 #define MINOR_CLASS_PALM_SIZED 0x05 #define MINOR_CLASS_WEARABLE 0x06 #define MINOR_CLASS_TABLET 0x07 static uint8_t major_class = MAJOR_CLASS_MISCELLANEOUS; static uint8_t minor_class = MINOR_CLASS_UNCATEGORIZED; static char *pretty_hostname = NULL; static char *static_hostname = NULL; static char *transient_hostname = NULL; static guint hostname_id = 0; /* * Fallback to static hostname only if empty pretty hostname was already * received. */ static const char *get_hostname(void) { if (pretty_hostname) { if (g_str_equal(pretty_hostname, "") == FALSE) return pretty_hostname; if (static_hostname && g_str_equal(static_hostname, "") == FALSE) return static_hostname; if (transient_hostname && g_str_equal(transient_hostname, "") == FALSE) return transient_hostname; } return NULL; } static void update_name(struct btd_adapter *adapter, gpointer user_data) { const char *hostname = get_hostname(); if (hostname == NULL) return; if (btd_adapter_is_default(adapter)) { DBG("name: %s", hostname); btd_adapter_set_name(adapter, hostname); } else { uint16_t index = btd_adapter_get_index(adapter); char *str; /* Avoid "some device #0" names, start at #1 */ str = g_strdup_printf("%s #%u", hostname, index + 1); DBG("name: %s", str); btd_adapter_set_name(adapter, str); g_free(str); } } static void update_class(struct btd_adapter *adapter, gpointer user_data) { if (major_class == MAJOR_CLASS_MISCELLANEOUS) return; DBG("major: 0x%02x minor: 0x%02x", major_class, minor_class); btd_adapter_set_class(adapter, major_class, minor_class); } static const struct { const char *chassis; uint8_t major_class; uint8_t minor_class; } chassis_table[] = { { "desktop", MAJOR_CLASS_COMPUTER, MINOR_CLASS_DESKTOP }, { "server", MAJOR_CLASS_COMPUTER, MINOR_CLASS_SERVER }, { "laptop", MAJOR_CLASS_COMPUTER, MINOR_CLASS_LAPTOP }, { "handset", MAJOR_CLASS_COMPUTER, MINOR_CLASS_HANDHELD }, { "tablet", MAJOR_CLASS_COMPUTER, MINOR_CLASS_TABLET }, { } }; static void property_changed(GDBusProxy *proxy, const char *name, DBusMessageIter *iter, void *user_data) { if (g_str_equal(name, "PrettyHostname") == TRUE) { if (iter == NULL) { g_dbus_proxy_refresh_property(proxy, name); return; } if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRING) { const char *str; dbus_message_iter_get_basic(iter, &str); DBG("pretty hostname: '%s'", str); g_free(pretty_hostname); pretty_hostname = g_strdup(str); btd_adapter_foreach(update_name, NULL); } } else if (g_str_equal(name, "StaticHostname") == TRUE) { if (iter == NULL) { g_dbus_proxy_refresh_property(proxy, name); return; } if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRING) { const char *str; dbus_message_iter_get_basic(iter, &str); DBG("static hostname: '%s'", str); g_free(static_hostname); static_hostname = g_strdup(str); btd_adapter_foreach(update_name, NULL); } } else if (g_str_equal(name, "Chassis") == TRUE) { if (iter == NULL) { g_dbus_proxy_refresh_property(proxy, name); return; } if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRING) { const char *str; int i; dbus_message_iter_get_basic(iter, &str); DBG("chassis: '%s'", str); for (i = 0; chassis_table[i].chassis; i++) { if (strcmp(chassis_table[i].chassis, str)) continue; major_class = chassis_table[i].major_class; minor_class = chassis_table[i].minor_class; btd_adapter_foreach(update_class, NULL); break; } } } } static void read_transient_hostname(void) { struct utsname u; if (uname(&u) != 0) { g_free(transient_hostname); transient_hostname = NULL; DBG("failed to read transient hostname"); return; } g_free(transient_hostname); transient_hostname = g_strdup(u.nodename); DBG("read transient hostname: '%s'", transient_hostname); } static gboolean hostname_cb(GIOChannel *io, GIOCondition cond, gpointer user_data) { DBG("transient hostname changed"); read_transient_hostname(); btd_adapter_foreach(update_class, NULL); return TRUE; } static int hostname_probe(struct btd_adapter *adapter) { DBG(""); update_name(adapter, NULL); update_class(adapter, NULL); return 0; } static void hostname_remove(struct btd_adapter *adapter) { DBG(""); } static struct btd_adapter_driver hostname_driver = { .name = "hostname", .probe = hostname_probe, .remove = hostname_remove, }; static void read_dmi_fallback(void) { char *contents; int i, type; const char *str; if (g_file_get_contents("/sys/class/dmi/id/chassis_type", &contents, NULL, NULL) == FALSE) return; type = atoi(contents); g_free(contents); if (type < 0 || type > 0x1D) return; /* from systemd hostname chassis list */ switch (type) { case 0x3: case 0x4: case 0x6: case 0x7: str = "desktop"; break; case 0x8: case 0x9: case 0xA: case 0xE: str = "laptop"; break; case 0xB: str = "handset"; break; case 0x11: case 0x1C: str = "server"; break; default: return; } DBG("chassis: %s", str); for (i = 0; chassis_table[i].chassis; i++) { if (!strcmp(chassis_table[i].chassis, str)) { major_class = chassis_table[i].major_class; minor_class = chassis_table[i].minor_class; break; } } DBG("major: 0x%02x minor: 0x%02x", major_class, minor_class); } static GDBusClient *hostname_client = NULL; static GDBusProxy *hostname_proxy = NULL; static int hostname_init(void) { DBusConnection *conn = btd_get_dbus_connection(); int fd; int err; read_dmi_fallback(); read_transient_hostname(); hostname_client = g_dbus_client_new(conn, "org.freedesktop.hostname1", "/org/freedesktop/hostname1"); if (!hostname_client) return -EIO; hostname_proxy = g_dbus_proxy_new(hostname_client, "/org/freedesktop/hostname1", "org.freedesktop.hostname1"); if (!hostname_proxy) { g_dbus_client_unref(hostname_client); hostname_client = NULL; return -EIO; } g_dbus_proxy_set_property_watch(hostname_proxy, property_changed, NULL); err = btd_register_adapter_driver(&hostname_driver); if (err < 0) { g_dbus_proxy_unref(hostname_proxy); hostname_proxy = NULL; g_dbus_client_unref(hostname_client); hostname_client = NULL; } fd = open("/proc/sys/kernel/hostname", O_RDONLY); if (fd < 0) { error("open(/proc/sys/kernel/hostname): %s (%d)", strerror(errno), errno); } else { GIOChannel *io = g_io_channel_unix_new(fd); hostname_id = g_io_add_watch(io, G_IO_ERR, hostname_cb, NULL); g_io_channel_unref(io); } return err; } static void hostname_exit(void) { btd_unregister_adapter_driver(&hostname_driver); if (hostname_proxy) { g_dbus_proxy_unref(hostname_proxy); hostname_proxy = NULL; } if (hostname_client) { g_dbus_client_unref(hostname_client); hostname_client = NULL; } if (hostname_id != 0) { g_source_remove(hostname_id); hostname_id = 0; } g_free(pretty_hostname); g_free(static_hostname); g_free(transient_hostname); } BLUETOOTH_PLUGIN_DEFINE(hostname, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, hostname_init, hostname_exit) bluez-5.87/plugins/PaxHeaders/policy.c0000644000000000000000000000005015063045254015003 xustar0020 atime=1783093607 20 ctime=1783094105 bluez-5.87/plugins/policy.c0000644000000000000000000005627115063045254014477 0ustar00rootroot// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2013 Intel Corporation. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/sdp.h" #include "bluetooth/uuid.h" #include "bluetooth/mgmt.h" #include "src/log.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/device.h" #include "src/service.h" #include "src/profile.h" #include "src/btd.h" #include "src/shared/timeout.h" #include "src/shared/util.h" #define CONTROL_CONNECT_TIMEOUT 2 #define SOURCE_RETRY_TIMEOUT 2 #define SINK_RETRY_TIMEOUT SOURCE_RETRY_TIMEOUT #define HS_RETRY_TIMEOUT SOURCE_RETRY_TIMEOUT #define CT_RETRY_TIMEOUT 1 #define TG_RETRY_TIMEOUT CT_RETRY_TIMEOUT #define SOURCE_RETRIES 1 #define SINK_RETRIES SOURCE_RETRIES #define HS_RETRIES SOURCE_RETRIES #define CT_RETRIES 1 #define TG_RETRIES CT_RETRIES struct reconnect_data { struct btd_device *dev; bool reconnect; GSList *services; unsigned int timer; bool active; unsigned int attempt; bool on_resume; }; static const char *default_reconnect[] = { HSP_AG_UUID, HFP_AG_UUID, A2DP_SOURCE_UUID, A2DP_SINK_UUID, NULL }; static char **reconnect_uuids = NULL; static const size_t default_attempts = 7; static size_t reconnect_attempts = 0; static const int default_intervals[] = { 1, 2, 4, 8, 16, 32, 64 }; static int *reconnect_intervals = NULL; static size_t reconnect_intervals_len = 0; static const int default_resume_delay = 2; static int resume_delay; static GSList *reconnects = NULL; static unsigned int service_id = 0; static GSList *devices = NULL; static const bool default_auto_enable = true; static bool auto_enable = false; struct policy_data { struct btd_device *dev; unsigned int source_timer; uint8_t source_retries; unsigned int sink_timer; uint8_t sink_retries; unsigned int ct_timer; uint8_t ct_retries; unsigned int tg_timer; uint8_t tg_retries; unsigned int hs_timer; uint8_t hs_retries; }; static struct reconnect_data *reconnect_find(struct btd_device *dev) { GSList *l; for (l = reconnects; l; l = g_slist_next(l)) { struct reconnect_data *reconnect = l->data; if (reconnect->dev == dev) return reconnect; } return NULL; } static void policy_connect(struct policy_data *data, struct btd_service *service) { struct btd_profile *profile = btd_service_get_profile(service); struct reconnect_data *reconnect; reconnect = reconnect_find(btd_service_get_device(service)); if (reconnect && reconnect->active) return; DBG("%s profile %s", device_get_path(data->dev), profile->name); btd_service_connect(service); } static void policy_disconnect(struct policy_data *data, struct btd_service *service) { struct btd_profile *profile = btd_service_get_profile(service); DBG("%s profile %s", device_get_path(data->dev), profile->name); btd_service_disconnect(service); } static bool policy_connect_ct(gpointer user_data) { struct policy_data *data = user_data; struct btd_service *service; data->ct_timer = 0; data->ct_retries++; service = btd_device_get_service(data->dev, AVRCP_REMOTE_UUID); if (service != NULL) policy_connect(data, service); return FALSE; } static void policy_set_ct_timer(struct policy_data *data, int timeout) { if (data->ct_timer > 0) timeout_remove(data->ct_timer); data->ct_timer = timeout_add_seconds(timeout, policy_connect_ct, data, NULL); } static struct policy_data *find_data(struct btd_device *dev) { GSList *l; for (l = devices; l; l = l->next) { struct policy_data *data = l->data; if (data->dev == dev) return data; } return NULL; } static void policy_remove(void *user_data) { struct policy_data *data = user_data; if (data->source_timer > 0) timeout_remove(data->source_timer); if (data->sink_timer > 0) timeout_remove(data->sink_timer); if (data->ct_timer > 0) timeout_remove(data->ct_timer); if (data->tg_timer > 0) timeout_remove(data->tg_timer); if (data->hs_timer > 0) timeout_remove(data->hs_timer); g_free(data); } static struct policy_data *policy_get_data(struct btd_device *dev) { struct policy_data *data; data = find_data(dev); if (data != NULL) return data; data = g_new0(struct policy_data, 1); data->dev = dev; devices = g_slist_prepend(devices, data); return data; } static bool policy_connect_hs(gpointer user_data) { struct policy_data *data = user_data; struct btd_service *service; data->hs_timer = 0; data->hs_retries++; service = btd_device_get_service(data->dev, HFP_HS_UUID); if (service == NULL) service = btd_device_get_service(data->dev, HSP_HS_UUID); if (service != NULL) policy_connect(data, service); return FALSE; } static void policy_set_hs_timer(struct policy_data *data) { if (data->hs_timer > 0) timeout_remove(data->hs_timer); data->hs_timer = timeout_add_seconds(HS_RETRY_TIMEOUT, policy_connect_hs, data, NULL); } static bool policy_connect_sink(gpointer user_data) { struct policy_data *data = user_data; struct btd_service *service; data->sink_timer = 0; data->sink_retries++; service = btd_device_get_service(data->dev, A2DP_SINK_UUID); if (service != NULL) policy_connect(data, service); return FALSE; } static void policy_set_sink_timer(struct policy_data *data) { if (data->sink_timer > 0) timeout_remove(data->sink_timer); data->sink_timer = timeout_add_seconds(SINK_RETRY_TIMEOUT, policy_connect_sink, data, NULL); } static void sink_cb(struct btd_service *service, btd_service_state_t old_state, btd_service_state_t new_state) { struct btd_device *dev = btd_service_get_device(service); struct policy_data *data; struct btd_service *controller, *hs; controller = btd_device_get_service(dev, AVRCP_REMOTE_UUID); if (controller == NULL) return; data = policy_get_data(dev); switch (new_state) { case BTD_SERVICE_STATE_UNAVAILABLE: if (data->sink_timer > 0) { timeout_remove(data->sink_timer); data->sink_timer = 0; } break; case BTD_SERVICE_STATE_DISCONNECTED: if (old_state == BTD_SERVICE_STATE_CONNECTING) { int err = btd_service_get_error(service); if (err == -EAGAIN) { if (data->sink_retries < SINK_RETRIES) policy_set_sink_timer(data); else data->sink_retries = 0; break; } else if (data->sink_timer > 0) { timeout_remove(data->sink_timer); data->sink_timer = 0; } } if (data->ct_timer > 0) { timeout_remove(data->ct_timer); data->ct_timer = 0; } else if (btd_service_get_state(controller) != BTD_SERVICE_STATE_DISCONNECTED) policy_disconnect(data, controller); break; case BTD_SERVICE_STATE_CONNECTING: break; case BTD_SERVICE_STATE_CONNECTED: if (data->sink_timer > 0) { timeout_remove(data->sink_timer); data->sink_timer = 0; } data->sink_retries = 0; /* Try connecting HSP/HFP if it is not connected */ hs = btd_device_get_service(dev, HFP_HS_UUID); if (hs == NULL) hs = btd_device_get_service(data->dev, HSP_HS_UUID); if (hs && btd_service_get_state(hs) != BTD_SERVICE_STATE_CONNECTED) policy_set_hs_timer(data); /* Check if service initiate the connection then proceed * immediately otherwise set timer */ if (btd_service_is_initiator(service)) policy_connect(data, controller); else if (btd_service_get_state(controller) != BTD_SERVICE_STATE_CONNECTED) policy_set_ct_timer(data, CONTROL_CONNECT_TIMEOUT); break; case BTD_SERVICE_STATE_DISCONNECTING: break; } } static void hs_cb(struct btd_service *service, btd_service_state_t old_state, btd_service_state_t new_state) { struct btd_device *dev = btd_service_get_device(service); struct policy_data *data; struct btd_service *sink; /* If the device supports Sink set a timer to connect it as well */ sink = btd_device_get_service(dev, A2DP_SINK_UUID); if (sink == NULL) return; data = policy_get_data(dev); switch (new_state) { case BTD_SERVICE_STATE_UNAVAILABLE: if (data->hs_timer > 0) { timeout_remove(data->hs_timer); data->hs_timer = 0; } break; case BTD_SERVICE_STATE_DISCONNECTED: if (old_state == BTD_SERVICE_STATE_CONNECTING) { int err = btd_service_get_error(service); if (err == -EAGAIN) { if (data->hs_retries < HS_RETRIES) policy_set_hs_timer(data); else data->hs_retries = 0; break; } else if (data->hs_timer > 0) { timeout_remove(data->hs_timer); data->hs_timer = 0; } } break; case BTD_SERVICE_STATE_CONNECTING: break; case BTD_SERVICE_STATE_CONNECTED: if (data->hs_timer > 0) { timeout_remove(data->hs_timer); data->hs_timer = 0; } data->hs_retries = 0; /* Check if service initiate the connection then proceed * immediately otherwise set timer */ if (btd_service_is_initiator(service)) policy_connect(data, sink); else if (btd_service_get_state(sink) != BTD_SERVICE_STATE_CONNECTED) policy_set_sink_timer(data); break; case BTD_SERVICE_STATE_DISCONNECTING: break; } } static bool policy_connect_tg(gpointer user_data) { struct policy_data *data = user_data; struct btd_service *service; data->tg_timer = 0; data->tg_retries++; service = btd_device_get_service(data->dev, AVRCP_TARGET_UUID); if (service != NULL) policy_connect(data, service); return FALSE; } static void policy_set_tg_timer(struct policy_data *data, int timeout) { if (data->tg_timer > 0) timeout_remove(data->tg_timer); data->tg_timer = timeout_add_seconds(timeout, policy_connect_tg, data, NULL); } static bool policy_connect_source(gpointer user_data) { struct policy_data *data = user_data; struct btd_service *service; data->source_timer = 0; data->source_retries++; service = btd_device_get_service(data->dev, A2DP_SOURCE_UUID); if (service != NULL) policy_connect(data, service); return FALSE; } static void policy_set_source_timer(struct policy_data *data) { if (data->source_timer > 0) timeout_remove(data->source_timer); data->source_timer = timeout_add_seconds(SOURCE_RETRY_TIMEOUT, policy_connect_source, data, NULL); } static void source_cb(struct btd_service *service, btd_service_state_t old_state, btd_service_state_t new_state) { struct btd_device *dev = btd_service_get_device(service); struct policy_data *data; struct btd_service *target; target = btd_device_get_service(dev, AVRCP_TARGET_UUID); if (target == NULL) return; data = policy_get_data(dev); switch (new_state) { case BTD_SERVICE_STATE_UNAVAILABLE: if (data->source_timer > 0) { timeout_remove(data->source_timer); data->source_timer = 0; } break; case BTD_SERVICE_STATE_DISCONNECTED: if (old_state == BTD_SERVICE_STATE_CONNECTING) { int err = btd_service_get_error(service); if (err == -EAGAIN) { if (data->source_retries < SOURCE_RETRIES) policy_set_source_timer(data); else data->source_retries = 0; break; } else if (data->source_timer > 0) { timeout_remove(data->source_timer); data->source_timer = 0; } } if (data->tg_timer > 0) { timeout_remove(data->tg_timer); data->tg_timer = 0; } else if (btd_service_get_state(target) != BTD_SERVICE_STATE_DISCONNECTED) policy_disconnect(data, target); break; case BTD_SERVICE_STATE_CONNECTING: break; case BTD_SERVICE_STATE_CONNECTED: if (data->source_timer > 0) { timeout_remove(data->source_timer); data->source_timer = 0; } data->source_retries = 0; /* Check if service initiate the connection then proceed * immediately otherwise set timer */ if (btd_service_is_initiator(service)) policy_connect(data, target); else if (btd_service_get_state(target) != BTD_SERVICE_STATE_CONNECTED) policy_set_tg_timer(data, CONTROL_CONNECT_TIMEOUT); break; case BTD_SERVICE_STATE_DISCONNECTING: break; } } static void controller_cb(struct btd_service *service, btd_service_state_t old_state, btd_service_state_t new_state) { struct btd_device *dev = btd_service_get_device(service); struct policy_data *data; data = find_data(dev); if (data == NULL) return; switch (new_state) { case BTD_SERVICE_STATE_UNAVAILABLE: if (data->ct_timer > 0) { timeout_remove(data->ct_timer); data->ct_timer = 0; } break; case BTD_SERVICE_STATE_DISCONNECTED: if (old_state == BTD_SERVICE_STATE_CONNECTING) { int err = btd_service_get_error(service); if (err == -EAGAIN) { if (data->ct_retries < CT_RETRIES) policy_set_ct_timer(data, CT_RETRY_TIMEOUT); else data->ct_retries = 0; break; } else if (data->ct_timer > 0) { timeout_remove(data->ct_timer); data->ct_timer = 0; } } break; case BTD_SERVICE_STATE_CONNECTING: break; case BTD_SERVICE_STATE_CONNECTED: if (data->ct_timer > 0) { timeout_remove(data->ct_timer); data->ct_timer = 0; } data->ct_retries = 0; break; case BTD_SERVICE_STATE_DISCONNECTING: break; } } static void target_cb(struct btd_service *service, btd_service_state_t old_state, btd_service_state_t new_state) { struct btd_device *dev = btd_service_get_device(service); struct policy_data *data; data = find_data(dev); if (data == NULL) return; switch (new_state) { case BTD_SERVICE_STATE_UNAVAILABLE: if (data->tg_timer > 0) { timeout_remove(data->tg_timer); data->tg_timer = 0; } break; case BTD_SERVICE_STATE_DISCONNECTED: if (old_state == BTD_SERVICE_STATE_CONNECTING) { int err = btd_service_get_error(service); if (err == -EAGAIN) { if (data->tg_retries < TG_RETRIES) policy_set_tg_timer(data, TG_RETRY_TIMEOUT); else data->tg_retries = 0; break; } else if (data->tg_timer > 0) { timeout_remove(data->tg_timer); data->tg_timer = 0; } } break; case BTD_SERVICE_STATE_CONNECTING: break; case BTD_SERVICE_STATE_CONNECTED: if (data->tg_timer > 0) { timeout_remove(data->tg_timer); data->tg_timer = 0; } data->tg_retries = 0; break; case BTD_SERVICE_STATE_DISCONNECTING: break; } } static void reconnect_reset(struct reconnect_data *reconnect) { reconnect->attempt = 0; reconnect->active = false; if (reconnect->timer > 0) { timeout_remove(reconnect->timer); reconnect->timer = 0; } } static bool reconnect_match(const char *uuid) { char **str; if (!reconnect_uuids) return false; for (str = reconnect_uuids; *str; str++) { if (!bt_uuid_strcmp(uuid, *str)) return true; } return false; } static struct reconnect_data *reconnect_add(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); struct reconnect_data *reconnect; reconnect = reconnect_find(dev); if (!reconnect) { reconnect = g_new0(struct reconnect_data, 1); reconnect->dev = dev; reconnects = g_slist_append(reconnects, reconnect); } if (g_slist_find(reconnect->services, service)) return reconnect; reconnect->services = g_slist_append(reconnect->services, btd_service_ref(service)); return reconnect; } static void reconnect_destroy(gpointer data) { struct reconnect_data *reconnect = data; if (reconnect->timer > 0) timeout_remove(reconnect->timer); g_slist_free_full(reconnect->services, (GDestroyNotify) btd_service_unref); g_free(reconnect); } static void reconnect_remove(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); struct reconnect_data *reconnect; GSList *l; reconnect = reconnect_find(dev); if (!reconnect) return; l = g_slist_find(reconnect->services, service); if (!l) return; reconnect->services = g_slist_delete_link(reconnect->services, l); btd_service_unref(service); if (reconnect->services) return; reconnects = g_slist_remove(reconnects, reconnect); if (reconnect->timer > 0) timeout_remove(reconnect->timer); g_free(reconnect); } static void service_cb(struct btd_service *service, btd_service_state_t old_state, btd_service_state_t new_state, void *user_data) { struct btd_profile *profile = btd_service_get_profile(service); struct reconnect_data *reconnect; if (g_str_equal(profile->remote_uuid, A2DP_SINK_UUID)) sink_cb(service, old_state, new_state); else if (g_str_equal(profile->remote_uuid, A2DP_SOURCE_UUID)) source_cb(service, old_state, new_state); else if (g_str_equal(profile->remote_uuid, AVRCP_REMOTE_UUID)) controller_cb(service, old_state, new_state); else if (g_str_equal(profile->remote_uuid, AVRCP_TARGET_UUID)) target_cb(service, old_state, new_state); else if (g_str_equal(profile->remote_uuid, HFP_HS_UUID) || g_str_equal(profile->remote_uuid, HSP_HS_UUID)) hs_cb(service, old_state, new_state); /* * Return if the reconnection feature is not enabled (all * subsequent code in this function is about that). */ if (!reconnect_uuids || !reconnect_uuids[0]) return; /* * We're only interested in reconnecting profiles which have set * auto_connect to true. */ if (!profile->auto_connect) return; /* * If the service went away remove it from the reconnection * tracking. The function will remove the entire tracking data * if this was the last service for the device. */ if (new_state == BTD_SERVICE_STATE_UNAVAILABLE) { reconnect_remove(service); return; } if (new_state != BTD_SERVICE_STATE_CONNECTED) return; /* * Add an entry to track reconnections. The function will return * an existing entry if there is one. */ reconnect = reconnect_add(service); reconnect->active = false; /* * Should this device be reconnected? A matching UUID might not * be the first profile that's connected so we might have an * entry but with the reconnect flag set to false. */ if (!reconnect->reconnect) reconnect->reconnect = reconnect_match(profile->remote_uuid); DBG("Added %s reconnect %u", profile->name, reconnect->reconnect); } static bool reconnect_timeout(gpointer data) { struct reconnect_data *reconnect = data; int err; DBG("Reconnecting profiles"); /* Mark the GSource as invalid */ reconnect->timer = 0; /* Mark any reconnect on resume as handled */ reconnect->on_resume = false; err = btd_device_connect_services(reconnect->dev, reconnect->services); if (err < 0) { error("Reconnecting services failed: %s (%d)", strerror(-err), -err); reconnect_reset(reconnect); return FALSE; } reconnect->attempt++; return FALSE; } static void reconnect_set_timer(struct reconnect_data *reconnect, int timeout) { static int interval_timeout = 0; reconnect->active = true; if (reconnect->attempt < reconnect_intervals_len) interval_timeout = reconnect_intervals[reconnect->attempt]; if (timeout < 0) timeout = interval_timeout; DBG("attempt %u/%zu %d seconds", reconnect->attempt + 1, reconnect_attempts, timeout); reconnect->timer = timeout_add_seconds(timeout, reconnect_timeout, reconnect, NULL); } static void disconnect_cb(struct btd_device *dev, uint8_t reason) { struct reconnect_data *reconnect; DBG("reason %u", reason); /* Only attempt reconnect for the following reasons */ if (reason != MGMT_DEV_DISCONN_TIMEOUT && reason != MGMT_DEV_DISCONN_LOCAL_HOST_SUSPEND) return; reconnect = reconnect_find(dev); if (!reconnect || !reconnect->reconnect) return; reconnect_reset(reconnect); DBG("Device %s identified for auto-reconnection", device_get_path(dev)); switch (reason) { case MGMT_DEV_DISCONN_LOCAL_HOST_SUSPEND: if (btd_device_get_service(dev, A2DP_SINK_UUID)) { DBG("%s configured to reconnect on resume", device_get_path(dev)); reconnect->on_resume = true; /* If the kernel supports resume events, it is * preferable to set the reconnect timer there as it is * a more predictable delay. */ if (!btd_has_kernel_features(KERNEL_HAS_RESUME_EVT)) reconnect_set_timer(reconnect, resume_delay); } break; case MGMT_DEV_DISCONN_TIMEOUT: reconnect_set_timer(reconnect, -1); break; default: DBG("Developer error. Reason = %d", reason); break; } } static void policy_adapter_resume(struct btd_adapter *adapter) { GSList *l; /* Check if devices on this adapter need to be reconnected on resume */ for (l = reconnects; l; l = g_slist_next(l)) { struct reconnect_data *reconnect = l->data; if (reconnect->on_resume && device_get_adapter(reconnect->dev) == adapter) { reconnect_set_timer(reconnect, resume_delay); } } } static void conn_fail_cb(struct btd_device *dev, uint8_t status) { struct reconnect_data *reconnect; DBG("status %u", status); reconnect = reconnect_find(dev); if (!reconnect || !reconnect->reconnect) return; if (!reconnect->active) return; /* Give up if we were powered off */ if (status == MGMT_STATUS_NOT_POWERED) { reconnect_reset(reconnect); return; } /* Reset if ReconnectAttempts was reached */ if (reconnect->attempt == reconnect_attempts) { reconnect_reset(reconnect); return; } reconnect_set_timer(reconnect, -1); } static int policy_adapter_probe(struct btd_adapter *adapter) { DBG(""); if (auto_enable) btd_adapter_restore_powered(adapter); return 0; } static struct btd_adapter_driver policy_driver = { .name = "policy", .probe = policy_adapter_probe, .resume = policy_adapter_resume, }; static int policy_init(void) { GError *gerr = NULL; GKeyFile *conf; service_id = btd_service_add_state_cb(service_cb, NULL); conf = btd_get_main_conf(); if (!conf) { reconnect_uuids = g_strdupv((char **) default_reconnect); reconnect_attempts = default_attempts; reconnect_intervals_len = sizeof(default_intervals) / sizeof(*reconnect_intervals); reconnect_intervals = util_memdup(default_intervals, sizeof(default_intervals)); auto_enable = default_auto_enable; goto done; } g_key_file_set_list_separator(conf, ','); reconnect_uuids = g_key_file_get_string_list(conf, "Policy", "ReconnectUUIDs", NULL, &gerr); if (gerr) { g_clear_error(&gerr); reconnect_uuids = g_strdupv((char **) default_reconnect); } reconnect_attempts = g_key_file_get_integer(conf, "Policy", "ReconnectAttempts", &gerr); if (gerr) { g_clear_error(&gerr); reconnect_attempts = default_attempts; } reconnect_intervals = g_key_file_get_integer_list(conf, "Policy", "ReconnectIntervals", (size_t *) &reconnect_intervals_len, &gerr); if (gerr) { g_clear_error(&gerr); reconnect_intervals_len = sizeof(default_intervals) / sizeof(*reconnect_intervals); reconnect_intervals = util_memdup(default_intervals, sizeof(default_intervals)); } auto_enable = g_key_file_get_boolean(conf, "Policy", "AutoEnable", &gerr); if (gerr) { g_clear_error(&gerr); auto_enable = default_auto_enable; } resume_delay = g_key_file_get_integer( conf, "Policy", "ResumeDelay", &gerr); if (gerr) { g_clear_error(&gerr); resume_delay = default_resume_delay; } done: if (reconnect_uuids && reconnect_uuids[0] && reconnect_attempts) { btd_add_disconnect_cb(disconnect_cb); btd_add_conn_fail_cb(conn_fail_cb); } btd_register_adapter_driver(&policy_driver); return 0; } static void policy_exit(void) { btd_remove_disconnect_cb(disconnect_cb); btd_remove_conn_fail_cb(conn_fail_cb); if (reconnect_uuids) g_strfreev(reconnect_uuids); free(reconnect_intervals); g_slist_free_full(reconnects, reconnect_destroy); g_slist_free_full(devices, policy_remove); btd_service_remove_state_cb(service_id); btd_unregister_adapter_driver(&policy_driver); } BLUETOOTH_PLUGIN_DEFINE(policy, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, policy_init, policy_exit) bluez-5.87/plugins/PaxHeaders/admin.c0000644000000000000000000000005015142177620014575 xustar0020 atime=1778585135 20 ctime=1783094105 bluez-5.87/plugins/admin.c0000644000000000000000000003502215142177620014260 0ustar00rootroot// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2021 Google LLC * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "bluetooth/bluetooth.h" #include "bluetooth/uuid.h" #include "src/adapter.h" #include "src/dbus-common.h" #include "src/device.h" #include "src/error.h" #include "src/log.h" #include "src/plugin.h" #include "src/textfile.h" #include "src/shared/queue.h" #define ADMIN_POLICY_SET_INTERFACE "org.bluez.AdminPolicySet1" #define ADMIN_POLICY_STATUS_INTERFACE "org.bluez.AdminPolicyStatus1" #define DBUS_BLUEZ_SERVICE "org.bluez" #define BTD_DEVICE_INTERFACE "org.bluez.Device1" static DBusConnection *dbus_conn; static struct queue *devices; /* List of struct device_data objects */ /* |policy_data| has the same life cycle as btd_adapter */ static struct btd_admin_policy { struct btd_adapter *adapter; uint16_t adapter_id; struct queue *service_allowlist; } *policy_data = NULL; struct device_data { struct btd_device *device; char *path; bool affected; }; static struct btd_admin_policy *admin_policy_new(struct btd_adapter *adapter) { struct btd_admin_policy *admin_policy = NULL; admin_policy = g_try_malloc(sizeof(*admin_policy)); if (!admin_policy) { btd_error(btd_adapter_get_index(adapter), "Failed to allocate memory for admin_policy"); return NULL; } admin_policy->adapter = adapter; admin_policy->adapter_id = btd_adapter_get_index(adapter); admin_policy->service_allowlist = queue_new(); return admin_policy; } static void free_service_allowlist(struct queue *q) { queue_destroy(q, free); } static void admin_policy_free(void *data) { struct btd_admin_policy *admin_policy = data; free_service_allowlist(admin_policy->service_allowlist); g_free(admin_policy); } static void admin_policy_destroy(struct btd_admin_policy *admin_policy) { const char *path = adapter_get_path(admin_policy->adapter); g_dbus_unregister_interface(dbus_conn, path, ADMIN_POLICY_SET_INTERFACE); g_dbus_unregister_interface(dbus_conn, path, ADMIN_POLICY_STATUS_INTERFACE); admin_policy_free(admin_policy); } static bool uuid_match(const void *data, const void *match_data) { const bt_uuid_t *uuid = data; const bt_uuid_t *match_uuid = match_data; return bt_uuid_cmp(uuid, match_uuid) == 0; } static struct queue *parse_allow_service_list(struct btd_adapter *adapter, DBusMessage *msg) { DBusMessageIter iter, arr_iter; struct queue *uuid_list = NULL; dbus_message_iter_init(msg, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) return NULL; uuid_list = queue_new(); dbus_message_iter_recurse(&iter, &arr_iter); do { const int type = dbus_message_iter_get_arg_type(&arr_iter); char *uuid_param; bt_uuid_t *uuid; if (type == DBUS_TYPE_INVALID) break; if (type != DBUS_TYPE_STRING) goto failed; dbus_message_iter_get_basic(&arr_iter, &uuid_param); uuid = g_try_malloc(sizeof(*uuid)); if (!uuid) goto failed; if (bt_string_to_uuid(uuid, uuid_param)) { g_free(uuid); goto failed; } dbus_message_iter_next(&arr_iter); if (queue_find(uuid_list, uuid_match, uuid)) { g_free(uuid); continue; } queue_push_head(uuid_list, uuid); } while (true); return uuid_list; failed: queue_destroy(uuid_list, g_free); return NULL; } static bool service_allowlist_set(struct btd_admin_policy *admin_policy, struct queue *uuid_list) { struct btd_adapter *adapter = admin_policy->adapter; if (!btd_adapter_set_allowed_uuids(adapter, uuid_list)) return false; free_service_allowlist(admin_policy->service_allowlist); admin_policy->service_allowlist = uuid_list; return true; } static void update_device_affected(void *data, void *user_data) { struct device_data *dev_data = data; bool affected; if (!dev_data) { error("Unexpected NULL device_data when updating device"); return; } affected = !btd_device_all_services_allowed(dev_data->device); if (affected == dev_data->affected) return; dev_data->affected = affected; g_dbus_emit_property_changed(dbus_conn, dev_data->path, ADMIN_POLICY_STATUS_INTERFACE, "AffectedByPolicy"); } static void free_uuid_strings(char **uuid_strs, gsize num) { gsize i; for (i = 0; i < num; i++) g_free(uuid_strs[i]); g_free(uuid_strs); } static char **new_uuid_strings(struct queue *allowlist, gsize *num) { const struct queue_entry *entry = NULL; bt_uuid_t *uuid = NULL; char **uuid_strs = NULL; gsize i = 0, allowlist_num; allowlist_num = queue_length(allowlist); if (!allowlist_num) { *num = 0; return NULL; } /* Set num to a non-zero number so that whoever call this could know if * this function success or not */ *num = 1; uuid_strs = g_try_malloc_n(allowlist_num, sizeof(char *)); if (!uuid_strs) return NULL; for (entry = queue_get_entries(allowlist); entry != NULL; entry = entry->next) { uuid = entry->data; uuid_strs[i] = g_try_malloc0(MAX_LEN_UUID_STR * sizeof(char)); if (!uuid_strs[i]) goto failed; bt_uuid_to_string(uuid, uuid_strs[i], MAX_LEN_UUID_STR); i++; } *num = allowlist_num; return uuid_strs; failed: free_uuid_strings(uuid_strs, i); return NULL; } static void store_policy_settings(struct btd_admin_policy *admin_policy) { GKeyFile *key_file = NULL; GError *gerr = NULL; char filename[PATH_MAX]; char *key_file_data = NULL; char **uuid_strs = NULL; gsize length, num_uuids; key_file = g_key_file_new(); uuid_strs = new_uuid_strings(admin_policy->service_allowlist, &num_uuids); if (!uuid_strs && num_uuids) { btd_error(admin_policy->adapter_id, "Failed to allocate uuid strings"); goto failed; } g_key_file_set_string_list(key_file, "General", "ServiceAllowlist", (const gchar * const *)uuid_strs, num_uuids); create_filename(filename, PATH_MAX, "/%s/admin_policy_settings", btd_adapter_get_storage_dir(admin_policy->adapter)); create_file(filename, 0600); key_file_data = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, key_file_data, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, gerr->message); g_error_free(gerr); } g_free(key_file_data); free_uuid_strings(uuid_strs, num_uuids); failed: g_key_file_free(key_file); } static void key_file_load_service_allowlist(GKeyFile *key_file, struct btd_admin_policy *admin_policy) { GError *gerr = NULL; struct queue *uuid_list = NULL; gchar **uuids = NULL; gsize num, i; uuids = g_key_file_get_string_list(key_file, "General", "ServiceAllowlist", &num, &gerr); if (gerr) { btd_error(admin_policy->adapter_id, "Failed to load ServiceAllowlist"); g_error_free(gerr); return; } uuid_list = queue_new(); for (i = 0; i < num; i++) { bt_uuid_t *uuid = g_try_malloc(sizeof(*uuid)); if (!uuid) goto failed; if (bt_string_to_uuid(uuid, uuids[i])) { btd_error(admin_policy->adapter_id, "Failed to convert '%s' to uuid struct", *uuids); g_free(uuid); goto failed; } queue_push_tail(uuid_list, uuid); } if (!service_allowlist_set(admin_policy, uuid_list)) goto failed; g_strfreev(uuids); return; failed: g_strfreev(uuids); free_service_allowlist(uuid_list); } static void load_policy_settings(struct btd_admin_policy *admin_policy) { GKeyFile *key_file; GError *gerr = NULL; char filename[PATH_MAX]; struct stat st; create_filename(filename, PATH_MAX, "/%s/admin_policy_settings", btd_adapter_get_storage_dir(admin_policy->adapter)); if (stat(filename, &st) < 0) store_policy_settings(policy_data); key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) { error("Unable to load key file from %s: (%s)", filename, gerr->message); g_error_free(gerr); } key_file_load_service_allowlist(key_file, admin_policy); g_key_file_free(key_file); } static DBusMessage *set_service_allowlist(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_admin_policy *admin_policy = user_data; struct btd_adapter *adapter = admin_policy->adapter; struct queue *uuid_list = NULL; const char *sender = dbus_message_get_sender(msg); DBG("sender %s", sender); /* Parse parameters */ uuid_list = parse_allow_service_list(adapter, msg); if (!uuid_list) { btd_error(admin_policy->adapter_id, "Failed on parsing allowed service list"); return btd_error_invalid_args(msg); } if (service_allowlist_set(admin_policy, uuid_list)) { store_policy_settings(admin_policy); } else { free_service_allowlist(uuid_list); return btd_error_failed(msg, "service_allowlist_set failed"); } g_dbus_emit_property_changed(dbus_conn, adapter_get_path(policy_data->adapter), ADMIN_POLICY_STATUS_INTERFACE, "ServiceAllowList"); queue_foreach(devices, update_device_affected, NULL); return dbus_message_new_method_return(msg); } static const GDBusMethodTable admin_policy_adapter_methods[] = { { GDBUS_METHOD("SetServiceAllowList", GDBUS_ARGS({ "UUIDs", "as" }), NULL, set_service_allowlist) }, { } }; static void append_service_uuid(void *data, void *user_data) { bt_uuid_t *uuid = data; DBusMessageIter *entry = user_data; char uuid_str[MAX_LEN_UUID_STR]; const char *uuid_str_ptr = uuid_str; if (!uuid) { error("Unexpected NULL uuid data in service_allowlist"); return; } bt_uuid_to_string(uuid, uuid_str, MAX_LEN_UUID_STR); dbus_message_iter_append_basic(entry, DBUS_TYPE_STRING, &uuid_str_ptr); } static gboolean property_get_service_allowlist( const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct btd_admin_policy *admin_policy = user_data; DBusMessageIter entry; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); queue_foreach(admin_policy->service_allowlist, append_service_uuid, &entry); dbus_message_iter_close_container(iter, &entry); return TRUE; } static const GDBusPropertyTable admin_policy_adapter_properties[] = { { "ServiceAllowList", "as", property_get_service_allowlist }, { } }; static bool device_data_match(const void *a, const void *b) { const struct device_data *data = a; const struct btd_device *dev = b; if (!data) { error("Unexpected NULL device_data"); return false; } return data->device == dev; } static gboolean property_get_affected_by_policy( const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct device_data *data = user_data; if (!data) { error("Unexpected error: device_data is NULL"); return FALSE; } dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &data->affected); return TRUE; } static const GDBusPropertyTable admin_policy_device_properties[] = { { "AffectedByPolicy", "b", property_get_affected_by_policy }, { } }; static void free_device_data(void *data) { struct device_data *device_data = data; g_free(device_data->path); g_free(device_data); } static void remove_device_data(void *data) { struct device_data *device_data = data; DBG("device_data for %s removing", device_data->path); queue_remove(devices, device_data); free_device_data(device_data); } static int admin_policy_adapter_probe(struct btd_adapter *adapter) { const char *adapter_path; if (!devices) devices = queue_new(); if (policy_data) { btd_warn(policy_data->adapter_id, "Policy data already exists"); policy_data = NULL; } policy_data = admin_policy_new(adapter); if (!policy_data) return -ENOMEM; load_policy_settings(policy_data); adapter_path = adapter_get_path(adapter); if (!g_dbus_register_interface(dbus_conn, adapter_path, ADMIN_POLICY_SET_INTERFACE, admin_policy_adapter_methods, NULL, NULL, policy_data, NULL)) { btd_error(policy_data->adapter_id, "Admin Policy Set interface init failed on path %s", adapter_path); return -EINVAL; } btd_info(policy_data->adapter_id, "Admin Policy Set interface registered"); if (!g_dbus_register_interface(dbus_conn, adapter_path, ADMIN_POLICY_STATUS_INTERFACE, NULL, NULL, admin_policy_adapter_properties, policy_data, NULL)) { btd_error(policy_data->adapter_id, "Admin Policy Status interface init failed on path %s", adapter_path); return -EINVAL; } btd_info(policy_data->adapter_id, "Admin Policy Status interface registered"); return 0; } static void admin_policy_device_added(struct btd_adapter *adapter, struct btd_device *device) { struct device_data *data; if (queue_find(devices, device_data_match, device)) return; data = g_new0(struct device_data, 1); if (!data) { btd_error(btd_adapter_get_index(adapter), "Failed to allocate memory for device_data"); return; } data->device = device; data->path = g_strdup(device_get_path(device)); data->affected = !btd_device_all_services_allowed(data->device); if (!g_dbus_register_interface(dbus_conn, data->path, ADMIN_POLICY_STATUS_INTERFACE, NULL, NULL, admin_policy_device_properties, data, remove_device_data)) { btd_error(btd_adapter_get_index(adapter), "Admin Policy Status interface init failed on path %s", device_get_path(device)); free_device_data(data); return; } queue_push_tail(devices, data); DBG("device_data for %s added", data->path); } static void unregister_device_data(void *data, void *user_data) { struct device_data *dev_data = data; g_dbus_unregister_interface(dbus_conn, dev_data->path, ADMIN_POLICY_STATUS_INTERFACE); } static void admin_policy_device_removed(struct btd_adapter *adapter, struct btd_device *device) { struct device_data *data; data = queue_find(devices, device_data_match, device); if (data) unregister_device_data(data, NULL); } static void admin_policy_remove(struct btd_adapter *adapter) { DBG(""); queue_foreach(devices, unregister_device_data, NULL); queue_destroy(devices, g_free); devices = NULL; if (policy_data) { admin_policy_destroy(policy_data); policy_data = NULL; } } static struct btd_adapter_driver admin_policy_driver = { .name = "admin_policy", .probe = admin_policy_adapter_probe, .resume = NULL, .remove = admin_policy_remove, .device_resolved = admin_policy_device_added, .device_removed = admin_policy_device_removed, .experimental = true, }; static int admin_init(void) { dbus_conn = btd_get_dbus_connection(); return btd_register_adapter_driver(&admin_policy_driver); } static void admin_exit(void) { btd_unregister_adapter_driver(&admin_policy_driver); } BLUETOOTH_PLUGIN_DEFINE(admin, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, admin_init, admin_exit) bluez-5.87/PaxHeaders/ltmain.sh0000644000000000000000000000005015221752776013511 xustar0020 atime=1783092779 20 ctime=1783094098 bluez-5.87/ltmain.sh0000755000000000000000000122277715221752776013217 0ustar00rootroot#! /usr/bin/env sh ## DO NOT EDIT - This file generated from ./build-aux/ltmain.in ## by inline-source v2019-02-19.15 # libtool (GNU libtool) 2.5.4 # Provide generalized library-building support services. # Written by Gordon Matzigkeit , 1996 # Copyright (C) 1996-2019, 2021-2024 Free Software Foundation, Inc. # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # GNU Libtool 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. # # As a special exception to the GNU General Public License, # if you distribute this file as part of a program or library that # is built using GNU Libtool, you may include this file under the # same distribution terms that you use for the rest of that program. # # GNU Libtool 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, see . PROGRAM=libtool PACKAGE=libtool VERSION="2.5.4 Debian-2.5.4-11" package_revision=2.5.4 ## ------ ## ## Usage. ## ## ------ ## # Run './libtool --help' for help with using this script from the # command line. ## ------------------------------- ## ## User overridable command paths. ## ## ------------------------------- ## # After configure completes, it has a better idea of some of the # shell tools we need than the defaults used by the functions shared # with bootstrap, so set those here where they can still be over- # ridden by the user, but otherwise take precedence. : ${AUTOCONF="autoconf"} : ${AUTOMAKE="automake"} ## -------------------------- ## ## Source external libraries. ## ## -------------------------- ## # Much of our low-level functionality needs to be sourced from external # libraries, which are installed to $pkgauxdir. # Set a version string for this script. scriptversion=2024-12-01.17; # UTC # General shell script boiler plate, and helper functions. # Written by Gary V. Vaughan, 2004 # This is free software. There is NO warranty; not even for # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # Copyright (C) 2004-2019, 2021, 2023-2024 Bootstrap Authors # # This file is dual licensed under the terms of the MIT license # , and GPL version 2 or later # . You must apply one of # these licenses when using or redistributing this software or any of # the files within it. See the URLs above, or the file `LICENSE` # included in the Bootstrap distribution for the full license texts. # Please report bugs or propose patches to: # ## ------ ## ## Usage. ## ## ------ ## # Evaluate this file near the top of your script to gain access to # the functions and variables defined here: # # . `echo "$0" | ${SED-sed} 's|[^/]*$||'`/build-aux/funclib.sh # # If you need to override any of the default environment variable # settings, do that before evaluating this file. ## -------------------- ## ## Shell normalisation. ## ## -------------------- ## # Some shells need a little help to be as Bourne compatible as possible. # Before doing anything else, make sure all that help has been provided! DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac fi # NLS nuisances: We save the old values in case they are required later. _G_user_locale= _G_safe_locale= for _G_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES do eval "if test set = \"\${$_G_var+set}\"; then save_$_G_var=\$$_G_var $_G_var=C export $_G_var _G_user_locale=\"$_G_var=\\\$save_\$_G_var; \$_G_user_locale\" _G_safe_locale=\"$_G_var=C; \$_G_safe_locale\" fi" done # These NLS vars are set unconditionally (bootstrap issue #24). Unset those # in case the environment reset is needed later and the $save_* variant is not # defined (see the code above). LC_ALL=C LANGUAGE=C export LANGUAGE LC_ALL # Make sure IFS has a sensible default sp=' ' nl=' ' IFS="$sp $nl" # There are apparently some systems that use ';' as a PATH separator! if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # func_unset VAR # -------------- # Portably unset VAR. # In some shells, an 'unset VAR' statement leaves a non-zero return # status if VAR is already unset, which might be problematic if the # statement is used at the end of a function (thus poisoning its return # value) or when 'set -e' is active (causing even a spurious abort of # the script in this case). func_unset () { { eval $1=; (eval unset $1) >/dev/null 2>&1 && eval unset $1 || : ; } } # Make sure CDPATH doesn't cause `cd` commands to output the target dir. func_unset CDPATH # Make sure ${,E,F}GREP behave sanely. func_unset GREP_OPTIONS ## ------------------------- ## ## Locate command utilities. ## ## ------------------------- ## # func_executable_p FILE # ---------------------- # Check that FILE is an executable regular file. func_executable_p () { test -f "$1" && test -x "$1" } # func_path_progs PROGS_LIST CHECK_FUNC [PATH] # -------------------------------------------- # Search for either a program that responds to --version with output # containing "GNU", or else returned by CHECK_FUNC otherwise, by # trying all the directories in PATH with each of the elements of # PROGS_LIST. # # CHECK_FUNC should accept the path to a candidate program, and # set $func_check_prog_result if it truncates its output less than # $_G_path_prog_max characters. func_path_progs () { _G_progs_list=$1 _G_check_func=$2 _G_PATH=${3-"$PATH"} _G_path_prog_max=0 _G_path_prog_found=false _G_save_IFS=$IFS; IFS=${PATH_SEPARATOR-:} for _G_dir in $_G_PATH; do IFS=$_G_save_IFS test -z "$_G_dir" && _G_dir=. for _G_prog_name in $_G_progs_list; do for _exeext in '' .EXE; do _G_path_prog=$_G_dir/$_G_prog_name$_exeext func_executable_p "$_G_path_prog" || continue case `"$_G_path_prog" --version 2>&1` in *GNU*) func_path_progs_result=$_G_path_prog _G_path_prog_found=: ;; *) $_G_check_func $_G_path_prog func_path_progs_result=$func_check_prog_result ;; esac $_G_path_prog_found && break 3 done done done IFS=$_G_save_IFS test -z "$func_path_progs_result" && { echo "no acceptable sed could be found in \$PATH" >&2 exit 1 } } # We want to be able to use the functions in this file before configure # has figured out where the best binaries are kept, which means we have # to search for them ourselves - except when the results are already set # where we skip the searches. # Unless the user overrides by setting SED, search the path for either GNU # sed, or the sed that truncates its output the least. test -z "$SED" && { _G_sed_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ for _G_i in 1 2 3 4 5 6 7; do _G_sed_script=$_G_sed_script$nl$_G_sed_script done echo "$_G_sed_script" 2>/dev/null | sed 99q >conftest.sed _G_sed_script= func_check_prog_sed () { _G_path_prog=$1 _G_count=0 printf 0123456789 >conftest.in while : do cat conftest.in conftest.in >conftest.tmp mv conftest.tmp conftest.in cp conftest.in conftest.nl echo '' >> conftest.nl "$_G_path_prog" -f conftest.sed conftest.out 2>/dev/null || break diff conftest.out conftest.nl >/dev/null 2>&1 || break _G_count=`expr $_G_count + 1` if test "$_G_count" -gt "$_G_path_prog_max"; then # Best one so far, save it but keep looking for a better one func_check_prog_result=$_G_path_prog _G_path_prog_max=$_G_count fi # 10*(2^10) chars as input seems more than enough test 10 -lt "$_G_count" && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out } func_path_progs "sed gsed" func_check_prog_sed "$PATH:/usr/xpg4/bin" rm -f conftest.sed SED=$func_path_progs_result } # Unless the user overrides by setting GREP, search the path for either GNU # grep, or the grep that truncates its output the least. test -z "$GREP" && { func_check_prog_grep () { _G_path_prog=$1 _G_count=0 _G_path_prog_max=0 printf 0123456789 >conftest.in while : do cat conftest.in conftest.in >conftest.tmp mv conftest.tmp conftest.in cp conftest.in conftest.nl echo 'GREP' >> conftest.nl "$_G_path_prog" -e 'GREP$' -e '-(cannot match)-' conftest.out 2>/dev/null || break diff conftest.out conftest.nl >/dev/null 2>&1 || break _G_count=`expr $_G_count + 1` if test "$_G_count" -gt "$_G_path_prog_max"; then # Best one so far, save it but keep looking for a better one func_check_prog_result=$_G_path_prog _G_path_prog_max=$_G_count fi # 10*(2^10) chars as input seems more than enough test 10 -lt "$_G_count" && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out } func_path_progs "grep ggrep" func_check_prog_grep "$PATH:/usr/xpg4/bin" GREP=$func_path_progs_result } ## ------------------------------- ## ## User overridable command paths. ## ## ------------------------------- ## # All uppercase variable names are used for environment variables. These # variables can be overridden by the user before calling a script that # uses them if a suitable command of that name is not already available # in the command search PATH. : ${CP="cp -f"} : ${ECHO="printf %s\n"} : ${EGREP="$GREP -E"} : ${FGREP="$GREP -F"} : ${LN_S="ln -s"} : ${MAKE="make"} : ${MKDIR="mkdir"} : ${MV="mv -f"} : ${RM="rm -f"} : ${SHELL="${CONFIG_SHELL-/bin/sh}"} ## -------------------- ## ## Useful sed snippets. ## ## -------------------- ## sed_dirname='s|/[^/]*$||' sed_basename='s|^.*/||' # Sed substitution that helps us do robust quoting. It backslashifies # metacharacters that are still active within double-quoted strings. sed_quote_subst='s|\([`"$\\]\)|\\\1|g' # Same as above, but do not quote variable references. sed_double_quote_subst='s/\(["`\\]\)/\\\1/g' # Sed substitution that turns a string into a regex matching for the # string literally. sed_make_literal_regex='s|[].[^$\\*\/]|\\&|g' # Sed substitution that converts a w32 file name or path # that contains forward slashes, into one that contains # (escaped) backslashes. A very naive implementation. sed_naive_backslashify='s|\\\\*|\\|g;s|/|\\|g;s|\\|\\\\|g' # Re-'\' parameter expansions in output of sed_double_quote_subst that # were '\'-ed in input to the same. If an odd number of '\' preceded a # '$' in input to sed_double_quote_subst, that '$' was protected from # expansion. Since each input '\' is now two '\'s, look for any number # of runs of four '\'s followed by two '\'s and then a '$'. '\' that '$'. _G_bs='\\' _G_bs2='\\\\' _G_bs4='\\\\\\\\' _G_dollar='\$' sed_double_backslash="\ s/$_G_bs4/&\\ /g s/^$_G_bs2$_G_dollar/$_G_bs&/ s/\\([^$_G_bs]\\)$_G_bs2$_G_dollar/\\1$_G_bs2$_G_bs$_G_dollar/g s/\n//g" # require_check_ifs_backslash # --------------------------- # Check if we can use backslash as IFS='\' separator, and set # $check_ifs_backshlash_broken to ':' or 'false'. require_check_ifs_backslash=func_require_check_ifs_backslash func_require_check_ifs_backslash () { _G_save_IFS=$IFS IFS='\' _G_check_ifs_backshlash='a\\b' for _G_i in $_G_check_ifs_backshlash do case $_G_i in a) check_ifs_backshlash_broken=false ;; '') break ;; *) check_ifs_backshlash_broken=: break ;; esac done IFS=$_G_save_IFS require_check_ifs_backslash=: } ## ----------------- ## ## Global variables. ## ## ----------------- ## # Except for the global variables explicitly listed below, the following # functions in the '^func_' namespace, and the '^require_' namespace # variables initialised in the 'Resource management' section, sourcing # this file will not pollute your global namespace with anything # else. There's no portable way to scope variables in Bourne shell # though, so actually running these functions will sometimes place # results into a variable named after the function, and often use # temporary variables in the '^_G_' namespace. If you are careful to # avoid using those namespaces casually in your sourcing script, things # should continue to work as you expect. And, of course, you can freely # overwrite any of the functions or variables defined here before # calling anything to customize them. EXIT_SUCCESS=0 EXIT_FAILURE=1 EXIT_MISMATCH=63 # $? = 63 is used to indicate version mismatch to missing. EXIT_SKIP=77 # $? = 77 is used to indicate a skipped test to automake. # Allow overriding, eg assuming that you follow the convention of # putting '$debug_cmd' at the start of all your functions, you can get # bash to show function call trace with: # # debug_cmd='echo "${FUNCNAME[0]} $*" >&2' bash your-script-name debug_cmd=${debug_cmd-":"} exit_cmd=: # By convention, finish your script with: # # exit $exit_status # # so that you can set exit_status to non-zero if you want to indicate # something went wrong during execution without actually bailing out at # the point of failure. exit_status=$EXIT_SUCCESS # Work around backward compatibility issue on IRIX 6.5. On IRIX 6.4+, sh # is ksh but when the shell is invoked as "sh" and the current value of # the _XPG environment variable is not equal to 1 (one), the special # positional parameter $0, within a function call, is the name of the # function. progpath=$0 # The name of this program. progname=`$ECHO "$progpath" |$SED "$sed_basename"` # Make sure we have an absolute progpath for reexecution: case $progpath in [\\/]*|[A-Za-z]:\\*) ;; *[\\/]*) progdir=`$ECHO "$progpath" |$SED "$sed_dirname"` progdir=`cd "$progdir" && pwd` progpath=$progdir/$progname ;; *) _G_IFS=$IFS IFS=${PATH_SEPARATOR-:} for progdir in $PATH; do IFS=$_G_IFS test -x "$progdir/$progname" && break done IFS=$_G_IFS test -n "$progdir" || progdir=`pwd` progpath=$progdir/$progname ;; esac ## ----------------- ## ## Standard options. ## ## ----------------- ## # The following options affect the operation of the functions defined # below, and should be set appropriately depending on run-time para- # meters passed on the command line. opt_dry_run=false opt_quiet=false opt_verbose=false # Categories 'all' and 'none' are always available. Append any others # you will pass as the first argument to func_warning from your own # code. warning_categories= # By default, display warnings according to 'opt_warning_types'. Set # 'warning_func' to ':' to elide all warnings, or func_fatal_error to # treat the next displayed warning as a fatal error. warning_func=func_warn_and_continue # Set to 'all' to display all warnings, 'none' to suppress all # warnings, or a space delimited list of some subset of # 'warning_categories' to display only the listed warnings. opt_warning_types=all ## -------------------- ## ## Resource management. ## ## -------------------- ## # This section contains definitions for functions that each ensure a # particular resource (a file, or a non-empty configuration variable for # example) is available, and if appropriate to extract default values # from pertinent package files. Call them using their associated # 'require_*' variable to ensure that they are executed, at most, once. # # It's entirely deliberate that calling these functions can set # variables that don't obey the namespace limitations obeyed by the rest # of this file, in order that that they be as useful as possible to # callers. # require_term_colors # ------------------- # Allow display of bold text on terminals that support it. require_term_colors=func_require_term_colors func_require_term_colors () { $debug_cmd test -t 1 && { # COLORTERM and USE_ANSI_COLORS environment variables take # precedence, because most terminfo databases neglect to describe # whether color sequences are supported. test -n "${COLORTERM+set}" && : ${USE_ANSI_COLORS="1"} if test 1 = "$USE_ANSI_COLORS"; then # Standard ANSI escape sequences tc_reset='' tc_bold=''; tc_standout='' tc_red=''; tc_green='' tc_blue=''; tc_cyan='' else # Otherwise trust the terminfo database after all. test -n "`tput sgr0 2>/dev/null`" && { tc_reset=`tput sgr0` test -n "`tput bold 2>/dev/null`" && tc_bold=`tput bold` tc_standout=$tc_bold test -n "`tput smso 2>/dev/null`" && tc_standout=`tput smso` test -n "`tput setaf 1 2>/dev/null`" && tc_red=`tput setaf 1` test -n "`tput setaf 2 2>/dev/null`" && tc_green=`tput setaf 2` test -n "`tput setaf 4 2>/dev/null`" && tc_blue=`tput setaf 4` test -n "`tput setaf 5 2>/dev/null`" && tc_cyan=`tput setaf 5` } fi } require_term_colors=: } ## ----------------- ## ## Function library. ## ## ----------------- ## # This section contains a variety of useful functions to call in your # scripts. Take note of the portable wrappers for features provided by # some modern shells, which will fall back to slower equivalents on # less featureful shells. # func_append VAR VALUE # --------------------- # Append VALUE onto the existing contents of VAR. # _G_HAVE_PLUSEQ_OP # Can be empty, in which case the shell is probed, "yes" if += is # usable or anything else if it does not work. if test -z "$_G_HAVE_PLUSEQ_OP" && \ __PLUSEQ_TEST="a" && \ __PLUSEQ_TEST+=" b" 2>/dev/null && \ test "a b" = "$__PLUSEQ_TEST"; then _G_HAVE_PLUSEQ_OP=yes fi if test yes = "$_G_HAVE_PLUSEQ_OP" then # This is an XSI compatible shell, allowing a faster implementation... eval 'func_append () { $debug_cmd eval "$1+=\$2" }' else # ...otherwise fall back to using expr, which is often a shell builtin. func_append () { $debug_cmd eval "$1=\$$1\$2" } fi # func_append_quoted VAR VALUE # ---------------------------- # Quote VALUE and append to the end of shell variable VAR, separated # by a space. if test yes = "$_G_HAVE_PLUSEQ_OP"; then eval 'func_append_quoted () { $debug_cmd func_quote_arg pretty "$2" eval "$1+=\\ \$func_quote_arg_result" }' else func_append_quoted () { $debug_cmd func_quote_arg pretty "$2" eval "$1=\$$1\\ \$func_quote_arg_result" } fi # func_append_uniq VAR VALUE # -------------------------- # Append unique VALUE onto the existing contents of VAR, assuming # entries are delimited by the first character of VALUE. For example: # # func_append_uniq options " --another-option option-argument" # # will only append to $options if " --another-option option-argument " # is not already present somewhere in $options already (note spaces at # each end implied by leading space in second argument). func_append_uniq () { $debug_cmd eval _G_current_value='`$ECHO $'$1'`' _G_delim=`expr "$2" : '\(.\)'` case $_G_delim$_G_current_value$_G_delim in *"$2$_G_delim"*) ;; *) func_append "$@" ;; esac } # func_arith TERM... # ------------------ # Set func_arith_result to the result of evaluating TERMs. test -z "$_G_HAVE_ARITH_OP" \ && (eval 'test 2 = $(( 1 + 1 ))') 2>/dev/null \ && _G_HAVE_ARITH_OP=yes if test yes = "$_G_HAVE_ARITH_OP"; then eval 'func_arith () { $debug_cmd func_arith_result=$(( $* )) }' else func_arith () { $debug_cmd func_arith_result=`expr "$@"` } fi # func_basename FILE # ------------------ # Set func_basename_result to FILE with everything up to and including # the last / stripped. if test yes = "$_G_HAVE_XSI_OPS"; then # If this shell supports suffix pattern removal, then use it to avoid # forking. Hide the definitions single quotes in case the shell chokes # on unsupported syntax... _b='func_basename_result=${1##*/}' _d='case $1 in */*) func_dirname_result=${1%/*}$2 ;; * ) func_dirname_result=$3 ;; esac' else # ...otherwise fall back to using sed. _b='func_basename_result=`$ECHO "$1" |$SED "$sed_basename"`' _d='func_dirname_result=`$ECHO "$1" |$SED "$sed_dirname"` if test "X$func_dirname_result" = "X$1"; then func_dirname_result=$3 else func_append func_dirname_result "$2" fi' fi eval 'func_basename () { $debug_cmd '"$_b"' }' # func_dirname FILE APPEND NONDIR_REPLACEMENT # ------------------------------------------- # Compute the dirname of FILE. If nonempty, add APPEND to the result, # otherwise set result to NONDIR_REPLACEMENT. eval 'func_dirname () { $debug_cmd '"$_d"' }' # func_dirname_and_basename FILE APPEND NONDIR_REPLACEMENT # -------------------------------------------------------- # Perform func_basename and func_dirname in a single function # call: # dirname: Compute the dirname of FILE. If nonempty, # add APPEND to the result, otherwise set result # to NONDIR_REPLACEMENT. # value returned in "$func_dirname_result" # basename: Compute filename of FILE. # value returned in "$func_basename_result" # For efficiency, we do not delegate to the functions above but instead # duplicate the functionality here. eval 'func_dirname_and_basename () { $debug_cmd '"$_b"' '"$_d"' }' # func_echo ARG... # ---------------- # Echo program name prefixed message. func_echo () { $debug_cmd _G_message=$* func_echo_IFS=$IFS IFS=$nl for _G_line in $_G_message; do IFS=$func_echo_IFS $ECHO "$progname: $_G_line" done IFS=$func_echo_IFS } # func_echo_all ARG... # -------------------- # Invoke $ECHO with all args, space-separated. func_echo_all () { $ECHO "$*" } # func_echo_infix_1 INFIX ARG... # ------------------------------ # Echo program name, followed by INFIX on the first line, with any # additional lines not showing INFIX. func_echo_infix_1 () { $debug_cmd $require_term_colors _G_infix=$1; shift _G_indent=$_G_infix _G_prefix="$progname: $_G_infix: " _G_message=$* # Strip color escape sequences before counting printable length for _G_tc in "$tc_reset" "$tc_bold" "$tc_standout" "$tc_red" "$tc_green" "$tc_blue" "$tc_cyan" do test -n "$_G_tc" && { _G_esc_tc=`$ECHO "$_G_tc" | $SED "$sed_make_literal_regex"` _G_indent=`$ECHO "$_G_indent" | $SED "s|$_G_esc_tc||g"` } done _G_indent="$progname: "`echo "$_G_indent" | $SED 's|.| |g'`" " ## exclude from sc_prohibit_nested_quotes func_echo_infix_1_IFS=$IFS IFS=$nl for _G_line in $_G_message; do IFS=$func_echo_infix_1_IFS $ECHO "$_G_prefix$tc_bold$_G_line$tc_reset" >&2 _G_prefix=$_G_indent done IFS=$func_echo_infix_1_IFS } # func_error ARG... # ----------------- # Echo program name prefixed message to standard error. func_error () { $debug_cmd $require_term_colors func_echo_infix_1 " $tc_standout${tc_red}error$tc_reset" "$*" >&2 } # func_fatal_error ARG... # ----------------------- # Echo program name prefixed message to standard error, and exit. func_fatal_error () { $debug_cmd func_error "$*" exit $EXIT_FAILURE } # func_grep EXPRESSION FILENAME # ----------------------------- # Check whether EXPRESSION matches any line of FILENAME, without output. func_grep () { $debug_cmd $GREP "$1" "$2" >/dev/null 2>&1 } # func_len STRING # --------------- # Set func_len_result to the length of STRING. STRING may not # start with a hyphen. test -z "$_G_HAVE_XSI_OPS" \ && (eval 'x=a/b/c; test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ && _G_HAVE_XSI_OPS=yes if test yes = "$_G_HAVE_XSI_OPS"; then eval 'func_len () { $debug_cmd func_len_result=${#1} }' else func_len () { $debug_cmd func_len_result=`expr "$1" : ".*" 2>/dev/null || echo $max_cmd_len` } fi # func_mkdir_p DIRECTORY-PATH # --------------------------- # Make sure the entire path to DIRECTORY-PATH is available. func_mkdir_p () { $debug_cmd _G_directory_path=$1 _G_dir_list= if test -n "$_G_directory_path" && test : != "$opt_dry_run"; then # Protect directory names starting with '-' case $_G_directory_path in -*) _G_directory_path=./$_G_directory_path ;; esac # While some portion of DIR does not yet exist... while test ! -d "$_G_directory_path"; do # ...make a list in topmost first order. Use a colon delimited # list in case some portion of path contains whitespace. _G_dir_list=$_G_directory_path:$_G_dir_list # If the last portion added has no slash in it, the list is done case $_G_directory_path in */*) ;; *) break ;; esac # ...otherwise throw away the child directory and loop _G_directory_path=`$ECHO "$_G_directory_path" | $SED -e "$sed_dirname"` done _G_dir_list=`$ECHO "$_G_dir_list" | $SED 's|:*$||'` func_mkdir_p_IFS=$IFS; IFS=: for _G_dir in $_G_dir_list; do IFS=$func_mkdir_p_IFS # mkdir can fail with a 'File exist' error if two processes # try to create one of the directories concurrently. Don't # stop in that case! $MKDIR "$_G_dir" 2>/dev/null || : done IFS=$func_mkdir_p_IFS # Bail out if we (or some other process) failed to create a directory. test -d "$_G_directory_path" || \ func_fatal_error "Failed to create '$1'" fi } # func_mktempdir [BASENAME] # ------------------------- # Make a temporary directory that won't clash with other running # libtool processes, and avoids race conditions if possible. If # given, BASENAME is the basename for that directory. func_mktempdir () { $debug_cmd _G_template=${TMPDIR-/tmp}/${1-$progname} if test : = "$opt_dry_run"; then # Return a directory name, but don't create it in dry-run mode _G_tmpdir=$_G_template-$$ else # If mktemp works, use that first and foremost _G_tmpdir=`mktemp -d "$_G_template-XXXXXXXX" 2>/dev/null` if test ! -d "$_G_tmpdir"; then # Failing that, at least try and use $RANDOM to avoid a race _G_tmpdir=$_G_template-${RANDOM-0}$$ func_mktempdir_umask=`umask` umask 0077 $MKDIR "$_G_tmpdir" umask $func_mktempdir_umask fi # If we're not in dry-run mode, bomb out on failure test -d "$_G_tmpdir" || \ func_fatal_error "cannot create temporary directory '$_G_tmpdir'" fi $ECHO "$_G_tmpdir" } # func_normal_abspath PATH # ------------------------ # Remove doubled-up and trailing slashes, "." path components, # and cancel out any ".." path components in PATH after making # it an absolute path. func_normal_abspath () { $debug_cmd # These SED scripts presuppose an absolute path with a trailing slash. _G_pathcar='s|^/\([^/]*\).*$|\1|' _G_pathcdr='s|^/[^/]*||' _G_removedotparts=':dotsl s|/\./|/|g t dotsl s|/\.$|/|' _G_collapseslashes='s|/\{1,\}|/|g' _G_finalslash='s|/*$|/|' # Start from root dir and reassemble the path. func_normal_abspath_result= func_normal_abspath_tpath=$1 func_normal_abspath_altnamespace= case $func_normal_abspath_tpath in "") # Empty path, that just means $cwd. func_stripname '' '/' "`pwd`" func_normal_abspath_result=$func_stripname_result return ;; # The next three entries are used to spot a run of precisely # two leading slashes without using negated character classes; # we take advantage of case's first-match behaviour. ///*) # Unusual form of absolute path, do nothing. ;; //*) # Not necessarily an ordinary path; POSIX reserves leading '//' # and for example Cygwin uses it to access remote file shares # over CIFS/SMB, so we conserve a leading double slash if found. func_normal_abspath_altnamespace=/ ;; /*) # Absolute path, do nothing. ;; *) # Relative path, prepend $cwd. func_normal_abspath_tpath=`pwd`/$func_normal_abspath_tpath ;; esac # Cancel out all the simple stuff to save iterations. We also want # the path to end with a slash for ease of parsing, so make sure # there is one (and only one) here. func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ -e "$_G_removedotparts" -e "$_G_collapseslashes" -e "$_G_finalslash"` while :; do # Processed it all yet? if test / = "$func_normal_abspath_tpath"; then # If we ascended to the root using ".." the result may be empty now. if test -z "$func_normal_abspath_result"; then func_normal_abspath_result=/ fi break fi func_normal_abspath_tcomponent=`$ECHO "$func_normal_abspath_tpath" | $SED \ -e "$_G_pathcar"` func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ -e "$_G_pathcdr"` # Figure out what to do with it case $func_normal_abspath_tcomponent in "") # Trailing empty path component, ignore it. ;; ..) # Parent dir; strip last assembled component from result. func_dirname "$func_normal_abspath_result" func_normal_abspath_result=$func_dirname_result ;; *) # Actual path component, append it. func_append func_normal_abspath_result "/$func_normal_abspath_tcomponent" ;; esac done # Restore leading double-slash if one was found on entry. func_normal_abspath_result=$func_normal_abspath_altnamespace$func_normal_abspath_result } # func_notquiet ARG... # -------------------- # Echo program name prefixed message only when not in quiet mode. func_notquiet () { $debug_cmd $opt_quiet || func_echo ${1+"$@"} # A bug in bash halts the script if the last line of a function # fails when set -e is in force, so we need another command to # work around that: : } # func_relative_path SRCDIR DSTDIR # -------------------------------- # Set func_relative_path_result to the relative path from SRCDIR to DSTDIR. func_relative_path () { $debug_cmd func_relative_path_result= func_normal_abspath "$1" func_relative_path_tlibdir=$func_normal_abspath_result func_normal_abspath "$2" func_relative_path_tbindir=$func_normal_abspath_result # Ascend the tree starting from libdir while :; do # check if we have found a prefix of bindir case $func_relative_path_tbindir in $func_relative_path_tlibdir) # found an exact match func_relative_path_tcancelled= break ;; $func_relative_path_tlibdir*) # found a matching prefix func_stripname "$func_relative_path_tlibdir" '' "$func_relative_path_tbindir" func_relative_path_tcancelled=$func_stripname_result if test -z "$func_relative_path_result"; then func_relative_path_result=. fi break ;; *) func_dirname $func_relative_path_tlibdir func_relative_path_tlibdir=$func_dirname_result if test -z "$func_relative_path_tlibdir"; then # Have to descend all the way to the root! func_relative_path_result=../$func_relative_path_result func_relative_path_tcancelled=$func_relative_path_tbindir break fi func_relative_path_result=../$func_relative_path_result ;; esac done # Now calculate path; take care to avoid doubling-up slashes. func_stripname '' '/' "$func_relative_path_result" func_relative_path_result=$func_stripname_result func_stripname '/' '/' "$func_relative_path_tcancelled" if test -n "$func_stripname_result"; then func_append func_relative_path_result "/$func_stripname_result" fi # Normalisation. If bindir is libdir, return '.' else relative path. if test -n "$func_relative_path_result"; then func_stripname './' '' "$func_relative_path_result" func_relative_path_result=$func_stripname_result fi test -n "$func_relative_path_result" || func_relative_path_result=. : } # func_quote_portable EVAL ARG # ---------------------------- # Internal function to portably implement func_quote_arg. Note that we still # keep attention to performance here so we as much as possible try to avoid # calling sed binary (so far O(N) complexity as long as func_append is O(1)). func_quote_portable () { $debug_cmd $require_check_ifs_backslash func_quote_portable_result=$2 # one-time-loop (easy break) while true do if $1; then func_quote_portable_result=`$ECHO "$2" | $SED \ -e "$sed_double_quote_subst" -e "$sed_double_backslash"` break fi # Quote for eval. case $func_quote_portable_result in *[\\\`\"\$]*) # Fallback to sed for $func_check_bs_ifs_broken=:, or when the string # contains the shell wildcard characters. case $check_ifs_backshlash_broken$func_quote_portable_result in :*|*[\[\*\?]*) func_quote_portable_result=`$ECHO "$func_quote_portable_result" \ | $SED "$sed_quote_subst"` break ;; esac func_quote_portable_old_IFS=$IFS for _G_char in '\' '`' '"' '$' do # STATE($1) PREV($2) SEPARATOR($3) set start "" "" func_quote_portable_result=dummy"$_G_char$func_quote_portable_result$_G_char"dummy IFS=$_G_char for _G_part in $func_quote_portable_result do case $1 in quote) func_append func_quote_portable_result "$3$2" set quote "$_G_part" "\\$_G_char" ;; start) set first "" "" func_quote_portable_result= ;; first) set quote "$_G_part" "" ;; esac done done IFS=$func_quote_portable_old_IFS ;; *) ;; esac break done func_quote_portable_unquoted_result=$func_quote_portable_result case $func_quote_portable_result in # double-quote args containing shell metacharacters to delay # word splitting, command substitution and variable expansion # for a subsequent eval. # many bourne shells cannot handle close brackets correctly # in scan sets, so we specify it separately. *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") func_quote_portable_result=\"$func_quote_portable_result\" ;; esac } # func_quotefast_eval ARG # ----------------------- # Quote one ARG (internal). This is equivalent to 'func_quote_arg eval ARG', # but optimized for speed. Result is stored in $func_quotefast_eval. if test xyes = `(x=; printf -v x %q yes; echo x"$x") 2>/dev/null`; then printf -v _GL_test_printf_tilde %q '~' if test '\~' = "$_GL_test_printf_tilde"; then func_quotefast_eval () { printf -v func_quotefast_eval_result %q "$1" } else # Broken older Bash implementations. Make those faster too if possible. func_quotefast_eval () { case $1 in '~'*) func_quote_portable false "$1" func_quotefast_eval_result=$func_quote_portable_result ;; *) printf -v func_quotefast_eval_result %q "$1" ;; esac } fi else func_quotefast_eval () { func_quote_portable false "$1" func_quotefast_eval_result=$func_quote_portable_result } fi # func_quote_arg MODEs ARG # ------------------------ # Quote one ARG to be evaled later. MODEs argument may contain zero or more # specifiers listed below separated by ',' character. This function returns two # values: # i) func_quote_arg_result # double-quoted (when needed), suitable for a subsequent eval # ii) func_quote_arg_unquoted_result # has all characters that are still active within double # quotes backslashified. Available only if 'unquoted' is specified. # # Available modes: # ---------------- # 'eval' (default) # - escape shell special characters # 'expand' # - the same as 'eval'; but do not quote variable references # 'pretty' # - request aesthetic output, i.e. '"a b"' instead of 'a\ b'. This might # be used later in func_quote to get output like: 'echo "a b"' instead # of 'echo a\ b'. This is slower than default on some shells. # 'unquoted' # - produce also $func_quote_arg_unquoted_result which does not contain # wrapping double-quotes. # # Examples for 'func_quote_arg pretty,unquoted string': # # string | *_result | *_unquoted_result # ------------+-----------------------+------------------- # " | \" | \" # a b | "a b" | a b # "a b" | "\"a b\"" | \"a b\" # * | "*" | * # z="${x-$y}" | "z=\"\${x-\$y}\"" | z=\"\${x-\$y}\" # # Examples for 'func_quote_arg pretty,unquoted,expand string': # # string | *_result | *_unquoted_result # --------------+---------------------+-------------------- # z="${x-$y}" | "z=\"${x-$y}\"" | z=\"${x-$y}\" func_quote_arg () { _G_quote_expand=false case ,$1, in *,expand,*) _G_quote_expand=: ;; esac case ,$1, in *,pretty,*|*,expand,*|*,unquoted,*) func_quote_portable $_G_quote_expand "$2" func_quote_arg_result=$func_quote_portable_result func_quote_arg_unquoted_result=$func_quote_portable_unquoted_result ;; *) # Faster quote-for-eval for some shells. func_quotefast_eval "$2" func_quote_arg_result=$func_quotefast_eval_result ;; esac } # func_quote MODEs ARGs... # ------------------------ # Quote all ARGs to be evaled later and join them into single command. See # func_quote_arg's description for more info. func_quote () { $debug_cmd _G_func_quote_mode=$1 ; shift func_quote_result= while test 0 -lt $#; do func_quote_arg "$_G_func_quote_mode" "$1" if test -n "$func_quote_result"; then func_append func_quote_result " $func_quote_arg_result" else func_append func_quote_result "$func_quote_arg_result" fi shift done } # func_stripname PREFIX SUFFIX NAME # --------------------------------- # strip PREFIX and SUFFIX from NAME, and store in func_stripname_result. # PREFIX and SUFFIX must not contain globbing or regex special # characters, hashes, percent signs, but SUFFIX may contain a leading # dot (in which case that matches only a dot). if test yes = "$_G_HAVE_XSI_OPS"; then eval 'func_stripname () { $debug_cmd # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are # positional parameters, so assign one to ordinary variable first. func_stripname_result=$3 func_stripname_result=${func_stripname_result#"$1"} func_stripname_result=${func_stripname_result%"$2"} }' else func_stripname () { $debug_cmd case $2 in .*) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%\\\\$2\$%%"`;; *) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%$2\$%%"`;; esac } fi # func_show_eval CMD [FAIL_EXP] # ----------------------------- # Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is # not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP # is given, then evaluate it. func_show_eval () { $debug_cmd _G_cmd=$1 _G_fail_exp=${2-':'} func_quote_arg pretty,expand "$_G_cmd" eval "func_notquiet $func_quote_arg_result" $opt_dry_run || { eval "$_G_cmd" _G_status=$? if test 0 -ne "$_G_status"; then eval "(exit $_G_status); $_G_fail_exp" fi } } # func_show_eval_locale CMD [FAIL_EXP] # ------------------------------------ # Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is # not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP # is given, then evaluate it. Use the saved locale for evaluation. func_show_eval_locale () { $debug_cmd _G_cmd=$1 _G_fail_exp=${2-':'} $opt_quiet || { func_quote_arg expand,pretty "$_G_cmd" eval "func_echo $func_quote_arg_result" } $opt_dry_run || { eval "$_G_user_locale $_G_cmd" _G_status=$? eval "$_G_safe_locale" if test 0 -ne "$_G_status"; then eval "(exit $_G_status); $_G_fail_exp" fi } } # func_tr_sh # ---------- # Turn $1 into a string suitable for a shell variable name. # Result is stored in $func_tr_sh_result. All characters # not in the set a-zA-Z0-9_ are replaced with '_'. Further, # if $1 begins with a digit, a '_' is prepended as well. func_tr_sh () { $debug_cmd case $1 in [0-9]* | *[!a-zA-Z0-9_]*) func_tr_sh_result=`$ECHO "$1" | $SED -e 's/^\([0-9]\)/_\1/' -e 's/[^a-zA-Z0-9_]/_/g'` ;; * ) func_tr_sh_result=$1 ;; esac } # func_verbose ARG... # ------------------- # Echo program name prefixed message in verbose mode only. func_verbose () { $debug_cmd $opt_verbose && func_echo "$*" : } # func_warn_and_continue ARG... # ----------------------------- # Echo program name prefixed warning message to standard error. func_warn_and_continue () { $debug_cmd $require_term_colors func_echo_infix_1 "${tc_red}warning$tc_reset" "$*" >&2 } # func_warning CATEGORY ARG... # ---------------------------- # Echo program name prefixed warning message to standard error. Warning # messages can be filtered according to CATEGORY, where this function # elides messages where CATEGORY is not listed in the global variable # 'opt_warning_types'. func_warning () { $debug_cmd # CATEGORY must be in the warning_categories list! case " $warning_categories " in *" $1 "*) ;; *) func_internal_error "invalid warning category '$1'" ;; esac _G_category=$1 shift case " $opt_warning_types " in *" $_G_category "*) $warning_func ${1+"$@"} ;; esac } # func_sort_ver VER1 VER2 # ----------------------- # 'sort -V' is not generally available. # Note this deviates from the version comparison in automake # in that it treats 1.5 < 1.5.0, and treats 1.4.4a < 1.4-p3a # but this should suffice as we won't be specifying old # version formats or redundant trailing .0 in bootstrap.conf. # If we did want full compatibility then we should probably # use m4_version_compare from autoconf. func_sort_ver () { $debug_cmd printf '%s\n%s\n' "$1" "$2" \ | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n -k 5,5n -k 6,6n -k 7,7n -k 8,8n -k 9,9n } # func_lt_ver PREV CURR # --------------------- # Return true if PREV and CURR are in the correct order according to # func_sort_ver, otherwise false. Use it like this: # # func_lt_ver "$prev_ver" "$proposed_ver" || func_fatal_error "..." func_lt_ver () { $debug_cmd test "x$1" = x`func_sort_ver "$1" "$2" | $SED 1q` } # Local variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" # time-stamp-time-zone: "UTC" # End: #! /bin/sh # A portable, pluggable option parser for Bourne shell. # Written by Gary V. Vaughan, 2010 # This is free software. There is NO warranty; not even for # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # Copyright (C) 2010-2019, 2021, 2023-2024 Bootstrap Authors # # This file is dual licensed under the terms of the MIT license # , and GPL version 2 or later # . You must apply one of # these licenses when using or redistributing this software or any of # the files within it. See the URLs above, or the file `LICENSE` # included in the Bootstrap distribution for the full license texts. # Please report bugs or propose patches to: # # Set a version string for this script. scriptversion=2019-02-19.15; # UTC ## ------ ## ## Usage. ## ## ------ ## # This file is a library for parsing options in your shell scripts along # with assorted other useful supporting features that you can make use # of too. # # For the simplest scripts you might need only: # # #!/bin/sh # . relative/path/to/funclib.sh # . relative/path/to/options-parser # scriptversion=1.0 # func_options ${1+"$@"} # eval set dummy "$func_options_result"; shift # ...rest of your script... # # In order for the '--version' option to work, you will need to have a # suitably formatted comment like the one at the top of this file # starting with '# Written by ' and ending with '# Copyright'. # # For '-h' and '--help' to work, you will also need a one line # description of your script's purpose in a comment directly above the # '# Written by ' line, like the one at the top of this file. # # The default options also support '--debug', which will turn on shell # execution tracing (see the comment above debug_cmd below for another # use), and '--verbose' and the func_verbose function to allow your script # to display verbose messages only when your user has specified # '--verbose'. # # After sourcing this file, you can plug in processing for additional # options by amending the variables from the 'Configuration' section # below, and following the instructions in the 'Option parsing' # section further down. ## -------------- ## ## Configuration. ## ## -------------- ## # You should override these variables in your script after sourcing this # file so that they reflect the customisations you have added to the # option parser. # The usage line for option parsing errors and the start of '-h' and # '--help' output messages. You can embed shell variables for delayed # expansion at the time the message is displayed, but you will need to # quote other shell meta-characters carefully to prevent them being # expanded when the contents are evaled. usage='$progpath [OPTION]...' # Short help message in response to '-h' and '--help'. Add to this or # override it after sourcing this library to reflect the full set of # options your script accepts. usage_message="\ --debug enable verbose shell tracing -W, --warnings=CATEGORY report the warnings falling in CATEGORY [all] -v, --verbose verbosely report processing --version print version information and exit -h, --help print short or long help message and exit " # Additional text appended to 'usage_message' in response to '--help'. long_help_message=" Warning categories include: 'all' show all warnings 'none' turn off all the warnings 'error' warnings are treated as fatal errors" # Help message printed before fatal option parsing errors. fatal_help="Try '\$progname --help' for more information." ## ------------------------- ## ## Hook function management. ## ## ------------------------- ## # This section contains functions for adding, removing, and running hooks # in the main code. A hook is just a list of function names that can be # run in order later on. # func_hookable FUNC_NAME # ----------------------- # Declare that FUNC_NAME will run hooks added with # 'func_add_hook FUNC_NAME ...'. func_hookable () { $debug_cmd func_append hookable_fns " $1" } # func_add_hook FUNC_NAME HOOK_FUNC # --------------------------------- # Request that FUNC_NAME call HOOK_FUNC before it returns. FUNC_NAME must # first have been declared "hookable" by a call to 'func_hookable'. func_add_hook () { $debug_cmd case " $hookable_fns " in *" $1 "*) ;; *) func_fatal_error "'$1' does not accept hook functions." ;; esac eval func_append ${1}_hooks '" $2"' } # func_remove_hook FUNC_NAME HOOK_FUNC # ------------------------------------ # Remove HOOK_FUNC from the list of hook functions to be called by # FUNC_NAME. func_remove_hook () { $debug_cmd eval ${1}_hooks='`$ECHO "\$'$1'_hooks" |$SED "s| '$2'||"`' } # func_propagate_result FUNC_NAME_A FUNC_NAME_B # --------------------------------------------- # If the *_result variable of FUNC_NAME_A _is set_, assign its value to # *_result variable of FUNC_NAME_B. func_propagate_result () { $debug_cmd func_propagate_result_result=: if eval "test \"\${${1}_result+set}\" = set" then eval "${2}_result=\$${1}_result" else func_propagate_result_result=false fi } # func_run_hooks FUNC_NAME [ARG]... # --------------------------------- # Run all hook functions registered to FUNC_NAME. # It's assumed that the list of hook functions contains nothing more # than a whitespace-delimited list of legal shell function names, and # no effort is wasted trying to catch shell meta-characters or preserve # whitespace. func_run_hooks () { $debug_cmd _G_rc_run_hooks=false case " $hookable_fns " in *" $1 "*) ;; *) func_fatal_error "'$1' does not support hook functions." ;; esac eval _G_hook_fns=\$$1_hooks; shift for _G_hook in $_G_hook_fns; do func_unset "${_G_hook}_result" eval $_G_hook '${1+"$@"}' func_propagate_result $_G_hook func_run_hooks if $func_propagate_result_result; then eval set dummy "$func_run_hooks_result"; shift fi done } ## --------------- ## ## Option parsing. ## ## --------------- ## # In order to add your own option parsing hooks, you must accept the # full positional parameter list from your hook function. You may remove # or edit any options that you action, and then pass back the remaining # unprocessed options in '_result', escaped # suitably for 'eval'. # # The '_result' variable is automatically unset # before your hook gets called; for best performance, only set the # *_result variable when necessary (i.e. don't call the 'func_quote' # function unnecessarily because it can be an expensive operation on some # machines). # # Like this: # # my_options_prep () # { # $debug_cmd # # # Extend the existing usage message. # usage_message=$usage_message' # -s, --silent don'\''t print informational messages # ' # # No change in '$@' (ignored completely by this hook). Leave # # my_options_prep_result variable intact. # } # func_add_hook func_options_prep my_options_prep # # # my_silent_option () # { # $debug_cmd # # args_changed=false # # # Note that, for efficiency, we parse as many options as we can # # recognise in a loop before passing the remainder back to the # # caller on the first unrecognised argument we encounter. # while test $# -gt 0; do # opt=$1; shift # case $opt in # --silent|-s) opt_silent=: # args_changed=: # ;; # # Separate non-argument short options: # -s*) func_split_short_opt "$_G_opt" # set dummy "$func_split_short_opt_name" \ # "-$func_split_short_opt_arg" ${1+"$@"} # shift # args_changed=: # ;; # *) # Make sure the first unrecognised option "$_G_opt" # # is added back to "$@" in case we need it later, # # if $args_changed was set to 'true'. # set dummy "$_G_opt" ${1+"$@"}; shift; break ;; # esac # done # # # Only call 'func_quote' here if we processed at least one argument. # if $args_changed; then # func_quote eval ${1+"$@"} # my_silent_option_result=$func_quote_result # fi # } # func_add_hook func_parse_options my_silent_option # # # my_option_validation () # { # $debug_cmd # # $opt_silent && $opt_verbose && func_fatal_help "\ # '--silent' and '--verbose' options are mutually exclusive." # } # func_add_hook func_validate_options my_option_validation # # You'll also need to manually amend $usage_message to reflect the extra # options you parse. It's preferable to append if you can, so that # multiple option parsing hooks can be added safely. # func_options_finish [ARG]... # ---------------------------- # Finishing the option parse loop (call 'func_options' hooks ATM). func_options_finish () { $debug_cmd func_run_hooks func_options ${1+"$@"} func_propagate_result func_run_hooks func_options_finish } # func_options [ARG]... # --------------------- # All the functions called inside func_options are hookable. See the # individual implementations for details. func_hookable func_options func_options () { $debug_cmd _G_options_quoted=false for my_func in options_prep parse_options validate_options options_finish do func_unset func_${my_func}_result func_unset func_run_hooks_result eval func_$my_func '${1+"$@"}' func_propagate_result func_$my_func func_options if $func_propagate_result_result; then eval set dummy "$func_options_result"; shift _G_options_quoted=: fi done $_G_options_quoted || { # As we (func_options) are top-level options-parser function and # nobody quoted "$@" for us yet, we need to do it explicitly for # caller. func_quote eval ${1+"$@"} func_options_result=$func_quote_result } } # func_options_prep [ARG]... # -------------------------- # All initialisations required before starting the option parse loop. # Note that when calling hook functions, we pass through the list of # positional parameters. If a hook function modifies that list, and # needs to propagate that back to rest of this script, then the complete # modified list must be put in 'func_run_hooks_result' before returning. func_hookable func_options_prep func_options_prep () { $debug_cmd # Option defaults: opt_verbose=false opt_warning_types= func_run_hooks func_options_prep ${1+"$@"} func_propagate_result func_run_hooks func_options_prep } # func_parse_options [ARG]... # --------------------------- # The main option parsing loop. func_hookable func_parse_options func_parse_options () { $debug_cmd _G_parse_options_requote=false # this just eases exit handling while test $# -gt 0; do # Defer to hook functions for initial option parsing, so they # get priority in the event of reusing an option name. func_run_hooks func_parse_options ${1+"$@"} func_propagate_result func_run_hooks func_parse_options if $func_propagate_result_result; then eval set dummy "$func_parse_options_result"; shift # Even though we may have changed "$@", we passed the "$@" array # down into the hook and it quoted it for us (because we are in # this if-branch). No need to quote it again. _G_parse_options_requote=false fi # Break out of the loop if we already parsed every option. test $# -gt 0 || break # We expect that one of the options parsed in this function matches # and thus we remove _G_opt from "$@" and need to re-quote. _G_match_parse_options=: _G_opt=$1 shift case $_G_opt in --debug|-x) debug_cmd='set -x' func_echo "enabling shell trace mode" >&2 $debug_cmd ;; --no-warnings|--no-warning|--no-warn) set dummy --warnings none ${1+"$@"} shift ;; --warnings|--warning|-W) if test $# = 0 && func_missing_arg $_G_opt; then _G_parse_options_requote=: break fi case " $warning_categories $1" in *" $1 "*) # trailing space prevents matching last $1 above func_append_uniq opt_warning_types " $1" ;; *all) opt_warning_types=$warning_categories ;; *none) opt_warning_types=none warning_func=: ;; *error) opt_warning_types=$warning_categories warning_func=func_fatal_error ;; *) func_fatal_error \ "unsupported warning category: '$1'" ;; esac shift ;; --verbose|-v) opt_verbose=: ;; --version) func_version ;; -\?|-h) func_usage ;; --help) func_help ;; # Separate optargs to long options (plugins may need this): --*=*) func_split_equals "$_G_opt" set dummy "$func_split_equals_lhs" \ "$func_split_equals_rhs" ${1+"$@"} shift ;; # Separate optargs to short options: -W*) func_split_short_opt "$_G_opt" set dummy "$func_split_short_opt_name" \ "$func_split_short_opt_arg" ${1+"$@"} shift ;; # Separate non-argument short options: -\?*|-h*|-v*|-x*) func_split_short_opt "$_G_opt" set dummy "$func_split_short_opt_name" \ "-$func_split_short_opt_arg" ${1+"$@"} shift ;; --) _G_parse_options_requote=: ; break ;; -*) func_fatal_help "unrecognised option: '$_G_opt'" ;; *) set dummy "$_G_opt" ${1+"$@"}; shift _G_match_parse_options=false break ;; esac if $_G_match_parse_options; then _G_parse_options_requote=: fi done if $_G_parse_options_requote; then # save modified positional parameters for caller func_quote eval ${1+"$@"} func_parse_options_result=$func_quote_result fi } # func_validate_options [ARG]... # ------------------------------ # Perform any sanity checks on option settings and/or unconsumed # arguments. func_hookable func_validate_options func_validate_options () { $debug_cmd # Display all warnings if -W was not given. test -n "$opt_warning_types" || opt_warning_types=" $warning_categories" func_run_hooks func_validate_options ${1+"$@"} func_propagate_result func_run_hooks func_validate_options # Bail if the options were screwed! $exit_cmd $EXIT_FAILURE } ## ----------------- ## ## Helper functions. ## ## ----------------- ## # This section contains the helper functions used by the rest of the # hookable option parser framework in ascii-betical order. # func_fatal_help ARG... # ---------------------- # Echo program name prefixed message to standard error, followed by # a help hint, and exit. func_fatal_help () { $debug_cmd eval \$ECHO \""Usage: $usage"\" eval \$ECHO \""$fatal_help"\" func_error ${1+"$@"} exit $EXIT_FAILURE } # func_help # --------- # Echo long help message to standard output and exit. func_help () { $debug_cmd func_usage_message $ECHO "$long_help_message" exit 0 } # func_missing_arg ARGNAME # ------------------------ # Echo program name prefixed message to standard error and set global # exit_cmd. func_missing_arg () { $debug_cmd func_error "Missing argument for '$1'." exit_cmd=exit } # func_split_equals STRING # ------------------------ # Set func_split_equals_lhs and func_split_equals_rhs shell variables # after splitting STRING at the '=' sign. test -z "$_G_HAVE_XSI_OPS" \ && (eval 'x=a/b/c; test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ && _G_HAVE_XSI_OPS=yes if test yes = "$_G_HAVE_XSI_OPS" then # This is an XSI compatible shell, allowing a faster implementation... eval 'func_split_equals () { $debug_cmd func_split_equals_lhs=${1%%=*} func_split_equals_rhs=${1#*=} if test "x$func_split_equals_lhs" = "x$1"; then func_split_equals_rhs= fi }' else # ...otherwise fall back to using expr, which is often a shell builtin. func_split_equals () { $debug_cmd func_split_equals_lhs=`expr "x$1" : 'x\([^=]*\)'` func_split_equals_rhs= test "x$func_split_equals_lhs=" = "x$1" \ || func_split_equals_rhs=`expr "x$1" : 'x[^=]*=\(.*\)$'` } fi #func_split_equals # func_split_short_opt SHORTOPT # ----------------------------- # Set func_split_short_opt_name and func_split_short_opt_arg shell # variables after splitting SHORTOPT after the 2nd character. if test yes = "$_G_HAVE_XSI_OPS" then # This is an XSI compatible shell, allowing a faster implementation... eval 'func_split_short_opt () { $debug_cmd func_split_short_opt_arg=${1#??} func_split_short_opt_name=${1%"$func_split_short_opt_arg"} }' else # ...otherwise fall back to using expr, which is often a shell builtin. func_split_short_opt () { $debug_cmd func_split_short_opt_name=`expr "x$1" : 'x\(-.\)'` func_split_short_opt_arg=`expr "x$1" : 'x-.\(.*\)$'` } fi #func_split_short_opt # func_usage # ---------- # Echo short help message to standard output and exit. func_usage () { $debug_cmd func_usage_message $ECHO "Run '$progname --help |${PAGER-more}' for full usage" exit 0 } # func_usage_message # ------------------ # Echo short help message to standard output. func_usage_message () { $debug_cmd eval \$ECHO \""Usage: $usage"\" echo $SED -n 's|^# || /^Written by/{ x;p;x } h /^Written by/q' < "$progpath" echo eval \$ECHO \""$usage_message"\" } # func_version # ------------ # Echo version message to standard output and exit. # The version message is extracted from the calling file's header # comments, with leading '# ' stripped: # 1. First display the progname and version # 2. Followed by the header comment line matching /^# Written by / # 3. Then a blank line followed by the first following line matching # /^# Copyright / # 4. Immediately followed by any lines between the previous matches, # except lines preceding the intervening completely blank line. # For example, see the header comments of this file. func_version () { $debug_cmd printf '%s\n' "$progname $scriptversion" $SED -n ' /^# Written by /!b s|^# ||; p; n :fwd2blnk /./ { n b fwd2blnk } p; n :holdwrnt s|^# || s|^# *$|| /^Copyright /!{ /./H n b holdwrnt } s|\((C)\)[ 0-9,-]*[ ,-]\([1-9][0-9]* \)|\1 \2| G s|\(\n\)\n*|\1|g p; q' < "$progpath" exit $? } # Local variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-pattern: "30/scriptversion=%:y-%02m-%02d.%02H; # UTC" # time-stamp-time-zone: "UTC" # End: # Set a version string. scriptversion='(GNU libtool) 2.5.4' # func_version # ------------ # Echo version message to standard output and exit. func_version () { $debug_cmd year=`date +%Y` cat < This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Originally written by Gordon Matzigkeit, 1996 (See AUTHORS for complete contributor listing) EOF exit $? } # func_echo ARG... # ---------------- # Libtool also displays the current mode in messages, so override # funclib.sh func_echo with this custom definition. func_echo () { $debug_cmd _G_message=$* func_echo_IFS=$IFS IFS=$nl for _G_line in $_G_message; do IFS=$func_echo_IFS $ECHO "$progname${opt_mode+: $opt_mode}: $_G_line" done IFS=$func_echo_IFS } ## ---------------- ## ## Options parsing. ## ## ---------------- ## # Hook in the functions to make sure our own options are parsed during # the option parsing loop. usage='$progpath [OPTION]... [MODE-ARG]...' # Short help message in response to '-h'. usage_message="Options: --config show all configuration variables --debug enable verbose shell tracing -n, --dry-run display commands without modifying any files --features display basic configuration information --finish use operation '--mode=finish' --mode=MODE use operation mode MODE --no-finish don't update shared library cache --no-quiet, --no-silent print default informational messages --no-warnings equivalent to '-Wnone' --preserve-dup-deps don't remove duplicate dependency libraries --quiet, --silent don't print informational messages --reorder-cache=DIRS reorder shared library cache for preferred DIRS --tag=TAG use configuration variables from tag TAG -v, --verbose print more informational messages than default --version print version information -W, --warnings=CATEGORY report the warnings falling in CATEGORY [all] -h, --help, --help-all print short, long, or detailed help message " # Additional text appended to 'usage_message' in response to '--help'. func_help () { $debug_cmd func_usage_message $ECHO "$long_help_message MODE must be one of the following: clean remove files from the build directory compile compile a source file into a libtool object execute automatically set library path, then run a program finish complete the installation of libtool libraries install install libraries or executables link create a library or an executable uninstall remove libraries from an installed directory MODE-ARGS vary depending on the MODE. When passed as first option, '--mode=MODE' may be abbreviated as 'MODE' or a unique abbreviation of that. Try '$progname --help --mode=MODE' for a more detailed description of MODE. When reporting a bug, please describe a test case to reproduce it and include the following information: host-triplet: $host shell: $SHELL compiler: $LTCC compiler flags: $LTCFLAGS linker: $LD (gnu? $with_gnu_ld) version: $progname $scriptversion Debian-2.5.4-11 automake: `($AUTOMAKE --version) 2>/dev/null |$SED 1q` autoconf: `($AUTOCONF --version) 2>/dev/null |$SED 1q` Report bugs to . GNU libtool home page: . General help using GNU software: ." exit 0 } # func_lo2o OBJECT-NAME # --------------------- # Transform OBJECT-NAME from a '.lo' suffix to the platform specific # object suffix. lo2o=s/\\.lo\$/.$objext/ o2lo=s/\\.$objext\$/.lo/ if test yes = "$_G_HAVE_XSI_OPS"; then eval 'func_lo2o () { case $1 in *.lo) func_lo2o_result=${1%.lo}.$objext ;; * ) func_lo2o_result=$1 ;; esac }' # func_xform LIBOBJ-OR-SOURCE # --------------------------- # Transform LIBOBJ-OR-SOURCE from a '.o' or '.c' (or otherwise) # suffix to a '.lo' libtool-object suffix. eval 'func_xform () { func_xform_result=${1%.*}.lo }' else # ...otherwise fall back to using sed. func_lo2o () { func_lo2o_result=`$ECHO "$1" | $SED "$lo2o"` } func_xform () { func_xform_result=`$ECHO "$1" | $SED 's|\.[^.]*$|.lo|'` } fi # func_fatal_configuration ARG... # ------------------------------- # Echo program name prefixed message to standard error, followed by # a configuration failure hint, and exit. func_fatal_configuration () { func_fatal_error ${1+"$@"} \ "See the $PACKAGE documentation for more information." \ "Fatal configuration error." } # func_config # ----------- # Display the configuration for all the tags in this script. func_config () { re_begincf='^# ### BEGIN LIBTOOL' re_endcf='^# ### END LIBTOOL' # Default configuration. $SED "1,/$re_begincf CONFIG/d;/$re_endcf CONFIG/,\$d" < "$progpath" # Now print the configurations for the tags. for tagname in $taglist; do $SED -n "/$re_begincf TAG CONFIG: $tagname\$/,/$re_endcf TAG CONFIG: $tagname\$/p" < "$progpath" done exit $? } # func_features # ------------- # Display the features supported by this script. func_features () { echo "host: $host" if test yes = "$build_libtool_libs"; then echo "enable shared libraries" else echo "disable shared libraries" fi if test yes = "$build_old_libs"; then echo "enable static libraries" else echo "disable static libraries" fi exit $? } # func_enable_tag TAGNAME # ----------------------- # Verify that TAGNAME is valid, and either flag an error and exit, or # enable the TAGNAME tag. We also add TAGNAME to the global $taglist # variable here. func_enable_tag () { # Global variable: tagname=$1 re_begincf="^# ### BEGIN LIBTOOL TAG CONFIG: $tagname\$" re_endcf="^# ### END LIBTOOL TAG CONFIG: $tagname\$" sed_extractcf=/$re_begincf/,/$re_endcf/p # Validate tagname. case $tagname in *[!-_A-Za-z0-9,/]*) func_fatal_error "invalid tag name: $tagname" ;; esac # Don't test for the "default" C tag, as we know it's # there but not specially marked. case $tagname in CC) ;; *) if $GREP "$re_begincf" "$progpath" >/dev/null 2>&1; then taglist="$taglist $tagname" # Evaluate the configuration. Be careful to quote the path # and the sed script, to avoid splitting on whitespace, but # also don't use non-portable quotes within backquotes within # quotes we have to do it in 2 steps: extractedcf=`$SED -n -e "$sed_extractcf" < "$progpath"` eval "$extractedcf" else func_error "ignoring unknown tag $tagname" fi ;; esac } # func_check_version_match # ------------------------ # Ensure that we are using m4 macros, and libtool script from the same # release of libtool. func_check_version_match () { if test "$package_revision" != "$macro_revision"; then if test "$VERSION" != "$macro_version"; then if test -z "$macro_version"; then cat >&2 <<_LT_EOF $progname: Version mismatch error. This is $PACKAGE $VERSION, but the $progname: definition of this LT_INIT comes from an older release. $progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION $progname: and run autoconf again. _LT_EOF else cat >&2 <<_LT_EOF $progname: Version mismatch error. This is $PACKAGE $VERSION, but the $progname: definition of this LT_INIT comes from $PACKAGE $macro_version. $progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION $progname: and run autoconf again. _LT_EOF fi else cat >&2 <<_LT_EOF $progname: Version mismatch error. This is $PACKAGE $VERSION, revision $package_revision, $progname: but the definition of this LT_INIT comes from revision $macro_revision. $progname: You should recreate aclocal.m4 with macros from revision $package_revision $progname: of $PACKAGE $VERSION and run autoconf again. _LT_EOF fi exit $EXIT_MISMATCH fi } # libtool_options_prep [ARG]... # ----------------------------- # Preparation for options parsed by libtool. libtool_options_prep () { $debug_mode # Option defaults: opt_config=false opt_dlopen= opt_dry_run=false opt_help=false opt_mode= opt_reorder_cache=false opt_preserve_dup_deps=false opt_quiet=false opt_finishing=true opt_warning= nonopt= preserve_args= _G_rc_lt_options_prep=: _G_rc_lt_options_prep=: # Shorthand for --mode=foo, only valid as the first argument case $1 in clean|clea|cle|cl) shift; set dummy --mode clean ${1+"$@"}; shift ;; compile|compil|compi|comp|com|co|c) shift; set dummy --mode compile ${1+"$@"}; shift ;; execute|execut|execu|exec|exe|ex|e) shift; set dummy --mode execute ${1+"$@"}; shift ;; finish|finis|fini|fin|fi|f) shift; set dummy --mode finish ${1+"$@"}; shift ;; install|instal|insta|inst|ins|in|i) shift; set dummy --mode install ${1+"$@"}; shift ;; link|lin|li|l) shift; set dummy --mode link ${1+"$@"}; shift ;; uninstall|uninstal|uninsta|uninst|unins|unin|uni|un|u) shift; set dummy --mode uninstall ${1+"$@"}; shift ;; *) _G_rc_lt_options_prep=false ;; esac if $_G_rc_lt_options_prep; then # Pass back the list of options. func_quote eval ${1+"$@"} libtool_options_prep_result=$func_quote_result fi } func_add_hook func_options_prep libtool_options_prep # libtool_parse_options [ARG]... # --------------------------------- # Provide handling for libtool specific options. libtool_parse_options () { $debug_cmd _G_rc_lt_parse_options=false # Perform our own loop to consume as many options as possible in # each iteration. while test $# -gt 0; do _G_match_lt_parse_options=: _G_opt=$1 shift case $_G_opt in --dry-run|--dryrun|-n) opt_dry_run=: ;; --config) func_config ;; --dlopen|-dlopen) opt_dlopen="${opt_dlopen+$opt_dlopen }$1" shift ;; --preserve-dup-deps) opt_preserve_dup_deps=: ;; --features) func_features ;; --finish) set dummy --mode finish ${1+"$@"}; shift ;; --help) opt_help=: ;; --help-all) opt_help=': help-all' ;; --mode) test $# = 0 && func_missing_arg $_G_opt && break opt_mode=$1 case $1 in # Valid mode arguments: clean|compile|execute|finish|install|link|relink|uninstall) ;; # Catch anything else as an error *) func_error "invalid argument '$1' for $_G_opt" exit_cmd=exit ;; esac shift ;; --no-finish) opt_finishing=false func_append preserve_args " $_G_opt" ;; --no-silent|--no-quiet) opt_quiet=false func_append preserve_args " $_G_opt" ;; --no-warnings|--no-warning|--no-warn) opt_warning=false func_append preserve_args " $_G_opt" ;; --no-verbose) opt_verbose=false func_append preserve_args " $_G_opt" ;; --reorder-cache) opt_reorder_cache=true shared_lib_dirs=$1 if test -n "$shared_lib_dirs"; then case $1 in # Must begin with /: /*) ;; # Catch anything else as an error (relative paths) *) func_error "invalid argument '$1' for $_G_opt" func_error "absolute paths are required for $_G_opt" exit_cmd=exit ;; esac fi shift ;; --silent|--quiet) opt_quiet=: opt_verbose=false func_append preserve_args " $_G_opt" ;; --tag) test $# = 0 && func_missing_arg $_G_opt && break opt_tag=$1 func_append preserve_args " $_G_opt $1" func_enable_tag "$1" shift ;; --verbose|-v) opt_quiet=false opt_verbose=: func_append preserve_args " $_G_opt" ;; # An option not handled by this hook function: *) set dummy "$_G_opt" ${1+"$@"} ; shift _G_match_lt_parse_options=false break ;; esac $_G_match_lt_parse_options && _G_rc_lt_parse_options=: done if $_G_rc_lt_parse_options; then # save modified positional parameters for caller func_quote eval ${1+"$@"} libtool_parse_options_result=$func_quote_result fi } func_add_hook func_parse_options libtool_parse_options # func_warning ARG... # ------------------- # Libtool warnings are not categorized, so override funclib.sh # func_warning with this simpler definition. func_warning () { if $opt_warning; then $debug_cmd $warning_func ${1+"$@"} fi } # libtool_validate_options [ARG]... # --------------------------------- # Perform any sanity checks on option settings and/or unconsumed # arguments. libtool_validate_options () { # save first non-option argument if test 0 -lt $#; then nonopt=$1 shift fi # preserve --debug test : = "$debug_cmd" || func_append preserve_args " --debug" case $host_os in # Solaris2 added to fix http://debbugs.gnu.org/cgi/bugreport.cgi?bug=16452 # see also: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=59788 cygwin* | mingw* | windows* | pw32* | cegcc* | solaris2* | os2*) # don't eliminate duplications in $postdeps and $predeps opt_duplicate_compiler_generated_deps=: ;; *) opt_duplicate_compiler_generated_deps=$opt_preserve_dup_deps ;; esac $opt_help || { # Sanity checks first: func_check_version_match test yes != "$build_libtool_libs" \ && test yes != "$build_old_libs" \ && func_fatal_configuration "not configured to build any kind of library" # Darwin sucks eval std_shrext=\"$shrext_cmds\" # Only execute mode is allowed to have -dlopen flags. if test -n "$opt_dlopen" && test execute != "$opt_mode"; then func_error "unrecognized option '-dlopen'" $ECHO "$help" 1>&2 exit $EXIT_FAILURE fi # Change the help message to a mode-specific one. generic_help=$help help="Try '$progname --help --mode=$opt_mode' for more information." } # Pass back the unparsed argument list func_quote eval ${1+"$@"} libtool_validate_options_result=$func_quote_result } func_add_hook func_validate_options libtool_validate_options # Process options as early as possible so that --help and --version # can return quickly. func_options ${1+"$@"} eval set dummy "$func_options_result"; shift ## ----------- ## ## Main. ## ## ----------- ## magic='%%%MAGIC variable%%%' magic_exe='%%%MAGIC EXE variable%%%' # Global variables. extracted_archives= extracted_serial=0 # If this variable is set in any of the actions, the command in it # will be execed at the end. This prevents here-documents from being # left over by shells. exec_cmd= # A function that is used when there is no print builtin or printf. func_fallback_echo () { eval 'cat <<_LTECHO_EOF $1 _LTECHO_EOF' } # func_generated_by_libtool # True iff stdin has been generated by Libtool. This function is only # a basic sanity check; it will hardly flush out determined imposters. func_generated_by_libtool_p () { $GREP "^# Generated by .*$PACKAGE" > /dev/null 2>&1 } # func_lalib_p file # True iff FILE is a libtool '.la' library or '.lo' object file. # This function is only a basic sanity check; it will hardly flush out # determined imposters. func_lalib_p () { test -f "$1" && $SED -e 4q "$1" 2>/dev/null | func_generated_by_libtool_p } # func_lalib_unsafe_p file # True iff FILE is a libtool '.la' library or '.lo' object file. # This function implements the same check as func_lalib_p without # resorting to external programs. To this end, it redirects stdin and # closes it afterwards, without saving the original file descriptor. # As a safety measure, use it only where a negative result would be # fatal anyway. Works if 'file' does not exist. func_lalib_unsafe_p () { lalib_p=no if test -f "$1" && test -r "$1" && exec 5<&0 <"$1"; then for lalib_p_l in 1 2 3 4 do read lalib_p_line case $lalib_p_line in \#\ Generated\ by\ *$PACKAGE* ) lalib_p=yes; break;; esac done exec 0<&5 5<&- fi test yes = "$lalib_p" } # func_ltwrapper_script_p file # True iff FILE is a libtool wrapper script # This function is only a basic sanity check; it will hardly flush out # determined imposters. func_ltwrapper_script_p () { test -f "$1" && $lt_truncate_bin < "$1" 2>/dev/null | func_generated_by_libtool_p } # func_ltwrapper_executable_p file # True iff FILE is a libtool wrapper executable # This function is only a basic sanity check; it will hardly flush out # determined imposters. func_ltwrapper_executable_p () { func_ltwrapper_exec_suffix= case $1 in *.exe) ;; *) func_ltwrapper_exec_suffix=.exe ;; esac $GREP "$magic_exe" "$1$func_ltwrapper_exec_suffix" >/dev/null 2>&1 } # func_ltwrapper_scriptname file # Assumes file is an ltwrapper_executable # uses $file to determine the appropriate filename for a # temporary ltwrapper_script. func_ltwrapper_scriptname () { func_dirname_and_basename "$1" "" "." func_stripname '' '.exe' "$func_basename_result" func_ltwrapper_scriptname_result=$func_dirname_result/$objdir/${func_stripname_result}_ltshwrapper } # func_ltwrapper_p file # True iff FILE is a libtool wrapper script or wrapper executable # This function is only a basic sanity check; it will hardly flush out # determined imposters. func_ltwrapper_p () { func_ltwrapper_script_p "$1" || func_ltwrapper_executable_p "$1" } # func_execute_cmds commands fail_cmd # Execute tilde-delimited COMMANDS. # If FAIL_CMD is given, eval that upon failure. # FAIL_CMD may read-access the current command in variable CMD! func_execute_cmds () { $debug_cmd save_ifs=$IFS; IFS='~' for cmd in $1; do IFS=$sp$nl eval cmd=\"$cmd\" IFS=$save_ifs func_show_eval "$cmd" "${2-:}" done IFS=$save_ifs } # func_source file # Source FILE, adding directory component if necessary. # Note that it is not necessary on cygwin/mingw to append a dot to # FILE even if both FILE and FILE.exe exist: automatic-append-.exe # behavior happens only for exec(3), not for open(2)! Also, sourcing # 'FILE.' does not work on cygwin managed mounts. func_source () { $debug_cmd case $1 in */* | *\\*) . "$1" ;; *) . "./$1" ;; esac } # func_resolve_sysroot PATH # Replace a leading = in PATH with a sysroot. Store the result into # func_resolve_sysroot_result func_resolve_sysroot () { func_resolve_sysroot_result=$1 case $func_resolve_sysroot_result in =*) func_stripname '=' '' "$func_resolve_sysroot_result" func_resolve_sysroot_result=$lt_sysroot$func_stripname_result ;; esac } # func_replace_sysroot PATH # If PATH begins with the sysroot, replace it with = and # store the result into func_replace_sysroot_result. func_replace_sysroot () { case $lt_sysroot:$1 in ?*:"$lt_sysroot"*) func_stripname "$lt_sysroot" '' "$1" func_replace_sysroot_result='='$func_stripname_result ;; *) # Including no sysroot. func_replace_sysroot_result=$1 ;; esac } # func_infer_tag arg # Infer tagged configuration to use if any are available and # if one wasn't chosen via the "--tag" command line option. # Only attempt this if the compiler in the base compile # command doesn't match the default compiler. # arg is usually of the form 'gcc ...' func_infer_tag () { $debug_cmd if test -n "$available_tags" && test -z "$tagname"; then CC_quoted= for arg in $CC; do func_append_quoted CC_quoted "$arg" done CC_expanded=`func_echo_all $CC` CC_quoted_expanded=`func_echo_all $CC_quoted` case $@ in # Blanks in the command may have been stripped by the calling shell, # but not from the CC environment variable when configure was run. " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \ " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*) ;; # Blanks at the start of $base_compile will cause this to fail # if we don't check for them as well. *) for z in $available_tags; do if $GREP "^# ### BEGIN LIBTOOL TAG CONFIG: $z$" < "$progpath" > /dev/null; then # Evaluate the configuration. eval "`$SED -n -e '/^# ### BEGIN LIBTOOL TAG CONFIG: '$z'$/,/^# ### END LIBTOOL TAG CONFIG: '$z'$/p' < $progpath`" CC_quoted= for arg in $CC; do # Double-quote args containing other shell metacharacters. func_append_quoted CC_quoted "$arg" done CC_expanded=`func_echo_all $CC` CC_quoted_expanded=`func_echo_all $CC_quoted` case "$@ " in " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \ " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*) # The compiler in the base compile command matches # the one in the tagged configuration. # Assume this is the tagged configuration we want. tagname=$z break ;; esac fi done # If $tagname still isn't set, then no tagged configuration # was found and let the user know that the "--tag" command # line option must be used. if test -z "$tagname"; then func_echo "unable to infer tagged configuration" func_fatal_error "specify a tag with '--tag'" # else # func_verbose "using $tagname tagged configuration" fi ;; esac fi } # func_write_libtool_object output_name pic_name nonpic_name # Create a libtool object file (analogous to a ".la" file), # but don't create it if we're doing a dry run. func_write_libtool_object () { write_libobj=$1 if test yes = "$build_libtool_libs"; then write_lobj=\'$2\' else write_lobj=none fi if test yes = "$build_old_libs"; then write_oldobj=\'$3\' else write_oldobj=none fi $opt_dry_run || { cat >${write_libobj}T </dev/null` if test "$?" -eq 0 && test -n "$func_convert_core_file_wine_to_w32_tmp"; then func_convert_core_file_wine_to_w32_result=`$ECHO "$func_convert_core_file_wine_to_w32_tmp" | $SED -e "$sed_naive_backslashify"` else func_convert_core_file_wine_to_w32_result= fi fi } # end: func_convert_core_file_wine_to_w32 # func_convert_core_path_wine_to_w32 ARG # Helper function used by path conversion functions when $build is *nix, and # $host is mingw, windows, cygwin, or some other w32 environment. Relies on a # correctly configured wine environment available, with the winepath program # in $build's $PATH. Assumes ARG has no leading or trailing path separator # characters. # # ARG is path to be converted from $build format to win32. # Result is available in $func_convert_core_path_wine_to_w32_result. # Unconvertible file (directory) names in ARG are skipped; if no directory names # are convertible, then the result may be empty. func_convert_core_path_wine_to_w32 () { $debug_cmd # unfortunately, winepath doesn't convert paths, only file names func_convert_core_path_wine_to_w32_result= if test -n "$1"; then oldIFS=$IFS IFS=: for func_convert_core_path_wine_to_w32_f in $1; do IFS=$oldIFS func_convert_core_file_wine_to_w32 "$func_convert_core_path_wine_to_w32_f" if test -n "$func_convert_core_file_wine_to_w32_result"; then if test -z "$func_convert_core_path_wine_to_w32_result"; then func_convert_core_path_wine_to_w32_result=$func_convert_core_file_wine_to_w32_result else func_append func_convert_core_path_wine_to_w32_result ";$func_convert_core_file_wine_to_w32_result" fi fi done IFS=$oldIFS fi } # end: func_convert_core_path_wine_to_w32 # func_cygpath ARGS... # Wrapper around calling the cygpath program via LT_CYGPATH. This is used when # when (1) $build is *nix and Cygwin is hosted via a wine environment; or (2) # $build is MSYS and $host is Cygwin, or (3) $build is Cygwin. In case (1) or # (2), returns the Cygwin file name or path in func_cygpath_result (input # file name or path is assumed to be in w32 format, as previously converted # from $build's *nix or MSYS format). In case (3), returns the w32 file name # or path in func_cygpath_result (input file name or path is assumed to be in # Cygwin format). Returns an empty string on error. # # ARGS are passed to cygpath, with the last one being the file name or path to # be converted. # # Specify the absolute *nix (or w32) name to cygpath in the LT_CYGPATH # environment variable; do not put it in $PATH. func_cygpath () { $debug_cmd if test -n "$LT_CYGPATH" && test -f "$LT_CYGPATH"; then func_cygpath_result=`$LT_CYGPATH "$@" 2>/dev/null` if test "$?" -ne 0; then # on failure, ensure result is empty func_cygpath_result= fi else func_cygpath_result= func_error "LT_CYGPATH is empty or specifies non-existent file: '$LT_CYGPATH'" fi } #end: func_cygpath # func_convert_core_msys_to_w32 ARG # Convert file name or path ARG from MSYS format to w32 format. Return # result in func_convert_core_msys_to_w32_result. func_convert_core_msys_to_w32 () { $debug_cmd # awkward: cmd appends spaces to result func_convert_core_msys_to_w32_result=`( cmd //c echo "$1" ) 2>/dev/null | $SED -e 's/[ ]*$//' -e "$sed_naive_backslashify"` } #end: func_convert_core_msys_to_w32 # func_convert_file_check ARG1 ARG2 # Verify that ARG1 (a file name in $build format) was converted to $host # format in ARG2. Otherwise, emit an error message, but continue (resetting # func_to_host_file_result to ARG1). func_convert_file_check () { $debug_cmd if test -z "$2" && test -n "$1"; then func_error "Could not determine host file name corresponding to" func_error " '$1'" func_error "Continuing, but uninstalled executables may not work." # Fallback: func_to_host_file_result=$1 fi } # end func_convert_file_check # func_convert_path_check FROM_PATHSEP TO_PATHSEP FROM_PATH TO_PATH # Verify that FROM_PATH (a path in $build format) was converted to $host # format in TO_PATH. Otherwise, emit an error message, but continue, resetting # func_to_host_file_result to a simplistic fallback value (see below). func_convert_path_check () { $debug_cmd if test -z "$4" && test -n "$3"; then func_error "Could not determine the host path corresponding to" func_error " '$3'" func_error "Continuing, but uninstalled executables may not work." # Fallback. This is a deliberately simplistic "conversion" and # should not be "improved". See libtool.info. if test "x$1" != "x$2"; then lt_replace_pathsep_chars="s|$1|$2|g" func_to_host_path_result=`echo "$3" | $SED -e "$lt_replace_pathsep_chars"` else func_to_host_path_result=$3 fi fi } # end func_convert_path_check # func_convert_path_front_back_pathsep FRONTPAT BACKPAT REPL ORIG # Modifies func_to_host_path_result by prepending REPL if ORIG matches FRONTPAT # and appending REPL if ORIG matches BACKPAT. func_convert_path_front_back_pathsep () { $debug_cmd case $4 in $1 ) func_to_host_path_result=$3$func_to_host_path_result ;; esac case $4 in $2 ) func_append func_to_host_path_result "$3" ;; esac } # end func_convert_path_front_back_pathsep # func_convert_delimited_path PATH ORIG_DELIMITER NEW_DELIMITER # Replaces a delimiter for a given path. func_convert_delimited_path () { converted_path=`$ECHO "$1" | $SED "s#$2#$3#g"` } # end func_convert_delimited_path ################################################## # $build to $host FILE NAME CONVERSION FUNCTIONS # ################################################## # invoked via '$to_host_file_cmd ARG' # # In each case, ARG is the path to be converted from $build to $host format. # Result will be available in $func_to_host_file_result. # func_to_host_file ARG # Converts the file name ARG from $build format to $host format. Return result # in func_to_host_file_result. func_to_host_file () { $debug_cmd $to_host_file_cmd "$1" } # end func_to_host_file # func_to_tool_file ARG LAZY # converts the file name ARG from $build format to toolchain format. Return # result in func_to_tool_file_result. If the conversion in use is listed # in (the comma separated) LAZY, no conversion takes place. func_to_tool_file () { $debug_cmd case ,$2, in *,"$to_tool_file_cmd",*) func_to_tool_file_result=$1 ;; *) $to_tool_file_cmd "$1" func_to_tool_file_result=$func_to_host_file_result ;; esac } # end func_to_tool_file # func_convert_file_noop ARG # Copy ARG to func_to_host_file_result. func_convert_file_noop () { func_to_host_file_result=$1 } # end func_convert_file_noop # func_convert_file_msys_to_w32 ARG # Convert file name ARG from (mingw) MSYS to (mingw) w32 format; automatic # conversion to w32 is not available inside the cwrapper. Returns result in # func_to_host_file_result. func_convert_file_msys_to_w32 () { $debug_cmd func_to_host_file_result=$1 if test -n "$1"; then func_convert_core_msys_to_w32 "$1" func_to_host_file_result=$func_convert_core_msys_to_w32_result fi func_convert_file_check "$1" "$func_to_host_file_result" } # end func_convert_file_msys_to_w32 # func_convert_file_cygwin_to_w32 ARG # Convert file name ARG from Cygwin to w32 format. Returns result in # func_to_host_file_result. func_convert_file_cygwin_to_w32 () { $debug_cmd func_to_host_file_result=$1 if test -n "$1"; then # because $build is cygwin, we call "the" cygpath in $PATH; no need to use # LT_CYGPATH in this case. func_to_host_file_result=`cygpath -m "$1"` fi func_convert_file_check "$1" "$func_to_host_file_result" } # end func_convert_file_cygwin_to_w32 # func_convert_file_nix_to_w32 ARG # Convert file name ARG from *nix to w32 format. Requires a wine environment # and a working winepath. Returns result in func_to_host_file_result. func_convert_file_nix_to_w32 () { $debug_cmd func_to_host_file_result=$1 if test -n "$1"; then func_convert_core_file_wine_to_w32 "$1" func_to_host_file_result=$func_convert_core_file_wine_to_w32_result fi func_convert_file_check "$1" "$func_to_host_file_result" } # end func_convert_file_nix_to_w32 # func_convert_file_msys_to_cygwin ARG # Convert file name ARG from MSYS to Cygwin format. Requires LT_CYGPATH set. # Returns result in func_to_host_file_result. func_convert_file_msys_to_cygwin () { $debug_cmd func_to_host_file_result=$1 if test -n "$1"; then func_convert_core_msys_to_w32 "$1" func_cygpath -u "$func_convert_core_msys_to_w32_result" func_to_host_file_result=$func_cygpath_result fi func_convert_file_check "$1" "$func_to_host_file_result" } # end func_convert_file_msys_to_cygwin # func_convert_file_nix_to_cygwin ARG # Convert file name ARG from *nix to Cygwin format. Requires Cygwin installed # in a wine environment, working winepath, and LT_CYGPATH set. Returns result # in func_to_host_file_result. func_convert_file_nix_to_cygwin () { $debug_cmd func_to_host_file_result=$1 if test -n "$1"; then # convert from *nix to w32, then use cygpath to convert from w32 to cygwin. func_convert_core_file_wine_to_w32 "$1" func_cygpath -u "$func_convert_core_file_wine_to_w32_result" func_to_host_file_result=$func_cygpath_result fi func_convert_file_check "$1" "$func_to_host_file_result" } # end func_convert_file_nix_to_cygwin ############################################# # $build to $host PATH CONVERSION FUNCTIONS # ############################################# # invoked via '$to_host_path_cmd ARG' # # In each case, ARG is the path to be converted from $build to $host format. # The result will be available in $func_to_host_path_result. # # Path separators are also converted from $build format to $host format. If # ARG begins or ends with a path separator character, it is preserved (but # converted to $host format) on output. # # All path conversion functions are named using the following convention: # file name conversion function : func_convert_file_X_to_Y () # path conversion function : func_convert_path_X_to_Y () # where, for any given $build/$host combination the 'X_to_Y' value is the # same. If conversion functions are added for new $build/$host combinations, # the two new functions must follow this pattern, or func_init_to_host_path_cmd # will break. # func_init_to_host_path_cmd # Ensures that function "pointer" variable $to_host_path_cmd is set to the # appropriate value, based on the value of $to_host_file_cmd. to_host_path_cmd= func_init_to_host_path_cmd () { $debug_cmd if test -z "$to_host_path_cmd"; then func_stripname 'func_convert_file_' '' "$to_host_file_cmd" to_host_path_cmd=func_convert_path_$func_stripname_result fi } # func_to_host_path ARG # Converts the path ARG from $build format to $host format. Return result # in func_to_host_path_result. func_to_host_path () { $debug_cmd func_init_to_host_path_cmd $to_host_path_cmd "$1" } # end func_to_host_path # func_convert_path_noop ARG # Copy ARG to func_to_host_path_result. func_convert_path_noop () { func_to_host_path_result=$1 } # end func_convert_path_noop # func_convert_path_msys_to_w32 ARG # Convert path ARG from (mingw) MSYS to (mingw) w32 format; automatic # conversion to w32 is not available inside the cwrapper. Returns result in # func_to_host_path_result. func_convert_path_msys_to_w32 () { $debug_cmd func_to_host_path_result=$1 if test -n "$1"; then # Remove leading and trailing path separator characters from ARG. MSYS # behavior is inconsistent here; cygpath turns them into '.;' and ';.'; # and winepath ignores them completely. func_stripname : : "$1" func_to_host_path_tmp1=$func_stripname_result func_convert_core_msys_to_w32 "$func_to_host_path_tmp1" func_to_host_path_result=$func_convert_core_msys_to_w32_result func_convert_path_check : ";" \ "$func_to_host_path_tmp1" "$func_to_host_path_result" func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" fi } # end func_convert_path_msys_to_w32 # func_convert_path_cygwin_to_w32 ARG # Convert path ARG from Cygwin to w32 format. Returns result in # func_to_host_file_result. func_convert_path_cygwin_to_w32 () { $debug_cmd func_to_host_path_result=$1 if test -n "$1"; then # See func_convert_path_msys_to_w32: func_stripname : : "$1" func_to_host_path_tmp1=$func_stripname_result func_to_host_path_result=`cygpath -m -p "$func_to_host_path_tmp1"` func_convert_path_check : ";" \ "$func_to_host_path_tmp1" "$func_to_host_path_result" func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" fi } # end func_convert_path_cygwin_to_w32 # func_convert_path_nix_to_w32 ARG # Convert path ARG from *nix to w32 format. Requires a wine environment and # a working winepath. Returns result in func_to_host_file_result. func_convert_path_nix_to_w32 () { $debug_cmd func_to_host_path_result=$1 if test -n "$1"; then # See func_convert_path_msys_to_w32: func_stripname : : "$1" func_to_host_path_tmp1=$func_stripname_result func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1" func_to_host_path_result=$func_convert_core_path_wine_to_w32_result func_convert_path_check : ";" \ "$func_to_host_path_tmp1" "$func_to_host_path_result" func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" fi } # end func_convert_path_nix_to_w32 # func_convert_path_msys_to_cygwin ARG # Convert path ARG from MSYS to Cygwin format. Requires LT_CYGPATH set. # Returns result in func_to_host_file_result. func_convert_path_msys_to_cygwin () { $debug_cmd func_to_host_path_result=$1 if test -n "$1"; then # See func_convert_path_msys_to_w32: func_stripname : : "$1" func_to_host_path_tmp1=$func_stripname_result func_convert_core_msys_to_w32 "$func_to_host_path_tmp1" func_cygpath -u -p "$func_convert_core_msys_to_w32_result" func_to_host_path_result=$func_cygpath_result func_convert_path_check : : \ "$func_to_host_path_tmp1" "$func_to_host_path_result" func_convert_path_front_back_pathsep ":*" "*:" : "$1" fi } # end func_convert_path_msys_to_cygwin # func_convert_path_nix_to_cygwin ARG # Convert path ARG from *nix to Cygwin format. Requires Cygwin installed in a # a wine environment, working winepath, and LT_CYGPATH set. Returns result in # func_to_host_file_result. func_convert_path_nix_to_cygwin () { $debug_cmd func_to_host_path_result=$1 if test -n "$1"; then # Remove leading and trailing path separator characters from # ARG. msys behavior is inconsistent here, cygpath turns them # into '.;' and ';.', and winepath ignores them completely. func_stripname : : "$1" func_to_host_path_tmp1=$func_stripname_result func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1" func_cygpath -u -p "$func_convert_core_path_wine_to_w32_result" func_to_host_path_result=$func_cygpath_result func_convert_path_check : : \ "$func_to_host_path_tmp1" "$func_to_host_path_result" func_convert_path_front_back_pathsep ":*" "*:" : "$1" fi } # end func_convert_path_nix_to_cygwin # func_dll_def_p FILE # True iff FILE is a Windows DLL '.def' file. # Keep in sync with _LT_DLL_DEF_P in libtool.m4 func_dll_def_p () { $debug_cmd func_dll_def_p_tmp=`$SED -n \ -e 's/^[ ]*//' \ -e '/^\(;.*\)*$/d' \ -e 's/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p' \ -e q \ "$1"` test DEF = "$func_dll_def_p_tmp" } # func_reorder_shared_lib_cache DIRS # Reorder the shared library cache by unconfiguring previous shared library cache # and configuring preferred search directories before previous search directories. # Previous shared library cache: /usr/lib /usr/local/lib # Preferred search directories: /tmp/testing # Reordered shared library cache: /tmp/testing /usr/lib /usr/local/lib func_reorder_shared_lib_cache () { $debug_cmd case $host_os in openbsd*) get_search_directories=`PATH="$PATH:/sbin" ldconfig -r | $GREP "search directories" | $SED "s#.*search directories:\ ##g"` func_convert_delimited_path "$get_search_directories" ':' '\ ' save_search_directories=$converted_path func_convert_delimited_path "$1" ':' '\ ' # Ensure directories exist for dir in $converted_path; do # Ensure each directory is an absolute path case $dir in /*) ;; *) func_error "Directory '$dir' is not an absolute path" exit $EXIT_FAILURE ;; esac # Ensure no trailing slashes func_stripname '' '/' "$dir" dir=$func_stripname_result if test -d "$dir"; then if test -n "$preferred_search_directories"; then preferred_search_directories="$preferred_search_directories $dir" else preferred_search_directories=$dir fi else func_error "Directory '$dir' does not exist" exit $EXIT_FAILURE fi done PATH="$PATH:/sbin" ldconfig -U $save_search_directories PATH="$PATH:/sbin" ldconfig -m $preferred_search_directories $save_search_directories get_search_directories=`PATH="$PATH:/sbin" ldconfig -r | $GREP "search directories" | $SED "s#.*search directories:\ ##g"` func_convert_delimited_path "$get_search_directories" ':' '\ ' reordered_search_directories=$converted_path $ECHO "Original: $save_search_directories" $ECHO "Reordered: $reordered_search_directories" exit $EXIT_SUCCESS ;; *) func_error "--reorder-cache is not supported for host_os=$host_os." exit $EXIT_FAILURE ;; esac } # end func_reorder_shared_lib_cache # func_mode_compile arg... func_mode_compile () { $debug_cmd # Get the compilation command and the source file. base_compile= srcfile=$nonopt # always keep a non-empty value in "srcfile" suppress_opt=yes suppress_output= arg_mode=normal libobj= later= pie_flag= for arg do case $arg_mode in arg ) # do not "continue". Instead, add this to base_compile lastarg=$arg arg_mode=normal ;; target ) libobj=$arg arg_mode=normal continue ;; normal ) # Accept any command-line options. case $arg in -o) test -n "$libobj" && \ func_fatal_error "you cannot specify '-o' more than once" arg_mode=target continue ;; -pie | -fpie | -fPIE) func_append pie_flag " $arg" continue ;; -shared | -static | -prefer-pic | -prefer-non-pic) func_append later " $arg" continue ;; -no-suppress) suppress_opt=no continue ;; -Xcompiler) arg_mode=arg # the next one goes into the "base_compile" arg list continue # The current "srcfile" will either be retained or ;; # replaced later. I would guess that would be a bug. -Wc,*) func_stripname '-Wc,' '' "$arg" args=$func_stripname_result lastarg= save_ifs=$IFS; IFS=, for arg in $args; do IFS=$save_ifs func_append_quoted lastarg "$arg" done IFS=$save_ifs func_stripname ' ' '' "$lastarg" lastarg=$func_stripname_result # Add the arguments to base_compile. func_append base_compile " $lastarg" continue ;; *) # Accept the current argument as the source file. # The previous "srcfile" becomes the current argument. # lastarg=$srcfile srcfile=$arg ;; esac # case $arg ;; esac # case $arg_mode # Aesthetically quote the previous argument. func_append_quoted base_compile "$lastarg" done # for arg case $arg_mode in arg) func_fatal_error "you must specify an argument for -Xcompile" ;; target) func_fatal_error "you must specify a target with '-o'" ;; *) # Get the name of the library object. test -z "$libobj" && { func_basename "$srcfile" libobj=$func_basename_result } ;; esac # Recognize several different file suffixes. # If the user specifies -o file.o, it is replaced with file.lo case $libobj in *.[cCFSifmso] | \ *.ada | *.adb | *.ads | *.asm | \ *.c++ | *.cc | *.ii | *.class | *.cpp | *.cxx | \ *.[fF][09]? | *.for | *.java | *.go | *.obj | *.sx | *.cu | *.cup) func_xform "$libobj" libobj=$func_xform_result ;; esac case $libobj in *.lo) func_lo2o "$libobj"; obj=$func_lo2o_result ;; *) func_fatal_error "cannot determine name of library object from '$libobj'" ;; esac func_infer_tag $base_compile for arg in $later; do case $arg in -shared) test yes = "$build_libtool_libs" \ || func_fatal_configuration "cannot build a shared library" build_old_libs=no continue ;; -static) build_libtool_libs=no build_old_libs=yes continue ;; -prefer-pic) pic_mode=yes continue ;; -prefer-non-pic) pic_mode=no continue ;; esac done func_quote_arg pretty "$libobj" test "X$libobj" != "X$func_quote_arg_result" \ && $ECHO "X$libobj" | $GREP '[]~#^*{};<>?"'"'"' &()|`$[]' \ && func_warning "libobj name '$libobj' may not contain shell special characters." func_dirname_and_basename "$obj" "/" "" objname=$func_basename_result xdir=$func_dirname_result lobj=$xdir$objdir/$objname test -z "$base_compile" && \ func_fatal_help "you must specify a compilation command" # Delete any leftover library objects. if test yes = "$build_old_libs"; then removelist="$obj $lobj $libobj ${libobj}T" else removelist="$lobj $libobj ${libobj}T" fi # On Cygwin there's no "real" PIC flag so we must build both object types case $host_os in cygwin* | mingw* | windows* | pw32* | os2* | cegcc*) pic_mode=default ;; esac if test no = "$pic_mode" && test pass_all != "$deplibs_check_method"; then # non-PIC code in shared libraries is not supported pic_mode=default fi # Calculate the filename of the output object if compiler does # not support -o with -c if test no = "$compiler_c_o"; then output_obj=`$ECHO "$srcfile" | $SED 's%^.*/%%; s%\.[^.]*$%%'`.$objext lockfile=$output_obj.lock else output_obj= need_locks=no lockfile= fi # Lock this critical section if it is needed # We use this script file to make the link, it avoids creating a new file if test yes = "$need_locks"; then until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do func_echo "Waiting for $lockfile to be removed" sleep 2 done elif test warn = "$need_locks"; then if test -f "$lockfile"; then $ECHO "\ *** ERROR, $lockfile exists and contains: `cat $lockfile 2>/dev/null` This indicates that another process is trying to use the same temporary object file, and libtool could not work around it because your compiler does not support '-c' and '-o' together. If you repeat this compilation, it may succeed, by chance, but you had better avoid parallel builds (make -j) in this platform, or get a better compiler." $opt_dry_run || $RM $removelist exit $EXIT_FAILURE fi func_append removelist " $output_obj" $ECHO "$srcfile" > "$lockfile" fi $opt_dry_run || $RM $removelist func_append removelist " $lockfile" trap '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE' 1 2 15 func_to_tool_file "$srcfile" func_convert_file_msys_to_w32 srcfile=$func_to_tool_file_result func_quote_arg pretty "$srcfile" qsrcfile=$func_quote_arg_result # Only build a PIC object if we are building libtool libraries. if test yes = "$build_libtool_libs"; then # Without this assignment, base_compile gets emptied. fbsd_hideous_sh_bug=$base_compile if test no != "$pic_mode"; then command="$base_compile $qsrcfile $pic_flag" else # Don't build PIC code command="$base_compile $qsrcfile" fi func_mkdir_p "$xdir$objdir" if test -z "$output_obj"; then # Place PIC objects in $objdir func_append command " -o $lobj" fi func_show_eval_locale "$command" \ 'test -n "$output_obj" && $RM $removelist; exit $EXIT_FAILURE' if test warn = "$need_locks" && test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then $ECHO "\ *** ERROR, $lockfile contains: `cat $lockfile 2>/dev/null` but it should contain: $srcfile This indicates that another process is trying to use the same temporary object file, and libtool could not work around it because your compiler does not support '-c' and '-o' together. If you repeat this compilation, it may succeed, by chance, but you had better avoid parallel builds (make -j) in this platform, or get a better compiler." $opt_dry_run || $RM $removelist exit $EXIT_FAILURE fi # Just move the object if needed, then go on to compile the next one if test -n "$output_obj" && test "X$output_obj" != "X$lobj"; then func_show_eval '$MV "$output_obj" "$lobj"' \ 'error=$?; $opt_dry_run || $RM $removelist; exit $error' fi # Allow error messages only from the first compilation. if test yes = "$suppress_opt"; then suppress_output=' >/dev/null 2>&1' fi fi # Only build a position-dependent object if we build old libraries. if test yes = "$build_old_libs"; then if test yes != "$pic_mode"; then # Don't build PIC code command="$base_compile $qsrcfile$pie_flag" else command="$base_compile $qsrcfile $pic_flag" fi if test yes = "$compiler_c_o"; then func_append command " -o $obj" fi # Suppress compiler output if we already did a PIC compilation. func_append command "$suppress_output" func_show_eval_locale "$command" \ '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE' if test warn = "$need_locks" && test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then $ECHO "\ *** ERROR, $lockfile contains: `cat $lockfile 2>/dev/null` but it should contain: $srcfile This indicates that another process is trying to use the same temporary object file, and libtool could not work around it because your compiler does not support '-c' and '-o' together. If you repeat this compilation, it may succeed, by chance, but you had better avoid parallel builds (make -j) in this platform, or get a better compiler." $opt_dry_run || $RM $removelist exit $EXIT_FAILURE fi # Just move the object if needed if test -n "$output_obj" && test "X$output_obj" != "X$obj"; then func_show_eval '$MV "$output_obj" "$obj"' \ 'error=$?; $opt_dry_run || $RM $removelist; exit $error' fi fi $opt_dry_run || { func_write_libtool_object "$libobj" "$objdir/$objname" "$objname" # Unlock the critical section if it was locked if test no != "$need_locks"; then removelist=$lockfile $RM "$lockfile" fi } exit $EXIT_SUCCESS } $opt_help || { test compile = "$opt_mode" && func_mode_compile ${1+"$@"} } func_mode_help () { # We need to display help for each of the modes. case $opt_mode in "") # Generic help is extracted from the usage comments # at the start of this file. func_help ;; clean) $ECHO \ "Usage: $progname [OPTION]... --mode=clean RM [RM-OPTION]... FILE... Remove files from the build directory. RM is the name of the program to use to delete files associated with each FILE (typically '/bin/rm'). RM-OPTIONS are options (such as '-f') to be passed to RM. If FILE is a libtool library, object or program, all the files associated with it are deleted. Otherwise, only FILE itself is deleted using RM." ;; compile) $ECHO \ "Usage: $progname [OPTION]... --mode=compile COMPILE-COMMAND... SOURCEFILE Compile a source file into a libtool library object. This mode accepts the following additional options: -o OUTPUT-FILE set the output file name to OUTPUT-FILE -no-suppress do not suppress compiler output for multiple passes -prefer-pic try to build PIC objects only -prefer-non-pic try to build non-PIC objects only -shared do not build a '.o' file suitable for static linking -static only build a '.o' file suitable for static linking -Wc,FLAG -Xcompiler FLAG pass FLAG directly to the compiler COMPILE-COMMAND is a command to be used in creating a 'standard' object file from the given SOURCEFILE. The output file name is determined by removing the directory component from SOURCEFILE, then substituting the C source code suffix '.c' with the library object suffix, '.lo'." ;; execute) $ECHO \ "Usage: $progname [OPTION]... --mode=execute COMMAND [ARGS]... Automatically set library path, then run a program. This mode accepts the following additional options: -dlopen FILE add the directory containing FILE to the library path This mode sets the library path environment variable according to '-dlopen' flags. If any of the ARGS are libtool executable wrappers, then they are translated into their corresponding uninstalled binary, and any of their required library directories are added to the library path. Then, COMMAND is executed, with ARGS as arguments." ;; finish) $ECHO \ "Usage: $progname [OPTION]... --mode=finish [LIBDIR]... Complete the installation of libtool libraries. Each LIBDIR is a directory that contains libtool libraries. The commands that this mode executes may require superuser privileges. Use the '--dry-run' option if you just want to see what would be executed." ;; install) $ECHO \ "Usage: $progname [OPTION]... --mode=install INSTALL-COMMAND... Install executables or libraries. INSTALL-COMMAND is the installation command. The first component should be either the 'install' or 'cp' program. The following components of INSTALL-COMMAND are treated specially: -inst-prefix-dir PREFIX-DIR Use PREFIX-DIR as a staging area for installation The rest of the components are interpreted as arguments to that command (only BSD-compatible install options are recognized)." ;; link) $ECHO \ "Usage: $progname [OPTION]... --mode=link LINK-COMMAND... Link object files or libraries together to form another library, or to create an executable program. LINK-COMMAND is a command using the C compiler that you would use to create a program from several object files. The following components of LINK-COMMAND are treated specially: -all-static do not do any dynamic linking at all -avoid-version do not add a version suffix if possible -bindir BINDIR specify path to binaries directory (for systems where libraries must be found in the PATH setting at runtime) -dlopen FILE '-dlpreopen' FILE if it cannot be dlopened at runtime -dlpreopen FILE link in FILE and add its symbols to lt_preloaded_symbols -export-dynamic allow symbols from OUTPUT-FILE to be resolved with dlsym(3) -export-symbols SYMFILE try to export only the symbols listed in SYMFILE -export-symbols-regex REGEX try to export only the symbols matching REGEX -LLIBDIR search LIBDIR for required installed libraries -lNAME OUTPUT-FILE requires the installed library libNAME -module build a library that can dlopened -no-fast-install disable the fast-install mode -no-install link a not-installable executable -no-undefined declare that a library does not refer to external symbols -o OUTPUT-FILE create OUTPUT-FILE from the specified objects -objectlist FILE use a list of object files found in FILE to specify objects -os2dllname NAME force a short DLL name on OS/2 (no effect on other OSes) -precious-files-regex REGEX don't remove output files matching REGEX -release RELEASE specify package release information -rpath LIBDIR the created library will eventually be installed in LIBDIR -R[ ]LIBDIR add LIBDIR to the runtime path of programs and libraries -shared only do dynamic linking of libtool libraries -shrext SUFFIX override the standard shared library file extension -static do not do any dynamic linking of uninstalled libtool libraries -static-libtool-libs do not do any dynamic linking of libtool libraries -version-info CURRENT[:REVISION[:AGE]] specify library version info [each variable defaults to 0] -weak LIBNAME declare that the target provides the LIBNAME interface -Wc,FLAG -Xcompiler FLAG pass linker-specific FLAG directly to the compiler -Wa,FLAG -Xassembler FLAG pass linker-specific FLAG directly to the assembler -Wl,FLAG -Xlinker FLAG pass linker-specific FLAG directly to the linker -XCClinker FLAG pass link-specific FLAG to the compiler driver (CC) All other options (arguments beginning with '-') are ignored. Every other argument is treated as a filename. Files ending in '.la' are treated as uninstalled libtool libraries, other files are standard or library object files. If the OUTPUT-FILE ends in '.la', then a libtool library is created, only library objects ('.lo' files) may be specified, and '-rpath' is required, except when creating a convenience library. If OUTPUT-FILE ends in '.a' or '.lib', then a standard library is created using 'ar' and 'ranlib', or on Windows using 'lib'. If OUTPUT-FILE ends in '.lo' or '.$objext', then a reloadable object file is created, otherwise an executable program is created." ;; uninstall) $ECHO \ "Usage: $progname [OPTION]... --mode=uninstall RM [RM-OPTION]... FILE... Remove libraries from an installation directory. RM is the name of the program to use to delete files associated with each FILE (typically '/bin/rm'). RM-OPTIONS are options (such as '-f') to be passed to RM. If FILE is a libtool library, all the files associated with it are deleted. Otherwise, only FILE itself is deleted using RM." ;; *) func_fatal_help "invalid operation mode '$opt_mode'" ;; esac echo $ECHO "Try '$progname --help' for more information about other modes." } # Now that we've collected a possible --mode arg, show help if necessary if $opt_help; then if test : = "$opt_help"; then func_mode_help else { func_help noexit for opt_mode in compile link execute install finish uninstall clean; do func_mode_help done } | $SED -n '1p; 2,$s/^Usage:/ or: /p' { func_help noexit for opt_mode in compile link execute install finish uninstall clean; do echo func_mode_help done } | $SED '1d /^When reporting/,/^Report/{ H d } $x /information about other modes/d /more detailed .*MODE/d s/^Usage:.*--mode=\([^ ]*\) .*/Description of \1 mode:/' fi exit $? fi # If option '--reorder-cache', reorder the shared library cache and exit. if $opt_reorder_cache; then func_reorder_shared_lib_cache $shared_lib_dirs fi # func_mode_execute arg... func_mode_execute () { $debug_cmd # The first argument is the command name. cmd=$nonopt test -z "$cmd" && \ func_fatal_help "you must specify a COMMAND" # Handle -dlopen flags immediately. for file in $opt_dlopen; do test -f "$file" \ || func_fatal_help "'$file' is not a file" dir= case $file in *.la) func_resolve_sysroot "$file" file=$func_resolve_sysroot_result # Check to see that this really is a libtool archive. func_lalib_unsafe_p "$file" \ || func_fatal_help "'$lib' is not a valid libtool archive" # Read the libtool library. dlname= library_names= func_source "$file" # Skip this library if it cannot be dlopened. if test -z "$dlname"; then # Warn if it was a shared library. test -n "$library_names" && \ func_warning "'$file' was not linked with '-export-dynamic'" continue fi func_dirname "$file" "" "." dir=$func_dirname_result if test -f "$dir/$objdir/$dlname"; then func_append dir "/$objdir" else if test ! -f "$dir/$dlname"; then func_fatal_error "cannot find '$dlname' in '$dir' or '$dir/$objdir'" fi fi ;; *.lo) # Just add the directory containing the .lo file. func_dirname "$file" "" "." dir=$func_dirname_result ;; *) func_warning "'-dlopen' is ignored for non-libtool libraries and objects" continue ;; esac # Get the absolute pathname. absdir=`cd "$dir" && pwd` test -n "$absdir" && dir=$absdir # Now add the directory to shlibpath_var. if eval "test -z \"\$$shlibpath_var\""; then eval "$shlibpath_var=\"\$dir\"" else eval "$shlibpath_var=\"\$dir:\$$shlibpath_var\"" fi done # This variable tells wrapper scripts just to set shlibpath_var # rather than running their programs. libtool_execute_magic=$magic # Check if any of the arguments is a wrapper script. args= for file do case $file in -* | *.la | *.lo ) ;; *) # Do a test to see if this is really a libtool program. if func_ltwrapper_script_p "$file"; then func_source "$file" # Transform arg to wrapped name. file=$progdir/$program elif func_ltwrapper_executable_p "$file"; then func_ltwrapper_scriptname "$file" func_source "$func_ltwrapper_scriptname_result" # Transform arg to wrapped name. file=$progdir/$program fi ;; esac # Quote arguments (to preserve shell metacharacters). func_append_quoted args "$file" done if $opt_dry_run; then # Display what would be done. if test -n "$shlibpath_var"; then eval "\$ECHO \"\$shlibpath_var=\$$shlibpath_var\"" echo "export $shlibpath_var" fi $ECHO "$cmd$args" exit $EXIT_SUCCESS else if test -n "$shlibpath_var"; then # Export the shlibpath_var. eval "export $shlibpath_var" fi # Restore saved environment variables for lt_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES do eval "if test \"\${save_$lt_var+set}\" = set; then $lt_var=\$save_$lt_var; export $lt_var else $lt_unset $lt_var fi" done # Now prepare to actually exec the command. exec_cmd=\$cmd$args fi } test execute = "$opt_mode" && func_mode_execute ${1+"$@"} # func_mode_finish arg... func_mode_finish () { $debug_cmd libs= libdirs= admincmds= for opt in "$nonopt" ${1+"$@"} do if test -d "$opt"; then func_append libdirs " $opt" elif test -f "$opt"; then if func_lalib_unsafe_p "$opt"; then func_append libs " $opt" else func_warning "'$opt' is not a valid libtool archive" fi else func_fatal_error "invalid argument '$opt'" fi done if test -n "$libs"; then if test -n "$lt_sysroot"; then sysroot_regex=`$ECHO "$lt_sysroot" | $SED "$sed_make_literal_regex"` sysroot_cmd="s/\([ ']\)$sysroot_regex/\1/g;" else sysroot_cmd= fi # Remove sysroot references if $opt_dry_run; then for lib in $libs; do echo "removing references to $lt_sysroot and '=' prefixes from $lib" done else tmpdir=`func_mktempdir` for lib in $libs; do $SED -e "$sysroot_cmd s/\([ ']-[LR]\)=/\1/g; s/\([ ']\)=/\1/g" $lib \ > $tmpdir/tmp-la mv -f $tmpdir/tmp-la $lib done ${RM}r "$tmpdir" fi fi if test -n "$finish_cmds$finish_eval" && test -n "$libdirs" && $opt_finishing; then for libdir in $libdirs; do if test -n "$finish_cmds"; then # Do each command in the finish commands. func_execute_cmds "$finish_cmds" 'admincmds="$admincmds '"$cmd"'"' fi if test -n "$finish_eval"; then # Do the single finish_eval. eval cmds=\"$finish_eval\" $opt_dry_run || eval "$cmds" || func_append admincmds " $cmds" fi done fi # Exit here if they wanted silent mode. $opt_quiet && exit $EXIT_SUCCESS if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then echo "----------------------------------------------------------------------" echo "Libraries have been installed in:" for libdir in $libdirs; do $ECHO " $libdir" done if test "false" = "$opt_finishing"; then echo echo "NOTE: finish_cmds were not executed during testing, so you must" echo "manually run ldconfig to add a given test directory, LIBDIR, to" echo "the search path for generated executables." fi echo echo "If you ever happen to want to link against installed libraries" echo "in a given directory, LIBDIR, you must either use libtool, and" echo "specify the full pathname of the library, or use the '-LLIBDIR'" echo "flag during linking and do at least one of the following:" if test -n "$shlibpath_var"; then echo " - add LIBDIR to the '$shlibpath_var' environment variable" echo " during execution" fi if test -n "$runpath_var"; then echo " - add LIBDIR to the '$runpath_var' environment variable" echo " during linking" fi if test -n "$hardcode_libdir_flag_spec"; then libdir=LIBDIR eval flag=\"$hardcode_libdir_flag_spec\" $ECHO " - use the '$flag' linker flag" fi if test -n "$admincmds"; then $ECHO " - have your system administrator run these commands:$admincmds" fi if test -f /etc/ld.so.conf; then echo " - have your system administrator add LIBDIR to '/etc/ld.so.conf'" fi echo echo "See any operating system documentation about shared libraries for" case $host in solaris2.[6789]|solaris2.1[0-9]) echo "more information, such as the ld(1), crle(1) and ld.so(8) manual" echo "pages." ;; *) echo "more information, such as the ld(1) and ld.so(8) manual pages." ;; esac echo "----------------------------------------------------------------------" fi exit $EXIT_SUCCESS } test finish = "$opt_mode" && func_mode_finish ${1+"$@"} # func_mode_install arg... func_mode_install () { $debug_cmd # There may be an optional sh(1) argument at the beginning of # install_prog (especially on Windows NT). if test "$SHELL" = "$nonopt" || test /bin/sh = "$nonopt" || # Allow the use of GNU shtool's install command. case $nonopt in *shtool*) :;; *) false;; esac then # Aesthetically quote it. func_quote_arg pretty "$nonopt" install_prog="$func_quote_arg_result " arg=$1 shift else install_prog= arg=$nonopt fi # The real first argument should be the name of the installation program. # Aesthetically quote it. func_quote_arg pretty "$arg" func_append install_prog "$func_quote_arg_result" install_shared_prog=$install_prog case " $install_prog " in *[\\\ /]cp\ *) install_cp=: ;; *) install_cp=false ;; esac # We need to accept at least all the BSD install flags. dest= files= opts= prev= install_type= isdir=false stripme= no_mode=: for arg do arg2= if test -n "$dest"; then func_append files " $dest" dest=$arg continue fi case $arg in -d) isdir=: ;; -f) if $install_cp; then :; else prev=$arg fi ;; -g | -m | -o) prev=$arg ;; -s) stripme=" -s" continue ;; -*) ;; *) # If the previous option needed an argument, then skip it. if test -n "$prev"; then if test X-m = "X$prev" && test -n "$install_override_mode"; then arg2=$install_override_mode no_mode=false fi prev= else dest=$arg continue fi ;; esac # Aesthetically quote the argument. func_quote_arg pretty "$arg" func_append install_prog " $func_quote_arg_result" if test -n "$arg2"; then func_quote_arg pretty "$arg2" fi func_append install_shared_prog " $func_quote_arg_result" done test -z "$install_prog" && \ func_fatal_help "you must specify an install program" test -n "$prev" && \ func_fatal_help "the '$prev' option requires an argument" if test -n "$install_override_mode" && $no_mode; then if $install_cp; then :; else func_quote_arg pretty "$install_override_mode" func_append install_shared_prog " -m $func_quote_arg_result" fi fi if test -z "$files"; then if test -z "$dest"; then func_fatal_help "no file or destination specified" else func_fatal_help "you must specify a destination" fi fi # Strip any trailing slash from the destination. func_stripname '' '/' "$dest" dest=$func_stripname_result # Check to see that the destination is a directory. test -d "$dest" && isdir=: if $isdir; then destdir=$dest destname= else func_dirname_and_basename "$dest" "" "." destdir=$func_dirname_result destname=$func_basename_result # Not a directory, so check to see that there is only one file specified. set dummy $files; shift test "$#" -gt 1 && \ func_fatal_help "'$dest' is not a directory" fi case $destdir in [\\/]* | [A-Za-z]:[\\/]*) ;; *) for file in $files; do case $file in *.lo) ;; *) func_fatal_help "'$destdir' must be an absolute directory name" ;; esac done ;; esac # This variable tells wrapper scripts just to set variables rather # than running their programs. libtool_install_magic=$magic staticlibs= future_libdirs= current_libdirs= for file in $files; do # Do each installation. case $file in *.$libext) # Do the static libraries later. func_append staticlibs " $file" ;; *.la) func_resolve_sysroot "$file" file=$func_resolve_sysroot_result # Check to see that this really is a libtool archive. func_lalib_unsafe_p "$file" \ || func_fatal_help "'$file' is not a valid libtool archive" library_names= old_library= relink_command= func_source "$file" # Add the libdir to current_libdirs if it is the destination. if test "X$destdir" = "X$libdir"; then case "$current_libdirs " in *" $libdir "*) ;; *) func_append current_libdirs " $libdir" ;; esac else # Note the libdir as a future libdir. case "$future_libdirs " in *" $libdir "*) ;; *) func_append future_libdirs " $libdir" ;; esac fi func_dirname "$file" "/" "" dir=$func_dirname_result func_append dir "$objdir" if test -n "$relink_command"; then # Strip any trailing slash from the destination. func_stripname '' '/' "$libdir" destlibdir=$func_stripname_result func_stripname '' '/' "$destdir" s_destdir=$func_stripname_result # Determine the prefix the user has applied to our future dir. inst_prefix_dir=`$ECHO "X$s_destdir" | $Xsed -e "s%$destlibdir\$%%"` # Don't allow the user to place us outside of our expected # location b/c this prevents finding dependent libraries that # are installed to the same prefix. # At present, this check doesn't affect windows .dll's that # are installed into $libdir/../bin (currently, that works fine) # but it's something to keep an eye on. test "$inst_prefix_dir" = "$destdir" && \ func_fatal_error "error: cannot install '$file' to a directory not ending in $libdir" if test -n "$inst_prefix_dir"; then # Stick the inst_prefix_dir data into the link command. relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%-inst-prefix-dir $inst_prefix_dir%"` else relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%%"` fi func_warning "relinking '$file'" func_show_eval "$relink_command" \ 'func_fatal_error "error: relink '\''$file'\'' with the above command before installing it"' fi # See the names of the shared library. set dummy $library_names; shift if test -n "$1"; then realname=$1 shift srcname=$realname test -n "$relink_command" && srcname=${realname}T # Install the shared library and build the symlinks. func_show_eval "$install_shared_prog $dir/$srcname $destdir/$realname" \ 'exit $?' tstripme=$stripme case $host_os in cygwin* | mingw* | windows* | pw32* | cegcc*) case $realname in *.dll.a) tstripme= ;; esac ;; os2*) case $realname in *_dll.a) tstripme= ;; esac ;; esac if test -n "$tstripme" && test -n "$striplib"; then func_show_eval "$striplib $destdir/$realname" 'exit $?' fi if test "$#" -gt 0; then # Delete the old symlinks, and create new ones. # Try 'ln -sf' first, because the 'ln' binary might depend on # the symlink we replace! Solaris /bin/ln does not understand -f, # so we also need to try rm && ln -s. for linkname do test "$linkname" != "$realname" \ && func_show_eval "(cd $destdir && { $LN_S -f $realname $linkname || { $RM $linkname && $LN_S $realname $linkname; }; })" done fi # Do each command in the postinstall commands. lib=$destdir/$realname func_execute_cmds "$postinstall_cmds" 'exit $?' fi # Install the pseudo-library for information purposes. func_basename "$file" name=$func_basename_result instname=$dir/${name}i func_show_eval "$install_prog $instname $destdir/$name" 'exit $?' # Maybe install the static library, too. test -n "$old_library" && func_append staticlibs " $dir/$old_library" ;; *.lo) # Install (i.e. copy) a libtool object. # Figure out destination file name, if it wasn't already specified. if test -n "$destname"; then destfile=$destdir/$destname else func_basename "$file" destfile=$func_basename_result destfile=$destdir/$destfile fi # Deduce the name of the destination old-style object file. case $destfile in *.lo) func_lo2o "$destfile" staticdest=$func_lo2o_result ;; *.$objext) staticdest=$destfile destfile= ;; *) func_fatal_help "cannot copy a libtool object to '$destfile'" ;; esac # Install the libtool object if requested. test -n "$destfile" && \ func_show_eval "$install_prog $file $destfile" 'exit $?' # Install the old object if enabled. if test yes = "$build_old_libs"; then # Deduce the name of the old-style object file. func_lo2o "$file" staticobj=$func_lo2o_result func_show_eval "$install_prog \$staticobj \$staticdest" 'exit $?' fi exit $EXIT_SUCCESS ;; *) # Figure out destination file name, if it wasn't already specified. if test -n "$destname"; then destfile=$destdir/$destname else func_basename "$file" destfile=$func_basename_result destfile=$destdir/$destfile fi # If the file is missing, and there is a .exe on the end, strip it # because it is most likely a libtool script we actually want to # install stripped_ext= case $file in *.exe) if test ! -f "$file"; then func_stripname '' '.exe' "$file" file=$func_stripname_result stripped_ext=.exe fi ;; esac # Do a test to see if this is really a libtool program. case $host in *cygwin* | *mingw* | *windows*) if func_ltwrapper_executable_p "$file"; then func_ltwrapper_scriptname "$file" wrapper=$func_ltwrapper_scriptname_result else func_stripname '' '.exe' "$file" wrapper=$func_stripname_result fi ;; *) wrapper=$file ;; esac if func_ltwrapper_script_p "$wrapper"; then notinst_deplibs= relink_command= func_source "$wrapper" # Check the variables that should have been set. test -z "$generated_by_libtool_version" && \ func_fatal_error "invalid libtool wrapper script '$wrapper'" finalize=: for lib in $notinst_deplibs; do # Check to see that each library is installed. libdir= if test -f "$lib"; then func_source "$lib" fi libfile=$libdir/`$ECHO "$lib" | $SED 's%^.*/%%g'` if test -n "$libdir" && test ! -f "$libfile"; then func_warning "'$lib' has not been installed in '$libdir'" finalize=false fi done relink_command= func_source "$wrapper" outputname= if test no = "$fast_install" && test -n "$relink_command"; then $opt_dry_run || { if $finalize; then tmpdir=`func_mktempdir` func_basename "$file$stripped_ext" file=$func_basename_result outputname=$tmpdir/$file # Replace the output file specification. relink_command=`$ECHO "$relink_command" | $SED 's%@OUTPUT@%'"$outputname"'%g'` $opt_quiet || { func_quote_arg expand,pretty "$relink_command" eval "func_echo $func_quote_arg_result" } if eval "$relink_command"; then : else func_error "error: relink '$file' with the above command before installing it" $opt_dry_run || ${RM}r "$tmpdir" continue fi file=$outputname else func_warning "cannot relink '$file'" fi } else # Install the binary that we compiled earlier. file=`$ECHO "$file$stripped_ext" | $SED "s%\([^/]*\)$%$objdir/\1%"` fi fi # remove .exe since cygwin /usr/bin/install will append another # one anyway case $install_prog,$host in */usr/bin/install*,*cygwin*) case $file:$destfile in *.exe:*.exe) # this is ok ;; *.exe:*) destfile=$destfile.exe ;; *:*.exe) func_stripname '' '.exe' "$destfile" destfile=$func_stripname_result ;; esac ;; esac func_show_eval "$install_prog\$stripme \$file \$destfile" 'exit $?' $opt_dry_run || if test -n "$outputname"; then ${RM}r "$tmpdir" fi ;; esac done for file in $staticlibs; do func_basename "$file" name=$func_basename_result # Set up the ranlib parameters. oldlib=$destdir/$name func_to_tool_file "$oldlib" func_convert_file_msys_to_w32 tool_oldlib=$func_to_tool_file_result func_show_eval "$install_prog \$file \$oldlib" 'exit $?' if test -n "$stripme" && test -n "$old_striplib"; then func_show_eval "$old_striplib $tool_oldlib" 'exit $?' fi # Do each command in the postinstall commands. func_execute_cmds "$old_postinstall_cmds" 'exit $?' done test -n "$future_libdirs" && \ func_warning "remember to run '$progname --finish$future_libdirs'" if test -n "$current_libdirs"; then # Maybe just do a dry run. $opt_dry_run && current_libdirs=" -n$current_libdirs" exec_cmd='$SHELL "$progpath" $preserve_args --finish$current_libdirs' else exit $EXIT_SUCCESS fi } test install = "$opt_mode" && func_mode_install ${1+"$@"} # func_generate_dlsyms outputname originator pic_p # Extract symbols from dlprefiles and create ${outputname}S.o with # a dlpreopen symbol table. func_generate_dlsyms () { $debug_cmd my_outputname=$1 my_originator=$2 my_pic_p=${3-false} my_prefix=`$ECHO "$my_originator" | $SED 's%[^a-zA-Z0-9]%_%g'` my_dlsyms= if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then if test -n "$NM" && test -n "$global_symbol_pipe"; then my_dlsyms=${my_outputname}S.c else func_error "not configured to extract global symbols from dlpreopened files" fi fi if test -n "$my_dlsyms"; then case $my_dlsyms in "") ;; *.c) # Discover the nlist of each of the dlfiles. nlist=$output_objdir/$my_outputname.nm func_show_eval "$RM $nlist ${nlist}S ${nlist}T" # Parse the name list into a source file. func_verbose "creating $output_objdir/$my_dlsyms" $opt_dry_run || $ECHO > "$output_objdir/$my_dlsyms" "\ /* $my_dlsyms - symbol resolution table for '$my_outputname' dlsym emulation. */ /* Generated by $PROGRAM (GNU $PACKAGE) $VERSION */ #ifdef __cplusplus extern \"C\" { #endif #if defined __GNUC__ && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 4)) || (__GNUC__ > 4)) #pragma GCC diagnostic ignored \"-Wstrict-prototypes\" #endif /* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ #if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE /* DATA imports from DLLs on WIN32 can't be const, because runtime relocations are performed -- see ld's documentation on pseudo-relocs. */ # define LT_DLSYM_CONST #elif defined __osf__ /* This system does not cope well with relocations in const data. */ # define LT_DLSYM_CONST #else # define LT_DLSYM_CONST const #endif #define STREQ(s1, s2) (strcmp ((s1), (s2)) == 0) /* External symbol declarations for the compiler. */\ " if test yes = "$dlself"; then func_verbose "generating symbol list for '$output'" $opt_dry_run || echo ': @PROGRAM@ ' > "$nlist" # Add our own program objects to the symbol list. progfiles=`$ECHO "$objs$old_deplibs" | $SP2NL | $SED "$lo2o" | $NL2SP` for progfile in $progfiles; do func_to_tool_file "$progfile" func_convert_file_msys_to_w32 func_verbose "extracting global C symbols from '$func_to_tool_file_result'" $opt_dry_run || eval "$NM $func_to_tool_file_result | $global_symbol_pipe >> '$nlist'" done if test -n "$exclude_expsyms"; then $opt_dry_run || { eval '$EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T' eval '$MV "$nlist"T "$nlist"' } fi if test -n "$export_symbols_regex"; then $opt_dry_run || { eval '$EGREP -e "$export_symbols_regex" "$nlist" > "$nlist"T' eval '$MV "$nlist"T "$nlist"' } fi # Prepare the list of exported symbols if test -z "$export_symbols"; then export_symbols=$output_objdir/$outputname.exp $opt_dry_run || { $RM $export_symbols eval "$SED -n -e '/^: @PROGRAM@ $/d' -e 's/^.* \(.*\)$/\1/p' "'< "$nlist" > "$export_symbols"' case $host in *cygwin* | *mingw* | *windows* | *cegcc* ) eval "echo EXPORTS "'> "$output_objdir/$outputname.def"' eval 'cat "$export_symbols" >> "$output_objdir/$outputname.def"' ;; esac } else $opt_dry_run || { eval "$SED -e 's/\([].[*^$]\)/\\\\\1/g' -e 's/^/ /' -e 's/$/$/'"' < "$export_symbols" > "$output_objdir/$outputname.exp"' eval '$GREP -f "$output_objdir/$outputname.exp" < "$nlist" > "$nlist"T' eval '$MV "$nlist"T "$nlist"' case $host in *cygwin* | *mingw* | *windows* | *cegcc* ) eval "echo EXPORTS "'> "$output_objdir/$outputname.def"' eval 'cat "$nlist" >> "$output_objdir/$outputname.def"' ;; esac } fi fi for dlprefile in $dlprefiles; do func_verbose "extracting global C symbols from '$dlprefile'" func_basename "$dlprefile" name=$func_basename_result case $host in *cygwin* | *mingw* | *windows* | *cegcc* ) # if an import library, we need to obtain dlname if func_win32_import_lib_p "$dlprefile"; then func_tr_sh "$dlprefile" eval "curr_lafile=\$libfile_$func_tr_sh_result" dlprefile_dlbasename= if test -n "$curr_lafile" && func_lalib_p "$curr_lafile"; then # Use subshell, to avoid clobbering current variable values dlprefile_dlname=`source "$curr_lafile" && echo "$dlname"` if test -n "$dlprefile_dlname"; then func_basename "$dlprefile_dlname" dlprefile_dlbasename=$func_basename_result else # no lafile. user explicitly requested -dlpreopen . $sharedlib_from_linklib_cmd "$dlprefile" dlprefile_dlbasename=$sharedlib_from_linklib_result fi fi $opt_dry_run || { if test -n "$dlprefile_dlbasename"; then eval '$ECHO ": $dlprefile_dlbasename" >> "$nlist"' else func_warning "Could not compute DLL name from $name" eval '$ECHO ": $name " >> "$nlist"' fi func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 case $host in i[3456]86-*-mingw32*) eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe | $SED -e '/I __imp/d' -e 's/I __nm_/D /;s/_nm__//' >> '$nlist'" ;; *) eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe | $SED -e '/I __imp/d' -e 's/I __nm_/D /;s/__nm_//' >> '$nlist'" ;; esac } else # not an import lib $opt_dry_run || { eval '$ECHO ": $name " >> "$nlist"' func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'" } fi ;; *) $opt_dry_run || { eval '$ECHO ": $name " >> "$nlist"' func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'" } ;; esac done $opt_dry_run || { # Make sure we have at least an empty file. test -f "$nlist" || : > "$nlist" if test -n "$exclude_expsyms"; then $EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T $MV "$nlist"T "$nlist" fi # Try sorting and uniquifying the output. if $GREP -v "^: " < "$nlist" | if sort -k 3 /dev/null 2>&1; then sort -k 3 else sort +2 fi | uniq > "$nlist"S; then : else $GREP -v "^: " < "$nlist" > "$nlist"S fi if test -f "$nlist"S; then eval "$global_symbol_to_cdecl"' < "$nlist"S >> "$output_objdir/$my_dlsyms"' else echo '/* NONE */' >> "$output_objdir/$my_dlsyms" fi func_show_eval '$RM "${nlist}I"' if test -n "$global_symbol_to_import"; then eval "$global_symbol_to_import"' < "$nlist"S > "$nlist"I' fi echo >> "$output_objdir/$my_dlsyms" "\ /* The mapping between symbol names and symbols. */ typedef struct { const char *name; void *address; } lt_dlsymlist; extern LT_DLSYM_CONST lt_dlsymlist lt_${my_prefix}_LTX_preloaded_symbols[];\ " if test -s "$nlist"I; then echo >> "$output_objdir/$my_dlsyms" "\ static void lt_syminit(void) { LT_DLSYM_CONST lt_dlsymlist *symbol = lt_${my_prefix}_LTX_preloaded_symbols; for (; symbol->name; ++symbol) {" $SED 's/.*/ if (STREQ (symbol->name, \"&\")) symbol->address = (void *) \&&;/' < "$nlist"I >> "$output_objdir/$my_dlsyms" echo >> "$output_objdir/$my_dlsyms" "\ } }" fi echo >> "$output_objdir/$my_dlsyms" "\ LT_DLSYM_CONST lt_dlsymlist lt_${my_prefix}_LTX_preloaded_symbols[] = { {\"$my_originator\", (void *) 0}," if test -s "$nlist"I; then echo >> "$output_objdir/$my_dlsyms" "\ {\"@INIT@\", (void *) <_syminit}," fi case $need_lib_prefix in no) eval "$global_symbol_to_c_name_address" < "$nlist" >> "$output_objdir/$my_dlsyms" ;; *) eval "$global_symbol_to_c_name_address_lib_prefix" < "$nlist" >> "$output_objdir/$my_dlsyms" ;; esac echo >> "$output_objdir/$my_dlsyms" "\ {0, (void *) 0} }; /* This works around a problem in FreeBSD linker */ #ifdef FREEBSD_WORKAROUND static const void *lt_preloaded_setup() { return lt_${my_prefix}_LTX_preloaded_symbols; } #endif #ifdef __cplusplus } #endif\ " } # !$opt_dry_run pic_flag_for_symtable= case "$compile_command " in *" -static "*) ;; *) case $host in # compiling the symbol table file with pic_flag works around # a FreeBSD bug that causes programs to crash when -lm is # linked before any other PIC object. But we must not use # pic_flag when linking with -static. The problem exists in # FreeBSD 2.2.6 and is fixed in FreeBSD 3.1. *-*-freebsd2.*|*-*-freebsd3.0*|*-*-freebsdelf3.0*) pic_flag_for_symtable=" $pic_flag -DFREEBSD_WORKAROUND" ;; *-*-hpux*) pic_flag_for_symtable=" $pic_flag" ;; *) $my_pic_p && pic_flag_for_symtable=" $pic_flag" ;; esac ;; esac symtab_cflags= for arg in $LTCFLAGS; do case $arg in -pie | -fpie | -fPIE) ;; *) func_append symtab_cflags " $arg" ;; esac done # Now compile the dynamic symbol file. func_show_eval '(cd $output_objdir && $LTCC$symtab_cflags -c$no_builtin_flag$pic_flag_for_symtable "$my_dlsyms")' 'exit $?' # Clean up the generated files. func_show_eval '$RM "$output_objdir/$my_dlsyms" "$nlist" "${nlist}S" "${nlist}T" "${nlist}I"' # Transform the symbol file into the correct name. symfileobj=$output_objdir/${my_outputname}S.$objext case $host in *cygwin* | *mingw* | *windows* | *cegcc* ) if test -f "$output_objdir/$my_outputname.def"; then compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"` finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"` else compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"` finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"` fi ;; *) compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"` finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"` ;; esac ;; *) func_fatal_error "unknown suffix for '$my_dlsyms'" ;; esac else # We keep going just in case the user didn't refer to # lt_preloaded_symbols. The linker will fail if global_symbol_pipe # really was required. # Nullify the symbol file. compile_command=`$ECHO "$compile_command" | $SED "s% @SYMFILE@%%"` finalize_command=`$ECHO "$finalize_command" | $SED "s% @SYMFILE@%%"` fi } # func_cygming_gnu_implib_p ARG # This predicate returns with zero status (TRUE) if # ARG is a GNU/binutils-style import library. Returns # with nonzero status (FALSE) otherwise. func_cygming_gnu_implib_p () { $debug_cmd func_to_tool_file "$1" func_convert_file_msys_to_w32 func_cygming_gnu_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $EGREP ' (_head_[A-Za-z0-9_]+_[ad]l*|[A-Za-z0-9_]+_[ad]l*_iname)$'` test -n "$func_cygming_gnu_implib_tmp" } # func_cygming_ms_implib_p ARG # This predicate returns with zero status (TRUE) if # ARG is an MS-style import library. Returns # with nonzero status (FALSE) otherwise. func_cygming_ms_implib_p () { $debug_cmd func_to_tool_file "$1" func_convert_file_msys_to_w32 func_cygming_ms_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $GREP '_NULL_IMPORT_DESCRIPTOR'` test -n "$func_cygming_ms_implib_tmp" } # func_win32_libid arg # return the library type of file 'arg' # # Need a lot of goo to handle *both* DLLs and import libs # Has to be a shell function in order to 'eat' the argument # that is supplied when $file_magic_command is called. # Despite the name, also deal with 64 bit binaries. func_win32_libid () { $debug_cmd win32_libid_type=unknown win32_fileres=`file -L $1 2>/dev/null` case $win32_fileres in *ar\ archive\ import\ library*) # definitely import win32_libid_type="x86 archive import" ;; *ar\ archive*) # could be an import, or static # Keep the egrep pattern in sync with the one in _LT_CHECK_MAGIC_METHOD. if eval $OBJDUMP -f $1 | $SED -e '10q' 2>/dev/null | $EGREP 'file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64|pe-aarch64)' >/dev/null; then case $nm_interface in "MS dumpbin") if func_cygming_ms_implib_p "$1" || func_cygming_gnu_implib_p "$1" then win32_nmres=import else win32_nmres= fi ;; *) func_to_tool_file "$1" func_convert_file_msys_to_w32 win32_nmres=`eval $NM -f posix -A \"$func_to_tool_file_result\" | $SED -n -e ' 1,100{ / I /{ s|.*|import| p q } }'` ;; esac case $win32_nmres in import*) win32_libid_type="x86 archive import";; *) win32_libid_type="x86 archive static";; esac fi ;; *DLL*) win32_libid_type="x86 DLL" ;; *executable*) # but shell scripts are "executable" too... case $win32_fileres in *MS\ Windows\ PE\ Intel*) win32_libid_type="x86 DLL" ;; esac ;; esac $ECHO "$win32_libid_type" } # func_cygming_dll_for_implib ARG # # Platform-specific function to extract the # name of the DLL associated with the specified # import library ARG. # Invoked by eval'ing the libtool variable # $sharedlib_from_linklib_cmd # Result is available in the variable # $sharedlib_from_linklib_result func_cygming_dll_for_implib () { $debug_cmd sharedlib_from_linklib_result=`$DLLTOOL --identify-strict --identify "$1"` } # func_cygming_dll_for_implib_fallback_core SECTION_NAME LIBNAMEs # # The is the core of a fallback implementation of a # platform-specific function to extract the name of the # DLL associated with the specified import library LIBNAME. # # SECTION_NAME is either .idata$6 or .idata$7, depending # on the platform and compiler that created the implib. # # Echos the name of the DLL associated with the # specified import library. func_cygming_dll_for_implib_fallback_core () { $debug_cmd match_literal=`$ECHO "$1" | $SED "$sed_make_literal_regex"` $OBJDUMP -s --section "$1" "$2" 2>/dev/null | $SED '/^Contents of section '"$match_literal"':/{ # Place marker at beginning of archive member dllname section s/.*/====MARK====/ p d } # These lines can sometimes be longer than 43 characters, but # are always uninteresting /:[ ]*file format pe[i]\{,1\}-/d /^In archive [^:]*:/d # Ensure marker is printed /^====MARK====/p # Remove all lines with less than 43 characters /^.\{43\}/!d # From remaining lines, remove first 43 characters s/^.\{43\}//' | $SED -n ' # Join marker and all lines until next marker into a single line /^====MARK====/ b para H $ b para b :para x s/\n//g # Remove the marker s/^====MARK====// # Remove trailing dots and whitespace s/[\. \t]*$// # Print /./p' | # we now have a list, one entry per line, of the stringified # contents of the appropriate section of all members of the # archive that possess that section. Heuristic: eliminate # all those that have a first or second character that is # a '.' (that is, objdump's representation of an unprintable # character.) This should work for all archives with less than # 0x302f exports -- but will fail for DLLs whose name actually # begins with a literal '.' or a single character followed by # a '.'. # # Of those that remain, print the first one. $SED -e '/^\./d;/^.\./d;q' } # func_cygming_dll_for_implib_fallback ARG # Platform-specific function to extract the # name of the DLL associated with the specified # import library ARG. # # This fallback implementation is for use when $DLLTOOL # does not support the --identify-strict option. # Invoked by eval'ing the libtool variable # $sharedlib_from_linklib_cmd # Result is available in the variable # $sharedlib_from_linklib_result func_cygming_dll_for_implib_fallback () { $debug_cmd if func_cygming_gnu_implib_p "$1"; then # binutils import library sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$7' "$1"` elif func_cygming_ms_implib_p "$1"; then # ms-generated import library sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$6' "$1"` else # unknown sharedlib_from_linklib_result= fi } # func_extract_an_archive dir oldlib func_extract_an_archive () { $debug_cmd f_ex_an_ar_dir=$1; shift f_ex_an_ar_oldlib=$1 if test yes = "$lock_old_archive_extraction"; then lockfile=$f_ex_an_ar_oldlib.lock until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do func_echo "Waiting for $lockfile to be removed" sleep 2 done fi func_show_eval "(cd \$f_ex_an_ar_dir && $AR x \"\$f_ex_an_ar_oldlib\")" \ 'stat=$?; rm -f "$lockfile"; exit $stat' if test yes = "$lock_old_archive_extraction"; then $opt_dry_run || rm -f "$lockfile" fi if ($AR t "$f_ex_an_ar_oldlib" | sort | sort -uc >/dev/null 2>&1); then : else func_fatal_error "object name conflicts in archive: $f_ex_an_ar_dir/$f_ex_an_ar_oldlib" fi } # func_extract_archives gentop oldlib ... func_extract_archives () { $debug_cmd my_gentop=$1; shift my_oldlibs=${1+"$@"} my_oldobjs= my_xlib= my_xabs= my_xdir= for my_xlib in $my_oldlibs; do # Extract the objects. case $my_xlib in [\\/]* | [A-Za-z]:[\\/]*) my_xabs=$my_xlib ;; *) my_xabs=`pwd`"/$my_xlib" ;; esac func_basename "$my_xlib" my_xlib=$func_basename_result my_xlib_u=$my_xlib while :; do case " $extracted_archives " in *" $my_xlib_u "*) func_arith $extracted_serial + 1 extracted_serial=$func_arith_result my_xlib_u=lt$extracted_serial-$my_xlib ;; *) break ;; esac done extracted_archives="$extracted_archives $my_xlib_u" my_xdir=$my_gentop/$my_xlib_u func_mkdir_p "$my_xdir" case $host in *-darwin*) func_verbose "Extracting $my_xabs" # Do not bother doing anything if just a dry run $opt_dry_run || { darwin_orig_dir=`pwd` cd $my_xdir || exit $? darwin_archive=$my_xabs darwin_curdir=`pwd` func_basename "$darwin_archive" darwin_base_archive=$func_basename_result darwin_arches=`$LIPO -info "$darwin_archive" 2>/dev/null | $GREP Architectures 2>/dev/null || true` if test -n "$darwin_arches"; then darwin_arches=`$ECHO "$darwin_arches" | $SED -e 's/.*are://'` darwin_arch= func_verbose "$darwin_base_archive has multiple architectures $darwin_arches" for darwin_arch in $darwin_arches; do func_mkdir_p "unfat-$$/$darwin_base_archive-$darwin_arch" $LIPO -thin $darwin_arch -output "unfat-$$/$darwin_base_archive-$darwin_arch/$darwin_base_archive" "$darwin_archive" cd "unfat-$$/$darwin_base_archive-$darwin_arch" func_extract_an_archive "`pwd`" "$darwin_base_archive" cd "$darwin_curdir" $RM "unfat-$$/$darwin_base_archive-$darwin_arch/$darwin_base_archive" done # $darwin_arches ## Okay now we've a bunch of thin objects, gotta fatten them up :) darwin_filelist=`find unfat-$$ -type f -name \*.o -print -o -name \*.lo -print | $SED -e "$sed_basename" | sort -u` darwin_file= darwin_files= for darwin_file in $darwin_filelist; do darwin_files=`find unfat-$$ -name $darwin_file -print | sort | $NL2SP` $LIPO -create -output "$darwin_file" $darwin_files done # $darwin_filelist $RM -rf unfat-$$ cd "$darwin_orig_dir" else cd $darwin_orig_dir func_extract_an_archive "$my_xdir" "$my_xabs" fi # $darwin_arches } # !$opt_dry_run ;; *) func_extract_an_archive "$my_xdir" "$my_xabs" ;; esac my_oldobjs="$my_oldobjs "`find $my_xdir -name \*.$objext -print -o -name \*.lo -print | sort | $NL2SP` done func_extract_archives_result=$my_oldobjs } # func_emit_wrapper [arg=no] # # Emit a libtool wrapper script on stdout. # Don't directly open a file because we may want to # incorporate the script contents within a cygwin/mingw/windows # wrapper executable. Must ONLY be called from within # func_mode_link because it depends on a number of variables # set therein. # # ARG is the value that the WRAPPER_SCRIPT_BELONGS_IN_OBJDIR # variable will take. If 'yes', then the emitted script # will assume that the directory where it is stored is # the $objdir directory. This is a cygwin/mingw/windows-specific # behavior. func_emit_wrapper () { func_emit_wrapper_arg1=${1-no} $ECHO "\ #! $SHELL # $output - temporary wrapper script for $objdir/$outputname # Generated by $PROGRAM (GNU $PACKAGE) $VERSION # # The $output program cannot be directly executed until all the libtool # libraries that it depends on are installed. # # This wrapper script should never be moved out of the build directory. # If it is, it will not operate correctly. # Sed substitution that helps us do robust quoting. It backslashifies # metacharacters that are still active within double-quoted strings. sed_quote_subst='$sed_quote_subst' # Be Bourne compatible if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then emulate sh NULLCMD=: # Zsh 3.x and 4.x performs word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else case \`(set -o) 2>/dev/null\` in *posix*) set -o posix;; esac fi BIN_SH=xpg4; export BIN_SH # for Tru64 DUALCASE=1; export DUALCASE # for MKS sh # The HP-UX ksh and POSIX shell print the target directory to stdout # if CDPATH is set. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH relink_command=\"$relink_command\" # This environment variable determines our operation mode. if test \"\$libtool_install_magic\" = \"$magic\"; then # install mode needs the following variables: generated_by_libtool_version='$macro_version' notinst_deplibs='$notinst_deplibs' else # When we are sourced in execute mode, \$file and \$ECHO are already set. if test \"\$libtool_execute_magic\" != \"$magic\"; then file=\"\$0\"" func_quote_arg pretty "$ECHO" qECHO=$func_quote_arg_result $ECHO "\ # A function that is used when there is no print builtin or printf. func_fallback_echo () { eval 'cat <<_LTECHO_EOF \$1 _LTECHO_EOF' } ECHO=$qECHO fi # Very basic option parsing. These options are (a) specific to # the libtool wrapper, (b) are identical between the wrapper # /script/ and the wrapper /executable/ that is used only on # windows platforms, and (c) all begin with the string "--lt-" # (application programs are unlikely to have options that match # this pattern). # # There are only two supported options: --lt-debug and # --lt-dump-script. There is, deliberately, no --lt-help. # # The first argument to this parsing function should be the # script's $0 value, followed by "$@". lt_option_debug= func_parse_lt_options () { lt_script_arg0=\$0 shift for lt_opt do case \"\$lt_opt\" in --lt-debug) lt_option_debug=1 ;; --lt-dump-script) lt_dump_D=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%/[^/]*$%%'\` test \"X\$lt_dump_D\" = \"X\$lt_script_arg0\" && lt_dump_D=. lt_dump_F=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%^.*/%%'\` cat \"\$lt_dump_D/\$lt_dump_F\" exit 0 ;; --lt-*) \$ECHO \"Unrecognized --lt- option: '\$lt_opt'\" 1>&2 exit 1 ;; esac done # Print the debug banner immediately: if test -n \"\$lt_option_debug\"; then echo \"$outputname:$output:\$LINENO: libtool wrapper (GNU $PACKAGE) $VERSION\" 1>&2 fi } # Used when --lt-debug. Prints its arguments to stdout # (redirection is the responsibility of the caller) func_lt_dump_args () { lt_dump_args_N=1; for lt_arg do \$ECHO \"$outputname:$output:\$LINENO: newargv[\$lt_dump_args_N]: \$lt_arg\" lt_dump_args_N=\`expr \$lt_dump_args_N + 1\` done } # Core function for launching the target application func_exec_program_core () { " case $host in # Backslashes separate directories on plain windows *-*-mingw* | *-*-windows* | *-*-os2* | *-cegcc*) $ECHO "\ if test -n \"\$lt_option_debug\"; then \$ECHO \"$outputname:$output:\$LINENO: newargv[0]: \$progdir\\\\\$program\" 1>&2 func_lt_dump_args \${1+\"\$@\"} 1>&2 fi exec \"\$progdir\\\\\$program\" \${1+\"\$@\"} " ;; *) $ECHO "\ if test -n \"\$lt_option_debug\"; then \$ECHO \"$outputname:$output:\$LINENO: newargv[0]: \$progdir/\$program\" 1>&2 func_lt_dump_args \${1+\"\$@\"} 1>&2 fi exec \"\$progdir/\$program\" \${1+\"\$@\"} " ;; esac $ECHO "\ \$ECHO \"\$0: cannot exec \$program \$*\" 1>&2 exit 1 } # A function to encapsulate launching the target application # Strips options in the --lt-* namespace from \$@ and # launches target application with the remaining arguments. func_exec_program () { case \" \$* \" in *\\ --lt-*) for lt_wr_arg do case \$lt_wr_arg in --lt-*) ;; *) set x \"\$@\" \"\$lt_wr_arg\"; shift;; esac shift done ;; esac func_exec_program_core \${1+\"\$@\"} } # Parse options func_parse_lt_options \"\$0\" \${1+\"\$@\"} # Find the directory that this script lives in. thisdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*$%%'\` test \"x\$thisdir\" = \"x\$file\" && thisdir=. # Follow symbolic links until we get to the real thisdir. file=\`ls -ld \"\$file\" | $SED -n 's/.*-> //p'\` while test -n \"\$file\"; do destdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*\$%%'\` # If there was a directory component, then change thisdir. if test \"x\$destdir\" != \"x\$file\"; then case \"\$destdir\" in [\\\\/]* | [A-Za-z]:[\\\\/]*) thisdir=\"\$destdir\" ;; *) thisdir=\"\$thisdir/\$destdir\" ;; esac fi file=\`\$ECHO \"\$file\" | $SED 's%^.*/%%'\` file=\`ls -ld \"\$thisdir/\$file\" | $SED -n 's/.*-> //p'\` done # Usually 'no', except on cygwin/mingw/windows when embedded into # the cwrapper. WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=$func_emit_wrapper_arg1 if test \"\$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR\" = \"yes\"; then # special case for '.' if test \"\$thisdir\" = \".\"; then thisdir=\`pwd\` fi # remove .libs from thisdir case \"\$thisdir\" in *[\\\\/]$objdir ) thisdir=\`\$ECHO \"\$thisdir\" | $SED 's%[\\\\/][^\\\\/]*$%%'\` ;; $objdir ) thisdir=. ;; esac fi # Try to get the absolute directory name. absdir=\`cd \"\$thisdir\" && pwd\` test -n \"\$absdir\" && thisdir=\"\$absdir\" " if test yes = "$fast_install"; then $ECHO "\ program=lt-'$outputname'$exeext progdir=\"\$thisdir/$objdir\" if test ! -f \"\$progdir/\$program\" || { file=\`ls -1dt \"\$progdir/\$program\" \"\$progdir/../\$program\" 2>/dev/null | $SED 1q\`; \\ test \"X\$file\" != \"X\$progdir/\$program\"; }; then file=\"\$\$-\$program\" if test ! -d \"\$progdir\"; then $MKDIR \"\$progdir\" else $RM \"\$progdir/\$file\" fi" $ECHO "\ # relink executable if necessary if test -n \"\$relink_command\"; then if relink_command_output=\`eval \$relink_command 2>&1\`; then : else \$ECHO \"\$relink_command_output\" >&2 $RM \"\$progdir/\$file\" exit 1 fi fi $MV \"\$progdir/\$file\" \"\$progdir/\$program\" 2>/dev/null || { $RM \"\$progdir/\$program\"; $MV \"\$progdir/\$file\" \"\$progdir/\$program\"; } $RM \"\$progdir/\$file\" fi" else $ECHO "\ program='$outputname' progdir=\"\$thisdir/$objdir\" " fi $ECHO "\ if test -f \"\$progdir/\$program\"; then" # fixup the dll searchpath if we need to. # # Fix the DLL searchpath if we need to. Do this before prepending # to shlibpath, because on Windows, both are PATH and uninstalled # libraries must come first. if test -n "$dllsearchpath"; then $ECHO "\ # Add the dll search path components to the executable PATH PATH=$dllsearchpath:\$PATH " fi # Export our shlibpath_var if we have one. if test yes = "$shlibpath_overrides_runpath" && test -n "$shlibpath_var" && test -n "$temp_rpath"; then $ECHO "\ # Add our own library path to $shlibpath_var $shlibpath_var=\"$temp_rpath\$$shlibpath_var\" # Some systems cannot cope with colon-terminated $shlibpath_var # The second colon is a workaround for a bug in BeOS R4 sed $shlibpath_var=\`\$ECHO \"\$$shlibpath_var\" | $SED 's/::*\$//'\` export $shlibpath_var " fi $ECHO "\ if test \"\$libtool_execute_magic\" != \"$magic\"; then # Run the actual program with our arguments. func_exec_program \${1+\"\$@\"} fi else # The program doesn't exist. \$ECHO \"\$0: error: '\$progdir/\$program' does not exist\" 1>&2 \$ECHO \"This script is just a wrapper for \$program.\" 1>&2 \$ECHO \"See the $PACKAGE documentation for more information.\" 1>&2 exit 1 fi fi\ " } # func_emit_cwrapperexe_src # emit the source code for a wrapper executable on stdout # Must ONLY be called from within func_mode_link because # it depends on a number of variable set therein. func_emit_cwrapperexe_src () { cat < #include #if defined _WIN32 && !defined __GNUC__ # include # include # include #else # include # include # ifdef __CYGWIN__ # include # endif #endif #include #include #include #include #include #include #include #include #define STREQ(s1, s2) (strcmp ((s1), (s2)) == 0) /* declarations of non-ANSI functions */ #if defined __MINGW32__ # ifdef __STRICT_ANSI__ _CRTIMP int __cdecl _putenv (const char *); # endif #elif defined __CYGWIN__ # ifdef __STRICT_ANSI__ char *realpath (const char *, char *); int putenv (char *); int setenv (const char *, const char *, int); # endif /* #elif defined other_platform || defined ... */ #endif /* portability defines, excluding path handling macros */ #if defined _MSC_VER # define setmode _setmode # define stat _stat # define chmod _chmod # define getcwd _getcwd # define putenv _putenv # define S_IXUSR _S_IEXEC #elif defined __MINGW32__ # define setmode _setmode # define stat _stat # define chmod _chmod # define getcwd _getcwd # define putenv _putenv #elif defined __CYGWIN__ # define HAVE_SETENV # define FOPEN_WB "wb" /* #elif defined other platforms ... */ #endif #if defined PATH_MAX # define LT_PATHMAX PATH_MAX #elif defined MAXPATHLEN # define LT_PATHMAX MAXPATHLEN #else # define LT_PATHMAX 1024 #endif #ifndef S_IXOTH # define S_IXOTH 0 #endif #ifndef S_IXGRP # define S_IXGRP 0 #endif /* path handling portability macros */ #ifndef DIR_SEPARATOR # define DIR_SEPARATOR '/' # define PATH_SEPARATOR ':' #endif #if defined _WIN32 || defined __MSDOS__ || defined __DJGPP__ || \ defined __OS2__ # define HAVE_DOS_BASED_FILE_SYSTEM # define FOPEN_WB "wb" # ifndef DIR_SEPARATOR_2 # define DIR_SEPARATOR_2 '\\' # endif # ifndef PATH_SEPARATOR_2 # define PATH_SEPARATOR_2 ';' # endif #endif #ifndef DIR_SEPARATOR_2 # define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR) #else /* DIR_SEPARATOR_2 */ # define IS_DIR_SEPARATOR(ch) \ (((ch) == DIR_SEPARATOR) || ((ch) == DIR_SEPARATOR_2)) #endif /* DIR_SEPARATOR_2 */ #ifndef PATH_SEPARATOR_2 # define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR) #else /* PATH_SEPARATOR_2 */ # define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR_2) #endif /* PATH_SEPARATOR_2 */ #ifndef FOPEN_WB # define FOPEN_WB "w" #endif #ifndef _O_BINARY # define _O_BINARY 0 #endif #define XMALLOC(type, num) ((type *) xmalloc ((num) * sizeof(type))) #define XFREE(stale) do { \ if (stale) { free (stale); stale = 0; } \ } while (0) #if defined LT_DEBUGWRAPPER static int lt_debug = 1; #else static int lt_debug = 0; #endif const char *program_name = "libtool-wrapper"; /* in case xstrdup fails */ void *xmalloc (size_t num); char *xstrdup (const char *string); const char *base_name (const char *name); char *find_executable (const char *wrapper); char *chase_symlinks (const char *pathspec); int make_executable (const char *path); int check_executable (const char *path); char *strendzap (char *str, const char *pat); void lt_debugprintf (const char *file, int line, const char *fmt, ...); void lt_fatal (const char *file, int line, const char *message, ...); static const char *nonnull (const char *s); static const char *nonempty (const char *s); void lt_setenv (const char *name, const char *value); char *lt_extend_str (const char *orig_value, const char *add, int to_end); void lt_update_exe_path (const char *name, const char *value); void lt_update_lib_path (const char *name, const char *value); char **prepare_spawn (char **argv); void lt_dump_script (FILE *f); EOF cat <= 0) && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) return 1; else return 0; } int make_executable (const char *path) { int rval = 0; struct stat st; lt_debugprintf (__FILE__, __LINE__, "(make_executable): %s\n", nonempty (path)); if ((!path) || (!*path)) return 0; if (stat (path, &st) >= 0) { rval = chmod (path, st.st_mode | S_IXOTH | S_IXGRP | S_IXUSR); } return rval; } /* Searches for the full path of the wrapper. Returns newly allocated full path name if found, NULL otherwise Does not chase symlinks, even on platforms that support them. */ char * find_executable (const char *wrapper) { int has_slash = 0; const char *p; const char *p_next; /* static buffer for getcwd */ char tmp[LT_PATHMAX + 1]; size_t tmp_len; char *concat_name; lt_debugprintf (__FILE__, __LINE__, "(find_executable): %s\n", nonempty (wrapper)); if ((wrapper == NULL) || (*wrapper == '\0')) return NULL; /* Absolute path? */ #if defined HAVE_DOS_BASED_FILE_SYSTEM if (isalpha ((unsigned char) wrapper[0]) && wrapper[1] == ':') { concat_name = xstrdup (wrapper); if (check_executable (concat_name)) return concat_name; XFREE (concat_name); } else { #endif if (IS_DIR_SEPARATOR (wrapper[0])) { concat_name = xstrdup (wrapper); if (check_executable (concat_name)) return concat_name; XFREE (concat_name); } #if defined HAVE_DOS_BASED_FILE_SYSTEM } #endif for (p = wrapper; *p; p++) if (*p == '/') { has_slash = 1; break; } if (!has_slash) { /* no slashes; search PATH */ const char *path = getenv ("PATH"); if (path != NULL) { for (p = path; *p; p = p_next) { const char *q; size_t p_len; for (q = p; *q; q++) if (IS_PATH_SEPARATOR (*q)) break; p_len = (size_t) (q - p); p_next = (*q == '\0' ? q : q + 1); if (p_len == 0) { /* empty path: current directory */ if (getcwd (tmp, LT_PATHMAX) == NULL) lt_fatal (__FILE__, __LINE__, "getcwd failed: %s", nonnull (strerror (errno))); tmp_len = strlen (tmp); concat_name = XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1); memcpy (concat_name, tmp, tmp_len); concat_name[tmp_len] = '/'; strcpy (concat_name + tmp_len + 1, wrapper); } else { concat_name = XMALLOC (char, p_len + 1 + strlen (wrapper) + 1); memcpy (concat_name, p, p_len); concat_name[p_len] = '/'; strcpy (concat_name + p_len + 1, wrapper); } if (check_executable (concat_name)) return concat_name; XFREE (concat_name); } } /* not found in PATH; assume curdir */ } /* Relative path | not found in path: prepend cwd */ if (getcwd (tmp, LT_PATHMAX) == NULL) lt_fatal (__FILE__, __LINE__, "getcwd failed: %s", nonnull (strerror (errno))); tmp_len = strlen (tmp); concat_name = XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1); memcpy (concat_name, tmp, tmp_len); concat_name[tmp_len] = '/'; strcpy (concat_name + tmp_len + 1, wrapper); if (check_executable (concat_name)) return concat_name; XFREE (concat_name); return NULL; } char * chase_symlinks (const char *pathspec) { #ifndef S_ISLNK return xstrdup (pathspec); #else char buf[LT_PATHMAX]; struct stat s; char *tmp_pathspec = xstrdup (pathspec); char *p; int has_symlinks = 0; while (strlen (tmp_pathspec) && !has_symlinks) { lt_debugprintf (__FILE__, __LINE__, "checking path component for symlinks: %s\n", tmp_pathspec); if (lstat (tmp_pathspec, &s) == 0) { if (S_ISLNK (s.st_mode) != 0) { has_symlinks = 1; break; } /* search backwards for last DIR_SEPARATOR */ p = tmp_pathspec + strlen (tmp_pathspec) - 1; while ((p > tmp_pathspec) && (!IS_DIR_SEPARATOR (*p))) p--; if ((p == tmp_pathspec) && (!IS_DIR_SEPARATOR (*p))) { /* no more DIR_SEPARATORS left */ break; } *p = '\0'; } else { lt_fatal (__FILE__, __LINE__, "error accessing file \"%s\": %s", tmp_pathspec, nonnull (strerror (errno))); } } XFREE (tmp_pathspec); if (!has_symlinks) { return xstrdup (pathspec); } tmp_pathspec = realpath (pathspec, buf); if (tmp_pathspec == 0) { lt_fatal (__FILE__, __LINE__, "could not follow symlinks for %s", pathspec); } return xstrdup (tmp_pathspec); #endif } char * strendzap (char *str, const char *pat) { size_t len, patlen; assert (str != NULL); assert (pat != NULL); len = strlen (str); patlen = strlen (pat); if (patlen <= len) { str += len - patlen; if (STREQ (str, pat)) *str = '\0'; } return str; } void lt_debugprintf (const char *file, int line, const char *fmt, ...) { va_list args; if (lt_debug) { (void) fprintf (stderr, "%s:%s:%d: ", program_name, file, line); va_start (args, fmt); (void) vfprintf (stderr, fmt, args); va_end (args); } } static void lt_error_core (int exit_status, const char *file, int line, const char *mode, const char *message, va_list ap) { fprintf (stderr, "%s:%s:%d: %s: ", program_name, file, line, mode); vfprintf (stderr, message, ap); fprintf (stderr, ".\n"); if (exit_status >= 0) exit (exit_status); } void lt_fatal (const char *file, int line, const char *message, ...) { va_list ap; va_start (ap, message); lt_error_core (EXIT_FAILURE, file, line, "FATAL", message, ap); va_end (ap); } static const char * nonnull (const char *s) { return s ? s : "(null)"; } static const char * nonempty (const char *s) { return (s && !*s) ? "(empty)" : nonnull (s); } void lt_setenv (const char *name, const char *value) { lt_debugprintf (__FILE__, __LINE__, "(lt_setenv) setting '%s' to '%s'\n", nonnull (name), nonnull (value)); { #ifdef HAVE_SETENV /* always make a copy, for consistency with !HAVE_SETENV */ char *str = xstrdup (value); setenv (name, str, 1); #else size_t len = strlen (name) + 1 + strlen (value) + 1; char *str = XMALLOC (char, len); sprintf (str, "%s=%s", name, value); if (putenv (str) != EXIT_SUCCESS) { XFREE (str); } #endif } } char * lt_extend_str (const char *orig_value, const char *add, int to_end) { char *new_value; if (orig_value && *orig_value) { size_t orig_value_len = strlen (orig_value); size_t add_len = strlen (add); new_value = XMALLOC (char, add_len + orig_value_len + 1); if (to_end) { strcpy (new_value, orig_value); strcpy (new_value + orig_value_len, add); } else { strcpy (new_value, add); strcpy (new_value + add_len, orig_value); } } else { new_value = xstrdup (add); } return new_value; } void lt_update_exe_path (const char *name, const char *value) { lt_debugprintf (__FILE__, __LINE__, "(lt_update_exe_path) modifying '%s' by prepending '%s'\n", nonnull (name), nonnull (value)); if (name && *name && value && *value) { char *new_value = lt_extend_str (getenv (name), value, 0); /* some systems can't cope with a ':'-terminated path #' */ size_t len = strlen (new_value); while ((len > 0) && IS_PATH_SEPARATOR (new_value[len-1])) { new_value[--len] = '\0'; } lt_setenv (name, new_value); XFREE (new_value); } } void lt_update_lib_path (const char *name, const char *value) { lt_debugprintf (__FILE__, __LINE__, "(lt_update_lib_path) modifying '%s' by prepending '%s'\n", nonnull (name), nonnull (value)); if (name && *name && value && *value) { char *new_value = lt_extend_str (getenv (name), value, 0); lt_setenv (name, new_value); XFREE (new_value); } } EOF case $host_os in mingw* | windows*) cat <<"EOF" /* Prepares an argument vector before calling spawn(). Note that spawn() does not by itself call the command interpreter (getenv ("COMSPEC") != NULL ? getenv ("COMSPEC") : ({ OSVERSIONINFO v; v.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&v); v.dwPlatformId == VER_PLATFORM_WIN32_NT; }) ? "cmd.exe" : "command.com"). Instead it simply concatenates the arguments, separated by ' ', and calls CreateProcess(). We must quote the arguments since Win32 CreateProcess() interprets characters like ' ', '\t', '\\', '"' (but not '<' and '>') in a special way: - Space and tab are interpreted as delimiters. They are not treated as delimiters if they are surrounded by double quotes: "...". - Unescaped double quotes are removed from the input. Their only effect is that within double quotes, space and tab are treated like normal characters. - Backslashes not followed by double quotes are not special. - But 2*n+1 backslashes followed by a double quote become n backslashes followed by a double quote (n >= 0): \" -> " \\\" -> \" \\\\\" -> \\" */ #define SHELL_SPECIAL_CHARS "\"\\ \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" #define SHELL_SPACE_CHARS " \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" char ** prepare_spawn (char **argv) { size_t argc; char **new_argv; size_t i; /* Count number of arguments. */ for (argc = 0; argv[argc] != NULL; argc++) ; /* Allocate new argument vector. */ new_argv = XMALLOC (char *, argc + 1); /* Put quoted arguments into the new argument vector. */ for (i = 0; i < argc; i++) { const char *string = argv[i]; if (string[0] == '\0') new_argv[i] = xstrdup ("\"\""); else if (strpbrk (string, SHELL_SPECIAL_CHARS) != NULL) { int quote_around = (strpbrk (string, SHELL_SPACE_CHARS) != NULL); size_t length; unsigned int backslashes; const char *s; char *quoted_string; char *p; length = 0; backslashes = 0; if (quote_around) length++; for (s = string; *s != '\0'; s++) { char c = *s; if (c == '"') length += backslashes + 1; length++; if (c == '\\') backslashes++; else backslashes = 0; } if (quote_around) length += backslashes + 1; quoted_string = XMALLOC (char, length + 1); p = quoted_string; backslashes = 0; if (quote_around) *p++ = '"'; for (s = string; *s != '\0'; s++) { char c = *s; if (c == '"') { unsigned int j; for (j = backslashes + 1; j > 0; j--) *p++ = '\\'; } *p++ = c; if (c == '\\') backslashes++; else backslashes = 0; } if (quote_around) { unsigned int j; for (j = backslashes; j > 0; j--) *p++ = '\\'; *p++ = '"'; } *p = '\0'; new_argv[i] = quoted_string; } else new_argv[i] = (char *) string; } new_argv[argc] = NULL; return new_argv; } EOF ;; esac cat <<"EOF" void lt_dump_script (FILE* f) { EOF func_emit_wrapper yes | $SED -n -e ' s/^\(.\{79\}\)\(..*\)/\1\ \2/ h s/\([\\"]\)/\\\1/g s/$/\\n/ s/\([^\n]*\).*/ fputs ("\1", f);/p g D' cat <<"EOF" } EOF } # end: func_emit_cwrapperexe_src # func_win32_import_lib_p ARG # True if ARG is an import lib, as indicated by $file_magic_cmd func_win32_import_lib_p () { $debug_cmd case `eval $file_magic_cmd \"\$1\" 2>/dev/null | $SED -e 10q` in *import*) : ;; *) false ;; esac } # func_suncc_cstd_abi # !!ONLY CALL THIS FOR SUN CC AFTER $compile_command IS FULLY EXPANDED!! # Several compiler flags select an ABI that is incompatible with the # Cstd library. Avoid specifying it if any are in CXXFLAGS. func_suncc_cstd_abi () { $debug_cmd case " $compile_command " in *" -compat=g "*|*\ -std=c++[0-9][0-9]\ *|*" -library=stdcxx4 "*|*" -library=stlport4 "*) suncc_use_cstd_abi=no ;; *) suncc_use_cstd_abi=yes ;; esac } # func_mode_link arg... func_mode_link () { $debug_cmd case $host in *-*-cygwin* | *-*-mingw* | *-*-windows* | *-*-pw32* | *-*-os2* | *-cegcc*) # It is impossible to link a dll without this setting, and # we shouldn't force the makefile maintainer to figure out # what system we are compiling for in order to pass an extra # flag for every libtool invocation. # allow_undefined=no # FIXME: Unfortunately, there are problems with the above when trying # to make a dll that has undefined symbols, in which case not # even a static library is built. For now, we need to specify # -no-undefined on the libtool link line when we can be certain # that all symbols are satisfied, otherwise we get a static library. allow_undefined=yes ;; *) allow_undefined=yes ;; esac libtool_args=$nonopt base_compile="$nonopt $@" compile_command=$nonopt finalize_command=$nonopt compile_rpath= compile_rpath_tail= finalize_rpath= compile_shlibpath= finalize_shlibpath= convenience= old_convenience= deplibs= old_deplibs= compiler_flags= linker_flags= dllsearchpath= lib_search_path=`pwd` inst_prefix_dir= new_inherited_linker_flags= avoid_version=no bindir= dlfiles= dlprefiles= dlself=no export_dynamic=no export_symbols= export_symbols_regex= generated= libobjs= ltlibs= module=no no_install=no objs= os2dllname= non_pic_objects= precious_files_regex= prefer_static_libs=no preload=false prev= prevarg= release= rpath= xrpath= perm_rpath= temp_rpath= temp_rpath_tail= thread_safe=no vinfo= vinfo_number=no weak_libs= rpath_arg= single_module=$wl-single_module func_infer_tag $base_compile # We need to know -static, to get the right output filenames. for arg do case $arg in -shared) test yes != "$build_libtool_libs" \ && func_fatal_configuration "cannot build a shared library" build_old_libs=no break ;; -all-static | -static | -static-libtool-libs) case $arg in -all-static) if test yes = "$build_libtool_libs" && test -z "$link_static_flag"; then func_warning "complete static linking is impossible in this configuration" fi if test -n "$link_static_flag"; then dlopen_self=$dlopen_self_static fi prefer_static_libs=yes ;; -static) if test -z "$pic_flag" && test -n "$link_static_flag"; then dlopen_self=$dlopen_self_static fi prefer_static_libs=built ;; -static-libtool-libs) if test -z "$pic_flag" && test -n "$link_static_flag"; then dlopen_self=$dlopen_self_static fi prefer_static_libs=yes ;; esac build_libtool_libs=no build_old_libs=yes break ;; esac done # See if our shared archives depend on static archives. test -n "$old_archive_from_new_cmds" && build_old_libs=yes # Go through the arguments, transforming them on the way. while test "$#" -gt 0; do arg=$1 shift func_quote_arg pretty,unquoted "$arg" qarg=$func_quote_arg_unquoted_result func_append libtool_args " $func_quote_arg_result" # If the previous option needs an argument, assign it. if test -n "$prev"; then case $prev in output) func_append compile_command " @OUTPUT@" func_append finalize_command " @OUTPUT@" ;; esac case $prev in bindir) bindir=$arg prev= continue ;; dlfiles|dlprefiles) $preload || { # Add the symbol object into the linking commands. func_append compile_command " @SYMFILE@" func_append finalize_command " @SYMFILE@" preload=: } case $arg in *.la | *.lo) ;; # We handle these cases below. force) if test no = "$dlself"; then dlself=needless export_dynamic=yes fi prev= continue ;; self) if test dlprefiles = "$prev"; then dlself=yes elif test dlfiles = "$prev" && test yes != "$dlopen_self"; then dlself=yes else dlself=needless export_dynamic=yes fi prev= continue ;; *) if test dlfiles = "$prev"; then func_append dlfiles " $arg" else func_append dlprefiles " $arg" fi prev= continue ;; esac ;; expsyms) export_symbols=$arg test -f "$arg" \ || func_fatal_error "symbol file '$arg' does not exist" prev= continue ;; expsyms_regex) export_symbols_regex=$arg prev= continue ;; framework) case $host in *-*-darwin*) case "$deplibs " in *" $qarg.ltframework "*) ;; *) func_append deplibs " $qarg.ltframework" # this is fixed later ;; esac ;; esac prev= continue ;; inst_prefix) inst_prefix_dir=$arg prev= continue ;; mllvm) # Clang does not use LLVM to link, so we can simply discard any # '-mllvm $arg' options when doing the link step. prev= continue ;; objectlist) if test -f "$arg"; then save_arg=$arg moreargs= for fil in `cat "$save_arg"` do # func_append moreargs " $fil" arg=$fil # A libtool-controlled object. # Check to see that this really is a libtool object. if func_lalib_unsafe_p "$arg"; then pic_object= non_pic_object= # Read the .lo file func_source "$arg" if test -z "$pic_object" || test -z "$non_pic_object" || test none = "$pic_object" && test none = "$non_pic_object"; then func_fatal_error "cannot find name of object for '$arg'" fi # Extract subdirectory from the argument. func_dirname "$arg" "/" "" xdir=$func_dirname_result if test none != "$pic_object"; then # Prepend the subdirectory the object is found in. pic_object=$xdir$pic_object if test dlfiles = "$prev"; then if test yes = "$build_libtool_libs" && test yes = "$dlopen_support"; then func_append dlfiles " $pic_object" prev= continue else # If libtool objects are unsupported, then we need to preload. prev=dlprefiles fi fi # CHECK ME: I think I busted this. -Ossama if test dlprefiles = "$prev"; then # Preload the old-style object. func_append dlprefiles " $pic_object" prev= fi # A PIC object. func_append libobjs " $pic_object" arg=$pic_object fi # Non-PIC object. if test none != "$non_pic_object"; then # Prepend the subdirectory the object is found in. non_pic_object=$xdir$non_pic_object # A standard non-PIC object func_append non_pic_objects " $non_pic_object" if test -z "$pic_object" || test none = "$pic_object"; then arg=$non_pic_object fi else # If the PIC object exists, use it instead. # $xdir was prepended to $pic_object above. non_pic_object=$pic_object func_append non_pic_objects " $non_pic_object" fi else # Only an error if not doing a dry-run. if $opt_dry_run; then # Extract subdirectory from the argument. func_dirname "$arg" "/" "" xdir=$func_dirname_result func_lo2o "$arg" pic_object=$xdir$objdir/$func_lo2o_result non_pic_object=$xdir$func_lo2o_result func_append libobjs " $pic_object" func_append non_pic_objects " $non_pic_object" else func_fatal_error "'$arg' is not a valid libtool object" fi fi done else func_fatal_error "link input file '$arg' does not exist" fi arg=$save_arg prev= continue ;; os2dllname) os2dllname=$arg prev= continue ;; precious_regex) precious_files_regex=$arg prev= continue ;; release) release=-$arg prev= continue ;; rpath | xrpath) # We need an absolute path. case $arg in [\\/]* | [A-Za-z]:[\\/]*) ;; *) func_fatal_error "argument to -rpath is not absolute: $arg" ;; esac if test rpath = "$prev"; then case "$rpath " in *" $arg "*) ;; *) func_append rpath " $arg" ;; esac else case "$xrpath " in *" $arg "*) ;; *) func_append xrpath " $arg" ;; esac fi prev= continue ;; shrext) shrext_cmds=$arg prev= continue ;; weak) func_append weak_libs " $arg" prev= continue ;; xassembler) func_append compiler_flags " -Xassembler $qarg" prev= func_append compile_command " -Xassembler $qarg" func_append finalize_command " -Xassembler $qarg" continue ;; xcclinker) func_append linker_flags " $qarg" func_append compiler_flags " $qarg" prev= func_append compile_command " $qarg" func_append finalize_command " $qarg" continue ;; xcompiler) func_append compiler_flags " $qarg" prev= func_append compile_command " $qarg" func_append finalize_command " $qarg" continue ;; xlinker) func_append linker_flags " $qarg" func_append compiler_flags " $wl$qarg" prev= func_append compile_command " $wl$qarg" func_append finalize_command " $wl$qarg" continue ;; *) eval "$prev=\"\$arg\"" prev= continue ;; esac fi # test -n "$prev" prevarg=$arg case $arg in -all-static) if test -n "$link_static_flag"; then # See comment for -static flag below, for more details. func_append compile_command " $link_static_flag" func_append finalize_command " $link_static_flag" fi continue ;; -allow-undefined) # FIXME: remove this flag sometime in the future. func_fatal_error "'-allow-undefined' must not be used because it is the default" ;; -avoid-version) avoid_version=yes continue ;; -bindir) prev=bindir continue ;; -dlopen) prev=dlfiles continue ;; -dlpreopen) prev=dlprefiles continue ;; -export-dynamic) export_dynamic=yes continue ;; -export-symbols | -export-symbols-regex) if test -n "$export_symbols" || test -n "$export_symbols_regex"; then func_fatal_error "more than one -exported-symbols argument is not allowed" fi if test X-export-symbols = "X$arg"; then prev=expsyms else prev=expsyms_regex fi continue ;; -framework) prev=framework continue ;; -inst-prefix-dir) prev=inst_prefix continue ;; # The native IRIX linker understands -LANG:*, -LIST:* and -LNO:* # so, if we see these flags be careful not to treat them like -L -L[A-Z][A-Z]*:*) case $with_gcc/$host in no/*-*-irix* | /*-*-irix*) func_append compile_command " $arg" func_append finalize_command " $arg" ;; esac continue ;; -L*) func_stripname "-L" '' "$arg" if test -z "$func_stripname_result"; then if test "$#" -gt 0; then func_fatal_error "require no space between '-L' and '$1'" else func_fatal_error "need path for '-L' option" fi fi func_resolve_sysroot "$func_stripname_result" dir=$func_resolve_sysroot_result # We need an absolute path. case $dir in [\\/]* | [A-Za-z]:[\\/]*) ;; *) absdir=`cd "$dir" && pwd` test -z "$absdir" && \ func_fatal_error "cannot determine absolute directory name of '$dir'" dir=$absdir ;; esac case "$deplibs " in *" -L$dir "* | *" $arg "*) # Will only happen for absolute or sysroot arguments ;; *) # Preserve sysroot, but never include relative directories case $dir in [\\/]* | [A-Za-z]:[\\/]* | =*) func_append deplibs " $arg" ;; *) func_append deplibs " -L$dir" ;; esac func_append lib_search_path " $dir" ;; esac case $host in *-*-cygwin* | *-*-mingw* | *-*-windows* | *-*-pw32* | *-*-os2* | *-cegcc*) testbindir=`$ECHO "$dir" | $SED 's*/lib$*/bin*'` case :$dllsearchpath: in *":$dir:"*) ;; ::) dllsearchpath=$dir;; *) func_append dllsearchpath ":$dir";; esac case :$dllsearchpath: in *":$testbindir:"*) ;; ::) dllsearchpath=$testbindir;; *) func_append dllsearchpath ":$testbindir";; esac ;; esac continue ;; -l*) if test X-lc = "X$arg" || test X-lm = "X$arg"; then case $host in *-*-cygwin* | *-*-mingw* | *-*-windows* | *-*-pw32* | *-*-beos* | *-cegcc* | *-*-haiku*) # These systems don't actually have a C or math library (as such) continue ;; *-*-os2*) # These systems don't actually have a C library (as such) test X-lc = "X$arg" && continue ;; *-*-openbsd* | *-*-freebsd* | *-*-dragonfly* | *-*-midnightbsd*) # Do not include libc due to us having libc/libc_r. test X-lc = "X$arg" && continue ;; *-*-rhapsody* | *-*-darwin1.[012]) # Rhapsody C and math libraries are in the System framework func_append deplibs " System.ltframework" continue ;; *-*-sco3.2v5* | *-*-sco5v6*) # Causes problems with __ctype test X-lc = "X$arg" && continue ;; *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*) # Compiler inserts libc in the correct place for threads to work test X-lc = "X$arg" && continue ;; esac elif test X-lc_r = "X$arg"; then case $host in *-*-openbsd* | *-*-freebsd* | *-*-dragonfly* | *-*-midnightbsd*) # Do not include libc_r directly, use -pthread flag. continue ;; esac fi func_append deplibs " $arg" continue ;; -mllvm) prev=mllvm continue ;; -module) module=yes continue ;; # Tru64 UNIX uses -model [arg] to determine the layout of C++ # classes, name mangling, and exception handling. # Darwin uses the -arch flag to determine output architecture. # -q